Merge "a11y: Pause auto click with pause button" into main
diff --git a/core/java/android/app/IActivityTaskManager.aidl b/core/java/android/app/IActivityTaskManager.aidl
index 4b1afa5..01b2953 100644
--- a/core/java/android/app/IActivityTaskManager.aidl
+++ b/core/java/android/app/IActivityTaskManager.aidl
@@ -146,6 +146,7 @@
     int getFrontActivityScreenCompatMode();
     void setFrontActivityScreenCompatMode(int mode);
     void setFocusedTask(int taskId);
+    boolean setTaskIsPerceptible(int taskId, boolean isPerceptible);
     boolean removeTask(int taskId);
     void removeAllVisibleRecentTasks();
     List<ActivityManager.RunningTaskInfo> getTasks(int maxNum, boolean filterOnlyVisibleRecents,
diff --git a/core/java/android/app/admin/DevicePolicyManager.java b/core/java/android/app/admin/DevicePolicyManager.java
index 08719fc..500f7585 100644
--- a/core/java/android/app/admin/DevicePolicyManager.java
+++ b/core/java/android/app/admin/DevicePolicyManager.java
@@ -14200,6 +14200,9 @@
      *    <li>Manifest.permission.ACTIVITY_RECOGNITION</li>
      *    <li>Manifest.permission.BODY_SENSORS</li>
      * </ul>
+     * On devices running {@link android.os.Build.VERSION_CODES#BAKLAVA}, the
+     * {@link android.health.connect.HealthPermissions} are also included in the
+     * restricted list.
      * <p>
      * A profile owner may not grant these permissions (i.e. call this method with any of the
      * permissions listed above and {@code grantState} of {@code #PERMISSION_GRANT_STATE_GRANTED}),
diff --git a/core/java/android/app/contextualsearch/flags.aconfig b/core/java/android/app/contextualsearch/flags.aconfig
index bc1f7ce..1de034b 100644
--- a/core/java/android/app/contextualsearch/flags.aconfig
+++ b/core/java/android/app/contextualsearch/flags.aconfig
@@ -24,7 +24,7 @@
 }
 
 flag {
-    name: "contextual_search_window_layer"
+    name: "contextual_search_prevent_self_capture"
     namespace: "sysui_integrations"
     description: "Identify live contextual search UI to exclude from contextual search screenshot."
     bug: "390176823"
diff --git a/core/java/android/content/res/Configuration.java b/core/java/android/content/res/Configuration.java
index ef200c3..2e09994 100644
--- a/core/java/android/content/res/Configuration.java
+++ b/core/java/android/content/res/Configuration.java
@@ -2358,8 +2358,13 @@
      * @param locales The locale list. If null, an empty LocaleList will be assigned.
      */
     public void setLocales(@Nullable LocaleList locales) {
+        LocaleList oldList = mLocaleList;
         mLocaleList = locales == null ? LocaleList.getEmptyLocaleList() : locales;
         locale = mLocaleList.get(0);
+        if (!mLocaleList.equals(oldList)) {
+            Slog.v(TAG, "Updating configuration, locales updated from " + oldList
+                    + " to " + mLocaleList);
+        }
         setLayoutDirection(locale);
     }
 
diff --git a/core/java/android/content/res/ResourcesImpl.java b/core/java/android/content/res/ResourcesImpl.java
index 96c7176..eec30f3 100644
--- a/core/java/android/content/res/ResourcesImpl.java
+++ b/core/java/android/content/res/ResourcesImpl.java
@@ -491,6 +491,9 @@
                             }
                             defaultLocale =
                                     adjustLanguageTag(lc.getDefaultLocale().toLanguageTag());
+                            Slog.v(TAG, "Updating configuration, with default locale "
+                                    + defaultLocale + " and selected locales "
+                                    + Arrays.toString(selectedLocales));
                         } else {
                             String[] availableLocales;
                             // The LocaleList has changed. We must query the AssetManager's
@@ -526,6 +529,7 @@
                         for (int i = 0; i < locales.size(); i++) {
                             selectedLocales[i] = adjustLanguageTag(locales.get(i).toLanguageTag());
                         }
+                        defaultLocale = adjustLanguageTag(lc.getDefaultLocale().toLanguageTag());
                     } else {
                         selectedLocales = new String[]{
                                 adjustLanguageTag(locales.get(0).toLanguageTag())};
diff --git a/core/java/android/window/DesktopModeFlags.java b/core/java/android/window/DesktopModeFlags.java
index b44620f..4ba9738 100644
--- a/core/java/android/window/DesktopModeFlags.java
+++ b/core/java/android/window/DesktopModeFlags.java
@@ -105,6 +105,7 @@
     ENABLE_RESIZING_METRICS(Flags::enableResizingMetrics, true),
     ENABLE_RESTORE_TO_PREVIOUS_SIZE_FROM_DESKTOP_IMMERSIVE(
             Flags::enableRestoreToPreviousSizeFromDesktopImmersive, true),
+    ENABLE_TASKBAR_OVERFLOW(Flags::enableTaskbarOverflow, false),
     ENABLE_TASK_RESIZING_KEYBOARD_SHORTCUTS(Flags::enableTaskResizingKeyboardShortcuts, true),
     ENABLE_TASK_STACK_OBSERVER_IN_SHELL(Flags::enableTaskStackObserverInShell, true),
     ENABLE_THEMED_APP_HEADERS(Flags::enableThemedAppHeaders, true),
diff --git a/core/java/android/window/flags/lse_desktop_experience.aconfig b/core/java/android/window/flags/lse_desktop_experience.aconfig
index 3e3b8e1..8265d25 100644
--- a/core/java/android/window/flags/lse_desktop_experience.aconfig
+++ b/core/java/android/window/flags/lse_desktop_experience.aconfig
@@ -705,6 +705,13 @@
 }
 
 flag {
+    name: "enable_activity_embedding_support_for_connected_displays"
+    namespace: "lse_desktop_experience"
+    description: "Enables activity embedding support for connected displays, including enabling AE optimization for Settings."
+    bug: "369438353"
+}
+
+flag {
     name: "enable_full_screen_window_on_removing_split_screen_stage_bugfix"
     namespace: "lse_desktop_experience"
     description: "Enables clearing the windowing mode of a freeform window when removing the task from the split screen stage."
@@ -764,3 +771,10 @@
         purpose: PURPOSE_BUGFIX
     }
 }
+
+flag {
+    name: "enable_taskbar_overflow"
+    namespace: "lse_desktop_experience"
+    description: "Show recent apps in the taskbar overflow."
+    bug: "368119679"
+}
diff --git a/core/java/com/android/internal/pm/pkg/component/AconfigFlags.java b/core/java/com/android/internal/pm/pkg/component/AconfigFlags.java
index b57acf3..b19967a 100644
--- a/core/java/com/android/internal/pm/pkg/component/AconfigFlags.java
+++ b/core/java/com/android/internal/pm/pkg/component/AconfigFlags.java
@@ -261,7 +261,12 @@
             // Default value is false for when the flag is not found.
             // Note: Unlike with the old storage, with AconfigPackage, we don't have a way to
             // know if the flag is not found or if it's found but the value is false.
-            value = aconfigPackage.getBooleanFlagValue(flagName, false);
+            try {
+                value = aconfigPackage.getBooleanFlagValue(flagName, false);
+            } catch (Exception e) {
+                Slog.e(LOG_TAG, "Failed to read Aconfig flag value for " + flagPackageAndName, e);
+                return null;
+            }
         }
         if (DEBUG) {
             Slog.v(LOG_TAG, "Aconfig flag value for " + flagPackageAndName + " = " + value);
diff --git a/core/java/com/android/internal/widget/MessagingLayout.java b/core/java/com/android/internal/widget/MessagingLayout.java
index e9d920c..64b44df 100644
--- a/core/java/com/android/internal/widget/MessagingLayout.java
+++ b/core/java/com/android/internal/widget/MessagingLayout.java
@@ -121,22 +121,38 @@
         setMessagingClippingDisabled(false);
     }
 
-    @RemotableViewMethod
+    @RemotableViewMethod(asyncImpl = "setAvatarReplacementAsync")
     public void setAvatarReplacement(Icon icon) {
         mAvatarReplacement = icon;
     }
 
-    @RemotableViewMethod
+    /**
+     * @hide
+     */
+    public Runnable setAvatarReplacementAsync(Icon icon) {
+        mAvatarReplacement = icon;
+        return () -> {};
+    }
+
+    @RemotableViewMethod(asyncImpl = "setNameReplacementAsync")
     public void setNameReplacement(CharSequence nameReplacement) {
         mNameReplacement = nameReplacement;
     }
 
     /**
+     * @hide
+     */
+    public Runnable setNameReplacementAsync(CharSequence nameReplacement) {
+        mNameReplacement = nameReplacement;
+        return () -> {};
+    }
+
+    /**
      * Set this layout to show the collapsed representation.
      *
      * @param isCollapsed is it collapsed
      */
-    @RemotableViewMethod
+    @RemotableViewMethod(asyncImpl = "setIsCollapsedAsync")
     public void setIsCollapsed(boolean isCollapsed) {
         mIsCollapsed = isCollapsed;
     }
@@ -145,7 +161,6 @@
      * setDataAsync needs to do different stuff for the collapsed vs expanded view, so store the
      * collapsed state early.
      */
-    @RemotableViewMethod(asyncImpl = "setIsCollapsedAsync")
     public Runnable setIsCollapsedAsync(boolean isCollapsed) {
         mIsCollapsed = isCollapsed;
         return () -> {};
@@ -161,12 +176,20 @@
      *
      * @param conversationTitle the conversation title
      */
-    @RemotableViewMethod
+    @RemotableViewMethod(asyncImpl = "setConversationTitleAsync")
     public void setConversationTitle(CharSequence conversationTitle) {
         mConversationTitle = conversationTitle;
     }
 
     /**
+     * @hide
+     */
+    public Runnable setConversationTitleAsync(CharSequence conversationTitle) {
+        mConversationTitle = conversationTitle;
+        return ()->{};
+    }
+
+    /**
      * Set Messaging data
      * @param extras Bundle contains messaging data
      */
@@ -417,22 +440,44 @@
         return mPeopleHelper.createAvatarSymbol(senderName, symbol, layoutColor);
     }
 
-    @RemotableViewMethod
+    @RemotableViewMethod(asyncImpl = "setLayoutColorAsync")
     public void setLayoutColor(int color) {
         mLayoutColor = color;
     }
 
-    @RemotableViewMethod
+    /**
+     * @hide
+     */
+    public Runnable setLayoutColorAsync(int color) {
+        mLayoutColor = color;
+        return () -> {};
+    }
+
+    @RemotableViewMethod(asyncImpl = "setIsOneToOneAsync")
     public void setIsOneToOne(boolean oneToOne) {
         mIsOneToOne = oneToOne;
     }
 
-    @RemotableViewMethod
+    /**
+     * @hide
+     */
+    public Runnable setIsOneToOneAsync(boolean oneToOne) {
+        mIsOneToOne = oneToOne;
+        return () -> {};
+    }
+
+    @RemotableViewMethod(asyncImpl = "setSenderTextColorAsync")
     public void setSenderTextColor(int color) {
         mSenderTextColor = color;
     }
 
-
+    /**
+     * @hide
+     */
+    public Runnable setSenderTextColorAsync(int color) {
+        mSenderTextColor = color;
+        return () -> {};
+    }
     /**
      * @param color the color of the notification background
      */
@@ -441,11 +486,19 @@
         // Nothing to do with this
     }
 
-    @RemotableViewMethod
+    @RemotableViewMethod(asyncImpl = "setMessageTextColorAsync")
     public void setMessageTextColor(int color) {
         mMessageTextColor = color;
     }
 
