Merge "Readd predictive back developer setting" into main
diff --git a/AndroidManifest.xml b/AndroidManifest.xml
index 2b49148..19927a2 100644
--- a/AndroidManifest.xml
+++ b/AndroidManifest.xml
@@ -138,6 +138,7 @@
     <uses-permission android:name="android.permission.CUSTOMIZE_SYSTEM_UI" />
     <uses-permission android:name="android.permission.REMAP_MODIFIER_KEYS" />
     <uses-permission android:name="android.permission.ACCESS_GPU_SERVICE" />
+    <uses-permission android:name="android.permission.REQUEST_INSTALL_PACKAGES" />
 
     <application
             android:name=".SettingsApplication"
@@ -491,6 +492,17 @@
                 android:value="@string/menu_key_display"/>
         </activity>
 
+        <activity android:name=".Settings$ScreenTimeoutActivity"
+                  android:label="@string/screen_timeout"
+                  android:exported="true">
+            <intent-filter>
+                <action android:name="android.settings.SCREEN_TIMEOUT_SETTINGS" />
+                <category android:name="android.intent.category.DEFAULT" />
+            </intent-filter>
+            <meta-data android:name="com.android.settings.FRAGMENT_CLASS"
+                       android:value="com.android.settings.display.ScreenTimeoutSettings"/>
+        </activity>
+
         <activity
             android:name="Settings$ConfigureWifiSettingsActivity"
             android:label="@string/wifi_configure_settings_preference_title"
diff --git a/aconfig/settings_telephony_flag_declarations.aconfig b/aconfig/settings_telephony_flag_declarations.aconfig
index 68e313b..0999a7b 100644
--- a/aconfig/settings_telephony_flag_declarations.aconfig
+++ b/aconfig/settings_telephony_flag_declarations.aconfig
@@ -6,3 +6,10 @@
     description: "Stop honoring CarrierConfigManager.KEY_HIDE_ENABLE_2G. Allow 2G toggle cannot be hidden from users by carriers."
     bug: "300248708"
 }
+
+flag {
+    name: "is_dual_sim_onboarding_enabled"
+    namespace: "settings_experience"
+    description: "Control the Dual SIM onobarding feature"
+    bug: "298898436"
+}
diff --git a/res/values/config.xml b/res/values/config.xml
index c30a047..d084ff9 100644
--- a/res/values/config.xml
+++ b/res/values/config.xml
@@ -258,9 +258,6 @@
         com.android.settings.intelligence
     </string>
 
-    <!-- Whether the confirmation for sim deletion is defaulted to be on or off-->
-    <bool name="config_sim_deletion_confirmation_default_on">false</bool>
-
     <!-- Package Installer package name -->
     <string name="config_package_installer_package_name" translatable="false">
         com.android.packageinstaller
diff --git a/res/values/strings.xml b/res/values/strings.xml
index 33cae6d..01f2525 100644
--- a/res/values/strings.xml
+++ b/res/values/strings.xml
@@ -3898,6 +3898,8 @@
     <string name="force_stop">Force stop</string>
     <!-- Manage applications, text label for button to archive an application. Archiving means uninstalling the app without deleting user's personal data and replacing the app with a stub app with minimum size. So, the user can unarchive the app later and not lose any personal data. -->
     <string name="archive">Archive</string>
+    <!-- Manage applications, text label for button to restore an application. Restoring means installing the archived app. -->
+    <string name="restore">Restore</string>
     <!-- Manage applications, individual application info screen,label under Storage heading.  The total storage space taken up by this app. -->
     <string name="total_size_label">Total</string>
     <!-- Manage applications, individual application info screen, label under Storage heading. The amount of space taken up by the application itself (for example, the java compield files and things like that) -->
@@ -4012,6 +4014,12 @@
     <string name="archiving_failed">Archiving failed</string>
     <!-- Toast message when archiving an app succeeded. -->
     <string name="archiving_succeeded">Archived <xliff:g id="package_label" example="Translate">%1$s</xliff:g></string>
+    <!-- Toast message when restoring an app failed. -->
+    <string name="restoring_failed">Restoring failed</string>
+    <!-- Toast message when restoring an app succeeded. -->
+    <string name="restoring_succeeded">Restored <xliff:g id="package_label" example="Translate">%1$s</xliff:g></string>
+    <!-- Toast message when restoring an app has started. -->
+    <string name="restoring_in_progress">Restoring <xliff:g id="package_label" example="Translate">%1$s</xliff:g></string>
 
     <!-- Text of pop up message if the request for a "migrate primary storage" operation
          (see storage_menu_migrate) is denied as another is already in progress. [CHAR LIMIT=75] -->
@@ -9107,6 +9115,12 @@
     <!-- Summary of the switch preference that controls whether the system will pause app activity when the app has not been used for months [CHAR LIMIT=NONE]-->
     <string name="unused_apps_switch_summary">Remove permissions, delete temporary files, and stop notifications</string>
 
+    <!-- Label of a switch preference that controls whether the system will pause app activity when the app has not been used for a while [CHAR LIMIT=40]-->
+    <string name="unused_apps_switch_v2">Manage app if unused</string>
+
+    <!-- Summary of the switch preference that controls whether the system will pause app activity when the app has not been used for a while [CHAR LIMIT=NONE]-->
+    <string name="unused_apps_switch_summary_v2">Remove permissions, delete temporary files, stop notifications, and archive the app</string>
+
     <!-- Label for showing all apps in list [CHAR LIMIT=30] -->
     <string name="filter_all_apps">All apps</string>
     <!-- Label for showing enabled apps in list [CHAR LIMIT=30] -->
diff --git a/res/values/styles.xml b/res/values/styles.xml
index 2c928ff..8df990b 100644
--- a/res/values/styles.xml
+++ b/res/values/styles.xml
@@ -438,11 +438,10 @@
     </style>
 
     <style name="DeviceAudioSharingText">
-        <item name="android:textAlignment">viewStart</item>
+        <item name="android:textAlignment">center</item>
         <item name="android:fontFamily">@*android:string/config_bodyFontFamily</item>
         <item name="android:textSize">14sp</item>
         <item name="android:textColor">?android:attr/textColorPrimary</item>
-        <item name="android:paddingBottom">24dp</item>
     </style>
 
     <style name="ContextualCardStyle">
diff --git a/res/xml/audio_stream_details_fragment.xml b/res/xml/audio_stream_details_fragment.xml
new file mode 100644
index 0000000..2a84939
--- /dev/null
+++ b/res/xml/audio_stream_details_fragment.xml
@@ -0,0 +1,36 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!--
+  ~ 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.
+  -->
+
+<PreferenceScreen
+    xmlns:android="http://schemas.android.com/apk/res/android"
+    xmlns:settings="http://schemas.android.com/apk/res-auto"
+    android:title="Audio stream details">
+
+    <com.android.settingslib.widget.LayoutPreference
+        android:key="audio_stream_header"
+        android:layout="@layout/settings_entity_header"
+        android:selectable="false"
+        settings:allowDividerBelow="true"
+        settings:searchable="false"
+        settings:controller="com.android.settings.connecteddevice.audiosharing.audiostreams.AudioStreamHeaderController" />
+
+    <com.android.settingslib.widget.ActionButtonsPreference
+        android:key="audio_stream_button"
+        settings:allowDividerBelow="true"
+        settings:controller="com.android.settings.connecteddevice.audiosharing.audiostreams.AudioStreamButtonController" />
+
+</PreferenceScreen>
diff --git a/res/xml/more_security_privacy_settings.xml b/res/xml/more_security_privacy_settings.xml
index 822b03d..5cd60f0 100644
--- a/res/xml/more_security_privacy_settings.xml
+++ b/res/xml/more_security_privacy_settings.xml
@@ -204,14 +204,6 @@
         settings:keywords="@string/keywords_app_pinning"
         settings:controller="com.android.settings.security.ScreenPinningPreferenceController" />
 
-    <SwitchPreferenceCompat
-        android:order="290"
-        android:key="confirm_sim_deletion"
-        android:title="@string/confirm_sim_deletion_title"
-        android:summary="@string/confirm_sim_deletion_description"
-        settings:isPreferenceVisible="@bool/config_show_sim_info"
-        settings:controller="com.android.settings.security.ConfirmSimDeletionPreferenceController" />
-
     <Preference
         android:order="300"
         android:id="@+id/memtag_page"
diff --git a/res/xml/security_advanced_settings.xml b/res/xml/security_advanced_settings.xml
index 89834b4..c220e99 100644
--- a/res/xml/security_advanced_settings.xml
+++ b/res/xml/security_advanced_settings.xml
@@ -106,14 +106,6 @@
         settings:keywords="@string/keywords_app_pinning"
         settings:controller="com.android.settings.security.ScreenPinningPreferenceController" />
 
-    <SwitchPreferenceCompat
-        android:order="290"
-        android:key="confirm_sim_deletion"
-        android:title="@string/confirm_sim_deletion_title"
-        android:summary="@string/confirm_sim_deletion_description"
-        settings:isPreferenceVisible="@bool/config_show_sim_info"
-        settings:controller="com.android.settings.security.ConfirmSimDeletionPreferenceController" />
-
   <com.android.settingslib.RestrictedPreference
         android:order="300"
         android:id="@+id/memtag_page"
diff --git a/src/com/android/settings/Settings.java b/src/com/android/settings/Settings.java
index 68ae0ae..86baba4 100644
--- a/src/com/android/settings/Settings.java
+++ b/src/com/android/settings/Settings.java
@@ -478,4 +478,6 @@
     public static class OneHandedSettingsActivity extends SettingsActivity { /* empty */ }
 
     public static class PreviouslyConnectedDeviceActivity extends SettingsActivity { /* empty */ }
+
+    public static class ScreenTimeoutActivity extends SettingsActivity { /* empty */ }
 }
diff --git a/src/com/android/settings/accessibility/RestrictedPreferenceHelper.java b/src/com/android/settings/accessibility/RestrictedPreferenceHelper.java
index dfa2f33..6f21fb8 100644
--- a/src/com/android/settings/accessibility/RestrictedPreferenceHelper.java
+++ b/src/com/android/settings/accessibility/RestrictedPreferenceHelper.java
@@ -234,6 +234,32 @@
         // permittedServices null means all accessibility services are allowed.
         boolean serviceAllowed = permittedServices == null || permittedServices.contains(
                 preference.getPackageName());
+
+        if (android.security.Flags.extendEcmToAllSettings()) {
+            preference.checkEcmRestrictionAndSetDisabled(
+                    AppOpsManager.OPSTR_BIND_ACCESSIBILITY_SERVICE,
+                    preference.getPackageName(), preference.getUid());
+            if (preference.isDisabledByEcm()) {
+                serviceAllowed = false;
+            }
+
+            if (serviceAllowed || serviceEnabled) {
+                preference.setEnabled(true);
+            } else {
+                // Disable accessibility service that are not permitted.
+                final RestrictedLockUtils.EnforcedAdmin admin =
+                        RestrictedLockUtilsInternal.checkIfAccessibilityServiceDisallowed(
+                                mContext, preference.getPackageName(), UserHandle.myUserId());
+
+                if (admin != null) {
+                    preference.setDisabledByAdmin(admin);
+                } else if (!preference.isDisabledByEcm()) {
+                    preference.setEnabled(false);
+                }
+            }
+            return;
+        }
+
         boolean appOpsAllowed;
         if (serviceAllowed) {
             try {
diff --git a/src/com/android/settings/applications/specialaccess/notificationaccess/ApprovalPreferenceController.java b/src/com/android/settings/applications/specialaccess/notificationaccess/ApprovalPreferenceController.java
index e3816bf..fb78e3e 100644
--- a/src/com/android/settings/applications/specialaccess/notificationaccess/ApprovalPreferenceController.java
+++ b/src/com/android/settings/applications/specialaccess/notificationaccess/ApprovalPreferenceController.java
@@ -41,6 +41,8 @@
     private PreferenceFragmentCompat mParent;
     private NotificationManager mNm;
     private PackageManager mPm;
+    // The appOp representing this preference
+    private String mAppOpStr;
 
     public ApprovalPreferenceController(Context context, String key) {
         super(context, key);
@@ -71,6 +73,14 @@
         return this;
     }
 
+    /**
+     * Set the associated appOp for the Setting
+     */
+    public ApprovalPreferenceController setAppOpStr(String appOpStr) {
+        mAppOpStr = appOpStr;
+        return this;
+    }
+
     @Override
     public int getAvailabilityStatus() {
         return AVAILABLE;
@@ -107,8 +117,20 @@
                 return false;
             }
         });
-        preference.updateState(
-                mCn.getPackageName(), mPkgInfo.applicationInfo.uid, isAllowedCn, isEnabled);
+
+        if (android.security.Flags.extendEcmToAllSettings()) {
+            if (!isAllowedCn && !isEnabled) {
+                preference.setEnabled(false);
+            } else if (isEnabled) {
+                preference.setEnabled(true);
+            } else {
+                preference.checkEcmRestrictionAndSetDisabled(mAppOpStr,
+                        mCn.getPackageName(), mPkgInfo.applicationInfo.uid);
+            }
+        } else {
+            preference.updateState(
+                    mCn.getPackageName(), mPkgInfo.applicationInfo.uid, isAllowedCn, isEnabled);
+        }
     }
 
     public void disable(final ComponentName cn) {
diff --git a/src/com/android/settings/applications/specialaccess/notificationaccess/NotificationAccessDetails.java b/src/com/android/settings/applications/specialaccess/notificationaccess/NotificationAccessDetails.java
index 17dabe4..89767dd 100644
--- a/src/com/android/settings/applications/specialaccess/notificationaccess/NotificationAccessDetails.java
+++ b/src/com/android/settings/applications/specialaccess/notificationaccess/NotificationAccessDetails.java
@@ -21,6 +21,7 @@
 import static com.android.settings.applications.AppInfoBase.ARG_PACKAGE_NAME;
 
 import android.Manifest;
+import android.app.AppOpsManager;
 import android.app.NotificationManager;
 import android.app.settings.SettingsEnums;
 import android.companion.ICompanionDeviceManager;
@@ -102,6 +103,7 @@
                 .setCn(mComponentName)
                 .setNm(context.getSystemService(NotificationManager.class))
                 .setPm(mPm)
+                .setAppOpStr(AppOpsManager.OPSTR_ACCESS_NOTIFICATIONS)
                 .setParent(this);
         use(HeaderPreferenceController.class)
                 .setFragment(this)
diff --git a/src/com/android/settings/biometrics/fingerprint/FingerprintEnrollFindSensor.java b/src/com/android/settings/biometrics/fingerprint/FingerprintEnrollFindSensor.java
index b71330a..059173c 100644
--- a/src/com/android/settings/biometrics/fingerprint/FingerprintEnrollFindSensor.java
+++ b/src/com/android/settings/biometrics/fingerprint/FingerprintEnrollFindSensor.java
@@ -290,6 +290,17 @@
     }
 
     @Override
+    protected Intent getFingerprintEnrollingIntent() {
+        final Intent ret = super.getFingerprintEnrollingIntent();
+        if (Flags.udfpsEnrollCalibration()) {
+            if (mCalibrator != null) {
+                ret.putExtras(mCalibrator.getExtrasForNextIntent(true));
+            }
+        }
+        return ret;
+    }
+
+    @Override
     public void onBackPressed() {
         stopLookingForFingerprint();
         super.onBackPressed();
diff --git a/src/com/android/settings/biometrics/fingerprint/FingerprintEnrollIntroduction.java b/src/com/android/settings/biometrics/fingerprint/FingerprintEnrollIntroduction.java
index bd52b64..aef3c06 100644
--- a/src/com/android/settings/biometrics/fingerprint/FingerprintEnrollIntroduction.java
+++ b/src/com/android/settings/biometrics/fingerprint/FingerprintEnrollIntroduction.java
@@ -385,7 +385,7 @@
         }
         if (Flags.udfpsEnrollCalibration()) {
             if (mCalibrator != null) {
-                intent.putExtras(mCalibrator.getExtrasForNextIntent());
+                intent.putExtras(mCalibrator.getExtrasForNextIntent(false));
             }
         }
         return intent;
