Merge "Mock BubbleStackView in BubbleViewInfoTest" into main
diff --git a/AconfigFlags.bp b/AconfigFlags.bp
index c775012..db90185 100644
--- a/AconfigFlags.bp
+++ b/AconfigFlags.bp
@@ -349,6 +349,7 @@
     name: "android.security.flags-aconfig",
     package: "android.security",
     container: "system",
+    exportable: true,
     srcs: ["core/java/android/security/*.aconfig"],
 }
 
@@ -359,6 +360,13 @@
 }
 
 java_aconfig_library {
+    name: "android.security.flags-aconfig-java-export",
+    aconfig_declarations: "android.security.flags-aconfig",
+    mode: "exported",
+    defaults: ["framework-minus-apex-aconfig-java-defaults"],
+}
+
+java_aconfig_library {
     name: "android.security.flags-aconfig-java-host",
     aconfig_declarations: "android.security.flags-aconfig",
     host_supported: true,
diff --git a/core/api/current.txt b/core/api/current.txt
index aa6615b..46a864e 100644
--- a/core/api/current.txt
+++ b/core/api/current.txt
@@ -32852,6 +32852,7 @@
 
   public static class Build.VERSION_CODES {
     ctor public Build.VERSION_CODES();
+    field @FlaggedApi("android.sdk.major_minor_versioning_scheme") public static final int BAKLAVA = 10000; // 0x2710
     field public static final int BASE = 1; // 0x1
     field public static final int BASE_1_1 = 2; // 0x2
     field public static final int CUPCAKE = 3; // 0x3
@@ -32891,6 +32892,7 @@
   }
 
   @FlaggedApi("android.sdk.major_minor_versioning_scheme") public static class Build.VERSION_CODES_FULL {
+    field public static final int BAKLAVA = 1000000000; // 0x3b9aca00
     field public static final int BASE = 100000; // 0x186a0
     field public static final int BASE_1_1 = 200000; // 0x30d40
     field public static final int CUPCAKE = 300000; // 0x493e0
diff --git a/core/api/system-current.txt b/core/api/system-current.txt
index 1fdb698..389789b 100644
--- a/core/api/system-current.txt
+++ b/core/api/system-current.txt
@@ -209,6 +209,8 @@
     field public static final String MANAGE_FACTORY_RESET_PROTECTION = "android.permission.MANAGE_FACTORY_RESET_PROTECTION";
     field public static final String MANAGE_GAME_ACTIVITY = "android.permission.MANAGE_GAME_ACTIVITY";
     field public static final String MANAGE_GAME_MODE = "android.permission.MANAGE_GAME_MODE";
+    field @FlaggedApi("android.media.tv.flags.media_quality_fw") public static final String MANAGE_GLOBAL_PICTURE_QUALITY_SERVICE = "android.permission.MANAGE_GLOBAL_PICTURE_QUALITY_SERVICE";
+    field @FlaggedApi("android.media.tv.flags.media_quality_fw") public static final String MANAGE_GLOBAL_SOUND_QUALITY_SERVICE = "android.permission.MANAGE_GLOBAL_SOUND_QUALITY_SERVICE";
     field public static final String MANAGE_HOTWORD_DETECTION = "android.permission.MANAGE_HOTWORD_DETECTION";
     field public static final String MANAGE_IPSEC_TUNNELS = "android.permission.MANAGE_IPSEC_TUNNELS";
     field public static final String MANAGE_LOW_POWER_STANDBY = "android.permission.MANAGE_LOW_POWER_STANDBY";
@@ -4637,7 +4639,7 @@
 
   @FlaggedApi("android.content.pm.verification_service") public final class VerificationSession implements android.os.Parcelable {
     method public int describeContents();
-    method @RequiresPermission(android.Manifest.permission.VERIFICATION_AGENT) public long extendTimeRemaining(long);
+    method public long extendTimeRemaining(long);
     method @NonNull public java.util.List<android.content.pm.SharedLibraryInfo> getDeclaredLibraries();
     method @NonNull public android.os.PersistableBundle getExtensionParams();
     method public int getId();
@@ -4645,12 +4647,12 @@
     method @NonNull public String getPackageName();
     method @NonNull public android.content.pm.SigningInfo getSigningInfo();
     method @NonNull public android.net.Uri getStagedPackageUri();
-    method @RequiresPermission(android.Manifest.permission.VERIFICATION_AGENT) public long getTimeoutTime();
+    method public long getTimeoutTime();
     method public int getVerificationPolicy();
-    method @RequiresPermission(android.Manifest.permission.VERIFICATION_AGENT) public void reportVerificationComplete(@NonNull android.content.pm.verify.pkg.VerificationStatus);
-    method @RequiresPermission(android.Manifest.permission.VERIFICATION_AGENT) public void reportVerificationComplete(@NonNull android.content.pm.verify.pkg.VerificationStatus, @NonNull android.os.PersistableBundle);
-    method @RequiresPermission(android.Manifest.permission.VERIFICATION_AGENT) public void reportVerificationIncomplete(int);
-    method @RequiresPermission(android.Manifest.permission.VERIFICATION_AGENT) public boolean setVerificationPolicy(int);
+    method public void reportVerificationComplete(@NonNull android.content.pm.verify.pkg.VerificationStatus);
+    method public void reportVerificationComplete(@NonNull android.content.pm.verify.pkg.VerificationStatus, @NonNull android.os.PersistableBundle);
+    method public void reportVerificationIncomplete(int);
+    method public boolean setVerificationPolicy(int);
     method public void writeToParcel(@NonNull android.os.Parcel, int);
     field @NonNull public static final android.os.Parcelable.Creator<android.content.pm.verify.pkg.VerificationSession> CREATOR;
     field public static final int VERIFICATION_INCOMPLETE_NETWORK_UNAVAILABLE = 1; // 0x1
diff --git a/core/java/android/app/Notification.java b/core/java/android/app/Notification.java
index 64aa705..b7de997c 100644
--- a/core/java/android/app/Notification.java
+++ b/core/java/android/app/Notification.java
@@ -1870,7 +1870,7 @@
          * You can test if a RemoteInput matches these constraints using
          * {@link RemoteInput#isDataOnly}.
          */
-        private static final String EXTRA_DATA_ONLY_INPUTS = "android.extra.DATA_ONLY_INPUTS";
+        static final String EXTRA_DATA_ONLY_INPUTS = "android.extra.DATA_ONLY_INPUTS";
 
         /**
          * No semantic action defined.
@@ -2089,7 +2089,7 @@
          * of non-textual RemoteInputs do not access these remote inputs.
          */
         public RemoteInput[] getDataOnlyRemoteInputs() {
-            return getParcelableArrayFromBundle(mExtras, EXTRA_DATA_ONLY_INPUTS, RemoteInput.class);
+            return mExtras.getParcelableArray(EXTRA_DATA_ONLY_INPUTS, RemoteInput.class);
         }
 
         /**
@@ -2330,8 +2330,8 @@
                 checkContextualActionNullFields();
 
                 ArrayList<RemoteInput> dataOnlyInputs = new ArrayList<>();
-                RemoteInput[] previousDataInputs = getParcelableArrayFromBundle(
-                        mExtras, EXTRA_DATA_ONLY_INPUTS, RemoteInput.class);
+                RemoteInput[] previousDataInputs =
+                        mExtras.getParcelableArray(EXTRA_DATA_ONLY_INPUTS, RemoteInput.class);
                 if (previousDataInputs != null) {
                     for (RemoteInput input : previousDataInputs) {
                         dataOnlyInputs.add(input);
@@ -3091,7 +3091,8 @@
                 visitor.accept(Uri.parse(extras.getString(EXTRA_BACKGROUND_IMAGE_URI)));
             }
 
-            ArrayList<Person> people = extras.getParcelableArrayList(EXTRA_PEOPLE_LIST, android.app.Person.class);
+            ArrayList<Person> people =
+                    extras.getParcelableArrayList(EXTRA_PEOPLE_LIST, android.app.Person.class);
             if (people != null && !people.isEmpty()) {
                 for (Person p : people) {
                     p.visitUris(visitor);
@@ -3117,8 +3118,8 @@
                 person.visitUris(visitor);
             }
 
-            final Parcelable[] messages = extras.getParcelableArray(EXTRA_MESSAGES,
-                    Parcelable.class);
+            final Bundle[] messages = extras.getParcelableArray(EXTRA_MESSAGES,
+                    Bundle.class);
             if (!ArrayUtils.isEmpty(messages)) {
                 for (MessagingStyle.Message message : MessagingStyle.Message
                         .getMessagesFromBundleArray(messages)) {
@@ -3126,8 +3127,8 @@
                 }
             }
 
-            final Parcelable[] historic = extras.getParcelableArray(EXTRA_HISTORIC_MESSAGES,
-                    Parcelable.class);
+            final Bundle[] historic = extras.getParcelableArray(EXTRA_HISTORIC_MESSAGES,
+                    Bundle.class);
             if (!ArrayUtils.isEmpty(historic)) {
                 for (MessagingStyle.Message message : MessagingStyle.Message
                         .getMessagesFromBundleArray(historic)) {
@@ -3838,17 +3839,9 @@
      */
     private void fixDuplicateExtras() {
         if (extras != null) {
-            fixDuplicateExtra(mLargeIcon, EXTRA_LARGE_ICON);
-        }
-    }
-
-    /**
-     * If we find an extra that's exactly the same as one of the "real" fields but refers to a
-     * separate object, replace it with the field's version to avoid holding duplicate copies.
-     */
-    private void fixDuplicateExtra(@Nullable Parcelable original, @NonNull String extraName) {
-        if (original != null && extras.getParcelable(extraName, Parcelable.class) != null) {
-            extras.putParcelable(extraName, original);
+            if (mLargeIcon != null) {
+                extras.putParcelable(EXTRA_LARGE_ICON, mLargeIcon);
+            }
         }
     }
 
@@ -6622,8 +6615,8 @@
                 big.setViewVisibility(R.id.actions_container, View.GONE);
             }
 
-            RemoteInputHistoryItem[] replyText = getParcelableArrayFromBundle(
-                    mN.extras, EXTRA_REMOTE_INPUT_HISTORY_ITEMS, RemoteInputHistoryItem.class);
+            RemoteInputHistoryItem[] replyText = mN.extras.getParcelableArray(
+                    EXTRA_REMOTE_INPUT_HISTORY_ITEMS, RemoteInputHistoryItem.class);
             if (validRemoteInput && replyText != null && replyText.length > 0
                     && !TextUtils.isEmpty(replyText[0].getText())
                     && p.maxRemoteInputHistory > 0) {
@@ -8027,8 +8020,7 @@
      */
     public boolean hasImage() {
         if (isStyle(MessagingStyle.class) && extras != null) {
-            final Parcelable[] messages = extras.getParcelableArray(EXTRA_MESSAGES,
-                    Parcelable.class);
+            final Bundle[] messages = extras.getParcelableArray(EXTRA_MESSAGES, Bundle.class);
             if (!ArrayUtils.isEmpty(messages)) {
                 for (MessagingStyle.Message m : MessagingStyle.Message
                         .getMessagesFromBundleArray(messages)) {
@@ -9348,10 +9340,10 @@
                 mUser = user;
             }
             mConversationTitle = extras.getCharSequence(EXTRA_CONVERSATION_TITLE);
-            Parcelable[] messages = extras.getParcelableArray(EXTRA_MESSAGES, Parcelable.class);
+            Bundle[] messages = extras.getParcelableArray(EXTRA_MESSAGES, Bundle.class);
             mMessages = Message.getMessagesFromBundleArray(messages);
             Parcelable[] histMessages = extras.getParcelableArray(EXTRA_HISTORIC_MESSAGES,
-                    Parcelable.class);
+                    Bundle.class);
             mHistoricMessages = Message.getMessagesFromBundleArray(histMessages);
             mIsGroupConversation = extras.getBoolean(EXTRA_IS_GROUP_CONVERSATION);
             mUnreadMessageCount = extras.getInt(EXTRA_CONVERSATION_UNREAD_MESSAGE_COUNT);
@@ -10218,8 +10210,8 @@
             if (mBuilder.mActions.size() > 0) {
                 maxRows--;
             }
-            RemoteInputHistoryItem[] remoteInputHistory = getParcelableArrayFromBundle(
-                    mBuilder.mN.extras, EXTRA_REMOTE_INPUT_HISTORY_ITEMS,
+            RemoteInputHistoryItem[] remoteInputHistory = mBuilder.mN.extras.getParcelableArray(
+                    EXTRA_REMOTE_INPUT_HISTORY_ITEMS,
                     RemoteInputHistoryItem.class);
             if (remoteInputHistory != null
                     && remoteInputHistory.length > NUMBER_OF_HISTORY_ALLOWED_UNTIL_REDUCTION) {
@@ -11823,28 +11815,42 @@
                         sanitizeProgressColor(indeterminateColor,
                                 backgroundColor, defaultProgressColor));
             } else {
-
                 // Ensure segment color contrasts.
                 final List<Segment> segments = new ArrayList<>();
+                int totalLength = 0;
                 for (Segment segment : mProgressSegments) {
-                    segments.add(sanitizeSegment(segment, backgroundColor,
-                            defaultProgressColor));
+                    final int length = segment.getLength();
+                    if (length <= 0) continue;
+
+                    try {
+                        totalLength += Math.addExact(totalLength, length);
+                        segments.add(sanitizeSegment(segment, backgroundColor,
+                                defaultProgressColor));
+                    } catch (ArithmeticException e) {
+                        totalLength = DEFAULT_PROGRESS_MAX;
+                        segments.clear();
+                        break;
+                    }
                 }
 
                 // Create default segment when no segments are provided.
                 if (segments.isEmpty()) {
-                    segments.add(sanitizeSegment(new Segment(100), backgroundColor,
+                    totalLength = DEFAULT_PROGRESS_MAX;
+                    segments.add(sanitizeSegment(new Segment(totalLength), backgroundColor,
                             defaultProgressColor));
                 }
 
                 // Ensure point color contrasts.
                 final List<Point> points = new ArrayList<>();
                 for (Point point : mProgressPoints) {
+                    final int position = point.getPosition();
+                    if (position < 0 || position > totalLength) continue;
+
                     points.add(sanitizePoint(point, backgroundColor, defaultProgressColor));
                 }
 
                 model = new NotificationProgressModel(segments, points,
-                        mProgress, mIsStyledByProgress);
+                        Math.clamp(mProgress, 0, totalLength), mIsStyledByProgress);
             }
             return model;
         }
@@ -13070,7 +13076,8 @@
         public WearableExtender(Notification notif) {
             Bundle wearableBundle = notif.extras.getBundle(EXTRA_WEARABLE_EXTENSIONS);
             if (wearableBundle != null) {
-                List<Action> actions = wearableBundle.getParcelableArrayList(KEY_ACTIONS, android.app.Notification.Action.class);
+                List<Action> actions = wearableBundle.getParcelableArrayList(
+                        KEY_ACTIONS, Notification.Action.class);
                 if (actions != null) {
                     mActions.addAll(actions);
                 }
@@ -13079,8 +13086,8 @@
                 mDisplayIntent = wearableBundle.getParcelable(
                         KEY_DISPLAY_INTENT, PendingIntent.class);
 
-                Notification[] pages = getParcelableArrayFromBundle(
-                        wearableBundle, KEY_PAGES, Notification.class);
+                Notification[] pages =
+                        wearableBundle.getParcelableArray(KEY_PAGES, Notification.class);
                 if (pages != null) {
                     Collections.addAll(mPages, pages);
                 }
@@ -14015,7 +14022,7 @@
                 if (mParticipants != null && mParticipants.length > 1) {
                     author = mParticipants[0];
                 }
-                Parcelable[] messages = new Parcelable[mMessages.length];
+                Bundle[] messages = new Bundle[mMessages.length];
                 for (int i = 0; i < messages.length; i++) {
                     Bundle m = new Bundle();
                     m.putString(KEY_TEXT, mMessages[i]);
@@ -14037,8 +14044,7 @@
                 if (b == null) {
                     return null;
                 }
-                Parcelable[] parcelableMessages = b.getParcelableArray(KEY_MESSAGES,
-                        Parcelable.class);
+                Bundle[] parcelableMessages = b.getParcelableArray(KEY_MESSAGES, Bundle.class);
                 String[] messages = null;
                 if (parcelableMessages != null) {
                     String[] tmp = new String[parcelableMessages.length];
@@ -14402,27 +14408,6 @@
         }
     }
 
-    /**
-     * Get an array of Parcelable objects from a parcelable array bundle field.
-     * Update the bundle to have a typed array so fetches in the future don't need
-     * to do an array copy.
-     */
-    @Nullable
-    private static <T extends Parcelable> T[] getParcelableArrayFromBundle(
-            Bundle bundle, String key, Class<T> itemClass) {
-        final Parcelable[] array = bundle.getParcelableArray(key, Parcelable.class);
-        final Class<?> arrayClass = Array.newInstance(itemClass, 0).getClass();
-        if (arrayClass.isInstance(array) || array == null) {
-            return (T[]) array;
-        }
-        final T[] typedArray = (T[]) Array.newInstance(itemClass, array.length);
-        for (int i = 0; i < array.length; i++) {
-            typedArray[i] = (T) array[i];
-        }
-        bundle.putParcelableArray(key, typedArray);
-        return typedArray;
-    }
-
     private static class BuilderRemoteViews extends RemoteViews {
         public BuilderRemoteViews(Parcel parcel) {
             super(parcel);
diff --git a/core/java/android/app/NotificationManager.java b/core/java/android/app/NotificationManager.java
index 06bf67c..f2a36e9 100644
--- a/core/java/android/app/NotificationManager.java
+++ b/core/java/android/app/NotificationManager.java
@@ -16,6 +16,8 @@
 
 package android.app;
 
+import static android.service.notification.Flags.notificationClassification;
+
 import android.annotation.CallbackExecutor;
 import android.annotation.FlaggedApi;
 import android.annotation.IntDef;
@@ -752,6 +754,11 @@
         INotificationManager service = getService();
         String pkg = mContext.getPackageName();
 
+        if (notificationClassification()
+                && NotificationChannel.SYSTEM_RESERVED_IDS.contains(notification.getChannelId())) {
+            return;
+        }
+
         try {
             if (localLOGV) Log.v(TAG, pkg + ": notify(" + id + ", " + notification + ")");
             service.enqueueNotificationWithTag(pkg, mContext.getOpPackageName(), tag, id,
@@ -1131,6 +1138,10 @@
      * had before it was deleted.
      */
     public void deleteNotificationChannel(String channelId) {
+        if (notificationClassification()
+                && NotificationChannel.SYSTEM_RESERVED_IDS.contains(channelId)) {
+            return;
+        }
         INotificationManager service = getService();
         try {
             service.deleteNotificationChannel(mContext.getPackageName(), channelId);
diff --git a/core/java/android/app/PropertyInvalidatedCache.java b/core/java/android/app/PropertyInvalidatedCache.java
index c93a6dd..bc9e709 100644
--- a/core/java/android/app/PropertyInvalidatedCache.java
+++ b/core/java/android/app/PropertyInvalidatedCache.java
@@ -30,16 +30,23 @@
 import android.os.Process;
 import android.os.SystemClock;
 import android.os.SystemProperties;
+import android.util.ArrayMap;
 import android.util.Log;
 
 import com.android.internal.annotations.GuardedBy;
+import com.android.internal.annotations.VisibleForTesting;
+import com.android.internal.os.ApplicationSharedMemory;
 import com.android.internal.os.BackgroundThread;
 
+import dalvik.annotation.optimization.CriticalNative;
+import dalvik.annotation.optimization.FastNative;
+
 import java.io.ByteArrayOutputStream;
 import java.io.FileOutputStream;
 import java.io.IOException;
 import java.io.PrintWriter;
 import java.util.ArrayList;
+import java.util.Arrays;
 import java.util.HashSet;
 import java.util.Iterator;
 import java.util.LinkedHashMap;
@@ -203,19 +210,14 @@
     };
 
     /**
-     * Verify that the property name conforms to the standard.  Log a warning if this is not true.
-     * Note that this is done once in the cache constructor; it does not have to be very fast.
+     * Verify that the property name conforms to the standard and throw if this is not true.  Note
+     * that this is done only once for a given property name; it does not have to be very fast.
      */
-    private void validateCacheKey(String name) {
-        if (Build.IS_USER) {
-            // Do not bother checking keys in user builds.  The keys will have been tested in
-            // eng/userdebug builds already.
-            return;
-        }
+    private static void throwIfInvalidCacheKey(String name) {
         for (int i = 0; i < sValidKeyPrefix.length; i++) {
             if (name.startsWith(sValidKeyPrefix[i])) return;
         }
-        Log.w(TAG, "invalid cache name: " + name);
+        throw new IllegalArgumentException("invalid cache name: " + name);
     }
 
     /**
@@ -234,7 +236,8 @@
      * reserved values cause the cache to be skipped.
      */
     // This is the initial value of all cache keys.  It is changed when a cache is invalidated.
-    private static final int NONCE_UNSET = 0;
+    @VisibleForTesting
+    static final int NONCE_UNSET = 0;
     // This value is used in two ways.  First, it is used internally to indicate that the cache is
     // disabled for the current query.  Secondly, it is used to globally disable the cache across
     // the entire system.  Once a cache is disabled, there is no way to enable it again.  The
@@ -685,6 +688,77 @@
     }
 
     /**
+     * Manage nonces that are stored in shared memory.
+     */
+    private static final class NonceSharedMem extends NonceHandler {
+        // The shared memory.
+        private volatile NonceStore mStore;
+
+        // The index of the nonce in shared memory.
+        private volatile int mHandle = NonceStore.INVALID_NONCE_INDEX;
+
+        // True if the string has been stored, ever.
+        private volatile boolean mRecorded = false;
+
+        // A short name that is saved in shared memory.  This is the portion of the property name
+        // that follows the prefix.
+        private final String mShortName;
+
+        NonceSharedMem(@NonNull String name, @Nullable String prefix) {
+            super(name);
+            if ((prefix != null) && name.startsWith(prefix)) {
+                mShortName = name.substring(prefix.length());
+            } else {
+                mShortName = name;
+            }
+        }
+
+        // Fetch the nonce from shared memory.  If the shared memory is not available, return
+        // UNSET.  If the shared memory is available but the nonce name is not known (it may not
+        // have been invalidated by the server yet), return UNSET.
+        @Override
+        long getNonceInternal() {
+            if (mHandle == NonceStore.INVALID_NONCE_INDEX) {
+                if (mStore == null) {
+                    mStore = NonceStore.getInstance();
+                    if (mStore == null) {
+                        return NONCE_UNSET;
+                    }
+                }
+                mHandle = mStore.getHandleForName(mShortName);
+                if (mHandle == NonceStore.INVALID_NONCE_INDEX) {
+                    return NONCE_UNSET;
+                }
+            }
+            return mStore.getNonce(mHandle);
+        }
+
+        // Set the nonce in shared mmory.  If the shared memory is not available, throw an
+        // exception.  Otherwise, if the nonce name has never been recorded, record it now and
+        // fetch the handle for the name.  If the handle cannot be created, throw an exception.
+        @Override
+        void setNonceInternal(long value) {
+            if (mHandle == NonceStore.INVALID_NONCE_INDEX || !mRecorded) {
+                if (mStore == null) {
+                    mStore = NonceStore.getInstance();
+                    if (mStore == null) {
+                        throw new IllegalStateException("setNonce: shared memory not ready");
+                    }
+                }
+                // Always store the name before fetching the handle.  storeName() is idempotent
+                // but does take a little time, so this code calls it just once.
+                mStore.storeName(mShortName);
+                mRecorded = true;
+                mHandle = mStore.getHandleForName(mShortName);
+                if (mHandle == NonceStore.INVALID_NONCE_INDEX) {
+                    throw new IllegalStateException("setNonce: shared memory store failed");
+                }
+            }
+            mStore.setNonce(mHandle, value);
+        }
+    }
+
+    /**
      * SystemProperties and shared storage are protected and cannot be written by random
      * processes.  So, for testing purposes, the NonceLocal handler stores the nonce locally.  The
      * NonceLocal uses the mTestNonce in the superclass, regardless of test mode.
@@ -712,6 +786,7 @@
      * Complete key prefixes.
      */
     private static final String PREFIX_TEST = CACHE_KEY_PREFIX + "." + MODULE_TEST + ".";
+    private static final String PREFIX_SYSTEM = CACHE_KEY_PREFIX + "." + MODULE_SYSTEM + ".";
 
     /**
      * A static list of nonce handlers, indexed by name.  NonceHandlers can be safely shared by
@@ -722,16 +797,32 @@
     private static final ConcurrentHashMap<String, NonceHandler> sHandlers
             = new ConcurrentHashMap<>();
 
+    // True if shared memory is flag-enabled, false otherwise.  Read the flags exactly once.
+    private static final boolean sSharedMemoryAvailable =
+            com.android.internal.os.Flags.applicationSharedMemoryEnabled()
+            && android.app.Flags.picUsesSharedMemory();
+
+    // Return true if this cache can use shared memory for its nonce.  Shared memory may be used
+    // if the module is the system.
+    private static boolean sharedMemoryOkay(@NonNull String name) {
+        return sSharedMemoryAvailable && name.startsWith(PREFIX_SYSTEM);
+    }
+
     /**
-     * Return the proper nonce handler, based on the property name.
+     * Return the proper nonce handler, based on the property name.  A handler is created if
+     * necessary.  Before a handler is created, the name is checked, and an exception is thrown if
+     * the name is not valid.
      */
     private static NonceHandler getNonceHandler(@NonNull String name) {
         NonceHandler h = sHandlers.get(name);
         if (h == null) {
             synchronized (sGlobalLock) {
+                throwIfInvalidCacheKey(name);
                 h = sHandlers.get(name);
                 if (h == null) {
-                    if (name.startsWith(PREFIX_TEST)) {
+                    if (sharedMemoryOkay(name)) {
+                        h = new NonceSharedMem(name, PREFIX_SYSTEM);
+                    } else if (name.startsWith(PREFIX_TEST)) {
                         h = new NonceLocal(name);
                     } else {
                         h = new NonceSysprop(name);
@@ -774,7 +865,6 @@
     public PropertyInvalidatedCache(int maxEntries, @NonNull String propertyName,
             @NonNull String cacheName) {
         mPropertyName = propertyName;
-        validateCacheKey(mPropertyName);
         mCacheName = cacheName;
         mNonce = getNonceHandler(mPropertyName);
         mMaxEntries = maxEntries;
@@ -799,7 +889,6 @@
     public PropertyInvalidatedCache(int maxEntries, @NonNull String module, @NonNull String api,
             @NonNull String cacheName, @NonNull QueryHandler<Query, Result> computer) {
         mPropertyName = createPropertyName(module, api);
-        validateCacheKey(mPropertyName);
         mCacheName = cacheName;
         mNonce = getNonceHandler(mPropertyName);
         mMaxEntries = maxEntries;
@@ -1620,6 +1709,14 @@
         // then only that cache is reported.
         boolean detail = anyDetailed(args);
 
+        if (sSharedMemoryAvailable) {
+            pw.println("  SharedMemory: enabled");
+            NonceStore.getInstance().dump(pw, "    ", detail);
+        } else {
+            pw.println("  SharedMemory: disabled");
+         }
+        pw.println();
+
         ArrayList<PropertyInvalidatedCache> activeCaches = getActiveCaches();
         for (int i = 0; i < activeCaches.size(); i++) {
             PropertyInvalidatedCache currentCache = activeCaches.get(i);
@@ -1654,4 +1751,363 @@
             Log.e(TAG, "Failed to dump PropertyInvalidatedCache instances");
         }
     }
+
+    /**
+     * Nonces in shared memory are supported by a string block that acts as a table of contents
+     * for nonce names, and an array of nonce values.  There are two key design principles with
+     * respect to nonce maps:
+     *
+     * 1. It is always okay if a nonce value cannot be determined.  If the nonce is UNSET, the
+     * cache is bypassed, which is always functionally correct.  Clients do not take extraordinary
+     * measures to be current with the nonce map.  Clients must be current with the nonce itself;
+     * this is achieved through the shared memory.
+     *
+     * 2. Once a name is mapped to a nonce index, the mapping is fixed for the lifetime of the
+     * system.  It is only necessary to distinguish between the unmapped and mapped states.  Once
+     * a client has mapped a nonce, that mapping is known to be good for the lifetime of the
+     * system.
+     * @hide
+     */
+    @VisibleForTesting
+    public static class NonceStore {
+
+        // A lock for the store.
+        private final Object mLock = new Object();
+
+        // The native pointer.  This is not owned by this class.  It is owned by
+        // ApplicationSharedMemory, and it disappears when the owning instance is closed.
+        private final long mPtr;
+
+        // True if the memory is immutable.
+        private final boolean mMutable;
+
+        // The maximum length of a string in the string block.  The maximum length must fit in a
+        // byte, but a smaller value has been chosen to limit memory use.  Because strings are
+        // run-length encoded, a string consumes at most MAX_STRING_LENGTH+1 bytes in the string
+        // block.
+        private static final int MAX_STRING_LENGTH = 63;
+
+        // The raw byte block.  Strings are stored as run-length encoded byte arrays.  The first
+        // byte is the length of the following string.  It is an axiom of the system that the
+        // string block is initially all zeros and that it is write-once memory: new strings are
+        // appended to existing strings, so there is never a need to revisit strings that have
+        // already been pulled from the string block.
+        @GuardedBy("mLock")
+        private final byte[] mStringBlock;
+
+        // The expected hash code of the string block.  If the hash over the string block equals
+        // this value, then the string block is valid.  Otherwise, the block is not valid and
+        // should be re-read.  An invalid block generally means that a client has read the shared
+        // memory while the server was still writing it.
+        @GuardedBy("mLock")
+        private int mBlockHash = 0;
+
+        // The number of nonces that the native layer can hold.  This is maintained for debug and
+        // logging.
+        private final int mMaxNonce;
+
+        /** @hide */
+        @VisibleForTesting
+        public NonceStore(long ptr, boolean mutable) {
+            mPtr = ptr;
+            mMutable = mutable;
+            mStringBlock = new byte[nativeGetMaxByte(ptr)];
+            mMaxNonce = nativeGetMaxNonce(ptr);
+            refreshStringBlockLocked();
+        }
+
+        // The static lock for singleton acquisition.
+        private static Object sLock = new Object();
+
+        // NonceStore is supposed to be a singleton.
+        private static NonceStore sInstance;
+
+        // Return the singleton instance.
+        static NonceStore getInstance() {
+            synchronized (sLock) {
+                if (sInstance == null) {
+                    try {
+                        ApplicationSharedMemory shmem = ApplicationSharedMemory.getInstance();
+                        sInstance = (shmem == null)
+                                    ? null
+                                    : new NonceStore(shmem.getSystemNonceBlock(),
+                                            shmem.isMutable());
+                    } catch (IllegalStateException e) {
+                        // ApplicationSharedMemory.getInstance() throws if the shared memory is
+                        // not yet mapped.  Swallow the exception and leave sInstance null.
+                    }
+                }
+                return sInstance;
+            }
+        }
+
+        // The index value of an unmapped name.
+        public static final int INVALID_NONCE_INDEX = -1;
+
+        // The highest string index extracted from the string block.  -1 means no strings have
+        // been seen.  This is used to skip strings that have already been processed, when the
+        // string block is updated.
+        @GuardedBy("mLock")
+        private int mHighestIndex = -1;
+
+        // The number bytes of the string block that has been used.  This is a statistics.
+        @GuardedBy("mLock")
+        private int mStringBytes = 0;
+
+        // The number of partial reads on the string block.  This is a statistic.
+        @GuardedBy("mLock")
+        private int mPartialReads = 0;
+
+        // The number of times the string block was updated.  This is a statistic.
+        @GuardedBy("mLock")
+        private int mStringUpdated = 0;
+
+        // Map a string to a native index.
+        @GuardedBy("mLock")
+        private final ArrayMap<String, Integer> mStringHandle = new ArrayMap<>();
+
+        // Update the string map from the current string block.  The string block is not modified
+        // and the block hash is not checked.  The function skips past strings that have already
+        // been read, and then processes any new strings.
+        @GuardedBy("mLock")
+        private void updateStringMapLocked() {
+            int index = 0;
+            int offset = 0;
+            while (offset < mStringBlock.length && mStringBlock[offset] != 0) {
+                if (index > mHighestIndex) {
+                    // Only record the string if it has not been seen yet.
+                    final String s = new String(mStringBlock, offset+1, mStringBlock[offset]);
+                    mStringHandle.put(s, index);
+                    mHighestIndex = index;
+                }
+                offset += mStringBlock[offset] + 1;
+                index++;
+            }
+            mStringBytes = offset;
+        }
+
+        // Append a string to the string block and update the hash.  This does not write the block
+        // to shared memory.
+        @GuardedBy("mLock")
+        private void appendStringToMapLocked(@NonNull String str) {
+            int offset = 0;
+            while (offset < mStringBlock.length && mStringBlock[offset] != 0) {
+                offset += mStringBlock[offset] + 1;
+            }
+            final byte[] strBytes = str.getBytes();
+
+            if (offset + strBytes.length >= mStringBlock.length) {
+                // Overflow.  Do not add the string to the block; the string will remain undefined.
+                return;
+            }
+
+            mStringBlock[offset] = (byte) strBytes.length;
+            offset++;
+            for (int i = 0; i < strBytes.length; i++, offset++) {
+                mStringBlock[offset] = strBytes[i];
+            }
+            mBlockHash = Arrays.hashCode(mStringBlock);
+        }
+
+        // Possibly update the string block.  If the native shared memory has a new block hash,
+        // then read the new string block values from shared memory, as well as the new hash.
+        @GuardedBy("mLock")
+        private void refreshStringBlockLocked() {
+            if (mBlockHash == nativeGetByteBlockHash(mPtr)) {
+                // The fastest way to know that the shared memory string block has not changed.
+                return;
+            }
+            final int hash = nativeGetByteBlock(mPtr, mBlockHash, mStringBlock);
+            if (hash != Arrays.hashCode(mStringBlock)) {
+                // This is a partial read: ignore it.  The next time someone needs this string
+                // the memory will be read again and should succeed.  Set the local hash to
+                // zero to ensure that the next read attempt will actually read from shared
+                // memory.
+                mBlockHash = 0;
+                mPartialReads++;
+                return;
+            }
+            // The hash has changed.  Update the strings from the byte block.
+            mStringUpdated++;
+            mBlockHash = hash;
+            updateStringMapLocked();
+        }
+
+        // Throw an exception if the string cannot be stored in the string block.
+        private static void throwIfBadString(@NonNull String s) {
+            if (s.length() == 0) {
+                throw new IllegalArgumentException("cannot store an empty string");
+            }
+            if (s.length() > MAX_STRING_LENGTH) {
+                throw new IllegalArgumentException("cannot store a string longer than "
+                        + MAX_STRING_LENGTH);
+            }
+        }
+
+        // Throw an exception if the nonce handle is invalid.  The handle is bad if it is out of
+        // range of allocated handles.  Note that NONCE_HANDLE_INVALID will throw: this is
+        // important for setNonce().
+        @GuardedBy("mLock")
+        private void throwIfBadHandle(int handle) {
+            if (handle < 0 || handle > mHighestIndex) {
+                throw new IllegalArgumentException("invalid nonce handle: " + handle);
+            }
+        }
+
+        // Throw if the memory is immutable (the process does not have write permission).  The
+        // exception mimics the permission-denied exception thrown when a process writes to an
+        // unauthorized system property.
+        private void throwIfImmutable() {
+            if (!mMutable) {
+                throw new RuntimeException("write permission denied");
+            }
+        }
+
+        // Add a string to the local copy of the block and write the block to shared memory.
+        // Return the index of the new string.  If the string has already been recorded, the
+        // shared memory is not updated but the index of the existing string is returned.
+        public int storeName(@NonNull String str) {
+            synchronized (mLock) {
+                Integer handle = mStringHandle.get(str);
+                if (handle == null) {
+                    throwIfImmutable();
+                    throwIfBadString(str);
+                    appendStringToMapLocked(str);
+                    nativeSetByteBlock(mPtr, mBlockHash, mStringBlock);
+                    updateStringMapLocked();
+                    handle = mStringHandle.get(str);
+                }
+                return handle;
+            }
+        }
+
+        // Retrieve the handle for a string.  -1 is returned if the string is not found.
+        public int getHandleForName(@NonNull String str) {
+            synchronized (mLock) {
+                Integer handle = mStringHandle.get(str);
+                if (handle == null) {
+                    refreshStringBlockLocked();
+                    handle  = mStringHandle.get(str);
+                }
+                return (handle != null) ? handle : INVALID_NONCE_INDEX;
+            }
+        }
+
+        // Thin wrapper around the native method.
+        public boolean setNonce(int handle, long value) {
+            synchronized (mLock) {
+                throwIfBadHandle(handle);
+                throwIfImmutable();
+                return nativeSetNonce(mPtr, handle, value);
+            }
+        }
+
+        public long getNonce(int handle) {
+            synchronized (mLock) {
+                throwIfBadHandle(handle);
+                return nativeGetNonce(mPtr, handle);
+            }
+        }
+
+        /**
+         * Dump the nonce statistics
+         */
+        public void dump(@NonNull PrintWriter pw, @NonNull String prefix, boolean detailed) {
+            synchronized (mLock) {
+                pw.println(formatSimple(
+                    "%sStringsMapped: %d, BytesUsed: %d",
+                    prefix, mHighestIndex, mStringBytes));
+                pw.println(formatSimple(
+                    "%sPartialReads: %d, StringUpdates: %d",
+                    prefix, mPartialReads, mStringUpdated));
+
+                if (detailed) {
+                    for (String s: mStringHandle.keySet()) {
+                        int h = mStringHandle.get(s);
+                        pw.println(formatSimple(
+                            "%sHandle:%d Name:%s", prefix, h, s));
+                    }
+                }
+            }
+        }
+    }
+
+    /**
+     * Return the maximum number of nonces supported in the native layer.
+     *
+     * @param mPtr the pointer to the native shared memory.
+     * @return the number of nonces supported by the shared memory.
+     */
+    private static native int nativeGetMaxNonce(long mPtr);
+
+    /**
+     * Return the maximum number of string bytes supported in the native layer.
+     *
+     * @param mPtr the pointer to the native shared memory.
+     * @return the number of string bytes supported by the shared memory.
+     */
+    private static native int nativeGetMaxByte(long mPtr);
+
+    /**
+     * Write the byte block and set the hash into shared memory.  The method is relatively
+     * forgiving, in that any non-null byte array will be stored without error.  The number of
+     * bytes will the lesser of the length of the block parameter and the size of the native
+     * array.  The native layer performs no checks on either byte block or the hash.
+     *
+     * @param mPtr the pointer to the native shared memory.
+     * @param hash a value to be stored in the native block hash.
+     * @param block the byte array to be store.
+     */
+    @FastNative
+    private static native void nativeSetByteBlock(long mPtr, int hash, @NonNull byte[] block);
+
+    /**
+     * Retrieve the string block into the array and return the hash value.  If the incoming hash
+     * value is the same as the hash in shared memory, the native function returns immediately
+     * without touching the block parameter.  Note that a zero hash value will always cause shared
+     * memory to be read.  The number of bytes read is the lesser of the length of the block
+     * parameter and the size of the native array.
+     *
+     * @param mPtr the pointer to the native shared memory.
+     * @param hash a value to be compared against the hash in the native layer.
+     * @param block an array to receive the bytes from the native layer.
+     * @return the hash from the native layer.
+     */
+    @FastNative
+    private static native int nativeGetByteBlock(long mPtr, int hash, @NonNull byte[] block);
+
+    /**
+     * Retrieve just the byte block hash from the native layer.  The function is CriticalNative
+     * and thus very fast.
+     *
+     * @param mPtr the pointer to the native shared memory.
+     * @return the current native hash value.
+     */
+    @CriticalNative
+    private static native int nativeGetByteBlockHash(long mPtr);
+
+    /**
+     * Set a nonce at the specified index.  The index is checked against the size of the native
+     * nonce array and the function returns true if the index is valid, and false.  The function
+     * is CriticalNative and thus very fast.
+     *
+     * @param mPtr the pointer to the native shared memory.
+     * @param index the index of the nonce to set.
+     * @param value the value to set for the nonce.
+     * @return true if the index is inside the nonce array and false otherwise.
+     */
+    @CriticalNative
+    private static native boolean nativeSetNonce(long mPtr, int index, long value);
+
+    /**
+     * Get the nonce from the specified index.  The index is checked against the size of the
+     * native nonce array; the function returns the nonce value if the index is valid, and 0
+     * otherwise.  The function is CriticalNative and thus very fast.
+     *
+     * @param mPtr the pointer to the native shared memory.
+     * @param index the index of the nonce to retrieve.
+     * @return the value of the specified nonce, of 0 if the index is out of bounds.
+     */
+    @CriticalNative
+    private static native long nativeGetNonce(long mPtr, int index);
 }
diff --git a/core/java/android/app/SystemServiceRegistry.java b/core/java/android/app/SystemServiceRegistry.java
index bd26db5..c6b8f3b 100644
--- a/core/java/android/app/SystemServiceRegistry.java
+++ b/core/java/android/app/SystemServiceRegistry.java
@@ -1750,10 +1750,13 @@
                         @Override
                         public AdvancedProtectionManager createService(ContextImpl ctx)
                                 throws ServiceNotFoundException {
-                            IBinder iBinder = ServiceManager.getServiceOrThrow(
+                            IBinder iBinder = ServiceManager.getService(
                                     Context.ADVANCED_PROTECTION_SERVICE);
                             IAdvancedProtectionService service =
                                     IAdvancedProtectionService.Stub.asInterface(iBinder);
+                            if (service == null) {
+                                return null;
+                            }
                             return new AdvancedProtectionManager(service);
                         }
                     });
diff --git a/core/java/android/app/performance.aconfig b/core/java/android/app/performance.aconfig
new file mode 100644
index 0000000..7c6989e
--- /dev/null
+++ b/core/java/android/app/performance.aconfig
@@ -0,0 +1,11 @@
+package: "android.app"
+container: "system"
+
+flag {
+     namespace: "system_performance"
+     name: "pic_uses_shared_memory"
+     is_exported: true
+     is_fixed_read_only: true
+     description: "PropertyInvalidatedCache uses shared memory for nonces."
+     bug: "366552454"
+}
diff --git a/core/java/android/content/Intent.java b/core/java/android/content/Intent.java
index e8cec70..66ef004 100644
--- a/core/java/android/content/Intent.java
+++ b/core/java/android/content/Intent.java
@@ -11672,6 +11672,7 @@
                 Log.w(TAG, "Failure filling in extras", e);
             }
         }
+        mCreatorTokenInfo = other.mCreatorTokenInfo;
         if (mayHaveCopiedUris && mContentUserHint == UserHandle.USER_CURRENT
                 && other.mContentUserHint != UserHandle.USER_CURRENT) {
             mContentUserHint = other.mContentUserHint;
@@ -12225,6 +12226,13 @@
     }
 
     /** @hide */
+    public void removeCreatorToken() {
+        if (mCreatorTokenInfo != null) {
+            mCreatorTokenInfo.mCreatorToken = null;
+        }
+    }
+
+    /** @hide */
     public @Nullable IBinder getCreatorToken() {
         return mCreatorTokenInfo == null ? null : mCreatorTokenInfo.mCreatorToken;
     }
@@ -12251,7 +12259,7 @@
     public void collectExtraIntentKeys() {
         if (!isPreventIntentRedirectEnabled()) return;
 
-        if (mExtras != null && !mExtras.isParcelled() && !mExtras.isEmpty()) {
+        if (mExtras != null && !mExtras.isEmpty()) {
             for (String key : mExtras.keySet()) {
                 if (mExtras.get(key) instanceof Intent) {
                     if (mCreatorTokenInfo == null) {
@@ -12833,6 +12841,8 @@
     private boolean isImageCaptureIntent() {
         return (MediaStore.ACTION_IMAGE_CAPTURE.equals(mAction)
                 || MediaStore.ACTION_IMAGE_CAPTURE_SECURE.equals(mAction)
+                || MediaStore.ACTION_MOTION_PHOTO_CAPTURE.equals(mAction)
+                || MediaStore.ACTION_MOTION_PHOTO_CAPTURE_SECURE.equals(mAction)
                 || MediaStore.ACTION_VIDEO_CAPTURE.equals(mAction));
     }
 
diff --git a/core/java/android/content/pm/IPackageInstaller.aidl b/core/java/android/content/pm/IPackageInstaller.aidl
index c911326..ecea479 100644
--- a/core/java/android/content/pm/IPackageInstaller.aidl
+++ b/core/java/android/content/pm/IPackageInstaller.aidl
@@ -94,9 +94,9 @@
     @JavaPassthrough(annotation="@android.annotation.RequiresPermission(anyOf={android.Manifest.permission.INSTALL_PACKAGES,android.Manifest.permission.REQUEST_INSTALL_PACKAGES})")
     void reportUnarchivalStatus(int unarchiveId, int status, long requiredStorageBytes, in PendingIntent userActionIntent, in UserHandle userHandle);
 
-    @JavaPassthrough(annotation="@android.annotation.RequiresPermission(android.Manifest.permission.VERIFICATION_AGENT)")
+    @EnforcePermission("VERIFICATION_AGENT")
     int getVerificationPolicy();
 
-    @JavaPassthrough(annotation="@android.annotation.RequiresPermission(android.Manifest.permission.VERIFICATION_AGENT)")
+    @EnforcePermission("VERIFICATION_AGENT")
     boolean setVerificationPolicy(int policy);
 }
diff --git a/core/java/android/content/pm/flags.aconfig b/core/java/android/content/pm/flags.aconfig
index 5f439b1..6f70586 100644
--- a/core/java/android/content/pm/flags.aconfig
+++ b/core/java/android/content/pm/flags.aconfig
@@ -297,14 +297,6 @@
 }
 
 flag {
-    name: "get_packages_from_launcher_apps"
-    namespace: "package_manager_service"
-    description: "Feature flag to provide the new methods within launcher apps class to get packages."
-    bug: "363324203"
-    is_fixed_read_only: true
-}
-
-flag {
     name: "remove_cross_user_permission_hack"
     namespace: "package_manager_service"
     description: "Feature flag to remove hack code of using PackageManager.MATCH_ANY_USER flag without cross user permission."
@@ -328,6 +320,13 @@
 }
 
 flag {
+    name: "sdk_dependency_installer"
+    namespace: "package_manager_service"
+    description: "Feature flag to enable installation of missing sdk dependency of app"
+    bug: "370822870"
+}
+
+flag {
     name: "include_feature_flags_in_package_cacher"
     namespace: "package_manager_service"
     description: "Include feature flag status when determining hits or misses in PackageCacher."
diff --git a/core/java/android/content/pm/parsing/ApkLite.java b/core/java/android/content/pm/parsing/ApkLite.java
index 19a13db..4220590 100644
--- a/core/java/android/content/pm/parsing/ApkLite.java
+++ b/core/java/android/content/pm/parsing/ApkLite.java
@@ -28,6 +28,7 @@
 import com.android.internal.util.CollectionUtils;
 import com.android.internal.util.DataClass;
 
+import java.util.Collections;
 import java.util.List;
 import java.util.Set;
 
@@ -141,6 +142,21 @@
     private final boolean mIsSdkLibrary;
 
     /**
+     * List of SDK names used by this apk.
+     */
+    private final @NonNull List<String> mUsesSdkLibraries;
+
+    /**
+     * List of SDK major versions used by this apk.
+     */
+    private final @Nullable long[] mUsesSdkLibrariesVersionsMajor;
+
+    /**
+     * List of SDK certificates used by this apk.
+     */
+    private final @Nullable String[][] mUsesSdkLibrariesCertDigests;
+
+    /**
      * Indicates if this system app can be updated.
      */
     private final boolean mUpdatableSystem;
@@ -167,7 +183,9 @@
             String requiredSystemPropertyName, String requiredSystemPropertyValue,
             int minSdkVersion, int targetSdkVersion, int rollbackDataPolicy,
             Set<String> requiredSplitTypes, Set<String> splitTypes,
-            boolean hasDeviceAdminReceiver, boolean isSdkLibrary, boolean updatableSystem,
+            boolean hasDeviceAdminReceiver, boolean isSdkLibrary,
+            List<String> usesSdkLibraries, long[] usesSdkLibrariesVersionsMajor,
+            String[][] usesSdkLibrariesCertDigests, boolean updatableSystem,
             String emergencyInstaller, List<SharedLibraryInfo> declaredLibraries) {
         mPath = path;
         mPackageName = packageName;
@@ -202,6 +220,9 @@
         mRollbackDataPolicy = rollbackDataPolicy;
         mHasDeviceAdminReceiver = hasDeviceAdminReceiver;
         mIsSdkLibrary = isSdkLibrary;
+        mUsesSdkLibraries = usesSdkLibraries;
+        mUsesSdkLibrariesVersionsMajor = usesSdkLibrariesVersionsMajor;
+        mUsesSdkLibrariesCertDigests = usesSdkLibrariesCertDigests;
         mUpdatableSystem = updatableSystem;
         mEmergencyInstaller = emergencyInstaller;
         mArchivedPackage = null;
@@ -242,6 +263,9 @@
         mRollbackDataPolicy = 0;
         mHasDeviceAdminReceiver = false;
         mIsSdkLibrary = false;
+        mUsesSdkLibraries = Collections.emptyList();
+        mUsesSdkLibrariesVersionsMajor = null;
+        mUsesSdkLibrariesCertDigests = null;
         mUpdatableSystem = true;
         mEmergencyInstaller = null;
         mArchivedPackage = archivedPackage;
@@ -555,6 +579,30 @@
     }
 
     /**
+     * List of SDK names used by this apk.
+     */
+    @DataClass.Generated.Member
+    public @NonNull List<String> getUsesSdkLibraries() {
+        return mUsesSdkLibraries;
+    }
+
+    /**
+     * List of SDK major versions used by this apk.
+     */
+    @DataClass.Generated.Member
+    public @Nullable long[] getUsesSdkLibrariesVersionsMajor() {
+        return mUsesSdkLibrariesVersionsMajor;
+    }
+
+    /**
+     * List of SDK certificates used by this apk.
+     */
+    @DataClass.Generated.Member
+    public @Nullable String[][] getUsesSdkLibrariesCertDigests() {
+        return mUsesSdkLibrariesCertDigests;
+    }
+
+    /**
      * Indicates if this system app can be updated.
      */
     @DataClass.Generated.Member
@@ -584,10 +632,10 @@
     }
 
     @DataClass.Generated(
-            time = 1728333566322L,
+            time = 1729247366948L,
             codegenVersion = "1.0.23",
             sourceFile = "frameworks/base/core/java/android/content/pm/parsing/ApkLite.java",
-            inputSignatures = "private final @android.annotation.NonNull java.lang.String mPackageName\nprivate final @android.annotation.NonNull java.lang.String mPath\nprivate final @android.annotation.Nullable java.lang.String mSplitName\nprivate final @android.annotation.Nullable java.lang.String mUsesSplitName\nprivate final @android.annotation.Nullable java.lang.String mConfigForSplit\nprivate final @android.annotation.Nullable java.util.Set<java.lang.String> mRequiredSplitTypes\nprivate final @android.annotation.Nullable java.util.Set<java.lang.String> mSplitTypes\nprivate final  int mVersionCodeMajor\nprivate final  int mVersionCode\nprivate final  int mRevisionCode\nprivate final  int mInstallLocation\nprivate final  int mMinSdkVersion\nprivate final  int mTargetSdkVersion\nprivate final @android.annotation.NonNull android.content.pm.VerifierInfo[] mVerifiers\nprivate final @android.annotation.NonNull android.content.pm.SigningDetails mSigningDetails\nprivate final  boolean mFeatureSplit\nprivate final  boolean mIsolatedSplits\nprivate final  boolean mSplitRequired\nprivate final  boolean mCoreApp\nprivate final  boolean mDebuggable\nprivate final  boolean mProfileableByShell\nprivate final  boolean mMultiArch\nprivate final  boolean mUse32bitAbi\nprivate final  boolean mExtractNativeLibs\nprivate final  boolean mUseEmbeddedDex\nprivate final @android.annotation.Nullable java.lang.String mTargetPackageName\nprivate final  boolean mOverlayIsStatic\nprivate final  int mOverlayPriority\nprivate final @android.annotation.Nullable java.lang.String mRequiredSystemPropertyName\nprivate final @android.annotation.Nullable java.lang.String mRequiredSystemPropertyValue\nprivate final  int mRollbackDataPolicy\nprivate final  boolean mHasDeviceAdminReceiver\nprivate final  boolean mIsSdkLibrary\nprivate final  boolean mUpdatableSystem\nprivate final @android.annotation.Nullable java.lang.String mEmergencyInstaller\nprivate final @android.annotation.NonNull java.util.List<android.content.pm.SharedLibraryInfo> mDeclaredLibraries\nprivate final @android.annotation.Nullable android.content.pm.ArchivedPackageParcel mArchivedPackage\npublic  long getLongVersionCode()\nprivate  boolean hasAnyRequiredSplitTypes()\nclass ApkLite extends java.lang.Object implements []\n@com.android.internal.util.DataClass(genConstructor=false, genConstDefs=false)")
+            inputSignatures = "private final @android.annotation.NonNull java.lang.String mPackageName\nprivate final @android.annotation.NonNull java.lang.String mPath\nprivate final @android.annotation.Nullable java.lang.String mSplitName\nprivate final @android.annotation.Nullable java.lang.String mUsesSplitName\nprivate final @android.annotation.Nullable java.lang.String mConfigForSplit\nprivate final @android.annotation.Nullable java.util.Set<java.lang.String> mRequiredSplitTypes\nprivate final @android.annotation.Nullable java.util.Set<java.lang.String> mSplitTypes\nprivate final  int mVersionCodeMajor\nprivate final  int mVersionCode\nprivate final  int mRevisionCode\nprivate final  int mInstallLocation\nprivate final  int mMinSdkVersion\nprivate final  int mTargetSdkVersion\nprivate final @android.annotation.NonNull android.content.pm.VerifierInfo[] mVerifiers\nprivate final @android.annotation.NonNull android.content.pm.SigningDetails mSigningDetails\nprivate final  boolean mFeatureSplit\nprivate final  boolean mIsolatedSplits\nprivate final  boolean mSplitRequired\nprivate final  boolean mCoreApp\nprivate final  boolean mDebuggable\nprivate final  boolean mProfileableByShell\nprivate final  boolean mMultiArch\nprivate final  boolean mUse32bitAbi\nprivate final  boolean mExtractNativeLibs\nprivate final  boolean mUseEmbeddedDex\nprivate final @android.annotation.Nullable java.lang.String mTargetPackageName\nprivate final  boolean mOverlayIsStatic\nprivate final  int mOverlayPriority\nprivate final @android.annotation.Nullable java.lang.String mRequiredSystemPropertyName\nprivate final @android.annotation.Nullable java.lang.String mRequiredSystemPropertyValue\nprivate final  int mRollbackDataPolicy\nprivate final  boolean mHasDeviceAdminReceiver\nprivate final  boolean mIsSdkLibrary\nprivate final @android.annotation.NonNull java.util.List<java.lang.String> mUsesSdkLibraries\nprivate final @android.annotation.Nullable long[] mUsesSdkLibrariesVersionsMajor\nprivate final @android.annotation.Nullable java.lang.String[][] mUsesSdkLibrariesCertDigests\nprivate final  boolean mUpdatableSystem\nprivate final @android.annotation.Nullable java.lang.String mEmergencyInstaller\nprivate final @android.annotation.NonNull java.util.List<android.content.pm.SharedLibraryInfo> mDeclaredLibraries\nprivate final @android.annotation.Nullable android.content.pm.ArchivedPackageParcel mArchivedPackage\npublic  long getLongVersionCode()\nprivate  boolean hasAnyRequiredSplitTypes()\nclass ApkLite extends java.lang.Object implements []\n@com.android.internal.util.DataClass(genConstructor=false, genConstDefs=false)")
     @Deprecated
     private void __metadata() {}
 
diff --git a/core/java/android/content/pm/parsing/ApkLiteParseUtils.java b/core/java/android/content/pm/parsing/ApkLiteParseUtils.java
index 1a7f628..50d8758 100644
--- a/core/java/android/content/pm/parsing/ApkLiteParseUtils.java
+++ b/core/java/android/content/pm/parsing/ApkLiteParseUtils.java
@@ -32,6 +32,7 @@
 import android.content.res.ApkAssets;
 import android.content.res.XmlResourceParser;
 import android.os.Build;
+import android.os.SystemProperties;
 import android.os.Trace;
 import android.text.TextUtils;
 import android.util.ArrayMap;
@@ -44,6 +45,7 @@
 import com.android.internal.util.ArrayUtils;
 
 import libcore.io.IoUtils;
+import libcore.util.HexEncoding;
 
 import org.xmlpull.v1.XmlPullParser;
 import org.xmlpull.v1.XmlPullParserException;
@@ -88,6 +90,7 @@
     private static final String TAG_USES_SDK = "uses-sdk";
     private static final String TAG_USES_SPLIT = "uses-split";
     private static final String TAG_MANIFEST = "manifest";
+    private static final String TAG_USES_SDK_LIBRARY = "uses-sdk-library";
     private static final String TAG_SDK_LIBRARY = "sdk-library";
     private static final int SDK_VERSION = Build.VERSION.SDK_INT;
     private static final String[] SDK_CODENAMES = Build.VERSION.ACTIVE_CODENAMES;
@@ -460,6 +463,9 @@
         boolean hasDeviceAdminReceiver = false;
 
         boolean isSdkLibrary = false;
+        List<String> usesSdkLibraries = new ArrayList<>();
+        long[] usesSdkLibrariesVersionsMajor = new long[0];
+        String[][] usesSdkLibrariesCertDigests = new String[0][0];
         List<SharedLibraryInfo> declaredLibraries = new ArrayList<>();
 
         // Only search the tree when the tag is the direct child of <manifest> tag
@@ -523,6 +529,57 @@
                             hasDeviceAdminReceiver |= isDeviceAdminReceiver(parser,
                                     hasBindDeviceAdminPermission);
                             break;
+                        case TAG_USES_SDK_LIBRARY:
+                            String usesSdkLibName = parser.getAttributeValue(
+                                    ANDROID_RES_NAMESPACE, "name");
+                            long usesSdkLibVersionMajor = parser.getAttributeIntValue(
+                                    ANDROID_RES_NAMESPACE, "versionMajor", -1);
+                            String usesSdkCertDigest = parser.getAttributeValue(
+                                     ANDROID_RES_NAMESPACE, "certDigest");
+
+                            if (usesSdkLibName == null || usesSdkLibName.isBlank()
+                                    || usesSdkLibVersionMajor < 0) {
+                                return input.error(
+                                        PackageManager.INSTALL_PARSE_FAILED_MANIFEST_MALFORMED,
+                                        "Bad uses-sdk-library declaration name: "
+                                                + usesSdkLibName
+                                                + " version: " + usesSdkLibVersionMajor);
+                            }
+
+                            if (usesSdkLibraries.contains(usesSdkLibName)) {
+                                return input.error(
+                                        PackageManager.INSTALL_PARSE_FAILED_MANIFEST_MALFORMED,
+                                        "Bad uses-sdk-library declaration. Depending on"
+                                                + " multiple versions of SDK library: "
+                                                + usesSdkLibName);
+                            }
+
+                            usesSdkLibraries.add(usesSdkLibName);
+                            usesSdkLibrariesVersionsMajor = ArrayUtils.appendLong(
+                                    usesSdkLibrariesVersionsMajor, usesSdkLibVersionMajor,
+                                    /*allowDuplicates=*/ true);
+
+                            // We allow ":" delimiters in the SHA declaration as this is the format
+                            // emitted by the certtool making it easy for developers to copy/paste.
+                            // TODO(372862145): Add test for this replacement
+                            usesSdkCertDigest = usesSdkCertDigest.replace(":", "").toLowerCase();
+
+                            if ("".equals(usesSdkCertDigest)) {
+                                // Test-only uses-sdk-library empty certificate digest override.
+                                usesSdkCertDigest = SystemProperties.get(
+                                        "debug.pm.uses_sdk_library_default_cert_digest", "");
+                                // Validate the overridden digest.
+                                try {
+                                    HexEncoding.decode(usesSdkCertDigest, false);
+                                } catch (IllegalArgumentException e) {
+                                    usesSdkCertDigest = "";
+                                }
+                            }
+                            // TODO(372862145): Add support for multiple signer
+                            usesSdkLibrariesCertDigests = ArrayUtils.appendElement(String[].class,
+                                    usesSdkLibrariesCertDigests, new String[]{usesSdkCertDigest},
+                                    /*allowDuplicates=*/ true);
+                            break;
                         case TAG_SDK_LIBRARY:
                             isSdkLibrary = true;
                             // Mirrors ParsingPackageUtils#parseSdkLibrary until lite and full
@@ -534,7 +591,7 @@
                             if (sdkLibName == null || sdkLibVersionMajor < 0) {
                                 return input.error(
                                         PackageManager.INSTALL_PARSE_FAILED_MANIFEST_MALFORMED,
-                                        "Bad uses-sdk-library declaration name: " + sdkLibName
+                                        "Bad sdk-library declaration name: " + sdkLibName
                                                 + " version: " + sdkLibVersionMajor);
                             }
                             declaredLibraries.add(new SharedLibraryInfo(
@@ -694,8 +751,9 @@
                         overlayIsStatic, overlayPriority, requiredSystemPropertyName,
                         requiredSystemPropertyValue, minSdkVersion, targetSdkVersion,
                         rollbackDataPolicy, requiredSplitTypes.first, requiredSplitTypes.second,
-                        hasDeviceAdminReceiver, isSdkLibrary, updatableSystem, emergencyInstaller,
-                        declaredLibraries));
+                        hasDeviceAdminReceiver, isSdkLibrary, usesSdkLibraries,
+                        usesSdkLibrariesVersionsMajor, usesSdkLibrariesCertDigests,
+                        updatableSystem, emergencyInstaller, declaredLibraries));
     }
 
     private static boolean isDeviceAdminReceiver(
diff --git a/core/java/android/content/pm/parsing/PackageLite.java b/core/java/android/content/pm/parsing/PackageLite.java
index 9a2ee7f..79c5973 100644
--- a/core/java/android/content/pm/parsing/PackageLite.java
+++ b/core/java/android/content/pm/parsing/PackageLite.java
@@ -115,6 +115,12 @@
      */
     private final boolean mIsSdkLibrary;
 
+    private final @NonNull List<String> mUsesSdkLibraries;
+
+    private final @Nullable long[] mUsesSdkLibrariesVersionsMajor;
+
+    private final @Nullable String[][] mUsesSdkLibrariesCertDigests;
+
     private final @NonNull List<SharedLibraryInfo> mDeclaredLibraries;
 
     /**
@@ -149,6 +155,9 @@
         mSplitRequired = (baseApk.isSplitRequired() || hasAnyRequiredSplitTypes());
         mProfileableByShell = baseApk.isProfileableByShell();
         mIsSdkLibrary = baseApk.isIsSdkLibrary();
+        mUsesSdkLibraries = baseApk.getUsesSdkLibraries();
+        mUsesSdkLibrariesVersionsMajor = baseApk.getUsesSdkLibrariesVersionsMajor();
+        mUsesSdkLibrariesCertDigests = baseApk.getUsesSdkLibrariesCertDigests();
         mSplitNames = splitNames;
         mSplitTypes = splitTypes;
         mIsFeatureSplits = isFeatureSplits;
@@ -438,6 +447,21 @@
     }
 
     @DataClass.Generated.Member
+    public @NonNull List<String> getUsesSdkLibraries() {
+        return mUsesSdkLibraries;
+    }
+
+    @DataClass.Generated.Member
+    public @Nullable long[] getUsesSdkLibrariesVersionsMajor() {
+        return mUsesSdkLibrariesVersionsMajor;
+    }
+
+    @DataClass.Generated.Member
+    public @Nullable String[][] getUsesSdkLibrariesCertDigests() {
+        return mUsesSdkLibrariesCertDigests;
+    }
+
+    @DataClass.Generated.Member
     public @NonNull List<SharedLibraryInfo> getDeclaredLibraries() {
         return mDeclaredLibraries;
     }
@@ -451,10 +475,10 @@
     }
 
     @DataClass.Generated(
-            time = 1728333569917L,
+            time = 1729248757933L,
             codegenVersion = "1.0.23",
             sourceFile = "frameworks/base/core/java/android/content/pm/parsing/PackageLite.java",
-            inputSignatures = "private final @android.annotation.NonNull java.lang.String mPackageName\nprivate final @android.annotation.NonNull java.lang.String mPath\nprivate final @android.annotation.NonNull java.lang.String mBaseApkPath\nprivate final @android.annotation.Nullable java.lang.String[] mSplitApkPaths\nprivate final @android.annotation.Nullable java.lang.String[] mSplitNames\nprivate final @android.annotation.Nullable java.lang.String[] mUsesSplitNames\nprivate final @android.annotation.Nullable java.lang.String[] mConfigForSplit\nprivate final @android.annotation.Nullable java.util.Set<java.lang.String> mBaseRequiredSplitTypes\nprivate final @android.annotation.Nullable java.util.Set<java.lang.String>[] mRequiredSplitTypes\nprivate final @android.annotation.Nullable java.util.Set<java.lang.String>[] mSplitTypes\nprivate final  int mVersionCodeMajor\nprivate final  int mVersionCode\nprivate final  int mTargetSdk\nprivate final  int mBaseRevisionCode\nprivate final @android.annotation.Nullable int[] mSplitRevisionCodes\nprivate final  int mInstallLocation\nprivate final @android.annotation.NonNull android.content.pm.VerifierInfo[] mVerifiers\nprivate final @android.annotation.NonNull android.content.pm.SigningDetails mSigningDetails\nprivate final @android.annotation.Nullable boolean[] mIsFeatureSplits\nprivate final  boolean mIsolatedSplits\nprivate final  boolean mSplitRequired\nprivate final  boolean mCoreApp\nprivate final  boolean mDebuggable\nprivate final  boolean mMultiArch\nprivate final  boolean mUse32bitAbi\nprivate final  boolean mExtractNativeLibs\nprivate final  boolean mProfileableByShell\nprivate final  boolean mUseEmbeddedDex\nprivate final  boolean mIsSdkLibrary\nprivate final @android.annotation.NonNull java.util.List<android.content.pm.SharedLibraryInfo> mDeclaredLibraries\nprivate final @android.annotation.Nullable android.content.pm.ArchivedPackageParcel mArchivedPackage\npublic  java.util.List<java.lang.String> getAllApkPaths()\npublic  long getLongVersionCode()\nprivate  boolean hasAnyRequiredSplitTypes()\nclass PackageLite extends java.lang.Object implements []\n@com.android.internal.util.DataClass(genConstructor=false, genConstDefs=false)")
+            inputSignatures = "private final @android.annotation.NonNull java.lang.String mPackageName\nprivate final @android.annotation.NonNull java.lang.String mPath\nprivate final @android.annotation.NonNull java.lang.String mBaseApkPath\nprivate final @android.annotation.Nullable java.lang.String[] mSplitApkPaths\nprivate final @android.annotation.Nullable java.lang.String[] mSplitNames\nprivate final @android.annotation.Nullable java.lang.String[] mUsesSplitNames\nprivate final @android.annotation.Nullable java.lang.String[] mConfigForSplit\nprivate final @android.annotation.Nullable java.util.Set<java.lang.String> mBaseRequiredSplitTypes\nprivate final @android.annotation.Nullable java.util.Set<java.lang.String>[] mRequiredSplitTypes\nprivate final @android.annotation.Nullable java.util.Set<java.lang.String>[] mSplitTypes\nprivate final  int mVersionCodeMajor\nprivate final  int mVersionCode\nprivate final  int mTargetSdk\nprivate final  int mBaseRevisionCode\nprivate final @android.annotation.Nullable int[] mSplitRevisionCodes\nprivate final  int mInstallLocation\nprivate final @android.annotation.NonNull android.content.pm.VerifierInfo[] mVerifiers\nprivate final @android.annotation.NonNull android.content.pm.SigningDetails mSigningDetails\nprivate final @android.annotation.Nullable boolean[] mIsFeatureSplits\nprivate final  boolean mIsolatedSplits\nprivate final  boolean mSplitRequired\nprivate final  boolean mCoreApp\nprivate final  boolean mDebuggable\nprivate final  boolean mMultiArch\nprivate final  boolean mUse32bitAbi\nprivate final  boolean mExtractNativeLibs\nprivate final  boolean mProfileableByShell\nprivate final  boolean mUseEmbeddedDex\nprivate final  boolean mIsSdkLibrary\nprivate final @android.annotation.NonNull java.util.List<java.lang.String> mUsesSdkLibraries\nprivate final @android.annotation.Nullable long[] mUsesSdkLibrariesVersionsMajor\nprivate final @android.annotation.Nullable java.lang.String[][] mUsesSdkLibrariesCertDigests\nprivate final @android.annotation.NonNull java.util.List<android.content.pm.SharedLibraryInfo> mDeclaredLibraries\nprivate final @android.annotation.Nullable android.content.pm.ArchivedPackageParcel mArchivedPackage\npublic  java.util.List<java.lang.String> getAllApkPaths()\npublic  long getLongVersionCode()\nprivate  boolean hasAnyRequiredSplitTypes()\nclass PackageLite extends java.lang.Object implements []\n@com.android.internal.util.DataClass(genConstructor=false, genConstDefs=false)")
     @Deprecated
     private void __metadata() {}
 
diff --git a/core/java/android/content/pm/verify/pkg/IVerificationSessionInterface.aidl b/core/java/android/content/pm/verify/pkg/IVerificationSessionInterface.aidl
index 66caf2d..2ab7452 100644
--- a/core/java/android/content/pm/verify/pkg/IVerificationSessionInterface.aidl
+++ b/core/java/android/content/pm/verify/pkg/IVerificationSessionInterface.aidl
@@ -24,16 +24,9 @@
  * @hide
  */
 interface IVerificationSessionInterface {
-    @JavaPassthrough(annotation="@android.annotation.RequiresPermission(android.Manifest.permission.VERIFICATION_AGENT)")
     long getTimeoutTime(int verificationId);
-    @JavaPassthrough(annotation="@android.annotation.RequiresPermission(android.Manifest.permission.VERIFICATION_AGENT)")
     long extendTimeRemaining(int verificationId, long additionalMs);
-    @JavaPassthrough(annotation="@android.annotation.RequiresPermission(android.Manifest.permission.VERIFICATION_AGENT)")
     boolean setVerificationPolicy(int verificationId, int policy);
-    @JavaPassthrough(annotation="@android.annotation.RequiresPermission(android.Manifest.permission.VERIFICATION_AGENT)")
     void reportVerificationIncomplete(int verificationId, int reason);
-    @JavaPassthrough(annotation="@android.annotation.RequiresPermission(android.Manifest.permission.VERIFICATION_AGENT)")
-    void reportVerificationComplete(int verificationId, in VerificationStatus status);
-    @JavaPassthrough(annotation="@android.annotation.RequiresPermission(android.Manifest.permission.VERIFICATION_AGENT)")
-    void reportVerificationCompleteWithExtensionResponse(int verificationId, in VerificationStatus status, in PersistableBundle response);
+    void reportVerificationComplete(int verificationId, in VerificationStatus status, in @nullable PersistableBundle extensionResponse);
 }
\ No newline at end of file
diff --git a/core/java/android/content/pm/verify/pkg/VerificationSession.java b/core/java/android/content/pm/verify/pkg/VerificationSession.java
index 4ade211..97f78e0 100644
--- a/core/java/android/content/pm/verify/pkg/VerificationSession.java
+++ b/core/java/android/content/pm/verify/pkg/VerificationSession.java
@@ -19,7 +19,6 @@
 import android.annotation.FlaggedApi;
 import android.annotation.IntDef;
 import android.annotation.NonNull;
-import android.annotation.RequiresPermission;
 import android.annotation.SystemApi;
 import android.content.pm.Flags;
 import android.content.pm.PackageInstaller;
@@ -166,8 +165,8 @@
     /**
      * Get the value of Clock.elapsedRealtime() at which time this verification
      * will timeout as incomplete if no other verification response is provided.
+     * @throws SecurityException if the caller is not the current verifier bound by the system.
      */
-    @RequiresPermission(android.Manifest.permission.VERIFICATION_AGENT)
     public long getTimeoutTime() {
         try {
             return mSession.getTimeoutTime(mId);
@@ -190,8 +189,8 @@
     /**
      * Override the verification policy for this session.
      * @return True if the override was successful, False otherwise.
+     * @throws SecurityException if the caller is not the current verifier bound by the system.
      */
-    @RequiresPermission(android.Manifest.permission.VERIFICATION_AGENT)
     public boolean setVerificationPolicy(@PackageInstaller.VerificationPolicy int policy) {
         if (mVerificationPolicy == policy) {
             // No effective policy change
@@ -215,8 +214,8 @@
      * This may be called multiple times. If the request would bypass any max
      * duration by the system, the method will return a lower value than the
      * requested amount that indicates how much the time was extended.
+     * @throws SecurityException if the caller is not the current verifier bound by the system.
      */
-    @RequiresPermission(android.Manifest.permission.VERIFICATION_AGENT)
     public long extendTimeRemaining(long additionalMs) {
         try {
             return mSession.extendTimeRemaining(mId, additionalMs);
@@ -227,9 +226,9 @@
 
     /**
      * Report to the system that verification could not be completed along
-     * with an approximate reason to pass on to the installer.
+     * with an approximate reason to pass on to the installer.]
+     * @throws SecurityException if the caller is not the current verifier bound by the system.
      */
-    @RequiresPermission(android.Manifest.permission.VERIFICATION_AGENT)
     public void reportVerificationIncomplete(@VerificationIncompleteReason int reason) {
         try {
             mSession.reportVerificationIncomplete(mId, reason);
@@ -242,11 +241,11 @@
      * Report to the system that the verification has completed and the
      * install process may act on that status to either block in the case
      * of failure or continue to process the install in the case of success.
+     * @throws SecurityException if the caller is not the current verifier bound by the system.
      */
-    @RequiresPermission(android.Manifest.permission.VERIFICATION_AGENT)
     public void reportVerificationComplete(@NonNull VerificationStatus status) {
         try {
-            mSession.reportVerificationComplete(mId, status);
+            mSession.reportVerificationComplete(mId, status,  /* extensionResponse= */ null);
         } catch (RemoteException e) {
             throw e.rethrowFromSystemServer();
         }
@@ -256,12 +255,12 @@
      * Same as {@link #reportVerificationComplete(VerificationStatus)}, but also provide
      * a result to the extension params provided in the request, which will be passed to the
      * installer in the installation result.
+     * @throws SecurityException if the caller is not the current verifier bound by the system.
      */
-    @RequiresPermission(android.Manifest.permission.VERIFICATION_AGENT)
     public void reportVerificationComplete(@NonNull VerificationStatus status,
-            @NonNull PersistableBundle response) {
+            @NonNull PersistableBundle extensionResponse) {
         try {
-            mSession.reportVerificationCompleteWithExtensionResponse(mId, status, response);
+            mSession.reportVerificationComplete(mId, status, extensionResponse);
         } catch (RemoteException e) {
             throw e.rethrowFromSystemServer();
         }
diff --git a/core/java/android/hardware/input/AidlKeyGestureEvent.aidl b/core/java/android/hardware/input/AidlKeyGestureEvent.aidl
index 7cf8795..fc71519 100644
--- a/core/java/android/hardware/input/AidlKeyGestureEvent.aidl
+++ b/core/java/android/hardware/input/AidlKeyGestureEvent.aidl
@@ -26,4 +26,10 @@
     int action;
     int displayId;
     int flags;
+
+    // App launch parameters: only set when gestureType = KEY_GESTURE_TYPE_LAUNCH_APPLICATION
+    String appLaunchCategory;
+    String appLaunchRole;
+    String appLaunchPackageName;
+    String appLaunchClassName;
 }
diff --git a/core/java/android/hardware/input/AppLaunchData.java b/core/java/android/hardware/input/AppLaunchData.java
new file mode 100644
index 0000000..43186f5
--- /dev/null
+++ b/core/java/android/hardware/input/AppLaunchData.java
@@ -0,0 +1,176 @@
+/*
+ * Copyright 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.hardware.input;
+
+import android.annotation.NonNull;
+import android.annotation.Nullable;
+import android.text.TextUtils;
+
+import java.util.Objects;
+
+/**
+ * Provides data for launching an application.
+ *
+ * @hide
+ */
+public interface AppLaunchData {
+
+    /** Creates AppLaunchData for the provided category */
+    @NonNull
+    static AppLaunchData createLaunchDataForCategory(@NonNull String category) {
+        return new CategoryData(category);
+    }
+
+    /** Creates AppLaunchData for the provided role */
+    @NonNull
+    static AppLaunchData createLaunchDataForRole(@NonNull String role) {
+        return new RoleData(role);
+    }
+
+    /** Creates AppLaunchData for the target package name and class name */
+    @NonNull
+    static AppLaunchData createLaunchDataForComponent(@NonNull String packageName,
+            @NonNull String className) {
+        return new ComponentData(packageName, className);
+    }
+
+    @Nullable
+    static AppLaunchData createLaunchData(@Nullable String category, @Nullable String role,
+            @Nullable String packageName, @Nullable String className) {
+        if (!TextUtils.isEmpty(category)) {
+            return new CategoryData(category);
+        }
+        if (!TextUtils.isEmpty(role)) {
+            return new RoleData(role);
+        }
+        if (!TextUtils.isEmpty(packageName) && !TextUtils.isEmpty(className)) {
+            return new ComponentData(packageName, className);
+        }
+        return null;
+    }
+
+    /** Intent category based app launch data */
+    class CategoryData implements AppLaunchData {
+        @NonNull
+        private final String mCategory;
+        public CategoryData(@NonNull String category) {
+            mCategory = category;
+        }
+
+        @NonNull
+        public String getCategory() {
+            return mCategory;
+        }
+
+        @Override
+        public boolean equals(Object o) {
+            if (this == o) return true;
+            if (!(o instanceof CategoryData that)) return false;
+            return Objects.equals(mCategory, that.mCategory);
+        }
+
+        @Override
+        public int hashCode() {
+            return Objects.hash(mCategory);
+        }
+
+        @Override
+        public String toString() {
+            return "CategoryData{" +
+                    "mCategory='" + mCategory + '\'' +
+                    '}';
+        }
+    }
+
+    /** Role based app launch data */
+    class RoleData implements AppLaunchData {
+        @NonNull
+        private final String mRole;
+        public RoleData(@NonNull String role) {
+            mRole = role;
+        }
+
+        @NonNull
+        public String getRole() {
+            return mRole;
+        }
+
+        @Override
+        public boolean equals(Object o) {
+            if (this == o) return true;
+            if (!(o instanceof RoleData roleData)) return false;
+            return Objects.equals(mRole, roleData.mRole);
+        }
+
+        @Override
+        public int hashCode() {
+            return Objects.hash(mRole);
+        }
+
+        @Override
+        public String toString() {
+            return "RoleData{" +
+                    "mRole='" + mRole + '\'' +
+                    '}';
+        }
+    }
+
+    /** Target application launch data */
+    class ComponentData implements AppLaunchData {
+        @NonNull
+        private final String mPackageName;
+
+        @NonNull
+        private final String mClassName;
+
+        public ComponentData(@NonNull String packageName, @NonNull String className) {
+            mPackageName = packageName;
+            mClassName = className;
+        }
+
+        @NonNull
+        public String getPackageName() {
+            return mPackageName;
+        }
+
+        @NonNull
+        public String getClassName() {
+            return mClassName;
+        }
+
+        @Override
+        public boolean equals(Object o) {
+            if (this == o) return true;
+            if (!(o instanceof ComponentData that)) return false;
+            return Objects.equals(mPackageName, that.mPackageName) && Objects.equals(
+                    mClassName, that.mClassName);
+        }
+
+        @Override
+        public int hashCode() {
+            return Objects.hash(mPackageName, mClassName);
+        }
+
+        @Override
+        public String toString() {
+            return "ComponentData{" +
+                    "mPackageName='" + mPackageName + '\'' +
+                    ", mClassName='" + mClassName + '\'' +
+                    '}';
+        }
+    }
+}
diff --git a/core/java/android/hardware/input/KeyGestureEvent.java b/core/java/android/hardware/input/KeyGestureEvent.java
index 71d17eb..ee1a6ab 100644
--- a/core/java/android/hardware/input/KeyGestureEvent.java
+++ b/core/java/android/hardware/input/KeyGestureEvent.java
@@ -19,6 +19,8 @@
 import android.annotation.IntDef;
 import android.annotation.NonNull;
 import android.annotation.Nullable;
+import android.app.role.RoleManager;
+import android.content.Intent;
 import android.view.Display;
 import android.view.KeyCharacterMap;
 
@@ -26,6 +28,7 @@
 
 import java.lang.annotation.Retention;
 import java.lang.annotation.RetentionPolicy;
+import java.util.Objects;
 
 /**
  * Provides information about the keyboard gesture event being triggered by an external keyboard.
@@ -37,6 +40,9 @@
     @NonNull
     private AidlKeyGestureEvent mKeyGestureEvent;
 
+    private static final int LOG_EVENT_UNSPECIFIED =
+            FrameworkStatsLog.KEYBOARD_SYSTEMS_EVENT_REPORTED__KEYBOARD_SYSTEM_EVENT__UNSPECIFIED;
+
     public static final int KEY_GESTURE_TYPE_UNSPECIFIED = 0;
     public static final int KEY_GESTURE_TYPE_HOME = 1;
     public static final int KEY_GESTURE_TYPE_RECENT_APPS = 2;
@@ -76,6 +82,8 @@
     public static final int KEY_GESTURE_TYPE_SLEEP = 36;
     public static final int KEY_GESTURE_TYPE_WAKEUP = 37;
     public static final int KEY_GESTURE_TYPE_MEDIA_KEY = 38;
+    // TODO(b/280423320): Remove "LAUNCH_DEFAULT_..." gestures and rely on launch intent to find
+    //  the correct logging event.
     public static final int KEY_GESTURE_TYPE_LAUNCH_DEFAULT_BROWSER = 39;
     public static final int KEY_GESTURE_TYPE_LAUNCH_DEFAULT_EMAIL = 40;
     public static final int KEY_GESTURE_TYPE_LAUNCH_DEFAULT_CONTACTS = 41;
@@ -88,7 +96,7 @@
     public static final int KEY_GESTURE_TYPE_LAUNCH_DEFAULT_FILES = 48;
     public static final int KEY_GESTURE_TYPE_LAUNCH_DEFAULT_WEATHER = 49;
     public static final int KEY_GESTURE_TYPE_LAUNCH_DEFAULT_FITNESS = 50;
-    public static final int KEY_GESTURE_TYPE_LAUNCH_APPLICATION_BY_PACKAGE_NAME = 51;
+    public static final int KEY_GESTURE_TYPE_LAUNCH_APPLICATION = 51;
     public static final int KEY_GESTURE_TYPE_DESKTOP_MODE = 52;
     public static final int KEY_GESTURE_TYPE_MULTI_WINDOW_NAVIGATION = 53;
     public static final int KEY_GESTURE_TYPE_RECENT_APPS_SWITCHER = 54;
@@ -166,7 +174,7 @@
             KEY_GESTURE_TYPE_LAUNCH_DEFAULT_FILES,
             KEY_GESTURE_TYPE_LAUNCH_DEFAULT_WEATHER,
             KEY_GESTURE_TYPE_LAUNCH_DEFAULT_FITNESS,
-            KEY_GESTURE_TYPE_LAUNCH_APPLICATION_BY_PACKAGE_NAME,
+            KEY_GESTURE_TYPE_LAUNCH_APPLICATION,
             KEY_GESTURE_TYPE_DESKTOP_MODE,
             KEY_GESTURE_TYPE_MULTI_WINDOW_NAVIGATION,
             KEY_GESTURE_TYPE_RECENT_APPS_SWITCHER,
@@ -210,6 +218,8 @@
         private int mAction = KeyGestureEvent.ACTION_GESTURE_COMPLETE;
         private int mDisplayId = Display.DEFAULT_DISPLAY;
         private int mFlags = 0;
+        @Nullable
+        private AppLaunchData mAppLaunchData = null;
 
         /**
          * @see KeyGestureEvent#getDeviceId()
@@ -268,6 +278,14 @@
         }
 
         /**
+         * @see KeyGestureEvent#getAppLaunchData()
+         */
+        public Builder setAppLaunchData(@NonNull AppLaunchData appLaunchData) {
+            mAppLaunchData = appLaunchData;
+            return this;
+        }
+
+        /**
          * Build {@link KeyGestureEvent}
          */
         public KeyGestureEvent build() {
@@ -279,6 +297,21 @@
             event.action = mAction;
             event.displayId = mDisplayId;
             event.flags = mFlags;
+            if (mAppLaunchData != null) {
+                if (mAppLaunchData instanceof AppLaunchData.CategoryData) {
+                    event.appLaunchCategory =
+                            ((AppLaunchData.CategoryData) mAppLaunchData).getCategory();
+                } else if (mAppLaunchData instanceof AppLaunchData.RoleData) {
+                    event.appLaunchRole = ((AppLaunchData.RoleData) mAppLaunchData).getRole();
+                } else if (mAppLaunchData instanceof AppLaunchData.ComponentData) {
+                    event.appLaunchPackageName =
+                            ((AppLaunchData.ComponentData) mAppLaunchData).getPackageName();
+                    event.appLaunchClassName =
+                            ((AppLaunchData.ComponentData) mAppLaunchData).getClassName();
+                } else {
+                    throw new IllegalArgumentException("AppLaunchData type is invalid!");
+                }
+            }
             return new KeyGestureEvent(event);
         }
     }
@@ -315,6 +348,27 @@
         return (mKeyGestureEvent.flags & FLAG_CANCELLED) != 0;
     }
 
+    public int getLogEvent() {
+        if (getKeyGestureType() == KEY_GESTURE_TYPE_LAUNCH_APPLICATION) {
+            return getLogEventFromLaunchAppData(getAppLaunchData());
+        }
+        return keyGestureTypeToLogEvent(getKeyGestureType());
+    }
+
+    /**
+     * @return Launch app data associated with the event, only if key gesture type is
+     * {@code KEY_GESTURE_TYPE_LAUNCH_APPLICATION}
+     */
+    @Nullable
+    public AppLaunchData getAppLaunchData() {
+        if (mKeyGestureEvent.gestureType != KEY_GESTURE_TYPE_LAUNCH_APPLICATION) {
+            return null;
+        }
+        return AppLaunchData.createLaunchData(mKeyGestureEvent.appLaunchCategory,
+                mKeyGestureEvent.appLaunchRole, mKeyGestureEvent.appLaunchPackageName,
+                mKeyGestureEvent.appLaunchClassName);
+    }
+
     @Override
     public String toString() {
         return "KeyGestureEvent { "
@@ -324,7 +378,8 @@
                 + "keyGestureType = " + keyGestureTypeToString(mKeyGestureEvent.gestureType) + ", "
                 + "action = " + mKeyGestureEvent.action + ", "
                 + "displayId = " + mKeyGestureEvent.displayId + ", "
-                + "flags = " + mKeyGestureEvent.flags
+                + "flags = " + mKeyGestureEvent.flags + ", "
+                + "appLaunchData = " + getAppLaunchData()
                 + " }";
     }
 
@@ -339,7 +394,11 @@
                 && mKeyGestureEvent.gestureType == that.mKeyGestureEvent.gestureType
                 && mKeyGestureEvent.action == that.mKeyGestureEvent.action
                 && mKeyGestureEvent.displayId == that.mKeyGestureEvent.displayId
-                && mKeyGestureEvent.flags == that.mKeyGestureEvent.flags;
+                && mKeyGestureEvent.flags == that.mKeyGestureEvent.flags
+                && Objects.equals(mKeyGestureEvent.appLaunchCategory, that.mKeyGestureEvent.appLaunchCategory)
+                && Objects.equals(mKeyGestureEvent.appLaunchRole, that.mKeyGestureEvent.appLaunchRole)
+                && Objects.equals(mKeyGestureEvent.appLaunchPackageName, that.mKeyGestureEvent.appLaunchPackageName)
+                && Objects.equals(mKeyGestureEvent.appLaunchClassName, that.mKeyGestureEvent.appLaunchClassName);
     }
 
     @Override
@@ -352,13 +411,21 @@
         _hash = 31 * _hash + mKeyGestureEvent.action;
         _hash = 31 * _hash + mKeyGestureEvent.displayId;
         _hash = 31 * _hash + mKeyGestureEvent.flags;
+        _hash = 31 * _hash + (mKeyGestureEvent.appLaunchCategory != null
+                ? mKeyGestureEvent.appLaunchCategory.hashCode() : 0);
+        _hash = 31 * _hash + (mKeyGestureEvent.appLaunchRole != null
+                ? mKeyGestureEvent.appLaunchRole.hashCode() : 0);
+        _hash = 31 * _hash + (mKeyGestureEvent.appLaunchPackageName != null
+                ? mKeyGestureEvent.appLaunchPackageName.hashCode() : 0);
+        _hash = 31 * _hash + (mKeyGestureEvent.appLaunchClassName != null
+                ? mKeyGestureEvent.appLaunchClassName.hashCode() : 0);
         return _hash;
     }
 
     /**
      * Convert KeyGestureEvent type to corresponding log event got KeyboardSystemsEvent
      */
-    public static int keyGestureTypeToLogEvent(@KeyGestureType int value) {
+    private static int keyGestureTypeToLogEvent(@KeyGestureType int value) {
         switch (value) {
             case KEY_GESTURE_TYPE_HOME:
                 return FrameworkStatsLog.KEYBOARD_SYSTEMS_EVENT_REPORTED__KEYBOARD_SYSTEM_EVENT__HOME;
@@ -460,14 +527,79 @@
                 return FrameworkStatsLog.KEYBOARD_SYSTEMS_EVENT_REPORTED__KEYBOARD_SYSTEM_EVENT__LAUNCH_DEFAULT_WEATHER;
             case KEY_GESTURE_TYPE_LAUNCH_DEFAULT_FITNESS:
                 return FrameworkStatsLog.KEYBOARD_SYSTEMS_EVENT_REPORTED__KEYBOARD_SYSTEM_EVENT__LAUNCH_DEFAULT_FITNESS;
-            case KEY_GESTURE_TYPE_LAUNCH_APPLICATION_BY_PACKAGE_NAME:
+            case KEY_GESTURE_TYPE_LAUNCH_APPLICATION:
                 return FrameworkStatsLog.KEYBOARD_SYSTEMS_EVENT_REPORTED__KEYBOARD_SYSTEM_EVENT__LAUNCH_APPLICATION_BY_PACKAGE_NAME;
             case KEY_GESTURE_TYPE_DESKTOP_MODE:
                 return FrameworkStatsLog.KEYBOARD_SYSTEMS_EVENT_REPORTED__KEYBOARD_SYSTEM_EVENT__DESKTOP_MODE;
             case KEY_GESTURE_TYPE_MULTI_WINDOW_NAVIGATION:
                 return FrameworkStatsLog.KEYBOARD_SYSTEMS_EVENT_REPORTED__KEYBOARD_SYSTEM_EVENT__MULTI_WINDOW_NAVIGATION;
             default:
-                return FrameworkStatsLog.KEYBOARD_SYSTEMS_EVENT_REPORTED__KEYBOARD_SYSTEM_EVENT__UNSPECIFIED;
+                return LOG_EVENT_UNSPECIFIED;
+        }
+    }
+
+    /**
+     * Find Log event type corresponding to app launch data.
+     * Returns {@code LOG_EVENT_UNSPECIFIED} if no matching event found
+     */
+    private static int getLogEventFromLaunchAppData(@Nullable AppLaunchData data) {
+        if (data == null) {
+            return LOG_EVENT_UNSPECIFIED;
+        }
+        if (data instanceof AppLaunchData.CategoryData) {
+            return getLogEventFromSelectorCategory(
+                    ((AppLaunchData.CategoryData) data).getCategory());
+        } else if (data instanceof AppLaunchData.RoleData) {
+            return getLogEventFromRole(((AppLaunchData.RoleData) data).getRole());
+        } else if (data instanceof AppLaunchData.ComponentData) {
+            return FrameworkStatsLog.KEYBOARD_SYSTEMS_EVENT_REPORTED__KEYBOARD_SYSTEM_EVENT__LAUNCH_APPLICATION_BY_PACKAGE_NAME;
+        } else {
+            throw new IllegalArgumentException("AppLaunchData type is invalid!");
+        }
+    }
+
+    private static int getLogEventFromSelectorCategory(@NonNull String category) {
+        switch (category) {
+            case Intent.CATEGORY_APP_BROWSER:
+                return FrameworkStatsLog.KEYBOARD_SYSTEMS_EVENT_REPORTED__KEYBOARD_SYSTEM_EVENT__LAUNCH_DEFAULT_BROWSER;
+            case Intent.CATEGORY_APP_EMAIL:
+                return FrameworkStatsLog.KEYBOARD_SYSTEMS_EVENT_REPORTED__KEYBOARD_SYSTEM_EVENT__LAUNCH_DEFAULT_EMAIL;
+            case Intent.CATEGORY_APP_CONTACTS:
+                return FrameworkStatsLog.KEYBOARD_SYSTEMS_EVENT_REPORTED__KEYBOARD_SYSTEM_EVENT__LAUNCH_DEFAULT_CONTACTS;
+            case Intent.CATEGORY_APP_CALENDAR:
+                return FrameworkStatsLog.KEYBOARD_SYSTEMS_EVENT_REPORTED__KEYBOARD_SYSTEM_EVENT__LAUNCH_DEFAULT_CALENDAR;
+            case Intent.CATEGORY_APP_CALCULATOR:
+                return FrameworkStatsLog.KEYBOARD_SYSTEMS_EVENT_REPORTED__KEYBOARD_SYSTEM_EVENT__LAUNCH_DEFAULT_CALCULATOR;
+            case Intent.CATEGORY_APP_MUSIC:
+                return FrameworkStatsLog.KEYBOARD_SYSTEMS_EVENT_REPORTED__KEYBOARD_SYSTEM_EVENT__LAUNCH_DEFAULT_MUSIC;
+            case Intent.CATEGORY_APP_MAPS:
+                return FrameworkStatsLog.KEYBOARD_SYSTEMS_EVENT_REPORTED__KEYBOARD_SYSTEM_EVENT__LAUNCH_DEFAULT_MAPS;
+            case Intent.CATEGORY_APP_MESSAGING:
+                return FrameworkStatsLog.KEYBOARD_SYSTEMS_EVENT_REPORTED__KEYBOARD_SYSTEM_EVENT__LAUNCH_DEFAULT_MESSAGING;
+            case Intent.CATEGORY_APP_GALLERY:
+                return FrameworkStatsLog.KEYBOARD_SYSTEMS_EVENT_REPORTED__KEYBOARD_SYSTEM_EVENT__LAUNCH_DEFAULT_GALLERY;
+            case Intent.CATEGORY_APP_FILES:
+                return FrameworkStatsLog.KEYBOARD_SYSTEMS_EVENT_REPORTED__KEYBOARD_SYSTEM_EVENT__LAUNCH_DEFAULT_FILES;
+            case Intent.CATEGORY_APP_WEATHER:
+                return FrameworkStatsLog.KEYBOARD_SYSTEMS_EVENT_REPORTED__KEYBOARD_SYSTEM_EVENT__LAUNCH_DEFAULT_WEATHER;
+            case Intent.CATEGORY_APP_FITNESS:
+                return FrameworkStatsLog.KEYBOARD_SYSTEMS_EVENT_REPORTED__KEYBOARD_SYSTEM_EVENT__LAUNCH_DEFAULT_FITNESS;
+            default:
+                return LOG_EVENT_UNSPECIFIED;
+        }
+    }
+
+    /**
+     * Find Log event corresponding to the provide system role name.
+     * Returns {@code LOG_EVENT_UNSPECIFIED} if no matching event found.
+     */
+    private static int getLogEventFromRole(@NonNull String role) {
+        if (RoleManager.ROLE_BROWSER.equals(role)) {
+            return FrameworkStatsLog.KEYBOARD_SYSTEMS_EVENT_REPORTED__KEYBOARD_SYSTEM_EVENT__LAUNCH_DEFAULT_BROWSER;
+        } else if (RoleManager.ROLE_SMS.equals(role)) {
+            return FrameworkStatsLog.KEYBOARD_SYSTEMS_EVENT_REPORTED__KEYBOARD_SYSTEM_EVENT__LAUNCH_DEFAULT_MESSAGING;
+        } else {
+            return LOG_EVENT_UNSPECIFIED;
         }
     }
 
@@ -577,8 +709,8 @@
                 return "KEY_GESTURE_TYPE_LAUNCH_DEFAULT_WEATHER";
             case KEY_GESTURE_TYPE_LAUNCH_DEFAULT_FITNESS:
                 return "KEY_GESTURE_TYPE_LAUNCH_DEFAULT_FITNESS";
-            case KEY_GESTURE_TYPE_LAUNCH_APPLICATION_BY_PACKAGE_NAME:
-                return "KEY_GESTURE_TYPE_LAUNCH_APPLICATION_BY_PACKAGE_NAME";
+            case KEY_GESTURE_TYPE_LAUNCH_APPLICATION:
+                return "KEY_GESTURE_TYPE_LAUNCH_APPLICATION";
             case KEY_GESTURE_TYPE_DESKTOP_MODE:
                 return "KEY_GESTURE_TYPE_DESKTOP_MODE";
             case KEY_GESTURE_TYPE_MULTI_WINDOW_NAVIGATION:
diff --git a/core/java/android/hardware/soundtrigger/SoundTrigger.java b/core/java/android/hardware/soundtrigger/SoundTrigger.java
index a1e7567..c6fd0ee 100644
--- a/core/java/android/hardware/soundtrigger/SoundTrigger.java
+++ b/core/java/android/hardware/soundtrigger/SoundTrigger.java
@@ -1627,7 +1627,7 @@
             boolean allowMultipleTriggers = in.readBoolean();
             KeyphraseRecognitionExtra[] keyphrases =
                     in.createTypedArray(KeyphraseRecognitionExtra.CREATOR);
-            byte[] data = in.readBlob();
+            byte[] data = in.createByteArray();
             int audioCapabilities = in.readInt();
             return new RecognitionConfig(captureRequested, allowMultipleTriggers, keyphrases, data,
                     audioCapabilities);
@@ -1638,7 +1638,7 @@
             dest.writeBoolean(mCaptureRequested);
             dest.writeBoolean(mAllowMultipleTriggers);
             dest.writeTypedArray(mKeyphrases, flags);
-            dest.writeBlob(mData);
+            dest.writeByteArray(mData);
             dest.writeInt(mAudioCapabilities);
         }
 
diff --git a/core/java/android/os/BatteryUsageStatsQuery.java b/core/java/android/os/BatteryUsageStatsQuery.java
index e68c4ca..6325b00 100644
--- a/core/java/android/os/BatteryUsageStatsQuery.java
+++ b/core/java/android/os/BatteryUsageStatsQuery.java
@@ -370,7 +370,10 @@
          * and most battery stats resets.
          */
         public Builder accumulated() {
-            mFlags |= FLAG_BATTERY_USAGE_STATS_ACCUMULATED;
+            mFlags |= FLAG_BATTERY_USAGE_STATS_ACCUMULATED
+                    | FLAG_BATTERY_USAGE_STATS_INCLUDE_POWER_STATE
+                    | FLAG_BATTERY_USAGE_STATS_INCLUDE_SCREEN_STATE
+                    | FLAG_BATTERY_USAGE_STATS_INCLUDE_PROCESS_STATE_DATA;
             return this;
         }
 
diff --git a/core/java/android/os/Build.java b/core/java/android/os/Build.java
index a894833..13d7e3c 100644
--- a/core/java/android/os/Build.java
+++ b/core/java/android/os/Build.java
@@ -1274,6 +1274,12 @@
          * Vanilla Ice Cream.
          */
         public static final int VANILLA_ICE_CREAM = 35;
+
+        /**
+         * Baklava.
+         */
+        @FlaggedApi(Flags.FLAG_MAJOR_MINOR_VERSIONING_SCHEME)
+        public static final int BAKLAVA = CUR_DEVELOPMENT;
     }
 
     /** @hide */
@@ -1313,6 +1319,7 @@
         VERSION_CODES_FULL.TIRAMISU,
         VERSION_CODES_FULL.UPSIDE_DOWN_CAKE,
         VERSION_CODES_FULL.VANILLA_ICE_CREAM,
+        VERSION_CODES_FULL.BAKLAVA,
     })
     @Retention(RetentionPolicy.SOURCE)
     public @interface SdkIntFull {}
@@ -1514,6 +1521,11 @@
          */
         public static final int VANILLA_ICE_CREAM =
                 VERSION_CODES.VANILLA_ICE_CREAM * SDK_INT_MULTIPLIER;
+
+        /**
+         * The upcoming, not yet finalized, version of Android.
+         */
+        public static final int BAKLAVA = VERSION_CODES.BAKLAVA * SDK_INT_MULTIPLIER;
     }
 
     /**
diff --git a/core/java/android/security/forensic/ForensicEvent.java b/core/java/android/security/forensic/ForensicEvent.java
index 9cbc5ec..90906ed 100644
--- a/core/java/android/security/forensic/ForensicEvent.java
+++ b/core/java/android/security/forensic/ForensicEvent.java
@@ -61,6 +61,14 @@
         in.readMap(mKeyValuePairs, getClass().getClassLoader(), String.class, String.class);
     }
 
+    public String getType() {
+        return mType;
+    }
+
+    public Map<String, String> getKeyValuePairs() {
+        return mKeyValuePairs;
+    }
+
     @Override
     public void writeToParcel(@NonNull Parcel out, int flags) {
         out.writeString(mType);
diff --git a/core/java/android/security/responsible_apis_flags.aconfig b/core/java/android/security/responsible_apis_flags.aconfig
index 9bb1039..d50c7e5 100644
--- a/core/java/android/security/responsible_apis_flags.aconfig
+++ b/core/java/android/security/responsible_apis_flags.aconfig
@@ -69,6 +69,7 @@
     description: "Android Advanced Protection Mode Service and Manager"
     bug: "352420507"
     is_fixed_read_only: true
+    is_exported: true
 }
 
 flag {
diff --git a/core/java/android/view/SurfaceControl.java b/core/java/android/view/SurfaceControl.java
index e6de478..94f415b 100644
--- a/core/java/android/view/SurfaceControl.java
+++ b/core/java/android/view/SurfaceControl.java
@@ -412,41 +412,28 @@
      */
     public static class JankData {
 
-        /** @hide */
-        @IntDef(flag = true, value = {JANK_NONE,
-                DISPLAY_HAL,
-                JANK_SURFACEFLINGER_DEADLINE_MISSED,
-                JANK_SURFACEFLINGER_GPU_DEADLINE_MISSED,
-                JANK_APP_DEADLINE_MISSED,
-                PREDICTION_ERROR,
-                SURFACE_FLINGER_SCHEDULING})
+        /**
+         * Needs to be kept in sync with android_view_SurfaceControl.cpp's
+         * JankDataListenerWrapper::onJankDataAvailable.
+         * @hide
+         */
+        @IntDef(flag = true, value = {
+            JANK_NONE,
+            JANK_COMPOSER,
+            JANK_APPLICATION,
+            JANK_OTHER,
+        })
         @Retention(RetentionPolicy.SOURCE)
         public @interface JankType {}
 
-        // Needs to be kept in sync with frameworks/native/libs/gui/include/gui/JankInfo.h
-
         // No Jank
-        public static final int JANK_NONE = 0x0;
-
-        // Jank not related to SurfaceFlinger or the App
-        public static final int DISPLAY_HAL = 0x1;
-        // SF took too long on the CPU
-        public static final int JANK_SURFACEFLINGER_DEADLINE_MISSED = 0x2;
-        // SF took too long on the GPU
-        public static final int JANK_SURFACEFLINGER_GPU_DEADLINE_MISSED = 0x4;
-        // Either App or GPU took too long on the frame
-        public static final int JANK_APP_DEADLINE_MISSED = 0x8;
-        // Vsync predictions have drifted beyond the threshold from the actual HWVsync
-        public static final int PREDICTION_ERROR = 0x10;
-        // Latching a buffer early might cause an early present of the frame
-        public static final int SURFACE_FLINGER_SCHEDULING = 0x20;
-        // A buffer is said to be stuffed if it was expected to be presented on a vsync but was
-        // presented later because the previous buffer was presented in its expected vsync. This
-        // usually happens if there is an unexpectedly long frame causing the rest of the buffers
-        // to enter a stuffed state.
-        public static final int BUFFER_STUFFING = 0x40;
-        // Jank due to unknown reasons.
-        public static final int UNKNOWN = 0x80;
+        public static final int JANK_NONE = 0;
+        // Jank caused by the composer missing a deadline
+        public static final int JANK_COMPOSER = 1 << 0;
+        // Jank caused by the application missing the composer's deadline
+        public static final int JANK_APPLICATION = 1 << 1;
+        // Jank due to other unknown reasons
+        public static final int JANK_OTHER = 1 << 2;
 
         public JankData(long frameVsyncId, @JankType int jankType, long frameIntervalNs,
                 long scheduledAppFrameTimeNs, long actualAppFrameTimeNs) {
diff --git a/core/java/android/window/BackEvent.java b/core/java/android/window/BackEvent.java
index 1b9235b..89b38d8 100644
--- a/core/java/android/window/BackEvent.java
+++ b/core/java/android/window/BackEvent.java
@@ -23,7 +23,6 @@
 import android.annotation.FlaggedApi;
 import android.annotation.FloatRange;
 import android.annotation.IntDef;
-import android.util.TimeUtils;
 
 import java.lang.annotation.Retention;
 import java.lang.annotation.RetentionPolicy;
@@ -76,7 +75,7 @@
     }
 
     /**
-     * Creates a new {@link BackEvent} instance with the current uptime as frame time.
+     * Creates a new {@link BackEvent} instance with a frame time of 0.
      *
      * @param touchX Absolute X location of the touch point of this event.
      * @param touchY Absolute Y location of the touch point of this event.
@@ -88,7 +87,7 @@
         mTouchY = touchY;
         mProgress = progress;
         mSwipeEdge = swipeEdge;
-        mFrameTimeMillis = System.nanoTime() / TimeUtils.NANOS_PER_MS;
+        mFrameTimeMillis = 0;
     }
 
     /**
diff --git a/core/java/android/window/flags/lse_desktop_experience.aconfig b/core/java/android/window/flags/lse_desktop_experience.aconfig
index b805f5a..c4a9e57 100644
--- a/core/java/android/window/flags/lse_desktop_experience.aconfig
+++ b/core/java/android/window/flags/lse_desktop_experience.aconfig
@@ -362,3 +362,10 @@
     description: "Change the default display's windowing mode to freeform when display connected in extended mode."
     bug: "374849026"
 }
+
+flag {
+    name: "enable_desktop_windowing_pip"
+    namespace: "lse_desktop_experience"
+    description: "Enables PiP features in desktop mode."
+    bug: "350475854"
+}
diff --git a/core/java/com/android/internal/jank/FrameTracker.java b/core/java/com/android/internal/jank/FrameTracker.java
index 003393c..0af4bea 100644
--- a/core/java/com/android/internal/jank/FrameTracker.java
+++ b/core/java/com/android/internal/jank/FrameTracker.java
@@ -16,13 +16,9 @@
 
 package com.android.internal.jank;
 
-import static android.view.SurfaceControl.JankData.DISPLAY_HAL;
-import static android.view.SurfaceControl.JankData.JANK_APP_DEADLINE_MISSED;
+import static android.view.SurfaceControl.JankData.JANK_APPLICATION;
+import static android.view.SurfaceControl.JankData.JANK_COMPOSER;
 import static android.view.SurfaceControl.JankData.JANK_NONE;
-import static android.view.SurfaceControl.JankData.JANK_SURFACEFLINGER_DEADLINE_MISSED;
-import static android.view.SurfaceControl.JankData.JANK_SURFACEFLINGER_GPU_DEADLINE_MISSED;
-import static android.view.SurfaceControl.JankData.PREDICTION_ERROR;
-import static android.view.SurfaceControl.JankData.SURFACE_FLINGER_SCHEDULING;
 
 import static com.android.internal.jank.DisplayRefreshRate.UNKNOWN_REFRESH_RATE;
 import static com.android.internal.jank.DisplayRefreshRate.VARIABLE_REFRESH_RATE;
@@ -181,23 +177,11 @@
                 case JANK_NONE:
                     str.append("JANK_NONE");
                     break;
-                case JANK_APP_DEADLINE_MISSED:
-                    str.append("JANK_APP_DEADLINE_MISSED");
+                case JANK_APPLICATION:
+                    str.append("JANK_APPLICATION");
                     break;
-                case JANK_SURFACEFLINGER_DEADLINE_MISSED:
-                    str.append("JANK_SURFACEFLINGER_DEADLINE_MISSED");
-                    break;
-                case JANK_SURFACEFLINGER_GPU_DEADLINE_MISSED:
-                    str.append("JANK_SURFACEFLINGER_GPU_DEADLINE_MISSED");
-                    break;
-                case DISPLAY_HAL:
-                    str.append("DISPLAY_HAL");
-                    break;
-                case PREDICTION_ERROR:
-                    str.append("PREDICTION_ERROR");
-                    break;
-                case SURFACE_FLINGER_SCHEDULING:
-                    str.append("SURFACE_FLINGER_SCHEDULING");
+                case JANK_COMPOSER:
+                    str.append("JANK_COMPOSER");
                     break;
                 default:
                     str.append("UNKNOWN: ").append(jankType);
@@ -628,16 +612,12 @@
             if (info.surfaceControlCallbackFired) {
                 totalFramesCount++;
                 boolean missedFrame = false;
-                if ((info.jankType & JANK_APP_DEADLINE_MISSED) != 0) {
+                if ((info.jankType & JANK_APPLICATION) != 0) {
                     Log.w(TAG, "Missed App frame:" + info + ", CUJ=" + name);
                     missedAppFramesCount++;
                     missedFrame = true;
                 }
-                if ((info.jankType & DISPLAY_HAL) != 0
-                        || (info.jankType & JANK_SURFACEFLINGER_DEADLINE_MISSED) != 0
-                        || (info.jankType & JANK_SURFACEFLINGER_GPU_DEADLINE_MISSED) != 0
-                        || (info.jankType & SURFACE_FLINGER_SCHEDULING) != 0
-                        || (info.jankType & PREDICTION_ERROR) != 0) {
+                if ((info.jankType & JANK_COMPOSER) != 0) {
                     Log.w(TAG, "Missed SF frame:" + info + ", CUJ=" + name);
                     missedSfFramesCount++;
                     missedFrame = true;
diff --git a/core/java/com/android/internal/os/ApplicationSharedMemory.java b/core/java/com/android/internal/os/ApplicationSharedMemory.java
index 84f713e..e6ea29e 100644
--- a/core/java/com/android/internal/os/ApplicationSharedMemory.java
+++ b/core/java/com/android/internal/os/ApplicationSharedMemory.java
@@ -21,6 +21,7 @@
 import com.android.internal.annotations.VisibleForTesting;
 
 import dalvik.annotation.optimization.CriticalNative;
+import dalvik.annotation.optimization.FastNative;
 
 import libcore.io.IoUtils;
 
@@ -293,4 +294,34 @@
             throw new IllegalStateException("Not mutable");
         }
     }
+
+    /**
+     * Return true if the memory has been mapped.  This never throws.
+     */
+    public boolean isMapped() {
+        return mPtr != 0;
+    }
+
+    /**
+     * Return true if the memory is mapped and mutable.  This never throws.  Note that it returns
+     * false if the memory is not mapped.
+     */
+    public boolean isMutable() {
+        return isMapped() && mMutable;
+    }
+
+    /**
+     * Provide access to the nonce block needed by {@link PropertyInvalidatedCache}.  This method
+     * returns 0 if the shared memory is not (yet) mapped.
+     */
+    public long getSystemNonceBlock() {
+        return isMapped() ? nativeGetSystemNonceBlock(mPtr) : 0;
+    }
+
+    /**
+     * Return a pointer to the system nonce cache in the shared memory region.  The method is
+     * idempotent.
+     */
+    @FastNative
+    private static native long nativeGetSystemNonceBlock(long ptr);
 }
diff --git a/core/java/com/android/internal/os/flags.aconfig b/core/java/com/android/internal/os/flags.aconfig
index 07df248..25a9fbc 100644
--- a/core/java/com/android/internal/os/flags.aconfig
+++ b/core/java/com/android/internal/os/flags.aconfig
@@ -2,6 +2,48 @@
 container: "system"
 
 flag {
+     namespace: "ravenwood"
+     name: "ravenwood_flag_rw_1"
+     description: "Ravenwood test RW flag 1"
+     bug: "311370221"
+     metadata {
+         purpose: PURPOSE_BUGFIX
+     }
+}
+
+flag {
+     namespace: "ravenwood"
+     name: "ravenwood_flag_rw_2"
+     description: "Ravenwood test RW flag 2"
+     bug: "311370221"
+     metadata {
+         purpose: PURPOSE_BUGFIX
+     }
+}
+
+flag {
+     namespace: "ravenwood"
+     name: "ravenwood_flag_ro_1"
+     description: "Ravenwood test RO flag 1"
+     is_fixed_read_only: true
+     bug: "311370221"
+     metadata {
+         purpose: PURPOSE_BUGFIX
+     }
+}
+
+flag {
+     namespace: "ravenwood"
+     name: "ravenwood_flag_ro_2"
+     description: "Ravenwood test RO flag 2"
+     is_fixed_read_only: true
+     bug: "311370221"
+     metadata {
+         purpose: PURPOSE_BUGFIX
+     }
+}
+
+flag {
     name: "enable_apache_http_legacy_preload"
     namespace: "system_performance"
     description: "Enables zygote preload of non-BCP org.apache.http.legacy.jar library."
diff --git a/core/java/com/android/internal/widget/NotificationProgressBar.java b/core/java/com/android/internal/widget/NotificationProgressBar.java
index d3b1f97..c5eac08 100644
--- a/core/java/com/android/internal/widget/NotificationProgressBar.java
+++ b/core/java/com/android/internal/widget/NotificationProgressBar.java
@@ -21,6 +21,9 @@
 import android.app.Notification.ProgressStyle;
 import android.content.Context;
 import android.content.res.ColorStateList;
+import android.content.res.TypedArray;
+import android.graphics.Canvas;
+import android.graphics.Rect;
 import android.graphics.drawable.Drawable;
 import android.graphics.drawable.Icon;
 import android.graphics.drawable.LayerDrawable;
@@ -52,7 +55,7 @@
  * represent Notification ProgressStyle progress, such as for ridesharing and navigation.
  */
 @RemoteViews.RemoteView
-public class NotificationProgressBar extends ProgressBar {
+public final class NotificationProgressBar extends ProgressBar {
     private static final String TAG = "NotificationProgressBar";
 
     private NotificationProgressModel mProgressModel;
@@ -61,7 +64,7 @@
     private List<Part> mProgressDrawableParts = null;
 
     @Nullable
-    private Drawable mProgressTrackerDrawable = null;
+    private Drawable mTracker = null;
 
     public NotificationProgressBar(Context context) {
         this(context, null);
@@ -78,28 +81,44 @@
     public NotificationProgressBar(Context context, AttributeSet attrs, int defStyleAttr,
             int defStyleRes) {
         super(context, attrs, defStyleAttr, defStyleRes);
+
+        final TypedArray a = context.obtainStyledAttributes(
+                attrs, R.styleable.NotificationProgressBar, defStyleAttr, defStyleRes);
+        saveAttributeDataForStyleable(context, R.styleable.NotificationProgressBar, attrs, a,
+                defStyleAttr,
+                defStyleRes);
+
+        // Supports setting the tracker in xml, but ProgressStyle notifications set/override it
+        // via {@code setProgressTrackerIcon}.
+        final Drawable tracker = a.getDrawable(R.styleable.NotificationProgressBar_tracker);
+        setTracker(tracker);
     }
 
     /**
      * Setter for the notification progress model.
      *
      * @see NotificationProgressModel#fromBundle
-     * @see #setProgressModelAsync
      */
-    @RemotableViewMethod(asyncImpl = "setProgressModelAsync")
+    @RemotableViewMethod
     public void setProgressModel(@Nullable Bundle bundle) {
         Preconditions.checkArgument(bundle != null,
                 "Bundle shouldn't be null");
 
         mProgressModel = NotificationProgressModel.fromBundle(bundle);
+        final boolean isIndeterminate = mProgressModel.isIndeterminate();
+        setIndeterminate(isIndeterminate);
 
-        if (mProgressModel.isIndeterminate()) {
+        if (isIndeterminate) {
             final int indeterminateColor = mProgressModel.getIndeterminateColor();
             setIndeterminateTintList(ColorStateList.valueOf(indeterminateColor));
         } else {
+            final int progress = mProgressModel.getProgress();
+            final int progressMax = mProgressModel.getProgressMax();
             mProgressDrawableParts = processAndConvertToDrawableParts(mProgressModel.getSegments(),
                     mProgressModel.getPoints(),
-                    mProgressModel.getProgress(), mProgressModel.isStyledByProgress());
+                    progress,
+                    progressMax,
+                    mProgressModel.isStyledByProgress());
 
             try {
                 final NotificationProgressDrawable drawable = getNotificationProgressDrawable();
@@ -107,6 +126,9 @@
             } catch (IllegalStateException ex) {
                 Log.e(TAG, "Can't set parts because can't get NotificationProgressDrawable", ex);
             }
+
+            setMax(progressMax);
+            setProgress(progress);
         }
     }
 
@@ -137,6 +159,13 @@
      */
     @RemotableViewMethod(asyncImpl = "setProgressTrackerIconAsync")
     public void setProgressTrackerIcon(@Nullable Icon icon) {
+        final Drawable progressTrackerDrawable;
+        if (icon != null) {
+            progressTrackerDrawable = icon.loadDrawable(getContext());
+        } else {
+            progressTrackerDrawable = null;
+        }
+        setTracker(progressTrackerDrawable);
     }
 
     /**
@@ -150,12 +179,245 @@
             progressTrackerDrawable = null;
         }
         return () -> {
-            setProgressTrackerDrawable(progressTrackerDrawable);
+            setTracker(progressTrackerDrawable);
         };
     }
 
-    private void setProgressTrackerDrawable(@Nullable  Drawable drawable) {
-        mProgressTrackerDrawable = drawable;
+    private void setTracker(@Nullable Drawable tracker) {
+        if (isIndeterminate() && tracker != null) {
+            return;
+        }
+
+        final boolean needUpdate = mTracker != null && tracker != mTracker;
+        if (needUpdate) {
+            mTracker.setCallback(null);
+        }
+
+        if (tracker != null) {
+            tracker.setCallback(this);
+            if (canResolveLayoutDirection()) {
+                tracker.setLayoutDirection(getLayoutDirection());
+            }
+
+            // If we're updating get the new states
+            if (needUpdate && (tracker.getIntrinsicWidth() != mTracker.getIntrinsicWidth()
+                    || tracker.getIntrinsicHeight() != mTracker.getIntrinsicHeight())) {
+                requestLayout();
+            }
+        }
+
+        mTracker = tracker;
+        invalidate();
+
+        if (needUpdate) {
+            updateTrackerAndBarPos(getWidth(), getHeight());
+            if (tracker != null && tracker.isStateful()) {
+                // Note that if the states are different this won't work.
+                // For now, let's consider that an app bug.
+                tracker.setState(getDrawableState());
+            }
+        }
+    }
+
+    @Override
+    @RemotableViewMethod
+    public synchronized void setIndeterminate(boolean indeterminate) {
+        super.setIndeterminate(indeterminate);
+
+        if (isIndeterminate()) {
+            setTracker(null);
+        }
+    }
+
+    @Override
+    protected boolean verifyDrawable(@NonNull Drawable who) {
+        return who == mTracker || super.verifyDrawable(who);
+    }
+
+    @Override
+    public void jumpDrawablesToCurrentState() {
+        super.jumpDrawablesToCurrentState();
+
+        if (mTracker != null) {
+            mTracker.jumpToCurrentState();
+        }
+    }
+
+    @Override
+    protected void drawableStateChanged() {
+        super.drawableStateChanged();
+
+        final Drawable tracker = mTracker;
+        if (tracker != null && tracker.isStateful()
+                && tracker.setState(getDrawableState())) {
+            invalidateDrawable(tracker);
+        }
+    }
+
+    @Override
+    public void drawableHotspotChanged(float x, float y) {
+        super.drawableHotspotChanged(x, y);
+
+        if (mTracker != null) {
+            mTracker.setHotspot(x, y);
+        }
+    }
+
+    @Override
+    protected void onSizeChanged(int w, int h, int oldw, int oldh) {
+        super.onSizeChanged(w, h, oldw, oldh);
+
+        updateTrackerAndBarPos(w, h);
+    }
+
+    private void updateTrackerAndBarPos(int w, int h) {
+        final int paddedHeight = h - mPaddingTop - mPaddingBottom;
+        final Drawable bar = getCurrentDrawable();
+        final Drawable tracker = mTracker;
+
+        // The max height does not incorporate padding, whereas the height
+        // parameter does.
+        final int barHeight = Math.min(getMaxHeight(), paddedHeight);
+        final int trackerHeight = tracker == null ? 0 : tracker.getIntrinsicHeight();
+
+        // Apply offset to whichever item is taller.
+        final int barOffsetY;
+        final int trackerOffsetY;
+        if (trackerHeight > barHeight) {
+            final int offsetHeight = (paddedHeight - trackerHeight) / 2;
+            barOffsetY = offsetHeight + (trackerHeight - barHeight) / 2;
+            trackerOffsetY = offsetHeight;
+        } else {
+            final int offsetHeight = (paddedHeight - barHeight) / 2;
+            barOffsetY = offsetHeight;
+            trackerOffsetY = offsetHeight + (barHeight - trackerHeight) / 2;
+        }
+
+        if (bar != null) {
+            final int barWidth = w - mPaddingRight - mPaddingLeft;
+            bar.setBounds(0, barOffsetY, barWidth, barOffsetY + barHeight);
+        }
+
+        if (tracker != null) {
+            setTrackerPos(w, tracker, getScale(), trackerOffsetY);
+        }
+    }
+
+    private float getScale() {
+        int min = getMin();
+        int max = getMax();
+        int range = max - min;
+        return range > 0 ? (getProgress() - min) / (float) range : 0;
+    }
+
+    /**
+     * Updates the tracker drawable bounds.
+     *
+     * @param w Width of the view, including padding
+     * @param tracker Drawable used for the tracker
+     * @param scale Current progress between 0 and 1
+     * @param offsetY Vertical offset for centering. If set to
+     *            {@link Integer#MIN_VALUE}, the current offset will be used.
+     */
+    private void setTrackerPos(int w, Drawable tracker, float scale, int offsetY) {
+        int available = w - mPaddingLeft - mPaddingRight;
+        final int trackerWidth = tracker.getIntrinsicWidth();
+        final int trackerHeight = tracker.getIntrinsicHeight();
+        available -= trackerWidth;
+
+        final int trackerPos = (int) (scale * available + 0.5f);
+
+        final int top, bottom;
+        if (offsetY == Integer.MIN_VALUE) {
+            final Rect oldBounds = tracker.getBounds();
+            top = oldBounds.top;
+            bottom = oldBounds.bottom;
+        } else {
+            top = offsetY;
+            bottom = offsetY + trackerHeight;
+        }
+
+        final int left = (isLayoutRtl() && getMirrorForRtl()) ? available - trackerPos : trackerPos;
+        final int right = left + trackerWidth;
+
+        final Drawable background = getBackground();
+        if (background != null) {
+            final int bkgOffsetX = mPaddingLeft;
+            final int bkgOffsetY = mPaddingTop;
+            background.setHotspotBounds(left + bkgOffsetX, top + bkgOffsetY,
+                    right + bkgOffsetX, bottom + bkgOffsetY);
+        }
+
+        // Canvas will be translated, so 0,0 is where we start drawing
+        tracker.setBounds(left, top, right, bottom);
+    }
+
+    @Override
+    public void onResolveDrawables(int layoutDirection) {
+        super.onResolveDrawables(layoutDirection);
+
+        if (mTracker != null) {
+            mTracker.setLayoutDirection(layoutDirection);
+        }
+    }
+
+    @Override
+    protected synchronized void onDraw(Canvas canvas) {
+        super.onDraw(canvas);
+        drawTracker(canvas);
+    }
+
+    /**
+     * Draw the tracker.
+     */
+    private void drawTracker(Canvas canvas) {
+        if (mTracker != null) {
+            final int saveCount = canvas.save();
+            // Translate the padding. For the x, we need to allow the tracker to
+            // draw in its extra space
+            canvas.translate(mPaddingLeft, mPaddingTop);
+            mTracker.draw(canvas);
+            canvas.restoreToCount(saveCount);
+        }
+    }
+
+    @Override
+    protected synchronized void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
+        Drawable d = getCurrentDrawable();
+
+        int trackerHeight = mTracker == null ? 0 : mTracker.getIntrinsicHeight();
+        int dw = 0;
+        int dh = 0;
+        if (d != null) {
+            dw = Math.max(getMinWidth(), Math.min(getMaxWidth(), d.getIntrinsicWidth()));
+            dh = Math.max(getMinHeight(), Math.min(getMaxHeight(), d.getIntrinsicHeight()));
+            dh = Math.max(trackerHeight, dh);
+        }
+        dw += mPaddingLeft + mPaddingRight;
+        dh += mPaddingTop + mPaddingBottom;
+
+        setMeasuredDimension(resolveSizeAndState(dw, widthMeasureSpec, 0),
+                resolveSizeAndState(dh, heightMeasureSpec, 0));
+    }
+
+    @Override
+    public CharSequence getAccessibilityClassName() {
+        return NotificationProgressBar.class.getName();
+    }
+
+    @Override
+    public void onRtlPropertiesChanged(int layoutDirection) {
+        super.onRtlPropertiesChanged(layoutDirection);
+
+        final Drawable tracker = mTracker;
+        if (tracker != null) {
+            setTrackerPos(getWidth(), tracker, getScale(), Integer.MIN_VALUE);
+
+            // Since we draw translated, the drawable's bounds that it signals
+            // for invalidation won't be the actual bounds we want invalidated,
+            // so just invalidate this whole view.
+            invalidate();
+        }
     }
 
     /**
@@ -167,12 +429,18 @@
             List<ProgressStyle.Segment> segments,
             List<ProgressStyle.Point> points,
             int progress,
+            int progressMax,
             boolean isStyledByProgress
     ) {
         if (segments.isEmpty()) {
             throw new IllegalArgumentException("List of segments shouldn't be empty");
         }
 
+        final int totalLength = segments.stream().mapToInt(ProgressStyle.Segment::getLength).sum();
+        if (progressMax != totalLength) {
+            throw new IllegalArgumentException("Invalid progressMax : " + progressMax);
+        }
+
         for (ProgressStyle.Segment segment : segments) {
             final int length = segment.getLength();
             if (length <= 0) {
@@ -180,14 +448,12 @@
             }
         }
 
-        final int progressMax = segments.stream().mapToInt(ProgressStyle.Segment::getLength).sum();
-
         if (progress < 0 || progress > progressMax) {
             throw new IllegalArgumentException("Invalid progress : " + progress);
         }
         for (ProgressStyle.Point point : points) {
             final int pos = point.getPosition();
-            if (pos <= 0 || pos >= progressMax) {
+            if (pos < 0 || pos > progressMax) {
                 throw new IllegalArgumentException("Invalid Point position : " + pos);
             }
         }
@@ -208,7 +474,6 @@
                 isStyledByProgress);
     }
 
-
     // Any segment with a point on it gets split by the point.
     // If isStyledByProgress is true, also split the segment with the progress value in its range.
     private static Map<Integer, ProgressStyle.Segment> splitSegmentsByPointsAndProgress(
diff --git a/core/java/com/android/internal/widget/NotificationProgressModel.java b/core/java/com/android/internal/widget/NotificationProgressModel.java
index e51ea99..e8cb37e 100644
--- a/core/java/com/android/internal/widget/NotificationProgressModel.java
+++ b/core/java/com/android/internal/widget/NotificationProgressModel.java
@@ -96,6 +96,10 @@
         return mProgress;
     }
 
+    public int getProgressMax() {
+        return mSegments.stream().mapToInt(Notification.ProgressStyle.Segment::getLength).sum();
+    }
+
     public boolean isStyledByProgress() {
         return mIsStyledByProgress;
     }
diff --git a/core/jni/Android.bp b/core/jni/Android.bp
index 816ace2..eb07f7c 100644
--- a/core/jni/Android.bp
+++ b/core/jni/Android.bp
@@ -249,6 +249,7 @@
                 "android_backup_BackupDataOutput.cpp",
                 "android_backup_FileBackupHelperBase.cpp",
                 "android_backup_BackupHelperDispatcher.cpp",
+                "android_app_PropertyInvalidatedCache.cpp",
                 "android_app_backup_FullBackup.cpp",
                 "android_content_res_ApkAssets.cpp",
                 "android_content_res_ObbScanner.cpp",
diff --git a/core/jni/AndroidRuntime.cpp b/core/jni/AndroidRuntime.cpp
index 76f66cd..821861e 100644
--- a/core/jni/AndroidRuntime.cpp
+++ b/core/jni/AndroidRuntime.cpp
@@ -177,6 +177,7 @@
 extern int register_android_app_Activity(JNIEnv *env);
 extern int register_android_app_ActivityThread(JNIEnv *env);
 extern int register_android_app_NativeActivity(JNIEnv *env);
+extern int register_android_app_PropertyInvalidatedCache(JNIEnv* env);
 extern int register_android_media_RemoteDisplay(JNIEnv *env);
 extern int register_android_util_jar_StrictJarFile(JNIEnv* env);
 extern int register_android_view_InputChannel(JNIEnv* env);
@@ -1659,6 +1660,7 @@
         REG_JNI(register_android_app_Activity),
         REG_JNI(register_android_app_ActivityThread),
         REG_JNI(register_android_app_NativeActivity),
+        REG_JNI(register_android_app_PropertyInvalidatedCache),
         REG_JNI(register_android_util_jar_StrictJarFile),
         REG_JNI(register_android_view_InputChannel),
         REG_JNI(register_android_view_InputEventReceiver),
diff --git a/core/jni/android_app_PropertyInvalidatedCache.cpp b/core/jni/android_app_PropertyInvalidatedCache.cpp
new file mode 100644
index 0000000..ead6666
--- /dev/null
+++ b/core/jni/android_app_PropertyInvalidatedCache.cpp
@@ -0,0 +1,119 @@
+/*
+ * 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.
+ */
+
+#define LOG_TAG "CacheNonce"
+
+#include <string.h>
+#include <memory.h>
+
+#include <atomic>
+
+#include <nativehelper/JNIHelp.h>
+#include <nativehelper/scoped_primitive_array.h>
+#include <android-base/logging.h>
+
+#include "core_jni_helpers.h"
+#include "android_app_PropertyInvalidatedCache.h"
+
+namespace {
+
+using namespace android::app::PropertyInvalidatedCache;
+
+// Convert a jlong to a nonce block.  This is a convenience function that should be inlined by
+// the compiler.
+inline SystemCacheNonce* sysCache(jlong ptr) {
+    return reinterpret_cast<SystemCacheNonce*>(ptr);
+}
+
+// Return the number of nonces in the nonce block.
+jint getMaxNonce(JNIEnv*, jclass, jlong ptr) {
+    return sysCache(ptr)->getMaxNonce();
+}
+
+// Return the number of string bytes in the nonce block.
+jint getMaxByte(JNIEnv*, jclass, jlong ptr) {
+    return sysCache(ptr)->getMaxByte();
+}
+
+// Set the byte block.  The first int is the hash to set and the second is the array to copy.
+// This should be synchronized in the Java layer.
+void setByteBlock(JNIEnv* env, jclass, jlong ptr, jint hash, jbyteArray val) {
+    ScopedByteArrayRO value(env, val);
+    if (value.get() == nullptr) {
+        jniThrowExceptionFmt(env, "java/lang/IllegalArgumentException", "null byte block");
+        return;
+    }
+    sysCache(ptr)->setByteBlock(hash, value.get(), value.size());
+}
+
+// Fetch the byte block.  If the incoming hash is the same as the local hash, the Java layer is
+// presumed to have an up-to-date copy of the byte block; do not copy byte array.  The local
+// hash is returned.
+jint getByteBlock(JNIEnv* env, jclass, jlong ptr, jint hash, jbyteArray val) {
+    if (sysCache(ptr)->getHash() == hash) {
+        return hash;
+    }
+    ScopedByteArrayRW value(env, val);
+    return sysCache(ptr)->getByteBlock(value.get(), value.size());
+}
+
+// Fetch the byte block hash.
+//
+// This is a CriticalNative method and therefore does not get the JNIEnv or jclass parameters.
+jint getByteBlockHash(jlong ptr) {
+    return sysCache(ptr)->getHash();
+}
+
+// Get a nonce value. So that this method can be CriticalNative, it returns 0 if the value is
+// out of range, rather than throwing an exception.  This is a CriticalNative method and
+// therefore does not get the JNIEnv or jclass parameters.
+//
+// This method is @CriticalNative and does not take a JNIEnv* or jclass argument.
+jlong getNonce(jlong ptr, jint index) {
+    return sysCache(ptr)->getNonce(index);
+}
+
+// Set a nonce value. So that this method can be CriticalNative, it returns a boolean: false if
+// the index is out of range and true otherwise.  Callers may test the returned boolean and
+// generate an exception.
+//
+// This method is @CriticalNative and does not take a JNIEnv* or jclass argument.
+jboolean setNonce(jlong ptr, jint index, jlong value) {
+    return sysCache(ptr)->setNonce(index, value);
+}
+
+static const JNINativeMethod gMethods[] = {
+    {"nativeGetMaxNonce",      "(J)I",    (void*) getMaxNonce },
+    {"nativeGetMaxByte",       "(J)I",    (void*) getMaxByte },
+    {"nativeSetByteBlock",     "(JI[B)V", (void*) setByteBlock },
+    {"nativeGetByteBlock",     "(JI[B)I", (void*) getByteBlock },
+    {"nativeGetByteBlockHash", "(J)I",    (void*) getByteBlockHash },
+    {"nativeGetNonce",         "(JI)J",   (void*) getNonce },
+    {"nativeSetNonce",         "(JIJ)Z",  (void*) setNonce },
+};
+
+static const char* kClassName = "android/app/PropertyInvalidatedCache";
+
+} // anonymous namespace
+
+namespace android {
+
+int register_android_app_PropertyInvalidatedCache(JNIEnv* env) {
+    RegisterMethodsOrDie(env, kClassName, gMethods, NELEM(gMethods));
+    return JNI_OK;
+}
+
+} // namespace android
diff --git a/core/jni/android_app_PropertyInvalidatedCache.h b/core/jni/android_app_PropertyInvalidatedCache.h
new file mode 100644
index 0000000..eefa8fa
--- /dev/null
+++ b/core/jni/android_app_PropertyInvalidatedCache.h
@@ -0,0 +1,146 @@
+/*
+ * 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.
+ */
+
+#include <string.h>
+#include <memory.h>
+
+#include <atomic>
+
+namespace android {
+namespace app {
+namespace PropertyInvalidatedCache {
+
+/**
+ * A cache nonce block contains an array of std::atomic<int64_t> and an array of bytes.  The
+ * byte array has an associated hash.  This class provides methods to read and write the fields
+ * of the block but it does not interpret the fields.
+ *
+ * On initialization, all fields are set to zero.
+ *
+ * In general, methods do not report errors.  This allows the methods to be used in
+ * CriticalNative JNI APIs.
+ *
+ * The template is parameterized by the number of nonces it supports and the number of bytes in
+ * the string block.
+ */
+template<int maxNonce, size_t maxByte> class CacheNonce {
+
+    // The value of an unset field.
+    static const int UNSET = 0;
+
+    // A convenient typedef.  The jbyteArray element type is jbyte, which the compiler treats as
+    // signed char.
+    typedef signed char block_t;
+
+    // The array of nonces
+    volatile std::atomic<int64_t> mNonce[maxNonce];
+
+    // The byte array.  This is not atomic but it is guarded by the mByteHash.
+    volatile block_t mByteBlock[maxByte];
+
+    // The hash that validates the byte block
+    volatile std::atomic<int32_t> mByteHash;
+
+    // Pad the class to a multiple of 8 bytes.
+    int32_t _pad;
+
+  public:
+
+    // The expected size of this instance.  This is a compile-time constant and can be used in a
+    // static assertion.
+    static const int expectedSize =
+            maxNonce * sizeof(std::atomic<int64_t>)
+            + sizeof(std::atomic<int32_t>)
+            + maxByte * sizeof(block_t)
+            + sizeof(int32_t);
+
+    // These provide run-time access to the sizing parameters.
+    int getMaxNonce() const {
+        return maxNonce;
+    }
+
+    size_t getMaxByte() const {
+        return maxByte;
+    }
+
+    // Construct and initialize the memory.
+    CacheNonce() {
+        for (int i = 0; i < maxNonce; i++) {
+            mNonce[i] = UNSET;
+        }
+        mByteHash = UNSET;
+        memset((void*) mByteBlock, UNSET, sizeof(mByteBlock));
+    }
+
+    // Fetch a nonce, returning UNSET if the index is out of range.  This method specifically
+    // does not throw or generate an error if the index is out of range; this allows the method
+    // to be called in a CriticalNative JNI API.
+    int64_t getNonce(int index) const {
+        if (index < 0 || index >= maxNonce) {
+            return UNSET;
+        } else {
+            return mNonce[index];
+        }
+    }
+
+    // Set a nonce and return true. Return false if the index is out of range.  This method
+    // specifically does not throw or generate an error if the index is out of range; this
+    // allows the method to be called in a CriticalNative JNI API.
+    bool setNonce(int index, int64_t value) {
+        if (index < 0 || index >= maxNonce) {
+            return false;
+        } else {
+            mNonce[index] = value;
+            return true;
+        }
+    }
+
+    // Fetch just the byte-block hash
+    int32_t getHash() const {
+        return mByteHash;
+    }
+
+    // Copy the byte block to the target and return the current hash.
+    int32_t getByteBlock(block_t* block, size_t len) const {
+        memcpy(block, (void*) mByteBlock, std::min(maxByte, len));
+        return mByteHash;
+    }
+
+    // Set the byte block and the hash.
+    void setByteBlock(int hash, const block_t* block, size_t len) {
+        memcpy((void*) mByteBlock, block, len = std::min(maxByte, len));
+        mByteHash = hash;
+    }
+};
+
+/**
+ * Sizing parameters for the system_server PropertyInvalidatedCache support.  A client can
+ * retrieve the values through the accessors in CacheNonce instances.
+ */
+static const int MAX_NONCE = 64;
+static const int BYTE_BLOCK_SIZE = 8192;
+
+// The CacheNonce for system server holds 64 nonces with a string block of 8192 bytes.
+typedef CacheNonce<MAX_NONCE, BYTE_BLOCK_SIZE> SystemCacheNonce;
+
+// The goal of this assertion is to ensure that the data structure is the same size across 32-bit
+// and 64-bit systems.
+static_assert(sizeof(SystemCacheNonce) == SystemCacheNonce::expectedSize,
+              "Unexpected SystemCacheNonce size");
+
+} // namespace PropertyInvalidatedCache
+} // namespace app
+} // namespace android
diff --git a/core/jni/android_hardware_Camera.cpp b/core/jni/android_hardware_Camera.cpp
index 10e49ef..50252c1 100644
--- a/core/jni/android_hardware_Camera.cpp
+++ b/core/jni/android_hardware_Camera.cpp
@@ -27,6 +27,7 @@
 #include <camera/StringUtils.h>
 #include <com_android_internal_camera_flags.h>
 #include <cutils/properties.h>
+#include <gui/Flags.h>
 #include <gui/GLConsumer.h>
 #include <gui/Surface.h>
 #include <nativehelper/JNIHelp.h>
@@ -715,16 +716,20 @@
     sp<Camera> camera = get_native_camera(env, thiz, NULL);
     if (camera == 0) return;
 
-    sp<IGraphicBufferProducer> gbp;
     sp<Surface> surface;
     if (jSurface) {
         surface = android_view_Surface_getSurface(env, jSurface);
-        if (surface != NULL) {
-            gbp = surface->getIGraphicBufferProducer();
-        }
     }
 
+#if WB_LIBCAMERASERVICE_WITH_DEPENDENCIES
+    if (camera->setPreviewTarget(surface) != NO_ERROR) {
+#else
+    sp<IGraphicBufferProducer> gbp;
+    if (surface != NULL) {
+        gbp = surface->getIGraphicBufferProducer();
+    }
     if (camera->setPreviewTarget(gbp) != NO_ERROR) {
+#endif
         jniThrowException(env, "java/io/IOException", "setPreviewTexture failed");
     }
 }
@@ -736,6 +741,9 @@
     sp<Camera> camera = get_native_camera(env, thiz, NULL);
     if (camera == 0) return;
 
+#if WB_LIBCAMERASERVICE_WITH_DEPENDENCIES
+    sp<Surface> surface;
+#endif
     sp<IGraphicBufferProducer> producer = NULL;
     if (jSurfaceTexture != NULL) {
         producer = SurfaceTexture_getProducer(env, jSurfaceTexture);
@@ -744,10 +752,16 @@
                     "SurfaceTexture already released in setPreviewTexture");
             return;
         }
-
+#if WB_LIBCAMERASERVICE_WITH_DEPENDENCIES
+        surface = new Surface(producer);
+#endif
     }
 
+#if WB_LIBCAMERASERVICE_WITH_DEPENDENCIES
+    if (camera->setPreviewTarget(surface) != NO_ERROR) {
+#else
     if (camera->setPreviewTarget(producer) != NO_ERROR) {
+#endif
         jniThrowException(env, "java/io/IOException",
                 "setPreviewTexture failed");
     }
@@ -761,18 +775,32 @@
     sp<Camera> camera = get_native_camera(env, thiz, &context);
     if (camera == 0) return;
 
+#if !WB_LIBCAMERASERVICE_WITH_DEPENDENCIES
     sp<IGraphicBufferProducer> gbp;
+#endif
     sp<Surface> surface;
     if (jSurface) {
         surface = android_view_Surface_getSurface(env, jSurface);
+        if (surface == NULL) {
+            jniThrowException(env, "java/lang/IllegalArgumentException",
+                              "android_view_Surface_getSurface failed");
+            return;
+        }
+
+#if !WB_LIBCAMERASERVICE_WITH_DEPENDENCIES
         if (surface != NULL) {
             gbp = surface->getIGraphicBufferProducer();
         }
+#endif
     }
     // Clear out normal preview callbacks
     context->setCallbackMode(env, false, false);
     // Then set up callback surface
+#if WB_LIBCAMERASERVICE_WITH_DEPENDENCIES
+    if (camera->setPreviewCallbackTarget(surface) != NO_ERROR) {
+#else
     if (camera->setPreviewCallbackTarget(gbp) != NO_ERROR) {
+#endif
         jniThrowException(env, "java/io/IOException", "setPreviewCallbackTarget failed");
     }
 }
diff --git a/core/jni/android_view_SurfaceControl.cpp b/core/jni/android_view_SurfaceControl.cpp
index 755704a..f162b74 100644
--- a/core/jni/android_view_SurfaceControl.cpp
+++ b/core/jni/android_view_SurfaceControl.cpp
@@ -35,6 +35,7 @@
 #include <android_runtime/android_view_SurfaceSession.h>
 #include <cutils/ashmem.h>
 #include <gui/ISurfaceComposer.h>
+#include <gui/JankInfo.h>
 #include <gui/Surface.h>
 #include <gui/SurfaceComposerClient.h>
 #include <jni.h>
@@ -2161,9 +2162,30 @@
         jobjectArray jJankDataArray = env->NewObjectArray(jankData.size(),
                 gJankDataClassInfo.clazz, nullptr);
         for (size_t i = 0; i < jankData.size(); i++) {
+            // The exposed constants in SurfaceControl are simplified, so we need to translate the
+            // jank type we get from SF to what is exposed in Java.
+            int sfJankType = jankData[i].jankType;
+            int javaJankType = 0x0; // SurfaceControl.JankData.JANK_NONE
+            if (sfJankType &
+                (JankType::DisplayHAL | JankType::SurfaceFlingerCpuDeadlineMissed |
+                 JankType::SurfaceFlingerGpuDeadlineMissed | JankType::PredictionError |
+                 JankType::SurfaceFlingerScheduling)) {
+                javaJankType |= 0x1; // SurfaceControl.JankData.JANK_COMPOSER
+            }
+            if (sfJankType & JankType::AppDeadlineMissed) {
+                javaJankType |= 0x2; // SurfaceControl.JankData.JANK_APPLICATION
+            }
+            if (sfJankType &
+                ~(JankType::DisplayHAL | JankType::SurfaceFlingerCpuDeadlineMissed |
+                  JankType::SurfaceFlingerGpuDeadlineMissed | JankType::AppDeadlineMissed |
+                  JankType::PredictionError | JankType::SurfaceFlingerScheduling |
+                  JankType::BufferStuffing | JankType::SurfaceFlingerStuffing)) {
+                javaJankType |= 0x4; // SurfaceControl.JankData.JANK_OTHER
+            }
+
             jobject jJankData =
                     env->NewObject(gJankDataClassInfo.clazz, gJankDataClassInfo.ctor,
-                                   jankData[i].frameVsyncId, jankData[i].jankType,
+                                   jankData[i].frameVsyncId, javaJankType,
                                    jankData[i].frameIntervalNs, jankData[i].scheduledAppFrameTimeNs,
                                    jankData[i].actualAppFrameTimeNs);
             env->SetObjectArrayElement(jJankDataArray, i, jJankData);
diff --git a/core/jni/com_android_internal_os_ApplicationSharedMemory.cpp b/core/jni/com_android_internal_os_ApplicationSharedMemory.cpp
index 453e539..cc1687c 100644
--- a/core/jni/com_android_internal_os_ApplicationSharedMemory.cpp
+++ b/core/jni/com_android_internal_os_ApplicationSharedMemory.cpp
@@ -29,8 +29,12 @@
 
 #include "core_jni_helpers.h"
 
+#include "android_app_PropertyInvalidatedCache.h"
+
 namespace {
 
+using namespace android::app::PropertyInvalidatedCache;
+
 // Atomics should be safe to use across processes if they are lock free.
 static_assert(std::atomic<int64_t>::is_always_lock_free == true,
               "atomic<int64_t> is not always lock free");
@@ -64,12 +68,15 @@
     void setLatestNetworkTimeUnixEpochMillisAtZeroElapsedRealtimeMillis(int64_t offset) {
         latestNetworkTimeUnixEpochMillisAtZeroElapsedRealtimeMillis = offset;
     }
+
+    // The nonce storage for pic.  The sizing is suitable for the system server module.
+    SystemCacheNonce systemPic;
 };
 
 // Update the expected value when modifying the members of SharedMemory.
 // The goal of this assertion is to ensure that the data structure is the same size across 32-bit
 // and 64-bit systems.
-static_assert(sizeof(SharedMemory) == 8, "Unexpected SharedMemory size");
+static_assert(sizeof(SharedMemory) == 8 + sizeof(SystemCacheNonce), "Unexpected SharedMemory size");
 
 static jint nativeCreate(JNIEnv* env, jclass) {
     // Create anonymous shared memory region
@@ -133,6 +140,12 @@
     return sharedMemory->getLatestNetworkTimeUnixEpochMillisAtZeroElapsedRealtimeMillis();
 }
 
+// This is a FastNative method.  It takes the usual JNIEnv* and jclass* arguments.
+static jlong nativeGetSystemNonceBlock(JNIEnv*, jclass*, jlong ptr) {
+    SharedMemory* sharedMemory = reinterpret_cast<SharedMemory*>(ptr);
+    return reinterpret_cast<jlong>(&sharedMemory->systemPic);
+}
+
 static const JNINativeMethod gMethods[] = {
         {"nativeCreate", "()I", (void*)nativeCreate},
         {"nativeMap", "(IZ)J", (void*)nativeMap},
@@ -143,16 +156,17 @@
          (void*)nativeSetLatestNetworkTimeUnixEpochMillisAtZeroElapsedRealtimeMillis},
         {"nativeGetLatestNetworkTimeUnixEpochMillisAtZeroElapsedRealtimeMillis", "(J)J",
          (void*)nativeGetLatestNetworkTimeUnixEpochMillisAtZeroElapsedRealtimeMillis},
+        {"nativeGetSystemNonceBlock", "(J)J", (void*) nativeGetSystemNonceBlock},
 };
 
-} // anonymous namespace
-
-namespace android {
-
 static const char kApplicationSharedMemoryClassName[] =
         "com/android/internal/os/ApplicationSharedMemory";
 static jclass gApplicationSharedMemoryClass;
 
+} // anonymous namespace
+
+namespace android {
+
 int register_com_android_internal_os_ApplicationSharedMemory(JNIEnv* env) {
     gApplicationSharedMemoryClass =
             MakeGlobalRefOrDie(env, FindClassOrDie(env, kApplicationSharedMemoryClassName));
diff --git a/core/res/AndroidManifest.xml b/core/res/AndroidManifest.xml
index fb06e96..4c38246 100644
--- a/core/res/AndroidManifest.xml
+++ b/core/res/AndroidManifest.xml
@@ -8483,6 +8483,27 @@
     <permission android:name="android.permission.RESERVED_FOR_TESTING_SIGNATURE"
                 android:protectionLevel="signature"/>
 
+    <!--
+        @SystemApi
+        @FlaggedApi("android.media.tv.flags.media_quality_fw")
+        Allows an application to access its picture profile from the media quality database.
+        <p> Protection level: signature|privileged|vendor privileged
+        @hide
+    -->
+    <permission android:name="android.permission.MANAGE_GLOBAL_PICTURE_QUALITY_SERVICE"
+                android:protectionLevel="signature|privileged|vendorPrivileged"
+                android:featureFlag="android.media.tv.flags.media_quality_fw"/>
+
+    <!--
+        @SystemApi
+        @FlaggedApi("android.media.tv.flags.media_quality_fw")
+        Allows an application to access its sound profile from the media quality database.
+        <p> Protection level: signature|privileged|vendor privileged
+        @hide
+    -->
+    <permission android:name="android.permission.MANAGE_GLOBAL_SOUND_QUALITY_SERVICE"
+                android:protectionLevel="signature|privileged|vendorPrivileged"
+                android:featureFlag="android.media.tv.flags.media_quality_fw"/>
     <!-- @SystemApi
         @FlaggedApi("android.content.pm.verification_service")
         Allows app to be the verification agent to verify packages.
diff --git a/core/res/res/values/attrs.xml b/core/res/res/values/attrs.xml
index 02f9f3c..990cbb4 100644
--- a/core/res/res/values/attrs.xml
+++ b/core/res/res/values/attrs.xml
@@ -5731,6 +5731,12 @@
         </attr>
     </declare-styleable>
 
+    <!-- @hide internal use only -->
+    <declare-styleable name="NotificationProgressBar">
+        <!-- Draws the tracker on a NotificationProgressBar. -->
+        <attr name="tracker" format="reference" />
+    </declare-styleable>
+
     <declare-styleable name="StackView">
         <!-- Color of the res-out outline. -->
         <attr name="resOutColor" format="color" />
diff --git a/core/tests/BroadcastRadioTests/src/android/hardware/radio/RadioAlertUnitTest.java b/core/tests/BroadcastRadioTests/src/android/hardware/radio/RadioAlertUnitTest.java
new file mode 100644
index 0000000..7afdde2
--- /dev/null
+++ b/core/tests/BroadcastRadioTests/src/android/hardware/radio/RadioAlertUnitTest.java
@@ -0,0 +1,377 @@
+/*
+ * 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.hardware.radio;
+
+import static org.junit.Assert.assertThrows;
+
+import android.os.Parcel;
+import android.platform.test.annotations.EnableFlags;
+
+import com.google.common.truth.Expect;
+
+import org.junit.Rule;
+import org.junit.Test;
+
+import java.util.ArrayList;
+import java.util.List;
+
+@EnableFlags(Flags.FLAG_HD_RADIO_EMERGENCY_ALERT_SYSTEM)
+public final class RadioAlertUnitTest {
+
+    private static final int TEST_FLAGS = 0;
+    private static final int CREATOR_ARRAY_SIZE = 3;
+    private static final String TEST_GEOCODE_VALUE_NAME = "SAME";
+    private static final String TEST_GEOCODE_VALUE_1 = "006109";
+    private static final String TEST_GEOCODE_VALUE_2 = "006009";
+    private static final double TEST_POLYGON_LATITUDE_START = -38.47;
+    private static final double TEST_POLYGON_LONGITUDE_START = -120.14;
+    private static final RadioAlert.Coordinate TEST_POLYGON_COORDINATE_START =
+            new RadioAlert.Coordinate(TEST_POLYGON_LATITUDE_START, TEST_POLYGON_LONGITUDE_START);
+    private static final List<RadioAlert.Coordinate> TEST_COORDINATES = List.of(
+            TEST_POLYGON_COORDINATE_START, new RadioAlert.Coordinate(38.34, -119.95),
+            new RadioAlert.Coordinate(38.52, -119.74), new RadioAlert.Coordinate(38.62, -119.89),
+            TEST_POLYGON_COORDINATE_START);
+    private static final RadioAlert.Polygon TEST_POLYGON = new RadioAlert.Polygon(TEST_COORDINATES);
+    private static final RadioAlert.Geocode TEST_GEOCODE_1 = new RadioAlert.Geocode(
+            TEST_GEOCODE_VALUE_NAME, TEST_GEOCODE_VALUE_1);
+    private static final RadioAlert.Geocode TEST_GEOCODE_2 = new RadioAlert.Geocode(
+            TEST_GEOCODE_VALUE_NAME, TEST_GEOCODE_VALUE_2);
+    private static final RadioAlert.AlertArea TEST_AREA_1 = new RadioAlert.AlertArea(
+            List.of(TEST_POLYGON), List.of(TEST_GEOCODE_1));
+    private static final RadioAlert.AlertArea TEST_AREA_2 = new RadioAlert.AlertArea(
+            new ArrayList<>(), List.of(TEST_GEOCODE_1, TEST_GEOCODE_2));
+
+    @Rule
+    public final Expect mExpect = Expect.create();
+
+    @Test
+    public void constructor_withNullValueName_forGeocode_fails() {
+        NullPointerException thrown = assertThrows(NullPointerException.class, () ->
+                new RadioAlert.Geocode(/* valueName= */ null, TEST_GEOCODE_VALUE_1));
+
+        mExpect.withMessage("Exception for geocode constructor with null value name")
+                .that(thrown).hasMessageThat()
+                .contains("Geocode value name can not be null");
+    }
+
+    @Test
+    public void constructor_withNullValue_forGeocode_fails() {
+        NullPointerException thrown = assertThrows(NullPointerException.class, () ->
+                new RadioAlert.Geocode(TEST_GEOCODE_VALUE_NAME, /* value= */ null));
+
+        mExpect.withMessage("Exception for geocode constructor with null value")
+                .that(thrown).hasMessageThat()
+                .contains("Geocode value can not be null");
+    }
+
+    @Test
+    public void getValueName_forGeocode() {
+        mExpect.withMessage("Value name of geocode").that(TEST_GEOCODE_1.getValueName())
+                .isEqualTo(TEST_GEOCODE_VALUE_NAME);
+    }
+
+    @Test
+    public void getValue_forGeocode() {
+        mExpect.withMessage("Value of geocode").that(TEST_GEOCODE_1.getValue())
+                .isEqualTo(TEST_GEOCODE_VALUE_1);
+    }
+
+    @Test
+    public void describeContents_forGeocode() {
+        mExpect.withMessage("Contents of geocode")
+                .that(TEST_GEOCODE_1.describeContents()).isEqualTo(0);
+    }
+
+    @Test
+    public void writeToParcel_forGeocode() {
+        Parcel parcel = Parcel.obtain();
+
+        TEST_GEOCODE_1.writeToParcel(parcel, TEST_FLAGS);
+
+        parcel.setDataPosition(0);
+        RadioAlert.Geocode geocodeFromParcel = RadioAlert.Geocode.CREATOR.createFromParcel(parcel);
+        mExpect.withMessage("Geocode from parcel").that(geocodeFromParcel)
+                .isEqualTo(TEST_GEOCODE_1);
+    }
+
+    @Test
+    public void newArray_forGeocodeCreator() {
+        RadioAlert.Geocode[] geocodes = RadioAlert.Geocode.CREATOR.newArray(CREATOR_ARRAY_SIZE);
+
+        mExpect.withMessage("Geocodes").that(geocodes).hasLength(CREATOR_ARRAY_SIZE);
+    }
+
+    @Test
+    public void hashCode_withSameGeocodes() {
+        RadioAlert.Geocode geocodeCompared = new RadioAlert.Geocode(TEST_GEOCODE_VALUE_NAME,
+                TEST_GEOCODE_VALUE_1);
+
+        mExpect.withMessage("Hash code of the same gecode")
+                .that(geocodeCompared.hashCode()).isEqualTo(TEST_GEOCODE_1.hashCode());
+    }
+
+    @Test
+    public void equals_withDifferentGeocodes() {
+        mExpect.withMessage("Different geocode").that(TEST_GEOCODE_1)
+                .isNotEqualTo(TEST_GEOCODE_2);
+    }
+
+    @Test
+    @SuppressWarnings("TruthIncompatibleType")
+    public void equals_withDifferentTypeObject_forGeocode() {
+        mExpect.withMessage("Non-geocode object").that(TEST_GEOCODE_1)
+                .isNotEqualTo(TEST_POLYGON_COORDINATE_START);
+    }
+
+    @Test
+    public void constructor_withInvalidLatitude_forCoordinate_fails() {
+        IllegalArgumentException thrown = assertThrows(IllegalArgumentException.class, () ->
+                new RadioAlert.Coordinate(/* latitude= */ -92.0, TEST_POLYGON_LONGITUDE_START));
+
+        mExpect.withMessage("Exception for coordinate constructor with invalid latitude")
+                .that(thrown).hasMessageThat().contains("Latitude");
+    }
+
+    @Test
+    public void constructor_withInvalidLongitude_forCoordinate_fails() {
+        IllegalArgumentException thrown = assertThrows(IllegalArgumentException.class, () ->
+                new RadioAlert.Coordinate(TEST_POLYGON_LATITUDE_START, /* longitude= */ 200.0));
+
+        mExpect.withMessage("Exception for coordinate constructor with invalid longitude")
+                .that(thrown).hasMessageThat().contains("Longitude");
+    }
+
+    @Test
+    public void getLatitude_forCoordinate() {
+        mExpect.withMessage("Latitude of coordinate")
+                .that(TEST_POLYGON_COORDINATE_START.getLatitude())
+                .isEqualTo(TEST_POLYGON_LATITUDE_START);
+    }
+
+    @Test
+    public void getLongitude_forCoordinate() {
+        mExpect.withMessage("Longitude of coordinate")
+                .that(TEST_POLYGON_COORDINATE_START.getLongitude())
+                .isEqualTo(TEST_POLYGON_LONGITUDE_START);
+    }
+
+    @Test
+    public void describeContents_forCoordinate() {
+        mExpect.withMessage("Contents of coordinate")
+                .that(TEST_POLYGON_COORDINATE_START.describeContents()).isEqualTo(0);
+    }
+
+    @Test
+    public void writeToParcel_forCoordinate() {
+        Parcel parcel = Parcel.obtain();
+
+        TEST_POLYGON_COORDINATE_START.writeToParcel(parcel, TEST_FLAGS);
+
+        parcel.setDataPosition(0);
+        RadioAlert.Coordinate coordinateFromParcel = RadioAlert.Coordinate.CREATOR
+                .createFromParcel(parcel);
+        mExpect.withMessage("Coordinate from parcel").that(coordinateFromParcel)
+                .isEqualTo(TEST_POLYGON_COORDINATE_START);
+    }
+
+    @Test
+    public void newArray_forCoordinateCreator() {
+        RadioAlert.Coordinate[] coordinates = RadioAlert.Coordinate.CREATOR
+                .newArray(CREATOR_ARRAY_SIZE);
+
+        mExpect.withMessage("Coordinates").that(coordinates).hasLength(CREATOR_ARRAY_SIZE);
+    }
+
+    @Test
+    public void hashCode_withSameCoordinates() {
+        RadioAlert.Coordinate coordinateCompared = new RadioAlert.Coordinate(
+                TEST_POLYGON_LATITUDE_START, TEST_POLYGON_LONGITUDE_START);
+
+        mExpect.withMessage("Hash code of the same coordinate")
+                .that(coordinateCompared.hashCode())
+                .isEqualTo(TEST_POLYGON_COORDINATE_START.hashCode());
+    }
+
+    @Test
+    public void equals_withDifferentCoordinates() {
+        mExpect.withMessage("Different coordinate").that(TEST_POLYGON_COORDINATE_START)
+                .isNotEqualTo(TEST_COORDINATES.get(1));
+    }
+
+    @Test
+    @SuppressWarnings("TruthIncompatibleType")
+    public void equals_withDifferentTypeObject_forCoordinate() {
+        mExpect.withMessage("Non-coordinate object").that(TEST_POLYGON_COORDINATE_START)
+                .isNotEqualTo(TEST_GEOCODE_1);
+    }
+
+    @Test
+    public void constructor_withNullCoordinates_forPolygon_fails() {
+        NullPointerException thrown = assertThrows(NullPointerException.class, () ->
+                new RadioAlert.Polygon(/* coordinates= */ null));
+
+        mExpect.withMessage("Exception for polygon constructor with null coordinates")
+                .that(thrown).hasMessageThat().contains("Coordinates can not be null");
+    }
+
+    @Test
+    public void constructor_withLessThanFourCoordinates_forPolygon_fails() {
+        IllegalArgumentException thrown = assertThrows(IllegalArgumentException.class, () ->
+                new RadioAlert.Polygon(List.of(TEST_POLYGON_COORDINATE_START,
+                        TEST_POLYGON_COORDINATE_START)));
+
+        mExpect.withMessage("Exception for polygon constructor with less than four coordinates")
+                .that(thrown).hasMessageThat().contains("Number of coordinates must be at least 4");
+    }
+
+    @Test
+    public void constructor_withDifferentFirstAndLastCoordinates_forPolygon_fails() {
+        IllegalArgumentException thrown = assertThrows(IllegalArgumentException.class, () ->
+                new RadioAlert.Polygon(List.of(TEST_POLYGON_COORDINATE_START,
+                        new RadioAlert.Coordinate(38.34, -119.95),
+                        new RadioAlert.Coordinate(38.52, -119.74),
+                        new RadioAlert.Coordinate(38.62, -119.89))));
+
+        mExpect.withMessage(
+                "Exception for polygon constructor with different first and last coordinates")
+                .that(thrown).hasMessageThat().contains(
+                        "last and first coordinates must be the same");
+    }
+
+    @Test
+    public void getCoordinates_forPolygon() {
+        mExpect.withMessage("Coordinates in polygon").that(TEST_POLYGON.getCoordinates())
+                .containsExactlyElementsIn(TEST_COORDINATES);
+    }
+
+    @Test
+    public void describeContents_forPolygon() {
+        mExpect.withMessage("Contents of polygon")
+                .that(TEST_POLYGON.describeContents()).isEqualTo(0);
+    }
+
+    @Test
+    public void writeToParcel_forPolygon() {
+        Parcel parcel = Parcel.obtain();
+
+        TEST_POLYGON.writeToParcel(parcel, TEST_FLAGS);
+
+        parcel.setDataPosition(0);
+        RadioAlert.Polygon polygonFromParcel = RadioAlert.Polygon.CREATOR.createFromParcel(parcel);
+        mExpect.withMessage("Polygon from parcel").that(polygonFromParcel)
+                .isEqualTo(TEST_POLYGON);
+    }
+
+    @Test
+    public void newArray_forPolygonCreator() {
+        RadioAlert.Polygon[] polygons = RadioAlert.Polygon.CREATOR.newArray(CREATOR_ARRAY_SIZE);
+
+        mExpect.withMessage("Polygons").that(polygons).hasLength(CREATOR_ARRAY_SIZE);
+    }
+
+    @Test
+    public void hashCode_withSamePolygons() {
+        RadioAlert.Polygon polygonCompared = new RadioAlert.Polygon(TEST_COORDINATES);
+
+        mExpect.withMessage("Hash code of the same polygon")
+                .that(polygonCompared.hashCode()).isEqualTo(TEST_POLYGON.hashCode());
+    }
+
+    @Test
+    @SuppressWarnings("TruthIncompatibleType")
+    public void equals_withDifferentTypeObject_forPolygon() {
+        mExpect.withMessage("Non-polygon object").that(TEST_POLYGON)
+                .isNotEqualTo(TEST_GEOCODE_1);
+    }
+
+    @Test
+    public void constructor_withNullPolygons_forAlertArea_fails() {
+        NullPointerException thrown = assertThrows(NullPointerException.class, () ->
+                new RadioAlert.AlertArea(/* polygons= */ null, List.of(TEST_GEOCODE_1)));
+
+        mExpect.withMessage("Exception for alert area constructor with null polygon list")
+                .that(thrown).hasMessageThat().contains("Polygons can not be null");
+    }
+
+    @Test
+    public void constructor_withNullGeocodes_forAlertArea_fails() {
+        NullPointerException thrown = assertThrows(NullPointerException.class, () ->
+                new RadioAlert.AlertArea(List.of(TEST_POLYGON), /* geocodes= */ null));
+
+        mExpect.withMessage("Exception for alert area constructor with null geocode list")
+                .that(thrown).hasMessageThat().contains("Geocodes can not be null");
+    }
+
+    @Test
+    public void getPolygons_forAlertArea() {
+        mExpect.withMessage("Polygons in alert area").that(TEST_AREA_1.getPolygons())
+                .containsExactly(TEST_POLYGON);
+    }
+
+    @Test
+    public void getGeocodes_forAlertArea() {
+        mExpect.withMessage("Polygons in alert area").that(TEST_AREA_2.getGeocodes())
+                .containsExactly(TEST_GEOCODE_1, TEST_GEOCODE_2);
+    }
+
+    @Test
+    public void describeContents_forAlertArea() {
+        mExpect.withMessage("Contents of alert area")
+                .that(TEST_AREA_1.describeContents()).isEqualTo(0);
+    }
+
+    @Test
+    public void writeToParcel_forAlertArea() {
+        Parcel parcel = Parcel.obtain();
+
+        TEST_AREA_1.writeToParcel(parcel, TEST_FLAGS);
+
+        parcel.setDataPosition(0);
+        RadioAlert.AlertArea areaFromParcel = RadioAlert.AlertArea.CREATOR.createFromParcel(parcel);
+        mExpect.withMessage("Alert area from parcel").that(areaFromParcel)
+                .isEqualTo(TEST_AREA_1);
+    }
+
+    @Test
+    public void newArray_forAlertAreaCreator() {
+        RadioAlert.AlertArea[] alertAreas = RadioAlert.AlertArea.CREATOR
+                .newArray(CREATOR_ARRAY_SIZE);
+
+        mExpect.withMessage("Alert areas").that(alertAreas).hasLength(CREATOR_ARRAY_SIZE);
+    }
+
+    @Test
+    public void hashCode_withSameAlertAreas() {
+        RadioAlert.AlertArea alertAreaCompared = new RadioAlert.AlertArea(List.of(TEST_POLYGON),
+                List.of(TEST_GEOCODE_1));
+
+        mExpect.withMessage("Hash code of the same alert area")
+                .that(alertAreaCompared.hashCode()).isEqualTo(TEST_AREA_1.hashCode());
+    }
+
+    @Test
+    public void equals_withDifferentAlertAreas() {
+        mExpect.withMessage("Different alert area").that(TEST_AREA_1).isNotEqualTo(TEST_AREA_2);
+    }
+
+    @Test
+    @SuppressWarnings("TruthIncompatibleType")
+    public void equals_withDifferentTypeObject_forAlertArea() {
+        mExpect.withMessage("Non-alert-area object").that(TEST_AREA_1)
+                .isNotEqualTo(TEST_GEOCODE_1);
+    }
+}
diff --git a/core/tests/coretests/Android.bp b/core/tests/coretests/Android.bp
index 56e18e6..aee1c3b 100644
--- a/core/tests/coretests/Android.bp
+++ b/core/tests/coretests/Android.bp
@@ -146,6 +146,10 @@
         ":BinderProxyCountingTestService",
         ":AppThatUsesAppOps",
         ":AppThatCallsBinderMethods",
+        ":HelloWorldSdk1",
+        ":HelloWorldUsingSdk1AndSdk1",
+        ":HelloWorldUsingSdk1And2",
+        ":HelloWorldUsingSdkMalformedNegativeVersion",
     ],
 }
 
diff --git a/core/tests/coretests/AndroidTest.xml b/core/tests/coretests/AndroidTest.xml
index 05ab783..3bc8172 100644
--- a/core/tests/coretests/AndroidTest.xml
+++ b/core/tests/coretests/AndroidTest.xml
@@ -29,6 +29,18 @@
         <option name="test-file-name" value="AppThatCallsBinderMethods.apk" />
     </target_preparer>
 
+    <target_preparer class="com.android.tradefed.targetprep.PushFilePreparer">
+        <option name="cleanup" value="true"/>
+        <option name="push-file" key="HelloWorldUsingSdk1And2.apk"
+            value="/data/local/tmp/tests/coretests/pm/HelloWorldUsingSdk1And2.apk"/>
+        <option name="push-file" key="HelloWorldUsingSdk1AndSdk1.apk"
+            value="/data/local/tmp/tests/coretests/pm/HelloWorldUsingSdk1AndSdk1.apk"/>
+        <option name="push-file" key="HelloWorldUsingSdkMalformedNegativeVersion.apk"
+            value="/data/local/tmp/tests/coretests/pm/HelloWorldUsingSdkMalformedNegativeVersion.apk"/>
+        <option name="push-file" key="HelloWorldSdk1.apk"
+            value="/data/local/tmp/tests/coretests/pm/HelloWorldSdk1.apk"/>
+    </target_preparer>
+
     <target_preparer class="com.android.tradefed.targetprep.RunCommandTargetPreparer">
         <!-- TODO(b/254155965): Design a mechanism to finally remove this command. -->
         <option name="run-command" value="settings put global device_config_sync_disabled 0" />
diff --git a/core/tests/coretests/src/android/app/NotificationTest.java b/core/tests/coretests/src/android/app/NotificationTest.java
index 23a0985..a2ff4ca 100644
--- a/core/tests/coretests/src/android/app/NotificationTest.java
+++ b/core/tests/coretests/src/android/app/NotificationTest.java
@@ -16,6 +16,7 @@
 
 package android.app;
 
+import static android.app.Notification.Action.EXTRA_DATA_ONLY_INPUTS;
 import static android.app.Notification.CarExtender.UnreadConversation.KEY_ON_READ;
 import static android.app.Notification.CarExtender.UnreadConversation.KEY_ON_REPLY;
 import static android.app.Notification.CarExtender.UnreadConversation.KEY_REMOTE_INPUT;
@@ -97,6 +98,7 @@
 import android.text.style.StyleSpan;
 import android.text.style.TextAppearanceSpan;
 import android.util.Pair;
+import android.view.View;
 import android.widget.RemoteViews;
 
 import androidx.test.InstrumentationRegistry;
@@ -106,10 +108,10 @@
 import com.android.internal.R;
 import com.android.internal.util.ContrastColorUtil;
 
-import junit.framework.Assert;
-
 import libcore.junit.util.compat.CoreCompatChangeRule;
 
+import junit.framework.Assert;
+
 import org.junit.Before;
 import org.junit.Ignore;
 import org.junit.Rule;
@@ -545,6 +547,22 @@
     }
 
     @Test
+    public void largeIconMultipleReferences_ignoreBadData() {
+        Icon originalIcon = Icon.createWithBitmap(BitmapFactory.decodeResource(
+                mContext.getResources(), com.android.frameworks.coretests.R.drawable.test128x96));
+
+        Notification n = new Notification.Builder(mContext).setLargeIcon(originalIcon).build();
+        assertSame(n.getLargeIcon(), originalIcon);
+        n.extras.putParcelable(EXTRA_LARGE_ICON, new NotificationChannelGroup("hi", "hi"));
+
+        Notification q = writeAndReadParcelable(n);
+        assertNotSame(q.getLargeIcon(), n.getLargeIcon());
+
+        assertTrue(q.getLargeIcon().getBitmap().sameAs(n.getLargeIcon().getBitmap()));
+        assertSame(q.getLargeIcon(), q.extras.getParcelable(EXTRA_LARGE_ICON));
+    }
+
+    @Test
     public void largeIconReferenceInExtrasOnly_keptAfterParcelling() {
         Icon originalIcon = Icon.createWithBitmap(BitmapFactory.decodeResource(
                 mContext.getResources(), com.android.frameworks.coretests.R.drawable.test128x96));
@@ -2444,6 +2462,69 @@
 
         assertThat(progressStyle1.isStyledByProgress()).isTrue();
     }
+
+    @Test
+    public void getDataOnlyRemoteInputs_invalidData() {
+        Notification.Action action = new Notification.Action.Builder(0, "title", null)
+                .addRemoteInput(new RemoteInput.Builder("result")
+                        .setAllowFreeFormInput(false)
+                        .setAllowDataType("mimeType", true)
+                        .build()).build();
+        action.getExtras().putParcelable(EXTRA_DATA_ONLY_INPUTS,
+                new NotificationChannelGroup("hi", "hi"));
+        assertThat(action.getDataOnlyRemoteInputs()).isNull();
+    }
+
+    @Test
+    public void actionAddExtras_invalidData() {
+        Bundle extras = new Bundle();
+        extras.putParcelableArray(EXTRA_DATA_ONLY_INPUTS,
+                new NotificationChannelGroup[]{new NotificationChannelGroup("hi", "hi")});
+        Notification.Action action = new Notification.Action.Builder(0, "title", null)
+                .addRemoteInput(new RemoteInput.Builder("result")
+                        .setAllowFreeFormInput(false)
+                        .setAllowDataType("mimeType", true)
+                        .addExtras(extras)
+                        .build()).build();
+        assertThat(action.getDataOnlyRemoteInputs()[0].getClass()).isEqualTo(RemoteInput.class);
+    }
+
+    @Test
+    public void makeBigTemplate_invalidData() {
+        PendingIntent pendingIntent = PendingIntent.getBroadcast(mContext, 0,
+                new Intent(mContext, NotificationTest.class),
+                PendingIntent.FLAG_MUTABLE | PendingIntent.FLAG_UPDATE_CURRENT);
+        RemoteInput remoteInput =
+                new RemoteInput.Builder("key_text_reply")
+                        .setLabel("Reply")
+                        .setChoices(new String[]{"Choice 1", "Choice 2"})
+                        .addExtras(new Bundle())
+                        .build();
+        Notification.Action replyAction = new Notification.Action.Builder(
+                R.drawable.stat_notify_chat, "Reply", pendingIntent)
+                .addRemoteInput(remoteInput)
+                .build();
+
+        Bundle bundle = new Bundle();
+        bundle.putParcelableArray(Notification.EXTRA_REMOTE_INPUT_HISTORY_ITEMS,
+                new NotificationChannelGroup[]{});
+
+        Notification.Builder nb = new Notification.Builder(mContext, "channel")
+                .setContentTitle("title")
+                .setVisibility(android.app.Notification.VISIBILITY_PUBLIC)
+                .setStyle(new Notification.InboxStyle())
+                .setExtras(bundle)
+                .setSmallIcon(R.drawable.stat_notify_chat)
+                .addAction(replyAction);
+
+        RemoteViews views = nb.createBigContentView();
+        View view = views.apply(mContext, null);
+        assertThat(view.findViewById(R.id.notification_material_reply_container).getVisibility())
+                .isNotEqualTo(View.VISIBLE);
+        assertThat(view.findViewById(R.id.inbox_text0).getVisibility())
+                .isNotEqualTo(View.VISIBLE);
+    }
+
     private void assertValid(Notification.Colors c) {
         // Assert that all colors are populated
         assertThat(c.getBackgroundColor()).isNotEqualTo(Notification.COLOR_INVALID);
diff --git a/core/tests/coretests/src/android/app/PropertyInvalidatedCacheTests.java b/core/tests/coretests/src/android/app/PropertyInvalidatedCacheTests.java
index dcea5b2..65153f5 100644
--- a/core/tests/coretests/src/android/app/PropertyInvalidatedCacheTests.java
+++ b/core/tests/coretests/src/android/app/PropertyInvalidatedCacheTests.java
@@ -16,13 +16,23 @@
 
 package android.app;
 
+import static android.app.PropertyInvalidatedCache.NONCE_UNSET;
+import static android.app.PropertyInvalidatedCache.NonceStore.INVALID_NONCE_INDEX;
+import static com.android.internal.os.Flags.FLAG_APPLICATION_SHARED_MEMORY_ENABLED;
+
 import static org.junit.Assert.assertEquals;
+import static org.junit.Assert.assertNotEquals;
 import static org.junit.Assert.assertNotSame;
 import static org.junit.Assert.assertSame;
 import static org.junit.Assert.assertTrue;
 import static org.junit.Assert.fail;
 
+import com.android.internal.os.ApplicationSharedMemory;
+
 import android.platform.test.annotations.IgnoreUnderRavenwood;
+import android.platform.test.annotations.RequiresFlagsEnabled;
+import android.platform.test.flag.junit.CheckFlagsRule;
+import android.platform.test.flag.junit.DeviceFlagsValueProvider;
 import android.platform.test.ravenwood.RavenwoodRule;
 
 import androidx.test.filters.SmallTest;
@@ -47,6 +57,9 @@
     @Rule
     public final RavenwoodRule mRavenwood = new RavenwoodRule();
 
+    public final CheckFlagsRule mCheckFlagsRule =
+            DeviceFlagsValueProvider.createCheckFlagsRule();
+
     // Configuration for creating caches
     private static final String MODULE = PropertyInvalidatedCache.MODULE_TEST;
     private static final String API = "testApi";
@@ -423,4 +436,54 @@
         // Re-enable test mode (so that the cleanup for the test does not throw).
         PropertyInvalidatedCache.setTestMode(true);
     }
+
+    // Verify the behavior of shared memory nonce storage.  This does not directly test the cache
+    // storing nonces in shared memory.
+    @RequiresFlagsEnabled(FLAG_APPLICATION_SHARED_MEMORY_ENABLED)
+    @Test
+    public void testSharedMemoryStorage() {
+        // Fetch a shared memory instance for testing.
+        ApplicationSharedMemory shmem = ApplicationSharedMemory.create();
+
+        // Create a server-side store and a client-side store.  The server's store is mutable and
+        // the client's store is not mutable.
+        PropertyInvalidatedCache.NonceStore server =
+                new PropertyInvalidatedCache.NonceStore(shmem.getSystemNonceBlock(), true);
+        PropertyInvalidatedCache.NonceStore client =
+                new PropertyInvalidatedCache.NonceStore(shmem.getSystemNonceBlock(), false);
+
+        final String name1 = "name1";
+        assertEquals(server.getHandleForName(name1), INVALID_NONCE_INDEX);
+        assertEquals(client.getHandleForName(name1), INVALID_NONCE_INDEX);
+        final int index1 = server.storeName(name1);
+        assertNotEquals(index1, INVALID_NONCE_INDEX);
+        assertEquals(server.getHandleForName(name1), index1);
+        assertEquals(client.getHandleForName(name1), index1);
+        assertEquals(server.storeName(name1), index1);
+
+        assertEquals(server.getNonce(index1), NONCE_UNSET);
+        assertEquals(client.getNonce(index1), NONCE_UNSET);
+        final int value1 = 4;
+        server.setNonce(index1, value1);
+        assertEquals(server.getNonce(index1), value1);
+        assertEquals(client.getNonce(index1), value1);
+        final int value2 = 8;
+        server.setNonce(index1, value2);
+        assertEquals(server.getNonce(index1), value2);
+        assertEquals(client.getNonce(index1), value2);
+
+        final String name2 = "name2";
+        assertEquals(server.getHandleForName(name2), INVALID_NONCE_INDEX);
+        assertEquals(client.getHandleForName(name2), INVALID_NONCE_INDEX);
+        final int index2 = server.storeName(name2);
+        assertNotEquals(index2, INVALID_NONCE_INDEX);
+        assertEquals(server.getHandleForName(name2), index2);
+        assertEquals(client.getHandleForName(name2), index2);
+        assertEquals(server.storeName(name2), index2);
+
+        // The names are different, so the indices must be different.
+        assertNotEquals(index1, index2);
+
+        shmem.close();
+    }
 }
diff --git a/core/tests/coretests/src/android/content/pm/parsing/ApkLiteParseUtilsTest.java b/core/tests/coretests/src/android/content/pm/parsing/ApkLiteParseUtilsTest.java
new file mode 100644
index 0000000..f9b481f
--- /dev/null
+++ b/core/tests/coretests/src/android/content/pm/parsing/ApkLiteParseUtilsTest.java
@@ -0,0 +1,301 @@
+/*
+ * 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.content.pm.parsing;
+
+import static android.content.pm.PackageManager.GET_SIGNING_CERTIFICATES;
+import static android.content.pm.PackageManager.MATCH_STATIC_SHARED_AND_SDK_LIBRARIES;
+
+import static com.google.common.truth.Truth.assertThat;
+
+import android.annotation.SuppressLint;
+import android.app.UiAutomation;
+import android.content.pm.ApplicationInfo;
+import android.content.pm.PackageInfo;
+import android.content.pm.PackageManager;
+import android.content.pm.Signature;
+import android.content.pm.SigningInfo;
+import android.content.pm.parsing.result.ParseResult;
+import android.content.pm.parsing.result.ParseTypeImpl;
+import android.os.FileUtils;
+import android.os.ParcelFileDescriptor;
+import android.platform.test.annotations.Presubmit;
+import android.util.ArraySet;
+import android.util.PackageUtils;
+
+import androidx.annotation.NonNull;
+import androidx.test.InstrumentationRegistry;
+
+import com.android.internal.pm.parsing.PackageParser2;
+import com.android.server.pm.pkg.AndroidPackage;
+
+import libcore.util.HexEncoding;
+
+import org.junit.After;
+import org.junit.Before;
+import org.junit.Rule;
+import org.junit.Test;
+import org.junit.rules.TemporaryFolder;
+
+import java.io.ByteArrayOutputStream;
+import java.io.File;
+import java.io.FileInputStream;
+import java.io.IOException;
+import java.io.InputStream;
+import java.io.OutputStream;
+import java.util.Arrays;
+import java.util.List;
+import java.util.Set;
+
+@Presubmit
+public class ApkLiteParseUtilsTest {
+
+    @Rule
+    public TemporaryFolder mTemporaryFolder = new TemporaryFolder();
+
+    private static final String PUSH_FILE_DIR = "/data/local/tmp/tests/coretests/pm/";
+    private static final String TEST_APP_USING_SDK1_AND_SDK2 = "HelloWorldUsingSdk1And2.apk";
+    private static final String TEST_APP_USING_SDK1_AND_SDK1 = "HelloWorldUsingSdk1AndSdk1.apk";
+    private static final String TEST_APP_USING_SDK_MALFORMED_VERSION =
+            "HelloWorldUsingSdkMalformedNegativeVersion.apk";
+    private static final String TEST_SDK1 = "HelloWorldSdk1.apk";
+    private static final String TEST_SDK1_PACKAGE = "com.test.sdk1_1";
+    private static final String TEST_SDK1_NAME = "com.test.sdk1";
+    private static final long TEST_SDK1_VERSION = 1;
+    private static final String TEST_SDK2_NAME = "com.test.sdk2";
+    private static final long TEST_SDK2_VERSION = 2;
+
+    private final PackageParser2 mPackageParser2 = new PackageParser2(
+            null, null, null, new FakePackageParser2Callback());
+
+    private File mTmpDir = null;
+
+    @Before
+    public void setUp() throws IOException {
+        mTmpDir = mTemporaryFolder.newFolder("DexMetadataHelperTest");
+    }
+
+    @After
+    public void tearDown() throws Exception {
+        setSystemProperty("debug.pm.uses_sdk_library_default_cert_digest", "invalid");
+        uninstallPackageSilently(TEST_SDK1_NAME);
+    }
+
+    @SuppressLint("CheckResult")
+    @Test
+    public void testParseApkLite_getUsesSdkLibrary() throws Exception {
+        File apkFile = copyApkToTmpDir(TEST_APP_USING_SDK1_AND_SDK2);
+        ParseResult<ApkLite> result = ApkLiteParseUtils
+                .parseApkLite(ParseTypeImpl.forDefaultParsing().reset(), apkFile, 0);
+        assertThat(result.isError()).isFalse();
+
+        ApkLite baseApk = result.getResult();
+        assertThat(baseApk.getUsesSdkLibraries()).containsExactly(TEST_SDK1_NAME, TEST_SDK2_NAME);
+        assertThat(baseApk.getUsesSdkLibrariesVersionsMajor()).asList().containsExactly(
+                TEST_SDK1_VERSION, TEST_SDK2_VERSION
+        );
+        for (String[] certDigests: baseApk.getUsesSdkLibrariesCertDigests()) {
+            assertThat(certDigests).asList().containsExactly("");
+        }
+    }
+
+    @SuppressLint("CheckResult")
+    @Test
+    public void testParseApkLite_getUsesSdkLibrary_overrideCertDigest() throws Exception {
+        installPackage(TEST_SDK1);
+        String certDigest = getPackageCertDigest(TEST_SDK1_PACKAGE);
+        overrideUsesSdkLibraryCertificateDigest(certDigest);
+
+        File apkFile = copyApkToTmpDir(TEST_APP_USING_SDK1_AND_SDK2);
+        ParseResult<ApkLite> result = ApkLiteParseUtils
+                .parseApkLite(ParseTypeImpl.forDefaultParsing().reset(), apkFile, 0);
+        ApkLite baseApk = result.getResult();
+
+        String[][] liteCerts = baseApk.getUsesSdkLibrariesCertDigests();
+        assertThat(liteCerts).isNotNull();
+        for (String[] certDigests: liteCerts) {
+            assertThat(certDigests).asList().containsExactly(certDigest);
+        }
+
+        // Same for package parser
+        AndroidPackage pkg = mPackageParser2.parsePackage(apkFile, 0, true).hideAsFinal();
+        String[][] pkgCerts = pkg.getUsesSdkLibrariesCertDigests();
+        assertThat(pkgCerts).isNotNull();
+        for (int i = 0; i < liteCerts.length; i++) {
+            assertThat(liteCerts[i]).isEqualTo(pkgCerts[i]);
+        }
+    }
+
+
+    @SuppressLint("CheckResult")
+    @Test
+    public void testParseApkLite_getUsesSdkLibrary_sameAsPackageParser() throws Exception {
+        File apkFile = copyApkToTmpDir(TEST_APP_USING_SDK1_AND_SDK2);
+        ParseResult<ApkLite> result = ApkLiteParseUtils
+                .parseApkLite(ParseTypeImpl.forDefaultParsing().reset(), apkFile, 0);
+        assertThat(result.isError()).isFalse();
+        ApkLite baseApk = result.getResult();
+
+        AndroidPackage pkg = mPackageParser2.parsePackage(apkFile, 0, true).hideAsFinal();
+        assertThat(baseApk.getUsesSdkLibraries())
+                .containsExactlyElementsIn(pkg.getUsesSdkLibraries());
+        List<Long> versionsBoxed = Arrays.stream(pkg.getUsesSdkLibrariesVersionsMajor()).boxed()
+                .toList();
+        assertThat(baseApk.getUsesSdkLibrariesVersionsMajor()).asList()
+                .containsExactlyElementsIn(versionsBoxed);
+
+        String[][] liteCerts = baseApk.getUsesSdkLibrariesCertDigests();
+        String[][] pkgCerts = pkg.getUsesSdkLibrariesCertDigests();
+        for (int i = 0; i < liteCerts.length; i++) {
+            assertThat(liteCerts[i]).isEqualTo(pkgCerts[i]);
+        }
+    }
+
+    @SuppressLint("CheckResult")
+    @Test
+    public void testParseApkLite_malformedUsesSdkLibrary_duplicate() throws Exception {
+        File apkFile = copyApkToTmpDir(TEST_APP_USING_SDK1_AND_SDK1);
+        ParseResult<ApkLite> result = ApkLiteParseUtils
+                .parseApkLite(ParseTypeImpl.forDefaultParsing().reset(), apkFile, 0);
+        assertThat(result.isError()).isTrue();
+        assertThat(result.getErrorMessage()).contains("Bad uses-sdk-library declaration");
+        assertThat(result.getErrorMessage()).contains(
+                "Depending on multiple versions of SDK library");
+    }
+
+    @SuppressLint("CheckResult")
+    @Test
+    public void testParseApkLite_malformedUsesSdkLibrary_missingVersion() throws Exception {
+        File apkFile = copyApkToTmpDir(TEST_APP_USING_SDK_MALFORMED_VERSION);
+        ParseResult<ApkLite> result = ApkLiteParseUtils
+                .parseApkLite(ParseTypeImpl.forDefaultParsing().reset(), apkFile, 0);
+        assertThat(result.isError()).isTrue();
+        assertThat(result.getErrorMessage()).contains("Bad uses-sdk-library declaration");
+    }
+
+    private String getPackageCertDigest(String packageName) throws Exception {
+        getUiAutomation().adoptShellPermissionIdentity();
+        try {
+            PackageInfo sdkPackageInfo = getPackageManager().getPackageInfo(packageName,
+                    PackageManager.PackageInfoFlags.of(
+                            GET_SIGNING_CERTIFICATES | MATCH_STATIC_SHARED_AND_SDK_LIBRARIES));
+            SigningInfo signingInfo = sdkPackageInfo.signingInfo;
+            Signature[] signatures =
+                    signingInfo != null ? signingInfo.getSigningCertificateHistory() : null;
+            byte[] digest = PackageUtils.computeSha256DigestBytes(signatures[0].toByteArray());
+            return new String(HexEncoding.encode(digest));
+        } finally {
+            getUiAutomation().dropShellPermissionIdentity();
+        }
+    }
+
+    private static PackageManager getPackageManager() {
+        return InstrumentationRegistry.getContext().getPackageManager();
+    }
+
+    /**
+     * SDK package is signed by build system. In theory we could try to extract the signature,
+     * and patch the app manifest. This property allows us to override in runtime, which is much
+     * easier.
+     */
+    private void overrideUsesSdkLibraryCertificateDigest(String sdkCertDigest) throws Exception {
+        setSystemProperty("debug.pm.uses_sdk_library_default_cert_digest", sdkCertDigest);
+    }
+
+    private void setSystemProperty(String name, String value) throws Exception {
+        assertThat(executeShellCommand("setprop " + name + " " + value)).isEmpty();
+    }
+
+    private static String executeShellCommand(String command) throws IOException {
+        final ParcelFileDescriptor stdout = getUiAutomation().executeShellCommand(command);
+        try (InputStream inputStream = new ParcelFileDescriptor.AutoCloseInputStream(stdout)) {
+            return readFullStream(inputStream);
+        }
+    }
+
+    private static String readFullStream(InputStream inputStream) throws IOException {
+        ByteArrayOutputStream result = new ByteArrayOutputStream();
+        writeFullStream(inputStream, result, -1);
+        return result.toString("UTF-8");
+    }
+
+    private static void writeFullStream(InputStream inputStream, OutputStream outputStream,
+            long expected) throws IOException {
+        byte[] buffer = new byte[1024];
+        long total = 0;
+        int length;
+        while ((length = inputStream.read(buffer)) != -1) {
+            outputStream.write(buffer, 0, length);
+            total += length;
+        }
+        if (expected > 0) {
+            assertThat(expected).isEqualTo(total);
+        }
+    }
+
+    private static UiAutomation getUiAutomation() {
+        return InstrumentationRegistry.getInstrumentation().getUiAutomation();
+    }
+
+    private File copyApkToTmpDir(String apkFileName) throws Exception {
+        File outFile = new File(mTmpDir, apkFileName);
+        String apkFilePath = PUSH_FILE_DIR + apkFileName;
+        File apkFile = new File(apkFilePath);
+        assertThat(apkFile.exists()).isTrue();
+        try (InputStream is = new FileInputStream(apkFile)) {
+            FileUtils.copyToFileOrThrow(is, outFile);
+        }
+        return outFile;
+    }
+
+    static String createApkPath(String baseName) {
+        return PUSH_FILE_DIR + baseName;
+    }
+
+    /* Install for all the users */
+    private void installPackage(String baseName) throws IOException {
+        File file = new File(createApkPath(baseName));
+        assertThat(executeShellCommand("pm install -t -g " + file.getPath()))
+                .isEqualTo("Success\n");
+    }
+
+    private static String uninstallPackageSilently(String packageName) throws IOException {
+        return executeShellCommand("pm uninstall " + packageName);
+    }
+
+    static class FakePackageParser2Callback extends PackageParser2.Callback {
+
+        @Override
+        public boolean isChangeEnabled(long changeId, @NonNull ApplicationInfo appInfo) {
+            return true;
+        }
+
+        @Override
+        public boolean hasFeature(String feature) {
+            return true;
+        }
+
+        @Override
+        public @NonNull Set<String> getHiddenApiWhitelistedApps() {
+            return new ArraySet<>();
+        }
+
+        @Override
+        public @NonNull Set<String> getInstallConstraintsAllowlist() {
+            return new ArraySet<>();
+        }
+    }
+}
diff --git a/core/tests/coretests/src/android/content/pm/verify/VerificationSessionTest.java b/core/tests/coretests/src/android/content/pm/verify/VerificationSessionTest.java
index 90ae306..f1e1df5 100644
--- a/core/tests/coretests/src/android/content/pm/verify/VerificationSessionTest.java
+++ b/core/tests/coretests/src/android/content/pm/verify/VerificationSessionTest.java
@@ -142,10 +142,10 @@
                 new VerificationStatus.Builder().setVerified(true).build();
         mTestSession.reportVerificationComplete(status);
         verify(mTestSessionInterface, times(1)).reportVerificationComplete(
-                eq(TEST_ID), eq(status));
+                eq(TEST_ID), eq(status), eq(null));
         mTestSession.reportVerificationComplete(status, response);
         verify(mTestSessionInterface, times(1))
-                .reportVerificationCompleteWithExtensionResponse(
+                .reportVerificationComplete(
                         eq(TEST_ID), eq(status), eq(response));
 
         final int reason = VerificationSession.VERIFICATION_INCOMPLETE_UNKNOWN;
diff --git a/core/tests/coretests/src/com/android/internal/jank/FrameTrackerTest.java b/core/tests/coretests/src/com/android/internal/jank/FrameTrackerTest.java
index 1cbc7d6..60b5a42 100644
--- a/core/tests/coretests/src/com/android/internal/jank/FrameTrackerTest.java
+++ b/core/tests/coretests/src/com/android/internal/jank/FrameTrackerTest.java
@@ -16,9 +16,9 @@
 
 package com.android.internal.jank;
 
-import static android.view.SurfaceControl.JankData.JANK_APP_DEADLINE_MISSED;
+import static android.view.SurfaceControl.JankData.JANK_APPLICATION;
+import static android.view.SurfaceControl.JankData.JANK_COMPOSER;
 import static android.view.SurfaceControl.JankData.JANK_NONE;
-import static android.view.SurfaceControl.JankData.JANK_SURFACEFLINGER_DEADLINE_MISSED;
 
 import static com.android.internal.jank.FrameTracker.SurfaceControlWrapper;
 import static com.android.internal.jank.FrameTracker.ViewRootWrapper;
@@ -164,7 +164,7 @@
         verify(mRenderer, only()).addObserver(any());
 
         // send first frame with a long duration - should not be taken into account
-        sendFirstWindowFrame(tracker, 100, JANK_APP_DEADLINE_MISSED, 100L);
+        sendFirstWindowFrame(tracker, 100, JANK_APPLICATION, 100L);
 
         // send another frame with a short duration - should not be considered janky
         sendFrame(tracker, 5, JANK_NONE, 101L);
@@ -173,7 +173,7 @@
         when(mChoreographer.getVsyncId()).thenReturn(102L);
         tracker.end(FrameTracker.REASON_END_NORMAL);
         sendFrame(tracker, 5, JANK_NONE, 102L);
-        sendFrame(tracker, 500, JANK_APP_DEADLINE_MISSED, 103L);
+        sendFrame(tracker, 500, JANK_APPLICATION, 103L);
 
         verify(tracker).removeObservers();
         verify(mTrackerListener, never()).triggerPerfetto(any());
@@ -202,7 +202,7 @@
         sendFrame(tracker, 4, JANK_NONE, 100L);
 
         // send another frame - should be considered janky
-        sendFrame(tracker, 40, JANK_SURFACEFLINGER_DEADLINE_MISSED, 101L);
+        sendFrame(tracker, 40, JANK_COMPOSER, 101L);
 
         // end the trace session
         when(mChoreographer.getVsyncId()).thenReturn(102L);
@@ -236,7 +236,7 @@
         verify(mRenderer, only()).addObserver(any());
 
         // send first frame - janky
-        sendFrame(tracker, 40, JANK_APP_DEADLINE_MISSED, 100L);
+        sendFrame(tracker, 40, JANK_APPLICATION, 100L);
 
         // send another frame - not jank
         sendFrame(tracker, 4, JANK_NONE, 101L);
@@ -275,7 +275,7 @@
         sendFrame(tracker, 4, JANK_NONE, 100L);
 
         // send another frame - should be considered janky
-        sendFrame(tracker, 40, JANK_APP_DEADLINE_MISSED, 101L);
+        sendFrame(tracker, 40, JANK_APPLICATION, 101L);
 
         // end the trace session
         when(mChoreographer.getVsyncId()).thenReturn(102L);
@@ -317,7 +317,7 @@
         // end the trace session, simulate one more valid callback came after the end call.
         when(mChoreographer.getVsyncId()).thenReturn(102L);
         tracker.end(FrameTracker.REASON_END_NORMAL);
-        sendFrame(tracker, 50, JANK_APP_DEADLINE_MISSED, 102L);
+        sendFrame(tracker, 50, JANK_APPLICATION, 102L);
 
         // One more callback with VSYNC after the end() vsync id.
         sendFrame(tracker, 4, JANK_NONE, 103L);
@@ -365,7 +365,7 @@
         sendSfFrame(tracker, 4, 102L, JANK_NONE);
 
         // Send janky but complete callbck fo 103L
-        sendFrame(tracker, 50, JANK_APP_DEADLINE_MISSED, 103L);
+        sendFrame(tracker, 50, JANK_APPLICATION, 103L);
 
         verify(tracker).removeObservers();
         verify(mTrackerListener, never()).triggerPerfetto(any());
@@ -397,7 +397,7 @@
         sendFrame(tracker, 4, JANK_NONE, 101L);
 
         // a janky frame
-        sendFrame(tracker, 50, JANK_APP_DEADLINE_MISSED, 102L);
+        sendFrame(tracker, 50, JANK_APPLICATION, 102L);
 
         tracker.cancel(FrameTracker.REASON_CANCEL_NORMAL);
         verify(tracker).removeObservers();
@@ -481,7 +481,7 @@
         // normal frame - not janky
         sendFrame(tracker, JANK_NONE, 101L);
         // a janky frame
-        sendFrame(tracker, JANK_APP_DEADLINE_MISSED, 102L);
+        sendFrame(tracker, JANK_APPLICATION, 102L);
 
         when(mChoreographer.getVsyncId()).thenReturn(102L);
         tracker.end(FrameTracker.REASON_CANCEL_NORMAL);
@@ -514,7 +514,7 @@
         verify(mSurfaceControlWrapper).addJankStatsListener(any(), any());
 
         // First frame - janky
-        sendFrame(tracker, JANK_APP_DEADLINE_MISSED, 100L);
+        sendFrame(tracker, JANK_APPLICATION, 100L);
         // normal frame - not janky
         sendFrame(tracker, JANK_NONE, 101L);
         // normal frame - not janky
@@ -561,7 +561,7 @@
         tracker.end(FrameTracker.REASON_CANCEL_NORMAL);
 
         // janky frame, should be ignored, trigger finish
-        sendFrame(tracker, JANK_APP_DEADLINE_MISSED, 103L);
+        sendFrame(tracker, JANK_APPLICATION, 103L);
 
         verify(mJankStatsRegistration).removeAfter(anyLong());
         verify(mTrackerListener, never()).triggerPerfetto(any());
@@ -623,16 +623,16 @@
         tracker.begin();
         mRunnableArgumentCaptor.getValue().run();
         verify(mSurfaceControlWrapper).addJankStatsListener(any(), any());
-        sendFrame(tracker, JANK_SURFACEFLINGER_DEADLINE_MISSED, 100L);
-        sendFrame(tracker, JANK_SURFACEFLINGER_DEADLINE_MISSED, 101L);
-        sendFrame(tracker, JANK_APP_DEADLINE_MISSED, 102L);
+        sendFrame(tracker, JANK_COMPOSER, 100L);
+        sendFrame(tracker, JANK_COMPOSER, 101L);
+        sendFrame(tracker, JANK_APPLICATION, 102L);
         sendFrame(tracker, JANK_NONE, 103L);
-        sendFrame(tracker, JANK_APP_DEADLINE_MISSED, 104L);
-        sendFrame(tracker, JANK_APP_DEADLINE_MISSED, 105L);
+        sendFrame(tracker, JANK_APPLICATION, 104L);
+        sendFrame(tracker, JANK_APPLICATION, 105L);
         when(mChoreographer.getVsyncId()).thenReturn(106L);
         tracker.end(FrameTracker.REASON_END_NORMAL);
-        sendFrame(tracker, JANK_SURFACEFLINGER_DEADLINE_MISSED, 106L);
-        sendFrame(tracker, JANK_SURFACEFLINGER_DEADLINE_MISSED, 107L);
+        sendFrame(tracker, JANK_COMPOSER, 106L);
+        sendFrame(tracker, JANK_COMPOSER, 107L);
         verify(mJankStatsRegistration).removeAfter(anyLong());
         verify(mTrackerListener).triggerPerfetto(any());
         verify(mStatsLog).write(eq(UI_INTERACTION_FRAME_INFO_REPORTED),
diff --git a/core/tests/coretests/src/com/android/internal/widget/NotificationProgressBarTest.java b/core/tests/coretests/src/com/android/internal/widget/NotificationProgressBarTest.java
index 79a478a..5613caf 100644
--- a/core/tests/coretests/src/com/android/internal/widget/NotificationProgressBarTest.java
+++ b/core/tests/coretests/src/com/android/internal/widget/NotificationProgressBarTest.java
@@ -41,9 +41,26 @@
         List<ProgressStyle.Segment> segments = new ArrayList<>();
         List<ProgressStyle.Point> points = new ArrayList<>();
         int progress = 50;
+        int progressMax = 100;
         boolean isStyledByProgress = true;
 
         NotificationProgressBar.processAndConvertToDrawableParts(segments, points, progress,
+                progressMax,
+                isStyledByProgress);
+    }
+
+    @Test(expected = IllegalArgumentException.class)
+    public void processAndConvertToDrawableParts_segmentsLengthNotMatchingProgressMax() {
+        List<ProgressStyle.Segment> segments = new ArrayList<>();
+        segments.add(new ProgressStyle.Segment(50));
+        segments.add(new ProgressStyle.Segment(100));
+        List<ProgressStyle.Point> points = new ArrayList<>();
+        int progress = 50;
+        int progressMax = 100;
+        boolean isStyledByProgress = true;
+
+        NotificationProgressBar.processAndConvertToDrawableParts(segments, points, progress,
+                progressMax,
                 isStyledByProgress);
     }
 
@@ -51,11 +68,14 @@
     public void processAndConvertToDrawableParts_segmentLengthIsNegative() {
         List<ProgressStyle.Segment> segments = new ArrayList<>();
         segments.add(new ProgressStyle.Segment(-50));
+        segments.add(new ProgressStyle.Segment(150));
         List<ProgressStyle.Point> points = new ArrayList<>();
         int progress = 50;
+        int progressMax = 100;
         boolean isStyledByProgress = true;
 
         NotificationProgressBar.processAndConvertToDrawableParts(segments, points, progress,
+                progressMax,
                 isStyledByProgress);
     }
 
@@ -63,11 +83,14 @@
     public void processAndConvertToDrawableParts_segmentLengthIsZero() {
         List<ProgressStyle.Segment> segments = new ArrayList<>();
         segments.add(new ProgressStyle.Segment(0));
+        segments.add(new ProgressStyle.Segment(100));
         List<ProgressStyle.Point> points = new ArrayList<>();
         int progress = 50;
+        int progressMax = 100;
         boolean isStyledByProgress = true;
 
         NotificationProgressBar.processAndConvertToDrawableParts(segments, points, progress,
+                progressMax,
                 isStyledByProgress);
     }
 
@@ -77,9 +100,11 @@
         segments.add(new ProgressStyle.Segment(100));
         List<ProgressStyle.Point> points = new ArrayList<>();
         int progress = -50;
+        int progressMax = 100;
         boolean isStyledByProgress = true;
 
         NotificationProgressBar.processAndConvertToDrawableParts(segments, points, progress,
+                progressMax,
                 isStyledByProgress);
     }
 
@@ -89,10 +114,11 @@
         segments.add(new ProgressStyle.Segment(100).setColor(Color.RED));
         List<ProgressStyle.Point> points = new ArrayList<>();
         int progress = 0;
+        int progressMax = 100;
         boolean isStyledByProgress = true;
 
         List<Part> parts = NotificationProgressBar.processAndConvertToDrawableParts(
-                segments, points, progress, isStyledByProgress);
+                segments, points, progress, progressMax, isStyledByProgress);
 
         int fadedRed = 0x7FFF0000;
         List<Part> expected = new ArrayList<>(List.of(new Segment(1f, fadedRed, true)));
@@ -106,10 +132,11 @@
         segments.add(new ProgressStyle.Segment(100).setColor(Color.RED));
         List<ProgressStyle.Point> points = new ArrayList<>();
         int progress = 100;
+        int progressMax = 100;
         boolean isStyledByProgress = true;
 
         List<Part> parts = NotificationProgressBar.processAndConvertToDrawableParts(
-                segments, points, progress, isStyledByProgress);
+                segments, points, progress, progressMax, isStyledByProgress);
 
         List<Part> expected = new ArrayList<>(List.of(new Segment(1f, Color.RED)));
 
@@ -122,10 +149,11 @@
         segments.add(new ProgressStyle.Segment(100));
         List<ProgressStyle.Point> points = new ArrayList<>();
         int progress = 150;
+        int progressMax = 100;
         boolean isStyledByProgress = true;
 
         NotificationProgressBar.processAndConvertToDrawableParts(segments, points, progress,
-                isStyledByProgress);
+                progressMax, isStyledByProgress);
     }
 
     @Test(expected = IllegalArgumentException.class)
@@ -135,35 +163,11 @@
         List<ProgressStyle.Point> points = new ArrayList<>();
         points.add(new ProgressStyle.Point(-50).setColor(Color.RED));
         int progress = 50;
+        int progressMax = 100;
         boolean isStyledByProgress = true;
 
         NotificationProgressBar.processAndConvertToDrawableParts(segments, points, progress,
-                isStyledByProgress);
-    }
-
-    @Test(expected = IllegalArgumentException.class)
-    public void processAndConvertToDrawableParts_pointPositionIsZero() {
-        List<ProgressStyle.Segment> segments = new ArrayList<>();
-        segments.add(new ProgressStyle.Segment(100));
-        List<ProgressStyle.Point> points = new ArrayList<>();
-        points.add(new ProgressStyle.Point(0).setColor(Color.RED));
-        int progress = 50;
-        boolean isStyledByProgress = true;
-
-        NotificationProgressBar.processAndConvertToDrawableParts(segments, points, progress,
-                isStyledByProgress);
-    }
-
-    @Test(expected = IllegalArgumentException.class)
-    public void processAndConvertToDrawableParts_pointPositionAtMax() {
-        List<ProgressStyle.Segment> segments = new ArrayList<>();
-        segments.add(new ProgressStyle.Segment(100));
-        List<ProgressStyle.Point> points = new ArrayList<>();
-        points.add(new ProgressStyle.Point(100).setColor(Color.RED));
-        int progress = 50;
-        boolean isStyledByProgress = true;
-
-        NotificationProgressBar.processAndConvertToDrawableParts(segments, points, progress,
+                progressMax,
                 isStyledByProgress);
     }
 
@@ -174,9 +178,11 @@
         List<ProgressStyle.Point> points = new ArrayList<>();
         points.add(new ProgressStyle.Point(150).setColor(Color.RED));
         int progress = 50;
+        int progressMax = 100;
         boolean isStyledByProgress = true;
 
         NotificationProgressBar.processAndConvertToDrawableParts(segments, points, progress,
+                progressMax,
                 isStyledByProgress);
     }
 
@@ -187,10 +193,11 @@
         segments.add(new ProgressStyle.Segment(50).setColor(Color.GREEN));
         List<ProgressStyle.Point> points = new ArrayList<>();
         int progress = 60;
+        int progressMax = 100;
         boolean isStyledByProgress = true;
 
         List<Part> parts = NotificationProgressBar.processAndConvertToDrawableParts(
-                segments, points, progress, isStyledByProgress);
+                segments, points, progress, progressMax, isStyledByProgress);
 
         // Colors with 50% opacity
         int fadedGreen = 0x7F00FF00;
@@ -213,6 +220,7 @@
         points.add(new ProgressStyle.Point(60).setColor(Color.BLUE));
         points.add(new ProgressStyle.Point(75).setColor(Color.YELLOW));
         int progress = 60;
+        int progressMax = 100;
         boolean isStyledByProgress = true;
 
         // Colors with 50% opacity
@@ -231,7 +239,7 @@
                 new Segment(0.25f, fadedBlue, true)));
 
         List<Part> parts = NotificationProgressBar.processAndConvertToDrawableParts(
-                segments, points, progress, isStyledByProgress);
+                segments, points, progress, progressMax, isStyledByProgress);
 
         assertThat(parts).isEqualTo(expected);
     }
@@ -247,10 +255,11 @@
         points.add(new ProgressStyle.Point(60).setColor(Color.BLUE));
         points.add(new ProgressStyle.Point(75).setColor(Color.YELLOW));
         int progress = 60;
+        int progressMax = 100;
         boolean isStyledByProgress = true;
 
         List<Part> parts = NotificationProgressBar.processAndConvertToDrawableParts(
-                segments, points, progress, isStyledByProgress);
+                segments, points, progress, progressMax, isStyledByProgress);
 
         // Colors with 50% opacity
         int fadedGreen = 0x7F00FF00;
@@ -281,10 +290,11 @@
         points.add(new ProgressStyle.Point(25).setColor(Color.BLUE));
         points.add(new ProgressStyle.Point(75).setColor(Color.YELLOW));
         int progress = 60;
+        int progressMax = 100;
         boolean isStyledByProgress = false;
 
         List<Part> parts = NotificationProgressBar.processAndConvertToDrawableParts(
-                segments, points, progress, isStyledByProgress);
+                segments, points, progress, progressMax, isStyledByProgress);
 
         List<Part> expected = new ArrayList<>(List.of(
                 new Segment(0.15f, Color.RED),
diff --git a/libs/WindowManager/Shell/Android.bp b/libs/WindowManager/Shell/Android.bp
index f36dff0..42188de 100644
--- a/libs/WindowManager/Shell/Android.bp
+++ b/libs/WindowManager/Shell/Android.bp
@@ -157,6 +157,13 @@
 }
 
 filegroup {
+    name: "wm_shell-shared-utils",
+    srcs: [
+        "shared/src/com/android/wm/shell/shared/TransitionUtil.java",
+    ],
+}
+
+filegroup {
     name: "wm_shell-shared-aidls",
 
     srcs: [
diff --git a/libs/WindowManager/Shell/multivalentTests/Android.bp b/libs/WindowManager/Shell/multivalentTests/Android.bp
index ee0d5bb..41d1b5c 100644
--- a/libs/WindowManager/Shell/multivalentTests/Android.bp
+++ b/libs/WindowManager/Shell/multivalentTests/Android.bp
@@ -53,6 +53,8 @@
         "mockito-robolectric-prebuilt",
         "mockito-kotlin2",
         "truth",
+        "flag-junit-base",
+        "flag-junit",
     ],
     auto_gen_config: true,
 }
diff --git a/libs/WindowManager/Shell/multivalentTests/src/com/android/wm/shell/bubbles/BubbleTaskViewTest.kt b/libs/WindowManager/Shell/multivalentTests/src/com/android/wm/shell/bubbles/BubbleTaskViewTest.kt
index 398fd55..4214e6f 100644
--- a/libs/WindowManager/Shell/multivalentTests/src/com/android/wm/shell/bubbles/BubbleTaskViewTest.kt
+++ b/libs/WindowManager/Shell/multivalentTests/src/com/android/wm/shell/bubbles/BubbleTaskViewTest.kt
@@ -18,14 +18,19 @@
 
 import android.content.ComponentName
 import android.content.Context
+import android.platform.test.annotations.DisableFlags
+import android.platform.test.annotations.EnableFlags
+import android.platform.test.flag.junit.SetFlagsRule
 import androidx.test.core.app.ApplicationProvider
 import androidx.test.ext.junit.runners.AndroidJUnit4
 import androidx.test.filters.SmallTest
+import com.android.wm.shell.Flags
 import com.android.wm.shell.taskview.TaskView
 
 import com.google.common.truth.Truth.assertThat
 import com.google.common.util.concurrent.MoreExecutors.directExecutor
 import org.junit.Before
+import org.junit.Rule
 import org.junit.Test
 import org.junit.runner.RunWith
 import org.mockito.kotlin.mock
@@ -36,6 +41,9 @@
 @RunWith(AndroidJUnit4::class)
 class BubbleTaskViewTest {
 
+    @get:Rule
+    val setFlagsRule = SetFlagsRule()
+
     private lateinit var bubbleTaskView: BubbleTaskView
     private val context = ApplicationProvider.getApplicationContext<Context>()
     private lateinit var taskView: TaskView
@@ -75,14 +83,33 @@
         assertThat(actualComponentName).isEqualTo(componentName)
     }
 
+    @DisableFlags(Flags.FLAG_ENABLE_TASK_VIEW_CONTROLLER_CLEANUP)
     @Test
-    fun cleanup_invalidTaskId_doesNotRemoveTask() {
+    fun cleanup_flagOff_invalidTaskId_doesNotRemoveTask() {
         bubbleTaskView.cleanup()
         verify(taskView, never()).removeTask()
     }
 
+    @EnableFlags(Flags.FLAG_ENABLE_TASK_VIEW_CONTROLLER_CLEANUP)
     @Test
-    fun cleanup_validTaskId_removesTask() {
+    fun cleanup_flagOn_invalidTaskId_removesTask() {
+        bubbleTaskView.cleanup()
+        verify(taskView).removeTask()
+    }
+
+    @DisableFlags(Flags.FLAG_ENABLE_TASK_VIEW_CONTROLLER_CLEANUP)
+    @Test
+    fun cleanup_flagOff_validTaskId_removesTask() {
+        val componentName = ComponentName(context, "TestClass")
+        bubbleTaskView.listener.onTaskCreated(123, componentName)
+
+        bubbleTaskView.cleanup()
+        verify(taskView).removeTask()
+    }
+
+    @EnableFlags(Flags.FLAG_ENABLE_TASK_VIEW_CONTROLLER_CLEANUP)
+    @Test
+    fun cleanup_flagOn_validTaskId_removesTask() {
         val componentName = ComponentName(context, "TestClass")
         bubbleTaskView.listener.onTaskCreated(123, componentName)
 
diff --git a/libs/WindowManager/Shell/res/values/config.xml b/libs/WindowManager/Shell/res/values/config.xml
index a14461a..86751d4 100644
--- a/libs/WindowManager/Shell/res/values/config.xml
+++ b/libs/WindowManager/Shell/res/values/config.xml
@@ -45,6 +45,9 @@
     <!-- Allow PIP to resize to a slightly bigger state upon touch/showing the menu -->
     <bool name="config_pipEnableResizeForMenu">true</bool>
 
+    <!-- Allow PIP to resize via dragging the corner of PiP. -->
+    <bool name="config_pipEnableDragCornerResize">false</bool>
+
     <!-- Time (duration in milliseconds) that the shell waits for an app to close the PiP by itself
          if a custom action is present before closing it. -->
     <integer name="config_pipForceCloseDelay">1000</integer>
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/ShellTaskOrganizer.java b/libs/WindowManager/Shell/src/com/android/wm/shell/ShellTaskOrganizer.java
index 4607a8e..f59fed9 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/ShellTaskOrganizer.java
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/ShellTaskOrganizer.java
@@ -583,9 +583,8 @@
             }
             final boolean windowModeChanged =
                     data.getTaskInfo().getWindowingMode() != taskInfo.getWindowingMode();
-            final boolean visibilityChanged = data.getTaskInfo().isVisible != taskInfo.isVisible;
-            if (windowModeChanged || (taskInfo.getWindowingMode() == WINDOWING_MODE_FREEFORM
-                    && visibilityChanged)) {
+            if (windowModeChanged
+                    || hasFreeformConfigurationChanged(data.getTaskInfo(), taskInfo)) {
                 mRecentTasks.ifPresent(recentTasks ->
                         recentTasks.onTaskRunningInfoChanged(taskInfo));
             }
@@ -606,6 +605,17 @@
         }
     }
 
+    private boolean hasFreeformConfigurationChanged(RunningTaskInfo oldTaskInfo,
+            RunningTaskInfo newTaskInfo) {
+        if (newTaskInfo.getWindowingMode() != WINDOWING_MODE_FREEFORM) {
+            return false;
+        }
+        return oldTaskInfo.isVisible != newTaskInfo.isVisible
+                || !oldTaskInfo.positionInParent.equals(newTaskInfo.positionInParent)
+                || !Objects.equals(oldTaskInfo.configuration.windowConfiguration.getAppBounds(),
+                newTaskInfo.configuration.windowConfiguration.getAppBounds());
+    }
+
     @Override
     public void onBackPressedOnTaskRoot(RunningTaskInfo taskInfo) {
         synchronized (mLock) {
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 73bc426..5eb5d89 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
@@ -95,6 +95,7 @@
 import com.android.wm.shell.transition.Transitions;
 
 import java.io.PrintWriter;
+import java.util.ArrayList;
 import java.util.concurrent.atomic.AtomicBoolean;
 import java.util.function.Predicate;
 
@@ -1277,42 +1278,42 @@
             }
             // Copy initial changes to final transition
             final TransitionInfo init = mOpenTransitionInfo;
-            // find prepare open target
+            // Find prepare open target
             boolean openShowWallpaper = false;
-            ComponentName openComponent = null;
+            final ArrayList<OpenChangeInfo> targets = new ArrayList<>();
             int tmpSize;
-            int openTaskId = INVALID_TASK_ID;
-            WindowContainerToken openToken = null;
             for (int j = init.getChanges().size() - 1; j >= 0; --j) {
                 final TransitionInfo.Change change = init.getChanges().get(j);
-                if (change.hasFlags(FLAG_BACK_GESTURE_ANIMATED)) {
-                    openComponent = findComponentName(change);
-                    openTaskId = findTaskId(change);
-                    openToken = findToken(change);
+                if (change.hasFlags(FLAG_BACK_GESTURE_ANIMATED)
+                        && TransitionUtil.isOpeningMode(change.getMode())) {
+                    final ComponentName openComponent = findComponentName(change);
+                    final int openTaskId = findTaskId(change);
+                    final WindowContainerToken openToken = findToken(change);
+                    if (openComponent == null && openTaskId == INVALID_TASK_ID
+                            && openToken == null) {
+                        continue;
+                    }
+                    targets.add(new OpenChangeInfo(openComponent, openTaskId, openToken));
                     if (change.hasFlags(FLAG_SHOW_WALLPAPER)) {
                         openShowWallpaper = true;
                     }
-                    break;
                 }
             }
-            if (openComponent == null && openTaskId == INVALID_TASK_ID && openToken == null) {
+            if (targets.isEmpty()) {
                 // This shouldn't happen, but if that happen, consume the initial transition anyway.
                 Log.e(TAG, "Unable to merge following transition, cannot find the gesture "
                         + "animated target from the open transition=" + mOpenTransitionInfo);
                 mOpenTransitionInfo = null;
                 return;
             }
-            // find first non-prepare open target
+            // Find first non-prepare open target
             boolean isOpen = false;
             tmpSize = info.getChanges().size();
             for (int j = 0; j < tmpSize; ++j) {
                 final TransitionInfo.Change change = info.getChanges().get(j);
-                final ComponentName firstNonOpen = findComponentName(change);
-                final int firstTaskId = findTaskId(change);
-                if ((firstNonOpen != null && firstNonOpen != openComponent)
-                        || (firstTaskId != INVALID_TASK_ID && firstTaskId != openTaskId)) {
-                    // this is original close target, potential be close, but cannot determine from
-                    // it
+                if (isOpenChangeMatched(targets, change)) {
+                    // This is original close target, potential be close, but cannot determine
+                    // from it.
                     if (change.hasFlags(FLAG_BACK_GESTURE_ANIMATED)) {
                         isOpen = !TransitionUtil.isClosingMode(change.getMode());
                     } else {
@@ -1321,33 +1322,44 @@
                     }
                 }
             }
-
             if (!isOpen) {
                 // Close transition, the transition info should be:
                 // init info(open A & wallpaper)
                 // current info(close B target)
                 // remove init info(open/change A target & wallpaper)
                 boolean moveToTop = false;
+                boolean excludeOpenTarget = false;
+                boolean mergePredictive = false;
                 for (int j = info.getChanges().size() - 1; j >= 0; --j) {
                     final TransitionInfo.Change change = info.getChanges().get(j);
-                    if (isSameChangeTarget(openComponent, openTaskId, openToken, change)) {
+                    if (isOpenChangeMatched(targets, change)) {
+                        if (TransitionUtil.isClosingMode(change.getMode())) {
+                            excludeOpenTarget = true;
+                        }
                         moveToTop = change.hasFlags(FLAG_MOVED_TO_TOP);
                         info.getChanges().remove(j);
                     } else if ((openShowWallpaper && change.hasFlags(FLAG_IS_WALLPAPER))
                             || !change.hasFlags(FLAG_BACK_GESTURE_ANIMATED)) {
                         info.getChanges().remove(j);
+                    } else if (!mergePredictive && TransitionUtil.isClosingMode(change.getMode())) {
+                        mergePredictive = true;
                     }
                 }
                 // Ignore merge if there is no close target
-                if (!info.getChanges().isEmpty()) {
+                if (!info.getChanges().isEmpty() && mergePredictive) {
                     tmpSize = init.getChanges().size();
                     for (int i = 0; i < tmpSize; ++i) {
                         final TransitionInfo.Change change = init.getChanges().get(i);
                         if (change.hasFlags(FLAG_IS_WALLPAPER)) {
                             continue;
                         }
-                        if (moveToTop) {
-                            if (isSameChangeTarget(openComponent, openTaskId, openToken, change)) {
+                        if (isOpenChangeMatched(targets, change)) {
+                            if (excludeOpenTarget) {
+                                // App has triggered another change during predictive back
+                                // transition, filter out predictive back target.
+                                continue;
+                            }
+                            if (moveToTop) {
                                 change.setFlags(change.getFlags() | FLAG_MOVED_TO_TOP);
                             }
                         }
@@ -1376,7 +1388,7 @@
                 if (nonBackClose && nonBackOpen) {
                     for (int j = info.getChanges().size() - 1; j >= 0; --j) {
                         final TransitionInfo.Change change = info.getChanges().get(j);
-                        if (isSameChangeTarget(openComponent, openTaskId, openToken, change)) {
+                        if (isOpenChangeMatched(targets, change)) {
                             info.getChanges().remove(j);
                         } else if ((openShowWallpaper && change.hasFlags(FLAG_IS_WALLPAPER))) {
                             info.getChanges().remove(j);
@@ -1659,9 +1671,21 @@
         final ComponentName openChange = findComponentName(change);
         final int firstTaskId = findTaskId(change);
         final WindowContainerToken openToken = findToken(change);
-        return (openChange != null && openChange == topActivity)
+        return (openChange != null && openChange.equals(topActivity))
                 || (firstTaskId != INVALID_TASK_ID && firstTaskId == taskId)
-                || (openToken != null && token == openToken);
+                || (openToken != null && openToken.equals(token));
+    }
+
+    static boolean isOpenChangeMatched(@NonNull ArrayList<OpenChangeInfo> targets,
+            TransitionInfo.Change change) {
+        for (int i = targets.size() - 1; i >= 0; --i) {
+            final OpenChangeInfo next = targets.get(i);
+            if (isSameChangeTarget(next.mOpenComponent, next.mOpenTaskId, next.mOpenToken,
+                    change)) {
+                return true;
+            }
+        }
+        return false;
     }
 
     private static boolean canBeTransitionTarget(TransitionInfo.Change change) {
@@ -1721,4 +1745,16 @@
             }
         }
     }
+
+    static class OpenChangeInfo {
+        final ComponentName mOpenComponent;
+        final int mOpenTaskId;
+        final WindowContainerToken mOpenToken;
+        OpenChangeInfo(ComponentName openComponent, int openTaskId,
+                WindowContainerToken openToken) {
+            mOpenComponent = openComponent;
+            mOpenTaskId = openTaskId;
+            mOpenToken = openToken;
+        }
+    }
 }
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/bubbles/BubbleTaskView.kt b/libs/WindowManager/Shell/src/com/android/wm/shell/bubbles/BubbleTaskView.kt
index 65f8e48..68fc0c9 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/bubbles/BubbleTaskView.kt
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/bubbles/BubbleTaskView.kt
@@ -16,14 +16,11 @@
 
 package com.android.wm.shell.bubbles
 
-import android.app.ActivityTaskManager
 import android.app.ActivityTaskManager.INVALID_TASK_ID
 import android.content.ComponentName
-import android.os.RemoteException
-import android.util.Log
 import androidx.annotation.VisibleForTesting
+import com.android.wm.shell.Flags
 import com.android.wm.shell.taskview.TaskView
-import com.android.wm.shell.transition.Transitions.ENABLE_SHELL_TRANSITIONS
 import java.util.concurrent.Executor
 
 /**
@@ -89,21 +86,8 @@
      * This should be called after all other cleanup animations have finished.
      */
     fun cleanup() {
-        if (taskId != INVALID_TASK_ID) {
-            // Ensure the task is removed from WM
-            if (ENABLE_SHELL_TRANSITIONS) {
-                taskView.removeTask()
-            } else {
-                try {
-                    ActivityTaskManager.getService().removeTask(taskId)
-                } catch (e: RemoteException) {
-                    Log.w(TAG, e.message ?: "")
-                }
-            }
+        if (Flags.enableTaskViewControllerCleanup() || taskId != INVALID_TASK_ID) {
+            taskView.removeTask()
         }
     }
-
-    private companion object {
-        const val TAG = "BubbleTaskView"
-    }
 }
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/bubbles/bar/BubbleEducationViewController.kt b/libs/WindowManager/Shell/src/com/android/wm/shell/bubbles/bar/BubbleEducationViewController.kt
index 9fd255d..7adec39 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/bubbles/bar/BubbleEducationViewController.kt
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/bubbles/bar/BubbleEducationViewController.kt
@@ -20,6 +20,7 @@
 import android.graphics.Point
 import android.graphics.Rect
 import android.util.Log
+import android.view.Gravity
 import android.view.LayoutInflater
 import android.view.View
 import android.view.ViewGroup
@@ -102,14 +103,17 @@
         hideEducation(animated = false)
         log { "showStackEducation at: $position" }
 
+        val rootBounds = Rect()
+        // Get root bounds on screen as position is in screen coordinates
+        root.getBoundsOnScreen(rootBounds)
         educationView =
             createEducationView(R.layout.bubble_bar_stack_education, root).apply {
                 setArrowDirection(BubblePopupDrawable.ArrowDirection.DOWN)
-                setArrowPosition(BubblePopupDrawable.ArrowPosition.End)
-                updateEducationPosition(view = this, position, root)
+                updateEducationPosition(view = this, position, rootBounds)
                 val arrowToEdgeOffset = popupDrawable?.config?.cornerRadius ?: 0f
                 doOnLayout {
-                    it.pivotX = it.width - arrowToEdgeOffset
+                    it.pivotX = if (position.x < rootBounds.centerX())
+                        arrowToEdgeOffset else it.width - arrowToEdgeOffset
                     it.pivotY = it.height.toFloat()
                 }
                 setOnClickListener { educationClickHandler() }
@@ -218,12 +222,9 @@
      *
      * @param view the user education view to layout
      * @param position the reference position in Screen coordinates
-     * @param root the root view to use for the layout
+     * @param rootBounds bounds of the parent the education view is placed in
      */
-    private fun updateEducationPosition(view: BubblePopupView, position: Point, root: ViewGroup) {
-        val rootBounds = Rect()
-        // Get root bounds on screen as position is in screen coordinates
-        root.getBoundsOnScreen(rootBounds)
+    private fun updateEducationPosition(view: BubblePopupView, position: Point, rootBounds: Rect) {
         // Get the offset to the arrow from the edge of the education view
         val arrowToEdgeOffset =
             view.popupDrawable?.config?.let { it.cornerRadius + it.arrowWidth / 2f }?.roundToInt()
@@ -231,7 +232,15 @@
         // Calculate education view margins
         val params = view.layoutParams as FrameLayout.LayoutParams
         params.bottomMargin = rootBounds.bottom - position.y
-        params.rightMargin = rootBounds.right - position.x - arrowToEdgeOffset
+        if (position.x < rootBounds.centerX()) {
+            params.leftMargin = position.x - arrowToEdgeOffset
+            params.gravity = Gravity.LEFT or Gravity.BOTTOM
+            view.setArrowPosition(BubblePopupDrawable.ArrowPosition.Start)
+        } else {
+            params.rightMargin = rootBounds.right - position.x - arrowToEdgeOffset
+            params.gravity = Gravity.RIGHT or Gravity.BOTTOM
+            view.setArrowPosition(BubblePopupDrawable.ArrowPosition.End)
+        }
         view.layoutParams = params
     }
 
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/desktopmode/DesktopRepository.kt b/libs/WindowManager/Shell/src/com/android/wm/shell/desktopmode/DesktopRepository.kt
index 5998dc8..6f7b716 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/desktopmode/DesktopRepository.kt
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/desktopmode/DesktopRepository.kt
@@ -129,19 +129,24 @@
                 DesktopModeStatus.getMaxTaskLimit(context).takeIf { it > 0 }
                     ?: desktop.zOrderedTasksCount
 
+            var visibleTasksCount = 0
             desktop.zOrderedTasksList
                 // Reverse it so we initialize the repo from bottom to top.
                 .reversed()
-                .mapNotNull { taskId ->
-                    desktop.tasksByTaskIdMap[taskId]?.takeIf {
-                        it.desktopTaskState == DesktopTaskState.VISIBLE
-                    }
-                }
-                .take(maxTasks)
+                .mapNotNull { taskId -> desktop.tasksByTaskIdMap[taskId] }
                 .forEach { task ->
-                    addOrMoveFreeformTaskToTop(desktop.displayId, task.taskId)
-                    addActiveTask(desktop.displayId, task.taskId)
-                    updateTaskVisibility(desktop.displayId, task.taskId, visible = false)
+                    if (task.desktopTaskState == DesktopTaskState.VISIBLE
+                        && visibleTasksCount < maxTasks
+                    ) {
+                        visibleTasksCount++
+                        addOrMoveFreeformTaskToTop(desktop.displayId, task.taskId)
+                        addActiveTask(desktop.displayId, task.taskId)
+                        updateTaskVisibility(desktop.displayId, task.taskId, visible = false)
+                    } else {
+                        addActiveTask(desktop.displayId, task.taskId)
+                        updateTaskVisibility(desktop.displayId, task.taskId, visible = false)
+                        minimizeTask(desktop.displayId, task.taskId)
+                    }
                 }
         }
     }
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/desktopmode/DesktopTasksController.kt b/libs/WindowManager/Shell/src/com/android/wm/shell/desktopmode/DesktopTasksController.kt
index 77fb4b4..bc78e43 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/desktopmode/DesktopTasksController.kt
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/desktopmode/DesktopTasksController.kt
@@ -373,7 +373,7 @@
         }
         logV("moveBackgroundTaskToDesktop with taskId=%d", taskId)
         // TODO(342378842): Instead of using default display, support multiple displays
-        val taskToMinimize = bringDesktopAppsToFrontBeforeShowingNewTask(
+        val taskIdToMinimize = bringDesktopAppsToFrontBeforeShowingNewTask(
             DEFAULT_DISPLAY, wct, taskId)
         val runOnTransit = desktopImmersiveController.exitImmersiveIfApplicable(
             wct = wct,
@@ -388,7 +388,7 @@
         )
         // TODO(343149901): Add DPI changes for task launch
         val transition = enterDesktopTaskTransitionHandler.moveToDesktop(wct, transitionSource)
-        addPendingMinimizeTransition(transition, taskToMinimize)
+        taskIdToMinimize?.let { addPendingMinimizeTransition(transition, it) }
         runOnTransit?.invoke(transition)
         return true
     }
@@ -412,12 +412,12 @@
             excludeTaskId = task.taskId,
         )
         // Bring other apps to front first
-        val taskToMinimize =
+        val taskIdToMinimize =
             bringDesktopAppsToFrontBeforeShowingNewTask(task.displayId, wct, task.taskId)
         addMoveToDesktopChanges(wct, task)
 
         val transition = enterDesktopTaskTransitionHandler.moveToDesktop(wct, transitionSource)
-        addPendingMinimizeTransition(transition, taskToMinimize)
+        taskIdToMinimize?.let { addPendingMinimizeTransition(transition, it) }
         runOnTransit?.invoke(transition)
     }
 
@@ -452,14 +452,14 @@
         val wct = WindowContainerTransaction()
         exitSplitIfApplicable(wct, taskInfo)
         moveHomeTask(wct, toTop = true)
-        val taskToMinimize =
+        val taskIdToMinimize =
             bringDesktopAppsToFrontBeforeShowingNewTask(taskInfo.displayId, wct, taskInfo.taskId)
         addMoveToDesktopChanges(wct, taskInfo)
         val runOnTransit = desktopImmersiveController.exitImmersiveIfApplicable(
             wct, taskInfo.displayId)
         val transition = dragToDesktopTransitionHandler.finishDragToDesktopTransition(wct)
         transition?.let {
-            addPendingMinimizeTransition(it, taskToMinimize)
+            taskIdToMinimize?.let { taskId -> addPendingMinimizeTransition(it, taskId) }
             runOnTransit?.invoke(transition)
         }
     }
@@ -651,7 +651,13 @@
             excludeTaskId = taskInfo.taskId,
         )
         val transition =
-            startLaunchTransition(TRANSIT_TO_FRONT, wct, taskInfo.taskId, remoteTransition)
+            startLaunchTransition(
+                TRANSIT_TO_FRONT,
+                wct,
+                taskInfo.taskId,
+                remoteTransition,
+                taskInfo.displayId
+            )
         runOnTransit?.invoke(transition)
     }
 
@@ -660,15 +666,15 @@
         wct: WindowContainerTransaction,
         taskId: Int,
         remoteTransition: RemoteTransition?,
+        displayId: Int = DEFAULT_DISPLAY,
     ): IBinder {
-        val taskToMinimize: RunningTaskInfo? =
-            addAndGetMinimizeChanges(DEFAULT_DISPLAY, wct, taskId)
+        val taskIdToMinimize = addAndGetMinimizeChanges(displayId, wct, taskId)
         if (remoteTransition == null) {
             val t = transitions.startTransition(transitionType, wct, null /* handler */)
-            addPendingMinimizeTransition(t, taskToMinimize)
+            taskIdToMinimize?.let { addPendingMinimizeTransition(t, it) }
             return t
         }
-        if (taskToMinimize == null) {
+        if (taskIdToMinimize == null) {
             val remoteTransitionHandler = OneShotRemoteHandler(mainExecutor, remoteTransition)
             val t = transitions.startTransition(transitionType, wct, remoteTransitionHandler)
             remoteTransitionHandler.setTransition(t)
@@ -676,10 +682,10 @@
         }
         val remoteTransitionHandler =
             DesktopWindowLimitRemoteHandler(
-                mainExecutor, rootTaskDisplayAreaOrganizer, remoteTransition, taskToMinimize.taskId)
+                mainExecutor, rootTaskDisplayAreaOrganizer, remoteTransition, taskIdToMinimize)
         val t = transitions.startTransition(transitionType, wct, remoteTransitionHandler)
         remoteTransitionHandler.setTransition(t)
-        addPendingMinimizeTransition(t, taskToMinimize)
+        taskIdToMinimize?.let { addPendingMinimizeTransition(t, it) }
         return t
     }
 
@@ -1032,13 +1038,13 @@
         displayId: Int,
         wct: WindowContainerTransaction,
         newTaskIdInFront: Int
-    ): RunningTaskInfo? = bringDesktopAppsToFront(displayId, wct, newTaskIdInFront)
+    ): Int? = bringDesktopAppsToFront(displayId, wct, newTaskIdInFront)
 
     private fun bringDesktopAppsToFront(
         displayId: Int,
         wct: WindowContainerTransaction,
         newTaskIdInFront: Int? = null
-    ): RunningTaskInfo? {
+    ): Int? {
         logV("bringDesktopAppsToFront, newTaskId=%d", newTaskIdInFront)
         // Move home to front, ensures that we go back home when all desktop windows are closed
         moveHomeTask(wct, toTop = true)
@@ -1054,11 +1060,10 @@
             taskRepository.getExpandedTasksOrdered(displayId)
         // If we're adding a new Task we might need to minimize an old one
         // TODO(b/365725441): Handle non running task minimization
-        val taskToMinimize: RunningTaskInfo? =
+        val taskIdToMinimize: Int? =
             if (newTaskIdInFront != null && desktopTasksLimiter.isPresent) {
-                desktopTasksLimiter
-                    .get()
-                    .getTaskToMinimize(
+                desktopTasksLimiter.get()
+                    .getTaskIdToMinimize(
                         expandedTasksOrderedFrontToBack,
                         newTaskIdInFront
                     )
@@ -1068,7 +1073,7 @@
 
         expandedTasksOrderedFrontToBack
             // If there is a Task to minimize, let it stay behind the Home Task
-            .filter { taskId -> taskId != taskToMinimize?.taskId }
+            .filter { taskId -> taskId != taskIdToMinimize }
             .reversed() // Start from the back so the front task is brought forward last
             .forEach { taskId ->
                 val runningTaskInfo = shellTaskOrganizer.getRunningTaskInfo(taskId)
@@ -1089,7 +1094,7 @@
         taskbarDesktopTaskListener?.
             onTaskbarCornerRoundingUpdate(doesAnyTaskRequireTaskbarRounding(displayId))
 
-        return taskToMinimize
+        return taskIdToMinimize
     }
 
     private fun moveHomeTask(wct: WindowContainerTransaction, toTop: Boolean) {
@@ -1341,7 +1346,7 @@
         val options = createNewWindowOptions(callingTask)
         if (options.launchWindowingMode == WINDOWING_MODE_FREEFORM) {
             wct.startTask(requestedTaskId, options.toBundle())
-            val taskToMinimize = bringDesktopAppsToFrontBeforeShowingNewTask(
+            val taskIdToMinimize = bringDesktopAppsToFrontBeforeShowingNewTask(
                 callingTask.displayId, wct, requestedTaskId)
             val runOnTransit = desktopImmersiveController.exitImmersiveIfApplicable(
                 wct = wct,
@@ -1349,7 +1354,7 @@
                 excludeTaskId = requestedTaskId,
             )
             val transition = transitions.startTransition(TRANSIT_OPEN, wct, null)
-            addPendingMinimizeTransition(transition, taskToMinimize)
+            taskIdToMinimize?.let { addPendingMinimizeTransition(transition, it) }
             runOnTransit?.invoke(transition)
         } else {
             val splitPosition = splitScreenController.determineNewInstancePosition(callingTask)
@@ -1489,9 +1494,9 @@
         // 1) Exit immersive if needed.
         desktopImmersiveController.exitImmersiveIfApplicable(transition, wct, task.displayId)
         // 2) minimize a Task if needed.
-        val taskToMinimize = addAndGetMinimizeChanges(task.displayId, wct, task.taskId)
-        if (taskToMinimize != null) {
-            addPendingMinimizeTransition(transition, taskToMinimize)
+        val taskIdToMinimize = addAndGetMinimizeChanges(task.displayId, wct, task.taskId)
+        if (taskIdToMinimize != null) {
+            addPendingMinimizeTransition(transition, taskIdToMinimize)
             return wct
         }
         return if (wct.isEmpty) null else wct
@@ -1515,9 +1520,8 @@
 
                 // Desktop Mode is already showing and we're launching a new Task - we might need to
                 // minimize another Task.
-                val taskToMinimize =
-                    addAndGetMinimizeChanges(task.displayId, wct, task.taskId)
-                addPendingMinimizeTransition(transition, taskToMinimize)
+                val taskIdToMinimize = addAndGetMinimizeChanges(task.displayId, wct, task.taskId)
+                taskIdToMinimize?.let { addPendingMinimizeTransition(transition, it) }
                 desktopImmersiveController.exitImmersiveIfApplicable(
                     transition, wct, task.displayId
                 )
@@ -1683,7 +1687,7 @@
         displayId: Int,
         wct: WindowContainerTransaction,
         newTaskId: Int
-    ): RunningTaskInfo? {
+    ): Int? {
         if (!desktopTasksLimiter.isPresent) return null
         return desktopTasksLimiter
             .get()
@@ -1692,9 +1696,9 @@
 
     private fun addPendingMinimizeTransition(
         transition: IBinder,
-        taskToMinimize: RunningTaskInfo?
+        taskIdToMinimize: Int,
     ) {
-        if (taskToMinimize == null) return
+        val taskToMinimize = shellTaskOrganizer.getRunningTaskInfo(taskIdToMinimize) ?: return
         desktopTasksLimiter.ifPresent {
             it.addPendingMinimizeChange(transition, taskToMinimize.displayId, taskToMinimize.taskId)
         }
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/desktopmode/DesktopTasksLimiter.kt b/libs/WindowManager/Shell/src/com/android/wm/shell/desktopmode/DesktopTasksLimiter.kt
index cd28a4f..7bcc5d1 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/desktopmode/DesktopTasksLimiter.kt
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/desktopmode/DesktopTasksLimiter.kt
@@ -16,7 +16,6 @@
 
 package com.android.wm.shell.desktopmode
 
-import android.app.ActivityManager.RunningTaskInfo
 import android.content.Context
 import android.os.Handler
 import android.os.IBinder
@@ -190,17 +189,19 @@
             displayId: Int,
             wct: WindowContainerTransaction,
             newFrontTaskId: Int,
-    ): RunningTaskInfo? {
+    ): Int? {
         logV("addAndGetMinimizeTaskChanges, newFrontTask=%d", newFrontTaskId)
-        // This list is ordered from front to back.
-        val newTaskOrderedList = createOrderedTaskListWithNewTask(
-            taskRepository.getExpandedTasksOrdered(displayId), newFrontTaskId)
-        val taskToMinimize = getTaskToMinimize(newTaskOrderedList)
-        if (taskToMinimize != null) {
-            wct.reorder(taskToMinimize.token, false /* onTop */)
-            return taskToMinimize
+
+        val taskIdToMinimize =
+            getTaskIdToMinimize(
+                taskRepository.getExpandedTasksOrdered(displayId),
+                newFrontTaskId
+            )
+        // If it's a running task, reorder it to back.
+        taskIdToMinimize?.let { shellTaskOrganizer.getRunningTaskInfo(it) }?.let {
+            wct.reorder(it.token, false /* onTop */)
         }
-        return null
+        return taskIdToMinimize
     }
 
     /**
@@ -216,31 +217,33 @@
      * Returns the minimized task from the list of visible tasks ordered from front to back with
      * the new task placed in front of other tasks.
      */
-    fun getTaskToMinimize(
+    fun getTaskIdToMinimize(
             visibleOrderedTasks: List<Int>,
-            newTaskIdInFront: Int
-    ): RunningTaskInfo? =
-        getTaskToMinimize(createOrderedTaskListWithNewTask(visibleOrderedTasks, newTaskIdInFront))
-
-    /** Returns the Task to minimize given a list of visible tasks ordered from front to back. */
-    fun getTaskToMinimize(visibleOrderedTasks: List<Int>): RunningTaskInfo? {
-        if (visibleOrderedTasks.size <= maxTasksLimit) {
-            logV("No need to minimize; tasks below limit")
-            return null
-        }
-        val taskIdToMinimize = visibleOrderedTasks.last()
-        val taskToMinimize =
-                shellTaskOrganizer.getRunningTaskInfo(taskIdToMinimize)
-        if (taskToMinimize == null) {
-            logE("taskToMinimize(taskId = %d) == null", taskIdToMinimize)
-            return null
-        }
-        return taskToMinimize
+            newTaskIdInFront: Int? = null
+    ): Int? {
+        return getTaskIdToMinimize(
+            createOrderedTaskListWithGivenTaskInFront(
+                visibleOrderedTasks, newTaskIdInFront))
     }
 
-    private fun createOrderedTaskListWithNewTask(
-        orderedTaskIds: List<Int>, newTaskId: Int): List<Int> =
-            listOf(newTaskId) + orderedTaskIds.filter { taskId -> taskId != newTaskId }
+    /** Returns the Task to minimize given a list of visible tasks ordered from front to back. */
+    private fun getTaskIdToMinimize(visibleOrderedTasks: List<Int>): Int? {
+        if (visibleOrderedTasks.size <= maxTasksLimit) {
+            logV("No need to minimize; tasks below limit")
+            // No need to minimize anything
+            return null
+        }
+        return visibleOrderedTasks.last()
+    }
+
+    private fun createOrderedTaskListWithGivenTaskInFront(
+            existingTaskIdsOrderedFrontToBack: List<Int>,
+            newTaskId: Int?
+    ): List<Int> {
+        return if (newTaskId == null) existingTaskIdsOrderedFrontToBack
+        else listOf(newTaskId) +
+                existingTaskIdsOrderedFrontToBack.filter { taskId -> taskId != newTaskId }
+    }
 
     @VisibleForTesting
     fun getTransitionObserver(): TransitionObserver = minimizeTransitionObserver
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/draganddrop/DragUtils.java b/libs/WindowManager/Shell/src/com/android/wm/shell/draganddrop/DragUtils.java
index 22cfa32..248a112 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/draganddrop/DragUtils.java
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/draganddrop/DragUtils.java
@@ -125,25 +125,35 @@
         StringJoiner str = new StringJoiner("|");
         if ((dragFlags & DRAG_FLAG_GLOBAL) != 0) {
             str.add("GLOBAL");
-        } else if ((dragFlags & DRAG_FLAG_GLOBAL_URI_READ) != 0) {
+        }
+        if ((dragFlags & DRAG_FLAG_GLOBAL_URI_READ) != 0) {
             str.add("GLOBAL_URI_READ");
-        } else if ((dragFlags & DRAG_FLAG_GLOBAL_URI_WRITE) != 0) {
+        }
+        if ((dragFlags & DRAG_FLAG_GLOBAL_URI_WRITE) != 0) {
             str.add("GLOBAL_URI_WRITE");
-        } else if ((dragFlags & DRAG_FLAG_GLOBAL_PERSISTABLE_URI_PERMISSION) != 0) {
+        }
+        if ((dragFlags & DRAG_FLAG_GLOBAL_PERSISTABLE_URI_PERMISSION) != 0) {
             str.add("GLOBAL_PERSISTABLE_URI_PERMISSION");
-        } else if ((dragFlags & DRAG_FLAG_GLOBAL_PREFIX_URI_PERMISSION) != 0) {
+        }
+        if ((dragFlags & DRAG_FLAG_GLOBAL_PREFIX_URI_PERMISSION) != 0) {
             str.add("GLOBAL_PREFIX_URI_PERMISSION");
-        } else if ((dragFlags & DRAG_FLAG_OPAQUE) != 0) {
+        }
+        if ((dragFlags & DRAG_FLAG_OPAQUE) != 0) {
             str.add("OPAQUE");
-        } else if ((dragFlags & DRAG_FLAG_ACCESSIBILITY_ACTION) != 0) {
+        }
+        if ((dragFlags & DRAG_FLAG_ACCESSIBILITY_ACTION) != 0) {
             str.add("ACCESSIBILITY_ACTION");
-        } else if ((dragFlags & DRAG_FLAG_REQUEST_SURFACE_FOR_RETURN_ANIMATION) != 0) {
+        }
+        if ((dragFlags & DRAG_FLAG_REQUEST_SURFACE_FOR_RETURN_ANIMATION) != 0) {
             str.add("REQUEST_SURFACE_FOR_RETURN_ANIMATION");
-        } else if ((dragFlags & DRAG_FLAG_GLOBAL_SAME_APPLICATION) != 0) {
+        }
+        if ((dragFlags & DRAG_FLAG_GLOBAL_SAME_APPLICATION) != 0) {
             str.add("GLOBAL_SAME_APPLICATION");
-        } else if ((dragFlags & DRAG_FLAG_START_INTENT_SENDER_ON_UNHANDLED_DRAG) != 0) {
+        }
+        if ((dragFlags & DRAG_FLAG_START_INTENT_SENDER_ON_UNHANDLED_DRAG) != 0) {
             str.add("START_INTENT_SENDER_ON_UNHANDLED_DRAG");
-        } else if ((dragFlags & DRAG_FLAG_HIDE_CALLING_TASK_ON_DRAG_START) != 0) {
+        }
+        if ((dragFlags & DRAG_FLAG_HIDE_CALLING_TASK_ON_DRAG_START) != 0) {
             str.add("HIDE_CALLING_TASK_ON_DRAG_START");
         }
         return str.toString();
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/pip/phone/PipResizeGestureHandler.java b/libs/WindowManager/Shell/src/com/android/wm/shell/pip/phone/PipResizeGestureHandler.java
index 89d3dd6..9e9de10 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/pip/phone/PipResizeGestureHandler.java
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/pip/phone/PipResizeGestureHandler.java
@@ -15,7 +15,12 @@
  */
 package com.android.wm.shell.pip.phone;
 
+import static com.android.internal.policy.TaskResizingAlgorithm.CTRL_BOTTOM;
+import static com.android.internal.policy.TaskResizingAlgorithm.CTRL_LEFT;
 import static com.android.internal.policy.TaskResizingAlgorithm.CTRL_NONE;
+import static com.android.internal.policy.TaskResizingAlgorithm.CTRL_RIGHT;
+import static com.android.internal.policy.TaskResizingAlgorithm.CTRL_TOP;
+import static com.android.wm.shell.pip.phone.PipMenuView.ANIM_TYPE_NONE;
 
 import android.annotation.Nullable;
 import android.content.Context;
@@ -23,6 +28,7 @@
 import android.graphics.Point;
 import android.graphics.PointF;
 import android.graphics.Rect;
+import android.graphics.Region;
 import android.hardware.input.InputManager;
 import android.os.Looper;
 import android.view.BatchedInputEventReceiver;
@@ -36,6 +42,7 @@
 
 import androidx.annotation.VisibleForTesting;
 
+import com.android.internal.policy.TaskResizingAlgorithm;
 import com.android.wm.shell.R;
 import com.android.wm.shell.common.ShellExecutor;
 import com.android.wm.shell.common.pip.PipBoundsAlgorithm;
@@ -48,6 +55,7 @@
 
 import java.io.PrintWriter;
 import java.util.function.Consumer;
+import java.util.function.Function;
 
 /**
  * Helper on top of PipTouchHandler that handles inputs OUTSIDE of the PIP window, which is used to
@@ -71,6 +79,7 @@
     private final PipPinchResizingAlgorithm mPinchResizingAlgorithm;
     private final int mDisplayId;
     private final ShellExecutor mMainExecutor;
+    private final Region mTmpRegion = new Region();
 
     private final PointF mDownPoint = new PointF();
     private final PointF mDownSecondPoint = new PointF();
@@ -81,15 +90,24 @@
     private final Rect mLastResizeBounds = new Rect();
     private final Rect mUserResizeBounds = new Rect();
     private final Rect mDownBounds = new Rect();
+    private final Rect mDragCornerSize = new Rect();
+    private final Rect mTmpTopLeftCorner = new Rect();
+    private final Rect mTmpTopRightCorner = new Rect();
+    private final Rect mTmpBottomLeftCorner = new Rect();
+    private final Rect mTmpBottomRightCorner = new Rect();
+    private final Rect mDisplayBounds = new Rect();
+    private final Function<Rect, Rect> mMovementBoundsSupplier;
     private final Runnable mUpdateMovementBoundsRunnable;
     private final Consumer<Rect> mUpdateResizeBoundsCallback;
 
+    private int mDelta;
     private float mTouchSlop;
 
     private boolean mAllowGesture;
     private boolean mIsAttached;
     private boolean mIsEnabled;
     private boolean mEnablePinchResize;
+    private boolean mEnableDragCornerResize;
     private boolean mIsSysUiStateValid;
     private boolean mThresholdCrossed;
     private boolean mOngoingPinchToResize = false;
@@ -113,7 +131,7 @@
             PipBoundsState pipBoundsState, PipMotionHelper motionHelper,
             PipTouchState pipTouchState, PipTaskOrganizer pipTaskOrganizer,
             PipDismissTargetHandler pipDismissTargetHandler,
-            Runnable updateMovementBoundsRunnable,
+            Function<Rect, Rect> movementBoundsSupplier, Runnable updateMovementBoundsRunnable,
             PipUiEventLogger pipUiEventLogger, PhonePipMenuController menuActivityController,
             ShellExecutor mainExecutor, @Nullable PipPerfHintController pipPerfHintController) {
         mContext = context;
@@ -126,6 +144,7 @@
         mPipTouchState = pipTouchState;
         mPipTaskOrganizer = pipTaskOrganizer;
         mPipDismissTargetHandler = pipDismissTargetHandler;
+        mMovementBoundsSupplier = movementBoundsSupplier;
         mUpdateMovementBoundsRunnable = updateMovementBoundsRunnable;
         mPhonePipMenuController = menuActivityController;
         mPipUiEventLogger = pipUiEventLogger;
@@ -161,9 +180,20 @@
     }
 
     private void reloadResources() {
+        final Resources res = mContext.getResources();
+        mDelta = res.getDimensionPixelSize(R.dimen.pip_resize_edge_size);
+        mEnableDragCornerResize = res.getBoolean(R.bool.config_pipEnableDragCornerResize);
         mTouchSlop = ViewConfiguration.get(mContext).getScaledTouchSlop();
     }
 
+    private void resetDragCorners() {
+        mDragCornerSize.set(0, 0, mDelta, mDelta);
+        mTmpTopLeftCorner.set(mDragCornerSize);
+        mTmpTopRightCorner.set(mDragCornerSize);
+        mTmpBottomLeftCorner.set(mDragCornerSize);
+        mTmpBottomRightCorner.set(mDragCornerSize);
+    }
+
     private void disposeInputChannel() {
         if (mInputEventReceiver != null) {
             mInputEventReceiver.dispose();
@@ -211,7 +241,7 @@
 
     @VisibleForTesting
     void onInputEvent(InputEvent ev) {
-        if (!mEnablePinchResize) {
+        if (!mEnableDragCornerResize && !mEnablePinchResize) {
             // No need to handle anything if neither form of resizing is enabled.
             return;
         }
@@ -239,6 +269,8 @@
 
             if (mEnablePinchResize && mOngoingPinchToResize) {
                 onPinchResize(mv);
+            } else if (mEnableDragCornerResize) {
+                onDragCornerResize(mv);
             }
         }
     }
@@ -250,6 +282,48 @@
         return mCtrlType != CTRL_NONE || mOngoingPinchToResize;
     }
 
+    /**
+     * Check whether the current x,y coordinate is within the region in which drag-resize should
+     * start.
+     * This consists of 4 small squares on the 4 corners of the PIP window, a quarter of which
+     * overlaps with the PIP window while the rest goes outside of the PIP window.
+     *  _ _           _ _
+     * |_|_|_________|_|_|
+     * |_|_|         |_|_|
+     *   |     PIP     |
+     *   |   WINDOW    |
+     *  _|_           _|_
+     * |_|_|_________|_|_|
+     * |_|_|         |_|_|
+     */
+    public boolean isWithinDragResizeRegion(int x, int y) {
+        if (!mEnableDragCornerResize) {
+            return false;
+        }
+
+        final Rect currentPipBounds = mPipBoundsState.getBounds();
+        if (currentPipBounds == null) {
+            return false;
+        }
+        resetDragCorners();
+        mTmpTopLeftCorner.offset(currentPipBounds.left - mDelta / 2,
+                currentPipBounds.top - mDelta /  2);
+        mTmpTopRightCorner.offset(currentPipBounds.right - mDelta / 2,
+                currentPipBounds.top - mDelta /  2);
+        mTmpBottomLeftCorner.offset(currentPipBounds.left - mDelta / 2,
+                currentPipBounds.bottom - mDelta /  2);
+        mTmpBottomRightCorner.offset(currentPipBounds.right - mDelta / 2,
+                currentPipBounds.bottom - mDelta /  2);
+
+        mTmpRegion.setEmpty();
+        mTmpRegion.op(mTmpTopLeftCorner, Region.Op.UNION);
+        mTmpRegion.op(mTmpTopRightCorner, Region.Op.UNION);
+        mTmpRegion.op(mTmpBottomLeftCorner, Region.Op.UNION);
+        mTmpRegion.op(mTmpBottomRightCorner, Region.Op.UNION);
+
+        return mTmpRegion.contains(x, y);
+    }
+
     public boolean isUsingPinchToZoom() {
         return mEnablePinchResize;
     }
@@ -260,17 +334,62 @@
 
     public boolean willStartResizeGesture(MotionEvent ev) {
         if (isInValidSysUiState()) {
-            if (ev.getActionMasked() == MotionEvent.ACTION_POINTER_DOWN) {
-                if (mEnablePinchResize && ev.getPointerCount() == 2) {
-                    onPinchResize(ev);
-                    mOngoingPinchToResize = mAllowGesture;
-                    return mAllowGesture;
-                }
+            switch (ev.getActionMasked()) {
+                case MotionEvent.ACTION_DOWN:
+                    if (isWithinDragResizeRegion((int) ev.getRawX(), (int) ev.getRawY())) {
+                        return true;
+                    }
+                    break;
+
+                case MotionEvent.ACTION_POINTER_DOWN:
+                    if (mEnablePinchResize && ev.getPointerCount() == 2) {
+                        onPinchResize(ev);
+                        mOngoingPinchToResize = mAllowGesture;
+                        return mAllowGesture;
+                    }
+                    break;
+
+                default:
+                    break;
             }
         }
         return false;
     }
 
+    private void setCtrlType(int x, int y) {
+        final Rect currentPipBounds = mPipBoundsState.getBounds();
+
+        Rect movementBounds = mMovementBoundsSupplier.apply(currentPipBounds);
+
+        mDisplayBounds.set(movementBounds.left,
+                movementBounds.top,
+                movementBounds.right + currentPipBounds.width(),
+                movementBounds.bottom + currentPipBounds.height());
+
+        if (mTmpTopLeftCorner.contains(x, y) && currentPipBounds.top != mDisplayBounds.top
+                && currentPipBounds.left != mDisplayBounds.left) {
+            mCtrlType |= CTRL_LEFT;
+            mCtrlType |= CTRL_TOP;
+        }
+        if (mTmpTopRightCorner.contains(x, y) && currentPipBounds.top != mDisplayBounds.top
+                && currentPipBounds.right != mDisplayBounds.right) {
+            mCtrlType |= CTRL_RIGHT;
+            mCtrlType |= CTRL_TOP;
+        }
+        if (mTmpBottomRightCorner.contains(x, y)
+                && currentPipBounds.bottom != mDisplayBounds.bottom
+                && currentPipBounds.right != mDisplayBounds.right) {
+            mCtrlType |= CTRL_RIGHT;
+            mCtrlType |= CTRL_BOTTOM;
+        }
+        if (mTmpBottomLeftCorner.contains(x, y)
+                && currentPipBounds.bottom != mDisplayBounds.bottom
+                && currentPipBounds.left != mDisplayBounds.left) {
+            mCtrlType |= CTRL_LEFT;
+            mCtrlType |= CTRL_BOTTOM;
+        }
+    }
+
     private boolean isInValidSysUiState() {
         return mIsSysUiStateValid;
     }
@@ -364,6 +483,59 @@
         }
     }
 
+    private void onDragCornerResize(MotionEvent ev) {
+        int action = ev.getActionMasked();
+        float x = ev.getX();
+        float y = ev.getY() - mOhmOffset;
+        if (action == MotionEvent.ACTION_DOWN) {
+            mLastResizeBounds.setEmpty();
+            mAllowGesture = isInValidSysUiState() && isWithinDragResizeRegion((int) x, (int) y);
+            if (mAllowGesture) {
+                setCtrlType((int) x, (int) y);
+                mDownPoint.set(x, y);
+                mDownBounds.set(mPipBoundsState.getBounds());
+            }
+        } else if (mAllowGesture) {
+            switch (action) {
+                case MotionEvent.ACTION_POINTER_DOWN:
+                    // We do not support multi touch for resizing via drag
+                    mAllowGesture = false;
+                    break;
+                case MotionEvent.ACTION_MOVE:
+                    // Capture inputs
+                    if (!mThresholdCrossed
+                            && Math.hypot(x - mDownPoint.x, y - mDownPoint.y) > mTouchSlop) {
+                        mThresholdCrossed = true;
+                        // Reset the down to begin resizing from this point
+                        mDownPoint.set(x, y);
+                        mInputMonitor.pilferPointers();
+                    }
+                    if (mThresholdCrossed) {
+                        if (mPhonePipMenuController.isMenuVisible()) {
+                            mPhonePipMenuController.hideMenu(ANIM_TYPE_NONE,
+                                    false /* resize */);
+                        }
+                        final Rect currentPipBounds = mPipBoundsState.getBounds();
+                        mLastResizeBounds.set(TaskResizingAlgorithm.resizeDrag(x, y,
+                                mDownPoint.x, mDownPoint.y, currentPipBounds, mCtrlType, mMinSize.x,
+                                mMinSize.y, mMaxSize, true,
+                                mDownBounds.width() > mDownBounds.height()));
+                        mPipBoundsAlgorithm.transformBoundsToAspectRatio(mLastResizeBounds,
+                                mPipBoundsState.getAspectRatio(), false /* useCurrentMinEdgeSize */,
+                                true /* useCurrentSize */);
+                        mPipTaskOrganizer.scheduleUserResizePip(mDownBounds, mLastResizeBounds,
+                                null);
+                        mPipBoundsState.setHasUserResizedPip(true);
+                    }
+                    break;
+                case MotionEvent.ACTION_UP:
+                case MotionEvent.ACTION_CANCEL:
+                    finishResize();
+                    break;
+            }
+        }
+    }
+
     private void snapToMovementBoundsEdge(Rect bounds, Rect movementBounds) {
         final int leftEdge = bounds.left;
 
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/pip/phone/PipTouchHandler.java b/libs/WindowManager/Shell/src/com/android/wm/shell/pip/phone/PipTouchHandler.java
index 9c4e723..f4c2a33 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/pip/phone/PipTouchHandler.java
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/pip/phone/PipTouchHandler.java
@@ -213,7 +213,7 @@
         mPipResizeGestureHandler =
                 new PipResizeGestureHandler(context, pipBoundsAlgorithm, pipBoundsState,
                         mMotionHelper, mTouchState, pipTaskOrganizer, mPipDismissTargetHandler,
-                        this::updateMovementBounds, pipUiEventLogger,
+                        this::getMovementBounds, this::updateMovementBounds, pipUiEventLogger,
                         menuController, mainExecutor, mPipPerfHintController);
         mConnection = new PipAccessibilityInteractionConnection(mContext, pipBoundsState,
                 mMotionHelper, pipTaskOrganizer, mPipBoundsAlgorithm.getSnapAlgorithm(),
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/pip/tv/TvPipController.java b/libs/WindowManager/Shell/src/com/android/wm/shell/pip/tv/TvPipController.java
index 0ed5079..8ac7f89 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/pip/tv/TvPipController.java
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/pip/tv/TvPipController.java
@@ -476,6 +476,7 @@
         }
         mPipTaskOrganizer.removePip();
         mTvPipMenuController.closeMenu();
+        mPipNotificationController.dismiss();
     }
 
     @Override
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/taskview/TaskViewTaskController.java b/libs/WindowManager/Shell/src/com/android/wm/shell/taskview/TaskViewTaskController.java
index e74342e..eaeedba 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/taskview/TaskViewTaskController.java
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/taskview/TaskViewTaskController.java
@@ -40,6 +40,7 @@
 import android.window.WindowContainerToken;
 import android.window.WindowContainerTransaction;
 
+import com.android.wm.shell.Flags;
 import com.android.wm.shell.ShellTaskOrganizer;
 import com.android.wm.shell.common.SyncTransactionQueue;
 
@@ -525,8 +526,13 @@
      */
     void removeTask() {
         if (mTaskToken == null) {
-            // Call to remove task before we have one, do nothing
-            Slog.w(TAG, "Trying to remove a task that was never added? (no taskToken)");
+            if (Flags.enableTaskViewControllerCleanup()) {
+                // We don't have a task yet. Only clean up the controller
+                mTaskViewTransitions.removeTaskView(this);
+            } else {
+                // Call to remove task before we have one, do nothing
+                Slog.w(TAG, "Trying to remove a task that was never added? (no taskToken)");
+            }
             return;
         }
         mShellExecutor.execute(() -> {
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/taskview/TaskViewTransitions.java b/libs/WindowManager/Shell/src/com/android/wm/shell/taskview/TaskViewTransitions.java
index 39648f6..ae75cb5 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/taskview/TaskViewTransitions.java
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/taskview/TaskViewTransitions.java
@@ -37,11 +37,14 @@
 
 import androidx.annotation.VisibleForTesting;
 
+import com.android.wm.shell.Flags;
 import com.android.wm.shell.shared.TransitionUtil;
 import com.android.wm.shell.transition.Transitions;
 
 import java.util.ArrayList;
+import java.util.Map;
 import java.util.Objects;
+import java.util.WeakHashMap;
 
 /**
  * Handles Shell Transitions that involve TaskView tasks.
@@ -49,8 +52,15 @@
 public class TaskViewTransitions implements Transitions.TransitionHandler {
     static final String TAG = "TaskViewTransitions";
 
-    private final ArrayMap<TaskViewTaskController, TaskViewRequestedState> mTaskViews =
-            new ArrayMap<>();
+    /**
+     * Map of {@link TaskViewTaskController} to {@link TaskViewRequestedState}.
+     * <p>
+     * {@link TaskView} keeps a reference to the {@link TaskViewTaskController} instance and
+     * manages its lifecycle.
+     * Only keep a weak reference to the controller instance here to allow for it to be cleaned
+     * up when its TaskView is no longer used.
+     */
+    private final Map<TaskViewTaskController, TaskViewRequestedState> mTaskViews;
     private final ArrayList<PendingTransition> mPending = new ArrayList<>();
     private final Transitions mTransitions;
     private final boolean[] mRegistered = new boolean[]{false};
@@ -95,6 +105,11 @@
 
     public TaskViewTransitions(Transitions transitions) {
         mTransitions = transitions;
+        if (Flags.enableTaskViewControllerCleanup()) {
+            mTaskViews = new WeakHashMap<>();
+        } else {
+            mTaskViews = new ArrayMap<>();
+        }
         // Defer registration until the first TaskView because we want this to be the "first" in
         // priority when handling requests.
         // TODO(210041388): register here once we have an explicit ordering mechanism.
@@ -208,10 +223,21 @@
     }
 
     private TaskViewTaskController findTaskView(ActivityManager.RunningTaskInfo taskInfo) {
-        for (int i = 0; i < mTaskViews.size(); ++i) {
-            if (mTaskViews.keyAt(i).getTaskInfo() == null) continue;
-            if (taskInfo.token.equals(mTaskViews.keyAt(i).getTaskInfo().token)) {
-                return mTaskViews.keyAt(i);
+        if (Flags.enableTaskViewControllerCleanup()) {
+            for (TaskViewTaskController controller : mTaskViews.keySet()) {
+                if (controller.getTaskInfo() == null) continue;
+                if (taskInfo.token.equals(controller.getTaskInfo().token)) {
+                    return controller;
+                }
+            }
+        } else {
+            ArrayMap<TaskViewTaskController, TaskViewRequestedState> taskViews =
+                    (ArrayMap<TaskViewTaskController, TaskViewRequestedState>) mTaskViews;
+            for (int i = 0; i < taskViews.size(); ++i) {
+                if (taskViews.keyAt(i).getTaskInfo() == null) continue;
+                if (taskInfo.token.equals(taskViews.keyAt(i).getTaskInfo().token)) {
+                    return taskViews.keyAt(i);
+                }
             }
         }
         return null;
diff --git a/libs/WindowManager/Shell/tests/unittest/Android.bp b/libs/WindowManager/Shell/tests/unittest/Android.bp
index 049a5a0..91be5f5 100644
--- a/libs/WindowManager/Shell/tests/unittest/Android.bp
+++ b/libs/WindowManager/Shell/tests/unittest/Android.bp
@@ -63,6 +63,7 @@
         "com.android.window.flags.window-aconfig-java",
         "platform-test-annotations",
         "flag-junit",
+        "platform-parametric-runner-lib",
     ],
 
     libs: [
diff --git a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/ShellTaskOrganizerTests.java b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/ShellTaskOrganizerTests.java
index f01ed84..841b6ce 100644
--- a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/ShellTaskOrganizerTests.java
+++ b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/ShellTaskOrganizerTests.java
@@ -45,6 +45,8 @@
 import android.app.TaskInfo;
 import android.content.LocusId;
 import android.content.pm.ParceledListSlice;
+import android.graphics.Point;
+import android.graphics.Rect;
 import android.os.Binder;
 import android.os.IBinder;
 import android.os.RemoteException;
@@ -589,9 +591,9 @@
 
     @Test
     public void testRecentTasks_visibilityChanges_shouldNotifyTaskController() {
-        RunningTaskInfo task1 = createTaskInfo(/* taskId= */ 1, WINDOWING_MODE_FREEFORM);
+        RunningTaskInfo task1 = createFreeformTaskInfo(/* taskId= */ 1);
         mOrganizer.onTaskAppeared(task1, /* leash= */ null);
-        RunningTaskInfo task2 = createTaskInfo(/* taskId= */ 1, WINDOWING_MODE_FREEFORM);
+        RunningTaskInfo task2 = createFreeformTaskInfo(/* taskId= */ 1);
         task2.isVisible = false;
 
         mOrganizer.onTaskInfoChanged(task2);
@@ -600,6 +602,30 @@
     }
 
     @Test
+    public void testRecentTasks_sizeChanges_shouldNotifyTaskController() {
+        RunningTaskInfo task1 = createFreeformTaskInfo(/* taskId= */ 1);
+        mOrganizer.onTaskAppeared(task1, /* leash= */ null);
+        RunningTaskInfo task2 = createFreeformTaskInfo(/* taskId= */ 1);
+        task2.configuration.windowConfiguration.setAppBounds(new Rect(0, 0, 300, 400));
+
+        mOrganizer.onTaskInfoChanged(task2);
+
+        verify(mRecentTasksController).onTaskRunningInfoChanged(task2);
+    }
+
+    @Test
+    public void testRecentTasks_positionChanges_shouldNotifyTaskController() {
+        RunningTaskInfo task1 = createFreeformTaskInfo(/* taskId= */ 1);
+        mOrganizer.onTaskAppeared(task1, /* leash= */ null);
+        RunningTaskInfo task2 = createFreeformTaskInfo(/* taskId= */ 1);
+        task2.positionInParent = new Point(200, 200);
+
+        mOrganizer.onTaskInfoChanged(task2);
+
+        verify(mRecentTasksController).onTaskRunningInfoChanged(task2);
+    }
+
+    @Test
     public void testRecentTasks_visibilityChanges_notFreeForm_shouldNotNotifyTaskController() {
         RunningTaskInfo task1_visible = createTaskInfo(/* taskId= */ 1, WINDOWING_MODE_FULLSCREEN);
         mOrganizer.onTaskAppeared(task1_visible, /* leash= */ null);
@@ -649,6 +675,13 @@
         return taskInfo;
     }
 
+    private static RunningTaskInfo createFreeformTaskInfo(int taskId) {
+        RunningTaskInfo taskInfo = createTaskInfo(taskId, WINDOWING_MODE_FREEFORM);
+        taskInfo.positionInParent = new Point(100, 100);
+        taskInfo.configuration.windowConfiguration.setAppBounds(new Rect(0, 0, 200, 200));
+        return taskInfo;
+    }
+
     private void verifyOnCompatInfoChangedInvokedWith(TaskInfo taskInfo,
                                                       ShellTaskOrganizer.TaskListener listener) {
         final ArgumentCaptor<CompatUIInfo> capture = ArgumentCaptor.forClass(CompatUIInfo.class);
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 6b62adb..72d4dc6 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
@@ -51,6 +51,7 @@
 import android.graphics.Point;
 import android.graphics.Rect;
 import android.hardware.input.InputManager;
+import android.os.Binder;
 import android.os.Bundle;
 import android.os.Handler;
 import android.os.IBinder;
@@ -812,7 +813,10 @@
         if (taskId != INVALID_TASK_ID) {
             final ActivityManager.RunningTaskInfo taskInfo = new ActivityManager.RunningTaskInfo();
             taskInfo.taskId = taskId;
-            taskInfo.token = new WindowContainerToken(mock(IWindowContainerToken.class));
+            final IWindowContainerToken mockT = mock(IWindowContainerToken.class);
+            Binder binder = new Binder();
+            doReturn(binder).when(mockT).asBinder();
+            taskInfo.token = new WindowContainerToken(mockT);
             change = new TransitionInfo.Change(
                     taskInfo.token, b.build());
             change.setTaskInfo(taskInfo);
diff --git a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/desktopmode/DesktopTasksLimiterTest.kt b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/desktopmode/DesktopTasksLimiterTest.kt
index fa878d0..4d7e47f 100644
--- a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/desktopmode/DesktopTasksLimiterTest.kt
+++ b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/desktopmode/DesktopTasksLimiterTest.kt
@@ -329,7 +329,7 @@
                         wct = wct,
                         newFrontTaskId = setUpFreeformTask().taskId)
 
-        assertThat(minimizedTaskId).isEqualTo(tasks.first())
+        assertThat(minimizedTaskId).isEqualTo(tasks.first().taskId)
         assertThat(wct.hierarchyOps.size).isEqualTo(1)
         assertThat(wct.hierarchyOps[0].type).isEqualTo(HIERARCHY_OP_TYPE_REORDER)
         assertThat(wct.hierarchyOps[0].toTop).isFalse() // Reorder to bottom
@@ -355,7 +355,7 @@
     fun getTaskToMinimize_tasksWithinLimit_returnsNull() {
         val tasks = (1..MAX_TASK_LIMIT).map { setUpFreeformTask() }
 
-        val minimizedTask = desktopTasksLimiter.getTaskToMinimize(
+        val minimizedTask = desktopTasksLimiter.getTaskIdToMinimize(
                 visibleOrderedTasks = tasks.map { it.taskId })
 
         assertThat(minimizedTask).isNull()
@@ -365,11 +365,11 @@
     fun getTaskToMinimize_tasksAboveLimit_returnsBackTask() {
         val tasks = (1..MAX_TASK_LIMIT + 1).map { setUpFreeformTask() }
 
-        val minimizedTask = desktopTasksLimiter.getTaskToMinimize(
+        val minimizedTask = desktopTasksLimiter.getTaskIdToMinimize(
                 visibleOrderedTasks = tasks.map { it.taskId })
 
         // first == front, last == back
-        assertThat(minimizedTask).isEqualTo(tasks.last())
+        assertThat(minimizedTask).isEqualTo(tasks.last().taskId)
     }
 
     @Test
@@ -379,23 +379,23 @@
                 interactionJankMonitor, mContext, handler)
         val tasks = (1..MAX_TASK_LIMIT2 + 1).map { setUpFreeformTask() }
 
-        val minimizedTask = desktopTasksLimiter.getTaskToMinimize(
+        val minimizedTask = desktopTasksLimiter.getTaskIdToMinimize(
             visibleOrderedTasks = tasks.map { it.taskId })
 
         // first == front, last == back
-        assertThat(minimizedTask).isEqualTo(tasks.last())
+        assertThat(minimizedTask).isEqualTo(tasks.last().taskId)
     }
 
     @Test
     fun getTaskToMinimize_withNewTask_tasksAboveLimit_returnsBackTask() {
         val tasks = (1..MAX_TASK_LIMIT).map { setUpFreeformTask() }
 
-        val minimizedTask = desktopTasksLimiter.getTaskToMinimize(
+        val minimizedTask = desktopTasksLimiter.getTaskIdToMinimize(
                 visibleOrderedTasks = tasks.map { it.taskId },
                 newTaskIdInFront = setUpFreeformTask().taskId)
 
         // first == front, last == back
-        assertThat(minimizedTask).isEqualTo(tasks.last())
+        assertThat(minimizedTask).isEqualTo(tasks.last().taskId)
     }
 
     @Test
diff --git a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/pip/phone/PipResizeGestureHandlerTest.java b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/pip/phone/PipResizeGestureHandlerTest.java
index 66f8c0b..880ca2c 100644
--- a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/pip/phone/PipResizeGestureHandlerTest.java
+++ b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/pip/phone/PipResizeGestureHandlerTest.java
@@ -124,7 +124,7 @@
         mPipResizeGestureHandler = new PipResizeGestureHandler(mContext, pipBoundsAlgorithm,
                 mPipBoundsState, motionHelper, mPipTouchState, mPipTaskOrganizer,
                 mPipDismissTargetHandler,
-                () -> {}, mPipUiEventLogger, mPhonePipMenuController,
+                (Rect bounds) -> new Rect(), () -> {}, mPipUiEventLogger, mPhonePipMenuController,
                 mMainExecutor, null /* pipPerfHintController */) {
             @Override
             public void pilferPointers() {
diff --git a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/taskview/TaskViewTransitionsTest.java b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/taskview/TaskViewTransitionsTest.java
index d3e40f2..60e2030 100644
--- a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/taskview/TaskViewTransitionsTest.java
+++ b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/taskview/TaskViewTransitionsTest.java
@@ -33,7 +33,8 @@
 import android.app.ActivityManager;
 import android.graphics.Rect;
 import android.os.IBinder;
-import android.testing.AndroidTestingRunner;
+import android.platform.test.flag.junit.FlagsParameterization;
+import android.platform.test.flag.junit.SetFlagsRule;
 import android.testing.TestableLooper;
 import android.view.SurfaceControl;
 import android.window.TransitionInfo;
@@ -42,10 +43,12 @@
 
 import androidx.test.filters.SmallTest;
 
+import com.android.wm.shell.Flags;
 import com.android.wm.shell.ShellTestCase;
 import com.android.wm.shell.transition.Transitions;
 
 import org.junit.Before;
+import org.junit.Rule;
 import org.junit.Test;
 import org.junit.runner.RunWith;
 import org.mockito.Mock;
@@ -54,11 +57,23 @@
 import java.util.ArrayList;
 import java.util.List;
 
+import platform.test.runner.parameterized.ParameterizedAndroidJunit4;
+import platform.test.runner.parameterized.Parameters;
+
 @SmallTest
-@RunWith(AndroidTestingRunner.class)
+@RunWith(ParameterizedAndroidJunit4.class)
 @TestableLooper.RunWithLooper(setAsMainLooper = true)
 public class TaskViewTransitionsTest extends ShellTestCase {
 
+    @Parameters(name = "{0}")
+    public static List<FlagsParameterization> getParams() {
+        return FlagsParameterization.allCombinationsOf(
+                Flags.FLAG_ENABLE_TASK_VIEW_CONTROLLER_CLEANUP);
+    }
+
+    @Rule
+    public final SetFlagsRule mSetFlagsRule;
+
     @Mock
     Transitions mTransitions;
     @Mock
@@ -70,6 +85,10 @@
 
     TaskViewTransitions mTaskViewTransitions;
 
+    public TaskViewTransitionsTest(FlagsParameterization flags) {
+        mSetFlagsRule = new SetFlagsRule(flags);
+    }
+
     @Before
     public void setUp() {
         MockitoAnnotations.initMocks(this);
diff --git a/lint-baseline.xml b/lint-baseline.xml
index 0320aab..8253c1f 100644
--- a/lint-baseline.xml
+++ b/lint-baseline.xml
@@ -11265,4 +11265,15 @@
             column="24"/>
     </issue>
 
-</issues>
\ No newline at end of file
+    <issue
+        id="SimpleManualPermissionEnforcement"
+        message="INetworkScoreCache permission check should be converted to @EnforcePermission annotation"
+        errorLine1="        mContext.enforceCallingOrSelfPermission(permission.DUMP, TAG);"
+        errorLine2="        ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~">
+        <location
+            file="frameworks/base/wifi/java/src/android/net/wifi/WifiNetworkScoreCache.java"
+            line="250"
+            column="9"/>
+    </issue>
+
+</issues>
diff --git a/media/java/android/media/AudioDeviceInfo.java b/media/java/android/media/AudioDeviceInfo.java
index 0efefb9..39b29d0 100644
--- a/media/java/android/media/AudioDeviceInfo.java
+++ b/media/java/android/media/AudioDeviceInfo.java
@@ -279,6 +279,7 @@
             TYPE_AUX_LINE,
             TYPE_IP,
             TYPE_BUS,
+            TYPE_REMOTE_SUBMIX,
             TYPE_HEARING_AID,
             TYPE_BUILTIN_SPEAKER_SAFE,
             TYPE_BLE_HEADSET,
@@ -312,6 +313,7 @@
             case TYPE_AUX_LINE:
             case TYPE_IP:
             case TYPE_BUS:
+            case TYPE_REMOTE_SUBMIX:
             case TYPE_HEARING_AID:
             case TYPE_BUILTIN_SPEAKER_SAFE:
             case TYPE_BLE_HEADSET:
diff --git a/media/java/android/media/audio/common/AidlConversion.java b/media/java/android/media/audio/common/AidlConversion.java
index 65fd51b..c1d73f9 100644
--- a/media/java/android/media/audio/common/AidlConversion.java
+++ b/media/java/android/media/audio/common/AidlConversion.java
@@ -753,9 +753,11 @@
                 break;
             case AudioSystem.DEVICE_IN_REMOTE_SUBMIX:
                 aidl.type = AudioDeviceType.IN_SUBMIX;
+                aidl.connection = AudioDeviceDescription.CONNECTION_VIRTUAL;
                 break;
             case AudioSystem.DEVICE_OUT_REMOTE_SUBMIX:
                 aidl.type = AudioDeviceType.OUT_SUBMIX;
+                aidl.connection = AudioDeviceDescription.CONNECTION_VIRTUAL;
                 break;
             case AudioSystem.DEVICE_IN_ANLG_DOCK_HEADSET:
                 aidl.type = AudioDeviceType.IN_DOCK;
diff --git a/packages/SettingsLib/src/com/android/settingslib/license/LicenseHtmlLoaderCompat.java b/packages/SettingsLib/src/com/android/settingslib/license/LicenseHtmlLoaderCompat.java
index 121f549..3696000 100644
--- a/packages/SettingsLib/src/com/android/settingslib/license/LicenseHtmlLoaderCompat.java
+++ b/packages/SettingsLib/src/com/android/settingslib/license/LicenseHtmlLoaderCompat.java
@@ -41,6 +41,7 @@
             "/system_ext/etc/NOTICE.xml.gz",
             "/vendor_dlkm/etc/NOTICE.xml.gz",
             "/odm_dlkm/etc/NOTICE.xml.gz",
+            "/system_dlkm/etc/NOTICE.xml.gz",
     };
     static final String NOTICE_HTML_FILE_NAME = "NOTICE.html";
 
diff --git a/packages/SystemUI/aconfig/systemui.aconfig b/packages/SystemUI/aconfig/systemui.aconfig
index 2278789..3d2c931 100644
--- a/packages/SystemUI/aconfig/systemui.aconfig
+++ b/packages/SystemUI/aconfig/systemui.aconfig
@@ -33,6 +33,13 @@
 }
 
 flag {
+  name: "notifications_redesign_footer_view"
+  namespace: "systemui"
+  description: "Notifications Redesign: Update the look of the notifications footer."
+  bug: "375010573"
+}
+
+flag {
    name: "notification_row_content_binder_refactor"
    namespace: "systemui"
    description: "Convert the NotificationContentInflater to Kotlin and restructure it to support modern views"
@@ -139,6 +146,13 @@
 }
 
 flag {
+    name: "notifications_dismiss_pruned_summaries"
+    namespace: "systemui"
+    description: "NotifCollection.dismissNotifications will now dismiss summaries that are pruned from the shade."
+    bug: "355967751"
+}
+
+flag {
    name: "notification_transparent_header_fix"
    namespace: "systemui"
    description: "fix the transparent group header issue for async header inflation."
@@ -421,9 +435,9 @@
 }
 
 flag {
-    name: "status_bar_notification_chips"
+    name: "status_bar_ron_chips"
     namespace: "systemui"
-    description: "Show promoted ongoing notifications as chips in the status bar"
+    description: "Show rich ongoing notifications as chips in the status bar"
     bug: "361346412"
 }
 
@@ -1104,6 +1118,13 @@
 }
 
 flag {
+  name: "communal_standalone_support"
+  namespace: "systemui"
+  description: "Support communal features without a dock"
+  bug: "352301247"
+}
+
+flag {
     name: "dream_overlay_updated_font"
     namespace: "systemui"
     description: "Flag to enable updated font settings for dream overlay"
@@ -1546,6 +1567,16 @@
 }
 
 flag {
+  name: "transition_race_condition"
+  namespace: "systemui"
+  description: "Thread-safe keyguard transitions"
+  bug: "358533338"
+  metadata {
+       purpose: PURPOSE_BUGFIX
+  }
+}
+
+flag {
    name: "media_projection_request_attribution_fix"
    namespace: "systemui"
    description: "Ensure MediaProjection consent requests are properly attributed"
diff --git a/packages/SystemUI/animation/lib/Android.bp b/packages/SystemUI/animation/lib/Android.bp
index d9230ec..7d73023 100644
--- a/packages/SystemUI/animation/lib/Android.bp
+++ b/packages/SystemUI/animation/lib/Android.bp
@@ -48,6 +48,19 @@
 }
 
 filegroup {
+    name: "PlatformAnimationLib-client-srcs",
+    srcs: [
+        "src/com/android/systemui/animation/OriginRemoteTransition.java",
+        "src/com/android/systemui/animation/OriginTransitionSession.java",
+        "src/com/android/systemui/animation/SurfaceUIComponent.java",
+        "src/com/android/systemui/animation/Transactions.java",
+        "src/com/android/systemui/animation/UIComponent.java",
+        "src/com/android/systemui/animation/ViewUIComponent.java",
+        ":PlatformAnimationLib-aidl",
+    ],
+}
+
+filegroup {
     name: "PlatformAnimationLib-aidl",
     srcs: [
         "src/**/*.aidl",
diff --git a/packages/SystemUI/animation/lib/OWNERS b/packages/SystemUI/animation/lib/OWNERS
new file mode 100644
index 0000000..7569419
--- /dev/null
+++ b/packages/SystemUI/animation/lib/OWNERS
@@ -0,0 +1,3 @@
+#inherits OWNERS from SystemUI in addition to WEAR framework owners below
+file:platform/frameworks/base:/WEAR_OWNERS
+
diff --git a/packages/SystemUI/animation/lib/src/com/android/systemui/animation/OriginRemoteTransition.java b/packages/SystemUI/animation/lib/src/com/android/systemui/animation/OriginRemoteTransition.java
index 08db95e..2b5ff7c 100644
--- a/packages/SystemUI/animation/lib/src/com/android/systemui/animation/OriginRemoteTransition.java
+++ b/packages/SystemUI/animation/lib/src/com/android/systemui/animation/OriginRemoteTransition.java
@@ -44,6 +44,7 @@
 /**
  * An implementation of {@link IRemoteTransition} that accepts a {@link UIComponent} as the origin
  * and automatically attaches it to the transition leash before the transition starts.
+ * @hide
  */
 public class OriginRemoteTransition extends IRemoteTransition.Stub {
     private static final String TAG = "OriginRemoteTransition";
@@ -328,7 +329,9 @@
                 /* baseBounds= */ maxBounds);
     }
 
-    /** An interface that represents an origin transitions. */
+    /** An interface that represents an origin transitions.
+     * @hide
+     */
     public interface TransitionPlayer {
 
         /**
diff --git a/packages/SystemUI/animation/lib/src/com/android/systemui/animation/OriginTransitionSession.java b/packages/SystemUI/animation/lib/src/com/android/systemui/animation/OriginTransitionSession.java
index 23693b6..6d6aa88 100644
--- a/packages/SystemUI/animation/lib/src/com/android/systemui/animation/OriginTransitionSession.java
+++ b/packages/SystemUI/animation/lib/src/com/android/systemui/animation/OriginTransitionSession.java
@@ -43,6 +43,7 @@
 /**
  * A session object that holds origin transition states for starting an activity from an on-screen
  * UI component and smoothly transitioning back from the activity to the same UI component.
+ * @hide
  */
 public class OriginTransitionSession {
     private static final String TAG = "OriginTransitionSession";
@@ -179,7 +180,10 @@
         }
     }
 
-    /** A builder to build a {@link OriginTransitionSession}. */
+    /**
+     * A builder to build a {@link OriginTransitionSession}.
+     * @hide
+     */
     public static class Builder {
         private final Context mContext;
         @Nullable private final IOriginTransitions mOriginTransitions;
diff --git a/packages/SystemUI/animation/lib/src/com/android/systemui/animation/SurfaceUIComponent.java b/packages/SystemUI/animation/lib/src/com/android/systemui/animation/SurfaceUIComponent.java
index 2438736..e1ff2a4 100644
--- a/packages/SystemUI/animation/lib/src/com/android/systemui/animation/SurfaceUIComponent.java
+++ b/packages/SystemUI/animation/lib/src/com/android/systemui/animation/SurfaceUIComponent.java
@@ -26,7 +26,10 @@
 import java.util.Collection;
 import java.util.concurrent.Executor;
 
-/** A {@link UIComponent} representing a {@link SurfaceControl}. */
+/**
+ * A {@link UIComponent} representing a {@link SurfaceControl}.
+ * @hide
+ */
 public class SurfaceUIComponent implements UIComponent {
     private final Collection<SurfaceControl> mSurfaces;
     private final Rect mBaseBounds;
@@ -89,7 +92,10 @@
                 + "}";
     }
 
-    /** A {@link Transaction} wrapping a {@link SurfaceControl.Transaction}. */
+    /**
+     * A {@link Transaction} wrapping a {@link SurfaceControl.Transaction}.
+     * @hide
+     */
     public static class Transaction implements UIComponent.Transaction<SurfaceUIComponent> {
         private final SurfaceControl.Transaction mTransaction;
         private final ArrayList<Runnable> mChanges = new ArrayList<>();
diff --git a/packages/SystemUI/animation/lib/src/com/android/systemui/animation/Transactions.java b/packages/SystemUI/animation/lib/src/com/android/systemui/animation/Transactions.java
index 5240d99..64d2172 100644
--- a/packages/SystemUI/animation/lib/src/com/android/systemui/animation/Transactions.java
+++ b/packages/SystemUI/animation/lib/src/com/android/systemui/animation/Transactions.java
@@ -27,6 +27,7 @@
 /**
  * A composite {@link UIComponent.Transaction} that combines multiple other transactions for each ui
  * type.
+ * @hide
  */
 public class Transactions implements UIComponent.Transaction<UIComponent> {
     private final Map<Class, UIComponent.Transaction> mTransactions = new ArrayMap<>();
diff --git a/packages/SystemUI/animation/lib/src/com/android/systemui/animation/UIComponent.java b/packages/SystemUI/animation/lib/src/com/android/systemui/animation/UIComponent.java
index 747e4d1..8aec2f4 100644
--- a/packages/SystemUI/animation/lib/src/com/android/systemui/animation/UIComponent.java
+++ b/packages/SystemUI/animation/lib/src/com/android/systemui/animation/UIComponent.java
@@ -17,12 +17,17 @@
 package com.android.systemui.animation;
 
 import android.annotation.FloatRange;
+import android.annotation.NonNull;
+import android.annotation.Nullable;
 import android.graphics.Rect;
 import android.view.SurfaceControl;
 
 import java.util.concurrent.Executor;
 
-/** An interface representing an UI component on the display. */
+/**
+ * An interface representing an UI component on the display.
+ * @hide
+ */
 public interface UIComponent {
 
     /** Get the current alpha of this UI. */
@@ -32,39 +37,48 @@
     boolean isVisible();
 
     /** Get the bounds of this UI in its display. */
+    @NonNull
     Rect getBounds();
 
     /** Create a new {@link Transaction} that can update this UI. */
+    @NonNull
     Transaction newTransaction();
 
     /**
      * A transaction class for updating {@link UIComponent}.
      *
      * @param <T> the subtype of {@link UIComponent} that this {@link Transaction} can handle.
+     * @hide
      */
     interface Transaction<T extends UIComponent> {
         /** Update alpha of an UI. Execution will be delayed until {@link #commit()} is called. */
-        Transaction setAlpha(T ui, @FloatRange(from = 0.0, to = 1.0) float alpha);
+        Transaction setAlpha(@NonNull T ui, @FloatRange(from = 0.0, to = 1.0) float alpha);
 
         /**
          * Update visibility of an UI. Execution will be delayed until {@link #commit()} is called.
          */
-        Transaction setVisible(T ui, boolean visible);
+        @NonNull
+        Transaction setVisible(@NonNull T ui, boolean visible);
 
         /** Update bounds of an UI. Execution will be delayed until {@link #commit()} is called. */
-        Transaction setBounds(T ui, Rect bounds);
+        @NonNull
+        Transaction setBounds(@NonNull T ui, @NonNull Rect bounds);
 
         /**
          * Attach a ui to the transition leash. Execution will be delayed until {@link #commit()} is
          * called.
          */
-        Transaction attachToTransitionLeash(T ui, SurfaceControl transitionLeash, int w, int h);
+        @NonNull
+        Transaction attachToTransitionLeash(
+                @NonNull T ui, @NonNull SurfaceControl transitionLeash, int w, int h);
 
         /**
          * Detach a ui from the transition leash. Execution will be delayed until {@link #commit} is
          * called.
          */
-        Transaction detachFromTransitionLeash(T ui, Executor executor, Runnable onDone);
+        @NonNull
+        Transaction detachFromTransitionLeash(
+                @NonNull T ui, @NonNull Executor executor, @Nullable Runnable onDone);
 
         /** Commit any pending changes added to this transaction. */
         void commit();
diff --git a/packages/SystemUI/animation/lib/src/com/android/systemui/animation/ViewUIComponent.java b/packages/SystemUI/animation/lib/src/com/android/systemui/animation/ViewUIComponent.java
index 313789c..4c047d5 100644
--- a/packages/SystemUI/animation/lib/src/com/android/systemui/animation/ViewUIComponent.java
+++ b/packages/SystemUI/animation/lib/src/com/android/systemui/animation/ViewUIComponent.java
@@ -38,6 +38,7 @@
  * be changed to INVISIBLE in its view tree. This allows the {@link View} to transform in the
  * full-screen size leash without being constrained by the view tree's boundary or inheriting its
  * parent's alpha and transformation.
+ * @hide
  */
 public class ViewUIComponent implements UIComponent {
     private static final String TAG = "ViewUIComponent";
@@ -234,6 +235,9 @@
         mView.post(this::draw);
     }
 
+    /**
+     * @hide
+     */
     public static class Transaction implements UIComponent.Transaction<ViewUIComponent> {
         private final List<Runnable> mChanges = new ArrayList<>();
 
diff --git a/packages/SystemUI/animation/src/com/android/systemui/animation/back/FlingOnBackAnimationCallback.kt b/packages/SystemUI/animation/src/com/android/systemui/animation/back/FlingOnBackAnimationCallback.kt
index cd2dd04..47ad0b3 100644
--- a/packages/SystemUI/animation/src/com/android/systemui/animation/back/FlingOnBackAnimationCallback.kt
+++ b/packages/SystemUI/animation/src/com/android/systemui/animation/back/FlingOnBackAnimationCallback.kt
@@ -95,16 +95,18 @@
     final override fun onBackProgressed(backEvent: BackEvent) {
         val interpolatedProgress = progressInterpolator.getInterpolation(backEvent.progress)
         if (predictiveBackTimestampApi()) {
-            velocityTracker.addMovement(
-                MotionEvent.obtain(
-                    /* downTime */ downTime!!,
-                    /* eventTime */ backEvent.frameTimeMillis,
-                    /* action */ ACTION_MOVE,
-                    /* x */ interpolatedProgress * SCALE_FACTOR,
-                    /* y */ 0f,
-                    /* metaState */ 0,
+            downTime?.let { downTime ->
+                velocityTracker.addMovement(
+                    MotionEvent.obtain(
+                        /* downTime */ downTime,
+                        /* eventTime */ backEvent.frameTimeMillis,
+                        /* action */ ACTION_MOVE,
+                        /* x */ interpolatedProgress * SCALE_FACTOR,
+                        /* y */ 0f,
+                        /* metaState */ 0,
+                    )
                 )
-            )
+            }
             lastBackEvent =
                 BackEvent(
                     backEvent.touchX,
diff --git a/packages/SystemUI/common/Android.bp b/packages/SystemUI/common/Android.bp
index 6fc13d7..91dc3e3 100644
--- a/packages/SystemUI/common/Android.bp
+++ b/packages/SystemUI/common/Android.bp
@@ -22,20 +22,13 @@
     default_applicable_licenses: ["frameworks_base_packages_SystemUI_license"],
 }
 
-android_library {
-
+java_library {
     name: "SystemUICommon",
-    use_resource_processor: true,
 
     srcs: [
         "src/**/*.java",
         "src/**/*.kt",
     ],
 
-    static_libs: [
-        "androidx.core_core-ktx",
-    ],
-
-    manifest: "AndroidManifest.xml",
     kotlincflags: ["-Xjvm-default=all"],
 }
diff --git a/packages/SystemUI/common/AndroidManifest.xml b/packages/SystemUI/common/AndroidManifest.xml
deleted file mode 100644
index 6f757eb..0000000
--- a/packages/SystemUI/common/AndroidManifest.xml
+++ /dev/null
@@ -1,21 +0,0 @@
-<?xml version="1.0" encoding="utf-8"?>
-<!--
-     Copyright (C) 2023 The Android Open Source Project
-
-     Licensed under the Apache License, Version 2.0 (the "License");
-     you may not use this file except in compliance with the License.
-     You may obtain a copy of the License at
-
-          http://www.apache.org/licenses/LICENSE-2.0
-
-     Unless required by applicable law or agreed to in writing, software
-     distributed under the License is distributed on an "AS IS" BASIS,
-     WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
-     See the License for the specific language governing permissions and
-     limitations under the License.
--->
-
-<manifest xmlns:android="http://schemas.android.com/apk/res/android"
-    package="com.android.systemui.common">
-
-</manifest>
diff --git a/packages/SystemUI/common/README.md b/packages/SystemUI/common/README.md
index 1cc5277..17e5412 100644
--- a/packages/SystemUI/common/README.md
+++ b/packages/SystemUI/common/README.md
@@ -1,5 +1,9 @@
 # SystemUICommon
 
-`SystemUICommon` is a module within SystemUI that hosts standalone helper libraries. It is intended to be used by other modules, and therefore should not have other SystemUI dependencies to avoid circular dependencies.
+`SystemUICommon` is a module within SystemUI that hosts standalone helper libraries. It is intended
+to be used by other modules, and therefore should not have other SystemUI dependencies to avoid
+circular dependencies.
 
-To maintain the structure of this module, please refrain from adding components at the top level. Instead, add them to specific sub-packages, such as `systemui/common/buffer/`. This will help to keep the module organized and easy to navigate.
+To maintain the structure of this module, please refrain from adding components at the top level.
+Instead, add them to specific sub-packages, such as `systemui/common/buffer/`. This will help to
+keep the module organized and easy to navigate.
diff --git a/packages/SystemUI/compose/features/src/com/android/systemui/notifications/ui/composable/NotificationScrimNestedScrollConnection.kt b/packages/SystemUI/compose/features/src/com/android/systemui/notifications/ui/composable/NotificationScrimNestedScrollConnection.kt
index e4c60e1..5cb45e5 100644
--- a/packages/SystemUI/compose/features/src/com/android/systemui/notifications/ui/composable/NotificationScrimNestedScrollConnection.kt
+++ b/packages/SystemUI/compose/features/src/com/android/systemui/notifications/ui/composable/NotificationScrimNestedScrollConnection.kt
@@ -18,9 +18,11 @@
 
 import androidx.compose.foundation.gestures.Orientation
 import androidx.compose.ui.input.nestedscroll.NestedScrollConnection
+import androidx.compose.ui.input.nestedscroll.NestedScrollSource
 import androidx.compose.ui.util.fastCoerceAtLeast
 import androidx.compose.ui.util.fastCoerceAtMost
 import com.android.compose.nestedscroll.PriorityNestedScrollConnection
+import com.android.compose.nestedscroll.ScrollController
 
 /**
  * A [NestedScrollConnection] that listens for all vertical scroll events and responds in the
@@ -58,33 +60,40 @@
             offsetAvailable > 0 && (scrimOffset() < maxScrimOffset || isCurrentGestureOverscroll())
         },
         canStartPostFling = { false },
-        canStopOnPreFling = { false },
-        onStart = { offsetAvailable -> onStart(offsetAvailable) },
-        onScroll = { offsetAvailable, _ ->
-            val currentHeight = scrimOffset()
-            val amountConsumed =
-                if (offsetAvailable > 0) {
-                    val amountLeft = maxScrimOffset - currentHeight
-                    offsetAvailable.fastCoerceAtMost(amountLeft)
-                } else {
-                    val amountLeft = minScrimOffset() - currentHeight
-                    offsetAvailable.fastCoerceAtLeast(amountLeft)
+        onStart = { firstScroll ->
+            onStart(firstScroll)
+            object : ScrollController {
+                override fun onScroll(deltaScroll: Float, source: NestedScrollSource): Float {
+                    val currentHeight = scrimOffset()
+                    val amountConsumed =
+                        if (deltaScroll > 0) {
+                            val amountLeft = maxScrimOffset - currentHeight
+                            deltaScroll.fastCoerceAtMost(amountLeft)
+                        } else {
+                            val amountLeft = minScrimOffset() - currentHeight
+                            deltaScroll.fastCoerceAtLeast(amountLeft)
+                        }
+                    snapScrimOffset(currentHeight + amountConsumed)
+                    return amountConsumed
                 }
-            snapScrimOffset(currentHeight + amountConsumed)
-            amountConsumed
-        },
-        onStop = { velocityAvailable ->
-            onStop(velocityAvailable)
-            if (scrimOffset() < minScrimOffset()) {
-                animateScrimOffset(minScrimOffset())
-            }
-            // Don't consume the velocity on pre/post fling
-            0f
-        },
-        onCancel = {
-            onStop(0f)
-            if (scrimOffset() < minScrimOffset()) {
-                animateScrimOffset(minScrimOffset())
+
+                override suspend fun onStop(initialVelocity: Float): Float {
+                    onStop(initialVelocity)
+                    if (scrimOffset() < minScrimOffset()) {
+                        animateScrimOffset(minScrimOffset())
+                    }
+                    // Don't consume the velocity on pre/post fling
+                    return 0f
+                }
+
+                override fun onCancel() {
+                    onStop(0f)
+                    if (scrimOffset() < minScrimOffset()) {
+                        animateScrimOffset(minScrimOffset())
+                    }
+                }
+
+                override fun canStopOnPreFling() = false
             }
         },
     )
diff --git a/packages/SystemUI/compose/features/src/com/android/systemui/notifications/ui/composable/NotificationStackNestedScrollConnection.kt b/packages/SystemUI/compose/features/src/com/android/systemui/notifications/ui/composable/NotificationStackNestedScrollConnection.kt
index edb05eb..e1b74a9 100644
--- a/packages/SystemUI/compose/features/src/com/android/systemui/notifications/ui/composable/NotificationStackNestedScrollConnection.kt
+++ b/packages/SystemUI/compose/features/src/com/android/systemui/notifications/ui/composable/NotificationStackNestedScrollConnection.kt
@@ -23,6 +23,7 @@
 import androidx.compose.runtime.Composable
 import androidx.compose.runtime.remember
 import androidx.compose.ui.Modifier
+import androidx.compose.ui.input.nestedscroll.NestedScrollSource
 import androidx.compose.ui.input.nestedscroll.nestedScroll
 import androidx.compose.ui.platform.LocalConfiguration
 import androidx.compose.ui.platform.LocalDensity
@@ -30,6 +31,7 @@
 import androidx.compose.ui.unit.dp
 import androidx.compose.ui.util.fastCoerceAtLeast
 import com.android.compose.nestedscroll.PriorityNestedScrollConnection
+import com.android.compose.nestedscroll.ScrollController
 import kotlin.math.max
 import kotlin.math.roundToInt
 import kotlin.math.tanh
@@ -92,20 +94,29 @@
             offsetAvailable < 0f && offsetBeforeStart < 0f && !canScrollForward()
         },
         canStartPostFling = { velocityAvailable -> velocityAvailable < 0f && !canScrollForward() },
-        canStopOnPreFling = { false },
-        onStart = { offsetAvailable -> onStart(offsetAvailable) },
-        onScroll = { offsetAvailable, _ ->
-            val minOffset = 0f
-            val consumed = offsetAvailable.fastCoerceAtLeast(minOffset - stackOffset())
-            if (consumed != 0f) {
-                onScroll(consumed)
+        onStart = { firstScroll ->
+            onStart(firstScroll)
+            object : ScrollController {
+                override fun onScroll(deltaScroll: Float, source: NestedScrollSource): Float {
+                    val minOffset = 0f
+                    val consumed = deltaScroll.fastCoerceAtLeast(minOffset - stackOffset())
+                    if (consumed != 0f) {
+                        onScroll(consumed)
+                    }
+                    return consumed
+                }
+
+                override suspend fun onStop(initialVelocity: Float): Float {
+                    onStop(initialVelocity)
+                    return initialVelocity
+                }
+
+                override fun onCancel() {
+                    onStop(0f)
+                }
+
+                override fun canStopOnPreFling() = false
             }
-            consumed
         },
-        onStop = { velocityAvailable ->
-            onStop(velocityAvailable)
-            velocityAvailable
-        },
-        onCancel = { onStop(0f) },
     )
 }
diff --git a/packages/SystemUI/compose/scene/src/com/android/compose/animation/scene/DraggableHandler.kt b/packages/SystemUI/compose/scene/src/com/android/compose/animation/scene/DraggableHandler.kt
index 8469007..7c7202a 100644
--- a/packages/SystemUI/compose/scene/src/com/android/compose/animation/scene/DraggableHandler.kt
+++ b/packages/SystemUI/compose/scene/src/com/android/compose/animation/scene/DraggableHandler.kt
@@ -20,6 +20,7 @@
 
 import androidx.compose.foundation.gestures.Orientation
 import androidx.compose.ui.geometry.Offset
+import androidx.compose.ui.input.nestedscroll.NestedScrollSource
 import androidx.compose.ui.unit.dp
 import androidx.compose.ui.unit.round
 import androidx.compose.ui.util.fastCoerceIn
@@ -27,6 +28,7 @@
 import com.android.compose.animation.scene.content.state.TransitionState
 import com.android.compose.animation.scene.content.state.TransitionState.HasOverscrollProperties.Companion.DistanceUnspecified
 import com.android.compose.nestedscroll.PriorityNestedScrollConnection
+import com.android.compose.nestedscroll.ScrollController
 import kotlin.math.absoluteValue
 
 internal typealias SuspendedValue<T> = suspend () -> T
@@ -66,6 +68,7 @@
     internal val orientation: Orientation,
 ) : DraggableHandler {
     internal val nestedScrollKey = Any()
+
     /** The [DraggableHandler] can only have one active [DragController] at a time. */
     private var dragController: DragControllerImpl? = null
 
@@ -345,6 +348,7 @@
                     distance == DistanceUnspecified ||
                         swipeAnimation.contentTransition.isWithinProgressRange(desiredProgress) ->
                         desiredOffset
+
                     distance > 0f -> desiredOffset.fastCoerceIn(0f, distance)
                     else -> desiredOffset.fastCoerceIn(distance, 0f)
                 }
@@ -545,6 +549,7 @@
             upOrLeftResult == null && downOrRightResult == null -> null
             (directionOffset < 0f && upOrLeftResult != null) || downOrRightResult == null ->
                 upOrLeftResult
+
             else -> downOrRightResult
         }
     }
@@ -608,7 +613,6 @@
             return overscrollSpec != null
         }
 
-        var dragController: DragController? = null
         var isIntercepting = false
 
         return PriorityNestedScrollConnection(
@@ -669,10 +673,12 @@
                             canChangeScene = isZeroOffset
                             isZeroOffset && hasNextScene(offsetAvailable)
                         }
+
                         NestedScrollBehavior.EdgeWithPreview -> {
                             canChangeScene = isZeroOffset
                             hasNextScene(offsetAvailable)
                         }
+
                         NestedScrollBehavior.EdgeAlways -> {
                             canChangeScene = true
                             hasNextScene(offsetAvailable)
@@ -710,56 +716,59 @@
 
                 canStart
             },
-            // We need to maintain scroll priority even if the scene transition can no longer
-            // consume the scroll gesture to allow us to return to the previous scene.
-            canStopOnScroll = { _, _ -> false },
-            canStopOnPreFling = { true },
-            onStart = { offsetAvailable ->
+            onStart = { firstScroll ->
                 val pointersInfo = pointersInfo()
-                dragController =
-                    draggableHandler.onDragStarted(
-                        pointersDown = pointersInfo.pointersDown,
-                        startedPosition = pointersInfo.startedPosition,
-                        overSlop = if (isIntercepting) 0f else offsetAvailable,
-                    )
-            },
-            onScroll = { offsetAvailable, _ ->
-                val controller = dragController ?: error("Should be called after onStart")
-
-                val pointersInfo = pointersInfoOwner.pointersInfo()
-                if (pointersInfo.isMouseWheel) {
-                    // Do not support mouse wheel interactions
-                    return@PriorityNestedScrollConnection 0f
-                }
-
-                // TODO(b/297842071) We should handle the overscroll or slow drag if the gesture is
-                // initiated in a nested child.
-                controller.onDrag(delta = offsetAvailable)
-            },
-            onStop = { velocityAvailable ->
-                val controller = dragController ?: error("Should be called after onStart")
-                try {
-                    controller
-                        .onStop(velocity = velocityAvailable, canChangeContent = canChangeScene)
-                        .invoke()
-                } finally {
-                    // onStop might still be running when a new gesture begins.
-                    // To prevent conflicts, we should only remove the drag controller if it's the
-                    // same one that was active initially.
-                    if (dragController == controller) {
-                        dragController = null
-                    }
-                }
-            },
-            onCancel = {
-                val controller = dragController ?: error("Should be called after onStart")
-                controller.onStop(velocity = 0f, canChangeContent = canChangeScene)
-                dragController = null
+                scrollController(
+                    dragController =
+                        draggableHandler.onDragStarted(
+                            pointersDown = pointersInfo.pointersDown,
+                            startedPosition = pointersInfo.startedPosition,
+                            overSlop = if (isIntercepting) 0f else firstScroll,
+                        ),
+                    canChangeScene = canChangeScene,
+                    pointersInfoOwner = pointersInfoOwner,
+                )
             },
         )
     }
 }
 
+private fun scrollController(
+    dragController: DragController,
+    canChangeScene: Boolean,
+    pointersInfoOwner: PointersInfoOwner,
+): ScrollController {
+    return object : ScrollController {
+        override fun onScroll(deltaScroll: Float, source: NestedScrollSource): Float {
+            val pointersInfo = pointersInfoOwner.pointersInfo()
+            if (pointersInfo.isMouseWheel) {
+                // Do not support mouse wheel interactions
+                return 0f
+            }
+
+            return dragController.onDrag(delta = deltaScroll)
+        }
+
+        override suspend fun onStop(initialVelocity: Float): Float {
+            return dragController
+                .onStop(velocity = initialVelocity, canChangeContent = canChangeScene)
+                .invoke()
+        }
+
+        override fun onCancel() {
+            dragController.onStop(velocity = 0f, canChangeContent = canChangeScene)
+        }
+
+        /**
+         * We need to maintain scroll priority even if the scene transition can no longer consume
+         * the scroll gesture to allow us to return to the previous scene.
+         */
+        override fun canCancelScroll(available: Float, consumed: Float) = false
+
+        override fun canStopOnPreFling() = true
+    }
+}
+
 /**
  * The number of pixels below which there won't be a visible difference in the transition and from
  * which the animation can stop.
diff --git a/packages/SystemUI/compose/scene/src/com/android/compose/animation/scene/NestedScrollToScene.kt b/packages/SystemUI/compose/scene/src/com/android/compose/animation/scene/NestedScrollToScene.kt
index 8a0e462..fbd1cd5 100644
--- a/packages/SystemUI/compose/scene/src/com/android/compose/animation/scene/NestedScrollToScene.kt
+++ b/packages/SystemUI/compose/scene/src/com/android/compose/animation/scene/NestedScrollToScene.kt
@@ -128,10 +128,10 @@
 ) : DelegatingNode() {
     private var scrollBehaviorOwner: ScrollBehaviorOwner? = null
 
-    private fun requireScrollBehaviorOwner(): ScrollBehaviorOwner {
+    private fun findScrollBehaviorOwner(): ScrollBehaviorOwner? {
         var behaviorOwner = scrollBehaviorOwner
         if (behaviorOwner == null) {
-            behaviorOwner = requireScrollBehaviorOwner(layoutImpl.draggableHandler(orientation))
+            behaviorOwner = findScrollBehaviorOwner(layoutImpl.draggableHandler(orientation))
             scrollBehaviorOwner = behaviorOwner
         }
         return behaviorOwner
@@ -156,8 +156,8 @@
                 // transition between scenes. We can assume that the behavior is only needed if
                 // there is some remaining amount.
                 if (available != Offset.Zero) {
-                    requireScrollBehaviorOwner()
-                        .updateScrollBehaviors(
+                    findScrollBehaviorOwner()
+                        ?.updateScrollBehaviors(
                             topOrLeftBehavior = topOrLeftBehavior,
                             bottomOrRightBehavior = bottomOrRightBehavior,
                             isExternalOverscrollGesture = isExternalOverscrollGesture,
diff --git a/packages/SystemUI/compose/scene/src/com/android/compose/animation/scene/SwipeToScene.kt b/packages/SystemUI/compose/scene/src/com/android/compose/animation/scene/SwipeToScene.kt
index a3f2a43..fdf01cc 100644
--- a/packages/SystemUI/compose/scene/src/com/android/compose/animation/scene/SwipeToScene.kt
+++ b/packages/SystemUI/compose/scene/src/com/android/compose/animation/scene/SwipeToScene.kt
@@ -172,15 +172,12 @@
 }
 
 /** Find the [ScrollBehaviorOwner] for the current orientation. */
-internal fun DelegatableNode.requireScrollBehaviorOwner(
+internal fun DelegatableNode.findScrollBehaviorOwner(
     draggableHandler: DraggableHandlerImpl
-): ScrollBehaviorOwner {
-    val ancestorNode =
-        checkNotNull(findNearestAncestor(draggableHandler.nestedScrollKey)) {
-            "This should never happen! Couldn't find a ScrollBehaviorOwner. " +
-                "Are we inside an SceneTransitionLayout?"
-        }
-    return ancestorNode as ScrollBehaviorOwner
+): ScrollBehaviorOwner? {
+    // If there are no scenes in a particular orientation, the corresponding ScrollBehaviorOwnerNode
+    // is removed from the composition.
+    return findNearestAncestor(draggableHandler.nestedScrollKey) as? ScrollBehaviorOwner
 }
 
 internal fun interface ScrollBehaviorOwner {
diff --git a/packages/SystemUI/compose/scene/src/com/android/compose/nestedscroll/LargeTopAppBarNestedScrollConnection.kt b/packages/SystemUI/compose/scene/src/com/android/compose/nestedscroll/LargeTopAppBarNestedScrollConnection.kt
index ecf64b7..255da31 100644
--- a/packages/SystemUI/compose/scene/src/com/android/compose/nestedscroll/LargeTopAppBarNestedScrollConnection.kt
+++ b/packages/SystemUI/compose/scene/src/com/android/compose/nestedscroll/LargeTopAppBarNestedScrollConnection.kt
@@ -18,6 +18,7 @@
 
 import androidx.compose.foundation.gestures.Orientation
 import androidx.compose.ui.input.nestedscroll.NestedScrollConnection
+import androidx.compose.ui.input.nestedscroll.NestedScrollSource
 import androidx.compose.ui.util.fastCoerceAtLeast
 import androidx.compose.ui.util.fastCoerceAtMost
 
@@ -54,23 +55,38 @@
             offsetAvailable > 0 && height() < maxHeight()
         },
         canStartPostFling = { false },
-        canStopOnPreFling = { false },
-        onStart = { /* do nothing */ },
-        onScroll = { offsetAvailable, _ ->
-            val currentHeight = height()
-            val amountConsumed =
-                if (offsetAvailable > 0) {
-                    val amountLeft = maxHeight() - currentHeight
-                    offsetAvailable.fastCoerceAtMost(amountLeft)
-                } else {
-                    val amountLeft = minHeight() - currentHeight
-                    offsetAvailable.fastCoerceAtLeast(amountLeft)
-                }
-            onHeightChanged(currentHeight + amountConsumed)
-            amountConsumed
-        },
-        // Don't consume the velocity on pre/post fling
-        onStop = { 0f },
-        onCancel = { /* do nothing */ },
+        onStart = { LargeTopAppBarScrollController(height, maxHeight, minHeight, onHeightChanged) },
     )
 }
+
+private class LargeTopAppBarScrollController(
+    val height: () -> Float,
+    val maxHeight: () -> Float,
+    val minHeight: () -> Float,
+    val onHeightChanged: (Float) -> Unit,
+) : ScrollController {
+    override fun onScroll(deltaScroll: Float, source: NestedScrollSource): Float {
+        val currentHeight = height()
+        val amountConsumed =
+            if (deltaScroll > 0) {
+                val amountLeft = maxHeight() - currentHeight
+                deltaScroll.fastCoerceAtMost(amountLeft)
+            } else {
+                val amountLeft = minHeight() - currentHeight
+                deltaScroll.fastCoerceAtLeast(amountLeft)
+            }
+        onHeightChanged(currentHeight + amountConsumed)
+        return amountConsumed
+    }
+
+    override suspend fun onStop(initialVelocity: Float): Float {
+        // Don't consume the velocity on pre/post fling
+        return 0f
+    }
+
+    override fun onCancel() {
+        // do nothing
+    }
+
+    override fun canStopOnPreFling() = false
+}
diff --git a/packages/SystemUI/compose/scene/src/com/android/compose/nestedscroll/PriorityNestedScrollConnection.kt b/packages/SystemUI/compose/scene/src/com/android/compose/nestedscroll/PriorityNestedScrollConnection.kt
index 57d236b..ca44a5c 100644
--- a/packages/SystemUI/compose/scene/src/com/android/compose/nestedscroll/PriorityNestedScrollConnection.kt
+++ b/packages/SystemUI/compose/scene/src/com/android/compose/nestedscroll/PriorityNestedScrollConnection.kt
@@ -16,46 +16,106 @@
 
 package com.android.compose.nestedscroll
 
-import androidx.compose.animation.core.AnimationState
-import androidx.compose.animation.core.DecayAnimationSpec
-import androidx.compose.animation.core.animateDecay
 import androidx.compose.foundation.gestures.Orientation
 import androidx.compose.ui.geometry.Offset
 import androidx.compose.ui.input.nestedscroll.NestedScrollConnection
 import androidx.compose.ui.input.nestedscroll.NestedScrollSource
 import androidx.compose.ui.unit.Velocity
 import com.android.compose.ui.util.SpaceVectorConverter
-import kotlin.math.abs
 import kotlin.math.sign
-import kotlinx.coroutines.CompletableDeferred
 import kotlinx.coroutines.Deferred
 import kotlinx.coroutines.async
 import kotlinx.coroutines.coroutineScope
 
 /**
- * A [NestedScrollConnection] that intercepts scroll events in priority mode.
+ * The [ScrollController] provides control over the scroll gesture. It allows you to:
+ * - Scroll the content by a given pixel amount.
+ * - Cancel the current scroll operation.
+ * - Stop the scrolling with a given initial velocity.
  *
- * Priority mode allows this connection to take control over scroll events within a nested scroll
- * hierarchy. When in priority mode, this connection consumes scroll events before its children,
- * enabling custom scrolling behaviors like sticky headers.
+ * **Important Notes:**
+ * - [onCancel] is called only when [PriorityNestedScrollConnection.reset] is invoked or when
+ *   [canCancelScroll] returns `true` after a call to [onScroll]. It is never called after [onStop].
+ * - [onStop] can be interrupted by a new gesture. In such cases, you need to handle a potential
+ *   cancellation within your implementation of [onStop], although [onCancel] will not be called.
+ */
+interface ScrollController {
+    /**
+     * Scrolls the current content by [deltaScroll] pixels.
+     *
+     * @param deltaScroll The amount of pixels to scroll by.
+     * @param source The source of the scroll event.
+     * @return The amount of [deltaScroll] that was consumed.
+     */
+    fun onScroll(deltaScroll: Float, source: NestedScrollSource): Float
+
+    /**
+     * Checks if the current scroll operation can be canceled. This is typically called after
+     * [onScroll] to determine if the [ScrollController] has lost priority and should cancel the
+     * ongoing scroll operation.
+     *
+     * @param available The total amount of scroll available.
+     * @param consumed The amount of scroll consumed by [onScroll].
+     * @return `true` if the scroll can be canceled.
+     */
+    fun canCancelScroll(available: Float, consumed: Float): Boolean {
+        return consumed == 0f
+    }
+
+    /**
+     * Cancels the current scroll operation. This method is called when
+     * [PriorityNestedScrollConnection.reset] is invoked or when [canCancelScroll] returns `true`.
+     */
+    fun onCancel()
+
+    /**
+     * Checks if the scroll can be stopped during the [NestedScrollConnection.onPreFling] phase.
+     *
+     * @return `true` if the scroll can be stopped.
+     */
+    fun canStopOnPreFling(): Boolean
+
+    /**
+     * Stops the controller with the given [initialVelocity]. This typically starts a decay
+     * animation to smoothly bring the scrolling to a stop. This method can be interrupted by a new
+     * gesture, requiring you to handle potential cancellation within your implementation.
+     *
+     * @param initialVelocity The initial velocity of the scroll when stopping.
+     * @return The consumed [initialVelocity] when the animation completes.
+     */
+    suspend fun onStop(initialVelocity: Float): Float
+}
+
+/**
+ * A [NestedScrollConnection] that lets you implement custom scroll behaviors that take priority
+ * over the default nested scrolling logic.
+ *
+ * When started, this connection intercepts scroll events *before* they reach child composables.
+ * This "priority mode" is activated activated when either [canStartPreScroll], [canStartPostScroll]
+ * or [canStartPostFling] returns `true`.
+ *
+ * Once started, the [onStart] lambda provides a [ScrollController] to manage the scrolling. This
+ * controller allows you to directly manipulate the scroll state and define how scroll events are
+ * consumed.
+ *
+ * **Important Considerations:**
+ * - When started, scroll events are typically consumed in `onPreScroll`.
+ * - The provided [ScrollController] should handle potential cancellation of `onStop` due to new
+ *   gestures.
+ * - Use [reset] to release the current [ScrollController] and reset the connection to its initial
+ *   state.
  *
  * @param orientation The orientation of the scroll.
- * @param canStartPreScroll lambda that returns true if the connection can start consuming scroll
- *   events in pre-scroll mode.
- * @param canStartPostScroll lambda that returns true if the connection can start consuming scroll
- *   events in post-scroll mode.
- * @param canStartPostFling lambda that returns true if the connection can start consuming scroll
- *   events in post-fling mode.
- * @param canStopOnScroll lambda that returns true if the connection can stop consuming scroll
- *   events in scroll mode.
- * @param canStopOnPreFling lambda that returns true if the connection can stop consuming scroll
- *   events in pre-fling (i.e. as soon as the user lifts their fingers).
- * @param onStart lambda that is called when the connection starts consuming scroll events.
- * @param onScroll lambda that is called when the connection consumes a scroll event and returns the
- *   consumed amount.
- * @param onStop lambda that is called when the connection stops consuming scroll events and returns
- *   the consumed velocity.
- * @param onCancel lambda that is called when the connection is cancelled.
+ * @param canStartPreScroll A lambda that returns `true` if the connection should enter priority
+ *   mode during the pre-scroll phase. This is called before child connections have a chance to
+ *   consume the scroll.
+ * @param canStartPostScroll A lambda that returns `true` if the connection should enter priority
+ *   mode during the post-scroll phase. This is called after child connections have consumed the
+ *   scroll.
+ * @param canStartPostFling A lambda that returns `true` if the connection should enter priority
+ *   mode during the post-fling phase. This is called after a fling gesture has been initiated.
+ * @param onStart A lambda that is called when the connection enters priority mode. It should return
+ *   a [ScrollController] that will be used to control the scroll.
  * @sample LargeTopAppBarNestedScrollConnection
  * @sample com.android.compose.animation.scene.NestedScrollHandlerImpl.nestedScrollConnection
  */
@@ -66,169 +126,213 @@
     private val canStartPostScroll:
         (offsetAvailable: Float, offsetBeforeStart: Float, source: NestedScrollSource) -> Boolean,
     private val canStartPostFling: (velocityAvailable: Float) -> Boolean,
-    private val canStopOnScroll: (available: Float, consumed: Float) -> Boolean = { _, consumed ->
-        consumed == 0f
-    },
-    private val canStopOnPreFling: () -> Boolean,
-    private val onStart: (offsetAvailable: Float) -> Unit,
-    private val onScroll: (offsetAvailable: Float, source: NestedScrollSource) -> Float,
-    private val onStop: suspend (velocityAvailable: Float) -> Float,
-    private val onCancel: () -> Unit,
+    private val onStart: (firstScroll: Float) -> ScrollController,
 ) : NestedScrollConnection, SpaceVectorConverter by SpaceVectorConverter(orientation) {
 
-    /** In priority mode [onPreScroll] events are first consumed by the parent, via [onScroll]. */
-    private var isPriorityMode = false
+    /** The currently active [ScrollController], or `null` if not in priority mode. */
+    private var currentController: ScrollController? = null
 
+    /**
+     * A [Deferred] representing the ongoing `onStop` animation. Used to interrupt the animation if
+     * a new gesture occurs.
+     */
+    private var stoppingJob: Deferred<Float>? = null
+
+    /**
+     * Indicates whether the connection is currently in the process of stopping the scroll with the
+     * [ScrollController.onStop] animation.
+     */
+    private val isStopping
+        get() = stoppingJob?.isActive ?: false
+
+    /**
+     * Tracks the cumulative scroll offset that has been consumed by other composables before this
+     * connection enters priority mode. This is used to determine when the connection should take
+     * over scrolling based on the [canStartPreScroll] and [canStartPostScroll] conditions.
+     */
     private var offsetScrolledBeforePriorityMode = 0f
 
-    /** This job allows us to interrupt the onStop animation */
-    private var onStopJob: Deferred<Float> = CompletableDeferred(0f)
+    override fun onPreScroll(available: Offset, source: NestedScrollSource): Offset {
+        // If stopping, interrupt the animation and clear the controller.
+        if (isStopping) {
+            interruptStopping()
+        }
+
+        // If in priority mode, consume the scroll using the current controller.
+        if (currentController != null) {
+            return scroll(available.toFloat(), source)
+        }
+
+        // Check if pre-scroll condition is met, and start priority mode if necessary.
+        val availableFloat = available.toFloat()
+        if (canStartPreScroll(availableFloat, offsetScrolledBeforePriorityMode, source)) {
+            start(availableFloat)
+            return scroll(availableFloat, source)
+        }
+
+        // Track offset consumed before entering priority mode.
+        offsetScrolledBeforePriorityMode += availableFloat
+        return Offset.Zero
+    }
 
     override fun onPostScroll(
         consumed: Offset,
         available: Offset,
         source: NestedScrollSource,
     ): Offset {
+        // If in priority mode, scroll events are consumed only in pre-scroll phase.
+        if (currentController != null) return Offset.Zero
+
+        // Check if post-scroll condition is met, and start priority mode if necessary.
         val availableFloat = available.toFloat()
-        // The offset before the start takes into account the up and down movements, starting from
-        // the beginning or from the last fling gesture.
         val offsetBeforeStart = offsetScrolledBeforePriorityMode - availableFloat
-
-        if (isPriorityMode || !canStartPostScroll(availableFloat, offsetBeforeStart, source)) {
-            // The priority mode cannot start so we won't consume the available offset.
-            return Offset.Zero
+        if (canStartPostScroll(availableFloat, offsetBeforeStart, source)) {
+            start(availableFloat)
+            return scroll(availableFloat, source)
         }
 
-        return start(availableFloat, source).toOffset()
-    }
-
-    override fun onPreScroll(available: Offset, source: NestedScrollSource): Offset {
-        if (!isPriorityMode) {
-            val availableFloat = available.toFloat()
-            if (canStartPreScroll(availableFloat, offsetScrolledBeforePriorityMode, source)) {
-                return start(availableFloat, source).toOffset()
-            }
-            // We want to track the amount of offset consumed before entering priority mode
-            offsetScrolledBeforePriorityMode += availableFloat
-            return Offset.Zero
-        }
-
-        return scroll(available.toFloat(), source).toOffset()
+        // Do not consume the offset if priority mode is not activated.
+        return Offset.Zero
     }
 
     override suspend fun onPreFling(available: Velocity): Velocity {
-        if (!isPriorityMode) {
-            resetOffsetTracker()
-            return Velocity.Zero
+        val controller = currentController ?: return Velocity.Zero
+
+        // If in priority mode and can stop on pre-fling phase, stop the scroll.
+        if (controller.canStopOnPreFling()) {
+            return stop(velocity = available.toFloat())
         }
 
-        if (canStopOnPreFling()) {
-            // Step 3b: The finger is lifted, we can stop intercepting scroll events and use the
-            // velocity of the fling gesture.
-            return stop(velocityAvailable = available.toFloat()).toVelocity()
-        }
-
-        // We don't want to consume the velocity, we prefer to continue receiving scroll events.
+        // Do not consume the velocity if not stopping on pre-fling phase.
         return Velocity.Zero
     }
 
     override suspend fun onPostFling(consumed: Velocity, available: Velocity): Velocity {
         val availableFloat = available.toFloat()
-        if (isPriorityMode) {
-            return stop(velocityAvailable = availableFloat).toVelocity()
+        val controller = currentController
+
+        // If in priority mode, stop the scroll.
+        if (controller != null) {
+            return stop(velocity = availableFloat)
         }
 
-        if (!canStartPostFling(availableFloat)) {
-            return Velocity.Zero
-        }
-
-        // The offset passed to onPriorityStart() must be != 0f, so we create a small offset of 1px
-        // given the available velocity.
+        // Check if post-fling condition is met, and start priority mode if necessary.
         // TODO(b/291053278): Remove canStartPostFling() and instead make it possible to define the
         // overscroll behavior on the Scene level.
-        val smallOffset = availableFloat.sign
-        start(
-            availableOffset = smallOffset,
-            source = NestedScrollSource.SideEffect,
-            skipScroll = true,
-        )
+        if (canStartPostFling(availableFloat)) {
+            // The offset passed to onPriorityStart() must be != 0f, so we create a small offset of
+            // 1px given the available velocity.
+            val smallOffset = availableFloat.sign
+            start(availableOffset = smallOffset)
+            return stop(availableFloat)
+        }
 
-        // This is the last event of a scroll gesture.
-        return stop(availableFloat).toVelocity()
+        // Reset offset tracking after the fling gesture is finished.
+        resetOffsetTracker()
+        return Velocity.Zero
     }
 
     /**
-     * Method to call before destroying the object or to reset the initial state.
-     *
-     * TODO(b/303224944) This method should be removed.
+     * Resets the connection to its initial state. This cancels any ongoing scroll operation and
+     * clears the current [ScrollController].
      */
     fun reset() {
-        if (isPriorityMode) {
-            // Step 3c: To ensure that an onStop (or onCancel) is always called for every onStart.
+        if (currentController != null && !isStopping) {
             cancel()
         } else {
             resetOffsetTracker()
         }
     }
 
-    private fun start(
-        availableOffset: Float,
-        source: NestedScrollSource,
-        skipScroll: Boolean = false,
-    ): Float {
-        check(!isPriorityMode) {
-            "This should never happen, start() was called when isPriorityMode"
-        }
+    /**
+     * Starts priority mode by creating a new [ScrollController] using the [onStart] lambda.
+     *
+     * @param availableOffset The initial scroll offset available.
+     */
+    private fun start(availableOffset: Float) {
+        check(currentController == null) { "Another controller is active: $currentController" }
 
-        // Step 1: It's our turn! We start capturing scroll events when one of our children has an
-        // available offset following a scroll event.
-        isPriorityMode = true
+        resetOffsetTracker()
 
-        onStopJob.cancel()
-
-        // Note: onStop will be called if we cannot continue to scroll (step 3a), or the finger is
-        // lifted (step 3b), or this object has been destroyed (step 3c).
-        onStart(availableOffset)
-
-        return if (skipScroll) 0f else scroll(availableOffset, source)
+        currentController = onStart(availableOffset)
     }
 
-    private fun scroll(offsetAvailable: Float, source: NestedScrollSource): Float {
-        // Step 2: We have the priority and can consume the scroll events.
-        val consumedByScroll = onScroll(offsetAvailable, source)
+    /**
+     * Retrieves the current [ScrollController], ensuring that it is not null and that the
+     * [isStopping] state matches the expected value.
+     */
+    private fun requireController(isStopping: Boolean): ScrollController {
+        check(this.isStopping == isStopping) {
+            "isStopping is ${this.isStopping}, instead of $isStopping"
+        }
+        check(offsetScrolledBeforePriorityMode == 0f) {
+            "offset scrolled should be zero, but it was $offsetScrolledBeforePriorityMode"
+        }
+        return checkNotNull(currentController) { "The controller is $currentController" }
+    }
 
-        if (canStopOnScroll(offsetAvailable, consumedByScroll)) {
-            // Step 3a: We have lost priority and we no longer need to intercept scroll events.
+    /**
+     * Scrolls the content using the current [ScrollController].
+     *
+     * @param delta The amount of scroll to apply.
+     * @param source The source of the scroll event.
+     * @return The amount of scroll consumed.
+     */
+    private fun scroll(delta: Float, source: NestedScrollSource): Offset {
+        val controller = requireController(isStopping = false)
+        val consumedByScroll = controller.onScroll(delta, source)
+
+        if (controller.canCancelScroll(delta, consumedByScroll)) {
+            // We have lost priority and we no longer need to intercept scroll events.
             cancel()
-
-            // We've just reset offsetScrolledBeforePriorityMode to 0f
-            // We want to track the amount of offset consumed before entering priority mode
-            offsetScrolledBeforePriorityMode += offsetAvailable - consumedByScroll
+            offsetScrolledBeforePriorityMode = delta - consumedByScroll
         }
 
-        return consumedByScroll
+        return consumedByScroll.toOffset()
     }
 
-    /** Reset the tracking of consumed offsets before entering in priority mode. */
+    /** Cancels the current scroll operation and clears the current [ScrollController]. */
+    private fun cancel() {
+        requireController(isStopping = false).onCancel()
+        currentController = null
+    }
+
+    /**
+     * Stops the scroll with the given velocity using the current [ScrollController].
+     *
+     * @param velocity The velocity to stop with.
+     * @return The consumed velocity.
+     */
+    suspend fun stop(velocity: Float): Velocity {
+        val controller = requireController(isStopping = false)
+        return coroutineScope {
+            try {
+                async { controller.onStop(velocity) }
+                    // Allows others to interrupt the job.
+                    .also { stoppingJob = it }
+                    // Note: this can be cancelled by [interruptStopping]
+                    .await()
+                    .toVelocity()
+            } finally {
+                // If the job is interrupted, it might take a while to cancel. We need to make sure
+                // the current controller is still the initial one.
+                if (currentController == controller) {
+                    currentController = null
+                }
+            }
+        }
+    }
+
+    /** Interrupts the ongoing stop animation and clears the current [ScrollController]. */
+    private fun interruptStopping() {
+        requireController(isStopping = true)
+        // We are throwing a CancellationException in the [ScrollController.onStop] method.
+        stoppingJob?.cancel()
+        currentController = null
+    }
+
+    /** Resets the tracking of consumed offsets before entering priority mode. */
     private fun resetOffsetTracker() {
         offsetScrolledBeforePriorityMode = 0f
     }
-
-    private suspend fun stop(velocityAvailable: Float): Float {
-        check(isPriorityMode) { "This should never happen, stop() was called before start()" }
-        isPriorityMode = false
-        resetOffsetTracker()
-
-        return coroutineScope {
-            onStopJob = async { onStop(velocityAvailable) }
-            onStopJob.await()
-        }
-    }
-
-    private fun cancel() {
-        check(isPriorityMode) { "This should never happen, cancel() was called before start()" }
-        isPriorityMode = false
-        resetOffsetTracker()
-        onCancel()
-    }
 }
diff --git a/packages/SystemUI/compose/scene/tests/src/com/android/compose/animation/scene/NestedScrollToSceneTest.kt b/packages/SystemUI/compose/scene/tests/src/com/android/compose/animation/scene/NestedScrollToSceneTest.kt
index 5edb99e..37dae39 100644
--- a/packages/SystemUI/compose/scene/tests/src/com/android/compose/animation/scene/NestedScrollToSceneTest.kt
+++ b/packages/SystemUI/compose/scene/tests/src/com/android/compose/animation/scene/NestedScrollToSceneTest.kt
@@ -225,6 +225,40 @@
     }
 
     @Test
+    fun stlNotConsumeUnobservedGesture() {
+        val state =
+            rule.runOnUiThread {
+                MutableSceneTransitionLayoutState(SceneA, transitions = EmptyTestTransitions)
+            }
+
+        rule.setContent {
+            touchSlop = LocalViewConfiguration.current.touchSlop
+            SceneTransitionLayout(
+                state = state,
+                modifier = Modifier.size(layoutWidth, layoutHeight),
+            ) {
+                scene(SceneA) {
+                    Spacer(
+                        Modifier.verticalNestedScrollToScene()
+                            // This scrollable will not consume the gesture.
+                            .scrollable(rememberScrollableState { 0f }, Vertical)
+                            .fillMaxSize()
+                    )
+                }
+            }
+        }
+
+        rule.onRoot().performTouchInput {
+            down(Offset.Zero)
+            // There is no vertical scene.
+            moveBy(Offset(0f, layoutWidth.toPx()), delayMillis = 1_000)
+        }
+
+        rule.waitForIdle()
+        assertThat(state.transitionState).isIdle()
+    }
+
+    @Test
     fun customizeStlNestedScrollBehavior_multipleRequests() {
         var canScroll = true
         val state = setup2ScenesAndScrollTouchSlop {
diff --git a/packages/SystemUI/compose/scene/tests/src/com/android/compose/nestedscroll/PriorityNestedScrollConnectionTest.kt b/packages/SystemUI/compose/scene/tests/src/com/android/compose/nestedscroll/PriorityNestedScrollConnectionTest.kt
index 1a3b86b..0364cdc 100644
--- a/packages/SystemUI/compose/scene/tests/src/com/android/compose/nestedscroll/PriorityNestedScrollConnectionTest.kt
+++ b/packages/SystemUI/compose/scene/tests/src/com/android/compose/nestedscroll/PriorityNestedScrollConnectionTest.kt
@@ -20,6 +20,7 @@
 
 import androidx.compose.foundation.gestures.Orientation
 import androidx.compose.ui.geometry.Offset
+import androidx.compose.ui.input.nestedscroll.NestedScrollSource
 import androidx.compose.ui.input.nestedscroll.NestedScrollSource.Companion.UserInput
 import androidx.compose.ui.unit.Velocity
 import androidx.test.ext.junit.runners.AndroidJUnit4
@@ -48,17 +49,26 @@
             canStartPreScroll = { _, _, _ -> canStartPreScroll },
             canStartPostScroll = { _, _, _ -> canStartPostScroll },
             canStartPostFling = { canStartPostFling },
-            canStopOnPreFling = { canStopOnPreFling },
-            onStart = { isStarted = true },
-            onScroll = { offsetAvailable, _ ->
-                lastScroll = offsetAvailable
-                if (consumeScroll) offsetAvailable else 0f
+            onStart = { _ ->
+                isStarted = true
+                object : ScrollController {
+                    override fun onScroll(deltaScroll: Float, source: NestedScrollSource): Float {
+                        lastScroll = deltaScroll
+                        return if (consumeScroll) deltaScroll else 0f
+                    }
+
+                    override suspend fun onStop(initialVelocity: Float): Float {
+                        lastStop = initialVelocity
+                        return if (consumeStop) initialVelocity else 0f
+                    }
+
+                    override fun onCancel() {
+                        isCancelled = true
+                    }
+
+                    override fun canStopOnPreFling() = canStopOnPreFling
+                }
             },
-            onStop = {
-                lastStop = it
-                if (consumeStop) it else 0f
-            },
-            onCancel = { isCancelled = true },
         )
 
     @Test
diff --git a/packages/SystemUI/customization/src/com/android/systemui/shared/clocks/DefaultClockProvider.kt b/packages/SystemUI/customization/src/com/android/systemui/shared/clocks/DefaultClockProvider.kt
index 8e838f3..a89e6fb 100644
--- a/packages/SystemUI/customization/src/com/android/systemui/shared/clocks/DefaultClockProvider.kt
+++ b/packages/SystemUI/customization/src/com/android/systemui/shared/clocks/DefaultClockProvider.kt
@@ -19,12 +19,14 @@
 import com.android.systemui.customization.R
 import com.android.systemui.log.core.LogLevel
 import com.android.systemui.log.core.LogcatOnlyMessageBuffer
+import com.android.systemui.plugins.clocks.AxisType
 import com.android.systemui.plugins.clocks.ClockController
 import com.android.systemui.plugins.clocks.ClockId
 import com.android.systemui.plugins.clocks.ClockMessageBuffers
 import com.android.systemui.plugins.clocks.ClockMetadata
 import com.android.systemui.plugins.clocks.ClockPickerConfig
 import com.android.systemui.plugins.clocks.ClockProvider
+import com.android.systemui.plugins.clocks.ClockReactiveAxis
 import com.android.systemui.plugins.clocks.ClockSettings
 import com.android.systemui.shared.clocks.view.HorizontalAlignment
 import com.android.systemui.shared.clocks.view.VerticalAlignment
@@ -85,8 +87,21 @@
             // TODO(b/352049256): Update placeholder to actual resource
             resources.getDrawable(R.drawable.clock_default_thumbnail, null),
             isReactiveToTone = true,
-            isReactiveToTouch = isClockReactiveVariantsEnabled,
-            axes = listOf(), // TODO: Ater some picker definition
+            // TODO(b/364673969): Populate the rest of this
+            axes =
+                if (isClockReactiveVariantsEnabled)
+                    listOf(
+                        ClockReactiveAxis(
+                            key = "wdth",
+                            type = AxisType.Slider,
+                            maxValue = 1000f,
+                            minValue = 100f,
+                            currentValue = 400f,
+                            name = "Width",
+                            description = "Glyph Width",
+                        )
+                    )
+                else listOf(),
         )
     }
 
diff --git a/packages/SystemUI/log/Android.bp b/packages/SystemUI/log/Android.bp
index 2f1d354..afdcae48 100644
--- a/packages/SystemUI/log/Android.bp
+++ b/packages/SystemUI/log/Android.bp
@@ -22,19 +22,15 @@
     default_applicable_licenses: ["frameworks_base_packages_SystemUI_license"],
 }
 
-android_library {
+java_library {
     name: "SystemUILogLib",
-    use_resource_processor: true,
     srcs: [
         "src/**/*.java",
         "src/**/*.kt",
     ],
     static_libs: [
-        "androidx.core_core-ktx",
-        "androidx.annotation_annotation",
         "error_prone_annotations",
         "SystemUICommon",
     ],
-    manifest: "AndroidManifest.xml",
     kotlincflags: ["-Xjvm-default=all"],
 }
diff --git a/packages/SystemUI/log/AndroidManifest.xml b/packages/SystemUI/log/AndroidManifest.xml
deleted file mode 100644
index 4021e1a..0000000
--- a/packages/SystemUI/log/AndroidManifest.xml
+++ /dev/null
@@ -1,22 +0,0 @@
-<?xml version="1.0" encoding="utf-8"?>
-<!--
-     Copyright (C) 2023 The Android Open Source Project
-
-     Licensed under the Apache License, Version 2.0 (the "License");
-     you may not use this file except in compliance with the License.
-     You may obtain a copy of the License at
-
-          http://www.apache.org/licenses/LICENSE-2.0
-
-     Unless required by applicable law or agreed to in writing, software
-     distributed under the License is distributed on an "AS IS" BASIS,
-     WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
-     See the License for the specific language governing permissions and
-     limitations under the License.
--->
-
-<manifest xmlns:android="http://schemas.android.com/apk/res/android"
-    package="com.android.systemui.log">
-
-
-</manifest>
diff --git a/packages/SystemUI/multivalentTests/src/com/android/systemui/ambient/touch/BouncerFullscreenSwipeTouchHandlerTest.java b/packages/SystemUI/multivalentTests/src/com/android/systemui/ambient/touch/BouncerFullscreenSwipeTouchHandlerTest.java
index bd33e52..58c3fec 100644
--- a/packages/SystemUI/multivalentTests/src/com/android/systemui/ambient/touch/BouncerFullscreenSwipeTouchHandlerTest.java
+++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/ambient/touch/BouncerFullscreenSwipeTouchHandlerTest.java
@@ -20,9 +20,7 @@
 
 import static kotlinx.coroutines.flow.StateFlowKt.MutableStateFlow;
 
-import static org.mockito.ArgumentMatchers.any;
 import static org.mockito.ArgumentMatchers.anyFloat;
-import static org.mockito.Mockito.spy;
 import static org.mockito.Mockito.verify;
 import static org.mockito.Mockito.when;
 
@@ -31,12 +29,11 @@
 import android.graphics.Rect;
 import android.platform.test.annotations.DisableFlags;
 import android.platform.test.annotations.EnableFlags;
-import android.platform.test.flag.junit.FlagsParameterization;
-import android.view.GestureDetector;
 import android.view.GestureDetector.OnGestureListener;
 import android.view.MotionEvent;
 import android.view.VelocityTracker;
 
+import androidx.test.ext.junit.runners.AndroidJUnit4;
 import androidx.test.filters.SmallTest;
 
 import com.android.internal.logging.UiEventLogger;
@@ -45,12 +42,9 @@
 import com.android.systemui.ambient.touch.scrim.ScrimController;
 import com.android.systemui.ambient.touch.scrim.ScrimManager;
 import com.android.systemui.communal.ui.viewmodel.CommunalViewModel;
-import com.android.systemui.flags.SceneContainerFlagParameterizationKt;
 import com.android.systemui.keyguard.domain.interactor.KeyguardInteractor;
 import com.android.systemui.kosmos.KosmosJavaAdapter;
 import com.android.systemui.plugins.ActivityStarter;
-import com.android.systemui.scene.domain.interactor.SceneInteractor;
-import com.android.systemui.scene.ui.view.WindowRootView;
 import com.android.systemui.shared.system.InputChannelCompat;
 import com.android.systemui.statusbar.NotificationShadeWindowController;
 import com.android.systemui.statusbar.phone.CentralSurfaces;
@@ -64,14 +58,10 @@
 import org.mockito.Mockito;
 import org.mockito.MockitoAnnotations;
 
-import java.util.List;
 import java.util.Optional;
 
-import platform.test.runner.parameterized.ParameterizedAndroidJunit4;
-import platform.test.runner.parameterized.Parameters;
-
 @SmallTest
-@RunWith(ParameterizedAndroidJunit4.class)
+@RunWith(AndroidJUnit4.class)
 @EnableFlags(Flags.FLAG_HUBMODE_FULLSCREEN_VERTICAL_SWIPE_FIX)
 @DisableFlags(Flags.FLAG_COMMUNAL_BOUNCER_DO_NOT_MODIFY_PLUGIN_OPEN)
 public class BouncerFullscreenSwipeTouchHandlerTest extends SysuiTestCase {
@@ -124,11 +114,6 @@
     @Mock
     KeyguardInteractor mKeyguardInteractor;
 
-    @Mock
-    WindowRootView mWindowRootView;
-
-    private SceneInteractor mSceneInteractor;
-
     private static final float TOUCH_REGION = .3f;
     private static final float MIN_BOUNCER_HEIGHT = .05f;
 
@@ -139,21 +124,9 @@
             /* flags= */ 0
     );
 
-    @Parameters(name = "{0}")
-    public static List<FlagsParameterization> getParams() {
-        return SceneContainerFlagParameterizationKt.parameterizeSceneContainerFlag();
-    }
-
-    public BouncerFullscreenSwipeTouchHandlerTest(FlagsParameterization flags) {
-        super();
-        mSetFlagsRule.setFlagsParameterization(flags);
-    }
-
     @Before
     public void setup() {
         mKosmos = new KosmosJavaAdapter(this);
-        mSceneInteractor = spy(mKosmos.getSceneInteractor());
-
         MockitoAnnotations.initMocks(this);
         mTouchHandler = new BouncerSwipeTouchHandler(
                 mKosmos.getTestScope(),
@@ -169,9 +142,7 @@
                 MIN_BOUNCER_HEIGHT,
                 mUiEventLogger,
                 mActivityStarter,
-                mKeyguardInteractor,
-                mSceneInteractor,
-                Optional.of(() -> mWindowRootView));
+                mKeyguardInteractor);
 
         when(mScrimManager.getCurrentController()).thenReturn(mScrimController);
         when(mValueAnimatorCreator.create(anyFloat(), anyFloat())).thenReturn(mValueAnimator);
@@ -182,38 +153,6 @@
     }
 
     /**
-     * Makes sure that touches go to the scene container when the flag is on.
-     */
-    @Test
-    @EnableFlags(Flags.FLAG_SCENE_CONTAINER)
-    public void testSwipeUp_sendsTouchesToWindowRootView() {
-        mTouchHandler.onGlanceableTouchAvailable(true);
-        mTouchHandler.onSessionStart(mTouchSession);
-        ArgumentCaptor<GestureDetector.OnGestureListener> gestureListenerCaptor =
-                ArgumentCaptor.forClass(GestureDetector.OnGestureListener.class);
-        verify(mTouchSession).registerGestureListener(gestureListenerCaptor.capture());
-
-        final OnGestureListener gestureListener = gestureListenerCaptor.getValue();
-
-        final int screenHeight = 100;
-        final float distanceY = screenHeight * 0.42f;
-
-        final MotionEvent event1 = MotionEvent.obtain(0, 0, MotionEvent.ACTION_MOVE,
-                0, screenHeight, 0);
-        final MotionEvent event2 = MotionEvent.obtain(0, 0, MotionEvent.ACTION_MOVE,
-                0, screenHeight - distanceY, 0);
-
-        assertThat(gestureListener.onScroll(event1, event2, 0,
-                distanceY))
-                .isTrue();
-
-        // Ensure only called once
-        verify(mSceneInteractor).onRemoteUserInputStarted(any());
-        verify(mWindowRootView).dispatchTouchEvent(event1);
-        verify(mWindowRootView).dispatchTouchEvent(event2);
-    }
-
-    /**
      * Ensures expansion does not happen for full vertical swipes when touch is not available.
      */
     @Test
diff --git a/packages/SystemUI/multivalentTests/src/com/android/systemui/ambient/touch/BouncerSwipeTouchHandlerTest.java b/packages/SystemUI/multivalentTests/src/com/android/systemui/ambient/touch/BouncerSwipeTouchHandlerTest.java
index 494e0b4..9568167 100644
--- a/packages/SystemUI/multivalentTests/src/com/android/systemui/ambient/touch/BouncerSwipeTouchHandlerTest.java
+++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/ambient/touch/BouncerSwipeTouchHandlerTest.java
@@ -26,7 +26,6 @@
 import static org.mockito.ArgumentMatchers.isNull;
 import static org.mockito.Mockito.never;
 import static org.mockito.Mockito.reset;
-import static org.mockito.Mockito.spy;
 import static org.mockito.Mockito.verify;
 import static org.mockito.Mockito.verifyNoMoreInteractions;
 import static org.mockito.Mockito.when;
@@ -38,12 +37,12 @@
 import android.graphics.Region;
 import android.platform.test.annotations.DisableFlags;
 import android.platform.test.annotations.EnableFlags;
-import android.platform.test.flag.junit.FlagsParameterization;
 import android.view.GestureDetector;
 import android.view.GestureDetector.OnGestureListener;
 import android.view.MotionEvent;
 import android.view.VelocityTracker;
 
+import androidx.test.ext.junit.runners.AndroidJUnit4;
 import androidx.test.filters.SmallTest;
 
 import com.android.internal.logging.UiEventLogger;
@@ -53,12 +52,9 @@
 import com.android.systemui.ambient.touch.scrim.ScrimManager;
 import com.android.systemui.bouncer.shared.constants.KeyguardBouncerConstants;
 import com.android.systemui.communal.ui.viewmodel.CommunalViewModel;
-import com.android.systemui.flags.SceneContainerFlagParameterizationKt;
 import com.android.systemui.keyguard.domain.interactor.KeyguardInteractor;
 import com.android.systemui.kosmos.KosmosJavaAdapter;
 import com.android.systemui.plugins.ActivityStarter;
-import com.android.systemui.scene.domain.interactor.SceneInteractor;
-import com.android.systemui.scene.ui.view.WindowRootView;
 import com.android.systemui.shade.ShadeExpansionChangeEvent;
 import com.android.systemui.shared.system.InputChannelCompat;
 import com.android.systemui.statusbar.NotificationShadeWindowController;
@@ -74,14 +70,10 @@
 import org.mockito.Mockito;
 import org.mockito.MockitoAnnotations;
 
-import java.util.List;
 import java.util.Optional;
 
-import platform.test.runner.parameterized.ParameterizedAndroidJunit4;
-import platform.test.runner.parameterized.Parameters;
-
 @SmallTest
-@RunWith(ParameterizedAndroidJunit4.class)
+@RunWith(AndroidJUnit4.class)
 @DisableFlags(Flags.FLAG_HUBMODE_FULLSCREEN_VERTICAL_SWIPE_FIX)
 public class BouncerSwipeTouchHandlerTest extends SysuiTestCase {
     private KosmosJavaAdapter mKosmos;
@@ -130,9 +122,6 @@
     Region mRegion;
 
     @Mock
-    WindowRootView mWindowRootView;
-
-    @Mock
     CommunalViewModel mCommunalViewModel;
 
     @Mock
@@ -141,8 +130,6 @@
     @Captor
     ArgumentCaptor<Rect> mRectCaptor;
 
-    private SceneInteractor mSceneInteractor;
-
     private static final float TOUCH_REGION = .3f;
     private static final int SCREEN_WIDTH_PX = 1024;
     private static final int SCREEN_HEIGHT_PX = 100;
@@ -155,21 +142,9 @@
             /* flags= */ 0
     );
 
-    @Parameters(name = "{0}")
-    public static List<FlagsParameterization> getParams() {
-        return SceneContainerFlagParameterizationKt.parameterizeSceneContainerFlag();
-    }
-
-    public BouncerSwipeTouchHandlerTest(FlagsParameterization flags) {
-        super();
-        mSetFlagsRule.setFlagsParameterization(flags);
-    }
-
     @Before
     public void setup() {
         mKosmos = new KosmosJavaAdapter(this);
-        mSceneInteractor = spy(mKosmos.getSceneInteractor());
-
         MockitoAnnotations.initMocks(this);
         mTouchHandler = new BouncerSwipeTouchHandler(
                 mKosmos.getTestScope(),
@@ -185,10 +160,7 @@
                 MIN_BOUNCER_HEIGHT,
                 mUiEventLogger,
                 mActivityStarter,
-                mKeyguardInteractor,
-                mSceneInteractor,
-                Optional.of(() -> mWindowRootView)
-        );
+                mKeyguardInteractor);
 
         when(mScrimManager.getCurrentController()).thenReturn(mScrimController);
         when(mValueAnimatorCreator.create(anyFloat(), anyFloat())).thenReturn(mValueAnimator);
@@ -395,7 +367,6 @@
      * Makes sure the expansion amount is proportional to (1 - scroll).
      */
     @Test
-    @DisableFlags(Flags.FLAG_SCENE_CONTAINER)
     public void testSwipeUp_setsCorrectExpansionAmount() {
         mTouchHandler.onSessionStart(mTouchSession);
         ArgumentCaptor<GestureDetector.OnGestureListener> gestureListenerCaptor =
@@ -409,36 +380,6 @@
     }
 
     /**
-     * Makes sure that touches go to the scene container when the flag is on.
-     */
-    @Test
-    @EnableFlags(Flags.FLAG_SCENE_CONTAINER)
-    public void testSwipeUp_sendsTouchesToWindowRootView() {
-        mTouchHandler.onSessionStart(mTouchSession);
-        ArgumentCaptor<GestureDetector.OnGestureListener> gestureListenerCaptor =
-                ArgumentCaptor.forClass(GestureDetector.OnGestureListener.class);
-        verify(mTouchSession).registerGestureListener(gestureListenerCaptor.capture());
-
-        final OnGestureListener gestureListener = gestureListenerCaptor.getValue();
-
-        final float distanceY = SCREEN_HEIGHT_PX * 0.42f;
-
-        final MotionEvent event1 = MotionEvent.obtain(0, 0, MotionEvent.ACTION_MOVE,
-                0, SCREEN_HEIGHT_PX, 0);
-        final MotionEvent event2 = MotionEvent.obtain(0, 0, MotionEvent.ACTION_MOVE,
-                0, SCREEN_HEIGHT_PX - distanceY, 0);
-
-        assertThat(gestureListener.onScroll(event1, event2, 0,
-                distanceY))
-                .isTrue();
-
-        // Ensure only called once
-        verify(mSceneInteractor).onRemoteUserInputStarted(any());
-        verify(mWindowRootView).dispatchTouchEvent(event1);
-        verify(mWindowRootView).dispatchTouchEvent(event2);
-    }
-
-    /**
      * Verifies that swiping up when the lock pattern is not secure dismissed dream and consumes
      * the gesture.
      */
@@ -535,7 +476,6 @@
      * Tests that ending an upward swipe before the set threshold leads to bouncer collapsing down.
      */
     @Test
-    @DisableFlags(Flags.FLAG_SCENE_CONTAINER)
     public void testSwipeUpPositionBelowThreshold_collapsesBouncer() {
         final float swipeUpPercentage = .3f;
         final float expansion = 1 - swipeUpPercentage;
@@ -559,7 +499,6 @@
      * Tests that ending an upward swipe above the set threshold will continue the expansion.
      */
     @Test
-    @DisableFlags(Flags.FLAG_SCENE_CONTAINER)
     public void testSwipeUpPositionAboveThreshold_expandsBouncer() {
         final float swipeUpPercentage = .7f;
         final float expansion = 1 - swipeUpPercentage;
@@ -589,7 +528,6 @@
      * Tests that swiping up with a speed above the set threshold will continue the expansion.
      */
     @Test
-    @DisableFlags(Flags.FLAG_SCENE_CONTAINER)
     public void testSwipeUpVelocityAboveMin_expandsBouncer() {
         when(mFlingAnimationUtils.getMinVelocityPxPerSecond()).thenReturn((float) 0);
 
diff --git a/packages/SystemUI/multivalentTests/src/com/android/systemui/ambient/touch/ShadeTouchHandlerTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/ambient/touch/ShadeTouchHandlerTest.kt
index ad636cf..38ea4497 100644
--- a/packages/SystemUI/multivalentTests/src/com/android/systemui/ambient/touch/ShadeTouchHandlerTest.kt
+++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/ambient/touch/ShadeTouchHandlerTest.kt
@@ -18,9 +18,9 @@
 import android.app.DreamManager
 import android.platform.test.annotations.DisableFlags
 import android.platform.test.annotations.EnableFlags
-import android.platform.test.flag.junit.FlagsParameterization
 import android.view.GestureDetector
 import android.view.MotionEvent
+import androidx.test.ext.junit.runners.AndroidJUnit4
 import androidx.test.filters.SmallTest
 import com.android.systemui.Flags
 import com.android.systemui.SysuiTestCase
@@ -28,20 +28,14 @@
 import com.android.systemui.communal.domain.interactor.communalSettingsInteractor
 import com.android.systemui.communal.ui.viewmodel.CommunalViewModel
 import com.android.systemui.flags.Flags.COMMUNAL_SERVICE_ENABLED
-import com.android.systemui.flags.andSceneContainer
 import com.android.systemui.flags.fakeFeatureFlagsClassic
 import com.android.systemui.kosmos.testScope
-import com.android.systemui.scene.data.repository.sceneContainerRepository
-import com.android.systemui.scene.domain.interactor.sceneInteractor
-import com.android.systemui.scene.ui.view.WindowRootView
 import com.android.systemui.shade.ShadeViewController
 import com.android.systemui.shared.system.InputChannelCompat
 import com.android.systemui.statusbar.phone.CentralSurfaces
 import com.android.systemui.testKosmos
 import com.google.common.truth.Truth
-import com.google.common.truth.Truth.assertThat
 import java.util.Optional
-import javax.inject.Provider
 import org.junit.Before
 import org.junit.Test
 import org.junit.runner.RunWith
@@ -53,29 +47,22 @@
 import org.mockito.kotlin.times
 import org.mockito.kotlin.verify
 import org.mockito.kotlin.whenever
-import platform.test.runner.parameterized.ParameterizedAndroidJunit4
-import platform.test.runner.parameterized.Parameters
 
 @SmallTest
-@RunWith(ParameterizedAndroidJunit4::class)
-class ShadeTouchHandlerTest(flags: FlagsParameterization) : SysuiTestCase() {
+@RunWith(AndroidJUnit4::class)
+class ShadeTouchHandlerTest : SysuiTestCase() {
     private var kosmos = testKosmos()
     private var mCentralSurfaces = mock<CentralSurfaces>()
     private var mShadeViewController = mock<ShadeViewController>()
     private var mDreamManager = mock<DreamManager>()
     private var mTouchSession = mock<TouchSession>()
     private var communalViewModel = mock<CommunalViewModel>()
-    private var windowRootView = mock<WindowRootView>()
 
     private lateinit var mTouchHandler: ShadeTouchHandler
 
     private var mGestureListenerCaptor = argumentCaptor<GestureDetector.OnGestureListener>()
     private var mInputListenerCaptor = argumentCaptor<InputChannelCompat.InputEventListener>()
 
-    init {
-        mSetFlagsRule.setFlagsParameterization(flags)
-    }
-
     @Before
     fun setup() {
         mTouchHandler =
@@ -86,9 +73,7 @@
                 mDreamManager,
                 communalViewModel,
                 kosmos.communalSettingsInteractor,
-                kosmos.sceneInteractor,
-                Optional.of(Provider<WindowRootView> { windowRootView }),
-                TOUCH_HEIGHT,
+                TOUCH_HEIGHT
             )
     }
 
@@ -112,7 +97,7 @@
 
     // Verifies that a swipe down forwards captured touches to central surfaces for handling.
     @Test
-    @DisableFlags(Flags.FLAG_HUBMODE_FULLSCREEN_VERTICAL_SWIPE_FIX, Flags.FLAG_SCENE_CONTAINER)
+    @DisableFlags(Flags.FLAG_HUBMODE_FULLSCREEN_VERTICAL_SWIPE_FIX)
     @EnableFlags(Flags.FLAG_COMMUNAL_HUB)
     fun testSwipeDown_communalEnabled_sentToCentralSurfaces() {
         kosmos.fakeFeatureFlagsClassic.set(COMMUNAL_SERVICE_ENABLED, true)
@@ -125,11 +110,7 @@
 
     // Verifies that a swipe down forwards captured touches to the shade view for handling.
     @Test
-    @DisableFlags(
-        Flags.FLAG_COMMUNAL_HUB,
-        Flags.FLAG_HUBMODE_FULLSCREEN_VERTICAL_SWIPE_FIX,
-        Flags.FLAG_SCENE_CONTAINER,
-    )
+    @DisableFlags(Flags.FLAG_COMMUNAL_HUB, Flags.FLAG_HUBMODE_FULLSCREEN_VERTICAL_SWIPE_FIX)
     fun testSwipeDown_communalDisabled_sentToShadeView() {
         swipe(Direction.DOWN)
 
@@ -140,7 +121,7 @@
     // Verifies that a swipe down while dreaming forwards captured touches to the shade view for
     // handling.
     @Test
-    @DisableFlags(Flags.FLAG_HUBMODE_FULLSCREEN_VERTICAL_SWIPE_FIX, Flags.FLAG_SCENE_CONTAINER)
+    @DisableFlags(Flags.FLAG_HUBMODE_FULLSCREEN_VERTICAL_SWIPE_FIX)
     fun testSwipeDown_dreaming_sentToShadeView() {
         whenever(mDreamManager.isDreaming).thenReturn(true)
         swipe(Direction.DOWN)
@@ -149,34 +130,9 @@
         verify(mShadeViewController, times(2)).handleExternalTouch(any())
     }
 
-    // Verifies that a swipe down forwards captured touches to the window root view for handling.
-    @Test
-    @EnableFlags(Flags.FLAG_COMMUNAL_HUB, Flags.FLAG_SCENE_CONTAINER)
-    fun testSwipeDown_sceneContainerEnabled_sentToWindowRootView() {
-        swipe(Direction.DOWN)
-
-        // Both motion events are sent for central surfaces to process.
-        assertThat(kosmos.sceneContainerRepository.isRemoteUserInputOngoing.value).isTrue()
-        verify(windowRootView, times(2)).dispatchTouchEvent(any())
-    }
-
-    // Verifies that a swipe down while dreaming forwards captured touches to the window root view
-    // for handling.
-    @Test
-    @EnableFlags(Flags.FLAG_SCENE_CONTAINER)
-    @DisableFlags(Flags.FLAG_HUBMODE_FULLSCREEN_VERTICAL_SWIPE_FIX)
-    fun testSwipeDown_dreaming_sentToWindowRootView() {
-        whenever(mDreamManager.isDreaming).thenReturn(true)
-        swipe(Direction.DOWN)
-
-        // Both motion events are sent for the shade view to process.
-        assertThat(kosmos.sceneContainerRepository.isRemoteUserInputOngoing.value).isTrue()
-        verify(windowRootView, times(2)).dispatchTouchEvent(any())
-    }
-
     // Verifies that a swipe up is not forwarded to central surfaces.
     @Test
-    @DisableFlags(Flags.FLAG_HUBMODE_FULLSCREEN_VERTICAL_SWIPE_FIX, Flags.FLAG_SCENE_CONTAINER)
+    @DisableFlags(Flags.FLAG_HUBMODE_FULLSCREEN_VERTICAL_SWIPE_FIX)
     @EnableFlags(Flags.FLAG_COMMUNAL_HUB)
     fun testSwipeUp_communalEnabled_touchesNotSent() {
         kosmos.fakeFeatureFlagsClassic.set(COMMUNAL_SERVICE_ENABLED, true)
@@ -190,11 +146,7 @@
 
     // Verifies that a swipe up is not forwarded to the shade view.
     @Test
-    @DisableFlags(
-        Flags.FLAG_COMMUNAL_HUB,
-        Flags.FLAG_HUBMODE_FULLSCREEN_VERTICAL_SWIPE_FIX,
-        Flags.FLAG_SCENE_CONTAINER,
-    )
+    @DisableFlags(Flags.FLAG_COMMUNAL_HUB, Flags.FLAG_HUBMODE_FULLSCREEN_VERTICAL_SWIPE_FIX)
     fun testSwipeUp_communalDisabled_touchesNotSent() {
         swipe(Direction.UP)
 
@@ -203,17 +155,6 @@
         verify(mShadeViewController, never()).handleExternalTouch(any())
     }
 
-    // Verifies that a swipe up is not forwarded to the window root view.
-    @Test
-    @EnableFlags(Flags.FLAG_COMMUNAL_HUB, Flags.FLAG_SCENE_CONTAINER)
-    fun testSwipeUp_sceneContainerEnabled_touchesNotSent() {
-        swipe(Direction.UP)
-
-        // Motion events are not sent for window root view to process as the swipe is going in the
-        // wrong direction.
-        verify(windowRootView, never()).dispatchTouchEvent(any())
-    }
-
     @Test
     @DisableFlags(Flags.FLAG_HUBMODE_FULLSCREEN_VERTICAL_SWIPE_FIX)
     fun testCancelMotionEvent_popsTouchSession() {
@@ -302,16 +243,10 @@
 
     private enum class Direction {
         DOWN,
-        UP,
+        UP
     }
 
     companion object {
         private const val TOUCH_HEIGHT = 20
-
-        @JvmStatic
-        @Parameters(name = "{0}")
-        fun getParams(): List<FlagsParameterization> {
-            return FlagsParameterization.allCombinationsOf().andSceneContainer()
-        }
     }
 }
diff --git a/packages/SystemUI/multivalentTests/src/com/android/systemui/keyguard/domain/interactor/KeyguardQuickAffordanceInteractorParameterizedTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/keyguard/domain/interactor/KeyguardQuickAffordanceInteractorParameterizedTest.kt
index eef4c3d..83d2617 100644
--- a/packages/SystemUI/multivalentTests/src/com/android/systemui/keyguard/domain/interactor/KeyguardQuickAffordanceInteractorParameterizedTest.kt
+++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/keyguard/domain/interactor/KeyguardQuickAffordanceInteractorParameterizedTest.kt
@@ -20,7 +20,6 @@
 import android.app.admin.DevicePolicyManager
 import android.content.Intent
 import android.os.UserHandle
-import androidx.test.filters.FlakyTest
 import androidx.test.filters.SmallTest
 import com.android.internal.widget.LockPatternUtils
 import com.android.keyguard.logging.KeyguardQuickAffordancesLogger
@@ -79,10 +78,6 @@
 import platform.test.runner.parameterized.Parameters
 
 @OptIn(ExperimentalCoroutinesApi::class)
-@FlakyTest(
-    bugId = 292574995,
-    detail = "on certain architectures all permutations with startActivity=true is causing failures"
-)
 @SmallTest
 @RunWith(ParameterizedAndroidJunit4::class)
 @DisableSceneContainer
@@ -93,11 +88,7 @@
         private val DRAWABLE =
             mock<Icon> {
                 whenever(this.contentDescription)
-                    .thenReturn(
-                        ContentDescription.Resource(
-                            res = CONTENT_DESCRIPTION_RESOURCE_ID,
-                        )
-                    )
+                    .thenReturn(ContentDescription.Resource(res = CONTENT_DESCRIPTION_RESOURCE_ID))
             }
         private const val CONTENT_DESCRIPTION_RESOURCE_ID = 1337
 
@@ -273,13 +264,7 @@
                 context = context,
                 userFileManager =
                     mock<UserFileManager>().apply {
-                        whenever(
-                                getSharedPreferences(
-                                    anyString(),
-                                    anyInt(),
-                                    anyInt(),
-                                )
-                            )
+                        whenever(getSharedPreferences(anyString(), anyInt(), anyInt()))
                             .thenReturn(FakeSharedPreferences())
                     },
                 userTracker = userTracker,
@@ -316,9 +301,7 @@
         underTest =
             KeyguardQuickAffordanceInteractor(
                 keyguardInteractor =
-                    KeyguardInteractorFactory.create(
-                            featureFlags = featureFlags,
-                        )
+                    KeyguardInteractorFactory.create(featureFlags = featureFlags)
                         .keyguardInteractor,
                 shadeInteractor = kosmos.shadeInteractor,
                 lockPatternUtils = lockPatternUtils,
@@ -350,9 +333,7 @@
 
             homeControls.setState(
                 lockScreenState =
-                    KeyguardQuickAffordanceConfig.LockScreenState.Visible(
-                        icon = DRAWABLE,
-                    )
+                    KeyguardQuickAffordanceConfig.LockScreenState.Visible(icon = DRAWABLE)
             )
             homeControls.onTriggeredResult =
                 if (startActivity) {
diff --git a/packages/SystemUI/multivalentTests/src/com/android/systemui/shade/data/repository/ShadeRepositoryImplTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/shade/data/repository/ShadeRepositoryImplTest.kt
index 7d4918a..b5043ce 100644
--- a/packages/SystemUI/multivalentTests/src/com/android/systemui/shade/data/repository/ShadeRepositoryImplTest.kt
+++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/shade/data/repository/ShadeRepositoryImplTest.kt
@@ -39,7 +39,7 @@
 
     @Before
     fun setUp() {
-        underTest = ShadeRepositoryImpl(getContext())
+        underTest = ShadeRepositoryImpl()
     }
 
     @Test
diff --git a/packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/RemoteInputNotificationRebuilderTest.java b/packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/RemoteInputNotificationRebuilderTest.java
index 58943ea..c8ae358 100644
--- a/packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/RemoteInputNotificationRebuilderTest.java
+++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/RemoteInputNotificationRebuilderTest.java
@@ -16,6 +16,8 @@
 
 package com.android.systemui.statusbar;
 
+import static android.app.Notification.EXTRA_REMOTE_INPUT_HISTORY_ITEMS;
+
 import static junit.framework.Assert.assertEquals;
 import static junit.framework.Assert.assertFalse;
 import static junit.framework.Assert.assertTrue;
@@ -23,6 +25,7 @@
 import static org.mockito.Mockito.mock;
 
 import android.app.Notification;
+import android.app.NotificationChannelGroup;
 import android.app.RemoteInputHistoryItem;
 import android.net.Uri;
 import android.os.UserHandle;
@@ -71,6 +74,25 @@
     }
 
     @Test
+    public void testRebuildWithRemoteInput_invalidData() {
+        Uri uri = mock(Uri.class);
+        String mimeType = "image/jpeg";
+        String text = "image inserted";
+        mEntry.getSbn().getNotification().extras.putParcelableArray(
+                EXTRA_REMOTE_INPUT_HISTORY_ITEMS,
+                new NotificationChannelGroup[]{});
+        StatusBarNotification newSbn =
+                mRebuilder.rebuildWithRemoteInputInserted(
+                        mEntry, text, false, mimeType, uri);
+        RemoteInputHistoryItem[] messages = (RemoteInputHistoryItem[]) newSbn.getNotification()
+                .extras.getParcelableArray(EXTRA_REMOTE_INPUT_HISTORY_ITEMS);
+        assertEquals(1, messages.length);
+        assertEquals(text, messages[0].getText());
+        assertEquals(mimeType, messages[0].getMimeType());
+        assertEquals(uri, messages[0].getUri());
+    }
+
+    @Test
     public void testRebuildWithRemoteInput_noExistingInput_image() {
         Uri uri = mock(Uri.class);
         String mimeType = "image/jpeg";
@@ -79,7 +101,7 @@
                 mRebuilder.rebuildWithRemoteInputInserted(
                         mEntry, text, false, mimeType, uri);
         RemoteInputHistoryItem[] messages = (RemoteInputHistoryItem[]) newSbn.getNotification()
-                .extras.getParcelableArray(Notification.EXTRA_REMOTE_INPUT_HISTORY_ITEMS);
+                .extras.getParcelableArray(EXTRA_REMOTE_INPUT_HISTORY_ITEMS);
         assertEquals(1, messages.length);
         assertEquals(text, messages[0].getText());
         assertEquals(mimeType, messages[0].getMimeType());
@@ -92,7 +114,7 @@
                 mRebuilder.rebuildWithRemoteInputInserted(
                         mEntry, "A Reply", false, null, null);
         RemoteInputHistoryItem[] messages = (RemoteInputHistoryItem[]) newSbn.getNotification()
-                .extras.getParcelableArray(Notification.EXTRA_REMOTE_INPUT_HISTORY_ITEMS);
+                .extras.getParcelableArray(EXTRA_REMOTE_INPUT_HISTORY_ITEMS);
         assertEquals(1, messages.length);
         assertEquals("A Reply", messages[0].getText());
         assertFalse(newSbn.getNotification().extras
@@ -107,7 +129,7 @@
                 mRebuilder.rebuildWithRemoteInputInserted(
                         mEntry, "A Reply", true, null, null);
         RemoteInputHistoryItem[] messages = (RemoteInputHistoryItem[]) newSbn.getNotification()
-                .extras.getParcelableArray(Notification.EXTRA_REMOTE_INPUT_HISTORY_ITEMS);
+                .extras.getParcelableArray(EXTRA_REMOTE_INPUT_HISTORY_ITEMS);
         assertEquals(1, messages.length);
         assertEquals("A Reply", messages[0].getText());
         assertTrue(newSbn.getNotification().extras
@@ -130,7 +152,7 @@
         newSbn = mRebuilder.rebuildWithRemoteInputInserted(
                 entry, "Reply 2", true, null, null);
         RemoteInputHistoryItem[] messages = (RemoteInputHistoryItem[]) newSbn.getNotification()
-                .extras.getParcelableArray(Notification.EXTRA_REMOTE_INPUT_HISTORY_ITEMS);
+                .extras.getParcelableArray(EXTRA_REMOTE_INPUT_HISTORY_ITEMS);
         assertEquals(2, messages.length);
         assertEquals("Reply 2", messages[0].getText());
         assertEquals("A Reply", messages[1].getText());
@@ -153,7 +175,7 @@
         newSbn = mRebuilder.rebuildWithRemoteInputInserted(
                 entry, "Reply 2", true, null, null);
         RemoteInputHistoryItem[] messages = (RemoteInputHistoryItem[]) newSbn.getNotification()
-                .extras.getParcelableArray(Notification.EXTRA_REMOTE_INPUT_HISTORY_ITEMS);
+                .extras.getParcelableArray(EXTRA_REMOTE_INPUT_HISTORY_ITEMS);
         assertEquals(2, messages.length);
         assertEquals("Reply 2", messages[0].getText());
         assertEquals(text, messages[1].getText());
diff --git a/packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/chips/notification/ui/viewmodel/NotifChipsViewModelTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/chips/notification/ui/viewmodel/NotifChipsViewModelTest.kt
index eb5d931..2872900 100644
--- a/packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/chips/notification/ui/viewmodel/NotifChipsViewModelTest.kt
+++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/chips/notification/ui/viewmodel/NotifChipsViewModelTest.kt
@@ -19,11 +19,12 @@
 import android.platform.test.annotations.EnableFlags
 import androidx.test.ext.junit.runners.AndroidJUnit4
 import androidx.test.filters.SmallTest
+import com.android.systemui.Flags.FLAG_STATUS_BAR_RON_CHIPS
 import com.android.systemui.SysuiTestCase
 import com.android.systemui.coroutines.collectLastValue
 import com.android.systemui.kosmos.testScope
 import com.android.systemui.statusbar.StatusBarIconView
-import com.android.systemui.statusbar.chips.notification.shared.StatusBarNotifChips
+import com.android.systemui.statusbar.chips.ron.ui.viewmodel.notifChipsViewModel
 import com.android.systemui.statusbar.chips.ui.model.OngoingActivityChipModel
 import com.android.systemui.statusbar.notification.data.model.activeNotificationModel
 import com.android.systemui.statusbar.notification.data.repository.ActiveNotificationsStore
@@ -41,7 +42,7 @@
 @SmallTest
 @RunWith(AndroidJUnit4::class)
 @OptIn(ExperimentalCoroutinesApi::class)
-@EnableFlags(StatusBarNotifChips.FLAG_NAME)
+@EnableFlags(FLAG_STATUS_BAR_RON_CHIPS)
 class NotifChipsViewModelTest : SysuiTestCase() {
     private val kosmos = testKosmos()
     private val testScope = kosmos.testScope
diff --git a/packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/chips/notification/demo/ui/viewmodel/DemoNotifChipViewModelTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/chips/ron/demo/ui/viewmodel/DemoRonChipViewModelTest.kt
similarity index 74%
rename from packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/chips/notification/demo/ui/viewmodel/DemoNotifChipViewModelTest.kt
rename to packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/chips/ron/demo/ui/viewmodel/DemoRonChipViewModelTest.kt
index 69a7627..118dea6 100644
--- a/packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/chips/notification/demo/ui/viewmodel/DemoNotifChipViewModelTest.kt
+++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/chips/ron/demo/ui/viewmodel/DemoRonChipViewModelTest.kt
@@ -14,17 +14,17 @@
  * limitations under the License.
  */
 
-package com.android.systemui.statusbar.chips.notification.demo.ui.viewmodel
+package com.android.systemui.statusbar.chips.ron.demo.ui.viewmodel
 
 import android.content.packageManager
 import android.graphics.drawable.BitmapDrawable
 import android.platform.test.annotations.DisableFlags
 import android.platform.test.annotations.EnableFlags
 import androidx.test.filters.SmallTest
+import com.android.systemui.Flags.FLAG_STATUS_BAR_RON_CHIPS
 import com.android.systemui.SysuiTestCase
 import com.android.systemui.coroutines.collectLastValue
 import com.android.systemui.kosmos.testScope
-import com.android.systemui.statusbar.chips.notification.shared.StatusBarNotifChips
 import com.android.systemui.statusbar.chips.ui.model.ColorsModel
 import com.android.systemui.statusbar.chips.ui.model.OngoingActivityChipModel
 import com.android.systemui.statusbar.commandline.CommandRegistry
@@ -40,13 +40,13 @@
 import org.mockito.kotlin.whenever
 
 @SmallTest
-class DemoNotifChipViewModelTest : SysuiTestCase() {
+class DemoRonChipViewModelTest : SysuiTestCase() {
     private val kosmos = testKosmos()
     private val testScope = kosmos.testScope
     private val commandRegistry = kosmos.commandRegistry
     private val pw = PrintWriter(StringWriter())
 
-    private val underTest = kosmos.demoNotifChipViewModel
+    private val underTest = kosmos.demoRonChipViewModel
 
     @Before
     fun setUp() {
@@ -56,61 +56,61 @@
     }
 
     @Test
-    @DisableFlags(StatusBarNotifChips.FLAG_NAME)
+    @DisableFlags(FLAG_STATUS_BAR_RON_CHIPS)
     fun chip_flagOff_hidden() =
         testScope.runTest {
             val latest by collectLastValue(underTest.chip)
 
-            addDemoNotifChip()
+            addDemoRonChip()
 
             assertThat(latest).isInstanceOf(OngoingActivityChipModel.Hidden::class.java)
         }
 
     @Test
-    @EnableFlags(StatusBarNotifChips.FLAG_NAME)
+    @EnableFlags(FLAG_STATUS_BAR_RON_CHIPS)
     fun chip_noPackage_hidden() =
         testScope.runTest {
             val latest by collectLastValue(underTest.chip)
 
-            commandRegistry.onShellCommand(pw, arrayOf("demo-notif"))
+            commandRegistry.onShellCommand(pw, arrayOf("demo-ron"))
 
             assertThat(latest).isInstanceOf(OngoingActivityChipModel.Hidden::class.java)
         }
 
     @Test
-    @EnableFlags(StatusBarNotifChips.FLAG_NAME)
+    @EnableFlags(FLAG_STATUS_BAR_RON_CHIPS)
     fun chip_hasPackage_shown() =
         testScope.runTest {
             val latest by collectLastValue(underTest.chip)
 
-            commandRegistry.onShellCommand(pw, arrayOf("demo-notif", "-p", "com.android.systemui"))
+            commandRegistry.onShellCommand(pw, arrayOf("demo-ron", "-p", "com.android.systemui"))
 
             assertThat(latest).isInstanceOf(OngoingActivityChipModel.Shown::class.java)
         }
 
     @Test
-    @EnableFlags(StatusBarNotifChips.FLAG_NAME)
+    @EnableFlags(FLAG_STATUS_BAR_RON_CHIPS)
     fun chip_hasText_shownWithText() =
         testScope.runTest {
             val latest by collectLastValue(underTest.chip)
 
             commandRegistry.onShellCommand(
                 pw,
-                arrayOf("demo-notif", "-p", "com.android.systemui", "-t", "test"),
+                arrayOf("demo-ron", "-p", "com.android.systemui", "-t", "test")
             )
 
             assertThat(latest).isInstanceOf(OngoingActivityChipModel.Shown.Text::class.java)
         }
 
     @Test
-    @EnableFlags(StatusBarNotifChips.FLAG_NAME)
+    @EnableFlags(FLAG_STATUS_BAR_RON_CHIPS)
     fun chip_supportsColor() =
         testScope.runTest {
             val latest by collectLastValue(underTest.chip)
 
             commandRegistry.onShellCommand(
                 pw,
-                arrayOf("demo-notif", "-p", "com.android.systemui", "-c", "#434343"),
+                arrayOf("demo-ron", "-p", "com.android.systemui", "-c", "#434343")
             )
 
             assertThat(latest).isInstanceOf(OngoingActivityChipModel.Shown::class.java)
@@ -119,28 +119,28 @@
         }
 
     @Test
-    @EnableFlags(StatusBarNotifChips.FLAG_NAME)
+    @EnableFlags(FLAG_STATUS_BAR_RON_CHIPS)
     fun chip_hasHideArg_hidden() =
         testScope.runTest {
             val latest by collectLastValue(underTest.chip)
 
             // First, show a chip
-            addDemoNotifChip()
+            addDemoRonChip()
             assertThat(latest).isInstanceOf(OngoingActivityChipModel.Shown::class.java)
 
             // Then, hide the chip
-            commandRegistry.onShellCommand(pw, arrayOf("demo-notif", "--hide"))
+            commandRegistry.onShellCommand(pw, arrayOf("demo-ron", "--hide"))
 
             assertThat(latest).isInstanceOf(OngoingActivityChipModel.Hidden::class.java)
         }
 
-    private fun addDemoNotifChip() {
-        addDemoNotifChip(commandRegistry, pw)
+    private fun addDemoRonChip() {
+        Companion.addDemoRonChip(commandRegistry, pw)
     }
 
     companion object {
-        fun addDemoNotifChip(commandRegistry: CommandRegistry, pw: PrintWriter) {
-            commandRegistry.onShellCommand(pw, arrayOf("demo-notif", "-p", "com.android.systemui"))
+        fun addDemoRonChip(commandRegistry: CommandRegistry, pw: PrintWriter) {
+            commandRegistry.onShellCommand(pw, arrayOf("demo-ron", "-p", "com.android.systemui"))
         }
     }
 }
diff --git a/packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/chips/ui/viewmodel/OngoingActivityChipsViewModelTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/chips/ui/viewmodel/OngoingActivityChipsViewModelTest.kt
index e96def6..26ce7b9 100644
--- a/packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/chips/ui/viewmodel/OngoingActivityChipsViewModelTest.kt
+++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/chips/ui/viewmodel/OngoingActivityChipsViewModelTest.kt
@@ -25,6 +25,7 @@
 import android.view.View
 import androidx.test.ext.junit.runners.AndroidJUnit4
 import androidx.test.filters.SmallTest
+import com.android.systemui.Flags.FLAG_STATUS_BAR_RON_CHIPS
 import com.android.systemui.SysuiTestCase
 import com.android.systemui.common.shared.model.Icon
 import com.android.systemui.coroutines.collectLastValue
@@ -39,8 +40,7 @@
 import com.android.systemui.screenrecord.data.repository.screenRecordRepository
 import com.android.systemui.statusbar.chips.mediaprojection.domain.interactor.MediaProjectionChipInteractorTest.Companion.NORMAL_PACKAGE
 import com.android.systemui.statusbar.chips.mediaprojection.domain.interactor.MediaProjectionChipInteractorTest.Companion.setUpPackageManagerForMediaProjection
-import com.android.systemui.statusbar.chips.notification.demo.ui.viewmodel.demoNotifChipViewModel
-import com.android.systemui.statusbar.chips.notification.shared.StatusBarNotifChips
+import com.android.systemui.statusbar.chips.ron.demo.ui.viewmodel.demoRonChipViewModel
 import com.android.systemui.statusbar.chips.ui.model.OngoingActivityChipModel
 import com.android.systemui.statusbar.chips.ui.view.ChipBackgroundContainer
 import com.android.systemui.statusbar.phone.SystemUIDialog
@@ -66,11 +66,13 @@
 import org.mockito.kotlin.verify
 import org.mockito.kotlin.whenever
 
-/** Tests for [OngoingActivityChipsViewModel] when the [StatusBarNotifChips] flag is disabled. */
+/**
+ * Tests for [OngoingActivityChipsViewModel] when the [FLAG_STATUS_BAR_RON_CHIPS] flag is disabled.
+ */
 @SmallTest
 @RunWith(AndroidJUnit4::class)
 @OptIn(ExperimentalCoroutinesApi::class)
-@DisableFlags(StatusBarNotifChips.FLAG_NAME)
+@DisableFlags(FLAG_STATUS_BAR_RON_CHIPS)
 class OngoingActivityChipsViewModelTest : SysuiTestCase() {
     private val kosmos = Kosmos().also { it.testCase = this }
     private val testScope = kosmos.testScope
@@ -97,11 +99,11 @@
     @Before
     fun setUp() {
         setUpPackageManagerForMediaProjection(kosmos)
-        kosmos.demoNotifChipViewModel.start()
+        kosmos.demoRonChipViewModel.start()
         val icon =
             BitmapDrawable(
                 context.resources,
-                Bitmap.createBitmap(/* width= */ 100, /* height= */ 100, Bitmap.Config.ARGB_8888),
+                Bitmap.createBitmap(/* width= */ 100, /* height= */ 100, Bitmap.Config.ARGB_8888)
             )
         whenever(kosmos.packageManager.getApplicationIcon(any<String>())).thenReturn(icon)
     }
@@ -323,7 +325,7 @@
             latest: OngoingActivityChipModel?,
             chipView: View,
             dialog: SystemUIDialog,
-            kosmos: Kosmos,
+            kosmos: Kosmos
         ): DialogInterface.OnClickListener {
             // Capture the action that would get invoked when the user clicks "Stop" on the dialog
             lateinit var dialogStopAction: DialogInterface.OnClickListener
diff --git a/packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/chips/ui/viewmodel/OngoingActivityChipsWithNotifsViewModelTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/chips/ui/viewmodel/OngoingActivityChipsWithRonsViewModelTest.kt
similarity index 93%
rename from packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/chips/ui/viewmodel/OngoingActivityChipsWithNotifsViewModelTest.kt
rename to packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/chips/ui/viewmodel/OngoingActivityChipsWithRonsViewModelTest.kt
index b12d7c5..c5b857f 100644
--- a/packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/chips/ui/viewmodel/OngoingActivityChipsWithNotifsViewModelTest.kt
+++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/chips/ui/viewmodel/OngoingActivityChipsWithRonsViewModelTest.kt
@@ -24,6 +24,7 @@
 import android.view.View
 import androidx.test.ext.junit.runners.AndroidJUnit4
 import androidx.test.filters.SmallTest
+import com.android.systemui.Flags.FLAG_STATUS_BAR_RON_CHIPS
 import com.android.systemui.SysuiTestCase
 import com.android.systemui.coroutines.collectLastValue
 import com.android.systemui.kosmos.testScope
@@ -36,10 +37,9 @@
 import com.android.systemui.statusbar.StatusBarIconView
 import com.android.systemui.statusbar.chips.mediaprojection.domain.interactor.MediaProjectionChipInteractorTest.Companion.NORMAL_PACKAGE
 import com.android.systemui.statusbar.chips.mediaprojection.domain.interactor.MediaProjectionChipInteractorTest.Companion.setUpPackageManagerForMediaProjection
-import com.android.systemui.statusbar.chips.notification.demo.ui.viewmodel.DemoNotifChipViewModelTest.Companion.addDemoNotifChip
-import com.android.systemui.statusbar.chips.notification.demo.ui.viewmodel.demoNotifChipViewModel
-import com.android.systemui.statusbar.chips.notification.shared.StatusBarNotifChips
 import com.android.systemui.statusbar.chips.notification.ui.viewmodel.NotifChipsViewModelTest.Companion.assertIsNotifChip
+import com.android.systemui.statusbar.chips.ron.demo.ui.viewmodel.DemoRonChipViewModelTest.Companion.addDemoRonChip
+import com.android.systemui.statusbar.chips.ron.demo.ui.viewmodel.demoRonChipViewModel
 import com.android.systemui.statusbar.chips.ui.model.MultipleOngoingActivityChipsModel
 import com.android.systemui.statusbar.chips.ui.model.OngoingActivityChipModel
 import com.android.systemui.statusbar.chips.ui.view.ChipBackgroundContainer
@@ -73,12 +73,14 @@
 import org.mockito.kotlin.mock
 import org.mockito.kotlin.whenever
 
-/** Tests for [OngoingActivityChipsViewModel] when the [StatusBarNotifChips] flag is enabled. */
+/**
+ * Tests for [OngoingActivityChipsViewModel] when the [FLAG_STATUS_BAR_RON_CHIPS] flag is enabled.
+ */
 @SmallTest
 @RunWith(AndroidJUnit4::class)
 @OptIn(ExperimentalCoroutinesApi::class)
-@EnableFlags(StatusBarNotifChips.FLAG_NAME)
-class OngoingActivityChipsWithNotifsViewModelTest : SysuiTestCase() {
+@EnableFlags(FLAG_STATUS_BAR_RON_CHIPS)
+class OngoingActivityChipsWithRonsViewModelTest : SysuiTestCase() {
     private val kosmos = testKosmos()
     private val testScope = kosmos.testScope
     private val systemClock = kosmos.fakeSystemClock
@@ -108,7 +110,7 @@
     @Before
     fun setUp() {
         setUpPackageManagerForMediaProjection(kosmos)
-        kosmos.demoNotifChipViewModel.start()
+        kosmos.demoRonChipViewModel.start()
         val icon =
             BitmapDrawable(
                 context.resources,
@@ -117,7 +119,7 @@
         whenever(kosmos.packageManager.getApplicationIcon(any<String>())).thenReturn(icon)
     }
 
-    // Even though the `primaryChip` flow isn't used when the notifs flag is on, still test that the
+    // Even though the `primaryChip` flow isn't used when the RONs flag is on, still test that the
     // flow has the right behavior to verify that we don't break any existing functionality.
 
     @Test
@@ -254,13 +256,13 @@
         testScope.runTest {
             screenRecordState.value = ScreenRecordModel.Recording
             callRepo.setOngoingCallState(inCallModel(startTimeMs = 34))
-            addDemoNotifChip(commandRegistry, pw)
+            addDemoRonChip(commandRegistry, pw)
 
             val latest by collectLastValue(underTest.chips)
 
             assertIsScreenRecordChip(latest!!.primary)
             assertIsCallChip(latest!!.secondary)
-            // Demo notif chip is dropped
+            // Demo RON chip is dropped
         }
 
     @Test
@@ -387,7 +389,7 @@
     fun primaryChip_higherPriorityChipAdded_lowerPriorityChipReplaced() =
         testScope.runTest {
             // Start with just the lowest priority chip shown
-            addDemoNotifChip(commandRegistry, pw)
+            addDemoRonChip(commandRegistry, pw)
             // And everything else hidden
             callRepo.setOngoingCallState(OngoingCallModel.NoCall)
             mediaProjectionState.value = MediaProjectionState.NotProjecting
@@ -395,7 +397,7 @@
 
             val latest by collectLastValue(underTest.primaryChip)
 
-            assertIsDemoNotifChip(latest)
+            assertIsDemoRonChip(latest)
 
             // WHEN the higher priority call chip is added
             callRepo.setOngoingCallState(inCallModel(startTimeMs = 34))
@@ -429,7 +431,7 @@
             mediaProjectionState.value =
                 MediaProjectionState.Projecting.EntireScreen(NORMAL_PACKAGE)
             callRepo.setOngoingCallState(inCallModel(startTimeMs = 34))
-            addDemoNotifChip(commandRegistry, pw)
+            addDemoRonChip(commandRegistry, pw)
 
             val latest by collectLastValue(underTest.primaryChip)
 
@@ -451,15 +453,15 @@
             // WHEN the higher priority call is removed
             callRepo.setOngoingCallState(OngoingCallModel.NoCall)
 
-            // THEN the lower priority demo notif is used
-            assertIsDemoNotifChip(latest)
+            // THEN the lower priority demo RON is used
+            assertIsDemoRonChip(latest)
         }
 
     @Test
     fun chips_movesChipsAroundAccordingToPriority() =
         testScope.runTest {
             // Start with just the lowest priority chip shown
-            addDemoNotifChip(commandRegistry, pw)
+            addDemoRonChip(commandRegistry, pw)
             // And everything else hidden
             callRepo.setOngoingCallState(OngoingCallModel.NoCall)
             mediaProjectionState.value = MediaProjectionState.NotProjecting
@@ -467,16 +469,16 @@
 
             val latest by collectLastValue(underTest.chips)
 
-            assertIsDemoNotifChip(latest!!.primary)
+            assertIsDemoRonChip(latest!!.primary)
             assertThat(latest!!.secondary).isInstanceOf(OngoingActivityChipModel.Hidden::class.java)
 
             // WHEN the higher priority call chip is added
             callRepo.setOngoingCallState(inCallModel(startTimeMs = 34))
 
-            // THEN the higher priority call chip is used as primary and demo notif is demoted to
+            // THEN the higher priority call chip is used as primary and demo ron is demoted to
             // secondary
             assertIsCallChip(latest!!.primary)
-            assertIsDemoNotifChip(latest!!.secondary)
+            assertIsDemoRonChip(latest!!.secondary)
 
             // WHEN the higher priority media projection chip is added
             mediaProjectionState.value =
@@ -487,7 +489,7 @@
                 )
 
             // THEN the higher priority media projection chip is used as primary and call is demoted
-            // to secondary (and demo notif is dropped altogether)
+            // to secondary (and demo RON is dropped altogether)
             assertIsShareToAppChip(latest!!.primary)
             assertIsCallChip(latest!!.secondary)
 
@@ -501,15 +503,15 @@
             screenRecordState.value = ScreenRecordModel.DoingNothing
             callRepo.setOngoingCallState(OngoingCallModel.NoCall)
 
-            // THEN media projection and demo notif remain
+            // THEN media projection and demo RON remain
             assertIsShareToAppChip(latest!!.primary)
-            assertIsDemoNotifChip(latest!!.secondary)
+            assertIsDemoRonChip(latest!!.secondary)
 
             // WHEN media projection is dropped
             mediaProjectionState.value = MediaProjectionState.NotProjecting
 
-            // THEN demo notif is promoted to primary
-            assertIsDemoNotifChip(latest!!.primary)
+            // THEN demo RON is promoted to primary
+            assertIsDemoRonChip(latest!!.primary)
             assertThat(latest!!.secondary).isInstanceOf(OngoingActivityChipModel.Hidden::class.java)
         }
 
@@ -622,7 +624,7 @@
             assertThat(latest).isEqualTo(OngoingActivityChipModel.Hidden(shouldAnimate = false))
         }
 
-    private fun assertIsDemoNotifChip(latest: OngoingActivityChipModel?) {
+    private fun assertIsDemoRonChip(latest: OngoingActivityChipModel?) {
         assertThat(latest).isInstanceOf(OngoingActivityChipModel.Shown::class.java)
         assertThat((latest as OngoingActivityChipModel.Shown).icon)
             .isInstanceOf(OngoingActivityChipModel.ChipIcon.FullColorAppIcon::class.java)
diff --git a/packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/notification/stack/ui/viewmodel/SharedNotificationContainerViewModelTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/notification/stack/ui/viewmodel/SharedNotificationContainerViewModelTest.kt
index 25670cb..d5a7c89 100644
--- a/packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/notification/stack/ui/viewmodel/SharedNotificationContainerViewModelTest.kt
+++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/notification/stack/ui/viewmodel/SharedNotificationContainerViewModelTest.kt
@@ -64,7 +64,6 @@
 import com.android.systemui.scene.data.repository.Transition
 import com.android.systemui.scene.data.repository.setTransition
 import com.android.systemui.scene.shared.model.Scenes
-import com.android.systemui.shade.data.repository.fakeShadeRepository
 import com.android.systemui.shade.mockLargeScreenHeaderHelper
 import com.android.systemui.shade.shadeTestUtil
 import com.android.systemui.statusbar.notification.stack.domain.interactor.sharedNotificationContainerInteractor
@@ -116,36 +115,18 @@
         kosmos.aodBurnInViewModel = aodBurnInViewModel
     }
 
-    val testScope = kosmos.testScope
-    val configurationRepository
-        get() = kosmos.fakeConfigurationRepository
-
-    val keyguardRepository
-        get() = kosmos.fakeKeyguardRepository
-
-    val keyguardInteractor
-        get() = kosmos.keyguardInteractor
-
-    val keyguardRootViewModel
-        get() = kosmos.keyguardRootViewModel
-
-    val keyguardTransitionRepository
-        get() = kosmos.fakeKeyguardTransitionRepository
-
-    val shadeTestUtil
-        get() = kosmos.shadeTestUtil
-
-    val sharedNotificationContainerInteractor
-        get() = kosmos.sharedNotificationContainerInteractor
-
-    val largeScreenHeaderHelper
-        get() = kosmos.mockLargeScreenHeaderHelper
-
-    val communalSceneRepository
-        get() = kosmos.communalSceneRepository
-
-    val shadeRepository
-        get() = kosmos.fakeShadeRepository
+    private val testScope = kosmos.testScope
+    private val configurationRepository by lazy { kosmos.fakeConfigurationRepository }
+    private val keyguardRepository by lazy { kosmos.fakeKeyguardRepository }
+    private val keyguardInteractor by lazy { kosmos.keyguardInteractor }
+    private val keyguardRootViewModel by lazy { kosmos.keyguardRootViewModel }
+    private val keyguardTransitionRepository by lazy { kosmos.fakeKeyguardTransitionRepository }
+    private val shadeTestUtil by lazy { kosmos.shadeTestUtil }
+    private val sharedNotificationContainerInteractor by lazy {
+        kosmos.sharedNotificationContainerInteractor
+    }
+    private val largeScreenHeaderHelper by lazy { kosmos.mockLargeScreenHeaderHelper }
+    private val communalSceneRepository by lazy { kosmos.communalSceneRepository }
 
     lateinit var underTest: SharedNotificationContainerViewModel
 
@@ -637,6 +618,45 @@
 
     @Test
     @DisableSceneContainer
+    fun boundsStableWhenGoingToAlternateBouncer() =
+        testScope.runTest {
+            val bounds by collectLastValue(underTest.bounds)
+
+            // Start on lockscreen
+            showLockscreen()
+
+            keyguardInteractor.setNotificationContainerBounds(
+                NotificationContainerBounds(top = 1f, bottom = 2f)
+            )
+
+            assertThat(bounds).isEqualTo(NotificationContainerBounds(top = 1f, bottom = 2f))
+
+            // Begin transition to AOD
+            keyguardTransitionRepository.sendTransitionStep(
+                TransitionStep(LOCKSCREEN, ALTERNATE_BOUNCER, 0f, TransitionState.STARTED)
+            )
+            runCurrent()
+            keyguardTransitionRepository.sendTransitionStep(
+                TransitionStep(LOCKSCREEN, ALTERNATE_BOUNCER, 0f, TransitionState.RUNNING)
+            )
+            runCurrent()
+
+            // This is the last step before FINISHED is sent, which could trigger a change in bounds
+            keyguardTransitionRepository.sendTransitionStep(
+                TransitionStep(LOCKSCREEN, ALTERNATE_BOUNCER, 1f, TransitionState.RUNNING)
+            )
+            runCurrent()
+            assertThat(bounds).isEqualTo(NotificationContainerBounds(top = 1f, bottom = 2f))
+
+            keyguardTransitionRepository.sendTransitionStep(
+                TransitionStep(LOCKSCREEN, ALTERNATE_BOUNCER, 1f, TransitionState.FINISHED)
+            )
+            runCurrent()
+            assertThat(bounds).isEqualTo(NotificationContainerBounds(top = 1f, bottom = 2f))
+        }
+
+    @Test
+    @DisableSceneContainer
     fun boundsDoNotChangeWhileLockscreenToAodTransitionIsActive() =
         testScope.runTest {
             val bounds by collectLastValue(underTest.bounds)
@@ -1229,6 +1249,75 @@
             assertThat(alpha).isEqualTo(1f)
         }
 
+    @Test
+    @DisableSceneContainer
+    fun notificationAbsoluteBottom() =
+        testScope.runTest {
+            var notificationCount = 2
+            val calculateSpace = { _: Float, _: Boolean -> notificationCount }
+            val shelfHeight = 10F
+            val heightForNotification = 20F
+            val calculateHeight = { count: Int -> count * heightForNotification + shelfHeight }
+            val stackAbsoluteBottom by
+                collectLastValue(
+                    underTest.getNotificationStackAbsoluteBottom(
+                        calculateSpace,
+                        calculateHeight,
+                        shelfHeight,
+                    )
+                )
+            advanceTimeBy(50L)
+            showLockscreen()
+
+            shadeTestUtil.setSplitShade(false)
+            keyguardInteractor.setNotificationContainerBounds(
+                NotificationContainerBounds(top = 100F, bottom = 300F)
+            )
+            configurationRepository.onAnyConfigurationChange()
+
+            assertThat(stackAbsoluteBottom).isEqualTo(150F)
+
+            // Also updates when directly requested (as it would from NotificationStackScrollLayout)
+            notificationCount = 3
+            sharedNotificationContainerInteractor.notificationStackChanged()
+            advanceTimeBy(50L)
+            assertThat(stackAbsoluteBottom).isEqualTo(170F)
+        }
+
+    @Test
+    @DisableSceneContainer
+    fun notificationAbsoluteBottom_maxNotificationIsZero_noShelfHeight() =
+        testScope.runTest {
+            var notificationCount = 2
+            val calculateSpace = { _: Float, _: Boolean -> notificationCount }
+            val shelfHeight = 10F
+            val heightForNotification = 20F
+            val calculateHeight = { count: Int -> count * heightForNotification + shelfHeight }
+            val stackAbsoluteBottom by
+                collectLastValue(
+                    underTest.getNotificationStackAbsoluteBottom(
+                        calculateSpace,
+                        calculateHeight,
+                        shelfHeight,
+                    )
+                )
+            advanceTimeBy(50L)
+            showLockscreen()
+
+            shadeTestUtil.setSplitShade(false)
+            keyguardInteractor.setNotificationContainerBounds(
+                NotificationContainerBounds(top = 100F, bottom = 300F)
+            )
+            configurationRepository.onAnyConfigurationChange()
+
+            assertThat(stackAbsoluteBottom).isEqualTo(150F)
+
+            notificationCount = 0
+            sharedNotificationContainerInteractor.notificationStackChanged()
+            advanceTimeBy(50L)
+            assertThat(stackAbsoluteBottom).isEqualTo(100F)
+        }
+
     private suspend fun TestScope.showLockscreen() {
         shadeTestUtil.setQsExpansion(0f)
         shadeTestUtil.setLockscreenShadeExpansion(0f)
diff --git a/packages/SystemUI/multivalentTests/src/com/android/systemui/volume/dialog/sliders/domain/interactor/VolumeDialogSliderInteractorTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/volume/dialog/sliders/domain/interactor/VolumeDialogSliderInteractorTest.kt
new file mode 100644
index 0000000..bfafdab
--- /dev/null
+++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/volume/dialog/sliders/domain/interactor/VolumeDialogSliderInteractorTest.kt
@@ -0,0 +1,90 @@
+/*
+ * 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.volume.dialog.sliders.domain.interactor
+
+import android.testing.TestableLooper
+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.kosmos.testScope
+import com.android.systemui.plugins.VolumeDialogController
+import com.android.systemui.plugins.fakeVolumeDialogController
+import com.android.systemui.testKosmos
+import com.android.systemui.volume.dialog.sliders.domain.model.volumeDialogSliderType
+import com.google.common.truth.Truth.assertThat
+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
+
+@OptIn(ExperimentalCoroutinesApi::class)
+@SmallTest
+@RunWith(AndroidJUnit4::class)
+@TestableLooper.RunWithLooper
+class VolumeDialogSliderInteractorTest : SysuiTestCase() {
+
+    private val kosmos = testKosmos()
+
+    private lateinit var underTest: VolumeDialogSliderInteractor
+
+    @Before
+    fun setUp() {
+        underTest = kosmos.volumeDialogSliderInteractor
+    }
+
+    @Test
+    fun settingStreamVolume_setsActiveStream() =
+        with(kosmos) {
+            testScope.runTest {
+                runCurrent()
+                // initialize the stream model
+                fakeVolumeDialogController.setStreamVolume(volumeDialogSliderType.audioStream, 0)
+
+                val sliderModel by collectLastValue(underTest.slider)
+                underTest.setStreamVolume(1)
+                runCurrent()
+
+                assertThat(sliderModel!!.isActive).isTrue()
+            }
+        }
+
+    @Test
+    fun streamVolumeIs_minMaxAreEnforced() =
+        with(kosmos) {
+            testScope.runTest {
+                runCurrent()
+                fakeVolumeDialogController.updateState {
+                    states.put(
+                        volumeDialogSliderType.audioStream,
+                        VolumeDialogController.StreamState().apply {
+                            levelMin = 0
+                            level = 2
+                            levelMax = 1
+                        },
+                    )
+                }
+
+                val sliderModel by collectLastValue(underTest.slider)
+                runCurrent()
+
+                assertThat(sliderModel!!.level).isEqualTo(1)
+            }
+        }
+}
diff --git a/packages/SystemUI/multivalentTests/src/com/android/systemui/wallpapers/data/repository/FakeWallpaperRepository.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/wallpapers/data/repository/FakeWallpaperRepository.kt
index 59676ce..111c232 100644
--- a/packages/SystemUI/multivalentTests/src/com/android/systemui/wallpapers/data/repository/FakeWallpaperRepository.kt
+++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/wallpapers/data/repository/FakeWallpaperRepository.kt
@@ -25,9 +25,4 @@
     override val wallpaperInfo = MutableStateFlow<WallpaperInfo?>(null)
     override val wallpaperSupportsAmbientMode = MutableStateFlow(false)
     override var rootView: View? = null
-    private val _notificationStackAbsoluteBottom = MutableStateFlow(0F)
-
-    override fun setNotificationStackAbsoluteBottom(bottom: Float) {
-        _notificationStackAbsoluteBottom.value = bottom
-    }
 }
diff --git a/packages/SystemUI/plugin/src/com/android/systemui/plugins/clocks/ClockProviderPlugin.kt b/packages/SystemUI/plugin/src/com/android/systemui/plugins/clocks/ClockProviderPlugin.kt
index f975c4f..e264264 100644
--- a/packages/SystemUI/plugin/src/com/android/systemui/plugins/clocks/ClockProviderPlugin.kt
+++ b/packages/SystemUI/plugin/src/com/android/systemui/plugins/clocks/ClockProviderPlugin.kt
@@ -182,7 +182,7 @@
 interface ClockEvents {
     @get:ProtectedReturn("return false;")
     /** Set to enable or disable swipe interaction */
-    var isReactiveTouchInteractionEnabled: Boolean
+    var isReactiveTouchInteractionEnabled: Boolean // TODO(b/364664388): Remove/Rename
 
     /** Call whenever timezone changes */
     fun onTimeZoneChanged(timeZone: TimeZone)
@@ -322,9 +322,6 @@
     /** True if the clock will react to tone changes in the seed color */
     val isReactiveToTone: Boolean = true,
 
-    /** True if the clock is capable of changing style in reaction to touches */
-    val isReactiveToTouch: Boolean = false,
-
     /** Font axes that can be modified on this clock */
     val axes: List<ClockReactiveAxis> = listOf(),
 )
diff --git a/packages/SystemUI/plugin_core/AndroidManifest.xml b/packages/SystemUI/plugin_core/AndroidManifest.xml
deleted file mode 100644
index df835fd..0000000
--- a/packages/SystemUI/plugin_core/AndroidManifest.xml
+++ /dev/null
@@ -1,24 +0,0 @@
-<?xml version="1.0" encoding="utf-8"?>
-<!--
-     Copyright (C) 2018 The Android Open Source Project
-
-     Licensed under the Apache License, Version 2.0 (the "License");
-     you may not use this file except in compliance with the License.
-     You may obtain a copy of the License at
-
-          http://www.apache.org/licenses/LICENSE-2.0
-
-     Unless required by applicable law or agreed to in writing, software
-     distributed under the License is distributed on an "AS IS" BASIS,
-     WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
-     See the License for the specific language governing permissions and
-     limitations under the License.
--->
-
-<manifest xmlns:android="http://schemas.android.com/apk/res/android"
-    package="com.android.systemui.plugin_core">
-
-    <uses-sdk
-        android:minSdkVersion="28" />
-
-</manifest>
diff --git a/packages/SystemUI/res/drawable/notif_footer_btn_history.xml b/packages/SystemUI/res/drawable/notif_footer_btn_history.xml
new file mode 100644
index 0000000..0460a72
--- /dev/null
+++ b/packages/SystemUI/res/drawable/notif_footer_btn_history.xml
@@ -0,0 +1,10 @@
+<vector xmlns:android="http://schemas.android.com/apk/res/android"
+    android:width="20dp"
+    android:height="20dp"
+    android:viewportWidth="960"
+    android:viewportHeight="960"
+    android:tint="?attr/colorControlNormal">
+  <path
+      android:fillColor="@android:color/white"
+      android:pathData="M480,840Q342,840 239.5,748.5Q137,657 122,520L204,520Q218,624 296.5,692Q375,760 480,760Q597,760 678.5,678.5Q760,597 760,480Q760,363 678.5,281.5Q597,200 480,200Q411,200 351,232Q291,264 250,320L360,320L360,400L120,400L120,160L200,160L200,254Q251,190 324.5,155Q398,120 480,120Q555,120 620.5,148.5Q686,177 734.5,225.5Q783,274 811.5,339.5Q840,405 840,480Q840,555 811.5,620.5Q783,686 734.5,734.5Q686,783 620.5,811.5Q555,840 480,840ZM592,648L440,496L440,280L520,280L520,464L648,592L592,648Z"/>
+</vector>
diff --git a/packages/SystemUI/res/drawable/notif_footer_btn_settings.xml b/packages/SystemUI/res/drawable/notif_footer_btn_settings.xml
new file mode 100644
index 0000000..800060d
--- /dev/null
+++ b/packages/SystemUI/res/drawable/notif_footer_btn_settings.xml
@@ -0,0 +1,10 @@
+<vector xmlns:android="http://schemas.android.com/apk/res/android"
+    android:width="20dp"
+    android:height="20dp"
+    android:viewportWidth="960"
+    android:viewportHeight="960"
+    android:tint="?attr/colorControlNormal">
+  <path
+      android:fillColor="@android:color/white"
+      android:pathData="M370,880L354,752Q341,747 329.5,740Q318,733 307,725L188,775L78,585L181,507Q180,500 180,493.5Q180,487 180,480Q180,473 180,466.5Q180,460 181,453L78,375L188,185L307,235Q318,227 330,220Q342,213 354,208L370,80L590,80L606,208Q619,213 630.5,220Q642,227 653,235L772,185L882,375L779,453Q780,460 780,466.5Q780,473 780,480Q780,487 780,493.5Q780,500 778,507L881,585L771,775L653,725Q642,733 630,740Q618,747 606,752L590,880L370,880ZM440,800L519,800L533,694Q564,686 590.5,670.5Q617,655 639,633L738,674L777,606L691,541Q696,527 698,511.5Q700,496 700,480Q700,464 698,448.5Q696,433 691,419L777,354L738,286L639,328Q617,305 590.5,289.5Q564,274 533,266L520,160L441,160L427,266Q396,274 369.5,289.5Q343,305 321,327L222,286L183,354L269,418Q264,433 262,448Q260,463 260,480Q260,496 262,511Q264,526 269,541L183,606L222,674L321,632Q343,655 369.5,670.5Q396,686 427,694L440,800ZM482,620Q540,620 581,579Q622,538 622,480Q622,422 581,381Q540,340 482,340Q423,340 382.5,381Q342,422 342,480Q342,538 382.5,579Q423,620 482,620ZM480,480L480,480Q480,480 480,480Q480,480 480,480L480,480L480,480L480,480Q480,480 480,480Q480,480 480,480Q480,480 480,480Q480,480 480,480L480,480L480,480L480,480Q480,480 480,480Q480,480 480,480L480,480L480,480L480,480Q480,480 480,480Q480,480 480,480L480,480L480,480L480,480Q480,480 480,480Q480,480 480,480Q480,480 480,480Q480,480 480,480L480,480L480,480L480,480Q480,480 480,480Q480,480 480,480L480,480Z"/>
+</vector>
diff --git a/packages/SystemUI/res/drawable/volume_background_top.xml b/packages/SystemUI/res/drawable/volume_background_top.xml
index 3cd87fc..75849be 100644
--- a/packages/SystemUI/res/drawable/volume_background_top.xml
+++ b/packages/SystemUI/res/drawable/volume_background_top.xml
@@ -1,27 +1,26 @@
-<?xml version="1.0" encoding="utf-8"?>
-<!--
-  ~ Copyright (C) 2021 The Android Open Source Project
-  ~
-  ~ Licensed under the Apache License, Version 2.0 (the "License");
-  ~ you may not use this file except in compliance with the License.
-  ~ You may obtain a copy of the License at
-  ~
-  ~      http://www.apache.org/licenses/LICENSE-2.0
-  ~
-  ~ Unless required by applicable law or agreed to in writing, software
-  ~ distributed under the License is distributed on an "AS IS" BASIS,
-  ~ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
-  ~ See the License for the specific language governing permissions and
-  ~ limitations under the License
-  -->
+<?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.
+-->
 <layer-list xmlns:android="http://schemas.android.com/apk/res/android"
     xmlns:androidprv="http://schemas.android.com/apk/prv/res/android">
     <item>
         <shape>
-            <size android:width="@dimen/volume_dialog_panel_width" />
-            <solid android:color="?androidprv:attr/colorSurface" />
-            <corners android:topLeftRadius="@dimen/volume_dialog_panel_width_half"
-                android:topRightRadius="@dimen/volume_dialog_panel_width_half"/>
+            <size android:width="@dimen/volume_dialog_width" />
+            <solid android:color="?androidprv:attr/materialColorSurface" />
+            <corners android:topLeftRadius="@dimen/volume_dialog_background_corner_radius"
+                android:topRightRadius="@dimen/volume_dialog_background_corner_radius"/>
         </shape>
     </item>
-</layer-list>
\ No newline at end of file
+</layer-list>
diff --git a/packages/SystemUI/res/drawable/volume_background_top_legacy.xml b/packages/SystemUI/res/drawable/volume_background_top_legacy.xml
new file mode 100644
index 0000000..3cd87fc
--- /dev/null
+++ b/packages/SystemUI/res/drawable/volume_background_top_legacy.xml
@@ -0,0 +1,27 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!--
+  ~ Copyright (C) 2021 The Android Open Source Project
+  ~
+  ~ Licensed under the Apache License, Version 2.0 (the "License");
+  ~ you may not use this file except in compliance with the License.
+  ~ You may obtain a copy of the License at
+  ~
+  ~      http://www.apache.org/licenses/LICENSE-2.0
+  ~
+  ~ Unless required by applicable law or agreed to in writing, software
+  ~ distributed under the License is distributed on an "AS IS" BASIS,
+  ~ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+  ~ See the License for the specific language governing permissions and
+  ~ limitations under the License
+  -->
+<layer-list xmlns:android="http://schemas.android.com/apk/res/android"
+    xmlns:androidprv="http://schemas.android.com/apk/prv/res/android">
+    <item>
+        <shape>
+            <size android:width="@dimen/volume_dialog_panel_width" />
+            <solid android:color="?androidprv:attr/colorSurface" />
+            <corners android:topLeftRadius="@dimen/volume_dialog_panel_width_half"
+                android:topRightRadius="@dimen/volume_dialog_panel_width_half"/>
+        </shape>
+    </item>
+</layer-list>
\ No newline at end of file
diff --git a/packages/SystemUI/res/drawable/volume_drawer_selection_bg.xml b/packages/SystemUI/res/drawable/volume_drawer_selection_bg.xml
index 5e7cb12..df8521a 100644
--- a/packages/SystemUI/res/drawable/volume_drawer_selection_bg.xml
+++ b/packages/SystemUI/res/drawable/volume_drawer_selection_bg.xml
@@ -1,6 +1,5 @@
-<?xml version="1.0" encoding="utf-8"?>
-<!--
-  ~ Copyright (C) 2021 The Android Open Source Project
+<?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.
@@ -15,10 +14,11 @@
   ~ limitations under the License.
   -->
 <shape xmlns:android="http://schemas.android.com/apk/res/android"
-            android:paddingMode="stack" >
+    xmlns:androidprv="http://schemas.android.com/apk/prv/res/android"
+    android:paddingMode="stack" >
     <size
-        android:height="@dimen/volume_ringer_drawer_item_size"
-        android:width="@dimen/volume_ringer_drawer_item_size" />
-    <solid android:color="?android:attr/colorAccent" />
-    <corners android:radius="@dimen/volume_ringer_drawer_item_size_half" />
+        android:height="@dimen/volume_ringer_item_size"
+        android:width="@dimen/volume_ringer_item_size" />
+    <solid android:color="?androidprv:attr/materialColorPrimary" />
+    <corners android:radius="360dp" />
 </shape>
\ No newline at end of file
diff --git a/packages/SystemUI/res/drawable/volume_drawer_selection_bg_legacy.xml b/packages/SystemUI/res/drawable/volume_drawer_selection_bg_legacy.xml
new file mode 100644
index 0000000..5e7cb12
--- /dev/null
+++ b/packages/SystemUI/res/drawable/volume_drawer_selection_bg_legacy.xml
@@ -0,0 +1,24 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!--
+  ~ Copyright (C) 2021 The Android Open Source Project
+  ~
+  ~ Licensed under the Apache License, Version 2.0 (the "License");
+  ~ you may not use this file except in compliance with the License.
+  ~ You may obtain a copy of the License at
+  ~
+  ~      http://www.apache.org/licenses/LICENSE-2.0
+  ~
+  ~ Unless required by applicable law or agreed to in writing, software
+  ~ distributed under the License is distributed on an "AS IS" BASIS,
+  ~ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+  ~ See the License for the specific language governing permissions and
+  ~ limitations under the License.
+  -->
+<shape xmlns:android="http://schemas.android.com/apk/res/android"
+            android:paddingMode="stack" >
+    <size
+        android:height="@dimen/volume_ringer_drawer_item_size"
+        android:width="@dimen/volume_ringer_drawer_item_size" />
+    <solid android:color="?android:attr/colorAccent" />
+    <corners android:radius="@dimen/volume_ringer_drawer_item_size_half" />
+</shape>
\ No newline at end of file
diff --git a/packages/SystemUI/res/drawable/volume_ringer_item_bg.xml b/packages/SystemUI/res/drawable/volume_ringer_item_bg.xml
new file mode 100644
index 0000000..7ddd57b7
--- /dev/null
+++ b/packages/SystemUI/res/drawable/volume_ringer_item_bg.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.
+  -->
+<shape xmlns:android="http://schemas.android.com/apk/res/android"
+    xmlns:androidprv="http://schemas.android.com/apk/prv/res/android"
+    android:shape="rectangle" >
+    <size android:width="@dimen/volume_ringer_item_size" android:height="@dimen/volume_ringer_item_size"/>
+    <solid android:color="?androidprv:attr/materialColorSurfaceContainerHighest" />
+    <corners android:radius="@dimen/volume_ringer_item_radius" />
+</shape>
\ No newline at end of file
diff --git a/packages/SystemUI/res/layout-land-television/volume_dialog.xml b/packages/SystemUI/res/layout-land-television/volume_dialog.xml
deleted file mode 100644
index f77db95..0000000
--- a/packages/SystemUI/res/layout-land-television/volume_dialog.xml
+++ /dev/null
@@ -1,67 +0,0 @@
-<!--
-     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.
--->
-<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
-    xmlns:androidprv="http://schemas.android.com/apk/prv/res/android"
-    android:id="@+id/volume_dialog_container"
-    android:layout_width="wrap_content"
-    android:layout_height="wrap_content"
-    android:layout_gravity="right"
-    android:divider="@drawable/volume_dialog_floating_sliders_spacer"
-    android:orientation="horizontal"
-    android:showDividers="middle|end|beginning"
-    android:theme="@style/volume_dialog_theme">
-
-    <LinearLayout
-        android:id="@+id/volume_dialog_floating_sliders_container"
-        android:layout_width="wrap_content"
-        android:layout_height="match_parent"
-        android:background="@drawable/volume_dialog_background"
-        android:divider="@drawable/volume_dialog_floating_sliders_spacer"
-        android:gravity="bottom"
-        android:orientation="horizontal"
-        android:paddingBottom="@dimen/volume_dialog_floating_sliders_bottom_padding"
-        android:showDividers="middle" />
-
-    <LinearLayout
-        android:layout_width="@dimen/volume_dialog_width"
-        android:layout_height="wrap_content"
-        android:background="@drawable/volume_dialog_background"
-        android:divider="@drawable/volume_dialog_spacer"
-        android:gravity="center_horizontal"
-        android:orientation="vertical"
-        android:paddingVertical="@dimen/volume_dialog_vertical_padding"
-        android:showDividers="middle">
-
-        <FrameLayout
-            android:id="@+id/volume_dialog_ringer_button"
-            android:layout_width="@dimen/volume_dialog_button_size"
-            android:layout_height="@dimen/volume_dialog_button_size" />
-
-        <include
-            android:id="@+id/volume_dialog_slider"
-            layout="@layout/volume_dialog_slider" />
-
-        <Button
-            android:id="@+id/volume_dialog_settings"
-            android:layout_width="@dimen/volume_dialog_button_size"
-            android:layout_height="@dimen/volume_dialog_button_size"
-            android:background="@drawable/ripple_drawable_20dp"
-            android:contentDescription="@string/accessibility_volume_settings"
-            android:soundEffectsEnabled="false"
-            android:src="@drawable/horizontal_ellipsis"
-            android:tint="?androidprv:attr/materialColorPrimary" />
-    </LinearLayout>
-</LinearLayout>
\ No newline at end of file
diff --git a/packages/SystemUI/res/layout-land/volume_dialog.xml b/packages/SystemUI/res/layout-land/volume_dialog.xml
deleted file mode 100644
index f77db95..0000000
--- a/packages/SystemUI/res/layout-land/volume_dialog.xml
+++ /dev/null
@@ -1,67 +0,0 @@
-<!--
-     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.
--->
-<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
-    xmlns:androidprv="http://schemas.android.com/apk/prv/res/android"
-    android:id="@+id/volume_dialog_container"
-    android:layout_width="wrap_content"
-    android:layout_height="wrap_content"
-    android:layout_gravity="right"
-    android:divider="@drawable/volume_dialog_floating_sliders_spacer"
-    android:orientation="horizontal"
-    android:showDividers="middle|end|beginning"
-    android:theme="@style/volume_dialog_theme">
-
-    <LinearLayout
-        android:id="@+id/volume_dialog_floating_sliders_container"
-        android:layout_width="wrap_content"
-        android:layout_height="match_parent"
-        android:background="@drawable/volume_dialog_background"
-        android:divider="@drawable/volume_dialog_floating_sliders_spacer"
-        android:gravity="bottom"
-        android:orientation="horizontal"
-        android:paddingBottom="@dimen/volume_dialog_floating_sliders_bottom_padding"
-        android:showDividers="middle" />
-
-    <LinearLayout
-        android:layout_width="@dimen/volume_dialog_width"
-        android:layout_height="wrap_content"
-        android:background="@drawable/volume_dialog_background"
-        android:divider="@drawable/volume_dialog_spacer"
-        android:gravity="center_horizontal"
-        android:orientation="vertical"
-        android:paddingVertical="@dimen/volume_dialog_vertical_padding"
-        android:showDividers="middle">
-
-        <FrameLayout
-            android:id="@+id/volume_dialog_ringer_button"
-            android:layout_width="@dimen/volume_dialog_button_size"
-            android:layout_height="@dimen/volume_dialog_button_size" />
-
-        <include
-            android:id="@+id/volume_dialog_slider"
-            layout="@layout/volume_dialog_slider" />
-
-        <Button
-            android:id="@+id/volume_dialog_settings"
-            android:layout_width="@dimen/volume_dialog_button_size"
-            android:layout_height="@dimen/volume_dialog_button_size"
-            android:background="@drawable/ripple_drawable_20dp"
-            android:contentDescription="@string/accessibility_volume_settings"
-            android:soundEffectsEnabled="false"
-            android:src="@drawable/horizontal_ellipsis"
-            android:tint="?androidprv:attr/materialColorPrimary" />
-    </LinearLayout>
-</LinearLayout>
\ No newline at end of file
diff --git a/packages/SystemUI/res/layout-land/volume_dialog_legacy.xml b/packages/SystemUI/res/layout-land/volume_dialog_legacy.xml
index 08edf59..de346db 100644
--- a/packages/SystemUI/res/layout-land/volume_dialog_legacy.xml
+++ b/packages/SystemUI/res/layout-land/volume_dialog_legacy.xml
@@ -44,7 +44,7 @@
             android:clipChildren="false"
             android:gravity="right">
 
-            <include layout="@layout/volume_ringer_drawer" />
+            <include layout="@layout/volume_ringer_drawer_legacy" />
 
             <FrameLayout
                 android:visibility="gone"
diff --git a/packages/SystemUI/res/layout/status_bar_notification_footer_redesign.xml b/packages/SystemUI/res/layout/status_bar_notification_footer_redesign.xml
new file mode 100644
index 0000000..7c59aad
--- /dev/null
+++ b/packages/SystemUI/res/layout/status_bar_notification_footer_redesign.xml
@@ -0,0 +1,82 @@
+<!--
+  ~ 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
+  -->
+
+<!-- Extends Framelayout -->
+<com.android.systemui.statusbar.notification.footer.ui.view.FooterView
+    xmlns:android="http://schemas.android.com/apk/res/android"
+    xmlns:app="http://schemas.android.com/apk/res-auto"
+    android:layout_width="match_parent"
+    android:layout_height="wrap_content"
+    android:visibility="gone">
+
+    <com.android.systemui.statusbar.AlphaOptimizedFrameLayout
+        android:id="@+id/content"
+        android:layout_width="match_parent"
+        android:layout_height="wrap_content"
+        android:paddingTop="12dp">
+
+        <TextView
+            android:id="@+id/unlock_prompt_footer"
+            android:layout_width="wrap_content"
+            android:layout_height="wrap_content"
+            android:layout_gravity="center_horizontal"
+            android:drawablePadding="8dp"
+            android:gravity="center"
+            android:text="@string/unlock_to_see_notif_text"
+            android:textAppearance="?android:attr/textAppearanceButton"
+            android:visibility="gone" />
+
+        <androidx.constraintlayout.widget.ConstraintLayout
+            android:layout_width="match_parent"
+            android:layout_height="wrap_content">
+
+            <com.android.systemui.statusbar.notification.row.FooterViewButton
+                android:id="@+id/settings_button"
+                style="@style/TextAppearance.NotificationFooterButtonRedesign"
+                android:layout_width="wrap_content"
+                android:layout_height="48dp"
+                android:background="@drawable/notif_footer_btn_background"
+                android:contentDescription="@string/notification_settings_button_description"
+                android:drawableStart="@drawable/notif_footer_btn_settings"
+                android:focusable="true"
+                app:layout_constraintStart_toStartOf="parent" />
+
+            <com.android.systemui.statusbar.notification.row.FooterViewButton
+                android:id="@+id/dismiss_text"
+                style="@style/TextAppearance.NotificationFooterButtonRedesign"
+                android:layout_width="0dp"
+                android:layout_height="48dp"
+                android:layout_marginHorizontal="8dp"
+                android:background="@drawable/notif_footer_btn_background"
+                android:contentDescription="@string/accessibility_clear_all"
+                android:focusable="true"
+                android:text="@string/clear_all_notifications_text"
+                app:layout_constraintEnd_toStartOf="@id/history_button"
+                app:layout_constraintStart_toEndOf="@id/settings_button" />
+
+            <com.android.systemui.statusbar.notification.row.FooterViewButton
+                android:id="@+id/history_button"
+                style="@style/TextAppearance.NotificationFooterButtonRedesign"
+                android:layout_width="wrap_content"
+                android:layout_height="48dp"
+                android:background="@drawable/notif_footer_btn_background"
+                android:contentDescription="@string/notification_history_button_description"
+                android:drawableStart="@drawable/notif_footer_btn_history"
+                android:focusable="true"
+                app:layout_constraintEnd_toEndOf="parent" />
+        </androidx.constraintlayout.widget.ConstraintLayout>
+    </com.android.systemui.statusbar.AlphaOptimizedFrameLayout>
+</com.android.systemui.statusbar.notification.footer.ui.view.FooterView>
diff --git a/packages/SystemUI/res/layout/volume_dialog.xml b/packages/SystemUI/res/layout/volume_dialog.xml
index f77db95..8b39e5e 100644
--- a/packages/SystemUI/res/layout/volume_dialog.xml
+++ b/packages/SystemUI/res/layout/volume_dialog.xml
@@ -28,7 +28,6 @@
         android:id="@+id/volume_dialog_floating_sliders_container"
         android:layout_width="wrap_content"
         android:layout_height="match_parent"
-        android:background="@drawable/volume_dialog_background"
         android:divider="@drawable/volume_dialog_floating_sliders_spacer"
         android:gravity="bottom"
         android:orientation="horizontal"
@@ -36,6 +35,7 @@
         android:showDividers="middle" />
 
     <LinearLayout
+        android:id="@+id/volume_dialog"
         android:layout_width="@dimen/volume_dialog_width"
         android:layout_height="wrap_content"
         android:background="@drawable/volume_dialog_background"
@@ -45,16 +45,11 @@
         android:paddingVertical="@dimen/volume_dialog_vertical_padding"
         android:showDividers="middle">
 
-        <FrameLayout
-            android:id="@+id/volume_dialog_ringer_button"
-            android:layout_width="@dimen/volume_dialog_button_size"
-            android:layout_height="@dimen/volume_dialog_button_size" />
+        <include layout="@layout/volume_ringer_drawer" />
 
-        <include
-            android:id="@+id/volume_dialog_slider"
-            layout="@layout/volume_dialog_slider" />
+        <include layout="@layout/volume_dialog_slider" />
 
-        <Button
+        <ImageButton
             android:id="@+id/volume_dialog_settings"
             android:layout_width="@dimen/volume_dialog_button_size"
             android:layout_height="@dimen/volume_dialog_button_size"
diff --git a/packages/SystemUI/res/layout/volume_dialog_legacy.xml b/packages/SystemUI/res/layout/volume_dialog_legacy.xml
index 39a1f1f..9010ab7 100644
--- a/packages/SystemUI/res/layout/volume_dialog_legacy.xml
+++ b/packages/SystemUI/res/layout/volume_dialog_legacy.xml
@@ -45,7 +45,7 @@
             android:orientation="vertical"
             android:gravity="right">
 
-            <include layout="@layout/volume_ringer_drawer" />
+            <include layout="@layout/volume_ringer_drawer_legacy" />
 
             <FrameLayout
                 android:visibility="gone"
diff --git a/packages/SystemUI/res/layout/volume_ringer_drawer.xml b/packages/SystemUI/res/layout/volume_ringer_drawer.xml
index 8f1e061..94b55b1 100644
--- a/packages/SystemUI/res/layout/volume_ringer_drawer.xml
+++ b/packages/SystemUI/res/layout/volume_ringer_drawer.xml
@@ -1,35 +1,32 @@
-<?xml version="1.0" encoding="utf-8"?>
-<!--
-     Copyright (C) 2021 The Android Open Source Project
+<?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.
+  -->
 
-     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.
--->
-
-<!-- Contains the active ringer icon and a hidden drawer containing all three ringer options. -->
-<FrameLayout
-    xmlns:android="http://schemas.android.com/apk/res/android"
+<FrameLayout xmlns:android="http://schemas.android.com/apk/res/android"
+    xmlns:androidprv="http://schemas.android.com/apk/prv/res/android"
     android:id="@+id/volume_ringer_and_drawer_container"
     android:layout_width="wrap_content"
     android:layout_height="wrap_content"
     android:gravity="center"
-    android:paddingLeft="@dimen/volume_dialog_ringer_rows_padding"
-    android:paddingTop="@dimen/volume_dialog_ringer_rows_padding"
-    android:paddingRight="@dimen/volume_dialog_ringer_rows_padding"
-    android:paddingBottom="@dimen/volume_dialog_ringer_rows_padding"
-    android:background="@drawable/volume_background_top"
+    android:paddingLeft="@dimen/volume_dialog_ringer_container_padding"
+    android:paddingTop="@dimen/volume_dialog_ringer_container_padding"
+    android:paddingRight="@dimen/volume_dialog_ringer_container_padding"
     android:layoutDirection="ltr"
     android:clipToPadding="false"
-    android:clipChildren="false">
+    android:clipChildren="false"
+    android:background="@drawable/volume_background_top">
 
     <!-- Drawer view, invisible by default. -->
     <FrameLayout
@@ -37,15 +34,15 @@
         android:alpha="0.0"
         android:layout_width="match_parent"
         android:layout_height="wrap_content"
-        android:background="@drawable/volume_drawer_bg"
         android:orientation="vertical">
 
         <!-- View that is animated to a tapped ringer selection, so it appears selected. -->
         <FrameLayout
             android:id="@+id/volume_drawer_selection_background"
             android:alpha="0.0"
-            android:layout_width="@dimen/volume_ringer_drawer_item_size"
-            android:layout_height="@dimen/volume_ringer_drawer_item_size"
+            android:layout_width="@dimen/volume_ringer_item_size"
+            android:layout_marginBottom="8dp"
+            android:layout_height="@dimen/volume_ringer_item_size"
             android:layout_gravity="bottom|right"
             android:background="@drawable/volume_drawer_selection_bg" />
 
@@ -57,54 +54,60 @@
 
             <FrameLayout
                 android:id="@+id/volume_drawer_vibrate"
-                android:layout_width="@dimen/volume_ringer_drawer_item_size"
-                android:layout_height="@dimen/volume_ringer_drawer_item_size"
+                android:layout_width="@dimen/volume_ringer_item_size"
+                android:layout_height="@dimen/volume_ringer_item_size"
+                android:layout_marginBottom="8dp"
                 android:contentDescription="@string/volume_ringer_hint_vibrate"
-                android:gravity="center">
+                android:gravity="center"
+                android:background="@drawable/volume_ringer_item_bg">
 
                 <ImageView
                     android:id="@+id/volume_drawer_vibrate_icon"
-                    android:layout_width="@dimen/volume_ringer_drawer_icon_size"
-                    android:layout_height="@dimen/volume_ringer_drawer_icon_size"
+                    android:layout_width="@dimen/volume_ringer_icon_size"
+                    android:layout_height="@dimen/volume_ringer_icon_size"
                     android:layout_gravity="center"
                     android:src="@drawable/ic_volume_ringer_vibrate"
-                    android:tint="?android:attr/textColorPrimary" />
+                    android:tint="?androidprv:attr/materialColorOnSurface" />
 
             </FrameLayout>
 
             <FrameLayout
                 android:id="@+id/volume_drawer_mute"
-                android:layout_width="@dimen/volume_ringer_drawer_item_size"
-                android:layout_height="@dimen/volume_ringer_drawer_item_size"
+                android:layout_width="@dimen/volume_ringer_item_size"
+                android:layout_height="@dimen/volume_ringer_item_size"
+                android:layout_marginBottom="8dp"
                 android:accessibilityTraversalAfter="@id/volume_drawer_vibrate"
                 android:contentDescription="@string/volume_ringer_hint_mute"
-                android:gravity="center">
+                android:gravity="center"
+                android:background="@drawable/volume_ringer_item_bg">
 
                 <ImageView
                     android:id="@+id/volume_drawer_mute_icon"
-                    android:layout_width="@dimen/volume_ringer_drawer_icon_size"
-                    android:layout_height="@dimen/volume_ringer_drawer_icon_size"
+                    android:layout_width="@dimen/volume_ringer_icon_size"
+                    android:layout_height="@dimen/volume_ringer_icon_size"
                     android:layout_gravity="center"
                     android:src="@drawable/ic_speaker_mute"
-                    android:tint="?android:attr/textColorPrimary" />
+                    android:tint="?androidprv:attr/materialColorOnSurface" />
 
             </FrameLayout>
 
             <FrameLayout
                 android:id="@+id/volume_drawer_normal"
-                android:layout_width="@dimen/volume_ringer_drawer_item_size"
-                android:layout_height="@dimen/volume_ringer_drawer_item_size"
+                android:layout_width="@dimen/volume_ringer_item_size"
+                android:layout_height="@dimen/volume_ringer_item_size"
+                android:layout_marginBottom="8dp"
                 android:accessibilityTraversalAfter="@id/volume_drawer_mute"
                 android:contentDescription="@string/volume_ringer_hint_unmute"
-                android:gravity="center">
+                android:gravity="center"
+                android:background="@drawable/volume_ringer_item_bg">
 
                 <ImageView
                     android:id="@+id/volume_drawer_normal_icon"
-                    android:layout_width="@dimen/volume_ringer_drawer_icon_size"
-                    android:layout_height="@dimen/volume_ringer_drawer_icon_size"
+                    android:layout_width="@dimen/volume_ringer_icon_size"
+                    android:layout_height="@dimen/volume_ringer_icon_size"
                     android:layout_gravity="center"
                     android:src="@drawable/ic_speaker_on"
-                    android:tint="?android:attr/textColorPrimary" />
+                    android:tint="?androidprv:attr/materialColorOnSurface" />
 
             </FrameLayout>
 
@@ -116,18 +119,19 @@
          position in the drawer. When the drawer is closed, it animates back. -->
     <FrameLayout
         android:id="@+id/volume_new_ringer_active_icon_container"
-        android:layout_width="@dimen/volume_ringer_drawer_item_size"
-        android:layout_height="@dimen/volume_ringer_drawer_item_size"
+        android:layout_width="@dimen/volume_ringer_item_size"
+        android:layout_height="@dimen/volume_ringer_item_size"
+        android:layout_marginBottom="8dp"
         android:layout_gravity="bottom|right"
         android:contentDescription="@string/volume_ringer_change"
         android:background="@drawable/volume_drawer_selection_bg">
 
         <ImageView
             android:id="@+id/volume_new_ringer_active_icon"
-            android:layout_width="@dimen/volume_ringer_drawer_icon_size"
-            android:layout_height="@dimen/volume_ringer_drawer_icon_size"
+            android:layout_width="@dimen/volume_ringer_icon_size"
+            android:layout_height="@dimen/volume_ringer_icon_size"
             android:layout_gravity="center"
-            android:tint="?android:attr/textColorPrimaryInverse"
+            android:tint="?androidprv:attr/materialColorOnPrimary"
             android:src="@drawable/ic_volume_media" />
 
     </FrameLayout>
diff --git a/packages/SystemUI/res/layout/volume_ringer_drawer_legacy.xml b/packages/SystemUI/res/layout/volume_ringer_drawer_legacy.xml
new file mode 100644
index 0000000..0efbc6d
--- /dev/null
+++ b/packages/SystemUI/res/layout/volume_ringer_drawer_legacy.xml
@@ -0,0 +1,135 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!--
+     Copyright (C) 2021 The Android Open Source Project
+
+     Licensed under the Apache License, Version 2.0 (the "License");
+     you may not use this file except in compliance with the License.
+     You may obtain a copy of the License at
+
+          http://www.apache.org/licenses/LICENSE-2.0
+
+     Unless required by applicable law or agreed to in writing, software
+     distributed under the License is distributed on an "AS IS" BASIS,
+     WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+     See the License for the specific language governing permissions and
+     limitations under the License.
+-->
+
+<!-- Contains the active ringer icon and a hidden drawer containing all three ringer options. -->
+<FrameLayout
+    xmlns:android="http://schemas.android.com/apk/res/android"
+    android:id="@+id/volume_ringer_and_drawer_container"
+    android:layout_width="wrap_content"
+    android:layout_height="wrap_content"
+    android:gravity="center"
+    android:paddingLeft="@dimen/volume_dialog_ringer_rows_padding"
+    android:paddingTop="@dimen/volume_dialog_ringer_rows_padding"
+    android:paddingRight="@dimen/volume_dialog_ringer_rows_padding"
+    android:paddingBottom="@dimen/volume_dialog_ringer_rows_padding"
+    android:background="@drawable/volume_background_top_legacy"
+    android:layoutDirection="ltr"
+    android:clipToPadding="false"
+    android:clipChildren="false">
+
+    <!-- Drawer view, invisible by default. -->
+    <FrameLayout
+        android:id="@+id/volume_drawer_container"
+        android:alpha="0.0"
+        android:layout_width="match_parent"
+        android:layout_height="wrap_content"
+        android:background="@drawable/volume_drawer_bg"
+        android:orientation="vertical">
+
+        <!-- View that is animated to a tapped ringer selection, so it appears selected. -->
+        <FrameLayout
+            android:id="@+id/volume_drawer_selection_background"
+            android:alpha="0.0"
+            android:layout_width="@dimen/volume_ringer_drawer_item_size"
+            android:layout_height="@dimen/volume_ringer_drawer_item_size"
+            android:layout_gravity="bottom|right"
+            android:background="@drawable/volume_drawer_selection_bg_legacy" />
+
+        <LinearLayout
+            android:id="@+id/volume_drawer_options"
+            android:layout_width="match_parent"
+            android:layout_height="wrap_content"
+            android:orientation="vertical">
+
+            <FrameLayout
+                android:id="@+id/volume_drawer_vibrate"
+                android:layout_width="@dimen/volume_ringer_drawer_item_size"
+                android:layout_height="@dimen/volume_ringer_drawer_item_size"
+                android:contentDescription="@string/volume_ringer_hint_vibrate"
+                android:gravity="center">
+
+                <ImageView
+                    android:id="@+id/volume_drawer_vibrate_icon"
+                    android:layout_width="@dimen/volume_ringer_drawer_icon_size"
+                    android:layout_height="@dimen/volume_ringer_drawer_icon_size"
+                    android:layout_gravity="center"
+                    android:src="@drawable/ic_volume_ringer_vibrate"
+                    android:tint="?android:attr/textColorPrimary" />
+
+            </FrameLayout>
+
+            <FrameLayout
+                android:id="@+id/volume_drawer_mute"
+                android:layout_width="@dimen/volume_ringer_drawer_item_size"
+                android:layout_height="@dimen/volume_ringer_drawer_item_size"
+                android:accessibilityTraversalAfter="@id/volume_drawer_vibrate"
+                android:contentDescription="@string/volume_ringer_hint_mute"
+                android:gravity="center">
+
+                <ImageView
+                    android:id="@+id/volume_drawer_mute_icon"
+                    android:layout_width="@dimen/volume_ringer_drawer_icon_size"
+                    android:layout_height="@dimen/volume_ringer_drawer_icon_size"
+                    android:layout_gravity="center"
+                    android:src="@drawable/ic_speaker_mute"
+                    android:tint="?android:attr/textColorPrimary" />
+
+            </FrameLayout>
+
+            <FrameLayout
+                android:id="@+id/volume_drawer_normal"
+                android:layout_width="@dimen/volume_ringer_drawer_item_size"
+                android:layout_height="@dimen/volume_ringer_drawer_item_size"
+                android:accessibilityTraversalAfter="@id/volume_drawer_mute"
+                android:contentDescription="@string/volume_ringer_hint_unmute"
+                android:gravity="center">
+
+                <ImageView
+                    android:id="@+id/volume_drawer_normal_icon"
+                    android:layout_width="@dimen/volume_ringer_drawer_icon_size"
+                    android:layout_height="@dimen/volume_ringer_drawer_icon_size"
+                    android:layout_gravity="center"
+                    android:src="@drawable/ic_speaker_on"
+                    android:tint="?android:attr/textColorPrimary" />
+
+            </FrameLayout>
+
+        </LinearLayout>
+
+    </FrameLayout>
+
+    <!-- The current ringer selection. When the drawer is opened, this animates to the corresponding
+         position in the drawer. When the drawer is closed, it animates back. -->
+    <FrameLayout
+        android:id="@+id/volume_new_ringer_active_icon_container"
+        android:layout_width="@dimen/volume_ringer_drawer_item_size"
+        android:layout_height="@dimen/volume_ringer_drawer_item_size"
+        android:layout_gravity="bottom|right"
+        android:contentDescription="@string/volume_ringer_change"
+        android:background="@drawable/volume_drawer_selection_bg_legacy">
+
+        <ImageView
+            android:id="@+id/volume_new_ringer_active_icon"
+            android:layout_width="@dimen/volume_ringer_drawer_icon_size"
+            android:layout_height="@dimen/volume_ringer_drawer_icon_size"
+            android:layout_gravity="center"
+            android:tint="?android:attr/textColorPrimaryInverse"
+            android:src="@drawable/ic_volume_media" />
+
+    </FrameLayout>
+
+</FrameLayout>
\ No newline at end of file
diff --git a/packages/SystemUI/res/values/dimens.xml b/packages/SystemUI/res/values/dimens.xml
index 87e6aa0..c2d942f 100644
--- a/packages/SystemUI/res/values/dimens.xml
+++ b/packages/SystemUI/res/values/dimens.xml
@@ -2064,5 +2064,11 @@
 
     <dimen name="volume_panel_slice_vertical_padding">8dp</dimen>
     <dimen name="volume_panel_slice_horizontal_padding">24dp</dimen>
+
+    <dimen name="volume_dialog_ringer_container_padding">10dp</dimen>
+    <dimen name="volume_ringer_item_size">40dp</dimen>
+    <dimen name="volume_ringer_icon_size">20dp</dimen>
+    <dimen name="volume_ringer_item_radius">12dp</dimen>
+    <dimen name="volume_dialog_ringer_item_margin_bottom">8dp</dimen>
     <!-- Volume end -->
 </resources>
diff --git a/packages/SystemUI/res/values/strings.xml b/packages/SystemUI/res/values/strings.xml
index c494e85..0aa5ccf 100644
--- a/packages/SystemUI/res/values/strings.xml
+++ b/packages/SystemUI/res/values/strings.xml
@@ -1463,6 +1463,12 @@
     <!-- The text for the notification history link. [CHAR LIMIT=40] -->
     <string name="manage_notifications_history_text">History</string>
 
+    <!-- The accessibility description for the notification settings button in the notification shade. -->
+    <string name="notification_settings_button_description">Notification settings</string>
+
+    <!-- The accessibility description for the notification history button in the notification shade. -->
+    <string name="notification_history_button_description">Notification history</string>
+
     <!-- Section title for notifications that have recently appeared. [CHAR LIMIT=40] -->
     <string name="notification_section_header_incoming">New</string>
 
diff --git a/packages/SystemUI/res/values/styles.xml b/packages/SystemUI/res/values/styles.xml
index 1ab9242..ab45b9f 100644
--- a/packages/SystemUI/res/values/styles.xml
+++ b/packages/SystemUI/res/values/styles.xml
@@ -785,6 +785,16 @@
         <item name="android:minWidth">0dp</item>
     </style>
 
+    <style
+        name="TextAppearance.NotificationFooterButtonRedesign"
+        parent="@android:style/Widget.DeviceDefault.Button.Borderless">
+        <item name="android:textColor">?androidprv:attr/materialColorOnSurface</item>
+        <item name="android:drawableTint">?androidprv:attr/materialColorOnSurface</item>
+        <item name="android:textAllCaps">false</item>
+        <item name="android:textSize">16sp</item>
+        <item name="android:minWidth">0dp</item>
+    </style>
+
     <style name="TextAppearance.HeadsUpStatusBarText"
         parent="@*android:style/TextAppearance.DeviceDefault.Notification.Info">
     </style>
diff --git a/packages/SystemUI/src/com/android/keyguard/KeyguardUpdateMonitor.java b/packages/SystemUI/src/com/android/keyguard/KeyguardUpdateMonitor.java
index b3ea75d..8b2cf7a 100644
--- a/packages/SystemUI/src/com/android/keyguard/KeyguardUpdateMonitor.java
+++ b/packages/SystemUI/src/com/android/keyguard/KeyguardUpdateMonitor.java
@@ -80,6 +80,7 @@
 import android.hardware.fingerprint.IFingerprintAuthenticatorsRegisteredCallback;
 import android.hardware.usb.UsbManager;
 import android.nfc.NfcAdapter;
+import android.os.BatteryManager;
 import android.os.CancellationSignal;
 import android.os.Handler;
 import android.os.Looper;
@@ -345,9 +346,9 @@
     // Device provisioning state
     private boolean mDeviceProvisioned;
 
-    // Battery status
+    // Battery status (null until first update is received)
     @VisibleForTesting
-    BatteryStatus mBatteryStatus;
+    BatteryStatus mBatteryStatus = null;
     @VisibleForTesting
     boolean mIncompatibleCharger;
 
@@ -2367,9 +2368,27 @@
             watchForDeviceProvisioning();
         }
 
-        // Take a guess at initial SIM state, battery status and PLMN until we get an update
-        mBatteryStatus = new BatteryStatus(BATTERY_STATUS_UNKNOWN, /* level= */ -1, /* plugged= */
-                0, CHARGING_POLICY_DEFAULT, /* maxChargingWattage= */0, /* present= */true);
+        // Request the initial battery level
+        mBackgroundExecutor.execute(() -> {
+            BatteryManager batteryManager = mContext.getSystemService(BatteryManager.class);
+            int level = -1;
+            if (batteryManager != null) {
+                int capacity = batteryManager.getIntProperty(
+                        BatteryManager.BATTERY_PROPERTY_CAPACITY);
+                if (capacity >= 0 && capacity <= 100) {
+                    level = capacity;
+                }
+            }
+            // Don't override if a valid battery status update has come in
+            final BatteryStatus status = new BatteryStatus(BATTERY_STATUS_UNKNOWN,
+                    /* level= */ level, /* plugged= */ 0, CHARGING_POLICY_DEFAULT,
+                    /* maxChargingWattage= */0, /* present= */true);
+            mMainExecutor.execute(() -> {
+                if (mBatteryStatus == null) {
+                    handleBatteryUpdate(status);
+                }
+            });
+        });
 
         // Watch for interesting updates
         final IntentFilter filter = new IntentFilter();
@@ -3591,6 +3610,9 @@
     }
 
     private boolean isBatteryUpdateInteresting(BatteryStatus old, BatteryStatus current) {
+        if (old == null) {
+            return true;
+        }
         final boolean nowPluggedIn = current.isPluggedIn();
         final boolean wasPluggedIn = old.isPluggedIn();
         final boolean stateChangedWhilePluggedIn = wasPluggedIn && nowPluggedIn
@@ -3687,7 +3709,9 @@
 
     private void sendUpdates(KeyguardUpdateMonitorCallback callback) {
         // Notify listener of the current state
-        callback.onRefreshBatteryInfo(mBatteryStatus);
+        if (mBatteryStatus != null) {
+            callback.onRefreshBatteryInfo(mBatteryStatus);
+        }
         callback.onTimeChanged();
         callback.onPhoneStateChanged(mPhoneState);
         callback.onRefreshCarrierInfo();
diff --git a/packages/SystemUI/src/com/android/systemui/ambient/touch/BouncerSwipeTouchHandler.kt b/packages/SystemUI/src/com/android/systemui/ambient/touch/BouncerSwipeTouchHandler.kt
index 7b3d337..223a21d 100644
--- a/packages/SystemUI/src/com/android/systemui/ambient/touch/BouncerSwipeTouchHandler.kt
+++ b/packages/SystemUI/src/com/android/systemui/ambient/touch/BouncerSwipeTouchHandler.kt
@@ -38,9 +38,6 @@
 import com.android.systemui.communal.ui.viewmodel.CommunalViewModel
 import com.android.systemui.keyguard.domain.interactor.KeyguardInteractor
 import com.android.systemui.plugins.ActivityStarter
-import com.android.systemui.scene.domain.interactor.SceneInteractor
-import com.android.systemui.scene.shared.flag.SceneContainerFlag
-import com.android.systemui.scene.ui.view.WindowRootView
 import com.android.systemui.shade.ShadeExpansionChangeEvent
 import com.android.systemui.statusbar.NotificationShadeWindowController
 import com.android.systemui.statusbar.phone.CentralSurfaces
@@ -48,7 +45,6 @@
 import java.util.Optional
 import javax.inject.Inject
 import javax.inject.Named
-import javax.inject.Provider
 import kotlin.math.abs
 import kotlin.math.hypot
 import kotlin.math.max
@@ -78,8 +74,6 @@
     private val uiEventLogger: UiEventLogger,
     private val activityStarter: ActivityStarter,
     private val keyguardInteractor: KeyguardInteractor,
-    private val sceneInteractor: SceneInteractor,
-    private val windowRootViewProvider: Optional<Provider<WindowRootView>>,
 ) : TouchHandler {
     /** An interface for creating ValueAnimators. */
     interface ValueAnimatorCreator {
@@ -106,8 +100,6 @@
             currentScrimController = controller
         }
 
-    private val windowRootView by lazy { windowRootViewProvider.get().get() }
-
     /** Determines whether the touch handler should process touches in fullscreen swiping mode */
     private var touchAvailable = false
 
@@ -117,7 +109,7 @@
                 e1: MotionEvent?,
                 e2: MotionEvent,
                 distanceX: Float,
-                distanceY: Float,
+                distanceY: Float
             ): Boolean {
                 if (capture == null) {
                     capture =
@@ -136,11 +128,6 @@
                         expanded = false
                         // Since the user is dragging the bouncer up, set scrimmed to false.
                         currentScrimController?.show()
-
-                        if (SceneContainerFlag.isEnabled) {
-                            sceneInteractor.onRemoteUserInputStarted("bouncer touch handler")
-                            e1?.apply { windowRootView.dispatchTouchEvent(e1) }
-                        }
                     }
                 }
                 if (capture != true) {
@@ -165,27 +152,20 @@
                             /* cancelAction= */ null,
                             /* dismissShade= */ true,
                             /* afterKeyguardGone= */ true,
-                            /* deferred= */ false,
+                            /* deferred= */ false
                         )
                         return true
                     }
 
-                    if (SceneContainerFlag.isEnabled) {
-                        windowRootView.dispatchTouchEvent(e2)
-                    } else {
-                        // For consistency, we adopt the expansion definition found in the
-                        // PanelViewController. In this case, expansion refers to the view above the
-                        // bouncer. As that view's expansion shrinks, the bouncer appears. The
-                        // bouncer
-                        // is fully hidden at full expansion (1) and fully visible when fully
-                        // collapsed
-                        // (0).
-                        touchSession?.apply {
-                            val screenTravelPercentage =
-                                (abs((this@outer.y - e2.y).toDouble()) / getBounds().height())
-                                    .toFloat()
-                            setPanelExpansion(1 - screenTravelPercentage)
-                        }
+                    // For consistency, we adopt the expansion definition found in the
+                    // PanelViewController. In this case, expansion refers to the view above the
+                    // bouncer. As that view's expansion shrinks, the bouncer appears. The bouncer
+                    // is fully hidden at full expansion (1) and fully visible when fully collapsed
+                    // (0).
+                    touchSession?.apply {
+                        val screenTravelPercentage =
+                            (abs((this@outer.y - e2.y).toDouble()) / getBounds().height()).toFloat()
+                        setPanelExpansion(1 - screenTravelPercentage)
                     }
                 }
 
@@ -214,7 +194,7 @@
             ShadeExpansionChangeEvent(
                 /* fraction= */ currentExpansion,
                 /* expanded= */ expanded,
-                /* tracking= */ true,
+                /* tracking= */ true
             )
         currentScrimController?.expand(event)
     }
@@ -367,7 +347,7 @@
                     currentHeight,
                     targetHeight,
                     velocity,
-                    viewHeight,
+                    viewHeight
                 )
             } else {
                 // Shows the bouncer, i.e., fully collapses the space above the bouncer.
@@ -376,7 +356,7 @@
                     currentHeight,
                     targetHeight,
                     velocity,
-                    viewHeight,
+                    viewHeight
                 )
             }
             animator.start()
diff --git a/packages/SystemUI/src/com/android/systemui/ambient/touch/ShadeTouchHandler.kt b/packages/SystemUI/src/com/android/systemui/ambient/touch/ShadeTouchHandler.kt
index 05a1009..1951a23 100644
--- a/packages/SystemUI/src/com/android/systemui/ambient/touch/ShadeTouchHandler.kt
+++ b/packages/SystemUI/src/com/android/systemui/ambient/touch/ShadeTouchHandler.kt
@@ -27,15 +27,11 @@
 import com.android.systemui.ambient.touch.dagger.ShadeModule
 import com.android.systemui.communal.domain.interactor.CommunalSettingsInteractor
 import com.android.systemui.communal.ui.viewmodel.CommunalViewModel
-import com.android.systemui.scene.domain.interactor.SceneInteractor
-import com.android.systemui.scene.shared.flag.SceneContainerFlag
-import com.android.systemui.scene.ui.view.WindowRootView
 import com.android.systemui.shade.ShadeViewController
 import com.android.systemui.statusbar.phone.CentralSurfaces
 import java.util.Optional
 import javax.inject.Inject
 import javax.inject.Named
-import javax.inject.Provider
 import kotlin.math.abs
 import kotlinx.coroutines.CoroutineScope
 import com.android.app.tracing.coroutines.launchTraced as launch
@@ -53,10 +49,8 @@
     private val dreamManager: DreamManager,
     private val communalViewModel: CommunalViewModel,
     private val communalSettingsInteractor: CommunalSettingsInteractor,
-    private val sceneInteractor: SceneInteractor,
-    private val windowRootViewProvider: Optional<Provider<WindowRootView>>,
     @param:Named(ShadeModule.NOTIFICATION_SHADE_GESTURE_INITIATION_HEIGHT)
-    private val initiationHeight: Int,
+    private val initiationHeight: Int
 ) : TouchHandler {
     /**
      * Tracks whether or not we are capturing a given touch. Will be null before and after a touch.
@@ -66,8 +60,6 @@
     /** Determines whether the touch handler should process touches in fullscreen swiping mode */
     private var touchAvailable = false
 
-    private val windowRootView by lazy { windowRootViewProvider.get().get() }
-
     init {
         if (Flags.hubmodeFullscreenVerticalSwipeFix()) {
             scope.launch {
@@ -108,7 +100,7 @@
                     e1: MotionEvent?,
                     e2: MotionEvent,
                     distanceX: Float,
-                    distanceY: Float,
+                    distanceY: Float
                 ): Boolean {
                     if (capture == null) {
                         // Only capture swipes that are going downwards.
@@ -118,10 +110,6 @@
                                 if (Flags.hubmodeFullscreenVerticalSwipeFix()) touchAvailable
                                 else true
                         if (capture == true) {
-                            if (SceneContainerFlag.isEnabled) {
-                                sceneInteractor.onRemoteUserInputStarted("shade touch handler")
-                            }
-
                             // Send the initial touches over, as the input listener has already
                             // processed these touches.
                             e1?.apply { sendTouchEvent(this) }
@@ -135,7 +123,7 @@
                     e1: MotionEvent?,
                     e2: MotionEvent,
                     velocityX: Float,
-                    velocityY: Float,
+                    velocityY: Float
                 ): Boolean {
                     return capture == true
                 }
@@ -144,11 +132,6 @@
     }
 
     private fun sendTouchEvent(event: MotionEvent) {
-        if (SceneContainerFlag.isEnabled) {
-            windowRootView.dispatchTouchEvent(event)
-            return
-        }
-
         if (communalSettingsInteractor.isCommunalFlagEnabled() && !dreamManager.isDreaming) {
             // Send touches to central surfaces only when on the glanceable hub while not dreaming.
             // While sending touches where while dreaming will open the shade, the shade
diff --git a/packages/SystemUI/src/com/android/systemui/ambient/touch/dagger/ShadeModule.java b/packages/SystemUI/src/com/android/systemui/ambient/touch/dagger/ShadeModule.java
index 1c781d6..bc2f354 100644
--- a/packages/SystemUI/src/com/android/systemui/ambient/touch/dagger/ShadeModule.java
+++ b/packages/SystemUI/src/com/android/systemui/ambient/touch/dagger/ShadeModule.java
@@ -22,10 +22,8 @@
 import com.android.systemui.ambient.touch.TouchHandler;
 import com.android.systemui.dagger.qualifiers.Main;
 import com.android.systemui.res.R;
-import com.android.systemui.scene.ui.view.WindowRootView;
 
 import dagger.Binds;
-import dagger.BindsOptionalOf;
 import dagger.Module;
 import dagger.Provides;
 import dagger.multibindings.IntoSet;
@@ -53,13 +51,6 @@
             ShadeTouchHandler touchHandler);
 
     /**
-     * Window root view is used to send touches to the scene container. Declaring as optional as it
-     * may not be present on all SysUI variants.
-     */
-    @BindsOptionalOf
-    abstract WindowRootView bindWindowRootView();
-
-    /**
      * Provides the height of the gesture area for notification swipe down.
      */
     @Provides
diff --git a/packages/SystemUI/src/com/android/systemui/communal/domain/interactor/CommunalSceneTransitionInteractor.kt b/packages/SystemUI/src/com/android/systemui/communal/domain/interactor/CommunalSceneTransitionInteractor.kt
index 7b8c19c..ec55401 100644
--- a/packages/SystemUI/src/com/android/systemui/communal/domain/interactor/CommunalSceneTransitionInteractor.kt
+++ b/packages/SystemUI/src/com/android/systemui/communal/domain/interactor/CommunalSceneTransitionInteractor.kt
@@ -73,7 +73,7 @@
     private var progressJob: Job? = null
 
     private val currentToState: KeyguardState
-        get() = internalTransitionInteractor.currentTransitionInfoInternal.value.to
+        get() = internalTransitionInteractor.currentTransitionInfoInternal().to
 
     /**
      * The next keyguard state to trigger when exiting [CommunalScenes.Communal]. This is only used
@@ -197,7 +197,7 @@
         val newTransition =
             TransitionInfo(
                 ownerName = this::class.java.simpleName,
-                from = internalTransitionInteractor.currentTransitionInfoInternal.value.to,
+                from = internalTransitionInteractor.currentTransitionInfoInternal().to,
                 to = state,
                 animator = null,
                 modeOnCanceled = TransitionModeOnCanceled.REVERSE,
@@ -273,7 +273,7 @@
     }
 
     private suspend fun startTransitionToGlanceableHub() {
-        val currentState = internalTransitionInteractor.currentTransitionInfoInternal.value.to
+        val currentState = internalTransitionInteractor.currentTransitionInfoInternal().to
         val newTransition =
             TransitionInfo(
                 ownerName = this::class.java.simpleName,
diff --git a/packages/SystemUI/src/com/android/systemui/dreams/DreamOverlayService.java b/packages/SystemUI/src/com/android/systemui/dreams/DreamOverlayService.java
index 1ffbbd2..7a6ca08 100644
--- a/packages/SystemUI/src/com/android/systemui/dreams/DreamOverlayService.java
+++ b/packages/SystemUI/src/com/android/systemui/dreams/DreamOverlayService.java
@@ -65,6 +65,7 @@
 import com.android.systemui.keyguard.domain.interactor.KeyguardInteractor;
 import com.android.systemui.navigationbar.gestural.domain.GestureInteractor;
 import com.android.systemui.navigationbar.gestural.domain.TaskMatcher;
+import com.android.systemui.scene.shared.flag.SceneContainerFlag;
 import com.android.systemui.shade.ShadeExpansionChangeEvent;
 import com.android.systemui.touch.TouchInsetManager;
 import com.android.systemui.util.concurrency.DelayableExecutor;
@@ -502,10 +503,10 @@
         mDreamOverlayContainerViewController =
                 dreamOverlayComponent.getDreamOverlayContainerViewController();
 
-        // Touch monitor are also used with SceneContainer. See individual touch handlers for
-        // handling of SceneContainer.
-        mTouchMonitor = ambientTouchComponent.getTouchMonitor();
-        mTouchMonitor.init();
+        if (!SceneContainerFlag.isEnabled()) {
+            mTouchMonitor = ambientTouchComponent.getTouchMonitor();
+            mTouchMonitor.init();
+        }
 
         mStateController.setShouldShowComplications(shouldShowComplications());
 
diff --git a/packages/SystemUI/src/com/android/systemui/dreams/dagger/DreamOverlayModule.java b/packages/SystemUI/src/com/android/systemui/dreams/dagger/DreamOverlayModule.java
index 85fb90d..12984efb 100644
--- a/packages/SystemUI/src/com/android/systemui/dreams/dagger/DreamOverlayModule.java
+++ b/packages/SystemUI/src/com/android/systemui/dreams/dagger/DreamOverlayModule.java
@@ -30,10 +30,8 @@
 import com.android.systemui.dagger.qualifiers.Main;
 import com.android.systemui.dreams.DreamOverlayContainerView;
 import com.android.systemui.res.R;
-import com.android.systemui.scene.ui.view.WindowRootView;
 import com.android.systemui.touch.TouchInsetManager;
 
-import dagger.BindsOptionalOf;
 import dagger.Module;
 import dagger.Provides;
 
@@ -56,13 +54,6 @@
     public static final String DREAM_IN_TRANSLATION_Y_DURATION =
             "dream_in_complications_translation_y_duration";
 
-    /**
-     * Window root view is used to send touches to the scene container. Declaring as optional as it
-     * may not be present on all SysUI variants.
-     */
-    @BindsOptionalOf
-    abstract WindowRootView bindWindowRootView();
-
     /** */
     @Provides
     @DreamOverlayComponent.DreamOverlayScope
diff --git a/packages/SystemUI/src/com/android/systemui/dreams/touch/CommunalTouchHandler.java b/packages/SystemUI/src/com/android/systemui/dreams/touch/CommunalTouchHandler.java
index 42a6877..5ba780f 100644
--- a/packages/SystemUI/src/com/android/systemui/dreams/touch/CommunalTouchHandler.java
+++ b/packages/SystemUI/src/com/android/systemui/dreams/touch/CommunalTouchHandler.java
@@ -31,9 +31,6 @@
 import com.android.systemui.common.ui.domain.interactor.ConfigurationInteractor;
 import com.android.systemui.communal.domain.interactor.CommunalInteractor;
 import com.android.systemui.dreams.touch.dagger.CommunalTouchModule;
-import com.android.systemui.scene.domain.interactor.SceneInteractor;
-import com.android.systemui.scene.shared.flag.SceneContainerFlag;
-import com.android.systemui.scene.ui.view.WindowRootView;
 import com.android.systemui.statusbar.phone.CentralSurfaces;
 
 import kotlinx.coroutines.Job;
@@ -45,7 +42,6 @@
 
 import javax.inject.Inject;
 import javax.inject.Named;
-import javax.inject.Provider;
 
 /** {@link TouchHandler} responsible for handling touches to open communal hub. **/
 public class CommunalTouchHandler implements TouchHandler {
@@ -55,8 +51,6 @@
     private final CommunalInteractor mCommunalInteractor;
 
     private final ConfigurationInteractor mConfigurationInteractor;
-    private final SceneInteractor mSceneInteractor;
-    private final WindowRootView mWindowRootView;
     private Boolean mIsEnabled = false;
 
     private ArrayList<Job> mFlows = new ArrayList<>();
@@ -75,16 +69,12 @@
             @Named(CommunalTouchModule.COMMUNAL_GESTURE_INITIATION_WIDTH) int initiationWidth,
             CommunalInteractor communalInteractor,
             ConfigurationInteractor configurationInteractor,
-            SceneInteractor sceneInteractor,
-            Optional<Provider<WindowRootView>> windowRootViewProvider,
             Lifecycle lifecycle) {
         mInitiationWidth = initiationWidth;
         mCentralSurfaces = centralSurfaces;
         mLifecycle = lifecycle;
         mCommunalInteractor = communalInteractor;
         mConfigurationInteractor = configurationInteractor;
-        mSceneInteractor = sceneInteractor;
-        mWindowRootView = windowRootViewProvider.get().get();
 
         mFlows.add(collectFlow(
                 mLifecycle,
@@ -135,15 +125,8 @@
     private void handleSessionStart(CentralSurfaces surfaces, TouchSession session) {
         // Notification shade window has its own logic to be visible if the hub is open, no need to
         // do anything here other than send touch events over.
-        if (SceneContainerFlag.isEnabled()) {
-            mSceneInteractor.onRemoteUserInputStarted("communal touch handler");
-        }
         session.registerInputListener(ev -> {
-            if (SceneContainerFlag.isEnabled()) {
-                mWindowRootView.dispatchTouchEvent((MotionEvent) ev);
-            } else {
-                surfaces.handleCommunalHubTouch((MotionEvent) ev);
-            }
+            surfaces.handleCommunalHubTouch((MotionEvent) ev);
             if (ev != null && ((MotionEvent) ev).getAction() == MotionEvent.ACTION_UP) {
                 var unused = session.pop();
             }
diff --git a/packages/SystemUI/src/com/android/systemui/keyguard/data/repository/KeyguardRepository.kt b/packages/SystemUI/src/com/android/systemui/keyguard/data/repository/KeyguardRepository.kt
index bbaa9eb..d3c17cc 100644
--- a/packages/SystemUI/src/com/android/systemui/keyguard/data/repository/KeyguardRepository.kt
+++ b/packages/SystemUI/src/com/android/systemui/keyguard/data/repository/KeyguardRepository.kt
@@ -269,6 +269,8 @@
     /** The top of shortcut in screen, used by wallpaper to find remaining space in lockscreen */
     val shortcutAbsoluteTop: StateFlow<Float>
 
+    val notificationStackAbsoluteBottom: StateFlow<Float>
+
     /**
      * Returns `true` if the keyguard is showing; `false` otherwise.
      *
@@ -339,6 +341,12 @@
     fun isShowKeyguardWhenReenabled(): Boolean
 
     fun setShortcutAbsoluteTop(top: Float)
+
+    /**
+     * Set bottom of notifications from notification stack, and Magic Portrait will layout base on
+     * this value
+     */
+    fun setNotificationStackAbsoluteBottom(bottom: Float)
 }
 
 /** Encapsulates application state for the keyguard. */
@@ -635,6 +643,9 @@
     private val _shortcutAbsoluteTop = MutableStateFlow(0F)
     override val shortcutAbsoluteTop = _shortcutAbsoluteTop.asStateFlow()
 
+    private val _notificationStackAbsoluteBottom = MutableStateFlow(0F)
+    override val notificationStackAbsoluteBottom = _notificationStackAbsoluteBottom.asStateFlow()
+
     init {
         val callback =
             object : KeyguardStateController.Callback {
@@ -717,6 +728,10 @@
         _shortcutAbsoluteTop.value = top
     }
 
+    override fun setNotificationStackAbsoluteBottom(bottom: Float) {
+        _notificationStackAbsoluteBottom.value = bottom
+    }
+
     private fun dozeMachineStateToModel(state: DozeMachine.State): DozeStateModel {
         return when (state) {
             DozeMachine.State.UNINITIALIZED -> DozeStateModel.UNINITIALIZED
diff --git a/packages/SystemUI/src/com/android/systemui/keyguard/data/repository/KeyguardTransitionRepository.kt b/packages/SystemUI/src/com/android/systemui/keyguard/data/repository/KeyguardTransitionRepository.kt
index b7d0d45..3a5614f 100644
--- a/packages/SystemUI/src/com/android/systemui/keyguard/data/repository/KeyguardTransitionRepository.kt
+++ b/packages/SystemUI/src/com/android/systemui/keyguard/data/repository/KeyguardTransitionRepository.kt
@@ -25,6 +25,7 @@
 import android.util.Log
 import com.android.app.animation.Interpolators
 import com.android.app.tracing.coroutines.withContextTraced as withContext
+import com.android.systemui.Flags.transitionRaceCondition
 import com.android.systemui.dagger.SysUISingleton
 import com.android.systemui.dagger.qualifiers.Main
 import com.android.systemui.keyguard.shared.model.KeyguardState
@@ -77,6 +78,8 @@
 
     /** The [TransitionInfo] of the most recent call to [startTransition]. */
     val currentTransitionInfoInternal: StateFlow<TransitionInfo>
+    /** The [TransitionInfo] of the most recent call to [startTransition]. */
+    val currentTransitionInfo: TransitionInfo
 
     /**
      * Interactors that require information about changes between [KeyguardState]s will call this to
@@ -132,7 +135,7 @@
     private var lastStep: TransitionStep = TransitionStep()
     private var lastAnimator: ValueAnimator? = null
 
-    private val _currentTransitionMutex = Mutex()
+    private val withContextMutex = Mutex()
     private val _currentTransitionInfo: MutableStateFlow<TransitionInfo> =
         MutableStateFlow(
             TransitionInfo(
@@ -144,6 +147,16 @@
         )
     override var currentTransitionInfoInternal = _currentTransitionInfo.asStateFlow()
 
+    @Volatile
+    override var currentTransitionInfo: TransitionInfo =
+        TransitionInfo(
+            ownerName = "",
+            from = KeyguardState.OFF,
+            to = KeyguardState.OFF,
+            animator = null,
+        )
+        private set
+
     /*
      * When manual control of the transition is requested, a unique [UUID] is used as the handle
      * to permit calls to [updateTransition]
@@ -163,13 +176,17 @@
     }
 
     override suspend fun startTransition(info: TransitionInfo): UUID? {
-        _currentTransitionInfo.value = info
+        if (transitionRaceCondition()) {
+            currentTransitionInfo = info
+        } else {
+            _currentTransitionInfo.value = info
+        }
         Log.d(TAG, "(Internal) Setting current transition info: $info")
 
         // There is no fairness guarantee with 'withContext', which means that transitions could
         // be processed out of order. Use a Mutex to guarantee ordering. [updateTransition]
         // requires the same lock
-        _currentTransitionMutex.lock()
+        withContextMutex.lock()
         // Only used in a test environment
         if (forceDelayForRaceConditionTest) {
             delay(50L)
@@ -177,7 +194,7 @@
 
         // Animators must be started on the main thread.
         return withContext("$TAG#startTransition", mainDispatcher) {
-            _currentTransitionMutex.unlock()
+            withContextMutex.unlock()
             if (lastStep.from == info.from && lastStep.to == info.to) {
                 Log.i(TAG, "Duplicate call to start the transition, rejecting: $info")
                 return@withContext null
@@ -265,9 +282,9 @@
         // There is no fairness guarantee with 'withContext', which means that transitions could
         // be processed out of order. Use a Mutex to guarantee ordering. [startTransition]
         // requires the same lock
-        _currentTransitionMutex.lock()
+        withContextMutex.lock()
         withContext("$TAG#updateTransition", mainDispatcher) {
-            _currentTransitionMutex.unlock()
+            withContextMutex.unlock()
 
             updateTransitionInternal(transitionId, value, state)
         }
@@ -302,13 +319,23 @@
         // Tests runs on testDispatcher, which is not the main thread, causing the animator thread
         // check to fail
         if (testSetup) {
-            _currentTransitionInfo.value =
-                TransitionInfo(
-                    ownerName = ownerName,
-                    from = KeyguardState.OFF,
-                    to = to,
-                    animator = null,
-                )
+            if (transitionRaceCondition()) {
+                currentTransitionInfo =
+                    TransitionInfo(
+                        ownerName = ownerName,
+                        from = KeyguardState.OFF,
+                        to = to,
+                        animator = null,
+                    )
+            } else {
+                _currentTransitionInfo.value =
+                    TransitionInfo(
+                        ownerName = ownerName,
+                        from = KeyguardState.OFF,
+                        to = to,
+                        animator = null,
+                    )
+            }
             emitTransition(
                 TransitionStep(
                     KeyguardState.OFF,
diff --git a/packages/SystemUI/src/com/android/systemui/keyguard/domain/interactor/FromLockscreenTransitionInteractor.kt b/packages/SystemUI/src/com/android/systemui/keyguard/domain/interactor/FromLockscreenTransitionInteractor.kt
index a7dde34e..8b75545 100644
--- a/packages/SystemUI/src/com/android/systemui/keyguard/domain/interactor/FromLockscreenTransitionInteractor.kt
+++ b/packages/SystemUI/src/com/android/systemui/keyguard/domain/interactor/FromLockscreenTransitionInteractor.kt
@@ -40,6 +40,7 @@
 import com.android.systemui.scene.shared.model.Scenes
 import com.android.systemui.shade.data.repository.ShadeRepository
 import com.android.systemui.util.kotlin.Utils.Companion.sample as sampleCombine
+import com.android.systemui.util.kotlin.sample
 import java.util.UUID
 import javax.inject.Inject
 import kotlin.time.Duration.Companion.milliseconds
@@ -132,11 +133,10 @@
         scope.launch("$TAG#listenForLockscreenToDreaming") {
             keyguardInteractor.isAbleToDream
                 .filterRelevantKeyguardState()
-                .sampleCombine(
-                    internalTransitionInteractor.currentTransitionInfoInternal,
-                    transitionInteractor.isFinishedIn(KeyguardState.LOCKSCREEN),
-                )
-                .collect { (isAbleToDream, transitionInfo, isOnLockscreen) ->
+                .sample(transitionInteractor.isFinishedIn(KeyguardState.LOCKSCREEN), ::Pair)
+                .collect { (isAbleToDream, isOnLockscreen) ->
+                    val transitionInfo =
+                        internalTransitionInteractor.currentTransitionInfoInternal()
                     val isTransitionInterruptible =
                         transitionInfo.to == KeyguardState.LOCKSCREEN &&
                             !invalidFromStates.contains(transitionInfo.from)
@@ -179,7 +179,6 @@
             shadeRepository.legacyShadeExpansion
                 .sampleCombine(
                     transitionInteractor.startedKeyguardTransitionStep,
-                    internalTransitionInteractor.currentTransitionInfoInternal,
                     keyguardInteractor.statusBarState,
                     keyguardInteractor.isKeyguardDismissible,
                     keyguardInteractor.isKeyguardOccluded,
@@ -188,11 +187,12 @@
                     (
                         shadeExpansion,
                         startedStep,
-                        currentTransitionInfo,
                         statusBarState,
                         isKeyguardUnlocked,
                         isKeyguardOccluded) ->
                     val id = transitionId
+                    val currentTransitionInfo =
+                        internalTransitionInteractor.currentTransitionInfoInternal()
                     if (id != null) {
                         if (startedStep.to == KeyguardState.PRIMARY_BOUNCER) {
                             // An existing `id` means a transition is started, and calls to
diff --git a/packages/SystemUI/src/com/android/systemui/keyguard/domain/interactor/InternalKeyguardTransitionInteractor.kt b/packages/SystemUI/src/com/android/systemui/keyguard/domain/interactor/InternalKeyguardTransitionInteractor.kt
index 2cc6afa..0507834 100644
--- a/packages/SystemUI/src/com/android/systemui/keyguard/domain/interactor/InternalKeyguardTransitionInteractor.kt
+++ b/packages/SystemUI/src/com/android/systemui/keyguard/domain/interactor/InternalKeyguardTransitionInteractor.kt
@@ -17,13 +17,13 @@
 package com.android.systemui.keyguard.domain.interactor
 
 import android.annotation.FloatRange
+import com.android.systemui.Flags.transitionRaceCondition
 import com.android.systemui.dagger.SysUISingleton
 import com.android.systemui.keyguard.data.repository.KeyguardTransitionRepository
 import com.android.systemui.keyguard.shared.model.TransitionInfo
 import com.android.systemui.keyguard.shared.model.TransitionState
 import java.util.UUID
 import javax.inject.Inject
-import kotlinx.coroutines.flow.StateFlow
 
 /**
  * This interactor provides direct access to [KeyguardTransitionRepository] internals and exposes
@@ -32,9 +32,7 @@
 @SysUISingleton
 class InternalKeyguardTransitionInteractor
 @Inject
-constructor(
-    private val repository: KeyguardTransitionRepository,
-) {
+constructor(private val repository: KeyguardTransitionRepository) {
 
     /**
      * The [TransitionInfo] of the most recent call to
@@ -58,14 +56,19 @@
      * *will* be emitted, and therefore that it can safely request an AOD -> LOCKSCREEN transition
      * which will subsequently cancel GONE -> AOD.
      */
-    internal val currentTransitionInfoInternal: StateFlow<TransitionInfo> =
-        repository.currentTransitionInfoInternal
+    internal fun currentTransitionInfoInternal(): TransitionInfo {
+        return if (transitionRaceCondition()) {
+            repository.currentTransitionInfo
+        } else {
+            repository.currentTransitionInfoInternal.value
+        }
+    }
 
     suspend fun startTransition(info: TransitionInfo) = repository.startTransition(info)
 
     suspend fun updateTransition(
         transitionId: UUID,
         @FloatRange(from = 0.0, to = 1.0) value: Float,
-        state: TransitionState
+        state: TransitionState,
     ) = repository.updateTransition(transitionId, value, state)
 }
diff --git a/packages/SystemUI/src/com/android/systemui/keyguard/domain/interactor/KeyguardDismissTransitionInteractor.kt b/packages/SystemUI/src/com/android/systemui/keyguard/domain/interactor/KeyguardDismissTransitionInteractor.kt
index c19bbbc..4793d95 100644
--- a/packages/SystemUI/src/com/android/systemui/keyguard/domain/interactor/KeyguardDismissTransitionInteractor.kt
+++ b/packages/SystemUI/src/com/android/systemui/keyguard/domain/interactor/KeyguardDismissTransitionInteractor.kt
@@ -17,6 +17,7 @@
 package com.android.systemui.keyguard.domain.interactor
 
 import android.util.Log
+import com.android.systemui.Flags.transitionRaceCondition
 import com.android.systemui.dagger.SysUISingleton
 import com.android.systemui.keyguard.data.repository.KeyguardTransitionRepository
 import com.android.systemui.keyguard.shared.model.KeyguardState
@@ -51,7 +52,13 @@
     fun startDismissKeyguardTransition(reason: String = "") {
         if (SceneContainerFlag.isEnabled) return
         Log.d(TAG, "#startDismissKeyguardTransition(reason=$reason)")
-        when (val startedState = repository.currentTransitionInfoInternal.value.to) {
+        val startedState =
+            if (transitionRaceCondition()) {
+                repository.currentTransitionInfo.to
+            } else {
+                repository.currentTransitionInfoInternal.value.to
+            }
+        when (startedState) {
             LOCKSCREEN -> fromLockscreenTransitionInteractor.dismissKeyguard()
             PRIMARY_BOUNCER -> fromPrimaryBouncerTransitionInteractor.dismissPrimaryBouncer()
             ALTERNATE_BOUNCER -> fromAlternateBouncerTransitionInteractor.dismissAlternateBouncer()
@@ -61,7 +68,7 @@
             KeyguardState.GONE ->
                 Log.i(
                     TAG,
-                    "Already transitioning to GONE; ignoring startDismissKeyguardTransition."
+                    "Already transitioning to GONE; ignoring startDismissKeyguardTransition.",
                 )
             else -> Log.e(TAG, "We don't know how to dismiss keyguard from state $startedState.")
         }
diff --git a/packages/SystemUI/src/com/android/systemui/keyguard/domain/interactor/KeyguardEnabledInteractor.kt b/packages/SystemUI/src/com/android/systemui/keyguard/domain/interactor/KeyguardEnabledInteractor.kt
index 5f08aa3..631e44a 100644
--- a/packages/SystemUI/src/com/android/systemui/keyguard/domain/interactor/KeyguardEnabledInteractor.kt
+++ b/packages/SystemUI/src/com/android/systemui/keyguard/domain/interactor/KeyguardEnabledInteractor.kt
@@ -22,7 +22,7 @@
 import com.android.systemui.keyguard.data.repository.KeyguardRepository
 import com.android.systemui.keyguard.shared.model.KeyguardState
 import com.android.systemui.scene.shared.flag.SceneContainerFlag
-import com.android.systemui.util.kotlin.Utils.Companion.sample as sampleCombine
+import com.android.systemui.util.kotlin.sample
 import javax.inject.Inject
 import kotlinx.coroutines.CoroutineScope
 import kotlinx.coroutines.flow.Flow
@@ -74,11 +74,9 @@
             .onEach { SceneContainerFlag.assertInLegacyMode() }
             // Whenever the keyguard is disabled...
             .filter { enabled -> !enabled }
-            .sampleCombine(
-                internalTransitionInteractor.currentTransitionInfoInternal,
-                biometricSettingsRepository.isCurrentUserInLockdown,
-            )
-            .map { (_, transitionInfo, inLockdown) ->
+            .sample(biometricSettingsRepository.isCurrentUserInLockdown, ::Pair)
+            .map { (_, inLockdown) ->
+                val transitionInfo = internalTransitionInteractor.currentTransitionInfoInternal()
                 // ...we hide the keyguard, if it's showing and we're not in lockdown. In that case,
                 // we want to remember that and re-show it when keyguard is enabled again.
                 transitionInfo.to != KeyguardState.GONE && !inLockdown
@@ -93,11 +91,10 @@
             if (!SceneContainerFlag.isEnabled) {
                 repository.isKeyguardEnabled
                     .filter { enabled -> !enabled }
-                    .sampleCombine(
-                        biometricSettingsRepository.isCurrentUserInLockdown,
-                        internalTransitionInteractor.currentTransitionInfoInternal,
-                    )
-                    .collect { (_, inLockdown, currentTransitionInfo) ->
+                    .sample(biometricSettingsRepository.isCurrentUserInLockdown, ::Pair)
+                    .collect { (_, inLockdown) ->
+                        val currentTransitionInfo =
+                            internalTransitionInteractor.currentTransitionInfoInternal()
                         if (currentTransitionInfo.to != KeyguardState.GONE && !inLockdown) {
                             keyguardDismissTransitionInteractor.startDismissKeyguardTransition(
                                 "keyguard disabled"
diff --git a/packages/SystemUI/src/com/android/systemui/keyguard/domain/interactor/KeyguardInteractor.kt b/packages/SystemUI/src/com/android/systemui/keyguard/domain/interactor/KeyguardInteractor.kt
index 29c6d5a..b24ca1a 100644
--- a/packages/SystemUI/src/com/android/systemui/keyguard/domain/interactor/KeyguardInteractor.kt
+++ b/packages/SystemUI/src/com/android/systemui/keyguard/domain/interactor/KeyguardInteractor.kt
@@ -545,6 +545,10 @@
         repository.isKeyguardGoingAway.value = isGoingAway
     }
 
+    fun setNotificationStackAbsoluteBottom(bottom: Float) {
+        repository.setNotificationStackAbsoluteBottom(bottom)
+    }
+
     companion object {
         private const val TAG = "KeyguardInteractor"
     }
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 7f1e881..278a98f 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
@@ -80,7 +80,7 @@
         // *_BOUNCER -> LOCKSCREEN.
         return powerInteractor.detailedWakefulness.value.powerButtonLaunchGestureTriggered &&
             KeyguardState.deviceIsAsleepInState(
-                internalTransitionInteractor.currentTransitionInfoInternal.value.to
+                internalTransitionInteractor.currentTransitionInfoInternal().to
             )
     }
 
@@ -100,13 +100,13 @@
                             scene = Scenes.Gone,
                             stateWithoutSceneContainer = KeyguardState.GONE,
                         ),
-                        ::Pair
+                        ::Pair,
                     )
                     .map { (wakefulness, isOnGone) ->
                         wakefulness.powerButtonLaunchGestureTriggered && !isOnGone
                     },
                 // Emit false once that activity goes away.
-                isShowWhenLockedActivityOnTop.filter { !it }.map { false }
+                isShowWhenLockedActivityOnTop.filter { !it }.map { false },
             )
             .stateIn(applicationScope, SharingStarted.Eagerly, false)
 
@@ -134,7 +134,7 @@
      */
     fun setWmNotifiedShowWhenLockedActivityOnTop(
         showWhenLockedActivityOnTop: Boolean,
-        taskInfo: RunningTaskInfo? = null
+        taskInfo: RunningTaskInfo? = null,
     ) {
         repository.setShowWhenLockedActivityInfo(showWhenLockedActivityOnTop, taskInfo)
     }
diff --git a/packages/SystemUI/src/com/android/systemui/keyguard/domain/interactor/KeyguardTransitionBootInteractor.kt b/packages/SystemUI/src/com/android/systemui/keyguard/domain/interactor/KeyguardTransitionBootInteractor.kt
index cddeaaf..b986d56 100644
--- a/packages/SystemUI/src/com/android/systemui/keyguard/domain/interactor/KeyguardTransitionBootInteractor.kt
+++ b/packages/SystemUI/src/com/android/systemui/keyguard/domain/interactor/KeyguardTransitionBootInteractor.kt
@@ -61,7 +61,7 @@
 
     fun start() {
         scope.launch {
-            if (internalTransitionInteractor.currentTransitionInfoInternal.value.from != OFF) {
+            if (internalTransitionInteractor.currentTransitionInfoInternal().from != OFF) {
                 Log.e(
                     "KeyguardTransitionInteractor",
                     "showLockscreenOnBoot emitted, but we've already " +
diff --git a/packages/SystemUI/src/com/android/systemui/keyguard/domain/interactor/TransitionInteractor.kt b/packages/SystemUI/src/com/android/systemui/keyguard/domain/interactor/TransitionInteractor.kt
index 249982d..abd7f90 100644
--- a/packages/SystemUI/src/com/android/systemui/keyguard/domain/interactor/TransitionInteractor.kt
+++ b/packages/SystemUI/src/com/android/systemui/keyguard/domain/interactor/TransitionInteractor.kt
@@ -71,14 +71,14 @@
         ownerReason: String = "",
     ): UUID? {
         toState.checkValidState()
-        if (fromState != internalTransitionInteractor.currentTransitionInfoInternal.value.to) {
+        if (fromState != internalTransitionInteractor.currentTransitionInfoInternal().to) {
             Log.e(
                 name,
                 "Ignoring startTransition: This interactor asked to transition from " +
                     "$fromState -> $toState, but we last transitioned to " +
-                    "${internalTransitionInteractor.currentTransitionInfoInternal.value.to}, not" +
+                    "${internalTransitionInteractor.currentTransitionInfoInternal().to}, not" +
                     " $fromState. This should never happen - check currentTransitionInfoInternal" +
-                    " or use filterRelevantKeyguardState before starting transitions."
+                    " or use filterRelevantKeyguardState before starting transitions.",
             )
             return null
         }
@@ -149,7 +149,7 @@
             if (keyguardInteractor.isKeyguardDismissible.value) {
                 startTransitionTo(
                     KeyguardState.GONE,
-                    ownerReason = "Power button gesture while keyguard is dismissible"
+                    ownerReason = "Power button gesture while keyguard is dismissible",
                 )
 
                 return true
@@ -159,7 +159,7 @@
                 // should transition to GONE.
                 startTransitionTo(
                     KeyguardState.GONE,
-                    ownerReason = "Power button gesture on dismissable keyguard"
+                    ownerReason = "Power button gesture on dismissable keyguard",
                 )
 
                 return true
@@ -190,16 +190,13 @@
                 startTransitionTo(
                     toState = keyguardInteractor.asleepKeyguardState.value,
                     modeOnCanceled = modeOnCanceled,
-                    ownerReason = "Sleep transition triggered"
+                    ownerReason = "Sleep transition triggered",
                 )
             }
     }
 
     /** This signal may come in before the occlusion signal, and can provide a custom transition */
-    fun listenForTransitionToCamera(
-        scope: CoroutineScope,
-        keyguardInteractor: KeyguardInteractor,
-    ) {
+    fun listenForTransitionToCamera(scope: CoroutineScope, keyguardInteractor: KeyguardInteractor) {
         if (!KeyguardWmStateRefactor.isEnabled) {
             scope.launch {
                 keyguardInteractor.onCameraLaunchDetected.filterRelevantKeyguardState().collect {
@@ -223,7 +220,7 @@
      * [startedKeyguardState] as it does not wait for the emission of the first STARTED step.
      */
     fun inOrTransitioningToRelevantKeyguardState(): Boolean {
-        return internalTransitionInteractor.currentTransitionInfoInternal.value.to == fromState
+        return internalTransitionInteractor.currentTransitionInfoInternal().to == fromState
     }
 
     /**
diff --git a/packages/SystemUI/src/com/android/systemui/keyguard/domain/interactor/WindowManagerLockscreenVisibilityInteractor.kt b/packages/SystemUI/src/com/android/systemui/keyguard/domain/interactor/WindowManagerLockscreenVisibilityInteractor.kt
index a09cd7c..a1f6067 100644
--- a/packages/SystemUI/src/com/android/systemui/keyguard/domain/interactor/WindowManagerLockscreenVisibilityInteractor.kt
+++ b/packages/SystemUI/src/com/android/systemui/keyguard/domain/interactor/WindowManagerLockscreenVisibilityInteractor.kt
@@ -19,6 +19,7 @@
 package com.android.systemui.keyguard.domain.interactor
 
 import com.android.compose.animation.scene.ObservableTransitionState
+import com.android.systemui.Flags.transitionRaceCondition
 import com.android.systemui.dagger.SysUISingleton
 import com.android.systemui.deviceentry.domain.interactor.DeviceEntryInteractor
 import com.android.systemui.keyguard.data.repository.KeyguardTransitionRepository
@@ -65,7 +66,7 @@
         combine(
             transitionInteractor.isFinishedIn(
                 scene = Scenes.Gone,
-                stateWithoutSceneContainer = KeyguardState.GONE
+                stateWithoutSceneContainer = KeyguardState.GONE,
             ),
             wakeToGoneInteractor.canWakeDirectlyToGone,
         ) { isOnGone, canWakeDirectlyToGone ->
@@ -197,11 +198,11 @@
             combine(
                     transitionInteractor.isInTransition(
                         edge = Edge.create(to = Scenes.Gone),
-                        edgeWithoutSceneContainer = Edge.create(to = KeyguardState.GONE)
+                        edgeWithoutSceneContainer = Edge.create(to = KeyguardState.GONE),
                     ),
                     transitionInteractor.isFinishedIn(
                         scene = Scenes.Gone,
-                        stateWithoutSceneContainer = KeyguardState.GONE
+                        stateWithoutSceneContainer = KeyguardState.GONE,
                     ),
                     surfaceBehindInteractor.isAnimatingSurface,
                     notificationLaunchAnimationInteractor.isLaunchAnimationRunning,
@@ -231,7 +232,7 @@
             combine(
                     transitionInteractor.currentKeyguardState,
                     wakeToGoneInteractor.canWakeDirectlyToGone,
-                    ::Pair
+                    ::Pair,
                 )
                 .sample(transitionInteractor.startedStepWithPrecedingStep, ::toTriple)
                 .map { (currentState, canWakeDirectlyToGone, startedWithPrev) ->
@@ -242,7 +243,12 @@
                             startedFromStep.transitionState == TransitionState.CANCELED &&
                             startedFromStep.from == KeyguardState.GONE
 
-                    val transitionInfo = transitionRepository.currentTransitionInfoInternal.value
+                    val transitionInfo =
+                        if (transitionRaceCondition()) {
+                            transitionRepository.currentTransitionInfo
+                        } else {
+                            transitionRepository.currentTransitionInfoInternal.value
+                        }
                     val wakingDirectlyToGone =
                         deviceIsAsleepInState(transitionInfo.from) &&
                             transitionInfo.to == KeyguardState.GONE
diff --git a/packages/SystemUI/src/com/android/systemui/keyguard/domain/interactor/scenetransition/LockscreenSceneTransitionInteractor.kt b/packages/SystemUI/src/com/android/systemui/keyguard/domain/interactor/scenetransition/LockscreenSceneTransitionInteractor.kt
index 5524b20..aa44b6d 100644
--- a/packages/SystemUI/src/com/android/systemui/keyguard/domain/interactor/scenetransition/LockscreenSceneTransitionInteractor.kt
+++ b/packages/SystemUI/src/com/android/systemui/keyguard/domain/interactor/scenetransition/LockscreenSceneTransitionInteractor.kt
@@ -106,7 +106,7 @@
 
     private suspend fun handleIdle(
         prevTransition: ObservableTransitionState,
-        idle: ObservableTransitionState.Idle
+        idle: ObservableTransitionState.Idle,
     ) {
         if (currentTransitionId == null) return
         if (prevTransition !is ObservableTransitionState.Transition) return
@@ -133,10 +133,10 @@
         val newTransition =
             TransitionInfo(
                 ownerName = this::class.java.simpleName,
-                from = internalTransitionInteractor.currentTransitionInfoInternal.value.to,
+                from = internalTransitionInteractor.currentTransitionInfoInternal().to,
                 to = state,
                 animator = null,
-                modeOnCanceled = TransitionModeOnCanceled.REVERSE
+                modeOnCanceled = TransitionModeOnCanceled.REVERSE,
             )
         currentTransitionId = internalTransitionInteractor.startTransition(newTransition)
         internalTransitionInteractor.updateTransition(currentTransitionId!!, 1f, FINISHED)
@@ -152,8 +152,7 @@
     private suspend fun handleTransition(transition: ObservableTransitionState.Transition) {
         if (transition.fromContent == Scenes.Lockscreen) {
             if (currentTransitionId != null) {
-                val currentToState =
-                    internalTransitionInteractor.currentTransitionInfoInternal.value.to
+                val currentToState = internalTransitionInteractor.currentTransitionInfoInternal().to
                 if (currentToState == UNDEFINED) {
                     transitionKtfTo(transitionInteractor.startedKeyguardTransitionStep.value.from)
                 }
@@ -197,21 +196,21 @@
                 from = UNDEFINED,
                 to = repository.nextLockscreenTargetState.value,
                 animator = null,
-                modeOnCanceled = TransitionModeOnCanceled.RESET
+                modeOnCanceled = TransitionModeOnCanceled.RESET,
             )
         repository.nextLockscreenTargetState.value = DEFAULT_STATE
         startTransition(newTransition)
     }
 
     private suspend fun startTransitionFromLockscreen() {
-        val currentState = internalTransitionInteractor.currentTransitionInfoInternal.value.to
+        val currentState = internalTransitionInteractor.currentTransitionInfoInternal().to
         val newTransition =
             TransitionInfo(
                 ownerName = this::class.java.simpleName,
                 from = currentState,
                 to = UNDEFINED,
                 animator = null,
-                modeOnCanceled = TransitionModeOnCanceled.RESET
+                modeOnCanceled = TransitionModeOnCanceled.RESET,
             )
         startTransition(newTransition)
     }
@@ -228,7 +227,7 @@
         internalTransitionInteractor.updateTransition(
             currentTransitionId!!,
             progress.coerceIn(0f, 1f),
-            RUNNING
+            RUNNING,
         )
     }
 }
diff --git a/packages/SystemUI/src/com/android/systemui/keyguard/ui/viewmodel/DozingToOccludedTransitionViewModel.kt b/packages/SystemUI/src/com/android/systemui/keyguard/ui/viewmodel/DozingToOccludedTransitionViewModel.kt
index 12bcc7e..b15cacf 100644
--- a/packages/SystemUI/src/com/android/systemui/keyguard/ui/viewmodel/DozingToOccludedTransitionViewModel.kt
+++ b/packages/SystemUI/src/com/android/systemui/keyguard/ui/viewmodel/DozingToOccludedTransitionViewModel.kt
@@ -35,9 +35,7 @@
 @SysUISingleton
 class DozingToOccludedTransitionViewModel
 @Inject
-constructor(
-    animationFlow: KeyguardTransitionAnimationFlow,
-) : DeviceEntryIconTransition {
+constructor(animationFlow: KeyguardTransitionAnimationFlow) : DeviceEntryIconTransition {
     private val transitionAnimation =
         animationFlow.setup(
             duration = FromAodTransitionInteractor.TO_OCCLUDED_DURATION,
@@ -56,11 +54,7 @@
         var currentAlpha = 0f
         return transitionAnimation.sharedFlow(
             duration = 250.milliseconds,
-            startTime = if (lightRevealMigration()) {
-                100.milliseconds // Wait for the light reveal to "hit" the LS elements.
-            } else {
-                0.milliseconds
-            },
+            startTime = 0.milliseconds,
             onStart = {
                 if (lightRevealMigration()) {
                     currentAlpha = viewState.alpha()
@@ -69,7 +63,6 @@
                 }
             },
             onStep = { MathUtils.lerp(currentAlpha, 0f, it) },
-            onCancel = { 0f },
         )
     }
 
diff --git a/packages/SystemUI/src/com/android/systemui/scene/domain/startable/ScrimStartable.kt b/packages/SystemUI/src/com/android/systemui/scene/domain/startable/ScrimStartable.kt
index 55033f0..1d97034 100644
--- a/packages/SystemUI/src/com/android/systemui/scene/domain/startable/ScrimStartable.kt
+++ b/packages/SystemUI/src/com/android/systemui/scene/domain/startable/ScrimStartable.kt
@@ -185,6 +185,7 @@
                 } else if (isOnKeyguard && !unlocking && isDreaming) {
                     Model(scrimState = ScrimState.DREAMING, unlocking = false)
                 } else {
+                    onKeyguardFadedAway(transitionState.isIdle(Scenes.Gone))
                     Model(scrimState = ScrimState.UNLOCKED, unlocking = unlocking)
                 }
             }
diff --git a/packages/SystemUI/src/com/android/systemui/shade/LargeScreenHeaderHelper.kt b/packages/SystemUI/src/com/android/systemui/shade/LargeScreenHeaderHelper.kt
index c74f038..38de17e 100644
--- a/packages/SystemUI/src/com/android/systemui/shade/LargeScreenHeaderHelper.kt
+++ b/packages/SystemUI/src/com/android/systemui/shade/LargeScreenHeaderHelper.kt
@@ -22,7 +22,7 @@
 import javax.inject.Inject
 import kotlin.math.max
 
-class LargeScreenHeaderHelper @Inject constructor(private val context: Context) {
+class LargeScreenHeaderHelper @Inject constructor(@ShadeDisplayAware private val context: Context) {
 
     fun getLargeScreenHeaderHeight(): Int = getLargeScreenHeaderHeight(context)
 
diff --git a/packages/SystemUI/src/com/android/systemui/shade/NotificationPanelUnfoldAnimationController.kt b/packages/SystemUI/src/com/android/systemui/shade/NotificationPanelUnfoldAnimationController.kt
index f463cb5..6e63446 100644
--- a/packages/SystemUI/src/com/android/systemui/shade/NotificationPanelUnfoldAnimationController.kt
+++ b/packages/SystemUI/src/com/android/systemui/shade/NotificationPanelUnfoldAnimationController.kt
@@ -34,7 +34,7 @@
 class NotificationPanelUnfoldAnimationController
 @Inject
 constructor(
-    private val context: Context,
+    @ShadeDisplayAware private val context: Context,
     statusBarStateController: StatusBarStateController,
     progressProvider: NaturalRotationUnfoldProgressProvider,
 ) {
diff --git a/packages/SystemUI/src/com/android/systemui/shade/NotificationPanelViewController.java b/packages/SystemUI/src/com/android/systemui/shade/NotificationPanelViewController.java
index 67d162b..083cf1f 100644
--- a/packages/SystemUI/src/com/android/systemui/shade/NotificationPanelViewController.java
+++ b/packages/SystemUI/src/com/android/systemui/shade/NotificationPanelViewController.java
@@ -668,7 +668,7 @@
     @Inject
     public NotificationPanelViewController(NotificationPanelView view,
             @Main Handler handler,
-            LayoutInflater layoutInflater,
+            @ShadeDisplayAware LayoutInflater layoutInflater,
             FeatureFlags featureFlags,
             NotificationWakeUpCoordinator coordinator,
             PulseExpansionHandler pulseExpansionHandler,
diff --git a/packages/SystemUI/src/com/android/systemui/shade/NotificationShadeWindowControllerImpl.java b/packages/SystemUI/src/com/android/systemui/shade/NotificationShadeWindowControllerImpl.java
index 5b6696b..24dba59 100644
--- a/packages/SystemUI/src/com/android/systemui/shade/NotificationShadeWindowControllerImpl.java
+++ b/packages/SystemUI/src/com/android/systemui/shade/NotificationShadeWindowControllerImpl.java
@@ -148,7 +148,7 @@
 
     @Inject
     public NotificationShadeWindowControllerImpl(
-            Context context,
+            @ShadeDisplayAware Context context,
             WindowRootViewComponent.Factory windowRootViewComponentFactory,
             ViewCaptureAwareWindowManager viewCaptureAwareWindowManager,
             IActivityManager activityManager,
diff --git a/packages/SystemUI/src/com/android/systemui/shade/QsBatteryModeController.kt b/packages/SystemUI/src/com/android/systemui/shade/QsBatteryModeController.kt
index 91627d6..7a70966 100644
--- a/packages/SystemUI/src/com/android/systemui/shade/QsBatteryModeController.kt
+++ b/packages/SystemUI/src/com/android/systemui/shade/QsBatteryModeController.kt
@@ -14,7 +14,7 @@
 class QsBatteryModeController
 @Inject
 constructor(
-    private val context: Context,
+    @ShadeDisplayAware private val context: Context,
     insetsProviderStore: StatusBarContentInsetsProviderStore,
 ) {
 
diff --git a/packages/SystemUI/src/com/android/systemui/shade/ShadeViewProviderModule.kt b/packages/SystemUI/src/com/android/systemui/shade/ShadeViewProviderModule.kt
index 1918094..15b2270 100644
--- a/packages/SystemUI/src/com/android/systemui/shade/ShadeViewProviderModule.kt
+++ b/packages/SystemUI/src/com/android/systemui/shade/ShadeViewProviderModule.kt
@@ -83,7 +83,7 @@
         @Provides
         @SysUISingleton
         fun providesWindowRootView(
-            layoutInflater: LayoutInflater,
+            @ShadeDisplayAware layoutInflater: LayoutInflater,
             viewModelFactory: SceneContainerViewModel.Factory,
             containerConfigProvider: Provider<SceneContainerConfig>,
             scenesProvider: Provider<Set<@JvmSuppressWildcards Scene>>,
@@ -156,7 +156,7 @@
         @Provides
         fun providesKeyguardBottomAreaView(
             npv: NotificationPanelView,
-            layoutInflater: LayoutInflater,
+            @ShadeDisplayAware layoutInflater: LayoutInflater,
         ): KeyguardBottomAreaView {
             return layoutInflater.inflate(R.layout.keyguard_bottom_area, npv, false)
                 as KeyguardBottomAreaView
diff --git a/packages/SystemUI/src/com/android/systemui/shade/carrier/ShadeCarrierGroupController.java b/packages/SystemUI/src/com/android/systemui/shade/carrier/ShadeCarrierGroupController.java
index a171d33..4b8cc00 100644
--- a/packages/SystemUI/src/com/android/systemui/shade/carrier/ShadeCarrierGroupController.java
+++ b/packages/SystemUI/src/com/android/systemui/shade/carrier/ShadeCarrierGroupController.java
@@ -44,6 +44,7 @@
 import com.android.systemui.dagger.qualifiers.Main;
 import com.android.systemui.plugins.ActivityStarter;
 import com.android.systemui.res.R;
+import com.android.systemui.shade.ShadeDisplayAware;
 import com.android.systemui.statusbar.connectivity.MobileDataIndicators;
 import com.android.systemui.statusbar.connectivity.NetworkController;
 import com.android.systemui.statusbar.connectivity.SignalCallback;
@@ -153,7 +154,7 @@
             ShadeCarrierGroupControllerLogger logger,
             NetworkController networkController,
             CarrierTextManager.Builder carrierTextManagerBuilder,
-            Context context,
+            @ShadeDisplayAware Context context,
             CarrierConfigTracker carrierConfigTracker,
             SlotIndexResolver slotIndexResolver,
             MobileUiAdapter mobileUiAdapter,
@@ -497,7 +498,7 @@
                 ShadeCarrierGroupControllerLogger logger,
                 NetworkController networkController,
                 CarrierTextManager.Builder carrierTextControllerBuilder,
-                Context context,
+                @ShadeDisplayAware Context context,
                 CarrierConfigTracker carrierConfigTracker,
                 SlotIndexResolver slotIndexResolver,
                 MobileUiAdapter mobileUiAdapter,
diff --git a/packages/SystemUI/src/com/android/systemui/shade/data/repository/ShadeRepository.kt b/packages/SystemUI/src/com/android/systemui/shade/data/repository/ShadeRepository.kt
index 193056c..5629938 100644
--- a/packages/SystemUI/src/com/android/systemui/shade/data/repository/ShadeRepository.kt
+++ b/packages/SystemUI/src/com/android/systemui/shade/data/repository/ShadeRepository.kt
@@ -182,7 +182,7 @@
 
 /** Business logic for shade interactions */
 @SysUISingleton
-class ShadeRepositoryImpl @Inject constructor(@Application applicationContext: Context) :
+class ShadeRepositoryImpl @Inject constructor() :
     ShadeRepository {
     private val _qsExpansion = MutableStateFlow(0f)
     @Deprecated("Use ShadeInteractor.qsExpansion instead")
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 6c44c73..e5d08a0 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
@@ -26,6 +26,7 @@
 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.ShadeDisplayAware
 import com.android.systemui.shade.ShadeExpansionStateManager
 import com.android.systemui.shade.TouchLogger.Companion.logTouchesTo
 import com.android.systemui.shade.data.repository.ShadeRepository
@@ -48,7 +49,7 @@
 @Inject
 constructor(
     @Application private val applicationScope: CoroutineScope,
-    @Application private val applicationContext: Context,
+    @ShadeDisplayAware private val context: Context,
     @ShadeTouchLog private val touchLog: LogBuffer,
     private val configurationRepository: ConfigurationRepository,
     private val shadeRepository: ShadeRepository,
@@ -94,7 +95,7 @@
                 // Force initial collection.
                 .onStart { emit(Unit) }
                 .collect {
-                    val resources = applicationContext.resources
+                    val resources = context.resources
                     // The configuration for 'shouldUseSplitNotificationShade' dictates the width of
                     // the shade in both split-shade and dual-shade modes.
                     shadeRepository.setShadeLayoutWide(
diff --git a/packages/SystemUI/src/com/android/systemui/shade/transition/LargeScreenShadeInterpolatorImpl.kt b/packages/SystemUI/src/com/android/systemui/shade/transition/LargeScreenShadeInterpolatorImpl.kt
index 823742a..a5caa09 100644
--- a/packages/SystemUI/src/com/android/systemui/shade/transition/LargeScreenShadeInterpolatorImpl.kt
+++ b/packages/SystemUI/src/com/android/systemui/shade/transition/LargeScreenShadeInterpolatorImpl.kt
@@ -30,7 +30,7 @@
 @Inject
 internal constructor(
     @ShadeDisplayAware configurationController: ConfigurationController,
-    private val context: Context,
+    @ShadeDisplayAware private val context: Context,
     private val splitShadeInterpolator: SplitShadeInterpolator,
     private val portraitShadeInterpolator: LargeScreenPortraitShadeInterpolator,
     private val splitShadeStateController: SplitShadeStateController,
diff --git a/packages/SystemUI/src/com/android/systemui/shade/ui/viewmodel/ShadeHeaderViewModel.kt b/packages/SystemUI/src/com/android/systemui/shade/ui/viewmodel/ShadeHeaderViewModel.kt
index 5be5ccc..45516aa 100644
--- a/packages/SystemUI/src/com/android/systemui/shade/ui/viewmodel/ShadeHeaderViewModel.kt
+++ b/packages/SystemUI/src/com/android/systemui/shade/ui/viewmodel/ShadeHeaderViewModel.kt
@@ -30,6 +30,7 @@
 import com.android.systemui.privacy.PrivacyItem
 import com.android.systemui.res.R
 import com.android.systemui.scene.shared.model.TransitionKeys.SlightlyFasterShadeCollapse
+import com.android.systemui.shade.ShadeDisplayAware
 import com.android.systemui.shade.domain.interactor.PrivacyChipInteractor
 import com.android.systemui.shade.domain.interactor.ShadeHeaderClockInteractor
 import com.android.systemui.shade.domain.interactor.ShadeInteractor
@@ -53,7 +54,7 @@
 class ShadeHeaderViewModel
 @AssistedInject
 constructor(
-    context: Context,
+    @ShadeDisplayAware context: Context,
     private val activityStarter: ActivityStarter,
     private val shadeInteractor: ShadeInteractor,
     private val mobileIconsInteractor: MobileIconsInteractor,
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/RemoteInputNotificationRebuilder.java b/packages/SystemUI/src/com/android/systemui/statusbar/RemoteInputNotificationRebuilder.java
index 321b608..31022d5 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/RemoteInputNotificationRebuilder.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/RemoteInputNotificationRebuilder.java
@@ -128,7 +128,8 @@
 
             // Read the whole remoteInputs list from the entry, then append all of those to the sbn.
             Parcelable[] oldHistoryItems = sbn.getNotification().extras
-                    .getParcelableArray(Notification.EXTRA_REMOTE_INPUT_HISTORY_ITEMS);
+                    .getParcelableArray(Notification.EXTRA_REMOTE_INPUT_HISTORY_ITEMS,
+                            RemoteInputHistoryItem.class);
 
             RemoteInputHistoryItem[] newHistoryItems = oldHistoryItems != null
                     ? Stream.concat(
@@ -144,7 +145,8 @@
                         ? new RemoteInputHistoryItem(mimeType, uri, remoteInputText)
                         : new RemoteInputHistoryItem(remoteInputText);
                 Parcelable[] oldHistoryItems = sbn.getNotification().extras
-                        .getParcelableArray(Notification.EXTRA_REMOTE_INPUT_HISTORY_ITEMS);
+                        .getParcelableArray(Notification.EXTRA_REMOTE_INPUT_HISTORY_ITEMS,
+                                RemoteInputHistoryItem.class);
                 RemoteInputHistoryItem[] newHistoryItems = oldHistoryItems != null
                         ? Stream.concat(
                                 Stream.of(newItem),
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/chips/StatusBarChipsModule.kt b/packages/SystemUI/src/com/android/systemui/statusbar/chips/StatusBarChipsModule.kt
index 8ce0dbf..be733d4 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/chips/StatusBarChipsModule.kt
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/chips/StatusBarChipsModule.kt
@@ -20,7 +20,7 @@
 import com.android.systemui.dagger.SysUISingleton
 import com.android.systemui.log.LogBuffer
 import com.android.systemui.log.LogBufferFactory
-import com.android.systemui.statusbar.chips.notification.demo.ui.viewmodel.DemoNotifChipViewModel
+import com.android.systemui.statusbar.chips.ron.demo.ui.viewmodel.DemoRonChipViewModel
 import dagger.Binds
 import dagger.Module
 import dagger.Provides
@@ -31,8 +31,8 @@
 abstract class StatusBarChipsModule {
     @Binds
     @IntoMap
-    @ClassKey(DemoNotifChipViewModel::class)
-    abstract fun binds(impl: DemoNotifChipViewModel): CoreStartable
+    @ClassKey(DemoRonChipViewModel::class)
+    abstract fun binds(impl: DemoRonChipViewModel): CoreStartable
 
     companion object {
         @Provides
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/chips/notification/demo/ui/viewmodel/DemoNotifChipViewModel.kt b/packages/SystemUI/src/com/android/systemui/statusbar/chips/ron/demo/ui/viewmodel/DemoRonChipViewModel.kt
similarity index 82%
rename from packages/SystemUI/src/com/android/systemui/statusbar/chips/notification/demo/ui/viewmodel/DemoNotifChipViewModel.kt
rename to packages/SystemUI/src/com/android/systemui/statusbar/chips/ron/demo/ui/viewmodel/DemoRonChipViewModel.kt
index 5fa19dd..cce9a16 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/chips/notification/demo/ui/viewmodel/DemoNotifChipViewModel.kt
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/chips/ron/demo/ui/viewmodel/DemoRonChipViewModel.kt
@@ -14,7 +14,7 @@
  * limitations under the License.
  */
 
-package com.android.systemui.statusbar.chips.notification.demo.ui.viewmodel
+package com.android.systemui.statusbar.chips.ron.demo.ui.viewmodel
 
 import android.content.pm.PackageManager
 import android.content.pm.PackageManager.NameNotFoundException
@@ -22,7 +22,7 @@
 import com.android.systemui.CoreStartable
 import com.android.systemui.common.shared.model.Icon
 import com.android.systemui.dagger.SysUISingleton
-import com.android.systemui.statusbar.chips.notification.shared.StatusBarNotifChips
+import com.android.systemui.statusbar.chips.ron.shared.StatusBarRonChips
 import com.android.systemui.statusbar.chips.ui.model.ColorsModel
 import com.android.systemui.statusbar.chips.ui.model.OngoingActivityChipModel
 import com.android.systemui.statusbar.chips.ui.viewmodel.OngoingActivityChipViewModel
@@ -37,25 +37,25 @@
 import kotlinx.coroutines.flow.asStateFlow
 
 /**
- * A view model that will emit demo promoted ongoing notification chips from [chip] based on adb
- * commands sent by the user.
+ * A view model that will emit demo RON chips (rich ongoing notification chips) from [chip] based on
+ * adb commands sent by the user.
  *
  * Example adb commands:
  *
  * To show a chip with the SysUI icon and custom text and color:
  * ```
- * adb shell cmd statusbar demo-notif -p com.android.systemui -t 10min -c "\\#434343"
+ * adb shell cmd statusbar demo-ron -p com.android.systemui -t 10min -c "\\#434343"
  * ```
  *
  * To hide the chip:
  * ```
- * adb shell cmd statusbar demo-notif --hide
+ * adb shell cmd statusbar demo-ron --hide
  * ```
  *
- * See [DemoNotifCommand] for more information on the adb command spec.
+ * See [DemoRonCommand] for more information on the adb command spec.
  */
 @SysUISingleton
-class DemoNotifChipViewModel
+class DemoRonChipViewModel
 @Inject
 constructor(
     private val commandRegistry: CommandRegistry,
@@ -63,19 +63,19 @@
     private val systemClock: SystemClock,
 ) : OngoingActivityChipViewModel, CoreStartable {
     override fun start() {
-        commandRegistry.registerCommand(DEMO_COMMAND_NAME) { DemoNotifCommand() }
+        commandRegistry.registerCommand("demo-ron") { DemoRonCommand() }
     }
 
     private val _chip =
         MutableStateFlow<OngoingActivityChipModel>(OngoingActivityChipModel.Hidden())
     override val chip: StateFlow<OngoingActivityChipModel> = _chip.asStateFlow()
 
-    private inner class DemoNotifCommand : ParseableCommand(DEMO_COMMAND_NAME) {
+    private inner class DemoRonCommand : ParseableCommand("demo-ron") {
         private val packageName: String? by
             param(
                 longName = "packageName",
                 shortName = "p",
-                description = "The package name for app \"posting\" the demo notification",
+                description = "The package name for the demo RON app",
                 valueParser = Type.String,
             )
 
@@ -99,12 +99,15 @@
             )
 
         private val hide by
-            flag(longName = "hide", description = "Hides any existing demo notification chip")
+            flag(
+                longName = "hide",
+                description = "Hides any existing demo RON chip",
+            )
 
         override fun execute(pw: PrintWriter) {
-            if (!StatusBarNotifChips.isEnabled) {
+            if (!StatusBarRonChips.isEnabled) {
                 pw.println(
-                    "Error: com.android.systemui.status_bar_notification_chips must be enabled " +
+                    "Error: com.android.systemui.status_bar_ron_chips must be enabled " +
                         "before using this demo feature"
                 )
                 return
@@ -164,12 +167,8 @@
                 return null
             }
             return OngoingActivityChipModel.ChipIcon.FullColorAppIcon(
-                Icon.Loaded(drawable = iconDrawable, contentDescription = null)
+                Icon.Loaded(drawable = iconDrawable, contentDescription = null),
             )
         }
     }
-
-    companion object {
-        private const val DEMO_COMMAND_NAME = "demo-notif"
-    }
 }
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/chips/notification/shared/StatusBarNotifChips.kt b/packages/SystemUI/src/com/android/systemui/statusbar/chips/ron/shared/StatusBarRonChips.kt
similarity index 87%
rename from packages/SystemUI/src/com/android/systemui/statusbar/chips/notification/shared/StatusBarNotifChips.kt
rename to packages/SystemUI/src/com/android/systemui/statusbar/chips/ron/shared/StatusBarRonChips.kt
index 47ffbaf..4ef1909 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/chips/notification/shared/StatusBarNotifChips.kt
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/chips/ron/shared/StatusBarRonChips.kt
@@ -14,17 +14,17 @@
  * limitations under the License.
  */
 
-package com.android.systemui.statusbar.chips.notification.shared
+package com.android.systemui.statusbar.chips.ron.shared
 
 import com.android.systemui.Flags
 import com.android.systemui.flags.FlagToken
 import com.android.systemui.flags.RefactorFlagUtils
 
-/** Helper for reading or using the status bar promoted notification chips flag state. */
+/** Helper for reading or using the status bar RON chips flag state. */
 @Suppress("NOTHING_TO_INLINE")
-object StatusBarNotifChips {
+object StatusBarRonChips {
     /** The aconfig flag name */
-    const val FLAG_NAME = Flags.FLAG_STATUS_BAR_NOTIFICATION_CHIPS
+    const val FLAG_NAME = Flags.FLAG_STATUS_BAR_RON_CHIPS
 
     /** A token used for dependency declaration */
     val token: FlagToken
@@ -33,7 +33,7 @@
     /** Is the refactor enabled */
     @JvmStatic
     inline val isEnabled
-        get() = Flags.statusBarNotificationChips()
+        get() = Flags.statusBarRonChips()
 
     /**
      * Called to ensure code is only run when the flag is enabled. This protects users from the
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/chips/ui/binder/OngoingActivityChipBinder.kt b/packages/SystemUI/src/com/android/systemui/statusbar/chips/ui/binder/OngoingActivityChipBinder.kt
index f4462a4..2220caab 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/chips/ui/binder/OngoingActivityChipBinder.kt
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/chips/ui/binder/OngoingActivityChipBinder.kt
@@ -28,7 +28,7 @@
 import com.android.systemui.common.ui.binder.IconViewBinder
 import com.android.systemui.res.R
 import com.android.systemui.statusbar.StatusBarIconView
-import com.android.systemui.statusbar.chips.notification.shared.StatusBarNotifChips
+import com.android.systemui.statusbar.chips.ron.shared.StatusBarRonChips
 import com.android.systemui.statusbar.chips.ui.model.OngoingActivityChipModel
 import com.android.systemui.statusbar.chips.ui.view.ChipBackgroundContainer
 import com.android.systemui.statusbar.chips.ui.view.ChipChronometer
@@ -102,7 +102,7 @@
                 defaultIconView.tintView(iconTint)
             }
             is OngoingActivityChipModel.ChipIcon.FullColorAppIcon -> {
-                StatusBarNotifChips.assertInNewMode()
+                StatusBarRonChips.assertInNewMode()
                 IconViewBinder.bind(icon.impl, defaultIconView)
                 defaultIconView.visibility = View.VISIBLE
                 defaultIconView.untintView()
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/chips/ui/model/OngoingActivityChipModel.kt b/packages/SystemUI/src/com/android/systemui/statusbar/chips/ui/model/OngoingActivityChipModel.kt
index cf07af1..2366572 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/chips/ui/model/OngoingActivityChipModel.kt
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/chips/ui/model/OngoingActivityChipModel.kt
@@ -20,7 +20,7 @@
 import com.android.systemui.Flags
 import com.android.systemui.common.shared.model.Icon
 import com.android.systemui.statusbar.StatusBarIconView
-import com.android.systemui.statusbar.chips.notification.shared.StatusBarNotifChips
+import com.android.systemui.statusbar.chips.ron.shared.StatusBarRonChips
 
 /** Model representing the display of an ongoing activity as a chip in the status bar. */
 sealed class OngoingActivityChipModel {
@@ -91,7 +91,10 @@
             override val onClickListener: View.OnClickListener?,
         ) : Shown(icon, colors, onClickListener) {
             init {
-                StatusBarNotifChips.assertInNewMode()
+                check(StatusBarRonChips.isEnabled) {
+                    "OngoingActivityChipModel.Shown.ShortTimeDelta created even though " +
+                        "Flags.statusBarRonChips is not enabled"
+                }
             }
 
             override val logName = "Shown.ShortTimeDelta"
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/chips/ui/viewmodel/OngoingActivityChipsViewModel.kt b/packages/SystemUI/src/com/android/systemui/statusbar/chips/ui/viewmodel/OngoingActivityChipsViewModel.kt
index ed32597..954386e 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/chips/ui/viewmodel/OngoingActivityChipsViewModel.kt
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/chips/ui/viewmodel/OngoingActivityChipsViewModel.kt
@@ -16,6 +16,7 @@
 
 package com.android.systemui.statusbar.chips.ui.viewmodel
 
+import com.android.systemui.Flags
 import com.android.systemui.dagger.SysUISingleton
 import com.android.systemui.dagger.qualifiers.Application
 import com.android.systemui.log.LogBuffer
@@ -23,9 +24,9 @@
 import com.android.systemui.statusbar.chips.StatusBarChipsLog
 import com.android.systemui.statusbar.chips.call.ui.viewmodel.CallChipViewModel
 import com.android.systemui.statusbar.chips.casttootherdevice.ui.viewmodel.CastToOtherDeviceChipViewModel
-import com.android.systemui.statusbar.chips.notification.demo.ui.viewmodel.DemoNotifChipViewModel
-import com.android.systemui.statusbar.chips.notification.shared.StatusBarNotifChips
 import com.android.systemui.statusbar.chips.notification.ui.viewmodel.NotifChipsViewModel
+import com.android.systemui.statusbar.chips.ron.demo.ui.viewmodel.DemoRonChipViewModel
+import com.android.systemui.statusbar.chips.ron.shared.StatusBarRonChips
 import com.android.systemui.statusbar.chips.screenrecord.ui.viewmodel.ScreenRecordChipViewModel
 import com.android.systemui.statusbar.chips.sharetoapp.ui.viewmodel.ShareToAppChipViewModel
 import com.android.systemui.statusbar.chips.ui.model.MultipleOngoingActivityChipsModel
@@ -56,7 +57,7 @@
     castToOtherDeviceChipViewModel: CastToOtherDeviceChipViewModel,
     callChipViewModel: CallChipViewModel,
     notifChipsViewModel: NotifChipsViewModel,
-    demoNotifChipViewModel: DemoNotifChipViewModel,
+    demoRonChipViewModel: DemoRonChipViewModel,
     @StatusBarChipsLog private val logger: LogBuffer,
 ) {
     private enum class ChipType {
@@ -65,8 +66,8 @@
         CastToOtherDevice,
         Call,
         Notification,
-        /** A demo of a notification chip, used just for testing. */
-        DemoNotification,
+        /** A demo of a RON chip (rich ongoing notification chip), used just for testing. */
+        DemoRon,
     }
 
     /** Model that helps us internally track the various chip states from each of the types. */
@@ -88,7 +89,7 @@
             val castToOtherDevice: OngoingActivityChipModel.Hidden,
             val call: OngoingActivityChipModel.Hidden,
             val notifs: OngoingActivityChipModel.Hidden,
-            val demoNotif: OngoingActivityChipModel.Hidden,
+            val demoRon: OngoingActivityChipModel.Hidden,
         ) : InternalChipModel
     }
 
@@ -98,7 +99,7 @@
         val castToOtherDevice: OngoingActivityChipModel = OngoingActivityChipModel.Hidden(),
         val call: OngoingActivityChipModel = OngoingActivityChipModel.Hidden(),
         val notifs: List<OngoingActivityChipModel.Shown> = emptyList(),
-        val demoNotif: OngoingActivityChipModel = OngoingActivityChipModel.Hidden(),
+        val demoRon: OngoingActivityChipModel = OngoingActivityChipModel.Hidden(),
     )
 
     /** Bundles all the incoming chips into one object to easily pass to various flows. */
@@ -109,8 +110,8 @@
                 castToOtherDeviceChipViewModel.chip,
                 callChipViewModel.chip,
                 notifChipsViewModel.chips,
-                demoNotifChipViewModel.chip,
-            ) { screenRecord, shareToApp, castToOtherDevice, call, notifs, demoNotif ->
+                demoRonChipViewModel.chip,
+            ) { screenRecord, shareToApp, castToOtherDevice, call, notifs, demoRon ->
                 logger.log(
                     TAG,
                     LogLevel.INFO,
@@ -128,9 +129,9 @@
                         str1 = call.logName
                         // TODO(b/364653005): Log other information for notification chips.
                         str2 = notifs.map { it.logName }.toString()
-                        str3 = demoNotif.logName
+                        str3 = demoRon.logName
                     },
-                    { "... > Call=$str1 > Notifs=$str2 > DemoNotif=$str3" },
+                    { "... > Call=$str1 > Notifs=$str2 > DemoRon=$str3" },
                 )
                 ChipBundle(
                     screenRecord = screenRecord,
@@ -138,7 +139,7 @@
                     castToOtherDevice = castToOtherDevice,
                     call = call,
                     notifs = notifs,
-                    demoNotif = demoNotif,
+                    demoRon = demoRon,
                 )
             }
             // Some of the chips could have timers in them and we don't want the start time
@@ -197,9 +198,9 @@
      * actually displaying the chip.
      */
     val chips: StateFlow<MultipleOngoingActivityChipsModel> =
-        if (!StatusBarNotifChips.isEnabled) {
-            // Multiple chips are only allowed with notification chips. If the flag isn't on, use
-            // just the primary chip.
+        if (!Flags.statusBarRonChips()) {
+            // Multiple chips are only allowed with RONs. If the flag isn't on, use just the
+            // primary chip.
             primaryChip
                 .map {
                     MultipleOngoingActivityChipsModel(
@@ -281,12 +282,11 @@
                     remainingChips =
                         bundle.copy(notifs = bundle.notifs.subList(1, bundle.notifs.size)),
                 )
-            bundle.demoNotif is OngoingActivityChipModel.Shown -> {
-                StatusBarNotifChips.assertInNewMode()
+            bundle.demoRon is OngoingActivityChipModel.Shown -> {
+                StatusBarRonChips.assertInNewMode()
                 MostImportantChipResult(
-                    mostImportantChip =
-                        InternalChipModel.Shown(ChipType.DemoNotification, bundle.demoNotif),
-                    remainingChips = bundle.copy(demoNotif = OngoingActivityChipModel.Hidden()),
+                    mostImportantChip = InternalChipModel.Shown(ChipType.DemoRon, bundle.demoRon),
+                    remainingChips = bundle.copy(demoRon = OngoingActivityChipModel.Hidden()),
                 )
             }
             else -> {
@@ -296,7 +296,7 @@
                 check(bundle.castToOtherDevice is OngoingActivityChipModel.Hidden)
                 check(bundle.call is OngoingActivityChipModel.Hidden)
                 check(bundle.notifs.isEmpty())
-                check(bundle.demoNotif is OngoingActivityChipModel.Hidden)
+                check(bundle.demoRon is OngoingActivityChipModel.Hidden)
                 MostImportantChipResult(
                     mostImportantChip =
                         InternalChipModel.Hidden(
@@ -305,7 +305,7 @@
                             castToOtherDevice = bundle.castToOtherDevice,
                             call = bundle.call,
                             notifs = OngoingActivityChipModel.Hidden(),
-                            demoNotif = bundle.demoNotif,
+                            demoRon = bundle.demoRon,
                         ),
                     // All the chips are already hidden, so no need to filter anything out of the
                     // bundle.
@@ -334,7 +334,7 @@
                 ChipType.CastToOtherDevice -> new.castToOtherDevice
                 ChipType.Call -> new.call
                 ChipType.Notification -> new.notifs
-                ChipType.DemoNotification -> new.demoNotif
+                ChipType.DemoRon -> new.demoRon
             }
         } else if (new is InternalChipModel.Shown) {
             // If we have a chip to show, always show it.
@@ -356,7 +356,7 @@
                 castToOtherDevice = OngoingActivityChipModel.Hidden(),
                 call = OngoingActivityChipModel.Hidden(),
                 notifs = OngoingActivityChipModel.Hidden(),
-                demoNotif = OngoingActivityChipModel.Hidden(),
+                demoRon = OngoingActivityChipModel.Hidden(),
             )
 
         private val DEFAULT_MULTIPLE_INTERNAL_HIDDEN_MODEL =
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/ConversationNotifications.kt b/packages/SystemUI/src/com/android/systemui/statusbar/notification/ConversationNotifications.kt
index 682a9ff..77ec65b 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/ConversationNotifications.kt
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/ConversationNotifications.kt
@@ -29,6 +29,7 @@
 import com.android.systemui.dagger.SysUISingleton
 import com.android.systemui.dagger.qualifiers.Main
 import com.android.systemui.plugins.statusbar.StatusBarStateController
+import com.android.systemui.shade.ShadeDisplayAware
 import com.android.systemui.statusbar.notification.collection.NotificationEntry
 import com.android.systemui.statusbar.notification.collection.inflation.BindEventManager
 import com.android.systemui.statusbar.notification.collection.notifcollection.CommonNotifCollection
@@ -141,7 +142,7 @@
 @Inject
 constructor(
     bindEventManager: BindEventManager,
-    private val context: Context,
+    @ShadeDisplayAware private val context: Context,
     private val notifCollection: CommonNotifCollection,
     @Main private val mainHandler: Handler
 ) {
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/EntryWithDismissStats.kt b/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/EntryWithDismissStats.kt
new file mode 100644
index 0000000..48a8c01
--- /dev/null
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/EntryWithDismissStats.kt
@@ -0,0 +1,46 @@
+/*
+ * 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.notification.collection
+
+import com.android.internal.statusbar.NotificationVisibility
+import com.android.systemui.statusbar.notification.collection.notifcollection.DismissedByUserStats
+
+/**
+ * A holder class for a [NotificationEntry] and an associated [DismissedByUserStats], used by
+ * [NotifCollection] for handling dismissal.
+ */
+data class EntryWithDismissStats(val entry: NotificationEntry, val stats: DismissedByUserStats) {
+    /**
+     * Creates deep a copy of this object, but with the entry, key and rank updated to correspond to
+     * the given entry.
+     */
+    fun copyForEntry(newEntry: NotificationEntry) =
+        EntryWithDismissStats(
+            entry = newEntry,
+            stats =
+                DismissedByUserStats(
+                    stats.dismissalSurface,
+                    stats.dismissalSentiment,
+                    NotificationVisibility.obtain(
+                        newEntry.key,
+                        newEntry.ranking.rank,
+                        stats.notificationVisibility.count,
+                        /* visible= */ false,
+                    ),
+                ),
+        )
+}
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/NotifCollection.java b/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/NotifCollection.java
index 7b3a93a..cf9ee61 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/NotifCollection.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/NotifCollection.java
@@ -39,6 +39,7 @@
 import static android.service.notification.NotificationListenerService.REASON_UNAUTOBUNDLED;
 import static android.service.notification.NotificationListenerService.REASON_USER_STOPPED;
 
+import static com.android.systemui.Flags.notificationsDismissPrunedSummaries;
 import static com.android.systemui.statusbar.notification.NotificationUtils.logKey;
 import static com.android.systemui.statusbar.notification.collection.NotificationEntry.DismissState.DISMISSED;
 import static com.android.systemui.statusbar.notification.collection.NotificationEntry.DismissState.NOT_DISMISSED;
@@ -62,7 +63,6 @@
 import android.service.notification.StatusBarNotification;
 import android.util.ArrayMap;
 import android.util.Log;
-import android.util.Pair;
 
 import androidx.annotation.NonNull;
 import androidx.annotation.Nullable;
@@ -111,6 +111,7 @@
 import java.util.Collections;
 import java.util.Comparator;
 import java.util.HashMap;
+import java.util.HashSet;
 import java.util.List;
 import java.util.Map;
 import java.util.Objects;
@@ -273,15 +274,19 @@
      * Dismisses multiple notifications on behalf of the user.
      */
     public void dismissNotifications(
-            List<Pair<NotificationEntry, DismissedByUserStats>> entriesToDismiss) {
+            List<EntryWithDismissStats> entriesToDismiss) {
         Assert.isMainThread();
         checkForReentrantCall();
 
+        if (notificationsDismissPrunedSummaries()) {
+            entriesToDismiss = includeSummariesToDismiss(entriesToDismiss);
+        }
+
         final int entryCount = entriesToDismiss.size();
         final List<NotificationEntry> entriesToLocallyDismiss = new ArrayList<>();
         for (int i = 0; i < entriesToDismiss.size(); i++) {
-            NotificationEntry entry = entriesToDismiss.get(i).first;
-            DismissedByUserStats stats = entriesToDismiss.get(i).second;
+            NotificationEntry entry = entriesToDismiss.get(i).getEntry();
+            DismissedByUserStats stats = entriesToDismiss.get(i).getStats();
 
             requireNonNull(stats);
             NotificationEntry storedEntry = mNotificationSet.get(entry.getKey());
@@ -336,13 +341,32 @@
         dispatchEventsAndRebuildList("dismissNotifications");
     }
 
+    private List<EntryWithDismissStats> includeSummariesToDismiss(
+            List<EntryWithDismissStats> entriesToDismiss) {
+        final HashSet<NotificationEntry> entriesSet = new HashSet<>(entriesToDismiss.size());
+        for (EntryWithDismissStats entryToStats : entriesToDismiss) {
+            entriesSet.add(entryToStats.getEntry());
+        }
+
+        final List<EntryWithDismissStats> entriesPlusSummaries =
+                new ArrayList<>(entriesToDismiss.size() + 1);
+        for (EntryWithDismissStats entryToStats : entriesToDismiss) {
+            entriesPlusSummaries.add(entryToStats);
+            NotificationEntry summary = fetchSummaryToDismiss(entryToStats.getEntry());
+            if (summary != null && !entriesSet.contains(summary)) {
+                entriesPlusSummaries.add(entryToStats.copyForEntry(summary));
+            }
+        }
+        return entriesPlusSummaries;
+    }
+
     /**
      * Dismisses a single notification on behalf of the user.
      */
     public void dismissNotification(
             NotificationEntry entry,
             @NonNull DismissedByUserStats stats) {
-        dismissNotifications(List.of(new Pair<>(entry, stats)));
+        dismissNotifications(List.of(new EntryWithDismissStats(entry, stats)));
     }
 
     /**
@@ -1062,6 +1086,16 @@
         }
     }
 
+    @Nullable
+    private NotificationEntry fetchSummaryToDismiss(NotificationEntry entry) {
+        if (isOnlyChildInGroup(entry)) {
+            String group = entry.getSbn().getGroupKey();
+            NotificationEntry summary = getGroupSummary(group);
+            if (summary != null && isDismissable(summary)) return summary;
+        }
+        return null;
+    }
+
     /** A single method interface that callers can pass in when registering future dismissals */
     public interface DismissedByUserStatsCreator {
         DismissedByUserStats createDismissedByUserStats(NotificationEntry entry);
@@ -1092,16 +1126,6 @@
                     + ">";
         }
 
-        @Nullable
-        private NotificationEntry fetchSummaryToDismiss(NotificationEntry entry) {
-            if (isOnlyChildInGroup(entry)) {
-                String group = entry.getSbn().getGroupKey();
-                NotificationEntry summary = getGroupSummary(group);
-                if (summary != null && isDismissable(summary)) return summary;
-            }
-            return null;
-        }
-
         /** called when the entry has been removed from the collection */
         public void onSystemServerCancel(@CancellationReason int cancellationReason) {
             Assert.isMainThread();
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 e74ed8d..205c42f 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
@@ -586,7 +586,8 @@
         }
         Bundle extras = mSbn.getNotification().extras;
         Parcelable[] replyTexts =
-                extras.getParcelableArray(Notification.EXTRA_REMOTE_INPUT_HISTORY_ITEMS);
+                extras.getParcelableArray(Notification.EXTRA_REMOTE_INPUT_HISTORY_ITEMS,
+                        RemoteInputHistoryItem.class);
         if (!ArrayUtils.isEmpty(replyTexts)) {
             return true;
         }
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/coordinator/RowAppearanceCoordinator.kt b/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/coordinator/RowAppearanceCoordinator.kt
index df694bb..de868d4 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/coordinator/RowAppearanceCoordinator.kt
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/coordinator/RowAppearanceCoordinator.kt
@@ -18,6 +18,7 @@
 
 import android.content.Context
 import com.android.systemui.res.R
+import com.android.systemui.shade.ShadeDisplayAware
 import com.android.systemui.statusbar.notification.AssistantFeedbackController
 import com.android.systemui.statusbar.notification.collection.ListEntry
 import com.android.systemui.statusbar.notification.collection.NotifPipeline
@@ -33,7 +34,7 @@
  */
 @CoordinatorScope
 class RowAppearanceCoordinator @Inject internal constructor(
-    context: Context,
+    @ShadeDisplayAware context: Context,
     private var mAssistantFeedbackController: AssistantFeedbackController,
     private var mSectionStyleProvider: SectionStyleProvider
 ) : Coordinator {
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/coordinator/ViewConfigCoordinator.kt b/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/coordinator/ViewConfigCoordinator.kt
index b8a9594..db778b80 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/coordinator/ViewConfigCoordinator.kt
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/coordinator/ViewConfigCoordinator.kt
@@ -30,6 +30,7 @@
 import com.android.systemui.statusbar.policy.ConfigurationController
 import com.android.systemui.util.Compile
 import com.android.app.tracing.traceSection
+import com.android.systemui.shade.ShadeDisplayAware
 import javax.inject.Inject
 
 /**
@@ -39,7 +40,7 @@
  */
 @CoordinatorScope
 class ViewConfigCoordinator @Inject internal constructor(
-    private val mConfigurationController: ConfigurationController,
+    @ShadeDisplayAware private val mConfigurationController: ConfigurationController,
     private val mLockscreenUserManager: NotificationLockscreenUserManager,
     private val mGutsManager: NotificationGutsManager,
     private val mKeyguardUpdateMonitor: KeyguardUpdateMonitor,
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/inflation/NotificationRowBinderImpl.java b/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/inflation/NotificationRowBinderImpl.java
index 9d5d7a1..e6d22b0 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/inflation/NotificationRowBinderImpl.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/inflation/NotificationRowBinderImpl.java
@@ -37,6 +37,7 @@
 import com.android.systemui.dagger.SysUISingleton;
 import com.android.systemui.flags.FeatureFlags;
 import com.android.systemui.flags.Flags;
+import com.android.systemui.shade.ShadeDisplayAware;
 import com.android.systemui.statusbar.NotificationLockscreenUserManager;
 import com.android.systemui.statusbar.NotificationPresenter;
 import com.android.systemui.statusbar.NotificationRemoteInputManager;
@@ -86,7 +87,7 @@
 
     @Inject
     public NotificationRowBinderImpl(
-            Context context,
+            @ShadeDisplayAware Context context,
             NotificationMessagingUtil notificationMessagingUtil,
             NotificationRemoteInputManager notificationRemoteInputManager,
             NotificationLockscreenUserManager notificationLockscreenUserManager,
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/render/ShadeViewManager.kt b/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/render/ShadeViewManager.kt
index 1935866..cab4c1c 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/render/ShadeViewManager.kt
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/render/ShadeViewManager.kt
@@ -27,6 +27,7 @@
 import com.android.systemui.statusbar.notification.collection.provider.SectionHeaderVisibilityProvider
 import com.android.systemui.statusbar.notification.stack.NotificationListContainer
 import com.android.app.tracing.traceSection
+import com.android.systemui.shade.ShadeDisplayAware
 import dagger.assisted.Assisted
 import dagger.assisted.AssistedFactory
 import dagger.assisted.AssistedInject
@@ -36,7 +37,7 @@
  * currently populate the notification shade.
  */
 class ShadeViewManager @AssistedInject constructor(
-    context: Context,
+    @ShadeDisplayAware context: Context,
     @Assisted listContainer: NotificationListContainer,
     @Assisted private val stackController: NotifStackController,
     mediaContainerController: MediaContainerController,
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/domain/interactor/ActiveNotificationsInteractor.kt b/packages/SystemUI/src/com/android/systemui/statusbar/notification/domain/interactor/ActiveNotificationsInteractor.kt
index 697a6ce..5900fb0 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/domain/interactor/ActiveNotificationsInteractor.kt
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/domain/interactor/ActiveNotificationsInteractor.kt
@@ -17,7 +17,7 @@
 
 import com.android.systemui.dagger.SysUISingleton
 import com.android.systemui.dagger.qualifiers.Background
-import com.android.systemui.statusbar.chips.notification.shared.StatusBarNotifChips
+import com.android.systemui.statusbar.chips.ron.shared.StatusBarRonChips
 import com.android.systemui.statusbar.notification.collection.render.NotifStats
 import com.android.systemui.statusbar.notification.data.repository.ActiveNotificationListRepository
 import com.android.systemui.statusbar.notification.shared.ActiveNotificationGroupModel
@@ -78,7 +78,7 @@
 
     /** The notifications that are promoted and ongoing. Sorted by priority order. */
     val promotedOngoingNotifications: Flow<List<ActiveNotificationModel>> =
-        if (StatusBarNotifChips.isEnabled) {
+        if (StatusBarRonChips.isEnabled) {
             // TODO(b/364653005): Filter all the notifications down to just the promoted ones.
             // TODO(b/364653005): [ongoingCallNotification] should be incorporated into this flow
             // instead of being separate.
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/footer/ui/view/FooterView.java b/packages/SystemUI/src/com/android/systemui/statusbar/notification/footer/ui/view/FooterView.java
index 5a616df..e5ce25d 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/footer/ui/view/FooterView.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/footer/ui/view/FooterView.java
@@ -40,6 +40,7 @@
 import androidx.annotation.NonNull;
 
 import com.android.settingslib.Utils;
+import com.android.systemui.Flags;
 import com.android.systemui.res.R;
 import com.android.systemui.statusbar.notification.ColorUpdateLogger;
 import com.android.systemui.statusbar.notification.footer.shared.FooterViewRefactor;
@@ -59,6 +60,9 @@
 
     private FooterViewButton mClearAllButton;
     private FooterViewButton mManageOrHistoryButton;
+    // The settings & history buttons replace the single manage/history button in the redesign
+    private FooterViewButton mSettingsButton;
+    private FooterViewButton mHistoryButton;
     private boolean mShouldBeHidden;
     private boolean mShowHistory;
     // String cache, for performance reasons.
@@ -269,7 +273,12 @@
         }
         super.onFinishInflate();
         mClearAllButton = (FooterViewButton) findSecondaryView();
-        mManageOrHistoryButton = findViewById(R.id.manage_text);
+        if (Flags.notificationsRedesignFooterView()) {
+            mSettingsButton = findViewById(R.id.settings_button);
+            mHistoryButton = findViewById(R.id.history_button);
+        } else {
+            mManageOrHistoryButton = findViewById(R.id.manage_text);
+        }
         mSeenNotifsFooterTextView = findViewById(R.id.unlock_prompt_footer);
         if (!FooterViewRefactor.isEnabled()) {
             updateResources();
@@ -342,8 +351,10 @@
             updateClearAllButtonText();
             updateClearAllButtonDescription();
 
-            updateManageOrHistoryButtonText();
-            updateManageOrHistoryButtonDescription();
+            if (!Flags.notificationsRedesignFooterView()) {
+                updateManageOrHistoryButtonText();
+                updateManageOrHistoryButtonDescription();
+            }
 
             updateMessageString();
             updateMessageIcon();
@@ -420,8 +431,16 @@
         }
         mClearAllButton.setBackground(clearAllBg);
         mClearAllButton.setTextColor(onSurface);
-        mManageOrHistoryButton.setBackground(manageBg);
-        mManageOrHistoryButton.setTextColor(onSurface);
+        if (Flags.notificationsRedesignFooterView()) {
+            mSettingsButton.setBackground(manageBg);
+            mSettingsButton.setCompoundDrawableTintList(ColorStateList.valueOf(onSurface));
+
+            mHistoryButton.setBackground(manageBg);
+            mHistoryButton.setCompoundDrawableTintList(ColorStateList.valueOf(onSurface));
+        } else {
+            mManageOrHistoryButton.setBackground(manageBg);
+            mManageOrHistoryButton.setTextColor(onSurface);
+        }
         mSeenNotifsFooterTextView.setTextColor(onSurface);
         mSeenNotifsFooterTextView.setCompoundDrawableTintList(ColorStateList.valueOf(onSurface));
         ColorUpdateLogger colorUpdateLogger = ColorUpdateLogger.getInstance();
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/footer/ui/viewbinder/FooterViewBinder.kt b/packages/SystemUI/src/com/android/systemui/statusbar/notification/footer/ui/viewbinder/FooterViewBinder.kt
index 2ec7f53..ddd9cdd 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/footer/ui/viewbinder/FooterViewBinder.kt
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/footer/ui/viewbinder/FooterViewBinder.kt
@@ -18,6 +18,7 @@
 
 import android.view.View
 import androidx.lifecycle.lifecycleScope
+import com.android.systemui.Flags
 import com.android.systemui.lifecycle.repeatWhenAttached
 import com.android.systemui.statusbar.notification.NotificationActivityStarter
 import com.android.systemui.statusbar.notification.emptyshade.shared.ModesEmptyShadeFix
@@ -63,14 +64,16 @@
         notificationActivityStarter: NotificationActivityStarter,
     ) = coroutineScope {
         launch { bindClearAllButton(footer, viewModel, clearAllNotifications) }
-        launch {
-            bindManageOrHistoryButton(
-                footer,
-                viewModel,
-                launchNotificationSettings,
-                launchNotificationHistory,
-                notificationActivityStarter,
-            )
+        if (!Flags.notificationsRedesignFooterView()) {
+            launch {
+                bindManageOrHistoryButton(
+                    footer,
+                    viewModel,
+                    launchNotificationSettings,
+                    launchNotificationHistory,
+                    notificationActivityStarter,
+                )
+            }
         }
         launch { bindMessage(footer, viewModel) }
     }
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/interruption/VisualInterruptionDecisionProviderImpl.kt b/packages/SystemUI/src/com/android/systemui/statusbar/notification/interruption/VisualInterruptionDecisionProviderImpl.kt
index d4466f8..71cddc9 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/interruption/VisualInterruptionDecisionProviderImpl.kt
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/interruption/VisualInterruptionDecisionProviderImpl.kt
@@ -29,6 +29,7 @@
 import com.android.systemui.dagger.qualifiers.Main
 import com.android.systemui.plugins.statusbar.StatusBarStateController
 import com.android.systemui.settings.UserTracker
+import com.android.systemui.shade.ShadeDisplayAware
 import com.android.systemui.shared.notifications.domain.interactor.NotificationSettingsInteractor
 import com.android.systemui.statusbar.notification.collection.NotificationEntry
 import com.android.systemui.statusbar.notification.interruption.VisualInterruptionDecisionProvider.Decision
@@ -72,7 +73,7 @@
     private val systemSettings: SystemSettings,
     private val packageManager: PackageManager,
     private val bubbles: Optional<Bubbles>,
-    private val context: Context,
+    @ShadeDisplayAware private val context: Context,
     private val notificationManager: NotificationManager,
     private val settingsInteractor: NotificationSettingsInteractor
 ) : VisualInterruptionDecisionProvider {
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/BigPictureIconManager.kt b/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/BigPictureIconManager.kt
index 068f23d..e233def 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/BigPictureIconManager.kt
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/BigPictureIconManager.kt
@@ -32,6 +32,7 @@
 import com.android.systemui.dagger.qualifiers.Background
 import com.android.systemui.dagger.qualifiers.Main
 import com.android.systemui.graphics.ImageLoader
+import com.android.systemui.shade.ShadeDisplayAware
 import com.android.systemui.statusbar.notification.row.BigPictureIconManager.DrawableState.Empty
 import com.android.systemui.statusbar.notification.row.BigPictureIconManager.DrawableState.FullImage
 import com.android.systemui.statusbar.notification.row.BigPictureIconManager.DrawableState.Initial
@@ -61,7 +62,7 @@
 class BigPictureIconManager
 @Inject
 constructor(
-    private val context: Context,
+    @ShadeDisplayAware private val context: Context,
     private val imageLoader: ImageLoader,
     private val statsManager: BigPictureStatsManager,
     @Application private val scope: CoroutineScope,
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/icon/AppIconProvider.kt b/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/icon/AppIconProvider.kt
index 172b76c..98d704c 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/icon/AppIconProvider.kt
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/icon/AppIconProvider.kt
@@ -33,6 +33,7 @@
 import com.android.systemui.Dumpable
 import com.android.systemui.dagger.SysUISingleton
 import com.android.systemui.dump.DumpManager
+import com.android.systemui.shade.ShadeDisplayAware
 import com.android.systemui.statusbar.notification.collection.NotifCollectionCache
 import com.android.systemui.util.asIndenting
 import com.android.systemui.util.withIncreasedIndent
@@ -67,7 +68,7 @@
 @SysUISingleton
 class AppIconProviderImpl
 @Inject
-constructor(private val sysuiContext: Context, dumpManager: DumpManager) :
+constructor(@ShadeDisplayAware private val sysuiContext: Context, dumpManager: DumpManager) :
     AppIconProvider, Dumpable {
     init {
         dumpManager.registerNormalDumpable(TAG, this)
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/AmbientState.java b/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/AmbientState.java
index 129d4ce..7771421 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/AmbientState.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/AmbientState.java
@@ -596,10 +596,12 @@
     }
 
     public void setContentHeight(int contentHeight) {
+        SceneContainerFlag.assertInLegacyMode();
         mContentHeight = contentHeight;
     }
 
     public float getContentHeight() {
+        SceneContainerFlag.assertInLegacyMode();
         return mContentHeight;
     }
 
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/NotificationSectionsManager.kt b/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/NotificationSectionsManager.kt
index 7441c70..c9a0010 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/NotificationSectionsManager.kt
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/NotificationSectionsManager.kt
@@ -20,6 +20,7 @@
 import android.view.View
 import com.android.internal.annotations.VisibleForTesting
 import com.android.systemui.media.controls.ui.controller.KeyguardMediaController
+import com.android.systemui.shade.ShadeDisplayAware
 import com.android.systemui.statusbar.notification.NotificationSectionsFeatureManager
 import com.android.systemui.statusbar.notification.SourceType
 import com.android.systemui.statusbar.notification.collection.NotificationClassificationFlag
@@ -41,7 +42,7 @@
 class NotificationSectionsManager
 @Inject
 internal constructor(
-    private val configurationController: ConfigurationController,
+    @ShadeDisplayAware private val configurationController: ConfigurationController,
     private val keyguardMediaController: KeyguardMediaController,
     private val sectionsFeatureManager: NotificationSectionsFeatureManager,
     private val mediaContainerController: MediaContainerController,
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/NotificationStackScrollLayout.java b/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/NotificationStackScrollLayout.java
index 87b16ef..dde83b9 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/NotificationStackScrollLayout.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/NotificationStackScrollLayout.java
@@ -615,13 +615,13 @@
             if (SceneContainerFlag.isEnabled()) {
                 return mScrollViewFields.getScrollState().isScrolledToTop();
             } else {
-                return mOwnScrollY == 0;
+                return getOwnScrollY() == 0;
             }
         }
 
         @Override
         public boolean isScrolledToBottom() {
-            return mOwnScrollY >= getScrollRange();
+            return getOwnScrollY() >= getScrollRange();
         }
 
         @Override
@@ -900,11 +900,11 @@
         drawDebugInfo(canvas, y, Color.LTGRAY,
                 /* label= */ "mAmbientState.getStackY() + mAmbientState.getStackHeight() = " + y);
 
-        y = (int) (mAmbientState.getStackY() + mIntrinsicContentHeight);
+        y = (int) (mAmbientState.getStackY() + getIntrinsicContentHeight());
         drawDebugInfo(canvas, y, Color.YELLOW,
                 /* label= */ "mAmbientState.getStackY() + mIntrinsicContentHeight = " + y);
 
-        y = mContentHeight;
+        y = getContentHeight();
         drawDebugInfo(canvas, y, Color.MAGENTA,
                 /* label= */ "mContentHeight = " + y);
 
@@ -1200,7 +1200,6 @@
         if (!SceneContainerFlag.isEnabled()) {
             setMaxLayoutHeight(getHeight());
             updateContentHeight();
-            mWallpaperInteractor.setNotificationStackAbsoluteBottom(mContentHeight);
         }
         clampScrollPosition();
         requestChildrenUpdate();
@@ -1278,7 +1277,6 @@
         if (mAmbientState.getStackTop() != stackTop) {
             mAmbientState.setStackTop(stackTop);
             onTopPaddingChanged(/* animate = */ isAddOrRemoveAnimationPending());
-            mWallpaperInteractor.setNotificationStackAbsoluteBottom((int) stackTop);
         }
     }
 
@@ -1376,7 +1374,7 @@
 
     /**
      * Updates the children views according to the stack scroll algorithm. Call this whenever
-     * modifications to {@link #mOwnScrollY} are performed to reflect it in the view layout.
+     * modifications to {@link #getOwnScrollY()} are performed to reflect it in the view layout.
      */
     private void updateChildren() {
         Trace.beginSection("NSSL#updateChildren");
@@ -1406,11 +1404,11 @@
             if (mChildrenToAddAnimated.contains(child)) {
                 final int startingPosition = getPositionInLinearLayout(child);
                 final int childHeight = getIntrinsicHeight(child) + mPaddingBetweenElements;
-                if (startingPosition < mOwnScrollY) {
+                if (startingPosition < getOwnScrollY()) {
                     // This child starts off screen, so let's keep it offscreen to keep the
                     // others visible
 
-                    setOwnScrollY(mOwnScrollY + childHeight);
+                    setOwnScrollY(getOwnScrollY() + childHeight);
                 }
             }
         }
@@ -1430,7 +1428,7 @@
             targetScroll = Math.max(0, Math.min(targetScroll, getScrollRange()));
             // Only apply the scroll if we're scrolling the view upwards, or the view is so
             // far up that it is not visible anymore.
-            if (mOwnScrollY < targetScroll || outOfViewScroll < mOwnScrollY) {
+            if (getOwnScrollY() < targetScroll || outOfViewScroll < getOwnScrollY()) {
                 setOwnScrollY(targetScroll);
             }
         }
@@ -1454,7 +1452,7 @@
             return;
         }
         int scrollRange = getScrollRange();
-        if (scrollRange < mOwnScrollY && !mAmbientState.isClearAllInProgress()) {
+        if (scrollRange < getOwnScrollY() && !mAmbientState.isClearAllInProgress()) {
             // if the scroll boundary updates the position of the stack,
             boolean animateStackY = scrollRange < getScrollAmountToScrollBoundary()
                     && mAnimateStackYForContentHeightChange;
@@ -1588,7 +1586,7 @@
         if (mMaxDisplayedNotifications != -1) {
             // The stack intrinsic height already contains the correct value when there is a limit
             // in the max number of notifications (e.g. as in keyguard).
-            stackEndHeight = mIntrinsicContentHeight;
+            stackEndHeight = getIntrinsicContentHeight();
         } else {
             stackEndHeight = Math.max(0f, height - bottomMargin - topPadding);
         }
@@ -1694,7 +1692,8 @@
             if (mShouldShowShelfOnly) {
                 stackHeight = getTopPadding() + mShelf.getIntrinsicHeight();
             } else if (mQsFullScreen) {
-                int stackStartPosition = mContentHeight - getTopPadding() + getIntrinsicPadding();
+                int stackStartPosition =
+                        getContentHeight() - getTopPadding() + getIntrinsicPadding();
                 int stackEndPosition = mMaxTopPadding + mShelf.getIntrinsicHeight();
                 if (stackStartPosition <= stackEndPosition) {
                     stackHeight = stackEndPosition;
@@ -1763,14 +1762,6 @@
         updateClipping();
     }
 
-    /**
-     * Return the height of the content ignoring the footer.
-     */
-    public int getIntrinsicContentHeight() {
-        SceneContainerFlag.assertInLegacyMode();
-        return (int) mIntrinsicContentHeight;
-    }
-
     public void updateClipping() {
         boolean clipped = mRequestedClipBounds != null && !mInHeadsUpPinnedMode
                 && !mHeadsUpAnimatingAway;
@@ -2074,8 +2065,8 @@
 
         // Only apply the scroll if we're scrolling the view upwards, or the view is so far up
         // that it is not visible anymore.
-        if (mOwnScrollY < targetScroll || outOfViewScroll < mOwnScrollY) {
-            mScroller.startScroll(mScrollX, mOwnScrollY, 0, targetScroll - mOwnScrollY);
+        if (getOwnScrollY() < targetScroll || outOfViewScroll < getOwnScrollY()) {
+            mScroller.startScroll(mScrollX, getOwnScrollY(), 0, targetScroll - getOwnScrollY());
             mDontReportNextOverScroll = true;
             animateScroll();
             return true;
@@ -2109,7 +2100,7 @@
         }
 
         int range = getScrollRange();
-        if (mOwnScrollY > range) {
+        if (getOwnScrollY() > range) {
             setOwnScrollY(range);
         }
     }
@@ -2202,7 +2193,7 @@
         // Top overScroll might not grab all scrolling motion,
         // we have to scroll as well.
         float scrollAmount = newTopAmount < 0 ? -newTopAmount : 0.0f;
-        float newScrollY = mOwnScrollY + scrollAmount;
+        float newScrollY = getOwnScrollY() + scrollAmount;
         if (newScrollY > range) {
             if (!mExpandedInThisMotion) {
                 float currentBottomPixels = getCurrentOverScrolledPixels(false);
@@ -2235,7 +2226,7 @@
         // Bottom overScroll might not grab all scrolling motion,
         // we have to scroll as well.
         float scrollAmount = newBottomAmount < 0 ? newBottomAmount : 0.0f;
-        float newScrollY = mOwnScrollY + scrollAmount;
+        float newScrollY = getOwnScrollY() + scrollAmount;
         if (newScrollY < 0) {
             float currentTopPixels = getCurrentOverScrolledPixels(true);
             // We overScroll on the top
@@ -2275,7 +2266,7 @@
 
     private void animateScroll() {
         if (mScroller.computeScrollOffset()) {
-            int oldY = mOwnScrollY;
+            int oldY = getOwnScrollY();
             int y = mScroller.getCurrY();
 
             if (oldY != y) {
@@ -2462,8 +2453,8 @@
                 springBack();
             } else {
                 float overScrollTop = getCurrentOverScrollAmount(true);
-                if (mOwnScrollY < 0) {
-                    notifyOverscrollTopListener(-mOwnScrollY, isRubberbanded(true));
+                if (getOwnScrollY() < 0) {
+                    notifyOverscrollTopListener(-getOwnScrollY(), isRubberbanded(true));
                 } else {
                     notifyOverscrollTopListener(overScrollTop, isRubberbanded(true));
                 }
@@ -2479,19 +2470,19 @@
      */
     private void springBack() {
         int scrollRange = getScrollRange();
-        boolean overScrolledTop = mOwnScrollY <= 0;
-        boolean overScrolledBottom = mOwnScrollY >= scrollRange;
+        boolean overScrolledTop = getOwnScrollY() <= 0;
+        boolean overScrolledBottom = getOwnScrollY() >= scrollRange;
         if (overScrolledTop || overScrolledBottom) {
             boolean onTop;
             float newAmount;
             if (overScrolledTop) {
                 onTop = true;
-                newAmount = -mOwnScrollY;
+                newAmount = -getOwnScrollY();
                 setOwnScrollY(0);
                 mDontReportNextOverScroll = true;
             } else {
                 onTop = false;
-                newAmount = mOwnScrollY - scrollRange;
+                newAmount = getOwnScrollY() - scrollRange;
                 setOwnScrollY(scrollRange);
             }
             setOverScrollAmount(newAmount, onTop, false);
@@ -2508,7 +2499,7 @@
         }
         // In current design, it only use the top HUN to treat all of HUNs
         // although there are more than one HUNs
-        int contentHeight = mContentHeight;
+        int contentHeight = getContentHeight();
         if (!isExpanded() && mInHeadsUpPinnedMode) {
             contentHeight = mHeadsUpInset + getTopHeadsUpPinnedHeight();
         }
@@ -2644,16 +2635,16 @@
                 (int) scrimTopPadding + (int) mNotificationStackSizeCalculator.computeHeight(
                         /* notificationStackScrollLayout= */ this, mMaxDisplayedNotifications,
                         shelfIntrinsicHeight);
-        mIntrinsicContentHeight = height;
+        setIntrinsicContentHeight(height);
 
         // The topPadding can be bigger than the regular padding when qs is expanded, in that
         // state the maxPanelHeight and the contentHeight should be bigger
-        mContentHeight =
-                (int) (height + Math.max(getIntrinsicPadding(), getTopPadding()) + mBottomPadding);
+        setContentHeight(
+                (int) (height + Math.max(getIntrinsicPadding(), getTopPadding()) + mBottomPadding));
         updateScrollability();
         clampScrollPosition();
         updateStackPosition();
-        mAmbientState.setContentHeight(mContentHeight);
+        mAmbientState.setContentHeight(getContentHeight());
     }
 
     @Override
@@ -2795,7 +2786,7 @@
             float topAmount = getCurrentOverScrollAmount(true);
             float bottomAmount = getCurrentOverScrollAmount(false);
             if (velocityY < 0 && topAmount > 0) {
-                setOwnScrollY(mOwnScrollY - (int) topAmount);
+                setOwnScrollY(getOwnScrollY() - (int) topAmount);
                 if (!mShouldUseSplitNotificationShade) {
                     mDontReportNextOverScroll = true;
                     setOverScrollAmount(0, true, false);
@@ -2803,7 +2794,7 @@
                 mMaxOverScroll = Math.abs(velocityY) / 1000f * getRubberBandFactor(true /* onTop */)
                         * mOverflingDistance + topAmount;
             } else if (velocityY > 0 && bottomAmount > 0) {
-                setOwnScrollY((int) (mOwnScrollY + bottomAmount));
+                setOwnScrollY((int) (getOwnScrollY() + bottomAmount));
                 setOverScrollAmount(0, false, false);
                 mMaxOverScroll = Math.abs(velocityY) / 1000f
                         * getRubberBandFactor(false /* onTop */) * mOverflingDistance
@@ -2817,8 +2808,8 @@
             if (mExpandedInThisMotion) {
                 minScrollY = Math.min(minScrollY, mMaxScrollAfterExpand);
             }
-            mScroller.fling(mScrollX, mOwnScrollY, 1, velocityY, 0, 0, 0, minScrollY, 0,
-                    mExpandedInThisMotion && mOwnScrollY >= 0 ? 0 : Integer.MAX_VALUE / 2);
+            mScroller.fling(mScrollX, getOwnScrollY(), 1, velocityY, 0, 0, 0, minScrollY, 0,
+                    mExpandedInThisMotion && getOwnScrollY() >= 0 ? 0 : Integer.MAX_VALUE / 2);
 
             animateScroll();
         }
@@ -3139,11 +3130,11 @@
         final int scrollBoundaryStart = getScrollAmountToScrollBoundary();
         mAnimateStackYForContentHeightChange = true;
         // This is reset onLayout
-        if (endPosition <= mOwnScrollY - scrollBoundaryStart) {
+        if (endPosition <= getOwnScrollY() - scrollBoundaryStart) {
             // This child is fully scrolled of the top, so we have to deduct its height from the
             // scrollPosition
-            setOwnScrollY(mOwnScrollY - childHeight);
-        } else if (startingPosition < mOwnScrollY - scrollBoundaryStart) {
+            setOwnScrollY(getOwnScrollY() - childHeight);
+        } else if (startingPosition < getOwnScrollY() - scrollBoundaryStart) {
             // This child is currently being scrolled into, set the scroll position to the
             // start of this child
             setOwnScrollY(startingPosition + scrollBoundaryStart);
@@ -3765,7 +3756,7 @@
                         if (vscroll != 0) {
                             final int delta = (int) (vscroll * getVerticalScrollFactor());
                             final int range = getScrollRange();
-                            int oldScrollY = mOwnScrollY;
+                            int oldScrollY = getOwnScrollY();
                             int newScrollY = oldScrollY - delta;
                             if (newScrollY < 0) {
                                 newScrollY = 0;
@@ -3882,7 +3873,7 @@
                     if (scrollAmount != 0.0f) {
                         // The scrolling motion could not be compensated with the
                         // existing overScroll, we have to scroll the view
-                        customOverScrollBy((int) scrollAmount, mOwnScrollY,
+                        customOverScrollBy((int) scrollAmount, getOwnScrollY(),
                                 range, getHeight() / 2);
                         // If we're scrolling, leavebehinds should be dismissed
                         mController.checkSnoozeLeavebehind();
@@ -3914,7 +3905,7 @@
                                     onOverScrollFling(false, initialVelocity);
                                 }
                             } else {
-                                if (mScroller.springBack(mScrollX, mOwnScrollY, 0, 0, 0,
+                                if (mScroller.springBack(mScrollX, getOwnScrollY(), 0, 0, 0,
                                         getScrollRange())) {
                                     animateScroll();
                                 }
@@ -3928,7 +3919,7 @@
                 break;
             case ACTION_CANCEL:
                 if (mIsBeingDragged && getChildCount() > 0) {
-                    if (mScroller.springBack(mScrollX, mOwnScrollY, 0, 0, 0,
+                    if (mScroller.springBack(mScrollX, getOwnScrollY(), 0, 0, 0,
                             getScrollRange())) {
                         animateScroll();
                     }
@@ -4214,7 +4205,7 @@
                 setIsBeingDragged(false);
                 mActivePointerId = INVALID_POINTER;
                 recycleVelocityTracker();
-                if (mScroller.springBack(mScrollX, mOwnScrollY, 0, 0, 0, getScrollRange())) {
+                if (mScroller.springBack(mScrollX, getOwnScrollY(), 0, 0, 0, getScrollRange())) {
                     animateScroll();
                 }
                 break;
@@ -4312,10 +4303,10 @@
                         getHeight() - mPaddingBottom - getTopPadding() - mPaddingTop
                                 - mShelf.getIntrinsicHeight();
                 final int targetScrollY = Math.max(0,
-                        Math.min(mOwnScrollY + direction * viewportHeight, getScrollRange()));
-                if (targetScrollY != mOwnScrollY) {
-                    mScroller.startScroll(mScrollX, mOwnScrollY, 0,
-                            targetScrollY - mOwnScrollY);
+                        Math.min(getOwnScrollY() + direction * viewportHeight, getScrollRange()));
+                if (targetScrollY != getOwnScrollY()) {
+                    mScroller.startScroll(mScrollX, getOwnScrollY(), 0,
+                            targetScrollY - getOwnScrollY());
                     animateScroll();
                     return true;
                 }
@@ -4357,9 +4348,9 @@
             // it is based on notifications bottom, which is lower on split shade.
             // Here we prefer to use at least a minimum height defined for split shade.
             // Otherwise the expansion motion is too fast.
-            contentHeight = Math.max(mSplitShadeMinContentHeight, mContentHeight);
+            contentHeight = Math.max(mSplitShadeMinContentHeight, getContentHeight());
         } else {
-            contentHeight = mContentHeight;
+            contentHeight = getContentHeight();
         }
         return Math.max(mMaxLayoutHeight - contentHeight, 0);
     }
@@ -4569,7 +4560,7 @@
                         float diff = endPosition - layoutEnd;
                         mScrollViewFields.sendSyntheticScroll(diff);
                     }
-                    setOwnScrollY((int) (mOwnScrollY + endPosition - layoutEnd));
+                    setOwnScrollY((int) (getOwnScrollY() + endPosition - layoutEnd));
                     mDisallowScrollingInThisMotion = true;
                 }
             }
@@ -5080,7 +5071,7 @@
             event.setScrollY(mScrollViewFields.getScrollState().getScrollPosition());
             event.setMaxScrollY(mScrollViewFields.getScrollState().getMaxScrollPosition());
         } else {
-            event.setScrollY(mOwnScrollY);
+            event.setScrollY(getOwnScrollY());
             event.setMaxScrollY(getScrollRange());
         }
     }
@@ -5286,8 +5277,8 @@
 
         // If notifications are scrolled,
         // clear out scrollY by the time we push notifications offscreen
-        if (mOwnScrollY > 0) {
-            setOwnScrollY((int) MathUtils.lerp(mOwnScrollY, 0, mQsExpansionFraction));
+        if (getOwnScrollY() > 0) {
+            setOwnScrollY((int) MathUtils.lerp(getOwnScrollY(), 0, mQsExpansionFraction));
         }
         if (!FooterViewRefactor.isEnabled() && footerAffected) {
             updateFooter();
@@ -5295,6 +5286,15 @@
     }
 
     @VisibleForTesting
+    int getOwnScrollY() {
+        if (SceneContainerFlag.isEnabled()) {
+            return 0;
+        } else {
+            return mOwnScrollY;
+        }
+    }
+
+    @VisibleForTesting
     void setOwnScrollY(int ownScrollY) {
         setOwnScrollY(ownScrollY, false /* animateScrollChangeListener */);
     }
@@ -5311,11 +5311,11 @@
             return;
         }
 
-        if (ownScrollY != mOwnScrollY) {
+        if (ownScrollY != getOwnScrollY()) {
             // We still want to call the normal scrolled changed for accessibility reasons
-            onScrollChanged(mScrollX, ownScrollY, mScrollX, mOwnScrollY);
+            onScrollChanged(mScrollX, ownScrollY, mScrollX, getOwnScrollY());
             mOwnScrollY = ownScrollY;
-            mAmbientState.setScrollY(mOwnScrollY);
+            mAmbientState.setScrollY(getOwnScrollY());
             updateOnScrollChange();
             updateStackPosition(animateStackYChangeListener);
         }
@@ -5324,7 +5324,7 @@
     private void updateOnScrollChange() {
         SceneContainerFlag.assertInLegacyMode();
         if (mScrollListener != null) {
-            mScrollListener.accept(mOwnScrollY);
+            mScrollListener.accept(getOwnScrollY());
         }
         updateForwardAndBackwardScrollability();
         requestChildrenUpdate();
@@ -5517,12 +5517,7 @@
             println(pw, "hideAmount", mAmbientState.getHideAmount());
             println(pw, "ambientStateSwipingUp", mAmbientState.isSwipingUp());
             println(pw, "maxDisplayedNotifications", mMaxDisplayedNotifications);
-            println(pw, "intrinsicContentHeight", mIntrinsicContentHeight);
-            println(pw, "contentHeight", mContentHeight);
             println(pw, "intrinsicPadding", mIntrinsicPadding);
-            if (!SceneContainerFlag.isEnabled()) {
-                println(pw, "topPadding", getTopPadding());
-            }
             println(pw, "bottomPadding", mBottomPadding);
             dumpRoundedRectClipping(pw);
             println(pw, "requestedClipBounds", mRequestedClipBounds);
@@ -5547,6 +5542,12 @@
             println(pw, "isSmallLandscapeLockscreenEnabled", mIsSmallLandscapeLockscreenEnabled);
             mNotificationStackSizeCalculator.dump(pw, args);
             mScrollViewFields.dump(pw);
+            if (!SceneContainerFlag.isEnabled()) {
+                // fields which will be removed with SceneContainer
+                println(pw, "intrinsicContentHeight", getIntrinsicContentHeight());
+                println(pw, "contentHeight", getContentHeight());
+                println(pw, "topPadding", getTopPadding());
+            }
         });
         pw.println();
         pw.println("Contents:");
@@ -6891,7 +6892,7 @@
         public void expansionStateChanged(boolean isExpanding) {
             mExpandingNotification = isExpanding;
             if (!mExpandedInThisMotion) {
-                mMaxScrollAfterExpand = mOwnScrollY;
+                mMaxScrollAfterExpand = getOwnScrollY();
                 mExpandedInThisMotion = true;
             }
         }
@@ -6947,4 +6948,31 @@
         void onAnimationEnd(
                 List<ExpandableNotificationRow> viewsToRemove, @SelectedRows int selectedRows);
     }
+
+    // -------------------- Getters / Setters for the SceneContainer refactor ----------------------
+
+    /** Use {@link ScrollViewFields#intrinsicStackHeight}, when SceneContainerFlag is enabled. */
+    private int getContentHeight() {
+        SceneContainerFlag.assertInLegacyMode();
+        return mContentHeight;
+    }
+
+    private void setContentHeight(int contentHeight) {
+        SceneContainerFlag.assertInLegacyMode();
+        mContentHeight = contentHeight;
+    }
+
+    /**
+     * Use {@link ScrollViewFields#intrinsicStackHeight}, when SceneContainerFlag is enabled.
+     * @return the height of the content ignoring the footer.
+     */
+    public float getIntrinsicContentHeight() {
+        SceneContainerFlag.assertInLegacyMode();
+        return mIntrinsicContentHeight;
+    }
+
+    private void setIntrinsicContentHeight(float intrinsicContentHeight) {
+        SceneContainerFlag.assertInLegacyMode();
+        mIntrinsicContentHeight = intrinsicContentHeight;
+    }
 }
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/NotificationStackScrollLayoutController.java b/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/NotificationStackScrollLayoutController.java
index 04974b4..dc1a191 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/NotificationStackScrollLayoutController.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/NotificationStackScrollLayoutController.java
@@ -43,7 +43,6 @@
 import android.provider.Settings;
 import android.service.notification.StatusBarNotification;
 import android.util.Log;
-import android.util.Pair;
 import android.util.Property;
 import android.view.Display;
 import android.view.MotionEvent;
@@ -105,6 +104,7 @@
 import com.android.systemui.statusbar.notification.LaunchAnimationParameters;
 import com.android.systemui.statusbar.notification.NotificationActivityStarter;
 import com.android.systemui.statusbar.notification.NotificationWakeUpCoordinator;
+import com.android.systemui.statusbar.notification.collection.EntryWithDismissStats;
 import com.android.systemui.statusbar.notification.collection.NotifCollection;
 import com.android.systemui.statusbar.notification.collection.NotifPipeline;
 import com.android.systemui.statusbar.notification.collection.NotificationEntry;
@@ -1188,7 +1188,7 @@
 
     public int getIntrinsicContentHeight() {
         SceneContainerFlag.assertInLegacyMode();
-        return mView.getIntrinsicContentHeight();
+        return (int) mView.getIntrinsicContentHeight();
     }
 
     /**
@@ -1777,12 +1777,12 @@
             mNotifCollection.dismissAllNotifications(
                     mLockscreenUserManager.getCurrentUserId());
         } else {
-            final List<Pair<NotificationEntry, DismissedByUserStats>>
+            final List<EntryWithDismissStats>
                     entriesWithRowsDismissedFromShade = new ArrayList<>();
             for (ExpandableNotificationRow row : viewsToRemove) {
                 final NotificationEntry entry = row.getEntry();
                 entriesWithRowsDismissedFromShade.add(
-                        new Pair<>(entry, getDismissedByUserStats(entry)));
+                        new EntryWithDismissStats(entry, getDismissedByUserStats(entry)));
             }
             mNotifCollection.dismissNotifications(entriesWithRowsDismissedFromShade);
         }
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/domain/interactor/SharedNotificationContainerInteractor.kt b/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/domain/interactor/SharedNotificationContainerInteractor.kt
index 6042964..e644815 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/domain/interactor/SharedNotificationContainerInteractor.kt
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/domain/interactor/SharedNotificationContainerInteractor.kt
@@ -25,7 +25,7 @@
 import com.android.systemui.res.R
 import com.android.systemui.scene.shared.flag.SceneContainerFlag
 import com.android.systemui.shade.LargeScreenHeaderHelper
-import com.android.systemui.shade.domain.interactor.ShadeInteractor
+import com.android.systemui.shade.ShadeDisplayAware
 import com.android.systemui.statusbar.policy.SplitShadeStateController
 import dagger.Lazy
 import javax.inject.Inject
@@ -45,9 +45,8 @@
 class SharedNotificationContainerInteractor
 @Inject
 constructor(
-    private val context: Context,
+    @ShadeDisplayAware private val context: Context,
     private val splitShadeStateController: Lazy<SplitShadeStateController>,
-    private val shadeInteractor: Lazy<ShadeInteractor>,
     configurationInteractor: ConfigurationInteractor,
     keyguardInteractor: KeyguardInteractor,
     deviceEntryUdfpsInteractor: DeviceEntryUdfpsInteractor,
@@ -66,23 +65,14 @@
      * distinctUntilChanged() to this would cause configurationBasedDimensions to miss configuration
      * updates that affect other resources, like margins or the large screen header flag.
      */
-    private val dimensionsUpdateEventsWithShouldUseSplitShade: Flow<Boolean> =
-        if (SceneContainerFlag.isEnabled) {
-            combine(
-                configurationInteractor.onAnyConfigurationChange,
-                shadeInteractor.get().isShadeLayoutWide,
-            ) { _, isShadeLayoutWide ->
-                isShadeLayoutWide
-            }
-        } else {
-            configurationInteractor.onAnyConfigurationChange.map {
-                splitShadeStateController.get().shouldUseSplitNotificationShade(context.resources)
-            }
-        }
-
+    @Deprecated("Use SharedNotificationContainerViewModel.ConfigurationBasedDimensions instead")
     val configurationBasedDimensions: Flow<ConfigurationBasedDimensions> =
-        dimensionsUpdateEventsWithShouldUseSplitShade
-            .map { shouldUseSplitShade ->
+        configurationInteractor.onAnyConfigurationChange
+            .map {
+                val shouldUseSplitShade =
+                    splitShadeStateController
+                        .get()
+                        .shouldUseSplitNotificationShade(context.resources)
                 with(context.resources) {
                     ConfigurationBasedDimensions(
                         useSplitShade = shouldUseSplitShade,
@@ -101,6 +91,10 @@
                 }
             }
             .distinctUntilChanged()
+        get() {
+            SceneContainerFlag.assertInLegacyMode()
+            return field
+        }
 
     /**
      * The notification shelf can extend over the lock icon area if:
@@ -125,6 +119,7 @@
         _notificationStackChanged.value = _notificationStackChanged.value + 1
     }
 
+    @Deprecated("Use SharedNotificationContainerViewModel.ConfigurationBasedDimensions instead")
     data class ConfigurationBasedDimensions(
         val useSplitShade: Boolean,
         val useLargeScreenHeader: Boolean,
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/ui/viewbinder/NotificationListViewBinder.kt b/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/ui/viewbinder/NotificationListViewBinder.kt
index 787ff02..fd19f1f 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/ui/viewbinder/NotificationListViewBinder.kt
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/ui/viewbinder/NotificationListViewBinder.kt
@@ -22,6 +22,7 @@
 import com.android.app.tracing.TraceUtils.traceAsync
 import com.android.internal.logging.MetricsLogger
 import com.android.internal.logging.nano.MetricsProto
+import com.android.systemui.Flags
 import com.android.systemui.common.ui.ConfigurationState
 import com.android.systemui.common.ui.view.setImportantForAccessibilityYesNo
 import com.android.systemui.dagger.qualifiers.Background
@@ -145,7 +146,9 @@
             // The footer needs to be re-inflated every time the theme or the font size changes.
             configuration
                 .inflateLayout<FooterView>(
-                    R.layout.status_bar_notification_footer,
+                    if (Flags.notificationsRedesignFooterView())
+                        R.layout.status_bar_notification_footer_redesign
+                    else R.layout.status_bar_notification_footer,
                     parentView,
                     attachToRoot = false,
                 )
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/ui/viewbinder/SharedNotificationContainerBinder.kt b/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/ui/viewbinder/SharedNotificationContainerBinder.kt
index 54b5ca3..ce89d78 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/ui/viewbinder/SharedNotificationContainerBinder.kt
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/ui/viewbinder/SharedNotificationContainerBinder.kt
@@ -18,10 +18,12 @@
 
 import androidx.lifecycle.Lifecycle
 import androidx.lifecycle.repeatOnLifecycle
+import com.android.systemui.Flags
 import com.android.systemui.common.ui.view.onLayoutChanged
 import com.android.systemui.communal.domain.interactor.CommunalSettingsInteractor
 import com.android.systemui.dagger.SysUISingleton
 import com.android.systemui.dagger.qualifiers.Main
+import com.android.systemui.keyguard.domain.interactor.KeyguardInteractor
 import com.android.systemui.keyguard.ui.viewmodel.ViewStateAccessor
 import com.android.systemui.lifecycle.repeatWhenAttached
 import com.android.systemui.scene.shared.flag.SceneContainerFlag
@@ -48,8 +50,19 @@
     private val notificationScrollViewBinder: NotificationScrollViewBinder,
     private val communalSettingsInteractor: CommunalSettingsInteractor,
     @Main private val mainImmediateDispatcher: CoroutineDispatcher,
+    val keyguardInteractor: KeyguardInteractor,
 ) {
 
+    private val calculateMaxNotifications: (Float, Boolean) -> Int = { space, extraShelfSpace ->
+        val shelfHeight = controller.getShelfHeight().toFloat()
+        notificationStackSizeCalculator.computeMaxKeyguardNotifications(
+            controller.view,
+            space,
+            if (extraShelfSpace) shelfHeight else 0f,
+            shelfHeight,
+        )
+    }
+
     fun bind(
         view: SharedNotificationContainer,
         viewModel: SharedNotificationContainerViewModel,
@@ -107,17 +120,9 @@
                     }
 
                     launch {
-                        viewModel
-                            .getMaxNotifications { space, extraShelfSpace ->
-                                val shelfHeight = controller.getShelfHeight().toFloat()
-                                notificationStackSizeCalculator.computeMaxKeyguardNotifications(
-                                    controller.getView(),
-                                    space,
-                                    if (extraShelfSpace) shelfHeight else 0f,
-                                    shelfHeight,
-                                )
-                            }
-                            .collect { controller.setMaxDisplayedNotifications(it) }
+                        viewModel.getMaxNotifications(calculateMaxNotifications).collect {
+                            controller.setMaxDisplayedNotifications(it)
+                        }
                     }
 
                     if (!SceneContainerFlag.isEnabled) {
@@ -136,6 +141,30 @@
                         }
                     }
 
+                    if (!SceneContainerFlag.isEnabled) {
+                        if (Flags.magicPortraitWallpapers()) {
+                            launch {
+                                viewModel
+                                    .getNotificationStackAbsoluteBottom(
+                                        calculateMaxNotifications = calculateMaxNotifications,
+                                        calculateHeight = { maxNotifications ->
+                                            notificationStackSizeCalculator.computeHeight(
+                                                maxNotifs = maxNotifications,
+                                                shelfHeight = controller.getShelfHeight().toFloat(),
+                                                stack = controller.view,
+                                            )
+                                        },
+                                        controller.getShelfHeight().toFloat(),
+                                    )
+                                    .collect { bottom ->
+                                        keyguardInteractor.setNotificationStackAbsoluteBottom(
+                                            bottom
+                                        )
+                                    }
+                            }
+                        }
+                    }
+
                     launch { viewModel.translationX.collect { x -> controller.translationX = x } }
 
                     launch {
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 9515029..e6663d5 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
@@ -19,9 +19,11 @@
 
 package com.android.systemui.statusbar.notification.stack.ui.viewmodel
 
+import android.content.Context
 import androidx.annotation.VisibleForTesting
 import com.android.app.tracing.coroutines.flow.flowName
 import com.android.systemui.common.shared.model.NotificationContainerBounds
+import com.android.systemui.common.ui.domain.interactor.ConfigurationInteractor
 import com.android.systemui.communal.domain.interactor.CommunalSceneInteractor
 import com.android.systemui.dagger.SysUISingleton
 import com.android.systemui.dagger.qualifiers.Application
@@ -66,10 +68,14 @@
 import com.android.systemui.keyguard.ui.viewmodel.PrimaryBouncerToGoneTransitionViewModel
 import com.android.systemui.keyguard.ui.viewmodel.PrimaryBouncerToLockscreenTransitionViewModel
 import com.android.systemui.keyguard.ui.viewmodel.ViewStateAccessor
+import com.android.systemui.res.R
 import com.android.systemui.scene.shared.flag.SceneContainerFlag
 import com.android.systemui.scene.shared.model.Scenes
+import com.android.systemui.shade.LargeScreenHeaderHelper
 import com.android.systemui.shade.domain.interactor.ShadeInteractor
-import com.android.systemui.shade.shared.flag.DualShade
+import com.android.systemui.shade.shared.model.ShadeMode.Dual
+import com.android.systemui.shade.shared.model.ShadeMode.Single
+import com.android.systemui.shade.shared.model.ShadeMode.Split
 import com.android.systemui.statusbar.notification.domain.interactor.HeadsUpNotificationInteractor
 import com.android.systemui.statusbar.notification.stack.domain.interactor.NotificationStackAppearanceInteractor
 import com.android.systemui.statusbar.notification.stack.domain.interactor.SharedNotificationContainerInteractor
@@ -90,6 +96,7 @@
 import kotlinx.coroutines.flow.combineTransform
 import kotlinx.coroutines.flow.distinctUntilChanged
 import kotlinx.coroutines.flow.emptyFlow
+import kotlinx.coroutines.flow.filter
 import kotlinx.coroutines.flow.first
 import kotlinx.coroutines.flow.flatMapLatest
 import kotlinx.coroutines.flow.flow
@@ -108,6 +115,8 @@
     private val interactor: SharedNotificationContainerInteractor,
     dumpManager: DumpManager,
     @Application applicationScope: CoroutineScope,
+    private val context: Context,
+    configurationInteractor: ConfigurationInteractor,
     private val keyguardInteractor: KeyguardInteractor,
     private val keyguardTransitionInteractor: KeyguardTransitionInteractor,
     private val shadeInteractor: ShadeInteractor,
@@ -145,6 +154,7 @@
     private val communalSceneInteractor: CommunalSceneInteractor,
     // Lazy because it's only used in the SceneContainer + Dual Shade configuration.
     headsUpNotificationInteractor: Lazy<HeadsUpNotificationInteractor>,
+    private val largeScreenHeaderHelperLazy: Lazy<LargeScreenHeaderHelper>,
     unfoldTransitionInteractor: UnfoldTransitionInteractor,
 ) : FlowDumperImpl(dumpManager) {
 
@@ -187,33 +197,62 @@
 
     @VisibleForTesting
     val paddingTopDimen: Flow<Int> =
-        interactor.configurationBasedDimensions
-            .map {
-                when {
-                    it.useLargeScreenHeader -> it.marginTopLargeScreen
-                    else -> it.marginTop
+        if (SceneContainerFlag.isEnabled) {
+                configurationInteractor.onAnyConfigurationChange.map {
+                    with(context.resources) {
+                        val useLargeScreenHeader =
+                            getBoolean(R.bool.config_use_large_screen_shade_header)
+                        if (useLargeScreenHeader) {
+                            largeScreenHeaderHelperLazy.get().getLargeScreenHeaderHeight()
+                        } else {
+                            getDimensionPixelSize(R.dimen.notification_panel_margin_top)
+                        }
+                    }
+                }
+            } else {
+                interactor.configurationBasedDimensions.map {
+                    when {
+                        it.useLargeScreenHeader -> it.marginTopLargeScreen
+                        else -> it.marginTop
+                    }
                 }
             }
             .distinctUntilChanged()
             .dumpWhileCollecting("paddingTopDimen")
 
     val configurationBasedDimensions: Flow<ConfigurationBasedDimensions> =
-        interactor.configurationBasedDimensions
-            .map {
-                val marginTop =
-                    when {
-                        // y position of the NSSL in the window needs to be 0 under scene container
-                        SceneContainerFlag.isEnabled -> 0
-                        it.useLargeScreenHeader -> it.marginTopLargeScreen
-                        else -> it.marginTop
+        if (SceneContainerFlag.isEnabled) {
+                combine(
+                    shadeInteractor.isShadeLayoutWide,
+                    configurationInteractor.onAnyConfigurationChange,
+                ) { isShadeLayoutWide, _ ->
+                    with(context.resources) {
+                        // TODO(b/338033836): Define separate horizontal margins for dual shade.
+                        val marginHorizontal =
+                            getDimensionPixelSize(R.dimen.notification_panel_margin_horizontal)
+                        ConfigurationBasedDimensions(
+                            marginStart = if (isShadeLayoutWide) 0 else marginHorizontal,
+                            marginEnd = marginHorizontal,
+                            marginBottom =
+                                getDimensionPixelSize(R.dimen.notification_panel_margin_bottom),
+                            // y position of the NSSL in the window needs to be 0 under scene
+                            // container
+                            marginTop = 0,
+                            useSplitShade = isShadeLayoutWide,
+                        )
                     }
-                ConfigurationBasedDimensions(
-                    marginStart = if (it.useSplitShade) 0 else it.marginHorizontal,
-                    marginEnd = it.marginHorizontal,
-                    marginBottom = it.marginBottom,
-                    marginTop = marginTop,
-                    useSplitShade = it.useSplitShade,
-                )
+                }
+            } else {
+                interactor.configurationBasedDimensions.map {
+                    ConfigurationBasedDimensions(
+                        marginStart = if (it.useSplitShade) 0 else it.marginHorizontal,
+                        marginEnd = it.marginHorizontal,
+                        marginBottom = it.marginBottom,
+                        marginTop =
+                            if (it.useLargeScreenHeader) it.marginTopLargeScreen else it.marginTop,
+                        useSplitShade = it.useSplitShade,
+                    )
+                }
             }
             .distinctUntilChanged()
             .dumpWhileCollecting("configurationBasedDimensions")
@@ -221,13 +260,15 @@
     /** If the user is visually on one of the unoccluded lockscreen states. */
     val isOnLockscreen: Flow<Boolean> =
         anyOf(
-                keyguardTransitionInteractor.isFinishedIn(AOD),
-                keyguardTransitionInteractor.isFinishedIn(DOZING),
-                keyguardTransitionInteractor.isFinishedIn(ALTERNATE_BOUNCER),
-                keyguardTransitionInteractor.isFinishedIn(
-                    scene = Scenes.Bouncer,
-                    stateWithoutSceneContainer = PRIMARY_BOUNCER,
-                ),
+                keyguardTransitionInteractor.transitionValue(AOD).map { it > 0f },
+                keyguardTransitionInteractor.transitionValue(DOZING).map { it > 0f },
+                keyguardTransitionInteractor.transitionValue(ALTERNATE_BOUNCER).map { it > 0f },
+                keyguardTransitionInteractor
+                    .transitionValue(
+                        scene = Scenes.Bouncer,
+                        stateWithoutSceneContainer = PRIMARY_BOUNCER,
+                    )
+                    .map { it > 0f },
                 keyguardTransitionInteractor.transitionValue(LOCKSCREEN).map { it > 0f },
             )
             .flowName("isOnLockscreen")
@@ -404,42 +445,60 @@
      * notifications unless in splitshade.
      */
     private val alphaForShadeAndQsExpansion: Flow<Float> =
-        if (DualShade.isEnabled) {
-                combineTransform(
-                    headsUpNotificationInteractor.get().isHeadsUpOrAnimatingAway,
-                    shadeInteractor.shadeExpansion,
-                    shadeInteractor.qsExpansion,
-                ) { isHeadsUpOrAnimatingAway, shadeExpansion, qsExpansion ->
-                    if (isHeadsUpOrAnimatingAway) {
-                        // Ensure HUNs will be visible in QS shade (at least while unlocked)
-                        emit(1f)
-                    } else if (shadeExpansion > 0f || qsExpansion > 0f) {
-                        // Fade out as QS shade expands
-                        emit(1f - qsExpansion)
-                    }
-                }
-            } else {
-                interactor.configurationBasedDimensions.flatMapLatest { configurationBasedDimensions
-                    ->
-                    combineTransform(shadeInteractor.shadeExpansion, shadeInteractor.qsExpansion) {
-                        shadeExpansion,
-                        qsExpansion ->
-                        if (shadeExpansion > 0f || qsExpansion > 0f) {
-                            if (configurationBasedDimensions.useSplitShade) {
-                                emit(1f)
-                            } else if (qsExpansion == 1f) {
+        if (SceneContainerFlag.isEnabled) {
+            shadeInteractor.shadeMode.flatMapLatest { shadeMode ->
+                when (shadeMode) {
+                    Single ->
+                        combineTransform(
+                            shadeInteractor.shadeExpansion,
+                            shadeInteractor.qsExpansion,
+                        ) { shadeExpansion, qsExpansion ->
+                            if (qsExpansion == 1f) {
                                 // Ensure HUNs will be visible in QS shade (at least while unlocked)
                                 emit(1f)
-                            } else {
+                            } else if (shadeExpansion > 0f || qsExpansion > 0f) {
                                 // Fade as QS shade expands
                                 emit(1f - qsExpansion)
                             }
                         }
+                    Split -> isAnyExpanded.filter { it }.map { 1f }
+                    Dual ->
+                        combineTransform(
+                            headsUpNotificationInteractor.get().isHeadsUpOrAnimatingAway,
+                            shadeInteractor.shadeExpansion,
+                            shadeInteractor.qsExpansion,
+                        ) { isHeadsUpOrAnimatingAway, shadeExpansion, qsExpansion ->
+                            if (isHeadsUpOrAnimatingAway) {
+                                // Ensure HUNs will be visible in QS shade (at least while unlocked)
+                                emit(1f)
+                            } else if (shadeExpansion > 0f || qsExpansion > 0f) {
+                                // Fade out as QS shade expands
+                                emit(1f - qsExpansion)
+                            }
+                        }
+                }
+            }
+        } else {
+            interactor.configurationBasedDimensions.flatMapLatest { configurationBasedDimensions ->
+                combineTransform(shadeInteractor.shadeExpansion, shadeInteractor.qsExpansion) {
+                    shadeExpansion,
+                    qsExpansion ->
+                    if (shadeExpansion > 0f || qsExpansion > 0f) {
+                        if (configurationBasedDimensions.useSplitShade) {
+                            emit(1f)
+                        } else if (qsExpansion == 1f) {
+                            // Ensure HUNs will be visible in QS shade (at least while unlocked)
+                            emit(1f)
+                        } else {
+                            // Fade as QS shade expands
+                            emit(1f - qsExpansion)
+                        }
                     }
                 }
             }
-            .onStart { emit(1f) }
-            .dumpWhileCollecting("alphaForShadeAndQsExpansion")
+        }
+        .onStart { emit(1f) }
+        .dumpWhileCollecting("alphaForShadeAndQsExpansion")
 
     val panelAlpha = keyguardInteractor.panelAlpha
 
@@ -672,6 +731,36 @@
             .dumpWhileCollecting("maxNotifications")
     }
 
+    /**
+     * Wallpaper needs the absolute bottom of notification stack to avoid occlusion
+     *
+     * @param calculateMaxNotifications is required by getMaxNotifications as calculateSpace by
+     *   calling computeMaxKeyguardNotifications in NotificationStackSizeCalculator
+     * @param calculateHeight is calling computeHeight in NotificationStackSizeCalculator The edge
+     *   case is that when maxNotifications is 0, we won't take shelfHeight into account
+     */
+    fun getNotificationStackAbsoluteBottom(
+        calculateMaxNotifications: (Float, Boolean) -> Int,
+        calculateHeight: (Int) -> Float,
+        shelfHeight: Float,
+    ): Flow<Float> {
+        SceneContainerFlag.assertInLegacyMode()
+
+        return combine(
+            getMaxNotifications(calculateMaxNotifications).map {
+                val height = calculateHeight(it)
+                if (it == 0) {
+                    height - shelfHeight
+                } else {
+                    height
+                }
+            },
+            bounds.map { it.top },
+        ) { height, top ->
+            top + height
+        }
+    }
+
     fun notificationStackChanged() {
         interactor.notificationStackChanged()
     }
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 b469e3d..65663fd 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/CentralSurfacesImpl.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/CentralSurfacesImpl.java
@@ -646,6 +646,8 @@
             NavigationBarController navigationBarController,
             AccessibilityFloatingMenuController accessibilityFloatingMenuController,
             Lazy<AssistManager> assistManagerLazy,
+            // TODO: b/374267505 - Decouple the config change needed for shade window classes from
+            //  the one for other windows.
             ConfigurationController configurationController,
             NotificationShadeWindowController notificationShadeWindowController,
             Lazy<NotificationShadeWindowViewController> notificationShadeWindowViewControllerLazy,
@@ -1975,6 +1977,10 @@
      * meantime, just update the things that we know change.
      */
     void updateResources() {
+        // TODO: b/374267505 - we shouldn't propagate this from here. Each class should be
+        //  listening at the correct configuration change. For example, shade window classes should
+        //  be listening at @ShadeDisplayAware configurations (as it can be on a different display.
+
         // Update the quick setting tiles
         if (mQSPanelController != null) {
             mQSPanelController.updateResources();
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 013141b..f85785f 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
@@ -56,7 +56,7 @@
 import com.android.systemui.statusbar.OperatorNameView;
 import com.android.systemui.statusbar.OperatorNameViewController;
 import com.android.systemui.statusbar.StatusBarState;
-import com.android.systemui.statusbar.chips.notification.shared.StatusBarNotifChips;
+import com.android.systemui.statusbar.chips.ron.shared.StatusBarRonChips;
 import com.android.systemui.statusbar.core.StatusBarConnectedDisplays;
 import com.android.systemui.statusbar.core.StatusBarSimpleFragment;
 import com.android.systemui.statusbar.disableflags.DisableFlagsLogger;
@@ -657,7 +657,7 @@
         }
         boolean showSecondaryOngoingActivityChip =
                 Flags.statusBarScreenSharingChips()
-                        && StatusBarNotifChips.isEnabled()
+                        && StatusBarRonChips.isEnabled()
                         && mHasSecondaryOngoingActivity;
 
         return new StatusBarVisibilityModel(
@@ -699,7 +699,7 @@
 
         boolean showSecondaryOngoingActivityChip =
                 // Secondary chips are only supported when RONs are enabled.
-                StatusBarNotifChips.isEnabled()
+                StatusBarRonChips.isEnabled()
                         && visibilityModel.getShowSecondaryOngoingActivityChip()
                         && !disableNotifications;
         if (showSecondaryOngoingActivityChip) {
@@ -832,7 +832,7 @@
     }
 
     private void showSecondaryOngoingActivityChip(boolean animate) {
-        StatusBarNotifChips.assertInNewMode();
+        StatusBarRonChips.assertInNewMode();
         StatusBarSimpleFragment.assertInLegacyMode();
         animateShow(mSecondaryOngoingActivityChip, animate);
     }
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/shared/ui/binder/HomeStatusBarViewBinder.kt b/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/shared/ui/binder/HomeStatusBarViewBinder.kt
index 11d7339..473f956 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/shared/ui/binder/HomeStatusBarViewBinder.kt
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/shared/ui/binder/HomeStatusBarViewBinder.kt
@@ -27,7 +27,6 @@
 import com.android.systemui.lifecycle.repeatWhenAttached
 import com.android.systemui.res.R
 import com.android.systemui.scene.shared.flag.SceneContainerFlag
-import com.android.systemui.statusbar.chips.notification.shared.StatusBarNotifChips
 import com.android.systemui.statusbar.chips.ui.binder.OngoingActivityChipBinder
 import com.android.systemui.statusbar.chips.ui.model.OngoingActivityChipModel
 import com.android.systemui.statusbar.core.StatusBarSimpleFragment
@@ -85,7 +84,7 @@
                     }
                 }
 
-                if (Flags.statusBarScreenSharingChips() && !StatusBarNotifChips.isEnabled) {
+                if (Flags.statusBarScreenSharingChips() && !Flags.statusBarRonChips()) {
                     val primaryChipView: View =
                         view.requireViewById(R.id.ongoing_activity_chip_primary)
                     launch {
@@ -121,7 +120,7 @@
                     }
                 }
 
-                if (Flags.statusBarScreenSharingChips() && StatusBarNotifChips.isEnabled) {
+                if (Flags.statusBarScreenSharingChips() && Flags.statusBarRonChips()) {
                     val primaryChipView: View =
                         view.requireViewById(R.id.ongoing_activity_chip_primary)
                     val secondaryChipView: View =
diff --git a/packages/SystemUI/src/com/android/systemui/volume/dialog/domain/interactor/VolumeDialogCallbacksInteractor.kt b/packages/SystemUI/src/com/android/systemui/volume/dialog/domain/interactor/VolumeDialogCallbacksInteractor.kt
index 3d125b8..fa108842 100644
--- a/packages/SystemUI/src/com/android/systemui/volume/dialog/domain/interactor/VolumeDialogCallbacksInteractor.kt
+++ b/packages/SystemUI/src/com/android/systemui/volume/dialog/domain/interactor/VolumeDialogCallbacksInteractor.kt
@@ -105,8 +105,8 @@
             scope.trySend(VolumeDialogEventModel.ShowSafetyWarning(flags))
         }
 
-        override fun onAccessibilityModeChanged(showA11yStream: Boolean) {
-            scope.trySend(VolumeDialogEventModel.AccessibilityModeChanged(showA11yStream))
+        override fun onAccessibilityModeChanged(showA11yStream: Boolean?) {
+            scope.trySend(VolumeDialogEventModel.AccessibilityModeChanged(showA11yStream == true))
         }
 
         // Captions button is remove from the Volume Dialog
diff --git a/packages/SystemUI/src/com/android/systemui/volume/dialog/domain/interactor/VolumeDialogVisibilityInteractor.kt b/packages/SystemUI/src/com/android/systemui/volume/dialog/domain/interactor/VolumeDialogVisibilityInteractor.kt
index 2668589b..fb108c5 100644
--- a/packages/SystemUI/src/com/android/systemui/volume/dialog/domain/interactor/VolumeDialogVisibilityInteractor.kt
+++ b/packages/SystemUI/src/com/android/systemui/volume/dialog/domain/interactor/VolumeDialogVisibilityInteractor.kt
@@ -60,7 +60,7 @@
 ) {
 
     @SuppressLint("SharedFlowCreation")
-    private val mutableDismissDialogEvents = MutableSharedFlow<Unit>()
+    private val mutableDismissDialogEvents = MutableSharedFlow<Unit>(extraBufferCapacity = 1)
     val dialogVisibility: Flow<VolumeDialogVisibilityModel> = repository.dialogVisibility
 
     init {
@@ -74,7 +74,7 @@
             .mapNotNull { it.toVisibilityModel() }
             .onEach { model ->
                 updateVisibility { model }
-                if (model is VolumeDialogVisibilityModel.Visible) {
+                if (model is Visible) {
                     resetDismissTimeout()
                 }
             }
@@ -87,17 +87,17 @@
      */
     fun dismissDialog(reason: Int) {
         updateVisibility { visibilityModel ->
-            if (visibilityModel is VolumeDialogVisibilityModel.Dismissed) {
+            if (visibilityModel is Dismissed) {
                 visibilityModel
             } else {
-                VolumeDialogVisibilityModel.Dismissed(reason)
+                Dismissed(reason)
             }
         }
     }
 
     /** Resets current dialog timeout. */
-    suspend fun resetDismissTimeout() {
-        mutableDismissDialogEvents.emit(Unit)
+    fun resetDismissTimeout() {
+        mutableDismissDialogEvents.tryEmit(Unit)
     }
 
     private fun updateVisibility(
diff --git a/packages/SystemUI/src/com/android/systemui/volume/dialog/ringer/ui/binder/VolumeDialogRingerViewBinder.kt b/packages/SystemUI/src/com/android/systemui/volume/dialog/ringer/ui/binder/VolumeDialogRingerViewBinder.kt
new file mode 100644
index 0000000..c05a0b2
--- /dev/null
+++ b/packages/SystemUI/src/com/android/systemui/volume/dialog/ringer/ui/binder/VolumeDialogRingerViewBinder.kt
@@ -0,0 +1,48 @@
+/*
+ * 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.volume.dialog.ringer.ui.binder
+
+import android.view.View
+import com.android.systemui.lifecycle.WindowLifecycleState
+import com.android.systemui.lifecycle.repeatWhenAttached
+import com.android.systemui.lifecycle.setSnapshotBinding
+import com.android.systemui.lifecycle.viewModel
+import com.android.systemui.volume.dialog.dagger.scope.VolumeDialogScope
+import com.android.systemui.volume.dialog.ringer.ui.viewmodel.VolumeDialogRingerDrawerViewModel
+import javax.inject.Inject
+import kotlinx.coroutines.awaitCancellation
+
+@VolumeDialogScope
+class VolumeDialogRingerViewBinder
+@Inject
+constructor(private val viewModelFactory: VolumeDialogRingerDrawerViewModel.Factory) {
+
+    fun bind(view: View) {
+        with(view) {
+            repeatWhenAttached {
+                viewModel(
+                    traceName = "VolumeDialogRingerViewBinder",
+                    minWindowLifecycleState = WindowLifecycleState.ATTACHED,
+                    factory = { viewModelFactory.create() },
+                ) { viewModel ->
+                    setSnapshotBinding {}
+                    awaitCancellation()
+                }
+            }
+        }
+    }
+}
diff --git a/packages/SystemUI/src/com/android/systemui/volume/dialog/sliders/domain/interactor/VolumeDialogSliderInteractor.kt b/packages/SystemUI/src/com/android/systemui/volume/dialog/sliders/domain/interactor/VolumeDialogSliderInteractor.kt
index f78a8dc..876bf2c 100644
--- a/packages/SystemUI/src/com/android/systemui/volume/dialog/sliders/domain/interactor/VolumeDialogSliderInteractor.kt
+++ b/packages/SystemUI/src/com/android/systemui/volume/dialog/sliders/domain/interactor/VolumeDialogSliderInteractor.kt
@@ -25,6 +25,7 @@
 import dagger.assisted.AssistedFactory
 import dagger.assisted.AssistedInject
 import kotlinx.coroutines.flow.Flow
+import kotlinx.coroutines.flow.distinctUntilChanged
 import kotlinx.coroutines.flow.mapNotNull
 
 /** Operates a state of particular slider of the Volume Dialog. */
@@ -37,12 +38,23 @@
 ) {
 
     val slider: Flow<VolumeDialogStreamModel> =
-        volumeDialogStateInteractor.volumeDialogState.mapNotNull {
-            it.streamModels[sliderType.audioStream]
-        }
+        volumeDialogStateInteractor.volumeDialogState
+            .mapNotNull {
+                it.streamModels[sliderType.audioStream]?.run {
+                    if (level < levelMin || level > levelMax) {
+                        copy(level = level.coerceIn(levelMin, levelMax))
+                    } else {
+                        this
+                    }
+                }
+            }
+            .distinctUntilChanged()
 
     fun setStreamVolume(userLevel: Int) {
-        volumeDialogController.setStreamVolume(sliderType.audioStream, userLevel)
+        with(volumeDialogController) {
+            setStreamVolume(sliderType.audioStream, userLevel)
+            setActiveStream(sliderType.audioStream)
+        }
     }
 
     @VolumeDialogScope
diff --git a/packages/SystemUI/src/com/android/systemui/volume/dialog/sliders/ui/VolumeDialogSliderViewBinder.kt b/packages/SystemUI/src/com/android/systemui/volume/dialog/sliders/ui/VolumeDialogSliderViewBinder.kt
index 25a5f28..5c4d53a 100644
--- a/packages/SystemUI/src/com/android/systemui/volume/dialog/sliders/ui/VolumeDialogSliderViewBinder.kt
+++ b/packages/SystemUI/src/com/android/systemui/volume/dialog/sliders/ui/VolumeDialogSliderViewBinder.kt
@@ -16,32 +16,55 @@
 
 package com.android.systemui.volume.dialog.sliders.ui
 
+import android.animation.Animator
+import android.animation.ObjectAnimator
 import android.view.View
-import androidx.lifecycle.viewmodel.compose.viewModel
+import android.view.animation.DecelerateInterpolator
 import com.android.systemui.lifecycle.WindowLifecycleState
 import com.android.systemui.lifecycle.repeatWhenAttached
-import com.android.systemui.lifecycle.setSnapshotBinding
 import com.android.systemui.lifecycle.viewModel
+import com.android.systemui.res.R
 import com.android.systemui.volume.dialog.dagger.scope.VolumeDialogScope
+import com.android.systemui.volume.dialog.shared.model.VolumeDialogStreamModel
 import com.android.systemui.volume.dialog.sliders.ui.viewmodel.VolumeDialogSliderViewModel
+import com.android.systemui.volume.dialog.ui.utils.JankListenerFactory
+import com.android.systemui.volume.dialog.ui.utils.awaitAnimation
+import com.google.android.material.slider.LabelFormatter
+import com.google.android.material.slider.Slider
 import dagger.assisted.Assisted
 import dagger.assisted.AssistedFactory
 import dagger.assisted.AssistedInject
+import kotlin.math.roundToInt
 import kotlinx.coroutines.awaitCancellation
+import kotlinx.coroutines.flow.launchIn
+import kotlinx.coroutines.flow.onEach
+
+private const val PROGRESS_CHANGE_ANIMATION_DURATION_MS = 80L
 
 class VolumeDialogSliderViewBinder
 @AssistedInject
-constructor(@Assisted private val viewModelProvider: () -> VolumeDialogSliderViewModel) {
+constructor(
+    @Assisted private val viewModelProvider: () -> VolumeDialogSliderViewModel,
+    private val jankListenerFactory: JankListenerFactory,
+) {
 
     fun bind(view: View) {
         with(view) {
+            val sliderView: Slider =
+                requireViewById<Slider>(R.id.volume_dialog_slider).apply {
+                    labelBehavior = LabelFormatter.LABEL_GONE
+                }
             repeatWhenAttached {
                 viewModel(
                     traceName = "VolumeDialogSliderViewBinder",
                     minWindowLifecycleState = WindowLifecycleState.ATTACHED,
                     factory = { viewModelProvider() },
                 ) { viewModel ->
-                    setSnapshotBinding {}
+                    sliderView.addOnChangeListener { _, value, fromUser ->
+                        viewModel.setStreamVolume(value.roundToInt(), fromUser)
+                    }
+
+                    viewModel.model.onEach { it.bindToSlider(sliderView) }.launchIn(this)
 
                     awaitCancellation()
                 }
@@ -49,6 +72,19 @@
         }
     }
 
+    private suspend fun VolumeDialogStreamModel.bindToSlider(slider: Slider) {
+        with(slider) {
+            valueFrom = levelMin.toFloat()
+            valueTo = levelMax.toFloat()
+            // coerce the current value to the new value range before animating it
+            value = value.coerceIn(valueFrom, valueTo)
+            setValueAnimated(
+                level.toFloat(),
+                jankListenerFactory.update(this, PROGRESS_CHANGE_ANIMATION_DURATION_MS),
+            )
+        }
+    }
+
     @AssistedFactory
     @VolumeDialogScope
     interface Factory {
@@ -58,3 +94,16 @@
         ): VolumeDialogSliderViewBinder
     }
 }
+
+private suspend fun Slider.setValueAnimated(
+    newValue: Float,
+    jankListener: Animator.AnimatorListener,
+) {
+    ObjectAnimator.ofFloat(value, newValue)
+        .apply {
+            duration = PROGRESS_CHANGE_ANIMATION_DURATION_MS
+            interpolator = DecelerateInterpolator()
+            addListener(jankListener)
+        }
+        .awaitAnimation<Float> { value = it }
+}
diff --git a/packages/SystemUI/src/com/android/systemui/volume/dialog/sliders/ui/VolumeDialogSlidersViewBinder.kt b/packages/SystemUI/src/com/android/systemui/volume/dialog/sliders/ui/VolumeDialogSlidersViewBinder.kt
index 0a00f70..f486fe1 100644
--- a/packages/SystemUI/src/com/android/systemui/volume/dialog/sliders/ui/VolumeDialogSlidersViewBinder.kt
+++ b/packages/SystemUI/src/com/android/systemui/volume/dialog/sliders/ui/VolumeDialogSlidersViewBinder.kt
@@ -16,14 +16,20 @@
 
 package com.android.systemui.volume.dialog.sliders.ui
 
+import android.view.LayoutInflater
 import android.view.View
+import android.view.ViewGroup
+import androidx.annotation.LayoutRes
+import androidx.compose.ui.util.fastForEachIndexed
 import com.android.systemui.lifecycle.WindowLifecycleState
 import com.android.systemui.lifecycle.repeatWhenAttached
 import com.android.systemui.lifecycle.setSnapshotBinding
 import com.android.systemui.lifecycle.viewModel
+import com.android.systemui.res.R
 import com.android.systemui.volume.dialog.dagger.scope.VolumeDialogScope
 import com.android.systemui.volume.dialog.sliders.ui.viewmodel.VolumeDialogSlidersViewModel
 import javax.inject.Inject
+import kotlin.math.abs
 import kotlinx.coroutines.awaitCancellation
 
 @VolumeDialogScope
@@ -33,17 +39,44 @@
 
     fun bind(view: View) {
         with(view) {
+            val volumeDialog: View = requireViewById(R.id.volume_dialog)
+            val floatingSlidersContainer: ViewGroup =
+                requireViewById(R.id.volume_dialog_floating_sliders_container)
             repeatWhenAttached {
                 viewModel(
                     traceName = "VolumeDialogSlidersViewBinder",
                     minWindowLifecycleState = WindowLifecycleState.ATTACHED,
                     factory = { viewModelFactory.create() },
                 ) { viewModel ->
-                    setSnapshotBinding {}
+                    setSnapshotBinding {
+                        viewModel.uiModel?.sliderViewBinder?.bind(volumeDialog)
 
+                        val floatingSliderViewBinders =
+                            viewModel.uiModel?.floatingSliderViewBinders ?: emptyList()
+                        floatingSlidersContainer.ensureChildCount(
+                            viewLayoutId = R.layout.volume_dialog_slider_floating,
+                            count = floatingSliderViewBinders.size,
+                        )
+                        floatingSliderViewBinders.fastForEachIndexed { index, viewBinder ->
+                            viewBinder.bind(floatingSlidersContainer.getChildAt(index))
+                        }
+                    }
                     awaitCancellation()
                 }
             }
         }
     }
 }
+
+private fun ViewGroup.ensureChildCount(@LayoutRes viewLayoutId: Int, count: Int) {
+    val childCountDelta = childCount - count
+    when {
+        childCountDelta > 0 -> {
+            removeViews(0, childCountDelta)
+        }
+        childCountDelta < 0 -> {
+            val inflater = LayoutInflater.from(context)
+            repeat(abs(childCountDelta)) { inflater.inflate(viewLayoutId, this, true) }
+        }
+    }
+}
diff --git a/packages/SystemUI/src/com/android/systemui/volume/dialog/sliders/ui/viewmodel/VolumeDialogSliderViewModel.kt b/packages/SystemUI/src/com/android/systemui/volume/dialog/sliders/ui/viewmodel/VolumeDialogSliderViewModel.kt
index 7ee722d..ea0b49d 100644
--- a/packages/SystemUI/src/com/android/systemui/volume/dialog/sliders/ui/viewmodel/VolumeDialogSliderViewModel.kt
+++ b/packages/SystemUI/src/com/android/systemui/volume/dialog/sliders/ui/viewmodel/VolumeDialogSliderViewModel.kt
@@ -16,26 +16,85 @@
 
 package com.android.systemui.volume.dialog.sliders.ui.viewmodel
 
+import com.android.systemui.util.time.SystemClock
+import com.android.systemui.volume.Events
+import com.android.systemui.volume.dialog.dagger.scope.VolumeDialog
+import com.android.systemui.volume.dialog.domain.interactor.VolumeDialogVisibilityInteractor
 import com.android.systemui.volume.dialog.shared.model.VolumeDialogStreamModel
 import com.android.systemui.volume.dialog.sliders.domain.interactor.VolumeDialogSliderInteractor
 import dagger.assisted.Assisted
 import dagger.assisted.AssistedFactory
 import dagger.assisted.AssistedInject
+import kotlinx.coroutines.CoroutineScope
+import kotlinx.coroutines.ExperimentalCoroutinesApi
 import kotlinx.coroutines.flow.Flow
+import kotlinx.coroutines.flow.MutableStateFlow
+import kotlinx.coroutines.flow.SharingStarted
+import kotlinx.coroutines.flow.filter
+import kotlinx.coroutines.flow.filterNotNull
+import kotlinx.coroutines.flow.first
+import kotlinx.coroutines.flow.launchIn
+import kotlinx.coroutines.flow.mapLatest
+import kotlinx.coroutines.flow.stateIn
 
+/*
+ This prevents volume slider updates while user interacts with it. This is needed due to the
+ flawed VolumeDialogControllerImpl. It has a single threaded message queue that handles all state
+ updates and doesn't skip sequential updates of the same stream. This leads to a bottleneck when
+ user rigorously adjusts the slider.
+
+ Remove this when getting rid of the VolumeDialogControllerImpl as this doesn't happen in the
+ Volume Panel that uses the new coroutine-backed AudioRepository.
+*/
+// TODO(b/375355785) remove this
+private const val VOLUME_UPDATE_GRACE_PERIOD = 1000
+
+@OptIn(ExperimentalCoroutinesApi::class)
 class VolumeDialogSliderViewModel
 @AssistedInject
-constructor(@Assisted private val interactor: VolumeDialogSliderInteractor) {
+constructor(
+    @Assisted private val interactor: VolumeDialogSliderInteractor,
+    private val visibilityInteractor: VolumeDialogVisibilityInteractor,
+    @VolumeDialog private val coroutineScope: CoroutineScope,
+    private val systemClock: SystemClock,
+) {
 
-    val model: Flow<VolumeDialogStreamModel> = interactor.slider
+    private val userVolumeUpdates = MutableStateFlow<VolumeUpdate?>(null)
 
-    fun setStreamVolume(volume: Int) {
-        interactor.setStreamVolume(volume)
+    val model: Flow<VolumeDialogStreamModel> =
+        interactor.slider
+            .filter {
+                val lastVolumeUpdateTime = userVolumeUpdates.value?.timestampMillis ?: 0
+                getTimestampMillis() - lastVolumeUpdateTime > VOLUME_UPDATE_GRACE_PERIOD
+            }
+            .stateIn(coroutineScope, SharingStarted.Eagerly, null)
+            .filterNotNull()
+
+    init {
+        userVolumeUpdates
+            .filterNotNull()
+            .mapLatest { volume ->
+                interactor.setStreamVolume(volume.newVolumeLevel)
+                Events.writeEvent(Events.EVENT_TOUCH_LEVEL_CHANGED, model.first().stream, volume)
+            }
+            .launchIn(coroutineScope)
     }
 
+    fun setStreamVolume(volume: Int, fromUser: Boolean) {
+        if (fromUser) {
+            visibilityInteractor.resetDismissTimeout()
+            userVolumeUpdates.value =
+                VolumeUpdate(newVolumeLevel = volume, timestampMillis = getTimestampMillis())
+        }
+    }
+
+    private fun getTimestampMillis(): Long = systemClock.uptimeMillis()
+
     @AssistedFactory
     interface Factory {
 
         fun create(interactor: VolumeDialogSliderInteractor): VolumeDialogSliderViewModel
     }
+
+    private data class VolumeUpdate(val newVolumeLevel: Int, val timestampMillis: Long)
 }
diff --git a/packages/SystemUI/src/com/android/systemui/volume/dialog/sliders/ui/viewmodel/VolumeDialogSlidersViewModel.kt b/packages/SystemUI/src/com/android/systemui/volume/dialog/sliders/ui/viewmodel/VolumeDialogSlidersViewModel.kt
index b5b292f..22cf89f 100644
--- a/packages/SystemUI/src/com/android/systemui/volume/dialog/sliders/ui/viewmodel/VolumeDialogSlidersViewModel.kt
+++ b/packages/SystemUI/src/com/android/systemui/volume/dialog/sliders/ui/viewmodel/VolumeDialogSlidersViewModel.kt
@@ -16,6 +16,9 @@
 
 package com.android.systemui.volume.dialog.sliders.ui.viewmodel
 
+import androidx.compose.runtime.getValue
+import com.android.systemui.lifecycle.ExclusiveActivatable
+import com.android.systemui.lifecycle.Hydrator
 import com.android.systemui.volume.dialog.dagger.scope.VolumeDialog
 import com.android.systemui.volume.dialog.sliders.domain.interactor.VolumeDialogSliderInteractor
 import com.android.systemui.volume.dialog.sliders.domain.interactor.VolumeDialogSlidersInteractor
@@ -24,10 +27,9 @@
 import dagger.assisted.AssistedFactory
 import dagger.assisted.AssistedInject
 import kotlinx.coroutines.CoroutineScope
-import kotlinx.coroutines.flow.Flow
 import kotlinx.coroutines.flow.SharingStarted
+import kotlinx.coroutines.flow.StateFlow
 import kotlinx.coroutines.flow.distinctUntilChanged
-import kotlinx.coroutines.flow.filterNotNull
 import kotlinx.coroutines.flow.map
 import kotlinx.coroutines.flow.stateIn
 
@@ -39,9 +41,10 @@
     private val sliderInteractorFactory: VolumeDialogSliderInteractor.Factory,
     private val sliderViewModelFactory: VolumeDialogSliderViewModel.Factory,
     private val sliderViewBinderFactory: VolumeDialogSliderViewBinder.Factory,
-) {
+) : ExclusiveActivatable() {
 
-    val sliders: Flow<VolumeDialogSliderUiModel> =
+    private val hydrator = Hydrator("VolumeDialogSlidersViewModel")
+    private val slidersStateFlow: StateFlow<VolumeDialogSliderUiModel?> =
         slidersInteractor.sliders
             .distinctUntilChanged()
             .map { slidersModel ->
@@ -52,7 +55,13 @@
                 )
             }
             .stateIn(coroutineScope, SharingStarted.Eagerly, null)
-            .filterNotNull()
+
+    val uiModel: VolumeDialogSliderUiModel? by
+        hydrator.hydratedStateOf("VolumeDialogSlidersViewModel#uiModel", slidersStateFlow)
+
+    override suspend fun onActivated(): Nothing {
+        hydrator.activate()
+    }
 
     private fun createSliderViewBinder(type: VolumeDialogSliderType): VolumeDialogSliderViewBinder =
         sliderViewBinderFactory.create {
diff --git a/packages/SystemUI/src/com/android/systemui/volume/dialog/ui/binder/VolumeDialogBinder.kt b/packages/SystemUI/src/com/android/systemui/volume/dialog/ui/binder/VolumeDialogBinder.kt
index 77733fe..cd535e4 100644
--- a/packages/SystemUI/src/com/android/systemui/volume/dialog/ui/binder/VolumeDialogBinder.kt
+++ b/packages/SystemUI/src/com/android/systemui/volume/dialog/ui/binder/VolumeDialogBinder.kt
@@ -20,13 +20,16 @@
 import android.graphics.Color
 import android.graphics.PixelFormat
 import android.graphics.drawable.ColorDrawable
+import android.view.View
 import android.view.ViewGroup
 import android.view.Window
 import android.view.WindowManager
 import com.android.systemui.res.R
 import com.android.systemui.volume.dialog.dagger.scope.VolumeDialog
 import com.android.systemui.volume.dialog.dagger.scope.VolumeDialogScope
+import com.android.systemui.volume.dialog.ringer.ui.binder.VolumeDialogRingerViewBinder
 import com.android.systemui.volume.dialog.settings.ui.binder.VolumeDialogSettingsButtonViewBinder
+import com.android.systemui.volume.dialog.sliders.ui.VolumeDialogSlidersViewBinder
 import com.android.systemui.volume.dialog.ui.viewmodel.VolumeDialogGravityViewModel
 import javax.inject.Inject
 import kotlinx.coroutines.CoroutineScope
@@ -40,6 +43,8 @@
 constructor(
     @VolumeDialog private val coroutineScope: CoroutineScope,
     private val volumeDialogViewBinder: VolumeDialogViewBinder,
+    private val slidersViewBinder: VolumeDialogSlidersViewBinder,
+    private val volumeDialogRingerViewBinder: VolumeDialogRingerViewBinder,
     private val settingsButtonViewBinder: VolumeDialogSettingsButtonViewBinder,
     private val gravityViewModel: VolumeDialogGravityViewModel,
 ) {
@@ -50,11 +55,12 @@
             dialog.setContentView(R.layout.volume_dialog)
             dialog.setCanceledOnTouchOutside(true)
 
-            settingsButtonViewBinder.bind(dialog.requireViewById(R.id.volume_dialog_settings))
-            volumeDialogViewBinder.bind(
-                dialog,
-                dialog.requireViewById(R.id.volume_dialog_container),
-            )
+            with(dialog.requireViewById<View>(R.id.volume_dialog_container)) {
+                volumeDialogRingerViewBinder.bind(this)
+                slidersViewBinder.bind(this)
+                settingsButtonViewBinder.bind(this)
+                volumeDialogViewBinder.bind(dialog, this)
+            }
         }
     }
 
diff --git a/packages/SystemUI/src/com/android/systemui/wallpapers/data/repository/NoopWallpaperRepository.kt b/packages/SystemUI/src/com/android/systemui/wallpapers/data/repository/NoopWallpaperRepository.kt
index 54953c9..9055d18 100644
--- a/packages/SystemUI/src/com/android/systemui/wallpapers/data/repository/NoopWallpaperRepository.kt
+++ b/packages/SystemUI/src/com/android/systemui/wallpapers/data/repository/NoopWallpaperRepository.kt
@@ -35,6 +35,4 @@
     override val wallpaperInfo: StateFlow<WallpaperInfo?> = MutableStateFlow(null).asStateFlow()
     override val wallpaperSupportsAmbientMode = MutableStateFlow(false).asStateFlow()
     override var rootView: View? = null
-
-    override fun setNotificationStackAbsoluteBottom(bottom: Float) {}
 }
diff --git a/packages/SystemUI/src/com/android/systemui/wallpapers/data/repository/WallpaperRepository.kt b/packages/SystemUI/src/com/android/systemui/wallpapers/data/repository/WallpaperRepository.kt
index 0f6e960..015b480 100644
--- a/packages/SystemUI/src/com/android/systemui/wallpapers/data/repository/WallpaperRepository.kt
+++ b/packages/SystemUI/src/com/android/systemui/wallpapers/data/repository/WallpaperRepository.kt
@@ -45,7 +45,6 @@
 import kotlinx.coroutines.flow.SharingStarted
 import kotlinx.coroutines.flow.StateFlow
 import kotlinx.coroutines.flow.asStateFlow
-import kotlinx.coroutines.flow.collect
 import kotlinx.coroutines.flow.combine
 import kotlinx.coroutines.flow.filter
 import kotlinx.coroutines.flow.map
@@ -64,12 +63,6 @@
 
     /** Set rootView to get its windowToken afterwards */
     var rootView: View?
-
-    /**
-     * Set bottom of notifications from notification stack, and Magic Portrait will layout base on
-     * this value
-     */
-    fun setNotificationStackAbsoluteBottom(bottom: Float)
 }
 
 @SysUISingleton
@@ -106,7 +99,8 @@
             .filter { it.selectionStatus == SelectionStatus.SELECTION_COMPLETE }
 
     /** The bottom of notification stack respect to the top of screen. */
-    private val notificationStackAbsoluteBottom: MutableStateFlow<Float> = MutableStateFlow(0F)
+    private val notificationStackAbsoluteBottom: StateFlow<Float> =
+        keyguardRepository.notificationStackAbsoluteBottom
 
     /** The top of shortcut respect to the top of screen. */
     private val shortcutAbsoluteTop: StateFlow<Float> = keyguardRepository.shortcutAbsoluteTop
@@ -206,10 +200,6 @@
                 initialValue = false,
             )
 
-    override fun setNotificationStackAbsoluteBottom(bottom: Float) {
-        notificationStackAbsoluteBottom.value = bottom
-    }
-
     private suspend fun getWallpaper(selectedUser: SelectedUserModel): WallpaperInfo? {
         return withContext(bgDispatcher) {
             wallpaperManager.getWallpaperInfoForUser(selectedUser.userInfo.id)
diff --git a/packages/SystemUI/src/com/android/systemui/wallpapers/domain/interactor/WallpaperInteractor.kt b/packages/SystemUI/src/com/android/systemui/wallpapers/domain/interactor/WallpaperInteractor.kt
index fe6977c..88795ca 100644
--- a/packages/SystemUI/src/com/android/systemui/wallpapers/domain/interactor/WallpaperInteractor.kt
+++ b/packages/SystemUI/src/com/android/systemui/wallpapers/domain/interactor/WallpaperInteractor.kt
@@ -21,10 +21,6 @@
 import kotlinx.coroutines.flow.StateFlow
 
 class WallpaperInteractor @Inject constructor(val wallpaperRepository: WallpaperRepository) {
-    fun setNotificationStackAbsoluteBottom(bottom: Float) {
-        wallpaperRepository.setNotificationStackAbsoluteBottom(bottom)
-    }
-
     val wallpaperSupportsAmbientMode: StateFlow<Boolean> =
         wallpaperRepository.wallpaperSupportsAmbientMode
 }
diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/collection/NotifCollectionTest.java b/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/collection/NotifCollectionTest.java
index 2cf599a..e21a005 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/collection/NotifCollectionTest.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/collection/NotifCollectionTest.java
@@ -45,6 +45,7 @@
 import static org.mockito.ArgumentMatchers.anyInt;
 import static org.mockito.ArgumentMatchers.eq;
 import static org.mockito.Mockito.clearInvocations;
+import static org.mockito.Mockito.doReturn;
 import static org.mockito.Mockito.inOrder;
 import static org.mockito.Mockito.mock;
 import static org.mockito.Mockito.never;
@@ -63,6 +64,7 @@
 import android.app.NotificationManager;
 import android.os.Handler;
 import android.os.RemoteException;
+import android.platform.test.annotations.EnableFlags;
 import android.service.notification.NotificationListenerService.Ranking;
 import android.service.notification.NotificationListenerService.RankingMap;
 import android.service.notification.StatusBarNotification;
@@ -70,13 +72,13 @@
 import android.testing.TestableLooper;
 import android.util.ArrayMap;
 import android.util.ArraySet;
-import android.util.Pair;
 
 import androidx.annotation.NonNull;
 import androidx.test.filters.SmallTest;
 
 import com.android.internal.statusbar.IStatusBarService;
 import com.android.internal.statusbar.NotificationVisibility;
+import com.android.systemui.Flags;
 import com.android.systemui.SysuiTestCase;
 import com.android.systemui.dump.DumpManager;
 import com.android.systemui.dump.LogBufferEulogizer;
@@ -129,6 +131,7 @@
     @Mock private GroupCoalescer mGroupCoalescer;
     @Spy private RecordingCollectionListener mCollectionListener;
     @Mock private CollectionReadyForBuildListener mBuildListener;
+    @Mock private NotificationDismissibilityProvider mDismissibilityProvider;
 
     @Spy private RecordingLifetimeExtender mExtender1 = new RecordingLifetimeExtender("Extender1");
     @Spy private RecordingLifetimeExtender mExtender2 = new RecordingLifetimeExtender("Extender2");
@@ -160,6 +163,7 @@
         allowTestableLooperAsMainThread();
 
         when(mEulogizer.record(any(Exception.class))).thenAnswer(i -> i.getArguments()[0]);
+        doReturn(Boolean.TRUE).when(mDismissibilityProvider).isDismissable(any());
 
         mListenerInOrder = inOrder(mCollectionListener);
 
@@ -172,7 +176,7 @@
                 mBgExecutor,
                 mEulogizer,
                 mock(DumpManager.class),
-                mock(NotificationDismissibilityProvider.class));
+                mDismissibilityProvider);
         mCollection.attach(mGroupCoalescer);
         mCollection.addCollectionListener(mCollectionListener);
         mCollection.setBuildListener(mBuildListener);
@@ -1287,8 +1291,8 @@
 
         // WHEN both notifications are manually dismissed together
         mCollection.dismissNotifications(
-                List.of(new Pair<>(entry1, defaultStats(entry1)),
-                        new Pair<>(entry2, defaultStats(entry2))));
+                List.of(entryWithDefaultStats(entry1),
+                        entryWithDefaultStats(entry2)));
 
         // THEN build list is only called one time
         verifyBuiltList(List.of(entry1, entry2));
@@ -1306,8 +1310,8 @@
         DismissedByUserStats stats1 = defaultStats(entry1);
         DismissedByUserStats stats2 = defaultStats(entry2);
         mCollection.dismissNotifications(
-                List.of(new Pair<>(entry1, defaultStats(entry1)),
-                        new Pair<>(entry2, defaultStats(entry2))));
+                List.of(entryWithDefaultStats(entry1),
+                        entryWithDefaultStats(entry2)));
 
         // THEN we send the dismissals to system server
         FakeExecutor.exhaustExecutors(mBgExecutor);
@@ -1338,8 +1342,8 @@
 
         // WHEN both notifications are manually dismissed together
         mCollection.dismissNotifications(
-                List.of(new Pair<>(entry1, defaultStats(entry1)),
-                        new Pair<>(entry2, defaultStats(entry2))));
+                List.of(entryWithDefaultStats(entry1),
+                        entryWithDefaultStats(entry2)));
 
         // THEN the entries are marked as dismissed
         assertEquals(DISMISSED, entry1.getDismissState());
@@ -1363,8 +1367,8 @@
 
         // WHEN both notifications are manually dismissed together
         mCollection.dismissNotifications(
-                List.of(new Pair<>(entry1, defaultStats(entry1)),
-                        new Pair<>(entry2, defaultStats(entry2))));
+                List.of(entryWithDefaultStats(entry1),
+                        entryWithDefaultStats(entry2)));
 
         // THEN all interceptors get checked
         verify(mInterceptor1).shouldInterceptDismissal(entry1);
@@ -1379,6 +1383,43 @@
     }
 
     @Test
+    @EnableFlags(Flags.FLAG_NOTIFICATIONS_DISMISS_PRUNED_SUMMARIES)
+    public void testDismissNotificationsIncludesPrunedParents() {
+        // GIVEN a collection with 2 groups; one has a single child, one has two.
+        mCollection.addNotificationDismissInterceptor(mInterceptor1);
+
+        NotifEvent notif1summary = mNoMan.postNotif(
+                buildNotif(TEST_PACKAGE, 1, "notif1summary").setGroup(mContext, "group1")
+                        .setGroupSummary(mContext, true));
+        NotifEvent notif1child = mNoMan.postNotif(
+                buildNotif(TEST_PACKAGE, 1, "notif1child").setGroup(mContext, "group1"));
+        NotifEvent notif2summary = mNoMan.postNotif(
+                buildNotif(TEST_PACKAGE2, 2, "notif2summary").setGroup(mContext, "group2")
+                        .setGroupSummary(mContext, true));
+        NotifEvent notif2child1 = mNoMan.postNotif(
+                buildNotif(TEST_PACKAGE2, 2, "notif2child1").setGroup(mContext, "group2"));
+        NotifEvent notif2child2 = mNoMan.postNotif(
+                buildNotif(TEST_PACKAGE2, 2, "notif2child2").setGroup(mContext, "group2"));
+        NotificationEntry entry1summary = mCollectionListener.getEntry(notif1summary.key);
+        NotificationEntry entry1child = mCollectionListener.getEntry(notif1child.key);
+        NotificationEntry entry2summary = mCollectionListener.getEntry(notif2summary.key);
+        NotificationEntry entry2child1 = mCollectionListener.getEntry(notif2child1.key);
+        NotificationEntry entry2child2 = mCollectionListener.getEntry(notif2child2.key);
+
+        // WHEN one child from each group are manually dismissed together
+        mCollection.dismissNotifications(
+                List.of(entryWithDefaultStats(entry1child),
+                        entryWithDefaultStats(entry2child1)));
+
+        // THEN the summary for the singleton child is dismissed, but not the other summary
+        verify(mInterceptor1).shouldInterceptDismissal(entry1summary);
+        verify(mInterceptor1).shouldInterceptDismissal(entry1child);
+        verify(mInterceptor1, never()).shouldInterceptDismissal(entry2summary);
+        verify(mInterceptor1).shouldInterceptDismissal(entry2child1);
+        verify(mInterceptor1, never()).shouldInterceptDismissal(entry2child2);
+    }
+
+    @Test
     public void testDismissAllNotificationsCallsRebuildOnce() {
         // GIVEN a collection with a couple notifications
         NotifEvent notif1 = mNoMan.postNotif(buildNotif(TEST_PACKAGE, 47, "myTag"));
@@ -1764,6 +1805,10 @@
                 NotificationVisibility.obtain(entry.getKey(), 7, 2, true));
     }
 
+    private static EntryWithDismissStats entryWithDefaultStats(NotificationEntry entry) {
+        return new EntryWithDismissStats(entry, defaultStats(entry));
+    }
+
     private CollectionEvent postNotif(NotificationEntryBuilder builder) {
         clearInvocations(mCollectionListener);
         NotifEvent rawEvent = mNoMan.postNotif(builder);
diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/collection/NotificationEntryTest.java b/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/collection/NotificationEntryTest.java
index 72d1db3..31c650d 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/collection/NotificationEntryTest.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/collection/NotificationEntryTest.java
@@ -36,6 +36,7 @@
 import android.app.ActivityManager;
 import android.app.Notification;
 import android.app.NotificationChannel;
+import android.app.NotificationChannelGroup;
 import android.app.PendingIntent;
 import android.app.Person;
 import android.content.Intent;
@@ -372,6 +373,29 @@
     }
 
     @Test
+    public void notificationDataEntry_testIsLastMessageFromReply_invalidData() {
+        Bundle bundle = new Bundle();
+        bundle.putParcelableArray(Notification.EXTRA_REMOTE_INPUT_HISTORY_ITEMS,
+                new NotificationChannelGroup[]{});
+
+        Notification notification = new Notification.Builder(mContext, "test")
+                .addExtras(bundle)
+                .build();
+
+        NotificationEntry entry = new NotificationEntryBuilder()
+                .setPkg("pkg")
+                .setOpPkg("pkg")
+                .setTag("tag")
+                .setNotification(notification)
+                .setUser(mContext.getUser())
+                .setOverrideGroupKey("")
+                .build();
+        entry.setHasSentReply();
+
+        assertFalse(entry.isLastMessageFromReply());
+    }
+
+    @Test
     public void notificationDataEntry_testIsLastMessageFromReply_invalidPerson_noCrash() {
         Person.Builder person = new Person.Builder()
                 .setName("name")
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 d01c1ca..e26a7ea 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
@@ -16,6 +16,7 @@
 
 import static android.view.Display.DEFAULT_DISPLAY;
 
+import static com.android.systemui.Flags.FLAG_STATUS_BAR_RON_CHIPS;
 import static com.android.systemui.Flags.FLAG_STATUS_BAR_SCREEN_SHARING_CHIPS;
 import static com.android.systemui.Flags.FLAG_STATUS_BAR_SIMPLE_FRAGMENT;
 import static com.android.systemui.shade.ShadeExpansionStateManagerKt.STATE_CLOSED;
@@ -60,7 +61,6 @@
 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.chips.notification.shared.StatusBarNotifChips;
 import com.android.systemui.statusbar.disableflags.DisableFlagsLogger;
 import com.android.systemui.statusbar.events.SystemStatusAnimationScheduler;
 import com.android.systemui.statusbar.notification.icon.ui.viewbinder.NotificationIconContainerStatusBarViewBinder;
@@ -633,7 +633,7 @@
     @Test
     @EnableFlags({
             FLAG_STATUS_BAR_SCREEN_SHARING_CHIPS,
-            StatusBarNotifChips.FLAG_NAME,
+            FLAG_STATUS_BAR_RON_CHIPS,
             FLAG_STATUS_BAR_SIMPLE_FRAGMENT})
     public void hasPrimaryOngoingActivity_viewsUnchangedWhenSimpleFragmentFlagOn() {
         resumeAndGetFragment();
@@ -660,8 +660,8 @@
 
     @Test
     @EnableFlags(FLAG_STATUS_BAR_SCREEN_SHARING_CHIPS)
-    @DisableFlags({StatusBarNotifChips.FLAG_NAME, FLAG_STATUS_BAR_SIMPLE_FRAGMENT})
-    public void hasSecondaryOngoingActivity_butNotifsFlagOff_secondaryChipHidden() {
+    @DisableFlags({FLAG_STATUS_BAR_RON_CHIPS, FLAG_STATUS_BAR_SIMPLE_FRAGMENT})
+    public void hasSecondaryOngoingActivity_butRonsFlagOff_secondaryChipHidden() {
         resumeAndGetFragment();
 
         mCollapsedStatusBarViewBinder.getListener().onOngoingActivityStatusChanged(
@@ -673,7 +673,7 @@
     }
 
     @Test
-    @EnableFlags({FLAG_STATUS_BAR_SCREEN_SHARING_CHIPS, StatusBarNotifChips.FLAG_NAME})
+    @EnableFlags({FLAG_STATUS_BAR_SCREEN_SHARING_CHIPS, FLAG_STATUS_BAR_RON_CHIPS})
     @DisableFlags(FLAG_STATUS_BAR_SIMPLE_FRAGMENT)
     public void hasSecondaryOngoingActivity_flagOn_secondaryChipShownAndNotificationIconsHidden() {
         resumeAndGetFragment();
@@ -689,8 +689,8 @@
 
     @Test
     @EnableFlags(FLAG_STATUS_BAR_SCREEN_SHARING_CHIPS)
-    @DisableFlags({StatusBarNotifChips.FLAG_NAME, FLAG_STATUS_BAR_SIMPLE_FRAGMENT})
-    public void hasOngoingActivityButNotificationIconsDisabled_chipHidden_notifsFlagOff() {
+    @DisableFlags({FLAG_STATUS_BAR_RON_CHIPS, FLAG_STATUS_BAR_SIMPLE_FRAGMENT})
+    public void hasOngoingActivityButNotificationIconsDisabled_chipHidden_ronsFlagOff() {
         CollapsedStatusBarFragment fragment = resumeAndGetFragment();
 
         mCollapsedStatusBarViewBinder.getListener().onOngoingActivityStatusChanged(
@@ -705,9 +705,9 @@
     }
 
     @Test
-    @EnableFlags({FLAG_STATUS_BAR_SCREEN_SHARING_CHIPS, StatusBarNotifChips.FLAG_NAME})
+    @EnableFlags({FLAG_STATUS_BAR_SCREEN_SHARING_CHIPS, FLAG_STATUS_BAR_RON_CHIPS})
     @DisableFlags(FLAG_STATUS_BAR_SIMPLE_FRAGMENT)
-    public void hasOngoingActivitiesButNotificationIconsDisabled_chipsHidden_notifsFlagOn() {
+    public void hasOngoingActivitiesButNotificationIconsDisabled_chipsHidden_ronsFlagOn() {
         CollapsedStatusBarFragment fragment = resumeAndGetFragment();
 
         mCollapsedStatusBarViewBinder.getListener().onOngoingActivityStatusChanged(
@@ -724,8 +724,8 @@
 
     @Test
     @EnableFlags(FLAG_STATUS_BAR_SCREEN_SHARING_CHIPS)
-    @DisableFlags({StatusBarNotifChips.FLAG_NAME, FLAG_STATUS_BAR_SIMPLE_FRAGMENT})
-    public void hasOngoingActivityButAlsoHun_chipHidden_notifsFlagOff() {
+    @DisableFlags({FLAG_STATUS_BAR_RON_CHIPS, FLAG_STATUS_BAR_SIMPLE_FRAGMENT})
+    public void hasOngoingActivityButAlsoHun_chipHidden_ronsFlagOff() {
         CollapsedStatusBarFragment fragment = resumeAndGetFragment();
 
         mCollapsedStatusBarViewBinder.getListener().onOngoingActivityStatusChanged(
@@ -740,9 +740,9 @@
     }
 
     @Test
-    @EnableFlags({FLAG_STATUS_BAR_SCREEN_SHARING_CHIPS, StatusBarNotifChips.FLAG_NAME})
+    @EnableFlags({FLAG_STATUS_BAR_SCREEN_SHARING_CHIPS, FLAG_STATUS_BAR_RON_CHIPS})
     @DisableFlags(FLAG_STATUS_BAR_SIMPLE_FRAGMENT)
-    public void hasOngoingActivitiesButAlsoHun_chipsHidden_notifsFlagOn() {
+    public void hasOngoingActivitiesButAlsoHun_chipsHidden_ronsFlagOn() {
         CollapsedStatusBarFragment fragment = resumeAndGetFragment();
 
         mCollapsedStatusBarViewBinder.getListener().onOngoingActivityStatusChanged(
@@ -759,8 +759,8 @@
 
     @Test
     @EnableFlags(FLAG_STATUS_BAR_SCREEN_SHARING_CHIPS)
-    @DisableFlags({StatusBarNotifChips.FLAG_NAME, FLAG_STATUS_BAR_SIMPLE_FRAGMENT})
-    public void primaryOngoingActivityEnded_chipHidden_notifsFlagOff() {
+    @DisableFlags({FLAG_STATUS_BAR_RON_CHIPS, FLAG_STATUS_BAR_SIMPLE_FRAGMENT})
+    public void primaryOngoingActivityEnded_chipHidden_ronsFlagOff() {
         resumeAndGetFragment();
 
         // Ongoing activity started
@@ -781,9 +781,9 @@
     }
 
     @Test
-    @EnableFlags({FLAG_STATUS_BAR_SCREEN_SHARING_CHIPS, StatusBarNotifChips.FLAG_NAME})
+    @EnableFlags({FLAG_STATUS_BAR_SCREEN_SHARING_CHIPS, FLAG_STATUS_BAR_RON_CHIPS})
     @DisableFlags(FLAG_STATUS_BAR_SIMPLE_FRAGMENT)
-    public void primaryOngoingActivityEnded_chipHidden_notifsFlagOn() {
+    public void primaryOngoingActivityEnded_chipHidden_ronsFlagOn() {
         resumeAndGetFragment();
 
         // Ongoing activity started
@@ -804,7 +804,7 @@
     }
 
     @Test
-    @EnableFlags({FLAG_STATUS_BAR_SCREEN_SHARING_CHIPS, StatusBarNotifChips.FLAG_NAME})
+    @EnableFlags({FLAG_STATUS_BAR_SCREEN_SHARING_CHIPS, FLAG_STATUS_BAR_RON_CHIPS})
     @DisableFlags(FLAG_STATUS_BAR_SIMPLE_FRAGMENT)
     public void secondaryOngoingActivityEnded_chipHidden() {
         resumeAndGetFragment();
@@ -828,8 +828,8 @@
 
     @Test
     @EnableFlags(FLAG_STATUS_BAR_SCREEN_SHARING_CHIPS)
-    @DisableFlags({StatusBarNotifChips.FLAG_NAME, FLAG_STATUS_BAR_SIMPLE_FRAGMENT})
-    public void hasOngoingActivity_hidesNotifsWithoutAnimation_notifsFlagOff() {
+    @DisableFlags({FLAG_STATUS_BAR_RON_CHIPS, FLAG_STATUS_BAR_SIMPLE_FRAGMENT})
+    public void hasOngoingActivity_hidesNotifsWithoutAnimation_ronsFlagOff() {
         CollapsedStatusBarFragment fragment = resumeAndGetFragment();
         // Enable animations for testing so that we can verify we still aren't animating
         fragment.enableAnimationsForTesting();
@@ -846,9 +846,9 @@
     }
 
     @Test
-    @EnableFlags({FLAG_STATUS_BAR_SCREEN_SHARING_CHIPS, StatusBarNotifChips.FLAG_NAME})
+    @EnableFlags({FLAG_STATUS_BAR_SCREEN_SHARING_CHIPS, FLAG_STATUS_BAR_RON_CHIPS})
     @DisableFlags(FLAG_STATUS_BAR_SIMPLE_FRAGMENT)
-    public void hasOngoingActivity_hidesNotifsWithoutAnimation_notifsFlagOn() {
+    public void hasOngoingActivity_hidesNotifsWithoutAnimation_ronsFlagOn() {
         CollapsedStatusBarFragment fragment = resumeAndGetFragment();
         // Enable animations for testing so that we can verify we still aren't animating
         fragment.enableAnimationsForTesting();
@@ -866,8 +866,8 @@
 
     @Test
     @EnableFlags(FLAG_STATUS_BAR_SCREEN_SHARING_CHIPS)
-    @DisableFlags({StatusBarNotifChips.FLAG_NAME, FLAG_STATUS_BAR_SIMPLE_FRAGMENT})
-    public void screenSharingChipsEnabled_ignoresOngoingCallController_notifsFlagOff() {
+    @DisableFlags({FLAG_STATUS_BAR_RON_CHIPS, FLAG_STATUS_BAR_SIMPLE_FRAGMENT})
+    public void screenSharingChipsEnabled_ignoresOngoingCallController_ronsFlagOff() {
         CollapsedStatusBarFragment fragment = resumeAndGetFragment();
 
         // WHEN there *is* an ongoing call via old callback
@@ -898,9 +898,9 @@
     }
 
     @Test
-    @EnableFlags({FLAG_STATUS_BAR_SCREEN_SHARING_CHIPS, StatusBarNotifChips.FLAG_NAME})
+    @EnableFlags({FLAG_STATUS_BAR_SCREEN_SHARING_CHIPS, FLAG_STATUS_BAR_RON_CHIPS})
     @DisableFlags(FLAG_STATUS_BAR_SIMPLE_FRAGMENT)
-    public void screenSharingChipsEnabled_ignoresOngoingCallController_notifsFlagOn() {
+    public void screenSharingChipsEnabled_ignoresOngoingCallController_ronsFlagOn() {
         CollapsedStatusBarFragment fragment = resumeAndGetFragment();
 
         // WHEN there *is* an ongoing call via old callback
diff --git a/packages/SystemUI/tests/utils/src/com/android/systemui/keyguard/data/repository/FakeKeyguardRepository.kt b/packages/SystemUI/tests/utils/src/com/android/systemui/keyguard/data/repository/FakeKeyguardRepository.kt
index 0878649..693ec79 100644
--- a/packages/SystemUI/tests/utils/src/com/android/systemui/keyguard/data/repository/FakeKeyguardRepository.kt
+++ b/packages/SystemUI/tests/utils/src/com/android/systemui/keyguard/data/repository/FakeKeyguardRepository.kt
@@ -131,6 +131,10 @@
     override val shortcutAbsoluteTop: StateFlow<Float>
         get() = _shortcutAbsoluteTop.asStateFlow()
 
+    private val _notificationStackAbsoluteBottom = MutableStateFlow(0F)
+    override val notificationStackAbsoluteBottom: StateFlow<Float>
+        get() = _notificationStackAbsoluteBottom.asStateFlow()
+
     private val _isKeyguardEnabled = MutableStateFlow(true)
     override val isKeyguardEnabled: StateFlow<Boolean> = _isKeyguardEnabled.asStateFlow()
 
@@ -294,6 +298,10 @@
         _shortcutAbsoluteTop.value = top
     }
 
+    override fun setNotificationStackAbsoluteBottom(bottom: Float) {
+        _notificationStackAbsoluteBottom.value = bottom
+    }
+
     override fun setCanIgnoreAuthAndReturnToGone(canWake: Boolean) {
         _canIgnoreAuthAndReturnToGone.value = canWake
     }
diff --git a/packages/SystemUI/tests/utils/src/com/android/systemui/keyguard/data/repository/FakeKeyguardTransitionRepository.kt b/packages/SystemUI/tests/utils/src/com/android/systemui/keyguard/data/repository/FakeKeyguardTransitionRepository.kt
index 70b4f79..4976cc2 100644
--- a/packages/SystemUI/tests/utils/src/com/android/systemui/keyguard/data/repository/FakeKeyguardTransitionRepository.kt
+++ b/packages/SystemUI/tests/utils/src/com/android/systemui/keyguard/data/repository/FakeKeyguardTransitionRepository.kt
@@ -18,6 +18,7 @@
 package com.android.systemui.keyguard.data.repository
 
 import android.annotation.FloatRange
+import com.android.systemui.Flags.transitionRaceCondition
 import com.android.systemui.dagger.SysUISingleton
 import com.android.systemui.keyguard.shared.model.KeyguardState
 import com.android.systemui.keyguard.shared.model.TransitionInfo
@@ -88,6 +89,13 @@
             )
         )
     override var currentTransitionInfoInternal = _currentTransitionInfo.asStateFlow()
+    override var currentTransitionInfo =
+        TransitionInfo(
+            ownerName = "",
+            from = KeyguardState.OFF,
+            to = KeyguardState.LOCKSCREEN,
+            animator = null,
+        )
 
     init {
         // Seed with a FINISHED transition in OFF, same as the real repository.
@@ -261,8 +269,13 @@
         validateStep: Boolean = true,
     ) {
         if (step.transitionState == TransitionState.STARTED) {
-            _currentTransitionInfo.value =
-                TransitionInfo(from = step.from, to = step.to, animator = null, ownerName = "")
+            if (transitionRaceCondition()) {
+                currentTransitionInfo =
+                    TransitionInfo(from = step.from, to = step.to, animator = null, ownerName = "")
+            } else {
+                _currentTransitionInfo.value =
+                    TransitionInfo(from = step.from, to = step.to, animator = null, ownerName = "")
+            }
         }
 
         _transitions.replayCache.last().let { lastStep ->
@@ -308,7 +321,11 @@
     }
 
     override suspend fun startTransition(info: TransitionInfo): UUID? {
-        _currentTransitionInfo.value = info
+        if (transitionRaceCondition()) {
+            currentTransitionInfo = info
+        } else {
+            _currentTransitionInfo.value = info
+        }
 
         if (sendTransitionStepsOnStartTransition) {
             sendTransitionSteps(from = info.from, to = info.to, testScope = testScope)
diff --git a/packages/SystemUI/tests/utils/src/com/android/systemui/plugins/FakeVolumeDialogController.kt b/packages/SystemUI/tests/utils/src/com/android/systemui/plugins/FakeVolumeDialogController.kt
index b45120e..43eb93e 100644
--- a/packages/SystemUI/tests/utils/src/com/android/systemui/plugins/FakeVolumeDialogController.kt
+++ b/packages/SystemUI/tests/utils/src/com/android/systemui/plugins/FakeVolumeDialogController.kt
@@ -43,30 +43,36 @@
     private var state = VolumeDialogController.State()
 
     override fun setActiveStream(stream: Int) {
-        // ensure streamState existence for the active stream
-        state.states.getOrElse(stream) {
-            VolumeDialogController.StreamState().also { streamState ->
-                state.states.put(stream, streamState)
-            }
-        }
-        state.activeStream = stream
-    }
-
-    override fun setStreamVolume(stream: Int, userLevel: Int) {
-        val streamState =
-            state.states.getOrElse(stream) {
+        updateState {
+            // ensure streamState existence for the active stream`
+            states.getOrElse(stream) {
                 VolumeDialogController.StreamState().also { streamState ->
                     state.states.put(stream, streamState)
                 }
             }
-        streamState.level = userLevel.coerceIn(streamState.levelMin, streamState.levelMax)
+            activeStream = stream
+        }
+    }
+
+    override fun setStreamVolume(stream: Int, userLevel: Int) {
+        updateState {
+            val streamState =
+                states.getOrElse(stream) {
+                    VolumeDialogController.StreamState().also { streamState ->
+                        states.put(stream, streamState)
+                    }
+                }
+            streamState.level = userLevel.coerceIn(streamState.levelMin, streamState.levelMax)
+        }
     }
 
     override fun setRingerMode(ringerModeNormal: Int, external: Boolean) {
-        if (external) {
-            state.ringerModeExternal = ringerModeNormal
-        } else {
-            state.ringerModeInternal = ringerModeNormal
+        updateState {
+            if (external) {
+                ringerModeExternal = ringerModeNormal
+            } else {
+                ringerModeInternal = ringerModeNormal
+            }
         }
     }
 
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 a1f157f1..10534a0 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
@@ -40,7 +40,7 @@
 val Kosmos.shadeStartable by Fixture {
     ShadeStartable(
         applicationScope = applicationCoroutineScope,
-        applicationContext = applicationContext,
+        context = applicationContext,
         touchLog = mock<LogBuffer>(),
         configurationRepository = configurationRepository,
         shadeRepository = shadeRepository,
diff --git a/packages/SystemUI/tests/utils/src/com/android/systemui/statusbar/chips/notification/demo/ui/viewmodel/DemoNotifChipViewModelKosmos.kt b/packages/SystemUI/tests/utils/src/com/android/systemui/statusbar/chips/ron/demo/ui/viewmodel/DemoRonChipViewModelKosmos.kt
similarity index 85%
rename from packages/SystemUI/tests/utils/src/com/android/systemui/statusbar/chips/notification/demo/ui/viewmodel/DemoNotifChipViewModelKosmos.kt
rename to packages/SystemUI/tests/utils/src/com/android/systemui/statusbar/chips/ron/demo/ui/viewmodel/DemoRonChipViewModelKosmos.kt
index 2316a2f..c0d65a0 100644
--- a/packages/SystemUI/tests/utils/src/com/android/systemui/statusbar/chips/notification/demo/ui/viewmodel/DemoNotifChipViewModelKosmos.kt
+++ b/packages/SystemUI/tests/utils/src/com/android/systemui/statusbar/chips/ron/demo/ui/viewmodel/DemoRonChipViewModelKosmos.kt
@@ -14,16 +14,16 @@
  * limitations under the License.
  */
 
-package com.android.systemui.statusbar.chips.notification.demo.ui.viewmodel
+package com.android.systemui.statusbar.chips.ron.demo.ui.viewmodel
 
 import android.content.packageManager
 import com.android.systemui.kosmos.Kosmos
 import com.android.systemui.statusbar.commandline.commandRegistry
 import com.android.systemui.util.time.fakeSystemClock
 
-val Kosmos.demoNotifChipViewModel: DemoNotifChipViewModel by
+val Kosmos.demoRonChipViewModel: DemoRonChipViewModel by
     Kosmos.Fixture {
-        DemoNotifChipViewModel(
+        DemoRonChipViewModel(
             commandRegistry = commandRegistry,
             packageManager = packageManager,
             systemClock = fakeSystemClock,
diff --git a/packages/SystemUI/tests/utils/src/com/android/systemui/statusbar/chips/notification/ui/viewmodel/NotifChipsViewModelKosmos.kt b/packages/SystemUI/tests/utils/src/com/android/systemui/statusbar/chips/ron/ui/viewmodel/NotifChipsViewModelKosmos.kt
similarity index 85%
rename from packages/SystemUI/tests/utils/src/com/android/systemui/statusbar/chips/notification/ui/viewmodel/NotifChipsViewModelKosmos.kt
rename to packages/SystemUI/tests/utils/src/com/android/systemui/statusbar/chips/ron/ui/viewmodel/NotifChipsViewModelKosmos.kt
index af24c37..11d1cb7 100644
--- a/packages/SystemUI/tests/utils/src/com/android/systemui/statusbar/chips/notification/ui/viewmodel/NotifChipsViewModelKosmos.kt
+++ b/packages/SystemUI/tests/utils/src/com/android/systemui/statusbar/chips/ron/ui/viewmodel/NotifChipsViewModelKosmos.kt
@@ -14,9 +14,10 @@
  * limitations under the License.
  */
 
-package com.android.systemui.statusbar.chips.notification.ui.viewmodel
+package com.android.systemui.statusbar.chips.ron.ui.viewmodel
 
 import com.android.systemui.kosmos.Kosmos
+import com.android.systemui.statusbar.chips.notification.ui.viewmodel.NotifChipsViewModel
 import com.android.systemui.statusbar.notification.domain.interactor.activeNotificationsInteractor
 
 val Kosmos.notifChipsViewModel: NotifChipsViewModel by
diff --git a/packages/SystemUI/tests/utils/src/com/android/systemui/statusbar/chips/ui/viewmodel/OngoingActivityChipsViewModelKosmos.kt b/packages/SystemUI/tests/utils/src/com/android/systemui/statusbar/chips/ui/viewmodel/OngoingActivityChipsViewModelKosmos.kt
index 0300bf4..b2be0b2 100644
--- a/packages/SystemUI/tests/utils/src/com/android/systemui/statusbar/chips/ui/viewmodel/OngoingActivityChipsViewModelKosmos.kt
+++ b/packages/SystemUI/tests/utils/src/com/android/systemui/statusbar/chips/ui/viewmodel/OngoingActivityChipsViewModelKosmos.kt
@@ -20,8 +20,8 @@
 import com.android.systemui.kosmos.testScope
 import com.android.systemui.statusbar.chips.call.ui.viewmodel.callChipViewModel
 import com.android.systemui.statusbar.chips.casttootherdevice.ui.viewmodel.castToOtherDeviceChipViewModel
-import com.android.systemui.statusbar.chips.notification.demo.ui.viewmodel.demoNotifChipViewModel
-import com.android.systemui.statusbar.chips.notification.ui.viewmodel.notifChipsViewModel
+import com.android.systemui.statusbar.chips.ron.demo.ui.viewmodel.demoRonChipViewModel
+import com.android.systemui.statusbar.chips.ron.ui.viewmodel.notifChipsViewModel
 import com.android.systemui.statusbar.chips.screenrecord.ui.viewmodel.screenRecordChipViewModel
 import com.android.systemui.statusbar.chips.sharetoapp.ui.viewmodel.shareToAppChipViewModel
 import com.android.systemui.statusbar.chips.statusBarChipsLogger
@@ -35,7 +35,7 @@
             castToOtherDeviceChipViewModel = castToOtherDeviceChipViewModel,
             callChipViewModel = callChipViewModel,
             notifChipsViewModel = notifChipsViewModel,
-            demoNotifChipViewModel = demoNotifChipViewModel,
+            demoRonChipViewModel = demoRonChipViewModel,
             logger = statusBarChipsLogger,
         )
     }
diff --git a/packages/SystemUI/tests/utils/src/com/android/systemui/statusbar/notification/stack/domain/interactor/SharedNotificationContainerInteractorKosmos.kt b/packages/SystemUI/tests/utils/src/com/android/systemui/statusbar/notification/stack/domain/interactor/SharedNotificationContainerInteractorKosmos.kt
index 83fc3e9..b1e9d89 100644
--- a/packages/SystemUI/tests/utils/src/com/android/systemui/statusbar/notification/stack/domain/interactor/SharedNotificationContainerInteractorKosmos.kt
+++ b/packages/SystemUI/tests/utils/src/com/android/systemui/statusbar/notification/stack/domain/interactor/SharedNotificationContainerInteractorKosmos.kt
@@ -23,7 +23,6 @@
 import com.android.systemui.deviceentry.domain.interactor.deviceEntryUdfpsInteractor
 import com.android.systemui.keyguard.domain.interactor.keyguardInteractor
 import com.android.systemui.kosmos.Kosmos
-import com.android.systemui.shade.domain.interactor.shadeInteractor
 import com.android.systemui.shade.largeScreenHeaderHelper
 import com.android.systemui.statusbar.policy.splitShadeStateController
 import kotlinx.coroutines.ExperimentalCoroutinesApi
@@ -33,7 +32,6 @@
         SharedNotificationContainerInteractor(
             context = applicationContext,
             splitShadeStateController = { splitShadeStateController },
-            shadeInteractor = { shadeInteractor },
             configurationInteractor = configurationInteractor,
             keyguardInteractor = keyguardInteractor,
             deviceEntryUdfpsInteractor = deviceEntryUdfpsInteractor,
diff --git a/packages/SystemUI/tests/utils/src/com/android/systemui/statusbar/notification/stack/ui/viewmodel/SharedNotificationContainerViewModelKosmos.kt b/packages/SystemUI/tests/utils/src/com/android/systemui/statusbar/notification/stack/ui/viewmodel/SharedNotificationContainerViewModelKosmos.kt
index a25a3c0..7fbf4e4 100644
--- a/packages/SystemUI/tests/utils/src/com/android/systemui/statusbar/notification/stack/ui/viewmodel/SharedNotificationContainerViewModelKosmos.kt
+++ b/packages/SystemUI/tests/utils/src/com/android/systemui/statusbar/notification/stack/ui/viewmodel/SharedNotificationContainerViewModelKosmos.kt
@@ -16,6 +16,8 @@
 
 package com.android.systemui.statusbar.notification.stack.ui.viewmodel
 
+import android.content.applicationContext
+import com.android.systemui.common.ui.domain.interactor.configurationInteractor
 import com.android.systemui.communal.domain.interactor.communalSceneInteractor
 import com.android.systemui.dump.dumpManager
 import com.android.systemui.keyguard.domain.interactor.keyguardInteractor
@@ -49,6 +51,7 @@
 import com.android.systemui.kosmos.Kosmos.Fixture
 import com.android.systemui.kosmos.applicationCoroutineScope
 import com.android.systemui.shade.domain.interactor.shadeInteractor
+import com.android.systemui.shade.largeScreenHeaderHelper
 import com.android.systemui.statusbar.notification.stack.domain.interactor.headsUpNotificationInteractor
 import com.android.systemui.statusbar.notification.stack.domain.interactor.notificationStackAppearanceInteractor
 import com.android.systemui.statusbar.notification.stack.domain.interactor.sharedNotificationContainerInteractor
@@ -61,6 +64,8 @@
         interactor = sharedNotificationContainerInteractor,
         dumpManager = dumpManager,
         applicationScope = applicationCoroutineScope,
+        context = applicationContext,
+        configurationInteractor = configurationInteractor,
         keyguardInteractor = keyguardInteractor,
         keyguardTransitionInteractor = keyguardTransitionInteractor,
         shadeInteractor = shadeInteractor,
@@ -94,6 +99,7 @@
         aodBurnInViewModel = aodBurnInViewModel,
         communalSceneInteractor = communalSceneInteractor,
         headsUpNotificationInteractor = { headsUpNotificationInteractor },
+        largeScreenHeaderHelperLazy = { largeScreenHeaderHelper },
         unfoldTransitionInteractor = unfoldTransitionInteractor,
     )
 }
diff --git a/packages/SystemUI/tests/utils/src/com/android/systemui/volume/dialog/sliders/domain/interactor/VolumeDialogSliderInteractorKosmos.kt b/packages/SystemUI/tests/utils/src/com/android/systemui/volume/dialog/sliders/domain/interactor/VolumeDialogSliderInteractorKosmos.kt
new file mode 100644
index 0000000..423100a
--- /dev/null
+++ b/packages/SystemUI/tests/utils/src/com/android/systemui/volume/dialog/sliders/domain/interactor/VolumeDialogSliderInteractorKosmos.kt
@@ -0,0 +1,31 @@
+/*
+ * 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.volume.dialog.sliders.domain.interactor
+
+import com.android.systemui.kosmos.Kosmos
+import com.android.systemui.plugins.volumeDialogController
+import com.android.systemui.volume.dialog.domain.interactor.volumeDialogStateInteractor
+import com.android.systemui.volume.dialog.sliders.domain.model.volumeDialogSliderType
+
+val Kosmos.volumeDialogSliderInteractor: VolumeDialogSliderInteractor by
+    Kosmos.Fixture {
+        VolumeDialogSliderInteractor(
+            volumeDialogSliderType,
+            volumeDialogStateInteractor,
+            volumeDialogController,
+        )
+    }
diff --git a/packages/SystemUI/tests/utils/src/com/android/systemui/statusbar/chips/notification/ui/viewmodel/NotifChipsViewModelKosmos.kt b/packages/SystemUI/tests/utils/src/com/android/systemui/volume/dialog/sliders/domain/model/VolumeDialogSliderTypeKosmos.kt
similarity index 68%
copy from packages/SystemUI/tests/utils/src/com/android/systemui/statusbar/chips/notification/ui/viewmodel/NotifChipsViewModelKosmos.kt
copy to packages/SystemUI/tests/utils/src/com/android/systemui/volume/dialog/sliders/domain/model/VolumeDialogSliderTypeKosmos.kt
index af24c37..cc8c1ea 100644
--- a/packages/SystemUI/tests/utils/src/com/android/systemui/statusbar/chips/notification/ui/viewmodel/NotifChipsViewModelKosmos.kt
+++ b/packages/SystemUI/tests/utils/src/com/android/systemui/volume/dialog/sliders/domain/model/VolumeDialogSliderTypeKosmos.kt
@@ -14,10 +14,10 @@
  * limitations under the License.
  */
 
-package com.android.systemui.statusbar.chips.notification.ui.viewmodel
+package com.android.systemui.volume.dialog.sliders.domain.model
 
+import android.media.AudioManager
 import com.android.systemui.kosmos.Kosmos
-import com.android.systemui.statusbar.notification.domain.interactor.activeNotificationsInteractor
 
-val Kosmos.notifChipsViewModel: NotifChipsViewModel by
-    Kosmos.Fixture { NotifChipsViewModel(activeNotificationsInteractor) }
+var Kosmos.volumeDialogSliderType: VolumeDialogSliderType by
+    Kosmos.Fixture { VolumeDialogSliderType.Stream(AudioManager.STREAM_SYSTEM) }
diff --git a/ravenwood/junit-impl-src/android/platform/test/ravenwood/RavenwoodAwareTestRunnerHook.java b/ravenwood/junit-impl-src/android/platform/test/ravenwood/RavenwoodAwareTestRunnerHook.java
index 478bead..e0f9ec9 100644
--- a/ravenwood/junit-impl-src/android/platform/test/ravenwood/RavenwoodAwareTestRunnerHook.java
+++ b/ravenwood/junit-impl-src/android/platform/test/ravenwood/RavenwoodAwareTestRunnerHook.java
@@ -25,6 +25,7 @@
 
 import androidx.test.platform.app.InstrumentationRegistry;
 
+import org.junit.AssumptionViolatedException;
 import org.junit.runner.Description;
 import org.junit.runners.model.TestClass;
 
@@ -134,8 +135,17 @@
         if (scope == Scope.Instance && order == Order.Outer) {
             // End of a test method.
             runner.mState.exitTestMethod();
-            RavenwoodTestStats.getInstance().onTestFinished(classDescription, description,
-                    th == null ? Result.Passed : Result.Failed);
+
+            final Result result;
+            if (th == null) {
+                result = Result.Passed;
+            } else if (th instanceof AssumptionViolatedException) {
+                result = Result.Skipped;
+            } else {
+                result = Result.Failed;
+            }
+
+            RavenwoodTestStats.getInstance().onTestFinished(classDescription, description, result);
         }
 
         // If RUN_DISABLED_TESTS is set, and the method did _not_ throw, make it an error.
diff --git a/ravenwood/scripts/ravenwood-test-summary b/ravenwood/scripts/ravenwood-test-summary
new file mode 100755
index 0000000..602fbff
--- /dev/null
+++ b/ravenwood/scripts/ravenwood-test-summary
@@ -0,0 +1,75 @@
+#!/usr/bin/env python3
+#
+# 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.
+
+'''
+Print the latest Ravenwood test execution summary
+
+Usage: /ravenwood-test-summary
+
+Example output:
+Module                                                         Passed   Failed  Skipped
+android.test.mock.ravenwood.tests                                   2        0        0
+CarLibHostUnitTest                                                565        0        7
+CarServiceHostUnitTest                                            364        0        0
+CtsAccountManagerTestCasesRavenwood                                 4        0        0
+CtsAppTestCasesRavenwood                                           21        0        0
+
+Description:
+This script finds all the test execution result from /tmp/Ravenwood-stats*,
+and shows per-module summary.
+'''
+
+import csv
+import glob
+import sys
+
+# Find the latest stats files.
+stats_files = glob.glob('/tmp/Ravenwood-stats_*_latest.csv')
+
+if len(stats_files) == 0:
+    print("No log files found.", file=sys.stderr)
+    exit(1)
+
+
+def parse_stats(file, result):
+    module = '(unknwon)'
+    passed = 0
+    failed = 0
+    skipped = 0
+    with open(file) as csvfile:
+        reader = csv.reader(csvfile, delimiter=',')
+
+
+        for i, row in enumerate(reader):
+            if i == 0: continue # Skip header line
+            module = row[0]
+            passed += int(row[3])
+            failed += int(row[4])
+            skipped += int(row[5])
+
+    result[module] = (passed, failed, skipped)
+
+
+result = {}
+
+for file in stats_files:
+    parse_stats(file, result)
+
+print('%-60s %8s %8s %8s' % ("Module", "Passed", "Failed", "Skipped"))
+
+for module in sorted(result.keys(), key=str.casefold):
+    r = result[module]
+    print('%-60s %8d %8d %8d' % (module, r[0], r[1], r[2]))
diff --git a/services/core/java/com/android/server/SystemConfig.java b/services/core/java/com/android/server/SystemConfig.java
index 6a1e319..1082e62 100644
--- a/services/core/java/com/android/server/SystemConfig.java
+++ b/services/core/java/com/android/server/SystemConfig.java
@@ -670,6 +670,17 @@
     }
 
     private void readAllPermissions() {
+        readAllPermissionsFromXml();
+        readAllPermissionsFromEnvironment();
+
+        // Apply global feature removal last, after all features have been read.
+        // This only needs to happen once.
+        for (String featureName : mUnavailableFeatures) {
+            removeFeature(featureName);
+        }
+    }
+
+    private void readAllPermissionsFromXml() {
         final XmlPullParser parser = Xml.newPullParser();
 
         // Read configuration from system
@@ -1730,7 +1741,13 @@
         } finally {
             IoUtils.closeQuietly(permReader);
         }
+    }
 
+    // Add features or permission dependent on global system properties (as
+    // opposed to XML permission files).
+    // This only needs to be called once after all features have been parsed
+    // from various partition/apex sources.
+    private void readAllPermissionsFromEnvironment() {
         // Some devices can be field-converted to FBE, so offer to splice in
         // those features if not already defined by the static config
         if (StorageManager.isFileEncrypted()) {
@@ -1771,10 +1788,6 @@
                 addFeature(PackageManager.FEATURE_EROFS_LEGACY, 0);
             }
         }
-
-        for (String featureName : mUnavailableFeatures) {
-            removeFeature(featureName);
-        }
     }
 
     private @Nullable SignedPackage parseEnhancedConfirmationTrustedPackage(XmlPullParser parser,
diff --git a/services/core/java/com/android/server/accounts/AccountManagerService.java b/services/core/java/com/android/server/accounts/AccountManagerService.java
index 0ca3b56..679c7ac 100644
--- a/services/core/java/com/android/server/accounts/AccountManagerService.java
+++ b/services/core/java/com/android/server/accounts/AccountManagerService.java
@@ -112,6 +112,7 @@
 import com.android.internal.notification.SystemNotificationChannels;
 import com.android.internal.util.ArrayUtils;
 import com.android.internal.util.DumpUtils;
+import com.android.internal.util.FrameworkStatsLog;
 import com.android.internal.util.IndentingPrintWriter;
 import com.android.internal.util.Preconditions;
 import com.android.modules.expresslog.Histogram;
@@ -1226,7 +1227,17 @@
                 // been re-enabled (after being updated for example), then we just overwrite the old
                 // values.
                 for (Entry<String, Integer> entry : knownAuth.entrySet()) {
-                    accountsDb.insertOrReplaceMetaAuthTypeAndUid(entry.getKey(), entry.getValue());
+                    String type = entry.getKey();
+                    Integer newUid = entry.getValue();
+                    if (!Objects.equals(metaAuthUid.get(type), newUid)) {
+                        FrameworkStatsLog.write(
+                                FrameworkStatsLog.ACCOUNT_MANAGER_EVENT,
+                                type,
+                                newUid,
+                                FrameworkStatsLog
+                                        .ACCOUNT_MANAGER_EVENT__EVENT_TYPE__AUTHENTICATOR_ADDED);
+                    }
+                    accountsDb.insertOrReplaceMetaAuthTypeAndUid(type, newUid);
                 }
 
                 final Map<Long, Account> accountsMap = accountsDb.findAllDeAccounts();
@@ -1945,6 +1956,11 @@
                     }
                     accounts.accountsDb.setTransactionSuccessful();
 
+                    FrameworkStatsLog.write(
+                            FrameworkStatsLog.ACCOUNT_MANAGER_EVENT,
+                            account.type,
+                            callingUid,
+                            FrameworkStatsLog.ACCOUNT_MANAGER_EVENT__EVENT_TYPE__ACCOUNT_ADDED);
                     logRecord(AccountsDb.DEBUG_ACTION_ACCOUNT_ADD, AccountsDb.TABLE_ACCOUNTS,
                             accountId,
                             accounts, callingUid);
@@ -2544,6 +2560,11 @@
                     }
                     String action = userUnlocked ? AccountsDb.DEBUG_ACTION_ACCOUNT_REMOVE
                             : AccountsDb.DEBUG_ACTION_ACCOUNT_REMOVE_DE;
+                    FrameworkStatsLog.write(
+                            FrameworkStatsLog.ACCOUNT_MANAGER_EVENT,
+                            account.type,
+                            callingUid,
+                            FrameworkStatsLog.ACCOUNT_MANAGER_EVENT__EVENT_TYPE__ACCOUNT_REMOVED);
                     logRecord(action, AccountsDb.TABLE_ACCOUNTS, accountId, accounts);
                 }
             }
diff --git a/services/core/java/com/android/server/am/ActivityManagerService.java b/services/core/java/com/android/server/am/ActivityManagerService.java
index 3e03045..87ce649 100644
--- a/services/core/java/com/android/server/am/ActivityManagerService.java
+++ b/services/core/java/com/android/server/am/ActivityManagerService.java
@@ -2772,8 +2772,12 @@
             // Add common services.
             // IMPORTANT: Before adding services here, make sure ephemeral apps can access them too.
             // Enable the check in ApplicationThread.bindApplication() to make sure.
-            if (!android.server.Flags.removeJavaServiceManagerCache()) {
-                addServiceToMap(mAppBindArgs, "permissionmgr");
+
+            // Removing User Service and App Ops Service from cache breaks boot for auto.
+            // Removing permissionmgr breaks tests for Android Auto due to SELinux restrictions.
+            // TODO: fix SELinux restrictions and remove caching for Android Auto.
+            if (mContext.getPackageManager().hasSystemFeature(PackageManager.FEATURE_AUTOMOTIVE)
+                    || !android.server.Flags.removeJavaServiceManagerCache()) {
                 addServiceToMap(mAppBindArgs, Context.ALARM_SERVICE);
                 addServiceToMap(mAppBindArgs, Context.DISPLAY_SERVICE);
                 addServiceToMap(mAppBindArgs, Context.NETWORKMANAGEMENT_SERVICE);
@@ -2790,16 +2794,16 @@
                 addServiceToMap(mAppBindArgs, Context.POWER_SERVICE);
                 addServiceToMap(mAppBindArgs, "mount");
                 addServiceToMap(mAppBindArgs, Context.PLATFORM_COMPAT_SERVICE);
+                addServiceToMap(mAppBindArgs, "permissionmgr");
+                addServiceToMap(mAppBindArgs, Context.APP_OPS_SERVICE);
+                addServiceToMap(mAppBindArgs, Context.USER_SERVICE);
             }
             // See b/79378449
             // Getting the window service and package service binder from servicemanager
             // is blocked for Apps. However they are necessary for apps.
-            // Removing User Service and App Ops Service from cache breaks boot for auto.
             // TODO: remove exception
-            addServiceToMap(mAppBindArgs, Context.APP_OPS_SERVICE);
             addServiceToMap(mAppBindArgs, "package");
             addServiceToMap(mAppBindArgs, Context.WINDOW_SERVICE);
-            addServiceToMap(mAppBindArgs, Context.USER_SERVICE);
         }
         return mAppBindArgs;
     }
@@ -5551,6 +5555,8 @@
         if (target instanceof PendingIntentRecord) {
             final PendingIntentRecord originalRecord = (PendingIntentRecord) target;
 
+            addCreatorToken(intent, originalRecord.getPackageName());
+
             // In multi-display scenarios, there can be background users who execute the
             // PendingIntent. In these scenarios, we don't want to use the foreground user as the
             // current user.
diff --git a/services/core/java/com/android/server/display/DisplayTopology.java b/services/core/java/com/android/server/display/DisplayTopology.java
index b01d617..fdadafe 100644
--- a/services/core/java/com/android/server/display/DisplayTopology.java
+++ b/services/core/java/com/android/server/display/DisplayTopology.java
@@ -16,7 +16,13 @@
 
 package com.android.server.display;
 
+import static com.android.server.display.DisplayTopology.TreeNode.Position.POSITION_BOTTOM;
+import static com.android.server.display.DisplayTopology.TreeNode.Position.POSITION_LEFT;
+import static com.android.server.display.DisplayTopology.TreeNode.Position.POSITION_TOP;
+import static com.android.server.display.DisplayTopology.TreeNode.Position.POSITION_RIGHT;
+
 import android.annotation.Nullable;
+import android.graphics.RectF;
 import android.util.IndentingPrintWriter;
 import android.util.Pair;
 import android.util.Slog;
@@ -25,16 +31,21 @@
 import com.android.internal.annotations.VisibleForTesting;
 
 import java.io.PrintWriter;
+import java.util.ArrayDeque;
 import java.util.ArrayList;
-import java.util.LinkedList;
+import java.util.Comparator;
+import java.util.HashMap;
 import java.util.List;
+import java.util.Map;
 import java.util.Queue;
 
 /**
  * Represents the relative placement of extended displays.
+ * Does not support concurrent calls, so a lock should be held when calling into this class.
  */
 class DisplayTopology {
     private static final String TAG = "DisplayTopology";
+    private static final float EPSILON = 0.0001f;
 
     /**
      * The topology tree
@@ -58,7 +69,7 @@
      * @param width The width of the display
      * @param height The height of the display
      */
-    void addDisplay(int displayId, double width, double height) {
+    void addDisplay(int displayId, float width, float height) {
         addDisplay(displayId, width, height, /* shouldLog= */ true);
     }
 
@@ -69,10 +80,10 @@
      * @param displayId The logical display ID
      */
     void removeDisplay(int displayId) {
-        if (!isDisplayPresent(displayId, mRoot)) {
+        if (findDisplay(displayId, mRoot) == null) {
             return;
         }
-        Queue<TreeNode> queue = new LinkedList<>();
+        Queue<TreeNode> queue = new ArrayDeque<>();
         queue.add(mRoot);
         mRoot = null;
         while (!queue.isEmpty()) {
@@ -115,7 +126,11 @@
         }
     }
 
-    private void addDisplay(int displayId, double width, double height, boolean shouldLog) {
+    private void addDisplay(int displayId, float width, float height, boolean shouldLog) {
+        if (findDisplay(displayId, mRoot) != null) {
+            throw new IllegalArgumentException(
+                    "DisplayTopology: attempting to add a display that already exists");
+        }
         if (mRoot == null) {
             mRoot = new TreeNode(displayId, width, height, /* position= */ null, /* offset= */ 0);
             mPrimaryDisplayId = displayId;
@@ -124,9 +139,8 @@
             }
         } else if (mRoot.mChildren.isEmpty()) {
             // This is the 2nd display. Align the middles of the top and bottom edges.
-            double offset = mRoot.mWidth / 2 - width / 2;
-            TreeNode display = new TreeNode(displayId, width, height,
-                    TreeNode.Position.POSITION_TOP, offset);
+            float offset = mRoot.mWidth / 2 - width / 2;
+            TreeNode display = new TreeNode(displayId, width, height, POSITION_TOP, offset);
             mRoot.mChildren.add(display);
             if (shouldLog) {
                 Slog.i(TAG, "Second display added: " + display + ", parent ID: "
@@ -134,8 +148,8 @@
             }
         } else {
             TreeNode rightMostDisplay = findRightMostDisplay(mRoot, mRoot.mWidth).first;
-            TreeNode newDisplay = new TreeNode(displayId, width, height,
-                    TreeNode.Position.POSITION_RIGHT, /* offset= */ 0);
+            TreeNode newDisplay = new TreeNode(displayId, width, height, POSITION_RIGHT,
+                    /* offset= */ 0);
             rightMostDisplay.mChildren.add(newDisplay);
             if (shouldLog) {
                 Slog.i(TAG, "Display added: " + newDisplay + ", parent ID: "
@@ -150,11 +164,11 @@
      * @return The display that is the furthest to the right and the x position of the right edge
      * of that display
      */
-    private Pair<TreeNode, Double> findRightMostDisplay(TreeNode display, double xPos) {
-        Pair<TreeNode, Double> result = new Pair<>(display, xPos);
+    private static Pair<TreeNode, Float> findRightMostDisplay(TreeNode display, float xPos) {
+        Pair<TreeNode, Float> result = new Pair<>(display, xPos);
         for (TreeNode child : display.mChildren) {
             // The x position of the right edge of the child
-            double childXPos;
+            float childXPos;
             switch (child.mPosition) {
                 case POSITION_LEFT -> childXPos = xPos - display.mWidth;
                 case POSITION_TOP, POSITION_BOTTOM ->
@@ -164,7 +178,7 @@
             }
 
             // Recursive call - find the rightmost display starting from the child
-            Pair<TreeNode, Double> childResult = findRightMostDisplay(child, childXPos);
+            Pair<TreeNode, Float> childResult = findRightMostDisplay(child, childXPos);
             // Check if the one found is further right
             if (childResult.second > result.second) {
                 result = new Pair<>(childResult.first, childResult.second);
@@ -173,19 +187,200 @@
         return result;
     }
 
-    private boolean isDisplayPresent(int displayId, TreeNode node) {
-        if (node == null) {
-            return false;
+    @Nullable
+    private static TreeNode findDisplay(int displayId, TreeNode startingNode) {
+        if (startingNode == null) {
+            return null;
         }
-        if (node.mDisplayId == displayId) {
-            return true;
+        if (startingNode.mDisplayId == displayId) {
+            return startingNode;
         }
-        for (TreeNode child : node.mChildren) {
-            if (isDisplayPresent(displayId, child)) {
-                return true;
+        for (TreeNode child : startingNode.mChildren) {
+            TreeNode display = findDisplay(displayId, child);
+            if (display != null) {
+                return display;
             }
         }
-        return false;
+        return null;
+    }
+
+    /**
+     * Get information about the topology that will be used for the normalization algorithm.
+     * Assigns origins to each display to compute the bounds.
+     * @param bounds The map where the bounds of each display will be put
+     * @param depths The map where the depths of each display in the tree will be put
+     * @param parents The map where the parent of each display will be put
+     * @param display The starting node
+     * @param x The starting x position
+     * @param y The starting y position
+     * @param depth The starting depth
+     */
+    private static void getInfo(Map<TreeNode, RectF> bounds, Map<TreeNode, Integer> depths,
+            Map<TreeNode, TreeNode> parents, TreeNode display, float x, float y, int depth) {
+        bounds.put(display, new RectF(x, y, x + display.mWidth, y + display.mHeight));
+        depths.put(display, depth);
+        for (TreeNode child : display.mChildren) {
+            parents.put(child, display);
+            if (child.mPosition == POSITION_LEFT) {
+                getInfo(bounds, depths, parents, child, x - child.mWidth, y + child.mOffset,
+                        depth + 1);
+            } else if (child.mPosition == POSITION_RIGHT) {
+                getInfo(bounds, depths, parents, child, x + display.mWidth, y + child.mOffset,
+                        depth + 1);
+            } else if (child.mPosition == POSITION_TOP) {
+                getInfo(bounds, depths, parents, child, x + child.mOffset, y - child.mHeight,
+                        depth + 1);
+            } else if (child.mPosition == POSITION_BOTTOM) {
+                getInfo(bounds, depths, parents, child, x + child.mOffset, y + display.mHeight,
+                        depth + 1);
+            }
+        }
+    }
+
+    /**
+     * Update the topology to remove any overlaps between displays.
+     */
+    @VisibleForTesting
+    void normalize() {
+        if (mRoot == null) {
+            return;
+        }
+        Map<TreeNode, RectF> bounds = new HashMap<>();
+        Map<TreeNode, Integer> depths = new HashMap<>();
+        Map<TreeNode, TreeNode> parents = new HashMap<>();
+        getInfo(bounds, depths, parents, mRoot, /* x= */ 0, /* y= */ 0, /* depth= */ 0);
+
+        // Sort the displays first by their depth in the tree, then by the distance of their top
+        // left point from the root display's origin (0, 0). This way we process the displays
+        // starting at the root and we push out a display if necessary.
+        Comparator<TreeNode> comparator = (d1, d2) -> {
+            if (d1 == d2) {
+                return 0;
+            }
+
+            int compareDepths = Integer.compare(depths.get(d1), depths.get(d2));
+            if (compareDepths != 0) {
+                return compareDepths;
+            }
+
+            RectF bounds1 = bounds.get(d1);
+            RectF bounds2 = bounds.get(d2);
+            return Double.compare(Math.hypot(bounds1.left, bounds1.top),
+                    Math.hypot(bounds2.left, bounds2.top));
+        };
+        List<TreeNode> displays = new ArrayList<>(bounds.keySet());
+        displays.sort(comparator);
+
+        for (int i = 1; i < displays.size(); i++) {
+            TreeNode targetDisplay = displays.get(i);
+            TreeNode lastIntersectingSourceDisplay = null;
+            float lastOffsetX = 0;
+            float lastOffsetY = 0;
+
+            for (int j = 0; j < i; j++) {
+                TreeNode sourceDisplay = displays.get(j);
+                RectF sourceBounds = bounds.get(sourceDisplay);
+                RectF targetBounds = bounds.get(targetDisplay);
+
+                if (!RectF.intersects(sourceBounds, targetBounds)) {
+                    continue;
+                }
+
+                // Find the offset by which to move the display. Pick the smaller one among the x
+                // and y axes.
+                float offsetX = targetBounds.left >= 0
+                        ? sourceBounds.right - targetBounds.left
+                        : sourceBounds.left - targetBounds.right;
+                float offsetY = targetBounds.top >= 0
+                        ? sourceBounds.bottom - targetBounds.top
+                        : sourceBounds.top - targetBounds.bottom;
+                if (Math.abs(offsetX) <= Math.abs(offsetY)) {
+                    targetBounds.left += offsetX;
+                    targetBounds.right += offsetX;
+                    // We need to also update the offset in the tree
+                    if (targetDisplay.mPosition == POSITION_TOP
+                            || targetDisplay.mPosition == POSITION_BOTTOM) {
+                        targetDisplay.mOffset += offsetX;
+                    }
+                    offsetY = 0;
+                } else {
+                    targetBounds.top += offsetY;
+                    targetBounds.bottom += offsetY;
+                    // We need to also update the offset in the tree
+                    if (targetDisplay.mPosition == POSITION_LEFT
+                            || targetDisplay.mPosition == POSITION_RIGHT) {
+                        targetDisplay.mOffset += offsetY;
+                    }
+                    offsetX = 0;
+                }
+
+                lastIntersectingSourceDisplay = sourceDisplay;
+                lastOffsetX = offsetX;
+                lastOffsetY = offsetY;
+            }
+
+            // Now re-parent the target display to the last intersecting source display if it no
+            // longer touches its parent.
+            if (lastIntersectingSourceDisplay == null) {
+                // There was no overlap.
+                continue;
+            }
+            TreeNode parent = parents.get(targetDisplay);
+            if (parent == lastIntersectingSourceDisplay) {
+                // The displays are moved in such a way that they're adjacent to the intersecting
+                // display. If the last intersecting display happens to be the parent then we
+                // already know that the display is adjacent to its parent.
+                continue;
+            }
+
+            RectF childBounds = bounds.get(targetDisplay);
+            RectF parentBounds = bounds.get(parent);
+            // Check that the edges are on the same line
+            boolean areTouching = switch (targetDisplay.mPosition) {
+                case POSITION_LEFT -> floatEquals(parentBounds.left, childBounds.right);
+                case POSITION_RIGHT -> floatEquals(parentBounds.right, childBounds.left);
+                case POSITION_TOP -> floatEquals(parentBounds.top, childBounds.bottom);
+                case POSITION_BOTTOM -> floatEquals(parentBounds.bottom, childBounds.top);
+            };
+            // Check that the offset is within bounds
+            areTouching &= switch (targetDisplay.mPosition) {
+                case POSITION_LEFT, POSITION_RIGHT ->
+                        childBounds.bottom + EPSILON >= parentBounds.top
+                                && childBounds.top <= parentBounds.bottom + EPSILON;
+                case POSITION_TOP, POSITION_BOTTOM ->
+                        childBounds.right + EPSILON >= parentBounds.left
+                                && childBounds.left <= parentBounds.right + EPSILON;
+            };
+
+            if (!areTouching) {
+                // Re-parent the display.
+                parent.mChildren.remove(targetDisplay);
+                RectF lastIntersectingSourceDisplayBounds =
+                        bounds.get(lastIntersectingSourceDisplay);
+                lastIntersectingSourceDisplay.mChildren.add(targetDisplay);
+
+                if (lastOffsetX != 0) {
+                    targetDisplay.mPosition = lastOffsetX > 0 ? POSITION_RIGHT : POSITION_LEFT;
+                    targetDisplay.mOffset =
+                            childBounds.top - lastIntersectingSourceDisplayBounds.top;
+                } else if (lastOffsetY != 0) {
+                    targetDisplay.mPosition = lastOffsetY > 0 ? POSITION_BOTTOM : POSITION_TOP;
+                    targetDisplay.mOffset =
+                            childBounds.left - lastIntersectingSourceDisplayBounds.left;
+                }
+            }
+        }
+    }
+
+    /**
+     * Tests whether two brightness float values are within a small enough tolerance
+     * of each other.
+     * @param a first float to compare
+     * @param b second float to compare
+     * @return whether the two values are within a small enough tolerance value
+     */
+    public static boolean floatEquals(float a, float b) {
+        return a == b || Float.isNaN(a) && Float.isNaN(b) || Math.abs(a - b) < EPSILON;
     }
 
     @VisibleForTesting
@@ -201,13 +396,13 @@
          * The width of the display in density-independent pixels (dp).
          */
         @VisibleForTesting
-        double mWidth;
+        float mWidth;
 
         /**
          * The height of the display in density-independent pixels (dp).
          */
         @VisibleForTesting
-        double mHeight;
+        float mHeight;
 
         /**
          * The position of this display relative to its parent.
@@ -222,13 +417,12 @@
          * used is density-independent pixels (dp).
          */
         @VisibleForTesting
-        double mOffset;
+        float mOffset;
 
         @VisibleForTesting
         final List<TreeNode> mChildren = new ArrayList<>();
 
-        TreeNode(int displayId, double width, double height, Position position,
-                double offset) {
+        TreeNode(int displayId, float width, float height, Position position, float offset) {
             mDisplayId = displayId;
             mWidth = width;
             mHeight = height;
diff --git a/services/core/java/com/android/server/display/DisplayTopologyCoordinator.java b/services/core/java/com/android/server/display/DisplayTopologyCoordinator.java
index 46358dfd..b101e58 100644
--- a/services/core/java/com/android/server/display/DisplayTopologyCoordinator.java
+++ b/services/core/java/com/android/server/display/DisplayTopologyCoordinator.java
@@ -89,8 +89,8 @@
      * @param info The display info
      * @return The width of the display in dp
      */
-    private double getWidth(DisplayInfo info) {
-        return info.logicalWidth * (double) DisplayMetrics.DENSITY_DEFAULT
+    private float getWidth(DisplayInfo info) {
+        return info.logicalWidth * (float) DisplayMetrics.DENSITY_DEFAULT
                 / info.logicalDensityDpi;
     }
 
@@ -98,8 +98,8 @@
      * @param info The display info
      * @return The height of the display in dp
      */
-    private double getHeight(DisplayInfo info) {
-        return info.logicalHeight * (double) DisplayMetrics.DENSITY_DEFAULT
+    private float getHeight(DisplayInfo info) {
+        return info.logicalHeight * (float) DisplayMetrics.DENSITY_DEFAULT
                 / info.logicalDensityDpi;
     }
 
diff --git a/services/core/java/com/android/server/hdmi/HdmiCecLocalDevicePlayback.java b/services/core/java/com/android/server/hdmi/HdmiCecLocalDevicePlayback.java
index d0ad6fc..b696c54 100644
--- a/services/core/java/com/android/server/hdmi/HdmiCecLocalDevicePlayback.java
+++ b/services/core/java/com/android/server/hdmi/HdmiCecLocalDevicePlayback.java
@@ -444,14 +444,62 @@
                         new Runnable() {
                             @Override
                             public void run() {
-                                if (!isActiveSource()) {
+                                if (isActiveSource()) {
+                                    return;
+                                }
+
+                                if (getActiveSource().logicalAddress != Constants.ADDR_TV) {
                                     startHdmiCecActiveSourceLostActivity();
                                     mDelayedStandbyOnActiveSourceLostHandler
                                             .removeCallbacksAndMessages(null);
                                     mDelayedStandbyOnActiveSourceLostHandler.postDelayed(
                                             new DelayedStandbyOnActiveSourceLostRunnable(),
                                             STANDBY_AFTER_ACTIVE_SOURCE_LOST_DELAY_MS);
+                                    return;
                                 }
+
+                                // We observed specific TV panels (old models) that send faulty CEC
+                                // source changing messages, especially during wake-up.
+                                // This request helps to check if the TV correctly asserted active
+                                // source or not. If the request times out, active source is
+                                // asserted by the local device.
+                                addAndStartAction(new RequestActiveSourceAction(mService.playback(),
+                                        new IHdmiControlCallback.Stub() {
+                                    @Override
+                                    public void onComplete(int result) {
+                                        // If a device answers to <Request Active Source>, the
+                                        // pop-up should be triggered.
+                                        // During this action, the TV can switch to an HDMI input
+                                        // with a non-CEC capable device that won't be able to
+                                        // answer this request.
+                                        // In this case, the known active source would be
+                                        // represented by a valid physical address, but invalid
+                                        // logical address. The pop-up will be shown and the local
+                                        // device will not assert active source.
+                                        if (result == HdmiControlManager.RESULT_SUCCESS
+                                                || getActiveSource().logicalAddress
+                                                != Constants.ADDR_TV) {
+                                                startHdmiCecActiveSourceLostActivity();
+                                                mDelayedStandbyOnActiveSourceLostHandler
+                                                        .removeCallbacksAndMessages(null);
+                                                mDelayedStandbyOnActiveSourceLostHandler
+                                                        .postDelayed(
+                                                                new DelayedStandbyOnActiveSourceLostRunnable(),
+                                                        STANDBY_AFTER_ACTIVE_SOURCE_LOST_DELAY_MS);
+                                        } else {
+                                            // The request times out and the local device is not
+                                            // active source, but the TV previously asserted active
+                                            // source.
+                                            if (getActiveSource().logicalAddress
+                                                    == Constants.ADDR_TV) {
+                                                mService.setAndBroadcastActiveSource(
+                                                        mService.getPhysicalAddress(),
+                                                        getDeviceInfo().getDeviceType(),
+                                                        Constants.ADDR_BROADCAST,
+                                                        "RequestActiveSourceAction#RESULT_TIMEOUT");
+                                            }
+                                        }
+                                    }}));
                             }
                         }, POPUP_AFTER_ACTIVE_SOURCE_LOST_DELAY_MS);
                 return;
@@ -698,6 +746,7 @@
         removeAction(HotplugDetectionAction.class);
         removeAction(NewDeviceAction.class);
         removeAction(PowerStatusMonitorActionFromPlayback.class);
+        removeAction(RequestActiveSourceAction.class);
         super.disableDevice(initiatedByCec, callback);
         clearDeviceInfoList();
         checkIfPendingActionsCleared();
diff --git a/services/core/java/com/android/server/hdmi/HdmiCecLocalDeviceTv.java b/services/core/java/com/android/server/hdmi/HdmiCecLocalDeviceTv.java
index aae7b59..5682c33 100644
--- a/services/core/java/com/android/server/hdmi/HdmiCecLocalDeviceTv.java
+++ b/services/core/java/com/android/server/hdmi/HdmiCecLocalDeviceTv.java
@@ -471,6 +471,10 @@
     void startRoutingControl(int oldPath, int newPath, IHdmiControlCallback callback) {
         assertRunOnServiceThread();
         if (oldPath == newPath) {
+            HdmiCecMessage setStreamPath =
+                    HdmiCecMessageBuilder.buildSetStreamPath(getDeviceInfo().getLogicalAddress(),
+                            oldPath);
+            mService.sendCecCommand(setStreamPath);
             return;
         }
         HdmiCecMessage routingChange =
@@ -642,7 +646,8 @@
         int address = message.getSource();
         int type = message.getParams()[2];
 
-        if (!mService.getHdmiCecNetwork().isInDeviceList(address, path)) {
+        if (!ActiveSource.of(address, path).equals(getActiveSource())) {
+            HdmiLogger.debug("Check if a new device is connected to the active path");
             handleNewDeviceAtTheTailOfActivePath(path);
         }
         startNewDeviceAction(ActiveSource.of(address, path), type);
diff --git a/services/core/java/com/android/server/hdmi/HdmiControlService.java b/services/core/java/com/android/server/hdmi/HdmiControlService.java
index 81be0ba..cb0b4b0 100644
--- a/services/core/java/com/android/server/hdmi/HdmiControlService.java
+++ b/services/core/java/com/android/server/hdmi/HdmiControlService.java
@@ -4344,6 +4344,7 @@
         if (deviceType == HdmiDeviceInfo.DEVICE_PLAYBACK) {
             HdmiCecLocalDevicePlayback playback = playback();
             playback.dismissUiOnActiveSourceStatusRecovered();
+            playback.removeAction(RequestActiveSourceAction.class);
             playback.setActiveSource(playback.getDeviceInfo().getLogicalAddress(), physicalAddress,
                     caller);
             playback.wakeUpIfActiveSource();
diff --git a/services/core/java/com/android/server/hdmi/RequestActiveSourceAction.java b/services/core/java/com/android/server/hdmi/RequestActiveSourceAction.java
index a33d70a..b0e9398 100644
--- a/services/core/java/com/android/server/hdmi/RequestActiveSourceAction.java
+++ b/services/core/java/com/android/server/hdmi/RequestActiveSourceAction.java
@@ -22,11 +22,11 @@
 import com.android.internal.annotations.VisibleForTesting;
 
 /**
- * Feature action that sends <Request Active Source> message and waits for <Active Source> on TV
- * panels.
- * This action has a delay before sending <Request Active Source>. This is because it should wait
- * for a possible request from LauncherX and can be cancelled if an <Active Source> message was
- * received or the TV switched to another input.
+ * Feature action that sends <Request Active Source> message and waits for <Active Source>.
+ *
+ * For TV panels, this action has a delay before sending <Request Active Source>. This is because it
+ * should wait for a possible request from LauncherX and can be cancelled if an <Active Source>
+ * message was received or the TV switched to another input.
  */
 public class RequestActiveSourceAction extends HdmiCecFeatureAction {
     private static final String TAG = "RequestActiveSourceAction";
@@ -55,6 +55,13 @@
     boolean start() {
         Slog.v(TAG, "RequestActiveSourceAction started.");
 
+        if (localDevice().mService.isPlaybackDevice()) {
+            mState = STATE_WAIT_FOR_ACTIVE_SOURCE;
+            sendCommand(HdmiCecMessageBuilder.buildRequestActiveSource(getSourceAddress()));
+            addTimer(mState, HdmiConfig.TIMEOUT_MS);
+            return true;
+        }
+
         mState = STATE_WAIT_FOR_LAUNCHERX_API_CALL;
 
         // We wait for default timeout to allow the message triggered by the LauncherX API call to
diff --git a/services/core/java/com/android/server/input/InputManagerInternal.java b/services/core/java/com/android/server/input/InputManagerInternal.java
index e40d855..1c5bd59 100644
--- a/services/core/java/com/android/server/input/InputManagerInternal.java
+++ b/services/core/java/com/android/server/input/InputManagerInternal.java
@@ -127,6 +127,13 @@
      */
     public abstract void notifyInputMethodConnectionActive(boolean connectionIsActive);
 
+    /**
+     * Notify user id changes to input.
+     *
+     * TODO(b/362473586): Cleanup after input shifts to Lifecycle with user change callbacks
+     */
+    public abstract void setCurrentUser(@UserIdInt int newUserId);
+
     /** Callback interface for notifications relating to the lid switch. */
     public interface LidSwitchCallback {
         /**
diff --git a/services/core/java/com/android/server/input/InputManagerService.java b/services/core/java/com/android/server/input/InputManagerService.java
index bea520f..a421d04 100644
--- a/services/core/java/com/android/server/input/InputManagerService.java
+++ b/services/core/java/com/android/server/input/InputManagerService.java
@@ -175,6 +175,7 @@
     private static final int MSG_DELIVER_INPUT_DEVICES_CHANGED = 1;
     private static final int MSG_RELOAD_DEVICE_ALIASES = 2;
     private static final int MSG_DELIVER_TABLET_MODE_CHANGED = 3;
+    private static final int MSG_CURRENT_USER_CHANGED = 4;
 
     private static final int DEFAULT_VIBRATION_MAGNITUDE = 192;
     private static final AdditionalDisplayInputProperties
@@ -184,6 +185,8 @@
 
     private final Context mContext;
     private final InputManagerHandler mHandler;
+    @UserIdInt
+    private int mCurrentUserId = UserHandle.USER_SYSTEM;
     private DisplayManagerInternal mDisplayManagerInternal;
 
     private WindowManagerInternal mWindowManagerInternal;
@@ -2982,6 +2985,10 @@
         mKeyGestureController.unregisterKeyGestureHandler(handler, Binder.getCallingPid());
     }
 
+    private void handleCurrentUserChanged(@UserIdInt int userId) {
+        mCurrentUserId = userId;
+    }
+
     /**
      * Callback interface implemented by the Window Manager.
      */
@@ -3150,6 +3157,9 @@
                     boolean inTabletMode = (boolean) args.arg1;
                     deliverTabletModeChanged(whenNanos, inTabletMode);
                     break;
+                case MSG_CURRENT_USER_CHANGED:
+                    handleCurrentUserChanged((int) msg.obj);
+                    break;
             }
         }
     }
@@ -3513,6 +3523,11 @@
         }
 
         @Override
+        public void setCurrentUser(@UserIdInt int newUserId) {
+            mHandler.obtainMessage(MSG_CURRENT_USER_CHANGED, newUserId).sendToTarget();
+        }
+
+        @Override
         public boolean setKernelWakeEnabled(int deviceId, boolean enabled) {
             return mNative.setKernelWakeEnabled(deviceId, enabled);
         }
diff --git a/services/core/java/com/android/server/input/KeyGestureController.java b/services/core/java/com/android/server/input/KeyGestureController.java
index bd1625e..4d93e65 100644
--- a/services/core/java/com/android/server/input/KeyGestureController.java
+++ b/services/core/java/com/android/server/input/KeyGestureController.java
@@ -1012,16 +1012,16 @@
         if (device == null) {
             return;
         }
+        KeyGestureEvent keyGestureEvent = new KeyGestureEvent(event);
         if (event.action == KeyGestureEvent.ACTION_GESTURE_COMPLETE) {
             KeyboardMetricsCollector.logKeyboardSystemsEventReportedAtom(device, event.keycodes,
-                    event.modifierState,
-                    KeyGestureEvent.keyGestureTypeToLogEvent(event.gestureType));
+                    event.modifierState, keyGestureEvent.getLogEvent());
         }
         notifyAllListeners(event);
         while (mLastHandledEvents.size() >= MAX_TRACKED_EVENTS) {
             mLastHandledEvents.removeFirst();
         }
-        mLastHandledEvents.addLast(new KeyGestureEvent(event));
+        mLastHandledEvents.addLast(keyGestureEvent);
     }
 
     @MainThread
diff --git a/services/core/java/com/android/server/integrity/parser/RuleIndexRange.java b/services/core/java/com/android/server/integrity/parser/RuleIndexRange.java
index 595a035..408df5a 100644
--- a/services/core/java/com/android/server/integrity/parser/RuleIndexRange.java
+++ b/services/core/java/com/android/server/integrity/parser/RuleIndexRange.java
@@ -19,8 +19,7 @@
 import android.annotation.Nullable;
 
 /**
- * A wrapper class to represent an indexing range that is identified by the {@link
- * RuleIndexingController}.
+ * A wrapper class to represent an indexing range.
  */
 public class RuleIndexRange {
     private int mStartIndex;
diff --git a/services/core/java/com/android/server/integrity/parser/RuleIndexingController.java b/services/core/java/com/android/server/integrity/parser/RuleIndexingController.java
deleted file mode 100644
index 348a03b..0000000
--- a/services/core/java/com/android/server/integrity/parser/RuleIndexingController.java
+++ /dev/null
@@ -1,128 +0,0 @@
-/*
- * Copyright (C) 2020 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- *      http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-package com.android.server.integrity.parser;
-
-import static com.android.server.integrity.model.IndexingFileConstants.END_INDEXING_KEY;
-import static com.android.server.integrity.model.IndexingFileConstants.START_INDEXING_KEY;
-import static com.android.server.integrity.parser.BinaryFileOperations.getIntValue;
-import static com.android.server.integrity.parser.BinaryFileOperations.getStringValue;
-
-import android.content.integrity.AppInstallMetadata;
-
-import com.android.server.integrity.model.BitInputStream;
-
-import java.io.IOException;
-import java.io.InputStream;
-import java.util.ArrayList;
-import java.util.Arrays;
-import java.util.LinkedHashMap;
-import java.util.List;
-import java.util.stream.Collectors;
-
-/** Helper class to identify the necessary indexes that needs to be read. */
-public class RuleIndexingController {
-
-    private static LinkedHashMap<String, Integer> sPackageNameBasedIndexes;
-    private static LinkedHashMap<String, Integer> sAppCertificateBasedIndexes;
-    private static LinkedHashMap<String, Integer> sUnindexedRuleIndexes;
-
-    /**
-     * Provide the indexing file to read and the object will be constructed by reading and
-     * identifying the indexes.
-     */
-    public RuleIndexingController(InputStream inputStream) throws IOException {
-        BitInputStream bitInputStream = new BitInputStream(inputStream);
-        sPackageNameBasedIndexes = getNextIndexGroup(bitInputStream);
-        sAppCertificateBasedIndexes = getNextIndexGroup(bitInputStream);
-        sUnindexedRuleIndexes = getNextIndexGroup(bitInputStream);
-    }
-
-    /**
-     * Returns a list of integers with the starting and ending bytes of the rules that needs to be
-     * read and evaluated.
-     */
-    public List<RuleIndexRange> identifyRulesToEvaluate(AppInstallMetadata appInstallMetadata) {
-        List<RuleIndexRange> indexRanges = new ArrayList<>();
-
-        // Add the range for package name indexes rules.
-        indexRanges.add(
-                searchIndexingKeysRangeContainingKey(
-                        sPackageNameBasedIndexes, appInstallMetadata.getPackageName()));
-
-        // Add the range for app certificate indexes rules of all certificates.
-        for (String appCertificate : appInstallMetadata.getAppCertificates()) {
-            indexRanges.add(
-                    searchIndexingKeysRangeContainingKey(
-                            sAppCertificateBasedIndexes, appCertificate));
-        }
-
-        // Add the range for unindexed rules.
-        indexRanges.add(
-                new RuleIndexRange(
-                        sUnindexedRuleIndexes.get(START_INDEXING_KEY),
-                        sUnindexedRuleIndexes.get(END_INDEXING_KEY)));
-
-        return indexRanges;
-    }
-
-    private LinkedHashMap<String, Integer> getNextIndexGroup(BitInputStream bitInputStream)
-            throws IOException {
-        LinkedHashMap<String, Integer> keyToIndexMap = new LinkedHashMap<>();
-        while (bitInputStream.hasNext()) {
-            String key = getStringValue(bitInputStream);
-            int value = getIntValue(bitInputStream);
-
-            keyToIndexMap.put(key, value);
-
-            if (key.matches(END_INDEXING_KEY)) {
-                break;
-            }
-        }
-        if (keyToIndexMap.size() < 2) {
-            throw new IllegalStateException("Indexing file is corrupt.");
-        }
-        return keyToIndexMap;
-    }
-
-    private static RuleIndexRange searchIndexingKeysRangeContainingKey(
-            LinkedHashMap<String, Integer> indexMap, String searchedKey) {
-        List<String> keys = indexMap.keySet().stream().collect(Collectors.toList());
-        List<String> identifiedKeyRange =
-                searchKeysRangeContainingKey(keys, searchedKey, 0, keys.size() - 1);
-        return new RuleIndexRange(
-                indexMap.get(identifiedKeyRange.get(0)), indexMap.get(identifiedKeyRange.get(1)));
-    }
-
-    private static List<String> searchKeysRangeContainingKey(
-            List<String> sortedKeyList, String key, int startIndex, int endIndex) {
-        if (endIndex <= startIndex) {
-            throw new IllegalStateException("Indexing file is corrupt.");
-        }
-        if (endIndex - startIndex == 1) {
-            return Arrays.asList(sortedKeyList.get(startIndex), sortedKeyList.get(endIndex));
-        }
-
-        int midKeyIndex = startIndex + ((endIndex - startIndex) / 2);
-        String midKey = sortedKeyList.get(midKeyIndex);
-
-        if (key.compareTo(midKey) >= 0) {
-            return searchKeysRangeContainingKey(sortedKeyList, key, midKeyIndex, endIndex);
-        } else {
-            return searchKeysRangeContainingKey(sortedKeyList, key, startIndex, midKeyIndex);
-        }
-    }
-}
diff --git a/services/core/java/com/android/server/notification/NotificationManagerService.java b/services/core/java/com/android/server/notification/NotificationManagerService.java
index 48cc032..abf3da4 100644
--- a/services/core/java/com/android/server/notification/NotificationManagerService.java
+++ b/services/core/java/com/android/server/notification/NotificationManagerService.java
@@ -49,10 +49,6 @@
 import static android.app.Notification.FLAG_PROMOTED_ONGOING;
 import static android.app.Notification.FLAG_USER_INITIATED_JOB;
 import static android.app.NotificationChannel.CONVERSATION_CHANNEL_ID_FORMAT;
-import static android.app.NotificationChannel.NEWS_ID;
-import static android.app.NotificationChannel.PROMOTIONS_ID;
-import static android.app.NotificationChannel.RECS_ID;
-import static android.app.NotificationChannel.SOCIAL_MEDIA_ID;
 import static android.app.NotificationChannel.SYSTEM_RESERVED_IDS;
 import static android.app.NotificationManager.ACTION_APP_BLOCK_STATE_CHANGED;
 import static android.app.NotificationManager.ACTION_AUTOMATIC_ZEN_RULE_STATUS_CHANGED;
@@ -108,9 +104,7 @@
 import static android.os.UserHandle.USER_SYSTEM;
 import static android.service.notification.Adjustment.KEY_TYPE;
 import static android.service.notification.Adjustment.TYPE_CONTENT_RECOMMENDATION;
-import static android.service.notification.Adjustment.TYPE_NEWS;
 import static android.service.notification.Adjustment.TYPE_PROMOTION;
-import static android.service.notification.Adjustment.TYPE_SOCIAL_MEDIA;
 import static android.service.notification.Flags.callstyleCallbackApi;
 import static android.service.notification.Flags.notificationClassification;
 import static android.service.notification.Flags.notificationForceGrouping;
@@ -6924,21 +6918,19 @@
 
     @GuardedBy("mNotificationLock")
     @Nullable
+    @FlaggedApi(android.service.notification.Flags.FLAG_NOTIFICATION_CLASSIFICATION)
     private NotificationChannel getClassificationChannelLocked(NotificationRecord r,
             Bundle adjustments) {
         int type = adjustments.getInt(KEY_TYPE);
-        if (TYPE_NEWS == type) {
-            return mPreferencesHelper.getNotificationChannel(
-                    r.getSbn().getPackageName(), r.getUid(), NEWS_ID, false);
-        } else if (TYPE_PROMOTION == type) {
-            return mPreferencesHelper.getNotificationChannel(
-                    r.getSbn().getPackageName(), r.getUid(), PROMOTIONS_ID, false);
-        } else if (TYPE_SOCIAL_MEDIA == type) {
-            return mPreferencesHelper.getNotificationChannel(
-                    r.getSbn().getPackageName(), r.getUid(), SOCIAL_MEDIA_ID, false);
-        } else if (TYPE_CONTENT_RECOMMENDATION == type) {
-            return mPreferencesHelper.getNotificationChannel(
-                    r.getSbn().getPackageName(), r.getUid(), RECS_ID, false);
+        if (type >= TYPE_PROMOTION && type <= TYPE_CONTENT_RECOMMENDATION) {
+            NotificationChannel channel = mPreferencesHelper.getReservedChannel(
+                    r.getSbn().getPackageName(), r.getUid(), type);
+            if (channel == null) {
+                channel = mPreferencesHelper.createReservedChannel(
+                        r.getSbn().getPackageName(), r.getUid(), type);
+                handleSavePolicyFile();
+            }
+            return channel;
         }
         return null;
     }
diff --git a/services/core/java/com/android/server/notification/PreferencesHelper.java b/services/core/java/com/android/server/notification/PreferencesHelper.java
index 964a5d0..d26a5aa 100644
--- a/services/core/java/com/android/server/notification/PreferencesHelper.java
+++ b/services/core/java/com/android/server/notification/PreferencesHelper.java
@@ -17,6 +17,7 @@
 package com.android.server.notification;
 
 import static android.app.AppOpsManager.OP_SYSTEM_ALERT_WINDOW;
+import static android.app.NotificationChannel.DEFAULT_CHANNEL_ID;
 import static android.app.NotificationChannel.NEWS_ID;
 import static android.app.NotificationChannel.PLACEHOLDER_CONVERSATION_ID;
 import static android.app.NotificationChannel.PROMOTIONS_ID;
@@ -32,6 +33,10 @@
 import static android.app.NotificationManager.IMPORTANCE_NONE;
 import static android.app.NotificationManager.IMPORTANCE_UNSPECIFIED;
 import static android.os.UserHandle.USER_SYSTEM;
+import static android.service.notification.Adjustment.TYPE_CONTENT_RECOMMENDATION;
+import static android.service.notification.Adjustment.TYPE_NEWS;
+import static android.service.notification.Adjustment.TYPE_PROMOTION;
+import static android.service.notification.Adjustment.TYPE_SOCIAL_MEDIA;
 import static android.service.notification.Flags.notificationClassification;
 
 import static com.android.internal.util.FrameworkStatsLog.PACKAGE_NOTIFICATION_CHANNEL_GROUP_PREFERENCES;
@@ -66,6 +71,7 @@
 import android.os.UserHandle;
 import android.permission.PermissionManager;
 import android.provider.Settings;
+import android.service.notification.Adjustment;
 import android.service.notification.ConversationChannelWrapper;
 import android.service.notification.NotificationListenerService;
 import android.service.notification.RankingHelperProto;
@@ -549,10 +555,6 @@
                 Slog.e(TAG, "createDefaultChannelIfNeededLocked - Exception: " + e);
             }
 
-            if (notificationClassification()) {
-                addReservedChannelsLocked(r);
-            }
-
             if (r.uid == UNKNOWN_UID) {
                 if (Flags.persistIncompleteRestoreData()) {
                     r.userId = userId;
@@ -587,7 +589,7 @@
 
     private boolean deleteDefaultChannelIfNeededLocked(PackagePreferences r) throws
             PackageManager.NameNotFoundException {
-        if (!r.channels.containsKey(NotificationChannel.DEFAULT_CHANNEL_ID)) {
+        if (!r.channels.containsKey(DEFAULT_CHANNEL_ID)) {
             // Not present
             return false;
         }
@@ -598,7 +600,7 @@
         }
 
         // Remove Default Channel.
-        r.channels.remove(NotificationChannel.DEFAULT_CHANNEL_ID);
+        r.channels.remove(DEFAULT_CHANNEL_ID);
 
         return true;
     }
@@ -609,8 +611,8 @@
             return false;
         }
 
-        if (r.channels.containsKey(NotificationChannel.DEFAULT_CHANNEL_ID)) {
-            r.channels.get(NotificationChannel.DEFAULT_CHANNEL_ID).setName(mContext.getString(
+        if (r.channels.containsKey(DEFAULT_CHANNEL_ID)) {
+            r.channels.get(DEFAULT_CHANNEL_ID).setName(mContext.getString(
                     com.android.internal.R.string.default_notification_channel_label));
             return false;
         }
@@ -623,7 +625,7 @@
         // Create Default Channel
         NotificationChannel channel;
         channel = new NotificationChannel(
-                NotificationChannel.DEFAULT_CHANNEL_ID,
+                DEFAULT_CHANNEL_ID,
                 mContext.getString(R.string.default_notification_channel_label),
                 r.importance);
         channel.setBypassDnd(r.priority == Notification.PRIORITY_MAX);
@@ -642,38 +644,25 @@
         return true;
     }
 
-    private void addReservedChannelsLocked(PackagePreferences p) {
-        if (!p.channels.containsKey(NotificationChannel.PROMOTIONS_ID)) {
-            NotificationChannel channel = new NotificationChannel(
-                    NotificationChannel.PROMOTIONS_ID,
-                    mContext.getString(R.string.promotional_notification_channel_label),
-                    IMPORTANCE_LOW);
-            p.channels.put(channel.getId(), channel);
+    private NotificationChannel addReservedChannelLocked(PackagePreferences p, String channelId) {
+        String label = "";
+        switch (channelId) {
+            case PROMOTIONS_ID:
+                label = mContext.getString(R.string.promotional_notification_channel_label);
+                break;
+            case RECS_ID:
+                label = mContext.getString(R.string.recs_notification_channel_label);
+                break;
+            case NEWS_ID:
+                label = mContext.getString(R.string.news_notification_channel_label);
+                break;
+            case SOCIAL_MEDIA_ID:
+                label = mContext.getString(R.string.social_notification_channel_label);
+                break;
         }
-
-        if (!p.channels.containsKey(NotificationChannel.RECS_ID)) {
-            NotificationChannel channel = new NotificationChannel(
-                    NotificationChannel.RECS_ID,
-                    mContext.getString(R.string.recs_notification_channel_label),
-                    IMPORTANCE_LOW);
-            p.channels.put(channel.getId(), channel);
-        }
-
-        if (!p.channels.containsKey(NotificationChannel.NEWS_ID)) {
-            NotificationChannel channel = new NotificationChannel(
-                    NotificationChannel.NEWS_ID,
-                    mContext.getString(R.string.news_notification_channel_label),
-                    IMPORTANCE_LOW);
-            p.channels.put(channel.getId(), channel);
-        }
-
-        if (!p.channels.containsKey(NotificationChannel.SOCIAL_MEDIA_ID)) {
-            NotificationChannel channel = new NotificationChannel(
-                    NotificationChannel.SOCIAL_MEDIA_ID,
-                    mContext.getString(R.string.social_notification_channel_label),
-                    IMPORTANCE_LOW);
-            p.channels.put(channel.getId(), channel);
-        }
+        NotificationChannel channel = new NotificationChannel(channelId, label, IMPORTANCE_LOW);
+        p.channels.put(channelId, channel);
+        return channel;
     }
 
     public void writeXml(TypedXmlSerializer out, boolean forBackup, int userId) throws IOException {
@@ -1078,7 +1067,7 @@
             if (channel.getGroup() != null && !r.groups.containsKey(channel.getGroup())) {
                 throw new IllegalArgumentException("NotificationChannelGroup doesn't exist");
             }
-            if (NotificationChannel.DEFAULT_CHANNEL_ID.equals(channel.getId())) {
+            if (DEFAULT_CHANNEL_ID.equals(channel.getId())) {
                 throw new IllegalArgumentException("Reserved id");
             }
             // Only the user can update bundle channel settings
@@ -1411,6 +1400,54 @@
         }
     }
 
+    private @Nullable String getChannelIdForBundleType(@Adjustment.Types int type) {
+        switch (type) {
+            case TYPE_CONTENT_RECOMMENDATION:
+                return RECS_ID;
+            case TYPE_NEWS:
+                return NEWS_ID;
+            case TYPE_PROMOTION:
+                return PROMOTIONS_ID;
+            case TYPE_SOCIAL_MEDIA:
+                return SOCIAL_MEDIA_ID;
+        }
+        return null;
+    }
+
+    @FlaggedApi(android.service.notification.Flags.FLAG_NOTIFICATION_CLASSIFICATION)
+    public NotificationChannel getReservedChannel(String pkg, int uid,
+            @Adjustment.Types int type) {
+        if (!notificationClassification()) {
+            return null;
+        }
+        Objects.requireNonNull(pkg);
+        String channelId = getChannelIdForBundleType(type);
+        if (channelId == null) {
+            return null;
+        }
+        NotificationChannel channel =
+                getConversationNotificationChannel(pkg, uid, channelId, null, true, false);
+        return channel;
+    }
+
+    @FlaggedApi(android.service.notification.Flags.FLAG_NOTIFICATION_CLASSIFICATION)
+    public NotificationChannel createReservedChannel(String pkg, int uid,
+            @Adjustment.Types int type) {
+        if (!notificationClassification()) {
+            return null;
+        }
+        Objects.requireNonNull(pkg);
+        PackagePreferences r = getOrCreatePackagePreferencesLocked(pkg, uid);
+        if (r == null) {
+            return null;
+        }
+        String channelId = getChannelIdForBundleType(type);
+        if (channelId == null) {
+            return null;
+        }
+        return addReservedChannelLocked(r, channelId);
+    }
+
     @Override
     public NotificationChannel getNotificationChannel(String pkg, int uid, String channelId,
             boolean includeDeleted) {
@@ -1429,7 +1466,7 @@
                 return null;
             }
             if (channelId == null) {
-                channelId = NotificationChannel.DEFAULT_CHANNEL_ID;
+                channelId = DEFAULT_CHANNEL_ID;
             }
             NotificationChannel channel = null;
             if (conversationId != null) {
@@ -1540,7 +1577,7 @@
             int N = r.channels.size() - 1;
             for (int i = N; i >= 0; i--) {
                 String key = r.channels.keyAt(i);
-                if (!NotificationChannel.DEFAULT_CHANNEL_ID.equals(key)) {
+                if (!DEFAULT_CHANNEL_ID.equals(key)) {
                     r.channels.remove(key);
                 }
             }
@@ -1658,10 +1695,7 @@
                         && (activeChannelFilter == null
                                 || (includeBlocked && nc.getImportance() == IMPORTANCE_NONE)
                                 || activeChannelFilter.contains(nc.getId()))
-                        && !PROMOTIONS_ID.equals(nc.getId())
-                        && !NEWS_ID.equals(nc.getId())
-                        && !SOCIAL_MEDIA_ID.equals(nc.getId())
-                        && !RECS_ID.equals(nc.getId());
+                        && !SYSTEM_RESERVED_IDS.contains(nc.getId());
                 if (includeChannel) {
                     if (nc.getGroup() != null) {
                         if (r.groups.get(nc.getGroup()) != null) {
@@ -1924,9 +1958,23 @@
     public boolean onlyHasDefaultChannel(String pkg, int uid) {
         synchronized (mLock) {
             PackagePreferences r = getOrCreatePackagePreferencesLocked(pkg, uid);
-            if (r.channels.size() == (notificationClassification() ? 5 : 1)
-                    && r.channels.containsKey(NotificationChannel.DEFAULT_CHANNEL_ID)) {
-                return true;
+            if (r.channels.containsKey(DEFAULT_CHANNEL_ID)) {
+                if (r.channels.size() == 1) {
+                    return true;
+                }
+                if (notificationClassification()) {
+                    if (r.channels.size() <= 5) {
+                        for (NotificationChannel c : r.channels.values()) {
+                            if (!SYSTEM_RESERVED_IDS.contains(c.getId()) &&
+                                    !DEFAULT_CHANNEL_ID.equals(c.getId())) {
+                                return false;
+                            }
+                            return true;
+                        }
+                    } else {
+                        return false;
+                    }
+                }
             }
             return false;
         }
@@ -2744,9 +2792,9 @@
                 PackagePreferences PackagePreferences = mPackagePreferences.valueAt(i);
                 if (UserHandle.getUserId(PackagePreferences.uid) == userId) {
                     if (PackagePreferences.channels.containsKey(
-                            NotificationChannel.DEFAULT_CHANNEL_ID)) {
+                            DEFAULT_CHANNEL_ID)) {
                         PackagePreferences.channels.get(
-                                NotificationChannel.DEFAULT_CHANNEL_ID).setName(
+                                DEFAULT_CHANNEL_ID).setName(
                                 context.getResources().getString(
                                         R.string.default_notification_channel_label));
                     }
diff --git a/services/core/java/com/android/server/pm/DefaultCrossProfileIntentFiltersUtils.java b/services/core/java/com/android/server/pm/DefaultCrossProfileIntentFiltersUtils.java
index 5ac883c..9c24abc 100644
--- a/services/core/java/com/android/server/pm/DefaultCrossProfileIntentFiltersUtils.java
+++ b/services/core/java/com/android/server/pm/DefaultCrossProfileIntentFiltersUtils.java
@@ -316,6 +316,8 @@
                     /* letsPersonalDataIntoProfile= */ true)
                     .addAction(MediaStore.ACTION_IMAGE_CAPTURE)
                     .addAction(MediaStore.ACTION_IMAGE_CAPTURE_SECURE)
+                    .addAction(MediaStore.ACTION_MOTION_PHOTO_CAPTURE)
+                    .addAction(MediaStore.ACTION_MOTION_PHOTO_CAPTURE_SECURE)
                     .addAction(MediaStore.ACTION_VIDEO_CAPTURE)
                     .addAction(MediaStore.Audio.Media.RECORD_SOUND_ACTION)
                     .addAction(MediaStore.INTENT_ACTION_STILL_IMAGE_CAMERA)
@@ -438,6 +440,8 @@
                     /* letsPersonalDataIntoProfile= */ false)
                     .addAction(MediaStore.ACTION_IMAGE_CAPTURE)
                     .addAction(MediaStore.ACTION_IMAGE_CAPTURE_SECURE)
+                    .addAction(MediaStore.ACTION_MOTION_PHOTO_CAPTURE)
+                    .addAction(MediaStore.ACTION_MOTION_PHOTO_CAPTURE_SECURE)
                     .addAction(MediaStore.ACTION_VIDEO_CAPTURE)
                     .addAction(MediaStore.Audio.Media.RECORD_SOUND_ACTION)
                     .addAction(MediaStore.INTENT_ACTION_STILL_IMAGE_CAMERA)
diff --git a/services/core/java/com/android/server/pm/PackageInstallerService.java b/services/core/java/com/android/server/pm/PackageInstallerService.java
index f6a808b..a59f4bd 100644
--- a/services/core/java/com/android/server/pm/PackageInstallerService.java
+++ b/services/core/java/com/android/server/pm/PackageInstallerService.java
@@ -39,6 +39,7 @@
 import static org.xmlpull.v1.XmlPullParser.START_TAG;
 
 import android.Manifest;
+import android.annotation.EnforcePermission;
 import android.annotation.NonNull;
 import android.annotation.Nullable;
 import android.app.ActivityManager;
@@ -87,6 +88,7 @@
 import android.os.Looper;
 import android.os.Message;
 import android.os.ParcelableException;
+import android.os.PermissionEnforcer;
 import android.os.Process;
 import android.os.RemoteCallback;
 import android.os.RemoteCallbackList;
@@ -314,6 +316,8 @@
 
     public PackageInstallerService(Context context, PackageManagerService pm,
             Supplier<PackageParser2> apexParserSupplier) {
+        super(PermissionEnforcer.fromContext(context));
+
         mContext = context;
         mPm = pm;
 
@@ -1877,23 +1881,20 @@
     }
 
     @Override
+    @EnforcePermission(android.Manifest.permission.VERIFICATION_AGENT)
     public @PackageInstaller.VerificationPolicy int getVerificationPolicy() {
-        if (mContext.checkCallingOrSelfPermission(Manifest.permission.VERIFICATION_AGENT)
-                != PackageManager.PERMISSION_GRANTED) {
-            throw new SecurityException("You need the "
-                    + "com.android.permission.VERIFICATION_AGENT permission "
-                    + "to get the verification policy");
-        }
+        getVerificationPolicy_enforcePermission();
         return mVerificationPolicy.get();
     }
 
     @Override
+    @EnforcePermission(android.Manifest.permission.VERIFICATION_AGENT)
     public boolean setVerificationPolicy(@PackageInstaller.VerificationPolicy int policy) {
-        if (mContext.checkCallingOrSelfPermission(Manifest.permission.VERIFICATION_AGENT)
-                != PackageManager.PERMISSION_GRANTED) {
-            throw new SecurityException("You need the "
-                    + "com.android.permission.VERIFICATION_AGENT permission "
-                    + "to set the verification policy");
+        setVerificationPolicy_enforcePermission();
+        final int callingUid = getCallingUid();
+        // Only the verifier currently bound by the system can change the policy, except for Shell
+        if (!PackageManagerServiceUtils.isRootOrShell(callingUid)) {
+            mVerifierController.assertCallerIsCurrentVerifier(callingUid);
         }
         if (!isValidVerificationPolicy(policy)) {
             return false;
diff --git a/services/core/java/com/android/server/pm/PackageInstallerSession.java b/services/core/java/com/android/server/pm/PackageInstallerSession.java
index 9a9e434..6ea5369 100644
--- a/services/core/java/com/android/server/pm/PackageInstallerSession.java
+++ b/services/core/java/com/android/server/pm/PackageInstallerSession.java
@@ -2884,14 +2884,13 @@
             }
             // Send the request to the verifier and wait for its response before the rest of
             // the installation can proceed.
+            final VerifierCallback verifierCallback = new VerifierCallback();
             if (!mVerifierController.startVerificationSession(mPm::snapshotComputer, userId,
                     sessionId, getPackageName(), Uri.fromFile(stageDir), signingInfo,
                     declaredLibraries, mVerificationPolicy.get(), /* extensionParams= */ null,
-                    new VerifierCallback(), /* retry= */ false)) {
-                // A verifier is installed but cannot be connected. Installation disallowed.
-                onSessionVerificationFailure(INSTALL_FAILED_INTERNAL_ERROR,
-                        "A verifier agent is available on device but cannot be connected.",
-                        /* extras= */ null);
+                    verifierCallback, /* retry= */ false)) {
+                // A verifier is installed but cannot be connected.
+                verifierCallback.onConnectionFailed();
             }
         } else {
             // No need to check with verifier. Proceed with the rest of the verification.
@@ -2995,7 +2994,6 @@
                         onSessionVerificationFailure(INSTALL_FAILED_VERIFICATION_FAILURE,
                                 "A verifier agent is available on device but cannot be connected.",
                                 bundle);
-
                     });
         }
         /**
@@ -4447,9 +4445,7 @@
      * @return the uid of the owner this session
      */
     public int getInstallerUid() {
-        synchronized (mLock) {
-            return mInstallerUid;
-        }
+        return mInstallerUid;
     }
 
     /**
diff --git a/services/core/java/com/android/server/pm/PackageManagerShellCommand.java b/services/core/java/com/android/server/pm/PackageManagerShellCommand.java
index 4652c3a..f8e56e1 100644
--- a/services/core/java/com/android/server/pm/PackageManagerShellCommand.java
+++ b/services/core/java/com/android/server/pm/PackageManagerShellCommand.java
@@ -399,6 +399,10 @@
                     return runUnarchive();
                 case "get-domain-verification-agent":
                     return runGetDomainVerificationAgent();
+                case "get-verification-policy":
+                    return runGetVerificationPolicy();
+                case "set-verification-policy":
+                    return runSetVerificationPolicy();
                 default: {
                     if (ART_SERVICE_COMMANDS.contains(cmd)) {
                         return runArtServiceCommand();
@@ -4645,6 +4649,86 @@
         return 0;
     }
 
+    private int runGetVerificationPolicy() 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, "runGetVerificationPolicy");
+        try {
+            final IPackageInstaller installer = mInterface.getPackageInstaller();
+            // TODO(b/360129657): global verification policy should be per user
+            final int policy = installer.getVerificationPolicy();
+            pw.println(policy);
+        } catch (Exception e) {
+            pw.println("Failure [" + e.getMessage() + "]");
+            return 1;
+        }
+        return 0;
+    }
+
+    private int runSetVerificationPolicy() 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 String policyStr = getNextArg();
+        if (policyStr == null) {
+            pw.println("Error: policy not specified");
+            return 1;
+        }
+        final int translatedUserId =
+                translateUserId(userId, UserHandle.USER_SYSTEM, "runSetVerificationPolicy");
+        try {
+            final IPackageInstaller installer = mInterface.getPackageInstaller();
+            // TODO(b/360129657): global verification policy should be per user
+            final boolean success = installer.setVerificationPolicy(Integer.parseInt(policyStr));
+            if (!success) {
+                pw.println("Failure setting verification policy.");
+                return 1;
+            }
+        } catch (Exception e) {
+            pw.println("Failure [" + e.getMessage() + "]");
+            return 1;
+        }
+        return 0;
+    }
+
     @Override
     public void onHelp() {
         final PrintWriter pw = getOutPrintWriter();
@@ -5073,6 +5157,14 @@
         pw.println("      --user: return the agent of the given user (SYSTEM_USER if unspecified)");
         pw.println("  get-package-storage-stats [--user <USER_ID>] <PACKAGE>");
         pw.println("    Return the storage stats for the given app, if present");
+        pw.println("  get-verification-policy [--user USER_ID]");
+        pw.println("    Display current verification enforcement policy which will be applied to");
+        pw.println("    all the future installation sessions");
+        pw.println("      --user: show the policy of the given user (SYSTEM_USER if unspecified)");
+        pw.println("  set-verification-policy POLICY [--user USER_ID]");
+        pw.println("    Sets the verification policy of all the future installation sessions.");
+        pw.println("      --user: set the policy of the given user (SYSTEM_USER if unspecified)");
+        pw.println("");
         pw.println("");
         printArtServiceHelp();
         pw.println("");
diff --git a/services/core/java/com/android/server/pm/verify/pkg/VerifierController.java b/services/core/java/com/android/server/pm/verify/pkg/VerifierController.java
index a35618b..78849d2 100644
--- a/services/core/java/com/android/server/pm/verify/pkg/VerifierController.java
+++ b/services/core/java/com/android/server/pm/verify/pkg/VerifierController.java
@@ -18,13 +18,12 @@
 
 import static android.content.pm.PackageManager.MATCH_DIRECT_BOOT_AWARE;
 import static android.content.pm.PackageManager.MATCH_DIRECT_BOOT_UNAWARE;
+import static android.os.Process.INVALID_UID;
 import static android.os.Process.SYSTEM_UID;
 import static android.provider.DeviceConfig.NAMESPACE_PACKAGE_MANAGER_SERVICE;
 
-import android.Manifest;
 import android.annotation.NonNull;
 import android.annotation.Nullable;
-import android.annotation.RequiresPermission;
 import android.annotation.SuppressLint;
 import android.content.ComponentName;
 import android.content.Context;
@@ -114,10 +113,17 @@
 
     private final Context mContext;
     private final Handler mHandler;
+    // Guards the remote service object, as well as the verifier name and UID, which should all be
+    // changed at the same time.
+    private final Object mLock = new Object();
     @Nullable
+    @GuardedBy("mLock")
     private ServiceConnector<IVerifierService> mRemoteService;
     @Nullable
+    @GuardedBy("mLock")
     private ComponentName mRemoteServiceComponentName;
+    @GuardedBy("mLock")
+    private int mRemoteServiceUid = INVALID_UID;
     @NonNull
     private Injector mInjector;
 
@@ -143,9 +149,11 @@
      */
     @Nullable
     public String getVerifierPackageName(Supplier<Computer> snapshotSupplier, int userId) {
-        if (isVerifierConnected()) {
-            // Verifier is connected or is being connected, so it must be installed.
-            return mRemoteServiceComponentName.getPackageName();
+        synchronized (mLock) {
+            if (isVerifierConnectedLocked()) {
+                // Verifier is connected or is being connected, so it must be installed.
+                return mRemoteServiceComponentName.getPackageName();
+            }
         }
         // Verifier has been disconnected, or it hasn't been connected. Check if it's installed.
         return mInjector.getVerifierPackageName(snapshotSupplier.get(), userId);
@@ -178,16 +186,29 @@
             }
             return true;
         }
+        Computer snapshot = snapshotSupplier.get();
         Pair<ServiceConnector<IVerifierService>, ComponentName> result =
-                mInjector.getRemoteService(snapshotSupplier.get(), mContext, userId, mHandler);
+                mInjector.getRemoteService(snapshot, mContext, userId, mHandler);
         if (result == null || result.first == null) {
             if (DEBUG) {
                 Slog.i(TAG, "Unable to find a qualified verifier.");
             }
             return false;
         }
-        mRemoteService = result.first;
-        mRemoteServiceComponentName = result.second;
+        final int verifierUid = snapshot.getPackageUidInternal(
+                result.second.getPackageName(), 0, userId, /* callingUid= */ SYSTEM_UID);
+        if (verifierUid == INVALID_UID) {
+            if (DEBUG) {
+                Slog.i(TAG, "Unable to find the UID of the qualified verifier.");
+            }
+            return false;
+        }
+        synchronized (mLock) {
+            mRemoteService = result.first;
+            mRemoteServiceComponentName = result.second;
+            mRemoteServiceUid = verifierUid;
+        }
+
         if (DEBUG) {
             Slog.i(TAG, "Connecting to a qualified verifier: " + mRemoteServiceComponentName);
         }
@@ -212,10 +233,13 @@
                     }
 
                     private void destroy() {
-                        if (isVerifierConnected()) {
-                            mRemoteService.unbind();
-                            mRemoteService = null;
-                            mRemoteServiceComponentName = null;
+                        synchronized (mLock) {
+                            if (isVerifierConnectedLocked()) {
+                                mRemoteService.unbind();
+                                mRemoteService = null;
+                                mRemoteServiceComponentName = null;
+                                mRemoteServiceUid = INVALID_UID;
+                            }
                         }
                     }
                 });
@@ -223,7 +247,8 @@
         return true;
     }
 
-    private boolean isVerifierConnected() {
+    @GuardedBy("mLock")
+    private boolean isVerifierConnectedLocked() {
         return mRemoteService != null && mRemoteServiceComponentName != null;
     }
 
@@ -232,19 +257,21 @@
      * requested for verification.
      */
     public void notifyPackageNameAvailable(@NonNull String packageName) {
-        if (!isVerifierConnected()) {
-            if (DEBUG) {
-                Slog.i(TAG, "Verifier is not connected. Not notifying package name available");
+        synchronized (mLock) {
+            if (!isVerifierConnectedLocked()) {
+                if (DEBUG) {
+                    Slog.i(TAG, "Verifier is not connected. Not notifying package name available");
+                }
+                return;
             }
-            return;
+            // Best effort. We don't check for the result.
+            mRemoteService.run(service -> {
+                if (DEBUG) {
+                    Slog.i(TAG, "Notifying package name available for " + packageName);
+                }
+                service.onPackageNameAvailable(packageName);
+            });
         }
-        // Best effort. We don't check for the result.
-        mRemoteService.run(service -> {
-            if (DEBUG) {
-                Slog.i(TAG, "Notifying package name available for " + packageName);
-            }
-            service.onPackageNameAvailable(packageName);
-        });
     }
 
     /**
@@ -253,27 +280,29 @@
      * will no longer be requested for verification, possibly because the installation is canceled.
      */
     public void notifyVerificationCancelled(@NonNull String packageName) {
-        if (!isVerifierConnected()) {
-            if (DEBUG) {
-                Slog.i(TAG, "Verifier is not connected. Not notifying verification cancelled");
+        synchronized (mLock) {
+            if (!isVerifierConnectedLocked()) {
+                if (DEBUG) {
+                    Slog.i(TAG, "Verifier is not connected. Not notifying verification cancelled");
+                }
+                return;
             }
-            return;
+            // Best effort. We don't check for the result.
+            mRemoteService.run(service -> {
+                if (DEBUG) {
+                    Slog.i(TAG, "Notifying verification cancelled for " + packageName);
+                }
+                service.onVerificationCancelled(packageName);
+            });
         }
-        // Best effort. We don't check for the result.
-        mRemoteService.run(service -> {
-            if (DEBUG) {
-                Slog.i(TAG, "Notifying verification cancelled for " + packageName);
-            }
-            service.onVerificationCancelled(packageName);
-        });
     }
 
     /**
      * Called to notify the bound verifier agent that a package that's pending installation needs
      * to be verified right now.
      * <p>The verification request must be sent to the verifier as soon as the verifier is
-     * connected. If the connection cannot be made within {@link #CONNECTION_TIMEOUT_SECONDS}</p>
-     * of when the request is sent out, we consider the verification to be failed and notify the
+     * connected. If the connection cannot be made within the specified time limit from
+     * when the request is sent out, we consider the verification to be failed and notify the
      * installation session.</p>
      * <p>If a response is not returned from the verifier agent within a timeout duration from the
      * time the request is sent to the verifier, the verification will be considered a failure.</p>
@@ -291,43 +320,48 @@
         if (!bindToVerifierServiceIfNeeded(snapshotSupplier, userId)) {
             return false;
         }
-        if (!isVerifierConnected()) {
-            if (DEBUG) {
-                Slog.i(TAG, "Verifier is not connected. Not notifying verification required");
-            }
-            // Normally this should not happen because we just tried to bind. But if the verifier
-            // just crashed or just became unavailable, we should notify the installation session so
-            // it can finish with a verification failure.
-            return false;
-        }
         // For now, the verification id is the same as the installation session id.
         final int verificationId = installationSessionId;
-        final VerificationSession session = new VerificationSession(
-                /* id= */ verificationId,
-                /* installSessionId= */ installationSessionId,
-                packageName, stagedPackageUri, signingInfo, declaredLibraries, extensionParams,
-                verificationPolicy, new VerificationSessionInterface(callback));
-        AndroidFuture<Void> unusedFuture = mRemoteService.post(service -> {
-            if (!retry) {
+        synchronized (mLock) {
+            if (!isVerifierConnectedLocked()) {
                 if (DEBUG) {
-                    Slog.i(TAG, "Notifying verification required for session " + verificationId);
+                    Slog.i(TAG, "Verifier is not connected. Not notifying verification required");
                 }
-                service.onVerificationRequired(session);
-            } else {
-                if (DEBUG) {
-                    Slog.i(TAG, "Notifying verification retry for session " + verificationId);
+                // Normally this should not happen because we just tried to bind. But if the
+                // verifier just crashed or just became unavailable, we should notify the
+                // installation session so it can finish with a verification failure.
+                return false;
+            }
+            final VerificationSession session = new VerificationSession(
+                    /* id= */ verificationId,
+                    /* installSessionId= */ installationSessionId,
+                    packageName, stagedPackageUri, signingInfo, declaredLibraries, extensionParams,
+                    verificationPolicy, new VerificationSessionInterface(callback));
+            AndroidFuture<Void> unusedFuture = mRemoteService.post(service -> {
+                if (!retry) {
+                    if (DEBUG) {
+                        Slog.i(TAG, "Notifying verification required for session "
+                                + verificationId);
+                    }
+                    service.onVerificationRequired(session);
+                } else {
+                    if (DEBUG) {
+                        Slog.i(TAG, "Notifying verification retry for session "
+                                + verificationId);
+                    }
+                    service.onVerificationRetry(session);
                 }
-                service.onVerificationRetry(session);
-            }
-        }).orTimeout(mInjector.getVerifierConnectionTimeoutMillis(), TimeUnit.MILLISECONDS)
-                .whenComplete((res, err) -> {
-            if (err != null) {
-                Slog.e(TAG, "Error notifying verification request for session " + verificationId,
-                        err);
-                // Notify the installation session so it can finish with verification failure.
-                callback.onConnectionFailed();
-            }
-        });
+            }).orTimeout(mInjector.getVerifierConnectionTimeoutMillis(), TimeUnit.MILLISECONDS)
+                    .whenComplete((res, err) -> {
+                        if (err != null) {
+                            Slog.e(TAG, "Error notifying verification request for session "
+                                    + verificationId, err);
+                            // Notify the installation session so it can finish with verification
+                            // failure.
+                            callback.onConnectionFailed();
+                        }
+                    });
+        }
         // Keep track of the session status with the ID. Start counting down the session timeout.
         final long defaultTimeoutMillis = mInjector.getVerificationRequestTimeoutMillis();
         final long maxExtendedTimeoutMillis = mInjector.getMaxVerificationExtendedTimeoutMillis();
@@ -369,24 +403,27 @@
      * Called to notify the bound verifier agent that a verification request has timed out.
      */
     public void notifyVerificationTimeout(int verificationId) {
-        if (!isVerifierConnected()) {
-            if (DEBUG) {
-                Slog.i(TAG,
-                        "Verifier is not connected. Not notifying timeout for " + verificationId);
+        synchronized (mLock) {
+            if (!isVerifierConnectedLocked()) {
+                if (DEBUG) {
+                    Slog.i(TAG,
+                            "Verifier is not connected. Not notifying timeout for "
+                                    + verificationId);
+                }
+                return;
             }
-            return;
+            AndroidFuture<Void> unusedFuture = mRemoteService.post(service -> {
+                if (DEBUG) {
+                    Slog.i(TAG, "Notifying timeout for " + verificationId);
+                }
+                service.onVerificationTimeout(verificationId);
+            }).whenComplete((res, err) -> {
+                if (err != null) {
+                    Slog.e(TAG, "Error notifying VerificationTimeout for session "
+                            + verificationId, err);
+                }
+            });
         }
-        AndroidFuture<Void> unusedFuture = mRemoteService.post(service -> {
-            if (DEBUG) {
-                Slog.i(TAG, "Notifying timeout for " + verificationId);
-            }
-            service.onVerificationTimeout(verificationId);
-        }).whenComplete((res, err) -> {
-            if (err != null) {
-                Slog.e(TAG, "Error notifying VerificationTimeout for session "
-                        + verificationId, (Throwable) err);
-            }
-        });
     }
 
     /**
@@ -405,17 +442,19 @@
         }
     }
 
-    @RequiresPermission(Manifest.permission.VERIFICATION_AGENT)
-    private void checkCallerPermission() {
-        // TODO: think of a better way to test it on non-eng builds
-        if (Build.IS_ENG) {
-            return;
-        }
-        if (mContext.checkCallingOrSelfPermission(Manifest.permission.VERIFICATION_AGENT)
-                != PackageManager.PERMISSION_GRANTED) {
-            throw new SecurityException("You need the"
-                    + " com.android.permission.VERIFICATION_AGENT permission"
-                    + " to use VerificationSession APIs.");
+    /**
+     * Assert that the calling UID is the same as the UID of the currently connected verifier.
+     */
+    public void assertCallerIsCurrentVerifier(int callingUid) {
+        synchronized (mLock) {
+            if (!isVerifierConnectedLocked()) {
+                throw new IllegalStateException(
+                        "Unable to proceed because the verifier has been disconnected.");
+            }
+            if (callingUid != mRemoteServiceUid) {
+                throw new IllegalStateException(
+                        "Calling uid " + callingUid + " is not the current verifier.");
+            }
         }
     }
 
@@ -429,7 +468,7 @@
 
         @Override
         public long getTimeoutTime(int verificationId) {
-            checkCallerPermission();
+            assertCallerIsCurrentVerifier(getCallingUid());
             synchronized (mVerificationStatus) {
                 final VerificationStatusTracker tracker = mVerificationStatus.get(verificationId);
                 if (tracker == null) {
@@ -442,7 +481,7 @@
 
         @Override
         public long extendTimeRemaining(int verificationId, long additionalMs) {
-            checkCallerPermission();
+            assertCallerIsCurrentVerifier(getCallingUid());
             synchronized (mVerificationStatus) {
                 final VerificationStatusTracker tracker = mVerificationStatus.get(verificationId);
                 if (tracker == null) {
@@ -456,7 +495,7 @@
         @Override
         public boolean setVerificationPolicy(int verificationId,
                 @PackageInstaller.VerificationPolicy int policy) {
-            checkCallerPermission();
+            assertCallerIsCurrentVerifier(getCallingUid());
             synchronized (mVerificationStatus) {
                 final VerificationStatusTracker tracker = mVerificationStatus.get(verificationId);
                 if (tracker == null) {
@@ -469,7 +508,7 @@
 
         @Override
         public void reportVerificationIncomplete(int id, int reason) {
-            checkCallerPermission();
+            assertCallerIsCurrentVerifier(getCallingUid());
             final VerificationStatusTracker tracker;
             synchronized (mVerificationStatus) {
                 tracker = mVerificationStatus.get(id);
@@ -484,15 +523,9 @@
         }
 
         @Override
-        public void reportVerificationComplete(int id, VerificationStatus verificationStatus) {
-            reportVerificationCompleteWithExtensionResponse(id, verificationStatus,
-                    /* extensionResponse= */ null);
-        }
-
-        @Override
-        public void reportVerificationCompleteWithExtensionResponse(int id,
-                VerificationStatus verificationStatus, PersistableBundle extensionResponse) {
-            checkCallerPermission();
+        public void reportVerificationComplete(int id, VerificationStatus verificationStatus,
+                @Nullable PersistableBundle extensionResponse) {
+            assertCallerIsCurrentVerifier(getCallingUid());
             final VerificationStatusTracker tracker;
             synchronized (mVerificationStatus) {
                 tracker = mVerificationStatus.get(id);
diff --git a/services/core/java/com/android/server/policy/ModifierShortcutManager.java b/services/core/java/com/android/server/policy/ModifierShortcutManager.java
index 66ec53e..a1236e5 100644
--- a/services/core/java/com/android/server/policy/ModifierShortcutManager.java
+++ b/services/core/java/com/android/server/policy/ModifierShortcutManager.java
@@ -31,6 +31,7 @@
 import android.content.pm.PackageManager;
 import android.content.res.XmlResourceParser;
 import android.graphics.drawable.Icon;
+import android.hardware.input.AppLaunchData;
 import android.hardware.input.KeyGestureEvent;
 import android.os.Handler;
 import android.os.RemoteException;
@@ -48,6 +49,7 @@
 import android.view.KeyboardShortcutInfo;
 
 import com.android.internal.R;
+import com.android.internal.annotations.GuardedBy;
 import com.android.internal.annotations.VisibleForTesting;
 import com.android.internal.policy.IShortcutService;
 import com.android.internal.util.XmlUtils;
@@ -63,6 +65,7 @@
 import java.util.HashMap;
 import java.util.List;
 import java.util.Map;
+import java.util.Objects;
 import java.util.Set;
 
 /**
@@ -131,7 +134,10 @@
     private boolean mConsumeSearchKeyUp = true;
     private UserHandle mCurrentUser;
     private final Map<Pair<Character, Boolean>, Bookmark> mBookmarks = new HashMap<>();
+    @GuardedBy("mAppIntentCache")
+    private final Map<AppLaunchData, Intent> mAppIntentCache = new HashMap<>();
 
+    @SuppressLint("MissingPermission")
     ModifierShortcutManager(Context context, Handler handler, UserHandle currentUser) {
         mContext = context;
         mHandler = handler;
@@ -146,6 +152,17 @@
                     } else {
                         mRoleIntents.remove(roleName);
                     }
+                    synchronized (mAppIntentCache) {
+                        mAppIntentCache.entrySet().removeIf(
+                                entry -> {
+                                    if (entry.getKey() instanceof AppLaunchData.RoleData) {
+                                        return Objects.equals(
+                                                ((AppLaunchData.RoleData) entry.getKey()).getRole(),
+                                                roleName);
+                                    }
+                                    return false;
+                                });
+                    }
                 }, UserHandle.ALL);
         mCurrentUser = currentUser;
         mInputManagerInternal = LocalServices.getService(InputManagerInternal.class);
@@ -159,6 +176,10 @@
         // so clear the cache.
         clearRoleIntents();
         clearComponentIntents();
+
+        synchronized (mAppIntentCache) {
+            mAppIntentCache.clear();
+        }
     }
 
     void clearRoleIntents() {
@@ -748,6 +769,46 @@
                 shortcuts);
     }
 
+    private Intent getIntentFromAppLaunchData(@NonNull AppLaunchData data) {
+        Context context = mContext.createContextAsUser(mCurrentUser, 0);
+        synchronized (mAppIntentCache) {
+            Intent intent = mAppIntentCache.get(data);
+            if (intent != null) {
+                return intent;
+            }
+            if (data instanceof AppLaunchData.CategoryData) {
+                intent = Intent.makeMainSelectorActivity(Intent.ACTION_MAIN,
+                        ((AppLaunchData.CategoryData) data).getCategory());
+            } else if (data instanceof AppLaunchData.RoleData) {
+                intent = getRoleLaunchIntent(context, ((AppLaunchData.RoleData) data).getRole());
+            } else if (data instanceof AppLaunchData.ComponentData) {
+                AppLaunchData.ComponentData componentData = (AppLaunchData.ComponentData) data;
+                intent = resolveComponentNameIntent(context, componentData.getPackageName(),
+                        componentData.getClassName());
+            }
+            if (intent != null) {
+                mAppIntentCache.put(data, intent);
+            }
+            return intent;
+        }
+    }
+
+    boolean launchApplication(@NonNull AppLaunchData data) {
+        Intent intent  = getIntentFromAppLaunchData(data);
+        if (intent == null) {
+            return false;
+        }
+        intent.addFlags(Intent.FLAG_ACTIVITY_NEW_TASK);
+        try {
+            mContext.startActivityAsUser(intent, mCurrentUser);
+            return true;
+        } catch (ActivityNotFoundException ex) {
+            Slog.w(TAG, "Not launching app because "
+                    + "the activity to which it refers to was not found: " + data);
+        }
+        return false;
+    }
+
     /**
      * Given an intent to launch an application and the character and shift state that should
      * trigger it, return a suitable {@link KeyboardShortcutInfo} that contains the label and
@@ -869,7 +930,7 @@
 
         // TODO(b/280423320): Add new field package name associated in the
         //  KeyboardShortcutEvent atom and log it accordingly.
-        return KeyGestureEvent.KEY_GESTURE_TYPE_LAUNCH_APPLICATION_BY_PACKAGE_NAME;
+        return KeyGestureEvent.KEY_GESTURE_TYPE_LAUNCH_APPLICATION;
     }
 
     @KeyGestureEvent.KeyGestureType
diff --git a/services/core/java/com/android/server/policy/PhoneWindowManager.java b/services/core/java/com/android/server/policy/PhoneWindowManager.java
index 6a7f22e..3ab1009 100644
--- a/services/core/java/com/android/server/policy/PhoneWindowManager.java
+++ b/services/core/java/com/android/server/policy/PhoneWindowManager.java
@@ -149,6 +149,7 @@
 import android.hardware.hdmi.HdmiControlManager;
 import android.hardware.hdmi.HdmiPlaybackClient;
 import android.hardware.hdmi.HdmiPlaybackClient.OneTouchPlayCallback;
+import android.hardware.input.AppLaunchData;
 import android.hardware.input.InputManager;
 import android.hardware.input.KeyGestureEvent;
 import android.media.AudioManager;
@@ -4037,6 +4038,7 @@
                     case KeyGestureEvent.KEY_GESTURE_TYPE_LANGUAGE_SWITCH:
                     case KeyGestureEvent.KEY_GESTURE_TYPE_ACCESSIBILITY_SHORTCUT:
                     case KeyGestureEvent.KEY_GESTURE_TYPE_CLOSE_ALL_DIALOGS:
+                    case KeyGestureEvent.KEY_GESTURE_TYPE_LAUNCH_APPLICATION:
                         return true;
                     case KeyGestureEvent.KEY_GESTURE_TYPE_SCREENSHOT_CHORD:
                     case KeyGestureEvent.KEY_GESTURE_TYPE_RINGER_TOGGLE_CHORD:
@@ -4279,6 +4281,14 @@
                     return true;
                 }
                 break;
+            case KeyGestureEvent.KEY_GESTURE_TYPE_LAUNCH_APPLICATION:
+                AppLaunchData data = event.getAppLaunchData();
+                if (complete && isUserSetupComplete() && !keyguardOn
+                        && data != null && mModifierShortcutManager.launchApplication(data)) {
+                    dismissKeyboardShortcutsMenu();
+                    return true;
+                }
+                break;
         }
         return false;
     }
@@ -6948,6 +6958,7 @@
         if (modifierShortcutManagerMultiuser()) {
             mModifierShortcutManager.setCurrentUser(UserHandle.of(newUserId));
         }
+        mInputManagerInternal.setCurrentUser(newUserId);
     }
 
     @Override
diff --git a/services/core/java/com/android/server/security/forensic/DataAggregator.java b/services/core/java/com/android/server/security/forensic/DataAggregator.java
new file mode 100644
index 0000000..0079818
--- /dev/null
+++ b/services/core/java/com/android/server/security/forensic/DataAggregator.java
@@ -0,0 +1,142 @@
+/*
+ * 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.security.forensic;
+
+import android.os.Handler;
+import android.os.Looper;
+import android.os.Message;
+import android.security.forensic.ForensicEvent;
+import android.util.Slog;
+
+import com.android.internal.annotations.VisibleForTesting;
+import com.android.server.ServiceThread;
+
+import java.util.ArrayList;
+import java.util.List;
+
+public class DataAggregator {
+    private static final String TAG = "Forensic DataAggregator";
+    private static final int MSG_SINGLE_DATA = 0;
+    private static final int MSG_BATCH_DATA = 1;
+    private static final int MSG_DISABLE = 2;
+
+    private static final int STORED_EVENTS_SIZE_LIMIT = 1024;
+    private final ForensicService mForensicService;
+    private final ArrayList<DataSource> mDataSources;
+
+    private List<ForensicEvent> mStoredEvents = new ArrayList<>();
+    private ServiceThread mHandlerThread;
+    private Handler mHandler;
+    public DataAggregator(ForensicService forensicService) {
+        mForensicService = forensicService;
+        mDataSources = new ArrayList<DataSource>();
+    }
+
+    @VisibleForTesting
+    void setHandler(Looper looper, ServiceThread serviceThread) {
+        mHandlerThread = serviceThread;
+        mHandler = new EventHandler(looper, this);
+    }
+
+    /**
+     * Initialize DataSources
+     * @return Whether the initialization succeeds.
+     */
+    // TODO: Add the corresponding data sources
+    public boolean initialize() {
+        return true;
+    }
+
+    /**
+     * Enable the data collection of all DataSources.
+     */
+    public void enable() {
+        mHandlerThread = new ServiceThread(TAG, android.os.Process.THREAD_PRIORITY_BACKGROUND,
+                /* allowIo */ false);
+        mHandlerThread.start();
+        mHandler = new EventHandler(mHandlerThread.getLooper(), this);
+        for (DataSource ds : mDataSources) {
+            ds.enable();
+        }
+    }
+
+    /**
+     * DataSource calls it to transmit a single event.
+     */
+    public void addSingleData(ForensicEvent event) {
+        mHandler.obtainMessage(MSG_SINGLE_DATA, event).sendToTarget();
+    }
+
+    /**
+     * DataSource calls it to transmit list of events.
+     */
+    public void addBatchData(List<ForensicEvent> events) {
+        mHandler.obtainMessage(MSG_BATCH_DATA, events).sendToTarget();
+    }
+
+    /**
+     * Disable the data collection of all DataSources.
+     */
+    public void disable() {
+        mHandler.obtainMessage(MSG_DISABLE).sendToTarget();
+    }
+
+    private void onNewSingleData(ForensicEvent event) {
+        if (mStoredEvents.size() < STORED_EVENTS_SIZE_LIMIT) {
+            mStoredEvents.add(event);
+        } else {
+            mForensicService.addNewData(mStoredEvents);
+            mStoredEvents = new ArrayList<>();
+        }
+    }
+
+    private void onNewBatchData(List<ForensicEvent> events) {
+        mForensicService.addNewData(events);
+    }
+
+    private void onDisable() {
+        for (DataSource ds : mDataSources) {
+            ds.disable();
+        }
+        mHandlerThread.quitSafely();
+        mHandlerThread = null;
+    }
+
+    private static class EventHandler extends Handler {
+        private final DataAggregator mDataAggregator;
+        EventHandler(Looper looper, DataAggregator dataAggregator) {
+            super(looper);
+            mDataAggregator = dataAggregator;
+        }
+        @Override
+        public void handleMessage(Message msg) {
+            switch (msg.what) {
+                case MSG_SINGLE_DATA:
+                    mDataAggregator.onNewSingleData((ForensicEvent) msg.obj);
+                    break;
+                case MSG_BATCH_DATA:
+                    mDataAggregator.onNewBatchData((List<ForensicEvent>) msg.obj);
+                    break;
+                case MSG_DISABLE:
+                    mDataAggregator.onDisable();
+                    break;
+                default:
+                    Slog.w(TAG, "Unknown message: " + msg.what);
+            }
+        }
+    }
+}
diff --git a/packages/SystemUI/tests/utils/src/com/android/systemui/statusbar/chips/notification/ui/viewmodel/NotifChipsViewModelKosmos.kt b/services/core/java/com/android/server/security/forensic/DataSource.java
similarity index 63%
copy from packages/SystemUI/tests/utils/src/com/android/systemui/statusbar/chips/notification/ui/viewmodel/NotifChipsViewModelKosmos.kt
copy to services/core/java/com/android/server/security/forensic/DataSource.java
index af24c37..da7ee21 100644
--- a/packages/SystemUI/tests/utils/src/com/android/systemui/statusbar/chips/notification/ui/viewmodel/NotifChipsViewModelKosmos.kt
+++ b/services/core/java/com/android/server/security/forensic/DataSource.java
@@ -14,10 +14,16 @@
  * limitations under the License.
  */
 
-package com.android.systemui.statusbar.chips.notification.ui.viewmodel
+package com.android.server.security.forensic;
 
-import com.android.systemui.kosmos.Kosmos
-import com.android.systemui.statusbar.notification.domain.interactor.activeNotificationsInteractor
+public interface DataSource {
+    /**
+     * Enable the data collection.
+     */
+    void enable();
 
-val Kosmos.notifChipsViewModel: NotifChipsViewModel by
-    Kosmos.Fixture { NotifChipsViewModel(activeNotificationsInteractor) }
+    /**
+     * Disable the data collection.
+     */
+    void disable();
+}
diff --git a/services/core/java/com/android/server/security/forensic/ForensicService.java b/services/core/java/com/android/server/security/forensic/ForensicService.java
index 20c648e..53b07c0 100644
--- a/services/core/java/com/android/server/security/forensic/ForensicService.java
+++ b/services/core/java/com/android/server/security/forensic/ForensicService.java
@@ -22,6 +22,7 @@
 import android.os.Looper;
 import android.os.Message;
 import android.os.RemoteException;
+import android.security.forensic.ForensicEvent;
 import android.security.forensic.IForensicService;
 import android.security.forensic.IForensicServiceCommandCallback;
 import android.security.forensic.IForensicServiceStateCallback;
@@ -32,6 +33,7 @@
 import com.android.server.SystemService;
 
 import java.util.ArrayList;
+import java.util.List;
 
 /**
  * @hide
@@ -64,6 +66,7 @@
     private final Context mContext;
     private final Handler mHandler;
     private final BackupTransportConnection mBackupTransportConnection;
+    private final DataAggregator mDataAggregator;
     private final BinderService mBinderService;
 
     private final ArrayList<IForensicServiceStateCallback> mStateMonitors = new ArrayList<>();
@@ -79,6 +82,7 @@
         mContext = injector.getContext();
         mHandler = new EventHandler(injector.getLooper(), this);
         mBackupTransportConnection = injector.getBackupTransportConnection();
+        mDataAggregator = injector.getDataAggregator(this);
         mBinderService = new BinderService(this);
     }
 
@@ -167,6 +171,9 @@
                         Slog.e(TAG, "RemoteException", e);
                     }
                     break;
+                case MSG_BACKUP:
+                    mService.backup((List<ForensicEvent>) msg.obj);
+                    break;
                 default:
                     Slog.w(TAG, "Unknown message: " + msg.what);
             }
@@ -192,6 +199,10 @@
     private void makeVisible(IForensicServiceCommandCallback callback) throws RemoteException {
         switch (mState) {
             case STATE_INVISIBLE:
+                if (!mDataAggregator.initialize()) {
+                    callback.onFailure(ERROR_DATA_SOURCE_UNAVAILABLE);
+                    break;
+                }
                 mState = STATE_VISIBLE;
                 notifyStateMonitors();
                 callback.onSuccess();
@@ -227,6 +238,7 @@
                     callback.onFailure(ERROR_BACKUP_TRANSPORT_UNAVAILABLE);
                     break;
                 }
+                mDataAggregator.enable();
                 mState = STATE_ENABLED;
                 notifyStateMonitors();
                 callback.onSuccess();
@@ -243,6 +255,7 @@
         switch (mState) {
             case STATE_ENABLED:
                 mBackupTransportConnection.release();
+                mDataAggregator.disable();
                 mState = STATE_VISIBLE;
                 notifyStateMonitors();
                 callback.onSuccess();
@@ -255,6 +268,17 @@
         }
     }
 
+    /**
+     * Add a list of ForensicEvent.
+     */
+    public void addNewData(List<ForensicEvent> events) {
+        mHandler.obtainMessage(MSG_BACKUP, events).sendToTarget();
+    }
+
+    private void backup(List<ForensicEvent> events) {
+        mBackupTransportConnection.addData(events);
+    }
+
     @Override
     public void onStart() {
         try {
@@ -275,6 +299,8 @@
         Looper getLooper();
 
         BackupTransportConnection getBackupTransportConnection();
+
+        DataAggregator getDataAggregator(ForensicService forensicService);
     }
 
     private static final class InjectorImpl implements Injector {
@@ -303,6 +329,11 @@
         public BackupTransportConnection getBackupTransportConnection() {
             return new BackupTransportConnection(mContext);
         }
+
+        @Override
+        public DataAggregator getDataAggregator(ForensicService forensicService) {
+            return new DataAggregator(forensicService);
+        }
     }
 }
 
diff --git a/services/core/java/com/android/server/wm/ActivityStarter.java b/services/core/java/com/android/server/wm/ActivityStarter.java
index 8dab717..2070c91 100644
--- a/services/core/java/com/android/server/wm/ActivityStarter.java
+++ b/services/core/java/com/android/server/wm/ActivityStarter.java
@@ -1275,8 +1275,8 @@
                         "Creator PermissionPolicyService.checkStartActivity Caused abortion.",
                         intent, intentCreatorUid, intentCreatorPackage, callingUid, callingPackage);
             }
-            intent.removeCreatorTokenInfo();
         }
+        intent.removeCreatorToken();
 
         // Merge the two options bundles, while realCallerOptions takes precedence.
         ActivityOptions checkedOptions = options != null
diff --git a/services/core/java/com/android/server/wm/ActivityTaskManagerService.java b/services/core/java/com/android/server/wm/ActivityTaskManagerService.java
index 111e74e..25a1ea9 100644
--- a/services/core/java/com/android/server/wm/ActivityTaskManagerService.java
+++ b/services/core/java/com/android/server/wm/ActivityTaskManagerService.java
@@ -1228,7 +1228,6 @@
             String callingFeatureId, Intent intent, String resolvedType, IBinder resultTo,
             String resultWho, int requestCode, int startFlags, ProfilerInfo profilerInfo,
             Bundle bOptions) {
-        mAmInternal.addCreatorToken(intent, callingPackage);
         return startActivityAsUser(caller, callingPackage, callingFeatureId, intent, resolvedType,
                 resultTo, resultWho, requestCode, startFlags, profilerInfo, bOptions,
                 UserHandle.getCallingUserId());
diff --git a/services/core/java/com/android/server/wm/ActivityTaskSupervisor.java b/services/core/java/com/android/server/wm/ActivityTaskSupervisor.java
index 6a1f28d..fa678e2 100644
--- a/services/core/java/com/android/server/wm/ActivityTaskSupervisor.java
+++ b/services/core/java/com/android/server/wm/ActivityTaskSupervisor.java
@@ -242,6 +242,8 @@
     static {
         ACTION_TO_RUNTIME_PERMISSION.put(MediaStore.ACTION_IMAGE_CAPTURE,
                 Manifest.permission.CAMERA);
+        ACTION_TO_RUNTIME_PERMISSION.put(MediaStore.ACTION_MOTION_PHOTO_CAPTURE,
+                Manifest.permission.CAMERA);
         ACTION_TO_RUNTIME_PERMISSION.put(MediaStore.ACTION_VIDEO_CAPTURE,
                 Manifest.permission.CAMERA);
         ACTION_TO_RUNTIME_PERMISSION.put(Intent.ACTION_CALL,
diff --git a/services/core/java/com/android/server/wm/DragDropController.java b/services/core/java/com/android/server/wm/DragDropController.java
index c849a37..258a87e 100644
--- a/services/core/java/com/android/server/wm/DragDropController.java
+++ b/services/core/java/com/android/server/wm/DragDropController.java
@@ -156,7 +156,7 @@
             SurfaceControl surface, int touchSource, int touchDeviceId, int touchPointerId,
             float touchX, float touchY, float thumbCenterX, float thumbCenterY, ClipData data) {
         if (DEBUG_DRAG) {
-            Slog.d(TAG_WM, "perform drag: win=" + window + " surface=" + surface + " flags=" +
+            Slog.d(TAG_WM, "perform drag: win=" + window + " surface=" + surface + " flags=0x" +
                     Integer.toHexString(flags) + " data=" + data + " touch(" + touchX + ","
                     + touchY + ") thumb center(" + thumbCenterX + "," + thumbCenterY + ")");
         }
diff --git a/services/core/lint-baseline.xml b/services/core/lint-baseline.xml
index 3b81f0a..4c1ac39 100644
--- a/services/core/lint-baseline.xml
+++ b/services/core/lint-baseline.xml
@@ -178,4 +178,675 @@
             column="51"/>
     </issue>
 
-</issues>
\ No newline at end of file
+    <issue
+        id="MissingPermissionAnnotation"
+        message="onShellCommand should be annotated with either @EnforcePermission, @RequiresNoPermission or @PermissionManuallyEnforced."
+        errorLine1="    @Override"
+        errorLine2="    ^">
+        <location
+            file="frameworks/base/services/core/java/com/android/server/security/advancedprotection/AdvancedProtectionService.java"
+            line="128"
+            column="5"/>
+    </issue>
+
+    <issue
+        id="MissingPermissionAnnotation"
+        message="monitorState should be annotated with either @EnforcePermission, @RequiresNoPermission or @PermissionManuallyEnforced."
+        errorLine1="        @Override"
+        errorLine2="        ^">
+        <location
+            file="frameworks/base/services/core/java/com/android/server/security/forensic/ForensicService.java"
+            line="95"
+            column="9"/>
+    </issue>
+
+    <issue
+        id="MissingPermissionAnnotation"
+        message="makeVisible should be annotated with either @EnforcePermission, @RequiresNoPermission or @PermissionManuallyEnforced."
+        errorLine1="        @Override"
+        errorLine2="        ^">
+        <location
+            file="frameworks/base/services/core/java/com/android/server/security/forensic/ForensicService.java"
+            line="100"
+            column="9"/>
+    </issue>
+
+    <issue
+        id="MissingPermissionAnnotation"
+        message="makeInvisible should be annotated with either @EnforcePermission, @RequiresNoPermission or @PermissionManuallyEnforced."
+        errorLine1="        @Override"
+        errorLine2="        ^">
+        <location
+            file="frameworks/base/services/core/java/com/android/server/security/forensic/ForensicService.java"
+            line="105"
+            column="9"/>
+    </issue>
+
+    <issue
+        id="MissingPermissionAnnotation"
+        message="enable should be annotated with either @EnforcePermission, @RequiresNoPermission or @PermissionManuallyEnforced."
+        errorLine1="        @Override"
+        errorLine2="        ^">
+        <location
+            file="frameworks/base/services/core/java/com/android/server/security/forensic/ForensicService.java"
+            line="110"
+            column="9"/>
+    </issue>
+
+    <issue
+        id="MissingPermissionAnnotation"
+        message="disable should be annotated with either @EnforcePermission, @RequiresNoPermission or @PermissionManuallyEnforced."
+        errorLine1="        @Override"
+        errorLine2="        ^">
+        <location
+            file="frameworks/base/services/core/java/com/android/server/security/forensic/ForensicService.java"
+            line="115"
+            column="9"/>
+    </issue>
+
+    <issue
+        id="MissingPermissionAnnotation"
+        message="getTimeoutTime should be annotated with either @EnforcePermission, @RequiresNoPermission or @PermissionManuallyEnforced."
+        errorLine1="        @Override"
+        errorLine2="        ^">
+        <location
+            file="frameworks/base/services/core/java/com/android/server/pm/verify/pkg/VerifierController.java"
+            line="430"
+            column="9"/>
+    </issue>
+
+    <issue
+        id="MissingPermissionAnnotation"
+        message="extendTimeRemaining should be annotated with either @EnforcePermission, @RequiresNoPermission or @PermissionManuallyEnforced."
+        errorLine1="        @Override"
+        errorLine2="        ^">
+        <location
+            file="frameworks/base/services/core/java/com/android/server/pm/verify/pkg/VerifierController.java"
+            line="443"
+            column="9"/>
+    </issue>
+
+    <issue
+        id="MissingPermissionAnnotation"
+        message="setVerificationPolicy should be annotated with either @EnforcePermission, @RequiresNoPermission or @PermissionManuallyEnforced."
+        errorLine1="        @Override"
+        errorLine2="        ^">
+        <location
+            file="frameworks/base/services/core/java/com/android/server/pm/verify/pkg/VerifierController.java"
+            line="456"
+            column="9"/>
+    </issue>
+
+    <issue
+        id="MissingPermissionAnnotation"
+        message="reportVerificationIncomplete should be annotated with either @EnforcePermission, @RequiresNoPermission or @PermissionManuallyEnforced."
+        errorLine1="        @Override"
+        errorLine2="        ^">
+        <location
+            file="frameworks/base/services/core/java/com/android/server/pm/verify/pkg/VerifierController.java"
+            line="470"
+            column="9"/>
+    </issue>
+
+    <issue
+        id="MissingPermissionAnnotation"
+        message="reportVerificationComplete should be annotated with either @EnforcePermission, @RequiresNoPermission or @PermissionManuallyEnforced."
+        errorLine1="        @Override"
+        errorLine2="        ^">
+        <location
+            file="frameworks/base/services/core/java/com/android/server/pm/verify/pkg/VerifierController.java"
+            line="486"
+            column="9"/>
+    </issue>
+
+    <issue
+        id="MissingPermissionAnnotation"
+        message="reportVerificationCompleteWithExtensionResponse should be annotated with either @EnforcePermission, @RequiresNoPermission or @PermissionManuallyEnforced."
+        errorLine1="        @Override"
+        errorLine2="        ^">
+        <location
+            file="frameworks/base/services/core/java/com/android/server/pm/verify/pkg/VerifierController.java"
+            line="492"
+            column="9"/>
+    </issue>
+
+    <issue
+        id="SimpleManualPermissionEnforcement"
+        message="IActivityClientController permission check can be converted to @EnforcePermission annotation"
+        errorLine1="        mService.mAmInternal.enforceCallingPermission("
+        errorLine2="        ^">
+        <location
+            file="out/soong/.intermediates/frameworks/base/services/core/services.core.protologsrc/gen/services.core.protolog.srcjar!/frameworks/base/services/core/java/com/android/server/wm/ActivityClientController.java"
+            line="592"
+            column="9"/>
+    </issue>
+
+    <issue
+        id="SimpleManualPermissionEnforcement"
+        message="IActivityClientController permission check can be converted to @EnforcePermission annotation"
+        errorLine1="        mService.mAmInternal.enforceCallingPermission(CONTROL_REMOTE_APP_TRANSITION_ANIMATIONS,"
+        errorLine2="        ^">
+        <location
+            file="out/soong/.intermediates/frameworks/base/services/core/services.core.protologsrc/gen/services.core.protolog.srcjar!/frameworks/base/services/core/java/com/android/server/wm/ActivityClientController.java"
+            line="1636"
+            column="9"/>
+    </issue>
+
+    <issue
+        id="SimpleManualPermissionEnforcement"
+        message="IActivityClientController permission check can be converted to @EnforcePermission annotation"
+        errorLine1="        mService.mAmInternal.enforceCallingPermission(CONTROL_REMOTE_APP_TRANSITION_ANIMATIONS,"
+        errorLine2="        ^">
+        <location
+            file="out/soong/.intermediates/frameworks/base/services/core/services.core.protologsrc/gen/services.core.protolog.srcjar!/frameworks/base/services/core/java/com/android/server/wm/ActivityClientController.java"
+            line="1654"
+            column="9"/>
+    </issue>
+
+    <issue
+        id="SimpleManualPermissionEnforcement"
+        message="IActivityTaskManager permission check can be converted to @EnforcePermission annotation"
+        errorLine1="        mAmInternal.enforceCallingPermission(START_TASKS_FROM_RECENTS,"
+        errorLine2="        ^">
+        <location
+            file="out/soong/.intermediates/frameworks/base/services/core/services.core.protologsrc/gen/services.core.protolog.srcjar!/frameworks/base/services/core/java/com/android/server/wm/ActivityTaskManagerService.java"
+            line="1820"
+            column="9"/>
+    </issue>
+
+    <issue
+        id="SimpleManualPermissionEnforcement"
+        message="IActivityTaskManager permission check can be converted to @EnforcePermission annotation"
+        errorLine1="        mAmInternal.enforceCallingPermission(START_TASKS_FROM_RECENTS,"
+        errorLine2="        ^">
+        <location
+            file="out/soong/.intermediates/frameworks/base/services/core/services.core.protologsrc/gen/services.core.protolog.srcjar!/frameworks/base/services/core/java/com/android/server/wm/ActivityTaskManagerService.java"
+            line="1875"
+            column="9"/>
+    </issue>
+
+    <issue
+        id="SimpleManualPermissionEnforcement"
+        message="IActivityTaskManager permission check can be converted to @EnforcePermission annotation"
+        errorLine1="        mAmInternal.enforceCallingPermission(android.Manifest.permission.SET_SCREEN_COMPATIBILITY,"
+        errorLine2="        ^">
+        <location
+            file="out/soong/.intermediates/frameworks/base/services/core/services.core.protologsrc/gen/services.core.protolog.srcjar!/frameworks/base/services/core/java/com/android/server/wm/ActivityTaskManagerService.java"
+            line="1980"
+            column="9"/>
+    </issue>
+
+    <issue
+        id="SimpleManualPermissionEnforcement"
+        message="IActivityTaskManager permission check can be converted to @EnforcePermission annotation"
+        errorLine1="        mAmInternal.enforceCallingPermission(REMOVE_TASKS, &quot;removeTask()&quot;);"
+        errorLine2="        ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~">
+        <location
+            file="out/soong/.intermediates/frameworks/base/services/core/services.core.protologsrc/gen/services.core.protolog.srcjar!/frameworks/base/services/core/java/com/android/server/wm/ActivityTaskManagerService.java"
+            line="2116"
+            column="9"/>
+    </issue>
+
+    <issue
+        id="SimpleManualPermissionEnforcement"
+        message="IActivityTaskManager permission check can be converted to @EnforcePermission annotation"
+        errorLine1="        mAmInternal.enforceCallingPermission(REMOVE_TASKS, &quot;removeAllVisibleRecentTasks()&quot;);"
+        errorLine2="        ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~">
+        <location
+            file="out/soong/.intermediates/frameworks/base/services/core/services.core.protologsrc/gen/services.core.protolog.srcjar!/frameworks/base/services/core/java/com/android/server/wm/ActivityTaskManagerService.java"
+            line="2144"
+            column="9"/>
+    </issue>
+
+    <issue
+        id="SimpleManualPermissionEnforcement"
+        message="IActivityTaskManager permission check can be converted to @EnforcePermission annotation"
+        errorLine1="        mAmInternal.enforceCallingPermission(android.Manifest.permission.FORCE_BACK,"
+        errorLine2="        ^">
+        <location
+            file="out/soong/.intermediates/frameworks/base/services/core/services.core.protologsrc/gen/services.core.protolog.srcjar!/frameworks/base/services/core/java/com/android/server/wm/ActivityTaskManagerService.java"
+            line="2206"
+            column="9"/>
+    </issue>
+
+    <issue
+        id="SimpleManualPermissionEnforcement"
+        message="IActivityTaskManager permission check can be converted to @EnforcePermission annotation"
+        errorLine1="        mAmInternal.enforceCallingPermission(android.Manifest.permission.REORDER_TASKS,"
+        errorLine2="        ^">
+        <location
+            file="out/soong/.intermediates/frameworks/base/services/core/services.core.protologsrc/gen/services.core.protolog.srcjar!/frameworks/base/services/core/java/com/android/server/wm/ActivityTaskManagerService.java"
+            line="2228"
+            column="9"/>
+    </issue>
+
+    <issue
+        id="SimpleManualPermissionEnforcement"
+        message="IActivityTaskManager permission check can be converted to @EnforcePermission annotation"
+        errorLine1="        mAmInternal.enforceCallingPermission(android.Manifest.permission.SET_ACTIVITY_WATCHER,"
+        errorLine2="        ^">
+        <location
+            file="out/soong/.intermediates/frameworks/base/services/core/services.core.protologsrc/gen/services.core.protolog.srcjar!/frameworks/base/services/core/java/com/android/server/wm/ActivityTaskManagerService.java"
+            line="2371"
+            column="9"/>
+    </issue>
+
+    <issue
+        id="SimpleManualPermissionEnforcement"
+        message="IActivityTaskManager permission check can be converted to @EnforcePermission annotation"
+        errorLine1="        mAmInternal.enforceCallingPermission(INTERNAL_SYSTEM_WINDOW, &quot;moveRootTaskToDisplay()&quot;);"
+        errorLine2="        ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~">
+        <location
+            file="out/soong/.intermediates/frameworks/base/services/core/services.core.protologsrc/gen/services.core.protolog.srcjar!/frameworks/base/services/core/java/com/android/server/wm/ActivityTaskManagerService.java"
+            line="3103"
+            column="9"/>
+    </issue>
+
+    <issue
+        id="SimpleManualPermissionEnforcement"
+        message="IActivityTaskManager permission check can be converted to @EnforcePermission annotation"
+        errorLine1="        mAmInternal.enforceCallingPermission(android.Manifest.permission.GET_TOP_ACTIVITY_INFO,"
+        errorLine2="        ^">
+        <location
+            file="out/soong/.intermediates/frameworks/base/services/core/services.core.protologsrc/gen/services.core.protolog.srcjar!/frameworks/base/services/core/java/com/android/server/wm/ActivityTaskManagerService.java"
+            line="3157"
+            column="9"/>
+    </issue>
+
+    <issue
+        id="SimpleManualPermissionEnforcement"
+        message="IActivityTaskManager permission check can be converted to @EnforcePermission annotation"
+        errorLine1="        mAmInternal.enforceCallingPermission(CONTROL_KEYGUARD, &quot;unlock keyguard&quot;);"
+        errorLine2="        ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~">
+        <location
+            file="out/soong/.intermediates/frameworks/base/services/core/services.core.protologsrc/gen/services.core.protolog.srcjar!/frameworks/base/services/core/java/com/android/server/wm/ActivityTaskManagerService.java"
+            line="3640"
+            column="9"/>
+    </issue>
+
+    <issue
+        id="SimpleManualPermissionEnforcement"
+        message="IActivityTaskManager permission check can be converted to @EnforcePermission annotation"
+        errorLine1="        mAmInternal.enforceCallingPermission(MANAGE_ACTIVITY_TASKS,"
+        errorLine2="        ^">
+        <location
+            file="out/soong/.intermediates/frameworks/base/services/core/services.core.protologsrc/gen/services.core.protolog.srcjar!/frameworks/base/services/core/java/com/android/server/wm/ActivityTaskManagerService.java"
+            line="3683"
+            column="9"/>
+    </issue>
+
+    <issue
+        id="SimpleManualPermissionEnforcement"
+        message="IActivityTaskManager permission check can be converted to @EnforcePermission annotation"
+        errorLine1="        mAmInternal.enforceCallingPermission(MANAGE_ACTIVITY_TASKS,"
+        errorLine2="        ^">
+        <location
+            file="out/soong/.intermediates/frameworks/base/services/core/services.core.protologsrc/gen/services.core.protolog.srcjar!/frameworks/base/services/core/java/com/android/server/wm/ActivityTaskManagerService.java"
+            line="3701"
+            column="9"/>
+    </issue>
+
+    <issue
+        id="SimpleManualPermissionEnforcement"
+        message="IActivityTaskManager permission check can be converted to @EnforcePermission annotation"
+        errorLine1="        mAmInternal.enforceCallingPermission(CHANGE_CONFIGURATION, &quot;updateConfiguration()&quot;);"
+        errorLine2="        ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~">
+        <location
+            file="out/soong/.intermediates/frameworks/base/services/core/services.core.protologsrc/gen/services.core.protolog.srcjar!/frameworks/base/services/core/java/com/android/server/wm/ActivityTaskManagerService.java"
+            line="3904"
+            column="9"/>
+    </issue>
+
+    <issue
+        id="SimpleManualPermissionEnforcement"
+        message="IActivityTaskManager permission check can be converted to @EnforcePermission annotation"
+        errorLine1="        mAmInternal.enforceCallingPermission(READ_FRAME_BUFFER, &quot;getTaskSnapshot()&quot;);"
+        errorLine2="        ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~">
+        <location
+            file="out/soong/.intermediates/frameworks/base/services/core/services.core.protologsrc/gen/services.core.protolog.srcjar!/frameworks/base/services/core/java/com/android/server/wm/ActivityTaskManagerService.java"
+            line="3978"
+            column="9"/>
+    </issue>
+
+    <issue
+        id="SimpleManualPermissionEnforcement"
+        message="IActivityTaskManager permission check can be converted to @EnforcePermission annotation"
+        errorLine1="        mAmInternal.enforceCallingPermission(READ_FRAME_BUFFER, &quot;takeTaskSnapshot()&quot;);"
+        errorLine2="        ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~">
+        <location
+            file="out/soong/.intermediates/frameworks/base/services/core/services.core.protologsrc/gen/services.core.protolog.srcjar!/frameworks/base/services/core/java/com/android/server/wm/ActivityTaskManagerService.java"
+            line="4000"
+            column="9"/>
+    </issue>
+
+    <issue
+        id="SimpleManualPermissionEnforcement"
+        message="IActivityTaskManager permission check can be converted to @EnforcePermission annotation"
+        errorLine1="        mAmInternal.enforceCallingPermission("
+        errorLine2="        ^">
+        <location
+            file="out/soong/.intermediates/frameworks/base/services/core/services.core.protologsrc/gen/services.core.protolog.srcjar!/frameworks/base/services/core/java/com/android/server/wm/ActivityTaskManagerService.java"
+            line="4029"
+            column="9"/>
+    </issue>
+
+    <issue
+        id="SimpleManualPermissionEnforcement"
+        message="IActivityTaskManager permission check can be converted to @EnforcePermission annotation"
+        errorLine1="        mAmInternal.enforceCallingPermission(CONTROL_REMOTE_APP_TRANSITION_ANIMATIONS,"
+        errorLine2="        ^">
+        <location
+            file="out/soong/.intermediates/frameworks/base/services/core/services.core.protologsrc/gen/services.core.protolog.srcjar!/frameworks/base/services/core/java/com/android/server/wm/ActivityTaskManagerService.java"
+            line="4057"
+            column="9"/>
+    </issue>
+
+    <issue
+        id="SimpleManualPermissionEnforcement"
+        message="IActivityTaskManager permission check can be converted to @EnforcePermission annotation"
+        errorLine1="        mAmInternal.enforceCallingPermission(CONTROL_REMOTE_APP_TRANSITION_ANIMATIONS,"
+        errorLine2="        ^">
+        <location
+            file="out/soong/.intermediates/frameworks/base/services/core/services.core.protologsrc/gen/services.core.protolog.srcjar!/frameworks/base/services/core/java/com/android/server/wm/ActivityTaskManagerService.java"
+            line="4074"
+            column="9"/>
+    </issue>
+
+    <issue
+        id="SimpleManualPermissionEnforcement"
+        message="IActivityTaskManager permission check can be converted to @EnforcePermission annotation"
+        errorLine1="        mAmInternal.enforceCallingPermission(STOP_APP_SWITCHES, &quot;stopAppSwitches&quot;);"
+        errorLine2="        ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~">
+        <location
+            file="out/soong/.intermediates/frameworks/base/services/core/services.core.protologsrc/gen/services.core.protolog.srcjar!/frameworks/base/services/core/java/com/android/server/wm/ActivityTaskManagerService.java"
+            line="4136"
+            column="9"/>
+    </issue>
+
+    <issue
+        id="SimpleManualPermissionEnforcement"
+        message="IActivityTaskManager permission check can be converted to @EnforcePermission annotation"
+        errorLine1="        mAmInternal.enforceCallingPermission(STOP_APP_SWITCHES, &quot;resumeAppSwitches&quot;);"
+        errorLine2="        ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~">
+        <location
+            file="out/soong/.intermediates/frameworks/base/services/core/services.core.protologsrc/gen/services.core.protolog.srcjar!/frameworks/base/services/core/java/com/android/server/wm/ActivityTaskManagerService.java"
+            line="4147"
+            column="9"/>
+    </issue>
+
+    <issue
+        id="SimpleManualPermissionEnforcement"
+        message="IActivityTaskManager permission check can be converted to @EnforcePermission annotation"
+        errorLine1="        mAmInternal.enforceCallingPermission(android.Manifest.permission.SET_SCREEN_COMPATIBILITY,"
+        errorLine2="        ^">
+        <location
+            file="out/soong/.intermediates/frameworks/base/services/core/services.core.protologsrc/gen/services.core.protolog.srcjar!/frameworks/base/services/core/java/com/android/server/wm/ActivityTaskManagerService.java"
+            line="4198"
+            column="9"/>
+    </issue>
+
+    <issue
+        id="SimpleManualPermissionEnforcement"
+        message="IActivityTaskManager permission check can be converted to @EnforcePermission annotation"
+        errorLine1="        mAmInternal.enforceCallingPermission(android.Manifest.permission.SET_SCREEN_COMPATIBILITY,"
+        errorLine2="        ^">
+        <location
+            file="out/soong/.intermediates/frameworks/base/services/core/services.core.protologsrc/gen/services.core.protolog.srcjar!/frameworks/base/services/core/java/com/android/server/wm/ActivityTaskManagerService.java"
+            line="4215"
+            column="9"/>
+    </issue>
+
+    <issue
+        id="SimpleManualPermissionEnforcement"
+        message="IActivityTaskManager permission check can be converted to @EnforcePermission annotation"
+        errorLine1="        mAmInternal.enforceCallingPermission(CONTROL_REMOTE_APP_TRANSITION_ANIMATIONS,"
+        errorLine2="        ^">
+        <location
+            file="out/soong/.intermediates/frameworks/base/services/core/services.core.protologsrc/gen/services.core.protolog.srcjar!/frameworks/base/services/core/java/com/android/server/wm/ActivityTaskManagerService.java"
+            line="4280"
+            column="9"/>
+    </issue>
+
+    <issue
+        id="SimpleManualPermissionEnforcement"
+        message="IActivityTaskManager permission check can be converted to @EnforcePermission annotation"
+        errorLine1="        mAmInternal.enforceCallingPermission(DETECT_SCREEN_CAPTURE,"
+        errorLine2="        ^">
+        <location
+            file="out/soong/.intermediates/frameworks/base/services/core/services.core.protologsrc/gen/services.core.protolog.srcjar!/frameworks/base/services/core/java/com/android/server/wm/ActivityTaskManagerService.java"
+            line="5836"
+            column="9"/>
+    </issue>
+
+    <issue
+        id="SimpleManualPermissionEnforcement"
+        message="IActivityTaskManager permission check can be converted to @EnforcePermission annotation"
+        errorLine1="        mAmInternal.enforceCallingPermission(DETECT_SCREEN_CAPTURE,"
+        errorLine2="        ^">
+        <location
+            file="out/soong/.intermediates/frameworks/base/services/core/services.core.protologsrc/gen/services.core.protolog.srcjar!/frameworks/base/services/core/java/com/android/server/wm/ActivityTaskManagerService.java"
+            line="5849"
+            column="9"/>
+    </issue>
+
+    <issue
+        id="SimpleManualPermissionEnforcement"
+        message="IColorDisplayManager permission check should be converted to @EnforcePermission annotation"
+        errorLine1="            getContext().enforceCallingOrSelfPermission("
+        errorLine2="            ^">
+        <location
+            file="frameworks/base/services/core/java/com/android/server/display/color/ColorDisplayService.java"
+            line="2152"
+            column="13"/>
+    </issue>
+
+    <issue
+        id="SimpleManualPermissionEnforcement"
+        message="IMediaProjectionManager permission check can be converted to @EnforcePermission annotation"
+        errorLine1="            if (mContext.checkCallingPermission(MANAGE_MEDIA_PROJECTION)"
+        errorLine2="            ^">
+        <location
+            file="frameworks/base/services/core/java/com/android/server/media/projection/MediaProjectionManagerService.java"
+            line="779"
+            column="13"/>
+    </issue>
+
+    <issue
+        id="SimpleManualPermissionEnforcement"
+        message="IMediaProjectionManager permission check can be converted to @EnforcePermission annotation"
+        errorLine1="            if (mContext.checkCallingPermission(MANAGE_MEDIA_PROJECTION)"
+        errorLine2="            ^">
+        <location
+            file="frameworks/base/services/core/java/com/android/server/media/projection/MediaProjectionManagerService.java"
+            line="820"
+            column="13"/>
+    </issue>
+
+    <issue
+        id="SimpleManualPermissionEnforcement"
+        message="IMediaProjectionManager permission check can be converted to @EnforcePermission annotation"
+        errorLine1="            if (mContext.checkCallingPermission(MANAGE_MEDIA_PROJECTION)"
+        errorLine2="            ^">
+        <location
+            file="frameworks/base/services/core/java/com/android/server/media/projection/MediaProjectionManagerService.java"
+            line="906"
+            column="13"/>
+    </issue>
+
+    <issue
+        id="SimpleManualPermissionEnforcement"
+        message="INotificationManager permission check can be converted to @EnforcePermission annotation"
+        errorLine1="            if (PERMISSION_GRANTED"
+        errorLine2="            ^">
+        <location
+            file="frameworks/base/services/core/java/com/android/server/notification/NotificationManagerService.java"
+            line="6691"
+            column="13"/>
+    </issue>
+
+    <issue
+        id="SimpleManualPermissionEnforcement"
+        message="INotificationManager permission check can be converted to @EnforcePermission annotation"
+        errorLine1="            if (PERMISSION_GRANTED"
+        errorLine2="            ^">
+        <location
+            file="frameworks/base/services/core/java/com/android/server/notification/NotificationManagerService.java"
+            line="6712"
+            column="13"/>
+    </issue>
+
+    <issue
+        id="SimpleManualPermissionEnforcement"
+        message="IOnDeviceIntelligenceManager permission check can be converted to @EnforcePermission annotation"
+        errorLine1="                mContext.enforceCallingPermission("
+        errorLine2="                ^">
+        <location
+            file="frameworks/base/services/core/java/com/android/server/ondeviceintelligence/OnDeviceIntelligenceManagerService.java"
+            line="237"
+            column="17"/>
+    </issue>
+
+    <issue
+        id="SimpleManualPermissionEnforcement"
+        message="IPackageInstaller permission check should be converted to @EnforcePermission annotation"
+        errorLine1="        if (mContext.checkCallingOrSelfPermission(Manifest.permission.VERIFICATION_AGENT)"
+        errorLine2="        ^">
+        <location
+            file="frameworks/base/services/core/java/com/android/server/pm/PackageInstallerService.java"
+            line="1881"
+            column="9"/>
+    </issue>
+
+    <issue
+        id="SimpleManualPermissionEnforcement"
+        message="IPackageInstaller permission check should be converted to @EnforcePermission annotation"
+        errorLine1="        if (mContext.checkCallingOrSelfPermission(Manifest.permission.VERIFICATION_AGENT)"
+        errorLine2="        ^">
+        <location
+            file="frameworks/base/services/core/java/com/android/server/pm/PackageInstallerService.java"
+            line="1892"
+            column="9"/>
+    </issue>
+
+    <issue
+        id="SimpleManualPermissionEnforcement"
+        message="IPackageManager permission check can be converted to @EnforcePermission annotation"
+        errorLine1="            mContext.enforceCallingPermission(Manifest.permission.SEND_DEVICE_CUSTOMIZATION_READY,"
+        errorLine2="            ^">
+        <location
+            file="frameworks/base/services/core/java/com/android/server/pm/PackageManagerService.java"
+            line="5798"
+            column="13"/>
+    </issue>
+
+    <issue
+        id="SimpleManualPermissionEnforcement"
+        message="IPackageManager permission check can be converted to @EnforcePermission annotation"
+        errorLine1="            mContext.enforceCallingPermission("
+        errorLine2="            ^">
+        <location
+            file="frameworks/base/services/core/java/com/android/server/pm/PackageManagerService.java"
+            line="6266"
+            column="13"/>
+    </issue>
+
+    <issue
+        id="SimpleManualPermissionEnforcement"
+        message="ITvInputManager permission check can be converted to @EnforcePermission annotation"
+        errorLine1="            if (mContext.checkCallingPermission("
+        errorLine2="            ^">
+        <location
+            file="frameworks/base/services/core/java/com/android/server/tv/TvInputManagerService.java"
+            line="1406"
+            column="13"/>
+    </issue>
+
+    <issue
+        id="SimpleManualPermissionEnforcement"
+        message="ITvInputManager permission check can be converted to @EnforcePermission annotation"
+        errorLine1="            if (mContext.checkCallingPermission(android.Manifest.permission.NOTIFY_TV_INPUTS)"
+        errorLine2="            ^">
+        <location
+            file="frameworks/base/services/core/java/com/android/server/tv/TvInputManagerService.java"
+            line="1427"
+            column="13"/>
+    </issue>
+
+    <issue
+        id="SimpleManualPermissionEnforcement"
+        message="ITvInputManager permission check can be converted to @EnforcePermission annotation"
+        errorLine1="            if (mContext.checkCallingPermission("
+        errorLine2="            ^">
+        <location
+            file="frameworks/base/services/core/java/com/android/server/tv/TvInputManagerService.java"
+            line="1734"
+            column="13"/>
+    </issue>
+
+    <issue
+        id="SimpleManualPermissionEnforcement"
+        message="ITvInputManager permission check can be converted to @EnforcePermission annotation"
+        errorLine1="            if (mContext.checkCallingPermission(android.Manifest.permission.DVB_DEVICE)"
+        errorLine2="            ^">
+        <location
+            file="frameworks/base/services/core/java/com/android/server/tv/TvInputManagerService.java"
+            line="2509"
+            column="13"/>
+    </issue>
+
+    <issue
+        id="SimpleManualPermissionEnforcement"
+        message="ITvInputManager permission check can be converted to @EnforcePermission annotation"
+        errorLine1="            if (mContext.checkCallingPermission(android.Manifest.permission.DVB_DEVICE)"
+        errorLine2="            ^">
+        <location
+            file="frameworks/base/services/core/java/com/android/server/tv/TvInputManagerService.java"
+            line="2564"
+            column="13"/>
+    </issue>
+
+    <issue
+        id="SimpleManualPermissionEnforcement"
+        message="ITvInputManager permission check can be converted to @EnforcePermission annotation"
+        errorLine1="            if (mContext.checkCallingPermission(android.Manifest.permission.ACCESS_TUNED_INFO)"
+        errorLine2="            ^">
+        <location
+            file="frameworks/base/services/core/java/com/android/server/tv/TvInputManagerService.java"
+            line="2932"
+            column="13"/>
+    </issue>
+
+    <issue
+        id="SimpleManualPermissionEnforcement"
+        message="IUriGrantsManager permission check can be converted to @EnforcePermission annotation"
+        errorLine1="        mAmInternal.enforceCallingPermission("
+        errorLine2="        ^">
+        <location
+            file="frameworks/base/services/core/java/com/android/server/uri/UriGrantsManagerService.java"
+            line="366"
+            column="9"/>
+    </issue>
+
+    <issue
+        id="SimpleManualPermissionEnforcement"
+        message="IUriGrantsManager permission check can be converted to @EnforcePermission annotation"
+        errorLine1="        mAmInternal.enforceCallingPermission("
+        errorLine2="        ^">
+        <location
+            file="frameworks/base/services/core/java/com/android/server/uri/UriGrantsManagerService.java"
+            line="444"
+            column="9"/>
+    </issue>
+
+    <issue
+        id="SimpleManualPermissionEnforcement"
+        message="IVcnManagementService permission check should be converted to @EnforcePermission annotation"
+        errorLine1="        mContext.enforceCallingOrSelfPermission(DUMP, TAG);"
+        errorLine2="        ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~">
+        <location
+            file="frameworks/base/services/core/java/com/android/server/VcnManagementService.java"
+            line="1329"
+            column="9"/>
+    </issue>
+
+</issues>
diff --git a/services/profcollect/src/com/android/server/profcollect/ProfcollectForwardingService.java b/services/profcollect/src/com/android/server/profcollect/ProfcollectForwardingService.java
index 105147f..42c171b 100644
--- a/services/profcollect/src/com/android/server/profcollect/ProfcollectForwardingService.java
+++ b/services/profcollect/src/com/android/server/profcollect/ProfcollectForwardingService.java
@@ -26,6 +26,7 @@
 import android.content.Intent;
 import android.content.IntentFilter;
 import android.hardware.camera2.CameraManager;
+import android.hardware.usb.UsbManager;
 import android.os.Handler;
 import android.os.IBinder.DeathRecipient;
 import android.os.Looper;
@@ -67,6 +68,8 @@
     private int mUsageSetting;
     private boolean mUploadEnabled;
 
+    private boolean mAdbActive;
+
     private IProfCollectd mIProfcollect;
     private static ProfcollectForwardingService sSelfService;
     private final Handler mHandler = new ProfcollectdHandler(IoThread.getHandler().getLooper());
@@ -84,6 +87,14 @@
                 Log.d(LOG_TAG, "Received broadcast to pack and upload reports");
                 createAndUploadReport(sSelfService);
             }
+            if (UsbManager.ACTION_USB_STATE.equals(intent.getAction())) {
+                boolean connected = intent.getBooleanExtra(UsbManager.USB_CONNECTED, false);
+                boolean isADB = intent.getBooleanExtra(UsbManager.USB_FUNCTION_ADB, false);
+                if (isADB) {
+                    Log.d(LOG_TAG, "Received broadcast that ADB became " + connected);
+                    mAdbActive = connected;
+                }
+            }
         }
     };
 
@@ -106,8 +117,12 @@
         mUploadEnabled =
             context.getResources().getBoolean(R.bool.config_profcollectReportUploaderEnabled);
 
+        // TODO: ADB might already be active when our service started.
+        mAdbActive = false;
+
         final IntentFilter filter = new IntentFilter();
         filter.addAction(INTENT_UPLOAD_PROFILES);
+        filter.addAction(UsbManager.ACTION_USB_STATE);
         context.registerReceiver(mBroadcastReceiver, filter, Context.RECEIVER_NOT_EXPORTED);
     }
 
@@ -281,6 +296,9 @@
             if (mIProfcollect == null) {
                 return;
             }
+            if (mAdbActive) {
+                return;
+            }
             if (Utils.withFrequency("applaunch_trace_freq", 5)) {
                 Utils.traceSystem(mIProfcollect, "applaunch");
             }
@@ -303,6 +321,9 @@
         if (mIProfcollect == null) {
             return;
         }
+        if (mAdbActive) {
+            return;
+        }
         if (Utils.withFrequency("dex2oat_trace_freq", 25)) {
             // Dex2oat could take a while before it starts. Add a short delay before start tracing.
             Utils.traceSystem(mIProfcollect, "dex2oat", /* delayMs */ 1000);
diff --git a/services/tests/PackageManagerServiceTests/server/src/com/android/server/pm/verify/pkg/VerifierControllerTest.java b/services/tests/PackageManagerServiceTests/server/src/com/android/server/pm/verify/pkg/VerifierControllerTest.java
index 2461798..3046d4b 100644
--- a/services/tests/PackageManagerServiceTests/server/src/com/android/server/pm/verify/pkg/VerifierControllerTest.java
+++ b/services/tests/PackageManagerServiceTests/server/src/com/android/server/pm/verify/pkg/VerifierControllerTest.java
@@ -24,6 +24,7 @@
 import static org.mockito.ArgumentMatchers.any;
 import static org.mockito.ArgumentMatchers.anyInt;
 import static org.mockito.ArgumentMatchers.anyLong;
+import static org.mockito.ArgumentMatchers.anyString;
 import static org.mockito.ArgumentMatchers.eq;
 import static org.mockito.Mockito.times;
 import static org.mockito.Mockito.verify;
@@ -49,6 +50,7 @@
 
 import androidx.test.ext.junit.runners.AndroidJUnit4;
 import androidx.test.filters.SmallTest;
+import androidx.test.platform.app.InstrumentationRegistry;
 
 import com.android.internal.infra.AndroidFuture;
 import com.android.internal.infra.ServiceConnector;
@@ -122,6 +124,10 @@
     @Before
     public void setUp() {
         MockitoAnnotations.initMocks(this);
+        // Mock that the UID of this test becomes the UID of the verifier
+        when(mSnapshot.getPackageUidInternal(anyString(), anyLong(), anyInt(), anyInt()))
+                .thenReturn(InstrumentationRegistry.getInstrumentation().getContext()
+                        .getApplicationInfo().uid);
         when(mInjector.getVerifierPackageName(any(Computer.class), anyInt())).thenReturn(
                 TEST_VERIFIER_COMPONENT_NAME.getPackageName());
         when(mInjector.getRemoteService(
diff --git a/services/tests/displayservicetests/src/com/android/server/display/DisplayTopologyCoordinatorTest.kt b/services/tests/displayservicetests/src/com/android/server/display/DisplayTopologyCoordinatorTest.kt
index 17af633..85e7356 100644
--- a/services/tests/displayservicetests/src/com/android/server/display/DisplayTopologyCoordinatorTest.kt
+++ b/services/tests/displayservicetests/src/com/android/server/display/DisplayTopologyCoordinatorTest.kt
@@ -21,7 +21,7 @@
 import android.view.DisplayInfo
 import org.junit.Before
 import org.junit.Test
-import org.mockito.ArgumentMatchers.anyDouble
+import org.mockito.ArgumentMatchers.anyFloat
 import org.mockito.ArgumentMatchers.anyInt
 import org.mockito.kotlin.mock
 import org.mockito.kotlin.never
@@ -55,9 +55,9 @@
 
         coordinator.onDisplayAdded(displayInfo)
 
-        val widthDp = displayInfo.logicalWidth * (DisplayMetrics.DENSITY_DEFAULT.toDouble()
+        val widthDp = displayInfo.logicalWidth * (DisplayMetrics.DENSITY_DEFAULT.toFloat()
                 / displayInfo.logicalDensityDpi)
-        val heightDp = displayInfo.logicalHeight * (DisplayMetrics.DENSITY_DEFAULT.toDouble()
+        val heightDp = displayInfo.logicalHeight * (DisplayMetrics.DENSITY_DEFAULT.toFloat()
                 / displayInfo.logicalDensityDpi)
         verify(mockTopology).addDisplay(displayInfo.displayId, widthDp, heightDp)
     }
@@ -68,7 +68,7 @@
 
         coordinator.onDisplayAdded(displayInfo)
 
-        verify(mockTopology, never()).addDisplay(anyInt(), anyDouble(), anyDouble())
+        verify(mockTopology, never()).addDisplay(anyInt(), anyFloat(), anyFloat())
     }
 
     @Test
@@ -78,6 +78,6 @@
 
         coordinator.onDisplayAdded(displayInfo)
 
-        verify(mockTopology, never()).addDisplay(anyInt(), anyDouble(), anyDouble())
+        verify(mockTopology, never()).addDisplay(anyInt(), anyFloat(), anyFloat())
     }
 }
\ No newline at end of file
diff --git a/services/tests/displayservicetests/src/com/android/server/display/DisplayTopologyTest.kt b/services/tests/displayservicetests/src/com/android/server/display/DisplayTopologyTest.kt
index f3a8d841..cd8c26d 100644
--- a/services/tests/displayservicetests/src/com/android/server/display/DisplayTopologyTest.kt
+++ b/services/tests/displayservicetests/src/com/android/server/display/DisplayTopologyTest.kt
@@ -17,6 +17,9 @@
 package com.android.server.display
 
 import android.view.Display
+import com.android.server.display.DisplayTopology.TreeNode.Position.POSITION_BOTTOM
+import com.android.server.display.DisplayTopology.TreeNode.Position.POSITION_TOP
+import com.android.server.display.DisplayTopology.TreeNode.Position.POSITION_RIGHT
 import com.google.common.truth.Truth.assertThat
 import org.junit.Test
 
@@ -26,8 +29,8 @@
     @Test
     fun addOneDisplay() {
         val displayId = 1
-        val width = 800.0
-        val height = 600.0
+        val width = 800f
+        val height = 600f
 
         topology.addDisplay(displayId, width, height)
 
@@ -43,12 +46,12 @@
     @Test
     fun addTwoDisplays() {
         val displayId1 = 1
-        val width1 = 800.0
-        val height1 = 600.0
+        val width1 = 800f
+        val height1 = 600f
 
         val displayId2 = 2
-        val width2 = 1000.0
-        val height2 = 1500.0
+        val width2 = 1000f
+        val height2 = 1500f
 
         topology.addDisplay(displayId1, width1, height1)
         topology.addDisplay(displayId2, width2, height2)
@@ -66,20 +69,19 @@
         assertThat(display2.mWidth).isEqualTo(width2)
         assertThat(display2.mHeight).isEqualTo(height2)
         assertThat(display2.mChildren).isEmpty()
-        assertThat(display2.mPosition).isEqualTo(
-            DisplayTopology.TreeNode.Position.POSITION_TOP)
+        assertThat(display2.mPosition).isEqualTo(POSITION_TOP)
         assertThat(display2.mOffset).isEqualTo(width1 / 2 - width2 / 2)
     }
 
     @Test
     fun addManyDisplays() {
         val displayId1 = 1
-        val width1 = 800.0
-        val height1 = 600.0
+        val width1 = 800f
+        val height1 = 600f
 
         val displayId2 = 2
-        val width2 = 1000.0
-        val height2 = 1500.0
+        val width2 = 1000f
+        val height2 = 1500f
 
         topology.addDisplay(displayId1, width1, height1)
         topology.addDisplay(displayId2, width2, height2)
@@ -102,8 +104,7 @@
         assertThat(display2.mWidth).isEqualTo(width2)
         assertThat(display2.mHeight).isEqualTo(height2)
         assertThat(display2.mChildren).hasSize(1)
-        assertThat(display2.mPosition).isEqualTo(
-            DisplayTopology.TreeNode.Position.POSITION_TOP)
+        assertThat(display2.mPosition).isEqualTo(POSITION_TOP)
         assertThat(display2.mOffset).isEqualTo(width1 / 2 - width2 / 2)
 
         var display = display2
@@ -114,8 +115,7 @@
             assertThat(display.mHeight).isEqualTo(height1)
             // The last display should have no children
             assertThat(display.mChildren).hasSize(if (i < noOfDisplays) 1 else 0)
-            assertThat(display.mPosition).isEqualTo(
-                DisplayTopology.TreeNode.Position.POSITION_RIGHT)
+            assertThat(display.mPosition).isEqualTo(POSITION_RIGHT)
             assertThat(display.mOffset).isEqualTo(0)
         }
     }
@@ -123,12 +123,12 @@
     @Test
     fun removeDisplays() {
         val displayId1 = 1
-        val width1 = 800.0
-        val height1 = 600.0
+        val width1 = 800f
+        val height1 = 600f
 
         val displayId2 = 2
-        val width2 = 1000.0
-        val height2 = 1500.0
+        val width2 = 1000f
+        val height2 = 1500f
 
         topology.addDisplay(displayId1, width1, height1)
         topology.addDisplay(displayId2, width2, height2)
@@ -154,8 +154,7 @@
         assertThat(display2.mWidth).isEqualTo(width2)
         assertThat(display2.mHeight).isEqualTo(height2)
         assertThat(display2.mChildren).hasSize(1)
-        assertThat(display2.mPosition).isEqualTo(
-            DisplayTopology.TreeNode.Position.POSITION_TOP)
+        assertThat(display2.mPosition).isEqualTo(POSITION_TOP)
         assertThat(display2.mOffset).isEqualTo(width1 / 2 - width2 / 2)
 
         var display = display2
@@ -169,8 +168,7 @@
             assertThat(display.mHeight).isEqualTo(height1)
             // The last display should have no children
             assertThat(display.mChildren).hasSize(if (i < noOfDisplays) 1 else 0)
-            assertThat(display.mPosition).isEqualTo(
-                DisplayTopology.TreeNode.Position.POSITION_RIGHT)
+            assertThat(display.mPosition).isEqualTo(POSITION_RIGHT)
             assertThat(display.mOffset).isEqualTo(0)
         }
 
@@ -194,8 +192,7 @@
         assertThat(display2.mWidth).isEqualTo(width2)
         assertThat(display2.mHeight).isEqualTo(height2)
         assertThat(display2.mChildren).hasSize(1)
-        assertThat(display2.mPosition).isEqualTo(
-            DisplayTopology.TreeNode.Position.POSITION_TOP)
+        assertThat(display2.mPosition).isEqualTo(POSITION_TOP)
         assertThat(display2.mOffset).isEqualTo(width1 / 2 - width2 / 2)
 
         display = display2
@@ -209,8 +206,7 @@
             assertThat(display.mHeight).isEqualTo(height1)
             // The last display should have no children
             assertThat(display.mChildren).hasSize(if (i < noOfDisplays) 1 else 0)
-            assertThat(display.mPosition).isEqualTo(
-                DisplayTopology.TreeNode.Position.POSITION_RIGHT)
+            assertThat(display.mPosition).isEqualTo(POSITION_RIGHT)
             assertThat(display.mOffset).isEqualTo(0)
         }
     }
@@ -218,8 +214,8 @@
     @Test
     fun removeAllDisplays() {
         val displayId = 1
-        val width = 800.0
-        val height = 600.0
+        val width = 800f
+        val height = 600f
 
         topology.addDisplay(displayId, width, height)
         topology.removeDisplay(displayId)
@@ -231,8 +227,8 @@
     @Test
     fun removeDisplayThatDoesNotExist() {
         val displayId = 1
-        val width = 800.0
-        val height = 600.0
+        val width = 800f
+        val height = 600f
 
         topology.addDisplay(displayId, width, height)
         topology.removeDisplay(3)
@@ -245,4 +241,236 @@
         assertThat(display.mHeight).isEqualTo(height)
         assertThat(display.mChildren).isEmpty()
     }
+
+    @Test
+    fun removePrimaryDisplay() {
+        val displayId1 = 1
+        val displayId2 = 2
+        val width = 800f
+        val height = 600f
+
+        topology.addDisplay(displayId1, width, height)
+        topology.addDisplay(displayId2, width, height)
+        topology.mPrimaryDisplayId = displayId2
+        topology.removeDisplay(displayId2)
+
+        assertThat(topology.mPrimaryDisplayId).isEqualTo(displayId1)
+        val display = topology.mRoot!!
+        assertThat(display.mDisplayId).isEqualTo(displayId1)
+        assertThat(display.mWidth).isEqualTo(width)
+        assertThat(display.mHeight).isEqualTo(height)
+        assertThat(display.mChildren).isEmpty()
+    }
+
+    @Test
+    fun normalization_noOverlaps_leavesTopologyUnchanged() {
+        val display1 = DisplayTopology.TreeNode(/* displayId= */ 1, /* width= */ 200f,
+            /* height= */ 600f, /* position= */ null, /* offset= */ 0f)
+        topology.mRoot = display1
+
+        val display2 = DisplayTopology.TreeNode(/* displayId= */ 2, /* width= */ 600f,
+            /* height= */ 200f, POSITION_RIGHT, /* offset= */ 0f)
+        display1.mChildren.add(display2)
+
+        val primaryDisplayId = 3
+        val display3 = DisplayTopology.TreeNode(primaryDisplayId, /* width= */ 600f,
+            /* height= */ 200f, POSITION_RIGHT, /* offset= */ 400f)
+        display1.mChildren.add(display3)
+        topology.mPrimaryDisplayId = primaryDisplayId
+
+        val display4 = DisplayTopology.TreeNode(/* displayId= */ 4, /* width= */ 200f,
+            /* height= */ 600f, POSITION_RIGHT, /* offset= */ 0f)
+        display2.mChildren.add(display4)
+
+        topology.normalize()
+
+        assertThat(topology.mPrimaryDisplayId).isEqualTo(primaryDisplayId)
+
+        val actualDisplay1 = topology.mRoot!!
+        assertThat(actualDisplay1.mDisplayId).isEqualTo(1)
+        assertThat(actualDisplay1.mWidth).isEqualTo(200f)
+        assertThat(actualDisplay1.mHeight).isEqualTo(600f)
+        assertThat(actualDisplay1.mChildren).hasSize(2)
+
+        val actualDisplay2 = actualDisplay1.mChildren[0]
+        assertThat(actualDisplay2.mDisplayId).isEqualTo(2)
+        assertThat(actualDisplay2.mWidth).isEqualTo(600f)
+        assertThat(actualDisplay2.mHeight).isEqualTo(200f)
+        assertThat(actualDisplay2.mPosition).isEqualTo(POSITION_RIGHT)
+        assertThat(actualDisplay2.mOffset).isEqualTo(0f)
+        assertThat(actualDisplay2.mChildren).hasSize(1)
+
+        val actualDisplay3 = actualDisplay1.mChildren[1]
+        assertThat(actualDisplay3.mDisplayId).isEqualTo(3)
+        assertThat(actualDisplay3.mWidth).isEqualTo(600f)
+        assertThat(actualDisplay3.mHeight).isEqualTo(200f)
+        assertThat(actualDisplay3.mPosition).isEqualTo(POSITION_RIGHT)
+        assertThat(actualDisplay3.mOffset).isEqualTo(400f)
+        assertThat(actualDisplay3.mChildren).isEmpty()
+
+        val actualDisplay4 = actualDisplay2.mChildren[0]
+        assertThat(actualDisplay4.mDisplayId).isEqualTo(4)
+        assertThat(actualDisplay4.mWidth).isEqualTo(200f)
+        assertThat(actualDisplay4.mHeight).isEqualTo(600f)
+        assertThat(actualDisplay4.mPosition).isEqualTo(POSITION_RIGHT)
+        assertThat(actualDisplay4.mOffset).isEqualTo(0f)
+        assertThat(actualDisplay4.mChildren).isEmpty()
+    }
+
+    @Test
+    fun normalization_moveDisplayWithoutReparenting() {
+        val display1 = DisplayTopology.TreeNode(/* displayId= */ 1, /* width= */ 200f,
+            /* height= */ 600f, /* position= */ null, /* offset= */ 0f)
+        topology.mRoot = display1
+
+        val display2 = DisplayTopology.TreeNode(/* displayId= */ 2, /* width= */ 200f,
+            /* height= */ 600f, POSITION_RIGHT, /* offset= */ 0f)
+        display1.mChildren.add(display2)
+
+        val primaryDisplayId = 3
+        val display3 = DisplayTopology.TreeNode(primaryDisplayId, /* width= */ 600f,
+            /* height= */ 200f, POSITION_RIGHT, /* offset= */ 10f)
+        display1.mChildren.add(display3)
+        topology.mPrimaryDisplayId = primaryDisplayId
+
+        val display4 = DisplayTopology.TreeNode(/* displayId= */ 4, /* width= */ 200f,
+            /* height= */ 600f, POSITION_RIGHT, /* offset= */ 0f)
+        display2.mChildren.add(display4)
+
+        // Display 3 becomes a child of display 2. Display 4 gets moved without changing its parent.
+        topology.normalize()
+
+        assertThat(topology.mPrimaryDisplayId).isEqualTo(primaryDisplayId)
+
+        val actualDisplay1 = topology.mRoot!!
+        assertThat(actualDisplay1.mDisplayId).isEqualTo(1)
+        assertThat(actualDisplay1.mWidth).isEqualTo(200f)
+        assertThat(actualDisplay1.mHeight).isEqualTo(600f)
+        assertThat(actualDisplay1.mChildren).hasSize(1)
+
+        val actualDisplay2 = actualDisplay1.mChildren[0]
+        assertThat(actualDisplay2.mDisplayId).isEqualTo(2)
+        assertThat(actualDisplay2.mWidth).isEqualTo(200f)
+        assertThat(actualDisplay2.mHeight).isEqualTo(600f)
+        assertThat(actualDisplay2.mPosition).isEqualTo(POSITION_RIGHT)
+        assertThat(actualDisplay2.mOffset).isEqualTo(0f)
+        assertThat(actualDisplay2.mChildren).hasSize(2)
+
+        val actualDisplay3 = actualDisplay2.mChildren[1]
+        assertThat(actualDisplay3.mDisplayId).isEqualTo(3)
+        assertThat(actualDisplay3.mWidth).isEqualTo(600f)
+        assertThat(actualDisplay3.mHeight).isEqualTo(200f)
+        assertThat(actualDisplay3.mPosition).isEqualTo(POSITION_RIGHT)
+        assertThat(actualDisplay3.mOffset).isEqualTo(10f)
+        assertThat(actualDisplay3.mChildren).isEmpty()
+
+        val actualDisplay4 = actualDisplay2.mChildren[0]
+        assertThat(actualDisplay4.mDisplayId).isEqualTo(4)
+        assertThat(actualDisplay4.mWidth).isEqualTo(200f)
+        assertThat(actualDisplay4.mHeight).isEqualTo(600f)
+        assertThat(actualDisplay4.mPosition).isEqualTo(POSITION_RIGHT)
+        assertThat(actualDisplay4.mOffset).isEqualTo(210f)
+        assertThat(actualDisplay4.mChildren).isEmpty()
+    }
+
+    @Test
+    fun normalization_moveDisplayWithoutReparenting_offsetOutOfBounds() {
+        val display1 = DisplayTopology.TreeNode(/* displayId= */ 1, /* width= */ 200f,
+            /* height= */ 50f, /* position= */ null, /* offset= */ 0f)
+        topology.mRoot = display1
+
+        val display2 = DisplayTopology.TreeNode(/* displayId= */ 2, /* width= */ 600f,
+            /* height= */ 200f, POSITION_RIGHT, /* offset= */ 0f)
+        display1.mChildren.add(display2)
+
+        val primaryDisplayId = 3
+        val display3 = DisplayTopology.TreeNode(primaryDisplayId, /* width= */ 600f,
+            /* height= */ 200f, POSITION_RIGHT, /* offset= */ 10f)
+        display1.mChildren.add(display3)
+        topology.mPrimaryDisplayId = primaryDisplayId
+
+        // Display 3 gets moved and its left side is still on the same line as the right side
+        // of Display 1, but it no longer touches it (the offset is out of bounds), so Display 2
+        // becomes its new parent.
+        topology.normalize()
+
+        assertThat(topology.mPrimaryDisplayId).isEqualTo(primaryDisplayId)
+
+        val actualDisplay1 = topology.mRoot!!
+        assertThat(actualDisplay1.mDisplayId).isEqualTo(1)
+        assertThat(actualDisplay1.mWidth).isEqualTo(200f)
+        assertThat(actualDisplay1.mHeight).isEqualTo(50f)
+        assertThat(actualDisplay1.mChildren).hasSize(1)
+
+        val actualDisplay2 = actualDisplay1.mChildren[0]
+        assertThat(actualDisplay2.mDisplayId).isEqualTo(2)
+        assertThat(actualDisplay2.mWidth).isEqualTo(600f)
+        assertThat(actualDisplay2.mHeight).isEqualTo(200f)
+        assertThat(actualDisplay2.mPosition).isEqualTo(POSITION_RIGHT)
+        assertThat(actualDisplay2.mOffset).isEqualTo(0f)
+        assertThat(actualDisplay2.mChildren).hasSize(1)
+
+        val actualDisplay3 = actualDisplay2.mChildren[0]
+        assertThat(actualDisplay3.mDisplayId).isEqualTo(3)
+        assertThat(actualDisplay3.mWidth).isEqualTo(600f)
+        assertThat(actualDisplay3.mHeight).isEqualTo(200f)
+        assertThat(actualDisplay3.mPosition).isEqualTo(POSITION_BOTTOM)
+        assertThat(actualDisplay3.mOffset).isEqualTo(0f)
+        assertThat(actualDisplay3.mChildren).isEmpty()
+    }
+
+    @Test
+    fun normalization_moveAndReparentDisplay() {
+        val display1 = DisplayTopology.TreeNode(/* displayId= */ 1, /* width= */ 200f,
+            /* height= */ 600f, /* position= */ null, /* offset= */ 0f)
+        topology.mRoot = display1
+
+        val display2 = DisplayTopology.TreeNode(/* displayId= */ 2, /* width= */ 200f,
+            /* height= */ 600f, POSITION_RIGHT, /* offset= */ 0f)
+        display1.mChildren.add(display2)
+
+        val primaryDisplayId = 3
+        val display3 = DisplayTopology.TreeNode(primaryDisplayId, /* width= */ 600f,
+            /* height= */ 200f, POSITION_RIGHT, /* offset= */ 400f)
+        display1.mChildren.add(display3)
+        topology.mPrimaryDisplayId = primaryDisplayId
+
+        val display4 = DisplayTopology.TreeNode(/* displayId= */ 4, /* width= */ 200f,
+            /* height= */ 600f, POSITION_RIGHT, /* offset= */ 0f)
+        display2.mChildren.add(display4)
+
+        topology.normalize()
+
+        assertThat(topology.mPrimaryDisplayId).isEqualTo(primaryDisplayId)
+
+        val actualDisplay1 = topology.mRoot!!
+        assertThat(actualDisplay1.mDisplayId).isEqualTo(1)
+        assertThat(actualDisplay1.mWidth).isEqualTo(200f)
+        assertThat(actualDisplay1.mHeight).isEqualTo(600f)
+        assertThat(actualDisplay1.mChildren).hasSize(1)
+
+        val actualDisplay2 = actualDisplay1.mChildren[0]
+        assertThat(actualDisplay2.mDisplayId).isEqualTo(2)
+        assertThat(actualDisplay2.mWidth).isEqualTo(200f)
+        assertThat(actualDisplay2.mHeight).isEqualTo(600f)
+        assertThat(actualDisplay2.mPosition).isEqualTo(POSITION_RIGHT)
+        assertThat(actualDisplay2.mOffset).isEqualTo(0f)
+        assertThat(actualDisplay2.mChildren).hasSize(1)
+
+        val actualDisplay3 = actualDisplay2.mChildren[0]
+        assertThat(actualDisplay3.mDisplayId).isEqualTo(3)
+        assertThat(actualDisplay3.mWidth).isEqualTo(600f)
+        assertThat(actualDisplay3.mHeight).isEqualTo(200f)
+        assertThat(actualDisplay3.mPosition).isEqualTo(POSITION_RIGHT)
+        assertThat(actualDisplay3.mOffset).isEqualTo(400f)
+        assertThat(actualDisplay3.mChildren).hasSize(1)
+
+        val actualDisplay4 = actualDisplay3.mChildren[0]
+        assertThat(actualDisplay4.mDisplayId).isEqualTo(4)
+        assertThat(actualDisplay4.mWidth).isEqualTo(200f)
+        assertThat(actualDisplay4.mHeight).isEqualTo(600f)
+        assertThat(actualDisplay4.mPosition).isEqualTo(POSITION_RIGHT)
+        assertThat(actualDisplay4.mOffset).isEqualTo(-400f)
+        assertThat(actualDisplay4.mChildren).isEmpty()
+    }
 }
\ No newline at end of file
diff --git a/services/tests/security/forensic/src/com/android/server/security/forensic/ForensicServiceTest.java b/services/tests/security/forensic/src/com/android/server/security/forensic/ForensicServiceTest.java
index 2b55303..feb00e7 100644
--- a/services/tests/security/forensic/src/com/android/server/security/forensic/ForensicServiceTest.java
+++ b/services/tests/security/forensic/src/com/android/server/security/forensic/ForensicServiceTest.java
@@ -19,23 +19,35 @@
 import static org.junit.Assert.assertEquals;
 import static org.junit.Assert.assertNotNull;
 import static org.junit.Assert.assertNull;
+import static org.mockito.ArgumentMatchers.any;
 import static org.mockito.Mockito.doNothing;
 import static org.mockito.Mockito.doReturn;
 import static org.mockito.Mockito.spy;
+import static org.mockito.Mockito.times;
+import static org.mockito.Mockito.verify;
 
 import android.annotation.SuppressLint;
 import android.content.Context;
 import android.os.Looper;
 import android.os.RemoteException;
 import android.os.test.TestLooper;
+import android.security.forensic.ForensicEvent;
 import android.security.forensic.IForensicServiceCommandCallback;
 import android.security.forensic.IForensicServiceStateCallback;
+import android.util.ArrayMap;
+
+import com.android.server.ServiceThread;
 
 import org.junit.Before;
 import org.junit.Test;
+import org.mockito.ArgumentCaptor;
 import org.mockito.Mock;
 import org.mockito.MockitoAnnotations;
 
+import java.util.ArrayList;
+import java.util.List;
+import java.util.Map;
+
 public class ForensicServiceTest {
     private static final int STATE_UNKNOWN = IForensicServiceStateCallback.State.UNKNOWN;
     private static final int STATE_INVISIBLE = IForensicServiceStateCallback.State.INVISIBLE;
@@ -55,10 +67,12 @@
     @Mock
     private Context mContext;
     private BackupTransportConnection mBackupTransportConnection;
-
+    private DataAggregator mDataAggregator;
     private ForensicService mForensicService;
     private TestLooper mTestLooper;
     private Looper mLooper;
+    private TestLooper mTestLooperOfDataAggregator;
+    private Looper mLooperOfDataAggregator;
 
     @SuppressLint("VisibleForTests")
     @Before
@@ -67,6 +81,8 @@
 
         mTestLooper = new TestLooper();
         mLooper = mTestLooper.getLooper();
+        mTestLooperOfDataAggregator = new TestLooper();
+        mLooperOfDataAggregator = mTestLooperOfDataAggregator.getLooper();
         mForensicService = new ForensicService(new MockInjector(mContext));
         mForensicService.onStart();
     }
@@ -121,6 +137,8 @@
         assertEquals(STATE_INVISIBLE, scb1.mState);
         assertEquals(STATE_INVISIBLE, scb2.mState);
 
+        doReturn(true).when(mDataAggregator).initialize();
+
         CommandCallback ccb = new CommandCallback();
         mForensicService.getBinderService().makeVisible(ccb);
         mTestLooper.dispatchAll();
@@ -130,6 +148,29 @@
     }
 
     @Test
+    public void testMakeVisible_FromInvisible_TwoMonitors_DataSourceUnavailable()
+            throws RemoteException {
+        mForensicService.setState(STATE_INVISIBLE);
+        StateCallback scb1 = new StateCallback();
+        StateCallback scb2 = new StateCallback();
+        mForensicService.getBinderService().monitorState(scb1);
+        mForensicService.getBinderService().monitorState(scb2);
+        mTestLooper.dispatchAll();
+        assertEquals(STATE_INVISIBLE, scb1.mState);
+        assertEquals(STATE_INVISIBLE, scb2.mState);
+
+        doReturn(false).when(mDataAggregator).initialize();
+
+        CommandCallback ccb = new CommandCallback();
+        mForensicService.getBinderService().makeVisible(ccb);
+        mTestLooper.dispatchAll();
+        assertEquals(STATE_INVISIBLE, scb1.mState);
+        assertEquals(STATE_INVISIBLE, scb2.mState);
+        assertNotNull(ccb.mErrorCode);
+        assertEquals(ERROR_DATA_SOURCE_UNAVAILABLE, ccb.mErrorCode.intValue());
+    }
+
+    @Test
     public void testMakeVisible_FromVisible_TwoMonitors() throws RemoteException {
         mForensicService.setState(STATE_VISIBLE);
         StateCallback scb1 = new StateCallback();
@@ -262,6 +303,8 @@
         CommandCallback ccb = new CommandCallback();
         mForensicService.getBinderService().enable(ccb);
         mTestLooper.dispatchAll();
+
+        verify(mDataAggregator, times(1)).enable();
         assertEquals(STATE_ENABLED, scb1.mState);
         assertEquals(STATE_ENABLED, scb2.mState);
         assertNull(ccb.mErrorCode);
@@ -361,14 +404,67 @@
 
         doNothing().when(mBackupTransportConnection).release();
 
+        ServiceThread mockThread = spy(ServiceThread.class);
+        mDataAggregator.setHandler(mLooperOfDataAggregator, mockThread);
+
         CommandCallback ccb = new CommandCallback();
         mForensicService.getBinderService().disable(ccb);
         mTestLooper.dispatchAll();
+        mTestLooperOfDataAggregator.dispatchAll();
+        // TODO: We can verify the data sources once we implement them.
+        verify(mockThread, times(1)).quitSafely();
         assertEquals(STATE_VISIBLE, scb1.mState);
         assertEquals(STATE_VISIBLE, scb2.mState);
         assertNull(ccb.mErrorCode);
     }
 
+    @Test
+    public void testDataAggregator_AddBatchData() {
+        mForensicService.setState(STATE_ENABLED);
+        ServiceThread mockThread = spy(ServiceThread.class);
+        mDataAggregator.setHandler(mLooperOfDataAggregator, mockThread);
+
+        String eventOneType = "event_one_type";
+        String eventOneMapKey = "event_one_map_key";
+        String eventOneMapVal = "event_one_map_val";
+        Map<String, String> eventOneMap = new ArrayMap<String, String>();
+        eventOneMap.put(eventOneMapKey, eventOneMapVal);
+        ForensicEvent eventOne = new ForensicEvent(eventOneType, eventOneMap);
+
+        String eventTwoType = "event_two_type";
+        String eventTwoMapKey = "event_two_map_key";
+        String eventTwoMapVal = "event_two_map_val";
+        Map<String, String> eventTwoMap = new ArrayMap<String, String>();
+        eventTwoMap.put(eventTwoMapKey, eventTwoMapVal);
+        ForensicEvent eventTwo = new ForensicEvent(eventTwoType, eventTwoMap);
+
+        List<ForensicEvent> events = new ArrayList<>();
+        events.add(eventOne);
+        events.add(eventTwo);
+
+        doReturn(true).when(mBackupTransportConnection).addData(any());
+
+        mDataAggregator.addBatchData(events);
+        mTestLooperOfDataAggregator.dispatchAll();
+        mTestLooper.dispatchAll();
+
+        ArgumentCaptor<List<ForensicEvent>> captor = ArgumentCaptor.forClass(List.class);
+        verify(mBackupTransportConnection).addData(captor.capture());
+        List<ForensicEvent> receivedEvents = captor.getValue();
+        assertEquals(receivedEvents.size(), 2);
+
+        assertEquals(receivedEvents.getFirst().getType(), eventOneType);
+        assertEquals(receivedEvents.getFirst().getKeyValuePairs().size(), 1);
+        assertEquals(receivedEvents.getFirst().getKeyValuePairs().get(eventOneMapKey),
+                eventOneMapVal);
+
+        assertEquals(receivedEvents.getLast().getType(), eventTwoType);
+        assertEquals(receivedEvents.getLast().getKeyValuePairs().size(), 1);
+        assertEquals(receivedEvents.getLast().getKeyValuePairs().get(eventTwoMapKey),
+                eventTwoMapVal);
+
+    }
+
     private class MockInjector implements ForensicService.Injector {
         private final Context mContext;
 
@@ -393,6 +489,11 @@
             return mBackupTransportConnection;
         }
 
+        @Override
+        public DataAggregator getDataAggregator(ForensicService forensicService) {
+            mDataAggregator = spy(new DataAggregator(forensicService));
+            return mDataAggregator;
+        }
     }
 
     private static class StateCallback extends IForensicServiceStateCallback.Stub {
diff --git a/services/tests/servicestests/src/com/android/server/hdmi/HdmiCecLocalDevicePlaybackTest.java b/services/tests/servicestests/src/com/android/server/hdmi/HdmiCecLocalDevicePlaybackTest.java
index a0005d9..3360e1d 100644
--- a/services/tests/servicestests/src/com/android/server/hdmi/HdmiCecLocalDevicePlaybackTest.java
+++ b/services/tests/servicestests/src/com/android/server/hdmi/HdmiCecLocalDevicePlaybackTest.java
@@ -413,7 +413,8 @@
                 .isEqualTo(Constants.HANDLED);
         assertThat(mHdmiCecLocalDevicePlayback.isActiveSource()).isFalse();
         // After 30s of device inactivity, device would go to sleep.
-        skipActiveSourceLostUi(STANDBY_AFTER_ACTIVE_SOURCE_LOST_DELAY_MS);
+        skipActiveSourceLostUi(STANDBY_AFTER_ACTIVE_SOURCE_LOST_DELAY_MS, false,
+                false);
         assertThat(mPowerManager.isInteractive()).isFalse();
     }
 
@@ -422,9 +423,8 @@
         int newPlaybackPhysicalAddress = 0x2100;
         int switchPhysicalAddress = 0x2000;
         mNativeWrapper.setPhysicalAddress(newPlaybackPhysicalAddress);
-        mHdmiControlService.onHotplug(newPlaybackPhysicalAddress, true);
         mHdmiControlService.allocateLogicalAddress(mLocalDevices, INITIATED_BY_ENABLE_CEC);
-
+        mHdmiControlService.onHotplug(newPlaybackPhysicalAddress, true);
         mHdmiCecLocalDevicePlayback.mService.getHdmiCecConfig().setStringValue(
                 HdmiControlManager.CEC_SETTING_NAME_POWER_STATE_CHANGE_ON_ACTIVE_SOURCE_LOST,
                 HdmiControlManager.POWER_STATE_CHANGE_ON_ACTIVE_SOURCE_LOST_STANDBY_NOW);
@@ -457,7 +457,8 @@
                 .isEqualTo(Constants.HANDLED);
         assertThat(mHdmiCecLocalDevicePlayback.isActiveSource()).isFalse();
         // After 30s of device inactivity, device would go to sleep.
-        skipActiveSourceLostUi(STANDBY_AFTER_ACTIVE_SOURCE_LOST_DELAY_MS);
+        skipActiveSourceLostUi(STANDBY_AFTER_ACTIVE_SOURCE_LOST_DELAY_MS, false,
+                false);
         assertThat(mPowerManager.isInteractive()).isFalse();
     }
 
@@ -617,7 +618,8 @@
                 .isEqualTo(Constants.HANDLED);
         assertThat(mHdmiCecLocalDevicePlayback.isActiveSource()).isFalse();
         // After 30s of device inactivity, device would go to sleep.
-        skipActiveSourceLostUi(STANDBY_AFTER_ACTIVE_SOURCE_LOST_DELAY_MS);
+        skipActiveSourceLostUi(STANDBY_AFTER_ACTIVE_SOURCE_LOST_DELAY_MS, false,
+                false);
         assertThat(mPowerManager.isInteractive()).isFalse();
     }
 
@@ -722,7 +724,8 @@
         assertThat(mHdmiCecLocalDevicePlayback.getActiveSource().logicalAddress).isEqualTo(
                 ADDR_INVALID);
         // After 30s of device inactivity, device would go to sleep.
-        skipActiveSourceLostUi(STANDBY_AFTER_ACTIVE_SOURCE_LOST_DELAY_MS);
+        skipActiveSourceLostUi(STANDBY_AFTER_ACTIVE_SOURCE_LOST_DELAY_MS,true,
+                true);
         assertThat(mPowerManager.isInteractive()).isFalse();
     }
 
@@ -1265,14 +1268,35 @@
         assertThat(mHdmiCecLocalDevicePlayback.handleActiveSource(message))
                 .isEqualTo(Constants.HANDLED);
         mTestLooper.dispatchAll();
-
-        // After 30s of device inactivity, device would go to sleep.
-        skipActiveSourceLostUi(STANDBY_AFTER_ACTIVE_SOURCE_LOST_DELAY_MS);
+        // After 30s of device inactivity, device would assert active source.
+        skipActiveSourceLostUi(STANDBY_AFTER_ACTIVE_SOURCE_LOST_DELAY_MS, true,
+                true);
         assertThat(mPowerManager.isInteractive()).isFalse();
         assertThat(mHdmiCecLocalDevicePlayback.isActiveSource()).isFalse();
     }
 
     @Test
+    public void handleActiveSourceFromTv_tvNotAnswerRequest_assertActiveSource() {
+        mHdmiCecLocalDevicePlayback.mService.getHdmiCecConfig().setStringValue(
+                HdmiControlManager.CEC_SETTING_NAME_POWER_STATE_CHANGE_ON_ACTIVE_SOURCE_LOST,
+                HdmiControlManager.POWER_STATE_CHANGE_ON_ACTIVE_SOURCE_LOST_STANDBY_NOW);
+        mPowerManager.setInteractive(true);
+        HdmiCecMessage message = HdmiCecMessageBuilder.buildActiveSource(mPlaybackLogicalAddress,
+                mPlaybackPhysicalAddress);
+        assertThat(mHdmiCecLocalDevicePlayback.handleActiveSource(message))
+                .isEqualTo(Constants.HANDLED);
+        message = HdmiCecMessageBuilder.buildActiveSource(ADDR_TV, 0x0000);
+        assertThat(mHdmiCecLocalDevicePlayback.handleActiveSource(message))
+                .isEqualTo(Constants.HANDLED);
+        mTestLooper.dispatchAll();
+        // After 30s of device inactivity, device would assert active source.
+        skipActiveSourceLostUi(STANDBY_AFTER_ACTIVE_SOURCE_LOST_DELAY_MS, true,
+                false);
+        assertThat(mPowerManager.isInteractive()).isTrue();
+        assertThat(mHdmiCecLocalDevicePlayback.isActiveSource()).isTrue();
+    }
+
+    @Test
     public void handleActiveSource_otherDevice_ActiveSource_mediaSessionsPaused() {
         mHdmiCecLocalDevicePlayback.setActiveSource(mPlaybackLogicalAddress,
                 mPlaybackPhysicalAddress, "HdmiCecLocalDevicePlaybackTest");
@@ -1343,7 +1367,8 @@
                 .isEqualTo(Constants.HANDLED);
         assertThat(mHdmiCecLocalDevicePlayback.isActiveSource()).isFalse();
         // After 30s of device inactivity, device would go to sleep.
-        skipActiveSourceLostUi(STANDBY_AFTER_ACTIVE_SOURCE_LOST_DELAY_MS);
+        skipActiveSourceLostUi(STANDBY_AFTER_ACTIVE_SOURCE_LOST_DELAY_MS, true,
+                true);
         assertThat(mPowerManager.isInteractive()).isFalse();
         mHdmiControlService.onStandby(HdmiControlService.STANDBY_SCREEN_OFF);
         // 3. DUT becomes <AS> again.
@@ -1704,9 +1729,9 @@
         assertThat(mHdmiCecLocalDevicePlayback.handleSetStreamPath(message))
                 .isEqualTo(Constants.HANDLED);
         assertThat(mHdmiCecLocalDevicePlayback.isActiveSource()).isFalse();
-
         // After 30s of device inactivity, device would go to sleep.
-        skipActiveSourceLostUi(STANDBY_AFTER_ACTIVE_SOURCE_LOST_DELAY_MS);
+        skipActiveSourceLostUi(STANDBY_AFTER_ACTIVE_SOURCE_LOST_DELAY_MS, false,
+                false);
         assertThat(mPowerManager.isInteractive()).isFalse();
     }
 
@@ -2323,11 +2348,197 @@
         mTestLooper.dispatchAll();
 
         // After 30s of device inactivity, device would go to sleep.
-        skipActiveSourceLostUi(STANDBY_AFTER_ACTIVE_SOURCE_LOST_DELAY_MS);
+        skipActiveSourceLostUi(STANDBY_AFTER_ACTIVE_SOURCE_LOST_DELAY_MS, true,
+                true);
         assertThat(mPowerManager.isInteractive()).isFalse();
     }
 
     @Test
+    public void onActiveSourceLostToTv_requestActiveSourceAnsweredFromTv_showPopup() {
+        mHdmiCecLocalDevicePlayback.mService.getHdmiCecConfig().setStringValue(
+                HdmiControlManager.CEC_SETTING_NAME_POWER_STATE_CHANGE_ON_ACTIVE_SOURCE_LOST,
+                HdmiControlManager.POWER_STATE_CHANGE_ON_ACTIVE_SOURCE_LOST_STANDBY_NOW);
+
+        mHdmiCecLocalDevicePlayback.setActiveSource(mPlaybackLogicalAddress,
+                mPlaybackPhysicalAddress, "HdmiCecLocalDevicePlaybackTest");
+        mPowerManager.setInteractive(true);
+        mNativeWrapper.clearResultMessages();
+        mTestLooper.dispatchAll();
+
+        HdmiCecMessage activeSourceFromTv =
+                HdmiCecMessageBuilder.buildActiveSource(ADDR_TV, 0x0000);
+        HdmiCecMessage requestActiveSource =
+                HdmiCecMessageBuilder.buildRequestActiveSource(mPlaybackLogicalAddress);
+
+        assertThat(mHdmiCecLocalDevicePlayback.handleActiveSource(activeSourceFromTv))
+                .isEqualTo(Constants.HANDLED);
+        mTestLooper.dispatchAll();
+
+        mTestLooper.moveTimeForward(POPUP_AFTER_ACTIVE_SOURCE_LOST_DELAY_MS);
+        mTestLooper.dispatchAll();
+
+        // RequestActiveSourceAction is triggered.
+        assertThat(mNativeWrapper.getResultMessages()).contains(requestActiveSource);
+        mTestLooper.moveTimeForward(TIMEOUT_MS);
+        mTestLooper.dispatchAll();
+
+        assertThat(mNativeWrapper.getResultMessages()).contains(requestActiveSource);
+        mNativeWrapper.onCecMessage(activeSourceFromTv);
+        mTestLooper.moveTimeForward(TIMEOUT_MS);
+        mTestLooper.dispatchAll();
+
+        assertThat(mHdmiCecLocalDevicePlayback.getActiveSource().logicalAddress).isEqualTo(ADDR_TV);
+        assertThat(mIsOnActiveSourceLostPopupActive).isTrue();
+    }
+
+    @Test
+    public void onActiveSourceLostToTv_requestActiveSourceNotAnswered_assertActiveSource() {
+        mHdmiCecLocalDevicePlayback.mService.getHdmiCecConfig().setStringValue(
+                HdmiControlManager.CEC_SETTING_NAME_POWER_STATE_CHANGE_ON_ACTIVE_SOURCE_LOST,
+                HdmiControlManager.POWER_STATE_CHANGE_ON_ACTIVE_SOURCE_LOST_STANDBY_NOW);
+
+        mHdmiCecLocalDevicePlayback.setActiveSource(mPlaybackLogicalAddress,
+                mPlaybackPhysicalAddress, "HdmiCecLocalDevicePlaybackTest");
+        mPowerManager.setInteractive(true);
+        mNativeWrapper.clearResultMessages();
+        mTestLooper.dispatchAll();
+
+        HdmiCecMessage activeSourceFromTv =
+                HdmiCecMessageBuilder.buildActiveSource(ADDR_TV, 0x0000);
+        HdmiCecMessage requestActiveSource =
+                HdmiCecMessageBuilder.buildRequestActiveSource(mPlaybackLogicalAddress);
+        HdmiCecMessage activeSourceFromPlayback =
+                HdmiCecMessageBuilder.buildActiveSource(mPlaybackLogicalAddress,
+                        mPlaybackPhysicalAddress);
+        assertThat(mHdmiCecLocalDevicePlayback.handleActiveSource(activeSourceFromTv))
+                .isEqualTo(Constants.HANDLED);
+        assertThat(mHdmiCecLocalDevicePlayback.getActiveSource().logicalAddress).isEqualTo(ADDR_TV);
+        mTestLooper.dispatchAll();
+
+        mTestLooper.moveTimeForward(POPUP_AFTER_ACTIVE_SOURCE_LOST_DELAY_MS);
+        mTestLooper.dispatchAll();
+
+        // RequestActiveSourceAction is triggered.
+        assertThat(mNativeWrapper.getResultMessages()).contains(requestActiveSource);
+        mTestLooper.moveTimeForward(TIMEOUT_MS);
+        mTestLooper.dispatchAll();
+
+        assertThat(mNativeWrapper.getResultMessages()).contains(requestActiveSource);
+        mTestLooper.moveTimeForward(TIMEOUT_MS);
+        mTestLooper.dispatchAll();
+
+        // Pop-up is not shown, playback device asserts active source since TV doesn't answer the
+        // request.
+        assertThat(mIsOnActiveSourceLostPopupActive).isFalse();
+        assertThat(mNativeWrapper.getResultMessages().contains(activeSourceFromPlayback)).isTrue();
+        assertThat(mHdmiCecLocalDevicePlayback.getActiveSource().logicalAddress)
+                .isEqualTo(mPlaybackLogicalAddress);
+        assertThat(mHdmiCecLocalDevicePlayback.getActiveSource().physicalAddress)
+                .isEqualTo(mPlaybackPhysicalAddress);
+    }
+
+    @Test
+    public void onActiveSourceLost_requestActiveSourceNotAnswered_playbackIsAS_dontShowPopup() {
+        mHdmiCecLocalDevicePlayback.mService.getHdmiCecConfig().setStringValue(
+                HdmiControlManager.CEC_SETTING_NAME_POWER_STATE_CHANGE_ON_ACTIVE_SOURCE_LOST,
+                HdmiControlManager.POWER_STATE_CHANGE_ON_ACTIVE_SOURCE_LOST_STANDBY_NOW);
+
+        mHdmiCecLocalDevicePlayback.setActiveSource(mPlaybackLogicalAddress,
+                mPlaybackPhysicalAddress, "HdmiCecLocalDevicePlaybackTest");
+        mPowerManager.setInteractive(true);
+        mNativeWrapper.clearResultMessages();
+        mTestLooper.dispatchAll();
+
+        HdmiCecMessage activeSourceFromTv =
+                HdmiCecMessageBuilder.buildActiveSource(ADDR_TV, 0x0000);
+        HdmiCecMessage requestActiveSource =
+                HdmiCecMessageBuilder.buildRequestActiveSource(mPlaybackLogicalAddress);
+        HdmiCecMessage setStreamPathToPlayback = HdmiCecMessageBuilder.buildSetStreamPath(ADDR_TV,
+                mPlaybackPhysicalAddress);
+        HdmiCecMessage activeSourceFromPlayback =
+                HdmiCecMessageBuilder.buildActiveSource(mPlaybackLogicalAddress,
+                        mPlaybackPhysicalAddress);
+        assertThat(mHdmiCecLocalDevicePlayback.handleActiveSource(activeSourceFromTv))
+                .isEqualTo(Constants.HANDLED);
+        assertThat(mHdmiCecLocalDevicePlayback.getActiveSource().logicalAddress).isEqualTo(ADDR_TV);
+        mTestLooper.dispatchAll();
+
+        mTestLooper.moveTimeForward(POPUP_AFTER_ACTIVE_SOURCE_LOST_DELAY_MS);
+        mTestLooper.dispatchAll();
+
+        // RequestActiveSourceAction is triggered.
+        assertThat(mNativeWrapper.getResultMessages()).contains(requestActiveSource);
+        mTestLooper.moveTimeForward(TIMEOUT_MS);
+        mTestLooper.dispatchAll();
+
+        assertThat(mHdmiCecLocalDevicePlayback.handleSetStreamPath(setStreamPathToPlayback))
+                .isEqualTo(Constants.HANDLED);
+        mTestLooper.dispatchAll();
+
+        mTestLooper.moveTimeForward(TIMEOUT_MS);
+        mTestLooper.dispatchAll();
+
+        // Pop-up is not shown since playback device is active source.
+        assertThat(mIsOnActiveSourceLostPopupActive).isFalse();
+        assertThat(mNativeWrapper.getResultMessages().contains(activeSourceFromPlayback)).isTrue();
+        assertThat(mHdmiCecLocalDevicePlayback.getActiveSource().logicalAddress)
+                .isEqualTo(mPlaybackLogicalAddress);
+        assertThat(mHdmiCecLocalDevicePlayback.getActiveSource().physicalAddress)
+                .isEqualTo(mPlaybackPhysicalAddress);
+    }
+
+    @Test
+    public void onActiveSourceLost_requestASNotAnswered_setStreamPathToNonCecInput_dontShowPopup() {
+        int otherPhysicalAddress = mPlaybackPhysicalAddress + 0x0100;
+        mHdmiCecLocalDevicePlayback.mService.getHdmiCecConfig().setStringValue(
+                HdmiControlManager.CEC_SETTING_NAME_POWER_STATE_CHANGE_ON_ACTIVE_SOURCE_LOST,
+                HdmiControlManager.POWER_STATE_CHANGE_ON_ACTIVE_SOURCE_LOST_STANDBY_NOW);
+
+        mHdmiCecLocalDevicePlayback.setActiveSource(mPlaybackLogicalAddress,
+                mPlaybackPhysicalAddress, "HdmiCecLocalDevicePlaybackTest");
+        mPowerManager.setInteractive(true);
+        mNativeWrapper.clearResultMessages();
+        mTestLooper.dispatchAll();
+
+        HdmiCecMessage activeSourceFromTv =
+                HdmiCecMessageBuilder.buildActiveSource(ADDR_TV, 0x0000);
+        HdmiCecMessage requestActiveSource =
+                HdmiCecMessageBuilder.buildRequestActiveSource(mPlaybackLogicalAddress);
+        HdmiCecMessage setStreamPathToOtherInput = HdmiCecMessageBuilder.buildSetStreamPath(ADDR_TV,
+                otherPhysicalAddress);
+        HdmiCecMessage activeSourceFromPlayback =
+                HdmiCecMessageBuilder.buildActiveSource(mPlaybackLogicalAddress,
+                        mPlaybackPhysicalAddress);
+        assertThat(mHdmiCecLocalDevicePlayback.handleActiveSource(activeSourceFromTv))
+                .isEqualTo(Constants.HANDLED);
+        assertThat(mHdmiCecLocalDevicePlayback.getActiveSource().logicalAddress).isEqualTo(ADDR_TV);
+        mTestLooper.dispatchAll();
+
+        mTestLooper.moveTimeForward(POPUP_AFTER_ACTIVE_SOURCE_LOST_DELAY_MS);
+        mTestLooper.dispatchAll();
+
+        // RequestActiveSourceAction is triggered.
+        assertThat(mNativeWrapper.getResultMessages()).contains(requestActiveSource);
+        mTestLooper.moveTimeForward(TIMEOUT_MS);
+        mTestLooper.dispatchAll();
+
+        assertThat(mHdmiCecLocalDevicePlayback.handleSetStreamPath(setStreamPathToOtherInput))
+                .isEqualTo(Constants.HANDLED);
+        mTestLooper.dispatchAll();
+
+        mTestLooper.moveTimeForward(TIMEOUT_MS);
+        mTestLooper.dispatchAll();
+
+        // Pop-up is shown, playback device doesn't assert active source since active path is
+        // switched to a non-CEC device.
+        assertThat(mIsOnActiveSourceLostPopupActive).isTrue();
+        assertThat(mHdmiCecLocalDevicePlayback.getActiveSource().logicalAddress)
+                .isEqualTo(ADDR_INVALID);
+        assertThat(mHdmiCecLocalDevicePlayback.getActiveSource().physicalAddress)
+                .isEqualTo(otherPhysicalAddress);
+    }
+
+    @Test
     public void onActiveSourceLost_interactionWithDut_noStandbyAfterTimeout() {
         mHdmiCecLocalDevicePlayback.mService.getHdmiCecConfig().setStringValue(
                 HdmiControlManager.CEC_SETTING_NAME_POWER_STATE_CHANGE_ON_ACTIVE_SOURCE_LOST,
@@ -2350,7 +2561,7 @@
         mTestLooper.dispatchAll();
 
         // User interacted with the DUT, so the device will not go to standby.
-        skipActiveSourceLostUi(0);
+        skipActiveSourceLostUi(0, true, true);
         assertThat(mIsOnActiveSourceLostPopupActive).isFalse();
         assertThat(mPowerManager.isInteractive()).isTrue();
         assertThat(mNativeWrapper.getResultMessages().contains(activeSourceFromPlayback)).isTrue();
@@ -2387,6 +2598,10 @@
         // Pop-up is triggered.
         mTestLooper.moveTimeForward(POPUP_AFTER_ACTIVE_SOURCE_LOST_DELAY_MS);
         mTestLooper.dispatchAll();
+        // RequestActiveSourceAction is triggered and TV confirms active source.
+        mNativeWrapper.onCecMessage(activeSourceFromTv);
+        mTestLooper.dispatchAll();
+
         assertThat(mIsOnActiveSourceLostPopupActive).isTrue();
 
         assertThat(mHdmiCecLocalDevicePlayback.handleSetStreamPath(setStreamPathToPlayback))
@@ -2407,6 +2622,8 @@
 
     @Test
     public void onActiveSourceLost_incomingRoutingChangeToDut_noStandbyAfterTimeout() {
+        int otherPlaybackLogicalAddress = mPlaybackLogicalAddress == Constants.ADDR_PLAYBACK_2
+                ? Constants.ADDR_PLAYBACK_1 : Constants.ADDR_PLAYBACK_2;
         mHdmiCecLocalDevicePlayback.mService.getHdmiCecConfig().setStringValue(
                 HdmiControlManager.CEC_SETTING_NAME_POWER_STATE_CHANGE_ON_ACTIVE_SOURCE_LOST,
                 HdmiControlManager.POWER_STATE_CHANGE_ON_ACTIVE_SOURCE_LOST_STANDBY_NOW);
@@ -2420,18 +2637,21 @@
         mNativeWrapper.clearResultMessages();
         mTestLooper.dispatchAll();
 
-        HdmiCecMessage activeSourceFromTv =
-                HdmiCecMessageBuilder.buildActiveSource(ADDR_TV, 0x0000);
+        HdmiCecMessage activeSourceFromOtherPlayback =
+                HdmiCecMessageBuilder.buildActiveSource(otherPlaybackLogicalAddress,
+                        mPlaybackPhysicalAddress + 0x0100);
         HdmiCecMessage activeSourceFromPlayback =
                 HdmiCecMessageBuilder.buildActiveSource(mPlaybackLogicalAddress,
                         mPlaybackPhysicalAddress);
         HdmiCecMessage routingChangeToPlayback =
-                HdmiCecMessageBuilder.buildRoutingChange(ADDR_TV, 0x0000,
+                HdmiCecMessageBuilder.buildRoutingChange(ADDR_TV,
+                        mPlaybackPhysicalAddress + 0x0100,
                         mPlaybackPhysicalAddress);
 
-        assertThat(mHdmiCecLocalDevicePlayback.handleActiveSource(activeSourceFromTv))
+        assertThat(mHdmiCecLocalDevicePlayback.handleActiveSource(activeSourceFromOtherPlayback))
                 .isEqualTo(Constants.HANDLED);
-        assertThat(mHdmiCecLocalDevicePlayback.getActiveSource().logicalAddress).isEqualTo(ADDR_TV);
+        assertThat(mHdmiCecLocalDevicePlayback.getActiveSource().logicalAddress).isEqualTo(
+                otherPlaybackLogicalAddress);
         mTestLooper.dispatchAll();
 
         // Pop-up is triggered.
@@ -2600,13 +2820,30 @@
         assertThat(mPowerManager.isInteractive()).isFalse();
     }
 
-    private void skipActiveSourceLostUi(long idleDuration) {
+    private void skipActiveSourceLostUi(long idleDuration, boolean activeSourceLostToTv,
+            boolean tvAnswersRequest) {
         mTestLooper.moveTimeForward(POPUP_AFTER_ACTIVE_SOURCE_LOST_DELAY_MS);
         mTestLooper.dispatchAll();
+        if (activeSourceLostToTv) {
+            // RequestActiveSourceAction is triggered.
+            mTestLooper.moveTimeForward(TIMEOUT_MS);
+            mTestLooper.dispatchAll();
+            if (tvAnswersRequest) {
+                HdmiCecMessage activeSource = HdmiCecMessageBuilder.buildActiveSource(ADDR_TV,
+                        0x0000);
+                mNativeWrapper.onCecMessage(activeSource);
+                mTestLooper.dispatchAll();
+            } else {
+                mTestLooper.moveTimeForward(TIMEOUT_MS);
+                mTestLooper.dispatchAll();
+                assertThat(mIsOnActiveSourceLostPopupActive).isFalse();
+                return;
+            }
+        }
         assertThat(mIsOnActiveSourceLostPopupActive).isTrue();
 
         mPowerManagerInternal.setIdleDuration(idleDuration);
         mTestLooper.moveTimeForward(STANDBY_AFTER_ACTIVE_SOURCE_LOST_DELAY_MS);
         mTestLooper.dispatchAll();
     }
-}
+}
\ No newline at end of file
diff --git a/services/tests/servicestests/src/com/android/server/hdmi/HdmiCecLocalDeviceTvTest.java b/services/tests/servicestests/src/com/android/server/hdmi/HdmiCecLocalDeviceTvTest.java
index 935c8b8..51276a4 100644
--- a/services/tests/servicestests/src/com/android/server/hdmi/HdmiCecLocalDeviceTvTest.java
+++ b/services/tests/servicestests/src/com/android/server/hdmi/HdmiCecLocalDeviceTvTest.java
@@ -2234,6 +2234,25 @@
     }
 
     @Test
+    public void handleReportPhysicalAddress_samePathAsActiveSource_differentLA_newActiveSource() {
+        // This scenario can be reproduced if active source is hotplugged out and replaced with
+        // another device that might have another LA.
+        int physicalAddress = 0x1000;
+        mHdmiControlService.setActiveSource(Constants.ADDR_PLAYBACK_1, physicalAddress,
+                "HdmiControlServiceTest");
+        mHdmiCecLocalDeviceTv.setActivePath(physicalAddress);
+        HdmiCecMessage reportPhysicalAddressFromPlayback2 =
+                HdmiCecMessageBuilder.buildReportPhysicalAddressCommand(ADDR_PLAYBACK_2,
+                        physicalAddress, HdmiDeviceInfo.DEVICE_PLAYBACK);
+        HdmiCecMessage setStreamPath = HdmiCecMessageBuilder.buildSetStreamPath(ADDR_TV,
+                physicalAddress);
+        mNativeWrapper.onCecMessage(reportPhysicalAddressFromPlayback2);
+        mTestLooper.dispatchAll();
+
+        assertThat(mNativeWrapper.getResultMessages()).contains(setStreamPath);
+    }
+
+    @Test
     public void onOneTouchPlay_wakeUp_addCecDevice() {
         assertThat(mHdmiControlService.getHdmiCecNetwork().getDeviceInfoList(false))
                 .isEmpty();
diff --git a/services/tests/servicestests/src/com/android/server/integrity/parser/RuleIndexingControllerTest.java b/services/tests/servicestests/src/com/android/server/integrity/parser/RuleIndexingControllerTest.java
deleted file mode 100644
index 370bd80..0000000
--- a/services/tests/servicestests/src/com/android/server/integrity/parser/RuleIndexingControllerTest.java
+++ /dev/null
@@ -1,216 +0,0 @@
-/*
- * Copyright (C) 2020 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- *      http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-package com.android.server.integrity.parser;
-
-import static com.android.server.integrity.model.ComponentBitSize.VALUE_SIZE_BITS;
-import static com.android.server.integrity.model.IndexingFileConstants.END_INDEXING_KEY;
-import static com.android.server.integrity.model.IndexingFileConstants.START_INDEXING_KEY;
-import static com.android.server.integrity.utils.TestUtils.getBits;
-import static com.android.server.integrity.utils.TestUtils.getBytes;
-import static com.android.server.integrity.utils.TestUtils.getValueBits;
-
-import static com.google.common.truth.Truth.assertThat;
-
-import static org.testng.Assert.assertThrows;
-
-import android.content.integrity.AppInstallMetadata;
-
-import org.junit.Test;
-import org.junit.runner.RunWith;
-import org.junit.runners.JUnit4;
-
-import java.io.ByteArrayInputStream;
-import java.io.IOException;
-import java.io.InputStream;
-import java.nio.ByteBuffer;
-import java.util.Arrays;
-import java.util.Collections;
-import java.util.List;
-
-@RunWith(JUnit4.class)
-public class RuleIndexingControllerTest {
-
-    @Test
-    public void verifyIndexRangeSearchIsCorrect() throws IOException {
-        InputStream inputStream = obtainDefaultIndexingMapForTest();
-
-        RuleIndexingController indexingController = new RuleIndexingController(inputStream);
-
-        AppInstallMetadata appInstallMetadata =
-                new AppInstallMetadata.Builder()
-                        .setPackageName("ddd")
-                        .setAppCertificates(Collections.singletonList("777"))
-                        .setAppCertificateLineage(Collections.singletonList("777"))
-                        .build();
-
-        List<RuleIndexRange> resultingIndexes =
-                indexingController.identifyRulesToEvaluate(appInstallMetadata);
-
-        assertThat(resultingIndexes)
-                .containsExactly(
-                        new RuleIndexRange(200, 300),
-                        new RuleIndexRange(700, 800),
-                        new RuleIndexRange(900, 945));
-    }
-
-    @Test
-    public void verifyIndexRangeSearchIsCorrect_multipleAppCertificates() throws IOException {
-        InputStream inputStream = obtainDefaultIndexingMapForTest();
-
-        RuleIndexingController indexingController = new RuleIndexingController(inputStream);
-
-        AppInstallMetadata appInstallMetadata =
-                new AppInstallMetadata.Builder()
-                        .setPackageName("ddd")
-                        .setAppCertificates(Arrays.asList("777", "999"))
-                        .setAppCertificateLineage(Arrays.asList("777", "999"))
-                        .build();
-
-        List<RuleIndexRange> resultingIndexes =
-                indexingController.identifyRulesToEvaluate(appInstallMetadata);
-
-        assertThat(resultingIndexes)
-                .containsExactly(
-                        new RuleIndexRange(200, 300),
-                        new RuleIndexRange(700, 800),
-                        new RuleIndexRange(800, 900),
-                        new RuleIndexRange(900, 945));
-    }
-
-    @Test
-    public void verifyIndexRangeSearchIsCorrect_keysInFirstAndLastBlock() throws IOException {
-        InputStream inputStream = obtainDefaultIndexingMapForTest();
-
-        RuleIndexingController indexingController = new RuleIndexingController(inputStream);
-
-        AppInstallMetadata appInstallMetadata =
-                new AppInstallMetadata.Builder()
-                        .setPackageName("bbb")
-                        .setAppCertificates(Collections.singletonList("999"))
-                        .setAppCertificateLineage(Collections.singletonList("999"))
-                        .build();
-
-        List<RuleIndexRange> resultingIndexes =
-                indexingController.identifyRulesToEvaluate(appInstallMetadata);
-
-        assertThat(resultingIndexes)
-                .containsExactly(
-                        new RuleIndexRange(100, 200),
-                        new RuleIndexRange(800, 900),
-                        new RuleIndexRange(900, 945));
-    }
-
-    @Test
-    public void verifyIndexRangeSearchIsCorrect_keysMatchWithValues() throws IOException {
-        InputStream inputStream = obtainDefaultIndexingMapForTest();
-
-        RuleIndexingController indexingController = new RuleIndexingController(inputStream);
-
-        AppInstallMetadata appInstallMetadata =
-                new AppInstallMetadata.Builder()
-                        .setPackageName("ccc")
-                        .setAppCertificates(Collections.singletonList("444"))
-                        .setAppCertificateLineage(Collections.singletonList("444"))
-                        .build();
-
-        List<RuleIndexRange> resultingIndexes =
-                indexingController.identifyRulesToEvaluate(appInstallMetadata);
-
-        assertThat(resultingIndexes)
-                .containsExactly(
-                        new RuleIndexRange(200, 300),
-                        new RuleIndexRange(700, 800),
-                        new RuleIndexRange(900, 945));
-    }
-
-    @Test
-    public void verifyIndexRangeSearchIsCorrect_noIndexesAvailable() throws IOException {
-        byte[] stringBytes =
-                getBytes(
-                        getKeyValueString(START_INDEXING_KEY, 100)
-                                + getKeyValueString(END_INDEXING_KEY, 500)
-                                + getKeyValueString(START_INDEXING_KEY, 500)
-                                + getKeyValueString(END_INDEXING_KEY, 900)
-                                + getKeyValueString(START_INDEXING_KEY, 900)
-                                + getKeyValueString(END_INDEXING_KEY, 945));
-        ByteBuffer rule = ByteBuffer.allocate(stringBytes.length);
-        rule.put(stringBytes);
-        InputStream inputStream = new ByteArrayInputStream(rule.array());
-
-        RuleIndexingController indexingController = new RuleIndexingController(inputStream);
-
-        AppInstallMetadata appInstallMetadata =
-                new AppInstallMetadata.Builder()
-                        .setPackageName("ccc")
-                        .setAppCertificates(Collections.singletonList("444"))
-                        .setAppCertificateLineage(Collections.singletonList("444"))
-                        .build();
-
-        List<RuleIndexRange> resultingIndexes =
-                indexingController.identifyRulesToEvaluate(appInstallMetadata);
-
-        assertThat(resultingIndexes)
-                .containsExactly(
-                        new RuleIndexRange(100, 500),
-                        new RuleIndexRange(500, 900),
-                        new RuleIndexRange(900, 945));
-    }
-
-    @Test
-    public void verifyIndexingFileIsCorrupt() throws IOException {
-        byte[] stringBytes =
-                getBytes(
-                        getKeyValueString(START_INDEXING_KEY, 100)
-                                + getKeyValueString("ccc", 200)
-                                + getKeyValueString(END_INDEXING_KEY, 300)
-                                + getKeyValueString(END_INDEXING_KEY, 900));
-        ByteBuffer rule = ByteBuffer.allocate(stringBytes.length);
-        rule.put(stringBytes);
-        InputStream inputStream = new ByteArrayInputStream(rule.array());
-
-        assertThrows(IllegalStateException.class,
-                () -> new RuleIndexingController(inputStream));
-    }
-
-    private static InputStream obtainDefaultIndexingMapForTest() {
-        byte[] stringBytes =
-                getBytes(
-                        getKeyValueString(START_INDEXING_KEY, 100)
-                                + getKeyValueString("ccc", 200)
-                                + getKeyValueString("eee", 300)
-                                + getKeyValueString("hhh", 400)
-                                + getKeyValueString(END_INDEXING_KEY, 500)
-                                + getKeyValueString(START_INDEXING_KEY, 500)
-                                + getKeyValueString("111", 600)
-                                + getKeyValueString("444", 700)
-                                + getKeyValueString("888", 800)
-                                + getKeyValueString(END_INDEXING_KEY, 900)
-                                + getKeyValueString(START_INDEXING_KEY, 900)
-                                + getKeyValueString(END_INDEXING_KEY, 945));
-        ByteBuffer rule = ByteBuffer.allocate(stringBytes.length);
-        rule.put(stringBytes);
-        return new ByteArrayInputStream(rule.array());
-    }
-
-    private static String getKeyValueString(String key, int value) {
-        String isNotHashed = "0";
-        return isNotHashed
-                + getBits(key.length(), VALUE_SIZE_BITS)
-                + getValueBits(key)
-                + getBits(value, /* numOfBits= */ 32);
-    }
-}
diff --git a/services/tests/uiservicestests/src/com/android/server/notification/NotificationManagerServiceTest.java b/services/tests/uiservicestests/src/com/android/server/notification/NotificationManagerServiceTest.java
index e845d80..dec7f09 100644
--- a/services/tests/uiservicestests/src/com/android/server/notification/NotificationManagerServiceTest.java
+++ b/services/tests/uiservicestests/src/com/android/server/notification/NotificationManagerServiceTest.java
@@ -45,6 +45,9 @@
 import static android.app.Notification.VISIBILITY_PRIVATE;
 import static android.app.NotificationChannel.NEWS_ID;
 import static android.app.NotificationChannel.DEFAULT_CHANNEL_ID;
+import static android.app.NotificationChannel.PROMOTIONS_ID;
+import static android.app.NotificationChannel.RECS_ID;
+import static android.app.NotificationChannel.SOCIAL_MEDIA_ID;
 import static android.app.NotificationChannel.USER_LOCKED_ALLOW_BUBBLE;
 import static android.app.NotificationManager.ACTION_INTERRUPTION_FILTER_CHANGED;
 import static android.app.NotificationManager.BUBBLE_PREFERENCE_ALL;
@@ -98,7 +101,10 @@
 import static android.service.notification.Adjustment.KEY_TEXT_REPLIES;
 import static android.service.notification.Adjustment.KEY_TYPE;
 import static android.service.notification.Adjustment.KEY_USER_SENTIMENT;
+import static android.service.notification.Adjustment.TYPE_CONTENT_RECOMMENDATION;
 import static android.service.notification.Adjustment.TYPE_NEWS;
+import static android.service.notification.Adjustment.TYPE_PROMOTION;
+import static android.service.notification.Adjustment.TYPE_SOCIAL_MEDIA;
 import static android.service.notification.Condition.SOURCE_CONTEXT;
 import static android.service.notification.Condition.SOURCE_USER_ACTION;
 import static android.service.notification.Condition.STATE_TRUE;
@@ -16767,6 +16773,24 @@
         r.applyAdjustments();
 
         assertThat(r.getChannel().getId()).isEqualTo(NEWS_ID);
+
+        signals.putInt(KEY_TYPE, TYPE_PROMOTION);
+        mBinderService.applyAdjustmentFromAssistant(null, adjustment);
+        waitForIdle();
+        r.applyAdjustments();
+        assertThat(r.getChannel().getId()).isEqualTo(PROMOTIONS_ID);
+
+        signals.putInt(KEY_TYPE, TYPE_SOCIAL_MEDIA);
+        mBinderService.applyAdjustmentFromAssistant(null, adjustment);
+        waitForIdle();
+        r.applyAdjustments();
+        assertThat(r.getChannel().getId()).isEqualTo(SOCIAL_MEDIA_ID);
+
+        signals.putInt(KEY_TYPE, TYPE_CONTENT_RECOMMENDATION);
+        mBinderService.applyAdjustmentFromAssistant(null, adjustment);
+        waitForIdle();
+        r.applyAdjustments();
+        assertThat(r.getChannel().getId()).isEqualTo(RECS_ID);
     }
 
     @Test
@@ -17066,7 +17090,7 @@
     @Test
     @EnableFlags(FLAG_NOTIFICATION_CLASSIFICATION)
     public void testAppCannotUseReservedBundleChannels() throws Exception {
-        mBinderService.getBubblePreferenceForPackage(mPkg, mUid);
+        mService.mPreferencesHelper.createReservedChannel(mPkg, mUid, TYPE_NEWS);
         NotificationChannel news = mBinderService.getNotificationChannel(
                 mPkg, mContext.getUserId(), mPkg, NEWS_ID);
         assertThat(news).isNotNull();
diff --git a/services/tests/uiservicestests/src/com/android/server/notification/PreferencesHelperTest.java b/services/tests/uiservicestests/src/com/android/server/notification/PreferencesHelperTest.java
index 36fa1b8..1a1da0f 100644
--- a/services/tests/uiservicestests/src/com/android/server/notification/PreferencesHelperTest.java
+++ b/services/tests/uiservicestests/src/com/android/server/notification/PreferencesHelperTest.java
@@ -54,6 +54,11 @@
 import static android.os.UserHandle.USER_ALL;
 import static android.os.UserHandle.USER_SYSTEM;
 import static android.platform.test.flag.junit.SetFlagsRule.DefaultInitValueType.DEVICE_DEFAULT;
+import static android.service.notification.Adjustment.TYPE_CONTENT_RECOMMENDATION;
+import static android.service.notification.Adjustment.TYPE_NEWS;
+import static android.service.notification.Adjustment.TYPE_OTHER;
+import static android.service.notification.Adjustment.TYPE_PROMOTION;
+import static android.service.notification.Adjustment.TYPE_SOCIAL_MEDIA;
 import static android.service.notification.Flags.FLAG_NOTIFICATION_CLASSIFICATION;
 import static android.service.notification.Flags.notificationClassification;
 
@@ -640,6 +645,7 @@
 
         NotificationChannel updateNews = null;
         if (notificationClassification()) {
+            mHelper.createReservedChannel(PKG_N_MR1, UID_N_MR1, TYPE_NEWS);
             // change one of the reserved bundle channels to ensure changes are persisted across
             // boot
             updateNews = mHelper.getNotificationChannel(
@@ -1210,22 +1216,9 @@
                 + "app_user_locked_fields=\"0\" sent_invalid_msg=\"false\" "
                 + "sent_valid_msg=\"false\" user_demote_msg_app=\"false\" sent_valid_bubble"
                 + "=\"false\" uid=\"10002\">\n"
-                + (notificationClassification() ? "<channel id=\"android.app.social\" "
-                + "name=\"Social\" importance=\"2\" "
-                + "sound=\"content://settings/system/notification_sound\" "
-                + "usage=\"5\" content_type=\"4\" flags=\"0\" show_badge=\"true\" />\n" : "")
                 + "<channel id=\"id\" name=\"name\" importance=\"2\" "
                 + "sound=\"content://settings/system/notification_sound\" usage=\"5\" "
                 + "content_type=\"4\" flags=\"0\" show_badge=\"true\" orig_imp=\"2\" />\n"
-                + (notificationClassification() ? "<channel id=\"android.app.news\" name=\"News\" "
-                + "importance=\"2\" sound=\"content://settings/system/notification_sound\" "
-                + "usage=\"5\" content_type=\"4\" flags=\"0\" show_badge=\"true\" />\n"
-                + "<channel id=\"android.app.recs\" name=\"Recommendations\" importance=\"2\" "
-                + "sound=\"content://settings/system/notification_sound\" usage=\"5\" "
-                + "content_type=\"4\" flags=\"0\" show_badge=\"true\" />\n"
-                + "<channel id=\"android.app.promotions\" name=\"Promotions\" importance=\"2\" "
-                + "sound=\"content://settings/system/notification_sound\" usage=\"5\" "
-                + "content_type=\"4\" flags=\"0\" show_badge=\"true\" />\n" : "")
                 + "</package>\n"
                 + "<package name=\"com.example.n_mr1\" show_badge=\"true\" "
                 + "app_user_locked_fields=\"0\" sent_invalid_msg=\"false\" "
@@ -1233,10 +1226,6 @@
                 + "=\"false\" uid=\"10001\">\n"
                 + "<channelGroup id=\"1\" name=\"bye\" blocked=\"false\" locked=\"0\" />\n"
                 + "<channelGroup id=\"2\" name=\"hello\" blocked=\"false\" locked=\"0\" />\n"
-                + (notificationClassification() ? "<channel id=\"android.app.social\" "
-                + "name=\"Social\" importance=\"2\" "
-                + "sound=\"content://settings/system/notification_sound\" "
-                + "usage=\"5\" content_type=\"4\" flags=\"0\" show_badge=\"true\" />\n" : "")
                 + "<channel id=\"id1\" name=\"name1\" importance=\"4\" show_badge=\"true\" "
                 + "orig_imp=\"4\" />\n"
                 + "<channel id=\"id2\" name=\"name2\" desc=\"descriptions for all\" "
@@ -1246,15 +1235,6 @@
                 + "sound=\"content://settings/system/notification_sound\" usage=\"5\" "
                 + "content_type=\"4\" flags=\"0\" vibration_enabled=\"true\" show_badge=\"true\" "
                 + "orig_imp=\"4\" />\n"
-                + (notificationClassification() ? "<channel id=\"android.app.news\" name=\"News\" "
-                + "importance=\"2\" sound=\"content://settings/system/notification_sound\" "
-                + "usage=\"5\" content_type=\"4\" flags=\"0\" show_badge=\"true\" />\n"
-                + "<channel id=\"android.app.recs\" name=\"Recommendations\" importance=\"2\" "
-                + "sound=\"content://settings/system/notification_sound\" usage=\"5\" "
-                + "content_type=\"4\" flags=\"0\" show_badge=\"true\" />\n"
-                + "<channel id=\"android.app.promotions\" name=\"Promotions\" importance=\"2\" "
-                + "sound=\"content://settings/system/notification_sound\" usage=\"5\" "
-                + "content_type=\"4\" flags=\"0\" show_badge=\"true\" />\n" : "")
                 + "<channel id=\"miscellaneous\" name=\"Uncategorized\" "
                 + "sound=\"content://settings/system/notification_sound\" usage=\"5\" "
                 + "content_type=\"4\" flags=\"0\" show_badge=\"true\" />\n"
@@ -1321,22 +1301,9 @@
                 + "app_user_locked_fields=\"0\" sent_invalid_msg=\"false\" "
                 + "sent_valid_msg=\"false\" user_demote_msg_app=\"false\" sent_valid_bubble"
                 + "=\"false\">\n"
-                + (notificationClassification() ? "<channel id=\"android.app.social\" "
-                + "name=\"Social\" importance=\"2\" "
-                + "sound=\"content://settings/system/notification_sound\" "
-                + "usage=\"5\" content_type=\"4\" flags=\"0\" show_badge=\"true\" />\n" : "")
                 + "<channel id=\"id\" name=\"name\" importance=\"2\" "
                 + "sound=\"content://settings/system/notification_sound\" usage=\"5\" "
                 + "content_type=\"4\" flags=\"0\" show_badge=\"true\" orig_imp=\"2\" />\n"
-                + (notificationClassification() ? "<channel id=\"android.app.news\" name=\"News\" "
-                + "importance=\"2\" sound=\"content://settings/system/notification_sound\" "
-                + "usage=\"5\" content_type=\"4\" flags=\"0\" show_badge=\"true\" />\n"
-                + "<channel id=\"android.app.recs\" name=\"Recommendations\" importance=\"2\" "
-                + "sound=\"content://settings/system/notification_sound\" usage=\"5\" "
-                + "content_type=\"4\" flags=\"0\" show_badge=\"true\" />\n"
-                + "<channel id=\"android.app.promotions\" name=\"Promotions\" importance=\"2\" "
-                + "sound=\"content://settings/system/notification_sound\" usage=\"5\" "
-                + "content_type=\"4\" flags=\"0\" show_badge=\"true\" />\n" : "")
                 + "</package>\n"
                 // Importance default because on in permission helper
                 + "<package name=\"com.example.n_mr1\" importance=\"3\" show_badge=\"true\" "
@@ -1345,10 +1312,6 @@
                 + "=\"false\">\n"
                 + "<channelGroup id=\"1\" name=\"bye\" blocked=\"false\" locked=\"0\" />\n"
                 + "<channelGroup id=\"2\" name=\"hello\" blocked=\"false\" locked=\"0\" />\n"
-                + (notificationClassification() ? "<channel id=\"android.app.social\" "
-                + "name=\"Social\" importance=\"2\" "
-                + "sound=\"content://settings/system/notification_sound\" "
-                + "usage=\"5\" content_type=\"4\" flags=\"0\" show_badge=\"true\" />\n" : "")
                 + "<channel id=\"id1\" name=\"name1\" importance=\"4\" show_badge=\"true\" "
                 + "orig_imp=\"4\" />\n"
                 + "<channel id=\"id2\" name=\"name2\" desc=\"descriptions for all\" "
@@ -1358,15 +1321,6 @@
                 + "sound=\"content://settings/system/notification_sound\" usage=\"5\" "
                 + "content_type=\"4\" flags=\"0\" vibration_enabled=\"true\" show_badge=\"true\" "
                 + "orig_imp=\"4\" />\n"
-                + (notificationClassification() ? "<channel id=\"android.app.news\" name=\"News\" "
-                + "importance=\"2\" sound=\"content://settings/system/notification_sound\" "
-                + "usage=\"5\" content_type=\"4\" flags=\"0\" show_badge=\"true\" />\n"
-                + "<channel id=\"android.app.recs\" name=\"Recommendations\" importance=\"2\" "
-                + "sound=\"content://settings/system/notification_sound\" usage=\"5\" "
-                + "content_type=\"4\" flags=\"0\" show_badge=\"true\" />\n"
-                + "<channel id=\"android.app.promotions\" name=\"Promotions\" importance=\"2\" "
-                + "sound=\"content://settings/system/notification_sound\" usage=\"5\" "
-                + "content_type=\"4\" flags=\"0\" show_badge=\"true\" />\n" : "")
                 + "<channel id=\"miscellaneous\" name=\"Uncategorized\" "
                 + "sound=\"content://settings/system/notification_sound\" usage=\"5\" "
                 + "content_type=\"4\" flags=\"0\" show_badge=\"true\" />\n"
@@ -1433,22 +1387,9 @@
                 + "app_user_locked_fields=\"0\" sent_invalid_msg=\"false\" "
                 + "sent_valid_msg=\"false\" user_demote_msg_app=\"false\" sent_valid_bubble"
                 + "=\"false\">\n"
-                + (notificationClassification() ? "<channel id=\"android.app.social\" "
-                + "name=\"Social\" importance=\"2\" "
-                + "sound=\"content://settings/system/notification_sound\" usage=\"5\" "
-                + "content_type=\"4\" flags=\"0\" show_badge=\"true\" />\n" : "")
                 + "<channel id=\"id\" name=\"name\" importance=\"2\" "
                 + "sound=\"content://settings/system/notification_sound\" usage=\"5\" "
                 + "content_type=\"4\" flags=\"0\" show_badge=\"true\" orig_imp=\"2\" />\n"
-                + (notificationClassification() ? "<channel id=\"android.app.news\" name=\"News\" "
-                + "importance=\"2\" sound=\"content://settings/system/notification_sound\" "
-                + "usage=\"5\" content_type=\"4\" flags=\"0\" show_badge=\"true\" />\n"
-                + "<channel id=\"android.app.recs\" name=\"Recommendations\" importance=\"2\" "
-                + "sound=\"content://settings/system/notification_sound\" usage=\"5\" "
-                + "content_type=\"4\" flags=\"0\" show_badge=\"true\" />\n"
-                + "<channel id=\"android.app.promotions\" name=\"Promotions\" importance=\"2\" "
-                + "sound=\"content://settings/system/notification_sound\" usage=\"5\" "
-                + "content_type=\"4\" flags=\"0\" show_badge=\"true\" />\n" : "")
                 + "</package>\n"
                 // Importance 0 because missing from permission helper
                 + "<package name=\"com.example.n_mr1\" importance=\"0\" show_badge=\"true\" "
@@ -1457,10 +1398,6 @@
                 + "=\"false\">\n"
                 + "<channelGroup id=\"1\" name=\"bye\" blocked=\"false\" locked=\"0\" />\n"
                 + "<channelGroup id=\"2\" name=\"hello\" blocked=\"false\" locked=\"0\" />\n"
-                + (notificationClassification() ? "<channel id=\"android.app.social\" "
-                + "name=\"Social\" importance=\"2\" "
-                + "sound=\"content://settings/system/notification_sound\" usage=\"5\" "
-                + "content_type=\"4\" flags=\"0\" show_badge=\"true\" />\n" : "")
                 + "<channel id=\"id1\" name=\"name1\" importance=\"4\" show_badge=\"true\" "
                 + "orig_imp=\"4\" />\n"
                 + "<channel id=\"id2\" name=\"name2\" desc=\"descriptions for all\" "
@@ -1470,15 +1407,6 @@
                 + "sound=\"content://settings/system/notification_sound\" usage=\"5\" "
                 + "content_type=\"4\" flags=\"0\" vibration_enabled=\"true\" show_badge=\"true\" "
                 + "orig_imp=\"4\" />\n"
-                + (notificationClassification() ? "<channel id=\"android.app.news\" name=\"News\" "
-                + "importance=\"2\" sound=\"content://settings/system/notification_sound\" "
-                + "usage=\"5\" content_type=\"4\" flags=\"0\" show_badge=\"true\" />\n"
-                + "<channel id=\"android.app.recs\" name=\"Recommendations\" importance=\"2\" "
-                + "sound=\"content://settings/system/notification_sound\" usage=\"5\" "
-                + "content_type=\"4\" flags=\"0\" show_badge=\"true\" />\n"
-                + "<channel id=\"android.app.promotions\" name=\"Promotions\" importance=\"2\" "
-                + "sound=\"content://settings/system/notification_sound\" usage=\"5\" "
-                + "content_type=\"4\" flags=\"0\" show_badge=\"true\" />\n" : "")
                 + "<channel id=\"miscellaneous\" name=\"Uncategorized\" "
                 + "sound=\"content://settings/system/notification_sound\" usage=\"5\" "
                 + "content_type=\"4\" flags=\"0\" show_badge=\"true\" />\n"
@@ -2183,10 +2111,10 @@
     }
 
     @Test
-    @DisableFlags(FLAG_NOTIFICATION_CLASSIFICATION)
     public void testUpdate_preUpgrade_updatesAppFields() throws Exception {
         assertTrue(mHelper.canShowBadge(PKG_N_MR1, UID_N_MR1));
-        assertEquals(Notification.PRIORITY_DEFAULT, mHelper.getPackagePriority(PKG_N_MR1, UID_N_MR1));
+        assertEquals(Notification.PRIORITY_DEFAULT,
+                mHelper.getPackagePriority(PKG_N_MR1, UID_N_MR1));
         assertEquals(VISIBILITY_NO_OVERRIDE,
                 mHelper.getPackageVisibility(PKG_N_MR1, UID_N_MR1));
 
@@ -2549,7 +2477,7 @@
         List<NotificationChannel> channels =
                 mHelper.getNotificationChannels(PKG_N_MR1, UID_N_MR1, false, true).getList();
         // Default channel + non-deleted channel + system defaults
-        assertEquals(notificationClassification() ? 6 : 2, channels.size());
+        assertEquals(2, channels.size());
         for (NotificationChannel nc : channels) {
             if (channel2.getId().equals(nc.getId())) {
                 compareChannels(channel2, nc);
@@ -2559,7 +2487,7 @@
         // Returns deleted channels too
         channels = mHelper.getNotificationChannels(PKG_N_MR1, UID_N_MR1, true, true).getList();
         // Includes system channel(s)
-        assertEquals(notificationClassification() ? 7 : 3, channels.size());
+        assertEquals(3, channels.size());
         for (NotificationChannel nc : channels) {
             if (channel2.getId().equals(nc.getId())) {
                 compareChannels(channelMap.get(nc.getId()), nc);
@@ -3036,7 +2964,6 @@
     }
 
     @Test
-    @DisableFlags(FLAG_NOTIFICATION_CLASSIFICATION)
     public void testOnlyHasDefaultChannel() throws Exception {
         assertTrue(mHelper.onlyHasDefaultChannel(PKG_N_MR1, UID_N_MR1));
         assertFalse(mHelper.onlyHasDefaultChannel(PKG_O, UID_O));
@@ -3047,6 +2974,18 @@
     }
 
     @Test
+    @EnableFlags(FLAG_NOTIFICATION_CLASSIFICATION)
+    public void testOnlyHasDefaultChannel_bundleExists() throws Exception {
+        mHelper.createReservedChannel(PKG_N_MR1, UID_N_MR1, TYPE_NEWS);
+        assertTrue(mHelper.onlyHasDefaultChannel(PKG_N_MR1, UID_N_MR1));
+        assertFalse(mHelper.onlyHasDefaultChannel(PKG_O, UID_O));
+
+        mHelper.createNotificationChannel(PKG_N_MR1, UID_N_MR1, getChannel(), true, false,
+                UID_N_MR1, false);
+        assertFalse(mHelper.onlyHasDefaultChannel(PKG_N_MR1, UID_N_MR1));
+    }
+
+    @Test
     public void testCreateDeletedChannel() throws Exception {
         long[] vibration = new long[]{100, 67, 145, 156};
         NotificationChannel channel =
@@ -3315,7 +3254,7 @@
 
         // user 0 records remain
         for (int i = 0; i < user0Uids.length; i++) {
-            assertEquals(notificationClassification() ? 5 : 1,
+            assertEquals(1,
                     mHelper.getNotificationChannels(PKG_N_MR1, user0Uids[i], false, true)
                             .getList().size());
         }
@@ -3346,7 +3285,7 @@
 
         assertFalse(mHelper.onPackagesChanged(false, USER_SYSTEM,
                 new String[]{PKG_N_MR1}, new int[]{UID_N_MR1}));
-        assertEquals(notificationClassification() ? 6 : 2,
+        assertEquals(2,
                 mHelper.getNotificationChannels(PKG_N_MR1, UID_N_MR1, false, true)
                         .getList().size());
     }
@@ -3420,7 +3359,7 @@
     @Test
     public void testRecordDefaults() throws Exception {
         assertEquals(true, mHelper.canShowBadge(PKG_N_MR1, UID_N_MR1));
-        assertEquals(notificationClassification() ? 5 : 1,
+        assertEquals(1,
                 mHelper.getNotificationChannels(PKG_N_MR1, UID_N_MR1, false, true)
                         .getList().size());
     }
@@ -3659,9 +3598,6 @@
                         new NotificationChannel("" + j, "a", IMPORTANCE_HIGH), true, false,
                         UID_N_MR1, false);
             }
-            if (notificationClassification()) {
-                numChannels += 4;
-            }
             expectedChannels.put(pkgName, numChannels);
         }
 
@@ -4883,10 +4819,6 @@
     @Test
     public void testTooManyChannels() {
         int numToCreate = NOTIFICATION_CHANNEL_COUNT_LIMIT;
-        if (notificationClassification()) {
-            // reserved channels lower limit
-            numToCreate -= 4;
-        }
         for (int i = 0; i < numToCreate; i++) {
             NotificationChannel channel = new NotificationChannel(String.valueOf(i),
                     String.valueOf(i), NotificationManager.IMPORTANCE_HIGH);
@@ -4907,10 +4839,6 @@
     @Test
     public void testTooManyChannels_xml() throws Exception {
         int numToCreate = NOTIFICATION_CHANNEL_COUNT_LIMIT;
-        if (notificationClassification()) {
-            // reserved channels lower limit
-            numToCreate -= 4;
-        }
         String extraChannel = "EXTRA";
         String extraChannel1 = "EXTRA1";
 
@@ -5928,9 +5856,7 @@
         ArrayList<StatsEvent> events = new ArrayList<>();
         mHelper.pullPackageChannelPreferencesStats(events);
 
-        assertEquals("expected number of events",
-                notificationClassification() ? 7 : 3,
-                events.size());
+        assertEquals("expected number of events", 3, events.size());
         for (StatsEvent ev : events) {
             // all of these events should be of PackageNotificationChannelPreferences type,
             // and therefore we expect the atom to have this field.
@@ -5971,17 +5897,11 @@
         mHelper.createNotificationChannel(PKG_O, UID_O, channelC, true, false, UID_O, false);
 
         List<String> channels = new LinkedList<>(Arrays.asList("a", "b", "c"));
-        if (notificationClassification()) {
-            channels.add(NEWS_ID);
-            channels.add(PROMOTIONS_ID);
-            channels.add(SOCIAL_MEDIA_ID);
-            channels.add(RECS_ID);
-        }
 
         ArrayList<StatsEvent> events = new ArrayList<>();
         mHelper.pullPackageChannelPreferencesStats(events);
 
-        assertEquals("total events", notificationClassification() ? 7 : 3, events.size());
+        assertEquals("total events", 3, events.size());
         for (StatsEvent ev : events) {
             AtomsProto.Atom atom = StatsEventTestUtils.convertToAtom(ev);
             assertTrue(atom.hasPackageNotificationChannelPreferences());
@@ -6015,7 +5935,7 @@
         mHelper.pullPackageChannelPreferencesStats(events);
 
         // In this case, we want to check the properties of the conversation channel (not parent)
-        assertEquals("total events", notificationClassification() ? 6 : 2, events.size());
+        assertEquals("total events", 2, events.size());
         for (StatsEvent ev : events) {
             AtomsProto.Atom atom = StatsEventTestUtils.convertToAtom(ev);
             assertTrue(atom.hasPackageNotificationChannelPreferences());
@@ -6047,9 +5967,7 @@
         ArrayList<StatsEvent> events = new ArrayList<>();
         mHelper.pullPackageChannelPreferencesStats(events);
 
-        assertEquals("total events",
-                notificationClassification() ? 6 : 2,
-                events.size());
+        assertEquals("total events", 2, events.size());
         for (StatsEvent ev : events) {
             AtomsProto.Atom atom = StatsEventTestUtils.convertToAtom(ev);
             assertTrue(atom.hasPackageNotificationChannelPreferences());
@@ -6080,9 +5998,7 @@
         ArrayList<StatsEvent> events = new ArrayList<>();
         mHelper.pullPackageChannelPreferencesStats(events);
 
-        assertEquals("total events",
-                notificationClassification() ? 6 : 2,
-                events.size());
+        assertEquals("total events", 2, events.size());
         for (StatsEvent ev : events) {
             AtomsProto.Atom atom = StatsEventTestUtils.convertToAtom(ev);
             assertTrue(atom.hasPackageNotificationChannelPreferences());
@@ -6367,8 +6283,7 @@
     @Test
     @EnableFlags(FLAG_NOTIFICATION_CLASSIFICATION)
     public void testGetNotificationChannels_omitBundleChannels() {
-        // do something that triggers settings creation for an app
-        mHelper.setShowBadge(PKG_O, UID_O, true);
+        mHelper.createReservedChannel(PKG_O, UID_O, TYPE_NEWS);
 
         assertThat(mHelper.getNotificationChannels(PKG_O, UID_O, true, false).getList()).isEmpty();
     }
@@ -6376,18 +6291,34 @@
     @Test
     @EnableFlags(FLAG_NOTIFICATION_CLASSIFICATION)
     public void testNotificationBundles() {
-        // do something that triggers settings creation for an app
-        mHelper.setShowBadge(PKG_O, UID_O, true);
-
-        // verify 4 reserved channels are created
+        mHelper.createReservedChannel(PKG_O, UID_O, TYPE_NEWS);
         assertThat(mHelper.getNotificationChannel(PKG_O, UID_O, NEWS_ID, false).getImportance())
                 .isEqualTo(IMPORTANCE_LOW);
-        assertThat(mHelper.getNotificationChannel(PKG_O, UID_O, PROMOTIONS_ID, false)
-                .getImportance()).isEqualTo(IMPORTANCE_LOW);
-        assertThat(mHelper.getNotificationChannel(PKG_O, UID_O, SOCIAL_MEDIA_ID, false)
-                .getImportance()).isEqualTo(IMPORTANCE_LOW);
+        assertThat(mHelper.getNotificationChannels(PKG_O, UID_O, true, true).getList().size())
+                .isEqualTo(1);
+
+        mHelper.createReservedChannel(PKG_O, UID_O, TYPE_SOCIAL_MEDIA);
+        assertThat(mHelper.getNotificationChannel(PKG_O, UID_O, SOCIAL_MEDIA_ID, false).
+                getImportance()).isEqualTo(IMPORTANCE_LOW);
+        assertThat(mHelper.getNotificationChannels(PKG_O, UID_O, true, true).getList().size())
+                .isEqualTo(2);
+
+        mHelper.createReservedChannel(PKG_O, UID_O, TYPE_CONTENT_RECOMMENDATION);
         assertThat(mHelper.getNotificationChannel(PKG_O, UID_O, RECS_ID, false).getImportance())
                 .isEqualTo(IMPORTANCE_LOW);
+        assertThat(mHelper.getNotificationChannels(PKG_O, UID_O, true, true).getList().size())
+                .isEqualTo(3);
+
+        mHelper.createReservedChannel(PKG_O, UID_O, TYPE_PROMOTION);
+        assertThat(mHelper.getNotificationChannel(PKG_O, UID_O, PROMOTIONS_ID, false)
+                .getImportance()).isEqualTo(IMPORTANCE_LOW);
+        assertThat(mHelper.getNotificationChannels(PKG_O, UID_O, true, true).getList().size())
+                .isEqualTo(4);
+
+        // only the first 4 types are created; no others
+        mHelper.createReservedChannel(PKG_O, UID_O, TYPE_OTHER);
+        assertThat(mHelper.getNotificationChannels(PKG_O, UID_O, true, true).getList().size())
+                .isEqualTo(4);
     }
 
     @Test
@@ -6417,8 +6348,7 @@
     @Test
     @EnableFlags(FLAG_NOTIFICATION_CLASSIFICATION)
     public void testNotificationBundles_appsCannotUpdate() {
-        // do something that triggers settings creation for an app
-        mHelper.setShowBadge(PKG_O, UID_O, true);
+        mHelper.createReservedChannel(PKG_O, UID_O, TYPE_NEWS);
 
         NotificationChannel fromApp =
                 new NotificationChannel(NEWS_ID, "The best channel", IMPORTANCE_HIGH);
@@ -6431,8 +6361,7 @@
     @Test
     @EnableFlags(FLAG_NOTIFICATION_CLASSIFICATION)
     public void testNotificationBundles_osCanAllowToBypassDnd() {
-        // do something that triggers settings creation for an app
-        mHelper.setShowBadge(PKG_O, UID_O, true);
+        mHelper.createReservedChannel(PKG_O, UID_O, TYPE_NEWS);
 
         NotificationChannel fromApp =
                 new NotificationChannel(NEWS_ID, "The best channel", IMPORTANCE_HIGH);
@@ -6442,18 +6371,17 @@
     @Test
     @EnableFlags(FLAG_NOTIFICATION_CLASSIFICATION)
     public void testUnDeleteBundleChannelsOnLoadIfNotUserChange() throws Exception {
-        mHelper.setShowBadge(PKG_P, UID_P, true);
         // the public create/update methods should prevent this, so take advantage of the fact that
         // the object is in the same process
-        mHelper.getNotificationChannel(PKG_P, UID_P, SOCIAL_MEDIA_ID, true).setDeleted(true);
+        mHelper.createReservedChannel(PKG_N_MR1, UID_N_MR1, TYPE_SOCIAL_MEDIA).setDeleted(true);
 
         ByteArrayOutputStream baos = writeXmlAndPurge(PKG_N_MR1, UID_N_MR1, false,
                 UserHandle.USER_ALL, SOCIAL_MEDIA_ID);
 
         loadStreamXml(baos, false, UserHandle.USER_ALL);
 
-        assertThat(mXmlHelper.getNotificationChannel(PKG_P, UID_P, SOCIAL_MEDIA_ID, true).
-                isDeleted()).isFalse();
+        assertThat(mXmlHelper.getNotificationChannel(PKG_N_MR1, UID_N_MR1, SOCIAL_MEDIA_ID, true)
+                .isDeleted()).isFalse();
     }
 
     @Test
diff --git a/services/tests/wmtests/src/com/android/server/policy/ModifierShortcutTests.java b/services/tests/wmtests/src/com/android/server/policy/ModifierShortcutTests.java
index c186a03..28ae271 100644
--- a/services/tests/wmtests/src/com/android/server/policy/ModifierShortcutTests.java
+++ b/services/tests/wmtests/src/com/android/server/policy/ModifierShortcutTests.java
@@ -43,6 +43,8 @@
 import android.app.role.RoleManager;
 import android.content.ComponentName;
 import android.content.Intent;
+import android.hardware.input.AppLaunchData;
+import android.hardware.input.KeyGestureEvent;
 import android.os.RemoteException;
 import android.platform.test.annotations.DisableFlags;
 import android.platform.test.annotations.EnableFlags;
@@ -85,7 +87,8 @@
      * Test meta+ shortcuts defined in bookmarks.xml.
      */
     @Test
-    public void testMetaShortcuts() {
+    @DisableFlags(com.android.hardware.input.Flags.FLAG_USE_KEY_GESTURE_EVENT_HANDLER)
+    public void testMetaShortcuts_withoutKeyGestureEventHandling() {
         for (int i = 0; i < INTENT_SHORTCUTS.size(); i++) {
             final int keyCode = INTENT_SHORTCUTS.keyAt(i);
             final String category = INTENT_SHORTCUTS.valueAt(i);
@@ -115,6 +118,49 @@
 
     }
 
+    @Test
+    @EnableFlags(com.android.hardware.input.Flags.FLAG_USE_KEY_GESTURE_EVENT_HANDLER)
+    public void testMetaShortcuts_withKeyGestureEventHandling() {
+        for (int i = 0; i < INTENT_SHORTCUTS.size(); i++) {
+            final String category = INTENT_SHORTCUTS.valueAt(i);
+            mPhoneWindowManager.sendKeyGestureEvent(
+                    new KeyGestureEvent.Builder()
+                            .setKeyGestureType(KeyGestureEvent.KEY_GESTURE_TYPE_LAUNCH_APPLICATION)
+                            .setAction(KeyGestureEvent.ACTION_GESTURE_COMPLETE)
+                            .setAppLaunchData(AppLaunchData.createLaunchDataForCategory(category))
+                            .build()
+            );
+            mPhoneWindowManager.assertLaunchCategory(category);
+        }
+
+        mPhoneWindowManager.overrideRoleManager();
+        for (int i = 0; i < ROLE_SHORTCUTS.size(); i++) {
+            final String role = ROLE_SHORTCUTS.valueAt(i);
+
+            mPhoneWindowManager.sendKeyGestureEvent(
+                    new KeyGestureEvent.Builder()
+                            .setKeyGestureType(KeyGestureEvent.KEY_GESTURE_TYPE_LAUNCH_APPLICATION)
+                            .setAction(KeyGestureEvent.ACTION_GESTURE_COMPLETE)
+                            .setAppLaunchData(AppLaunchData.createLaunchDataForRole(role))
+                            .build()
+            );
+            mPhoneWindowManager.assertLaunchRole(role);
+        }
+
+        mPhoneWindowManager.sendKeyGestureEvent(
+                new KeyGestureEvent.Builder()
+                        .setKeyGestureType(KeyGestureEvent.KEY_GESTURE_TYPE_LAUNCH_APPLICATION)
+                        .setAction(KeyGestureEvent.ACTION_GESTURE_COMPLETE)
+                        .setAppLaunchData(
+                                new AppLaunchData.ComponentData("com.test",
+                                        "com.test.BookmarkTest"))
+                        .build()
+        );
+        mPhoneWindowManager.assertActivityTargetLaunched(
+                new ComponentName("com.test", "com.test.BookmarkTest"));
+
+    }
+
     /**
      * ALT + TAB to show recent apps.
      */
diff --git a/telecomm/java/android/telecom/Conference.java b/telecomm/java/android/telecom/Conference.java
index f803717..7adcd46 100644
--- a/telecomm/java/android/telecom/Conference.java
+++ b/telecomm/java/android/telecom/Conference.java
@@ -101,7 +101,7 @@
     private Set<String> mPreviousExtraKeys;
     private final Object mExtrasLock = new Object();
     private Uri mAddress;
-    private int mAddressPresentation;
+    private int mAddressPresentation = TelecomManager.PRESENTATION_UNKNOWN;
     private String mCallerDisplayName;
     private int mCallerDisplayNamePresentation;
     private int mCallDirection;
diff --git a/telecomm/java/android/telecom/Connection.java b/telecomm/java/android/telecom/Connection.java
index ad7d987..29d3942 100644
--- a/telecomm/java/android/telecom/Connection.java
+++ b/telecomm/java/android/telecom/Connection.java
@@ -2164,7 +2164,7 @@
     private CallAudioState mCallAudioState;
     private CallEndpoint mCallEndpoint;
     private Uri mAddress;
-    private int mAddressPresentation;
+    private int mAddressPresentation = TelecomManager.PRESENTATION_UNKNOWN;
     private String mCallerDisplayName;
     private int mCallerDisplayNamePresentation;
     private boolean mRingbackRequested = false;
diff --git a/telephony/java/android/telephony/TelephonyManager.java b/telephony/java/android/telephony/TelephonyManager.java
index a7fe0cb..fad59f8b 100644
--- a/telephony/java/android/telephony/TelephonyManager.java
+++ b/telephony/java/android/telephony/TelephonyManager.java
@@ -17278,6 +17278,18 @@
     }
 
     /**
+     * Setup sISms for testing.
+     *
+     * @hide
+     */
+    @VisibleForTesting
+    public static void setupISmsForTest(ISms iSms) {
+        synchronized (sCacheLock) {
+            sISms = iSms;
+        }
+    }
+
+    /**
      * Whether device can connect to 5G network when two SIMs are active.
      *
      * @hide TODO b/153669716: remove or make system API.