+    /**
+     * @hide
+     */
+    public Runnable setMessageTextColorAsync(int color) {
+        mMessageTextColor = color;
+        return () -> {};
+    }
+
     public void setUser(Person user) {
         mUser = user;
         if (mUser.getIcon() == null) {
diff --git a/core/jni/android_app_PropertyInvalidatedCache.cpp b/core/jni/android_app_PropertyInvalidatedCache.cpp
index 12585d5..6267522 100644
--- a/core/jni/android_app_PropertyInvalidatedCache.cpp
+++ b/core/jni/android_app_PropertyInvalidatedCache.cpp
@@ -35,7 +35,7 @@
     return kMaxNonce;
 }
 
-size_t NonceStore::getMaxByte() const {
+int32_t NonceStore::getMaxByte() const {
     return kMaxByte;
 }
 
@@ -68,13 +68,13 @@
 }
 
 // Copy the byte block to the target and return the current hash.
-int32_t NonceStore::getByteBlock(block_t* block, size_t len) const {
+int32_t NonceStore::getByteBlock(block_t* block, int32_t len) const {
     memcpy(block, (void*) byteBlock(), std::min(kMaxByte, len));
     return mByteHash;
 }
 
 // Set the byte block and the hash.
-void NonceStore::setByteBlock(int hash, const block_t* block, size_t len) {
+void NonceStore::setByteBlock(int hash, const block_t* block, int32_t len) {
     memcpy((void*) byteBlock(), block, len = std::min(kMaxByte, len));
     mByteHash = hash;
 }
diff --git a/core/jni/android_app_PropertyInvalidatedCache.h b/core/jni/android_app_PropertyInvalidatedCache.h
index 54a4ac6..1d75182 100644
--- a/core/jni/android_app_PropertyInvalidatedCache.h
+++ b/core/jni/android_app_PropertyInvalidatedCache.h
@@ -27,8 +27,12 @@
  * location.  Fields with a variable location are found via offsets.  The offsets make this
  * object position-independent, which is required because it is in shared memory and would be
  * mapped into different virtual addresses for different processes.
+ *
+ * This structure is shared between 64-bit and 32-bit processes.  Therefore it is imperative
+ * that the structure not use any datatypes that are architecture-dependent (like size_t).
+ * Additionally, care must be taken to avoid unexpected padding in the structure.
  */
-class NonceStore {
+class alignas(8) NonceStore {
   protected:
     // A convenient typedef.  The jbyteArray element type is jbyte, which the compiler treats as
     // signed char.
@@ -43,23 +47,27 @@
     // The value of an unset field.
     static constexpr int UNSET = 0;
 
-    // The size of the nonce array.
+    // The size of the nonce array.  This and the following sizes are int32_t to
+    // be ABI independent.
     const int32_t kMaxNonce;
 
     // The size of the byte array.
-    const size_t kMaxByte;
+    const int32_t kMaxByte;
 
     // The offset to the nonce array.
-    const size_t mNonceOffset;
+    const int32_t mNonceOffset;
 
     // The offset to the byte array.
-    const size_t mByteOffset;
+    const int32_t mByteOffset;
 
     // The byte block hash.  This is fixed and at a known offset, so leave it in the base class.
     volatile std::atomic<int32_t> mByteHash;
 
+    // A 4-byte padd that makes the size of this structure a multiple of 8 bytes.
+    const int32_t _pad = 0;
+
     // The constructor is protected!  It only makes sense when called from a subclass.
-    NonceStore(int kMaxNonce, size_t kMaxByte, volatile nonce_t* nonce, volatile block_t* block) :
+    NonceStore(int kMaxNonce, int kMaxByte, volatile nonce_t* nonce, volatile block_t* block) :
             kMaxNonce(kMaxNonce),
             kMaxByte(kMaxByte),
             mNonceOffset(offset(this, const_cast<nonce_t*>(nonce))),
@@ -70,7 +78,7 @@
 
     // These provide run-time access to the sizing parameters.
     int getMaxNonce() const;
-    size_t getMaxByte() const;
+    int getMaxByte() const;
 
     // 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
@@ -86,10 +94,10 @@
     int32_t getHash() const;
 
     // Copy the byte block to the target and return the current hash.
-    int32_t getByteBlock(block_t* block, size_t len) const;
+    int32_t getByteBlock(block_t* block, int32_t len) const;
 
     // Set the byte block and the hash.
-    void setByteBlock(int hash, const block_t* block, size_t len);
+    void setByteBlock(int hash, const block_t* block, int32_t len);
 
   private:
 
@@ -113,6 +121,12 @@
     }
 };
 
+// Assert that the size of the object is fixed, independent of the CPU architecture.  There are
+// four int32_t fields and one atomic<int32_t>, which sums to 20 bytes total.  This assertion
+// uses a constant instead of computing the size of the objects in the compiler, to avoid
+// different answers on different architectures.
+static_assert(sizeof(NonceStore) == 24);
+
 /**
  * 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
@@ -126,20 +140,22 @@
  * 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 : public NonceStore {
+template<int MAX_NONCE, int MAX_BYTE> class CacheNonce : public NonceStore {
 
     // The array of nonces
-    volatile nonce_t mNonce[maxNonce];
+    volatile nonce_t mNonce[MAX_NONCE];
 
     // The byte array.  This is not atomic but it is guarded by the mByteHash.
-    volatile block_t mByteBlock[maxByte];
+    volatile block_t mByteBlock[MAX_BYTE];
 
   public:
+    // Export the parameters for use in compiler assertions.
+    static constexpr int kMaxNonceCount = MAX_NONCE;
+    static constexpr int kMaxByteCount = MAX_BYTE;
+
     // Construct and initialize the memory.
-    CacheNonce() :
-            NonceStore(maxNonce, maxByte, &mNonce[0], &mByteBlock[0])
-    {
-        for (int i = 0; i < maxNonce; i++) {
+    CacheNonce() : NonceStore(MAX_NONCE, MAX_BYTE, &mNonce[0], &mByteBlock[0]) {
+        for (int i = 0; i < MAX_NONCE; i++) {
             mNonce[i] = UNSET;
         }
         mByteHash = UNSET;
@@ -155,4 +171,10 @@
 typedef CacheNonce</* max nonce */ 128, /* byte block size */ 8192> SystemCacheNonce;
 // LINT.ThenChange(/core/tests/coretests/src/android/app/PropertyInvalidatedCacheTests.java:system_nonce_config)
 
+// Verify that there is no padding in the final class.
+static_assert(sizeof(SystemCacheNonce) ==
+              sizeof(NonceStore)
+              + SystemCacheNonce::kMaxNonceCount*8
+              + SystemCacheNonce::kMaxByteCount);
+
 } // namespace android.app.PropertyInvalidatedCache
diff --git a/core/jni/android_view_SurfaceControl.cpp b/core/jni/android_view_SurfaceControl.cpp
index b99b0ef..a8e51a7 100644
--- a/core/jni/android_view_SurfaceControl.cpp
+++ b/core/jni/android_view_SurfaceControl.cpp
@@ -765,28 +765,28 @@
     std::vector<int32_t> dimensions;
     std::vector<int32_t> sizes;
     std::vector<int32_t> samplingKeys;
-    int32_t fd = -1;
+    base::unique_fd fd;
 
     if (jdimensionArray) {
         jsize numLuts = env->GetArrayLength(jdimensionArray);
-        ScopedIntArrayRW joffsets(env, joffsetArray);
+        ScopedIntArrayRO joffsets(env, joffsetArray);
         if (joffsets.get() == nullptr) {
-            jniThrowRuntimeException(env, "Failed to get ScopedIntArrayRW from joffsetArray");
+            jniThrowRuntimeException(env, "Failed to get ScopedIntArrayRO from joffsetArray");
             return;
         }
-        ScopedIntArrayRW jdimensions(env, jdimensionArray);
+        ScopedIntArrayRO jdimensions(env, jdimensionArray);
         if (jdimensions.get() == nullptr) {
-            jniThrowRuntimeException(env, "Failed to get ScopedIntArrayRW from jdimensionArray");
+            jniThrowRuntimeException(env, "Failed to get ScopedIntArrayRO from jdimensionArray");
             return;
         }
-        ScopedIntArrayRW jsizes(env, jsizeArray);
+        ScopedIntArrayRO jsizes(env, jsizeArray);
         if (jsizes.get() == nullptr) {
-            jniThrowRuntimeException(env, "Failed to get ScopedIntArrayRW from jsizeArray");
+            jniThrowRuntimeException(env, "Failed to get ScopedIntArrayRO from jsizeArray");
             return;
         }
-        ScopedIntArrayRW jsamplingKeys(env, jsamplingKeyArray);
+        ScopedIntArrayRO jsamplingKeys(env, jsamplingKeyArray);
         if (jsamplingKeys.get() == nullptr) {
-            jniThrowRuntimeException(env, "Failed to get ScopedIntArrayRW from jsamplingKeyArray");
+            jniThrowRuntimeException(env, "Failed to get ScopedIntArrayRO from jsamplingKeyArray");
             return;
         }
 
@@ -796,15 +796,15 @@
             sizes = std::vector<int32_t>(jsizes.get(), jsizes.get() + numLuts);
             samplingKeys = std::vector<int32_t>(jsamplingKeys.get(), jsamplingKeys.get() + numLuts);
 
-            ScopedFloatArrayRW jbuffers(env, jbufferArray);
+            ScopedFloatArrayRO jbuffers(env, jbufferArray);
             if (jbuffers.get() == nullptr) {
-                jniThrowRuntimeException(env, "Failed to get ScopedFloatArrayRW from jbufferArray");
+                jniThrowRuntimeException(env, "Failed to get ScopedFloatArrayRO from jbufferArray");
                 return;
             }
 
             // create the shared memory and copy jbuffers
             size_t bufferSize = jbuffers.size() * sizeof(float);
-            fd = ashmem_create_region("lut_shared_mem", bufferSize);
+            fd.reset(ashmem_create_region("lut_shared_mem", bufferSize));
             if (fd < 0) {
                 jniThrowRuntimeException(env, "ashmem_create_region() failed");
                 return;
@@ -820,7 +820,7 @@
         }
     }
 
-    transaction->setLuts(ctrl, base::unique_fd(fd), offsets, dimensions, sizes, samplingKeys);
+    transaction->setLuts(ctrl, std::move(fd), offsets, dimensions, sizes, samplingKeys);
 }
 
 static void nativeSetPictureProfileId(JNIEnv* env, jclass clazz, jlong transactionObj,
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/desktopmode/DisplayDeskState.aidl b/libs/WindowManager/Shell/src/com/android/wm/shell/desktopmode/DisplayDeskState.aidl
index 59add47..5f45192 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/desktopmode/DisplayDeskState.aidl
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/desktopmode/DisplayDeskState.aidl
@@ -18,13 +18,11 @@
 
 /**
  * Defines the state of desks on a display whose ID is `displayId`, which is:
- * - `canCreateDesks`: whether it's possible to create new desks on this display.
  * - `activeDeskId`: the currently active desk Id, or `-1` if none is active.
  * - `deskId`: the list of desk Ids of the available desks on this display.
  */
 parcelable DisplayDeskState {
     int displayId;
-    boolean canCreateDesk;
     int activeDeskId;
     int[] deskIds;
 }
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/desktopmode/IDesktopTaskListener.aidl b/libs/WindowManager/Shell/src/com/android/wm/shell/desktopmode/IDesktopTaskListener.aidl
index 7ed1581..cefbd89 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/desktopmode/IDesktopTaskListener.aidl
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/desktopmode/IDesktopTaskListener.aidl
@@ -28,7 +28,7 @@
      * Called once when the listener first gets connected to initialize it with the current state of
      * desks in Shell.
      */
-    void onListenerConnected(in DisplayDeskState[] displayDeskStates);
+    void onListenerConnected(in DisplayDeskState[] displayDeskStates, boolean canCreateDesks);
 
     /** Desktop tasks visibility has changed. Visible if at least 1 task is visible. */
     void onTasksVisibilityChanged(int displayId, int visibleTasksCount);
@@ -49,10 +49,10 @@
     void onExitDesktopModeTransitionStarted(int transitionDuration);
 
     /**
-     * Called when the conditions that allow the creation of a new desk on the display whose ID is
-     * `displayId` changes to `canCreateDesks`. It's also called when a new display is added.
+     * Called when the conditions that allow the creation of a new desk changes. This is a global
+     * state for the entire device.
      */
-    void onCanCreateDesksChanged(int displayId, boolean canCreateDesks);
+    void onCanCreateDesksChanged(boolean canCreateDesks);
 
     /** Called when a desk whose ID is `deskId` is added to the display whose ID is `displayId`. */
     void onDeskAdded(int displayId, int deskId);
diff --git a/native/android/surface_control.cpp b/native/android/surface_control.cpp
index 4fe0b80..2759724 100644
--- a/native/android/surface_control.cpp
+++ b/native/android/surface_control.cpp
@@ -715,47 +715,45 @@
     sp<SurfaceControl> surfaceControl = ASurfaceControl_to_SurfaceControl(aSurfaceControl);
     Transaction* transaction = ASurfaceTransaction_to_Transaction(aSurfaceTransaction);
 
-    int fd = -1;
+    base::unique_fd fd;
     std::vector<int32_t> offsets;
     std::vector<int32_t> dimensions;
     std::vector<int32_t> sizes;
     std::vector<int32_t> samplingKeys;
 
     if (luts) {
-        std::vector<float> buffer(luts->totalBufferSize);
         int32_t count = luts->offsets.size();
         offsets = luts->offsets;
 
         dimensions.reserve(count);
         sizes.reserve(count);
         samplingKeys.reserve(count);
-        for (int32_t i = 0; i < count; i++) {
-            dimensions.emplace_back(luts->entries[i]->properties.dimension);
-            sizes.emplace_back(luts->entries[i]->properties.size);
-            samplingKeys.emplace_back(luts->entries[i]->properties.samplingKey);
-            std::copy(luts->entries[i]->buffer.data.begin(), luts->entries[i]->buffer.data.end(),
-                      buffer.begin() + offsets[i]);
-        }
 
         // mmap
-        fd = ashmem_create_region("lut_shared_mem", luts->totalBufferSize * sizeof(float));
+        fd.reset(ashmem_create_region("lut_shared_mem", luts->totalBufferSize * sizeof(float)));
         if (fd < 0) {
             LOG_ALWAYS_FATAL("setLuts, ashmem_create_region() failed");
             return;
         }
-        void* ptr = mmap(nullptr, luts->totalBufferSize * sizeof(float), PROT_READ | PROT_WRITE,
-                         MAP_SHARED, fd, 0);
+        float* ptr = (float*)mmap(nullptr, luts->totalBufferSize * sizeof(float),
+                                  PROT_READ | PROT_WRITE, MAP_SHARED, fd, 0);
         if (ptr == MAP_FAILED) {
             LOG_ALWAYS_FATAL("setLuts, Failed to map the shared memory");
             return;
         }
 
-        memcpy(ptr, buffer.data(), luts->totalBufferSize * sizeof(float));
+        for (int32_t i = 0; i < count; i++) {
+            dimensions.emplace_back(luts->entries[i]->properties.dimension);
+            sizes.emplace_back(luts->entries[i]->properties.size);
+            samplingKeys.emplace_back(luts->entries[i]->properties.samplingKey);
+            std::copy(luts->entries[i]->buffer.data.begin(), luts->entries[i]->buffer.data.end(),
+                      ptr + offsets[i]);
+        }
+
         munmap(ptr, luts->totalBufferSize * sizeof(float));
     }
 
-    transaction->setLuts(surfaceControl, base::unique_fd(fd), offsets, dimensions, sizes,
-                         samplingKeys);
+    transaction->setLuts(surfaceControl, std::move(fd), offsets, dimensions, sizes, samplingKeys);
 }
 
 void ASurfaceTransaction_setColor(ASurfaceTransaction* aSurfaceTransaction,
diff --git a/packages/CtsShim/build/Android.bp b/packages/CtsShim/build/Android.bp
index bd89263..853d1ad 100644
--- a/packages/CtsShim/build/Android.bp
+++ b/packages/CtsShim/build/Android.bp
@@ -152,34 +152,6 @@
 }
 
 //##########################################################
-// Variant: System app upgrade
-
-android_app {
-    name: "CtsShimUpgrade",
-
-    sdk_version: "current",
-    optimize: {
-        enabled: false,
-    },
-    dex_preopt: {
-        enabled: false,
-    },
-
-    manifest: "shim/AndroidManifestUpgrade.xml",
-    min_sdk_version: "24",
-}
-
-java_genrule {
-    name: "generate_shim_manifest",
-    srcs: [
-        "shim/AndroidManifest.xml",
-        ":CtsShimUpgrade",
-    ],
-    out: ["AndroidManifest.xml"],
-    cmd: "sed -e s/__HASH__/`sha512sum -b $(location :CtsShimUpgrade) | cut -d' ' -f1`/ $(location shim/AndroidManifest.xml) > $(out)",
-}
-
-//##########################################################
 // Variant: System app
 
 android_app {
@@ -193,7 +165,7 @@
         enabled: false,
     },
 
-    manifest: ":generate_shim_manifest",
+    manifest: "shim/AndroidManifest.xml",
     apex_available: [
         "//apex_available:platform",
         "com.android.apex.cts.shim.v1",
diff --git a/packages/CtsShim/build/shim/AndroidManifest.xml b/packages/CtsShim/build/shim/AndroidManifest.xml
index 3b8276b6..1ffe56c 100644
--- a/packages/CtsShim/build/shim/AndroidManifest.xml
+++ b/packages/CtsShim/build/shim/AndroidManifest.xml
@@ -17,15 +17,13 @@
 <!-- Manifest for the system CTS shim -->
 <manifest xmlns:android="http://schemas.android.com/apk/res/android"
     xmlns:tools="http://schemas.android.com/tools"
-    package="com.android.cts.ctsshim"
-    android:sharedUserId="com.android.cts.ctsshim" >
+    package="com.android.cts.ctsshim" >
 
-    <uses-sdk
-        android:minSdkVersion="24"
+    <uses-sdk android:minSdkVersion="24"
         android:targetSdkVersion="28" />
 
     <restrict-update
-        android:hash="__HASH__" />
+        android:hash="__CAN_NOT_BE_UPDATED__" />
 
     <application
         android:hasCode="false"
diff --git a/packages/CtsShim/build/shim/AndroidManifestUpgrade.xml b/packages/CtsShim/build/shim/AndroidManifestUpgrade.xml
deleted file mode 100644
index 7f3644a..0000000
--- a/packages/CtsShim/build/shim/AndroidManifestUpgrade.xml
+++ /dev/null
@@ -1,30 +0,0 @@
-<?xml version="1.0" encoding="utf-8"?>
-<!-- Copyright (C) 2016 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 for the system CTS shim -->
-<manifest xmlns:android="http://schemas.android.com/apk/res/android"
-    xmlns:tools="http://schemas.android.com/tools"
-    package="com.android.cts.ctsshim" >
-
-    <uses-sdk
-        android:minSdkVersion="24"
-        android:targetSdkVersion="28" />
-
-    <application
-        android:hasCode="false"
-        tools:ignore="AllowBackup,MissingApplicationIcon" />
-</manifest>
-
diff --git a/packages/SettingsLib/SettingsTheme/res/values-v35/themes.xml b/packages/SettingsLib/SettingsTheme/res/values-v35/themes.xml
index dc5c9b2..b6e80c7 100644
--- a/packages/SettingsLib/SettingsTheme/res/values-v35/themes.xml
+++ b/packages/SettingsLib/SettingsTheme/res/values-v35/themes.xml
@@ -18,7 +18,7 @@
 <resources>
     <style name="Theme.SettingsBase_v35" parent="Theme.SettingsBase_v33" >
         <item name="android:colorAccent">@color/settingslib_materialColorPrimary</item>
-        <item name="android:colorBackground">@color/settingslib_materialColorSurfaceContainerLowest</item>
+        <item name="android:colorBackground">@color/settingslib_materialColorSurfaceContainer</item>
         <item name="android:textColorPrimary">@color/settingslib_materialColorOnSurface</item>
         <item name="android:textColorSecondary">@color/settingslib_text_color_secondary</item>
         <item name="android:textColorTertiary">@color/settingslib_materialColorOutline</item>
diff --git a/packages/SettingsLib/res/drawable/ic_mobile_0_4_bar_error.xml b/packages/SettingsLib/res/drawable/ic_mobile_0_4_bar_error.xml
index 6015be8..aa5e9d2 100644
--- a/packages/SettingsLib/res/drawable/ic_mobile_0_4_bar_error.xml
+++ b/packages/SettingsLib/res/drawable/ic_mobile_0_4_bar_error.xml
@@ -23,10 +23,10 @@
         <clip-path
             android:pathData="
             M0,0
-            V13.5,0
-            H13.5,20
-            V0,20
-            H0,0
+            H16
+            V12
+            H0
+            Z
             M14.999,13.5C17.761,13.5 19.999,11.261 19.999,8.5C19.999,5.739 17.761,3.5 14.999,3.5C12.238,3.5 9.999,5.739 9.999,8.5C9.999,11.261 12.238,13.5 14.999,13.5Z" />
         <path
             android:pathData="M1.249,7C1.939,7 2.499,7.56 2.499,8.25L2.499,10.75C2.499,11.44 1.939,12 1.249,12C0.559,12 -0.001,11.44 -0.001,10.75L-0.001,8.25C-0.001,7.56 0.559,7 1.249,7Z"
diff --git a/packages/SettingsLib/res/drawable/ic_mobile_1_4_bar_error.xml b/packages/SettingsLib/res/drawable/ic_mobile_1_4_bar_error.xml
index fb73b6b..f7bf851 100644
--- a/packages/SettingsLib/res/drawable/ic_mobile_1_4_bar_error.xml
+++ b/packages/SettingsLib/res/drawable/ic_mobile_1_4_bar_error.xml
@@ -8,10 +8,10 @@
         <clip-path
             android:pathData="
             M0,0
-            V13.5,0
-            H13.5,20
-            V0,20
-            H0,0
+            H16
+            V12
+            H0
+            Z
             M14.999,13.5C17.761,13.5 19.999,11.261 19.999,8.5C19.999,5.739 17.761,3.5 14.999,3.5C12.238,3.5 9.999,5.739 9.999,8.5C9.999,11.261 12.238,13.5 14.999,13.5Z" />
         <path
             android:pathData="M1.249,7C1.939,7 2.499,7.56 2.499,8.25L2.499,10.75C2.499,11.44 1.939,12 1.249,12C0.559,12 -0.001,11.44 -0.001,10.75L-0.001,8.25C-0.001,7.56 0.559,7 1.249,7Z"
diff --git a/packages/SettingsLib/res/drawable/ic_mobile_3_4_bar_error.xml b/packages/SettingsLib/res/drawable/ic_mobile_3_4_bar_error.xml
index 7c4c1c6..016c614 100644
--- a/packages/SettingsLib/res/drawable/ic_mobile_3_4_bar_error.xml
+++ b/packages/SettingsLib/res/drawable/ic_mobile_3_4_bar_error.xml
@@ -23,10 +23,10 @@
         <clip-path
             android:pathData="
             M0,0
-            V13.5,0
-            H13.5,20
-            V0,20
-            H0,0
+            H16
+            V12
+            H0
+            Z
             M14.999,13.5C17.761,13.5 19.999,11.261 19.999,8.5C19.999,5.739 17.761,3.5 14.999,3.5C12.238,3.5 9.999,5.739 9.999,8.5C9.999,11.261 12.238,13.5 14.999,13.5Z" />
         <path
             android:pathData="M1.249,7C1.939,7 2.499,7.56 2.499,8.25L2.499,10.75C2.499,11.44 1.939,12 1.249,12C0.559,12 -0.001,11.44 -0.001,10.75L-0.001,8.25C-0.001,7.56 0.559,7 1.249,7Z"
diff --git a/packages/SettingsLib/res/drawable/ic_mobile_4_4_bar_error.xml b/packages/SettingsLib/res/drawable/ic_mobile_4_4_bar_error.xml
index d23680d..b830415 100644
--- a/packages/SettingsLib/res/drawable/ic_mobile_4_4_bar_error.xml
+++ b/packages/SettingsLib/res/drawable/ic_mobile_4_4_bar_error.xml
@@ -23,10 +23,10 @@
         <clip-path
             android:pathData="
             M0,0
-            V13.5,0
-            H13.5,20
-            V0,20
-            H0,0
+            H16
+            V12
+            H0
+            Z
             M14.999,13.5C17.761,13.5 19.999,11.261 19.999,8.5C19.999,5.739 17.761,3.5 14.999,3.5C12.238,3.5 9.999,5.739 9.999,8.5C9.999,11.261 12.238,13.5 14.999,13.5Z" />
         <path
             android:pathData="M1.249,7C1.939,7 2.499,7.56 2.499,8.25L2.499,10.75C2.499,11.44 1.939,12 1.249,12C0.559,12 -0.001,11.44 -0.001,10.75L-0.001,8.25C-0.001,7.56 0.559,7 1.249,7Z"
diff --git a/packages/SystemUI/compose/features/src/com/android/systemui/shade/ui/composable/ShadeHeader.kt b/packages/SystemUI/compose/features/src/com/android/systemui/shade/ui/composable/ShadeHeader.kt
index 5e12ee1..db9035b 100644
--- a/packages/SystemUI/compose/features/src/com/android/systemui/shade/ui/composable/ShadeHeader.kt
+++ b/packages/SystemUI/compose/features/src/com/android/systemui/shade/ui/composable/ShadeHeader.kt
@@ -65,6 +65,7 @@
 import com.android.compose.animation.scene.animateElementFloatAsState
 import com.android.compose.animation.scene.content.state.TransitionState
 import com.android.compose.modifiers.thenIf
+import com.android.compose.theme.colorAttr
 import com.android.settingslib.Utils
 import com.android.systemui.battery.BatteryMeterView
 import com.android.systemui.battery.BatteryMeterViewController
@@ -75,8 +76,6 @@
 import com.android.systemui.privacy.OngoingPrivacyChip
 import com.android.systemui.res.R
 import com.android.systemui.scene.shared.model.Scenes
-import com.android.systemui.shade.ui.composable.ShadeHeader.Colors.chipBackground
-import com.android.systemui.shade.ui.composable.ShadeHeader.Colors.chipHighlighted
 import com.android.systemui.shade.ui.composable.ShadeHeader.Colors.onScrimDim
 import com.android.systemui.shade.ui.composable.ShadeHeader.Dimensions.ChipPaddingHorizontal
 import com.android.systemui.shade.ui.composable.ShadeHeader.Dimensions.ChipPaddingVertical
@@ -107,7 +106,7 @@
     object Dimensions {
         val CollapsedHeight = 48.dp
         val ExpandedHeight = 120.dp
-        val ChipPaddingHorizontal = 8.dp
+        val ChipPaddingHorizontal = 6.dp
         val ChipPaddingVertical = 4.dp
     }
 
@@ -117,12 +116,6 @@
 
         val ColorScheme.onScrimDim: Color
             get() = Color.DarkGray
-
-        val ColorScheme.chipBackground: Color
-            get() = Color.DarkGray
-
-        val ColorScheme.chipHighlighted: Color
-            get() = Color.LightGray
     }
 
     object TestTags {
@@ -165,7 +158,7 @@
                 VariableDayDate(
                     longerDateText = viewModel.longerDateText,
                     shorterDateText = viewModel.shorterDateText,
-                    chipHighlight = viewModel.notificationsChipHighlight,
+                    textColor = colorAttr(android.R.attr.textColorPrimary),
                     modifier = Modifier.element(ShadeHeader.Elements.CollapsedContentStart),
                 )
             }
@@ -265,7 +258,7 @@
                 VariableDayDate(
                     longerDateText = viewModel.longerDateText,
                     shorterDateText = viewModel.shorterDateText,
-                    chipHighlight = viewModel.notificationsChipHighlight,
+                    textColor = colorAttr(android.R.attr.textColorPrimary),
                     modifier = Modifier.widthIn(max = 90.dp),
                 )
                 Spacer(modifier = Modifier.weight(1f))
@@ -310,6 +303,7 @@
                 verticalAlignment = Alignment.CenterVertically,
                 modifier = Modifier.padding(horizontal = horizontalPadding),
             ) {
+                val chipHighlight = viewModel.notificationsChipHighlight
                 if (isShadeLayoutWide) {
                     Clock(
                         scale = 1f,
@@ -319,13 +313,13 @@
                     Spacer(modifier = Modifier.width(5.dp))
                 }
                 NotificationsChip(
-                    chipHighlight = viewModel.notificationsChipHighlight,
                     onClick = viewModel::onNotificationIconChipClicked,
+                    backgroundColor = chipHighlight.backgroundColor(MaterialTheme.colorScheme),
                 ) {
                     VariableDayDate(
                         longerDateText = viewModel.longerDateText,
                         shorterDateText = viewModel.shorterDateText,
-                        chipHighlight = viewModel.notificationsChipHighlight,
+                        textColor = chipHighlight.foregroundColor(MaterialTheme.colorScheme),
                     )
                 }
             }
@@ -338,14 +332,13 @@
             ) {
                 val chipHighlight = viewModel.quickSettingsChipHighlight
                 SystemIconChip(
-                    chipHighlight = chipHighlight,
+                    backgroundColor = chipHighlight.backgroundColor(MaterialTheme.colorScheme),
                     onClick = viewModel::onSystemIconChipClicked,
                 ) {
                     StatusIcons(
                         viewModel = viewModel,
                         useExpandedFormat = false,
                         modifier = Modifier.padding(end = 6.dp).weight(1f, fill = false),
-                        chipHighlight = chipHighlight,
                     )
                     BatteryIcon(
                         createBatteryMeterViewController =
@@ -515,6 +508,7 @@
             batteryIcon.setPercentShowMode(
                 if (useExpandedFormat) BatteryMeterView.MODE_ESTIMATE else BatteryMeterView.MODE_ON
             )
+            // TODO(b/397223606): Get the actual spec for this.
             if (chipHighlight is HeaderChipHighlight.Strong) {
                 batteryIcon.updateColors(primaryColor, inverseColor, inverseColor)
             } else if (chipHighlight is HeaderChipHighlight.Weak) {
@@ -553,7 +547,6 @@
     viewModel: ShadeHeaderViewModel,
     useExpandedFormat: Boolean,
     modifier: Modifier = Modifier,
-    chipHighlight: HeaderChipHighlight = HeaderChipHighlight.None,
 ) {
     val localContext = LocalContext.current
     val themedContext =
@@ -581,6 +574,8 @@
         viewModel.createTintedIconManager(iconContainer, StatusBarLocation.QS)
     }
 
+    val chipHighlight = viewModel.quickSettingsChipHighlight
+
     AndroidView(
         factory = { context ->
             iconManager.setTint(primaryColor, inverseColor)
@@ -617,6 +612,7 @@
                 iconContainer.removeIgnoredSlot(locationSlot)
             }
 
+            // TODO(b/397223606): Get the actual spec for this.
             if (chipHighlight is HeaderChipHighlight.Strong) {
                 iconManager.setTint(inverseColor, primaryColor)
             } else if (chipHighlight is HeaderChipHighlight.Weak) {
@@ -629,16 +625,12 @@
 
 @Composable
 private fun NotificationsChip(
-    chipHighlight: HeaderChipHighlight,
     onClick: () -> Unit,
     modifier: Modifier = Modifier,
+    backgroundColor: Color = Color.Unspecified,
     content: @Composable BoxScope.() -> Unit,
 ) {
     val interactionSource = remember { MutableInteractionSource() }
-    val chipBackground =
-        with(MaterialTheme.colorScheme) {
-            if (chipHighlight is HeaderChipHighlight.Strong) chipHighlighted else chipBackground
-        }
     Box(
         modifier =
             modifier
@@ -647,7 +639,7 @@
                     indication = null,
                     onClick = onClick,
                 )
-                .background(chipBackground, RoundedCornerShape(25.dp))
+                .background(backgroundColor, RoundedCornerShape(25.dp))
                 .padding(horizontal = ChipPaddingHorizontal, vertical = ChipPaddingVertical)
     ) {
         content()
@@ -657,7 +649,7 @@
 @Composable
 private fun SystemIconChip(
     modifier: Modifier = Modifier,
-    chipHighlight: HeaderChipHighlight = HeaderChipHighlight.None,
+    backgroundColor: Color = Color.Unspecified,
     onClick: (() -> Unit)? = null,
     content: @Composable RowScope.() -> Unit,
 ) {
@@ -667,16 +659,12 @@
         with(MaterialTheme.colorScheme) {
             Modifier.background(onScrimDim, RoundedCornerShape(CollapsedHeight / 4))
         }
-    val backgroundColor =
-        with(MaterialTheme.colorScheme) {
-            if (chipHighlight is HeaderChipHighlight.Strong) chipHighlighted else chipBackground
-        }
 
     Row(
         verticalAlignment = Alignment.CenterVertically,
         modifier =
             modifier
-                .thenIf(chipHighlight !is HeaderChipHighlight.None) {
+                .thenIf(backgroundColor != Color.Unspecified) {
                     Modifier.background(backgroundColor, RoundedCornerShape(25.dp))
                         .padding(horizontal = ChipPaddingHorizontal, vertical = ChipPaddingVertical)
                 }
diff --git a/packages/SystemUI/compose/features/src/com/android/systemui/shade/ui/composable/VariableDayDate.kt b/packages/SystemUI/compose/features/src/com/android/systemui/shade/ui/composable/VariableDayDate.kt
index 64aada5..8fbd051 100644
--- a/packages/SystemUI/compose/features/src/com/android/systemui/shade/ui/composable/VariableDayDate.kt
+++ b/packages/SystemUI/compose/features/src/com/android/systemui/shade/ui/composable/VariableDayDate.kt
@@ -4,22 +4,16 @@
 import androidx.compose.material3.Text
 import androidx.compose.runtime.Composable
 import androidx.compose.ui.Modifier
+import androidx.compose.ui.graphics.Color
 import androidx.compose.ui.layout.Layout
-import com.android.compose.theme.colorAttr
-import com.android.systemui.shade.ui.viewmodel.ShadeHeaderViewModel.HeaderChipHighlight
 
 @Composable
 fun VariableDayDate(
     longerDateText: String,
     shorterDateText: String,
-    chipHighlight: HeaderChipHighlight,
+    textColor: Color,
     modifier: Modifier = Modifier,
 ) {
-    val textColor =
-        if (chipHighlight is HeaderChipHighlight.Strong)
-            colorAttr(android.R.attr.textColorPrimaryInverse)
-        else colorAttr(android.R.attr.textColorPrimary)
-
     Layout(
         contents =
             listOf(
diff --git a/packages/SystemUI/multivalentTests/src/com/android/systemui/media/controls/ui/binder/SeekBarObserverTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/media/controls/ui/binder/SeekBarObserverTest.kt
index 4e14fec..943ada9 100644
--- a/packages/SystemUI/multivalentTests/src/com/android/systemui/media/controls/ui/binder/SeekBarObserverTest.kt
+++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/media/controls/ui/binder/SeekBarObserverTest.kt
@@ -18,6 +18,9 @@
 
 import android.animation.Animator
 import android.animation.ObjectAnimator
+import android.icu.text.MeasureFormat
+import android.icu.util.Measure
+import android.icu.util.MeasureUnit
 import android.testing.TestableLooper
 import android.view.View
 import android.widget.SeekBar
@@ -30,6 +33,7 @@
 import com.android.systemui.media.controls.ui.viewmodel.SeekBarViewModel
 import com.android.systemui.res.R
 import com.google.common.truth.Truth.assertThat
+import java.util.Locale
 import org.junit.Before
 import org.junit.Rule
 import org.junit.Test
@@ -61,11 +65,11 @@
     fun setUp() {
         context.orCreateTestableResources.addOverride(
             R.dimen.qs_media_enabled_seekbar_height,
-            enabledHeight
+            enabledHeight,
         )
         context.orCreateTestableResources.addOverride(
             R.dimen.qs_media_disabled_seekbar_height,
-            disabledHeight
+            disabledHeight,
         )
 
         seekBarView = SeekBar(context)
@@ -110,14 +114,31 @@
 
     @Test
     fun seekBarProgress() {
+        val elapsedTime = 3000
+        val duration = (1.5 * 60 * 60 * 1000).toInt()
         // WHEN part of the track has been played
-        val data = SeekBarViewModel.Progress(true, true, true, false, 3000, 120000, true)
+        val data = SeekBarViewModel.Progress(true, true, true, false, elapsedTime, duration, true)
         observer.onChanged(data)
         // THEN seek bar shows the progress
-        assertThat(seekBarView.progress).isEqualTo(3000)
-        assertThat(seekBarView.max).isEqualTo(120000)
+        assertThat(seekBarView.progress).isEqualTo(elapsedTime)
+        assertThat(seekBarView.max).isEqualTo(duration)
 
-        val desc = context.getString(R.string.controls_media_seekbar_description, "00:03", "02:00")
+        val expectedProgress =
+            MeasureFormat.getInstance(Locale.getDefault(), MeasureFormat.FormatWidth.WIDE)
+                .formatMeasures(Measure(3, MeasureUnit.SECOND))
+        val expectedDuration =
+            MeasureFormat.getInstance(Locale.getDefault(), MeasureFormat.FormatWidth.WIDE)
+                .formatMeasures(
+                    Measure(1, MeasureUnit.HOUR),
+                    Measure(30, MeasureUnit.MINUTE),
+                    Measure(0, MeasureUnit.SECOND),
+                )
+        val desc =
+            context.getString(
+                R.string.controls_media_seekbar_description,
+                expectedProgress,
+                expectedDuration,
+            )
         assertThat(seekBarView.contentDescription).isEqualTo(desc)
     }
 
diff --git a/packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/notification/collection/BundleEntryTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/notification/collection/BundleEntryTest.kt
index 426af26..83e26c4 100644
--- a/packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/notification/collection/BundleEntryTest.kt
+++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/notification/collection/BundleEntryTest.kt
@@ -16,6 +16,8 @@
 
 package com.android.systemui.statusbar.notification.collection
 
+import android.app.Notification
+import android.graphics.Color
 import android.platform.test.annotations.EnableFlags
 import android.platform.test.flag.junit.SetFlagsRule
 import android.testing.TestableLooper.RunWithLooper
@@ -72,4 +74,35 @@
     fun getKey_adapter() {
         assertThat(underTest.entryAdapter.key).isEqualTo("key")
     }
+
+    @Test
+    @EnableFlags(NotificationBundleUi.FLAG_NAME)
+    fun isClearable_adapter() {
+        assertThat(underTest.entryAdapter.isClearable).isTrue()
+    }
+
+    @Test
+    @EnableFlags(NotificationBundleUi.FLAG_NAME)
+    fun getSummarization_adapter() {
+        assertThat(underTest.entryAdapter.summarization).isNull()
+    }
+
+    @Test
+    @EnableFlags(NotificationBundleUi.FLAG_NAME)
+    fun getContrastedColor_adapter() {
+        assertThat(underTest.entryAdapter.getContrastedColor(context, false, Color.WHITE))
+            .isEqualTo(Notification.COLOR_DEFAULT)
+    }
+
+    @Test
+    @EnableFlags(NotificationBundleUi.FLAG_NAME)
+    fun canPeek_adapter() {
+        assertThat(underTest.entryAdapter.canPeek()).isFalse()
+    }
+
+    @Test
+    @EnableFlags(NotificationBundleUi.FLAG_NAME)
+    fun getWhen_adapter() {
+        assertThat(underTest.entryAdapter.`when`).isEqualTo(0)
+    }
 }
\ No newline at end of file
diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/collection/NotificationEntryTest.java b/packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/notification/collection/NotificationEntryTest.java
similarity index 89%
rename from packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/collection/NotificationEntryTest.java
rename to packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/notification/collection/NotificationEntryTest.java
index 19d1224..1f5c672 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/collection/NotificationEntryTest.java
+++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/notification/collection/NotificationEntryTest.java
@@ -163,9 +163,10 @@
 
     @Test
     public void testIsExemptFromDndVisualSuppression_media() {
+        MediaSession session = new MediaSession(mContext, "test");
         Notification.Builder n = new Notification.Builder(mContext, "")
                 .setStyle(new Notification.MediaStyle()
-                        .setMediaSession(mock(MediaSession.Token.class)))
+                        .setMediaSession(session.getSessionToken()))
                 .setSmallIcon(R.drawable.ic_person)
                 .setContentTitle("Title")
                 .setContentText("Text");
@@ -593,6 +594,76 @@
         assertThat(entry.getEntryAdapter().getGroupRoot()).isEqualTo(parent.getEntryAdapter());
     }
 
+    @Test
+    @EnableFlags(NotificationBundleUi.FLAG_NAME)
+    public void isClearable_adapter() {
+        ExpandableNotificationRow row = mock(ExpandableNotificationRow.class);
+        Notification notification = new Notification.Builder(mContext, "")
+                .setSmallIcon(R.drawable.ic_person)
+                .build();
+
+        NotificationEntry entry = new NotificationEntryBuilder()
+                .setPkg(TEST_PACKAGE_NAME)
+                .setOpPkg(TEST_PACKAGE_NAME)
+                .setUid(TEST_UID)
+                .setChannel(mChannel)
+                .setId(mId++)
+                .setNotification(notification)
+                .setUser(new UserHandle(ActivityManager.getCurrentUser()))
+                .build();
+        entry.setRow(row);
+
+        assertThat(entry.getEntryAdapter().isClearable()).isEqualTo(entry.isClearable());
+    }
+
+    @Test
+    @EnableFlags(NotificationBundleUi.FLAG_NAME)
+    public void getSummarization_adapter() {
+        ExpandableNotificationRow row = mock(ExpandableNotificationRow.class);
+        Notification notification = new Notification.Builder(mContext, "")
+                .setSmallIcon(R.drawable.ic_person)
+                .build();
+
+        NotificationEntry entry = new NotificationEntryBuilder()
+                .setPkg(TEST_PACKAGE_NAME)
+                .setOpPkg(TEST_PACKAGE_NAME)
+                .setUid(TEST_UID)
+                .setChannel(mChannel)
+                .setId(mId++)
+                .setNotification(notification)
+                .setUser(new UserHandle(ActivityManager.getCurrentUser()))
+                .build();
+        Ranking ranking = new RankingBuilder(entry.getRanking())
+                .setSummarization("hello")
+                .build();
+        entry.setRanking(ranking);
+        entry.setRow(row);
+
+        assertThat(entry.getEntryAdapter().getSummarization()).isEqualTo("hello");
+    }
+
+    @Test
+    @EnableFlags(NotificationBundleUi.FLAG_NAME)
+    public void getIcons_adapter() {
+        ExpandableNotificationRow row = mock(ExpandableNotificationRow.class);
+        Notification notification = new Notification.Builder(mContext, "")
+                .setSmallIcon(R.drawable.ic_person)
+                .build();
+
+        NotificationEntry entry = new NotificationEntryBuilder()
+                .setPkg(TEST_PACKAGE_NAME)
+                .setOpPkg(TEST_PACKAGE_NAME)
+                .setUid(TEST_UID)
+                .setChannel(mChannel)
+                .setId(mId++)
+                .setNotification(notification)
+                .setUser(new UserHandle(ActivityManager.getCurrentUser()))
+                .build();
+        entry.setRow(row);
+
+        assertThat(entry.getEntryAdapter().getIcons()).isEqualTo(entry.getIcons());
+    }
+
     private Notification.Action createContextualAction(String title) {
         return new Notification.Action.Builder(
                 Icon.createWithResource(getContext(), android.R.drawable.sym_def_app_icon),
diff --git a/packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/notification/collection/coordinator/OriginalUnseenKeyguardCoordinatorTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/notification/collection/coordinator/OriginalUnseenKeyguardCoordinatorTest.kt
index 7781df1..43cb957 100644
--- a/packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/notification/collection/coordinator/OriginalUnseenKeyguardCoordinatorTest.kt
+++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/notification/collection/coordinator/OriginalUnseenKeyguardCoordinatorTest.kt
@@ -16,8 +16,11 @@
 package com.android.systemui.statusbar.notification.collection.coordinator
 
 import android.app.Notification
+import android.app.Notification.MediaStyle
+import android.media.session.MediaSession
 import android.platform.test.flag.junit.FlagsParameterization
 import android.provider.Settings
+import android.service.notification.StatusBarNotification
 import androidx.test.filters.SmallTest
 import com.android.systemui.SysuiTestCase
 import com.android.systemui.dump.dumpManager
@@ -36,6 +39,7 @@
 import com.android.systemui.scene.data.repository.setTransition
 import com.android.systemui.scene.domain.interactor.sceneInteractor
 import com.android.systemui.scene.shared.model.Scenes
+import com.android.systemui.statusbar.SbnBuilder
 import com.android.systemui.statusbar.notification.collection.GroupEntryBuilder
 import com.android.systemui.statusbar.notification.collection.NotifPipeline
 import com.android.systemui.statusbar.notification.collection.NotificationEntryBuilder
@@ -217,6 +221,16 @@
                         mock<ExpandableNotificationRow>().apply {
                             whenever(isMediaRow).thenReturn(true)
                         }
+                    sbn = SbnBuilder().setNotification(
+                        Notification.Builder(context, "channel").setStyle(
+                            MediaStyle().setMediaSession(
+                                MediaSession(
+                                    context,
+                                    "tag"
+                                ).sessionToken
+                            )
+                        ).build()
+                    ).build()
                 }
             collectionListener.onEntryAdded(fakeEntry)
 
diff --git a/packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/notification/row/NotificationGutsManagerTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/notification/row/NotificationGutsManagerTest.kt
index 4c1f4f1..1b8d64d 100644
--- a/packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/notification/row/NotificationGutsManagerTest.kt
+++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/notification/row/NotificationGutsManagerTest.kt
@@ -80,9 +80,6 @@
 import com.android.systemui.testKosmos
 import com.android.systemui.util.kotlin.JavaAdapter
 import com.android.systemui.wmshell.BubblesManager
-import java.util.Optional
-import kotlin.test.assertNotNull
-import kotlin.test.fail
 import kotlinx.coroutines.test.runCurrent
 import kotlinx.coroutines.test.runTest
 import org.junit.Assert.assertEquals
@@ -110,6 +107,9 @@
 import org.mockito.kotlin.whenever
 import platform.test.runner.parameterized.ParameterizedAndroidJunit4
 import platform.test.runner.parameterized.Parameters
+import java.util.Optional
+import kotlin.test.assertNotNull
+import kotlin.test.fail
 
 /** Tests for [NotificationGutsManager]. */
 @SmallTest
@@ -509,7 +509,6 @@
             .setImportance(NotificationManager.IMPORTANCE_HIGH)
             .build()
 
-        whenever(row.isNonblockable).thenReturn(false)
         whenever(highPriorityProvider.isHighPriority(entry)).thenReturn(true)
         val statusBarNotification = entry.sbn
         gutsManager.initializeNotificationInfo(row, notificationInfoView)
@@ -546,7 +545,6 @@
         NotificationEntryHelper.modifyRanking(row.entry)
             .setUserSentiment(NotificationListenerService.Ranking.USER_SENTIMENT_NEGATIVE)
             .build()
-        whenever(row.isNonblockable).thenReturn(false)
         val statusBarNotification = row.entry.sbn
         val entry = row.entry
 
@@ -586,7 +584,6 @@
         NotificationEntryHelper.modifyRanking(row.entry)
             .setUserSentiment(NotificationListenerService.Ranking.USER_SENTIMENT_NEGATIVE)
             .build()
-        whenever(row.isNonblockable).thenReturn(false)
         val statusBarNotification = row.entry.sbn
         val entry = row.entry
 
@@ -641,7 +638,7 @@
     ): NotificationMenuRowPlugin.MenuItem {
         val menuRow: NotificationMenuRowPlugin =
             NotificationMenuRow(mContext, peopleNotificationIdentifier)
-        menuRow.createMenu(row, row.entry.sbn)
+        menuRow.createMenu(row)
 
         val menuItem = menuRow.getLongpressMenuItem(mContext)
         assertNotNull(menuItem)
diff --git a/packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/notification/row/NotificationMenuRowTest.java b/packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/notification/row/NotificationMenuRowTest.java
index 027e899..9fdfca1 100644
--- a/packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/notification/row/NotificationMenuRowTest.java
+++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/notification/row/NotificationMenuRowTest.java
@@ -72,7 +72,7 @@
     public void testAttachDetach() {
         NotificationMenuRowPlugin row =
                 new NotificationMenuRow(mContext, mPeopleNotificationIdentifier);
-        row.createMenu(mRow, null);
+        row.createMenu(mRow);
         ViewUtils.attachView(row.getMenuView());
         TestableLooper.get(this).processAllMessages();
         ViewUtils.detachView(row.getMenuView());
@@ -83,9 +83,9 @@
     public void testRecreateMenu() {
         NotificationMenuRowPlugin row =
                 new NotificationMenuRow(mContext, mPeopleNotificationIdentifier);
-        row.createMenu(mRow, null);
+        row.createMenu(mRow);
         assertTrue(row.getMenuView() != null);
-        row.createMenu(mRow, null);
+        row.createMenu(mRow);
         assertTrue(row.getMenuView() != null);
     }
 
@@ -103,7 +103,7 @@
         Settings.Global.putInt(mContext.getContentResolver(), SHOW_NEW_NOTIF_DISMISS, 0);
 
         NotificationMenuRow row = new NotificationMenuRow(mContext, mPeopleNotificationIdentifier);
-        row.createMenu(mRow, null);
+        row.createMenu(mRow);
 
         ViewGroup container = (ViewGroup) row.getMenuView();
         // noti blocking
@@ -116,7 +116,7 @@
         Settings.Global.putInt(mContext.getContentResolver(), SHOW_NEW_NOTIF_DISMISS, 0);
 
         NotificationMenuRow row = new NotificationMenuRow(mContext, mPeopleNotificationIdentifier);
-        row.createMenu(mRow, null);
+        row.createMenu(mRow);
 
         ViewGroup container = (ViewGroup) row.getMenuView();
         // just for noti blocking
@@ -129,7 +129,7 @@
         Settings.Global.putInt(mContext.getContentResolver(), SHOW_NEW_NOTIF_DISMISS, 0);
 
         NotificationMenuRow row = new NotificationMenuRow(mContext, mPeopleNotificationIdentifier);
-        row.createMenu(mRow, null);
+        row.createMenu(mRow);
 
         ViewGroup container = (ViewGroup) row.getMenuView();
         // one for snooze and one for noti blocking
@@ -142,7 +142,7 @@
         Settings.Global.putInt(mContext.getContentResolver(), SHOW_NEW_NOTIF_DISMISS, 1);
 
         NotificationMenuRow row = new NotificationMenuRow(mContext, mPeopleNotificationIdentifier);
-        row.createMenu(mRow, null);
+        row.createMenu(mRow);
 
         ViewGroup container = (ViewGroup) row.getMenuView();
         // Clear menu
@@ -417,7 +417,7 @@
     public void testOnTouchMove() {
         NotificationMenuRow row = Mockito.spy(
                 new NotificationMenuRow(mContext, mPeopleNotificationIdentifier));
-        row.createMenu(mRow, null);
+        row.createMenu(mRow);
         doReturn(50f).when(row).getDismissThreshold();
         doReturn(true).when(row).canBeDismissed();
         doReturn(mView).when(row).getMenuView();
diff --git a/packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/notification/stack/MagneticNotificationRowManagerImplTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/notification/stack/MagneticNotificationRowManagerImplTest.kt
index e5cb0fb..885e71e 100644
--- a/packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/notification/stack/MagneticNotificationRowManagerImplTest.kt
+++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/notification/stack/MagneticNotificationRowManagerImplTest.kt
@@ -32,6 +32,7 @@
 import com.google.android.msdl.data.model.MSDLToken
 import com.google.common.truth.Truth.assertThat
 import kotlinx.coroutines.test.runTest
+import org.junit.After
 import org.junit.Before
 import org.junit.Test
 import org.junit.runner.RunWith
@@ -49,7 +50,7 @@
     private val sectionsManager = mock<NotificationSectionsManager>()
     private val msdlPlayer = kosmos.fakeMSDLPlayer
     private var canRowBeDismissed = true
-    private var magneticAnimationsCancelled = false
+    private var magneticAnimationsCancelled = MutableList(childrenNumber) { false }
 
     private val underTest = kosmos.magneticNotificationRowManagerImpl
 
@@ -64,8 +65,10 @@
             NotificationTestHelper(mContext, mDependency, kosmos.testableLooper, featureFlags)
         children = notificationTestHelper.createGroup(childrenNumber).childrenContainer
         swipedRow = children.attachedChildren[childrenNumber / 2]
-        configureMagneticRowListener(swipedRow)
-        magneticAnimationsCancelled = false
+        children.attachedChildren.forEachIndexed { index, row ->
+            row.magneticRowListener = row.magneticRowListener.asTestableListener(index)
+        }
+        magneticAnimationsCancelled.replaceAll { false }
     }
 
     @Test
@@ -259,14 +262,14 @@
             underTest.onMagneticInteractionEnd(swipedRow, velocity = null)
 
             // THEN magnetic animations are cancelled
-            assertThat(magneticAnimationsCancelled).isTrue()
+            assertThat(magneticAnimationsCancelled[childrenNumber / 2]).isTrue()
         }
 
     @Test
     fun onMagneticInteractionEnd_forMagneticNeighbor_cancelsMagneticAnimations() =
         kosmos.testScope.runTest {
-            val neighborRow = children.attachedChildren[childrenNumber / 2 - 1]
-            configureMagneticRowListener(neighborRow)
+            val neighborIndex = childrenNumber / 2 - 1
+            val neighborRow = children.attachedChildren[neighborIndex]
 
             // GIVEN that targets are set
             setTargets()
@@ -275,9 +278,15 @@
             underTest.onMagneticInteractionEnd(neighborRow, null)
 
             // THEN magnetic animations are cancelled
-            assertThat(magneticAnimationsCancelled).isTrue()
+            assertThat(magneticAnimationsCancelled[neighborIndex]).isTrue()
         }
 
+    @After
+    fun tearDown() {
+        // We reset the manager so that all MagneticRowListener can cancel all animations
+        underTest.reset()
+    }
+
     private fun setDetachedState() {
         val threshold = 100f
         underTest.setSwipeThresholdPx(threshold)
@@ -302,27 +311,33 @@
             originalTranslation *
             MagneticNotificationRowManagerImpl.MAGNETIC_REDUCTION
 
-    private fun configureMagneticRowListener(row: ExpandableNotificationRow) {
-        val listener =
-            object : MagneticRowListener {
-                override fun setMagneticTranslation(translation: Float) {
-                    row.translation = translation
-                }
-
-                override fun triggerMagneticForce(
-                    endTranslation: Float,
-                    springForce: SpringForce,
-                    startVelocity: Float,
-                ) {}
-
-                override fun cancelMagneticAnimations() {
-                    magneticAnimationsCancelled = true
-                }
-
-                override fun cancelTranslationAnimations() {}
-
-                override fun canRowBeDismissed(): Boolean = canRowBeDismissed
+    private fun MagneticRowListener.asTestableListener(rowIndex: Int): MagneticRowListener {
+        val delegate = this
+        return object : MagneticRowListener {
+            override fun setMagneticTranslation(translation: Float) {
+                delegate.setMagneticTranslation(translation)
             }
-        row.magneticRowListener = listener
+
+            override fun triggerMagneticForce(
+                endTranslation: Float,
+                springForce: SpringForce,
+                startVelocity: Float,
+            ) {
+                delegate.triggerMagneticForce(endTranslation, springForce, startVelocity)
+            }
+
+            override fun cancelMagneticAnimations() {
+                magneticAnimationsCancelled[rowIndex] = true
+                delegate.cancelMagneticAnimations()
+            }
+
+            override fun cancelTranslationAnimations() {
+                delegate.cancelTranslationAnimations()
+            }
+
+            override fun canRowBeDismissed(): Boolean {
+                return canRowBeDismissed
+            }
+        }
     }
 }
diff --git a/packages/SystemUI/plugin/src/com/android/systemui/plugins/statusbar/NotificationMenuRowPlugin.java b/packages/SystemUI/plugin/src/com/android/systemui/plugins/statusbar/NotificationMenuRowPlugin.java
index 94fdbae..9b961d2 100644
--- a/packages/SystemUI/plugin/src/com/android/systemui/plugins/statusbar/NotificationMenuRowPlugin.java
+++ b/packages/SystemUI/plugin/src/com/android/systemui/plugins/statusbar/NotificationMenuRowPlugin.java
@@ -122,7 +122,7 @@
 
     public void setAppName(String appName);
 
-    public void createMenu(ViewGroup parent, StatusBarNotification sbn);
+    public void createMenu(ViewGroup parent);
 
     public void resetMenu();
 
@@ -215,9 +215,8 @@
 
     /**
      * Callback used to signal the menu that its parent notification has been updated.
-     * @param sbn
      */
-    public void onNotificationUpdated(StatusBarNotification sbn);
+    public void onNotificationUpdated();
 
     /**
      * Callback used to signal the menu that a user is moving the parent notification.
diff --git a/packages/SystemUI/res/values/strings.xml b/packages/SystemUI/res/values/strings.xml
index 3fc46ed..359bd2b 100644
--- a/packages/SystemUI/res/values/strings.xml
+++ b/packages/SystemUI/res/values/strings.xml
@@ -3170,8 +3170,8 @@
     <string name="controls_media_settings_button">Settings</string>
     <!-- Description for media control's playing media item, including information for the media's title, the artist, and source app [CHAR LIMIT=NONE]-->
     <string name="controls_media_playing_item_description"><xliff:g id="song_name" example="Daily mix">%1$s</xliff:g> by <xliff:g id="artist_name" example="Various artists">%2$s</xliff:g> is playing from <xliff:g id="app_label" example="Spotify">%3$s</xliff:g></string>
-    <!-- Content description for media cotnrols progress bar [CHAR_LIMIT=NONE] -->
-    <string name="controls_media_seekbar_description"><xliff:g id="elapsed_time" example="1:30">%1$s</xliff:g> of <xliff:g id="total_time" example="3:00">%2$s</xliff:g></string>
+    <!-- Content description for media controls progress bar [CHAR_LIMIT=NONE] -->
+    <string name="controls_media_seekbar_description"><xliff:g id="elapsed_time" example="1 hour 2 minutes 30 seconds">%1$s</xliff:g> of <xliff:g id="total_time" example="4 hours 5 seconds">%2$s</xliff:g></string>
     <!-- Placeholder title to inform user that an app has posted media controls [CHAR_LIMIT=NONE] -->
     <string name="controls_media_empty_title"><xliff:g id="app_name" example="Foo Music App">%1$s</xliff:g> is running</string>
 
diff --git a/packages/SystemUI/shared/Android.bp b/packages/SystemUI/shared/Android.bp
index 0f1da50..ae3a76e 100644
--- a/packages/SystemUI/shared/Android.bp
+++ b/packages/SystemUI/shared/Android.bp
@@ -71,6 +71,7 @@
         "//frameworks/libs/systemui:com_android_systemui_shared_flags_lib",
         "//frameworks/libs/systemui:msdl",
         "//frameworks/libs/systemui:view_capture",
+        "am_flags_lib",
     ],
     resource_dirs: [
         "res",
diff --git a/packages/SystemUI/shared/res/values/bools.xml b/packages/SystemUI/shared/res/values/bools.xml
index f22dac4..98e5cea 100644
--- a/packages/SystemUI/shared/res/values/bools.xml
+++ b/packages/SystemUI/shared/res/values/bools.xml
@@ -22,4 +22,7 @@
 <resources>
     <!-- Whether to add padding at the bottom of the complication clock -->
     <bool name="dream_overlay_complication_clock_bottom_padding">false</bool>
-</resources>
\ No newline at end of file
+
+    <!-- Whether to mark tasks that are present in the UI as perceptible tasks. -->
+    <bool name="config_usePerceptibleTasks">false</bool>
+</resources>
diff --git a/packages/SystemUI/shared/src/com/android/systemui/shared/system/ActivityManagerWrapper.java b/packages/SystemUI/shared/src/com/android/systemui/shared/system/ActivityManagerWrapper.java
index ed9ba7a..26d78bb 100644
--- a/packages/SystemUI/shared/src/com/android/systemui/shared/system/ActivityManagerWrapper.java
+++ b/packages/SystemUI/shared/src/com/android/systemui/shared/system/ActivityManagerWrapper.java
@@ -46,6 +46,8 @@
 import android.window.TaskSnapshot;
 
 import com.android.internal.app.IVoiceInteractionManagerService;
+import com.android.server.am.Flags;
+import com.android.systemui.shared.R;
 import com.android.systemui.shared.recents.model.Task;
 import com.android.systemui.shared.recents.model.ThumbnailData;
 
@@ -227,6 +229,17 @@
     }
 
     /**
+     * Sets whether or not the specified task is perceptible.
+     */
+    public boolean setTaskIsPerceptible(int taskId, boolean isPerceptible) {
+        try {
+            return getService().setTaskIsPerceptible(taskId, isPerceptible);
+        } catch (RemoteException e) {
+            throw e.rethrowFromSystemServer();
+        }
+    }
+
+    /**
      * Removes a task by id.
      */
     public void removeTask(final int taskId) {
@@ -311,6 +324,14 @@
     }
 
     /**
+     * Returns true if tasks with a presence in the UI should be marked as perceptible tasks.
+     */
+    public static boolean usePerceptibleTasks(Context context) {
+        return Flags.perceptibleTasks()
+                && context.getResources().getBoolean(R.bool.config_usePerceptibleTasks);
+    }
+
+    /**
      * Returns true if the running task represents the home task
      */
     public static boolean isHomeTask(RunningTaskInfo info) {
diff --git a/packages/SystemUI/src/com/android/systemui/accessibility/hearingaid/AmbientVolumeLayout.java b/packages/SystemUI/src/com/android/systemui/accessibility/hearingaid/AmbientVolumeLayout.java
index 7c141c1..5247acc 100644
--- a/packages/SystemUI/src/com/android/systemui/accessibility/hearingaid/AmbientVolumeLayout.java
+++ b/packages/SystemUI/src/com/android/systemui/accessibility/hearingaid/AmbientVolumeLayout.java
@@ -58,11 +58,22 @@
     private final BiMap<Integer, AmbientVolumeSlider> mSideToSliderMap = HashBiMap.create();
     private int mVolumeLevel = AMBIENT_VOLUME_LEVEL_DEFAULT;
 
+    private HearingDevicesUiEventLogger mUiEventLogger;
+    private int mLaunchSourceId;
+
     private final AmbientVolumeSlider.OnChangeListener mSliderOnChangeListener =
             (slider, value) -> {
-                if (mListener != null) {
-                    final int side = mSideToSliderMap.inverse().get(slider);
-                    mListener.onSliderValueChange(side, value);
+                final Integer side = mSideToSliderMap.inverse().get(slider);
+                if (side != null) {
+                    if (mUiEventLogger != null) {
+                        HearingDevicesUiEvent uiEvent = side == SIDE_UNIFIED
+                                ? HearingDevicesUiEvent.HEARING_DEVICES_AMBIENT_CHANGE_UNIFIED
+                                : HearingDevicesUiEvent.HEARING_DEVICES_AMBIENT_CHANGE_SEPARATED;
+                        mUiEventLogger.log(uiEvent, mLaunchSourceId);
+                    }
+                    if (mListener != null) {
+                        mListener.onSliderValueChange(side, value);
+                    }
                 }
             };
 
@@ -94,6 +105,12 @@
                 return;
             }
             setMuted(!mMuted);
+            if (mUiEventLogger != null) {
+                HearingDevicesUiEvent uiEvent = mMuted
+                        ? HearingDevicesUiEvent.HEARING_DEVICES_AMBIENT_MUTE
+                        : HearingDevicesUiEvent.HEARING_DEVICES_AMBIENT_UNMUTE;
+                mUiEventLogger.log(uiEvent, mLaunchSourceId);
+            }
             if (mListener != null) {
                 mListener.onAmbientVolumeIconClick();
             }
@@ -103,6 +120,12 @@
         mExpandIcon = requireViewById(R.id.ambient_expand_icon);
         mExpandIcon.setOnClickListener(v -> {
             setExpanded(!mExpanded);
+            if (mUiEventLogger != null) {
+                HearingDevicesUiEvent uiEvent = mExpanded
+                        ? HearingDevicesUiEvent.HEARING_DEVICES_AMBIENT_EXPAND_CONTROLS
+                        : HearingDevicesUiEvent.HEARING_DEVICES_AMBIENT_COLLAPSE_CONTROLS;
+                mUiEventLogger.log(uiEvent, mLaunchSourceId);
+            }
             if (mListener != null) {
                 mListener.onExpandIconClick();
             }
@@ -243,6 +266,11 @@
         updateVolumeLevel();
     }
 
+    void setUiEventLogger(HearingDevicesUiEventLogger uiEventLogger, int launchSourceId) {
+        mUiEventLogger = uiEventLogger;
+        mLaunchSourceId = launchSourceId;
+    }
+
     private void updateVolumeLevel() {
         int leftLevel, rightLevel;
         if (mExpanded) {
diff --git a/packages/SystemUI/src/com/android/systemui/accessibility/hearingaid/HearingDevicesDialogDelegate.java b/packages/SystemUI/src/com/android/systemui/accessibility/hearingaid/HearingDevicesDialogDelegate.java
index 22ecb0a..786d27a 100644
--- a/packages/SystemUI/src/com/android/systemui/accessibility/hearingaid/HearingDevicesDialogDelegate.java
+++ b/packages/SystemUI/src/com/android/systemui/accessibility/hearingaid/HearingDevicesDialogDelegate.java
@@ -382,6 +382,7 @@
 
     private void setupAmbientControls(CachedBluetoothDevice activeHearingDevice) {
         final AmbientVolumeLayout ambientLayout = mDialog.requireViewById(R.id.ambient_layout);
+        ambientLayout.setUiEventLogger(mUiEventLogger, mLaunchSourceId);
         mAmbientController = new AmbientVolumeUiController(
                 mDialog.getContext(), mLocalBluetoothManager, ambientLayout);
         mAmbientController.setShowUiWhenLocalDataExist(false);
diff --git a/packages/SystemUI/src/com/android/systemui/accessibility/hearingaid/HearingDevicesUiEvent.kt b/packages/SystemUI/src/com/android/systemui/accessibility/hearingaid/HearingDevicesUiEvent.kt
index 9e77b02..fe1d504 100644
--- a/packages/SystemUI/src/com/android/systemui/accessibility/hearingaid/HearingDevicesUiEvent.kt
+++ b/packages/SystemUI/src/com/android/systemui/accessibility/hearingaid/HearingDevicesUiEvent.kt
@@ -29,7 +29,17 @@
     @UiEvent(doc = "Click on the device gear to enter device detail page")
     HEARING_DEVICES_GEAR_CLICK(1853),
     @UiEvent(doc = "Select a preset from preset spinner") HEARING_DEVICES_PRESET_SELECT(1854),
-    @UiEvent(doc = "Click on related tool") HEARING_DEVICES_RELATED_TOOL_CLICK(1856);
+    @UiEvent(doc = "Click on related tool") HEARING_DEVICES_RELATED_TOOL_CLICK(1856),
+    @UiEvent(doc = "Change the ambient volume with unified control")
+    HEARING_DEVICES_AMBIENT_CHANGE_UNIFIED(2149),
+    @UiEvent(doc = "Change the ambient volume with separated control")
+    HEARING_DEVICES_AMBIENT_CHANGE_SEPARATED(2150),
+    @UiEvent(doc = "Mute the ambient volume") HEARING_DEVICES_AMBIENT_MUTE(2151),
+    @UiEvent(doc = "Unmute the ambient volume") HEARING_DEVICES_AMBIENT_UNMUTE(2152),
+    @UiEvent(doc = "Expand the ambient volume controls")
+    HEARING_DEVICES_AMBIENT_EXPAND_CONTROLS(2153),
+    @UiEvent(doc = "Collapse the ambient volume controls")
+    HEARING_DEVICES_AMBIENT_COLLAPSE_CONTROLS(2154);
 
     override fun getId(): Int = this.id
 }
diff --git a/packages/SystemUI/src/com/android/systemui/communal/ui/viewmodel/CommunalViewModel.kt b/packages/SystemUI/src/com/android/systemui/communal/ui/viewmodel/CommunalViewModel.kt
index cce1ae1..6473b1c 100644
--- a/packages/SystemUI/src/com/android/systemui/communal/ui/viewmodel/CommunalViewModel.kt
+++ b/packages/SystemUI/src/com/android/systemui/communal/ui/viewmodel/CommunalViewModel.kt
@@ -237,7 +237,15 @@
         with(mediaHost) {
             expansion = MediaHostState.EXPANDED
             expandedMatchesParentHeight = true
-            showsOnlyActiveMedia = false
+            if (v2FlagEnabled()) {
+                // Only show active media to match lock screen, not resumable media, which can
+                // persist
+                // for up to 2 days.
+                showsOnlyActiveMedia = true
+            } else {
+                // Maintain old behavior on tablet until V2 flag rolls out.
+                showsOnlyActiveMedia = false
+            }
             falsingProtectionNeeded = false
             disablePagination = true
             init(MediaHierarchyManager.LOCATION_COMMUNAL_HUB)
diff --git a/packages/SystemUI/src/com/android/systemui/media/controls/ui/binder/MediaControlViewBinder.kt b/packages/SystemUI/src/com/android/systemui/media/controls/ui/binder/MediaControlViewBinder.kt
index 6d796d9..3f53820 100644
--- a/packages/SystemUI/src/com/android/systemui/media/controls/ui/binder/MediaControlViewBinder.kt
+++ b/packages/SystemUI/src/com/android/systemui/media/controls/ui/binder/MediaControlViewBinder.kt
@@ -49,9 +49,10 @@
 import com.android.systemui.media.controls.ui.view.MediaViewHolder
 import com.android.systemui.media.controls.ui.viewmodel.MediaActionViewModel
 import com.android.systemui.media.controls.ui.viewmodel.MediaControlViewModel
-import com.android.systemui.media.controls.ui.viewmodel.MediaControlViewModel.Companion.MEDIA_PLAYER_SCRIM_CENTER_ALPHA
 import com.android.systemui.media.controls.ui.viewmodel.MediaControlViewModel.Companion.MEDIA_PLAYER_SCRIM_END_ALPHA
+import com.android.systemui.media.controls.ui.viewmodel.MediaControlViewModel.Companion.MEDIA_PLAYER_SCRIM_END_ALPHA_LEGACY
 import com.android.systemui.media.controls.ui.viewmodel.MediaControlViewModel.Companion.MEDIA_PLAYER_SCRIM_START_ALPHA
+import com.android.systemui.media.controls.ui.viewmodel.MediaControlViewModel.Companion.MEDIA_PLAYER_SCRIM_START_ALPHA_LEGACY
 import com.android.systemui.media.controls.ui.viewmodel.MediaControlViewModel.Companion.SEMANTIC_ACTIONS_ALL
 import com.android.systemui.media.controls.ui.viewmodel.MediaControlViewModel.Companion.SEMANTIC_ACTIONS_COMPACT
 import com.android.systemui.media.controls.ui.viewmodel.MediaOutputSwitcherViewModel
@@ -537,18 +538,24 @@
         height: Int,
     ): LayerDrawable {
         val albumArt = MediaArtworkHelper.getScaledBackground(context, artworkIcon, width, height)
-        val alpha =
+        val startAlpha =
             if (Flags.mediaControlsA11yColors()) {
-                MEDIA_PLAYER_SCRIM_CENTER_ALPHA
-            } else {
                 MEDIA_PLAYER_SCRIM_START_ALPHA
+            } else {
+                MEDIA_PLAYER_SCRIM_START_ALPHA_LEGACY
+            }
+        val endAlpha =
+            if (Flags.mediaControlsA11yColors()) {
+                MEDIA_PLAYER_SCRIM_END_ALPHA
+            } else {
+                MEDIA_PLAYER_SCRIM_END_ALPHA_LEGACY
             }
         return MediaArtworkHelper.setUpGradientColorOnDrawable(
             albumArt,
             context.getDrawable(R.drawable.qs_media_scrim)?.mutate() as GradientDrawable,
             mutableColorScheme,
-            alpha,
-            MEDIA_PLAYER_SCRIM_END_ALPHA,
+            startAlpha,
+            endAlpha,
         )
     }
 
diff --git a/packages/SystemUI/src/com/android/systemui/media/controls/ui/binder/SeekBarObserver.kt b/packages/SystemUI/src/com/android/systemui/media/controls/ui/binder/SeekBarObserver.kt
index 34f7c4d..c9716be 100644
--- a/packages/SystemUI/src/com/android/systemui/media/controls/ui/binder/SeekBarObserver.kt
+++ b/packages/SystemUI/src/com/android/systemui/media/controls/ui/binder/SeekBarObserver.kt
@@ -18,6 +18,9 @@
 
 import android.animation.Animator
 import android.animation.ObjectAnimator
+import android.icu.text.MeasureFormat
+import android.icu.util.Measure
+import android.icu.util.MeasureUnit
 import android.text.format.DateUtils
 import androidx.annotation.UiThread
 import androidx.lifecycle.Observer
@@ -28,8 +31,11 @@
 import com.android.systemui.media.controls.ui.view.MediaViewHolder
 import com.android.systemui.media.controls.ui.viewmodel.SeekBarViewModel
 import com.android.systemui.res.R
+import java.util.Locale
 
 private const val TAG = "SeekBarObserver"
+private const val MIN_IN_SEC = 60
+private const val HOUR_IN_SEC = MIN_IN_SEC * 60
 
 /**
  * Observer for changes from SeekBarViewModel.
@@ -127,10 +133,9 @@
         }
 
         holder.seekBar.setMax(data.duration)
-        val totalTimeString =
-            DateUtils.formatElapsedTime(data.duration / DateUtils.SECOND_IN_MILLIS)
+        val totalTimeDescription = formatTimeContentDescription(data.duration)
         if (data.scrubbing) {
-            holder.scrubbingTotalTimeView.text = totalTimeString
+            holder.scrubbingTotalTimeView.text = formatTimeLabel(data.duration)
         }
 
         data.elapsedTime?.let {
@@ -148,20 +153,62 @@
                 }
             }
 
-            val elapsedTimeString = DateUtils.formatElapsedTime(it / DateUtils.SECOND_IN_MILLIS)
+            val elapsedTimeDescription = formatTimeContentDescription(it)
             if (data.scrubbing) {
-                holder.scrubbingElapsedTimeView.text = elapsedTimeString
+                holder.scrubbingElapsedTimeView.text = formatTimeLabel(it)
             }
 
             holder.seekBar.contentDescription =
                 holder.seekBar.context.getString(
                     R.string.controls_media_seekbar_description,
-                    elapsedTimeString,
-                    totalTimeString
+                    elapsedTimeDescription,
+                    totalTimeDescription,
                 )
         }
     }
 
+    /** Returns a time string suitable for display, e.g. "12:34" */
+    private fun formatTimeLabel(milliseconds: Int): CharSequence {
+        return DateUtils.formatElapsedTime(milliseconds / DateUtils.SECOND_IN_MILLIS)
+    }
+
+    /**
+     * Returns a time string suitable for content description, e.g. "12 minutes 34 seconds"
+     *
+     * Follows same logic as Chronometer#formatDuration
+     */
+    private fun formatTimeContentDescription(milliseconds: Int): CharSequence {
+        var seconds = milliseconds / DateUtils.SECOND_IN_MILLIS
+
+        val hours =
+            if (seconds >= HOUR_IN_SEC) {
+                seconds / HOUR_IN_SEC
+            } else {
+                0
+            }
+        seconds -= hours * HOUR_IN_SEC
+
+        val minutes =
+            if (seconds >= MIN_IN_SEC) {
+                seconds / MIN_IN_SEC
+            } else {
+                0
+            }
+        seconds -= minutes * MIN_IN_SEC
+
+        val measures = arrayListOf<Measure>()
+        if (hours > 0) {
+            measures.add(Measure(hours, MeasureUnit.HOUR))
+        }
+        if (minutes > 0) {
+            measures.add(Measure(minutes, MeasureUnit.MINUTE))
+        }
+        measures.add(Measure(seconds, MeasureUnit.SECOND))
+
+        return MeasureFormat.getInstance(Locale.getDefault(), MeasureFormat.FormatWidth.WIDE)
+            .formatMeasures(*measures.toTypedArray())
+    }
+
     @VisibleForTesting
     open fun buildResetAnimator(targetTime: Int): Animator {
         val animator =
@@ -169,7 +216,7 @@
                 holder.seekBar,
                 "progress",
                 holder.seekBar.progress,
-                targetTime + RESET_ANIMATION_DURATION_MS
+                targetTime + RESET_ANIMATION_DURATION_MS,
             )
         animator.setAutoCancel(true)
         animator.duration = RESET_ANIMATION_DURATION_MS.toLong()
diff --git a/packages/SystemUI/src/com/android/systemui/media/controls/ui/controller/MediaControlPanel.java b/packages/SystemUI/src/com/android/systemui/media/controls/ui/controller/MediaControlPanel.java
index 39c08da..694a4c7 100644
--- a/packages/SystemUI/src/com/android/systemui/media/controls/ui/controller/MediaControlPanel.java
+++ b/packages/SystemUI/src/com/android/systemui/media/controls/ui/controller/MediaControlPanel.java
@@ -23,7 +23,10 @@
 import static com.android.systemui.Flags.mediaLockscreenLaunchAnimation;
 import static com.android.systemui.media.controls.domain.pipeline.MediaActionsKt.getNotificationActions;
 import static com.android.systemui.media.controls.shared.model.SmartspaceMediaDataKt.NUM_REQUIRED_RECOMMENDATIONS;
-import static com.android.systemui.media.controls.ui.viewmodel.MediaControlViewModel.MEDIA_PLAYER_SCRIM_CENTER_ALPHA;
+import static com.android.systemui.media.controls.ui.viewmodel.MediaControlViewModel.MEDIA_PLAYER_SCRIM_END_ALPHA;
+import static com.android.systemui.media.controls.ui.viewmodel.MediaControlViewModel.MEDIA_PLAYER_SCRIM_END_ALPHA_LEGACY;
+import static com.android.systemui.media.controls.ui.viewmodel.MediaControlViewModel.MEDIA_PLAYER_SCRIM_START_ALPHA;
+import static com.android.systemui.media.controls.ui.viewmodel.MediaControlViewModel.MEDIA_PLAYER_SCRIM_START_ALPHA_LEGACY;
 
 import android.animation.Animator;
 import android.animation.AnimatorInflater;
@@ -176,9 +179,7 @@
     protected static final int SMARTSPACE_CARD_DISMISS_EVENT = 761;
 
     private static final float REC_MEDIA_COVER_SCALE_FACTOR = 1.25f;
-    private static final float MEDIA_SCRIM_START_ALPHA = 0.25f;
     private static final float MEDIA_REC_SCRIM_START_ALPHA = 0.15f;
-    private static final float MEDIA_PLAYER_SCRIM_END_ALPHA = 1.0f;
     private static final float MEDIA_REC_SCRIM_END_ALPHA = 1.0f;
 
     private static final Intent SETTINGS_INTENT = new Intent(ACTION_MEDIA_CONTROLS_SETTINGS);
@@ -1093,11 +1094,12 @@
         Drawable albumArt = getScaledBackground(artworkIcon, width, height);
         GradientDrawable gradient = (GradientDrawable) mContext.getDrawable(
                 R.drawable.qs_media_scrim).mutate();
-        float startAlpha = (Flags.mediaControlsA11yColors())
-                ? MEDIA_PLAYER_SCRIM_CENTER_ALPHA
-                : MEDIA_SCRIM_START_ALPHA;
+        if (Flags.mediaControlsA11yColors()) {
+            return setupGradientColorOnDrawable(albumArt, gradient, mutableColorScheme,
+                    MEDIA_PLAYER_SCRIM_START_ALPHA, MEDIA_PLAYER_SCRIM_END_ALPHA);
+        }
         return setupGradientColorOnDrawable(albumArt, gradient, mutableColorScheme,
-                startAlpha, MEDIA_PLAYER_SCRIM_END_ALPHA);
+                MEDIA_PLAYER_SCRIM_START_ALPHA_LEGACY, MEDIA_PLAYER_SCRIM_END_ALPHA_LEGACY);
     }
 
     @VisibleForTesting
diff --git a/packages/SystemUI/src/com/android/systemui/media/controls/ui/controller/MediaViewController.kt b/packages/SystemUI/src/com/android/systemui/media/controls/ui/controller/MediaViewController.kt
index 198155b..b687dce 100644
--- a/packages/SystemUI/src/com/android/systemui/media/controls/ui/controller/MediaViewController.kt
+++ b/packages/SystemUI/src/com/android/systemui/media/controls/ui/controller/MediaViewController.kt
@@ -1137,6 +1137,7 @@
     ) {
         gutsViewHolder.gutsText.setTypeface(menuTF)
         gutsViewHolder.dismissText.setTypeface(menuTF)
+        gutsViewHolder.cancelText.setTypeface(menuTF)
         titleText.setTypeface(titleTF)
         artistText.setTypeface(artistTF)
         seamlessText.setTypeface(menuTF)
diff --git a/packages/SystemUI/src/com/android/systemui/media/controls/ui/viewmodel/MediaControlViewModel.kt b/packages/SystemUI/src/com/android/systemui/media/controls/ui/viewmodel/MediaControlViewModel.kt
index 9153e17..bcda485 100644
--- a/packages/SystemUI/src/com/android/systemui/media/controls/ui/viewmodel/MediaControlViewModel.kt
+++ b/packages/SystemUI/src/com/android/systemui/media/controls/ui/viewmodel/MediaControlViewModel.kt
@@ -419,8 +419,10 @@
 
         const val TURBULENCE_NOISE_PLAY_MS_DURATION = 7500L
         @Deprecated("Remove with media_controls_a11y_colors flag")
-        const val MEDIA_PLAYER_SCRIM_START_ALPHA = 0.25f
-        const val MEDIA_PLAYER_SCRIM_CENTER_ALPHA = 0.75f
-        const val MEDIA_PLAYER_SCRIM_END_ALPHA = 1.0f
+        const val MEDIA_PLAYER_SCRIM_START_ALPHA_LEGACY = 0.25f
+        @Deprecated("Remove with media_controls_a11y_colors flag")
+        const val MEDIA_PLAYER_SCRIM_END_ALPHA_LEGACY = 1.0f
+        const val MEDIA_PLAYER_SCRIM_START_ALPHA = 0.65f
+        const val MEDIA_PLAYER_SCRIM_END_ALPHA = 0.75f
     }
 }
diff --git a/packages/SystemUI/src/com/android/systemui/qs/QSEvents.kt b/packages/SystemUI/src/com/android/systemui/qs/QSEvents.kt
index 24bb16a..3a81102 100644
--- a/packages/SystemUI/src/com/android/systemui/qs/QSEvents.kt
+++ b/packages/SystemUI/src/com/android/systemui/qs/QSEvents.kt
@@ -27,9 +27,7 @@
         private set
 
     fun setLoggerForTesting(): UiEventLoggerFake {
-        return UiEventLoggerFake().also {
-            qsUiEventsLogger = it
-        }
+        return UiEventLoggerFake().also { qsUiEventsLogger = it }
     }
 
     fun resetLogger() {
@@ -40,32 +38,28 @@
 enum class QSEvent(private val _id: Int) : UiEventLogger.UiEventEnum {
     @UiEvent(doc = "Tile clicked. It has an instance id and a spec (or packageName)")
     QS_ACTION_CLICK(387),
-
-    @UiEvent(doc = "Tile secondary button clicked. " +
-            "It has an instance id and a spec (or packageName)")
+    @UiEvent(
+        doc =
+            "Tile secondary button clicked. " + "It has an instance id and a spec (or packageName)"
+    )
     QS_ACTION_SECONDARY_CLICK(388),
-
     @UiEvent(doc = "Tile long clicked. It has an instance id and a spec (or packageName)")
     QS_ACTION_LONG_PRESS(389),
-
-    @UiEvent(doc = "Quick Settings panel expanded")
-    QS_PANEL_EXPANDED(390),
-
-    @UiEvent(doc = "Quick Settings panel collapsed")
-    QS_PANEL_COLLAPSED(391),
-
-    @UiEvent(doc = "Tile visible in Quick Settings panel. The tile may be in a different page. " +
-            "It has an instance id and a spec (or packageName)")
+    @UiEvent(doc = "Quick Settings panel expanded") QS_PANEL_EXPANDED(390),
+    @UiEvent(doc = "Quick Settings panel collapsed") QS_PANEL_COLLAPSED(391),
+    @UiEvent(
+        doc =
+            "Tile visible in Quick Settings panel. The tile may be in a different page. " +
+                "It has an instance id and a spec (or packageName)"
+    )
     QS_TILE_VISIBLE(392),
-
-    @UiEvent(doc = "Quick Quick Settings panel expanded")
-    QQS_PANEL_EXPANDED(393),
-
-    @UiEvent(doc = "Quick Quick Settings panel collapsed")
-    QQS_PANEL_COLLAPSED(394),
-
-    @UiEvent(doc = "Tile visible in Quick Quick Settings panel. " +
-            "It has an instance id and a spec (or packageName)")
+    @UiEvent(doc = "Quick Quick Settings panel expanded") QQS_PANEL_EXPANDED(393),
+    @UiEvent(doc = "Quick Quick Settings panel collapsed") QQS_PANEL_COLLAPSED(394),
+    @UiEvent(
+        doc =
+            "Tile visible in Quick Quick Settings panel. " +
+                "It has an instance id and a spec (or packageName)"
+    )
     QQS_TILE_VISIBLE(395);
 
     override fun getId() = _id
@@ -73,47 +67,32 @@
 
 enum class QSEditEvent(private val _id: Int) : UiEventLogger.UiEventEnum {
 
-    @UiEvent(doc = "Tile removed from current tiles")
-    QS_EDIT_REMOVE(210),
-
-    @UiEvent(doc = "Tile added to current tiles")
-    QS_EDIT_ADD(211),
-
-    @UiEvent(doc = "Tile moved")
-    QS_EDIT_MOVE(212),
-
-    @UiEvent(doc = "QS customizer open")
-    QS_EDIT_OPEN(213),
-
-    @UiEvent(doc = "QS customizer closed")
-    QS_EDIT_CLOSED(214),
-
-    @UiEvent(doc = "QS tiles reset")
-    QS_EDIT_RESET(215);
+    @UiEvent(doc = "Tile removed from current tiles") QS_EDIT_REMOVE(210),
+    @UiEvent(doc = "Tile added to current tiles") QS_EDIT_ADD(211),
+    @UiEvent(doc = "Tile moved") QS_EDIT_MOVE(212),
+    @UiEvent(doc = "QS customizer open") QS_EDIT_OPEN(213),
+    @UiEvent(doc = "QS customizer closed") QS_EDIT_CLOSED(214),
+    @UiEvent(doc = "QS tiles reset") QS_EDIT_RESET(215),
+    @UiEvent(doc = "QS edit mode resize tile to large") QS_EDIT_RESIZE_LARGE(2122),
+    @UiEvent(doc = "QS edit mode resize tile to small") QS_EDIT_RESIZE_SMALL(2123);
 
     override fun getId() = _id
 }
 
 /**
- * Events from the QS DND tile dialog. {@see QSZenModeDialogMetricsLogger}
- * Other names for DND (Do Not Disturb) include "Zen" and "Priority mode".
+ * Events from the QS DND tile dialog. {@see QSZenModeDialogMetricsLogger} Other names for DND (Do
+ * Not Disturb) include "Zen" and "Priority mode".
  */
 enum class QSDndEvent(private val _id: Int) : UiEventLogger.UiEventEnum {
-    @UiEvent(doc = "User selected an option on the DND dialog")
-    QS_DND_CONDITION_SELECT(420),
-
+    @UiEvent(doc = "User selected an option on the DND dialog") QS_DND_CONDITION_SELECT(420),
     @UiEvent(doc = "User increased countdown duration of DND from the DND dialog")
     QS_DND_TIME_UP(422),
-
     @UiEvent(doc = "User decreased countdown duration of DND from the DND dialog")
     QS_DND_TIME_DOWN(423),
-
     @UiEvent(doc = "User enabled DND from the QS DND dialog to last until manually turned off")
     QS_DND_DIALOG_ENABLE_FOREVER(946),
-
     @UiEvent(doc = "User enabled DND from the QS DND dialog to last until the next alarm goes off")
     QS_DND_DIALOG_ENABLE_UNTIL_ALARM(947),
-
     @UiEvent(doc = "User enabled DND from the QS DND dialog to last until countdown is done")
     QS_DND_DIALOG_ENABLE_UNTIL_COUNTDOWN(948);
 
@@ -121,29 +100,17 @@
 }
 
 enum class QSUserSwitcherEvent(private val _id: Int) : UiEventLogger.UiEventEnum {
-    @UiEvent(doc = "The current user has been switched in the detail panel")
-    QS_USER_SWITCH(424),
-
-    @UiEvent(doc = "User switcher QS dialog open")
-    QS_USER_DETAIL_OPEN(425),
-
-    @UiEvent(doc = "User switcher QS dialog closed")
-    QS_USER_DETAIL_CLOSE(426),
-
-    @UiEvent(doc = "User switcher QS dialog more settings pressed")
-    QS_USER_MORE_SETTINGS(427),
-
-    @UiEvent(doc = "The user has added a guest in the detail panel")
-    QS_USER_GUEST_ADD(754),
-
+    @UiEvent(doc = "The current user has been switched in the detail panel") QS_USER_SWITCH(424),
+    @UiEvent(doc = "User switcher QS dialog open") QS_USER_DETAIL_OPEN(425),
+    @UiEvent(doc = "User switcher QS dialog closed") QS_USER_DETAIL_CLOSE(426),
+    @UiEvent(doc = "User switcher QS dialog more settings pressed") QS_USER_MORE_SETTINGS(427),
+    @UiEvent(doc = "The user has added a guest in the detail panel") QS_USER_GUEST_ADD(754),
     @UiEvent(doc = "The user selected 'Start over' after switching to the existing Guest user")
     QS_USER_GUEST_WIPE(755),
-
     @UiEvent(doc = "The user selected 'Yes, continue' after switching to the existing Guest user")
     QS_USER_GUEST_CONTINUE(756),
-
     @UiEvent(doc = "The user has pressed 'Remove guest' in the detail panel")
     QS_USER_GUEST_REMOVE(757);
 
     override fun getId() = _id
-}
\ No newline at end of file
+}
diff --git a/packages/SystemUI/src/com/android/systemui/qs/panels/domain/interactor/IconTilesInteractor.kt b/packages/SystemUI/src/com/android/systemui/qs/panels/domain/interactor/IconTilesInteractor.kt
index 482cd40..3f279b0 100644
--- a/packages/SystemUI/src/com/android/systemui/qs/panels/domain/interactor/IconTilesInteractor.kt
+++ b/packages/SystemUI/src/com/android/systemui/qs/panels/domain/interactor/IconTilesInteractor.kt
@@ -16,15 +16,18 @@
 
 package com.android.systemui.qs.panels.domain.interactor
 
+import com.android.internal.logging.UiEventLogger
 import com.android.systemui.dagger.SysUISingleton
 import com.android.systemui.dagger.qualifiers.Application
 import com.android.systemui.log.LogBuffer
 import com.android.systemui.log.core.LogLevel
+import com.android.systemui.qs.QSEditEvent
 import com.android.systemui.qs.panels.data.repository.DefaultLargeTilesRepository
 import com.android.systemui.qs.panels.data.repository.LargeTileSpanRepository
 import com.android.systemui.qs.panels.shared.model.PanelsLog
 import com.android.systemui.qs.pipeline.domain.interactor.CurrentTilesInteractor
 import com.android.systemui.qs.pipeline.shared.TileSpec
+import com.android.systemui.qs.pipeline.shared.metricSpec
 import javax.inject.Inject
 import kotlinx.coroutines.CoroutineScope
 import kotlinx.coroutines.flow.SharingStarted
@@ -40,6 +43,7 @@
     private val repo: DefaultLargeTilesRepository,
     private val currentTilesInteractor: CurrentTilesInteractor,
     private val preferencesInteractor: QSPreferencesInteractor,
+    private val uiEventLogger: UiEventLogger,
     largeTilesSpanRepo: LargeTileSpanRepository,
     @PanelsLog private val logBuffer: LogBuffer,
     @Application private val applicationScope: CoroutineScope,
@@ -70,8 +74,18 @@
         val isIcon = !largeTilesSpecs.value.contains(spec)
         if (toIcon && !isIcon) {
             preferencesInteractor.setLargeTilesSpecs(largeTilesSpecs.value - spec)
+            uiEventLogger.log(
+                /* event= */ QSEditEvent.QS_EDIT_RESIZE_SMALL,
+                /* uid= */ 0,
+                /* packageName= */ spec.metricSpec,
+            )
         } else if (!toIcon && isIcon) {
             preferencesInteractor.setLargeTilesSpecs(largeTilesSpecs.value + spec)
+            uiEventLogger.log(
+                /* event= */ QSEditEvent.QS_EDIT_RESIZE_LARGE,
+                /* uid= */ 0,
+                /* packageName= */ spec.metricSpec,
+            )
         }
     }
 
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 4adc1a5..20b44d7 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
@@ -22,7 +22,9 @@
 import android.icu.text.DisplayContext
 import android.provider.Settings
 import android.view.ViewGroup
+import androidx.compose.material3.ColorScheme
 import androidx.compose.runtime.getValue
+import androidx.compose.ui.graphics.Color
 import com.android.app.tracing.coroutines.launchTraced as launch
 import com.android.systemui.battery.BatteryMeterViewController
 import com.android.systemui.lifecycle.ExclusiveActivatable
@@ -244,11 +246,29 @@
 
     /** Represents the background highlight of a header icons chip. */
     sealed interface HeaderChipHighlight {
-        data object None : HeaderChipHighlight
 
-        data object Weak : HeaderChipHighlight
+        fun backgroundColor(colorScheme: ColorScheme): Color
 
-        data object Strong : HeaderChipHighlight
+        fun foregroundColor(colorScheme: ColorScheme): Color
+
+        data object None : HeaderChipHighlight {
+            override fun backgroundColor(colorScheme: ColorScheme): Color = Color.Unspecified
+
+            override fun foregroundColor(colorScheme: ColorScheme): Color = colorScheme.primary
+        }
+
+        data object Weak : HeaderChipHighlight {
+            override fun backgroundColor(colorScheme: ColorScheme): Color =
+                colorScheme.primary.copy(alpha = 0.1f)
+
+            override fun foregroundColor(colorScheme: ColorScheme): Color = colorScheme.primary
+        }
+
+        data object Strong : HeaderChipHighlight {
+            override fun backgroundColor(colorScheme: ColorScheme): Color = colorScheme.secondary
+
+            override fun foregroundColor(colorScheme: ColorScheme): Color = colorScheme.onSecondary
+        }
     }
 
     private fun getFormatFromPattern(pattern: String?): DateFormat {
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/BundleEntry.java b/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/BundleEntry.java
index 0e3f103..24ab695 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/BundleEntry.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/BundleEntry.java
@@ -21,9 +21,14 @@
 import static android.app.NotificationChannel.RECS_ID;
 import static android.app.NotificationChannel.SOCIAL_MEDIA_ID;
 
+import android.app.Notification;
+import android.content.Context;
+import android.os.Build;
+
 import androidx.annotation.Nullable;
 import androidx.annotation.VisibleForTesting;
 
+import com.android.systemui.statusbar.notification.icon.IconPack;
 import com.android.systemui.statusbar.notification.row.ExpandableNotificationRow;
 
 import java.util.List;
@@ -79,6 +84,43 @@
         public EntryAdapter getGroupRoot() {
             return this;
         }
+
+        @Override
+        public boolean isClearable() {
+            // TODO(b/394483200): check whether all of the children are clearable, when implemented
+            return true;
+        }
+
+        @Override
+        public int getTargetSdk() {
+            return Build.VERSION_CODES.CUR_DEVELOPMENT;
+        }
+
+        @Override
+        public String getSummarization() {
+            return null;
+        }
+
+        @Override
+        public int getContrastedColor(Context context, boolean isLowPriority, int backgroundColor) {
+            return Notification.COLOR_DEFAULT;
+        }
+
+        @Override
+        public boolean canPeek() {
+            return false;
+        }
+
+        @Override
+        public long getWhen() {
+            return 0;
+        }
+
+        @Override
+        public IconPack getIcons() {
+            // TODO(b/396446620): implement bundle icons
+            return null;
+        }
     }
 
     public static final List<BundleEntry> ROOT_BUNDLES = List.of(
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/EntryAdapter.java b/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/EntryAdapter.java
index 4df81c9..6431cac 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/EntryAdapter.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/EntryAdapter.java
@@ -16,9 +16,12 @@
 
 package com.android.systemui.statusbar.notification.collection;
 
+import android.content.Context;
+
 import androidx.annotation.NonNull;
 import androidx.annotation.Nullable;
 
+import com.android.systemui.statusbar.notification.icon.IconPack;
 import com.android.systemui.statusbar.notification.row.ExpandableNotificationRow;
 
 /**
@@ -59,9 +62,49 @@
     EntryAdapter getGroupRoot();
 
     /**
+     * @return whether the row can be removed with the 'Clear All' action
+     */
+    boolean isClearable();
+
+    /**
      * Returns whether the entry is attached to the current shade list
      */
     default boolean isAttached() {
         return getParent() != null;
     }
+
+    /**
+     * Returns the target sdk of the package that owns this entry.
+     */
+    int getTargetSdk();
+
+    /**
+     * Returns the summarization for this entry, if there is one
+     */
+    @Nullable String getSummarization();
+
+    /**
+     * Performs any steps needed to set or reset data before an inflation or reinflation.
+     */
+    default void prepareForInflation() {}
+
+    /**
+     * Gets a color that would have sufficient contrast on the given background color.
+     */
+    int getContrastedColor(Context context, boolean isLowPriority, int backgroundColor);
+
+    /**
+     * Whether this entry can peek on screen as a heads up view
+     */
+    boolean canPeek();
+
+    /**
+     * Returns the visible 'time', in milliseconds, of the entry
+     */
+    long getWhen();
+
+    /**
+     * Retrieves the pack of icons associated with this entry
+     */
+    IconPack getIcons();
 }
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 90f9525..698a563 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
@@ -78,6 +78,7 @@
 import com.android.systemui.statusbar.notification.row.shared.HeadsUpStatusBarModel;
 import com.android.systemui.statusbar.notification.row.shared.NotificationContentModel;
 import com.android.systemui.statusbar.notification.row.shared.NotificationRowContentBinderRefactor;
+import com.android.systemui.statusbar.notification.shared.NotificationBundleUi;
 import com.android.systemui.statusbar.notification.stack.PriorityBucket;
 import com.android.systemui.util.ListenerSet;
 
@@ -309,6 +310,47 @@
             }
             return null;
         }
+
+        @Override
+        public boolean isClearable() {
+            return NotificationEntry.this.isClearable();
+        }
+
+        @Override
+        public int getTargetSdk() {
+            return NotificationEntry.this.targetSdk;
+        }
+
+        @Override
+        public String getSummarization() {
+            return getRanking().getSummarization();
+        }
+
+        @Override
+        public void prepareForInflation() {
+            getSbn().clearPackageContext();
+        }
+
+        @Override
+        public int getContrastedColor(Context context, boolean isLowPriority, int backgroundColor) {
+            return NotificationEntry.this.getContrastedColor(
+                    context, isLowPriority, backgroundColor);
+        }
+
+        @Override
+        public boolean canPeek() {
+            return isStickyAndNotDemoted();
+        }
+
+        @Override
+        public long getWhen() {
+            return getSbn().getNotification().getWhen();
+        }
+
+        @Override
+        public IconPack getIcons() {
+            return NotificationEntry.this.getIcons();
+        }
     }
 
     public EntryAdapter getEntryAdapter() {
@@ -580,6 +622,7 @@
     }
 
     public boolean hasFinishedInitialization() {
+        NotificationBundleUi.assertInLegacyMode();
         return initializationTime != -1
                 && SystemClock.elapsedRealtime() > initializationTime + INITIALIZATION_DELAY;
     }
@@ -663,10 +706,12 @@
     }
 
     public void resetInitializationTime() {
+        NotificationBundleUi.assertInLegacyMode();
         initializationTime = -1;
     }
 
     public void setInitializationTime(long time) {
+        NotificationBundleUi.assertInLegacyMode();
         if (initializationTime == -1) {
             initializationTime = time;
         }
@@ -683,9 +728,13 @@
      * @return {@code true} if we are a media notification
      */
     public boolean isMediaNotification() {
-        if (row == null) return false;
+        if (NotificationBundleUi.isEnabled()) {
+            return getSbn().getNotification().isMediaNotification();
+        } else {
+            if (row == null) return false;
 
-        return row.isMediaRow();
+            return row.isMediaRow();
+        }
     }
 
     public boolean containsCustomViews() {
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/ShadeListBuilder.java b/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/ShadeListBuilder.java
index 9c1d073..ac11c15 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/ShadeListBuilder.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/ShadeListBuilder.java
@@ -67,6 +67,7 @@
 import com.android.systemui.statusbar.notification.collection.listbuilder.pluggable.NotifStabilityManager;
 import com.android.systemui.statusbar.notification.collection.listbuilder.pluggable.Pluggable;
 import com.android.systemui.statusbar.notification.collection.notifcollection.CollectionReadyForBuildListener;
+import com.android.systemui.statusbar.notification.shared.NotificationBundleUi;
 import com.android.systemui.statusbar.notification.stack.NotificationPriorityBucketKt;
 import com.android.systemui.util.Assert;
 import com.android.systemui.util.NamedListenerSet;
@@ -1282,7 +1283,13 @@
         entry.getAttachState().setExcludingFilter(filter);
         if (filter != null) {
             // notification is removed from the list, so we reset its initialization time
-            entry.resetInitializationTime();
+            if (NotificationBundleUi.isEnabled()) {
+                if (entry.getRow() != null) {
+                    entry.getRow().resetInitializationTime();
+                }
+            } else {
+                entry.resetInitializationTime();
+            }
         }
         return filter != null;
     }
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/ExpandableNotificationRow.java b/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/ExpandableNotificationRow.java
index 64cd617..473460b 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/ExpandableNotificationRow.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/ExpandableNotificationRow.java
@@ -46,6 +46,7 @@
 import android.graphics.drawable.Drawable;
 import android.os.Build;
 import android.os.Bundle;
+import android.os.SystemClock;
 import android.os.SystemProperties;
 import android.os.Trace;
 import android.os.UserHandle;
@@ -117,7 +118,6 @@
 import com.android.systemui.statusbar.notification.logging.NotificationCounters;
 import com.android.systemui.statusbar.notification.people.PeopleNotificationIdentifier;
 import com.android.systemui.statusbar.notification.promoted.PromotedNotificationUiForceExpanded;
-import com.android.systemui.statusbar.notification.row.NotificationRowContentBinder.InflationFlag;
 import com.android.systemui.statusbar.notification.row.shared.AsyncGroupHeaderViewInflation;
 import com.android.systemui.statusbar.notification.row.shared.LockscreenOtpRedaction;
 import com.android.systemui.statusbar.notification.row.wrapper.NotificationCompactMessagingTemplateViewWrapper;
@@ -169,6 +169,7 @@
     private static final long RECENTLY_ALERTED_THRESHOLD_MS = TimeUnit.SECONDS.toMillis(30);
     private static final SourceType BASE_VALUE = SourceType.from("BaseValue");
     private static final SourceType FROM_PARENT = SourceType.from("FromParent(ENR)");
+    private static final long INITIALIZATION_DELAY = 400;
 
     // We don't correctly track dark mode until the content views are inflated, so always update
     // the background on first content update just in case it happens to be during a theme change.
@@ -267,7 +268,6 @@
     private NotificationContentView mPublicLayout;
     private NotificationContentView mPrivateLayout;
     private NotificationContentView[] mLayouts;
-    private int mNotificationColor;
     private ExpandableNotificationRowLogger mLogger;
     private String mLoggingKey;
     private NotificationGuts mGuts;
@@ -352,6 +352,10 @@
      */
     private boolean mSaveSpaceOnLockscreen;
 
+    // indicates when this view was first attached to a window
+    // this value will reset when the view is completely removed from the shade (ie: filtered out)
+    private long initializationTime = -1;
+
     /**
      * It is added for unit testing purpose.
      * Please do not use it for other purposes.
@@ -625,26 +629,11 @@
     }
 
     /**
-     * Marks a content view as freeable, setting it so that future inflations do not reinflate
-     * and ensuring that the view is freed when it is safe to remove.
-     *
-     * @param inflationFlag flag corresponding to the content view to be freed
-     * @deprecated By default, {@link NotificationRowContentBinder#unbindContent} will tell the
-     * view hierarchy to only free when the view is safe to remove so this method is no longer
-     * needed. Will remove when all uses are gone.
-     */
-    @Deprecated
-    public void freeContentViewWhenSafe(@InflationFlag int inflationFlag) {
-        RowContentBindParams params = mRowContentBindStage.getStageParams(mEntry);
-        params.markContentViewsFreeable(inflationFlag);
-        mRowContentBindStage.requestRebind(mEntry, null /* callback */);
-    }
-
-    /**
      * Returns whether this row is considered non-blockable (i.e. it's a non-blockable system notif
      * or is in an allowList).
      */
     public boolean getIsNonblockable() {
+        NotificationBundleUi.assertInLegacyMode();
         if (mEntry == null) {
             return true;
         }
@@ -666,9 +655,8 @@
             l.onNotificationUpdated(mEntry);
         }
         mShowingPublicInitialized = false;
-        updateNotificationColor();
         if (mMenuRow != null) {
-            mMenuRow.onNotificationUpdated(mEntry.getSbn());
+            mMenuRow.onNotificationUpdated();
             mMenuRow.setAppName(mAppName);
         }
         if (mIsSummaryWithChildren) {
@@ -727,15 +715,6 @@
     }
 
     /**
-     * Called when the notification's ranking was changed (but nothing else changed).
-     */
-    public void onNotificationRankingUpdated() {
-        if (mMenuRow != null) {
-            mMenuRow.onNotificationUpdated(mEntry.getSbn());
-        }
-    }
-
-    /**
      * Call when bubble state has changed and the button on the notification should be updated.
      */
     public void updateBubbleButton() {
@@ -746,7 +725,7 @@
 
     @VisibleForTesting
     void updateShelfIconColor() {
-        StatusBarIconView expandedIcon = mEntry.getIcons().getShelfIcon();
+        StatusBarIconView expandedIcon = getShelfIcon();
         boolean isPreL = Boolean.TRUE.equals(expandedIcon.getTag(R.id.icon_is_pre_L));
         boolean colorize = !isPreL || NotificationUtils.isGrayscale(expandedIcon,
                 ContrastColorUtil.getInstance(mContext));
@@ -767,8 +746,13 @@
         if (color != Notification.COLOR_INVALID) {
             return color;
         } else {
-            return mEntry.getContrastedColor(mContext, mIsMinimized && !isExpanded(),
-                    getBackgroundColorWithoutTint());
+            if (NotificationBundleUi.isEnabled()) {
+                return mEntryAdapter.getContrastedColor(mContext, mIsMinimized && !isExpanded(),
+                        getBackgroundColorWithoutTint());
+            } else {
+                return mEntry.getContrastedColor(mContext, mIsMinimized && !isExpanded(),
+                        getBackgroundColorWithoutTint());
+            }
         }
     }
 
@@ -870,15 +854,29 @@
         boolean customView = contractedView != null
                 && contractedView.getId()
                 != com.android.internal.R.id.status_bar_latest_event_content;
-        boolean beforeN = mEntry.targetSdk < Build.VERSION_CODES.N;
-        boolean beforeP = mEntry.targetSdk < Build.VERSION_CODES.P;
-        boolean beforeS = mEntry.targetSdk < Build.VERSION_CODES.S;
+        int targetSdk = Build.VERSION_CODES.CUR_DEVELOPMENT;
+        if (NotificationBundleUi.isEnabled()) {
+            targetSdk = mEntryAdapter.getTargetSdk();
+        } else {
+            targetSdk = mEntry.targetSdk;
+        }
+
+        boolean beforeN = targetSdk < Build.VERSION_CODES.N;
+        boolean beforeP = targetSdk < Build.VERSION_CODES.P;
+        boolean beforeS = targetSdk < Build.VERSION_CODES.S;
         int smallHeight;
 
         boolean isCallLayout = contractedView instanceof CallLayout;
         boolean isMessagingLayout = contractedView instanceof MessagingLayout
                 || contractedView instanceof ConversationLayout;
 
+        String summarization = null;
+        if (NotificationBundleUi.isEnabled()) {
+            summarization = mEntryAdapter.getSummarization();
+        } else {
+            summarization = mEntry.getRanking().getSummarization();
+        }
+
         if (customView && beforeS && !mIsSummaryWithChildren) {
             if (beforeN) {
                 smallHeight = mMaxSmallHeightBeforeN;
@@ -891,7 +889,7 @@
             smallHeight = maxExpandedHeight;
         } else if (NmSummarizationUiFlag.isEnabled()
                 && isMessagingLayout
-                && !TextUtils.isEmpty(mEntry.getRanking().getSummarization())) {
+                && !TextUtils.isEmpty(summarization)) {
             smallHeight = mMaxSmallHeightWithSummarization;
         } else {
             smallHeight = mMaxSmallHeight;
@@ -1524,7 +1522,22 @@
 
     @Override
     public boolean hasFinishedInitialization() {
-        return getEntry().hasFinishedInitialization();
+        if (NotificationBundleUi.isEnabled()) {
+            return initializationTime != -1
+                    && SystemClock.elapsedRealtime() > initializationTime + INITIALIZATION_DELAY;
+        } else {
+            return getEntry().hasFinishedInitialization();
+        }
+    }
+
+    public void resetInitializationTime() {
+        initializationTime = -1;
+    }
+
+    public void setInitializationTime(long time) {
+        if (initializationTime == -1) {
+            initializationTime = time;
+        }
     }
 
     /**
@@ -1539,7 +1552,7 @@
             return null;
         }
         if (mMenuRow.getMenuView() == null) {
-            mMenuRow.createMenu(this, mEntry.getSbn());
+            mMenuRow.createMenu(this);
             mMenuRow.setAppName(mAppName);
             FrameLayout.LayoutParams lp = new LayoutParams(LayoutParams.MATCH_PARENT,
                     LayoutParams.MATCH_PARENT);
@@ -1581,7 +1594,7 @@
         if (oldMenu != null) {
             int menuIndex = indexOfChild(oldMenu);
             removeView(oldMenu);
-            mMenuRow.createMenu(ExpandableNotificationRow.this, mEntry.getSbn());
+            mMenuRow.createMenu(ExpandableNotificationRow.this);
             mMenuRow.setAppName(mAppName);
             addView(mMenuRow.getMenuView(), menuIndex);
         }
@@ -1589,12 +1602,17 @@
             l.reinflate();
             l.reInflateViews();
         }
-        mEntry.getSbn().clearPackageContext();
+        if (NotificationBundleUi.isEnabled()) {
+            mEntryAdapter.prepareForInflation();
+        } else {
+            mEntry.getSbn().clearPackageContext();
+        }
         // TODO: Move content inflation logic out of this call
         RowContentBindParams params = mRowContentBindStage.getStageParams(mEntry);
         params.setNeedsReinflation(true);
 
-        var rebindEndCallback = mRebindingTracker.trackRebinding(mEntry.getKey());
+        var rebindEndCallback = mRebindingTracker.trackRebinding(NotificationBundleUi.isEnabled()
+                ? mEntryAdapter.getKey() : mEntry.getKey());
         mRowContentBindStage.requestRebind(mEntry, (e) -> rebindEndCallback.onFinished());
         Trace.endSection();
     }
@@ -1658,20 +1676,6 @@
         mPrivateLayout.setSingleLineWidthIndention(indention);
     }
 
-    public int getNotificationColor() {
-        return mNotificationColor;
-    }
-
-    public void updateNotificationColor() {
-        Configuration currentConfig = getResources().getConfiguration();
-        boolean nightMode = (currentConfig.uiMode & Configuration.UI_MODE_NIGHT_MASK)
-                == Configuration.UI_MODE_NIGHT_YES;
-
-        mNotificationColor = ContrastColorUtil.resolveContrastColor(mContext,
-                mEntry.getSbn().getNotification().color,
-                getBackgroundColorWithoutTint(), nightMode);
-    }
-
     public HybridNotificationView getSingleLineView() {
         return mPrivateLayout.getSingleLineView();
     }
@@ -2260,6 +2264,11 @@
         mEntry = entry;
     }
 
+    @VisibleForTesting
+    protected void setEntryAdapter(EntryAdapter entry) {
+        mEntryAdapter = entry;
+    }
+
     private final Runnable mExpireRecentlyAlertedFlag = () -> applyAudiblyAlertedRecently(false);
 
     private void applyAudiblyAlertedRecently(boolean audiblyAlertedRecently) {
@@ -2497,7 +2506,7 @@
                 mTranslateableViews.get(i).setTranslationX(0);
             }
             invalidateOutline();
-            getEntry().getIcons().getShelfIcon().setScrollX(0);
+            getShelfIcon().setScrollX(0);
         }
 
         if (mMenuRow != null) {
@@ -2616,7 +2625,7 @@
             // In order to keep the shelf in sync with this swiping, we're simply translating
             // it's icon by the same amount. The translation is already being used for the normal
             // positioning, so we can use the scrollX instead.
-            getEntry().getIcons().getShelfIcon().setScrollX((int) -translationX);
+            getShelfIcon().setScrollX((int) -translationX);
         }
 
         if (mMenuRow != null && mMenuRow.getMenuView() != null) {
@@ -2843,7 +2852,11 @@
 
     @Override
     public @NonNull StatusBarIconView getShelfIcon() {
-        return getEntry().getIcons().getShelfIcon();
+        if (NotificationBundleUi.isEnabled()) {
+            return getEntryAdapter().getIcons().getShelfIcon();
+        } else {
+            return mEntry.getIcons().getShelfIcon();
+        }
     }
 
     @Override
@@ -3072,8 +3085,11 @@
      * except for legacy use cases.
      */
     public boolean canShowHeadsUp() {
+        boolean canEntryHun = NotificationBundleUi.isEnabled()
+                ? mEntryAdapter.canPeek()
+                : mEntry.isStickyAndNotDemoted();
         if (mOnKeyguard && !isDozing() && !isBypassEnabled() &&
-                (!mEntry.isStickyAndNotDemoted()
+                (!canEntryHun
                         || (!mIgnoreLockscreenConstraints && mSaveSpaceOnLockscreen))) {
             return false;
         }
@@ -3112,7 +3128,11 @@
         }
         if (!mIsSummaryWithChildren && wasSummary) {
             // Reset the 'when' once the row stops being a summary
-            mPublicLayout.setNotificationWhen(mEntry.getSbn().getNotification().getWhen());
+            if (NotificationBundleUi.isEnabled()) {
+                mPublicLayout.setNotificationWhen(mEntryAdapter.getWhen());
+            } else {
+                mPublicLayout.setNotificationWhen(mEntry.getSbn().getNotification().getWhen());
+            }
         }
         getShowingLayout().updateBackgroundColor(false /* animate */);
         mPrivateLayout.updateExpandButtons(isExpandable());
@@ -3381,7 +3401,7 @@
      */
     @Override
     public boolean canExpandableViewBeDismissed() {
-        if (areGutsExposed() || !mEntry.hasFinishedInitialization()) {
+        if (areGutsExposed() || !hasFinishedInitialization()) {
             return false;
         }
         return canViewBeDismissed();
@@ -3405,7 +3425,12 @@
      * clearability see {@link NotificationEntry#isClearable()}.
      */
     public boolean canViewBeCleared() {
-        return mEntry.isClearable() && (!shouldShowPublic() || !mSensitiveHiddenInGeneral);
+        if (NotificationBundleUi.isEnabled()) {
+            return mEntryAdapter.isClearable()
+                    && (!shouldShowPublic() || !mSensitiveHiddenInGeneral);
+        } else {
+            return mEntry.isClearable() && (!shouldShowPublic() || !mSensitiveHiddenInGeneral);
+        }
     }
 
     private boolean shouldShowPublic() {
@@ -4082,6 +4107,7 @@
     }
 
     public boolean isMediaRow() {
+        NotificationBundleUi.assertInLegacyMode();
         return mEntry.getSbn().getNotification().isMediaNotification();
     }
 
@@ -4204,7 +4230,11 @@
     public void dump(PrintWriter pwOriginal, String[] args) {
         IndentingPrintWriter pw = DumpUtilsKt.asIndenting(pwOriginal);
         // Skip super call; dump viewState ourselves
-        pw.println("Notification: " + mEntry.getKey());
+        if (NotificationBundleUi.isEnabled()) {
+            pw.println("Notification: " + mEntryAdapter.getKey());
+        } else {
+            pw.println("Notification: " + mEntry.getKey());
+        }
         DumpUtilsKt.withIncreasedIndent(pw, () -> {
             pw.println(this);
             pw.print("visibility: " + getVisibility());
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/ExpandableNotificationRowController.java b/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/ExpandableNotificationRowController.java
index b43a387..07711b6 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/ExpandableNotificationRowController.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/ExpandableNotificationRowController.java
@@ -374,7 +374,11 @@
         mView.addOnAttachStateChangeListener(new View.OnAttachStateChangeListener() {
             @Override
             public void onViewAttachedToWindow(View v) {
-                mView.getEntry().setInitializationTime(mClock.elapsedRealtime());
+                if (NotificationBundleUi.isEnabled()) {
+                    mView.setInitializationTime(mClock.elapsedRealtime());
+                } else {
+                    mView.getEntry().setInitializationTime(mClock.elapsedRealtime());
+                }
                 mPluginManager.addPluginListener(mView,
                         NotificationMenuRowPlugin.class, false /* Allow multiple */);
                 if (!SceneContainerFlag.isEnabled()) {
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/NotificationGutsManager.java b/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/NotificationGutsManager.java
index 6e638f5..9a75253 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/NotificationGutsManager.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/NotificationGutsManager.java
@@ -73,6 +73,7 @@
 import com.android.systemui.statusbar.notification.headsup.HeadsUpManager;
 import com.android.systemui.statusbar.notification.row.icon.AppIconProvider;
 import com.android.systemui.statusbar.notification.row.icon.NotificationIconStyleProvider;
+import com.android.systemui.statusbar.notification.shared.NotificationBundleUi;
 import com.android.systemui.statusbar.notification.stack.NotificationListContainer;
 import com.android.systemui.statusbar.phone.CentralSurfaces;
 import com.android.systemui.statusbar.policy.DeviceProvisionedController;
@@ -436,7 +437,9 @@
                 onNasFeedbackClick,
                 mUiEventLogger,
                 mDeviceProvisionedController.isDeviceProvisioned(),
-                row.getIsNonblockable(),
+                NotificationBundleUi.isEnabled()
+                        ? !row.getEntry().isBlockable()
+                        : row.getIsNonblockable(),
                 mHighPriorityProvider.isHighPriority(row.getEntry()),
                 mAssistantFeedbackController,
                 mMetricsLogger,
@@ -480,7 +483,9 @@
                 row.getEntry(),
                 onSettingsClick,
                 mDeviceProvisionedController.isDeviceProvisioned(),
-                row.getIsNonblockable());
+                NotificationBundleUi.isEnabled()
+                        ? !row.getEntry().isBlockable()
+                        : row.getIsNonblockable());
     }
 
     /**
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/NotificationMenuRow.java b/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/NotificationMenuRow.java
index b96b224a..ab382df 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/NotificationMenuRow.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/NotificationMenuRow.java
@@ -186,7 +186,7 @@
     }
 
     @Override
-    public void createMenu(ViewGroup parent, StatusBarNotification sbn) {
+    public void createMenu(ViewGroup parent) {
         mParent = (ExpandableNotificationRow) parent;
         createMenuViews(true /* resetState */);
     }
@@ -227,7 +227,7 @@
     }
 
     @Override
-    public void onNotificationUpdated(StatusBarNotification sbn) {
+    public void onNotificationUpdated() {
         if (mMenuContainer == null) {
             // Menu hasn't been created yet, no need to do anything.
             return;
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 3d60e03..3ff18ef 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
@@ -6686,7 +6686,7 @@
 
     static boolean canChildBeCleared(View v) {
         if (v instanceof ExpandableNotificationRow row) {
-            if (row.areGutsExposed() || !row.getEntry().hasFinishedInitialization()) {
+            if (row.areGutsExposed() || !row.hasFinishedInitialization()) {
                 return false;
             }
             return row.canViewBeCleared();
diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/collection/ShadeListBuilderTest.java b/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/collection/ShadeListBuilderTest.java
index 4ff09d3..e8b50d5 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/collection/ShadeListBuilderTest.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/collection/ShadeListBuilderTest.java
@@ -45,6 +45,9 @@
 import static java.util.Collections.singletonList;
 
 import android.os.SystemClock;
+import android.platform.test.annotations.DisableFlags;
+import android.platform.test.annotations.EnableFlags;
+import android.platform.test.flag.junit.SetFlagsRule;
 import android.testing.AndroidTestingRunner;
 import android.testing.TestableLooper;
 import android.util.ArrayMap;
@@ -74,10 +77,14 @@
 import com.android.systemui.statusbar.notification.collection.listbuilder.pluggable.NotifStabilityManager;
 import com.android.systemui.statusbar.notification.collection.listbuilder.pluggable.Pluggable;
 import com.android.systemui.statusbar.notification.collection.notifcollection.CollectionReadyForBuildListener;
+import com.android.systemui.statusbar.notification.row.ExpandableNotificationRow;
+import com.android.systemui.statusbar.notification.row.NotificationTestHelper;
+import com.android.systemui.statusbar.notification.shared.NotificationBundleUi;
 import com.android.systemui.util.time.FakeSystemClock;
 
 import org.junit.Assert;
 import org.junit.Before;
+import org.junit.Rule;
 import org.junit.Test;
 import org.junit.runner.RunWith;
 import org.mockito.ArgumentCaptor;
@@ -127,6 +134,9 @@
     private TestableStabilityManager mStabilityManager;
     private TestableNotifFilter mFinalizeFilter;
 
+    @Rule
+    public final SetFlagsRule mSetFlagsRule = new SetFlagsRule();
+
     private Map<String, Integer> mNextIdMap = new ArrayMap<>();
     private int mNextRank = 0;
 
@@ -561,6 +571,7 @@
     }
 
     @Test
+    @DisableFlags(NotificationBundleUi.FLAG_NAME)
     public void testFilter_resetsInitalizationTime() {
         // GIVEN a NotifFilter that filters out a specific package
         NotifFilter filter1 = spy(new PackageFilter(PACKAGE_1));
@@ -584,6 +595,31 @@
     }
 
     @Test
+    @EnableFlags(NotificationBundleUi.FLAG_NAME)
+    public void testFilter_resetsInitializationTime_onRow() throws Exception {
+        // GIVEN a NotifFilter that filters out a specific package
+        NotifFilter filter1 = spy(new PackageFilter(PACKAGE_1));
+        mListBuilder.addFinalizeFilter(filter1);
+
+        // GIVEN a notification that was initialized 1 second ago that will be filtered out
+        final NotificationEntry entry = new NotificationEntryBuilder()
+                .setPkg(PACKAGE_1)
+                .setId(nextId(PACKAGE_1))
+                .setRank(nextRank())
+                .build();
+        entry.setRow(new NotificationTestHelper(mContext, mDependency).createRow());
+        entry.getRow().setInitializationTime(SystemClock.elapsedRealtime() - 1000);
+        assertTrue(entry.getRow().hasFinishedInitialization());
+
+        // WHEN the pipeline is kicked off
+        mReadyForBuildListener.onBuildList(singletonList(entry), "test");
+        mPipelineChoreographer.runIfScheduled();
+
+        // THEN the entry's initialization time is reset
+        assertFalse(entry.getRow().hasFinishedInitialization());
+    }
+
+    @Test
     public void testNotifFiltersCanBePreempted() {
         // GIVEN two notif filters
         NotifFilter filter1 = spy(new PackageFilter(PACKAGE_2));
diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/row/ExpandableNotificationRowTest.java b/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/row/ExpandableNotificationRowTest.java
index 1b53531..24d8d1c 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/row/ExpandableNotificationRowTest.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/row/ExpandableNotificationRowTest.java
@@ -75,6 +75,7 @@
 import com.android.systemui.statusbar.notification.promoted.PromotedNotificationUiForceExpanded;
 import com.android.systemui.statusbar.notification.row.ExpandableView.OnHeightChangedListener;
 import com.android.systemui.statusbar.notification.row.wrapper.NotificationViewWrapper;
+import com.android.systemui.statusbar.notification.shared.NotificationBundleUi;
 import com.android.systemui.statusbar.notification.shared.NotificationContentAlphaOptimization;
 import com.android.systemui.statusbar.notification.stack.NotificationChildrenContainer;
 import com.android.systemui.statusbar.phone.KeyguardBypassController;
@@ -589,6 +590,7 @@
     }
 
     @Test
+    @DisableFlags(NotificationBundleUi.FLAG_NAME)
     public void testGetIsNonblockable() throws Exception {
         ExpandableNotificationRow row =
                 mNotificationTestHelper.createRow(mNotificationTestHelper.createNotification());
diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/row/NotificationContentViewTest.kt b/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/row/NotificationContentViewTest.kt
index 47238fe..c874bc6 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/row/NotificationContentViewTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/row/NotificationContentViewTest.kt
@@ -36,6 +36,7 @@
 import com.android.internal.widget.NotificationExpandButton
 import com.android.systemui.SysuiTestCase
 import com.android.systemui.statusbar.notification.FeedbackIcon
+import com.android.systemui.statusbar.notification.collection.EntryAdapter
 import com.android.systemui.statusbar.notification.collection.NotificationEntry
 import com.android.systemui.statusbar.notification.people.PeopleNotificationIdentifier
 import com.android.systemui.statusbar.notification.shared.NotificationBundleUi
@@ -82,6 +83,7 @@
         fakeParent =
             spy(FrameLayout(mContext, /* attrs= */ null).also { it.visibility = View.GONE })
         val mockEntry = createMockNotificationEntry()
+        val mockEntryAdapter = createMockNotificationEntryAdapter()
         row =
             spy(
                 when (NotificationBundleUi.isEnabled) {
@@ -92,6 +94,7 @@
                             UserHandle.CURRENT
                         ).apply {
                             entry = mockEntry
+                            entryAdapter = mockEntryAdapter
                         }
                     }
                     false -> {
@@ -611,6 +614,7 @@
             whenever(this.entry).thenReturn(notificationEntry)
             whenever(this.context).thenReturn(mContext)
             whenever(this.bubbleClickListener).thenReturn(View.OnClickListener {})
+            whenever(this.entryAdapter).thenReturn(createMockNotificationEntryAdapter())
         }
 
     private fun createMockNotificationEntry() =
@@ -624,6 +628,9 @@
             whenever(sbnMock.user).thenReturn(userMock)
         }
 
+    private fun createMockNotificationEntryAdapter() =
+        mock<EntryAdapter>()
+
     private fun createLinearLayoutWithBottomMargin(bottomMargin: Int): LinearLayout {
         val outerLayout = LinearLayout(mContext)
         val innerLayout = LinearLayout(mContext)
diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/row/NotificationGutsManagerWithScenesTest.kt b/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/row/NotificationGutsManagerWithScenesTest.kt
index 3d4c901..99b99ee 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/row/NotificationGutsManagerWithScenesTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/row/NotificationGutsManagerWithScenesTest.kt
@@ -427,7 +427,6 @@
             .setUserSentiment(Ranking.USER_SENTIMENT_NEGATIVE)
             .setImportance(NotificationManager.IMPORTANCE_HIGH)
             .build()
-        whenever(row.getIsNonblockable()).thenReturn(false)
         whenever(highPriorityProvider.isHighPriority(entry)).thenReturn(true)
         val statusBarNotification = entry.sbn
         gutsManager.initializeNotificationInfo(row, notificationInfoView)
@@ -463,7 +462,6 @@
         NotificationEntryHelper.modifyRanking(row.entry)
             .setUserSentiment(Ranking.USER_SENTIMENT_NEGATIVE)
             .build()
-        whenever(row.getIsNonblockable()).thenReturn(false)
         val statusBarNotification = row.entry.sbn
         val entry = row.entry
         gutsManager.initializeNotificationInfo(row, notificationInfoView)
@@ -499,7 +497,6 @@
         NotificationEntryHelper.modifyRanking(row.entry)
             .setUserSentiment(Ranking.USER_SENTIMENT_NEGATIVE)
             .build()
-        whenever(row.getIsNonblockable()).thenReturn(false)
         val statusBarNotification = row.entry.sbn
         val entry = row.entry
         gutsManager.initializeNotificationInfo(row, notificationInfoView)
@@ -566,7 +563,7 @@
     ): NotificationMenuRowPlugin.MenuItem {
         val menuRow: NotificationMenuRowPlugin =
             NotificationMenuRow(mContext, peopleNotificationIdentifier)
-        menuRow.createMenu(row, row!!.entry.sbn)
+        menuRow.createMenu(row)
         val menuItem = menuRow.getLongpressMenuItem(mContext)
         Assert.assertNotNull(menuItem)
         return menuItem
diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/stack/NotificationStackScrollLayoutTest.java b/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/stack/NotificationStackScrollLayoutTest.java
index 6ac20d4..955de273 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/stack/NotificationStackScrollLayoutTest.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/stack/NotificationStackScrollLayoutTest.java
@@ -675,7 +675,7 @@
     @Test
     public void testClearNotifications_clearAllInProgress() {
         ExpandableNotificationRow row = createClearableRow();
-        when(row.getEntry().hasFinishedInitialization()).thenReturn(true);
+        when(row.hasFinishedInitialization()).thenReturn(true);
         doReturn(true).when(mStackScroller).isVisible(row);
         mStackScroller.addContainerView(row);
 
diff --git a/packages/SystemUI/tests/utils/src/com/android/systemui/qs/panels/domain/interactor/IconTilesInteractorKosmos.kt b/packages/SystemUI/tests/utils/src/com/android/systemui/qs/panels/domain/interactor/IconTilesInteractorKosmos.kt
index 8d4db8b..8a6f68c 100644
--- a/packages/SystemUI/tests/utils/src/com/android/systemui/qs/panels/domain/interactor/IconTilesInteractorKosmos.kt
+++ b/packages/SystemUI/tests/utils/src/com/android/systemui/qs/panels/domain/interactor/IconTilesInteractorKosmos.kt
@@ -16,6 +16,7 @@
 
 package com.android.systemui.qs.panels.domain.interactor
 
+import com.android.internal.logging.uiEventLoggerFake
 import com.android.systemui.kosmos.Kosmos
 import com.android.systemui.kosmos.applicationCoroutineScope
 import com.android.systemui.log.core.FakeLogBuffer
@@ -29,6 +30,7 @@
             defaultLargeTilesRepository,
             currentTilesInteractor,
             qsPreferencesInteractor,
+            uiEventLoggerFake,
             largeTileSpanRepository,
             FakeLogBuffer.Factory.create(),
             applicationCoroutineScope,
diff --git a/packages/Vcn/framework-b/Android.bp b/packages/Vcn/framework-b/Android.bp
index edb22c0..c531233 100644
--- a/packages/Vcn/framework-b/Android.bp
+++ b/packages/Vcn/framework-b/Android.bp
@@ -77,8 +77,7 @@
     ],
     soong_config_variables: {
         is_vcn_in_mainline: {
-            //TODO: b/380155299 Make it Baklava when aidl tool can understand it
-            min_sdk_version: "current",
+            min_sdk_version: "36",
             static_libs: ["android.net.vcn.flags-aconfig-java"],
             apex_available: ["com.android.tethering"],
 
diff --git a/services/companion/java/com/android/server/companion/CompanionDeviceManagerService.java b/services/companion/java/com/android/server/companion/CompanionDeviceManagerService.java
index 05301fd..4f56483 100644
--- a/services/companion/java/com/android/server/companion/CompanionDeviceManagerService.java
+++ b/services/companion/java/com/android/server/companion/CompanionDeviceManagerService.java
@@ -31,6 +31,8 @@
 
 import static com.android.internal.util.CollectionUtils.any;
 import static com.android.internal.util.Preconditions.checkState;
+import static com.android.server.companion.association.DisassociationProcessor.REASON_API;
+import static com.android.server.companion.association.DisassociationProcessor.REASON_PKG_DATA_CLEARED;
 import static com.android.server.companion.utils.PackageUtils.enforceUsesCompanionDeviceFeature;
 import static com.android.server.companion.utils.PackageUtils.isRestrictedSettingsAllowed;
 import static com.android.server.companion.utils.PermissionsUtils.enforceCallerCanManageAssociationsForPackage;
@@ -250,7 +252,7 @@
                     + packageName + "]. Cleaning up CDM data...");
 
             for (AssociationInfo association : associationsForPackage) {
-                mDisassociationProcessor.disassociate(association.getId());
+                mDisassociationProcessor.disassociate(association.getId(), REASON_PKG_DATA_CLEARED);
             }
 
             mCompanionAppBinder.onPackageChanged(userId);
@@ -426,7 +428,7 @@
 
         @Override
         public void disassociate(int associationId) {
-            mDisassociationProcessor.disassociate(associationId);
+            mDisassociationProcessor.disassociate(associationId, REASON_API);
         }
 
         @Override
diff --git a/services/companion/java/com/android/server/companion/CompanionDeviceShellCommand.java b/services/companion/java/com/android/server/companion/CompanionDeviceShellCommand.java
index e7d1460..c5ac7c3 100644
--- a/services/companion/java/com/android/server/companion/CompanionDeviceShellCommand.java
+++ b/services/companion/java/com/android/server/companion/CompanionDeviceShellCommand.java
@@ -18,6 +18,8 @@
 
 import static android.companion.CompanionDeviceManager.MESSAGE_REQUEST_CONTEXT_SYNC;
 
+import static com.android.server.companion.association.DisassociationProcessor.REASON_SHELL;
+
 import android.companion.AssociationInfo;
 import android.companion.ContextSyncMessage;
 import android.companion.Flags;
@@ -122,7 +124,7 @@
                     if (association == null) {
                         out.println("Association doesn't exist.");
                     } else {
-                        mDisassociationProcessor.disassociate(association.getId());
+                        mDisassociationProcessor.disassociate(association.getId(), REASON_SHELL);
                     }
                 }
                 break;
@@ -132,7 +134,7 @@
                     final List<AssociationInfo> userAssociations =
                             mAssociationStore.getAssociationsByUser(userId);
                     for (AssociationInfo association : userAssociations) {
-                        mDisassociationProcessor.disassociate(association.getId());
+                        mDisassociationProcessor.disassociate(association.getId(), REASON_SHELL);
                     }
                 }
                 break;
diff --git a/services/companion/java/com/android/server/companion/association/AssociationDiskStore.java b/services/companion/java/com/android/server/companion/association/AssociationDiskStore.java
index f2d019b..ce7dcd0 100644
--- a/services/companion/java/com/android/server/companion/association/AssociationDiskStore.java
+++ b/services/companion/java/com/android/server/companion/association/AssociationDiskStore.java
@@ -58,6 +58,7 @@
 import java.io.ByteArrayOutputStream;
 import java.io.File;
 import java.io.FileInputStream;
+import java.io.FileNotFoundException;
 import java.io.IOException;
 import java.io.InputStream;
 import java.util.HashMap;
@@ -164,6 +165,7 @@
 
     private static final String FILE_NAME_LEGACY = "companion_device_manager_associations.xml";
     private static final String FILE_NAME = "companion_device_manager.xml";
+    private static final String FILE_NAME_LAST_REMOVED_ASSOCIATION = "last_removed_association.txt";
 
     private static final String XML_TAG_STATE = "state";
     private static final String XML_TAG_ASSOCIATIONS = "associations";
@@ -268,6 +270,46 @@
         }
     }
 
+    /**
+     * Read the last removed association from disk.
+     */
+    public String readLastRemovedAssociation(@UserIdInt int userId) {
+        final AtomicFile file = createStorageFileForUser(
+                userId, FILE_NAME_LAST_REMOVED_ASSOCIATION);
+        StringBuilder sb = new StringBuilder();
+        int c;
+        try (FileInputStream fis = file.openRead()) {
+            while ((c = fis.read()) != -1) {
+                sb.append((char) c);
+            }
+            fis.close();
+            return sb.toString();
+        } catch (FileNotFoundException e) {
+            Slog.e(TAG, "File " + file + " for user=" + userId + " doesn't exist.");
+            return null;
+        } catch (IOException e) {
+            Slog.e(TAG, "Can't read file " + file + " for user=" + userId);
+            return null;
+        }
+    }
+
+    /**
+     * Write the last removed association to disk.
+     */
+    public void writeLastRemovedAssociation(AssociationInfo association, String reason) {
+        Slog.i(TAG, "Writing last removed association=" + association.getId() + " to disk...");
+
+        final AtomicFile file = createStorageFileForUser(
+                association.getUserId(), FILE_NAME_LAST_REMOVED_ASSOCIATION);
+        writeToFileSafely(file, out -> {
+            out.write(String.valueOf(System.currentTimeMillis()).getBytes());
+            out.write(' ');
+            out.write(reason.getBytes());
+            out.write(' ');
+            out.write(association.toString().getBytes());
+        });
+    }
+
     @NonNull
     private static Associations readAssociationsFromFile(@UserIdInt int userId,
             @NonNull AtomicFile file, @NonNull String rootTag) {
diff --git a/services/companion/java/com/android/server/companion/association/AssociationStore.java b/services/companion/java/com/android/server/companion/association/AssociationStore.java
index 757abd9..f70c434 100644
--- a/services/companion/java/com/android/server/companion/association/AssociationStore.java
+++ b/services/companion/java/com/android/server/companion/association/AssociationStore.java
@@ -276,7 +276,7 @@
     /**
      * Remove an association.
      */
-    public void removeAssociation(int id) {
+    public void removeAssociation(int id, String reason) {
         Slog.i(TAG, "Removing association id=[" + id + "]...");
 
         final AssociationInfo association;
@@ -291,6 +291,8 @@
 
             writeCacheToDisk(association.getUserId());
 
+            mDiskStore.writeLastRemovedAssociation(association, reason);
+
             Slog.i(TAG, "Done removing association.");
         }
 
@@ -525,6 +527,14 @@
                 out.append("  ").append(a.toString()).append('\n');
             }
         }
+
+        out.append("Last Removed Association:\n");
+        for (UserInfo user : mUserManager.getAliveUsers()) {
+            String lastRemovedAssociation = mDiskStore.readLastRemovedAssociation(user.id);
+            if (lastRemovedAssociation != null) {
+                out.append("  ").append(lastRemovedAssociation).append('\n');
+            }
+        }
     }
 
     private void broadcastChange(@ChangeType int changeType, AssociationInfo association) {
diff --git a/services/companion/java/com/android/server/companion/association/DisassociationProcessor.java b/services/companion/java/com/android/server/companion/association/DisassociationProcessor.java
index 150e8da..248056f3 100644
--- a/services/companion/java/com/android/server/companion/association/DisassociationProcessor.java
+++ b/services/companion/java/com/android/server/companion/association/DisassociationProcessor.java
@@ -47,6 +47,13 @@
 @SuppressLint("LongLogTag")
 public class DisassociationProcessor {
 
+    public static final String REASON_REVOKED = "revoked";
+    public static final String REASON_SELF_IDLE = "self-idle";
+    public static final String REASON_SHELL = "shell";
+    public static final String REASON_LEGACY = "legacy";
+    public static final String REASON_API = "api";
+    public static final String REASON_PKG_DATA_CLEARED = "pkg-data-cleared";
+
     private static final String TAG = "CDM_DisassociationProcessor";
 
     private static final String SYS_PROP_DEBUG_REMOVAL_TIME_WINDOW =
@@ -94,7 +101,7 @@
      * Disassociate an association by id.
      */
     // TODO: also revoke notification access
-    public void disassociate(int id) {
+    public void disassociate(int id, String reason) {
         Slog.i(TAG, "Disassociating id=[" + id + "]...");
 
         final AssociationInfo association = mAssociationStore.getAssociationWithCallerChecks(id);
@@ -126,7 +133,7 @@
 
         // Association cleanup.
         mSystemDataTransferRequestStore.removeRequestsByAssociationId(userId, id);
-        mAssociationStore.removeAssociation(association.getId());
+        mAssociationStore.removeAssociation(association.getId(), reason);
 
         // If role is not in use by other associations, revoke the role.
         // Do not need to remove the system role since it was pre-granted by the system.
@@ -151,7 +158,7 @@
     }
 
     /**
-     * @deprecated Use {@link #disassociate(int)} instead.
+     * @deprecated Use {@link #disassociate(int, String)} instead.
      */
     @Deprecated
     public void disassociate(int userId, String packageName, String macAddress) {
@@ -165,7 +172,7 @@
 
         mAssociationStore.getAssociationWithCallerChecks(association.getId());
 
-        disassociate(association.getId());
+        disassociate(association.getId(), REASON_LEGACY);
     }
 
     @SuppressLint("MissingPermission")
@@ -223,7 +230,7 @@
 
             Slog.i(TAG, "Removing inactive self-managed association=[" + association.toShortString()
                     + "].");
-            disassociate(id);
+            disassociate(id, REASON_SELF_IDLE);
         }
     }
 
@@ -234,7 +241,7 @@
      *
      * Lastly remove the role holder for the revoked associations for the same packages.
      *
-     * @see #disassociate(int)
+     * @see #disassociate(int, String)
      */
     private class OnPackageVisibilityChangeListener implements
             ActivityManager.OnUidImportanceListener {
@@ -260,7 +267,7 @@
             int userId = UserHandle.getUserId(uid);
             for (AssociationInfo association : mAssociationStore.getRevokedAssociations(userId,
                     packageName)) {
-                disassociate(association.getId());
+                disassociate(association.getId(), REASON_REVOKED);
             }
 
             if (mAssociationStore.getRevokedAssociations().isEmpty()) {
diff --git a/services/contextualsearch/java/com/android/server/contextualsearch/ContextualSearchManagerService.java b/services/contextualsearch/java/com/android/server/contextualsearch/ContextualSearchManagerService.java
index d8e10f8..7eb7072 100644
--- a/services/contextualsearch/java/com/android/server/contextualsearch/ContextualSearchManagerService.java
+++ b/services/contextualsearch/java/com/android/server/contextualsearch/ContextualSearchManagerService.java
@@ -365,7 +365,7 @@
             }
         }
         final ScreenshotHardwareBuffer shb = mWmInternal.takeContextualSearchScreenshot(
-                (Flags.contextualSearchWindowLayer() ? csUid : -1));
+                (Flags.contextualSearchPreventSelfCapture() ? csUid : -1));
         final Bitmap bm = shb != null ? shb.asBitmap() : null;
         // Now that everything is fetched, putting it in the launchIntent.
         if (bm != null) {
@@ -549,7 +549,7 @@
                 Binder.withCleanCallingIdentity(() -> {
                     final ScreenshotHardwareBuffer shb =
                             mWmInternal.takeContextualSearchScreenshot(
-                               (Flags.contextualSearchWindowLayer() ? callingUid : -1));
+                               (Flags.contextualSearchPreventSelfCapture() ? callingUid : -1));
                     final Bitmap bm = shb != null ? shb.asBitmap() : null;
                     if (bm != null) {
                         bundle.putParcelable(ContextualSearchManager.EXTRA_SCREENSHOT,
diff --git a/services/core/java/com/android/server/am/OomAdjuster.java b/services/core/java/com/android/server/am/OomAdjuster.java
index 61c5501..13d367a 100644
--- a/services/core/java/com/android/server/am/OomAdjuster.java
+++ b/services/core/java/com/android/server/am/OomAdjuster.java
@@ -446,6 +446,8 @@
     private static final int CACHING_UI_SERVICE_CLIENT_ADJ_THRESHOLD =
             Flags.raiseBoundUiServiceThreshold() ? SERVICE_ADJ : PERCEPTIBLE_APP_ADJ;
 
+    static final long PERCEPTIBLE_TASK_TIMEOUT_MILLIS = 5 * 60 * 1000;
+
     @VisibleForTesting
     public static class Injector {
         boolean isChangeEnabled(@CachedCompatChangeId int cachedCompatChangeId,
@@ -1847,7 +1849,7 @@
             mHasVisibleActivities = false;
         }
 
-        void onOtherActivity() {
+        void onOtherActivity(long perceptibleTaskStoppedTimeMillis) {
             if (procState > PROCESS_STATE_CACHED_ACTIVITY) {
                 procState = PROCESS_STATE_CACHED_ACTIVITY;
                 mAdjType = "cch-act";
@@ -1856,6 +1858,28 @@
                             "Raise procstate to cached activity: " + app);
                 }
             }
+            if (Flags.perceptibleTasks() && adj > PERCEPTIBLE_MEDIUM_APP_ADJ) {
+                if (perceptibleTaskStoppedTimeMillis >= 0) {
+                    final long now = mInjector.getUptimeMillis();
+                    if (now - perceptibleTaskStoppedTimeMillis < PERCEPTIBLE_TASK_TIMEOUT_MILLIS) {
+                        adj = PERCEPTIBLE_MEDIUM_APP_ADJ;
+                        mAdjType = "perceptible-act";
+                        if (procState > PROCESS_STATE_IMPORTANT_BACKGROUND) {
+                            procState = PROCESS_STATE_IMPORTANT_BACKGROUND;
+                        }
+
+                        maybeSetProcessFollowUpUpdateLocked(app,
+                                perceptibleTaskStoppedTimeMillis + PERCEPTIBLE_TASK_TIMEOUT_MILLIS,
+                                now);
+                    } else if (adj > PREVIOUS_APP_ADJ) {
+                        adj = PREVIOUS_APP_ADJ;
+                        mAdjType = "stale-perceptible-act";
+                        if (procState > PROCESS_STATE_LAST_ACTIVITY) {
+                            procState = PROCESS_STATE_LAST_ACTIVITY;
+                        }
+                    }
+                }
+            }
             mHasVisibleActivities = false;
         }
     }
diff --git a/services/core/java/com/android/server/am/ProcessStateRecord.java b/services/core/java/com/android/server/am/ProcessStateRecord.java
index b0f808b..25175e6 100644
--- a/services/core/java/com/android/server/am/ProcessStateRecord.java
+++ b/services/core/java/com/android/server/am/ProcessStateRecord.java
@@ -1120,7 +1120,8 @@
         } else if ((flags & ACTIVITY_STATE_FLAG_IS_STOPPING) != 0) {
             callback.onStoppingActivity((flags & ACTIVITY_STATE_FLAG_IS_STOPPING_FINISHING) != 0);
         } else {
-            callback.onOtherActivity();
+            final long ts = mApp.getWindowProcessController().getPerceptibleTaskStoppedTimeMillis();
+            callback.onOtherActivity(ts);
         }
 
         mCachedAdj = callback.adj;
diff --git a/services/core/java/com/android/server/am/flags.aconfig b/services/core/java/com/android/server/am/flags.aconfig
index 27c384a..c8fedf3 100644
--- a/services/core/java/com/android/server/am/flags.aconfig
+++ b/services/core/java/com/android/server/am/flags.aconfig
@@ -293,6 +293,13 @@
 }
 
 flag {
+    name: "perceptible_tasks"
+    namespace: "system_performance"
+    description: "Boost the oom_score_adj of activities in perceptible tasks"
+    bug: "370890207"
+}
+
+flag {
     name: "expedite_activity_launch_on_cold_start"
     namespace: "system_performance"
     description: "Notify ActivityTaskManager of cold starts early to fix app launch behavior."
diff --git a/services/core/java/com/android/server/inputmethod/InputMethodManagerService.java b/services/core/java/com/android/server/inputmethod/InputMethodManagerService.java
index 508bc2f..dfdd9e5 100644
--- a/services/core/java/com/android/server/inputmethod/InputMethodManagerService.java
+++ b/services/core/java/com/android/server/inputmethod/InputMethodManagerService.java
@@ -3485,7 +3485,7 @@
                         || (windowPerceptible != null && windowPerceptible == perceptible)) {
                     return;
                 }
-                mFocusedWindowPerceptible.put(windowToken, windowPerceptible);
+                mFocusedWindowPerceptible.put(windowToken, perceptible);
                 updateSystemUiLocked(userId);
             }
         });
diff --git a/services/core/java/com/android/server/notification/PreferencesHelper.java b/services/core/java/com/android/server/notification/PreferencesHelper.java
index 0fc182f..fff812c 100644
--- a/services/core/java/com/android/server/notification/PreferencesHelper.java
+++ b/services/core/java/com/android/server/notification/PreferencesHelper.java
@@ -42,6 +42,7 @@
 import static com.android.internal.util.FrameworkStatsLog.PACKAGE_NOTIFICATION_PREFERENCES__FSI_STATE__DENIED;
 import static com.android.internal.util.FrameworkStatsLog.PACKAGE_NOTIFICATION_PREFERENCES__FSI_STATE__GRANTED;
 import static com.android.internal.util.FrameworkStatsLog.PACKAGE_NOTIFICATION_PREFERENCES__FSI_STATE__NOT_REQUESTED;
+import static com.android.server.notification.PreferencesHelper.LockableAppFields.USER_LOCKED_BUBBLE;
 import static com.android.server.notification.PreferencesHelper.LockableAppFields.USER_LOCKED_PROMOTABLE;
 
 import android.annotation.FlaggedApi;
@@ -286,7 +287,7 @@
         if (!TAG_RANKING.equals(tag)) return;
 
         final int xmlVersion = parser.getAttributeInt(null, ATT_VERSION, -1);
-        boolean upgradeForBubbles = xmlVersion == XML_VERSION_BUBBLES_UPGRADE;
+        boolean upgradeForBubbles = xmlVersion >= XML_VERSION_BUBBLES_UPGRADE;
         boolean migrateToPermission = (xmlVersion < XML_VERSION_NOTIF_PERMISSION);
         if (mShowReviewPermissionsNotification
                 && (xmlVersion < XML_VERSION_REVIEW_PERMISSIONS_NOTIFICATION)) {
@@ -337,15 +338,19 @@
             }
             boolean skipWarningLogged = false;
             boolean skipGroupWarningLogged = false;
-            boolean hasSAWPermission = false;
-            if (upgradeForBubbles && uid != UNKNOWN_UID) {
-                hasSAWPermission = mAppOps.noteOpNoThrow(
-                        OP_SYSTEM_ALERT_WINDOW, uid, name, null,
-                        "check-notif-bubble") == AppOpsManager.MODE_ALLOWED;
+            int bubblePref = parser.getAttributeInt(null, ATT_ALLOW_BUBBLE,
+                    DEFAULT_BUBBLE_PREFERENCE);
+            boolean bubbleLocked = (parser.getAttributeInt(null,
+                    ATT_APP_USER_LOCKED_FIELDS, DEFAULT_LOCKED_APP_FIELDS) & USER_LOCKED_BUBBLE)
+                    != 0;
+            if (!bubbleLocked
+                    && upgradeForBubbles
+                    && uid != UNKNOWN_UID
+                    && mAppOps.noteOpNoThrow(OP_SYSTEM_ALERT_WINDOW, uid, name, null,
+                    "check-notif-bubble") == AppOpsManager.MODE_ALLOWED) {
+                // User hasn't changed bubble pref & the app has SAW, so allow all bubbles.
+                bubblePref = BUBBLE_PREFERENCE_ALL;
             }
-            int bubblePref = hasSAWPermission
-                    ? BUBBLE_PREFERENCE_ALL
-                    : parser.getAttributeInt(null, ATT_ALLOW_BUBBLE, DEFAULT_BUBBLE_PREFERENCE);
             int appImportance = parser.getAttributeInt(null, ATT_IMPORTANCE, DEFAULT_IMPORTANCE);
 
             // when data is loaded from disk it's loaded as USER_ALL, but restored data that
diff --git a/services/core/java/com/android/server/wm/ActivityRecord.java b/services/core/java/com/android/server/wm/ActivityRecord.java
index 9692b69..aa3b500 100644
--- a/services/core/java/com/android/server/wm/ActivityRecord.java
+++ b/services/core/java/com/android/server/wm/ActivityRecord.java
@@ -493,6 +493,7 @@
     private long createTime = System.currentTimeMillis();
     long lastVisibleTime;         // last time this activity became visible
     long pauseTime;               // last time we started pausing the activity
+    long mStoppedTime;            // last time we completely stopped the activity
     long launchTickTime;          // base time for launch tick messages
     long topResumedStateLossTime; // last time we reported top resumed state loss to an activity
     // Last configuration reported to the activity in the client process.
@@ -6447,6 +6448,7 @@
             Slog.w(TAG, "Exception thrown during pause", e);
             // Just in case, assume it to be stopped.
             mAppStopped = true;
+            mStoppedTime = SystemClock.uptimeMillis();
             ProtoLog.v(WM_DEBUG_STATES, "Stop failed; moving to STOPPED: %s", this);
             setState(STOPPED, "stopIfPossible");
         }
@@ -6480,6 +6482,7 @@
 
         if (isStopping) {
             ProtoLog.v(WM_DEBUG_STATES, "Moving to STOPPED: %s (stop complete)", this);
+            mStoppedTime = SystemClock.uptimeMillis();
             setState(STOPPED, "activityStopped");
         }
 
diff --git a/services/core/java/com/android/server/wm/ActivityTaskManagerService.java b/services/core/java/com/android/server/wm/ActivityTaskManagerService.java
index 3778378..6f83822 100644
--- a/services/core/java/com/android/server/wm/ActivityTaskManagerService.java
+++ b/services/core/java/com/android/server/wm/ActivityTaskManagerService.java
@@ -2130,6 +2130,26 @@
     }
 
     @Override
+    public boolean setTaskIsPerceptible(int taskId, boolean isPerceptible) {
+        enforceTaskPermission("setTaskIsPerceptible()");
+        final long ident = Binder.clearCallingIdentity();
+        try {
+            synchronized (mGlobalLock) {
+                final Task task = mRootWindowContainer.anyTaskForId(taskId,
+                        MATCH_ATTACHED_TASK_ONLY);
+                if (task == null) {
+                    Slog.w(TAG, "setTaskIsPerceptible: No task to set with id=" + taskId);
+                    return false;
+                }
+                task.mIsPerceptible = isPerceptible;
+            }
+            return true;
+        } finally {
+            Binder.restoreCallingIdentity(ident);
+        }
+    }
+
+    @Override
     public boolean removeTask(int taskId) {
         mAmInternal.enforceCallingPermission(REMOVE_TASKS, "removeTask()");
         synchronized (mGlobalLock) {
diff --git a/services/core/java/com/android/server/wm/PresentationController.java b/services/core/java/com/android/server/wm/PresentationController.java
index 6946343..b3cff9c 100644
--- a/services/core/java/com/android/server/wm/PresentationController.java
+++ b/services/core/java/com/android/server/wm/PresentationController.java
@@ -56,11 +56,6 @@
         ProtoLog.v(WmProtoLogGroups.WM_DEBUG_PRESENTATION, "Presentation added to display %d: %s",
                 win.getDisplayId(), win);
         mPresentingDisplayIds.add(win.getDisplayId());
-        if (enablePresentationForConnectedDisplays()) {
-            // A presentation hides all activities behind on the same display.
-            win.mDisplayContent.ensureActivitiesVisible(/*starting=*/ null,
-                    /*notifyClients=*/ true);
-        }
         win.mWmService.mDisplayManagerInternal.onPresentation(displayId, /*isShown=*/ true);
     }
 
@@ -76,11 +71,6 @@
         if (displayIdIndex != -1) {
             mPresentingDisplayIds.remove(displayIdIndex);
         }
-        if (enablePresentationForConnectedDisplays()) {
-            // A presentation hides all activities behind on the same display.
-            win.mDisplayContent.ensureActivitiesVisible(/*starting=*/ null,
-                    /*notifyClients=*/ true);
-        }
         win.mWmService.mDisplayManagerInternal.onPresentation(displayId, /*isShown=*/ false);
     }
 }
diff --git a/services/core/java/com/android/server/wm/Task.java b/services/core/java/com/android/server/wm/Task.java
index 6b3499a..988af44 100644
--- a/services/core/java/com/android/server/wm/Task.java
+++ b/services/core/java/com/android/server/wm/Task.java
@@ -504,6 +504,17 @@
     int mOffsetYForInsets;
 
     /**
+     * When set to true, the task will be kept at a PERCEPTIBLE_APP_ADJ, and downgraded
+     * to PREVIOUS_APP_ADJ if not in foreground for a period of time.
+     * One example use case is for desktop form factors, where it is important keep tasks in the
+     * perceptible state (rather than cached where it may be frozen) when a user moves it to the
+     * foreground.
+     * On startup, restored Tasks will not be perceptible, until user actually interacts with it
+     * (i.e. brings it to the foreground)
+     */
+    boolean mIsPerceptible = false;
+
+    /**
      * Whether the compatibility overrides that change the resizability of the app should be allowed
      * for the specific app.
      */
@@ -3854,6 +3865,7 @@
         pw.print(ActivityInfo.resizeModeToString(mResizeMode));
         pw.print(" mSupportsPictureInPicture="); pw.print(mSupportsPictureInPicture);
         pw.print(" isResizeable="); pw.println(isResizeable());
+        pw.print(" isPerceptible="); pw.println(mIsPerceptible);
         pw.print(prefix); pw.print("lastActiveTime="); pw.print(lastActiveTime);
         pw.println(" (inactive for " + (getInactiveDuration() / 1000) + "s)");
         pw.print(prefix); pw.println(" isTrimmable=" + mIsTrimmableFromRecents);
diff --git a/services/core/java/com/android/server/wm/WindowManagerService.java b/services/core/java/com/android/server/wm/WindowManagerService.java
index 0b20911..8aed91b 100644
--- a/services/core/java/com/android/server/wm/WindowManagerService.java
+++ b/services/core/java/com/android/server/wm/WindowManagerService.java
@@ -157,6 +157,7 @@
 import static com.android.server.wm.WindowManagerServiceDumpProto.ROOT_WINDOW_CONTAINER;
 import static com.android.server.wm.WindowManagerServiceDumpProto.WINDOW_FRAMES_VALID;
 import static com.android.window.flags.Flags.enableDisplayFocusInShellTransitions;
+import static com.android.window.flags.Flags.enablePresentationForConnectedDisplays;
 import static com.android.window.flags.Flags.multiCrop;
 import static com.android.window.flags.Flags.setScPropertiesInClient;
 
@@ -1820,8 +1821,28 @@
             final boolean hideSystemAlertWindows = shouldHideNonSystemOverlayWindow(win);
             win.setForceHideNonSystemOverlayWindowIfNeeded(hideSystemAlertWindows);
 
-            res |= addWindowInner(win, displayPolicy, activity, displayContent, outInsetsState,
-                    outAttachedFrame, outActiveControls, client, outSizeCompatScale, attrs);
+            // Only a presentation window needs a transition because its visibility affets the
+            // lifecycle of apps below (b/390481865).
+            if (enablePresentationForConnectedDisplays() && win.isPresentation()) {
+                Transition transition = null;
+                if (!win.mTransitionController.isCollecting()) {
+                    transition = win.mTransitionController.createAndStartCollecting(TRANSIT_OPEN);
+                }
+                win.mTransitionController.collect(win.mToken);
+                res |= addWindowInner(win, displayPolicy, activity, displayContent, outInsetsState,
+                        outAttachedFrame, outActiveControls, client, outSizeCompatScale, attrs);
+                // A presentation hides all activities behind on the same display.
+                win.mDisplayContent.ensureActivitiesVisible(/*starting=*/ null,
+                        /*notifyClients=*/ true);
+                win.mTransitionController.getCollectingTransition().setReady(win.mToken, true);
+                if (transition != null) {
+                    win.mTransitionController.requestStartTransition(transition, null,
+                            null /* remoteTransition */, null /* displayChange */);
+                }
+            } else {
+                res |= addWindowInner(win, displayPolicy, activity, displayContent, outInsetsState,
+                        outAttachedFrame, outActiveControls, client, outSizeCompatScale, attrs);
+            }
         }
 
         Binder.restoreCallingIdentity(origId);
diff --git a/services/core/java/com/android/server/wm/WindowProcessController.java b/services/core/java/com/android/server/wm/WindowProcessController.java
index 30dde54..b2d28a3 100644
--- a/services/core/java/com/android/server/wm/WindowProcessController.java
+++ b/services/core/java/com/android/server/wm/WindowProcessController.java
@@ -37,6 +37,7 @@
 import static com.android.server.wm.ActivityRecord.State.RESUMED;
 import static com.android.server.wm.ActivityRecord.State.STARTED;
 import static com.android.server.wm.ActivityRecord.State.STOPPING;
+import static com.android.server.wm.ActivityRecord.State.STOPPED;
 import static com.android.server.wm.ActivityTaskManagerDebugConfig.DEBUG_RELEASE;
 import static com.android.server.wm.ActivityTaskManagerDebugConfig.POSTFIX_CONFIGURATION;
 import static com.android.server.wm.ActivityTaskManagerDebugConfig.POSTFIX_RELEASE;
@@ -344,6 +345,12 @@
      */
     private volatile int mActivityStateFlags = ACTIVITY_STATE_FLAG_MASK_MIN_TASK_LAYER;
 
+    /**
+     * The most recent timestamp of when one of this process's stopped activities in a
+     * perceptible task became stopped. Written by window manager and read by activity manager.
+     */
+    private volatile long mPerceptibleTaskStoppedTimeMillis = Long.MIN_VALUE;
+
     public WindowProcessController(@NonNull ActivityTaskManagerService atm,
             @NonNull ApplicationInfo info, String name, int uid, int userId, Object owner,
             @NonNull WindowProcessListener listener) {
@@ -1228,6 +1235,17 @@
         return mActivityStateFlags;
     }
 
+    /**
+     * Returns the most recent timestamp when one of this process's stopped activities in a
+     * perceptible task became stopped. It should only be called if {@link #hasActivities}
+     * returns {@code true} and {@link #getActivityStateFlags} does not have any of
+     * the ACTIVITY_STATE_FLAG_IS_(VISIBLE|PAUSING_OR_PAUSED|STOPPING) bit set.
+     */
+    @HotPath(caller = HotPath.OOM_ADJUSTMENT)
+    public long getPerceptibleTaskStoppedTimeMillis() {
+        return mPerceptibleTaskStoppedTimeMillis;
+    }
+
     void computeProcessActivityState() {
         // Since there could be more than one activities in a process record, we don't need to
         // compute the OomAdj with each of them, just need to find out the activity with the
@@ -1239,6 +1257,7 @@
         int minTaskLayer = Integer.MAX_VALUE;
         int stateFlags = 0;
         int nonOccludedRatio = 0;
+        long perceptibleTaskStoppedTimeMillis = Long.MIN_VALUE;
         final boolean wasResumed = hasResumedActivity();
         final boolean wasAnyVisible = (mActivityStateFlags
                 & (ACTIVITY_STATE_FLAG_IS_VISIBLE | ACTIVITY_STATE_FLAG_IS_WINDOW_VISIBLE)) != 0;
@@ -1287,6 +1306,11 @@
                     bestInvisibleState = STOPPING;
                     // Not "finishing" if any of activity isn't finishing.
                     allStoppingFinishing &= r.finishing;
+                } else if (bestInvisibleState == DESTROYED && r.isState(STOPPED)) {
+                    if (task.mIsPerceptible) {
+                        perceptibleTaskStoppedTimeMillis =
+                                Long.max(r.mStoppedTime, perceptibleTaskStoppedTimeMillis);
+                    }
                 }
             }
         }
@@ -1324,6 +1348,7 @@
             }
         }
         mActivityStateFlags = stateFlags;
+        mPerceptibleTaskStoppedTimeMillis = perceptibleTaskStoppedTimeMillis;
 
         final boolean anyVisible = (stateFlags
                 & (ACTIVITY_STATE_FLAG_IS_VISIBLE | ACTIVITY_STATE_FLAG_IS_WINDOW_VISIBLE)) != 0;
diff --git a/services/core/java/com/android/server/wm/WindowState.java b/services/core/java/com/android/server/wm/WindowState.java
index 9f1289b2..92ad2ce 100644
--- a/services/core/java/com/android/server/wm/WindowState.java
+++ b/services/core/java/com/android/server/wm/WindowState.java
@@ -95,6 +95,7 @@
 import static android.view.WindowManager.LayoutParams.TYPE_VOLUME_OVERLAY;
 import static android.view.WindowManager.LayoutParams.TYPE_WALLPAPER;
 import static android.view.WindowManager.LayoutParams.isSystemAlertWindowType;
+import static android.view.WindowManager.TRANSIT_CLOSE;
 import static android.view.WindowManagerGlobal.RELAYOUT_RES_FIRST_TIME;
 import static android.view.WindowManagerPolicyConstants.TYPE_LAYER_MULTIPLIER;
 import static android.view.WindowManagerPolicyConstants.TYPE_LAYER_OFFSET;
@@ -182,6 +183,7 @@
 import static com.android.server.wm.WindowStateProto.VIEW_VISIBILITY;
 import static com.android.server.wm.WindowStateProto.WINDOW_CONTAINER;
 import static com.android.server.wm.WindowStateProto.WINDOW_FRAMES;
+import static com.android.window.flags.Flags.enablePresentationForConnectedDisplays;
 import static com.android.window.flags.Flags.surfaceTrustedOverlay;
 
 import android.annotation.CallSuper;
@@ -2297,11 +2299,6 @@
             dc.updateImeInputAndControlTarget(null);
         }
 
-        final int type = mAttrs.type;
-
-        if (isPresentation()) {
-            mWmService.mPresentationController.onPresentationRemoved(this);
-        }
         // Check if window provides non decor insets before clearing its provided insets.
         final boolean windowProvidesDisplayDecorInsets = providesDisplayDecorInsets();
 
@@ -2442,11 +2439,33 @@
                 }
             }
 
-            removeImmediately();
-            mWmService.updateFocusedWindowLocked(isFocused()
-                            ? UPDATE_FOCUS_REMOVING_FOCUS
-                            : UPDATE_FOCUS_NORMAL,
-                    true /*updateInputWindows*/);
+            // Only a presentation window needs a transition because its visibility affets the
+            // lifecycle of apps below (b/390481865).
+            if (enablePresentationForConnectedDisplays() && isPresentation()) {
+                Transition transition = null;
+                if (!mTransitionController.isCollecting()) {
+                    transition = mTransitionController.createAndStartCollecting(TRANSIT_CLOSE);
+                }
+                mTransitionController.collect(mToken);
+                mAnimatingExit = true;
+                mRemoveOnExit = true;
+                mToken.setVisibleRequested(false);
+                mWmService.mPresentationController.onPresentationRemoved(this);
+                // A presentation hides all activities behind on the same display.
+                mDisplayContent.ensureActivitiesVisible(/*starting=*/ null,
+                        /*notifyClients=*/ true);
+                mTransitionController.getCollectingTransition().setReady(mToken, true);
+                if (transition != null) {
+                    mTransitionController.requestStartTransition(transition, null,
+                            null /* remoteTransition */, null /* displayChange */);
+                }
+            } else {
+                removeImmediately();
+                mWmService.updateFocusedWindowLocked(isFocused()
+                                ? UPDATE_FOCUS_REMOVING_FOCUS
+                                : UPDATE_FOCUS_NORMAL,
+                        true /*updateInputWindows*/);
+            }
         } finally {
             Binder.restoreCallingIdentity(origId);
         }
diff --git a/services/devicepolicy/java/com/android/server/devicepolicy/DevicePolicyManagerService.java b/services/devicepolicy/java/com/android/server/devicepolicy/DevicePolicyManagerService.java
index 191c21e..aee32a0 100644
--- a/services/devicepolicy/java/com/android/server/devicepolicy/DevicePolicyManagerService.java
+++ b/services/devicepolicy/java/com/android/server/devicepolicy/DevicePolicyManagerService.java
@@ -423,6 +423,7 @@
 import android.database.Cursor;
 import android.graphics.Bitmap;
 import android.hardware.usb.UsbManager;
+import android.health.connect.HealthConnectManager;
 import android.location.Location;
 import android.location.LocationManager;
 import android.media.AudioManager;
@@ -2149,6 +2150,14 @@
                 .hasSystemFeature(PackageManager.FEATURE_AUTOMOTIVE);
         mBackgroundHandler = BackgroundThread.getHandler();
 
+        // Add the health permission to the list of restricted permissions.
+        if (android.permission.flags.Flags.replaceBodySensorPermissionEnabled()) {
+            Set<String> healthPermissions = HealthConnectManager.getHealthPermissions(mContext);
+            for (String permission : healthPermissions) {
+                SENSOR_PERMISSIONS.add(permission);
+            }
+        }
+
         // Needed when mHasFeature == false, because it controls the certificate warning text.
         mCertificateMonitor = new CertificateMonitor(this, mInjector, mBackgroundHandler);
 
diff --git a/services/tests/mockingservicestests/src/com/android/server/am/MockingOomAdjusterTests.java b/services/tests/mockingservicestests/src/com/android/server/am/MockingOomAdjusterTests.java
index 1ef758c..340115a 100644
--- a/services/tests/mockingservicestests/src/com/android/server/am/MockingOomAdjusterTests.java
+++ b/services/tests/mockingservicestests/src/com/android/server/am/MockingOomAdjusterTests.java
@@ -3337,6 +3337,108 @@
                 followUpTimeCaptor.capture());
     }
 
+    /**
+     * For Perceptible Tasks adjustment, this solely unit-tests OomAdjuster -> onOtherActivity()
+     */
+    @SuppressWarnings("GuardedBy")
+    @Test
+    @EnableFlags(Flags.FLAG_PERCEPTIBLE_TASKS)
+    public void testPerceptibleAdjustment() {
+        ProcessRecord app = spy(makeDefaultProcessRecord(MOCKAPP_PID, MOCKAPP_UID,
+                MOCKAPP_PROCESSNAME, MOCKAPP_PACKAGENAME, true));
+
+        long now = mInjector.getUptimeMillis();
+
+        // GIVEN: perceptible adjustment is NOT enabled (perceptible stop time is not set)
+        // EXPECT: zero adjustment
+        // TLDR: App is not set as a perceptible task and hence no oom_adj boosting.
+        mService.mOomAdjuster.mTmpComputeOomAdjWindowCallback.initialize(app, CACHED_APP_MIN_ADJ,
+                false, false, PROCESS_STATE_CACHED_ACTIVITY,
+                SCHED_GROUP_DEFAULT, 0, 0, PROCESS_STATE_IMPORTANT_FOREGROUND);
+        mService.mOomAdjuster.mTmpComputeOomAdjWindowCallback.onOtherActivity(-1);
+        assertEquals(CACHED_APP_MIN_ADJ, mService.mOomAdjuster.mTmpComputeOomAdjWindowCallback.adj);
+
+        // GIVEN: perceptible adjustment is enabled (perceptible stop time is set) and
+        //        elapsed time < PERCEPTIBLE_TASK_TIMEOUT
+        // EXPECT: adjustment to PERCEPTIBLE_MEDIUM_APP_ADJ
+        // TLDR: App is a perceptible task (e.g. opened from launcher) and has oom_adj boosting.
+        mService.mOomAdjuster.mTmpComputeOomAdjWindowCallback.initialize(app, CACHED_APP_MIN_ADJ,
+                false, false, PROCESS_STATE_CACHED_ACTIVITY,
+                SCHED_GROUP_DEFAULT, 0, 0, PROCESS_STATE_IMPORTANT_FOREGROUND);
+        mInjector.reset();
+        mService.mOomAdjuster.mTmpComputeOomAdjWindowCallback.onOtherActivity(now);
+        assertEquals(PERCEPTIBLE_MEDIUM_APP_ADJ,
+                mService.mOomAdjuster.mTmpComputeOomAdjWindowCallback.adj);
+
+        // GIVEN: perceptible adjustment is enabled (perceptible stop time is set) and
+        //        elapsed time >  PERCEPTIBLE_TASK_TIMEOUT
+        // EXPECT: adjustment to PREVIOUS_APP_ADJ
+        // TLDR: App is a perceptible task (e.g. opened from launcher) and has oom_adj boosting, but
+        //       time has elapsed and has dropped to a lower boosting of PREVIOUS_APP_ADJ
+        mService.mOomAdjuster.mTmpComputeOomAdjWindowCallback.initialize(app, CACHED_APP_MIN_ADJ,
+                false, false, PROCESS_STATE_CACHED_ACTIVITY,
+                SCHED_GROUP_DEFAULT, 0, 0, PROCESS_STATE_IMPORTANT_FOREGROUND);
+        mInjector.jumpUptimeAheadTo(OomAdjuster.PERCEPTIBLE_TASK_TIMEOUT_MILLIS + 1000);
+        mService.mOomAdjuster.mTmpComputeOomAdjWindowCallback.onOtherActivity(0);
+        assertEquals(PREVIOUS_APP_ADJ, mService.mOomAdjuster.mTmpComputeOomAdjWindowCallback.adj);
+    }
+
+    /**
+     * For Perceptible Tasks adjustment, this tests overall adjustment flow.
+     */
+    @SuppressWarnings("GuardedBy")
+    @Test
+    @EnableFlags(Flags.FLAG_PERCEPTIBLE_TASKS)
+    public void testUpdateOomAdjPerceptible() {
+        ProcessRecord app = spy(makeDefaultProcessRecord(MOCKAPP_PID, MOCKAPP_UID,
+                MOCKAPP_PROCESSNAME, MOCKAPP_PACKAGENAME, true));
+        WindowProcessController wpc = app.getWindowProcessController();
+
+        // Set uptime to be at least the timeout time + buffer, so that we don't end up with
+        // negative stopTime in our test input
+        mInjector.jumpUptimeAheadTo(OomAdjuster.PERCEPTIBLE_TASK_TIMEOUT_MILLIS + 60L * 1000L);
+        long now = mInjector.getUptimeMillis();
+        doReturn(true).when(wpc).hasActivities();
+
+        // GIVEN: perceptible adjustment is is enabled
+        // EXPECT: perceptible-act adjustment
+        doReturn(WindowProcessController.ACTIVITY_STATE_FLAG_IS_STOPPING_FINISHING)
+                .when(wpc).getActivityStateFlags();
+        doReturn(now).when(wpc).getPerceptibleTaskStoppedTimeMillis();
+        updateOomAdj(app);
+        assertProcStates(app, PROCESS_STATE_IMPORTANT_BACKGROUND, PERCEPTIBLE_MEDIUM_APP_ADJ,
+                SCHED_GROUP_BACKGROUND, "perceptible-act");
+
+        // GIVEN: perceptible adjustment is is enabled and timeout has been reached
+        // EXPECT: stale-perceptible-act adjustment
+        doReturn(WindowProcessController.ACTIVITY_STATE_FLAG_IS_STOPPING_FINISHING)
+                .when(wpc).getActivityStateFlags();
+
+        doReturn(now - OomAdjuster.PERCEPTIBLE_TASK_TIMEOUT_MILLIS).when(
+                wpc).getPerceptibleTaskStoppedTimeMillis();
+        updateOomAdj(app);
+        assertProcStates(app, PROCESS_STATE_LAST_ACTIVITY, PREVIOUS_APP_ADJ,
+                SCHED_GROUP_BACKGROUND, "stale-perceptible-act");
+
+        // GIVEN: perceptible adjustment is is disabled
+        // EXPECT: no perceptible adjustment
+        doReturn(WindowProcessController.ACTIVITY_STATE_FLAG_IS_STOPPING_FINISHING)
+                .when(wpc).getActivityStateFlags();
+        doReturn(Long.MIN_VALUE).when(wpc).getPerceptibleTaskStoppedTimeMillis();
+        updateOomAdj(app);
+        assertProcStates(app, PROCESS_STATE_CACHED_ACTIVITY, CACHED_APP_MIN_ADJ,
+                SCHED_GROUP_BACKGROUND, "cch-act");
+
+        // GIVEN: perceptible app is in foreground
+        // EXPECT: no perceptible adjustment
+        doReturn(WindowProcessController.ACTIVITY_STATE_FLAG_IS_VISIBLE)
+                .when(wpc).getActivityStateFlags();
+        doReturn(now).when(wpc).getPerceptibleTaskStoppedTimeMillis();
+        updateOomAdj(app);
+        assertProcStates(app, PROCESS_STATE_TOP, VISIBLE_APP_ADJ,
+                SCHED_GROUP_DEFAULT, "vis-activity");
+    }
+
     @SuppressWarnings("GuardedBy")
     @Test
     public void testUpdateOomAdj_DoAll_Multiple_Provider_Retention() {
diff --git a/services/tests/servicestests/src/com/android/server/devicepolicy/DpmMockContext.java b/services/tests/servicestests/src/com/android/server/devicepolicy/DpmMockContext.java
index e5fac7a..00b0c55 100644
--- a/services/tests/servicestests/src/com/android/server/devicepolicy/DpmMockContext.java
+++ b/services/tests/servicestests/src/com/android/server/devicepolicy/DpmMockContext.java
@@ -263,6 +263,11 @@
     }
 
     @Override
+    public Context getApplicationContext() {
+        return this;
+    }
+
+    @Override
     public PackageManager getPackageManager() {
         return mMockSystemServices.packageManager;
     }
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 5dea44d..67e85ff 100644
--- a/services/tests/uiservicestests/src/com/android/server/notification/PreferencesHelperTest.java
+++ b/services/tests/uiservicestests/src/com/android/server/notification/PreferencesHelperTest.java
@@ -4495,6 +4495,27 @@
     }
 
     @Test
+    public void testBubblePreference_sameVersionWithSAWPermission() throws Exception {
+        when(mAppOpsManager.noteOpNoThrow(eq(OP_SYSTEM_ALERT_WINDOW), anyInt(),
+                anyString(), eq(null), anyString())).thenReturn(MODE_ALLOWED);
+
+        final String xml = "<ranking version=\"4\">\n"
+                + "<package name=\"" + PKG_O + "\" uid=\"" + UID_O + "\">\n"
+                + "<channel id=\"someId\" name=\"hi\""
+                + " importance=\"3\"/>"
+                + "</package>"
+                + "</ranking>";
+        TypedXmlPullParser parser = Xml.newFastPullParser();
+        parser.setInput(new BufferedInputStream(new ByteArrayInputStream(xml.getBytes())),
+                null);
+        parser.nextTag();
+        mHelper.readXml(parser, false, UserHandle.USER_ALL);
+
+        assertEquals(BUBBLE_PREFERENCE_ALL, mHelper.getBubblePreference(PKG_O, UID_O));
+        assertEquals(0, mHelper.getAppLockedFields(PKG_O, UID_O));
+    }
+
+    @Test
     public void testBubblePreference_upgradeWithSAWThenUserOverride() throws Exception {
         when(mAppOpsManager.noteOpNoThrow(eq(OP_SYSTEM_ALERT_WINDOW), anyInt(),
                 anyString(), eq(null), anyString())).thenReturn(MODE_ALLOWED);
diff --git a/services/tests/wmtests/src/com/android/server/wm/PresentationControllerTests.java b/services/tests/wmtests/src/com/android/server/wm/PresentationControllerTests.java
index db90c28..7c8a883 100644
--- a/services/tests/wmtests/src/com/android/server/wm/PresentationControllerTests.java
+++ b/services/tests/wmtests/src/com/android/server/wm/PresentationControllerTests.java
@@ -17,14 +17,18 @@
 package com.android.server.wm;
 
 import static android.view.Display.FLAG_PRESENTATION;
+import static android.view.WindowManager.TRANSIT_CLOSE;
+import static android.view.WindowManager.TRANSIT_OPEN;
 
 import static com.android.dx.mockito.inline.extended.ExtendedMockito.doReturn;
 import static com.android.window.flags.Flags.FLAG_ENABLE_PRESENTATION_FOR_CONNECTED_DISPLAYS;
 
+import static org.junit.Assert.assertEquals;
 import static org.junit.Assert.assertFalse;
 import static org.junit.Assert.assertTrue;
 import static org.mockito.ArgumentMatchers.eq;
 
+import android.annotation.NonNull;
 import android.graphics.Rect;
 import android.os.UserHandle;
 import android.os.UserManager;
@@ -41,6 +45,7 @@
 
 import androidx.test.filters.SmallTest;
 
+import org.junit.Before;
 import org.junit.Test;
 import org.junit.runner.RunWith;
 
@@ -53,9 +58,16 @@
 @RunWith(WindowTestRunner.class)
 public class PresentationControllerTests extends WindowTestsBase {
 
+    TestTransitionPlayer mPlayer;
+
+    @Before
+    public void setUp() {
+        mPlayer = registerTestTransitionPlayer();
+    }
+
     @EnableFlags(FLAG_ENABLE_PRESENTATION_FOR_CONNECTED_DISPLAYS)
     @Test
-    public void testPresentationHidesActivitiesBehind() {
+    public void testPresentationShowAndHide() {
         final DisplayInfo displayInfo = new DisplayInfo();
         displayInfo.copyFrom(mDisplayInfo);
         displayInfo.flags = FLAG_PRESENTATION;
@@ -64,7 +76,6 @@
         doReturn(dc).when(mWm.mRoot).getDisplayContentOrCreate(displayId);
         final ActivityRecord activity = createActivityRecord(createTask(dc));
         assertTrue(activity.isVisible());
-
         doReturn(true).when(() -> UserManager.isVisibleBackgroundUsersEnabled());
         final int uid = 100000; // uid for non-system user
         final Session session = createTestSession(mAtm, 1234 /* pid */, uid);
@@ -72,16 +83,48 @@
         doReturn(false).when(mWm.mUmInternal).isUserVisible(eq(userId), eq(displayId));
         final WindowManager.LayoutParams params = new WindowManager.LayoutParams(
                 WindowManager.LayoutParams.TYPE_PRESENTATION);
-
         final IWindow clientWindow = new TestIWindow();
+
+        // Show a Presentation window, which requests the activity to be stopped.
         final int result = mWm.addWindow(session, clientWindow, params, View.VISIBLE, displayId,
                 userId, WindowInsets.Type.defaultVisible(), null, new InsetsState(),
                 new InsetsSourceControl.Array(), new Rect(), new float[1]);
         assertTrue(result >= WindowManagerGlobal.ADD_OKAY);
+        assertFalse(activity.isVisibleRequested());
+        assertTrue(activity.isVisible());
+        final WindowState window = mWm.windowForClientLocked(session, clientWindow, false);
+        window.mHasSurface = true;
+        final Transition addTransition = window.mTransitionController.getCollectingTransition();
+        assertEquals(TRANSIT_OPEN, addTransition.mType);
+        assertTrue(addTransition.isInTransition(window));
+        assertTrue(addTransition.isInTransition(activity));
+
+        // Completing the transition makes the activity invisible.
+        completeTransition(addTransition, /*abortSync=*/ true);
         assertFalse(activity.isVisible());
 
-        final WindowState window = mWm.windowForClientLocked(session, clientWindow, false);
-        window.removeImmediately();
+        // Remove a Presentation window, which requests the activity to be resumed back.
+        window.removeIfPossible();
+        final Transition removeTransition = window.mTransitionController.getCollectingTransition();
+        assertEquals(TRANSIT_CLOSE, removeTransition.mType);
+        assertTrue(removeTransition.isInTransition(window));
+        assertTrue(removeTransition.isInTransition(activity));
+        assertTrue(activity.isVisibleRequested());
+        assertFalse(activity.isVisible());
+
+        // Completing the transition makes the activity visible.
+        completeTransition(removeTransition, /*abortSync=*/ false);
         assertTrue(activity.isVisible());
     }
+
+    private void completeTransition(@NonNull Transition transition, boolean abortSync) {
+        final ActionChain chain = ActionChain.testFinish(transition);
+        if (abortSync) {
+            // Forcefully finishing the active sync for testing purpose.
+            mWm.mSyncEngine.abort(transition.getSyncId());
+        } else {
+            transition.onTransactionReady(transition.getSyncId(), mTransaction);
+        }
+        transition.finishTransition(chain);
+    }
 }
diff --git a/tests/testables/src/android/testing/TestableLooper.java b/tests/testables/src/android/testing/TestableLooper.java
index 7a19add..8cd89ce 100644
--- a/tests/testables/src/android/testing/TestableLooper.java
+++ b/tests/testables/src/android/testing/TestableLooper.java
@@ -34,6 +34,7 @@
 import java.lang.annotation.RetentionPolicy;
 import java.lang.annotation.Target;
 import java.lang.reflect.Field;
+import java.lang.reflect.Method;
 import java.util.ArrayDeque;
 import java.util.Map;
 import java.util.Objects;
@@ -73,15 +74,21 @@
     /**
      * Baklava introduces new {@link TestLooperManager} APIs that we can use instead of reflection.
      */
-    private static boolean newTestabilityApisSupported() {
-        return android.os.Flags.messageQueueTestability();
+    private static boolean isAtLeastBaklava() {
+        Method[] methods = TestLooperManager.class.getMethods();
+        for (Method method : methods) {
+            if (method.getName().equals("peekWhen")) {
+                return true;
+            }
+        }
+        return false;
         // TODO(shayba): delete the above, uncomment the below.
         // SDK_INT has not yet ramped to Baklava in all 25Q2 builds.
         // return Build.VERSION.SDK_INT >= Build.VERSION_CODES.BAKLAVA;
     }
 
     static {
-        if (newTestabilityApisSupported()) {
+        if (isAtLeastBaklava()) {
             MESSAGE_QUEUE_MESSAGES_FIELD = null;
             MESSAGE_NEXT_FIELD = null;
             MESSAGE_WHEN_FIELD = null;
@@ -241,14 +248,14 @@
     }
 
     public void moveTimeForward(long milliSeconds) {
-        if (newTestabilityApisSupported()) {
-            moveTimeForwardModern(milliSeconds);
+        if (isAtLeastBaklava()) {
+            moveTimeForwardBaklava(milliSeconds);
         } else {
             moveTimeForwardLegacy(milliSeconds);
         }
     }
 
-    private void moveTimeForwardModern(long milliSeconds) {
+    private void moveTimeForwardBaklava(long milliSeconds) {
         // Drain all Messages from the queue.
         Queue<Message> messages = new ArrayDeque<>();
         while (true) {
diff --git a/tests/utils/testutils/java/android/os/test/TestLooper.java b/tests/utils/testutils/java/android/os/test/TestLooper.java
index c7a36dd..4d379e4 100644
--- a/tests/utils/testutils/java/android/os/test/TestLooper.java
+++ b/tests/utils/testutils/java/android/os/test/TestLooper.java
@@ -65,13 +65,19 @@
     private AutoDispatchThread mAutoDispatchThread;
 
     /**
-     * Modern introduces new {@link TestLooperManager} APIs that we can use instead of reflection.
+     * Baklava introduces new {@link TestLooperManager} APIs that we can use instead of reflection.
      */
-    private static boolean newTestabilityApisSupported() {
-        return android.os.Flags.messageQueueTestability();
+    private static boolean isAtLeastBaklava() {
+        Method[] methods = TestLooperManager.class.getMethods();
+        for (Method method : methods) {
+            if (method.getName().equals("peekWhen")) {
+                return true;
+            }
+        }
+        return false;
         // TODO(shayba): delete the above, uncomment the below.
-        // SDK_INT has not yet ramped to Modern in all 25Q2 builds.
-        // return Build.VERSION.SDK_INT >= Build.VERSION_CODES.Modern;
+        // SDK_INT has not yet ramped to Baklava in all 25Q2 builds.
+        // return Build.VERSION.SDK_INT >= Build.VERSION_CODES.BAKLAVA;
     }
 
     static {
@@ -81,7 +87,7 @@
             THREAD_LOCAL_LOOPER_FIELD = Looper.class.getDeclaredField("sThreadLocal");
             THREAD_LOCAL_LOOPER_FIELD.setAccessible(true);
 
-            if (newTestabilityApisSupported()) {
+            if (isAtLeastBaklava()) {
                 MESSAGE_QUEUE_MESSAGES_FIELD = null;
                 MESSAGE_NEXT_FIELD = null;
                 MESSAGE_WHEN_FIELD = null;
@@ -130,7 +136,7 @@
             throw new RuntimeException("Reflection error constructing or accessing looper", e);
         }
 
-        if (newTestabilityApisSupported()) {
+        if (isAtLeastBaklava()) {
             mTestLooperManager =
                 InstrumentationRegistry.getInstrumentation().acquireLooperManager(mLooper);
         } else {
@@ -159,14 +165,14 @@
     }
 
     public void moveTimeForward(long milliSeconds) {
-        if (newTestabilityApisSupported()) {
-            moveTimeForwardModern(milliSeconds);
+        if (isAtLeastBaklava()) {
+            moveTimeForwardBaklava(milliSeconds);
         } else {
             moveTimeForwardLegacy(milliSeconds);
         }
     }
 
-    private void moveTimeForwardModern(long milliSeconds) {
+    private void moveTimeForwardBaklava(long milliSeconds) {
         // Drain all Messages from the queue.
         Queue<Message> messages = new ArrayDeque<>();
         while (true) {
@@ -259,14 +265,14 @@
      * @return true if there are pending messages in the message queue
      */
     public boolean isIdle() {
-        if (newTestabilityApisSupported()) {
-            return isIdleModern();
+        if (isAtLeastBaklava()) {
+            return isIdleBaklava();
         } else {
             return isIdleLegacy();
         }
     }
 
-    private boolean isIdleModern() {
+    private boolean isIdleBaklava() {
         Long when = mTestLooperManager.peekWhen();
         return when != null && currentTime() >= when;
     }
@@ -280,14 +286,14 @@
      * @return the next message in the Looper's message queue or null if there is none
      */
     public Message nextMessage() {
-        if (newTestabilityApisSupported()) {
-            return nextMessageModern();
+        if (isAtLeastBaklava()) {
+            return nextMessageBaklava();
         } else {
             return nextMessageLegacy();
         }
     }
 
-    private Message nextMessageModern() {
+    private Message nextMessageBaklava() {
         if (isIdle()) {
             return mTestLooperManager.poll();
         } else {
@@ -308,14 +314,14 @@
      * Asserts that there is a message in the queue
      */
     public void dispatchNext() {
-        if (newTestabilityApisSupported()) {
-            dispatchNextModern();
+        if (isAtLeastBaklava()) {
+            dispatchNextBaklava();
         } else {
             dispatchNextLegacy();
         }
     }
 
-    private void dispatchNextModern() {
+    private void dispatchNextBaklava() {
         assertTrue(isIdle());
         Message msg = mTestLooperManager.poll();
         if (msg == null) {
diff --git a/tools/aapt2/cmd/Link.cpp b/tools/aapt2/cmd/Link.cpp
index ff4d8ef..0a5cb1f 100644
--- a/tools/aapt2/cmd/Link.cpp
+++ b/tools/aapt2/cmd/Link.cpp
@@ -2649,6 +2649,10 @@
       ".mpg", ".mpeg", ".mp4", ".m4a", ".m4v", ".3gp", ".3gpp", ".3g2", ".3gpp2", ".wma", ".wmv",
       ".webm", ".mkv"});
 
+  if (options_.no_compress_fonts) {
+    options_.extensions_to_not_compress.insert({".ttf", ".otf", ".ttc"});
+  }
+
   // Turn off auto versioning for static-libs.
   if (context.GetPackageType() == PackageType::kStaticLib) {
     options_.no_auto_version = true;
diff --git a/tools/aapt2/cmd/Link.h b/tools/aapt2/cmd/Link.h
index 2f17853..9779788 100644
--- a/tools/aapt2/cmd/Link.h
+++ b/tools/aapt2/cmd/Link.h
@@ -78,6 +78,7 @@
   bool use_sparse_encoding = false;
   std::unordered_set<std::string> extensions_to_not_compress;
   std::optional<std::regex> regex_to_not_compress;
+  bool no_compress_fonts = false;
   FeatureFlagValues feature_flag_values;
 
   // Static lib options.
@@ -300,6 +301,14 @@
             "use the '$' symbol for end of line. Uses a case-sensitive ECMAScript"
             "regular expression grammar.",
         &no_compress_regex);
+    AddOptionalSwitch("--no-compress-fonts",
+        "Do not compress files with common extensions for fonts.\n"
+            "This allows loading fonts directly from the APK, without needing to\n"
+            "decompress them first. Loading fonts will be faster and use less memory.\n"
+            "The downside is that the APK will be larger.\n"
+            "Passing this flag is functionally equivalent to passing the following flags:\n"
+            "-0 .ttf -0 .otf -0 .ttc",
+        &options_.no_compress_fonts);
     AddOptionalSwitch("--warn-manifest-validation",
         "Treat manifest validation errors as warnings.",
         &options_.manifest_fixer_options.warn_validation);
diff --git a/tools/aapt2/cmd/Link_test.cpp b/tools/aapt2/cmd/Link_test.cpp
index a2dc8f8..41f8e250 100644
--- a/tools/aapt2/cmd/Link_test.cpp
+++ b/tools/aapt2/cmd/Link_test.cpp
@@ -98,6 +98,7 @@
   WriteFile(GetTestPath("assets/test.txt"), content);
   WriteFile(GetTestPath("assets/test.hello.txt"), content);
   WriteFile(GetTestPath("assets/test.hello.xml"), content);
+  WriteFile(GetTestPath("assets/fonts/myfont.ttf"), content);
 
   const std::string out_apk = GetTestPath("out.apk");
   std::vector<std::string> link_args = {
@@ -136,6 +137,10 @@
   file = zip->FindFile("assets/test.hello.xml");
   ASSERT_THAT(file, Ne(nullptr));
   EXPECT_FALSE(file->WasCompressed());
+
+  file = zip->FindFile("assets/fonts/myfont.ttf");
+  ASSERT_THAT(file, Ne(nullptr));
+  EXPECT_TRUE(file->WasCompressed());
 }
 
 TEST_F(LinkTest, NoCompressResources) {
@@ -182,6 +187,42 @@
   EXPECT_FALSE(file->WasCompressed());
 }
 
+TEST_F(LinkTest, NoCompressFonts) {
+  StdErrDiagnostics diag;
+  std::string content(500, 'a');
+  const std::string compiled_files_dir = GetTestPath("compiled");
+  ASSERT_TRUE(CompileFile(GetTestPath("res/raw/test.txt"), content, compiled_files_dir, &diag));
+  WriteFile(GetTestPath("assets/fonts/myfont1.ttf"), content);
+  WriteFile(GetTestPath("assets/fonts/myfont2.ttf"), content);
+
+  const std::string out_apk = GetTestPath("out.apk");
+  std::vector<std::string> link_args = {
+      "--manifest", GetDefaultManifest(),
+      "-o", out_apk,
+      "--no-compress-fonts",
+      "-A", GetTestPath("assets")
+  };
+
+  ASSERT_TRUE(Link(link_args, compiled_files_dir, &diag));
+
+  std::unique_ptr<LoadedApk> apk = LoadedApk::LoadApkFromPath(out_apk, &diag);
+  ASSERT_THAT(apk, Ne(nullptr));
+  io::IFileCollection* zip = apk->GetFileCollection();
+  ASSERT_THAT(zip, Ne(nullptr));
+
+  auto file = zip->FindFile("res/raw/test.txt");
+  ASSERT_THAT(file, Ne(nullptr));
+  EXPECT_TRUE(file->WasCompressed());
+
+  file = zip->FindFile("assets/fonts/myfont1.ttf");
+  ASSERT_THAT(file, Ne(nullptr));
+  EXPECT_FALSE(file->WasCompressed());
+
+  file = zip->FindFile("assets/fonts/myfont2.ttf");
+  ASSERT_THAT(file, Ne(nullptr));
+  EXPECT_FALSE(file->WasCompressed());
+}
+
 TEST_F(LinkTest, OverlayStyles) {
   StdErrDiagnostics diag;
   const std::string compiled_files_dir = GetTestPath("compiled");
diff --git a/tools/aapt2/readme.md b/tools/aapt2/readme.md
index 5c3dfdc..6bdbaae 100644
--- a/tools/aapt2/readme.md
+++ b/tools/aapt2/readme.md
@@ -3,6 +3,8 @@
 ## Version 2.20
 - Too many features, bug fixes, and improvements to list since the last minor version update in
   2017. This README will be updated more frequently in the future.
+- Added a new flag `--no-compress-fonts`. This can significantly speed up loading fonts from APK
+  assets, at the cost of increasing the storage size of the APK.
 
 ## Version 2.19
 - Added navigation resource type.