diff --git a/src/com/android/settings/biometrics/fingerprint/UdfpsEnrollCalibrator.kt b/src/com/android/settings/biometrics/fingerprint/UdfpsEnrollCalibrator.kt
index 9809bcc..c54c6b5 100644
--- a/src/com/android/settings/biometrics/fingerprint/UdfpsEnrollCalibrator.kt
+++ b/src/com/android/settings/biometrics/fingerprint/UdfpsEnrollCalibrator.kt
@@ -6,7 +6,7 @@
 
 interface UdfpsEnrollCalibrator {
 
-    val extrasForNextIntent: Bundle
+    fun getExtrasForNextIntent(isEnrolling: Boolean): Bundle
 
     fun onSaveInstanceState(outState: Bundle)
 
diff --git a/src/com/android/settings/connecteddevice/AvailableMediaDeviceGroupController.java b/src/com/android/settings/connecteddevice/AvailableMediaDeviceGroupController.java
index c095fee..fc3493c 100644
--- a/src/com/android/settings/connecteddevice/AvailableMediaDeviceGroupController.java
+++ b/src/com/android/settings/connecteddevice/AvailableMediaDeviceGroupController.java
@@ -17,11 +17,16 @@
 
 import static com.android.settingslib.Utils.isAudioModeOngoingCall;
 
+import android.bluetooth.BluetoothDevice;
+import android.bluetooth.BluetoothLeBroadcastAssistant;
+import android.bluetooth.BluetoothLeBroadcastMetadata;
+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;
 import androidx.annotation.VisibleForTesting;
 import androidx.fragment.app.FragmentManager;
 import androidx.preference.Preference;
@@ -33,36 +38,94 @@
 import com.android.settings.bluetooth.AvailableMediaBluetoothDeviceUpdater;
 import com.android.settings.bluetooth.BluetoothDeviceUpdater;
 import com.android.settings.bluetooth.Utils;
+import com.android.settings.connecteddevice.audiosharing.AudioSharingUtils;
 import com.android.settings.core.BasePreferenceController;
 import com.android.settings.dashboard.DashboardFragment;
 import com.android.settingslib.bluetooth.BluetoothCallback;
+import com.android.settingslib.bluetooth.BluetoothUtils;
 import com.android.settingslib.bluetooth.CachedBluetoothDevice;
+import com.android.settingslib.bluetooth.LocalBluetoothLeBroadcastAssistant;
 import com.android.settingslib.bluetooth.LocalBluetoothManager;
 import com.android.settingslib.core.lifecycle.LifecycleObserver;
 import com.android.settingslib.core.lifecycle.events.OnStart;
 import com.android.settingslib.core.lifecycle.events.OnStop;
 
+import java.util.concurrent.Executor;
+import java.util.concurrent.Executors;
+
 /**
- * Controller to maintain the {@link androidx.preference.PreferenceGroup} for all
- * available media devices. It uses {@link DevicePreferenceCallback}
- * to add/remove {@link Preference}
+ * Controller to maintain the {@link androidx.preference.PreferenceGroup} for all available media
+ * devices. It uses {@link DevicePreferenceCallback} to add/remove {@link Preference}
  */
 public class AvailableMediaDeviceGroupController extends BasePreferenceController
         implements LifecycleObserver, OnStart, OnStop, DevicePreferenceCallback, BluetoothCallback {
+    private static final boolean DEBUG = BluetoothUtils.D;
 
     private static final String TAG = "AvailableMediaDeviceGroupController";
     private static final String KEY = "available_device_list";
 
-    @VisibleForTesting
-    PreferenceGroup mPreferenceGroup;
-    @VisibleForTesting
-    LocalBluetoothManager mLocalBluetoothManager;
+    @VisibleForTesting PreferenceGroup mPreferenceGroup;
+    @VisibleForTesting LocalBluetoothManager mLocalBluetoothManager;
+    private final Executor mExecutor;
     private BluetoothDeviceUpdater mBluetoothDeviceUpdater;
     private FragmentManager mFragmentManager;
+    private BluetoothLeBroadcastAssistant.Callback mAssistantCallback =
+            new BluetoothLeBroadcastAssistant.Callback() {
+                @Override
+                public void onSearchStarted(int reason) {}
+
+                @Override
+                public void onSearchStartFailed(int reason) {}
+
+                @Override
+                public void onSearchStopped(int reason) {}
+
+                @Override
+                public void onSearchStopFailed(int reason) {}
+
+                @Override
+                public void onSourceFound(@NonNull BluetoothLeBroadcastMetadata source) {}
+
+                @Override
+                public void onSourceAdded(@NonNull BluetoothDevice sink, int sourceId, int reason) {
+                    mBluetoothDeviceUpdater.forceUpdate();
+                }
+
+                @Override
+                public void onSourceAddFailed(
+                        @NonNull BluetoothDevice sink,
+                        @NonNull BluetoothLeBroadcastMetadata source,
+                        int reason) {}
+
+                @Override
+                public void onSourceModified(
+                        @NonNull BluetoothDevice sink, int sourceId, int reason) {}
+
+                @Override
+                public void onSourceModifyFailed(
+                        @NonNull BluetoothDevice sink, int sourceId, int reason) {}
+
+                @Override
+                public void onSourceRemoved(
+                        @NonNull BluetoothDevice sink, int sourceId, int reason) {
+                    mBluetoothDeviceUpdater.forceUpdate();
+                }
+
+                @Override
+                public void onSourceRemoveFailed(
+                        @NonNull BluetoothDevice sink, int sourceId, int reason) {}
+
+                @Override
+                public void onReceiveStateChanged(
+                        BluetoothDevice sink,
+                        int sourceId,
+                        BluetoothLeBroadcastReceiveState state) {}
+            };
 
     public AvailableMediaDeviceGroupController(Context context) {
         super(context, KEY);
         mLocalBluetoothManager = Utils.getLocalBtManager(mContext);
+        mExecutor = Executors.newSingleThreadExecutor();
     }
 
     @Override
@@ -71,6 +134,18 @@
             Log.e(TAG, "onStart() Bluetooth is not supported on this device");
             return;
         }
+        if (AudioSharingUtils.isFeatureEnabled()) {
+            LocalBluetoothLeBroadcastAssistant assistant =
+                    mLocalBluetoothManager
+                            .getProfileManager()
+                            .getLeAudioBroadcastAssistantProfile();
+            if (assistant != null) {
+                if (DEBUG) {
+                    Log.d(TAG, "onStart() Register callbacks for assistant.");
+                }
+                assistant.registerServiceCallBack(mExecutor, mAssistantCallback);
+            }
+        }
         mBluetoothDeviceUpdater.registerCallback();
         mLocalBluetoothManager.getEventManager().registerCallback(this);
         mBluetoothDeviceUpdater.refreshPreference();
@@ -82,6 +157,18 @@
             Log.e(TAG, "onStop() Bluetooth is not supported on this device");
             return;
         }
+        if (AudioSharingUtils.isFeatureEnabled()) {
+            LocalBluetoothLeBroadcastAssistant assistant =
+                    mLocalBluetoothManager
+                            .getProfileManager()
+                            .getLeAudioBroadcastAssistantProfile();
+            if (assistant != null) {
+                if (DEBUG) {
+                    Log.d(TAG, "onStop() Register callbacks for assistant.");
+                }
+                assistant.unregisterServiceCallBack(mAssistantCallback);
+            }
+        }
         mBluetoothDeviceUpdater.unregisterCallback();
         mLocalBluetoothManager.getEventManager().unregisterCallback(this);
     }
@@ -130,8 +217,11 @@
 
     public void init(DashboardFragment fragment) {
         mFragmentManager = fragment.getParentFragmentManager();
-        mBluetoothDeviceUpdater = new AvailableMediaBluetoothDeviceUpdater(fragment.getContext(),
-                AvailableMediaDeviceGroupController.this, fragment.getMetricsCategory());
+        mBluetoothDeviceUpdater =
+                new AvailableMediaBluetoothDeviceUpdater(
+                        fragment.getContext(),
+                        AvailableMediaDeviceGroupController.this,
+                        fragment.getMetricsCategory());
     }
 
     @VisibleForTesting
@@ -157,20 +247,20 @@
         }
 
         if (bluetoothProfile == BluetoothProfile.HEARING_AID) {
-            HearingAidUtils.launchHearingAidPairingDialog(mFragmentManager, activeDevice,
-                    getMetricsCategory());
+            HearingAidUtils.launchHearingAidPairingDialog(
+                    mFragmentManager, activeDevice, getMetricsCategory());
         }
     }
 
     private void updateTitle() {
         if (isAudioModeOngoingCall(mContext)) {
             // in phone call
-            mPreferenceGroup.
-                    setTitle(mContext.getString(R.string.connected_device_call_device_title));
+            mPreferenceGroup.setTitle(
+                    mContext.getString(R.string.connected_device_call_device_title));
         } else {
             // without phone call
-            mPreferenceGroup.
-                    setTitle(mContext.getString(R.string.connected_device_media_device_title));
+            mPreferenceGroup.setTitle(
+                    mContext.getString(R.string.connected_device_media_device_title));
         }
     }
 }
diff --git a/src/com/android/settings/connecteddevice/audiosharing/AudioSharingDevicePreferenceController.java b/src/com/android/settings/connecteddevice/audiosharing/AudioSharingDevicePreferenceController.java
index 9329cc29..70859c2 100644
--- a/src/com/android/settings/connecteddevice/audiosharing/AudioSharingDevicePreferenceController.java
+++ b/src/com/android/settings/connecteddevice/audiosharing/AudioSharingDevicePreferenceController.java
@@ -47,6 +47,9 @@
 import com.android.settingslib.bluetooth.LocalBluetoothLeBroadcast;
 import com.android.settingslib.bluetooth.LocalBluetoothLeBroadcastAssistant;
 import com.android.settingslib.bluetooth.LocalBluetoothManager;
+import com.android.settingslib.utils.ThreadUtils;
+
+import com.google.common.collect.ImmutableList;
 
 import java.util.ArrayList;
 import java.util.List;
@@ -260,6 +263,9 @@
             return;
         }
         mLocalBtManager.getEventManager().registerCallback(this);
+        if (DEBUG) {
+            Log.d(TAG, "onStart() Register callbacks for broadcast and assistant.");
+        }
         mBroadcast.registerServiceCallBack(mExecutor, mBroadcastCallback);
         mAssistant.registerServiceCallBack(mExecutor, mBroadcastAssistantCallback);
         mBluetoothDeviceUpdater.registerCallback();
@@ -281,15 +287,11 @@
             return;
         }
         mLocalBtManager.getEventManager().unregisterCallback(this);
-        // TODO: verify the reason for failing to unregister
-        try {
-            mBroadcast.unregisterServiceCallBack(mBroadcastCallback);
-            mAssistant.unregisterServiceCallBack(mBroadcastAssistantCallback);
-        } catch (IllegalArgumentException e) {
-            Log.e(
-                    TAG,
-                    "Fail to unregister broadcast or assistant callback due to " + e.getMessage());
+        if (DEBUG) {
+            Log.d(TAG, "onStop() Unregister callbacks for broadcast and assistant.");
         }
+        mBroadcast.unregisterServiceCallBack(mBroadcastCallback);
+        mAssistant.unregisterServiceCallBack(mBroadcastAssistantCallback);
         mBluetoothDeviceUpdater.unregisterCallback();
     }
 
@@ -358,6 +360,28 @@
                     "Ignore onProfileConnectionStateChanged, no broadcast or assistant supported");
             return;
         }
