Merge "Make DeviceEntryIconViewModels singletons" into main
diff --git a/Android.bp b/Android.bp
index 59e903e..f6bfe65 100644
--- a/Android.bp
+++ b/Android.bp
@@ -389,7 +389,6 @@
         // TODO(b/120066492): remove gps_debug and protolog.conf.json when the build
         // system propagates "required" properly.
         "gps_debug.conf",
-        "core.protolog.pb",
         "framework-res",
         // any install dependencies should go into framework-minus-apex-install-dependencies
         // rather than here to avoid bloating incremental build time
diff --git a/Ravenwood.bp b/Ravenwood.bp
index f43c37b..c3b22c4 100644
--- a/Ravenwood.bp
+++ b/Ravenwood.bp
@@ -30,7 +30,7 @@
     name: "framework-minus-apex.ravenwood-base",
     tools: ["hoststubgen"],
     cmd: "$(location hoststubgen) " +
-        "@$(location ravenwood/ravenwood-standard-options.txt) " +
+        "@$(location ravenwood/texts/ravenwood-standard-options.txt) " +
 
         "--debug-log $(location hoststubgen_framework-minus-apex.log) " +
         "--stats-file $(location hoststubgen_framework-minus-apex_stats.csv) " +
@@ -41,13 +41,13 @@
         "--gen-input-dump-file $(location hoststubgen_dump.txt) " +
 
         "--in-jar $(location :framework-minus-apex-for-hoststubgen) " +
-        "--policy-override-file $(location ravenwood/framework-minus-apex-ravenwood-policies.txt) " +
-        "--annotation-allowed-classes-file $(location ravenwood/ravenwood-annotation-allowed-classes.txt) ",
+        "--policy-override-file $(location ravenwood/texts/framework-minus-apex-ravenwood-policies.txt) " +
+        "--annotation-allowed-classes-file $(location ravenwood/texts/ravenwood-annotation-allowed-classes.txt) ",
     srcs: [
         ":framework-minus-apex-for-hoststubgen",
-        "ravenwood/framework-minus-apex-ravenwood-policies.txt",
-        "ravenwood/ravenwood-standard-options.txt",
-        "ravenwood/ravenwood-annotation-allowed-classes.txt",
+        "ravenwood/texts/framework-minus-apex-ravenwood-policies.txt",
+        "ravenwood/texts/ravenwood-standard-options.txt",
+        "ravenwood/texts/ravenwood-annotation-allowed-classes.txt",
     ],
     out: [
         "ravenwood.jar",
@@ -77,6 +77,19 @@
     ],
 }
 
+// Extract the stats file.
+genrule {
+    name: "framework-minus-apex.ravenwood.stats",
+    defaults: ["ravenwood-internal-only-visibility-genrule"],
+    cmd: "cp $(in) $(out)",
+    srcs: [
+        ":framework-minus-apex.ravenwood-base{hoststubgen_framework-minus-apex_stats.csv}",
+    ],
+    out: [
+        "hoststubgen_framework-minus-apex_stats.csv",
+    ],
+}
+
 java_library {
     name: "services.core-for-hoststubgen",
     installable: false, // host only jar.
@@ -91,7 +104,7 @@
     name: "services.core.ravenwood-base",
     tools: ["hoststubgen"],
     cmd: "$(location hoststubgen) " +
-        "@$(location ravenwood/ravenwood-standard-options.txt) " +
+        "@$(location ravenwood/texts/ravenwood-standard-options.txt) " +
 
         "--debug-log $(location hoststubgen_services.core.log) " +
         "--stats-file $(location hoststubgen_services.core_stats.csv) " +
@@ -102,13 +115,13 @@
         "--gen-input-dump-file $(location hoststubgen_dump.txt) " +
 
         "--in-jar $(location :services.core-for-hoststubgen) " +
-        "--policy-override-file $(location ravenwood/services.core-ravenwood-policies.txt) " +
-        "--annotation-allowed-classes-file $(location ravenwood/ravenwood-annotation-allowed-classes.txt) ",
+        "--policy-override-file $(location ravenwood/texts/services.core-ravenwood-policies.txt) " +
+        "--annotation-allowed-classes-file $(location ravenwood/texts/ravenwood-annotation-allowed-classes.txt) ",
     srcs: [
         ":services.core-for-hoststubgen",
-        "ravenwood/services.core-ravenwood-policies.txt",
-        "ravenwood/ravenwood-standard-options.txt",
-        "ravenwood/ravenwood-annotation-allowed-classes.txt",
+        "ravenwood/texts/services.core-ravenwood-policies.txt",
+        "ravenwood/texts/ravenwood-standard-options.txt",
+        "ravenwood/texts/ravenwood-annotation-allowed-classes.txt",
     ],
     out: [
         "ravenwood.jar",
@@ -135,6 +148,19 @@
     ],
 }
 
+// Extract the stats file.
+genrule {
+    name: "services.core.ravenwood.stats",
+    defaults: ["ravenwood-internal-only-visibility-genrule"],
+    cmd: "cp $(in) $(out)",
+    srcs: [
+        ":services.core.ravenwood-base{hoststubgen_services.core_stats.csv}",
+    ],
+    out: [
+        "hoststubgen_services.core_stats.csv",
+    ],
+}
+
 java_library {
     name: "services.core.ravenwood-jarjar",
     installable: false,
diff --git a/apex/jobscheduler/service/java/com/android/server/alarm/AlarmManagerService.java b/apex/jobscheduler/service/java/com/android/server/alarm/AlarmManagerService.java
index d0a1b02..154b2d7 100644
--- a/apex/jobscheduler/service/java/com/android/server/alarm/AlarmManagerService.java
+++ b/apex/jobscheduler/service/java/com/android/server/alarm/AlarmManagerService.java
@@ -290,6 +290,8 @@
 
     // List of alarms per uid deferred due to user applied background restrictions on the source app
     SparseArray<ArrayList<Alarm>> mPendingBackgroundAlarms = new SparseArray<>();
+
+    private boolean mStartUserBeforeScheduledAlarms;
     private long mNextWakeup;
     private long mNextNonWakeup;
     private long mNextWakeUpSetAt;
@@ -1382,6 +1384,7 @@
     @GuardedBy("mLock")
     AlarmStore mAlarmStore;
 
+    UserWakeupStore mUserWakeupStore;
     // set to non-null if in idle mode; while in this mode, any alarms we don't want
     // to run during this time are rescehduled to go off after this alarm.
     Alarm mPendingIdleUntil = null;
@@ -1882,6 +1885,7 @@
         mActivityManagerInternal = LocalServices.getService(ActivityManagerInternal.class);
 
         mUseFrozenStateToDropListenerAlarms = Flags.useFrozenStateToDropListenerAlarms();
+        mStartUserBeforeScheduledAlarms = Flags.startUserBeforeScheduledAlarms();
         if (mUseFrozenStateToDropListenerAlarms) {
             final ActivityManager.UidFrozenStateChangedCallback callback = (uids, frozenStates) -> {
                 final int size = frozenStates.length;
@@ -2000,6 +2004,10 @@
                 Slog.w(TAG, "Failed to open alarm driver. Falling back to a handler.");
             }
         }
+        if (mStartUserBeforeScheduledAlarms) {
+            mUserWakeupStore = new UserWakeupStore();
+            mUserWakeupStore.init();
+        }
         publishLocalService(AlarmManagerInternal.class, new LocalService());
         publishBinderService(Context.ALARM_SERVICE, mService);
     }
@@ -2041,6 +2049,9 @@
     public void onUserStarting(TargetUser user) {
         super.onUserStarting(user);
         final int userId = user.getUserIdentifier();
+        if (mStartUserBeforeScheduledAlarms) {
+            mUserWakeupStore.onUserStarting(userId);
+        }
         mHandler.post(() -> {
             for (final int appId : mExactAlarmCandidates) {
                 final int uid = UserHandle.getUid(userId, appId);
@@ -3150,6 +3161,9 @@
             pw.increaseIndent();
             pw.print(Flags.FLAG_USE_FROZEN_STATE_TO_DROP_LISTENER_ALARMS,
                     mUseFrozenStateToDropListenerAlarms);
+            pw.println();
+            pw.print(Flags.FLAG_START_USER_BEFORE_SCHEDULED_ALARMS,
+                    mStartUserBeforeScheduledAlarms);
             pw.decreaseIndent();
             pw.println();
             pw.println();
@@ -3398,6 +3412,12 @@
             pw.println("]");
             pw.println();
 
+            if (mStartUserBeforeScheduledAlarms) {
+                pw.println("Scheduled user wakeups:");
+                mUserWakeupStore.dump(pw, nowELAPSED);
+                pw.println();
+            }
+
             pw.println("App Alarm history:");
             mAppWakeupHistory.dump(pw, nowELAPSED);
 
@@ -3945,10 +3965,19 @@
                         formatNextAlarm(getContext(), alarmClock, userId));
             }
             mNextAlarmClockForUser.put(userId, alarmClock);
+            if (mStartUserBeforeScheduledAlarms) {
+                mUserWakeupStore.addUserWakeup(userId, convertToElapsed(
+                        mNextAlarmClockForUser.get(userId).getTriggerTime(), RTC));
+            }
         } else {
             if (DEBUG_ALARM_CLOCK) {
                 Log.v(TAG, "Next AlarmClockInfoForUser(" + userId + "): None");
             }
+            if (mStartUserBeforeScheduledAlarms) {
+                if (mActivityManagerInternal.isUserRunning(userId, 0)) {
+                    mUserWakeupStore.removeUserWakeup(userId);
+                }
+            }
             mNextAlarmClockForUser.remove(userId);
         }
 
@@ -4003,13 +4032,20 @@
                 DateFormat.format(pattern, info.getTriggerTime()).toString();
     }
 
+    @GuardedBy("mLock")
     void rescheduleKernelAlarmsLocked() {
         // Schedule the next upcoming wakeup alarm.  If there is a deliverable batch
         // prior to that which contains no wakeups, we schedule that as well.
         final long nowElapsed = mInjector.getElapsedRealtimeMillis();
         long nextNonWakeup = 0;
         if (mAlarmStore.size() > 0) {
-            final long firstWakeup = mAlarmStore.getNextWakeupDeliveryTime();
+            long firstWakeup = mAlarmStore.getNextWakeupDeliveryTime();
+            if (mStartUserBeforeScheduledAlarms) {
+                final long firstUserWakeup = mUserWakeupStore.getNextWakeupTime();
+                if (firstUserWakeup >= 0 && firstUserWakeup < firstWakeup) {
+                    firstWakeup = firstUserWakeup;
+                }
+            }
             final long first = mAlarmStore.getNextDeliveryTime();
             if (firstWakeup != 0) {
                 mNextWakeup = firstWakeup;
@@ -4716,6 +4752,16 @@
                                             + ", elapsed=" + nowELAPSED);
                         }
 
+                        if (mStartUserBeforeScheduledAlarms) {
+                            final int[] userIds =
+                                    mUserWakeupStore.getUserIdsToWakeup(nowELAPSED);
+                            for (int i = 0; i < userIds.length; i++) {
+                                if (!mActivityManagerInternal.startUserInBackground(
+                                        userIds[i])) {
+                                    mUserWakeupStore.removeUserWakeup(userIds[i]);
+                                }
+                            }
+                        }
                         mLastTrigger = nowELAPSED;
                         final int wakeUps = triggerAlarmsLocked(triggerList, nowELAPSED);
                         if (wakeUps == 0 && checkAllowNonWakeupDelayLocked(nowELAPSED)) {
@@ -5164,6 +5210,10 @@
             IntentFilter sdFilter = new IntentFilter();
             sdFilter.addAction(Intent.ACTION_EXTERNAL_APPLICATIONS_UNAVAILABLE);
             sdFilter.addAction(Intent.ACTION_USER_STOPPED);
+            if (mStartUserBeforeScheduledAlarms) {
+                sdFilter.addAction(Intent.ACTION_LOCKED_BOOT_COMPLETED);
+                sdFilter.addAction(Intent.ACTION_USER_REMOVED);
+            }
             sdFilter.addAction(Intent.ACTION_UID_REMOVED);
             getContext().registerReceiverForAllUsers(this, sdFilter,
                     /* broadcastPermission */ null, /* scheduler */ null);
@@ -5194,6 +5244,22 @@
                             mTemporaryQuotaReserve.removeForUser(userHandle);
                         }
                         return;
+                    case Intent.ACTION_LOCKED_BOOT_COMPLETED:
+                        final int handle = intent.getIntExtra(Intent.EXTRA_USER_HANDLE, -1);
+                        if (handle >= 0) {
+                            if (mStartUserBeforeScheduledAlarms) {
+                                mUserWakeupStore.onUserStarted(handle);
+                            }
+                        }
+                        return;
+                    case Intent.ACTION_USER_REMOVED:
+                        final int user = intent.getIntExtra(Intent.EXTRA_USER_HANDLE, -1);
+                        if (user >= 0) {
+                            if (mStartUserBeforeScheduledAlarms) {
+                                mUserWakeupStore.onUserRemoved(user);
+                            }
+                        }
+                        return;
                     case Intent.ACTION_UID_REMOVED:
                         mLastPriorityAlarmDispatch.delete(uid);
                         mRemovalHistory.delete(uid);
diff --git a/apex/jobscheduler/service/java/com/android/server/alarm/UserWakeupStore.java b/apex/jobscheduler/service/java/com/android/server/alarm/UserWakeupStore.java
new file mode 100644
index 0000000..a0d9133
--- /dev/null
+++ b/apex/jobscheduler/service/java/com/android/server/alarm/UserWakeupStore.java
@@ -0,0 +1,381 @@
+/*
+ * Copyright (C) 2024 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.server.alarm;
+
+
+import android.annotation.Nullable;
+import android.os.Environment;
+import android.os.SystemClock;
+import android.util.AtomicFile;
+import android.util.IndentingPrintWriter;
+import android.util.Pair;
+import android.util.Slog;
+import android.util.SparseLongArray;
+import android.util.TimeUtils;
+import android.util.Xml;
+
+import com.android.internal.annotations.GuardedBy;
+import com.android.internal.annotations.VisibleForTesting;
+import com.android.internal.os.BackgroundThread;
+import com.android.internal.util.FastXmlSerializer;
+import com.android.internal.util.XmlUtils;
+import com.android.modules.utils.TypedXmlPullParser;
+
+import org.xmlpull.v1.XmlPullParser;
+import org.xmlpull.v1.XmlPullParserException;
+import org.xmlpull.v1.XmlSerializer;
+
+import java.io.File;
+import java.io.FileInputStream;
+import java.io.FileOutputStream;
+import java.io.IOException;
+import java.nio.charset.StandardCharsets;
+import java.util.ArrayList;
+import java.util.Arrays;
+import java.util.Collections;
+import java.util.Comparator;
+import java.util.List;
+import java.util.Random;
+import java.util.concurrent.Executor;
+import java.util.concurrent.TimeUnit;
+
+/**
+ * User wakeup store keeps the list of user ids with the times that user needs to be started in
+ * sorted list in order for alarms to execute even if user gets stopped.
+ * The list of user ids with at least one alarms scheduled is also persisted to the XML file to
+ * start them after the device reboot.
+ */
+public class UserWakeupStore {
+    private static final boolean DEBUG = false;
+
+    static final String USER_WAKEUP_TAG = UserWakeupStore.class.getSimpleName();
+    private static final String TAG_USERS = "users";
+    private static final String TAG_USER = "user";
+    private static final String ATTR_USER_ID = "user_id";
+    private static final String ATTR_VERSION = "version";
+
+    public static final int XML_VERSION_CURRENT = 1;
+    @VisibleForTesting
+    static final String ROOT_DIR_NAME = "alarms";
+    @VisibleForTesting
+    static final String USERS_FILE_NAME = "usersWithAlarmClocks.xml";
+
+    /**
+     * Time offset of user start before the original alarm time in milliseconds.
+     * Also used to schedule user start after reboot to avoid starting them simultaneously.
+     */
+    @VisibleForTesting
+    static final long BUFFER_TIME_MS = TimeUnit.SECONDS.toMillis(30);
+    /**
+     * Maximum time deviation limit to introduce a 5-second time window for user starts.
+     */
+    @VisibleForTesting
+    static final long USER_START_TIME_DEVIATION_LIMIT_MS = TimeUnit.SECONDS.toMillis(5);
+    /**
+     * Delay between two consecutive user starts scheduled during user wakeup store initialization.
+     */
+    @VisibleForTesting
+    static final long INITIAL_USER_START_SCHEDULING_DELAY_MS = TimeUnit.SECONDS.toMillis(5);
+
+    private final Object mUserWakeupLock = new Object();
+
+    /**
+     * A list of wakeups for users with scheduled alarms.
+     */
+    @GuardedBy("mUserWakeupLock")
+    private final SparseLongArray mUserStarts = new SparseLongArray();
+    /**
+     * A list of users that are in a phase after they have been started but before alarms were
+     * initialized.
+     */
+    @GuardedBy("mUserWakeupLock")
+    private final SparseLongArray mStartingUsers = new SparseLongArray();
+    private Executor mBackgroundExecutor;
+    private static final File USER_WAKEUP_DIR = new File(Environment.getDataSystemDirectory(),
+            ROOT_DIR_NAME);
+    private static final Random sRandom = new Random(500);
+
+    /**
+     * Initialize mUserWakeups with persisted values.
+     */
+    public void init() {
+        mBackgroundExecutor = BackgroundThread.getExecutor();
+        mBackgroundExecutor.execute(this::readUserIdList);
+    }
+
+    /**
+     * Add user wakeup for the alarm.
+     * @param userId Id of the user that scheduled alarm.
+     * @param alarmTime time when alarm is expected to trigger.
+     */
+    public void addUserWakeup(int userId, long alarmTime) {
+        synchronized (mUserWakeupLock) {
+            // This should not be needed, but if an app in the user is scheduling an alarm clock, we
+            // consider the user start complete. There is a dedicated removal when user is started.
+            mStartingUsers.delete(userId);
+            mUserStarts.put(userId, alarmTime - BUFFER_TIME_MS + getUserWakeupOffset());
+        }
+        updateUserListFile();
+    }
+
+    /**
+     * Remove wakeup scheduled for the user with given userId if present.
+     */
+    public void removeUserWakeup(int userId) {
+        synchronized (mUserWakeupLock) {
+            mUserStarts.delete(userId);
+        }
+        updateUserListFile();
+    }
+
+    /**
+     * Get ids of users that need to be started now.
+     * @param nowElapsed current time.
+     * @return user ids to be started, or empty if no user needs to be started.
+     */
+    public int[] getUserIdsToWakeup(long nowElapsed) {
+        synchronized (mUserWakeupLock) {
+            final int[] userIds = new int[mUserStarts.size()];
+            int index = 0;
+            for (int i = mUserStarts.size() - 1; i >= 0; i--) {
+                if (mUserStarts.valueAt(i) <= nowElapsed) {
+                    userIds[index++] = mUserStarts.keyAt(i);
+                }
+            }
+            return Arrays.copyOfRange(userIds, 0, index);
+        }
+    }
+
+    /**
+     * Persist user ids that have alarms scheduled so that they can be started after device reboot.
+     */
+    private void updateUserListFile() {
+        mBackgroundExecutor.execute(() -> {
+            try {
+                writeUserIdList();
+                if (DEBUG) {
+                    synchronized (mUserWakeupLock) {
+                        Slog.i(USER_WAKEUP_TAG, "Printing out user wakeups " + mUserStarts.size());
+                        for (int i = 0; i < mUserStarts.size(); i++) {
+                            Slog.i(USER_WAKEUP_TAG, "User id: " + mUserStarts.keyAt(i) + "  time: "
+                                    + mUserStarts.valueAt(i));
+                        }
+                    }
+                }
+            } catch (Exception e) {
+                Slog.e(USER_WAKEUP_TAG, "Failed to write " + e.getLocalizedMessage());
+            }
+        });
+    }
+
+    /**
+     * Return scheduled start time for user or -1 if user does not have alarm set.
+     */
+    @VisibleForTesting
+    long getWakeupTimeForUserForTest(int userId) {
+        synchronized (mUserWakeupLock) {
+            return mUserStarts.get(userId, -1);
+        }
+    }
+
+    /**
+     * Move user from wakeup list to starting user list.
+     */
+    public void onUserStarting(int userId) {
+        synchronized (mUserWakeupLock) {
+            mStartingUsers.put(userId, getWakeupTimeForUserForTest(userId));
+            mUserStarts.delete(userId);
+        }
+    }
+
+    /**
+     * Remove userId from starting user list once start is complete.
+     */
+    public void onUserStarted(int userId) {
+        synchronized (mUserWakeupLock) {
+            mStartingUsers.delete(userId);
+        }
+        updateUserListFile();
+    }
+
+    /**
+     * Remove userId from the store when the user is removed.
+     */
+    public void onUserRemoved(int userId) {
+        synchronized (mUserWakeupLock) {
+            mUserStarts.delete(userId);
+            mStartingUsers.delete(userId);
+        }
+        updateUserListFile();
+    }
+
+    /**
+     * Get the soonest wakeup time in the store.
+     */
+    public long getNextWakeupTime() {
+        long nextWakeupTime = -1;
+        synchronized (mUserWakeupLock) {
+            for (int i = 0; i < mUserStarts.size(); i++) {
+                if (mUserStarts.valueAt(i) < nextWakeupTime || nextWakeupTime == -1) {
+                    nextWakeupTime = mUserStarts.valueAt(i);
+                }
+            }
+        }
+        return nextWakeupTime;
+    }
+
+    private static long getUserWakeupOffset() {
+        return sRandom.nextLong(USER_START_TIME_DEVIATION_LIMIT_MS * 2)
+                - USER_START_TIME_DEVIATION_LIMIT_MS;
+    }
+
+    /**
+     * Write a list of ids for users who have alarm scheduled.
+     * Sample XML file:
+     *
+     * <?xml version='1.0' encoding='utf-8' standalone='yes' ?>
+     * <users version="1">
+     * <user user_id="12" />
+     * <user user_id="10" />
+     * </users>
+     * ~
+     */
+    private void writeUserIdList() {
+        final AtomicFile file = getUserWakeupFile();
+        if (file == null) {
+            return;
+        }
+        try (FileOutputStream fos = file.startWrite(SystemClock.uptimeMillis())) {
+            final XmlSerializer out = new FastXmlSerializer();
+            out.setOutput(fos, StandardCharsets.UTF_8.name());
+            out.startDocument(null, true);
+            out.startTag(null, TAG_USERS);
+            XmlUtils.writeIntAttribute(out, ATTR_VERSION, XML_VERSION_CURRENT);
+            final List<Pair<Integer, Long>> listOfUsers = new ArrayList<>();
+            synchronized (mUserWakeupLock) {
+                for (int i = 0; i < mUserStarts.size(); i++) {
+                    listOfUsers.add(new Pair<>(mUserStarts.keyAt(i), mUserStarts.valueAt(i)));
+                }
+                for (int i = 0; i < mStartingUsers.size(); i++) {
+                    listOfUsers.add(new Pair<>(mStartingUsers.keyAt(i), mStartingUsers.valueAt(i)));
+                }
+            }
+            Collections.sort(listOfUsers, Comparator.comparingLong(pair -> pair.second));
+            for (int i = 0; i < listOfUsers.size(); i++) {
+                out.startTag(null, TAG_USER);
+                XmlUtils.writeIntAttribute(out, ATTR_USER_ID, listOfUsers.get(i).first);
+                out.endTag(null, TAG_USER);
+            }
+            out.endTag(null, TAG_USERS);
+            out.endDocument();
+            file.finishWrite(fos);
+        } catch (IOException e) {
+            Slog.wtf(USER_WAKEUP_TAG, "Error writing user wakeup data", e);
+            file.delete();
+        }
+    }
+
+    private void readUserIdList() {
+        final AtomicFile userWakeupFile = getUserWakeupFile();
+        if (userWakeupFile == null) {
+            return;
+        } else if (!userWakeupFile.exists()) {
+            Slog.w(USER_WAKEUP_TAG, "User wakeup file not available: "
+                    + userWakeupFile.getBaseFile());
+            return;
+        }
+        synchronized (mUserWakeupLock) {
+            mUserStarts.clear();
+            mStartingUsers.clear();
+        }
+        try (FileInputStream fis = userWakeupFile.openRead()) {
+            final TypedXmlPullParser parser = Xml.resolvePullParser(fis);
+            int type;
+            while ((type = parser.next()) != XmlPullParser.START_TAG
+                    && type != XmlPullParser.END_DOCUMENT) {
+                // Skip
+            }
+            if (type != XmlPullParser.START_TAG) {
+                Slog.e(USER_WAKEUP_TAG, "Unable to read user list. No start tag found in "
+                        + userWakeupFile.getBaseFile());
+                return;
+            }
+            int version = -1;
+            if (parser.getName().equals(TAG_USERS)) {
+                version = parser.getAttributeInt(null, ATTR_VERSION, version);
+            }
+
+            long counter = 0;
+            final long currentTime = SystemClock.elapsedRealtime();
+            // Time delay between now and first user wakeup is scheduled.
+            final long scheduleOffset = currentTime + BUFFER_TIME_MS + getUserWakeupOffset();
+            while ((type = parser.next()) != XmlPullParser.END_DOCUMENT) {
+                if (type == XmlPullParser.START_TAG) {
+                    if (parser.getName().equals(TAG_USER)) {
+                        final int id = parser.getAttributeInt(null, ATTR_USER_ID);
+                        synchronized (mUserWakeupLock) {
+                            mUserStarts.put(id, scheduleOffset + (counter++
+                                    * INITIAL_USER_START_SCHEDULING_DELAY_MS));
+                        }
+                    }
+                }
+            }
+        } catch (IOException | XmlPullParserException e) {
+            Slog.wtf(USER_WAKEUP_TAG, "Error reading user wakeup data", e);
+        }
+    }
+
+    @Nullable
+    private AtomicFile getUserWakeupFile() {
+        if (!USER_WAKEUP_DIR.exists() && !USER_WAKEUP_DIR.mkdir()) {
+            Slog.wtf(USER_WAKEUP_TAG, "Failed to mkdir() user list file: " + USER_WAKEUP_DIR);
+            return null;
+        }
+        final File userFile = new File(USER_WAKEUP_DIR, USERS_FILE_NAME);
+        return new AtomicFile(userFile);
+    }
+
+    void dump(IndentingPrintWriter pw, long nowELAPSED) {
+        synchronized (mUserWakeupLock) {
+            pw.increaseIndent();
+            pw.print("User wakeup store file path: ");
+            final AtomicFile file = getUserWakeupFile();
+            if (file == null) {
+                pw.println("null");
+            } else {
+                pw.println(file.getBaseFile().getAbsolutePath());
+            }
+            pw.println(mUserStarts.size() + " user wakeups scheduled: ");
+            for (int i = 0; i < mUserStarts.size(); i++) {
+                pw.print("UserId: ");
+                pw.print(mUserStarts.keyAt(i));
+                pw.print(", userStartTime: ");
+                TimeUtils.formatDuration(mUserStarts.valueAt(i), nowELAPSED, pw);
+                pw.println();
+            }
+            pw.println(mStartingUsers.size() + " starting users: ");
+            for (int i = 0; i < mStartingUsers.size(); i++) {
+                pw.print("UserId: ");
+                pw.print(mStartingUsers.keyAt(i));
+                pw.print(", userStartTime: ");
+                TimeUtils.formatDuration(mStartingUsers.valueAt(i), nowELAPSED, pw);
+                pw.println();
+            }
+            pw.decreaseIndent();
+        }
+    }
+}
diff --git a/core/api/current.txt b/core/api/current.txt
index 1cfc025..b19c3ab 100644
--- a/core/api/current.txt
+++ b/core/api/current.txt
@@ -10147,17 +10147,17 @@
 
   public interface ComponentCallbacks {
     method public void onConfigurationChanged(@NonNull android.content.res.Configuration);
-    method public void onLowMemory();
+    method @Deprecated public void onLowMemory();
   }
 
   public interface ComponentCallbacks2 extends android.content.ComponentCallbacks {
     method public void onTrimMemory(int);
     field public static final int TRIM_MEMORY_BACKGROUND = 40; // 0x28
-    field public static final int TRIM_MEMORY_COMPLETE = 80; // 0x50
-    field public static final int TRIM_MEMORY_MODERATE = 60; // 0x3c
-    field public static final int TRIM_MEMORY_RUNNING_CRITICAL = 15; // 0xf
-    field public static final int TRIM_MEMORY_RUNNING_LOW = 10; // 0xa
-    field public static final int TRIM_MEMORY_RUNNING_MODERATE = 5; // 0x5
+    field @Deprecated public static final int TRIM_MEMORY_COMPLETE = 80; // 0x50
+    field @Deprecated public static final int TRIM_MEMORY_MODERATE = 60; // 0x3c
+    field @Deprecated public static final int TRIM_MEMORY_RUNNING_CRITICAL = 15; // 0xf
+    field @Deprecated public static final int TRIM_MEMORY_RUNNING_LOW = 10; // 0xa
+    field @Deprecated public static final int TRIM_MEMORY_RUNNING_MODERATE = 5; // 0x5
     field public static final int TRIM_MEMORY_UI_HIDDEN = 20; // 0x14
   }
 
diff --git a/core/api/test-current.txt b/core/api/test-current.txt
index a73aa71..6189703 100644
--- a/core/api/test-current.txt
+++ b/core/api/test-current.txt
@@ -3676,6 +3676,10 @@
     field public static final int FLAG_IS_ACCESSIBILITY_EVENT = 2048; // 0x800
   }
 
+  public final class PointerIcon implements android.os.Parcelable {
+    method @FlaggedApi("android.view.flags.enable_vector_cursors") public void setDrawNativeDropShadow(boolean);
+  }
+
   @java.lang.annotation.Retention(java.lang.annotation.RetentionPolicy.RUNTIME) @java.lang.annotation.Target({java.lang.annotation.ElementType.METHOD}) public @interface RemotableViewMethod {
     method public abstract String asyncImpl() default "";
   }
@@ -3977,6 +3981,7 @@
 
   public final class InputMethodManager {
     method @RequiresPermission(android.Manifest.permission.TEST_INPUT_METHOD) public void addVirtualStylusIdForTestSession();
+    method @RequiresPermission(android.Manifest.permission.TEST_INPUT_METHOD) public void finishTrackingPendingImeVisibilityRequests();
     method public int getDisplayId();
     method @FlaggedApi("android.view.inputmethod.imm_userhandle_hostsidetests") @NonNull @RequiresPermission(value=android.Manifest.permission.INTERACT_ACROSS_USERS_FULL, conditional=true) public java.util.List<android.view.inputmethod.InputMethodInfo> getEnabledInputMethodListAsUser(@NonNull android.os.UserHandle);
     method @FlaggedApi("android.view.inputmethod.imm_userhandle_hostsidetests") @NonNull @RequiresPermission(value=android.Manifest.permission.INTERACT_ACROSS_USERS_FULL, conditional=true) public java.util.List<android.view.inputmethod.InputMethodSubtype> getEnabledInputMethodSubtypeListAsUser(@NonNull String, boolean, @NonNull android.os.UserHandle);
diff --git a/core/java/android/app/ActivityManagerInternal.java b/core/java/android/app/ActivityManagerInternal.java
index 5843e51..e66f7fe 100644
--- a/core/java/android/app/ActivityManagerInternal.java
+++ b/core/java/android/app/ActivityManagerInternal.java
@@ -148,6 +148,13 @@
     public abstract void onUserRemoved(@UserIdInt int userId);
 
     /**
+     * Start user, if it is not already running, but don't bring it to foreground.
+     * @param userId ID of the user to start
+     * @return true if the user has been successfully started
+     */
+    public abstract boolean startUserInBackground(int userId);
+
+    /**
      * Kill foreground apps from the specified user.
      */
     public abstract void killForegroundAppsForUser(@UserIdInt int userId);
diff --git a/core/java/android/app/AppOpsManager.java b/core/java/android/app/AppOpsManager.java
index 0ed25eb..7ed10e7 100644
--- a/core/java/android/app/AppOpsManager.java
+++ b/core/java/android/app/AppOpsManager.java
@@ -18,7 +18,7 @@
 
 
 import static android.location.flags.Flags.FLAG_LOCATION_BYPASS;
-import static android.media.audio.Flags.foregroundAudioControl;
+import static android.media.audio.Flags.roForegroundAudioControl;
 import static android.permission.flags.Flags.FLAG_OP_ENABLE_MOBILE_DATA_BY_USER;
 import static android.view.contentprotection.flags.Flags.FLAG_CREATE_ACCESSIBILITY_OVERLAY_APP_OP_ENABLED;
 import static android.view.contentprotection.flags.Flags.FLAG_RAPID_CLEAR_NOTIFICATIONS_BY_LISTENER_APP_OP_ENABLED;
@@ -3246,7 +3246,7 @@
      * @hide
      */
     public static @Mode int opToDefaultMode(int op) {
-        if (op == OP_TAKE_AUDIO_FOCUS && foregroundAudioControl()) {
+        if (op == OP_TAKE_AUDIO_FOCUS && roForegroundAudioControl()) {
             // when removing the flag, change the entry in sAppOpInfos for OP_TAKE_AUDIO_FOCUS
             return AppOpsManager.MODE_FOREGROUND;
         }
diff --git a/core/java/android/app/IActivityManager.aidl b/core/java/android/app/IActivityManager.aidl
index 5e6b54b..84bc6ce 100644
--- a/core/java/android/app/IActivityManager.aidl
+++ b/core/java/android/app/IActivityManager.aidl
@@ -962,4 +962,12 @@
      */
     oneway void frozenBinderTransactionDetected(int debugPid, int code, int flags, int err);
     int getBindingUidProcessState(int uid, in String callingPackage);
+
+    /**
+     * Return the timestampe (in the elapsed timebase) when the UID became idle from active
+     * last time (regardless of if the UID is still idle, or became active again).
+     * This is useful when trying to detect whether an UID has ever became idle since a certain
+     * time in the past.
+     */
+    long getUidLastIdleElapsedTime(int uid, in String callingPackage);
 }
diff --git a/core/java/android/app/IActivityTaskManager.aidl b/core/java/android/app/IActivityTaskManager.aidl
index 08636ae..55ce90d 100644
--- a/core/java/android/app/IActivityTaskManager.aidl
+++ b/core/java/android/app/IActivityTaskManager.aidl
@@ -157,7 +157,6 @@
     ParceledListSlice<ActivityManager.RecentTaskInfo> getRecentTasks(int maxNum, int flags,
             int userId);
     boolean isTopActivityImmersive();
-    ActivityManager.TaskDescription getTaskDescription(int taskId);
     void reportAssistContextExtras(in IBinder assistToken, in Bundle extras,
             in AssistStructure structure, in AssistContent content, in Uri referrer);
 
diff --git a/core/java/android/app/ResourcesManager.java b/core/java/android/app/ResourcesManager.java
index b1e7b62..370aff8 100644
--- a/core/java/android/app/ResourcesManager.java
+++ b/core/java/android/app/ResourcesManager.java
@@ -120,6 +120,17 @@
     private final ReferenceQueue<Resources> mResourcesReferencesQueue = new ReferenceQueue<>();
 
     /**
+     * A list of Resources references for all Resources instances created through Resources public
+     * constructor, only system Resources created by the private constructor are excluded.
+     * This addition is necessary due to certain Application Resources created by constructor
+     * directly which are not managed by ResourcesManager, hence we require a comprehensive
+     * collection of all Resources references to help with asset paths appending tasks when shared
+     * libraries are registered.
+     */
+    private final ArrayList<WeakReference<Resources>> mAllResourceReferences = new ArrayList<>();
+    private final ReferenceQueue<Resources> mAllResourceReferencesQueue = new ReferenceQueue<>();
+
+    /**
      * The localeConfig of the app.
      */
     private LocaleConfig mLocaleConfig = new LocaleConfig(LocaleList.getEmptyLocaleList());
@@ -1568,7 +1579,7 @@
                 }
             }
 
-            redirectResourcesToNewImplLocked(updatedResourceKeys);
+            redirectAllResourcesToNewImplLocked(updatedResourceKeys);
         }
     }
 
@@ -1707,6 +1718,43 @@
         }
     }
 
+    // Another redirect function which will loop through all Resources and reload ResourcesImpl
+    // if it needs a shared library asset paths update.
+    private void redirectAllResourcesToNewImplLocked(
+            @NonNull final ArrayMap<ResourcesImpl, ResourcesKey> updatedResourceKeys) {
+        cleanupReferences(mAllResourceReferences, mAllResourceReferencesQueue);
+
+        // Update any references to ResourcesImpl that require reloading.
+        final int resourcesCount = mAllResourceReferences.size();
+        for (int i = 0; i < resourcesCount; i++) {
+            final WeakReference<Resources> ref = mAllResourceReferences.get(i);
+            final Resources r = ref != null ? ref.get() : null;
+            if (r != null) {
+                final ResourcesKey key = updatedResourceKeys.get(r.getImpl());
+                if (key != null) {
+                    final ResourcesImpl impl = findOrCreateResourcesImplForKeyLocked(key);
+                    if (impl == null) {
+                        throw new Resources.NotFoundException("failed to redirect ResourcesImpl");
+                    }
+                    r.setImpl(impl);
+                } else {
+                    // ResourcesKey is null which means the ResourcesImpl could belong to a
+                    // Resources created by application through Resources constructor and was not
+                    // managed by ResourcesManager, so the ResourcesImpl needs to be recreated to
+                    // have shared library asset paths appended if there are any.
+                    if (r.getImpl() != null) {
+                        final ResourcesImpl oldImpl = r.getImpl();
+                        // ResourcesImpl constructor will help to append shared library asset paths.
+                        final ResourcesImpl newImpl = new ResourcesImpl(oldImpl.getAssets(),
+                                oldImpl.getMetrics(), oldImpl.getConfiguration(),
+                                oldImpl.getDisplayAdjustments());
+                        r.setImpl(newImpl);
+                    }
+                }
+            }
+        }
+    }
+
     /**
      * Returns the LocaleConfig current set
      */
@@ -1827,4 +1875,17 @@
     public @NonNull ArrayMap<String, SharedLibraryAssets> getSharedLibAssetsMap() {
         return new ArrayMap<>(mSharedLibAssetsMap);
     }
+
+    /**
+     * Add all resources references to the list which is designed to help to append shared library
+     * asset paths. This is invoked in Resources constructor to include all Resources instances.
+     */
+    public void registerAllResourcesReference(@NonNull Resources resources) {
+        if (android.content.res.Flags.registerResourcePaths()) {
+            synchronized (mLock) {
+                mAllResourceReferences.add(
+                        new WeakReference<>(resources, mAllResourceReferencesQueue));
+            }
+        }
+    }
 }
diff --git a/core/java/android/app/admin/flags/flags.aconfig b/core/java/android/app/admin/flags/flags.aconfig
index be0aaff..25697c5 100644
--- a/core/java/android/app/admin/flags/flags.aconfig
+++ b/core/java/android/app/admin/flags/flags.aconfig
@@ -197,8 +197,8 @@
   description: "Fix provisioning for single-user headless DO"
   bug: "289515470"
   metadata {
-      purpose: PURPOSE_BUGFIX
-    }
+    purpose: PURPOSE_BUGFIX
+  }
 }
 
 flag {
@@ -214,3 +214,13 @@
   description: "Guards a new flow for recursive required enterprise app list merging"
   bug: "319084618"
 }
+
+flag {
+  name: "headless_device_owner_delegate_security_logging_bug_fix"
+  namespace: "enterprise"
+  description: "Fix delegate security logging for single user headless DO."
+  bug: "289515470"
+  metadata {
+    purpose: PURPOSE_BUGFIX
+  }
+}
diff --git a/core/java/android/companion/CompanionDeviceManager.java b/core/java/android/companion/CompanionDeviceManager.java
index 2c26389..a08d659 100644
--- a/core/java/android/companion/CompanionDeviceManager.java
+++ b/core/java/android/companion/CompanionDeviceManager.java
@@ -26,6 +26,7 @@
 import android.annotation.IntDef;
 import android.annotation.NonNull;
 import android.annotation.Nullable;
+import android.annotation.RequiresFeature;
 import android.annotation.RequiresPermission;
 import android.annotation.SuppressLint;
 import android.annotation.SystemApi;
@@ -80,25 +81,27 @@
 import java.util.function.Consumer;
 
 /**
- * System level service for managing companion devices
+ * Public interfaces for managing companion devices.
  *
- * See <a href="{@docRoot}guide/topics/connectivity/companion-device-pairing">this guide</a>
- * for a usage example.
+ * <p>The interfaces in this class allow companion apps to
+ * {@link #associate(AssociationRequest, Executor, Callback)} discover and request device profiles}
+ * for companion devices, {@link #startObservingDevicePresence(String) listen to device presence
+ * events}, {@link #startSystemDataTransfer(int, Executor, OutcomeReceiver) transfer system level
+ * data} via {@link #attachSystemDataTransport(int, InputStream, OutputStream) the reported
+ * channel} and more.</p>
  *
- * <p>To obtain an instance call {@link Context#getSystemService}({@link
- * Context#COMPANION_DEVICE_SERVICE}) Then, call {@link #associate(AssociationRequest,
- * Callback, Handler)} to initiate the flow of associating current package with a
- * device selected by user.</p>
- *
- * @see CompanionDeviceManager#associate
- * @see AssociationRequest
+ * <div class="special reference">
+ * <h3>Developer Guides</h3>
+ * <p>For more information about managing companion devices, read the <a href=
+ * "{@docRoot}guide/topics/connectivity/companion-device-pairing">Companion Device Pairing</a>
+ * developer guide.
+ * </div>
  */
 @SuppressLint("LongLogTag")
 @SystemService(Context.COMPANION_DEVICE_SERVICE)
+@RequiresFeature(PackageManager.FEATURE_COMPANION_DEVICE_SETUP)
 public final class CompanionDeviceManager {
-
-    private static final boolean DEBUG = false;
-    private static final String LOG_TAG = "CDM_CompanionDeviceManager";
+    private static final String TAG = "CDM_CompanionDeviceManager";
 
     /** @hide */
     @IntDef(prefix = {"RESULT_"}, value = {
@@ -374,7 +377,7 @@
     }
 
     private final ICompanionDeviceManager mService;
-    private Context mContext;
+    private final Context mContext;
 
     @GuardedBy("mListeners")
     private final ArrayList<OnAssociationsChangedListenerProxy> mListeners = new ArrayList<>();
@@ -432,7 +435,11 @@
             @NonNull AssociationRequest request,
             @NonNull Callback callback,
             @Nullable Handler handler) {
-        if (!checkFeaturePresent()) return;
+        if (mService == null) {
+            Log.w(TAG, "CompanionDeviceManager service is not available.");
+            return;
+        }
+
         Objects.requireNonNull(request, "Request cannot be null");
         Objects.requireNonNull(callback, "Callback cannot be null");
         handler = Handler.mainIfNull(handler);
@@ -492,7 +499,11 @@
             @NonNull AssociationRequest request,
             @NonNull Executor executor,
             @NonNull Callback callback) {
-        if (!checkFeaturePresent()) return;
+        if (mService == null) {
+            Log.w(TAG, "CompanionDeviceManager service is not available.");
+            return;
+        }
+
         Objects.requireNonNull(request, "Request cannot be null");
         Objects.requireNonNull(executor, "Executor cannot be null");
         Objects.requireNonNull(callback, "Callback cannot be null");
@@ -521,7 +532,10 @@
     @UserHandleAware
     @Nullable
     public IntentSender buildAssociationCancellationIntent() {
-        if (!checkFeaturePresent()) return null;
+        if (mService == null) {
+            Log.w(TAG, "CompanionDeviceManager service is not available.");
+            return null;
+        }
 
         try {
             PendingIntent pendingIntent = mService.buildAssociationCancellationIntent(
@@ -543,7 +557,8 @@
      * @param flags system data types to be enabled.
      */
     public void enableSystemDataSyncForTypes(int associationId, @DataSyncTypes int flags) {
-        if (!checkFeaturePresent()) {
+        if (mService == null) {
+            Log.w(TAG, "CompanionDeviceManager service is not available.");
             return;
         }
 
@@ -565,7 +580,8 @@
      * @param flags system data types to be disabled.
      */
     public void disableSystemDataSyncForTypes(int associationId, @DataSyncTypes int flags) {
-        if (!checkFeaturePresent()) {
+        if (mService == null) {
+            Log.w(TAG, "CompanionDeviceManager service is not available.");
             return;
         }
 
@@ -580,6 +596,11 @@
      * @hide
      */
     public void enablePermissionsSync(int associationId) {
+        if (mService == null) {
+            Log.w(TAG, "CompanionDeviceManager service is not available.");
+            return;
+        }
+
         try {
             mService.enablePermissionsSync(associationId);
         } catch (RemoteException e) {
@@ -591,6 +612,11 @@
      * @hide
      */
     public void disablePermissionsSync(int associationId) {
+        if (mService == null) {
+            Log.w(TAG, "CompanionDeviceManager service is not available.");
+            return;
+        }
+
         try {
             mService.disablePermissionsSync(associationId);
         } catch (RemoteException e) {
@@ -602,6 +628,11 @@
      * @hide
      */
     public PermissionSyncRequest getPermissionSyncRequest(int associationId) {
+        if (mService == null) {
+            Log.w(TAG, "CompanionDeviceManager service is not available.");
+            return null;
+        }
+
         try {
             return mService.getPermissionSyncRequest(associationId);
         } catch (RemoteException e) {
@@ -636,7 +667,10 @@
     @UserHandleAware
     @NonNull
     public List<AssociationInfo> getMyAssociations() {
-        if (!checkFeaturePresent()) return Collections.emptyList();
+        if (mService == null) {
+            Log.w(TAG, "CompanionDeviceManager service is not available.");
+            return Collections.emptyList();
+        }
 
         try {
             return mService.getAssociations(mContext.getOpPackageName(), mContext.getUserId());
@@ -665,7 +699,10 @@
     @UserHandleAware
     @Deprecated
     public void disassociate(@NonNull String deviceMacAddress) {
-        if (!checkFeaturePresent()) return;
+        if (mService == null) {
+            Log.w(TAG, "CompanionDeviceManager service is not available.");
+            return;
+        }
 
         try {
             mService.legacyDisassociate(deviceMacAddress, mContext.getOpPackageName(),
@@ -690,7 +727,10 @@
      */
     @UserHandleAware
     public void disassociate(int associationId) {
-        if (!checkFeaturePresent()) return;
+        if (mService == null) {
+            Log.w(TAG, "CompanionDeviceManager service is not available.");
+            return;
+        }
 
         try {
             mService.disassociate(associationId);
@@ -716,9 +756,11 @@
      */
     @UserHandleAware
     public void requestNotificationAccess(ComponentName component) {
-        if (!checkFeaturePresent()) {
+        if (mService == null) {
+            Log.w(TAG, "CompanionDeviceManager service is not available.");
             return;
         }
+
         try {
             PendingIntent pendingIntent = mService.requestNotificationAccess(
                     component, mContext.getUserId());
@@ -755,9 +797,11 @@
      */
     @Deprecated
     public boolean hasNotificationAccess(ComponentName component) {
-        if (!checkFeaturePresent()) {
+        if (mService == null) {
+            Log.w(TAG, "CompanionDeviceManager service is not available.");
             return false;
         }
+
         try {
             return mService.hasNotificationAccess(component);
         } catch (RemoteException e) {
@@ -793,7 +837,11 @@
             @NonNull String packageName,
             @NonNull MacAddress macAddress,
             @NonNull UserHandle user) {
-        if (!checkFeaturePresent()) return false;
+        if (mService == null) {
+            Log.w(TAG, "CompanionDeviceManager service is not available.");
+            return false;
+        }
+
         Objects.requireNonNull(packageName, "package name cannot be null");
         Objects.requireNonNull(macAddress, "mac address cannot be null");
         Objects.requireNonNull(user, "user cannot be null");
@@ -816,7 +864,8 @@
     @SystemApi
     @UserHandleAware
     @RequiresPermission(android.Manifest.permission.MANAGE_COMPANION_DEVICES)
-    public @NonNull List<AssociationInfo> getAllAssociations() {
+    @NonNull
+    public List<AssociationInfo> getAllAssociations() {
         return getAllAssociations(mContext.getUserId());
     }
 
@@ -826,8 +875,13 @@
      * @hide
      */
     @RequiresPermission(android.Manifest.permission.MANAGE_COMPANION_DEVICES)
-    public @NonNull List<AssociationInfo> getAllAssociations(@UserIdInt int userId) {
-        if (!checkFeaturePresent()) return Collections.emptyList();
+    @NonNull
+    public List<AssociationInfo> getAllAssociations(@UserIdInt int userId) {
+        if (mService == null) {
+            Log.w(TAG, "CompanionDeviceManager service is not available.");
+            return Collections.emptyList();
+        }
+
         try {
             return mService.getAllAssociationsForUser(userId);
         } catch (RemoteException e) {
@@ -875,7 +929,11 @@
     public void addOnAssociationsChangedListener(
             @NonNull Executor executor, @NonNull OnAssociationsChangedListener listener,
             @UserIdInt int userId) {
-        if (!checkFeaturePresent()) return;
+        if (mService == null) {
+            Log.w(TAG, "CompanionDeviceManager service is not available.");
+            return;
+        }
+
         synchronized (mListeners) {
             final OnAssociationsChangedListenerProxy proxy = new OnAssociationsChangedListenerProxy(
                     executor, listener);
@@ -899,7 +957,11 @@
     @RequiresPermission(android.Manifest.permission.MANAGE_COMPANION_DEVICES)
     public void removeOnAssociationsChangedListener(
             @NonNull OnAssociationsChangedListener listener) {
-        if (!checkFeaturePresent()) return;
+        if (mService == null) {
+            Log.w(TAG, "CompanionDeviceManager service is not available.");
+            return;
+        }
+
         synchronized (mListeners) {
             final Iterator<OnAssociationsChangedListenerProxy> iterator = mListeners.iterator();
             while (iterator.hasNext()) {
@@ -931,6 +993,11 @@
     public void addOnTransportsChangedListener(
             @NonNull @CallbackExecutor Executor executor,
             @NonNull Consumer<List<AssociationInfo>> listener) {
+        if (mService == null) {
+            Log.w(TAG, "CompanionDeviceManager service is not available.");
+            return;
+        }
+
         final OnTransportsChangedListenerProxy proxy = new OnTransportsChangedListenerProxy(
                 executor, listener);
         try {
@@ -950,6 +1017,11 @@
     @RequiresPermission(android.Manifest.permission.USE_COMPANION_TRANSPORTS)
     public void removeOnTransportsChangedListener(
             @NonNull Consumer<List<AssociationInfo>> listener) {
+        if (mService == null) {
+            Log.w(TAG, "CompanionDeviceManager service is not available.");
+            return;
+        }
+
         final OnTransportsChangedListenerProxy proxy = new OnTransportsChangedListenerProxy(
                 null, listener);
         try {
@@ -969,6 +1041,11 @@
      */
     @RequiresPermission(android.Manifest.permission.USE_COMPANION_TRANSPORTS)
     public void sendMessage(int messageType, @NonNull byte[] data, @NonNull int[] associationIds) {
+        if (mService == null) {
+            Log.w(TAG, "CompanionDeviceManager service is not available.");
+            return;
+        }
+
         try {
             mService.sendMessage(messageType, data, associationIds);
         } catch (RemoteException e) {
@@ -989,6 +1066,11 @@
     public void addOnMessageReceivedListener(
             @NonNull @CallbackExecutor Executor executor, int messageType,
             @NonNull BiConsumer<Integer, byte[]> listener) {
+        if (mService == null) {
+            Log.w(TAG, "CompanionDeviceManager service is not available.");
+            return;
+        }
+
         final OnMessageReceivedListenerProxy proxy = new OnMessageReceivedListenerProxy(
                 executor, listener);
         try {
@@ -1006,6 +1088,11 @@
     @RequiresPermission(android.Manifest.permission.USE_COMPANION_TRANSPORTS)
     public void removeOnMessageReceivedListener(int messageType,
             @NonNull BiConsumer<Integer, byte[]> listener) {
+        if (mService == null) {
+            Log.w(TAG, "CompanionDeviceManager service is not available.");
+            return;
+        }
+
         final OnMessageReceivedListenerProxy proxy = new OnMessageReceivedListenerProxy(
                 null, listener);
         try {
@@ -1031,9 +1118,11 @@
     @RequiresPermission(android.Manifest.permission.MANAGE_COMPANION_DEVICES)
     public boolean canPairWithoutPrompt(@NonNull String packageName,
             @NonNull String deviceMacAddress, @NonNull UserHandle user) {
-        if (!checkFeaturePresent()) {
+        if (mService == null) {
+            Log.w(TAG, "CompanionDeviceManager service is not available.");
             return false;
         }
+
         Objects.requireNonNull(packageName, "package name cannot be null");
         Objects.requireNonNull(deviceMacAddress, "device mac address cannot be null");
         Objects.requireNonNull(user, "user handle cannot be null");
@@ -1081,9 +1170,11 @@
     @RequiresPermission(android.Manifest.permission.REQUEST_OBSERVE_COMPANION_DEVICE_PRESENCE)
     public void startObservingDevicePresence(@NonNull String deviceAddress)
             throws DeviceNotAssociatedException {
-        if (!checkFeaturePresent()) {
+        if (mService == null) {
+            Log.w(TAG, "CompanionDeviceManager service is not available.");
             return;
         }
+
         Objects.requireNonNull(deviceAddress, "address cannot be null");
         try {
             mService.legacyStartObservingDevicePresence(deviceAddress,
@@ -1123,9 +1214,11 @@
     @RequiresPermission(android.Manifest.permission.REQUEST_OBSERVE_COMPANION_DEVICE_PRESENCE)
     public void stopObservingDevicePresence(@NonNull String deviceAddress)
             throws DeviceNotAssociatedException {
-        if (!checkFeaturePresent()) {
+        if (mService == null) {
+            Log.w(TAG, "CompanionDeviceManager service is not available.");
             return;
         }
+
         Objects.requireNonNull(deviceAddress, "address cannot be null");
         try {
             mService.legacyStopObservingDevicePresence(deviceAddress,
@@ -1171,6 +1264,11 @@
     @FlaggedApi(Flags.FLAG_DEVICE_PRESENCE)
     @RequiresPermission(android.Manifest.permission.REQUEST_OBSERVE_COMPANION_DEVICE_PRESENCE)
     public void startObservingDevicePresence(@NonNull ObservingDevicePresenceRequest request) {
+        if (mService == null) {
+            Log.w(TAG, "CompanionDeviceManager service is not available.");
+            return;
+        }
+
         Objects.requireNonNull(request, "request cannot be null");
 
         try {
@@ -1192,6 +1290,11 @@
     @FlaggedApi(Flags.FLAG_DEVICE_PRESENCE)
     @RequiresPermission(android.Manifest.permission.REQUEST_OBSERVE_COMPANION_DEVICE_PRESENCE)
     public void stopObservingDevicePresence(@NonNull ObservingDevicePresenceRequest request) {
+        if (mService == null) {
+            Log.w(TAG, "CompanionDeviceManager service is not available.");
+            return;
+        }
+
         Objects.requireNonNull(request, "request cannot be null");
 
         try {
@@ -1222,7 +1325,7 @@
     @RequiresPermission(android.Manifest.permission.DELIVER_COMPANION_MESSAGES)
     public void dispatchMessage(int messageId, int associationId, @NonNull byte[] message)
             throws DeviceNotAssociatedException {
-        Log.w(LOG_TAG, "dispatchMessage replaced by attachSystemDataTransport");
+        Log.w(TAG, "dispatchMessage replaced by attachSystemDataTransport");
     }
 
     /**
@@ -1244,6 +1347,11 @@
     @RequiresPermission(android.Manifest.permission.DELIVER_COMPANION_MESSAGES)
     public void attachSystemDataTransport(int associationId, @NonNull InputStream in,
             @NonNull OutputStream out) throws DeviceNotAssociatedException {
+        if (mService == null) {
+            Log.w(TAG, "CompanionDeviceManager service is not available.");
+            return;
+        }
+
         synchronized (mTransports) {
             if (mTransports.contains(associationId)) {
                 detachSystemDataTransport(associationId);
@@ -1272,6 +1380,11 @@
     @RequiresPermission(android.Manifest.permission.DELIVER_COMPANION_MESSAGES)
     public void detachSystemDataTransport(int associationId)
             throws DeviceNotAssociatedException {
+        if (mService == null) {
+            Log.w(TAG, "CompanionDeviceManager service is not available.");
+            return;
+        }
+
         synchronized (mTransports) {
             final Transport transport = mTransports.get(associationId);
             if (transport != null) {
@@ -1296,9 +1409,11 @@
             @NonNull String packageName,
             @NonNull MacAddress macAddress,
             @NonNull byte[] certificate) {
-        if (!checkFeaturePresent()) {
+        if (mService == null) {
+            Log.w(TAG, "CompanionDeviceManager service is not available.");
             return;
         }
+
         Objects.requireNonNull(packageName, "package name cannot be null");
         Objects.requireNonNull(macAddress, "mac address cannot be null");
 
@@ -1327,6 +1442,11 @@
     @SystemApi
     @RequiresPermission(android.Manifest.permission.REQUEST_COMPANION_SELF_MANAGED)
     public void notifyDeviceAppeared(int associationId) {
+        if (mService == null) {
+            Log.w(TAG, "CompanionDeviceManager service is not available.");
+            return;
+        }
+
         try {
             mService.notifySelfManagedDeviceAppeared(associationId);
         } catch (RemoteException e) {
@@ -1349,6 +1469,11 @@
     @SystemApi
     @RequiresPermission(android.Manifest.permission.REQUEST_COMPANION_SELF_MANAGED)
     public void notifyDeviceDisappeared(int associationId) {
+        if (mService == null) {
+            Log.w(TAG, "CompanionDeviceManager service is not available.");
+            return;
+        }
+
         try {
             mService.notifySelfManagedDeviceDisappeared(associationId);
         } catch (RemoteException e) {
@@ -1384,6 +1509,11 @@
     @Nullable
     public IntentSender buildPermissionTransferUserConsentIntent(int associationId)
             throws DeviceNotAssociatedException {
+        if (mService == null) {
+            Log.w(TAG, "CompanionDeviceManager service is not available.");
+            return null;
+        }
+
         try {
             PendingIntent pendingIntent = mService.buildPermissionTransferUserConsentIntent(
                     mContext.getOpPackageName(),
@@ -1420,6 +1550,11 @@
     @UserHandleAware
     @FlaggedApi(Flags.FLAG_PERM_SYNC_USER_CONSENT)
     public boolean isPermissionTransferUserConsented(int associationId) {
+        if (mService == null) {
+            Log.w(TAG, "CompanionDeviceManager service is not available.");
+            return false;
+        }
+
         try {
             return mService.isPermissionTransferUserConsented(mContext.getOpPackageName(),
                     mContext.getUserId(), associationId);
@@ -1446,6 +1581,11 @@
     @Deprecated
     @UserHandleAware
     public void startSystemDataTransfer(int associationId) throws DeviceNotAssociatedException {
+        if (mService == null) {
+            Log.w(TAG, "CompanionDeviceManager service is not available.");
+            return;
+        }
+
         try {
             mService.startSystemDataTransfer(mContext.getOpPackageName(), mContext.getUserId(),
                     associationId, null);
@@ -1478,6 +1618,11 @@
             @NonNull Executor executor,
             @NonNull OutcomeReceiver<Void, CompanionException> result)
             throws DeviceNotAssociatedException {
+        if (mService == null) {
+            Log.w(TAG, "CompanionDeviceManager service is not available.");
+            return;
+        }
+
         try {
             mService.startSystemDataTransfer(mContext.getOpPackageName(), mContext.getUserId(),
                     associationId, new SystemDataTransferCallbackProxy(executor, result));
@@ -1495,6 +1640,11 @@
      */
     @UserHandleAware
     public boolean isCompanionApplicationBound() {
+        if (mService == null) {
+            Log.w(TAG, "CompanionDeviceManager service is not available.");
+            return false;
+        }
+
         try {
             return mService.isCompanionApplicationBound(
                     mContext.getOpPackageName(), mContext.getUserId());
@@ -1513,6 +1663,11 @@
     @TestApi
     @RequiresPermission(android.Manifest.permission.MANAGE_COMPANION_DEVICES)
     public void enableSecureTransport(boolean enabled) {
+        if (mService == null) {
+            Log.w(TAG, "CompanionDeviceManager service is not available.");
+            return;
+        }
+
         try {
             mService.enableSecureTransport(enabled);
         } catch (RemoteException e) {
@@ -1534,6 +1689,11 @@
     @FlaggedApi(Flags.FLAG_ASSOCIATION_TAG)
     @UserHandleAware
     public void setAssociationTag(int associationId, @NonNull String tag) {
+        if (mService == null) {
+            Log.w(TAG, "CompanionDeviceManager service is not available.");
+            return;
+        }
+
         Objects.requireNonNull(tag, "tag cannot be null");
 
         if (tag.length() > ASSOCIATION_TAG_LENGTH_LIMIT) {
@@ -1560,6 +1720,11 @@
     @FlaggedApi(Flags.FLAG_ASSOCIATION_TAG)
     @UserHandleAware
     public void clearAssociationTag(int associationId) {
+        if (mService == null) {
+            Log.w(TAG, "CompanionDeviceManager service is not available.");
+            return;
+        }
+
         try {
             mService.clearAssociationTag(associationId);
         } catch (RemoteException e) {
@@ -1567,15 +1732,6 @@
         }
     }
 
-    private boolean checkFeaturePresent() {
-        boolean featurePresent = mService != null;
-        if (!featurePresent && DEBUG) {
-            Log.d(LOG_TAG, "Feature " + PackageManager.FEATURE_COMPANION_DEVICE_SETUP
-                    + " not available");
-        }
-        return featurePresent;
-    }
-
     private static class AssociationRequestCallbackProxy extends IAssociationRequestCallback.Stub {
         private final Handler mHandler;
         private final Callback mCallback;
@@ -1613,7 +1769,7 @@
         private <T> void execute(Consumer<T> callback, T arg) {
             if (mExecutor != null) {
                 mExecutor.execute(() -> callback.accept(arg));
-            } else {
+            } else if (mHandler != null) {
                 mHandler.post(() -> callback.accept(arg));
             }
         }
@@ -1716,6 +1872,11 @@
         }
 
         public void start() throws IOException {
+            if (mService == null) {
+                Log.w(TAG, "CompanionDeviceManager service is not available.");
+                return;
+            }
+
             final ParcelFileDescriptor[] pair = ParcelFileDescriptor.createSocketPair();
             final ParcelFileDescriptor localFd = pair[0];
             final ParcelFileDescriptor remoteFd = pair[1];
@@ -1734,7 +1895,7 @@
                     copyWithFlushing(mLocalIn, mRemoteOut);
                 } catch (IOException e) {
                     if (!mStopped) {
-                        Log.w(LOG_TAG, "Trouble during outgoing transport", e);
+                        Log.w(TAG, "Trouble during outgoing transport", e);
                         stop();
                     }
                 }
@@ -1744,7 +1905,7 @@
                     copyWithFlushing(mRemoteIn, mLocalOut);
                 } catch (IOException e) {
                     if (!mStopped) {
-                        Log.w(LOG_TAG, "Trouble during incoming transport", e);
+                        Log.w(TAG, "Trouble during incoming transport", e);
                         stop();
                     }
                 }
@@ -1752,13 +1913,18 @@
         }
 
         public void stop() {
+            if (mService == null) {
+                Log.w(TAG, "CompanionDeviceManager service is not available.");
+                return;
+            }
+
             mStopped = true;
 
             try {
                 mService.detachSystemDataTransport(mContext.getPackageName(),
                         mContext.getUserId(), mAssociationId);
             } catch (RemoteException e) {
-                Log.w(LOG_TAG, "Failed to detach transport", e);
+                Log.w(TAG, "Failed to detach transport", e);
             }
 
             IoUtils.closeQuietly(mRemoteIn);
diff --git a/core/java/android/content/ComponentCallbacks.java b/core/java/android/content/ComponentCallbacks.java
index 428545d..fb9536f 100644
--- a/core/java/android/content/ComponentCallbacks.java
+++ b/core/java/android/content/ComponentCallbacks.java
@@ -55,15 +55,11 @@
      * That is, before reaching the point of killing processes hosting
      * service and foreground UI that we would like to avoid killing.
      *
-     * <p>You should implement this method to release
-     * any caches or other unnecessary resources you may be holding on to.
-     * The system will perform a garbage collection for you after returning from this method.
-     * <p>Preferably, you should implement {@link ComponentCallbacks2#onTrimMemory} from
-     * {@link ComponentCallbacks2} to incrementally unload your resources based on various
-     * levels of memory demands.  That API is available for API level 14 and higher, so you should
-     * only use this {@link #onLowMemory} method as a fallback for older versions, which can be
-     * treated the same as {@link ComponentCallbacks2#onTrimMemory} with the {@link
-     * ComponentCallbacks2#TRIM_MEMORY_COMPLETE} level.</p>
+     * @deprecated Since API level 14 this is superseded by
+     *             {@link ComponentCallbacks2#onTrimMemory}.
+     *             Since API level 34 this is never called.
+     *             Apps targeting API level 34 and above may provide an empty implementation.
      */
+    @Deprecated
     void onLowMemory();
 }
diff --git a/core/java/android/content/ComponentCallbacks2.java b/core/java/android/content/ComponentCallbacks2.java
index 6576c0f..a165b18 100644
--- a/core/java/android/content/ComponentCallbacks2.java
+++ b/core/java/android/content/ComponentCallbacks2.java
@@ -105,14 +105,20 @@
      * Level for {@link #onTrimMemory(int)}: the process is nearing the end
      * of the background LRU list, and if more memory isn't found soon it will
      * be killed.
+     *
+     * @deprecated Apps are not notified of this level since API level 34
      */
+    @Deprecated
     static final int TRIM_MEMORY_COMPLETE = 80;
     
     /**
      * Level for {@link #onTrimMemory(int)}: the process is around the middle
      * of the background LRU list; freeing memory can help the system keep
      * other processes running later in the list for better overall performance.
+     *
+     * @deprecated Apps are not notified of this level since API level 34
      */
+    @Deprecated
     static final int TRIM_MEMORY_MODERATE = 60;
     
     /**
@@ -139,7 +145,10 @@
      * will happen after this is {@link #onLowMemory()} called to report that
      * nothing at all can be kept in the background, a situation that can start
      * to notably impact the user.
+     *
+     * @deprecated Apps are not notified of this level since API level 34
      */
+    @Deprecated
     static final int TRIM_MEMORY_RUNNING_CRITICAL = 15;
 
     /**
@@ -147,7 +156,10 @@
      * background process, but the device is running low on memory.
      * Your running process should free up unneeded resources to allow that
      * memory to be used elsewhere.
+     *
+     * @deprecated Apps are not notified of this level since API level 34
      */
+    @Deprecated
     static final int TRIM_MEMORY_RUNNING_LOW = 10;
 
     /**
@@ -155,17 +167,19 @@
      * background process, but the device is running moderately low on memory.
      * Your running process may want to release some unneeded resources for
      * use elsewhere.
+     *
+     * @deprecated Apps are not notified of this level since API level 34
      */
+    @Deprecated
     static final int TRIM_MEMORY_RUNNING_MODERATE = 5;
 
     /**
      * Called when the operating system has determined that it is a good
-     * time for a process to trim unneeded memory from its process.  This will
-     * happen for example when it goes in the background and there is not enough
-     * memory to keep as many background processes running as desired.  You
-     * should never compare to exact values of the level, since new intermediate
-     * values may be added -- you will typically want to compare if the value
-     * is greater or equal to a level you are interested in.
+     * time for a process to trim unneeded memory from its process.
+     *
+     * You should never compare to exact values of the level, since new
+     * intermediate values may be added -- you will typically want to compare if
+     * the value is greater or equal to a level you are interested in.
      *
      * <p>To retrieve the processes current trim level at any point, you can
      * use {@link android.app.ActivityManager#getMyMemoryState
diff --git a/core/java/android/content/pm/IPackageManager.aidl b/core/java/android/content/pm/IPackageManager.aidl
index bff90f1..5d4babb 100644
--- a/core/java/android/content/pm/IPackageManager.aidl
+++ b/core/java/android/content/pm/IPackageManager.aidl
@@ -847,5 +847,5 @@
     @EnforcePermission("GET_APP_METADATA")
     int getAppMetadataSource(String packageName, int userId);
 
-    ComponentName getDomainVerificationAgent();
+    ComponentName getDomainVerificationAgent(int userId);
 }
diff --git a/core/java/android/content/pm/PackageManager.java b/core/java/android/content/pm/PackageManager.java
index 4c0da7c..f5bff9d 100644
--- a/core/java/android/content/pm/PackageManager.java
+++ b/core/java/android/content/pm/PackageManager.java
@@ -4844,6 +4844,16 @@
     public static final String FEATURE_ROTARY_ENCODER_LOW_RES =
             "android.hardware.rotaryencoder.lowres";
 
+  /**
+   * Feature for {@link #getSystemAvailableFeatures} and {@link #hasSystemFeature}: The device has
+   * support for contextual search helper.
+   *
+   * @hide
+   */
+  @SdkConstant(SdkConstantType.FEATURE)
+  public static final String FEATURE_CONTEXTUAL_SEARCH_HELPER =
+      "android.software.contextualsearch";
+
     /** @hide */
     public static final boolean APP_ENUMERATION_ENABLED_BY_DEFAULT = true;
 
diff --git a/core/java/android/content/res/Resources.java b/core/java/android/content/res/Resources.java
index 1f5f88f..a720b64 100644
--- a/core/java/android/content/res/Resources.java
+++ b/core/java/android/content/res/Resources.java
@@ -339,14 +339,17 @@
     public Resources(@Nullable ClassLoader classLoader) {
         mClassLoader = classLoader == null ? ClassLoader.getSystemClassLoader() : classLoader;
         sResourcesHistory.add(this);
+        ResourcesManager.getInstance().registerAllResourcesReference(this);
     }
 
     /**
-     * Only for creating the System resources.
+     * Only for creating the System resources. This is the only constructor that doesn't add
+     * Resources itself to the ResourcesManager list of all Resources references.
      */
     @UnsupportedAppUsage
     private Resources() {
-        this(null);
+        mClassLoader = ClassLoader.getSystemClassLoader();
+        sResourcesHistory.add(this);
 
         final DisplayMetrics metrics = new DisplayMetrics();
         metrics.setToDefaults();
diff --git a/core/java/android/content/res/ResourcesImpl.java b/core/java/android/content/res/ResourcesImpl.java
index 4c22fee..31cacb7 100644
--- a/core/java/android/content/res/ResourcesImpl.java
+++ b/core/java/android/content/res/ResourcesImpl.java
@@ -228,6 +228,11 @@
         return mAssets;
     }
 
+    @UnsupportedAppUsage
+    public DisplayMetrics getMetrics() {
+        return mMetrics;
+    }
+
     @UnsupportedAppUsage(maxTargetSdk = Build.VERSION_CODES.R, trackingBug = 170729553)
     DisplayMetrics getDisplayMetrics() {
         if (DEBUG_CONFIG) Slog.v(TAG, "Returning DisplayMetrics: " + mMetrics.widthPixels
@@ -235,7 +240,8 @@
         return mMetrics;
     }
 
-    Configuration getConfiguration() {
+    @UnsupportedAppUsage
+    public Configuration getConfiguration() {
         return mConfiguration;
     }
 
diff --git a/core/java/android/hardware/camera2/CameraManager.java b/core/java/android/hardware/camera2/CameraManager.java
index 8feb133..ea7db4c 100644
--- a/core/java/android/hardware/camera2/CameraManager.java
+++ b/core/java/android/hardware/camera2/CameraManager.java
@@ -1081,6 +1081,12 @@
      * {@link java.util.concurrent.Executor} as an argument instead of
      * {@link android.os.Handler}.</p>
      *
+     * <p>Do note that typically callbacks are expected to be dispatched
+     * by the executor in a single thread. If the executor uses two or
+     * more threads to dispatch callbacks, then clients must ensure correct
+     * synchronization and must also be able to handle potentially different
+     * ordering of the incoming callbacks.</p>
+     *
      * @param cameraId
      *             The unique identifier of the camera device to open
      * @param executor
diff --git a/core/java/android/hardware/camera2/impl/CameraDeviceImpl.java b/core/java/android/hardware/camera2/impl/CameraDeviceImpl.java
index 97c03ed..81bb9ac 100644
--- a/core/java/android/hardware/camera2/impl/CameraDeviceImpl.java
+++ b/core/java/android/hardware/camera2/impl/CameraDeviceImpl.java
@@ -290,6 +290,55 @@
         }
     };
 
+    private class ClientStateCallback extends StateCallback {
+        private final Executor mClientExecutor;
+        private final StateCallback mClientStateCallback;
+
+        private ClientStateCallback(@NonNull Executor clientExecutor,
+                @NonNull StateCallback clientStateCallback) {
+            mClientExecutor = clientExecutor;
+            mClientStateCallback = clientStateCallback;
+        }
+
+        public void onClosed(@NonNull CameraDevice camera) {
+            mClientExecutor.execute(new Runnable() {
+                @Override
+                public void run() {
+                    mClientStateCallback.onClosed(camera);
+                }
+            });
+        }
+        @Override
+        public void onOpened(@NonNull CameraDevice camera) {
+            mClientExecutor.execute(new Runnable() {
+                @Override
+                public void run() {
+                    mClientStateCallback.onOpened(camera);
+                }
+            });
+        }
+
+        @Override
+        public void onDisconnected(@NonNull CameraDevice camera) {
+            mClientExecutor.execute(new Runnable() {
+                @Override
+                public void run() {
+                    mClientStateCallback.onDisconnected(camera);
+                }
+            });
+        }
+
+        @Override
+        public void onError(@NonNull CameraDevice camera, int error) {
+            mClientExecutor.execute(new Runnable() {
+                @Override
+                public void run() {
+                    mClientStateCallback.onError(camera, error);
+                }
+            });
+        }
+    }
+
     public CameraDeviceImpl(String cameraId, StateCallback callback, Executor executor,
                         CameraCharacteristics characteristics,
                         Map<String, CameraCharacteristics> physicalIdsToChars,
@@ -300,8 +349,13 @@
             throw new IllegalArgumentException("Null argument given");
         }
         mCameraId = cameraId;
-        mDeviceCallback = callback;
-        mDeviceExecutor = executor;
+        if (Flags.singleThreadExecutor()) {
+            mDeviceCallback = new ClientStateCallback(executor, callback);
+            mDeviceExecutor = Executors.newSingleThreadExecutor();
+        } else {
+            mDeviceCallback = callback;
+            mDeviceExecutor = executor;
+        }
         mCharacteristics = characteristics;
         mPhysicalIdsToChars = physicalIdsToChars;
         mAppTargetSdkVersion = appTargetSdkVersion;
diff --git a/core/java/android/hardware/face/FaceSensorConfigurations.java b/core/java/android/hardware/face/FaceSensorConfigurations.java
index 6ef692f..1247168 100644
--- a/core/java/android/hardware/face/FaceSensorConfigurations.java
+++ b/core/java/android/hardware/face/FaceSensorConfigurations.java
@@ -22,11 +22,12 @@
 import android.content.Context;
 import android.hardware.biometrics.face.IFace;
 import android.hardware.biometrics.face.SensorProps;
+import android.os.Binder;
 import android.os.Parcel;
 import android.os.Parcelable;
 import android.os.RemoteException;
+import android.os.ServiceManager;
 import android.util.Log;
-import android.util.Pair;
 import android.util.Slog;
 
 import androidx.annotation.NonNull;
@@ -36,7 +37,6 @@
 import java.util.List;
 import java.util.Map;
 import java.util.Optional;
-import java.util.function.Function;
 
 /**
  * Provides the sensor props for face sensor, if available.
@@ -74,22 +74,10 @@
     /**
      * Process AIDL instances to extract sensor props and add it to the sensor map.
      * @param aidlInstances available face AIDL instances
-     * @param getIFace function that provides the daemon for the specific instance
      */
-    public void addAidlConfigs(@NonNull String[] aidlInstances,
-            @NonNull Function<String, IFace> getIFace) {
+    public void addAidlConfigs(@NonNull String[] aidlInstances) {
         for (String aidlInstance : aidlInstances) {
-            final String fqName = IFace.DESCRIPTOR + "/" + aidlInstance;
-            IFace face = getIFace.apply(fqName);
-            try {
-                if (face != null) {
-                    mSensorPropsMap.put(aidlInstance, face.getSensorProps());
-                } else {
-                    Slog.e(TAG, "Unable to get declared service: " + fqName);
-                }
-            } catch (RemoteException e) {
-                Log.d(TAG, "Unable to get sensor properties!");
-            }
+            mSensorPropsMap.put(aidlInstance, null);
         }
     }
 
@@ -131,38 +119,31 @@
     }
 
     /**
-     * Return sensor props for the given instance. If instance is not available,
-     * then null is returned.
+     * Checks if {@param instance} exists.
      */
     @Nullable
-    public Pair<String, SensorProps[]> getSensorPairForInstance(String instance) {
-        if (mSensorPropsMap.containsKey(instance)) {
-            return new Pair<>(instance, mSensorPropsMap.get(instance));
-        }
-
-        return null;
+    public boolean doesInstanceExist(String instance) {
+        return mSensorPropsMap.containsKey(instance);
     }
 
     /**
-     * Return the first pair of instance and sensor props, which does not correspond to the given
-     * If instance is not available, then null is returned.
+     * Return the first HAL instance, which does not correspond to the given {@param instance}.
+     * If another instance is not available, then null is returned.
      */
     @Nullable
-    public Pair<String, SensorProps[]> getSensorPairNotForInstance(String instance) {
+    public String getSensorNameNotForInstance(String instance) {
         Optional<String> notAVirtualInstance = mSensorPropsMap.keySet().stream().filter(
                 (instanceName) -> !instanceName.equals(instance)).findFirst();
-        return notAVirtualInstance.map(this::getSensorPairForInstance).orElseGet(
-                this::getSensorPair);
+        return notAVirtualInstance.orElse(null);
     }
 
     /**
-     * Returns the first pair of instance and sensor props that has been added to the map.
+     * Returns the first instance that has been added to the map.
      */
     @Nullable
-    public Pair<String, SensorProps[]> getSensorPair() {
+    public String getSensorInstance() {
         Optional<String> optionalInstance = mSensorPropsMap.keySet().stream().findFirst();
-        return optionalInstance.map(this::getSensorPairForInstance).orElse(null);
-
+        return optionalInstance.orElse(null);
     }
 
     public boolean getResetLockoutRequiresChallenge() {
@@ -179,4 +160,31 @@
         dest.writeByte((byte) (mResetLockoutRequiresChallenge ? 1 : 0));
         dest.writeMap(mSensorPropsMap);
     }
+
+    /**
+     * Returns face sensor props for the HAL {@param instance}.
+     */
+    @Nullable
+    public SensorProps[] getSensorPropForInstance(String instance) {
+        SensorProps[] props = mSensorPropsMap.get(instance);
+
+        //Props should not be null for HIDL configs
+        if (props != null) {
+            return props;
+        }
+
+        final String fqName = IFace.DESCRIPTOR + "/" + instance;
+        IFace face = IFace.Stub.asInterface(Binder.allowBlocking(
+                ServiceManager.waitForDeclaredService(fqName)));
+        try {
+            if (face != null) {
+                props = face.getSensorProps();
+            } else {
+                Slog.e(TAG, "Unable to get declared service: " + fqName);
+            }
+        } catch (RemoteException e) {
+            Log.d(TAG, "Unable to get sensor properties!");
+        }
+        return props;
+    }
 }
diff --git a/core/java/android/hardware/fingerprint/FingerprintSensorConfigurations.java b/core/java/android/hardware/fingerprint/FingerprintSensorConfigurations.java
index f214494a..43c0da9 100644
--- a/core/java/android/hardware/fingerprint/FingerprintSensorConfigurations.java
+++ b/core/java/android/hardware/fingerprint/FingerprintSensorConfigurations.java
@@ -23,18 +23,18 @@
 import android.content.Context;
 import android.hardware.biometrics.fingerprint.IFingerprint;
 import android.hardware.biometrics.fingerprint.SensorProps;
+import android.os.Binder;
 import android.os.Parcel;
 import android.os.Parcelable;
 import android.os.RemoteException;
+import android.os.ServiceManager;
 import android.util.Log;
-import android.util.Pair;
 
 import java.util.ArrayList;
 import java.util.HashMap;
 import java.util.List;
 import java.util.Map;
 import java.util.Optional;
-import java.util.function.Function;
 
 /**
  * Provides the sensor props for fingerprint sensor, if available.
@@ -68,23 +68,10 @@
     /**
      * Process AIDL instances to extract sensor props and add it to the sensor map.
      * @param aidlInstances available face AIDL instances
-     * @param getIFingerprint function that provides the daemon for the specific instance
      */
-    public void addAidlSensors(@NonNull String[] aidlInstances,
-            @NonNull Function<String, IFingerprint> getIFingerprint) {
+    public void addAidlSensors(@NonNull String[] aidlInstances) {
         for (String aidlInstance : aidlInstances) {
-            try {
-                final String fqName = IFingerprint.DESCRIPTOR + "/" + aidlInstance;
-                final IFingerprint fp = getIFingerprint.apply(fqName);
-                if (fp != null) {
-                    SensorProps[] props = fp.getSensorProps();
-                    mSensorPropsMap.put(aidlInstance, props);
-                } else {
-                    Log.d(TAG, "IFingerprint null for instance " + aidlInstance);
-                }
-            } catch (RemoteException e) {
-                Log.d(TAG, "Unable to get sensor properties!");
-            }
+            mSensorPropsMap.put(aidlInstance, null);
         }
     }
 
@@ -133,38 +120,31 @@
     }
 
     /**
-     * Return sensor props for the given instance. If instance is not available,
-     * then null is returned.
+     * Checks if {@param instance} exists.
      */
     @Nullable
-    public Pair<String, SensorProps[]> getSensorPairForInstance(String instance) {
-        if (mSensorPropsMap.containsKey(instance)) {
-            return new Pair<>(instance, mSensorPropsMap.get(instance));
-        }
-
-        return null;
+    public boolean doesInstanceExist(String instance) {
+        return mSensorPropsMap.containsKey(instance);
     }
 
     /**
-     * Return the first pair of instance and sensor props, which does not correspond to the given
-     * If instance is not available, then null is returned.
+     * Return the first HAL instance, which does not correspond to the given {@param instance}.
+     * If another instance is not available, then null is returned.
      */
     @Nullable
-    public Pair<String, SensorProps[]> getSensorPairNotForInstance(String instance) {
+    public String getSensorNameNotForInstance(String instance) {
         Optional<String> notAVirtualInstance = mSensorPropsMap.keySet().stream().filter(
                 (instanceName) -> !instanceName.equals(instance)).findFirst();
-        return notAVirtualInstance.map(this::getSensorPairForInstance).orElseGet(
-                this::getSensorPair);
+        return notAVirtualInstance.orElse(null);
     }
 
     /**
-     * Returns the first pair of instance and sensor props that has been added to the map.
+     * Returns the first instance that has been added to the map.
      */
     @Nullable
-    public Pair<String, SensorProps[]> getSensorPair() {
+    public String getSensorInstance() {
         Optional<String> optionalInstance = mSensorPropsMap.keySet().stream().findFirst();
-        return optionalInstance.map(this::getSensorPairForInstance).orElse(null);
-
+        return optionalInstance.orElse(null);
     }
 
     public boolean getResetLockoutRequiresHardwareAuthToken() {
@@ -181,4 +161,31 @@
         dest.writeByte((byte) (mResetLockoutRequiresHardwareAuthToken ? 1 : 0));
         dest.writeMap(mSensorPropsMap);
     }
+
+    /**
+     * Returns fingerprint sensor props for the HAL {@param instance}.
+     */
+    @Nullable
+    public SensorProps[] getSensorPropForInstance(String instance) {
+        SensorProps[] props = mSensorPropsMap.get(instance);
+
+        //Props should not be null for HIDL configs
+        if (props != null) {
+            return props;
+        }
+
+        try {
+            final String fqName = IFingerprint.DESCRIPTOR + "/" + instance;
+            final IFingerprint fp = IFingerprint.Stub.asInterface(Binder.allowBlocking(
+                    ServiceManager.waitForDeclaredService(fqName)));
+            if (fp != null) {
+                props = fp.getSensorProps();
+            } else {
+                Log.d(TAG, "IFingerprint null for instance " + instance);
+            }
+        } catch (RemoteException e) {
+            Log.d(TAG, "Unable to get sensor properties!");
+        }
+        return props;
+    }
 }
diff --git a/core/java/android/inputmethodservice/InputMethodService.java b/core/java/android/inputmethodservice/InputMethodService.java
index 4dbdd91..2cd7aeb 100644
--- a/core/java/android/inputmethodservice/InputMethodService.java
+++ b/core/java/android/inputmethodservice/InputMethodService.java
@@ -16,6 +16,7 @@
 
 package android.inputmethodservice;
 
+import static android.view.inputmethod.Flags.predictiveBackIme;
 import static android.inputmethodservice.InputMethodServiceProto.CANDIDATES_VIEW_STARTED;
 import static android.inputmethodservice.InputMethodServiceProto.CANDIDATES_VISIBILITY;
 import static android.inputmethodservice.InputMethodServiceProto.CONFIGURATION;
@@ -3098,7 +3099,7 @@
         cancelImeSurfaceRemoval();
         mInShowWindow = false;
         Trace.traceEnd(TRACE_TAG_WINDOW_MANAGER);
-        registerCompatOnBackInvokedCallback();
+        registerDefaultOnBackInvokedCallback();
     }
 
 
@@ -3107,18 +3108,27 @@
      *  back dispatching is enabled. We keep the {@link KeyEvent#KEYCODE_BACK} based legacy code
      *  around to handle back on older devices.
      */
-    private void registerCompatOnBackInvokedCallback() {
+    private void registerDefaultOnBackInvokedCallback() {
         if (mBackCallbackRegistered) {
             return;
         }
         if (mWindow != null) {
-            mWindow.getOnBackInvokedDispatcher().registerOnBackInvokedCallback(
-                    OnBackInvokedDispatcher.PRIORITY_DEFAULT, mCompatBackCallback);
+            if (getApplicationInfo().isOnBackInvokedCallbackEnabled() && predictiveBackIme()) {
+                // Register the compat callback as system-callback if IME has opted in for
+                // predictive back (and predictiveBackIme feature flag is enabled). This indicates
+                // to the receiving process (application process) that a predictive IME dismiss
+                // animation may be played instead of invoking the callback.
+                mWindow.getOnBackInvokedDispatcher().registerSystemOnBackInvokedCallback(
+                        mCompatBackCallback);
+            } else {
+                mWindow.getOnBackInvokedDispatcher().registerOnBackInvokedCallback(
+                        OnBackInvokedDispatcher.PRIORITY_DEFAULT, mCompatBackCallback);
+            }
             mBackCallbackRegistered = true;
         }
     }
 
-    private void unregisterCompatOnBackInvokedCallback() {
+    private void unregisterDefaultOnBackInvokedCallback() {
         if (!mBackCallbackRegistered) {
             return;
         }
@@ -3251,7 +3261,7 @@
         }
         mLastWasInFullscreenMode = mIsFullscreen;
         updateFullscreenMode();
-        unregisterCompatOnBackInvokedCallback();
+        unregisterDefaultOnBackInvokedCallback();
     }
 
     /**
@@ -3328,7 +3338,7 @@
         // Back callback is typically unregistered in {@link #hideWindow()}, but it's possible
         // for {@link #doFinishInput()} to be called without {@link #hideWindow()} so we also
         // unregister here.
-        unregisterCompatOnBackInvokedCallback();
+        unregisterDefaultOnBackInvokedCallback();
     }
 
     void doStartInput(InputConnection ic, EditorInfo editorInfo, boolean restarting) {
@@ -4473,7 +4483,7 @@
     private void compatHandleBack() {
         if (!mDecorViewVisible) {
             Log.e(TAG, "Back callback invoked on a hidden IME. Removing the callback...");
-            unregisterCompatOnBackInvokedCallback();
+            unregisterDefaultOnBackInvokedCallback();
             return;
         }
         final KeyEvent downEvent = createBackKeyEvent(
diff --git a/core/java/android/permission/flags.aconfig b/core/java/android/permission/flags.aconfig
index 2710df2..3e4454f 100644
--- a/core/java/android/permission/flags.aconfig
+++ b/core/java/android/permission/flags.aconfig
@@ -116,6 +116,15 @@
 }
 
 flag {
+  name: "sensitive_content_improvements"
+  namespace: "permissions"
+  description: "Improvements to sensitive content/notification features, such as the Toast UX."
+  bug: "301960090"
+  # Referenced in WM where WM starts before DeviceConfig
+  is_fixed_read_only: true
+}
+
+flag {
     name: "device_aware_permissions_enabled"
     is_fixed_read_only: true
     namespace: "permissions"
diff --git a/core/java/android/provider/Settings.java b/core/java/android/provider/Settings.java
index c74d50a..d91b051 100644
--- a/core/java/android/provider/Settings.java
+++ b/core/java/android/provider/Settings.java
@@ -1002,6 +1002,21 @@
             "android.settings.BLUETOOTH_SETTINGS";
 
     /**
+     * Activity Action: Show settings to allow configuration of Hearing Devices.
+     * <p>
+     * In some cases, a matching Activity may not exist, so ensure you
+     * safeguard against this.
+     * <p>
+     * Input: Nothing.
+     * <p>
+     * Output: Nothing.
+     * @hide
+     */
+    @SdkConstant(SdkConstantType.ACTIVITY_INTENT_ACTION)
+    public static final String ACTION_HEARING_DEVICES_SETTINGS =
+            "android.settings.HEARING_DEVICES_SETTINGS";
+
+    /**
      * Activity action: Show Settings app search UI when this action is available for device.
      * <p>
      * Input: Nothing.
@@ -12436,7 +12451,7 @@
         /** @hide */
         public static final int PRIVATE_SPACE_AUTO_LOCK_AFTER_INACTIVITY = 1;
         /** @hide */
-        public static final int PRIVATE_SPACE_AUTO_LOCK_NEVER = 2;
+        public static final int PRIVATE_SPACE_AUTO_LOCK_AFTER_DEVICE_RESTART = 2;
 
         /**
          * The different auto lock options for private space.
@@ -12446,7 +12461,7 @@
         @IntDef(prefix = {"PRIVATE_SPACE_AUTO_LOCK_"}, value = {
                 PRIVATE_SPACE_AUTO_LOCK_ON_DEVICE_LOCK,
                 PRIVATE_SPACE_AUTO_LOCK_AFTER_INACTIVITY,
-                PRIVATE_SPACE_AUTO_LOCK_NEVER,
+                PRIVATE_SPACE_AUTO_LOCK_AFTER_DEVICE_RESTART,
         })
         @Retention(RetentionPolicy.SOURCE)
         public @interface PrivateSpaceAutoLockOption {
diff --git a/core/java/android/service/quickaccesswallet/QuickAccessWalletServiceInfo.java b/core/java/android/service/quickaccesswallet/QuickAccessWalletServiceInfo.java
index e6d8fd0..8a3f6ce 100644
--- a/core/java/android/service/quickaccesswallet/QuickAccessWalletServiceInfo.java
+++ b/core/java/android/service/quickaccesswallet/QuickAccessWalletServiceInfo.java
@@ -71,9 +71,11 @@
 
     @Nullable
     static QuickAccessWalletServiceInfo tryCreate(@NonNull Context context) {
-        String defaultAppPackageName = getDefaultWalletApp(context);
+        String defaultAppPackageName = null;
 
-        if (defaultAppPackageName == null) {
+        if (isWalletRoleAvailable(context)) {
+            defaultAppPackageName = getDefaultWalletApp(context);
+        } else {
             ComponentName defaultPaymentApp = getDefaultPaymentApp(context);
             if (defaultPaymentApp == null) {
                 return null;
@@ -103,14 +105,21 @@
         final long token = Binder.clearCallingIdentity();
         try {
             RoleManager roleManager = context.getSystemService(RoleManager.class);
-            if (roleManager.isRoleAvailable(RoleManager.ROLE_WALLET)) {
-                List<String> roleHolders = roleManager.getRoleHolders(RoleManager.ROLE_WALLET);
-                return roleHolders.isEmpty() ? null : roleHolders.get(0);
-            }
+            List<String> roleHolders = roleManager.getRoleHolders(RoleManager.ROLE_WALLET);
+            return roleHolders.isEmpty() ? null : roleHolders.get(0);
         } finally {
             Binder.restoreCallingIdentity(token);
         }
-        return null;
+    }
+
+    private static boolean isWalletRoleAvailable(Context context) {
+        final long token = Binder.clearCallingIdentity();
+        try {
+            RoleManager roleManager = context.getSystemService(RoleManager.class);
+            return roleManager.isRoleAvailable(RoleManager.ROLE_WALLET);
+        } finally {
+            Binder.restoreCallingIdentity(token);
+        }
     }
 
     private static ComponentName getDefaultPaymentApp(Context context) {
diff --git a/core/java/android/view/BatchedInputEventReceiver.java b/core/java/android/view/BatchedInputEventReceiver.java
index ca2e56d..2e39f73 100644
--- a/core/java/android/view/BatchedInputEventReceiver.java
+++ b/core/java/android/view/BatchedInputEventReceiver.java
@@ -29,6 +29,7 @@
     private Choreographer mChoreographer;
     private boolean mBatchingEnabled;
     private boolean mBatchedInputScheduled;
+    private final String mTag;
     private final Handler mHandler;
     private final Runnable mConsumeBatchedInputEvents = new Runnable() {
         @Override
@@ -43,6 +44,7 @@
         super(inputChannel, looper);
         mChoreographer = choreographer;
         mBatchingEnabled = true;
+        mTag = inputChannel.getName();
         traceBoolVariable("mBatchingEnabled", mBatchingEnabled);
         traceBoolVariable("mBatchedInputScheduled", mBatchedInputScheduled);
         mHandler = new Handler(looper);
@@ -123,7 +125,12 @@
     private final class BatchedInputRunnable implements Runnable {
         @Override
         public void run() {
-            doConsumeBatchedInput(mChoreographer.getFrameTimeNanos());
+            try {
+                Trace.traceBegin(Trace.TRACE_TAG_INPUT, mTag);
+                doConsumeBatchedInput(mChoreographer.getFrameTimeNanos());
+            } finally {
+                Trace.traceEnd(Trace.TRACE_TAG_INPUT);
+            }
         }
     }
     private final BatchedInputRunnable mBatchedInputRunnable = new BatchedInputRunnable();
diff --git a/core/java/android/view/ImeBackAnimationController.java b/core/java/android/view/ImeBackAnimationController.java
new file mode 100644
index 0000000..d14e858
--- /dev/null
+++ b/core/java/android/view/ImeBackAnimationController.java
@@ -0,0 +1,220 @@
+/*
+ * Copyright (C) 2024 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package android.view;
+
+import static android.view.InsetsController.ANIMATION_TYPE_USER;
+import static android.view.WindowInsets.Type.ime;
+import static android.view.WindowManager.LayoutParams.SOFT_INPUT_ADJUST_RESIZE;
+import static android.view.WindowManager.LayoutParams.SOFT_INPUT_MASK_ADJUST;
+
+import android.animation.Animator;
+import android.animation.AnimatorListenerAdapter;
+import android.animation.ValueAnimator;
+import android.annotation.NonNull;
+import android.annotation.Nullable;
+import android.graphics.Insets;
+import android.util.Log;
+import android.view.animation.Interpolator;
+import android.view.animation.PathInterpolator;
+import android.window.BackEvent;
+import android.window.OnBackAnimationCallback;
+
+/**
+ * Controller for IME predictive back animation
+ *
+ * @hide
+ */
+public class ImeBackAnimationController implements OnBackAnimationCallback {
+
+    private static final String TAG = "ImeBackAnimationController";
+    private static final int POST_COMMIT_DURATION_MS = 200;
+    private static final int POST_COMMIT_CANCEL_DURATION_MS = 50;
+    private static final float PEEK_FRACTION = 0.1f;
+    private static final Interpolator STANDARD_DECELERATE = new PathInterpolator(0f, 0f, 0f, 1f);
+    private static final Interpolator EMPHASIZED_DECELERATE = new PathInterpolator(
+            0.05f, 0.7f, 0.1f, 1f);
+    private static final Interpolator STANDARD_ACCELERATE = new PathInterpolator(0.3f, 0f, 1f, 1f);
+
+    private final InsetsController mInsetsController;
+    private final ViewRootImpl mViewRoot;
+    private WindowInsetsAnimationController mWindowInsetsAnimationController = null;
+    private ValueAnimator mPostCommitAnimator = null;
+    private float mLastProgress = 0f;
+    private boolean mTriggerBack = false;
+    private boolean mIsPreCommitAnimationInProgress = false;
+
+    public ImeBackAnimationController(ViewRootImpl viewRoot) {
+        mInsetsController = viewRoot.getInsetsController();
+        mViewRoot = viewRoot;
+    }
+
+    @Override
+    public void onBackStarted(@NonNull BackEvent backEvent) {
+        if (isAdjustResize()) {
+            // There is no good solution for a predictive back animation if the app uses
+            // adjustResize, since we can't relayout the whole app for every frame. We also don't
+            // want to reveal any black areas behind the IME. Therefore let's not play any animation
+            // in that case for now.
+            Log.d(TAG, "onBackStarted -> not playing predictive back animation due to softinput"
+                    + " mode adjustResize");
+            return;
+        }
+        if (isHideAnimationInProgress()) {
+            // If IME is currently animating away, skip back gesture
+            return;
+        }
+        mIsPreCommitAnimationInProgress = true;
+        if (mWindowInsetsAnimationController != null) {
+            // There's still an active animation controller. This means that a cancel post commit
+            // animation of an earlier back gesture is still in progress. Let's cancel it and let
+            // the new gesture seamlessly take over.
+            resetPostCommitAnimator();
+            setPreCommitProgress(0f);
+            return;
+        }
+        mInsetsController.controlWindowInsetsAnimation(ime(), /*cancellationSignal*/ null,
+                new WindowInsetsAnimationControlListener() {
+                    @Override
+                    public void onReady(@NonNull WindowInsetsAnimationController controller,
+                            @WindowInsets.Type.InsetsType int types) {
+                        mWindowInsetsAnimationController = controller;
+                        if (mIsPreCommitAnimationInProgress) {
+                            setPreCommitProgress(mLastProgress);
+                        } else {
+                            // gesture has already finished before IME became ready to animate
+                            startPostCommitAnim(mTriggerBack);
+                        }
+                    }
+
+                    @Override
+                    public void onFinished(@NonNull WindowInsetsAnimationController controller) {
+                        reset();
+                    }
+
+                    @Override
+                    public void onCancelled(@Nullable WindowInsetsAnimationController controller) {
+                        reset();
+                    }
+                }, /*fromIme*/ false, /*durationMs*/ -1, /*interpolator*/ null, ANIMATION_TYPE_USER,
+                /*fromPredictiveBack*/ true);
+    }
+
+    @Override
+    public void onBackProgressed(@NonNull BackEvent backEvent) {
+        mLastProgress = backEvent.getProgress();
+        setPreCommitProgress(mLastProgress);
+    }
+
+    @Override
+    public void onBackCancelled() {
+        if (isAdjustResize()) return;
+        startPostCommitAnim(/*hideIme*/ false);
+    }
+
+    @Override
+    public void onBackInvoked() {
+        if (isAdjustResize()) {
+            mInsetsController.hide(ime());
+            return;
+        }
+        startPostCommitAnim(/*hideIme*/ true);
+    }
+
+    private void setPreCommitProgress(float progress) {
+        if (isHideAnimationInProgress()) return;
+        if (mWindowInsetsAnimationController != null) {
+            float hiddenY = mWindowInsetsAnimationController.getHiddenStateInsets().bottom;
+            float shownY = mWindowInsetsAnimationController.getShownStateInsets().bottom;
+            float imeHeight = shownY - hiddenY;
+            float interpolatedProgress = STANDARD_DECELERATE.getInterpolation(progress);
+            int newY = (int) (imeHeight - interpolatedProgress * (imeHeight * PEEK_FRACTION));
+            mWindowInsetsAnimationController.setInsetsAndAlpha(Insets.of(0, 0, 0, newY), 1f,
+                    progress);
+        }
+    }
+
+    private void startPostCommitAnim(boolean triggerBack) {
+        mIsPreCommitAnimationInProgress = false;
+        if (mWindowInsetsAnimationController == null || isHideAnimationInProgress()) {
+            mTriggerBack = triggerBack;
+            return;
+        }
+        mTriggerBack = triggerBack;
+        int currentBottomInset = mWindowInsetsAnimationController.getCurrentInsets().bottom;
+        int targetBottomInset;
+        if (triggerBack) {
+            targetBottomInset = mWindowInsetsAnimationController.getHiddenStateInsets().bottom;
+        } else {
+            targetBottomInset = mWindowInsetsAnimationController.getShownStateInsets().bottom;
+        }
+        mPostCommitAnimator = ValueAnimator.ofFloat(currentBottomInset, targetBottomInset);
+        mPostCommitAnimator.setInterpolator(
+                triggerBack ? STANDARD_ACCELERATE : EMPHASIZED_DECELERATE);
+        mPostCommitAnimator.addUpdateListener(animation -> {
+            int bottomInset = (int) ((float) animation.getAnimatedValue());
+            if (mWindowInsetsAnimationController != null) {
+                mWindowInsetsAnimationController.setInsetsAndAlpha(Insets.of(0, 0, 0, bottomInset),
+                        1f, animation.getAnimatedFraction());
+            } else {
+                reset();
+            }
+        });
+        mPostCommitAnimator.addListener(new AnimatorListenerAdapter() {
+            @Override
+            public void onAnimationEnd(Animator animator) {
+                if (mIsPreCommitAnimationInProgress) {
+                    // this means a new gesture has started while the cancel-post-commit-animation
+                    // was in progress. Let's not reset anything and let the new user gesture take
+                    // over seamlessly
+                    return;
+                }
+                if (mWindowInsetsAnimationController != null) {
+                    mWindowInsetsAnimationController.finish(!triggerBack);
+                }
+                reset();
+            }
+        });
+        mPostCommitAnimator.setDuration(
+                triggerBack ? POST_COMMIT_DURATION_MS : POST_COMMIT_CANCEL_DURATION_MS);
+        mPostCommitAnimator.start();
+    }
+
+    private void reset() {
+        mWindowInsetsAnimationController = null;
+        resetPostCommitAnimator();
+        mLastProgress = 0f;
+        mTriggerBack = false;
+        mIsPreCommitAnimationInProgress = false;
+    }
+
+    private void resetPostCommitAnimator() {
+        if (mPostCommitAnimator != null) {
+            mPostCommitAnimator.cancel();
+            mPostCommitAnimator = null;
+        }
+    }
+
+    private boolean isAdjustResize() {
+        return (mViewRoot.mWindowAttributes.softInputMode & SOFT_INPUT_MASK_ADJUST)
+                == SOFT_INPUT_ADJUST_RESIZE;
+    }
+
+    private boolean isHideAnimationInProgress() {
+        return mPostCommitAnimator != null && mTriggerBack;
+    }
+
+}
diff --git a/core/java/android/view/InsetsController.java b/core/java/android/view/InsetsController.java
index 2fcffd0..90aafbd 100644
--- a/core/java/android/view/InsetsController.java
+++ b/core/java/android/view/InsetsController.java
@@ -29,6 +29,8 @@
 import static android.view.WindowInsets.Type.captionBar;
 import static android.view.WindowInsets.Type.ime;
 
+import static com.android.internal.annotations.VisibleForTesting.Visibility.PACKAGE;
+
 import android.animation.AnimationHandler;
 import android.animation.Animator;
 import android.animation.AnimatorListenerAdapter;
@@ -322,7 +324,7 @@
     public static final int ANIMATION_TYPE_HIDE = 1;
 
     /** Running animation is controlled by user via {@link #controlWindowInsetsAnimation} */
-    @VisibleForTesting
+    @VisibleForTesting(visibility = PACKAGE)
     public static final int ANIMATION_TYPE_USER = 2;
 
     /** Running animation will resize insets */
@@ -1076,7 +1078,7 @@
         show(types, false /* fromIme */, null /* statsToken */);
     }
 
-    @VisibleForTesting(visibility = VisibleForTesting.Visibility.PACKAGE)
+    @VisibleForTesting(visibility = PACKAGE)
     public void show(@InsetsType int types, boolean fromIme,
             @Nullable ImeTracker.Token statsToken) {
         if ((types & ime()) != 0) {
@@ -1260,14 +1262,15 @@
             @Nullable CancellationSignal cancellationSignal,
             @NonNull WindowInsetsAnimationControlListener listener) {
         controlWindowInsetsAnimation(types, cancellationSignal, listener,
-                false /* fromIme */, durationMillis, interpolator, ANIMATION_TYPE_USER);
+                false /* fromIme */, durationMillis, interpolator, ANIMATION_TYPE_USER,
+                false /* fromPredictiveBack */);
     }
 
-    private void controlWindowInsetsAnimation(@InsetsType int types,
+    void controlWindowInsetsAnimation(@InsetsType int types,
             @Nullable CancellationSignal cancellationSignal,
             WindowInsetsAnimationControlListener listener,
             boolean fromIme, long durationMs, @Nullable Interpolator interpolator,
-            @AnimationType int animationType) {
+            @AnimationType int animationType, boolean fromPredictiveBack) {
         if ((mState.calculateUncontrollableInsetsFromFrame(mFrame) & types) != 0) {
             listener.onCancelled(null);
             return;
@@ -1279,7 +1282,8 @@
         }
 
         controlAnimationUnchecked(types, cancellationSignal, listener, mFrame, fromIme, durationMs,
-                interpolator, animationType, getLayoutInsetsDuringAnimationMode(types),
+                interpolator, animationType,
+                getLayoutInsetsDuringAnimationMode(types, fromPredictiveBack),
                 false /* useInsetsAnimationThread */, null /* statsToken */);
     }
 
@@ -1526,7 +1530,12 @@
     }
 
     private @LayoutInsetsDuringAnimation int getLayoutInsetsDuringAnimationMode(
-            @InsetsType int types) {
+            @InsetsType int types, boolean fromPredictiveBack) {
+        if (fromPredictiveBack) {
+            // When insets are animated by predictive back, we want insets to be shown to prevent a
+            // jump cut from shown to hidden at the start of the predictive back animation
+            return LAYOUT_INSETS_DURING_ANIMATION_SHOWN;
+        }
         // Generally, we want to layout the opposite of the current state. This is to make animation
         // callbacks easy to use: The can capture the layout values and then treat that as end-state
         // during the animation.
@@ -1730,7 +1739,7 @@
         return ANIMATION_TYPE_NONE;
     }
 
-    @VisibleForTesting(visibility = VisibleForTesting.Visibility.PACKAGE)
+    @VisibleForTesting(visibility = PACKAGE)
     public void setRequestedVisibleTypes(@InsetsType int visibleTypes, @InsetsType int mask) {
         final @InsetsType int requestedVisibleTypes =
                 (mRequestedVisibleTypes & ~mask) | (visibleTypes & mask);
diff --git a/core/java/android/view/PointerIcon.java b/core/java/android/view/PointerIcon.java
index 147d562..17d1404 100644
--- a/core/java/android/view/PointerIcon.java
+++ b/core/java/android/view/PointerIcon.java
@@ -16,8 +16,10 @@
 
 package android.view;
 
+import android.annotation.FlaggedApi;
 import android.annotation.NonNull;
 import android.annotation.Nullable;
+import android.annotation.TestApi;
 import android.annotation.XmlRes;
 import android.compat.annotation.UnsupportedAppUsage;
 import android.content.Context;
@@ -39,6 +41,7 @@
 import android.os.PointerIconType;
 import android.util.Log;
 import android.util.SparseArray;
+import android.view.flags.Flags;
 
 import com.android.internal.util.XmlUtils;
 
@@ -637,4 +640,15 @@
             default: return Integer.toString(type);
         }
     }
+
+    /**
+     * Sets whether drop shadow will draw in the native code.
+     *
+     * @hide
+     */
+    @TestApi
+    @FlaggedApi(Flags.FLAG_ENABLE_VECTOR_CURSORS)
+    public void setDrawNativeDropShadow(boolean drawNativeDropShadow) {
+        mDrawNativeDropShadow = drawNativeDropShadow;
+    }
 }
diff --git a/core/java/android/view/ViewRootImpl.java b/core/java/android/view/ViewRootImpl.java
index dd548c6..0d9e471 100644
--- a/core/java/android/view/ViewRootImpl.java
+++ b/core/java/android/view/ViewRootImpl.java
@@ -110,10 +110,12 @@
 import static android.view.flags.Flags.toolkitFrameRateTypingReadOnly;
 import static android.view.flags.Flags.toolkitMetricsForFrameRateDecision;
 import static android.view.flags.Flags.toolkitSetFrameRateReadOnly;
+import static android.view.flags.Flags.toolkitFrameRateFunctionEnablingReadOnly;
 import static android.view.inputmethod.InputMethodEditorTraceProto.InputMethodClientsTraceProto.ClientSideProto.IME_FOCUS_CONTROLLER;
 import static android.view.inputmethod.InputMethodEditorTraceProto.InputMethodClientsTraceProto.ClientSideProto.INSETS_CONTROLLER;
 
 import static com.android.input.flags.Flags.enablePointerChoreographer;
+import static com.android.internal.annotations.VisibleForTesting.Visibility.PACKAGE;
 import static com.android.window.flags.Flags.activityWindowInfoFlag;
 import static com.android.window.flags.Flags.enableBufferTransformHintFromDisplay;
 import static com.android.window.flags.Flags.setScPropertiesInClient;
@@ -941,6 +943,7 @@
                     new InputEventConsistencyVerifier(this, 0) : null;
 
     private final InsetsController mInsetsController;
+    private final ImeBackAnimationController mImeBackAnimationController;
     private final ImeFocusController mImeFocusController;
 
     private boolean mIsSurfaceOpaque;
@@ -1148,6 +1151,7 @@
     private String mLargestViewTraceName;
 
     private static boolean sToolkitSetFrameRateReadOnlyFlagValue;
+    private static boolean sToolkitFrameRateFunctionEnablingReadOnlyFlagValue;
     private static boolean sToolkitMetricsForFrameRateDecisionFlagValue;
     private static boolean sToolkitFrameRateTypingReadOnlyFlagValue;
 
@@ -1155,6 +1159,8 @@
         sToolkitSetFrameRateReadOnlyFlagValue = toolkitSetFrameRateReadOnly();
         sToolkitMetricsForFrameRateDecisionFlagValue = toolkitMetricsForFrameRateDecision();
         sToolkitFrameRateTypingReadOnlyFlagValue = toolkitFrameRateTypingReadOnly();
+        sToolkitFrameRateFunctionEnablingReadOnlyFlagValue =
+                toolkitFrameRateFunctionEnablingReadOnly();
     }
 
     // The latest input event from the gesture that was used to resolve the pointer icon.
@@ -1202,6 +1208,7 @@
         // TODO(b/222696368): remove getSfInstance usage and use vsyncId for transactions
         mChoreographer = Choreographer.getInstance();
         mInsetsController = new InsetsController(new ViewRootInsetsControllerHost(this));
+        mImeBackAnimationController = new ImeBackAnimationController(this);
         mHandwritingInitiator = new HandwritingInitiator(
                 mViewConfiguration,
                 mContext.getSystemService(InputMethodManager.class));
@@ -3177,7 +3184,7 @@
                         == LAYOUT_IN_DISPLAY_CUTOUT_MODE_SHORT_EDGES;
     }
 
-    @VisibleForTesting
+    @VisibleForTesting(visibility = PACKAGE)
     public InsetsController getInsetsController() {
         return mInsetsController;
     }
@@ -10166,13 +10173,18 @@
     final class ConsumeBatchedInputRunnable implements Runnable {
         @Override
         public void run() {
-            mConsumeBatchedInputScheduled = false;
-            if (doConsumeBatchedInput(mChoreographer.getFrameTimeNanos())) {
-                // If we consumed a batch here, we want to go ahead and schedule the
-                // consumption of batched input events on the next frame. Otherwise, we would
-                // wait until we have more input events pending and might get starved by other
-                // things occurring in the process.
-                scheduleConsumeBatchedInput();
+            Trace.traceBegin(TRACE_TAG_VIEW, mTag);
+            try {
+                mConsumeBatchedInputScheduled = false;
+                if (doConsumeBatchedInput(mChoreographer.getFrameTimeNanos())) {
+                    // If we consumed a batch here, we want to go ahead and schedule the
+                    // consumption of batched input events on the next frame. Otherwise, we would
+                    // wait until we have more input events pending and might get starved by other
+                    // things occurring in the process.
+                    scheduleConsumeBatchedInput();
+                }
+            } finally {
+                Trace.traceEnd(TRACE_TAG_VIEW);
             }
         }
     }
@@ -12144,7 +12156,8 @@
                             + "IWindow:%s Session:%s",
                     mOnBackInvokedDispatcher, mBasePackageName, mWindow, mWindowSession));
         }
-        mOnBackInvokedDispatcher.attachToWindow(mWindowSession, mWindow);
+        mOnBackInvokedDispatcher.attachToWindow(mWindowSession, mWindow,
+                mImeBackAnimationController);
     }
 
     private void sendBackKeyEvent(int action) {
@@ -12788,7 +12801,9 @@
 
     private boolean shouldEnableDvrr() {
         // uncomment this when we are ready for enabling dVRR
-        // return sToolkitSetFrameRateReadOnlyFlagValue && isFrameRatePowerSavingsBalanced();
+        if (sToolkitFrameRateFunctionEnablingReadOnlyFlagValue) {
+            return sToolkitSetFrameRateReadOnlyFlagValue && isFrameRatePowerSavingsBalanced();
+        }
         return false;
     }
 
diff --git a/core/java/android/view/flags/refresh_rate_flags.aconfig b/core/java/android/view/flags/refresh_rate_flags.aconfig
index 06598b3..1d4d18b 100644
--- a/core/java/android/view/flags/refresh_rate_flags.aconfig
+++ b/core/java/android/view/flags/refresh_rate_flags.aconfig
@@ -86,4 +86,12 @@
     description: "Feature flag for suppressing boost on typing"
     bug: "239979904"
     is_fixed_read_only: true
+}
+
+flag {
+    name: "toolkit_frame_rate_function_enabling_read_only"
+    namespace: "toolkit"
+    description: "Feature flag to enable the functionality of the dVRR feature"
+    bug: "239979904"
+    is_fixed_read_only: true
 }
\ No newline at end of file
diff --git a/core/java/android/view/inputmethod/IInputMethodManagerGlobalInvoker.java b/core/java/android/view/inputmethod/IInputMethodManagerGlobalInvoker.java
index cedf8d0..f454a6a 100644
--- a/core/java/android/view/inputmethod/IInputMethodManagerGlobalInvoker.java
+++ b/core/java/android/view/inputmethod/IInputMethodManagerGlobalInvoker.java
@@ -770,6 +770,20 @@
     }
 
     @AnyThread
+    @RequiresPermission(Manifest.permission.TEST_INPUT_METHOD)
+    static void finishTrackingPendingImeVisibilityRequests() {
+        final var service = getImeTrackerService();
+        if (service == null) {
+            return;
+        }
+        try {
+            service.finishTrackingPendingImeVisibilityRequests();
+        } catch (RemoteException e) {
+            throw e.rethrowFromSystemServer();
+        }
+    }
+
+    @AnyThread
     @Nullable
     private static IImeTracker getImeTrackerService() {
         var trackerService = sTrackerServiceCache;
diff --git a/core/java/android/view/inputmethod/InputMethodManager.java b/core/java/android/view/inputmethod/InputMethodManager.java
index 8efb201..80b2396 100644
--- a/core/java/android/view/inputmethod/InputMethodManager.java
+++ b/core/java/android/view/inputmethod/InputMethodManager.java
@@ -4316,6 +4316,19 @@
     }
 
     /**
+     * A test API for CTS to finish the tracking of any pending IME visibility requests. This
+     * won't stop the actual requests, but allows resetting the state when starting up test runs.
+     *
+     * @hide
+     */
+    @SuppressLint("UnflaggedApi") // @TestApi without associated feature.
+    @TestApi
+    @RequiresPermission(Manifest.permission.TEST_INPUT_METHOD)
+    public void finishTrackingPendingImeVisibilityRequests() {
+        IInputMethodManagerGlobalInvoker.finishTrackingPendingImeVisibilityRequests();
+    }
+
+    /**
      * Show the settings for enabling subtypes of the specified input method.
      *
      * @param imiId An input method, whose subtypes settings will be shown. If imiId is null,
diff --git a/core/java/android/window/ImeOnBackInvokedDispatcher.java b/core/java/android/window/ImeOnBackInvokedDispatcher.java
index 0cc9a0d..da1993d 100644
--- a/core/java/android/window/ImeOnBackInvokedDispatcher.java
+++ b/core/java/android/window/ImeOnBackInvokedDispatcher.java
@@ -148,8 +148,17 @@
             @OnBackInvokedDispatcher.Priority int priority,
             int callbackId,
             @NonNull WindowOnBackInvokedDispatcher receivingDispatcher) {
-        final ImeOnBackInvokedCallback imeCallback =
-                new ImeOnBackInvokedCallback(iCallback, callbackId, priority);
+        final ImeOnBackInvokedCallback imeCallback;
+        if (priority == PRIORITY_SYSTEM) {
+            // A callback registration with PRIORITY_SYSTEM indicates that a predictive back
+            // animation can be played on the IME. Therefore register the
+            // DefaultImeOnBackInvokedCallback with the receiving dispatcher and override the
+            // priority to PRIORITY_DEFAULT.
+            priority = PRIORITY_DEFAULT;
+            imeCallback = new DefaultImeOnBackAnimationCallback(iCallback, callbackId, priority);
+        } else {
+            imeCallback = new ImeOnBackInvokedCallback(iCallback, callbackId, priority);
+        }
         mImeCallbacks.add(imeCallback);
         receivingDispatcher.registerOnBackInvokedCallbackUnchecked(imeCallback, priority);
     }
@@ -230,6 +239,17 @@
     }
 
     /**
+     * Subclass of ImeOnBackInvokedCallback indicating that a predictive IME back animation may be
+     * played instead of invoking the callback.
+     */
+    static class DefaultImeOnBackAnimationCallback extends ImeOnBackInvokedCallback {
+        DefaultImeOnBackAnimationCallback(@NonNull IOnBackInvokedCallback iCallback, int id,
+                int priority) {
+            super(iCallback, id, priority);
+        }
+    }
+
+    /**
      * Transfers {@link ImeOnBackInvokedCallback}s registered on one {@link ViewRootImpl} to
      * another {@link ViewRootImpl} on focus change.
      *
diff --git a/core/java/android/window/WindowOnBackInvokedDispatcher.java b/core/java/android/window/WindowOnBackInvokedDispatcher.java
index 45d7767..bcbac93 100644
--- a/core/java/android/window/WindowOnBackInvokedDispatcher.java
+++ b/core/java/android/window/WindowOnBackInvokedDispatcher.java
@@ -31,6 +31,7 @@
 import android.util.Log;
 import android.view.IWindow;
 import android.view.IWindowSession;
+import android.view.ImeBackAnimationController;
 
 import androidx.annotation.VisibleForTesting;
 
@@ -71,6 +72,9 @@
     @Nullable
     private ImeOnBackInvokedDispatcher mImeDispatcher;
 
+    @Nullable
+    private ImeBackAnimationController mImeBackAnimationController;
+
     /** Convenience hashmap to quickly decide if a callback has been added. */
     private final HashMap<OnBackInvokedCallback, Integer> mAllCallbacks = new HashMap<>();
     /** Holds all callbacks by priorities. */
@@ -88,9 +92,11 @@
      * Sends the pending top callback (if one exists) to WM when the view root
      * is attached a window.
      */
-    public void attachToWindow(@NonNull IWindowSession windowSession, @NonNull IWindow window) {
+    public void attachToWindow(@NonNull IWindowSession windowSession, @NonNull IWindow window,
+            @Nullable ImeBackAnimationController imeBackAnimationController) {
         mWindowSession = windowSession;
         mWindow = window;
+        mImeBackAnimationController = imeBackAnimationController;
         if (!mAllCallbacks.isEmpty()) {
             setTopOnBackInvokedCallback(getTopCallback());
         }
@@ -101,6 +107,7 @@
         clear();
         mWindow = null;
         mWindowSession = null;
+        mImeBackAnimationController = null;
     }
 
     // TODO: Take an Executor for the callback to run on.
@@ -125,6 +132,9 @@
         if (!mOnBackInvokedCallbacks.containsKey(priority)) {
             mOnBackInvokedCallbacks.put(priority, new ArrayList<>());
         }
+        if (callback instanceof ImeOnBackInvokedDispatcher.DefaultImeOnBackAnimationCallback) {
+            callback = mImeBackAnimationController;
+        }
         ArrayList<OnBackInvokedCallback> callbacks = mOnBackInvokedCallbacks.get(priority);
 
         // If callback has already been added, remove it and re-add it.
@@ -152,6 +162,9 @@
             mImeDispatcher.unregisterOnBackInvokedCallback(callback);
             return;
         }
+        if (callback instanceof ImeOnBackInvokedDispatcher.DefaultImeOnBackAnimationCallback) {
+            callback = mImeBackAnimationController;
+        }
         if (!mAllCallbacks.containsKey(callback)) {
             if (DEBUG) {
                 Log.i(TAG, "Callback not found. returning...");
@@ -199,7 +212,7 @@
             }
         } else {
             Log.w(TAG, "sendCancelIfRunning: isInProgress=" + isInProgress
-                    + "callback=" + callback);
+                    + " callback=" + callback);
         }
     }
 
@@ -243,9 +256,9 @@
                 int priority = mAllCallbacks.get(callback);
                 final IOnBackInvokedCallback iCallback =
                         callback instanceof ImeOnBackInvokedDispatcher
-                                    .ImeOnBackInvokedCallback
+                                .ImeOnBackInvokedCallback
                                 ? ((ImeOnBackInvokedDispatcher.ImeOnBackInvokedCallback)
-                                        callback).getIOnBackInvokedCallback()
+                                callback).getIOnBackInvokedCallback()
                                 : new OnBackInvokedCallbackWrapper(
                                         callback,
                                         mProgressAnimator,
diff --git a/core/java/com/android/internal/accessibility/dialog/AccessibilityShortcutChooserActivity.java b/core/java/com/android/internal/accessibility/dialog/AccessibilityShortcutChooserActivity.java
index c70febb..34b487f 100644
--- a/core/java/com/android/internal/accessibility/dialog/AccessibilityShortcutChooserActivity.java
+++ b/core/java/com/android/internal/accessibility/dialog/AccessibilityShortcutChooserActivity.java
@@ -37,7 +37,6 @@
 import android.view.Window;
 import android.view.WindowManager;
 import android.view.accessibility.AccessibilityManager;
-import android.view.accessibility.Flags;
 import android.widget.AdapterView;
 
 import com.android.internal.R;
@@ -248,12 +247,10 @@
 
         boolean allowEditing = isUserSetupCompleted(this);
         boolean showWhenLocked = false;
-        if (Flags.allowShortcutChooserOnLockscreen()) {
-            final KeyguardManager keyguardManager = getSystemService(KeyguardManager.class);
-            if (keyguardManager != null && keyguardManager.isKeyguardLocked()) {
-                allowEditing = false;
-                showWhenLocked = true;
-            }
+        final KeyguardManager keyguardManager = getSystemService(KeyguardManager.class);
+        if (keyguardManager != null && keyguardManager.isKeyguardLocked()) {
+            allowEditing = false;
+            showWhenLocked = true;
         }
         if (allowEditing) {
             final String positiveButtonText =
diff --git a/core/java/com/android/internal/inputmethod/IImeTracker.aidl b/core/java/com/android/internal/inputmethod/IImeTracker.aidl
index b45bc1c..ab4edb6 100644
--- a/core/java/com/android/internal/inputmethod/IImeTracker.aidl
+++ b/core/java/com/android/internal/inputmethod/IImeTracker.aidl
@@ -86,4 +86,13 @@
     @JavaPassthrough(annotation="@android.annotation.RequiresPermission(value = "
             + "android.Manifest.permission.TEST_INPUT_METHOD)")
     boolean hasPendingImeVisibilityRequests();
+
+    /**
+     * Finishes the tracking of any pending IME visibility requests. This won't stop the actual
+     * requests, but allows resetting the state when starting up test runs.
+     */
+    @EnforcePermission("TEST_INPUT_METHOD")
+    @JavaPassthrough(annotation="@android.annotation.RequiresPermission(value = "
+            + "android.Manifest.permission.TEST_INPUT_METHOD)")
+    oneway void finishTrackingPendingImeVisibilityRequests();
 }
diff --git a/core/java/com/android/internal/net/ConnectivityBlobStore.java b/core/java/com/android/internal/net/ConnectivityBlobStore.java
index 1b18485..f8eb5be 100644
--- a/core/java/com/android/internal/net/ConnectivityBlobStore.java
+++ b/core/java/com/android/internal/net/ConnectivityBlobStore.java
@@ -19,6 +19,7 @@
 import android.annotation.NonNull;
 import android.content.ContentValues;
 import android.database.Cursor;
+import android.database.DatabaseUtils;
 import android.database.SQLException;
 import android.database.sqlite.SQLiteDatabase;
 import android.os.Binder;
@@ -153,8 +154,11 @@
         final List<String> names = new ArrayList<String>();
         try (Cursor cursor = mDb.query(TABLENAME,
                 new String[] {"name"} /* columns */,
-                "owner=? AND name LIKE ?" /* selection */,
-                new String[] {Integer.toString(ownerUid), prefix + "%"} /* selectionArgs */,
+                "owner=? AND name LIKE ? ESCAPE '\\'" /* selection */,
+                new String[] {
+                        Integer.toString(ownerUid),
+                        DatabaseUtils.escapeForLike(prefix) + "%"
+                } /* selectionArgs */,
                 null /* groupBy */,
                 null /* having */,
                 "name ASC" /* orderBy */)) {
diff --git a/core/java/com/android/internal/os/BinderInternal.java b/core/java/com/android/internal/os/BinderInternal.java
index c14d8d8..8063be6 100644
--- a/core/java/com/android/internal/os/BinderInternal.java
+++ b/core/java/com/android/internal/os/BinderInternal.java
@@ -45,8 +45,8 @@
     static ArrayList<Runnable> sGcWatchers = new ArrayList<>();
     static Runnable[] sTmpWatchers = new Runnable[1];
     static long sLastGcTime;
-    static final BinderProxyLimitListenerDelegate sBinderProxyLimitListenerDelegate =
-            new BinderProxyLimitListenerDelegate();
+    static final BinderProxyCountEventListenerDelegate sBinderProxyCountEventListenerDelegate =
+            new BinderProxyCountEventListenerDelegate();
 
     static final class GcWatcher {
         @Override
@@ -226,15 +226,24 @@
      * @param low   The threshold a binder count must drop below before the callback
      *              can be called again. (This is to avoid many repeated calls to the
      *              callback in a brief period of time)
+     * @param warning The threshold between {@code high} and {@code low} where if the binder count
+     *                exceeds that, the warning callback would be triggered.
      */
-    public static final native void nSetBinderProxyCountWatermarks(int high, int low);
+    public static final native void nSetBinderProxyCountWatermarks(int high, int low, int warning);
 
     /**
      * Interface for callback invocation when the Binder Proxy limit is reached. onLimitReached will
      * be called with the uid of the app causing too many Binder Proxies
      */
-    public interface BinderProxyLimitListener {
+    public interface BinderProxyCountEventListener {
         public void onLimitReached(int uid);
+
+        /**
+         * Call when the number of binder proxies from the uid of the app reaches
+         * the warning threshold.
+         */
+        default void onWarningThresholdReached(int uid) {
+        }
     }
 
     /**
@@ -243,7 +252,17 @@
      * @param uid The uid of the bad behaving app sending too many binders
      */
     public static void binderProxyLimitCallbackFromNative(int uid) {
-       sBinderProxyLimitListenerDelegate.notifyClient(uid);
+        sBinderProxyCountEventListenerDelegate.notifyLimitReached(uid);
+    }
+
+    /**
+     * Callback used by native code to trigger a callback in java code. The callback will be
+     * triggered when too many binder proxies from a uid hits the warning limit.
+     * @param uid The uid of the bad behaving app sending too many binders
+     */
+    @SuppressWarnings("unused")
+    public static void binderProxyWarningCallbackFromNative(int uid) {
+        sBinderProxyCountEventListenerDelegate.notifyWarningReached(uid);
     }
 
     /**
@@ -252,41 +271,45 @@
      * @param handler must not be null, callback will be posted through the handler;
      *
      */
-    public static void setBinderProxyCountCallback(BinderProxyLimitListener listener,
+    public static void setBinderProxyCountCallback(BinderProxyCountEventListener listener,
             @NonNull Handler handler) {
         Preconditions.checkNotNull(handler,
                 "Must provide NonNull Handler to setBinderProxyCountCallback when setting "
-                        + "BinderProxyLimitListener");
-        sBinderProxyLimitListenerDelegate.setListener(listener, handler);
+                        + "BinderProxyCountEventListener");
+        sBinderProxyCountEventListenerDelegate.setListener(listener, handler);
     }
 
     /**
      * Clear the Binder Proxy callback
      */
     public static void clearBinderProxyCountCallback() {
-        sBinderProxyLimitListenerDelegate.setListener(null, null);
+        sBinderProxyCountEventListenerDelegate.setListener(null, null);
     }
 
-    static private class BinderProxyLimitListenerDelegate {
-        private BinderProxyLimitListener mBinderProxyLimitListener;
+    private static class BinderProxyCountEventListenerDelegate {
+        private BinderProxyCountEventListener mBinderProxyCountEventListener;
         private Handler mHandler;
 
-        void setListener(BinderProxyLimitListener listener, Handler handler) {
+        void setListener(BinderProxyCountEventListener listener, Handler handler) {
             synchronized (this) {
-                mBinderProxyLimitListener = listener;
+                mBinderProxyCountEventListener = listener;
                 mHandler = handler;
             }
         }
 
-        void notifyClient(final int uid) {
+        void notifyLimitReached(final int uid) {
             synchronized (this) {
-                if (mBinderProxyLimitListener != null) {
-                    mHandler.post(new Runnable() {
-                        @Override
-                        public void run() {
-                            mBinderProxyLimitListener.onLimitReached(uid);
-                        }
-                    });
+                if (mBinderProxyCountEventListener != null) {
+                    mHandler.post(() -> mBinderProxyCountEventListener.onLimitReached(uid));
+                }
+            }
+        }
+
+        void notifyWarningReached(final int uid) {
+            synchronized (this) {
+                if (mBinderProxyCountEventListener != null) {
+                    mHandler.post(() ->
+                            mBinderProxyCountEventListener.onWarningThresholdReached(uid));
                 }
             }
         }
diff --git a/core/java/com/android/internal/pm/pkg/parsing/ParsingPackageUtils.java b/core/java/com/android/internal/pm/pkg/parsing/ParsingPackageUtils.java
index 9df93f9..e12becd 100644
--- a/core/java/com/android/internal/pm/pkg/parsing/ParsingPackageUtils.java
+++ b/core/java/com/android/internal/pm/pkg/parsing/ParsingPackageUtils.java
@@ -407,8 +407,10 @@
      */
     private ParseResult<ParsingPackage> parseMonolithicPackage(ParseInput input, File apkFile,
             int flags) {
+        // The signature parsing will be done later in method parseBaseApk.
+        int liteParseFlags = flags & ~PARSE_COLLECT_CERTIFICATES;
         final ParseResult<PackageLite> liteResult =
-                ApkLiteParseUtils.parseMonolithicPackageLite(input, apkFile, flags);
+                ApkLiteParseUtils.parseMonolithicPackageLite(input, apkFile, liteParseFlags);
         if (liteResult.isError()) {
             return input.error(liteResult);
         }
diff --git a/core/jni/android_util_Binder.cpp b/core/jni/android_util_Binder.cpp
index d2d5186..2068bd7 100644
--- a/core/jni/android_util_Binder.cpp
+++ b/core/jni/android_util_Binder.cpp
@@ -88,6 +88,7 @@
     jclass mClass;
     jmethodID mForceGc;
     jmethodID mProxyLimitCallback;
+    jmethodID mProxyWarningCallback;
 
 } gBinderInternalOffsets;
 
@@ -1240,7 +1241,7 @@
     gCollectedAtRefs = gNumLocalRefsCreated + gNumDeathRefsCreated;
 }
 
-static void android_os_BinderInternal_proxyLimitcallback(int uid)
+static void android_os_BinderInternal_proxyLimitCallback(int uid)
 {
     JNIEnv *env = AndroidRuntime::getJNIEnv();
     env->CallStaticVoidMethod(gBinderInternalOffsets.mClass,
@@ -1254,6 +1255,20 @@
     }
 }
 
+static void android_os_BinderInternal_proxyWarningCallback(int uid)
+{
+    JNIEnv *env = AndroidRuntime::getJNIEnv();
+    env->CallStaticVoidMethod(gBinderInternalOffsets.mClass,
+                              gBinderInternalOffsets.mProxyWarningCallback,
+                              uid);
+
+    if (env->ExceptionCheck()) {
+        ScopedLocalRef<jthrowable> excep(env, env->ExceptionOccurred());
+        binder_report_exception(env, excep.get(),
+                                "*** Uncaught exception in binderProxyWarningCallbackFromNative");
+    }
+}
+
 static void android_os_BinderInternal_setBinderProxyCountEnabled(JNIEnv* env, jobject clazz,
                                                                  jboolean enable)
 {
@@ -1278,9 +1293,10 @@
 }
 
 static void android_os_BinderInternal_setBinderProxyCountWatermarks(JNIEnv* env, jobject clazz,
-                                                                    jint high, jint low)
+                                                                    jint high, jint low,
+                                                                    jint warning)
 {
-    BpBinder::setBinderProxyCountWatermarks(high, low);
+    BpBinder::setBinderProxyCountWatermarks(high, low, warning);
 }
 
 // ----------------------------------------------------------------------------
@@ -1295,7 +1311,7 @@
     { "nSetBinderProxyCountEnabled", "(Z)V", (void*)android_os_BinderInternal_setBinderProxyCountEnabled },
     { "nGetBinderProxyPerUidCounts", "()Landroid/util/SparseIntArray;", (void*)android_os_BinderInternal_getBinderProxyPerUidCounts },
     { "nGetBinderProxyCount", "(I)I", (void*)android_os_BinderInternal_getBinderProxyCount },
-    { "nSetBinderProxyCountWatermarks", "(II)V", (void*)android_os_BinderInternal_setBinderProxyCountWatermarks}
+    { "nSetBinderProxyCountWatermarks", "(III)V", (void*)android_os_BinderInternal_setBinderProxyCountWatermarks}
 };
 
 const char* const kBinderInternalPathName = "com/android/internal/os/BinderInternal";
@@ -1307,6 +1323,8 @@
     gBinderInternalOffsets.mClass = MakeGlobalRefOrDie(env, clazz);
     gBinderInternalOffsets.mForceGc = GetStaticMethodIDOrDie(env, clazz, "forceBinderGc", "()V");
     gBinderInternalOffsets.mProxyLimitCallback = GetStaticMethodIDOrDie(env, clazz, "binderProxyLimitCallbackFromNative", "(I)V");
+    gBinderInternalOffsets.mProxyWarningCallback =
+        GetStaticMethodIDOrDie(env, clazz, "binderProxyWarningCallbackFromNative", "(I)V");
 
     jclass SparseIntArrayClass = FindClassOrDie(env, "android/util/SparseIntArray");
     gSparseIntArrayOffsets.classObject = MakeGlobalRefOrDie(env, SparseIntArrayClass);
@@ -1315,7 +1333,8 @@
     gSparseIntArrayOffsets.put = GetMethodIDOrDie(env, gSparseIntArrayOffsets.classObject, "put",
                                                    "(II)V");
 
-    BpBinder::setLimitCallback(android_os_BinderInternal_proxyLimitcallback);
+    BpBinder::setBinderProxyCountEventCallback(android_os_BinderInternal_proxyLimitCallback,
+                                               android_os_BinderInternal_proxyWarningCallback);
 
     return RegisterMethodsOrDie(
         env, kBinderInternalPathName,
diff --git a/core/res/res/drawable/ic_bt_hearing_aid.xml b/core/res/res/drawable/ic_bt_hearing_aid.xml
index e14c99b..1517147 100644
--- a/core/res/res/drawable/ic_bt_hearing_aid.xml
+++ b/core/res/res/drawable/ic_bt_hearing_aid.xml
@@ -14,10 +14,11 @@
      limitations under the License.
 -->
 <vector xmlns:android="http://schemas.android.com/apk/res/android"
-        android:width="24dp"
-        android:height="24dp"
-        android:viewportWidth="24.0"
-        android:viewportHeight="24.0">
+    android:width="24dp"
+    android:height="24dp"
+    android:viewportWidth="24.0"
+    android:viewportHeight="24.0"
+    android:tint="?android:attr/colorControlNormal">
     <path
         android:fillColor="#FF000000"
         android:pathData="M17,20c-0.29,0 -0.56,-0.06 -0.76,-0.15 -0.71,-0.37 -1.21,-0.88 -1.71,-2.38 -0.51,-1.56 -1.47,-2.29 -2.39,-3 -0.79,-0.61 -1.61,-1.24 -2.32,-2.53C9.29,10.98 9,9.93 9,9c0,-2.8 2.2,-5 5,-5s5,2.2 5,5h2c0,-3.93 -3.07,-7 -7,-7S7,5.07 7,9c0,1.26 0.38,2.65 1.07,3.9 0.91,1.65 1.98,2.48 2.85,3.15 0.81,0.62 1.39,1.07 1.71,2.05 0.6,1.82 1.37,2.84 2.73,3.55 0.51,0.23 1.07,0.35 1.64,0.35 2.21,0 4,-1.79 4,-4h-2c0,1.1 -0.9,2 -2,2zM7.64,2.64L6.22,1.22C4.23,3.21 3,5.96 3,9s1.23,5.79 3.22,7.78l1.41,-1.41C6.01,13.74 5,11.49 5,9s1.01,-4.74 2.64,-6.36zM11.5,9c0,1.38 1.12,2.5 2.5,2.5s2.5,-1.12 2.5,-2.5 -1.12,-2.5 -2.5,-2.5 -2.5,1.12 -2.5,2.5z"/>
diff --git a/core/tests/coretests/Android.bp b/core/tests/coretests/Android.bp
index 24031cad..4808204 100644
--- a/core/tests/coretests/Android.bp
+++ b/core/tests/coretests/Android.bp
@@ -134,6 +134,8 @@
         ":BinderDeathRecipientHelperApp1",
         ":BinderDeathRecipientHelperApp2",
         ":com.android.cts.helpers.aosp",
+        ":BinderProxyCountingTestApp",
+        ":BinderProxyCountingTestService",
     ],
 }
 
diff --git a/core/tests/coretests/AndroidTest.xml b/core/tests/coretests/AndroidTest.xml
index 05b309b..bf2a5b8 100644
--- a/core/tests/coretests/AndroidTest.xml
+++ b/core/tests/coretests/AndroidTest.xml
@@ -22,6 +22,8 @@
         <option name="test-file-name" value="FrameworksCoreTests.apk" />
         <option name="test-file-name" value="BinderDeathRecipientHelperApp1.apk" />
         <option name="test-file-name" value="BinderDeathRecipientHelperApp2.apk" />
+        <option name="test-file-name" value="BinderProxyCountingTestApp.apk" />
+        <option name="test-file-name" value="BinderProxyCountingTestService.apk" />
     </target_preparer>
 
     <target_preparer class="com.android.tradefed.targetprep.RunCommandTargetPreparer">
diff --git a/core/tests/coretests/BinderProxyCountingTestApp/AndroidManifest.xml b/core/tests/coretests/BinderProxyCountingTestApp/AndroidManifest.xml
index a971730..c8407b8 100644
--- a/core/tests/coretests/BinderProxyCountingTestApp/AndroidManifest.xml
+++ b/core/tests/coretests/BinderProxyCountingTestApp/AndroidManifest.xml
@@ -16,6 +16,9 @@
 <manifest xmlns:android="http://schemas.android.com/apk/res/android"
         package="com.android.frameworks.coretests.binderproxycountingtestapp">
 
+    <queries>
+        <package android:name="com.android.frameworks.coretests.binderproxycountingtestservice" />
+    </queries>
     <application>
         <service android:name=".BpcTestAppCmdService"
                  android:exported="true"/>
diff --git a/core/tests/coretests/BinderProxyCountingTestApp/src/com/android/frameworks/coretests/binderproxycountingtestapp/BpcTestAppCmdService.java b/core/tests/coretests/BinderProxyCountingTestApp/src/com/android/frameworks/coretests/binderproxycountingtestapp/BpcTestAppCmdService.java
index 5aae1203..a7e97d3 100644
--- a/core/tests/coretests/BinderProxyCountingTestApp/src/com/android/frameworks/coretests/binderproxycountingtestapp/BpcTestAppCmdService.java
+++ b/core/tests/coretests/BinderProxyCountingTestApp/src/com/android/frameworks/coretests/binderproxycountingtestapp/BpcTestAppCmdService.java
@@ -17,14 +17,15 @@
 package com.android.frameworks.coretests.binderproxycountingtestapp;
 
 import android.app.Service;
-import android.content.BroadcastReceiver;
 import android.content.ComponentName;
 import android.content.Context;
 import android.content.Intent;
-import android.content.IntentFilter;
 import android.content.ServiceConnection;
+import android.database.ContentObserver;
+import android.os.Handler;
 import android.os.IBinder;
 import android.os.RemoteException;
+import android.provider.Settings;
 import android.util.Log;
 
 import com.android.frameworks.coretests.aidl.IBinderProxyCountingService;
@@ -49,24 +50,20 @@
 
     private IBpcTestAppCmdService.Stub mBinder = new IBpcTestAppCmdService.Stub() {
 
-        private ArrayList<BroadcastReceiver> mBrList = new ArrayList();
+        private ArrayList<ContentObserver> mCoList = new ArrayList();
         private ArrayList<ITestRemoteCallback> mTrcList = new ArrayList();
+        private Handler mHandler = new Handler();
 
         @Override
         public void createSystemBinders(int count) {
             int i = 0;
             while (i++ < count) {
-                BroadcastReceiver br = new BroadcastReceiver() {
-                    @Override
-                    public void onReceive(Context context, Intent intent) {
-
-                    }
-                };
-                IntentFilter filt = new IntentFilter(Intent.ACTION_POWER_DISCONNECTED);
-                synchronized (mBrList) {
-                    mBrList.add(br);
+                final ContentObserver co = new ContentObserver(mHandler) {};
+                synchronized (mCoList) {
+                    mCoList.add(co);
                 }
-                registerReceiver(br, filt);
+                getContentResolver().registerContentObserver(
+                        Settings.System.CONTENT_URI, false, co);
             }
         }
 
@@ -74,11 +71,11 @@
         public void releaseSystemBinders(int count) {
             int i = 0;
             while (i++ < count) {
-                BroadcastReceiver br;
-                synchronized (mBrList) {
-                    br = mBrList.remove(0);
+                ContentObserver co;
+                synchronized (mCoList) {
+                    co = mCoList.remove(0);
                 }
-                unregisterReceiver(br);
+                getContentResolver().unregisterContentObserver(co);
             }
         }
 
@@ -117,9 +114,9 @@
 
         @Override
         public void releaseAllBinders() {
-            synchronized (mBrList) {
-                while (mBrList.size() > 0) {
-                    unregisterReceiver(mBrList.remove(0));
+            synchronized (mCoList) {
+                while (mCoList.size() > 0) {
+                    getContentResolver().unregisterContentObserver(mCoList.remove(0));
                 }
             }
             synchronized (mTrcList) {
@@ -179,4 +176,4 @@
     public IBinder onBind(Intent intent) {
         return mBinder;
     }
-}
\ No newline at end of file
+}
diff --git a/core/tests/coretests/BinderProxyCountingTestService/src/com/android/frameworks/coretests/binderproxycountingtestservice/BpcTestServiceCmdService.java b/core/tests/coretests/BinderProxyCountingTestService/src/com/android/frameworks/coretests/binderproxycountingtestservice/BpcTestServiceCmdService.java
index 6bed2a2..0f1accc 100644
--- a/core/tests/coretests/BinderProxyCountingTestService/src/com/android/frameworks/coretests/binderproxycountingtestservice/BpcTestServiceCmdService.java
+++ b/core/tests/coretests/BinderProxyCountingTestService/src/com/android/frameworks/coretests/binderproxycountingtestservice/BpcTestServiceCmdService.java
@@ -55,8 +55,8 @@
         }
 
         @Override
-        public void setBinderProxyWatermarks(int high, int low) {
-            BinderInternal.nSetBinderProxyCountWatermarks(high, low);
+        public void setBinderProxyWatermarks(int high, int low, int warning) {
+            BinderInternal.nSetBinderProxyCountWatermarks(high, low, warning);
         }
 
         @Override
@@ -68,12 +68,23 @@
         public void setBinderProxyCountCallback(IBpcCallbackObserver observer) {
             if (observer != null) {
                 BinderInternal.setBinderProxyCountCallback(
-                        new BinderInternal.BinderProxyLimitListener() {
+                        new BinderInternal.BinderProxyCountEventListener() {
                             @Override
                             public void onLimitReached(int uid) {
                                 try {
                                     synchronized (observer) {
-                                        observer.onCallback(uid);
+                                        observer.onLimitReached(uid);
+                                    }
+                                } catch (Exception e) {
+                                    Log.e(TAG, e.toString());
+                                }
+                            }
+
+                            @Override
+                            public void onWarningThresholdReached(int uid) {
+                                try {
+                                    synchronized (observer) {
+                                        observer.onWarningThresholdReached(uid);
                                     }
                                 } catch (Exception e) {
                                     Log.e(TAG, e.toString());
@@ -98,4 +109,4 @@
         mHandlerThread.start();
         mHandler = new Handler(mHandlerThread.getLooper());
     }
-}
\ No newline at end of file
+}
diff --git a/core/tests/coretests/aidl/com/android/frameworks/coretests/aidl/IBpcCallbackObserver.aidl b/core/tests/coretests/aidl/com/android/frameworks/coretests/aidl/IBpcCallbackObserver.aidl
index c4ebd56..ada7d92 100644
--- a/core/tests/coretests/aidl/com/android/frameworks/coretests/aidl/IBpcCallbackObserver.aidl
+++ b/core/tests/coretests/aidl/com/android/frameworks/coretests/aidl/IBpcCallbackObserver.aidl
@@ -17,5 +17,6 @@
 package com.android.frameworks.coretests.aidl;
 
 interface IBpcCallbackObserver {
-    void onCallback(int uid);
-}
\ No newline at end of file
+    void onLimitReached(int uid);
+    void onWarningThresholdReached(int uid);
+}
diff --git a/core/tests/coretests/aidl/com/android/frameworks/coretests/aidl/IBpcTestServiceCmdService.aidl b/core/tests/coretests/aidl/com/android/frameworks/coretests/aidl/IBpcTestServiceCmdService.aidl
index abdab41..cdcda9d 100644
--- a/core/tests/coretests/aidl/com/android/frameworks/coretests/aidl/IBpcTestServiceCmdService.aidl
+++ b/core/tests/coretests/aidl/com/android/frameworks/coretests/aidl/IBpcTestServiceCmdService.aidl
@@ -20,7 +20,7 @@
 interface IBpcTestServiceCmdService {
    void forceGc();
    int getBinderProxyCount(int uid);
-   void setBinderProxyWatermarks(int high, int low);
+   void setBinderProxyWatermarks(int high, int low, int warning);
    void enableBinderProxyLimit(boolean enable);
    void setBinderProxyCountCallback(IBpcCallbackObserver observer);
-}
\ No newline at end of file
+}
diff --git a/core/tests/coretests/src/android/content/res/ResourcesManagerTest.java b/core/tests/coretests/src/android/content/res/ResourcesManagerTest.java
index 38288dc..6b3cf7b 100644
--- a/core/tests/coretests/src/android/content/res/ResourcesManagerTest.java
+++ b/core/tests/coretests/src/android/content/res/ResourcesManagerTest.java
@@ -424,6 +424,44 @@
     @Test
     @SmallTest
     @RequiresFlagsEnabled(Flags.FLAG_REGISTER_RESOURCE_PATHS)
+    public void testExistingResourcesCreatedByConstructorAfterResourcePathsRegistration()
+            throws PackageManager.NameNotFoundException {
+        // Inject ResourcesManager instance from this test to the ResourcesManager class so that all
+        // the static method can interact with this test smoothly.
+        ResourcesManager oriResourcesManager = ResourcesManager.getInstance();
+        ResourcesManager.setInstance(mResourcesManager);
+
+        // Create a Resources through constructor directly before register resources' paths.
+        final DisplayMetrics metrics = new DisplayMetrics();
+        metrics.setToDefaults();
+        final Configuration config = new Configuration();
+        config.setToDefaults();
+        Resources resources = new Resources(new AssetManager(), metrics, config);
+        assertNotNull(resources);
+
+        ResourcesImpl oriResImpl = resources.getImpl();
+
+        ApplicationInfo appInfo = mPackageManager.getApplicationInfo(TEST_LIB, 0);
+        Resources.registerResourcePaths(TEST_LIB, appInfo);
+
+        assertNotSame(oriResImpl, resources.getImpl());
+
+        String[] resourcePaths = appInfo.getAllApkPaths();
+        resourcePaths = removeDuplicates(resourcePaths);
+        ApkAssets[] loadedAssets = resources.getAssets().getApkAssets();
+        assertTrue(allResourcePathsLoaded(resourcePaths, loadedAssets));
+
+        // Package resources' paths should be cached in ResourcesManager.
+        assertEquals(Arrays.toString(resourcePaths), Arrays.toString(ResourcesManager.getInstance()
+                .getSharedLibAssetsMap().get(TEST_LIB).getAllAssetPaths()));
+
+        // Revert the ResourcesManager instance back.
+        ResourcesManager.setInstance(oriResourcesManager);
+    }
+
+    @Test
+    @SmallTest
+    @RequiresFlagsEnabled(Flags.FLAG_REGISTER_RESOURCE_PATHS)
     public void testNewResourcesWithOutdatedImplAfterResourcePathsRegistration()
             throws PackageManager.NameNotFoundException {
         ResourcesManager oriResourcesManager = ResourcesManager.getInstance();
diff --git a/core/tests/coretests/src/android/hardware/face/FaceSensorConfigurationsTest.java b/core/tests/coretests/src/android/hardware/face/FaceSensorConfigurationsTest.java
index b61104d..7d7f8ed 100644
--- a/core/tests/coretests/src/android/hardware/face/FaceSensorConfigurationsTest.java
+++ b/core/tests/coretests/src/android/hardware/face/FaceSensorConfigurationsTest.java
@@ -18,13 +18,10 @@
 
 import static com.google.common.truth.Truth.assertThat;
 
-import static org.mockito.ArgumentMatchers.anyString;
 import static org.mockito.Mockito.when;
 
 import android.content.Context;
 import android.content.res.Resources;
-import android.hardware.biometrics.face.IFace;
-import android.hardware.biometrics.face.SensorProps;
 import android.os.RemoteException;
 import android.platform.test.annotations.Presubmit;
 
@@ -37,8 +34,6 @@
 import org.mockito.junit.MockitoJUnit;
 import org.mockito.junit.MockitoRule;
 
-import java.util.function.Function;
-
 @Presubmit
 @SmallTest
 public class FaceSensorConfigurationsTest {
@@ -48,10 +43,6 @@
     private Context mContext;
     @Mock
     private Resources mResources;
-    @Mock
-    private IFace mFace;
-    @Mock
-    private Function<String, IFace> mGetIFace;
 
     private final String[] mAidlInstances = new String[]{"default", "virtual"};
     private String[] mHidlConfigStrings = new String[]{"0:2:15", "0:8:15"};
@@ -59,15 +50,13 @@
 
     @Before
     public void setUp() throws RemoteException {
-        when(mGetIFace.apply(anyString())).thenReturn(mFace);
-        when(mFace.getSensorProps()).thenReturn(new SensorProps[]{});
         when(mContext.getResources()).thenReturn(mResources);
     }
 
     @Test
     public void testAidlInstanceSensorProps() {
         mFaceSensorConfigurations = new FaceSensorConfigurations(false);
-        mFaceSensorConfigurations.addAidlConfigs(mAidlInstances, mGetIFace);
+        mFaceSensorConfigurations.addAidlConfigs(mAidlInstances);
 
         assertThat(mFaceSensorConfigurations.hasSensorConfigurations()).isTrue();
         assertThat(!mFaceSensorConfigurations.isSingleSensorConfigurationPresent()).isTrue();
diff --git a/core/tests/coretests/src/android/hardware/fingerprint/FingerprintSensorConfigurationsTest.java b/core/tests/coretests/src/android/hardware/fingerprint/FingerprintSensorConfigurationsTest.java
index f058c16..b2f8299 100644
--- a/core/tests/coretests/src/android/hardware/fingerprint/FingerprintSensorConfigurationsTest.java
+++ b/core/tests/coretests/src/android/hardware/fingerprint/FingerprintSensorConfigurationsTest.java
@@ -18,13 +18,10 @@
 
 import static com.google.common.truth.Truth.assertThat;
 
-import static org.mockito.ArgumentMatchers.anyString;
 import static org.mockito.Mockito.when;
 
 import android.content.Context;
 import android.content.res.Resources;
-import android.hardware.biometrics.fingerprint.IFingerprint;
-import android.hardware.biometrics.fingerprint.SensorProps;
 import android.os.RemoteException;
 import android.platform.test.annotations.Presubmit;
 
@@ -37,8 +34,6 @@
 import org.mockito.junit.MockitoJUnit;
 import org.mockito.junit.MockitoRule;
 
-import java.util.function.Function;
-
 @Presubmit
 @SmallTest
 public class FingerprintSensorConfigurationsTest {
@@ -48,10 +43,6 @@
     private Context mContext;
     @Mock
     private Resources mResources;
-    @Mock
-    private IFingerprint mFingerprint;
-    @Mock
-    private Function<String, IFingerprint> mGetIFingerprint;
 
     private final String[] mAidlInstances = new String[]{"default", "virtual"};
     private String[] mHidlConfigStrings = new String[]{"0:2:15", "0:8:15"};
@@ -59,8 +50,6 @@
 
     @Before
     public void setUp() throws RemoteException {
-        when(mGetIFingerprint.apply(anyString())).thenReturn(mFingerprint);
-        when(mFingerprint.getSensorProps()).thenReturn(new SensorProps[]{});
         when(mContext.getResources()).thenReturn(mResources);
     }
 
@@ -68,7 +57,7 @@
     public void testAidlInstanceSensorProps() {
         mFingerprintSensorConfigurations = new FingerprintSensorConfigurations(
                 true /* resetLockoutRequiresHardwareAuthToken */);
-        mFingerprintSensorConfigurations.addAidlSensors(mAidlInstances, mGetIFingerprint);
+        mFingerprintSensorConfigurations.addAidlSensors(mAidlInstances);
 
         assertThat(mFingerprintSensorConfigurations.hasSensorConfigurations()).isTrue();
         assertThat(!mFingerprintSensorConfigurations.isSingleSensorConfigurationPresent())
diff --git a/core/tests/coretests/src/android/os/BinderProxyCountingTest.java b/core/tests/coretests/src/android/os/BinderProxyCountingTest.java
index bcd9521..84d2995 100644
--- a/core/tests/coretests/src/android/os/BinderProxyCountingTest.java
+++ b/core/tests/coretests/src/android/os/BinderProxyCountingTest.java
@@ -88,9 +88,10 @@
 
     private static final int BIND_SERVICE_TIMEOUT_SEC = 5;
     private static final int TOO_MANY_BINDERS_TIMEOUT_SEC = 2;
+    private static final int TOO_MANY_BINDERS_WITH_KILL_TIMEOUT_SEC = 30;
 
-    // Keep in sync with sBinderProxyCountLimit in BpBinder.cpp
-    private static final int BINDER_PROXY_LIMIT = 2500;
+    // Keep in sync with BINDER_PROXY_HIGH_WATERMARK in ActivityManagerService.java
+    private static final int BINDER_PROXY_LIMIT = 6000;
 
     private static Context sContext;
     private static UiDevice sUiDevice;
@@ -175,18 +176,26 @@
         }
     }
 
-    private CountDownLatch createBinderLimitLatch() throws RemoteException {
-        final CountDownLatch latch = new CountDownLatch(1);
+    private CountDownLatch[] createBinderLimitLatch() throws RemoteException {
+        final CountDownLatch[] latches = new CountDownLatch[] {
+            new CountDownLatch(1), new CountDownLatch(1)
+        };
         sBpcTestServiceCmdService.setBinderProxyCountCallback(
                 new IBpcCallbackObserver.Stub() {
                     @Override
-                    public void onCallback(int uid) {
+                    public void onLimitReached(int uid) {
                         if (uid == sTestPkgUid) {
-                            latch.countDown();
+                            latches[0].countDown();
+                        }
+                    }
+                    @Override
+                    public void onWarningThresholdReached(int uid) {
+                        if (uid == sTestPkgUid) {
+                            latches[1].countDown();
                         }
                     }
                 });
-        return latch;
+        return latches;
     }
 
     /**
@@ -227,6 +236,7 @@
     @Test
     public void testBinderProxyLimitBoundary() throws Exception {
         final int binderProxyLimit = 2000;
+        final int binderProxyWarning = 1900;
         final int rearmThreshold = 1800;
         try {
             sTestAppConnection = bindService(sTestAppConsumer, sTestAppIntent);
@@ -238,19 +248,33 @@
             // Get the baseline of binders naturally held by the test Package
             int baseBinderCount = sBpcTestServiceCmdService.getBinderProxyCount(sTestPkgUid);
 
-            final CountDownLatch binderLimitLatch = createBinderLimitLatch();
-            sBpcTestServiceCmdService.setBinderProxyWatermarks(binderProxyLimit, rearmThreshold);
+            final CountDownLatch[] binderLatches = createBinderLimitLatch();
+            sBpcTestServiceCmdService.setBinderProxyWatermarks(binderProxyLimit, rearmThreshold,
+                    binderProxyWarning);
+
+            // Create Binder Proxies up to the warning;
+            sBpcTestAppCmdService.createTestBinders(binderProxyWarning - baseBinderCount);
+            if (binderLatches[1].await(TOO_MANY_BINDERS_TIMEOUT_SEC, TimeUnit.SECONDS)) {
+                fail("Received BinderProxyLimitCallback for uid " + sTestPkgUid
+                        + " when proxy warning should not have been triggered");
+            }
+
+            // Create one more Binder to trigger the warning
+            sBpcTestAppCmdService.createTestBinders(1);
+            if (!binderLatches[1].await(TOO_MANY_BINDERS_TIMEOUT_SEC, TimeUnit.SECONDS)) {
+                fail("Timed out waiting for uid " + sTestPkgUid + " to trigger the warning");
+            }
 
             // Create Binder Proxies up to the limit
-            sBpcTestAppCmdService.createTestBinders(binderProxyLimit - baseBinderCount);
-            if (binderLimitLatch.await(TOO_MANY_BINDERS_TIMEOUT_SEC, TimeUnit.SECONDS)) {
+            sBpcTestAppCmdService.createTestBinders(binderProxyLimit - binderProxyWarning - 1);
+            if (binderLatches[0].await(TOO_MANY_BINDERS_TIMEOUT_SEC, TimeUnit.SECONDS)) {
                 fail("Received BinderProxyLimitCallback for uid " + sTestPkgUid
                         + " when proxy limit should not have been reached");
             }
 
             // Create one more Binder to cross the limit
             sBpcTestAppCmdService.createTestBinders(1);
-            if (!binderLimitLatch.await(TOO_MANY_BINDERS_TIMEOUT_SEC, TimeUnit.SECONDS)) {
+            if (!binderLatches[0].await(TOO_MANY_BINDERS_TIMEOUT_SEC, TimeUnit.SECONDS)) {
                 fail("Timed out waiting for uid " + sTestPkgUid + " to hit limit");
             }
 
@@ -274,12 +298,20 @@
             sBpcTestServiceCmdService.forceGc();
             int baseBinderCount = sBpcTestServiceCmdService.getBinderProxyCount(sTestPkgUid);
             for (int testLimit : testLimits) {
-                final CountDownLatch binderLimitLatch = createBinderLimitLatch();
+                final CountDownLatch[] binderLatches = createBinderLimitLatch();
                 // Change the BinderProxyLimit
-                sBpcTestServiceCmdService.setBinderProxyWatermarks(testLimit, baseBinderCount + 10);
+                sBpcTestServiceCmdService.setBinderProxyWatermarks(testLimit, baseBinderCount + 10,
+                        testLimit - 10);
+
+                // Trigger the new Binder Proxy warning
+                sBpcTestAppCmdService.createTestBinders(testLimit - 9 - baseBinderCount);
+                if (!binderLatches[1].await(TOO_MANY_BINDERS_TIMEOUT_SEC, TimeUnit.SECONDS)) {
+                    fail("Timed out waiting for uid " + sTestPkgUid + " to trigger the warning");
+                }
+
                 // Exceed the new Binder Proxy Limit
-                sBpcTestAppCmdService.createTestBinders(testLimit + 1);
-                if (!binderLimitLatch.await(TOO_MANY_BINDERS_TIMEOUT_SEC, TimeUnit.SECONDS)) {
+                sBpcTestAppCmdService.createTestBinders(10);
+                if (!binderLatches[0].await(TOO_MANY_BINDERS_TIMEOUT_SEC, TimeUnit.SECONDS)) {
                     fail("Timed out waiting for uid " + sTestPkgUid + " to hit limit");
                 }
 
@@ -297,6 +329,7 @@
     public void testRearmCallbackThreshold() throws Exception {
         final int binderProxyLimit = 2000;
         final int exceedBinderProxyLimit = binderProxyLimit + 10;
+        final int binderProxyWarning = 1900;
         final int rearmThreshold = 1800;
         try {
             sTestAppConnection = bindService(sTestAppConsumer, sTestAppIntent);
@@ -305,11 +338,19 @@
             sBpcTestServiceCmdService.enableBinderProxyLimit(true);
 
             sBpcTestServiceCmdService.forceGc();
-            final CountDownLatch firstBinderLimitLatch = createBinderLimitLatch();
-            sBpcTestServiceCmdService.setBinderProxyWatermarks(binderProxyLimit, rearmThreshold);
+            int baseBinderCount = sBpcTestServiceCmdService.getBinderProxyCount(sTestPkgUid);
+            final CountDownLatch[] firstBinderLatches = createBinderLimitLatch();
+            sBpcTestServiceCmdService.setBinderProxyWatermarks(binderProxyLimit, rearmThreshold,
+                    binderProxyWarning);
+            // Trigger the Binder Proxy Waring
+            sBpcTestAppCmdService.createTestBinders(binderProxyWarning - baseBinderCount + 1);
+            if (!firstBinderLatches[1].await(TOO_MANY_BINDERS_TIMEOUT_SEC, TimeUnit.SECONDS)) {
+                fail("Timed out waiting for uid " + sTestPkgUid + " to trigger warning");
+            }
+
             // Exceed the Binder Proxy Limit
-            sBpcTestAppCmdService.createTestBinders(exceedBinderProxyLimit);
-            if (!firstBinderLimitLatch.await(TOO_MANY_BINDERS_TIMEOUT_SEC, TimeUnit.SECONDS)) {
+            sBpcTestAppCmdService.createTestBinders(exceedBinderProxyLimit - binderProxyWarning);
+            if (!firstBinderLatches[0].await(TOO_MANY_BINDERS_TIMEOUT_SEC, TimeUnit.SECONDS)) {
                 fail("Timed out waiting for uid " + sTestPkgUid + " to hit limit");
             }
 
@@ -321,11 +362,20 @@
             sBpcTestServiceCmdService.forceGc();
             currentBinderCount = sBpcTestServiceCmdService.getBinderProxyCount(sTestPkgUid);
 
-            final CountDownLatch secondBinderLimitLatch = createBinderLimitLatch();
+            final CountDownLatch[] secondBinderLatches = createBinderLimitLatch();
+
+            // Exceed the Binder Proxy warning which should not cause a callback since there has
+            // been no rearm
+            sBpcTestAppCmdService.createTestBinders(binderProxyWarning - currentBinderCount + 1);
+            if (secondBinderLatches[1].await(TOO_MANY_BINDERS_TIMEOUT_SEC, TimeUnit.SECONDS)) {
+                fail("Received BinderProxyLimitCallback for uid " + sTestPkgUid
+                        + " when the callback has not been rearmed yet");
+            }
+
             // Exceed the Binder Proxy limit which should not cause a callback since there has
             // been no rearm
-            sBpcTestAppCmdService.createTestBinders(exceedBinderProxyLimit - currentBinderCount);
-            if (secondBinderLimitLatch.await(TOO_MANY_BINDERS_TIMEOUT_SEC, TimeUnit.SECONDS)) {
+            sBpcTestAppCmdService.createTestBinders(exceedBinderProxyLimit - binderProxyWarning);
+            if (secondBinderLatches[0].await(TOO_MANY_BINDERS_TIMEOUT_SEC, TimeUnit.SECONDS)) {
                 fail("Received BinderProxyLimitCallback for uid " + sTestPkgUid
                         + " when the callback has not been rearmed yet");
             }
@@ -337,10 +387,16 @@
 
             sBpcTestServiceCmdService.forceGc();
             currentBinderCount = sBpcTestServiceCmdService.getBinderProxyCount(sTestPkgUid);
+            // Trigger the Binder Proxy Waring
+            sBpcTestAppCmdService.createTestBinders(binderProxyWarning - currentBinderCount + 1);
+            if (!secondBinderLatches[1].await(TOO_MANY_BINDERS_TIMEOUT_SEC, TimeUnit.SECONDS)) {
+                fail("Timed out waiting for uid " + sTestPkgUid + " to trigger warning");
+            }
+
             // Exceed the Binder Proxy limit for the last time
             sBpcTestAppCmdService.createTestBinders(exceedBinderProxyLimit - currentBinderCount);
 
-            if (!secondBinderLimitLatch.await(TOO_MANY_BINDERS_TIMEOUT_SEC, TimeUnit.SECONDS)) {
+            if (!secondBinderLatches[0].await(TOO_MANY_BINDERS_TIMEOUT_SEC, TimeUnit.SECONDS)) {
                 fail("Timed out waiting for uid " + sTestPkgUid + " to hit limit");
             }
             sBpcTestAppCmdService.releaseTestBinders(currentBinderCount);
@@ -373,7 +429,7 @@
                 // is not unexpected
             }
 
-            if (!binderDeathLatch.await(TOO_MANY_BINDERS_TIMEOUT_SEC, TimeUnit.SECONDS)) {
+            if (!binderDeathLatch.await(TOO_MANY_BINDERS_WITH_KILL_TIMEOUT_SEC, TimeUnit.SECONDS)) {
                 sBpcTestAppCmdService.releaseSystemBinders(exceedBinderProxyLimit);
                 fail("Timed out waiting for uid " + sTestPkgUid + " to die.");
             }
diff --git a/core/tests/coretests/src/android/window/WindowOnBackInvokedDispatcherTest.java b/core/tests/coretests/src/android/window/WindowOnBackInvokedDispatcherTest.java
index a709d7b..6321e5d 100644
--- a/core/tests/coretests/src/android/window/WindowOnBackInvokedDispatcherTest.java
+++ b/core/tests/coretests/src/android/window/WindowOnBackInvokedDispatcherTest.java
@@ -96,7 +96,7 @@
         doReturn(mApplicationInfo).when(mContext).getApplicationInfo();
 
         mDispatcher = new WindowOnBackInvokedDispatcher(mContext);
-        mDispatcher.attachToWindow(mWindowSession, mWindow);
+        mDispatcher.attachToWindow(mWindowSession, mWindow, null);
     }
 
     private void waitForIdle() {
diff --git a/core/tests/coretests/src/com/android/internal/accessibility/AccessibilityShortcutChooserActivityTest.java b/core/tests/coretests/src/com/android/internal/accessibility/AccessibilityShortcutChooserActivityTest.java
index 745390d..9f5ed29 100644
--- a/core/tests/coretests/src/com/android/internal/accessibility/AccessibilityShortcutChooserActivityTest.java
+++ b/core/tests/coretests/src/com/android/internal/accessibility/AccessibilityShortcutChooserActivityTest.java
@@ -53,7 +53,6 @@
 import android.content.pm.ServiceInfo;
 import android.os.Bundle;
 import android.os.Handler;
-import android.platform.test.annotations.RequiresFlagsEnabled;
 import android.platform.test.flag.junit.CheckFlagsRule;
 import android.platform.test.flag.junit.DeviceFlagsValueProvider;
 import android.support.test.uiautomator.By;
@@ -63,7 +62,6 @@
 import android.view.View;
 import android.view.WindowManager;
 import android.view.accessibility.AccessibilityManager;
-import android.view.accessibility.Flags;
 import android.view.accessibility.IAccessibilityManager;
 import android.widget.Button;
 
@@ -298,7 +296,6 @@
     }
 
     @Test
-    @RequiresFlagsEnabled(Flags.FLAG_ALLOW_SHORTCUT_CHOOSER_ON_LOCKSCREEN)
     public void createDialog_onLockscreen_hasExpectedContent() {
         when(mKeyguardManager.isKeyguardLocked()).thenReturn(true);
         launchActivity();
diff --git a/core/tests/coretests/src/com/android/internal/net/ConnectivityBlobStoreTest.java b/core/tests/coretests/src/com/android/internal/net/ConnectivityBlobStoreTest.java
index 68545cf..ad4ccc9 100644
--- a/core/tests/coretests/src/com/android/internal/net/ConnectivityBlobStoreTest.java
+++ b/core/tests/coretests/src/com/android/internal/net/ConnectivityBlobStoreTest.java
@@ -153,4 +153,41 @@
         final String[] actual = connectivityBlobStore.list(TEST_NAME /* prefix */);
         assertArrayEquals(expected, actual);
     }
+
+    @Test
+    public void testList_underscoreInPrefix() throws Exception {
+        final String prefix = TEST_NAME + "_";
+        final String[] unsortedNames = new String[] {
+                prefix + "000",
+                TEST_NAME + "123",
+        };
+        // The '_' in the prefix should not be treated as a wildcard so the only match is "000".
+        final String[] expected = new String[] {"000"};
+        final ConnectivityBlobStore connectivityBlobStore = createConnectivityBlobStore();
+
+        for (int i = 0; i < unsortedNames.length; i++) {
+            assertTrue(connectivityBlobStore.put(unsortedNames[i], TEST_BLOB));
+        }
+        final String[] actual = connectivityBlobStore.list(prefix);
+        assertArrayEquals(expected, actual);
+    }
+
+    @Test
+    public void testList_percentInPrefix() throws Exception {
+        final String prefix = "%" + TEST_NAME + "%";
+        final String[] unsortedNames = new String[] {
+                TEST_NAME + "12345",
+                prefix + "0",
+                "abc" + TEST_NAME + "987",
+        };
+        // The '%' in the prefix should not be treated as a wildcard so the only match is "0".
+        final String[] expected = new String[] {"0"};
+        final ConnectivityBlobStore connectivityBlobStore = createConnectivityBlobStore();
+
+        for (int i = 0; i < unsortedNames.length; i++) {
+            assertTrue(connectivityBlobStore.put(unsortedNames[i], TEST_BLOB));
+        }
+        final String[] actual = connectivityBlobStore.list(prefix);
+        assertArrayEquals(expected, actual);
+    }
 }
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/back/BackAnimationController.java b/libs/WindowManager/Shell/src/com/android/wm/shell/back/BackAnimationController.java
index ad3be3d..59c841f 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/back/BackAnimationController.java
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/back/BackAnimationController.java
@@ -686,7 +686,11 @@
         }
     }
 
-    private void dispatchOnBackCancelled(IOnBackInvokedCallback callback) {
+    private void tryDispatchOnBackCancelled(IOnBackInvokedCallback callback) {
+        if (!mOnBackStartDispatched) {
+            Log.e(TAG, "Skipping dispatching onBackCancelled. Start was never dispatched.");
+            return;
+        }
         if (callback == null) {
             return;
         }
@@ -745,7 +749,7 @@
             if (touchTracker.getTriggerBack()) {
                 dispatchOrAnimateOnBackInvoked(callback, touchTracker);
             } else {
-                dispatchOnBackCancelled(callback);
+                tryDispatchOnBackCancelled(callback);
             }
         }
         finishBackNavigation(touchTracker.getTriggerBack());
@@ -824,7 +828,7 @@
         if (mCurrentTracker.getTriggerBack()) {
             dispatchOrAnimateOnBackInvoked(mActiveCallback, mCurrentTracker);
         } else {
-            dispatchOnBackCancelled(mActiveCallback);
+            tryDispatchOnBackCancelled(mActiveCallback);
         }
     }
 
@@ -876,7 +880,7 @@
         if (mCurrentTracker.isInInitialState()) {
             if (mBackGestureStarted) {
                 mBackGestureStarted = false;
-                dispatchOnBackCancelled(mActiveCallback);
+                tryDispatchOnBackCancelled(mActiveCallback);
                 finishBackNavigation(false);
                 ProtoLog.d(WM_SHELL_BACK_PREVIEW,
                         "resetTouchTracker -> reset an unfinished gesture");
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/back/CrossActivityBackAnimation.kt b/libs/WindowManager/Shell/src/com/android/wm/shell/back/CrossActivityBackAnimation.kt
index 3253cac..772eae7 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/back/CrossActivityBackAnimation.kt
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/back/CrossActivityBackAnimation.kt
@@ -65,7 +65,7 @@
     private val targetEnteringRect = RectF()
     private val currentEnteringRect = RectF()
 
-    private val taskBoundsRect = Rect()
+    private val backAnimRect = Rect()
 
     private val cornerRadius = ScreenDecorationsUtils.getWindowCornerRadius(context)
 
@@ -109,10 +109,10 @@
         transaction.setAnimationTransaction()
 
         // Offset start rectangle to align task bounds.
-        taskBoundsRect.set(closingTarget!!.windowConfiguration.bounds)
-        taskBoundsRect.offsetTo(0, 0)
+        backAnimRect.set(closingTarget!!.localBounds)
+        backAnimRect.offsetTo(0, 0)
 
-        startClosingRect.set(taskBoundsRect)
+        startClosingRect.set(backAnimRect)
 
         // scale closing target into the middle for rhs and to the right for lhs
         targetClosingRect.set(startClosingRect)
@@ -154,7 +154,7 @@
     }
 
     private fun getYOffset(centeredRect: RectF, touchY: Float): Float {
-        val screenHeight = taskBoundsRect.height()
+        val screenHeight = backAnimRect.height()
         // Base the window movement in the Y axis on the touch movement in the Y axis.
         val rawYDelta = touchY - initialTouchPos.y
         val yDirection = (if (rawYDelta < 0) -1 else 1)
@@ -181,8 +181,8 @@
         // off the animator
         startClosingRect.set(currentClosingRect)
         startEnteringRect.set(currentEnteringRect)
-        targetEnteringRect.set(taskBoundsRect)
-        targetClosingRect.set(taskBoundsRect)
+        targetEnteringRect.set(backAnimRect)
+        targetClosingRect.set(backAnimRect)
         targetClosingRect.offset(currentClosingRect.left + enteringStartOffset, 0f)
 
         val valueAnimator = ValueAnimator.ofFloat(1f, 0f).setDuration(POST_ANIMATION_DURATION)
@@ -240,13 +240,13 @@
 
     private fun applyTransform(leash: SurfaceControl?, rect: RectF, alpha: Float) {
         if (leash == null || !leash.isValid) return
-        val scale = rect.width() / taskBoundsRect.width()
+        val scale = rect.width() / backAnimRect.width()
         transformMatrix.reset()
         transformMatrix.setScale(scale, scale)
         transformMatrix.postTranslate(rect.left, rect.top)
         transaction.setAlpha(leash, alpha)
             .setMatrix(leash, transformMatrix, tmpFloat9)
-            .setCrop(leash, taskBoundsRect)
+            .setCrop(leash, backAnimRect)
             .setCornerRadius(leash, cornerRadius)
     }
 
@@ -267,6 +267,7 @@
         transaction
             .setColor(scrimLayer, colorComponents)
             .setAlpha(scrimLayer!!, maxScrimAlpha)
+            .setCrop(scrimLayer!!, closingTarget!!.localBounds)
             .setRelativeLayer(scrimLayer!!, closingTarget!!.leash, -1)
             .show(scrimLayer)
     }
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/dagger/pip/Pip2Module.java b/libs/WindowManager/Shell/src/com/android/wm/shell/dagger/pip/Pip2Module.java
index ae07812..b86e39f 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/dagger/pip/Pip2Module.java
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/dagger/pip/Pip2Module.java
@@ -93,8 +93,9 @@
     @Provides
     static PipScheduler providePipScheduler(Context context,
             PipBoundsState pipBoundsState,
-            @ShellMainThread ShellExecutor mainExecutor) {
-        return new PipScheduler(context, pipBoundsState, mainExecutor);
+            @ShellMainThread ShellExecutor mainExecutor,
+            ShellTaskOrganizer shellTaskOrganizer) {
+        return new PipScheduler(context, pipBoundsState, mainExecutor, shellTaskOrganizer);
     }
 
     @WMSingleton
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/desktopmode/DesktopModeTaskRepository.kt b/libs/WindowManager/Shell/src/com/android/wm/shell/desktopmode/DesktopModeTaskRepository.kt
index 7c8fcbb..99a00b8 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/desktopmode/DesktopModeTaskRepository.kt
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/desktopmode/DesktopModeTaskRepository.kt
@@ -20,6 +20,7 @@
 import android.util.ArrayMap
 import android.util.ArraySet
 import android.util.SparseArray
+import android.view.Display.INVALID_DISPLAY
 import androidx.core.util.forEach
 import androidx.core.util.keyIterator
 import androidx.core.util.valueIterator
@@ -226,6 +227,14 @@
                         displayData[otherDisplayId].visibleTasks.size)
                 }
             }
+        } else if (displayId == INVALID_DISPLAY) {
+            // Task has vanished. Check which display to remove the task from.
+            displayData.forEach { displayId, data ->
+                if (data.visibleTasks.remove(taskId)) {
+                    notifyVisibleTaskListeners(displayId, data.visibleTasks.size)
+                }
+            }
+            return
         }
 
         val prevCount = getVisibleTaskCount(displayId)
@@ -236,6 +245,7 @@
         }
         val newCount = getVisibleTaskCount(displayId)
 
+        // Check if count changed
         if (prevCount != newCount) {
             KtProtoLog.d(
                 WM_SHELL_DESKTOP_MODE,
@@ -244,10 +254,6 @@
                 visible,
                 displayId
             )
-        }
-
-        // Check if count changed
-        if (prevCount != newCount) {
             KtProtoLog.d(
                 WM_SHELL_DESKTOP_MODE,
                 "DesktopTaskRepo: visibleTaskCount has changed from %d to %d",
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/pip2/phone/PipController.java b/libs/WindowManager/Shell/src/com/android/wm/shell/pip2/phone/PipController.java
index 4c69cc3..1e18b8c 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/pip2/phone/PipController.java
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/pip2/phone/PipController.java
@@ -245,9 +245,9 @@
             Rect destinationBounds, SurfaceControl overlay, Rect appBounds) {
         ProtoLog.d(ShellProtoLogGroup.WM_SHELL_PICTURE_IN_PICTURE,
                 "onSwipePipToHomeAnimationStart: %s", componentName);
-        mPipScheduler.setInSwipePipToHomeTransition(true);
+        mPipScheduler.onSwipePipToHomeAnimationStart(taskId, componentName, destinationBounds,
+                overlay, appBounds);
         mPipRecentsAnimationListener.onPipAnimationStarted();
-        // TODO: cache the overlay if provided for reparenting later.
     }
 
     //
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/pip2/phone/PipScheduler.java b/libs/WindowManager/Shell/src/com/android/wm/shell/pip2/phone/PipScheduler.java
index 6665013..b4ca7df 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/pip2/phone/PipScheduler.java
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/pip2/phone/PipScheduler.java
@@ -21,6 +21,7 @@
 import static com.android.wm.shell.transition.Transitions.TRANSIT_EXIT_PIP;
 
 import android.content.BroadcastReceiver;
+import android.content.ComponentName;
 import android.content.Context;
 import android.content.Intent;
 import android.content.IntentFilter;
@@ -30,9 +31,11 @@
 import android.window.WindowContainerTransaction;
 
 import androidx.annotation.IntDef;
+import androidx.annotation.NonNull;
 import androidx.annotation.Nullable;
 import androidx.core.content.ContextCompat;
 
+import com.android.wm.shell.ShellTaskOrganizer;
 import com.android.wm.shell.common.ShellExecutor;
 import com.android.wm.shell.common.pip.PipBoundsState;
 import com.android.wm.shell.common.pip.PipUtils;
@@ -52,6 +55,7 @@
     private final Context mContext;
     private final PipBoundsState mPipBoundsState;
     private final ShellExecutor mMainExecutor;
+    private final ShellTaskOrganizer mShellTaskOrganizer;
     private PipSchedulerReceiver mSchedulerReceiver;
     private PipTransitionController mPipTransitionController;
 
@@ -66,6 +70,16 @@
     // true if Launcher has started swipe PiP to home animation
     private boolean mInSwipePipToHomeTransition;
 
+    // Overlay leash potentially used during swipe PiP to home transition;
+    // if null while mInSwipePipToHomeTransition is true, then srcRectHint was invalid.
+    @Nullable
+    SurfaceControl mSwipePipToHomeOverlay;
+
+    // App bounds used when as a starting point to swipe PiP to home animation in Launcher;
+    // these are also used to calculate the app icon overlay buffer size.
+    @NonNull
+    final Rect mSwipePipToHomeAppBounds = new Rect();
+
     /**
      * Temporary PiP CUJ codes to schedule PiP related transitions directly from Shell.
      * This is used for a broadcast receiver to resolve intents. This should be removed once
@@ -101,11 +115,14 @@
         }
     }
 
-    public PipScheduler(Context context, PipBoundsState pipBoundsState,
-            ShellExecutor mainExecutor) {
+    public PipScheduler(Context context,
+            PipBoundsState pipBoundsState,
+            ShellExecutor mainExecutor,
+            ShellTaskOrganizer shellTaskOrganizer) {
         mContext = context;
         mPipBoundsState = pipBoundsState;
         mMainExecutor = mainExecutor;
+        mShellTaskOrganizer = shellTaskOrganizer;
 
         if (PipUtils.isPip2ExperimentEnabled()) {
             // temporary broadcast receiver to initiate exit PiP via expand
@@ -115,6 +132,10 @@
         }
     }
 
+    ShellExecutor getMainExecutor() {
+        return mMainExecutor;
+    }
+
     void setPipTransitionController(PipTransitionController pipTransitionController) {
         mPipTransitionController = pipTransitionController;
     }
@@ -171,6 +192,24 @@
         mPipTransitionController.startResizeTransition(wct, onFinishResizeCallback);
     }
 
+    void onSwipePipToHomeAnimationStart(int taskId, ComponentName componentName,
+            Rect destinationBounds, SurfaceControl overlay, Rect appBounds) {
+        mInSwipePipToHomeTransition = true;
+        mSwipePipToHomeOverlay = overlay;
+        mSwipePipToHomeAppBounds.set(appBounds);
+        if (overlay != null) {
+            // Shell transitions might use a root animation leash, which will be removed when
+            // the Recents transition is finished. Launcher attaches the overlay leash to this
+            // animation target leash; thus, we need to reparent it to the actual Task surface now.
+            // PipTransition is responsible to fade it out and cleanup when finishing the enter PIP
+            // transition.
+            SurfaceControl.Transaction tx = new SurfaceControl.Transaction();
+            mShellTaskOrganizer.reparentChildSurfaceToTask(taskId, overlay, tx);
+            tx.setLayer(overlay, Integer.MAX_VALUE);
+            tx.apply();
+        }
+    }
+
     void setInSwipePipToHomeTransition(boolean inSwipePipToHome) {
         mInSwipePipToHomeTransition = inSwipePipToHome;
     }
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/pip2/phone/PipTransition.java b/libs/WindowManager/Shell/src/com/android/wm/shell/pip2/phone/PipTransition.java
index d15da4a..b179b5b 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/pip2/phone/PipTransition.java
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/pip2/phone/PipTransition.java
@@ -24,6 +24,9 @@
 import static com.android.wm.shell.transition.Transitions.TRANSIT_EXIT_PIP;
 import static com.android.wm.shell.transition.Transitions.TRANSIT_RESIZE_PIP;
 
+import android.animation.Animator;
+import android.animation.AnimatorListenerAdapter;
+import android.animation.ValueAnimator;
 import android.annotation.NonNull;
 import android.app.ActivityManager;
 import android.app.PictureInPictureParams;
@@ -44,6 +47,7 @@
 import com.android.wm.shell.common.pip.PipBoundsState;
 import com.android.wm.shell.common.pip.PipMenuController;
 import com.android.wm.shell.common.pip.PipUtils;
+import com.android.wm.shell.pip.PipContentOverlay;
 import com.android.wm.shell.pip.PipTransitionController;
 import com.android.wm.shell.sysui.ShellInit;
 import com.android.wm.shell.transition.Transitions;
@@ -55,6 +59,11 @@
  */
 public class PipTransition extends PipTransitionController {
     private static final String TAG = PipTransition.class.getSimpleName();
+    /**
+     * The fixed start delay in ms when fading out the content overlay from bounds animation.
+     * The fadeout animation is guaranteed to start after the client has drawn under the new config.
+     */
+    private static final int CONTENT_OVERLAY_FADE_OUT_DELAY_MS = 400;
 
     private final Context mContext;
     private final PipScheduler mPipScheduler;
@@ -230,10 +239,13 @@
 
         PictureInPictureParams params = pipChange.getTaskInfo().pictureInPictureParams;
         Rect srcRectHint = params.getSourceRectHint();
+        Rect startBounds = pipChange.getStartAbsBounds();
         Rect destinationBounds = pipChange.getEndAbsBounds();
 
+        WindowContainerTransaction finishWct = new WindowContainerTransaction();
+
         if (PipBoundsAlgorithm.isSourceRectHintValidForEnterPip(srcRectHint, destinationBounds)) {
-            float scale = (float) destinationBounds.width() / srcRectHint.width();
+            final float scale = (float) destinationBounds.width() / srcRectHint.width();
             startTransaction.setWindowCrop(pipLeash, srcRectHint);
             startTransaction.setPosition(pipLeash,
                     destinationBounds.left - srcRectHint.left * scale,
@@ -244,13 +256,62 @@
             // in multi-activity case, reparenting yields new reset scales coming from pinned task.
             startTransaction.setScale(pipLeash, scale, scale);
         } else {
-            // TODO(b/325481148): handle the case with invalid srcRectHint (using overlay).
+            final float scaleX = (float) destinationBounds.width() / startBounds.width();
+            final float scaleY = (float) destinationBounds.height() / startBounds.height();
+            final int overlaySize = PipContentOverlay.PipAppIconOverlay
+                    .getOverlaySize(mPipScheduler.mSwipePipToHomeAppBounds, destinationBounds);
+            SurfaceControl overlayLeash = mPipScheduler.mSwipePipToHomeOverlay;
+
+            startTransaction.setPosition(pipLeash, destinationBounds.left, destinationBounds.top)
+                    .setScale(pipLeash, scaleX, scaleY)
+                    .setWindowCrop(pipLeash, startBounds)
+                    .reparent(overlayLeash, pipLeash)
+                    .setLayer(overlayLeash, Integer.MAX_VALUE);
+
+            if (mPipTaskToken != null) {
+                SurfaceControl.Transaction tx = new SurfaceControl.Transaction();
+                tx.addTransactionCommittedListener(mPipScheduler.getMainExecutor(),
+                                this::onClientDrawAtTransitionEnd)
+                        .setScale(overlayLeash, 1f, 1f)
+                        .setPosition(overlayLeash,
+                                (destinationBounds.width() - overlaySize) / 2f,
+                                (destinationBounds.height() - overlaySize) / 2f);
+                finishWct.setBoundsChangeTransaction(mPipTaskToken, tx);
+            }
         }
         startTransaction.apply();
-        finishCallback.onTransitionFinished(null);
+
+        // Note that finishWct should be free of any actual WM state changes; we are using
+        // it for syncing with the client draw after delayed configuration changes are dispatched.
+        finishCallback.onTransitionFinished(finishWct.isEmpty() ? null : finishWct);
         return true;
     }
 
+    private void onClientDrawAtTransitionEnd() {
+        startOverlayFadeoutAnimation();
+    }
+
+    private void startOverlayFadeoutAnimation() {
+        ValueAnimator animator = ValueAnimator.ofFloat(1f, 0f);
+        animator.setDuration(CONTENT_OVERLAY_FADE_OUT_DELAY_MS);
+        animator.addListener(new AnimatorListenerAdapter() {
+            @Override
+            public void onAnimationEnd(Animator animation) {
+                super.onAnimationEnd(animation);
+                SurfaceControl.Transaction tx = new SurfaceControl.Transaction();
+                tx.remove(mPipScheduler.mSwipePipToHomeOverlay);
+                tx.apply();
+                mPipScheduler.mSwipePipToHomeOverlay = null;
+            }
+        });
+        animator.addUpdateListener(animation -> {
+            float alpha = (float) animation.getAnimatedValue();
+            SurfaceControl.Transaction tx = new SurfaceControl.Transaction();
+            tx.setAlpha(mPipScheduler.mSwipePipToHomeOverlay, alpha).apply();
+        });
+        animator.start();
+    }
+
     private boolean startBoundsTypeEnterAnimation(@NonNull TransitionInfo info,
             @NonNull SurfaceControl.Transaction startTransaction,
             @NonNull SurfaceControl.Transaction finishTransaction,
diff --git a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/back/BackAnimationControllerTest.java b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/back/BackAnimationControllerTest.java
index 2919782..d839eae 100644
--- a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/back/BackAnimationControllerTest.java
+++ b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/back/BackAnimationControllerTest.java
@@ -557,6 +557,23 @@
     }
 
     @Test
+    public void skipsCancelWithoutStart() throws RemoteException {
+        final int type = BackNavigationInfo.TYPE_CALLBACK;
+        final ResultListener result = new ResultListener();
+        createNavigationInfo(new BackNavigationInfo.Builder()
+                .setType(type)
+                .setOnBackInvokedCallback(mAppCallback)
+                .setOnBackNavigationDone(new RemoteCallback(result)));
+        doMotionEvent(MotionEvent.ACTION_CANCEL, 0);
+        mShellExecutor.flushAll();
+
+        verify(mAppCallback, never()).onBackStarted(any());
+        verify(mAppCallback, never()).onBackProgressed(any());
+        verify(mAppCallback, never()).onBackInvoked();
+        verify(mAppCallback, never()).onBackCancelled();
+    }
+
+    @Test
     public void testBackToActivity() throws RemoteException {
         final CrossActivityBackAnimation animation = new CrossActivityBackAnimation(
                 mContext, mAnimationBackground, mRootTaskDisplayAreaOrganizer);
diff --git a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/desktopmode/DesktopModeTaskRepositoryTest.kt b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/desktopmode/DesktopModeTaskRepositoryTest.kt
index 445f74a..9f3a4d9 100644
--- a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/desktopmode/DesktopModeTaskRepositoryTest.kt
+++ b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/desktopmode/DesktopModeTaskRepositoryTest.kt
@@ -18,6 +18,7 @@
 
 import android.testing.AndroidTestingRunner
 import android.view.Display.DEFAULT_DISPLAY
+import android.view.Display.INVALID_DISPLAY
 import androidx.test.filters.SmallTest
 import com.android.wm.shell.ShellTestCase
 import com.android.wm.shell.TestShellExecutor
@@ -237,6 +238,27 @@
         assertThat(listener.visibleChangesOnDefaultDisplay).isEqualTo(4)
     }
 
+    /**
+     * When a task vanishes, the displayId of the task is set to INVALID_DISPLAY.
+     * This tests that task is removed from the last parent display when it vanishes.
+     */
+    @Test
+    fun updateVisibleFreeformTasks_removeVisibleTasksRemovesTaskWithInvalidDisplay() {
+        val listener = TestVisibilityListener()
+        val executor = TestShellExecutor()
+        repo.addVisibleTasksListener(listener, executor)
+        repo.updateVisibleFreeformTasks(DEFAULT_DISPLAY, taskId = 1, visible = true)
+        repo.updateVisibleFreeformTasks(DEFAULT_DISPLAY, taskId = 2, visible = true)
+        executor.flushAll()
+
+        assertThat(listener.visibleTasksCountOnDefaultDisplay).isEqualTo(2)
+        repo.updateVisibleFreeformTasks(INVALID_DISPLAY, taskId = 1, visible = false)
+        executor.flushAll()
+
+        assertThat(listener.visibleChangesOnDefaultDisplay).isEqualTo(3)
+        assertThat(listener.visibleTasksCountOnDefaultDisplay).isEqualTo(1)
+    }
+
     @Test
     fun getVisibleTaskCount() {
         // No tasks, count is 0
diff --git a/libs/hwui/WebViewFunctorManager.cpp b/libs/hwui/WebViewFunctorManager.cpp
index 5b4ab5f..efa9b11 100644
--- a/libs/hwui/WebViewFunctorManager.cpp
+++ b/libs/hwui/WebViewFunctorManager.cpp
@@ -16,15 +16,16 @@
 
 #include "WebViewFunctorManager.h"
 
+#include <log/log.h>
 #include <private/hwui/WebViewFunctor.h>
+#include <utils/Trace.h>
+
+#include <atomic>
+
 #include "Properties.h"
 #include "renderthread/CanvasContext.h"
 #include "renderthread/RenderThread.h"
 
-#include <log/log.h>
-#include <utils/Trace.h>
-#include <atomic>
-
 namespace android::uirenderer {
 
 namespace {
@@ -265,7 +266,7 @@
 }
 
 void WebViewFunctor::reportRenderingThreads(const int32_t* thread_ids, size_t size) {
-    // TODO(b/329219352): Pass the threads to HWUI and update the ADPF session.
+    mRenderingThreads = std::vector<int32_t>(thread_ids, thread_ids + size);
 }
 
 WebViewFunctorManager& WebViewFunctorManager::instance() {
@@ -365,6 +366,21 @@
     }
 }
 
+std::vector<int32_t> WebViewFunctorManager::getRenderingThreadsForActiveFunctors() {
+    std::vector<int32_t> renderingThreads;
+    std::lock_guard _lock{mLock};
+    for (const auto& iter : mActiveFunctors) {
+        const auto& functorThreads = iter->getRenderingThreads();
+        for (const auto& tid : functorThreads) {
+            if (std::find(renderingThreads.begin(), renderingThreads.end(), tid) ==
+                renderingThreads.end()) {
+                renderingThreads.push_back(tid);
+            }
+        }
+    }
+    return renderingThreads;
+}
+
 sp<WebViewFunctor::Handle> WebViewFunctorManager::handleFor(int functor) {
     std::lock_guard _lock{mLock};
     for (auto& iter : mActiveFunctors) {
diff --git a/libs/hwui/WebViewFunctorManager.h b/libs/hwui/WebViewFunctorManager.h
index 1bf2c1f..2d77dd8 100644
--- a/libs/hwui/WebViewFunctorManager.h
+++ b/libs/hwui/WebViewFunctorManager.h
@@ -60,6 +60,10 @@
 
         void onRemovedFromTree() { mReference.onRemovedFromTree(); }
 
+        const std::vector<int32_t>& getRenderingThreads() const {
+            return mReference.getRenderingThreads();
+        }
+
     private:
         friend class WebViewFunctor;
 
@@ -82,6 +86,7 @@
     void mergeTransaction(ASurfaceTransaction* transaction);
 
     void reportRenderingThreads(const int32_t* thread_ids, size_t size);
+    const std::vector<int32_t>& getRenderingThreads() const { return mRenderingThreads; }
 
     sp<Handle> createHandle() {
         LOG_ALWAYS_FATAL_IF(mCreatedHandle);
@@ -102,6 +107,7 @@
     bool mCreatedHandle = false;
     int32_t mParentSurfaceControlGenerationId = 0;
     ASurfaceControl* mSurfaceControl = nullptr;
+    std::vector<int32_t> mRenderingThreads;
 };
 
 class WebViewFunctorManager {
@@ -113,6 +119,7 @@
     void onContextDestroyed();
     void destroyFunctor(int functor);
     void reportRenderingThreads(int functor, const int32_t* thread_ids, size_t size);
+    std::vector<int32_t> getRenderingThreadsForActiveFunctors();
 
     sp<WebViewFunctor::Handle> handleFor(int functor);
 
diff --git a/libs/hwui/renderthread/CanvasContext.cpp b/libs/hwui/renderthread/CanvasContext.cpp
index abf64d0..1fbd580 100644
--- a/libs/hwui/renderthread/CanvasContext.cpp
+++ b/libs/hwui/renderthread/CanvasContext.cpp
@@ -777,6 +777,8 @@
                                  (std::min(syncDelayDuration, mLastDequeueBufferDuration)) -
                                  dequeueBufferDuration - idleDuration;
         mHintSessionWrapper->reportActualWorkDuration(actualDuration);
+        mHintSessionWrapper->setActiveFunctorThreads(
+                WebViewFunctorManager::instance().getRenderingThreadsForActiveFunctors());
     }
 
     mLastDequeueBufferDuration = dequeueBufferDuration;
diff --git a/libs/hwui/renderthread/HintSessionWrapper.cpp b/libs/hwui/renderthread/HintSessionWrapper.cpp
index 2362331..6993d52 100644
--- a/libs/hwui/renderthread/HintSessionWrapper.cpp
+++ b/libs/hwui/renderthread/HintSessionWrapper.cpp
@@ -20,6 +20,7 @@
 #include <private/performance_hint_private.h>
 #include <utils/Log.h>
 
+#include <algorithm>
 #include <chrono>
 #include <vector>
 
@@ -49,6 +50,7 @@
     BIND_APH_METHOD(updateTargetWorkDuration);
     BIND_APH_METHOD(reportActualWorkDuration);
     BIND_APH_METHOD(sendHint);
+    BIND_APH_METHOD(setThreads);
 
     mInitialized = true;
 }
@@ -67,6 +69,10 @@
         mHintSession = mHintSessionFuture->get();
         mHintSessionFuture = std::nullopt;
     }
+    if (mSetThreadsFuture.has_value()) {
+        mSetThreadsFuture->wait();
+        mSetThreadsFuture = std::nullopt;
+    }
     if (mHintSession) {
         mBinding->closeSession(mHintSession);
         mSessionValid = true;
@@ -106,16 +112,16 @@
     APerformanceHintManager* manager = mBinding->getManager();
     if (!manager) return false;
 
-    std::vector<pid_t> tids = CommonPool::getThreadIds();
-    tids.push_back(mUiThreadId);
-    tids.push_back(mRenderThreadId);
+    mPermanentSessionTids = CommonPool::getThreadIds();
+    mPermanentSessionTids.push_back(mUiThreadId);
+    mPermanentSessionTids.push_back(mRenderThreadId);
 
     // Use the cached target value if there is one, otherwise use a default. This is to ensure
     // the cached target and target in PowerHAL are consistent, and that it updates correctly
     // whenever there is a change.
     int64_t targetDurationNanos =
             mLastTargetWorkDuration == 0 ? kDefaultTargetDuration : mLastTargetWorkDuration;
-    mHintSessionFuture = CommonPool::async([=, this, tids = std::move(tids)] {
+    mHintSessionFuture = CommonPool::async([=, this, tids = mPermanentSessionTids] {
         return mBinding->createSession(manager, tids.data(), tids.size(), targetDurationNanos);
     });
     return false;
@@ -143,6 +149,23 @@
     mLastFrameNotification = systemTime();
 }
 
+void HintSessionWrapper::setActiveFunctorThreads(std::vector<pid_t> threadIds) {
+    if (!init()) return;
+    if (!mBinding || !mHintSession) return;
+    // Sort the vector to make sure they're compared as sets.
+    std::sort(threadIds.begin(), threadIds.end());
+    if (threadIds == mActiveFunctorTids) return;
+    mActiveFunctorTids = std::move(threadIds);
+    std::vector<pid_t> combinedTids = mPermanentSessionTids;
+    std::copy(mActiveFunctorTids.begin(), mActiveFunctorTids.end(),
+              std::back_inserter(combinedTids));
+    mSetThreadsFuture = CommonPool::async([this, tids = std::move(combinedTids)] {
+        int ret = mBinding->setThreads(mHintSession, tids.data(), tids.size());
+        ALOGE_IF(ret != 0, "APerformaceHint_setThreads failed: %d", ret);
+        return ret;
+    });
+}
+
 void HintSessionWrapper::sendLoadResetHint() {
     static constexpr int kMaxResetsSinceLastReport = 2;
     if (!init()) return;
diff --git a/libs/hwui/renderthread/HintSessionWrapper.h b/libs/hwui/renderthread/HintSessionWrapper.h
index 41891cd..14e7a53 100644
--- a/libs/hwui/renderthread/HintSessionWrapper.h
+++ b/libs/hwui/renderthread/HintSessionWrapper.h
@@ -20,6 +20,7 @@
 
 #include <future>
 #include <optional>
+#include <vector>
 
 #include "utils/TimeUtils.h"
 
@@ -47,11 +48,15 @@
     nsecs_t getLastUpdate();
     void delayedDestroy(renderthread::RenderThread& rt, nsecs_t delay,
                         std::shared_ptr<HintSessionWrapper> wrapperPtr);
+    // Must be called on Render thread. Otherwise can cause a race condition.
+    void setActiveFunctorThreads(std::vector<pid_t> threadIds);
 
 private:
     APerformanceHintSession* mHintSession = nullptr;
     // This needs to work concurrently for testing
     std::optional<std::shared_future<APerformanceHintSession*>> mHintSessionFuture;
+    // This needs to work concurrently for testing
+    std::optional<std::shared_future<int>> mSetThreadsFuture;
 
     int mResetsSinceLastReport = 0;
     nsecs_t mLastFrameNotification = 0;
@@ -59,6 +64,8 @@
 
     pid_t mUiThreadId;
     pid_t mRenderThreadId;
+    std::vector<pid_t> mPermanentSessionTids;
+    std::vector<pid_t> mActiveFunctorTids;
 
     bool mSessionValid = true;
 
@@ -82,6 +89,8 @@
         void (*reportActualWorkDuration)(APerformanceHintSession* session,
                                          int64_t actualDuration) = nullptr;
         void (*sendHint)(APerformanceHintSession* session, int32_t hintId) = nullptr;
+        int (*setThreads)(APerformanceHintSession* session, const pid_t* tids,
+                          size_t size) = nullptr;
 
     private:
         bool mInitialized = false;
diff --git a/libs/hwui/tests/unit/HintSessionWrapperTests.cpp b/libs/hwui/tests/unit/HintSessionWrapperTests.cpp
index 10a740a1..c16602c 100644
--- a/libs/hwui/tests/unit/HintSessionWrapperTests.cpp
+++ b/libs/hwui/tests/unit/HintSessionWrapperTests.cpp
@@ -58,6 +58,7 @@
         MOCK_METHOD(void, fakeUpdateTargetWorkDuration, (APerformanceHintSession*, int64_t));
         MOCK_METHOD(void, fakeReportActualWorkDuration, (APerformanceHintSession*, int64_t));
         MOCK_METHOD(void, fakeSendHint, (APerformanceHintSession*, int32_t));
+        MOCK_METHOD(int, fakeSetThreads, (APerformanceHintSession*, const std::vector<pid_t>&));
         // Needs to be on the binding so it can be accessed from static methods
         std::promise<int> allowCreationToFinish;
     };
@@ -102,11 +103,20 @@
     static void stubSendHint(APerformanceHintSession* session, int32_t hintId) {
         sMockBinding->fakeSendHint(session, hintId);
     };
+    static int stubSetThreads(APerformanceHintSession* session, const pid_t* ids, size_t size) {
+        std::vector<pid_t> tids(ids, ids + size);
+        return sMockBinding->fakeSetThreads(session, tids);
+    }
     void waitForWrapperReady() {
         if (mWrapper->mHintSessionFuture.has_value()) {
             mWrapper->mHintSessionFuture->wait();
         }
     }
+    void waitForSetThreadsReady() {
+        if (mWrapper->mSetThreadsFuture.has_value()) {
+            mWrapper->mSetThreadsFuture->wait();
+        }
+    }
     void scheduleDelayedDestroyManaged() {
         TestUtils::runOnRenderThread([&](renderthread::RenderThread& rt) {
             // Guaranteed to be scheduled first, allows destruction to start
@@ -130,6 +140,7 @@
     mWrapper->mBinding = sMockBinding;
     EXPECT_CALL(*sMockBinding, fakeGetManager).WillOnce(Return(managerPtr));
     ON_CALL(*sMockBinding, fakeCreateSession).WillByDefault(Return(sessionPtr));
+    ON_CALL(*sMockBinding, fakeSetThreads).WillByDefault(Return(0));
 }
 
 void HintSessionWrapperTests::MockHintSessionBinding::init() {
@@ -141,6 +152,7 @@
     sMockBinding->updateTargetWorkDuration = &stubUpdateTargetWorkDuration;
     sMockBinding->reportActualWorkDuration = &stubReportActualWorkDuration;
     sMockBinding->sendHint = &stubSendHint;
+    sMockBinding->setThreads = &stubSetThreads;
 }
 
 void HintSessionWrapperTests::TearDown() {
@@ -339,4 +351,44 @@
     EXPECT_EQ(mWrapper->alive(), false);
 }
 
+TEST_F(HintSessionWrapperTests, setThreadsUpdatesSessionThreads) {
+    EXPECT_CALL(*sMockBinding, fakeCreateSession(managerPtr, _, Gt(1), _)).Times(1);
+    EXPECT_CALL(*sMockBinding, fakeSetThreads(sessionPtr, testing::IsSupersetOf({11, 22})))
+            .Times(1);
+    mWrapper->init();
+    waitForWrapperReady();
+
+    // This changes the overall set of threads in the session, so the session wrapper should call
+    // setThreads.
+    mWrapper->setActiveFunctorThreads({11, 22});
+    waitForSetThreadsReady();
+
+    // The set of threads doesn't change, so the session wrapper should not call setThreads this
+    // time. The order of the threads shouldn't matter.
+    mWrapper->setActiveFunctorThreads({22, 11});
+    waitForSetThreadsReady();
+}
+
+TEST_F(HintSessionWrapperTests, setThreadsDoesntCrashAfterDestroy) {
+    EXPECT_CALL(*sMockBinding, fakeCloseSession(sessionPtr)).Times(1);
+
+    mWrapper->init();
+    waitForWrapperReady();
+    // Init a second time just to grab the wrapper from the promise
+    mWrapper->init();
+    EXPECT_EQ(mWrapper->alive(), true);
+
+    // Then, kill the session
+    mWrapper->destroy();
+
+    // Verify it died
+    Mock::VerifyAndClearExpectations(sMockBinding.get());
+    EXPECT_EQ(mWrapper->alive(), false);
+
+    // setActiveFunctorThreads shouldn't do anything, and shouldn't crash.
+    EXPECT_CALL(*sMockBinding, fakeSetThreads(_, _)).Times(0);
+    mWrapper->setActiveFunctorThreads({11, 22});
+    waitForSetThreadsReady();
+}
+
 }  // namespace android::uirenderer::renderthread
\ No newline at end of file
diff --git a/media/java/android/media/MediaCodec.java b/media/java/android/media/MediaCodec.java
index 1905fa8..82d43bc 100644
--- a/media/java/android/media/MediaCodec.java
+++ b/media/java/android/media/MediaCodec.java
@@ -65,6 +65,7 @@
 import java.util.concurrent.LinkedBlockingQueue;
 import java.util.concurrent.locks.Lock;
 import java.util.concurrent.locks.ReentrantLock;
+import java.util.function.Supplier;
 
 /**
  MediaCodec class can be used to access low-level media codecs, i.e. encoder/decoder components.
@@ -2014,6 +2015,23 @@
         }
     }
 
+    // HACKY(b/325389296): aconfig flag accessors may not work in all contexts where MediaCodec API
+    // is used, so allow accessors to fail. In those contexts use a default value, normally false.
+
+    /* package private */
+    static boolean GetFlag(Supplier<Boolean> flagValueSupplier) {
+        return GetFlag(flagValueSupplier, false /* defaultValue */);
+    }
+
+    /* package private */
+    static boolean GetFlag(Supplier<Boolean> flagValueSupplier, boolean defaultValue) {
+        try {
+            return flagValueSupplier.get();
+        } catch (java.lang.RuntimeException e) {
+            return defaultValue;
+        }
+    }
+
     private boolean mHasSurface = false;
 
     /**
@@ -2346,7 +2364,7 @@
         }
 
         // at the moment no codecs support detachable surface
-        if (android.media.codec.Flags.nullOutputSurface()) {
+        if (GetFlag(() -> android.media.codec.Flags.nullOutputSurface())) {
             // Detached surface flag is only meaningful if surface is null. Otherwise, it is
             // ignored.
             if (surface == null && (flags & CONFIGURE_FLAG_DETACHED_SURFACE) != 0) {
diff --git a/media/java/android/media/MediaCodecInfo.java b/media/java/android/media/MediaCodecInfo.java
index abad460..8ff4305 100644
--- a/media/java/android/media/MediaCodecInfo.java
+++ b/media/java/android/media/MediaCodecInfo.java
@@ -23,6 +23,7 @@
 import static android.media.codec.Flags.FLAG_IN_PROCESS_SW_AUDIO_CODEC;
 import static android.media.codec.Flags.FLAG_NULL_OUTPUT_SURFACE;
 import static android.media.codec.Flags.FLAG_REGION_OF_INTEREST;
+import static android.media.MediaCodec.GetFlag;
 
 import android.annotation.FlaggedApi;
 import android.annotation.IntDef;
@@ -827,10 +828,10 @@
                 features.add(new Feature(FEATURE_MultipleFrames,   (1 << 5), false));
                 features.add(new Feature(FEATURE_DynamicTimestamp, (1 << 6), false));
                 features.add(new Feature(FEATURE_LowLatency,       (1 << 7), true));
-                if (android.media.codec.Flags.dynamicColorAspects()) {
+                if (GetFlag(() -> android.media.codec.Flags.dynamicColorAspects())) {
                     features.add(new Feature(FEATURE_DynamicColorAspects, (1 << 8), true));
                 }
-                if (android.media.codec.Flags.nullOutputSurface()) {
+                if (GetFlag(() -> android.media.codec.Flags.nullOutputSurface())) {
                     features.add(new Feature(FEATURE_DetachedSurface,     (1 << 9), true));
                 }
 
@@ -851,10 +852,10 @@
                 features.add(new Feature(FEATURE_QpBounds, (1 << 3), false));
                 features.add(new Feature(FEATURE_EncodingStatistics, (1 << 4), false));
                 features.add(new Feature(FEATURE_HdrEditing, (1 << 5), false));
-                if (android.media.codec.Flags.hlgEditing()) {
+                if (GetFlag(() -> android.media.codec.Flags.hlgEditing())) {
                     features.add(new Feature(FEATURE_HlgEditing, (1 << 6), true));
                 }
-                if (android.media.codec.Flags.regionOfInterest()) {
+                if (GetFlag(() -> android.media.codec.Flags.regionOfInterest())) {
                     features.add(new Feature(FEATURE_Roi, (1 << 7), true));
                 }
 
diff --git a/packages/SettingsLib/res/values/strings.xml b/packages/SettingsLib/res/values/strings.xml
index 69e4bd7..46c6494 100644
--- a/packages/SettingsLib/res/values/strings.xml
+++ b/packages/SettingsLib/res/values/strings.xml
@@ -222,6 +222,30 @@
     <!-- Connected device settings. Message when the left-side and right-side hearing aids device are active. [CHAR LIMIT=NONE] -->
     <string name="bluetooth_hearing_aid_left_and_right_active">Active, left and right</string>
 
+    <!-- Connected devices settings. Message when Bluetooth is connected and active for media only, showing remote device status and battery level. [CHAR LIMIT=NONE] -->
+    <string name="bluetooth_active_media_only_battery_level">Active (media only), <xliff:g id="battery_level_as_percentage">%1$s</xliff:g> battery</string>
+    <!-- Connected devices settings. Message when Bluetooth is connected and active for media only, showing remote device status and battery level for untethered headset. [CHAR LIMIT=NONE] -->
+    <string name="bluetooth_active_media_only_battery_level_untethered">Active (media only), L: <xliff:g id="battery_level_as_percentage" example="25%">%1$s</xliff:g> battery, R: <xliff:g id="battery_level_as_percentage" example="25%">%2$s</xliff:g> battery</string>
+    <!-- Connected devices settings. Message when Bluetooth is connected but not in use, showing remote device battery level, supports audio sharing. [CHAR LIMIT=NONE] -->
+    <string name="bluetooth_battery_level_lea_support">Connected (supports audio sharing), <xliff:g id="battery_level_as_percentage">%1$s</xliff:g> battery</string>
+    <!-- Connected devices settings. Message when Bluetooth is connected but not in use, showing remote device battery level for untethered headset, supports audio sharing. [CHAR LIMIT=NONE] -->
+    <string name="bluetooth_battery_level_untethered_lea_support">Connected (supports audio sharing), L: <xliff:g id="battery_level_as_percentage" example="25%">%1$s</xliff:g> battery, R: <xliff:g id="battery_level_as_percentage" example="25%">%2$s</xliff:g> battery</string>
+    <!-- Connected devices settings. Message when Bluetooth is connected but not in use, showing remote device battery level for the left part of the untethered headset, supports audio sharing. [CHAR LIMIT=NONE] -->
+    <string name="bluetooth_battery_level_untethered_left_lea_support">Connected (supports audio sharing), left <xliff:g id="battery_level_as_percentage" example="25%">%1$s</xliff:g></string>
+    <!-- Connected devices settings. Message when Bluetooth is connected but not in use, showing remote device battery level for the right part of the untethered headset, supports audio sharing. [CHAR LIMIT=NONE] -->
+    <string name="bluetooth_battery_level_untethered_right_lea_support">Connected (supports audio sharing), right <xliff:g id="battery_level_as_percentage" example="25%">%1$s</xliff:g></string>
+    <!-- Connected devices settings. Message when Bluetooth is connected and active for media only but no battery information, showing remote device status. [CHAR LIMIT=NONE] -->
+    <string name="bluetooth_active_media_only_no_battery_level">Active (media only)</string>
+    <!-- Connected devices settings. Message shown when bluetooth device is disconnected but is a known, previously connected device, supports audio sharing [CHAR LIMIT=NONE] -->
+    <string name="bluetooth_saved_device_lea_support">Supports audio sharing</string>
+
+    <!-- Connected device settings. Message when the left-side hearing aid device is active for media only. [CHAR LIMIT=NONE] -->
+    <string name="bluetooth_hearing_aid_media_only_left_active">Active (media only), left only</string>
+    <!-- Connected device settings. Message when the right-side hearing aid device is active for media only. [CHAR LIMIT=NONE] -->
+    <string name="bluetooth_hearing_aid_media_only_right_active">Active (media only), right only</string>
+    <!-- Connected device settings. Message when the left-side and right-side hearing aids device are active for media only. [CHAR LIMIT=NONE] -->
+    <string name="bluetooth_hearing_aid_media_only_left_and_right_active">Active (media only), left and right</string>
+
     <!-- Bluetooth settings.  The user-visible string that is used whenever referring to the A2DP profile. -->
     <string name="bluetooth_profile_a2dp">Media audio</string>
     <!-- Bluetooth settings.  The user-visible string that is used whenever referring to the headset or handsfree profile. -->
diff --git a/packages/SettingsLib/src/com/android/settingslib/volume/data/repository/AudioRepository.kt b/packages/SettingsLib/src/com/android/settingslib/volume/data/repository/AudioRepository.kt
index e7fec69..65a5317 100644
--- a/packages/SettingsLib/src/com/android/settingslib/volume/data/repository/AudioRepository.kt
+++ b/packages/SettingsLib/src/com/android/settingslib/volume/data/repository/AudioRepository.kt
@@ -73,6 +73,10 @@
     suspend fun setVolume(audioStream: AudioStream, volume: Int)
 
     suspend fun setMuted(audioStream: AudioStream, isMuted: Boolean)
+
+    suspend fun setRingerMode(audioStream: AudioStream, mode: RingerMode)
+
+    suspend fun isAffectedByMute(audioStream: AudioStream): Boolean
 }
 
 class AudioRepositoryImpl(
@@ -169,6 +173,16 @@
             )
         }
 
+    override suspend fun setRingerMode(audioStream: AudioStream, mode: RingerMode) {
+        withContext(backgroundCoroutineContext) { audioManager.ringerMode = mode.value }
+    }
+
+    override suspend fun isAffectedByMute(audioStream: AudioStream): Boolean {
+        return withContext(backgroundCoroutineContext) {
+            audioManager.isStreamAffectedByMute(audioStream.value)
+        }
+    }
+
     private fun getMinVolume(stream: AudioStream): Int =
         try {
             audioManager.getStreamMinVolume(stream.value)
diff --git a/packages/SettingsLib/src/com/android/settingslib/volume/domain/interactor/AudioVolumeInteractor.kt b/packages/SettingsLib/src/com/android/settingslib/volume/domain/interactor/AudioVolumeInteractor.kt
index 778653b..33f917e 100644
--- a/packages/SettingsLib/src/com/android/settingslib/volume/domain/interactor/AudioVolumeInteractor.kt
+++ b/packages/SettingsLib/src/com/android/settingslib/volume/domain/interactor/AudioVolumeInteractor.kt
@@ -49,8 +49,14 @@
     suspend fun setVolume(audioStream: AudioStream, volume: Int) =
         audioRepository.setVolume(audioStream, volume)
 
-    suspend fun setMuted(audioStream: AudioStream, isMuted: Boolean) =
+    suspend fun setMuted(audioStream: AudioStream, isMuted: Boolean) {
+        if (audioStream.value == AudioManager.STREAM_RING) {
+            val mode =
+                if (isMuted) AudioManager.RINGER_MODE_VIBRATE else AudioManager.RINGER_MODE_NORMAL
+            audioRepository.setRingerMode(audioStream, RingerMode(mode))
+        }
         audioRepository.setMuted(audioStream, isMuted)
+    }
 
     /** Checks if the volume can be changed via the UI. */
     fun canChangeVolume(audioStream: AudioStream): Flow<Boolean> {
@@ -66,9 +72,8 @@
         }
     }
 
-    fun isMutable(audioStream: AudioStream): Boolean =
-        // Alarm stream doesn't support muting
-        audioStream.value != AudioManager.STREAM_ALARM
+    suspend fun isAffectedByMute(audioStream: AudioStream): Boolean =
+        audioRepository.isAffectedByMute(audioStream)
 
     private suspend fun processVolume(
         audioStreamModel: AudioStreamModel,
diff --git a/packages/SystemUI/animation/src/com/android/systemui/animation/DialogTransitionAnimator.kt b/packages/SystemUI/animation/src/com/android/systemui/animation/DialogTransitionAnimator.kt
index dbdf970..24cc8a4 100644
--- a/packages/SystemUI/animation/src/com/android/systemui/animation/DialogTransitionAnimator.kt
+++ b/packages/SystemUI/animation/src/com/android/systemui/animation/DialogTransitionAnimator.kt
@@ -711,7 +711,6 @@
         dialog.setDismissOverride(this::onDialogDismissed)
 
         if (featureFlags.isPredictiveBackQsDialogAnim) {
-            // TODO(b/265923095) Improve animations for QS dialogs on configuration change
             dialog.registerAnimationOnBackInvoked(targetView = dialogContentWithBackground)
         }
 
diff --git a/packages/SystemUI/animation/src/com/android/systemui/animation/back/BackAnimationSpec.kt b/packages/SystemUI/animation/src/com/android/systemui/animation/back/BackAnimationSpec.kt
index dd32851..4d327e1 100644
--- a/packages/SystemUI/animation/src/com/android/systemui/animation/back/BackAnimationSpec.kt
+++ b/packages/SystemUI/animation/src/com/android/systemui/animation/back/BackAnimationSpec.kt
@@ -37,7 +37,7 @@
 
 /** Create a [BackAnimationSpec] from [displayMetrics] and design specs. */
 fun BackAnimationSpec.Companion.createFloatingSurfaceAnimationSpec(
-    displayMetrics: DisplayMetrics,
+    displayMetricsProvider: () -> DisplayMetrics,
     maxMarginXdp: Float,
     maxMarginYdp: Float,
     minScale: Float,
@@ -45,18 +45,19 @@
     translateYEasing: Interpolator = Interpolators.LINEAR,
     scaleEasing: Interpolator = Interpolators.STANDARD_DECELERATE,
 ): BackAnimationSpec {
-    val screenWidthPx = displayMetrics.widthPixels
-    val screenHeightPx = displayMetrics.heightPixels
-
-    val maxMarginXPx = maxMarginXdp.dpToPx(displayMetrics)
-    val maxMarginYPx = maxMarginYdp.dpToPx(displayMetrics)
-    val maxTranslationXByScale = (screenWidthPx - screenWidthPx * minScale) / 2
-    val maxTranslationX = maxTranslationXByScale - maxMarginXPx
-    val maxTranslationYByScale = (screenHeightPx - screenHeightPx * minScale) / 2
-    val maxTranslationY = maxTranslationYByScale - maxMarginYPx
-    val minScaleReversed = 1f - minScale
-
     return BackAnimationSpec { backEvent, progressY, result ->
+        val displayMetrics = displayMetricsProvider()
+        val screenWidthPx = displayMetrics.widthPixels
+        val screenHeightPx = displayMetrics.heightPixels
+
+        val maxMarginXPx = maxMarginXdp.dpToPx(displayMetrics)
+        val maxMarginYPx = maxMarginYdp.dpToPx(displayMetrics)
+        val maxTranslationXByScale = (screenWidthPx - screenWidthPx * minScale) / 2
+        val maxTranslationX = maxTranslationXByScale - maxMarginXPx
+        val maxTranslationYByScale = (screenHeightPx - screenHeightPx * minScale) / 2
+        val maxTranslationY = maxTranslationYByScale - maxMarginYPx
+        val minScaleReversed = 1f - minScale
+
         val direction = if (backEvent.swipeEdge == BackEvent.EDGE_LEFT) 1 else -1
         val progressX = backEvent.progress
 
diff --git a/packages/SystemUI/animation/src/com/android/systemui/animation/back/BackAnimationSpecForSysUi.kt b/packages/SystemUI/animation/src/com/android/systemui/animation/back/BackAnimationSpecForSysUi.kt
index b8d4469..b057296 100644
--- a/packages/SystemUI/animation/src/com/android/systemui/animation/back/BackAnimationSpecForSysUi.kt
+++ b/packages/SystemUI/animation/src/com/android/systemui/animation/back/BackAnimationSpecForSysUi.kt
@@ -23,10 +23,10 @@
  * https://carbon.googleplex.com/predictive-back-for-apps/pages/st-1-dismiss-app
  */
 fun BackAnimationSpec.Companion.dismissAppForSysUi(
-    displayMetrics: DisplayMetrics,
+    displayMetricsProvider: () -> DisplayMetrics,
 ): BackAnimationSpec =
     BackAnimationSpec.createFloatingSurfaceAnimationSpec(
-        displayMetrics = displayMetrics,
+        displayMetricsProvider = displayMetricsProvider,
         maxMarginXdp = 8f,
         maxMarginYdp = 8f,
         minScale = 0.8f,
@@ -37,10 +37,10 @@
  * https://carbon.googleplex.com/predictive-back-for-apps/pages/st-2-cross-task
  */
 fun BackAnimationSpec.Companion.crossTaskForSysUi(
-    displayMetrics: DisplayMetrics,
+    displayMetricsProvider: () -> DisplayMetrics,
 ): BackAnimationSpec =
     BackAnimationSpec.createFloatingSurfaceAnimationSpec(
-        displayMetrics = displayMetrics,
+        displayMetricsProvider = displayMetricsProvider,
         maxMarginXdp = 8f,
         maxMarginYdp = 8f,
         minScale = 0.8f,
@@ -51,10 +51,10 @@
  * https://carbon.googleplex.com/predictive-back-for-apps/pages/st-3-inner-area-dismiss
  */
 fun BackAnimationSpec.Companion.innerAreaDismissForSysUi(
-    displayMetrics: DisplayMetrics,
+    displayMetricsProvider: () -> DisplayMetrics,
 ): BackAnimationSpec =
     BackAnimationSpec.createFloatingSurfaceAnimationSpec(
-        displayMetrics = displayMetrics,
+        displayMetricsProvider = displayMetricsProvider,
         maxMarginXdp = 0f,
         maxMarginYdp = 0f,
         minScale = 0.9f,
@@ -65,10 +65,10 @@
  * https://carbon.googleplex.com/predictive-back-for-apps/pages/st-4-floating-system-surfaces
  */
 fun BackAnimationSpec.Companion.floatingSystemSurfacesForSysUi(
-    displayMetrics: DisplayMetrics,
+    displayMetricsProvider: () -> DisplayMetrics,
 ): BackAnimationSpec =
     BackAnimationSpec.createFloatingSurfaceAnimationSpec(
-        displayMetrics = displayMetrics,
+        displayMetricsProvider = displayMetricsProvider,
         maxMarginXdp = 8f,
         maxMarginYdp = 8f,
         minScale = 0.9f,
diff --git a/packages/SystemUI/animation/src/com/android/systemui/util/Dialog.kt b/packages/SystemUI/animation/src/com/android/systemui/util/Dialog.kt
index 0f63548..9dd2328 100644
--- a/packages/SystemUI/animation/src/com/android/systemui/util/Dialog.kt
+++ b/packages/SystemUI/animation/src/com/android/systemui/util/Dialog.kt
@@ -39,7 +39,7 @@
     targetView: View,
     backAnimationSpec: BackAnimationSpec =
         BackAnimationSpec.floatingSystemSurfacesForSysUi(
-            displayMetrics = targetView.resources.displayMetrics,
+            displayMetricsProvider = { targetView.resources.displayMetrics },
         ),
 ) {
     targetView.registerOnBackInvokedCallbackOnViewAttached(
diff --git a/packages/SystemUI/compose/features/src/com/android/systemui/communal/ui/compose/CommunalContainer.kt b/packages/SystemUI/compose/features/src/com/android/systemui/communal/ui/compose/CommunalContainer.kt
index a1d8c29..bdd888f 100644
--- a/packages/SystemUI/compose/features/src/com/android/systemui/communal/ui/compose/CommunalContainer.kt
+++ b/packages/SystemUI/compose/features/src/com/android/systemui/communal/ui/compose/CommunalContainer.kt
@@ -8,12 +8,15 @@
 import androidx.compose.runtime.DisposableEffect
 import androidx.compose.runtime.collectAsState
 import androidx.compose.runtime.getValue
+import androidx.compose.runtime.remember
+import androidx.compose.runtime.rememberCoroutineScope
 import androidx.compose.ui.Modifier
 import androidx.compose.ui.res.dimensionResource
 import com.android.compose.animation.scene.Edge
 import com.android.compose.animation.scene.ElementKey
 import com.android.compose.animation.scene.FixedSizeEdgeDetector
 import com.android.compose.animation.scene.LowestZIndexScenePicker
+import com.android.compose.animation.scene.MutableSceneTransitionLayoutState
 import com.android.compose.animation.scene.SceneKey
 import com.android.compose.animation.scene.SceneScope
 import com.android.compose.animation.scene.SceneTransitionLayout
@@ -21,12 +24,13 @@
 import com.android.compose.animation.scene.SwipeDirection
 import com.android.compose.animation.scene.observableTransitionState
 import com.android.compose.animation.scene.transitions
-import com.android.compose.animation.scene.updateSceneTransitionLayoutState
 import com.android.compose.theme.LocalAndroidColorScheme
 import com.android.systemui.communal.shared.model.CommunalScenes
 import com.android.systemui.communal.ui.viewmodel.BaseCommunalViewModel
 import com.android.systemui.communal.ui.viewmodel.CommunalViewModel
 import com.android.systemui.res.R
+import com.android.systemui.scene.shared.model.SceneDataSourceDelegator
+import com.android.systemui.scene.ui.composable.SceneTransitionLayoutDataSource
 import com.android.systemui.statusbar.phone.SystemUIDialogFactory
 
 object Communal {
@@ -63,27 +67,35 @@
 fun CommunalContainer(
     modifier: Modifier = Modifier,
     viewModel: CommunalViewModel,
+    dataSourceDelegator: SceneDataSourceDelegator,
     dialogFactory: SystemUIDialogFactory,
 ) {
-    val currentScene: SceneKey by viewModel.currentScene.collectAsState(CommunalScenes.Blank)
-    val sceneTransitionLayoutState =
-        updateSceneTransitionLayoutState(
-            currentScene,
-            onChangeScene = { viewModel.onSceneChanged(it) },
+    val coroutineScope = rememberCoroutineScope()
+    val currentSceneKey: SceneKey by viewModel.currentScene.collectAsState(CommunalScenes.Blank)
+    val touchesAllowed by viewModel.touchesAllowed.collectAsState(initial = false)
+    val state: MutableSceneTransitionLayoutState = remember {
+        MutableSceneTransitionLayoutState(
+            initialScene = currentSceneKey,
             transitions = sceneTransitions,
             enableInterruptions = false,
         )
-    val touchesAllowed by viewModel.touchesAllowed.collectAsState(initial = false)
+    }
+
+    DisposableEffect(state) {
+        val dataSource = SceneTransitionLayoutDataSource(state, coroutineScope)
+        dataSourceDelegator.setDelegate(dataSource)
+        onDispose { dataSourceDelegator.setDelegate(null) }
+    }
 
     // This effect exposes the SceneTransitionLayout's observable transition state to the rest of
     // the system, and unsets it when the view is disposed to avoid a memory leak.
-    DisposableEffect(viewModel, sceneTransitionLayoutState) {
-        viewModel.setTransitionState(sceneTransitionLayoutState.observableTransitionState())
+    DisposableEffect(viewModel, state) {
+        viewModel.setTransitionState(state.observableTransitionState())
         onDispose { viewModel.setTransitionState(null) }
     }
 
     SceneTransitionLayout(
-        state = sceneTransitionLayoutState,
+        state = state,
         modifier = modifier.fillMaxSize(),
         swipeSourceDetector =
             FixedSizeEdgeDetector(
diff --git a/packages/SystemUI/compose/features/src/com/android/systemui/keyguard/ui/composable/LockscreenContent.kt b/packages/SystemUI/compose/features/src/com/android/systemui/keyguard/ui/composable/LockscreenContent.kt
index 1178cc8..4bef9ef 100644
--- a/packages/SystemUI/compose/features/src/com/android/systemui/keyguard/ui/composable/LockscreenContent.kt
+++ b/packages/SystemUI/compose/features/src/com/android/systemui/keyguard/ui/composable/LockscreenContent.kt
@@ -16,7 +16,6 @@
 
 package com.android.systemui.keyguard.ui.composable
 
-import androidx.compose.foundation.layout.fillMaxSize
 import androidx.compose.runtime.Composable
 import androidx.compose.runtime.DisposableEffect
 import androidx.compose.runtime.collectAsState
@@ -24,9 +23,7 @@
 import androidx.compose.runtime.rememberCoroutineScope
 import androidx.compose.ui.Modifier
 import androidx.compose.ui.platform.LocalView
-import com.android.compose.animation.scene.SceneKey
-import com.android.compose.animation.scene.SceneTransitionLayout
-import com.android.compose.animation.scene.transitions
+import com.android.compose.animation.scene.SceneScope
 import com.android.systemui.keyguard.domain.interactor.KeyguardClockInteractor
 import com.android.systemui.keyguard.ui.composable.blueprint.ComposableLockscreenSceneBlueprint
 import com.android.systemui.keyguard.ui.viewmodel.LockscreenContentViewModel
@@ -45,16 +42,12 @@
     private val blueprints: Set<@JvmSuppressWildcards ComposableLockscreenSceneBlueprint>,
     private val clockInteractor: KeyguardClockInteractor,
 ) {
-
-    private val sceneKeyByBlueprint: Map<ComposableLockscreenSceneBlueprint, SceneKey> by lazy {
-        blueprints.associateWith { blueprint -> SceneKey(blueprint.id) }
-    }
-    private val sceneKeyByBlueprintId: Map<String, SceneKey> by lazy {
-        sceneKeyByBlueprint.entries.associate { (blueprint, sceneKey) -> blueprint.id to sceneKey }
+    private val blueprintByBlueprintId: Map<String, ComposableLockscreenSceneBlueprint> by lazy {
+        blueprints.associateBy { it.id }
     }
 
     @Composable
-    fun Content(
+    fun SceneScope.Content(
         modifier: Modifier = Modifier,
     ) {
         val coroutineScope = rememberCoroutineScope()
@@ -66,19 +59,7 @@
             onDispose { clockInteractor.clockEventController.unregisterListeners() }
         }
 
-        // Switch smoothly between blueprints, any composable tagged with element() will be
-        // transition-animated between any two blueprints, if they both display the same element.
-        SceneTransitionLayout(
-            currentScene = checkNotNull(sceneKeyByBlueprintId[blueprintId]),
-            onChangeScene = {},
-            transitions =
-                transitions { sceneKeyByBlueprintId.values.forEach { sceneKey -> to(sceneKey) } },
-            modifier = modifier,
-            enableInterruptions = false,
-        ) {
-            sceneKeyByBlueprint.entries.forEach { (blueprint, sceneKey) ->
-                scene(sceneKey) { with(blueprint) { Content(Modifier.fillMaxSize()) } }
-            }
-        }
+        val blueprint = blueprintByBlueprintId[blueprintId] ?: return
+        with(blueprint) { Content(modifier) }
     }
 }
diff --git a/packages/SystemUI/compose/features/src/com/android/systemui/keyguard/ui/composable/LockscreenScene.kt b/packages/SystemUI/compose/features/src/com/android/systemui/keyguard/ui/composable/LockscreenScene.kt
index 7acb4d5..c241f9c 100644
--- a/packages/SystemUI/compose/features/src/com/android/systemui/keyguard/ui/composable/LockscreenScene.kt
+++ b/packages/SystemUI/compose/features/src/com/android/systemui/keyguard/ui/composable/LockscreenScene.kt
@@ -66,9 +66,5 @@
         key = QuickSettings.SharedValues.TilesSquishiness,
     )
 
-    lockscreenContent
-        .get()
-        .Content(
-            modifier = modifier.fillMaxSize(),
-        )
+    with(lockscreenContent.get()) { Content(modifier = modifier.fillMaxSize()) }
 }
diff --git a/packages/SystemUI/compose/features/src/com/android/systemui/keyguard/ui/composable/blueprint/DefaultBlueprint.kt b/packages/SystemUI/compose/features/src/com/android/systemui/keyguard/ui/composable/blueprint/DefaultBlueprint.kt
index 320c455..c008a1a 100644
--- a/packages/SystemUI/compose/features/src/com/android/systemui/keyguard/ui/composable/blueprint/DefaultBlueprint.kt
+++ b/packages/SystemUI/compose/features/src/com/android/systemui/keyguard/ui/composable/blueprint/DefaultBlueprint.kt
@@ -16,10 +16,15 @@
 
 package com.android.systemui.keyguard.ui.composable.blueprint
 
+import androidx.compose.foundation.layout.Box
 import androidx.compose.foundation.layout.Column
+import androidx.compose.foundation.layout.fillMaxHeight
 import androidx.compose.foundation.layout.fillMaxSize
 import androidx.compose.foundation.layout.fillMaxWidth
 import androidx.compose.runtime.Composable
+import androidx.compose.runtime.collectAsState
+import androidx.compose.runtime.getValue
+import androidx.compose.ui.Alignment
 import androidx.compose.ui.Modifier
 import androidx.compose.ui.layout.Layout
 import androidx.compose.ui.unit.IntRect
@@ -28,6 +33,7 @@
 import com.android.systemui.keyguard.ui.composable.section.AmbientIndicationSection
 import com.android.systemui.keyguard.ui.composable.section.BottomAreaSection
 import com.android.systemui.keyguard.ui.composable.section.LockSection
+import com.android.systemui.keyguard.ui.composable.section.NotificationSection
 import com.android.systemui.keyguard.ui.composable.section.SettingsMenuSection
 import com.android.systemui.keyguard.ui.composable.section.StatusBarSection
 import com.android.systemui.keyguard.ui.composable.section.TopAreaSection
@@ -52,6 +58,7 @@
     private val bottomAreaSection: BottomAreaSection,
     private val settingsMenuSection: SettingsMenuSection,
     private val topAreaSection: TopAreaSection,
+    private val notificationSection: NotificationSection,
 ) : ComposableLockscreenSceneBlueprint {
 
     override val id: String = "default"
@@ -59,6 +66,8 @@
     @Composable
     override fun SceneScope.Content(modifier: Modifier) {
         val isUdfpsVisible = viewModel.isUdfpsVisible
+        val shouldUseSplitNotificationShade by
+            viewModel.shouldUseSplitNotificationShade.collectAsState()
 
         LockscreenLongPress(
             viewModel = viewModel.longPress,
@@ -68,10 +77,27 @@
                 content = {
                     // Constrained to above the lock icon.
                     Column(
-                        modifier = Modifier.fillMaxWidth(),
+                        modifier = Modifier.fillMaxSize(),
                     ) {
                         with(statusBarSection) { StatusBar(modifier = Modifier.fillMaxWidth()) }
-                        with(topAreaSection) { DefaultClockLayoutWithNotifications() }
+
+                        Box {
+                            with(topAreaSection) { DefaultClockLayout() }
+                            if (shouldUseSplitNotificationShade) {
+                                with(notificationSection) {
+                                    Notifications(
+                                        Modifier.fillMaxWidth(0.5f)
+                                            .fillMaxHeight()
+                                            .align(alignment = Alignment.TopEnd)
+                                    )
+                                }
+                            }
+                        }
+                        if (!shouldUseSplitNotificationShade) {
+                            with(notificationSection) {
+                                Notifications(Modifier.weight(weight = 1f))
+                            }
+                        }
                         if (!isUdfpsVisible && ambientIndicationSectionOptional.isPresent) {
                             with(ambientIndicationSectionOptional.get()) {
                                 AmbientIndication(modifier = Modifier.fillMaxWidth())
diff --git a/packages/SystemUI/compose/features/src/com/android/systemui/keyguard/ui/composable/blueprint/ShortcutsBesideUdfpsBlueprint.kt b/packages/SystemUI/compose/features/src/com/android/systemui/keyguard/ui/composable/blueprint/ShortcutsBesideUdfpsBlueprint.kt
index 64c2cb3..091a439 100644
--- a/packages/SystemUI/compose/features/src/com/android/systemui/keyguard/ui/composable/blueprint/ShortcutsBesideUdfpsBlueprint.kt
+++ b/packages/SystemUI/compose/features/src/com/android/systemui/keyguard/ui/composable/blueprint/ShortcutsBesideUdfpsBlueprint.kt
@@ -16,10 +16,15 @@
 
 package com.android.systemui.keyguard.ui.composable.blueprint
 
+import androidx.compose.foundation.layout.Box
 import androidx.compose.foundation.layout.Column
+import androidx.compose.foundation.layout.fillMaxHeight
 import androidx.compose.foundation.layout.fillMaxSize
 import androidx.compose.foundation.layout.fillMaxWidth
 import androidx.compose.runtime.Composable
+import androidx.compose.runtime.collectAsState
+import androidx.compose.runtime.getValue
+import androidx.compose.ui.Alignment
 import androidx.compose.ui.Modifier
 import androidx.compose.ui.layout.Layout
 import androidx.compose.ui.unit.IntRect
@@ -28,6 +33,7 @@
 import com.android.systemui.keyguard.ui.composable.section.AmbientIndicationSection
 import com.android.systemui.keyguard.ui.composable.section.BottomAreaSection
 import com.android.systemui.keyguard.ui.composable.section.LockSection
+import com.android.systemui.keyguard.ui.composable.section.NotificationSection
 import com.android.systemui.keyguard.ui.composable.section.SettingsMenuSection
 import com.android.systemui.keyguard.ui.composable.section.StatusBarSection
 import com.android.systemui.keyguard.ui.composable.section.TopAreaSection
@@ -52,6 +58,7 @@
     private val bottomAreaSection: BottomAreaSection,
     private val settingsMenuSection: SettingsMenuSection,
     private val topAreaSection: TopAreaSection,
+    private val notificationSection: NotificationSection,
 ) : ComposableLockscreenSceneBlueprint {
 
     override val id: String = "shortcuts-besides-udfps"
@@ -59,6 +66,8 @@
     @Composable
     override fun SceneScope.Content(modifier: Modifier) {
         val isUdfpsVisible = viewModel.isUdfpsVisible
+        val shouldUseSplitNotificationShade by
+            viewModel.shouldUseSplitNotificationShade.collectAsState()
 
         LockscreenLongPress(
             viewModel = viewModel.longPress,
@@ -68,11 +77,27 @@
                 content = {
                     // Constrained to above the lock icon.
                     Column(
-                        modifier = Modifier.fillMaxWidth(),
+                        modifier = Modifier.fillMaxSize(),
                     ) {
                         with(statusBarSection) { StatusBar(modifier = Modifier.fillMaxWidth()) }
-                        with(topAreaSection) { DefaultClockLayoutWithNotifications() }
 
+                        Box {
+                            with(topAreaSection) { DefaultClockLayout() }
+                            if (shouldUseSplitNotificationShade) {
+                                with(notificationSection) {
+                                    Notifications(
+                                        Modifier.fillMaxWidth(0.5f)
+                                            .fillMaxHeight()
+                                            .align(alignment = Alignment.TopEnd)
+                                    )
+                                }
+                            }
+                        }
+                        if (!shouldUseSplitNotificationShade) {
+                            with(notificationSection) {
+                                Notifications(Modifier.weight(weight = 1f))
+                            }
+                        }
                         if (!isUdfpsVisible && ambientIndicationSectionOptional.isPresent) {
                             with(ambientIndicationSectionOptional.get()) {
                                 AmbientIndication(modifier = Modifier.fillMaxWidth())
diff --git a/packages/SystemUI/compose/features/src/com/android/systemui/keyguard/ui/composable/blueprint/WeatherClockBlueprint.kt b/packages/SystemUI/compose/features/src/com/android/systemui/keyguard/ui/composable/blueprint/WeatherClockBlueprint.kt
index fe774a0..09d76a3 100644
--- a/packages/SystemUI/compose/features/src/com/android/systemui/keyguard/ui/composable/blueprint/WeatherClockBlueprint.kt
+++ b/packages/SystemUI/compose/features/src/com/android/systemui/keyguard/ui/composable/blueprint/WeatherClockBlueprint.kt
@@ -86,6 +86,7 @@
         val burnIn = rememberBurnIn(clockInteractor)
         val resources = LocalContext.current.resources
         val currentClockState = clockViewModel.currentClock.collectAsState()
+        val areNotificationsVisible by viewModel.areNotificationsVisible.collectAsState()
         LockscreenLongPress(
             viewModel = viewModel.longPress,
             modifier = modifier,
@@ -145,7 +146,7 @@
 
                         with(mediaCarouselSection) { MediaCarousel() }
 
-                        if (viewModel.areNotificationsVisible) {
+                        if (areNotificationsVisible) {
                             with(notificationSection) {
                                 Notifications(
                                     modifier = Modifier.fillMaxWidth().weight(weight = 1f)
diff --git a/packages/SystemUI/compose/features/src/com/android/systemui/keyguard/ui/composable/section/NotificationSection.kt b/packages/SystemUI/compose/features/src/com/android/systemui/keyguard/ui/composable/section/NotificationSection.kt
index 6b86a48..fa0a1c4 100644
--- a/packages/SystemUI/compose/features/src/com/android/systemui/keyguard/ui/composable/section/NotificationSection.kt
+++ b/packages/SystemUI/compose/features/src/com/android/systemui/keyguard/ui/composable/section/NotificationSection.kt
@@ -17,12 +17,25 @@
 package com.android.systemui.keyguard.ui.composable.section
 
 import android.view.ViewGroup
+import androidx.compose.foundation.layout.fillMaxWidth
+import androidx.compose.foundation.layout.padding
 import androidx.compose.runtime.Composable
+import androidx.compose.runtime.collectAsState
+import androidx.compose.runtime.getValue
 import androidx.compose.ui.Modifier
+import androidx.compose.ui.platform.LocalContext
+import androidx.compose.ui.res.dimensionResource
+import androidx.compose.ui.unit.Dp
+import androidx.compose.ui.unit.dp
 import com.android.compose.animation.scene.SceneScope
+import com.android.compose.modifiers.thenIf
+import com.android.systemui.Flags
 import com.android.systemui.dagger.SysUISingleton
 import com.android.systemui.keyguard.MigrateClocksToBlueprint
+import com.android.systemui.keyguard.ui.viewmodel.LockscreenContentViewModel
 import com.android.systemui.notifications.ui.composable.NotificationStack
+import com.android.systemui.res.R
+import com.android.systemui.shade.LargeScreenHeaderHelper
 import com.android.systemui.statusbar.notification.stack.NotificationStackScrollLayout
 import com.android.systemui.statusbar.notification.stack.ui.view.SharedNotificationContainer
 import com.android.systemui.statusbar.notification.stack.ui.viewbinder.SharedNotificationContainerBinder
@@ -39,6 +52,7 @@
     sharedNotificationContainerViewModel: SharedNotificationContainerViewModel,
     stackScrollLayout: NotificationStackScrollLayout,
     sharedNotificationContainerBinder: SharedNotificationContainerBinder,
+    private val lockscreenContentViewModel: LockscreenContentViewModel,
 ) {
 
     init {
@@ -65,9 +79,27 @@
 
     @Composable
     fun SceneScope.Notifications(modifier: Modifier = Modifier) {
+        val shouldUseSplitNotificationShade by
+            lockscreenContentViewModel.shouldUseSplitNotificationShade.collectAsState()
+        val areNotificationsVisible by
+            lockscreenContentViewModel.areNotificationsVisible.collectAsState()
+        val splitShadeTopMargin: Dp =
+            if (Flags.centralizedStatusBarHeightFix()) {
+                LargeScreenHeaderHelper.getLargeScreenHeaderHeight(LocalContext.current).dp
+            } else {
+                dimensionResource(id = R.dimen.large_screen_shade_header_height)
+            }
+
+        if (!areNotificationsVisible) {
+            return
+        }
+
         NotificationStack(
             viewModel = viewModel,
-            modifier = modifier,
+            modifier =
+                modifier.fillMaxWidth().thenIf(shouldUseSplitNotificationShade) {
+                    Modifier.padding(top = splitShadeTopMargin)
+                },
         )
     }
 }
diff --git a/packages/SystemUI/compose/features/src/com/android/systemui/keyguard/ui/composable/section/TopAreaSection.kt b/packages/SystemUI/compose/features/src/com/android/systemui/keyguard/ui/composable/section/TopAreaSection.kt
index b4472fc..f8e6341 100644
--- a/packages/SystemUI/compose/features/src/com/android/systemui/keyguard/ui/composable/section/TopAreaSection.kt
+++ b/packages/SystemUI/compose/features/src/com/android/systemui/keyguard/ui/composable/section/TopAreaSection.kt
@@ -16,30 +16,20 @@
 
 package com.android.systemui.keyguard.ui.composable.section
 
-import androidx.compose.foundation.layout.Box
 import androidx.compose.foundation.layout.Column
-import androidx.compose.foundation.layout.Row
-import androidx.compose.foundation.layout.Spacer
-import androidx.compose.foundation.layout.fillMaxHeight
 import androidx.compose.foundation.layout.fillMaxSize
 import androidx.compose.foundation.layout.fillMaxWidth
 import androidx.compose.foundation.layout.offset
-import androidx.compose.foundation.layout.padding
 import androidx.compose.foundation.layout.wrapContentSize
 import androidx.compose.runtime.Composable
 import androidx.compose.runtime.LaunchedEffect
 import androidx.compose.runtime.collectAsState
 import androidx.compose.runtime.getValue
-import androidx.compose.ui.Alignment
 import androidx.compose.ui.Modifier
-import androidx.compose.ui.platform.LocalContext
-import androidx.compose.ui.res.dimensionResource
-import androidx.compose.ui.unit.Dp
 import androidx.compose.ui.unit.IntOffset
-import androidx.compose.ui.unit.dp
+import com.android.compose.animation.scene.SceneScope
 import com.android.compose.animation.scene.SceneTransitionLayout
 import com.android.compose.modifiers.thenIf
-import com.android.systemui.Flags
 import com.android.systemui.keyguard.domain.interactor.KeyguardClockInteractor
 import com.android.systemui.keyguard.ui.composable.blueprint.ClockScenes.largeClockScene
 import com.android.systemui.keyguard.ui.composable.blueprint.ClockScenes.smallClockScene
@@ -48,8 +38,6 @@
 import com.android.systemui.keyguard.ui.composable.blueprint.ClockTransition
 import com.android.systemui.keyguard.ui.composable.blueprint.rememberBurnIn
 import com.android.systemui.keyguard.ui.viewmodel.KeyguardClockViewModel
-import com.android.systemui.res.R
-import com.android.systemui.shade.LargeScreenHeaderHelper
 import javax.inject.Inject
 
 class TopAreaSection
@@ -58,19 +46,16 @@
     private val clockViewModel: KeyguardClockViewModel,
     private val smartSpaceSection: SmartSpaceSection,
     private val mediaCarouselSection: MediaCarouselSection,
-    private val notificationSection: NotificationSection,
     private val clockSection: DefaultClockSection,
     private val clockInteractor: KeyguardClockInteractor,
 ) {
     @Composable
-    fun DefaultClockLayoutWithNotifications(
+    fun DefaultClockLayout(
         modifier: Modifier = Modifier,
     ) {
-        val isLargeClockVisible by clockViewModel.isLargeClockVisible.collectAsState()
         val currentClockLayout by clockViewModel.currentClockLayout.collectAsState()
         val hasCustomPositionUpdatedAnimation by
             clockViewModel.hasCustomPositionUpdatedAnimation.collectAsState()
-
         val currentScene =
             when (currentClockLayout) {
                 KeyguardClockViewModel.ClockLayout.SPLIT_SHADE_LARGE_CLOCK ->
@@ -81,144 +66,83 @@
                 KeyguardClockViewModel.ClockLayout.SMALL_CLOCK -> smallClockScene
             }
 
-        val splitShadeTopMargin: Dp =
-            if (Flags.centralizedStatusBarHeightFix()) {
-                LargeScreenHeaderHelper.getLargeScreenHeaderHeight(LocalContext.current).dp
-            } else {
-                dimensionResource(id = R.dimen.large_screen_shade_header_height)
+        SceneTransitionLayout(
+            modifier = modifier,
+            currentScene = currentScene,
+            onChangeScene = {},
+            transitions = ClockTransition.defaultClockTransitions,
+            enableInterruptions = false,
+        ) {
+            scene(splitShadeLargeClockScene) {
+                LargeClockWithSmartSpace(
+                    shouldOffSetClockToOneHalf = !hasCustomPositionUpdatedAnimation
+                )
             }
+
+            scene(splitShadeSmallClockScene) {
+                SmallClockWithSmartSpace(modifier = Modifier.fillMaxWidth(0.5f))
+            }
+
+            scene(smallClockScene) { SmallClockWithSmartSpace() }
+
+            scene(largeClockScene) { LargeClockWithSmartSpace() }
+        }
+    }
+
+    @Composable
+    private fun SceneScope.SmallClockWithSmartSpace(modifier: Modifier = Modifier) {
         val burnIn = rememberBurnIn(clockInteractor)
 
+        Column(modifier = modifier) {
+            with(clockSection) {
+                SmallClock(
+                    burnInParams = burnIn.parameters,
+                    onTopChanged = burnIn.onSmallClockTopChanged,
+                    modifier = Modifier.wrapContentSize()
+                )
+            }
+            with(smartSpaceSection) {
+                SmartSpace(
+                    burnInParams = burnIn.parameters,
+                    onTopChanged = burnIn.onSmartspaceTopChanged,
+                )
+            }
+            with(mediaCarouselSection) { MediaCarousel() }
+        }
+    }
+
+    @Composable
+    private fun SceneScope.LargeClockWithSmartSpace(shouldOffSetClockToOneHalf: Boolean = false) {
+        val burnIn = rememberBurnIn(clockInteractor)
+        val isLargeClockVisible by clockViewModel.isLargeClockVisible.collectAsState()
+
         LaunchedEffect(isLargeClockVisible) {
             if (isLargeClockVisible) {
                 burnIn.onSmallClockTopChanged(null)
             }
         }
 
-        SceneTransitionLayout(
-            modifier = modifier.fillMaxSize(),
-            currentScene = currentScene,
-            onChangeScene = {},
-            transitions = ClockTransition.defaultClockTransitions,
-            enableInterruptions = false,
-        ) {
-            scene(splitShadeLargeClockScene) {
-                Box(modifier = Modifier.fillMaxSize()) {
-                    Column(
-                        modifier = Modifier.fillMaxSize(),
-                        horizontalAlignment = Alignment.CenterHorizontally,
-                    ) {
-                        with(smartSpaceSection) {
-                            SmartSpace(
-                                burnInParams = burnIn.parameters,
-                                onTopChanged = burnIn.onSmartspaceTopChanged,
-                            )
-                        }
-
-                        with(clockSection) {
-                            LargeClock(
-                                modifier =
-                                    Modifier.fillMaxSize().thenIf(
-                                        !hasCustomPositionUpdatedAnimation
-                                    ) {
-                                        // If we do not have a custom position animation, we want
-                                        // the clock to be on one half of the screen.
-                                        Modifier.offset {
-                                            IntOffset(
-                                                x =
-                                                    -clockSection
-                                                        .getClockCenteringDistance()
-                                                        .toInt(),
-                                                y = 0,
-                                            )
-                                        }
-                                    }
-                            )
-                        }
-                    }
-                }
-
-                Row(
-                    modifier = Modifier.fillMaxSize(),
-                ) {
-                    Spacer(modifier = Modifier.weight(weight = 1f))
-                    with(notificationSection) {
-                        Notifications(
-                            modifier =
-                                Modifier.fillMaxHeight()
-                                    .weight(weight = 1f)
-                                    .padding(top = splitShadeTopMargin)
-                        )
-                    }
-                }
+        Column {
+            with(smartSpaceSection) {
+                SmartSpace(
+                    burnInParams = burnIn.parameters,
+                    onTopChanged = burnIn.onSmartspaceTopChanged,
+                )
             }
-
-            scene(splitShadeSmallClockScene) {
-                Row(
-                    modifier = Modifier.fillMaxSize(),
-                ) {
-                    Column(
-                        modifier = Modifier.fillMaxHeight().weight(weight = 1f),
-                        horizontalAlignment = Alignment.CenterHorizontally,
-                    ) {
-                        with(clockSection) {
-                            SmallClock(
-                                burnInParams = burnIn.parameters,
-                                onTopChanged = burnIn.onSmallClockTopChanged,
-                                modifier = Modifier.wrapContentSize()
-                            )
+            with(clockSection) {
+                LargeClock(
+                    modifier =
+                        Modifier.fillMaxSize().thenIf(shouldOffSetClockToOneHalf) {
+                            // If we do not have a custom position animation, we want
+                            // the clock to be on one half of the screen.
+                            Modifier.offset {
+                                IntOffset(
+                                    x = -clockSection.getClockCenteringDistance().toInt(),
+                                    y = 0,
+                                )
+                            }
                         }
-                        with(smartSpaceSection) {
-                            SmartSpace(
-                                burnInParams = burnIn.parameters,
-                                onTopChanged = burnIn.onSmartspaceTopChanged,
-                            )
-                        }
-                        with(mediaCarouselSection) { MediaCarousel() }
-                    }
-                    with(notificationSection) {
-                        Notifications(
-                            modifier =
-                                Modifier.fillMaxHeight()
-                                    .weight(weight = 1f)
-                                    .padding(top = splitShadeTopMargin)
-                        )
-                    }
-                }
-            }
-
-            scene(smallClockScene) {
-                Column {
-                    with(clockSection) {
-                        SmallClock(
-                            burnInParams = burnIn.parameters,
-                            onTopChanged = burnIn.onSmallClockTopChanged,
-                            modifier = Modifier.wrapContentSize()
-                        )
-                    }
-                    with(smartSpaceSection) {
-                        SmartSpace(
-                            burnInParams = burnIn.parameters,
-                            onTopChanged = burnIn.onSmartspaceTopChanged,
-                        )
-                    }
-                    with(mediaCarouselSection) { MediaCarousel() }
-                    with(notificationSection) {
-                        Notifications(modifier = Modifier.fillMaxWidth().weight(weight = 1f))
-                    }
-                }
-            }
-
-            scene(largeClockScene) {
-                Column {
-                    with(smartSpaceSection) {
-                        SmartSpace(
-                            burnInParams = burnIn.parameters,
-                            onTopChanged = burnIn.onSmartspaceTopChanged,
-                        )
-                    }
-                    with(clockSection) { LargeClock(modifier = Modifier.fillMaxSize()) }
-                }
+                )
             }
         }
     }
diff --git a/packages/SystemUI/multivalentTests/src/com/android/systemui/biometrics/UdfpsKeyguardViewLegacyControllerBaseTest.java b/packages/SystemUI/multivalentTests/src/com/android/systemui/biometrics/UdfpsKeyguardViewLegacyControllerBaseTest.java
index 324534f..7986051 100644
--- a/packages/SystemUI/multivalentTests/src/com/android/systemui/biometrics/UdfpsKeyguardViewLegacyControllerBaseTest.java
+++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/biometrics/UdfpsKeyguardViewLegacyControllerBaseTest.java
@@ -105,7 +105,7 @@
         when(mKeyguardViewMediator.isAnimatingScreenOff()).thenReturn(false);
         when(mView.getUnpausedAlpha()).thenReturn(255);
         when(mShadeExpansionStateManager.addExpansionListener(any())).thenReturn(
-                new ShadeExpansionChangeEvent(0, false, false, 0));
+                new ShadeExpansionChangeEvent(0, false, false));
         mController = createUdfpsKeyguardViewController();
     }
 
diff --git a/packages/SystemUI/multivalentTests/src/com/android/systemui/communal/CommunalSceneStartableTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/communal/CommunalSceneStartableTest.kt
index 43266bf..a944afb 100644
--- a/packages/SystemUI/multivalentTests/src/com/android/systemui/communal/CommunalSceneStartableTest.kt
+++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/communal/CommunalSceneStartableTest.kt
@@ -88,7 +88,7 @@
             testScope.runTest {
                 val scene by collectLastValue(communalInteractor.desiredScene)
 
-                communalInteractor.onSceneChanged(CommunalScenes.Communal)
+                communalInteractor.changeScene(CommunalScenes.Communal)
                 assertThat(scene).isEqualTo(CommunalScenes.Communal)
 
                 fakeKeyguardTransitionRepository.sendTransitionSteps(
@@ -158,7 +158,7 @@
         with(kosmos) {
             testScope.runTest {
                 val scene by collectLastValue(communalInteractor.desiredScene)
-                communalInteractor.onSceneChanged(CommunalScenes.Communal)
+                communalInteractor.changeScene(CommunalScenes.Communal)
                 assertThat(scene).isEqualTo(CommunalScenes.Communal)
 
                 fakeKeyguardTransitionRepository.sendTransitionSteps(
@@ -179,7 +179,7 @@
         with(kosmos) {
             testScope.runTest {
                 val scene by collectLastValue(communalInteractor.desiredScene)
-                communalInteractor.onSceneChanged(CommunalScenes.Communal)
+                communalInteractor.changeScene(CommunalScenes.Communal)
                 assertThat(scene).isEqualTo(CommunalScenes.Communal)
 
                 fakeKeyguardTransitionRepository.sendTransitionSteps(
@@ -207,7 +207,7 @@
     fun dockingOnLockscreen_forcesCommunal() =
         with(kosmos) {
             testScope.runTest {
-                communalInteractor.onSceneChanged(CommunalScenes.Blank)
+                communalInteractor.changeScene(CommunalScenes.Blank)
                 val scene by collectLastValue(communalInteractor.desiredScene)
 
                 // device is docked while on the lockscreen
@@ -229,7 +229,7 @@
     fun dockingOnLockscreen_doesNotForceCommunalIfDreamStarts() =
         with(kosmos) {
             testScope.runTest {
-                communalInteractor.onSceneChanged(CommunalScenes.Blank)
+                communalInteractor.changeScene(CommunalScenes.Blank)
                 val scene by collectLastValue(communalInteractor.desiredScene)
 
                 // device is docked while on the lockscreen
@@ -261,7 +261,7 @@
             testScope.runTest {
                 // Device is dreaming and on communal.
                 fakeKeyguardRepository.setDreaming(true)
-                communalInteractor.onSceneChanged(CommunalScenes.Communal)
+                communalInteractor.changeScene(CommunalScenes.Communal)
 
                 val scene by collectLastValue(communalInteractor.desiredScene)
                 assertThat(scene).isEqualTo(CommunalScenes.Communal)
@@ -278,7 +278,7 @@
             testScope.runTest {
                 // Device is not dreaming and on communal.
                 fakeKeyguardRepository.setDreaming(false)
-                communalInteractor.onSceneChanged(CommunalScenes.Communal)
+                communalInteractor.changeScene(CommunalScenes.Communal)
 
                 // Scene stays as Communal
                 advanceTimeBy(SCREEN_TIMEOUT.milliseconds)
@@ -293,7 +293,7 @@
             testScope.runTest {
                 // Device is dreaming and on communal.
                 fakeKeyguardRepository.setDreaming(true)
-                communalInteractor.onSceneChanged(CommunalScenes.Communal)
+                communalInteractor.changeScene(CommunalScenes.Communal)
 
                 val scene by collectLastValue(communalInteractor.desiredScene)
                 assertThat(scene).isEqualTo(CommunalScenes.Communal)
@@ -316,7 +316,7 @@
             testScope.runTest {
                 // Device is on communal, but not dreaming.
                 fakeKeyguardRepository.setDreaming(false)
-                communalInteractor.onSceneChanged(CommunalScenes.Communal)
+                communalInteractor.changeScene(CommunalScenes.Communal)
 
                 val scene by collectLastValue(communalInteractor.desiredScene)
                 assertThat(scene).isEqualTo(CommunalScenes.Communal)
@@ -338,7 +338,7 @@
             testScope.runTest {
                 // Device is dreaming and on communal.
                 fakeKeyguardRepository.setDreaming(true)
-                communalInteractor.onSceneChanged(CommunalScenes.Communal)
+                communalInteractor.changeScene(CommunalScenes.Communal)
 
                 val scene by collectLastValue(communalInteractor.desiredScene)
                 assertThat(scene).isEqualTo(CommunalScenes.Communal)
@@ -367,7 +367,7 @@
 
                 // Device is dreaming and on communal.
                 fakeKeyguardRepository.setDreaming(true)
-                communalInteractor.onSceneChanged(CommunalScenes.Communal)
+                communalInteractor.changeScene(CommunalScenes.Communal)
 
                 val scene by collectLastValue(communalInteractor.desiredScene)
                 assertThat(scene).isEqualTo(CommunalScenes.Communal)
diff --git a/packages/SystemUI/multivalentTests/src/com/android/systemui/communal/data/repository/CommunalRepositoryImplTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/communal/data/repository/CommunalRepositoryImplTest.kt
index 43acf31..2d78a9b 100644
--- a/packages/SystemUI/multivalentTests/src/com/android/systemui/communal/data/repository/CommunalRepositoryImplTest.kt
+++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/communal/data/repository/CommunalRepositoryImplTest.kt
@@ -22,36 +22,26 @@
 import com.android.systemui.SysuiTestCase
 import com.android.systemui.communal.shared.model.CommunalScenes
 import com.android.systemui.coroutines.collectLastValue
+import com.android.systemui.kosmos.applicationCoroutineScope
 import com.android.systemui.kosmos.testScope
-import com.android.systemui.scene.data.repository.sceneContainerRepository
-import com.android.systemui.scene.shared.flag.fakeSceneContainerFlags
+import com.android.systemui.scene.shared.model.sceneDataSource
 import com.android.systemui.testKosmos
 import com.google.common.truth.Truth.assertThat
 import kotlinx.coroutines.flow.flowOf
 import kotlinx.coroutines.test.runTest
-import org.junit.Before
 import org.junit.Test
 import org.junit.runner.RunWith
 
 @SmallTest
 @RunWith(AndroidJUnit4::class)
 class CommunalRepositoryImplTest : SysuiTestCase() {
-    private lateinit var underTest: CommunalRepositoryImpl
 
     private val kosmos = testKosmos()
     private val testScope = kosmos.testScope
-    private val sceneContainerRepository = kosmos.sceneContainerRepository
-
-    @Before
-    fun setUp() {
-        underTest = createRepositoryImpl(false)
-    }
-
-    private fun createRepositoryImpl(sceneContainerEnabled: Boolean): CommunalRepositoryImpl {
-        return CommunalRepositoryImpl(
-            testScope.backgroundScope,
-            kosmos.fakeSceneContainerFlags.apply { enabled = sceneContainerEnabled },
-            sceneContainerRepository,
+    private val underTest by lazy {
+        CommunalRepositoryImpl(
+            kosmos.applicationCoroutineScope,
+            kosmos.sceneDataSource,
         )
     }
 
diff --git a/packages/SystemUI/multivalentTests/src/com/android/systemui/communal/domain/interactor/CommunalInteractorTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/communal/domain/interactor/CommunalInteractorTest.kt
index 8e9d769..e7ccde2 100644
--- a/packages/SystemUI/multivalentTests/src/com/android/systemui/communal/domain/interactor/CommunalInteractorTest.kt
+++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/communal/domain/interactor/CommunalInteractorTest.kt
@@ -482,7 +482,7 @@
             assertThat(desiredScene()).isEqualTo(CommunalScenes.Blank)
 
             val targetScene = CommunalScenes.Communal
-            communalRepository.setDesiredScene(targetScene)
+            communalRepository.changeScene(targetScene)
             desiredScene = collectLastValue(underTest.desiredScene)
             runCurrent()
             assertThat(desiredScene()).isEqualTo(targetScene)
@@ -493,9 +493,9 @@
         testScope.runTest {
             val targetScene = CommunalScenes.Communal
 
-            underTest.onSceneChanged(targetScene)
+            underTest.changeScene(targetScene)
 
-            val desiredScene = collectLastValue(communalRepository.desiredScene)
+            val desiredScene = collectLastValue(communalRepository.currentScene)
             runCurrent()
             assertThat(desiredScene()).isEqualTo(targetScene)
         }
@@ -508,7 +508,7 @@
 
             val desiredScene by collectLastValue(underTest.desiredScene)
 
-            underTest.onSceneChanged(CommunalScenes.Communal)
+            underTest.changeScene(CommunalScenes.Communal)
             assertThat(desiredScene).isEqualTo(CommunalScenes.Communal)
 
             kosmos.setCommunalAvailable(false)
@@ -659,7 +659,7 @@
             runCurrent()
             assertThat(isCommunalShowing()).isEqualTo(false)
 
-            underTest.onSceneChanged(CommunalScenes.Communal)
+            underTest.changeScene(CommunalScenes.Communal)
 
             isCommunalShowing = collectLastValue(underTest.isCommunalShowing)
             runCurrent()
@@ -683,12 +683,12 @@
             assertThat(isCommunalShowing).isFalse()
 
             // Verify scene changes (without the flag) to communal sets the value to true
-            underTest.onSceneChanged(CommunalScenes.Communal)
+            underTest.changeScene(CommunalScenes.Communal)
             runCurrent()
             assertThat(isCommunalShowing).isTrue()
 
             // Verify scene changes (without the flag) to blank sets the value back to false
-            underTest.onSceneChanged(CommunalScenes.Blank)
+            underTest.changeScene(CommunalScenes.Blank)
             runCurrent()
             assertThat(isCommunalShowing).isFalse()
         }
@@ -704,7 +704,7 @@
             assertThat(isCommunalShowing).isFalse()
 
             // Verify scene changes without the flag doesn't have any impact
-            underTest.onSceneChanged(CommunalScenes.Communal)
+            underTest.changeScene(CommunalScenes.Communal)
             runCurrent()
             assertThat(isCommunalShowing).isFalse()
 
diff --git a/packages/SystemUI/multivalentTests/src/com/android/systemui/communal/domain/interactor/CommunalTutorialInteractorTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/communal/domain/interactor/CommunalTutorialInteractorTest.kt
index 50b8da6..3a23e14 100644
--- a/packages/SystemUI/multivalentTests/src/com/android/systemui/communal/domain/interactor/CommunalTutorialInteractorTest.kt
+++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/communal/domain/interactor/CommunalTutorialInteractorTest.kt
@@ -158,7 +158,7 @@
             kosmos.setCommunalAvailable(true)
             communalTutorialRepository.setTutorialSettingState(HUB_MODE_TUTORIAL_NOT_STARTED)
 
-            communalInteractor.onSceneChanged(CommunalScenes.Blank)
+            communalInteractor.changeScene(CommunalScenes.Blank)
 
             assertThat(tutorialSettingState).isEqualTo(HUB_MODE_TUTORIAL_NOT_STARTED)
         }
@@ -171,7 +171,7 @@
             goToCommunal()
             communalTutorialRepository.setTutorialSettingState(HUB_MODE_TUTORIAL_STARTED)
 
-            communalInteractor.onSceneChanged(CommunalScenes.Blank)
+            communalInteractor.changeScene(CommunalScenes.Blank)
 
             assertThat(tutorialSettingState).isEqualTo(HUB_MODE_TUTORIAL_COMPLETED)
         }
@@ -184,13 +184,13 @@
             goToCommunal()
             communalTutorialRepository.setTutorialSettingState(HUB_MODE_TUTORIAL_COMPLETED)
 
-            communalInteractor.onSceneChanged(CommunalScenes.Blank)
+            communalInteractor.changeScene(CommunalScenes.Blank)
 
             assertThat(tutorialSettingState).isEqualTo(HUB_MODE_TUTORIAL_COMPLETED)
         }
 
     private suspend fun goToCommunal() {
         kosmos.setCommunalAvailable(true)
-        communalInteractor.onSceneChanged(CommunalScenes.Communal)
+        communalInteractor.changeScene(CommunalScenes.Communal)
     }
 }
diff --git a/packages/SystemUI/multivalentTests/src/com/android/systemui/dreams/touch/BouncerSwipeTouchHandlerTest.java b/packages/SystemUI/multivalentTests/src/com/android/systemui/dreams/touch/BouncerSwipeTouchHandlerTest.java
index 5827671..6a86801 100644
--- a/packages/SystemUI/multivalentTests/src/com/android/systemui/dreams/touch/BouncerSwipeTouchHandlerTest.java
+++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/dreams/touch/BouncerSwipeTouchHandlerTest.java
@@ -376,7 +376,7 @@
         // Ensure correct expansion passed in.
         ShadeExpansionChangeEvent event =
                 new ShadeExpansionChangeEvent(
-                        expansion, /* expanded= */ false, /* tracking= */ true, dragDownAmount);
+                        expansion, /* expanded= */ false, /* tracking= */ true);
         verify(mScrimController).expand(event);
     }
 
diff --git a/packages/SystemUI/multivalentTests/src/com/android/systemui/dreams/touch/scrim/BouncerlessScrimControllerTest.java b/packages/SystemUI/multivalentTests/src/com/android/systemui/dreams/touch/scrim/BouncerlessScrimControllerTest.java
index 97052a8..7cdd478 100644
--- a/packages/SystemUI/multivalentTests/src/com/android/systemui/dreams/touch/scrim/BouncerlessScrimControllerTest.java
+++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/dreams/touch/scrim/BouncerlessScrimControllerTest.java
@@ -59,7 +59,7 @@
         final BouncerlessScrimController scrimController =
                 new BouncerlessScrimController(mExecutor, mPowerManager);
         scrimController.addCallback(mCallback);
-        scrimController.expand(new ShadeExpansionChangeEvent(.5f, true, false, 0.0f));
+        scrimController.expand(new ShadeExpansionChangeEvent(.5f, true, false));
         mExecutor.runAllReady();
         verify(mPowerManager).wakeUp(anyLong(), eq(PowerManager.WAKE_REASON_GESTURE), any());
         verify(mCallback).onWakeup();
@@ -71,7 +71,7 @@
                 new BouncerlessScrimController(mExecutor, mPowerManager);
         scrimController.addCallback(mCallback);
         final ShadeExpansionChangeEvent expansionEvent =
-                new ShadeExpansionChangeEvent(0.5f, false, false, 0.0f);
+                new ShadeExpansionChangeEvent(0.5f, false, false);
         scrimController.expand(expansionEvent);
         mExecutor.runAllReady();
         verify(mCallback).onExpansion(eq(expansionEvent));
diff --git a/packages/SystemUI/tests/src/com/android/systemui/keyguard/domain/interactor/KeyguardOcclusionInteractorTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/keyguard/domain/interactor/KeyguardOcclusionInteractorTest.kt
similarity index 78%
rename from packages/SystemUI/tests/src/com/android/systemui/keyguard/domain/interactor/KeyguardOcclusionInteractorTest.kt
rename to packages/SystemUI/multivalentTests/src/com/android/systemui/keyguard/domain/interactor/KeyguardOcclusionInteractorTest.kt
index 8a77ed2..056a401 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/keyguard/domain/interactor/KeyguardOcclusionInteractorTest.kt
+++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/keyguard/domain/interactor/KeyguardOcclusionInteractorTest.kt
@@ -30,17 +30,24 @@
  * limitations under the License.
  */
 
+@file:OptIn(ExperimentalCoroutinesApi::class)
+
 package com.android.systemui.keyguard.domain.interactor
 
 import androidx.test.ext.junit.runners.AndroidJUnit4
 import androidx.test.filters.SmallTest
 import com.android.systemui.SysuiTestCase
+import com.android.systemui.coroutines.collectLastValue
 import com.android.systemui.coroutines.collectValues
+import com.android.systemui.deviceentry.data.repository.fakeDeviceEntryRepository
+import com.android.systemui.flags.EnableSceneContainer
+import com.android.systemui.keyguard.data.repository.FakeKeyguardTransitionRepository
 import com.android.systemui.keyguard.data.repository.fakeKeyguardTransitionRepository
 import com.android.systemui.keyguard.data.repository.keyguardOcclusionRepository
 import com.android.systemui.keyguard.shared.model.KeyguardState
 import com.android.systemui.keyguard.shared.model.TransitionState
 import com.android.systemui.kosmos.testScope
+import com.android.systemui.power.domain.interactor.PowerInteractor
 import com.android.systemui.power.domain.interactor.PowerInteractor.Companion.setAsleepForTest
 import com.android.systemui.power.domain.interactor.PowerInteractor.Companion.setAwakeForTest
 import com.android.systemui.power.domain.interactor.powerInteractor
@@ -49,22 +56,33 @@
 import com.google.common.truth.Truth.assertThat
 import junit.framework.Assert.assertFalse
 import junit.framework.Assert.assertTrue
-import kotlin.test.Test
+import kotlinx.coroutines.ExperimentalCoroutinesApi
 import kotlinx.coroutines.test.runCurrent
 import kotlinx.coroutines.test.runTest
+import org.junit.Before
+import org.junit.Test
 import org.junit.runner.RunWith
 
 @SmallTest
 @RunWith(AndroidJUnit4::class)
 class KeyguardOcclusionInteractorTest : SysuiTestCase() {
+
     private val kosmos = testKosmos()
     private val testScope = kosmos.testScope
-    private val underTest = kosmos.keyguardOcclusionInteractor
-    private val powerInteractor = kosmos.powerInteractor
-    private val transitionRepository = kosmos.fakeKeyguardTransitionRepository
+
+    private lateinit var underTest: KeyguardOcclusionInteractor
+    private lateinit var powerInteractor: PowerInteractor
+    private lateinit var transitionRepository: FakeKeyguardTransitionRepository
+
+    @Before
+    fun setUp() {
+        powerInteractor = kosmos.powerInteractor
+        transitionRepository = kosmos.fakeKeyguardTransitionRepository
+        underTest = kosmos.keyguardOcclusionInteractor
+    }
 
     @Test
-    fun testTransitionFromPowerGesture_whileGoingToSleep_isTrue() =
+    fun transitionFromPowerGesture_whileGoingToSleep_isTrue() =
         testScope.runTest {
             powerInteractor.setAwakeForTest()
             transitionRepository.sendTransitionSteps(
@@ -81,7 +99,7 @@
         }
 
     @Test
-    fun testTransitionFromPowerGesture_whileAsleep_isTrue() =
+    fun transitionFromPowerGesture_whileAsleep_isTrue() =
         testScope.runTest {
             powerInteractor.setAwakeForTest()
             transitionRepository.sendTransitionSteps(
@@ -97,7 +115,7 @@
         }
 
     @Test
-    fun testTransitionFromPowerGesture_whileWaking_isFalse() =
+    fun transitionFromPowerGesture_whileWaking_isFalse() =
         testScope.runTest {
             powerInteractor.setAwakeForTest()
             transitionRepository.sendTransitionSteps(
@@ -119,7 +137,7 @@
         }
 
     @Test
-    fun testTransitionFromPowerGesture_whileAwake_isFalse() =
+    fun transitionFromPowerGesture_whileAwake_isFalse() =
         testScope.runTest {
             powerInteractor.setAwakeForTest()
             transitionRepository.sendTransitionSteps(
@@ -140,7 +158,7 @@
         }
 
     @Test
-    fun testShowWhenLockedActivityLaunchedFromPowerGesture_notTrueSecondTime() =
+    fun showWhenLockedActivityLaunchedFromPowerGesture_notTrueSecondTime() =
         testScope.runTest {
             val values by collectValues(underTest.showWhenLockedActivityLaunchedFromPowerGesture)
             powerInteractor.setAsleepForTest()
@@ -187,7 +205,7 @@
         }
 
     @Test
-    fun testShowWhenLockedActivityLaunchedFromPowerGesture_falseIfReturningToGone() =
+    fun showWhenLockedActivityLaunchedFromPowerGesture_falseIfReturningToGone() =
         testScope.runTest {
             val values by collectValues(underTest.showWhenLockedActivityLaunchedFromPowerGesture)
             powerInteractor.setAwakeForTest()
@@ -221,4 +239,23 @@
                     false,
                 )
         }
+
+    @Test
+    @EnableSceneContainer
+    fun occludingActivityWillDismissKeyguard() =
+        testScope.runTest {
+            val occludingActivityWillDismissKeyguard by
+                collectLastValue(underTest.occludingActivityWillDismissKeyguard)
+            assertThat(occludingActivityWillDismissKeyguard).isFalse()
+
+            // Unlock device:
+            kosmos.fakeDeviceEntryRepository.setUnlocked(true)
+            runCurrent()
+            assertThat(occludingActivityWillDismissKeyguard).isTrue()
+
+            // Re-lock device:
+            kosmos.fakeDeviceEntryRepository.setUnlocked(false)
+            runCurrent()
+            assertThat(occludingActivityWillDismissKeyguard).isFalse()
+        }
 }
diff --git a/packages/SystemUI/multivalentTests/src/com/android/systemui/keyguard/ui/viewmodel/AlternateBouncerToGoneTransitionViewModelTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/keyguard/ui/viewmodel/AlternateBouncerToGoneTransitionViewModelTest.kt
index 0cc0c2f..78bdfb3 100644
--- a/packages/SystemUI/multivalentTests/src/com/android/systemui/keyguard/ui/viewmodel/AlternateBouncerToGoneTransitionViewModelTest.kt
+++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/keyguard/ui/viewmodel/AlternateBouncerToGoneTransitionViewModelTest.kt
@@ -30,6 +30,7 @@
 import com.android.systemui.keyguard.shared.model.TransitionState.RUNNING
 import com.android.systemui.keyguard.shared.model.TransitionStep
 import com.android.systemui.kosmos.testScope
+import com.android.systemui.statusbar.sysuiStatusBarStateController
 import com.android.systemui.testKosmos
 import com.google.common.truth.Truth.assertThat
 import kotlinx.coroutines.ExperimentalCoroutinesApi
@@ -51,6 +52,7 @@
         }
     private val testScope = kosmos.testScope
     private val keyguardTransitionRepository = kosmos.fakeKeyguardTransitionRepository
+    private val sysuiStatusBarStateController = kosmos.sysuiStatusBarStateController
     private val underTest by lazy { kosmos.alternateBouncerToGoneTransitionViewModel }
 
     @Test
@@ -112,6 +114,21 @@
         }
 
     @Test
+    fun notificationAlpha_leaveShadeOpen() =
+        testScope.runTest {
+            val values by collectValues(underTest.notificationAlpha(ViewStateAccessor()))
+            runCurrent()
+
+            sysuiStatusBarStateController.setLeaveOpenOnKeyguardHide(true)
+
+            keyguardTransitionRepository.sendTransitionStep(step(0f, TransitionState.STARTED))
+            keyguardTransitionRepository.sendTransitionStep(step(1f))
+
+            assertThat(values.size).isEqualTo(2)
+            values.forEach { assertThat(it).isEqualTo(1f) }
+        }
+
+    @Test
     fun lockscreenAlpha_zeroInitialAlpha() =
         testScope.runTest {
             // ViewState starts at 0 alpha.
diff --git a/packages/SystemUI/multivalentTests/src/com/android/systemui/keyguard/ui/viewmodel/BouncerToGoneFlowsTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/keyguard/ui/viewmodel/BouncerToGoneFlowsTest.kt
index ad2ae8b..e6b3017 100644
--- a/packages/SystemUI/multivalentTests/src/com/android/systemui/keyguard/ui/viewmodel/BouncerToGoneFlowsTest.kt
+++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/keyguard/ui/viewmodel/BouncerToGoneFlowsTest.kt
@@ -20,6 +20,7 @@
 import androidx.test.filters.SmallTest
 import com.android.systemui.SysuiTestCase
 import com.android.systemui.bouncer.domain.interactor.mockPrimaryBouncerInteractor
+import com.android.systemui.coroutines.collectLastValue
 import com.android.systemui.coroutines.collectValues
 import com.android.systemui.flags.Flags
 import com.android.systemui.flags.fakeFeatureFlagsClassic
@@ -149,6 +150,38 @@
         }
 
     @Test
+    fun showAllNotifications_isTrue_whenLeaveShadeOpen() =
+        testScope.runTest {
+            val showAllNotifications by
+                collectLastValue(underTest.showAllNotifications(500.milliseconds, PRIMARY_BOUNCER))
+
+            sysuiStatusBarStateController.setLeaveOpenOnKeyguardHide(true)
+
+            keyguardTransitionRepository.sendTransitionStep(step(0f, TransitionState.STARTED))
+            keyguardTransitionRepository.sendTransitionStep(step(0.1f))
+
+            assertThat(showAllNotifications).isTrue()
+            keyguardTransitionRepository.sendTransitionStep(step(1f, TransitionState.FINISHED))
+            assertThat(showAllNotifications).isFalse()
+        }
+
+    @Test
+    fun showAllNotifications_isFalse_whenLeaveShadeIsNotOpen() =
+        testScope.runTest {
+            val showAllNotifications by
+                collectLastValue(underTest.showAllNotifications(500.milliseconds, PRIMARY_BOUNCER))
+
+            sysuiStatusBarStateController.setLeaveOpenOnKeyguardHide(false)
+
+            keyguardTransitionRepository.sendTransitionStep(step(0f, TransitionState.STARTED))
+            keyguardTransitionRepository.sendTransitionStep(step(0.1f))
+
+            assertThat(showAllNotifications).isFalse()
+            keyguardTransitionRepository.sendTransitionStep(step(1f, TransitionState.FINISHED))
+            assertThat(showAllNotifications).isFalse()
+        }
+
+    @Test
     fun scrimBehindAlpha_doNotLeaveShadeOpen() =
         testScope.runTest {
             val values by collectValues(underTest.scrimAlpha(500.milliseconds, PRIMARY_BOUNCER))
diff --git a/packages/SystemUI/multivalentTests/src/com/android/systemui/keyguard/ui/viewmodel/LockscreenContentViewModelTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/keyguard/ui/viewmodel/LockscreenContentViewModelTest.kt
index 751ac1d..e9a8257 100644
--- a/packages/SystemUI/multivalentTests/src/com/android/systemui/keyguard/ui/viewmodel/LockscreenContentViewModelTest.kt
+++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/keyguard/ui/viewmodel/LockscreenContentViewModelTest.kt
@@ -21,6 +21,7 @@
 import com.android.keyguard.KeyguardClockSwitch
 import com.android.systemui.SysuiTestCase
 import com.android.systemui.biometrics.authController
+import com.android.systemui.coroutines.collectLastValue
 import com.android.systemui.flags.Flags
 import com.android.systemui.flags.fakeFeatureFlagsClassic
 import com.android.systemui.keyguard.data.repository.fakeKeyguardClockRepository
@@ -96,7 +97,7 @@
                 shadeRepository.setShadeMode(ShadeMode.Split)
                 kosmos.fakeKeyguardClockRepository.setClockSize(KeyguardClockSwitch.LARGE)
 
-                assertThat(underTest.areNotificationsVisible).isTrue()
+                assertThat(collectLastValue(underTest.areNotificationsVisible).invoke()).isTrue()
             }
         }
     @Test
@@ -104,7 +105,7 @@
         with(kosmos) {
             testScope.runTest {
                 kosmos.fakeKeyguardClockRepository.setClockSize(KeyguardClockSwitch.SMALL)
-                assertThat(underTest.areNotificationsVisible).isTrue()
+                assertThat(collectLastValue(underTest.areNotificationsVisible).invoke()).isTrue()
             }
         }
 
@@ -113,7 +114,7 @@
         with(kosmos) {
             testScope.runTest {
                 kosmos.fakeKeyguardClockRepository.setClockSize(KeyguardClockSwitch.LARGE)
-                assertThat(underTest.areNotificationsVisible).isFalse()
+                assertThat(collectLastValue(underTest.areNotificationsVisible).invoke()).isFalse()
             }
         }
 
@@ -122,7 +123,8 @@
         with(kosmos) {
             testScope.runTest {
                 shadeRepository.setShadeMode(ShadeMode.Split)
-                assertThat(underTest.shouldUseSplitNotificationShade).isTrue()
+                assertThat(collectLastValue(underTest.shouldUseSplitNotificationShade).invoke())
+                    .isTrue()
             }
         }
 
@@ -131,16 +133,8 @@
         with(kosmos) {
             testScope.runTest {
                 shadeRepository.setShadeMode(ShadeMode.Single)
-                assertThat(underTest.shouldUseSplitNotificationShade).isFalse()
-            }
-        }
-
-    @Test
-    fun sceneKey() =
-        with(kosmos) {
-            testScope.runTest {
-                shadeRepository.setShadeMode(ShadeMode.Single)
-                assertThat(underTest.shouldUseSplitNotificationShade).isFalse()
+                assertThat(collectLastValue(underTest.shouldUseSplitNotificationShade).invoke())
+                    .isFalse()
             }
         }
 }
diff --git a/packages/SystemUI/multivalentTests/src/com/android/systemui/keyguard/ui/viewmodel/LockscreenToGoneTransitionViewModelTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/keyguard/ui/viewmodel/LockscreenToGoneTransitionViewModelTest.kt
index 857b9f8..26cb485 100644
--- a/packages/SystemUI/multivalentTests/src/com/android/systemui/keyguard/ui/viewmodel/LockscreenToGoneTransitionViewModelTest.kt
+++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/keyguard/ui/viewmodel/LockscreenToGoneTransitionViewModelTest.kt
@@ -26,6 +26,7 @@
 import com.android.systemui.keyguard.shared.model.TransitionState
 import com.android.systemui.keyguard.shared.model.TransitionStep
 import com.android.systemui.kosmos.testScope
+import com.android.systemui.statusbar.sysuiStatusBarStateController
 import com.android.systemui.testKosmos
 import com.google.common.truth.Truth.assertThat
 import kotlinx.coroutines.ExperimentalCoroutinesApi
@@ -40,6 +41,7 @@
     private val kosmos = testKosmos()
     private val testScope = kosmos.testScope
     private val repository = kosmos.fakeKeyguardTransitionRepository
+    private val sysuiStatusBarStateController = kosmos.sysuiStatusBarStateController
     private val underTest = kosmos.lockscreenToGoneTransitionViewModel
 
     @Test
@@ -90,6 +92,20 @@
             assertThat(alpha).isEqualTo(0f)
         }
 
+    @Test
+    fun notificationAlpha_leaveShadeOpen() =
+        testScope.runTest {
+            val values by collectValues(underTest.notificationAlpha(ViewStateAccessor()))
+
+            sysuiStatusBarStateController.setLeaveOpenOnKeyguardHide(true)
+
+            repository.sendTransitionStep(step(0f, TransitionState.STARTED))
+            repository.sendTransitionStep(step(1f))
+
+            assertThat(values.size).isEqualTo(2)
+            values.forEach { assertThat(it).isEqualTo(1f) }
+        }
+
     private fun step(
         value: Float,
         state: TransitionState = TransitionState.RUNNING
diff --git a/packages/SystemUI/multivalentTests/src/com/android/systemui/scene/SceneFrameworkIntegrationTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/scene/SceneFrameworkIntegrationTest.kt
index c80835d..efbdb7d 100644
--- a/packages/SystemUI/multivalentTests/src/com/android/systemui/scene/SceneFrameworkIntegrationTest.kt
+++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/scene/SceneFrameworkIntegrationTest.kt
@@ -64,6 +64,7 @@
 import com.android.systemui.qs.footerActionsController
 import com.android.systemui.qs.footerActionsViewModelFactory
 import com.android.systemui.qs.ui.adapter.FakeQSSceneAdapter
+import com.android.systemui.scene.domain.interactor.sceneContainerOcclusionInteractor
 import com.android.systemui.scene.domain.interactor.sceneInteractor
 import com.android.systemui.scene.domain.startable.SceneContainerStartable
 import com.android.systemui.scene.shared.flag.fakeSceneContainerFlags
@@ -286,6 +287,7 @@
                 deviceProvisioningInteractor = kosmos.deviceProvisioningInteractor,
                 centralSurfaces = mock(),
                 headsUpInteractor = kosmos.headsUpNotificationInteractor,
+                occlusionInteractor = kosmos.sceneContainerOcclusionInteractor,
             )
         startable.start()
 
diff --git a/packages/SystemUI/multivalentTests/src/com/android/systemui/scene/domain/interactor/SceneContainerOcclusionInteractorTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/scene/domain/interactor/SceneContainerOcclusionInteractorTest.kt
new file mode 100644
index 0000000..c3366ad
--- /dev/null
+++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/scene/domain/interactor/SceneContainerOcclusionInteractorTest.kt
@@ -0,0 +1,270 @@
+/*
+ * Copyright (C) 2024 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+@file:OptIn(ExperimentalCoroutinesApi::class)
+
+package com.android.systemui.scene.domain.interactor
+
+import androidx.test.ext.junit.runners.AndroidJUnit4
+import androidx.test.filters.SmallTest
+import com.android.compose.animation.scene.ObservableTransitionState
+import com.android.systemui.SysuiTestCase
+import com.android.systemui.coroutines.collectLastValue
+import com.android.systemui.keyguard.data.repository.fakeKeyguardTransitionRepository
+import com.android.systemui.keyguard.domain.interactor.keyguardTransitionInteractor
+import com.android.systemui.keyguard.shared.model.KeyguardState
+import com.android.systemui.keyguard.shared.model.TransitionState
+import com.android.systemui.keyguard.shared.model.TransitionStep
+import com.android.systemui.kosmos.testScope
+import com.android.systemui.scene.shared.model.Scenes
+import com.android.systemui.scene.shared.model.sceneDataSource
+import com.android.systemui.statusbar.domain.interactor.keyguardOcclusionInteractor
+import com.android.systemui.testKosmos
+import com.android.systemui.util.mockito.mock
+import com.google.common.truth.Truth.assertThat
+import com.google.common.truth.Truth.assertWithMessage
+import kotlinx.coroutines.ExperimentalCoroutinesApi
+import kotlinx.coroutines.flow.MutableStateFlow
+import kotlinx.coroutines.flow.flowOf
+import kotlinx.coroutines.test.TestScope
+import kotlinx.coroutines.test.runCurrent
+import kotlinx.coroutines.test.runTest
+import org.junit.Test
+import org.junit.runner.RunWith
+
+@SmallTest
+@RunWith(AndroidJUnit4::class)
+class SceneContainerOcclusionInteractorTest : SysuiTestCase() {
+
+    private val kosmos = testKosmos()
+    private val testScope = kosmos.testScope
+    private val keyguardOcclusionInteractor = kosmos.keyguardOcclusionInteractor
+    private val keyguardTransitionRepository = kosmos.fakeKeyguardTransitionRepository
+    private val keyguardTransitionInteractor = kosmos.keyguardTransitionInteractor
+    private val mutableTransitionState =
+        MutableStateFlow<ObservableTransitionState>(
+            ObservableTransitionState.Idle(Scenes.Lockscreen)
+        )
+    private val sceneInteractor =
+        kosmos.sceneInteractor.apply { setTransitionState(mutableTransitionState) }
+    private val sceneDataSource =
+        kosmos.sceneDataSource.apply { changeScene(toScene = Scenes.Lockscreen) }
+
+    private val underTest = kosmos.sceneContainerOcclusionInteractor
+
+    @Test
+    fun invisibleDueToOcclusion() =
+        testScope.runTest {
+            val invisibleDueToOcclusion by collectLastValue(underTest.invisibleDueToOcclusion)
+            val keyguardState by collectLastValue(keyguardTransitionInteractor.currentKeyguardState)
+
+            // Assert that we have the desired preconditions:
+            assertThat(keyguardState).isEqualTo(KeyguardState.LOCKSCREEN)
+            assertThat(sceneInteractor.currentScene.value).isEqualTo(Scenes.Lockscreen)
+            assertThat(sceneInteractor.transitionState.value)
+                .isEqualTo(ObservableTransitionState.Idle(Scenes.Lockscreen))
+            assertWithMessage("Should start unoccluded").that(invisibleDueToOcclusion).isFalse()
+
+            // Actual testing starts here:
+            showOccludingActivity()
+            assertWithMessage("Should become occluded when occluding activity is shown")
+                .that(invisibleDueToOcclusion)
+                .isTrue()
+
+            transitionIntoAod {
+                assertWithMessage("Should become unoccluded when transitioning into AOD")
+                    .that(invisibleDueToOcclusion)
+                    .isFalse()
+            }
+            assertWithMessage("Should stay unoccluded when in AOD")
+                .that(invisibleDueToOcclusion)
+                .isFalse()
+
+            transitionOutOfAod {
+                assertWithMessage("Should remain unoccluded while transitioning away from AOD")
+                    .that(invisibleDueToOcclusion)
+                    .isFalse()
+            }
+            assertWithMessage("Should become occluded now that no longer in AOD")
+                .that(invisibleDueToOcclusion)
+                .isTrue()
+
+            expandShade {
+                assertWithMessage("Should become unoccluded once shade begins to expand")
+                    .that(invisibleDueToOcclusion)
+                    .isFalse()
+            }
+            assertWithMessage("Should be unoccluded when shade is fully expanded")
+                .that(invisibleDueToOcclusion)
+                .isFalse()
+
+            collapseShade {
+                assertWithMessage("Should remain unoccluded while shade is collapsing")
+                    .that(invisibleDueToOcclusion)
+                    .isFalse()
+            }
+            assertWithMessage("Should become occluded now that shade is fully collapsed")
+                .that(invisibleDueToOcclusion)
+                .isTrue()
+
+            hideOccludingActivity()
+            assertWithMessage("Should become unoccluded once the occluding activity is hidden")
+                .that(invisibleDueToOcclusion)
+                .isFalse()
+        }
+
+    /** Simulates the appearance of a show-when-locked `Activity` in the foreground. */
+    private fun TestScope.showOccludingActivity() {
+        keyguardOcclusionInteractor.setWmNotifiedShowWhenLockedActivityOnTop(
+            showWhenLockedActivityOnTop = true,
+            taskInfo = mock(),
+        )
+        runCurrent()
+    }
+
+    /** Simulates the disappearance of a show-when-locked `Activity` from the foreground. */
+    private fun TestScope.hideOccludingActivity() {
+        keyguardOcclusionInteractor.setWmNotifiedShowWhenLockedActivityOnTop(
+            showWhenLockedActivityOnTop = false,
+        )
+        runCurrent()
+    }
+
+    /** Simulates a user-driven gradual expansion of the shade. */
+    private fun TestScope.expandShade(
+        assertMidTransition: () -> Unit = {},
+    ) {
+        val progress = MutableStateFlow(0f)
+        mutableTransitionState.value =
+            ObservableTransitionState.Transition(
+                fromScene = sceneDataSource.currentScene.value,
+                toScene = Scenes.Shade,
+                progress = progress,
+                isInitiatedByUserInput = true,
+                isUserInputOngoing = flowOf(true),
+            )
+        runCurrent()
+
+        progress.value = 0.5f
+        runCurrent()
+        assertMidTransition()
+
+        progress.value = 1f
+        runCurrent()
+
+        mutableTransitionState.value = ObservableTransitionState.Idle(Scenes.Shade)
+        runCurrent()
+    }
+
+    /** Simulates a user-driven gradual collapse of the shade. */
+    private fun TestScope.collapseShade(
+        assertMidTransition: () -> Unit = {},
+    ) {
+        val progress = MutableStateFlow(0f)
+        mutableTransitionState.value =
+            ObservableTransitionState.Transition(
+                fromScene = Scenes.Shade,
+                toScene = Scenes.Lockscreen,
+                progress = progress,
+                isInitiatedByUserInput = true,
+                isUserInputOngoing = flowOf(true),
+            )
+        runCurrent()
+
+        progress.value = 0.5f
+        runCurrent()
+        assertMidTransition()
+
+        progress.value = 1f
+        runCurrent()
+
+        mutableTransitionState.value = ObservableTransitionState.Idle(Scenes.Lockscreen)
+        runCurrent()
+    }
+
+    /** Simulates a transition into AOD. */
+    private suspend fun TestScope.transitionIntoAod(
+        assertMidTransition: () -> Unit = {},
+    ) {
+        val currentKeyguardState = keyguardTransitionInteractor.getCurrentState()
+        keyguardTransitionRepository.sendTransitionStep(
+            TransitionStep(
+                from = currentKeyguardState,
+                to = KeyguardState.AOD,
+                value = 0f,
+                transitionState = TransitionState.STARTED,
+            )
+        )
+        runCurrent()
+
+        keyguardTransitionRepository.sendTransitionStep(
+            TransitionStep(
+                from = currentKeyguardState,
+                to = KeyguardState.AOD,
+                value = 0.5f,
+                transitionState = TransitionState.RUNNING,
+            )
+        )
+        runCurrent()
+        assertMidTransition()
+
+        keyguardTransitionRepository.sendTransitionStep(
+            TransitionStep(
+                from = currentKeyguardState,
+                to = KeyguardState.AOD,
+                value = 1f,
+                transitionState = TransitionState.FINISHED,
+            )
+        )
+        runCurrent()
+    }
+
+    /** Simulates a transition away from AOD. */
+    private suspend fun TestScope.transitionOutOfAod(
+        assertMidTransition: () -> Unit = {},
+    ) {
+        keyguardTransitionRepository.sendTransitionStep(
+            TransitionStep(
+                from = KeyguardState.AOD,
+                to = KeyguardState.LOCKSCREEN,
+                value = 0f,
+                transitionState = TransitionState.STARTED,
+            )
+        )
+        runCurrent()
+
+        keyguardTransitionRepository.sendTransitionStep(
+            TransitionStep(
+                from = KeyguardState.AOD,
+                to = KeyguardState.LOCKSCREEN,
+                value = 0.5f,
+                transitionState = TransitionState.RUNNING,
+            )
+        )
+        runCurrent()
+        assertMidTransition()
+
+        keyguardTransitionRepository.sendTransitionStep(
+            TransitionStep(
+                from = KeyguardState.AOD,
+                to = KeyguardState.LOCKSCREEN,
+                value = 1f,
+                transitionState = TransitionState.FINISHED,
+            )
+        )
+        runCurrent()
+    }
+}
diff --git a/packages/SystemUI/multivalentTests/src/com/android/systemui/scene/domain/startable/SceneContainerStartableTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/scene/domain/startable/SceneContainerStartableTest.kt
index 594543b..605e5c0 100644
--- a/packages/SystemUI/multivalentTests/src/com/android/systemui/scene/domain/startable/SceneContainerStartableTest.kt
+++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/scene/domain/startable/SceneContainerStartableTest.kt
@@ -46,11 +46,13 @@
 import com.android.systemui.power.domain.interactor.PowerInteractor.Companion.setAsleepForTest
 import com.android.systemui.power.domain.interactor.PowerInteractor.Companion.setAwakeForTest
 import com.android.systemui.power.domain.interactor.PowerInteractorFactory
+import com.android.systemui.scene.domain.interactor.sceneContainerOcclusionInteractor
 import com.android.systemui.scene.domain.interactor.sceneInteractor
 import com.android.systemui.scene.shared.flag.fakeSceneContainerFlags
 import com.android.systemui.scene.shared.model.Scenes
 import com.android.systemui.scene.shared.model.fakeSceneDataSource
 import com.android.systemui.statusbar.NotificationShadeWindowController
+import com.android.systemui.statusbar.domain.interactor.keyguardOcclusionInteractor
 import com.android.systemui.statusbar.notification.data.repository.FakeHeadsUpRowRepository
 import com.android.systemui.statusbar.notification.data.repository.HeadsUpRowRepository
 import com.android.systemui.statusbar.notification.stack.data.repository.headsUpNotificationRepository
@@ -128,6 +130,7 @@
                 deviceProvisioningInteractor = kosmos.deviceProvisioningInteractor,
                 centralSurfaces = centralSurfaces,
                 headsUpInteractor = kosmos.headsUpNotificationInteractor,
+                occlusionInteractor = kosmos.sceneContainerOcclusionInteractor,
             )
     }
 
@@ -204,6 +207,28 @@
         }
 
     @Test
+    fun hydrateVisibility_basedOnOcclusion() =
+        testScope.runTest {
+            val isVisible by collectLastValue(sceneInteractor.isVisible)
+            prepareState(
+                isDeviceUnlocked = true,
+                initialSceneKey = Scenes.Lockscreen,
+            )
+
+            underTest.start()
+            assertThat(isVisible).isTrue()
+
+            kosmos.keyguardOcclusionInteractor.setWmNotifiedShowWhenLockedActivityOnTop(
+                true,
+                mock()
+            )
+            assertThat(isVisible).isFalse()
+
+            kosmos.keyguardOcclusionInteractor.setWmNotifiedShowWhenLockedActivityOnTop(false)
+            assertThat(isVisible).isTrue()
+        }
+
+    @Test
     fun startsInLockscreenScene() =
         testScope.runTest {
             val currentSceneKey by collectLastValue(sceneInteractor.currentScene)
diff --git a/packages/SystemUI/multivalentTests/src/com/android/systemui/shade/domain/interactor/PanelExpansionInteractorImplTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/shade/domain/interactor/PanelExpansionInteractorImplTest.kt
index 16b68cc..ad40f8e 100644
--- a/packages/SystemUI/multivalentTests/src/com/android/systemui/shade/domain/interactor/PanelExpansionInteractorImplTest.kt
+++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/shade/domain/interactor/PanelExpansionInteractorImplTest.kt
@@ -52,6 +52,7 @@
     private val deviceEntryRepository = kosmos.fakeDeviceEntryRepository
     private val deviceUnlockedInteractor = kosmos.deviceUnlockedInteractor
     private val sceneInteractor = kosmos.sceneInteractor
+    private val shadeAnimationInteractor = kosmos.shadeAnimationInteractor
     private val transitionState =
         MutableStateFlow<ObservableTransitionState>(
             ObservableTransitionState.Idle(Scenes.Lockscreen)
@@ -112,6 +113,40 @@
             changeScene(Scenes.Communal) { assertThat(panelExpansion).isEqualTo(1f) }
             assertThat(panelExpansion).isEqualTo(1f)
         }
+
+    @Test
+    @EnableSceneContainer
+    fun shouldHideStatusBarIconsWhenExpanded_goneScene() =
+        testScope.runTest {
+            underTest = kosmos.panelExpansionInteractorImpl
+            shadeAnimationInteractor.setIsLaunchingActivity(false)
+            changeScene(Scenes.Gone)
+
+            assertThat(underTest.shouldHideStatusBarIconsWhenExpanded()).isFalse()
+        }
+
+    @Test
+    @EnableSceneContainer
+    fun shouldHideStatusBarIconsWhenExpanded_lockscreenScene() =
+        testScope.runTest {
+            underTest = kosmos.panelExpansionInteractorImpl
+            shadeAnimationInteractor.setIsLaunchingActivity(false)
+            changeScene(Scenes.Lockscreen)
+
+            assertThat(underTest.shouldHideStatusBarIconsWhenExpanded()).isTrue()
+        }
+
+    @Test
+    @EnableSceneContainer
+    fun shouldHideStatusBarIconsWhenExpanded_activityLaunch() =
+        testScope.runTest {
+            underTest = kosmos.panelExpansionInteractorImpl
+            changeScene(Scenes.Gone)
+            shadeAnimationInteractor.setIsLaunchingActivity(true)
+
+            assertThat(underTest.shouldHideStatusBarIconsWhenExpanded()).isFalse()
+        }
+
     private fun TestScope.setUnlocked(isUnlocked: Boolean) {
         val isDeviceUnlocked by collectLastValue(deviceUnlockedInteractor.isDeviceUnlocked)
         deviceEntryRepository.setUnlocked(isUnlocked)
diff --git a/packages/SystemUI/multivalentTests/src/com/android/systemui/shade/domain/startable/ShadeStartableTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/shade/domain/startable/ShadeStartableTest.kt
index 31dacdd..52caa78 100644
--- a/packages/SystemUI/multivalentTests/src/com/android/systemui/shade/domain/startable/ShadeStartableTest.kt
+++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/shade/domain/startable/ShadeStartableTest.kt
@@ -18,15 +18,32 @@
 
 import androidx.test.ext.junit.runners.AndroidJUnit4
 import androidx.test.filters.SmallTest
+import com.android.compose.animation.scene.ObservableTransitionState
+import com.android.compose.animation.scene.SceneKey
 import com.android.systemui.SysuiTestCase
 import com.android.systemui.common.ui.data.repository.fakeConfigurationRepository
 import com.android.systemui.coroutines.collectLastValue
+import com.android.systemui.deviceentry.data.repository.fakeDeviceEntryRepository
+import com.android.systemui.deviceentry.domain.interactor.deviceUnlockedInteractor
+import com.android.systemui.flags.EnableSceneContainer
 import com.android.systemui.kosmos.testScope
 import com.android.systemui.res.R
+import com.android.systemui.scene.domain.interactor.sceneInteractor
+import com.android.systemui.scene.shared.model.Scenes
+import com.android.systemui.scene.shared.model.fakeSceneDataSource
+import com.android.systemui.shade.ShadeExpansionChangeEvent
+import com.android.systemui.shade.ShadeExpansionListener
 import com.android.systemui.shade.domain.interactor.shadeInteractor
 import com.android.systemui.shade.shared.model.ShadeMode
 import com.android.systemui.testKosmos
+import com.android.systemui.util.mockito.any
+import com.android.systemui.util.mockito.mock
+import com.android.systemui.util.mockito.whenever
 import com.google.common.truth.Truth.assertThat
+import kotlinx.coroutines.flow.MutableStateFlow
+import kotlinx.coroutines.flow.flowOf
+import kotlinx.coroutines.test.TestScope
+import kotlinx.coroutines.test.runCurrent
 import kotlinx.coroutines.test.runTest
 import org.junit.Test
 import org.junit.runner.RunWith
@@ -34,11 +51,15 @@
 @SmallTest
 @RunWith(AndroidJUnit4::class)
 class ShadeStartableTest : SysuiTestCase() {
-
     private val kosmos = testKosmos()
     private val testScope = kosmos.testScope
     private val shadeInteractor = kosmos.shadeInteractor
+    private val sceneInteractor = kosmos.sceneInteractor
+    private val shadeExpansionStateManager = kosmos.shadeExpansionStateManager
+    private val deviceEntryRepository = kosmos.fakeDeviceEntryRepository
+    private val deviceUnlockedInteractor = kosmos.deviceUnlockedInteractor
     private val fakeConfigurationRepository = kosmos.fakeConfigurationRepository
+    private val fakeSceneDataSource = kosmos.fakeSceneDataSource
 
     private val underTest = kosmos.shadeStartable
 
@@ -59,4 +80,89 @@
             fakeConfigurationRepository.onAnyConfigurationChange()
             assertThat(shadeMode).isEqualTo(ShadeMode.Single)
         }
+
+    @Test
+    @EnableSceneContainer
+    fun hydrateShadeExpansionStateManager() =
+        testScope.runTest {
+            val expansionListener = mock<ShadeExpansionListener>()
+            var latestChangeEvent: ShadeExpansionChangeEvent? = null
+            whenever(expansionListener.onPanelExpansionChanged(any())).thenAnswer {
+                latestChangeEvent = it.arguments[0] as ShadeExpansionChangeEvent
+                Unit
+            }
+            shadeExpansionStateManager.addExpansionListener(expansionListener)
+
+            underTest.start()
+
+            setUnlocked(true)
+            val transitionState =
+                MutableStateFlow<ObservableTransitionState>(
+                    ObservableTransitionState.Idle(Scenes.Gone)
+                )
+            sceneInteractor.setTransitionState(transitionState)
+
+            changeScene(Scenes.Gone, transitionState)
+            val currentScene by collectLastValue(sceneInteractor.currentScene)
+            assertThat(currentScene).isEqualTo(Scenes.Gone)
+
+            assertThat(latestChangeEvent)
+                .isEqualTo(
+                    ShadeExpansionChangeEvent(
+                        fraction = 0f,
+                        expanded = false,
+                        tracking = false,
+                    )
+                )
+
+            changeScene(Scenes.Shade, transitionState) { progress ->
+                assertThat(latestChangeEvent?.fraction).isEqualTo(progress)
+            }
+        }
+
+    private fun TestScope.setUnlocked(isUnlocked: Boolean) {
+        val isDeviceUnlocked by collectLastValue(deviceUnlockedInteractor.isDeviceUnlocked)
+        deviceEntryRepository.setUnlocked(isUnlocked)
+        runCurrent()
+
+        assertThat(isDeviceUnlocked).isEqualTo(isUnlocked)
+    }
+
+    private fun TestScope.changeScene(
+        toScene: SceneKey,
+        transitionState: MutableStateFlow<ObservableTransitionState>,
+        assertDuringProgress: ((progress: Float) -> Unit) = {},
+    ) {
+        val currentScene by collectLastValue(sceneInteractor.currentScene)
+        val progressFlow = MutableStateFlow(0f)
+        transitionState.value =
+            ObservableTransitionState.Transition(
+                fromScene = checkNotNull(currentScene),
+                toScene = toScene,
+                progress = progressFlow,
+                isInitiatedByUserInput = true,
+                isUserInputOngoing = flowOf(true),
+            )
+        runCurrent()
+        assertDuringProgress(progressFlow.value)
+
+        progressFlow.value = 0.2f
+        runCurrent()
+        assertDuringProgress(progressFlow.value)
+
+        progressFlow.value = 0.6f
+        runCurrent()
+        assertDuringProgress(progressFlow.value)
+
+        progressFlow.value = 1f
+        runCurrent()
+        assertDuringProgress(progressFlow.value)
+
+        transitionState.value = ObservableTransitionState.Idle(toScene)
+        fakeSceneDataSource.changeScene(toScene)
+        runCurrent()
+        assertDuringProgress(progressFlow.value)
+
+        assertThat(currentScene).isEqualTo(toScene)
+    }
 }
diff --git a/packages/SystemUI/multivalentTests/src/com/android/systemui/volume/domain/interactor/AudioVolumeInteractorTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/volume/domain/interactor/AudioVolumeInteractorTest.kt
index 5358a6d..fa79e7f 100644
--- a/packages/SystemUI/multivalentTests/src/com/android/systemui/volume/domain/interactor/AudioVolumeInteractorTest.kt
+++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/volume/domain/interactor/AudioVolumeInteractorTest.kt
@@ -201,11 +201,38 @@
     }
 
     @Test
-    fun alarmStream_isNotMutable() {
+    fun streamNotAffectedByMute_isNotMutable() {
         with(kosmos) {
-            val isMutable = underTest.isMutable(AudioStream(AudioManager.STREAM_ALARM))
+            testScope.runTest {
+                audioRepository.setIsAffectedByMute(audioStream, false)
+                val isMutable = underTest.isAffectedByMute(audioStream)
 
-            assertThat(isMutable).isFalse()
+                assertThat(isMutable).isFalse()
+            }
+        }
+    }
+
+    @Test
+    fun muteRingerStream_ringerMode_vibrate() {
+        with(kosmos) {
+            testScope.runTest {
+                val ringerMode by collectLastValue(audioRepository.ringerMode)
+                underTest.setMuted(AudioStream(AudioManager.STREAM_RING), true)
+
+                assertThat(ringerMode).isEqualTo(RingerMode(AudioManager.RINGER_MODE_VIBRATE))
+            }
+        }
+    }
+
+    @Test
+    fun unMuteRingerStream_ringerMode_normal() {
+        with(kosmos) {
+            testScope.runTest {
+                val ringerMode by collectLastValue(audioRepository.ringerMode)
+                underTest.setMuted(AudioStream(AudioManager.STREAM_RING), false)
+
+                assertThat(ringerMode).isEqualTo(RingerMode(AudioManager.RINGER_MODE_NORMAL))
+            }
         }
     }
 
diff --git a/packages/SystemUI/multivalentTests/src/com/android/systemui/volume/panel/component/bottombar/ui/viewmodel/BottomBarViewModelTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/volume/panel/component/bottombar/ui/viewmodel/BottomBarViewModelTest.kt
index 8e92557..2cc1ad3 100644
--- a/packages/SystemUI/multivalentTests/src/com/android/systemui/volume/panel/component/bottombar/ui/viewmodel/BottomBarViewModelTest.kt
+++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/volume/panel/component/bottombar/ui/viewmodel/BottomBarViewModelTest.kt
@@ -84,8 +84,17 @@
 
                 runCurrent()
 
-                verify(activityStarter).startActivity(capture(intentCaptor), eq(true),
-                        capture(activityStartedCaptor))
+                verify(activityStarter)
+                    .startActivityDismissingKeyguard(
+                        /* intent = */ capture(intentCaptor),
+                        /* onlyProvisioned = */ eq(false),
+                        /* dismissShade = */ eq(true),
+                        /* disallowEnterPictureInPictureWhileLaunching = */ eq(false),
+                        /* callback = */ capture(activityStartedCaptor),
+                        /* flags = */ eq(Intent.FLAG_ACTIVITY_REORDER_TO_FRONT),
+                        /* animationController = */ eq(null),
+                        /* userHandle = */ eq(null),
+                    )
                 assertThat(intentCaptor.value.action).isEqualTo(Settings.ACTION_SOUND_SETTINGS)
 
                 activityStartedCaptor.value.onActivityStarted(ActivityManager.START_SUCCESS)
diff --git a/packages/SystemUI/res/drawable/accessibility_fullscreen_magnification_border_background.xml b/packages/SystemUI/res/drawable/accessibility_fullscreen_magnification_border_background.xml
new file mode 100644
index 0000000..edfbe7b
--- /dev/null
+++ b/packages/SystemUI/res/drawable/accessibility_fullscreen_magnification_border_background.xml
@@ -0,0 +1,25 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!--
+  ~ Copyright (C) 2024 The Android Open Source Project
+  ~
+  ~ Licensed under the Apache License, Version 2.0 (the "License");
+  ~ you may not use this file except in compliance with the License.
+  ~ You may obtain a copy of the License at
+  ~
+  ~      http://www.apache.org/licenses/LICENSE-2.0
+  ~
+  ~ Unless required by applicable law or agreed to in writing, software
+  ~ distributed under the License is distributed on an "AS IS" BASIS,
+  ~ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+  ~ See the License for the specific language governing permissions and
+  ~ limitations under the License.
+  -->
+<shape xmlns:android="http://schemas.android.com/apk/res/android"
+       android:shape="rectangle">
+    <corners android:radius="@*android:dimen/rounded_corner_radius" />
+    <!-- Since the device corners are not perfectly rounded, we create the stroke with offset
+         to fill up the space between border and device corner -->
+    <stroke
+        android:color="@color/magnification_border_color"
+        android:width="@dimen/magnifier_border_width_fullscreen_with_offset"/>
+</shape>
\ No newline at end of file
diff --git a/packages/SystemUI/res/drawable/qs_hearing_devices_icon.xml b/packages/SystemUI/res/drawable/qs_hearing_devices_icon.xml
new file mode 100644
index 0000000..c1573a3
--- /dev/null
+++ b/packages/SystemUI/res/drawable/qs_hearing_devices_icon.xml
@@ -0,0 +1,25 @@
+<!--
+    Copyright (C) 2024 The Android Open Source Project
+
+    Licensed under the Apache License, Version 2.0 (the "License");
+    you may not use this file except in compliance with the License.
+    You may obtain a copy of the License at
+
+         http://www.apache.org/licenses/LICENSE-2.0
+
+    Unless required by applicable law or agreed to in writing, software
+    distributed under the License is distributed on an "AS IS" BASIS,
+    WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+    See the License for the specific language governing permissions and
+    limitations under the License.
+-->
+
+<vector xmlns:android="http://schemas.android.com/apk/res/android"
+    android:width="18dp"
+    android:height="18dp"
+    android:viewportWidth="24"
+    android:viewportHeight="24">
+    <path
+        android:fillColor="#ffffff"
+        android:pathData="M17,20c-0.29,0 -0.56,-0.06 -0.76,-0.15 -0.71,-0.37 -1.21,-0.88 -1.71,-2.38 -0.51,-1.56 -1.47,-2.29 -2.39,-3 -0.79,-0.61 -1.61,-1.24 -2.32,-2.53C9.29,10.98 9,9.93 9,9c0,-2.8 2.2,-5 5,-5s5,2.2 5,5h2c0,-3.93 -3.07,-7 -7,-7S7,5.07 7,9c0,1.26 0.38,2.65 1.07,3.9 0.91,1.65 1.98,2.48 2.85,3.15 0.81,0.62 1.39,1.07 1.71,2.05 0.6,1.82 1.37,2.84 2.73,3.55 0.51,0.23 1.07,0.35 1.64,0.35 2.21,0 4,-1.79 4,-4h-2c0,1.1 -0.9,2 -2,2zM7.64,2.64L6.22,1.22C4.23,3.21 3,5.96 3,9s1.23,5.79 3.22,7.78l1.41,-1.41C6.01,13.74 5,11.49 5,9s1.01,-4.74 2.64,-6.36zM11.5,9c0,1.38 1.12,2.5 2.5,2.5s2.5,-1.12 2.5,-2.5 -1.12,-2.5 -2.5,-2.5 -2.5,1.12 -2.5,2.5z"/>
+</vector>
\ No newline at end of file
diff --git a/packages/SystemUI/res/layout/fullscreen_magnification_border.xml b/packages/SystemUI/res/layout/fullscreen_magnification_border.xml
new file mode 100644
index 0000000..5f738c0
--- /dev/null
+++ b/packages/SystemUI/res/layout/fullscreen_magnification_border.xml
@@ -0,0 +1,22 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!--
+  ~ Copyright (C) 2024 The Android Open Source Project
+  ~
+  ~ Licensed under the Apache License, Version 2.0 (the "License");
+  ~ you may not use this file except in compliance with the License.
+  ~ You may obtain a copy of the License at
+  ~
+  ~      http://www.apache.org/licenses/LICENSE-2.0
+  ~
+  ~ Unless required by applicable law or agreed to in writing, software
+  ~ distributed under the License is distributed on an "AS IS" BASIS,
+  ~ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+  ~ See the License for the specific language governing permissions and
+  ~ limitations under the License.
+  -->
+<View xmlns:android="http://schemas.android.com/apk/res/android"
+    android:id="@+id/magnification_fullscreen_border"
+    android:layout_width="match_parent"
+    android:layout_height="match_parent"
+    android:focusable="false"
+    android:background="@drawable/accessibility_fullscreen_magnification_border_background"/>
\ No newline at end of file
diff --git a/packages/SystemUI/res/layout/global_actions_grid_lite.xml b/packages/SystemUI/res/layout/global_actions_grid_lite.xml
index a64c9ae..9a7108a 100644
--- a/packages/SystemUI/res/layout/global_actions_grid_lite.xml
+++ b/packages/SystemUI/res/layout/global_actions_grid_lite.xml
@@ -19,6 +19,7 @@
     android:id="@+id/global_actions_container"
     android:layout_width="match_parent"
     android:layout_height="match_parent"
+    android:clipChildren="false"
     android:gravity="center"
     android:layout_gravity="center">
   <com.android.systemui.globalactions.GlobalActionsLayoutLite
diff --git a/packages/SystemUI/res/layout/screenshot_shelf.xml b/packages/SystemUI/res/layout/screenshot_shelf.xml
index ef1a21f..c988b4a 100644
--- a/packages/SystemUI/res/layout/screenshot_shelf.xml
+++ b/packages/SystemUI/res/layout/screenshot_shelf.xml
@@ -28,7 +28,7 @@
         android:elevation="4dp"
         android:background="@drawable/action_chip_container_background"
         android:layout_marginStart="@dimen/overlay_action_container_margin_horizontal"
-        android:layout_marginBottom="@dimen/overlay_action_container_margin_bottom"
+        android:layout_marginBottom="@dimen/screenshot_shelf_vertical_margin"
         app:layout_constraintStart_toStartOf="parent"
         app:layout_constraintTop_toTopOf="@+id/actions_container"
         app:layout_constraintEnd_toEndOf="@+id/actions_container"
@@ -38,14 +38,14 @@
         android:layout_width="0dp"
         android:layout_height="wrap_content"
         android:layout_marginEnd="@dimen/overlay_action_container_margin_horizontal"
-        android:paddingEnd="@dimen/overlay_action_container_padding_end"
+        android:paddingHorizontal="@dimen/overlay_action_container_padding_end"
         android:paddingVertical="@dimen/overlay_action_container_padding_vertical"
         android:elevation="4dp"
         android:scrollbars="none"
         app:layout_constraintHorizontal_bias="0"
         app:layout_constraintWidth_percent="1.0"
         app:layout_constraintWidth_max="wrap"
-        app:layout_constraintStart_toEndOf="@+id/screenshot_preview_border"
+        app:layout_constraintStart_toStartOf="parent"
         app:layout_constraintEnd_toEndOf="parent"
         app:layout_constraintBottom_toBottomOf="@id/actions_container_background">
         <LinearLayout
@@ -65,16 +65,16 @@
         android:id="@+id/screenshot_preview_border"
         android:layout_width="0dp"
         android:layout_height="0dp"
-        android:layout_marginStart="16dp"
+        android:layout_marginStart="@dimen/overlay_action_container_margin_horizontal"
         android:layout_marginTop="@dimen/overlay_border_width_neg"
         android:layout_marginEnd="@dimen/overlay_border_width_neg"
-        android:layout_marginBottom="14dp"
+        android:layout_marginBottom="@dimen/screenshot_shelf_vertical_margin"
         android:elevation="8dp"
         android:background="@drawable/overlay_border"
         app:layout_constraintStart_toStartOf="parent"
         app:layout_constraintTop_toTopOf="@id/screenshot_preview"
         app:layout_constraintEnd_toEndOf="@id/screenshot_preview"
-        app:layout_constraintBottom_toBottomOf="parent"/>
+        app:layout_constraintBottom_toTopOf="@id/actions_container"/>
     <ImageView
         android:id="@+id/screenshot_preview"
         android:layout_width="@dimen/overlay_x_scale"
diff --git a/packages/SystemUI/res/values/config.xml b/packages/SystemUI/res/values/config.xml
index 35f6a08..a6f6d4d 100644
--- a/packages/SystemUI/res/values/config.xml
+++ b/packages/SystemUI/res/values/config.xml
@@ -101,7 +101,7 @@
 
     <!-- Tiles native to System UI. Order should match "quick_settings_tiles_default" -->
     <string name="quick_settings_tiles_stock" translatable="false">
-        internet,bt,flashlight,dnd,alarm,airplane,controls,wallet,rotation,battery,cast,screenrecord,mictoggle,cameratoggle,location,hotspot,inversion,saver,dark,work,night,reverse,reduce_brightness,qr_code_scanner,onehanded,color_correction,dream,font_scaling,record_issue
+        internet,bt,flashlight,dnd,alarm,airplane,controls,wallet,rotation,battery,cast,screenrecord,mictoggle,cameratoggle,location,hotspot,inversion,saver,dark,work,night,reverse,reduce_brightness,qr_code_scanner,onehanded,color_correction,dream,font_scaling,record_issue,hearing_devices
     </string>
 
     <!-- The tiles to display in QuickSettings -->
diff --git a/packages/SystemUI/res/values/dimens.xml b/packages/SystemUI/res/values/dimens.xml
index e004ee9..d2efccd 100644
--- a/packages/SystemUI/res/values/dimens.xml
+++ b/packages/SystemUI/res/values/dimens.xml
@@ -448,6 +448,7 @@
     <dimen name="overlay_action_container_padding_end">8dp</dimen>
     <dimen name="overlay_dismiss_button_tappable_size">48dp</dimen>
     <dimen name="overlay_dismiss_button_margin">8dp</dimen>
+    <dimen name="screenshot_shelf_vertical_margin">8dp</dimen>
     <!-- must be kept aligned with overlay_border_width_neg, below;
          overlay_border_width = overlay_border_width_neg * -1 -->
     <dimen name="overlay_border_width">4dp</dimen>
@@ -1266,6 +1267,8 @@
     <dimen name="magnifier_corner_radius">28dp</dimen>
     <dimen name="magnifier_edit_corner_radius">16dp</dimen>
     <dimen name="magnifier_edit_outer_corner_radius">18dp</dimen>
+    <dimen name="magnifier_border_width_fullscreen_with_offset">12dp</dimen>
+    <dimen name="magnifier_border_width_fullscreen">6dp</dimen>
     <dimen name="magnifier_border_width">8dp</dimen>
     <dimen name="magnifier_stroke_width">2dp</dimen>
     <dimen name="magnifier_edit_dash_gap">20dp</dimen>
diff --git a/packages/SystemUI/res/values/strings.xml b/packages/SystemUI/res/values/strings.xml
index 2195849..b0d98e7 100644
--- a/packages/SystemUI/res/values/strings.xml
+++ b/packages/SystemUI/res/values/strings.xml
@@ -899,6 +899,9 @@
     <!-- QuickSettings: Contrast tile description: high [CHAR LIMIT=NONE] -->
     <string name="quick_settings_contrast_high">High</string>
 
+    <!-- QuickSettings: Hearing devices [CHAR LIMIT=NONE] -->
+    <string name="quick_settings_hearing_devices_label">Hearing devices</string>
+
     <!--- Title of dialog triggered if the microphone is disabled but an app tried to access it. [CHAR LIMIT=150] -->
     <string name="sensor_privacy_start_use_mic_dialog_title">Unblock device microphone?</string>
     <!--- Title of dialog triggered if the camera is disabled but an app tried to access it. [CHAR LIMIT=150] -->
diff --git a/packages/SystemUI/res/values/tiles_states_strings.xml b/packages/SystemUI/res/values/tiles_states_strings.xml
index 9036a35..ad09b46 100644
--- a/packages/SystemUI/res/values/tiles_states_strings.xml
+++ b/packages/SystemUI/res/values/tiles_states_strings.xml
@@ -338,4 +338,14 @@
         <item>Off</item>
         <item>On</item>
     </string-array>
+
+    <!-- State names for hearing devices tile: unavailable, off, on.
+         This subtitle is shown when the tile is in that particular state but does not set its own
+         subtitle, so some of these may never appear on screen. They should still be translated as
+         if they could appear. [CHAR LIMIT=32] -->
+    <string-array name="tile_states_hearing_devices">
+        <item>Unavailable</item>
+        <item>Off</item>
+        <item>On</item>
+    </string-array>
 </resources>
\ No newline at end of file
diff --git a/packages/SystemUI/src/com/android/keyguard/LockIconViewController.java b/packages/SystemUI/src/com/android/keyguard/LockIconViewController.java
index 8f1a5f7..985f6c8 100644
--- a/packages/SystemUI/src/com/android/keyguard/LockIconViewController.java
+++ b/packages/SystemUI/src/com/android/keyguard/LockIconViewController.java
@@ -454,6 +454,7 @@
         final float scaleFactor = mAuthController.getScaleFactor();
         final int scaledPadding = (int) (mDefaultPaddingPx * scaleFactor);
         if (KeyguardBottomAreaRefactor.isEnabled() || MigrateClocksToBlueprint.isEnabled()) {
+            // positioning in this case is handled by [DefaultDeviceEntrySection]
             mView.getLockIcon().setPadding(scaledPadding, scaledPadding, scaledPadding,
                     scaledPadding);
         } else {
diff --git a/packages/SystemUI/src/com/android/systemui/SystemUIService.java b/packages/SystemUI/src/com/android/systemui/SystemUIService.java
index b26be0c..0cc3be2 100644
--- a/packages/SystemUI/src/com/android/systemui/SystemUIService.java
+++ b/packages/SystemUI/src/com/android/systemui/SystemUIService.java
@@ -99,9 +99,10 @@
         if (Build.IS_DEBUGGABLE) {
             // b/71353150 - looking for leaked binder proxies
             BinderInternal.nSetBinderProxyCountEnabled(true);
-            BinderInternal.nSetBinderProxyCountWatermarks(1000,900);
+            BinderInternal.nSetBinderProxyCountWatermarks(
+                    /* high= */ 1000, /* low= */ 900, /* warning= */ 950);
             BinderInternal.setBinderProxyCountCallback(
-                    new BinderInternal.BinderProxyLimitListener() {
+                    new BinderInternal.BinderProxyCountEventListener() {
                         @Override
                         public void onLimitReached(int uid) {
                             Slog.w(SystemUIApplication.TAG,
diff --git a/packages/SystemUI/src/com/android/systemui/accessibility/FullscreenMagnificationController.java b/packages/SystemUI/src/com/android/systemui/accessibility/FullscreenMagnificationController.java
new file mode 100644
index 0000000..af8149f
--- /dev/null
+++ b/packages/SystemUI/src/com/android/systemui/accessibility/FullscreenMagnificationController.java
@@ -0,0 +1,140 @@
+/*
+ * Copyright (C) 2024 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.systemui.accessibility;
+
+import static android.view.WindowManager.LayoutParams;
+
+import android.annotation.UiContext;
+import android.content.Context;
+import android.graphics.PixelFormat;
+import android.graphics.Rect;
+import android.graphics.Region;
+import android.view.AttachedSurfaceControl;
+import android.view.LayoutInflater;
+import android.view.SurfaceControl;
+import android.view.SurfaceControlViewHost;
+import android.view.View;
+import android.view.WindowManager;
+import android.view.accessibility.AccessibilityManager;
+
+import androidx.annotation.UiThread;
+
+import com.android.systemui.res.R;
+
+import java.util.function.Supplier;
+
+class FullscreenMagnificationController {
+
+    private final Context mContext;
+    private final AccessibilityManager mAccessibilityManager;
+    private final WindowManager mWindowManager;
+    private Supplier<SurfaceControlViewHost> mScvhSupplier;
+    private SurfaceControlViewHost mSurfaceControlViewHost;
+    private Rect mWindowBounds;
+    private SurfaceControl.Transaction mTransaction;
+    private View mFullscreenBorder = null;
+    private int mBorderOffset;
+    private final int mDisplayId;
+    private static final Region sEmptyRegion = new Region();
+
+    FullscreenMagnificationController(
+            @UiContext Context context,
+            AccessibilityManager accessibilityManager,
+            WindowManager windowManager,
+            Supplier<SurfaceControlViewHost> scvhSupplier) {
+        mContext = context;
+        mAccessibilityManager = accessibilityManager;
+        mWindowManager = windowManager;
+        mWindowBounds = mWindowManager.getCurrentWindowMetrics().getBounds();
+        mTransaction = new SurfaceControl.Transaction();
+        mScvhSupplier = scvhSupplier;
+        mBorderOffset = mContext.getResources().getDimensionPixelSize(
+                R.dimen.magnifier_border_width_fullscreen_with_offset)
+                - mContext.getResources().getDimensionPixelSize(
+                R.dimen.magnifier_border_width_fullscreen);
+        mDisplayId = mContext.getDisplayId();
+    }
+
+    @UiThread
+    void onFullscreenMagnificationActivationChanged(boolean activated) {
+        if (activated) {
+            createFullscreenMagnificationBorder();
+        } else {
+            removeFullscreenMagnificationBorder();
+        }
+    }
+
+    @UiThread
+    private void removeFullscreenMagnificationBorder() {
+        if (mSurfaceControlViewHost != null) {
+            mSurfaceControlViewHost.release();
+            mSurfaceControlViewHost = null;
+        }
+
+        if (mFullscreenBorder != null) {
+            mFullscreenBorder = null;
+        }
+    }
+
+    /**
+     * Since the device corners are not perfectly rounded, we would like to create a thick stroke,
+     * and set negative offset to the border view to fill up the spaces between the border and the
+     * device corners.
+     */
+    @UiThread
+    private void createFullscreenMagnificationBorder() {
+        mFullscreenBorder = LayoutInflater.from(mContext)
+                .inflate(R.layout.fullscreen_magnification_border, null);
+        mSurfaceControlViewHost = mScvhSupplier.get();
+        mSurfaceControlViewHost.setView(mFullscreenBorder, getBorderLayoutParams());
+
+        SurfaceControl surfaceControl = mSurfaceControlViewHost
+                .getSurfacePackage().getSurfaceControl();
+
+        mTransaction
+                .setPosition(surfaceControl, -mBorderOffset, -mBorderOffset)
+                .setLayer(surfaceControl, Integer.MAX_VALUE)
+                .show(surfaceControl)
+                .apply();
+
+        mAccessibilityManager.attachAccessibilityOverlayToDisplay(mDisplayId, surfaceControl);
+
+        applyTouchableRegion();
+    }
+
+    private LayoutParams getBorderLayoutParams() {
+        LayoutParams params =  new LayoutParams(
+                mWindowBounds.width() + 2 * mBorderOffset,
+                mWindowBounds.height() + 2 * mBorderOffset,
+                LayoutParams.TYPE_ACCESSIBILITY_OVERLAY,
+                LayoutParams.FLAG_NOT_TOUCH_MODAL | LayoutParams.FLAG_NOT_FOCUSABLE,
+                PixelFormat.TRANSPARENT);
+        params.setTrustedOverlay();
+        return params;
+    }
+
+    private void applyTouchableRegion() {
+        // Sometimes this can get posted and run after deleteWindowMagnification() is called.
+        if (mFullscreenBorder == null) return;
+
+        AttachedSurfaceControl surfaceControl = mSurfaceControlViewHost.getRootSurfaceControl();
+
+        // The touchable region of the mFullscreenBorder will be empty since we are going to allow
+        // all touch events to go through this view.
+        surfaceControl.setTouchableRegion(sEmptyRegion);
+    }
+}
diff --git a/packages/SystemUI/src/com/android/systemui/accessibility/Magnification.java b/packages/SystemUI/src/com/android/systemui/accessibility/Magnification.java
index 88fa2de..70165f3 100644
--- a/packages/SystemUI/src/com/android/systemui/accessibility/Magnification.java
+++ b/packages/SystemUI/src/com/android/systemui/accessibility/Magnification.java
@@ -34,6 +34,7 @@
 import android.view.Display;
 import android.view.SurfaceControl;
 import android.view.SurfaceControlViewHost;
+import android.view.WindowManager;
 import android.view.WindowManagerGlobal;
 import android.view.accessibility.AccessibilityManager;
 import android.view.accessibility.IMagnificationConnection;
@@ -134,6 +135,37 @@
     @VisibleForTesting
     DisplayIdIndexSupplier<WindowMagnificationController> mWindowMagnificationControllerSupplier;
 
+    private static class FullscreenMagnificationControllerSupplier extends
+            DisplayIdIndexSupplier<FullscreenMagnificationController> {
+
+        private final Context mContext;
+
+        FullscreenMagnificationControllerSupplier(Context context, Handler handler,
+                DisplayManager displayManager, SysUiState sysUiState,
+                SecureSettings secureSettings) {
+            super(displayManager);
+            mContext = context;
+        }
+
+        @Override
+        protected FullscreenMagnificationController createInstance(Display display) {
+            final Context windowContext = mContext.createWindowContext(display,
+                    TYPE_ACCESSIBILITY_OVERLAY, /* options */ null);
+            Supplier<SurfaceControlViewHost> scvhSupplier = () -> new SurfaceControlViewHost(
+                    mContext, mContext.getDisplay(), new InputTransferToken(), TAG);
+            windowContext.setTheme(com.android.systemui.res.R.style.Theme_SystemUI);
+            return new FullscreenMagnificationController(
+                    windowContext,
+                    windowContext.getSystemService(AccessibilityManager.class),
+                    windowContext.getSystemService(WindowManager.class),
+                    scvhSupplier);
+        }
+    }
+
+    @VisibleForTesting
+    DisplayIdIndexSupplier<FullscreenMagnificationController>
+            mFullscreenMagnificationControllerSupplier;
+
     private static class SettingsSupplier extends
             DisplayIdIndexSupplier<MagnificationSettingsController> {
 
@@ -185,6 +217,8 @@
         mWindowMagnificationControllerSupplier = new WindowMagnificationControllerSupplier(context,
                 mHandler, mWindowMagnifierCallback,
                 displayManager, sysUiState, secureSettings);
+        mFullscreenMagnificationControllerSupplier = new FullscreenMagnificationControllerSupplier(
+                context, mHandler, displayManager, sysUiState, secureSettings);
         mMagnificationSettingsSupplier = new SettingsSupplier(context,
                 mMagnificationSettingsControllerCallback, displayManager, secureSettings);
 
@@ -273,8 +307,13 @@
         }
     }
 
+    @MainThread
     void onFullscreenMagnificationActivationChanged(int displayId, boolean activated) {
-        // Do nothing
+        final FullscreenMagnificationController fullscreenMagnificationController =
+                mFullscreenMagnificationControllerSupplier.get(displayId);
+        if (fullscreenMagnificationController != null) {
+            fullscreenMagnificationController.onFullscreenMagnificationActivationChanged(activated);
+        }
     }
 
     @MainThread
diff --git a/packages/SystemUI/src/com/android/systemui/accessibility/floatingmenu/MenuView.java b/packages/SystemUI/src/com/android/systemui/accessibility/floatingmenu/MenuView.java
index 1f5a0bf..be75e10 100644
--- a/packages/SystemUI/src/com/android/systemui/accessibility/floatingmenu/MenuView.java
+++ b/packages/SystemUI/src/com/android/systemui/accessibility/floatingmenu/MenuView.java
@@ -36,7 +36,6 @@
 import androidx.recyclerview.widget.RecyclerView;
 
 import com.android.internal.accessibility.dialog.AccessibilityTarget;
-import com.android.internal.annotations.VisibleForTesting;
 import com.android.modules.expresslog.Counter;
 import com.android.systemui.Flags;
 import com.android.systemui.util.settings.SecureSettings;
@@ -418,18 +417,11 @@
         onPositionChanged();
     }
 
-    void incrementTexMetricForAllTargets(String metric) {
+    void incrementTexMetric(String metric) {
         if (!Flags.floatingMenuDragToEdit()) {
             return;
         }
-        for (AccessibilityTarget target : mTargetFeatures) {
-            incrementTexMetric(metric, target.getUid());
-        }
-    }
-
-    @VisibleForTesting
-    void incrementTexMetric(String metric, int uid) {
-        Counter.logIncrementWithUid(metric, uid);
+        Counter.logIncrement(metric);
     }
 
     private InstantInsetLayerDrawable getContainerViewInsetLayer() {
diff --git a/packages/SystemUI/src/com/android/systemui/accessibility/floatingmenu/MenuViewLayer.java b/packages/SystemUI/src/com/android/systemui/accessibility/floatingmenu/MenuViewLayer.java
index 85bf784..86279be 100644
--- a/packages/SystemUI/src/com/android/systemui/accessibility/floatingmenu/MenuViewLayer.java
+++ b/packages/SystemUI/src/com/android/systemui/accessibility/floatingmenu/MenuViewLayer.java
@@ -105,14 +105,14 @@
      *
      * <p>Defined in frameworks/proto_logging/stats/express/catalog/accessibility.cfg.
      */
-    static final String TEX_METRIC_DISMISS = "accessibility.value_fab_shortcut_action_dismiss";
+    static final String TEX_METRIC_DISMISS = "accessibility.value_fab_shortcut_dismiss";
 
     /**
      * Counter indicating the FAB was dragged to the Edit action button.
      *
      * <p>Defined in frameworks/proto_logging/stats/express/catalog/accessibility.cfg.
      */
-    static final String TEX_METRIC_EDIT = "accessibility.value_fab_shortcut_action_edit";
+    static final String TEX_METRIC_EDIT = "accessibility.value_fab_shortcut_edit";
 
     private final WindowManager mWindowManager;
     private final MenuView mMenuView;
@@ -492,11 +492,11 @@
             } else {
                 hideMenuAndShowMessage();
             }
-            mMenuView.incrementTexMetricForAllTargets(TEX_METRIC_DISMISS);
+            mMenuView.incrementTexMetric(TEX_METRIC_DISMISS);
         } else if (id == R.id.action_edit
                 && Flags.floatingMenuDragToEdit()) {
             gotoEditScreen();
-            mMenuView.incrementTexMetricForAllTargets(TEX_METRIC_EDIT);
+            mMenuView.incrementTexMetric(TEX_METRIC_EDIT);
         }
         mDismissView.hide();
         mDragToInteractView.hide();
diff --git a/packages/SystemUI/src/com/android/systemui/accessibility/hearingaid/HearingDevicesDialogDelegate.java b/packages/SystemUI/src/com/android/systemui/accessibility/hearingaid/HearingDevicesDialogDelegate.java
new file mode 100644
index 0000000..4733d06
--- /dev/null
+++ b/packages/SystemUI/src/com/android/systemui/accessibility/hearingaid/HearingDevicesDialogDelegate.java
@@ -0,0 +1,57 @@
+/*
+ * Copyright (C) 2024 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.systemui.accessibility.hearingaid;
+
+import com.android.systemui.statusbar.phone.SystemUIDialog;
+
+import dagger.assisted.AssistedFactory;
+import dagger.assisted.AssistedInject;
+
+/**
+ * Dialog for showing hearing devices controls.
+ */
+public class HearingDevicesDialogDelegate implements SystemUIDialog.Delegate{
+
+    private final SystemUIDialog.Factory mSystemUIDialogFactory;
+
+    private SystemUIDialog mDialog;
+
+    /** Factory to create a {@link HearingDevicesDialogDelegate} dialog instance. */
+    @AssistedFactory
+    public interface Factory {
+        /** Create a {@link HearingDevicesDialogDelegate} instance */
+        HearingDevicesDialogDelegate create();
+    }
+
+    @AssistedInject
+    public HearingDevicesDialogDelegate(
+            SystemUIDialog.Factory systemUIDialogFactory) {
+        mSystemUIDialogFactory = systemUIDialogFactory;
+    }
+
+    @Override
+    public SystemUIDialog createDialog() {
+        SystemUIDialog dialog = mSystemUIDialogFactory.create(this);
+
+        if (mDialog != null) {
+            mDialog.dismiss();
+        }
+        mDialog = dialog;
+
+        return dialog;
+    }
+}
diff --git a/packages/SystemUI/src/com/android/systemui/accessibility/hearingaid/HearingDevicesDialogManager.java b/packages/SystemUI/src/com/android/systemui/accessibility/hearingaid/HearingDevicesDialogManager.java
new file mode 100644
index 0000000..c83043e
--- /dev/null
+++ b/packages/SystemUI/src/com/android/systemui/accessibility/hearingaid/HearingDevicesDialogManager.java
@@ -0,0 +1,79 @@
+/*
+ * Copyright (C) 2024 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.systemui.accessibility.hearingaid;
+
+import android.util.Log;
+import android.view.View;
+
+import com.android.internal.jank.InteractionJankMonitor;
+import com.android.systemui.animation.DialogCuj;
+import com.android.systemui.animation.DialogTransitionAnimator;
+import com.android.systemui.dagger.SysUISingleton;
+import com.android.systemui.statusbar.phone.SystemUIDialog;
+
+import javax.inject.Inject;
+
+/**
+ * Factory to create {@link HearingDevicesDialogDelegate} objects and manage its lifecycle.
+ */
+@SysUISingleton
+public class HearingDevicesDialogManager {
+
+    private static final boolean DEBUG = true;
+    private static final String TAG = "HearingDevicesDialogManager";
+    private static final String INTERACTION_JANK_TAG = "hearing_devices_tile";
+    private SystemUIDialog mDialog;
+    private final DialogTransitionAnimator mDialogTransitionAnimator;
+    private final HearingDevicesDialogDelegate.Factory mDialogFactory;
+
+    @Inject
+    public HearingDevicesDialogManager(
+            DialogTransitionAnimator dialogTransitionAnimator,
+            HearingDevicesDialogDelegate.Factory dialogFactory) {
+        mDialogTransitionAnimator = dialogTransitionAnimator;
+        mDialogFactory = dialogFactory;
+    }
+
+    /**
+     * Shows the dialog.
+     *
+     * @param view The view from which the dialog is shown.
+     */
+    public void showDialog(View view) {
+        if (mDialog != null) {
+            if (DEBUG) {
+                Log.d(TAG, "HearingDevicesDialog already showing. Destroy it first.");
+            }
+            destroyDialog();
+        }
+        mDialog = mDialogFactory.create().createDialog();
+
+        if (view != null) {
+            mDialogTransitionAnimator.showFromView(mDialog, view,
+                    new DialogCuj(InteractionJankMonitor.CUJ_SHADE_DIALOG_OPEN,
+                            INTERACTION_JANK_TAG),
+                    true);
+        } else {
+            mDialog.show();
+        }
+    }
+
+    private void destroyDialog() {
+        mDialog.dismiss();
+        mDialog = null;
+    }
+}
diff --git a/packages/SystemUI/src/com/android/systemui/accessibility/qs/QSAccessibilityModule.kt b/packages/SystemUI/src/com/android/systemui/accessibility/qs/QSAccessibilityModule.kt
index 4047623..7cb028a 100644
--- a/packages/SystemUI/src/com/android/systemui/accessibility/qs/QSAccessibilityModule.kt
+++ b/packages/SystemUI/src/com/android/systemui/accessibility/qs/QSAccessibilityModule.kt
@@ -23,6 +23,7 @@
 import com.android.systemui.qs.tiles.ColorInversionTile
 import com.android.systemui.qs.tiles.DreamTile
 import com.android.systemui.qs.tiles.FontScalingTile
+import com.android.systemui.qs.tiles.HearingDevicesTile
 import com.android.systemui.qs.tiles.NightDisplayTile
 import com.android.systemui.qs.tiles.OneHandedModeTile
 import com.android.systemui.qs.tiles.ReduceBrightColorsTile
@@ -94,6 +95,12 @@
     @StringKey(FontScalingTile.TILE_SPEC)
     fun bindFontScalingTile(fontScalingTile: FontScalingTile): QSTileImpl<*>
 
+    /** Inject HearingDevicesTile into tileMap in QSModule */
+    @Binds
+    @IntoMap
+    @StringKey(HearingDevicesTile.TILE_SPEC)
+    fun bindHearingDevicesTile(hearingDevicesTile: HearingDevicesTile): QSTileImpl<*>
+
     companion object {
         const val COLOR_CORRECTION_TILE_SPEC = "color_correction"
         const val COLOR_INVERSION_TILE_SPEC = "inversion"
diff --git a/packages/SystemUI/src/com/android/systemui/communal/CommunalSceneStartable.kt b/packages/SystemUI/src/com/android/systemui/communal/CommunalSceneStartable.kt
index ef686f9..4d328d6 100644
--- a/packages/SystemUI/src/com/android/systemui/communal/CommunalSceneStartable.kt
+++ b/packages/SystemUI/src/com/android/systemui/communal/CommunalSceneStartable.kt
@@ -72,7 +72,7 @@
             .filterNotNull()
             // TODO(b/322787129): Also set a custom transition animation here to avoid the regular
             // slide-in animation when setting the scene programmatically
-            .onEach { nextScene -> communalInteractor.onSceneChanged(nextScene) }
+            .onEach { nextScene -> communalInteractor.changeScene(nextScene) }
             .launchIn(applicationScope)
 
         // TODO(b/322787129): re-enable once custom animations are in place
@@ -129,7 +129,7 @@
                 .sample(keyguardInteractor.isDreaming, ::Pair)
                 .collect { (shouldTimeout, isDreaming) ->
                     if (isDreaming && shouldTimeout) {
-                        communalInteractor.onSceneChanged(CommunalScenes.Blank)
+                        communalInteractor.changeScene(CommunalScenes.Blank)
                     }
                 }
         }
diff --git a/packages/SystemUI/src/com/android/systemui/communal/dagger/Communal.kt b/packages/SystemUI/src/com/android/systemui/communal/dagger/Communal.kt
new file mode 100644
index 0000000..5e41a1b
--- /dev/null
+++ b/packages/SystemUI/src/com/android/systemui/communal/dagger/Communal.kt
@@ -0,0 +1,21 @@
+/*
+ * Copyright (C) 2024 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.systemui.communal.dagger
+
+import javax.inject.Qualifier
+
+@Qualifier @MustBeDocumented @Retention(AnnotationRetention.RUNTIME) annotation class Communal
diff --git a/packages/SystemUI/src/com/android/systemui/communal/dagger/CommunalModule.kt b/packages/SystemUI/src/com/android/systemui/communal/dagger/CommunalModule.kt
index 82d9437..72dcb26 100644
--- a/packages/SystemUI/src/com/android/systemui/communal/dagger/CommunalModule.kt
+++ b/packages/SystemUI/src/com/android/systemui/communal/dagger/CommunalModule.kt
@@ -23,11 +23,19 @@
 import com.android.systemui.communal.data.repository.CommunalSettingsRepositoryModule
 import com.android.systemui.communal.data.repository.CommunalTutorialRepositoryModule
 import com.android.systemui.communal.data.repository.CommunalWidgetRepositoryModule
+import com.android.systemui.communal.shared.model.CommunalScenes
 import com.android.systemui.communal.widgets.CommunalWidgetModule
 import com.android.systemui.communal.widgets.EditWidgetsActivityStarter
 import com.android.systemui.communal.widgets.EditWidgetsActivityStarterImpl
+import com.android.systemui.dagger.SysUISingleton
+import com.android.systemui.dagger.qualifiers.Application
+import com.android.systemui.scene.shared.model.SceneContainerConfig
+import com.android.systemui.scene.shared.model.SceneDataSource
+import com.android.systemui.scene.shared.model.SceneDataSourceDelegator
 import dagger.Binds
 import dagger.Module
+import dagger.Provides
+import kotlinx.coroutines.CoroutineScope
 
 @Module(
     includes =
@@ -47,4 +55,24 @@
     fun bindEditWidgetsActivityStarter(
         starter: EditWidgetsActivityStarterImpl
     ): EditWidgetsActivityStarter
+
+    @Binds
+    @Communal
+    fun bindCommunalSceneDataSource(@Communal delegator: SceneDataSourceDelegator): SceneDataSource
+
+    companion object {
+        @Provides
+        @Communal
+        @SysUISingleton
+        fun providesCommunalSceneDataSourceDelegator(
+            @Application applicationScope: CoroutineScope
+        ): SceneDataSourceDelegator {
+            val config =
+                SceneContainerConfig(
+                    sceneKeys = listOf(CommunalScenes.Blank, CommunalScenes.Communal),
+                    initialSceneKey = CommunalScenes.Blank
+                )
+            return SceneDataSourceDelegator(applicationScope, config)
+        }
+    }
 }
diff --git a/packages/SystemUI/src/com/android/systemui/communal/data/repository/CommunalRepository.kt b/packages/SystemUI/src/com/android/systemui/communal/data/repository/CommunalRepository.kt
index 201ce83..8bfd8d9 100644
--- a/packages/SystemUI/src/com/android/systemui/communal/data/repository/CommunalRepository.kt
+++ b/packages/SystemUI/src/com/android/systemui/communal/data/repository/CommunalRepository.kt
@@ -18,11 +18,12 @@
 
 import com.android.compose.animation.scene.ObservableTransitionState
 import com.android.compose.animation.scene.SceneKey
+import com.android.compose.animation.scene.TransitionKey
+import com.android.systemui.communal.dagger.Communal
 import com.android.systemui.communal.shared.model.CommunalScenes
 import com.android.systemui.dagger.SysUISingleton
 import com.android.systemui.dagger.qualifiers.Background
-import com.android.systemui.scene.data.repository.SceneContainerRepository
-import com.android.systemui.scene.shared.flag.SceneContainerFlags
+import com.android.systemui.scene.shared.model.SceneDataSource
 import javax.inject.Inject
 import kotlinx.coroutines.CoroutineScope
 import kotlinx.coroutines.ExperimentalCoroutinesApi
@@ -30,7 +31,6 @@
 import kotlinx.coroutines.flow.MutableStateFlow
 import kotlinx.coroutines.flow.SharingStarted
 import kotlinx.coroutines.flow.StateFlow
-import kotlinx.coroutines.flow.asStateFlow
 import kotlinx.coroutines.flow.flatMapLatest
 import kotlinx.coroutines.flow.flowOf
 import kotlinx.coroutines.flow.stateIn
@@ -38,16 +38,15 @@
 /** Encapsulates the state of communal mode. */
 interface CommunalRepository {
     /**
-     * Target scene as requested by the underlying [SceneTransitionLayout] or through
-     * [setDesiredScene].
+     * Target scene as requested by the underlying [SceneTransitionLayout] or through [changeScene].
      */
-    val desiredScene: StateFlow<SceneKey>
+    val currentScene: StateFlow<SceneKey>
 
     /** Exposes the transition state of the communal [SceneTransitionLayout]. */
     val transitionState: StateFlow<ObservableTransitionState>
 
     /** Updates the requested scene. */
-    fun setDesiredScene(desiredScene: SceneKey)
+    fun changeScene(toScene: SceneKey, transitionKey: TransitionKey? = null)
 
     /**
      * Updates the transition state of the hub [SceneTransitionLayout].
@@ -63,12 +62,10 @@
 @Inject
 constructor(
     @Background backgroundScope: CoroutineScope,
-    sceneContainerFlags: SceneContainerFlags,
-    sceneContainerRepository: SceneContainerRepository,
+    @Communal private val sceneDataSource: SceneDataSource,
 ) : CommunalRepository {
 
-    private val _desiredScene: MutableStateFlow<SceneKey> = MutableStateFlow(CommunalScenes.Default)
-    override val desiredScene: StateFlow<SceneKey> = _desiredScene.asStateFlow()
+    override val currentScene: StateFlow<SceneKey> = sceneDataSource.currentScene
 
     private val defaultTransitionState = ObservableTransitionState.Idle(CommunalScenes.Default)
     private val _transitionState = MutableStateFlow<Flow<ObservableTransitionState>?>(null)
@@ -81,8 +78,8 @@
                 initialValue = defaultTransitionState,
             )
 
-    override fun setDesiredScene(desiredScene: SceneKey) {
-        _desiredScene.value = desiredScene
+    override fun changeScene(toScene: SceneKey, transitionKey: TransitionKey?) {
+        sceneDataSource.changeScene(toScene, transitionKey)
     }
 
     /**
diff --git a/packages/SystemUI/src/com/android/systemui/communal/domain/interactor/CommunalInteractor.kt b/packages/SystemUI/src/com/android/systemui/communal/domain/interactor/CommunalInteractor.kt
index ada984d..246d5d9 100644
--- a/packages/SystemUI/src/com/android/systemui/communal/domain/interactor/CommunalInteractor.kt
+++ b/packages/SystemUI/src/com/android/systemui/communal/domain/interactor/CommunalInteractor.kt
@@ -25,6 +25,7 @@
 import android.provider.Settings
 import com.android.compose.animation.scene.ObservableTransitionState
 import com.android.compose.animation.scene.SceneKey
+import com.android.compose.animation.scene.TransitionKey
 import com.android.systemui.broadcast.BroadcastDispatcher
 import com.android.systemui.communal.data.repository.CommunalMediaRepository
 import com.android.systemui.communal.data.repository.CommunalPrefsRepository
@@ -80,6 +81,7 @@
 import kotlinx.coroutines.flow.map
 import kotlinx.coroutines.flow.onEach
 import kotlinx.coroutines.flow.shareIn
+import kotlinx.coroutines.flow.stateIn
 
 /** Encapsulates business-logic related to communal mode. */
 @OptIn(ExperimentalCoroutinesApi::class)
@@ -142,13 +144,12 @@
             )
 
     /**
-     * Target scene as requested by the underlying [SceneTransitionLayout] or through
-     * [onSceneChanged].
+     * Target scene as requested by the underlying [SceneTransitionLayout] or through [changeScene].
      *
      * If [isCommunalAvailable] is false, will return [CommunalScenes.Blank]
      */
     val desiredScene: Flow<SceneKey> =
-        communalRepository.desiredScene.combine(isCommunalAvailable) { scene, available ->
+        communalRepository.currentScene.combine(isCommunalAvailable) { scene, available ->
             if (available) scene else CommunalScenes.Blank
         }
 
@@ -239,10 +240,14 @@
      * This will not be true while transitioning to the hub and will turn false immediately when a
      * swipe to exit the hub starts.
      */
-    val isIdleOnCommunal: Flow<Boolean> =
-        communalRepository.transitionState.map {
-            it is ObservableTransitionState.Idle && it.scene == CommunalScenes.Communal
-        }
+    val isIdleOnCommunal: StateFlow<Boolean> =
+        communalRepository.transitionState
+            .map { it is ObservableTransitionState.Idle && it.scene == CommunalScenes.Communal }
+            .stateIn(
+                scope = applicationScope,
+                started = SharingStarted.Eagerly,
+                initialValue = false,
+            )
 
     /**
      * Flow that emits a boolean if any portion of the communal UI is visible at all.
@@ -254,9 +259,12 @@
             !(it is ObservableTransitionState.Idle && it.scene == CommunalScenes.Blank)
         }
 
-    /** Callback received whenever the [SceneTransitionLayout] finishes a scene transition. */
-    fun onSceneChanged(newScene: SceneKey) {
-        communalRepository.setDesiredScene(newScene)
+    /**
+     * Asks for an asynchronous scene witch to [newScene], which will use the corresponding
+     * installed transition or the one specified by [transitionKey], if provided.
+     */
+    fun changeScene(newScene: SceneKey, transitionKey: TransitionKey? = null) {
+        communalRepository.changeScene(newScene, transitionKey)
     }
 
     fun setEditModeOpen(isOpen: Boolean) {
diff --git a/packages/SystemUI/src/com/android/systemui/communal/ui/viewmodel/BaseCommunalViewModel.kt b/packages/SystemUI/src/com/android/systemui/communal/ui/viewmodel/BaseCommunalViewModel.kt
index 531f1987..095222a 100644
--- a/packages/SystemUI/src/com/android/systemui/communal/ui/viewmodel/BaseCommunalViewModel.kt
+++ b/packages/SystemUI/src/com/android/systemui/communal/ui/viewmodel/BaseCommunalViewModel.kt
@@ -49,8 +49,8 @@
         communalInteractor.signalUserInteraction()
     }
 
-    fun onSceneChanged(scene: SceneKey) {
-        communalInteractor.onSceneChanged(scene)
+    fun changeScene(scene: SceneKey) {
+        communalInteractor.changeScene(scene)
     }
 
     /**
diff --git a/packages/SystemUI/src/com/android/systemui/communal/widgets/EditWidgetsActivity.kt b/packages/SystemUI/src/com/android/systemui/communal/widgets/EditWidgetsActivity.kt
index 902133d..5f4b394 100644
--- a/packages/SystemUI/src/com/android/systemui/communal/widgets/EditWidgetsActivity.kt
+++ b/packages/SystemUI/src/com/android/systemui/communal/widgets/EditWidgetsActivity.kt
@@ -31,7 +31,6 @@
 import androidx.compose.foundation.layout.fillMaxSize
 import androidx.compose.ui.Modifier
 import androidx.lifecycle.lifecycleScope
-import com.android.app.tracing.coroutines.launch
 import com.android.compose.theme.LocalAndroidColorScheme
 import com.android.compose.theme.PlatformTheme
 import com.android.internal.logging.UiEventLogger
@@ -150,7 +149,7 @@
 
     private fun onEditDone() {
         try {
-            communalViewModel.onSceneChanged(CommunalScenes.Communal)
+            communalViewModel.changeScene(CommunalScenes.Communal)
             checkNotNull(windowManagerService).lockNow(/* options */ null)
             finish()
         } catch (e: RemoteException) {
diff --git a/packages/SystemUI/src/com/android/systemui/dagger/SystemUIModule.java b/packages/SystemUI/src/com/android/systemui/dagger/SystemUIModule.java
index 19af371..1ed4b50 100644
--- a/packages/SystemUI/src/com/android/systemui/dagger/SystemUIModule.java
+++ b/packages/SystemUI/src/com/android/systemui/dagger/SystemUIModule.java
@@ -49,6 +49,7 @@
 import com.android.systemui.communal.dagger.CommunalModule;
 import com.android.systemui.complication.dagger.ComplicationComponent;
 import com.android.systemui.controls.dagger.ControlsModule;
+import com.android.systemui.dagger.qualifiers.Application;
 import com.android.systemui.dagger.qualifiers.Main;
 import com.android.systemui.dagger.qualifiers.SystemUser;
 import com.android.systemui.dagger.qualifiers.UiBackground;
@@ -89,6 +90,7 @@
 import com.android.systemui.recents.Recents;
 import com.android.systemui.recordissue.RecordIssueModule;
 import com.android.systemui.retail.dagger.RetailModeModule;
+import com.android.systemui.scene.shared.model.SceneContainerConfig;
 import com.android.systemui.scene.shared.model.SceneDataSource;
 import com.android.systemui.scene.shared.model.SceneDataSourceDelegator;
 import com.android.systemui.scene.ui.view.WindowRootViewComponent;
@@ -165,6 +167,8 @@
 
 import javax.inject.Named;
 
+import kotlinx.coroutines.CoroutineScope;
+
 /**
  * A dagger module for injecting components of System UI that are required by System UI.
  *
@@ -402,6 +406,13 @@
     @ClassKey(SystemUISecondaryUserService.class)
     abstract Service bindsSystemUISecondaryUserService(SystemUISecondaryUserService service);
 
+    @Provides
+    @SysUISingleton
+    static SceneDataSourceDelegator providesSceneDataSourceDelegator(
+            @Application CoroutineScope applicationScope, SceneContainerConfig config) {
+        return new SceneDataSourceDelegator(applicationScope, config);
+    }
+
     @Binds
     abstract SceneDataSource bindSceneDataSource(SceneDataSourceDelegator delegator);
 }
diff --git a/packages/SystemUI/src/com/android/systemui/dreams/touch/BouncerSwipeTouchHandler.java b/packages/SystemUI/src/com/android/systemui/dreams/touch/BouncerSwipeTouchHandler.java
index 7f3b5eb..926f7f1 100644
--- a/packages/SystemUI/src/com/android/systemui/dreams/touch/BouncerSwipeTouchHandler.java
+++ b/packages/SystemUI/src/com/android/systemui/dreams/touch/BouncerSwipeTouchHandler.java
@@ -172,19 +172,18 @@
                     final float screenTravelPercentage = Math.abs(e1.getY() - e2.getY())
                             / mTouchSession.getBounds().height();
                     setPanelExpansion(mBouncerInitiallyShowing
-                            ? screenTravelPercentage : 1 - screenTravelPercentage, dragDownAmount);
+                            ? screenTravelPercentage : 1 - screenTravelPercentage);
                     return true;
                 }
             };
 
-    private void setPanelExpansion(float expansion, float dragDownAmount) {
+    private void setPanelExpansion(float expansion) {
         mCurrentExpansion = expansion;
         ShadeExpansionChangeEvent event =
                 new ShadeExpansionChangeEvent(
                         /* fraction= */ mCurrentExpansion,
                         /* expanded= */ mExpanded,
-                        /* tracking= */ true,
-                        /* dragDownPxAmount= */ dragDownAmount);
+                        /* tracking= */ true);
         mCurrentScrimController.expand(event);
     }
 
@@ -333,7 +332,7 @@
                 animation -> {
                     float expansionFraction = (float) animation.getAnimatedValue();
                     float dragDownAmount = expansionFraction * expansionHeight;
-                    setPanelExpansion(expansionFraction, dragDownAmount);
+                    setPanelExpansion(expansionFraction);
                 });
         if (!mBouncerInitiallyShowing
                 && targetExpansion == KeyguardBouncerConstants.EXPANSION_VISIBLE) {
diff --git a/packages/SystemUI/src/com/android/systemui/dreams/ui/viewmodel/DreamViewModel.kt b/packages/SystemUI/src/com/android/systemui/dreams/ui/viewmodel/DreamViewModel.kt
index 1b832d4..037c23b 100644
--- a/packages/SystemUI/src/com/android/systemui/dreams/ui/viewmodel/DreamViewModel.kt
+++ b/packages/SystemUI/src/com/android/systemui/dreams/ui/viewmodel/DreamViewModel.kt
@@ -57,7 +57,7 @@
                 !keyguardUpdateMonitor.isEncryptedOrLockdown(userTracker.userId)
         if (showGlanceableHub) {
             toGlanceableHubTransitionViewModel.startTransition()
-            communalInteractor.onSceneChanged(CommunalScenes.Communal)
+            communalInteractor.changeScene(CommunalScenes.Communal)
         } else {
             toLockscreenTransitionViewModel.startTransition()
         }
diff --git a/packages/SystemUI/src/com/android/systemui/haptics/qs/QSLongPressEffectViewBinder.kt b/packages/SystemUI/src/com/android/systemui/haptics/qs/QSLongPressEffectViewBinder.kt
index 1705909..f4998a7 100644
--- a/packages/SystemUI/src/com/android/systemui/haptics/qs/QSLongPressEffectViewBinder.kt
+++ b/packages/SystemUI/src/com/android/systemui/haptics/qs/QSLongPressEffectViewBinder.kt
@@ -18,10 +18,10 @@
 
 import androidx.lifecycle.Lifecycle
 import androidx.lifecycle.repeatOnLifecycle
+import com.android.app.tracing.coroutines.launch
 import com.android.systemui.lifecycle.repeatWhenAttached
 import com.android.systemui.qs.tileimpl.QSTileViewImpl
 import kotlinx.coroutines.DisposableHandle
-import kotlinx.coroutines.launch
 
 class QSLongPressEffectViewBinder {
 
@@ -31,16 +31,18 @@
 
     fun bind(
         tile: QSTileViewImpl,
+        tileSpec: String?,
         effect: QSLongPressEffect?,
     ) {
         if (effect == null) return
 
         handle =
             tile.repeatWhenAttached {
-                repeatOnLifecycle(Lifecycle.State.STARTED) {
+                repeatOnLifecycle(Lifecycle.State.CREATED) {
                     effect.scope = this
+                    val tag = "${tileSpec ?: "unknownTileSpec"}#LongPressEffect"
 
-                    launch {
+                    launch("$tag#progress") {
                         effect.effectProgress.collect { progress ->
                             progress?.let {
                                 if (it == 0f) {
@@ -51,7 +53,7 @@
                         }
                     }
 
-                    launch {
+                    launch("$tag#action") {
                         effect.actionType.collect { action ->
                             action?.let {
                                 when (it) {
diff --git a/packages/SystemUI/src/com/android/systemui/keyguard/KeyguardViewConfigurator.kt b/packages/SystemUI/src/com/android/systemui/keyguard/KeyguardViewConfigurator.kt
index fa845c7..3da3e2f 100644
--- a/packages/SystemUI/src/com/android/systemui/keyguard/KeyguardViewConfigurator.kt
+++ b/packages/SystemUI/src/com/android/systemui/keyguard/KeyguardViewConfigurator.kt
@@ -29,6 +29,9 @@
 import androidx.constraintlayout.widget.ConstraintSet.PARENT_ID
 import androidx.constraintlayout.widget.ConstraintSet.START
 import androidx.constraintlayout.widget.ConstraintSet.TOP
+import com.android.compose.animation.scene.SceneKey
+import com.android.compose.animation.scene.SceneTransitionLayout
+import com.android.compose.animation.scene.transitions
 import com.android.internal.jank.InteractionJankMonitor
 import com.android.keyguard.KeyguardStatusView
 import com.android.keyguard.KeyguardStatusViewController
@@ -109,6 +112,7 @@
 
     private var rootViewHandle: DisposableHandle? = null
     private var indicationAreaHandle: DisposableHandle? = null
+    private val sceneKey = SceneKey("root-view-scene-key")
 
     var keyguardStatusViewController: KeyguardStatusViewController? = null
         get() {
@@ -219,12 +223,25 @@
             blueprints.mapNotNull { it as? ComposableLockscreenSceneBlueprint }.toSet()
         return ComposeView(context).apply {
             setContent {
-                LockscreenContent(
-                        viewModel = viewModel,
-                        blueprints = sceneBlueprints,
-                        clockInteractor = clockInteractor
-                    )
-                    .Content(modifier = Modifier.fillMaxSize())
+                // STL is used solely to provide a SceneScope to enable us to invoke SceneScope
+                // composables.
+                SceneTransitionLayout(
+                    currentScene = sceneKey,
+                    onChangeScene = {},
+                    transitions = transitions {},
+                ) {
+                    scene(sceneKey) {
+                        with(
+                            LockscreenContent(
+                                viewModel = viewModel,
+                                blueprints = sceneBlueprints,
+                                clockInteractor = clockInteractor
+                            )
+                        ) {
+                            Content(modifier = Modifier.fillMaxSize())
+                        }
+                    }
+                }
             }
         }
     }
diff --git a/packages/SystemUI/src/com/android/systemui/keyguard/data/quickaffordance/QuickAccessWalletKeyguardQuickAffordanceConfig.kt b/packages/SystemUI/src/com/android/systemui/keyguard/data/quickaffordance/QuickAccessWalletKeyguardQuickAffordanceConfig.kt
index 88eadd7..b3d9a76 100644
--- a/packages/SystemUI/src/com/android/systemui/keyguard/data/quickaffordance/QuickAccessWalletKeyguardQuickAffordanceConfig.kt
+++ b/packages/SystemUI/src/com/android/systemui/keyguard/data/quickaffordance/QuickAccessWalletKeyguardQuickAffordanceConfig.kt
@@ -92,7 +92,8 @@
                 walletController.setupWalletChangeObservers(
                     callback,
                     QuickAccessWalletController.WalletChangeEvent.WALLET_PREFERENCE_CHANGE,
-                    QuickAccessWalletController.WalletChangeEvent.DEFAULT_PAYMENT_APP_CHANGE
+                    QuickAccessWalletController.WalletChangeEvent.DEFAULT_PAYMENT_APP_CHANGE,
+                    QuickAccessWalletController.WalletChangeEvent.DEFAULT_WALLET_APP_CHANGE
                 )
 
                 withContext(backgroundDispatcher) {
@@ -104,7 +105,8 @@
                 awaitClose {
                     walletController.unregisterWalletChangeObservers(
                         QuickAccessWalletController.WalletChangeEvent.WALLET_PREFERENCE_CHANGE,
-                        QuickAccessWalletController.WalletChangeEvent.DEFAULT_PAYMENT_APP_CHANGE
+                        QuickAccessWalletController.WalletChangeEvent.DEFAULT_PAYMENT_APP_CHANGE,
+                        QuickAccessWalletController.WalletChangeEvent.DEFAULT_WALLET_APP_CHANGE
                     )
                 }
             }
diff --git a/packages/SystemUI/src/com/android/systemui/keyguard/domain/backup/KeyguardQuickAffordanceBackupHelper.kt b/packages/SystemUI/src/com/android/systemui/keyguard/domain/backup/KeyguardQuickAffordanceBackupHelper.kt
index fa6efa5..f763e62 100644
--- a/packages/SystemUI/src/com/android/systemui/keyguard/domain/backup/KeyguardQuickAffordanceBackupHelper.kt
+++ b/packages/SystemUI/src/com/android/systemui/keyguard/domain/backup/KeyguardQuickAffordanceBackupHelper.kt
@@ -17,14 +17,17 @@
 
 package com.android.systemui.keyguard.domain.backup
 
+import android.app.backup.BackupDataInputStream
 import android.app.backup.SharedPreferencesBackupHelper
 import android.content.Context
+import android.util.Log
+import com.android.app.tracing.traceSection
 import com.android.systemui.keyguard.data.quickaffordance.KeyguardQuickAffordanceSelectionManager
 import com.android.systemui.settings.UserFileManagerImpl
 
 /** Handles backup & restore for keyguard quick affordances. */
 class KeyguardQuickAffordanceBackupHelper(
-    context: Context,
+    private val context: Context,
     userId: Int,
 ) :
     SharedPreferencesBackupHelper(
@@ -34,4 +37,17 @@
                 fileName = KeyguardQuickAffordanceSelectionManager.FILE_NAME,
             )
             .getPath()
-    )
+    ) {
+
+    override fun restoreEntity(data: BackupDataInputStream?) {
+        Log.d(TAG, "Starting restore for ${data?.key} for user ${context.userId}")
+        traceSection("$TAG File restore: ${data?.key}") {
+            super.restoreEntity(data)
+        }
+        Log.d(TAG, "Finished restore for ${data?.key} for user ${context.userId}")
+    }
+
+    companion object {
+        private const val TAG = "KeyguardQuickAffordanceBackupHelper"
+    }
+}
diff --git a/packages/SystemUI/src/com/android/systemui/keyguard/domain/interactor/KeyguardOcclusionInteractor.kt b/packages/SystemUI/src/com/android/systemui/keyguard/domain/interactor/KeyguardOcclusionInteractor.kt
index 9aa2202..03ed567 100644
--- a/packages/SystemUI/src/com/android/systemui/keyguard/domain/interactor/KeyguardOcclusionInteractor.kt
+++ b/packages/SystemUI/src/com/android/systemui/keyguard/domain/interactor/KeyguardOcclusionInteractor.kt
@@ -19,13 +19,17 @@
 import android.app.ActivityManager.RunningTaskInfo
 import com.android.systemui.dagger.SysUISingleton
 import com.android.systemui.dagger.qualifiers.Application
+import com.android.systemui.deviceentry.domain.interactor.DeviceUnlockedInteractor
 import com.android.systemui.keyguard.data.repository.KeyguardOcclusionRepository
 import com.android.systemui.keyguard.shared.model.KeyguardState
 import com.android.systemui.power.domain.interactor.PowerInteractor
+import com.android.systemui.scene.shared.flag.SceneContainerFlag
 import com.android.systemui.util.kotlin.sample
+import dagger.Lazy
 import javax.inject.Inject
 import kotlinx.coroutines.CoroutineScope
 import kotlinx.coroutines.flow.SharingStarted
+import kotlinx.coroutines.flow.StateFlow
 import kotlinx.coroutines.flow.asStateFlow
 import kotlinx.coroutines.flow.filter
 import kotlinx.coroutines.flow.map
@@ -45,11 +49,12 @@
 class KeyguardOcclusionInteractor
 @Inject
 constructor(
-    @Application scope: CoroutineScope,
-    val repository: KeyguardOcclusionRepository,
-    val powerInteractor: PowerInteractor,
-    val transitionInteractor: KeyguardTransitionInteractor,
-    val keyguardInteractor: KeyguardInteractor,
+    @Application applicationScope: CoroutineScope,
+    private val repository: KeyguardOcclusionRepository,
+    private val powerInteractor: PowerInteractor,
+    private val transitionInteractor: KeyguardTransitionInteractor,
+    keyguardInteractor: KeyguardInteractor,
+    deviceUnlockedInteractor: Lazy<DeviceUnlockedInteractor>,
 ) {
     val showWhenLockedActivityInfo = repository.showWhenLockedActivityInfo.asStateFlow()
 
@@ -94,14 +99,19 @@
                 // Emit false once that activity goes away.
                 isShowWhenLockedActivityOnTop.filter { !it }.map { false }
             )
-            .stateIn(scope, SharingStarted.Eagerly, false)
+            .stateIn(applicationScope, SharingStarted.Eagerly, false)
 
     /**
      * Whether launching an occluding activity will automatically dismiss keyguard. This happens if
      * the keyguard is dismissable.
      */
-    val occludingActivityWillDismissKeyguard =
-        keyguardInteractor.isKeyguardDismissible.stateIn(scope, SharingStarted.Eagerly, false)
+    val occludingActivityWillDismissKeyguard: StateFlow<Boolean> =
+        if (SceneContainerFlag.isEnabled) {
+                deviceUnlockedInteractor.get().isDeviceUnlocked
+            } else {
+                keyguardInteractor.isKeyguardDismissible
+            }
+            .stateIn(scope = applicationScope, SharingStarted.Eagerly, false)
 
     /**
      * Called to let System UI know that WM says a SHOW_WHEN_LOCKED activity is on top (or no longer
diff --git a/packages/SystemUI/src/com/android/systemui/keyguard/ui/binder/KeyguardPreviewClockViewBinder.kt b/packages/SystemUI/src/com/android/systemui/keyguard/ui/binder/KeyguardPreviewClockViewBinder.kt
index d9f12c3..5906cfd 100644
--- a/packages/SystemUI/src/com/android/systemui/keyguard/ui/binder/KeyguardPreviewClockViewBinder.kt
+++ b/packages/SystemUI/src/com/android/systemui/keyguard/ui/binder/KeyguardPreviewClockViewBinder.kt
@@ -18,6 +18,7 @@
 package com.android.systemui.keyguard.ui.binder
 
 import android.content.Context
+import android.util.DisplayMetrics
 import android.view.View
 import android.view.View.INVISIBLE
 import android.view.View.VISIBLE
@@ -109,7 +110,7 @@
     private fun applyClockDefaultConstraints(context: Context, constraints: ConstraintSet) {
         constraints.apply {
             constrainWidth(R.id.lockscreen_clock_view_large, ConstraintSet.WRAP_CONTENT)
-            constrainHeight(R.id.lockscreen_clock_view_large, ConstraintSet.WRAP_CONTENT)
+            constrainHeight(R.id.lockscreen_clock_view_large, ConstraintSet.MATCH_CONSTRAINT)
             val largeClockTopMargin =
                 context.resources.getDimensionPixelSize(R.dimen.status_bar_height) +
                     context.resources.getDimensionPixelSize(
@@ -129,7 +130,29 @@
                 ConstraintSet.END
             )
 
-            connect(R.id.lockscreen_clock_view_large, BOTTOM, R.id.lock_icon_view, TOP)
+            // In preview, we'll show UDFPS icon for UDFPS devices
+            // and nothing for non-UDFPS devices,
+            // but we need position of device entry icon to constrain clock
+            if (getConstraint(R.id.lock_icon_view) != null) {
+                connect(R.id.lockscreen_clock_view_large, BOTTOM, R.id.lock_icon_view, TOP)
+            } else {
+                // Copied calculation codes from applyConstraints in DefaultDeviceEntrySection
+                val bottomPaddingPx =
+                    context.resources.getDimensionPixelSize(R.dimen.lock_icon_margin_bottom)
+                val defaultDensity =
+                    DisplayMetrics.DENSITY_DEVICE_STABLE.toFloat() /
+                        DisplayMetrics.DENSITY_DEFAULT.toFloat()
+                val lockIconRadiusPx = (defaultDensity * 36).toInt()
+                val clockBottomMargin = bottomPaddingPx + 2 * lockIconRadiusPx
+                connect(
+                    R.id.lockscreen_clock_view_large,
+                    BOTTOM,
+                    PARENT_ID,
+                    BOTTOM,
+                    clockBottomMargin
+                )
+            }
+
             constrainWidth(R.id.lockscreen_clock_view, WRAP_CONTENT)
             constrainHeight(
                 R.id.lockscreen_clock_view,
diff --git a/packages/SystemUI/src/com/android/systemui/keyguard/ui/binder/KeyguardRootViewBinder.kt b/packages/SystemUI/src/com/android/systemui/keyguard/ui/binder/KeyguardRootViewBinder.kt
index 4d9354dd..33052be 100644
--- a/packages/SystemUI/src/com/android/systemui/keyguard/ui/binder/KeyguardRootViewBinder.kt
+++ b/packages/SystemUI/src/com/android/systemui/keyguard/ui/binder/KeyguardRootViewBinder.kt
@@ -82,7 +82,6 @@
 /** Bind occludingAppDeviceEntryMessageViewModel to run whenever the keyguard view is attached. */
 @OptIn(ExperimentalCoroutinesApi::class)
 object KeyguardRootViewBinder {
-
     @SuppressLint("ClickableViewAccessibility")
     @JvmStatic
     fun bind(
@@ -102,14 +101,6 @@
     ): DisposableHandle {
         var onLayoutChangeListener: OnLayoutChange? = null
         val childViews = mutableMapOf<Int, View>()
-        val statusViewId = R.id.keyguard_status_view
-        val burnInLayerId = R.id.burn_in_layer
-        val aodNotificationIconContainerId = R.id.aod_notification_icon_container
-        val largeClockId = R.id.lockscreen_clock_view_large
-        val indicationArea = R.id.keyguard_indication_area
-        val startButton = R.id.start_button
-        val endButton = R.id.end_button
-        val lockIcon = R.id.lock_icon_view
 
         if (KeyguardBottomAreaRefactor.isEnabled) {
             view.setOnTouchListener { _, event ->
@@ -214,7 +205,7 @@
                                 val px = state.value ?: return@collect
                                 when {
                                     state.isToOrFrom(KeyguardState.AOD) -> {
-                                        childViews[largeClockId]?.translationX = px
+                                        // Large Clock is not translated in the x direction
                                         childViews[burnInLayerId]?.translationX = px
                                         childViews[aodNotificationIconContainerId]?.translationX =
                                             px
@@ -436,7 +427,7 @@
             oldRight: Int,
             oldBottom: Int
         ) {
-            childViews[R.id.nssl_placeholder]?.let { notificationListPlaceholder ->
+            childViews[nsslPlaceholderId]?.let { notificationListPlaceholder ->
                 // After layout, ensure the notifications are positioned correctly
                 viewModel.onNotificationContainerBoundsChanged(
                     notificationListPlaceholder.top.toFloat(),
@@ -460,14 +451,14 @@
                                 )
                             }
                         } else {
-                            childViews[R.id.keyguard_status_view]?.top ?: 0
+                            childViews[statusViewId]?.top ?: 0
                         }
                 )
             }
         }
 
         private fun isUserVisible(view: View): Boolean {
-            return view.id != R.id.burn_in_layer &&
+            return view.id != burnInLayerId &&
                 view.visibility == VISIBLE &&
                 view.width > 0 &&
                 view.height > 0
@@ -589,6 +580,17 @@
     private fun ViewPropertyAnimator.animateInIconTranslation(): ViewPropertyAnimator =
         setInterpolator(Interpolators.DECELERATE_QUINT).translationY(0f)
 
+    private val statusViewId = R.id.keyguard_status_view
+    private val burnInLayerId = R.id.burn_in_layer
+    private val aodNotificationIconContainerId = R.id.aod_notification_icon_container
+    private val largeClockId = R.id.lockscreen_clock_view_large
+    private val smallClockId = R.id.lockscreen_clock_view
+    private val indicationArea = R.id.keyguard_indication_area
+    private val startButton = R.id.start_button
+    private val endButton = R.id.end_button
+    private val lockIcon = R.id.lock_icon_view
+    private val nsslPlaceholderId = R.id.nssl_placeholder
+
     private const val ID = "occluding_app_device_entry_unlock_msg"
     private const val AOD_ICONS_APPEAR_DURATION: Long = 200
 }
diff --git a/packages/SystemUI/src/com/android/systemui/keyguard/ui/view/layout/sections/DefaultDeviceEntrySection.kt b/packages/SystemUI/src/com/android/systemui/keyguard/ui/view/layout/sections/DefaultDeviceEntrySection.kt
index 4c846e4..29041d1 100644
--- a/packages/SystemUI/src/com/android/systemui/keyguard/ui/view/layout/sections/DefaultDeviceEntrySection.kt
+++ b/packages/SystemUI/src/com/android/systemui/keyguard/ui/view/layout/sections/DefaultDeviceEntrySection.kt
@@ -34,6 +34,7 @@
 import com.android.systemui.flags.FeatureFlags
 import com.android.systemui.flags.Flags
 import com.android.systemui.keyguard.KeyguardBottomAreaRefactor
+import com.android.systemui.keyguard.MigrateClocksToBlueprint
 import com.android.systemui.keyguard.shared.model.KeyguardSection
 import com.android.systemui.keyguard.ui.binder.DeviceEntryIconViewBinder
 import com.android.systemui.keyguard.ui.view.DeviceEntryIconView
@@ -72,7 +73,7 @@
     override fun addViews(constraintLayout: ConstraintLayout) {
         if (
             !KeyguardBottomAreaRefactor.isEnabled &&
-                !DeviceEntryUdfpsRefactor.isEnabled &&
+                !MigrateClocksToBlueprint.isEnabled &&
                 !DeviceEntryUdfpsRefactor.isEnabled
         ) {
             return
diff --git a/packages/SystemUI/src/com/android/systemui/keyguard/ui/viewmodel/AlternateBouncerToGoneTransitionViewModel.kt b/packages/SystemUI/src/com/android/systemui/keyguard/ui/viewmodel/AlternateBouncerToGoneTransitionViewModel.kt
index ac2713d..8c6be98 100644
--- a/packages/SystemUI/src/com/android/systemui/keyguard/ui/viewmodel/AlternateBouncerToGoneTransitionViewModel.kt
+++ b/packages/SystemUI/src/com/android/systemui/keyguard/ui/viewmodel/AlternateBouncerToGoneTransitionViewModel.kt
@@ -24,6 +24,7 @@
 import com.android.systemui.keyguard.shared.model.ScrimAlpha
 import com.android.systemui.keyguard.ui.KeyguardTransitionAnimationFlow
 import com.android.systemui.keyguard.ui.transitions.DeviceEntryIconTransition
+import com.android.systemui.statusbar.SysuiStatusBarStateController
 import javax.inject.Inject
 import kotlin.time.Duration.Companion.milliseconds
 import kotlinx.coroutines.ExperimentalCoroutinesApi
@@ -40,6 +41,7 @@
 constructor(
     bouncerToGoneFlows: BouncerToGoneFlows,
     animationFlow: KeyguardTransitionAnimationFlow,
+    private val statusBarStateController: SysuiStatusBarStateController,
 ) : DeviceEntryIconTransition {
     private val transitionAnimation =
         animationFlow.setup(
@@ -59,6 +61,30 @@
         )
     }
 
+    fun notificationAlpha(viewState: ViewStateAccessor): Flow<Float> {
+        var startAlpha = 1f
+        var leaveShadeOpen = false
+
+        return transitionAnimation.sharedFlow(
+            duration = 200.milliseconds,
+            onStart = {
+                leaveShadeOpen = statusBarStateController.leaveOpenOnKeyguardHide()
+                startAlpha = viewState.alpha()
+            },
+            onStep = {
+                if (leaveShadeOpen) {
+                    1f
+                } else {
+                    MathUtils.lerp(startAlpha, 0f, it)
+                }
+            },
+        )
+    }
+
+    /** See [BouncerToGoneFlows#showAllNotifications] */
+    val showAllNotifications: Flow<Boolean> =
+        bouncerToGoneFlows.showAllNotifications(TO_GONE_DURATION, ALTERNATE_BOUNCER)
+
     /** Scrim alpha values */
     val scrimAlpha: Flow<ScrimAlpha> =
         bouncerToGoneFlows.scrimAlpha(TO_GONE_DURATION, ALTERNATE_BOUNCER)
diff --git a/packages/SystemUI/src/com/android/systemui/keyguard/ui/viewmodel/BouncerToGoneFlows.kt b/packages/SystemUI/src/com/android/systemui/keyguard/ui/viewmodel/BouncerToGoneFlows.kt
index 924fc5d..fe88b81 100644
--- a/packages/SystemUI/src/com/android/systemui/keyguard/ui/viewmodel/BouncerToGoneFlows.kt
+++ b/packages/SystemUI/src/com/android/systemui/keyguard/ui/viewmodel/BouncerToGoneFlows.kt
@@ -32,6 +32,7 @@
 import kotlin.time.Duration
 import kotlinx.coroutines.ExperimentalCoroutinesApi
 import kotlinx.coroutines.flow.Flow
+import kotlinx.coroutines.flow.distinctUntilChanged
 import kotlinx.coroutines.flow.flatMapLatest
 import kotlinx.coroutines.flow.map
 
@@ -63,6 +64,31 @@
         }
     }
 
+    /**
+     * When the shade is expanded, make sure that all notifications can be seen immediately during a
+     * transition to GONE. This matters especially when the user has chosen to not show
+     * notifications on the lockscreen and then pulls down the shade, which presents them with an
+     * immediate auth prompt, followed by a notification animation.
+     */
+    fun showAllNotifications(duration: Duration, from: KeyguardState): Flow<Boolean> {
+        var leaveShadeOpen = false
+        return animationFlow
+            .setup(
+                duration = duration,
+                from = from,
+                to = GONE,
+            )
+            .sharedFlow(
+                duration = duration,
+                onStart = { leaveShadeOpen = statusBarStateController.leaveOpenOnKeyguardHide() },
+                onStep = { if (leaveShadeOpen) 1f else 0f },
+                onFinish = { 0f },
+                onCancel = { 0f },
+            )
+            .map { it == 1f }
+            .distinctUntilChanged()
+    }
+
     private fun createScrimAlphaFlow(
         duration: Duration,
         fromState: KeyguardState,
diff --git a/packages/SystemUI/src/com/android/systemui/keyguard/ui/viewmodel/LockscreenContentViewModel.kt b/packages/SystemUI/src/com/android/systemui/keyguard/ui/viewmodel/LockscreenContentViewModel.kt
index 1f80441..36896f9 100644
--- a/packages/SystemUI/src/com/android/systemui/keyguard/ui/viewmodel/LockscreenContentViewModel.kt
+++ b/packages/SystemUI/src/com/android/systemui/keyguard/ui/viewmodel/LockscreenContentViewModel.kt
@@ -18,8 +18,10 @@
 
 import android.content.res.Resources
 import com.android.keyguard.KeyguardClockSwitch
+import com.android.keyguard.KeyguardClockSwitch.SMALL
 import com.android.systemui.biometrics.AuthController
 import com.android.systemui.dagger.SysUISingleton
+import com.android.systemui.dagger.qualifiers.Application
 import com.android.systemui.keyguard.domain.interactor.KeyguardBlueprintInteractor
 import com.android.systemui.keyguard.domain.interactor.KeyguardClockInteractor
 import com.android.systemui.res.R
@@ -29,6 +31,7 @@
 import kotlinx.coroutines.CoroutineScope
 import kotlinx.coroutines.flow.SharingStarted
 import kotlinx.coroutines.flow.StateFlow
+import kotlinx.coroutines.flow.combine
 import kotlinx.coroutines.flow.distinctUntilChanged
 import kotlinx.coroutines.flow.map
 import kotlinx.coroutines.flow.stateIn
@@ -42,6 +45,7 @@
     private val authController: AuthController,
     val longPress: KeyguardLongPressViewModel,
     val shadeInteractor: ShadeInteractor,
+    @Application private val applicationScope: CoroutineScope,
 ) {
     private val clockSize = clockInteractor.clockSize
 
@@ -50,11 +54,26 @@
     val isLargeClockVisible: Boolean
         get() = clockSize.value == KeyguardClockSwitch.LARGE
 
-    val areNotificationsVisible: Boolean
-        get() = !isLargeClockVisible || shouldUseSplitNotificationShade
+    val shouldUseSplitNotificationShade: StateFlow<Boolean> =
+        shadeInteractor.shadeMode
+            .map { it == ShadeMode.Split }
+            .stateIn(
+                scope = applicationScope,
+                started = SharingStarted.WhileSubscribed(),
+                initialValue = false,
+            )
 
-    val shouldUseSplitNotificationShade: Boolean
-        get() = shadeInteractor.shadeMode.value == ShadeMode.Split
+    val areNotificationsVisible: StateFlow<Boolean> =
+        combine(clockSize, shouldUseSplitNotificationShade) {
+                clockSize,
+                shouldUseSplitNotificationShade ->
+                clockSize == SMALL || shouldUseSplitNotificationShade
+            }
+            .stateIn(
+                scope = applicationScope,
+                started = SharingStarted.WhileSubscribed(),
+                initialValue = false,
+            )
 
     fun getSmartSpacePaddingTop(resources: Resources): Int {
         return if (isLargeClockVisible) {
diff --git a/packages/SystemUI/src/com/android/systemui/keyguard/ui/viewmodel/LockscreenToGoneTransitionViewModel.kt b/packages/SystemUI/src/com/android/systemui/keyguard/ui/viewmodel/LockscreenToGoneTransitionViewModel.kt
index 4e6aa03..f03625e 100644
--- a/packages/SystemUI/src/com/android/systemui/keyguard/ui/viewmodel/LockscreenToGoneTransitionViewModel.kt
+++ b/packages/SystemUI/src/com/android/systemui/keyguard/ui/viewmodel/LockscreenToGoneTransitionViewModel.kt
@@ -23,6 +23,7 @@
 import com.android.systemui.keyguard.ui.KeyguardTransitionAnimationFlow
 import com.android.systemui.keyguard.ui.KeyguardTransitionAnimationFlow.FlowBuilder
 import com.android.systemui.keyguard.ui.transitions.DeviceEntryIconTransition
+import com.android.systemui.statusbar.SysuiStatusBarStateController
 import javax.inject.Inject
 import kotlin.time.Duration.Companion.milliseconds
 import kotlinx.coroutines.ExperimentalCoroutinesApi
@@ -37,6 +38,7 @@
 @Inject
 constructor(
     animationFlow: KeyguardTransitionAnimationFlow,
+    private val statusBarStateController: SysuiStatusBarStateController,
 ) : DeviceEntryIconTransition {
 
     private val transitionAnimation: FlowBuilder =
@@ -54,6 +56,26 @@
             onCancel = { 1f },
         )
 
+    fun notificationAlpha(viewState: ViewStateAccessor): Flow<Float> {
+        var startAlpha = 1f
+        var leaveShadeOpen = false
+
+        return transitionAnimation.sharedFlow(
+            duration = 200.milliseconds,
+            onStart = {
+                leaveShadeOpen = statusBarStateController.leaveOpenOnKeyguardHide()
+                startAlpha = viewState.alpha()
+            },
+            onStep = {
+                if (leaveShadeOpen) {
+                    1f
+                } else {
+                    MathUtils.lerp(startAlpha, 0f, it)
+                }
+            },
+        )
+    }
+
     fun lockscreenAlpha(viewState: ViewStateAccessor): Flow<Float> {
         var startAlpha = 1f
         return transitionAnimation.sharedFlow(
diff --git a/packages/SystemUI/src/com/android/systemui/keyguard/ui/viewmodel/PrimaryBouncerToGoneTransitionViewModel.kt b/packages/SystemUI/src/com/android/systemui/keyguard/ui/viewmodel/PrimaryBouncerToGoneTransitionViewModel.kt
index 53f4488..0587826 100644
--- a/packages/SystemUI/src/com/android/systemui/keyguard/ui/viewmodel/PrimaryBouncerToGoneTransitionViewModel.kt
+++ b/packages/SystemUI/src/com/android/systemui/keyguard/ui/viewmodel/PrimaryBouncerToGoneTransitionViewModel.kt
@@ -60,6 +60,10 @@
     private var leaveShadeOpen: Boolean = false
     private var willRunDismissFromKeyguard: Boolean = false
 
+    /** See [BouncerToGoneFlows#showAllNotifications] */
+    val showAllNotifications: Flow<Boolean> =
+        bouncerToGoneFlows.showAllNotifications(TO_GONE_DURATION, PRIMARY_BOUNCER)
+
     val notificationAlpha: Flow<Float> =
         transitionAnimation.sharedFlow(
             duration = 200.milliseconds,
diff --git a/packages/SystemUI/src/com/android/systemui/mediaprojection/permission/BaseMediaProjectionPermissionDialogDelegate.kt b/packages/SystemUI/src/com/android/systemui/mediaprojection/permission/BaseMediaProjectionPermissionDialogDelegate.kt
index 1983a67..dba0173 100644
--- a/packages/SystemUI/src/com/android/systemui/mediaprojection/permission/BaseMediaProjectionPermissionDialogDelegate.kt
+++ b/packages/SystemUI/src/com/android/systemui/mediaprojection/permission/BaseMediaProjectionPermissionDialogDelegate.kt
@@ -53,19 +53,17 @@
     private lateinit var cancelButton: TextView
     private lateinit var warning: TextView
     private lateinit var screenShareModeSpinner: Spinner
-    private var hasCancelBeenLogged: Boolean = false
     protected lateinit var dialog: AlertDialog
+    private var shouldLogCancel: Boolean = true
     var selectedScreenShareOption: ScreenShareOption = screenShareOptions.first()
 
     @CallSuper
     override fun onStop(dialog: T) {
         // onStop can be called multiple times and we only want to log once.
-        if (hasCancelBeenLogged) {
-            return
+        if (shouldLogCancel) {
+            mediaProjectionMetricsLogger.notifyProjectionRequestCancelled(hostUid)
+            shouldLogCancel = false
         }
-
-        mediaProjectionMetricsLogger.notifyProjectionRequestCancelled(hostUid)
-        hasCancelBeenLogged = true
     }
 
     @CallSuper
@@ -140,7 +138,10 @@
     }
 
     protected fun setStartButtonOnClickListener(listener: View.OnClickListener?) {
-        startButton.setOnClickListener(listener)
+        startButton.setOnClickListener { view ->
+            shouldLogCancel = false
+            listener?.onClick(view)
+        }
     }
 
     protected fun setCancelButtonOnClickListener(listener: View.OnClickListener?) {
diff --git a/packages/SystemUI/src/com/android/systemui/qs/dagger/QSModule.java b/packages/SystemUI/src/com/android/systemui/qs/dagger/QSModule.java
index 1aef920..8d3500a 100644
--- a/packages/SystemUI/src/com/android/systemui/qs/dagger/QSModule.java
+++ b/packages/SystemUI/src/com/android/systemui/qs/dagger/QSModule.java
@@ -29,6 +29,7 @@
 import com.android.systemui.qs.QSHost;
 import com.android.systemui.qs.ReduceBrightColorsController;
 import com.android.systemui.qs.external.QSExternalModule;
+import com.android.systemui.qs.panels.dagger.PanelsModule;
 import com.android.systemui.qs.pipeline.dagger.QSPipelineModule;
 import com.android.systemui.qs.tileimpl.QSTileImpl;
 import com.android.systemui.qs.tiles.di.QSTilesModule;
@@ -44,21 +45,22 @@
 import com.android.systemui.statusbar.policy.WalletController;
 import com.android.systemui.util.settings.SecureSettings;
 
-import java.util.Map;
-
-import javax.inject.Named;
-
 import dagger.Binds;
 import dagger.Module;
 import dagger.Provides;
 import dagger.multibindings.Multibinds;
 
+import java.util.Map;
+
+import javax.inject.Named;
+
 /**
  * Module for QS dependencies
  */
 @Module(subcomponents = {QSFragmentComponent.class, QSSceneComponent.class},
         includes = {
                 MediaModule.class,
+                PanelsModule.class,
                 QSExternalModule.class,
                 QSFlagsModule.class,
                 QSHostModule.class,
diff --git a/packages/SystemUI/src/com/android/systemui/qs/panels/dagger/PanelsModule.kt b/packages/SystemUI/src/com/android/systemui/qs/panels/dagger/PanelsModule.kt
new file mode 100644
index 0000000..1307296
--- /dev/null
+++ b/packages/SystemUI/src/com/android/systemui/qs/panels/dagger/PanelsModule.kt
@@ -0,0 +1,27 @@
+/*
+ * Copyright (C) 2024 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.systemui.qs.panels.dagger
+
+import com.android.systemui.qs.panels.data.repository.IconTilesRepository
+import com.android.systemui.qs.panels.data.repository.IconTilesRepositoryImpl
+import dagger.Binds
+import dagger.Module
+
+@Module
+interface PanelsModule {
+    @Binds fun bindIconTilesRepository(impl: IconTilesRepositoryImpl): IconTilesRepository
+}
diff --git a/packages/SystemUI/src/com/android/systemui/qs/panels/data/repository/IconTilesRepository.kt b/packages/SystemUI/src/com/android/systemui/qs/panels/data/repository/IconTilesRepository.kt
new file mode 100644
index 0000000..92f87e7
--- /dev/null
+++ b/packages/SystemUI/src/com/android/systemui/qs/panels/data/repository/IconTilesRepository.kt
@@ -0,0 +1,53 @@
+/*
+ * Copyright (C) 2024 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.systemui.qs.panels.data.repository
+
+import com.android.systemui.dagger.SysUISingleton
+import com.android.systemui.qs.pipeline.shared.TileSpec
+import javax.inject.Inject
+import kotlinx.coroutines.flow.Flow
+import kotlinx.coroutines.flow.flowOf
+
+/** Repository for retrieving the list of [TileSpec] to be displayed as icons. */
+interface IconTilesRepository {
+    val iconTilesSpecs: Flow<Set<TileSpec>>
+}
+
+@SysUISingleton
+class IconTilesRepositoryImpl @Inject constructor() : IconTilesRepository {
+
+    /** Set of toggleable tiles that are suitable for being shown as an icon. */
+    override val iconTilesSpecs: Flow<Set<TileSpec>> =
+        flowOf(
+            setOf(
+                TileSpec.create("airplane"),
+                TileSpec.create("battery"),
+                TileSpec.create("cameratoggle"),
+                TileSpec.create("cast"),
+                TileSpec.create("color_correction"),
+                TileSpec.create("inversion"),
+                TileSpec.create("saver"),
+                TileSpec.create("dnd"),
+                TileSpec.create("flashlight"),
+                TileSpec.create("location"),
+                TileSpec.create("mictoggle"),
+                TileSpec.create("nfc"),
+                TileSpec.create("night"),
+                TileSpec.create("rotation")
+            )
+        )
+}
diff --git a/packages/SystemUI/src/com/android/systemui/qs/panels/domain/interactor/IconTilesInteractor.kt b/packages/SystemUI/src/com/android/systemui/qs/panels/domain/interactor/IconTilesInteractor.kt
new file mode 100644
index 0000000..367c670
--- /dev/null
+++ b/packages/SystemUI/src/com/android/systemui/qs/panels/domain/interactor/IconTilesInteractor.kt
@@ -0,0 +1,29 @@
+/*
+ * Copyright (C) 2024 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.systemui.qs.panels.domain.interactor
+
+import com.android.systemui.dagger.SysUISingleton
+import com.android.systemui.qs.panels.data.repository.IconTilesRepository
+import com.android.systemui.qs.pipeline.shared.TileSpec
+import javax.inject.Inject
+import kotlinx.coroutines.flow.Flow
+
+/** Interactor for retrieving the list of [TileSpec] to be displayed as icons. */
+@SysUISingleton
+class IconTilesInteractor @Inject constructor(private val repo: IconTilesRepository) {
+    val iconTilesSpecs: Flow<Set<TileSpec>> = repo.iconTilesSpecs
+}
diff --git a/packages/SystemUI/src/com/android/systemui/qs/tileimpl/QSTileViewImpl.kt b/packages/SystemUI/src/com/android/systemui/qs/tileimpl/QSTileViewImpl.kt
index 2360f27..3004485 100644
--- a/packages/SystemUI/src/com/android/systemui/qs/tileimpl/QSTileViewImpl.kt
+++ b/packages/SystemUI/src/com/android/systemui/qs/tileimpl/QSTileViewImpl.kt
@@ -620,7 +620,7 @@
                 showRippleEffect = false
                 setOnTouchListener(longPressEffect)
                 if (!longPressEffectViewBinder.isBound) {
-                    longPressEffectViewBinder.bind(this, longPressEffect)
+                    longPressEffectViewBinder.bind(this, state.spec, longPressEffect)
                 }
             } else {
                 // Long-press effects might have been enabled before but the new state does not
diff --git a/packages/SystemUI/src/com/android/systemui/qs/tileimpl/SubtitleArrayMapping.kt b/packages/SystemUI/src/com/android/systemui/qs/tileimpl/SubtitleArrayMapping.kt
index 15b8cfb..f34389e 100644
--- a/packages/SystemUI/src/com/android/systemui/qs/tileimpl/SubtitleArrayMapping.kt
+++ b/packages/SystemUI/src/com/android/systemui/qs/tileimpl/SubtitleArrayMapping.kt
@@ -52,6 +52,7 @@
         subtitleIdsMap["color_correction"] = R.array.tile_states_color_correction
         subtitleIdsMap["dream"] = R.array.tile_states_dream
         subtitleIdsMap["font_scaling"] = R.array.tile_states_font_scaling
+        subtitleIdsMap["hearing_devices"] = R.array.tile_states_hearing_devices
     }
 
     /** Get the subtitle resource id of the given tile */
diff --git a/packages/SystemUI/src/com/android/systemui/qs/tiles/HearingDevicesTile.java b/packages/SystemUI/src/com/android/systemui/qs/tiles/HearingDevicesTile.java
new file mode 100644
index 0000000..81a2026
--- /dev/null
+++ b/packages/SystemUI/src/com/android/systemui/qs/tiles/HearingDevicesTile.java
@@ -0,0 +1,100 @@
+/*
+ * Copyright (C) 2024 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.systemui.qs.tiles;
+
+import android.content.Intent;
+import android.os.Handler;
+import android.os.Looper;
+import android.provider.Settings;
+import android.view.View;
+
+import androidx.annotation.Nullable;
+
+import com.android.internal.logging.MetricsLogger;
+import com.android.systemui.Flags;
+import com.android.systemui.accessibility.hearingaid.HearingDevicesDialogManager;
+import com.android.systemui.dagger.qualifiers.Background;
+import com.android.systemui.dagger.qualifiers.Main;
+import com.android.systemui.plugins.ActivityStarter;
+import com.android.systemui.plugins.FalsingManager;
+import com.android.systemui.plugins.qs.QSTile.State;
+import com.android.systemui.plugins.statusbar.StatusBarStateController;
+import com.android.systemui.qs.QSHost;
+import com.android.systemui.qs.QsEventLogger;
+import com.android.systemui.qs.logging.QSLogger;
+import com.android.systemui.qs.tileimpl.QSTileImpl;
+import com.android.systemui.res.R;
+
+import javax.inject.Inject;
+
+/** Quick settings tile: Hearing Devices **/
+public class HearingDevicesTile extends QSTileImpl<State> {
+
+    public static final String TILE_SPEC = "hearing_devices";
+
+    private final HearingDevicesDialogManager mDialogManager;
+
+    @Inject
+    public HearingDevicesTile(
+            QSHost host,
+            QsEventLogger uiEventLogger,
+            @Background Looper backgroundLooper,
+            @Main Handler mainHandler,
+            FalsingManager falsingManager,
+            MetricsLogger metricsLogger,
+            StatusBarStateController statusBarStateController,
+            ActivityStarter activityStarter,
+            QSLogger qsLogger,
+            HearingDevicesDialogManager hearingDevicesDialogManager
+    ) {
+        super(host, uiEventLogger, backgroundLooper, mainHandler, falsingManager, metricsLogger,
+                statusBarStateController, activityStarter, qsLogger);
+        mDialogManager = hearingDevicesDialogManager;
+    }
+
+    @Override
+    public State newTileState() {
+        return new State();
+    }
+
+    @Override
+    protected void handleClick(@Nullable View view) {
+        mUiHandler.post(() -> mDialogManager.showDialog(view));
+    }
+
+    @Override
+    protected void handleUpdateState(State state, Object arg) {
+        state.label = mContext.getString(R.string.quick_settings_hearing_devices_label);
+        state.icon = ResourceIcon.get(R.drawable.qs_hearing_devices_icon);
+    }
+
+    @Nullable
+    @Override
+    public Intent getLongClickIntent() {
+        return new Intent(Settings.ACTION_HEARING_DEVICES_SETTINGS);
+    }
+
+    @Override
+    public CharSequence getTileLabel() {
+        return mContext.getString(R.string.quick_settings_hearing_devices_label);
+    }
+
+    @Override
+    public boolean isAvailable() {
+        return Flags.hearingAidsQsTileDialog();
+    }
+}
diff --git a/packages/SystemUI/src/com/android/systemui/qs/tiles/QuickAccessWalletTile.java b/packages/SystemUI/src/com/android/systemui/qs/tiles/QuickAccessWalletTile.java
index 1b73225..e1b742e 100644
--- a/packages/SystemUI/src/com/android/systemui/qs/tiles/QuickAccessWalletTile.java
+++ b/packages/SystemUI/src/com/android/systemui/qs/tiles/QuickAccessWalletTile.java
@@ -182,12 +182,19 @@
 
     @Override
     public boolean isAvailable() {
+        if (isWalletRoleAvailable()) {
+            return !mPackageManager.hasSystemFeature(FEATURE_CHROME_OS);
+        }
         return mPackageManager.hasSystemFeature(PackageManager.FEATURE_NFC_HOST_CARD_EMULATION)
                 && !mPackageManager.hasSystemFeature(FEATURE_CHROME_OS)
                 && mSecureSettings.getStringForUser(NFC_PAYMENT_DEFAULT_COMPONENT,
                     UserHandle.USER_CURRENT) != null;
     }
 
+    private boolean isWalletRoleAvailable() {
+        return mHost.getUserId() == UserHandle.USER_SYSTEM && mController.isWalletRoleAvailable();
+    }
+
     @Nullable
     @Override
     public Intent getLongClickIntent() {
diff --git a/packages/SystemUI/src/com/android/systemui/scene/domain/interactor/SceneContainerOcclusionInteractor.kt b/packages/SystemUI/src/com/android/systemui/scene/domain/interactor/SceneContainerOcclusionInteractor.kt
new file mode 100644
index 0000000..a823916
--- /dev/null
+++ b/packages/SystemUI/src/com/android/systemui/scene/domain/interactor/SceneContainerOcclusionInteractor.kt
@@ -0,0 +1,86 @@
+/*
+ * Copyright (C) 2024 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.systemui.scene.domain.interactor
+
+import com.android.compose.animation.scene.ObservableTransitionState
+import com.android.compose.animation.scene.SceneKey
+import com.android.systemui.dagger.SysUISingleton
+import com.android.systemui.keyguard.domain.interactor.KeyguardOcclusionInteractor
+import com.android.systemui.keyguard.domain.interactor.KeyguardTransitionInteractor
+import com.android.systemui.keyguard.shared.model.KeyguardState
+import com.android.systemui.scene.shared.model.Scenes
+import javax.inject.Inject
+import kotlinx.coroutines.flow.Flow
+import kotlinx.coroutines.flow.combine
+import kotlinx.coroutines.flow.distinctUntilChanged
+import kotlinx.coroutines.flow.map
+import kotlinx.coroutines.flow.onStart
+
+/** Encapsulates logic regarding the occlusion state of the scene container. */
+@SysUISingleton
+class SceneContainerOcclusionInteractor
+@Inject
+constructor(
+    keyguardOcclusionInteractor: KeyguardOcclusionInteractor,
+    sceneInteractor: SceneInteractor,
+    keyguardTransitionInteractor: KeyguardTransitionInteractor,
+) {
+    /**
+     * Whether the scene container should become invisible due to "occlusion" by an in-foreground
+     * "show when locked" activity.
+     */
+    val invisibleDueToOcclusion: Flow<Boolean> =
+        combine(
+                keyguardOcclusionInteractor.isShowWhenLockedActivityOnTop,
+                sceneInteractor.transitionState,
+                keyguardTransitionInteractor
+                    .transitionValue(KeyguardState.AOD)
+                    .onStart { emit(0f) }
+                    .map { it > 0 }
+                    .distinctUntilChanged(),
+            ) { isOccludingActivityShown, sceneTransitionState, isAodFullyOrPartiallyShown ->
+                isOccludingActivityShown &&
+                    !isAodFullyOrPartiallyShown &&
+                    sceneTransitionState.canBeOccluded
+            }
+            .distinctUntilChanged()
+
+    private val ObservableTransitionState.canBeOccluded: Boolean
+        get() =
+            when (this) {
+                is ObservableTransitionState.Idle -> scene.canBeOccluded
+                is ObservableTransitionState.Transition ->
+                    fromScene.canBeOccluded && toScene.canBeOccluded
+            }
+
+    /**
+     * Whether the scene can be occluded by a "show when locked" activity. Some scenes should, on
+     * principle not be occlude-able because they render as if they are expanding on top of the
+     * occluding activity.
+     */
+    private val SceneKey.canBeOccluded: Boolean
+        get() =
+            when (this) {
+                Scenes.Bouncer -> true
+                Scenes.Communal -> true
+                Scenes.Gone -> true
+                Scenes.Lockscreen -> true
+                Scenes.QuickSettings -> false
+                Scenes.Shade -> false
+                else -> error("SceneKey \"$this\" doesn't have a mapping for canBeOccluded!")
+            }
+}
diff --git a/packages/SystemUI/src/com/android/systemui/scene/domain/startable/SceneContainerStartable.kt b/packages/SystemUI/src/com/android/systemui/scene/domain/startable/SceneContainerStartable.kt
index 42b41f8..0e4049b 100644
--- a/packages/SystemUI/src/com/android/systemui/scene/domain/startable/SceneContainerStartable.kt
+++ b/packages/SystemUI/src/com/android/systemui/scene/domain/startable/SceneContainerStartable.kt
@@ -40,6 +40,7 @@
 import com.android.systemui.plugins.FalsingManager
 import com.android.systemui.plugins.FalsingManager.FalsingBeliefListener
 import com.android.systemui.power.domain.interactor.PowerInteractor
+import com.android.systemui.scene.domain.interactor.SceneContainerOcclusionInteractor
 import com.android.systemui.scene.domain.interactor.SceneInteractor
 import com.android.systemui.scene.shared.flag.SceneContainerFlags
 import com.android.systemui.scene.shared.logger.SceneLogger
@@ -94,6 +95,7 @@
     private val deviceProvisioningInteractor: DeviceProvisioningInteractor,
     private val centralSurfaces: CentralSurfaces,
     private val headsUpInteractor: HeadsUpNotificationInteractor,
+    private val occlusionInteractor: SceneContainerOcclusionInteractor,
 ) : CoreStartable {
 
     override fun start() {
@@ -130,32 +132,36 @@
                 .distinctUntilChanged()
                 .flatMapLatest { isAllowedToBeVisible ->
                     if (isAllowedToBeVisible) {
-                        sceneInteractor.transitionState
-                            .mapNotNull { state ->
-                                when (state) {
-                                    is ObservableTransitionState.Idle -> {
-                                        if (state.scene != Scenes.Gone) {
-                                            true to "scene is not Gone"
-                                        } else {
-                                            false to "scene is Gone"
+                        combine(
+                                sceneInteractor.transitionState.mapNotNull { state ->
+                                    when (state) {
+                                        is ObservableTransitionState.Idle -> {
+                                            if (state.scene != Scenes.Gone) {
+                                                true to "scene is not Gone"
+                                            } else {
+                                                false to "scene is Gone"
+                                            }
+                                        }
+                                        is ObservableTransitionState.Transition -> {
+                                            if (state.fromScene == Scenes.Gone) {
+                                                true to "scene transitioning away from Gone"
+                                            } else {
+                                                null
+                                            }
                                         }
                                     }
-                                    is ObservableTransitionState.Transition -> {
-                                        if (state.fromScene == Scenes.Gone) {
-                                            true to "scene transitioning away from Gone"
-                                        } else {
-                                            null
-                                        }
-                                    }
-                                }
-                            }
-                            .combine(headsUpInteractor.isHeadsUpOrAnimatingAway) {
+                                },
+                                headsUpInteractor.isHeadsUpOrAnimatingAway,
+                                occlusionInteractor.invisibleDueToOcclusion,
+                            ) {
                                 visibilityForTransitionState,
-                                isHeadsUpOrAnimatingAway ->
-                                if (isHeadsUpOrAnimatingAway) {
-                                    true to "showing a HUN"
-                                } else {
-                                    visibilityForTransitionState
+                                isHeadsUpOrAnimatingAway,
+                                invisibleDueToOcclusion,
+                                ->
+                                when {
+                                    isHeadsUpOrAnimatingAway -> true to "showing a HUN"
+                                    invisibleDueToOcclusion -> false to "invisible due to occlusion"
+                                    else -> visibilityForTransitionState
                                 }
                             }
                             .distinctUntilChanged()
diff --git a/packages/SystemUI/src/com/android/systemui/scene/shared/model/SceneDataSourceDelegator.kt b/packages/SystemUI/src/com/android/systemui/scene/shared/model/SceneDataSourceDelegator.kt
index 69dce83..2fbcba9 100644
--- a/packages/SystemUI/src/com/android/systemui/scene/shared/model/SceneDataSourceDelegator.kt
+++ b/packages/SystemUI/src/com/android/systemui/scene/shared/model/SceneDataSourceDelegator.kt
@@ -20,9 +20,6 @@
 
 import com.android.compose.animation.scene.SceneKey
 import com.android.compose.animation.scene.TransitionKey
-import com.android.systemui.dagger.SysUISingleton
-import com.android.systemui.dagger.qualifiers.Application
-import javax.inject.Inject
 import kotlinx.coroutines.CoroutineScope
 import kotlinx.coroutines.ExperimentalCoroutinesApi
 import kotlinx.coroutines.flow.MutableStateFlow
@@ -36,14 +33,10 @@
  * Delegates calls to a runtime-provided [SceneDataSource] or to a no-op implementation if a
  * delegate isn't set.
  */
-@SysUISingleton
-class SceneDataSourceDelegator
-@Inject
-constructor(
-    @Application private val applicationScope: CoroutineScope,
+class SceneDataSourceDelegator(
+    applicationScope: CoroutineScope,
     config: SceneContainerConfig,
 ) : SceneDataSource {
-
     private val noOpDelegate = NoOpSceneDataSource(config.initialSceneKey)
     private val delegateMutable = MutableStateFlow<SceneDataSource>(noOpDelegate)
 
@@ -82,6 +75,7 @@
     ) : SceneDataSource {
         override val currentScene: StateFlow<SceneKey> =
             MutableStateFlow(initialSceneKey).asStateFlow()
+
         override fun changeScene(toScene: SceneKey, transitionKey: TransitionKey?) = Unit
     }
 }
diff --git a/packages/SystemUI/src/com/android/systemui/screenshot/ScreenshotController.java b/packages/SystemUI/src/com/android/systemui/screenshot/ScreenshotController.java
index b796a20..047ecb4 100644
--- a/packages/SystemUI/src/com/android/systemui/screenshot/ScreenshotController.java
+++ b/packages/SystemUI/src/com/android/systemui/screenshot/ScreenshotController.java
@@ -29,8 +29,6 @@
 import static com.android.systemui.screenshot.ScreenshotEvent.SCREENSHOT_DISMISSED_OTHER;
 import static com.android.systemui.screenshot.ScreenshotEvent.SCREENSHOT_INTERACTION_TIMEOUT;
 
-import static java.util.Objects.requireNonNull;
-
 import android.animation.Animator;
 import android.animation.AnimatorListenerAdapter;
 import android.annotation.MainThread;
@@ -41,7 +39,6 @@
 import android.app.ExitTransitionCoordinator;
 import android.app.ICompatCameraControlCallback;
 import android.app.Notification;
-import android.app.assist.AssistContent;
 import android.content.BroadcastReceiver;
 import android.content.Context;
 import android.content.Intent;
@@ -73,7 +70,6 @@
 import android.view.WindowInsets;
 import android.view.WindowManager;
 import android.view.WindowManagerGlobal;
-import android.view.accessibility.AccessibilityManager;
 import android.widget.Toast;
 import android.window.WindowContext;
 
@@ -81,11 +77,11 @@
 import com.android.internal.logging.UiEventLogger;
 import com.android.internal.policy.PhoneWindow;
 import com.android.settingslib.applications.InterestingConfigChanges;
+import com.android.systemui.broadcast.BroadcastDispatcher;
 import com.android.systemui.broadcast.BroadcastSender;
 import com.android.systemui.clipboardoverlay.ClipboardOverlayController;
 import com.android.systemui.dagger.qualifiers.Main;
 import com.android.systemui.flags.FeatureFlags;
-import com.android.systemui.flags.Flags;
 import com.android.systemui.res.R;
 import com.android.systemui.screenshot.TakeScreenshotService.RequestCallback;
 import com.android.systemui.screenshot.scroll.LongScreenshotActivity;
@@ -218,17 +214,10 @@
     // ScreenshotNotificationSmartActionsProvider.
     static final String EXTRA_ACTION_TYPE = "android:screenshot_action_type";
     static final String EXTRA_ID = "android:screenshot_id";
-    static final String ACTION_TYPE_DELETE = "Delete";
-    static final String ACTION_TYPE_SHARE = "Share";
-    static final String ACTION_TYPE_EDIT = "Edit";
     static final String EXTRA_SMART_ACTIONS_ENABLED = "android:smart_actions_enabled";
-    static final String EXTRA_OVERRIDE_TRANSITION = "android:screenshot_override_transition";
     static final String EXTRA_ACTION_INTENT = "android:screenshot_action_intent";
     static final String EXTRA_ACTION_INTENT_FILLIN = "android:screenshot_action_intent_fillin";
 
-    static final String SCREENSHOT_URI_ID = "android:screenshot_uri_id";
-    static final String EXTRA_CANCEL_NOTIFICATION = "android:screenshot_cancel_notification";
-    static final String EXTRA_DISALLOW_ENTER_PIP = "android:screenshot_disallow_enter_pip";
 
     // From WizardManagerHelper.java
     private static final String SETTINGS_SECURE_USER_SETUP_COMPLETE = "user_setup_complete";
@@ -247,10 +236,10 @@
     private final Executor mMainExecutor;
     private final ExecutorService mBgExecutor;
     private final BroadcastSender mBroadcastSender;
+    private final BroadcastDispatcher mBroadcastDispatcher;
 
     private final WindowManager mWindowManager;
     private final WindowManager.LayoutParams mWindowLayoutParams;
-    private final AccessibilityManager mAccessibilityManager;
     @Nullable
     private final ScreenshotSoundController mScreenshotSoundController;
     private final ScrollCaptureClient mScrollCaptureClient;
@@ -278,7 +267,7 @@
     private Animator mScreenshotAnimation;
     private RequestCallback mCurrentRequestCallback;
     private String mPackageName = "";
-    private BroadcastReceiver mCopyBroadcastReceiver;
+    private final BroadcastReceiver mCopyBroadcastReceiver;
 
     // When false, the screenshot is taken without showing the ui. Note that this only applies to
     // external displays, as on the default one the UI should **always** be shown.
@@ -300,6 +289,8 @@
     @AssistedInject
     ScreenshotController(
             Context context,
+            DisplayManager displayManager,
+            WindowManager windowManager,
             FeatureFlags flags,
             ScreenshotViewProxy.Factory viewProxyFactory,
             ScreenshotActionsProvider.Factory actionsProviderFactory,
@@ -315,6 +306,7 @@
             ActivityManager activityManager,
             TimeoutHandler timeoutHandler,
             BroadcastSender broadcastSender,
+            BroadcastDispatcher broadcastDispatcher,
             ScreenshotNotificationSmartActionsProvider screenshotNotificationSmartActionsProvider,
             ActionIntentExecutor actionExecutor,
             UserManager userManager,
@@ -337,16 +329,17 @@
         mScreenshotNotificationSmartActionsProvider = screenshotNotificationSmartActionsProvider;
         mBgExecutor = Executors.newSingleThreadExecutor();
         mBroadcastSender = broadcastSender;
+        mBroadcastDispatcher = broadcastDispatcher;
 
         mScreenshotHandler = timeoutHandler;
         mScreenshotHandler.setDefaultTimeoutMillis(SCREENSHOT_CORNER_DEFAULT_TIMEOUT_MILLIS);
 
 
         mDisplayId = displayId;
-        mDisplayManager = requireNonNull(context.getSystemService(DisplayManager.class));
+        mDisplayManager = displayManager;
+        mWindowManager = windowManager;
         final Context displayContext = context.createDisplayContext(getDisplay());
         mContext = (WindowContext) displayContext.createWindowContext(TYPE_SCREENSHOT, null);
-        mWindowManager = mContext.getSystemService(WindowManager.class);
         mFlags = flags;
         mActionExecutor = actionExecutor;
         mUserManager = userManager;
@@ -363,8 +356,6 @@
             mViewProxy.requestDismissal(SCREENSHOT_INTERACTION_TIMEOUT);
         });
 
-        mAccessibilityManager = AccessibilityManager.getInstance(mContext);
-
         // Setup the window that we are going to use
         mWindowLayoutParams = FloatingWindowUtil.getFloatingWindowParams();
         mWindowLayoutParams.setTitle("ScreenshotAnimation");
@@ -390,9 +381,9 @@
                 }
             }
         };
-        mContext.registerReceiver(mCopyBroadcastReceiver, new IntentFilter(
-                        ClipboardOverlayController.COPY_OVERLAY_ACTION),
-                ClipboardOverlayController.SELF_PERMISSION, null, Context.RECEIVER_NOT_EXPORTED);
+        mBroadcastDispatcher.registerReceiver(mCopyBroadcastReceiver, new IntentFilter(
+                        ClipboardOverlayController.COPY_OVERLAY_ACTION), null, null,
+                Context.RECEIVER_NOT_EXPORTED, ClipboardOverlayController.SELF_PERMISSION);
         mShowUIOnExternalDisplay = showUIOnExternalDisplay;
     }
 
@@ -442,16 +433,6 @@
 
         prepareViewForNewScreenshot(screenshot, oldPackageName);
 
-        if (mFlags.isEnabled(Flags.SCREENSHOT_METADATA) && screenshot.getTaskId() >= 0) {
-            mAssistContentRequester.requestAssistContent(screenshot.getTaskId(),
-                    new AssistContentRequester.Callback() {
-                        @Override
-                        public void onAssistContentAvailable(AssistContent assistContent) {
-                            screenshot.setContextUrl(assistContent.getWebUri());
-                        }
-                    });
-        }
-
         if (!shouldShowUi()) {
             saveScreenshotInWorkerThread(
                     screenshot.getUserHandle(), finisher, this::logSuccessOnActionsReady,
@@ -567,7 +548,7 @@
      * Release the constructed window context.
      */
     private void releaseContext() {
-        mContext.unregisterReceiver(mCopyBroadcastReceiver);
+        mBroadcastDispatcher.unregisterReceiver(mCopyBroadcastReceiver);
         mContext.release();
     }
 
@@ -615,7 +596,7 @@
         if (DEBUG_WINDOW) {
             Log.d(TAG, "setContentView: " + mViewProxy.getView());
         }
-        setContentView(mViewProxy.getView());
+        mWindow.setContentView(mViewProxy.getView());
     }
 
     private void enqueueScrollCaptureRequest(UserHandle owner) {
@@ -697,10 +678,8 @@
 
             final ScrollCaptureResponse response = mLastScrollCaptureResponse;
             mViewProxy.showScrollChip(response.getPackageName(), /* onClick */ () -> {
-                DisplayMetrics displayMetrics = new DisplayMetrics();
-                getDisplay().getRealMetrics(displayMetrics);
-                Bitmap newScreenshot = mImageCapture.captureDisplay(mDisplayId,
-                        new Rect(0, 0, displayMetrics.widthPixels, displayMetrics.heightPixels));
+                Bitmap newScreenshot =
+                        mImageCapture.captureDisplay(mDisplayId, getFullScreenRect());
 
                 if (newScreenshot != null) {
                     // delay starting scroll capture to make sure scrim is up before the app moves
@@ -797,10 +776,6 @@
         }
     }
 
-    private void setContentView(View contentView) {
-        mWindow.setContentView(contentView);
-    }
-
     @MainThread
     private void attachWindow() {
         View decorView = mWindow.getDecorView();
@@ -912,12 +887,10 @@
                     public void onFinish() {
                     }
                 };
-        Pair<ActivityOptions, ExitTransitionCoordinator> transition =
-                ActivityOptions.startSharedElementAnimation(mWindow, callbacks, null,
-                        Pair.create(mViewProxy.getScreenshotPreview(),
-                                ChooserActivity.FIRST_IMAGE_PREVIEW_TRANSITION_NAME));
 
-        return transition;
+        return ActivityOptions.startSharedElementAnimation(mWindow, callbacks, null,
+                Pair.create(mViewProxy.getScreenshotPreview(),
+                        ChooserActivity.FIRST_IMAGE_PREVIEW_TRANSITION_NAME));
     }
 
     /** Reset screenshot view and then call onCompleteRunnable */
diff --git a/packages/SystemUI/src/com/android/systemui/screenshot/ScreenshotView.java b/packages/SystemUI/src/com/android/systemui/screenshot/ScreenshotView.java
index cb2dba0..65e8457 100644
--- a/packages/SystemUI/src/com/android/systemui/screenshot/ScreenshotView.java
+++ b/packages/SystemUI/src/com/android/systemui/screenshot/ScreenshotView.java
@@ -90,7 +90,6 @@
 import com.android.internal.jank.InteractionJankMonitor;
 import com.android.internal.logging.UiEventLogger;
 import com.android.systemui.flags.FeatureFlags;
-import com.android.systemui.flags.Flags;
 import com.android.systemui.res.R;
 import com.android.systemui.screenshot.scroll.ScrollCaptureController;
 import com.android.systemui.shared.system.InputChannelCompat;
@@ -789,15 +788,8 @@
             mUiEventLogger.log(ScreenshotEvent.SCREENSHOT_SHARE_TAPPED, 0, mPackageName);
             prepareSharedTransition();
 
-            Intent shareIntent;
-            if (mFlags.isEnabled(Flags.SCREENSHOT_METADATA) && mScreenshotData != null
-                    && mScreenshotData.getContextUrl() != null) {
-                shareIntent = ActionIntentCreator.INSTANCE.createShareWithText(
-                        imageData.uri, mScreenshotData.getContextUrl().toString());
-            } else {
-                shareIntent = ActionIntentCreator.INSTANCE.createShareWithSubject(
-                        imageData.uri, imageData.subject);
-            }
+            Intent shareIntent = ActionIntentCreator.INSTANCE.createShareWithSubject(
+                    imageData.uri, imageData.subject);
             mCallbacks.onAction(shareIntent, imageData.owner, false);
 
         });
diff --git a/packages/SystemUI/src/com/android/systemui/screenshot/data/model/DisplayContentModel.kt b/packages/SystemUI/src/com/android/systemui/screenshot/data/model/DisplayContentModel.kt
new file mode 100644
index 0000000..837a661
--- /dev/null
+++ b/packages/SystemUI/src/com/android/systemui/screenshot/data/model/DisplayContentModel.kt
@@ -0,0 +1,29 @@
+/*
+ * Copyright (C) 2024 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.systemui.screenshot.data.model
+
+import android.app.ActivityTaskManager.RootTaskInfo
+
+/** Information about the tasks on a display. */
+data class DisplayContentModel(
+    /** The id of the display. */
+    val displayId: Int,
+    /** Information about the current System UI state which can affect capture. */
+    val systemUiState: SystemUiState,
+    /** A list of root tasks on the display, ordered from bottom to top along the z-axis */
+    val rootTasks: List<RootTaskInfo>,
+)
diff --git a/packages/SystemUI/src/com/android/systemui/screenshot/data/model/SystemUiState.kt b/packages/SystemUI/src/com/android/systemui/screenshot/data/model/SystemUiState.kt
new file mode 100644
index 0000000..78be6bd
--- /dev/null
+++ b/packages/SystemUI/src/com/android/systemui/screenshot/data/model/SystemUiState.kt
@@ -0,0 +1,20 @@
+/*
+ * Copyright (C) 2024 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.systemui.screenshot.data.model
+
+/** Information about SystemUI state relevant to screenshot policy. */
+data class SystemUiState(val shadeExpanded: Boolean)
diff --git a/packages/SystemUI/src/com/android/systemui/screenshot/data/repository/DisplayContentRepository.kt b/packages/SystemUI/src/com/android/systemui/screenshot/data/repository/DisplayContentRepository.kt
new file mode 100644
index 0000000..9c81b32
--- /dev/null
+++ b/packages/SystemUI/src/com/android/systemui/screenshot/data/repository/DisplayContentRepository.kt
@@ -0,0 +1,24 @@
+/*
+ * Copyright (C) 2024 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package com.android.systemui.screenshot.data.repository
+
+import com.android.systemui.screenshot.data.model.DisplayContentModel
+
+/** Provides information about tasks related to a display. */
+interface DisplayContentRepository {
+    /** Provides information about the tasks and content presented on a given display. */
+    suspend fun getDisplayContent(displayId: Int): DisplayContentModel
+}
diff --git a/packages/SystemUI/src/com/android/systemui/screenshot/data/repository/DisplayContentRepositoryImpl.kt b/packages/SystemUI/src/com/android/systemui/screenshot/data/repository/DisplayContentRepositoryImpl.kt
new file mode 100644
index 0000000..e9599dc
--- /dev/null
+++ b/packages/SystemUI/src/com/android/systemui/screenshot/data/repository/DisplayContentRepositoryImpl.kt
@@ -0,0 +1,60 @@
+/*
+ * Copyright (C) 2024 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.systemui.screenshot.data.repository
+
+import android.annotation.SuppressLint
+import android.app.ActivityTaskManager
+import android.app.IActivityTaskManager
+import com.android.systemui.dagger.qualifiers.Background
+import com.android.systemui.screenshot.data.model.DisplayContentModel
+import com.android.systemui.screenshot.data.model.SystemUiState
+import com.android.systemui.screenshot.proxy.SystemUiProxy
+import javax.inject.Inject
+import kotlinx.coroutines.CoroutineDispatcher
+import kotlinx.coroutines.withContext
+
+/**
+ * Implements DisplayTaskRepository using [IActivityTaskManager], along with [ProfileTypeRepository]
+ * and [SystemUiProxy].
+ */
+@SuppressLint("MissingPermission")
+class DisplayContentRepositoryImpl
+@Inject
+constructor(
+    private val atmService: IActivityTaskManager,
+    private val systemUiProxy: SystemUiProxy,
+    @Background private val background: CoroutineDispatcher,
+) : DisplayContentRepository {
+
+    override suspend fun getDisplayContent(displayId: Int): DisplayContentModel {
+        return withContext(background) {
+            val rootTasks = atmService.getAllRootTaskInfosOnDisplay(displayId)
+            toDisplayTasksModel(displayId, rootTasks)
+        }
+    }
+
+    private suspend fun toDisplayTasksModel(
+        displayId: Int,
+        rootTasks: List<ActivityTaskManager.RootTaskInfo>,
+    ): DisplayContentModel {
+        return DisplayContentModel(
+            displayId,
+            SystemUiState(systemUiProxy.isNotificationShadeExpanded()),
+            rootTasks
+        )
+    }
+}
diff --git a/packages/SystemUI/src/com/android/systemui/screenshot/policy/ScreenshotPolicyModule.kt b/packages/SystemUI/src/com/android/systemui/screenshot/policy/ScreenshotPolicyModule.kt
index 39b07e3..bc71ab7 100644
--- a/packages/SystemUI/src/com/android/systemui/screenshot/policy/ScreenshotPolicyModule.kt
+++ b/packages/SystemUI/src/com/android/systemui/screenshot/policy/ScreenshotPolicyModule.kt
@@ -21,6 +21,8 @@
 import com.android.systemui.screenshot.RequestProcessor
 import com.android.systemui.screenshot.ScreenshotPolicy
 import com.android.systemui.screenshot.ScreenshotRequestProcessor
+import com.android.systemui.screenshot.data.repository.DisplayContentRepository
+import com.android.systemui.screenshot.data.repository.DisplayContentRepositoryImpl
 import com.android.systemui.screenshot.data.repository.ProfileTypeRepository
 import com.android.systemui.screenshot.data.repository.ProfileTypeRepositoryImpl
 import dagger.Binds
@@ -45,4 +47,8 @@
             return RequestProcessor(imageCapture, policyProvider.get())
         }
     }
+
+    @Binds
+    @SysUISingleton
+    fun bindDisplayContentRepository(impl: DisplayContentRepositoryImpl): DisplayContentRepository
 }
diff --git a/packages/SystemUI/src/com/android/systemui/shade/GlanceableHubContainerController.kt b/packages/SystemUI/src/com/android/systemui/shade/GlanceableHubContainerController.kt
index 3169e9c..33cf9ba 100644
--- a/packages/SystemUI/src/com/android/systemui/shade/GlanceableHubContainerController.kt
+++ b/packages/SystemUI/src/com/android/systemui/shade/GlanceableHubContainerController.kt
@@ -26,12 +26,14 @@
 import androidx.compose.ui.platform.ComposeView
 import com.android.compose.theme.PlatformTheme
 import com.android.internal.annotations.VisibleForTesting
+import com.android.systemui.communal.dagger.Communal
 import com.android.systemui.communal.domain.interactor.CommunalInteractor
 import com.android.systemui.communal.ui.compose.CommunalContainer
 import com.android.systemui.communal.ui.viewmodel.CommunalViewModel
 import com.android.systemui.keyguard.domain.interactor.KeyguardTransitionInteractor
 import com.android.systemui.keyguard.shared.model.KeyguardState
 import com.android.systemui.res.R
+import com.android.systemui.scene.shared.model.SceneDataSourceDelegator
 import com.android.systemui.shade.domain.interactor.ShadeInteractor
 import com.android.systemui.statusbar.phone.SystemUIDialogFactory
 import com.android.systemui.util.kotlin.collectFlow
@@ -52,6 +54,7 @@
     private val keyguardTransitionInteractor: KeyguardTransitionInteractor,
     private val shadeInteractor: ShadeInteractor,
     private val powerManager: PowerManager,
+    @Communal private val dataSourceDelegator: SceneDataSourceDelegator,
 ) {
     /** The container view for the hub. This will not be initialized until [initView] is called. */
     private var communalContainerView: View? = null
@@ -125,6 +128,7 @@
                     PlatformTheme {
                         CommunalContainer(
                             viewModel = communalViewModel,
+                            dataSourceDelegator = dataSourceDelegator,
                             dialogFactory = dialogFactory,
                         )
                     }
diff --git a/packages/SystemUI/src/com/android/systemui/shade/NotificationPanelView.java b/packages/SystemUI/src/com/android/systemui/shade/NotificationPanelView.java
index c501d88..6bf5535 100644
--- a/packages/SystemUI/src/com/android/systemui/shade/NotificationPanelView.java
+++ b/packages/SystemUI/src/com/android/systemui/shade/NotificationPanelView.java
@@ -27,6 +27,8 @@
 import android.view.MotionEvent;
 import android.widget.FrameLayout;
 
+import com.android.systemui.scene.shared.flag.SceneContainerFlag;
+
 /** The shade view. */
 public final class NotificationPanelView extends FrameLayout {
     static final boolean DEBUG = false;
@@ -41,14 +43,20 @@
 
     public NotificationPanelView(Context context, AttributeSet attrs) {
         super(context, attrs);
-        setWillNotDraw(!DEBUG);
-        mAlphaPaint.setXfermode(new PorterDuffXfermode(PorterDuff.Mode.MULTIPLY));
+        if (!SceneContainerFlag.isEnabled()) {
+            setWillNotDraw(!DEBUG);
+            mAlphaPaint.setXfermode(new PorterDuffXfermode(PorterDuff.Mode.MULTIPLY));
 
-        setBackgroundColor(Color.TRANSPARENT);
+            setBackgroundColor(Color.TRANSPARENT);
+        }
     }
 
     @Override
     public void onRtlPropertiesChanged(int layoutDirection) {
+        if (SceneContainerFlag.isEnabled()) {
+            super.onRtlPropertiesChanged(layoutDirection);
+            return;
+        }
         if (mRtlChangeListener != null) {
             mRtlChangeListener.onRtlPropertielsChanged(layoutDirection);
         }
@@ -56,14 +64,19 @@
 
     @Override
     public boolean shouldDelayChildPressedState() {
+        if (SceneContainerFlag.isEnabled()) {
+            return super.shouldDelayChildPressedState();
+        }
         return true;
     }
 
     @Override
     protected void dispatchDraw(Canvas canvas) {
         super.dispatchDraw(canvas);
-        if (mCurrentPanelAlpha != 255) {
-            canvas.drawRect(0, 0, canvas.getWidth(), canvas.getHeight(), mAlphaPaint);
+        if (!SceneContainerFlag.isEnabled()) {
+            if (mCurrentPanelAlpha != 255) {
+                canvas.drawRect(0, 0, canvas.getWidth(), canvas.getHeight(), mAlphaPaint);
+            }
         }
     }
 
@@ -83,6 +96,9 @@
 
     @Override
     public boolean hasOverlappingRendering() {
+        if (SceneContainerFlag.isEnabled()) {
+            return super.hasOverlappingRendering();
+        }
         return !mDozing;
     }
 
@@ -102,6 +118,9 @@
 
     @Override
     public boolean onInterceptTouchEvent(MotionEvent event) {
+        if (SceneContainerFlag.isEnabled()) {
+            return super.onInterceptTouchEvent(event);
+        }
         return mTouchHandler.onInterceptTouchEvent(event);
     }
 
@@ -113,7 +132,9 @@
     @Override
     public void dispatchConfigurationChanged(Configuration newConfig) {
         super.dispatchConfigurationChanged(newConfig);
-        mOnConfigurationChangedListener.onConfigurationChanged(newConfig);
+        if (!SceneContainerFlag.isEnabled()) {
+            mOnConfigurationChangedListener.onConfigurationChanged(newConfig);
+        }
     }
 
     /** Callback for right-to-left setting changes. */
diff --git a/packages/SystemUI/src/com/android/systemui/shade/NotificationPanelViewController.java b/packages/SystemUI/src/com/android/systemui/shade/NotificationPanelViewController.java
index e4f5aeb..0ddea30 100644
--- a/packages/SystemUI/src/com/android/systemui/shade/NotificationPanelViewController.java
+++ b/packages/SystemUI/src/com/android/systemui/shade/NotificationPanelViewController.java
@@ -982,6 +982,7 @@
                 });
         mAlternateBouncerInteractor = alternateBouncerInteractor;
         dumpManager.registerDumpable(this);
+        SceneContainerFlag.assertInLegacyMode();
     }
 
     private void unlockAnimationFinished() {
@@ -3555,9 +3556,9 @@
     }
 
     @Override
-    public ViewPropertyAnimator fadeOut(long startDelayMs, long durationMs, Runnable endAction) {
+    public void fadeOut(long startDelayMs, long durationMs, Runnable endAction) {
         mView.animate().cancel();
-        return mView.animate().alpha(0).setStartDelay(startDelayMs).setDuration(
+        mView.animate().alpha(0).setStartDelay(startDelayMs).setDuration(
                 durationMs).setInterpolator(Interpolators.ALPHA_OUT).withLayer().withEndAction(
                 endAction);
     }
@@ -4121,9 +4122,10 @@
 
     @Override
     public void updateExpansionAndVisibility() {
-        mShadeExpansionStateManager.onPanelExpansionChanged(
-                mExpandedFraction, isExpanded(), isTracking(), mExpansionDragDownAmountPx);
-
+        if (!SceneContainerFlag.isEnabled()) {
+            mShadeExpansionStateManager.onPanelExpansionChanged(
+                    mExpandedFraction, isExpanded(), isTracking());
+        }
         updateVisibility();
     }
 
@@ -4159,7 +4161,8 @@
     }
 
     /** Sends an external (e.g. Status Bar) intercept touch event to the Shade touch handler. */
-    boolean handleExternalInterceptTouch(MotionEvent event) {
+    @Override
+    public boolean handleExternalInterceptTouch(MotionEvent event) {
         try {
             mUseExternalTouch = true;
             return mTouchHandler.onInterceptTouchEvent(event);
diff --git a/packages/SystemUI/src/com/android/systemui/shade/NotificationShadeWindowViewController.java b/packages/SystemUI/src/com/android/systemui/shade/NotificationShadeWindowViewController.java
index 59da8f1..324dfdf 100644
--- a/packages/SystemUI/src/com/android/systemui/shade/NotificationShadeWindowViewController.java
+++ b/packages/SystemUI/src/com/android/systemui/shade/NotificationShadeWindowViewController.java
@@ -52,6 +52,7 @@
 import com.android.systemui.keyguard.shared.model.TransitionState;
 import com.android.systemui.keyguard.shared.model.TransitionStep;
 import com.android.systemui.res.R;
+import com.android.systemui.shade.domain.interactor.PanelExpansionInteractor;
 import com.android.systemui.shared.animation.DisableSubpixelTextTransitionListener;
 import com.android.systemui.statusbar.DragDownHelper;
 import com.android.systemui.statusbar.LockscreenShadeTransitionController;
@@ -132,7 +133,8 @@
     private DragDownHelper mDragDownHelper;
     private boolean mExpandingBelowNotch;
     private final DockManager mDockManager;
-    private final NotificationPanelViewController mNotificationPanelViewController;
+    private final ShadeViewController mShadeViewController;
+    private final PanelExpansionInteractor mPanelExpansionInteractor;
     private final ShadeExpansionStateManager mShadeExpansionStateManager;
 
     private boolean mIsTrackingBarGesture = false;
@@ -154,7 +156,8 @@
             DockManager dockManager,
             NotificationShadeDepthController depthController,
             NotificationShadeWindowView notificationShadeWindowView,
-            NotificationPanelViewController notificationPanelViewController,
+            ShadeViewController shadeViewController,
+            PanelExpansionInteractor panelExpansionInteractor,
             ShadeExpansionStateManager shadeExpansionStateManager,
             NotificationStackScrollLayoutController notificationStackScrollLayoutController,
             StatusBarKeyguardViewManager statusBarKeyguardViewManager,
@@ -187,7 +190,8 @@
         mStatusBarStateController = statusBarStateController;
         mView = notificationShadeWindowView;
         mDockManager = dockManager;
-        mNotificationPanelViewController = notificationPanelViewController;
+        mShadeViewController = shadeViewController;
+        mPanelExpansionInteractor = panelExpansionInteractor;
         mShadeExpansionStateManager = shadeExpansionStateManager;
         mDepthController = depthController;
         mNotificationStackScrollLayoutController = notificationStackScrollLayoutController;
@@ -374,7 +378,7 @@
                 }
 
                 if (!mIsTrackingBarGesture && isDown
-                        && mNotificationPanelViewController.isFullyCollapsed()) {
+                        && mPanelExpansionInteractor.isFullyCollapsed()) {
                     float x = ev.getRawX();
                     float y = ev.getRawY();
                     if (mStatusBarViewController.touchIsWithinView(x, y)) {
@@ -447,7 +451,7 @@
                 } else {
                     bouncerShowing = mService.isBouncerShowing();
                 }
-                if (mNotificationPanelViewController.isFullyExpanded()
+                if (mPanelExpansionInteractor.isFullyExpanded()
                         && !bouncerShowing
                         && !mStatusBarStateController.isDozing()) {
                     if (mDragDownHelper.isDragDownEnabled()) {
@@ -503,7 +507,7 @@
                 cancellation.setAction(MotionEvent.ACTION_CANCEL);
                 mStackScrollLayout.onInterceptTouchEvent(cancellation);
                 if (!MigrateClocksToBlueprint.isEnabled()) {
-                    mNotificationPanelViewController.handleExternalInterceptTouch(cancellation);
+                    mShadeViewController.handleExternalInterceptTouch(cancellation);
                 }
                 cancellation.recycle();
             }
@@ -522,7 +526,7 @@
                         // we still want to finish our drag down gesture when locking the screen
                         handled |= mDragDownHelper.onTouchEvent(ev) || handled;
                     }
-                    if (!handled && mNotificationPanelViewController.handleExternalTouch(ev)) {
+                    if (!handled && mShadeViewController.handleExternalTouch(ev)) {
                         return true;
                     }
                 } else {
@@ -611,7 +615,7 @@
             // Since NotificationStackScrollLayout is now a sibling of notification_panel, we need
             // to also ask NotificationPanelViewController directly, in order to process swipe up
             // events originating from notifications
-            if (mNotificationPanelViewController.handleExternalInterceptTouch(ev)) {
+            if (mShadeViewController.handleExternalInterceptTouch(ev)) {
                 mShadeLogger.d("NSWVC: NPVC intercepted");
                 return true;
             }
diff --git a/packages/SystemUI/src/com/android/systemui/shade/ShadeControllerImpl.java b/packages/SystemUI/src/com/android/systemui/shade/ShadeControllerImpl.java
index 037dc4d..07836e4 100644
--- a/packages/SystemUI/src/com/android/systemui/shade/ShadeControllerImpl.java
+++ b/packages/SystemUI/src/com/android/systemui/shade/ShadeControllerImpl.java
@@ -127,7 +127,9 @@
     @Override
     public void animateCollapseShade(int flags, boolean force, boolean delayed,
             float speedUpFactor) {
-        if (!force && mStatusBarStateController.getState() != StatusBarState.SHADE) {
+        int statusBarState = mStatusBarStateController.getState();
+        if (!force && statusBarState != StatusBarState.SHADE
+                && statusBarState != StatusBarState.SHADE_LOCKED) {
             runPostCollapseActions();
             return;
         }
diff --git a/packages/SystemUI/src/com/android/systemui/shade/ShadeExpansionChangeEvent.kt b/packages/SystemUI/src/com/android/systemui/shade/ShadeExpansionChangeEvent.kt
index 71dfafa..d9c1f0a 100644
--- a/packages/SystemUI/src/com/android/systemui/shade/ShadeExpansionChangeEvent.kt
+++ b/packages/SystemUI/src/com/android/systemui/shade/ShadeExpansionChangeEvent.kt
@@ -23,7 +23,5 @@
     /** Whether the panel should be considered expanded */
     val expanded: Boolean,
     /** Whether the user is actively dragging the panel. */
-    val tracking: Boolean,
-    /** The amount of pixels that the user has dragged during the expansion. */
-    val dragDownPxAmount: Float
+    val tracking: Boolean
 )
diff --git a/packages/SystemUI/src/com/android/systemui/shade/ShadeExpansionStateManager.kt b/packages/SystemUI/src/com/android/systemui/shade/ShadeExpansionStateManager.kt
index df5ff5a..359ddd8 100644
--- a/packages/SystemUI/src/com/android/systemui/shade/ShadeExpansionStateManager.kt
+++ b/packages/SystemUI/src/com/android/systemui/shade/ShadeExpansionStateManager.kt
@@ -18,13 +18,13 @@
 
 import android.annotation.IntDef
 import android.os.Trace
-import android.os.Trace.TRACE_TAG_APP as TRACE_TAG
 import android.util.Log
 import androidx.annotation.FloatRange
 import com.android.systemui.dagger.SysUISingleton
 import com.android.systemui.util.Compile
 import java.util.concurrent.CopyOnWriteArrayList
 import javax.inject.Inject
+import android.os.Trace.TRACE_TAG_APP as TRACE_TAG
 
 /**
  * A class responsible for managing the notification panel's current state.
@@ -42,7 +42,6 @@
     @FloatRange(from = 0.0, to = 1.0) private var fraction: Float = 0f
     private var expanded: Boolean = false
     private var tracking: Boolean = false
-    private var dragDownPxAmount: Float = 0f
 
     /**
      * Adds a listener that will be notified when the panel expansion fraction has changed and
@@ -53,7 +52,7 @@
     @Deprecated("Use ShadeInteractor instead")
     fun addExpansionListener(listener: ShadeExpansionListener): ShadeExpansionChangeEvent {
         expansionListeners.add(listener)
-        return ShadeExpansionChangeEvent(fraction, expanded, tracking, dragDownPxAmount)
+        return ShadeExpansionChangeEvent(fraction, expanded, tracking)
     }
 
     /** Adds a listener that will be notified when the panel state has changed. */
@@ -76,8 +75,7 @@
     fun onPanelExpansionChanged(
         @FloatRange(from = 0.0, to = 1.0) fraction: Float,
         expanded: Boolean,
-        tracking: Boolean,
-        dragDownPxAmount: Float
+        tracking: Boolean
     ) {
         require(!fraction.isNaN()) { "fraction cannot be NaN" }
         val oldState = state
@@ -85,7 +83,6 @@
         this.fraction = fraction
         this.expanded = expanded
         this.tracking = tracking
-        this.dragDownPxAmount = dragDownPxAmount
 
         var fullyClosed = true
         var fullyOpened = false
@@ -111,7 +108,6 @@
                 "f=$fraction " +
                 "expanded=$expanded " +
                 "tracking=$tracking " +
-                "dragDownPxAmount=$dragDownPxAmount " +
                 "${if (fullyOpened) " fullyOpened" else ""} " +
                 if (fullyClosed) " fullyClosed" else ""
         )
@@ -124,8 +120,7 @@
             }
         }
 
-        val expansionChangeEvent =
-            ShadeExpansionChangeEvent(fraction, expanded, tracking, dragDownPxAmount)
+        val expansionChangeEvent = ShadeExpansionChangeEvent(fraction, expanded, tracking)
         expansionListeners.forEach { it.onPanelExpansionChanged(expansionChangeEvent) }
     }
 
diff --git a/packages/SystemUI/src/com/android/systemui/shade/ShadeModule.kt b/packages/SystemUI/src/com/android/systemui/shade/ShadeModule.kt
index 2d3833c..648d4b5 100644
--- a/packages/SystemUI/src/com/android/systemui/shade/ShadeModule.kt
+++ b/packages/SystemUI/src/com/android/systemui/shade/ShadeModule.kt
@@ -162,9 +162,7 @@
 
     @Binds
     @SysUISingleton
-    abstract fun bindsShadeViewController(
-        notificationPanelViewController: NotificationPanelViewController
-    ): ShadeViewController
+    abstract fun bindsShadeViewController(shadeSurface: ShadeSurface): ShadeViewController
 
     @Binds
     @SysUISingleton
diff --git a/packages/SystemUI/src/com/android/systemui/shade/ShadeSurface.kt b/packages/SystemUI/src/com/android/systemui/shade/ShadeSurface.kt
index d02c215..7346a28 100644
--- a/packages/SystemUI/src/com/android/systemui/shade/ShadeSurface.kt
+++ b/packages/SystemUI/src/com/android/systemui/shade/ShadeSurface.kt
@@ -15,7 +15,6 @@
  */
 package com.android.systemui.shade
 
-import android.view.ViewPropertyAnimator
 import com.android.systemui.shade.domain.interactor.PanelExpansionInteractor
 import com.android.systemui.shade.domain.interactor.ShadeBackActionInteractor
 import com.android.systemui.shade.domain.interactor.ShadeLockscreenInteractor
@@ -48,7 +47,7 @@
     fun cancelAnimation()
 
     /** Animates the view from its current alpha to zero then runs the runnable. */
-    fun fadeOut(startDelayMs: Long, durationMs: Long, endAction: Runnable): ViewPropertyAnimator
+    fun fadeOut(startDelayMs: Long, durationMs: Long, endAction: Runnable)
 
     /** Set whether the bouncer is showing. */
     fun setBouncerShowing(bouncerShowing: Boolean)
diff --git a/packages/SystemUI/src/com/android/systemui/shade/ShadeSurfaceImpl.kt b/packages/SystemUI/src/com/android/systemui/shade/ShadeSurfaceImpl.kt
new file mode 100644
index 0000000..adb2928
--- /dev/null
+++ b/packages/SystemUI/src/com/android/systemui/shade/ShadeSurfaceImpl.kt
@@ -0,0 +1,87 @@
+/*
+ * Copyright (C) 2024 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.systemui.shade
+
+import com.android.systemui.statusbar.GestureRecorder
+import com.android.systemui.statusbar.phone.CentralSurfaces
+import com.android.systemui.statusbar.policy.HeadsUpManager
+import javax.inject.Inject
+
+class ShadeSurfaceImpl @Inject constructor() : ShadeSurface, ShadeViewControllerEmptyImpl() {
+    override fun initDependencies(
+        centralSurfaces: CentralSurfaces,
+        recorder: GestureRecorder,
+        hideExpandedRunnable: Runnable,
+        headsUpManager: HeadsUpManager
+    ) {}
+
+    override fun cancelPendingCollapse() {
+        // Do nothing
+    }
+
+    override fun cancelAnimation() {
+        // Do nothing
+    }
+
+    override fun fadeOut(startDelayMs: Long, durationMs: Long, endAction: Runnable) {
+        // Do nothing
+    }
+
+    override fun setBouncerShowing(bouncerShowing: Boolean) {
+        // Do nothing
+    }
+
+    override fun setTouchAndAnimationDisabled(disabled: Boolean) {
+        // TODO(b/322197941): determine if still needed
+    }
+
+    override fun setWillPlayDelayedDozeAmountAnimation(willPlay: Boolean) {
+        // TODO(b/322494538): determine if still needed
+    }
+
+    override fun setDozing(dozing: Boolean, animate: Boolean) {
+        // Do nothing
+    }
+
+    override fun setImportantForAccessibility(mode: Int) {
+        // Do nothing
+    }
+
+    override fun resetTranslation() {
+        // Do nothing
+    }
+
+    override fun resetAlpha() {
+        // Do nothing
+    }
+
+    override fun onScreenTurningOn() {
+        // Do nothing
+    }
+
+    override fun onThemeChanged() {
+        // Do nothing
+    }
+
+    override fun updateExpansionAndVisibility() {
+        // Do nothing
+    }
+
+    override fun updateResources() {
+        // Do nothing
+    }
+}
diff --git a/packages/SystemUI/src/com/android/systemui/shade/ShadeViewController.kt b/packages/SystemUI/src/com/android/systemui/shade/ShadeViewController.kt
index 5b2377f..4e1edd3 100644
--- a/packages/SystemUI/src/com/android/systemui/shade/ShadeViewController.kt
+++ b/packages/SystemUI/src/com/android/systemui/shade/ShadeViewController.kt
@@ -33,14 +33,12 @@
     /** Returns whether the shade's top level view is enabled. */
     @Deprecated("No longer supported. Do not add new calls to this.") val isViewEnabled: Boolean
 
-    /** Returns whether status bar icons should be hidden when the shade is expanded. */
-    fun shouldHideStatusBarIconsWhenExpanded(): Boolean
-
     /** If the latency tracker is enabled, begins tracking expand latency. */
     @Deprecated("No longer supported. Do not add new calls to this.")
     fun startExpandLatencyTracking()
 
     /** Sets the alpha value of the shade to a value between 0 and 255. */
+    @Deprecated("No longer supported. Do not add new calls to this.")
     fun setAlpha(alpha: Int, animate: Boolean)
 
     /**
@@ -48,6 +46,7 @@
      *
      * @see .setAlpha
      */
+    @Deprecated("No longer supported. Do not add new calls to this.")
     fun setAlphaChangeAnimationEndAction(r: Runnable)
 
     /** Sets Qs ScrimEnabled and updates QS state. */
@@ -61,7 +60,7 @@
     @Deprecated("Does nothing when scene container is enabled.") fun updateSystemUiStateFlags()
 
     /** Ensures that the touchable region is updated. */
-    fun updateTouchableRegion()
+    @Deprecated("No longer supported. Do not add new calls to this.") fun updateTouchableRegion()
 
     /**
      * Sends an external (e.g. Status Bar) touch event to the Shade touch handler.
@@ -72,6 +71,8 @@
      */
     fun handleExternalTouch(event: MotionEvent): Boolean
 
+    fun handleExternalInterceptTouch(event: MotionEvent): Boolean
+
     /**
      * Triggered when an input focus transfer gesture has started.
      *
diff --git a/packages/SystemUI/src/com/android/systemui/shade/ShadeViewControllerEmptyImpl.kt b/packages/SystemUI/src/com/android/systemui/shade/ShadeViewControllerEmptyImpl.kt
index e037c70..0c41efd 100644
--- a/packages/SystemUI/src/com/android/systemui/shade/ShadeViewControllerEmptyImpl.kt
+++ b/packages/SystemUI/src/com/android/systemui/shade/ShadeViewControllerEmptyImpl.kt
@@ -28,7 +28,7 @@
 import kotlinx.coroutines.flow.flowOf
 
 /** Empty implementation of ShadeViewController for variants with no shade. */
-class ShadeViewControllerEmptyImpl @Inject constructor() :
+open class ShadeViewControllerEmptyImpl @Inject constructor() :
     ShadeViewController,
     ShadeBackActionInteractor,
     ShadeLockscreenInteractor,
@@ -81,6 +81,10 @@
     override fun handleExternalTouch(event: MotionEvent): Boolean {
         return false
     }
+    override fun handleExternalInterceptTouch(event: MotionEvent): Boolean {
+        return false
+    }
+
     override fun startInputFocusTransfer() {}
     override fun cancelInputFocusTransfer() {}
     override fun finishInputFocusTransfer(velocity: Float) {}
diff --git a/packages/SystemUI/src/com/android/systemui/shade/domain/interactor/PanelExpansionInteractor.kt b/packages/SystemUI/src/com/android/systemui/shade/domain/interactor/PanelExpansionInteractor.kt
index 6611303..dfdf2ad 100644
--- a/packages/SystemUI/src/com/android/systemui/shade/domain/interactor/PanelExpansionInteractor.kt
+++ b/packages/SystemUI/src/com/android/systemui/shade/domain/interactor/PanelExpansionInteractor.kt
@@ -72,4 +72,8 @@
 
     /** Returns the StatusBarState. Note: System UI was formerly known simply as Status Bar. */
     @Deprecated("Use SceneInteractor or ShadeInteractor instead") val barState: Int
+
+    /** Returns whether status bar icons should be hidden when the shade is expanded. */
+    @Deprecated("No longer supported. Do not add new calls to this.")
+    fun shouldHideStatusBarIconsWhenExpanded(): Boolean
 }
diff --git a/packages/SystemUI/src/com/android/systemui/shade/domain/interactor/PanelExpansionInteractorImpl.kt b/packages/SystemUI/src/com/android/systemui/shade/domain/interactor/PanelExpansionInteractorImpl.kt
index 561d0bc..58bcd2e 100644
--- a/packages/SystemUI/src/com/android/systemui/shade/domain/interactor/PanelExpansionInteractorImpl.kt
+++ b/packages/SystemUI/src/com/android/systemui/shade/domain/interactor/PanelExpansionInteractorImpl.kt
@@ -69,27 +69,21 @@
                         state.fromScene == Scenes.Gone ->
                             if (state.toScene.isExpandable()) {
                                 // Moving from Gone to a scene that can animate-expand has a
-                                // panel
-                                // expansion
-                                // that tracks with the transition.
+                                // panel expansion that tracks with the transition.
                                 state.progress
                             } else {
                                 // Moving from Gone to a scene that doesn't animate-expand
-                                // immediately makes
-                                // the panel fully expanded.
+                                // immediately makes the panel fully expanded.
                                 flowOf(1f)
                             }
                         state.toScene == Scenes.Gone ->
                             if (state.fromScene.isExpandable()) {
                                 // Moving to Gone from a scene that can animate-expand has a
-                                // panel
-                                // expansion
-                                // that tracks with the transition.
+                                // panel expansion that tracks with the transition.
                                 state.progress.map { 1 - it }
                             } else {
                                 // Moving to Gone from a scene that doesn't animate-expand
-                                // immediately makes
-                                // the panel fully collapsed.
+                                // immediately makes the panel fully collapsed.
                                 flowOf(0f)
                             }
                         else -> flowOf(1f)
@@ -126,6 +120,15 @@
     override val barState
         get() = statusBarStateController.state
 
+    @Deprecated("No longer supported. Do not add new calls to this.")
+    override fun shouldHideStatusBarIconsWhenExpanded(): Boolean {
+        if (shadeAnimationInteractor.isLaunchingActivity.value) {
+            return false
+        }
+        // TODO(b/325936094) if a HUN is showing, return false
+        return sceneInteractor.currentScene.value == Scenes.Lockscreen
+    }
+
     private fun SceneKey.isExpandable(): Boolean {
         return this == Scenes.Shade || this == Scenes.QuickSettings
     }
diff --git a/packages/SystemUI/src/com/android/systemui/shade/domain/startable/ShadeStartable.kt b/packages/SystemUI/src/com/android/systemui/shade/domain/startable/ShadeStartable.kt
index d8216dc..f3802da 100644
--- a/packages/SystemUI/src/com/android/systemui/shade/domain/startable/ShadeStartable.kt
+++ b/packages/SystemUI/src/com/android/systemui/shade/domain/startable/ShadeStartable.kt
@@ -23,13 +23,20 @@
 import com.android.systemui.dagger.qualifiers.Application
 import com.android.systemui.log.LogBuffer
 import com.android.systemui.log.dagger.ShadeTouchLog
+import com.android.systemui.scene.domain.interactor.SceneInteractor
+import com.android.systemui.scene.shared.flag.SceneContainerFlag
+import com.android.systemui.shade.ShadeExpansionStateManager
 import com.android.systemui.shade.TouchLogger.Companion.logTouchesTo
 import com.android.systemui.shade.data.repository.ShadeRepository
+import com.android.systemui.shade.domain.interactor.PanelExpansionInteractor
 import com.android.systemui.shade.shared.model.ShadeMode
 import com.android.systemui.shade.transition.ScrimShadeTransitionController
 import com.android.systemui.statusbar.policy.SplitShadeStateController
 import javax.inject.Inject
+import javax.inject.Provider
 import kotlinx.coroutines.CoroutineScope
+import kotlinx.coroutines.flow.combine
+import kotlinx.coroutines.flow.launchIn
 import kotlinx.coroutines.flow.map
 import kotlinx.coroutines.flow.onStart
 import kotlinx.coroutines.launch
@@ -43,23 +50,44 @@
     @ShadeTouchLog private val touchLog: LogBuffer,
     private val configurationRepository: ConfigurationRepository,
     private val shadeRepository: ShadeRepository,
-    private val controller: SplitShadeStateController,
+    private val splitShadeStateController: SplitShadeStateController,
     private val scrimShadeTransitionController: ScrimShadeTransitionController,
+    private val sceneInteractorProvider: Provider<SceneInteractor>,
+    private val panelExpansionInteractorProvider: Provider<PanelExpansionInteractor>,
+    private val shadeExpansionStateManager: ShadeExpansionStateManager,
 ) : CoreStartable {
 
     override fun start() {
         hydrateShadeMode()
+        hydrateShadeExpansionStateManager()
         logTouchesTo(touchLog)
         scrimShadeTransitionController.init()
     }
 
+    private fun hydrateShadeExpansionStateManager() {
+        if (SceneContainerFlag.isEnabled) {
+            combine(
+                panelExpansionInteractorProvider.get().legacyPanelExpansion,
+                sceneInteractorProvider.get().isTransitionUserInputOngoing,
+            ) { panelExpansion, tracking ->
+                shadeExpansionStateManager.onPanelExpansionChanged(
+                    fraction = panelExpansion,
+                    expanded = panelExpansion > 0f,
+                    tracking = tracking,
+                )
+            }.launchIn(applicationScope)
+        }
+    }
+
     private fun hydrateShadeMode() {
         applicationScope.launch {
             configurationRepository.onAnyConfigurationChange
                 // Force initial collection.
                 .onStart { emit(Unit) }
                 .map { applicationContext.resources }
-                .map { resources -> controller.shouldUseSplitNotificationShade(resources) }
+                .map { resources ->
+                    splitShadeStateController.shouldUseSplitNotificationShade(resources)
+                }
                 .collect { isSplitShade ->
                     shadeRepository.setShadeMode(
                         if (isSplitShade) {
diff --git a/packages/SystemUI/src/com/android/systemui/shade/transition/ScrimShadeTransitionController.kt b/packages/SystemUI/src/com/android/systemui/shade/transition/ScrimShadeTransitionController.kt
index 151e289..e38e53d 100644
--- a/packages/SystemUI/src/com/android/systemui/shade/transition/ScrimShadeTransitionController.kt
+++ b/packages/SystemUI/src/com/android/systemui/shade/transition/ScrimShadeTransitionController.kt
@@ -17,28 +17,20 @@
 package com.android.systemui.shade.transition
 
 import com.android.systemui.dagger.SysUISingleton
-import com.android.systemui.dagger.qualifiers.Application
 import com.android.systemui.dump.DumpManager
-import com.android.systemui.scene.shared.flag.SceneContainerFlag
 import com.android.systemui.shade.PanelState
 import com.android.systemui.shade.ShadeExpansionChangeEvent
 import com.android.systemui.shade.ShadeExpansionStateManager
-import com.android.systemui.shade.domain.interactor.PanelExpansionInteractor
 import com.android.systemui.statusbar.phone.ScrimController
-import dagger.Lazy
 import java.io.PrintWriter
 import javax.inject.Inject
-import kotlinx.coroutines.CoroutineScope
-import kotlinx.coroutines.launch
 
 /** Controls the scrim properties during the shade expansion transition on non-lockscreen. */
 @SysUISingleton
 class ScrimShadeTransitionController
 @Inject
 constructor(
-    @Application private val applicationScope: CoroutineScope,
     private val shadeExpansionStateManager: ShadeExpansionStateManager,
-    private val panelExpansionInteractor: Lazy<PanelExpansionInteractor>,
     private val dumpManager: DumpManager,
     private val scrimController: ScrimController,
 ) {
@@ -47,32 +39,17 @@
     private var currentPanelState: Int? = null
 
     fun init() {
-        if (SceneContainerFlag.isEnabled) {
-            applicationScope.launch {
-                panelExpansionInteractor.get().legacyPanelExpansion.collect { panelExpansion ->
-                    onPanelExpansionChanged(
-                        ShadeExpansionChangeEvent(
-                            fraction = panelExpansion,
-                            expanded = panelExpansion > 0f,
-                            tracking = true,
-                            dragDownPxAmount = 0f,
-                        )
-                    )
-                }
-            }
-        } else {
-            val currentState =
-                shadeExpansionStateManager.addExpansionListener(this::onPanelExpansionChanged)
-            onPanelExpansionChanged(currentState)
-            shadeExpansionStateManager.addStateListener(this::onPanelStateChanged)
-        }
+        val currentState =
+            shadeExpansionStateManager.addExpansionListener(this::onPanelExpansionChanged)
+        onPanelExpansionChanged(currentState)
+        shadeExpansionStateManager.addStateListener(this::onPanelStateChanged)
         dumpManager.registerDumpable(
             ScrimShadeTransitionController::class.java.simpleName,
             this::dump
         )
     }
 
-    fun onPanelStateChanged(@PanelState state: Int) {
+    private fun onPanelStateChanged(@PanelState state: Int) {
         currentPanelState = state
         onStateChanged()
     }
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/HeadsUpStatusBarView.java b/packages/SystemUI/src/com/android/systemui/statusbar/HeadsUpStatusBarView.java
index 0715dfc..8d9fab1 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/HeadsUpStatusBarView.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/HeadsUpStatusBarView.java
@@ -117,9 +117,9 @@
         mShowingEntry = entry;
 
         if (mShowingEntry != null) {
-            CharSequence text = entry.headsUpStatusBarText;
-            if (entry.isSensitive()) {
-                text = entry.headsUpStatusBarTextPublic;
+            CharSequence text = entry.getHeadsUpStatusBarText().getValue();
+            if (entry.isSensitive().getValue()) {
+                text = entry.getHeadsUpStatusBarTextPublic().getValue();
             }
             mTextView.setText(text);
             mShowingEntry.addOnSensitivityChangedListener(mOnSensitivityChangedListener);
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/LockscreenShadeTransitionController.kt b/packages/SystemUI/src/com/android/systemui/statusbar/LockscreenShadeTransitionController.kt
index fc1dc62..519d719 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/LockscreenShadeTransitionController.kt
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/LockscreenShadeTransitionController.kt
@@ -396,7 +396,7 @@
             }
             if (view is ExpandableNotificationRow) {
                 // Only drag down on sensitive views, otherwise the ExpandHelper will take this
-                return view.entry.isSensitive
+                return view.entry.isSensitive.value
             }
         }
         return false
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/NotificationRemoteInputManager.java b/packages/SystemUI/src/com/android/systemui/statusbar/NotificationRemoteInputManager.java
index 5f0b298..307e702 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/NotificationRemoteInputManager.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/NotificationRemoteInputManager.java
@@ -41,7 +41,6 @@
 import android.view.ViewParent;
 import android.widget.RemoteViews;
 import android.widget.RemoteViews.InteractionHandler;
-import android.widget.TextView;
 
 import androidx.annotation.NonNull;
 import androidx.annotation.Nullable;
@@ -473,25 +472,7 @@
             // if we still didn't find a view that is attached, let's abort.
             return false;
         }
-        int width = view.getWidth();
-        if (view instanceof TextView) {
-            // Center the reveal on the text which might be off-center from the TextView
-            TextView tv = (TextView) view;
-            if (tv.getLayout() != null) {
-                int innerWidth = (int) tv.getLayout().getLineWidth(0);
-                innerWidth += tv.getCompoundPaddingLeft() + tv.getCompoundPaddingRight();
-                width = Math.min(width, innerWidth);
-            }
-        }
-        int cx = view.getLeft() + width / 2;
-        int cy = view.getTop() + view.getHeight() / 2;
-        int w = riv.getWidth();
-        int h = riv.getHeight();
-        int r = Math.max(
-                Math.max(cx + cy, cx + (h - cy)),
-                Math.max((w - cx) + cy, (w - cx) + (h - cy)));
 
-        riv.getController().setRevealParams(new RemoteInputView.RevealParams(cx, cy, r));
         riv.getController().setPendingIntent(pendingIntent);
         riv.getController().setRemoteInput(input);
         riv.getController().setRemoteInputs(inputs);
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/OperatorNameView.java b/packages/SystemUI/src/com/android/systemui/statusbar/OperatorNameView.java
index 8d7fc98..acb5339 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/OperatorNameView.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/OperatorNameView.java
@@ -19,8 +19,6 @@
 import android.util.AttributeSet;
 import android.widget.TextView;
 
-import com.android.settingslib.WirelessUtils;
-
 /** Shows the operator name */
 public class OperatorNameView extends TextView {
     private boolean mDemoMode;
@@ -41,13 +39,14 @@
         mDemoMode = demoMode;
     }
 
-    void update(boolean showOperatorName,
+    void update(
+            boolean showOperatorName,
             boolean hasMobile,
+            boolean airplaneMode,
             OperatorNameViewController.SubInfo sub
     ) {
         setVisibility(showOperatorName ? VISIBLE : GONE);
 
-        boolean airplaneMode = WirelessUtils.isAirplaneModeOn(mContext);
         if (!hasMobile || airplaneMode) {
             setText(null);
             setVisibility(GONE);
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/OperatorNameViewController.java b/packages/SystemUI/src/com/android/systemui/statusbar/OperatorNameViewController.java
index 8afc72f..6e7d8f4 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/OperatorNameViewController.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/OperatorNameViewController.java
@@ -16,11 +16,9 @@
 
 package com.android.systemui.statusbar;
 
-import android.annotation.NonNull;
 import android.os.Bundle;
 import android.telephony.ServiceState;
 import android.telephony.SubscriptionInfo;
-import android.telephony.SubscriptionManager;
 import android.telephony.TelephonyManager;
 import android.view.View;
 
@@ -28,47 +26,60 @@
 import com.android.keyguard.KeyguardUpdateMonitorCallback;
 import com.android.systemui.demomode.DemoModeCommandReceiver;
 import com.android.systemui.plugins.DarkIconDispatcher;
-import com.android.systemui.statusbar.connectivity.IconState;
-import com.android.systemui.statusbar.connectivity.NetworkController;
-import com.android.systemui.statusbar.connectivity.SignalCallback;
 import com.android.systemui.statusbar.phone.fragment.CollapsedStatusBarFragment;
+import com.android.systemui.statusbar.pipeline.airplane.domain.interactor.AirplaneModeInteractor;
+import com.android.systemui.statusbar.pipeline.mobile.util.SubscriptionManagerProxy;
 import com.android.systemui.tuner.TunerService;
 import com.android.systemui.util.CarrierConfigTracker;
 import com.android.systemui.util.ViewController;
+import com.android.systemui.util.kotlin.JavaAdapter;
 
 import javax.inject.Inject;
 
+import kotlinx.coroutines.Job;
+
 /** Controller for {@link OperatorNameView}. */
 public class OperatorNameViewController extends ViewController<OperatorNameView> {
     private static final String KEY_SHOW_OPERATOR_NAME = "show_operator_name";
 
     private final DarkIconDispatcher mDarkIconDispatcher;
-    private final NetworkController mNetworkController;
     private final TunerService mTunerService;
     private final TelephonyManager mTelephonyManager;
     private final KeyguardUpdateMonitor mKeyguardUpdateMonitor;
     private final CarrierConfigTracker mCarrierConfigTracker;
+    private final AirplaneModeInteractor mAirplaneModeInteractor;
+    private final SubscriptionManagerProxy mSubscriptionManagerProxy;
+    private final JavaAdapter mJavaAdapter;
+
+    private Job mAirplaneModeJob;
 
     private OperatorNameViewController(OperatorNameView view,
             DarkIconDispatcher darkIconDispatcher,
-            NetworkController networkController,
             TunerService tunerService,
             TelephonyManager telephonyManager,
             KeyguardUpdateMonitor keyguardUpdateMonitor,
-            CarrierConfigTracker carrierConfigTracker) {
+            CarrierConfigTracker carrierConfigTracker,
+            AirplaneModeInteractor airplaneModeInteractor,
+            SubscriptionManagerProxy subscriptionManagerProxy,
+            JavaAdapter javaAdapter) {
         super(view);
         mDarkIconDispatcher = darkIconDispatcher;
-        mNetworkController = networkController;
         mTunerService = tunerService;
         mTelephonyManager = telephonyManager;
         mKeyguardUpdateMonitor = keyguardUpdateMonitor;
         mCarrierConfigTracker = carrierConfigTracker;
+        mAirplaneModeInteractor = airplaneModeInteractor;
+        mSubscriptionManagerProxy = subscriptionManagerProxy;
+        mJavaAdapter = javaAdapter;
     }
 
     @Override
     protected void onViewAttached() {
         mDarkIconDispatcher.addDarkReceiver(mDarkReceiver);
-        mNetworkController.addCallback(mSignalCallback);
+        mAirplaneModeJob =
+                mJavaAdapter.alwaysCollectFlow(
+                        mAirplaneModeInteractor.isAirplaneMode(),
+                        (isAirplaneMode) -> update());
         mTunerService.addTunable(mTunable, KEY_SHOW_OPERATOR_NAME);
         mKeyguardUpdateMonitor.registerCallback(mKeyguardUpdateMonitorCallback);
     }
@@ -76,7 +87,7 @@
     @Override
     protected void onViewDetached() {
         mDarkIconDispatcher.removeDarkReceiver(mDarkReceiver);
-        mNetworkController.removeCallback(mSignalCallback);
+        mAirplaneModeJob.cancel(null);
         mTunerService.removeTunable(mTunable);
         mKeyguardUpdateMonitor.removeCallback(mKeyguardUpdateMonitorCallback);
     }
@@ -87,11 +98,17 @@
                 mCarrierConfigTracker
                         .getShowOperatorNameInStatusBarConfig(defaultSubInfo.getSubId())
                         && (mTunerService.getValue(KEY_SHOW_OPERATOR_NAME, 1) != 0);
-        mView.update(showOperatorName, mTelephonyManager.isDataCapable(), getDefaultSubInfo());
+        mView.update(
+                showOperatorName,
+                mTelephonyManager.isDataCapable(),
+                mAirplaneModeInteractor.isAirplaneMode().getValue(),
+                getDefaultSubInfo()
+        );
     }
 
     private SubInfo getDefaultSubInfo() {
-        int defaultSubId = SubscriptionManager.getDefaultDataSubscriptionId();
+        int defaultSubId = mSubscriptionManagerProxy.getDefaultDataSubscriptionId();
+
         SubscriptionInfo sI = mKeyguardUpdateMonitor.getSubscriptionInfoForSubId(defaultSubId);
         return new SubInfo(
                 sI.getSubscriptionId(),
@@ -103,36 +120,44 @@
     /** Factory for constructing an {@link OperatorNameViewController}. */
     public static class Factory {
         private final DarkIconDispatcher mDarkIconDispatcher;
-        private final NetworkController mNetworkController;
         private final TunerService mTunerService;
         private final TelephonyManager mTelephonyManager;
         private final KeyguardUpdateMonitor mKeyguardUpdateMonitor;
         private final CarrierConfigTracker mCarrierConfigTracker;
+        private final AirplaneModeInteractor mAirplaneModeInteractor;
+        private final SubscriptionManagerProxy mSubscriptionManagerProxy;
+        private final JavaAdapter mJavaAdapter;
 
         @Inject
         public Factory(DarkIconDispatcher darkIconDispatcher,
-                NetworkController networkController,
                 TunerService tunerService,
                 TelephonyManager telephonyManager,
                 KeyguardUpdateMonitor keyguardUpdateMonitor,
-                CarrierConfigTracker carrierConfigTracker) {
+                CarrierConfigTracker carrierConfigTracker,
+                AirplaneModeInteractor airplaneModeInteractor,
+                SubscriptionManagerProxy subscriptionManagerProxy,
+                JavaAdapter javaAdapter) {
             mDarkIconDispatcher = darkIconDispatcher;
-            mNetworkController = networkController;
             mTunerService = tunerService;
             mTelephonyManager = telephonyManager;
             mKeyguardUpdateMonitor = keyguardUpdateMonitor;
             mCarrierConfigTracker = carrierConfigTracker;
+            mAirplaneModeInteractor = airplaneModeInteractor;
+            mSubscriptionManagerProxy = subscriptionManagerProxy;
+            mJavaAdapter = javaAdapter;
         }
 
         /** Create an {@link OperatorNameViewController}. */
         public OperatorNameViewController create(OperatorNameView view) {
             return new OperatorNameViewController(view,
                     mDarkIconDispatcher,
-                    mNetworkController,
                     mTunerService,
                     mTelephonyManager,
                     mKeyguardUpdateMonitor,
-                    mCarrierConfigTracker);
+                    mCarrierConfigTracker,
+                    mAirplaneModeInteractor,
+                    mSubscriptionManagerProxy,
+                    mJavaAdapter);
         }
     }
 
@@ -149,13 +174,6 @@
             (area, darkIntensity, tint) ->
                     mView.setTextColor(DarkIconDispatcher.getTint(area, mView, tint));
 
-    private final SignalCallback mSignalCallback = new SignalCallback() {
-        @Override
-        public void setIsAirplaneMode(@NonNull IconState icon) {
-            update();
-        }
-    };
-
     private final TunerService.Tunable mTunable = (key, newValue) -> update();
 
 
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/dagger/CentralSurfacesDependenciesModule.java b/packages/SystemUI/src/com/android/systemui/statusbar/dagger/CentralSurfacesDependenciesModule.java
index f960fca..e5b6497 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/dagger/CentralSurfacesDependenciesModule.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/dagger/CentralSurfacesDependenciesModule.java
@@ -35,9 +35,11 @@
 import com.android.systemui.dump.DumpManager;
 import com.android.systemui.media.controls.domain.pipeline.MediaDataManager;
 import com.android.systemui.power.domain.interactor.PowerInteractor;
+import com.android.systemui.scene.shared.flag.SceneContainerFlags;
 import com.android.systemui.settings.DisplayTracker;
 import com.android.systemui.shade.NotificationPanelViewController;
 import com.android.systemui.shade.ShadeSurface;
+import com.android.systemui.shade.ShadeSurfaceImpl;
 import com.android.systemui.shade.carrier.ShadeCarrierGroupController;
 import com.android.systemui.statusbar.CommandQueue;
 import com.android.systemui.statusbar.NotificationClickNotifier;
@@ -59,6 +61,8 @@
 import com.android.systemui.statusbar.phone.StatusBarRemoteInputCallback;
 import com.android.systemui.statusbar.policy.KeyguardStateController;
 
+import javax.inject.Provider;
+
 import dagger.Binds;
 import dagger.Lazy;
 import dagger.Module;
@@ -178,9 +182,20 @@
      * The {@link com.android.systemui.shade.ShadeViewController} interface is bound in
      * {@link com.android.systemui.shade.ShadeModule} so others can access it.
      */
-    @Binds
+    @Provides
     @SysUISingleton
-    ShadeSurface provideShadeSurface(NotificationPanelViewController impl);
+    static ShadeSurface provideShadeSurface(
+            SceneContainerFlags sceneContainerFlags,
+            Provider<ShadeSurfaceImpl> sceneContainerOn,
+            Provider<NotificationPanelViewController> sceneContainerOff) {
+        if (sceneContainerFlags.isEnabled()) {
+            return sceneContainerOn.get();
+        } else {
+            return sceneContainerOff.get();
+        }
+
+    }
+
 
     /** */
     @Binds
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/NotificationWakeUpCoordinator.kt b/packages/SystemUI/src/com/android/systemui/statusbar/notification/NotificationWakeUpCoordinator.kt
index 7c71864..4c66f66 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/NotificationWakeUpCoordinator.kt
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/NotificationWakeUpCoordinator.kt
@@ -23,7 +23,9 @@
 import com.android.app.animation.Interpolators
 import com.android.app.animation.InterpolatorsAndroidX
 import com.android.systemui.Dumpable
+import com.android.systemui.communal.domain.interactor.CommunalInteractor
 import com.android.systemui.dagger.SysUISingleton
+import com.android.systemui.dagger.qualifiers.Application
 import com.android.systemui.dump.DumpManager
 import com.android.systemui.plugins.statusbar.StatusBarStateController
 import com.android.systemui.shade.ShadeExpansionChangeEvent
@@ -47,11 +49,14 @@
 import javax.inject.Inject
 import kotlin.math.max
 import kotlin.math.min
+import kotlinx.coroutines.CoroutineScope
+import kotlinx.coroutines.launch
 
 @SysUISingleton
 class NotificationWakeUpCoordinator
 @Inject
 constructor(
+    @Application applicationScope: CoroutineScope,
     dumpManager: DumpManager,
     private val mHeadsUpManager: HeadsUpManager,
     private val statusBarStateController: StatusBarStateController,
@@ -60,6 +65,7 @@
     private val screenOffAnimationController: ScreenOffAnimationController,
     private val logger: NotificationWakeUpCoordinatorLogger,
     private val notifsKeyguardInteractor: NotificationsKeyguardInteractor,
+    private val communalInteractor: CommunalInteractor,
 ) :
     OnHeadsUpChangedListener,
     StatusBarStateController.StateListener,
@@ -201,6 +207,13 @@
                 }
             }
         )
+        applicationScope.launch {
+            communalInteractor.isIdleOnCommunal.collect {
+                if (!overrideDozeAmountIfCommunalShowing()) {
+                    maybeClearHardDozeAmountOverrideHidingNotifs()
+                }
+            }
+        }
     }
 
     fun setStackScroller(stackScrollerController: NotificationStackScrollLayoutController) {
@@ -302,6 +315,10 @@
             return
         }
 
+        if (overrideDozeAmountIfCommunalShowing()) {
+            return
+        }
+
         if (clearHardDozeAmountOverride()) {
             return
         }
@@ -311,9 +328,12 @@
 
     private fun setHardDozeAmountOverride(dozing: Boolean, source: String) {
         logger.logSetDozeAmountOverride(dozing = dozing, source = source)
+        val previousOverride = hardDozeAmountOverride
         hardDozeAmountOverride = if (dozing) 1f else 0f
         hardDozeAmountOverrideSource = source
-        updateDozeAmount()
+        if (previousOverride != hardDozeAmountOverride) {
+            updateDozeAmount()
+        }
     }
 
     private fun clearHardDozeAmountOverride(): Boolean {
@@ -434,6 +454,11 @@
             return
         }
 
+        if (overrideDozeAmountIfCommunalShowing()) {
+            this.state = newState
+            return
+        }
+
         maybeClearHardDozeAmountOverrideHidingNotifs()
 
         this.state = newState
@@ -471,6 +496,18 @@
         return false
     }
 
+    private fun overrideDozeAmountIfCommunalShowing(): Boolean {
+        if (communalInteractor.isIdleOnCommunal.value) {
+            if (statusBarStateController.state == StatusBarState.KEYGUARD) {
+                setHardDozeAmountOverride(dozing = true, source = "Override: communal (keyguard)")
+            } else {
+                setHardDozeAmountOverride(dozing = false, source = "Override: communal (shade)")
+            }
+            return true
+        }
+        return false
+    }
+
     /**
      * If the last [setDozeAmount] call was an override to hide notifications, then this call will
      * check for the set of states that may have caused that override, and if none of them still
@@ -483,20 +520,23 @@
             val onKeyguard = statusBarStateController.state == StatusBarState.KEYGUARD
             val dozing = statusBarStateController.isDozing
             val bypass = bypassController.bypassEnabled
+            val idleOnCommunal = communalInteractor.isIdleOnCommunal.value
             val animating =
                 screenOffAnimationController.overrideNotificationsFullyDozingOnKeyguard()
-            // Overrides are set by [overrideDozeAmountIfAnimatingScreenOff] and
-            // [overrideDozeAmountIfBypass] based on 'animating' and 'bypass' respectively, so only
-            // clear the override if both those conditions are cleared.  But also require either
+            // Overrides are set by [overrideDozeAmountIfAnimatingScreenOff],
+            // [overrideDozeAmountIfBypass] and [overrideDozeAmountIfCommunalShowing] based on
+            // 'animating', 'bypass' and 'idleOnCommunal' respectively, so only clear the override
+            // if all of those conditions are cleared.  But also require either
             // !dozing or !onKeyguard because those conditions should indicate that we intend
             // notifications to be visible, and thus it is safe to unhide them.
-            val willRemove = (!onKeyguard || !dozing) && !bypass && !animating
+            val willRemove = (!onKeyguard || !dozing) && !bypass && !animating && !idleOnCommunal
             logger.logMaybeClearHardDozeAmountOverrideHidingNotifs(
                 willRemove = willRemove,
                 onKeyguard = onKeyguard,
                 dozing = dozing,
                 bypass = bypass,
                 animating = animating,
+                idleOnCommunal = idleOnCommunal,
             )
             if (willRemove) {
                 clearHardDozeAmountOverride()
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/NotificationWakeUpCoordinatorLogger.kt b/packages/SystemUI/src/com/android/systemui/statusbar/notification/NotificationWakeUpCoordinatorLogger.kt
index 502e1d9..9619bea 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/NotificationWakeUpCoordinatorLogger.kt
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/NotificationWakeUpCoordinatorLogger.kt
@@ -95,6 +95,7 @@
         onKeyguard: Boolean,
         dozing: Boolean,
         bypass: Boolean,
+        idleOnCommunal: Boolean,
         animating: Boolean,
     ) {
         buffer.log(
@@ -103,7 +104,7 @@
             {
                 str1 =
                     "willRemove=$willRemove onKeyguard=$onKeyguard dozing=$dozing" +
-                        " bypass=$bypass animating=$animating"
+                        " bypass=$bypass animating=$animating idleOnCommunal=$idleOnCommunal"
             },
             { "maybeClearHardDozeAmountOverrideHidingNotifs() $str1" }
         )
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/NotificationEntry.java b/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/NotificationEntry.java
index c1dd992..9ce38db 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/NotificationEntry.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/NotificationEntry.java
@@ -76,6 +76,10 @@
 import java.util.List;
 import java.util.Objects;
 
+import kotlinx.coroutines.flow.MutableStateFlow;
+import kotlinx.coroutines.flow.StateFlow;
+import kotlinx.coroutines.flow.StateFlowKt;
+
 /**
  * Represents a notification that the system UI knows about
  *
@@ -150,8 +154,11 @@
     public CharSequence remoteInputTextWhenReset;
     public long lastRemoteInputSent = NOT_LAUNCHED_YET;
     public final ArraySet<Integer> mActiveAppOps = new ArraySet<>(3);
-    public CharSequence headsUpStatusBarText;
-    public CharSequence headsUpStatusBarTextPublic;
+
+    private final MutableStateFlow<CharSequence> mHeadsUpStatusBarText =
+            StateFlowKt.MutableStateFlow(null);
+    private final MutableStateFlow<CharSequence> mHeadsUpStatusBarTextPublic =
+            StateFlowKt.MutableStateFlow(null);
 
     // indicates when this entry's view was first attached to a window
     // this value will reset when the view is completely removed from the shade (ie: filtered out)
@@ -162,8 +169,8 @@
      */
     private boolean hasSentReply;
 
-    private boolean mSensitive = true;
-    private ListenerSet<OnSensitivityChangedListener> mOnSensitivityChangedListeners =
+    private final MutableStateFlow<Boolean> mSensitive = StateFlowKt.MutableStateFlow(true);
+    private final ListenerSet<OnSensitivityChangedListener> mOnSensitivityChangedListeners =
             new ListenerSet<>();
 
     private boolean mPulseSupressed;
@@ -934,6 +941,11 @@
         return Objects.equals(n.category, category);
     }
 
+    /** @see #setSensitive(boolean, boolean)  */
+    public StateFlow<Boolean> isSensitive() {
+        return mSensitive;
+    }
+
     /**
      * Set this notification to be sensitive.
      *
@@ -942,8 +954,8 @@
      */
     public void setSensitive(boolean sensitive, boolean deviceSensitive) {
         getRow().setSensitive(sensitive, deviceSensitive);
-        if (sensitive != mSensitive) {
-            mSensitive = sensitive;
+        if (sensitive != mSensitive.getValue()) {
+            mSensitive.setValue(sensitive);
             for (NotificationEntry.OnSensitivityChangedListener listener :
                     mOnSensitivityChangedListeners) {
                 listener.onSensitivityChanged(this);
@@ -951,10 +963,6 @@
         }
     }
 
-    public boolean isSensitive() {
-        return mSensitive;
-    }
-
     /** Add a listener to be notified when the entry's sensitivity changes. */
     public void addOnSensitivityChangedListener(OnSensitivityChangedListener listener) {
         mOnSensitivityChangedListeners.addIfAbsent(listener);
@@ -965,6 +973,32 @@
         mOnSensitivityChangedListeners.remove(listener);
     }
 
+    /** @see #setHeadsUpStatusBarText(CharSequence) */
+    public StateFlow<CharSequence> getHeadsUpStatusBarText() {
+        return mHeadsUpStatusBarText;
+    }
+
+    /**
+     * Sets the text to be displayed on the StatusBar, when this notification is the top pinned
+     * heads up.
+     */
+    public void setHeadsUpStatusBarText(CharSequence headsUpStatusBarText) {
+        this.mHeadsUpStatusBarText.setValue(headsUpStatusBarText);
+    }
+
+    /** @see #setHeadsUpStatusBarTextPublic(CharSequence) */
+    public StateFlow<CharSequence> getHeadsUpStatusBarTextPublic() {
+        return mHeadsUpStatusBarTextPublic;
+    }
+
+    /**
+     * Sets the text to be displayed on the StatusBar, when this notification is the top pinned
+     * heads up, and its content is sensitive right now.
+     */
+    public void setHeadsUpStatusBarTextPublic(CharSequence headsUpStatusBarTextPublic) {
+        this.mHeadsUpStatusBarTextPublic.setValue(headsUpStatusBarTextPublic);
+    }
+
     public boolean isPulseSuppressed() {
         return mPulseSupressed;
     }
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/icon/IconManager.kt b/packages/SystemUI/src/com/android/systemui/statusbar/notification/icon/IconManager.kt
index a900e45..4ebb699 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/icon/IconManager.kt
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/icon/IconManager.kt
@@ -39,13 +39,13 @@
 import com.android.systemui.statusbar.notification.collection.NotificationEntry
 import com.android.systemui.statusbar.notification.collection.notifcollection.CommonNotifCollection
 import com.android.systemui.statusbar.notification.collection.notifcollection.NotifCollectionListener
-import java.util.concurrent.ConcurrentHashMap
-import javax.inject.Inject
-import kotlin.coroutines.CoroutineContext
 import kotlinx.coroutines.CoroutineScope
 import kotlinx.coroutines.Job
 import kotlinx.coroutines.launch
 import kotlinx.coroutines.withContext
+import java.util.concurrent.ConcurrentHashMap
+import javax.inject.Inject
+import kotlin.coroutines.CoroutineContext
 
 /**
  * Inflates and updates icons associated with notifications
@@ -206,7 +206,7 @@
     private fun getIconDescriptors(entry: NotificationEntry): Pair<StatusBarIcon, StatusBarIcon> {
         val iconDescriptor = getIconDescriptor(entry, redact = false)
         val sensitiveDescriptor =
-            if (entry.isSensitive) {
+            if (entry.isSensitive.value) {
                 getIconDescriptor(entry, redact = true)
             } else {
                 iconDescriptor
@@ -376,7 +376,7 @@
         val isSmallIcon = iconDescriptor.icon.equals(entry.sbn.notification.smallIcon)
         return isImportantConversation(entry) &&
             !isSmallIcon &&
-            (!usedInSensitiveContext || !entry.isSensitive)
+            (!usedInSensitiveContext || !entry.isSensitive.value)
     }
 
     private fun isImportantConversation(entry: NotificationEntry): Boolean {
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/NotificationContentInflater.java b/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/NotificationContentInflater.java
index ded635c..31e69c9 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/NotificationContentInflater.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/NotificationContentInflater.java
@@ -988,8 +988,8 @@
             }
         }
 
-        entry.headsUpStatusBarText = result.headsUpStatusBarText;
-        entry.headsUpStatusBarTextPublic = result.headsUpStatusBarTextPublic;
+        entry.setHeadsUpStatusBarText(result.headsUpStatusBarText);
+        entry.setHeadsUpStatusBarTextPublic(result.headsUpStatusBarTextPublic);
         Trace.endAsyncSection(APPLY_TRACE_METHOD, System.identityHashCode(row));
         if (endListener != null) {
             endListener.onAsyncInflationFinished(entry);
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/ui/viewmodel/NotificationListViewModel.kt b/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/ui/viewmodel/NotificationListViewModel.kt
index 5a7433d..5ab5857 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/ui/viewmodel/NotificationListViewModel.kt
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/ui/viewmodel/NotificationListViewModel.kt
@@ -123,7 +123,10 @@
             // When the shade is closed, the footer is still present in the list, but not visible.
             // This prevents the footer from being shown when a HUN is present, while still allowing
             // the footer to be counted as part of the shade for measurements.
-            shadeInteractor.shadeExpansion.map { it == 0f }.distinctUntilChanged()
+            shadeInteractor.shadeExpansion
+                .map { it == 0f }
+                .flowOn(bgDispatcher)
+                .distinctUntilChanged()
         }
     }
 
@@ -274,5 +277,6 @@
     // TODO(b/325936094) use it for the text displayed in the StatusBar
     fun headsUpRow(key: HeadsUpRowKey): HeadsUpRowViewModel =
         HeadsUpRowViewModel(headsUpNotificationInteractor.headsUpRow(key))
+
     fun elementKeyFor(key: HeadsUpRowKey): Any = headsUpNotificationInteractor.elementKeyFor(key)
 }
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/ui/viewmodel/SharedNotificationContainerViewModel.kt b/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/ui/viewmodel/SharedNotificationContainerViewModel.kt
index 9df6f93..9f57606 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/ui/viewmodel/SharedNotificationContainerViewModel.kt
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/ui/viewmodel/SharedNotificationContainerViewModel.kt
@@ -445,7 +445,7 @@
         // All transition view models are mututally exclusive, and safe to merge
         val alphaTransitions =
             merge(
-                alternateBouncerToGoneTransitionViewModel.lockscreenAlpha(viewState),
+                alternateBouncerToGoneTransitionViewModel.notificationAlpha(viewState),
                 aodToLockscreenTransitionViewModel.notificationAlpha,
                 aodToOccludedTransitionViewModel.lockscreenAlpha(viewState),
                 dozingToLockscreenTransitionViewModel.lockscreenAlpha,
@@ -455,7 +455,7 @@
                 goneToDreamingTransitionViewModel.lockscreenAlpha,
                 goneToDozingTransitionViewModel.lockscreenAlpha,
                 lockscreenToDreamingTransitionViewModel.lockscreenAlpha,
-                lockscreenToGoneTransitionViewModel.lockscreenAlpha(viewState),
+                lockscreenToGoneTransitionViewModel.notificationAlpha(viewState),
                 lockscreenToOccludedTransitionViewModel.lockscreenAlpha,
                 lockscreenToPrimaryBouncerTransitionViewModel.lockscreenAlpha,
                 occludedToAodTransitionViewModel.lockscreenAlpha,
@@ -589,8 +589,13 @@
             combine(
                 isOnLockscreen,
                 keyguardInteractor.statusBarState,
-            ) { isOnLockscreen, statusBarState ->
-                statusBarState == SHADE_LOCKED || !isOnLockscreen
+                merge(
+                        primaryBouncerToGoneTransitionViewModel.showAllNotifications,
+                        alternateBouncerToGoneTransitionViewModel.showAllNotifications,
+                    )
+                    .onStart { emit(false) }
+            ) { isOnLockscreen, statusBarState, showAllNotifications ->
+                statusBarState == SHADE_LOCKED || !isOnLockscreen || showAllNotifications
             }
 
         return combineTransform(
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/phone/CentralSurfacesImpl.java b/packages/SystemUI/src/com/android/systemui/statusbar/phone/CentralSurfacesImpl.java
index b6f653f..2798dbf 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/CentralSurfacesImpl.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/CentralSurfacesImpl.java
@@ -164,6 +164,7 @@
 import com.android.systemui.qs.QSPanelController;
 import com.android.systemui.res.R;
 import com.android.systemui.scene.domain.interactor.WindowRootViewVisibilityInteractor;
+import com.android.systemui.scene.shared.flag.SceneContainerFlag;
 import com.android.systemui.scene.shared.flag.SceneContainerFlags;
 import com.android.systemui.scrim.ScrimView;
 import com.android.systemui.settings.UserTracker;
@@ -1256,11 +1257,13 @@
         mScreenOffAnimationController.initialize(this, mShadeSurface, mLightRevealScrim);
         updateLightRevealScrimVisibility();
 
-        mShadeSurface.initDependencies(
-                this,
-                mGestureRec,
-                mShadeController::makeExpandedInvisible,
-                mHeadsUpManager);
+        if (!SceneContainerFlag.isEnabled()) {
+            mShadeSurface.initDependencies(
+                    this,
+                    mGestureRec,
+                    mShadeController::makeExpandedInvisible,
+                    mHeadsUpManager);
+        }
 
         // Set up the quick settings tile panel
         final View container = getNotificationShadeWindowView().findViewById(R.id.qs_frame);
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/phone/StatusBarKeyguardViewManager.java b/packages/SystemUI/src/com/android/systemui/statusbar/phone/StatusBarKeyguardViewManager.java
index febe5a2..afd2415 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/StatusBarKeyguardViewManager.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/StatusBarKeyguardViewManager.java
@@ -579,7 +579,7 @@
             ViewRootImpl viewRoot = getViewRootImpl();
             if (viewRoot != null) {
                 viewRoot.getOnBackInvokedDispatcher().registerOnBackInvokedCallback(
-                        OnBackInvokedDispatcher.PRIORITY_OVERLAY, mOnBackInvokedCallback);
+                        OnBackInvokedDispatcher.PRIORITY_DEFAULT, mOnBackInvokedCallback);
                 mIsBackCallbackRegistered = true;
             } else {
                 if (DEBUG) {
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/phone/StatusBarNotificationPresenter.java b/packages/SystemUI/src/com/android/systemui/statusbar/phone/StatusBarNotificationPresenter.java
index c193783..79ea59c 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/StatusBarNotificationPresenter.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/StatusBarNotificationPresenter.java
@@ -247,7 +247,7 @@
         if (nowExpanded) {
             if (mStatusBarStateController.getState() == StatusBarState.KEYGUARD) {
                 mShadeTransitionController.goToLockedShade(clickedEntry.getRow());
-            } else if (clickedEntry.isSensitive()
+            } else if (clickedEntry.isSensitive().getValue()
                     && mDynamicPrivacyController.isInLockedDownShade()) {
                 mStatusBarStateController.setLeaveOpenOnKeyguardHide(true);
                 mActivityStarter.dismissKeyguardThenExecute(() -> false /* dismissAction */
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/phone/fragment/CollapsedStatusBarFragment.java b/packages/SystemUI/src/com/android/systemui/statusbar/phone/fragment/CollapsedStatusBarFragment.java
index 617e107..c52132f 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/fragment/CollapsedStatusBarFragment.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/fragment/CollapsedStatusBarFragment.java
@@ -46,7 +46,7 @@
 import com.android.systemui.plugins.statusbar.StatusBarStateController;
 import com.android.systemui.res.R;
 import com.android.systemui.shade.ShadeExpansionStateManager;
-import com.android.systemui.shade.ShadeViewController;
+import com.android.systemui.shade.domain.interactor.PanelExpansionInteractor;
 import com.android.systemui.statusbar.CommandQueue;
 import com.android.systemui.statusbar.OperatorNameView;
 import com.android.systemui.statusbar.OperatorNameViewController;
@@ -79,6 +79,8 @@
 import com.android.systemui.util.CarrierConfigTracker.DefaultDataSubscriptionChangedListener;
 import com.android.systemui.util.settings.SecureSettings;
 
+import kotlin.Unit;
+
 import java.io.PrintWriter;
 import java.util.ArrayList;
 import java.util.Arrays;
@@ -89,8 +91,6 @@
 
 import javax.inject.Inject;
 
-import kotlin.Unit;
-
 import kotlinx.coroutines.DisposableHandle;
 
 /**
@@ -115,7 +115,7 @@
     private PhoneStatusBarView mStatusBar;
     private final StatusBarStateController mStatusBarStateController;
     private final KeyguardStateController mKeyguardStateController;
-    private final ShadeViewController mShadeViewController;
+    private final PanelExpansionInteractor mPanelExpansionInteractor;
     private MultiSourceMinAlphaController mEndSideAlphaController;
     private LinearLayout mEndSideContent;
     private View mClockView;
@@ -227,7 +227,7 @@
             CollapsedStatusBarViewBinder collapsedStatusBarViewBinder,
             StatusBarHideIconsForBouncerManager statusBarHideIconsForBouncerManager,
             KeyguardStateController keyguardStateController,
-            ShadeViewController shadeViewController,
+            PanelExpansionInteractor panelExpansionInteractor,
             StatusBarStateController statusBarStateController,
             NotificationIconContainerStatusBarViewBinder nicViewBinder,
             CommandQueue commandQueue,
@@ -252,7 +252,7 @@
         mStatusBarHideIconsForBouncerManager = statusBarHideIconsForBouncerManager;
         mDarkIconManagerFactory = darkIconManagerFactory;
         mKeyguardStateController = keyguardStateController;
-        mShadeViewController = shadeViewController;
+        mPanelExpansionInteractor = panelExpansionInteractor;
         mStatusBarStateController = statusBarStateController;
         mNicViewBinder = nicViewBinder;
         mCommandQueue = commandQueue;
@@ -603,7 +603,7 @@
 
     private boolean shouldHideStatusBar() {
         if (!mShadeExpansionStateManager.isClosed()
-                && mShadeViewController.shouldHideStatusBarIconsWhenExpanded()) {
+                && mPanelExpansionInteractor.shouldHideStatusBarIconsWhenExpanded()) {
             return true;
         }
 
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/airplane/domain/interactor/AirplaneModeInteractor.kt b/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/airplane/domain/interactor/AirplaneModeInteractor.kt
index c6c8823..684e38e 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/airplane/domain/interactor/AirplaneModeInteractor.kt
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/airplane/domain/interactor/AirplaneModeInteractor.kt
@@ -23,6 +23,7 @@
 import com.android.systemui.statusbar.pipeline.shared.data.repository.ConnectivityRepository
 import javax.inject.Inject
 import kotlinx.coroutines.flow.Flow
+import kotlinx.coroutines.flow.StateFlow
 import kotlinx.coroutines.flow.map
 
 /**
@@ -40,7 +41,7 @@
     private val mobileConnectionsRepository: MobileConnectionsRepository,
 ) {
     /** True if the device is currently in airplane mode. */
-    val isAirplaneMode: Flow<Boolean> = airplaneModeRepository.isAirplaneMode
+    val isAirplaneMode: StateFlow<Boolean> = airplaneModeRepository.isAirplaneMode
 
     /** True if we're configured to force-hide the airplane mode icon and false otherwise. */
     val isForceHidden: Flow<Boolean> =
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/policy/RemoteInputView.java b/packages/SystemUI/src/com/android/systemui/statusbar/policy/RemoteInputView.java
index 579d43b..1fc7bf4 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/policy/RemoteInputView.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/policy/RemoteInputView.java
@@ -45,7 +45,6 @@
 import android.view.MotionEvent;
 import android.view.OnReceiveContentListener;
 import android.view.View;
-import android.view.ViewAnimationUtils;
 import android.view.ViewGroup;
 import android.view.ViewRootImpl;
 import android.view.WindowInsets;
@@ -85,9 +84,7 @@
 import com.android.systemui.statusbar.RemoteInputController;
 import com.android.systemui.statusbar.notification.collection.NotificationEntry;
 import com.android.systemui.statusbar.notification.row.wrapper.NotificationViewWrapper;
-import com.android.systemui.statusbar.notification.stack.StackStateAnimator;
 import com.android.systemui.statusbar.phone.LightBarController;
-import com.android.wm.shell.animation.Interpolators;
 
 import java.util.ArrayList;
 import java.util.Collection;
@@ -132,8 +129,6 @@
     private boolean mColorized;
     private int mLastBackgroundColor;
     private boolean mResetting;
-    @Nullable
-    private RevealParams mRevealParams;
     private Rect mContentBackgroundBounds;
     private boolean mIsAnimatingAppearance = false;
 
@@ -465,20 +460,6 @@
                 if (actionsContainer != null) actionsContainer.setAlpha(0f);
                 animator.start();
 
-            } else if (animate && mRevealParams != null && mRevealParams.radius > 0) {
-                android.animation.Animator reveal = mRevealParams.createCircularHideAnimator(this);
-                reveal.setInterpolator(Interpolators.FAST_OUT_LINEAR_IN);
-                reveal.setDuration(StackStateAnimator.ANIMATION_DURATION_CLOSE_REMOTE_INPUT);
-                reveal.addListener(new android.animation.AnimatorListenerAdapter() {
-                    @Override
-                    public void onAnimationEnd(android.animation.Animator animation) {
-                        setVisibility(GONE);
-                        if (mWrapper != null) {
-                            mWrapper.setRemoteInputVisible(false);
-                        }
-                    }
-                });
-                reveal.start();
             } else {
                 setVisibility(GONE);
                 if (doAfterDefocus != null) doAfterDefocus.run();
@@ -720,10 +701,6 @@
         mRemoved = true;
     }
 
-    public void setRevealParameters(@Nullable RevealParams revealParams) {
-        mRevealParams = revealParams;
-    }
-
     @Override
     public void dispatchStartTemporaryDetach() {
         super.dispatchStartTemporaryDetach();
@@ -1178,24 +1155,4 @@
         }
 
     }
-
-    public static class RevealParams {
-        final int centerX;
-        final int centerY;
-        final int radius;
-
-        public RevealParams(int centerX, int centerY, int radius) {
-            this.centerX = centerX;
-            this.centerY = centerY;
-            this.radius = radius;
-        }
-
-        android.animation.Animator createCircularHideAnimator(View view) {
-            return ViewAnimationUtils.createCircularReveal(view, centerX, centerY, radius, 0);
-        }
-
-        android.animation.Animator createCircularRevealAnimator(View view) {
-            return ViewAnimationUtils.createCircularReveal(view, centerX, centerY, 0, radius);
-        }
-    }
 }
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/policy/RemoteInputViewController.kt b/packages/SystemUI/src/com/android/systemui/statusbar/policy/RemoteInputViewController.kt
index bfee9ad..f619369 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/policy/RemoteInputViewController.kt
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/policy/RemoteInputViewController.kt
@@ -30,14 +30,13 @@
 import android.util.Log
 import android.view.View
 import com.android.internal.logging.UiEventLogger
-import com.android.systemui.res.R
 import com.android.systemui.flags.FeatureFlags
+import com.android.systemui.res.R
 import com.android.systemui.statusbar.NotificationRemoteInputManager
 import com.android.systemui.statusbar.RemoteInputController
 import com.android.systemui.statusbar.notification.collection.NotificationEntry
 import com.android.systemui.statusbar.notification.collection.NotificationEntry.EditedSuggestionInfo
 import com.android.systemui.statusbar.policy.RemoteInputView.NotificationRemoteInputEvent
-import com.android.systemui.statusbar.policy.RemoteInputView.RevealParams
 import com.android.systemui.statusbar.policy.dagger.RemoteInputViewScope
 import javax.inject.Inject
 
@@ -61,8 +60,6 @@
     /** Other [RemoteInput]s from the notification associated with this Controller. */
     var remoteInputs: Array<RemoteInput>?
 
-    var revealParams: RevealParams?
-
     /**
      * Sets the smart reply that should be inserted in the remote input, or `null` if the user is
      * not editing a smart reply.
@@ -91,7 +88,6 @@
         other.close()
         remoteInput = other.remoteInput
         remoteInputs = other.remoteInputs
-        revealParams = other.revealParams
         pendingIntent = other.pendingIntent
         focus()
     }
@@ -142,14 +138,6 @@
     override var pendingIntent: PendingIntent? = null
     override var remoteInputs: Array<RemoteInput>? = null
 
-    override var revealParams: RevealParams? = null
-        set(value) {
-            field = value
-            if (isBound) {
-                view.setRevealParameters(value)
-            }
-        }
-
     override val isActive: Boolean get() = view.isActive
 
     override fun bind() {
@@ -161,7 +149,6 @@
             view.setHintText(it.label)
             view.setSupportedMimeTypes(it.allowedDataTypes)
         }
-        view.setRevealParameters(revealParams)
 
         view.addOnEditTextFocusChangedListener(onFocusChangeListener)
         view.addOnSendRemoteInputListener(onSendRemoteInputListener)
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/policy/SensitiveNotificationProtectionControllerImpl.java b/packages/SystemUI/src/com/android/systemui/statusbar/policy/SensitiveNotificationProtectionControllerImpl.java
index 068e0a6..860068c 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/policy/SensitiveNotificationProtectionControllerImpl.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/policy/SensitiveNotificationProtectionControllerImpl.java
@@ -94,7 +94,8 @@
 
                         int packageUid;
                         try {
-                            packageUid = mPackageManager.getPackageUid(info.getPackageName(), 0);
+                            packageUid = mPackageManager.getPackageUidAsUser(info.getPackageName(),
+                                    info.getUserHandle().getIdentifier());
                         } catch (PackageManager.NameNotFoundException e) {
                             Log.w(LOG_TAG, "Package " + info.getPackageName() + " not found");
                             packageUid = -1;
diff --git a/packages/SystemUI/src/com/android/systemui/user/domain/interactor/UserActionsUtil.kt b/packages/SystemUI/src/com/android/systemui/user/domain/interactor/UserActionsUtil.kt
index dc004f3..d66fe891 100644
--- a/packages/SystemUI/src/com/android/systemui/user/domain/interactor/UserActionsUtil.kt
+++ b/packages/SystemUI/src/com/android/systemui/user/domain/interactor/UserActionsUtil.kt
@@ -24,50 +24,53 @@
 /** Utilities related to user management actions. */
 object UserActionsUtil {
 
-    /** Returns `true` if it's possible to add a guest user to the device; `false` otherwise. */
+    /**
+     * Returns `true` if it's possible for the given user to add a guest user to the device; `false`
+     * otherwise.
+     */
     fun canCreateGuest(
         manager: UserManager,
         repository: UserRepository,
         isUserSwitcherEnabled: Boolean,
-        isAddUsersFromLockScreenEnabled: Boolean,
+        canAddUsersWhenLockedOrDeviceUnlocked: Boolean,
     ): Boolean {
-        if (!isUserSwitcherEnabled) {
-            return false
-        }
-
-        return currentUserCanCreateUsers(manager, repository) ||
-            anyoneCanCreateUsers(manager, isAddUsersFromLockScreenEnabled)
+        return canAddMoreUsers(
+            manager,
+            repository,
+            isUserSwitcherEnabled,
+            canAddUsersWhenLockedOrDeviceUnlocked,
+            UserManager.USER_TYPE_FULL_GUEST
+        )
     }
 
-    /** Returns `true` if it's possible to add a user to the device; `false` otherwise. */
+    /**
+     * Returns `true` if it's possible for the given user to add a user to the device; `false`
+     * otherwise.
+     */
     fun canCreateUser(
         manager: UserManager,
         repository: UserRepository,
         isUserSwitcherEnabled: Boolean,
-        isAddUsersFromLockScreenEnabled: Boolean,
+        canAddUsersWhenLockedOrDeviceUnlocked: Boolean,
     ): Boolean {
-        if (!isUserSwitcherEnabled) {
-            return false
-        }
-
-        if (
-            !currentUserCanCreateUsers(manager, repository) &&
-                !anyoneCanCreateUsers(manager, isAddUsersFromLockScreenEnabled)
-        ) {
-            return false
-        }
-
-        return manager.canAddMoreUsers(UserManager.USER_TYPE_FULL_SECONDARY)
+        return canAddMoreUsers(
+            manager,
+            repository,
+            isUserSwitcherEnabled,
+            canAddUsersWhenLockedOrDeviceUnlocked,
+            UserManager.USER_TYPE_FULL_SECONDARY
+        )
     }
 
     /**
-     * Returns `true` if it's possible to add a supervised user to the device; `false` otherwise.
+     * Returns `true` if it's possible to add a supervised user to the device given the current
+     * user; false` otherwise.
      */
     fun canCreateSupervisedUser(
         manager: UserManager,
         repository: UserRepository,
         isUserSwitcherEnabled: Boolean,
-        isAddUsersFromLockScreenEnabled: Boolean,
+        canAddUsersWhenLockedOrDeviceUnlocked: Boolean,
         supervisedUserPackageName: String?
     ): Boolean {
         if (supervisedUserPackageName.isNullOrEmpty()) {
@@ -78,17 +81,30 @@
             manager,
             repository,
             isUserSwitcherEnabled,
-            isAddUsersFromLockScreenEnabled
+            canAddUsersWhenLockedOrDeviceUnlocked
         )
     }
 
-    fun canManageUsers(
+    fun canManageUsers(repository: UserRepository, isUserSwitcherEnabled: Boolean): Boolean {
+        return isUserSwitcherEnabled && repository.getSelectedUserInfo().isAdmin
+    }
+
+    /**
+     * Returns `true` if it's possible to add a user to the device for the given user type; false
+     * otherwise.
+     */
+    private fun canAddMoreUsers(
+        manager: UserManager,
         repository: UserRepository,
         isUserSwitcherEnabled: Boolean,
-        isAddUsersFromLockScreenEnabled: Boolean,
+        canAddUsersWhenLockedOrDeviceUnlocked: Boolean,
+        userType: String
     ): Boolean {
-        return isUserSwitcherEnabled &&
-            (repository.getSelectedUserInfo().isAdmin || isAddUsersFromLockScreenEnabled)
+        if (!isUserSwitcherEnabled || !canAddUsersWhenLockedOrDeviceUnlocked) {
+            return false
+        }
+
+        return currentUserCanCreateUsers(manager, repository) && manager.canAddMoreUsers(userType)
     }
 
     /**
@@ -96,28 +112,15 @@
      */
     private fun currentUserCanCreateUsers(
         manager: UserManager,
-        repository: UserRepository,
+        repository: UserRepository
     ): Boolean {
         val currentUser = repository.getSelectedUserInfo()
         if (!currentUser.isAdmin && currentUser.id != UserHandle.USER_SYSTEM) {
             return false
         }
-
-        return systemCanCreateUsers(manager)
-    }
-
-    /** Returns `true` if the system can add users to the device; `false` otherwise. */
-    private fun systemCanCreateUsers(
-        manager: UserManager,
-    ): Boolean {
-        return !manager.hasBaseUserRestriction(UserManager.DISALLOW_ADD_USER, UserHandle.SYSTEM)
-    }
-
-    /** Returns `true` if it's allowed to add users to the device at all; `false` otherwise. */
-    private fun anyoneCanCreateUsers(
-        manager: UserManager,
-        isAddUsersFromLockScreenEnabled: Boolean,
-    ): Boolean {
-        return systemCanCreateUsers(manager) && isAddUsersFromLockScreenEnabled
+        return !manager.hasUserRestrictionForUser(
+            UserManager.DISALLOW_ADD_USER,
+            UserHandle.of(currentUser.id)
+        ) && !manager.hasUserRestrictionForUser(UserManager.DISALLOW_ADD_USER, UserHandle.SYSTEM)
     }
 }
diff --git a/packages/SystemUI/src/com/android/systemui/user/domain/interactor/UserSwitcherInteractor.kt b/packages/SystemUI/src/com/android/systemui/user/domain/interactor/UserSwitcherInteractor.kt
index a122311..382bc03 100644
--- a/packages/SystemUI/src/com/android/systemui/user/domain/interactor/UserSwitcherInteractor.kt
+++ b/packages/SystemUI/src/com/android/systemui/user/domain/interactor/UserSwitcherInteractor.kt
@@ -170,7 +170,8 @@
                 keyguardInteractor.isKeyguardShowing,
             ) { _, userInfos, settings, isDeviceLocked ->
                 buildList {
-                    if (!isDeviceLocked || settings.isAddUsersFromLockscreen) {
+                    val canAccessUserSwitcher = !isDeviceLocked || settings.isAddUsersFromLockscreen
+                    if (canAccessUserSwitcher) {
                         // The device is locked and our setting to allow actions that add users
                         // from the lock-screen is not enabled. We can finish building the list
                         // here.
@@ -194,7 +195,10 @@
                             when (it) {
                                 UserActionModel.ENTER_GUEST_MODE -> {
                                     val hasGuestUser = userInfos.any { it.isGuest }
-                                    if (!hasGuestUser && canCreateGuestUser(settings)) {
+                                    if (
+                                        !hasGuestUser &&
+                                            canCreateGuestUser(settings, canAccessUserSwitcher)
+                                    ) {
                                         add(UserActionModel.ENTER_GUEST_MODE)
                                     }
                                 }
@@ -204,7 +208,7 @@
                                             manager,
                                             repository,
                                             settings.isUserSwitcherEnabled,
-                                            settings.isAddUsersFromLockscreen,
+                                            canAccessUserSwitcher
                                         )
 
                                     if (canCreateUsers) {
@@ -217,7 +221,7 @@
                                             manager,
                                             repository,
                                             settings.isUserSwitcherEnabled,
-                                            settings.isAddUsersFromLockscreen,
+                                            canAccessUserSwitcher,
                                             supervisedUserPackageName,
                                         )
                                     ) {
@@ -229,11 +233,7 @@
                         }
                     }
                     if (
-                        UserActionsUtil.canManageUsers(
-                            repository,
-                            settings.isUserSwitcherEnabled,
-                            settings.isAddUsersFromLockscreen,
-                        )
+                        UserActionsUtil.canManageUsers(repository, settings.isUserSwitcherEnabled)
                     ) {
                         add(UserActionModel.NAVIGATE_TO_USER_MANAGEMENT)
                     }
@@ -820,13 +820,16 @@
         )
     }
 
-    private fun canCreateGuestUser(settings: UserSwitcherSettingsModel): Boolean {
+    private fun canCreateGuestUser(
+        settings: UserSwitcherSettingsModel,
+        canAccessUserSwitcher: Boolean
+    ): Boolean {
         return guestUserInteractor.isGuestUserAutoCreated ||
             UserActionsUtil.canCreateGuest(
                 manager,
                 repository,
                 settings.isUserSwitcherEnabled,
-                settings.isAddUsersFromLockscreen,
+                canAccessUserSwitcher,
             )
     }
 
diff --git a/packages/SystemUI/src/com/android/systemui/volume/panel/component/bottombar/ui/viewmodel/BottomBarViewModel.kt b/packages/SystemUI/src/com/android/systemui/volume/panel/component/bottombar/ui/viewmodel/BottomBarViewModel.kt
index 0207d6e..04d7b1f 100644
--- a/packages/SystemUI/src/com/android/systemui/volume/panel/component/bottombar/ui/viewmodel/BottomBarViewModel.kt
+++ b/packages/SystemUI/src/com/android/systemui/volume/panel/component/bottombar/ui/viewmodel/BottomBarViewModel.kt
@@ -36,10 +36,15 @@
     }
 
     fun onSettingsClicked() {
-        activityStarter.startActivity(
-            Intent(Settings.ACTION_SOUND_SETTINGS)
-                .addFlags(Intent.FLAG_ACTIVITY_NEW_TASK or Intent.FLAG_ACTIVITY_REORDER_TO_FRONT),
-            true,
-        ) { volumePanelViewModel.dismissPanel() }
+        activityStarter.startActivityDismissingKeyguard(
+            /* intent = */ Intent(Settings.ACTION_SOUND_SETTINGS),
+            /* onlyProvisioned = */ false,
+            /* dismissShade = */ true,
+            /* disallowEnterPictureInPictureWhileLaunching = */ false,
+            /* callback = */ { volumePanelViewModel.dismissPanel() },
+            /* flags = */ Intent.FLAG_ACTIVITY_REORDER_TO_FRONT,
+            /* animationController = */ null,
+            /* userHandle = */ null,
+        )
     }
 }
diff --git a/packages/SystemUI/src/com/android/systemui/volume/panel/component/volume/slider/ui/viewmodel/AudioStreamSliderViewModel.kt b/packages/SystemUI/src/com/android/systemui/volume/panel/component/volume/slider/ui/viewmodel/AudioStreamSliderViewModel.kt
index 3242c28..57b5d57 100644
--- a/packages/SystemUI/src/com/android/systemui/volume/panel/component/volume/slider/ui/viewmodel/AudioStreamSliderViewModel.kt
+++ b/packages/SystemUI/src/com/android/systemui/volume/panel/component/volume/slider/ui/viewmodel/AudioStreamSliderViewModel.kt
@@ -98,7 +98,7 @@
         }
     }
 
-    private fun AudioStreamModel.toState(
+    private suspend fun AudioStreamModel.toState(
         isEnabled: Boolean,
         ringerMode: RingerMode,
     ): State {
@@ -116,7 +116,7 @@
             isEnabled = isEnabled,
             a11yStep = volumeRange.step,
             audioStreamModel = this,
-            isMutable = audioVolumeInteractor.isMutable(audioStream),
+            isMutable = audioVolumeInteractor.isAffectedByMute(audioStream),
         )
     }
 
diff --git a/packages/SystemUI/src/com/android/systemui/wallet/controller/QuickAccessWalletController.java b/packages/SystemUI/src/com/android/systemui/wallet/controller/QuickAccessWalletController.java
index 1d9b90a..ff18418 100644
--- a/packages/SystemUI/src/com/android/systemui/wallet/controller/QuickAccessWalletController.java
+++ b/packages/SystemUI/src/com/android/systemui/wallet/controller/QuickAccessWalletController.java
@@ -91,17 +91,22 @@
             @Background Executor bgExecutor,
             SecureSettings secureSettings,
             QuickAccessWalletClient quickAccessWalletClient,
-            SystemClock clock) {
+            SystemClock clock,
+            RoleManager roleManager) {
         mContext = context;
         mExecutor = executor;
         mBgExecutor = bgExecutor;
         mSecureSettings = secureSettings;
-        mRoleManager = mContext.getSystemService(RoleManager.class);
+        mRoleManager = roleManager;
         mQuickAccessWalletClient = quickAccessWalletClient;
         mClock = clock;
         mQawClientCreatedTimeMillis = mClock.elapsedRealtime();
     }
 
+    public boolean isWalletRoleAvailable() {
+        return mRoleManager.isRoleAvailable(RoleManager.ROLE_WALLET);
+    }
+
     /**
      * Returns true if the Quick Access Wallet service & feature is available.
      */
diff --git a/packages/SystemUI/tests/src/com/android/systemui/accessibility/FullscreenMagnificationControllerTest.java b/packages/SystemUI/tests/src/com/android/systemui/accessibility/FullscreenMagnificationControllerTest.java
new file mode 100644
index 0000000..12f334b
--- /dev/null
+++ b/packages/SystemUI/tests/src/com/android/systemui/accessibility/FullscreenMagnificationControllerTest.java
@@ -0,0 +1,97 @@
+/*
+ * Copyright (C) 2024 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.systemui.accessibility;
+
+import static androidx.test.platform.app.InstrumentationRegistry.getInstrumentation;
+
+import static com.google.common.truth.Truth.assertThat;
+
+import android.testing.AndroidTestingRunner;
+import android.testing.TestableLooper;
+import android.view.SurfaceControlViewHost;
+import android.view.WindowManager;
+import android.view.accessibility.AccessibilityManager;
+import android.window.InputTransferToken;
+
+import androidx.test.filters.SmallTest;
+
+import com.android.systemui.SysuiTestCase;
+
+import org.junit.After;
+import org.junit.Before;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+
+import java.util.function.Supplier;
+
+@SmallTest
+@TestableLooper.RunWithLooper
+@RunWith(AndroidTestingRunner.class)
+public class FullscreenMagnificationControllerTest extends SysuiTestCase {
+
+    private FullscreenMagnificationController mFullscreenMagnificationController;
+    private SurfaceControlViewHost mSurfaceControlViewHost;
+
+    @Before
+    public void setUp() {
+        getInstrumentation().runOnMainSync(() -> mSurfaceControlViewHost =
+                new SurfaceControlViewHost(mContext, mContext.getDisplay(),
+                        new InputTransferToken(), "FullscreenMagnification"));
+
+        Supplier<SurfaceControlViewHost> scvhSupplier = () -> mSurfaceControlViewHost;
+
+        mFullscreenMagnificationController = new FullscreenMagnificationController(
+                mContext,
+                mContext.getSystemService(AccessibilityManager.class),
+                mContext.getSystemService(WindowManager.class),
+                scvhSupplier);
+    }
+
+    @After
+    public void tearDown() {
+        getInstrumentation().runOnMainSync(
+                () -> mFullscreenMagnificationController
+                        .onFullscreenMagnificationActivationChanged(false));
+    }
+
+    @Test
+    public void onFullscreenMagnificationActivationChange_activated_visibleBorder() {
+        getInstrumentation().runOnMainSync(
+                () -> mFullscreenMagnificationController
+                        .onFullscreenMagnificationActivationChanged(true)
+        );
+
+        // Wait for Rects updated.
+        waitForIdleSync();
+        assertThat(mSurfaceControlViewHost.getView().isVisibleToUser()).isTrue();
+    }
+
+    @Test
+    public void onFullscreenMagnificationActivationChange_deactivated_invisibleBorder() {
+        getInstrumentation().runOnMainSync(
+                () -> {
+                    mFullscreenMagnificationController
+                            .onFullscreenMagnificationActivationChanged(true);
+                    mFullscreenMagnificationController
+                            .onFullscreenMagnificationActivationChanged(false);
+                }
+        );
+
+        assertThat(mSurfaceControlViewHost.getView()).isNull();
+    }
+
+}
diff --git a/packages/SystemUI/tests/src/com/android/systemui/accessibility/IMagnificationConnectionTest.java b/packages/SystemUI/tests/src/com/android/systemui/accessibility/IMagnificationConnectionTest.java
index bd49927..41d5d5d 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/accessibility/IMagnificationConnectionTest.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/accessibility/IMagnificationConnectionTest.java
@@ -71,6 +71,8 @@
     @Mock
     private WindowMagnificationController mWindowMagnificationController;
     @Mock
+    private FullscreenMagnificationController mFullscreenMagnificationController;
+    @Mock
     private MagnificationSettingsController mMagnificationSettingsController;
     @Mock
     private ModeSwitchesController mModeSwitchesController;
@@ -105,6 +107,9 @@
         mMagnification.mWindowMagnificationControllerSupplier =
                 new FakeWindowMagnificationControllerSupplier(
                         mContext.getSystemService(DisplayManager.class));
+        mMagnification.mFullscreenMagnificationControllerSupplier =
+                new FakeFullscreenMagnificationControllerSupplier(
+                        mContext.getSystemService(DisplayManager.class));
         mMagnification.mMagnificationSettingsSupplier = new FakeSettingsSupplier(
                 mContext.getSystemService(DisplayManager.class));
 
@@ -124,6 +129,15 @@
     }
 
     @Test
+    public void onFullscreenMagnificationActivationChanged_passThrough() throws RemoteException {
+        mIMagnificationConnection.onFullscreenMagnificationActivationChanged(TEST_DISPLAY, true);
+        waitForIdleSync();
+
+        verify(mFullscreenMagnificationController)
+                .onFullscreenMagnificationActivationChanged(eq(true));
+    }
+
+    @Test
     public void disableWindowMagnification_deleteWindowMagnification() throws RemoteException {
         mIMagnificationConnection.disableWindowMagnification(TEST_DISPLAY,
                 mAnimationCallback);
@@ -215,6 +229,20 @@
         }
     }
 
+
+    private class FakeFullscreenMagnificationControllerSupplier extends
+            DisplayIdIndexSupplier<FullscreenMagnificationController> {
+
+        FakeFullscreenMagnificationControllerSupplier(DisplayManager displayManager) {
+            super(displayManager);
+        }
+
+        @Override
+        protected FullscreenMagnificationController createInstance(Display display) {
+            return mFullscreenMagnificationController;
+        }
+    }
+
     private class FakeSettingsSupplier extends
             DisplayIdIndexSupplier<MagnificationSettingsController> {
 
diff --git a/packages/SystemUI/tests/src/com/android/systemui/accessibility/floatingmenu/MenuViewLayerTest.java b/packages/SystemUI/tests/src/com/android/systemui/accessibility/floatingmenu/MenuViewLayerTest.java
index de696f4..71f6081 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/accessibility/floatingmenu/MenuViewLayerTest.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/accessibility/floatingmenu/MenuViewLayerTest.java
@@ -165,7 +165,7 @@
         mMenuView = spy(
                 new MenuView(mSpyContext, mMenuViewModel, menuViewAppearance, mSecureSettings));
         // Ensure tests don't actually update metrics.
-        doNothing().when(mMenuView).incrementTexMetric(any(), anyInt());
+        doNothing().when(mMenuView).incrementTexMetric(any());
 
         mMenuViewLayer = spy(new MenuViewLayer(mSpyContext, mStubWindowManager,
                 mStubAccessibilityManager, mMenuViewModel, menuViewAppearance, mMenuView,
@@ -414,33 +414,25 @@
     @Test
     @EnableFlags(Flags.FLAG_FLOATING_MENU_DRAG_TO_EDIT)
     public void onDismissAction_incrementsTexMetricDismiss() {
-        int uid1 = 1234, uid2 = 5678;
         mMenuViewModel.onTargetFeaturesChanged(
-                List.of(new TestAccessibilityTarget(mSpyContext, uid1),
-                        new TestAccessibilityTarget(mSpyContext, uid2)));
+                List.of(new TestAccessibilityTarget(mSpyContext, 1234),
+                        new TestAccessibilityTarget(mSpyContext, 5678)));
 
         mMenuViewLayer.dispatchAccessibilityAction(R.id.action_remove_menu);
 
-        ArgumentCaptor<Integer> uidCaptor = ArgumentCaptor.forClass(Integer.class);
-        verify(mMenuView, times(2)).incrementTexMetric(eq(MenuViewLayer.TEX_METRIC_DISMISS),
-                uidCaptor.capture());
-        assertThat(uidCaptor.getAllValues()).containsExactly(uid1, uid2);
+        verify(mMenuView, times(1)).incrementTexMetric(eq(MenuViewLayer.TEX_METRIC_DISMISS));
     }
 
     @Test
     @EnableFlags(Flags.FLAG_FLOATING_MENU_DRAG_TO_EDIT)
     public void onEditAction_incrementsTexMetricEdit() {
-        int uid1 = 1234, uid2 = 5678;
         mMenuViewModel.onTargetFeaturesChanged(
-                List.of(new TestAccessibilityTarget(mSpyContext, uid1),
-                        new TestAccessibilityTarget(mSpyContext, uid2)));
+                List.of(new TestAccessibilityTarget(mSpyContext, 1234),
+                        new TestAccessibilityTarget(mSpyContext, 5678)));
 
         mMenuViewLayer.dispatchAccessibilityAction(R.id.action_edit);
 
-        ArgumentCaptor<Integer> uidCaptor = ArgumentCaptor.forClass(Integer.class);
-        verify(mMenuView, times(2)).incrementTexMetric(eq(MenuViewLayer.TEX_METRIC_EDIT),
-                uidCaptor.capture());
-        assertThat(uidCaptor.getAllValues()).containsExactly(uid1, uid2);
+        verify(mMenuView, times(1)).incrementTexMetric(eq(MenuViewLayer.TEX_METRIC_EDIT));
     }
 
     /** Simplified AccessibilityTarget for testing MenuViewLayer. */
diff --git a/packages/SystemUI/tests/src/com/android/systemui/accessibility/hearingaid/HearingDevicesDialogDelegateTest.java b/packages/SystemUI/tests/src/com/android/systemui/accessibility/hearingaid/HearingDevicesDialogDelegateTest.java
new file mode 100644
index 0000000..38e3171
--- /dev/null
+++ b/packages/SystemUI/tests/src/com/android/systemui/accessibility/hearingaid/HearingDevicesDialogDelegateTest.java
@@ -0,0 +1,86 @@
+/*
+ * Copyright (C) 2024 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.systemui.accessibility.hearingaid;
+
+import static com.android.systemui.statusbar.phone.SystemUIDialog.DEFAULT_DISMISS_ON_DEVICE_LOCK;
+
+import static com.google.common.truth.Truth.assertThat;
+
+import static org.mockito.ArgumentMatchers.any;
+import static org.mockito.Mockito.when;
+
+import android.testing.AndroidTestingRunner;
+import android.testing.TestableLooper;
+
+import androidx.test.filters.SmallTest;
+
+import com.android.systemui.SysuiTestCase;
+import com.android.systemui.animation.DialogTransitionAnimator;
+import com.android.systemui.model.SysUiState;
+import com.android.systemui.statusbar.phone.SystemUIDialog;
+import com.android.systemui.statusbar.phone.SystemUIDialogManager;
+
+import org.junit.Before;
+import org.junit.Rule;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+import org.mockito.Mock;
+import org.mockito.junit.MockitoJUnit;
+import org.mockito.junit.MockitoRule;
+
+/** Tests for {@link HearingDevicesDialogDelegate}. */
+@RunWith(AndroidTestingRunner.class)
+@TestableLooper.RunWithLooper(setAsMainLooper = true)
+@SmallTest
+public class HearingDevicesDialogDelegateTest extends SysuiTestCase {
+    @Rule
+    public MockitoRule mockito = MockitoJUnit.rule();
+
+    @Mock
+    private SystemUIDialog.Factory mSystemUIDialogFactory;
+    @Mock
+    private SystemUIDialogManager mSystemUIDialogManager;
+    @Mock
+    private SysUiState mSysUiState;
+    @Mock
+    private DialogTransitionAnimator mDialogTransitionAnimator;
+    private SystemUIDialog mDialog;
+    private HearingDevicesDialogDelegate mDialogDelegate;
+
+    @Before
+    public void setUp() {
+        mDialogDelegate = new HearingDevicesDialogDelegate(mSystemUIDialogFactory);
+        mDialog = new SystemUIDialog(
+                mContext,
+                0,
+                DEFAULT_DISMISS_ON_DEVICE_LOCK,
+                mSystemUIDialogManager,
+                mSysUiState,
+                getFakeBroadcastDispatcher(),
+                mDialogTransitionAnimator,
+                mDialogDelegate
+        );
+
+        when(mSystemUIDialogFactory.create(any(SystemUIDialog.Delegate.class)))
+                .thenReturn(mDialog);
+    }
+
+    @Test
+    public void createDialog_dialogShown() {
+        assertThat(mDialogDelegate.createDialog()).isEqualTo(mDialog);
+    }
+}
diff --git a/packages/SystemUI/tests/src/com/android/systemui/animation/back/BackAnimationSpecTest.kt b/packages/SystemUI/tests/src/com/android/systemui/animation/back/BackAnimationSpecTest.kt
index 58011eb..190babd 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/animation/back/BackAnimationSpecTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/animation/back/BackAnimationSpecTest.kt
@@ -27,7 +27,7 @@
         val maxY = 14.0f
         val minScale = 0.9f
 
-        val backAnimationSpec = BackAnimationSpec.floatingSystemSurfacesForSysUi(displayMetrics)
+        val backAnimationSpec = BackAnimationSpec.floatingSystemSurfacesForSysUi { displayMetrics }
 
         assertBackTransformation(
             backAnimationSpec = backAnimationSpec,
diff --git a/packages/SystemUI/tests/src/com/android/systemui/animation/back/OnBackAnimationCallbackExtensionTest.kt b/packages/SystemUI/tests/src/com/android/systemui/animation/back/OnBackAnimationCallbackExtensionTest.kt
index f5c9bef..314abda 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/animation/back/OnBackAnimationCallbackExtensionTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/animation/back/OnBackAnimationCallbackExtensionTest.kt
@@ -30,7 +30,7 @@
 
     private val onBackAnimationCallback =
         onBackAnimationCallbackFrom(
-            backAnimationSpec = BackAnimationSpec.floatingSystemSurfacesForSysUi(displayMetrics),
+            backAnimationSpec = BackAnimationSpec.floatingSystemSurfacesForSysUi { displayMetrics },
             displayMetrics = displayMetrics,
             onBackProgressed = onBackProgress,
             onBackStarted = onBackStart,
diff --git a/packages/SystemUI/tests/src/com/android/systemui/keyguard/domain/interactor/FromAodTransitionInteractorTest.kt b/packages/SystemUI/tests/src/com/android/systemui/keyguard/domain/interactor/FromAodTransitionInteractorTest.kt
index 7bef01a..3926f92 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/keyguard/domain/interactor/FromAodTransitionInteractorTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/keyguard/domain/interactor/FromAodTransitionInteractorTest.kt
@@ -48,6 +48,7 @@
 import com.android.systemui.keyguard.shared.model.TransitionState
 import com.android.systemui.keyguard.util.KeyguardTransitionRepositorySpySubject.Companion.assertThat
 import com.android.systemui.kosmos.testScope
+import com.android.systemui.power.domain.interactor.PowerInteractor
 import com.android.systemui.power.domain.interactor.PowerInteractor.Companion.setAsleepForTest
 import com.android.systemui.power.domain.interactor.PowerInteractor.Companion.setAwakeForTest
 import com.android.systemui.power.domain.interactor.powerInteractor
@@ -73,13 +74,17 @@
         }
 
     private val testScope = kosmos.testScope
-    private val underTest = kosmos.fromAodTransitionInteractor
+    private lateinit var underTest: FromAodTransitionInteractor
 
-    private val powerInteractor = kosmos.powerInteractor
-    private val transitionRepository = kosmos.fakeKeyguardTransitionRepository
+    private lateinit var powerInteractor: PowerInteractor
+    private lateinit var transitionRepository: FakeKeyguardTransitionRepository
 
     @Before
     fun setup() {
+        powerInteractor = kosmos.powerInteractor
+        transitionRepository = kosmos.fakeKeyguardTransitionRepository
+        underTest = kosmos.fromAodTransitionInteractor
+
         underTest.start()
 
         // Transition to AOD and set the power interactor asleep.
diff --git a/packages/SystemUI/tests/src/com/android/systemui/keyguard/domain/interactor/FromDozingTransitionInteractorTest.kt b/packages/SystemUI/tests/src/com/android/systemui/keyguard/domain/interactor/FromDozingTransitionInteractorTest.kt
index 258dbf3..cded2a4 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/keyguard/domain/interactor/FromDozingTransitionInteractorTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/keyguard/domain/interactor/FromDozingTransitionInteractorTest.kt
@@ -51,6 +51,7 @@
 import com.android.systemui.keyguard.shared.model.TransitionState
 import com.android.systemui.keyguard.util.KeyguardTransitionRepositorySpySubject.Companion.assertThat
 import com.android.systemui.kosmos.testScope
+import com.android.systemui.power.domain.interactor.PowerInteractor
 import com.android.systemui.power.domain.interactor.PowerInteractor.Companion.setAsleepForTest
 import com.android.systemui.power.domain.interactor.PowerInteractor.Companion.setAwakeForTest
 import com.android.systemui.power.domain.interactor.powerInteractor
@@ -77,13 +78,17 @@
         }
 
     private val testScope = kosmos.testScope
-    private val underTest = kosmos.fromDozingTransitionInteractor
+    private lateinit var underTest: FromDozingTransitionInteractor
 
-    private val powerInteractor = kosmos.powerInteractor
-    private val transitionRepository = kosmos.fakeKeyguardTransitionRepository
+    private lateinit var powerInteractor: PowerInteractor
+    private lateinit var transitionRepository: FakeKeyguardTransitionRepository
 
     @Before
     fun setup() {
+        powerInteractor = kosmos.powerInteractor
+        transitionRepository = kosmos.fakeKeyguardTransitionRepository
+        underTest = kosmos.fromDozingTransitionInteractor
+
         underTest.start()
 
         // Transition to DOZING and set the power interactor asleep.
diff --git a/packages/SystemUI/tests/src/com/android/systemui/qs/panels/domain/repository/IconTilesRepositoryImplTest.kt b/packages/SystemUI/tests/src/com/android/systemui/qs/panels/domain/repository/IconTilesRepositoryImplTest.kt
new file mode 100644
index 0000000..8cc3a85
--- /dev/null
+++ b/packages/SystemUI/tests/src/com/android/systemui/qs/panels/domain/repository/IconTilesRepositoryImplTest.kt
@@ -0,0 +1,61 @@
+/*
+ * Copyright (C) 2024 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.systemui.qs.panels.domain.repository
+
+import androidx.test.ext.junit.runners.AndroidJUnit4
+import androidx.test.filters.SmallTest
+import com.android.systemui.SysuiTestCase
+import com.android.systemui.coroutines.collectLastValue
+import com.android.systemui.qs.panels.data.repository.IconTilesRepositoryImpl
+import com.android.systemui.qs.pipeline.shared.TileSpec
+import com.google.common.truth.Truth.assertThat
+import kotlinx.coroutines.test.runTest
+import org.junit.Test
+import org.junit.runner.RunWith
+
+@SmallTest
+@RunWith(AndroidJUnit4::class)
+class IconTilesRepositoryImplTest : SysuiTestCase() {
+
+    private val underTest = IconTilesRepositoryImpl()
+
+    @Test
+    fun iconTilesSpecsIsValid() = runTest {
+        val tilesSpecs by collectLastValue(underTest.iconTilesSpecs)
+        assertThat(tilesSpecs).isEqualTo(ICON_ONLY_TILES_SPECS)
+    }
+
+    companion object {
+        private val ICON_ONLY_TILES_SPECS =
+            setOf(
+                TileSpec.create("airplane"),
+                TileSpec.create("battery"),
+                TileSpec.create("cameratoggle"),
+                TileSpec.create("cast"),
+                TileSpec.create("color_correction"),
+                TileSpec.create("inversion"),
+                TileSpec.create("saver"),
+                TileSpec.create("dnd"),
+                TileSpec.create("flashlight"),
+                TileSpec.create("location"),
+                TileSpec.create("mictoggle"),
+                TileSpec.create("nfc"),
+                TileSpec.create("night"),
+                TileSpec.create("rotation")
+            )
+    }
+}
diff --git a/packages/SystemUI/tests/src/com/android/systemui/qs/tiles/HearingDevicesTileTest.java b/packages/SystemUI/tests/src/com/android/systemui/qs/tiles/HearingDevicesTileTest.java
new file mode 100644
index 0000000..73aa54c
--- /dev/null
+++ b/packages/SystemUI/tests/src/com/android/systemui/qs/tiles/HearingDevicesTileTest.java
@@ -0,0 +1,144 @@
+/*
+ * Copyright (C) 2024 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.systemui.qs.tiles;
+
+import static com.google.common.truth.Truth.assertThat;
+
+import static org.mockito.ArgumentMatchers.any;
+import static org.mockito.ArgumentMatchers.anyInt;
+import static org.mockito.Mockito.verify;
+import static org.mockito.Mockito.when;
+
+import android.content.Intent;
+import android.os.Handler;
+import android.platform.test.annotations.DisableFlags;
+import android.platform.test.annotations.EnableFlags;
+import android.provider.Settings;
+import android.testing.AndroidTestingRunner;
+import android.testing.TestableLooper;
+import android.view.View;
+
+import androidx.test.filters.SmallTest;
+
+import com.android.internal.logging.MetricsLogger;
+import com.android.systemui.Flags;
+import com.android.systemui.SysuiTestCase;
+import com.android.systemui.accessibility.hearingaid.HearingDevicesDialogManager;
+import com.android.systemui.classifier.FalsingManagerFake;
+import com.android.systemui.plugins.ActivityStarter;
+import com.android.systemui.plugins.statusbar.StatusBarStateController;
+import com.android.systemui.qs.QSHost;
+import com.android.systemui.qs.QsEventLogger;
+import com.android.systemui.qs.logging.QSLogger;
+
+import org.junit.After;
+import org.junit.Before;
+import org.junit.Rule;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+import org.mockito.ArgumentCaptor;
+import org.mockito.Mock;
+import org.mockito.junit.MockitoJUnit;
+import org.mockito.junit.MockitoRule;
+
+/** Tests for {@link HearingDevicesTile}. */
+@RunWith(AndroidTestingRunner.class)
+@TestableLooper.RunWithLooper(setAsMainLooper = true)
+@SmallTest
+public class HearingDevicesTileTest extends SysuiTestCase {
+
+    @Rule
+    public MockitoRule mockito = MockitoJUnit.rule();
+
+    @Mock
+    private QSHost mHost;
+    @Mock
+    private QsEventLogger mUiEventLogger;
+    @Mock
+    private MetricsLogger mMetricsLogger;
+    @Mock
+    private StatusBarStateController mStatusBarStateController;
+    @Mock
+    private ActivityStarter mActivityStarter;
+    @Mock
+    private QSLogger mQSLogger;
+    @Mock
+    HearingDevicesDialogManager mHearingDevicesDialogManager;
+
+    private TestableLooper mTestableLooper;
+    private HearingDevicesTile mTile;
+
+    @Before
+    public void setUp() throws Exception {
+        mTestableLooper = TestableLooper.get(this);
+        when(mHost.getContext()).thenReturn(mContext);
+
+        mTile = new HearingDevicesTile(
+                mHost,
+                mUiEventLogger,
+                mTestableLooper.getLooper(),
+                new Handler(mTestableLooper.getLooper()),
+                new FalsingManagerFake(),
+                mMetricsLogger,
+                mStatusBarStateController,
+                mActivityStarter,
+                mQSLogger,
+                mHearingDevicesDialogManager);
+
+        mTile.initialize();
+        mTestableLooper.processAllMessages();
+    }
+
+    @After
+    public void tearDown() {
+        mTile.destroy();
+        mTestableLooper.processAllMessages();
+    }
+
+    @Test
+    @EnableFlags(Flags.FLAG_HEARING_AIDS_QS_TILE_DIALOG)
+    public void isAvailable_flagEnabled_true() {
+        assertThat(mTile.isAvailable()).isTrue();
+    }
+
+    @Test
+    @DisableFlags(Flags.FLAG_HEARING_AIDS_QS_TILE_DIALOG)
+    public void isAvailable_flagDisabled_false() {
+        assertThat(mTile.isAvailable()).isFalse();
+    }
+
+    @Test
+    public void longClick_expectedAction() {
+        mTile.longClick(null);
+        mTestableLooper.processAllMessages();
+
+        ArgumentCaptor<Intent> IntentCaptor = ArgumentCaptor.forClass(Intent.class);
+        verify(mActivityStarter).postStartActivityDismissingKeyguard(IntentCaptor.capture(),
+                anyInt(), any());
+        assertThat(IntentCaptor.getValue().getAction()).isEqualTo(
+                Settings.ACTION_HEARING_DEVICES_SETTINGS);
+    }
+
+    @Test
+    public void handleClick_dialogShown() {
+        View view = new View(mContext);
+        mTile.handleClick(view);
+        mTestableLooper.processAllMessages();
+
+        verify(mHearingDevicesDialogManager).showDialog(view);
+    }
+}
diff --git a/packages/SystemUI/tests/src/com/android/systemui/qs/tiles/QuickAccessWalletTileTest.java b/packages/SystemUI/tests/src/com/android/systemui/qs/tiles/QuickAccessWalletTileTest.java
index 2d18f92..122d9e4 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/qs/tiles/QuickAccessWalletTileTest.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/qs/tiles/QuickAccessWalletTileTest.java
@@ -58,7 +58,6 @@
 import androidx.test.filters.SmallTest;
 
 import com.android.internal.logging.MetricsLogger;
-import com.android.systemui.res.R;
 import com.android.systemui.SysuiTestCase;
 import com.android.systemui.classifier.FalsingManagerFake;
 import com.android.systemui.plugins.ActivityStarter;
@@ -68,6 +67,7 @@
 import com.android.systemui.qs.QsEventLogger;
 import com.android.systemui.qs.logging.QSLogger;
 import com.android.systemui.qs.tileimpl.QSTileImpl;
+import com.android.systemui.res.R;
 import com.android.systemui.statusbar.policy.KeyguardStateController;
 import com.android.systemui.util.settings.SecureSettings;
 import com.android.systemui.wallet.controller.QuickAccessWalletController;
@@ -198,7 +198,8 @@
     }
 
     @Test
-    public void testIsAvailable_qawFeatureAvailable() {
+    public void testIsAvailable_qawFeatureAvailableWalletUnavailable() {
+        when(mController.isWalletRoleAvailable()).thenReturn(false);
         when(mPackageManager.hasSystemFeature(FEATURE_NFC_HOST_CARD_EMULATION)).thenReturn(true);
         when(mPackageManager.hasSystemFeature("org.chromium.arc")).thenReturn(false);
         when(mSecureSettings.getStringForUser(NFC_PAYMENT_DEFAULT_COMPONENT,
@@ -208,6 +209,16 @@
     }
 
     @Test
+    public void testIsAvailable_nfcUnavailableWalletAvailable() {
+        when(mController.isWalletRoleAvailable()).thenReturn(true);
+        when(mHost.getUserId()).thenReturn(PRIMARY_USER_ID);
+        when(mPackageManager.hasSystemFeature(FEATURE_NFC_HOST_CARD_EMULATION)).thenReturn(false);
+        when(mPackageManager.hasSystemFeature("org.chromium.arc")).thenReturn(false);
+
+        assertTrue(mTile.isAvailable());
+    }
+
+    @Test
     public void testHandleClick_startQuickAccessUiIntent_noCard() {
         setUpWalletCard(/* hasCard= */ false);
 
diff --git a/packages/SystemUI/tests/src/com/android/systemui/screenrecord/ScreenRecordPermissionDialogDelegateTest.kt b/packages/SystemUI/tests/src/com/android/systemui/screenrecord/ScreenRecordPermissionDialogDelegateTest.kt
index 6e48074..598a0f2 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/screenrecord/ScreenRecordPermissionDialogDelegateTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/screenrecord/ScreenRecordPermissionDialogDelegateTest.kt
@@ -39,7 +39,6 @@
 import com.android.systemui.settings.UserContextProvider
 import com.android.systemui.statusbar.phone.SystemUIDialog
 import com.android.systemui.statusbar.phone.SystemUIDialogManager
-import com.android.systemui.util.mockito.any
 import com.android.systemui.util.mockito.argumentCaptor
 import com.android.systemui.util.mockito.mock
 import com.google.common.truth.Truth.assertThat
@@ -50,6 +49,7 @@
 import org.junit.runner.RunWith
 import org.mockito.Mock
 import org.mockito.Mockito.eq
+import org.mockito.Mockito.never
 import org.mockito.Mockito.verify
 import org.mockito.Mockito.`when` as whenever
 import org.mockito.MockitoAnnotations
@@ -59,7 +59,6 @@
 @TestableLooper.RunWithLooper(setAsMainLooper = true)
 class ScreenRecordPermissionDialogDelegateTest : SysuiTestCase() {
 
-    //@Mock private lateinit var dialogFactory: SystemUIDialog.Factory
     @Mock private lateinit var starter: ActivityStarter
     @Mock private lateinit var controller: RecordingController
     @Mock private lateinit var userContextProvider: UserContextProvider
@@ -76,13 +75,13 @@
         whenever(flags.isEnabled(Flags.WM_ENABLE_PARTIAL_SCREEN_SHARING)).thenReturn(true)
 
         val systemUIDialogFactory =
-                SystemUIDialog.Factory(
-                        context,
-                        Dependency.get(SystemUIDialogManager::class.java),
-                        Dependency.get(SysUiState::class.java),
-                        Dependency.get(BroadcastDispatcher::class.java),
-                        Dependency.get(DialogTransitionAnimator::class.java),
-                )
+            SystemUIDialog.Factory(
+                context,
+                Dependency.get(SystemUIDialogManager::class.java),
+                Dependency.get(SysUiState::class.java),
+                Dependency.get(BroadcastDispatcher::class.java),
+                Dependency.get(DialogTransitionAnimator::class.java),
+            )
 
         val delegate =
             ScreenRecordPermissionDialogDelegate(
@@ -189,6 +188,17 @@
         verify(mediaProjectionMetricsLogger).notifyProjectionRequestCancelled(TEST_HOST_UID)
     }
 
+    @Test
+    fun showDialog_singleAppSelected_clickOnStart_projectionRequestCancelledIsNotLoggedOnce() {
+        showDialog()
+        onSpinnerItemSelected(SINGLE_APP)
+
+        clickOnStart()
+
+        verify(mediaProjectionMetricsLogger, never())
+            .notifyProjectionRequestCancelled(TEST_HOST_UID)
+    }
+
     private fun showDialog() {
         dialog.show()
     }
diff --git a/packages/SystemUI/tests/src/com/android/systemui/shade/GlanceableHubContainerControllerTest.kt b/packages/SystemUI/tests/src/com/android/systemui/shade/GlanceableHubContainerControllerTest.kt
index 07d9350..5ca6cf1 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/shade/GlanceableHubContainerControllerTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/shade/GlanceableHubContainerControllerTest.kt
@@ -41,6 +41,7 @@
 import com.android.systemui.kosmos.testDispatcher
 import com.android.systemui.kosmos.testScope
 import com.android.systemui.res.R
+import com.android.systemui.scene.shared.model.sceneDataSourceDelegator
 import com.android.systemui.shade.domain.interactor.ShadeInteractor
 import com.android.systemui.statusbar.phone.SystemUIDialogFactory
 import com.android.systemui.testKosmos
@@ -104,7 +105,8 @@
                 dialogFactory,
                 keyguardTransitionInteractor,
                 shadeInteractor,
-                powerManager
+                powerManager,
+                kosmos.sceneDataSourceDelegator,
             )
         testableLooper = TestableLooper.get(this)
 
@@ -145,6 +147,7 @@
                 keyguardTransitionInteractor,
                 shadeInteractor,
                 powerManager,
+                kosmos.sceneDataSourceDelegator,
             )
 
         // First call succeeds.
@@ -268,7 +271,7 @@
     }
 
     private fun goToScene(scene: SceneKey) {
-        communalRepository.setDesiredScene(scene)
+        communalRepository.changeScene(scene)
         testableLooper.processAllMessages()
     }
 
diff --git a/packages/SystemUI/tests/src/com/android/systemui/shade/NotificationPanelViewControllerBaseTest.java b/packages/SystemUI/tests/src/com/android/systemui/shade/NotificationPanelViewControllerBaseTest.java
index e957ca2..d534974 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/shade/NotificationPanelViewControllerBaseTest.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/shade/NotificationPanelViewControllerBaseTest.java
@@ -604,6 +604,7 @@
                 new NotificationsKeyguardInteractor(notifsKeyguardViewStateRepository);
         NotificationWakeUpCoordinator coordinator =
                 new NotificationWakeUpCoordinator(
+                        mKosmos.getTestScope().getBackgroundScope(),
                         mDumpManager,
                         mock(HeadsUpManager.class),
                         new StatusBarStateControllerImpl(
@@ -618,7 +619,8 @@
                         mDozeParameters,
                         mScreenOffAnimationController,
                         new NotificationWakeUpCoordinatorLogger(logcatLogBuffer()),
-                        notifsKeyguardInteractor);
+                        notifsKeyguardInteractor,
+                        mKosmos.getCommunalInteractor());
         mConfigurationController = new ConfigurationControllerImpl(mContext);
         PulseExpansionHandler expansionHandler = new PulseExpansionHandler(
                 mContext,
diff --git a/packages/SystemUI/tests/src/com/android/systemui/shade/NotificationPanelViewControllerTest.java b/packages/SystemUI/tests/src/com/android/systemui/shade/NotificationPanelViewControllerTest.java
index 29a92d9..650c45b 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/shade/NotificationPanelViewControllerTest.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/shade/NotificationPanelViewControllerTest.java
@@ -1061,7 +1061,7 @@
     @Test
     public void testPanelClosedWhenClosingQsInSplitShade() {
         mShadeExpansionStateManager.onPanelExpansionChanged(/* fraction= */ 1,
-                /* expanded= */ true, /* tracking= */ false, /* dragDownPxAmount= */ 0);
+                /* expanded= */ true, /* tracking= */ false);
         enableSplitShade(/* enabled= */ true);
         mNotificationPanelViewController.setExpandedFraction(1f);
 
diff --git a/packages/SystemUI/tests/src/com/android/systemui/shade/NotificationShadeWindowViewControllerTest.kt b/packages/SystemUI/tests/src/com/android/systemui/shade/NotificationShadeWindowViewControllerTest.kt
index b699613..7a3b561 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/shade/NotificationShadeWindowViewControllerTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/shade/NotificationShadeWindowViewControllerTest.kt
@@ -46,6 +46,7 @@
 import com.android.systemui.keyguard.shared.model.TransitionStep
 import com.android.systemui.res.R
 import com.android.systemui.shade.NotificationShadeWindowView.InteractionEventHandler
+import com.android.systemui.shade.domain.interactor.PanelExpansionInteractor
 import com.android.systemui.statusbar.DragDownHelper
 import com.android.systemui.statusbar.LockscreenShadeTransitionController
 import com.android.systemui.statusbar.NotificationInsetsController
@@ -68,6 +69,7 @@
 import com.android.systemui.util.mockito.eq
 import com.android.systemui.util.time.FakeSystemClock
 import com.google.common.truth.Truth.assertThat
+import java.util.Optional
 import kotlinx.coroutines.ExperimentalCoroutinesApi
 import kotlinx.coroutines.flow.MutableStateFlow
 import kotlinx.coroutines.flow.emptyFlow
@@ -85,7 +87,6 @@
 import org.mockito.Mockito.times
 import org.mockito.Mockito.verify
 import org.mockito.MockitoAnnotations
-import java.util.Optional
 import org.mockito.Mockito.`when` as whenever
 
 @OptIn(ExperimentalCoroutinesApi::class)
@@ -100,7 +101,8 @@
     @Mock private lateinit var dozeServiceHost: DozeServiceHost
     @Mock private lateinit var dozeScrimController: DozeScrimController
     @Mock private lateinit var dockManager: DockManager
-    @Mock private lateinit var notificationPanelViewController: NotificationPanelViewController
+    @Mock private lateinit var shadeViewController: ShadeViewController
+    @Mock private lateinit var panelExpansionInteractor: PanelExpansionInteractor
     @Mock private lateinit var notificationShadeDepthController: NotificationShadeDepthController
     @Mock private lateinit var notificationShadeWindowController: NotificationShadeWindowController
     @Mock private lateinit var keyguardUnlockAnimationController: KeyguardUnlockAnimationController
@@ -177,7 +179,8 @@
                 dockManager,
                 notificationShadeDepthController,
                 view,
-                notificationPanelViewController,
+                shadeViewController,
+                panelExpansionInteractor,
                 ShadeExpansionStateManager(),
                 stackScrollLayoutController,
                 statusBarKeyguardViewManager,
@@ -263,7 +266,7 @@
         testScope.runTest {
             underTest.setStatusBarViewController(phoneStatusBarViewController)
             whenever(statusBarWindowStateController.windowIsShowing()).thenReturn(true)
-            whenever(notificationPanelViewController.isFullyCollapsed).thenReturn(true)
+            whenever(panelExpansionInteractor.isFullyCollapsed).thenReturn(true)
             whenever(phoneStatusBarViewController.touchIsWithinView(anyFloat(), anyFloat()))
                 .thenReturn(true)
             whenever(phoneStatusBarViewController.sendTouchToView(DOWN_EVENT)).thenReturn(true)
@@ -282,7 +285,7 @@
             whenever(phoneStatusBarViewController.touchIsWithinView(anyFloat(), anyFloat()))
                 .thenReturn(true)
             // Item we're testing
-            whenever(notificationPanelViewController.isFullyCollapsed).thenReturn(false)
+            whenever(panelExpansionInteractor.isFullyCollapsed).thenReturn(false)
 
             val returnVal = interactionEventHandler.handleDispatchTouchEvent(DOWN_EVENT)
 
@@ -295,7 +298,7 @@
         testScope.runTest {
             underTest.setStatusBarViewController(phoneStatusBarViewController)
             whenever(statusBarWindowStateController.windowIsShowing()).thenReturn(true)
-            whenever(notificationPanelViewController.isFullyCollapsed).thenReturn(true)
+            whenever(panelExpansionInteractor.isFullyCollapsed).thenReturn(true)
             // Item we're testing
             whenever(phoneStatusBarViewController.touchIsWithinView(anyFloat(), anyFloat()))
                 .thenReturn(false)
@@ -310,7 +313,7 @@
     fun handleDispatchTouchEvent_sbWindowNotShowing_noSendTouchToSbAndReturnsTrue() =
         testScope.runTest {
             underTest.setStatusBarViewController(phoneStatusBarViewController)
-            whenever(notificationPanelViewController.isFullyCollapsed).thenReturn(true)
+            whenever(panelExpansionInteractor.isFullyCollapsed).thenReturn(true)
             whenever(phoneStatusBarViewController.touchIsWithinView(anyFloat(), anyFloat()))
                 .thenReturn(true)
             // Item we're testing
@@ -327,7 +330,7 @@
         testScope.runTest {
             underTest.setStatusBarViewController(phoneStatusBarViewController)
             whenever(statusBarWindowStateController.windowIsShowing()).thenReturn(true)
-            whenever(notificationPanelViewController.isFullyCollapsed).thenReturn(true)
+            whenever(panelExpansionInteractor.isFullyCollapsed).thenReturn(true)
             whenever(phoneStatusBarViewController.touchIsWithinView(anyFloat(), anyFloat()))
                 .thenReturn(true)
 
@@ -486,7 +489,7 @@
         // AND bouncer is not showing
         whenever(centralSurfaces.isBouncerShowing()).thenReturn(false)
         // AND panel view controller wants it
-        whenever(notificationPanelViewController.handleExternalInterceptTouch(DOWN_EVENT))
+        whenever(shadeViewController.handleExternalInterceptTouch(DOWN_EVENT))
             .thenReturn(true)
 
         mSetFlagsRule.enableFlags(Flags.FLAG_MIGRATE_CLOCKS_TO_BLUEPRINT)
diff --git a/packages/SystemUI/tests/src/com/android/systemui/shade/NotificationShadeWindowViewTest.kt b/packages/SystemUI/tests/src/com/android/systemui/shade/NotificationShadeWindowViewTest.kt
index 2ecca2e..0f54e07 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/shade/NotificationShadeWindowViewTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/shade/NotificationShadeWindowViewTest.kt
@@ -38,6 +38,7 @@
 import com.android.systemui.keyguard.domain.interactor.KeyguardTransitionInteractor
 import com.android.systemui.res.R
 import com.android.systemui.shade.NotificationShadeWindowView.InteractionEventHandler
+import com.android.systemui.shade.domain.interactor.PanelExpansionInteractor
 import com.android.systemui.statusbar.DragDownHelper
 import com.android.systemui.statusbar.LockscreenShadeTransitionController
 import com.android.systemui.statusbar.NotificationInsetsController
@@ -89,7 +90,8 @@
     @Mock private lateinit var dozeServiceHost: DozeServiceHost
     @Mock private lateinit var dozeScrimController: DozeScrimController
     @Mock private lateinit var dockManager: DockManager
-    @Mock private lateinit var notificationPanelViewController: NotificationPanelViewController
+    @Mock private lateinit var shadeViewController: ShadeViewController
+    @Mock private lateinit var panelExpansionInteractor: PanelExpansionInteractor
     @Mock private lateinit var notificationStackScrollLayout: NotificationStackScrollLayout
     @Mock private lateinit var notificationShadeDepthController: NotificationShadeDepthController
     @Mock private lateinit var notificationShadeWindowController: NotificationShadeWindowController
@@ -166,7 +168,8 @@
                 dockManager,
                 notificationShadeDepthController,
                 underTest,
-                notificationPanelViewController,
+                shadeViewController,
+                panelExpansionInteractor,
                 ShadeExpansionStateManager(),
                 notificationStackScrollLayoutController,
                 statusBarKeyguardViewManager,
diff --git a/packages/SystemUI/tests/src/com/android/systemui/shade/ShadeExpansionStateManagerTest.kt b/packages/SystemUI/tests/src/com/android/systemui/shade/ShadeExpansionStateManagerTest.kt
index 15c04eb..89ae42f 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/shade/ShadeExpansionStateManagerTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/shade/ShadeExpansionStateManagerTest.kt
@@ -42,17 +42,11 @@
         val tracking = true
         val dragDownAmount = 1234f
 
-        shadeExpansionStateManager.onPanelExpansionChanged(
-            fraction,
-            expanded,
-            tracking,
-            dragDownAmount
-        )
+        shadeExpansionStateManager.onPanelExpansionChanged(fraction, expanded, tracking)
 
         assertThat(listener.fraction).isEqualTo(fraction)
         assertThat(listener.expanded).isEqualTo(expanded)
         assertThat(listener.tracking).isEqualTo(tracking)
-        assertThat(listener.dragDownAmountPx).isEqualTo(dragDownAmount)
     }
 
     @Test
@@ -61,12 +55,7 @@
         val expanded = true
         val tracking = true
         val dragDownAmount = 1234f
-        shadeExpansionStateManager.onPanelExpansionChanged(
-            fraction,
-            expanded,
-            tracking,
-            dragDownAmount
-        )
+        shadeExpansionStateManager.onPanelExpansionChanged(fraction, expanded, tracking)
         val listener = TestShadeExpansionListener()
 
         val currentState = shadeExpansionStateManager.addExpansionListener(listener)
@@ -75,7 +64,6 @@
         assertThat(listener.fraction).isEqualTo(fraction)
         assertThat(listener.expanded).isEqualTo(expanded)
         assertThat(listener.tracking).isEqualTo(tracking)
-        assertThat(listener.dragDownAmountPx).isEqualTo(dragDownAmount)
     }
 
     @Test
@@ -100,8 +88,7 @@
         shadeExpansionStateManager.onPanelExpansionChanged(
             fraction = 0.5f,
             expanded = true,
-            tracking = false,
-            dragDownPxAmount = 0f
+            tracking = false
         )
 
         assertThat(listener.state).isEqualTo(STATE_OPENING)
@@ -115,8 +102,7 @@
         shadeExpansionStateManager.onPanelExpansionChanged(
             fraction = 0.5f,
             expanded = true,
-            tracking = true,
-            dragDownPxAmount = 0f
+            tracking = true
         )
 
         assertThat(listener.state).isEqualTo(STATE_OPENING)
@@ -132,8 +118,7 @@
         shadeExpansionStateManager.onPanelExpansionChanged(
             fraction = 0.5f,
             expanded = false,
-            tracking = false,
-            dragDownPxAmount = 0f
+            tracking = false
         )
 
         assertThat(listener.state).isEqualTo(STATE_CLOSED)
@@ -149,8 +134,7 @@
         shadeExpansionStateManager.onPanelExpansionChanged(
             fraction = 0.5f,
             expanded = false,
-            tracking = true,
-            dragDownPxAmount = 0f
+            tracking = true
         )
 
         assertThat(listener.state).isEqualTo(STATE_OPEN)
@@ -166,8 +150,7 @@
         shadeExpansionStateManager.onPanelExpansionChanged(
             fraction = 1f,
             expanded = true,
-            tracking = false,
-            dragDownPxAmount = 0f
+            tracking = false
         )
 
         assertThat(listener.previousState).isEqualTo(STATE_OPENING)
@@ -182,8 +165,7 @@
         shadeExpansionStateManager.onPanelExpansionChanged(
             fraction = 1f,
             expanded = true,
-            tracking = true,
-            dragDownPxAmount = 0f
+            tracking = true
         )
 
         assertThat(listener.state).isEqualTo(STATE_OPENING)
@@ -199,8 +181,7 @@
         shadeExpansionStateManager.onPanelExpansionChanged(
             fraction = 1f,
             expanded = false,
-            tracking = false,
-            dragDownPxAmount = 0f
+            tracking = false
         )
 
         assertThat(listener.state).isEqualTo(STATE_CLOSED)
@@ -216,8 +197,7 @@
         shadeExpansionStateManager.onPanelExpansionChanged(
             fraction = 1f,
             expanded = false,
-            tracking = true,
-            dragDownPxAmount = 0f
+            tracking = true
         )
 
         assertThat(listener.state).isEqualTo(STATE_OPEN)
@@ -229,13 +209,11 @@
         var fraction: Float = 0f
         var expanded: Boolean = false
         var tracking: Boolean = false
-        var dragDownAmountPx: Float = 0f
 
         override fun onPanelExpansionChanged(event: ShadeExpansionChangeEvent) {
             this.fraction = event.fraction
             this.expanded = event.expanded
             this.tracking = event.tracking
-            this.dragDownAmountPx = event.dragDownPxAmount
         }
     }
 
diff --git a/packages/SystemUI/tests/src/com/android/systemui/shade/transition/ScrimShadeTransitionControllerTest.kt b/packages/SystemUI/tests/src/com/android/systemui/shade/transition/ScrimShadeTransitionControllerTest.kt
index dea905a..f2abb90 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/shade/transition/ScrimShadeTransitionControllerTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/shade/transition/ScrimShadeTransitionControllerTest.kt
@@ -3,23 +3,18 @@
 import android.platform.test.annotations.DisableFlags
 import android.testing.AndroidTestingRunner
 import androidx.test.filters.SmallTest
-import com.android.compose.animation.scene.ObservableTransitionState
-import com.android.compose.animation.scene.SceneKey
 import com.android.systemui.Flags
 import com.android.systemui.SysuiTestCase
-import com.android.systemui.coroutines.collectLastValue
 import com.android.systemui.deviceentry.data.repository.FakeDeviceEntryRepository
 import com.android.systemui.deviceentry.data.repository.fakeDeviceEntryRepository
 import com.android.systemui.deviceentry.domain.interactor.DeviceUnlockedInteractor
 import com.android.systemui.deviceentry.domain.interactor.deviceUnlockedInteractor
 import com.android.systemui.dump.DumpManager
-import com.android.systemui.flags.EnableSceneContainer
 import com.android.systemui.kosmos.applicationCoroutineScope
 import com.android.systemui.kosmos.testScope
 import com.android.systemui.scene.domain.interactor.SceneInteractor
 import com.android.systemui.scene.domain.interactor.sceneInteractor
 import com.android.systemui.scene.shared.model.FakeSceneDataSource
-import com.android.systemui.scene.shared.model.Scenes
 import com.android.systemui.scene.shared.model.fakeSceneDataSource
 import com.android.systemui.shade.ShadeExpansionChangeEvent
 import com.android.systemui.shade.ShadeExpansionStateManager
@@ -27,15 +22,8 @@
 import com.android.systemui.shade.domain.interactor.panelExpansionInteractor
 import com.android.systemui.statusbar.phone.ScrimController
 import com.android.systemui.testKosmos
-import com.android.systemui.util.mockito.any
-import com.android.systemui.util.mockito.whenever
-import com.google.common.truth.Truth
 import kotlinx.coroutines.CoroutineScope
-import kotlinx.coroutines.flow.MutableStateFlow
-import kotlinx.coroutines.flow.flowOf
 import kotlinx.coroutines.test.TestScope
-import kotlinx.coroutines.test.runCurrent
-import kotlinx.coroutines.test.runTest
 import org.junit.Before
 import org.junit.Test
 import org.junit.runner.RunWith
@@ -74,9 +62,7 @@
         sceneInteractor = kosmos.sceneInteractor
         fakeSceneDataSource = kosmos.fakeSceneDataSource
         underTest = ScrimShadeTransitionController(
-            applicationScope,
             shadeExpansionStateManager,
-            { panelExpansionInteractor },
             dumpManager,
             scrimController,
             )
@@ -99,96 +85,20 @@
         verify(scrimController).setRawPanelExpansionFraction(DEFAULT_EXPANSION_EVENT.fraction)
     }
 
-    @Test
-    @EnableSceneContainer
-    fun sceneChanges_forwardsToScrimTransitionController() =
-        testScope.runTest {
-            var rawExpansion: Float? = null
-            whenever(scrimController.setRawPanelExpansionFraction(any())).then {
-                (it.arguments[0] as Float?).also { rawExp -> rawExpansion = rawExp }
-            }
-            setUnlocked(true)
-            val transitionState =
-                MutableStateFlow<ObservableTransitionState>(
-                    ObservableTransitionState.Idle(Scenes.Gone)
-                )
-            sceneInteractor.setTransitionState(transitionState)
-
-            changeScene(Scenes.Gone, transitionState)
-            val currentScene by collectLastValue(sceneInteractor.currentScene)
-            Truth.assertThat(currentScene).isEqualTo(Scenes.Gone)
-
-            Truth.assertThat(rawExpansion)
-                    .isEqualTo(0f)
-
-            changeScene(Scenes.Shade, transitionState) { progress ->
-                Truth.assertThat(rawExpansion)
-                        .isEqualTo(progress)
-            }
-        }
-
     private fun startLegacyPanelExpansion() {
         shadeExpansionStateManager.onPanelExpansionChanged(
             DEFAULT_EXPANSION_EVENT.fraction,
             DEFAULT_EXPANSION_EVENT.expanded,
             DEFAULT_EXPANSION_EVENT.tracking,
-            DEFAULT_EXPANSION_EVENT.dragDownPxAmount,
         )
     }
 
-    private fun TestScope.setUnlocked(isUnlocked: Boolean) {
-        val isDeviceUnlocked by collectLastValue(deviceUnlockedInteractor.isDeviceUnlocked)
-        deviceEntryRepository.setUnlocked(isUnlocked)
-        runCurrent()
-
-        Truth.assertThat(isDeviceUnlocked).isEqualTo(isUnlocked)
-    }
-
-    private fun TestScope.changeScene(
-        toScene: SceneKey,
-        transitionState: MutableStateFlow<ObservableTransitionState>,
-        assertDuringProgress: ((progress: Float) -> Unit) = {},
-    ) {
-        val currentScene by collectLastValue(sceneInteractor.currentScene)
-        val progressFlow = MutableStateFlow(0f)
-        transitionState.value =
-            ObservableTransitionState.Transition(
-                fromScene = checkNotNull(currentScene),
-                toScene = toScene,
-                progress = progressFlow,
-                isInitiatedByUserInput = true,
-                isUserInputOngoing = flowOf(true),
-            )
-        runCurrent()
-        assertDuringProgress(progressFlow.value)
-
-        progressFlow.value = 0.2f
-        runCurrent()
-        assertDuringProgress(progressFlow.value)
-
-        progressFlow.value = 0.6f
-        runCurrent()
-        assertDuringProgress(progressFlow.value)
-
-        progressFlow.value = 1f
-        runCurrent()
-        assertDuringProgress(progressFlow.value)
-
-        transitionState.value = ObservableTransitionState.Idle(toScene)
-        fakeSceneDataSource.changeScene(toScene)
-        runCurrent()
-        assertDuringProgress(progressFlow.value)
-
-        Truth.assertThat(currentScene).isEqualTo(toScene)
-    }
-
     companion object {
         val DEFAULT_EXPANSION_EVENT =
             ShadeExpansionChangeEvent(
                 fraction = 0.5f,
                 expanded = true,
-                tracking = true,
-                dragDownPxAmount = 10f
+                tracking = true
             )
     }
 }
diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/NotificationShadeDepthControllerTest.kt b/packages/SystemUI/tests/src/com/android/systemui/statusbar/NotificationShadeDepthControllerTest.kt
index 68bc72b..fc0c85e 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/NotificationShadeDepthControllerTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/NotificationShadeDepthControllerTest.kt
@@ -23,18 +23,18 @@
 import android.view.View
 import android.view.ViewRootImpl
 import androidx.test.filters.SmallTest
-import com.android.systemui.res.R
 import com.android.systemui.SysuiTestCase
 import com.android.systemui.animation.ShadeInterpolation
 import com.android.systemui.dump.DumpManager
 import com.android.systemui.plugins.statusbar.StatusBarStateController
+import com.android.systemui.res.R
 import com.android.systemui.shade.ShadeExpansionChangeEvent
 import com.android.systemui.statusbar.phone.BiometricUnlockController
 import com.android.systemui.statusbar.phone.DozeParameters
 import com.android.systemui.statusbar.phone.ScrimController
 import com.android.systemui.statusbar.policy.FakeConfigurationController
-import com.android.systemui.statusbar.policy.ResourcesSplitShadeStateController
 import com.android.systemui.statusbar.policy.KeyguardStateController
+import com.android.systemui.statusbar.policy.ResourcesSplitShadeStateController
 import com.android.systemui.util.WallpaperController
 import com.android.systemui.util.mockito.eq
 import com.google.common.truth.Truth.assertThat
@@ -142,7 +142,7 @@
     fun onPanelExpansionChanged_apliesBlur_ifShade() {
         notificationShadeDepthController.onPanelExpansionChanged(
             ShadeExpansionChangeEvent(
-                fraction = 1f, expanded = true, tracking = false, dragDownPxAmount = 0f))
+                fraction = 1f, expanded = true, tracking = false))
         verify(shadeAnimation).animateTo(eq(maxBlur))
     }
 
@@ -150,7 +150,7 @@
     fun onPanelExpansionChanged_animatesBlurIn_ifShade() {
         notificationShadeDepthController.onPanelExpansionChanged(
             ShadeExpansionChangeEvent(
-                fraction = 0.01f, expanded = false, tracking = false, dragDownPxAmount = 0f))
+                fraction = 0.01f, expanded = false, tracking = false))
         verify(shadeAnimation).animateTo(eq(maxBlur))
     }
 
@@ -160,7 +160,7 @@
         clearInvocations(shadeAnimation)
         notificationShadeDepthController.onPanelExpansionChanged(
             ShadeExpansionChangeEvent(
-                fraction = 0f, expanded = false, tracking = false, dragDownPxAmount = 0f))
+                fraction = 0f, expanded = false, tracking = false))
         verify(shadeAnimation).animateTo(eq(0))
     }
 
@@ -168,7 +168,7 @@
     fun onPanelExpansionChanged_animatesBlurOut_ifFlick() {
         val event =
             ShadeExpansionChangeEvent(
-                fraction = 1f, expanded = true, tracking = false, dragDownPxAmount = 0f)
+                fraction = 1f, expanded = true, tracking = false)
         onPanelExpansionChanged_apliesBlur_ifShade()
         clearInvocations(shadeAnimation)
         notificationShadeDepthController.onPanelExpansionChanged(event)
@@ -189,7 +189,7 @@
         clearInvocations(shadeAnimation)
         notificationShadeDepthController.onPanelExpansionChanged(
             ShadeExpansionChangeEvent(
-                fraction = 0.6f, expanded = true, tracking = true, dragDownPxAmount = 0f))
+                fraction = 0.6f, expanded = true, tracking = true))
         verify(shadeAnimation).animateTo(eq(maxBlur))
     }
 
@@ -197,7 +197,7 @@
     fun onPanelExpansionChanged_respectsMinPanelPullDownFraction() {
         val event =
             ShadeExpansionChangeEvent(
-                fraction = 0.5f, expanded = true, tracking = true, dragDownPxAmount = 0f)
+                fraction = 0.5f, expanded = true, tracking = true)
         notificationShadeDepthController.panelPullDownMinFraction = 0.5f
         notificationShadeDepthController.onPanelExpansionChanged(event)
         assertThat(notificationShadeDepthController.shadeExpansion).isEqualTo(0f)
@@ -225,7 +225,7 @@
         notificationShadeDepthController.qsPanelExpansion = 1f
         notificationShadeDepthController.onPanelExpansionChanged(
             ShadeExpansionChangeEvent(
-                fraction = 1f, expanded = true, tracking = false, dragDownPxAmount = 0f))
+                fraction = 1f, expanded = true, tracking = false))
         notificationShadeDepthController.updateBlurCallback.doFrame(0)
         verify(blurUtils).applyBlur(any(), eq(maxBlur), eq(false))
     }
@@ -236,7 +236,7 @@
         notificationShadeDepthController.qsPanelExpansion = 0.25f
         notificationShadeDepthController.onPanelExpansionChanged(
             ShadeExpansionChangeEvent(
-                fraction = 1f, expanded = true, tracking = false, dragDownPxAmount = 0f))
+                fraction = 1f, expanded = true, tracking = false))
         notificationShadeDepthController.updateBlurCallback.doFrame(0)
         verify(wallpaperController)
             .setNotificationShadeZoom(eq(ShadeInterpolation.getNotificationScrimAlpha(0.25f)))
@@ -248,7 +248,7 @@
 
         notificationShadeDepthController.onPanelExpansionChanged(
             ShadeExpansionChangeEvent(
-                fraction = 1f, expanded = true, tracking = false, dragDownPxAmount = 0f))
+                fraction = 1f, expanded = true, tracking = false))
         notificationShadeDepthController.updateBlurCallback.doFrame(0)
 
         verify(wallpaperController).setNotificationShadeZoom(0f)
@@ -260,7 +260,7 @@
 
         notificationShadeDepthController.onPanelExpansionChanged(
             ShadeExpansionChangeEvent(
-                fraction = 1f, expanded = true, tracking = false, dragDownPxAmount = 0f))
+                fraction = 1f, expanded = true, tracking = false))
         notificationShadeDepthController.updateBlurCallback.doFrame(0)
 
         verify(wallpaperController).setNotificationShadeZoom(floatThat { it > 0 })
@@ -273,7 +273,7 @@
         val expanded = true
         val tracking = false
         val dragDownPxAmount = 0f
-        val event = ShadeExpansionChangeEvent(rawFraction, expanded, tracking, dragDownPxAmount)
+        val event = ShadeExpansionChangeEvent(rawFraction, expanded, tracking)
         val inOrder = Mockito.inOrder(wallpaperController)
 
         notificationShadeDepthController.onPanelExpansionChanged(event)
@@ -338,7 +338,7 @@
     fun updateBlurCallback_setsBlur_whenExpanded() {
         notificationShadeDepthController.onPanelExpansionChanged(
             ShadeExpansionChangeEvent(
-                fraction = 1f, expanded = true, tracking = false, dragDownPxAmount = 0f))
+                fraction = 1f, expanded = true, tracking = false))
         `when`(shadeAnimation.radius).thenReturn(maxBlur.toFloat())
         notificationShadeDepthController.updateBlurCallback.doFrame(0)
         verify(blurUtils).applyBlur(any(), eq(maxBlur), eq(false))
@@ -348,7 +348,7 @@
     fun updateBlurCallback_ignoreShadeBlurUntilHidden_overridesZoom() {
         notificationShadeDepthController.onPanelExpansionChanged(
             ShadeExpansionChangeEvent(
-                fraction = 1f, expanded = true, tracking = false, dragDownPxAmount = 0f))
+                fraction = 1f, expanded = true, tracking = false))
         `when`(shadeAnimation.radius).thenReturn(maxBlur.toFloat())
         notificationShadeDepthController.blursDisabledForAppLaunch = true
         notificationShadeDepthController.updateBlurCallback.doFrame(0)
@@ -367,7 +367,7 @@
     fun ignoreBlurForUnlock_ignores() {
         notificationShadeDepthController.onPanelExpansionChanged(
             ShadeExpansionChangeEvent(
-                fraction = 1f, expanded = true, tracking = false, dragDownPxAmount = 0f))
+                fraction = 1f, expanded = true, tracking = false))
         `when`(shadeAnimation.radius).thenReturn(maxBlur.toFloat())
 
         notificationShadeDepthController.blursDisabledForAppLaunch = false
@@ -384,7 +384,7 @@
     fun ignoreBlurForUnlock_doesNotIgnore() {
         notificationShadeDepthController.onPanelExpansionChanged(
             ShadeExpansionChangeEvent(
-                fraction = 1f, expanded = true, tracking = false, dragDownPxAmount = 0f))
+                fraction = 1f, expanded = true, tracking = false))
         `when`(shadeAnimation.radius).thenReturn(maxBlur.toFloat())
 
         notificationShadeDepthController.blursDisabledForAppLaunch = false
@@ -416,7 +416,7 @@
         // And shade is blurred
         notificationShadeDepthController.onPanelExpansionChanged(
             ShadeExpansionChangeEvent(
-                fraction = 1f, expanded = true, tracking = false, dragDownPxAmount = 0f))
+                fraction = 1f, expanded = true, tracking = false))
         `when`(shadeAnimation.radius).thenReturn(maxBlur.toFloat())
 
         notificationShadeDepthController.updateBlurCallback.doFrame(0)
diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/OperatorNameViewControllerTest.kt b/packages/SystemUI/tests/src/com/android/systemui/statusbar/OperatorNameViewControllerTest.kt
new file mode 100644
index 0000000..9c59f9b
--- /dev/null
+++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/OperatorNameViewControllerTest.kt
@@ -0,0 +1,202 @@
+/*
+ * Copyright (C) 2024 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.systemui.statusbar
+
+import android.telephony.ServiceState
+import android.telephony.SubscriptionInfo
+import android.telephony.TelephonyManager
+import android.telephony.telephonyManager
+import androidx.test.filters.SmallTest
+import com.android.keyguard.keyguardUpdateMonitor
+import com.android.systemui.SysuiTestCase
+import com.android.systemui.kosmos.Kosmos
+import com.android.systemui.plugins.DarkIconDispatcher
+import com.android.systemui.statusbar.pipeline.airplane.data.repository.FakeAirplaneModeRepository
+import com.android.systemui.statusbar.pipeline.airplane.domain.interactor.AirplaneModeInteractor
+import com.android.systemui.statusbar.pipeline.mobile.data.repository.FakeMobileConnectionsRepository
+import com.android.systemui.statusbar.pipeline.mobile.util.FakeSubscriptionManagerProxy
+import com.android.systemui.statusbar.pipeline.shared.data.repository.FakeConnectivityRepository
+import com.android.systemui.tuner.TunerService
+import com.android.systemui.util.CarrierConfigTracker
+import com.android.systemui.util.kotlin.JavaAdapter
+import com.android.systemui.util.mockito.any
+import com.android.systemui.util.mockito.mock
+import com.android.systemui.util.mockito.whenever
+import com.google.common.truth.Truth.assertThat
+import junit.framework.Assert.assertTrue
+import kotlin.test.Test
+import kotlinx.coroutines.ExperimentalCoroutinesApi
+import kotlinx.coroutines.test.TestScope
+import kotlinx.coroutines.test.runCurrent
+import kotlinx.coroutines.test.runTest
+import org.junit.Before
+import org.mockito.Mock
+import org.mockito.MockitoAnnotations
+
+@OptIn(ExperimentalCoroutinesApi::class)
+@SmallTest
+class OperatorNameViewControllerTest : SysuiTestCase() {
+    private lateinit var underTest: OperatorNameViewController
+    private lateinit var airplaneModeInteractor: AirplaneModeInteractor
+
+    private val kosmos = Kosmos()
+    private val testScope = TestScope()
+
+    private val view = OperatorNameView(mContext)
+    private val javaAdapter = JavaAdapter(testScope.backgroundScope)
+
+    @Mock private lateinit var darkIconDispatcher: DarkIconDispatcher
+    @Mock private lateinit var tunerService: TunerService
+    private var telephonyManager = kosmos.telephonyManager
+    private val keyguardUpdateMonitor = kosmos.keyguardUpdateMonitor
+    @Mock private lateinit var carrierConfigTracker: CarrierConfigTracker
+    private val subscriptionManagerProxy = FakeSubscriptionManagerProxy()
+
+    private val airplaneModeRepository = FakeAirplaneModeRepository()
+    private val connectivityRepository = FakeConnectivityRepository()
+    private val mobileConnectionsRepository = FakeMobileConnectionsRepository()
+
+    @Before
+    fun setup() {
+        MockitoAnnotations.initMocks(this)
+
+        airplaneModeInteractor =
+            AirplaneModeInteractor(
+                airplaneModeRepository,
+                connectivityRepository,
+                mobileConnectionsRepository,
+            )
+
+        underTest =
+            OperatorNameViewController.Factory(
+                    darkIconDispatcher,
+                    tunerService,
+                    telephonyManager,
+                    keyguardUpdateMonitor,
+                    carrierConfigTracker,
+                    airplaneModeInteractor,
+                    subscriptionManagerProxy,
+                    javaAdapter,
+                )
+                .create(view)
+    }
+
+    @Test
+    fun updateFromSubInfo_showsCarrieName() =
+        testScope.runTest {
+            whenever(telephonyManager.isDataCapable).thenReturn(true)
+
+            val mockSubInfo =
+                mock<SubscriptionInfo>().also {
+                    whenever(it.subscriptionId).thenReturn(1)
+                    whenever(it.carrierName).thenReturn("test_carrier")
+                }
+            whenever(keyguardUpdateMonitor.getSubscriptionInfoForSubId(any()))
+                .thenReturn(mockSubInfo)
+            whenever(keyguardUpdateMonitor.getSimState(any()))
+                .thenReturn(TelephonyManager.SIM_STATE_READY)
+            whenever(keyguardUpdateMonitor.getServiceState(any()))
+                .thenReturn(ServiceState().also { it.state = ServiceState.STATE_IN_SERVICE })
+            subscriptionManagerProxy.defaultDataSubId = 1
+            airplaneModeRepository.setIsAirplaneMode(false)
+
+            underTest.onViewAttached()
+            runCurrent()
+
+            assertThat(view.text).isEqualTo("test_carrier")
+        }
+
+    @Test
+    fun notDataCapable_doesNotShowOperatorName() =
+        testScope.runTest {
+            whenever(telephonyManager.isDataCapable).thenReturn(false)
+
+            val mockSubInfo =
+                mock<SubscriptionInfo>().also {
+                    whenever(it.subscriptionId).thenReturn(1)
+                    whenever(it.carrierName).thenReturn("test_carrier")
+                }
+            whenever(keyguardUpdateMonitor.getSubscriptionInfoForSubId(any()))
+                .thenReturn(mockSubInfo)
+            whenever(keyguardUpdateMonitor.getSimState(any()))
+                .thenReturn(TelephonyManager.SIM_STATE_READY)
+            whenever(keyguardUpdateMonitor.getServiceState(any()))
+                .thenReturn(ServiceState().also { it.state = ServiceState.STATE_IN_SERVICE })
+            subscriptionManagerProxy.defaultDataSubId = 1
+            airplaneModeRepository.setIsAirplaneMode(false)
+
+            underTest.onViewAttached()
+            runCurrent()
+
+            assertTrue(view.text.isNullOrEmpty())
+        }
+
+    @Test
+    fun airplaneMode_doesNotShowOperatorName() =
+        testScope.runTest {
+            whenever(telephonyManager.isDataCapable).thenReturn(false)
+            val mockSubInfo =
+                mock<SubscriptionInfo>().also {
+                    whenever(it.subscriptionId).thenReturn(1)
+                    whenever(it.carrierName).thenReturn("test_carrier")
+                }
+            whenever(keyguardUpdateMonitor.getSubscriptionInfoForSubId(any()))
+                .thenReturn(mockSubInfo)
+            whenever(keyguardUpdateMonitor.getSimState(any()))
+                .thenReturn(TelephonyManager.SIM_STATE_READY)
+            whenever(keyguardUpdateMonitor.getServiceState(any()))
+                .thenReturn(ServiceState().also { it.state = ServiceState.STATE_IN_SERVICE })
+            subscriptionManagerProxy.defaultDataSubId = 1
+            airplaneModeRepository.setIsAirplaneMode(true)
+
+            underTest.onViewAttached()
+            runCurrent()
+
+            assertTrue(view.text.isNullOrEmpty())
+        }
+
+    @Test
+    fun notInService_doesNotShowOperatorName() =
+        testScope.runTest {
+            // Data capable
+            whenever(telephonyManager.isDataCapable).thenReturn(true)
+
+            // Valid subscription
+            val mockSubInfo =
+                mock<SubscriptionInfo>().also {
+                    whenever(it.subscriptionId).thenReturn(1)
+                    whenever(it.carrierName).thenReturn("test_carrier")
+                }
+            whenever(keyguardUpdateMonitor.getSubscriptionInfoForSubId(any()))
+                .thenReturn(mockSubInfo)
+            whenever(keyguardUpdateMonitor.getSimState(any()))
+                .thenReturn(TelephonyManager.SIM_STATE_READY)
+
+            // Not in service
+            whenever(keyguardUpdateMonitor.getServiceState(any()))
+                .thenReturn(ServiceState().also { it.state = ServiceState.STATE_OUT_OF_SERVICE })
+            // Subscription is default for data
+            subscriptionManagerProxy.defaultDataSubId = 1
+            // Not airplane mode
+            airplaneModeRepository.setIsAirplaneMode(false)
+
+            underTest.onViewAttached()
+            runCurrent()
+
+            assertTrue(view.text.isNullOrEmpty())
+        }
+}
diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/NotificationWakeUpCoordinatorTest.kt b/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/NotificationWakeUpCoordinatorTest.kt
index 82093ad..67b540c 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/NotificationWakeUpCoordinatorTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/NotificationWakeUpCoordinatorTest.kt
@@ -19,10 +19,15 @@
 import android.testing.AndroidTestingRunner
 import android.testing.TestableLooper
 import androidx.test.filters.SmallTest
+import com.android.compose.animation.scene.ObservableTransitionState
 import com.android.systemui.SysuiTestCase
 import com.android.systemui.animation.AnimatorTestRule
+import com.android.systemui.communal.data.repository.communalRepository
+import com.android.systemui.communal.domain.interactor.communalInteractor
+import com.android.systemui.communal.shared.model.CommunalScenes
 import com.android.systemui.dump.DumpManager
-import com.android.systemui.kosmos.Kosmos
+import com.android.systemui.kosmos.applicationCoroutineScope
+import com.android.systemui.kosmos.testScope
 import com.android.systemui.log.logcatLogBuffer
 import com.android.systemui.plugins.statusbar.StatusBarStateController
 import com.android.systemui.shade.ShadeViewController.Companion.WAKEUP_ANIMATION_DELAY_MS
@@ -34,11 +39,16 @@
 import com.android.systemui.statusbar.phone.KeyguardBypassController
 import com.android.systemui.statusbar.phone.ScreenOffAnimationController
 import com.android.systemui.statusbar.policy.HeadsUpManager
+import com.android.systemui.testKosmos
 import com.android.systemui.util.mockito.eq
 import com.android.systemui.util.mockito.mock
 import com.android.systemui.util.mockito.whenever
 import com.android.systemui.util.mockito.withArgCaptor
 import com.google.common.truth.Truth.assertThat
+import kotlinx.coroutines.ExperimentalCoroutinesApi
+import kotlinx.coroutines.flow.MutableStateFlow
+import kotlinx.coroutines.test.runCurrent
+import kotlinx.coroutines.test.runTest
 import org.junit.Before
 import org.junit.Rule
 import org.junit.Test
@@ -49,6 +59,7 @@
 import org.mockito.Mockito.verify
 import org.mockito.Mockito.verifyNoMoreInteractions
 
+@OptIn(ExperimentalCoroutinesApi::class)
 @RunWith(AndroidTestingRunner::class)
 @SmallTest
 @TestableLooper.RunWithLooper(setAsMainLooper = true)
@@ -56,7 +67,8 @@
 
     @get:Rule val animatorTestRule = AnimatorTestRule(this)
 
-    private val kosmos = Kosmos()
+    private val kosmos = testKosmos()
+    private val testScope = kosmos.testScope
 
     private val dumpManager: DumpManager = mock()
     private val headsUpManager: HeadsUpManager = mock()
@@ -97,6 +109,7 @@
         whenever(statusBarStateController.state).then { statusBarState }
         notificationWakeUpCoordinator =
             NotificationWakeUpCoordinator(
+                kosmos.applicationCoroutineScope,
                 dumpManager,
                 headsUpManager,
                 statusBarStateController,
@@ -105,6 +118,7 @@
                 screenOffAnimationController,
                 logger,
                 kosmos.notificationsKeyguardInteractor,
+                kosmos.communalInteractor,
             )
         statusBarStateCallback = withArgCaptor {
             verify(statusBarStateController).addCallback(capture())
@@ -161,6 +175,39 @@
     }
 
     @Test
+    fun setDozeToZeroWhenCommunalShowingWillFullyHideNotifications() =
+        testScope.runTest {
+            val transitionState =
+                MutableStateFlow<ObservableTransitionState>(
+                    ObservableTransitionState.Idle(CommunalScenes.Communal)
+                )
+            kosmos.communalRepository.setTransitionState(transitionState)
+            runCurrent()
+            setDozeAmount(0f)
+            verifyStackScrollerDozeAndHideAmount(dozeAmount = 1f, hideAmount = 1f)
+            assertThat(notificationWakeUpCoordinator.notificationsFullyHidden).isTrue()
+        }
+
+    @Test
+    fun closingCommunalWillShowNotifications() =
+        testScope.runTest {
+            val transitionState =
+                MutableStateFlow<ObservableTransitionState>(
+                    ObservableTransitionState.Idle(CommunalScenes.Communal)
+                )
+            kosmos.communalRepository.setTransitionState(transitionState)
+            runCurrent()
+            setDozeAmount(0f)
+            verifyStackScrollerDozeAndHideAmount(dozeAmount = 1f, hideAmount = 1f)
+            assertThat(notificationWakeUpCoordinator.notificationsFullyHidden).isTrue()
+
+            transitionState.value = ObservableTransitionState.Idle(CommunalScenes.Blank)
+            runCurrent()
+            verifyStackScrollerDozeAndHideAmount(dozeAmount = 0f, hideAmount = 0f)
+            assertThat(notificationWakeUpCoordinator.notificationsFullyHidden).isFalse()
+        }
+
+    @Test
     fun switchingToShadeWithBypassEnabledWillShowNotifications() {
         setDozeToZeroWithBypassWillFullyHideNotifications()
         clearInvocations(stackScrollerController)
diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/row/ExpandableNotificationRowTest.java b/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/row/ExpandableNotificationRowTest.java
index 0e89d80..06a4d08 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/row/ExpandableNotificationRowTest.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/row/ExpandableNotificationRowTest.java
@@ -59,7 +59,8 @@
 import com.android.internal.widget.CachingIconView;
 import com.android.systemui.SysuiTestCase;
 import com.android.systemui.SysuiTestableContext;
-import com.android.systemui.flags.FakeFeatureFlags;
+import com.android.systemui.flags.FakeFeatureFlagsClassic;
+import com.android.systemui.flags.Flags;
 import com.android.systemui.plugins.statusbar.NotificationMenuRowPlugin;
 import com.android.systemui.plugins.statusbar.StatusBarStateController;
 import com.android.systemui.statusbar.notification.AboveShelfChangedListener;
@@ -90,13 +91,14 @@
 @RunWithLooper
 public class ExpandableNotificationRowTest extends SysuiTestCase {
 
-    private final FakeFeatureFlags mFeatureFlags = new FakeFeatureFlags();
+    private final FakeFeatureFlagsClassic mFeatureFlags = new FakeFeatureFlagsClassic();
     private NotificationTestHelper mNotificationTestHelper;
     @Rule public MockitoRule mockito = MockitoJUnit.rule();
 
     @Before
     public void setUp() throws Exception {
         allowTestableLooperAsMainThread();
+        mFeatureFlags.set(Flags.ENABLE_NOTIFICATIONS_SIMULATE_SLOW_MEASURE, false);
         mNotificationTestHelper = new NotificationTestHelper(
                 mContext,
                 mDependency,
diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/StatusBarKeyguardViewManagerTest.java b/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/StatusBarKeyguardViewManagerTest.java
index 054680d..34605fe 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/StatusBarKeyguardViewManagerTest.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/StatusBarKeyguardViewManagerTest.java
@@ -597,8 +597,7 @@
 
     private static ShadeExpansionChangeEvent expansionEvent(
             float fraction, boolean expanded, boolean tracking) {
-        return new ShadeExpansionChangeEvent(
-                fraction, expanded, tracking, /* dragDownPxAmount= */ 0f);
+        return new ShadeExpansionChangeEvent(fraction, expanded, tracking);
     }
 
     @Test
@@ -607,7 +606,7 @@
         /* verify that a predictive back callback is registered when the bouncer becomes visible */
         mBouncerExpansionCallback.onVisibilityChanged(true);
         verify(mOnBackInvokedDispatcher).registerOnBackInvokedCallback(
-                eq(OnBackInvokedDispatcher.PRIORITY_OVERLAY),
+                eq(OnBackInvokedDispatcher.PRIORITY_DEFAULT),
                 mBackCallbackCaptor.capture());
 
         /* verify that the same callback is unregistered when the bouncer becomes invisible */
@@ -622,7 +621,7 @@
         mBouncerExpansionCallback.onVisibilityChanged(true);
         /* capture the predictive back callback during registration */
         verify(mOnBackInvokedDispatcher).registerOnBackInvokedCallback(
-                eq(OnBackInvokedDispatcher.PRIORITY_OVERLAY),
+                eq(OnBackInvokedDispatcher.PRIORITY_DEFAULT),
                 mBackCallbackCaptor.capture());
 
         when(mPrimaryBouncerInteractor.isFullyShowing()).thenReturn(true);
@@ -642,7 +641,7 @@
         mBouncerExpansionCallback.onVisibilityChanged(true);
         /* capture the predictive back callback during registration */
         verify(mOnBackInvokedDispatcher).registerOnBackInvokedCallback(
-                eq(OnBackInvokedDispatcher.PRIORITY_OVERLAY),
+                eq(OnBackInvokedDispatcher.PRIORITY_DEFAULT),
                 mBackCallbackCaptor.capture());
         assertTrue(mBackCallbackCaptor.getValue() instanceof OnBackAnimationCallback);
 
@@ -660,7 +659,7 @@
         mBouncerExpansionCallback.onVisibilityChanged(true);
         /* capture the predictive back callback during registration */
         verify(mOnBackInvokedDispatcher).registerOnBackInvokedCallback(
-                eq(OnBackInvokedDispatcher.PRIORITY_OVERLAY),
+                eq(OnBackInvokedDispatcher.PRIORITY_DEFAULT),
                 mBackCallbackCaptor.capture());
         assertTrue(mBackCallbackCaptor.getValue() instanceof OnBackAnimationCallback);
 
diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/fragment/CollapsedStatusBarFragmentTest.java b/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/fragment/CollapsedStatusBarFragmentTest.java
index 3da5ab9..9c3d9c6 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/fragment/CollapsedStatusBarFragmentTest.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/fragment/CollapsedStatusBarFragmentTest.java
@@ -52,7 +52,7 @@
 import com.android.systemui.plugins.statusbar.StatusBarStateController;
 import com.android.systemui.res.R;
 import com.android.systemui.shade.ShadeExpansionStateManager;
-import com.android.systemui.shade.ShadeViewController;
+import com.android.systemui.shade.domain.interactor.PanelExpansionInteractor;
 import com.android.systemui.statusbar.CommandQueue;
 import com.android.systemui.statusbar.OperatorNameViewController;
 import com.android.systemui.statusbar.disableflags.DisableFlagsLogger;
@@ -115,7 +115,7 @@
     @Mock
     private HeadsUpAppearanceController mHeadsUpAppearanceController;
     @Mock
-    private ShadeViewController mShadeViewController;
+    private PanelExpansionInteractor mPanelExpansionInteractor;
     @Mock
     private StatusBarIconController.DarkIconManager.Factory mIconManagerFactory;
     @Mock
@@ -304,7 +304,7 @@
 
         // WHEN the shade is open and configured to hide the status bar icons
         mShadeExpansionStateManager.updateState(STATE_OPEN);
-        when(mShadeViewController.shouldHideStatusBarIconsWhenExpanded()).thenReturn(true);
+        when(mPanelExpansionInteractor.shouldHideStatusBarIconsWhenExpanded()).thenReturn(true);
 
         fragment.disable(DEFAULT_DISPLAY, 0, 0, false);
 
@@ -320,7 +320,7 @@
 
         // WHEN the shade is open but *not* configured to hide the status bar icons
         mShadeExpansionStateManager.updateState(STATE_OPEN);
-        when(mShadeViewController.shouldHideStatusBarIconsWhenExpanded()).thenReturn(false);
+        when(mPanelExpansionInteractor.shouldHideStatusBarIconsWhenExpanded()).thenReturn(false);
 
         fragment.disable(DEFAULT_DISPLAY, 0, 0, false);
 
@@ -337,7 +337,7 @@
 
         // WHEN the shade is open and configured to hide the status bar icons
         mShadeExpansionStateManager.updateState(STATE_OPEN);
-        when(mShadeViewController.shouldHideStatusBarIconsWhenExpanded()).thenReturn(true);
+        when(mPanelExpansionInteractor.shouldHideStatusBarIconsWhenExpanded()).thenReturn(true);
 
         fragment.disable(DEFAULT_DISPLAY, 0, 0, false);
 
@@ -696,7 +696,7 @@
                 mCollapsedStatusBarViewBinder,
                 mStatusBarHideIconsForBouncerManager,
                 mKeyguardStateController,
-                mShadeViewController,
+                mPanelExpansionInteractor,
                 mStatusBarStateController,
                 mock(NotificationIconContainerStatusBarViewBinder.class),
                 mCommandQueue,
diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/policy/SensitiveNotificationProtectionControllerTest.kt b/packages/SystemUI/tests/src/com/android/systemui/statusbar/policy/SensitiveNotificationProtectionControllerTest.kt
index 867476f..581ca3b 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/policy/SensitiveNotificationProtectionControllerTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/policy/SensitiveNotificationProtectionControllerTest.kt
@@ -28,6 +28,7 @@
 import android.content.pm.PackageManager
 import android.media.projection.MediaProjectionInfo
 import android.media.projection.MediaProjectionManager
+import android.os.UserHandle
 import android.permission.flags.Flags.FLAG_SENSITIVE_NOTIFICATION_APP_PROTECTION
 import android.platform.test.annotations.EnableFlags
 import android.platform.test.annotations.RequiresFlagsDisabled
@@ -85,7 +86,6 @@
     @Mock private lateinit var activityManager: IActivityManager
     @Mock private lateinit var mediaProjectionManager: MediaProjectionManager
     @Mock private lateinit var packageManager: PackageManager
-    @Mock private lateinit var mediaProjectionInfo: MediaProjectionInfo
     @Mock private lateinit var listener1: Runnable
     @Mock private lateinit var listener2: Runnable
     @Mock private lateinit var listener3: Runnable
@@ -95,6 +95,7 @@
     private lateinit var globalSettings: FakeGlobalSettings
     private lateinit var mediaProjectionCallback: MediaProjectionManager.Callback
     private lateinit var controller: SensitiveNotificationProtectionControllerImpl
+    private lateinit var mediaProjectionInfo: MediaProjectionInfo
 
     @Before
     fun setUp() {
@@ -109,14 +110,29 @@
         setShareFullScreen()
         whenever(activityManager.bugreportWhitelistedPackages)
             .thenReturn(listOf(BUGREPORT_PACKAGE_NAME))
-        whenever(packageManager.getPackageUid(TEST_PROJECTION_PACKAGE_NAME, 0))
+        whenever(
+                packageManager.getPackageUidAsUser(
+                    TEST_PROJECTION_PACKAGE_NAME,
+                    UserHandle.CURRENT.identifier
+                )
+            )
             .thenReturn(TEST_PROJECTION_PACKAGE_UID)
-        whenever(packageManager.getPackageUid(BUGREPORT_PACKAGE_NAME, 0))
+        whenever(
+                packageManager.getPackageUidAsUser(
+                    BUGREPORT_PACKAGE_NAME,
+                    UserHandle.CURRENT.identifier
+                )
+            )
             .thenReturn(BUGREPORT_PACKAGE_UID)
         // SystemUi context package name is exempt, but in test scenarios its
         // com.android.systemui.tests so use that instead of hardcoding. Setup packagemanager to
         // return the correct uid in this scenario
-        whenever(packageManager.getPackageUid(mContext.packageName, 0))
+        whenever(
+                packageManager.getPackageUidAsUser(
+                    mContext.packageName,
+                    UserHandle.CURRENT.identifier
+                )
+            )
             .thenReturn(mContext.applicationInfo.uid)
 
         whenever(packageManager.checkPermission(anyString(), anyString()))
@@ -271,7 +287,7 @@
     fun isSensitiveStateActive_projectionActive_sysuiExempt_false() {
         // SystemUi context package name is exempt, but in test scenarios its
         // com.android.systemui.tests so use that instead of hardcoding
-        whenever(mediaProjectionInfo.packageName).thenReturn(mContext.packageName)
+        setShareFullScreenViaSystemUi()
         mediaProjectionCallback.onStart(mediaProjectionInfo)
 
         assertFalse(controller.isSensitiveStateActive)
@@ -309,7 +325,7 @@
 
     @Test
     fun isSensitiveStateActive_projectionActive_bugReportHandlerExempt_false() {
-        whenever(mediaProjectionInfo.packageName).thenReturn(BUGREPORT_PACKAGE_NAME)
+        setShareFullScreenViaBugReportHandler()
         mediaProjectionCallback.onStart(mediaProjectionInfo)
 
         assertFalse(controller.isSensitiveStateActive)
@@ -371,7 +387,7 @@
     fun shouldProtectNotification_projectionActive_sysuiExempt_false() {
         // SystemUi context package name is exempt, but in test scenarios its
         // com.android.systemui.tests so use that instead of hardcoding
-        whenever(mediaProjectionInfo.packageName).thenReturn(mContext.packageName)
+        setShareFullScreenViaSystemUi()
         mediaProjectionCallback.onStart(mediaProjectionInfo)
 
         val notificationEntry = setupNotificationEntry(TEST_PACKAGE_NAME, false)
@@ -415,7 +431,7 @@
 
     @Test
     fun shouldProtectNotification_projectionActive_bugReportHandlerExempt_false() {
-        whenever(mediaProjectionInfo.packageName).thenReturn(BUGREPORT_PACKAGE_NAME)
+        setShareFullScreenViaBugReportHandler()
         mediaProjectionCallback.onStart(mediaProjectionInfo)
 
         val notificationEntry = setupNotificationEntry(TEST_PACKAGE_NAME, false)
@@ -548,9 +564,7 @@
     fun logSensitiveContentProtectionSession_exemptViaSystemUi() {
         // SystemUi context package name is exempt, but in test scenarios its
         // com.android.systemui.tests so use that instead of hardcoding
-        val testPackageName = mContext.packageName
-        val testUid = mContext.applicationInfo.uid
-        whenever(mediaProjectionInfo.packageName).thenReturn(testPackageName)
+        setShareFullScreenViaSystemUi()
 
         mediaProjectionCallback.onStart(mediaProjectionInfo)
 
@@ -558,7 +572,7 @@
             FrameworkStatsLog.write(
                 eq(FrameworkStatsLog.SENSITIVE_CONTENT_MEDIA_PROJECTION_SESSION),
                 anyLong(),
-                eq(testUid),
+                eq(mContext.applicationInfo.uid),
                 eq(true),
                 eq(FrameworkStatsLog.SENSITIVE_CONTENT_MEDIA_PROJECTION_SESSION__STATE__START),
                 eq(FrameworkStatsLog.SENSITIVE_CONTENT_MEDIA_PROJECTION_SESSION__SOURCE__SYS_UI)
@@ -571,7 +585,7 @@
             FrameworkStatsLog.write(
                 eq(FrameworkStatsLog.SENSITIVE_CONTENT_MEDIA_PROJECTION_SESSION),
                 anyLong(),
-                eq(testUid),
+                eq(mContext.applicationInfo.uid),
                 eq(true),
                 eq(FrameworkStatsLog.SENSITIVE_CONTENT_MEDIA_PROJECTION_SESSION__STATE__STOP),
                 eq(FrameworkStatsLog.SENSITIVE_CONTENT_MEDIA_PROJECTION_SESSION__SOURCE__SYS_UI)
@@ -582,8 +596,7 @@
     @Test
     fun logSensitiveContentProtectionSession_exemptViaBugReportHandler() {
         // Setup exempt via bugreport handler
-        whenever(mediaProjectionInfo.packageName).thenReturn(BUGREPORT_PACKAGE_NAME)
-
+        setShareFullScreenViaBugReportHandler()
         mediaProjectionCallback.onStart(mediaProjectionInfo)
 
         verify {
@@ -619,13 +632,26 @@
     }
 
     private fun setShareFullScreen() {
-        whenever(mediaProjectionInfo.packageName).thenReturn(TEST_PROJECTION_PACKAGE_NAME)
-        whenever(mediaProjectionInfo.launchCookie).thenReturn(null)
+        setShareScreen(TEST_PROJECTION_PACKAGE_NAME, true)
+    }
+
+    private fun setShareFullScreenViaBugReportHandler() {
+        setShareScreen(BUGREPORT_PACKAGE_NAME, true)
+    }
+
+    private fun setShareFullScreenViaSystemUi() {
+        // SystemUi context package name is exempt, but in test scenarios its
+        // com.android.systemui.tests so use that instead of hardcoding
+        setShareScreen(mContext.packageName, true)
     }
 
     private fun setShareSingleApp() {
-        whenever(mediaProjectionInfo.packageName).thenReturn(TEST_PROJECTION_PACKAGE_NAME)
-        whenever(mediaProjectionInfo.launchCookie).thenReturn(ActivityOptions.LaunchCookie())
+        setShareScreen(TEST_PROJECTION_PACKAGE_NAME, false)
+    }
+
+    private fun setShareScreen(packageName: String, fullScreen: Boolean) {
+        val launchCookie = if (fullScreen) null else ActivityOptions.LaunchCookie()
+        mediaProjectionInfo = MediaProjectionInfo(packageName, UserHandle.CURRENT, launchCookie)
     }
 
     private fun setupNotificationEntry(
diff --git a/packages/SystemUI/tests/src/com/android/systemui/user/domain/interactor/UserSwitcherInteractorTest.kt b/packages/SystemUI/tests/src/com/android/systemui/user/domain/interactor/UserSwitcherInteractorTest.kt
index 1b43851..3dee093 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/user/domain/interactor/UserSwitcherInteractorTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/user/domain/interactor/UserSwitcherInteractorTest.kt
@@ -1014,6 +1014,136 @@
         verify(spyContext, never()).startServiceAsUser(any(), any())
     }
 
+    @Test
+    fun userIsAdminAndRestricted_addUserActionsNotAdded() {
+        createUserInteractor()
+        testScope.runTest {
+            val id = 0
+            val userInfo =
+                UserInfo(
+                    id,
+                    "child",
+                    /* iconPath= */ "",
+                    /* flags= */ UserInfo.FLAG_ADMIN,
+                    UserManager.USER_TYPE_FULL_RESTRICTED,
+                )
+            whenever(
+                    manager.hasUserRestrictionForUser(
+                        UserManager.DISALLOW_ADD_USER,
+                        UserHandle.of(id)
+                    )
+                )
+                .thenReturn(true)
+
+            userRepository.setUserInfos(listOf(userInfo))
+            userRepository.setSelectedUserInfo(userInfo)
+            userRepository.setSettings(UserSwitcherSettingsModel(isUserSwitcherEnabled = true))
+
+            val value = collectLastValue(underTest.actions)
+            runCurrent()
+
+            assertThat(value()).isEqualTo(listOf(UserActionModel.NAVIGATE_TO_USER_MANAGEMENT))
+        }
+    }
+
+    @Test
+    fun userIsNotRestrictedAndCannotAddGuests_actionsDoesNotIncludeAddGuest() {
+        createUserInteractor()
+        testScope.runTest {
+            val userInfos = createUserInfos(count = 2, includeGuest = false)
+
+            userRepository.setUserInfos(userInfos)
+            userRepository.setSelectedUserInfo(userInfos[0])
+            userRepository.setSettings(UserSwitcherSettingsModel(isUserSwitcherEnabled = true))
+            keyguardRepository.setKeyguardShowing(false)
+
+            whenever(manager.canAddMoreUsers(UserManager.USER_TYPE_FULL_GUEST)).thenReturn(false)
+
+            val value = collectLastValue(underTest.actions)
+            runCurrent()
+
+            assertThat(value())
+                .isEqualTo(
+                    listOf(
+                        UserActionModel.ADD_USER,
+                        UserActionModel.ADD_SUPERVISED_USER,
+                        UserActionModel.NAVIGATE_TO_USER_MANAGEMENT,
+                    )
+                )
+        }
+    }
+
+    @Test
+    fun userIsNotRestrictedAndCannotAddUsers_actionsDoesNotIncludeAddUsers() {
+        createUserInteractor()
+        testScope.runTest {
+            val userInfos = createUserInfos(count = 2, includeGuest = false)
+
+            userRepository.setUserInfos(userInfos)
+            userRepository.setSelectedUserInfo(userInfos[0])
+            userRepository.setSettings(UserSwitcherSettingsModel(isUserSwitcherEnabled = true))
+            keyguardRepository.setKeyguardShowing(false)
+
+            whenever(manager.canAddMoreUsers(UserManager.USER_TYPE_FULL_SECONDARY))
+                .thenReturn(false)
+
+            val value = collectLastValue(underTest.actions)
+            runCurrent()
+
+            assertThat(value())
+                .isEqualTo(
+                    listOf(
+                        UserActionModel.ENTER_GUEST_MODE,
+                        UserActionModel.NAVIGATE_TO_USER_MANAGEMENT,
+                    )
+                )
+        }
+    }
+
+    @Test
+    fun systemUserHasRestrictions_addUserActionsNotAdded() {
+        createUserInteractor()
+        testScope.runTest {
+            val systemId = 0
+            val systemUser =
+                UserInfo(
+                    systemId,
+                    "system",
+                    /* iconPath= */ "",
+                    /* flags= */ UserInfo.FLAG_SYSTEM,
+                    UserManager.USER_TYPE_SYSTEM_HEADLESS,
+                )
+            val adminId = 10
+            val adminUser =
+                UserInfo(
+                    adminId,
+                    "admin",
+                    /* iconPath= */ "",
+                    /* flags= */ UserInfo.FLAG_ADMIN,
+                    UserManager.USER_TYPE_FULL_SYSTEM,
+                )
+
+            userRepository.setUserInfos(listOf(systemUser, adminUser))
+            userRepository.setSelectedUserInfo(adminUser)
+            userRepository.setSettings(UserSwitcherSettingsModel(isUserSwitcherEnabled = true))
+            keyguardRepository.setKeyguardShowing(false)
+
+            whenever(headlessSystemUserMode.isHeadlessSystemUserMode()).thenReturn(true)
+            whenever(
+                    manager.hasUserRestrictionForUser(
+                        UserManager.DISALLOW_ADD_USER,
+                        UserHandle.of(0)
+                    )
+                )
+                .thenReturn(true)
+
+            val value = collectLastValue(underTest.actions)
+            runCurrent()
+
+            assertThat(value()).isEqualTo(listOf(UserActionModel.NAVIGATE_TO_USER_MANAGEMENT))
+        }
+    }
+
     private fun assertUsers(
         models: List<UserModel>?,
         count: Int,
diff --git a/packages/SystemUI/tests/src/com/android/systemui/wallet/controller/QuickAccessWalletControllerTest.java b/packages/SystemUI/tests/src/com/android/systemui/wallet/controller/QuickAccessWalletControllerTest.java
index fccb936..dc5597a 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/wallet/controller/QuickAccessWalletControllerTest.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/wallet/controller/QuickAccessWalletControllerTest.java
@@ -29,6 +29,7 @@
 import static org.mockito.Mockito.when;
 
 import android.app.PendingIntent;
+import android.app.role.RoleManager;
 import android.content.Intent;
 import android.service.quickaccesswallet.GetWalletCardsRequest;
 import android.service.quickaccesswallet.QuickAccessWalletClient;
@@ -54,11 +55,14 @@
 import org.mockito.Mock;
 import org.mockito.MockitoAnnotations;
 
+import java.util.List;
+
 @RunWith(AndroidTestingRunner.class)
 @TestableLooper.RunWithLooper
 @SmallTest
 public class QuickAccessWalletControllerTest extends SysuiTestCase {
 
+    private static final String WALLET_ROLE_HOLDER = "wallet.role.holder";
     @Mock
     private QuickAccessWalletClient mQuickAccessWalletClient;
     @Mock
@@ -69,6 +73,8 @@
     private ActivityStarter mActivityStarter;
     @Mock
     private ActivityTransitionAnimator.Controller mAnimationController;
+    @Mock
+    private RoleManager mRoleManager;
     @Captor
     private ArgumentCaptor<GetWalletCardsRequest> mRequestCaptor;
     @Captor
@@ -102,7 +108,8 @@
                 MoreExecutors.directExecutor(),
                 mSecureSettings,
                 mQuickAccessWalletClient,
-                mClock);
+                mClock,
+                mRoleManager);
     }
 
     @Test
@@ -113,6 +120,24 @@
     }
 
     @Test
+    public void walletRoleAvailable_isAvailable() {
+        when(mRoleManager.isRoleAvailable(eq(RoleManager.ROLE_WALLET))).thenReturn(true);
+        when(mRoleManager.getRoleHolders(eq(RoleManager.ROLE_WALLET)))
+                .thenReturn(List.of(WALLET_ROLE_HOLDER));
+
+        assertTrue(mController.isWalletRoleAvailable());
+    }
+
+    @Test
+    public void walletRoleAvailable_isNotAvailable() {
+        when(mRoleManager.isRoleAvailable(eq(RoleManager.ROLE_WALLET))).thenReturn(false);
+        when(mRoleManager.getRoleHolders(eq(RoleManager.ROLE_WALLET)))
+                .thenReturn(List.of(WALLET_ROLE_HOLDER));
+
+        assertFalse(mController.isWalletRoleAvailable());
+    }
+
+    @Test
     public void walletServiceUnavailable_walletNotEnabled() {
         when(mQuickAccessWalletClient.isWalletServiceAvailable()).thenReturn(false);
 
diff --git a/packages/SystemUI/tests/utils/src/com/android/systemui/SysUITestModule.kt b/packages/SystemUI/tests/utils/src/com/android/systemui/SysUITestModule.kt
index bc0bf9d..de7b14d 100644
--- a/packages/SystemUI/tests/utils/src/com/android/systemui/SysUITestModule.kt
+++ b/packages/SystemUI/tests/utils/src/com/android/systemui/SysUITestModule.kt
@@ -32,6 +32,7 @@
 import com.android.systemui.deviceentry.domain.interactor.SystemUIDeviceEntryFaceAuthInteractor
 import com.android.systemui.scene.SceneContainerFrameworkModule
 import com.android.systemui.scene.shared.flag.SceneContainerFlags
+import com.android.systemui.scene.shared.model.SceneContainerConfig
 import com.android.systemui.scene.shared.model.SceneDataSource
 import com.android.systemui.scene.shared.model.SceneDataSourceDelegator
 import com.android.systemui.shade.domain.interactor.BaseShadeInteractor
@@ -45,6 +46,7 @@
 import javax.inject.Provider
 import kotlin.coroutines.CoroutineContext
 import kotlin.coroutines.EmptyCoroutineContext
+import kotlinx.coroutines.CoroutineScope
 import kotlinx.coroutines.CoroutineStart
 import kotlinx.coroutines.ExperimentalCoroutinesApi
 import kotlinx.coroutines.flow.Flow
@@ -71,7 +73,10 @@
     @Binds @Main fun bindMainResources(resources: Resources): Resources
     @Binds fun bindBroadcastDispatcher(fake: FakeBroadcastDispatcher): BroadcastDispatcher
     @Binds @SysUISingleton fun bindsShadeInteractor(sii: ShadeInteractorImpl): ShadeInteractor
-    @Binds fun bindSceneDataSource(delegator: SceneDataSourceDelegator): SceneDataSource
+
+    @Binds
+    @SysUISingleton
+    fun bindSceneDataSource(delegator: SceneDataSourceDelegator): SceneDataSource
 
     @Binds
     fun provideFaceAuthInteractor(
@@ -109,6 +114,15 @@
                 sceneContainerOff.get()
             }
         }
+
+        @Provides
+        @SysUISingleton
+        fun providesSceneDataSourceDelegator(
+            @Application applicationScope: CoroutineScope,
+            config: SceneContainerConfig,
+        ): SceneDataSourceDelegator {
+            return SceneDataSourceDelegator(applicationScope, config)
+        }
     }
 }
 
diff --git a/packages/SystemUI/tests/utils/src/com/android/systemui/communal/data/repository/FakeCommunalRepository.kt b/packages/SystemUI/tests/utils/src/com/android/systemui/communal/data/repository/FakeCommunalRepository.kt
index 5ff588f..9f5c6b8 100644
--- a/packages/SystemUI/tests/utils/src/com/android/systemui/communal/data/repository/FakeCommunalRepository.kt
+++ b/packages/SystemUI/tests/utils/src/com/android/systemui/communal/data/repository/FakeCommunalRepository.kt
@@ -2,6 +2,7 @@
 
 import com.android.compose.animation.scene.ObservableTransitionState
 import com.android.compose.animation.scene.SceneKey
+import com.android.compose.animation.scene.TransitionKey
 import com.android.systemui.communal.shared.model.CommunalScenes
 import kotlinx.coroutines.CoroutineScope
 import kotlinx.coroutines.ExperimentalCoroutinesApi
@@ -17,11 +18,11 @@
 @OptIn(ExperimentalCoroutinesApi::class)
 class FakeCommunalRepository(
     applicationScope: CoroutineScope,
-    override val desiredScene: MutableStateFlow<SceneKey> =
+    override val currentScene: MutableStateFlow<SceneKey> =
         MutableStateFlow(CommunalScenes.Default),
 ) : CommunalRepository {
-    override fun setDesiredScene(desiredScene: SceneKey) {
-        this.desiredScene.value = desiredScene
+    override fun changeScene(toScene: SceneKey, transitionKey: TransitionKey?) {
+        this.currentScene.value = toScene
     }
 
     private val defaultTransitionState = ObservableTransitionState.Idle(CommunalScenes.Default)
diff --git a/packages/SystemUI/tests/utils/src/com/android/systemui/keyguard/ui/viewmodel/AlternateBouncerToGoneTransitionViewModelKosmos.kt b/packages/SystemUI/tests/utils/src/com/android/systemui/keyguard/ui/viewmodel/AlternateBouncerToGoneTransitionViewModelKosmos.kt
index c909dd6..b943298 100644
--- a/packages/SystemUI/tests/utils/src/com/android/systemui/keyguard/ui/viewmodel/AlternateBouncerToGoneTransitionViewModelKosmos.kt
+++ b/packages/SystemUI/tests/utils/src/com/android/systemui/keyguard/ui/viewmodel/AlternateBouncerToGoneTransitionViewModelKosmos.kt
@@ -21,11 +21,13 @@
 import com.android.systemui.keyguard.ui.keyguardTransitionAnimationFlow
 import com.android.systemui.kosmos.Kosmos
 import com.android.systemui.kosmos.Kosmos.Fixture
+import com.android.systemui.statusbar.sysuiStatusBarStateController
 import kotlinx.coroutines.ExperimentalCoroutinesApi
 
 val Kosmos.alternateBouncerToGoneTransitionViewModel by Fixture {
     AlternateBouncerToGoneTransitionViewModel(
         bouncerToGoneFlows = bouncerToGoneFlows,
         animationFlow = keyguardTransitionAnimationFlow,
+        statusBarStateController = sysuiStatusBarStateController,
     )
 }
diff --git a/packages/SystemUI/tests/utils/src/com/android/systemui/keyguard/ui/viewmodel/LockscreenContentViewModelKosmos.kt b/packages/SystemUI/tests/utils/src/com/android/systemui/keyguard/ui/viewmodel/LockscreenContentViewModelKosmos.kt
index f0fedd2..1e25f7f 100644
--- a/packages/SystemUI/tests/utils/src/com/android/systemui/keyguard/ui/viewmodel/LockscreenContentViewModelKosmos.kt
+++ b/packages/SystemUI/tests/utils/src/com/android/systemui/keyguard/ui/viewmodel/LockscreenContentViewModelKosmos.kt
@@ -20,6 +20,7 @@
 import com.android.systemui.keyguard.domain.interactor.keyguardBlueprintInteractor
 import com.android.systemui.keyguard.domain.interactor.keyguardClockInteractor
 import com.android.systemui.kosmos.Kosmos
+import com.android.systemui.kosmos.applicationCoroutineScope
 import com.android.systemui.shade.domain.interactor.shadeInteractor
 
 val Kosmos.lockscreenContentViewModel by
@@ -30,5 +31,6 @@
             authController = authController,
             longPress = keyguardLongPressViewModel,
             shadeInteractor = shadeInteractor,
+            applicationScope = applicationCoroutineScope,
         )
     }
diff --git a/packages/SystemUI/tests/utils/src/com/android/systemui/keyguard/ui/viewmodel/LockscreenToGoneTransitionViewModelKosmos.kt b/packages/SystemUI/tests/utils/src/com/android/systemui/keyguard/ui/viewmodel/LockscreenToGoneTransitionViewModelKosmos.kt
index 17c3a14..7a023ee 100644
--- a/packages/SystemUI/tests/utils/src/com/android/systemui/keyguard/ui/viewmodel/LockscreenToGoneTransitionViewModelKosmos.kt
+++ b/packages/SystemUI/tests/utils/src/com/android/systemui/keyguard/ui/viewmodel/LockscreenToGoneTransitionViewModelKosmos.kt
@@ -19,11 +19,13 @@
 import com.android.systemui.keyguard.ui.keyguardTransitionAnimationFlow
 import com.android.systemui.kosmos.Kosmos
 import com.android.systemui.kosmos.Kosmos.Fixture
+import com.android.systemui.statusbar.sysuiStatusBarStateController
 import kotlinx.coroutines.ExperimentalCoroutinesApi
 
 @ExperimentalCoroutinesApi
 val Kosmos.lockscreenToGoneTransitionViewModel by Fixture {
     LockscreenToGoneTransitionViewModel(
         animationFlow = keyguardTransitionAnimationFlow,
+        statusBarStateController = sysuiStatusBarStateController,
     )
 }
diff --git a/packages/SystemUI/tests/utils/src/com/android/systemui/scene/domain/interactor/SceneContainerOcclusionInteractorKosmos.kt b/packages/SystemUI/tests/utils/src/com/android/systemui/scene/domain/interactor/SceneContainerOcclusionInteractorKosmos.kt
new file mode 100644
index 0000000..b32960a
--- /dev/null
+++ b/packages/SystemUI/tests/utils/src/com/android/systemui/scene/domain/interactor/SceneContainerOcclusionInteractorKosmos.kt
@@ -0,0 +1,30 @@
+/*
+ * Copyright (C) 2024 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.systemui.scene.domain.interactor
+
+import com.android.systemui.keyguard.domain.interactor.keyguardTransitionInteractor
+import com.android.systemui.kosmos.Kosmos
+import com.android.systemui.kosmos.Kosmos.Fixture
+import com.android.systemui.statusbar.domain.interactor.keyguardOcclusionInteractor
+
+val Kosmos.sceneContainerOcclusionInteractor by Fixture {
+    SceneContainerOcclusionInteractor(
+        keyguardOcclusionInteractor = keyguardOcclusionInteractor,
+        sceneInteractor = sceneInteractor,
+        keyguardTransitionInteractor = keyguardTransitionInteractor,
+    )
+}
diff --git a/packages/SystemUI/tests/utils/src/com/android/systemui/shade/ShadeControllerKosmos.kt b/packages/SystemUI/tests/utils/src/com/android/systemui/shade/ShadeControllerKosmos.kt
index 513c6ab..4a2eaf0 100644
--- a/packages/SystemUI/tests/utils/src/com/android/systemui/shade/ShadeControllerKosmos.kt
+++ b/packages/SystemUI/tests/utils/src/com/android/systemui/shade/ShadeControllerKosmos.kt
@@ -27,6 +27,7 @@
 import com.android.systemui.log.LogBuffer
 import com.android.systemui.plugins.statusbar.statusBarStateController
 import com.android.systemui.scene.domain.interactor.sceneInteractor
+import com.android.systemui.scene.shared.flag.SceneContainerFlag
 import com.android.systemui.shade.domain.interactor.shadeInteractor
 import com.android.systemui.statusbar.CommandQueue
 import com.android.systemui.statusbar.NotificationShadeWindowController
@@ -78,4 +79,11 @@
             { mock<NotificationGutsManager>() },
         )
     }
-var Kosmos.shadeController: ShadeController by Kosmos.Fixture { shadeControllerImpl }
+var Kosmos.shadeController: ShadeController by
+    Kosmos.Fixture {
+        if (SceneContainerFlag.isEnabled) {
+            shadeControllerSceneImpl
+        } else {
+            shadeControllerImpl
+        }
+    }
diff --git a/packages/SystemUI/tests/utils/src/com/android/systemui/shade/domain/startable/ShadeStartableKosmos.kt b/packages/SystemUI/tests/utils/src/com/android/systemui/shade/domain/startable/ShadeStartableKosmos.kt
index 7dfa686..b85858d 100644
--- a/packages/SystemUI/tests/utils/src/com/android/systemui/shade/domain/startable/ShadeStartableKosmos.kt
+++ b/packages/SystemUI/tests/utils/src/com/android/systemui/shade/domain/startable/ShadeStartableKosmos.kt
@@ -22,11 +22,17 @@
 import com.android.systemui.kosmos.Kosmos.Fixture
 import com.android.systemui.kosmos.applicationCoroutineScope
 import com.android.systemui.log.LogBuffer
+import com.android.systemui.scene.domain.interactor.sceneInteractor
+import com.android.systemui.shade.ShadeExpansionStateManager
 import com.android.systemui.shade.data.repository.shadeRepository
+import com.android.systemui.shade.domain.interactor.panelExpansionInteractor
 import com.android.systemui.shade.transition.ScrimShadeTransitionController
 import com.android.systemui.statusbar.policy.splitShadeStateController
 import com.android.systemui.util.mockito.mock
 
+@Deprecated("ShadeExpansionStateManager is deprecated. Remove your dependency on it instead.")
+val Kosmos.shadeExpansionStateManager by Fixture { ShadeExpansionStateManager() }
+
 val Kosmos.shadeStartable by Fixture {
     ShadeStartable(
         applicationScope = applicationCoroutineScope,
@@ -34,7 +40,10 @@
         touchLog = mock<LogBuffer>(),
         configurationRepository = configurationRepository,
         shadeRepository = shadeRepository,
-        controller = splitShadeStateController,
+        splitShadeStateController = splitShadeStateController,
         scrimShadeTransitionController = mock<ScrimShadeTransitionController>(),
+        sceneInteractorProvider = { sceneInteractor },
+        panelExpansionInteractorProvider = { panelExpansionInteractor },
+        shadeExpansionStateManager = shadeExpansionStateManager,
     )
 }
diff --git a/packages/SystemUI/tests/utils/src/com/android/systemui/statusbar/domain/interactor/KeyguardOcclusionInteractorKosmos.kt b/packages/SystemUI/tests/utils/src/com/android/systemui/statusbar/domain/interactor/KeyguardOcclusionInteractorKosmos.kt
index d793740..a90a9ff 100644
--- a/packages/SystemUI/tests/utils/src/com/android/systemui/statusbar/domain/interactor/KeyguardOcclusionInteractorKosmos.kt
+++ b/packages/SystemUI/tests/utils/src/com/android/systemui/statusbar/domain/interactor/KeyguardOcclusionInteractorKosmos.kt
@@ -16,6 +16,7 @@
 
 package com.android.systemui.statusbar.domain.interactor
 
+import com.android.systemui.deviceentry.domain.interactor.deviceUnlockedInteractor
 import com.android.systemui.keyguard.data.repository.keyguardOcclusionRepository
 import com.android.systemui.keyguard.domain.interactor.KeyguardOcclusionInteractor
 import com.android.systemui.keyguard.domain.interactor.keyguardInteractor
@@ -27,10 +28,11 @@
 val Kosmos.keyguardOcclusionInteractor by
     Kosmos.Fixture {
         KeyguardOcclusionInteractor(
-            scope = testScope.backgroundScope,
+            applicationScope = testScope.backgroundScope,
             repository = keyguardOcclusionRepository,
             powerInteractor = powerInteractor,
             transitionInteractor = keyguardTransitionInteractor,
             keyguardInteractor = keyguardInteractor,
+            deviceUnlockedInteractor = { deviceUnlockedInteractor },
         )
     }
diff --git a/packages/SystemUI/tests/utils/src/com/android/systemui/volume/data/repository/FakeAudioRepository.kt b/packages/SystemUI/tests/utils/src/com/android/systemui/volume/data/repository/FakeAudioRepository.kt
index a3ad2b8..4788624 100644
--- a/packages/SystemUI/tests/utils/src/com/android/systemui/volume/data/repository/FakeAudioRepository.kt
+++ b/packages/SystemUI/tests/utils/src/com/android/systemui/volume/data/repository/FakeAudioRepository.kt
@@ -44,6 +44,8 @@
     private val models: MutableMap<AudioStream, MutableStateFlow<AudioStreamModel>> = mutableMapOf()
     private val lastAudibleVolumes: MutableMap<AudioStream, Int> = mutableMapOf()
 
+    private var isAffectedByMute: MutableMap<AudioStream, Boolean> = mutableMapOf()
+
     private fun getAudioStreamModelState(
         audioStream: AudioStream
     ): MutableStateFlow<AudioStreamModel> =
@@ -93,4 +95,15 @@
     fun setLastAudibleVolume(audioStream: AudioStream, volume: Int) {
         lastAudibleVolumes[audioStream] = volume
     }
+
+    override suspend fun setRingerMode(audioStream: AudioStream, mode: RingerMode) {
+        mutableRingerMode.value = mode
+    }
+
+    override suspend fun isAffectedByMute(audioStream: AudioStream): Boolean =
+        isAffectedByMute[audioStream] ?: true
+
+    fun setIsAffectedByMute(audioStream: AudioStream, isAffected: Boolean) {
+        isAffectedByMute[audioStream] = isAffected
+    }
 }
diff --git a/ravenwood/Android.bp b/ravenwood/Android.bp
index 178102e..8905ad3 100644
--- a/ravenwood/Android.bp
+++ b/ravenwood/Android.bp
@@ -151,6 +151,18 @@
 
 filegroup {
     name: "ravenwood-services-jarjar-rules",
-    srcs: ["ravenwood-services-jarjar-rules.txt"],
+    srcs: ["texts/ravenwood-services-jarjar-rules.txt"],
     visibility: ["//frameworks/base"],
 }
+
+// For collecting the *stats.csv files in a known directory under out/host/linux-x86/testcases/.
+// The "test" just shows the available stats filenames.
+sh_test_host {
+    name: "ravenwood-stats-checker",
+    src: "scripts/ravenwood-stats-checker.sh",
+    test_suites: ["general-tests"],
+    data: [
+        ":framework-minus-apex.ravenwood.stats",
+        ":services.core.ravenwood.stats",
+    ],
+}
diff --git a/ravenwood/bulk_enable.py b/ravenwood/scripts/bulk_enable.py
similarity index 100%
rename from ravenwood/bulk_enable.py
rename to ravenwood/scripts/bulk_enable.py
diff --git a/ravenwood/fix_test_runner.py b/ravenwood/scripts/fix_test_runner.py
similarity index 100%
rename from ravenwood/fix_test_runner.py
rename to ravenwood/scripts/fix_test_runner.py
diff --git a/ravenwood/list-ravenwood-tests.sh b/ravenwood/scripts/list-ravenwood-tests.sh
similarity index 100%
rename from ravenwood/list-ravenwood-tests.sh
rename to ravenwood/scripts/list-ravenwood-tests.sh
diff --git a/ravenwood/run-ravenwood-tests.sh b/ravenwood/scripts/ravenwood-stats-checker.sh
similarity index 65%
copy from ravenwood/run-ravenwood-tests.sh
copy to ravenwood/scripts/ravenwood-stats-checker.sh
index a303626..93f4a3f 100755
--- a/ravenwood/run-ravenwood-tests.sh
+++ b/ravenwood/scripts/ravenwood-stats-checker.sh
@@ -13,16 +13,6 @@
 # See the License for the specific language governing permissions and
 # limitations under the License.
 
-# Run all the ravenwood tests + hoststubgen unit tests.
-
-all_tests="hoststubgentest tiny-framework-dump-test hoststubgen-invoke-test"
-
-# "echo" is to remove the newlines
-all_tests="$all_tests $(echo $(${0%/*}/list-ravenwood-tests.sh) )"
-
-run() {
-    echo "Running: $*"
-    "${@}"
-}
-
-run ${ATEST:-atest} $all_tests
+# Just print the available *.csv filenames.
+echo '#Stats files:'
+ls *.csv
\ No newline at end of file
diff --git a/ravenwood/scripts/ravenwood-stats-collector.sh b/ravenwood/scripts/ravenwood-stats-collector.sh
new file mode 100755
index 0000000..4dcaa2b
--- /dev/null
+++ b/ravenwood/scripts/ravenwood-stats-collector.sh
@@ -0,0 +1,52 @@
+#!/bin/bash
+# Copyright (C) 2024 The Android Open Source Project
+#
+# Licensed under the Apache License, Version 2.0 (the "License");
+# you may not use this file except in compliance with the License.
+# You may obtain a copy of the License at
+#
+#      http://www.apache.org/licenses/LICENSE-2.0
+#
+# Unless required by applicable law or agreed to in writing, software
+# distributed under the License is distributed on an "AS IS" BASIS,
+# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+# See the License for the specific language governing permissions and
+# limitations under the License.
+
+# Script to collect the ravenwood "stats" CVS files and create a single file.
+
+set -e
+
+# Output file
+out=/tmp/ravenwood-stats-all.csv
+
+# Where the input files are.
+path=$ANDROID_BUILD_TOP/out/host/linux-x86/testcases/ravenwood-stats-checker/x86_64/
+
+m() {
+    ${ANDROID_BUILD_TOP}/build/soong/soong_ui.bash --make-mode "$@"
+}
+
+# Building this will generate the files we need.
+m ravenwood-stats-checker
+
+# Start...
+
+cd $path
+
+dump() {
+    local jar=$1
+    local file=$2
+
+    sed -e '1d' -e "s/^/$jar,/"  $file
+}
+
+collect() {
+    echo 'Jar,PackageName,ClassName,SupportedMethods,TotalMethods'
+    dump "framework-minus-apex"  hoststubgen_framework-minus-apex_stats.csv
+    dump "service.core"  hoststubgen_services.core_stats.csv
+}
+
+collect >$out
+
+echo "Full dump CVS created at $out"
diff --git a/ravenwood/run-ravenwood-tests.sh b/ravenwood/scripts/run-ravenwood-tests.sh
similarity index 95%
rename from ravenwood/run-ravenwood-tests.sh
rename to ravenwood/scripts/run-ravenwood-tests.sh
index a303626..926c08f 100755
--- a/ravenwood/run-ravenwood-tests.sh
+++ b/ravenwood/scripts/run-ravenwood-tests.sh
@@ -15,7 +15,7 @@
 
 # Run all the ravenwood tests + hoststubgen unit tests.
 
-all_tests="hoststubgentest tiny-framework-dump-test hoststubgen-invoke-test"
+all_tests="hoststubgentest tiny-framework-dump-test hoststubgen-invoke-test ravenwood-stats-checker"
 
 # "echo" is to remove the newlines
 all_tests="$all_tests $(echo $(${0%/*}/list-ravenwood-tests.sh) )"
diff --git a/ravenwood/framework-minus-apex-ravenwood-policies.txt b/ravenwood/texts/framework-minus-apex-ravenwood-policies.txt
similarity index 100%
rename from ravenwood/framework-minus-apex-ravenwood-policies.txt
rename to ravenwood/texts/framework-minus-apex-ravenwood-policies.txt
diff --git a/ravenwood/ravenwood-annotation-allowed-classes.txt b/ravenwood/texts/ravenwood-annotation-allowed-classes.txt
similarity index 100%
rename from ravenwood/ravenwood-annotation-allowed-classes.txt
rename to ravenwood/texts/ravenwood-annotation-allowed-classes.txt
diff --git a/ravenwood/ravenwood-services-jarjar-rules.txt b/ravenwood/texts/ravenwood-services-jarjar-rules.txt
similarity index 100%
rename from ravenwood/ravenwood-services-jarjar-rules.txt
rename to ravenwood/texts/ravenwood-services-jarjar-rules.txt
diff --git a/ravenwood/ravenwood-standard-options.txt b/ravenwood/texts/ravenwood-standard-options.txt
similarity index 100%
rename from ravenwood/ravenwood-standard-options.txt
rename to ravenwood/texts/ravenwood-standard-options.txt
diff --git a/ravenwood/services.core-ravenwood-policies.txt b/ravenwood/texts/services.core-ravenwood-policies.txt
similarity index 100%
rename from ravenwood/services.core-ravenwood-policies.txt
rename to ravenwood/texts/services.core-ravenwood-policies.txt
diff --git a/services/Android.bp b/services/Android.bp
index 7bbb42e..29d1acf 100644
--- a/services/Android.bp
+++ b/services/Android.bp
@@ -254,6 +254,7 @@
     required: [
         "libukey2_jni_shared",
         "protolog.conf.json.gz",
+        "core.protolog.pb",
     ],
     lint: {
         baseline_filename: "lint-baseline.xml",
diff --git a/services/companion/java/com/android/server/companion/CompanionDeviceManagerService.java b/services/companion/java/com/android/server/companion/CompanionDeviceManagerService.java
index 2d59309..8ac1eb9 100644
--- a/services/companion/java/com/android/server/companion/CompanionDeviceManagerService.java
+++ b/services/companion/java/com/android/server/companion/CompanionDeviceManagerService.java
@@ -82,7 +82,6 @@
 import android.os.UserManager;
 import android.util.ArraySet;
 import android.util.ExceptionUtils;
-import android.util.Log;
 import android.util.Slog;
 
 import com.android.internal.app.IAppOpsService;
@@ -121,8 +120,7 @@
 
 @SuppressLint("LongLogTag")
 public class CompanionDeviceManagerService extends SystemService {
-    static final String TAG = "CDM_CompanionDeviceManagerService";
-    static final boolean DEBUG = false;
+    private static final String TAG = "CDM_CompanionDeviceManagerService";
 
     private static final long PAIR_WITHOUT_PROMPT_WINDOW_MS = 10 * 60 * 1000; // 10 min
 
@@ -135,7 +133,6 @@
     private final IAppOpsService mAppOpsManager;
     private final PowerExemptionManager mPowerExemptionManager;
     private final PackageManagerInternal mPackageManagerInternal;
-    private final PowerManagerInternal mPowerManagerInternal;
 
     private final AssociationStore mAssociationStore;
     private final SystemDataTransferRequestStore mSystemDataTransferRequestStore;
@@ -160,7 +157,8 @@
         mAmInternal = LocalServices.getService(ActivityManagerInternal.class);
         mPackageManagerInternal = LocalServices.getService(PackageManagerInternal.class);
         final UserManager userManager = context.getSystemService(UserManager.class);
-        mPowerManagerInternal = LocalServices.getService(PowerManagerInternal.class);
+        final PowerManagerInternal powerManagerInternal = LocalServices.getService(
+                PowerManagerInternal.class);
 
         final AssociationDiskStore associationDiskStore = new AssociationDiskStore();
         mAssociationStore = new AssociationStore(context, userManager, associationDiskStore);
@@ -178,7 +176,7 @@
 
         mDevicePresenceProcessor = new DevicePresenceProcessor(context,
                 mCompanionAppBinder, userManager, mAssociationStore, mObservableUuidStore,
-                mPowerManagerInternal);
+                powerManagerInternal);
 
         mTransportManager = new CompanionTransportManager(context, mAssociationStore);
 
@@ -252,11 +250,6 @@
 
     private void onPackageRemoveOrDataClearedInternal(
             @UserIdInt int userId, @NonNull String packageName) {
-        if (DEBUG) {
-            Log.i(TAG, "onPackageRemove_Or_DataCleared() u" + userId + "/"
-                    + packageName);
-        }
-
         // Clear all associations for the package.
         final List<AssociationInfo> associationsForPackage =
                 mAssociationStore.getAssociationsByPackage(userId, packageName);
@@ -279,8 +272,6 @@
     }
 
     private void onPackageModifiedInternal(@UserIdInt int userId, @NonNull String packageName) {
-        if (DEBUG) Log.i(TAG, "onPackageModified() u" + userId + "/" + packageName);
-
         final List<AssociationInfo> associationsForPackage =
                 mAssociationStore.getAssociationsByPackage(userId, packageName);
         for (AssociationInfo association : associationsForPackage) {
diff --git a/services/core/java/com/android/server/SensitiveContentProtectionManagerService.java b/services/core/java/com/android/server/SensitiveContentProtectionManagerService.java
index 253fe35..ac19d8b 100644
--- a/services/core/java/com/android/server/SensitiveContentProtectionManagerService.java
+++ b/services/core/java/com/android/server/SensitiveContentProtectionManagerService.java
@@ -161,20 +161,20 @@
         }
 
         if (DEBUG) Log.d(TAG, "onBootPhase - PHASE_BOOT_COMPLETED");
-
-        mPackageManagerInternal = LocalServices.getService(PackageManagerInternal.class);
         init(getContext().getSystemService(MediaProjectionManager.class),
                 LocalServices.getService(WindowManagerInternal.class),
-                getExemptedPackages());
+                LocalServices.getService(PackageManagerInternal.class),
+                getExemptedPackages()
+        );
         if (sensitiveContentAppProtection()) {
             publishBinderService(Context.SENSITIVE_CONTENT_PROTECTION_SERVICE,
-                    new SensitiveContentProtectionManagerServiceBinder(mPackageManagerInternal));
+                    new SensitiveContentProtectionManagerServiceBinder());
         }
     }
 
     @VisibleForTesting
     void init(MediaProjectionManager projectionManager, WindowManagerInternal windowManager,
-            ArraySet<String> exemptedPackages) {
+            PackageManagerInternal packageManagerInternal, ArraySet<String> exemptedPackages) {
         if (DEBUG) Log.d(TAG, "init");
 
         Objects.requireNonNull(projectionManager);
@@ -182,6 +182,7 @@
 
         mProjectionManager = projectionManager;
         mWindowManager = windowManager;
+        mPackageManagerInternal = packageManagerInternal;
         mExemptedPackages = exemptedPackages;
 
         // TODO(b/317250444): use MediaProjectionManagerService directly, reduces unnecessary
@@ -231,14 +232,16 @@
     }
 
     private void onProjectionStart(MediaProjectionInfo projectionInfo) {
-        int uid = mPackageManagerInternal.getPackageUid(projectionInfo.getPackageName(), 0,
-                projectionInfo.getUserHandle().getIdentifier());
         boolean isPackageExempted = (mExemptedPackages != null && mExemptedPackages.contains(
                 projectionInfo.getPackageName()))
-                || canRecordSensitiveContent(projectionInfo.getPackageName());
+                || canRecordSensitiveContent(projectionInfo.getPackageName())
+                || isAutofillServiceRecorderPackage(projectionInfo.getUserHandle().getIdentifier(),
+                        projectionInfo.getPackageName());
         // TODO(b/324447419): move GlobalSettings lookup to background thread
         boolean isFeatureDisabled = Settings.Global.getInt(getContext().getContentResolver(),
                 DISABLE_SCREEN_SHARE_PROTECTIONS_FOR_APPS_AND_NOTIFICATIONS, 0) != 0;
+        int uid = mPackageManagerInternal.getPackageUid(projectionInfo.getPackageName(), 0,
+                projectionInfo.getUserHandle().getIdentifier());
         mMediaProjectionSession = new MediaProjectionSession(
                 uid, isPackageExempted || isFeatureDisabled, new Random().nextLong());
 
@@ -295,8 +298,9 @@
         // notify windowmanager of any currently posted sensitive content notifications
         ArraySet<PackageInfo> packageInfos =
                 getSensitivePackagesFromNotifications(notifications, rankingMap);
-
-        mWindowManager.addBlockScreenCaptureForApps(packageInfos);
+        if (packageInfos.size() > 0) {
+            mWindowManager.addBlockScreenCaptureForApps(packageInfos);
+        }
     }
 
     private ArraySet<PackageInfo> getSensitivePackagesFromNotifications(
@@ -422,6 +426,7 @@
             if (!mProjectionActive) {
                 return;
             }
+
             if (DEBUG) {
                 Log.d(TAG, "setSensitiveContentProtection - current package=" + packageInfo
                         + ", isShowingSensitiveContent=" + isShowingSensitiveContent
@@ -452,15 +457,29 @@
         }
     }
 
-    private final class SensitiveContentProtectionManagerServiceBinder
-            extends ISensitiveContentProtectionManager.Stub {
-        private final PackageManagerInternal mPackageManagerInternal;
-
-        SensitiveContentProtectionManagerServiceBinder(
-                PackageManagerInternal packageManagerInternal) {
-            mPackageManagerInternal = packageManagerInternal;
+    // TODO: b/328251279 - Autofill service exemption is temporary and will be removed in future.
+    private boolean isAutofillServiceRecorderPackage(int userId, String projectionPackage) {
+        String autofillServiceName = Settings.Secure.getStringForUser(
+                getContext().getContentResolver(), Settings.Secure.AUTOFILL_SERVICE, userId);
+        if (DEBUG) {
+            Log.d(TAG, "autofill service for user " + userId + " is " + autofillServiceName);
         }
 
+        if (autofillServiceName == null) {
+            return false;
+        }
+        ComponentName serviceComponent = ComponentName.unflattenFromString(autofillServiceName);
+        if (serviceComponent == null) {
+            return false;
+        }
+        String autofillServicePackage = serviceComponent.getPackageName();
+
+        return autofillServicePackage != null
+                && autofillServicePackage.equals(projectionPackage);
+    }
+
+    private final class SensitiveContentProtectionManagerServiceBinder
+            extends ISensitiveContentProtectionManager.Stub {
         public void setSensitiveContentProtection(IBinder windowToken, String packageName,
                 boolean isShowingSensitiveContent) {
             Trace.traceBegin(Trace.TRACE_TAG_SYSTEM_SERVER,
diff --git a/services/core/java/com/android/server/am/ActivityManagerService.java b/services/core/java/com/android/server/am/ActivityManagerService.java
index ed1a763..47f03f3 100644
--- a/services/core/java/com/android/server/am/ActivityManagerService.java
+++ b/services/core/java/com/android/server/am/ActivityManagerService.java
@@ -414,6 +414,7 @@
 import com.android.internal.os.BinderCallHeavyHitterWatcher.BinderCallHeavyHitterListener;
 import com.android.internal.os.BinderCallHeavyHitterWatcher.HeavyHitterContainer;
 import com.android.internal.os.BinderInternal;
+import com.android.internal.os.BinderInternal.BinderProxyCountEventListener;
 import com.android.internal.os.BinderTransactionNameResolver;
 import com.android.internal.os.ByteTransferPipe;
 import com.android.internal.os.IResultReceiver;
@@ -614,8 +615,8 @@
     private static final int MINIMUM_MEMORY_GROWTH_THRESHOLD = 10 * 1000; // 10 MB
 
     /**
-     * The number of binder proxies we need to have before we start warning and
-     * dumping debug info.
+     * The number of binder proxies we need to have before we start dumping debug info
+     * and kill the offenders.
      */
     private static final int BINDER_PROXY_HIGH_WATERMARK = 6000;
 
@@ -625,6 +626,11 @@
      */
     private static final int BINDER_PROXY_LOW_WATERMARK = 5500;
 
+    /**
+     * The number of binder proxies we need to have before we start warning.
+     */
+    private static final int BINDER_PROXY_WARNING_WATERMARK = 5750;
+
     // Max character limit for a notification title. If the notification title is larger than this
     // the notification will not be legible to the user.
     private static final int MAX_BUGREPORT_TITLE_SIZE = 100;
@@ -1680,6 +1686,11 @@
     PermissionManagerServiceInternal mPermissionManagerInt;
     private TestUtilityService mTestUtilityService;
 
+    // Packages which have received a (LOCKED_)BOOT_COMPLETED broadcast since
+    // the private space profile has been started
+    @GuardedBy("this")
+    private final ArraySet<String> mPrivateSpaceBootCompletedPackages = new ArraySet<String>();
+
     /**
      * Whether to force background check on all apps (for battery saver) or not.
      */
@@ -2307,6 +2318,19 @@
         @Override
         public void onUserStopped(@NonNull TargetUser user) {
             mService.mBatteryStatsService.onCleanupUser(user.getUserIdentifier());
+
+            if (android.os.Flags.allowPrivateProfile()
+                    && android.multiuser.Flags.enablePrivateSpaceFeatures()) {
+                final UserManagerInternal umInternal =
+                        LocalServices.getService(UserManagerInternal.class);
+                UserInfo userInfo = umInternal.getUserInfo(user.getUserIdentifier());
+
+                if (userInfo != null && userInfo.isPrivateProfile()) {
+                    synchronized (mService) {
+                        mService.mPrivateSpaceBootCompletedPackages.clear();
+                    }
+                }
+            }
         }
 
         public ActivityManagerService getService() {
@@ -5042,13 +5066,32 @@
     }
 
     /**
-     * Send LOCKED_BOOT_COMPLETED and BOOT_COMPLETED to the package explicitly when unstopped
+     * Send LOCKED_BOOT_COMPLETED and BOOT_COMPLETED to the package explicitly when unstopped,
+     * or when the package first starts in private space
      */
     private void maybeSendBootCompletedLocked(ProcessRecord app) {
-        if (!android.content.pm.Flags.stayStopped()) return;
-        // Nothing to do if it wasn't previously stopped
-        if (!app.wasForceStopped() && !app.getWindowProcessController().wasForceStopped()) {
-            return;
+        boolean sendBroadcast = false;
+        if (android.os.Flags.allowPrivateProfile()
+                && android.multiuser.Flags.enablePrivateSpaceFeatures()) {
+            final UserManagerInternal umInternal =
+                    LocalServices.getService(UserManagerInternal.class);
+            UserInfo userInfo = umInternal.getUserInfo(app.userId);
+
+            if (userInfo != null && userInfo.isPrivateProfile()) {
+                // Packages in private space get deferred boot completed whenever they start the
+                // first time since profile start
+                if (!mPrivateSpaceBootCompletedPackages.contains(app.info.packageName)) {
+                    mPrivateSpaceBootCompletedPackages.add(app.info.packageName);
+                    sendBroadcast = true;
+                } // else, stopped packages in private space may still hit the logic below
+            }
+        }
+        if (!sendBroadcast) {
+            if (!android.content.pm.Flags.stayStopped()) return;
+            // Nothing to do if it wasn't previously stopped
+            if (!app.wasForceStopped() && !app.getWindowProcessController().wasForceStopped()) {
+                return;
+            }
         }
 
         // Send LOCKED_BOOT_COMPLETED, if necessary
@@ -7978,6 +8021,18 @@
         return uidRecord != null && !uidRecord.isSetIdle();
     }
 
+    @Override
+    public long getUidLastIdleElapsedTime(int uid, String callingPackage) {
+        if (!hasUsageStatsPermission(callingPackage)) {
+            enforceCallingPermission(android.Manifest.permission.PACKAGE_USAGE_STATS,
+                    "getUidLastIdleElapsedTime");
+        }
+        synchronized (mProcLock) {
+            final UidRecord uidRecord = mProcessList.getUidRecordLOSP(uid);
+            return uidRecord != null ? uidRecord.getRealLastIdleTime() : 0;
+        }
+    }
+
     @GuardedBy("mUidFrozenStateChangedCallbackList")
     private final RemoteCallbackList<IUidFrozenStateChangedCallback>
             mUidFrozenStateChangedCallbackList = new RemoteCallbackList<>();
@@ -9014,34 +9069,10 @@
 
             t.traceBegin("setBinderProxies");
             BinderInternal.nSetBinderProxyCountWatermarks(BINDER_PROXY_HIGH_WATERMARK,
-                    BINDER_PROXY_LOW_WATERMARK);
+                    BINDER_PROXY_LOW_WATERMARK, BINDER_PROXY_WARNING_WATERMARK);
             BinderInternal.nSetBinderProxyCountEnabled(true);
-            BinderInternal.setBinderProxyCountCallback(
-                    (uid) -> {
-                        Slog.wtf(TAG, "Uid " + uid + " sent too many Binders to uid "
-                                + Process.myUid());
-                        BinderProxy.dumpProxyDebugInfo();
-                        CriticalEventLog.getInstance().logExcessiveBinderCalls(uid);
-                        if (uid == Process.SYSTEM_UID) {
-                            Slog.i(TAG, "Skipping kill (uid is SYSTEM)");
-                        } else {
-                            killUid(UserHandle.getAppId(uid), UserHandle.getUserId(uid),
-                                    ApplicationExitInfo.REASON_EXCESSIVE_RESOURCE_USAGE,
-                                    ApplicationExitInfo.SUBREASON_EXCESSIVE_BINDER_OBJECTS,
-                                    "Too many Binders sent to SYSTEM");
-                            // We need to run a GC here, because killing the processes involved
-                            // actually isn't guaranteed to free up the proxies; in fact, if the
-                            // GC doesn't run for a long time, we may even exceed the global
-                            // proxy limit for a process (20000), resulting in system_server itself
-                            // being killed.
-                            // Note that the GC here might not actually clean up all the proxies,
-                            // because the binder reference decrements will come in asynchronously;
-                            // but if new processes belonging to the UID keep adding proxies, we
-                            // will get another callback here, and run the GC again - this time
-                            // cleaning up the old proxies.
-                            VMRuntime.getRuntime().requestConcurrentGC();
-                        }
-                    }, mHandler);
+            BinderInternal.setBinderProxyCountCallback(new MyBinderProxyCountEventListener(),
+                    mHandler);
             t.traceEnd(); // setBinderProxies
 
             t.traceEnd(); // ActivityManagerStartApps
@@ -9056,6 +9087,46 @@
         }
     }
 
+    private class MyBinderProxyCountEventListener implements BinderProxyCountEventListener {
+        @Override
+        public void onLimitReached(int uid) {
+            Slog.wtf(TAG, "Uid " + uid + " sent too many Binders to uid "
+                    + Process.myUid());
+            BinderProxy.dumpProxyDebugInfo();
+            CriticalEventLog.getInstance().logExcessiveBinderCalls(uid);
+            if (uid == Process.SYSTEM_UID) {
+                Slog.i(TAG, "Skipping kill (uid is SYSTEM)");
+            } else {
+                killUid(UserHandle.getAppId(uid), UserHandle.getUserId(uid),
+                        ApplicationExitInfo.REASON_EXCESSIVE_RESOURCE_USAGE,
+                        ApplicationExitInfo.SUBREASON_EXCESSIVE_BINDER_OBJECTS,
+                        "Too many Binders sent to SYSTEM");
+                // We need to run a GC here, because killing the processes involved
+                // actually isn't guaranteed to free up the proxies; in fact, if the
+                // GC doesn't run for a long time, we may even exceed the global
+                // proxy limit for a process (20000), resulting in system_server itself
+                // being killed.
+                // Note that the GC here might not actually clean up all the proxies,
+                // because the binder reference decrements will come in asynchronously;
+                // but if new processes belonging to the UID keep adding proxies, we
+                // will get another callback here, and run the GC again - this time
+                // cleaning up the old proxies.
+                VMRuntime.getRuntime().requestConcurrentGC();
+            }
+        }
+
+        @Override
+        public void onWarningThresholdReached(int uid) {
+            if (Flags.logExcessiveBinderProxies()) {
+                Slog.w(TAG, "Uid " + uid + " sent too many ("
+                        + BINDER_PROXY_WARNING_WATERMARK + ") Binders to uid " + Process.myUid());
+                FrameworkStatsLog.write(
+                        FrameworkStatsLog.EXCESSIVE_BINDER_PROXY_COUNT_REPORTED,
+                        uid);
+            }
+        }
+    }
+
     private void watchDeviceProvisioning(Context context) {
         // setting system property based on whether device is provisioned
 
@@ -18189,6 +18260,11 @@
         }
 
         @Override
+        public boolean startUserInBackground(final int userId) {
+            return ActivityManagerService.this.startUserInBackground(userId);
+        }
+
+        @Override
         public void killForegroundAppsForUser(@UserIdInt int userId) {
             final ArrayList<ProcessRecord> procs = new ArrayList<>();
             synchronized (mProcLock) {
diff --git a/services/core/java/com/android/server/am/OomAdjuster.java b/services/core/java/com/android/server/am/OomAdjuster.java
index df6481d..5a750c2 100644
--- a/services/core/java/com/android/server/am/OomAdjuster.java
+++ b/services/core/java/com/android/server/am/OomAdjuster.java
@@ -71,7 +71,7 @@
 import static android.content.pm.ServiceInfo.FOREGROUND_SERVICE_TYPE_MEDIA_PLAYBACK;
 import static android.content.pm.ServiceInfo.FOREGROUND_SERVICE_TYPE_MICROPHONE;
 import static android.content.pm.ServiceInfo.FOREGROUND_SERVICE_TYPE_PHONE_CALL;
-import static android.media.audio.Flags.foregroundAudioControl;
+import static android.media.audio.Flags.roForegroundAudioControl;
 import static android.os.Process.SCHED_OTHER;
 import static android.os.Process.THREAD_GROUP_BACKGROUND;
 import static android.os.Process.THREAD_GROUP_DEFAULT;
@@ -2212,7 +2212,7 @@
                             (fgsType & FOREGROUND_SERVICE_TYPE_LOCATION)
                                     != 0 ? PROCESS_CAPABILITY_FOREGROUND_LOCATION : 0;
 
-                    if (foregroundAudioControl()) { // flag check
+                    if (roForegroundAudioControl()) { // flag check
                         final int fgsAudioType = FOREGROUND_SERVICE_TYPE_MEDIA_PLAYBACK
                                 | FOREGROUND_SERVICE_TYPE_CAMERA
                                 | FOREGROUND_SERVICE_TYPE_MICROPHONE
@@ -3681,7 +3681,7 @@
         for (int i = N - 1; i >= 0; i--) {
             final UidRecord uidRec = mActiveUids.valueAt(i);
             final long bgTime = uidRec.getLastBackgroundTime();
-            final long idleTime = uidRec.getLastIdleTime();
+            final long idleTime = uidRec.getLastIdleTimeIfStillIdle();
             if (bgTime > 0 && (!uidRec.isIdle() || idleTime == 0)) {
                 if (bgTime <= maxBgTime) {
                     EventLogTags.writeAmUidIdle(uidRec.getUid());
diff --git a/services/core/java/com/android/server/am/UidRecord.java b/services/core/java/com/android/server/am/UidRecord.java
index 45fd470..86fa0fc 100644
--- a/services/core/java/com/android/server/am/UidRecord.java
+++ b/services/core/java/com/android/server/am/UidRecord.java
@@ -17,6 +17,7 @@
 package com.android.server.am;
 
 import android.Manifest;
+import android.annotation.ElapsedRealtimeLong;
 import android.app.ActivityManager;
 import android.content.pm.PackageManager;
 import android.os.SystemClock;
@@ -65,8 +66,19 @@
     @CompositeRWLock({"mService", "mProcLock"})
     private long mLastBackgroundTime;
 
+    /**
+     * Last time the UID became idle. This is set to 0, once the UID becomes active.
+     */
+    @ElapsedRealtimeLong
     @CompositeRWLock({"mService", "mProcLock"})
-    private long mLastIdleTime;
+    private long mLastIdleTimeIfStillIdle;
+
+    /**
+     * Last time the UID became idle. Unlike {@link #mLastIdleTimeIfStillIdle}, we never clear it.
+     */
+    @ElapsedRealtimeLong
+    @CompositeRWLock({"mService", "mProcLock"})
+    private long mRealLastIdleTime;
 
     @CompositeRWLock({"mService", "mProcLock"})
     private boolean mEphemeral;
@@ -257,14 +269,28 @@
         mLastBackgroundTime = lastBackgroundTime;
     }
 
+    /**
+     * Last time the UID became idle. This is set to 0, once the UID becomes active.
+     */
     @GuardedBy(anyOf = {"mService", "mProcLock"})
-    long getLastIdleTime() {
-        return mLastIdleTime;
+    long getLastIdleTimeIfStillIdle() {
+        return mLastIdleTimeIfStillIdle;
+    }
+
+    /**
+     * Last time the UID became idle. Unlike {@link #getLastIdleTimeIfStillIdle}, we never clear it.
+     */
+    @GuardedBy(anyOf = {"mService", "mProcLock"})
+    long getRealLastIdleTime() {
+        return mRealLastIdleTime;
     }
 
     @GuardedBy({"mService", "mProcLock"})
-    void setLastIdleTime(long lastActiveTime) {
-        mLastIdleTime = lastActiveTime;
+    void setLastIdleTime(@ElapsedRealtimeLong long lastIdleTime) {
+        mLastIdleTimeIfStillIdle = lastIdleTime;
+        if (lastIdleTime > 0) {
+            mRealLastIdleTime = lastIdleTime;
+        }
     }
 
     @GuardedBy(anyOf = {"mService", "mProcLock"})
diff --git a/services/core/java/com/android/server/am/UserController.java b/services/core/java/com/android/server/am/UserController.java
index c2613bc..60a8b50 100644
--- a/services/core/java/com/android/server/am/UserController.java
+++ b/services/core/java/com/android/server/am/UserController.java
@@ -671,6 +671,14 @@
     }
 
     private void sendLockedBootCompletedBroadcast(IIntentReceiver receiver, @UserIdInt int userId) {
+        if (android.os.Flags.allowPrivateProfile()
+                && android.multiuser.Flags.enablePrivateSpaceFeatures()) {
+            final UserInfo userInfo = getUserInfo(userId);
+            if (userInfo != null && userInfo.isPrivateProfile()) {
+                Slogf.i(TAG, "Skipping LOCKED_BOOT_COMPLETED for private profile user #" + userId);
+                return;
+            }
+        }
         final Intent intent = new Intent(Intent.ACTION_LOCKED_BOOT_COMPLETED, null);
         intent.putExtra(Intent.EXTRA_USER_HANDLE, userId);
         intent.addFlags(Intent.FLAG_RECEIVER_NO_ABORT
@@ -877,6 +885,13 @@
 
         mHandler.obtainMessage(USER_UNLOCKED_MSG, userId, 0).sendToTarget();
 
+        if (android.os.Flags.allowPrivateProfile()
+                && android.multiuser.Flags.enablePrivateSpaceFeatures()) {
+            if (userInfo.isPrivateProfile()) {
+                Slogf.i(TAG, "Skipping BOOT_COMPLETED for private profile user #" + userId);
+                return;
+            }
+        }
         Slogf.i(TAG, "Posting BOOT_COMPLETED user #" + userId);
         // Do not report secondary users, runtime restarts or first boot/upgrade
         if (userId == UserHandle.USER_SYSTEM
diff --git a/services/core/java/com/android/server/audio/AudioService.java b/services/core/java/com/android/server/audio/AudioService.java
index be47f85..ed58c40 100644
--- a/services/core/java/com/android/server/audio/AudioService.java
+++ b/services/core/java/com/android/server/audio/AudioService.java
@@ -34,7 +34,7 @@
 import static android.media.audio.Flags.automaticBtDeviceType;
 import static android.media.audio.Flags.featureSpatialAudioHeadtrackingLowLatency;
 import static android.media.audio.Flags.focusFreezeTestApi;
-import static android.media.audio.Flags.foregroundAudioControl;
+import static android.media.audio.Flags.roForegroundAudioControl;
 import static android.media.audiopolicy.Flags.enableFadeManagerConfiguration;
 import static android.os.Process.FIRST_APPLICATION_UID;
 import static android.os.Process.INVALID_UID;
@@ -4539,10 +4539,11 @@
                 + focusFreezeTestApi());
         pw.println("\tcom.android.media.audio.disablePrescaleAbsoluteVolume:"
                 + disablePrescaleAbsoluteVolume());
+
         pw.println("\tcom.android.media.audio.setStreamVolumeOrder:"
                 + setStreamVolumeOrder());
-        pw.println("\tandroid.media.audio.foregroundAudioControl:"
-                + foregroundAudioControl());
+        pw.println("\tandroid.media.audio.roForegroundAudioControl:"
+                + roForegroundAudioControl());
     }
 
     private void dumpAudioMode(PrintWriter pw) {
diff --git a/services/core/java/com/android/server/biometrics/AuthService.java b/services/core/java/com/android/server/biometrics/AuthService.java
index 14f3120..7df63b1 100644
--- a/services/core/java/com/android/server/biometrics/AuthService.java
+++ b/services/core/java/com/android/server/biometrics/AuthService.java
@@ -869,9 +869,7 @@
             }
 
             if (faceAidlInstances != null && faceAidlInstances.length > 0) {
-                mFaceSensorConfigurations.addAidlConfigs(faceAidlInstances,
-                        name -> IFace.Stub.asInterface(Binder.allowBlocking(
-                                ServiceManager.waitForDeclaredService(name))));
+                mFaceSensorConfigurations.addAidlConfigs(faceAidlInstances);
             }
 
             if (faceService != null) {
@@ -909,9 +907,7 @@
             }
 
             if (fingerprintAidlInstances != null && fingerprintAidlInstances.length > 0) {
-                mFingerprintSensorConfigurations.addAidlSensors(fingerprintAidlInstances,
-                        name -> IFingerprint.Stub.asInterface(Binder.allowBlocking(
-                                ServiceManager.waitForDeclaredService(name))));
+                mFingerprintSensorConfigurations.addAidlSensors(fingerprintAidlInstances);
             }
 
             if (fingerprintService != null) {
diff --git a/services/core/java/com/android/server/biometrics/sensors/face/FaceService.java b/services/core/java/com/android/server/biometrics/sensors/face/FaceService.java
index 7ee2a7a..1037124 100644
--- a/services/core/java/com/android/server/biometrics/sensors/face/FaceService.java
+++ b/services/core/java/com/android/server/biometrics/sensors/face/FaceService.java
@@ -729,8 +729,8 @@
         private List<ServiceProvider> getProviders(
                 FaceSensorConfigurations faceSensorConfigurations) {
             final List<ServiceProvider> providers = new ArrayList<>();
-            final Pair<String, SensorProps[]> filteredSensorProps =
-                    filterAvailableHalInstances(faceSensorConfigurations);
+            final Pair<String, SensorProps[]> filteredSensorProps = filterAvailableHalInstances(
+                            faceSensorConfigurations);
             providers.add(mFaceProviderFunction.getFaceProvider(filteredSensorProps,
                     faceSensorConfigurations.getResetLockoutRequiresChallenge()));
             return providers;
@@ -739,28 +739,36 @@
         @NonNull
         private Pair<String, SensorProps[]> filterAvailableHalInstances(
                 FaceSensorConfigurations faceSensorConfigurations) {
-            Pair<String, SensorProps[]> finalSensorPair = faceSensorConfigurations.getSensorPair();
+            String finalSensorInstance = faceSensorConfigurations.getSensorInstance();
 
             if (faceSensorConfigurations.isSingleSensorConfigurationPresent()) {
-                return finalSensorPair;
+                return new Pair<>(finalSensorInstance,
+                        faceSensorConfigurations.getSensorPropForInstance(finalSensorInstance));
             }
-
-            final Pair<String, SensorProps[]> virtualSensorProps = faceSensorConfigurations
-                    .getSensorPairForInstance("virtual");
-
-            if (Utils.isVirtualEnabled(getContext())) {
-                if (virtualSensorProps != null) {
-                    return virtualSensorProps;
+            final String virtualInstance = "virtual";
+            final boolean isVirtualHalPresent =
+                    faceSensorConfigurations.doesInstanceExist(virtualInstance);
+            if (Flags.faceVhalFeature() && Utils.isVirtualEnabled(getContext())) {
+                if (isVirtualHalPresent) {
+                    return new Pair<>(virtualInstance,
+                            faceSensorConfigurations.getSensorPropForInstance(virtualInstance));
                 } else {
                     Slog.e(TAG, "Could not find virtual interface while it is enabled");
-                    return finalSensorPair;
+                    return new Pair<>(finalSensorInstance,
+                            faceSensorConfigurations.getSensorPropForInstance(finalSensorInstance));
                 }
             } else {
-                if (virtualSensorProps != null) {
-                    return faceSensorConfigurations.getSensorPairNotForInstance("virtual");
+                if (isVirtualHalPresent) {
+                    final String notAVirtualInstance =
+                            faceSensorConfigurations.getSensorNameNotForInstance(virtualInstance);
+                    if (notAVirtualInstance != null) {
+                        return new Pair<>(notAVirtualInstance, faceSensorConfigurations
+                                .getSensorPropForInstance(notAVirtualInstance));
+                    }
                 }
             }
-            return finalSensorPair;
+            return new Pair<>(finalSensorInstance, faceSensorConfigurations
+                    .getSensorPropForInstance(finalSensorInstance));
         }
 
         private Pair<List<FaceSensorPropertiesInternal>, List<String>>
diff --git a/services/core/java/com/android/server/biometrics/sensors/fingerprint/FingerprintService.java b/services/core/java/com/android/server/biometrics/sensors/fingerprint/FingerprintService.java
index 1ba1213..2dc03ed 100644
--- a/services/core/java/com/android/server/biometrics/sensors/fingerprint/FingerprintService.java
+++ b/services/core/java/com/android/server/biometrics/sensors/fingerprint/FingerprintService.java
@@ -1128,27 +1128,36 @@
     @NonNull
     private Pair<String, SensorProps[]> filterAvailableHalInstances(
             FingerprintSensorConfigurations fingerprintSensorConfigurations) {
-        Pair<String, SensorProps[]> finalSensorPair =
-                fingerprintSensorConfigurations.getSensorPair();
+        final String finalSensorInstance = fingerprintSensorConfigurations.getSensorInstance();
         if (fingerprintSensorConfigurations.isSingleSensorConfigurationPresent()) {
-            return finalSensorPair;
+            return new Pair<>(finalSensorInstance,
+                    fingerprintSensorConfigurations.getSensorPropForInstance(finalSensorInstance));
         }
-
-        final Pair<String, SensorProps[]> virtualSensorPropsPair = fingerprintSensorConfigurations
-                .getSensorPairForInstance("virtual");
+        final String virtualInstance = "virtual";
+        final boolean isVirtualHalPresent =
+                fingerprintSensorConfigurations.doesInstanceExist(virtualInstance);
         if (Utils.isVirtualEnabled(getContext())) {
-            if (virtualSensorPropsPair != null) {
-                return virtualSensorPropsPair;
+            if (isVirtualHalPresent) {
+                return new Pair<>(virtualInstance,
+                        fingerprintSensorConfigurations.getSensorPropForInstance(virtualInstance));
             } else {
                 Slog.e(TAG, "Could not find virtual interface while it is enabled");
-                return finalSensorPair;
+                return new Pair<>(finalSensorInstance,
+                        fingerprintSensorConfigurations.getSensorPropForInstance(
+                                finalSensorInstance));
             }
         } else {
-            if (virtualSensorPropsPair != null) {
-                return fingerprintSensorConfigurations.getSensorPairNotForInstance("virtual");
+            if (isVirtualHalPresent) {
+                final String notAVirtualInstance = fingerprintSensorConfigurations
+                        .getSensorNameNotForInstance(virtualInstance);
+                if (notAVirtualInstance != null) {
+                    return new Pair<>(notAVirtualInstance, fingerprintSensorConfigurations
+                            .getSensorPropForInstance(notAVirtualInstance));
+                }
             }
         }
-        return finalSensorPair;
+        return new Pair<>(finalSensorInstance, fingerprintSensorConfigurations
+                .getSensorPropForInstance(finalSensorInstance));
     }
 
     private Pair<List<FingerprintSensorPropertiesInternal>, List<String>>
diff --git a/services/core/java/com/android/server/display/DisplayManagerService.java b/services/core/java/com/android/server/display/DisplayManagerService.java
index ba21a32..434985e 100644
--- a/services/core/java/com/android/server/display/DisplayManagerService.java
+++ b/services/core/java/com/android/server/display/DisplayManagerService.java
@@ -464,10 +464,11 @@
     // May be used outside of the lock but only on the handler thread.
     private final ArrayList<CallbackRecord> mTempCallbacks = new ArrayList<CallbackRecord>();
 
-    // Pending callback records indexed by calling process uid.
+    // Pending callback records indexed by calling process uid and pid.
     // Must be used outside of the lock mSyncRoot and should be selflocked.
     @GuardedBy("mPendingCallbackSelfLocked")
-    public final SparseArray<PendingCallback> mPendingCallbackSelfLocked = new SparseArray<>();
+    public final SparseArray<SparseArray<PendingCallback>> mPendingCallbackSelfLocked =
+            new SparseArray<>();
 
     // Temporary viewports, used when sending new viewport information to the
     // input system.  May be used outside of the lock but only on the handler thread.
@@ -1011,8 +1012,8 @@
                 }
 
                 // Do we care about this uid?
-                PendingCallback pendingCallback = mPendingCallbackSelfLocked.get(uid);
-                if (pendingCallback == null) {
+                SparseArray<PendingCallback> pendingCallbacks = mPendingCallbackSelfLocked.get(uid);
+                if (pendingCallbacks == null) {
                     return;
                 }
 
@@ -1020,7 +1021,12 @@
                 if (DEBUG) {
                     Slog.d(TAG, "Uid " + uid + " becomes " + importance);
                 }
-                pendingCallback.sendPendingDisplayEvent();
+                for (int i = 0; i < pendingCallbacks.size(); i++) {
+                    PendingCallback pendingCallback = pendingCallbacks.valueAt(i);
+                    if (pendingCallback != null) {
+                        pendingCallback.sendPendingDisplayEvent();
+                    }
+                }
                 mPendingCallbackSelfLocked.delete(uid);
             }
         }
@@ -3193,16 +3199,23 @@
         for (int i = 0; i < mTempCallbacks.size(); i++) {
             CallbackRecord callbackRecord = mTempCallbacks.get(i);
             final int uid = callbackRecord.mUid;
+            final int pid = callbackRecord.mPid;
             if (isUidCached(uid)) {
                 // For cached apps, save the pending event until it becomes non-cached
                 synchronized (mPendingCallbackSelfLocked) {
-                    PendingCallback pendingCallback = mPendingCallbackSelfLocked.get(uid);
+                    SparseArray<PendingCallback> pendingCallbacks = mPendingCallbackSelfLocked.get(
+                            uid);
                     if (extraLogging(callbackRecord.mPackageName)) {
-                        Slog.i(TAG,
-                                "Uid is cached: " + uid + ", pendingCallback: " + pendingCallback);
+                        Slog.i(TAG, "Uid is cached: " + uid
+                                + ", pendingCallbacks: " + pendingCallbacks);
                     }
+                    if (pendingCallbacks == null) {
+                        pendingCallbacks = new SparseArray<>();
+                        mPendingCallbackSelfLocked.put(uid, pendingCallbacks);
+                    }
+                    PendingCallback pendingCallback = pendingCallbacks.get(pid);
                     if (pendingCallback == null) {
-                        mPendingCallbackSelfLocked.put(uid,
+                        pendingCallbacks.put(pid,
                                 new PendingCallback(callbackRecord, displayId, event));
                     } else {
                         pendingCallback.addDisplayEvent(displayId, event);
diff --git a/services/core/java/com/android/server/inputmethod/ImeTrackerService.java b/services/core/java/com/android/server/inputmethod/ImeTrackerService.java
index 85ab773..1c14fc1 100644
--- a/services/core/java/com/android/server/inputmethod/ImeTrackerService.java
+++ b/services/core/java/com/android/server/inputmethod/ImeTrackerService.java
@@ -165,6 +165,15 @@
         }
     }
 
+    @EnforcePermission(Manifest.permission.TEST_INPUT_METHOD)
+    @Override
+    public void finishTrackingPendingImeVisibilityRequests() {
+        super.finishTrackingPendingImeVisibilityRequests_enforcePermission();
+        synchronized (mLock) {
+            mHistory.mLiveEntries.clear();
+        }
+    }
+
     /**
      * A circular buffer storing the most recent few {@link ImeTracker.Token} entries information.
      */
diff --git a/services/core/java/com/android/server/pm/PackageManagerService.java b/services/core/java/com/android/server/pm/PackageManagerService.java
index fd3da85..4da280b 100644
--- a/services/core/java/com/android/server/pm/PackageManagerService.java
+++ b/services/core/java/com/android/server/pm/PackageManagerService.java
@@ -2446,7 +2446,7 @@
             ComponentName intentFilterVerifierComponent =
                     getIntentFilterVerifierComponentNameLPr(computer);
             ComponentName domainVerificationAgent =
-                    getDomainVerificationAgentComponentNameLPr(computer);
+                    getDomainVerificationAgentComponentNameLPr(computer, UserHandle.USER_SYSTEM);
 
             DomainVerificationProxy domainVerificationProxy = DomainVerificationProxy.makeProxy(
                     intentFilterVerifierComponent, domainVerificationAgent, mContext,
@@ -2754,12 +2754,13 @@
     }
 
     @Nullable
-    private ComponentName getDomainVerificationAgentComponentNameLPr(@NonNull Computer computer) {
+    private ComponentName getDomainVerificationAgentComponentNameLPr(@NonNull Computer computer,
+            int userId) {
         Intent intent = new Intent(Intent.ACTION_DOMAINS_NEED_VERIFICATION);
         List<ResolveInfo> matches =
                 mResolveIntentHelper.queryIntentReceiversInternal(computer, intent, null,
                         MATCH_SYSTEM_ONLY | MATCH_DIRECT_BOOT_AWARE | MATCH_DIRECT_BOOT_UNAWARE,
-                        UserHandle.USER_SYSTEM, Binder.getCallingUid());
+                        userId, Binder.getCallingUid());
         ResolveInfo best = null;
         final int N = matches.size();
         for (int i = 0; i < N; i++) {
@@ -2767,7 +2768,7 @@
             final String packageName = cur.getComponentInfo().packageName;
             if (checkPermission(
                     android.Manifest.permission.DOMAIN_VERIFICATION_AGENT, packageName,
-                    UserHandle.USER_SYSTEM) != PackageManager.PERMISSION_GRANTED) {
+                    userId) != PackageManager.PERMISSION_GRANTED) {
                 Slog.w(TAG, "Domain verification agent found but does not hold permission: "
                         + packageName);
                 continue;
@@ -2775,7 +2776,7 @@
 
             if (best == null || cur.priority > best.priority) {
                 if (computer.isComponentEffectivelyEnabled(cur.getComponentInfo(),
-                        UserHandle.SYSTEM)) {
+                        UserHandle.of(userId))) {
                     best = cur;
                 } else {
                     Slog.w(TAG, "Domain verification agent found but not enabled");
@@ -6512,13 +6513,13 @@
 
         @Override
         @Nullable
-        public ComponentName getDomainVerificationAgent() {
+        public ComponentName getDomainVerificationAgent(int userId) {
             final int callerUid = Binder.getCallingUid();
             if (!PackageManagerServiceUtils.isRootOrShell(callerUid)) {
                 throw new SecurityException("Not allowed to query domain verification agent");
             }
             final Computer snapshot = snapshotComputer();
-            return getDomainVerificationAgentComponentNameLPr(snapshot);
+            return getDomainVerificationAgentComponentNameLPr(snapshot, userId);
         }
 
         @Override
diff --git a/services/core/java/com/android/server/pm/PackageManagerShellCommand.java b/services/core/java/com/android/server/pm/PackageManagerShellCommand.java
index a9e1725..59faf24 100644
--- a/services/core/java/com/android/server/pm/PackageManagerShellCommand.java
+++ b/services/core/java/com/android/server/pm/PackageManagerShellCommand.java
@@ -4412,8 +4412,31 @@
 
     private int runGetDomainVerificationAgent() throws RemoteException {
         final PrintWriter pw = getOutPrintWriter();
+        int userId = UserHandle.USER_ALL;
+
+        String opt;
+        while ((opt = getNextOption()) != null) {
+            if (opt.equals("--user")) {
+                userId = UserHandle.parseUserArg(getNextArgRequired());
+                if (userId != UserHandle.USER_ALL && userId != UserHandle.USER_CURRENT) {
+                    UserManagerInternal umi =
+                            LocalServices.getService(UserManagerInternal.class);
+                    UserInfo userInfo = umi.getUserInfo(userId);
+                    if (userInfo == null) {
+                        pw.println("Failure [user " + userId + " doesn't exist]");
+                        return 1;
+                    }
+                }
+            } else {
+                pw.println("Error: Unknown option: " + opt);
+                return 1;
+            }
+        }
+        final int translatedUserId =
+                translateUserId(userId, UserHandle.USER_SYSTEM, "runGetDomainVerificationAgent");
         try {
-            final ComponentName domainVerificationAgent = mInterface.getDomainVerificationAgent();
+            final ComponentName domainVerificationAgent =
+                    mInterface.getDomainVerificationAgent(translatedUserId);
             pw.println(domainVerificationAgent == null
                     ? "No Domain Verifier available!" : domainVerificationAgent.flattenToString());
         } catch (Exception e) {
diff --git a/services/core/java/com/android/server/pm/Settings.java b/services/core/java/com/android/server/pm/Settings.java
index f5ed8d4..9f2c36a 100644
--- a/services/core/java/com/android/server/pm/Settings.java
+++ b/services/core/java/com/android/server/pm/Settings.java
@@ -93,6 +93,7 @@
 import com.android.internal.annotations.GuardedBy;
 import com.android.internal.annotations.VisibleForTesting;
 import com.android.internal.os.BackgroundThread;
+import com.android.internal.pm.parsing.pkg.AndroidPackageInternal;
 import com.android.internal.pm.pkg.component.ParsedComponent;
 import com.android.internal.pm.pkg.component.ParsedIntentInfo;
 import com.android.internal.pm.pkg.component.ParsedPermission;
@@ -911,8 +912,10 @@
             sharedUserSetting.mDisabledPackages.remove(p);
         }
         p.getPkgState().setUpdatedSystemApp(false);
+        final AndroidPackageInternal pkg = p.getPkg();
         PackageSetting ret = addPackageLPw(name, p.getRealName(), p.getPath(), p.getAppId(),
-                p.getFlags(), p.getPrivateFlags(), mDomainVerificationManager.generateNewId());
+                p.getFlags(), p.getPrivateFlags(), mDomainVerificationManager.generateNewId(),
+                pkg == null ? false : pkg.isSdkLibrary());
         if (ret != null) {
             ret.setLegacyNativeLibraryPath(p.getLegacyNativeLibraryPath());
             ret.setPrimaryCpuAbi(p.getPrimaryCpuAbiLegacy());
@@ -951,8 +954,8 @@
         }
     }
 
-    PackageSetting addPackageLPw(String name, String realName, File codePath, int uid, int pkgFlags,
-                                 int pkgPrivateFlags, @NonNull UUID domainSetId) {
+    PackageSetting addPackageLPw(String name, String realName, File codePath, int uid,
+            int pkgFlags, int pkgPrivateFlags, @NonNull UUID domainSetId, boolean isSdkLibrary) {
         PackageSetting p = mPackages.get(name);
         if (p != null) {
             if (p.getAppId() == uid) {
@@ -964,7 +967,8 @@
         }
         p = new PackageSetting(name, realName, codePath, pkgFlags, pkgPrivateFlags, domainSetId)
                 .setAppId(uid);
-        if (mAppIds.registerExistingAppId(uid, p, name)) {
+        if ((uid == Process.INVALID_UID && isSdkLibrary && Flags.disallowSdkLibsToBeApps())
+                || mAppIds.registerExistingAppId(uid, p, name)) {
             mPackages.put(name, p);
             return p;
         }
@@ -4176,7 +4180,7 @@
             } else if (appId > 0 || (appId == Process.INVALID_UID && isSdkLibrary
                     && Flags.disallowSdkLibsToBeApps())) {
                 packageSetting = addPackageLPw(name.intern(), realName, new File(codePathStr),
-                        appId, pkgFlags, pkgPrivateFlags, domainSetId);
+                        appId, pkgFlags, pkgPrivateFlags, domainSetId, isSdkLibrary);
                 if (PackageManagerService.DEBUG_SETTINGS)
                     Log.i(PackageManagerService.TAG, "Reading package " + name + ": appId="
                             + appId + " pkg=" + packageSetting);
diff --git a/services/core/java/com/android/server/pm/UserManagerService.java b/services/core/java/com/android/server/pm/UserManagerService.java
index f5ac830..63386a9 100644
--- a/services/core/java/com/android/server/pm/UserManagerService.java
+++ b/services/core/java/com/android/server/pm/UserManagerService.java
@@ -567,7 +567,7 @@
                     int autoLockPreference =
                             Settings.Secure.getIntForUser(mContext.getContentResolver(),
                                     Settings.Secure.PRIVATE_SPACE_AUTO_LOCK,
-                                    Settings.Secure.PRIVATE_SPACE_AUTO_LOCK_NEVER,
+                                    Settings.Secure.PRIVATE_SPACE_AUTO_LOCK_AFTER_DEVICE_RESTART,
                                     getMainUserIdUnchecked());
                     Slog.i(LOG_TAG, "Auto-lock settings changed to " + autoLockPreference);
                     setOrUpdateAutoLockPreferenceForPrivateProfile(autoLockPreference);
@@ -615,7 +615,7 @@
         int privateSpaceAutoLockPreference =
                 Settings.Secure.getIntForUser(mContext.getContentResolver(),
                         Settings.Secure.PRIVATE_SPACE_AUTO_LOCK,
-                        Settings.Secure.PRIVATE_SPACE_AUTO_LOCK_NEVER,
+                        Settings.Secure.PRIVATE_SPACE_AUTO_LOCK_AFTER_DEVICE_RESTART,
                         getMainUserIdUnchecked());
         if (privateSpaceAutoLockPreference
                 != Settings.Secure.PRIVATE_SPACE_AUTO_LOCK_AFTER_INACTIVITY) {
@@ -714,7 +714,7 @@
         if (isAutoLockForPrivateSpaceEnabled()) {
             int autoLockPreference = Settings.Secure.getIntForUser(mContext.getContentResolver(),
                     Settings.Secure.PRIVATE_SPACE_AUTO_LOCK,
-                    Settings.Secure.PRIVATE_SPACE_AUTO_LOCK_NEVER,
+                    Settings.Secure.PRIVATE_SPACE_AUTO_LOCK_AFTER_DEVICE_RESTART,
                     getMainUserIdUnchecked());
             boolean isAutoLockOnDeviceLockSelected =
                     autoLockPreference == Settings.Secure.PRIVATE_SPACE_AUTO_LOCK_ON_DEVICE_LOCK;
@@ -1052,7 +1052,8 @@
                 setOrUpdateAutoLockPreferenceForPrivateProfile(
                         Settings.Secure.getIntForUser(mContext.getContentResolver(),
                                 Settings.Secure.PRIVATE_SPACE_AUTO_LOCK,
-                                Settings.Secure.PRIVATE_SPACE_AUTO_LOCK_NEVER, mainUserId));
+                                Settings.Secure.PRIVATE_SPACE_AUTO_LOCK_AFTER_DEVICE_RESTART,
+                                mainUserId));
             }
         }
 
diff --git a/services/core/java/com/android/server/stats/pull/AggregatedMobileDataStatsPuller.java b/services/core/java/com/android/server/stats/pull/AggregatedMobileDataStatsPuller.java
index 881583a..f28a91c 100644
--- a/services/core/java/com/android/server/stats/pull/AggregatedMobileDataStatsPuller.java
+++ b/services/core/java/com/android/server/stats/pull/AggregatedMobileDataStatsPuller.java
@@ -125,6 +125,11 @@
     @GuardedBy("mLock")
     private final Map<UidProcState, MobileDataStats> mUidStats;
 
+    // No reason to keep more dimensions than 3000. The 3000 is the hard top for the statsd metrics
+    // dimensions guardrail. It also will keep the result binder transaction size capped to
+    // approximately 220kB for 3000 atoms
+    private static final int UID_STATS_MAX_SIZE = 3000;
+
     private final SparseIntArray mUidPreviousState;
 
     private NetworkStats mLastMobileUidStats = new NetworkStats(0, -1);
@@ -188,14 +193,18 @@
         }
 
         final UidProcState statsKey = new UidProcState(uid, previousState);
-        MobileDataStats stats;
         if (mUidStats.containsKey(statsKey)) {
-            stats = mUidStats.get(statsKey);
-        } else {
-            stats = new MobileDataStats();
-            mUidStats.put(statsKey, stats);
+            return mUidStats.get(statsKey);
         }
-        return stats;
+        if (mUidStats.size() < UID_STATS_MAX_SIZE) {
+            MobileDataStats stats = new MobileDataStats();
+            mUidStats.put(statsKey, stats);
+            return stats;
+        }
+        if (DEBUG) {
+            Slog.w(TAG, "getUidStatsForPreviousStateLocked() UID_STATS_MAX_SIZE reached");
+        }
+        return null;
     }
 
     private void noteUidProcessStateImpl(int uid, int state) {
@@ -252,10 +261,12 @@
                     continue;
                 }
                 MobileDataStats stats = getUidStatsForPreviousStateLocked(entry.getUid());
-                stats.addTxBytes(entry.getTxBytes());
-                stats.addRxBytes(entry.getRxBytes());
-                stats.addTxPackets(entry.getTxPackets());
-                stats.addRxPackets(entry.getRxPackets());
+                if (stats != null) {
+                    stats.addTxBytes(entry.getTxBytes());
+                    stats.addRxBytes(entry.getRxBytes());
+                    stats.addTxPackets(entry.getTxPackets());
+                    stats.addRxPackets(entry.getRxPackets());
+                }
             }
         }
     }
diff --git a/services/core/java/com/android/server/wm/ActivityRecord.java b/services/core/java/com/android/server/wm/ActivityRecord.java
index 257dadf..2ec26ca 100644
--- a/services/core/java/com/android/server/wm/ActivityRecord.java
+++ b/services/core/java/com/android/server/wm/ActivityRecord.java
@@ -6501,9 +6501,7 @@
         }
         newIntents = null;
 
-        if (isActivityTypeHome()) {
-            mTaskSupervisor.updateHomeProcess(task.getBottomMostActivity().app);
-        }
+        mTaskSupervisor.updateHomeProcessIfNeeded(this);
 
         if (nowVisible) {
             mTaskSupervisor.stopWaitingForActivityVisible(this);
diff --git a/services/core/java/com/android/server/wm/ActivityTaskManagerService.java b/services/core/java/com/android/server/wm/ActivityTaskManagerService.java
index 38f0587a..354cab3 100644
--- a/services/core/java/com/android/server/wm/ActivityTaskManagerService.java
+++ b/services/core/java/com/android/server/wm/ActivityTaskManagerService.java
@@ -2160,19 +2160,6 @@
         return rect;
     }
 
-    @Override
-    public ActivityManager.TaskDescription getTaskDescription(int id) {
-        synchronized (mGlobalLock) {
-            enforceTaskPermission("getTaskDescription()");
-            final Task tr = mRootWindowContainer.anyTaskForId(id,
-                    MATCH_ATTACHED_TASK_OR_RECENT_TASKS);
-            if (tr != null) {
-                return tr.getTaskDescription();
-            }
-        }
-        return null;
-    }
-
     /**
      * Sets the locusId for a particular activity.
      *
@@ -3072,8 +3059,33 @@
 
     @Override
     public Bitmap getTaskDescriptionIcon(String filePath, int userId) {
-        userId = handleIncomingUser(Binder.getCallingPid(), Binder.getCallingUid(),
-                userId, "getTaskDescriptionIcon");
+        final int callingUid = Binder.getCallingUid();
+        // Verify that the caller can make the request for the given userId
+        userId = handleIncomingUser(Binder.getCallingPid(), callingUid, userId,
+                "getTaskDescriptionIcon");
+        synchronized (mGlobalLock) {
+            // Verify that the caller can make the request for given icon file path
+            final ActivityRecord matchingActivity = mRootWindowContainer.getActivity(
+                    r -> {
+                        if (r.taskDescription == null
+                                || r.taskDescription.getIconFilename() == null) {
+                            return false;
+                        }
+                        return r.taskDescription.getIconFilename().equals(filePath);
+                    });
+            if (matchingActivity == null || (matchingActivity.getUid() != callingUid)) {
+                // Caller UID doesn't match the requested Activity's UID, check if caller is
+                // privileged
+                try {
+                    enforceActivityTaskPermission("getTaskDescriptionIcon");
+                } catch (SecurityException e) {
+                    Slog.w(TAG, "getTaskDescriptionIcon(): request (callingUid=" + callingUid
+                            + ", filePath=" + filePath + ", user=" + userId + ") doesn't match any "
+                            + "activity");
+                    throw e;
+                }
+            }
+        }
 
         final File passedIconFile = new File(filePath);
         final File legitIconFile = new File(TaskPersister.getUserImagesDir(userId),
@@ -3303,6 +3315,13 @@
         return false;
     }
 
+    /**
+     * An instance method that's easier for mocking in tests.
+     */
+    void enforceActivityTaskPermission(String func) {
+        enforceTaskPermission(func);
+    }
+
     static void enforceTaskPermission(String func) {
         if (checkCallingPermission(MANAGE_ACTIVITY_TASKS) == PackageManager.PERMISSION_GRANTED) {
             return;
diff --git a/services/core/java/com/android/server/wm/ActivityTaskSupervisor.java b/services/core/java/com/android/server/wm/ActivityTaskSupervisor.java
index 07f52574..430232c 100644
--- a/services/core/java/com/android/server/wm/ActivityTaskSupervisor.java
+++ b/services/core/java/com/android/server/wm/ActivityTaskSupervisor.java
@@ -902,10 +902,7 @@
                                 + " andResume=" + andResume);
                 EventLogTags.writeWmRestartActivity(r.mUserId, System.identityHashCode(r),
                         task.mTaskId, r.shortComponentName);
-                if (r.isActivityTypeHome()) {
-                    // Home process is the root process of the task.
-                    updateHomeProcess(task.getBottomMostActivity().app);
-                }
+                updateHomeProcessIfNeeded(r);
                 mService.getPackageManagerInternalLocked().notifyPackageUse(
                         r.intent.getComponent().getPackageName(), NOTIFY_PACKAGE_USE_ACTIVITY);
                 mService.getAppWarningsLocked().onStartActivity(r);
@@ -1050,6 +1047,16 @@
         return true;
     }
 
+    void updateHomeProcessIfNeeded(@NonNull ActivityRecord r) {
+        if (!r.isActivityTypeHome()) return;
+        // Make sure that we use the bottom most activity from the same package, because the home
+        // task can also embed third-party -1 activities.
+        final ActivityRecord bottom = r.getTask().getBottomMostActivityInSamePackage();
+        if (bottom != null) {
+            updateHomeProcess(bottom.app);
+        }
+    }
+
     void updateHomeProcess(WindowProcessController app) {
         if (app != null && mService.mHomeProcess != app) {
             scheduleStartHome("homeChanged");
diff --git a/services/core/java/com/android/server/wm/ImeInsetsSourceProvider.java b/services/core/java/com/android/server/wm/ImeInsetsSourceProvider.java
index 21326be..8c4f9ef 100644
--- a/services/core/java/com/android/server/wm/ImeInsetsSourceProvider.java
+++ b/services/core/java/com/android/server/wm/ImeInsetsSourceProvider.java
@@ -38,7 +38,6 @@
 import android.view.InsetsSourceControl;
 import android.view.WindowInsets;
 import android.view.inputmethod.ImeTracker;
-import android.window.TaskSnapshot;
 
 import com.android.internal.annotations.VisibleForTesting;
 import com.android.internal.protolog.common.ProtoLog;
@@ -78,17 +77,22 @@
         final InsetsSourceControl control = super.getControl(target);
         if (control != null && target != null && target.getWindow() != null) {
             final WindowState targetWin = target.getWindow();
+            final Task task = targetWin.getTask();
             // If the control target changes during the app transition with the task snapshot
             // starting window and the IME snapshot is visible, in case not have duplicated IME
             // showing animation during transitioning, use a flag to inform IME source control to
             // skip showing animation once.
-            final TaskSnapshot snapshot = targetWin.getRootTask() != null
-                    ? targetWin.mWmService.getTaskSnapshot(targetWin.getRootTask().mTaskId,
-                        0 /* userId */, false /* isLowResolution */, false /* restoreFromDisk */)
-                    : null;
-            control.setSkipAnimationOnce(targetWin.mActivityRecord != null
-                    && targetWin.mActivityRecord.hasStartingWindow()
-                    && snapshot != null && snapshot.hasImeSurface());
+            StartingData startingData = null;
+            if (task != null) {
+                startingData = targetWin.mActivityRecord.mStartingData;
+                if (startingData == null) {
+                    final WindowState startingWin = task.topStartingWindow();
+                    if (startingWin != null) {
+                        startingData = startingWin.mStartingData;
+                    }
+                }
+            }
+            control.setSkipAnimationOnce(startingData != null && startingData.hasImeSurface());
         }
         return control;
     }
diff --git a/services/core/java/com/android/server/wm/Task.java b/services/core/java/com/android/server/wm/Task.java
index b89c12b..1ce2cd8 100644
--- a/services/core/java/com/android/server/wm/Task.java
+++ b/services/core/java/com/android/server/wm/Task.java
@@ -1277,8 +1277,9 @@
             final ActivityRecord top = topRunningActivity();
             final ActivityRecord resumedActivity = getResumedActivity();
             if (resumedActivity != null
-                    && (top.getTaskFragment() != this || !canBeResumed(resuming))) {
-                // Pausing the resumed activity because it is occluded by other task fragment.
+                    && (top == null || top.getTaskFragment() != this || !canBeResumed(resuming))) {
+                // Pausing the resumed activity because it is occluded by other task fragment, or
+                // should not be remained in resumed state.
                 if (startPausing(false /* uiSleeping*/, resuming, reason)) {
                     someActivityPaused[0]++;
                 }
@@ -6799,6 +6800,15 @@
         }
     }
 
+    @Nullable
+    ActivityRecord getBottomMostActivityInSamePackage() {
+        if (realActivity == null) {
+            return null;
+        }
+        return getActivity(ar -> ar.packageName.equals(
+                realActivity.getPackageName()), false /* traverseTopToBottom */);
+    }
+
     /**
      * Associates the decor surface with the given TF, or create one if there
      * isn't one in the Task yet. The surface will be removed with the TF,
diff --git a/services/devicepolicy/java/com/android/server/devicepolicy/DevicePolicyManagerService.java b/services/devicepolicy/java/com/android/server/devicepolicy/DevicePolicyManagerService.java
index c3175d6..318042e 100644
--- a/services/devicepolicy/java/com/android/server/devicepolicy/DevicePolicyManagerService.java
+++ b/services/devicepolicy/java/com/android/server/devicepolicy/DevicePolicyManagerService.java
@@ -9030,7 +9030,13 @@
 
     void sendDeviceOwnerOrProfileOwnerCommand(String action, Bundle extras, int userId) {
         if (userId == UserHandle.USER_ALL) {
-            userId = UserHandle.USER_SYSTEM;
+            if (Flags.headlessDeviceOwnerDelegateSecurityLoggingBugFix()
+                    && getHeadlessDeviceOwnerModeForDeviceOwner()
+                    == HEADLESS_DEVICE_OWNER_MODE_SINGLE_USER) {
+                userId = mOwners.getDeviceOwnerUserId();
+            } else {
+                userId = UserHandle.USER_SYSTEM;
+            }
         }
         boolean inForeground = false;
         ComponentName receiverComponent = null;
diff --git a/services/tests/mockingservicestests/src/com/android/server/SensitiveContentProtectionManagerServiceContentTest.java b/services/tests/mockingservicestests/src/com/android/server/SensitiveContentProtectionManagerServiceContentTest.java
index 2366f56..7aafa8e 100644
--- a/services/tests/mockingservicestests/src/com/android/server/SensitiveContentProtectionManagerServiceContentTest.java
+++ b/services/tests/mockingservicestests/src/com/android/server/SensitiveContentProtectionManagerServiceContentTest.java
@@ -24,9 +24,11 @@
 
 import static org.mockito.ArgumentMatchers.any;
 import static org.mockito.Mockito.atLeast;
+import static org.mockito.Mockito.never;
 import static org.mockito.Mockito.verify;
 import static org.mockito.Mockito.verifyZeroInteractions;
 
+import android.content.pm.PackageManagerInternal;
 import android.media.projection.MediaProjectionInfo;
 import android.media.projection.MediaProjectionManager;
 import android.os.Binder;
@@ -72,6 +74,7 @@
 
     @Mock private WindowManagerInternal mWindowManager;
     @Mock private MediaProjectionManager mProjectionManager;
+    @Mock private PackageManagerInternal mPackageManagerInternal;
     private MediaProjectionInfo mMediaProjectionInfo;
 
     @Captor
@@ -91,7 +94,7 @@
         mSensitiveContentProtectionManagerService =
                 new SensitiveContentProtectionManagerService(mContext);
         mSensitiveContentProtectionManagerService.init(mProjectionManager, mWindowManager,
-                new ArraySet<>(Set.of(mExemptedScreenRecorderPackage)));
+                mPackageManagerInternal, new ArraySet<>(Set.of(mExemptedScreenRecorderPackage)));
         verify(mProjectionManager).addCallback(mMediaProjectionCallbackCaptor.capture(), any());
         mMediaPorjectionCallback = mMediaProjectionCallbackCaptor.getValue();
         mMediaProjectionInfo =
@@ -146,6 +149,20 @@
     }
 
     @Test
+    public void testAutofillServicePackageExemption() {
+        String testAutofillService = mScreenRecorderPackage + "/com.example.SampleAutofillService";
+        int userId = Process.myUserHandle().getIdentifier();
+        Settings.Secure.putStringForUser(mContext.getContentResolver(),
+                Settings.Secure.AUTOFILL_SERVICE, testAutofillService , userId);
+
+        mMediaPorjectionCallback.onStart(mMediaProjectionInfo);
+        mSensitiveContentProtectionManagerService.setSensitiveContentProtection(
+                mPackageInfo.getWindowToken(), mPackageInfo.getPkg(), mPackageInfo.getUid(), true);
+        verify(mWindowManager, never())
+                .addBlockScreenCaptureForApps(mPackageInfoCaptor.capture());
+    }
+
+    @Test
     public void testDeveloperOptionDisableFeature() {
         mockDisabledViaDeveloperOption();
         mMediaProjectionCallbackCaptor.getValue().onStart(mMediaProjectionInfo);
diff --git a/services/tests/mockingservicestests/src/com/android/server/SensitiveContentProtectionManagerServiceNotificationTest.java b/services/tests/mockingservicestests/src/com/android/server/SensitiveContentProtectionManagerServiceNotificationTest.java
index e74fe29..5065144 100644
--- a/services/tests/mockingservicestests/src/com/android/server/SensitiveContentProtectionManagerServiceNotificationTest.java
+++ b/services/tests/mockingservicestests/src/com/android/server/SensitiveContentProtectionManagerServiceNotificationTest.java
@@ -31,6 +31,7 @@
 import static org.mockito.Mockito.verifyZeroInteractions;
 import static org.mockito.Mockito.when;
 
+import android.content.pm.PackageManagerInternal;
 import android.media.projection.MediaProjectionInfo;
 import android.media.projection.MediaProjectionManager;
 import android.platform.test.annotations.RequiresFlagsEnabled;
@@ -104,6 +105,9 @@
     private WindowManagerInternal mWindowManager;
 
     @Mock
+    private PackageManagerInternal mPackageManagerInternal;
+
+    @Mock
     private StatusBarNotification mNotification1;
 
     @Mock
@@ -141,7 +145,7 @@
         setupSensitiveNotification();
 
         mSensitiveContentProtectionManagerService.init(mProjectionManager, mWindowManager,
-                new ArraySet<>(Set.of(EXEMPTED_SCREEN_RECORDER_PACKAGE)));
+                mPackageManagerInternal, new ArraySet<>(Set.of(EXEMPTED_SCREEN_RECORDER_PACKAGE)));
 
         // Obtain useful mMediaProjectionCallback
         verify(mProjectionManager).addCallback(mMediaProjectionCallbackCaptor.capture(), any());
diff --git a/services/tests/mockingservicestests/src/com/android/server/alarm/AlarmManagerServiceTest.java b/services/tests/mockingservicestests/src/com/android/server/alarm/AlarmManagerServiceTest.java
index 9975221..ce5cee0 100644
--- a/services/tests/mockingservicestests/src/com/android/server/alarm/AlarmManagerServiceTest.java
+++ b/services/tests/mockingservicestests/src/com/android/server/alarm/AlarmManagerServiceTest.java
@@ -137,6 +137,8 @@
 import android.net.Uri;
 import android.os.BatteryManager;
 import android.os.Bundle;
+import android.os.Environment;
+import android.os.FileUtils;
 import android.os.Handler;
 import android.os.HandlerExecutor;
 import android.os.IBinder;
@@ -149,6 +151,7 @@
 import android.os.ServiceManager;
 import android.os.SystemProperties;
 import android.os.UserHandle;
+import android.platform.test.annotations.DisableFlags;
 import android.platform.test.annotations.EnableFlags;
 import android.platform.test.annotations.Presubmit;
 import android.platform.test.flag.junit.SetFlagsRule;
@@ -159,6 +162,7 @@
 import android.util.Log;
 import android.util.SparseArray;
 
+import androidx.test.platform.app.InstrumentationRegistry;
 import androidx.test.runner.AndroidJUnit4;
 
 import com.android.dx.mockito.inline.extended.MockedVoidMethod;
@@ -183,6 +187,7 @@
 
 import libcore.util.EmptyArray;
 
+import org.junit.After;
 import org.junit.Before;
 import org.junit.Rule;
 import org.junit.Test;
@@ -194,6 +199,7 @@
 import org.mockito.quality.Strictness;
 import org.mockito.stubbing.Answer;
 
+import java.io.File;
 import java.util.ArrayList;
 import java.util.Collections;
 import java.util.HashSet;
@@ -224,6 +230,7 @@
     private ActivityManager.UidFrozenStateChangedCallback mUidFrozenStateCallback;
     private IAppOpsCallback mIAppOpsCallback;
     private IAlarmManager mBinder;
+    private File mTestDir;
     @Mock
     private Context mMockContext;
     @Mock
@@ -413,6 +420,7 @@
             .mockStatic(PermissionManagerService.class)
             .mockStatic(ServiceManager.class)
             .mockStatic(SystemProperties.class)
+            .mockStatic(Environment.class)
             .spyStatic(UserHandle.class)
             .afterSessionFinished(
                     () -> LocalServices.removeServiceForTest(AlarmManagerInternal.class))
@@ -429,7 +437,8 @@
      */
     private void disableFlagsNotSetByAnnotation() {
         try {
-            mSetFlagsRule.disableFlags(Flags.FLAG_USE_FROZEN_STATE_TO_DROP_LISTENER_ALARMS);
+            mSetFlagsRule.disableFlags(Flags.FLAG_USE_FROZEN_STATE_TO_DROP_LISTENER_ALARMS,
+                    Flags.FLAG_START_USER_BEFORE_SCHEDULED_ALARMS);
         } catch (FlagSetException fse) {
             // Expected if the test about to be run requires this enabled.
         }
@@ -460,7 +469,10 @@
         when(mUsageStatsManagerInternal.getAppStandbyBucket(eq(TEST_CALLING_PACKAGE),
                 eq(TEST_CALLING_USER), anyLong())).thenReturn(STANDBY_BUCKET_ACTIVE);
         doReturn(Looper.getMainLooper()).when(Looper::myLooper);
-
+        mTestDir = new File(InstrumentationRegistry.getInstrumentation().getTargetContext()
+                .getFilesDir(), "alarmsTestDir");
+        mTestDir.mkdirs();
+        doReturn(mTestDir).when(Environment::getDataSystemDirectory);
         when(mMockContext.getContentResolver()).thenReturn(mContentResolver);
 
         doReturn(mDeviceConfigKeys).when(mDeviceConfigProperties).getKeyset();
@@ -579,6 +591,12 @@
         setTestableQuotas();
     }
 
+    @After
+    public void tearDown() {
+        // Clean up test dir to remove persisted user files.
+        FileUtils.deleteContentsAndDir(mTestDir);
+    }
+
     private void setTestAlarm(int type, long triggerTime, PendingIntent operation) {
         setTestAlarm(type, triggerTime, operation, 0, FLAG_STANDALONE, TEST_CALLING_UID);
     }
@@ -3792,6 +3810,7 @@
     }
 
     @EnableFlags(Flags.FLAG_USE_FROZEN_STATE_TO_DROP_LISTENER_ALARMS)
+    @DisableFlags(Flags.FLAG_START_USER_BEFORE_SCHEDULED_ALARMS)
     @Test
     public void exactListenerAlarmsRemovedOnFrozen() {
         mockChangeEnabled(EXACT_LISTENER_ALARMS_DROPPED_ON_CACHED, true);
@@ -3823,6 +3842,7 @@
     }
 
     @EnableFlags(Flags.FLAG_USE_FROZEN_STATE_TO_DROP_LISTENER_ALARMS)
+    @DisableFlags(Flags.FLAG_START_USER_BEFORE_SCHEDULED_ALARMS)
     @Test
     public void alarmCountOnListenerFrozen() {
         mockChangeEnabled(EXACT_LISTENER_ALARMS_DROPPED_ON_CACHED, true);
diff --git a/services/tests/mockingservicestests/src/com/android/server/alarm/UserWakeupStoreTest.java b/services/tests/mockingservicestests/src/com/android/server/alarm/UserWakeupStoreTest.java
new file mode 100644
index 0000000..5d3e499
--- /dev/null
+++ b/services/tests/mockingservicestests/src/com/android/server/alarm/UserWakeupStoreTest.java
@@ -0,0 +1,170 @@
+/*
+ * Copyright (C) 2024 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.server.alarm;
+
+import static com.android.dx.mockito.inline.extended.ExtendedMockito.doReturn;
+import static com.android.dx.mockito.inline.extended.ExtendedMockito.spyOn;
+import static com.android.server.alarm.UserWakeupStore.BUFFER_TIME_MS;
+import static com.android.server.alarm.UserWakeupStore.USER_START_TIME_DEVIATION_LIMIT_MS;
+
+import static org.junit.Assert.assertEquals;
+import static org.junit.Assert.assertTrue;
+
+import android.os.Environment;
+import android.os.FileUtils;
+import android.os.SystemClock;
+
+import androidx.test.platform.app.InstrumentationRegistry;
+import androidx.test.runner.AndroidJUnit4;
+
+import com.android.internal.os.BackgroundThread;
+import com.android.modules.utils.testing.ExtendedMockitoRule;
+
+import org.junit.After;
+import org.junit.Before;
+import org.junit.Rule;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+import org.mockito.Mockito;
+
+import java.io.File;
+import java.util.ArrayList;
+import java.util.Collections;
+import java.util.concurrent.ExecutorService;
+
+@RunWith(AndroidJUnit4.class)
+public class UserWakeupStoreTest {
+    private static final int USER_ID_1 = 10;
+    private static final int USER_ID_2 = 11;
+    private static final int USER_ID_3 = 12;
+    private static final long TEST_TIMESTAMP = 150_000;
+    private static final File TEST_SYSTEM_DIR = new File(InstrumentationRegistry
+            .getInstrumentation().getContext().getDataDir(), "alarmsTestDir");
+    private static final File ROOT_DIR = new File(TEST_SYSTEM_DIR, UserWakeupStore.ROOT_DIR_NAME);
+    private ExecutorService mMockExecutorService = null;
+    UserWakeupStore mUserWakeupStore;
+
+    @Rule
+    public final ExtendedMockitoRule mExtendedMockitoRule = new ExtendedMockitoRule.Builder(this)
+            .mockStatic(Environment.class)
+            .mockStatic(BackgroundThread.class)
+            .build();
+
+    @Before
+    public void setUp() {
+        TEST_SYSTEM_DIR.mkdirs();
+        doReturn(TEST_SYSTEM_DIR).when(Environment::getDataSystemDirectory);
+        mMockExecutorService = Mockito.mock(ExecutorService.class);
+        Mockito.doAnswer((invocation) -> {
+            Runnable task = invocation.getArgument(0);
+            task.run();
+            return null;
+        }).when(mMockExecutorService).execute(Mockito.any(Runnable.class));
+        doReturn(mMockExecutorService).when(BackgroundThread::getExecutor);
+        mUserWakeupStore = new UserWakeupStore();
+        spyOn(mUserWakeupStore);
+        mUserWakeupStore.init();
+    }
+
+    @After
+    public void tearDown() {
+        // Clean up test dir to remove persisted user files.
+        FileUtils.deleteContentsAndDir(TEST_SYSTEM_DIR);
+    }
+
+    @Test
+    public void testAddWakeups() {
+        mUserWakeupStore.addUserWakeup(USER_ID_1, TEST_TIMESTAMP - 19_000);
+        mUserWakeupStore.addUserWakeup(USER_ID_2, TEST_TIMESTAMP - 7_000);
+        mUserWakeupStore.addUserWakeup(USER_ID_3, TEST_TIMESTAMP - 13_000);
+        assertEquals(3, mUserWakeupStore.getUserIdsToWakeup(TEST_TIMESTAMP).length);
+        ArrayList<Integer> userIds = new ArrayList<>();
+        userIds.add(USER_ID_1);
+        userIds.add(USER_ID_2);
+        userIds.add(USER_ID_3);
+        final int[] usersToWakeup = mUserWakeupStore.getUserIdsToWakeup(TEST_TIMESTAMP);
+        ArrayList<Integer> userWakeups = new ArrayList<>();
+        for (int i = 0; i < usersToWakeup.length; i++) {
+            userWakeups.add(usersToWakeup[i]);
+        }
+        Collections.sort(userIds);
+        Collections.sort(userWakeups);
+        assertEquals(userIds, userWakeups);
+
+        final File file = new File(ROOT_DIR , "usersWithAlarmClocks.xml");
+        assertTrue(file.exists());
+    }
+
+    @Test
+    public void testAddMultipleWakeupsForUser_ensureOnlyLastWakeupRemains() {
+        final long finalAlarmTime = TEST_TIMESTAMP - 13_000;
+        mUserWakeupStore.addUserWakeup(USER_ID_1, TEST_TIMESTAMP - 29_000);
+        mUserWakeupStore.addUserWakeup(USER_ID_1, TEST_TIMESTAMP - 7_000);
+        mUserWakeupStore.addUserWakeup(USER_ID_1, finalAlarmTime);
+        assertEquals(1, mUserWakeupStore.getUserIdsToWakeup(TEST_TIMESTAMP).length);
+        final long alarmTime = mUserWakeupStore.getWakeupTimeForUserForTest(USER_ID_1)
+                + BUFFER_TIME_MS;
+        assertTrue(finalAlarmTime + USER_START_TIME_DEVIATION_LIMIT_MS >= alarmTime);
+        assertTrue(finalAlarmTime - USER_START_TIME_DEVIATION_LIMIT_MS <= alarmTime);
+    }
+
+    @Test
+    public void testRemoveWakeupForUser_negativeWakeupTimeIsReturnedForUser() {
+        mUserWakeupStore.addUserWakeup(USER_ID_1, TEST_TIMESTAMP - 19_000);
+        mUserWakeupStore.addUserWakeup(USER_ID_2, TEST_TIMESTAMP - 7_000);
+        mUserWakeupStore.addUserWakeup(USER_ID_3, TEST_TIMESTAMP - 13_000);
+        assertEquals(3, mUserWakeupStore.getUserIdsToWakeup(TEST_TIMESTAMP).length);
+        mUserWakeupStore.removeUserWakeup(USER_ID_3);
+        assertEquals(-1, mUserWakeupStore.getWakeupTimeForUserForTest(USER_ID_3));
+        assertTrue(mUserWakeupStore.getWakeupTimeForUserForTest(USER_ID_2) > 0);
+    }
+
+    @Test
+    public void testGetNextUserWakeup() {
+        mUserWakeupStore.addUserWakeup(USER_ID_1, TEST_TIMESTAMP - 19_000);
+        mUserWakeupStore.addUserWakeup(USER_ID_2, TEST_TIMESTAMP - 3_000);
+        mUserWakeupStore.addUserWakeup(USER_ID_3, TEST_TIMESTAMP - 13_000);
+        assertEquals(mUserWakeupStore.getNextWakeupTime(),
+                mUserWakeupStore.getWakeupTimeForUserForTest(USER_ID_1));
+        mUserWakeupStore.removeUserWakeup(USER_ID_1);
+        assertEquals(mUserWakeupStore.getNextWakeupTime(),
+                mUserWakeupStore.getWakeupTimeForUserForTest(USER_ID_3));
+    }
+
+    @Test
+    public void testWriteAndReadUsersFromFile() {
+        mUserWakeupStore.addUserWakeup(USER_ID_1, TEST_TIMESTAMP - 19_000);
+        mUserWakeupStore.addUserWakeup(USER_ID_2, TEST_TIMESTAMP - 7_000);
+        mUserWakeupStore.addUserWakeup(USER_ID_3, TEST_TIMESTAMP - 13_000);
+        assertEquals(3, mUserWakeupStore.getUserIdsToWakeup(TEST_TIMESTAMP).length);
+        mUserWakeupStore.init();
+        final long realtime = SystemClock.elapsedRealtime();
+        assertEquals(0, mUserWakeupStore.getUserIdsToWakeup(TEST_TIMESTAMP).length);
+        assertTrue(mUserWakeupStore.getWakeupTimeForUserForTest(USER_ID_2) > realtime);
+        assertTrue(mUserWakeupStore.getWakeupTimeForUserForTest(USER_ID_1)
+                < mUserWakeupStore.getWakeupTimeForUserForTest(USER_ID_3));
+        assertTrue(mUserWakeupStore.getWakeupTimeForUserForTest(USER_ID_3)
+                < mUserWakeupStore.getWakeupTimeForUserForTest(USER_ID_2));
+        assertTrue(mUserWakeupStore.getWakeupTimeForUserForTest(USER_ID_1) - realtime
+                < BUFFER_TIME_MS + USER_START_TIME_DEVIATION_LIMIT_MS);
+        assertTrue(mUserWakeupStore.getWakeupTimeForUserForTest(USER_ID_3) - realtime
+                < 2 * BUFFER_TIME_MS + USER_START_TIME_DEVIATION_LIMIT_MS);
+        assertTrue(mUserWakeupStore.getWakeupTimeForUserForTest(USER_ID_2) - realtime
+                < 3 * BUFFER_TIME_MS + USER_START_TIME_DEVIATION_LIMIT_MS);
+    }
+    //TODO: b/330264023 - Add tests for I/O in usersWithAlarmClocks.xml.
+}
diff --git a/services/tests/mockingservicestests/src/com/android/server/pm/MockSystem.kt b/services/tests/mockingservicestests/src/com/android/server/pm/MockSystem.kt
index 538c0ee..c9aab53 100644
--- a/services/tests/mockingservicestests/src/com/android/server/pm/MockSystem.kt
+++ b/services/tests/mockingservicestests/src/com/android/server/pm/MockSystem.kt
@@ -166,7 +166,7 @@
             null
         }
         whenever(mocks.settings.addPackageLPw(nullable(), nullable(), nullable(), nullable(),
-                nullable(), nullable(), nullable())) {
+                nullable(), nullable(), nullable(), nullable())) {
             val name: String = getArgument(0)
             val pendingAdd = mPendingPackageAdds.firstOrNull { it.first == name }
                     ?: return@whenever null
diff --git a/services/tests/mockingservicestests/src/com/android/server/pm/UserManagerServiceTest.java b/services/tests/mockingservicestests/src/com/android/server/pm/UserManagerServiceTest.java
index 6aa1825..759a974 100644
--- a/services/tests/mockingservicestests/src/com/android/server/pm/UserManagerServiceTest.java
+++ b/services/tests/mockingservicestests/src/com/android/server/pm/UserManagerServiceTest.java
@@ -736,9 +736,10 @@
         Mockito.clearInvocations(mKeyguardManager);
         Mockito.clearInvocations(mSpiedContext);
 
-        // Finally, set the preference to don't auto-lock
+        // Finally, set the preference to auto-lock only after device restart, which is the default
+        // behaviour
         mUms.setOrUpdateAutoLockPreferenceForPrivateProfile(
-                Settings.Secure.PRIVATE_SPACE_AUTO_LOCK_NEVER);
+                Settings.Secure.PRIVATE_SPACE_AUTO_LOCK_AFTER_DEVICE_RESTART);
 
         // Verify that inactivity broadcasts are unregistered and keyguard listener was removed
         Mockito.verify(mSpiedContext).unregisterReceiver(any());
diff --git a/services/tests/servicestests/src/com/android/server/audio/VolumeHelperTest.java b/services/tests/servicestests/src/com/android/server/audio/VolumeHelperTest.java
index 83bbd0e..23728db 100644
--- a/services/tests/servicestests/src/com/android/server/audio/VolumeHelperTest.java
+++ b/services/tests/servicestests/src/com/android/server/audio/VolumeHelperTest.java
@@ -35,6 +35,7 @@
 import static android.media.AudioManager.STREAM_RING;
 import static android.media.AudioManager.STREAM_SYSTEM;
 import static android.media.AudioManager.STREAM_VOICE_CALL;
+import static android.media.audio.Flags.autoPublicVolumeApiHardening;
 import static android.view.KeyEvent.ACTION_DOWN;
 import static android.view.KeyEvent.KEYCODE_VOLUME_UP;
 
@@ -47,6 +48,7 @@
 import static org.junit.Assert.assertFalse;
 import static org.junit.Assert.assertNotEquals;
 import static org.junit.Assert.assertTrue;
+import static org.junit.Assume.assumeFalse;
 import static org.junit.Assume.assumeNotNull;
 import static org.mockito.ArgumentMatchers.any;
 import static org.mockito.ArgumentMatchers.anyBoolean;
@@ -66,6 +68,8 @@
 import android.annotation.Nullable;
 import android.app.AppOpsManager;
 import android.content.Context;
+import android.content.pm.PackageManager;
+import android.content.res.Resources;
 import android.media.AudioDeviceAttributes;
 import android.media.AudioDeviceInfo;
 import android.media.AudioManager;
@@ -214,6 +218,19 @@
 
         reset(mSpyAudioSystem);
 
+        final boolean useFixedVolume = mContext.getResources().getBoolean(
+                Resources.getSystem().getIdentifier("config_useFixedVolume", "bool", "android"));
+        final PackageManager packageManager = mContext.getPackageManager();
+        final boolean isTelevision = packageManager != null
+                && (packageManager.hasSystemFeature(PackageManager.FEATURE_LEANBACK)
+                || packageManager.hasSystemFeature(PackageManager.FEATURE_TELEVISION));
+        final boolean isSingleVolume = mContext.getResources().getBoolean(
+                Resources.getSystem().getIdentifier("config_single_volume", "bool", "android"));
+        final boolean automotiveHardened = mContext.getPackageManager().hasSystemFeature(
+                PackageManager.FEATURE_AUTOMOTIVE) && autoPublicVolumeApiHardening();
+        assumeFalse("Skipping test for fixed, TV, single volume and auto devices",
+                useFixedVolume || isTelevision || isSingleVolume || automotiveHardened);
+
         InstrumentationRegistry.getInstrumentation().getUiAutomation()
                 .adoptShellPermissionIdentity(Manifest.permission.MODIFY_AUDIO_SETTINGS_PRIVILEGED,
                         Manifest.permission.MODIFY_AUDIO_ROUTING,
diff --git a/services/tests/servicestests/src/com/android/server/biometrics/AuthServiceTest.java b/services/tests/servicestests/src/com/android/server/biometrics/AuthServiceTest.java
index f0dc5f0..7e04277 100644
--- a/services/tests/servicestests/src/com/android/server/biometrics/AuthServiceTest.java
+++ b/services/tests/servicestests/src/com/android/server/biometrics/AuthServiceTest.java
@@ -269,7 +269,7 @@
                 mFingerprintSensorConfigurationsCaptor.capture());
 
         final SensorProps[] fingerprintProp = mFingerprintSensorConfigurationsCaptor.getValue()
-                .getSensorPairForInstance("defaultHIDL").second;
+                .getSensorPropForInstance("defaultHIDL");
 
         assertEquals(fingerprintProp[0].commonProps.sensorId, fingerprintId);
         assertEquals(fingerprintProp[0].commonProps.sensorStrength,
@@ -280,7 +280,7 @@
 
         final android.hardware.biometrics.face.SensorProps[] faceProp =
                 mFaceSensorConfigurationsCaptor.getValue()
-                        .getSensorPairForInstance("defaultHIDL").second;
+                        .getSensorPropForInstance("defaultHIDL");
 
         assertEquals(faceProp[0].commonProps.sensorId, faceId);
         assertEquals(faceProp[0].commonProps.sensorStrength,
diff --git a/services/tests/servicestests/src/com/android/server/biometrics/sensors/face/FaceServiceTest.java b/services/tests/servicestests/src/com/android/server/biometrics/sensors/face/FaceServiceTest.java
index 3aaac2e..e015e97 100644
--- a/services/tests/servicestests/src/com/android/server/biometrics/sensors/face/FaceServiceTest.java
+++ b/services/tests/servicestests/src/com/android/server/biometrics/sensors/face/FaceServiceTest.java
@@ -32,8 +32,6 @@
 import android.content.pm.PackageManager;
 import android.hardware.biometrics.BiometricAuthenticator;
 import android.hardware.biometrics.IBiometricService;
-import android.hardware.biometrics.face.IFace;
-import android.hardware.biometrics.face.SensorProps;
 import android.hardware.face.FaceAuthenticateOptions;
 import android.hardware.face.FaceSensorConfigurations;
 import android.hardware.face.FaceSensorPropertiesInternal;
@@ -93,18 +91,12 @@
     @Mock
     private FaceProvider mFaceProviderVirtual;
     @Mock
-    private IFace mDefaultFaceDaemon;
-    @Mock
-    private IFace mVirtualFaceDaemon;
-    @Mock
     private IBiometricService mIBiometricService;
     @Mock
     private IBinder mToken;
     @Mock
     private IFaceServiceReceiver mFaceServiceReceiver;
 
-    private final SensorProps mDefaultSensorProps = new SensorProps();
-    private final SensorProps mVirtualSensorProps = new SensorProps();
     private FaceService mFaceService;
     private final FaceSensorPropertiesInternal mSensorPropsDefault =
             new FaceSensorPropertiesInternal(ID_DEFAULT, STRENGTH_STRONG,
@@ -126,10 +118,6 @@
 
     @Before
     public void setUp() throws RemoteException {
-        when(mDefaultFaceDaemon.getSensorProps()).thenReturn(
-                new SensorProps[]{mDefaultSensorProps});
-        when(mVirtualFaceDaemon.getSensorProps()).thenReturn(
-                new SensorProps[]{mVirtualSensorProps});
         when(mFaceProviderDefault.getSensorProperties()).thenReturn(List.of(mSensorPropsDefault));
         when(mFaceProviderVirtual.getSensorProperties()).thenReturn(List.of(mSensorPropsVirtual));
         when(mFaceProviderDefault.containsSensor(anyInt()))
@@ -140,15 +128,7 @@
         mContext.getTestablePermissions().setPermission(
                 USE_BIOMETRIC_INTERNAL, PackageManager.PERMISSION_GRANTED);
         mFaceSensorConfigurations = new FaceSensorConfigurations(false);
-        mFaceSensorConfigurations.addAidlConfigs(new String[]{NAME_DEFAULT, NAME_VIRTUAL},
-                (name) -> {
-                    if (name.equals(IFace.DESCRIPTOR + "/" + NAME_DEFAULT)) {
-                        return mDefaultFaceDaemon;
-                    } else if (name.equals(IFace.DESCRIPTOR + "/" + NAME_VIRTUAL)) {
-                        return mVirtualFaceDaemon;
-                    }
-                    return null;
-                });
+        mFaceSensorConfigurations.addAidlConfigs(new String[]{NAME_DEFAULT, NAME_VIRTUAL});
     }
 
     private void initService() {
@@ -199,15 +179,7 @@
     @RequiresFlagsEnabled(Flags.FLAG_DE_HIDL)
     public void registerAuthenticatorsLegacy_virtualAlwaysWhenNoOther() throws Exception {
         mFaceSensorConfigurations = new FaceSensorConfigurations(false);
-        mFaceSensorConfigurations.addAidlConfigs(new String[]{NAME_VIRTUAL},
-                (name) -> {
-                    if (name.equals(IFace.DESCRIPTOR + "/" + NAME_DEFAULT)) {
-                        return mDefaultFaceDaemon;
-                    } else if (name.equals(IFace.DESCRIPTOR + "/" + NAME_VIRTUAL)) {
-                        return mVirtualFaceDaemon;
-                    }
-                    return null;
-                });
+        mFaceSensorConfigurations.addAidlConfigs(new String[]{NAME_VIRTUAL});
         initService();
 
         mFaceService.mServiceWrapper.registerAuthenticatorsLegacy(mFaceSensorConfigurations);
diff --git a/services/tests/servicestests/src/com/android/server/biometrics/sensors/fingerprint/FingerprintServiceTest.java b/services/tests/servicestests/src/com/android/server/biometrics/sensors/fingerprint/FingerprintServiceTest.java
index 88956b6..20961a9 100644
--- a/services/tests/servicestests/src/com/android/server/biometrics/sensors/fingerprint/FingerprintServiceTest.java
+++ b/services/tests/servicestests/src/com/android/server/biometrics/sensors/fingerprint/FingerprintServiceTest.java
@@ -43,8 +43,6 @@
 import android.content.ComponentName;
 import android.content.pm.PackageManager;
 import android.hardware.biometrics.IBiometricService;
-import android.hardware.biometrics.fingerprint.IFingerprint;
-import android.hardware.biometrics.fingerprint.SensorProps;
 import android.hardware.fingerprint.FingerprintAuthenticateOptions;
 import android.hardware.fingerprint.FingerprintSensorConfigurations;
 import android.hardware.fingerprint.FingerprintSensorPropertiesInternal;
@@ -123,10 +121,6 @@
     private IBinder mToken;
     @Mock
     private VirtualDeviceManagerInternal mVdmInternal;
-    @Mock
-    private IFingerprint mDefaultFingerprintDaemon;
-    @Mock
-    private IFingerprint mVirtualFingerprintDaemon;
 
     @Captor
     private ArgumentCaptor<FingerprintAuthenticateOptions> mAuthenticateOptionsCaptor;
@@ -145,8 +139,6 @@
                     false /* resetLockoutRequiresHardwareAuthToken */);
     private FingerprintSensorConfigurations mFingerprintSensorConfigurations;
     private FingerprintService mService;
-    private final SensorProps mDefaultSensorProps = new SensorProps();
-    private final SensorProps mVirtualSensorProps = new SensorProps();
 
     @Before
     public void setup() throws Exception {
@@ -159,10 +151,6 @@
                 .thenAnswer(i -> i.getArguments()[0].equals(ID_DEFAULT));
         when(mFingerprintVirtual.containsSensor(anyInt()))
                 .thenAnswer(i -> i.getArguments()[0].equals(ID_VIRTUAL));
-        when(mDefaultFingerprintDaemon.getSensorProps()).thenReturn(
-                new SensorProps[]{mDefaultSensorProps});
-        when(mVirtualFingerprintDaemon.getSensorProps()).thenReturn(
-                new SensorProps[]{mVirtualSensorProps});
 
         mContext.addMockSystemService(AppOpsManager.class, mAppOpsManager);
         for (int permission : List.of(OP_USE_BIOMETRIC, OP_USE_FINGERPRINT)) {
@@ -177,15 +165,7 @@
 
         mFingerprintSensorConfigurations = new FingerprintSensorConfigurations(
                 true /* resetLockoutRequiresHardwareAuthToken */);
-        mFingerprintSensorConfigurations.addAidlSensors(new String[]{NAME_DEFAULT, NAME_VIRTUAL},
-                (name) -> {
-                    if (name.equals(IFingerprint.DESCRIPTOR + "/" + NAME_DEFAULT)) {
-                        return mDefaultFingerprintDaemon;
-                    } else if (name.equals(IFingerprint.DESCRIPTOR + "/" + NAME_VIRTUAL)) {
-                        return mVirtualFingerprintDaemon;
-                    }
-                    return null;
-                });
+        mFingerprintSensorConfigurations.addAidlSensors(new String[]{NAME_DEFAULT, NAME_VIRTUAL});
     }
 
     private void initServiceWith(String... aidlInstances) {
@@ -270,15 +250,7 @@
     public void registerAuthenticatorsLegacy_virtualAlwaysWhenNoOther() throws Exception {
         mFingerprintSensorConfigurations =
                 new FingerprintSensorConfigurations(true);
-        mFingerprintSensorConfigurations.addAidlSensors(new String[]{NAME_VIRTUAL},
-                        (name) -> {
-                            if (name.equals(IFingerprint.DESCRIPTOR + "/" + NAME_DEFAULT)) {
-                                return mDefaultFingerprintDaemon;
-                            } else if (name.equals(IFingerprint.DESCRIPTOR + "/" + NAME_VIRTUAL)) {
-                                return mVirtualFingerprintDaemon;
-                            }
-                            return null;
-                        });
+        mFingerprintSensorConfigurations.addAidlSensors(new String[]{NAME_VIRTUAL});
         initServiceWith(NAME_VIRTUAL);
 
         mService.mServiceWrapper.registerAuthenticatorsLegacy(mFingerprintSensorConfigurations);
diff --git a/services/tests/wmtests/src/com/android/server/wm/ActivityStarterTests.java b/services/tests/wmtests/src/com/android/server/wm/ActivityStarterTests.java
index 173a1b6..1355092 100644
--- a/services/tests/wmtests/src/com/android/server/wm/ActivityStarterTests.java
+++ b/services/tests/wmtests/src/com/android/server/wm/ActivityStarterTests.java
@@ -1634,6 +1634,28 @@
         assertThat(transition.isInTransientHide(top.getTask())).isTrue();
     }
 
+    /**
+     * Tests ATMS#startActivityWithScreenshot should collect display content for creating snapshot.
+     */
+    @Test
+    public void testActivityStartWithScreenshot() {
+        final ActivityStarter starter = prepareStarter(0 /* flags */);
+        starter.setFreezeScreen(true);
+
+        registerTestTransitionPlayer();
+
+        final Intent intent = new Intent();
+        intent.setComponent(ActivityBuilder.getDefaultComponent());
+        starter.setReason("testActivityStartWithScreenshot")
+                .setIntent(intent)
+                .execute();
+
+        final TransitionController controller = mRootWindowContainer.mTransitionController;
+        final Transition transition = controller.getCollectingTransition();
+        final Transition.ChangeInfo targetChangeInfo = transition.mChanges.get(mDisplayContent);
+        assertThat(targetChangeInfo).isNotNull();
+    }
+
     @Test
     public void testActivityStart_expectAddedToRecentTask() {
         RecentTasks recentTasks = mock(RecentTasks.class);
diff --git a/services/tests/wmtests/src/com/android/server/wm/ActivityTaskManagerServiceTests.java b/services/tests/wmtests/src/com/android/server/wm/ActivityTaskManagerServiceTests.java
index ed99108..400f9bb 100644
--- a/services/tests/wmtests/src/com/android/server/wm/ActivityTaskManagerServiceTests.java
+++ b/services/tests/wmtests/src/com/android/server/wm/ActivityTaskManagerServiceTests.java
@@ -38,18 +38,22 @@
 import static org.junit.Assert.assertNotEquals;
 import static org.junit.Assert.assertNotNull;
 import static org.junit.Assert.assertNull;
+import static org.junit.Assert.assertThrows;
 import static org.junit.Assert.assertTrue;
+import static org.junit.Assert.fail;
 import static org.mockito.ArgumentMatchers.any;
 import static org.mockito.ArgumentMatchers.anyBoolean;
 import static org.mockito.ArgumentMatchers.anyString;
 import static org.mockito.Mockito.clearInvocations;
 import static org.mockito.Mockito.doCallRealMethod;
+import static org.mockito.Mockito.doThrow;
 import static org.mockito.Mockito.never;
 
 import android.annotation.NonNull;
 import android.annotation.Nullable;
 import android.app.Activity;
 import android.app.ActivityManager;
+import android.app.ActivityManager.TaskDescription;
 import android.app.IApplicationThread;
 import android.app.PictureInPictureParams;
 import android.app.servertransaction.ClientTransactionItem;
@@ -62,6 +66,7 @@
 import android.os.LocaleList;
 import android.os.PowerManagerInternal;
 import android.os.RemoteException;
+import android.os.UserHandle;
 import android.platform.test.annotations.Presubmit;
 import android.view.Display;
 import android.view.DisplayInfo;
@@ -1099,4 +1104,61 @@
 
         verify(mClientLifecycleManager).onLayoutContinued();
     }
+
+    @Test
+    public void testGetTaskDescriptionIcon_matchingUid() {
+        // Ensure that we do not hold MANAGE_ACTIVITY_TASKS
+        doThrow(new SecurityException()).when(mAtm).enforceActivityTaskPermission(any());
+
+        final String filePath = "abc/def";
+        // Create an activity with a task description at the test icon filepath
+        final ActivityRecord activity = new ActivityBuilder(mAtm)
+                .setUid(android.os.Process.myUid())
+                .setCreateTask(true)
+                .build();
+        final TaskDescription td = new TaskDescription.Builder().build();
+        td.setIconFilename(filePath);
+        activity.setTaskDescription(td);
+
+        // Verify this calls and does not throw a security exception
+        try {
+            mAtm.getTaskDescriptionIcon(filePath, activity.mUserId);
+        } catch (SecurityException e) {
+            fail("Unexpected security exception: " + e);
+        } catch (IllegalArgumentException e) {
+            // Ok, the file doesn't actually exist
+        }
+    }
+
+    @Test
+    public void testGetTaskDescriptionIcon_noMatchingActivity_expectException() {
+        // Ensure that we do not hold MANAGE_ACTIVITY_TASKS
+        doThrow(new SecurityException()).when(mAtm).enforceActivityTaskPermission(any());
+
+        final String filePath = "abc/def";
+
+        // Verify this throws a security exception due to no matching activity
+        assertThrows(SecurityException.class,
+                () -> mAtm.getTaskDescriptionIcon(filePath, UserHandle.myUserId()));
+    }
+
+    @Test
+    public void testGetTaskDescriptionIcon_noMatchingUid_expectException() {
+        // Ensure that we do not hold MANAGE_ACTIVITY_TASKS
+        doThrow(new SecurityException()).when(mAtm).enforceActivityTaskPermission(any());
+
+        final String filePath = "abc/def";
+        // Create an activity with a task description at the test icon filepath
+        final ActivityRecord activity = new ActivityBuilder(mAtm)
+                .setCreateTask(true)
+                .setUid(101010)
+                .build();
+        final TaskDescription td = new TaskDescription.Builder().build();
+        td.setIconFilename(filePath);
+        activity.setTaskDescription(td);
+
+        // Verify this throws a security exception due to no matching UID
+        assertThrows(SecurityException.class,
+                () -> mAtm.getTaskDescriptionIcon(filePath, activity.mUserId));
+    }
 }
diff --git a/services/tests/wmtests/src/com/android/server/wm/BackNavigationControllerTests.java b/services/tests/wmtests/src/com/android/server/wm/BackNavigationControllerTests.java
index 363ae14..21251c3 100644
--- a/services/tests/wmtests/src/com/android/server/wm/BackNavigationControllerTests.java
+++ b/services/tests/wmtests/src/com/android/server/wm/BackNavigationControllerTests.java
@@ -537,7 +537,7 @@
         }).when(appWindow.mSession).setOnBackInvokedCallbackInfo(eq(appWindow.mClient), any());
 
         addToWindowMap(appWindow, true);
-        dispatcher.attachToWindow(appWindow.mSession, appWindow.mClient);
+        dispatcher.attachToWindow(appWindow.mSession, appWindow.mClient, null);
 
 
         OnBackInvokedCallback appCallback = createBackCallback(appLatch);
diff --git a/services/tests/wmtests/src/com/android/server/wm/RecentTasksTest.java b/services/tests/wmtests/src/com/android/server/wm/RecentTasksTest.java
index bfa191e..75b84d1 100644
--- a/services/tests/wmtests/src/com/android/server/wm/RecentTasksTest.java
+++ b/services/tests/wmtests/src/com/android/server/wm/RecentTasksTest.java
@@ -1472,7 +1472,6 @@
         assertSecurityException(expectCallable, () -> mAtm.registerTaskStackListener(null));
         assertSecurityException(expectCallable,
                 () -> mAtm.unregisterTaskStackListener(null));
-        assertSecurityException(expectCallable, () -> mAtm.getTaskDescription(0));
         assertSecurityException(expectCallable, () -> mAtm.cancelTaskWindowTransition(0));
         assertSecurityException(expectCallable, () -> mAtm.startRecentsActivity(null, 0,
                 null));
diff --git a/services/tests/wmtests/src/com/android/server/wm/TaskTests.java b/services/tests/wmtests/src/com/android/server/wm/TaskTests.java
index a88680a..1ca808f 100644
--- a/services/tests/wmtests/src/com/android/server/wm/TaskTests.java
+++ b/services/tests/wmtests/src/com/android/server/wm/TaskTests.java
@@ -1960,6 +1960,27 @@
         verify(task).startPausing(eq(true) /* userLeaving */, anyBoolean(), any(), any());
     }
 
+    @Test
+    public void testGetBottomMostActivityInSamePackage() {
+        final String packageName = "homePackage";
+        final TaskFragmentOrganizer organizer = new TaskFragmentOrganizer(Runnable::run);
+        final Task task = new TaskBuilder(mSupervisor).setCreateActivity(false).build();
+        task.realActivity = new ComponentName(packageName, packageName + ".root_activity");
+        doNothing().when(task).sendTaskFragmentParentInfoChangedIfNeeded();
+
+        final TaskFragment fragment1 = createTaskFragmentWithEmbeddedActivity(task, organizer);
+        final ActivityRecord activityDifferentPackage =
+                new ActivityBuilder(mAtm).setTask(task).build();
+        final ActivityRecord activitySamePackage =
+                new ActivityBuilder(mAtm)
+                        .setComponent(new ComponentName(packageName, packageName + ".activity2"))
+                        .setTask(task).build();
+
+        assertEquals(fragment1.getChildAt(0), task.getBottomMostActivity());
+        assertEquals(activitySamePackage, task.getBottomMostActivityInSamePackage());
+        assertNotEquals(activityDifferentPackage, task.getBottomMostActivityInSamePackage());
+    }
+
     private Task getTestTask() {
         return new TaskBuilder(mSupervisor).setCreateActivity(true).build();
     }
diff --git a/telephony/java/android/telephony/CarrierConfigManager.java b/telephony/java/android/telephony/CarrierConfigManager.java
index df32fbd..ebdd556 100644
--- a/telephony/java/android/telephony/CarrierConfigManager.java
+++ b/telephony/java/android/telephony/CarrierConfigManager.java
@@ -8013,6 +8013,27 @@
                 KEY_SCAN_LIMITED_SERVICE_AFTER_VOLTE_FAILURE_BOOL =
                     KEY_PREFIX + "scan_limited_service_after_volte_failure_bool";
 
+        /**
+         * This config defines {@link ImsReasonInfo} code with which the emergency call
+         * shall be retried.
+         *
+         * <p>
+         * If the reason code is one of the following, the emergency call shall be retried
+         * regardless of this configuration.
+         * <ul>
+         * <li>{@link ImsReasonInfo#CODE_LOCAL_CALL_CS_RETRY_REQUIRED}</li>
+         * <li>{@link ImsReasonInfo#CODE_LOCAL_NOT_REGISTERED}</li>
+         * <li>{@link ImsReasonInfo#CODE_SIP_ALTERNATE_EMERGENCY_CALL}</li>
+         * </ul>
+         * <p>
+         *
+         * This config is empty by default.
+         *
+         * @hide
+         */
+        public static final String KEY_IMS_REASONINFO_CODE_TO_RETRY_EMERGENCY_INT_ARRAY =
+                KEY_PREFIX + "ims_reasoninfo_code_to_retry_emergency_int_array";
+
         private static PersistableBundle getDefaults() {
             PersistableBundle defaults = new PersistableBundle();
             defaults.putBoolean(KEY_RETRY_EMERGENCY_ON_IMS_PDN_BOOL, false);
@@ -8085,6 +8106,8 @@
             defaults.putBoolean(KEY_START_QUICK_CROSS_STACK_REDIAL_TIMER_WHEN_REGISTERED_BOOL,
                     true);
             defaults.putBoolean(KEY_SCAN_LIMITED_SERVICE_AFTER_VOLTE_FAILURE_BOOL, false);
+            defaults.putIntArray(KEY_IMS_REASONINFO_CODE_TO_RETRY_EMERGENCY_INT_ARRAY,
+                    new int[0]);
 
             return defaults;
         }
@@ -9836,7 +9859,7 @@
      * An integer key holds the time interval for refreshing or re-querying the satellite
      * entitlement status from the entitlement server to ensure it is the latest.
      *
-     * The default value is 30 days (1 month).
+     * The default value is 7 days.
      */
     @FlaggedApi(Flags.FLAG_CARRIER_ENABLED_SATELLITE_FLAG)
     public static final String KEY_SATELLITE_ENTITLEMENT_STATUS_REFRESH_DAYS_INT =
@@ -9854,6 +9877,19 @@
             "satellite_entitlement_supported_bool";
 
     /**
+     * Indicates the appName that is used when querying the entitlement server for satellite.
+     *
+     * The default value is androidSatmode.
+     *
+     * Reference: GSMA TS.43-v11, 2.8.5 Fast Authentication and Token Management.
+     * `app_name` is an optional attribute in the request and may vary depending on the carrier
+     * requirement.
+     * @hide
+     */
+    public static final String KEY_SATELLITE_ENTITLEMENT_APP_NAME_STRING =
+            "satellite_entitlement_app_name_string";
+
+    /**
      * Indicating whether DUN APN should be disabled when the device is roaming. In that case,
      * the default APN (i.e. internet) will be used for tethering.
      *
@@ -10995,8 +11031,9 @@
                 CellSignalStrengthLte.USE_RSRP);
         sDefaults.putBoolean(KEY_REMOVE_SATELLITE_PLMN_IN_MANUAL_NETWORK_SCAN_BOOL, true);
         sDefaults.putBoolean(KEY_OVERRIDE_WFC_ROAMING_MODE_WHILE_USING_NTN_BOOL, true);
-        sDefaults.putInt(KEY_SATELLITE_ENTITLEMENT_STATUS_REFRESH_DAYS_INT, 30);
+        sDefaults.putInt(KEY_SATELLITE_ENTITLEMENT_STATUS_REFRESH_DAYS_INT, 7);
         sDefaults.putBoolean(KEY_SATELLITE_ENTITLEMENT_SUPPORTED_BOOL, false);
+        sDefaults.putString(KEY_SATELLITE_ENTITLEMENT_APP_NAME_STRING, "androidSatmode");
         sDefaults.putBoolean(KEY_DISABLE_DUN_APN_WHILE_ROAMING_WITH_PRESET_APN_BOOL, false);
         sDefaults.putString(KEY_DEFAULT_PREFERRED_APN_NAME_STRING, "");
         sDefaults.putBoolean(KEY_SUPPORTS_CALL_COMPOSER_BOOL, false);
diff --git a/wifi/tests/Android.bp b/wifi/tests/Android.bp
index 5a0f742..1d3e4bd 100644
--- a/wifi/tests/Android.bp
+++ b/wifi/tests/Android.bp
@@ -39,7 +39,7 @@
         "androidx.test.core",
         "frameworks-base-testutils",
         "guava",
-        "mockito-target-minus-junit4",
+        "mockito-target-extended-minus-junit4",
         "truth",
     ],
 
@@ -48,6 +48,12 @@
         "android.test.base",
     ],
 
+    // Required by Extended Mockito
+    jni_libs: [
+        "libdexmakerjvmtiagent",
+        "libstaticjvmtiagent",
+    ],
+
     test_suites: [
         "general-tests",
     ],
diff --git a/wifi/tests/AndroidManifest.xml b/wifi/tests/AndroidManifest.xml
index 18986fc..66056e5 100644
--- a/wifi/tests/AndroidManifest.xml
+++ b/wifi/tests/AndroidManifest.xml
@@ -18,7 +18,7 @@
 <manifest xmlns:android="http://schemas.android.com/apk/res/android"
      package="android.net.wifi.nonupdatable.test">
 
-    <application>
+    <application android:debuggable="true">
         <uses-library android:name="android.test.runner"/>
         <activity android:label="WifiTestDummyLabel"
              android:name="WifiTestDummyName"