+        var unused =
+                ThreadUtils.postOnBackgroundThread(
+                        () -> handleOnProfileStateChanged(cachedDevice, bluetoothProfile));
+    }
+
+    /**
+     * Initialize the controller.
+     *
+     * @param fragment The fragment to provide the context and metrics category for {@link
+     *     AudioSharingBluetoothDeviceUpdater} and provide the host for dialogs.
+     */
+    public void init(DashboardFragment fragment) {
+        mFragment = fragment;
+        mBluetoothDeviceUpdater =
+                new AudioSharingBluetoothDeviceUpdater(
+                        fragment.getContext(),
+                        AudioSharingDevicePreferenceController.this,
+                        fragment.getMetricsCategory());
+    }
+
+    private void handleOnProfileStateChanged(
+            @NonNull CachedBluetoothDevice cachedDevice, int bluetoothProfile) {
         boolean isLeAudioSupported = isLeAudioSupported(cachedDevice);
         // For eligible (LE audio) remote device, we only check its connected LE audio profile.
         if (isLeAudioSupported && bluetoothProfile != BluetoothProfile.LE_AUDIO) {
@@ -384,120 +408,143 @@
         }
         if (!isLeAudioSupported) {
             // Handle connected ineligible (non LE audio) remote device
-            if (isBroadcasting()) {
-                // Show stop audio sharing dialog when an ineligible (non LE audio) remote device
-                // connected during a sharing session.
-                closeOpeningDialogs();
-                AudioSharingStopDialogFragment.show(
-                        mFragment,
-                        cachedDevice.getName(),
-                        () -> mBroadcast.stopBroadcast(mBroadcast.getLatestBroadcastId()));
-            } else {
-                // Do nothing for ineligible (non LE audio) remote device when no sharing session.
-                if (DEBUG) {
-                    Log.d(
-                            TAG,
-                            "Ignore onProfileConnectionStateChanged for non LE audio without"
-                                + " sharing session");
-                }
-            }
+            handleOnProfileStateChangedForNonLeAudioDevice(cachedDevice);
         } else {
-            Map<Integer, List<CachedBluetoothDevice>> groupedDevices =
-                    AudioSharingUtils.fetchConnectedDevicesByGroupId(mLocalBtManager);
             // Handle connected eligible (LE audio) remote device
-            if (isBroadcasting()) {
-                // Show audio sharing switch or join dialog according to device count in the sharing
-                // session.
-                ArrayList<AudioSharingDeviceItem> deviceItemsInSharingSession =
-                        AudioSharingUtils.buildOrderedConnectedLeadAudioSharingDeviceItem(
-                                mLocalBtManager, groupedDevices, /* filterByInSharing= */ true);
-                // Show audio sharing switch dialog when the third eligible (LE audio) remote device
-                // connected during a sharing session.
-                if (deviceItemsInSharingSession.size() >= 2) {
-                    closeOpeningDialogs();
-                    AudioSharingDisconnectDialogFragment.show(
-                            mFragment,
-                            deviceItemsInSharingSession,
-                            cachedDevice.getName(),
-                            (AudioSharingDeviceItem item) -> {
-                                // Remove all sources from the device user clicked
-                                for (CachedBluetoothDevice device :
-                                        groupedDevices.get(item.getGroupId())) {
-                                    for (BluetoothLeBroadcastReceiveState source :
-                                            mAssistant.getAllSources(device.getDevice())) {
-                                        mAssistant.removeSource(
-                                                device.getDevice(), source.getSourceId());
-                                    }
-                                }
-                                // Add current broadcast to the latest connected device
-                                mAssistant.addSource(
-                                        cachedDevice.getDevice(),
-                                        mBroadcast.getLatestBluetoothLeBroadcastMetadata(),
-                                        /* isGroupOp= */ true);
-                            });
-                } else {
-                    // Show audio sharing join dialog when the first or second eligible (LE audio)
-                    // remote device connected during a sharing session.
-                    closeOpeningDialogs();
-                    AudioSharingJoinDialogFragment.show(
-                            mFragment,
-                            deviceItemsInSharingSession,
-                            cachedDevice.getName(),
-                            () -> {
-                                // Add current broadcast to the latest connected device
-                                mAssistant.addSource(
-                                        cachedDevice.getDevice(),
-                                        mBroadcast.getLatestBluetoothLeBroadcastMetadata(),
-                                        /* isGroupOp= */ true);
-                            });
-                }
-            } else {
-                ArrayList<AudioSharingDeviceItem> deviceItems = new ArrayList<>();
-                for (List<CachedBluetoothDevice> devices : groupedDevices.values()) {
-                    // Use random device in the group within the sharing session to
-                    // represent the group.
-                    CachedBluetoothDevice device = devices.get(0);
-                    if (device.getGroupId() == cachedDevice.getGroupId()) {
-                        continue;
-                    }
-                    deviceItems.add(AudioSharingUtils.buildAudioSharingDeviceItem(device));
-                }
-                // Show audio sharing join dialog when the second eligible (LE audio) remote device
-                // connect and no sharing session.
-                if (deviceItems.size() == 1) {
-                    closeOpeningDialogs();
-                    AudioSharingJoinDialogFragment.show(
-                            mFragment,
-                            deviceItems,
-                            cachedDevice.getName(),
-                            () -> {
-                                mTargetSinks = new ArrayList<>();
-                                for (List<CachedBluetoothDevice> devices :
-                                        groupedDevices.values()) {
-                                    for (CachedBluetoothDevice device : devices) {
-                                        mTargetSinks.add(device.getDevice());
-                                    }
-                                }
-                                mBroadcast.startBroadcast("test", null);
-                            });
-                }
+            handleOnProfileStateChangedForLeAudioDevice(cachedDevice);
+        }
+    }
+
+    private void handleOnProfileStateChangedForNonLeAudioDevice(
+            @NonNull CachedBluetoothDevice cachedDevice) {
+        if (isBroadcasting()) {
+            // Show stop audio sharing dialog when an ineligible (non LE audio) remote device
+            // connected during a sharing session.
+            ThreadUtils.postOnMainThread(
+                    () -> {
+                        closeOpeningDialogs();
+                        AudioSharingStopDialogFragment.show(
+                                mFragment,
+                                cachedDevice.getName(),
+                                () -> mBroadcast.stopBroadcast(mBroadcast.getLatestBroadcastId()));
+                    });
+        } else {
+            // Do nothing for ineligible (non LE audio) remote device when no sharing session.
+            if (DEBUG) {
+                Log.d(
+                        TAG,
+                        "Ignore onProfileConnectionStateChanged for non LE audio without"
+                                + " sharing session");
             }
         }
     }
 
-    /**
-     * Initialize the controller.
-     *
-     * @param fragment The fragment to provide the context and metrics category for {@link
-     *     AudioSharingBluetoothDeviceUpdater} and provide the host for dialogs.
-     */
-    public void init(DashboardFragment fragment) {
-        mFragment = fragment;
-        mBluetoothDeviceUpdater =
-                new AudioSharingBluetoothDeviceUpdater(
-                        fragment.getContext(),
-                        AudioSharingDevicePreferenceController.this,
-                        fragment.getMetricsCategory());
+    private void handleOnProfileStateChangedForLeAudioDevice(
+            @NonNull CachedBluetoothDevice cachedDevice) {
+        Map<Integer, List<CachedBluetoothDevice>> groupedDevices =
+                AudioSharingUtils.fetchConnectedDevicesByGroupId(mLocalBtManager);
+        if (isBroadcasting()) {
+            if (groupedDevices.containsKey(cachedDevice.getGroupId())
+                    && groupedDevices.get(cachedDevice.getGroupId()).stream()
+                            .anyMatch(
+                                    device ->
+                                            AudioSharingUtils.hasBroadcastSource(
+                                                    device, mLocalBtManager))) {
+                Log.d(
+                        TAG,
+                        "Automatically add another device within the same group to the sharing: "
+                                + cachedDevice.getDevice().getAnonymizedAddress());
+                addSourceToTargetDevices(ImmutableList.of(cachedDevice.getDevice()));
+                return;
+            }
+            // Show audio sharing switch or join dialog according to device count in the sharing
+            // session.
+            ArrayList<AudioSharingDeviceItem> deviceItemsInSharingSession =
+                    AudioSharingUtils.buildOrderedConnectedLeadAudioSharingDeviceItem(
+                            mLocalBtManager, groupedDevices, /* filterByInSharing= */ true);
+            // Show audio sharing switch dialog when the third eligible (LE audio) remote device
+            // connected during a sharing session.
+            if (deviceItemsInSharingSession.size() >= 2) {
+                ThreadUtils.postOnMainThread(
+                        () -> {
+                            closeOpeningDialogs();
+                            AudioSharingDisconnectDialogFragment.show(
+                                    mFragment,
+                                    deviceItemsInSharingSession,
+                                    cachedDevice.getName(),
+                                    (AudioSharingDeviceItem item) -> {
+                                        // Remove all sources from the device user clicked
+                                        if (groupedDevices.containsKey(item.getGroupId())) {
+                                            for (CachedBluetoothDevice device :
+                                                    groupedDevices.get(item.getGroupId())) {
+                                                for (BluetoothLeBroadcastReceiveState source :
+                                                        mAssistant.getAllSources(
+                                                                device.getDevice())) {
+                                                    mAssistant.removeSource(
+                                                            device.getDevice(),
+                                                            source.getSourceId());
+                                                }
+                                            }
+                                        }
+                                        // Add current broadcast to the latest connected device
+                                        mAssistant.addSource(
+                                                cachedDevice.getDevice(),
+                                                mBroadcast.getLatestBluetoothLeBroadcastMetadata(),
+                                                /* isGroupOp= */ true);
+                                    });
+                        });
+            } else {
+                // Show audio sharing join dialog when the first or second eligible (LE audio)
+                // remote device connected during a sharing session.
+                ThreadUtils.postOnMainThread(
+                        () -> {
+                            closeOpeningDialogs();
+                            AudioSharingJoinDialogFragment.show(
+                                    mFragment,
+                                    deviceItemsInSharingSession,
+                                    cachedDevice.getName(),
+                                    () -> {
+                                        // Add current broadcast to the latest connected device
+                                        mAssistant.addSource(
+                                                cachedDevice.getDevice(),
+                                                mBroadcast.getLatestBluetoothLeBroadcastMetadata(),
+                                                /* isGroupOp= */ true);
+                                    });
+                        });
+            }
+        } else {
+            ArrayList<AudioSharingDeviceItem> deviceItems = new ArrayList<>();
+            for (List<CachedBluetoothDevice> devices : groupedDevices.values()) {
+                // Use random device in the group within the sharing session to represent the group.
+                CachedBluetoothDevice device = devices.get(0);
+                if (device.getGroupId() == cachedDevice.getGroupId()) {
+                    continue;
+                }
+                deviceItems.add(AudioSharingUtils.buildAudioSharingDeviceItem(device));
+            }
+            // Show audio sharing join dialog when the second eligible (LE audio) remote
+            // device connect and no sharing session.
+            if (deviceItems.size() == 1) {
+                ThreadUtils.postOnMainThread(
+                        () -> {
+                            closeOpeningDialogs();
+                            AudioSharingJoinDialogFragment.show(
+                                    mFragment,
+                                    deviceItems,
+                                    cachedDevice.getName(),
+                                    () -> {
+                                        mTargetSinks = new ArrayList<>();
+                                        for (List<CachedBluetoothDevice> devices :
+                                                groupedDevices.values()) {
+                                            for (CachedBluetoothDevice device : devices) {
+                                                mTargetSinks.add(device.getDevice());
+                                            }
+                                        }
+                                        mBroadcast.startBroadcast("test", null);
+                                    });
+                        });
+            }
+        }
     }
 
     private boolean isLeAudioSupported(CachedBluetoothDevice cachedDevice) {
diff --git a/src/com/android/settings/connecteddevice/audiosharing/AudioSharingDeviceVolumeControlUpdater.java b/src/com/android/settings/connecteddevice/audiosharing/AudioSharingDeviceVolumeControlUpdater.java
index e60eabd..5c0a90a 100644
--- a/src/com/android/settings/connecteddevice/audiosharing/AudioSharingDeviceVolumeControlUpdater.java
+++ b/src/com/android/settings/connecteddevice/audiosharing/AudioSharingDeviceVolumeControlUpdater.java
@@ -51,9 +51,10 @@
     public boolean isFilterMatched(CachedBluetoothDevice cachedDevice) {
         boolean isFilterMatched = false;
         if (isDeviceConnected(cachedDevice) && isDeviceInCachedDevicesList(cachedDevice)) {
-            // If device is LE audio device and has a broadcast source,
-            // it would show in audio sharing devices group.
+            // If device is LE audio device and in a sharing session on current sharing device,
+            // it would show in volume control group.
             if (cachedDevice.isConnectedLeAudioDevice()
+                    && AudioSharingUtils.isBroadcasting(mLocalBtManager)
                     && AudioSharingUtils.hasBroadcastSource(cachedDevice, mLocalBtManager)) {
                 isFilterMatched = true;
             }
diff --git a/src/com/android/settings/connecteddevice/audiosharing/AudioSharingUtils.java b/src/com/android/settings/connecteddevice/audiosharing/AudioSharingUtils.java
index b5361f2..3d4ef82 100644
--- a/src/com/android/settings/connecteddevice/audiosharing/AudioSharingUtils.java
+++ b/src/com/android/settings/connecteddevice/audiosharing/AudioSharingUtils.java
@@ -41,6 +41,8 @@
 import java.util.Optional;
 import java.util.stream.Collectors;
 
+import javax.annotation.Nullable;
+
 public class AudioSharingUtils {
     private static final String TAG = "AudioSharingUtils";
     private static final boolean DEBUG = BluetoothUtils.D;
@@ -237,7 +239,7 @@
      * @return An Optional containing the active LE Audio device, or an empty Optional if not found.
      */
     public static Optional<CachedBluetoothDevice> getActiveSinkOnAssistant(
-            LocalBluetoothManager manager) {
+            @Nullable LocalBluetoothManager manager) {
         if (manager == null) {
             Log.w(TAG, "getActiveSinksOnAssistant(): LocalBluetoothManager is null!");
             return Optional.empty();
diff --git a/src/com/android/settings/connecteddevice/audiosharing/audiostreams/AudioStreamButtonController.java b/src/com/android/settings/connecteddevice/audiosharing/audiostreams/AudioStreamButtonController.java
new file mode 100644
index 0000000..bb729d6
--- /dev/null
+++ b/src/com/android/settings/connecteddevice/audiosharing/audiostreams/AudioStreamButtonController.java
@@ -0,0 +1,66 @@
+/*
+ * 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.content.Context;
+
+import androidx.annotation.Nullable;
+import androidx.lifecycle.DefaultLifecycleObserver;
+import androidx.preference.PreferenceScreen;
+
+import com.android.settings.R;
+import com.android.settings.core.BasePreferenceController;
+import com.android.settingslib.widget.ActionButtonsPreference;
+
+public class AudioStreamButtonController extends BasePreferenceController
+        implements DefaultLifecycleObserver {
+    private static final String KEY = "audio_stream_button";
+    private @Nullable ActionButtonsPreference mPreference;
+    private int mBroadcastId = -1;
+
+    public AudioStreamButtonController(Context context, String preferenceKey) {
+        super(context, preferenceKey);
+    }
+
+    @Override
+    public final void displayPreference(PreferenceScreen screen) {
+        mPreference = screen.findPreference(getPreferenceKey());
+        if (mPreference != null) {
+            mPreference.setButton1Enabled(true);
+            // TODO(chelseahao): update this based on stream connection state
+            mPreference
+                    .setButton1Text(R.string.bluetooth_device_context_disconnect)
+                    .setButton1Icon(R.drawable.ic_settings_close);
+        }
+        super.displayPreference(screen);
+    }
+
+    @Override
+    public int getAvailabilityStatus() {
+        return AVAILABLE;
+    }
+
+    @Override
+    public String getPreferenceKey() {
+        return KEY;
+    }
+
+    /** Initialize with broadcast id */
+    void init(int broadcastId) {
+        mBroadcastId = broadcastId;
+    }
+}
diff --git a/src/com/android/settings/connecteddevice/audiosharing/audiostreams/AudioStreamDetailsFragment.java b/src/com/android/settings/connecteddevice/audiosharing/audiostreams/AudioStreamDetailsFragment.java
new file mode 100644
index 0000000..e1dc228
--- /dev/null
+++ b/src/com/android/settings/connecteddevice/audiosharing/audiostreams/AudioStreamDetailsFragment.java
@@ -0,0 +1,59 @@
+/*
+ * 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.content.Context;
+import android.os.Bundle;
+
+import com.android.settings.R;
+import com.android.settings.dashboard.DashboardFragment;
+
+public class AudioStreamDetailsFragment extends DashboardFragment {
+    static final String BROADCAST_NAME_ARG = "broadcast_name";
+    static final String BROADCAST_ID_ARG = "broadcast_id";
+    private static final String TAG = "AudioStreamDetailsFragment";
+
+    @Override
+    public void onAttach(Context context) {
+        super.onAttach(context);
+        Bundle arguments = getArguments();
+        if (arguments != null) {
+            use(AudioStreamHeaderController.class)
+                    .init(
+                            this,
+                            arguments.getString(BROADCAST_NAME_ARG),
+                            arguments.getInt(BROADCAST_ID_ARG));
+            use(AudioStreamButtonController.class).init(arguments.getInt(BROADCAST_ID_ARG));
+        }
+    }
+
+    @Override
+    public int getMetricsCategory() {
+        // TODO(chelseahao): update metrics id
+        return 0;
+    }
+
+    @Override
+    protected int getPreferenceScreenResId() {
+        return R.xml.audio_stream_details_fragment;
+    }
+
+    @Override
+    protected String getLogTag() {
+        return TAG;
+    }
+}
diff --git a/src/com/android/settings/connecteddevice/audiosharing/audiostreams/AudioStreamHeaderController.java b/src/com/android/settings/connecteddevice/audiosharing/audiostreams/AudioStreamHeaderController.java
new file mode 100644
index 0000000..89f24bc
--- /dev/null
+++ b/src/com/android/settings/connecteddevice/audiosharing/audiostreams/AudioStreamHeaderController.java
@@ -0,0 +1,85 @@
+/*
+ * 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.content.Context;
+
+import androidx.lifecycle.DefaultLifecycleObserver;
+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.widget.EntityHeaderController;
+import com.android.settingslib.widget.LayoutPreference;
+
+import javax.annotation.Nullable;
+
+public class AudioStreamHeaderController extends BasePreferenceController
+        implements DefaultLifecycleObserver {
+    private static final String KEY = "audio_stream_header";
+    private @Nullable EntityHeaderController mHeaderController;
+    private @Nullable DashboardFragment mFragment;
+    private String mBroadcastName = "";
+    private int mBroadcastId = -1;
+
+    public AudioStreamHeaderController(Context context, String preferenceKey) {
+        super(context, preferenceKey);
+    }
+
+    @Override
+    public final void displayPreference(PreferenceScreen screen) {
+        LayoutPreference headerPreference = screen.findPreference(KEY);
+        if (headerPreference != null && mFragment != null) {
+            mHeaderController =
+                    EntityHeaderController.newInstance(
+                            mFragment.getActivity(),
+                            mFragment,
+                            headerPreference.findViewById(R.id.entity_header));
+            if (mBroadcastName != null) {
+                mHeaderController.setLabel(mBroadcastName);
+            }
+            mHeaderController.setIcon(
+                    screen.getContext().getDrawable(R.drawable.ic_bt_audio_sharing));
+            // TODO(chelseahao): update this based on stream connection state
+            mHeaderController.setSummary("Listening now");
+            mHeaderController.done(true);
+            screen.addPreference(headerPreference);
+        }
+        super.displayPreference(screen);
+    }
+
+    @Override
+    public int getAvailabilityStatus() {
+        return AVAILABLE;
+    }
+
+    @Override
+    public String getPreferenceKey() {
+        return KEY;
+    }
+
+    /** Initialize with {@link AudioStreamDetailsFragment} and broadcast name and id */
+    void init(
+            AudioStreamDetailsFragment audioStreamDetailsFragment,
+            String broadcastName,
+            int broadcastId) {
+        mFragment = audioStreamDetailsFragment;
+        mBroadcastName = broadcastName;
+        mBroadcastId = broadcastId;
+    }
+}
diff --git a/src/com/android/settings/connecteddevice/audiosharing/audiostreams/AudioStreamsHelper.java b/src/com/android/settings/connecteddevice/audiosharing/audiostreams/AudioStreamsHelper.java
index 5acbc1f..198e8e5 100644
--- a/src/com/android/settings/connecteddevice/audiosharing/audiostreams/AudioStreamsHelper.java
+++ b/src/com/android/settings/connecteddevice/audiosharing/audiostreams/AudioStreamsHelper.java
@@ -80,8 +80,8 @@
                         });
     }
 
-    /** Removes all sources from LE broadcasts associated for all active sinks. */
-    void removeSource() {
+    /** Removes sources from LE broadcasts associated for all active sinks based on broadcast Id. */
+    void removeSource(int broadcastId) {
         if (mLeBroadcastAssistant == null) {
             Log.w(TAG, "removeSource(): LeBroadcastAssistant is null!");
             return;
@@ -93,14 +93,17 @@
                                 if (DEBUG) {
                                     Log.d(
                                             TAG,
-                                            "removeSource(): remove all sources from sink : "
+                                            "removeSource(): remove all sources with broadcast id :"
+                                                    + broadcastId
+                                                    + " from sink : "
                                                     + sink.getAddress());
                                 }
-                                var sources = mLeBroadcastAssistant.getAllSources(sink);
-                                if (!sources.isEmpty()) {
-                                    mLeBroadcastAssistant.removeSource(
-                                            sink, sources.get(0).getSourceId());
-                                }
+                                mLeBroadcastAssistant.getAllSources(sink).stream()
+                                        .filter(state -> state.getBroadcastId() == broadcastId)
+                                        .forEach(
+                                                state ->
+                                                        mLeBroadcastAssistant.removeSource(
+                                                                sink, state.getSourceId()));
                             }
                         });
     }
@@ -121,6 +124,12 @@
         return mLeBroadcastAssistant;
     }
 
+    static boolean isConnected(BluetoothLeBroadcastReceiveState state) {
+        return state.getPaSyncState() == BluetoothLeBroadcastReceiveState.PA_SYNC_STATE_SYNCHRONIZED
+                && state.getBigEncryptionState()
+                        == BluetoothLeBroadcastReceiveState.BIG_ENCRYPTION_STATE_DECRYPTING;
+    }
+
     private static List<BluetoothDevice> getActiveSinksOnAssistant(
             @Nullable LocalBluetoothManager manager) {
         if (manager == null) {
diff --git a/src/com/android/settings/connecteddevice/audiosharing/audiostreams/AudioStreamsProgressCategoryController.java b/src/com/android/settings/connecteddevice/audiosharing/audiostreams/AudioStreamsProgressCategoryController.java
index 6cf69c5..3c005b2 100644
--- a/src/com/android/settings/connecteddevice/audiosharing/audiostreams/AudioStreamsProgressCategoryController.java
+++ b/src/com/android/settings/connecteddevice/audiosharing/audiostreams/AudioStreamsProgressCategoryController.java
@@ -18,10 +18,18 @@
 
 import static java.util.Collections.emptyList;
 
+import android.app.AlertDialog;
+import android.app.settings.SettingsEnums;
 import android.bluetooth.BluetoothLeBroadcastMetadata;
 import android.bluetooth.BluetoothLeBroadcastReceiveState;
+import android.bluetooth.BluetoothProfile;
 import android.content.Context;
+import android.os.Bundle;
 import android.util.Log;
+import android.view.LayoutInflater;
+import android.view.View;
+import android.widget.EditText;
+import android.widget.TextView;
 
 import androidx.annotation.NonNull;
 import androidx.lifecycle.DefaultLifecycleObserver;
@@ -29,13 +37,19 @@
 import androidx.preference.Preference;
 import androidx.preference.PreferenceScreen;
 
+import com.android.settings.R;
 import com.android.settings.bluetooth.Utils;
 import com.android.settings.connecteddevice.audiosharing.AudioSharingUtils;
 import com.android.settings.core.BasePreferenceController;
+import com.android.settings.core.SubSettingLauncher;
+import com.android.settingslib.bluetooth.BluetoothCallback;
 import com.android.settingslib.bluetooth.BluetoothUtils;
+import com.android.settingslib.bluetooth.CachedBluetoothDevice;
 import com.android.settingslib.bluetooth.LocalBluetoothLeBroadcastAssistant;
+import com.android.settingslib.bluetooth.LocalBluetoothManager;
 import com.android.settingslib.utils.ThreadUtils;
 
+import java.nio.charset.StandardCharsets;
 import java.util.concurrent.ConcurrentHashMap;
 import java.util.concurrent.Executor;
 import java.util.concurrent.Executors;
@@ -46,11 +60,22 @@
         implements DefaultLifecycleObserver {
     private static final String TAG = "AudioStreamsProgressCategoryController";
     private static final boolean DEBUG = BluetoothUtils.D;
+    private final BluetoothCallback mBluetoothCallback =
+            new BluetoothCallback() {
+                @Override
+                public void onActiveDeviceChanged(
+                        @Nullable CachedBluetoothDevice activeDevice, int bluetoothProfile) {
+                    if (bluetoothProfile == BluetoothProfile.LE_AUDIO) {
+                        mExecutor.execute(() -> init(activeDevice != null));
+                    }
+                }
+            };
 
     private final Executor mExecutor;
     private final AudioStreamsBroadcastAssistantCallback mBroadcastAssistantCallback;
     private final AudioStreamsHelper mAudioStreamsHelper;
     private final @Nullable LocalBluetoothLeBroadcastAssistant mLeBroadcastAssistant;
+    private final @Nullable LocalBluetoothManager mBluetoothManager;
     private final ConcurrentHashMap<Integer, AudioStreamPreference> mBroadcastIdToPreferenceMap =
             new ConcurrentHashMap<>();
     private AudioStreamsProgressCategoryPreference mCategoryPreference;
@@ -58,7 +83,8 @@
     public AudioStreamsProgressCategoryController(Context context, String preferenceKey) {
         super(context, preferenceKey);
         mExecutor = Executors.newSingleThreadExecutor();
-        mAudioStreamsHelper = new AudioStreamsHelper(Utils.getLocalBtManager(mContext));
+        mBluetoothManager = Utils.getLocalBtManager(mContext);
+        mAudioStreamsHelper = new AudioStreamsHelper(mBluetoothManager);
         mLeBroadcastAssistant = mAudioStreamsHelper.getLeBroadcastAssistant();
         mBroadcastAssistantCallback = new AudioStreamsBroadcastAssistantCallback(this);
     }
@@ -76,48 +102,24 @@
 
     @Override
     public void onStart(@NonNull LifecycleOwner owner) {
-        if (mLeBroadcastAssistant == null) {
-            Log.w(TAG, "onStart(): LeBroadcastAssistant is null!");
-            return;
-        }
-        mBroadcastIdToPreferenceMap.clear();
-        if (mCategoryPreference != null) {
-            mCategoryPreference.removeAll();
+        if (mBluetoothManager != null) {
+            mBluetoothManager.getEventManager().registerCallback(mBluetoothCallback);
         }
         mExecutor.execute(
                 () -> {
-                    mLeBroadcastAssistant.registerServiceCallBack(
-                            mExecutor, mBroadcastAssistantCallback);
-                    if (DEBUG) {
-                        Log.d(TAG, "scanAudioStreamsStart()");
-                    }
-                    mLeBroadcastAssistant.startSearchingForSources(emptyList());
-                    // Display currently connected streams
-                    var unused =
-                            ThreadUtils.postOnBackgroundThread(
-                                    () ->
-                                            mAudioStreamsHelper
-                                                    .getAllSources()
-                                                    .forEach(this::handleSourceConnected));
+                    boolean hasActive =
+                            AudioSharingUtils.getActiveSinkOnAssistant(mBluetoothManager)
+                                    .isPresent();
+                    init(hasActive);
                 });
     }
 
     @Override
     public void onStop(@NonNull LifecycleOwner owner) {
-        if (mLeBroadcastAssistant == null) {
-            Log.w(TAG, "onStop(): LeBroadcastAssistant is null!");
-            return;
+        if (mBluetoothManager != null) {
+            mBluetoothManager.getEventManager().unregisterCallback(mBluetoothCallback);
         }
-        mExecutor.execute(
-                () -> {
-                    if (mLeBroadcastAssistant.isSearchInProgress()) {
-                        if (DEBUG) {
-                            Log.d(TAG, "scanAudioStreamsStop()");
-                        }
-                        mLeBroadcastAssistant.stopSearchingForSources();
-                    }
-                    mLeBroadcastAssistant.unregisterServiceCallBack(mBroadcastAssistantCallback);
-                });
+        mExecutor.execute(this::stopScanning);
     }
 
     void setScanning(boolean isScanning) {
@@ -131,7 +133,10 @@
         Preference.OnPreferenceClickListener addSourceOrShowDialog =
                 preference -> {
                     if (DEBUG) {
-                        Log.d(TAG, "preferenceClicked(): attempt to join broadcast");
+                        Log.d(
+                                TAG,
+                                "preferenceClicked(): attempt to join broadcast id : "
+                                        + source.getBroadcastId());
                     }
                     if (source.isEncrypted()) {
                         ThreadUtils.postOnMainThread(
@@ -166,11 +171,13 @@
                         }
                     });
         }
-        mAudioStreamsHelper.removeSource();
+        mAudioStreamsHelper.removeSource(broadcastId);
     }
 
     void handleSourceConnected(BluetoothLeBroadcastReceiveState state) {
-        // TODO(chelseahao): only continue when the state indicates a successful connection
+        if (!AudioStreamsHelper.isConnected(state)) {
+            return;
+        }
         mBroadcastIdToPreferenceMap.compute(
                 state.getBroadcastId(),
                 (k, v) -> {
@@ -183,7 +190,7 @@
                     ThreadUtils.postOnMainThread(
                             () -> {
                                 preference.setIsConnected(
-                                        true, p -> launchDetailFragment((AudioStreamPreference) p));
+                                        true, p -> launchDetailFragment(state.getBroadcastId()));
                                 if (mCategoryPreference != null && !existed) {
                                     mCategoryPreference.addPreference(preference);
                                 }
@@ -197,12 +204,111 @@
         AudioSharingUtils.toastMessage(mContext, msg);
     }
 
-    private boolean launchDetailFragment(AudioStreamPreference preference) {
-        // TODO(chelseahao): impl
+    private void init(boolean hasActive) {
+        mBroadcastIdToPreferenceMap.clear();
+        ThreadUtils.postOnMainThread(
+                () -> {
+                    if (mCategoryPreference != null) {
+                        mCategoryPreference.removeAll();
+                        mCategoryPreference.setVisible(hasActive);
+                    }
+                });
+        if (hasActive) {
+            startScanning();
+        } else {
+            stopScanning();
+        }
+    }
+
+    private void startScanning() {
+        if (mLeBroadcastAssistant == null) {
+            Log.w(TAG, "startScanning(): LeBroadcastAssistant is null!");
+            return;
+        }
+        if (mLeBroadcastAssistant.isSearchInProgress()) {
+            showToast("Failed to start scanning, please try again.");
+            return;
+        }
+        if (DEBUG) {
+            Log.d(TAG, "startScanning()");
+        }
+        mLeBroadcastAssistant.registerServiceCallBack(mExecutor, mBroadcastAssistantCallback);
+        mLeBroadcastAssistant.startSearchingForSources(emptyList());
+
+        // Display currently connected streams
+        var unused =
+                ThreadUtils.postOnBackgroundThread(
+                        () ->
+                                mAudioStreamsHelper
+                                        .getAllSources()
+                                        .forEach(this::handleSourceConnected));
+    }
+
+    private void stopScanning() {
+        if (mLeBroadcastAssistant == null) {
+            Log.w(TAG, "stopScanning(): LeBroadcastAssistant is null!");
+            return;
+        }
+        if (mLeBroadcastAssistant.isSearchInProgress()) {
+            if (DEBUG) {
+                Log.d(TAG, "stopScanning()");
+            }
+            mLeBroadcastAssistant.stopSearchingForSources();
+        }
+        mLeBroadcastAssistant.unregisterServiceCallBack(mBroadcastAssistantCallback);
+    }
+
+    private boolean launchDetailFragment(int broadcastId) {
+        if (!mBroadcastIdToPreferenceMap.containsKey(broadcastId)) {
+            Log.w(
+                    TAG,
+                    "launchDetailFragment(): broadcastId not exist in BroadcastIdToPreferenceMap!");
+            return false;
+        }
+        AudioStreamPreference preference = mBroadcastIdToPreferenceMap.get(broadcastId);
+
+        Bundle broadcast = new Bundle();
+        broadcast.putString(
+                AudioStreamDetailsFragment.BROADCAST_NAME_ARG, (String) preference.getTitle());
+        broadcast.putInt(AudioStreamDetailsFragment.BROADCAST_ID_ARG, broadcastId);
+
+        new SubSettingLauncher(mContext)
+                .setTitleText("Audio stream details")
+                .setDestination(AudioStreamDetailsFragment.class.getName())
+                // TODO(chelseahao): Add logging enum
+                .setSourceMetricsCategory(SettingsEnums.PAGE_UNKNOWN)
+                .setArguments(broadcast)
+                .launch();
         return true;
     }
 
     private void launchPasswordDialog(BluetoothLeBroadcastMetadata source, Preference preference) {
-        // TODO(chelseahao): impl
+        View layout =
+                LayoutInflater.from(mContext)
+                        .inflate(R.layout.bluetooth_find_broadcast_password_dialog, null);
+        ((TextView) layout.requireViewById(R.id.broadcast_name_text))
+                .setText(preference.getTitle());
+        AlertDialog alertDialog =
+                new AlertDialog.Builder(mContext)
+                        .setTitle(R.string.find_broadcast_password_dialog_title)
+                        .setView(layout)
+                        .setNeutralButton(android.R.string.cancel, null)
+                        .setPositiveButton(
+                                R.string.bluetooth_connect_access_dialog_positive,
+                                (dialog, which) -> {
+                                    var code =
+                                            ((EditText)
+                                                            layout.requireViewById(
+                                                                    R.id.broadcast_edit_text))
+                                                    .getText()
+                                                    .toString();
+                                    mAudioStreamsHelper.addSource(
+                                            new BluetoothLeBroadcastMetadata.Builder(source)
+                                                    .setBroadcastCode(
+                                                            code.getBytes(StandardCharsets.UTF_8))
+                                                    .build());
+                                })
+                        .create();
+        alertDialog.show();
     }
 }
diff --git a/src/com/android/settings/core/gateway/SettingsGateway.java b/src/com/android/settings/core/gateway/SettingsGateway.java
index 6d1d4e8..d68f2c8 100644
--- a/src/com/android/settings/core/gateway/SettingsGateway.java
+++ b/src/com/android/settings/core/gateway/SettingsGateway.java
@@ -100,6 +100,7 @@
 import com.android.settings.deviceinfo.legal.ModuleLicensesDashboard;
 import com.android.settings.display.AutoBrightnessSettings;
 import com.android.settings.display.NightDisplaySettings;
+import com.android.settings.display.ScreenTimeoutSettings;
 import com.android.settings.display.SmartAutoRotatePreferenceFragment;
 import com.android.settings.display.darkmode.DarkModeSettingsFragment;
 import com.android.settings.dream.DreamSettings;
@@ -369,7 +370,8 @@
             LongBackgroundTasksDetails.class.getName(),
             RegionalPreferencesEntriesFragment.class.getName(),
             BatteryInfoFragment.class.getName(),
-            UserAspectRatioDetails.class.getName()
+            UserAspectRatioDetails.class.getName(),
+            ScreenTimeoutSettings.class.getName(),
     };
 
     public static final String[] SETTINGS_FOR_RESTRICTED = {
diff --git a/src/com/android/settings/dashboard/profileselector/ProfileSelectFragment.java b/src/com/android/settings/dashboard/profileselector/ProfileSelectFragment.java
index 3321d50..0348e11 100644
--- a/src/com/android/settings/dashboard/profileselector/ProfileSelectFragment.java
+++ b/src/com/android/settings/dashboard/profileselector/ProfileSelectFragment.java
@@ -130,7 +130,6 @@
         if (titleResId > 0) {
             activity.setTitle(titleResId);
         }
-        final int selectedTab = getTabId(activity, getArguments());
 
         final View tabContainer = mContentView.findViewById(R.id.tab_container);
         mViewPager = tabContainer.findViewById(R.id.view_pager);
@@ -149,6 +148,7 @@
                 }
         );
         tabContainer.setVisibility(View.VISIBLE);
+        final int selectedTab = getTabId(activity, getArguments());
         final TabLayout.Tab tab = tabs.getTabAt(selectedTab);
         tab.select();
 
@@ -228,7 +228,7 @@
         if (bundle != null) {
             final int extraTab = bundle.getInt(SettingsActivity.EXTRA_SHOW_FRAGMENT_TAB, -1);
             if (extraTab != -1) {
-                return extraTab;
+                return ((ViewPagerAdapter) mViewPager.getAdapter()).getTabForPosition(extraTab);
             }
             final int userId = bundle.getInt(EXTRA_USER_ID, UserHandle.SYSTEM.getIdentifier());
             final boolean isWorkProfile = UserManager.get(activity).isManagedProfile(userId);
diff --git a/src/com/android/settings/fuelgauge/PowerUsageFeatureProvider.java b/src/com/android/settings/fuelgauge/PowerUsageFeatureProvider.java
index a2ee3e4..0bc6176 100644
--- a/src/com/android/settings/fuelgauge/PowerUsageFeatureProvider.java
+++ b/src/com/android/settings/fuelgauge/PowerUsageFeatureProvider.java
@@ -22,6 +22,7 @@
 import android.util.ArrayMap;
 import android.util.SparseIntArray;
 
+import com.android.settings.fuelgauge.batteryusage.DetectRequestSourceType;
 import com.android.settings.fuelgauge.batteryusage.PowerAnomalyEventList;
 import com.android.settingslib.fuelgauge.Estimate;
 
@@ -103,11 +104,9 @@
     /** Returns {@code true} if delay the hourly job when device is booting */
     boolean delayHourlyJobWhenBooting();
 
-    /** Insert settings configuration data for anomaly detection */
-    void insertSettingsData(Context context, double displayDrain);
-
     /** Returns {@link Bundle} for settings anomaly detection result */
-    PowerAnomalyEventList detectSettingsAnomaly(Context context, double displayDrain);
+    PowerAnomalyEventList detectSettingsAnomaly(
+            Context context, double displayDrain, DetectRequestSourceType detectRequestSourceType);
 
     /** Gets an intent for one time bypass charge limited to resume charging. */
     Intent getResumeChargeIntent(boolean isDockDefender);
diff --git a/src/com/android/settings/fuelgauge/PowerUsageFeatureProviderImpl.java b/src/com/android/settings/fuelgauge/PowerUsageFeatureProviderImpl.java
index 2e2cf12..a8a2f75 100644
--- a/src/com/android/settings/fuelgauge/PowerUsageFeatureProviderImpl.java
+++ b/src/com/android/settings/fuelgauge/PowerUsageFeatureProviderImpl.java
@@ -27,6 +27,7 @@
 import android.util.SparseIntArray;
 
 import com.android.internal.util.ArrayUtils;
+import com.android.settings.fuelgauge.batteryusage.DetectRequestSourceType;
 import com.android.settings.fuelgauge.batteryusage.PowerAnomalyEventList;
 import com.android.settingslib.fuelgauge.Estimate;
 
@@ -168,10 +169,8 @@
     }
 
     @Override
-    public void insertSettingsData(Context context, double displayDrain) {}
-
-    @Override
-    public PowerAnomalyEventList detectSettingsAnomaly(Context context, double displayDrain) {
+    public PowerAnomalyEventList detectSettingsAnomaly(
+            Context context, double displayDrain, DetectRequestSourceType detectRequestSourceType) {
         return null;
     }
 
diff --git a/src/com/android/settings/fuelgauge/batteryusage/BatteryUsageDataLoader.java b/src/com/android/settings/fuelgauge/batteryusage/BatteryUsageDataLoader.java
index ead580b..fb5b9a1 100644
--- a/src/com/android/settings/fuelgauge/batteryusage/BatteryUsageDataLoader.java
+++ b/src/com/android/settings/fuelgauge/batteryusage/BatteryUsageDataLoader.java
@@ -135,7 +135,10 @@
                                                                             .isEmpty()))) {
                                 FeatureFactory.getFeatureFactory()
                                         .getPowerUsageFeatureProvider()
-                                        .detectSettingsAnomaly(context, /* displayDrain= */ 0);
+                                        .detectSettingsAnomaly(
+                                                context,
+                                                /* displayDrain= */ 0,
+                                                DetectRequestSourceType.TYPE_DATA_LOADER);
                             }
                         });
         if (batteryLevelData == null) {
diff --git a/src/com/android/settings/fuelgauge/batteryusage/PowerUsageAdvanced.java b/src/com/android/settings/fuelgauge/batteryusage/PowerUsageAdvanced.java
index 072040d..1482117 100644
--- a/src/com/android/settings/fuelgauge/batteryusage/PowerUsageAdvanced.java
+++ b/src/com/android/settings/fuelgauge/batteryusage/PowerUsageAdvanced.java
@@ -263,7 +263,9 @@
                             FeatureFactory.getFeatureFactory().getPowerUsageFeatureProvider();
                     final PowerAnomalyEventList anomalyEventList =
                             powerUsageFeatureProvider.detectSettingsAnomaly(
-                                    getContext(), /* displayDrain= */ 0);
+                                    getContext(),
+                                    /* displayDrain= */ 0,
+                                    DetectRequestSourceType.TYPE_USAGE_UI);
                     mHandler.post(() -> onAnomalyDetected(anomalyEventList));
                 });
     }
diff --git a/src/com/android/settings/fuelgauge/protos/power_anomaly_event.proto b/src/com/android/settings/fuelgauge/protos/power_anomaly_event.proto
index 930a21b..3c0705f 100644
--- a/src/com/android/settings/fuelgauge/protos/power_anomaly_event.proto
+++ b/src/com/android/settings/fuelgauge/protos/power_anomaly_event.proto
@@ -21,18 +21,12 @@
   optional string dismiss_record_key = 8;
 }
 
-// NOTE: Please DO NOT delete enum items or change enum values. Use [deprecated = true] instead.
-// The enum value will be used to decide the tips card style like icons and colors.
-//
 // Next id: 2
 enum PowerAnomalyType{
   TYPE_SETTINGS_BANNER = 0;
   TYPE_APPS_ITEM = 1;
 }
 
-// NOTE: Please DO NOT delete enum items or change enum values. Use [deprecated = true] instead.
-// The enum value will be used to decide pre-defined title and button labels.
-//
 // Next id: 8
 enum PowerAnomalyKey{
   KEY_BRIGHTNESS = 0;
@@ -45,6 +39,13 @@
   KEY_APP_FOREGROUND_HIGHER_THAN_USUAL = 7;
 }
 
+// Next id: 3
+enum DetectRequestSourceType{
+  TYPE_UNKNOWN_SOURCE = 0;
+  TYPE_USAGE_UI = 1;
+  TYPE_DATA_LOADER = 2;
+}
+
 message WarningBannerInfo {
   optional string title_string = 1;
   optional string description_string = 2;
diff --git a/src/com/android/settings/network/EraseEuiccDataDialogFragment.java b/src/com/android/settings/network/EraseEuiccDataDialogFragment.java
index 0200e52..3bf9db3 100644
--- a/src/com/android/settings/network/EraseEuiccDataDialogFragment.java
+++ b/src/com/android/settings/network/EraseEuiccDataDialogFragment.java
@@ -32,7 +32,6 @@
 
 import com.android.settings.R;
 import com.android.settings.core.instrumentation.InstrumentedDialogFragment;
-import com.android.settings.network.helper.ConfirmationSimDeletionPredicate;
 import com.android.settings.system.ResetDashboardFragment;
 import com.android.settings.wifi.dpp.WifiDppUtils;
 
@@ -76,14 +75,7 @@
 
         if (which == DialogInterface.BUTTON_POSITIVE) {
             Context context = getContext();
-            if (ConfirmationSimDeletionPredicate.getSingleton().test(context)) {
-                // Create a "verify it's you" verification over keyguard
-                // when "erase" button been pressed.
-                // This might protect from erasing by some automation process.
-                WifiDppUtils.showLockScreen(context, () -> runAsyncWipe(context));
-            } else {
-                runAsyncWipe(context);
-            }
+            WifiDppUtils.showLockScreen(context, () -> runAsyncWipe(context));
         }
     }
 
diff --git a/src/com/android/settings/network/apn/ApnEditPageProvider.kt b/src/com/android/settings/network/apn/ApnEditPageProvider.kt
index bb19c59..52066a1 100644
--- a/src/com/android/settings/network/apn/ApnEditPageProvider.kt
+++ b/src/com/android/settings/network/apn/ApnEditPageProvider.kt
@@ -39,7 +39,6 @@
 import com.android.settings.network.apn.ApnNetworkTypes.getNetworkTypeSelectedOptionsState
 import com.android.settingslib.spa.framework.common.SettingsPageProvider
 import com.android.settingslib.spa.framework.compose.LocalNavController
-import com.android.settingslib.spa.framework.compose.OnBackEffect
 import com.android.settingslib.spa.widget.editor.SettingsExposedDropdownMenuBox
 import com.android.settingslib.spa.widget.editor.SettingsExposedDropdownMenuCheckBox
 import com.android.settingslib.spa.widget.editor.SettingsOutlinedTextField
@@ -98,29 +97,35 @@
     val networkTypeSelectedOptionsState = remember {
         getNetworkTypeSelectedOptionsState(apnData.networkType)
     }
-    OnBackEffect{
-        validateAndSaveApnData(
-            apnDataInit,
-            apnData,
-            context,
-            uriInit,
-            networkTypeSelectedOptionsState
-        )
-    }
     val navController = LocalNavController.current
     RegularScaffold(
-        title = if(apnDataInit.newApn) stringResource(id = R.string.apn_add) else stringResource(id = R.string.apn_edit),
+        title = if (apnDataInit.newApn) stringResource(id = R.string.apn_add) else stringResource(id = R.string.apn_edit),
+        actions = {
+            IconButton(onClick = {
+                if (!apnData.validEnabled) apnData = apnData.copy(validEnabled = true)
+                val valid = validateAndSaveApnData(
+                    apnDataInit,
+                    apnData,
+                    context,
+                    uriInit,
+                    networkTypeSelectedOptionsState
+                )
+                if (valid) navController.navigateBack()
+            }) { Icon(imageVector = Icons.Outlined.Done, contentDescription = null) }
+        },
     ) {
         Column {
             SettingsOutlinedTextField(
                 value = apnData.name,
                 label = stringResource(R.string.apn_name),
-                enabled = apnData.nameEnabled
+                enabled = apnData.nameEnabled,
+                errorMessage = validateName(apnData.validEnabled, apnData.name, context)
             ) { apnData = apnData.copy(name = it) }
             SettingsOutlinedTextField(
                 value = apnData.apn,
                 label = stringResource(R.string.apn_apn),
-                enabled = apnData.apnEnabled
+                enabled = apnData.apnEnabled,
+                errorMessage = validateAPN(apnData.validEnabled, apnData.apn, context)
             ) { apnData = apnData.copy(apn = it) }
             SettingsOutlinedTextField(
                 value = apnData.proxy,
@@ -150,7 +155,7 @@
             SettingsOutlinedTextField(
                 value = apnData.mmsc,
                 label = stringResource(R.string.apn_mmsc),
-                errorMessage = validateMMSC(apnData.mmsc, context),
+                errorMessage = validateMMSC(apnData.validEnabled, apnData.mmsc, context),
                 enabled = apnData.mmscEnabled
             ) { apnData = apnData.copy(mmsc = it) }
             SettingsOutlinedTextField(
@@ -172,7 +177,11 @@
             SettingsOutlinedTextField(
                 value = apnData.apnType,
                 label = stringResource(R.string.apn_type),
-                enabled = apnData.apnTypeEnabled
+                enabled = apnData.apnTypeEnabled,
+                errorMessage = validateAPNType(
+                    apnData.validEnabled, apnData.apnType,
+                    apnData.customizedConfig.readOnlyApnTypes, context
+                )
             ) { apnData = apnData.copy(apnType = updateApnType(apnData.copy(apnType = it))) }
             SettingsExposedDropdownMenuBox(
                 label = stringResource(R.string.apn_protocol),
@@ -209,7 +218,6 @@
                         override val title = stringResource(R.string.menu_delete)
                         override val onClick = {
                             deleteApn(uriInit, context)
-                            apnData = apnData.copy(saveEnabled = false)
                             navController.navigateBack()
                         }
                     }
diff --git a/src/com/android/settings/network/apn/ApnStatus.kt b/src/com/android/settings/network/apn/ApnStatus.kt
index 668ea9b..38c6684 100644
--- a/src/com/android/settings/network/apn/ApnStatus.kt
+++ b/src/com/android/settings/network/apn/ApnStatus.kt
@@ -69,7 +69,7 @@
     val networkTypeEnabled: Boolean = true,
     val newApn: Boolean = false,
     val subId: Int = -1,
-    val saveEnabled: Boolean = true,
+    val validEnabled: Boolean = false,
     val customizedConfig: CustomizedConfig = CustomizedConfig()
 ) {
     fun getContentValues(context: Context): ContentValues {
@@ -96,7 +96,8 @@
         values.put(Telephony.Carriers.EDITED_STATUS, Telephony.Carriers.USER_EDITED)
         if (newApn) {
             val simCarrierId =
-                context.getSystemService(TelephonyManager::class.java)!!.createForSubscriptionId(subId)
+                context.getSystemService(TelephonyManager::class.java)!!
+                    .createForSubscriptionId(subId)
                     .getSimCarrierId()
             values.put(Telephony.Carriers.CARRIER_ID, simCarrierId)
         }
@@ -231,22 +232,12 @@
     uriInit: Uri,
     networkTypeSelectedOptionsState: SnapshotStateList<Int>
 ): Boolean {
-    // Can not be saved
-    if (!apnData.saveEnabled) {
-        return false
-    }
     // Nothing to do if it's a read only APN
     if (apnData.customizedConfig.readOnlyApn) {
         return true
     }
-    var errorMsg = validateApnData(apnData, context)
+    val errorMsg = validateApnData(apnData, context)
     if (errorMsg != null) {
-        //TODO: showError(this)
-        return false
-    }
-    errorMsg = validateMMSC(apnData.mmsc, context)
-    if (errorMsg != null) {
-        //TODO: showError(this)
         return false
     }
     val newApnData = apnData.copy(networkType = getNetworkType(networkTypeSelectedOptionsState))
@@ -268,37 +259,23 @@
  * @return An error message if the apn data is invalid, otherwise return null.
  */
 fun validateApnData(apnData: ApnData, context: Context): String? {
-    var errorMsg: String? = null
+    var errorMsg: String?
     val name = apnData.name
     val apn = apnData.apn
-    if (name == "") {
-        errorMsg = context.resources.getString(R.string.error_name_empty)
+    errorMsg = if (name == "") {
+        context.resources.getString(R.string.error_name_empty)
     } else if (apn == "") {
-        errorMsg = context.resources.getString(R.string.error_apn_empty)
+        context.resources.getString(R.string.error_apn_empty)
+    } else {
+        validateMMSC(apnData.validEnabled, apnData.mmsc, context)
     }
     if (errorMsg == null) {
-        // if carrier does not allow editing certain apn types, make sure type does not include
-        // those
-        if (!ArrayUtils.isEmpty(apnData.customizedConfig.readOnlyApnTypes)
-            && apnTypesMatch(
-                apnData.customizedConfig.readOnlyApnTypes,
-                getUserEnteredApnType(apnData.apnType, apnData.customizedConfig.readOnlyApnTypes)
-            )
-        ) {
-            val stringBuilder = StringBuilder()
-            for (type in apnData.customizedConfig.readOnlyApnTypes) {
-                stringBuilder.append(type).append(", ")
-                Log.d(TAG, "validateApnData: appending type: $type")
-            }
-            // remove last ", "
-            if (stringBuilder.length >= 2) {
-                stringBuilder.delete(stringBuilder.length - 2, stringBuilder.length)
-            }
-            errorMsg = String.format(
-                context.resources.getString(R.string.error_adding_apn_type),
-                stringBuilder
-            )
-        }
+        errorMsg = validateAPNType(
+            apnData.validEnabled,
+            apnData.apnType,
+            apnData.customizedConfig.readOnlyApnTypes,
+            context
+        )
     }
     return errorMsg
 }
@@ -536,7 +513,39 @@
     contentResolver.delete(uri, null, null)
 }
 
-fun validateMMSC(mmsc: String, context: Context): String? {
-    return if (mmsc.matches(Regex("^https?:\\/\\/.+"))) null
-            else context.resources.getString(R.string.error_mmsc_valid)
+fun validateMMSC(validEnabled: Boolean, mmsc: String, context: Context): String? {
+    return if (validEnabled && !mmsc.matches(Regex("^https?:\\/\\/.+")))
+        context.resources.getString(R.string.error_mmsc_valid)
+    else null
+}
+
+fun validateName(validEnabled: Boolean, name: String, context: Context): String? {
+    return if (validEnabled && (name == "")) context.resources.getString(R.string.error_name_empty)
+    else null
+}
+
+fun validateAPN(validEnabled: Boolean, apn: String, context: Context): String? {
+    return if (validEnabled && (apn == "")) context.resources.getString(R.string.error_apn_empty)
+    else null
+}
+
+fun validateAPNType(
+    validEnabled: Boolean,
+    apnType: String,
+    readOnlyApnTypes: List<String>,
+    context: Context
+): String? {
+    // if carrier does not allow editing certain apn types, make sure type does not include those
+    if (validEnabled && !ArrayUtils.isEmpty(readOnlyApnTypes)
+        && apnTypesMatch(
+            readOnlyApnTypes,
+            getUserEnteredApnType(apnType, readOnlyApnTypes)
+        )
+    ) {
+        return String.format(
+            context.resources.getString(R.string.error_adding_apn_type),
+            readOnlyApnTypes.joinToString(", ")
+        )
+    }
+    return null
 }
\ No newline at end of file
diff --git a/src/com/android/settings/network/helper/ConfirmationSimDeletionPredicate.java b/src/com/android/settings/network/helper/ConfirmationSimDeletionPredicate.java
deleted file mode 100644
index 420f6db..0000000
--- a/src/com/android/settings/network/helper/ConfirmationSimDeletionPredicate.java
+++ /dev/null
@@ -1,66 +0,0 @@
-/*
- * Copyright (C) 2021 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.helper;
-
-import android.app.KeyguardManager;
-import android.content.Context;
-import android.provider.Settings;
-
-import com.android.settings.R;
-
-import java.util.function.Predicate;
-
-/**
- * {@link Predicate} for detecting the configuration of confirm SIM deletion.
- */
-public class ConfirmationSimDeletionPredicate implements Predicate<Context> {
-
-    public static final String KEY_CONFIRM_SIM_DELETION = "confirm_sim_deletion";
-
-    private static final ConfirmationSimDeletionPredicate sSingleton =
-            new ConfirmationSimDeletionPredicate();
-
-    // Get singleton of this predicate
-    public static final ConfirmationSimDeletionPredicate getSingleton() {
-        return sSingleton;
-    }
-
-    /**
-     * Get default configuration of confirm SIM deletion.
-     *
-     * @param Context context
-     * @return the configuration of confirm SIM deletion
-     */
-    private static boolean getDefaultValue(Context context) {
-        return context.getResources()
-                .getBoolean(R.bool.config_sim_deletion_confirmation_default_on);
-    }
-
-    /**
-     * Get the configuration of confirm SIM deletion.
-     *
-     * @param Context context
-     * @return the configuration of confirm SIM deletion
-     */
-    public boolean test(Context context) {
-        final KeyguardManager keyguardManager = context.getSystemService(KeyguardManager.class);
-        if ((keyguardManager != null) && !keyguardManager.isKeyguardSecure()) {
-            return false;
-        }
-        return Settings.Global.getInt(context.getContentResolver(), KEY_CONFIRM_SIM_DELETION,
-                getDefaultValue(context) ? 1 : 0) == 1;
-    }
-}
diff --git a/src/com/android/settings/network/telephony/DeleteSimProfilePreferenceController.kt b/src/com/android/settings/network/telephony/DeleteSimProfilePreferenceController.kt
index 093c4bf..64f9730 100644
--- a/src/com/android/settings/network/telephony/DeleteSimProfilePreferenceController.kt
+++ b/src/com/android/settings/network/telephony/DeleteSimProfilePreferenceController.kt
@@ -26,10 +26,8 @@
 import com.android.settings.R
 import com.android.settings.core.BasePreferenceController
 import com.android.settings.network.SubscriptionUtil
-import com.android.settings.security.ConfirmSimDeletionPreferenceController.KEY_CONFIRM_SIM_DELETION
 import com.android.settings.wifi.dpp.WifiDppUtils
 import com.android.settingslib.spa.framework.util.collectLatestWithLifecycle
-import com.android.settingslib.spaprivileged.settingsprovider.settingsGlobalBoolean
 
 /** This controls a preference allowing the user to delete the profile for an eSIM.  */
 class DeleteSimProfilePreferenceController(context: Context, preferenceKey: String) :
@@ -63,16 +61,8 @@
     override fun handlePreferenceTreeClick(preference: Preference): Boolean {
         if (preference.key != preferenceKey) return false
 
-        val confirmDeletion by mContext.settingsGlobalBoolean(
-            name = KEY_CONFIRM_SIM_DELETION,
-            defaultValue = mContext.resources
-                .getBoolean(R.bool.config_sim_deletion_confirmation_default_on),
-        )
-        if (confirmDeletion) {
-            WifiDppUtils.showLockScreen(mContext) { deleteSim() }
-        } else {
-            deleteSim()
-        }
+        WifiDppUtils.showLockScreen(mContext) { deleteSim() }
+
         return true
     }
 
diff --git a/src/com/android/settings/password/ConfirmDeviceCredentialActivity.java b/src/com/android/settings/password/ConfirmDeviceCredentialActivity.java
index a0db4ce..2a299c5 100644
--- a/src/com/android/settings/password/ConfirmDeviceCredentialActivity.java
+++ b/src/com/android/settings/password/ConfirmDeviceCredentialActivity.java
@@ -43,6 +43,7 @@
 import android.os.Looper;
 import android.os.UserHandle;
 import android.os.UserManager;
+import android.os.storage.StorageManager;
 import android.util.Log;
 import android.view.WindowManager;
 
@@ -292,7 +293,7 @@
             // use, which optionally accepts a challenge.
             mForceVerifyPath = true;
             if (isBiometricAllowed(effectiveUserId, mUserId)) {
-                showBiometricPrompt(promptInfo);
+                showBiometricPrompt(promptInfo, mUserId);
                 launchedBiometric = true;
             } else {
                 showConfirmCredentials();
@@ -302,19 +303,25 @@
                 && userProperties != null
                 && userProperties.isAuthAlwaysRequiredToDisableQuietMode()
                 && isInternalActivity()) {
-            // Force verification path is required to be invoked as we might need to verify the tied
-            // profile challenge if the profile is using the unified challenge mode. This would
-            // result in ConfirmLockPassword.startVerifyPassword/
+            // Force verification path is required to be invoked as we might need to verify the
+            // tied profile challenge if the profile is using the unified challenge mode. This
+            // would result in ConfirmLockPassword.startVerifyPassword/
             // ConfirmLockPattern.startVerifyPattern being called instead of the
             // startCheckPassword/startCheckPattern
             mForceVerifyPath = userProperties.isCredentialShareableWithParent();
-            showConfirmCredentials();
-            launchedCDC = true;
+            if (android.multiuser.Flags.enableBiometricsToUnlockPrivateSpace()
+                    && isBiometricAllowed(effectiveUserId, mUserId)) {
+                showBiometricPrompt(promptInfo, effectiveUserId);
+                launchedBiometric = true;
+            } else {
+                showConfirmCredentials();
+                launchedCDC = true;
+            }
         } else {
             if (isBiometricAllowed(effectiveUserId, mUserId)) {
                 // Don't need to check if biometrics / pin/pattern/pass are enrolled. It will go to
                 // onAuthenticationError and do the right thing automatically.
-                showBiometricPrompt(promptInfo);
+                showBiometricPrompt(promptInfo, mUserId);
                 launchedBiometric = true;
             } else {
                 showConfirmCredentials();
@@ -400,7 +407,19 @@
     // biometric is disabled due to device restart.
     private boolean isStrongAuthRequired(int effectiveUserId) {
         return !mLockPatternUtils.isBiometricAllowedForUser(effectiveUserId)
-                || !mUserManager.isUserUnlocked(mUserId);
+                || doesUserStateEnforceStrongAuth(mUserId);
+    }
+
+    private boolean doesUserStateEnforceStrongAuth(int userId) {
+        if (android.os.Flags.allowPrivateProfile()
+                && android.multiuser.Flags.enableBiometricsToUnlockPrivateSpace()) {
+            // Check if CE storage for user is locked since biometrics can't unlock fbe/keystore of
+            // the profile user using verifyTiedProfileChallenge. Biometrics can still be used if
+            // the user is stopped with delayed locking (i.e., with storage unlocked), so the user
+            // state (whether the user is in the RUNNING_UNLOCKED state) should not be relied upon.
+            return !StorageManager.isUserKeyUnlocked(userId);
+        }
+        return !mUserManager.isUserUnlocked(userId);
     }
 
     private boolean isBiometricAllowed(int effectiveUserId, int realUserId) {
@@ -408,7 +427,7 @@
                 .hasPendingEscrowToken(realUserId);
     }
 
-    private void showBiometricPrompt(PromptInfo promptInfo) {
+    private void showBiometricPrompt(PromptInfo promptInfo, int userId) {
         mBiometricFragment = (BiometricFragment) getSupportFragmentManager()
                 .findFragmentByTag(TAG_BIOMETRIC_FRAGMENT);
         boolean newFragment = false;
@@ -418,7 +437,9 @@
             newFragment = true;
         }
         mBiometricFragment.setCallbacks(mExecutor, mAuthenticationCallback);
-        mBiometricFragment.setUser(mUserId);
+        // TODO(b/315864564): Move the logic of choosing the user id against which the
+        //  authentication needs to happen to the BiometricPrompt API
+        mBiometricFragment.setUser(userId);
 
         if (newFragment) {
             getSupportFragmentManager().beginTransaction()
diff --git a/src/com/android/settings/privatespace/PrivateSpaceMaintainer.java b/src/com/android/settings/privatespace/PrivateSpaceMaintainer.java
index b5e76920..b8f140f 100644
--- a/src/com/android/settings/privatespace/PrivateSpaceMaintainer.java
+++ b/src/com/android/settings/privatespace/PrivateSpaceMaintainer.java
@@ -18,6 +18,7 @@
 
 import static android.os.UserManager.USER_TYPE_PROFILE_PRIVATE;
 import static android.provider.Settings.Secure.HIDE_PRIVATESPACE_ENTRY_POINT;
+import static android.provider.Settings.Secure.USER_SETUP_COMPLETE;
 
 import android.app.ActivityManager;
 import android.app.IActivityManager;
@@ -96,6 +97,7 @@
 
             IActivityManager am = ActivityManager.getService();
             try {
+                //TODO(b/313926659): To check and handle failure of startProfile
                 am.startProfile(mUserHandle.getIdentifier());
             } catch (RemoteException e) {
                 Log.e(TAG, "Failed to start private profile");
@@ -104,6 +106,7 @@
 
             Log.i(TAG, "Private space created with id: " + mUserHandle.getIdentifier());
             resetPrivateSpaceSettings();
+            setUserSetupComplete();
         }
         return true;
     }
@@ -250,4 +253,14 @@
     private void resetPrivateSpaceSettings() {
         setHidePrivateSpaceEntryPointSetting(HIDE_PRIVATE_SPACE_ENTRY_POINT_DISABLED_VAL);
     }
+
+    /**
+     * Sets the USER_SETUP_COMPLETE for private profile on which device theme is applied to the
+     * profile.
+     */
+    @GuardedBy("this")
+    private void setUserSetupComplete() {
+        Settings.Secure.putIntForUser(mContext.getContentResolver(), USER_SETUP_COMPLETE,
+                1, mUserHandle.getIdentifier());
+    }
 }
diff --git a/src/com/android/settings/security/ConfirmSimDeletionPreferenceController.java b/src/com/android/settings/security/ConfirmSimDeletionPreferenceController.java
deleted file mode 100644
index b6b3608..0000000
--- a/src/com/android/settings/security/ConfirmSimDeletionPreferenceController.java
+++ /dev/null
@@ -1,123 +0,0 @@
-/*
- * Copyright (C) 2020 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.security;
-
-import android.app.KeyguardManager;
-import android.app.settings.SettingsEnums;
-import android.content.Context;
-import android.os.UserManager;
-import android.provider.Settings;
-
-import androidx.preference.Preference;
-import androidx.preference.TwoStatePreference;
-
-import com.android.settings.R;
-import com.android.settings.core.BasePreferenceController;
-import com.android.settings.network.helper.ConfirmationSimDeletionPredicate;
-import com.android.settings.network.telephony.MobileNetworkUtils;
-import com.android.settings.overlay.FeatureFactory;
-import com.android.settings.wifi.dpp.WifiDppUtils;
-import com.android.settingslib.core.instrumentation.MetricsFeatureProvider;
-
-/** Enable/disable user confirmation before deleting an eSim */
-public class ConfirmSimDeletionPreferenceController extends BasePreferenceController implements
-        Preference.OnPreferenceChangeListener{
-    public static final String KEY_CONFIRM_SIM_DELETION =
-            ConfirmationSimDeletionPredicate.KEY_CONFIRM_SIM_DELETION;
-    private boolean mConfirmationDefaultOn;
-    private MetricsFeatureProvider mMetricsFeatureProvider;
-    private UserManager mUserManager;
-    private KeyguardManager mKeyguardManager;
-
-    public ConfirmSimDeletionPreferenceController(Context context, String key) {
-        super(context, key);
-        mConfirmationDefaultOn =
-                context.getResources()
-                        .getBoolean(R.bool.config_sim_deletion_confirmation_default_on);
-        mMetricsFeatureProvider = FeatureFactory.getFeatureFactory().getMetricsFeatureProvider();
-
-        mUserManager = context.getSystemService(UserManager.class);
-        mKeyguardManager = mContext.getSystemService(KeyguardManager.class);
-    }
-
-    @Override
-    public int getAvailabilityStatus() {
-        // hide if eSim is not supported on the device
-        return (!MobileNetworkUtils.isMobileNetworkUserRestricted(mContext)) &&
-                MobileNetworkUtils.showEuiccSettings(mContext) ? AVAILABLE : UNSUPPORTED_ON_DEVICE;
-    }
-
-    private boolean getGlobalState() {
-        return Settings.Global.getInt(
-                        mContext.getContentResolver(),
-                        KEY_CONFIRM_SIM_DELETION,
-                        mConfirmationDefaultOn ? 1 : 0)
-                == 1;
-    }
-
-    public boolean isChecked() {
-        return getGlobalState();
-    }
-
-    public boolean setChecked(boolean isChecked) {
-        Settings.Global.putInt(
-                mContext.getContentResolver(), KEY_CONFIRM_SIM_DELETION, isChecked ? 1 : 0);
-        return true;
-    }
-
-    // handle UI change
-    @Override
-    public boolean onPreferenceChange(Preference preference, Object newValue) {
-        if (!preference.getKey().equals(getPreferenceKey())) {
-            return false;
-        }
-        if (!isChecked()) {
-            mMetricsFeatureProvider.action(mContext,
-                    SettingsEnums.ACTION_CONFIRM_SIM_DELETION_ON);
-            setChecked(true);
-            return true;
-        } else {
-            // prevent disabling the feature until authorized
-            WifiDppUtils.showLockScreen(mContext, () -> {
-                mMetricsFeatureProvider.action(mContext,
-                        SettingsEnums.ACTION_CONFIRM_SIM_DELETION_OFF);
-                // set data
-                setChecked(false);
-                // set UI
-                ((TwoStatePreference) preference).setChecked(false);
-            });
-            return false;
-        }
-    }
-
-    @Override
-    public void updateState(Preference preference) {
-        if (!mKeyguardManager.isKeyguardSecure() && mUserManager.isGuestUser()) {
-            preference.setEnabled(false);
-            if (preference instanceof TwoStatePreference) {
-                ((TwoStatePreference) preference).setChecked(false);
-            }
-            preference.setSummary(R.string.disabled_because_no_backup_security);
-        } else {
-            preference.setEnabled(true);
-            if (preference instanceof TwoStatePreference) {
-                ((TwoStatePreference) preference).setChecked(getGlobalState());
-            }
-            preference.setSummary(R.string.confirm_sim_deletion_description);
-        }
-    }
-}
diff --git a/src/com/android/settings/spa/app/appinfo/AppBatteryPreference.kt b/src/com/android/settings/spa/app/appinfo/AppBatteryPreference.kt
index c707b44b..af4fc17 100644
--- a/src/com/android/settings/spa/app/appinfo/AppBatteryPreference.kt
+++ b/src/com/android/settings/spa/app/appinfo/AppBatteryPreference.kt
@@ -49,7 +49,7 @@
 @Composable
 fun AppBatteryPreference(app: ApplicationInfo) {
     val context = LocalContext.current
-    val presenter = remember { AppBatteryPresenter(context, app) }
+    val presenter = remember(app) { AppBatteryPresenter(context, app) }
     if (!presenter.isAvailable()) return
 
     Preference(object : PreferenceModel {
diff --git a/src/com/android/settings/spa/app/appinfo/AppButtons.kt b/src/com/android/settings/spa/app/appinfo/AppButtons.kt
index f6fafd7..403263c 100644
--- a/src/com/android/settings/spa/app/appinfo/AppButtons.kt
+++ b/src/com/android/settings/spa/app/appinfo/AppButtons.kt
@@ -53,6 +53,7 @@
     private val appClearButton = AppClearButton(packageInfoPresenter)
     private val appForceStopButton = AppForceStopButton(packageInfoPresenter)
     private val appArchiveButton = AppArchiveButton(packageInfoPresenter)
+    private val appRestoreButton = AppRestoreButton(packageInfoPresenter)
 
     @Composable
     fun getActionButtons() =
@@ -63,7 +64,11 @@
     @Composable
     private fun getActionButtons(app: ApplicationInfo): List<ActionButton> = listOfNotNull(
         if (featureFlags.archiving()) {
-            appArchiveButton.getActionButton(app)
+            if (app.isArchived) {
+                appRestoreButton.getActionButton(app)
+            } else {
+                appArchiveButton.getActionButton(app)
+            }
         } else {
             appLaunchButton.getActionButton(app)
         },
diff --git a/src/com/android/settings/spa/app/appinfo/AppDataUsagePreference.kt b/src/com/android/settings/spa/app/appinfo/AppDataUsagePreference.kt
index 057f911..7e6e726 100644
--- a/src/com/android/settings/spa/app/appinfo/AppDataUsagePreference.kt
+++ b/src/com/android/settings/spa/app/appinfo/AppDataUsagePreference.kt
@@ -59,7 +59,7 @@
 ) {
     val context = LocalContext.current
     val coroutineScope = rememberCoroutineScope()
-    val presenter = remember {
+    val presenter = remember(app) {
         AppDataUsagePresenter(context, app, coroutineScope, networkTemplates, repositoryFactory)
     }
     if (!presenter.isAvailableFlow.collectAsStateWithLifecycle(initialValue = false).value) return
diff --git a/src/com/android/settings/spa/app/appinfo/AppPermissionSummary.kt b/src/com/android/settings/spa/app/appinfo/AppPermissionSummary.kt
index de6bd10..91c3887 100644
--- a/src/com/android/settings/spa/app/appinfo/AppPermissionSummary.kt
+++ b/src/com/android/settings/spa/app/appinfo/AppPermissionSummary.kt
@@ -39,7 +39,7 @@
 @Composable
 fun rememberAppPermissionSummary(app: ApplicationInfo): AppPermissionSummaryLiveData {
     val context = LocalContext.current
-    return remember { AppPermissionSummaryLiveData(context, app) }
+    return remember(app) { AppPermissionSummaryLiveData(context, app) }
 }
 
 class AppPermissionSummaryLiveData(
@@ -55,7 +55,11 @@
 
     override fun onActive() {
         userPackageManager.addOnPermissionsChangeListener(onPermissionsChangedListener)
-        update()
+        if (app.isArchived) {
+            postValue(noPermissionRequestedState())
+        } else {
+            update()
+        }
     }
 
     override fun onInactive() {
diff --git a/src/com/android/settings/spa/app/appinfo/AppRestoreButton.kt b/src/com/android/settings/spa/app/appinfo/AppRestoreButton.kt
new file mode 100644
index 0000000..c47fdac
--- /dev/null
+++ b/src/com/android/settings/spa/app/appinfo/AppRestoreButton.kt
@@ -0,0 +1,135 @@
+/*
+ * 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.spa.app.appinfo
+
+import android.app.PendingIntent
+import android.content.Intent
+import android.content.IntentFilter
+import android.content.pm.ApplicationInfo
+import android.content.pm.PackageInstaller
+import android.os.UserHandle
+import android.util.Log
+import android.widget.Toast
+import androidx.compose.material.icons.Icons
+import androidx.compose.material.icons.outlined.CloudDownload
+import androidx.compose.runtime.Composable
+import com.android.settings.R
+import com.android.settingslib.spa.widget.button.ActionButton
+import com.android.settingslib.spaprivileged.framework.compose.DisposableBroadcastReceiverAsUser
+
+class AppRestoreButton(packageInfoPresenter: PackageInfoPresenter) {
+    private companion object {
+        private const val LOG_TAG = "AppRestoreButton"
+        private const val INTENT_ACTION = "com.android.settings.unarchive.action"
+    }
+
+    private val context = packageInfoPresenter.context
+    private val userPackageManager = packageInfoPresenter.userPackageManager
+    private val packageInstaller = userPackageManager.packageInstaller
+    private val packageName = packageInfoPresenter.packageName
+    private val userHandle = UserHandle.of(packageInfoPresenter.userId)
+    private var broadcastReceiverIsCreated = false
+
+    @Composable
+    fun getActionButton(app: ApplicationInfo): ActionButton {
+        if (!broadcastReceiverIsCreated) {
+            val intentFilter = IntentFilter(INTENT_ACTION)
+            DisposableBroadcastReceiverAsUser(intentFilter, userHandle) { intent ->
+                if (app.packageName == intent.getStringExtra(PackageInstaller.EXTRA_PACKAGE_NAME)) {
+                    onReceive(intent, app)
+                }
+            }
+            broadcastReceiverIsCreated = true
+        }
+        return ActionButton(
+            text = context.getString(R.string.restore),
+            imageVector = Icons.Outlined.CloudDownload,
+            enabled = app.isArchived
+        ) { onRestoreClicked(app) }
+    }
+
+    private fun onRestoreClicked(app: ApplicationInfo) {
+        val intent = Intent(INTENT_ACTION)
+        intent.setPackage(context.packageName)
+        val pendingIntent = PendingIntent.getBroadcastAsUser(
+            context, 0, intent,
+            // FLAG_MUTABLE is required by PackageInstaller#requestUnarchive
+            PendingIntent.FLAG_ONE_SHOT or PendingIntent.FLAG_MUTABLE,
+            userHandle
+        )
+        try {
+            packageInstaller.requestUnarchive(app.packageName, pendingIntent.intentSender)
+            val appLabel = userPackageManager.getApplicationLabel(app)
+            Toast.makeText(
+                context,
+                context.getString(R.string.restoring_in_progress, appLabel),
+                Toast.LENGTH_SHORT
+            ).show()
+        } catch (e: Exception) {
+            Log.e(LOG_TAG, "Request unarchive failed", e)
+            Toast.makeText(
+                context,
+                context.getString(R.string.restoring_failed),
+                Toast.LENGTH_SHORT
+            ).show()
+        }
+    }
+
+    private fun onReceive(intent: Intent, app: ApplicationInfo) {
+        when (val unarchiveStatus =
+            intent.getIntExtra(PackageInstaller.EXTRA_UNARCHIVE_STATUS, Int.MIN_VALUE)) {
+            PackageInstaller.STATUS_PENDING_USER_ACTION -> {
+                Log.e(
+                    LOG_TAG,
+                    "Request unarchiving failed for $packageName with code $unarchiveStatus"
+                )
+                Toast.makeText(
+                    context,
+                    context.getString(R.string.restoring_failed),
+                    Toast.LENGTH_SHORT
+                ).show()
+            }
+
+            PackageInstaller.STATUS_SUCCESS -> {
+                val appLabel = userPackageManager.getApplicationLabel(app)
+                Toast.makeText(
+                    context,
+                    context.getString(R.string.restoring_succeeded, appLabel),
+                    Toast.LENGTH_SHORT
+                ).show()
+            }
+
+            else -> {
+                Log.e(
+                    LOG_TAG,
+                    "Request unarchiving failed for $packageName with code $unarchiveStatus"
+                )
+                val errorDialogIntent =
+                    intent.getParcelableExtra(Intent.EXTRA_INTENT, Intent::class.java)
+                if (errorDialogIntent != null) {
+                    context.startActivityAsUser(errorDialogIntent, userHandle)
+                } else {
+                    Toast.makeText(
+                        context,
+                        context.getString(R.string.restoring_failed),
+                        Toast.LENGTH_SHORT
+                    ).show()
+                }
+            }
+        }
+    }
+}
diff --git a/src/com/android/settings/spa/app/appinfo/AppTimeSpentPreference.kt b/src/com/android/settings/spa/app/appinfo/AppTimeSpentPreference.kt
index 7ba61dc..837df67 100644
--- a/src/com/android/settings/spa/app/appinfo/AppTimeSpentPreference.kt
+++ b/src/com/android/settings/spa/app/appinfo/AppTimeSpentPreference.kt
@@ -40,7 +40,7 @@
 @Composable
 fun AppTimeSpentPreference(app: ApplicationInfo) {
     val context = LocalContext.current
-    val presenter = remember { AppTimeSpentPresenter(context, app) }
+    val presenter = remember(app) { AppTimeSpentPresenter(context, app) }
     if (!presenter.isAvailable()) return
 
     val summary by presenter.summaryLiveData.observeAsState(
diff --git a/src/com/android/settings/spa/app/appinfo/HibernationSwitchPreference.kt b/src/com/android/settings/spa/app/appinfo/HibernationSwitchPreference.kt
index 78ca15b..324fa06 100644
--- a/src/com/android/settings/spa/app/appinfo/HibernationSwitchPreference.kt
+++ b/src/com/android/settings/spa/app/appinfo/HibernationSwitchPreference.kt
@@ -22,6 +22,7 @@
 import android.app.AppOpsManager.OP_AUTO_REVOKE_PERMISSIONS_IF_UNUSED
 import android.content.Context
 import android.content.pm.ApplicationInfo
+import android.content.pm.Flags
 import android.os.Build
 import android.permission.PermissionControllerManager.HIBERNATION_ELIGIBILITY_EXEMPT_BY_SYSTEM
 import android.permission.PermissionControllerManager.HIBERNATION_ELIGIBILITY_UNKNOWN
@@ -53,15 +54,24 @@
 @Composable
 fun HibernationSwitchPreference(app: ApplicationInfo) {
     val context = LocalContext.current
-    val presenter = remember { HibernationSwitchPresenter(context, app) }
+    val presenter = remember(app) { HibernationSwitchPresenter(context, app) }
     if (!presenter.isAvailable()) return
 
     val isEligibleState by presenter.isEligibleFlow.collectAsStateWithLifecycle(initialValue = false)
     val isCheckedState = presenter.isCheckedFlow.collectAsStateWithLifecycle(initialValue = null)
     SwitchPreference(remember {
         object : SwitchPreferenceModel {
-            override val title = context.getString(R.string.unused_apps_switch)
-            override val summary = { context.getString(R.string.unused_apps_switch_summary) }
+            override val title =
+                if (isArchivingEnabled())
+                    context.getString(R.string.unused_apps_switch_v2)
+                else
+                    context.getString(R.string.unused_apps_switch)
+            override val summary = {
+                if (isArchivingEnabled())
+                    context.getString(R.string.unused_apps_switch_summary_v2)
+                else
+                    context.getString(R.string.unused_apps_switch_summary)
+            }
             override val changeable = { isEligibleState }
             override val checked = { if (changeable()) isCheckedState.value else false }
             override val onCheckedChange = presenter::onCheckedChange
@@ -69,6 +79,9 @@
     })
 }
 
+private fun isArchivingEnabled() =
+        Flags.archiving() || "true" == System.getProperty("pm.archiving.enabled")
+
 private class HibernationSwitchPresenter(context: Context, private val app: ApplicationInfo) {
     private val appOpsManager = context.appOpsManager
     private val permissionControllerManager =
@@ -80,6 +93,10 @@
         DeviceConfig.getBoolean(NAMESPACE_APP_HIBERNATION, PROPERTY_APP_HIBERNATION_ENABLED, true)
 
     val isEligibleFlow = flow {
+        if (app.isArchived) {
+            emit(false)
+            return@flow
+        }
         val eligibility = getEligibility()
         emit(
             eligibility != HIBERNATION_ELIGIBILITY_EXEMPT_BY_SYSTEM &&
diff --git a/tests/robotests/src/com/android/settings/connecteddevice/AvailableMediaDeviceGroupControllerTest.java b/tests/robotests/src/com/android/settings/connecteddevice/AvailableMediaDeviceGroupControllerTest.java
index 06dd42b..e5964d0 100644
--- a/tests/robotests/src/com/android/settings/connecteddevice/AvailableMediaDeviceGroupControllerTest.java
+++ b/tests/robotests/src/com/android/settings/connecteddevice/AvailableMediaDeviceGroupControllerTest.java
@@ -17,20 +17,29 @@
 
 import static com.android.settings.core.BasePreferenceController.AVAILABLE_UNSEARCHABLE;
 import static com.android.settings.core.BasePreferenceController.UNSUPPORTED_ON_DEVICE;
+
 import static com.google.common.truth.Truth.assertThat;
+
 import static org.mockito.ArgumentMatchers.any;
 import static org.mockito.ArgumentMatchers.anyString;
+import static org.mockito.Mockito.doNothing;
 import static org.mockito.Mockito.doReturn;
 import static org.mockito.Mockito.spy;
 import static org.mockito.Mockito.verify;
 import static org.mockito.Mockito.when;
 import static org.robolectric.shadows.ShadowLooper.shadowMainLooper;
 
+import android.bluetooth.BluetoothAdapter;
 import android.bluetooth.BluetoothDevice;
+import android.bluetooth.BluetoothLeBroadcastAssistant;
 import android.bluetooth.BluetoothProfile;
+import android.bluetooth.BluetoothStatusCodes;
 import android.content.Context;
 import android.content.pm.PackageManager;
 import android.media.AudioManager;
+import android.platform.test.annotations.RequiresFlagsEnabled;
+import android.platform.test.flag.junit.CheckFlagsRule;
+import android.platform.test.flag.junit.DeviceFlagsValueProvider;
 
 import androidx.appcompat.app.AlertDialog;
 import androidx.fragment.app.FragmentActivity;
@@ -42,6 +51,7 @@
 import com.android.settings.R;
 import com.android.settings.bluetooth.AvailableMediaBluetoothDeviceUpdater;
 import com.android.settings.bluetooth.Utils;
+import com.android.settings.flags.Flags;
 import com.android.settings.testutils.shadow.ShadowAlertDialogCompat;
 import com.android.settings.testutils.shadow.ShadowAudioManager;
 import com.android.settings.testutils.shadow.ShadowBluetoothAdapter;
@@ -51,9 +61,12 @@
 import com.android.settingslib.bluetooth.CachedBluetoothDevice;
 import com.android.settingslib.bluetooth.CachedBluetoothDeviceManager;
 import com.android.settingslib.bluetooth.HearingAidInfo;
+import com.android.settingslib.bluetooth.LocalBluetoothLeBroadcastAssistant;
 import com.android.settingslib.bluetooth.LocalBluetoothManager;
+import com.android.settingslib.bluetooth.LocalBluetoothProfileManager;
 
 import org.junit.Before;
+import org.junit.Rule;
 import org.junit.Test;
 import org.junit.runner.RunWith;
 import org.mockito.Answers;
@@ -63,42 +76,46 @@
 import org.robolectric.RobolectricTestRunner;
 import org.robolectric.RuntimeEnvironment;
 import org.robolectric.annotation.Config;
+import org.robolectric.shadow.api.Shadow;
+
+import java.util.concurrent.Executor;
 
 /** Tests for {@link AvailableMediaDeviceGroupController}. */
 @RunWith(RobolectricTestRunner.class)
-@Config(shadows = {
-        ShadowAudioManager.class,
-        ShadowBluetoothAdapter.class,
-        ShadowBluetoothUtils.class,
-        ShadowAlertDialogCompat.class,
-})
+@Config(
+        shadows = {
+            ShadowAudioManager.class,
+            ShadowBluetoothAdapter.class,
+            ShadowBluetoothUtils.class,
+            ShadowAlertDialogCompat.class,
+        })
 public class AvailableMediaDeviceGroupControllerTest {
+    @Rule
+    public final CheckFlagsRule mCheckFlagsRule = DeviceFlagsValueProvider.createCheckFlagsRule();
 
     private static final String TEST_DEVICE_ADDRESS = "00:A1:A1:A1:A1:A1";
     private static final String PREFERENCE_KEY_1 = "pref_key_1";
 
-    @Mock
-    private AvailableMediaBluetoothDeviceUpdater mAvailableMediaBluetoothDeviceUpdater;
-    @Mock
-    private PreferenceScreen mPreferenceScreen;
+    @Mock private AvailableMediaBluetoothDeviceUpdater mAvailableMediaBluetoothDeviceUpdater;
+    @Mock private PreferenceScreen mPreferenceScreen;
+
     @Mock(answer = Answers.RETURNS_DEEP_STUBS)
     private PreferenceManager mPreferenceManager;
-    @Mock
-    private PackageManager mPackageManager;
-    @Mock
-    private BluetoothEventManager mEventManager;
-    @Mock
-    private LocalBluetoothManager mLocalBluetoothManager;
-    @Mock
-    private CachedBluetoothDeviceManager mCachedDeviceManager;
-    @Mock
-    private CachedBluetoothDevice mCachedBluetoothDevice;
+
+    @Mock private PackageManager mPackageManager;
+    @Mock private BluetoothEventManager mEventManager;
+    @Mock private LocalBluetoothManager mLocalBluetoothManager;
+    @Mock private LocalBluetoothProfileManager mLocalBtProfileManager;
+    @Mock private CachedBluetoothDeviceManager mCachedDeviceManager;
+    @Mock private LocalBluetoothLeBroadcastAssistant mAssistant;
+    @Mock private CachedBluetoothDevice mCachedBluetoothDevice;
 
     private PreferenceGroup mPreferenceGroup;
     private Context mContext;
     private Preference mPreference;
     private AvailableMediaDeviceGroupController mAvailableMediaDeviceGroupController;
     private AudioManager mAudioManager;
+    private ShadowBluetoothAdapter mShadowBluetoothAdapter;
 
     @Before
     public void setUp() {
@@ -113,19 +130,26 @@
         doReturn(mPackageManager).when(mContext).getPackageManager();
         doReturn(true).when(mPackageManager).hasSystemFeature(PackageManager.FEATURE_BLUETOOTH);
 
+        mShadowBluetoothAdapter = Shadow.extract(BluetoothAdapter.getDefaultAdapter());
+        mShadowBluetoothAdapter.setEnabled(true);
+        mShadowBluetoothAdapter.setIsLeAudioBroadcastSourceSupported(
+                BluetoothStatusCodes.FEATURE_NOT_SUPPORTED);
+        mShadowBluetoothAdapter.setIsLeAudioBroadcastAssistantSupported(
+                BluetoothStatusCodes.FEATURE_NOT_SUPPORTED);
         ShadowBluetoothUtils.sLocalBluetoothManager = mLocalBluetoothManager;
         mLocalBluetoothManager = Utils.getLocalBtManager(mContext);
         mAudioManager = mContext.getSystemService(AudioManager.class);
         doReturn(mEventManager).when(mLocalBluetoothManager).getEventManager();
+        when(mLocalBluetoothManager.getProfileManager()).thenReturn(mLocalBtProfileManager);
         when(mLocalBluetoothManager.getCachedDeviceManager()).thenReturn(mCachedDeviceManager);
-        when(mCachedDeviceManager.findDevice(any(BluetoothDevice.class))).thenReturn(
-                mCachedBluetoothDevice);
+        when(mCachedDeviceManager.findDevice(any(BluetoothDevice.class)))
+                .thenReturn(mCachedBluetoothDevice);
         when(mCachedBluetoothDevice.getAddress()).thenReturn(TEST_DEVICE_ADDRESS);
 
-        mAvailableMediaDeviceGroupController = spy(
-                new AvailableMediaDeviceGroupController(mContext));
-        mAvailableMediaDeviceGroupController.
-                setBluetoothDeviceUpdater(mAvailableMediaBluetoothDeviceUpdater);
+        mAvailableMediaDeviceGroupController =
+                spy(new AvailableMediaDeviceGroupController(mContext));
+        mAvailableMediaDeviceGroupController.setBluetoothDeviceUpdater(
+                mAvailableMediaBluetoothDeviceUpdater);
         mAvailableMediaDeviceGroupController.setFragmentManager(
                 mActivity.getSupportFragmentManager());
         mAvailableMediaDeviceGroupController.mPreferenceGroup = mPreferenceGroup;
@@ -176,34 +200,55 @@
         mAvailableMediaDeviceGroupController.onStart();
 
         verify(mAvailableMediaBluetoothDeviceUpdater).registerCallback();
-        verify(mLocalBluetoothManager.getEventManager()).registerCallback(
-                any(BluetoothCallback.class));
+        verify(mLocalBluetoothManager.getEventManager())
+                .registerCallback(any(BluetoothCallback.class));
         verify(mAvailableMediaBluetoothDeviceUpdater).refreshPreference();
     }
 
     @Test
+    @RequiresFlagsEnabled(Flags.FLAG_ENABLE_LE_AUDIO_SHARING)
+    public void testRegister_audioSharingOn() {
+        setUpBroadcast();
+        // register the callback in onStart()
+        mAvailableMediaDeviceGroupController.onStart();
+        verify(mAssistant)
+                .registerServiceCallBack(
+                        any(Executor.class), any(BluetoothLeBroadcastAssistant.Callback.class));
+    }
+
+    @Test
     public void testUnregister() {
         // unregister the callback in onStop()
         mAvailableMediaDeviceGroupController.onStop();
         verify(mAvailableMediaBluetoothDeviceUpdater).unregisterCallback();
-        verify(mLocalBluetoothManager.getEventManager()).unregisterCallback(
-                any(BluetoothCallback.class));
+        verify(mLocalBluetoothManager.getEventManager())
+                .unregisterCallback(any(BluetoothCallback.class));
+    }
+
+    @Test
+    @RequiresFlagsEnabled(Flags.FLAG_ENABLE_LE_AUDIO_SHARING)
+    public void testUnregister_audioSharingOn() {
+        setUpBroadcast();
+        // unregister the callback in onStop()
+        mAvailableMediaDeviceGroupController.onStop();
+        verify(mAssistant)
+                .unregisterServiceCallBack(any(BluetoothLeBroadcastAssistant.Callback.class));
     }
 
     @Test
     public void testGetAvailabilityStatus_noBluetoothFeature_returnUnSupported() {
         doReturn(false).when(mPackageManager).hasSystemFeature(PackageManager.FEATURE_BLUETOOTH);
 
-        assertThat(mAvailableMediaDeviceGroupController.getAvailabilityStatus()).isEqualTo(
-                UNSUPPORTED_ON_DEVICE);
+        assertThat(mAvailableMediaDeviceGroupController.getAvailabilityStatus())
+                .isEqualTo(UNSUPPORTED_ON_DEVICE);
     }
 
     @Test
     public void testGetAvailabilityStatus_BluetoothFeature_returnSupported() {
         doReturn(true).when(mPackageManager).hasSystemFeature(PackageManager.FEATURE_BLUETOOTH);
 
-        assertThat(mAvailableMediaDeviceGroupController.getAvailabilityStatus()).isEqualTo(
-                AVAILABLE_UNSEARCHABLE);
+        assertThat(mAvailableMediaDeviceGroupController.getAvailabilityStatus())
+                .isEqualTo(AVAILABLE_UNSEARCHABLE);
     }
 
     @Test
@@ -211,8 +256,8 @@
         mAudioManager.setMode(AudioManager.MODE_IN_CALL);
         mAvailableMediaDeviceGroupController.onAudioModeChanged();
 
-        assertThat(mPreferenceGroup.getTitle()).isEqualTo(
-                mContext.getText(R.string.connected_device_call_device_title));
+        assertThat(mPreferenceGroup.getTitle())
+                .isEqualTo(mContext.getText(R.string.connected_device_call_device_title));
     }
 
     @Test
@@ -220,8 +265,8 @@
         mAudioManager.setMode(AudioManager.MODE_NORMAL);
         mAvailableMediaDeviceGroupController.onAudioModeChanged();
 
-        assertThat(mPreferenceGroup.getTitle()).isEqualTo(
-                mContext.getText(R.string.connected_device_media_device_title));
+        assertThat(mPreferenceGroup.getTitle())
+                .isEqualTo(mContext.getText(R.string.connected_device_media_device_title));
     }
 
     @Test
@@ -243,16 +288,31 @@
     @Test
     public void onActiveDeviceChanged_hearingAidProfile_launchHearingAidPairingDialog() {
         when(mCachedBluetoothDevice.isConnectedAshaHearingAidDevice()).thenReturn(true);
-        when(mCachedBluetoothDevice.getDeviceMode()).thenReturn(
-                HearingAidInfo.DeviceMode.MODE_BINAURAL);
-        when(mCachedBluetoothDevice.getDeviceSide()).thenReturn(
-                HearingAidInfo.DeviceSide.SIDE_LEFT);
+        when(mCachedBluetoothDevice.getDeviceMode())
+                .thenReturn(HearingAidInfo.DeviceMode.MODE_BINAURAL);
+        when(mCachedBluetoothDevice.getDeviceSide())
+                .thenReturn(HearingAidInfo.DeviceSide.SIDE_LEFT);
 
-        mAvailableMediaDeviceGroupController.onActiveDeviceChanged(mCachedBluetoothDevice,
-                BluetoothProfile.HEARING_AID);
+        mAvailableMediaDeviceGroupController.onActiveDeviceChanged(
+                mCachedBluetoothDevice, BluetoothProfile.HEARING_AID);
         shadowMainLooper().idle();
 
         final AlertDialog dialog = ShadowAlertDialogCompat.getLatestAlertDialog();
         assertThat(dialog.isShowing()).isTrue();
     }
+
+    private void setUpBroadcast() {
+        mShadowBluetoothAdapter.setIsLeAudioBroadcastSourceSupported(
+                BluetoothStatusCodes.FEATURE_SUPPORTED);
+        mShadowBluetoothAdapter.setIsLeAudioBroadcastAssistantSupported(
+                BluetoothStatusCodes.FEATURE_SUPPORTED);
+        when(mLocalBtProfileManager.getLeAudioBroadcastAssistantProfile()).thenReturn(mAssistant);
+        doNothing()
+                .when(mAssistant)
+                .registerServiceCallBack(
+                        any(Executor.class), any(BluetoothLeBroadcastAssistant.Callback.class));
+        doNothing()
+                .when(mAssistant)
+                .unregisterServiceCallBack(any(BluetoothLeBroadcastAssistant.Callback.class));
+    }
 }
diff --git a/tests/spa_unit/src/com/android/settings/network/telephony/DeleteSimProfilePreferenceControllerTest.kt b/tests/spa_unit/src/com/android/settings/network/telephony/DeleteSimProfilePreferenceControllerTest.kt
index 7285ff8..c132273 100644
--- a/tests/spa_unit/src/com/android/settings/network/telephony/DeleteSimProfilePreferenceControllerTest.kt
+++ b/tests/spa_unit/src/com/android/settings/network/telephony/DeleteSimProfilePreferenceControllerTest.kt
@@ -16,15 +16,15 @@
 
 package com.android.settings.network.telephony
 
+import android.app.KeyguardManager
 import android.content.Context
+import android.os.UserManager
 import android.telephony.SubscriptionInfo
 import androidx.preference.Preference
 import androidx.preference.PreferenceManager
 import androidx.test.core.app.ApplicationProvider
 import androidx.test.ext.junit.runners.AndroidJUnit4
 import com.android.settings.network.SubscriptionUtil
-import com.android.settings.security.ConfirmSimDeletionPreferenceController
-import com.android.settingslib.spaprivileged.settingsprovider.settingsGlobalBoolean
 import com.google.common.truth.Truth.assertThat
 import org.junit.After
 import org.junit.Before
@@ -46,8 +46,13 @@
         on { isEmbedded } doReturn true
     }
 
+    private val mockKeyguardManager = mock<KeyguardManager>() {
+        on { isKeyguardSecure() } doReturn false
+    }
+
     private var context: Context = spy(ApplicationProvider.getApplicationContext()) {
         doNothing().whenever(mock).startActivity(any())
+        on { getSystemService(Context.KEYGUARD_SERVICE) } doReturn mockKeyguardManager
     }
 
     private val preference = Preference(context).apply { key = PREF_KEY }
@@ -103,11 +108,6 @@
     fun onPreferenceClick_startsIntent() {
         controller.init(SUB_ID)
         controller.displayPreference(preferenceScreen)
-        // turn off confirmation before click
-        var confirmDeletion by context.settingsGlobalBoolean(
-            name = ConfirmSimDeletionPreferenceController.KEY_CONFIRM_SIM_DELETION,
-        )
-        confirmDeletion = false
 
         controller.handlePreferenceTreeClick(preference)
 
diff --git a/tests/spa_unit/src/com/android/settings/spa/app/appinfo/AppButtonsTest.kt b/tests/spa_unit/src/com/android/settings/spa/app/appinfo/AppButtonsTest.kt
index 50094f2..6d22c92 100644
--- a/tests/spa_unit/src/com/android/settings/spa/app/appinfo/AppButtonsTest.kt
+++ b/tests/spa_unit/src/com/android/settings/spa/app/appinfo/AppButtonsTest.kt
@@ -140,6 +140,38 @@
         composeTestRule.onNodeWithText(context.getString(R.string.uninstall_text)).assertIsEnabled()
     }
 
+    @Test
+    fun archiveButton_displayed_whenAppIsNotArchived() {
+        featureFlags.setFlag(Flags.FLAG_ARCHIVING, true)
+        val packageInfo = PackageInfo().apply {
+            applicationInfo = ApplicationInfo().apply {
+                packageName = PACKAGE_NAME
+                isArchived = false
+            }
+            packageName = PACKAGE_NAME
+        }
+        setContent(packageInfo)
+
+        composeTestRule.onNodeWithText(context.getString(R.string.archive)).assertIsDisplayed()
+        composeTestRule.onNodeWithText(context.getString(R.string.restore)).assertIsNotDisplayed()
+    }
+
+    @Test
+    fun restoreButton_displayed_whenAppIsArchived() {
+        featureFlags.setFlag(Flags.FLAG_ARCHIVING, true)
+        val packageInfo = PackageInfo().apply {
+            applicationInfo = ApplicationInfo().apply {
+                packageName = PACKAGE_NAME
+                isArchived = true
+            }
+            packageName = PACKAGE_NAME
+        }
+        setContent(packageInfo)
+
+        composeTestRule.onNodeWithText(context.getString(R.string.restore)).assertIsDisplayed()
+        composeTestRule.onNodeWithText(context.getString(R.string.archive)).assertIsNotDisplayed()
+    }
+
     private fun setContent(packageInfo: PackageInfo = PACKAGE_INFO) {
         whenever(packageInfoPresenter.flow).thenReturn(MutableStateFlow(packageInfo))
         composeTestRule.setContent {
diff --git a/tests/spa_unit/src/com/android/settings/spa/app/appinfo/AppRestoreButtonTest.kt b/tests/spa_unit/src/com/android/settings/spa/app/appinfo/AppRestoreButtonTest.kt
new file mode 100644
index 0000000..9d30521
--- /dev/null
+++ b/tests/spa_unit/src/com/android/settings/spa/app/appinfo/AppRestoreButtonTest.kt
@@ -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.spa.app.appinfo
+
+import android.content.Context
+import android.content.pm.ApplicationInfo
+import android.content.pm.PackageInstaller
+import android.content.pm.PackageManager
+import androidx.compose.material.icons.Icons
+import androidx.compose.material.icons.outlined.CloudDownload
+import androidx.compose.ui.test.junit4.createComposeRule
+import androidx.test.annotation.UiThreadTest
+import androidx.test.core.app.ApplicationProvider
+import androidx.test.ext.junit.runners.AndroidJUnit4
+import com.android.settings.R
+import com.android.settingslib.spa.widget.button.ActionButton
+import com.google.common.truth.Truth.assertThat
+import org.junit.Before
+import org.junit.Rule
+import org.junit.Test
+import org.junit.runner.RunWith
+import org.mockito.kotlin.any
+import org.mockito.kotlin.eq
+import org.mockito.kotlin.mock
+import org.mockito.kotlin.spy
+import org.mockito.kotlin.verify
+import org.mockito.kotlin.whenever
+
+@RunWith(AndroidJUnit4::class)
+class AppRestoreButtonTest {
+    @get:Rule
+    val composeTestRule = createComposeRule()
+
+    private val context: Context = spy(ApplicationProvider.getApplicationContext()) {}
+
+    private val packageInfoPresenter = mock<PackageInfoPresenter>()
+
+    private val userPackageManager = mock<PackageManager>()
+
+    private val packageInstaller = mock<PackageInstaller>()
+
+    private lateinit var appRestoreButton: AppRestoreButton
+
+    @Before
+    fun setUp() {
+        whenever(packageInfoPresenter.context).thenReturn(context)
+        whenever(packageInfoPresenter.userPackageManager).thenReturn(userPackageManager)
+        whenever(userPackageManager.packageInstaller).thenReturn(packageInstaller)
+        whenever(packageInfoPresenter.packageName).thenReturn(PACKAGE_NAME)
+        appRestoreButton = AppRestoreButton(packageInfoPresenter)
+    }
+
+    @Test
+    fun appRestoreButton_whenIsNotArchived_isDisabled() {
+        val app = ApplicationInfo().apply {
+            packageName = PACKAGE_NAME
+            isArchived = false
+        }
+
+        val actionButton = setContent(app)
+
+        assertThat(actionButton.enabled).isFalse()
+    }
+
+    @Test
+    fun appRestoreButton_whenIsArchived_isEnabled() {
+        val app = ApplicationInfo().apply {
+            packageName = PACKAGE_NAME
+            isArchived = true
+        }
+
+        val actionButton = setContent(app)
+
+        assertThat(actionButton.enabled).isTrue()
+    }
+
+    @Test
+    fun appRestoreButton_displaysRightTextAndIcon() {
+        val app = ApplicationInfo().apply {
+            packageName = PACKAGE_NAME
+            isArchived = false
+        }
+
+        val actionButton = setContent(app)
+
+        assertThat(actionButton.text).isEqualTo(context.getString(R.string.restore))
+        assertThat(actionButton.imageVector).isEqualTo(Icons.Outlined.CloudDownload)
+    }
+
+    @Test
+    @UiThreadTest
+    fun appRestoreButton_clicked() {
+        val app = ApplicationInfo().apply {
+            packageName = PACKAGE_NAME
+            isArchived = true
+        }
+
+        val actionButton = setContent(app)
+        actionButton.onClick()
+
+        verify(packageInstaller).requestUnarchive(
+            eq(PACKAGE_NAME),
+            any()
+        )
+    }
+
+    private fun setContent(app: ApplicationInfo): ActionButton {
+        lateinit var actionButton: ActionButton
+        composeTestRule.setContent {
+            actionButton = appRestoreButton.getActionButton(app)
+        }
+        return actionButton
+    }
+
+    private companion object {
+        const val PACKAGE_NAME = "package.name"
+    }
+}
diff --git a/tests/spa_unit/src/com/android/settings/spa/app/appinfo/HibernationSwitchPreferenceTest.kt b/tests/spa_unit/src/com/android/settings/spa/app/appinfo/HibernationSwitchPreferenceTest.kt
index 6acdcf5..57e31da 100644
--- a/tests/spa_unit/src/com/android/settings/spa/app/appinfo/HibernationSwitchPreferenceTest.kt
+++ b/tests/spa_unit/src/com/android/settings/spa/app/appinfo/HibernationSwitchPreferenceTest.kt
@@ -24,8 +24,8 @@
 import android.apphibernation.AppHibernationManager
 import android.content.Context
 import android.content.pm.ApplicationInfo
+import android.content.pm.Flags
 import android.os.Build
-import android.os.UserHandle
 import android.permission.PermissionControllerManager
 import android.permission.PermissionControllerManager.HIBERNATION_ELIGIBILITY_ELIGIBLE
 import android.permission.PermissionControllerManager.HIBERNATION_ELIGIBILITY_EXEMPT_BY_SYSTEM
@@ -63,7 +63,6 @@
 import org.mockito.Mockito.anyBoolean
 import org.mockito.Mockito.anyString
 import org.mockito.Mockito.doAnswer
-import org.mockito.Mockito.doReturn
 import org.mockito.Mockito.eq
 import org.mockito.Mockito.never
 import org.mockito.Mockito.verify
@@ -147,12 +146,20 @@
 
         setContent()
 
-        composeTestRule.onNodeWithText(context.getString(R.string.unused_apps_switch))
+        val text = if (isArchivingEnabled()) {
+            context.getString(R.string.unused_apps_switch_v2)
+        } else {
+            context.getString(R.string.unused_apps_switch)
+        }
+        composeTestRule.onNodeWithText(text)
             .assertIsDisplayed()
             .assertIsNotEnabled()
             .assertIsOff()
     }
 
+    private fun isArchivingEnabled() =
+            Flags.archiving() || "true" == System.getProperty("pm.archiving.enabled")
+
     @Test
     fun `An app targets Q with ops mode default when hibernation targets pre S - not exempted`() {
         mockOpsMode(MODE_DEFAULT)
diff --git a/tests/unit/src/com/android/settings/privatespace/PrivateSpaceMaintainerTest.java b/tests/unit/src/com/android/settings/privatespace/PrivateSpaceMaintainerTest.java
index 1d27326..1605ae6 100644
--- a/tests/unit/src/com/android/settings/privatespace/PrivateSpaceMaintainerTest.java
+++ b/tests/unit/src/com/android/settings/privatespace/PrivateSpaceMaintainerTest.java
@@ -198,4 +198,40 @@
         assertThat(privateSpaceMaintainer.doesPrivateSpaceExist()).isFalse();
         assertThat(privateSpaceMaintainer.lockPrivateSpace()).isFalse();
     }
+
+    /**
+     * Tests that {@link PrivateSpaceMaintainer#createPrivateSpace()} when no PS exists sets
+     * USER_SETUP_COMPLETE setting.
+     */
+    @Test
+    public void createPrivateSpace_psDoesNotExist_setsUserSetupComplete() {
+        PrivateSpaceMaintainer privateSpaceMaintainer =
+                PrivateSpaceMaintainer.getInstance(mContext);
+        privateSpaceMaintainer.createPrivateSpace();
+        assertThat(getSecureUserSetupComplete()).isEqualTo(1);
+    }
+
+    /**
+     * Tests that {@link PrivateSpaceMaintainer#createPrivateSpace()} when PS exists does not
+     * change USER_SETUP_COMPLETE setting.
+     */
+    @Test
+    public void createPrivateSpace_pSExists_doesNotChangeUserSetupSetting() {
+        PrivateSpaceMaintainer privateSpaceMaintainer =
+                PrivateSpaceMaintainer.getInstance(mContext);
+        privateSpaceMaintainer.createPrivateSpace();
+        assertThat(getSecureUserSetupComplete()).isEqualTo(1);
+        privateSpaceMaintainer.createPrivateSpace();
+        assertThat(getSecureUserSetupComplete()).isEqualTo(1);
+    }
+
+    private int getSecureUserSetupComplete() {
+        PrivateSpaceMaintainer privateSpaceMaintainer =
+                PrivateSpaceMaintainer.getInstance(mContext);
+        return Settings.Secure.getIntForUser(
+                mContentResolver,
+                Settings.Secure.USER_SETUP_COMPLETE,
+                0,
+                privateSpaceMaintainer.getPrivateProfileHandle().getIdentifier());
+    }
 }