Merge "Fix isInSection NPE from null representative entry" into main
diff --git a/apct-tests/perftests/core/src/android/os/TracePerfTest.java b/apct-tests/perftests/core/src/android/os/TracePerfTest.java
index 0b941c9..d905124 100644
--- a/apct-tests/perftests/core/src/android/os/TracePerfTest.java
+++ b/apct-tests/perftests/core/src/android/os/TracePerfTest.java
@@ -127,14 +127,14 @@
     public void testInstantPerfettoWithArgs() {
         PerfettoTrace.instant(FOO_CATEGORY, "testInstantP")
                 .addArg("foo", "bar")
-                .addFlow(1)
+                .setFlow(1)
                 .emit();
 
         BenchmarkState state = mPerfStatusReporter.getBenchmarkState();
         while (state.keepRunning()) {
             PerfettoTrace.instant(FOO_CATEGORY, "testInstantP")
                     .addArg("foo", "bar")
-                    .addFlow(1)
+                    .setFlow(1)
                     .emit();
         }
     }
diff --git a/boot/boot-image-profile-extra.txt b/boot/boot-image-profile-extra.txt
index ced0d17..ce99bfe 100644
--- a/boot/boot-image-profile-extra.txt
+++ b/boot/boot-image-profile-extra.txt
@@ -45,11 +45,15 @@
 HSPLandroid/os/MessageQueue$StackNodeType;->*
 HSPLandroid/os/MessageQueue$StateNode;->*
 HSPLandroid/os/MessageQueue$TimedParkStateNode;->*
+
+# For now, compile all methods in PerfettoTrace and PerfettoTrackEventExtra.
+# Similar to the existing Trace APIs, these new APIs can impact the performance
+# of many subsystems including MessageQueue. This also keeps benchmark
+# comparisons between both APIs fair.
 HSPLandroid/os/PerfettoTrace$Category;->*
 HSPLandroid/os/PerfettoTrace;->*
 HSPLandroid/os/PerfettoTrackEventExtra;->*
-HSPLandroid/os/PerfettoTrackEventExtra$BuilderImpl;->*
-HSPLandroid/os/PerfettoTrackEventExtra$NoOpBuilder;->*
+HSPLandroid/os/PerfettoTrackEventExtra$Builder;->*
 HSPLandroid/os/PerfettoTrackEventExtra$ArgBool;->*
 HSPLandroid/os/PerfettoTrackEventExtra$ArgInt64;->*
 HSPLandroid/os/PerfettoTrackEventExtra$ArgDouble;->*
diff --git a/core/api/current.txt b/core/api/current.txt
index 9ebb506..4862236 100644
--- a/core/api/current.txt
+++ b/core/api/current.txt
@@ -53802,8 +53802,8 @@
     method @FlaggedApi("com.android.graphics.hwui.flags.limited_hdr") public void setDesiredHdrHeadroom(@FloatRange(from=0.0f, to=10000.0) float);
     method public void setSecure(boolean);
     method public void setSurfaceLifecycle(int);
-    method public void setZOrderMediaOverlay(boolean);
-    method public void setZOrderOnTop(boolean);
+    method @Deprecated @FlaggedApi("android.view.flags.deprecate_surface_view_z_order_apis") public void setZOrderMediaOverlay(boolean);
+    method @Deprecated @FlaggedApi("android.view.flags.deprecate_surface_view_z_order_apis") public void setZOrderOnTop(boolean);
     field public static final int SURFACE_LIFECYCLE_DEFAULT = 0; // 0x0
     field public static final int SURFACE_LIFECYCLE_FOLLOWS_ATTACHMENT = 2; // 0x2
     field public static final int SURFACE_LIFECYCLE_FOLLOWS_VISIBILITY = 1; // 0x1
diff --git a/core/api/module-lib-current.txt b/core/api/module-lib-current.txt
index 40069aa..526a213 100644
--- a/core/api/module-lib-current.txt
+++ b/core/api/module-lib-current.txt
@@ -165,6 +165,16 @@
 
 package android.media {
 
+  public class AudioDeviceVolumeManager {
+    method @FlaggedApi("android.media.audio.unify_absolute_volume_management") @RequiresPermission(anyOf={android.Manifest.permission.MODIFY_AUDIO_ROUTING, android.Manifest.permission.MODIFY_AUDIO_SETTINGS_PRIVILEGED, android.Manifest.permission.BLUETOOTH_PRIVILEGED, android.Manifest.permission.BLUETOOTH_STACK}) public void setDeviceAbsoluteMultiVolumeBehavior(@NonNull android.media.AudioDeviceAttributes, @NonNull java.util.List<android.media.VolumeInfo>, @NonNull java.util.concurrent.Executor, @NonNull android.media.AudioDeviceVolumeManager.OnAudioDeviceVolumeChangedListener);
+    method @FlaggedApi("android.media.audio.unify_absolute_volume_management") @RequiresPermission(anyOf={android.Manifest.permission.MODIFY_AUDIO_ROUTING, android.Manifest.permission.MODIFY_AUDIO_SETTINGS_PRIVILEGED, android.Manifest.permission.BLUETOOTH_PRIVILEGED, android.Manifest.permission.BLUETOOTH_STACK}) public void setDeviceAbsoluteVolumeBehavior(@NonNull android.media.AudioDeviceAttributes, @NonNull android.media.VolumeInfo, @NonNull java.util.concurrent.Executor, @NonNull android.media.AudioDeviceVolumeManager.OnAudioDeviceVolumeChangedListener);
+  }
+
+  @FlaggedApi("android.media.audio.unify_absolute_volume_management") public static interface AudioDeviceVolumeManager.OnAudioDeviceVolumeChangedListener {
+    method public void onAudioDeviceVolumeAdjusted(@NonNull android.media.AudioDeviceAttributes, @NonNull android.media.VolumeInfo, int, int);
+    method public void onAudioDeviceVolumeChanged(@NonNull android.media.AudioDeviceAttributes, @NonNull android.media.VolumeInfo);
+  }
+
   public class AudioManager {
     method public void adjustStreamVolumeForUid(int, int, int, @NonNull String, int, int, int);
     method public void adjustSuggestedStreamVolumeForUid(int, int, int, @NonNull String, int, int, int);
diff --git a/core/api/test-current.txt b/core/api/test-current.txt
index 89b3773..514a582 100644
--- a/core/api/test-current.txt
+++ b/core/api/test-current.txt
@@ -1737,6 +1737,7 @@
     method @NonNull public int[] getUserDisabledHdrTypes();
     method public boolean isMinimalPostProcessingRequested(int);
     method @RequiresPermission(android.Manifest.permission.ACCESS_SURFACE_FLINGER) public void overrideHdrTypes(int, @NonNull int[]);
+    method @FlaggedApi("com.android.server.display.feature.flags.delay_implicit_rr_registration_until_rr_accessed") public void resetImplicitRefreshRateCallbackStatus();
     method @RequiresPermission(android.Manifest.permission.WRITE_SECURE_SETTINGS) public void setAreUserDisabledHdrTypesAllowed(boolean);
     method @RequiresPermission(android.Manifest.permission.MODIFY_USER_PREFERRED_DISPLAY_MODE) public void setGlobalUserPreferredDisplayMode(@NonNull android.view.Display.Mode);
     method @RequiresPermission(android.Manifest.permission.MODIFY_HDR_CONVERSION_MODE) public void setHdrConversionMode(@NonNull android.hardware.display.HdrConversionMode);
@@ -2585,7 +2586,7 @@
   }
 
   public class UserManager {
-    method @FlaggedApi("android.os.allow_private_profile") @RequiresPermission(anyOf={android.Manifest.permission.MANAGE_USERS, android.Manifest.permission.CREATE_USERS}, conditional=true) public boolean canAddPrivateProfile();
+    method @FlaggedApi("android.os.allow_private_profile") @RequiresPermission(anyOf={android.Manifest.permission.MANAGE_USERS, android.Manifest.permission.CREATE_USERS}) public boolean canAddPrivateProfile();
     method @Nullable @RequiresPermission(anyOf={android.Manifest.permission.MANAGE_USERS, android.Manifest.permission.CREATE_USERS}) public android.content.pm.UserInfo createProfileForUser(@Nullable String, @NonNull String, int, int, @Nullable String[]);
     method @Nullable @RequiresPermission(anyOf={android.Manifest.permission.MANAGE_USERS, android.Manifest.permission.CREATE_USERS}) public android.content.pm.UserInfo createRestrictedProfile(@Nullable String);
     method @Nullable @RequiresPermission(anyOf={android.Manifest.permission.MANAGE_USERS, android.Manifest.permission.CREATE_USERS}) public android.content.pm.UserInfo createUser(@Nullable String, @NonNull String, int);
diff --git a/core/java/android/app/ActivityManagerInternal.java b/core/java/android/app/ActivityManagerInternal.java
index 6151b8e..a12c067 100644
--- a/core/java/android/app/ActivityManagerInternal.java
+++ b/core/java/android/app/ActivityManagerInternal.java
@@ -23,6 +23,7 @@
 import android.annotation.Nullable;
 import android.annotation.PermissionMethod;
 import android.annotation.PermissionName;
+import android.annotation.SpecialUsers.CanBeALL;
 import android.annotation.UserIdInt;
 import android.app.ActivityManager.ProcessCapability;
 import android.app.ActivityManager.RestrictionLevel;
@@ -1365,8 +1366,8 @@
      * watchdog reset.
      * @hide
      */
-    public abstract void killApplicationSync(String pkgName, int appId, int userId,
-            String reason, int exitInfoReason);
+    public abstract void killApplicationSync(String pkgName, int appId,
+            @CanBeALL @UserIdInt int userId, String reason, int exitInfoReason);
 
     /**
      * Queries the offset data for a given method on a process.
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/Instrumentation.java b/core/java/android/app/Instrumentation.java
index b611acf..eb9feb9 100644
--- a/core/java/android/app/Instrumentation.java
+++ b/core/java/android/app/Instrumentation.java
@@ -50,6 +50,7 @@
 import android.os.UserManager;
 import android.ravenwood.annotation.RavenwoodKeep;
 import android.ravenwood.annotation.RavenwoodKeepPartialClass;
+import android.ravenwood.annotation.RavenwoodKeepWholeClass;
 import android.ravenwood.annotation.RavenwoodReplace;
 import android.util.AndroidRuntimeException;
 import android.util.Log;
@@ -460,6 +461,7 @@
      * 
      * @param runner The code to run on the main thread.
      */
+    @RavenwoodReplace(blockedBy = ActivityThread.class)
     public void runOnMainSync(Runnable runner) {
         validateNotAppThread();
         SyncRunnable sr = new SyncRunnable(runner);
@@ -467,6 +469,13 @@
         sr.waitForComplete();
     }
 
+    private void runOnMainSync$ravenwood(Runnable runner) {
+        validateNotAppThread();
+        SyncRunnable sr = new SyncRunnable(runner);
+        mInstrContext.getMainExecutor().execute(sr);
+        sr.waitForComplete();
+    }
+
     boolean isSdkSandboxAllowedToStartActivities() {
         return Process.isSdkSandbox()
                 && mThread != null
@@ -2442,7 +2451,8 @@
         }
     }
 
-    private final void validateNotAppThread() {
+    @RavenwoodKeep
+    private void validateNotAppThread() {
         if (Looper.myLooper() == Looper.getMainLooper()) {
             throw new RuntimeException(
                 "This method can not be called from the main application thread");
@@ -2586,6 +2596,7 @@
         }
     }
 
+    @RavenwoodKeepWholeClass
     private static final class SyncRunnable implements Runnable {
         private final Runnable mTarget;
         private boolean mComplete;
diff --git a/core/java/android/app/Notification.java b/core/java/android/app/Notification.java
index 5dca1c7..719e438 100644
--- a/core/java/android/app/Notification.java
+++ b/core/java/android/app/Notification.java
@@ -18,6 +18,7 @@
 
 import static android.annotation.Dimension.DP;
 import static android.app.Flags.evenlyDividedCallStyleActionLayout;
+import static android.app.Flags.notificationsRedesignTemplates;
 import static android.app.admin.DevicePolicyResources.Drawables.Source.NOTIFICATION;
 import static android.app.admin.DevicePolicyResources.Drawables.Style.SOLID_COLORED;
 import static android.app.admin.DevicePolicyResources.Drawables.WORK_PROFILE_ICON;
@@ -818,7 +819,8 @@
                      R.layout.notification_2025_template_expanded_base,
                      R.layout.notification_2025_template_heads_up_base,
                      R.layout.notification_2025_template_header,
-                     R.layout.notification_2025_template_conversation,
+                     R.layout.notification_2025_template_collapsed_conversation,
+                     R.layout.notification_2025_template_expanded_conversation,
                      R.layout.notification_2025_template_collapsed_call,
                      R.layout.notification_2025_template_expanded_call,
                      R.layout.notification_2025_template_collapsed_messaging,
@@ -5963,7 +5965,10 @@
                     || resId == getCompactHeadsUpBaseLayoutResource()
                     || resId == getMessagingCompactHeadsUpLayoutResource()
                     || resId == getCollapsedMessagingLayoutResource()
-                    || resId == getCollapsedMediaLayoutResource());
+                    || resId == getCollapsedMediaLayoutResource()
+                    || resId == getCollapsedConversationLayoutResource()
+                    || (notificationsRedesignTemplates()
+                    && resId == getCollapsedCallLayoutResource()));
             RemoteViews contentView = new BuilderRemoteViews(mContext.getApplicationInfo(), resId);
 
             resetStandardTemplate(contentView);
@@ -6001,8 +6006,7 @@
             // Update margins to leave space for the top line (but not for headerless views like
             // HUNS, which use a different layout that already accounts for that). Templates that
             // have content that will be displayed under the small icon also use a different margin.
-            if (Flags.notificationsRedesignTemplates()
-                    && !p.mHeaderless && !p.mSkipTopLineAlignment) {
+            if (Flags.notificationsRedesignTemplates() && !p.mHeaderless) {
                 int margin = getContentMarginTop(mContext,
                         R.dimen.notification_2025_content_margin_top);
                 contentView.setViewLayoutMargin(R.id.notification_main_column,
@@ -7673,12 +7677,18 @@
             }
         }
 
+        // Note: In the 2025 redesign, we use two separate layouts for the collapsed and expanded
+        //  version of conversations. See below.
         private int getConversationLayoutResource() {
-            if (Flags.notificationsRedesignTemplates()) {
-                return R.layout.notification_2025_template_conversation;
-            } else {
-                return R.layout.notification_template_material_conversation;
-            }
+            return R.layout.notification_template_material_conversation;
+        }
+
+        private int getCollapsedConversationLayoutResource() {
+            return R.layout.notification_2025_template_collapsed_conversation;
+        }
+
+        private int getExpandedConversationLayoutResource() {
+            return R.layout.notification_2025_template_expanded_conversation;
         }
 
         private int getCollapsedCallLayoutResource() {
@@ -9462,7 +9472,7 @@
             boolean hideRightIcons = viewType != StandardTemplateParams.VIEW_TYPE_NORMAL;
             boolean isConversationLayout = mConversationType != CONVERSATION_TYPE_LEGACY;
             boolean isImportantConversation = mConversationType == CONVERSATION_TYPE_IMPORTANT;
-            boolean isHeaderless = !isConversationLayout && isCollapsed;
+            boolean isLegacyHeaderless = !isConversationLayout && isCollapsed;
 
             //TODO (b/217799515): ensure mConversationTitle always returns the correct
             // conversationTitle, probably set mConversationTitle = conversationTitle after this
@@ -9483,7 +9493,8 @@
             } else {
                 isOneToOne = !isGroupConversation();
             }
-            if (isHeaderless && isOneToOne && TextUtils.isEmpty(conversationTitle)) {
+            if ((isLegacyHeaderless || notificationsRedesignTemplates())
+                    && isOneToOne && TextUtils.isEmpty(conversationTitle)) {
                 conversationTitle = getOtherPersonName();
             }
 
@@ -9493,22 +9504,24 @@
                     .viewType(viewType)
                     .highlightExpander(isConversationLayout)
                     .hideProgress(true)
-                    .title(isHeaderless ? conversationTitle : null)
                     .text(null)
                     .hideLeftIcon(isOneToOne)
-                    .hideRightIcon(hideRightIcons || isOneToOne)
-                    .headerTextSecondary(isHeaderless ? null : conversationTitle)
-                    .skipTopLineAlignment(true);
+                    .hideRightIcon(hideRightIcons || isOneToOne);
+            if (notificationsRedesignTemplates()) {
+                p.title(conversationTitle)
+                        .hideAppName(isCollapsed);
+            } else {
+                p.title(isLegacyHeaderless ? conversationTitle : null)
+                        .headerTextSecondary(isLegacyHeaderless ? null : conversationTitle);
+            }
             RemoteViews contentView = mBuilder.applyStandardTemplateWithActions(
-                    isConversationLayout
-                            ? mBuilder.getConversationLayoutResource()
-                            : isCollapsed
-                                    ? mBuilder.getCollapsedMessagingLayoutResource()
-                                    : mBuilder.getExpandedMessagingLayoutResource(),
+                    getMessagingLayoutResource(isConversationLayout, isCollapsed),
                     p,
                     bindResult);
-            if (isConversationLayout) {
+            if (isConversationLayout && !notificationsRedesignTemplates()) {
+                // Redesign note: This view is replaced by the `title`, which is handled normally.
                 mBuilder.setTextViewColorPrimary(contentView, R.id.conversation_text, p);
+                // Redesign note: This special divider is no longer needed.
                 mBuilder.setTextViewColorSecondary(contentView, R.id.app_name_divider, p);
             }
 
@@ -9538,7 +9551,18 @@
                 contentView.setBoolean(R.id.status_bar_latest_event_content,
                         "setIsImportantConversation", isImportantConversation);
             }
-            if (isHeaderless) {
+            if (notificationsRedesignTemplates() && !isCollapsed) {
+                // Align the title to the app/small icon in the expanded form. In other layouts,
+                // this margin is added directly to the notification_main_column parent, but for
+                // messages we don't want the margin to be applied to the actual messaging
+                // content since it can contain icons that are displayed below the app icon.
+                Resources res = mBuilder.mContext.getResources();
+                int marginStart = res.getDimensionPixelSize(
+                        R.dimen.notification_2025_content_margin_start);
+                contentView.setViewLayoutMargin(R.id.title,
+                        RemoteViews.MARGIN_START, marginStart, TypedValue.COMPLEX_UNIT_PX);
+            }
+            if (isLegacyHeaderless) {
                 // Collapsed legacy messaging style has a 1-line limit.
                 contentView.setInt(R.id.notification_messaging, "setMaxDisplayedLines", 1);
             }
@@ -9549,6 +9573,33 @@
             return contentView;
         }
 
+        private int getMessagingLayoutResource(boolean isConversationLayout, boolean isCollapsed) {
+            if (notificationsRedesignTemplates()) {
+                // Note: We eventually would like to use the same layouts for both conversations and
+                //  regular messaging notifications.
+                if (isConversationLayout) {
+                    if (isCollapsed) {
+                        return mBuilder.getCollapsedConversationLayoutResource();
+                    } else {
+                        return mBuilder.getExpandedConversationLayoutResource();
+                    }
+                } else {
+                    if (isCollapsed) {
+                        return mBuilder.getCollapsedMessagingLayoutResource();
+                    } else {
+                        return mBuilder.getExpandedMessagingLayoutResource();
+                    }
+                }
+
+            } else {
+                return isConversationLayout
+                        ? mBuilder.getConversationLayoutResource()
+                        : isCollapsed
+                                ? mBuilder.getCollapsedMessagingLayoutResource()
+                                : mBuilder.getExpandedMessagingLayoutResource();
+            }
+        }
+
         private CharSequence getKey(Person person) {
             return person == null ? null
                     : person.getKey() == null ? person.getName() : person.getKey();
@@ -10986,6 +11037,7 @@
 
         private RemoteViews makeCallLayout(int viewType) {
             final boolean isCollapsed = viewType == StandardTemplateParams.VIEW_TYPE_NORMAL;
+            final boolean isHeadsUp = viewType == StandardTemplateParams.VIEW_TYPE_HEADS_UP;
             Bundle extras = mBuilder.mN.extras;
             CharSequence title = mPerson != null ? mPerson.getName() : null;
             CharSequence text = mBuilder.processLegacyText(extras.getCharSequence(EXTRA_TEXT));
@@ -11001,22 +11053,31 @@
                     .hideLeftIcon(true)
                     .hideRightIcon(true)
                     .hideAppName(isCollapsed)
-                    .titleViewId(R.id.conversation_text)
                     .title(title)
-                    .text(text)
-                    .summaryText(mBuilder.processLegacyText(mVerificationText));
+                    .text(text);
+            if (!notificationsRedesignTemplates()) {
+                // We're using the normal title in the redesign, not a special text.
+                p.titleViewId(R.id.conversation_text)
+                        // The verification text is now part of the top line views, so this is no
+                        // longer necessary.
+                        .summaryText(mBuilder.processLegacyText(mVerificationText));
+            }
             mBuilder.mActions = getActionsListWithSystemActions();
             final RemoteViews contentView;
             if (isCollapsed) {
                 contentView = mBuilder.applyStandardTemplate(
                         mBuilder.getCollapsedCallLayoutResource(), p, null /* result */);
+            } else if (notificationsRedesignTemplates() && isHeadsUp) {
+                contentView = mBuilder.applyStandardTemplateWithActions(
+                        mBuilder.getCollapsedCallLayoutResource(), p, null /* result */);
             } else {
                 contentView = mBuilder.applyStandardTemplateWithActions(
                     mBuilder.getExpandedCallLayoutResource(), p, null /* result */);
             }
 
             // Bind some extra conversation-specific header fields.
-            if (!p.mHideAppName) {
+            if (!notificationsRedesignTemplates() && !p.mHideAppName) {
+                // Redesign note: This special divider is no longer needed.
                 mBuilder.setTextViewColorSecondary(contentView, R.id.app_name_divider, p);
                 contentView.setViewVisibility(R.id.app_name_divider, View.VISIBLE);
             }
@@ -14676,7 +14737,6 @@
         Icon mPromotedPicture;
         boolean mCallStyleActions;
         boolean mAllowTextWithProgress;
-        boolean mSkipTopLineAlignment;
         int mTitleViewId;
         int mTextViewId;
         @Nullable CharSequence mTitle;
@@ -14702,7 +14762,6 @@
             mPromotedPicture = null;
             mCallStyleActions = false;
             mAllowTextWithProgress = false;
-            mSkipTopLineAlignment = false;
             mTitleViewId = R.id.title;
             mTextViewId = R.id.text;
             mTitle = null;
@@ -14769,11 +14828,6 @@
             return this;
         }
 
-        public StandardTemplateParams skipTopLineAlignment(boolean skipTopLineAlignment) {
-            mSkipTopLineAlignment = skipTopLineAlignment;
-            return this;
-        }
-
         final StandardTemplateParams hideSnoozeButton(boolean hideSnoozeButton) {
             this.mHideSnoozeButton = hideSnoozeButton;
             return this;
diff --git a/core/java/android/app/admin/DevicePolicyManager.java b/core/java/android/app/admin/DevicePolicyManager.java
index 08719fc..5359ba4 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}),
@@ -17644,9 +17647,17 @@
             android.Manifest.permission.MANAGE_PROFILE_AND_DEVICE_OWNERS
     })
     public boolean isFinancedDevice() {
-        return isDeviceManaged()
-                && getDeviceOwnerType(getDeviceOwnerComponentOnAnyUser())
-                == DEVICE_OWNER_TYPE_FINANCED;
+        try {
+            return isDeviceManaged()
+                    && getDeviceOwnerType(getDeviceOwnerComponentOnAnyUser())
+                    == DEVICE_OWNER_TYPE_FINANCED;
+        } catch (IllegalStateException e) {
+            // getDeviceOwnerType() will throw IllegalStateException if the device does not have a
+            // DO. This can happen under a race condition when the DO is removed after
+            // isDeviceManaged() (so it still returns true) but before getDeviceOwnerType().
+            // In this case, the device should not be considered a financed device.
+            return false;
+        }
     }
 
     // TODO(b/315298076): revert ag/25574027 and update the doc
diff --git a/core/java/android/app/supervision/flags.aconfig b/core/java/android/app/supervision/flags.aconfig
index 94de038..7094183 100644
--- a/core/java/android/app/supervision/flags.aconfig
+++ b/core/java/android/app/supervision/flags.aconfig
@@ -72,3 +72,11 @@
   description: "Flag that enables system APIs in Supervision Manager"
   bug: "382034839"
 }
+
+flag {
+  name: "enable_web_content_filters_screen"
+  is_exported: true
+  namespace: "supervision"
+  description: "Flag that enables the web content filters screen with Supervision settings entry point"
+  bug: "395134536"
+}
diff --git a/core/java/android/content/ContentResolver.java b/core/java/android/content/ContentResolver.java
index efcaa0e..a753cbf 100644
--- a/core/java/android/content/ContentResolver.java
+++ b/core/java/android/content/ContentResolver.java
@@ -23,6 +23,8 @@
 import android.annotation.NonNull;
 import android.annotation.Nullable;
 import android.annotation.RequiresPermission;
+import android.annotation.SpecialUsers.CanBeALL;
+import android.annotation.SpecialUsers.CanBeCURRENT;
 import android.annotation.SuppressLint;
 import android.annotation.SystemApi;
 import android.annotation.TestApi;
@@ -2709,7 +2711,7 @@
     public final void registerContentObserverAsUser(@NonNull Uri uri,
             boolean notifyForDescendants,
             @NonNull ContentObserver observer,
-            @NonNull UserHandle userHandle) {
+            @NonNull @CanBeALL @CanBeCURRENT UserHandle userHandle) {
         Objects.requireNonNull(uri, "uri");
         Objects.requireNonNull(observer, "observer");
         Objects.requireNonNull(userHandle, "userHandle");
@@ -2723,7 +2725,7 @@
     /** @hide - designated user version */
     @UnsupportedAppUsage
     public final void registerContentObserver(Uri uri, boolean notifyForDescendants,
-            ContentObserver observer, @UserIdInt int userHandle) {
+            ContentObserver observer, @CanBeALL @CanBeCURRENT @UserIdInt int userHandle) {
         try {
             getContentService().registerContentObserver(uri, notifyForDescendants,
                     observer.getContentObserver(), userHandle, mTargetSdkVersion);
diff --git a/core/java/android/content/res/Resources.java b/core/java/android/content/res/Resources.java
index 0559631..2658efa 100644
--- a/core/java/android/content/res/Resources.java
+++ b/core/java/android/content/res/Resources.java
@@ -482,7 +482,6 @@
      *
      * @return Typeface The Typeface data associated with the resource.
      */
-    @RavenwoodThrow(blockedBy = Typeface.class)
     @NonNull public Typeface getFont(@FontRes int id) throws NotFoundException {
         final TypedValue value = obtainTempTypedValue();
         try {
@@ -507,7 +506,6 @@
     /**
      * @hide
      */
-    @RavenwoodThrow(blockedBy = Typeface.class)
     public void preloadFonts(@ArrayRes int id) {
         final TypedArray array = obtainTypedArray(id);
         try {
diff --git a/core/java/android/content/res/ResourcesImpl.java b/core/java/android/content/res/ResourcesImpl.java
index eec30f3..8c76fd7 100644
--- a/core/java/android/content/res/ResourcesImpl.java
+++ b/core/java/android/content/res/ResourcesImpl.java
@@ -1068,7 +1068,6 @@
      * Loads a font from XML or resources stream.
      */
     @Nullable
-    @RavenwoodThrow(blockedBy = Typeface.class)
     public Typeface loadFont(Resources wrapper, TypedValue value, int id) {
         if (value.string == null) {
             throw new NotFoundException("Resource \"" + getResourceName(id) + "\" ("
diff --git a/core/java/android/content/res/TypedArray.java b/core/java/android/content/res/TypedArray.java
index 79185a1..ee7d008 100644
--- a/core/java/android/content/res/TypedArray.java
+++ b/core/java/android/content/res/TypedArray.java
@@ -1043,7 +1043,6 @@
      *         not a font resource.
      */
     @Nullable
-    @RavenwoodThrow(blockedBy = Typeface.class)
     public Typeface getFont(@StyleableRes int index) {
         if (mRecycled) {
             throw new RuntimeException("Cannot make calls to a recycled instance!");
diff --git a/core/java/android/graphics/fonts/FontFamilyUpdateRequest.java b/core/java/android/graphics/fonts/FontFamilyUpdateRequest.java
index bfbcfd8..1bf01f4 100644
--- a/core/java/android/graphics/fonts/FontFamilyUpdateRequest.java
+++ b/core/java/android/graphics/fonts/FontFamilyUpdateRequest.java
@@ -68,6 +68,7 @@
  * @hide
  */
 @SystemApi
+@android.ravenwood.annotation.RavenwoodKeepWholeClass
 public final class FontFamilyUpdateRequest {
 
     /**
diff --git a/core/java/android/graphics/fonts/FontFileUpdateRequest.java b/core/java/android/graphics/fonts/FontFileUpdateRequest.java
index cf1dca9..1f2be6f 100644
--- a/core/java/android/graphics/fonts/FontFileUpdateRequest.java
+++ b/core/java/android/graphics/fonts/FontFileUpdateRequest.java
@@ -28,6 +28,7 @@
  * @hide
  */
 @SystemApi
+@android.ravenwood.annotation.RavenwoodKeepWholeClass
 public final class FontFileUpdateRequest {
 
     private final ParcelFileDescriptor mParcelFileDescriptor;
diff --git a/core/java/android/hardware/contexthub/HubEndpoint.java b/core/java/android/hardware/contexthub/HubEndpoint.java
index b7edef6..47ba51d 100644
--- a/core/java/android/hardware/contexthub/HubEndpoint.java
+++ b/core/java/android/hardware/contexthub/HubEndpoint.java
@@ -126,7 +126,16 @@
                     if (sessionExists) {
                         Log.w(
                                 TAG,
-                                "onSessionOpenComplete: session already exists, id=" + sessionId);
+                                "onSessionOpenRequest: session already exists, id=" + sessionId);
+                    }
+
+                    if (mLifecycleCallback == null) {
+                        Log.w(
+                                TAG,
+                                "onSessionOpenRequest: "
+                                        + "failed to open session, no lifecycle callback attached",
+                                new Exception());
+                        rejectSession(sessionId);
                     }
 
                     if (!sessionExists && mLifecycleCallback != null) {
diff --git a/core/java/android/hardware/contexthub/HubEndpointSession.java b/core/java/android/hardware/contexthub/HubEndpointSession.java
index ca59be8..c5e2d7a 100644
--- a/core/java/android/hardware/contexthub/HubEndpointSession.java
+++ b/core/java/android/hardware/contexthub/HubEndpointSession.java
@@ -88,7 +88,6 @@
                                 : ContextHubTransaction.TYPE_HUB_MESSAGE_DEFAULT);
         if (!isResponseRequired) {
             // If the message doesn't require acknowledgement, respond with success immediately
-            // TODO(b/379162322): Improve handling of synchronous failures.
             mHubEndpoint.sendMessage(this, message, null);
             ret.setResponse(
                     new ContextHubTransaction.Response<>(
diff --git a/core/java/android/hardware/display/DisplayManager.java b/core/java/android/hardware/display/DisplayManager.java
index 7850e37..92a56fc 100644
--- a/core/java/android/hardware/display/DisplayManager.java
+++ b/core/java/android/hardware/display/DisplayManager.java
@@ -851,6 +851,12 @@
      * Registers a display listener to receive notifications about when
      * displays are added, removed or changed.
      *
+     * Because of the high frequency at which the refresh rate can change, clients will be
+     * registered for refresh rate change callbacks only when they request for refresh rate
+     * data({@link Display#getRefreshRate()}. Or alternately, you can consider using
+     * {@link #registerDisplayListener(Executor, long, DisplayListener)} and explicitly subscribe to
+     * {@link #EVENT_TYPE_DISPLAY_REFRESH_RATE} event
+     *
      * We encourage to use {@link #registerDisplayListener(Executor, long, DisplayListener)}
      * instead to subscribe for explicit events of interest
      *
@@ -863,8 +869,8 @@
     public void registerDisplayListener(DisplayListener listener, Handler handler) {
         registerDisplayListener(listener, handler, EVENT_TYPE_DISPLAY_ADDED
                 | EVENT_TYPE_DISPLAY_CHANGED
-                | EVENT_TYPE_DISPLAY_REFRESH_RATE
-                | EVENT_TYPE_DISPLAY_REMOVED);
+                | EVENT_TYPE_DISPLAY_REMOVED, 0,
+                ActivityThread.currentPackageName(), /* isEventFilterExplicit */ false);
     }
 
     /**
@@ -882,9 +888,8 @@
      */
     public void registerDisplayListener(@NonNull DisplayListener listener,
             @Nullable Handler handler, @EventType long eventFilter) {
-        mGlobal.registerDisplayListener(listener, handler,
-                mGlobal.mapFiltersToInternalEventFlag(eventFilter, 0),
-                ActivityThread.currentPackageName());
+        registerDisplayListener(listener, handler, eventFilter, 0,
+                ActivityThread.currentPackageName(), /* isEventFilterExplicit */ true);
     }
 
     /**
@@ -901,9 +906,8 @@
     @FlaggedApi(FLAG_DISPLAY_LISTENER_PERFORMANCE_IMPROVEMENTS)
     public void registerDisplayListener(@NonNull Executor executor, @EventType long eventFilter,
             @NonNull DisplayListener listener) {
-        mGlobal.registerDisplayListener(listener, executor,
-                mGlobal.mapFiltersToInternalEventFlag(eventFilter, 0),
-                ActivityThread.currentPackageName());
+        registerDisplayListener(listener, executor, eventFilter, 0,
+                ActivityThread.currentPackageName(), /* isEventFilterExplicit */ true);
     }
 
     /**
@@ -924,9 +928,39 @@
     public void registerDisplayListener(@NonNull DisplayListener listener,
             @Nullable Handler handler, @EventType long eventFilter,
             @PrivateEventType long privateEventFilter) {
+        registerDisplayListener(listener, handler, eventFilter, privateEventFilter,
+                ActivityThread.currentPackageName(), /* isEventFilterExplicit */ true);
+    }
+
+    /**
+     * Registers a display listener to receive notifications about given display event types.
+     *
+     * @param listener The listener to register.
+     * @param handler The handler on which the listener should be invoked, or null
+     * if the listener should be invoked on the calling thread's looper.
+     * @param eventFilter A bitmask of the event types for which this listener is subscribed.
+     * @param privateEventFilter A bitmask of the private event types for which this listener
+     *                          is subscribed.
+     * @param isEventFilterExplicit Indicates if the client explicitly supplied the display events
+     *                          to be subscribed to.
+     *
+     */
+    private void registerDisplayListener(@NonNull DisplayListener listener,
+            @Nullable Handler handler, @EventType long eventFilter,
+            @PrivateEventType long privateEventFilter, String packageName,
+            boolean isEventFilterExplicit) {
         mGlobal.registerDisplayListener(listener, handler,
                 mGlobal.mapFiltersToInternalEventFlag(eventFilter, privateEventFilter),
-                ActivityThread.currentPackageName());
+                packageName, /* isEventFilterExplicit */ isEventFilterExplicit);
+    }
+
+    private void registerDisplayListener(@NonNull DisplayListener listener,
+            Executor executor, @EventType long eventFilter,
+            @PrivateEventType long privateEventFilter, String packageName,
+            boolean isEventFilterExplicit) {
+        mGlobal.registerDisplayListener(listener, executor,
+                mGlobal.mapFiltersToInternalEventFlag(eventFilter, privateEventFilter),
+                packageName, /* isEventFilterExplicit */ isEventFilterExplicit);
     }
 
     /**
@@ -1146,6 +1180,28 @@
     }
 
     /**
+     * Resets the behavior that automatically registers clients for refresh rate change callbacks
+     * when they register via {@link #registerDisplayListener(DisplayListener, Handler)}
+     *
+     * <p>By default, clients are not registered for refresh rate change callbacks via
+     * {@link #registerDisplayListener(DisplayListener, Handler)}. However, calling
+     * {@link Display#getRefreshRate()} triggers automatic registration for existing and future
+     * {@link DisplayListener} instances. This method reverts this behavior, preventing new
+     * clients from being automatically registered for refresh rate change callbacks. Note that the
+     * existing ones will continue to stay registered
+     *
+     * <p>In essence, this method returns the system to its initial state, where explicit calls to
+     * {{@link Display#getRefreshRate()} are required to receive refresh rate change notifications.
+     *
+     * @hide
+     */
+    @FlaggedApi(Flags.FLAG_DELAY_IMPLICIT_RR_REGISTRATION_UNTIL_RR_ACCESSED)
+    @TestApi
+    public void resetImplicitRefreshRateCallbackStatus() {
+        mGlobal.resetImplicitRefreshRateCallbackStatus();
+    }
+
+    /**
      * Overrides HDR modes for a display device.
      *
      * @hide
diff --git a/core/java/android/hardware/display/DisplayManagerGlobal.java b/core/java/android/hardware/display/DisplayManagerGlobal.java
index a7d610e..c4af871 100644
--- a/core/java/android/hardware/display/DisplayManagerGlobal.java
+++ b/core/java/android/hardware/display/DisplayManagerGlobal.java
@@ -187,6 +187,9 @@
 
     private final Binder mToken = new Binder();
 
+    // Guarded by mLock
+    private boolean mShouldImplicitlyRegisterRrChanges = false;
+
     @VisibleForTesting
     public DisplayManagerGlobal(IDisplayManager dm) {
         mDm = dm;
@@ -390,16 +393,36 @@
      * the handler for the main thread.
      * If that is still null, a runtime exception will be thrown.
      * @param packageName of the calling package.
+     * @param isEventFilterExplicit Indicates if the client explicitly supplied the display events
+     *                              to be subscribed to.
+     */
+    public void registerDisplayListener(@NonNull DisplayListener listener,
+            @Nullable Handler handler, @InternalEventFlag long internalEventFlagsMask,
+            String packageName, boolean isEventFilterExplicit) {
+        Looper looper = getLooperForHandler(handler);
+        Handler springBoard = new Handler(looper);
+        registerDisplayListener(listener, new HandlerExecutor(springBoard), internalEventFlagsMask,
+                packageName, isEventFilterExplicit);
+    }
+
+    /**
+     * Register a listener for display-related changes.
+     *
+     * @param listener The listener that will be called when display changes occur.
+     * @param handler Handler for the thread that will be receiving the callbacks. May be null.
+     * If null, listener will use the handler for the current thread, and if still null,
+     * the handler for the main thread.
+     * If that is still null, a runtime exception will be thrown.
+     * @param internalEventFlagsMask Mask of events to be listened to.
+     * @param packageName of the calling package.
      */
     public void registerDisplayListener(@NonNull DisplayListener listener,
             @Nullable Handler handler, @InternalEventFlag long internalEventFlagsMask,
             String packageName) {
-        Looper looper = getLooperForHandler(handler);
-        Handler springBoard = new Handler(looper);
-        registerDisplayListener(listener, new HandlerExecutor(springBoard), internalEventFlagsMask,
-                packageName);
+        registerDisplayListener(listener, handler, internalEventFlagsMask, packageName, true);
     }
 
+
     /**
      * Register a listener for display-related changes.
      *
@@ -407,10 +430,12 @@
      * @param executor Executor for the thread that will be receiving the callbacks. Cannot be null.
      * @param internalEventFlagsMask Mask of events to be listened to.
      * @param packageName of the calling package.
+     * @param isEventFilterExplicit Indicates if the explicit events to be subscribed to
+     *                                     were supplied or not
      */
     public void registerDisplayListener(@NonNull DisplayListener listener,
             @NonNull Executor executor, @InternalEventFlag long internalEventFlagsMask,
-            String packageName) {
+            String packageName, boolean isEventFilterExplicit) {
         if (listener == null) {
             throw new IllegalArgumentException("listener must not be null");
         }
@@ -429,7 +454,7 @@
             int index = findDisplayListenerLocked(listener);
             if (index < 0) {
                 mDisplayListeners.add(new DisplayListenerDelegate(listener, executor,
-                        internalEventFlagsMask, packageName));
+                        internalEventFlagsMask, packageName, isEventFilterExplicit));
                 registerCallbackIfNeededLocked();
             } else {
                 mDisplayListeners.get(index).setEventsMask(internalEventFlagsMask);
@@ -439,6 +464,22 @@
         }
     }
 
+
+    /**
+     * Registers all the clients to INTERNAL_EVENT_FLAG_DISPLAY_REFRESH_RATE events if qualified
+     */
+    public void registerForRefreshRateChanges() {
+        if (!Flags.delayImplicitRrRegistrationUntilRrAccessed()) {
+            return;
+        }
+        synchronized (mLock) {
+            if (!mShouldImplicitlyRegisterRrChanges) {
+                mShouldImplicitlyRegisterRrChanges = true;
+                updateCallbackIfNeededLocked();
+            }
+        }
+    }
+
     public void unregisterDisplayListener(DisplayListener listener) {
         if (listener == null) {
             throw new IllegalArgumentException("listener must not be null");
@@ -521,8 +562,14 @@
         long mask = 0;
         final int numListeners = mDisplayListeners.size();
         for (int i = 0; i < numListeners; i++) {
-            mask |= mDisplayListeners.get(i).mInternalEventFlagsMask;
+            DisplayListenerDelegate displayListenerDelegate = mDisplayListeners.get(i);
+            if (!Flags.delayImplicitRrRegistrationUntilRrAccessed()
+                    || mShouldImplicitlyRegisterRrChanges) {
+                displayListenerDelegate.implicitlyRegisterForRRChanges();
+            }
+            mask |= displayListenerDelegate.mInternalEventFlagsMask;
         }
+
         if (mDispatchNativeCallbacks) {
             mask |= INTERNAL_EVENT_FLAG_DISPLAY_ADDED
                     | INTERNAL_EVENT_FLAG_DISPLAY_BASIC_CHANGED
@@ -802,6 +849,18 @@
     }
 
     /**
+     * Resets the implicit registration of refresh rate change callbacks
+     *
+     */
+    public void resetImplicitRefreshRateCallbackStatus() {
+        if (Flags.delayImplicitRrRegistrationUntilRrAccessed()) {
+            synchronized (mLock) {
+                mShouldImplicitlyRegisterRrChanges = false;
+            }
+        }
+    }
+
+    /**
      * Overrides HDR modes for a display device.
      *
      */
@@ -1439,21 +1498,27 @@
         }
     }
 
-    private static final class DisplayListenerDelegate {
+    @VisibleForTesting
+    static final class DisplayListenerDelegate {
         public final DisplayListener mListener;
         public volatile long mInternalEventFlagsMask;
 
+        // Indicates if the client explicitly supplied the display events to be subscribed to.
+        private final boolean mIsEventFilterExplicit;
+
         private final DisplayInfo mDisplayInfo = new DisplayInfo();
         private final Executor mExecutor;
         private AtomicLong mGenerationId = new AtomicLong(1);
         private final String mPackageName;
 
         DisplayListenerDelegate(DisplayListener listener, @NonNull Executor executor,
-                @InternalEventFlag long internalEventFlag, String packageName) {
+                @InternalEventFlag long internalEventFlag, String packageName,
+                boolean isEventFilterExplicit) {
             mExecutor = executor;
             mListener = listener;
             mInternalEventFlagsMask = internalEventFlag;
             mPackageName = packageName;
+            mIsEventFilterExplicit = isEventFilterExplicit;
         }
 
         void sendDisplayEvent(int displayId, @DisplayEvent int event, @Nullable DisplayInfo info,
@@ -1470,6 +1535,11 @@
             });
         }
 
+        @VisibleForTesting
+        boolean isEventFilterExplicit() {
+            return mIsEventFilterExplicit;
+        }
+
         void clearEvents() {
             mGenerationId.incrementAndGet();
         }
@@ -1478,6 +1548,17 @@
             mInternalEventFlagsMask = newInternalEventFlagsMask;
         }
 
+        private void implicitlyRegisterForRRChanges() {
+            // For backward compatibility, if the user didn't supply the explicit events while
+            // subscribing, register them to refresh rate change events if they subscribed to
+            // display changed events
+            if ((mInternalEventFlagsMask & INTERNAL_EVENT_FLAG_DISPLAY_BASIC_CHANGED) != 0
+                    && !mIsEventFilterExplicit) {
+                setEventsMask(mInternalEventFlagsMask
+                        | INTERNAL_EVENT_FLAG_DISPLAY_REFRESH_RATE);
+            }
+        }
+
         private void handleDisplayEventInner(int displayId, @DisplayEvent int event,
                 @Nullable DisplayInfo info, boolean forceUpdate) {
             if (extraLogging()) {
@@ -1677,6 +1758,9 @@
     public void registerNativeChoreographerForRefreshRateCallbacks() {
         synchronized (mLock) {
             mDispatchNativeCallbacks = true;
+            if (Flags.delayImplicitRrRegistrationUntilRrAccessed()) {
+                mShouldImplicitlyRegisterRrChanges = true;
+            }
             registerCallbackIfNeededLocked();
             updateCallbackIfNeededLocked();
             DisplayInfo display = getDisplayInfoLocked(Display.DEFAULT_DISPLAY);
@@ -1806,4 +1890,9 @@
 
         return baseEventMask;
     }
+
+    @VisibleForTesting
+    CopyOnWriteArrayList<DisplayListenerDelegate> getDisplayListeners() {
+        return mDisplayListeners;
+    }
 }
diff --git a/core/java/android/inputmethodservice/InputMethodService.java b/core/java/android/inputmethodservice/InputMethodService.java
index a528ba4..7b47efd4 100644
--- a/core/java/android/inputmethodservice/InputMethodService.java
+++ b/core/java/android/inputmethodservice/InputMethodService.java
@@ -990,6 +990,8 @@
             }
             Trace.traceEnd(TRACE_TAG_WINDOW_MANAGER);
             if (android.view.inputmethod.Flags.refactorInsetsController()) {
+                // After the IME window was hidden, we can remove its surface
+                scheduleImeSurfaceRemoval();
                 // The hide request first finishes the animation and then proceeds to the server
                 // side, finally reaching here, marking this the end state.
                 ImeTracker.forLogging().onHidden(statsToken);
diff --git a/core/java/android/os/BundleMerger.java b/core/java/android/os/BundleMerger.java
index dc243a5..fd5a18c 100644
--- a/core/java/android/os/BundleMerger.java
+++ b/core/java/android/os/BundleMerger.java
@@ -119,11 +119,23 @@
     public static final int STRATEGY_ARRAY_APPEND = 50;
 
     /**
+     * Merge strategy that combines two conflicting array values by creating a new array
+     * containing all unique elements from both arrays.
+     */
+    public static final int STRATEGY_ARRAY_UNION = 55;
+
+    /**
      * Merge strategy that combines two conflicting {@link ArrayList} values by
      * appending the last {@link ArrayList} after the first {@link ArrayList}.
      */
     public static final int STRATEGY_ARRAY_LIST_APPEND = 60;
 
+    /**
+     * Merge strategy that combines two conflicting {@link String} values by
+     * appending the last {@link String} after the first {@link String}.
+     */
+    public static final int STRATEGY_STRING_APPEND = 70;
+
     @IntDef(flag = false, prefix = { "STRATEGY_" }, value = {
             STRATEGY_REJECT,
             STRATEGY_FIRST,
@@ -136,7 +148,9 @@
             STRATEGY_BOOLEAN_AND,
             STRATEGY_BOOLEAN_OR,
             STRATEGY_ARRAY_APPEND,
+            STRATEGY_ARRAY_UNION,
             STRATEGY_ARRAY_LIST_APPEND,
+            STRATEGY_STRING_APPEND,
     })
     @Retention(RetentionPolicy.SOURCE)
     public @interface Strategy {}
@@ -298,8 +312,12 @@
                 return booleanOr(first, last);
             case STRATEGY_ARRAY_APPEND:
                 return arrayAppend(first, last);
+            case STRATEGY_ARRAY_UNION:
+                return arrayUnion(first, last);
             case STRATEGY_ARRAY_LIST_APPEND:
                 return arrayListAppend(first, last);
+            case STRATEGY_STRING_APPEND:
+                return stringAppend(first, last);
             default:
                 throw new UnsupportedOperationException();
         }
@@ -361,6 +379,29 @@
         return res;
     }
 
+    private static @NonNull Object arrayUnion(@NonNull Object first, @NonNull Object last) {
+        if (!first.getClass().isArray()) {
+            throw new IllegalArgumentException("Unable to union " + first.getClass());
+        }
+        final int firstLength = Array.getLength(first);
+        final int lastLength = Array.getLength(last);
+        final ArrayList<Object> list = new ArrayList<>(firstLength + lastLength);
+        final ArraySet<Object> set = new ArraySet<>();
+        for (int i = 0; i < firstLength; i++) {
+            set.add(Array.get(first, i));
+        }
+        for (int i = 0; i < lastLength; i++) {
+            set.add(Array.get(last, i));
+        }
+        final Class<?> clazz = first.getClass().getComponentType();
+        final int setSize = set.size();
+        final Object res = Array.newInstance(clazz, setSize);
+        for (int i = 0; i < setSize; i++) {
+            Array.set(res, i, set.valueAt(i));
+        }
+        return res;
+    }
+
     @SuppressWarnings("unchecked")
     private static @NonNull Object arrayListAppend(@NonNull Object first, @NonNull Object last) {
         if (!(first instanceof ArrayList)) {
@@ -374,6 +415,13 @@
         return res;
     }
 
+    private static @NonNull Object stringAppend(@NonNull Object first, @NonNull Object last) {
+        if (!(first instanceof String)) {
+            throw new IllegalArgumentException("Unable to append " + first.getClass());
+        }
+        return ((String) first) + ((String) last);
+    }
+
     public static final @android.annotation.NonNull Parcelable.Creator<BundleMerger> CREATOR =
             new Parcelable.Creator<BundleMerger>() {
                 @Override
diff --git a/core/java/android/os/PerfettoTrace.java b/core/java/android/os/PerfettoTrace.java
index 741d542..932836f 100644
--- a/core/java/android/os/PerfettoTrace.java
+++ b/core/java/android/os/PerfettoTrace.java
@@ -232,10 +232,6 @@
      * @param eventName The event name to appear in the trace.
      */
     public static PerfettoTrackEventExtra.Builder instant(Category category, String eventName) {
-        if (!category.isEnabled()) {
-            return PerfettoTrackEventExtra.noOpBuilder();
-        }
-
         return PerfettoTrackEventExtra.builder().init(PERFETTO_TE_TYPE_INSTANT, category)
             .setEventName(eventName);
     }
@@ -247,10 +243,6 @@
      * @param eventName The event name to appear in the trace.
      */
     public static PerfettoTrackEventExtra.Builder begin(Category category, String eventName) {
-        if (!category.isEnabled()) {
-            return PerfettoTrackEventExtra.noOpBuilder();
-        }
-
         return PerfettoTrackEventExtra.builder().init(PERFETTO_TE_TYPE_SLICE_BEGIN, category)
             .setEventName(eventName);
     }
@@ -261,10 +253,6 @@
      * @param category The perfetto category.
      */
     public static PerfettoTrackEventExtra.Builder end(Category category) {
-        if (!category.isEnabled()) {
-            return PerfettoTrackEventExtra.noOpBuilder();
-        }
-
         return PerfettoTrackEventExtra.builder().init(PERFETTO_TE_TYPE_SLICE_END, category);
     }
 
@@ -275,10 +263,6 @@
      * @param value The value of the counter.
      */
     public static PerfettoTrackEventExtra.Builder counter(Category category, long value) {
-        if (!category.isEnabled()) {
-            return PerfettoTrackEventExtra.noOpBuilder();
-        }
-
         return PerfettoTrackEventExtra.builder().init(PERFETTO_TE_TYPE_COUNTER, category)
             .setCounter(value);
     }
@@ -302,10 +286,6 @@
      * @param value The value of the counter.
      */
     public static PerfettoTrackEventExtra.Builder counter(Category category, double value) {
-        if (!category.isEnabled()) {
-            return PerfettoTrackEventExtra.noOpBuilder();
-        }
-
         return PerfettoTrackEventExtra.builder().init(PERFETTO_TE_TYPE_COUNTER, category)
             .setCounter(value);
     }
diff --git a/core/java/android/os/PerfettoTrackEventExtra.java b/core/java/android/os/PerfettoTrackEventExtra.java
index 6844229..f4b5dfe 100644
--- a/core/java/android/os/PerfettoTrackEventExtra.java
+++ b/core/java/android/os/PerfettoTrackEventExtra.java
@@ -35,8 +35,8 @@
  */
 @android.ravenwood.annotation.RavenwoodKeepWholeClass
 public final class PerfettoTrackEventExtra {
+    private static final boolean DEBUG = false;
     private static final int DEFAULT_EXTRA_CACHE_SIZE = 5;
-    private static final Builder NO_OP_BUILDER = new NoOpBuilder();
     private static final ThreadLocal<PerfettoTrackEventExtra> sTrackEventExtra =
             new ThreadLocal<PerfettoTrackEventExtra>() {
                 @Override
@@ -46,7 +46,7 @@
             };
     private static final AtomicLong sNamedTrackId = new AtomicLong();
     private static final Supplier<Flow> sFlowSupplier = Flow::new;
-    private static final Supplier<BuilderImpl> sBuilderSupplier = BuilderImpl::new;
+    private static final Supplier<Builder> sBuilderSupplier = Builder::new;
     private static final Supplier<FieldInt64> sFieldInt64Supplier = FieldInt64::new;
     private static final Supplier<FieldDouble> sFieldDoubleSupplier = FieldDouble::new;
     private static final Supplier<FieldString> sFieldStringSupplier = FieldString::new;
@@ -56,6 +56,8 @@
     private CounterInt64 mCounterInt64;
     private CounterDouble mCounterDouble;
     private Proto mProto;
+    private Flow mFlow;
+    private Flow mTerminatingFlow;
 
     /**
      * Represents a native pointer to a Perfetto C SDK struct. E.g. PerfettoTeHlExtra.
@@ -135,245 +137,10 @@
         }
     }
 
-    public interface Builder {
-        /**
-         * Emits the track event.
-         */
-        void emit();
-
-        /**
-         * Initialize the builder for a new trace event.
-         */
-        Builder init(int traceType, PerfettoTrace.Category category);
-
-        /**
-         * Sets the event name for the track event.
-         */
-        Builder setEventName(String eventName);
-
-        /**
-         * Adds a debug arg with key {@code name} and value {@code val}.
-         */
-        Builder addArg(String name, long val);
-
-        /**
-         * Adds a debug arg with key {@code name} and value {@code val}.
-         */
-        Builder addArg(String name, boolean val);
-
-        /**
-         * Adds a debug arg with key {@code name} and value {@code val}.
-         */
-        Builder addArg(String name, double val);
-
-        /**
-         * Adds a debug arg with key {@code name} and value {@code val}.
-         */
-        Builder addArg(String name, String val);
-
-        /**
-         * Adds a flow with {@code id}.
-         */
-        Builder addFlow(int id);
-
-        /**
-         * Adds a terminating flow with {@code id}.
-         */
-        Builder addTerminatingFlow(int id);
-
-        /**
-         * Adds the events to a named track instead of the thread track where the
-         * event occurred.
-         */
-        Builder usingNamedTrack(long parentUuid, String name);
-
-        /**
-         * Adds the events to a process scoped named track instead of the thread track where the
-         * event occurred.
-         */
-        Builder usingProcessNamedTrack(String name);
-
-        /**
-         * Adds the events to a thread scoped named track instead of the thread track where the
-         * event occurred.
-         */
-        Builder usingThreadNamedTrack(long tid, String name);
-
-        /**
-         * Adds the events to a counter track instead. This is required for
-         * setting counter values.
-         */
-        Builder usingCounterTrack(long parentUuid, String name);
-
-        /**
-         * Adds the events to a process scoped counter track instead. This is required for
-         * setting counter values.
-         */
-        Builder usingProcessCounterTrack(String name);
-
-        /**
-         * Adds the events to a thread scoped counter track instead. This is required for
-         * setting counter values.
-         */
-        Builder usingThreadCounterTrack(long tid, String name);
-
-        /**
-         * Sets a long counter value on the event.
-         *
-         */
-        Builder setCounter(long val);
-
-        /**
-         * Sets a double counter value on the event.
-         *
-         */
-        Builder setCounter(double val);
-
-        /**
-         * Adds a proto field with field id {@code id} and value {@code val}.
-         */
-        Builder addField(long id, long val);
-
-        /**
-         * Adds a proto field with field id {@code id} and value {@code val}.
-         */
-        Builder addField(long id, double val);
-
-        /**
-         * Adds a proto field with field id {@code id} and value {@code val}.
-         */
-        Builder addField(long id, String val);
-
-        /**
-         * Begins a proto field with field
-         * Fields can be added from this point and there must be a corresponding
-         * {@link endProto}.
-         *
-         * The proto field is a singleton and all proto fields get added inside the
-         * one {@link beginProto} and {@link endProto} within the {@link Builder}.
-         */
-        Builder beginProto();
-
-        /**
-         * Ends a proto field.
-         */
-        Builder endProto();
-
-        /**
-         * Begins a nested proto field with field id {@code id}.
-         * Fields can be added from this point and there must be a corresponding
-         * {@link endNested}.
-         */
-        Builder beginNested(long id);
-
-        /**
-         * Ends a nested proto field.
-         */
-        Builder endNested();
-    }
-
-    @android.ravenwood.annotation.RavenwoodKeepWholeClass
-    public static final class NoOpBuilder implements Builder {
-        @Override
-        public void emit() {}
-        @Override
-        public Builder init(int traceType, PerfettoTrace.Category category) {
-            return this;
-        }
-        @Override
-        public Builder setEventName(String eventName) {
-            return this;
-        }
-        @Override
-        public Builder addArg(String name, long val) {
-            return this;
-        }
-        @Override
-        public Builder addArg(String name, boolean val) {
-            return this;
-        }
-        @Override
-        public Builder addArg(String name, double val) {
-            return this;
-        }
-        @Override
-        public Builder addArg(String name, String val) {
-            return this;
-        }
-        @Override
-        public Builder addFlow(int id) {
-            return this;
-        }
-        @Override
-        public Builder addTerminatingFlow(int id) {
-            return this;
-        }
-        @Override
-        public Builder usingNamedTrack(long parentUuid, String name) {
-            return this;
-        }
-        @Override
-        public Builder usingProcessNamedTrack(String name) {
-            return this;
-        }
-        @Override
-        public Builder usingThreadNamedTrack(long tid, String name) {
-            return this;
-        }
-        @Override
-        public Builder usingCounterTrack(long parentUuid, String name) {
-            return this;
-        }
-        @Override
-        public Builder usingProcessCounterTrack(String name) {
-            return this;
-        }
-        @Override
-        public Builder usingThreadCounterTrack(long tid, String name) {
-            return this;
-        }
-        @Override
-        public Builder setCounter(long val) {
-            return this;
-        }
-        @Override
-        public Builder setCounter(double val) {
-            return this;
-        }
-        @Override
-        public Builder addField(long id, long val) {
-            return this;
-        }
-        @Override
-        public Builder addField(long id, double val) {
-            return this;
-        }
-        @Override
-        public Builder addField(long id, String val) {
-            return this;
-        }
-        @Override
-        public Builder beginProto() {
-            return this;
-        }
-        @Override
-        public Builder endProto() {
-            return this;
-        }
-        @Override
-        public Builder beginNested(long id) {
-            return this;
-        }
-        @Override
-        public Builder endNested() {
-            return this;
-        }
-    }
-
     /**
      * Builder for Perfetto track event extras.
      */
-    public static final class BuilderImpl implements Builder {
+    public static final class Builder {
         // For performance reasons, we hold a reference to mExtra as a holder for
         // perfetto pointers being added. This way, we avoid an additional list to hold
         // the pointers in Java and we can pass them down directly to native code.
@@ -386,10 +153,13 @@
 
         private Builder mParent;
         private FieldContainer mCurrentContainer;
+        private boolean mIsCategoryEnabled;
 
         private final CounterInt64 mCounterInt64;
         private final CounterDouble mCounterDouble;
         private final Proto mProto;
+        private final Flow mFlow;
+        private final Flow mTerminatingFlow;
 
         private final RingBuffer<NamedTrack> mNamedTrackCache;
         private final RingBuffer<CounterTrack> mCounterTrackCache;
@@ -403,9 +173,9 @@
         private final Pool<FieldString> mFieldStringCache;
         private final Pool<FieldNested> mFieldNestedCache;
         private final Pool<Flow> mFlowCache;
-        private final Pool<BuilderImpl> mBuilderCache;
+        private final Pool<Builder> mBuilderCache;
 
-        private BuilderImpl() {
+        private Builder() {
             mExtra = sTrackEventExtra.get();
             mNamedTrackCache = mExtra.mNamedTrackCache;
             mCounterTrackCache = mExtra.mCounterTrackCache;
@@ -423,20 +193,32 @@
             mCounterInt64 = mExtra.getCounterInt64();
             mCounterDouble = mExtra.getCounterDouble();
             mProto = mExtra.getProto();
+            mFlow = mExtra.getFlow();
+            mTerminatingFlow = mExtra.getTerminatingFlow();
         }
 
-        @Override
+        /**
+         * Emits the track event.
+         */
         public void emit() {
-            checkParent();
-            mIsBuilt = true;
+            if (!mIsCategoryEnabled) {
+                return;
+            }
+            if (DEBUG) {
+                checkParent();
+            }
 
+            mIsBuilt = true;
             native_emit(mTraceType, mCategory.getPtr(), mEventName, mExtra.getPtr());
-            // Reset after emitting to free any the extras used to trace the event.
-            mExtra.reset();
         }
 
-        @Override
+        /**
+         * Initialize the builder for a new trace event.
+         */
         public Builder init(int traceType, PerfettoTrace.Category category) {
+            if (!category.isEnabled()) {
+                return this;
+            }
             mTraceType = traceType;
             mCategory = category;
             mEventName = "";
@@ -449,18 +231,27 @@
 
             mExtra.reset();
             // Reset after on init in case the thread created builders without calling emit
-            return initInternal(this, null);
+            return initInternal(this, null, true);
         }
 
-        @Override
+        /**
+         * Sets the event name for the track event.
+         */
         public Builder setEventName(String eventName) {
             mEventName = eventName;
             return this;
         }
 
-        @Override
+        /**
+         * Adds a debug arg with key {@code name} and value {@code val}.
+         */
         public Builder addArg(String name, long val) {
-            checkParent();
+            if (!mIsCategoryEnabled) {
+                return this;
+            }
+            if (DEBUG) {
+                checkParent();
+            }
             ArgInt64 arg = mArgInt64Cache.get(name.hashCode());
             if (arg == null || !arg.getName().equals(name)) {
                 arg = new ArgInt64(name);
@@ -471,9 +262,16 @@
             return this;
         }
 
-        @Override
+        /**
+         * Adds a debug arg with key {@code name} and value {@code val}.
+         */
         public Builder addArg(String name, boolean val) {
-            checkParent();
+            if (!mIsCategoryEnabled) {
+                return this;
+            }
+            if (DEBUG) {
+                checkParent();
+            }
             ArgBool arg = mArgBoolCache.get(name.hashCode());
             if (arg == null || !arg.getName().equals(name)) {
                 arg = new ArgBool(name);
@@ -484,9 +282,16 @@
             return this;
         }
 
-        @Override
+        /**
+         * Adds a debug arg with key {@code name} and value {@code val}.
+         */
         public Builder addArg(String name, double val) {
-            checkParent();
+            if (!mIsCategoryEnabled) {
+                return this;
+            }
+            if (DEBUG) {
+                checkParent();
+            }
             ArgDouble arg = mArgDoubleCache.get(name.hashCode());
             if (arg == null || !arg.getName().equals(name)) {
                 arg = new ArgDouble(name);
@@ -497,9 +302,16 @@
             return this;
         }
 
-        @Override
+        /**
+         * Adds a debug arg with key {@code name} and value {@code val}.
+         */
         public Builder addArg(String name, String val) {
-            checkParent();
+            if (!mIsCategoryEnabled) {
+                return this;
+            }
+            if (DEBUG) {
+                checkParent();
+            }
             ArgString arg = mArgStringCache.get(name.hashCode());
             if (arg == null || !arg.getName().equals(name)) {
                 arg = new ArgString(name);
@@ -510,27 +322,79 @@
             return this;
         }
 
-        @Override
+        /**
+         * Adds a flow with {@code id}.
+         */
         public Builder addFlow(int id) {
-            checkParent();
+            if (!mIsCategoryEnabled) {
+                return this;
+            }
+            if (DEBUG) {
+                checkParent();
+            }
             Flow flow = mFlowCache.get(sFlowSupplier);
             flow.setProcessFlow(id);
             mExtra.addPerfettoPointer(flow);
             return this;
         }
 
-        @Override
+        /**
+         * Adds a terminating flow with {@code id}.
+         */
         public Builder addTerminatingFlow(int id) {
-            checkParent();
+            if (!mIsCategoryEnabled) {
+                return this;
+            }
+            if (DEBUG) {
+                checkParent();
+            }
             Flow flow = mFlowCache.get(sFlowSupplier);
             flow.setProcessTerminatingFlow(id);
             mExtra.addPerfettoPointer(flow);
             return this;
         }
 
-        @Override
+        /**
+         * Adds a flow with {@code id}.
+         */
+        public Builder setFlow(int id) {
+            if (!mIsCategoryEnabled) {
+                return this;
+            }
+            if (DEBUG) {
+                checkParent();
+            }
+            mFlow.setProcessFlow(id);
+            mExtra.addPerfettoPointer(mFlow);
+            return this;
+        }
+
+        /**
+         * Adds a terminating flow with {@code id}.
+         */
+        public Builder setTerminatingFlow(int id) {
+            if (!mIsCategoryEnabled) {
+                return this;
+            }
+            if (DEBUG) {
+                checkParent();
+            }
+            mTerminatingFlow.setProcessTerminatingFlow(id);
+            mExtra.addPerfettoPointer(mTerminatingFlow);
+            return this;
+        }
+
+        /**
+         * Adds the events to a named track instead of the thread track where the
+         * event occurred.
+         */
         public Builder usingNamedTrack(long parentUuid, String name) {
-            checkParent();
+            if (!mIsCategoryEnabled) {
+                return this;
+            }
+            if (DEBUG) {
+                checkParent();
+            }
 
             NamedTrack track = mNamedTrackCache.get(name.hashCode());
             if (track == null || !track.getName().equals(name)) {
@@ -541,19 +405,39 @@
             return this;
         }
 
-        @Override
+        /**
+         * Adds the events to a process scoped named track instead of the thread track where the
+         * event occurred.
+         */
         public Builder usingProcessNamedTrack(String name) {
+            if (!mIsCategoryEnabled) {
+                return this;
+            }
             return usingNamedTrack(PerfettoTrace.getProcessTrackUuid(), name);
         }
 
-        @Override
+        /**
+         * Adds the events to a thread scoped named track instead of the thread track where the
+         * event occurred.
+         */
         public Builder usingThreadNamedTrack(long tid, String name) {
+            if (!mIsCategoryEnabled) {
+                return this;
+            }
             return usingNamedTrack(PerfettoTrace.getThreadTrackUuid(tid), name);
         }
 
-        @Override
+        /**
+         * Adds the events to a counter track instead. This is required for
+         * setting counter values.
+         */
         public Builder usingCounterTrack(long parentUuid, String name) {
-            checkParent();
+            if (!mIsCategoryEnabled) {
+                return this;
+            }
+            if (DEBUG) {
+                checkParent();
+            }
 
             CounterTrack track = mCounterTrackCache.get(name.hashCode());
             if (track == null || !track.getName().equals(name)) {
@@ -564,95 +448,178 @@
             return this;
         }
 
-        @Override
+        /**
+         * Adds the events to a process scoped counter track instead. This is required for
+         * setting counter values.
+         */
         public Builder usingProcessCounterTrack(String name) {
+            if (!mIsCategoryEnabled) {
+                return this;
+            }
             return usingCounterTrack(PerfettoTrace.getProcessTrackUuid(), name);
         }
 
-        @Override
+        /**
+         * Adds the events to a thread scoped counter track instead. This is required for
+         * setting counter values.
+         */
         public Builder usingThreadCounterTrack(long tid, String name) {
+            if (!mIsCategoryEnabled) {
+                return this;
+            }
             return usingCounterTrack(PerfettoTrace.getThreadTrackUuid(tid), name);
         }
 
-        @Override
+        /**
+         * Sets a long counter value on the event.
+         *
+         */
         public Builder setCounter(long val) {
-            checkParent();
+            if (!mIsCategoryEnabled) {
+                return this;
+            }
+            if (DEBUG) {
+                checkParent();
+            }
             mCounterInt64.setValue(val);
             mExtra.addPerfettoPointer(mCounterInt64);
             return this;
         }
 
-        @Override
+        /**
+         * Sets a double counter value on the event.
+         *
+         */
         public Builder setCounter(double val) {
-            checkParent();
+            if (!mIsCategoryEnabled) {
+                return this;
+            }
+            if (DEBUG) {
+                checkParent();
+            }
             mCounterDouble.setValue(val);
             mExtra.addPerfettoPointer(mCounterDouble);
             return this;
         }
 
-        @Override
+        /**
+         * Adds a proto field with field id {@code id} and value {@code val}.
+         */
         public Builder addField(long id, long val) {
-            checkContainer();
+            if (!mIsCategoryEnabled) {
+                return this;
+            }
+            if (DEBUG) {
+                checkContainer();
+            }
             FieldInt64 field = mFieldInt64Cache.get(sFieldInt64Supplier);
             field.setValue(id, val);
             mExtra.addPerfettoPointer(mCurrentContainer, field);
             return this;
         }
 
-        @Override
+        /**
+         * Adds a proto field with field id {@code id} and value {@code val}.
+         */
         public Builder addField(long id, double val) {
-            checkContainer();
+            if (!mIsCategoryEnabled) {
+                return this;
+            }
+            if (DEBUG) {
+                checkContainer();
+            }
             FieldDouble field = mFieldDoubleCache.get(sFieldDoubleSupplier);
             field.setValue(id, val);
             mExtra.addPerfettoPointer(mCurrentContainer, field);
             return this;
         }
 
-        @Override
+        /**
+         * Adds a proto field with field id {@code id} and value {@code val}.
+         */
         public Builder addField(long id, String val) {
-            checkContainer();
+            if (!mIsCategoryEnabled) {
+                return this;
+            }
+            if (DEBUG) {
+                checkContainer();
+            }
             FieldString field = mFieldStringCache.get(sFieldStringSupplier);
             field.setValue(id, val);
             mExtra.addPerfettoPointer(mCurrentContainer, field);
             return this;
         }
 
-        @Override
+        /**
+         * Begins a proto field.
+         * Fields can be added from this point and there must be a corresponding
+         * {@link endProto}.
+         *
+         * The proto field is a singleton and all proto fields get added inside the
+         * one {@link beginProto} and {@link endProto} within the {@link Builder}.
+         */
         public Builder beginProto() {
-            checkParent();
+            if (!mIsCategoryEnabled) {
+                return this;
+            }
+            if (DEBUG) {
+                checkParent();
+            }
             mProto.clearFields();
             mExtra.addPerfettoPointer(mProto);
-            return mBuilderCache.get(sBuilderSupplier).initInternal(this, mProto);
+            return mBuilderCache.get(sBuilderSupplier).initInternal(this, mProto, true);
         }
 
-        @Override
+        /**
+         * Ends a proto field.
+         */
         public Builder endProto() {
+            if (!mIsCategoryEnabled) {
+                return this;
+            }
             if (mParent == null || mCurrentContainer == null) {
                 throw new IllegalStateException("No proto to end");
             }
             return mParent;
         }
 
-        @Override
+        /**
+         * Begins a nested proto field with field id {@code id}.
+         * Fields can be added from this point and there must be a corresponding
+         * {@link endNested}.
+         */
         public Builder beginNested(long id) {
-            checkContainer();
+            if (!mIsCategoryEnabled) {
+                return this;
+            }
+            if (DEBUG) {
+                checkContainer();
+            }
             FieldNested field = mFieldNestedCache.get(sFieldNestedSupplier);
             field.setId(id);
             mExtra.addPerfettoPointer(mCurrentContainer, field);
-            return mBuilderCache.get(sBuilderSupplier).initInternal(this, field);
+            return mBuilderCache.get(sBuilderSupplier).initInternal(this, field, true);
         }
 
-        @Override
+        /**
+         * Ends a nested proto field.
+         */
         public Builder endNested() {
+            if (!mIsCategoryEnabled) {
+                return this;
+            }
             if (mParent == null || mCurrentContainer == null) {
                 throw new IllegalStateException("No nested field to end");
             }
             return mParent;
         }
 
-        private Builder initInternal(Builder parent, FieldContainer container) {
+
+        private Builder initInternal(Builder parent, FieldContainer field,
+                boolean isCategoryEnabled) {
             mParent = parent;
-            mCurrentContainer = container;
+            mCurrentContainer = field;
+            mIsCategoryEnabled = isCategoryEnabled;
             mIsBuilt = false;
 
             return this;
@@ -685,14 +652,8 @@
      * Start a {@link Builder} to build a {@link PerfettoTrackEventExtra}.
      */
     public static Builder builder() {
-        return sTrackEventExtra.get().mBuilderCache.get(sBuilderSupplier).initInternal(null, null);
-    }
-
-    /**
-     * Returns a no-op {@link Builder}. Useful if a category is disabled.
-     */
-    public static Builder noOpBuilder() {
-        return NO_OP_BUILDER;
+        return sTrackEventExtra.get().mBuilderCache.get(sBuilderSupplier).initInternal(null, null,
+            false);
     }
 
     private final RingBuffer<NamedTrack> mNamedTrackCache =
@@ -710,7 +671,7 @@
     private final Pool<FieldString> mFieldStringCache = new Pool(DEFAULT_EXTRA_CACHE_SIZE);
     private final Pool<FieldNested> mFieldNestedCache = new Pool(DEFAULT_EXTRA_CACHE_SIZE);
     private final Pool<Flow> mFlowCache = new Pool(DEFAULT_EXTRA_CACHE_SIZE);
-    private final Pool<BuilderImpl> mBuilderCache = new Pool(DEFAULT_EXTRA_CACHE_SIZE);
+    private final Pool<Builder> mBuilderCache = new Pool(DEFAULT_EXTRA_CACHE_SIZE);
 
     private static final NativeAllocationRegistry sRegistry =
             NativeAllocationRegistry.createMalloced(
@@ -757,6 +718,7 @@
         mPendingPointers.clear();
     }
 
+    @android.ravenwood.annotation.RavenwoodReplace
     private CounterInt64 getCounterInt64() {
         if (mCounterInt64 == null) {
             mCounterInt64 = new CounterInt64();
@@ -764,6 +726,7 @@
         return mCounterInt64;
     }
 
+    @android.ravenwood.annotation.RavenwoodReplace
     private CounterDouble getCounterDouble() {
         if (mCounterDouble == null) {
             mCounterDouble = new CounterDouble();
@@ -771,6 +734,7 @@
         return mCounterDouble;
     }
 
+    @android.ravenwood.annotation.RavenwoodReplace
     private Proto getProto() {
         if (mProto == null) {
             mProto = new Proto();
@@ -778,6 +742,22 @@
         return mProto;
     }
 
+    @android.ravenwood.annotation.RavenwoodReplace
+    private Flow getFlow() {
+        if (mFlow == null) {
+            mFlow = new Flow();
+        }
+        return mFlow;
+    }
+
+    @android.ravenwood.annotation.RavenwoodReplace
+    private Flow getTerminatingFlow() {
+        if (mTerminatingFlow == null) {
+            mTerminatingFlow = new Flow();
+        }
+        return mTerminatingFlow;
+    }
+
     private static final class Flow implements PerfettoPointer {
         private static final NativeAllocationRegistry sRegistry =
                 NativeAllocationRegistry.createMalloced(
@@ -1337,4 +1317,29 @@
         // Tracing currently completely disabled under Ravenwood
         return 0;
     }
+
+    private CounterInt64 getCounterInt64$ravenwood() {
+        // Tracing currently completely disabled under Ravenwood
+        return null;
+    }
+
+    private CounterDouble getCounterDouble$ravenwood() {
+        // Tracing currently completely disabled under Ravenwood
+        return null;
+    }
+
+    private Proto getProto$ravenwood() {
+        // Tracing currently completely disabled under Ravenwood
+        return null;
+    }
+
+    private Flow getFlow$ravenwood() {
+        // Tracing currently completely disabled under Ravenwood
+        return null;
+    }
+
+    private Flow getTerminatingFlow$ravenwood() {
+        // Tracing currently completely disabled under Ravenwood
+        return null;
+    }
 }
diff --git a/core/java/android/os/UserManager.java b/core/java/android/os/UserManager.java
index 33bf4a2..767019d 100644
--- a/core/java/android/os/UserManager.java
+++ b/core/java/android/os/UserManager.java
@@ -3324,8 +3324,7 @@
     @FlaggedApi(android.os.Flags.FLAG_ALLOW_PRIVATE_PROFILE)
     @RequiresPermission(anyOf = {
             Manifest.permission.MANAGE_USERS,
-            Manifest.permission.CREATE_USERS},
-            conditional = true)
+            Manifest.permission.CREATE_USERS})
     @UserHandleAware
     public boolean canAddPrivateProfile() {
         if (!android.multiuser.Flags.enablePrivateSpaceFeatures()) return false;
diff --git a/core/java/android/os/vibrator/flags.aconfig b/core/java/android/os/vibrator/flags.aconfig
index 7ceb948..0615578 100644
--- a/core/java/android/os/vibrator/flags.aconfig
+++ b/core/java/android/os/vibrator/flags.aconfig
@@ -145,3 +145,13 @@
         purpose: PURPOSE_FEATURE
     }
 }
+
+flag {
+    namespace: "haptics"
+    name: "fix_vibration_thread_callback_handling"
+    description: "Fix how the VibrationThread handles late callbacks from the vibrator HAL"
+    bug: "395005081"
+    metadata {
+      purpose: PURPOSE_BUGFIX
+    }
+}
diff --git a/core/java/android/permission/flags.aconfig b/core/java/android/permission/flags.aconfig
index d469a2f..ca24c0c 100644
--- a/core/java/android/permission/flags.aconfig
+++ b/core/java/android/permission/flags.aconfig
@@ -514,3 +514,12 @@
     description: "Force AttributionSource.myAttributionSource() to return a default device id"
     bug: "343121936"
 }
+
+flag {
+    name: "grant_read_blocked_numbers_to_system_ui_intelligence"
+    is_exported: true
+    is_fixed_read_only: true
+    namespace: "permissions"
+    description: "This flag is used to add role protection to READ_BLOCKED_NUMBERS for SYSTEM_UI_INTELLIGENCE"
+    bug: "354758615"
+}
diff --git a/core/java/android/provider/Settings.java b/core/java/android/provider/Settings.java
index 4ebfe53..538283e 100644
--- a/core/java/android/provider/Settings.java
+++ b/core/java/android/provider/Settings.java
@@ -13017,18 +13017,16 @@
          * false/0.
          * @hide
          */
-        @Readable
         public static final String REDACT_OTP_NOTIFICATION_WHILE_CONNECTED_TO_WIFI =
                 "redact_otp_on_wifi";
 
         /**
-         * Toggle for whether to immediately redact OTP notifications, or require the device to be
-         * locked for 10 minutes. Defaults to false/0
+         * Time (in milliseconds) that the device should need to be locked, in order for an OTP
+         * notification to be redacted. Default is 10 minutes (600,000 ms)
          * @hide
          */
-        @Readable
-        public static final String REDACT_OTP_NOTIFICATION_IMMEDIATELY =
-                "remove_otp_redaction_delay";
+        public static final String OTP_NOTIFICATION_REDACTION_LOCK_TIME =
+                "otp_redaction_lock_time";
 
         /**
          * These entries are considered common between the personal and the managed profile,
diff --git a/core/java/android/text/AlteredCharSequence.java b/core/java/android/text/AlteredCharSequence.java
index 971a47d..a05c690a 100644
--- a/core/java/android/text/AlteredCharSequence.java
+++ b/core/java/android/text/AlteredCharSequence.java
@@ -24,6 +24,7 @@
  * @deprecated The functionality this class offers is easily implemented outside the framework.
  */
 @Deprecated
+@android.ravenwood.annotation.RavenwoodKeepWholeClass
 public class AlteredCharSequence
 implements CharSequence, GetChars
 {
diff --git a/core/java/android/text/AndroidBidi.java b/core/java/android/text/AndroidBidi.java
index 31da799..fcdd50a 100644
--- a/core/java/android/text/AndroidBidi.java
+++ b/core/java/android/text/AndroidBidi.java
@@ -28,6 +28,7 @@
  * @hide
  */
 @VisibleForTesting(visibility = VisibleForTesting.Visibility.PACKAGE)
+@android.ravenwood.annotation.RavenwoodKeepWholeClass
 public class AndroidBidi {
 
     /**
diff --git a/core/java/android/text/AndroidCharacter.java b/core/java/android/text/AndroidCharacter.java
index c5f1a01..37c4fdb 100644
--- a/core/java/android/text/AndroidCharacter.java
+++ b/core/java/android/text/AndroidCharacter.java
@@ -22,6 +22,7 @@
  * @deprecated Use various methods from {@link android.icu.lang.UCharacter}, instead.
  */
 @Deprecated
+@android.ravenwood.annotation.RavenwoodKeepWholeClass
 public class AndroidCharacter
 {
     public static final int EAST_ASIAN_WIDTH_NEUTRAL = 0;
diff --git a/core/java/android/text/Annotation.java b/core/java/android/text/Annotation.java
index bb5d3ea..ac3e5a9 100644
--- a/core/java/android/text/Annotation.java
+++ b/core/java/android/text/Annotation.java
@@ -23,6 +23,7 @@
  * TextView save/restore cycles and can be used to keep application-specific
  * data that needs to be maintained for regions of text.
  */
+@android.ravenwood.annotation.RavenwoodKeepWholeClass
 public class Annotation implements ParcelableSpan {
     private final String mKey;
     private final String mValue;
diff --git a/core/java/android/text/AutoGrowArray.java b/core/java/android/text/AutoGrowArray.java
index e428377..06c74c3 100644
--- a/core/java/android/text/AutoGrowArray.java
+++ b/core/java/android/text/AutoGrowArray.java
@@ -30,6 +30,7 @@
  *
  * @hide
  */
+@android.ravenwood.annotation.RavenwoodKeepWholeClass
 public final class AutoGrowArray {
     private static final int MIN_CAPACITY_INCREMENT = 12;
     private static final int MAX_CAPACITY_TO_BE_KEPT = 10000;
diff --git a/core/java/android/text/AutoText.java b/core/java/android/text/AutoText.java
index c5339a4..d7b0547 100644
--- a/core/java/android/text/AutoText.java
+++ b/core/java/android/text/AutoText.java
@@ -31,6 +31,7 @@
 /**
  * This class accesses a dictionary of corrections to frequent misspellings.
  */
+@android.ravenwood.annotation.RavenwoodKeepWholeClass
 public class AutoText {
     // struct trie {
     //     char c;
diff --git a/core/java/android/text/BidiFormatter.java b/core/java/android/text/BidiFormatter.java
index dfa172d..6d4103c 100644
--- a/core/java/android/text/BidiFormatter.java
+++ b/core/java/android/text/BidiFormatter.java
@@ -82,6 +82,7 @@
  * first-strong estimation algorithm. It can also be configured to use a custom directionality
  * estimation object.
  */
+@android.ravenwood.annotation.RavenwoodKeepWholeClass
 public final class BidiFormatter {
 
     /**
diff --git a/core/java/android/text/BoringLayout.java b/core/java/android/text/BoringLayout.java
index 4fdcecc..2b410e6 100644
--- a/core/java/android/text/BoringLayout.java
+++ b/core/java/android/text/BoringLayout.java
@@ -45,6 +45,7 @@
  * {@link android.graphics.Canvas#drawText(java.lang.CharSequence, int, int, float, float, android.graphics.Paint)
  *  Canvas.drawText()} directly.</p>
  */
+@android.ravenwood.annotation.RavenwoodKeepWholeClass
 public class BoringLayout extends Layout implements TextUtils.EllipsizeCallback {
 
     /**
diff --git a/core/java/android/text/CharSequenceCharacterIterator.java b/core/java/android/text/CharSequenceCharacterIterator.java
index 9b07d29..1599be8 100644
--- a/core/java/android/text/CharSequenceCharacterIterator.java
+++ b/core/java/android/text/CharSequenceCharacterIterator.java
@@ -24,6 +24,7 @@
  * An implementation of {@link java.text.CharacterIterator} that iterates over a given CharSequence.
  * {@hide}
  */
+@android.ravenwood.annotation.RavenwoodKeepWholeClass
 public class CharSequenceCharacterIterator implements CharacterIterator {
     private final int mBeginIndex, mEndIndex;
     private int mIndex;
diff --git a/core/java/android/text/ClipboardManager.java b/core/java/android/text/ClipboardManager.java
index d030910..41990f0 100644
--- a/core/java/android/text/ClipboardManager.java
+++ b/core/java/android/text/ClipboardManager.java
@@ -21,6 +21,7 @@
  * {@link android.content.ClipboardManager} for the modern API.
  */
 @Deprecated
+@android.ravenwood.annotation.RavenwoodKeepWholeClass
 public abstract class ClipboardManager {
     /**
      * Returns the text on the clipboard.  It will eventually be possible
diff --git a/core/java/android/text/DynamicLayout.java b/core/java/android/text/DynamicLayout.java
index 6b1aef7..3b66ce0 100644
--- a/core/java/android/text/DynamicLayout.java
+++ b/core/java/android/text/DynamicLayout.java
@@ -53,6 +53,7 @@
  * {@link android.graphics.Canvas#drawText(java.lang.CharSequence, int, int, float, float, android.graphics.Paint)
  *  Canvas.drawText()} directly.</p>
  */
+@android.ravenwood.annotation.RavenwoodKeepWholeClass
 public class DynamicLayout extends Layout {
     private static final int PRIORITY = 128;
     private static final int BLOCK_MINIMUM_CHARACTER_LENGTH = 400;
diff --git a/core/java/android/text/Editable.java b/core/java/android/text/Editable.java
index a942f6c..53d819f 100644
--- a/core/java/android/text/Editable.java
+++ b/core/java/android/text/Editable.java
@@ -22,6 +22,7 @@
  * to immutable text like Strings).  If you make a {@link DynamicLayout}
  * of an Editable, the layout will be reflowed as the text is changed.
  */
+@android.ravenwood.annotation.RavenwoodKeepWholeClass
 public interface Editable
 extends CharSequence, GetChars, Spannable, Appendable
 {
diff --git a/core/java/android/text/Emoji.java b/core/java/android/text/Emoji.java
index cf0e3c2..28c37c0 100644
--- a/core/java/android/text/Emoji.java
+++ b/core/java/android/text/Emoji.java
@@ -23,6 +23,7 @@
  * An utility class for Emoji.
  * @hide
  */
+@android.ravenwood.annotation.RavenwoodKeepWholeClass
 public class Emoji {
     public static int COMBINING_ENCLOSING_KEYCAP = 0x20E3;
 
diff --git a/core/java/android/text/EmojiConsistency.java b/core/java/android/text/EmojiConsistency.java
index dfaa217..9823305 100644
--- a/core/java/android/text/EmojiConsistency.java
+++ b/core/java/android/text/EmojiConsistency.java
@@ -48,6 +48,7 @@
  *     </ol>
  * </p>
  */
+@android.ravenwood.annotation.RavenwoodKeepWholeClass
 public final class EmojiConsistency {
     /* Cannot construct */
     private EmojiConsistency() { }
diff --git a/core/java/android/text/FontConfig.java b/core/java/android/text/FontConfig.java
index 783f3b7..5a4d3a8 100644
--- a/core/java/android/text/FontConfig.java
+++ b/core/java/android/text/FontConfig.java
@@ -55,6 +55,7 @@
  */
 @SystemApi
 @TestApi
+@android.ravenwood.annotation.RavenwoodKeepWholeClass
 public final class FontConfig implements Parcelable {
     private final @NonNull List<FontFamily> mFamilies;
     private final @NonNull List<Alias> mAliases;
diff --git a/core/java/android/text/GetChars.java b/core/java/android/text/GetChars.java
index 348a911..229f543 100644
--- a/core/java/android/text/GetChars.java
+++ b/core/java/android/text/GetChars.java
@@ -21,6 +21,7 @@
  * getChars() method like the one in String that is faster than
  * calling charAt() multiple times.
  */
+@android.ravenwood.annotation.RavenwoodKeepWholeClass
 public interface GetChars
 extends CharSequence
 {
diff --git a/core/java/android/text/GraphemeClusterSegmentFinder.java b/core/java/android/text/GraphemeClusterSegmentFinder.java
index 0f6fdaf..996223d 100644
--- a/core/java/android/text/GraphemeClusterSegmentFinder.java
+++ b/core/java/android/text/GraphemeClusterSegmentFinder.java
@@ -31,6 +31,7 @@
  * @see <a href="https://unicode.org/reports/tr29/#Grapheme_Cluster_Boundaries">Unicode Text
  *     Segmentation - Grapheme Cluster Boundaries</a>
  */
+@android.ravenwood.annotation.RavenwoodKeepWholeClass
 public class GraphemeClusterSegmentFinder extends SegmentFinder {
     private static AutoGrowArray.FloatArray sTempAdvances = null;
     private final boolean[] mIsGraphemeBreak;
diff --git a/core/java/android/text/GraphicsOperations.java b/core/java/android/text/GraphicsOperations.java
index 6c15446..f7fe805 100644
--- a/core/java/android/text/GraphicsOperations.java
+++ b/core/java/android/text/GraphicsOperations.java
@@ -26,6 +26,7 @@
  *
  * @hide
  */
+@android.ravenwood.annotation.RavenwoodKeepWholeClass
 public interface GraphicsOperations extends CharSequence {
     /**
      * Just like {@link Canvas#drawText}.
diff --git a/core/java/android/text/Highlights.java b/core/java/android/text/Highlights.java
index 693dbcf..217a38b 100644
--- a/core/java/android/text/Highlights.java
+++ b/core/java/android/text/Highlights.java
@@ -30,6 +30,7 @@
 /**
  * A class that represents of the highlight of the text.
  */
+@android.ravenwood.annotation.RavenwoodKeepWholeClass
 public class Highlights {
     private final List<Pair<Paint, int[]>> mHighlights;
 
diff --git a/core/java/android/text/Html.java b/core/java/android/text/Html.java
index a42eece..d412071 100644
--- a/core/java/android/text/Html.java
+++ b/core/java/android/text/Html.java
@@ -17,13 +17,13 @@
 package android.text;
 
 import android.app.ActivityThread;
-import android.app.Application;
 import android.compat.annotation.UnsupportedAppUsage;
 import android.content.res.Resources;
 import android.graphics.Color;
 import android.graphics.Typeface;
 import android.graphics.drawable.Drawable;
 import android.os.Build;
+import android.ravenwood.annotation.RavenwoodReplace;
 import android.text.style.AbsoluteSizeSpan;
 import android.text.style.AlignmentSpan;
 import android.text.style.BackgroundColorSpan;
@@ -65,6 +65,7 @@
  * This class processes HTML strings into displayable styled text.
  * Not all HTML tags are supported.
  */
+@android.ravenwood.annotation.RavenwoodKeepWholeClass
 public class Html {
     /**
      * Retrieves images for HTML &lt;img&gt; tags.
@@ -506,6 +507,15 @@
         out.append("</p>\n");
     }
 
+    @RavenwoodReplace(blockedBy = ActivityThread.class)
+    private static float getDisplayMetricsDensity() {
+        return ActivityThread.currentApplication().getResources().getDisplayMetrics().density;
+    }
+
+    private static float getDisplayMetricsDensity$ravenwood() {
+        return Resources.getSystem().getDisplayMetrics().density;
+    }
+
     private static void withinParagraph(StringBuilder out, Spanned text, int start, int end) {
         int next;
         for (int i = start; i < end; i = next) {
@@ -559,8 +569,7 @@
                     AbsoluteSizeSpan s = ((AbsoluteSizeSpan) style[j]);
                     float sizeDip = s.getSize();
                     if (!s.getDip()) {
-                        Application application = ActivityThread.currentApplication();
-                        sizeDip /= application.getResources().getDisplayMetrics().density;
+                        sizeDip /= getDisplayMetricsDensity();
                     }
 
                     // px in CSS is the equivalance of dip in Android
@@ -669,6 +678,7 @@
     }
 }
 
+@android.ravenwood.annotation.RavenwoodKeepWholeClass
 class HtmlToSpannedConverter implements ContentHandler {
 
     private static final float[] HEADING_SIZES = {
@@ -843,6 +853,16 @@
         }
     }
 
+    @RavenwoodReplace(blockedBy = ActivityThread.class)
+    private static int getFontWeightAdjustment() {
+        return ActivityThread.currentApplication().getResources()
+                .getConfiguration().fontWeightAdjustment;
+    }
+
+    private static int getFontWeightAdjustment$ravenwood() {
+        return Resources.getSystem().getConfiguration().fontWeightAdjustment;
+    }
+
     private void handleEndTag(String tag) {
         if (tag.equalsIgnoreCase("br")) {
             handleBr(mSpannableStringBuilder);
@@ -858,17 +878,11 @@
         } else if (tag.equalsIgnoreCase("span")) {
             endCssStyle(mSpannableStringBuilder);
         } else if (tag.equalsIgnoreCase("strong")) {
-            Application application = ActivityThread.currentApplication();
-            int fontWeightAdjustment =
-                    application.getResources().getConfiguration().fontWeightAdjustment;
             end(mSpannableStringBuilder, Bold.class, new StyleSpan(Typeface.BOLD,
-                    fontWeightAdjustment));
+                    getFontWeightAdjustment()));
         } else if (tag.equalsIgnoreCase("b")) {
-            Application application = ActivityThread.currentApplication();
-            int fontWeightAdjustment =
-                    application.getResources().getConfiguration().fontWeightAdjustment;
             end(mSpannableStringBuilder, Bold.class, new StyleSpan(Typeface.BOLD,
-                    fontWeightAdjustment));
+                    getFontWeightAdjustment()));
         } else if (tag.equalsIgnoreCase("em")) {
             end(mSpannableStringBuilder, Italic.class, new StyleSpan(Typeface.ITALIC));
         } else if (tag.equalsIgnoreCase("cite")) {
@@ -1036,11 +1050,8 @@
         // Their ranges should not include the newlines at the end
         Heading h = getLast(text, Heading.class);
         if (h != null) {
-            Application application = ActivityThread.currentApplication();
-            int fontWeightAdjustment =
-                    application.getResources().getConfiguration().fontWeightAdjustment;
             setSpanFromMark(text, h, new RelativeSizeSpan(HEADING_SIZES[h.mLevel]),
-                    new StyleSpan(Typeface.BOLD, fontWeightAdjustment));
+                    new StyleSpan(Typeface.BOLD, getFontWeightAdjustment()));
         }
 
         endBlockElement(text);
diff --git a/core/java/android/text/Hyphenator.java b/core/java/android/text/Hyphenator.java
index 6f0628a..7f9a8a1 100644
--- a/core/java/android/text/Hyphenator.java
+++ b/core/java/android/text/Hyphenator.java
@@ -21,6 +21,7 @@
  *
  * @hide
  */
+@android.ravenwood.annotation.RavenwoodKeepWholeClass
 public class Hyphenator {
     private Hyphenator() {}
 
diff --git a/core/java/android/text/InputFilter.java b/core/java/android/text/InputFilter.java
index 96e7bd0..ed5de03e 100644
--- a/core/java/android/text/InputFilter.java
+++ b/core/java/android/text/InputFilter.java
@@ -27,6 +27,7 @@
  * InputFilters can be attached to {@link Editable}s to constrain the
  * changes that can be made to them.
  */
+@android.ravenwood.annotation.RavenwoodKeepWholeClass
 public interface InputFilter
 {
     /**
diff --git a/core/java/android/text/InputType.java b/core/java/android/text/InputType.java
index 4ebecb7..03c9c02 100644
--- a/core/java/android/text/InputType.java
+++ b/core/java/android/text/InputType.java
@@ -44,6 +44,7 @@
  *     TYPE_DATETIME_VARIATION_TIME
  * </dl>
  */
+@android.ravenwood.annotation.RavenwoodKeepWholeClass
 public interface InputType {
     /**
      * Mask of bits that determine the overall class
diff --git a/core/java/android/text/Layout.java b/core/java/android/text/Layout.java
index b273a7f..44c3f9a 100644
--- a/core/java/android/text/Layout.java
+++ b/core/java/android/text/Layout.java
@@ -69,6 +69,7 @@
  * which will be updated as the text changes.
  * For text that will not change, use a {@link StaticLayout}.
  */
+@android.ravenwood.annotation.RavenwoodKeepWholeClass
 public abstract class Layout {
 
     // These should match the constants in framework/base/libs/hwui/hwui/DrawTextFunctor.h
diff --git a/core/java/android/text/LoginFilter.java b/core/java/android/text/LoginFilter.java
index 0e4eec44..94f196f 100644
--- a/core/java/android/text/LoginFilter.java
+++ b/core/java/android/text/LoginFilter.java
@@ -23,6 +23,7 @@
  * handle non-BMP characters.
  */
 @Deprecated
+@android.ravenwood.annotation.RavenwoodKeepWholeClass
 public abstract class LoginFilter implements InputFilter {
     private boolean mAppendInvalid;  // whether to append or ignore invalid characters
     /**
diff --git a/core/java/android/text/MeasuredParagraph.java b/core/java/android/text/MeasuredParagraph.java
index 31a2263..b2e4459 100644
--- a/core/java/android/text/MeasuredParagraph.java
+++ b/core/java/android/text/MeasuredParagraph.java
@@ -68,6 +68,7 @@
  * @hide
  */
 @TestApi
+@android.ravenwood.annotation.RavenwoodKeepWholeClass
 public class MeasuredParagraph {
     private static final char OBJECT_REPLACEMENT_CHARACTER = '\uFFFC';
 
diff --git a/core/java/android/text/NoCopySpan.java b/core/java/android/text/NoCopySpan.java
index e754d76..4cd3d04 100644
--- a/core/java/android/text/NoCopySpan.java
+++ b/core/java/android/text/NoCopySpan.java
@@ -21,6 +21,7 @@
  * into a new Spanned when performing a slice or copy operation on the original
  * Spanned it was placed in.
  */
+@android.ravenwood.annotation.RavenwoodKeepWholeClass
 public interface NoCopySpan {
     /**
      * Convenience equivalent for when you would just want a new Object() for
diff --git a/core/java/android/text/PackedIntVector.java b/core/java/android/text/PackedIntVector.java
index 3e5bf56..11dd0c3 100644
--- a/core/java/android/text/PackedIntVector.java
+++ b/core/java/android/text/PackedIntVector.java
@@ -29,6 +29,7 @@
  * @hide
  */
 @VisibleForTesting(visibility = VisibleForTesting.Visibility.PACKAGE)
+@android.ravenwood.annotation.RavenwoodKeepWholeClass
 public class PackedIntVector {
     private final int mColumns;
     private int mRows;
diff --git a/core/java/android/text/PackedObjectVector.java b/core/java/android/text/PackedObjectVector.java
index b777e16..beb5ea4 100644
--- a/core/java/android/text/PackedObjectVector.java
+++ b/core/java/android/text/PackedObjectVector.java
@@ -21,6 +21,7 @@
 
 import libcore.util.EmptyArray;
 
+@android.ravenwood.annotation.RavenwoodKeepWholeClass
 class PackedObjectVector<E>
 {
     private int mColumns;
diff --git a/core/java/android/text/ParcelableSpan.java b/core/java/android/text/ParcelableSpan.java
index d7c1a4b..a9a4893 100644
--- a/core/java/android/text/ParcelableSpan.java
+++ b/core/java/android/text/ParcelableSpan.java
@@ -24,6 +24,7 @@
  * This can only be used by code in the framework; it is not intended for
  * applications to implement their own Parcelable spans.
  */
+@android.ravenwood.annotation.RavenwoodKeepWholeClass
 public interface ParcelableSpan extends Parcelable {
     /**
      * Return a special type identifier for this span class.
diff --git a/core/java/android/text/PrecomputedText.java b/core/java/android/text/PrecomputedText.java
index 5f6a9bd..71cacd9 100644
--- a/core/java/android/text/PrecomputedText.java
+++ b/core/java/android/text/PrecomputedText.java
@@ -75,6 +75,7 @@
  * Note that any {@link android.text.NoCopySpan} attached to the original text won't be passed to
  * PrecomputedText.
  */
+@android.ravenwood.annotation.RavenwoodKeepWholeClass
 public class PrecomputedText implements Spannable {
     private static final char LINE_FEED = '\n';
 
diff --git a/core/java/android/text/SegmentFinder.java b/core/java/android/text/SegmentFinder.java
index 047d07a..b7ab0e6 100644
--- a/core/java/android/text/SegmentFinder.java
+++ b/core/java/android/text/SegmentFinder.java
@@ -39,6 +39,7 @@
  *
  * @see Layout#getRangeForRect(RectF, SegmentFinder, Layout.TextInclusionStrategy)
  */
+@android.ravenwood.annotation.RavenwoodKeepWholeClass
 public abstract class SegmentFinder {
     /**
      * Return value of previousStartBoundary(int), previousEndBoundary(int), nextStartBoundary(int),
diff --git a/core/java/android/text/Selection.java b/core/java/android/text/Selection.java
index 711578c..674b473 100644
--- a/core/java/android/text/Selection.java
+++ b/core/java/android/text/Selection.java
@@ -27,6 +27,7 @@
  * Utility class for manipulating cursors and selections in CharSequences.
  * A cursor is a selection where the start and end are at the same offset.
  */
+@android.ravenwood.annotation.RavenwoodKeepWholeClass
 public class Selection {
     private Selection() { /* cannot be instantiated */ }
 
diff --git a/core/java/android/text/SpanColors.java b/core/java/android/text/SpanColors.java
index fcd242b..3b6a041 100644
--- a/core/java/android/text/SpanColors.java
+++ b/core/java/android/text/SpanColors.java
@@ -27,6 +27,7 @@
  *
  * @hide
  */
+@android.ravenwood.annotation.RavenwoodKeepWholeClass
 public class SpanColors {
     public static final @ColorInt int NO_COLOR_FOUND = Color.TRANSPARENT;
 
diff --git a/core/java/android/text/SpanSet.java b/core/java/android/text/SpanSet.java
index d464278..4ad8106 100644
--- a/core/java/android/text/SpanSet.java
+++ b/core/java/android/text/SpanSet.java
@@ -31,6 +31,7 @@
  * Note that empty spans are ignored by this class.
  * @hide
  */
+@android.ravenwood.annotation.RavenwoodKeepWholeClass
 public class SpanSet<E> {
     private final Class<? extends E> classType;
 
diff --git a/core/java/android/text/SpanWatcher.java b/core/java/android/text/SpanWatcher.java
index 01e82c8..31d6320 100644
--- a/core/java/android/text/SpanWatcher.java
+++ b/core/java/android/text/SpanWatcher.java
@@ -21,6 +21,7 @@
  * will be called to notify it that other markup objects have been
  * added, changed, or removed.
  */
+@android.ravenwood.annotation.RavenwoodKeepWholeClass
 public interface SpanWatcher extends NoCopySpan {
     /**
      * This method is called to notify you that the specified object
diff --git a/core/java/android/text/Spannable.java b/core/java/android/text/Spannable.java
index 8315b2a..fac5131 100644
--- a/core/java/android/text/Spannable.java
+++ b/core/java/android/text/Spannable.java
@@ -21,6 +21,7 @@
  * attached and detached.  Not all Spannable classes have mutable text;
  * see {@link Editable} for that.
  */
+@android.ravenwood.annotation.RavenwoodKeepWholeClass
 public interface Spannable
 extends Spanned
 {
diff --git a/core/java/android/text/SpannableString.java b/core/java/android/text/SpannableString.java
index afb5df8..ee04a86 100644
--- a/core/java/android/text/SpannableString.java
+++ b/core/java/android/text/SpannableString.java
@@ -21,6 +21,7 @@
  * markup objects can be attached and detached.
  * For mutable text, see {@link SpannableStringBuilder}.
  */
+@android.ravenwood.annotation.RavenwoodKeepWholeClass
 public class SpannableString
 extends SpannableStringInternal
 implements CharSequence, GetChars, Spannable
diff --git a/core/java/android/text/SpannableStringBuilder.java b/core/java/android/text/SpannableStringBuilder.java
index 0e61eff..f8d7283 100644
--- a/core/java/android/text/SpannableStringBuilder.java
+++ b/core/java/android/text/SpannableStringBuilder.java
@@ -35,6 +35,7 @@
 /**
  * This is the class for text whose content and markup can both be changed.
  */
+@android.ravenwood.annotation.RavenwoodKeepWholeClass
 public class SpannableStringBuilder implements CharSequence, GetChars, Spannable, Editable,
         Appendable, GraphicsOperations {
     private final static String TAG = "SpannableStringBuilder";
diff --git a/core/java/android/text/SpannableStringInternal.java b/core/java/android/text/SpannableStringInternal.java
index f2ab1bb..90d83d5 100644
--- a/core/java/android/text/SpannableStringInternal.java
+++ b/core/java/android/text/SpannableStringInternal.java
@@ -27,6 +27,7 @@
 
 import java.lang.reflect.Array;
 
+@android.ravenwood.annotation.RavenwoodKeepWholeClass
 /* package */ abstract class SpannableStringInternal
 {
     /* package */ SpannableStringInternal(CharSequence source,
diff --git a/core/java/android/text/Spanned.java b/core/java/android/text/Spanned.java
index a0d54c26c..6706ffd 100644
--- a/core/java/android/text/Spanned.java
+++ b/core/java/android/text/Spanned.java
@@ -22,6 +22,7 @@
  * see {@link Spannable} for mutable markup and {@link Editable} for
  * mutable text.
  */
+@android.ravenwood.annotation.RavenwoodKeepWholeClass
 public interface Spanned
 extends CharSequence
 {
diff --git a/core/java/android/text/SpannedString.java b/core/java/android/text/SpannedString.java
index acee3c5..a3f1ee2 100644
--- a/core/java/android/text/SpannedString.java
+++ b/core/java/android/text/SpannedString.java
@@ -22,6 +22,7 @@
  * For mutable markup, see {@link SpannableString}; for mutable text,
  * see {@link SpannableStringBuilder}.
  */
+@android.ravenwood.annotation.RavenwoodKeepWholeClass
 public final class SpannedString
 extends SpannableStringInternal
 implements CharSequence, GetChars, Spanned
diff --git a/core/java/android/text/StaticLayout.java b/core/java/android/text/StaticLayout.java
index a5d52957..8193cd2 100644
--- a/core/java/android/text/StaticLayout.java
+++ b/core/java/android/text/StaticLayout.java
@@ -55,6 +55,7 @@
  * float, float, android.graphics.Paint)
  * Canvas.drawText()} directly.</p>
  */
+@android.ravenwood.annotation.RavenwoodKeepWholeClass
 public class StaticLayout extends Layout {
     /*
      * The break iteration is done in native code. The protocol for using the native code is as
diff --git a/core/java/android/text/TextDirectionHeuristic.java b/core/java/android/text/TextDirectionHeuristic.java
index 8a4ba42..66cea85 100644
--- a/core/java/android/text/TextDirectionHeuristic.java
+++ b/core/java/android/text/TextDirectionHeuristic.java
@@ -19,6 +19,7 @@
 /**
  * Interface for objects that use a heuristic for guessing at the paragraph direction by examining text.
  */
+@android.ravenwood.annotation.RavenwoodKeepWholeClass
 public interface TextDirectionHeuristic {
     /**
      * Guess if a chars array is in the RTL direction or not.
diff --git a/core/java/android/text/TextDirectionHeuristics.java b/core/java/android/text/TextDirectionHeuristics.java
index 85260f4..3af8fb7 100644
--- a/core/java/android/text/TextDirectionHeuristics.java
+++ b/core/java/android/text/TextDirectionHeuristics.java
@@ -32,6 +32,7 @@
  * class.
  *
  */
+@android.ravenwood.annotation.RavenwoodKeepWholeClass
 public class TextDirectionHeuristics {
 
     /**
diff --git a/core/java/android/text/TextLine.java b/core/java/android/text/TextLine.java
index 3015791..091eb60 100644
--- a/core/java/android/text/TextLine.java
+++ b/core/java/android/text/TextLine.java
@@ -53,6 +53,7 @@
  * @hide
  */
 @VisibleForTesting(visibility = VisibleForTesting.Visibility.PACKAGE)
+@android.ravenwood.annotation.RavenwoodKeepWholeClass
 public class TextLine {
     private static final boolean DEBUG = false;
 
diff --git a/core/java/android/text/TextPaint.java b/core/java/android/text/TextPaint.java
index 73825b1..ff063f2 100644
--- a/core/java/android/text/TextPaint.java
+++ b/core/java/android/text/TextPaint.java
@@ -25,6 +25,7 @@
  * TextPaint is an extension of Paint that leaves room for some extra
  * data used during text measuring and drawing.
  */
+@android.ravenwood.annotation.RavenwoodKeepWholeClass
 public class TextPaint extends Paint {
 
     // Special value 0 means no background paint
diff --git a/core/java/android/text/TextShaper.java b/core/java/android/text/TextShaper.java
index 6da0b63..6d17401 100644
--- a/core/java/android/text/TextShaper.java
+++ b/core/java/android/text/TextShaper.java
@@ -169,6 +169,7 @@
  * @see TextShaper#shapeText(CharSequence, int, int, TextDirectionHeuristic, TextPaint,
  * GlyphsConsumer)
  */
+@android.ravenwood.annotation.RavenwoodKeepWholeClass
 public class TextShaper {
     private TextShaper() {}
 
diff --git a/core/java/android/text/TextUtils.java b/core/java/android/text/TextUtils.java
index 6dc82c4..042966b 100644
--- a/core/java/android/text/TextUtils.java
+++ b/core/java/android/text/TextUtils.java
@@ -34,6 +34,7 @@
 import android.icu.util.ULocale;
 import android.os.Parcel;
 import android.os.Parcelable;
+import android.ravenwood.annotation.RavenwoodKeepWholeClass;
 import android.sysprop.DisplayProperties;
 import android.text.style.AbsoluteSizeSpan;
 import android.text.style.AccessibilityClickableSpan;
@@ -85,8 +86,7 @@
 import java.util.Locale;
 import java.util.regex.Pattern;
 
-@android.ravenwood.annotation.RavenwoodKeepStaticInitializer
-@android.ravenwood.annotation.RavenwoodKeepPartialClass
+@RavenwoodKeepWholeClass
 public class TextUtils {
     private static final String TAG = "TextUtils";
 
@@ -147,7 +147,6 @@
 
     private TextUtils() { /* cannot be instantiated */ }
 
-    @android.ravenwood.annotation.RavenwoodKeep
     public static void getChars(CharSequence s, int start, int end,
                                 char[] dest, int destoff) {
         Class<? extends CharSequence> c = s.getClass();
@@ -166,12 +165,10 @@
         }
     }
 
-    @android.ravenwood.annotation.RavenwoodKeep
     public static int indexOf(CharSequence s, char ch) {
         return indexOf(s, ch, 0);
     }
 
-    @android.ravenwood.annotation.RavenwoodKeep
     public static int indexOf(CharSequence s, char ch, int start) {
         Class<? extends CharSequence> c = s.getClass();
 
@@ -181,7 +178,6 @@
         return indexOf(s, ch, start, s.length());
     }
 
-    @android.ravenwood.annotation.RavenwoodKeep
     public static int indexOf(CharSequence s, char ch, int start, int end) {
         Class<? extends CharSequence> c = s.getClass();
 
@@ -219,12 +215,10 @@
         return -1;
     }
 
-    @android.ravenwood.annotation.RavenwoodKeep
     public static int lastIndexOf(CharSequence s, char ch) {
         return lastIndexOf(s, ch, s.length() - 1);
     }
 
-    @android.ravenwood.annotation.RavenwoodKeep
     public static int lastIndexOf(CharSequence s, char ch, int last) {
         Class<? extends CharSequence> c = s.getClass();
 
@@ -234,7 +228,6 @@
         return lastIndexOf(s, ch, 0, last);
     }
 
-    @android.ravenwood.annotation.RavenwoodKeep
     public static int lastIndexOf(CharSequence s, char ch,
                                   int start, int last) {
         if (last < 0)
@@ -280,17 +273,14 @@
         return -1;
     }
 
-    @android.ravenwood.annotation.RavenwoodKeep
     public static int indexOf(CharSequence s, CharSequence needle) {
         return indexOf(s, needle, 0, s.length());
     }
 
-    @android.ravenwood.annotation.RavenwoodKeep
     public static int indexOf(CharSequence s, CharSequence needle, int start) {
         return indexOf(s, needle, start, s.length());
     }
 
-    @android.ravenwood.annotation.RavenwoodKeep
     public static int indexOf(CharSequence s, CharSequence needle,
                               int start, int end) {
         int nlen = needle.length();
@@ -318,7 +308,6 @@
         return -1;
     }
 
-    @android.ravenwood.annotation.RavenwoodKeep
     public static boolean regionMatches(CharSequence one, int toffset,
                                         CharSequence two, int ooffset,
                                         int len) {
@@ -351,7 +340,6 @@
      * in that it does not preserve any style runs in the source sequence,
      * allowing a more efficient implementation.
      */
-    @android.ravenwood.annotation.RavenwoodKeep
     public static String substring(CharSequence source, int start, int end) {
         if (source instanceof String)
             return ((String) source).substring(start, end);
@@ -424,7 +412,6 @@
      *     calling object.toString(). If tokens is null, a NullPointerException will be thrown. If
      *     tokens is an empty array, an empty string will be returned.
      */
-    @android.ravenwood.annotation.RavenwoodKeep
     public static String join(@NonNull CharSequence delimiter, @NonNull Object[] tokens) {
         final int length = tokens.length;
         if (length == 0) {
@@ -448,7 +435,6 @@
      *     calling object.toString(). If tokens is null, a NullPointerException will be thrown. If
      *     tokens is empty, an empty string will be returned.
      */
-    @android.ravenwood.annotation.RavenwoodKeep
     public static String join(@NonNull CharSequence delimiter, @NonNull Iterable tokens) {
         final Iterator<?> it = tokens.iterator();
         if (!it.hasNext()) {
@@ -481,7 +467,6 @@
      *
      * @throws NullPointerException if expression or text is null
      */
-    @android.ravenwood.annotation.RavenwoodKeep
     public static String[] split(String text, String expression) {
         if (text.length() == 0) {
             return EmptyArray.STRING;
@@ -507,7 +492,6 @@
      *
      * @throws NullPointerException if expression or text is null
      */
-    @android.ravenwood.annotation.RavenwoodKeep
     public static String[] split(String text, Pattern pattern) {
         if (text.length() == 0) {
             return EmptyArray.STRING;
@@ -545,7 +529,6 @@
      * be returned for the empty string after that delimeter. That is, splitting <tt>"a,b,"</tt> on
      * comma will return <tt>"a", "b"</tt>, not <tt>"a", "b", ""</tt>.
      */
-    @android.ravenwood.annotation.RavenwoodKeepWholeClass
     public static class SimpleStringSplitter implements StringSplitter, Iterator<String> {
         private String mString;
         private char mDelimiter;
@@ -609,31 +592,26 @@
      * @param str the string to be examined
      * @return true if str is null or zero length
      */
-    @android.ravenwood.annotation.RavenwoodKeep
     public static boolean isEmpty(@Nullable CharSequence str) {
         return str == null || str.length() == 0;
     }
 
     /** {@hide} */
-    @android.ravenwood.annotation.RavenwoodKeep
     public static String nullIfEmpty(@Nullable String str) {
         return isEmpty(str) ? null : str;
     }
 
     /** {@hide} */
-    @android.ravenwood.annotation.RavenwoodKeep
     public static String emptyIfNull(@Nullable String str) {
         return str == null ? "" : str;
     }
 
     /** {@hide} */
-    @android.ravenwood.annotation.RavenwoodKeep
     public static String firstNotEmpty(@Nullable String a, @NonNull String b) {
         return !isEmpty(a) ? a : Preconditions.checkStringNotEmpty(b);
     }
 
     /** {@hide} */
-    @android.ravenwood.annotation.RavenwoodKeep
     public static int length(@Nullable String s) {
         return s != null ? s.length() : 0;
     }
@@ -642,7 +620,6 @@
      * @return interned string if it's null.
      * @hide
      */
-    @android.ravenwood.annotation.RavenwoodKeep
     public static String safeIntern(String s) {
         return (s != null) ? s.intern() : null;
     }
@@ -652,7 +629,6 @@
      * spaces and ASCII control characters were trimmed from the start and end,
      * as by {@link String#trim}.
      */
-    @android.ravenwood.annotation.RavenwoodKeep
     public static int getTrimmedLength(CharSequence s) {
         int len = s.length();
 
@@ -677,7 +653,6 @@
      * @param b second CharSequence to check
      * @return true if a and b are equal
      */
-    @android.ravenwood.annotation.RavenwoodKeep
     public static boolean equals(@Nullable CharSequence a, @Nullable CharSequence b) {
         if (a == b) return true;
         int length;
@@ -1713,7 +1688,6 @@
         return true;
     }
 
-    @android.ravenwood.annotation.RavenwoodKeep
     /* package */ static char[] obtain(int len) {
         char[] buf;
 
@@ -1728,7 +1702,6 @@
         return buf;
     }
 
-    @android.ravenwood.annotation.RavenwoodKeep
     /* package */ static void recycle(char[] temp) {
         if (temp.length > 1000)
             return;
@@ -1743,7 +1716,6 @@
      * @param s the string to be encoded
      * @return the encoded string
      */
-    @android.ravenwood.annotation.RavenwoodKeep
     public static String htmlEncode(String s) {
         StringBuilder sb = new StringBuilder();
         char c;
@@ -1830,7 +1802,6 @@
     /**
      * Returns whether the given CharSequence contains any printable characters.
      */
-    @android.ravenwood.annotation.RavenwoodKeep
     public static boolean isGraphic(CharSequence str) {
         final int len = str.length();
         for (int cp, i=0; i<len; i+=Character.charCount(cp)) {
@@ -1857,7 +1828,6 @@
      * @deprecated Use {@link #isGraphic(CharSequence)} instead.
      */
     @Deprecated
-    @android.ravenwood.annotation.RavenwoodKeep
     public static boolean isGraphic(char c) {
         int gc = Character.getType(c);
         return     gc != Character.CONTROL
@@ -1872,7 +1842,6 @@
     /**
      * Returns whether the given CharSequence contains only digits.
      */
-    @android.ravenwood.annotation.RavenwoodKeep
     public static boolean isDigitsOnly(CharSequence str) {
         final int len = str.length();
         for (int cp, i = 0; i < len; i += Character.charCount(cp)) {
@@ -1887,7 +1856,6 @@
     /**
      * @hide
      */
-    @android.ravenwood.annotation.RavenwoodKeep
     public static boolean isPrintableAscii(final char c) {
         final int asciiFirst = 0x20;
         final int asciiLast = 0x7E;  // included
@@ -1898,7 +1866,6 @@
      * @hide
      */
     @UnsupportedAppUsage
-    @android.ravenwood.annotation.RavenwoodKeep
     public static boolean isPrintableAsciiOnly(final CharSequence str) {
         final int len = str.length();
         for (int i = 0; i < len; i++) {
@@ -1950,7 +1917,6 @@
      * {@link #CAP_MODE_CHARACTERS}, {@link #CAP_MODE_WORDS}, and
      * {@link #CAP_MODE_SENTENCES}.
      */
-    @android.ravenwood.annotation.RavenwoodKeep
     public static int getCapsMode(CharSequence cs, int off, int reqModes) {
         if (off < 0) {
             return 0;
@@ -2162,7 +2128,6 @@
      *
      * Be careful: this code will need to be updated when vertical scripts will be supported
      */
-    @android.ravenwood.annotation.RavenwoodKeep
     public static int getLayoutDirectionFromLocale(Locale locale) {
         return ((locale != null && !locale.equals(Locale.ROOT)
                         && ULocale.forLocale(locale).isRightToLeft())
@@ -2197,7 +2162,6 @@
      *             match the supported grammar described above.
      * @hide
      */
-    @android.ravenwood.annotation.RavenwoodKeep
     public static @NonNull String formatSimple(@NonNull String format, Object... args) {
         final StringBuilder sb = new StringBuilder(format);
         int j = 0;
@@ -2387,7 +2351,6 @@
     }
 
     /** @hide */
-    @android.ravenwood.annotation.RavenwoodKeep
     public static boolean isNewline(int codePoint) {
         int type = Character.getType(codePoint);
         return type == Character.PARAGRAPH_SEPARATOR || type == Character.LINE_SEPARATOR
@@ -2395,19 +2358,16 @@
     }
 
     /** @hide */
-    @android.ravenwood.annotation.RavenwoodKeep
     public static boolean isWhitespace(int codePoint) {
         return Character.isWhitespace(codePoint) || codePoint == NBSP_CODE_POINT;
     }
 
     /** @hide */
-    @android.ravenwood.annotation.RavenwoodKeep
     public static boolean isWhitespaceExceptNewline(int codePoint) {
         return isWhitespace(codePoint) && !isNewline(codePoint);
     }
 
     /** @hide */
-    @android.ravenwood.annotation.RavenwoodKeep
     public static boolean isPunctuation(int codePoint) {
         int type = Character.getType(codePoint);
         return type == Character.CONNECTOR_PUNCTUATION
diff --git a/core/java/android/text/TextWatcher.java b/core/java/android/text/TextWatcher.java
index a0aef69..5963ca7 100644
--- a/core/java/android/text/TextWatcher.java
+++ b/core/java/android/text/TextWatcher.java
@@ -20,6 +20,7 @@
  * When an object of this type is attached to an Editable, its methods will
  * be called when the text is changed.
  */
+@android.ravenwood.annotation.RavenwoodKeepWholeClass
 public interface TextWatcher extends NoCopySpan {
     /**
      * This method is called to notify you that, within <code>s</code>,
diff --git a/core/java/android/text/WordSegmentFinder.java b/core/java/android/text/WordSegmentFinder.java
index b0a70ea..b8702d7 100644
--- a/core/java/android/text/WordSegmentFinder.java
+++ b/core/java/android/text/WordSegmentFinder.java
@@ -33,6 +33,7 @@
  * @see <a href="https://unicode.org/reports/tr29/#Word_Boundaries">Unicode Text Segmentation - Word
  *     Boundaries</a>
  */
+@android.ravenwood.annotation.RavenwoodKeepWholeClass
 public class WordSegmentFinder extends SegmentFinder {
     private final CharSequence mText;
     private final WordIterator mWordIterator;
diff --git a/core/java/android/text/format/DateFormat.java b/core/java/android/text/format/DateFormat.java
index e6dad27..ef60d30 100644
--- a/core/java/android/text/format/DateFormat.java
+++ b/core/java/android/text/format/DateFormat.java
@@ -63,6 +63,7 @@
  * Note that the non-{@code format} methods in this class are implemented by
  * {@code SimpleDateFormat}.
  */
+@android.ravenwood.annotation.RavenwoodKeepWholeClass
 public class DateFormat {
     /**
      * @deprecated Use a literal {@code '} instead.
diff --git a/core/java/android/text/format/DateIntervalFormat.java b/core/java/android/text/format/DateIntervalFormat.java
index 8dea322..5ec9561 100644
--- a/core/java/android/text/format/DateIntervalFormat.java
+++ b/core/java/android/text/format/DateIntervalFormat.java
@@ -37,6 +37,7 @@
  * @hide
  */
 @VisibleForTesting(visibility = PACKAGE)
+@android.ravenwood.annotation.RavenwoodKeepWholeClass
 public final class DateIntervalFormat {
 
     private static final LruCache<String, android.icu.text.DateIntervalFormat> CACHED_FORMATTERS =
diff --git a/core/java/android/text/format/DateTimeFormat.java b/core/java/android/text/format/DateTimeFormat.java
index 064d717..c8dd61d 100644
--- a/core/java/android/text/format/DateTimeFormat.java
+++ b/core/java/android/text/format/DateTimeFormat.java
@@ -29,6 +29,7 @@
  *
  * @hide
  */
+@android.ravenwood.annotation.RavenwoodKeepWholeClass
 class DateTimeFormat {
     private static final FormatterCache CACHED_FORMATTERS = new FormatterCache();
 
diff --git a/core/java/android/text/format/DateUtils.java b/core/java/android/text/format/DateUtils.java
index 518a549..12ad764 100644
--- a/core/java/android/text/format/DateUtils.java
+++ b/core/java/android/text/format/DateUtils.java
@@ -44,6 +44,7 @@
  * This class contains various date-related utilities for creating text for things like
  * elapsed time and date ranges, strings for days of the week and months, and AM/PM text etc.
  */
+@android.ravenwood.annotation.RavenwoodKeepWholeClass
 public class DateUtils
 {
     private static final Object sLock = new Object();
diff --git a/core/java/android/text/format/DateUtilsBridge.java b/core/java/android/text/format/DateUtilsBridge.java
index 92ec9cf..752a8c0 100644
--- a/core/java/android/text/format/DateUtilsBridge.java
+++ b/core/java/android/text/format/DateUtilsBridge.java
@@ -46,6 +46,7 @@
  * @hide
  */
 @VisibleForTesting(visibility = PACKAGE)
+@android.ravenwood.annotation.RavenwoodKeepWholeClass
 public final class DateUtilsBridge {
 
     /**
diff --git a/core/java/android/text/format/Formatter.java b/core/java/android/text/format/Formatter.java
index 7653bdb..e7783dc 100644
--- a/core/java/android/text/format/Formatter.java
+++ b/core/java/android/text/format/Formatter.java
@@ -41,6 +41,7 @@
  * Utility class to aid in formatting common values that are not covered
  * by the {@link java.util.Formatter} class in {@link java.util}
  */
+@android.ravenwood.annotation.RavenwoodKeepWholeClass
 public final class Formatter {
 
     /** {@hide} */
diff --git a/core/java/android/text/format/RelativeDateTimeFormatter.java b/core/java/android/text/format/RelativeDateTimeFormatter.java
index 9096469..6b940f8 100644
--- a/core/java/android/text/format/RelativeDateTimeFormatter.java
+++ b/core/java/android/text/format/RelativeDateTimeFormatter.java
@@ -42,6 +42,7 @@
  * @hide
  */
 @VisibleForTesting(visibility = PACKAGE)
+@android.ravenwood.annotation.RavenwoodKeepWholeClass
 public final class RelativeDateTimeFormatter {
 
     public static final long SECOND_IN_MILLIS = 1000;
diff --git a/core/java/android/text/format/Time.java b/core/java/android/text/format/Time.java
index bac7c6c..1beb573 100644
--- a/core/java/android/text/format/Time.java
+++ b/core/java/android/text/format/Time.java
@@ -53,6 +53,7 @@
  * @deprecated Use {@link java.util.GregorianCalendar} instead.
  */
 @Deprecated
+@android.ravenwood.annotation.RavenwoodKeepWholeClass
 public class Time {
     private static final String Y_M_D_T_H_M_S_000 = "%Y-%m-%dT%H:%M:%S.000";
     private static final String Y_M_D_T_H_M_S_000_Z = "%Y-%m-%dT%H:%M:%S.000Z";
diff --git a/core/java/android/text/format/TimeFormatter.java b/core/java/android/text/format/TimeFormatter.java
index e42ad63..dd703d8 100644
--- a/core/java/android/text/format/TimeFormatter.java
+++ b/core/java/android/text/format/TimeFormatter.java
@@ -40,6 +40,7 @@
  *
  * <p>This class is not thread safe.
  */
+@android.ravenwood.annotation.RavenwoodKeepWholeClass
 class TimeFormatter {
     // An arbitrary value outside the range representable by a char.
     private static final int FORCE_LOWER_CASE = -1;
diff --git a/core/java/android/text/format/TimeMigrationUtils.java b/core/java/android/text/format/TimeMigrationUtils.java
index 17bac8d..b2f5024 100644
--- a/core/java/android/text/format/TimeMigrationUtils.java
+++ b/core/java/android/text/format/TimeMigrationUtils.java
@@ -22,6 +22,7 @@
  *
  * @hide
  */
+@android.ravenwood.annotation.RavenwoodKeepWholeClass
 public class TimeMigrationUtils {
 
     private TimeMigrationUtils() {}
diff --git a/core/java/android/text/method/AllCapsTransformationMethod.java b/core/java/android/text/method/AllCapsTransformationMethod.java
index 305b056..70dcc52 100644
--- a/core/java/android/text/method/AllCapsTransformationMethod.java
+++ b/core/java/android/text/method/AllCapsTransformationMethod.java
@@ -33,6 +33,7 @@
  *
  * @hide
  */
+@android.ravenwood.annotation.RavenwoodKeepWholeClass
 public class AllCapsTransformationMethod implements TransformationMethod2 {
     private static final String TAG = "AllCapsTransformationMethod";
 
diff --git a/core/java/android/text/method/ArrowKeyMovementMethod.java b/core/java/android/text/method/ArrowKeyMovementMethod.java
index 37474e5..6099220 100644
--- a/core/java/android/text/method/ArrowKeyMovementMethod.java
+++ b/core/java/android/text/method/ArrowKeyMovementMethod.java
@@ -30,6 +30,7 @@
  * A movement method that provides cursor movement and selection.
  * Supports displaying the context menu on DPad Center.
  */
+@android.ravenwood.annotation.RavenwoodKeepWholeClass
 public class ArrowKeyMovementMethod extends BaseMovementMethod implements MovementMethod {
     private static boolean isSelecting(Spannable buffer) {
         return ((MetaKeyKeyListener.getMetaState(buffer, MetaKeyKeyListener.META_SHIFT_ON) == 1) ||
diff --git a/core/java/android/text/method/BaseKeyListener.java b/core/java/android/text/method/BaseKeyListener.java
index e427908..5ebfd99 100644
--- a/core/java/android/text/method/BaseKeyListener.java
+++ b/core/java/android/text/method/BaseKeyListener.java
@@ -47,6 +47,7 @@
  * with hardware keyboards.  Software input methods have no obligation to trigger
  * the methods in this class.
  */
+@android.ravenwood.annotation.RavenwoodKeepWholeClass
 public abstract class BaseKeyListener extends MetaKeyKeyListener
         implements KeyListener {
     /* package */ static final Object OLD_SEL_START = new NoCopySpan.Concrete();
diff --git a/core/java/android/text/method/BaseMovementMethod.java b/core/java/android/text/method/BaseMovementMethod.java
index 7a4b3a0..0c2e52e 100644
--- a/core/java/android/text/method/BaseMovementMethod.java
+++ b/core/java/android/text/method/BaseMovementMethod.java
@@ -27,6 +27,7 @@
 /**
  * Base classes for movement methods.
  */
+@android.ravenwood.annotation.RavenwoodKeepWholeClass
 public class BaseMovementMethod implements MovementMethod {
     @Override
     public boolean canSelectArbitrarily() {
diff --git a/core/java/android/text/method/CharacterPickerDialog.java b/core/java/android/text/method/CharacterPickerDialog.java
index 7d838e0..f084d03 100644
--- a/core/java/android/text/method/CharacterPickerDialog.java
+++ b/core/java/android/text/method/CharacterPickerDialog.java
@@ -38,6 +38,7 @@
 /**
  * Dialog for choosing accented characters related to a base character.
  */
+@android.ravenwood.annotation.RavenwoodKeepWholeClass
 public class CharacterPickerDialog extends Dialog
         implements OnItemClickListener, OnClickListener {
     private View mView;
diff --git a/core/java/android/text/method/DateKeyListener.java b/core/java/android/text/method/DateKeyListener.java
index 0accbf6..acf1822 100644
--- a/core/java/android/text/method/DateKeyListener.java
+++ b/core/java/android/text/method/DateKeyListener.java
@@ -35,6 +35,7 @@
  * with hardware keyboards.  Software input methods have no obligation to trigger
  * the methods in this class.
  */
+@android.ravenwood.annotation.RavenwoodKeepWholeClass
 public class DateKeyListener extends NumberKeyListener
 {
     public int getInputType() {
diff --git a/core/java/android/text/method/DateTimeKeyListener.java b/core/java/android/text/method/DateTimeKeyListener.java
index 1593db5..a46ae45 100644
--- a/core/java/android/text/method/DateTimeKeyListener.java
+++ b/core/java/android/text/method/DateTimeKeyListener.java
@@ -35,6 +35,7 @@
  * with hardware keyboards.  Software input methods have no obligation to trigger
  * the methods in this class.
  */
+@android.ravenwood.annotation.RavenwoodKeepWholeClass
 public class DateTimeKeyListener extends NumberKeyListener
 {
     public int getInputType() {
diff --git a/core/java/android/text/method/DialerKeyListener.java b/core/java/android/text/method/DialerKeyListener.java
index 17abed6..9eea51a 100644
--- a/core/java/android/text/method/DialerKeyListener.java
+++ b/core/java/android/text/method/DialerKeyListener.java
@@ -28,6 +28,7 @@
  * with hardware keyboards.  Software input methods have no obligation to trigger
  * the methods in this class.
  */
+@android.ravenwood.annotation.RavenwoodKeepWholeClass
 public class DialerKeyListener extends NumberKeyListener
 {
     @Override
diff --git a/core/java/android/text/method/DigitsKeyListener.java b/core/java/android/text/method/DigitsKeyListener.java
index d9f2dcf..c97d4af 100644
--- a/core/java/android/text/method/DigitsKeyListener.java
+++ b/core/java/android/text/method/DigitsKeyListener.java
@@ -40,6 +40,7 @@
  * with hardware keyboards.  Software input methods have no obligation to trigger
  * the methods in this class.
  */
+@android.ravenwood.annotation.RavenwoodKeepWholeClass
 public class DigitsKeyListener extends NumberKeyListener
 {
     private char[] mAccepted;
diff --git a/core/java/android/text/method/HideReturnsTransformationMethod.java b/core/java/android/text/method/HideReturnsTransformationMethod.java
index 40ce871..8b93b35 100644
--- a/core/java/android/text/method/HideReturnsTransformationMethod.java
+++ b/core/java/android/text/method/HideReturnsTransformationMethod.java
@@ -24,6 +24,7 @@
  * to be hidden by displaying them as zero-width non-breaking space
  * characters (\uFEFF).
  */
+@android.ravenwood.annotation.RavenwoodKeepWholeClass
 public class HideReturnsTransformationMethod
 extends ReplacementTransformationMethod {
     private static char[] ORIGINAL = new char[] { '\r' };
diff --git a/core/java/android/text/method/InsertModeTransformationMethod.java b/core/java/android/text/method/InsertModeTransformationMethod.java
index 6c6576f..ace2d25 100644
--- a/core/java/android/text/method/InsertModeTransformationMethod.java
+++ b/core/java/android/text/method/InsertModeTransformationMethod.java
@@ -58,6 +58,7 @@
  *   the new transformed text: "hello abc\n\n world", and the highlight range will be [5, 11).
  * @hide
  */
+@android.ravenwood.annotation.RavenwoodKeepWholeClass
 public class InsertModeTransformationMethod implements TransformationMethod, TextWatcher {
     /** The start offset of the highlight range in the original text, inclusive. */
     private int mStart;
diff --git a/core/java/android/text/method/KeyListener.java b/core/java/android/text/method/KeyListener.java
index ce7054c..447c4d9 100644
--- a/core/java/android/text/method/KeyListener.java
+++ b/core/java/android/text/method/KeyListener.java
@@ -34,6 +34,7 @@
  * targetting Jelly Bean or later, and will only deliver it for some
  * key presses to applications targetting Ice Cream Sandwich or earlier.
  */
+@android.ravenwood.annotation.RavenwoodKeepWholeClass
 public interface KeyListener {
     /**
      * Return the type of text that this key listener is manipulating,
diff --git a/core/java/android/text/method/LinkMovementMethod.java b/core/java/android/text/method/LinkMovementMethod.java
index 9f4a0ae..484bc1a 100644
--- a/core/java/android/text/method/LinkMovementMethod.java
+++ b/core/java/android/text/method/LinkMovementMethod.java
@@ -33,6 +33,7 @@
  * A movement method that traverses links in the text buffer and scrolls if necessary.
  * Supports clicking on links with DPad Center or Enter.
  */
+@android.ravenwood.annotation.RavenwoodKeepWholeClass
 public class LinkMovementMethod extends ScrollingMovementMethod {
     private static final int CLICK = 1;
     private static final int UP = 2;
diff --git a/core/java/android/text/method/MetaKeyKeyListener.java b/core/java/android/text/method/MetaKeyKeyListener.java
index d1d7c96..7c9c2f1 100644
--- a/core/java/android/text/method/MetaKeyKeyListener.java
+++ b/core/java/android/text/method/MetaKeyKeyListener.java
@@ -71,6 +71,7 @@
  * }
  * </code>
  */
+@android.ravenwood.annotation.RavenwoodKeepWholeClass
 public abstract class MetaKeyKeyListener {
     /**
      * Flag that indicates that the SHIFT key is on.
diff --git a/core/java/android/text/method/MovementMethod.java b/core/java/android/text/method/MovementMethod.java
index f6fe575..5ea439d 100644
--- a/core/java/android/text/method/MovementMethod.java
+++ b/core/java/android/text/method/MovementMethod.java
@@ -32,6 +32,7 @@
  * directly by applications.
  * </p>
  */
+@android.ravenwood.annotation.RavenwoodKeepWholeClass
 public interface MovementMethod {
     public void initialize(TextView widget, Spannable text);
     public boolean onKeyDown(TextView widget, Spannable text, int keyCode, KeyEvent event);
diff --git a/core/java/android/text/method/MultiTapKeyListener.java b/core/java/android/text/method/MultiTapKeyListener.java
index 5770482..022853ab 100644
--- a/core/java/android/text/method/MultiTapKeyListener.java
+++ b/core/java/android/text/method/MultiTapKeyListener.java
@@ -36,6 +36,7 @@
  * with hardware keyboards.  Software input methods have no obligation to trigger
  * the methods in this class.
  */
+@android.ravenwood.annotation.RavenwoodKeepWholeClass
 public class MultiTapKeyListener extends BaseKeyListener
         implements SpanWatcher {
     private static MultiTapKeyListener[] sInstance =
diff --git a/core/java/android/text/method/NumberKeyListener.java b/core/java/android/text/method/NumberKeyListener.java
index 2b038dd..e32ccd4 100644
--- a/core/java/android/text/method/NumberKeyListener.java
+++ b/core/java/android/text/method/NumberKeyListener.java
@@ -39,6 +39,7 @@
  * with hardware keyboards.  Software input methods have no obligation to trigger
  * the methods in this class.
  */
+@android.ravenwood.annotation.RavenwoodKeepWholeClass
 public abstract class NumberKeyListener extends BaseKeyListener
     implements InputFilter
 {
diff --git a/core/java/android/text/method/OffsetMapping.java b/core/java/android/text/method/OffsetMapping.java
index fcf3de6..99613d3 100644
--- a/core/java/android/text/method/OffsetMapping.java
+++ b/core/java/android/text/method/OffsetMapping.java
@@ -27,6 +27,7 @@
  * {@link TransformationMethod} that alters the text length.
  * @hide
  */
+@android.ravenwood.annotation.RavenwoodKeepWholeClass
 public interface OffsetMapping {
     /**
      * The mapping strategy for a character offset.
diff --git a/core/java/android/text/method/PasswordTransformationMethod.java b/core/java/android/text/method/PasswordTransformationMethod.java
index 53553be..4a61d9a 100644
--- a/core/java/android/text/method/PasswordTransformationMethod.java
+++ b/core/java/android/text/method/PasswordTransformationMethod.java
@@ -33,6 +33,7 @@
 
 import java.lang.ref.WeakReference;
 
+@android.ravenwood.annotation.RavenwoodKeepWholeClass
 public class PasswordTransformationMethod
 implements TransformationMethod, TextWatcher
 {
diff --git a/core/java/android/text/method/QwertyKeyListener.java b/core/java/android/text/method/QwertyKeyListener.java
index c43864d..27c58ea 100644
--- a/core/java/android/text/method/QwertyKeyListener.java
+++ b/core/java/android/text/method/QwertyKeyListener.java
@@ -37,6 +37,7 @@
  * with hardware keyboards.  Software input methods have no obligation to trigger
  * the methods in this class.
  */
+@android.ravenwood.annotation.RavenwoodKeepWholeClass
 public class QwertyKeyListener extends BaseKeyListener {
     private static QwertyKeyListener[] sInstance =
         new QwertyKeyListener[Capitalize.values().length * 2];
diff --git a/core/java/android/text/method/ReplacementTransformationMethod.java b/core/java/android/text/method/ReplacementTransformationMethod.java
index d6f879a..05899d7 100644
--- a/core/java/android/text/method/ReplacementTransformationMethod.java
+++ b/core/java/android/text/method/ReplacementTransformationMethod.java
@@ -30,6 +30,7 @@
  * array to be replaced by the corresponding characters in the
  * {@link #getReplacement} array.
  */
+@android.ravenwood.annotation.RavenwoodKeepWholeClass
 public abstract class ReplacementTransformationMethod
 implements TransformationMethod
 {
diff --git a/core/java/android/text/method/ScrollingMovementMethod.java b/core/java/android/text/method/ScrollingMovementMethod.java
index 4f422cb..2e0eda9 100644
--- a/core/java/android/text/method/ScrollingMovementMethod.java
+++ b/core/java/android/text/method/ScrollingMovementMethod.java
@@ -25,6 +25,7 @@
 /**
  * A movement method that interprets movement keys by scrolling the text buffer.
  */
+@android.ravenwood.annotation.RavenwoodKeepWholeClass
 public class ScrollingMovementMethod extends BaseMovementMethod implements MovementMethod {
     @Override
     protected boolean left(TextView widget, Spannable buffer) {
diff --git a/core/java/android/text/method/SingleLineTransformationMethod.java b/core/java/android/text/method/SingleLineTransformationMethod.java
index 818526a..d6eff86 100644
--- a/core/java/android/text/method/SingleLineTransformationMethod.java
+++ b/core/java/android/text/method/SingleLineTransformationMethod.java
@@ -21,6 +21,7 @@
  * displayed as spaces instead of causing line breaks, and causes
  * carriage return characters (\r) to have no appearance.
  */
+@android.ravenwood.annotation.RavenwoodKeepWholeClass
 public class SingleLineTransformationMethod
 extends ReplacementTransformationMethod {
     private static char[] ORIGINAL = new char[] { '\n', '\r' };
diff --git a/core/java/android/text/method/TextKeyListener.java b/core/java/android/text/method/TextKeyListener.java
index 2eb917b..1b0ae61 100644
--- a/core/java/android/text/method/TextKeyListener.java
+++ b/core/java/android/text/method/TextKeyListener.java
@@ -43,6 +43,7 @@
  * with hardware keyboards.  Software input methods have no obligation to trigger
  * the methods in this class.
  */
+@android.ravenwood.annotation.RavenwoodKeepWholeClass
 public class TextKeyListener extends BaseKeyListener implements SpanWatcher {
     private static TextKeyListener[] sInstance =
         new TextKeyListener[Capitalize.values().length * 2];
diff --git a/core/java/android/text/method/TimeKeyListener.java b/core/java/android/text/method/TimeKeyListener.java
index f11f400..337611c 100644
--- a/core/java/android/text/method/TimeKeyListener.java
+++ b/core/java/android/text/method/TimeKeyListener.java
@@ -35,6 +35,7 @@
  * with hardware keyboards.  Software input methods have no obligation to trigger
  * the methods in this class.
  */
+@android.ravenwood.annotation.RavenwoodKeepWholeClass
 public class TimeKeyListener extends NumberKeyListener
 {
     public int getInputType() {
diff --git a/core/java/android/text/method/Touch.java b/core/java/android/text/method/Touch.java
index 44811cb..85aadba 100644
--- a/core/java/android/text/method/Touch.java
+++ b/core/java/android/text/method/Touch.java
@@ -25,6 +25,7 @@
 import android.view.ViewConfiguration;
 import android.widget.TextView;
 
+@android.ravenwood.annotation.RavenwoodKeepWholeClass
 public class Touch {
     private Touch() { }
 
diff --git a/core/java/android/text/method/TransformationMethod.java b/core/java/android/text/method/TransformationMethod.java
index 8f3b334..5246baa 100644
--- a/core/java/android/text/method/TransformationMethod.java
+++ b/core/java/android/text/method/TransformationMethod.java
@@ -24,6 +24,7 @@
  * characters of passwords with dots, or keeping the newline characters
  * from causing line breaks in single-line text fields.
  */
+@android.ravenwood.annotation.RavenwoodKeepWholeClass
 public interface TransformationMethod
 {
     /**
diff --git a/core/java/android/text/method/TransformationMethod2.java b/core/java/android/text/method/TransformationMethod2.java
index 8d5ec24..6e0feb419 100644
--- a/core/java/android/text/method/TransformationMethod2.java
+++ b/core/java/android/text/method/TransformationMethod2.java
@@ -23,6 +23,7 @@
  *
  * @hide
  */
+@android.ravenwood.annotation.RavenwoodKeepWholeClass
 public interface TransformationMethod2 extends TransformationMethod {
     /**
      * Relax the contract of TransformationMethod to allow length changes,
diff --git a/core/java/android/text/method/TranslationTransformationMethod.java b/core/java/android/text/method/TranslationTransformationMethod.java
index 43d186e..8f43d3d 100644
--- a/core/java/android/text/method/TranslationTransformationMethod.java
+++ b/core/java/android/text/method/TranslationTransformationMethod.java
@@ -33,6 +33,7 @@
  *
  * @hide
  */
+@android.ravenwood.annotation.RavenwoodKeepWholeClass
 public class TranslationTransformationMethod implements TransformationMethod2 {
 
     private static final String TAG = "TranslationTransformationMethod";
diff --git a/core/java/android/text/method/WordIterator.java b/core/java/android/text/method/WordIterator.java
index 2956f84..d57fa9b 100644
--- a/core/java/android/text/method/WordIterator.java
+++ b/core/java/android/text/method/WordIterator.java
@@ -37,6 +37,7 @@
  * Also provides methods to determine word boundaries.
  * {@hide}
  */
+@android.ravenwood.annotation.RavenwoodKeepWholeClass
 public class WordIterator implements Selection.PositionIterator {
     // Size of the window for the word iterator, should be greater than the longest word's length
     private static final int WINDOW_WIDTH = 50;
diff --git a/core/java/android/text/style/AbsoluteSizeSpan.java b/core/java/android/text/style/AbsoluteSizeSpan.java
index 6d4f05a..1bc5d71 100644
--- a/core/java/android/text/style/AbsoluteSizeSpan.java
+++ b/core/java/android/text/style/AbsoluteSizeSpan.java
@@ -32,6 +32,7 @@
  * <img src="{@docRoot}reference/android/images/text/style/absolutesizespan.png" />
  * <figcaption>Text with text size updated.</figcaption>
  */
+@android.ravenwood.annotation.RavenwoodKeepWholeClass
 public class AbsoluteSizeSpan extends MetricAffectingSpan implements ParcelableSpan {
 
     private final int mSize;
diff --git a/core/java/android/text/style/AccessibilityClickableSpan.java b/core/java/android/text/style/AccessibilityClickableSpan.java
index ee8d156..5741f2a 100644
--- a/core/java/android/text/style/AccessibilityClickableSpan.java
+++ b/core/java/android/text/style/AccessibilityClickableSpan.java
@@ -43,6 +43,7 @@
  *
  * @hide
  */
+@android.ravenwood.annotation.RavenwoodKeepWholeClass
 public class AccessibilityClickableSpan extends ClickableSpan
         implements ParcelableSpan {
     // The id of the span this one replaces
diff --git a/core/java/android/text/style/AccessibilityReplacementSpan.java b/core/java/android/text/style/AccessibilityReplacementSpan.java
index e4fc147..af3a324 100644
--- a/core/java/android/text/style/AccessibilityReplacementSpan.java
+++ b/core/java/android/text/style/AccessibilityReplacementSpan.java
@@ -31,6 +31,7 @@
  *
  * @hide
  */
+@android.ravenwood.annotation.RavenwoodKeepWholeClass
 public class AccessibilityReplacementSpan extends ReplacementSpan
         implements ParcelableSpan {
 
diff --git a/core/java/android/text/style/AccessibilityURLSpan.java b/core/java/android/text/style/AccessibilityURLSpan.java
index e280bdf..1fb76e7 100644
--- a/core/java/android/text/style/AccessibilityURLSpan.java
+++ b/core/java/android/text/style/AccessibilityURLSpan.java
@@ -27,6 +27,7 @@
  * @hide
  */
 @SuppressWarnings("ParcelableCreator")
+@android.ravenwood.annotation.RavenwoodKeepWholeClass
 public class AccessibilityURLSpan extends URLSpan implements Parcelable {
     final AccessibilityClickableSpan mAccessibilityClickableSpan;
 
diff --git a/core/java/android/text/style/AlignmentSpan.java b/core/java/android/text/style/AlignmentSpan.java
index 31db78a..53cbd63 100644
--- a/core/java/android/text/style/AlignmentSpan.java
+++ b/core/java/android/text/style/AlignmentSpan.java
@@ -25,6 +25,7 @@
 /**
  * Span that allows defining the alignment of text at the paragraph level.
  */
+@android.ravenwood.annotation.RavenwoodKeepWholeClass
 public interface AlignmentSpan extends ParagraphStyle {
 
     /**
diff --git a/core/java/android/text/style/BackgroundColorSpan.java b/core/java/android/text/style/BackgroundColorSpan.java
index 13647d9..bb04d0ff 100644
--- a/core/java/android/text/style/BackgroundColorSpan.java
+++ b/core/java/android/text/style/BackgroundColorSpan.java
@@ -34,6 +34,7 @@
  * <img src="{@docRoot}reference/android/images/text/style/backgroundcolorspan.png" />
  * <figcaption>Set a background color for the text.</figcaption>
  */
+@android.ravenwood.annotation.RavenwoodKeepWholeClass
 public class BackgroundColorSpan extends CharacterStyle
         implements UpdateAppearance, ParcelableSpan {
 
diff --git a/core/java/android/text/style/BulletSpan.java b/core/java/android/text/style/BulletSpan.java
index f70e6c5..24ae6e2 100644
--- a/core/java/android/text/style/BulletSpan.java
+++ b/core/java/android/text/style/BulletSpan.java
@@ -63,6 +63,7 @@
  * <img src="{@docRoot}reference/android/images/text/style/custombulletspan.png" />
  * <figcaption>Customized BulletSpan.</figcaption>
  */
+@android.ravenwood.annotation.RavenwoodKeepWholeClass
 public class BulletSpan implements LeadingMarginSpan, ParcelableSpan {
     // Bullet is slightly bigger to avoid aliasing artifacts on mdpi devices.
     private static final int STANDARD_BULLET_RADIUS = 4;
diff --git a/core/java/android/text/style/CharacterStyle.java b/core/java/android/text/style/CharacterStyle.java
index 5b95f1a..2ea05e6 100644
--- a/core/java/android/text/style/CharacterStyle.java
+++ b/core/java/android/text/style/CharacterStyle.java
@@ -23,6 +23,7 @@
  * class.  Most extend its subclass {@link MetricAffectingSpan}, but simple
  * ones may just implement {@link UpdateAppearance}.
  */
+@android.ravenwood.annotation.RavenwoodKeepWholeClass
 public abstract class CharacterStyle {
     public abstract void updateDrawState(TextPaint tp);
 
diff --git a/core/java/android/text/style/ClickableSpan.java b/core/java/android/text/style/ClickableSpan.java
index 238da55..9e35d75 100644
--- a/core/java/android/text/style/ClickableSpan.java
+++ b/core/java/android/text/style/ClickableSpan.java
@@ -36,6 +36,7 @@
  * <img src="{@docRoot}reference/android/images/text/style/clickablespan.png" />
  * <figcaption>Text with <code>ClickableSpan</code>.</figcaption>
  */
+@android.ravenwood.annotation.RavenwoodKeepWholeClass
 public abstract class ClickableSpan extends CharacterStyle implements UpdateAppearance {
     private static int sIdCounter = 0;
 
diff --git a/core/java/android/text/style/ForegroundColorSpan.java b/core/java/android/text/style/ForegroundColorSpan.java
index 5c97426..337c49f 100644
--- a/core/java/android/text/style/ForegroundColorSpan.java
+++ b/core/java/android/text/style/ForegroundColorSpan.java
@@ -34,6 +34,7 @@
  * <img src="{@docRoot}reference/android/images/text/style/foregroundcolorspan.png" />
  * <figcaption>Set a text color.</figcaption>
  */
+@android.ravenwood.annotation.RavenwoodKeepWholeClass
 public class ForegroundColorSpan extends CharacterStyle
         implements UpdateAppearance, ParcelableSpan {
 
diff --git a/core/java/android/text/style/IconMarginSpan.java b/core/java/android/text/style/IconMarginSpan.java
index a6c5139..cc946e9 100644
--- a/core/java/android/text/style/IconMarginSpan.java
+++ b/core/java/android/text/style/IconMarginSpan.java
@@ -44,6 +44,7 @@
  * @see DrawableMarginSpan for working with a {@link android.graphics.drawable.Drawable} instead of
  * a {@link Bitmap}.
  */
+@android.ravenwood.annotation.RavenwoodKeepWholeClass
 public class IconMarginSpan implements LeadingMarginSpan, LineHeightSpan {
 
     @NonNull
diff --git a/core/java/android/text/style/LeadingMarginSpan.java b/core/java/android/text/style/LeadingMarginSpan.java
index 5bd2d60..60c4578 100644
--- a/core/java/android/text/style/LeadingMarginSpan.java
+++ b/core/java/android/text/style/LeadingMarginSpan.java
@@ -32,6 +32,7 @@
  * LeadingMarginSpans should be attached from the first character to the last
  * character of a single paragraph.
  */
+@android.ravenwood.annotation.RavenwoodKeepWholeClass
 public interface LeadingMarginSpan
 extends ParagraphStyle
 {
diff --git a/core/java/android/text/style/LineBackgroundSpan.java b/core/java/android/text/style/LineBackgroundSpan.java
index 7cb9147..c2d38ce 100644
--- a/core/java/android/text/style/LineBackgroundSpan.java
+++ b/core/java/android/text/style/LineBackgroundSpan.java
@@ -28,6 +28,7 @@
 /**
  * Used to change the background of lines where the span is attached to.
  */
+@android.ravenwood.annotation.RavenwoodKeepWholeClass
 public interface LineBackgroundSpan extends ParagraphStyle
 {
     /**
diff --git a/core/java/android/text/style/LineBreakConfigSpan.java b/core/java/android/text/style/LineBreakConfigSpan.java
index eeb6383..1af1eed 100644
--- a/core/java/android/text/style/LineBreakConfigSpan.java
+++ b/core/java/android/text/style/LineBreakConfigSpan.java
@@ -31,6 +31,7 @@
  * LineBreakSpan for changing line break style of the specific region of the text.
  */
 @FlaggedApi(FLAG_NO_BREAK_NO_HYPHENATION_SPAN)
+@android.ravenwood.annotation.RavenwoodKeepWholeClass
 public final class LineBreakConfigSpan implements ParcelableSpan {
     private final LineBreakConfig mLineBreakConfig;
 
diff --git a/core/java/android/text/style/LineHeightSpan.java b/core/java/android/text/style/LineHeightSpan.java
index ae565d1..71e8932 100644
--- a/core/java/android/text/style/LineHeightSpan.java
+++ b/core/java/android/text/style/LineHeightSpan.java
@@ -30,6 +30,7 @@
 /**
  * The classes that affect the line height of paragraph should implement this interface.
  */
+@android.ravenwood.annotation.RavenwoodKeepWholeClass
 public interface LineHeightSpan extends ParagraphStyle, WrapTogetherSpan {
     /**
      * Classes that implement this should define how the height is being calculated.
diff --git a/core/java/android/text/style/LocaleSpan.java b/core/java/android/text/style/LocaleSpan.java
index 489ceea..be5525a 100644
--- a/core/java/android/text/style/LocaleSpan.java
+++ b/core/java/android/text/style/LocaleSpan.java
@@ -32,6 +32,7 @@
 /**
  * Changes the {@link Locale} of the text to which the span is attached.
  */
+@android.ravenwood.annotation.RavenwoodKeepWholeClass
 public class LocaleSpan extends MetricAffectingSpan implements ParcelableSpan {
     @NonNull
     private final LocaleList mLocales;
diff --git a/core/java/android/text/style/MaskFilterSpan.java b/core/java/android/text/style/MaskFilterSpan.java
index 587d1b4..44db012 100644
--- a/core/java/android/text/style/MaskFilterSpan.java
+++ b/core/java/android/text/style/MaskFilterSpan.java
@@ -30,6 +30,7 @@
  * <img src="{@docRoot}reference/android/images/text/style/maskfilterspan.png" />
  * <figcaption>Text blurred with the <code>MaskFilterSpan</code>.</figcaption>
  */
+@android.ravenwood.annotation.RavenwoodKeepWholeClass
 public class MaskFilterSpan extends CharacterStyle implements UpdateAppearance {
 
     private MaskFilter mFilter;
diff --git a/core/java/android/text/style/MetricAffectingSpan.java b/core/java/android/text/style/MetricAffectingSpan.java
index 61b7947..f30fdc1 100644
--- a/core/java/android/text/style/MetricAffectingSpan.java
+++ b/core/java/android/text/style/MetricAffectingSpan.java
@@ -23,6 +23,7 @@
  * The classes that affect character-level text formatting in a way that
  * changes the width or height of characters extend this class.
  */
+@android.ravenwood.annotation.RavenwoodKeepWholeClass
 public abstract class MetricAffectingSpan
         extends CharacterStyle
         implements UpdateLayout {
diff --git a/core/java/android/text/style/NoWritingToolsSpan.java b/core/java/android/text/style/NoWritingToolsSpan.java
index 90f85aa..c7dfcfa 100644
--- a/core/java/android/text/style/NoWritingToolsSpan.java
+++ b/core/java/android/text/style/NoWritingToolsSpan.java
@@ -32,6 +32,7 @@
  * tools should only rewrite the user input text, and not modify the quoted text.
  */
 @FlaggedApi(FLAG_WRITING_TOOLS)
+@android.ravenwood.annotation.RavenwoodKeepWholeClass
 public final class NoWritingToolsSpan implements ParcelableSpan {
 
     /**
diff --git a/core/java/android/text/style/ParagraphStyle.java b/core/java/android/text/style/ParagraphStyle.java
index 423156e..27c1e26 100644
--- a/core/java/android/text/style/ParagraphStyle.java
+++ b/core/java/android/text/style/ParagraphStyle.java
@@ -20,6 +20,7 @@
  * The classes that affect paragraph-level text formatting implement
  * this interface.
  */
+@android.ravenwood.annotation.RavenwoodKeepWholeClass
 public interface ParagraphStyle
 {
 
diff --git a/core/java/android/text/style/QuoteSpan.java b/core/java/android/text/style/QuoteSpan.java
index 393ede6..99c9574 100644
--- a/core/java/android/text/style/QuoteSpan.java
+++ b/core/java/android/text/style/QuoteSpan.java
@@ -57,6 +57,7 @@
  * <img src="{@docRoot}reference/android/images/text/style/customquotespan.png" />
  * <figcaption>Customized <code>QuoteSpan</code>.</figcaption>
  */
+@android.ravenwood.annotation.RavenwoodKeepWholeClass
 public class QuoteSpan implements LeadingMarginSpan, ParcelableSpan {
     /**
      * Default stripe width in pixels.
diff --git a/core/java/android/text/style/RasterizerSpan.java b/core/java/android/text/style/RasterizerSpan.java
index f0be50a..cf8599c 100644
--- a/core/java/android/text/style/RasterizerSpan.java
+++ b/core/java/android/text/style/RasterizerSpan.java
@@ -22,6 +22,7 @@
 /**
  *  @removed Rasterizer is not supported for hw-accerlerated and PDF rendering
  */
+@android.ravenwood.annotation.RavenwoodKeepWholeClass
 public class RasterizerSpan extends CharacterStyle implements UpdateAppearance {
 
     private Rasterizer mRasterizer;
diff --git a/core/java/android/text/style/RelativeSizeSpan.java b/core/java/android/text/style/RelativeSizeSpan.java
index 5c91b20..38d5d38 100644
--- a/core/java/android/text/style/RelativeSizeSpan.java
+++ b/core/java/android/text/style/RelativeSizeSpan.java
@@ -34,6 +34,7 @@
  * <img src="{@docRoot}reference/android/images/text/style/relativesizespan.png" />
  * <figcaption>Text increased by 50% with <code>RelativeSizeSpan</code>.</figcaption>
  */
+@android.ravenwood.annotation.RavenwoodKeepWholeClass
 public class RelativeSizeSpan extends MetricAffectingSpan implements ParcelableSpan {
 
     private final float mProportion;
diff --git a/core/java/android/text/style/ReplacementSpan.java b/core/java/android/text/style/ReplacementSpan.java
index 9430fd3..a6fe1fe 100644
--- a/core/java/android/text/style/ReplacementSpan.java
+++ b/core/java/android/text/style/ReplacementSpan.java
@@ -23,6 +23,7 @@
 import android.graphics.Paint;
 import android.text.TextPaint;
 
+@android.ravenwood.annotation.RavenwoodKeepWholeClass
 public abstract class ReplacementSpan extends MetricAffectingSpan {
 
     private CharSequence mContentDescription = null;
diff --git a/core/java/android/text/style/ScaleXSpan.java b/core/java/android/text/style/ScaleXSpan.java
index d022b07..009973e 100644
--- a/core/java/android/text/style/ScaleXSpan.java
+++ b/core/java/android/text/style/ScaleXSpan.java
@@ -36,6 +36,7 @@
  * <img src="{@docRoot}reference/android/images/text/style/scalexspan.png" />
  * <figcaption>Text scaled by 100% with <code>ScaleXSpan</code>.</figcaption>
  */
+@android.ravenwood.annotation.RavenwoodKeepWholeClass
 public class ScaleXSpan extends MetricAffectingSpan implements ParcelableSpan {
 
     private final float mProportion;
diff --git a/core/java/android/text/style/SpanUtils.java b/core/java/android/text/style/SpanUtils.java
index 6b4bd1a..21a96cd 100644
--- a/core/java/android/text/style/SpanUtils.java
+++ b/core/java/android/text/style/SpanUtils.java
@@ -30,6 +30,7 @@
 /**
  * @hide
  */
+@android.ravenwood.annotation.RavenwoodKeepWholeClass
 public class SpanUtils {
     private SpanUtils() {}  // Do not instantiate
 
diff --git a/core/java/android/text/style/SpellCheckSpan.java b/core/java/android/text/style/SpellCheckSpan.java
index e8ec3c6..39cd279 100644
--- a/core/java/android/text/style/SpellCheckSpan.java
+++ b/core/java/android/text/style/SpellCheckSpan.java
@@ -28,6 +28,7 @@
  *
  * @hide
  */
+@android.ravenwood.annotation.RavenwoodKeepWholeClass
 public class SpellCheckSpan implements ParcelableSpan {
 
     private boolean mSpellCheckInProgress;
diff --git a/core/java/android/text/style/StrikethroughSpan.java b/core/java/android/text/style/StrikethroughSpan.java
index 65ee347..3654870 100644
--- a/core/java/android/text/style/StrikethroughSpan.java
+++ b/core/java/android/text/style/StrikethroughSpan.java
@@ -32,6 +32,7 @@
  * <img src="{@docRoot}reference/android/images/text/style/strikethroughspan.png" />
  * <figcaption>Strikethrough text.</figcaption>
  */
+@android.ravenwood.annotation.RavenwoodKeepWholeClass
 public class StrikethroughSpan extends CharacterStyle
         implements UpdateAppearance, ParcelableSpan {
 
diff --git a/core/java/android/text/style/StyleSpan.java b/core/java/android/text/style/StyleSpan.java
index 378682b..c01e134 100644
--- a/core/java/android/text/style/StyleSpan.java
+++ b/core/java/android/text/style/StyleSpan.java
@@ -44,6 +44,7 @@
  * <img src="{@docRoot}reference/android/images/text/style/stylespan.png" />
  * <figcaption>Text styled bold and italic with the <code>StyleSpan</code>.</figcaption>
  */
+@android.ravenwood.annotation.RavenwoodKeepWholeClass
 public class StyleSpan extends MetricAffectingSpan implements ParcelableSpan {
 
     private final int mStyle;
diff --git a/core/java/android/text/style/SubscriptSpan.java b/core/java/android/text/style/SubscriptSpan.java
index 729a9ad..54c765d 100644
--- a/core/java/android/text/style/SubscriptSpan.java
+++ b/core/java/android/text/style/SubscriptSpan.java
@@ -37,6 +37,7 @@
  * Note: Since the span affects the position of the text, if the text is on the last line of a
  * TextView, it may appear cut.
  */
+@android.ravenwood.annotation.RavenwoodKeepWholeClass
 public class SubscriptSpan extends MetricAffectingSpan implements ParcelableSpan {
 
     /**
diff --git a/core/java/android/text/style/SuggestionRangeSpan.java b/core/java/android/text/style/SuggestionRangeSpan.java
index 1eee99a..640fae4 100644
--- a/core/java/android/text/style/SuggestionRangeSpan.java
+++ b/core/java/android/text/style/SuggestionRangeSpan.java
@@ -27,6 +27,7 @@
  * A SuggestionRangeSpan is used to show which part of an EditText is affected by a suggestion
  * popup window.
  */
+@android.ravenwood.annotation.RavenwoodKeepWholeClass
 public final class SuggestionRangeSpan extends CharacterStyle implements ParcelableSpan {
     private int mBackgroundColor;
 
diff --git a/core/java/android/text/style/SuggestionSpan.java b/core/java/android/text/style/SuggestionSpan.java
index 0cf96f6..d819062 100644
--- a/core/java/android/text/style/SuggestionSpan.java
+++ b/core/java/android/text/style/SuggestionSpan.java
@@ -48,6 +48,7 @@
  *
  * @see TextView#isSuggestionsEnabled()
  */
+@android.ravenwood.annotation.RavenwoodKeepWholeClass
 public class SuggestionSpan extends CharacterStyle implements ParcelableSpan {
 
     private static final String TAG = "SuggestionSpan";
diff --git a/core/java/android/text/style/SuperscriptSpan.java b/core/java/android/text/style/SuperscriptSpan.java
index 5610223..d3b339c 100644
--- a/core/java/android/text/style/SuperscriptSpan.java
+++ b/core/java/android/text/style/SuperscriptSpan.java
@@ -35,6 +35,7 @@
  * TextView, it may appear cut. This can be avoided by decreasing the text size with an {@link
  * AbsoluteSizeSpan}
  */
+@android.ravenwood.annotation.RavenwoodKeepWholeClass
 public class SuperscriptSpan extends MetricAffectingSpan implements ParcelableSpan {
     /**
      * Creates a {@link SuperscriptSpan}.
diff --git a/core/java/android/text/style/TabStopSpan.java b/core/java/android/text/style/TabStopSpan.java
index 8128475..e6733a2 100644
--- a/core/java/android/text/style/TabStopSpan.java
+++ b/core/java/android/text/style/TabStopSpan.java
@@ -24,6 +24,7 @@
  * the leading margin of the line. <code>TabStopSpan</code> will only affect the first tab
  * encountered on the first line of the text.
  */
+@android.ravenwood.annotation.RavenwoodKeepWholeClass
 public interface TabStopSpan extends ParagraphStyle {
 
     /**
diff --git a/core/java/android/text/style/TextAppearanceSpan.java b/core/java/android/text/style/TextAppearanceSpan.java
index 245a9db..7ede349 100644
--- a/core/java/android/text/style/TextAppearanceSpan.java
+++ b/core/java/android/text/style/TextAppearanceSpan.java
@@ -58,6 +58,7 @@
  * @attr ref android.R.styleable#TextAppearance_fontVariationSettings
  *
  */
+@android.ravenwood.annotation.RavenwoodKeepWholeClass
 public class TextAppearanceSpan extends MetricAffectingSpan implements ParcelableSpan {
     private final String mFamilyName;
     private final int mStyle;
diff --git a/core/java/android/text/style/TtsSpan.java b/core/java/android/text/style/TtsSpan.java
index e0d4ec1..6d776d1 100644
--- a/core/java/android/text/style/TtsSpan.java
+++ b/core/java/android/text/style/TtsSpan.java
@@ -42,6 +42,7 @@
  * The inner classes are there for convenience and provide builders for each
  * TtsSpan type.
  */
+@android.ravenwood.annotation.RavenwoodKeepWholeClass
 public class TtsSpan implements ParcelableSpan {
     private final String mType;
     private final PersistableBundle mArgs;
diff --git a/core/java/android/text/style/TypefaceSpan.java b/core/java/android/text/style/TypefaceSpan.java
index bdfc772..86f7f76 100644
--- a/core/java/android/text/style/TypefaceSpan.java
+++ b/core/java/android/text/style/TypefaceSpan.java
@@ -50,6 +50,7 @@
  * <figcaption>Text with <code>TypefaceSpan</code>s constructed based on a font from resource and
  * from a font family.</figcaption>
  */
+@android.ravenwood.annotation.RavenwoodKeepWholeClass
 public class TypefaceSpan extends MetricAffectingSpan implements ParcelableSpan {
 
     @Nullable
diff --git a/core/java/android/text/style/URLSpan.java b/core/java/android/text/style/URLSpan.java
index 9969d29..f06627d 100644
--- a/core/java/android/text/style/URLSpan.java
+++ b/core/java/android/text/style/URLSpan.java
@@ -41,6 +41,7 @@
  * <img src="{@docRoot}reference/android/images/text/style/urlspan.png" />
  * <figcaption>Text with <code>URLSpan</code>.</figcaption>
  */
+@android.ravenwood.annotation.RavenwoodKeepWholeClass
 public class URLSpan extends ClickableSpan implements ParcelableSpan {
 
     private final String mURL;
diff --git a/core/java/android/text/style/UnderlineSpan.java b/core/java/android/text/style/UnderlineSpan.java
index 075e70b..b3bb142 100644
--- a/core/java/android/text/style/UnderlineSpan.java
+++ b/core/java/android/text/style/UnderlineSpan.java
@@ -32,6 +32,7 @@
  * <img src="{@docRoot}reference/android/images/text/style/underlinespan.png" />
  * <figcaption>Underlined text.</figcaption>
  */
+@android.ravenwood.annotation.RavenwoodKeepWholeClass
 public class UnderlineSpan extends CharacterStyle
         implements UpdateAppearance, ParcelableSpan {
 
diff --git a/core/java/android/text/style/UpdateAppearance.java b/core/java/android/text/style/UpdateAppearance.java
index 7112347..7b0a6d3 100644
--- a/core/java/android/text/style/UpdateAppearance.java
+++ b/core/java/android/text/style/UpdateAppearance.java
@@ -22,5 +22,6 @@
  * that if the class also impacts size or other metrics, it should instead
  * implement {@link UpdateLayout}.
  */
+@android.ravenwood.annotation.RavenwoodKeepWholeClass
 public interface UpdateAppearance {
 }
diff --git a/core/java/android/text/style/UpdateLayout.java b/core/java/android/text/style/UpdateLayout.java
index 591075e..5af4141 100644
--- a/core/java/android/text/style/UpdateLayout.java
+++ b/core/java/android/text/style/UpdateLayout.java
@@ -22,4 +22,5 @@
  * this interface.  This interface also includes {@link UpdateAppearance}
  * since such a change implicitly also impacts the appearance.
  */
+@android.ravenwood.annotation.RavenwoodKeepWholeClass
 public interface UpdateLayout extends UpdateAppearance { }
diff --git a/core/java/android/text/style/WrapTogetherSpan.java b/core/java/android/text/style/WrapTogetherSpan.java
index 11721a8..cf74c1b 100644
--- a/core/java/android/text/style/WrapTogetherSpan.java
+++ b/core/java/android/text/style/WrapTogetherSpan.java
@@ -16,6 +16,7 @@
 
 package android.text.style;
 
+@android.ravenwood.annotation.RavenwoodKeepWholeClass
 public interface WrapTogetherSpan
 extends ParagraphStyle
 {
diff --git a/core/java/android/text/util/Rfc822Token.java b/core/java/android/text/util/Rfc822Token.java
index 2f207db..d6e987b 100644
--- a/core/java/android/text/util/Rfc822Token.java
+++ b/core/java/android/text/util/Rfc822Token.java
@@ -22,6 +22,7 @@
  * This class stores an RFC 822-like name, address, and comment,
  * and provides methods to convert them to quoted strings.
  */
+@android.ravenwood.annotation.RavenwoodKeepWholeClass
 public class Rfc822Token {
     @Nullable
     private String mName, mAddress, mComment;
diff --git a/core/java/android/text/util/Rfc822Tokenizer.java b/core/java/android/text/util/Rfc822Tokenizer.java
index 68334e4..8a9252a 100644
--- a/core/java/android/text/util/Rfc822Tokenizer.java
+++ b/core/java/android/text/util/Rfc822Tokenizer.java
@@ -27,6 +27,7 @@
  * a string of addresses (such as might be typed into such a field)
  * into a series of Rfc822Tokens.
  */
+@android.ravenwood.annotation.RavenwoodKeepWholeClass
 public class Rfc822Tokenizer implements MultiAutoCompleteTextView.Tokenizer {
 
     /**
diff --git a/core/java/android/view/Display.java b/core/java/android/view/Display.java
index 231aa68..3f45e29 100644
--- a/core/java/android/view/Display.java
+++ b/core/java/android/view/Display.java
@@ -108,6 +108,7 @@
     private final String mOwnerPackageName;
     private final Resources mResources;
     private DisplayAdjustments mDisplayAdjustments;
+    private boolean mRefreshRateChangesRegistered;
 
     @UnsupportedAppUsage
     private DisplayInfo mDisplayInfo; // never null
@@ -1217,6 +1218,10 @@
      */
     public float getRefreshRate() {
         synchronized (mLock) {
+            if (!mRefreshRateChangesRegistered) {
+                DisplayManagerGlobal.getInstance().registerForRefreshRateChanges();
+                mRefreshRateChangesRegistered = true;
+            }
             updateDisplayInfoLocked();
             return mDisplayInfo.getRefreshRate();
         }
@@ -1601,7 +1606,7 @@
                                     .INTERNAL_EVENT_FLAG_DISPLAY_BASIC_CHANGED
                             | DisplayManagerGlobal
                                     .INTERNAL_EVENT_FLAG_DISPLAY_HDR_SDR_RATIO_CHANGED,
-                    ActivityThread.currentPackageName());
+                    ActivityThread.currentPackageName(), /* isEventFilterImplicit */ true);
         }
 
     }
diff --git a/core/java/android/view/InsetsController.java b/core/java/android/view/InsetsController.java
index c174fbe..3659e78 100644
--- a/core/java/android/view/InsetsController.java
+++ b/core/java/android/view/InsetsController.java
@@ -1299,7 +1299,11 @@
             // Handle the pending show request for other insets types since the IME insets
             // has being requested hidden.
             handlePendingControlRequest(statsToken);
-            getImeSourceConsumer().removeSurface();
+            if (!Flags.refactorInsetsController()) {
+                // the surface can't be removed until the end of the animation. This is handled by
+                // IMMS after the window was requested to be hidden.
+                getImeSourceConsumer().removeSurface();
+            }
         }
         applyAnimation(typesReady, false /* show */, fromIme, false /* skipsCallbacks */,
                 statsToken);
diff --git a/core/java/android/view/SurfaceControl.java b/core/java/android/view/SurfaceControl.java
index 0903d22..c4347f0 100644
--- a/core/java/android/view/SurfaceControl.java
+++ b/core/java/android/view/SurfaceControl.java
@@ -3080,6 +3080,21 @@
         }
 
         /**
+         * Changes the default ApplyToken.
+         *
+         * ApplyToken is used to determine the order in which Transactions are applied.
+         * Transactions applied with the same ApplyToken will be applied in the order
+         * they were queued in SurfaceFlinger. Transactions are sent via binder so the
+         * caller should be aware of the order in which binder calls are executed in
+         * SurfaceFlinger. This along with the ApplyToken will determine the order
+         * in which Transactions are applied. Transactions with different apply tokens
+         * will be applied in arbitrary order regardless of when they were queued in
+         * SurfaceFlinger.
+         *
+         * Caller must keep track of the previous ApplyToken if they want to restore it.
+         *
+         * Note each buffer producer should have its own ApplyToken in order to ensure
+         * that Transactions are not delayed by Transactions from other buffer producers.
          *
          * @hide
          */
@@ -3088,6 +3103,7 @@
         }
 
         /**
+         * Returns the default ApplyToken.
          *
          * @hide
          */
@@ -3096,8 +3112,10 @@
         }
 
         /**
-         * Apply the transaction, clearing it's state, and making it usable
+         * Apply the transaction, clearing its state, and making it usable
          * as a new transaction.
+         *
+         * This method will also increment the transaction ID for debugging purposes.
          */
         public void apply() {
             apply(/*sync*/ false);
@@ -3116,7 +3134,7 @@
 
 
         /**
-         * Clear the transaction object, without applying it.
+         * Clear the transaction object, without applying it. The transction ID is preserved.
          *
          * @hide
          */
@@ -3375,6 +3393,9 @@
          * If two siblings share the same Z order the ordering is undefined. Surfaces
          * with a negative Z will be placed below the parent surface.
          *
+         * Calling setLayer after setRelativeLayer will reset the relative layer
+         * in the same transaction.
+         *
          * @param sc The SurfaceControl to set the Z order on
          * @param z The Z-order
          * @return This Transaction.
@@ -3392,6 +3413,22 @@
         }
 
         /**
+         * Set the Z-order for a given SurfaceControl, relative to the specified SurfaceControl.
+         * The SurfaceControl with a negative z will be placed below the relativeTo
+         * SurfaceControl and the SurfaceControl with a positive z will be placed above the
+         * relativeTo SurfaceControl.
+         *
+         * Calling setLayer will reset the relative layer. Calling setRelativeLayer after setLayer
+         * will override the setLayer call.
+         *
+         * If a layer is set to be relative to a layer that is destroyed, the layer will be
+         * offscreen until setLayer is called or setRelativeLayer is called with a valid
+         * SurfaceControl.
+         *
+         * @param sc The SurfaceControl to set the Z order on
+         * @param relativeTo The SurfaceControl to set the Z order relative to
+         * @param z The Z-order
+         * @return This Transaction.
          * @hide
          */
         public Transaction setRelativeLayer(SurfaceControl sc, SurfaceControl relativeTo, int z) {
@@ -3405,6 +3442,9 @@
         }
 
         /**
+         * The hint from the buffer producer as to what portion of the layer is
+         * transparent.
+         *
          * @hide
          */
         public Transaction setTransparentRegionHint(SurfaceControl sc, Region transparentRegion) {
@@ -3438,6 +3478,10 @@
         }
 
         /**
+         * Sets the input channel for a given SurfaceControl. The position and order of the
+         * SurfaceControl in conjunction with the touchable region in the InputWindowHandle
+         * determines the hit region.
+         *
          * @hide
          */
         public Transaction setInputWindowInfo(SurfaceControl sc, InputWindowHandle handle) {
@@ -3549,6 +3593,8 @@
          * surface. If no crop is specified and the surface has no buffer, the surface bounds is
          * only constrained by the size of its parent bounds.
          *
+         * To unset the crop, pass in an invalid Rect (0, 0, -1, -1)
+         *
          * @param sc   SurfaceControl to set crop of.
          * @param crop Bounds of the crop to apply.
          * @hide
@@ -3578,6 +3624,8 @@
          * surface. If no crop is specified and the surface has no buffer, the surface bounds is
          * only constrained by the size of its parent bounds.
          *
+         * To unset the crop, pass in an invalid Rect (0, 0, -1, -1)
+         *
          * @param sc   SurfaceControl to set crop of.
          * @param crop Bounds of the crop to apply.
          * @return this This transaction for chaining
@@ -3625,6 +3673,8 @@
          * surface. If no crop is specified and the surface has no buffer, the surface bounds is
          * only constrained by the size of its parent bounds.
          *
+         * To unset the crop, pass in an invalid Rect (0, 0, -1, -1)
+         *
          * @param sc   SurfaceControl to set crop of.
          * @param crop Bounds of the crop to apply.
          * @return this This transaction for chaining
@@ -3643,7 +3693,12 @@
         }
 
         /**
-         * Sets the corner radius of a {@link SurfaceControl}.
+         * Sets the corner radius of a {@link SurfaceControl}. This corner radius is applied to the
+         * SurfaceControl and its children. The API expects a crop to be set on the SurfaceControl
+         * to ensure that the corner radius is applied to the correct region. If the crop does not
+         * intersect with the SurfaceControl's visible content, the corner radius will not be
+         * applied.
+         *
          * @param sc SurfaceControl
          * @param cornerRadius Corner radius in pixels.
          * @return Itself.
@@ -3753,6 +3808,9 @@
         }
 
         /**
+         * Associates a layer with a display. The layer will be drawn on the display with the
+         * specified layer stack. If the layer is not a root layer, this call has no effect.
+         *
          * @hide
          */
         @UnsupportedAppUsage(maxTargetSdk = Build.VERSION_CODES.O)
@@ -3791,6 +3849,7 @@
 
         /**
          * Fills the surface with the specified color.
+         *
          * @param color A float array with three values to represent r, g, b in range [0..1]. An
          * invalid color will remove the color fill.
          * @hide
@@ -3809,8 +3868,9 @@
 
         /**
          * Removes color fill.
-        * @hide
-        */
+         *
+         * @hide
+         */
         public Transaction unsetColor(SurfaceControl sc) {
             checkPreconditions(sc);
             if (SurfaceControlRegistry.sCallStackDebuggingEnabled) {
@@ -3898,6 +3958,8 @@
         }
 
         /**
+         * Sets the surface to render contents of the display to.
+         *
          * @hide
          */
         public Transaction setDisplaySurface(IBinder displayToken, Surface surface) {
@@ -3916,6 +3978,9 @@
         }
 
         /**
+         * Sets the layer stack of the display.
+         *
+         * All layers with the same layer stack will be drawn on this display.
          * @hide
          */
         public Transaction setDisplayLayerStack(IBinder displayToken, int layerStack) {
diff --git a/core/java/android/view/SurfaceView.java b/core/java/android/view/SurfaceView.java
index dd32947..b98f4db 100644
--- a/core/java/android/view/SurfaceView.java
+++ b/core/java/android/view/SurfaceView.java
@@ -16,6 +16,7 @@
 
 package android.view;
 
+import static android.view.flags.Flags.FLAG_DEPRECATE_SURFACE_VIEW_Z_ORDER_APIS;
 import static android.view.flags.Flags.FLAG_SURFACE_VIEW_GET_SURFACE_PACKAGE;
 import static android.view.flags.Flags.FLAG_SURFACE_VIEW_SET_COMPOSITION_ORDER;
 import static android.view.WindowManager.LayoutParams.FLAG_KEEP_SCREEN_ON;
@@ -812,7 +813,12 @@
      * window is attached to the window manager.
      *
      * <p>Calling this overrides any previous call to {@link #setZOrderOnTop}.
+     *
+     * @deprecated Use {@link #setCompositionOrder(int)} instead. It provides more
+     * control over the Z ordering behavior.
      */
+    @Deprecated
+    @FlaggedApi(FLAG_DEPRECATE_SURFACE_VIEW_Z_ORDER_APIS)
     public void setZOrderMediaOverlay(boolean isMediaOverlay) {
         mRequestedSubLayer = isMediaOverlay
             ? APPLICATION_MEDIA_OVERLAY_SUBLAYER : APPLICATION_MEDIA_SUBLAYER;
@@ -834,7 +840,12 @@
      * <p>Calling this overrides any previous call to {@link #setZOrderMediaOverlay}.
      *
      * @param onTop Whether to show the surface on top of this view's window.
+     *
+     * @deprecated Use {@link #setCompositionOrder(int)} instead. It provides more
+     * control over the Z ordering behavior.
      */
+    @Deprecated
+    @FlaggedApi(FLAG_DEPRECATE_SURFACE_VIEW_Z_ORDER_APIS)
     public void setZOrderOnTop(boolean onTop) {
         // In R and above we allow dynamic layer changes.
         final boolean allowDynamicChange = getContext().getApplicationInfo().targetSdkVersion
@@ -866,7 +877,11 @@
      * @return Whether the Z ordering changed.
      *
      * @hide
+     *
+     * @deprecated Use {@link #setCompositionOrder(int)} instead. It provides more control
+     * over the Z ordering behavior.
      */
+    @Deprecated
     public boolean setZOrderedOnTop(boolean onTop, boolean allowDynamicChange) {
         final int subLayer;
         if (onTop) {
diff --git a/core/java/android/view/WindowInsets.java b/core/java/android/view/WindowInsets.java
index e3ea6b22..a952967 100644
--- a/core/java/android/view/WindowInsets.java
+++ b/core/java/android/view/WindowInsets.java
@@ -1290,7 +1290,8 @@
         int newTop = Math.max(0, insets.top - top);
         int newRight = Math.max(0, insets.right - right);
         int newBottom = Math.max(0, insets.bottom - bottom);
-        if (newLeft == left && newTop == top && newRight == right && newBottom == bottom) {
+        if (newLeft == insets.left && newTop == insets.top
+                && newRight == insets.right && newBottom == insets.bottom) {
             return insets;
         }
         return Insets.of(newLeft, newTop, newRight, newBottom);
diff --git a/core/java/android/view/WindowManager.java b/core/java/android/view/WindowManager.java
index 93eed37..24647f4 100644
--- a/core/java/android/view/WindowManager.java
+++ b/core/java/android/view/WindowManager.java
@@ -1199,6 +1199,43 @@
             "android.window.PROPERTY_CAMERA_COMPAT_ENABLE_REFRESH_VIA_PAUSE";
 
     /**
+     * Application-level [PackageManager][android.content.pm.PackageManager.Property] tag that (when
+     * set to false) informs the system the app has opted out of the camera compatibility treatment
+     * for fixed-orientation apps, which simulates running on a portrait device, in the orientation
+     * requested by the app.
+     *
+     * <p>This treatment aims to mitigate camera issues on large screens, like stretched or sideways
+     * previews. It simulates running on a portrait device by:
+     * <ul>
+     *   <li>Letterboxing the app window,
+     *   <li>Cropping the camera buffer to match the app's requested orientation,
+     *   <li>Setting the camera sensor orientation to portrait.
+     *   <li>Setting the display rotation to match the app's requested orientation, given portrait
+     *       natural orientation,
+     *   <li>Refreshes the activity to trigger new camera setup, with sandboxed values.
+     * </ul>
+     *
+     * <p>To opt out of the camera compatibility treatment, add this property to your app manifest
+     * and set the value to {@code false}.
+     *
+     * <p>Not setting this property at all, or setting this property to {@code true} has no effect.
+     *
+     * <p><b>Syntax:</b>
+     * <pre>
+     * &lt;application&gt;
+     *   &lt;property
+     *     android:name="android.window.PROPERTY_CAMERA_COMPAT_ALLOW_SIMULATE_REQUESTED_ORIENTATION"
+     *     android:value="true|false"/&gt;
+     * &lt;/application&gt;
+     * </pre>
+     *
+     * @hide
+     */
+    //TODO(b/394590412): Make this property public.
+    String PROPERTY_CAMERA_COMPAT_ALLOW_SIMULATE_REQUESTED_ORIENTATION =
+            "android.window.PROPERTY_CAMERA_COMPAT_ALLOW_SIMULATE_REQUESTED_ORIENTATION";
+
+    /**
      * Application level {@link android.content.pm.PackageManager.Property PackageManager.Property}
      * for an app to inform the system that the app should be excluded from the compatibility
      * override for orientation set by the device manufacturer. When the orientation override is
diff --git a/core/java/android/view/flags/view_flags.aconfig b/core/java/android/view/flags/view_flags.aconfig
index f6fdec9..d06f885 100644
--- a/core/java/android/view/flags/view_flags.aconfig
+++ b/core/java/android/view/flags/view_flags.aconfig
@@ -129,6 +129,14 @@
 }
 
 flag {
+    name: "deprecate_surface_view_z_order_apis"
+    namespace: "window_surfaces"
+    description: "Deprecate SurfaceView z order control APIs."
+    bug: "341021569"
+    is_fixed_read_only: true
+}
+
+flag {
     name: "use_refactored_round_scrollbar"
     namespace: "wear_frameworks"
     description: "Use refactored round scrollbar."
diff --git a/core/java/android/view/inputmethod/InputMethodManager.java b/core/java/android/view/inputmethod/InputMethodManager.java
index 421001f..0b34600 100644
--- a/core/java/android/view/inputmethod/InputMethodManager.java
+++ b/core/java/android/view/inputmethod/InputMethodManager.java
@@ -532,6 +532,7 @@
     final H mH;
 
     // Our generic input connection if the current target does not have its own.
+    @NonNull
     private final RemoteInputConnectionImpl mFallbackInputConnection;
 
     private final int mDisplayId;
diff --git a/core/java/android/view/textclassifier/intent/OWNERS b/core/java/android/view/textclassifier/intent/OWNERS
index 3465fe6..dc18514 100644
--- a/core/java/android/view/textclassifier/intent/OWNERS
+++ b/core/java/android/view/textclassifier/intent/OWNERS
@@ -1,6 +1,5 @@
 # Bug component: 709498
 
-mns@google.com
 toki@google.com
 svetoslavganov@android.com
 svetoslavganov@google.com
diff --git a/core/java/android/widget/RemoteViews.java b/core/java/android/widget/RemoteViews.java
index 0a5c14e..222a7b3 100644
--- a/core/java/android/widget/RemoteViews.java
+++ b/core/java/android/widget/RemoteViews.java
@@ -9216,6 +9216,7 @@
         public static RemoteResponse fromFillInIntent(@NonNull Intent fillIntent) {
             RemoteResponse response = new RemoteResponse();
             response.mFillIntent = fillIntent;
+            fillIntent.collectExtraIntentKeys();
             return response;
         }
 
@@ -9224,6 +9225,7 @@
             RemoteResponse response = new RemoteResponse();
             response.mPendingIntent = pendingIntent;
             response.mFillIntent = intent;
+            intent.collectExtraIntentKeys();
             return response;
         }
 
diff --git a/core/java/android/window/DesktopModeFlags.java b/core/java/android/window/DesktopModeFlags.java
index b44620f..4aeedbb 100644
--- a/core/java/android/window/DesktopModeFlags.java
+++ b/core/java/android/window/DesktopModeFlags.java
@@ -102,9 +102,15 @@
     ENABLE_HOLD_TO_DRAG_APP_HANDLE(Flags::enableHoldToDragAppHandle, true),
     ENABLE_MINIMIZE_BUTTON(Flags::enableMinimizeButton, true),
     ENABLE_MODALS_FULLSCREEN_WITH_PERMISSIONS(Flags::enableModalsFullscreenWithPermission, false),
+    ENABLE_OPAQUE_BACKGROUND_FOR_TRANSPARENT_WINDOWS(
+            Flags::enableOpaqueBackgroundForTransparentWindows, false),
+    ENABLE_QUICKSWITCH_DESKTOP_SPLIT_BUGFIX(Flags::enableQuickswitchDesktopSplitBugfix, true),
     ENABLE_RESIZING_METRICS(Flags::enableResizingMetrics, true),
     ENABLE_RESTORE_TO_PREVIOUS_SIZE_FROM_DESKTOP_IMMERSIVE(
             Flags::enableRestoreToPreviousSizeFromDesktopImmersive, true),
+    ENABLE_START_LAUNCH_TRANSITION_FROM_TASKBAR_BUGFIX(
+            Flags::enableStartLaunchTransitionFromTaskbarBugfix, 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),
@@ -117,6 +123,8 @@
     ENABLE_WINDOWING_TRANSITION_HANDLERS_OBSERVERS(
             Flags::enableWindowingTransitionHandlersObservers, false),
     EXCLUDE_CAPTION_FROM_APP_BOUNDS(Flags::excludeCaptionFromAppBounds, false),
+    IGNORE_ASPECT_RATIO_RESTRICTIONS_FOR_RESIZEABLE_FREEFORM_ACTIVITIES(
+            Flags::ignoreAspectRatioRestrictionsForResizeableFreeformActivities, true),
     INCLUDE_TOP_TRANSPARENT_FULLSCREEN_TASK_IN_DESKTOP_HEURISTIC(
             Flags::includeTopTransparentFullscreenTaskInDesktopHeuristic, true)
     // go/keep-sorted end
diff --git a/core/java/android/window/TransitionFilter.java b/core/java/android/window/TransitionFilter.java
index 61fc622..d8d2011 100644
--- a/core/java/android/window/TransitionFilter.java
+++ b/core/java/android/window/TransitionFilter.java
@@ -31,8 +31,6 @@
 import android.os.Parcelable;
 import android.view.WindowManager;
 
-import com.android.window.flags.Flags;
-
 /**
  * A parcelable filter that can be used for rerouting transitions to a remote. This is a local
  * representation so that the transition system doesn't need to make blocking queries over
@@ -261,9 +259,7 @@
                         // only applies to activity/task
                         && (change.getTaskInfo() != null
                                 || change.getActivityComponent() != null)) {
-                    final TransitionInfo.AnimationOptions opts =
-                            Flags.moveAnimationOptionsToChange() ? change.getAnimationOptions()
-                                    : info.getAnimationOptions();
+                    final TransitionInfo.AnimationOptions opts = change.getAnimationOptions();
                     if (opts != null) {
                         boolean canActuallyOverride = change.getTaskInfo() == null
                                 || opts.getOverrideTaskTransition();
diff --git a/core/java/android/window/TransitionInfo.java b/core/java/android/window/TransitionInfo.java
index 4f34aa3..32175f1 100644
--- a/core/java/android/window/TransitionInfo.java
+++ b/core/java/android/window/TransitionInfo.java
@@ -57,8 +57,6 @@
 import android.view.SurfaceControl;
 import android.view.WindowManager;
 
-import com.android.window.flags.Flags;
-
 import java.lang.annotation.Retention;
 import java.lang.annotation.RetentionPolicy;
 import java.util.ArrayList;
@@ -220,10 +218,6 @@
     private final ArrayList<Change> mChanges = new ArrayList<>();
     private final ArrayList<Root> mRoots = new ArrayList<>();
 
-    // TODO(b/327332488): Clean-up usages after the flag is fully enabled.
-    @Deprecated
-    private AnimationOptions mOptions;
-
     /** This is only a BEST-EFFORT id used for log correlation. DO NOT USE for any real work! */
     private int mDebugId = -1;
 
@@ -238,7 +232,6 @@
         mFlags = in.readInt();
         in.readTypedList(mChanges, Change.CREATOR);
         in.readTypedList(mRoots, Root.CREATOR);
-        mOptions = in.readTypedObject(AnimationOptions.CREATOR);
         mDebugId = in.readInt();
         mTrack = in.readInt();
     }
@@ -250,7 +243,6 @@
         dest.writeInt(mFlags);
         dest.writeTypedList(mChanges);
         dest.writeTypedList(mRoots, flags);
-        dest.writeTypedObject(mOptions, flags);
         dest.writeInt(mDebugId);
         dest.writeInt(mTrack);
     }
@@ -286,18 +278,6 @@
         mRoots.add(other);
     }
 
-    /**
-     * @deprecated Set {@link AnimationOptions} to change. This method is only used if
-     * {@link Flags#FLAG_MOVE_ANIMATION_OPTIONS_TO_CHANGE} is disabled.
-     */
-    @Deprecated
-    public void setAnimationOptions(@Nullable AnimationOptions options) {
-        if (Flags.moveAnimationOptionsToChange()) {
-            return;
-        }
-        mOptions = options;
-    }
-
     public @TransitionType int getType() {
         return mType;
     }
@@ -360,16 +340,6 @@
     }
 
     /**
-     * @deprecated Use {@link Change#getAnimationOptions()} instead. This method is called only
-     * if {@link Flags#FLAG_MOVE_ANIMATION_OPTIONS_TO_CHANGE} is disabled.
-     */
-    @Deprecated
-    @Nullable
-    public AnimationOptions getAnimationOptions() {
-        return mOptions;
-    }
-
-    /**
      * @return the list of {@link Change}s in this transition. The list is sorted top-to-bottom
      *         in Z (meaning index 0 is the top-most container).
      */
@@ -455,9 +425,6 @@
         StringBuilder sb = new StringBuilder();
         sb.append("{id=").append(mDebugId).append(" t=").append(transitTypeToString(mType))
                 .append(" f=0x").append(Integer.toHexString(mFlags)).append(" trk=").append(mTrack);
-        if (mOptions != null) {
-            sb.append(" opt=").append(mOptions);
-        }
         sb.append(" r=[");
         for (int i = 0; i < mRoots.size(); ++i) {
             if (i > 0) {
@@ -656,8 +623,6 @@
         for (int i = 0; i < mRoots.size(); ++i) {
             out.mRoots.add(mRoots.get(i).localRemoteCopy());
         }
-        // Doesn't have any native stuff, so no need for actual copy
-        out.mOptions = mOptions;
         return out;
     }
 
@@ -860,9 +825,6 @@
          * Sets {@link AnimationOptions} to override animation.
          */
         public void setAnimationOptions(@Nullable AnimationOptions options) {
-            if (!Flags.moveAnimationOptionsToChange()) {
-                return;
-            }
             mAnimationOptions = options;
         }
 
diff --git a/core/java/android/window/flags/lse_desktop_experience.aconfig b/core/java/android/window/flags/lse_desktop_experience.aconfig
index 3e3b8e1..2e36e9a 100644
--- a/core/java/android/window/flags/lse_desktop_experience.aconfig
+++ b/core/java/android/window/flags/lse_desktop_experience.aconfig
@@ -206,6 +206,13 @@
 }
 
 flag {
+    name: "enable_camera_compat_for_desktop_windowing_opt_out_api"
+    namespace: "lse_desktop_experience"
+    description: "Introduces a developer API to opt-out of Camera Compat treatment for fixed-orientation apps in desktop windowing mode"
+    bug: "397165621"
+}
+
+flag {
     name: "enable_task_stack_observer_in_shell"
     namespace: "lse_desktop_experience"
     description: "Introduces a new observer in shell to track the task stack."
@@ -681,6 +688,16 @@
 }
 
 flag {
+  name: "enable_opaque_background_for_transparent_windows"
+  namespace: "lse_desktop_experience"
+  description: "Whether an opaque background should be forcefully set for windows with only transparent background."
+  bug: "397219542"
+  metadata {
+    purpose: PURPOSE_BUGFIX
+  }
+}
+
+flag {
     name: "enable_desktop_mode_through_dev_option"
     namespace: "lse_desktop_experience"
     description: "Enables support for desktop mode through developer options for devices eligible for desktop mode."
@@ -705,6 +722,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 +788,27 @@
         purpose: PURPOSE_BUGFIX
     }
 }
+
+flag {
+    name: "enable_taskbar_overflow"
+    namespace: "lse_desktop_experience"
+    description: "Show recent apps in the taskbar overflow."
+    bug: "368119679"
+}
+
+flag {
+    name: "enable_projected_display_desktop_mode"
+    namespace: "lse_desktop_experience"
+    description: "Enable Desktop Mode on Projected Mode devices but constrained to the external display."
+    bug: "384568161"
+}
+
+flag {
+    name: "enable_persisting_density_scale_for_connected_displays"
+    namespace: "lse_desktop_experience"
+    description: "Enables persisting density scale on resolution change for connected displays."
+    bug: "392855657"
+    metadata {
+        purpose: PURPOSE_BUGFIX
+    }
+}
\ No newline at end of file
diff --git a/core/java/android/window/flags/windowing_sdk.aconfig b/core/java/android/window/flags/windowing_sdk.aconfig
index 54d0eef..6e45d3d 100644
--- a/core/java/android/window/flags/windowing_sdk.aconfig
+++ b/core/java/android/window/flags/windowing_sdk.aconfig
@@ -62,16 +62,6 @@
 
 flag {
     namespace: "windowing_sdk"
-     name: "move_animation_options_to_change"
-     description: "Move AnimationOptions from TransitionInfo to each Change"
-     bug: "327332488"
-     metadata {
-         purpose: PURPOSE_BUGFIX
-     }
-}
-
-flag {
-    namespace: "windowing_sdk"
     name: "rear_display_disable_force_desktop_system_decorations"
     description: "Block system decorations from being added to a rear display when desktop mode is forced"
     bug: "346103150"
diff --git a/core/java/com/android/internal/app/MediaRouteControllerContentManager.java b/core/java/com/android/internal/app/MediaRouteControllerContentManager.java
index dc4caa3..3a8b94f 100644
--- a/core/java/com/android/internal/app/MediaRouteControllerContentManager.java
+++ b/core/java/com/android/internal/app/MediaRouteControllerContentManager.java
@@ -17,7 +17,12 @@
 package com.android.internal.app;
 
 import android.content.Context;
+import android.content.res.TypedArray;
+import android.graphics.drawable.AnimationDrawable;
+import android.graphics.drawable.Drawable;
+import android.graphics.drawable.StateListDrawable;
 import android.media.MediaRouter;
+import android.util.TypedValue;
 import android.view.View;
 import android.widget.LinearLayout;
 import android.widget.SeekBar;
@@ -35,9 +40,14 @@
      */
     public interface Delegate {
         /**
-         * Updates the title of the cast device
+         * Updates the title of the media route device
          */
-        void setCastDeviceTitle(CharSequence title);
+        void setMediaRouteDeviceTitle(CharSequence title);
+
+        /**
+         * Updates the icon of the media route device
+         */
+        void setMediaRouteDeviceIcon(Drawable icon);
 
         /**
          * Dismiss the UI to transition to a different workflow.
@@ -45,6 +55,7 @@
         void dismissView();
     }
 
+    private final Context mContext;
     private final Delegate mDelegate;
 
     // Time to wait before updating the volume when the user lets go of the seek bar
@@ -53,15 +64,25 @@
     private static final int VOLUME_UPDATE_DELAY_MILLIS = 250;
 
     private final MediaRouter mRouter;
+    private final MediaRouteControllerContentManager.MediaRouterCallback mCallback;
     private final MediaRouter.RouteInfo mRoute;
 
+    private Drawable mMediaRouteButtonDrawable;
+    private final int[] mMediaRouteConnectingState = { R.attr.state_checked, R.attr.state_enabled };
+    private final int[] mMediaRouteOnState = { R.attr.state_activated, R.attr.state_enabled };
+    private Drawable mCurrentIconDrawable;
+
+    private boolean mAttachedToWindow;
+
     private LinearLayout mVolumeLayout;
     private SeekBar mVolumeSlider;
     private boolean mVolumeSliderTouched;
 
     public MediaRouteControllerContentManager(Context context, Delegate delegate) {
+        mContext = context;
         mDelegate = delegate;
         mRouter = context.getSystemService(MediaRouter.class);
+        mCallback = new MediaRouteControllerContentManager.MediaRouterCallback();
         mRoute = mRouter.getSelectedRoute();
     }
 
@@ -70,7 +91,7 @@
      * given container view.
      */
     public void bindViews(View containerView) {
-        mDelegate.setCastDeviceTitle(mRoute.getName());
+        mDelegate.setMediaRouteDeviceTitle(mRoute.getName());
         mVolumeLayout = containerView.findViewById(R.id.media_route_volume_layout);
         mVolumeSlider = containerView.findViewById(R.id.media_route_volume_slider);
         mVolumeSlider.setOnSeekBarChangeListener(new SeekBar.OnSeekBarChangeListener() {
@@ -108,20 +129,58 @@
                 }
             }
         });
+
+        mMediaRouteButtonDrawable = obtainMediaRouteButtonDrawable();
+    }
+
+    /**
+     * Called when this UI is attached to a window..
+     */
+    public void onAttachedToWindow() {
+        mAttachedToWindow = true;
+        mRouter.addCallback(0, mCallback, MediaRouter.CALLBACK_FLAG_UNFILTERED_EVENTS);
+        update();
+    }
+
+    /**
+     * Called when this UI is detached from a window..
+     */
+    public void onDetachedFromWindow() {
+        mRouter.removeCallback(mCallback);
+        mAttachedToWindow = false;
     }
 
     /**
      * Updates all the views to reflect new states.
      */
     public void update() {
-        mDelegate.setCastDeviceTitle(mRoute.getName());
+        if (!mRoute.isSelected() || mRoute.isDefault()) {
+            mDelegate.dismissView();
+        }
+
+        mDelegate.setMediaRouteDeviceTitle(mRoute.getName());
         updateVolume();
+
+        Drawable icon = getIconDrawable();
+        if (icon != mCurrentIconDrawable) {
+            mCurrentIconDrawable = icon;
+            if (icon instanceof AnimationDrawable animDrawable) {
+                if (!mAttachedToWindow && !mRoute.isConnecting()) {
+                    // When the route is already connected before the view is attached, show the
+                    // last frame of the connected animation immediately.
+                    if (animDrawable.isRunning()) {
+                        animDrawable.stop();
+                    }
+                    icon = animDrawable.getFrame(animDrawable.getNumberOfFrames() - 1);
+                } else if (!animDrawable.isRunning()) {
+                    animDrawable.start();
+                }
+            }
+            mDelegate.setMediaRouteDeviceIcon(icon);
+        }
     }
 
-    /**
-     * Updates the volume layout and slider.
-     */
-    public void updateVolume() {
+    private void updateVolume() {
         if (!mVolumeSliderTouched) {
             if (isVolumeControlAvailable()) {
                 mVolumeLayout.setVisibility(View.VISIBLE);
@@ -150,4 +209,61 @@
     private boolean isVolumeControlAvailable() {
         return mRoute.getVolumeHandling() == MediaRouter.RouteInfo.PLAYBACK_VOLUME_VARIABLE;
     }
+
+    private Drawable obtainMediaRouteButtonDrawable() {
+        TypedValue value = new TypedValue();
+        if (!mContext.getTheme().resolveAttribute(R.attr.mediaRouteButtonStyle, value, true)) {
+            return null;
+        }
+        int[] drawableAttrs = new int[] { R.attr.externalRouteEnabledDrawable };
+        TypedArray a = mContext.obtainStyledAttributes(value.data, drawableAttrs);
+        Drawable drawable = a.getDrawable(0);
+        a.recycle();
+        return drawable;
+    }
+
+    private Drawable getIconDrawable() {
+        if (!(mMediaRouteButtonDrawable instanceof StateListDrawable)) {
+            return mMediaRouteButtonDrawable;
+        } else if (mRoute.isConnecting()) {
+            StateListDrawable stateListDrawable = (StateListDrawable) mMediaRouteButtonDrawable;
+            stateListDrawable.setState(mMediaRouteConnectingState);
+            return stateListDrawable.getCurrent();
+        } else {
+            StateListDrawable stateListDrawable = (StateListDrawable) mMediaRouteButtonDrawable;
+            stateListDrawable.setState(mMediaRouteOnState);
+            return stateListDrawable.getCurrent();
+        }
+    }
+
+    private final class MediaRouterCallback extends MediaRouter.SimpleCallback {
+        @Override
+        public void onRouteUnselected(MediaRouter router, int type, MediaRouter.RouteInfo info) {
+            update();
+        }
+
+        @Override
+        public void onRouteChanged(MediaRouter router, MediaRouter.RouteInfo route) {
+            update();
+        }
+
+        @Override
+        public void onRouteVolumeChanged(MediaRouter router, MediaRouter.RouteInfo route) {
+            if (route == mRoute) {
+                updateVolume();
+            }
+        }
+
+        @Override
+        public void onRouteGrouped(MediaRouter router, MediaRouter.RouteInfo info,
+                MediaRouter.RouteGroup group, int index) {
+            update();
+        }
+
+        @Override
+        public void onRouteUngrouped(MediaRouter router, MediaRouter.RouteInfo info,
+                MediaRouter.RouteGroup group) {
+            update();
+        }
+    }
 }
diff --git a/core/java/com/android/internal/app/MediaRouteControllerDialog.java b/core/java/com/android/internal/app/MediaRouteControllerDialog.java
index 45a4a13..5899963 100644
--- a/core/java/com/android/internal/app/MediaRouteControllerDialog.java
+++ b/core/java/com/android/internal/app/MediaRouteControllerDialog.java
@@ -21,15 +21,9 @@
 import android.app.MediaRouteButton;
 import android.content.Context;
 import android.content.res.Resources;
-import android.content.res.TypedArray;
-import android.graphics.drawable.AnimationDrawable;
 import android.graphics.drawable.Drawable;
-import android.graphics.drawable.StateListDrawable;
 import android.media.MediaRouter;
-import android.media.MediaRouter.RouteGroup;
-import android.media.MediaRouter.RouteInfo;
 import android.os.Bundle;
-import android.util.TypedValue;
 import android.view.KeyEvent;
 import android.view.View;
 
@@ -48,19 +42,11 @@
  */
 public class MediaRouteControllerDialog extends AlertDialog implements
         MediaRouteControllerContentManager.Delegate {
-    // TODO(b/360050020): Eventually these 3 variables should be in the content manager instead of
+    // TODO(b/360050020): Eventually these 2 variables should be in the content manager instead of
     //  here. So these should be removed when the migration is completed.
     private final MediaRouter mRouter;
-    private final MediaRouterCallback mCallback;
     private final MediaRouter.RouteInfo mRoute;
 
-    private Drawable mMediaRouteButtonDrawable;
-    private int[] mMediaRouteConnectingState = { R.attr.state_checked, R.attr.state_enabled };
-    private int[] mMediaRouteOnState = { R.attr.state_activated, R.attr.state_enabled };
-    private Drawable mCurrentIconDrawable;
-
-    private boolean mAttachedToWindow;
-
     private final MediaRouteControllerContentManager mContentManager;
 
     public MediaRouteControllerDialog(Context context, int theme) {
@@ -68,7 +54,6 @@
 
         mContentManager = new MediaRouteControllerContentManager(context, this);
         mRouter = (MediaRouter) context.getSystemService(Context.MEDIA_ROUTER_SERVICE);
-        mCallback = new MediaRouterCallback();
         mRoute = mRouter.getSelectedRoute();
     }
 
@@ -87,24 +72,18 @@
             customPanelView.setMinimumHeight(0);
         }
 
-        mMediaRouteButtonDrawable = obtainMediaRouteButtonDrawable();
-        update();
+        mContentManager.update();
     }
 
     @Override
     public void onAttachedToWindow() {
         super.onAttachedToWindow();
-        mAttachedToWindow = true;
-
-        mRouter.addCallback(0, mCallback, MediaRouter.CALLBACK_FLAG_UNFILTERED_EVENTS);
-        update();
+        mContentManager.onAttachedToWindow();
     }
 
     @Override
     public void onDetachedFromWindow() {
-        mRouter.removeCallback(mCallback);
-        mAttachedToWindow = false;
-
+        mContentManager.onDetachedFromWindow();
         super.onDetachedFromWindow();
     }
 
@@ -128,95 +107,17 @@
     }
 
     @Override
-    public void setCastDeviceTitle(CharSequence title) {
+    public void setMediaRouteDeviceTitle(CharSequence title) {
         setTitle(title);
     }
 
     @Override
+    public void setMediaRouteDeviceIcon(Drawable icon) {
+        setIcon(icon);
+    }
+
+    @Override
     public void dismissView() {
         dismiss();
     }
-
-    private void update() {
-        if (!mRoute.isSelected() || mRoute.isDefault()) {
-            dismissView();
-        }
-
-        mContentManager.update();
-
-        Drawable icon = getIconDrawable();
-        if (icon != mCurrentIconDrawable) {
-            mCurrentIconDrawable = icon;
-            if (icon instanceof AnimationDrawable animDrawable) {
-                if (!mAttachedToWindow && !mRoute.isConnecting()) {
-                    // When the route is already connected before the view is attached, show the
-                    // last frame of the connected animation immediately.
-                    if (animDrawable.isRunning()) {
-                        animDrawable.stop();
-                    }
-                    icon = animDrawable.getFrame(animDrawable.getNumberOfFrames() - 1);
-                } else if (!animDrawable.isRunning()) {
-                    animDrawable.start();
-                }
-            }
-            setIcon(icon);
-        }
-    }
-
-    private Drawable obtainMediaRouteButtonDrawable() {
-        Context context = getContext();
-        TypedValue value = new TypedValue();
-        if (!context.getTheme().resolveAttribute(R.attr.mediaRouteButtonStyle, value, true)) {
-            return null;
-        }
-        int[] drawableAttrs = new int[] { R.attr.externalRouteEnabledDrawable };
-        TypedArray a = context.obtainStyledAttributes(value.data, drawableAttrs);
-        Drawable drawable = a.getDrawable(0);
-        a.recycle();
-        return drawable;
-    }
-
-    private Drawable getIconDrawable() {
-        if (!(mMediaRouteButtonDrawable instanceof StateListDrawable)) {
-            return mMediaRouteButtonDrawable;
-        } else if (mRoute.isConnecting()) {
-            StateListDrawable stateListDrawable = (StateListDrawable) mMediaRouteButtonDrawable;
-            stateListDrawable.setState(mMediaRouteConnectingState);
-            return stateListDrawable.getCurrent();
-        } else {
-            StateListDrawable stateListDrawable = (StateListDrawable) mMediaRouteButtonDrawable;
-            stateListDrawable.setState(mMediaRouteOnState);
-            return stateListDrawable.getCurrent();
-        }
-    }
-
-    private final class MediaRouterCallback extends MediaRouter.SimpleCallback {
-        @Override
-        public void onRouteUnselected(MediaRouter router, int type, RouteInfo info) {
-            update();
-        }
-
-        @Override
-        public void onRouteChanged(MediaRouter router, MediaRouter.RouteInfo route) {
-            update();
-        }
-
-        @Override
-        public void onRouteVolumeChanged(MediaRouter router, MediaRouter.RouteInfo route) {
-            if (route == mRoute) {
-                mContentManager.updateVolume();
-            }
-        }
-
-        @Override
-        public void onRouteGrouped(MediaRouter router, RouteInfo info, RouteGroup group,
-                int index) {
-            update();
-        }
-
-        @Override
-        public void onRouteUngrouped(MediaRouter router, RouteInfo info, RouteGroup group) {
-            update();
-        }
-    }
 }
diff --git a/core/java/com/android/internal/jank/FrameTracker.java b/core/java/com/android/internal/jank/FrameTracker.java
index 454323b..a0f45b0 100644
--- a/core/java/com/android/internal/jank/FrameTracker.java
+++ b/core/java/com/android/internal/jank/FrameTracker.java
@@ -257,15 +257,11 @@
 
                 @Override
                 public void surfaceDestroyed() {
-
-                    // Wait a while to give the system a chance for the remaining
-                    // frames to arrive, then force finish the session.
-                    mHandler.postDelayed(() -> {
+                    mHandler.post(() -> {
                         if (!mMetricsFinalized) {
                             end(REASON_END_SURFACE_DESTROYED);
-                            finish();
                         }
-                    }, 50);
+                    });
                 }
             };
             // This callback has a reference to FrameTracker,
@@ -367,9 +363,9 @@
                     // Send a flush jank data transaction.
                     if (mSurfaceControl != null && mSurfaceControl.isValid()) {
                         SurfaceControl.Transaction.sendSurfaceFlushJankData(mSurfaceControl);
-                        if (mJankDataListenerRegistration != null) {
-                            mJankDataListenerRegistration.flush();
-                        }
+                    }
+                    if (mJankDataListenerRegistration != null) {
+                        mJankDataListenerRegistration.flush();
                     }
 
                     long delay;
@@ -650,6 +646,8 @@
                     Log.w(TAG, "Missing SF jank callback for vsyncId: " + info.frameVsyncId
                             + ", CUJ=" + name);
                 }
+            } else if (Flags.useSfFrameDuration() && info.surfaceControlCallbackFired) {
+                maxFrameTimeNanos = Math.max(info.totalDurationNanos, maxFrameTimeNanos);
             }
         }
         maxSuccessiveMissedFramesCount = Math.max(
diff --git a/core/java/com/android/internal/os/LongArrayMultiStateCounter.java b/core/java/com/android/internal/os/LongArrayMultiStateCounter.java
index fe616e0..5e9c87a 100644
--- a/core/java/com/android/internal/os/LongArrayMultiStateCounter.java
+++ b/core/java/com/android/internal/os/LongArrayMultiStateCounter.java
@@ -16,6 +16,7 @@
 
 package com.android.internal.os;
 
+import android.annotation.CheckResult;
 import android.annotation.Nullable;
 import android.os.Parcel;
 import android.os.Parcelable;
@@ -196,7 +197,8 @@
     /**
      * Populates the array with the accumulated counts for the specified state.
      */
-    public void getCounts(long[] counts, int state) {
+    @CheckResult
+    public boolean getCounts(long[] counts, int state) {
         if (state < 0 || state >= mStateCount) {
             throw new IllegalArgumentException(
                     "State: " + state + ", outside the range: [0-" + mStateCount + "]");
@@ -205,7 +207,7 @@
             throw new IllegalArgumentException(
                     "Invalid array length: " + counts.length + ", expected: " + mLength);
         }
-        native_getCounts(mNativeObject, counts, state);
+        return native_getCounts(mNativeObject, counts, state);
     }
 
     @Override
@@ -282,7 +284,8 @@
 
     @FastNative
     @RavenwoodRedirect
-    private static native void native_getCounts(long nativeObject, long[] counts, int state);
+    @CheckResult
+    private static native boolean native_getCounts(long nativeObject, long[] counts, int state);
 
     @FastNative
     @RavenwoodRedirect
diff --git a/core/java/com/android/internal/os/LongArrayMultiStateCounter_ravenwood.java b/core/java/com/android/internal/os/LongArrayMultiStateCounter_ravenwood.java
index 4f5f37d..403e8c1 100644
--- a/core/java/com/android/internal/os/LongArrayMultiStateCounter_ravenwood.java
+++ b/core/java/com/android/internal/os/LongArrayMultiStateCounter_ravenwood.java
@@ -168,8 +168,20 @@
             }
         }
 
-        public void getValues(long[] values, int state) {
-            System.arraycopy(mStates[state].mCounter, 0, values, 0, mArrayLength);
+        public boolean getValues(long[] values, int state) {
+            long[] counts = mStates[state].mCounter;
+            boolean allZeros = true;
+            for (int i = 0; i < counts.length; i++) {
+                if (counts[i] != 0) {
+                    allZeros = false;
+                    break;
+                }
+            }
+            if (allZeros) {
+                return false;
+            }
+            System.arraycopy(counts, 0, values, 0, mArrayLength);
+            return true;
         }
 
         public void reset() {
@@ -316,8 +328,8 @@
         getInstance(instanceId).addCounts(counts);
     }
 
-    public static void native_getCounts(long instanceId, long[] counts, int state) {
-        getInstance(instanceId).getValues(counts, state);
+    public static boolean native_getCounts(long instanceId, long[] counts, int state) {
+        return getInstance(instanceId).getValues(counts, state);
     }
 
     public static void native_reset(long instanceId) {
diff --git a/core/java/com/android/internal/widget/ConversationLayout.java b/core/java/com/android/internal/widget/ConversationLayout.java
index 641ecc9..ce46da1 100644
--- a/core/java/com/android/internal/widget/ConversationLayout.java
+++ b/core/java/com/android/internal/widget/ConversationLayout.java
@@ -250,7 +250,8 @@
             mPeopleHelper.animateViewForceHidden(mImportanceRingView, forceHidden);
             mPeopleHelper.animateViewForceHidden(mIcon, forceHidden);
         });
-        mConversationText = findViewById(R.id.conversation_text);
+        mConversationText = findViewById(notificationsRedesignTemplates()
+                ? R.id.title : R.id.conversation_text);
         mExpandButtonContainer = findViewById(R.id.expand_button_container);
         mExpandButtonContainerA11yContainer =
                 findViewById(R.id.expand_button_a11y_container);
@@ -716,17 +717,10 @@
     }
 
     private void updateImageMessages() {
-        View newMessage = null;
-        if (mIsCollapsed && !mGroups.isEmpty()) {
-
-            // When collapsed, we're displaying the image message in a dedicated container
-            // on the right of the layout instead of inline. Let's add the isolated image there
-            MessagingGroup messagingGroup = mGroups.getLast();
-            MessagingImageMessage isolatedMessage = messagingGroup.getIsolatedMessage();
-            if (isolatedMessage != null) {
-                newMessage = isolatedMessage.getView();
-            }
+        if (mImageMessageContainer == null) {
+            return;
         }
+        View newMessage = getNewImageMessage();
         // Remove all messages that don't belong into the image layout
         View previousMessage = mImageMessageContainer.getChildAt(0);
         if (previousMessage != newMessage) {
@@ -738,6 +732,20 @@
         mImageMessageContainer.setVisibility(newMessage != null ? VISIBLE : GONE);
     }
 
+    @Nullable
+    private View getNewImageMessage() {
+        if (mIsCollapsed && !mGroups.isEmpty()) {
+            // When collapsed, we're displaying the image message in a dedicated container
+            // on the right of the layout instead of inline. Let's add the isolated image there
+            MessagingGroup messagingGroup = mGroups.getLast();
+            MessagingImageMessage isolatedMessage = messagingGroup.getIsolatedMessage();
+            if (isolatedMessage != null) {
+                return isolatedMessage.getView();
+            }
+        }
+        return null;
+    }
+
     public void bindFacePile(ImageView bottomBackground, ImageView bottomView, ImageView topView) {
         applyNotificationBackgroundColor(bottomBackground);
         // Let's find the two last conversations:
@@ -841,6 +849,10 @@
     }
 
     private void updateAppName() {
+        if (notificationsRedesignTemplates()) {
+            return;
+        }
+
         mAppName.setVisibility(mIsCollapsed ? GONE : VISIBLE);
     }
 
@@ -1533,6 +1545,10 @@
     }
 
     private void updateExpandButton() {
+        if (notificationsRedesignTemplates()) {
+            return;
+        }
+
         int buttonGravity;
         ViewGroup newContainer;
         if (mIsCollapsed) {
@@ -1565,6 +1581,10 @@
     }
 
     private void updateContentEndPaddings() {
+        if (notificationsRedesignTemplates()) {
+            return;
+        }
+
         // Let's make sure the conversation header can't run into the expand button when we're
         // collapsed and update the paddings of the content
         int headerPaddingEnd;
@@ -1593,6 +1613,10 @@
     }
 
     private void onAppNameVisibilityChanged() {
+        if (notificationsRedesignTemplates()) {
+            return;
+        }
+
         boolean appNameGone = mAppName.getVisibility() == GONE;
         if (appNameGone != mAppNameGone) {
             mAppNameGone = appNameGone;
@@ -1601,10 +1625,18 @@
     }
 
     private void updateAppNameDividerVisibility() {
+        if (notificationsRedesignTemplates()) {
+            return;
+        }
+
         mAppNameDivider.setVisibility(mAppNameGone ? GONE : VISIBLE);
     }
 
     public void updateExpandability(boolean expandable, @Nullable OnClickListener onClickListener) {
+        if (notificationsRedesignTemplates()) {
+            return;
+        }
+
         mExpandable = expandable;
         if (expandable) {
             mExpandButtonContainer.setVisibility(VISIBLE);
diff --git a/core/java/com/android/internal/widget/MessagingGroup.java b/core/java/com/android/internal/widget/MessagingGroup.java
index 31d9770..b9a603cc 100644
--- a/core/java/com/android/internal/widget/MessagingGroup.java
+++ b/core/java/com/android/internal/widget/MessagingGroup.java
@@ -449,12 +449,8 @@
     }
 
     private void updateIconVisibility() {
-        if (Flags.notificationsRedesignTemplates() && !mIsInConversation) {
-            // We don't show any icon (other than the app icon) in the collapsed form. For
-            // conversations, keeping this container helps with aligning the message to the icon
-            // when collapsed, but the old messaging style already has this alignment built into
-            // the template like all other layouts. Conversations are special because we use the
-            // same base layout for both the collapsed and expanded views.
+        if (Flags.notificationsRedesignTemplates()) {
+            // We don't show any icon (other than the app or person icon) in the collapsed form.
             mMessagingIconContainer.setVisibility(mSingleLine ? GONE : VISIBLE);
         }
     }
diff --git a/core/java/com/android/internal/widget/MessagingLayout.java b/core/java/com/android/internal/widget/MessagingLayout.java
index 64b44df..eb22e7c 100644
--- a/core/java/com/android/internal/widget/MessagingLayout.java
+++ b/core/java/com/android/internal/widget/MessagingLayout.java
@@ -337,19 +337,10 @@
     }
 
     private void updateImageMessages() {
-        View newMessage = null;
         if (mImageMessageContainer == null) {
             return;
         }
-        if (mIsCollapsed && !mGroups.isEmpty()) {
-            // When collapsed, we're displaying the image message in a dedicated container
-            // on the right of the layout instead of inline. Let's add the isolated image there
-            MessagingGroup messagingGroup = mGroups.getLast();
-            MessagingImageMessage isolatedMessage = messagingGroup.getIsolatedMessage();
-            if (isolatedMessage != null) {
-                newMessage = isolatedMessage.getView();
-            }
-        }
+        View newMessage = getNewImageMessage();
         // Remove all messages that don't belong into the image layout
         View previousMessage = mImageMessageContainer.getChildAt(0);
         if (previousMessage != newMessage) {
@@ -368,6 +359,20 @@
         }
     }
 
+    @Nullable
+    private View getNewImageMessage() {
+        if (mIsCollapsed && !mGroups.isEmpty()) {
+            // When collapsed, we're displaying the image message in a dedicated container
+            // on the right of the layout instead of inline. Let's add the isolated image there
+            MessagingGroup messagingGroup = mGroups.getLast();
+            MessagingImageMessage isolatedMessage = messagingGroup.getIsolatedMessage();
+            if (isolatedMessage != null) {
+                return isolatedMessage.getView();
+            }
+        }
+        return null;
+    }
+
     private void removeGroups(ArrayList<MessagingGroup> oldGroups) {
         int size = oldGroups.size();
         for (int i = 0; i < size; i++) {
diff --git a/core/jni/Android.bp b/core/jni/Android.bp
index 06702e2..92a841f 100644
--- a/core/jni/Android.bp
+++ b/core/jni/Android.bp
@@ -76,6 +76,9 @@
     srcs: [
         "android_animation_PropertyValuesHolder.cpp",
         "android_content_res_ApkAssets.cpp",
+        "android_media_ImageReader.cpp",
+        "android_media_PublicFormatUtils.cpp",
+        "android_media_Utils.cpp",
         "android_os_SystemClock.cpp",
         "android_os_SystemProperties.cpp",
         "android_text_AndroidCharacter.cpp",
@@ -134,10 +137,7 @@
                 "android_app_ActivityThread.cpp",
                 "android_app_NativeActivity.cpp",
                 "android_app_admin_SecurityLog.cpp",
-                "android_media_ImageReader.cpp",
                 "android_media_ImageWriter.cpp",
-                "android_media_PublicFormatUtils.cpp",
-                "android_media_Utils.cpp",
                 "android_opengl_EGL14.cpp",
                 "android_opengl_EGL15.cpp",
                 "android_opengl_EGLExt.cpp",
@@ -489,6 +489,7 @@
                 "libsqlite",
                 "libgui_window_info_static",
                 "libbinder",
+                "libbinder_ndk",
                 "libhidlbase", // libhwbinder is in here
             ],
             version_script: "platform/linux/libandroid_runtime_export.txt",
diff --git a/core/jni/android_graphics_BLASTBufferQueue.cpp b/core/jni/android_graphics_BLASTBufferQueue.cpp
index a526783..1a7490e6 100644
--- a/core/jni/android_graphics_BLASTBufferQueue.cpp
+++ b/core/jni/android_graphics_BLASTBufferQueue.cpp
@@ -26,6 +26,7 @@
 #include <nativehelper/JNIHelp.h>
 #include <utils/Log.h>
 #include <utils/RefBase.h>
+#include <utils/StrongPointer.h>
 
 #include "core_jni_helpers.h"
 
@@ -124,7 +125,7 @@
 static jlong nativeCreate(JNIEnv* env, jclass clazz, jstring jName,
                           jboolean updateDestinationFrame) {
     ScopedUtfChars name(env, jName);
-    sp<BLASTBufferQueue> queue = new BLASTBufferQueue(name.c_str(), updateDestinationFrame);
+    sp<BLASTBufferQueue> queue = sp<BLASTBufferQueue>::make(name.c_str(), updateDestinationFrame);
     queue->incStrong((void*)nativeCreate);
     return reinterpret_cast<jlong>(queue.get());
 }
diff --git a/core/jni/android_media_ImageReader.cpp b/core/jni/android_media_ImageReader.cpp
index 20b9c57..a34cb39 100644
--- a/core/jni/android_media_ImageReader.cpp
+++ b/core/jni/android_media_ImageReader.cpp
@@ -25,13 +25,22 @@
 #include <android_runtime/android_view_Surface.h>
 #include <com_android_graphics_libgui_flags.h>
 #include <cutils/atomic.h>
+#ifdef __ANDROID__
 #include <grallocusage/GrallocUsageConversion.h>
+#else
+#define GRALLOC_USAGE_PROTECTED 0
+#define GRALLOC_USAGE_SW_READ_OFTEN 0
+#define GRALLOC_USAGE_SW_WRITE_OFTEN 0
+#endif
 #include <gui/BufferItemConsumer.h>
 #include <gui/Surface.h>
 #include <inttypes.h>
 #include <jni.h>
+#include <jni_wrappers.h>
 #include <nativehelper/JNIHelp.h>
+#ifdef __ANDROID__
 #include <private/android/AHardwareBufferHelpers.h>
+#endif
 #include <stdint.h>
 #include <ui/Rect.h>
 #include <utils/List.h>
@@ -393,8 +402,12 @@
     String8 consumerName = String8::format("ImageReader-%dx%df%xm%d-%d-%d",
             width, height, nativeHalFormat, maxImages, getpid(),
             createProcessUniqueId());
+#ifdef __ANDROID__
     uint64_t consumerUsage =
             android_hardware_HardwareBuffer_convertToGrallocUsageBits(ndkUsage);
+#else
+    uint64_t consumerUsage = 0;
+#endif
 
 #if COM_ANDROID_GRAPHICS_LIBGUI_FLAGS(WB_CONSUMER_BASE_OWNS_BQ)
     sp<BufferItemConsumer> bufferConsumer = new BufferItemConsumer(consumerUsage, maxImages,
@@ -773,6 +786,7 @@
     return true;
 }
 
+#ifdef __ANDROID__
 static void ImageReader_unlockGraphicBuffer(JNIEnv* env, jobject /*thiz*/,
         jobject buffer) {
     sp<GraphicBuffer> graphicBuffer =
@@ -856,6 +870,7 @@
 
     return imagePlanes;
 }
+#endif
 
 static jobjectArray Image_createSurfacePlanes(JNIEnv* env, jobject thiz,
         int numPlanes, int halReaderFormat, uint64_t ndkReaderUsage)
@@ -964,6 +979,7 @@
     }
 }
 
+#ifdef __ANDROID__
 static jobject Image_getHardwareBuffer(JNIEnv* env, jobject thiz) {
     BufferItem* buffer = Image_getBufferItem(env, thiz);
     if (buffer == nullptr) {
@@ -976,45 +992,48 @@
     // to link against libandroid.so
     return android_hardware_HardwareBuffer_createFromAHardwareBuffer(env, b);
 }
+#endif
 
 } // extern "C"
 
 // ----------------------------------------------------------------------------
 
-static const JNINativeMethod gImageReaderMethods[] = {
-    {"nativeClassInit",        "()V",                        (void*)ImageReader_classInit },
-    {"nativeInit",             "(Ljava/lang/Object;IIIJII)V",   (void*)ImageReader_init },
-    {"nativeClose",            "()V",                        (void*)ImageReader_close },
-    {"nativeReleaseImage",     "(Landroid/media/Image;)V",   (void*)ImageReader_imageRelease },
-    {"nativeImageSetup",       "(Landroid/media/Image;)I",   (void*)ImageReader_imageSetup },
-    {"nativeGetSurface",       "()Landroid/view/Surface;",   (void*)ImageReader_getSurface },
-    {"nativeDetachImage",      "(Landroid/media/Image;Z)I",   (void*)ImageReader_detachImage },
-    {"nativeCreateImagePlanes",
-        "(ILandroid/graphics/GraphicBuffer;IIIIII)[Landroid/media/ImageReader$ImagePlane;",
-                                                             (void*)ImageReader_createImagePlanes },
-    {"nativeUnlockGraphicBuffer",
-        "(Landroid/graphics/GraphicBuffer;)V",             (void*)ImageReader_unlockGraphicBuffer },
-    {"nativeDiscardFreeBuffers", "()V",                      (void*)ImageReader_discardFreeBuffers }
-};
+static const JNINativeMethod gImageReaderMethods[] =
+        {{"nativeClassInit", "()V", (void*)ImageReader_classInit},
+         {"nativeInit", "(Ljava/lang/Object;IIIJII)V", (void*)ImageReader_init},
+         {"nativeClose", "()V", (void*)ImageReader_close},
+         {"nativeReleaseImage", "(Landroid/media/Image;)V", (void*)ImageReader_imageRelease},
+         {"nativeImageSetup", "(Landroid/media/Image;)I", (void*)ImageReader_imageSetup},
+         {"nativeGetSurface", "()Landroid/view/Surface;", (void*)ImageReader_getSurface},
+         {"nativeDetachImage", "(Landroid/media/Image;Z)I", (void*)ImageReader_detachImage},
+#ifdef __ANDROID__
+         {"nativeCreateImagePlanes",
+          "(ILandroid/graphics/GraphicBuffer;IIIIII)[Landroid/media/ImageReader$ImagePlane;",
+          (void*)ImageReader_createImagePlanes},
+         {"nativeUnlockGraphicBuffer", "(Landroid/graphics/GraphicBuffer;)V",
+          (void*)ImageReader_unlockGraphicBuffer},
+#endif
+         {"nativeDiscardFreeBuffers", "()V", (void*)ImageReader_discardFreeBuffers}};
 
 static const JNINativeMethod gImageMethods[] = {
-    {"nativeCreatePlanes",      "(IIJ)[Landroid/media/ImageReader$SurfaceImage$SurfacePlane;",
-                                                             (void*)Image_createSurfacePlanes },
-    {"nativeGetWidth",          "()I",                       (void*)Image_getWidth },
-    {"nativeGetHeight",         "()I",                       (void*)Image_getHeight },
-    {"nativeGetFormat",         "(I)I",                      (void*)Image_getFormat },
-    {"nativeGetFenceFd",        "()I",                       (void*)Image_getFenceFd },
-    {"nativeGetHardwareBuffer", "()Landroid/hardware/HardwareBuffer;",
-                                                             (void*)Image_getHardwareBuffer },
+        {"nativeCreatePlanes", "(IIJ)[Landroid/media/ImageReader$SurfaceImage$SurfacePlane;",
+         (void*)Image_createSurfacePlanes},
+        {"nativeGetWidth", "()I", (void*)Image_getWidth},
+        {"nativeGetHeight", "()I", (void*)Image_getHeight},
+        {"nativeGetFormat", "(I)I", (void*)Image_getFormat},
+        {"nativeGetFenceFd", "()I", (void*)Image_getFenceFd},
+#ifdef __ANDROID__
+        {"nativeGetHardwareBuffer", "()Landroid/hardware/HardwareBuffer;",
+         (void*)Image_getHardwareBuffer},
+#endif
 };
 
 int register_android_media_ImageReader(JNIEnv *env) {
+    int ret1 = RegisterMethodsOrDie(env, "android/media/ImageReader", gImageReaderMethods,
+                                    NELEM(gImageReaderMethods));
 
-    int ret1 = AndroidRuntime::registerNativeMethods(env,
-                   "android/media/ImageReader", gImageReaderMethods, NELEM(gImageReaderMethods));
-
-    int ret2 = AndroidRuntime::registerNativeMethods(env,
-                   "android/media/ImageReader$SurfaceImage", gImageMethods, NELEM(gImageMethods));
+    int ret2 = RegisterMethodsOrDie(env, "android/media/ImageReader$SurfaceImage", gImageMethods,
+                                    NELEM(gImageMethods));
 
     return (ret1 || ret2);
 }
diff --git a/core/jni/android_media_PublicFormatUtils.cpp b/core/jni/android_media_PublicFormatUtils.cpp
index 04494ad..bcdf654 100644
--- a/core/jni/android_media_PublicFormatUtils.cpp
+++ b/core/jni/android_media_PublicFormatUtils.cpp
@@ -16,10 +16,10 @@
 
 #define LOG_TAG "PublicFormatUtils_JNI"
 
-#include <utils/misc.h>
-#include <ui/PublicFormat.h>
-#include <android_runtime/AndroidRuntime.h>
 #include <jni.h>
+#include <jni_wrappers.h>
+#include <ui/PublicFormat.h>
+#include <utils/misc.h>
 
 using namespace android;
 
@@ -53,7 +53,6 @@
 };
 
 int register_android_media_PublicFormatUtils(JNIEnv *env) {
-    return AndroidRuntime::registerNativeMethods(env,
-             "android/media/PublicFormatUtils", gMethods, NELEM(gMethods));
+    return RegisterMethodsOrDie(env, "android/media/PublicFormatUtils", gMethods, NELEM(gMethods));
 }
 
diff --git a/core/jni/android_media_Utils.cpp b/core/jni/android_media_Utils.cpp
index e8f8644..e484141 100644
--- a/core/jni/android_media_Utils.cpp
+++ b/core/jni/android_media_Utils.cpp
@@ -19,10 +19,12 @@
 
 #include "android_media_Utils.h"
 
+#ifdef __ANDROID__ // Layoutlib does not support hardware
 #include <aidl/android/hardware/graphics/common/PixelFormat.h>
 #include <aidl/android/hardware/graphics/common/PlaneLayoutComponentType.h>
 #include <ui/GraphicBufferMapper.h>
 #include <ui/GraphicTypes.h>
+#endif
 #include <utils/Log.h>
 
 #define ALIGN(x, mask) ( ((x) + (mask) - 1) & ~((mask) - 1) )
@@ -34,7 +36,13 @@
 
 // -----------Utility functions used by ImageReader/Writer JNI-----------------
 
+#ifdef __ANDROID__
 using AidlPixelFormat = aidl::android::hardware::graphics::common::PixelFormat;
+#else
+namespace AidlPixelFormat {
+const int32_t YCBCR_P210 = 60;
+}
+#endif
 
 enum {
     IMAGE_MAX_NUM_PLANES = 3,
@@ -517,6 +525,7 @@
     return OK;
 }
 
+#ifdef __ANDROID__
 static status_t extractP010Gralloc4PlaneLayout(
         sp<GraphicBuffer> buffer, void *pData, int format, LockedImage *outputImage) {
     using aidl::android::hardware::graphics::common::PlaneLayoutComponent;
@@ -663,6 +672,7 @@
     outputImage->chromaStep = 4;
     return OK;
 }
+#endif
 
 status_t lockImageFromBuffer(sp<GraphicBuffer> buffer, uint32_t inUsage,
         const Rect& rect, int fenceFd, LockedImage* outputImage) {
@@ -701,6 +711,7 @@
             ALOGE("Lock buffer failed!");
             return res;
         }
+#ifdef __ANDROID__
         if (isPossibly10BitYUV(format)) {
             if (format == HAL_PIXEL_FORMAT_YCBCR_P010 &&
                 OK == extractP010Gralloc4PlaneLayout(buffer, pData, format, outputImage)) {
@@ -713,6 +724,7 @@
                 return OK;
             }
         }
+#endif
     }
 
     outputImage->data = reinterpret_cast<uint8_t*>(pData);
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/core/jni/com_android_internal_os_LongArrayMultiStateCounter.cpp b/core/jni/com_android_internal_os_LongArrayMultiStateCounter.cpp
index 8f36ecb..7d94ef8 100644
--- a/core/jni/com_android_internal_os_LongArrayMultiStateCounter.cpp
+++ b/core/jni/com_android_internal_os_LongArrayMultiStateCounter.cpp
@@ -116,17 +116,26 @@
     counter->reset();
 }
 
-static void native_getCounts(JNIEnv *env, jclass, jlong nativePtr, jlongArray values, jint state) {
+static bool native_getCounts(JNIEnv *env, jclass, jlong nativePtr, jlongArray values, jint state) {
     auto *counter = reinterpret_cast<LongArrayMultiStateCounter *>(nativePtr);
-    ScopedLongArrayRW scopedArray(env, values);
     auto *data = counter->getCount(state).data();
-    auto size = env->GetArrayLength(values);
-    auto *outData = scopedArray.get();
     if (data == nullptr) {
-        memset(outData, 0, size * sizeof(uint64_t));
-    } else {
-        memcpy(outData, data, size * sizeof(uint64_t));
+        return false;
     }
+    auto size = env->GetArrayLength(values);
+    bool allZeros = true;
+    for (int i = 0; i < size; i++) {
+        if (data[i]) {
+            allZeros = false;
+            break;
+        }
+    }
+    if (allZeros) {
+        return false;
+    }
+    ScopedLongArrayRW scopedArray(env, values);
+    memcpy(scopedArray.get(), data, size * sizeof(uint64_t));
+    return true;
 }
 
 static jobject native_toString(JNIEnv *env, jclass, jlong nativePtr) {
@@ -255,7 +264,7 @@
         // @CriticalNative
         {"native_reset", "(J)V", (void *)native_reset},
         // @FastNative
-        {"native_getCounts", "(J[JI)V", (void *)native_getCounts},
+        {"native_getCounts", "(J[JI)Z", (void *)native_getCounts},
         // @FastNative
         {"native_toString", "(J)Ljava/lang/String;", (void *)native_toString},
         // @FastNative
diff --git a/core/jni/jni_wrappers.h b/core/jni/jni_wrappers.h
index e3e17ee..1f44994 100644
--- a/core/jni/jni_wrappers.h
+++ b/core/jni/jni_wrappers.h
@@ -22,6 +22,8 @@
 #include <log/log.h>
 #include <nativehelper/JNIHelp.h>
 
+#include <string>
+
 namespace android {
 
 static inline jclass FindClassOrDie(JNIEnv* env, const char* class_name) {
diff --git a/core/jni/platform/host/HostRuntime.cpp b/core/jni/platform/host/HostRuntime.cpp
index 1a03283..746740b 100644
--- a/core/jni/platform/host/HostRuntime.cpp
+++ b/core/jni/platform/host/HostRuntime.cpp
@@ -48,6 +48,8 @@
  * (see AndroidRuntime.cpp).
  */
 
+extern int register_android_media_ImageReader(JNIEnv* env);
+extern int register_android_media_PublicFormatUtils(JNIEnv* env);
 extern int register_android_os_Binder(JNIEnv* env);
 extern int register_libcore_util_NativeAllocationRegistry(JNIEnv* env);
 
@@ -126,6 +128,10 @@
         {"android.database.sqlite.SQLiteDebug", REG_JNI(register_android_database_SQLiteDebug)},
         {"android.database.sqlite.SQLiteRawStatement",
          REG_JNI(register_android_database_SQLiteRawStatement)},
+#endif
+        {"android.media.ImageReader", REG_JNI(register_android_media_ImageReader)},
+        {"android.media.PublicFormatUtils", REG_JNI(register_android_media_PublicFormatUtils)},
+#ifdef __linux__
         {"android.os.Binder", REG_JNI(register_android_os_Binder)},
         {"android.os.FileObserver", REG_JNI(register_android_os_FileObserver)},
         {"android.os.MessageQueue", REG_JNI(register_android_os_MessageQueue)},
diff --git a/core/res/AndroidManifest.xml b/core/res/AndroidManifest.xml
index 78526ad..ee6899c 100644
--- a/core/res/AndroidManifest.xml
+++ b/core/res/AndroidManifest.xml
@@ -7759,7 +7759,17 @@
          @FlaggedApi("com.android.server.telecom.flags.telecom_resolve_hidden_dependencies")
          @hide -->
     <permission android:name="android.permission.READ_BLOCKED_NUMBERS"
-                android:protectionLevel="signature" />
+                android:protectionLevel="signature"
+                android:featureFlag="!android.permission.flags.grant_read_blocked_numbers_to_system_ui_intelligence" />
+
+    <!-- Allows the holder to read blocked numbers. See
+         {@link android.provider.BlockedNumberContract}.
+         @SystemApi
+         @FlaggedApi("com.android.server.telecom.flags.telecom_resolve_hidden_dependencies")
+         @hide -->
+    <permission android:name="android.permission.READ_BLOCKED_NUMBERS"
+                android:protectionLevel="signature|role"
+                android:featureFlag="android.permission.flags.grant_read_blocked_numbers_to_system_ui_intelligence" />
 
     <!-- Allows the holder to write blocked numbers. See
          {@link android.provider.BlockedNumberContract}.
diff --git a/core/res/res/layout/notification_2025_conversation_header.xml b/core/res/res/layout/notification_2025_conversation_header.xml
index 1bde173..68096f8 100644
--- a/core/res/res/layout/notification_2025_conversation_header.xml
+++ b/core/res/res/layout/notification_2025_conversation_header.xml
@@ -1,6 +1,6 @@
 <?xml version="1.0" encoding="utf-8"?>
 <!--
-  ~ Copyright (C) 2024 The Android Open Source Project
+  ~ Copyright (C) 2025 The Android Open Source Project
   ~
   ~ Licensed under the Apache License, Version 2.0 (the "License");
   ~ you may not use this file except in compliance with the License.
@@ -15,157 +15,74 @@
   ~ limitations under the License
   -->
 
-<com.android.internal.widget.ConversationHeaderLinearLayout
+<!-- extends RelativeLayout -->
+<NotificationHeaderView
     xmlns:android="http://schemas.android.com/apk/res/android"
-    android:id="@+id/conversation_header"
-    android:layout_width="wrap_content"
-    android:layout_height="wrap_content"
+    android:id="@+id/notification_header"
+    android:layout_width="match_parent"
+    android:layout_height="@dimen/notification_2025_header_height"
+    android:clipChildren="false"
+    android:gravity="center_vertical"
     android:orientation="horizontal"
-    android:paddingTop="@dimen/notification_2025_margin"
+    android:theme="@style/Theme.DeviceDefault.Notification"
+    android:importantForAccessibility="no"
     >
 
-    <TextView
-        android:id="@+id/conversation_text"
-        android:layout_width="wrap_content"
-        android:layout_height="wrap_content"
-        android:textAppearance="@style/TextAppearance.DeviceDefault.Notification.Title"
-        android:textSize="16sp"
-        android:singleLine="true"
-        android:layout_weight="1"
-        />
-
-    <TextView
-        android:id="@+id/app_name_divider"
-        android:layout_width="wrap_content"
-        android:layout_height="wrap_content"
-        android:textAppearance="@style/TextAppearance.DeviceDefault.Notification.Info"
-        android:layout_marginStart="@dimen/notification_conversation_header_separating_margin"
-        android:text="@string/notification_header_divider_symbol"
-        android:singleLine="true"
-        android:visibility="gone"
-        />
-
-    <!-- App Name -->
-    <com.android.internal.widget.ObservableTextView
-        android:id="@+id/app_name_text"
-        android:textAppearance="@style/TextAppearance.DeviceDefault.Notification.Info"
-        android:layout_width="wrap_content"
-        android:layout_height="wrap_content"
-        android:layout_marginStart="@dimen/notification_conversation_header_separating_margin"
-        android:singleLine="true"
-        android:visibility="gone"
-        />
-
-    <TextView
-        android:id="@+id/time_divider"
-        android:layout_width="wrap_content"
-        android:layout_height="wrap_content"
-        android:textAppearance="@style/TextAppearance.DeviceDefault.Notification.Info"
-        android:layout_marginStart="@dimen/notification_conversation_header_separating_margin"
-        android:text="@string/notification_header_divider_symbol"
-        android:singleLine="true"
-        android:visibility="gone"
-        />
-
-    <DateTimeView
-        android:id="@+id/time"
-        android:textAppearance="@style/TextAppearance.DeviceDefault.Notification.Time"
-        android:layout_width="wrap_content"
-        android:layout_height="wrap_content"
-        android:layout_marginStart="@dimen/notification_conversation_header_separating_margin"
-        android:showRelative="true"
-        android:singleLine="true"
-        android:visibility="gone"
-        />
-
-    <ViewStub
-        android:id="@+id/chronometer"
-        android:layout_width="wrap_content"
-        android:layout_height="wrap_content"
-        android:layout_marginStart="@dimen/notification_conversation_header_separating_margin"
-        android:layout="@layout/notification_template_part_chronometer"
-        android:visibility="gone"
-        />
-
-    <TextView
-        android:id="@+id/verification_divider"
-        android:layout_width="wrap_content"
-        android:layout_height="wrap_content"
-        android:textAppearance="@style/TextAppearance.DeviceDefault.Notification.Info"
-        android:layout_marginStart="@dimen/notification_conversation_header_separating_margin"
-        android:text="@string/notification_header_divider_symbol"
-        android:singleLine="true"
-        android:visibility="gone"
-        />
-
     <ImageView
-        android:id="@+id/verification_icon"
-        android:layout_width="@dimen/notification_verification_icon_size"
-        android:layout_height="@dimen/notification_verification_icon_size"
-        android:layout_marginStart="@dimen/notification_conversation_header_separating_margin"
-        android:baseline="10dp"
-        android:scaleType="fitCenter"
-        android:src="@drawable/ic_notifications_alerted"
+        android:id="@+id/left_icon"
+        android:layout_width="@dimen/notification_2025_left_icon_size"
+        android:layout_height="@dimen/notification_2025_left_icon_size"
+        android:layout_alignParentStart="true"
+        android:layout_margin="@dimen/notification_2025_margin"
+        android:background="@drawable/notification_large_icon_outline"
+        android:clipToOutline="true"
+        android:importantForAccessibility="no"
+        android:scaleType="centerCrop"
         android:visibility="gone"
         />
 
-    <TextView
-        android:id="@+id/verification_text"
-        android:textAppearance="@style/TextAppearance.DeviceDefault.Notification.Info"
+    <include layout="@layout/notification_2025_conversation_icon_container" />
+
+    <!-- extends ViewGroup -->
+    <NotificationTopLineView
+        xmlns:android="http://schemas.android.com/apk/res/android"
+        android:id="@+id/notification_top_line"
         android:layout_width="wrap_content"
         android:layout_height="wrap_content"
-        android:layout_marginStart="@dimen/notification_conversation_header_separating_margin"
-        android:layout_weight="100"
-        android:showRelative="true"
-        android:singleLine="true"
-        android:visibility="gone"
+        android:layout_alignParentStart="true"
+        android:layout_toStartOf="@id/expand_button"
+        android:layout_alignWithParentIfMissing="true"
+        android:layout_marginVertical="@dimen/notification_2025_margin"
+        android:clipChildren="false"
+        android:gravity="center_vertical"
+        android:paddingStart="@dimen/notification_2025_content_margin_start"
+        android:theme="@style/Theme.DeviceDefault.Notification"
+        >
+
+        <include layout="@layout/notification_2025_top_line_views" />
+
+    </NotificationTopLineView>
+
+    <FrameLayout
+        android:id="@+id/alternate_expand_target"
+        android:layout_width="@dimen/notification_2025_content_margin_start"
+        android:layout_height="match_parent"
+        android:layout_alignParentStart="true"
+        android:importantForAccessibility="no"
+        android:focusable="false"
         />
 
-    <ImageButton
-        android:id="@+id/feedback"
-        android:layout_width="@dimen/notification_feedback_size"
-        android:layout_height="@dimen/notification_feedback_size"
-        android:layout_marginStart="@dimen/notification_header_separating_margin"
-        android:background="?android:selectableItemBackgroundBorderless"
-        android:contentDescription="@string/notification_feedback_indicator"
-        android:baseline="13dp"
-        android:scaleType="fitCenter"
-        android:src="@drawable/ic_feedback_indicator"
-        android:visibility="gone"
-        />
+    <include layout="@layout/notification_2025_expand_button"
+        android:layout_width="wrap_content"
+        android:layout_height="wrap_content"
+        android:layout_gravity="top|end"
+        android:layout_alignParentEnd="true" />
 
-    <ImageView
-        android:id="@+id/phishing_alert"
-        android:layout_width="@dimen/notification_2025_badge_size"
-        android:layout_height="@dimen/notification_2025_badge_size"
-        android:layout_marginStart="@dimen/notification_2025_badge_margin"
-        android:baseline="@dimen/notification_2025_badge_baseline"
-        android:scaleType="fitCenter"
-        android:src="@drawable/ic_dialog_alert_material"
-        android:visibility="gone"
-        android:contentDescription="@string/notification_phishing_alert_content_description"
-        />
+    <include layout="@layout/notification_close_button"
+        android:id="@+id/close_button"
+        android:layout_width="@dimen/notification_close_button_size"
+        android:layout_height="@dimen/notification_close_button_size"
+        android:layout_alignParentTop="true"
+        android:layout_alignParentEnd="true" />
 
-    <ImageView
-        android:id="@+id/profile_badge"
-        android:layout_width="@dimen/notification_2025_badge_size"
-        android:layout_height="@dimen/notification_2025_badge_size"
-        android:layout_marginStart="@dimen/notification_2025_badge_margin"
-        android:baseline="@dimen/notification_2025_badge_baseline"
-        android:scaleType="fitCenter"
-        android:visibility="gone"
-        android:contentDescription="@string/notification_work_profile_content_description"
-        />
-
-    <ImageView
-        android:id="@+id/alerted_icon"
-        android:layout_width="@dimen/notification_2025_badge_size"
-        android:layout_height="@dimen/notification_2025_badge_size"
-        android:layout_marginStart="@dimen/notification_2025_badge_margin"
-        android:baseline="@dimen/notification_2025_badge_baseline"
-        android:contentDescription="@string/notification_alerted_content_description"
-        android:scaleType="fitCenter"
-        android:src="@drawable/ic_notifications_alerted"
-        android:visibility="gone"
-        />
-</com.android.internal.widget.ConversationHeaderLinearLayout>
+</NotificationHeaderView>
diff --git a/core/res/res/layout/notification_2025_template_collapsed_call.xml b/core/res/res/layout/notification_2025_template_collapsed_call.xml
index 6f3c15a..ee691e4 100644
--- a/core/res/res/layout/notification_2025_template_collapsed_call.xml
+++ b/core/res/res/layout/notification_2025_template_collapsed_call.xml
@@ -1,5 +1,5 @@
 <?xml version="1.0" encoding="utf-8"?><!--
-  ~ Copyright (C) 2024 The Android Open Source Project
+  ~ Copyright (C) 2025 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.
@@ -25,55 +25,177 @@
     android:theme="@style/Theme.DeviceDefault.Notification"
     >
 
-    <!-- CallLayout shares visual appearance with ConversationLayout, so shares layouts -->
-    <include layout="@layout/notification_2025_conversation_icon_container" />
-
     <LinearLayout
-        xmlns:android="http://schemas.android.com/apk/res/android"
         android:layout_width="match_parent"
         android:layout_height="wrap_content"
-        android:minHeight="@dimen/notification_2025_min_height"
-        android:orientation="horizontal"
+        android:clipChildren="false"
+        android:orientation="vertical"
         >
 
-        <LinearLayout
-            android:id="@+id/notification_main_column"
+        <com.android.internal.widget.NotificationMaxHeightFrameLayout
             android:layout_width="match_parent"
             android:layout_height="wrap_content"
-            android:layout_weight="1"
-            android:layout_marginStart="@dimen/notification_2025_content_margin_start"
-            android:orientation="vertical"
-            android:paddingBottom="@dimen/notification_2025_margin"
+            android:minHeight="@dimen/notification_2025_min_height"
+            android:clipChildren="false"
             >
 
-            <include
-                layout="@layout/notification_2025_conversation_header"
-                android:layout_width="wrap_content"
-                android:layout_height="wrap_content"
+            <ImageView
+                android:id="@+id/left_icon"
+                android:layout_width="@dimen/notification_2025_left_icon_size"
+                android:layout_height="@dimen/notification_2025_left_icon_size"
+                android:layout_alignParentStart="true"
+                android:layout_margin="@dimen/notification_2025_margin"
+                android:background="@drawable/notification_large_icon_outline"
+                android:clipToOutline="true"
+                android:importantForAccessibility="no"
+                android:scaleType="centerCrop"
+                android:visibility="gone"
                 />
 
-            <include layout="@layout/notification_template_text"
-                android:layout_height="wrap_content"
-                android:minHeight="@dimen/notification_text_height"
+            <!-- CallLayout shares visual appearance with ConversationLayout, so shares layouts -->
+            <include layout="@layout/notification_2025_conversation_icon_container" />
+
+            <FrameLayout
+                android:id="@+id/alternate_expand_target"
+                android:layout_width="@dimen/notification_2025_content_margin_start"
+                android:layout_height="match_parent"
+                android:layout_gravity="start"
+                android:importantForAccessibility="no"
+                android:focusable="false"
                 />
 
+            <LinearLayout
+                android:id="@+id/notification_headerless_view_row"
+                android:layout_width="match_parent"
+                android:layout_height="match_parent"
+                android:layout_marginStart="@dimen/notification_2025_content_margin_start"
+                android:orientation="horizontal"
+                android:clipChildren="false"
+                >
+
+                <LinearLayout
+                    android:id="@+id/notification_headerless_view_column"
+                    android:layout_width="0px"
+                    android:layout_height="wrap_content"
+                    android:layout_gravity="center_vertical"
+                    android:layout_weight="1"
+                    android:layout_marginBottom="@dimen/notification_2025_margin"
+                    android:layout_marginTop="@dimen/notification_2025_margin"
+                    android:clipChildren="false"
+                    android:orientation="vertical"
+                    >
+
+                    <NotificationTopLineView
+                        android:id="@+id/notification_top_line"
+                        android:layout_width="wrap_content"
+                        android:layout_height="wrap_content"
+                        android:minHeight="@dimen/notification_headerless_line_height"
+                        android:clipChildren="false"
+                        android:theme="@style/Theme.DeviceDefault.Notification"
+                        >
+
+                        <!--
+                        NOTE: The notification_2025_top_line_views layout contains the app_name_text.
+                        In order to include the title view at the beginning, the Notification.Builder
+                        has logic to hide that view whenever this title view is to be visible.
+                        -->
+
+                        <TextView
+                            android:id="@+id/title"
+                            android:layout_width="wrap_content"
+                            android:layout_height="wrap_content"
+                            android:layout_marginEnd="@dimen/notification_header_separating_margin"
+                            android:ellipsize="end"
+                            android:fadingEdge="horizontal"
+                            android:singleLine="true"
+                            android:textAlignment="viewStart"
+                            android:textAppearance="@style/TextAppearance.DeviceDefault.Notification.Title"
+                            />
+
+                        <include layout="@layout/notification_2025_top_line_views" />
+
+                    </NotificationTopLineView>
+
+                    <LinearLayout
+                        android:id="@+id/notification_main_column"
+                        android:layout_width="match_parent"
+                        android:layout_height="wrap_content"
+                        android:orientation="vertical"
+                        android:clipChildren="false"
+                        >
+                        <com.android.internal.widget.NotificationVanishingFrameLayout
+                            android:layout_width="match_parent"
+                            android:layout_height="wrap_content"
+                            android:minHeight="@dimen/notification_headerless_line_height"
+                            >
+                            <!-- This is the simplest way to keep this text vertically centered without
+                             gravity="center_vertical" which causes jumpiness in expansion animations. -->
+                            <include
+                                layout="@layout/notification_2025_text"
+                                android:layout_width="match_parent"
+                                android:layout_height="@dimen/notification_text_height"
+                                android:layout_gravity="center_vertical"
+                                android:layout_marginTop="0dp"
+                                />
+                        </com.android.internal.widget.NotificationVanishingFrameLayout>
+                    </LinearLayout>
+
+                </LinearLayout>
+
+                <ImageView
+                    android:id="@+id/right_icon"
+                    android:layout_width="@dimen/notification_right_icon_size"
+                    android:layout_height="@dimen/notification_right_icon_size"
+                    android:layout_gravity="center_vertical|end"
+                    android:layout_marginTop="@dimen/notification_right_icon_headerless_margin"
+                    android:layout_marginBottom="@dimen/notification_right_icon_headerless_margin"
+                    android:layout_marginStart="@dimen/notification_right_icon_content_margin"
+                    android:background="@drawable/notification_large_icon_outline"
+                    android:clipToOutline="true"
+                    android:importantForAccessibility="no"
+                    android:scaleType="centerCrop"
+                    />
+
+                <FrameLayout
+                    android:id="@+id/expand_button_touch_container"
+                    android:layout_width="wrap_content"
+                    android:layout_height="match_parent"
+                    android:minWidth="@dimen/notification_content_margin_end"
+                    >
+
+                    <include layout="@layout/notification_2025_expand_button"
+                        android:layout_width="wrap_content"
+                        android:layout_height="wrap_content"
+                        android:layout_gravity="top|end"
+                        />
+
+                </FrameLayout>
+
+            </LinearLayout>
+
+            <include layout="@layout/notification_close_button"
+                android:id="@+id/close_button"
+                android:layout_width="@dimen/notification_close_button_size"
+                android:layout_height="@dimen/notification_close_button_size"
+                android:layout_gravity="top|end" />
+
+        </com.android.internal.widget.NotificationMaxHeightFrameLayout>
+
+        <LinearLayout
+            android:id="@+id/notification_action_list_margin_target"
+            android:layout_width="match_parent"
+            android:layout_height="wrap_content"
+            android:layout_marginTop="-20dp"
+            android:clipChildren="false"
+            android:orientation="vertical">
+            <include layout="@layout/notification_template_smart_reply_container"
+                android:layout_width="match_parent"
+                android:layout_height="wrap_content"
+                android:layout_marginTop="@dimen/notification_content_margin"
+                android:layout_marginStart="@dimen/notification_2025_content_margin_start"
+                android:layout_marginEnd="@dimen/notification_content_margin_end" />
+            <include layout="@layout/notification_material_action_list" />
         </LinearLayout>
-
-        <FrameLayout
-            android:layout_width="wrap_content"
-            android:layout_height="match_parent"
-            android:minWidth="@dimen/notification_content_margin_end"
-            >
-
-            <include
-                layout="@layout/notification_2025_expand_button"
-                android:layout_width="wrap_content"
-                android:layout_height="wrap_content"
-                android:layout_gravity="top|end"
-                />
-
-        </FrameLayout>
-
     </LinearLayout>
 
 </com.android.internal.widget.CallLayout>
diff --git a/core/res/res/layout/notification_2025_template_collapsed_conversation.xml b/core/res/res/layout/notification_2025_template_collapsed_conversation.xml
new file mode 100644
index 0000000..f804111
--- /dev/null
+++ b/core/res/res/layout/notification_2025_template_collapsed_conversation.xml
@@ -0,0 +1,216 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!--
+  ~ Copyright (C) 2025 The Android Open Source Project
+  ~
+  ~ Licensed under the Apache License, Version 2.0 (the "License");
+  ~ you may not use this file except in compliance with the License.
+  ~ You may obtain a copy of the License at
+  ~
+  ~      http://www.apache.org/licenses/LICENSE-2.0
+  ~
+  ~ Unless required by applicable law or agreed to in writing, software
+  ~ distributed under the License is distributed on an "AS IS" BASIS,
+  ~ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+  ~ See the License for the specific language governing permissions and
+  ~ limitations under the License
+  -->
+
+<!-- extends FrameLayout -->
+<com.android.internal.widget.ConversationLayout
+    xmlns:android="http://schemas.android.com/apk/res/android"
+    android:id="@+id/status_bar_latest_event_content"
+    android:layout_width="match_parent"
+    android:layout_height="wrap_content"
+    android:clipChildren="false"
+    android:tag="conversation"
+    >
+
+    <LinearLayout
+        android:layout_width="match_parent"
+        android:layout_height="wrap_content"
+        android:clipChildren="false"
+        android:orientation="vertical"
+        >
+
+
+        <com.android.internal.widget.NotificationMaxHeightFrameLayout
+            android:layout_width="match_parent"
+            android:layout_height="wrap_content"
+            android:minHeight="@dimen/notification_2025_min_height"
+            android:clipChildren="false"
+            >
+
+            <ImageView
+                android:id="@+id/left_icon"
+                android:layout_width="@dimen/notification_2025_left_icon_size"
+                android:layout_height="@dimen/notification_2025_left_icon_size"
+                android:layout_alignParentStart="true"
+                android:layout_margin="@dimen/notification_2025_margin"
+                android:background="@drawable/notification_large_icon_outline"
+                android:clipToOutline="true"
+                android:importantForAccessibility="no"
+                android:scaleType="centerCrop"
+                android:visibility="gone"
+                />
+
+            <include layout="@layout/notification_2025_conversation_icon_container" />
+
+            <FrameLayout
+                android:id="@+id/alternate_expand_target"
+                android:layout_width="@dimen/notification_2025_content_margin_start"
+                android:layout_height="match_parent"
+                android:layout_gravity="start"
+                android:importantForAccessibility="no"
+                android:focusable="false"
+                />
+
+            <!--
+              NOTE: to make the expansion animation of id/notification_messaging happen vertically,
+              its X positioning must be the left edge of the notification, so instead of putting the
+              layout_marginStart on the id/notification_headerless_view_row, we put it on
+              id/notification_top_line, making the layout here just a bit different from the base.
+              -->
+            <LinearLayout
+                android:id="@+id/notification_headerless_view_row"
+                android:layout_width="match_parent"
+                android:layout_height="match_parent"
+                android:orientation="horizontal"
+                android:clipChildren="false"
+                >
+
+                <!--
+                  NOTE: because messaging will always have 2 lines, this LinearLayout should NOT
+                  have the id/notification_headerless_view_column, as that is used for modifying
+                   vertical margins to accommodate the single-line state that base supports
+                  -->
+                <LinearLayout
+                    android:layout_width="0px"
+                    android:layout_height="wrap_content"
+                    android:layout_gravity="center_vertical"
+                    android:layout_weight="1"
+                    android:layout_marginBottom="@dimen/notification_2025_margin"
+                    android:layout_marginTop="@dimen/notification_2025_margin"
+                    android:layout_marginStart="@dimen/notification_2025_content_margin_start"
+                    android:clipChildren="false"
+                    android:orientation="vertical"
+                    >
+
+                    <NotificationTopLineView
+                        android:id="@+id/notification_top_line"
+                        android:layout_width="wrap_content"
+                        android:layout_height="wrap_content"
+                        android:minHeight="@dimen/notification_headerless_line_height"
+                        android:clipChildren="false"
+                        android:theme="@style/Theme.DeviceDefault.Notification"
+                        >
+
+                        <!--
+                        NOTE: The notification_2025_top_line_views layout contains the app_name_text.
+                        In order to include the title view at the beginning, the Notification.Builder
+                        has logic to hide that view whenever this title view is to be visible.
+                        -->
+
+                        <TextView
+                            android:id="@+id/title"
+                            android:layout_width="wrap_content"
+                            android:layout_height="wrap_content"
+                            android:layout_marginEnd="@dimen/notification_header_separating_margin"
+                            android:ellipsize="end"
+                            android:fadingEdge="horizontal"
+                            android:singleLine="true"
+                            android:textAlignment="viewStart"
+                            android:textAppearance="@style/TextAppearance.DeviceDefault.Notification.Title"
+                            />
+
+                        <include layout="@layout/notification_2025_top_line_views" />
+
+                    </NotificationTopLineView>
+
+                    <LinearLayout
+                        android:id="@+id/notification_main_column"
+                        android:layout_width="match_parent"
+                        android:layout_height="wrap_content"
+                        android:orientation="vertical"
+                        android:clipChildren="false"
+                        >
+                        <com.android.internal.widget.MessagingLinearLayout
+                            android:id="@+id/notification_messaging"
+                            android:layout_width="match_parent"
+                            android:layout_height="wrap_content"
+                            android:clipChildren="false"
+                            android:spacing="@dimen/notification_messaging_spacing" />
+                    </LinearLayout>
+
+                </LinearLayout>
+
+                <!-- Images -->
+                <com.android.internal.widget.MessagingLinearLayout
+                    android:id="@+id/conversation_image_message_container"
+                    android:layout_width="@dimen/notification_right_icon_size"
+                    android:layout_height="@dimen/notification_right_icon_size"
+                    android:layout_gravity="center_vertical|end"
+                    android:layout_marginTop="@dimen/notification_right_icon_headerless_margin"
+                    android:layout_marginBottom="@dimen/notification_right_icon_headerless_margin"
+                    android:layout_marginStart="@dimen/notification_right_icon_content_margin"
+                    android:forceHasOverlappingRendering="false"
+                    android:spacing="0dp"
+                    android:clipChildren="false"
+                    android:visibility="gone"
+                    />
+
+                <ImageView
+                    android:id="@+id/right_icon"
+                    android:layout_width="@dimen/notification_right_icon_size"
+                    android:layout_height="@dimen/notification_right_icon_size"
+                    android:layout_gravity="center_vertical|end"
+                    android:layout_marginTop="@dimen/notification_right_icon_headerless_margin"
+                    android:layout_marginBottom="@dimen/notification_right_icon_headerless_margin"
+                    android:layout_marginStart="@dimen/notification_right_icon_content_margin"
+                    android:background="@drawable/notification_large_icon_outline"
+                    android:clipToOutline="true"
+                    android:importantForAccessibility="no"
+                    android:scaleType="centerCrop"
+                    />
+
+                <FrameLayout
+                    android:id="@+id/expand_button_touch_container"
+                    android:layout_width="wrap_content"
+                    android:layout_height="match_parent"
+                    android:minWidth="@dimen/notification_content_margin_end"
+                    >
+
+                    <include layout="@layout/notification_2025_expand_button"
+                        android:layout_width="wrap_content"
+                        android:layout_height="wrap_content"
+                        android:layout_gravity="top|end"
+                        />
+
+                </FrameLayout>
+
+            </LinearLayout>
+
+            <include layout="@layout/notification_close_button"
+                android:id="@+id/close_button"
+                android:layout_width="@dimen/notification_close_button_size"
+                android:layout_height="@dimen/notification_close_button_size"
+                android:layout_gravity="top|end" />
+
+        </com.android.internal.widget.NotificationMaxHeightFrameLayout>
+
+    <LinearLayout
+            android:id="@+id/notification_action_list_margin_target"
+            android:layout_width="match_parent"
+            android:layout_height="wrap_content"
+            android:layout_marginTop="-20dp"
+            android:clipChildren="false"
+            android:orientation="vertical">
+        <include layout="@layout/notification_template_smart_reply_container"
+                android:layout_width="match_parent"
+                android:layout_height="wrap_content"
+                android:layout_marginTop="@dimen/notification_content_margin"
+                android:layout_marginStart="@dimen/notification_2025_content_margin_start"
+                android:layout_marginEnd="@dimen/notification_content_margin_end" />
+        <include layout="@layout/notification_material_action_list" />
+    </LinearLayout>
+</LinearLayout>
+</com.android.internal.widget.ConversationLayout>
diff --git a/core/res/res/layout/notification_2025_template_conversation.xml b/core/res/res/layout/notification_2025_template_conversation.xml
deleted file mode 100644
index 24b6ad0..0000000
--- a/core/res/res/layout/notification_2025_template_conversation.xml
+++ /dev/null
@@ -1,159 +0,0 @@
-<?xml version="1.0" encoding="utf-8"?>
-<!--
-  ~ Copyright (C) 2024 The Android Open Source Project
-  ~
-  ~ Licensed under the Apache License, Version 2.0 (the "License");
-  ~ you may not use this file except in compliance with the License.
-  ~ You may obtain a copy of the License at
-  ~
-  ~      http://www.apache.org/licenses/LICENSE-2.0
-  ~
-  ~ Unless required by applicable law or agreed to in writing, software
-  ~ distributed under the License is distributed on an "AS IS" BASIS,
-  ~ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
-  ~ See the License for the specific language governing permissions and
-  ~ limitations under the License
-  -->
-
-<!-- extends FrameLayout -->
-<com.android.internal.widget.ConversationLayout
-    xmlns:android="http://schemas.android.com/apk/res/android"
-    android:id="@+id/status_bar_latest_event_content"
-    android:layout_width="match_parent"
-    android:layout_height="wrap_content"
-    android:clipChildren="false"
-    android:tag="conversation"
-    android:theme="@style/Theme.DeviceDefault.Notification"
-    >
-
-    <include layout="@layout/notification_2025_conversation_icon_container" />
-
-    <!-- Wraps entire "expandable" notification -->
-    <com.android.internal.widget.RemeasuringLinearLayout
-        android:layout_width="match_parent"
-        android:layout_height="wrap_content"
-        android:layout_gravity="top"
-        android:clipToPadding="false"
-        android:clipChildren="false"
-        android:orientation="vertical"
-        >
-        <!-- LinearLayout for Expand Button-->
-        <com.android.internal.widget.RemeasuringLinearLayout
-            android:id="@+id/expand_button_and_content_container"
-            android:layout_width="match_parent"
-            android:layout_height="wrap_content"
-            android:layout_weight="1"
-            android:gravity="start|top"
-            android:orientation="horizontal"
-            android:clipChildren="false"
-            android:clipToPadding="false">
-            <!--TODO: move this into a separate layout and share logic with the header to bring back app opps etc-->
-            <com.android.internal.widget.RemeasuringLinearLayout
-                android:id="@+id/notification_action_list_margin_target"
-                android:layout_width="0dp"
-                android:orientation="vertical"
-                android:layout_height="wrap_content"
-                android:layout_weight="1">
-
-                <!-- Header -->
-
-                <!-- Use layout_marginStart instead of paddingStart to work around strange
-                     measurement behavior on lower display densities. -->
-                <include
-                    layout="@layout/notification_2025_conversation_header"
-                    android:layout_width="wrap_content"
-                    android:layout_height="wrap_content"
-                    android:layout_marginBottom="2dp"
-                    android:layout_marginStart="@dimen/notification_2025_content_margin_start"
-                    />
-
-                <!-- Messages -->
-                <com.android.internal.widget.MessagingLinearLayout
-                    android:id="@+id/notification_messaging"
-                    android:layout_width="match_parent"
-                    android:layout_height="wrap_content"
-                    android:minHeight="@dimen/notification_text_size"
-                    android:spacing="@dimen/notification_messaging_spacing"
-                    android:clipToPadding="false"
-                    android:clipChildren="false"
-                    />
-            </com.android.internal.widget.RemeasuringLinearLayout>
-
-            <!-- This is where the expand button container will be placed when collapsed-->
-        </com.android.internal.widget.RemeasuringLinearLayout>
-
-        <include layout="@layout/notification_template_smart_reply_container"
-            android:layout_width="match_parent"
-            android:layout_height="wrap_content"
-            android:layout_marginTop="@dimen/notification_content_margin"
-            android:layout_marginStart="@dimen/notification_2025_content_margin_start"
-            android:layout_marginEnd="@dimen/notification_content_margin_end" />
-        <include layout="@layout/notification_material_action_list" />
-    </com.android.internal.widget.RemeasuringLinearLayout>
-
-    <!--expand_button_a11y_container ensures talkback focus order is correct when view is expanded.
-    The -1px of marginTop and 1px of paddingTop make sure expand_button_a11y_container is prior to
-    its sibling view in accessibility focus order.
-    {see android.view.ViewGroup.addChildrenForAccessibility()}
-    expand_button_container will be moved under expand_button_and_content_container when collapsed,
-    this dynamic movement ensures message can flow under expand button when expanded-->
-    <FrameLayout
-        android:id="@+id/expand_button_a11y_container"
-        android:layout_width="match_parent"
-        android:layout_height="match_parent"
-        android:layout_gravity="end|top"
-        android:clipChildren="false"
-        android:clipToPadding="false"
-        android:layout_marginTop="-1px"
-        android:paddingTop="1px"
-        >
-        <!--expand_button_container is dynamically placed between here and at the end of the
-        layout. It starts here since only FrameLayout layout params have gravity-->
-        <LinearLayout
-            android:id="@+id/expand_button_container"
-            android:layout_width="wrap_content"
-            android:layout_height="match_parent"
-            android:layout_gravity="end|top"
-            android:clipChildren="false"
-            android:clipToPadding="false"
-            android:orientation="vertical">
-            <include layout="@layout/notification_close_button"
-                android:layout_width="@dimen/notification_close_button_size"
-                android:layout_height="@dimen/notification_close_button_size"
-                android:layout_gravity="end"
-                android:layout_marginEnd="20dp"
-                />
-            <!--expand_button_touch_container makes sure that we can nicely center the expand
-            content in the collapsed layout while the parent makes sure that we're never laid out
-            bigger than the messaging content.-->
-            <LinearLayout
-                android:id="@+id/expand_button_touch_container"
-                android:layout_width="wrap_content"
-                android:layout_height="@dimen/conversation_expand_button_height"
-                android:orientation="horizontal"
-                android:layout_gravity="end|top"
-                android:paddingEnd="0dp"
-                android:clipToPadding="false"
-                android:clipChildren="false"
-                >
-                <!-- Images -->
-                <com.android.internal.widget.MessagingLinearLayout
-                    android:id="@+id/conversation_image_message_container"
-                    android:forceHasOverlappingRendering="false"
-                    android:layout_width="40dp"
-                    android:layout_height="40dp"
-                    android:layout_marginStart="@dimen/conversation_image_start_margin"
-                    android:spacing="0dp"
-                    android:layout_gravity="center"
-                    android:clipToPadding="false"
-                    android:clipChildren="false"
-                    />
-                <include layout="@layout/notification_2025_expand_button"
-                    android:layout_width="wrap_content"
-                    android:layout_height="wrap_content"
-                    android:layout_gravity="top|end"
-                    />
-            </LinearLayout>
-        </LinearLayout>
-    </FrameLayout>
-</com.android.internal.widget.ConversationLayout>
diff --git a/core/res/res/layout/notification_2025_template_expanded_call.xml b/core/res/res/layout/notification_2025_template_expanded_call.xml
index 0be6125..bbc2966 100644
--- a/core/res/res/layout/notification_2025_template_expanded_call.xml
+++ b/core/res/res/layout/notification_2025_template_expanded_call.xml
@@ -1,6 +1,6 @@
 <?xml version="1.0" encoding="utf-8"?>
 <!--
-  ~ Copyright (C) 2024 The Android Open Source Project
+  ~ Copyright (C) 2025 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.
@@ -27,83 +27,47 @@
     >
 
     <!-- CallLayout shares visual appearance with ConversationLayout, so shares layouts -->
-    <include layout="@layout/notification_2025_conversation_icon_container" />
+    <include layout="@layout/notification_2025_conversation_header"/>
 
-    <LinearLayout
+    <com.android.internal.widget.RemeasuringLinearLayout
         android:id="@+id/notification_action_list_margin_target"
         android:layout_width="match_parent"
         android:layout_height="wrap_content"
-        android:layout_marginBottom="@dimen/notification_content_margin"
-        android:orientation="vertical"
-        >
+        android:layout_gravity="top"
+        android:clipChildren="false"
+        android:orientation="vertical">
 
-        <LinearLayout
+        <!-- Note: the top margin is being set in code based on the estimated space needed for
+        the header text. -->
+        <com.android.internal.widget.RemeasuringLinearLayout
+            android:id="@+id/notification_main_column"
             android:layout_width="match_parent"
             android:layout_height="wrap_content"
+            android:layout_gravity="top"
             android:layout_weight="1"
-            android:orientation="horizontal"
-            >
-
-            <LinearLayout
-                android:id="@+id/notification_main_column"
-                android:layout_width="match_parent"
-                android:layout_height="wrap_content"
-                android:layout_marginStart="@dimen/notification_2025_content_margin_start"
-                android:layout_weight="1"
-                android:orientation="vertical"
-                android:minHeight="68dp"
-                >
-
-                <include
-                    layout="@layout/notification_2025_conversation_header"
-                    android:layout_width="wrap_content"
-                    android:layout_height="wrap_content"
-                    />
-
-                <include layout="@layout/notification_template_text_multiline" />
-
-                <include
-                    android:layout_width="match_parent"
-                    android:layout_height="@dimen/notification_progress_bar_height"
-                    android:layout_marginTop="@dimen/notification_progress_margin_top"
-                    layout="@layout/notification_template_progress"
-                    />
-            </LinearLayout>
-
-            <FrameLayout
-                android:layout_width="wrap_content"
-                android:layout_height="match_parent"
-                android:minWidth="@dimen/notification_content_margin_end"
-                >
-
-                <include
-                    layout="@layout/notification_expand_button"
-                    android:layout_width="wrap_content"
-                    android:layout_height="wrap_content"
-                    />
-
-            </FrameLayout>
-
-        </LinearLayout>
-
-        <ViewStub
-            android:layout="@layout/notification_material_reply_text"
-            android:id="@+id/notification_material_reply_container"
-            android:layout_width="match_parent"
-            android:layout_height="wrap_content"
-            />
-
-        <include
-            layout="@layout/notification_template_smart_reply_container"
-            android:layout_width="match_parent"
-            android:layout_height="wrap_content"
             android:layout_marginStart="@dimen/notification_2025_content_margin_start"
             android:layout_marginEnd="@dimen/notification_content_margin_end"
+            android:orientation="vertical"
+            android:clipChildren="false"
+            >
+
+            <include layout="@layout/notification_template_part_line1"/>
+
+            <include layout="@layout/notification_template_text_multiline" />
+
+        </com.android.internal.widget.RemeasuringLinearLayout>
+
+        <include layout="@layout/notification_template_smart_reply_container"
+            android:layout_width="match_parent"
+            android:layout_height="wrap_content"
             android:layout_marginTop="@dimen/notification_content_margin"
-            />
+            android:layout_marginStart="@dimen/notification_2025_content_margin_start"
+            android:layout_marginEnd="@dimen/notification_content_margin_end" />
 
         <include layout="@layout/notification_material_action_list" />
 
-    </LinearLayout>
+    </com.android.internal.widget.RemeasuringLinearLayout>
+
+    <include layout="@layout/notification_template_right_icon" />
 
 </com.android.internal.widget.CallLayout>
diff --git a/core/res/res/layout/notification_2025_template_expanded_conversation.xml b/core/res/res/layout/notification_2025_template_expanded_conversation.xml
new file mode 100644
index 0000000..d7e8bb3
--- /dev/null
+++ b/core/res/res/layout/notification_2025_template_expanded_conversation.xml
@@ -0,0 +1,75 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!--
+  ~ Copyright (C) 2025 The Android Open Source Project
+  ~
+  ~ Licensed under the Apache License, Version 2.0 (the "License");
+  ~ you may not use this file except in compliance with the License.
+  ~ You may obtain a copy of the License at
+  ~
+  ~      http://www.apache.org/licenses/LICENSE-2.0
+  ~
+  ~ Unless required by applicable law or agreed to in writing, software
+  ~ distributed under the License is distributed on an "AS IS" BASIS,
+  ~ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+  ~ See the License for the specific language governing permissions and
+  ~ limitations under the License
+  -->
+
+<!-- extends FrameLayout -->
+<com.android.internal.widget.ConversationLayout
+    xmlns:android="http://schemas.android.com/apk/res/android"
+    android:id="@+id/status_bar_latest_event_content"
+    android:layout_width="match_parent"
+    android:layout_height="wrap_content"
+    android:clipToPadding="false"
+    android:clipChildren="false"
+    android:tag="conversation"
+    >
+
+    <include layout="@layout/notification_2025_conversation_header"/>
+
+    <com.android.internal.widget.RemeasuringLinearLayout
+            android:id="@+id/notification_action_list_margin_target"
+            android:layout_width="match_parent"
+            android:layout_height="wrap_content"
+            android:layout_gravity="top"
+            android:clipChildren="false"
+            android:orientation="vertical">
+
+        <!-- Note: the top margin is being set in code based on the estimated space needed for
+        the header text. -->
+        <com.android.internal.widget.RemeasuringLinearLayout
+            android:id="@+id/notification_main_column"
+            android:layout_width="match_parent"
+            android:layout_height="wrap_content"
+            android:layout_gravity="top"
+            android:layout_weight="1"
+            android:layout_marginEnd="@dimen/notification_content_margin_end"
+            android:orientation="vertical"
+            android:clipChildren="false"
+            >
+
+            <include layout="@layout/notification_template_part_line1"/>
+
+            <com.android.internal.widget.MessagingLinearLayout
+                android:id="@+id/notification_messaging"
+                android:layout_width="match_parent"
+                android:layout_height="wrap_content"
+                android:clipChildren="false"
+                android:spacing="@dimen/notification_messaging_spacing" />
+        </com.android.internal.widget.RemeasuringLinearLayout>
+
+        <include layout="@layout/notification_template_smart_reply_container"
+                android:layout_width="match_parent"
+                android:layout_height="wrap_content"
+                android:layout_marginTop="@dimen/notification_content_margin"
+                android:layout_marginStart="@dimen/notification_2025_content_margin_start"
+                android:layout_marginEnd="@dimen/notification_content_margin_end" />
+
+        <include layout="@layout/notification_material_action_list" />
+
+    </com.android.internal.widget.RemeasuringLinearLayout>
+
+    <include layout="@layout/notification_template_right_icon" />
+
+</com.android.internal.widget.ConversationLayout>
diff --git a/core/res/res/layout/notification_2025_template_expanded_messaging.xml b/core/res/res/layout/notification_2025_template_expanded_messaging.xml
index 177706c..20abfee 100644
--- a/core/res/res/layout/notification_2025_template_expanded_messaging.xml
+++ b/core/res/res/layout/notification_2025_template_expanded_messaging.xml
@@ -36,17 +36,21 @@
             android:clipChildren="false"
             android:orientation="vertical">
 
+        <!-- Note: the top margin is being set in code based on the estimated space needed for
+        the header text. -->
         <com.android.internal.widget.RemeasuringLinearLayout
             android:id="@+id/notification_main_column"
             android:layout_width="match_parent"
             android:layout_height="wrap_content"
             android:layout_gravity="top"
             android:layout_weight="1"
-            android:layout_marginTop="@dimen/notification_2025_header_height"
             android:layout_marginEnd="@dimen/notification_content_margin_end"
             android:orientation="vertical"
             android:clipChildren="false"
             >
+
+            <include layout="@layout/notification_template_part_line1"/>
+
             <com.android.internal.widget.MessagingLinearLayout
                 android:id="@+id/notification_messaging"
                 android:layout_width="match_parent"
diff --git a/core/res/res/layout/notification_2025_top_line_views.xml b/core/res/res/layout/notification_2025_top_line_views.xml
index 7487346..7431c42 100644
--- a/core/res/res/layout/notification_2025_top_line_views.xml
+++ b/core/res/res/layout/notification_2025_top_line_views.xml
@@ -20,7 +20,7 @@
 <merge
     xmlns:android="http://schemas.android.com/apk/res/android">
 
-    <TextView
+    <com.android.internal.widget.ObservableTextView
         android:id="@+id/app_name_text"
         android:layout_width="wrap_content"
         android:layout_height="wrap_content"
@@ -108,6 +108,42 @@
         android:visibility="gone"
         />
 
+    <TextView
+        android:id="@+id/verification_divider"
+        android:layout_width="wrap_content"
+        android:layout_height="wrap_content"
+        android:textAppearance="@style/TextAppearance.DeviceDefault.Notification.Info"
+        android:layout_marginStart="@dimen/notification_header_separating_margin"
+        android:layout_marginEnd="@dimen/notification_header_separating_margin"
+        android:text="@string/notification_header_divider_symbol"
+        android:singleLine="true"
+        android:visibility="gone"
+        />
+
+    <ImageView
+        android:id="@+id/verification_icon"
+        android:layout_width="@dimen/notification_2025_badge_size"
+        android:layout_height="@dimen/notification_2025_badge_size"
+        android:layout_marginStart="@dimen/notification_header_separating_margin"
+        android:layout_marginEnd="@dimen/notification_header_separating_margin"
+        android:baseline="@dimen/notification_2025_badge_baseline"
+        android:scaleType="fitCenter"
+        android:visibility="gone"
+        />
+
+    <TextView
+        android:id="@+id/verification_text"
+        android:textAppearance="@style/TextAppearance.DeviceDefault.Notification.Info"
+        android:layout_width="wrap_content"
+        android:layout_height="wrap_content"
+        android:layout_marginStart="@dimen/notification_header_separating_margin"
+        android:layout_marginEnd="@dimen/notification_header_separating_margin"
+        android:layout_weight="100"
+        android:showRelative="true"
+        android:singleLine="true"
+        android:visibility="gone"
+        />
+
     <ImageButton
         android:id="@+id/feedback"
         android:layout_width="@dimen/notification_feedback_size"
diff --git a/core/res/res/values/config_telephony.xml b/core/res/res/values/config_telephony.xml
index f2ec56c..59ed25a 100644
--- a/core/res/res/values/config_telephony.xml
+++ b/core/res/res/values/config_telephony.xml
@@ -64,6 +64,13 @@
     <integer name="auto_data_switch_performance_stability_time_threshold_millis">120000</integer>
     <java-symbol type="integer" name="auto_data_switch_performance_stability_time_threshold_millis" />
 
+    <!-- Define the bar for switching data back to the default SIM when both SIMs are out of service
+         in milliseconds. A value of 0 means an immediate switch, otherwise for a negative value,
+         the threshold defined by auto_data_switch_availability_stability_time_threshold_millis
+         will be used instead. -->
+    <integer name="auto_data_switch_availability_switchback_stability_time_threshold_millis">150000</integer>
+    <java-symbol type="integer" name="auto_data_switch_availability_switchback_stability_time_threshold_millis" />
+
     <!-- Define the maximum retry times when a validation for switching failed.-->
     <integer name="auto_data_switch_validation_max_retry">7</integer>
     <java-symbol type="integer" name="auto_data_switch_validation_max_retry" />
diff --git a/core/res/res/values/symbols.xml b/core/res/res/values/symbols.xml
index 26f0ab3..b013ffd 100644
--- a/core/res/res/values/symbols.xml
+++ b/core/res/res/values/symbols.xml
@@ -3264,6 +3264,7 @@
   <java-symbol type="dimen" name="notification_content_margin" />
   <java-symbol type="dimen" name="notification_2025_margin" />
   <java-symbol type="dimen" name="notification_2025_content_margin_top" />
+  <java-symbol type="dimen" name="notification_2025_content_margin_start" />
   <java-symbol type="dimen" name="notification_2025_expand_button_horizontal_icon_padding" />
   <java-symbol type="dimen" name="notification_2025_expand_button_reduced_end_padding" />
   <java-symbol type="dimen" name="notification_progress_margin_horizontal" />
@@ -4705,7 +4706,8 @@
   <java-symbol type="dimen" name="conversation_icon_container_top_padding" />
   <java-symbol type="dimen" name="conversation_icon_container_top_padding_small_avatar" />
   <java-symbol type="layout" name="notification_template_material_conversation" />
-  <java-symbol type="layout" name="notification_2025_template_conversation" />
+  <java-symbol type="layout" name="notification_2025_template_collapsed_conversation" />
+  <java-symbol type="layout" name="notification_2025_template_expanded_conversation" />
   <java-symbol type="dimen" name="button_padding_horizontal_material" />
   <java-symbol type="dimen" name="button_inset_horizontal_material" />
   <java-symbol type="layout" name="conversation_face_pile_layout" />
diff --git a/core/tests/coretests/Android.bp b/core/tests/coretests/Android.bp
index 4c49ff8..05fb5735 100644
--- a/core/tests/coretests/Android.bp
+++ b/core/tests/coretests/Android.bp
@@ -249,8 +249,14 @@
 android_ravenwood_test {
     name: "FrameworksCoreTestsRavenwood",
     libs: [
-        "android.test.base.stubs.system",
-        "android.test.runner.stubs.system",
+        "android.test.base.stubs",
+        "android.test.mock.stubs",
+        "android.test.runner.stubs",
+        "android.view.flags-aconfig-java",
+        "ext",
+        "framework",
+        "framework-res",
+        "org.apache.http.legacy.stubs",
     ],
     static_libs: [
         "androidx.annotation_annotation",
@@ -264,6 +270,7 @@
         "flag-junit",
         "flag-junit",
         "perfetto_trace_java_protos",
+        "platform-compat-test-rules",
         "platform-test-annotations",
         "testng",
     ],
@@ -278,8 +285,12 @@
         "src/android/content/res/*.java",
         "src/android/content/res/*.kt",
         "src/android/database/CursorWindowTest.java",
+        "src/android/graphics/*.java",
+        "src/android/graphics/*.kt",
         "src/android/os/**/*.java",
         "src/android/telephony/PinResultTest.java",
+        "src/android/text/**/*.java",
+        "src/android/text/**/*.kt",
         "src/android/util/**/*.java",
         "src/android/view/DisplayAdjustmentsTests.java",
         "src/android/view/DisplayInfoTest.java",
@@ -288,20 +299,21 @@
         "src/com/android/internal/os/**/*.java",
         "src/com/android/internal/power/EnergyConsumerStatsTest.java",
         "src/com/android/internal/ravenwood/**/*.java",
-
-        // Pull in R.java from FrameworksCoreTests-resonly, not from FrameworksCoreTests,
-        // to avoid having a dependency to FrameworksCoreTests.
-        // This way, when updating source files and running this test, we don't need to
-        // rebuild the entire FrameworksCoreTests, which would be slow.
         "src/com/android/internal/util/**/*.java",
 
         ":FrameworksCoreTestDoubles-sources",
         ":FrameworksCoreTests-aidl",
         ":FrameworksCoreTests-helpers",
+
+        // Pull in R.java from FrameworksCoreTests-resonly, not from FrameworksCoreTests,
+        // to avoid having a dependency to FrameworksCoreTests.
+        // This way, when updating source files and running this test, we don't need to
+        // rebuild the entire FrameworksCoreTests, which would be slow.
         ":FrameworksCoreTests-resonly{.aapt.srcjar}",
     ],
     exclude_srcs: [
         "src/android/content/res/FontScaleConverterActivityTest.java",
+        "src/android/graphics/GraphicsPerformanceTests.java",
     ],
     resource_apk: "FrameworksCoreTests-resonly",
     aidl: {
@@ -313,6 +325,7 @@
         "res/xml/power_profile_test_cpu_legacy.xml",
         "res/xml/power_profile_test_modem.xml",
     ],
+    sdk_version: "core_platform",
     auto_gen_config: true,
     team: "trendy_team_ravenwood",
 }
diff --git a/core/tests/coretests/src/android/content/res/FontScaleConverterTest.kt b/core/tests/coretests/src/android/content/res/FontScaleConverterTest.kt
index 0e5d926..2c61442 100644
--- a/core/tests/coretests/src/android/content/res/FontScaleConverterTest.kt
+++ b/core/tests/coretests/src/android/content/res/FontScaleConverterTest.kt
@@ -17,10 +17,8 @@
 package android.content.res
 
 import android.platform.test.annotations.Presubmit
-import android.platform.test.ravenwood.RavenwoodRule
 import androidx.test.ext.junit.runners.AndroidJUnit4
 import com.google.common.truth.Truth.assertWithMessage
-import org.junit.Rule
 import org.junit.Test
 import org.junit.runner.RunWith
 
@@ -28,9 +26,6 @@
 @RunWith(AndroidJUnit4::class)
 class FontScaleConverterTest {
 
-    @get:Rule
-    val ravenwoodRule: RavenwoodRule = RavenwoodRule.Builder().build()
-
     @Test
     fun straightInterpolation() {
         val table = createTable(8f to 8f, 10f to 10f, 20f to 20f)
diff --git a/core/tests/coretests/src/android/graphics/BitmapFactoryTest.java b/core/tests/coretests/src/android/graphics/BitmapFactoryTest.java
index 84bdbe0..263307e 100644
--- a/core/tests/coretests/src/android/graphics/BitmapFactoryTest.java
+++ b/core/tests/coretests/src/android/graphics/BitmapFactoryTest.java
@@ -20,7 +20,9 @@
 import static org.junit.Assert.assertNotNull;
 import static org.junit.Assert.assertTrue;
 
+import android.os.MemoryFile;
 import android.os.ParcelFileDescriptor;
+import android.platform.test.annotations.DisabledOnRavenwood;
 
 import androidx.test.ext.junit.runners.AndroidJUnit4;
 import androidx.test.filters.SmallTest;
@@ -37,6 +39,7 @@
     // tests that we can decode bitmaps from MemoryFiles
     @SmallTest
     @Test
+    @DisabledOnRavenwood(blockedBy = MemoryFile.class)
     public void testBitmapParcelFileDescriptor() throws Exception {
         Bitmap bitmap1 = Bitmap.createBitmap(
                 new int[] { Color.BLUE }, 1, 1, Bitmap.Config.RGB_565);
diff --git a/core/tests/coretests/src/android/graphics/BitmapTest.java b/core/tests/coretests/src/android/graphics/BitmapTest.java
index 0126d36..61c3d78 100644
--- a/core/tests/coretests/src/android/graphics/BitmapTest.java
+++ b/core/tests/coretests/src/android/graphics/BitmapTest.java
@@ -22,6 +22,7 @@
 import static org.junit.Assert.fail;
 
 import android.hardware.HardwareBuffer;
+import android.platform.test.annotations.DisabledOnRavenwood;
 
 import androidx.test.ext.junit.runners.AndroidJUnit4;
 import androidx.test.filters.SmallTest;
@@ -252,6 +253,7 @@
                     | GraphicBuffer.USAGE_SW_WRITE_OFTEN;
 
     @Test
+    @DisabledOnRavenwood(blockedBy = HardwareBuffer.class)
     public void testWrapHardwareBufferWithSrgbColorSpace() {
         GraphicBuffer buffer = GraphicBuffer.create(10, 10, PixelFormat.RGBA_8888, GRAPHICS_USAGE);
         Canvas canvas = buffer.lockCanvas();
@@ -265,6 +267,7 @@
     }
 
     @Test
+    @DisabledOnRavenwood(blockedBy = HardwareBuffer.class)
     public void testWrapHardwareBufferWithDisplayP3ColorSpace() {
         GraphicBuffer buffer = GraphicBuffer.create(10, 10, PixelFormat.RGBA_8888, GRAPHICS_USAGE);
         Canvas canvas = buffer.lockCanvas();
diff --git a/core/tests/coretests/src/android/graphics/PaintTest.java b/core/tests/coretests/src/android/graphics/PaintTest.java
index 56760d7..deb5157 100644
--- a/core/tests/coretests/src/android/graphics/PaintTest.java
+++ b/core/tests/coretests/src/android/graphics/PaintTest.java
@@ -24,6 +24,7 @@
 import static org.junit.Assert.assertTrue;
 import static org.junit.Assert.fail;
 
+import android.platform.test.annotations.DisabledOnRavenwood;
 import android.platform.test.annotations.RequiresFlagsEnabled;
 import android.platform.test.flag.junit.CheckFlagsRule;
 import android.platform.test.flag.junit.DeviceFlagsValueProvider;
@@ -97,6 +98,7 @@
 
     @SmallTest
     @Test
+    @DisabledOnRavenwood(bug = 391381043)
     public void testHintingWidth() {
         final Typeface fontTypeface = Typeface.createFromAsset(
                 InstrumentationRegistry.getInstrumentation().getContext().getAssets(), FONT_PATH);
@@ -143,6 +145,7 @@
     }
 
     @Test
+    @DisabledOnRavenwood(bug = 391381043)
     public void testHasGlyph_variationSelectors() {
         final Typeface fontTypeface = Typeface.createFromAsset(
                 InstrumentationRegistry.getInstrumentation().getContext().getAssets(),
diff --git a/core/tests/coretests/src/android/graphics/TypefaceSystemFallbackTest.java b/core/tests/coretests/src/android/graphics/TypefaceSystemFallbackTest.java
index 2b6eda8f..dc3376e 100644
--- a/core/tests/coretests/src/android/graphics/TypefaceSystemFallbackTest.java
+++ b/core/tests/coretests/src/android/graphics/TypefaceSystemFallbackTest.java
@@ -35,9 +35,9 @@
 import android.text.FontConfig;
 import android.util.ArrayMap;
 
-import androidx.test.InstrumentationRegistry;
 import androidx.test.ext.junit.runners.AndroidJUnit4;
 import androidx.test.filters.SmallTest;
+import androidx.test.platform.app.InstrumentationRegistry;
 
 import com.android.text.flags.Flags;
 
@@ -63,9 +63,6 @@
 @SmallTest
 @RunWith(AndroidJUnit4.class)
 public class TypefaceSystemFallbackTest {
-    private static final String SYSTEM_FONT_DIR = "/system/fonts/";
-    private static final String SYSTEM_FONTS_XML = "/system/etc/fonts.xml";
-
     private static final String[] TEST_FONT_FILES = {
         "a3em.ttf",  // Supports "a","b","c". The width of "a" is 3em,  others are 1em.
         "b3em.ttf",  // Supports "a","b","c". The width of "b" is 3em,  others are 1em.
@@ -118,8 +115,6 @@
 
     @Before
     public void setUp() {
-        final AssetManager am =
-                InstrumentationRegistry.getInstrumentation().getContext().getAssets();
         for (final String fontFile : TEST_FONT_FILES) {
             final String sourceInAsset = "fonts/" + fontFile;
             copyAssetToFile(sourceInAsset, new File(TEST_FONT_DIR, fontFile));
@@ -216,7 +211,8 @@
         FontConfig fontConfig;
         try {
             fontConfig = FontListParser.parse(
-                    SYSTEM_FONTS_XML, SYSTEM_FONT_DIR, null, TEST_OEM_DIR, null, 0, 0);
+                    SystemFonts.LEGACY_FONTS_XML, SystemFonts.SYSTEM_FONT_DIR,
+                    null, TEST_OEM_DIR, null, 0, 0);
         } catch (IOException | XmlPullParserException e) {
             throw new RuntimeException(e);
         }
diff --git a/core/tests/coretests/src/android/graphics/TypefaceTest.java b/core/tests/coretests/src/android/graphics/TypefaceTest.java
index 80efa51..0c8b5ab 100644
--- a/core/tests/coretests/src/android/graphics/TypefaceTest.java
+++ b/core/tests/coretests/src/android/graphics/TypefaceTest.java
@@ -26,6 +26,7 @@
 import android.graphics.fonts.FontFamily;
 import android.graphics.fonts.SystemFonts;
 import android.os.SharedMemory;
+import android.platform.test.annotations.DisabledOnRavenwood;
 import android.text.FontConfig;
 import android.util.ArrayMap;
 
@@ -196,6 +197,7 @@
 
     @SmallTest
     @Test
+    @DisabledOnRavenwood(blockedBy = SharedMemory.class)
     public void testSerialize() throws Exception {
         FontConfig fontConfig = SystemFonts.getSystemPreinstalledFontConfig();
         Map<String, FontFamily[]> fallbackMap = SystemFonts.buildSystemFallback(fontConfig);
diff --git a/core/tests/coretests/src/android/hardware/display/DisplayManagerGlobalTest.java b/core/tests/coretests/src/android/hardware/display/DisplayManagerGlobalTest.java
index 9383807..8ef105f 100644
--- a/core/tests/coretests/src/android/hardware/display/DisplayManagerGlobalTest.java
+++ b/core/tests/coretests/src/android/hardware/display/DisplayManagerGlobalTest.java
@@ -56,6 +56,7 @@
 import org.mockito.Mockito;
 import org.mockito.MockitoAnnotations;
 
+import java.util.concurrent.CopyOnWriteArrayList;
 import java.util.concurrent.Executor;
 import java.util.function.Consumer;
 
@@ -116,7 +117,8 @@
     @Test
     public void testDisplayListenerIsCalled_WhenDisplayEventOccurs() throws RemoteException {
         mDisplayManagerGlobal.registerDisplayListener(mDisplayListener, mHandler,
-                ALL_DISPLAY_EVENTS, /* packageName= */ null);
+                ALL_DISPLAY_EVENTS, /* packageName= */ null,
+                /* isEventFilterExplicit */ true);
         Mockito.verify(mDisplayManager)
                 .registerCallbackWithEventMask(mCallbackCaptor.capture(), anyLong());
         IDisplayManagerCallback callback = mCallbackCaptor.getValue();
@@ -151,7 +153,7 @@
         mDisplayManagerGlobal.registerDisplayListener(mDisplayListener, mHandler,
                 INTERNAL_EVENT_FLAG_DISPLAY_REFRESH_RATE
                         | INTERNAL_EVENT_FLAG_DISPLAY_STATE,
-                null);
+                null, /* isEventFilterExplicit */ true);
         Mockito.verify(mDisplayManager)
                 .registerCallbackWithEventMask(mCallbackCaptor.capture(), anyLong());
         IDisplayManagerCallback callback = mCallbackCaptor.getValue();
@@ -172,11 +174,80 @@
     }
 
     @Test
+    @RequiresFlagsEnabled(Flags.FLAG_DELAY_IMPLICIT_RR_REGISTRATION_UNTIL_RR_ACCESSED)
+    public void test_refreshRateRegistration_implicitRRCallbacksEnabled()
+            throws RemoteException {
+        // Subscription without supplied events doesn't subscribe to RR events
+        mDisplayManagerGlobal.registerDisplayListener(mDisplayListener, mHandler,
+                ALL_DISPLAY_EVENTS, /* packageName= */ null,
+                /* isEventFilterExplicit */ false);
+        Mockito.verify(mDisplayManager)
+                .registerCallbackWithEventMask(mCallbackCaptor.capture(), eq(ALL_DISPLAY_EVENTS));
+
+        // After registering to refresh rate changes, subscription without supplied events subscribe
+        // to RR events
+        mDisplayManagerGlobal.registerForRefreshRateChanges();
+        mDisplayManagerGlobal.registerDisplayListener(mDisplayListener, mHandler,
+                ALL_DISPLAY_EVENTS, /* packageName= */ null,
+                /* isEventFilterExplicit */ false);
+        Mockito.verify(mDisplayManager)
+                .registerCallbackWithEventMask(mCallbackCaptor.capture(), eq(ALL_DISPLAY_EVENTS
+                        | INTERNAL_EVENT_FLAG_DISPLAY_REFRESH_RATE));
+
+        // Assert all the existing listeners are also subscribed to RR events
+        CopyOnWriteArrayList<DisplayManagerGlobal.DisplayListenerDelegate> delegates =
+                mDisplayManagerGlobal.getDisplayListeners();
+        for (DisplayManagerGlobal.DisplayListenerDelegate delegate: delegates) {
+            assertEquals(ALL_DISPLAY_EVENTS | INTERNAL_EVENT_FLAG_DISPLAY_REFRESH_RATE,
+                    delegate.mInternalEventFlagsMask);
+        }
+
+        // Subscription to RR when events are supplied doesn't happen
+        mDisplayManagerGlobal.registerDisplayListener(mDisplayListener, mHandler,
+                ALL_DISPLAY_EVENTS, /* packageName= */ null,
+                /* isEventFilterExplicit */ true);
+        Mockito.verify(mDisplayManager)
+                .registerCallbackWithEventMask(mCallbackCaptor.capture(), eq(ALL_DISPLAY_EVENTS));
+
+        // Assert one listeners are not subscribed to RR events
+        delegates =  mDisplayManagerGlobal.getDisplayListeners();
+        int subscribedListenersCount = 0;
+        int nonSubscribedListenersCount = 0;
+        for (DisplayManagerGlobal.DisplayListenerDelegate delegate: delegates) {
+
+            if (delegate.isEventFilterExplicit()) {
+                assertEquals(ALL_DISPLAY_EVENTS, delegate.mInternalEventFlagsMask);
+                nonSubscribedListenersCount++;
+            } else {
+                assertEquals(ALL_DISPLAY_EVENTS | INTERNAL_EVENT_FLAG_DISPLAY_REFRESH_RATE,
+                        delegate.mInternalEventFlagsMask);
+                subscribedListenersCount++;
+            }
+        }
+
+        assertEquals(2, subscribedListenersCount);
+        assertEquals(1, nonSubscribedListenersCount);
+    }
+
+    @Test
+    @RequiresFlagsDisabled(Flags.FLAG_DELAY_IMPLICIT_RR_REGISTRATION_UNTIL_RR_ACCESSED)
+    public void test_refreshRateRegistration_implicitRRCallbacksDisabled()
+            throws RemoteException {
+        mDisplayManagerGlobal.registerDisplayListener(mDisplayListener, mHandler,
+                ALL_DISPLAY_EVENTS, /* packageName= */ null,
+                /* isEventFilterExplicit */ false);
+        Mockito.verify(mDisplayManager)
+                .registerCallbackWithEventMask(mCallbackCaptor.capture(), eq(ALL_DISPLAY_EVENTS
+                        | INTERNAL_EVENT_FLAG_DISPLAY_REFRESH_RATE));
+    }
+
+    @Test
     public void testDisplayListenerIsNotCalled_WhenClientIsNotSubscribed() throws RemoteException {
         // First we subscribe to all events in order to test that the subsequent calls to
         // registerDisplayListener will update the event mask.
         mDisplayManagerGlobal.registerDisplayListener(mDisplayListener, mHandler,
-                ALL_DISPLAY_EVENTS, /* packageName= */ null);
+                ALL_DISPLAY_EVENTS, /* packageName= */ null,
+                /* isEventFilterExplicit */ true);
         Mockito.verify(mDisplayManager)
                 .registerCallbackWithEventMask(mCallbackCaptor.capture(), anyLong());
         IDisplayManagerCallback callback = mCallbackCaptor.getValue();
@@ -184,21 +255,24 @@
         int displayId = 1;
         mDisplayManagerGlobal.registerDisplayListener(mDisplayListener, mHandler,
                 ALL_DISPLAY_EVENTS
-                        & ~DisplayManagerGlobal.INTERNAL_EVENT_FLAG_DISPLAY_ADDED, null);
+                        & ~DisplayManagerGlobal.INTERNAL_EVENT_FLAG_DISPLAY_ADDED, null,
+                        /* isEventFilterExplicit */ true);
         callback.onDisplayEvent(displayId, DisplayManagerGlobal.EVENT_DISPLAY_ADDED);
         waitForHandler();
         Mockito.verifyZeroInteractions(mDisplayListener);
 
         mDisplayManagerGlobal.registerDisplayListener(mDisplayListener, mHandler,
                 ALL_DISPLAY_EVENTS
-                        & ~DISPLAY_CHANGE_EVENTS, null);
+                        & ~DISPLAY_CHANGE_EVENTS, null,
+                        /* isEventFilterExplicit */ true);
         callback.onDisplayEvent(displayId, DisplayManagerGlobal.EVENT_DISPLAY_BASIC_CHANGED);
         waitForHandler();
         Mockito.verifyZeroInteractions(mDisplayListener);
 
         mDisplayManagerGlobal.registerDisplayListener(mDisplayListener, mHandler,
                 ALL_DISPLAY_EVENTS
-                        & ~DisplayManagerGlobal.INTERNAL_EVENT_FLAG_DISPLAY_REMOVED, null);
+                        & ~DisplayManagerGlobal.INTERNAL_EVENT_FLAG_DISPLAY_REMOVED, null,
+                        /* isEventFilterExplicit */ true);
         callback.onDisplayEvent(displayId, DisplayManagerGlobal.EVENT_DISPLAY_REMOVED);
         waitForHandler();
         Mockito.verifyZeroInteractions(mDisplayListener);
@@ -218,11 +292,34 @@
     }
 
     @Test
+    @RequiresFlagsEnabled(Flags.FLAG_DELAY_IMPLICIT_RR_REGISTRATION_UNTIL_RR_ACCESSED)
+    public void test_registerNativeRefreshRateCallbacks_enablesRRImplicitRegistrations()
+            throws RemoteException {
+        // Registering the display listener without supplied events doesn't subscribe to RR events
+        mDisplayManagerGlobal.registerDisplayListener(mDisplayListener, mHandler,
+                ALL_DISPLAY_EVENTS, /* packageName= */ null,
+                /* isEventFilterExplicit */ false);
+        Mockito.verify(mDisplayManager)
+                .registerCallbackWithEventMask(mCallbackCaptor.capture(), eq(ALL_DISPLAY_EVENTS));
+
+        // Native subscription for refresh rates is done
+        mDisplayManagerGlobal.registerNativeChoreographerForRefreshRateCallbacks();
+
+        // Registering the display listener without supplied events subscribe to RR events
+        mDisplayManagerGlobal.registerDisplayListener(mDisplayListener, mHandler,
+                ALL_DISPLAY_EVENTS, /* packageName= */ null,
+                /* isEventFilterExplicit */ false);
+        Mockito.verify(mDisplayManager)
+                .registerCallbackWithEventMask(mCallbackCaptor.capture(), eq(ALL_DISPLAY_EVENTS
+                        | INTERNAL_EVENT_FLAG_DISPLAY_REFRESH_RATE));
+    }
+
+    @Test
     public void testDisplayManagerGlobalRegistersWithDisplayManager_WhenThereAreListeners()
             throws RemoteException {
         mDisplayManagerGlobal.registerDisplayListener(mDisplayListener, mHandler,
                 DisplayManagerGlobal.INTERNAL_EVENT_FLAG_DISPLAY_BRIGHTNESS_CHANGED,
-                null);
+                null, /* isEventFilterExplicit */ true);
         InOrder inOrder = Mockito.inOrder(mDisplayManager);
 
         inOrder.verify(mDisplayManager)
@@ -260,9 +357,10 @@
         mDisplayManagerGlobal.registerDisplayListener(mDisplayListener, mHandler,
                 DisplayManagerGlobal.INTERNAL_EVENT_FLAG_DISPLAY_ADDED
                         | DisplayManagerGlobal.INTERNAL_EVENT_FLAG_DISPLAY_REMOVED,
-                null /* packageName */);
+                null /* packageName */, /* isEventFilterExplicit */ true);
         mDisplayManagerGlobal.registerDisplayListener(mDisplayListener2, mHandler,
-                DISPLAY_CHANGE_EVENTS, null /* packageName */);
+                DISPLAY_CHANGE_EVENTS, null /* packageName */,
+                /* isEventFilterExplicit */ true);
 
         mDisplayManagerGlobal.handleDisplayChangeFromWindowManager(321);
         waitForHandler();
diff --git a/core/tests/coretests/src/android/os/BundleMergerTest.java b/core/tests/coretests/src/android/os/BundleMergerTest.java
index 43ed821..5379993 100644
--- a/core/tests/coretests/src/android/os/BundleMergerTest.java
+++ b/core/tests/coretests/src/android/os/BundleMergerTest.java
@@ -18,6 +18,7 @@
 
 import static android.os.BundleMerger.STRATEGY_ARRAY_APPEND;
 import static android.os.BundleMerger.STRATEGY_ARRAY_LIST_APPEND;
+import static android.os.BundleMerger.STRATEGY_ARRAY_UNION;
 import static android.os.BundleMerger.STRATEGY_BOOLEAN_AND;
 import static android.os.BundleMerger.STRATEGY_BOOLEAN_OR;
 import static android.os.BundleMerger.STRATEGY_COMPARABLE_MAX;
@@ -28,6 +29,7 @@
 import static android.os.BundleMerger.STRATEGY_NUMBER_INCREMENT_FIRST;
 import static android.os.BundleMerger.STRATEGY_NUMBER_INCREMENT_FIRST_AND_ADD;
 import static android.os.BundleMerger.STRATEGY_REJECT;
+import static android.os.BundleMerger.STRATEGY_STRING_APPEND;
 import static android.os.BundleMerger.merge;
 
 import static org.junit.Assert.assertArrayEquals;
@@ -204,6 +206,33 @@
     }
 
     @Test
+    public void testStrategyArrayUnion() throws Exception {
+        assertArrayEquals(new int[] {},
+                (int[]) merge(STRATEGY_ARRAY_UNION, new int[] {}, new int[] {}));
+        assertArrayEquals(new int[] {10},
+                (int[]) merge(STRATEGY_ARRAY_UNION, new int[] {10}, new int[] {}));
+        assertArrayEquals(new int[] {20},
+                (int[]) merge(STRATEGY_ARRAY_UNION, new int[] {}, new int[] {20}));
+        assertArrayEquals(new int[] {10, 20},
+                (int[]) merge(STRATEGY_ARRAY_UNION, new int[] {10}, new int[] {20}));
+        assertArrayEquals(new int[] {10, 20, 30, 40},
+                (int[]) merge(STRATEGY_ARRAY_UNION, new int[] {10, 30}, new int[] {20, 40}));
+        assertArrayEquals(new int[] {10, 20, 30},
+                (int[]) merge(STRATEGY_ARRAY_UNION, new int[] {10, 30}, new int[] {10, 20}));
+        assertArrayEquals(new int[] {10, 20},
+                (int[]) merge(STRATEGY_ARRAY_UNION, new int[] {10, 20}, new int[] {20}));
+        assertArrayEquals(new String[] {"a", "b"},
+                (String[]) merge(STRATEGY_ARRAY_UNION, new String[] {"a"}, new String[] {"b"}));
+        assertArrayEquals(new String[] {"a", "b", "c"},
+                (String[]) merge(STRATEGY_ARRAY_UNION, new String[] {"a", "b"},
+                        new String[] {"b", "c"}));
+
+        assertThrows(Exception.class, () -> {
+            merge(STRATEGY_ARRAY_UNION, 10, 20);
+        });
+    }
+
+    @Test
     public void testStrategyArrayListAppend() throws Exception {
         assertEquals(arrayListOf(),
                 merge(STRATEGY_ARRAY_LIST_APPEND, arrayListOf(), arrayListOf()));
@@ -224,6 +253,18 @@
     }
 
     @Test
+    public void testStrategyStringAppend() throws Exception {
+        assertEquals("ab", merge(STRATEGY_STRING_APPEND, "a", "b"));
+        assertEquals("abc", merge(STRATEGY_STRING_APPEND, "a", "bc"));
+        assertEquals("abc", merge(STRATEGY_STRING_APPEND, "ab", "c"));
+        assertEquals("a,b,c,", merge(STRATEGY_STRING_APPEND, "a,", "b,c,"));
+
+        assertThrows(Exception.class, () -> {
+            merge(STRATEGY_STRING_APPEND, 10, 20);
+        });
+    }
+
+    @Test
     public void testSetDefaultMergeStrategy() throws Exception {
         final BundleMerger merger = new BundleMerger();
         merger.setDefaultMergeStrategy(STRATEGY_FIRST);
diff --git a/core/tests/coretests/src/android/os/PerfettoTraceTest.java b/core/tests/coretests/src/android/os/PerfettoTraceTest.java
index fb743d2..6915015 100644
--- a/core/tests/coretests/src/android/os/PerfettoTraceTest.java
+++ b/core/tests/coretests/src/android/os/PerfettoTraceTest.java
@@ -108,8 +108,8 @@
         PerfettoTrace.Session session = new PerfettoTrace.Session(true, traceConfig.toByteArray());
 
         PerfettoTrace.instant(FOO_CATEGORY, "event")
-                .addFlow(2)
-                .addTerminatingFlow(3)
+                .setFlow(2)
+                .setTerminatingFlow(3)
                 .addArg("long_val", 10000000000L)
                 .addArg("bool_val", true)
                 .addArg("double_val", 3.14)
diff --git a/core/tests/coretests/src/android/text/AndroidCharacterTest.java b/core/tests/coretests/src/android/text/AndroidCharacterTest.java
index 1c5986a..819a5fb 100644
--- a/core/tests/coretests/src/android/text/AndroidCharacterTest.java
+++ b/core/tests/coretests/src/android/text/AndroidCharacterTest.java
@@ -18,6 +18,7 @@
 
 import static org.junit.Assert.assertArrayEquals;
 
+import android.platform.test.annotations.DisabledOnRavenwood;
 import android.platform.test.annotations.Presubmit;
 
 import androidx.test.filters.SmallTest;
@@ -26,6 +27,7 @@
 
 @Presubmit
 @SmallTest
+@DisabledOnRavenwood(reason = "No need to make j.l.Character match behavior of AndroidCharacter")
 public class AndroidCharacterTest {
 
     @Test
diff --git a/core/tests/coretests/src/android/text/SpanColorsTest.java b/core/tests/coretests/src/android/text/SpanColorsTest.java
index d2cb8c1..4cdbd08 100644
--- a/core/tests/coretests/src/android/text/SpanColorsTest.java
+++ b/core/tests/coretests/src/android/text/SpanColorsTest.java
@@ -20,6 +20,7 @@
 
 import android.graphics.Color;
 import android.graphics.drawable.ShapeDrawable;
+import android.platform.test.annotations.DisabledOnRavenwood;
 import android.platform.test.annotations.Presubmit;
 import android.text.style.ForegroundColorSpan;
 import android.text.style.ImageSpan;
@@ -35,6 +36,7 @@
 @Presubmit
 @SmallTest
 @RunWith(AndroidJUnit4.class)
+@DisabledOnRavenwood(blockedBy = ShapeDrawable.class)
 public class SpanColorsTest {
     private final TextPaint mWorkPaint = new TextPaint();
     private SpanColors mSpanColors;
diff --git a/core/tests/coretests/src/android/text/SpannableTest.java b/core/tests/coretests/src/android/text/SpannableTest.java
index a3e6a78..710d1e2 100644
--- a/core/tests/coretests/src/android/text/SpannableTest.java
+++ b/core/tests/coretests/src/android/text/SpannableTest.java
@@ -16,10 +16,10 @@
 
 package android.text;
 
+import static org.junit.Assert.assertArrayEquals;
 import static org.junit.Assert.assertEquals;
 
 import android.platform.test.annotations.Presubmit;
-import android.test.MoreAsserts;
 
 import androidx.test.ext.junit.runners.AndroidJUnit4;
 import androidx.test.filters.SmallTest;
@@ -50,13 +50,13 @@
         // but other spans are not, unless the query region is empty, in
         // in which case any abutting spans are returned.
         spans = spannable.getSpans(0, 1, Object.class);
-        MoreAsserts.assertEquals(new Object[]{emptySpan}, spans);
+        assertArrayEquals(new Object[]{emptySpan}, spans);
         spans = spannable.getSpans(0, 2, Object.class);
-        MoreAsserts.assertEquals(new Object[]{emptySpan, unemptySpan}, spans);
+        assertArrayEquals(new Object[]{emptySpan, unemptySpan}, spans);
         spans = spannable.getSpans(1, 2, Object.class);
-        MoreAsserts.assertEquals(new Object[]{emptySpan, unemptySpan}, spans);
+        assertArrayEquals(new Object[]{emptySpan, unemptySpan}, spans);
         spans = spannable.getSpans(2, 2, Object.class);
-        MoreAsserts.assertEquals(new Object[]{unemptySpan}, spans);
+        assertArrayEquals(new Object[]{unemptySpan}, spans);
     }
 
     @Test
diff --git a/core/tests/coretests/src/android/text/StaticLayoutTest.java b/core/tests/coretests/src/android/text/StaticLayoutTest.java
index 3541900..55f38b2 100644
--- a/core/tests/coretests/src/android/text/StaticLayoutTest.java
+++ b/core/tests/coretests/src/android/text/StaticLayoutTest.java
@@ -25,6 +25,7 @@
 import android.graphics.Paint;
 import android.graphics.Paint.FontMetricsInt;
 import android.os.LocaleList;
+import android.platform.test.annotations.DisabledOnRavenwood;
 import android.platform.test.annotations.Presubmit;
 import android.text.Layout.Alignment;
 import android.text.method.EditorState;
@@ -726,6 +727,7 @@
     }
 
     @Test
+    @DisabledOnRavenwood(bug = 391342883)
     public void testLocaleSpanAffectsHyphenation() {
         TextPaint paint = new TextPaint();
         paint.setTextLocale(Locale.US);
diff --git a/core/tests/coretests/src/android/text/TextUtilsTest.java b/core/tests/coretests/src/android/text/TextUtilsTest.java
index f552265..e38c880 100644
--- a/core/tests/coretests/src/android/text/TextUtilsTest.java
+++ b/core/tests/coretests/src/android/text/TextUtilsTest.java
@@ -18,6 +18,7 @@
 
 import static android.text.TextUtils.formatSimple;
 
+import static org.junit.Assert.assertArrayEquals;
 import static org.junit.Assert.assertEquals;
 import static org.junit.Assert.assertFalse;
 import static org.junit.Assert.assertNotNull;
@@ -28,7 +29,6 @@
 
 import android.os.Parcel;
 import android.platform.test.annotations.Presubmit;
-import android.test.MoreAsserts;
 import android.text.style.StyleSpan;
 import android.text.util.Rfc822Token;
 import android.text.util.Rfc822Tokenizer;
@@ -237,7 +237,7 @@
         for (String s : splitter) {
             strings.add(s);
         }
-        MoreAsserts.assertEquals(expectedStrings, strings.toArray(new String[]{}));
+        assertArrayEquals(expectedStrings, strings.toArray(new String[]{}));
     }
 
     @Test
diff --git a/core/tests/coretests/src/android/text/format/DateFormatTest.java b/core/tests/coretests/src/android/text/format/DateFormatTest.java
index 59af6dd..c16393c 100644
--- a/core/tests/coretests/src/android/text/format/DateFormatTest.java
+++ b/core/tests/coretests/src/android/text/format/DateFormatTest.java
@@ -74,8 +74,9 @@
         DateFormatSymbols dfs = DateFormat.getIcuDateFormatSymbols(Locale.US);
         assertEquals("AM", dfs.getAmPmStrings()[0]);
         assertEquals("PM", dfs.getAmPmStrings()[1]);
-        assertEquals("a", dfs.getAmpmNarrowStrings()[0]);
-        assertEquals("p", dfs.getAmpmNarrowStrings()[1]);
+        // getAmpmNarrowStrings() is a @CorePlatformApi that we should stop using in framework
+        // assertEquals("a", dfs.getAmpmNarrowStrings()[0]);
+        // assertEquals("p", dfs.getAmpmNarrowStrings()[1]);
     }
 
     @Test
diff --git a/core/tests/coretests/src/android/text/format/DateIntervalFormatTest.java b/core/tests/coretests/src/android/text/format/DateIntervalFormatTest.java
index a07d399..e542734 100644
--- a/core/tests/coretests/src/android/text/format/DateIntervalFormatTest.java
+++ b/core/tests/coretests/src/android/text/format/DateIntervalFormatTest.java
@@ -40,6 +40,7 @@
 import android.icu.util.Calendar;
 import android.icu.util.TimeZone;
 import android.icu.util.ULocale;
+import android.platform.test.annotations.DisabledOnRavenwood;
 import android.platform.test.annotations.Presubmit;
 
 import androidx.test.ext.junit.runners.AndroidJUnit4;
@@ -686,6 +687,7 @@
     }
 
     @Test
+    @DisabledOnRavenwood(bug = 391381043)
     public void testIsLibcoreVFlagEnabled() {
         // This flag has been fully ramped. It should never be false.
         assertTrue(DateIntervalFormat.isLibcoreVFlagEnabled());
diff --git a/core/tests/coretests/src/android/text/format/DateUtilsTest.java b/core/tests/coretests/src/android/text/format/DateUtilsTest.java
index 47be893..a853d4a 100644
--- a/core/tests/coretests/src/android/text/format/DateUtilsTest.java
+++ b/core/tests/coretests/src/android/text/format/DateUtilsTest.java
@@ -21,6 +21,7 @@
 import android.content.res.Configuration;
 import android.content.res.Resources;
 import android.os.LocaleList;
+import android.platform.test.annotations.DisabledOnRavenwood;
 import android.platform.test.annotations.Presubmit;
 
 import androidx.test.ext.junit.runners.AndroidJUnit4;
@@ -116,6 +117,7 @@
     }
 
     @Test
+    @DisabledOnRavenwood(reason = "DateFormat.set24HourTimePref is not available on host JVM")
     public void testFormatSameDayTime() {
         // This test assumes a default DateFormat.is24Hour setting.
         DateFormat.set24HourTimePref(null);
diff --git a/core/tests/coretests/src/android/text/format/TimeMigrationUtilsTest.java b/core/tests/coretests/src/android/text/format/TimeMigrationUtilsTest.java
index c8cb5f3..49f3373 100644
--- a/core/tests/coretests/src/android/text/format/TimeMigrationUtilsTest.java
+++ b/core/tests/coretests/src/android/text/format/TimeMigrationUtilsTest.java
@@ -18,6 +18,7 @@
 
 import static org.junit.Assert.assertEquals;
 
+import android.platform.test.annotations.DisabledOnRavenwood;
 import android.platform.test.annotations.Presubmit;
 
 import androidx.test.ext.junit.runners.AndroidJUnit4;
@@ -72,6 +73,7 @@
      * Compares TimeMigrationUtils.formatSimpleDateTime() with the code it is replacing.
      */
     @Test
+    @DisabledOnRavenwood(blockedBy = Time.class)
     public void formatMillisAsDateTime_matchesOldBehavior() {
         // A selection of interesting locales.
         Locale[] locales = new Locale[] {
diff --git a/core/tests/coretests/src/android/text/format/TimeTest.java b/core/tests/coretests/src/android/text/format/TimeTest.java
index 6138ea1..29c5899 100644
--- a/core/tests/coretests/src/android/text/format/TimeTest.java
+++ b/core/tests/coretests/src/android/text/format/TimeTest.java
@@ -20,6 +20,7 @@
 import static org.junit.Assert.assertTrue;
 import static org.junit.Assert.fail;
 
+import android.platform.test.annotations.DisabledOnRavenwood;
 import android.platform.test.annotations.Presubmit;
 import android.util.Log;
 import android.util.TimeFormatException;
@@ -34,6 +35,7 @@
 @Presubmit
 @SmallTest
 @RunWith(AndroidJUnit4.class)
+@DisabledOnRavenwood(blockedBy = Time.class)
 public class TimeTest {
 
     @Test
diff --git a/core/tests/coretests/src/android/text/method/BackspaceTest.java b/core/tests/coretests/src/android/text/method/BackspaceTest.java
index a7ff244..646e8f9 100644
--- a/core/tests/coretests/src/android/text/method/BackspaceTest.java
+++ b/core/tests/coretests/src/android/text/method/BackspaceTest.java
@@ -16,6 +16,7 @@
 
 package android.text.method;
 
+import android.platform.test.annotations.DisabledOnRavenwood;
 import android.platform.test.annotations.Presubmit;
 import android.text.InputType;
 import android.util.KeyUtils;
@@ -41,6 +42,7 @@
 @Presubmit
 @SmallTest
 @RunWith(AndroidJUnit4.class)
+@DisabledOnRavenwood(blockedBy = EditText.class)
 public class BackspaceTest {
     private EditText mTextView;
 
diff --git a/core/tests/coretests/src/android/text/method/EditorState.java b/core/tests/coretests/src/android/text/method/EditorState.java
index 4eff7a4..633fa11 100644
--- a/core/tests/coretests/src/android/text/method/EditorState.java
+++ b/core/tests/coretests/src/android/text/method/EditorState.java
@@ -16,7 +16,7 @@
 
 package android.text.method;
 
-import static org.mockito.Matchers.any;
+import static org.mockito.ArgumentMatchers.any;
 import static org.mockito.Mockito.doNothing;
 import static org.mockito.Mockito.mock;
 import static org.mockito.Mockito.when;
@@ -181,4 +181,3 @@
         Assert.assertEquals(expected.mSelectionEnd, mSelectionEnd);
     }
 }
-
diff --git a/core/tests/coretests/src/android/text/method/ForwardDeleteTest.java b/core/tests/coretests/src/android/text/method/ForwardDeleteTest.java
index 1e4024d..8044fd7 100644
--- a/core/tests/coretests/src/android/text/method/ForwardDeleteTest.java
+++ b/core/tests/coretests/src/android/text/method/ForwardDeleteTest.java
@@ -16,6 +16,7 @@
 
 package android.text.method;
 
+import android.platform.test.annotations.DisabledOnRavenwood;
 import android.platform.test.annotations.Presubmit;
 import android.text.InputType;
 import android.util.KeyUtils;
@@ -40,6 +41,7 @@
 @Presubmit
 @SmallTest
 @RunWith(AndroidJUnit4.class)
+@DisabledOnRavenwood(blockedBy = EditText.class)
 public class ForwardDeleteTest {
     private EditText mTextView;
 
diff --git a/core/tests/coretests/src/android/text/method/InsertModeTransformationMethodTest.java b/core/tests/coretests/src/android/text/method/InsertModeTransformationMethodTest.java
index e2c1902..37ad204 100644
--- a/core/tests/coretests/src/android/text/method/InsertModeTransformationMethodTest.java
+++ b/core/tests/coretests/src/android/text/method/InsertModeTransformationMethodTest.java
@@ -19,6 +19,7 @@
 import static com.google.common.truth.Truth.assertThat;
 
 import android.content.Context;
+import android.platform.test.annotations.DisabledOnRavenwood;
 import android.platform.test.annotations.Presubmit;
 import android.platform.test.annotations.RequiresFlagsDisabled;
 import android.platform.test.annotations.RequiresFlagsEnabled;
@@ -44,6 +45,7 @@
 @Presubmit
 @SmallTest
 @RunWith(AndroidJUnit4.class)
+@DisabledOnRavenwood(blockedBy = View.class)
 public class InsertModeTransformationMethodTest {
     private static View sView;
     private static final String TEXT = "abc def";
diff --git a/core/tests/coretests/src/android/text/util/LinkifyTest.java b/core/tests/coretests/src/android/text/util/LinkifyTest.java
index 52f3b2e..98bdb0b 100644
--- a/core/tests/coretests/src/android/text/util/LinkifyTest.java
+++ b/core/tests/coretests/src/android/text/util/LinkifyTest.java
@@ -23,6 +23,7 @@
 import android.content.Context;
 import android.content.res.Configuration;
 import android.os.LocaleList;
+import android.platform.test.annotations.DisabledOnRavenwood;
 import android.text.Spannable;
 import android.text.SpannableString;
 import android.text.method.LinkMovementMethod;
@@ -46,6 +47,7 @@
  */
 @SmallTest
 @RunWith(AndroidJUnit4.class)
+@DisabledOnRavenwood(blockedBy = Linkify.class)
 public class LinkifyTest {
 
     private static final LocaleList LOCALE_LIST_US = new LocaleList(Locale.US);
diff --git a/core/tests/coretests/src/android/view/InsetsSourceConsumerTest.java b/core/tests/coretests/src/android/view/InsetsSourceConsumerTest.java
index e6361e1..6adceb9 100644
--- a/core/tests/coretests/src/android/view/InsetsSourceConsumerTest.java
+++ b/core/tests/coretests/src/android/view/InsetsSourceConsumerTest.java
@@ -230,7 +230,9 @@
                     new InsetsSourceControl(ID_STATUS_BAR, statusBars(), mLeash,
                             false /* initialVisible */, new Point(), Insets.NONE),
                     new int[1], hideTypes, new int[1], new int[1]);
-            assertTrue(mRemoveSurfaceCalled);
+            if (!android.view.inputmethod.Flags.refactorInsetsController()) {
+                assertTrue(mRemoveSurfaceCalled);
+            }
             assertEquals(0, hideTypes[0]);
         });
 
diff --git a/core/tests/coretests/src/android/widget/RemoteViewsTest.java b/core/tests/coretests/src/android/widget/RemoteViewsTest.java
index 8b0d315..9110898 100644
--- a/core/tests/coretests/src/android/widget/RemoteViewsTest.java
+++ b/core/tests/coretests/src/android/widget/RemoteViewsTest.java
@@ -22,6 +22,7 @@
 
 import static org.junit.Assert.assertArrayEquals;
 import static org.junit.Assert.assertEquals;
+import static org.junit.Assert.assertNotEquals;
 import static org.junit.Assert.assertNotNull;
 import static org.junit.Assert.assertNull;
 import static org.junit.Assert.assertSame;
@@ -1065,6 +1066,22 @@
         assertEquals(b1Memory + b2Memory, rv.estimateTotalBitmapMemoryUsage());
     }
 
+    @Test
+    public void remoteResponse_FillInIntentNestedIntentKeysCollected() {
+        Intent fillInIntent = new Intent();
+        fillInIntent.putExtra("extraIntent", new Intent());
+        RemoteViews.RemoteResponse.fromFillInIntent(fillInIntent);
+        assertNotEquals(0, fillInIntent.getExtendedFlags()
+                & Intent.EXTENDED_FLAG_NESTED_INTENT_KEYS_COLLECTED);
+
+        fillInIntent = new Intent();
+        fillInIntent.putExtra("extraIntent", new Intent());
+        RemoteViews rv = new RemoteViews(mPackage, R.layout.remote_views_test);
+        rv.setOnClickFillInIntent(R.id.view, fillInIntent);
+        assertNotEquals(0, fillInIntent.getExtendedFlags()
+                & Intent.EXTENDED_FLAG_NESTED_INTENT_KEYS_COLLECTED);
+    }
+
     private static LayoutInflater.Factory2 createLayoutInflaterFactory(String viewTypeToReplace,
             View replacementView) {
         return new LayoutInflater.Factory2() {
diff --git a/core/tests/coretests/src/com/android/internal/os/KernelSingleUidTimeReaderTest.java b/core/tests/coretests/src/com/android/internal/os/KernelSingleUidTimeReaderTest.java
index 3239598..0ba2d85 100644
--- a/core/tests/coretests/src/com/android/internal/os/KernelSingleUidTimeReaderTest.java
+++ b/core/tests/coretests/src/com/android/internal/os/KernelSingleUidTimeReaderTest.java
@@ -23,6 +23,7 @@
 import static org.junit.Assert.assertFalse;
 import static org.junit.Assert.assertTrue;
 
+import android.annotation.SuppressLint;
 import android.platform.test.annotations.IgnoreUnderRavenwood;
 import android.platform.test.ravenwood.RavenwoodRule;
 import android.util.SparseArray;
@@ -287,6 +288,7 @@
                 0, lastUidCpuTimes.size());
     }
 
+    @SuppressLint("CheckResult")
     @Test
     public void testAddDeltaFromBpf() {
         LongArrayMultiStateCounter counter = new LongArrayMultiStateCounter(2, 5);
diff --git a/core/tests/coretests/src/com/android/internal/os/LongArrayMultiStateCounterTest.java b/core/tests/coretests/src/com/android/internal/os/LongArrayMultiStateCounterTest.java
index 7e5d0a4..959e121 100644
--- a/core/tests/coretests/src/com/android/internal/os/LongArrayMultiStateCounterTest.java
+++ b/core/tests/coretests/src/com/android/internal/os/LongArrayMultiStateCounterTest.java
@@ -20,6 +20,7 @@
 
 import static org.junit.Assert.assertThrows;
 
+import android.annotation.SuppressLint;
 import android.os.BadParcelableException;
 import android.os.Parcel;
 import android.platform.test.ravenwood.RavenwoodRule;
@@ -176,6 +177,7 @@
         assertCounts(newCounter, 0, new long[]{116, 232, 364, 528});
     }
 
+    @SuppressLint("CheckResult")
     private void assertCounts(LongArrayMultiStateCounter counter, int state, long[] expected) {
         long[] counts = new long[expected.length];
         counter.getCounts(counts, state);
diff --git a/graphics/java/android/graphics/AvoidXfermode.java b/graphics/java/android/graphics/AvoidXfermode.java
index 683c157..5296ee8 100644
--- a/graphics/java/android/graphics/AvoidXfermode.java
+++ b/graphics/java/android/graphics/AvoidXfermode.java
@@ -23,6 +23,7 @@
  * @removed
  */
 @Deprecated
+@android.ravenwood.annotation.RavenwoodKeepWholeClass
 public class AvoidXfermode extends Xfermode {
 
     // these need to match the enum in AvoidXfermode.h on the native side
diff --git a/graphics/java/android/graphics/BLASTBufferQueue.java b/graphics/java/android/graphics/BLASTBufferQueue.java
index 9b9be244..9f60534 100644
--- a/graphics/java/android/graphics/BLASTBufferQueue.java
+++ b/graphics/java/android/graphics/BLASTBufferQueue.java
@@ -26,6 +26,7 @@
 /**
  * @hide
  */
+@android.ravenwood.annotation.RavenwoodKeepWholeClass
 public final class BLASTBufferQueue {
     // Note: This field is accessed by native code.
     public long mNativeObject; // BLASTBufferQueue*
diff --git a/graphics/java/android/graphics/BaseCanvas.java b/graphics/java/android/graphics/BaseCanvas.java
index a2a0f49..0ca58cc 100644
--- a/graphics/java/android/graphics/BaseCanvas.java
+++ b/graphics/java/android/graphics/BaseCanvas.java
@@ -48,6 +48,7 @@
  *
  * @hide
  */
+@android.ravenwood.annotation.RavenwoodKeepWholeClass
 public abstract class BaseCanvas {
     /**
      * Should only be assigned in constructors (or setBitmap if software canvas),
diff --git a/graphics/java/android/graphics/BaseRecordingCanvas.java b/graphics/java/android/graphics/BaseRecordingCanvas.java
index 5b1fa7b..0511bd1 100644
--- a/graphics/java/android/graphics/BaseRecordingCanvas.java
+++ b/graphics/java/android/graphics/BaseRecordingCanvas.java
@@ -44,6 +44,7 @@
  *
  * @hide
  */
+@android.ravenwood.annotation.RavenwoodKeepWholeClass
 public class BaseRecordingCanvas extends Canvas {
 
     public BaseRecordingCanvas(long nativeCanvas) {
diff --git a/graphics/java/android/graphics/Bitmap.java b/graphics/java/android/graphics/Bitmap.java
index 0c4ea79..cd5a54c 100644
--- a/graphics/java/android/graphics/Bitmap.java
+++ b/graphics/java/android/graphics/Bitmap.java
@@ -51,6 +51,7 @@
 import java.util.ArrayList;
 import java.util.WeakHashMap;
 
+@android.ravenwood.annotation.RavenwoodKeepWholeClass
 public final class Bitmap implements Parcelable {
     private static final String TAG = "Bitmap";
 
diff --git a/graphics/java/android/graphics/BitmapFactory.java b/graphics/java/android/graphics/BitmapFactory.java
index 1c20141..a5535c8 100644
--- a/graphics/java/android/graphics/BitmapFactory.java
+++ b/graphics/java/android/graphics/BitmapFactory.java
@@ -41,6 +41,7 @@
  * Creates Bitmap objects from various sources, including files, streams,
  * and byte-arrays.
  */
+@android.ravenwood.annotation.RavenwoodKeepWholeClass
 public class BitmapFactory {
     private static final int DECODE_BUFFER_SIZE = 16 * 1024;
 
diff --git a/graphics/java/android/graphics/BitmapRegionDecoder.java b/graphics/java/android/graphics/BitmapRegionDecoder.java
index 29112af..9b3f715 100644
--- a/graphics/java/android/graphics/BitmapRegionDecoder.java
+++ b/graphics/java/android/graphics/BitmapRegionDecoder.java
@@ -37,6 +37,7 @@
  * to get a decoded Bitmap of the specified region.
  *
  */
+@android.ravenwood.annotation.RavenwoodKeepWholeClass
 public final class BitmapRegionDecoder {
     private long mNativeBitmapRegionDecoder;
     private boolean mRecycled;
diff --git a/graphics/java/android/graphics/BitmapShader.java b/graphics/java/android/graphics/BitmapShader.java
index dcfff62..ac3543a 100644
--- a/graphics/java/android/graphics/BitmapShader.java
+++ b/graphics/java/android/graphics/BitmapShader.java
@@ -31,6 +31,7 @@
  * Shader used to draw a bitmap as a texture. The bitmap can be repeated or
  * mirrored by setting the tiling mode.
  */
+@android.ravenwood.annotation.RavenwoodKeepWholeClass
 public class BitmapShader extends Shader {
     /**
      * Prevent garbage collection.
diff --git a/graphics/java/android/graphics/BlendMode.java b/graphics/java/android/graphics/BlendMode.java
index c6ae680..c07af4e 100644
--- a/graphics/java/android/graphics/BlendMode.java
+++ b/graphics/java/android/graphics/BlendMode.java
@@ -19,6 +19,7 @@
 import android.annotation.NonNull;
 import android.annotation.Nullable;
 
+@android.ravenwood.annotation.RavenwoodKeepWholeClass
 public enum BlendMode {
 
     /**
diff --git a/graphics/java/android/graphics/BlendModeColorFilter.java b/graphics/java/android/graphics/BlendModeColorFilter.java
index d4e2373..d5dd0d3c 100644
--- a/graphics/java/android/graphics/BlendModeColorFilter.java
+++ b/graphics/java/android/graphics/BlendModeColorFilter.java
@@ -23,6 +23,7 @@
  * A color filter that can be used to tint the source pixels using a single
  * color and a specific {@link BlendMode}.
  */
+@android.ravenwood.annotation.RavenwoodKeepWholeClass
 public final class BlendModeColorFilter extends ColorFilter {
 
     @ColorInt final int mColor;
diff --git a/graphics/java/android/graphics/BlurMaskFilter.java b/graphics/java/android/graphics/BlurMaskFilter.java
index f3064f8..22ed524 100644
--- a/graphics/java/android/graphics/BlurMaskFilter.java
+++ b/graphics/java/android/graphics/BlurMaskFilter.java
@@ -22,6 +22,7 @@
  * inside, or straddles, the original mask's border, is controlled by the
  * Blur enum.
  */
+@android.ravenwood.annotation.RavenwoodKeepWholeClass
 public class BlurMaskFilter extends MaskFilter {
 
     public enum Blur {
diff --git a/graphics/java/android/graphics/Camera.java b/graphics/java/android/graphics/Camera.java
index 46640d7..27b695c 100644
--- a/graphics/java/android/graphics/Camera.java
+++ b/graphics/java/android/graphics/Camera.java
@@ -21,6 +21,7 @@
  * generate a matrix that can be applied, for instance, on a
  * {@link Canvas}.
  */
+@android.ravenwood.annotation.RavenwoodKeepWholeClass
 public class Camera {
     /**
      * Creates a new camera, with empty transformations.
diff --git a/graphics/java/android/graphics/Canvas.java b/graphics/java/android/graphics/Canvas.java
index 28c2ca3..9137150b 100644
--- a/graphics/java/android/graphics/Canvas.java
+++ b/graphics/java/android/graphics/Canvas.java
@@ -54,6 +54,7 @@
  * <a href="{@docRoot}guide/topics/graphics/2d-graphics.html">
  * Canvas and Drawables</a> developer guide.</p></div>
  */
+@android.ravenwood.annotation.RavenwoodKeepWholeClass
 public class Canvas extends BaseCanvas {
     private static int sCompatibilityVersion = 0;
     private static boolean sCompatibilityRestore = false;
diff --git a/graphics/java/android/graphics/CanvasProperty.java b/graphics/java/android/graphics/CanvasProperty.java
index e949584..755161d 100644
--- a/graphics/java/android/graphics/CanvasProperty.java
+++ b/graphics/java/android/graphics/CanvasProperty.java
@@ -25,6 +25,7 @@
  * TODO: Make public?
  * @hide
  */
+@android.ravenwood.annotation.RavenwoodKeepWholeClass
 public final class CanvasProperty<T> {
 
     private VirtualRefBasePtr mProperty;
diff --git a/graphics/java/android/graphics/ColorFilter.java b/graphics/java/android/graphics/ColorFilter.java
index 7050325..918f26d 100644
--- a/graphics/java/android/graphics/ColorFilter.java
+++ b/graphics/java/android/graphics/ColorFilter.java
@@ -23,6 +23,7 @@
  * each pixel drawn with that paint. This is an abstract class that should
  * never be used directly.
  */
+@android.ravenwood.annotation.RavenwoodKeepWholeClass
 public class ColorFilter {
 
     private static class NoImagePreloadHolder {
diff --git a/graphics/java/android/graphics/ColorMatrixColorFilter.java b/graphics/java/android/graphics/ColorMatrixColorFilter.java
index bfdf318..cb78a83 100644
--- a/graphics/java/android/graphics/ColorMatrixColorFilter.java
+++ b/graphics/java/android/graphics/ColorMatrixColorFilter.java
@@ -27,6 +27,7 @@
  *
  * @see ColorMatrix
  */
+@android.ravenwood.annotation.RavenwoodKeepWholeClass
 public class ColorMatrixColorFilter extends ColorFilter {
     @UnsupportedAppUsage
     private final ColorMatrix mMatrix = new ColorMatrix();
diff --git a/graphics/java/android/graphics/Compatibility.java b/graphics/java/android/graphics/Compatibility.java
index 747fbf1..f89a4f7 100644
--- a/graphics/java/android/graphics/Compatibility.java
+++ b/graphics/java/android/graphics/Compatibility.java
@@ -21,6 +21,7 @@
  * specified by the app.
  * @hide
  */
+@android.ravenwood.annotation.RavenwoodKeepWholeClass
 public final class Compatibility {
     private Compatibility() {}
 
diff --git a/graphics/java/android/graphics/ComposePathEffect.java b/graphics/java/android/graphics/ComposePathEffect.java
index 7d59ece..b380d2e 100644
--- a/graphics/java/android/graphics/ComposePathEffect.java
+++ b/graphics/java/android/graphics/ComposePathEffect.java
@@ -16,6 +16,7 @@
 
 package android.graphics;
 
+@android.ravenwood.annotation.RavenwoodKeepWholeClass
 public class ComposePathEffect extends PathEffect {
 
     /**
diff --git a/graphics/java/android/graphics/ComposeShader.java b/graphics/java/android/graphics/ComposeShader.java
index e714568..57a11d2 100644
--- a/graphics/java/android/graphics/ComposeShader.java
+++ b/graphics/java/android/graphics/ComposeShader.java
@@ -22,6 +22,7 @@
 /** A subclass of shader that returns the composition of two other shaders, combined by
     an {@link android.graphics.Xfermode} subclass.
 */
+@android.ravenwood.annotation.RavenwoodKeepWholeClass
 public class ComposeShader extends Shader {
 
     Shader mShaderA;
diff --git a/graphics/java/android/graphics/CornerPathEffect.java b/graphics/java/android/graphics/CornerPathEffect.java
index 8f4d7d9..37f0d29 100644
--- a/graphics/java/android/graphics/CornerPathEffect.java
+++ b/graphics/java/android/graphics/CornerPathEffect.java
@@ -16,6 +16,7 @@
 
 package android.graphics;
 
+@android.ravenwood.annotation.RavenwoodKeepWholeClass
 public class CornerPathEffect extends PathEffect {
 
     /**
diff --git a/graphics/java/android/graphics/DashPathEffect.java b/graphics/java/android/graphics/DashPathEffect.java
index ef3ebe8..ff3c376 100644
--- a/graphics/java/android/graphics/DashPathEffect.java
+++ b/graphics/java/android/graphics/DashPathEffect.java
@@ -16,6 +16,7 @@
 
 package android.graphics;
 
+@android.ravenwood.annotation.RavenwoodKeepWholeClass
 public class DashPathEffect extends PathEffect {
 
     /**
diff --git a/graphics/java/android/graphics/DiscretePathEffect.java b/graphics/java/android/graphics/DiscretePathEffect.java
index 3b3c9c9..77f9845 100644
--- a/graphics/java/android/graphics/DiscretePathEffect.java
+++ b/graphics/java/android/graphics/DiscretePathEffect.java
@@ -16,6 +16,7 @@
 
 package android.graphics;
 
+@android.ravenwood.annotation.RavenwoodKeepWholeClass
 public class DiscretePathEffect extends PathEffect {
 
     /**
diff --git a/graphics/java/android/graphics/DrawFilter.java b/graphics/java/android/graphics/DrawFilter.java
index c7fdcb2..505a830 100644
--- a/graphics/java/android/graphics/DrawFilter.java
+++ b/graphics/java/android/graphics/DrawFilter.java
@@ -22,6 +22,7 @@
  * can disable/enable antialiasing, or change the color for everything this is
  * drawn.
  */
+@android.ravenwood.annotation.RavenwoodKeepWholeClass
 public class DrawFilter {
 
     /**
diff --git a/graphics/java/android/graphics/EmbossMaskFilter.java b/graphics/java/android/graphics/EmbossMaskFilter.java
index 003678a..f0a661f 100644
--- a/graphics/java/android/graphics/EmbossMaskFilter.java
+++ b/graphics/java/android/graphics/EmbossMaskFilter.java
@@ -16,6 +16,7 @@
 
 package android.graphics;
 
+@android.ravenwood.annotation.RavenwoodKeepWholeClass
 public class EmbossMaskFilter extends MaskFilter {
     /**
      * Create an emboss maskfilter
diff --git a/graphics/java/android/graphics/FontFamily.java b/graphics/java/android/graphics/FontFamily.java
index 88f0e8e..09022ee 100644
--- a/graphics/java/android/graphics/FontFamily.java
+++ b/graphics/java/android/graphics/FontFamily.java
@@ -41,6 +41,7 @@
  * @deprecated Use {@link android.graphics.fonts.FontFamily} instead.
  */
 @Deprecated
+@android.ravenwood.annotation.RavenwoodKeepWholeClass
 public class FontFamily {
 
     private static String TAG = "FontFamily";
diff --git a/graphics/java/android/graphics/FontListParser.java b/graphics/java/android/graphics/FontListParser.java
index 13c4a94..8d0f128 100644
--- a/graphics/java/android/graphics/FontListParser.java
+++ b/graphics/java/android/graphics/FontListParser.java
@@ -48,6 +48,7 @@
  * Parser for font config files.
  * @hide
  */
+@android.ravenwood.annotation.RavenwoodKeepWholeClass
 public class FontListParser {
     private static final String TAG = "FontListParser";
 
diff --git a/graphics/java/android/graphics/ForceDarkType.java b/graphics/java/android/graphics/ForceDarkType.java
index 396b037..d21aef3 100644
--- a/graphics/java/android/graphics/ForceDarkType.java
+++ b/graphics/java/android/graphics/ForceDarkType.java
@@ -29,6 +29,7 @@
  *
  * @hide
  */
+@android.ravenwood.annotation.RavenwoodKeepWholeClass
 public class ForceDarkType {
     /**
      * Force dark disabled: normal, default operation.
diff --git a/graphics/java/android/graphics/FrameInfo.java b/graphics/java/android/graphics/FrameInfo.java
index 3b8f466..5202138 100644
--- a/graphics/java/android/graphics/FrameInfo.java
+++ b/graphics/java/android/graphics/FrameInfo.java
@@ -38,6 +38,7 @@
  *
  * @hide
  */
+@android.ravenwood.annotation.RavenwoodKeepWholeClass
 public final class FrameInfo {
 
     public long[] frameInfo = new long[FRAME_INFO_SIZE];
diff --git a/graphics/java/android/graphics/Gainmap.java b/graphics/java/android/graphics/Gainmap.java
index 63ca3b8..7fc13db 100644
--- a/graphics/java/android/graphics/Gainmap.java
+++ b/graphics/java/android/graphics/Gainmap.java
@@ -86,6 +86,7 @@
  * for these functions cancels out and does not affect the result, so other bases may be used
  * if preferred.
  */
+@android.ravenwood.annotation.RavenwoodKeepWholeClass
 public final class Gainmap implements Parcelable {
 
     /** @hide */
diff --git a/graphics/java/android/graphics/GraphicBuffer.java b/graphics/java/android/graphics/GraphicBuffer.java
index 6705b25..4982851 100644
--- a/graphics/java/android/graphics/GraphicBuffer.java
+++ b/graphics/java/android/graphics/GraphicBuffer.java
@@ -28,6 +28,7 @@
  * @hide
  */
 @SuppressWarnings("UnusedDeclaration")
+@android.ravenwood.annotation.RavenwoodKeepWholeClass
 public class GraphicBuffer implements Parcelable {
     // Note: keep usage flags in sync with GraphicBuffer.h and gralloc.h
     public static final int USAGE_SW_READ_NEVER = 0x0;
diff --git a/graphics/java/android/graphics/GraphicsProtos.java b/graphics/java/android/graphics/GraphicsProtos.java
index 6bc41d3..fa7eaf9 100644
--- a/graphics/java/android/graphics/GraphicsProtos.java
+++ b/graphics/java/android/graphics/GraphicsProtos.java
@@ -24,6 +24,7 @@
  *
  * @hide
  */
+@android.ravenwood.annotation.RavenwoodKeepWholeClass
 public final class GraphicsProtos {
     /** GraphicsProtos can never be an instance */
     private GraphicsProtos() {}
diff --git a/graphics/java/android/graphics/GraphicsStatsService.java b/graphics/java/android/graphics/GraphicsStatsService.java
index 7a012bc..d0b9998 100644
--- a/graphics/java/android/graphics/GraphicsStatsService.java
+++ b/graphics/java/android/graphics/GraphicsStatsService.java
@@ -74,6 +74,7 @@
  *    for the process to use.
  *
  *  @hide */
+@android.ravenwood.annotation.RavenwoodKeepWholeClass
 public class GraphicsStatsService extends IGraphicsStats.Stub {
     public static final String GRAPHICS_STATS_SERVICE = "graphicsstats";
 
diff --git a/graphics/java/android/graphics/HardwareBufferRenderer.java b/graphics/java/android/graphics/HardwareBufferRenderer.java
index e04f13c..8179870 100644
--- a/graphics/java/android/graphics/HardwareBufferRenderer.java
+++ b/graphics/java/android/graphics/HardwareBufferRenderer.java
@@ -55,6 +55,7 @@
  * HardwareBufferRenderer will never clear contents before each draw invocation so previous contents
  * in the {@link HardwareBuffer} target will be preserved across renders.
  */
+@android.ravenwood.annotation.RavenwoodKeepWholeClass
 public class HardwareBufferRenderer implements AutoCloseable {
 
     private static final ColorSpace DEFAULT_COLORSPACE = ColorSpace.get(Named.SRGB);
diff --git a/graphics/java/android/graphics/HardwareRenderer.java b/graphics/java/android/graphics/HardwareRenderer.java
index ef6b728..3444f84 100644
--- a/graphics/java/android/graphics/HardwareRenderer.java
+++ b/graphics/java/android/graphics/HardwareRenderer.java
@@ -79,6 +79,7 @@
  * Failure to do so will cause the render thread to stall on that surface, blocking all
  * HardwareRenderer instances.</p>
  */
+@android.ravenwood.annotation.RavenwoodKeepWholeClass
 public class HardwareRenderer {
     private static final String LOG_TAG = "HardwareRenderer";
 
diff --git a/graphics/java/android/graphics/HardwareRendererObserver.java b/graphics/java/android/graphics/HardwareRendererObserver.java
index d5a6a2f..9926378 100644
--- a/graphics/java/android/graphics/HardwareRendererObserver.java
+++ b/graphics/java/android/graphics/HardwareRendererObserver.java
@@ -28,6 +28,7 @@
  *
  * @hide
  */
+@android.ravenwood.annotation.RavenwoodKeepWholeClass
 public final class HardwareRendererObserver {
     private final long[] mFrameMetrics;
     private final Handler mHandler;
diff --git a/graphics/java/android/graphics/ImageDecoder.java b/graphics/java/android/graphics/ImageDecoder.java
index 6395179..419929a 100644
--- a/graphics/java/android/graphics/ImageDecoder.java
+++ b/graphics/java/android/graphics/ImageDecoder.java
@@ -44,6 +44,7 @@
 import android.net.Uri;
 import android.os.Build;
 import android.os.Trace;
+import android.ravenwood.annotation.RavenwoodIgnore;
 import android.system.ErrnoException;
 import android.system.Os;
 import android.util.DisplayMetrics;
@@ -173,6 +174,7 @@
  *  });
  *  </pre>
  */
+@android.ravenwood.annotation.RavenwoodKeepWholeClass
 public final class ImageDecoder implements AutoCloseable {
     /**
      *  Source of encoded image data.
@@ -1987,6 +1989,7 @@
      * Check if HEVC decoder is supported by the device.
      */
     @SuppressWarnings("AndroidFrameworkCompatChange")
+    @RavenwoodIgnore(blockedBy = MediaCodecList.class)
     private static boolean isHevcDecoderSupported() {
         synchronized (sIsHevcDecoderSupportedLock) {
             if (sIsHevcDecoderSupportedInitialized) {
@@ -2010,6 +2013,7 @@
      * Checks if the device supports decoding 10-bit AV1.
      */
     @SuppressWarnings("AndroidFrameworkCompatChange")  // This is not an app-visible API.
+    @RavenwoodIgnore(blockedBy = MediaCodecList.class)
     private static boolean isP010SupportedForAV1() {
         synchronized (sIsP010SupportedLock) {
             if (sIsP010SupportedFlagsInitialized) {
@@ -2025,6 +2029,7 @@
      * This method is called by JNI.
      */
     @SuppressWarnings("unused")
+    @RavenwoodIgnore(blockedBy = MediaCodecList.class)
     private static boolean isP010SupportedForHEVC() {
         synchronized (sIsP010SupportedLock) {
             if (sIsP010SupportedFlagsInitialized) {
diff --git a/graphics/java/android/graphics/ImageFormat.java b/graphics/java/android/graphics/ImageFormat.java
index b4899f9..4c9f5ac 100644
--- a/graphics/java/android/graphics/ImageFormat.java
+++ b/graphics/java/android/graphics/ImageFormat.java
@@ -24,6 +24,7 @@
 import java.lang.annotation.Retention;
 import java.lang.annotation.RetentionPolicy;
 
+@android.ravenwood.annotation.RavenwoodKeepWholeClass
 public class ImageFormat {
      /** @hide */
      @Retention(RetentionPolicy.SOURCE)
diff --git a/graphics/java/android/graphics/LayerRasterizer.java b/graphics/java/android/graphics/LayerRasterizer.java
index 25155ab..1a44248 100644
--- a/graphics/java/android/graphics/LayerRasterizer.java
+++ b/graphics/java/android/graphics/LayerRasterizer.java
@@ -20,6 +20,7 @@
  * @removed feature is not supported by hw-accerlerated or PDF backends
  */
 @Deprecated
+@android.ravenwood.annotation.RavenwoodKeepWholeClass
 public class LayerRasterizer extends Rasterizer {
     public LayerRasterizer() { }
 
diff --git a/graphics/java/android/graphics/LeakyTypefaceStorage.java b/graphics/java/android/graphics/LeakyTypefaceStorage.java
index 618e60d..25a84369 100644
--- a/graphics/java/android/graphics/LeakyTypefaceStorage.java
+++ b/graphics/java/android/graphics/LeakyTypefaceStorage.java
@@ -32,6 +32,7 @@
  *
  * @hide
  */
+@android.ravenwood.annotation.RavenwoodKeepWholeClass
 public class LeakyTypefaceStorage {
     private static final Object sLock = new Object();
 
diff --git a/graphics/java/android/graphics/LightingColorFilter.java b/graphics/java/android/graphics/LightingColorFilter.java
index fe73a1a..1afdc77 100644
--- a/graphics/java/android/graphics/LightingColorFilter.java
+++ b/graphics/java/android/graphics/LightingColorFilter.java
@@ -40,6 +40,7 @@
  * </pre>
  * The result is pinned to the <code>[0..255]</code> range for each channel.
  */
+@android.ravenwood.annotation.RavenwoodKeepWholeClass
 public class LightingColorFilter extends ColorFilter {
     @ColorInt
     private int mMul;
diff --git a/graphics/java/android/graphics/LinearGradient.java b/graphics/java/android/graphics/LinearGradient.java
index 0879371..c6566c9 100644
--- a/graphics/java/android/graphics/LinearGradient.java
+++ b/graphics/java/android/graphics/LinearGradient.java
@@ -24,6 +24,7 @@
 import android.os.Build;
 
 
+@android.ravenwood.annotation.RavenwoodKeepWholeClass
 public class LinearGradient extends Shader {
     @UnsupportedAppUsage(maxTargetSdk = Build.VERSION_CODES.R, trackingBug = 170729553)
     private float mX0;
diff --git a/graphics/java/android/graphics/MaskFilter.java b/graphics/java/android/graphics/MaskFilter.java
index d474315..b490650 100644
--- a/graphics/java/android/graphics/MaskFilter.java
+++ b/graphics/java/android/graphics/MaskFilter.java
@@ -21,6 +21,7 @@
  * an alpha-channel mask before drawing it. A subclass of MaskFilter may be
  * installed into a Paint. Blur and emboss are implemented as subclasses of MaskFilter.
  */
+@android.ravenwood.annotation.RavenwoodKeepWholeClass
 public class MaskFilter {
 
     protected void finalize() throws Throwable {
diff --git a/graphics/java/android/graphics/Mesh.java b/graphics/java/android/graphics/Mesh.java
index 6be8332..f4d841b 100644
--- a/graphics/java/android/graphics/Mesh.java
+++ b/graphics/java/android/graphics/Mesh.java
@@ -37,6 +37,7 @@
  * for the mesh. Once generated, a mesh object can be drawn through
  * {@link Canvas#drawMesh(Mesh, BlendMode, Paint)}
  */
+@android.ravenwood.annotation.RavenwoodKeepWholeClass
 public class Mesh {
     private long mNativeMeshWrapper;
     private boolean mIsIndexed;
diff --git a/graphics/java/android/graphics/MeshSpecification.java b/graphics/java/android/graphics/MeshSpecification.java
index b1aae7f..9c7e948 100644
--- a/graphics/java/android/graphics/MeshSpecification.java
+++ b/graphics/java/android/graphics/MeshSpecification.java
@@ -72,6 +72,7 @@
  * These should be kept in mind when generating a mesh specification, as exceeding them will
  * lead to errors.
  */
+@android.ravenwood.annotation.RavenwoodKeepWholeClass
 public class MeshSpecification {
     long mNativeMeshSpec;
 
diff --git a/graphics/java/android/graphics/Movie.java b/graphics/java/android/graphics/Movie.java
index 9c9535d..cefe391 100644
--- a/graphics/java/android/graphics/Movie.java
+++ b/graphics/java/android/graphics/Movie.java
@@ -27,6 +27,7 @@
  * @deprecated Prefer {@link android.graphics.drawable.AnimatedImageDrawable}.
  */
 @Deprecated
+@android.ravenwood.annotation.RavenwoodKeepWholeClass
 public class Movie {
     @UnsupportedAppUsage(maxTargetSdk = Build.VERSION_CODES.R, trackingBug = 170729553)
     private long mNativeMovie;
diff --git a/graphics/java/android/graphics/NinePatch.java b/graphics/java/android/graphics/NinePatch.java
index 382269f..00df23f 100644
--- a/graphics/java/android/graphics/NinePatch.java
+++ b/graphics/java/android/graphics/NinePatch.java
@@ -32,6 +32,7 @@
  * using a WYSIWYG graphics editor.
  * </p>
  */
+@android.ravenwood.annotation.RavenwoodKeepWholeClass
 public class NinePatch {
     /**
      * Struct of inset information attached to a 9 patch bitmap.
diff --git a/graphics/java/android/graphics/Paint.java b/graphics/java/android/graphics/Paint.java
index 3d4dccf..a0ca098 100644
--- a/graphics/java/android/graphics/Paint.java
+++ b/graphics/java/android/graphics/Paint.java
@@ -66,6 +66,7 @@
  * The Paint class holds the style and color information about how to draw
  * geometries, text and bitmaps.
  */
+@android.ravenwood.annotation.RavenwoodKeepWholeClass
 public class Paint {
     private static final String TAG = "Paint";
 
diff --git a/graphics/java/android/graphics/PaintFlagsDrawFilter.java b/graphics/java/android/graphics/PaintFlagsDrawFilter.java
index 2326611..f4c49b1 100644
--- a/graphics/java/android/graphics/PaintFlagsDrawFilter.java
+++ b/graphics/java/android/graphics/PaintFlagsDrawFilter.java
@@ -16,6 +16,7 @@
 
 package android.graphics;
 
+@android.ravenwood.annotation.RavenwoodKeepWholeClass
 public class PaintFlagsDrawFilter extends DrawFilter {
     /**
      * Subclass of DrawFilter that affects every paint by first clearing
diff --git a/graphics/java/android/graphics/PathDashPathEffect.java b/graphics/java/android/graphics/PathDashPathEffect.java
index 2b6a6ed..dc92e6c 100644
--- a/graphics/java/android/graphics/PathDashPathEffect.java
+++ b/graphics/java/android/graphics/PathDashPathEffect.java
@@ -16,6 +16,7 @@
 
 package android.graphics;
 
+@android.ravenwood.annotation.RavenwoodKeepWholeClass
 public class PathDashPathEffect extends PathEffect {
 
     public enum Style {
diff --git a/graphics/java/android/graphics/PathEffect.java b/graphics/java/android/graphics/PathEffect.java
index 3292501..9bb7193 100644
--- a/graphics/java/android/graphics/PathEffect.java
+++ b/graphics/java/android/graphics/PathEffect.java
@@ -21,6 +21,7 @@
  * the geometry of a drawing primitive before it is transformed by the
  * canvas' matrix and drawn.
  */
+@android.ravenwood.annotation.RavenwoodKeepWholeClass
 public class PathEffect {
 
     protected void finalize() throws Throwable {
diff --git a/graphics/java/android/graphics/PathIterator.java b/graphics/java/android/graphics/PathIterator.java
index d7caabf..1ed70d0 100644
--- a/graphics/java/android/graphics/PathIterator.java
+++ b/graphics/java/android/graphics/PathIterator.java
@@ -34,6 +34,7 @@
  * <code>PathIterator</code> can be used to query a given {@link Path} object, to discover its
  * operations and point values.
  */
+@android.ravenwood.annotation.RavenwoodKeepWholeClass
 public class PathIterator implements Iterator<PathIterator.Segment> {
 
     private final float[] mPointsArray;
@@ -47,9 +48,11 @@
     private static final boolean IS_DALVIK = "dalvik".equalsIgnoreCase(
             System.getProperty("java.vm.name"));
 
-    private static final NativeAllocationRegistry sRegistry =
-            NativeAllocationRegistry.createMalloced(
-                    PathIterator.class.getClassLoader(), nGetFinalizer());
+    private static class NoImagePreloadHolder {
+        private static final NativeAllocationRegistry sRegistry =
+                NativeAllocationRegistry.createMalloced(
+                        PathIterator.class.getClassLoader(), nGetFinalizer());
+    }
 
     /**
      * The <code>Verb</code> indicates the operation for a given segment of a path. These
@@ -69,6 +72,11 @@
     public static final int VERB_CLOSE = 5;
     public static final int VERB_DONE = 6;
 
+
+    static {
+        // Keep <cinit> exist in bytecode
+    }
+
     /**
      * Returns a {@link PathIterator} object for this path, which can be used to query the
      * data (operations and points) in the path. Iterators can only be used on Path objects
@@ -90,7 +98,7 @@
             mPointsArray = new float[POINT_ARRAY_SIZE];
             mPointsAddress = 0;
         }
-        sRegistry.registerNativeAllocation(this, mNativeIterator);
+        NoImagePreloadHolder.sRegistry.registerNativeAllocation(this, mNativeIterator);
     }
 
     /**
diff --git a/graphics/java/android/graphics/PathMeasure.java b/graphics/java/android/graphics/PathMeasure.java
index 2c6cfa5..4d123db 100644
--- a/graphics/java/android/graphics/PathMeasure.java
+++ b/graphics/java/android/graphics/PathMeasure.java
@@ -16,6 +16,7 @@
 
 package android.graphics;
 
+@android.ravenwood.annotation.RavenwoodKeepWholeClass
 public class PathMeasure {
     private Path mPath;
 
diff --git a/graphics/java/android/graphics/Picture.java b/graphics/java/android/graphics/Picture.java
index ee4165b..54eb2bc 100644
--- a/graphics/java/android/graphics/Picture.java
+++ b/graphics/java/android/graphics/Picture.java
@@ -33,6 +33,7 @@
  * <p class="note"><strong>Note:</strong> Prior to API level 23 a picture cannot
  * be replayed on a hardware accelerated canvas.</p>
  */
+@android.ravenwood.annotation.RavenwoodKeepWholeClass
 public class Picture {
     private PictureCanvas mRecordingCanvas;
     // TODO: Figure out if this was a false-positive
diff --git a/graphics/java/android/graphics/PixelXorXfermode.java b/graphics/java/android/graphics/PixelXorXfermode.java
index 27884e0..6427852 100644
--- a/graphics/java/android/graphics/PixelXorXfermode.java
+++ b/graphics/java/android/graphics/PixelXorXfermode.java
@@ -20,6 +20,7 @@
  * @removed
  */
 @Deprecated
+@android.ravenwood.annotation.RavenwoodKeepWholeClass
 public class PixelXorXfermode extends Xfermode {
 
     public PixelXorXfermode(int opColor) {
diff --git a/graphics/java/android/graphics/PorterDuff.java b/graphics/java/android/graphics/PorterDuff.java
index eb940e2..730a804 100644
--- a/graphics/java/android/graphics/PorterDuff.java
+++ b/graphics/java/android/graphics/PorterDuff.java
@@ -26,6 +26,7 @@
  *
  * Consider using {@link BlendMode} instead as it provides a wider variety of tinting options
  */
+@android.ravenwood.annotation.RavenwoodKeepWholeClass
 public class PorterDuff {
     /**
      * {@usesMathJax}
diff --git a/graphics/java/android/graphics/PorterDuffColorFilter.java b/graphics/java/android/graphics/PorterDuffColorFilter.java
index 0700f21..777ef6c 100644
--- a/graphics/java/android/graphics/PorterDuffColorFilter.java
+++ b/graphics/java/android/graphics/PorterDuffColorFilter.java
@@ -25,6 +25,7 @@
  * A color filter that can be used to tint the source pixels using a single
  * color and a specific {@link PorterDuff Porter-Duff composite mode}.
  */
+@android.ravenwood.annotation.RavenwoodKeepWholeClass
 public class PorterDuffColorFilter extends ColorFilter {
     @ColorInt
     private int mColor;
diff --git a/graphics/java/android/graphics/PorterDuffXfermode.java b/graphics/java/android/graphics/PorterDuffXfermode.java
index 83d0507..e10d7370 100644
--- a/graphics/java/android/graphics/PorterDuffXfermode.java
+++ b/graphics/java/android/graphics/PorterDuffXfermode.java
@@ -23,6 +23,7 @@
  * information on the available alpha compositing and blending modes.</p>
  *
  */
+@android.ravenwood.annotation.RavenwoodKeepWholeClass
 public class PorterDuffXfermode extends Xfermode {
     /**
      * Create an xfermode that uses the specified porter-duff mode.
diff --git a/graphics/java/android/graphics/PostProcessor.java b/graphics/java/android/graphics/PostProcessor.java
index 6fed39b..066214a 100644
--- a/graphics/java/android/graphics/PostProcessor.java
+++ b/graphics/java/android/graphics/PostProcessor.java
@@ -37,6 +37,7 @@
  *
  *  <p>Supplied to ImageDecoder via {@link ImageDecoder#setPostProcessor setPostProcessor}.</p>
  */
+@android.ravenwood.annotation.RavenwoodKeepWholeClass
 public interface PostProcessor {
     /**
      *  Do any processing after (for example) decoding.
diff --git a/graphics/java/android/graphics/RadialGradient.java b/graphics/java/android/graphics/RadialGradient.java
index e582e66..06e92ea 100644
--- a/graphics/java/android/graphics/RadialGradient.java
+++ b/graphics/java/android/graphics/RadialGradient.java
@@ -24,6 +24,7 @@
 import android.compat.annotation.UnsupportedAppUsage;
 import android.os.Build;
 
+@android.ravenwood.annotation.RavenwoodKeepWholeClass
 public class RadialGradient extends Shader {
     @UnsupportedAppUsage(maxTargetSdk = Build.VERSION_CODES.R, trackingBug = 170729553)
     private float mX;
diff --git a/graphics/java/android/graphics/Rasterizer.java b/graphics/java/android/graphics/Rasterizer.java
index 5750954..5e67da5 100644
--- a/graphics/java/android/graphics/Rasterizer.java
+++ b/graphics/java/android/graphics/Rasterizer.java
@@ -24,6 +24,7 @@
 /**
  * @removed feature is not supported by hw-accerlerated or PDF backends
  */
+@android.ravenwood.annotation.RavenwoodKeepWholeClass
 public class Rasterizer {
 
     protected void finalize() throws Throwable { }
diff --git a/graphics/java/android/graphics/RecordingCanvas.java b/graphics/java/android/graphics/RecordingCanvas.java
index cc5b3b9..a56f461 100644
--- a/graphics/java/android/graphics/RecordingCanvas.java
+++ b/graphics/java/android/graphics/RecordingCanvas.java
@@ -33,6 +33,7 @@
  * {@link RenderNode#endRecording()} is called. It must not be retained beyond that as it is
  * internally reused.
  */
+@android.ravenwood.annotation.RavenwoodKeepWholeClass
 public final class RecordingCanvas extends BaseRecordingCanvas {
     // The recording canvas pool should be large enough to handle a deeply nested
     // view hierarchy because display lists are generated recursively.
diff --git a/graphics/java/android/graphics/Region.java b/graphics/java/android/graphics/Region.java
index 2970873..e2215d4 100644
--- a/graphics/java/android/graphics/Region.java
+++ b/graphics/java/android/graphics/Region.java
@@ -23,6 +23,7 @@
 import android.os.Parcelable;
 import android.util.Pools.SynchronizedPool;
 
+@android.ravenwood.annotation.RavenwoodKeepWholeClass
 public class Region implements Parcelable {
 
     private static final int MAX_POOL_SIZE = 10;
diff --git a/graphics/java/android/graphics/RegionIterator.java b/graphics/java/android/graphics/RegionIterator.java
index 443b23c..5d74487 100644
--- a/graphics/java/android/graphics/RegionIterator.java
+++ b/graphics/java/android/graphics/RegionIterator.java
@@ -16,6 +16,7 @@
 
 package android.graphics;
 
+@android.ravenwood.annotation.RavenwoodKeepWholeClass
 public class RegionIterator {
 
     /**
diff --git a/graphics/java/android/graphics/RenderEffect.java b/graphics/java/android/graphics/RenderEffect.java
index b8a4685..06bfb82 100644
--- a/graphics/java/android/graphics/RenderEffect.java
+++ b/graphics/java/android/graphics/RenderEffect.java
@@ -30,6 +30,7 @@
  * Additionally a {@link RenderEffect} can be applied to a View's backing RenderNode through
  * {@link android.view.View#setRenderEffect(RenderEffect)}
  */
+@android.ravenwood.annotation.RavenwoodKeepWholeClass
 public final class RenderEffect {
 
     private static class RenderEffectHolder {
diff --git a/graphics/java/android/graphics/RenderNode.java b/graphics/java/android/graphics/RenderNode.java
index 03a8b30..fa41876 100644
--- a/graphics/java/android/graphics/RenderNode.java
+++ b/graphics/java/android/graphics/RenderNode.java
@@ -192,6 +192,7 @@
  * top-level content is desired, and finally calling {@link Surface#unlockCanvasAndPost(Canvas)}.
  * </p>
  */
+@android.ravenwood.annotation.RavenwoodKeepWholeClass
 public final class RenderNode {
 
     // Use a Holder to allow static initialization in the boot image.
diff --git a/graphics/java/android/graphics/RuntimeColorFilter.java b/graphics/java/android/graphics/RuntimeColorFilter.java
index a64acfe..06aecc3 100644
--- a/graphics/java/android/graphics/RuntimeColorFilter.java
+++ b/graphics/java/android/graphics/RuntimeColorFilter.java
@@ -37,6 +37,7 @@
  * </pre>
  */
 @FlaggedApi(Flags.FLAG_RUNTIME_COLOR_FILTERS_BLENDERS)
+@android.ravenwood.annotation.RavenwoodKeepWholeClass
 public class RuntimeColorFilter extends ColorFilter {
 
     private String mAgsl;
diff --git a/graphics/java/android/graphics/RuntimeShader.java b/graphics/java/android/graphics/RuntimeShader.java
index db2376e..6464f72 100644
--- a/graphics/java/android/graphics/RuntimeShader.java
+++ b/graphics/java/android/graphics/RuntimeShader.java
@@ -248,6 +248,7 @@
  * the bitmap), remember that the coordinates are local to the canvas.</p>
  *
  */
+@android.ravenwood.annotation.RavenwoodKeepWholeClass
 public class RuntimeShader extends Shader {
 
     private static class NoImagePreloadHolder {
diff --git a/graphics/java/android/graphics/RuntimeXfermode.java b/graphics/java/android/graphics/RuntimeXfermode.java
index c8a0b1a..1e20bd3 100644
--- a/graphics/java/android/graphics/RuntimeXfermode.java
+++ b/graphics/java/android/graphics/RuntimeXfermode.java
@@ -39,6 +39,7 @@
  * </pre>
  */
 @FlaggedApi(Flags.FLAG_RUNTIME_COLOR_FILTERS_BLENDERS)
+@android.ravenwood.annotation.RavenwoodKeepWholeClass
 public class RuntimeXfermode extends Xfermode {
 
     private static class NoImagePreloadHolder {
diff --git a/graphics/java/android/graphics/Shader.java b/graphics/java/android/graphics/Shader.java
index 4d6bead..369fab4 100644
--- a/graphics/java/android/graphics/Shader.java
+++ b/graphics/java/android/graphics/Shader.java
@@ -29,6 +29,7 @@
  * paint.setShader(shader). After that any object (other than a bitmap) that is
  * drawn with that paint will get its color(s) from the shader.
  */
+@android.ravenwood.annotation.RavenwoodKeepWholeClass
 public class Shader {
 
     private static class NoImagePreloadHolder {
diff --git a/graphics/java/android/graphics/SumPathEffect.java b/graphics/java/android/graphics/SumPathEffect.java
index 8fedc31..3543e10 100644
--- a/graphics/java/android/graphics/SumPathEffect.java
+++ b/graphics/java/android/graphics/SumPathEffect.java
@@ -16,6 +16,7 @@
 
 package android.graphics;
 
+@android.ravenwood.annotation.RavenwoodKeepWholeClass
 public class SumPathEffect extends PathEffect {
 
     /**
diff --git a/graphics/java/android/graphics/SurfaceTexture.java b/graphics/java/android/graphics/SurfaceTexture.java
index 5caedba..df384ea 100644
--- a/graphics/java/android/graphics/SurfaceTexture.java
+++ b/graphics/java/android/graphics/SurfaceTexture.java
@@ -78,6 +78,7 @@
  * frame-available callback is called on an arbitrary thread, so unless special care is taken {@link
  * #updateTexImage} should not be called directly from the callback.
  */
+@android.ravenwood.annotation.RavenwoodKeepWholeClass
 public class SurfaceTexture {
     private final Looper mCreatorLooper;
     @UnsupportedAppUsage(maxTargetSdk = Build.VERSION_CODES.R, trackingBug = 170729553)
diff --git a/graphics/java/android/graphics/SweepGradient.java b/graphics/java/android/graphics/SweepGradient.java
index 3a29395..9421925 100644
--- a/graphics/java/android/graphics/SweepGradient.java
+++ b/graphics/java/android/graphics/SweepGradient.java
@@ -23,6 +23,7 @@
 import android.compat.annotation.UnsupportedAppUsage;
 import android.os.Build;
 
+@android.ravenwood.annotation.RavenwoodKeepWholeClass
 public class SweepGradient extends Shader {
     @UnsupportedAppUsage(maxTargetSdk = Build.VERSION_CODES.R, trackingBug = 170729553)
     private float mCx;
diff --git a/graphics/java/android/graphics/TableMaskFilter.java b/graphics/java/android/graphics/TableMaskFilter.java
index 204f970..ca7627c 100644
--- a/graphics/java/android/graphics/TableMaskFilter.java
+++ b/graphics/java/android/graphics/TableMaskFilter.java
@@ -21,6 +21,7 @@
 /**
  * @hide
  */
+@android.ravenwood.annotation.RavenwoodKeepWholeClass
 public class TableMaskFilter extends MaskFilter {
 
     public TableMaskFilter(byte[] table) {
diff --git a/graphics/java/android/graphics/TemporaryBuffer.java b/graphics/java/android/graphics/TemporaryBuffer.java
index ef3f7f7..681c48e 100644
--- a/graphics/java/android/graphics/TemporaryBuffer.java
+++ b/graphics/java/android/graphics/TemporaryBuffer.java
@@ -23,6 +23,7 @@
 /**
  * @hide
  */
+@android.ravenwood.annotation.RavenwoodKeepWholeClass
 public class TemporaryBuffer {
     @UnsupportedAppUsage
     public static char[] obtain(int len) {
diff --git a/graphics/java/android/graphics/TextureLayer.java b/graphics/java/android/graphics/TextureLayer.java
index ac1bd69..981b78a 100644
--- a/graphics/java/android/graphics/TextureLayer.java
+++ b/graphics/java/android/graphics/TextureLayer.java
@@ -29,6 +29,7 @@
  *
  * @hide TODO: Make this a SystemApi for b/155905258
  */
+@android.ravenwood.annotation.RavenwoodKeepWholeClass
 public final class TextureLayer implements AutoCloseable {
     private HardwareRenderer mRenderer;
     private VirtualRefBasePtr mFinalizer;
diff --git a/graphics/java/android/graphics/Typeface.java b/graphics/java/android/graphics/Typeface.java
index 874b847..d1aca34 100644
--- a/graphics/java/android/graphics/Typeface.java
+++ b/graphics/java/android/graphics/Typeface.java
@@ -42,6 +42,7 @@
 import android.os.Trace;
 import android.provider.FontRequest;
 import android.provider.FontsContract;
+import android.ravenwood.annotation.RavenwoodReplace;
 import android.system.ErrnoException;
 import android.system.OsConstants;
 import android.text.FontConfig;
@@ -86,6 +87,7 @@
  * textSize, textSkewX, textScaleX to specify
  * how text appears when drawn (and measured).
  */
+@android.ravenwood.annotation.RavenwoodKeepWholeClass
 public class Typeface {
 
     private static String TAG = "Typeface";
@@ -93,9 +95,11 @@
     /** @hide */
     public static final boolean ENABLE_LAZY_TYPEFACE_INITIALIZATION = true;
 
-    private static final NativeAllocationRegistry sRegistry =
-            NativeAllocationRegistry.createMalloced(
-            Typeface.class.getClassLoader(), nativeGetReleaseFunc());
+    private static class NoImagePreloadHolder {
+        static final NativeAllocationRegistry sRegistry =
+                NativeAllocationRegistry.createMalloced(
+                        Typeface.class.getClassLoader(), nativeGetReleaseFunc());
+    }
 
     /** The default NORMAL typeface object */
     public static final Typeface DEFAULT = null;
@@ -1284,7 +1288,7 @@
         }
 
         native_instance = ni;
-        mCleaner = sRegistry.registerNativeAllocation(this, native_instance);
+        mCleaner = NoImagePreloadHolder.sRegistry.registerNativeAllocation(this, native_instance);
         mStyle = nativeGetStyle(ni);
         mWeight = nativeGetWeight(ni);
         mIsVariationInstance = nativeIsVariationInstance(ni);
@@ -1560,9 +1564,23 @@
     }
 
     static {
+        staticInitializer();
+    }
+
+    @RavenwoodReplace(reason = "Prevent circular reference on host side JVM", bug = 337329128)
+    private static void staticInitializer() {
+        init();
+    }
+
+    private static void staticInitializer$ravenwood() {
+        /* no-op */
+    }
+
+    /** @hide */
+    public static void init() {
         // Preload Roboto-Regular.ttf in Zygote for improving app launch performance.
-        preloadFontFile("/system/fonts/Roboto-Regular.ttf");
-        preloadFontFile("/system/fonts/RobotoStatic-Regular.ttf");
+        preloadFontFile(SystemFonts.SYSTEM_FONT_DIR + "Roboto-Regular.ttf");
+        preloadFontFile(SystemFonts.SYSTEM_FONT_DIR + "RobotoStatic-Regular.ttf");
 
         String locale = SystemProperties.get("persist.sys.locale", "en-US");
         String script = ULocale.addLikelySubtags(ULocale.forLanguageTag(locale)).getScript();
@@ -1642,6 +1660,21 @@
         setSystemFontMap(typefaceMap);
     }
 
+    /**
+     * {@link #loadPreinstalledSystemFontMap()} does not actually initialize the native
+     * system font APIs. Add a new method to actually load the font files without going
+     * through SharedMemory.
+     *
+     * @hide
+     */
+    public static void loadNativeSystemFonts() {
+        synchronized (SYSTEM_FONT_MAP_LOCK) {
+            for (var type : sSystemFontMap.values()) {
+                nativeAddFontCollections(type.native_instance);
+            }
+        }
+    }
+
     static {
         if (!ENABLE_LAZY_TYPEFACE_INITIALIZATION) {
             loadPreinstalledSystemFontMap();
diff --git a/graphics/java/android/graphics/Xfermode.java b/graphics/java/android/graphics/Xfermode.java
index fb689e4..eda9e3c 100644
--- a/graphics/java/android/graphics/Xfermode.java
+++ b/graphics/java/android/graphics/Xfermode.java
@@ -28,4 +28,5 @@
  * specified in the Modes enum. When an Xfermode is assigned to a Paint, then
  * objects drawn with that paint have the xfermode applied.
  */
+@android.ravenwood.annotation.RavenwoodKeepWholeClass
 public class Xfermode {}
diff --git a/graphics/java/android/graphics/YuvImage.java b/graphics/java/android/graphics/YuvImage.java
index b0c7f20..2b7f404 100644
--- a/graphics/java/android/graphics/YuvImage.java
+++ b/graphics/java/android/graphics/YuvImage.java
@@ -32,6 +32,7 @@
  * To compress a rectangle region in the YUV data, users have to specify the
  * region by left, top, width and height.
  */
+@android.ravenwood.annotation.RavenwoodKeepWholeClass
 public class YuvImage {
 
     /**
diff --git a/graphics/java/android/graphics/fonts/Font.java b/graphics/java/android/graphics/fonts/Font.java
index 2893177..8be3400 100644
--- a/graphics/java/android/graphics/fonts/Font.java
+++ b/graphics/java/android/graphics/fonts/Font.java
@@ -44,6 +44,7 @@
 import java.io.InputStream;
 import java.nio.ByteBuffer;
 import java.nio.ByteOrder;
+import java.nio.channels.Channels;
 import java.nio.channels.FileChannel;
 import java.util.Arrays;
 import java.util.Collections;
@@ -54,6 +55,7 @@
 /**
  * A font class can be used for creating FontFamily.
  */
+@android.ravenwood.annotation.RavenwoodKeepWholeClass
 public final class Font {
     private static final String TAG = "Font";
 
@@ -293,7 +295,14 @@
                 int capacity = assetStream.available();
                 ByteBuffer buffer = ByteBuffer.allocateDirect(capacity);
                 buffer.order(ByteOrder.nativeOrder());
-                assetStream.read(buffer.array(), buffer.arrayOffset(), assetStream.available());
+                if (buffer.hasArray()) {
+                    assetStream.read(buffer.array(), buffer.arrayOffset(), assetStream.available());
+                } else {
+                    // Direct buffer does not have a backing array on Ravenwood,
+                    // wrap it with a channel and read from it
+                    var ch = Channels.newChannel(assetStream);
+                    ch.read(buffer.duplicate());
+                }
 
                 if (assetStream.read() != -1) {
                     throw new IOException("Unable to access full contents of " + path);
diff --git a/graphics/java/android/graphics/fonts/FontCustomizationParser.java b/graphics/java/android/graphics/fonts/FontCustomizationParser.java
index b7bf055..732a5f3 100644
--- a/graphics/java/android/graphics/fonts/FontCustomizationParser.java
+++ b/graphics/java/android/graphics/fonts/FontCustomizationParser.java
@@ -43,6 +43,7 @@
  *
  * @hide
  */
+@android.ravenwood.annotation.RavenwoodKeepWholeClass
 public class FontCustomizationParser {
     private static final String TAG = "FontCustomizationParser";
 
diff --git a/graphics/java/android/graphics/fonts/FontFamily.java b/graphics/java/android/graphics/fonts/FontFamily.java
index 5a7b0bb..0ab4639 100644
--- a/graphics/java/android/graphics/fonts/FontFamily.java
+++ b/graphics/java/android/graphics/fonts/FontFamily.java
@@ -66,6 +66,7 @@
  * </p>
  *
  */
+@android.ravenwood.annotation.RavenwoodKeepWholeClass
 public final class FontFamily {
 
     private static final String TAG = "FontFamily";
diff --git a/graphics/java/android/graphics/fonts/FontFileUtil.java b/graphics/java/android/graphics/fonts/FontFileUtil.java
index abcafb6..305ab3b 100644
--- a/graphics/java/android/graphics/fonts/FontFileUtil.java
+++ b/graphics/java/android/graphics/fonts/FontFileUtil.java
@@ -32,6 +32,7 @@
  * Provides a utility for font file operations.
  * @hide
  */
+@android.ravenwood.annotation.RavenwoodKeepWholeClass
 public class FontFileUtil {
 
     private FontFileUtil() {}  // Do not instantiate
diff --git a/graphics/java/android/graphics/fonts/FontStyle.java b/graphics/java/android/graphics/fonts/FontStyle.java
index 48969aa..b3ddbed 100644
--- a/graphics/java/android/graphics/fonts/FontStyle.java
+++ b/graphics/java/android/graphics/fonts/FontStyle.java
@@ -44,6 +44,7 @@
  * </p>
  *
  */
+@android.ravenwood.annotation.RavenwoodKeepWholeClass
 public final class FontStyle {
     private static final String TAG = "FontStyle";
 
diff --git a/graphics/java/android/graphics/fonts/FontVariationAxis.java b/graphics/java/android/graphics/fonts/FontVariationAxis.java
index 30a248b..1d71594 100644
--- a/graphics/java/android/graphics/fonts/FontVariationAxis.java
+++ b/graphics/java/android/graphics/fonts/FontVariationAxis.java
@@ -31,6 +31,7 @@
 /**
  * Class that holds information about single font variation axis.
  */
+@android.ravenwood.annotation.RavenwoodKeepWholeClass
 public final class FontVariationAxis {
     @UnsupportedAppUsage(maxTargetSdk = Build.VERSION_CODES.P, trackingBug = 115609023)
     private final int mTag;
diff --git a/graphics/java/android/graphics/fonts/SystemFonts.java b/graphics/java/android/graphics/fonts/SystemFonts.java
index 0e25c34..599c426 100644
--- a/graphics/java/android/graphics/fonts/SystemFonts.java
+++ b/graphics/java/android/graphics/fonts/SystemFonts.java
@@ -25,6 +25,7 @@
 import android.graphics.FontListParser;
 import android.graphics.Typeface;
 import android.os.LocaleList;
+import android.ravenwood.annotation.RavenwoodReplace;
 import android.text.FontConfig;
 import android.util.ArrayMap;
 import android.util.Log;
@@ -32,6 +33,7 @@
 
 import com.android.internal.annotations.GuardedBy;
 import com.android.internal.annotations.VisibleForTesting;
+import com.android.internal.ravenwood.RavenwoodEnvironment;
 
 import org.xmlpull.v1.XmlPullParserException;
 
@@ -50,23 +52,46 @@
 /**
  * Provides the system font configurations.
  */
+@android.ravenwood.annotation.RavenwoodKeepWholeClass
 public final class SystemFonts {
     private static final String TAG = "SystemFonts";
 
-    private static final String FONTS_XML = "/system/etc/font_fallback.xml";
-    private static final String LEGACY_FONTS_XML = "/system/etc/fonts.xml";
+    private static final String FONTS_XML = getFontsXmlDir() + "font_fallback.xml";
+    /** @hide */
+    public static final String LEGACY_FONTS_XML = getFontsXmlDir() + "fonts.xml";
 
     /** @hide */
-    public static final String SYSTEM_FONT_DIR = "/system/fonts/";
+    public static final String SYSTEM_FONT_DIR = getSystemFontDir();
     private static final String OEM_XML = "/product/etc/fonts_customization.xml";
     /** @hide */
     public static final String OEM_FONT_DIR = "/product/fonts/";
 
+    private static final String DEVICE_FONTS_XML_DIR = "/system/etc/";
+    private static final String DEVICE_FONT_DIR = "/system/fonts/";
+
     private SystemFonts() {}  // Do not instansiate.
 
     private static final Object LOCK = new Object();
     private static @GuardedBy("sLock") Set<Font> sAvailableFonts;
 
+    @RavenwoodReplace
+    private static String getFontsXmlDir() {
+        return DEVICE_FONTS_XML_DIR;
+    }
+
+    private static String getFontsXmlDir$ravenwood() {
+        return RavenwoodEnvironment.getInstance().getRavenwoodRuntimePath() + "fonts/";
+    }
+
+    @RavenwoodReplace
+    private static String getSystemFontDir() {
+        return DEVICE_FONT_DIR;
+    }
+
+    private static String getSystemFontDir$ravenwood() {
+        return RavenwoodEnvironment.getInstance().getRavenwoodRuntimePath() + "fonts/";
+    }
+
     /**
      * Returns all available font files in the system.
      *
diff --git a/graphics/java/android/graphics/text/GraphemeBreak.java b/graphics/java/android/graphics/text/GraphemeBreak.java
index f82b2fd..0bc1e3b 100644
--- a/graphics/java/android/graphics/text/GraphemeBreak.java
+++ b/graphics/java/android/graphics/text/GraphemeBreak.java
@@ -17,6 +17,7 @@
 package android.graphics.text;
 
 /** @hide */
+@android.ravenwood.annotation.RavenwoodKeepWholeClass
 public class GraphemeBreak {
     private GraphemeBreak() { }
 
diff --git a/graphics/java/android/graphics/text/LineBreakConfig.java b/graphics/java/android/graphics/text/LineBreakConfig.java
index 5a1086c..fa3cfbd 100644
--- a/graphics/java/android/graphics/text/LineBreakConfig.java
+++ b/graphics/java/android/graphics/text/LineBreakConfig.java
@@ -24,12 +24,13 @@
 import android.annotation.NonNull;
 import android.annotation.Nullable;
 import android.annotation.SuppressLint;
-import android.app.ActivityThread;
 import android.os.Build;
 import android.os.LocaleList;
 import android.os.Parcel;
 import android.os.Parcelable;
 
+import dalvik.system.VMRuntime;
+
 import java.lang.annotation.Retention;
 import java.lang.annotation.RetentionPolicy;
 import java.util.Objects;
@@ -41,6 +42,7 @@
  * <a href="https://www.w3.org/TR/css-text-3/#line-break-property" class="external">
  * line-break property</a> for more information.
  */
+@android.ravenwood.annotation.RavenwoodKeepWholeClass
 public final class LineBreakConfig implements Parcelable {
     /**
      * No hyphenation preference is specified.
@@ -484,8 +486,7 @@
      * @hide
      */
     public static @LineBreakStyle int getResolvedLineBreakStyle(@Nullable LineBreakConfig config) {
-        final int targetSdkVersion = ActivityThread.currentApplication().getApplicationInfo()
-                .targetSdkVersion;
+        final int targetSdkVersion = VMRuntime.getRuntime().getTargetSdkVersion();
         final int defaultStyle;
         final int vicVersion = Build.VERSION_CODES.VANILLA_ICE_CREAM;
         if (targetSdkVersion >= vicVersion) {
@@ -519,8 +520,7 @@
      */
     public static @LineBreakWordStyle int getResolvedLineBreakWordStyle(
             @Nullable LineBreakConfig config) {
-        final int targetSdkVersion = ActivityThread.currentApplication().getApplicationInfo()
-                .targetSdkVersion;
+        final int targetSdkVersion = VMRuntime.getRuntime().getTargetSdkVersion();
         final int defaultWordStyle;
         final int vicVersion = Build.VERSION_CODES.VANILLA_ICE_CREAM;
         if (targetSdkVersion >= vicVersion) {
diff --git a/graphics/java/android/graphics/text/LineBreaker.java b/graphics/java/android/graphics/text/LineBreaker.java
index 94de066..29135b8 100644
--- a/graphics/java/android/graphics/text/LineBreaker.java
+++ b/graphics/java/android/graphics/text/LineBreaker.java
@@ -91,6 +91,7 @@
  * </pre>
  * </p>
  */
+@android.ravenwood.annotation.RavenwoodKeepWholeClass
 public class LineBreaker {
     /** @hide */
     @IntDef(prefix = { "BREAK_STRATEGY_" }, value = {
diff --git a/graphics/java/android/graphics/text/MeasuredText.java b/graphics/java/android/graphics/text/MeasuredText.java
index 884268a..f58d531 100644
--- a/graphics/java/android/graphics/text/MeasuredText.java
+++ b/graphics/java/android/graphics/text/MeasuredText.java
@@ -56,6 +56,7 @@
  * </pre>
  * </p>
  */
+@android.ravenwood.annotation.RavenwoodKeepWholeClass
 public class MeasuredText {
     private static final String TAG = "MeasuredText";
 
diff --git a/graphics/java/android/graphics/text/PositionedGlyphs.java b/graphics/java/android/graphics/text/PositionedGlyphs.java
index 43216ba..31125ff 100644
--- a/graphics/java/android/graphics/text/PositionedGlyphs.java
+++ b/graphics/java/android/graphics/text/PositionedGlyphs.java
@@ -46,6 +46,7 @@
  * @see TextRunShaper#shapeTextRun(char[], int, int, int, int, float, float, boolean, Paint)
  * @see TextRunShaper#shapeTextRun(CharSequence, int, int, int, int, float, float, boolean, Paint)
  */
+@android.ravenwood.annotation.RavenwoodKeepWholeClass
 public final class PositionedGlyphs {
     private static class NoImagePreloadHolder {
         private static final NativeAllocationRegistry REGISTRY =
diff --git a/graphics/java/android/graphics/text/TextRunShaper.java b/graphics/java/android/graphics/text/TextRunShaper.java
index 19ea04a..f1e3d67 100644
--- a/graphics/java/android/graphics/text/TextRunShaper.java
+++ b/graphics/java/android/graphics/text/TextRunShaper.java
@@ -40,6 +40,7 @@
  * @see android.text.TextShaper#shapeText(CharSequence, int, int, TextDirectionHeuristic, TextPaint,
  * TextShaper.GlyphsConsumer)
  */
+@android.ravenwood.annotation.RavenwoodKeepWholeClass
 public class TextRunShaper {
     private TextRunShaper() {}  // Do not instantiate
 
diff --git a/libs/WindowManager/Jetpack/src/androidx/window/extensions/embedding/SplitPresenter.java b/libs/WindowManager/Jetpack/src/androidx/window/extensions/embedding/SplitPresenter.java
index eb59d6ef..7ab9e2e 100644
--- a/libs/WindowManager/Jetpack/src/androidx/window/extensions/embedding/SplitPresenter.java
+++ b/libs/WindowManager/Jetpack/src/androidx/window/extensions/embedding/SplitPresenter.java
@@ -705,8 +705,7 @@
     }
 
     private static boolean isOverlayTransitionSupported() {
-        return Flags.moveAnimationOptionsToChange()
-                && Flags.activityEmbeddingOverlayPresentationFlag();
+        return Flags.activityEmbeddingOverlayPresentationFlag();
     }
 
     @NonNull
diff --git a/libs/WindowManager/Shell/aconfig/multitasking.aconfig b/libs/WindowManager/Shell/aconfig/multitasking.aconfig
index a08f88a..1e72d64 100644
--- a/libs/WindowManager/Shell/aconfig/multitasking.aconfig
+++ b/libs/WindowManager/Shell/aconfig/multitasking.aconfig
@@ -184,3 +184,13 @@
     description: "Try out bubble bar on phones"
     bug: "394869612"
 }
+
+flag {
+    name: "enable_bubble_task_view_listener"
+    namespace: "multitasking"
+    description: "Use the same taskview listener for bubble bar and floating"
+    bug: "272102927"
+    metadata {
+        purpose: PURPOSE_BUGFIX
+    }
+}
diff --git a/libs/WindowManager/Shell/multivalentTests/AndroidManifest.xml b/libs/WindowManager/Shell/multivalentTests/AndroidManifest.xml
index 95cd1c7..800ea74 100644
--- a/libs/WindowManager/Shell/multivalentTests/AndroidManifest.xml
+++ b/libs/WindowManager/Shell/multivalentTests/AndroidManifest.xml
@@ -2,6 +2,7 @@
     package="com.android.wm.shell.multivalenttests">
 
     <uses-permission android:name="android.permission.MANAGE_ACTIVITY_TASKS"/>
+    <uses-permission android:name="android.permission.GET_INTENT_SENDER_INTENT"/>
 
     <application android:debuggable="true" android:supportsRtl="true" >
         <uses-library android:name="android.test.runner" />
diff --git a/libs/WindowManager/Shell/multivalentTests/src/com/android/wm/shell/bubbles/BubbleTaskViewListenerTest.kt b/libs/WindowManager/Shell/multivalentTests/src/com/android/wm/shell/bubbles/BubbleTaskViewListenerTest.kt
index 9ebc3d7..3aefcd5 100644
--- a/libs/WindowManager/Shell/multivalentTests/src/com/android/wm/shell/bubbles/BubbleTaskViewListenerTest.kt
+++ b/libs/WindowManager/Shell/multivalentTests/src/com/android/wm/shell/bubbles/BubbleTaskViewListenerTest.kt
@@ -16,6 +16,7 @@
 
 package com.android.wm.shell.bubbles
 
+import android.app.ActivityOptions
 import android.app.Notification
 import android.app.PendingIntent
 import android.content.ComponentName
@@ -24,6 +25,8 @@
 import android.content.pm.ShortcutInfo
 import android.graphics.drawable.Icon
 import android.os.UserHandle
+import android.platform.test.annotations.EnableFlags
+import android.platform.test.flag.junit.SetFlagsRule
 import android.service.notification.NotificationListenerService.Ranking
 import android.service.notification.StatusBarNotification
 import android.view.View
@@ -33,6 +36,7 @@
 import androidx.test.filters.SmallTest
 import androidx.test.platform.app.InstrumentationRegistry.getInstrumentation
 import com.android.internal.protolog.ProtoLog
+import com.android.wm.shell.Flags.FLAG_ENABLE_BUBBLE_ANYTHING
 import com.android.wm.shell.R
 import com.android.wm.shell.bubbles.Bubbles.BubbleMetadataFlagListener
 import com.android.wm.shell.common.TestShellExecutor
@@ -41,6 +45,7 @@
 import com.android.wm.shell.taskview.TaskViewTaskController
 import com.google.common.truth.Truth.assertThat
 import org.junit.Before
+import org.junit.Rule
 import org.junit.Test
 import org.junit.runner.RunWith
 import org.mockito.Mockito
@@ -48,6 +53,7 @@
 import org.mockito.Mockito.reset
 import org.mockito.kotlin.any
 import org.mockito.kotlin.anyOrNull
+import org.mockito.kotlin.argumentCaptor
 import org.mockito.kotlin.doReturn
 import org.mockito.kotlin.eq
 import org.mockito.kotlin.mock
@@ -61,6 +67,9 @@
 @RunWith(AndroidJUnit4::class)
 class BubbleTaskViewListenerTest {
 
+    @get:Rule
+    val setFlagsRule = SetFlagsRule()
+
     private val context = ApplicationProvider.getApplicationContext<Context>()
 
     private var taskViewController = mock<TaskViewController>()
@@ -155,9 +164,22 @@
         }
         getInstrumentation().waitForIdleSync()
 
-        // ..so it's pending intent-based, and launches that
+        // ..so it's pending intent-based, so the pending intent should be active
         assertThat(b.isPendingIntentActive).isTrue()
-        verify(taskViewController).startActivity(any(), eq(pendingIntent), any(), any(), any())
+
+        val intentCaptor = argumentCaptor<Intent>()
+        val optionsCaptor = argumentCaptor<ActivityOptions>()
+
+        verify(taskViewController).startActivity(any(),
+            eq(pendingIntent),
+            intentCaptor.capture(),
+            optionsCaptor.capture(),
+            any())
+        val intentFlags = intentCaptor.lastValue.flags
+        assertThat((intentFlags and Intent.FLAG_ACTIVITY_NEW_DOCUMENT) != 0).isTrue()
+        assertThat((intentFlags and Intent.FLAG_ACTIVITY_MULTIPLE_TASK) != 0).isTrue()
+        assertThat(optionsCaptor.lastValue.launchedFromBubble).isTrue()
+        assertThat(optionsCaptor.lastValue.taskAlwaysOnTop).isTrue()
     }
 
     @Test
@@ -178,12 +200,52 @@
         }
         getInstrumentation().waitForIdleSync()
 
-        assertThat(b.isPendingIntentActive).isFalse()
-        verify(taskViewController).startShortcutActivity(any(), eq(shortcutInfo), any(), any())
+        val optionsCaptor = argumentCaptor<ActivityOptions>()
+
+        assertThat(b.isPendingIntentActive).isFalse() // not triggered for shortcut chats
+        verify(taskViewController).startShortcutActivity(any(),
+            eq(shortcutInfo),
+            optionsCaptor.capture(),
+            any())
+        assertThat(optionsCaptor.lastValue.launchedFromBubble).isTrue()
+        assertThat(optionsCaptor.lastValue.isApplyActivityFlagsForBubbles).isTrue()
+        assertThat(optionsCaptor.lastValue.taskAlwaysOnTop).isTrue()
+    }
+
+    @EnableFlags(FLAG_ENABLE_BUBBLE_ANYTHING)
+    @Test
+    fun onInitialized_shortcutBubble() {
+        val shortcutInfo = ShortcutInfo.Builder(context)
+            .setId("mockShortcutId")
+            .build()
+
+        val b = createShortcutBubble(shortcutInfo)
+        bubbleTaskViewListener.setBubble(b)
+
+        assertThat(b.isChat).isFalse()
+        assertThat(b.isShortcut).isTrue()
+        assertThat(b.shortcutInfo).isNotNull()
+
+        getInstrumentation().runOnMainSync {
+            bubbleTaskViewListener.onInitialized()
+        }
+        getInstrumentation().waitForIdleSync()
+
+        val optionsCaptor = argumentCaptor<ActivityOptions>()
+
+        assertThat(b.isPendingIntentActive).isFalse() // chat only triggers setting it active
+        verify(taskViewController).startShortcutActivity(any(),
+            eq(shortcutInfo),
+            optionsCaptor.capture(),
+            any())
+        assertThat(optionsCaptor.lastValue.launchedFromBubble).isFalse() // chat only
+        assertThat(optionsCaptor.lastValue.isApplyActivityFlagsForBubbles).isFalse() // chat only
+        assertThat(optionsCaptor.lastValue.isApplyMultipleTaskFlagForShortcut).isTrue()
+        assertThat(optionsCaptor.lastValue.taskAlwaysOnTop).isTrue()
     }
 
     @Test
-    fun onInitialized_appBubble() {
+    fun onInitialized_appBubble_intent() {
         val b = createAppBubble()
         bubbleTaskViewListener.setBubble(b)
 
@@ -194,11 +256,83 @@
         }
         getInstrumentation().waitForIdleSync()
 
-        assertThat(b.isPendingIntentActive).isFalse()
-        verify(taskViewController).startActivity(any(), any(), anyOrNull(), any(), any())
+        val intentCaptor = argumentCaptor<Intent>()
+        val optionsCaptor = argumentCaptor<ActivityOptions>()
+
+        assertThat(b.isPendingIntentActive).isFalse() // chat only triggers setting it active
+        verify(taskViewController).startActivity(any(),
+            any(),
+            intentCaptor.capture(),
+            optionsCaptor.capture(),
+            any())
+
+        assertThat((intentCaptor.lastValue.flags
+                and Intent.FLAG_ACTIVITY_MULTIPLE_TASK) != 0).isTrue()
+        assertThat(optionsCaptor.lastValue.launchedFromBubble).isFalse() // chat only
+        assertThat(optionsCaptor.lastValue.isApplyActivityFlagsForBubbles).isFalse() // chat only
+        assertThat(optionsCaptor.lastValue.taskAlwaysOnTop).isTrue()
     }
 
     @Test
+    fun onInitialized_appBubble_pendingIntent() {
+        val b = createAppBubble(usePendingIntent = true)
+        bubbleTaskViewListener.setBubble(b)
+
+        assertThat(b.isApp).isTrue()
+
+        getInstrumentation().runOnMainSync {
+            bubbleTaskViewListener.onInitialized()
+        }
+        getInstrumentation().waitForIdleSync()
+
+        val intentCaptor = argumentCaptor<Intent>()
+        val optionsCaptor = argumentCaptor<ActivityOptions>()
+
+        assertThat(b.isPendingIntentActive).isFalse() // chat only triggers setting it active
+        verify(taskViewController).startActivity(any(),
+            any(),
+            intentCaptor.capture(),
+            optionsCaptor.capture(),
+            any())
+
+        assertThat((intentCaptor.lastValue.flags
+                and Intent.FLAG_ACTIVITY_MULTIPLE_TASK) != 0).isTrue()
+        assertThat(optionsCaptor.lastValue.launchedFromBubble).isFalse() // chat only
+        assertThat(optionsCaptor.lastValue.isApplyActivityFlagsForBubbles).isFalse() // chat only
+        assertThat(optionsCaptor.lastValue.taskAlwaysOnTop).isTrue()
+    }
+
+    @Test
+    fun onInitialized_noteBubble() {
+        val b = createNoteBubble()
+        bubbleTaskViewListener.setBubble(b)
+
+        assertThat(b.isNote).isTrue()
+
+        getInstrumentation().runOnMainSync {
+            bubbleTaskViewListener.onInitialized()
+        }
+        getInstrumentation().waitForIdleSync()
+
+        val intentCaptor = argumentCaptor<Intent>()
+        val optionsCaptor = argumentCaptor<ActivityOptions>()
+
+        assertThat(b.isPendingIntentActive).isFalse() // chat only triggers setting it active
+        verify(taskViewController).startActivity(any(),
+            any(),
+            intentCaptor.capture(),
+            optionsCaptor.capture(),
+            any())
+
+        assertThat((intentCaptor.lastValue.flags
+                and Intent.FLAG_ACTIVITY_MULTIPLE_TASK) != 0).isTrue()
+        assertThat(optionsCaptor.lastValue.launchedFromBubble).isFalse() // chat only
+        assertThat(optionsCaptor.lastValue.isApplyActivityFlagsForBubbles).isFalse() // chat only
+        assertThat(optionsCaptor.lastValue.taskAlwaysOnTop).isTrue()
+    }
+
+
+    @Test
     fun onInitialized_preparingTransition() {
         val b = createAppBubble()
         bubbleTaskViewListener.setBubble(b)
@@ -416,13 +550,24 @@
         assertThat(isNew).isTrue()
     }
 
-    private fun createAppBubble(): Bubble {
+    private fun createAppBubble(usePendingIntent: Boolean = false): Bubble {
         val target = Intent(context, TestActivity::class.java)
         target.setPackage(context.packageName)
+        if (usePendingIntent) {
+            // Robolectric doesn't seem to play nice with PendingIntents, have to mock it.
+            val pendingIntent = mock<PendingIntent>()
+            whenever(pendingIntent.intent).thenReturn(target)
+            return Bubble.createAppBubble(pendingIntent, mock<UserHandle>(),
+                mainExecutor, bgExecutor)
+        }
         return Bubble.createAppBubble(target, mock<UserHandle>(), mock<Icon>(),
             mainExecutor, bgExecutor)
     }
 
+    private fun createShortcutBubble(shortcutInfo: ShortcutInfo): Bubble {
+        return Bubble.createShortcutBubble(shortcutInfo, mainExecutor, bgExecutor)
+    }
+
     private fun createNoteBubble(): Bubble {
         val target = Intent(context, TestActivity::class.java)
         target.setPackage(context.packageName)
diff --git a/libs/WindowManager/Shell/res/values/dimen.xml b/libs/WindowManager/Shell/res/values/dimen.xml
index f5f3f0f..a0c68ad 100644
--- a/libs/WindowManager/Shell/res/values/dimen.xml
+++ b/libs/WindowManager/Shell/res/values/dimen.xml
@@ -670,7 +670,4 @@
     <dimen name="desktop_windowing_education_promo_height">352dp</dimen>
     <!-- The corner radius of the desktop windowing education promo. -->
     <dimen name="desktop_windowing_education_promo_corner_radius">28dp</dimen>
-
-    <!-- The corner radius of freeform tasks in desktop windowing. -->
-    <dimen name="desktop_windowing_freeform_rounded_corner_radius">16dp</dimen>
 </resources>
diff --git a/libs/WindowManager/Shell/shared/res/values/dimen.xml b/libs/WindowManager/Shell/shared/res/values/dimen.xml
index 11a6f32..23c9caf 100644
--- a/libs/WindowManager/Shell/shared/res/values/dimen.xml
+++ b/libs/WindowManager/Shell/shared/res/values/dimen.xml
@@ -46,4 +46,7 @@
     <dimen name="drop_target_expanded_view_height">578</dimen>
     <dimen name="drop_target_expanded_view_padding_bottom">108</dimen>
     <dimen name="drop_target_expanded_view_padding_horizontal">24</dimen>
+
+    <!-- The corner radius of freeform tasks in desktop windowing. -->
+    <dimen name="desktop_windowing_freeform_rounded_corner_radius">16dp</dimen>
 </resources>
\ No newline at end of file
diff --git a/libs/WindowManager/Shell/shared/src/com/android/wm/shell/shared/animation/Interpolators.java b/libs/WindowManager/Shell/shared/src/com/android/wm/shell/shared/animation/Interpolators.java
index e92c1eb..43dd9b7 100644
--- a/libs/WindowManager/Shell/shared/src/com/android/wm/shell/shared/animation/Interpolators.java
+++ b/libs/WindowManager/Shell/shared/src/com/android/wm/shell/shared/animation/Interpolators.java
@@ -74,6 +74,12 @@
             0.05f, 0.7f, 0.1f, 1f);
 
     /**
+     * The standard accelerating interpolator that should be used on every regular movement of
+     * content that is disappearing e.g. when moving off screen.
+     */
+    public static final Interpolator STANDARD_ACCELERATE = new PathInterpolator(0.3f, 0f, 1f, 1f);
+
+    /**
      * The standard decelerating interpolator that should be used on every regular movement of
      * content that is appearing e.g. when coming from off screen.
      */
diff --git a/libs/WindowManager/Shell/shared/src/com/android/wm/shell/shared/animation/MinimizeAnimator.kt b/libs/WindowManager/Shell/shared/src/com/android/wm/shell/shared/animation/MinimizeAnimator.kt
index 0586e26..4ecace0 100644
--- a/libs/WindowManager/Shell/shared/src/com/android/wm/shell/shared/animation/MinimizeAnimator.kt
+++ b/libs/WindowManager/Shell/shared/src/com/android/wm/shell/shared/animation/MinimizeAnimator.kt
@@ -19,52 +19,76 @@
 import android.animation.Animator
 import android.animation.AnimatorSet
 import android.animation.ValueAnimator
-import android.util.DisplayMetrics
+import android.content.Context
+import android.os.Handler
+import android.view.Choreographer
 import android.view.SurfaceControl.Transaction
-import android.view.animation.LinearInterpolator
-import android.view.animation.PathInterpolator
 import android.window.TransitionInfo.Change
+import com.android.internal.jank.Cuj.CUJ_DESKTOP_MODE_MINIMIZE_WINDOW
+import com.android.internal.jank.InteractionJankMonitor
 
 /** Creates minimization animation */
 object MinimizeAnimator {
 
     private const val MINIMIZE_ANIM_ALPHA_DURATION_MS = 100L
 
-    private val STANDARD_ACCELERATE = PathInterpolator(0.3f, 0f, 1f, 1f)
-
     private val minimizeBoundsAnimationDef =
         WindowAnimator.BoundsAnimationParams(
             durationMs = 200,
             endOffsetYDp = 12f,
             endScale = 0.97f,
-            interpolator = STANDARD_ACCELERATE,
+            interpolator = Interpolators.STANDARD_ACCELERATE,
         )
 
+    /**
+     * Creates a minimize animator for given task [Change].
+     *
+     * @param onAnimFinish finish-callback for the animation, note that this is called on the same
+     * thread as the animation itself.
+     * @param animationHandler the Handler that the animation is running on.
+     */
     @JvmStatic
     fun create(
-        displayMetrics: DisplayMetrics,
+        context: Context,
         change: Change,
         transaction: Transaction,
         onAnimFinish: (Animator) -> Unit,
+        interactionJankMonitor: InteractionJankMonitor,
+        animationHandler: Handler,
     ): Animator {
         val boundsAnimator = WindowAnimator.createBoundsAnimator(
-            displayMetrics,
+            context.resources.displayMetrics,
             minimizeBoundsAnimationDef,
             change,
             transaction,
         )
         val alphaAnimator = ValueAnimator.ofFloat(1f, 0f).apply {
             duration = MINIMIZE_ANIM_ALPHA_DURATION_MS
-            interpolator = LinearInterpolator()
+            interpolator = Interpolators.LINEAR
             addUpdateListener { animation ->
-                transaction.setAlpha(change.leash, animation.animatedValue as Float).apply()
+                transaction
+                    .setAlpha(change.leash, animation.animatedValue as Float)
+                    .setFrameTimeline(Choreographer.getInstance().vsyncId)
+                    .apply()
             }
         }
         val listener = object : Animator.AnimatorListener {
-            override fun onAnimationEnd(animator: Animator) = onAnimFinish(animator)
-            override fun onAnimationCancel(animator: Animator) = Unit
+            override fun onAnimationStart(animator: Animator) {
+                interactionJankMonitor.begin(
+                    change.leash,
+                    context,
+                    animationHandler,
+                    CUJ_DESKTOP_MODE_MINIMIZE_WINDOW,
+                )
+            }
+            override fun onAnimationCancel(animator: Animator) {
+                interactionJankMonitor.cancel(CUJ_DESKTOP_MODE_MINIMIZE_WINDOW)
+            }
             override fun onAnimationRepeat(animator: Animator) = Unit
-            override fun onAnimationStart(animator: Animator) = Unit
+            override fun onAnimationEnd(animator: Animator) {
+                interactionJankMonitor.end(CUJ_DESKTOP_MODE_MINIMIZE_WINDOW)
+                onAnimFinish(animator)
+            }
         }
         return AnimatorSet().apply {
             playTogether(boundsAnimator, alphaAnimator)
diff --git a/libs/WindowManager/Shell/shared/src/com/android/wm/shell/shared/desktopmode/DesktopModeCompatPolicy.kt b/libs/WindowManager/Shell/shared/src/com/android/wm/shell/shared/desktopmode/DesktopModeCompatPolicy.kt
index 14338a4..0e4a6b9 100644
--- a/libs/WindowManager/Shell/shared/src/com/android/wm/shell/shared/desktopmode/DesktopModeCompatPolicy.kt
+++ b/libs/WindowManager/Shell/shared/src/com/android/wm/shell/shared/desktopmode/DesktopModeCompatPolicy.kt
@@ -16,12 +16,14 @@
 
 package com.android.wm.shell.shared.desktopmode
 
+import android.Manifest.permission.SYSTEM_ALERT_WINDOW
 import android.app.TaskInfo
 import android.content.Context
 import android.content.pm.ActivityInfo
 import android.content.pm.ActivityInfo.INSETS_DECOUPLED_CONFIGURATION_ENFORCED
 import android.content.pm.ActivityInfo.OVERRIDE_ENABLE_INSETS_DECOUPLED_CONFIGURATION
 import android.content.pm.ActivityInfo.OVERRIDE_EXCLUDE_CAPTION_INSETS_FROM_APP_BOUNDS
+import android.content.pm.PackageManager
 import android.window.DesktopModeFlags
 import com.android.internal.R
 
@@ -32,8 +34,10 @@
 class DesktopModeCompatPolicy(private val context: Context) {
 
     private val systemUiPackage: String = context.resources.getString(R.string.config_systemUi)
+    private val pkgManager: PackageManager
+        get() = context.getPackageManager()
     private val defaultHomePackage: String?
-        get() = context.getPackageManager().getHomeActivities(ArrayList())?.packageName
+        get() = pkgManager.getHomeActivities(ArrayList())?.packageName
 
     /**
      * If the top activity should be exempt from desktop windowing and forced back to fullscreen.
@@ -47,11 +51,12 @@
 
     fun isTopActivityExemptFromDesktopWindowing(packageName: String?,
         numActivities: Int, isTopActivityNoDisplay: Boolean, isActivityStackTransparent: Boolean) =
-        DesktopModeFlags.ENABLE_DESKTOP_WINDOWING_MODALS_POLICY.isTrue
-                && ((isSystemUiTask(packageName)
-                || isPartOfDefaultHomePackageOrNoHomeAvailable(packageName)
-                || isTransparentTask(isActivityStackTransparent, numActivities))
-                && !isTopActivityNoDisplay)
+        DesktopModeFlags.ENABLE_DESKTOP_WINDOWING_MODALS_POLICY.isTrue &&
+                ((isSystemUiTask(packageName) ||
+                        isPartOfDefaultHomePackageOrNoHomeAvailable(packageName) ||
+                        (isTransparentTask(isActivityStackTransparent, numActivities) &&
+                                hasFullscreenTransparentPermission(packageName))) &&
+                        !isTopActivityNoDisplay)
 
     /**
      * Whether the caption insets should be excluded from configuration for system to handle.
@@ -83,6 +88,26 @@
 
     private fun isSystemUiTask(packageName: String?) = packageName == systemUiPackage
 
+    // Checks if the app for the given package has the SYSTEM_ALERT_WINDOW permission.
+    private fun hasFullscreenTransparentPermission(packageName: String?): Boolean {
+        if (DesktopModeFlags.ENABLE_MODALS_FULLSCREEN_WITH_PERMISSIONS.isTrue) {
+            if (packageName == null) {
+                return false
+            }
+            return try {
+                val packageInfo = pkgManager.getPackageInfo(
+                    packageName,
+                    PackageManager.GET_PERMISSIONS
+                )
+                packageInfo?.requestedPermissions?.contains(SYSTEM_ALERT_WINDOW) == true
+            } catch (e: PackageManager.NameNotFoundException) {
+                false // Package not found
+            }
+        }
+        // If the flag is disabled we make this condition neutral.
+        return true
+    }
+
     /**
      * Returns true if the tasks base activity is part of the default home package, or there is
      * currently no default home package available.
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/activityembedding/ActivityEmbeddingAnimationRunner.java b/libs/WindowManager/Shell/src/com/android/wm/shell/activityembedding/ActivityEmbeddingAnimationRunner.java
index f269b38..78f5154 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/activityembedding/ActivityEmbeddingAnimationRunner.java
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/activityembedding/ActivityEmbeddingAnimationRunner.java
@@ -364,7 +364,7 @@
             @NonNull SurfaceControl.Transaction finishTransaction,
             @NonNull List<ActivityEmbeddingAnimationAdapter> adapters) {
         for (ActivityEmbeddingAnimationAdapter adapter : adapters) {
-            final int backgroundColor = getTransitionBackgroundColorIfSet(info, adapter.mChange,
+            final int backgroundColor = getTransitionBackgroundColorIfSet(adapter.mChange,
                     adapter.mAnimation, 0 /* defaultColor */);
             if (backgroundColor != 0) {
                 // We only need to show one color.
@@ -436,8 +436,8 @@
             final TransitionInfo.AnimationOptions options = boundsAnimationChange
                     .getAnimationOptions();
             if (options != null) {
-                final Animation overrideAnimation = mAnimationSpec.loadCustomAnimationFromOptions(
-                        options, TRANSIT_CHANGE);
+                final Animation overrideAnimation =
+                        mAnimationSpec.loadCustomAnimation(options, TRANSIT_CHANGE);
                 if (overrideAnimation != null) {
                     overrideShowBackdrop = overrideAnimation.getShowBackdrop();
                 }
@@ -447,7 +447,7 @@
             // There are two animations in the array. The first one is for the start leash
             // (snapshot), and the second one is for the end leash (TaskFragment).
             final Animation[] animations =
-                    mAnimationSpec.createChangeBoundsChangeAnimations(info, change, parentBounds);
+                    mAnimationSpec.createChangeBoundsChangeAnimations(change, parentBounds);
             // Jump cut if either animation has zero for duration.
             for (Animation animation : animations) {
                 if (shouldUseJumpCutForAnimation(animation)) {
@@ -500,12 +500,10 @@
                 // window without bounds change.
                 animation = ActivityEmbeddingAnimationSpec.createNoopAnimation(change);
             } else if (TransitionUtil.isClosingType(change.getMode())) {
-                animation =
-                        mAnimationSpec.createChangeBoundsCloseAnimation(info, change, parentBounds);
+                animation = mAnimationSpec.createChangeBoundsCloseAnimation(change, parentBounds);
                 shouldShowBackgroundColor = false;
             } else {
-                animation =
-                        mAnimationSpec.createChangeBoundsOpenAnimation(info, change, parentBounds);
+                animation = mAnimationSpec.createChangeBoundsOpenAnimation(change, parentBounds);
                 shouldShowBackgroundColor = false;
             }
             if (shouldUseJumpCutForAnimation(animation)) {
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/activityembedding/ActivityEmbeddingAnimationSpec.java b/libs/WindowManager/Shell/src/com/android/wm/shell/activityembedding/ActivityEmbeddingAnimationSpec.java
index 77799e9..2b9eda4 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/activityembedding/ActivityEmbeddingAnimationSpec.java
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/activityembedding/ActivityEmbeddingAnimationSpec.java
@@ -42,7 +42,6 @@
 import android.window.TransitionInfo;
 
 import com.android.internal.policy.TransitionAnimation;
-import com.android.window.flags.Flags;
 import com.android.wm.shell.shared.TransitionUtil;
 
 /** Animation spec for ActivityEmbedding transition. */
@@ -94,9 +93,10 @@
 
     /** Animation for window that is opening in a change transition. */
     @NonNull
-    Animation createChangeBoundsOpenAnimation(@NonNull TransitionInfo info,
-            @NonNull TransitionInfo.Change change, @NonNull Rect parentBounds) {
-        final Animation customAnimation = loadCustomAnimation(info, change, TRANSIT_CHANGE);
+    Animation createChangeBoundsOpenAnimation(@NonNull TransitionInfo.Change change,
+            @NonNull Rect parentBounds) {
+        final Animation customAnimation =
+                loadCustomAnimation(change.getAnimationOptions(), TRANSIT_CHANGE);
         if (customAnimation != null) {
             return customAnimation;
         }
@@ -126,9 +126,10 @@
 
     /** Animation for window that is closing in a change transition. */
     @NonNull
-    Animation createChangeBoundsCloseAnimation(@NonNull TransitionInfo info,
-            @NonNull TransitionInfo.Change change, @NonNull Rect parentBounds) {
-        final Animation customAnimation = loadCustomAnimation(info, change, TRANSIT_CHANGE);
+    Animation createChangeBoundsCloseAnimation(@NonNull TransitionInfo.Change change,
+            @NonNull Rect parentBounds) {
+        final Animation customAnimation =
+                loadCustomAnimation(change.getAnimationOptions(), TRANSIT_CHANGE);
         if (customAnimation != null) {
             return customAnimation;
         }
@@ -162,12 +163,13 @@
      *         the second one is for the end leash.
      */
     @NonNull
-    Animation[] createChangeBoundsChangeAnimations(@NonNull TransitionInfo info,
-            @NonNull TransitionInfo.Change change, @NonNull Rect parentBounds) {
+    Animation[] createChangeBoundsChangeAnimations(@NonNull TransitionInfo.Change change,
+            @NonNull Rect parentBounds) {
         // TODO(b/293658614): Support more complicated animations that may need more than a noop
         // animation as the start leash.
         final Animation noopAnimation = createNoopAnimation(change);
-        final Animation customAnimation = loadCustomAnimation(info, change, TRANSIT_CHANGE);
+        final Animation customAnimation =
+                loadCustomAnimation(change.getAnimationOptions(), TRANSIT_CHANGE);
         if (customAnimation != null) {
             return new Animation[]{noopAnimation, customAnimation};
         }
@@ -221,7 +223,8 @@
     Animation loadOpenAnimation(@NonNull TransitionInfo info,
             @NonNull TransitionInfo.Change change, @NonNull Rect wholeAnimationBounds) {
         final boolean isEnter = TransitionUtil.isOpeningType(change.getMode());
-        final Animation customAnimation = loadCustomAnimation(info, change, change.getMode());
+        final Animation customAnimation =
+                loadCustomAnimation(change.getAnimationOptions(), change.getMode());
         final Animation animation;
         if (customAnimation != null) {
             animation = customAnimation;
@@ -248,7 +251,8 @@
     Animation loadCloseAnimation(@NonNull TransitionInfo info,
             @NonNull TransitionInfo.Change change, @NonNull Rect wholeAnimationBounds) {
         final boolean isEnter = TransitionUtil.isOpeningType(change.getMode());
-        final Animation customAnimation = loadCustomAnimation(info, change, change.getMode());
+        final Animation customAnimation =
+                loadCustomAnimation(change.getAnimationOptions(), change.getMode());
         final Animation animation;
         if (customAnimation != null) {
             animation = customAnimation;
@@ -280,20 +284,8 @@
     }
 
     @Nullable
-    private Animation loadCustomAnimation(@NonNull TransitionInfo info,
-            @NonNull TransitionInfo.Change change, @WindowManager.TransitionType int mode) {
-        final TransitionInfo.AnimationOptions options;
-        if (Flags.moveAnimationOptionsToChange()) {
-            options = change.getAnimationOptions();
-        } else {
-            options = info.getAnimationOptions();
-        }
-        return loadCustomAnimationFromOptions(options, mode);
-    }
-
-    @Nullable
-    Animation loadCustomAnimationFromOptions(@Nullable TransitionInfo.AnimationOptions options,
-             @WindowManager.TransitionType int mode) {
+    Animation loadCustomAnimation(@Nullable TransitionInfo.AnimationOptions options,
+            @WindowManager.TransitionType int mode) {
         if (options == null || options.getType() != ANIM_CUSTOM) {
             return null;
         }
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/activityembedding/ActivityEmbeddingController.java b/libs/WindowManager/Shell/src/com/android/wm/shell/activityembedding/ActivityEmbeddingController.java
index 55ed5fa..3a95333 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/activityembedding/ActivityEmbeddingController.java
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/activityembedding/ActivityEmbeddingController.java
@@ -40,7 +40,6 @@
 import androidx.annotation.Nullable;
 
 import com.android.internal.annotations.VisibleForTesting;
-import com.android.window.flags.Flags;
 import com.android.wm.shell.shared.TransitionUtil;
 import com.android.wm.shell.sysui.ShellInit;
 import com.android.wm.shell.transition.Transitions;
@@ -123,9 +122,6 @@
     }
 
     private boolean shouldAnimateAnimationOptions(@NonNull TransitionInfo info) {
-        if (!Flags.moveAnimationOptionsToChange()) {
-            return shouldAnimateAnimationOptions(info.getAnimationOptions());
-        }
         for (TransitionInfo.Change change : info.getChanges()) {
             if (!shouldAnimateAnimationOptions(change.getAnimationOptions())) {
                 // If any of override animation is not supported, don't animate the transition.
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/bubbles/Bubble.java b/libs/WindowManager/Shell/src/com/android/wm/shell/bubbles/Bubble.java
index d948928..313d151 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/bubbles/Bubble.java
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/bubbles/Bubble.java
@@ -364,7 +364,7 @@
             @ShellMainThread Executor mainExecutor, @ShellBackgroundThread Executor bgExecutor) {
         return new Bubble(intent,
                 user,
-                /* key= */ getAppBubbleKeyForApp(ComponentUtils.getPackageName(intent), user),
+                /* key= */ getAppBubbleKeyForApp(intent.getIntent().getPackage(), user),
                 mainExecutor, bgExecutor);
     }
 
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/bubbles/BubbleExpandedView.java b/libs/WindowManager/Shell/src/com/android/wm/shell/bubbles/BubbleExpandedView.java
index 3f607a9..2c2451c 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/bubbles/BubbleExpandedView.java
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/bubbles/BubbleExpandedView.java
@@ -197,6 +197,8 @@
      */
     private final FrameLayout mExpandedViewContainer = new FrameLayout(getContext());
 
+    private TaskView.Listener mCurrentTaskViewListener;
+
     private final TaskView.Listener mTaskViewListener = new TaskView.Listener() {
         private boolean mInitialized = false;
         private boolean mDestroyed = false;
@@ -235,18 +237,24 @@
                         Context context =
                                 mContext.createContextAsUser(
                                         mBubble.getUser(), Context.CONTEXT_RESTRICTED);
+                        Intent fillInIntent = new Intent();
+                        fillInIntent.addFlags(FLAG_ACTIVITY_MULTIPLE_TASK);
                         PendingIntent pi = PendingIntent.getActivity(
                                 context,
                                 /* requestCode= */ 0,
-                                mBubble.getIntent().addFlags(FLAG_ACTIVITY_MULTIPLE_TASK),
-                                PendingIntent.FLAG_IMMUTABLE | PendingIntent.FLAG_UPDATE_CURRENT,
+                                mBubble.getIntent(),
+                                // Needs to be mutable for the fillInIntent
+                                PendingIntent.FLAG_MUTABLE | PendingIntent.FLAG_UPDATE_CURRENT,
                                 /* options= */ null);
-                        mTaskView.startActivity(pi, /* fillInIntent= */ null, options,
-                                launchBounds);
+                        mTaskView.startActivity(pi, fillInIntent, options, launchBounds);
                     } else if (!mIsOverflow && isShortcutBubble) {
                         ProtoLog.v(WM_SHELL_BUBBLES, "startingShortcutBubble=%s", getBubbleKey());
-                        options.setLaunchedFromBubble(true);
-                        options.setApplyActivityFlagsForBubbles(true);
+                        if (mBubble.isChat()) {
+                            options.setLaunchedFromBubble(true);
+                            options.setApplyActivityFlagsForBubbles(true);
+                        } else {
+                            options.setApplyMultipleTaskFlagForShortcut(true);
+                        }
                         mTaskView.startShortcutActivity(mBubble.getShortcutInfo(),
                                 options, launchBounds);
                     } else {
@@ -453,7 +461,34 @@
             mTaskView = bubbleTaskView.getTaskView();
             // reset the insets that might left after TaskView is shown in BubbleBarExpandedView
             mTaskView.setCaptionInsets(null);
-            bubbleTaskView.setDelegateListener(mTaskViewListener);
+            if (Flags.enableBubbleTaskViewListener()) {
+                mCurrentTaskViewListener = new BubbleTaskViewListener(mContext, bubbleTaskView,
+                        /* viewParent= */ this, expandedViewManager,
+                        new BubbleTaskViewListener.Callback() {
+                            @Override
+                            public void onTaskCreated() {
+                                setContentVisibility(true);
+                            }
+
+                            @Override
+                            public void onContentVisibilityChanged(boolean visible) {
+                                setContentVisibility(visible);
+                            }
+
+                            @Override
+                            public void onBackPressed() {
+                                mStackView.onBackPressed();
+                            }
+
+                            @Override
+                            public void onTaskRemovalStarted() {
+                                // nothing to do / handled in listener.
+                            }
+                        });
+            } else {
+                mCurrentTaskViewListener = mTaskViewListener;
+                bubbleTaskView.setDelegateListener(mCurrentTaskViewListener);
+            }
 
             // set a fixed width so it is not recalculated as part of a rotation. the width will be
             // updated manually after the rotation.
@@ -464,9 +499,12 @@
             }
             mExpandedViewContainer.addView(mTaskView, lp);
             bringChildToFront(mTaskView);
-            if (bubbleTaskView.isCreated()) {
-                mTaskViewListener.onTaskCreated(
-                        bubbleTaskView.getTaskId(), bubbleTaskView.getComponentName());
+
+            if (!Flags.enableBubbleTaskViewListener()) {
+                if (bubbleTaskView.isCreated()) {
+                    mCurrentTaskViewListener.onTaskCreated(
+                            bubbleTaskView.getTaskId(), bubbleTaskView.getComponentName());
+                }
             }
         }
     }
@@ -897,7 +935,12 @@
             Log.w(TAG, "Stack is null for bubble: " + bubble);
             return;
         }
-        boolean isNew = mBubble == null || didBackingContentChange(bubble);
+        boolean isNew;
+        if (mCurrentTaskViewListener instanceof BubbleTaskViewListener) {
+            isNew = ((BubbleTaskViewListener) mCurrentTaskViewListener).setBubble(bubble);
+        } else {
+            isNew = mBubble == null || didBackingContentChange(bubble);
+        }
         boolean isUpdate = bubble != null && mBubble != null
                 && bubble.getKey().equals(mBubble.getKey());
         ProtoLog.d(WM_SHELL_BUBBLES, "BubbleExpandedView - update bubble=%s; isNew=%b; isUpdate=%b",
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/bubbles/BubbleTaskViewListener.java b/libs/WindowManager/Shell/src/com/android/wm/shell/bubbles/BubbleTaskViewListener.java
index a38debb..63d7134 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/bubbles/BubbleTaskViewListener.java
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/bubbles/BubbleTaskViewListener.java
@@ -129,27 +129,28 @@
                     Context context =
                             mContext.createContextAsUser(
                                     mBubble.getUser(), Context.CONTEXT_RESTRICTED);
-                    Intent fillInIntent = null;
+                    Intent fillInIntent = new Intent();
+                    fillInIntent.addFlags(FLAG_ACTIVITY_MULTIPLE_TASK);
                     // First try get pending intent from the bubble
                     PendingIntent pi = mBubble.getPendingIntent();
                     if (pi == null) {
-                        // If null - create new one
+                        // If null - create new one based on the bubble intent
                         pi = PendingIntent.getActivity(
                                 context,
                                 /* requestCode= */ 0,
-                                mBubble.getIntent()
-                                        .addFlags(FLAG_ACTIVITY_MULTIPLE_TASK),
-                                PendingIntent.FLAG_IMMUTABLE
-                                        | PendingIntent.FLAG_UPDATE_CURRENT,
+                                mBubble.getIntent(),
+                                // Needs to be mutable for the fillInIntent
+                                PendingIntent.FLAG_MUTABLE | PendingIntent.FLAG_UPDATE_CURRENT,
                                 /* options= */ null);
-                    } else {
-                        fillInIntent = new Intent(pi.getIntent());
-                        fillInIntent.addFlags(FLAG_ACTIVITY_MULTIPLE_TASK);
                     }
                     mTaskView.startActivity(pi, fillInIntent, options, launchBounds);
                 } else if (isShortcutBubble) {
-                    options.setLaunchedFromBubble(true);
-                    options.setApplyActivityFlagsForBubbles(true);
+                    if (mBubble.isChat()) {
+                        options.setLaunchedFromBubble(true);
+                        options.setApplyActivityFlagsForBubbles(true);
+                    } else {
+                        options.setApplyMultipleTaskFlagForShortcut(true);
+                    }
                     mTaskView.startShortcutActivity(mBubble.getShortcutInfo(),
                             options, launchBounds);
                 } else {
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/bubbles/BubbleTransitions.java b/libs/WindowManager/Shell/src/com/android/wm/shell/bubbles/BubbleTransitions.java
index a676f41..338ffe7 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/bubbles/BubbleTransitions.java
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/bubbles/BubbleTransitions.java
@@ -21,6 +21,7 @@
 import static android.app.WindowConfiguration.WINDOWING_MODE_UNDEFINED;
 import static android.view.View.INVISIBLE;
 import static android.view.WindowManager.TRANSIT_CHANGE;
+import static android.view.WindowManager.TRANSIT_TO_FRONT;
 
 import static com.android.wm.shell.transition.Transitions.TRANSIT_CONVERT_TO_BUBBLE;
 
@@ -325,7 +326,7 @@
             for (int i = 0; i < info.getChanges().size(); ++i) {
                 final TransitionInfo.Change chg = info.getChanges().get(i);
                 if (chg.getTaskInfo() == null) continue;
-                if (chg.getMode() != TRANSIT_CHANGE) continue;
+                if (chg.getMode() != TRANSIT_CHANGE && chg.getMode() != TRANSIT_TO_FRONT) continue;
                 if (!mTaskInfo.token.equals(chg.getTaskInfo().token)) continue;
                 mStartBounds.set(chg.getStartAbsBounds());
                 // Converting a task into taskview, so treat as "new"
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/common/DisplayImeController.java b/libs/WindowManager/Shell/src/com/android/wm/shell/common/DisplayImeController.java
index 8377a35..df82091 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/common/DisplayImeController.java
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/common/DisplayImeController.java
@@ -227,7 +227,13 @@
     /** Hides the IME for Bubbles when the device is locked. */
     public void hideImeForBubblesWhenLocked(int displayId) {
         PerDisplay pd = mImePerDisplay.get(displayId);
-        pd.setImeInputTargetRequestedVisibility(false, pd.getImeSourceControl().getImeStatsToken());
+        InsetsSourceControl imeSourceControl = pd.getImeSourceControl();
+        if (imeSourceControl != null) {
+            ImeTracker.Token imeStatsToken = imeSourceControl.getImeStatsToken();
+            if (imeStatsToken != null) {
+                pd.setImeInputTargetRequestedVisibility(false, imeStatsToken);
+            }
+        }
     }
 
     /** An implementation of {@link IDisplayWindowInsetsController} for a given display id. */
@@ -324,8 +330,10 @@
                         }
                         applyVisibilityToLeash(imeSourceControl);
                     }
-                    if (!mImeShowing) {
-                        removeImeSurface(mDisplayId);
+                    if (!android.view.inputmethod.Flags.refactorInsetsController()) {
+                        if (!mImeShowing) {
+                            removeImeSurface(mDisplayId);
+                        }
                     }
                 }
             } else {
@@ -663,7 +671,9 @@
                         ImeTracker.forLogging().onProgress(mStatsToken,
                                 ImeTracker.PHASE_WM_ANIMATION_RUNNING);
                         t.hide(animatingLeash);
-                        removeImeSurface(mDisplayId);
+                        if (!android.view.inputmethod.Flags.refactorInsetsController()) {
+                            removeImeSurface(mDisplayId);
+                        }
                         if (android.view.inputmethod.Flags.refactorInsetsController()) {
                             setVisibleDirectly(false /* visible */, statsToken);
                         }
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/common/MultiDisplayDragMoveIndicatorController.kt b/libs/WindowManager/Shell/src/com/android/wm/shell/common/MultiDisplayDragMoveIndicatorController.kt
new file mode 100644
index 0000000..7a5bc13
--- /dev/null
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/common/MultiDisplayDragMoveIndicatorController.kt
@@ -0,0 +1,124 @@
+/*
+ * Copyright (C) 2025 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package com.android.wm.shell.common
+
+import android.app.ActivityManager.RunningTaskInfo
+import android.graphics.RectF
+import android.view.SurfaceControl
+import com.android.wm.shell.RootTaskDisplayAreaOrganizer
+import com.android.wm.shell.shared.annotations.ShellDesktopThread
+
+/**
+ * Controller to manage the indicators that show users the current position of the dragged window on
+ * the new display when performing drag move across displays.
+ */
+class MultiDisplayDragMoveIndicatorController(
+    private val displayController: DisplayController,
+    private val rootTaskDisplayAreaOrganizer: RootTaskDisplayAreaOrganizer,
+    private val indicatorSurfaceFactory: MultiDisplayDragMoveIndicatorSurface.Factory,
+    @ShellDesktopThread private val desktopExecutor: ShellExecutor,
+) {
+    @ShellDesktopThread
+    private val dragIndicators =
+        mutableMapOf<Int, MutableMap<Int, MultiDisplayDragMoveIndicatorSurface>>()
+
+    /**
+     * Called during drag move, which started at [startDisplayId]. Updates the position and
+     * visibility of the drag move indicators for the [taskInfo] based on [boundsDp] on the
+     * destination displays ([displayIds]) as the dragged window moves. [transactionSupplier]
+     * provides a [SurfaceControl.Transaction] for applying changes to the indicator surfaces.
+     *
+     * It is executed on the [desktopExecutor] to prevent blocking the main thread and avoid jank,
+     * as creating and manipulating surfaces can be expensive.
+     */
+    fun onDragMove(
+        boundsDp: RectF,
+        startDisplayId: Int,
+        taskInfo: RunningTaskInfo,
+        displayIds: Set<Int>,
+        transactionSupplier: () -> SurfaceControl.Transaction,
+    ) {
+        desktopExecutor.execute {
+            for (displayId in displayIds) {
+                if (displayId == startDisplayId) {
+                    // No need to render indicators on the original display where the drag started.
+                    continue
+                }
+                val displayLayout = displayController.getDisplayLayout(displayId) ?: continue
+                val shouldBeVisible =
+                    RectF.intersects(RectF(boundsDp), displayLayout.globalBoundsDp())
+                if (
+                    dragIndicators[taskInfo.taskId]?.containsKey(displayId) != true &&
+                        !shouldBeVisible
+                ) {
+                    // Skip this display if:
+                    // - It doesn't have an existing indicator that needs to be updated, AND
+                    // - The latest dragged window bounds don't intersect with this display.
+                    continue
+                }
+
+                val boundsPx =
+                    MultiDisplayDragMoveBoundsCalculator.convertGlobalDpToLocalPxForRect(
+                        boundsDp,
+                        displayLayout,
+                    )
+
+                // Get or create the inner map for the current task.
+                val dragIndicatorsForTask =
+                    dragIndicators.getOrPut(taskInfo.taskId) { mutableMapOf() }
+                dragIndicatorsForTask[displayId]?.also { existingIndicator ->
+                    val transaction = transactionSupplier()
+                    existingIndicator.relayout(boundsPx, transaction, shouldBeVisible)
+                    transaction.apply()
+                } ?: run {
+                    val newIndicator =
+                        indicatorSurfaceFactory.create(
+                            taskInfo,
+                            displayController.getDisplay(displayId),
+                        )
+                    newIndicator.show(
+                        transactionSupplier(),
+                        taskInfo,
+                        rootTaskDisplayAreaOrganizer,
+                        displayId,
+                        boundsPx,
+                    )
+                    dragIndicatorsForTask[displayId] = newIndicator
+                }
+            }
+        }
+    }
+
+    /**
+     * Called when the drag ends. Disposes of the drag move indicator surfaces associated with the
+     * given [taskId]. [transactionSupplier] provides a [SurfaceControl.Transaction] for applying
+     * changes to the indicator surfaces.
+     *
+     * It is executed on the [desktopExecutor] to ensure that any pending `onDragMove` operations
+     * have completed before disposing of the surfaces.
+     */
+    fun onDragEnd(taskId: Int, transactionSupplier: () -> SurfaceControl.Transaction) {
+        desktopExecutor.execute {
+            dragIndicators.remove(taskId)?.values?.takeIf { it.isNotEmpty() }?.let { indicators ->
+                val transaction = transactionSupplier()
+                indicators.forEach { indicator ->
+                    indicator.disposeSurface(transaction)
+                }
+                transaction.apply()
+            }
+        }
+    }
+}
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/common/MultiDisplayDragMoveIndicatorSurface.kt b/libs/WindowManager/Shell/src/com/android/wm/shell/common/MultiDisplayDragMoveIndicatorSurface.kt
new file mode 100644
index 0000000..d05d3b0
--- /dev/null
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/common/MultiDisplayDragMoveIndicatorSurface.kt
@@ -0,0 +1,151 @@
+/*
+ * Copyright (C) 2025 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package com.android.wm.shell.common
+
+import android.app.ActivityManager.RunningTaskInfo
+import android.content.Context
+import android.graphics.Color
+import android.graphics.Rect
+import android.os.Trace
+import android.view.Display
+import android.view.SurfaceControl
+import androidx.compose.material3.dynamicDarkColorScheme
+import androidx.compose.material3.dynamicLightColorScheme
+import androidx.compose.ui.graphics.toArgb
+import com.android.wm.shell.RootTaskDisplayAreaOrganizer
+import com.android.wm.shell.windowdecor.common.DecorThemeUtil
+import com.android.wm.shell.windowdecor.common.Theme
+
+/**
+ * Represents the indicator surface that visualizes the current position of a dragged window during
+ * a multi-display drag operation.
+ *
+ * This class manages the creation, display, and manipulation of the [SurfaceControl]s that act as a
+ * visual indicator, providing feedback to the user about the dragged window's location.
+ */
+class MultiDisplayDragMoveIndicatorSurface(
+    context: Context,
+    taskInfo: RunningTaskInfo,
+    display: Display,
+    surfaceControlBuilderFactory: Factory.SurfaceControlBuilderFactory,
+) {
+    private var isVisible = false
+
+    // A container surface to host the veil background
+    private var veilSurface: SurfaceControl? = null
+
+    private val decorThemeUtil = DecorThemeUtil(context)
+    private val lightColors = dynamicLightColorScheme(context)
+    private val darkColors = dynamicDarkColorScheme(context)
+
+    init {
+        Trace.beginSection("DragIndicatorSurface#init")
+
+        val displayId = display.displayId
+        veilSurface =
+            surfaceControlBuilderFactory
+                .create("Drag indicator veil of Task=${taskInfo.taskId} Display=$displayId")
+                .setColorLayer()
+                .setCallsite("DragIndicatorSurface#init")
+                .setHidden(true)
+                .build()
+
+        // TODO: b/383069173 - Add icon for the surface.
+
+        Trace.endSection()
+    }
+
+    /**
+     * Disposes the indicator surface using the provided [transaction].
+     */
+    fun disposeSurface(transaction: SurfaceControl.Transaction) {
+        veilSurface?.let { veil -> transaction.remove(veil) }
+        veilSurface = null
+    }
+
+    /**
+     * Shows the indicator surface at [bounds] on the specified display ([displayId]),
+     * visualizing the drag of the [taskInfo]. The indicator surface is shown using [transaction],
+     * and the [rootTaskDisplayAreaOrganizer] is used to reparent the surfaces.
+     */
+    fun show(
+        transaction: SurfaceControl.Transaction,
+        taskInfo: RunningTaskInfo,
+        rootTaskDisplayAreaOrganizer: RootTaskDisplayAreaOrganizer,
+        displayId: Int,
+        bounds: Rect,
+    ) {
+        val backgroundColor =
+            when (decorThemeUtil.getAppTheme(taskInfo)) {
+                Theme.LIGHT -> lightColors.surfaceContainer
+                Theme.DARK -> darkColors.surfaceContainer
+            }
+        val veil = veilSurface ?: return
+        isVisible = true
+
+        rootTaskDisplayAreaOrganizer.reparentToDisplayArea(displayId, veil, transaction)
+        relayout(bounds, transaction, shouldBeVisible = true)
+        transaction.show(veil).setColor(veil, Color.valueOf(backgroundColor.toArgb()).components)
+        transaction.apply()
+    }
+
+    /**
+     * Repositions and resizes the indicator surface based on [bounds] using [transaction]. The
+     * [shouldBeVisible] flag indicates whether the indicator is within the display after relayout.
+     */
+    fun relayout(bounds: Rect, transaction: SurfaceControl.Transaction, shouldBeVisible: Boolean) {
+        if (!isVisible && !shouldBeVisible) {
+            // No need to relayout if the surface is already invisible and should not be visible.
+            return
+        }
+        isVisible = shouldBeVisible
+        val veil = veilSurface ?: return
+        transaction.setCrop(veil, bounds)
+    }
+
+    /**
+     * Factory for creating [MultiDisplayDragMoveIndicatorSurface] instances with the [context].
+     */
+    class Factory(private val context: Context) {
+        private val surfaceControlBuilderFactory: SurfaceControlBuilderFactory =
+            object : SurfaceControlBuilderFactory {}
+
+        /**
+         * Creates a new [MultiDisplayDragMoveIndicatorSurface] instance to visualize the drag
+         * operation of the [taskInfo] on the given [display].
+         */
+        fun create(
+            taskInfo: RunningTaskInfo,
+            display: Display,
+        ) = MultiDisplayDragMoveIndicatorSurface(
+            context,
+            taskInfo,
+            display,
+            surfaceControlBuilderFactory,
+        )
+
+        /**
+         * Interface for creating [SurfaceControl.Builder] instances.
+         *
+         * This provides an abstraction over [SurfaceControl.Builder] creation for testing purposes.
+         */
+        interface SurfaceControlBuilderFactory {
+            fun create(name: String): SurfaceControl.Builder {
+                return SurfaceControl.Builder().setName(name)
+            }
+        }
+    }
+}
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/common/pip/PipBoundsAlgorithm.java b/libs/WindowManager/Shell/src/com/android/wm/shell/common/pip/PipBoundsAlgorithm.java
index 8e026f0..04e8d8d 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/common/pip/PipBoundsAlgorithm.java
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/common/pip/PipBoundsAlgorithm.java
@@ -24,6 +24,7 @@
 import android.content.res.Resources;
 import android.graphics.Rect;
 import android.util.DisplayMetrics;
+import android.util.Rational;
 import android.util.Size;
 import android.view.Gravity;
 
@@ -41,9 +42,6 @@
     private static final String TAG = PipBoundsAlgorithm.class.getSimpleName();
     private static final float INVALID_SNAP_FRACTION = -1f;
 
-    // The same value (with the same name) is used in Launcher.
-    private static final float PIP_ASPECT_RATIO_MISMATCH_THRESHOLD = 0.01f;
-
     @NonNull private final PipBoundsState mPipBoundsState;
     @NonNull protected final PipDisplayLayoutState mPipDisplayLayoutState;
     @NonNull protected final SizeSpecSource mSizeSpecSource;
@@ -223,9 +221,8 @@
                             + " than destination(%s)", sourceRectHint, destinationBounds);
             return false;
         }
-        final float reportedRatio = destinationBounds.width() / (float) destinationBounds.height();
-        final float inferredRatio = sourceRectHint.width() / (float) sourceRectHint.height();
-        if (Math.abs(reportedRatio - inferredRatio) > PIP_ASPECT_RATIO_MISMATCH_THRESHOLD) {
+        if (!PictureInPictureParams.isSameAspectRatio(sourceRectHint,
+                new Rational(destinationBounds.width(), destinationBounds.height()))) {
             ProtoLog.d(ShellProtoLogGroup.WM_SHELL_PICTURE_IN_PICTURE,
                     "isSourceRectHintValidForEnterPip=false, hint(%s) does not match"
                             + " destination(%s) aspect ratio", sourceRectHint, destinationBounds);
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/dagger/WMShellConcurrencyModule.java b/libs/WindowManager/Shell/src/com/android/wm/shell/dagger/WMShellConcurrencyModule.java
index ee3e39e..e9dc613 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/dagger/WMShellConcurrencyModule.java
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/dagger/WMShellConcurrencyModule.java
@@ -162,22 +162,31 @@
         }
     }
 
+    /** Provide a Shell animation-thread Handler. */
+    @WMSingleton
+    @Provides
+    @ShellAnimationThread
+    public static Handler provideShellAnimationHandler() {
+        HandlerThread animThread = new HandlerThread("wmshell.anim", THREAD_PRIORITY_DISPLAY);
+        animThread.start();
+        if (Build.IS_DEBUGGABLE) {
+            animThread.getLooper().setTraceTag(Trace.TRACE_TAG_WINDOW_MANAGER);
+            animThread.getLooper().setSlowLogThresholdMs(MSGQ_SLOW_DISPATCH_THRESHOLD_MS,
+                    MSGQ_SLOW_DELIVERY_THRESHOLD_MS);
+        }
+        return Handler.createAsync(animThread.getLooper());
+    }
+
     /**
      * Provide a Shell animation-thread Executor.
      */
     @WMSingleton
     @Provides
     @ShellAnimationThread
-    public static ShellExecutor provideShellAnimationExecutor() {
-         HandlerThread shellAnimationThread = new HandlerThread("wmshell.anim",
-                 THREAD_PRIORITY_DISPLAY);
-         shellAnimationThread.start();
-        if (Build.IS_DEBUGGABLE) {
-            shellAnimationThread.getLooper().setTraceTag(Trace.TRACE_TAG_WINDOW_MANAGER);
-            shellAnimationThread.getLooper().setSlowLogThresholdMs(MSGQ_SLOW_DISPATCH_THRESHOLD_MS,
-                    MSGQ_SLOW_DELIVERY_THRESHOLD_MS);
-        }
-         return new HandlerExecutor(Handler.createAsync(shellAnimationThread.getLooper()));
+    public static ShellExecutor provideShellAnimationExecutor(
+            @ShellAnimationThread Handler animHandler
+    ) {
+        return new HandlerExecutor(animHandler);
     }
 
     /**
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/dagger/WMShellModule.java b/libs/WindowManager/Shell/src/com/android/wm/shell/dagger/WMShellModule.java
index 2fd8c27..5d5e4d3 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/dagger/WMShellModule.java
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/dagger/WMShellModule.java
@@ -68,6 +68,8 @@
 import com.android.wm.shell.common.DisplayLayout;
 import com.android.wm.shell.common.FloatingContentCoordinator;
 import com.android.wm.shell.common.LaunchAdjacentController;
+import com.android.wm.shell.common.MultiDisplayDragMoveIndicatorController;
+import com.android.wm.shell.common.MultiDisplayDragMoveIndicatorSurface;
 import com.android.wm.shell.common.MultiInstanceHelper;
 import com.android.wm.shell.common.ShellExecutor;
 import com.android.wm.shell.common.SyncTransactionQueue;
@@ -82,6 +84,7 @@
 import com.android.wm.shell.desktopmode.DefaultDragToDesktopTransitionHandler;
 import com.android.wm.shell.desktopmode.DesktopActivityOrientationChangeHandler;
 import com.android.wm.shell.desktopmode.DesktopDisplayEventHandler;
+import com.android.wm.shell.desktopmode.DesktopDisplayModeController;
 import com.android.wm.shell.desktopmode.DesktopImmersiveController;
 import com.android.wm.shell.desktopmode.DesktopMinimizationTransitionHandler;
 import com.android.wm.shell.desktopmode.DesktopMixedTransitionHandler;
@@ -428,9 +431,10 @@
             Transitions transitions,
             DisplayController displayController,
             @ShellMainThread ShellExecutor mainExecutor,
-            @ShellAnimationThread ShellExecutor animExecutor) {
+            @ShellAnimationThread ShellExecutor animExecutor,
+            @ShellAnimationThread Handler animHandler) {
         return new FreeformTaskTransitionHandler(
-                transitions, displayController, mainExecutor, animExecutor);
+                transitions, displayController, mainExecutor, animExecutor, animHandler);
     }
 
     @WMSingleton
@@ -989,7 +993,8 @@
             WindowDecorTaskResourceLoader taskResourceLoader,
             RecentsTransitionHandler recentsTransitionHandler,
             DesktopModeCompatPolicy desktopModeCompatPolicy,
-            DesktopTilingDecorViewModel desktopTilingDecorViewModel
+            DesktopTilingDecorViewModel desktopTilingDecorViewModel,
+            MultiDisplayDragMoveIndicatorController multiDisplayDragMoveIndicatorController
     ) {
         if (!DesktopModeStatus.canEnterDesktopModeOrShowAppHandle(context)) {
             return Optional.empty();
@@ -1006,7 +1011,30 @@
                 windowDecorCaptionHandleRepository, activityOrientationChangeHandler,
                 focusTransitionObserver, desktopModeEventLogger, desktopModeUiEventLogger,
                 taskResourceLoader, recentsTransitionHandler, desktopModeCompatPolicy,
-                desktopTilingDecorViewModel));
+                desktopTilingDecorViewModel,
+                multiDisplayDragMoveIndicatorController));
+    }
+
+    @WMSingleton
+    @Provides
+    static MultiDisplayDragMoveIndicatorController
+            providesMultiDisplayDragMoveIndicatorController(
+            DisplayController displayController,
+            RootTaskDisplayAreaOrganizer rootTaskDisplayAreaOrganizer,
+            MultiDisplayDragMoveIndicatorSurface.Factory
+                multiDisplayDragMoveIndicatorSurfaceFactory,
+            @ShellDesktopThread ShellExecutor desktopExecutor
+    ) {
+        return new MultiDisplayDragMoveIndicatorController(
+                displayController, rootTaskDisplayAreaOrganizer,
+                multiDisplayDragMoveIndicatorSurfaceFactory, desktopExecutor);
+    }
+
+    @WMSingleton
+    @Provides
+    static MultiDisplayDragMoveIndicatorSurface.Factory
+            providesMultiDisplayDragMoveIndicatorSurfaceFactory(Context context) {
+        return new MultiDisplayDragMoveIndicatorSurface.Factory(context);
     }
 
     @WMSingleton
@@ -1074,8 +1102,9 @@
             Context context,
             @ShellMainThread ShellExecutor mainExecutor,
             @ShellAnimationThread ShellExecutor animExecutor,
-            @ShellMainThread Handler handler) {
-        return new CloseDesktopTaskTransitionHandler(context, mainExecutor, animExecutor, handler);
+            @ShellAnimationThread Handler animHandler) {
+        return new CloseDesktopTaskTransitionHandler(context, mainExecutor, animExecutor,
+                animHandler);
     }
 
     @WMSingleton
@@ -1083,9 +1112,10 @@
     static DesktopMinimizationTransitionHandler provideDesktopMinimizationTransitionHandler(
             @ShellMainThread ShellExecutor mainExecutor,
             @ShellAnimationThread ShellExecutor animExecutor,
-            DisplayController displayController) {
+            DisplayController displayController,
+            @ShellAnimationThread Handler mainHandler) {
         return new DesktopMinimizationTransitionHandler(mainExecutor, animExecutor,
-                displayController);
+                displayController, mainHandler);
     }
 
     @WMSingleton
@@ -1230,13 +1260,10 @@
     static Optional<DesktopDisplayEventHandler> provideDesktopDisplayEventHandler(
             Context context,
             ShellInit shellInit,
-            Transitions transitions,
             DisplayController displayController,
-            RootTaskDisplayAreaOrganizer rootTaskDisplayAreaOrganizer,
-            IWindowManager windowManager,
             Optional<DesktopUserRepositories> desktopUserRepositories,
             Optional<DesktopTasksController> desktopTasksController,
-            ShellTaskOrganizer shellTaskOrganizer
+            Optional<DesktopDisplayModeController> desktopDisplayModeController
     ) {
         if (!DesktopModeStatus.canEnterDesktopMode(context)) {
             return Optional.empty();
@@ -1245,13 +1272,10 @@
                 new DesktopDisplayEventHandler(
                         context,
                         shellInit,
-                        transitions,
                         displayController,
-                        rootTaskDisplayAreaOrganizer,
-                        windowManager,
                         desktopUserRepositories.get(),
                         desktopTasksController.get(),
-                        shellTaskOrganizer));
+                        desktopDisplayModeController.get()));
     }
 
     @WMSingleton
@@ -1381,6 +1405,29 @@
         return new DesktopModeUiEventLogger(uiEventLogger, packageManager);
     }
 
+    @WMSingleton
+    @Provides
+    static Optional<DesktopDisplayModeController> provideDesktopDisplayModeController(
+            Context context,
+            Transitions transitions,
+            RootTaskDisplayAreaOrganizer rootTaskDisplayAreaOrganizer,
+            IWindowManager windowManager,
+            ShellTaskOrganizer shellTaskOrganizer,
+            DesktopWallpaperActivityTokenProvider desktopWallpaperActivityTokenProvider
+    ) {
+        if (!DesktopModeStatus.canEnterDesktopMode(context)) {
+            return Optional.empty();
+        }
+        return Optional.of(
+                new DesktopDisplayModeController(
+                        context,
+                        transitions,
+                        rootTaskDisplayAreaOrganizer,
+                        windowManager,
+                        shellTaskOrganizer,
+                        desktopWallpaperActivityTokenProvider));
+    }
+
     //
     // App zoom out
     //
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/desktopmode/CloseDesktopTaskTransitionHandler.kt b/libs/WindowManager/Shell/src/com/android/wm/shell/desktopmode/CloseDesktopTaskTransitionHandler.kt
index 1ce093e..b22a46e 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/desktopmode/CloseDesktopTaskTransitionHandler.kt
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/desktopmode/CloseDesktopTaskTransitionHandler.kt
@@ -37,7 +37,6 @@
 import com.android.internal.jank.Cuj.CUJ_DESKTOP_MODE_CLOSE_TASK
 import com.android.internal.jank.InteractionJankMonitor
 import com.android.wm.shell.common.ShellExecutor
-import com.android.wm.shell.shared.annotations.ShellMainThread
 import com.android.wm.shell.transition.Transitions
 import java.util.function.Supplier
 
@@ -49,7 +48,7 @@
     private val mainExecutor: ShellExecutor,
     private val animExecutor: ShellExecutor,
     private val transactionSupplier: Supplier<Transaction> = Supplier { Transaction() },
-    @ShellMainThread private val handler: Handler,
+    private val animHandler: Handler,
 ) : Transitions.TransitionHandler {
 
     private val runningAnimations = mutableMapOf<IBinder, List<Animator>>()
@@ -95,7 +94,7 @@
             interactionJankMonitor.begin(
                 lastChangeLeash,
                 context,
-                handler,
+                animHandler,
                 CUJ_DESKTOP_MODE_CLOSE_TASK,
             )
         }
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/desktopmode/DesktopDisplayEventHandler.kt b/libs/WindowManager/Shell/src/com/android/wm/shell/desktopmode/DesktopDisplayEventHandler.kt
index c38558d..946e795 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/desktopmode/DesktopDisplayEventHandler.kt
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/desktopmode/DesktopDisplayEventHandler.kt
@@ -16,41 +16,25 @@
 
 package com.android.wm.shell.desktopmode
 
-import android.app.WindowConfiguration.ACTIVITY_TYPE_STANDARD
-import android.app.WindowConfiguration.WINDOWING_MODE_FREEFORM
-import android.app.WindowConfiguration.WINDOWING_MODE_UNDEFINED
-import android.app.WindowConfiguration.windowingModeToString
 import android.content.Context
-import android.provider.Settings
-import android.provider.Settings.Global.DEVELOPMENT_FORCE_DESKTOP_MODE_ON_EXTERNAL_DISPLAYS
 import android.view.Display.DEFAULT_DISPLAY
-import android.view.IWindowManager
-import android.view.WindowManager.TRANSIT_CHANGE
 import android.window.DesktopExperienceFlags
-import android.window.WindowContainerTransaction
 import com.android.internal.protolog.ProtoLog
-import com.android.window.flags.Flags
-import com.android.wm.shell.RootTaskDisplayAreaOrganizer
-import com.android.wm.shell.ShellTaskOrganizer
 import com.android.wm.shell.common.DisplayController
 import com.android.wm.shell.common.DisplayController.OnDisplaysChangedListener
 import com.android.wm.shell.desktopmode.multidesks.OnDeskRemovedListener
 import com.android.wm.shell.protolog.ShellProtoLogGroup.WM_SHELL_DESKTOP_MODE
 import com.android.wm.shell.shared.desktopmode.DesktopModeStatus
 import com.android.wm.shell.sysui.ShellInit
-import com.android.wm.shell.transition.Transitions
 
 /** Handles display events in desktop mode */
 class DesktopDisplayEventHandler(
     private val context: Context,
     shellInit: ShellInit,
-    private val transitions: Transitions,
     private val displayController: DisplayController,
-    private val rootTaskDisplayAreaOrganizer: RootTaskDisplayAreaOrganizer,
-    private val windowManager: IWindowManager,
     private val desktopUserRepositories: DesktopUserRepositories,
     private val desktopTasksController: DesktopTasksController,
-    private val shellTaskOrganizer: ShellTaskOrganizer,
+    private val desktopDisplayModeController: DesktopDisplayModeController,
 ) : OnDisplaysChangedListener, OnDeskRemovedListener {
 
     private val desktopRepository: DesktopRepository
@@ -70,7 +54,7 @@
 
     override fun onDisplayAdded(displayId: Int) {
         if (displayId != DEFAULT_DISPLAY) {
-            refreshDisplayWindowingMode()
+            desktopDisplayModeController.refreshDisplayWindowingMode()
         }
 
         if (!supportsDesks(displayId)) {
@@ -88,7 +72,7 @@
 
     override fun onDisplayRemoved(displayId: Int) {
         if (displayId != DEFAULT_DISPLAY) {
-            refreshDisplayWindowingMode()
+            desktopDisplayModeController.refreshDisplayWindowingMode()
         }
 
         // TODO: b/362720497 - move desks in closing display to the remaining desk.
@@ -102,65 +86,6 @@
         }
     }
 
-    private fun refreshDisplayWindowingMode() {
-        if (!Flags.enableDisplayWindowingModeSwitching()) return
-        // TODO: b/375319538 - Replace the check with a DisplayManager API once it's available.
-        val isExtendedDisplayEnabled =
-            0 !=
-                Settings.Global.getInt(
-                    context.contentResolver,
-                    DEVELOPMENT_FORCE_DESKTOP_MODE_ON_EXTERNAL_DISPLAYS,
-                    0,
-                )
-        if (!isExtendedDisplayEnabled) {
-            // No action needed in mirror or projected mode.
-            return
-        }
-
-        val hasNonDefaultDisplay =
-            rootTaskDisplayAreaOrganizer.getDisplayIds().any { displayId ->
-                displayId != DEFAULT_DISPLAY
-            }
-        val targetDisplayWindowingMode =
-            if (hasNonDefaultDisplay) {
-                WINDOWING_MODE_FREEFORM
-            } else {
-                // Use the default display windowing mode when no non-default display.
-                windowManager.getWindowingMode(DEFAULT_DISPLAY)
-            }
-        val tdaInfo = rootTaskDisplayAreaOrganizer.getDisplayAreaInfo(DEFAULT_DISPLAY)
-        requireNotNull(tdaInfo) { "DisplayAreaInfo of DEFAULT_DISPLAY must be non-null." }
-        val currentDisplayWindowingMode = tdaInfo.configuration.windowConfiguration.windowingMode
-        if (currentDisplayWindowingMode == targetDisplayWindowingMode) {
-            // Already in the target mode.
-            return
-        }
-
-        logV(
-            "As an external display is connected, changing default display's windowing mode from" +
-                " ${windowingModeToString(currentDisplayWindowingMode)}" +
-                " to ${windowingModeToString(targetDisplayWindowingMode)}"
-        )
-
-        val wct = WindowContainerTransaction()
-        wct.setWindowingMode(tdaInfo.token, targetDisplayWindowingMode)
-        shellTaskOrganizer
-            .getRunningTasks(DEFAULT_DISPLAY)
-            .filter { it.activityType == ACTIVITY_TYPE_STANDARD }
-            .forEach {
-                // TODO: b/391965153 - Reconsider the logic under multi-desk window hierarchy
-                when (it.windowingMode) {
-                    currentDisplayWindowingMode -> {
-                        wct.setWindowingMode(it.token, currentDisplayWindowingMode)
-                    }
-                    targetDisplayWindowingMode -> {
-                        wct.setWindowingMode(it.token, WINDOWING_MODE_UNDEFINED)
-                    }
-                }
-            }
-        transitions.startTransition(TRANSIT_CHANGE, wct, /* handler= */ null)
-    }
-
     // TODO: b/362720497 - connected/projected display considerations.
     private fun supportsDesks(displayId: Int): Boolean =
         DesktopModeStatus.canEnterDesktopMode(context)
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/desktopmode/DesktopDisplayModeController.kt b/libs/WindowManager/Shell/src/com/android/wm/shell/desktopmode/DesktopDisplayModeController.kt
new file mode 100644
index 0000000..c9a63ff
--- /dev/null
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/desktopmode/DesktopDisplayModeController.kt
@@ -0,0 +1,121 @@
+/*
+ * Copyright (C) 2025 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.wm.shell.desktopmode
+
+import android.app.WindowConfiguration.ACTIVITY_TYPE_STANDARD
+import android.app.WindowConfiguration.WINDOWING_MODE_FREEFORM
+import android.app.WindowConfiguration.WINDOWING_MODE_FULLSCREEN
+import android.app.WindowConfiguration.WINDOWING_MODE_UNDEFINED
+import android.app.WindowConfiguration.windowingModeToString
+import android.content.Context
+import android.provider.Settings
+import android.provider.Settings.Global.DEVELOPMENT_FORCE_DESKTOP_MODE_ON_EXTERNAL_DISPLAYS
+import android.view.Display.DEFAULT_DISPLAY
+import android.view.IWindowManager
+import android.view.WindowManager.TRANSIT_CHANGE
+import android.window.WindowContainerTransaction
+import com.android.internal.protolog.ProtoLog
+import com.android.window.flags.Flags
+import com.android.wm.shell.RootTaskDisplayAreaOrganizer
+import com.android.wm.shell.ShellTaskOrganizer
+import com.android.wm.shell.desktopmode.desktopwallpaperactivity.DesktopWallpaperActivityTokenProvider
+import com.android.wm.shell.protolog.ShellProtoLogGroup.WM_SHELL_DESKTOP_MODE
+import com.android.wm.shell.transition.Transitions
+
+/** Controls the display windowing mode in desktop mode */
+class DesktopDisplayModeController(
+    private val context: Context,
+    private val transitions: Transitions,
+    private val rootTaskDisplayAreaOrganizer: RootTaskDisplayAreaOrganizer,
+    private val windowManager: IWindowManager,
+    private val shellTaskOrganizer: ShellTaskOrganizer,
+    private val desktopWallpaperActivityTokenProvider: DesktopWallpaperActivityTokenProvider,
+) {
+
+    fun refreshDisplayWindowingMode() {
+        if (!Flags.enableDisplayWindowingModeSwitching()) return
+        // TODO: b/375319538 - Replace the check with a DisplayManager API once it's available.
+        val isExtendedDisplayEnabled =
+            0 !=
+                Settings.Global.getInt(
+                    context.contentResolver,
+                    DEVELOPMENT_FORCE_DESKTOP_MODE_ON_EXTERNAL_DISPLAYS,
+                    0,
+                )
+        if (!isExtendedDisplayEnabled) {
+            // No action needed in mirror or projected mode.
+            return
+        }
+
+        val hasNonDefaultDisplay =
+            rootTaskDisplayAreaOrganizer.getDisplayIds().any { displayId ->
+                displayId != DEFAULT_DISPLAY
+            }
+        val targetDisplayWindowingMode =
+            if (hasNonDefaultDisplay) {
+                WINDOWING_MODE_FREEFORM
+            } else {
+                // Use the default display windowing mode when no non-default display.
+                windowManager.getWindowingMode(DEFAULT_DISPLAY)
+            }
+        val tdaInfo = rootTaskDisplayAreaOrganizer.getDisplayAreaInfo(DEFAULT_DISPLAY)
+        requireNotNull(tdaInfo) { "DisplayAreaInfo of DEFAULT_DISPLAY must be non-null." }
+        val currentDisplayWindowingMode = tdaInfo.configuration.windowConfiguration.windowingMode
+        if (currentDisplayWindowingMode == targetDisplayWindowingMode) {
+            // Already in the target mode.
+            return
+        }
+
+        logV(
+            "As an external display is connected, changing default display's windowing mode from" +
+                " ${windowingModeToString(currentDisplayWindowingMode)}" +
+                " to ${windowingModeToString(targetDisplayWindowingMode)}"
+        )
+
+        val wct = WindowContainerTransaction()
+        wct.setWindowingMode(tdaInfo.token, targetDisplayWindowingMode)
+        shellTaskOrganizer
+            .getRunningTasks(DEFAULT_DISPLAY)
+            .filter { it.activityType == ACTIVITY_TYPE_STANDARD }
+            .forEach {
+                // TODO: b/391965153 - Reconsider the logic under multi-desk window hierarchy
+                when (it.windowingMode) {
+                    currentDisplayWindowingMode -> {
+                        wct.setWindowingMode(it.token, currentDisplayWindowingMode)
+                    }
+                    targetDisplayWindowingMode -> {
+                        wct.setWindowingMode(it.token, WINDOWING_MODE_UNDEFINED)
+                    }
+                }
+            }
+        // The override windowing mode of DesktopWallpaper can be UNDEFINED on fullscreen-display
+        // right after the first launch while its resolved windowing mode is FULLSCREEN. We here
+        // it has the FULLSCREEN override windowing mode.
+        desktopWallpaperActivityTokenProvider.getToken(DEFAULT_DISPLAY)?.let { token ->
+            wct.setWindowingMode(token, WINDOWING_MODE_FULLSCREEN)
+        }
+        transitions.startTransition(TRANSIT_CHANGE, wct, /* handler= */ null)
+    }
+
+    private fun logV(msg: String, vararg arguments: Any?) {
+        ProtoLog.v(WM_SHELL_DESKTOP_MODE, "%s: $msg", TAG, *arguments)
+    }
+
+    companion object {
+        private const val TAG = "DesktopDisplayModeController"
+    }
+}
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/desktopmode/DesktopMinimizationTransitionHandler.kt b/libs/WindowManager/Shell/src/com/android/wm/shell/desktopmode/DesktopMinimizationTransitionHandler.kt
index 728638d..7074e8b 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/desktopmode/DesktopMinimizationTransitionHandler.kt
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/desktopmode/DesktopMinimizationTransitionHandler.kt
@@ -18,14 +18,17 @@
 
 import android.animation.Animator
 import android.app.WindowConfiguration.WINDOWING_MODE_FREEFORM
+import android.os.Handler
 import android.os.IBinder
-import android.util.DisplayMetrics
 import android.view.SurfaceControl.Transaction
 import android.window.TransitionInfo
 import android.window.TransitionRequestInfo
 import android.window.WindowContainerTransaction
+import com.android.internal.jank.InteractionJankMonitor
+import com.android.internal.protolog.ProtoLog
 import com.android.wm.shell.common.DisplayController
 import com.android.wm.shell.common.ShellExecutor
+import com.android.wm.shell.protolog.ShellProtoLogGroup.WM_SHELL_DESKTOP_MODE
 import com.android.wm.shell.shared.TransitionUtil
 import com.android.wm.shell.shared.animation.MinimizeAnimator.create
 import com.android.wm.shell.transition.Transitions
@@ -41,6 +44,7 @@
     private val mainExecutor: ShellExecutor,
     private val animExecutor: ShellExecutor,
     private val displayController: DisplayController,
+    private val animHandler: Handler,
 ) : Transitions.TransitionHandler {
 
     /** Shouldn't handle anything */
@@ -90,10 +94,30 @@
         val t = Transaction()
         val sc = change.leash
         finishTransaction.hide(sc)
-        val displayMetrics: DisplayMetrics? =
-            change.taskInfo?.let {
-                displayController.getDisplayContext(it.displayId)?.getResources()?.displayMetrics
-            }
-        return displayMetrics?.let { create(it, change, t, onAnimFinish) }
+        val displayContext =
+            change.taskInfo?.let { displayController.getDisplayContext(it.displayId) }
+        if (displayContext == null) {
+            logW(
+                "displayContext is null for taskId=${change.taskInfo?.taskId}, " +
+                    "displayId=${change.taskInfo?.displayId}"
+            )
+            return null
+        }
+        return create(
+            displayContext,
+            change,
+            t,
+            onAnimFinish,
+            InteractionJankMonitor.getInstance(),
+            animHandler,
+        )
+    }
+
+    private companion object {
+        private fun logW(msg: String, vararg arguments: Any?) {
+            ProtoLog.w(WM_SHELL_DESKTOP_MODE, "%s: $msg", TAG, *arguments)
+        }
+
+        const val TAG = "DesktopMinimizationTransitionHandler"
     }
 }
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/desktopmode/DesktopRepository.kt b/libs/WindowManager/Shell/src/com/android/wm/shell/desktopmode/DesktopRepository.kt
index 031925b..7c6cf4a 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/desktopmode/DesktopRepository.kt
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/desktopmode/DesktopRepository.kt
@@ -332,29 +332,22 @@
         return false
     }
 
-    /**
-     * Adds given task to the closing task list for [displayId]'s active desk.
-     *
-     * TODO: b/389960283 - add explicit [deskId] argument.
-     */
-    fun addClosingTask(displayId: Int, taskId: Int) {
-        val activeDesk =
-            desktopData.getActiveDesk(displayId)
-                ?: error("Expected active desk in display: $displayId")
-        if (activeDesk.closingTasks.add(taskId)) {
-            logD(
-                "Added closing task=%d displayId=%d deskId=%d",
-                taskId,
-                displayId,
-                activeDesk.deskId,
-            )
+    /** Adds given task to the closing task list of its desk. */
+    fun addClosingTask(displayId: Int, deskId: Int?, taskId: Int) {
+        val desk =
+            deskId?.let { desktopData.getDesk(it) }
+                ?: checkNotNull(desktopData.getActiveDesk(displayId)) {
+                    "Expected active desk in display: $displayId"
+                }
+        if (desk.closingTasks.add(taskId)) {
+            logD("Added closing task=%d displayId=%d deskId=%d", taskId, displayId, desk.deskId)
         } else {
             // If the task hasn't been removed from closing list after it disappeared.
             logW(
                 "Task with taskId=%d displayId=%d deskId=%d is already closing",
                 taskId,
                 displayId,
-                activeDesk.deskId,
+                desk.deskId,
             )
         }
     }
@@ -392,7 +385,8 @@
      * Checks if a task is the only visible, non-closing, non-minimized task on the active desk of
      * the given display, or any display's active desk if [displayId] is [INVALID_DISPLAY].
      *
-     * TODO: b/389960283 - add explicit [deskId] argument.
+     * TODO: b/389960283 - consider forcing callers to use [isOnlyVisibleNonClosingTaskInDesk] with
+     *   an explicit desk id instead of using this function and defaulting to the active one.
      */
     fun isOnlyVisibleNonClosingTask(taskId: Int, displayId: Int = INVALID_DISPLAY): Boolean {
         val activeDesks =
@@ -402,14 +396,27 @@
                 desktopData.getAllActiveDesks()
             }
         return activeDesks.any { desk ->
-            desk.visibleTasks
-                .subtract(desk.closingTasks)
-                .subtract(desk.minimizedTasks)
-                .singleOrNull() == taskId
+            isOnlyVisibleNonClosingTaskInDesk(
+                taskId = taskId,
+                deskId = desk.deskId,
+                displayId = desk.displayId,
+            )
         }
     }
 
     /**
+     * Checks if a task is the only visible, non-closing, non-minimized task on the given desk of
+     * the given display.
+     */
+    fun isOnlyVisibleNonClosingTaskInDesk(taskId: Int, deskId: Int, displayId: Int): Boolean {
+        val desk = desktopData.getDesk(deskId) ?: return false
+        return desk.visibleTasks
+            .subtract(desk.closingTasks)
+            .subtract(desk.minimizedTasks)
+            .singleOrNull() == taskId
+    }
+
+    /**
      * Returns the active tasks in the given display's active desk.
      *
      * TODO: b/389960283 - migrate callers to [getActiveTaskIdsInDesk].
@@ -686,6 +693,11 @@
      * TODO: b/389960283 - add explicit [deskId] argument.
      */
     fun setTopTransparentFullscreenTaskId(displayId: Int, taskId: Int) {
+        logD(
+            "Top transparent fullscreen task set for display: taskId=%d, displayId=%d",
+            taskId,
+            displayId,
+        )
         desktopData.getActiveDesk(displayId)?.topTransparentFullscreenTaskId = taskId
     }
 
@@ -703,6 +715,11 @@
      * TODO: b/389960283 - add explicit [deskId] argument.
      */
     fun clearTopTransparentFullscreenTaskId(displayId: Int) {
+        logD(
+            "Top transparent fullscreen task cleared for display: taskId=%d, displayId=%d",
+            desktopData.getActiveDesk(displayId)?.topTransparentFullscreenTaskId,
+            displayId,
+        )
         desktopData.getActiveDesk(displayId)?.topTransparentFullscreenTaskId = null
     }
 
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/desktopmode/DesktopTasksController.kt b/libs/WindowManager/Shell/src/com/android/wm/shell/desktopmode/DesktopTasksController.kt
index 93058db..87d9674 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/desktopmode/DesktopTasksController.kt
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/desktopmode/DesktopTasksController.kt
@@ -117,6 +117,7 @@
 import com.android.wm.shell.recents.RecentsTransitionStateListener
 import com.android.wm.shell.recents.RecentsTransitionStateListener.RecentsTransitionState
 import com.android.wm.shell.recents.RecentsTransitionStateListener.TRANSITION_STATE_NOT_RUNNING
+import com.android.wm.shell.shared.R as SharedR
 import com.android.wm.shell.shared.TransitionUtil
 import com.android.wm.shell.shared.annotations.ExternalThread
 import com.android.wm.shell.shared.annotations.ShellDesktopThread
@@ -801,6 +802,9 @@
     ): ((IBinder) -> Unit) {
         val taskId = taskInfo.taskId
         val deskId = taskRepository.getDeskIdForTask(taskInfo.taskId)
+        if (deskId == null && DesktopExperienceFlags.ENABLE_MULTIPLE_DESKTOPS_BACKEND.isTrue) {
+            error("Did not find desk for task: $taskId")
+        }
         snapEventHandler.removeTaskIfTiled(displayId, taskId)
         val shouldExitDesktop =
             willExitDesktop(
@@ -818,7 +822,7 @@
                 shouldEndUpAtHome = true,
             )
 
-        taskRepository.addClosingTask(displayId, taskId)
+        taskRepository.addClosingTask(displayId = displayId, deskId = deskId, taskId = taskId)
         taskbarDesktopTaskListener?.onTaskbarCornerRoundingUpdate(
             doesAnyTaskRequireTaskbarRounding(displayId, taskId)
         )
@@ -870,6 +874,10 @@
     private fun minimizeTaskInner(taskInfo: RunningTaskInfo, minimizeReason: MinimizeReason) {
         val taskId = taskInfo.taskId
         val deskId = taskRepository.getDeskIdForTask(taskInfo.taskId)
+        if (deskId == null && DesktopExperienceFlags.ENABLE_MULTIPLE_DESKTOPS_BACKEND.isTrue) {
+            logW("minimizeTaskInner: desk not found for task: ${taskInfo.taskId}")
+            return
+        }
         val displayId = taskInfo.displayId
         val wct = WindowContainerTransaction()
 
@@ -890,10 +898,26 @@
                 taskInfo = taskInfo,
                 reason = DesktopImmersiveController.ExitReason.MINIMIZED,
             )
-
-        wct.reorder(taskInfo.token, false)
-        val isLastTask = taskRepository.isOnlyVisibleNonClosingTask(taskId, displayId)
-        val transition: IBinder =
+        if (DesktopExperienceFlags.ENABLE_MULTIPLE_DESKTOPS_BACKEND.isTrue) {
+            desksOrganizer.minimizeTask(
+                wct = wct,
+                deskId = checkNotNull(deskId) { "Expected non-null deskId" },
+                task = taskInfo,
+            )
+        } else {
+            wct.reorder(taskInfo.token, /* onTop= */ false)
+        }
+        val isLastTask =
+            if (DesktopExperienceFlags.ENABLE_MULTIPLE_DESKTOPS_BACKEND.isTrue) {
+                taskRepository.isOnlyVisibleNonClosingTaskInDesk(
+                    taskId = taskId,
+                    deskId = checkNotNull(deskId) { "Expected non-null deskId" },
+                    displayId = displayId,
+                )
+            } else {
+                taskRepository.isOnlyVisibleNonClosingTask(taskId = taskId, displayId = displayId)
+            }
+        val transition =
             freeformTaskTransitionStarter.startMinimizedModeTransition(wct, taskId, isLastTask)
         desktopTasksLimiter.ifPresent {
             it.addPendingMinimizeChange(
@@ -1055,7 +1079,8 @@
         )
     }
 
-    private fun startLaunchTransition(
+    @VisibleForTesting
+    fun startLaunchTransition(
         transitionType: Int,
         wct: WindowContainerTransaction,
         launchingTaskId: Int?,
@@ -1063,34 +1088,52 @@
         displayId: Int = DEFAULT_DISPLAY,
         unminimizeReason: UnminimizeReason = UnminimizeReason.UNKNOWN,
     ): IBinder {
+        // TODO: b/397619806 - Consolidate sharable logic with [handleFreeformTaskLaunch].
+        var launchTransaction = wct
         val taskIdToMinimize =
             addAndGetMinimizeChanges(
                 displayId,
-                wct,
+                launchTransaction,
                 newTaskId = launchingTaskId,
                 launchingNewIntent = launchingTaskId == null,
             )
         val exitImmersiveResult =
             desktopImmersiveController.exitImmersiveIfApplicable(
-                wct = wct,
+                wct = launchTransaction,
                 displayId = displayId,
                 excludeTaskId = launchingTaskId,
                 reason = DesktopImmersiveController.ExitReason.TASK_LAUNCH,
             )
+        var deskIdToActivate: Int? = null
+        if (
+            DesktopExperienceFlags.ENABLE_DISPLAY_WINDOWING_MODE_SWITCHING.isTrue &&
+                !isDesktopModeShowing(displayId)
+        ) {
+            deskIdToActivate =
+                checkNotNull(
+                    launchingTaskId?.let { taskRepository.getDeskIdForTask(it) }
+                        ?: getDefaultDeskId(displayId)
+                )
+            val activateDeskWct = WindowContainerTransaction()
+            addDeskActivationChanges(deskIdToActivate, activateDeskWct)
+            // Desk activation must be handled before app launch-related transactions.
+            activateDeskWct.merge(launchTransaction, /* transfer= */ true)
+            launchTransaction = activateDeskWct
+        }
         val t =
             if (remoteTransition == null) {
                 desktopMixedTransitionHandler.startLaunchTransition(
                     transitionType = transitionType,
-                    wct = wct,
+                    wct = launchTransaction,
                     taskId = launchingTaskId,
                     minimizingTaskId = taskIdToMinimize,
                     exitingImmersiveTask = exitImmersiveResult.asExit()?.exitingTask,
                 )
             } else if (taskIdToMinimize == null) {
                 val remoteTransitionHandler = OneShotRemoteHandler(mainExecutor, remoteTransition)
-                transitions.startTransition(transitionType, wct, remoteTransitionHandler).also {
-                    remoteTransitionHandler.setTransition(it)
-                }
+                transitions
+                    .startTransition(transitionType, launchTransaction, remoteTransitionHandler)
+                    .also { remoteTransitionHandler.setTransition(it) }
             } else {
                 val remoteTransitionHandler =
                     DesktopWindowLimitRemoteHandler(
@@ -1099,9 +1142,9 @@
                         remoteTransition,
                         taskIdToMinimize,
                     )
-                transitions.startTransition(transitionType, wct, remoteTransitionHandler).also {
-                    remoteTransitionHandler.setTransition(it)
-                }
+                transitions
+                    .startTransition(transitionType, launchTransaction, remoteTransitionHandler)
+                    .also { remoteTransitionHandler.setTransition(it) }
             }
         if (taskIdToMinimize != null) {
             addPendingMinimizeTransition(t, taskIdToMinimize, MinimizeReason.TASK_LIMIT)
@@ -1109,6 +1152,24 @@
         if (launchingTaskId != null && taskRepository.isMinimizedTask(launchingTaskId)) {
             addPendingUnminimizeTransition(t, displayId, launchingTaskId, unminimizeReason)
         }
+        if (
+            DesktopExperienceFlags.ENABLE_MULTIPLE_DESKTOPS_BACKEND.isTrue &&
+                deskIdToActivate != null
+        ) {
+            if (DesktopExperienceFlags.ENABLE_DISPLAY_WINDOWING_MODE_SWITCHING.isTrue) {
+                desksTransitionObserver.addPendingTransition(
+                    DeskTransition.ActivateDesk(
+                        token = t,
+                        displayId = displayId,
+                        deskId = deskIdToActivate,
+                    )
+                )
+            }
+
+            desktopModeEnterExitTransitionListener?.onEnterDesktopModeTransitionStarted(
+                FREEFORM_ANIMATION_DURATION
+            )
+        }
         exitImmersiveResult.asExit()?.runOnTransitionStart?.invoke(t)
         return t
     }
@@ -1231,9 +1292,9 @@
         //  home.
         if (Flags.enablePerDisplayDesktopWallpaperActivity()) {
             performDesktopExitCleanupIfNeeded(
-                task.taskId,
-                task.displayId,
-                wct,
+                taskId = task.taskId,
+                displayId = task.displayId,
+                wct = wct,
                 forceToFullscreen = false,
                 // TODO: b/371096166 - Temporary turing home relaunch off to prevent home stealing
                 // display focus. Remove shouldEndUpAtHome = false when home focus handling
@@ -1684,6 +1745,9 @@
                 launchWindowingMode = WINDOWING_MODE_FULLSCREEN
                 pendingIntentBackgroundActivityStartMode =
                     ActivityOptions.MODE_BACKGROUND_ACTIVITY_START_ALLOW_ALWAYS
+                if (Flags.enablePerDisplayDesktopWallpaperActivity()) {
+                    launchDisplayId = displayId
+                }
             }
         val pendingIntent =
             PendingIntent.getActivityAsUser(
@@ -1800,6 +1864,7 @@
 
     private fun performDesktopExitCleanupIfNeeded(
         taskId: Int,
+        deskId: Int? = null,
         displayId: Int,
         wct: WindowContainerTransaction,
         forceToFullscreen: Boolean,
@@ -1813,13 +1878,14 @@
         //  |RunOnTransitStart| when the transition is started.
         return performDesktopExitCleanUp(
             wct = wct,
-            deskId = null,
+            deskId = deskId,
             displayId = displayId,
             willExitDesktop = true,
             shouldEndUpAtHome = shouldEndUpAtHome,
         )
     }
 
+    /** TODO: b/394268248 - update [deskId] to be non-null. */
     private fun performDesktopExitCleanUp(
         wct: WindowContainerTransaction,
         deskId: Int?,
@@ -2015,7 +2081,9 @@
         }
         val cornerRadius =
             context.resources
-                .getDimensionPixelSize(R.dimen.desktop_windowing_freeform_rounded_corner_radius)
+                .getDimensionPixelSize(
+                    SharedR.dimen.desktop_windowing_freeform_rounded_corner_radius
+                )
                 .toFloat()
         info.changes
             .filter { it.taskInfo?.windowingMode == WINDOWING_MODE_FREEFORM }
@@ -2370,17 +2438,28 @@
     ): WindowContainerTransaction? {
         logV("handleTaskClosing")
         if (!isDesktopModeShowing(task.displayId)) return null
+        val deskId = taskRepository.getDeskIdForTask(task.taskId)
+        if (deskId == null && DesktopExperienceFlags.ENABLE_MULTIPLE_DESKTOPS_BACKEND.isTrue) {
+            return null
+        }
 
         val wct = WindowContainerTransaction()
-        performDesktopExitCleanupIfNeeded(
-            task.taskId,
-            task.displayId,
-            wct,
-            forceToFullscreen = false,
-        )
+        val deactivationRunnable =
+            performDesktopExitCleanupIfNeeded(
+                taskId = task.taskId,
+                deskId = deskId,
+                displayId = task.displayId,
+                wct = wct,
+                forceToFullscreen = false,
+            )
+        deactivationRunnable?.invoke(transition)
 
         if (!DesktopModeFlags.ENABLE_DESKTOP_WINDOWING_BACK_NAVIGATION.isTrue()) {
-            taskRepository.addClosingTask(task.displayId, task.taskId)
+            taskRepository.addClosingTask(
+                displayId = task.displayId,
+                deskId = deskId,
+                taskId = task.taskId,
+            )
             snapEventHandler.removeTaskIfTiled(task.displayId, task.taskId)
         }
 
@@ -2584,9 +2663,9 @@
         wct.setDensityDpi(taskInfo.token, getDefaultDensityDpi())
 
         performDesktopExitCleanupIfNeeded(
-            taskInfo.taskId,
-            taskInfo.displayId,
-            wct,
+            taskId = taskInfo.taskId,
+            displayId = taskInfo.displayId,
+            wct = wct,
             forceToFullscreen = false,
             shouldEndUpAtHome = false,
         )
@@ -2665,10 +2744,9 @@
         activateDesk(deskId, remoteTransition)
     }
 
-    /** Activates the given desk. */
-    fun activateDesk(deskId: Int, remoteTransition: RemoteTransition? = null) {
+    /** Activates the given desk but without starting a transition. */
+    fun addDeskActivationChanges(deskId: Int, wct: WindowContainerTransaction) {
         val displayId = taskRepository.getDisplayForDesk(deskId)
-        val wct = WindowContainerTransaction()
         if (DesktopExperienceFlags.ENABLE_MULTIPLE_DESKTOPS_BACKEND.isTrue) {
             prepareForDeskActivation(displayId, wct)
             desksOrganizer.activateDesk(wct, deskId)
@@ -2681,6 +2759,13 @@
         } else {
             bringDesktopAppsToFront(displayId, wct)
         }
+    }
+
+    /** Activates the given desk. */
+    fun activateDesk(deskId: Int, remoteTransition: RemoteTransition? = null) {
+        val displayId = taskRepository.getDisplayForDesk(deskId)
+        val wct = WindowContainerTransaction()
+        addDeskActivationChanges(deskId, wct)
 
         val transitionType = transitionType(remoteTransition)
         val handler =
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/desktopmode/DesktopTasksLimiter.kt b/libs/WindowManager/Shell/src/com/android/wm/shell/desktopmode/DesktopTasksLimiter.kt
index 81b136d..f9ab359 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/desktopmode/DesktopTasksLimiter.kt
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/desktopmode/DesktopTasksLimiter.kt
@@ -26,7 +26,6 @@
 import android.window.TransitionInfo
 import android.window.WindowContainerTransaction
 import androidx.annotation.VisibleForTesting
-import com.android.internal.jank.Cuj.CUJ_DESKTOP_MODE_MINIMIZE_WINDOW
 import com.android.internal.jank.InteractionJankMonitor
 import com.android.internal.protolog.ProtoLog
 import com.android.wm.shell.ShellTaskOrganizer
@@ -176,28 +175,13 @@
             return taskChange.mode == TRANSIT_TO_BACK
         }
 
-        override fun onTransitionStarting(transition: IBinder) {
-            val mActiveTaskDetails = activeTransitionTokensAndTasks[transition]
-            val info = mActiveTaskDetails?.transitionInfo ?: return
-            val minimizeChange = getMinimizeChange(info, mActiveTaskDetails.taskId) ?: return
-            // Begin minimize window CUJ instrumentation.
-            interactionJankMonitor.begin(
-                minimizeChange.leash,
-                context,
-                handler,
-                CUJ_DESKTOP_MODE_MINIMIZE_WINDOW,
-            )
-        }
-
         private fun getMinimizeChange(info: TransitionInfo, taskId: Int): TransitionInfo.Change? =
             info.changes.find { change ->
                 change.taskInfo?.taskId == taskId && change.mode == TRANSIT_TO_BACK
             }
 
         override fun onTransitionMerged(merged: IBinder, playing: IBinder) {
-            if (activeTransitionTokensAndTasks.remove(merged) != null) {
-                interactionJankMonitor.end(CUJ_DESKTOP_MODE_MINIMIZE_WINDOW)
-            }
+            activeTransitionTokensAndTasks.remove(merged)
             pendingTransitionTokensAndTasks.remove(merged)?.let { taskToTransfer ->
                 pendingTransitionTokensAndTasks[playing] = taskToTransfer
             }
@@ -209,13 +193,6 @@
         }
 
         override fun onTransitionFinished(transition: IBinder, aborted: Boolean) {
-            if (activeTransitionTokensAndTasks.remove(transition) != null) {
-                if (aborted) {
-                    interactionJankMonitor.cancel(CUJ_DESKTOP_MODE_MINIMIZE_WINDOW)
-                } else {
-                    interactionJankMonitor.end(CUJ_DESKTOP_MODE_MINIMIZE_WINDOW)
-                }
-            }
             pendingTransitionTokensAndTasks.remove(transition)
             activeUnminimizeTransitionTokensAndTasks.remove(transition)
             pendingUnminimizeTransitionTokensAndTasks.remove(transition)
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/libs/WindowManager/Shell/src/com/android/wm/shell/desktopmode/multidesks/DesksOrganizer.kt b/libs/WindowManager/Shell/src/com/android/wm/shell/desktopmode/multidesks/DesksOrganizer.kt
index 0f2f371..fc359d7 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/desktopmode/multidesks/DesksOrganizer.kt
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/desktopmode/multidesks/DesksOrganizer.kt
@@ -40,9 +40,19 @@
         task: ActivityManager.RunningTaskInfo,
     )
 
+    /** Minimizes the given task of the given deskId. */
+    fun minimizeTask(
+        wct: WindowContainerTransaction,
+        deskId: Int,
+        task: ActivityManager.RunningTaskInfo,
+    )
+
     /** Whether the change is for the given desk id. */
     fun isDeskChange(change: TransitionInfo.Change, deskId: Int): Boolean
 
+    /** Whether the change is for a known desk. */
+    fun isDeskChange(change: TransitionInfo.Change): Boolean
+
     /**
      * Returns the desk id in which the task in the given change is located at the end of a
      * transition, if any.
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/desktopmode/multidesks/RootTaskDesksOrganizer.kt b/libs/WindowManager/Shell/src/com/android/wm/shell/desktopmode/multidesks/RootTaskDesksOrganizer.kt
index 339932c..f576258 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/desktopmode/multidesks/RootTaskDesksOrganizer.kt
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/desktopmode/multidesks/RootTaskDesksOrganizer.kt
@@ -15,7 +15,9 @@
  */
 package com.android.wm.shell.desktopmode.multidesks
 
+import android.annotation.SuppressLint
 import android.app.ActivityManager.RunningTaskInfo
+import android.app.ActivityTaskManager.INVALID_TASK_ID
 import android.app.WindowConfiguration.ACTIVITY_TYPE_STANDARD
 import android.app.WindowConfiguration.ACTIVITY_TYPE_UNDEFINED
 import android.app.WindowConfiguration.WINDOWING_MODE_FREEFORM
@@ -25,6 +27,7 @@
 import android.view.WindowManager.TRANSIT_TO_FRONT
 import android.window.DesktopExperienceFlags
 import android.window.TransitionInfo
+import android.window.WindowContainerToken
 import android.window.WindowContainerTransaction
 import androidx.core.util.forEach
 import com.android.internal.annotations.VisibleForTesting
@@ -43,8 +46,12 @@
     private val shellTaskOrganizer: ShellTaskOrganizer,
 ) : DesksOrganizer, ShellTaskOrganizer.TaskListener {
 
-    private val deskCreateRequests = mutableListOf<CreateRequest>()
-    @VisibleForTesting val roots = SparseArray<DeskRoot>()
+    private val createDeskRootRequests = mutableListOf<CreateDeskRequest>()
+    @VisibleForTesting val deskRootsByDeskId = SparseArray<DeskRoot>()
+    private val createDeskMinimizationRootRequests =
+        mutableListOf<CreateDeskMinimizationRootRequest>()
+    @VisibleForTesting
+    val deskMinimizationRootsByDeskId: MutableMap<Int, DeskMinimizationRoot> = mutableMapOf()
 
     init {
         if (DesktopExperienceFlags.ENABLE_MULTIPLE_DESKTOPS_BACKEND.isTrue) {
@@ -57,7 +64,7 @@
 
     override fun createDesk(displayId: Int, callback: OnCreateCallback) {
         logV("createDesk in display: %d", displayId)
-        deskCreateRequests += CreateRequest(displayId, callback)
+        createDeskRootRequests += CreateDeskRequest(displayId, callback)
         shellTaskOrganizer.createRootTask(
             displayId,
             WINDOWING_MODE_FREEFORM,
@@ -68,14 +75,14 @@
 
     override fun removeDesk(wct: WindowContainerTransaction, deskId: Int) {
         logV("removeDesk %d", deskId)
-        val desk = checkNotNull(roots[deskId]) { "Root not found for desk: $deskId" }
-        wct.removeRootTask(desk.taskInfo.token)
+        deskRootsByDeskId[deskId]?.let { root -> wct.removeRootTask(root.token) }
+        deskMinimizationRootsByDeskId[deskId]?.let { root -> wct.removeRootTask(root.token) }
     }
 
     override fun activateDesk(wct: WindowContainerTransaction, deskId: Int) {
         logV("activateDesk %d", deskId)
-        val root = checkNotNull(roots[deskId]) { "Root not found for desk: $deskId" }
-        wct.reorder(root.taskInfo.token, /* onTop= */ true)
+        val root = checkNotNull(deskRootsByDeskId[deskId]) { "Root not found for desk: $deskId" }
+        wct.reorder(root.token, /* onTop= */ true)
         wct.setLaunchRoot(
             /* container= */ root.taskInfo.token,
             /* windowingModes= */ intArrayOf(WINDOWING_MODE_FREEFORM, WINDOWING_MODE_UNDEFINED),
@@ -85,7 +92,7 @@
 
     override fun deactivateDesk(wct: WindowContainerTransaction, deskId: Int) {
         logV("deactivateDesk %d", deskId)
-        val root = checkNotNull(roots[deskId]) { "Root not found for desk: $deskId" }
+        val root = checkNotNull(deskRootsByDeskId[deskId]) { "Root not found for desk: $deskId" }
         wct.setLaunchRoot(
             /* container= */ root.taskInfo.token,
             /* windowingModes= */ null,
@@ -98,16 +105,58 @@
         deskId: Int,
         task: RunningTaskInfo,
     ) {
-        val root = roots[deskId] ?: error("Root not found for desk: $deskId")
+        val root = deskRootsByDeskId[deskId] ?: error("Root not found for desk: $deskId")
         wct.setWindowingMode(task.token, WINDOWING_MODE_UNDEFINED)
         wct.reparent(task.token, root.taskInfo.token, /* onTop= */ true)
     }
 
-    override fun isDeskChange(change: TransitionInfo.Change, deskId: Int): Boolean =
-        roots.contains(deskId) && change.taskInfo?.taskId == deskId
+    override fun minimizeTask(wct: WindowContainerTransaction, deskId: Int, task: RunningTaskInfo) {
+        val deskRoot =
+            checkNotNull(deskRootsByDeskId[deskId]) { "Root not found for desk: $deskId" }
+        val minimizationRoot =
+            checkNotNull(deskMinimizationRootsByDeskId[deskId]) {
+                "Minimization root not found for desk: $deskId"
+            }
+        val taskId = task.taskId
+        if (taskId in minimizationRoot.children) {
+            logV("Task #$taskId is already minimized in desk #$deskId")
+            return
+        }
+        if (taskId !in deskRoot.children) {
+            logE("Attempted to minimize task=${task.taskId} in desk=$deskId but it was not a child")
+            return
+        }
+        wct.reparent(task.token, minimizationRoot.token, /* onTop= */ true)
+    }
 
-    override fun getDeskAtEnd(change: TransitionInfo.Change): Int? =
-        change.taskInfo?.parentTaskId?.takeIf { it in roots }
+    override fun isDeskChange(change: TransitionInfo.Change, deskId: Int): Boolean =
+        (isDeskRootChange(change) && change.taskId == deskId) ||
+            (getDeskMinimizationRootInChange(change)?.deskId == deskId)
+
+    override fun isDeskChange(change: TransitionInfo.Change): Boolean =
+        isDeskRootChange(change) || getDeskMinimizationRootInChange(change) != null
+
+    private fun isDeskRootChange(change: TransitionInfo.Change): Boolean =
+        change.taskId in deskRootsByDeskId
+
+    private fun getDeskMinimizationRootInChange(
+        change: TransitionInfo.Change
+    ): DeskMinimizationRoot? =
+        deskMinimizationRootsByDeskId.values.find { it.rootId == change.taskId }
+
+    private val TransitionInfo.Change.taskId: Int
+        get() = taskInfo?.taskId ?: INVALID_TASK_ID
+
+    override fun getDeskAtEnd(change: TransitionInfo.Change): Int? {
+        val parentTaskId = change.taskInfo?.parentTaskId ?: return null
+        if (parentTaskId in deskRootsByDeskId) {
+            return parentTaskId
+        }
+        val deskMinimizationRoot =
+            deskMinimizationRootsByDeskId.values.find { root -> root.rootId == parentTaskId }
+                ?: return null
+        return deskMinimizationRoot.deskId
+    }
 
     override fun isDeskActiveAtEnd(change: TransitionInfo.Change, deskId: Int): Boolean =
         change.taskInfo?.taskId == deskId &&
@@ -115,51 +164,176 @@
             change.mode == TRANSIT_TO_FRONT
 
     override fun onTaskAppeared(taskInfo: RunningTaskInfo, leash: SurfaceControl) {
-        if (taskInfo.parentTaskId in roots) {
+        // Check whether this task is appearing inside a desk.
+        if (taskInfo.parentTaskId in deskRootsByDeskId) {
             val deskId = taskInfo.parentTaskId
             val taskId = taskInfo.taskId
             logV("Task #$taskId appeared in desk #$deskId")
             addChildToDesk(taskId = taskId, deskId = deskId)
             return
         }
-        val deskId = taskInfo.taskId
-        check(deskId !in roots) { "A root already exists for desk: $deskId" }
-        val request =
-            checkNotNull(deskCreateRequests.firstOrNull { it.displayId == taskInfo.displayId }) {
-                "Task ${taskInfo.taskId} appeared without pending create request"
-            }
-        logV("Desk #$deskId appeared")
-        roots[deskId] = DeskRoot(deskId, taskInfo, leash)
-        deskCreateRequests.remove(request)
-        request.onCreateCallback.onCreated(deskId)
+        // Check whether this task is appearing in a minimization root.
+        val minimizationRoot =
+            deskMinimizationRootsByDeskId.values.singleOrNull { it.rootId == taskInfo.parentTaskId }
+        if (minimizationRoot != null) {
+            val deskId = minimizationRoot.deskId
+            val taskId = taskInfo.taskId
+            logV("Task #$taskId was minimized in desk #$deskId ")
+            addChildToMinimizationRoot(taskId = taskId, deskId = deskId)
+            return
+        }
+        // The appearing task is a root (either a desk or a minimization root), it should not exist
+        // already.
+        check(taskInfo.taskId !in deskRootsByDeskId) {
+            "A root already exists for desk: ${taskInfo.taskId}"
+        }
+        check(deskMinimizationRootsByDeskId.values.none { it.rootId == taskInfo.taskId }) {
+            "A minimization root already exists with rootId: ${taskInfo.taskId}"
+        }
+
+        val appearingInDisplayId = taskInfo.displayId
+        // Check if there's any pending desk creation requests under this display.
+        val deskRequest =
+            createDeskRootRequests.firstOrNull { it.displayId == appearingInDisplayId }
+        if (deskRequest != null) {
+            // Appearing root matches desk request.
+            val deskId = taskInfo.taskId
+            logV("Desk #$deskId appeared")
+            deskRootsByDeskId[deskId] = DeskRoot(deskId, taskInfo, leash)
+            createDeskRootRequests.remove(deskRequest)
+            deskRequest.onCreateCallback.onCreated(deskId)
+            createDeskMinimizationRoot(displayId = appearingInDisplayId, deskId = deskId)
+            return
+        }
+        // Check if there's any pending minimization container creation requests under this display.
+        val deskMinimizationRootRequest =
+            createDeskMinimizationRootRequests.first { it.displayId == appearingInDisplayId }
+        val deskId = deskMinimizationRootRequest.deskId
+        logV("Minimization container for desk #$deskId appeared with id=${taskInfo.taskId}")
+        val deskMinimizationRoot = DeskMinimizationRoot(deskId, taskInfo, leash)
+        deskMinimizationRootsByDeskId[deskId] = deskMinimizationRoot
+        createDeskMinimizationRootRequests.remove(deskMinimizationRootRequest)
+        hideMinimizationRoot(deskMinimizationRoot)
     }
 
     override fun onTaskInfoChanged(taskInfo: RunningTaskInfo) {
-        if (roots.contains(taskInfo.taskId)) {
+        if (deskRootsByDeskId.contains(taskInfo.taskId)) {
             val deskId = taskInfo.taskId
-            roots[deskId] = roots[deskId].copy(taskInfo = taskInfo)
+            deskRootsByDeskId[deskId] = deskRootsByDeskId[deskId].copy(taskInfo = taskInfo)
+            logV("Desk #$deskId's task info changed")
+            return
         }
+        val minimizationRoot =
+            deskMinimizationRootsByDeskId.values.find { root -> root.rootId == taskInfo.taskId }
+        if (minimizationRoot != null) {
+            deskMinimizationRootsByDeskId.remove(minimizationRoot.deskId)
+            deskMinimizationRootsByDeskId[minimizationRoot.deskId] =
+                minimizationRoot.copy(taskInfo = taskInfo)
+            logV("Minimization root for desk#${minimizationRoot.deskId} task info changed")
+            return
+        }
+
+        val parentTaskId = taskInfo.parentTaskId
+        if (parentTaskId in deskRootsByDeskId) {
+            val deskId = taskInfo.parentTaskId
+            val taskId = taskInfo.taskId
+            logV("onTaskInfoChanged: Task #$taskId appeared in desk #$deskId")
+            addChildToDesk(taskId = taskId, deskId = deskId)
+            return
+        }
+        // Check whether this task is appearing in a minimization root.
+        val parentMinimizationRoot =
+            deskMinimizationRootsByDeskId.values.singleOrNull { it.rootId == parentTaskId }
+        if (parentMinimizationRoot != null) {
+            val deskId = parentMinimizationRoot.deskId
+            val taskId = taskInfo.taskId
+            logV("onTaskInfoChanged: Task #$taskId was minimized in desk #$deskId ")
+            addChildToMinimizationRoot(taskId = taskId, deskId = deskId)
+            return
+        }
+        logE("onTaskInfoChanged: unknown task: ${taskInfo.taskId}")
     }
 
     override fun onTaskVanished(taskInfo: RunningTaskInfo) {
-        if (roots.contains(taskInfo.taskId)) {
+        if (deskRootsByDeskId.contains(taskInfo.taskId)) {
             val deskId = taskInfo.taskId
-            val deskRoot = roots[deskId]
+            val deskRoot = deskRootsByDeskId[deskId]
             // Use the last saved taskInfo to obtain the displayId. Using the local one here will
             // return -1 since the task is not unassociated with a display.
             val displayId = deskRoot.taskInfo.displayId
             logV("Desk #$deskId vanished from display #$displayId")
-            roots.remove(deskId)
+            deskRootsByDeskId.remove(deskId)
             return
         }
+        val deskMinimizationRoot =
+            deskMinimizationRootsByDeskId.values.singleOrNull { it.rootId == taskInfo.taskId }
+        if (deskMinimizationRoot != null) {
+            logV("Minimization root for desk ${deskMinimizationRoot.deskId} vanished")
+            deskMinimizationRootsByDeskId.remove(deskMinimizationRoot.deskId)
+            return
+        }
+
+        // Check whether the vanishing task was a child of any desk.
         // At this point, [parentTaskId] may be unset even if this is a task vanishing from a desk,
         // so search through each root to remove this if it's a child.
-        roots.forEach { deskId, deskRoot ->
+        deskRootsByDeskId.forEach { deskId, deskRoot ->
             if (deskRoot.children.remove(taskInfo.taskId)) {
                 logV("Task #${taskInfo.taskId} vanished from desk #$deskId")
                 return
             }
         }
+        // Check whether the vanishing task was a child of the minimized root and remove it.
+        deskMinimizationRootsByDeskId.values.forEach { root ->
+            val taskId = taskInfo.taskId
+            if (root.children.remove(taskId)) {
+                logV("Task #$taskId vanished from minimization root of desk #${root.deskId}")
+                return
+            }
+        }
+    }
+
+    private fun createDeskMinimizationRoot(displayId: Int, deskId: Int) {
+        createDeskMinimizationRootRequests +=
+            CreateDeskMinimizationRootRequest(displayId = displayId, deskId = deskId)
+        shellTaskOrganizer.createRootTask(
+            displayId,
+            WINDOWING_MODE_FREEFORM,
+            /* listener = */ this,
+            /* removeWithTaskOrganizer = */ true,
+        )
+    }
+
+    @SuppressLint("MissingPermission")
+    private fun hideMinimizationRoot(root: DeskMinimizationRoot) {
+        shellTaskOrganizer.applyTransaction(
+            WindowContainerTransaction().apply { setHidden(root.token, /* hidden= */ true) }
+        )
+    }
+
+    private fun addChildToDesk(taskId: Int, deskId: Int) {
+        deskRootsByDeskId.forEach { _, deskRoot ->
+            if (deskRoot.deskId == deskId) {
+                deskRoot.children.add(taskId)
+            } else {
+                deskRoot.children.remove(taskId)
+            }
+        }
+        // A task cannot be in both a desk root and a minimization root at the same time, so make
+        // sure to remove them if needed.
+        deskMinimizationRootsByDeskId.values.forEach { root -> root.children.remove(taskId) }
+    }
+
+    private fun addChildToMinimizationRoot(taskId: Int, deskId: Int) {
+        deskMinimizationRootsByDeskId.forEach { _, minimizationRoot ->
+            if (minimizationRoot.deskId == deskId) {
+                minimizationRoot.children += taskId
+            } else {
+                minimizationRoot.children -= taskId
+            }
+        }
+        // A task cannot be in both a desk root and a minimization root at the same time, so make
+        // sure to remove them if needed.
+        deskRootsByDeskId.forEach { _, deskRoot -> deskRoot.children -= taskId }
     }
 
     @VisibleForTesting
@@ -168,34 +342,55 @@
         val taskInfo: RunningTaskInfo,
         val leash: SurfaceControl,
         val children: MutableSet<Int> = mutableSetOf(),
+    ) {
+        val token: WindowContainerToken = taskInfo.token
+    }
+
+    @VisibleForTesting
+    data class DeskMinimizationRoot(
+        val deskId: Int,
+        val taskInfo: RunningTaskInfo,
+        val leash: SurfaceControl,
+        val children: MutableSet<Int> = mutableSetOf(),
+    ) {
+        val rootId: Int
+            get() = taskInfo.taskId
+
+        val token: WindowContainerToken = taskInfo.token
+    }
+
+    private data class CreateDeskRequest(
+        val displayId: Int,
+        val onCreateCallback: OnCreateCallback,
     )
 
+    private data class CreateDeskMinimizationRootRequest(val displayId: Int, val deskId: Int)
+
+    private fun logV(msg: String, vararg arguments: Any?) {
+        ProtoLog.v(WM_SHELL_DESKTOP_MODE, "%s: $msg", TAG, *arguments)
+    }
+
+    private fun logE(msg: String, vararg arguments: Any?) {
+        ProtoLog.e(WM_SHELL_DESKTOP_MODE, "%s: $msg", TAG, *arguments)
+    }
+
     override fun dump(pw: PrintWriter, prefix: String) {
         val innerPrefix = "$prefix  "
         pw.println("$prefix$TAG")
         pw.println("${innerPrefix}Desk Roots:")
-        roots.forEach { deskId, root ->
+        deskRootsByDeskId.forEach { deskId, root ->
+            val minimizationRoot = deskMinimizationRootsByDeskId[deskId]
             pw.println("$innerPrefix  #$deskId visible=${root.taskInfo.isVisible}")
+            pw.println("$innerPrefix    displayId=${root.taskInfo.displayId}")
             pw.println("$innerPrefix    children=${root.children}")
-        }
-    }
-
-    private fun addChildToDesk(taskId: Int, deskId: Int) {
-        roots.forEach { _, deskRoot ->
-            if (deskRoot.deskId == deskId) {
-                deskRoot.children.add(taskId)
-            } else {
-                deskRoot.children.remove(taskId)
+            pw.println("$innerPrefix    minimization root:")
+            pw.println("$innerPrefix      rootId=${minimizationRoot?.rootId}")
+            if (minimizationRoot != null) {
+                pw.println("$innerPrefix      children=${minimizationRoot.children}")
             }
         }
     }
 
-    private data class CreateRequest(val displayId: Int, val onCreateCallback: OnCreateCallback)
-
-    private fun logV(msg: String, vararg arguments: Any?) {
-        ProtoLog.v(WM_SHELL_DESKTOP_MODE, "%s: $msg", TAG, *arguments)
-    }
-
     companion object {
         private const val TAG = "RootTaskDesksOrganizer"
     }
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/docs/README.md b/libs/WindowManager/Shell/src/com/android/wm/shell/docs/README.md
index 3fad28a..a98ae55 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/docs/README.md
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/docs/README.md
@@ -9,6 +9,7 @@
 4) [Threading model in the Shell](threading.md)
 5) [Making changes in the Shell](changes.md)
 6) [Extending the Shell for Products/OEMs](extending.md)
+6) [Shell transitions](transitions.md)
 7) [Debugging in the Shell](debugging.md)
 8) [Testing in the Shell](testing.md)
 
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/docs/transitions.md b/libs/WindowManager/Shell/src/com/android/wm/shell/docs/transitions.md
new file mode 100644
index 0000000..dc23bb0
--- /dev/null
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/docs/transitions.md
@@ -0,0 +1,118 @@
+# Shell transitions
+[Back to home](README.md)
+
+---
+
+## General
+
+General guides for using Shell Transitions can be found here:
+- [Shell transitions animation guide](http://go/shell-transit-anim)
+- [Hitchhiker's guide to transitions](http://go/transitions-book)
+
+## Transient-launch transitions
+<span style="color:orange">Use with care!</span>
+
+Transient-launch transitions are a way to handle non-atomic (ie. gestural) transitions by allowing
+WM Core to put participating activities into a transiently visible or hidden state for the duration
+of the animation and adding the ability to cancel the transition.  
+
+For example, if you are launching an activity normally, WM Core will be updated
+at the start of the animation which includes pausing the previous activity and resuming the next
+activity (and subsequently the transition will reconcile that state via an animation).
+
+If you are transiently launching an activity though, WM Core will ensure that both the leaving 
+activity and the incoming activity will be RESUMED for the duration of the transition duration. In
+addition, WM Core will track the position of the transient-launch activity in the window hierarchy
+prior to the launch, and allow Shell to restore it to that position if the transitions needs to be
+canceled.
+
+Starting a transient-launch transition can be done via the activity options (since the activity may
+not have been started yet):
+```kotlin
+val opts = ActivityOptions.makeBasic().setTransientLaunch()
+val wct = WindowContainerTransaction()
+wct.sendPendingIntent(pendingIntent, new Intent(), opts.toBundle())
+transitions.startTransition(TRANSIT_OPEN, wct, ...)
+```
+
+And restoring the transient order via a WCT:
+```kotlin
+val wct = WindowContainerTransaction()
+wct.restoreTransientOrder(transientLaunchContainerToken)
+transitions.startTransition(TRANSIT_RESTORE, wct, ...)
+```
+
+### <span style="color:orange">Considerations</span>
+
+Usage of transient-launch transitions should be done with consideration, there are a few gotchas
+that might result in subtle and hard-to-reproduce issues. 
+
+#### Understanding the flow
+When starting a transient-launch transition, there are several possible outcomes:
+1) The transition finishes as normal: The user is committing the transition to the state requested
+   at the start of the transition.  In such cases, you can simply finish the transition and the
+   states of the transiently shown/hidden activities will be updated to match the original state
+   that a non-transient transition would have (ie. closing activities will be stopped).
+
+2) The transition is interrupted: A change in the system results in the window hierarchy changing
+   in a way which may or may not affect the transient-launch activity.  eg. We transiently-launch
+   home from app A, but then app B launches.  In this case, WM attempts to create a new transition
+   reflecting the window hierarchy changes (ie. if B occludes Home in the above example, then the
+   transition will have Home TO_BACK, and B TO_FRONT).
+
+   At this point, the transition handler can choose to merge the incoming transition or not (to
+   queue it after this current transition).  Take note of the next section for concerns re. bookend
+   transitions.
+
+3) The transition is canceled: The user is canceling the transition to the previous state.  In such
+   cases, you need to store the `WindowContainerToken` for the task associated with the 
+   transient-launch activity, and restore the transient order via the `WindowContainerTransaction`
+   API above.  In some cases, if anything has been reordered since (ie. due to other merged 
+   transitions), then you may also want to use `WindowContainerTransaction#reorder()` to place all
+   the relevant containers to their original order (provided via the change-order in the initial
+   launch transition).
+
+#### Finishing the transient-launch transition
+
+When restoring the transient order in the 3rd flow above, it is recommended to do it in a new 
+transition and <span style="color:orange">**not**</span> via the WindowContainerTransaction in 
+`TransitionFinishCallback#onTransitionFinished()` provided when starting the transition.
+
+Changes to the window hierarchy via the finish transaction are not applied in sync with other 
+transitions that are collecting and aplying, and are also not observable in Shell in any way.  
+Starting a new transition instead ensures both.  (The finish transaction can still be used if there
+are non-transition affecting properties (ie. container properties) that need to be updated as a part
+of finishing the transient-launch transition).
+
+So the general idea is when restoring is:
+
+1) Start transient-launch transition START_T
+2) ...
+3) All done, start bookend transition END_T
+4) Handler receives END_T, merges it and then finishes START_T
+
+In practice it's not quite that simple, due to the ordering of transitions and extra care must be
+taken when using a new transition to prevent deadlocking when merging transitions.
+
+When a new transition arrives while a transient-launch transition is playing, the handler can
+choose to handle/merge the transition into the ongoing one, or skip merging to queue it up to be
+played after.  In the above flow, we can see how this might result in a deadlock:
+
+Queueing END during merge:
+1) Start transient-launch transition START_T
+2) ...
+3) Incoming transition OTHER_T, choose to cancel START_T -> start bookend transition END_T, but don't merge OTHER_T
+3) Waiting for END_T... <span style="color:red">Deadlock!</span>
+
+Interrupt while pending END:
+1) Start transient-launch transition START_T
+2) ...
+3) All done, start bookend transition END_T
+3) Incoming transition OTHER_T occurs before END_T, but don't merge OTHER_T
+3) Waiting for END_T... <span style="color:red">Deadlock!</span>
+
+This means that when using transient-launch transitions with a bookend transition
+<span style="color:orange">requires</span> you to handle any incoming transitions if the bookend is 
+ever queued (or already posted) after it.  You can do so by preempting the bookend transition
+(finishing the transient-launch transition), or handling the merge of the new transition (so it 
+doesn't queue). 
\ No newline at end of file
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/freeform/FreeformTaskTransitionHandler.java b/libs/WindowManager/Shell/src/com/android/wm/shell/freeform/FreeformTaskTransitionHandler.java
index b60fb5e..16e411e 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/freeform/FreeformTaskTransitionHandler.java
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/freeform/FreeformTaskTransitionHandler.java
@@ -25,10 +25,12 @@
 import android.animation.ValueAnimator;
 import android.app.ActivityManager;
 import android.app.WindowConfiguration;
+import android.content.Context;
 import android.graphics.Rect;
+import android.os.Handler;
 import android.os.IBinder;
 import android.util.ArrayMap;
-import android.util.DisplayMetrics;
+import android.util.Log;
 import android.view.SurfaceControl;
 import android.view.WindowManager;
 import android.window.TransitionInfo;
@@ -38,6 +40,7 @@
 import androidx.annotation.NonNull;
 import androidx.annotation.Nullable;
 
+import com.android.internal.jank.InteractionJankMonitor;
 import com.android.wm.shell.common.DisplayController;
 import com.android.wm.shell.common.ShellExecutor;
 import com.android.wm.shell.shared.animation.MinimizeAnimator;
@@ -52,11 +55,13 @@
  */
 public class FreeformTaskTransitionHandler
         implements Transitions.TransitionHandler, FreeformTaskTransitionStarter {
+    private static final String TAG = "FreeformTaskTransitionHandler";
     private static final int CLOSE_ANIM_DURATION = 400;
     private final Transitions mTransitions;
     private final DisplayController mDisplayController;
     private final ShellExecutor mMainExecutor;
     private final ShellExecutor mAnimExecutor;
+    private final Handler mAnimHandler;
 
     private final List<IBinder> mPendingTransitionTokens = new ArrayList<>();
 
@@ -66,11 +71,13 @@
             Transitions transitions,
             DisplayController displayController,
             ShellExecutor mainExecutor,
-            ShellExecutor animExecutor) {
+            ShellExecutor animExecutor,
+            Handler animHandler) {
         mTransitions = transitions;
         mDisplayController = displayController;
         mMainExecutor = mainExecutor;
         mAnimExecutor = animExecutor;
+        mAnimHandler = animHandler;
     }
 
     @Override
@@ -123,13 +130,11 @@
             @NonNull Transitions.TransitionFinishCallback finishCallback) {
         boolean transitionHandled = false;
         final ArrayList<Animator> animations = new ArrayList<>();
-        final Runnable onAnimFinish = () -> {
+        final Runnable onAnimFinish = () -> mMainExecutor.execute(() -> {
             if (!animations.isEmpty()) return;
-            mMainExecutor.execute(() -> {
-                mAnimations.remove(transition);
-                finishCallback.onTransitionFinished(null /* wct */);
-            });
-        };
+            mAnimations.remove(transition);
+            finishCallback.onTransitionFinished(null /* wct */);
+        });
         for (TransitionInfo.Change change : info.getChanges()) {
             if ((change.getFlags() & TransitionInfo.FLAG_IS_WALLPAPER) != 0) {
                 continue;
@@ -234,18 +239,25 @@
         SurfaceControl.Transaction t = new SurfaceControl.Transaction();
         SurfaceControl sc = change.getLeash();
         finishT.hide(sc);
-        final DisplayMetrics displayMetrics =
-                mDisplayController
-                        .getDisplayContext(taskInfo.displayId).getResources().getDisplayMetrics();
+        final Context displayContext =
+                mDisplayController.getDisplayContext(taskInfo.displayId);
+        if (displayContext == null) {
+            Log.w(TAG, "No displayContext for displayId=" + taskInfo.displayId);
+            return false;
+        }
         final Animator animator = MinimizeAnimator.create(
-                displayMetrics,
+                displayContext,
                 change,
                 t,
                 (anim) -> {
-                    animations.remove(anim);
-                    onAnimFinish.run();
+                    mMainExecutor.execute(() -> {
+                        animations.remove(anim);
+                        onAnimFinish.run();
+                    });
                     return null;
-                });
+                },
+                InteractionJankMonitor.getInstance(),
+                mAnimHandler);
         animations.add(animator);
         return true;
     }
@@ -277,8 +289,10 @@
                 new AnimatorListenerAdapter() {
                     @Override
                     public void onAnimationEnd(Animator animation) {
-                        animations.remove(animator);
-                        onAnimFinish.run();
+                        mMainExecutor.execute(() -> {
+                            animations.remove(animator);
+                            onAnimFinish.run();
+                        });
                     }
                 });
         animations.add(animator);
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/pip2/PipSurfaceTransactionHelper.java b/libs/WindowManager/Shell/src/com/android/wm/shell/pip2/PipSurfaceTransactionHelper.java
index c4d065f..e0cae81 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/pip2/PipSurfaceTransactionHelper.java
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/pip2/PipSurfaceTransactionHelper.java
@@ -134,6 +134,18 @@
     }
 
     /**
+     * Operates the round corner radius on a given transaction and leash, scaled by bounds
+     * @return same {@link PipSurfaceTransactionHelper} instance for method chaining
+     */
+    public PipSurfaceTransactionHelper round(SurfaceControl.Transaction tx, SurfaceControl leash,
+            Rect fromBounds, Rect toBounds) {
+        final float scale = (float) (Math.hypot(fromBounds.width(), fromBounds.height())
+                / Math.hypot(toBounds.width(), toBounds.height()));
+        tx.setCornerRadius(leash, mCornerRadius * scale);
+        return this;
+    }
+
+    /**
      * Operates the shadow radius on a given transaction and leash
      * @return same {@link PipSurfaceTransactionHelper} instance for method chaining
      */
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/pip2/animation/PipResizeAnimator.java b/libs/WindowManager/Shell/src/com/android/wm/shell/pip2/animation/PipResizeAnimator.java
index 06e8349..eb5fe88 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/pip2/animation/PipResizeAnimator.java
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/pip2/animation/PipResizeAnimator.java
@@ -173,7 +173,7 @@
         transformTensor.postRotate(degrees, targetBounds.centerX(), targetBounds.centerY());
 
         tx.setMatrix(leash, transformTensor, mMatrixTmp)
-                .setCornerRadius(leash, cornerRadius)
+                .setCornerRadius(leash, cornerRadius / scaleX)
                 .setShadowRadius(leash, shadowRadius);
     }
 
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/pip2/phone/PipController.java b/libs/WindowManager/Shell/src/com/android/wm/shell/pip2/phone/PipController.java
index 99c9302..1ce24f7 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/pip2/phone/PipController.java
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/pip2/phone/PipController.java
@@ -404,6 +404,10 @@
 
         mPipBoundsState.setBoundsStateForEntry(componentName, activityInfo, pictureInPictureParams,
                 mPipBoundsAlgorithm);
+
+        // Update the size spec in case aspect ratio is invariant, but display has changed
+        // since the last PiP session, or this is the first PiP session altogether.
+        mPipBoundsState.updateMinMaxSize(mPipBoundsState.getAspectRatio());
         return mPipBoundsAlgorithm.getEntryDestinationBounds();
     }
 
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/pip2/phone/PipScheduler.java b/libs/WindowManager/Shell/src/com/android/wm/shell/pip2/phone/PipScheduler.java
index df7a25a..7805ec3 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/pip2/phone/PipScheduler.java
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/pip2/phone/PipScheduler.java
@@ -55,6 +55,7 @@
     private PipTransitionController mPipTransitionController;
     private PipSurfaceTransactionHelper.SurfaceControlTransactionFactory
             mSurfaceControlTransactionFactory;
+    private final PipSurfaceTransactionHelper mPipSurfaceTransactionHelper;
 
     @Nullable private Runnable mUpdateMovementBoundsRunnable;
 
@@ -75,6 +76,7 @@
 
         mSurfaceControlTransactionFactory =
                 new PipSurfaceTransactionHelper.VsyncSurfaceControlTransactionFactory();
+        mPipSurfaceTransactionHelper = new PipSurfaceTransactionHelper(mContext);
         mPipAlphaAnimatorSupplier = PipAlphaAnimator::new;
     }
 
@@ -214,6 +216,8 @@
         transformTensor.postTranslate(toBounds.left, toBounds.top);
         transformTensor.postRotate(degrees, toBounds.centerX(), toBounds.centerY());
 
+        mPipSurfaceTransactionHelper.round(tx, leash, mPipBoundsState.getBounds(), toBounds);
+
         tx.setMatrix(leash, transformTensor, mMatrixTmp);
         tx.apply();
     }
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/pip2/phone/PipTransition.java b/libs/WindowManager/Shell/src/com/android/wm/shell/pip2/phone/PipTransition.java
index 035c93d..0cfab11 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/pip2/phone/PipTransition.java
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/pip2/phone/PipTransition.java
@@ -50,6 +50,7 @@
 import android.graphics.Rect;
 import android.os.Bundle;
 import android.os.IBinder;
+import android.os.SystemProperties;
 import android.view.SurfaceControl;
 import android.view.WindowManager;
 import android.window.TransitionInfo;
@@ -105,7 +106,10 @@
      * The fixed start delay in ms when fading out the content overlay from bounds animation.
      * The fadeout animation is guaranteed to start after the client has drawn under the new config.
      */
-    private static final int CONTENT_OVERLAY_FADE_OUT_DELAY_MS = 500;
+    private static final int EXTRA_CONTENT_OVERLAY_FADE_OUT_DELAY_MS =
+            SystemProperties.getInt(
+                    "persist.wm.debug.extra_content_overlay_fade_out_delay_ms", 400);
+    private static final int CONTENT_OVERLAY_FADE_OUT_DURATION_MS = 500;
 
     //
     // Dependencies
@@ -551,7 +555,8 @@
             @NonNull Runnable onAnimationEnd) {
         PipAlphaAnimator animator = new PipAlphaAnimator(mContext, overlayLeash,
                 null /* startTx */, null /* finishTx */, PipAlphaAnimator.FADE_OUT);
-        animator.setDuration(CONTENT_OVERLAY_FADE_OUT_DELAY_MS);
+        animator.setDuration(CONTENT_OVERLAY_FADE_OUT_DURATION_MS);
+        animator.setStartDelay(EXTRA_CONTENT_OVERLAY_FADE_OUT_DELAY_MS);
         animator.setAnimationEndCallback(onAnimationEnd);
         animator.start();
     }
@@ -738,6 +743,13 @@
             }
         }
 
+        if (!mPipTransitionState.isInSwipePipToHomeTransition()) {
+            // Update the size spec in case aspect ratio is invariant, but display has changed
+            // since the last PiP session, or this is the first PiP session altogether.
+            // Skip the update if in swipe PiP to home, as this has already been done.
+            mPipBoundsState.updateMinMaxSize(mPipBoundsState.getAspectRatio());
+        }
+
         // calculate the entry bounds and notify core to move task to pinned with final bounds
         final Rect entryBounds = mPipBoundsAlgorithm.getEntryDestinationBounds();
         mPipBoundsState.setBounds(entryBounds);
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/recents/RecentsTransitionHandler.java b/libs/WindowManager/Shell/src/com/android/wm/shell/recents/RecentsTransitionHandler.java
index 847a038..3e03e00 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/recents/RecentsTransitionHandler.java
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/recents/RecentsTransitionHandler.java
@@ -47,6 +47,7 @@
 import android.app.ActivityTaskManager;
 import android.app.IApplicationThread;
 import android.app.PendingIntent;
+import android.app.WindowConfiguration;
 import android.content.Context;
 import android.content.Intent;
 import android.graphics.Color;
@@ -75,11 +76,11 @@
 import com.android.internal.os.IResultReceiver;
 import com.android.internal.protolog.ProtoLog;
 import com.android.wm.shell.Flags;
-import com.android.wm.shell.R;
 import com.android.wm.shell.ShellTaskOrganizer;
 import com.android.wm.shell.common.ShellExecutor;
 import com.android.wm.shell.common.pip.PipUtils;
 import com.android.wm.shell.protolog.ShellProtoLogGroup;
+import com.android.wm.shell.shared.R;
 import com.android.wm.shell.shared.TransitionUtil;
 import com.android.wm.shell.sysui.ShellInit;
 import com.android.wm.shell.transition.HomeTransitionObserver;
@@ -320,7 +321,7 @@
                     "RecentsTransitionHandler.mergeAnimation: no controller found");
             return;
         }
-        controller.merge(info, startT, finishT, mergeTarget, finishCallback);
+        controller.merge(info, startT, finishT, finishCallback);
     }
 
     @Override
@@ -408,7 +409,8 @@
         // next called.
         private Pair<int[], TaskSnapshot[]> mPendingPauseSnapshotsForCancel;
 
-        // Used to track a pending finish transition
+        // Used to track a pending finish transition, this is only non-null if
+        // enableRecentsBookendTransition() is enabled
         private IBinder mPendingFinishTransition;
         private IResultReceiver mPendingRunnerFinishCb;
 
@@ -917,7 +919,7 @@
          */
         @SuppressLint("NewApi")
         void merge(TransitionInfo info, SurfaceControl.Transaction startT,
-                SurfaceControl.Transaction finishT, IBinder mergeTarget,
+                SurfaceControl.Transaction finishT,
                 Transitions.TransitionFinishCallback finishCallback) {
             if (mFinishCB == null) {
                 ProtoLog.v(ShellProtoLogGroup.WM_SHELL_RECENTS_TRANSITION,
@@ -927,16 +929,25 @@
                 return;
             }
 
-            if (Flags.enableRecentsBookendTransition()
-                    && info.getType() == TRANSIT_END_RECENTS_TRANSITION
-                    && mergeTarget == mTransition) {
-                // This is a pending finish, so merge the end transition to trigger completing the
-                // cleanup of the recents transition
-                ProtoLog.v(ShellProtoLogGroup.WM_SHELL_RECENTS_TRANSITION,
-                        "[%d] RecentsController.merge: TRANSIT_END_RECENTS_TRANSITION",
-                        mInstanceId);
-                finishCallback.onTransitionFinished(null /* wct */);
-                return;
+            if (Flags.enableRecentsBookendTransition()) {
+                if (info.getType() == TRANSIT_END_RECENTS_TRANSITION) {
+                    // This is a pending finish, so merge the end transition to trigger completing
+                    // the cleanup of the recents transition
+                    ProtoLog.v(ShellProtoLogGroup.WM_SHELL_RECENTS_TRANSITION,
+                            "[%d] RecentsController.merge: TRANSIT_END_RECENTS_TRANSITION",
+                            mInstanceId);
+                    consumeMerge(info, startT, finishT, finishCallback);
+                    return;
+                } else if (mPendingFinishTransition != null) {
+                    // This transition is interrupting a pending finish that was already sent, so
+                    // pre-empt the pending finish transition since the state has already changed
+                    // in the core
+                    ProtoLog.v(ShellProtoLogGroup.WM_SHELL_RECENTS_TRANSITION,
+                            "[%d] RecentsController.merge: Awaiting TRANSIT_END_RECENTS_TRANSITION",
+                            mInstanceId);
+                    onFinishInner(null /* wct */);
+                    return;
+                }
             }
 
             if (info.getType() == TRANSIT_SLEEP) {
@@ -1210,16 +1221,12 @@
                 }
                 return;
             }
+
             // At this point, we are accepting the merge.
-            startT.apply();
-            // Since we're accepting the merge, update the finish transaction so that changes via
-            // that transaction will be applied on top of those of the merged transitions
-            mFinishTransaction = finishT;
+            consumeMerge(info, startT, finishT, finishCallback);
+
+            // Notify Launcher of the new opening tasks if necessary
             boolean passTransitionInfo = ENABLE_DESKTOP_RECENTS_TRANSITIONS_CORNERS_BUGFIX.isTrue();
-            if (!passTransitionInfo) {
-                // not using the incoming anim-only surfaces
-                info.releaseAnimSurfaces();
-            }
             if (appearedTargets != null) {
                 try {
                     ProtoLog.v(ShellProtoLogGroup.WM_SHELL_RECENTS_TRANSITION,
@@ -1229,6 +1236,27 @@
                     Slog.e(TAG, "Error sending appeared tasks to recents animation", e);
                 }
             }
+        }
+
+        /**
+         * Consumes the merge of the other given transition.
+         */
+        private void consumeMerge(TransitionInfo info, SurfaceControl.Transaction startT,
+                SurfaceControl.Transaction finishT,
+                Transitions.TransitionFinishCallback finishCallback) {
+            ProtoLog.v(ShellProtoLogGroup.WM_SHELL_RECENTS_TRANSITION,
+                    "[%d] RecentsController.merge: consuming merge",
+                    mInstanceId);
+
+            startT.apply();
+            // Since we're accepting the merge, update the finish transaction so that changes via
+            // that transaction will be applied on top of those of the merged transitions
+            mFinishTransaction = finishT;
+            boolean passTransitionInfo = ENABLE_DESKTOP_RECENTS_TRANSITIONS_CORNERS_BUGFIX.isTrue();
+            if (!passTransitionInfo) {
+                // not using the incoming anim-only surfaces
+                info.releaseAnimSurfaces();
+            }
             finishCallback.onTransitionFinished(null /* wct */);
         }
 
@@ -1346,9 +1374,16 @@
             final SurfaceControl.Transaction t = mFinishTransaction;
             final WindowContainerTransaction wct = new WindowContainerTransaction();
 
+            // The following code must set this if it is changing anything in core that might affect
+            // transitions as a part of finishing the recents transition
+            boolean requiresBookendTransition = false;
+
             if (mKeyguardLocked && mRecentsTask != null) {
                 if (toHome) wct.reorder(mRecentsTask, true /* toTop */);
                 else wct.restoreTransientOrder(mRecentsTask);
+                // We are manipulating the window hierarchy, which should only be done with the
+                // bookend transition
+                requiresBookendTransition = true;
             }
             if (returningToApp) {
                 ProtoLog.v(ShellProtoLogGroup.WM_SHELL_RECENTS_TRANSITION, "  returning to app");
@@ -1365,6 +1400,9 @@
                 if (!mKeyguardLocked && mRecentsTask != null) {
                     wct.restoreTransientOrder(mRecentsTask);
                 }
+                // We are manipulating the window hierarchy, which should only be done with the
+                // bookend transition
+                requiresBookendTransition = true;
             } else if (toHome && mOpeningSeparateHome && mPausingTasks != null) {
                 ProtoLog.v(ShellProtoLogGroup.WM_SHELL_RECENTS_TRANSITION, "  3p launching home");
                 // Special situation where 3p launcher was changed during recents (this happens
@@ -1384,6 +1422,9 @@
                 if (!mKeyguardLocked && mRecentsTask != null) {
                     wct.restoreTransientOrder(mRecentsTask);
                 }
+                // We are manipulating the window hierarchy, which should only be done with the
+                // bookend transition
+                requiresBookendTransition = true;
             } else {
                 if (mPausingSeparateHome) {
                     if (mOpeningTasks.isEmpty()) {
@@ -1484,13 +1525,21 @@
 
             if (Flags.enableRecentsBookendTransition()) {
                 if (!wct.isEmpty()) {
-                    ProtoLog.v(ShellProtoLogGroup.WM_SHELL_RECENTS_TRANSITION,
-                            "[%d] RecentsController.finishInner: "
-                                    + "Queuing TRANSIT_END_RECENTS_TRANSITION", mInstanceId);
-                    mPendingRunnerFinishCb = runnerFinishCb;
-                    mPendingFinishTransition = mTransitions.startTransition(
-                            TRANSIT_END_RECENTS_TRANSITION, wct,
-                            new PendingFinishTransitionHandler());
+                    if (requiresBookendTransition) {
+                        ProtoLog.v(ShellProtoLogGroup.WM_SHELL_RECENTS_TRANSITION,
+                                "[%d] RecentsController.finishInner: "
+                                        + "Queuing TRANSIT_END_RECENTS_TRANSITION", mInstanceId);
+                        mPendingRunnerFinishCb = runnerFinishCb;
+                        mPendingFinishTransition = mTransitions.startTransition(
+                                TRANSIT_END_RECENTS_TRANSITION, wct,
+                                new PendingFinishTransitionHandler());
+                    } else {
+                        ProtoLog.v(ShellProtoLogGroup.WM_SHELL_RECENTS_TRANSITION,
+                                "[%d] RecentsController.finishInner: Non-transition affecting wct",
+                                mInstanceId);
+                        mPendingRunnerFinishCb = runnerFinishCb;
+                        onFinishInner(wct);
+                    }
                 } else {
                     // If there's no work to do, just go ahead and clean up
                     mPendingRunnerFinishCb = runnerFinishCb;
@@ -1631,6 +1680,9 @@
                     @NonNull SurfaceControl.Transaction startTransaction,
                     @NonNull SurfaceControl.Transaction finishTransaction,
                     @NonNull Transitions.TransitionFinishCallback finishCallback) {
+                ProtoLog.v(ShellProtoLogGroup.WM_SHELL_RECENTS_TRANSITION,
+                        "[%d] PendingFinishTransitionHandler.startAnimation: "
+                                + "Started pending finish transition", mInstanceId);
                 return false;
             }
 
@@ -1644,10 +1696,15 @@
             @Override
             public void onTransitionConsumed(@NonNull IBinder transition, boolean aborted,
                     @Nullable SurfaceControl.Transaction finishTransaction) {
+                if (mPendingFinishTransition == null) {
+                    // The cleanup was pre-empted by an earlier transition, nothing there is nothing
+                    // to do here
+                    return;
+                }
                 // Once we have merged (or not if the WCT didn't result in any changes), then we can
                 // run the pending finish logic
                 ProtoLog.v(ShellProtoLogGroup.WM_SHELL_RECENTS_TRANSITION,
-                        "[%d] RecentsController.onTransitionConsumed: "
+                        "[%d] PendingFinishTransitionHandler.onTransitionConsumed: "
                                 + "Consumed pending finish transition", mInstanceId);
                 onFinishInner(null /* wct */);
             }
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/transition/DefaultMixedHandler.java b/libs/WindowManager/Shell/src/com/android/wm/shell/transition/DefaultMixedHandler.java
index 743bd05..347dcff 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/transition/DefaultMixedHandler.java
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/transition/DefaultMixedHandler.java
@@ -40,7 +40,6 @@
 import android.window.WindowContainerTransaction;
 
 import com.android.internal.protolog.ProtoLog;
-import com.android.window.flags.Flags;
 import com.android.wm.shell.ShellTaskOrganizer;
 import com.android.wm.shell.activityembedding.ActivityEmbeddingController;
 import com.android.wm.shell.common.ComponentUtils;
@@ -439,9 +438,6 @@
         for (int i = 0; i < info.getRootCount(); ++i) {
             out.addRoot(info.getRoot(i));
         }
-        if (!Flags.moveAnimationOptionsToChange()) {
-            out.setAnimationOptions(info.getAnimationOptions());
-        }
         return out;
     }
 
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/transition/DefaultTransitionHandler.java b/libs/WindowManager/Shell/src/com/android/wm/shell/transition/DefaultTransitionHandler.java
index 01428e6..e9c6ade 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/transition/DefaultTransitionHandler.java
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/transition/DefaultTransitionHandler.java
@@ -539,7 +539,7 @@
                     cornerRadius = 0;
                 }
 
-                backgroundColorForTransition = getTransitionBackgroundColorIfSet(info, change, a,
+                backgroundColorForTransition = getTransitionBackgroundColorIfSet(change, a,
                         backgroundColorForTransition);
 
                 if (!isTask && a.getExtensionEdges() != 0x0) {
@@ -606,12 +606,7 @@
                         mTransactionPool, mMainExecutor, animRelOffset, cornerRadius,
                         clipRect);
 
-                final TransitionInfo.AnimationOptions options;
-                if (Flags.moveAnimationOptionsToChange()) {
-                    options = change.getAnimationOptions();
-                } else {
-                    options = info.getAnimationOptions();
-                }
+                final TransitionInfo.AnimationOptions options = change.getAnimationOptions();
                 if (options != null) {
                     attachThumbnail(animations, onAnimFinish, change, options, cornerRadius);
                 }
@@ -834,12 +829,7 @@
         final boolean isOpeningType = TransitionUtil.isOpeningType(type);
         final boolean enter = TransitionUtil.isOpeningType(changeMode);
         final boolean isTask = change.getTaskInfo() != null;
-        final TransitionInfo.AnimationOptions options;
-        if (Flags.moveAnimationOptionsToChange()) {
-            options = change.getAnimationOptions();
-        } else {
-            options = info.getAnimationOptions();
-        }
+        final TransitionInfo.AnimationOptions options = change.getAnimationOptions();
         final int overrideType = options != null ? options.getType() : ANIM_NONE;
         final int userId = options != null ? options.getUserId() : UserHandle.USER_CURRENT;
         final Rect endBounds = TransitionUtil.isClosingType(changeMode)
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/transition/HomeTransitionObserver.java b/libs/WindowManager/Shell/src/com/android/wm/shell/transition/HomeTransitionObserver.java
index 1917996..938885c 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/transition/HomeTransitionObserver.java
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/transition/HomeTransitionObserver.java
@@ -21,6 +21,7 @@
 import static android.window.TransitionInfo.FLAG_BACK_GESTURE_ANIMATED;
 
 import static com.android.wm.shell.desktopmode.DesktopModeTransitionTypes.TRANSIT_DESKTOP_MODE_START_DRAG_TO_DESKTOP;
+import static com.android.wm.shell.transition.Transitions.TRANSIT_CONVERT_TO_BUBBLE;
 import static com.android.wm.shell.transition.Transitions.TransitionObserver;
 
 import android.annotation.NonNull;
@@ -35,6 +36,7 @@
 import com.android.wm.shell.common.SingleInstanceRemoteListener;
 import com.android.wm.shell.shared.IHomeTransitionListener;
 import com.android.wm.shell.shared.TransitionUtil;
+import com.android.wm.shell.shared.bubbles.BubbleAnythingFlagHelper;
 
 /**
  * The {@link TransitionObserver} that observes for transitions involving the home
@@ -48,6 +50,8 @@
 
     private @NonNull final Context mContext;
     private @NonNull final ShellExecutor mMainExecutor;
+    private Boolean mPendingHomeVisibilityUpdate;
+
     public HomeTransitionObserver(@NonNull Context context,
             @NonNull ShellExecutor mainExecutor) {
         mContext = context;
@@ -59,28 +63,78 @@
             @NonNull TransitionInfo info,
             @NonNull SurfaceControl.Transaction startTransaction,
             @NonNull SurfaceControl.Transaction finishTransaction) {
+        if (BubbleAnythingFlagHelper.enableBubbleToFullscreen()) {
+            handleTransitionReadyWithBubbleAnything(info);
+        } else {
+            handleTransitionReady(info);
+        }
+    }
+
+    private void handleTransitionReady(@NonNull TransitionInfo info) {
         for (TransitionInfo.Change change : info.getChanges()) {
             final ActivityManager.RunningTaskInfo taskInfo = change.getTaskInfo();
-            if (info.getType() == TRANSIT_DESKTOP_MODE_START_DRAG_TO_DESKTOP
-                    || taskInfo == null
+            if (taskInfo == null
+                    || info.getType() == TRANSIT_DESKTOP_MODE_START_DRAG_TO_DESKTOP
+                    || taskInfo.displayId != DEFAULT_DISPLAY
+                    || taskInfo.taskId == -1
+                    || !taskInfo.isRunning) {
+                continue;
+            }
+            Boolean homeVisibilityUpdate = getHomeVisibilityUpdate(info, change, taskInfo);
+            if (homeVisibilityUpdate != null) {
+                notifyHomeVisibilityChanged(homeVisibilityUpdate);
+            }
+        }
+    }
+
+    private void handleTransitionReadyWithBubbleAnything(@NonNull TransitionInfo info) {
+        Boolean homeVisibilityUpdate = null;
+        for (TransitionInfo.Change change : info.getChanges()) {
+            final ActivityManager.RunningTaskInfo taskInfo = change.getTaskInfo();
+            if (taskInfo == null
                     || taskInfo.displayId != DEFAULT_DISPLAY
                     || taskInfo.taskId == -1
                     || !taskInfo.isRunning) {
                 continue;
             }
 
-            final int mode = change.getMode();
-            final boolean isBackGesture = change.hasFlags(FLAG_BACK_GESTURE_ANIMATED);
-            if (taskInfo.getActivityType() == ACTIVITY_TYPE_HOME) {
-                final boolean gestureToHomeTransition = isBackGesture
-                        && TransitionUtil.isClosingType(info.getType());
-                if (gestureToHomeTransition || TransitionUtil.isClosingMode(mode)
-                        || (!isBackGesture && TransitionUtil.isOpeningMode(mode))) {
-                    notifyHomeVisibilityChanged(gestureToHomeTransition
-                            || TransitionUtil.isOpeningType(mode));
-                }
+            Boolean update = getHomeVisibilityUpdate(info, change, taskInfo);
+            if (update != null) {
+                homeVisibilityUpdate = update;
             }
         }
+
+        if (info.getType() == TRANSIT_DESKTOP_MODE_START_DRAG_TO_DESKTOP) {
+            // Do not apply at the start of desktop drag as that updates launcher UI visibility.
+            // Store the value and apply with a next transition if needed.
+            mPendingHomeVisibilityUpdate = homeVisibilityUpdate;
+            return;
+        }
+
+        if (info.getType() == TRANSIT_CONVERT_TO_BUBBLE && homeVisibilityUpdate == null) {
+            // We are converting to bubble and we did not get a change to home visibility in this
+            // transition. Apply the value from start of drag.
+            homeVisibilityUpdate = mPendingHomeVisibilityUpdate;
+        }
+        if (homeVisibilityUpdate != null) {
+            mPendingHomeVisibilityUpdate = null;
+            notifyHomeVisibilityChanged(homeVisibilityUpdate);
+        }
+    }
+
+    private Boolean getHomeVisibilityUpdate(TransitionInfo info,
+            TransitionInfo.Change change, ActivityManager.RunningTaskInfo taskInfo) {
+        final int mode = change.getMode();
+        final boolean isBackGesture = change.hasFlags(FLAG_BACK_GESTURE_ANIMATED);
+        if (taskInfo.getActivityType() == ACTIVITY_TYPE_HOME) {
+            final boolean gestureToHomeTransition = isBackGesture
+                    && TransitionUtil.isClosingType(info.getType());
+            if (gestureToHomeTransition || TransitionUtil.isClosingMode(mode)
+                    || (!isBackGesture && TransitionUtil.isOpeningMode(mode))) {
+                return gestureToHomeTransition || TransitionUtil.isOpeningType(mode);
+            }
+        }
+        return null;
     }
 
     @Override
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/transition/TransitionAnimationHelper.java b/libs/WindowManager/Shell/src/com/android/wm/shell/transition/TransitionAnimationHelper.java
index 4feb475..7984bce 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/transition/TransitionAnimationHelper.java
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/transition/TransitionAnimationHelper.java
@@ -58,7 +58,6 @@
 import com.android.internal.R;
 import com.android.internal.policy.TransitionAnimation;
 import com.android.internal.protolog.ProtoLog;
-import com.android.window.flags.Flags;
 import com.android.wm.shell.protolog.ShellProtoLogGroup;
 import com.android.wm.shell.shared.TransitionUtil;
 
@@ -78,12 +77,7 @@
         final boolean isFreeform = isTask && change.getTaskInfo().isFreeform();
         final boolean isCoveredByOpaqueFullscreenChange =
                 isCoveredByOpaqueFullscreenChange(info, change);
-        final TransitionInfo.AnimationOptions options;
-        if (Flags.moveAnimationOptionsToChange()) {
-            options = change.getAnimationOptions();
-        } else {
-            options = info.getAnimationOptions();
-        }
+        final TransitionInfo.AnimationOptions options = change.getAnimationOptions();
         final int overrideType = options != null ? options.getType() : ANIM_NONE;
         int animAttr = 0;
         boolean translucent = false;
@@ -279,16 +273,10 @@
      *                      the given transition animation.
      */
     @ColorInt
-    public static int getTransitionBackgroundColorIfSet(@NonNull TransitionInfo info,
-            @NonNull TransitionInfo.Change change, @NonNull Animation a,
-            @ColorInt int defaultColor) {
+    public static int getTransitionBackgroundColorIfSet(@NonNull TransitionInfo.Change change,
+            @NonNull Animation a, @ColorInt int defaultColor) {
         if (!a.getShowBackdrop()) {
             return defaultColor;
-        }
-        if (!Flags.moveAnimationOptionsToChange() && info.getAnimationOptions() != null
-                && info.getAnimationOptions().getBackgroundColor() != 0) {
-            // If available use the background color provided through AnimationOptions
-            return info.getAnimationOptions().getBackgroundColor();
         } else if (a.getBackdropColor() != 0) {
             // Otherwise fallback on the background color provided through the animation
             // definition.
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/windowdecor/DesktopModeWindowDecorViewModel.java b/libs/WindowManager/Shell/src/com/android/wm/shell/windowdecor/DesktopModeWindowDecorViewModel.java
index 5a6ea21..cf139a0 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/windowdecor/DesktopModeWindowDecorViewModel.java
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/windowdecor/DesktopModeWindowDecorViewModel.java
@@ -103,6 +103,7 @@
 import com.android.wm.shell.common.DisplayController;
 import com.android.wm.shell.common.DisplayInsetsController;
 import com.android.wm.shell.common.DisplayLayout;
+import com.android.wm.shell.common.MultiDisplayDragMoveIndicatorController;
 import com.android.wm.shell.common.MultiInstanceHelper;
 import com.android.wm.shell.common.ShellExecutor;
 import com.android.wm.shell.common.SyncTransactionQueue;
@@ -258,6 +259,7 @@
     private final RecentsTransitionHandler mRecentsTransitionHandler;
     private final DesktopModeCompatPolicy mDesktopModeCompatPolicy;
     private final DesktopTilingDecorViewModel mDesktopTilingDecorViewModel;
+    private final MultiDisplayDragMoveIndicatorController mMultiDisplayDragMoveIndicatorController;
 
     public DesktopModeWindowDecorViewModel(
             Context context,
@@ -296,7 +298,8 @@
             WindowDecorTaskResourceLoader taskResourceLoader,
             RecentsTransitionHandler recentsTransitionHandler,
             DesktopModeCompatPolicy desktopModeCompatPolicy,
-            DesktopTilingDecorViewModel desktopTilingDecorViewModel) {
+            DesktopTilingDecorViewModel desktopTilingDecorViewModel,
+            MultiDisplayDragMoveIndicatorController multiDisplayDragMoveIndicatorController) {
         this(
                 context,
                 shellExecutor,
@@ -340,7 +343,8 @@
                 taskResourceLoader,
                 recentsTransitionHandler,
                 desktopModeCompatPolicy,
-                desktopTilingDecorViewModel);
+                desktopTilingDecorViewModel,
+                multiDisplayDragMoveIndicatorController);
     }
 
     @VisibleForTesting
@@ -387,7 +391,8 @@
             WindowDecorTaskResourceLoader taskResourceLoader,
             RecentsTransitionHandler recentsTransitionHandler,
             DesktopModeCompatPolicy desktopModeCompatPolicy,
-            DesktopTilingDecorViewModel desktopTilingDecorViewModel) {
+            DesktopTilingDecorViewModel desktopTilingDecorViewModel,
+            MultiDisplayDragMoveIndicatorController multiDisplayDragMoveIndicatorController) {
         mContext = context;
         mMainExecutor = shellExecutor;
         mMainHandler = mainHandler;
@@ -460,6 +465,7 @@
         mDesktopModeCompatPolicy = desktopModeCompatPolicy;
         mDesktopTilingDecorViewModel = desktopTilingDecorViewModel;
         mDesktopTasksController.setSnapEventHandler(this);
+        mMultiDisplayDragMoveIndicatorController = multiDisplayDragMoveIndicatorController;
         shellInit.addInitCallback(this::onInit, this);
     }
 
@@ -1759,7 +1765,8 @@
                 mTransitions,
                 mInteractionJankMonitor,
                 mTransactionFactory,
-                mMainHandler);
+                mMainHandler,
+                mMultiDisplayDragMoveIndicatorController);
         windowDecoration.setTaskDragResizer(taskPositioner);
 
         final DesktopModeTouchEventListener touchEventListener =
@@ -2056,7 +2063,8 @@
                 Transitions transitions,
                 InteractionJankMonitor interactionJankMonitor,
                 Supplier<SurfaceControl.Transaction> transactionFactory,
-                Handler handler) {
+                Handler handler,
+                MultiDisplayDragMoveIndicatorController multiDisplayDragMoveIndicatorController) {
             final TaskPositioner taskPositioner = DesktopModeStatus.isVeiledResizeEnabled()
                     // TODO(b/383632995): Update when the flag is launched.
                     ? (Flags.enableConnectedDisplaysWindowDrag()
@@ -2067,7 +2075,8 @@
                             dragEventListener,
                             transitions,
                             interactionJankMonitor,
-                            handler)
+                            handler,
+                            multiDisplayDragMoveIndicatorController)
                         : new VeiledResizeTaskPositioner(
                             taskOrganizer,
                             windowDecoration,
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/windowdecor/DesktopModeWindowDecoration.java b/libs/WindowManager/Shell/src/com/android/wm/shell/windowdecor/DesktopModeWindowDecoration.java
index dca376f..6165dbf 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/windowdecor/DesktopModeWindowDecoration.java
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/windowdecor/DesktopModeWindowDecoration.java
@@ -1069,7 +1069,8 @@
     private static int getCornerRadius(@NonNull Context context, int layoutResId) {
         if (layoutResId == R.layout.desktop_mode_app_header) {
             return loadDimensionPixelSize(context.getResources(),
-                    R.dimen.desktop_windowing_freeform_rounded_corner_radius);
+                    com.android.wm.shell.shared.R.dimen
+                            .desktop_windowing_freeform_rounded_corner_radius);
         }
         return INVALID_CORNER_RADIUS;
     }
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/windowdecor/MultiDisplayVeiledResizeTaskPositioner.kt b/libs/WindowManager/Shell/src/com/android/wm/shell/windowdecor/MultiDisplayVeiledResizeTaskPositioner.kt
index bb20292..c6cb62d 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/windowdecor/MultiDisplayVeiledResizeTaskPositioner.kt
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/windowdecor/MultiDisplayVeiledResizeTaskPositioner.kt
@@ -17,6 +17,7 @@
 
 import android.graphics.PointF
 import android.graphics.Rect
+import android.hardware.display.DisplayTopology
 import android.os.Handler
 import android.os.IBinder
 import android.os.Looper
@@ -32,10 +33,10 @@
 import com.android.wm.shell.ShellTaskOrganizer
 import com.android.wm.shell.common.DisplayController
 import com.android.wm.shell.common.MultiDisplayDragMoveBoundsCalculator
+import com.android.wm.shell.common.MultiDisplayDragMoveIndicatorController
 import com.android.wm.shell.shared.annotations.ShellMainThread
 import com.android.wm.shell.transition.Transitions
 import java.util.concurrent.TimeUnit
-import java.util.function.Supplier
 
 /**
  * A task positioner that also takes into account resizing a
@@ -49,11 +50,12 @@
     private val desktopWindowDecoration: DesktopModeWindowDecoration,
     private val displayController: DisplayController,
     dragEventListener: DragPositioningCallbackUtility.DragEventListener,
-    private val transactionSupplier: Supplier<SurfaceControl.Transaction>,
+    private val transactionSupplier: () -> SurfaceControl.Transaction,
     private val transitions: Transitions,
     private val interactionJankMonitor: InteractionJankMonitor,
     @ShellMainThread private val handler: Handler,
-) : TaskPositioner, Transitions.TransitionHandler {
+    private val multiDisplayDragMoveIndicatorController: MultiDisplayDragMoveIndicatorController,
+) : TaskPositioner, Transitions.TransitionHandler, DisplayController.OnDisplaysChangedListener {
     private val dragEventListeners =
         mutableListOf<DragPositioningCallbackUtility.DragEventListener>()
     private val stableBounds = Rect()
@@ -71,6 +73,7 @@
     private var isResizingOrAnimatingResize = false
     @Surface.Rotation private var rotation = 0
     private var startDisplayId = 0
+    private val displayIds = mutableSetOf<Int>()
 
     constructor(
         taskOrganizer: ShellTaskOrganizer,
@@ -80,19 +83,22 @@
         transitions: Transitions,
         interactionJankMonitor: InteractionJankMonitor,
         @ShellMainThread handler: Handler,
+        multiDisplayDragMoveIndicatorController: MultiDisplayDragMoveIndicatorController,
     ) : this(
         taskOrganizer,
         windowDecoration,
         displayController,
         dragEventListener,
-        Supplier<SurfaceControl.Transaction> { SurfaceControl.Transaction() },
+        { SurfaceControl.Transaction() },
         transitions,
         interactionJankMonitor,
         handler,
+        multiDisplayDragMoveIndicatorController,
     )
 
     init {
         dragEventListeners.add(dragEventListener)
+        displayController.addDisplayWindowListener(this)
     }
 
     override fun onDragPositioningStart(ctrlType: Int, displayId: Int, x: Float, y: Float): Rect {
@@ -164,7 +170,7 @@
                 createLongTimeoutJankConfigBuilder(Cuj.CUJ_DESKTOP_MODE_DRAG_WINDOW)
             )
 
-            val t = transactionSupplier.get()
+            val t = transactionSupplier()
             val startDisplayLayout = displayController.getDisplayLayout(startDisplayId)
             val currentDisplayLayout = displayController.getDisplayLayout(displayId)
 
@@ -196,7 +202,13 @@
                     )
                 )
 
-                // TODO(b/383069173): Render drag indicator(s)
+                multiDisplayDragMoveIndicatorController.onDragMove(
+                    boundsDp,
+                    startDisplayId,
+                    desktopWindowDecoration.mTaskInfo,
+                    displayIds,
+                    transactionSupplier,
+                )
 
                 t.setPosition(
                     desktopWindowDecoration.leash,
@@ -267,7 +279,10 @@
                     )
                 )
 
-                // TODO(b/383069173): Clear drag indicator(s)
+                multiDisplayDragMoveIndicatorController.onDragEnd(
+                    desktopWindowDecoration.mTaskInfo.taskId,
+                    transactionSupplier,
+                )
             }
 
             interactionJankMonitor.end(Cuj.CUJ_DESKTOP_MODE_DRAG_WINDOW)
@@ -348,6 +363,14 @@
         dragEventListeners.remove(dragEventListener)
     }
 
+    override fun onTopologyChanged(topology: DisplayTopology) {
+        // TODO: b/383069173 - Cancel window drag when topology changes happen during drag.
+
+        displayIds.clear()
+        val displayBounds = topology.getAbsoluteBounds()
+        displayIds.addAll(List(displayBounds.size()) { displayBounds.keyAt(it) })
+    }
+
     companion object {
         // Timeout used for resize and drag CUJs, this is longer than the default timeout to avoid
         // timing out in the middle of a resize or drag action.
diff --git a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/activityembedding/ActivityEmbeddingAnimationRunnerTests.java b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/activityembedding/ActivityEmbeddingAnimationRunnerTests.java
index 94dc774..d4d8d93 100644
--- a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/activityembedding/ActivityEmbeddingAnimationRunnerTests.java
+++ b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/activityembedding/ActivityEmbeddingAnimationRunnerTests.java
@@ -39,7 +39,6 @@
 import android.annotation.NonNull;
 import android.graphics.Point;
 import android.graphics.Rect;
-import android.platform.test.annotations.DisableFlags;
 import android.platform.test.annotations.EnableFlags;
 import android.view.animation.AlphaAnimation;
 import android.view.animation.Animation;
@@ -77,7 +76,6 @@
         doNothing().when(mController).onAnimationFinished(any());
     }
 
-    @EnableFlags(Flags.FLAG_MOVE_ANIMATION_OPTIONS_TO_CHANGE)
     @Test
     public void testStartAnimation() {
         final TransitionInfo info = new TransitionInfoBuilder(TRANSIT_OPEN, 0)
@@ -103,7 +101,6 @@
         verify(mController).onAnimationFinished(mTransition);
     }
 
-    @EnableFlags(Flags.FLAG_MOVE_ANIMATION_OPTIONS_TO_CHANGE)
     @Test
     public void testChangesBehindStartingWindow() {
         final TransitionInfo info = new TransitionInfoBuilder(TRANSIT_OPEN, 0)
@@ -118,7 +115,6 @@
         assertEquals(0, animator.getDuration());
     }
 
-    @EnableFlags(Flags.FLAG_MOVE_ANIMATION_OPTIONS_TO_CHANGE)
     @Test
     public void testTransitionTypeDragResize() {
         final TransitionInfo info = new TransitionInfoBuilder(TRANSIT_TASK_FRAGMENT_DRAG_RESIZE, 0)
@@ -133,25 +129,6 @@
         assertEquals(0, animator.getDuration());
     }
 
-    @DisableFlags(Flags.FLAG_MOVE_ANIMATION_OPTIONS_TO_CHANGE)
-    @Test
-    public void testInvalidCustomAnimation_disableAnimationOptionsPerChange() {
-        final TransitionInfo info = new TransitionInfoBuilder(TRANSIT_OPEN, 0)
-                .addChange(createChange(FLAG_IN_TASK_WITH_EMBEDDED_ACTIVITY, TRANSIT_OPEN))
-                .build();
-        info.setAnimationOptions(TransitionInfo.AnimationOptions
-                .makeCustomAnimOptions("packageName", 0 /* enterResId */, 0 /* exitResId */,
-                        0 /* backgroundColor */, false /* overrideTaskTransition */));
-        final Animator animator = mAnimRunner.createAnimator(
-                info, mStartTransaction, mFinishTransaction,
-                () -> mFinishCallback.onTransitionFinished(null /* wct */),
-                new ArrayList<>());
-
-        // An invalid custom animation is equivalent to jump-cut.
-        assertEquals(0, animator.getDuration());
-    }
-
-    @EnableFlags(Flags.FLAG_MOVE_ANIMATION_OPTIONS_TO_CHANGE)
     @Test
     public void testInvalidCustomAnimation_enableAnimationOptionsPerChange() {
         final TransitionInfo info = new TransitionInfoBuilder(TRANSIT_OPEN, 0)
@@ -169,36 +146,6 @@
         assertEquals(0, animator.getDuration());
     }
 
-    @DisableFlags(Flags.FLAG_ACTIVITY_EMBEDDING_OVERLAY_PRESENTATION_FLAG)
-    @Test
-    public void testCalculateParentBounds_flagDisabled() {
-        final Rect parentBounds = new Rect(0, 0, 2000, 2000);
-        final Rect primaryBounds = new Rect();
-        final Rect secondaryBounds = new Rect();
-        parentBounds.splitVertically(primaryBounds, secondaryBounds);
-
-        final TransitionInfo.Change change = createChange(0 /* flags */);
-        change.setStartAbsBounds(secondaryBounds);
-
-        final TransitionInfo.Change boundsAnimationChange = createChange(0 /* flags */);
-        boundsAnimationChange.setStartAbsBounds(primaryBounds);
-        boundsAnimationChange.setEndAbsBounds(primaryBounds);
-        final Rect actualParentBounds = new Rect();
-
-        calculateParentBounds(change, boundsAnimationChange, actualParentBounds);
-
-        assertEquals(parentBounds, actualParentBounds);
-
-        actualParentBounds.setEmpty();
-
-        boundsAnimationChange.setStartAbsBounds(secondaryBounds);
-        boundsAnimationChange.setEndAbsBounds(primaryBounds);
-
-        calculateParentBounds(boundsAnimationChange, boundsAnimationChange, actualParentBounds);
-
-        assertEquals(parentBounds, actualParentBounds);
-    }
-
     // TODO(b/243518738): Rewrite with TestParameter
     @EnableFlags(Flags.FLAG_ACTIVITY_EMBEDDING_OVERLAY_PRESENTATION_FLAG)
     @Test
diff --git a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/activityembedding/ActivityEmbeddingControllerTests.java b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/activityembedding/ActivityEmbeddingControllerTests.java
index 9f29ef7..53a13d0 100644
--- a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/activityembedding/ActivityEmbeddingControllerTests.java
+++ b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/activityembedding/ActivityEmbeddingControllerTests.java
@@ -32,8 +32,6 @@
 import android.animation.Animator;
 import android.animation.ValueAnimator;
 import android.graphics.Rect;
-import android.platform.test.annotations.DisableFlags;
-import android.platform.test.annotations.EnableFlags;
 import android.view.SurfaceControl;
 import android.window.TransitionInfo;
 
@@ -41,7 +39,6 @@
 import androidx.test.ext.junit.runners.AndroidJUnit4;
 import androidx.test.filters.SmallTest;
 
-import com.android.window.flags.Flags;
 import com.android.wm.shell.transition.TransitionInfoBuilder;
 
 import org.junit.Before;
@@ -69,13 +66,11 @@
                 any());
     }
 
-    @EnableFlags(Flags.FLAG_MOVE_ANIMATION_OPTIONS_TO_CHANGE)
     @Test
     public void testInstantiate() {
         verify(mShellInit).addInitCallback(any(), any());
     }
 
-    @EnableFlags(Flags.FLAG_MOVE_ANIMATION_OPTIONS_TO_CHANGE)
     @Test
     public void testOnInit() {
         mController.onInit();
@@ -83,7 +78,6 @@
         verify(mTransitions).addHandler(mController);
     }
 
-    @EnableFlags(Flags.FLAG_MOVE_ANIMATION_OPTIONS_TO_CHANGE)
     @Test
     public void testSetAnimScaleSetting() {
         mController.setAnimScaleSetting(1.0f);
@@ -92,7 +86,6 @@
         verify(mAnimSpec).setAnimScaleSetting(1.0f);
     }
 
-    @EnableFlags(Flags.FLAG_MOVE_ANIMATION_OPTIONS_TO_CHANGE)
     @Test
     public void testStartAnimation_containsNonActivityEmbeddingChange() {
         final TransitionInfo.Change nonEmbeddedOpen = createChange(0 /* flags */);
@@ -129,7 +122,6 @@
         assertFalse(info2.getChanges().contains(nonEmbeddedClose));
     }
 
-    @EnableFlags(Flags.FLAG_MOVE_ANIMATION_OPTIONS_TO_CHANGE)
     @Test
     public void testStartAnimation_containsOnlyFillTaskActivityEmbeddingChange() {
         final TransitionInfo info = new TransitionInfoBuilder(TRANSIT_OPEN, 0)
@@ -146,7 +138,6 @@
         verifyNoMoreInteractions(mFinishCallback);
     }
 
-    @EnableFlags(Flags.FLAG_MOVE_ANIMATION_OPTIONS_TO_CHANGE)
     @Test
     public void testStartAnimation_containsActivityEmbeddingSplitChange() {
         // Change that occupies only part of the Task.
@@ -164,7 +155,6 @@
         verifyNoMoreInteractions(mFinishTransaction);
     }
 
-    @EnableFlags(Flags.FLAG_MOVE_ANIMATION_OPTIONS_TO_CHANGE)
     @Test
     public void testStartAnimation_containsChangeEnterActivityEmbeddingSplit() {
         // Change that is entering ActivityEmbedding split.
@@ -181,7 +171,6 @@
         verifyNoMoreInteractions(mFinishTransaction);
     }
 
-    @EnableFlags(Flags.FLAG_MOVE_ANIMATION_OPTIONS_TO_CHANGE)
     @Test
     public void testStartAnimation_containsChangeExitActivityEmbeddingSplit() {
         // Change that is exiting ActivityEmbedding split.
@@ -198,27 +187,6 @@
         verifyNoMoreInteractions(mFinishTransaction);
     }
 
-    @DisableFlags(Flags.FLAG_MOVE_ANIMATION_OPTIONS_TO_CHANGE)
-    @Test
-    public void testShouldAnimate_containsAnimationOptions_disableAnimOptionsPerChange() {
-        final TransitionInfo info = new TransitionInfoBuilder(TRANSIT_CLOSE, 0)
-                .addChange(createEmbeddedChange(EMBEDDED_RIGHT_BOUNDS, TASK_BOUNDS, TASK_BOUNDS))
-                .build();
-
-        info.setAnimationOptions(TransitionInfo.AnimationOptions
-                .makeCustomAnimOptions("packageName", 0 /* enterResId */, 0 /* exitResId */,
-                        0 /* backgroundColor */, false /* overrideTaskTransition */));
-        assertTrue(mController.shouldAnimate(info));
-
-        info.setAnimationOptions(TransitionInfo.AnimationOptions
-                .makeSceneTransitionAnimOptions());
-        assertFalse(mController.shouldAnimate(info));
-
-        info.setAnimationOptions(TransitionInfo.AnimationOptions.makeCrossProfileAnimOptions());
-        assertFalse(mController.shouldAnimate(info));
-    }
-
-    @EnableFlags(Flags.FLAG_MOVE_ANIMATION_OPTIONS_TO_CHANGE)
     @Test
     public void testShouldAnimate_containsAnimationOptions_enableAnimOptionsPerChange() {
         final TransitionInfo info = new TransitionInfoBuilder(TRANSIT_CLOSE, 0)
@@ -239,7 +207,6 @@
         assertFalse(mController.shouldAnimate(info));
     }
 
-    @EnableFlags(Flags.FLAG_MOVE_ANIMATION_OPTIONS_TO_CHANGE)
     @UiThreadTest
     @Test
     public void testMergeAnimation() {
@@ -278,7 +245,6 @@
         verify(mFinishCallback).onTransitionFinished(any());
     }
 
-    @EnableFlags(Flags.FLAG_MOVE_ANIMATION_OPTIONS_TO_CHANGE)
     @Test
     public void testOnAnimationFinished() {
         // Should not call finish when there is no transition.
diff --git a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/common/MultiDisplayDragMoveIndicatorControllerTest.kt b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/common/MultiDisplayDragMoveIndicatorControllerTest.kt
new file mode 100644
index 0000000..abd2388
--- /dev/null
+++ b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/common/MultiDisplayDragMoveIndicatorControllerTest.kt
@@ -0,0 +1,168 @@
+/*
+ * Copyright (C) 2025 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package com.android.wm.shell.common
+
+import android.app.ActivityManager.RunningTaskInfo
+import android.content.res.Configuration
+import android.graphics.Rect
+import android.graphics.RectF
+import android.testing.TestableResources
+import android.view.Display
+import android.view.SurfaceControl
+import androidx.test.ext.junit.runners.AndroidJUnit4
+import androidx.test.filters.SmallTest
+import com.android.wm.shell.RootTaskDisplayAreaOrganizer
+import com.android.wm.shell.ShellTestCase
+import com.android.wm.shell.TestShellExecutor
+import java.util.function.Supplier
+import org.junit.Before
+import org.junit.Test
+import org.junit.runner.RunWith
+import org.mockito.Mockito.never
+import org.mockito.Mockito.verify
+import org.mockito.kotlin.any
+import org.mockito.kotlin.eq
+import org.mockito.kotlin.mock
+import org.mockito.kotlin.times
+import org.mockito.kotlin.whenever
+
+/**
+ * Tests for [MultiDisplayDragMoveIndicatorController].
+ *
+ * Build/Install/Run: atest WMShellUnitTests:MultiDisplayDragMoveIndicatorControllerTest
+ */
+@SmallTest
+@RunWith(AndroidJUnit4::class)
+class MultiDisplayDragMoveIndicatorControllerTest : ShellTestCase() {
+    private val displayController = mock<DisplayController>()
+    private val rootTaskDisplayAreaOrganizer = mock<RootTaskDisplayAreaOrganizer>()
+    private val indicatorSurfaceFactory = mock<MultiDisplayDragMoveIndicatorSurface.Factory>()
+    private val indicatorSurface0 = mock<MultiDisplayDragMoveIndicatorSurface>()
+    private val indicatorSurface1 = mock<MultiDisplayDragMoveIndicatorSurface>()
+    private val transaction = mock<SurfaceControl.Transaction>()
+    private val transactionSupplier = mock<Supplier<SurfaceControl.Transaction>>()
+    private val taskInfo = mock<RunningTaskInfo>()
+    private val display0 = mock<Display>()
+    private val display1 = mock<Display>()
+
+    private lateinit var resources: TestableResources
+    private val executor = TestShellExecutor()
+
+    private lateinit var controller: MultiDisplayDragMoveIndicatorController
+
+    @Before
+    fun setUp() {
+        resources = mContext.getOrCreateTestableResources()
+        val resourceConfiguration = Configuration()
+        resourceConfiguration.uiMode = 0
+        resources.overrideConfiguration(resourceConfiguration)
+
+        controller =
+            MultiDisplayDragMoveIndicatorController(
+                displayController,
+                rootTaskDisplayAreaOrganizer,
+                indicatorSurfaceFactory,
+                executor,
+            )
+
+        val spyDisplayLayout0 =
+            MultiDisplayTestUtil.createSpyDisplayLayout(
+                MultiDisplayTestUtil.DISPLAY_GLOBAL_BOUNDS_0,
+                MultiDisplayTestUtil.DISPLAY_DPI_0,
+                resources.resources,
+            )
+        val spyDisplayLayout1 =
+            MultiDisplayTestUtil.createSpyDisplayLayout(
+                MultiDisplayTestUtil.DISPLAY_GLOBAL_BOUNDS_1,
+                MultiDisplayTestUtil.DISPLAY_DPI_1,
+                resources.resources,
+            )
+
+        taskInfo.taskId = TASK_ID
+        whenever(displayController.getDisplayLayout(0)).thenReturn(spyDisplayLayout0)
+        whenever(displayController.getDisplayLayout(1)).thenReturn(spyDisplayLayout1)
+        whenever(displayController.getDisplay(0)).thenReturn(display0)
+        whenever(displayController.getDisplay(1)).thenReturn(display1)
+        whenever(indicatorSurfaceFactory.create(taskInfo, display0)).thenReturn(indicatorSurface0)
+        whenever(indicatorSurfaceFactory.create(taskInfo, display1)).thenReturn(indicatorSurface1)
+        whenever(transactionSupplier.get()).thenReturn(transaction)
+    }
+
+    @Test
+    fun onDrag_boundsNotIntersectWithDisplay_noIndicator() {
+        controller.onDragMove(
+            RectF(2000f, 2000f, 2100f, 2200f), // not intersect with any display
+            startDisplayId = 0,
+            taskInfo,
+            displayIds = setOf(0, 1),
+        ) { transaction }
+        executor.flushAll()
+
+        verify(indicatorSurfaceFactory, never()).create(any(), any())
+    }
+
+    @Test
+    fun onDrag_boundsIntersectWithStartDisplay_noIndicator() {
+        controller.onDragMove(
+            RectF(100f, 100f, 200f, 200f), // intersect with display 0
+            startDisplayId = 0,
+            taskInfo,
+            displayIds = setOf(0, 1),
+        ) { transaction }
+        executor.flushAll()
+
+        verify(indicatorSurfaceFactory, never()).create(any(), any())
+    }
+
+    @Test
+    fun onDrag_boundsIntersectWithNonStartDisplay_showAndDisposeIndicator() {
+        controller.onDragMove(
+            RectF(100f, -100f, 200f, 200f), // intersect with display 0 and 1
+            startDisplayId = 0,
+            taskInfo,
+            displayIds = setOf(0, 1),
+        ) { transaction }
+        executor.flushAll()
+
+        verify(indicatorSurfaceFactory, times(1)).create(taskInfo, display1)
+        verify(indicatorSurface1, times(1))
+            .show(transaction, taskInfo, rootTaskDisplayAreaOrganizer, 1, Rect(0, 1800, 200, 2400))
+
+        controller.onDragMove(
+            RectF(2000f, 2000f, 2100f, 2200f), // not intersect with display 1
+            startDisplayId = 0,
+            taskInfo,
+            displayIds = setOf(0, 1)
+        ) { transaction }
+        while (executor.callbacks.isNotEmpty()) {
+            executor.flushAll()
+        }
+
+        verify(indicatorSurface1, times(1))
+            .relayout(any(), eq(transaction), shouldBeVisible = eq(false))
+
+        controller.onDragEnd(TASK_ID, { transaction })
+        while (executor.callbacks.isNotEmpty()) {
+            executor.flushAll()
+        }
+
+        verify(indicatorSurface1, times(1)).disposeSurface(transaction)
+    }
+
+    companion object {
+        private const val TASK_ID = 10
+    }
+}
diff --git a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/desktopmode/DesktopDisplayEventHandlerTest.kt b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/desktopmode/DesktopDisplayEventHandlerTest.kt
index 0d5741f..8ad54f5 100644
--- a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/desktopmode/DesktopDisplayEventHandlerTest.kt
+++ b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/desktopmode/DesktopDisplayEventHandlerTest.kt
@@ -16,51 +16,28 @@
 
 package com.android.wm.shell.desktopmode
 
-import android.app.ActivityManager.RunningTaskInfo
-import android.app.WindowConfiguration.WINDOWING_MODE_FREEFORM
-import android.app.WindowConfiguration.WINDOWING_MODE_FULLSCREEN
-import android.app.WindowConfiguration.WINDOWING_MODE_UNDEFINED
-import android.content.ContentResolver
-import android.os.Binder
 import android.platform.test.annotations.EnableFlags
-import android.provider.Settings
-import android.provider.Settings.Global.DEVELOPMENT_FORCE_DESKTOP_MODE_ON_EXTERNAL_DISPLAYS
 import android.testing.AndroidTestingRunner
 import android.view.Display.DEFAULT_DISPLAY
-import android.view.IWindowManager
-import android.view.WindowManager.TRANSIT_CHANGE
-import android.window.DisplayAreaInfo
-import android.window.WindowContainerTransaction
 import androidx.test.filters.SmallTest
 import com.android.dx.mockito.inline.extended.ExtendedMockito.mockitoSession
 import com.android.dx.mockito.inline.extended.ExtendedMockito.never
 import com.android.dx.mockito.inline.extended.StaticMockitoSession
 import com.android.window.flags.Flags
-import com.android.wm.shell.MockToken
-import com.android.wm.shell.RootTaskDisplayAreaOrganizer
-import com.android.wm.shell.ShellTaskOrganizer
 import com.android.wm.shell.ShellTestCase
-import com.android.wm.shell.TestRunningTaskInfoBuilder
 import com.android.wm.shell.common.DisplayController
 import com.android.wm.shell.common.DisplayController.OnDisplaysChangedListener
 import com.android.wm.shell.common.ShellExecutor
 import com.android.wm.shell.shared.desktopmode.DesktopModeStatus
 import com.android.wm.shell.sysui.ShellInit
-import com.android.wm.shell.transition.Transitions
-import com.google.common.truth.Truth.assertThat
 import org.junit.After
 import org.junit.Before
 import org.junit.Test
 import org.junit.runner.RunWith
-import org.mockito.ArgumentMatchers.isNull
 import org.mockito.Mock
-import org.mockito.Mockito.anyInt
 import org.mockito.Mockito.spy
-import org.mockito.Mockito.times
 import org.mockito.Mockito.verify
-import org.mockito.kotlin.any
 import org.mockito.kotlin.argumentCaptor
-import org.mockito.kotlin.eq
 import org.mockito.kotlin.whenever
 import org.mockito.quality.Strictness
 
@@ -73,27 +50,18 @@
 @RunWith(AndroidTestingRunner::class)
 class DesktopDisplayEventHandlerTest : ShellTestCase() {
     @Mock lateinit var testExecutor: ShellExecutor
-    @Mock lateinit var transitions: Transitions
     @Mock lateinit var displayController: DisplayController
-    @Mock lateinit var rootTaskDisplayAreaOrganizer: RootTaskDisplayAreaOrganizer
-    @Mock private lateinit var mockWindowManager: IWindowManager
     @Mock private lateinit var mockDesktopUserRepositories: DesktopUserRepositories
     @Mock private lateinit var mockDesktopRepository: DesktopRepository
     @Mock private lateinit var mockDesktopTasksController: DesktopTasksController
-    @Mock private lateinit var shellTaskOrganizer: ShellTaskOrganizer
+    @Mock private lateinit var desktopDisplayModeController: DesktopDisplayModeController
 
     private lateinit var mockitoSession: StaticMockitoSession
     private lateinit var shellInit: ShellInit
     private lateinit var handler: DesktopDisplayEventHandler
 
     private val onDisplaysChangedListenerCaptor = argumentCaptor<OnDisplaysChangedListener>()
-    private val runningTasks = mutableListOf<RunningTaskInfo>()
     private val externalDisplayId = 100
-    private val freeformTask =
-        TestRunningTaskInfoBuilder().setWindowingMode(WINDOWING_MODE_FREEFORM).build()
-    private val fullscreenTask =
-        TestRunningTaskInfoBuilder().setWindowingMode(WINDOWING_MODE_FULLSCREEN).build()
-    private val defaultTDA = DisplayAreaInfo(MockToken().token(), DEFAULT_DISPLAY, 0)
 
     @Before
     fun setUp() {
@@ -105,24 +73,15 @@
 
         shellInit = spy(ShellInit(testExecutor))
         whenever(mockDesktopUserRepositories.current).thenReturn(mockDesktopRepository)
-        whenever(transitions.startTransition(anyInt(), any(), isNull())).thenAnswer { Binder() }
-        whenever(rootTaskDisplayAreaOrganizer.getDisplayAreaInfo(DEFAULT_DISPLAY))
-            .thenReturn(defaultTDA)
         handler =
             DesktopDisplayEventHandler(
                 context,
                 shellInit,
-                transitions,
                 displayController,
-                rootTaskDisplayAreaOrganizer,
-                mockWindowManager,
                 mockDesktopUserRepositories,
                 mockDesktopTasksController,
-                shellTaskOrganizer,
+                desktopDisplayModeController,
             )
-        whenever(shellTaskOrganizer.getRunningTasks(anyInt())).thenAnswer { runningTasks }
-        runningTasks.add(freeformTask)
-        runningTasks.add(fullscreenTask)
         shellInit.init()
         verify(displayController)
             .addDisplayWindowListener(onDisplaysChangedListenerCaptor.capture())
@@ -133,65 +92,6 @@
         mockitoSession.finishMocking()
     }
 
-    private fun testDisplayWindowingModeSwitch(
-        defaultWindowingMode: Int,
-        extendedDisplayEnabled: Boolean,
-        expectTransition: Boolean,
-    ) {
-        defaultTDA.configuration.windowConfiguration.windowingMode = defaultWindowingMode
-        whenever(mockWindowManager.getWindowingMode(anyInt())).thenAnswer { defaultWindowingMode }
-        val settingsSession =
-            ExtendedDisplaySettingsSession(
-                context.contentResolver,
-                if (extendedDisplayEnabled) 1 else 0,
-            )
-
-        settingsSession.use {
-            connectExternalDisplay()
-            defaultTDA.configuration.windowConfiguration.windowingMode = WINDOWING_MODE_FREEFORM
-            disconnectExternalDisplay()
-
-            if (expectTransition) {
-                val arg = argumentCaptor<WindowContainerTransaction>()
-                verify(transitions, times(2))
-                    .startTransition(eq(TRANSIT_CHANGE), arg.capture(), isNull())
-                assertThat(arg.firstValue.changes[defaultTDA.token.asBinder()]?.windowingMode)
-                    .isEqualTo(WINDOWING_MODE_FREEFORM)
-                assertThat(arg.secondValue.changes[defaultTDA.token.asBinder()]?.windowingMode)
-                    .isEqualTo(defaultWindowingMode)
-            } else {
-                verify(transitions, never()).startTransition(eq(TRANSIT_CHANGE), any(), isNull())
-            }
-        }
-    }
-
-    @Test
-    fun displayWindowingModeSwitchOnDisplayConnected_extendedDisplayDisabled() {
-        testDisplayWindowingModeSwitch(
-            defaultWindowingMode = WINDOWING_MODE_FULLSCREEN,
-            extendedDisplayEnabled = false,
-            expectTransition = false,
-        )
-    }
-
-    @Test
-    fun displayWindowingModeSwitchOnDisplayConnected_fullscreenDisplay() {
-        testDisplayWindowingModeSwitch(
-            defaultWindowingMode = WINDOWING_MODE_FULLSCREEN,
-            extendedDisplayEnabled = true,
-            expectTransition = true,
-        )
-    }
-
-    @Test
-    fun displayWindowingModeSwitchOnDisplayConnected_freeformDisplay() {
-        testDisplayWindowingModeSwitch(
-            defaultWindowingMode = WINDOWING_MODE_FREEFORM,
-            extendedDisplayEnabled = true,
-            expectTransition = false,
-        )
-    }
-
     @Test
     fun testDisplayAdded_supportsDesks_createsDesk() {
         whenever(DesktopModeStatus.canEnterDesktopMode(context)).thenReturn(true)
@@ -231,70 +131,14 @@
     }
 
     @Test
-    fun displayWindowingModeSwitch_existingTasksOnConnected() {
-        defaultTDA.configuration.windowConfiguration.windowingMode = WINDOWING_MODE_FULLSCREEN
-        whenever(mockWindowManager.getWindowingMode(anyInt())).thenAnswer {
-            WINDOWING_MODE_FULLSCREEN
-        }
-
-        ExtendedDisplaySettingsSession(context.contentResolver, 1).use {
-            connectExternalDisplay()
-
-            val arg = argumentCaptor<WindowContainerTransaction>()
-            verify(transitions, times(1))
-                .startTransition(eq(TRANSIT_CHANGE), arg.capture(), isNull())
-            assertThat(arg.firstValue.changes[freeformTask.token.asBinder()]?.windowingMode)
-                .isEqualTo(WINDOWING_MODE_UNDEFINED)
-            assertThat(arg.firstValue.changes[fullscreenTask.token.asBinder()]?.windowingMode)
-                .isEqualTo(WINDOWING_MODE_FULLSCREEN)
-        }
+    fun testConnectExternalDisplay() {
+        onDisplaysChangedListenerCaptor.lastValue.onDisplayAdded(externalDisplayId)
+        verify(desktopDisplayModeController).refreshDisplayWindowingMode()
     }
 
     @Test
-    fun displayWindowingModeSwitch_existingTasksOnDisconnected() {
-        defaultTDA.configuration.windowConfiguration.windowingMode = WINDOWING_MODE_FREEFORM
-        whenever(mockWindowManager.getWindowingMode(anyInt())).thenAnswer {
-            WINDOWING_MODE_FULLSCREEN
-        }
-
-        ExtendedDisplaySettingsSession(context.contentResolver, 1).use {
-            disconnectExternalDisplay()
-
-            val arg = argumentCaptor<WindowContainerTransaction>()
-            verify(transitions, times(1))
-                .startTransition(eq(TRANSIT_CHANGE), arg.capture(), isNull())
-            assertThat(arg.firstValue.changes[freeformTask.token.asBinder()]?.windowingMode)
-                .isEqualTo(WINDOWING_MODE_FREEFORM)
-            assertThat(arg.firstValue.changes[fullscreenTask.token.asBinder()]?.windowingMode)
-                .isEqualTo(WINDOWING_MODE_UNDEFINED)
-        }
-    }
-
-    private fun connectExternalDisplay() {
-        whenever(rootTaskDisplayAreaOrganizer.getDisplayIds())
-            .thenReturn(intArrayOf(DEFAULT_DISPLAY, externalDisplayId))
-        onDisplaysChangedListenerCaptor.lastValue.onDisplayAdded(externalDisplayId)
-    }
-
-    private fun disconnectExternalDisplay() {
-        whenever(rootTaskDisplayAreaOrganizer.getDisplayIds())
-            .thenReturn(intArrayOf(DEFAULT_DISPLAY))
+    fun testDisconnectExternalDisplay() {
         onDisplaysChangedListenerCaptor.lastValue.onDisplayRemoved(externalDisplayId)
-    }
-
-    private class ExtendedDisplaySettingsSession(
-        private val contentResolver: ContentResolver,
-        private val overrideValue: Int,
-    ) : AutoCloseable {
-        private val settingName = DEVELOPMENT_FORCE_DESKTOP_MODE_ON_EXTERNAL_DISPLAYS
-        private val initialValue = Settings.Global.getInt(contentResolver, settingName, 0)
-
-        init {
-            Settings.Global.putInt(contentResolver, settingName, overrideValue)
-        }
-
-        override fun close() {
-            Settings.Global.putInt(contentResolver, settingName, initialValue)
-        }
+        verify(desktopDisplayModeController).refreshDisplayWindowingMode()
     }
 }
diff --git a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/desktopmode/DesktopDisplayModeControllerTest.kt b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/desktopmode/DesktopDisplayModeControllerTest.kt
new file mode 100644
index 0000000..0ff7230
--- /dev/null
+++ b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/desktopmode/DesktopDisplayModeControllerTest.kt
@@ -0,0 +1,233 @@
+/*
+ * Copyright (C) 2025 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.wm.shell.desktopmode
+
+import android.app.ActivityManager.RunningTaskInfo
+import android.app.WindowConfiguration.WINDOWING_MODE_FREEFORM
+import android.app.WindowConfiguration.WINDOWING_MODE_FULLSCREEN
+import android.app.WindowConfiguration.WINDOWING_MODE_UNDEFINED
+import android.content.ContentResolver
+import android.os.Binder
+import android.provider.Settings
+import android.provider.Settings.Global.DEVELOPMENT_FORCE_DESKTOP_MODE_ON_EXTERNAL_DISPLAYS
+import android.testing.AndroidTestingRunner
+import android.view.Display.DEFAULT_DISPLAY
+import android.view.IWindowManager
+import android.view.WindowManager.TRANSIT_CHANGE
+import android.window.DisplayAreaInfo
+import android.window.WindowContainerTransaction
+import androidx.test.filters.SmallTest
+import com.android.dx.mockito.inline.extended.ExtendedMockito.never
+import com.android.wm.shell.MockToken
+import com.android.wm.shell.RootTaskDisplayAreaOrganizer
+import com.android.wm.shell.ShellTaskOrganizer
+import com.android.wm.shell.ShellTestCase
+import com.android.wm.shell.TestRunningTaskInfoBuilder
+import com.android.wm.shell.desktopmode.desktopwallpaperactivity.DesktopWallpaperActivityTokenProvider
+import com.android.wm.shell.transition.Transitions
+import com.google.common.truth.Truth.assertThat
+import org.junit.Before
+import org.junit.Test
+import org.junit.runner.RunWith
+import org.mockito.ArgumentMatchers.isNull
+import org.mockito.Mockito.anyInt
+import org.mockito.Mockito.times
+import org.mockito.Mockito.verify
+import org.mockito.kotlin.any
+import org.mockito.kotlin.argumentCaptor
+import org.mockito.kotlin.eq
+import org.mockito.kotlin.mock
+import org.mockito.kotlin.whenever
+
+/**
+ * Test class for [DesktopDisplayModeController]
+ *
+ * Usage: atest WMShellUnitTests:DesktopDisplayModeControllerTest
+ */
+@SmallTest
+@RunWith(AndroidTestingRunner::class)
+class DesktopDisplayModeControllerTest : ShellTestCase() {
+    private val transitions = mock<Transitions>()
+    private val rootTaskDisplayAreaOrganizer = mock<RootTaskDisplayAreaOrganizer>()
+    private val mockWindowManager = mock<IWindowManager>()
+    private val shellTaskOrganizer = mock<ShellTaskOrganizer>()
+    private val desktopWallpaperActivityTokenProvider =
+        mock<DesktopWallpaperActivityTokenProvider>()
+
+    private lateinit var controller: DesktopDisplayModeController
+
+    private val runningTasks = mutableListOf<RunningTaskInfo>()
+    private val freeformTask =
+        TestRunningTaskInfoBuilder().setWindowingMode(WINDOWING_MODE_FREEFORM).build()
+    private val fullscreenTask =
+        TestRunningTaskInfoBuilder().setWindowingMode(WINDOWING_MODE_FULLSCREEN).build()
+    private val defaultTDA = DisplayAreaInfo(MockToken().token(), DEFAULT_DISPLAY, 0)
+    private val wallpaperToken = MockToken().token()
+
+    @Before
+    fun setUp() {
+        whenever(transitions.startTransition(anyInt(), any(), isNull())).thenReturn(Binder())
+        whenever(rootTaskDisplayAreaOrganizer.getDisplayAreaInfo(DEFAULT_DISPLAY))
+            .thenReturn(defaultTDA)
+        controller =
+            DesktopDisplayModeController(
+                context,
+                transitions,
+                rootTaskDisplayAreaOrganizer,
+                mockWindowManager,
+                shellTaskOrganizer,
+                desktopWallpaperActivityTokenProvider,
+            )
+        runningTasks.add(freeformTask)
+        runningTasks.add(fullscreenTask)
+        whenever(shellTaskOrganizer.getRunningTasks(anyInt())).thenReturn(ArrayList(runningTasks))
+        whenever(desktopWallpaperActivityTokenProvider.getToken()).thenReturn(wallpaperToken)
+    }
+
+    private fun testDisplayWindowingModeSwitch(
+        defaultWindowingMode: Int,
+        extendedDisplayEnabled: Boolean,
+        expectTransition: Boolean,
+    ) {
+        defaultTDA.configuration.windowConfiguration.windowingMode = defaultWindowingMode
+        whenever(mockWindowManager.getWindowingMode(anyInt())).thenReturn(defaultWindowingMode)
+        val settingsSession =
+            ExtendedDisplaySettingsSession(
+                context.contentResolver,
+                if (extendedDisplayEnabled) 1 else 0,
+            )
+
+        settingsSession.use {
+            connectExternalDisplay()
+            defaultTDA.configuration.windowConfiguration.windowingMode = WINDOWING_MODE_FREEFORM
+            disconnectExternalDisplay()
+
+            if (expectTransition) {
+                val arg = argumentCaptor<WindowContainerTransaction>()
+                verify(transitions, times(2))
+                    .startTransition(eq(TRANSIT_CHANGE), arg.capture(), isNull())
+                assertThat(arg.firstValue.changes[defaultTDA.token.asBinder()]?.windowingMode)
+                    .isEqualTo(WINDOWING_MODE_FREEFORM)
+                assertThat(arg.firstValue.changes[wallpaperToken.asBinder()]?.windowingMode)
+                    .isEqualTo(WINDOWING_MODE_FULLSCREEN)
+                assertThat(arg.secondValue.changes[defaultTDA.token.asBinder()]?.windowingMode)
+                    .isEqualTo(defaultWindowingMode)
+                assertThat(arg.secondValue.changes[wallpaperToken.asBinder()]?.windowingMode)
+                    .isEqualTo(WINDOWING_MODE_FULLSCREEN)
+            } else {
+                verify(transitions, never()).startTransition(eq(TRANSIT_CHANGE), any(), isNull())
+            }
+        }
+    }
+
+    @Test
+    fun displayWindowingModeSwitchOnDisplayConnected_extendedDisplayDisabled() {
+        testDisplayWindowingModeSwitch(
+            defaultWindowingMode = WINDOWING_MODE_FULLSCREEN,
+            extendedDisplayEnabled = false,
+            expectTransition = false,
+        )
+    }
+
+    @Test
+    fun displayWindowingModeSwitchOnDisplayConnected_fullscreenDisplay() {
+        testDisplayWindowingModeSwitch(
+            defaultWindowingMode = WINDOWING_MODE_FULLSCREEN,
+            extendedDisplayEnabled = true,
+            expectTransition = true,
+        )
+    }
+
+    @Test
+    fun displayWindowingModeSwitchOnDisplayConnected_freeformDisplay() {
+        testDisplayWindowingModeSwitch(
+            defaultWindowingMode = WINDOWING_MODE_FREEFORM,
+            extendedDisplayEnabled = true,
+            expectTransition = false,
+        )
+    }
+
+    @Test
+    fun displayWindowingModeSwitch_existingTasksOnConnected() {
+        defaultTDA.configuration.windowConfiguration.windowingMode = WINDOWING_MODE_FULLSCREEN
+        whenever(mockWindowManager.getWindowingMode(anyInt())).thenReturn(WINDOWING_MODE_FULLSCREEN)
+
+        ExtendedDisplaySettingsSession(context.contentResolver, 1).use {
+            connectExternalDisplay()
+
+            val arg = argumentCaptor<WindowContainerTransaction>()
+            verify(transitions, times(1))
+                .startTransition(eq(TRANSIT_CHANGE), arg.capture(), isNull())
+            assertThat(arg.firstValue.changes[freeformTask.token.asBinder()]?.windowingMode)
+                .isEqualTo(WINDOWING_MODE_UNDEFINED)
+            assertThat(arg.firstValue.changes[fullscreenTask.token.asBinder()]?.windowingMode)
+                .isEqualTo(WINDOWING_MODE_FULLSCREEN)
+        }
+    }
+
+    @Test
+    fun displayWindowingModeSwitch_existingTasksOnDisconnected() {
+        defaultTDA.configuration.windowConfiguration.windowingMode = WINDOWING_MODE_FREEFORM
+        whenever(mockWindowManager.getWindowingMode(anyInt())).thenAnswer {
+            WINDOWING_MODE_FULLSCREEN
+        }
+
+        ExtendedDisplaySettingsSession(context.contentResolver, 1).use {
+            disconnectExternalDisplay()
+
+            val arg = argumentCaptor<WindowContainerTransaction>()
+            verify(transitions, times(1))
+                .startTransition(eq(TRANSIT_CHANGE), arg.capture(), isNull())
+            assertThat(arg.firstValue.changes[freeformTask.token.asBinder()]?.windowingMode)
+                .isEqualTo(WINDOWING_MODE_FREEFORM)
+            assertThat(arg.firstValue.changes[fullscreenTask.token.asBinder()]?.windowingMode)
+                .isEqualTo(WINDOWING_MODE_UNDEFINED)
+        }
+    }
+
+    private fun connectExternalDisplay() {
+        whenever(rootTaskDisplayAreaOrganizer.getDisplayIds())
+            .thenReturn(intArrayOf(DEFAULT_DISPLAY, EXTERNAL_DISPLAY_ID))
+        controller.refreshDisplayWindowingMode()
+    }
+
+    private fun disconnectExternalDisplay() {
+        whenever(rootTaskDisplayAreaOrganizer.getDisplayIds())
+            .thenReturn(intArrayOf(DEFAULT_DISPLAY))
+        controller.refreshDisplayWindowingMode()
+    }
+
+    private class ExtendedDisplaySettingsSession(
+        private val contentResolver: ContentResolver,
+        private val overrideValue: Int,
+    ) : AutoCloseable {
+        private val settingName = DEVELOPMENT_FORCE_DESKTOP_MODE_ON_EXTERNAL_DISPLAYS
+        private val initialValue = Settings.Global.getInt(contentResolver, settingName, 0)
+
+        init {
+            Settings.Global.putInt(contentResolver, settingName, overrideValue)
+        }
+
+        override fun close() {
+            Settings.Global.putInt(contentResolver, settingName, initialValue)
+        }
+    }
+
+    private companion object {
+        const val EXTERNAL_DISPLAY_ID = 100
+    }
+}
diff --git a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/desktopmode/DesktopMinimizationTransitionHandlerTest.kt b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/desktopmode/DesktopMinimizationTransitionHandlerTest.kt
index 4c3325d..0d1c5722 100644
--- a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/desktopmode/DesktopMinimizationTransitionHandlerTest.kt
+++ b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/desktopmode/DesktopMinimizationTransitionHandlerTest.kt
@@ -21,6 +21,7 @@
 import android.app.WindowConfiguration.WINDOWING_MODE_FREEFORM
 import android.app.WindowConfiguration.WINDOWING_MODE_FULLSCREEN
 import android.app.WindowConfiguration.WindowingMode
+import android.os.Handler
 import android.testing.AndroidTestingRunner
 import android.testing.TestableLooper.RunWithLooper
 import android.view.SurfaceControl
@@ -56,7 +57,12 @@
     @Before
     fun setUp() {
         handler =
-            DesktopMinimizationTransitionHandler(testExecutor, testExecutor, displayController)
+            DesktopMinimizationTransitionHandler(
+                testExecutor,
+                testExecutor,
+                displayController,
+                mock<Handler>(),
+            )
         whenever(displayController.getDisplayContext(any())).thenReturn(mContext)
     }
 
diff --git a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/desktopmode/DesktopRepositoryTest.kt b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/desktopmode/DesktopRepositoryTest.kt
index ed9b97d..9bff287 100644
--- a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/desktopmode/DesktopRepositoryTest.kt
+++ b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/desktopmode/DesktopRepositoryTest.kt
@@ -333,7 +333,7 @@
     @Test
     fun isOnlyVisibleNonClosingTask_singleVisibleClosingTask() {
         repo.updateTask(DEFAULT_DISPLAY, taskId = 1, isVisible = true)
-        repo.addClosingTask(DEFAULT_DISPLAY, 1)
+        repo.addClosingTask(displayId = DEFAULT_DISPLAY, deskId = 0, taskId = 1)
 
         // A visible task that's closing
         assertThat(repo.isVisibleTask(1)).isTrue()
diff --git a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/desktopmode/DesktopTasksControllerTest.kt b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/desktopmode/DesktopTasksControllerTest.kt
index fcd92ac..9a73b02 100644
--- a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/desktopmode/DesktopTasksControllerTest.kt
+++ b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/desktopmode/DesktopTasksControllerTest.kt
@@ -2477,8 +2477,7 @@
         controller.moveTaskToFront(task.taskId, unminimizeReason = UnminimizeReason.UNKNOWN)
 
         val wct = getLatestDesktopMixedTaskWct(type = TRANSIT_OPEN)
-        assertThat(wct.hierarchyOps).hasSize(1)
-        wct.assertLaunchTaskAt(0, task.taskId, WINDOWING_MODE_FREEFORM)
+        wct.assertLaunchTask(task.taskId, WINDOWING_MODE_FREEFORM)
     }
 
     @Test
@@ -2810,6 +2809,7 @@
 
         // Should launch home
         wct.assertPendingIntentAt(0, launchHomeIntent(DEFAULT_DISPLAY))
+        wct.assertPendingIntentActivityOptionsLaunchDisplayIdAt(0, DEFAULT_DISPLAY)
     }
 
     @Test
@@ -2827,7 +2827,7 @@
     fun onDesktopWindowClose_singleActiveTask_isClosing() {
         val task = setUpFreeformTask()
 
-        taskRepository.addClosingTask(DEFAULT_DISPLAY, task.taskId)
+        taskRepository.addClosingTask(displayId = DEFAULT_DISPLAY, deskId = 0, taskId = task.taskId)
 
         val wct = WindowContainerTransaction()
         controller.onDesktopWindowClose(wct, displayId = DEFAULT_DISPLAY, task)
@@ -2864,7 +2864,11 @@
         val task1 = setUpFreeformTask()
         val task2 = setUpFreeformTask()
 
-        taskRepository.addClosingTask(DEFAULT_DISPLAY, task2.taskId)
+        taskRepository.addClosingTask(
+            displayId = DEFAULT_DISPLAY,
+            deskId = 0,
+            taskId = task2.taskId,
+        )
 
         val wct = WindowContainerTransaction()
         controller.onDesktopWindowClose(wct, displayId = DEFAULT_DISPLAY, task1)
@@ -2966,9 +2970,13 @@
         val captor = argumentCaptor<WindowContainerTransaction>()
         verify(freeformTaskTransitionStarter)
             .startMinimizedModeTransition(captor.capture(), eq(task.taskId), eq(false))
-        captor.firstValue.hierarchyOps.none { hop ->
-            hop.type == HIERARCHY_OP_TYPE_REMOVE_TASK && hop.container == wallpaperToken.asBinder()
-        }
+        assertThat(
+                captor.firstValue.hierarchyOps.none { hop ->
+                    hop.type == HIERARCHY_OP_TYPE_REMOVE_TASK &&
+                        hop.container == wallpaperToken.asBinder()
+                }
+            )
+            .isTrue()
     }
 
     @Test
@@ -3057,9 +3065,13 @@
 
         val captor = argumentCaptor<WindowContainerTransaction>()
         verify(freeformTaskTransitionStarter).startPipTransition(captor.capture())
-        captor.firstValue.hierarchyOps.none { hop ->
-            hop.type == HIERARCHY_OP_TYPE_REMOVE_TASK && hop.container == wallpaperToken.asBinder()
-        }
+        assertThat(
+                captor.firstValue.hierarchyOps.none { hop ->
+                    hop.type == HIERARCHY_OP_TYPE_REMOVE_TASK &&
+                        hop.container == wallpaperToken.asBinder()
+                }
+            )
+            .isTrue()
     }
 
     @Test
@@ -3080,7 +3092,12 @@
         val captor = argumentCaptor<WindowContainerTransaction>()
         verify(freeformTaskTransitionStarter)
             .startMinimizedModeTransition(captor.capture(), eq(task.taskId), eq(true))
-        captor.firstValue.hierarchyOps.none { hop -> hop.type == HIERARCHY_OP_TYPE_REMOVE_TASK }
+        assertThat(
+                captor.firstValue.hierarchyOps.none { hop ->
+                    hop.type == HIERARCHY_OP_TYPE_REMOVE_TASK
+                }
+            )
+            .isTrue()
     }
 
     @Test
@@ -3127,9 +3144,13 @@
         val captor = argumentCaptor<WindowContainerTransaction>()
         verify(freeformTaskTransitionStarter)
             .startMinimizedModeTransition(captor.capture(), eq(task.taskId), eq(false))
-        captor.firstValue.hierarchyOps.none { hop ->
-            hop.type == HIERARCHY_OP_TYPE_REMOVE_TASK && hop.container == wallpaperToken.asBinder()
-        }
+        assertThat(
+                captor.firstValue.hierarchyOps.none { hop ->
+                    hop.type == HIERARCHY_OP_TYPE_REMOVE_TASK &&
+                        hop.container == wallpaperToken.asBinder()
+                }
+            )
+            .isTrue()
     }
 
     @Test
@@ -3151,9 +3172,13 @@
         val captor = argumentCaptor<WindowContainerTransaction>()
         verify(freeformTaskTransitionStarter)
             .startMinimizedModeTransition(captor.capture(), eq(task1.taskId), eq(false))
-        captor.firstValue.hierarchyOps.none { hop ->
-            hop.type == HIERARCHY_OP_TYPE_REMOVE_TASK && hop.container == wallpaperToken.asBinder()
-        }
+        assertThat(
+                captor.firstValue.hierarchyOps.none { hop ->
+                    hop.type == HIERARCHY_OP_TYPE_REMOVE_TASK &&
+                        hop.container == wallpaperToken.asBinder()
+                }
+            )
+            .isTrue()
     }
 
     @Test
@@ -3225,6 +3250,30 @@
     }
 
     @Test
+    @EnableFlags(Flags.FLAG_ENABLE_MULTIPLE_DESKTOPS_BACKEND)
+    fun onDesktopWindowMinimize_minimizesTask() {
+        val task = setUpFreeformTask()
+        val transition = Binder()
+        val runOnTransit = RunOnStartTransitionCallback()
+        whenever(
+                freeformTaskTransitionStarter.startMinimizedModeTransition(
+                    any(),
+                    anyInt(),
+                    anyBoolean(),
+                )
+            )
+            .thenReturn(transition)
+        whenever(mMockDesktopImmersiveController.exitImmersiveIfApplicable(any(), eq(task), any()))
+            .thenReturn(
+                ExitResult.Exit(exitingTask = task.taskId, runOnTransitionStart = runOnTransit)
+            )
+
+        controller.minimizeTask(task, MinimizeReason.MINIMIZE_BUTTON)
+
+        verify(desksOrganizer).minimizeTask(any(), /* deskId= */ eq(0), eq(task))
+    }
+
+    @Test
     fun onDesktopWindowMinimize_triesToStopTiling() {
         val task = setUpFreeformTask(displayId = DEFAULT_DISPLAY)
         val transition = Binder()
@@ -3883,6 +3932,7 @@
         // Should launch home
         assertNotNull(result, "Should handle request")
             .assertPendingIntentAt(0, launchHomeIntent(DEFAULT_DISPLAY))
+        result!!.assertPendingIntentActivityOptionsLaunchDisplayIdAt(0, DEFAULT_DISPLAY)
     }
 
     @Test
@@ -3909,6 +3959,7 @@
         // Should launch home
         assertNotNull(result, "Should handle request")
             .assertPendingIntentAt(0, launchHomeIntent(DEFAULT_DISPLAY))
+        result!!.assertPendingIntentActivityOptionsLaunchDisplayIdAt(0, DEFAULT_DISPLAY)
     }
 
     @Test
@@ -3972,7 +4023,11 @@
         val task1 = setUpFreeformTask(displayId = DEFAULT_DISPLAY)
         val task2 = setUpFreeformTask(displayId = DEFAULT_DISPLAY)
 
-        taskRepository.addClosingTask(displayId = DEFAULT_DISPLAY, taskId = task2.taskId)
+        taskRepository.addClosingTask(
+            displayId = DEFAULT_DISPLAY,
+            deskId = 0,
+            taskId = task2.taskId,
+        )
         val result =
             controller.handleRequest(Binder(), createTransition(task1, type = TRANSIT_TO_BACK))
 
@@ -4037,6 +4092,7 @@
         // Should launch home
         assertNotNull(result, "Should handle request")
             .assertPendingIntentAt(0, launchHomeIntent(DEFAULT_DISPLAY))
+        result!!.assertPendingIntentActivityOptionsLaunchDisplayIdAt(0, DEFAULT_DISPLAY)
     }
 
     @Test
@@ -4053,6 +4109,7 @@
         // Should launch home
         assertNotNull(result, "Should handle request")
             .assertPendingIntentAt(0, launchHomeIntent(SECOND_DISPLAY))
+        result!!.assertPendingIntentActivityOptionsLaunchDisplayIdAt(0, SECOND_DISPLAY)
     }
 
     @Test
@@ -4083,6 +4140,36 @@
     }
 
     @Test
+    @EnableFlags(
+        Flags.FLAG_ENABLE_DESKTOP_WINDOWING_WALLPAPER_ACTIVITY,
+        Flags.FLAG_ENABLE_DESKTOP_WALLPAPER_ACTIVITY_FOR_SYSTEM_USER,
+        Flags.FLAG_ENABLE_MULTIPLE_DESKTOPS_BACKEND,
+    )
+    fun handleRequest_closeTransition_onlyDesktopTask_deactivatesDesk() {
+        val task = setUpFreeformTask()
+
+        controller.handleRequest(Binder(), createTransition(task, type = TRANSIT_CLOSE))
+
+        verify(desksOrganizer).deactivateDesk(any(), /* deskId= */ eq(0))
+    }
+
+    @Test
+    @EnableFlags(
+        Flags.FLAG_ENABLE_DESKTOP_WINDOWING_WALLPAPER_ACTIVITY,
+        Flags.FLAG_ENABLE_DESKTOP_WALLPAPER_ACTIVITY_FOR_SYSTEM_USER,
+        Flags.FLAG_ENABLE_MULTIPLE_DESKTOPS_BACKEND,
+    )
+    fun handleRequest_closeTransition_onlyDesktopTask_addsDeactivatesDeskTransition() {
+        val transition = Binder()
+        val task = setUpFreeformTask()
+
+        controller.handleRequest(transition, createTransition(task, type = TRANSIT_CLOSE))
+
+        verify(desksTransitionsObserver)
+            .addPendingTransition(DeskTransition.DeactivateDesk(token = transition, deskId = 0))
+    }
+
+    @Test
     @DisableFlags(Flags.FLAG_ENABLE_DESKTOP_WINDOWING_WALLPAPER_ACTIVITY)
     fun handleRequest_closeTransition_multipleTasks_noWallpaper_doesNotHandle() {
         val task1 = setUpFreeformTask()
@@ -4115,7 +4202,11 @@
         val task1 = setUpFreeformTask(displayId = DEFAULT_DISPLAY)
         val task2 = setUpFreeformTask(displayId = DEFAULT_DISPLAY)
 
-        taskRepository.addClosingTask(displayId = DEFAULT_DISPLAY, taskId = task2.taskId)
+        taskRepository.addClosingTask(
+            displayId = DEFAULT_DISPLAY,
+            deskId = 0,
+            taskId = task2.taskId,
+        )
         val result =
             controller.handleRequest(Binder(), createTransition(task1, type = TRANSIT_CLOSE))
 
@@ -6240,6 +6331,61 @@
         assertThat(taskRepository.getNumberOfDesks(DEFAULT_DISPLAY)).isEqualTo(currentDeskCount + 1)
     }
 
+    @Test
+    @EnableFlags(
+        Flags.FLAG_ENABLE_DESKTOP_WINDOWING_WALLPAPER_ACTIVITY,
+        Flags.FLAG_ENABLE_DESKTOP_WALLPAPER_ACTIVITY_FOR_SYSTEM_USER,
+    )
+    fun startLaunchTransition_desktopNotShowing_movesWallpaperToFront() {
+        val launchingTask = createFreeformTask()
+        val wct = WindowContainerTransaction()
+        wct.reorder(launchingTask.token, /* onTop= */ true)
+        whenever(
+                desktopMixedTransitionHandler.startLaunchTransition(
+                    eq(TRANSIT_OPEN),
+                    any(),
+                    anyOrNull(),
+                    anyOrNull(),
+                    anyOrNull(),
+                )
+            )
+            .thenReturn(Binder())
+
+        controller.startLaunchTransition(TRANSIT_OPEN, wct, launchingTaskId = null)
+
+        val latestWct = getLatestDesktopMixedTaskWct(type = TRANSIT_OPEN)
+        val launchingTaskReorderIndex = latestWct.indexOfReorder(launchingTask, toTop = true)
+        val wallpaperReorderIndex = latestWct.indexOfReorder(wallpaperToken, toTop = true)
+        assertThat(launchingTaskReorderIndex).isNotEqualTo(-1)
+        assertThat(wallpaperReorderIndex).isNotEqualTo(-1)
+        assertThat(launchingTaskReorderIndex).isGreaterThan(wallpaperReorderIndex)
+    }
+
+    @Test
+    @EnableFlags(
+        Flags.FLAG_ENABLE_DESKTOP_WINDOWING_WALLPAPER_ACTIVITY,
+        Flags.FLAG_ENABLE_DESKTOP_WALLPAPER_ACTIVITY_FOR_SYSTEM_USER,
+    )
+    fun startLaunchTransition_desktopShowing_doesNotReorderWallpaper() {
+        val wct = WindowContainerTransaction()
+        whenever(
+                desktopMixedTransitionHandler.startLaunchTransition(
+                    eq(TRANSIT_OPEN),
+                    any(),
+                    anyOrNull(),
+                    anyOrNull(),
+                    anyOrNull(),
+                )
+            )
+            .thenReturn(Binder())
+
+        setUpFreeformTask()
+        controller.startLaunchTransition(TRANSIT_OPEN, wct, launchingTaskId = null)
+
+        val latestWct = getLatestDesktopMixedTaskWct(type = TRANSIT_OPEN)
+        assertNull(latestWct.hierarchyOps.find { op -> op.container == wallpaperToken.asBinder() })
+    }
+
     private class RunOnStartTransitionCallback : ((IBinder) -> Unit) {
         var invocations = 0
             private set
@@ -6626,11 +6772,18 @@
 }
 
 private fun WindowContainerTransaction.indexOfReorder(
+    token: WindowContainerToken,
+    toTop: Boolean? = null,
+): Int {
+    val hop = hierarchyOps.singleOrNull(ReorderPredicate(token, toTop)) ?: return -1
+    return hierarchyOps.indexOf(hop)
+}
+
+private fun WindowContainerTransaction.indexOfReorder(
     task: RunningTaskInfo,
     toTop: Boolean? = null,
 ): Int {
-    val hop = hierarchyOps.singleOrNull(ReorderPredicate(task.token, toTop)) ?: return -1
-    return hierarchyOps.indexOf(hop)
+    return indexOfReorder(task.token, toTop)
 }
 
 private class ReorderPredicate(val token: WindowContainerToken, val toTop: Boolean? = null) :
@@ -6750,6 +6903,29 @@
     assertThat(op.pendingIntent?.intent?.categories).isEqualTo(intent.categories)
 }
 
+private fun WindowContainerTransaction.assertPendingIntentActivityOptionsLaunchDisplayIdAt(
+    index: Int,
+    displayId: Int,
+) {
+    assertIndexInBounds(index)
+    val op = hierarchyOps[index]
+    if (op.launchOptions != null) {
+        val options = ActivityOptions(op.launchOptions)
+        assertThat(options.launchDisplayId).isEqualTo(displayId)
+    }
+}
+
+private fun WindowContainerTransaction.assertLaunchTask(taskId: Int, windowingMode: Int) {
+    val keyLaunchWindowingMode = "android.activity.windowingMode"
+
+    assertHop { hop ->
+        hop.type == HIERARCHY_OP_TYPE_LAUNCH_TASK &&
+            hop.launchOptions?.getInt(LAUNCH_KEY_TASK_ID) == taskId &&
+            hop.launchOptions?.getInt(keyLaunchWindowingMode, WINDOWING_MODE_UNDEFINED) ==
+                windowingMode
+    }
+}
+
 private fun WindowContainerTransaction.assertLaunchTaskAt(
     index: Int,
     taskId: Int,
diff --git a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/desktopmode/DesktopTasksLimiterTest.kt b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/desktopmode/DesktopTasksLimiterTest.kt
index d33209d..62e3c54 100644
--- a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/desktopmode/DesktopTasksLimiterTest.kt
+++ b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/desktopmode/DesktopTasksLimiterTest.kt
@@ -37,7 +37,6 @@
 import com.android.dx.mockito.inline.extended.ExtendedMockito
 import com.android.dx.mockito.inline.extended.ExtendedMockito.doReturn
 import com.android.dx.mockito.inline.extended.StaticMockitoSession
-import com.android.internal.jank.Cuj.CUJ_DESKTOP_MODE_MINIMIZE_WINDOW
 import com.android.internal.jank.InteractionJankMonitor
 import com.android.window.flags.Flags.FLAG_ENABLE_DESKTOP_WINDOWING_BACK_NAVIGATION
 import com.android.wm.shell.ShellTaskOrganizer
@@ -72,8 +71,6 @@
 import org.mockito.Mockito.mock
 import org.mockito.Mockito.spy
 import org.mockito.Mockito.`when`
-import org.mockito.kotlin.eq
-import org.mockito.kotlin.verify
 import org.mockito.quality.Strictness
 
 /**
@@ -521,85 +518,6 @@
     }
 
     @Test
-    fun minimizeTransitionReadyAndFinished_logsJankInstrumentationBeginAndEnd() {
-        desktopTaskRepo.addDesk(displayId = DEFAULT_DISPLAY, deskId = 0)
-        desktopTaskRepo.setActiveDesk(displayId = DEFAULT_DISPLAY, deskId = 0)
-        (1..<MAX_TASK_LIMIT).forEach { _ -> setUpFreeformTask() }
-        val transition = Binder()
-        val task = setUpFreeformTask()
-        addPendingMinimizeChange(transition, taskId = task.taskId)
-
-        callOnTransitionReady(
-            transition,
-            TransitionInfoBuilder(TRANSIT_OPEN).addChange(TRANSIT_TO_BACK, task).build(),
-        )
-
-        desktopTasksLimiter.getTransitionObserver().onTransitionStarting(transition)
-
-        verify(interactionJankMonitor)
-            .begin(any(), eq(mContext), eq(handler), eq(CUJ_DESKTOP_MODE_MINIMIZE_WINDOW))
-
-        desktopTasksLimiter
-            .getTransitionObserver()
-            .onTransitionFinished(transition, /* aborted= */ false)
-
-        verify(interactionJankMonitor).end(eq(CUJ_DESKTOP_MODE_MINIMIZE_WINDOW))
-    }
-
-    @Test
-    fun minimizeTransitionReadyAndAborted_logsJankInstrumentationBeginAndCancel() {
-        desktopTaskRepo.addDesk(displayId = DEFAULT_DISPLAY, deskId = 0)
-        desktopTaskRepo.setActiveDesk(displayId = DEFAULT_DISPLAY, deskId = 0)
-        (1..<MAX_TASK_LIMIT).forEach { _ -> setUpFreeformTask() }
-        val transition = Binder()
-        val task = setUpFreeformTask()
-        addPendingMinimizeChange(transition, taskId = task.taskId)
-
-        callOnTransitionReady(
-            transition,
-            TransitionInfoBuilder(TRANSIT_OPEN).addChange(TRANSIT_TO_BACK, task).build(),
-        )
-
-        desktopTasksLimiter.getTransitionObserver().onTransitionStarting(transition)
-
-        verify(interactionJankMonitor)
-            .begin(any(), eq(mContext), eq(handler), eq(CUJ_DESKTOP_MODE_MINIMIZE_WINDOW))
-
-        desktopTasksLimiter
-            .getTransitionObserver()
-            .onTransitionFinished(transition, /* aborted= */ true)
-
-        verify(interactionJankMonitor).cancel(eq(CUJ_DESKTOP_MODE_MINIMIZE_WINDOW))
-    }
-
-    @Test
-    fun minimizeTransitionReadyAndMerged_logsJankInstrumentationBeginAndEnd() {
-        desktopTaskRepo.addDesk(displayId = DEFAULT_DISPLAY, deskId = 0)
-        desktopTaskRepo.setActiveDesk(displayId = DEFAULT_DISPLAY, deskId = 0)
-        (1..<MAX_TASK_LIMIT).forEach { _ -> setUpFreeformTask() }
-        val mergedTransition = Binder()
-        val newTransition = Binder()
-        val task = setUpFreeformTask()
-        addPendingMinimizeChange(mergedTransition, taskId = task.taskId)
-
-        callOnTransitionReady(
-            mergedTransition,
-            TransitionInfoBuilder(TRANSIT_OPEN).addChange(TRANSIT_TO_BACK, task).build(),
-        )
-
-        desktopTasksLimiter.getTransitionObserver().onTransitionStarting(mergedTransition)
-
-        verify(interactionJankMonitor)
-            .begin(any(), eq(mContext), eq(handler), eq(CUJ_DESKTOP_MODE_MINIMIZE_WINDOW))
-
-        desktopTasksLimiter
-            .getTransitionObserver()
-            .onTransitionMerged(mergedTransition, newTransition)
-
-        verify(interactionJankMonitor).end(eq(CUJ_DESKTOP_MODE_MINIMIZE_WINDOW))
-    }
-
-    @Test
     fun getMinimizingTask_noPendingTransition_returnsNull() {
         val transition = Binder()
 
diff --git a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/desktopmode/multidesks/RootTaskDesksOrganizerTest.kt b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/desktopmode/multidesks/RootTaskDesksOrganizerTest.kt
index 8b10ca1..96b85ad 100644
--- a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/desktopmode/multidesks/RootTaskDesksOrganizerTest.kt
+++ b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/desktopmode/multidesks/RootTaskDesksOrganizerTest.kt
@@ -22,6 +22,7 @@
 import android.view.WindowManager.TRANSIT_TO_FRONT
 import android.window.TransitionInfo
 import android.window.WindowContainerTransaction
+import android.window.WindowContainerTransaction.Change
 import android.window.WindowContainerTransaction.HierarchyOp
 import android.window.WindowContainerTransaction.HierarchyOp.HIERARCHY_OP_TYPE_SET_LAUNCH_ROOT
 import androidx.test.filters.SmallTest
@@ -29,15 +30,19 @@
 import com.android.wm.shell.ShellTestCase
 import com.android.wm.shell.TestShellExecutor
 import com.android.wm.shell.desktopmode.DesktopTestHelpers.createFreeformTask
+import com.android.wm.shell.desktopmode.multidesks.RootTaskDesksOrganizer.DeskMinimizationRoot
 import com.android.wm.shell.desktopmode.multidesks.RootTaskDesksOrganizer.DeskRoot
 import com.android.wm.shell.sysui.ShellCommandHandler
 import com.android.wm.shell.sysui.ShellInit
 import com.google.common.truth.Truth.assertThat
+import kotlin.test.assertNotNull
 import org.junit.Assert.assertEquals
 import org.junit.Assert.assertThrows
 import org.junit.Before
 import org.junit.Test
 import org.junit.runner.RunWith
+import org.mockito.Mockito.verify
+import org.mockito.kotlin.argThat
 import org.mockito.kotlin.mock
 
 /**
@@ -75,6 +80,43 @@
     }
 
     @Test
+    fun testCreateDesk_createsMinimizationRoot() {
+        val callback = FakeOnCreateCallback()
+        organizer.createDesk(Display.DEFAULT_DISPLAY, callback)
+        val freeformRoot = createFreeformTask().apply { parentTaskId = -1 }
+        organizer.onTaskAppeared(freeformRoot, SurfaceControl())
+
+        val minimizationRootTask = createFreeformTask().apply { parentTaskId = -1 }
+        organizer.onTaskAppeared(minimizationRootTask, SurfaceControl())
+
+        val minimizationRoot = organizer.deskMinimizationRootsByDeskId[freeformRoot.taskId]
+        assertNotNull(minimizationRoot)
+        assertThat(minimizationRoot.deskId).isEqualTo(freeformRoot.taskId)
+        assertThat(minimizationRoot.rootId).isEqualTo(minimizationRootTask.taskId)
+    }
+
+    @Test
+    fun testCreateMinimizationRoot_marksHidden() {
+        organizer.createDesk(Display.DEFAULT_DISPLAY, FakeOnCreateCallback())
+        val freeformRoot = createFreeformTask().apply { parentTaskId = -1 }
+        organizer.onTaskAppeared(freeformRoot, SurfaceControl())
+
+        val minimizationRootTask = createFreeformTask().apply { parentTaskId = -1 }
+        organizer.onTaskAppeared(minimizationRootTask, SurfaceControl())
+
+        verify(mockShellTaskOrganizer)
+            .applyTransaction(
+                argThat { wct ->
+                    wct.changes.any { change ->
+                        change.key == minimizationRootTask.token.asBinder() &&
+                            (change.value.changeMask and Change.CHANGE_HIDDEN != 0) &&
+                            change.value.hidden
+                    }
+                }
+            )
+    }
+
+    @Test
     fun testOnTaskAppeared_withoutRequest_throws() {
         val freeformRoot = createFreeformTask().apply { parentTaskId = -1 }
 
@@ -105,57 +147,122 @@
     }
 
     @Test
+    fun testOnTaskAppeared_duplicateMinimizedRoot_throws() {
+        organizer.createDesk(Display.DEFAULT_DISPLAY, FakeOnCreateCallback())
+        val freeformRoot = createFreeformTask().apply { parentTaskId = -1 }
+        val minimizationRootTask = createFreeformTask().apply { parentTaskId = -1 }
+        organizer.onTaskAppeared(freeformRoot, SurfaceControl())
+        organizer.onTaskAppeared(minimizationRootTask, SurfaceControl())
+
+        assertThrows(Exception::class.java) {
+            organizer.onTaskAppeared(minimizationRootTask, SurfaceControl())
+        }
+    }
+
+    @Test
     fun testOnTaskVanished_removesRoot() {
         val desk = createDesk()
 
-        organizer.onTaskVanished(desk.taskInfo)
+        organizer.onTaskVanished(desk.deskRoot.taskInfo)
 
-        assertThat(organizer.roots.contains(desk.deskId)).isFalse()
+        assertThat(organizer.deskRootsByDeskId.contains(desk.deskRoot.deskId)).isFalse()
+    }
+
+    @Test
+    fun testOnTaskVanished_removesMinimizedRoot() {
+        val desk = createDesk()
+
+        organizer.onTaskVanished(desk.deskRoot.taskInfo)
+        organizer.onTaskVanished(desk.minimizationRoot.taskInfo)
+
+        assertThat(organizer.deskMinimizationRootsByDeskId.contains(desk.deskRoot.deskId)).isFalse()
     }
 
     @Test
     fun testDesktopWindowAppearsInDesk() {
         val desk = createDesk()
-        val child = createFreeformTask().apply { parentTaskId = desk.deskId }
+        val child = createFreeformTask().apply { parentTaskId = desk.deskRoot.deskId }
 
         organizer.onTaskAppeared(child, SurfaceControl())
 
-        assertThat(desk.children).contains(child.taskId)
+        assertThat(desk.deskRoot.children).contains(child.taskId)
+    }
+
+    @Test
+    fun testDesktopWindowAppearsInDeskMinimizationRoot() {
+        val desk = createDesk()
+        val child = createFreeformTask().apply { parentTaskId = desk.minimizationRoot.rootId }
+
+        organizer.onTaskAppeared(child, SurfaceControl())
+
+        assertThat(desk.minimizationRoot.children).contains(child.taskId)
+    }
+
+    @Test
+    fun testDesktopWindowMovesToMinimizationRoot() {
+        val desk = createDesk()
+        val child = createFreeformTask().apply { parentTaskId = desk.deskRoot.deskId }
+        organizer.onTaskAppeared(child, SurfaceControl())
+
+        child.parentTaskId = desk.minimizationRoot.rootId
+        organizer.onTaskInfoChanged(child)
+
+        assertThat(desk.deskRoot.children).doesNotContain(child.taskId)
+        assertThat(desk.minimizationRoot.children).contains(child.taskId)
     }
 
     @Test
     fun testDesktopWindowDisappearsFromDesk() {
         val desk = createDesk()
-        val child = createFreeformTask().apply { parentTaskId = desk.deskId }
+        val child = createFreeformTask().apply { parentTaskId = desk.deskRoot.deskId }
 
         organizer.onTaskAppeared(child, SurfaceControl())
         organizer.onTaskVanished(child)
 
-        assertThat(desk.children).doesNotContain(child.taskId)
+        assertThat(desk.deskRoot.children).doesNotContain(child.taskId)
     }
 
     @Test
-    fun testRemoveDesk() {
+    fun testDesktopWindowDisappearsFromDeskMinimizationRoot() {
+        val desk = createDesk()
+        val child = createFreeformTask().apply { parentTaskId = desk.minimizationRoot.rootId }
+
+        organizer.onTaskAppeared(child, SurfaceControl())
+        organizer.onTaskVanished(child)
+
+        assertThat(desk.minimizationRoot.children).doesNotContain(child.taskId)
+    }
+
+    @Test
+    fun testRemoveDesk_removesDeskRoot() {
         val desk = createDesk()
 
         val wct = WindowContainerTransaction()
-        organizer.removeDesk(wct, desk.deskId)
+        organizer.removeDesk(wct, desk.deskRoot.deskId)
 
         assertThat(
                 wct.hierarchyOps.any { hop ->
                     hop.type == HierarchyOp.HIERARCHY_OP_TYPE_REMOVE_ROOT_TASK &&
-                        hop.container == desk.taskInfo.token.asBinder()
+                        hop.container == desk.deskRoot.token.asBinder()
                 }
             )
             .isTrue()
     }
 
     @Test
-    fun testRemoveDesk_didNotExist_throws() {
-        val freeformRoot = createFreeformTask().apply { parentTaskId = -1 }
+    fun testRemoveDesk_removesMinimizationRoot() {
+        val desk = createDesk()
 
         val wct = WindowContainerTransaction()
-        assertThrows(Exception::class.java) { organizer.removeDesk(wct, freeformRoot.taskId) }
+        organizer.removeDesk(wct, desk.deskRoot.deskId)
+
+        assertThat(
+                wct.hierarchyOps.any { hop ->
+                    hop.type == HierarchyOp.HIERARCHY_OP_TYPE_REMOVE_ROOT_TASK &&
+                        hop.container == desk.minimizationRoot.token.asBinder()
+                }
+            )
+            .isTrue()
     }
 
     @Test
@@ -163,20 +270,20 @@
         val desk = createDesk()
 
         val wct = WindowContainerTransaction()
-        organizer.activateDesk(wct, desk.deskId)
+        organizer.activateDesk(wct, desk.deskRoot.deskId)
 
         assertThat(
                 wct.hierarchyOps.any { hop ->
                     hop.type == HierarchyOp.HIERARCHY_OP_TYPE_REORDER &&
                         hop.toTop &&
-                        hop.container == desk.taskInfo.token.asBinder()
+                        hop.container == desk.deskRoot.taskInfo.token.asBinder()
                 }
             )
             .isTrue()
         assertThat(
                 wct.hierarchyOps.any { hop ->
                     hop.type == HierarchyOp.HIERARCHY_OP_TYPE_SET_LAUNCH_ROOT &&
-                        hop.container == desk.taskInfo.token.asBinder()
+                        hop.container == desk.deskRoot.taskInfo.token.asBinder()
                 }
             )
             .isTrue()
@@ -196,14 +303,14 @@
 
         val desktopTask = createFreeformTask().apply { parentTaskId = -1 }
         val wct = WindowContainerTransaction()
-        organizer.moveTaskToDesk(wct, desk.deskId, desktopTask)
+        organizer.moveTaskToDesk(wct, desk.deskRoot.deskId, desktopTask)
 
         assertThat(
                 wct.hierarchyOps.any { hop ->
                     hop.isReparent &&
                         hop.toTop &&
                         hop.container == desktopTask.token.asBinder() &&
-                        hop.newParent == desk.taskInfo.token.asBinder()
+                        hop.newParent == desk.deskRoot.taskInfo.token.asBinder()
                 }
             )
             .isTrue()
@@ -231,13 +338,26 @@
     fun testGetDeskAtEnd() {
         val desk = createDesk()
 
-        val task = createFreeformTask().apply { parentTaskId = desk.deskId }
+        val task = createFreeformTask().apply { parentTaskId = desk.deskRoot.deskId }
         val endDesk =
             organizer.getDeskAtEnd(
                 TransitionInfo.Change(task.token, SurfaceControl()).apply { taskInfo = task }
             )
 
-        assertThat(endDesk).isEqualTo(desk.deskId)
+        assertThat(endDesk).isEqualTo(desk.deskRoot.deskId)
+    }
+
+    @Test
+    fun testGetDeskAtEnd_inMinimizationRoot() {
+        val desk = createDesk()
+
+        val task = createFreeformTask().apply { parentTaskId = desk.minimizationRoot.rootId }
+        val endDesk =
+            organizer.getDeskAtEnd(
+                TransitionInfo.Change(task.token, SurfaceControl()).apply { taskInfo = task }
+            )
+
+        assertThat(endDesk).isEqualTo(desk.deskRoot.deskId)
     }
 
     @Test
@@ -264,14 +384,14 @@
     fun deactivateDesk_clearsLaunchRoot() {
         val wct = WindowContainerTransaction()
         val desk = createDesk()
-        organizer.activateDesk(wct, desk.deskId)
+        organizer.activateDesk(wct, desk.deskRoot.deskId)
 
-        organizer.deactivateDesk(wct, desk.deskId)
+        organizer.deactivateDesk(wct, desk.deskRoot.deskId)
 
         assertThat(
                 wct.hierarchyOps.any { hop ->
                     hop.type == HIERARCHY_OP_TYPE_SET_LAUNCH_ROOT &&
-                        hop.container == desk.taskInfo.token.asBinder() &&
+                        hop.container == desk.deskRoot.taskInfo.token.asBinder() &&
                         hop.windowingModes == null &&
                         hop.activityTypes == null
                 }
@@ -280,25 +400,129 @@
     }
 
     @Test
-    fun isDeskChange() {
+    fun isDeskChange_forDeskId() {
         val desk = createDesk()
 
         assertThat(
                 organizer.isDeskChange(
-                    TransitionInfo.Change(desk.taskInfo.token, desk.leash).apply {
-                        taskInfo = desk.taskInfo
+                    TransitionInfo.Change(desk.deskRoot.taskInfo.token, desk.deskRoot.leash).apply {
+                        taskInfo = desk.deskRoot.taskInfo
                     },
-                    desk.deskId,
+                    desk.deskRoot.deskId,
                 )
             )
             .isTrue()
     }
 
-    private fun createDesk(): DeskRoot {
+    @Test
+    fun isDeskChange_forDeskId_inMinimizationRoot() {
+        val desk = createDesk()
+
+        assertThat(
+                organizer.isDeskChange(
+                    change =
+                        TransitionInfo.Change(
+                                desk.minimizationRoot.token,
+                                desk.minimizationRoot.leash,
+                            )
+                            .apply { taskInfo = desk.minimizationRoot.taskInfo },
+                    deskId = desk.deskRoot.deskId,
+                )
+            )
+            .isTrue()
+    }
+
+    @Test
+    fun isDeskChange_anyDesk() {
+        val desk = createDesk()
+
+        assertThat(
+                organizer.isDeskChange(
+                    change =
+                        TransitionInfo.Change(desk.deskRoot.taskInfo.token, desk.deskRoot.leash)
+                            .apply { taskInfo = desk.deskRoot.taskInfo }
+                )
+            )
+            .isTrue()
+    }
+
+    @Test
+    fun isDeskChange_anyDesk_inMinimizationRoot() {
+        val desk = createDesk()
+
+        assertThat(
+                organizer.isDeskChange(
+                    change =
+                        TransitionInfo.Change(
+                                desk.minimizationRoot.taskInfo.token,
+                                desk.minimizationRoot.leash,
+                            )
+                            .apply { taskInfo = desk.minimizationRoot.taskInfo }
+                )
+            )
+            .isTrue()
+    }
+
+    @Test
+    fun minimizeTask() {
+        val desk = createDesk()
+        val task = createFreeformTask().apply { parentTaskId = desk.deskRoot.deskId }
+        val wct = WindowContainerTransaction()
+        organizer.moveTaskToDesk(wct, desk.deskRoot.deskId, task)
+        organizer.onTaskAppeared(task, SurfaceControl())
+
+        organizer.minimizeTask(wct, deskId = desk.deskRoot.deskId, task)
+
+        assertThat(
+                wct.hierarchyOps.any { hop ->
+                    hop.isReparent &&
+                        hop.container == task.token.asBinder() &&
+                        hop.newParent == desk.minimizationRoot.token.asBinder()
+                }
+            )
+            .isTrue()
+    }
+
+    @Test
+    fun minimizeTask_alreadyMinimized_noOp() {
+        val desk = createDesk()
+        val task = createFreeformTask().apply { parentTaskId = desk.minimizationRoot.rootId }
+        val wct = WindowContainerTransaction()
+        organizer.onTaskAppeared(task, SurfaceControl())
+
+        organizer.minimizeTask(wct, deskId = desk.deskRoot.deskId, task)
+
+        assertThat(wct.isEmpty).isTrue()
+    }
+
+    @Test
+    fun minimizeTask_inDifferentDesk_noOp() {
+        val desk = createDesk()
+        val otherDesk = createDesk()
+        val task = createFreeformTask().apply { parentTaskId = otherDesk.deskRoot.deskId }
+        val wct = WindowContainerTransaction()
+        organizer.onTaskAppeared(task, SurfaceControl())
+
+        organizer.minimizeTask(wct, deskId = desk.deskRoot.deskId, task)
+
+        assertThat(wct.isEmpty).isTrue()
+    }
+
+    private data class DeskRoots(
+        val deskRoot: DeskRoot,
+        val minimizationRoot: DeskMinimizationRoot,
+    )
+
+    private fun createDesk(): DeskRoots {
         organizer.createDesk(Display.DEFAULT_DISPLAY, FakeOnCreateCallback())
         val freeformRoot = createFreeformTask().apply { parentTaskId = -1 }
         organizer.onTaskAppeared(freeformRoot, SurfaceControl())
-        return organizer.roots[freeformRoot.taskId]
+        val minimizationRoot = createFreeformTask().apply { parentTaskId = -1 }
+        organizer.onTaskAppeared(minimizationRoot, SurfaceControl())
+        return DeskRoots(
+            organizer.deskRootsByDeskId[freeformRoot.taskId],
+            checkNotNull(organizer.deskMinimizationRootsByDeskId[freeformRoot.taskId]),
+        )
     }
 
     private class FakeOnCreateCallback : DesksOrganizer.OnCreateCallback {
diff --git a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/recents/RecentsTransitionHandlerTest.java b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/recents/RecentsTransitionHandlerTest.java
index fd5e567..93dd345 100644
--- a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/recents/RecentsTransitionHandlerTest.java
+++ b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/recents/RecentsTransitionHandlerTest.java
@@ -17,16 +17,20 @@
 package com.android.wm.shell.recents;
 
 import static android.app.WindowConfiguration.ACTIVITY_TYPE_HOME;
+import static android.app.WindowConfiguration.ACTIVITY_TYPE_STANDARD;
 import static android.app.WindowConfiguration.WINDOWING_MODE_FREEFORM;
 import static android.view.WindowManager.TRANSIT_CLOSE;
 import static android.view.WindowManager.TRANSIT_OPEN;
+import static android.view.WindowManager.TRANSIT_SLEEP;
 import static android.view.WindowManager.TRANSIT_TO_FRONT;
 
 import static com.android.dx.mockito.inline.extended.ExtendedMockito.mockitoSession;
 import static com.android.window.flags.Flags.FLAG_ENABLE_DESKTOP_RECENTS_TRANSITIONS_CORNERS_BUGFIX;
+import static com.android.wm.shell.Flags.FLAG_ENABLE_RECENTS_BOOKEND_TRANSITION;
 import static com.android.wm.shell.recents.RecentsTransitionStateListener.TRANSITION_STATE_ANIMATING;
 import static com.android.wm.shell.recents.RecentsTransitionStateListener.TRANSITION_STATE_NOT_RUNNING;
 import static com.android.wm.shell.recents.RecentsTransitionStateListener.TRANSITION_STATE_REQUESTED;
+import static com.android.wm.shell.transition.Transitions.TRANSIT_END_RECENTS_TRANSITION;
 import static com.android.wm.shell.transition.Transitions.TRANSIT_START_RECENTS_TRANSITION;
 
 import static com.google.common.truth.Truth.assertThat;
@@ -64,7 +68,6 @@
 import com.android.dx.mockito.inline.extended.ExtendedMockito;
 import com.android.dx.mockito.inline.extended.StaticMockitoSession;
 import com.android.internal.os.IResultReceiver;
-import com.android.wm.shell.R;
 import com.android.wm.shell.ShellTaskOrganizer;
 import com.android.wm.shell.ShellTestCase;
 import com.android.wm.shell.TestRunningTaskInfoBuilder;
@@ -73,6 +76,7 @@
 import com.android.wm.shell.common.TaskStackListenerImpl;
 import com.android.wm.shell.desktopmode.DesktopRepository;
 import com.android.wm.shell.desktopmode.DesktopUserRepositories;
+import com.android.wm.shell.shared.R;
 import com.android.wm.shell.shared.desktopmode.DesktopModeStatus;
 import com.android.wm.shell.sysui.ShellCommandHandler;
 import com.android.wm.shell.sysui.ShellController;
@@ -308,8 +312,7 @@
         mRecentsTransitionHandler.findController(transition).merge(
                 mergeTransitionInfo,
                 new StubTransaction(),
-                finishT,
-                transition,
+                new StubTransaction(),
                 mock(Transitions.TransitionFinishCallback.class));
         mMainExecutor.flushAll();
 
@@ -318,6 +321,69 @@
     }
 
     @Test
+    @EnableFlags(FLAG_ENABLE_RECENTS_BOOKEND_TRANSITION)
+    public void testMerge_consumeBookendTransition() throws Exception {
+        // Start and finish the transition
+        final IRecentsAnimationRunner animationRunner = mock(IRecentsAnimationRunner.class);
+        final IBinder transition = startRecentsTransition(/* synthetic= */ false, animationRunner);
+        mRecentsTransitionHandler.startAnimation(
+                transition, createTransitionInfo(), new StubTransaction(), new StubTransaction(),
+                mock(Transitions.TransitionFinishCallback.class));
+        mRecentsTransitionHandler.findController(transition).finish(/* toHome= */ false,
+                false /* sendUserLeaveHint */, mock(IResultReceiver.class));
+        mMainExecutor.flushAll();
+
+        // Merge the bookend transition
+        TransitionInfo mergeTransitionInfo =
+                new TransitionInfoBuilder(TRANSIT_END_RECENTS_TRANSITION)
+                        .addChange(TRANSIT_OPEN, new TestRunningTaskInfoBuilder().build())
+                        .build();
+        SurfaceControl.Transaction finishT = mock(SurfaceControl.Transaction.class);
+        Transitions.TransitionFinishCallback finishCallback
+                = mock(Transitions.TransitionFinishCallback.class);
+        mRecentsTransitionHandler.findController(transition).merge(
+                mergeTransitionInfo,
+                new StubTransaction(),
+                finishT,
+                finishCallback);
+        mMainExecutor.flushAll();
+
+        // Verify that we've merged
+        verify(finishCallback).onTransitionFinished(any());
+    }
+
+    @Test
+    @EnableFlags(FLAG_ENABLE_RECENTS_BOOKEND_TRANSITION)
+    public void testMerge_pendingBookendTransition_mergesTransition() throws Exception {
+        // Start and finish the transition
+        final IRecentsAnimationRunner animationRunner = mock(IRecentsAnimationRunner.class);
+        final IBinder transition = startRecentsTransition(/* synthetic= */ false, animationRunner);
+        mRecentsTransitionHandler.startAnimation(
+                transition, createTransitionInfo(), new StubTransaction(), new StubTransaction(),
+                mock(Transitions.TransitionFinishCallback.class));
+        mRecentsTransitionHandler.findController(transition).finish(/* toHome= */ false,
+                false /* sendUserLeaveHint */, mock(IResultReceiver.class));
+        mMainExecutor.flushAll();
+
+        // Merge a new transition while we have a pending finish
+        TransitionInfo mergeTransitionInfo = new TransitionInfoBuilder(TRANSIT_OPEN)
+                .addChange(TRANSIT_OPEN, new TestRunningTaskInfoBuilder().build())
+                .build();
+        SurfaceControl.Transaction finishT = mock(SurfaceControl.Transaction.class);
+        Transitions.TransitionFinishCallback finishCallback
+                = mock(Transitions.TransitionFinishCallback.class);
+        mRecentsTransitionHandler.findController(transition).merge(
+                mergeTransitionInfo,
+                new StubTransaction(),
+                finishT,
+                finishCallback);
+        mMainExecutor.flushAll();
+
+        // Verify that we've cleaned up the original transition
+        assertNull(mRecentsTransitionHandler.findController(transition));
+    }
+
+    @Test
     @EnableFlags(FLAG_ENABLE_DESKTOP_RECENTS_TRANSITIONS_CORNERS_BUGFIX)
     public void testMergeAndFinish_openingFreeformTasks_setsCornerRadius() {
         ActivityManager.RunningTaskInfo freeformTask =
@@ -336,7 +402,6 @@
                 mergeTransitionInfo,
                 new StubTransaction(),
                 finishT,
-                transition,
                 mock(Transitions.TransitionFinishCallback.class));
         mRecentsTransitionHandler.findController(transition).finish(/* toHome= */ false,
                 false /* sendUserLeaveHint */, mock(IResultReceiver.class));
@@ -385,15 +450,23 @@
     }
 
     private TransitionInfo createTransitionInfo() {
-        final ActivityManager.RunningTaskInfo task = new TestRunningTaskInfoBuilder()
+        final ActivityManager.RunningTaskInfo homeTask = new TestRunningTaskInfoBuilder()
                 .setTopActivityType(ACTIVITY_TYPE_HOME)
                 .build();
+        final ActivityManager.RunningTaskInfo appTask = new TestRunningTaskInfoBuilder()
+                .setTopActivityType(ACTIVITY_TYPE_STANDARD)
+                .build();
         final TransitionInfo.Change homeChange = new TransitionInfo.Change(
-                task.token, new SurfaceControl());
+                homeTask.token, new SurfaceControl());
         homeChange.setMode(TRANSIT_TO_FRONT);
-        homeChange.setTaskInfo(task);
+        homeChange.setTaskInfo(homeTask);
+        final TransitionInfo.Change appChange = new TransitionInfo.Change(
+                appTask.token, new SurfaceControl());
+        appChange.setMode(TRANSIT_TO_FRONT);
+        appChange.setTaskInfo(appTask);
         return new TransitionInfoBuilder(TRANSIT_START_RECENTS_TRANSITION)
                 .addChange(homeChange)
+                .addChange(appChange)
                 .build();
     }
 
diff --git a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/shared/animation/MinimizeAnimatorTest.kt b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/shared/animation/MinimizeAnimatorTest.kt
new file mode 100644
index 0000000..ba609d4
--- /dev/null
+++ b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/shared/animation/MinimizeAnimatorTest.kt
@@ -0,0 +1,118 @@
+/*
+ * Copyright (C) 2025 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.wm.shell.shared.animation
+
+import android.animation.Animator
+import android.animation.AnimatorSet
+import android.animation.ValueAnimator
+import android.content.Context
+import android.content.res.Resources
+import android.os.Handler
+import android.util.DisplayMetrics
+import android.view.SurfaceControl
+import android.view.SurfaceControl.Transaction
+import android.window.TransitionInfo
+import android.window.WindowContainerToken
+import androidx.test.ext.junit.runners.AndroidJUnit4
+import androidx.test.filters.SmallTest
+import androidx.test.internal.runner.junit4.statement.UiThreadStatement.runOnUiThread
+import com.android.internal.jank.Cuj.CUJ_DESKTOP_MODE_MINIMIZE_WINDOW
+import com.android.internal.jank.InteractionJankMonitor
+import com.google.common.truth.Truth.assertThat
+import org.junit.Before
+import org.junit.Test
+import org.junit.runner.RunWith
+import org.mockito.ArgumentMatchers.any
+import org.mockito.ArgumentMatchers.anyFloat
+import org.mockito.ArgumentMatchers.anyLong
+import org.mockito.kotlin.mock
+import org.mockito.kotlin.never
+import org.mockito.kotlin.verify
+import org.mockito.kotlin.whenever
+
+@SmallTest
+@RunWith(AndroidJUnit4::class)
+class MinimizeAnimatorTest {
+    private val context = mock<Context>()
+    private val resources = mock<Resources>()
+    private val transaction = mock<Transaction>()
+    private val leash = mock<SurfaceControl>()
+    private val interactionJankMonitor = mock<InteractionJankMonitor>()
+    private val animationHandler = mock<Handler>()
+
+    private val displayMetrics = DisplayMetrics().apply { density = 1f }
+
+    @Before
+    fun setup() {
+        whenever(context.resources).thenReturn(resources)
+        whenever(resources.displayMetrics).thenReturn(displayMetrics)
+        whenever(transaction.setAlpha(any(), anyFloat())).thenReturn(transaction)
+        whenever(transaction.setPosition(any(), anyFloat(), anyFloat())).thenReturn(transaction)
+        whenever(transaction.setScale(any(), anyFloat(), anyFloat())).thenReturn(transaction)
+        whenever(transaction.setFrameTimeline(anyLong())).thenReturn(transaction)
+    }
+
+    @Test
+    fun create_returnsBoundsAndAlphaAnimators() {
+        val change = TransitionInfo.Change(mock<WindowContainerToken>(), leash)
+
+        val animator = createAnimator(change)
+
+        assertThat(animator).isInstanceOf(AnimatorSet::class.java)
+        val animatorSet = animator as AnimatorSet
+        assertThat(animatorSet.childAnimations).hasSize(2)
+        assertIsBoundsAnimator(animatorSet.childAnimations[0])
+        assertIsAlphaAnimator(animatorSet.childAnimations[1])
+    }
+
+    @Test
+    fun create_doesNotlogJankInstrumentation() = runOnUiThread {
+        val change = TransitionInfo.Change(mock<WindowContainerToken>(), leash)
+
+        createAnimator(change)
+
+        verify(interactionJankMonitor, never()).begin(
+            leash, context, animationHandler, CUJ_DESKTOP_MODE_MINIMIZE_WINDOW)
+    }
+
+    @Test
+    fun onAnimationStart_logsJankInstrumentation() = runOnUiThread {
+        val change = TransitionInfo.Change(mock<WindowContainerToken>(), leash)
+
+        createAnimator(change).start()
+
+        verify(interactionJankMonitor).begin(
+            leash, context, animationHandler, CUJ_DESKTOP_MODE_MINIMIZE_WINDOW)
+    }
+
+    private fun createAnimator(change: TransitionInfo.Change): Animator =
+        MinimizeAnimator.create(context, change, transaction, {}, interactionJankMonitor,
+            animationHandler)
+
+    private fun assertIsBoundsAnimator(animator: Animator) {
+        assertThat(animator).isInstanceOf(ValueAnimator::class.java)
+        assertThat(animator.duration).isEqualTo(200)
+        assertThat(animator.interpolator).isEqualTo(Interpolators.STANDARD_ACCELERATE)
+    }
+
+    private fun assertIsAlphaAnimator(animator: Animator) {
+        assertThat(animator).isInstanceOf(ValueAnimator::class.java)
+        assertThat(animator.duration).isEqualTo(100)
+        assertThat(animator.interpolator).isEqualTo(Interpolators.LINEAR)
+    }
+}
+
diff --git a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/shared/desktopmode/DesktopModeCompatPolicyTest.kt b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/shared/desktopmode/DesktopModeCompatPolicyTest.kt
index f69bf34..88c6e49 100644
--- a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/shared/desktopmode/DesktopModeCompatPolicyTest.kt
+++ b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/shared/desktopmode/DesktopModeCompatPolicyTest.kt
@@ -16,13 +16,16 @@
 
 package com.android.wm.shell.shared.desktopmode
 
+import android.Manifest.permission.SYSTEM_ALERT_WINDOW
 import android.app.TaskInfo
 import android.compat.testing.PlatformCompatChangeRule
 import android.content.ComponentName
 import android.content.pm.ActivityInfo
 import android.content.pm.ApplicationInfo
+import android.content.pm.PackageInfo
 import android.content.pm.PackageManager
 import android.os.Process
+import android.platform.test.annotations.DisableFlags
 import android.platform.test.annotations.EnableFlags
 import android.testing.AndroidTestingRunner
 import androidx.test.filters.SmallTest
@@ -39,7 +42,9 @@
 import org.junit.Rule
 import org.junit.Test
 import org.junit.runner.RunWith
+import org.mockito.ArgumentMatchers.anyString
 import org.mockito.kotlin.any
+import org.mockito.kotlin.eq
 import org.mockito.kotlin.mock
 import org.mockito.kotlin.whenever
 
@@ -55,6 +60,7 @@
     private lateinit var desktopModeCompatPolicy: DesktopModeCompatPolicy
     private val packageManager: PackageManager = mock()
     private val homeActivities = ComponentName(HOME_LAUNCHER_PACKAGE_NAME, /* class */ "")
+    private val baseActivityTest = ComponentName("com.test.dummypackage", "TestClass")
 
     @Before
     fun setUp() {
@@ -64,6 +70,7 @@
     }
 
     @Test
+    @DisableFlags(Flags.FLAG_ENABLE_MODALS_FULLSCREEN_WITH_PERMISSION)
     fun testIsTopActivityExemptFromDesktopWindowing_onlyTransparentActivitiesInStack() {
         assertTrue(desktopModeCompatPolicy.isTopActivityExemptFromDesktopWindowing(
             createFreeformTask(/* displayId */ 0)
@@ -71,10 +78,39 @@
                         isActivityStackTransparent = true
                         isTopActivityNoDisplay = false
                         numActivities = 1
+                        baseActivity = baseActivityTest
                     }))
     }
 
     @Test
+    @EnableFlags(Flags.FLAG_ENABLE_MODALS_FULLSCREEN_WITH_PERMISSION)
+    fun testIsTopActivityExemptWithPermission_onlyTransparentActivitiesInStack() {
+        allowOverlayPermission(arrayOf(SYSTEM_ALERT_WINDOW))
+        assertTrue(desktopModeCompatPolicy.isTopActivityExemptFromDesktopWindowing(
+            createFreeformTask(/* displayId */ 0)
+                .apply {
+                    isActivityStackTransparent = true
+                    isTopActivityNoDisplay = false
+                    numActivities = 1
+                    baseActivity = baseActivityTest
+                }))
+    }
+
+    @Test
+    @EnableFlags(Flags.FLAG_ENABLE_MODALS_FULLSCREEN_WITH_PERMISSION)
+    fun testIsTopActivityExemptWithNoPermission_onlyTransparentActivitiesInStack() {
+        allowOverlayPermission(arrayOf())
+        assertFalse(desktopModeCompatPolicy.isTopActivityExemptFromDesktopWindowing(
+            createFreeformTask(/* displayId */ 0)
+                .apply {
+                    isActivityStackTransparent = true
+                    isTopActivityNoDisplay = false
+                    numActivities = 1
+                    baseActivity = baseActivityTest
+                }))
+    }
+
+    @Test
     fun testIsTopActivityExemptFromDesktopWindowing_noActivitiesInStack() {
         assertFalse(desktopModeCompatPolicy.isTopActivityExemptFromDesktopWindowing(
             createFreeformTask(/* displayId */ 0)
@@ -219,4 +255,15 @@
                 }
             }
         }
+
+    fun allowOverlayPermission(permissions: Array<String>) {
+        val packageInfo = mock<PackageInfo>()
+        packageInfo.requestedPermissions = permissions
+        whenever(
+            packageManager.getPackageInfo(
+                anyString(),
+                eq(PackageManager.GET_PERMISSIONS)
+            )
+        ).thenReturn(packageInfo)
+    }
 }
diff --git a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/transition/HomeTransitionObserverTest.java b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/transition/HomeTransitionObserverTest.java
index 6f28e656..3099b0f 100644
--- a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/transition/HomeTransitionObserverTest.java
+++ b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/transition/HomeTransitionObserverTest.java
@@ -17,36 +17,44 @@
 package com.android.wm.shell.transition;
 
 import static android.app.WindowConfiguration.ACTIVITY_TYPE_HOME;
+import static android.app.WindowConfiguration.ACTIVITY_TYPE_STANDARD;
 import static android.app.WindowConfiguration.ACTIVITY_TYPE_UNDEFINED;
 import static android.view.WindowManager.TRANSIT_CHANGE;
 import static android.view.WindowManager.TRANSIT_OPEN;
 import static android.view.WindowManager.TRANSIT_PREPARE_BACK_NAVIGATION;
 import static android.view.WindowManager.TRANSIT_TO_BACK;
+import static android.view.WindowManager.TRANSIT_TO_FRONT;
 import static android.window.TransitionInfo.FLAG_BACK_GESTURE_ANIMATED;
 
 import static com.android.dx.mockito.inline.extended.ExtendedMockito.verify;
 import static com.android.wm.shell.desktopmode.DesktopModeTransitionTypes.TRANSIT_DESKTOP_MODE_START_DRAG_TO_DESKTOP;
+import static com.android.wm.shell.transition.Transitions.TRANSIT_CONVERT_TO_BUBBLE;
 
 import static org.mockito.ArgumentMatchers.anyBoolean;
 import static org.mockito.Mockito.mock;
+import static org.mockito.Mockito.never;
 import static org.mockito.Mockito.times;
 import static org.mockito.Mockito.when;
 
 import android.app.ActivityManager;
 import android.app.WindowConfiguration.ActivityType;
 import android.content.Context;
+import android.os.Binder;
 import android.os.Handler;
 import android.os.IBinder;
 import android.os.Looper;
 import android.os.RemoteException;
+import android.platform.test.annotations.EnableFlags;
 import android.view.SurfaceControl;
 import android.window.TransitionInfo;
 import android.window.TransitionInfo.TransitionMode;
+import android.window.WindowContainerToken;
 
 import androidx.test.ext.junit.runners.AndroidJUnit4;
 import androidx.test.filters.SmallTest;
 import androidx.test.platform.app.InstrumentationRegistry;
 
+import com.android.wm.shell.Flags;
 import com.android.wm.shell.ShellTaskOrganizer;
 import com.android.wm.shell.ShellTestCase;
 import com.android.wm.shell.TestShellExecutor;
@@ -188,6 +196,72 @@
     }
 
     @Test
+    @EnableFlags({Flags.FLAG_ENABLE_BUBBLE_TO_FULLSCREEN, Flags.FLAG_ENABLE_CREATE_ANY_BUBBLE})
+    public void testDragTaskToBubbleOverHome_notifiesHomeIsVisible() throws RemoteException {
+        ActivityManager.RunningTaskInfo homeTask = createTaskInfo(1, ACTIVITY_TYPE_HOME);
+        ActivityManager.RunningTaskInfo bubbleTask = createTaskInfo(2, ACTIVITY_TYPE_STANDARD);
+
+        TransitionInfo startDragTransition =
+                new TransitionInfoBuilder(TRANSIT_DESKTOP_MODE_START_DRAG_TO_DESKTOP)
+                        .addChange(TRANSIT_TO_FRONT, homeTask)
+                        .addChange(TRANSIT_TO_BACK, bubbleTask)
+                        .build();
+
+        // Start drag to desktop which brings home to front
+        mHomeTransitionObserver.onTransitionReady(new Binder(), startDragTransition,
+                MockTransactionPool.create(), MockTransactionPool.create());
+        // Does not notify home visibility yet
+        verify(mListener, never()).onHomeVisibilityChanged(anyBoolean());
+
+        TransitionInfo convertToBubbleTransition =
+                new TransitionInfoBuilder(TRANSIT_CONVERT_TO_BUBBLE)
+                        .addChange(TRANSIT_TO_FRONT, bubbleTask)
+                        .build();
+
+        // Convert to bubble. Transition does not include changes for home task
+        mHomeTransitionObserver.onTransitionReady(new Binder(), convertToBubbleTransition,
+                MockTransactionPool.create(), MockTransactionPool.create());
+
+        // Notifies home visibility change that was pending from the start of drag
+        verify(mListener).onHomeVisibilityChanged(true);
+    }
+
+    @Test
+    @EnableFlags({Flags.FLAG_ENABLE_BUBBLE_TO_FULLSCREEN, Flags.FLAG_ENABLE_CREATE_ANY_BUBBLE})
+    public void testDragTaskToBubbleOverOtherTask_notifiesHomeIsNotVisible()
+            throws RemoteException {
+        ActivityManager.RunningTaskInfo homeTask = createTaskInfo(1, ACTIVITY_TYPE_HOME);
+        ActivityManager.RunningTaskInfo bubbleTask = createTaskInfo(2, ACTIVITY_TYPE_STANDARD);
+        ActivityManager.RunningTaskInfo otherTask = createTaskInfo(3, ACTIVITY_TYPE_STANDARD);
+
+        TransitionInfo startDragTransition =
+                new TransitionInfoBuilder(TRANSIT_DESKTOP_MODE_START_DRAG_TO_DESKTOP)
+                        .addChange(TRANSIT_TO_FRONT, homeTask)
+                        .addChange(TRANSIT_TO_BACK, bubbleTask)
+                        .build();
+
+        // Start drag to desktop which brings home to front
+        mHomeTransitionObserver.onTransitionReady(new Binder(), startDragTransition,
+                MockTransactionPool.create(), MockTransactionPool.create());
+        // Does not notify home visibility yet
+        verify(mListener, never()).onHomeVisibilityChanged(anyBoolean());
+
+        TransitionInfo convertToBubbleTransition =
+                new TransitionInfoBuilder(TRANSIT_CONVERT_TO_BUBBLE)
+                        .addChange(TRANSIT_TO_FRONT, bubbleTask)
+                        .addChange(TRANSIT_TO_FRONT, otherTask)
+                        .addChange(TRANSIT_TO_BACK, homeTask)
+                        .build();
+
+        // Convert to bubble. Transition includes home task to back which updates home visibility
+        mHomeTransitionObserver.onTransitionReady(new Binder(), convertToBubbleTransition,
+                MockTransactionPool.create(), MockTransactionPool.create());
+
+        // Notifies home visibility change due to home moving to back in the second transition
+        verify(mListener).onHomeVisibilityChanged(false);
+    }
+
+    @Test
     public void testHomeActivityWithBackGestureNotifiesHomeIsVisibleAfterClose()
             throws RemoteException {
         TransitionInfo info = mock(TransitionInfo.class);
@@ -227,4 +301,14 @@
         when(change.getMode()).thenReturn(mode);
         taskInfo.isRunning = isRunning;
     }
+
+    private static ActivityManager.RunningTaskInfo createTaskInfo(int taskId, int activityType) {
+        ActivityManager.RunningTaskInfo taskInfo = new ActivityManager.RunningTaskInfo();
+        taskInfo.taskId = taskId;
+        taskInfo.topActivityType = activityType;
+        taskInfo.configuration.windowConfiguration.setActivityType(activityType);
+        taskInfo.token = mock(WindowContainerToken.class);
+        taskInfo.isRunning = true;
+        return taskInfo;
+    }
 }
diff --git a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/windowdecor/DesktopModeWindowDecorViewModelTestsBase.kt b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/windowdecor/DesktopModeWindowDecorViewModelTestsBase.kt
index 8cccdb2..81dfaed 100644
--- a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/windowdecor/DesktopModeWindowDecorViewModelTestsBase.kt
+++ b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/windowdecor/DesktopModeWindowDecorViewModelTestsBase.kt
@@ -52,6 +52,7 @@
 import com.android.wm.shell.common.DisplayController
 import com.android.wm.shell.common.DisplayInsetsController
 import com.android.wm.shell.common.DisplayLayout
+import com.android.wm.shell.common.MultiDisplayDragMoveIndicatorController
 import com.android.wm.shell.common.MultiInstanceHelper
 import com.android.wm.shell.common.SyncTransactionQueue
 import com.android.wm.shell.desktopmode.DesktopActivityOrientationChangeHandler
@@ -138,6 +139,8 @@
     protected val mockFreeformTaskTransitionStarter = mock<FreeformTaskTransitionStarter>()
     protected val mockActivityOrientationChangeHandler =
         mock<DesktopActivityOrientationChangeHandler>()
+    protected val mockMultiDisplayDragMoveIndicatorController =
+        mock<MultiDisplayDragMoveIndicatorController>()
     protected val mockInputManager = mock<InputManager>()
     private val mockTaskPositionerFactory =
         mock<DesktopModeWindowDecorViewModel.TaskPositionerFactory>()
@@ -229,6 +232,7 @@
             mockRecentsTransitionHandler,
             desktopModeCompatPolicy,
             mockTilingWindowDecoration,
+            mockMultiDisplayDragMoveIndicatorController,
         )
         desktopModeWindowDecorViewModel.setSplitScreenController(mockSplitScreenController)
         whenever(mockDisplayController.getDisplayLayout(any())).thenReturn(mockDisplayLayout)
@@ -243,6 +247,7 @@
                 any(),
                 any(),
                 any(),
+                any(),
                 any()
             )
         )
diff --git a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/windowdecor/MultiDisplayVeiledResizeTaskPositionerTest.kt b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/windowdecor/MultiDisplayVeiledResizeTaskPositionerTest.kt
index 937938d..a6b0770 100644
--- a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/windowdecor/MultiDisplayVeiledResizeTaskPositionerTest.kt
+++ b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/windowdecor/MultiDisplayVeiledResizeTaskPositionerTest.kt
@@ -41,6 +41,7 @@
 import com.android.wm.shell.ShellTestCase
 import com.android.wm.shell.common.DisplayController
 import com.android.wm.shell.common.DisplayLayout
+import com.android.wm.shell.common.MultiDisplayDragMoveIndicatorController
 import com.android.wm.shell.common.MultiDisplayTestUtil
 import com.android.wm.shell.transition.Transitions
 import com.android.wm.shell.transition.Transitions.TransitionFinishCallback
@@ -62,8 +63,8 @@
 import org.mockito.Mockito.never
 import org.mockito.Mockito.times
 import org.mockito.Mockito.verify
-import org.mockito.Mockito.`when` as whenever
 import org.mockito.Mockito.`when`
+import org.mockito.Mockito.`when` as whenever
 import org.mockito.MockitoAnnotations
 
 /**
@@ -93,7 +94,8 @@
     @Mock private lateinit var mockTransitions: Transitions
     @Mock private lateinit var mockInteractionJankMonitor: InteractionJankMonitor
     @Mock private lateinit var mockSurfaceControl: SurfaceControl
-
+    @Mock private lateinit var mockMultiDisplayDragMoveIndicatorController:
+            MultiDisplayDragMoveIndicatorController
     private lateinit var resources: TestableResources
     private lateinit var spyDisplayLayout0: DisplayLayout
     private lateinit var spyDisplayLayout1: DisplayLayout
@@ -170,10 +172,11 @@
                 mockDesktopWindowDecoration,
                 mockDisplayController,
                 mockDragEventListener,
-                mockTransactionFactory,
+                { mockTransaction },
                 mockTransitions,
                 mockInteractionJankMonitor,
                 mainHandler,
+                mockMultiDisplayDragMoveIndicatorController,
             )
     }
 
diff --git a/libs/hostgraphics/include/gui/BufferItemConsumer.h b/libs/hostgraphics/include/gui/BufferItemConsumer.h
index c259411..5c96c82 100644
--- a/libs/hostgraphics/include/gui/BufferItemConsumer.h
+++ b/libs/hostgraphics/include/gui/BufferItemConsumer.h
@@ -17,6 +17,8 @@
 #ifndef ANDROID_GUI_BUFFERITEMCONSUMER_H
 #define ANDROID_GUI_BUFFERITEMCONSUMER_H
 
+#include <com_android_graphics_libgui_flags.h>
+#include <gui/BufferQueue.h>
 #include <gui/ConsumerBase.h>
 #include <gui/IGraphicBufferConsumer.h>
 #include <utils/RefBase.h>
@@ -26,9 +28,22 @@
 class BufferItemConsumer : public ConsumerBase {
 public:
     BufferItemConsumer(const sp<IGraphicBufferConsumer>& consumer, uint64_t consumerUsage,
-                       int bufferCount, bool controlledByApp)
+                       int bufferCount = -1, bool controlledByApp = false)
           : mConsumer(consumer) {}
 
+#if COM_ANDROID_GRAPHICS_LIBGUI_FLAGS(WB_CONSUMER_BASE_OWNS_BQ)
+    BufferItemConsumer(uint64_t consumerUsage, int bufferCount = -1,
+                       bool controlledByApp = false, bool isConsumerSurfaceFlinger = false) {
+        sp<IGraphicBufferProducer> producer;
+        BufferQueue::createBufferQueue(&producer, &mConsumer);
+        mSurface = sp<Surface>::make(producer, controlledByApp);
+    }
+
+    status_t setConsumerIsProtected(bool isProtected) {
+        return OK;
+    }
+#endif // COM_ANDROID_GRAPHICS_LIBGUI_FLAGS(WB_CONSUMER_BASE_OWNS_BQ)
+
     status_t acquireBuffer(BufferItem* item, nsecs_t presentWhen, bool waitForFence = true) {
         return mConsumer->acquireBuffer(item, presentWhen, 0);
     }
@@ -71,8 +86,20 @@
         return OK;
     }
 
+#if COM_ANDROID_GRAPHICS_LIBGUI_FLAGS(WB_CONSUMER_BASE_OWNS_BQ)
+// Returns a Surface that can be used as the producer for this consumer.
+    sp<Surface> getSurface() const {
+        return mSurface;
+    }
+#endif // COM_ANDROID_GRAPHICS_LIBGUI_FLAGS(WB_CONSUMER_BASE_OWNS_BQ)
+
 private:
     sp<IGraphicBufferConsumer> mConsumer;
+#if COM_ANDROID_GRAPHICS_LIBGUI_FLAGS(WB_CONSUMER_BASE_OWNS_BQ)
+        // This Surface wraps the IGraphicBufferConsumer created for this
+    // ConsumerBase.
+    sp<Surface> mSurface;
+#endif // COM_ANDROID_GRAPHICS_LIBGUI_FLAGS(WB_CONSUMER_BASE_OWNS_BQ)
 };
 
 } // namespace android
diff --git a/libs/hostgraphics/include/gui/Surface.h b/libs/hostgraphics/include/gui/Surface.h
index 2774f89..e268ce6 100644
--- a/libs/hostgraphics/include/gui/Surface.h
+++ b/libs/hostgraphics/include/gui/Surface.h
@@ -34,6 +34,10 @@
         ANativeWindow::query = hook_query;
     }
 
+    sp<IGraphicBufferProducer> getIGraphicBufferProducer() const {
+        return mBufferProducer;
+    }
+
     static bool isValid(const sp<Surface>& surface) {
         return surface != nullptr;
     }
diff --git a/libs/hostgraphics/include/ui/Fence.h b/libs/hostgraphics/include/ui/Fence.h
index 187c311..3364b8ae 100644
--- a/libs/hostgraphics/include/ui/Fence.h
+++ b/libs/hostgraphics/include/ui/Fence.h
@@ -60,6 +60,10 @@
         return 0;
     }
 
+    int get() const {
+        return 0;
+    }
+
     inline Status getStatus() {
         // The sync_wait call underlying wait() has been measured to be
         // significantly faster than the sync_fence_info call underlying
diff --git a/libs/hwui/Android.bp b/libs/hwui/Android.bp
index bb2a53b..38ac8ab 100644
--- a/libs/hwui/Android.bp
+++ b/libs/hwui/Android.bp
@@ -233,6 +233,14 @@
 }
 
 filegroup {
+    name: "framework-graphics-ravenwood-policies",
+    srcs: [
+        "framework-graphics-ravenwood-policies.txt",
+    ],
+    visibility: ["//frameworks/base/ravenwood"],
+}
+
+filegroup {
     name: "framework-graphics-srcs",
     srcs: [
         "apex/java/**/*.java",
@@ -461,6 +469,10 @@
         },
         linux: {
             srcs: ["platform/linux/utils/SharedLib.cpp"],
+            shared_libs: [
+                "libbinder",
+                "libbinder_ndk",
+            ],
         },
         darwin: {
             srcs: ["platform/darwin/utils/SharedLib.cpp"],
diff --git a/libs/hwui/framework-graphics-ravenwood-policies.txt b/libs/hwui/framework-graphics-ravenwood-policies.txt
new file mode 100644
index 0000000..7296225
--- /dev/null
+++ b/libs/hwui/framework-graphics-ravenwood-policies.txt
@@ -0,0 +1 @@
+class android.graphics.ColorMatrix keepclass
diff --git a/libs/hwui/jni/Bitmap.cpp b/libs/hwui/jni/Bitmap.cpp
index cfde0b2..27d4ac7 100644
--- a/libs/hwui/jni/Bitmap.cpp
+++ b/libs/hwui/jni/Bitmap.cpp
@@ -613,7 +613,7 @@
 ///////////////////////////////////////////////////////////////////////////////
 
 // TODO: Move somewhere else
-#ifdef __ANDROID__  // Layoutlib does not support parcel
+#ifdef __linux__  // Only Linux support parcel
 #define ON_ERROR_RETURN(X) \
     if ((error = (X)) != STATUS_OK) return error
 
@@ -717,7 +717,7 @@
 
 #undef ON_ERROR_RETURN
 
-#endif // __ANDROID__ // Layoutlib does not support parcel
+#endif // __linux__ // Only Linux support parcel
 
 // This is the maximum possible size because the SkColorSpace must be
 // representable (and therefore serializable) using a matrix and numerical
@@ -733,7 +733,7 @@
 }
 
 static jobject Bitmap_createFromParcel(JNIEnv* env, jobject, jobject parcel) {
-#ifdef __ANDROID__ // Layoutlib does not support parcel
+#ifdef __linux__ // Only Linux support parcel
     if (parcel == NULL) {
         jniThrowNullPointerException(env, "parcel cannot be null");
         return NULL;
@@ -836,14 +836,14 @@
     return createBitmap(env, nativeBitmap.release(), getPremulBitmapCreateFlags(isMutable), nullptr,
                         nullptr, density, sourceId);
 #else
-    jniThrowRuntimeException(env, "Cannot use parcels outside of Android");
+    jniThrowRuntimeException(env, "Cannot use parcels outside of Linux");
     return NULL;
 #endif
 }
 
 static jboolean Bitmap_writeToParcel(JNIEnv* env, jobject, jlong bitmapHandle, jint density,
                                      jobject parcel) {
-#ifdef __ANDROID__ // Layoutlib does not support parcel
+#ifdef __linux__ // Only Linux support parcel
     if (parcel == NULL) {
         ALOGD("------- writeToParcel null parcel\n");
         return JNI_FALSE;
@@ -901,7 +901,7 @@
     }
     return JNI_TRUE;
 #else
-    doThrowRE(env, "Cannot use parcels outside of Android");
+    doThrowRE(env, "Cannot use parcels outside of Linux");
     return JNI_FALSE;
 #endif
 }
diff --git a/libs/hwui/jni/Region.cpp b/libs/hwui/jni/Region.cpp
index 1e064b8..76986eeb 100644
--- a/libs/hwui/jni/Region.cpp
+++ b/libs/hwui/jni/Region.cpp
@@ -18,7 +18,7 @@
 #include "SkPath.h"
 #include "GraphicsJNI.h"
 
-#ifdef __ANDROID__ // Layoutlib does not support parcel
+#ifdef __linux__ // Only Linux support parcel
 #include <android/binder_parcel.h>
 #include <android/binder_parcel_jni.h>
 #include <android/binder_parcel_utils.h>
@@ -202,7 +202,7 @@
 
 static jlong Region_createFromParcel(JNIEnv* env, jobject clazz, jobject parcel)
 {
-#ifdef __ANDROID__ // Layoutlib does not support parcel
+#ifdef __linux__ // Only Linux support parcel
     if (parcel == nullptr) {
         return 0;
     }
@@ -230,7 +230,7 @@
 
 static jboolean Region_writeToParcel(JNIEnv* env, jobject clazz, jlong regionHandle, jobject parcel)
 {
-#ifdef __ANDROID__ // Layoutlib does not support parcel
+#ifdef __linux__ // Only Linux support parcel
     const SkRegion* region = reinterpret_cast<SkRegion*>(regionHandle);
     if (parcel == nullptr) {
         return JNI_FALSE;
diff --git a/libs/hwui/jni/ScopedParcel.cpp b/libs/hwui/jni/ScopedParcel.cpp
index 95e4e01..52cd988 100644
--- a/libs/hwui/jni/ScopedParcel.cpp
+++ b/libs/hwui/jni/ScopedParcel.cpp
@@ -15,7 +15,7 @@
  */
 #include "ScopedParcel.h"
 
-#ifdef __ANDROID__  // Layoutlib does not support parcel
+#ifdef __linux__  // Only Linux support parcel
 
 using namespace android;
 
@@ -92,4 +92,4 @@
         AParcel_writeByteArray(mParcel, nullptr, -1);
     }
 }
-#endif  // __ANDROID__ // Layoutlib does not support parcel
+#endif  // __linux__  // Only Linux support parcel
diff --git a/libs/hwui/jni/ScopedParcel.h b/libs/hwui/jni/ScopedParcel.h
index f2f138f..f2b793a 100644
--- a/libs/hwui/jni/ScopedParcel.h
+++ b/libs/hwui/jni/ScopedParcel.h
@@ -15,7 +15,7 @@
  */
 #include "SkData.h"
 
-#ifdef __ANDROID__  // Layoutlib does not support parcel
+#ifdef __linux__  // Only Linux support parcel
 #include <android-base/unique_fd.h>
 #include <android/binder_parcel.h>
 #include <android/binder_parcel_jni.h>
@@ -64,4 +64,4 @@
     ASHMEM,
 };
 
-#endif  // __ANDROID__ // Layoutlib does not support parcel
\ No newline at end of file
+#endif  // __linux__ // Only Linux support parcel
diff --git a/libs/hwui/jni/graphics_jni_helpers.h b/libs/hwui/jni/graphics_jni_helpers.h
index 91db134..ff26ec1 100644
--- a/libs/hwui/jni/graphics_jni_helpers.h
+++ b/libs/hwui/jni/graphics_jni_helpers.h
@@ -21,6 +21,7 @@
 #include <nativehelper/JNIPlatformHelp.h>
 #include <nativehelper/scoped_local_ref.h>
 #include <nativehelper/scoped_utf_chars.h>
+#include <nativehelper/scoped_primitive_array.h>
 #include <string>
 
 // Host targets (layoutlib) do not differentiate between regular and critical native methods,
diff --git a/media/java/android/media/AudioDeviceVolumeManager.java b/media/java/android/media/AudioDeviceVolumeManager.java
index 892a861..56d3df3 100644
--- a/media/java/android/media/AudioDeviceVolumeManager.java
+++ b/media/java/android/media/AudioDeviceVolumeManager.java
@@ -16,6 +16,9 @@
 
 package android.media;
 
+import static android.annotation.SystemApi.Client.MODULE_LIBRARIES;
+import static android.media.audio.Flags.FLAG_UNIFY_ABSOLUTE_VOLUME_MANAGEMENT;
+
 import static com.android.media.flags.Flags.FLAG_ENABLE_AUDIO_INPUT_DEVICE_ROUTING_AND_VOLUME_CONTROL;
 
 import android.Manifest;
@@ -91,6 +94,8 @@
      * @see #setDeviceAbsoluteVolumeBehavior(AudioDeviceAttributes, VolumeInfo, boolean, Executor,
      *          OnAudioDeviceVolumeChangedListener)
      */
+    @SystemApi(client = MODULE_LIBRARIES)
+    @FlaggedApi(FLAG_UNIFY_ABSOLUTE_VOLUME_MANAGEMENT)
     public interface OnAudioDeviceVolumeChangedListener {
         /**
          * Called the device for the given audio device has changed.
@@ -203,6 +208,30 @@
      * volume updates to apply on that device
      * @param device the audio device set to absolute volume mode
      * @param volume the type of volume this device responds to
+     * @param executor the Executor used for receiving volume updates through the listener
+     * @param vclistener the callback for volume updates
+     */
+    @SystemApi(client = MODULE_LIBRARIES)
+    @RequiresPermission(anyOf = { android.Manifest.permission.MODIFY_AUDIO_ROUTING,
+            android.Manifest.permission.MODIFY_AUDIO_SETTINGS_PRIVILEGED,
+            android.Manifest.permission.BLUETOOTH_PRIVILEGED,
+            android.Manifest.permission.BLUETOOTH_STACK})
+    @FlaggedApi(FLAG_UNIFY_ABSOLUTE_VOLUME_MANAGEMENT)
+    public void setDeviceAbsoluteVolumeBehavior(
+            @NonNull AudioDeviceAttributes device,
+            @NonNull VolumeInfo volume,
+            @NonNull @CallbackExecutor Executor executor,
+            @NonNull OnAudioDeviceVolumeChangedListener vclistener) {
+        setDeviceAbsoluteVolumeBehavior(device, volume, /*handlesVolumeAdjustment=*/false, executor,
+                vclistener);
+    }
+
+    /**
+     * @hide
+     * Configures a device to use absolute volume model, and registers a listener for receiving
+     * volume updates to apply on that device
+     * @param device the audio device set to absolute volume mode
+     * @param volume the type of volume this device responds to
      * @param handlesVolumeAdjustment whether the controller handles volume adjustments separately
      * from volume changes. If true, adjustments from {@link AudioManager#adjustStreamVolume}
      * will be sent via {@link OnAudioDeviceVolumeChangedListener#onAudioDeviceVolumeAdjusted}.
@@ -210,7 +239,7 @@
      * @param vclistener the callback for volume updates
      */
     @RequiresPermission(anyOf = { android.Manifest.permission.MODIFY_AUDIO_ROUTING,
-            android.Manifest.permission.BLUETOOTH_PRIVILEGED })
+            android.Manifest.permission.BLUETOOTH_PRIVILEGED})
     public void setDeviceAbsoluteVolumeBehavior(
             @NonNull AudioDeviceAttributes device,
             @NonNull VolumeInfo volume,
@@ -229,6 +258,30 @@
      * registers a listener for receiving volume updates to apply on that device
      * @param device the audio device set to absolute multi-volume mode
      * @param volumes the list of volumes the given device responds to
+     * @param executor the Executor used for receiving volume updates through the listener
+     * @param vclistener the callback for volume updates
+     */
+    @SystemApi(client = MODULE_LIBRARIES)
+    @RequiresPermission(anyOf = { android.Manifest.permission.MODIFY_AUDIO_ROUTING,
+            android.Manifest.permission.MODIFY_AUDIO_SETTINGS_PRIVILEGED,
+            android.Manifest.permission.BLUETOOTH_PRIVILEGED,
+            android.Manifest.permission.BLUETOOTH_STACK})
+    @FlaggedApi(FLAG_UNIFY_ABSOLUTE_VOLUME_MANAGEMENT)
+    public void setDeviceAbsoluteMultiVolumeBehavior(
+            @NonNull AudioDeviceAttributes device,
+            @NonNull List<VolumeInfo> volumes,
+            @NonNull @CallbackExecutor Executor executor,
+            @NonNull OnAudioDeviceVolumeChangedListener vclistener) {
+        setDeviceAbsoluteMultiVolumeBehavior(device, volumes, /*handlesVolumeAdjustment=*/false,
+                executor, vclistener);
+    }
+
+    /**
+     * @hide
+     * Configures a device to use absolute volume model applied to different volume types, and
+     * registers a listener for receiving volume updates to apply on that device
+     * @param device the audio device set to absolute multi-volume mode
+     * @param volumes the list of volumes the given device responds to
      * @param handlesVolumeAdjustment whether the controller handles volume adjustments separately
      * from volume changes. If true, adjustments from {@link AudioManager#adjustStreamVolume}
      * will be sent via {@link OnAudioDeviceVolumeChangedListener#onAudioDeviceVolumeAdjusted}.
@@ -236,7 +289,7 @@
      * @param vclistener the callback for volume updates
      */
     @RequiresPermission(anyOf = { android.Manifest.permission.MODIFY_AUDIO_ROUTING,
-            android.Manifest.permission.BLUETOOTH_PRIVILEGED })
+            android.Manifest.permission.BLUETOOTH_PRIVILEGED})
     public void setDeviceAbsoluteMultiVolumeBehavior(
             @NonNull AudioDeviceAttributes device,
             @NonNull List<VolumeInfo> volumes,
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/ActionButtonsPreference/res/layout-v35/settingslib_expressive_action_buttons.xml b/packages/SettingsLib/ActionButtonsPreference/res/layout-v36/settingslib_expressive_action_buttons.xml
similarity index 100%
rename from packages/SettingsLib/ActionButtonsPreference/res/layout-v35/settingslib_expressive_action_buttons.xml
rename to packages/SettingsLib/ActionButtonsPreference/res/layout-v36/settingslib_expressive_action_buttons.xml
diff --git a/packages/SettingsLib/ActionButtonsPreference/res/values-v35/styles_expressive.xml b/packages/SettingsLib/ActionButtonsPreference/res/values-v36/styles_expressive.xml
similarity index 100%
rename from packages/SettingsLib/ActionButtonsPreference/res/values-v35/styles_expressive.xml
rename to packages/SettingsLib/ActionButtonsPreference/res/values-v36/styles_expressive.xml
diff --git a/packages/SettingsLib/BannerMessagePreference/Android.bp b/packages/SettingsLib/BannerMessagePreference/Android.bp
index 182daeb..203a3bf 100644
--- a/packages/SettingsLib/BannerMessagePreference/Android.bp
+++ b/packages/SettingsLib/BannerMessagePreference/Android.bp
@@ -31,5 +31,6 @@
     apex_available: [
         "//apex_available:platform",
         "com.android.healthfitness",
+        "com.android.permission",
     ],
 }
diff --git a/packages/SettingsLib/BannerMessagePreference/res/drawable-v35/settingslib_expressive_card_background.xml b/packages/SettingsLib/BannerMessagePreference/res/drawable-v36/settingslib_expressive_card_background.xml
similarity index 100%
rename from packages/SettingsLib/BannerMessagePreference/res/drawable-v35/settingslib_expressive_card_background.xml
rename to packages/SettingsLib/BannerMessagePreference/res/drawable-v36/settingslib_expressive_card_background.xml
diff --git a/packages/SettingsLib/BannerMessagePreference/res/drawable-v36/settingslib_resolved_banner_avd.xml b/packages/SettingsLib/BannerMessagePreference/res/drawable-v36/settingslib_resolved_banner_avd.xml
new file mode 100644
index 0000000..c999de7
--- /dev/null
+++ b/packages/SettingsLib/BannerMessagePreference/res/drawable-v36/settingslib_resolved_banner_avd.xml
@@ -0,0 +1,157 @@
+<!--
+  ~ Copyright (C) 2025 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.
+  -->
+<animated-vector xmlns:android="http://schemas.android.com/apk/res/android"
+    xmlns:aapt="http://schemas.android.com/aapt">
+    <aapt:attr name="android:drawable">
+        <vector
+            android:width="112dp"
+            android:height="112dp"
+            android:viewportHeight="112"
+            android:viewportWidth="112">
+            <group android:name="_R_G">
+                <group
+                    android:name="_R_G_L_1_G"
+                    android:translateX="56.5"
+                    android:translateY="56.625">
+                    <path
+                        android:name="_R_G_L_1_G_D_0_P_0"
+                        android:fillAlpha="0"
+                        android:fillColor="?android:attr/textColorPrimary"
+                        android:fillType="nonZero"
+                        android:pathData=" M-14.75 -0.59 C-14.75,-0.59 -15.32,-1.16 -15.32,-1.16 C-15.32,-1.16 -14.67,-0.73 -14.67,-0.73 C-14.67,-0.73 -14.39,-0.44 -14.39,-0.44 C-14.39,-0.44 -17.32,2.5 -17.32,2.5 C-17.32,2.5 -17.59,2.23 -17.59,2.23 C-17.59,2.23 -14.75,-0.59 -14.75,-0.59c "
+                        android:trimPathEnd="1"
+                        android:trimPathOffset="0"
+                        android:trimPathStart="0" />
+                </group>
+                <group
+                    android:name="_R_G_L_0_G"
+                    android:rotation="-90"
+                    android:translateX="56"
+                    android:translateY="56">
+                    <path
+                        android:name="_R_G_L_0_G_D_0_P_0"
+                        android:pathData=" M53.5 0 C53.5,-29.53 29.53,-53.5 0,-53.5 C0,-53.5 0,-53.5 0,-53.5 C-29.53,-53.5 -53.5,-29.53 -53.5,0 C-53.5,0 -53.5,0 -53.5,0 C-53.5,29.53 -29.53,53.5 0,53.5 C0,53.5 0,53.5 0,53.5 C29.53,53.5 53.5,29.53 53.5,0 C53.5,0 53.5,0 53.5,0c "
+                        android:strokeAlpha="1"
+                        android:strokeColor="?android:attr/textColorTertiary"
+                        android:strokeLineCap="round"
+                        android:strokeLineJoin="round"
+                        android:strokeWidth="5"
+                        android:trimPathEnd="0"
+                        android:trimPathOffset="0"
+                        android:trimPathStart="0" />
+                </group>
+            </group>
+            <group android:name="time_group" />
+        </vector>
+    </aapt:attr>
+    <target android:name="_R_G_L_1_G_D_0_P_0">
+        <aapt:attr name="android:animation">
+            <set android:ordering="together">
+                <objectAnimator
+                    android:duration="400"
+                    android:propertyName="fillAlpha"
+                    android:startOffset="0"
+                    android:valueFrom="0"
+                    android:valueTo="0"
+                    android:valueType="floatType">
+                    <aapt:attr name="android:interpolator">
+                        <pathInterpolator android:pathData="M 0.0,0.0 c0.167,0.167 0.833,0.833 1.0,1.0" />
+                    </aapt:attr>
+                </objectAnimator>
+                <objectAnimator
+                    android:duration="17"
+                    android:propertyName="fillAlpha"
+                    android:startOffset="400"
+                    android:valueFrom="0"
+                    android:valueTo="1"
+                    android:valueType="floatType">
+                    <aapt:attr name="android:interpolator">
+                        <pathInterpolator android:pathData="M 0.0,0.0 c0.167,0.167 0.833,0.833 1.0,1.0" />
+                    </aapt:attr>
+                </objectAnimator>
+            </set>
+        </aapt:attr>
+    </target>
+    <target android:name="_R_G_L_1_G_D_0_P_0">
+        <aapt:attr name="android:animation">
+            <set android:ordering="together">
+                <objectAnimator
+                    android:duration="400"
+                    android:propertyName="pathData"
+                    android:startOffset="0"
+                    android:valueFrom="M-14.75 -0.59 C-14.75,-0.59 -15.32,-1.16 -15.32,-1.16 C-15.32,-1.16 -14.67,-0.73 -14.67,-0.73 C-14.67,-0.73 -14.39,-0.44 -14.39,-0.44 C-14.39,-0.44 -17.32,2.5 -17.32,2.5 C-17.32,2.5 -17.59,2.23 -17.59,2.23 C-17.59,2.23 -14.75,-0.59 -14.75,-0.59c "
+                    android:valueTo="M-14.75 -0.59 C-14.75,-0.59 -15.32,-1.16 -15.32,-1.16 C-15.32,-1.16 -14.67,-0.73 -14.67,-0.73 C-14.67,-0.73 -14.39,-0.44 -14.39,-0.44 C-14.39,-0.44 -17.32,2.5 -17.32,2.5 C-17.32,2.5 -17.59,2.23 -17.59,2.23 C-17.59,2.23 -14.75,-0.59 -14.75,-0.59c "
+                    android:valueType="pathType">
+                    <aapt:attr name="android:interpolator">
+                        <pathInterpolator android:pathData="M 0.0,0.0 c0.167,0.167 0.833,0.833 1.0,1.0" />
+                    </aapt:attr>
+                </objectAnimator>
+                <objectAnimator
+                    android:duration="83"
+                    android:propertyName="pathData"
+                    android:startOffset="400"
+                    android:valueFrom="M-14.75 -0.59 C-14.75,-0.59 -15.32,-1.16 -15.32,-1.16 C-15.32,-1.16 -14.67,-0.73 -14.67,-0.73 C-14.67,-0.73 -14.39,-0.44 -14.39,-0.44 C-14.39,-0.44 -17.32,2.5 -17.32,2.5 C-17.32,2.5 -17.59,2.23 -17.59,2.23 C-17.59,2.23 -14.75,-0.59 -14.75,-0.59c "
+                    android:valueTo="M-14.75 -0.59 C-14.75,-0.59 -6.41,7.75 -6.41,7.75 C-6.41,7.75 -6.3,7.65 -6.3,7.65 C-6.3,7.65 -3.48,10.47 -3.48,10.47 C-3.48,10.47 -6.41,13.41 -6.41,13.41 C-6.41,13.41 -17.59,2.23 -17.59,2.23 C-17.59,2.23 -14.75,-0.59 -14.75,-0.59c "
+                    android:valueType="pathType">
+                    <aapt:attr name="android:interpolator">
+                        <pathInterpolator android:pathData="M 0.0,0.0 c0.167,0.167 0.833,0.833 1.0,1.0" />
+                    </aapt:attr>
+                </objectAnimator>
+                <objectAnimator
+                    android:duration="167"
+                    android:propertyName="pathData"
+                    android:startOffset="483"
+                    android:valueFrom="M-14.75 -0.59 C-14.75,-0.59 -6.41,7.75 -6.41,7.75 C-6.41,7.75 -6.3,7.65 -6.3,7.65 C-6.3,7.65 -3.48,10.47 -3.48,10.47 C-3.48,10.47 -6.41,13.41 -6.41,13.41 C-6.41,13.41 -17.59,2.23 -17.59,2.23 C-17.59,2.23 -14.75,-0.59 -14.75,-0.59c "
+                    android:valueTo="M-14.75 -0.59 C-14.75,-0.59 -6.41,7.75 -6.41,7.75 C-6.41,7.75 14.77,-13.41 14.77,-13.41 C14.77,-13.41 17.59,-10.59 17.59,-10.59 C17.59,-10.59 -6.41,13.41 -6.41,13.41 C-6.41,13.41 -17.59,2.23 -17.59,2.23 C-17.59,2.23 -14.75,-0.59 -14.75,-0.59c "
+                    android:valueType="pathType">
+                    <aapt:attr name="android:interpolator">
+                        <pathInterpolator android:pathData="M 0.0,0.0 c0.167,0.167 0.667,1 1.0,1.0" />
+                    </aapt:attr>
+                </objectAnimator>
+            </set>
+        </aapt:attr>
+    </target>
+    <target android:name="_R_G_L_0_G_D_0_P_0">
+        <aapt:attr name="android:animation">
+            <set android:ordering="together">
+                <objectAnimator
+                    android:duration="500"
+                    android:propertyName="trimPathEnd"
+                    android:startOffset="0"
+                    android:valueFrom="0"
+                    android:valueTo="1"
+                    android:valueType="floatType">
+                    <aapt:attr name="android:interpolator">
+                        <pathInterpolator android:pathData="M 0.0,0.0 c0.2,0 0,1 1.0,1.0" />
+                    </aapt:attr>
+                </objectAnimator>
+            </set>
+        </aapt:attr>
+    </target>
+    <target android:name="time_group">
+        <aapt:attr name="android:animation">
+            <set android:ordering="together">
+                <objectAnimator
+                    android:duration="667"
+                    android:propertyName="translateX"
+                    android:startOffset="0"
+                    android:valueFrom="0"
+                    android:valueTo="1"
+                    android:valueType="floatType" />
+            </set>
+        </aapt:attr>
+    </target>
+</animated-vector>
\ No newline at end of file
diff --git a/packages/SettingsLib/BannerMessagePreference/res/layout-v35/settingslib_expressive_banner_message.xml b/packages/SettingsLib/BannerMessagePreference/res/layout-v35/settingslib_expressive_banner_message.xml
deleted file mode 100644
index b10ef6e..0000000
--- a/packages/SettingsLib/BannerMessagePreference/res/layout-v35/settingslib_expressive_banner_message.xml
+++ /dev/null
@@ -1,108 +0,0 @@
-<?xml version="1.0" encoding="utf-8"?>
-<!--
-  Copyright (C) 2025 The Android Open Source Project
-
-  Licensed under the Apache License, Version 2.0 (the "License");
-  you may not use this file except in compliance with the License.
-  You may obtain a copy of the License at
-
-       http://www.apache.org/licenses/LICENSE-2.0
-
-  Unless required by applicable law or agreed to in writing, software
-  distributed under the License is distributed on an "AS IS" BASIS,
-  WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
-  See the License for the specific language governing permissions and
-  limitations under the License.
-  -->
-
-<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
-    android:layout_width="match_parent"
-    android:layout_height="wrap_content"
-    android:paddingEnd="?android:attr/listPreferredItemPaddingEnd"
-    android:paddingStart="?android:attr/listPreferredItemPaddingStart">
-
-    <com.android.settingslib.widget.BannerMessageView
-        android:id="@+id/banner_background"
-        android:layout_width="match_parent"
-        android:layout_height="wrap_content"
-        android:orientation="vertical"
-        style="@style/Banner.Preference.SettingsLib.Expressive">
-
-        <RelativeLayout
-            android:id="@+id/top_row"
-            android:layout_width="match_parent"
-            android:layout_height="wrap_content"
-            android:orientation="horizontal">
-
-            <LinearLayout
-                android:layout_width="match_parent"
-                android:layout_height="wrap_content"
-                android:layout_alignParentStart="true"
-                android:layout_marginEnd="@dimen/settingslib_expressive_space_medium4"
-                android:orientation="vertical">
-                <TextView
-                    android:id="@+id/banner_header"
-                    android:layout_width="match_parent"
-                    android:layout_height="wrap_content"
-                    style="@style/Banner.Header.SettingsLib.Expressive"/>
-
-                <LinearLayout
-                    android:layout_width="wrap_content"
-                    android:layout_height="wrap_content">
-
-                    <ImageView
-                        android:id="@+id/banner_icon"
-                        android:layout_width="@dimen/settingslib_expressive_space_small3"
-                        android:layout_height="@dimen/settingslib_expressive_space_small3"
-                        android:layout_gravity="center_vertical"
-                        android:importantForAccessibility="no"
-                        android:scaleType="fitCenter" />
-
-                    <TextView
-                        android:id="@+id/banner_title"
-                        android:layout_width="match_parent"
-                        android:layout_height="wrap_content"
-                        style="@style/Banner.Title.SettingsLib.Expressive" />
-                </LinearLayout>
-
-                <TextView
-                    android:id="@+id/banner_subtitle"
-                    android:layout_width="match_parent"
-                    android:layout_height="wrap_content"
-                    style="@style/Banner.Subtitle.SettingsLib.Expressive"/>
-            </LinearLayout>
-
-            <ImageButton
-                android:id="@+id/banner_dismiss_btn"
-                android:layout_width="wrap_content"
-                android:layout_height="wrap_content"
-                style="@style/Banner.Dismiss.SettingsLib.Expressive" />
-        </RelativeLayout>
-
-        <TextView
-            android:id="@+id/banner_summary"
-            android:layout_width="wrap_content"
-            android:layout_height="wrap_content"
-            style="@style/Banner.Summary.SettingsLib.Expressive"/>
-
-        <LinearLayout
-            android:layout_width="match_parent"
-            android:layout_height="wrap_content"
-            android:id="@+id/banner_buttons_frame"
-            android:paddingTop="@dimen/settingslib_expressive_space_extrasmall6"
-            android:orientation="horizontal">
-
-            <com.google.android.material.button.MaterialButton
-                android:id="@+id/banner_negative_btn"
-                android:layout_weight="1"
-                style="@style/Banner.NegativeButton.SettingsLib.Expressive"/>
-            <Space
-                android:layout_width="@dimen/settingslib_expressive_space_extrasmall4"
-                android:layout_height="@dimen/settingslib_expressive_space_small1"/>
-            <com.google.android.material.button.MaterialButton
-                android:id="@+id/banner_positive_btn"
-                android:layout_weight="1"
-                style="@style/Banner.PositiveButton.SettingsLib.Expressive"/>
-        </LinearLayout>
-    </com.android.settingslib.widget.BannerMessageView>
-</LinearLayout>
\ No newline at end of file
diff --git a/packages/SettingsLib/BannerMessagePreference/res/layout-v36/settingslib_expressive_banner_message.xml b/packages/SettingsLib/BannerMessagePreference/res/layout-v36/settingslib_expressive_banner_message.xml
new file mode 100644
index 0000000..c448a2d
--- /dev/null
+++ b/packages/SettingsLib/BannerMessagePreference/res/layout-v36/settingslib_expressive_banner_message.xml
@@ -0,0 +1,131 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!--
+  Copyright (C) 2025 The Android Open Source Project
+
+  Licensed under the Apache License, Version 2.0 (the "License");
+  you may not use this file except in compliance with the License.
+  You may obtain a copy of the License at
+
+       http://www.apache.org/licenses/LICENSE-2.0
+
+  Unless required by applicable law or agreed to in writing, software
+  distributed under the License is distributed on an "AS IS" BASIS,
+  WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+  See the License for the specific language governing permissions and
+  limitations under the License.
+  -->
+
+<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
+    android:layout_width="match_parent"
+    android:layout_height="wrap_content"
+    android:paddingEnd="?android:attr/listPreferredItemPaddingEnd"
+    android:paddingStart="?android:attr/listPreferredItemPaddingStart">
+
+    <com.android.settingslib.widget.BannerMessageView
+        android:id="@+id/banner_background"
+        android:layout_width="match_parent"
+        android:layout_height="wrap_content"
+        android:orientation="vertical"
+        style="@style/Banner.Preference.SettingsLib.Expressive">
+
+        <FrameLayout
+            android:layout_width="match_parent"
+            android:layout_height="wrap_content">
+
+            <LinearLayout
+                android:id="@+id/banner_content"
+                android:layout_width="match_parent"
+                android:layout_height="wrap_content"
+                android:orientation="vertical">
+
+                <RelativeLayout
+                    android:id="@+id/top_row"
+                    android:layout_width="match_parent"
+                    android:layout_height="wrap_content"
+                    android:orientation="horizontal">
+
+                    <LinearLayout
+                        android:layout_width="match_parent"
+                        android:layout_height="wrap_content"
+                        android:layout_alignParentStart="true"
+                        android:layout_marginEnd="@dimen/settingslib_expressive_space_medium4"
+                        android:orientation="vertical">
+                        <TextView
+                            android:id="@+id/banner_header"
+                            android:layout_width="match_parent"
+                            android:layout_height="wrap_content"
+                            style="@style/Banner.Header.SettingsLib.Expressive"/>
+
+                        <LinearLayout
+                            android:layout_width="wrap_content"
+                            android:layout_height="wrap_content">
+
+                            <ImageView
+                                android:id="@+id/banner_icon"
+                                android:layout_width="@dimen/settingslib_expressive_space_small3"
+                                android:layout_height="@dimen/settingslib_expressive_space_small3"
+                                android:layout_gravity="center_vertical"
+                                android:layout_marginEnd="@dimen/settingslib_expressive_space_extrasmall4"
+                                android:importantForAccessibility="no"
+                                android:scaleType="fitCenter" />
+
+                            <TextView
+                                android:id="@+id/banner_title"
+                                android:layout_width="match_parent"
+                                android:layout_height="wrap_content"
+                                style="@style/Banner.Title.SettingsLib.Expressive" />
+                        </LinearLayout>
+
+                        <TextView
+                            android:id="@+id/banner_subtitle"
+                            android:layout_width="match_parent"
+                            android:layout_height="wrap_content"
+                            style="@style/Banner.Subtitle.SettingsLib.Expressive"/>
+                    </LinearLayout>
+
+                    <ImageButton
+                        android:id="@+id/banner_dismiss_btn"
+                        android:layout_width="wrap_content"
+                        android:layout_height="wrap_content"
+                        android:contentDescription="@string/accessibility_banner_message_dismiss"
+                        style="@style/Banner.Dismiss.SettingsLib.Expressive" />
+                </RelativeLayout>
+
+                <TextView
+                    android:id="@+id/banner_summary"
+                    android:layout_width="wrap_content"
+                    android:layout_height="wrap_content"
+                    style="@style/Banner.Summary.SettingsLib.Expressive"/>
+
+                <LinearLayout
+                    android:layout_width="match_parent"
+                    android:layout_height="wrap_content"
+                    android:id="@+id/banner_buttons_frame"
+                    android:paddingTop="@dimen/settingslib_expressive_space_extrasmall6"
+                    android:orientation="horizontal">
+
+                    <com.google.android.material.button.MaterialButton
+                        android:id="@+id/banner_negative_btn"
+                        android:layout_weight="1"
+                        style="@style/Banner.NegativeButton.SettingsLib.Expressive"/>
+                    <Space
+                        android:id="@+id/banner_button_space"
+                        android:layout_width="@dimen/settingslib_expressive_space_extrasmall4"
+                        android:layout_height="@dimen/settingslib_expressive_space_small1"/>
+                    <com.google.android.material.button.MaterialButton
+                        android:id="@+id/banner_positive_btn"
+                        android:layout_weight="1"
+                        style="@style/Banner.PositiveButton.SettingsLib.Expressive"/>
+                </LinearLayout>
+            </LinearLayout>
+
+            <TextView
+                android:id="@+id/resolved_banner_text"
+                android:layout_width="wrap_content"
+                android:layout_height="wrap_content"
+                android:drawableTop="@drawable/settingslib_resolved_banner_avd"
+                android:visibility="gone"
+                style="@style/Banner.ResolvedText.SettingsLib.Expressive"/>
+        </FrameLayout>
+    </com.android.settingslib.widget.BannerMessageView>
+</LinearLayout>
\ No newline at end of file
diff --git a/packages/SettingsLib/BannerMessagePreference/res/values-v35/styles_expressive.xml b/packages/SettingsLib/BannerMessagePreference/res/values-v36/styles_expressive.xml
similarity index 89%
rename from packages/SettingsLib/BannerMessagePreference/res/values-v35/styles_expressive.xml
rename to packages/SettingsLib/BannerMessagePreference/res/values-v36/styles_expressive.xml
index b864311..09e07cc 100644
--- a/packages/SettingsLib/BannerMessagePreference/res/values-v35/styles_expressive.xml
+++ b/packages/SettingsLib/BannerMessagePreference/res/values-v36/styles_expressive.xml
@@ -33,7 +33,6 @@
     <style name="Banner.Title.SettingsLib.Expressive"
         parent="">
         <item name="android:layout_gravity">start</item>
-        <item name="android:layout_marginLeft">@dimen/settingslib_expressive_space_extrasmall4</item>
         <item name="android:textAlignment">viewStart</item>
         <item name="android:textAppearance">@style/TextAppearance.SettingsLib.TitleLarge.Emphasized</item>
         <item name="android:textColor">?android:attr/textColorPrimary</item>
@@ -73,4 +72,11 @@
         parent="@style/SettingsLibButtonStyle.Expressive.Outline.Extra">
         <item name="materialSizeOverlay">@style/SizeOverlay.Material3Expressive.Button.Small</item>
     </style>
+
+    <style name="Banner.ResolvedText.SettingsLib.Expressive" parent="">
+        <item name="android:layout_gravity">center</item>
+        <item name="android:drawablePadding">@dimen/settingslib_expressive_space_small1</item>
+        <item name="android:textAppearance">@style/TextAppearance.SettingsLib.BodyMedium</item>
+        <item name="android:textColor">?android:attr/textColorPrimary</item>
+    </style>
 </resources>
diff --git a/packages/SettingsLib/BannerMessagePreference/src/com/android/settingslib/widget/BannerMessagePreference.java b/packages/SettingsLib/BannerMessagePreference/src/com/android/settingslib/widget/BannerMessagePreference.java
index c82829d..c90a76a 100644
--- a/packages/SettingsLib/BannerMessagePreference/src/com/android/settingslib/widget/BannerMessagePreference.java
+++ b/packages/SettingsLib/BannerMessagePreference/src/com/android/settingslib/widget/BannerMessagePreference.java
@@ -35,6 +35,8 @@
 
 import androidx.annotation.ColorInt;
 import androidx.annotation.ColorRes;
+import androidx.annotation.NonNull;
+import androidx.annotation.Nullable;
 import androidx.annotation.RequiresApi;
 import androidx.annotation.StringRes;
 import androidx.preference.Preference;
@@ -43,6 +45,9 @@
 import com.android.settingslib.widget.preference.banner.R;
 
 import com.google.android.material.button.MaterialButton;
+
+import java.util.Objects;
+
 /**
  * Banner message is a banner displaying important information (permission request, page error etc),
  * and provide actions for user to address. It requires a user action to be dismissed.
@@ -67,13 +72,15 @@
                 R.color.banner_accent_attention_normal,
                 R.color.settingslib_banner_button_background_normal);
 
-        // Corresponds to the enum valye of R.attr.attentionLevel
+        // Corresponds to the enum value of R.attr.attentionLevel
         private final int mAttrValue;
         @ColorRes private final int mBackgroundColorResId;
         @ColorRes private final int mAccentColorResId;
         @ColorRes private final int mButtonBackgroundColorResId;
 
-        AttentionLevel(int attrValue, @ColorRes int backgroundColorResId,
+        AttentionLevel(
+                int attrValue,
+                @ColorRes int backgroundColorResId,
                 @ColorRes int accentColorResId,
                 @ColorRes int buttonBackgroundColorResId) {
             mAttrValue = attrValue;
@@ -115,33 +122,38 @@
             new BannerMessagePreference.DismissButtonInfo();
 
     // Default attention level is High.
-    private AttentionLevel mAttentionLevel = AttentionLevel.HIGH;
-    private CharSequence mSubtitle;
-    private CharSequence mHeader;
+    @NonNull private AttentionLevel mAttentionLevel = AttentionLevel.HIGH;
+    @Nullable private CharSequence mSubtitle;
+    @Nullable private CharSequence mHeader;
     private int mButtonOrientation;
+    @Nullable private ResolutionAnimator.Data mResolutionData;
 
-    public BannerMessagePreference(Context context) {
+    public BannerMessagePreference(@NonNull Context context) {
         super(context);
         init(context, null /* attrs */);
     }
 
-    public BannerMessagePreference(Context context, AttributeSet attrs) {
+    public BannerMessagePreference(@NonNull Context context, @Nullable AttributeSet attrs) {
         super(context, attrs);
         init(context, attrs);
     }
 
-    public BannerMessagePreference(Context context, AttributeSet attrs, int defStyleAttr) {
+    public BannerMessagePreference(
+            @NonNull Context context, @Nullable AttributeSet attrs, int defStyleAttr) {
         super(context, attrs, defStyleAttr);
         init(context, attrs);
     }
 
-    public BannerMessagePreference(Context context, AttributeSet attrs, int defStyleAttr,
+    public BannerMessagePreference(
+            @NonNull Context context,
+            @Nullable AttributeSet attrs,
+            int defStyleAttr,
             int defStyleRes) {
         super(context, attrs, defStyleAttr, defStyleRes);
         init(context, attrs);
     }
 
-    private void init(Context context, AttributeSet attrs) {
+    private void init(@NonNull Context context, @Nullable AttributeSet attrs) {
         setSelectable(false);
         int resId = SettingsThemeHelper.isExpressiveTheme(context)
                 ? R.layout.settingslib_expressive_banner_message
@@ -166,7 +178,7 @@
     }
 
     @Override
-    public void onBindViewHolder(PreferenceViewHolder holder) {
+    public void onBindViewHolder(@NonNull PreferenceViewHolder holder) {
         super.onBindViewHolder(holder);
         final Context context = getContext();
 
@@ -193,14 +205,20 @@
         final ImageView iconView = (ImageView) holder.findViewById(R.id.banner_icon);
         if (iconView != null) {
             Drawable icon = getIcon();
-            iconView.setImageDrawable(
-                    icon == null
-                            ? getContext().getDrawable(R.drawable.ic_warning)
-                            : icon);
-            if (mAttentionLevel != AttentionLevel.NORMAL
-                    && !SettingsThemeHelper.isExpressiveTheme(context)) {
-                iconView.setColorFilter(
-                        new PorterDuffColorFilter(accentColor, PorterDuff.Mode.SRC_IN));
+
+            if (icon == null && SettingsThemeHelper.isExpressiveTheme(context)) {
+                iconView.setVisibility(View.GONE);
+            } else {
+                iconView.setVisibility(View.VISIBLE);
+                iconView.setImageDrawable(
+                        icon == null
+                                ? getContext().getDrawable(R.drawable.ic_warning)
+                                : icon);
+                if (mAttentionLevel != AttentionLevel.NORMAL
+                        && !SettingsThemeHelper.isExpressiveTheme(context)) {
+                    iconView.setColorFilter(
+                            new PorterDuffColorFilter(accentColor, PorterDuff.Mode.SRC_IN));
+                }
             }
         }
 
@@ -233,8 +251,10 @@
             mDismissButtonInfo.setUpButton();
 
             final TextView subtitleView = (TextView) holder.findViewById(R.id.banner_subtitle);
-            subtitleView.setText(mSubtitle);
-            subtitleView.setVisibility(mSubtitle == null ? View.GONE : View.VISIBLE);
+            if (subtitleView != null) {
+                subtitleView.setText(mSubtitle);
+                subtitleView.setVisibility(mSubtitle == null ? View.GONE : View.VISIBLE);
+            }
 
             TextView headerView = (TextView) holder.findViewById(R.id.banner_header);
             if (headerView != null) {
@@ -268,11 +288,25 @@
                 linearLayout.setOrientation(mButtonOrientation);
             }
         }
+
+        View buttonSpace = holder.findViewById(R.id.banner_button_space);
+        if (buttonSpace != null) {
+            if (mPositiveButtonInfo.shouldBeVisible() && mNegativeButtonInfo.shouldBeVisible()) {
+                buttonSpace.setVisibility(View.VISIBLE);
+            } else {
+                buttonSpace.setVisibility(View.GONE);
+            }
+        }
+
+        if (mResolutionData != null) {
+            new ResolutionAnimator(mResolutionData, holder).startResolutionAnimation();
+        }
     }
 
     /**
-     * Set the visibility state of positive button.
+     * Sets the visibility state of the positive button.
      */
+    @NonNull
     public BannerMessagePreference setPositiveButtonVisible(boolean isVisible) {
         if (isVisible != mPositiveButtonInfo.mIsVisible) {
             mPositiveButtonInfo.mIsVisible = isVisible;
@@ -282,8 +316,9 @@
     }
 
     /**
-     * Set the visibility state of negative button.
+     * Sets the visibility state of the negative button.
      */
+    @NonNull
     public BannerMessagePreference setNegativeButtonVisible(boolean isVisible) {
         if (isVisible != mNegativeButtonInfo.mIsVisible) {
             mNegativeButtonInfo.mIsVisible = isVisible;
@@ -293,9 +328,10 @@
     }
 
     /**
-     * Set the visibility state of dismiss button.
+     * Sets the visibility state of the dismiss button.
      */
     @RequiresApi(Build.VERSION_CODES.S)
+    @NonNull
     public BannerMessagePreference setDismissButtonVisible(boolean isVisible) {
         if (isVisible != mDismissButtonInfo.mIsVisible) {
             mDismissButtonInfo.mIsVisible = isVisible;
@@ -305,10 +341,35 @@
     }
 
     /**
-     * Register a callback to be invoked when positive button is clicked.
+     * Sets the enabled state of the positive button.
      */
+    @NonNull
+    public BannerMessagePreference setPositiveButtonEnabled(boolean isEnabled) {
+        if (isEnabled != mPositiveButtonInfo.mIsEnabled) {
+            mPositiveButtonInfo.mIsEnabled = isEnabled;
+            notifyChanged();
+        }
+        return this;
+    }
+
+    /**
+     * Sets the enabled state of the negative button.
+     */
+    @NonNull
+    public BannerMessagePreference setNegativeButtonEnabled(boolean isEnabled) {
+        if (isEnabled != mNegativeButtonInfo.mIsEnabled) {
+            mNegativeButtonInfo.mIsEnabled = isEnabled;
+            notifyChanged();
+        }
+        return this;
+    }
+
+    /**
+     * Registers a callback to be invoked when positive button is clicked.
+     */
+    @NonNull
     public BannerMessagePreference setPositiveButtonOnClickListener(
-            View.OnClickListener listener) {
+            @Nullable View.OnClickListener listener) {
         if (listener != mPositiveButtonInfo.mListener) {
             mPositiveButtonInfo.mListener = listener;
             notifyChanged();
@@ -317,10 +378,11 @@
     }
 
     /**
-     * Register a callback to be invoked when negative button is clicked.
+     * Registers a callback to be invoked when negative button is clicked.
      */
+    @NonNull
     public BannerMessagePreference setNegativeButtonOnClickListener(
-            View.OnClickListener listener) {
+            @Nullable View.OnClickListener listener) {
         if (listener != mNegativeButtonInfo.mListener) {
             mNegativeButtonInfo.mListener = listener;
             notifyChanged();
@@ -329,11 +391,12 @@
     }
 
     /**
-     * Register a callback to be invoked when the dismiss button is clicked.
+     * Registers a callback to be invoked when the dismiss button is clicked.
      */
     @RequiresApi(Build.VERSION_CODES.S)
+    @NonNull
     public BannerMessagePreference setDismissButtonOnClickListener(
-            View.OnClickListener listener) {
+            @Nullable View.OnClickListener listener) {
         if (listener != mDismissButtonInfo.mListener) {
             mDismissButtonInfo.mListener = listener;
             notifyChanged();
@@ -344,6 +407,7 @@
     /**
      * Sets the text to be displayed in positive button.
      */
+    @NonNull
     public BannerMessagePreference setPositiveButtonText(@StringRes int textResId) {
         return setPositiveButtonText(getContext().getString(textResId));
     }
@@ -351,7 +415,9 @@
     /**
      * Sets the text to be displayed in positive button.
      */
-    public BannerMessagePreference setPositiveButtonText(CharSequence positiveButtonText) {
+    @NonNull
+    public BannerMessagePreference setPositiveButtonText(
+            @Nullable CharSequence positiveButtonText) {
         if (!TextUtils.equals(positiveButtonText, mPositiveButtonInfo.mText)) {
             mPositiveButtonInfo.mText = positiveButtonText;
             notifyChanged();
@@ -362,6 +428,7 @@
     /**
      * Sets the text to be displayed in negative button.
      */
+    @NonNull
     public BannerMessagePreference setNegativeButtonText(@StringRes int textResId) {
         return setNegativeButtonText(getContext().getString(textResId));
     }
@@ -369,7 +436,9 @@
     /**
      * Sets the text to be displayed in negative button.
      */
-    public BannerMessagePreference setNegativeButtonText(CharSequence negativeButtonText) {
+    @NonNull
+    public BannerMessagePreference setNegativeButtonText(
+            @Nullable CharSequence negativeButtonText) {
         if (!TextUtils.equals(negativeButtonText, mNegativeButtonInfo.mText)) {
             mNegativeButtonInfo.mText = negativeButtonText;
             notifyChanged();
@@ -380,8 +449,12 @@
     /**
      * Sets button orientation.
      */
-    @RequiresApi(Build.VERSION_CODES.VANILLA_ICE_CREAM)
+    @NonNull
     public BannerMessagePreference setButtonOrientation(int orientation) {
+        if (!SettingsThemeHelper.isExpressiveTheme(getContext())) {
+            return this;
+        }
+
         if (mButtonOrientation != orientation) {
             mButtonOrientation = orientation;
             notifyChanged();
@@ -393,6 +466,7 @@
      * Sets the subtitle.
      */
     @RequiresApi(Build.VERSION_CODES.S)
+    @NonNull
     public BannerMessagePreference setSubtitle(@StringRes int textResId) {
         return setSubtitle(getContext().getString(textResId));
     }
@@ -401,7 +475,8 @@
      * Sets the subtitle.
      */
     @RequiresApi(Build.VERSION_CODES.S)
-    public BannerMessagePreference setSubtitle(CharSequence subtitle) {
+    @NonNull
+    public BannerMessagePreference setSubtitle(@Nullable CharSequence subtitle) {
         if (!TextUtils.equals(subtitle, mSubtitle)) {
             mSubtitle = subtitle;
             notifyChanged();
@@ -412,7 +487,7 @@
     /**
      * Sets the header.
      */
-    @RequiresApi(Build.VERSION_CODES.VANILLA_ICE_CREAM)
+    @NonNull
     public BannerMessagePreference setHeader(@StringRes int textResId) {
         return setHeader(getContext().getString(textResId));
     }
@@ -420,8 +495,12 @@
     /**
      * Sets the header.
      */
-    @RequiresApi(Build.VERSION_CODES.VANILLA_ICE_CREAM)
-    public BannerMessagePreference setHeader(CharSequence header) {
+    @NonNull
+    public BannerMessagePreference setHeader(@Nullable CharSequence header) {
+        if (!SettingsThemeHelper.isExpressiveTheme(getContext())) {
+            return this;
+        }
+
         if (!TextUtils.equals(header, mHeader)) {
             mHeader = header;
             notifyChanged();
@@ -430,32 +509,75 @@
     }
 
     /**
-     * Sets the attention level. This will update the color theme of the preference.
+     * Plays a resolution animation, showing the given message.
      */
-    public BannerMessagePreference setAttentionLevel(AttentionLevel attentionLevel) {
-        if (attentionLevel == mAttentionLevel) {
+    @NonNull
+    public BannerMessagePreference showResolutionAnimation(
+            @NonNull CharSequence resolutionMessage,
+            @NonNull ResolutionCompletedCallback onCompletionCallback) {
+        if (!SettingsThemeHelper.isExpressiveTheme(getContext())) {
             return this;
         }
-
-        if (attentionLevel != null) {
-            mAttentionLevel = attentionLevel;
+        ResolutionAnimator.Data resolutionData =
+                new ResolutionAnimator.Data(resolutionMessage, onCompletionCallback);
+        if (!Objects.equals(mResolutionData, resolutionData)) {
+            mResolutionData = resolutionData;
             notifyChanged();
         }
         return this;
     }
 
+    /**
+     * Removes the resolution animation from this preference.
+     *
+     * <p>Should be called after the resolution animation completes if this preference will be
+     * reused. Otherwise the resolution animation will be played everytime this preference is
+     * displayed.
+     */
+    @NonNull
+    public BannerMessagePreference clearResolutionAnimation() {
+        if (!SettingsThemeHelper.isExpressiveTheme(getContext())) {
+            return this;
+        }
+        if (mResolutionData != null) {
+            mResolutionData = null;
+            notifyChanged();
+        }
+        return this;
+    }
+
+    /**
+     * Sets the attention level. This will update the color theme of the preference.
+     */
+    @NonNull
+    public BannerMessagePreference setAttentionLevel(@NonNull AttentionLevel attentionLevel) {
+        if (attentionLevel == mAttentionLevel) {
+            return this;
+        }
+
+        mAttentionLevel = attentionLevel;
+        notifyChanged();
+        return this;
+    }
+
     static class ButtonInfo {
-        private Button mButton;
-        private CharSequence mText;
-        private View.OnClickListener mListener;
+        @Nullable private Button mButton;
+        @Nullable private CharSequence mText;
+        @Nullable private View.OnClickListener mListener;
         private boolean mIsVisible = true;
+        private boolean mIsEnabled = true;
         @ColorInt private int mColor;
         @ColorInt private int mBackgroundColor;
-        private ColorStateList mStrokeColor;
+        @Nullable private ColorStateList mStrokeColor;
 
         void setUpButton() {
+            if (mButton == null) {
+                return;
+            }
+
             mButton.setText(mText);
             mButton.setOnClickListener(mListener);
+            mButton.setEnabled(mIsEnabled);
 
             MaterialButton btn = null;
             if (mButton instanceof MaterialButton) {
@@ -492,11 +614,15 @@
     }
 
     static class DismissButtonInfo {
-        private ImageButton mButton;
-        private View.OnClickListener mListener;
+        @Nullable private ImageButton mButton;
+        @Nullable private View.OnClickListener mListener;
         private boolean mIsVisible = true;
 
         void setUpButton() {
+            if (mButton == null) {
+                return;
+            }
+
             mButton.setOnClickListener(mListener);
             if (shouldBeVisible()) {
                 mButton.setVisibility(View.VISIBLE);
diff --git a/packages/SettingsLib/BannerMessagePreference/src/com/android/settingslib/widget/BannerMessageView.java b/packages/SettingsLib/BannerMessagePreference/src/com/android/settingslib/widget/BannerMessageView.java
index ff4e79d..eabb634 100644
--- a/packages/SettingsLib/BannerMessagePreference/src/com/android/settingslib/widget/BannerMessageView.java
+++ b/packages/SettingsLib/BannerMessagePreference/src/com/android/settingslib/widget/BannerMessageView.java
@@ -23,6 +23,7 @@
 import android.view.View;
 import android.widget.LinearLayout;
 
+import androidx.annotation.NonNull;
 import androidx.annotation.Nullable;
 
 import com.android.settingslib.widget.preference.banner.R;
@@ -34,22 +35,25 @@
  * {@link BannerMessagePreference} to a {@code PreferenceScreen}.
  */
 public class BannerMessageView extends LinearLayout {
-    private Rect mTouchTargetForDismissButton;
+    @Nullable private Rect mTouchTargetForDismissButton;
 
-    public BannerMessageView(Context context) {
+    public BannerMessageView(@NonNull Context context) {
         super(context);
     }
 
-    public BannerMessageView(Context context,
-            @Nullable AttributeSet attrs) {
+    public BannerMessageView(@NonNull Context context, @Nullable AttributeSet attrs) {
         super(context, attrs);
     }
 
-    public BannerMessageView(Context context, @Nullable AttributeSet attrs, int defStyleAttr) {
+    public BannerMessageView(
+            @NonNull Context context, @Nullable AttributeSet attrs, int defStyleAttr) {
         super(context, attrs, defStyleAttr);
     }
 
-    public BannerMessageView(Context context, AttributeSet attrs, int defStyleAttr,
+    public BannerMessageView(
+            @NonNull Context context,
+            @Nullable AttributeSet attrs,
+            int defStyleAttr,
             int defStyleRes) {
         super(context, attrs, defStyleAttr, defStyleRes);
     }
diff --git a/packages/SettingsLib/BannerMessagePreference/src/com/android/settingslib/widget/ResolutionAnimator.kt b/packages/SettingsLib/BannerMessagePreference/src/com/android/settingslib/widget/ResolutionAnimator.kt
new file mode 100644
index 0000000..fbf910a
--- /dev/null
+++ b/packages/SettingsLib/BannerMessagePreference/src/com/android/settingslib/widget/ResolutionAnimator.kt
@@ -0,0 +1,196 @@
+/*
+ * Copyright (C) 2025 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.settingslib.widget
+
+import android.graphics.drawable.Animatable2
+import android.graphics.drawable.AnimatedVectorDrawable
+import android.graphics.drawable.Drawable
+import android.os.Build
+import android.provider.DeviceConfig
+import android.transition.Fade
+import android.transition.Transition
+import android.transition.TransitionListenerAdapter
+import android.transition.TransitionManager
+import android.transition.TransitionSet
+import android.view.View
+import android.view.ViewGroup
+import android.view.animation.LinearInterpolator
+import android.widget.TextView
+import androidx.preference.PreferenceViewHolder
+import com.android.settingslib.widget.preference.banner.R
+import java.time.Duration
+
+/** Callback to communicate when a banner message resolution animation is completed. */
+fun interface ResolutionCompletedCallback {
+    fun onCompleted()
+}
+
+internal class ResolutionAnimator(
+    private val data: Data,
+    private val preferenceViewHolder: PreferenceViewHolder,
+) {
+
+    data class Data(
+        val resolutionMessage: CharSequence,
+        val resolutionCompletedCallback: ResolutionCompletedCallback,
+    )
+
+    private val defaultBannerContent: View?
+        get() = preferenceViewHolder.findView(R.id.banner_content)
+    private val resolvedTextView: TextView?
+        get() = preferenceViewHolder.findView(R.id.resolved_banner_text)
+
+    fun startResolutionAnimation() {
+        resolvedTextView?.text = data.resolutionMessage
+        resolvedTextView?.resolutionDrawable?.reset()
+
+        val transitionSet =
+            TransitionSet()
+                .setOrdering(TransitionSet.ORDERING_SEQUENTIAL)
+                .setInterpolator(linearInterpolator)
+                .addTransition(hideIssueContentTransition)
+                .addTransition(
+                    showResolvedContentTransition
+                        .clone()
+                        .addListener(
+                            object : TransitionListenerAdapter() {
+                                override fun onTransitionEnd(transition: Transition) {
+                                    super.onTransitionEnd(transition)
+                                    startIssueResolvedAnimation()
+                                }
+                            }
+                        )
+                )
+
+        preferenceViewHolder.itemView.post {
+            TransitionManager.beginDelayedTransition(
+                preferenceViewHolder.itemView as ViewGroup,
+                transitionSet,
+            )
+
+            defaultBannerContent?.visibility = View.INVISIBLE
+            resolvedTextView?.visibility = View.VISIBLE
+        }
+
+        preferenceViewHolder.itemView.addOnAttachStateChangeListener(
+            object : View.OnAttachStateChangeListener {
+                override fun onViewAttachedToWindow(v: View) {}
+
+                override fun onViewDetachedFromWindow(v: View) {
+                    v.removeOnAttachStateChangeListener(this)
+                    cancelAnimationsAndFinish()
+                }
+            }
+        )
+    }
+
+    private fun startIssueResolvedAnimation() {
+        val animatedDrawable = resolvedTextView?.resolutionDrawable
+
+        if (animatedDrawable == null) {
+            hideResolvedUiAndFinish()
+            return
+        }
+
+        animatedDrawable.apply {
+            clearAnimationCallbacks()
+            registerAnimationCallback(
+                object : Animatable2.AnimationCallback() {
+                    override fun onAnimationEnd(drawable: Drawable) {
+                        super.onAnimationEnd(drawable)
+                        hideResolvedUiAndFinish()
+                    }
+                }
+            )
+            start()
+        }
+    }
+
+    private fun hideResolvedUiAndFinish() {
+        val hideTransition =
+            hideResolvedContentTransition
+                .clone()
+                .setInterpolator(linearInterpolator)
+                .addListener(
+                    object : TransitionListenerAdapter() {
+                        override fun onTransitionEnd(transition: Transition) {
+                            super.onTransitionEnd(transition)
+                            data.resolutionCompletedCallback.onCompleted()
+                        }
+                    }
+                )
+        TransitionManager.beginDelayedTransition(
+            preferenceViewHolder.itemView as ViewGroup,
+            hideTransition,
+        )
+        resolvedTextView?.visibility = View.GONE
+    }
+
+    private fun cancelAnimationsAndFinish() {
+        TransitionManager.endTransitions(preferenceViewHolder.itemView as ViewGroup)
+
+        resolvedTextView?.visibility = View.GONE
+
+        val animatedDrawable = resolvedTextView?.resolutionDrawable
+        animatedDrawable?.clearAnimationCallbacks()
+        animatedDrawable?.stop()
+
+        data.resolutionCompletedCallback.onCompleted()
+    }
+
+    private companion object {
+        private val linearInterpolator = LinearInterpolator()
+
+        private val HIDE_ISSUE_CONTENT_TRANSITION_DURATION = Duration.ofMillis(333)
+        private val hideIssueContentTransition =
+            Fade(Fade.OUT).setDuration(HIDE_ISSUE_CONTENT_TRANSITION_DURATION.toMillis())
+
+        private val SHOW_RESOLVED_CONTENT_TRANSITION_DELAY = Duration.ofMillis(133)
+        private val SHOW_RESOLVED_CONTENT_TRANSITION_DURATION = Duration.ofMillis(250)
+        private val showResolvedContentTransition =
+            Fade(Fade.IN)
+                .setStartDelay(SHOW_RESOLVED_CONTENT_TRANSITION_DELAY.toMillis())
+                .setDuration(SHOW_RESOLVED_CONTENT_TRANSITION_DURATION.toMillis())
+
+        private val hideResolvedContentTransitionDelay
+            get() =
+                if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.Q) {
+                    Duration.ofMillis(
+                        DeviceConfig.getLong(
+                            "settings_ui",
+                            "banner_message_pref_hide_resolved_content_delay_millis",
+                            400,
+                        )
+                    )
+                } else {
+                    Duration.ofMillis(400)
+                }
+
+        private val HIDE_RESOLVED_UI_TRANSITION_DURATION = Duration.ofMillis(167)
+        private val hideResolvedContentTransition
+            get() =
+                Fade(Fade.OUT)
+                    .setStartDelay(hideResolvedContentTransitionDelay.toMillis())
+                    .setDuration(HIDE_RESOLVED_UI_TRANSITION_DURATION.toMillis())
+
+        inline fun <reified T : View> PreferenceViewHolder.findView(id: Int): T? =
+            findViewById(id) as? T
+
+        val TextView.resolutionDrawable: AnimatedVectorDrawable?
+            get() = compoundDrawables.find { it != null } as? AnimatedVectorDrawable
+    }
+}
diff --git a/packages/SettingsLib/ButtonPreference/Android.bp b/packages/SettingsLib/ButtonPreference/Android.bp
index a377f31..c8375a9 100644
--- a/packages/SettingsLib/ButtonPreference/Android.bp
+++ b/packages/SettingsLib/ButtonPreference/Android.bp
@@ -30,5 +30,6 @@
     apex_available: [
         "//apex_available:platform",
         "com.android.healthfitness",
+        "com.android.permission",
     ],
 }
diff --git a/packages/SettingsLib/ButtonPreference/res/layout-v35/settingslib_expressive_button_filled.xml b/packages/SettingsLib/ButtonPreference/res/layout-v36/settingslib_expressive_button_filled.xml
similarity index 100%
rename from packages/SettingsLib/ButtonPreference/res/layout-v35/settingslib_expressive_button_filled.xml
rename to packages/SettingsLib/ButtonPreference/res/layout-v36/settingslib_expressive_button_filled.xml
diff --git a/packages/SettingsLib/ButtonPreference/res/layout-v35/settingslib_expressive_button_filled_extra.xml b/packages/SettingsLib/ButtonPreference/res/layout-v36/settingslib_expressive_button_filled_extra.xml
similarity index 100%
rename from packages/SettingsLib/ButtonPreference/res/layout-v35/settingslib_expressive_button_filled_extra.xml
rename to packages/SettingsLib/ButtonPreference/res/layout-v36/settingslib_expressive_button_filled_extra.xml
diff --git a/packages/SettingsLib/ButtonPreference/res/layout-v35/settingslib_expressive_button_filled_large.xml b/packages/SettingsLib/ButtonPreference/res/layout-v36/settingslib_expressive_button_filled_large.xml
similarity index 100%
rename from packages/SettingsLib/ButtonPreference/res/layout-v35/settingslib_expressive_button_filled_large.xml
rename to packages/SettingsLib/ButtonPreference/res/layout-v36/settingslib_expressive_button_filled_large.xml
diff --git a/packages/SettingsLib/ButtonPreference/res/layout-v35/settingslib_expressive_button_outline.xml b/packages/SettingsLib/ButtonPreference/res/layout-v36/settingslib_expressive_button_outline.xml
similarity index 100%
rename from packages/SettingsLib/ButtonPreference/res/layout-v35/settingslib_expressive_button_outline.xml
rename to packages/SettingsLib/ButtonPreference/res/layout-v36/settingslib_expressive_button_outline.xml
diff --git a/packages/SettingsLib/ButtonPreference/res/layout-v35/settingslib_expressive_button_outline_extra.xml b/packages/SettingsLib/ButtonPreference/res/layout-v36/settingslib_expressive_button_outline_extra.xml
similarity index 100%
rename from packages/SettingsLib/ButtonPreference/res/layout-v35/settingslib_expressive_button_outline_extra.xml
rename to packages/SettingsLib/ButtonPreference/res/layout-v36/settingslib_expressive_button_outline_extra.xml
diff --git a/packages/SettingsLib/ButtonPreference/res/layout-v35/settingslib_expressive_button_outline_large.xml b/packages/SettingsLib/ButtonPreference/res/layout-v36/settingslib_expressive_button_outline_large.xml
similarity index 100%
rename from packages/SettingsLib/ButtonPreference/res/layout-v35/settingslib_expressive_button_outline_large.xml
rename to packages/SettingsLib/ButtonPreference/res/layout-v36/settingslib_expressive_button_outline_large.xml
diff --git a/packages/SettingsLib/ButtonPreference/res/layout-v35/settingslib_expressive_button_tonal.xml b/packages/SettingsLib/ButtonPreference/res/layout-v36/settingslib_expressive_button_tonal.xml
similarity index 100%
rename from packages/SettingsLib/ButtonPreference/res/layout-v35/settingslib_expressive_button_tonal.xml
rename to packages/SettingsLib/ButtonPreference/res/layout-v36/settingslib_expressive_button_tonal.xml
diff --git a/packages/SettingsLib/ButtonPreference/res/layout-v35/settingslib_expressive_button_tonal_extra.xml b/packages/SettingsLib/ButtonPreference/res/layout-v36/settingslib_expressive_button_tonal_extra.xml
similarity index 100%
rename from packages/SettingsLib/ButtonPreference/res/layout-v35/settingslib_expressive_button_tonal_extra.xml
rename to packages/SettingsLib/ButtonPreference/res/layout-v36/settingslib_expressive_button_tonal_extra.xml
diff --git a/packages/SettingsLib/ButtonPreference/res/layout-v35/settingslib_expressive_button_tonal_large.xml b/packages/SettingsLib/ButtonPreference/res/layout-v36/settingslib_expressive_button_tonal_large.xml
similarity index 100%
rename from packages/SettingsLib/ButtonPreference/res/layout-v35/settingslib_expressive_button_tonal_large.xml
rename to packages/SettingsLib/ButtonPreference/res/layout-v36/settingslib_expressive_button_tonal_large.xml
diff --git a/packages/SettingsLib/CollapsingToolbarBaseActivity/res/drawable-v35/settingslib_expressive_icon_back.xml b/packages/SettingsLib/CollapsingToolbarBaseActivity/res/drawable-v36/settingslib_expressive_icon_back.xml
similarity index 100%
rename from packages/SettingsLib/CollapsingToolbarBaseActivity/res/drawable-v35/settingslib_expressive_icon_back.xml
rename to packages/SettingsLib/CollapsingToolbarBaseActivity/res/drawable-v36/settingslib_expressive_icon_back.xml
diff --git a/packages/SettingsLib/CollapsingToolbarBaseActivity/res/layout-v35/settingslib_expressive_collapsing_toolbar_base_layout.xml b/packages/SettingsLib/CollapsingToolbarBaseActivity/res/layout-v36/settingslib_expressive_collapsing_toolbar_base_layout.xml
similarity index 100%
rename from packages/SettingsLib/CollapsingToolbarBaseActivity/res/layout-v35/settingslib_expressive_collapsing_toolbar_base_layout.xml
rename to packages/SettingsLib/CollapsingToolbarBaseActivity/res/layout-v36/settingslib_expressive_collapsing_toolbar_base_layout.xml
diff --git a/packages/SettingsLib/CollapsingToolbarBaseActivity/res/layout-v35/settingslib_expressive_collapsing_toolbar_content_layout.xml b/packages/SettingsLib/CollapsingToolbarBaseActivity/res/layout-v36/settingslib_expressive_collapsing_toolbar_content_layout.xml
similarity index 100%
rename from packages/SettingsLib/CollapsingToolbarBaseActivity/res/layout-v35/settingslib_expressive_collapsing_toolbar_content_layout.xml
rename to packages/SettingsLib/CollapsingToolbarBaseActivity/res/layout-v36/settingslib_expressive_collapsing_toolbar_content_layout.xml
diff --git a/packages/SettingsLib/CollapsingToolbarBaseActivity/res/values-night-v35/themes.xml b/packages/SettingsLib/CollapsingToolbarBaseActivity/res/values-night-v36/themes.xml
similarity index 100%
rename from packages/SettingsLib/CollapsingToolbarBaseActivity/res/values-night-v35/themes.xml
rename to packages/SettingsLib/CollapsingToolbarBaseActivity/res/values-night-v36/themes.xml
diff --git a/packages/SettingsLib/CollapsingToolbarBaseActivity/res/values-v35/styles.xml b/packages/SettingsLib/CollapsingToolbarBaseActivity/res/values-v36/styles.xml
similarity index 100%
rename from packages/SettingsLib/CollapsingToolbarBaseActivity/res/values-v35/styles.xml
rename to packages/SettingsLib/CollapsingToolbarBaseActivity/res/values-v36/styles.xml
diff --git a/packages/SettingsLib/CollapsingToolbarBaseActivity/res/values-v35/styles_expressive.xml b/packages/SettingsLib/CollapsingToolbarBaseActivity/res/values-v36/styles_expressive.xml
similarity index 100%
rename from packages/SettingsLib/CollapsingToolbarBaseActivity/res/values-v35/styles_expressive.xml
rename to packages/SettingsLib/CollapsingToolbarBaseActivity/res/values-v36/styles_expressive.xml
diff --git a/packages/SettingsLib/CollapsingToolbarBaseActivity/res/values-v35/themes.xml b/packages/SettingsLib/CollapsingToolbarBaseActivity/res/values-v36/themes.xml
similarity index 100%
rename from packages/SettingsLib/CollapsingToolbarBaseActivity/res/values-v35/themes.xml
rename to packages/SettingsLib/CollapsingToolbarBaseActivity/res/values-v36/themes.xml
diff --git a/packages/SettingsLib/CollapsingToolbarBaseActivity/res/values-v35/themes_expressive.xml b/packages/SettingsLib/CollapsingToolbarBaseActivity/res/values-v36/themes_expressive.xml
similarity index 100%
rename from packages/SettingsLib/CollapsingToolbarBaseActivity/res/values-v35/themes_expressive.xml
rename to packages/SettingsLib/CollapsingToolbarBaseActivity/res/values-v36/themes_expressive.xml
diff --git a/packages/SettingsLib/MainSwitchPreference/res/color-night-v35/settingslib_main_switch_text_color.xml b/packages/SettingsLib/MainSwitchPreference/res/color-night-v36/settingslib_main_switch_text_color.xml
similarity index 100%
rename from packages/SettingsLib/MainSwitchPreference/res/color-night-v35/settingslib_main_switch_text_color.xml
rename to packages/SettingsLib/MainSwitchPreference/res/color-night-v36/settingslib_main_switch_text_color.xml
diff --git a/packages/SettingsLib/MainSwitchPreference/res/color-v35/settingslib_main_switch_text_color.xml b/packages/SettingsLib/MainSwitchPreference/res/color-v36/settingslib_main_switch_text_color.xml
similarity index 100%
rename from packages/SettingsLib/MainSwitchPreference/res/color-v35/settingslib_main_switch_text_color.xml
rename to packages/SettingsLib/MainSwitchPreference/res/color-v36/settingslib_main_switch_text_color.xml
diff --git a/packages/SettingsLib/MainSwitchPreference/res/drawable-v35/settingslib_expressive_switch_bar_bg.xml b/packages/SettingsLib/MainSwitchPreference/res/drawable-v36/settingslib_expressive_switch_bar_bg.xml
similarity index 100%
rename from packages/SettingsLib/MainSwitchPreference/res/drawable-v35/settingslib_expressive_switch_bar_bg.xml
rename to packages/SettingsLib/MainSwitchPreference/res/drawable-v36/settingslib_expressive_switch_bar_bg.xml
diff --git a/packages/SettingsLib/MainSwitchPreference/res/layout-v35/settingslib_expressive_main_switch_bar.xml b/packages/SettingsLib/MainSwitchPreference/res/layout-v36/settingslib_expressive_main_switch_bar.xml
similarity index 100%
rename from packages/SettingsLib/MainSwitchPreference/res/layout-v35/settingslib_expressive_main_switch_bar.xml
rename to packages/SettingsLib/MainSwitchPreference/res/layout-v36/settingslib_expressive_main_switch_bar.xml
diff --git a/packages/SettingsLib/MainSwitchPreference/res/layout-v35/settingslib_expressive_main_switch_layout.xml b/packages/SettingsLib/MainSwitchPreference/res/layout-v36/settingslib_expressive_main_switch_layout.xml
similarity index 100%
rename from packages/SettingsLib/MainSwitchPreference/res/layout-v35/settingslib_expressive_main_switch_layout.xml
rename to packages/SettingsLib/MainSwitchPreference/res/layout-v36/settingslib_expressive_main_switch_layout.xml
diff --git a/packages/SettingsLib/ProfileSelector/res/color-night-v35/settingslib_tabs_indicator_color.xml b/packages/SettingsLib/ProfileSelector/res/color-night-v36/settingslib_tabs_indicator_color.xml
similarity index 100%
rename from packages/SettingsLib/ProfileSelector/res/color-night-v35/settingslib_tabs_indicator_color.xml
rename to packages/SettingsLib/ProfileSelector/res/color-night-v36/settingslib_tabs_indicator_color.xml
diff --git a/packages/SettingsLib/SettingsTheme/res/color-night-v35/settingslib_switch_track_outline_color.xml b/packages/SettingsLib/SettingsTheme/res/color-night-v36/settingslib_switch_track_outline_color.xml
similarity index 100%
rename from packages/SettingsLib/SettingsTheme/res/color-night-v35/settingslib_switch_track_outline_color.xml
rename to packages/SettingsLib/SettingsTheme/res/color-night-v36/settingslib_switch_track_outline_color.xml
diff --git a/packages/SettingsLib/SettingsTheme/res/color-v35/settingslib_expressive_color_main_switch_track.xml b/packages/SettingsLib/SettingsTheme/res/color-v36/settingslib_expressive_color_main_switch_track.xml
similarity index 100%
rename from packages/SettingsLib/SettingsTheme/res/color-v35/settingslib_expressive_color_main_switch_track.xml
rename to packages/SettingsLib/SettingsTheme/res/color-v36/settingslib_expressive_color_main_switch_track.xml
diff --git a/packages/SettingsLib/SettingsTheme/res/color-v35/settingslib_preference_bg_color.xml b/packages/SettingsLib/SettingsTheme/res/color-v36/settingslib_preference_bg_color.xml
similarity index 100%
rename from packages/SettingsLib/SettingsTheme/res/color-v35/settingslib_preference_bg_color.xml
rename to packages/SettingsLib/SettingsTheme/res/color-v36/settingslib_preference_bg_color.xml
diff --git a/packages/SettingsLib/SettingsTheme/res/color-v35/settingslib_switch_track_outline_color.xml b/packages/SettingsLib/SettingsTheme/res/color-v36/settingslib_switch_track_outline_color.xml
similarity index 100%
rename from packages/SettingsLib/SettingsTheme/res/color-v35/settingslib_switch_track_outline_color.xml
rename to packages/SettingsLib/SettingsTheme/res/color-v36/settingslib_switch_track_outline_color.xml
diff --git a/packages/SettingsLib/SettingsTheme/res/color-v35/settingslib_text_color_primary.xml b/packages/SettingsLib/SettingsTheme/res/color-v36/settingslib_text_color_primary.xml
similarity index 100%
rename from packages/SettingsLib/SettingsTheme/res/color-v35/settingslib_text_color_primary.xml
rename to packages/SettingsLib/SettingsTheme/res/color-v36/settingslib_text_color_primary.xml
diff --git a/packages/SettingsLib/SettingsTheme/res/color-v35/settingslib_text_color_secondary.xml b/packages/SettingsLib/SettingsTheme/res/color-v36/settingslib_text_color_secondary.xml
similarity index 100%
rename from packages/SettingsLib/SettingsTheme/res/color-v35/settingslib_text_color_secondary.xml
rename to packages/SettingsLib/SettingsTheme/res/color-v36/settingslib_text_color_secondary.xml
diff --git a/packages/SettingsLib/SettingsTheme/res/drawable-v35/settingslib_arrow_drop_down.xml b/packages/SettingsLib/SettingsTheme/res/drawable-v36/settingslib_arrow_drop_down.xml
similarity index 100%
rename from packages/SettingsLib/SettingsTheme/res/drawable-v35/settingslib_arrow_drop_down.xml
rename to packages/SettingsLib/SettingsTheme/res/drawable-v36/settingslib_arrow_drop_down.xml
diff --git a/packages/SettingsLib/SettingsTheme/res/drawable-v35/settingslib_list_divider.xml b/packages/SettingsLib/SettingsTheme/res/drawable-v36/settingslib_list_divider.xml
similarity index 100%
rename from packages/SettingsLib/SettingsTheme/res/drawable-v35/settingslib_list_divider.xml
rename to packages/SettingsLib/SettingsTheme/res/drawable-v36/settingslib_list_divider.xml
diff --git a/packages/SettingsLib/SettingsTheme/res/drawable-v35/settingslib_progress_horizontal.xml b/packages/SettingsLib/SettingsTheme/res/drawable-v36/settingslib_progress_horizontal.xml
similarity index 100%
rename from packages/SettingsLib/SettingsTheme/res/drawable-v35/settingslib_progress_horizontal.xml
rename to packages/SettingsLib/SettingsTheme/res/drawable-v36/settingslib_progress_horizontal.xml
diff --git a/packages/SettingsLib/SettingsTheme/res/drawable-v35/settingslib_round_background.xml b/packages/SettingsLib/SettingsTheme/res/drawable-v36/settingslib_round_background.xml
similarity index 100%
rename from packages/SettingsLib/SettingsTheme/res/drawable-v35/settingslib_round_background.xml
rename to packages/SettingsLib/SettingsTheme/res/drawable-v36/settingslib_round_background.xml
diff --git a/packages/SettingsLib/SettingsTheme/res/drawable-v35/settingslib_round_background_bottom.xml b/packages/SettingsLib/SettingsTheme/res/drawable-v36/settingslib_round_background_bottom.xml
similarity index 100%
rename from packages/SettingsLib/SettingsTheme/res/drawable-v35/settingslib_round_background_bottom.xml
rename to packages/SettingsLib/SettingsTheme/res/drawable-v36/settingslib_round_background_bottom.xml
diff --git a/packages/SettingsLib/SettingsTheme/res/drawable-v35/settingslib_round_background_bottom_highlighted.xml b/packages/SettingsLib/SettingsTheme/res/drawable-v36/settingslib_round_background_bottom_highlighted.xml
similarity index 100%
rename from packages/SettingsLib/SettingsTheme/res/drawable-v35/settingslib_round_background_bottom_highlighted.xml
rename to packages/SettingsLib/SettingsTheme/res/drawable-v36/settingslib_round_background_bottom_highlighted.xml
diff --git a/packages/SettingsLib/SettingsTheme/res/drawable-v35/settingslib_round_background_bottom_selected.xml b/packages/SettingsLib/SettingsTheme/res/drawable-v36/settingslib_round_background_bottom_selected.xml
similarity index 100%
rename from packages/SettingsLib/SettingsTheme/res/drawable-v35/settingslib_round_background_bottom_selected.xml
rename to packages/SettingsLib/SettingsTheme/res/drawable-v36/settingslib_round_background_bottom_selected.xml
diff --git a/packages/SettingsLib/SettingsTheme/res/drawable-v35/settingslib_round_background_center.xml b/packages/SettingsLib/SettingsTheme/res/drawable-v36/settingslib_round_background_center.xml
similarity index 100%
rename from packages/SettingsLib/SettingsTheme/res/drawable-v35/settingslib_round_background_center.xml
rename to packages/SettingsLib/SettingsTheme/res/drawable-v36/settingslib_round_background_center.xml
diff --git a/packages/SettingsLib/SettingsTheme/res/drawable-v35/settingslib_round_background_center_highlighted.xml b/packages/SettingsLib/SettingsTheme/res/drawable-v36/settingslib_round_background_center_highlighted.xml
similarity index 100%
rename from packages/SettingsLib/SettingsTheme/res/drawable-v35/settingslib_round_background_center_highlighted.xml
rename to packages/SettingsLib/SettingsTheme/res/drawable-v36/settingslib_round_background_center_highlighted.xml
diff --git a/packages/SettingsLib/SettingsTheme/res/drawable-v35/settingslib_round_background_center_selected.xml b/packages/SettingsLib/SettingsTheme/res/drawable-v36/settingslib_round_background_center_selected.xml
similarity index 100%
rename from packages/SettingsLib/SettingsTheme/res/drawable-v35/settingslib_round_background_center_selected.xml
rename to packages/SettingsLib/SettingsTheme/res/drawable-v36/settingslib_round_background_center_selected.xml
diff --git a/packages/SettingsLib/SettingsTheme/res/drawable-v35/settingslib_round_background_highlighted.xml b/packages/SettingsLib/SettingsTheme/res/drawable-v36/settingslib_round_background_highlighted.xml
similarity index 100%
rename from packages/SettingsLib/SettingsTheme/res/drawable-v35/settingslib_round_background_highlighted.xml
rename to packages/SettingsLib/SettingsTheme/res/drawable-v36/settingslib_round_background_highlighted.xml
diff --git a/packages/SettingsLib/SettingsTheme/res/drawable-v35/settingslib_round_background_selected.xml b/packages/SettingsLib/SettingsTheme/res/drawable-v36/settingslib_round_background_selected.xml
similarity index 100%
rename from packages/SettingsLib/SettingsTheme/res/drawable-v35/settingslib_round_background_selected.xml
rename to packages/SettingsLib/SettingsTheme/res/drawable-v36/settingslib_round_background_selected.xml
diff --git a/packages/SettingsLib/SettingsTheme/res/drawable-v35/settingslib_round_background_top.xml b/packages/SettingsLib/SettingsTheme/res/drawable-v36/settingslib_round_background_top.xml
similarity index 100%
rename from packages/SettingsLib/SettingsTheme/res/drawable-v35/settingslib_round_background_top.xml
rename to packages/SettingsLib/SettingsTheme/res/drawable-v36/settingslib_round_background_top.xml
diff --git a/packages/SettingsLib/SettingsTheme/res/drawable-v35/settingslib_round_background_top_highlighted.xml b/packages/SettingsLib/SettingsTheme/res/drawable-v36/settingslib_round_background_top_highlighted.xml
similarity index 100%
rename from packages/SettingsLib/SettingsTheme/res/drawable-v35/settingslib_round_background_top_highlighted.xml
rename to packages/SettingsLib/SettingsTheme/res/drawable-v36/settingslib_round_background_top_highlighted.xml
diff --git a/packages/SettingsLib/SettingsTheme/res/drawable-v35/settingslib_round_background_top_selected.xml b/packages/SettingsLib/SettingsTheme/res/drawable-v36/settingslib_round_background_top_selected.xml
similarity index 100%
rename from packages/SettingsLib/SettingsTheme/res/drawable-v35/settingslib_round_background_top_selected.xml
rename to packages/SettingsLib/SettingsTheme/res/drawable-v36/settingslib_round_background_top_selected.xml
diff --git a/packages/SettingsLib/SettingsTheme/res/drawable-v35/settingslib_spinner_background.xml b/packages/SettingsLib/SettingsTheme/res/drawable-v36/settingslib_spinner_background.xml
similarity index 100%
rename from packages/SettingsLib/SettingsTheme/res/drawable-v35/settingslib_spinner_background.xml
rename to packages/SettingsLib/SettingsTheme/res/drawable-v36/settingslib_spinner_background.xml
diff --git a/packages/SettingsLib/SettingsTheme/res/drawable-v35/settingslib_spinner_dropdown_background.xml b/packages/SettingsLib/SettingsTheme/res/drawable-v36/settingslib_spinner_dropdown_background.xml
similarity index 100%
rename from packages/SettingsLib/SettingsTheme/res/drawable-v35/settingslib_spinner_dropdown_background.xml
rename to packages/SettingsLib/SettingsTheme/res/drawable-v36/settingslib_spinner_dropdown_background.xml
diff --git a/packages/SettingsLib/SettingsTheme/res/layout-v35/settingslib_expressive_preference.xml b/packages/SettingsLib/SettingsTheme/res/layout-v36/settingslib_expressive_preference.xml
similarity index 100%
rename from packages/SettingsLib/SettingsTheme/res/layout-v35/settingslib_expressive_preference.xml
rename to packages/SettingsLib/SettingsTheme/res/layout-v36/settingslib_expressive_preference.xml
diff --git a/packages/SettingsLib/SettingsTheme/res/layout-v35/settingslib_expressive_preference_icon_frame.xml b/packages/SettingsLib/SettingsTheme/res/layout-v36/settingslib_expressive_preference_icon_frame.xml
similarity index 100%
rename from packages/SettingsLib/SettingsTheme/res/layout-v35/settingslib_expressive_preference_icon_frame.xml
rename to packages/SettingsLib/SettingsTheme/res/layout-v36/settingslib_expressive_preference_icon_frame.xml
diff --git a/packages/SettingsLib/SettingsTheme/res/layout-v35/settingslib_expressive_preference_switch.xml b/packages/SettingsLib/SettingsTheme/res/layout-v36/settingslib_expressive_preference_switch.xml
similarity index 100%
rename from packages/SettingsLib/SettingsTheme/res/layout-v35/settingslib_expressive_preference_switch.xml
rename to packages/SettingsLib/SettingsTheme/res/layout-v36/settingslib_expressive_preference_switch.xml
diff --git a/packages/SettingsLib/SettingsTheme/res/layout-v35/settingslib_expressive_two_target_divider.xml b/packages/SettingsLib/SettingsTheme/res/layout-v36/settingslib_expressive_two_target_divider.xml
similarity index 100%
rename from packages/SettingsLib/SettingsTheme/res/layout-v35/settingslib_expressive_two_target_divider.xml
rename to packages/SettingsLib/SettingsTheme/res/layout-v36/settingslib_expressive_two_target_divider.xml
diff --git a/packages/SettingsLib/SettingsTheme/res/values-night-v35/colors.xml b/packages/SettingsLib/SettingsTheme/res/values-night-v36/colors.xml
similarity index 100%
rename from packages/SettingsLib/SettingsTheme/res/values-night-v35/colors.xml
rename to packages/SettingsLib/SettingsTheme/res/values-night-v36/colors.xml
diff --git a/packages/SettingsLib/SettingsTheme/res/values-v35/colors.xml b/packages/SettingsLib/SettingsTheme/res/values-v36/colors.xml
similarity index 100%
rename from packages/SettingsLib/SettingsTheme/res/values-v35/colors.xml
rename to packages/SettingsLib/SettingsTheme/res/values-v36/colors.xml
diff --git a/packages/SettingsLib/SettingsTheme/res/values-v35/dimens.xml b/packages/SettingsLib/SettingsTheme/res/values-v36/dimens.xml
similarity index 100%
rename from packages/SettingsLib/SettingsTheme/res/values-v35/dimens.xml
rename to packages/SettingsLib/SettingsTheme/res/values-v36/dimens.xml
diff --git a/packages/SettingsLib/SettingsTheme/res/values-v35/styles_expressive.xml b/packages/SettingsLib/SettingsTheme/res/values-v36/styles_expressive.xml
similarity index 100%
rename from packages/SettingsLib/SettingsTheme/res/values-v35/styles_expressive.xml
rename to packages/SettingsLib/SettingsTheme/res/values-v36/styles_expressive.xml
diff --git a/packages/SettingsLib/SettingsTheme/res/values-v35/styles_preference_expressive.xml b/packages/SettingsLib/SettingsTheme/res/values-v36/styles_preference_expressive.xml
similarity index 100%
rename from packages/SettingsLib/SettingsTheme/res/values-v35/styles_preference_expressive.xml
rename to packages/SettingsLib/SettingsTheme/res/values-v36/styles_preference_expressive.xml
diff --git a/packages/SettingsLib/SettingsTheme/res/values-v35/themes.xml b/packages/SettingsLib/SettingsTheme/res/values-v36/themes.xml
similarity index 92%
rename from packages/SettingsLib/SettingsTheme/res/values-v35/themes.xml
rename to packages/SettingsLib/SettingsTheme/res/values-v36/themes.xml
index dc5c9b2..1c45ff6 100644
--- a/packages/SettingsLib/SettingsTheme/res/values-v35/themes.xml
+++ b/packages/SettingsLib/SettingsTheme/res/values-v36/themes.xml
@@ -16,9 +16,9 @@
   -->
 
 <resources>
-    <style name="Theme.SettingsBase_v35" parent="Theme.SettingsBase_v33" >
+    <style name="Theme.SettingsBase_v36" 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>
@@ -27,5 +27,5 @@
         <item name="android:clipChildren">false</item>
     </style>
 
-    <style name="Theme.SettingsBase" parent="Theme.SettingsBase_v35" />
+    <style name="Theme.SettingsBase" parent="Theme.SettingsBase_v36" />
 </resources>
\ No newline at end of file
diff --git a/packages/SettingsLib/SettingsTheme/res/values-v35/themes_expressive.xml b/packages/SettingsLib/SettingsTheme/res/values-v36/themes_expressive.xml
similarity index 100%
rename from packages/SettingsLib/SettingsTheme/res/values-v35/themes_expressive.xml
rename to packages/SettingsLib/SettingsTheme/res/values-v36/themes_expressive.xml
diff --git a/packages/SettingsLib/SettingsTheme/res/values-v35/themes_preference_expressive.xml b/packages/SettingsLib/SettingsTheme/res/values-v36/themes_preference_expressive.xml
similarity index 100%
rename from packages/SettingsLib/SettingsTheme/res/values-v35/themes_preference_expressive.xml
rename to packages/SettingsLib/SettingsTheme/res/values-v36/themes_preference_expressive.xml
diff --git a/packages/SettingsLib/SettingsTheme/src/com/android/settingslib/widget/SettingsPreferenceGroupAdapter.kt b/packages/SettingsLib/SettingsTheme/src/com/android/settingslib/widget/SettingsPreferenceGroupAdapter.kt
index 6a06320..a04fce7 100644
--- a/packages/SettingsLib/SettingsTheme/src/com/android/settingslib/widget/SettingsPreferenceGroupAdapter.kt
+++ b/packages/SettingsLib/SettingsTheme/src/com/android/settingslib/widget/SettingsPreferenceGroupAdapter.kt
@@ -47,7 +47,7 @@
 
     private val mHandler = Handler(Looper.getMainLooper())
 
-    private val syncRunnable = Runnable { updatePreferences() }
+    private val syncRunnable = Runnable { updatePreferencesList() }
 
     init {
         val context = preferenceGroup.context
@@ -64,7 +64,7 @@
             true, /* resolveRefs */
         )
         mLegacyBackgroundRes = outValue.resourceId
-        updatePreferences()
+        updatePreferencesList()
     }
 
     @SuppressLint("RestrictedApi")
@@ -82,7 +82,7 @@
         updateBackground(holder, position)
     }
 
-    private fun updatePreferences() {
+    private fun updatePreferencesList() {
         val oldList = ArrayList(mRoundCornerMappingList)
         mRoundCornerMappingList = ArrayList()
         mappingPreferenceGroup(mRoundCornerMappingList, mPreferenceGroup)
diff --git a/packages/SettingsLib/TwoTargetPreference/res/layout-v35/settingslib_expressive_preference_two_target.xml b/packages/SettingsLib/TwoTargetPreference/res/layout-v36/settingslib_expressive_preference_two_target.xml
similarity index 100%
rename from packages/SettingsLib/TwoTargetPreference/res/layout-v35/settingslib_expressive_preference_two_target.xml
rename to packages/SettingsLib/TwoTargetPreference/res/layout-v36/settingslib_expressive_preference_two_target.xml
diff --git a/packages/SettingsLib/src/com/android/settingslib/bluetooth/BluetoothUtils.java b/packages/SettingsLib/src/com/android/settingslib/bluetooth/BluetoothUtils.java
index 522a436..31948e4 100644
--- a/packages/SettingsLib/src/com/android/settingslib/bluetooth/BluetoothUtils.java
+++ b/packages/SettingsLib/src/com/android/settingslib/bluetooth/BluetoothUtils.java
@@ -23,6 +23,7 @@
 import android.graphics.Canvas;
 import android.graphics.drawable.BitmapDrawable;
 import android.graphics.drawable.Drawable;
+import android.hardware.input.InputManager;
 import android.media.AudioDeviceAttributes;
 import android.media.AudioDeviceInfo;
 import android.media.AudioManager;
@@ -34,6 +35,7 @@
 import android.text.TextUtils;
 import android.util.Log;
 import android.util.Pair;
+import android.view.InputDevice;
 
 import androidx.annotation.DrawableRes;
 import androidx.annotation.NonNull;
@@ -1054,7 +1056,7 @@
 
     /** Get develop option value for audio sharing preview. */
     @WorkerThread
-    private static boolean getAudioSharingPreviewValue(@Nullable ContentResolver contentResolver) {
+    public static boolean getAudioSharingPreviewValue(@Nullable ContentResolver contentResolver) {
         if (contentResolver == null) return false;
         return Settings.Global.getInt(
                 contentResolver,
@@ -1193,4 +1195,53 @@
         }
         device.setMetadata(METADATA_FAST_PAIR_CUSTOMIZED_FIELDS, fastPairCustomizedMeta.getBytes());
     }
+
+    /**
+     * Returns the {@link InputDevice} of the given bluetooth address if the device is a input
+     * device.
+     *
+     * @param address The address of the bluetooth device
+     * @return The {@link InputDevice} of the given address if applicable
+     */
+    @Nullable
+    public static InputDevice getInputDevice(Context context, String address) {
+        InputManager im = context.getSystemService(InputManager.class);
+
+        if (im != null) {
+            for (int deviceId : im.getInputDeviceIds()) {
+                String btAddress = im.getInputDeviceBluetoothAddress(deviceId);
+
+                if (btAddress != null && btAddress.equals(address)) {
+                    return im.getInputDevice(deviceId);
+                }
+            }
+        }
+        return null;
+    }
+
+    /**
+     * Identifies whether a device is a stylus using the associated {@link InputDevice} or
+     * {@link CachedBluetoothDevice}.
+     * InputDevices are only available when the device is USI or Bluetooth-connected, whereas
+     * CachedBluetoothDevices are available for Bluetooth devices when connected or paired,
+     * so to handle all cases, both are needed.
+     *
+     * @param inputDevice           The associated input device of the stylus
+     * @param cachedBluetoothDevice The associated bluetooth device of the stylus
+     */
+    public static boolean isDeviceStylus(@Nullable InputDevice inputDevice,
+            @Nullable CachedBluetoothDevice cachedBluetoothDevice) {
+        if (inputDevice != null && inputDevice.supportsSource(InputDevice.SOURCE_STYLUS)) {
+            return true;
+        }
+
+        if (cachedBluetoothDevice != null) {
+            BluetoothDevice bluetoothDevice = cachedBluetoothDevice.getDevice();
+            String deviceType = BluetoothUtils.getStringMetaData(bluetoothDevice,
+                    BluetoothDevice.METADATA_DEVICE_TYPE);
+            return TextUtils.equals(deviceType, BluetoothDevice.DEVICE_TYPE_STYLUS);
+        }
+
+        return false;
+    }
 }
diff --git a/packages/SettingsLib/src/com/android/settingslib/bluetooth/LocalBluetoothLeBroadcast.java b/packages/SettingsLib/src/com/android/settingslib/bluetooth/LocalBluetoothLeBroadcast.java
index 3017d798..f18a2da 100644
--- a/packages/SettingsLib/src/com/android/settingslib/bluetooth/LocalBluetoothLeBroadcast.java
+++ b/packages/SettingsLib/src/com/android/settingslib/bluetooth/LocalBluetoothLeBroadcast.java
@@ -63,6 +63,7 @@
 import java.lang.annotation.Retention;
 import java.lang.annotation.RetentionPolicy;
 import java.nio.charset.StandardCharsets;
+import java.security.SecureRandom;
 import java.util.ArrayList;
 import java.util.Arrays;
 import java.util.Collections;
@@ -70,7 +71,6 @@
 import java.util.List;
 import java.util.Map;
 import java.util.Objects;
-import java.util.UUID;
 import java.util.concurrent.ConcurrentHashMap;
 import java.util.concurrent.Executor;
 import java.util.concurrent.Executors;
@@ -108,6 +108,10 @@
     private static final String SYSUI_PKG = "com.android.systemui";
     private static final String TAG = "LocalBluetoothLeBroadcast";
     private static final boolean DEBUG = BluetoothUtils.D;
+    private static final String VALID_PASSWORD_CHARACTERS =
+            "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789!@#$%^&*()-_=+[]{}|;:,"
+                    + ".<>?/";
+    private static final int PASSWORD_LENGTH = 16;
 
     static final String NAME = "LE_AUDIO_BROADCAST";
     private static final String UNDERLINE = "_";
@@ -145,8 +149,8 @@
     private ContentResolver mContentResolver;
     private ContentObserver mSettingsObserver;
     // Cached broadcast callbacks being register before service is connected.
-    private Map<BluetoothLeBroadcast.Callback, Executor> mCachedBroadcastCallbackExecutorMap =
-            new ConcurrentHashMap<>();
+    private ConcurrentHashMap<BluetoothLeBroadcast.Callback, Executor>
+            mCachedBroadcastCallbackExecutorMap = new ConcurrentHashMap<>();
 
     private final ServiceListener mServiceListener =
             new ServiceListener() {
@@ -876,7 +880,7 @@
             @NonNull @CallbackExecutor Executor executor,
             @NonNull BluetoothLeBroadcast.Callback callback) {
         if (mServiceBroadcast == null) {
-            Log.d(TAG, "registerServiceCallBack failed, the BluetoothLeBroadcast is null.");
+            Log.d(TAG, "registerServiceCallBack failed, proxy not attached.");
             mCachedBroadcastCallbackExecutorMap.putIfAbsent(callback, executor);
             return;
         }
@@ -898,10 +902,7 @@
             @NonNull @CallbackExecutor Executor executor,
             @NonNull BluetoothLeBroadcastAssistant.Callback callback) {
         if (mServiceBroadcastAssistant == null) {
-            Log.d(
-                    TAG,
-                    "registerBroadcastAssistantCallback failed, "
-                            + "the BluetoothLeBroadcastAssistant is null.");
+            Log.d(TAG, "registerBroadcastAssistantCallback failed, proxy not attached.");
             return;
         }
 
@@ -916,7 +917,7 @@
     public void unregisterServiceCallBack(@NonNull BluetoothLeBroadcast.Callback callback) {
         mCachedBroadcastCallbackExecutorMap.remove(callback);
         if (mServiceBroadcast == null) {
-            Log.d(TAG, "unregisterServiceCallBack failed, the BluetoothLeBroadcast is null.");
+            Log.d(TAG, "unregisterServiceCallBack failed, proxy not attached.");
             return;
         }
 
@@ -935,10 +936,7 @@
     private void unregisterBroadcastAssistantCallback(
             @NonNull BluetoothLeBroadcastAssistant.Callback callback) {
         if (mServiceBroadcastAssistant == null) {
-            Log.d(
-                    TAG,
-                    "unregisterBroadcastAssistantCallback, "
-                            + "the BluetoothLeBroadcastAssistant is null.");
+            Log.d(TAG, "unregisterBroadcastAssistantCallback, proxy not attched.");
             return;
         }
 
@@ -1088,11 +1086,16 @@
         mBroadcastId = UNKNOWN_VALUE_PLACEHOLDER;
     }
 
-    private String generateRandomPassword() {
-        String randomUUID = UUID.randomUUID().toString();
-        // first 16 chars from xxxxxxxx-xxxx-4xxx-yxxx-xxxxxxxxxxxx
-        return randomUUID.substring(0, 8) + randomUUID.substring(9, 13) + randomUUID.substring(14,
-                18);
+    private static String generateRandomPassword() {
+        SecureRandom random = new SecureRandom();
+        StringBuilder stringBuilder = new StringBuilder(PASSWORD_LENGTH);
+
+        for (int i = 0; i < PASSWORD_LENGTH; i++) {
+            int randomIndex = random.nextInt(VALID_PASSWORD_CHARACTERS.length());
+            stringBuilder.append(VALID_PASSWORD_CHARACTERS.charAt(randomIndex));
+        }
+
+        return stringBuilder.toString();
     }
 
     private void registerContentObserver() {
@@ -1131,7 +1134,9 @@
 
     /** Update fallback active device if needed. */
     public void updateFallbackActiveDeviceIfNeeded() {
-        if (Flags.disableAudioSharingAutoPickFallbackInUi()) {
+        if (Flags.disableAudioSharingAutoPickFallbackInUi() || (mContext != null
+                && Flags.audioSharingDeveloperOption()
+                && BluetoothUtils.getAudioSharingPreviewValue(mContext.getContentResolver()))) {
             Log.d(TAG, "Skip updateFallbackActiveDeviceIfNeeded, disable flag is on");
             return;
         }
diff --git a/packages/SettingsLib/src/com/android/settingslib/connectivity/OWNERS b/packages/SettingsLib/src/com/android/settingslib/connectivity/OWNERS
index 9f15c1b..b676d8d 100644
--- a/packages/SettingsLib/src/com/android/settingslib/connectivity/OWNERS
+++ b/packages/SettingsLib/src/com/android/settingslib/connectivity/OWNERS
@@ -1,6 +1,5 @@
 # Default reviewers for this and subdirectories.
 andychou@google.com
-arcwang@google.com
 changbetty@google.com
 qal@google.com
 wengsu@google.com
diff --git a/packages/SettingsLib/tests/robotests/src/com/android/settingslib/bluetooth/AmbientVolumeControllerTest.java b/packages/SettingsLib/tests/robotests/src/com/android/settingslib/bluetooth/AmbientVolumeControllerTest.java
index abc1d226..e4381ae 100644
--- a/packages/SettingsLib/tests/robotests/src/com/android/settingslib/bluetooth/AmbientVolumeControllerTest.java
+++ b/packages/SettingsLib/tests/robotests/src/com/android/settingslib/bluetooth/AmbientVolumeControllerTest.java
@@ -26,6 +26,7 @@
 
 import android.bluetooth.AudioInputControl;
 import android.bluetooth.BluetoothDevice;
+import android.bluetooth.BluetoothProfile;
 import android.content.Context;
 
 import androidx.test.core.app.ApplicationProvider;
@@ -67,6 +68,9 @@
     @Before
     public void setUp() {
         when(mProfileManager.getVolumeControlProfile()).thenReturn(mVolumeControlProfile);
+        when(mVolumeControlProfile.isProfileReady()).thenReturn(true);
+        when(mVolumeControlProfile.getConnectionStatus(mDevice)).thenReturn(
+                BluetoothProfile.STATE_CONNECTED);
         when(mDevice.getAddress()).thenReturn(TEST_ADDRESS);
         when(mDevice.isConnected()).thenReturn(true);
         mVolumeController = new AmbientVolumeController(mProfileManager, mCallback);
@@ -74,8 +78,6 @@
 
     @Test
     public void onServiceConnected_notifyCallback() {
-        when(mVolumeControlProfile.isProfileReady()).thenReturn(true);
-
         mVolumeController.onServiceConnected();
 
         verify(mCallback).onVolumeControlServiceConnected();
diff --git a/packages/SettingsLib/tests/robotests/src/com/android/settingslib/bluetooth/BluetoothUtilsTest.java b/packages/SettingsLib/tests/robotests/src/com/android/settingslib/bluetooth/BluetoothUtilsTest.java
index ebe6128..0325c0e 100644
--- a/packages/SettingsLib/tests/robotests/src/com/android/settingslib/bluetooth/BluetoothUtilsTest.java
+++ b/packages/SettingsLib/tests/robotests/src/com/android/settingslib/bluetooth/BluetoothUtilsTest.java
@@ -15,7 +15,9 @@
  */
 package com.android.settingslib.bluetooth;
 
+import static com.android.settingslib.bluetooth.BluetoothUtils.getInputDevice;
 import static com.android.settingslib.bluetooth.BluetoothUtils.isAvailableAudioSharingMediaBluetoothDevice;
+import static com.android.settingslib.bluetooth.BluetoothUtils.isDeviceStylus;
 import static com.android.settingslib.bluetooth.LocalBluetoothLeBroadcast.UNKNOWN_VALUE_PLACEHOLDER;
 import static com.android.settingslib.flags.Flags.FLAG_ENABLE_DETERMINING_ADVANCED_DETAILS_HEADER_WITH_METADATA;
 
@@ -42,6 +44,7 @@
 import android.content.pm.ApplicationInfo;
 import android.content.pm.PackageManager;
 import android.graphics.drawable.Drawable;
+import android.hardware.input.InputManager;
 import android.media.AudioDeviceAttributes;
 import android.media.AudioDeviceInfo;
 import android.media.AudioManager;
@@ -51,6 +54,7 @@
 import android.platform.test.flag.junit.SetFlagsRule;
 import android.provider.Settings;
 import android.util.Pair;
+import android.view.InputDevice;
 
 import com.android.internal.R;
 import com.android.settingslib.flags.Flags;
@@ -97,14 +101,18 @@
     @Mock private LocalBluetoothLeBroadcastAssistant mAssistant;
     @Mock private CachedBluetoothDeviceManager mDeviceManager;
     @Mock private BluetoothLeBroadcastReceiveState mLeBroadcastReceiveState;
+    @Mock
+    private InputManager mInputManager;
 
     private Context mContext;
     private ShadowBluetoothAdapter mShadowBluetoothAdapter;
+    private final InputDevice mInputDevice = mock(InputDevice.class);
     private static final String STRING_METADATA = "string_metadata";
     private static final String LE_AUDIO_SHARING_METADATA = "le_audio_sharing";
     private static final String BOOL_METADATA = "true";
     private static final String INT_METADATA = "25";
     private static final int METADATA_FAST_PAIR_CUSTOMIZED_FIELDS = 25;
+    private static final int TEST_DEVICE_ID = 123;
     private static final String KEY_HEARABLE_CONTROL_SLICE = "HEARABLE_CONTROL_SLICE_WITH_WIDTH";
     private static final String CONTROL_METADATA =
             "<HEARABLE_CONTROL_SLICE_WITH_WIDTH>"
@@ -115,6 +123,7 @@
     private static final String FAKE_TEMP_BOND_METADATA = "<TEMP_BOND_TYPE>fake</TEMP_BOND_TYPE>";
     private static final String TEST_EXCLUSIVE_MANAGER_PACKAGE = "com.test.manager";
     private static final String TEST_EXCLUSIVE_MANAGER_COMPONENT = "com.test.manager/.component";
+    private static final String TEST_ADDRESS = "11:22:33:44:55:66";
     private static final int TEST_BROADCAST_ID = 25;
 
     @Rule public final SetFlagsRule mSetFlagsRule = new SetFlagsRule();
@@ -134,6 +143,10 @@
         when(mA2dpProfile.getProfileId()).thenReturn(BluetoothProfile.A2DP);
         when(mLeAudioProfile.getProfileId()).thenReturn(BluetoothProfile.LE_AUDIO);
         when(mHearingAid.getProfileId()).thenReturn(BluetoothProfile.HEARING_AID);
+        when(mContext.getSystemService(InputManager.class)).thenReturn(mInputManager);
+        when(mInputManager.getInputDeviceIds()).thenReturn(new int[]{TEST_DEVICE_ID});
+        when(mInputManager.getInputDeviceBluetoothAddress(TEST_DEVICE_ID)).thenReturn(TEST_ADDRESS);
+        when(mInputManager.getInputDevice(TEST_DEVICE_ID)).thenReturn(mInputDevice);
     }
 
     @Test
@@ -1097,9 +1110,8 @@
 
     @Test
     public void getAudioDeviceAttributesForSpatialAudio_bleHeadset() {
-        String address = "11:22:33:44:55:66";
         when(mCachedBluetoothDevice.getDevice()).thenReturn(mBluetoothDevice);
-        when(mCachedBluetoothDevice.getAddress()).thenReturn(address);
+        when(mCachedBluetoothDevice.getAddress()).thenReturn(TEST_ADDRESS);
         when(mCachedBluetoothDevice.getProfiles()).thenReturn(List.of(mLeAudioProfile));
         when(mLeAudioProfile.isEnabled(mBluetoothDevice)).thenReturn(true);
 
@@ -1112,14 +1124,13 @@
                         new AudioDeviceAttributes(
                                 AudioDeviceAttributes.ROLE_OUTPUT,
                                 AudioDeviceInfo.TYPE_BLE_HEADSET,
-                                address));
+                                TEST_ADDRESS));
     }
 
     @Test
     public void getAudioDeviceAttributesForSpatialAudio_bleSpeaker() {
-        String address = "11:22:33:44:55:66";
         when(mCachedBluetoothDevice.getDevice()).thenReturn(mBluetoothDevice);
-        when(mCachedBluetoothDevice.getAddress()).thenReturn(address);
+        when(mCachedBluetoothDevice.getAddress()).thenReturn(TEST_ADDRESS);
         when(mCachedBluetoothDevice.getProfiles()).thenReturn(List.of(mLeAudioProfile));
         when(mLeAudioProfile.isEnabled(mBluetoothDevice)).thenReturn(true);
 
@@ -1132,14 +1143,14 @@
                         new AudioDeviceAttributes(
                                 AudioDeviceAttributes.ROLE_OUTPUT,
                                 AudioDeviceInfo.TYPE_BLE_SPEAKER,
-                                address));
+                                TEST_ADDRESS));
     }
 
     @Test
     public void getAudioDeviceAttributesForSpatialAudio_a2dp() {
-        String address = "11:22:33:44:55:66";
+
         when(mCachedBluetoothDevice.getDevice()).thenReturn(mBluetoothDevice);
-        when(mCachedBluetoothDevice.getAddress()).thenReturn(address);
+        when(mCachedBluetoothDevice.getAddress()).thenReturn(TEST_ADDRESS);
         when(mCachedBluetoothDevice.getProfiles()).thenReturn(List.of(mA2dpProfile));
         when(mA2dpProfile.isEnabled(mBluetoothDevice)).thenReturn(true);
 
@@ -1152,14 +1163,13 @@
                         new AudioDeviceAttributes(
                                 AudioDeviceAttributes.ROLE_OUTPUT,
                                 AudioDeviceInfo.TYPE_BLUETOOTH_A2DP,
-                                address));
+                                TEST_ADDRESS));
     }
 
     @Test
     public void getAudioDeviceAttributesForSpatialAudio_hearingAid() {
-        String address = "11:22:33:44:55:66";
         when(mCachedBluetoothDevice.getDevice()).thenReturn(mBluetoothDevice);
-        when(mCachedBluetoothDevice.getAddress()).thenReturn(address);
+        when(mCachedBluetoothDevice.getAddress()).thenReturn(TEST_ADDRESS);
         when(mCachedBluetoothDevice.getProfiles()).thenReturn(List.of(mHearingAid));
         when(mHearingAid.isEnabled(mBluetoothDevice)).thenReturn(true);
 
@@ -1172,7 +1182,7 @@
                         new AudioDeviceAttributes(
                                 AudioDeviceAttributes.ROLE_OUTPUT,
                                 AudioDeviceInfo.TYPE_HEARING_AID,
-                                address));
+                                TEST_ADDRESS));
     }
 
     @Test
@@ -1375,4 +1385,54 @@
         verify(mBluetoothDevice).setMetadata(METADATA_FAST_PAIR_CUSTOMIZED_FIELDS,
                 TEMP_BOND_METADATA.getBytes());
     }
+
+    @Test
+    public void getInputDevice_addressNotMatched_returnsNull() {
+        assertThat(getInputDevice(mContext, "123")).isNull();
+    }
+
+    @Test
+    public void getInputDevice_isInputDevice_returnsInputDevice() {
+        assertThat(getInputDevice(mContext, TEST_ADDRESS)).isEqualTo(mInputDevice);
+    }
+
+    @Test
+    public void isDeviceStylus_noDevices_false() {
+        assertThat(isDeviceStylus(null, null)).isFalse();
+    }
+
+    @Test
+    public void isDeviceStylus_nonStylusInputDevice_false() {
+        InputDevice inputDevice = new InputDevice.Builder()
+                .setSources(InputDevice.SOURCE_DPAD)
+                .build();
+
+        assertThat(isDeviceStylus(inputDevice, null)).isFalse();
+    }
+
+    @Test
+    public void isDeviceStylus_stylusInputDevice_true() {
+        InputDevice inputDevice = new InputDevice.Builder()
+                .setSources(InputDevice.SOURCE_STYLUS)
+                .build();
+
+        assertThat(isDeviceStylus(inputDevice, null)).isTrue();
+    }
+
+    @Test
+    public void isDeviceStylus_nonStylusBluetoothDevice_false() {
+        when(mBluetoothDevice.getMetadata(BluetoothDevice.METADATA_DEVICE_TYPE)).thenReturn(
+                BluetoothDevice.DEVICE_TYPE_WATCH.getBytes());
+
+        assertThat(isDeviceStylus(null, mCachedBluetoothDevice)).isFalse();
+    }
+
+    @Test
+    public void isDeviceStylus_stylusBluetoothDevice_true() {
+        when(mBluetoothDevice.getMetadata(BluetoothDevice.METADATA_DEVICE_TYPE)).thenReturn(
+                BluetoothDevice.DEVICE_TYPE_STYLUS.getBytes());
+        when(mCachedBluetoothDevice.getDevice()).thenReturn(mBluetoothDevice);
+
+        assertThat(isDeviceStylus(null, mCachedBluetoothDevice)).isTrue();
+    }
 }
diff --git a/packages/SettingsLib/tests/robotests/src/com/android/settingslib/bluetooth/CachedBluetoothDeviceManagerTest.java b/packages/SettingsLib/tests/robotests/src/com/android/settingslib/bluetooth/CachedBluetoothDeviceManagerTest.java
index 69e99c6..94199df 100644
--- a/packages/SettingsLib/tests/robotests/src/com/android/settingslib/bluetooth/CachedBluetoothDeviceManagerTest.java
+++ b/packages/SettingsLib/tests/robotests/src/com/android/settingslib/bluetooth/CachedBluetoothDeviceManagerTest.java
@@ -662,10 +662,10 @@
     @Test
     @RequiresFlagsEnabled(
             com.android.settingslib.flags.Flags.FLAG_HEARING_DEVICE_SET_CONNECTION_STATUS_REPORT)
-    public void onDeviceUnpaired_hearingDevice_callReportConnectionStatus() {
+    public void onDeviceUnpaired_containsHearingAidInfo_callReportConnectionStatus() {
         when(mDevice1.getBondState()).thenReturn(BluetoothDevice.BOND_BONDED);
-        when(mCachedDevice1.getProfiles()).thenReturn(
-                ImmutableList.of(mHapClientProfile, mHearingAidProfile));
+        mCachedDevice1.setHearingAidInfo(
+                new HearingAidInfo.Builder().setHiSyncId(HISYNCID1).build());
 
         mCachedDeviceManager.onDeviceUnpaired(mCachedDevice1);
 
diff --git a/packages/SettingsProvider/test/src/android/provider/SettingsBackupTest.java b/packages/SettingsProvider/test/src/android/provider/SettingsBackupTest.java
index 85617ba..70c042c 100644
--- a/packages/SettingsProvider/test/src/android/provider/SettingsBackupTest.java
+++ b/packages/SettingsProvider/test/src/android/provider/SettingsBackupTest.java
@@ -811,7 +811,7 @@
                  Settings.Secure.V_TO_U_RESTORE_ALLOWLIST,
                  Settings.Secure.V_TO_U_RESTORE_DENYLIST,
                  Settings.Secure.REDACT_OTP_NOTIFICATION_WHILE_CONNECTED_TO_WIFI,
-                 Settings.Secure.REDACT_OTP_NOTIFICATION_IMMEDIATELY);
+                 Settings.Secure.OTP_NOTIFICATION_REDACTION_LOCK_TIME);
 
     @Test
     public void systemSettingsBackedUpOrDenied() {
diff --git a/packages/SystemUI/aconfig/systemui.aconfig b/packages/SystemUI/aconfig/systemui.aconfig
index eb5b22f..f2c76ba 100644
--- a/packages/SystemUI/aconfig/systemui.aconfig
+++ b/packages/SystemUI/aconfig/systemui.aconfig
@@ -1939,6 +1939,16 @@
 }
 
 flag {
+   name: "notification_appear_nonlinear"
+   namespace: "systemui"
+   description: "Fix linear usage of notification appear"
+   bug: "397658189"
+   metadata {
+     purpose: PURPOSE_BUGFIX
+   }
+}
+
+flag {
   name: "disable_shade_trackpad_two_finger_swipe"
   namespace: "systemui"
   description: "Disables expansion of the shade via two finger swipe on a trackpad"
diff --git a/packages/SystemUI/animation/src/com/android/systemui/animation/FontInterpolator.kt b/packages/SystemUI/animation/src/com/android/systemui/animation/FontInterpolator.kt
index f8bcb81..bc75b1d 100644
--- a/packages/SystemUI/animation/src/com/android/systemui/animation/FontInterpolator.kt
+++ b/packages/SystemUI/animation/src/com/android/systemui/animation/FontInterpolator.kt
@@ -22,19 +22,8 @@
 import android.util.LruCache
 import android.util.MathUtils
 import androidx.annotation.VisibleForTesting
-import java.lang.Float.max
-import java.lang.Float.min
 import kotlin.math.roundToInt
 
-private const val TAG_WGHT = "wght"
-private const val TAG_ITAL = "ital"
-
-private const val FONT_WEIGHT_DEFAULT_VALUE = 400f
-private const val FONT_ITALIC_MAX = 1f
-private const val FONT_ITALIC_MIN = 0f
-private const val FONT_ITALIC_ANIMATION_STEP = 0.1f
-private const val FONT_ITALIC_DEFAULT_VALUE = 0f
-
 /** Caches for font interpolation */
 interface FontCache {
     val animationFrameCount: Int
@@ -91,11 +80,8 @@
 class FontInterpolator(val fontCache: FontCache = FontCacheImpl()) {
     /** Linear interpolate the font variation settings. */
     fun lerp(start: Font, end: Font, progress: Float, linearProgress: Float): Font {
-        if (progress == 0f) {
-            return start
-        } else if (progress == 1f) {
-            return end
-        }
+        if (progress <= 0f) return start
+        if (progress >= 1f) return end
 
         val startAxes = start.axes ?: EMPTY_AXES
         val endAxes = end.axes ?: EMPTY_AXES
@@ -110,7 +96,7 @@
             InterpKey(start, end, (linearProgress * fontCache.animationFrameCount).roundToInt())
         fontCache.get(iKey)?.let {
             if (DEBUG) {
-                Log.d(LOG_TAG, "[$progress] Interp. cache hit for $iKey")
+                Log.d(LOG_TAG, "[$progress, $linearProgress] Interp. cache hit for $iKey")
             }
             return it
         }
@@ -121,37 +107,16 @@
         // and also pre-fill the missing axes value with default value from 'fvar' table.
         val newAxes =
             lerp(startAxes, endAxes) { tag, startValue, endValue ->
-                when (tag) {
-                    TAG_WGHT ->
-                        MathUtils.lerp(
-                            startValue ?: FONT_WEIGHT_DEFAULT_VALUE,
-                            endValue ?: FONT_WEIGHT_DEFAULT_VALUE,
-                            progress,
-                        )
-                    TAG_ITAL ->
-                        adjustItalic(
-                            MathUtils.lerp(
-                                startValue ?: FONT_ITALIC_DEFAULT_VALUE,
-                                endValue ?: FONT_ITALIC_DEFAULT_VALUE,
-                                progress,
-                            )
-                        )
-                    else -> {
-                        require(startValue != null && endValue != null) {
-                            "Unable to interpolate due to unknown default axes value : $tag"
-                        }
-                        MathUtils.lerp(startValue, endValue, progress)
-                    }
-                }
+                MathUtils.lerp(startValue, endValue, progress)
             }
 
         // Check if we already make font for this axes. This is typically happens if the animation
-        // happens backward.
+        // happens backward and is being linearly interpolated.
         val vKey = VarFontKey(start, newAxes)
         fontCache.get(vKey)?.let {
             fontCache.put(iKey, it)
             if (DEBUG) {
-                Log.d(LOG_TAG, "[$progress] Axis cache hit for $vKey")
+                Log.d(LOG_TAG, "[$progress, $linearProgress] Axis cache hit for $vKey")
             }
             return it
         }
@@ -164,14 +129,14 @@
         fontCache.put(vKey, newFont)
 
         // Cache misses are likely to create memory leaks, so this is logged at error level.
-        Log.e(LOG_TAG, "[$progress] Cache MISS for $iKey / $vKey")
+        Log.e(LOG_TAG, "[$progress, $linearProgress] Cache MISS for $iKey / $vKey")
         return newFont
     }
 
     private fun lerp(
         start: Array<FontVariationAxis>,
         end: Array<FontVariationAxis>,
-        filter: (tag: String, left: Float?, right: Float?) -> Float,
+        filter: (tag: String, left: Float, right: Float) -> Float,
     ): List<FontVariationAxis> {
         // Safe to modify result of Font#getAxes since it returns cloned object.
         start.sortBy { axis -> axis.tag }
@@ -191,39 +156,37 @@
                     else -> tagA.compareTo(tagB)
                 }
 
-            val axis =
+            val tag =
                 when {
-                    comp == 0 -> {
-                        val v = filter(tagA!!, start[i++].styleValue, end[j++].styleValue)
-                        FontVariationAxis(tagA, v)
-                    }
-                    comp < 0 -> {
-                        val v = filter(tagA!!, start[i++].styleValue, null)
-                        FontVariationAxis(tagA, v)
-                    }
-                    else -> { // comp > 0
-                        val v = filter(tagB!!, null, end[j++].styleValue)
-                        FontVariationAxis(tagB, v)
-                    }
+                    comp == 0 -> tagA!!
+                    comp < 0 -> tagA!!
+                    else -> tagB!!
                 }
 
-            result.add(axis)
+            val axisDefinition = GSFAxes.getAxis(tag)
+            require(comp == 0 || axisDefinition != null) {
+                "Unable to interpolate due to unknown default axes value: $tag"
+            }
+
+            val axisValue =
+                when {
+                    comp == 0 -> filter(tag, start[i++].styleValue, end[j++].styleValue)
+                    comp < 0 -> filter(tag, start[i++].styleValue, axisDefinition!!.defaultValue)
+                    else -> filter(tag, axisDefinition!!.defaultValue, end[j++].styleValue)
+                }
+
+            // Round axis value to valid intermediate steps. This improves the cache hit rate.
+            val step = axisDefinition?.animationStep ?: DEFAULT_ANIMATION_STEP
+            result.add(FontVariationAxis(tag, (axisValue / step).roundToInt() * step))
         }
         return result
     }
 
-    // For the performance reasons, we animate italic with FONT_ITALIC_ANIMATION_STEP. This helps
-    // Cache hit ratio in the Skia glyph cache.
-    private fun adjustItalic(value: Float) =
-        coerceInWithStep(value, FONT_ITALIC_MIN, FONT_ITALIC_MAX, FONT_ITALIC_ANIMATION_STEP)
-
-    private fun coerceInWithStep(v: Float, min: Float, max: Float, step: Float) =
-        (v.coerceIn(min, max) / step).toInt() * step
-
     companion object {
         private const val LOG_TAG = "FontInterpolator"
         private val DEBUG = Log.isLoggable(LOG_TAG, Log.DEBUG)
         private val EMPTY_AXES = arrayOf<FontVariationAxis>()
+        private const val DEFAULT_ANIMATION_STEP = 1f
 
         // Returns true if given two font instance can be interpolated.
         fun canInterpolate(start: Font, end: Font) =
diff --git a/packages/SystemUI/animation/src/com/android/systemui/animation/FontVariationUtils.kt b/packages/SystemUI/animation/src/com/android/systemui/animation/FontVariationUtils.kt
index 9545bda..9a74687 100644
--- a/packages/SystemUI/animation/src/com/android/systemui/animation/FontVariationUtils.kt
+++ b/packages/SystemUI/animation/src/com/android/systemui/animation/FontVariationUtils.kt
@@ -1,12 +1,20 @@
-package com.android.systemui.animation
+/*
+ * Copyright (C) 2024 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
 
-object GSFAxes {
-    const val WEIGHT = "wght"
-    const val WIDTH = "wdth"
-    const val SLANT = "slnt"
-    const val ROUND = "ROND"
-    const val OPTICAL_SIZE = "opsz"
-}
+package com.android.systemui.animation
 
 class FontVariationUtils {
     private var mWeight = -1
@@ -46,20 +54,20 @@
         }
         var resultString = ""
         if (mWeight >= 0) {
-            resultString += "'${GSFAxes.WEIGHT}' $mWeight"
+            resultString += "'${GSFAxes.WEIGHT.tag}' $mWeight"
         }
         if (mWidth >= 0) {
             resultString +=
-                (if (resultString.isBlank()) "" else ", ") + "'${GSFAxes.WIDTH}' $mWidth"
+                (if (resultString.isBlank()) "" else ", ") + "'${GSFAxes.WIDTH.tag}' $mWidth"
         }
         if (mOpticalSize >= 0) {
             resultString +=
                 (if (resultString.isBlank()) "" else ", ") +
-                    "'${GSFAxes.OPTICAL_SIZE}' $mOpticalSize"
+                    "'${GSFAxes.OPTICAL_SIZE.tag}' $mOpticalSize"
         }
         if (mRoundness >= 0) {
             resultString +=
-                (if (resultString.isBlank()) "" else ", ") + "'${GSFAxes.ROUND}' $mRoundness"
+                (if (resultString.isBlank()) "" else ", ") + "'${GSFAxes.ROUND.tag}' $mRoundness"
         }
         return if (isUpdated) resultString else ""
     }
diff --git a/packages/SystemUI/animation/src/com/android/systemui/animation/GSFAxes.kt b/packages/SystemUI/animation/src/com/android/systemui/animation/GSFAxes.kt
new file mode 100644
index 0000000..f4e0361
--- /dev/null
+++ b/packages/SystemUI/animation/src/com/android/systemui/animation/GSFAxes.kt
@@ -0,0 +1,98 @@
+/*
+ * Copyright (C) 2025 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.systemui.animation
+
+data class AxisDefinition(
+    val tag: String,
+    val minValue: Float,
+    val defaultValue: Float,
+    val maxValue: Float,
+    val animationStep: Float,
+)
+
+object GSFAxes {
+    val WEIGHT =
+        AxisDefinition(
+            tag = "wght",
+            minValue = 1f,
+            defaultValue = 400f,
+            maxValue = 1000f,
+            animationStep = 10f,
+        )
+
+    val WIDTH =
+        AxisDefinition(
+            tag = "wdth",
+            minValue = 25f,
+            defaultValue = 100f,
+            maxValue = 151f,
+            animationStep = 1f,
+        )
+
+    val SLANT =
+        AxisDefinition(
+            tag = "slnt",
+            minValue = 0f,
+            defaultValue = 0f,
+            maxValue = -10f,
+            animationStep = 0.1f,
+        )
+
+    val ROUND =
+        AxisDefinition(
+            tag = "ROND",
+            minValue = 0f,
+            defaultValue = 0f,
+            maxValue = 100f,
+            animationStep = 1f,
+        )
+
+    val GRADE =
+        AxisDefinition(
+            tag = "GRAD",
+            minValue = 0f,
+            defaultValue = 0f,
+            maxValue = 100f,
+            animationStep = 1f,
+        )
+
+    val OPTICAL_SIZE =
+        AxisDefinition(
+            tag = "opsz",
+            minValue = 6f,
+            defaultValue = 18f,
+            maxValue = 144f,
+            animationStep = 1f,
+        )
+
+    // Not a GSF Axis, but present for FontInterpolator compatibility
+    val ITALIC =
+        AxisDefinition(
+            tag = "ITAL",
+            minValue = 0f,
+            defaultValue = 0f,
+            maxValue = 1f,
+            animationStep = 0.1f,
+        )
+
+    private val AXIS_MAP =
+        listOf(WEIGHT, WIDTH, SLANT, ROUND, GRADE, OPTICAL_SIZE, ITALIC)
+            .map { def -> def.tag.toLowerCase() to def }
+            .toMap()
+
+    fun getAxis(axis: String): AxisDefinition? = AXIS_MAP[axis.toLowerCase()]
+}
diff --git a/packages/SystemUI/compose/features/src/com/android/systemui/notifications/ui/composable/NotificationsShadeOverlay.kt b/packages/SystemUI/compose/features/src/com/android/systemui/notifications/ui/composable/NotificationsShadeOverlay.kt
index 297995b..7cd6c6b 100644
--- a/packages/SystemUI/compose/features/src/com/android/systemui/notifications/ui/composable/NotificationsShadeOverlay.kt
+++ b/packages/SystemUI/compose/features/src/com/android/systemui/notifications/ui/composable/NotificationsShadeOverlay.kt
@@ -124,7 +124,6 @@
                             SmallClock(
                                 burnInParams = burnIn.parameters,
                                 onTopChanged = burnIn.onSmallClockTopChanged,
-                                modifier = Modifier.fillMaxWidth(),
                             )
                         }
                     }
diff --git a/packages/SystemUI/compose/features/src/com/android/systemui/shade/ui/composable/OverlayShade.kt b/packages/SystemUI/compose/features/src/com/android/systemui/shade/ui/composable/OverlayShade.kt
index cdb1e2e..7e7b629 100644
--- a/packages/SystemUI/compose/features/src/com/android/systemui/shade/ui/composable/OverlayShade.kt
+++ b/packages/SystemUI/compose/features/src/com/android/systemui/shade/ui/composable/OverlayShade.kt
@@ -54,6 +54,7 @@
 import com.android.compose.animation.scene.LowestZIndexContentPicker
 import com.android.compose.windowsizeclass.LocalWindowSizeClass
 import com.android.systemui.res.R
+import androidx.compose.ui.unit.Dp
 
 /** Renders a lightweight shade UI container, as an overlay. */
 @Composable
@@ -202,10 +203,15 @@
     }
 
     object Dimensions {
-        val PanelCornerRadius = 46.dp
+        val PanelCornerRadius: Dp
+            @Composable
+            @ReadOnlyComposable get() =
+                dimensionResource(R.dimen.overlay_shade_panel_shape_radius)
     }
 
     object Shapes {
-        val RoundedCornerPanel = RoundedCornerShape(Dimensions.PanelCornerRadius)
+        val RoundedCornerPanel: RoundedCornerShape
+            @Composable
+            @ReadOnlyComposable get() = RoundedCornerShape(Dimensions.PanelCornerRadius)
     }
 }
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/customization/src/com/android/systemui/shared/clocks/FlexClockController.kt b/packages/SystemUI/customization/src/com/android/systemui/shared/clocks/FlexClockController.kt
index 004d1aa..ac1c5a8 100644
--- a/packages/SystemUI/customization/src/com/android/systemui/shared/clocks/FlexClockController.kt
+++ b/packages/SystemUI/customization/src/com/android/systemui/shared/clocks/FlexClockController.kt
@@ -130,39 +130,25 @@
 
         private val FONT_AXES =
             listOf(
-                ClockFontAxis(
-                    key = GSFAxes.WEIGHT,
+                GSFAxes.WEIGHT.toClockAxis(
                     type = AxisType.Float,
-                    minValue = 25f,
                     currentValue = 400f,
-                    maxValue = 1000f,
                     name = "Weight",
                     description = "Glyph Weight",
                 ),
-                ClockFontAxis(
-                    key = GSFAxes.WIDTH,
+                GSFAxes.WIDTH.toClockAxis(
                     type = AxisType.Float,
-                    minValue = 25f,
                     currentValue = 85f,
-                    maxValue = 151f,
                     name = "Width",
                     description = "Glyph Width",
                 ),
-                ClockFontAxis(
-                    key = GSFAxes.ROUND,
+                GSFAxes.ROUND.toClockAxis(
                     type = AxisType.Boolean,
-                    minValue = 0f,
-                    currentValue = 0f,
-                    maxValue = 100f,
                     name = "Round",
                     description = "Glyph Roundness",
                 ),
-                ClockFontAxis(
-                    key = GSFAxes.SLANT,
+                GSFAxes.SLANT.toClockAxis(
                     type = AxisType.Boolean,
-                    minValue = 0f,
-                    currentValue = 0f,
-                    maxValue = -10f,
                     name = "Slant",
                     description = "Glyph Slant",
                 ),
@@ -170,10 +156,10 @@
 
         private val LEGACY_FLEX_SETTINGS =
             listOf(
-                ClockFontAxisSetting(GSFAxes.WEIGHT, 600f),
-                ClockFontAxisSetting(GSFAxes.WIDTH, 100f),
-                ClockFontAxisSetting(GSFAxes.ROUND, 100f),
-                ClockFontAxisSetting(GSFAxes.SLANT, 0f),
+                GSFAxes.WEIGHT.toClockAxisSetting(600f),
+                GSFAxes.WIDTH.toClockAxisSetting(100f),
+                GSFAxes.ROUND.toClockAxisSetting(100f),
+                GSFAxes.SLANT.toClockAxisSetting(0f),
             )
     }
 }
diff --git a/packages/SystemUI/customization/src/com/android/systemui/shared/clocks/FlexClockFaceController.kt b/packages/SystemUI/customization/src/com/android/systemui/shared/clocks/FlexClockFaceController.kt
index b2dbd65..b4c2f5d 100644
--- a/packages/SystemUI/customization/src/com/android/systemui/shared/clocks/FlexClockFaceController.kt
+++ b/packages/SystemUI/customization/src/com/android/systemui/shared/clocks/FlexClockFaceController.kt
@@ -132,7 +132,7 @@
             if (!isLargeClock) {
                 axes =
                     axes.map { axis ->
-                        if (axis.key == GSFAxes.WIDTH && axis.value > SMALL_CLOCK_MAX_WDTH) {
+                        if (axis.key == GSFAxes.WIDTH.tag && axis.value > SMALL_CLOCK_MAX_WDTH) {
                             axis.copy(value = SMALL_CLOCK_MAX_WDTH)
                         } else {
                             axis
diff --git a/packages/SystemUI/customization/src/com/android/systemui/shared/clocks/FontUtils.kt b/packages/SystemUI/customization/src/com/android/systemui/shared/clocks/FontUtils.kt
new file mode 100644
index 0000000..212b1e2
--- /dev/null
+++ b/packages/SystemUI/customization/src/com/android/systemui/shared/clocks/FontUtils.kt
@@ -0,0 +1,43 @@
+/*
+ * Copyright (C) 2025 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.systemui.shared.clocks
+
+import com.android.systemui.animation.AxisDefinition
+import com.android.systemui.plugins.clocks.AxisType
+import com.android.systemui.plugins.clocks.ClockFontAxis
+import com.android.systemui.plugins.clocks.ClockFontAxisSetting
+
+fun AxisDefinition.toClockAxis(
+    type: AxisType,
+    currentValue: Float? = null,
+    name: String,
+    description: String,
+): ClockFontAxis {
+    return ClockFontAxis(
+        key = this.tag,
+        type = type,
+        maxValue = this.maxValue,
+        minValue = this.minValue,
+        currentValue = currentValue ?: this.defaultValue,
+        name = name,
+        description = description,
+    )
+}
+
+fun AxisDefinition.toClockAxisSetting(value: Float? = null): ClockFontAxisSetting {
+    return ClockFontAxisSetting(this.tag, value ?: this.defaultValue)
+}
diff --git a/packages/SystemUI/customization/src/com/android/systemui/shared/clocks/view/SimpleDigitalClockTextView.kt b/packages/SystemUI/customization/src/com/android/systemui/shared/clocks/view/SimpleDigitalClockTextView.kt
index 54be4d8..b7ce20e 100644
--- a/packages/SystemUI/customization/src/com/android/systemui/shared/clocks/view/SimpleDigitalClockTextView.kt
+++ b/packages/SystemUI/customization/src/com/android/systemui/shared/clocks/view/SimpleDigitalClockTextView.kt
@@ -49,6 +49,7 @@
 import com.android.systemui.shared.clocks.DimensionParser
 import com.android.systemui.shared.clocks.FLEX_CLOCK_ID
 import com.android.systemui.shared.clocks.FontTextStyle
+import com.android.systemui.shared.clocks.toClockAxisSetting
 import java.lang.Thread
 import kotlin.math.max
 import kotlin.math.min
@@ -585,25 +586,25 @@
         val FIDGET_INTERPOLATOR = PathInterpolator(0.26873f, 0f, 0.45042f, 1f)
         val FIDGET_DISTS =
             mapOf(
-                GSFAxes.WEIGHT to Pair(200f, 500f),
-                GSFAxes.WIDTH to Pair(30f, 75f),
-                GSFAxes.ROUND to Pair(0f, 50f),
-                GSFAxes.SLANT to Pair(0f, -5f),
+                GSFAxes.WEIGHT.tag to Pair(200f, 500f),
+                GSFAxes.WIDTH.tag to Pair(30f, 75f),
+                GSFAxes.ROUND.tag to Pair(0f, 50f),
+                GSFAxes.SLANT.tag to Pair(0f, -5f),
             )
 
         val AOD_COLOR = Color.WHITE
-        val LS_WEIGHT_AXIS = ClockFontAxisSetting(GSFAxes.WEIGHT, 400f)
-        val AOD_WEIGHT_AXIS = ClockFontAxisSetting(GSFAxes.WEIGHT, 200f)
-        val WIDTH_AXIS = ClockFontAxisSetting(GSFAxes.WIDTH, 85f)
-        val ROUND_AXIS = ClockFontAxisSetting(GSFAxes.ROUND, 0f)
-        val SLANT_AXIS = ClockFontAxisSetting(GSFAxes.SLANT, 0f)
+        val LS_WEIGHT_AXIS = GSFAxes.WEIGHT.toClockAxisSetting(400f)
+        val AOD_WEIGHT_AXIS = GSFAxes.WEIGHT.toClockAxisSetting(200f)
+        val WIDTH_AXIS = GSFAxes.WIDTH.toClockAxisSetting(85f)
+        val ROUND_AXIS = GSFAxes.ROUND.toClockAxisSetting(0f)
+        val SLANT_AXIS = GSFAxes.SLANT.toClockAxisSetting(0f)
 
         // Axes for Legacy version of the Flex Clock
-        val FLEX_LS_WEIGHT_AXIS = ClockFontAxisSetting(GSFAxes.WEIGHT, 600f)
-        val FLEX_AOD_LARGE_WEIGHT_AXIS = ClockFontAxisSetting(GSFAxes.WEIGHT, 74f)
-        val FLEX_AOD_SMALL_WEIGHT_AXIS = ClockFontAxisSetting(GSFAxes.WEIGHT, 133f)
-        val FLEX_LS_WIDTH_AXIS = ClockFontAxisSetting(GSFAxes.WIDTH, 100f)
-        val FLEX_AOD_WIDTH_AXIS = ClockFontAxisSetting(GSFAxes.WIDTH, 43f)
-        val FLEX_ROUND_AXIS = ClockFontAxisSetting(GSFAxes.ROUND, 100f)
+        val FLEX_LS_WEIGHT_AXIS = GSFAxes.WEIGHT.toClockAxisSetting(600f)
+        val FLEX_AOD_LARGE_WEIGHT_AXIS = GSFAxes.WEIGHT.toClockAxisSetting(74f)
+        val FLEX_AOD_SMALL_WEIGHT_AXIS = GSFAxes.WEIGHT.toClockAxisSetting(133f)
+        val FLEX_LS_WIDTH_AXIS = GSFAxes.WIDTH.toClockAxisSetting(100f)
+        val FLEX_AOD_WIDTH_AXIS = GSFAxes.WIDTH.toClockAxisSetting(43f)
+        val FLEX_ROUND_AXIS = GSFAxes.ROUND.toClockAxisSetting(100f)
     }
 }
diff --git a/packages/SystemUI/multivalentTests/src/com/android/keyguard/EmergencyButtonControllerTest.kt b/packages/SystemUI/multivalentTests/src/com/android/keyguard/EmergencyButtonControllerTest.kt
index c42e25b..d046ad1 100644
--- a/packages/SystemUI/multivalentTests/src/com/android/keyguard/EmergencyButtonControllerTest.kt
+++ b/packages/SystemUI/multivalentTests/src/com/android/keyguard/EmergencyButtonControllerTest.kt
@@ -103,7 +103,7 @@
     fun testUpdateEmergencyButton() {
         Mockito.`when`(telecomManager.isInCall).thenReturn(true)
         Mockito.`when`(lockPatternUtils.isSecure(anyInt())).thenReturn(true)
-        Mockito.`when`(packageManager.hasSystemFeature(PackageManager.FEATURE_TELEPHONY))
+        Mockito.`when`(packageManager.hasSystemFeature(PackageManager.FEATURE_TELEPHONY_CALLING))
             .thenReturn(true)
         underTest.updateEmergencyCallButton()
         backgroundExecutor.runAllReady()
@@ -112,7 +112,7 @@
                 /* isInCall= */ any(),
                 /* hasTelephonyRadio= */ any(),
                 /* simLocked= */ any(),
-                /* isSecure= */ any()
+                /* isSecure= */ any(),
             )
         mainExecutor.runAllReady()
         verify(emergencyButton)
@@ -120,7 +120,7 @@
                 /* isInCall= */ eq(true),
                 /* hasTelephonyRadio= */ eq(true),
                 /* simLocked= */ any(),
-                /* isSecure= */ eq(true)
+                /* isSecure= */ eq(true),
             )
     }
 
diff --git a/packages/SystemUI/multivalentTests/src/com/android/systemui/animation/FontVariationUtilsTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/animation/FontVariationUtilsTest.kt
index f44769d..8d3640d 100644
--- a/packages/SystemUI/multivalentTests/src/com/android/systemui/animation/FontVariationUtilsTest.kt
+++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/animation/FontVariationUtilsTest.kt
@@ -21,7 +21,7 @@
                 roundness = 100,
             )
         Assert.assertEquals(
-            "'${GSFAxes.WEIGHT}' 100, '${GSFAxes.WIDTH}' 100, '${GSFAxes.ROUND}' 100",
+            "'${GSFAxes.WEIGHT.tag}' 100, '${GSFAxes.WIDTH.tag}' 100, '${GSFAxes.ROUND.tag}' 100",
             initFvar,
         )
         val updatedFvar =
@@ -32,7 +32,8 @@
                 roundness = 100,
             )
         Assert.assertEquals(
-            "'${GSFAxes.WEIGHT}' 200, '${GSFAxes.WIDTH}' 100, '${GSFAxes.OPTICAL_SIZE}' 0, '${GSFAxes.ROUND}' 100",
+            "'${GSFAxes.WEIGHT.tag}' 200, '${GSFAxes.WIDTH.tag}' 100," +
+                " '${GSFAxes.OPTICAL_SIZE.tag}' 0, '${GSFAxes.ROUND.tag}' 100",
             updatedFvar,
         )
     }
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/qs/tiles/CastTileTest.java b/packages/SystemUI/multivalentTests/src/com/android/systemui/qs/tiles/CastTileTest.java
index 31a627f..765c574 100644
--- a/packages/SystemUI/multivalentTests/src/com/android/systemui/qs/tiles/CastTileTest.java
+++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/qs/tiles/CastTileTest.java
@@ -50,6 +50,8 @@
 import com.android.systemui.qs.QSHost;
 import com.android.systemui.qs.QsEventLogger;
 import com.android.systemui.qs.logging.QSLogger;
+import com.android.systemui.shade.domain.interactor.FakeShadeDialogContextInteractor;
+import com.android.systemui.shade.domain.interactor.ShadeDialogContextInteractor;
 import com.android.systemui.statusbar.connectivity.IconState;
 import com.android.systemui.statusbar.connectivity.NetworkController;
 import com.android.systemui.statusbar.connectivity.SignalCallback;
@@ -107,6 +109,8 @@
     private final FakeConnectivityRepository mConnectivityRepository =
             new FakeConnectivityRepository();
     private final FakeFeatureFlags mFeatureFlags = new FakeFeatureFlags();
+    private final ShadeDialogContextInteractor mShadeDialogContextInteractor =
+            new FakeShadeDialogContextInteractor(mContext);
 
     private TestableLooper mTestableLooper;
     private CastTile mCastTile;
@@ -535,7 +539,8 @@
                 mDialogTransitionAnimator,
                 mConnectivityRepository,
                 mJavaAdapter,
-                mFeatureFlags
+                mFeatureFlags,
+                mShadeDialogContextInteractor
         );
         mCastTile.initialize();
 
@@ -578,7 +583,8 @@
                 mDialogTransitionAnimator,
                 mConnectivityRepository,
                 mJavaAdapter,
-                mFeatureFlags
+                mFeatureFlags,
+                mShadeDialogContextInteractor
         );
         mCastTile.initialize();
 
diff --git a/packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/NotificationShadeDepthControllerTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/NotificationShadeDepthControllerTest.kt
index 83361da..4b06cde 100644
--- a/packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/NotificationShadeDepthControllerTest.kt
+++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/NotificationShadeDepthControllerTest.kt
@@ -34,7 +34,6 @@
 import com.android.systemui.plugins.statusbar.StatusBarStateController
 import com.android.systemui.res.R
 import com.android.systemui.shade.ShadeExpansionChangeEvent
-import com.android.systemui.shared.Flags as SharedFlags
 import com.android.systemui.statusbar.phone.BiometricUnlockController
 import com.android.systemui.statusbar.phone.DozeParameters
 import com.android.systemui.statusbar.phone.ScrimController
@@ -44,11 +43,10 @@
 import com.android.systemui.testKosmos
 import com.android.systemui.util.WallpaperController
 import com.android.systemui.util.mockito.eq
+import com.android.systemui.wallpapers.domain.interactor.WallpaperInteractor
 import com.android.systemui.window.domain.interactor.WindowRootViewBlurInteractor
 import com.android.wm.shell.appzoomout.AppZoomOut
 import com.google.common.truth.Truth.assertThat
-import java.util.Optional
-import java.util.function.Consumer
 import org.junit.Before
 import org.junit.Rule
 import org.junit.Test
@@ -69,6 +67,8 @@
 import org.mockito.Mockito.verify
 import org.mockito.Mockito.`when`
 import org.mockito.junit.MockitoJUnit
+import java.util.Optional
+import java.util.function.Consumer
 
 @RunWith(AndroidJUnit4::class)
 @RunWithLooper
@@ -76,6 +76,7 @@
 class NotificationShadeDepthControllerTest : SysuiTestCase() {
     private val kosmos = testKosmos()
 
+    private val applicationScope = kosmos.testScope.backgroundScope
     @Mock private lateinit var windowRootViewBlurInteractor: WindowRootViewBlurInteractor
     @Mock private lateinit var statusBarStateController: StatusBarStateController
     @Mock private lateinit var blurUtils: BlurUtils
@@ -84,6 +85,7 @@
     @Mock private lateinit var keyguardInteractor: KeyguardInteractor
     @Mock private lateinit var choreographer: Choreographer
     @Mock private lateinit var wallpaperController: WallpaperController
+    @Mock private lateinit var wallpaperInteractor: WallpaperInteractor
     @Mock private lateinit var notificationShadeWindowController: NotificationShadeWindowController
     @Mock private lateinit var dumpManager: DumpManager
     @Mock private lateinit var appZoomOutOptional: Optional<AppZoomOut>
@@ -128,12 +130,14 @@
                 keyguardInteractor,
                 choreographer,
                 wallpaperController,
+                wallpaperInteractor,
                 notificationShadeWindowController,
                 dozeParameters,
                 context,
                 ResourcesSplitShadeStateController(),
                 windowRootViewBlurInteractor,
                 appZoomOutOptional,
+                applicationScope,
                 dumpManager,
                 configurationController,
             )
@@ -310,22 +314,24 @@
     }
 
     @Test
-    @DisableFlags(SharedFlags.FLAG_AMBIENT_AOD)
-    fun onDozeAmountChanged_appliesBlur() {
-        statusBarStateListener.onDozeAmountChanged(1f, 1f)
-        notificationShadeDepthController.updateBlurCallback.doFrame(0)
-        verify(blurUtils).applyBlur(any(), eq(maxBlur), eq(false))
-    }
-
-    @Test
-    @EnableFlags(SharedFlags.FLAG_AMBIENT_AOD)
     fun onDozeAmountChanged_doesNotApplyBlurWithAmbientAod() {
+        notificationShadeDepthController.wallpaperSupportsAmbientMode = false
+
         statusBarStateListener.onDozeAmountChanged(1f, 1f)
         notificationShadeDepthController.updateBlurCallback.doFrame(0)
         verify(blurUtils).applyBlur(any(), eq(0), eq(false))
     }
 
     @Test
+    fun onDozeAmountChanged_appliesBlurWithAmbientAod() {
+        notificationShadeDepthController.wallpaperSupportsAmbientMode = true
+
+        statusBarStateListener.onDozeAmountChanged(1f, 1f)
+        notificationShadeDepthController.updateBlurCallback.doFrame(0)
+        verify(blurUtils).applyBlur(any(), eq(maxBlur), eq(false))
+    }
+
+    @Test
     fun setFullShadeTransition_appliesBlur_onlyIfSupported() {
         reset(blurUtils)
         `when`(blurUtils.blurRadiusOfRatio(anyFloat())).then { answer ->
diff --git a/packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/chips/call/domain/interactor/CallChipInteractorTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/chips/call/domain/interactor/CallChipInteractorTest.kt
index e38ea30..9fd189f 100644
--- a/packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/chips/call/domain/interactor/CallChipInteractorTest.kt
+++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/chips/call/domain/interactor/CallChipInteractorTest.kt
@@ -20,11 +20,14 @@
 import androidx.test.filters.SmallTest
 import com.android.systemui.SysuiTestCase
 import com.android.systemui.coroutines.collectLastValue
-import com.android.systemui.kosmos.Kosmos
-import com.android.systemui.kosmos.testScope
+import com.android.systemui.kosmos.collectLastValue
+import com.android.systemui.kosmos.runTest
+import com.android.systemui.kosmos.useUnconfinedTestDispatcher
 import com.android.systemui.statusbar.phone.ongoingcall.data.repository.ongoingCallRepository
 import com.android.systemui.statusbar.phone.ongoingcall.shared.model.OngoingCallModel
-import com.android.systemui.statusbar.phone.ongoingcall.shared.model.inCallModel
+import com.android.systemui.statusbar.phone.ongoingcall.shared.model.OngoingCallTestHelper.addOngoingCallState
+import com.android.systemui.statusbar.phone.ongoingcall.shared.model.OngoingCallTestHelper.removeOngoingCallState
+import com.android.systemui.testKosmos
 import com.google.common.truth.Truth.assertThat
 import kotlin.test.Test
 import kotlinx.coroutines.test.runTest
@@ -33,22 +36,20 @@
 @SmallTest
 @RunWith(AndroidJUnit4::class)
 class CallChipInteractorTest : SysuiTestCase() {
-    val kosmos = Kosmos()
+    val kosmos = testKosmos().useUnconfinedTestDispatcher()
     val repo = kosmos.ongoingCallRepository
 
     val underTest = kosmos.callChipInteractor
 
     @Test
-    fun ongoingCallState_matchesRepo() =
-        kosmos.testScope.runTest {
+    fun ongoingCallState_matchesState() =
+        kosmos.runTest {
             val latest by collectLastValue(underTest.ongoingCallState)
 
-            val inCall = inCallModel(startTimeMs = 1000)
-            repo.setOngoingCallState(inCall)
-            assertThat(latest).isEqualTo(inCall)
+            addOngoingCallState(key = "testKey")
+            assertThat(latest).isInstanceOf(OngoingCallModel.InCall::class.java)
 
-            val noCall = OngoingCallModel.NoCall
-            repo.setOngoingCallState(noCall)
-            assertThat(latest).isEqualTo(noCall)
+            removeOngoingCallState(key = "testKey")
+            assertThat(latest).isEqualTo(OngoingCallModel.NoCall)
         }
 }
diff --git a/packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/chips/call/ui/viewmodel/CallChipViewModelTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/chips/call/ui/viewmodel/CallChipViewModelTest.kt
index e044d1d..fda4ab0 100644
--- a/packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/chips/call/ui/viewmodel/CallChipViewModelTest.kt
+++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/chips/call/ui/viewmodel/CallChipViewModelTest.kt
@@ -27,7 +27,9 @@
 import com.android.systemui.common.shared.model.ContentDescription.Companion.loadContentDescription
 import com.android.systemui.common.shared.model.Icon
 import com.android.systemui.coroutines.collectLastValue
-import com.android.systemui.kosmos.testScope
+import com.android.systemui.kosmos.collectLastValue
+import com.android.systemui.kosmos.runTest
+import com.android.systemui.kosmos.useUnconfinedTestDispatcher
 import com.android.systemui.plugins.activityStarter
 import com.android.systemui.res.R
 import com.android.systemui.statusbar.StatusBarIconView
@@ -35,17 +37,11 @@
 import com.android.systemui.statusbar.chips.ui.model.OngoingActivityChipModel
 import com.android.systemui.statusbar.chips.ui.view.ChipBackgroundContainer
 import com.android.systemui.statusbar.core.StatusBarConnectedDisplays
-import com.android.systemui.statusbar.notification.data.model.activeNotificationModel
-import com.android.systemui.statusbar.notification.data.repository.ActiveNotificationListRepository
-import com.android.systemui.statusbar.notification.data.repository.ActiveNotificationsStore
-import com.android.systemui.statusbar.notification.data.repository.activeNotificationListRepository
 import com.android.systemui.statusbar.notification.promoted.shared.model.PromotedNotificationContentModel
-import com.android.systemui.statusbar.notification.shared.CallType
 import com.android.systemui.statusbar.phone.ongoingcall.DisableChipsModernization
 import com.android.systemui.statusbar.phone.ongoingcall.EnableChipsModernization
-import com.android.systemui.statusbar.phone.ongoingcall.data.repository.ongoingCallRepository
-import com.android.systemui.statusbar.phone.ongoingcall.shared.model.OngoingCallModel
-import com.android.systemui.statusbar.phone.ongoingcall.shared.model.inCallModel
+import com.android.systemui.statusbar.phone.ongoingcall.shared.model.OngoingCallTestHelper.addOngoingCallState
+import com.android.systemui.statusbar.phone.ongoingcall.shared.model.OngoingCallTestHelper.removeOngoingCallState
 import com.android.systemui.testKosmos
 import com.android.systemui.util.time.fakeSystemClock
 import com.google.common.truth.Truth.assertThat
@@ -60,10 +56,7 @@
 @SmallTest
 @RunWith(AndroidJUnit4::class)
 class CallChipViewModelTest : SysuiTestCase() {
-    private val kosmos = testKosmos()
-    private val notificationListRepository = kosmos.activeNotificationListRepository
-    private val testScope = kosmos.testScope
-    private val repo = kosmos.ongoingCallRepository
+    private val kosmos = testKosmos().useUnconfinedTestDispatcher()
 
     private val chipBackgroundView = mock<ChipBackgroundContainer>()
     private val chipView =
@@ -82,53 +75,53 @@
 
     @Test
     fun chip_noCall_isHidden() =
-        testScope.runTest {
+        kosmos.runTest {
             val latest by collectLastValue(underTest.chip)
 
-            repo.setOngoingCallState(OngoingCallModel.NoCall)
+            removeOngoingCallState("testKey")
 
             assertThat(latest).isInstanceOf(OngoingActivityChipModel.Inactive::class.java)
         }
 
     @Test
     fun chip_inCall_zeroStartTime_isShownAsIconOnly() =
-        testScope.runTest {
+        kosmos.runTest {
             val latest by collectLastValue(underTest.chip)
 
-            repo.setOngoingCallState(inCallModel(startTimeMs = 0))
+            addOngoingCallState(startTimeMs = 0)
 
             assertThat(latest).isInstanceOf(OngoingActivityChipModel.Active.IconOnly::class.java)
         }
 
     @Test
     fun chip_inCall_negativeStartTime_isShownAsIconOnly() =
-        testScope.runTest {
+        kosmos.runTest {
             val latest by collectLastValue(underTest.chip)
 
-            repo.setOngoingCallState(inCallModel(startTimeMs = -2))
+            addOngoingCallState(startTimeMs = -2)
 
             assertThat(latest).isInstanceOf(OngoingActivityChipModel.Active.IconOnly::class.java)
         }
 
     @Test
     fun chip_inCall_positiveStartTime_isShownAsTimer() =
-        testScope.runTest {
+        kosmos.runTest {
             val latest by collectLastValue(underTest.chip)
 
-            repo.setOngoingCallState(inCallModel(startTimeMs = 345))
+            addOngoingCallState(startTimeMs = 345)
 
             assertThat(latest).isInstanceOf(OngoingActivityChipModel.Active.Timer::class.java)
         }
 
     @Test
     fun chip_inCall_startTimeConvertedToElapsedRealtime() =
-        testScope.runTest {
+        kosmos.runTest {
             val latest by collectLastValue(underTest.chip)
 
             kosmos.fakeSystemClock.setCurrentTimeMillis(3000)
             kosmos.fakeSystemClock.setElapsedRealtime(400_000)
 
-            repo.setOngoingCallState(inCallModel(startTimeMs = 1000))
+            addOngoingCallState(startTimeMs = 1000)
 
             // The OngoingCallModel start time is relative to currentTimeMillis, so this call
             // started 2000ms ago (1000 - 3000). The OngoingActivityChipModel start time needs to be
@@ -141,13 +134,11 @@
     @Test
     @EnableFlags(StatusBarConnectedDisplays.FLAG_NAME)
     fun chip_positiveStartTime_connectedDisplaysFlagOn_iconIsNotifIcon() =
-        testScope.runTest {
+        kosmos.runTest {
             val latest by collectLastValue(underTest.chip)
 
             val notifKey = "testNotifKey"
-            repo.setOngoingCallState(
-                inCallModel(startTimeMs = 1000, notificationIcon = null, notificationKey = notifKey)
-            )
+            addOngoingCallState(startTimeMs = 1000, statusBarChipIconView = null, key = notifKey)
 
             assertThat((latest as OngoingActivityChipModel.Active).icon)
                 .isInstanceOf(
@@ -163,16 +154,14 @@
     @Test
     @DisableFlags(StatusBarConnectedDisplays.FLAG_NAME)
     fun chip_zeroStartTime_cdFlagOff_iconIsNotifIcon_withContentDescription() =
-        testScope.runTest {
+        kosmos.runTest {
             val latest by collectLastValue(underTest.chip)
 
             val notifIcon = createStatusBarIconViewOrNull()
-            repo.setOngoingCallState(
-                inCallModel(
-                    startTimeMs = 0,
-                    notificationIcon = notifIcon,
-                    appName = "Fake app name",
-                )
+            addOngoingCallState(
+                startTimeMs = 0,
+                statusBarChipIconView = notifIcon,
+                appName = "Fake app name",
             )
 
             assertThat((latest as OngoingActivityChipModel.Active).icon)
@@ -190,16 +179,13 @@
     @Test
     @EnableFlags(StatusBarConnectedDisplays.FLAG_NAME)
     fun chip_zeroStartTime_cdFlagOn_iconIsNotifKeyIcon_withContentDescription() =
-        testScope.runTest {
+        kosmos.runTest {
             val latest by collectLastValue(underTest.chip)
 
-            repo.setOngoingCallState(
-                inCallModel(
-                    startTimeMs = 0,
-                    notificationIcon = createStatusBarIconViewOrNull(),
-                    notificationKey = "notifKey",
-                    appName = "Fake app name",
-                )
+            addOngoingCallState(
+                key = "notifKey",
+                statusBarChipIconView = createStatusBarIconViewOrNull(),
+                appName = "Fake app name",
             )
 
             assertThat((latest as OngoingActivityChipModel.Active).icon)
@@ -219,10 +205,10 @@
     @Test
     @DisableFlags(StatusBarConnectedDisplays.FLAG_NAME)
     fun chip_notifIconFlagOn_butNullNotifIcon_cdFlagOff_iconIsPhone() =
-        testScope.runTest {
+        kosmos.runTest {
             val latest by collectLastValue(underTest.chip)
 
-            repo.setOngoingCallState(inCallModel(startTimeMs = 1000, notificationIcon = null))
+            addOngoingCallState(statusBarChipIconView = null)
 
             assertThat((latest as OngoingActivityChipModel.Active).icon)
                 .isInstanceOf(OngoingActivityChipModel.ChipIcon.SingleColorIcon::class.java)
@@ -237,16 +223,13 @@
     @Test
     @EnableFlags(StatusBarConnectedDisplays.FLAG_NAME)
     fun chip_notifIconFlagOn_butNullNotifIcon_cdFlagOn_iconIsNotifKeyIcon_withContentDescription() =
-        testScope.runTest {
+        kosmos.runTest {
             val latest by collectLastValue(underTest.chip)
 
-            repo.setOngoingCallState(
-                inCallModel(
-                    startTimeMs = 1000,
-                    notificationIcon = null,
-                    notificationKey = "notifKey",
-                    appName = "Fake app name",
-                )
+            addOngoingCallState(
+                key = "notifKey",
+                statusBarChipIconView = null,
+                appName = "Fake app name",
             )
 
             assertThat((latest as OngoingActivityChipModel.Active).icon)
@@ -265,10 +248,10 @@
 
     @Test
     fun chip_positiveStartTime_colorsAreAccentThemed() =
-        testScope.runTest {
+        kosmos.runTest {
             val latest by collectLastValue(underTest.chip)
 
-            repo.setOngoingCallState(inCallModel(startTimeMs = 1000, promotedContent = null))
+            addOngoingCallState(startTimeMs = 1000, promotedContent = null)
 
             assertThat((latest as OngoingActivityChipModel.Active).colors)
                 .isEqualTo(ColorsModel.AccentThemed)
@@ -276,10 +259,10 @@
 
     @Test
     fun chip_zeroStartTime_colorsAreAccentThemed() =
-        testScope.runTest {
+        kosmos.runTest {
             val latest by collectLastValue(underTest.chip)
 
-            repo.setOngoingCallState(inCallModel(startTimeMs = 0, promotedContent = null))
+            addOngoingCallState(startTimeMs = 0, promotedContent = null)
 
             assertThat((latest as OngoingActivityChipModel.Active).colors)
                 .isEqualTo(ColorsModel.AccentThemed)
@@ -287,19 +270,19 @@
 
     @Test
     fun chip_resetsCorrectly() =
-        testScope.runTest {
+        kosmos.runTest {
             val latest by collectLastValue(underTest.chip)
             kosmos.fakeSystemClock.setCurrentTimeMillis(3000)
             kosmos.fakeSystemClock.setElapsedRealtime(400_000)
 
             // Start a call
-            repo.setOngoingCallState(inCallModel(startTimeMs = 1000))
+            addOngoingCallState(key = "testKey", startTimeMs = 1000)
             assertThat(latest).isInstanceOf(OngoingActivityChipModel.Active::class.java)
             assertThat((latest as OngoingActivityChipModel.Active.Timer).startTimeMs)
                 .isEqualTo(398_000)
 
             // End the call
-            repo.setOngoingCallState(OngoingCallModel.NoCall)
+            removeOngoingCallState(key = "testKey")
             assertThat(latest).isInstanceOf(OngoingActivityChipModel.Inactive::class.java)
 
             // Let 100_000ms elapse
@@ -307,7 +290,7 @@
             kosmos.fakeSystemClock.setElapsedRealtime(500_000)
 
             // Start a new call, which started 1000ms ago
-            repo.setOngoingCallState(inCallModel(startTimeMs = 102_000))
+            addOngoingCallState(key = "testKey", startTimeMs = 102_000)
             assertThat(latest).isInstanceOf(OngoingActivityChipModel.Active::class.java)
             assertThat((latest as OngoingActivityChipModel.Active.Timer).startTimeMs)
                 .isEqualTo(499_000)
@@ -316,10 +299,10 @@
     @Test
     @DisableChipsModernization
     fun chip_inCall_nullIntent_nullClickListener() =
-        testScope.runTest {
+        kosmos.runTest {
             val latest by collectLastValue(underTest.chip)
 
-            repo.setOngoingCallState(inCallModel(startTimeMs = 1000, intent = null))
+            addOngoingCallState(contentIntent = null)
 
             assertThat((latest as OngoingActivityChipModel.Active).onClickListenerLegacy).isNull()
         }
@@ -327,11 +310,11 @@
     @Test
     @DisableChipsModernization
     fun chip_inCall_positiveStartTime_validIntent_clickListenerLaunchesIntent() =
-        testScope.runTest {
+        kosmos.runTest {
             val latest by collectLastValue(underTest.chip)
 
             val pendingIntent = mock<PendingIntent>()
-            repo.setOngoingCallState(inCallModel(startTimeMs = 1000, intent = pendingIntent))
+            addOngoingCallState(startTimeMs = 1000, contentIntent = pendingIntent)
             val clickListener = (latest as OngoingActivityChipModel.Active).onClickListenerLegacy
             assertThat(clickListener).isNotNull()
 
@@ -345,11 +328,11 @@
     @Test
     @DisableChipsModernization
     fun chip_inCall_zeroStartTime_validIntent_clickListenerLaunchesIntent() =
-        testScope.runTest {
+        kosmos.runTest {
             val latest by collectLastValue(underTest.chip)
 
             val pendingIntent = mock<PendingIntent>()
-            repo.setOngoingCallState(inCallModel(startTimeMs = 0, intent = pendingIntent))
+            addOngoingCallState(startTimeMs = 0, contentIntent = pendingIntent)
             val clickListener = (latest as OngoingActivityChipModel.Active).onClickListenerLegacy
 
             assertThat(clickListener).isNotNull()
@@ -364,14 +347,10 @@
     @Test
     @EnableChipsModernization
     fun chip_inCall_nullIntent_noneClickBehavior() =
-        testScope.runTest {
+        kosmos.runTest {
             val latest by collectLastValue(underTest.chip)
 
-            postOngoingCallNotification(
-                repository = notificationListRepository,
-                startTimeMs = 1000L,
-                intent = null,
-            )
+            addOngoingCallState(startTimeMs = 1000, contentIntent = null)
 
             assertThat((latest as OngoingActivityChipModel.Active).clickBehavior)
                 .isInstanceOf(OngoingActivityChipModel.ClickBehavior.None::class.java)
@@ -380,15 +359,11 @@
     @Test
     @EnableChipsModernization
     fun chip_inCall_positiveStartTime_validIntent_clickBehaviorLaunchesIntent() =
-        testScope.runTest {
+        kosmos.runTest {
             val latest by collectLastValue(underTest.chip)
 
             val pendingIntent = mock<PendingIntent>()
-            postOngoingCallNotification(
-                repository = notificationListRepository,
-                startTimeMs = 1000L,
-                intent = pendingIntent,
-            )
+            addOngoingCallState(startTimeMs = 1000, contentIntent = pendingIntent)
 
             val clickBehavior = (latest as OngoingActivityChipModel.Active).clickBehavior
             assertThat(clickBehavior)
@@ -405,15 +380,11 @@
     @Test
     @EnableChipsModernization
     fun chip_inCall_zeroStartTime_validIntent_clickBehaviorLaunchesIntent() =
-        testScope.runTest {
+        kosmos.runTest {
             val latest by collectLastValue(underTest.chip)
 
             val pendingIntent = mock<PendingIntent>()
-            postOngoingCallNotification(
-                repository = notificationListRepository,
-                startTimeMs = 0L,
-                intent = pendingIntent,
-            )
+            addOngoingCallState(startTimeMs = 0, contentIntent = pendingIntent)
 
             val clickBehavior = (latest as OngoingActivityChipModel.Active).clickBehavior
             assertThat(clickBehavior)
@@ -435,27 +406,6 @@
                 mock<StatusBarIconView>()
             }
 
-        fun postOngoingCallNotification(
-            repository: ActiveNotificationListRepository,
-            startTimeMs: Long,
-            intent: PendingIntent?,
-        ) {
-            repository.activeNotifications.value =
-                ActiveNotificationsStore.Builder()
-                    .apply {
-                        addIndividualNotif(
-                            activeNotificationModel(
-                                key = "notif1",
-                                whenTime = startTimeMs,
-                                callType = CallType.Ongoing,
-                                statusBarChipIcon = null,
-                                contentIntent = intent,
-                            )
-                        )
-                    }
-                    .build()
-        }
-
         private val PROMOTED_CONTENT_WITH_COLOR =
             PromotedNotificationContentModel.Builder("notif")
                 .apply {
diff --git a/packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/chips/ui/viewmodel/OngoingActivityChipsViewModelTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/chips/ui/viewmodel/OngoingActivityChipsViewModelTest.kt
index 626dcd5..1b5b0d6 100644
--- a/packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/chips/ui/viewmodel/OngoingActivityChipsViewModelTest.kt
+++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/chips/ui/viewmodel/OngoingActivityChipsViewModelTest.kt
@@ -22,15 +22,18 @@
 import android.graphics.Bitmap
 import android.graphics.drawable.BitmapDrawable
 import android.platform.test.annotations.DisableFlags
+import android.platform.test.annotations.EnableFlags
 import android.view.View
 import androidx.test.ext.junit.runners.AndroidJUnit4
 import androidx.test.filters.SmallTest
 import com.android.systemui.SysuiTestCase
 import com.android.systemui.animation.Expandable
 import com.android.systemui.common.shared.model.Icon
-import com.android.systemui.coroutines.collectLastValue
 import com.android.systemui.kosmos.Kosmos
+import com.android.systemui.kosmos.collectLastValue
+import com.android.systemui.kosmos.runTest
 import com.android.systemui.kosmos.testScope
+import com.android.systemui.kosmos.useUnconfinedTestDispatcher
 import com.android.systemui.mediaprojection.data.model.MediaProjectionState
 import com.android.systemui.mediaprojection.data.repository.fakeMediaProjectionRepository
 import com.android.systemui.mediaprojection.taskswitcher.FakeActivityTaskManager.Companion.createTask
@@ -48,16 +51,13 @@
 import com.android.systemui.statusbar.phone.SystemUIDialog
 import com.android.systemui.statusbar.phone.mockSystemUIDialogFactory
 import com.android.systemui.statusbar.phone.ongoingcall.StatusBarChipsModernization
-import com.android.systemui.statusbar.phone.ongoingcall.data.repository.ongoingCallRepository
-import com.android.systemui.statusbar.phone.ongoingcall.shared.model.OngoingCallModel
-import com.android.systemui.statusbar.phone.ongoingcall.shared.model.inCallModel
+import com.android.systemui.statusbar.phone.ongoingcall.shared.model.OngoingCallTestHelper.addOngoingCallState
+import com.android.systemui.statusbar.phone.ongoingcall.shared.model.OngoingCallTestHelper.removeOngoingCallState
 import com.android.systemui.testKosmos
 import com.android.systemui.util.time.fakeSystemClock
 import com.google.common.truth.Truth.assertThat
 import kotlinx.coroutines.flow.launchIn
 import kotlinx.coroutines.flow.onEach
-import kotlinx.coroutines.test.runCurrent
-import kotlinx.coroutines.test.runTest
 import org.junit.Before
 import org.junit.Test
 import org.junit.runner.RunWith
@@ -74,13 +74,11 @@
 @RunWith(AndroidJUnit4::class)
 @DisableFlags(StatusBarNotifChips.FLAG_NAME)
 class OngoingActivityChipsViewModelTest : SysuiTestCase() {
-    private val kosmos = testKosmos()
-    private val testScope = kosmos.testScope
+    private val kosmos = testKosmos().useUnconfinedTestDispatcher()
     private val systemClock = kosmos.fakeSystemClock
 
     private val screenRecordState = kosmos.screenRecordRepository.screenRecordState
     private val mediaProjectionState = kosmos.fakeMediaProjectionRepository.mediaProjectionState
-    private val callRepo = kosmos.ongoingCallRepository
 
     private val mockSystemUIDialog = mock<SystemUIDialog>()
     private val chipBackgroundView = mock<ChipBackgroundContainer>()
@@ -96,7 +94,7 @@
     private val mockExpandable: Expandable =
         mock<Expandable>().apply { whenever(dialogTransitionController(any())).thenReturn(mock()) }
 
-    private val underTest = kosmos.ongoingActivityChipsViewModel
+    private val Kosmos.underTest by Kosmos.Fixture { ongoingActivityChipsViewModel }
 
     @Before
     fun setUp() {
@@ -111,10 +109,10 @@
 
     @Test
     fun primaryChip_allHidden_hidden() =
-        testScope.runTest {
+        kosmos.runTest {
             screenRecordState.value = ScreenRecordModel.DoingNothing
             mediaProjectionState.value = MediaProjectionState.NotProjecting
-            callRepo.setOngoingCallState(OngoingCallModel.NoCall)
+            removeOngoingCallState("testKey")
 
             val latest by collectLastValue(underTest.primaryChip)
 
@@ -123,10 +121,10 @@
 
     @Test
     fun primaryChip_screenRecordShow_restHidden_screenRecordShown() =
-        testScope.runTest {
+        kosmos.runTest {
             screenRecordState.value = ScreenRecordModel.Recording
             mediaProjectionState.value = MediaProjectionState.NotProjecting
-            callRepo.setOngoingCallState(OngoingCallModel.NoCall)
+            removeOngoingCallState("testKey")
 
             val latest by collectLastValue(underTest.primaryChip)
 
@@ -135,10 +133,10 @@
 
     @Test
     fun primaryChip_screenRecordShowAndCallShow_screenRecordShown() =
-        testScope.runTest {
+        kosmos.runTest {
             screenRecordState.value = ScreenRecordModel.Recording
 
-            callRepo.setOngoingCallState(inCallModel(startTimeMs = 34))
+            addOngoingCallState()
 
             val latest by collectLastValue(underTest.primaryChip)
 
@@ -147,11 +145,11 @@
 
     @Test
     fun primaryChip_screenRecordShowAndShareToAppShow_screenRecordShown() =
-        testScope.runTest {
+        kosmos.runTest {
             screenRecordState.value = ScreenRecordModel.Recording
             mediaProjectionState.value =
                 MediaProjectionState.Projecting.EntireScreen(NORMAL_PACKAGE)
-            callRepo.setOngoingCallState(OngoingCallModel.NoCall)
+            removeOngoingCallState("testKey")
 
             val latest by collectLastValue(underTest.primaryChip)
 
@@ -160,11 +158,11 @@
 
     @Test
     fun primaryChip_shareToAppShowAndCallShow_shareToAppShown() =
-        testScope.runTest {
+        kosmos.runTest {
             screenRecordState.value = ScreenRecordModel.DoingNothing
             mediaProjectionState.value =
                 MediaProjectionState.Projecting.EntireScreen(NORMAL_PACKAGE)
-            callRepo.setOngoingCallState(inCallModel(startTimeMs = 34))
+            addOngoingCallState()
 
             val latest by collectLastValue(underTest.primaryChip)
 
@@ -173,15 +171,13 @@
 
     @Test
     fun primaryChip_screenRecordAndShareToAppAndCastToOtherHideAndCallShown_callShown() =
-        testScope.runTest {
+        kosmos.runTest {
             val notificationKey = "call"
             screenRecordState.value = ScreenRecordModel.DoingNothing
             // MediaProjection covers both share-to-app and cast-to-other-device
             mediaProjectionState.value = MediaProjectionState.NotProjecting
 
-            callRepo.setOngoingCallState(
-                inCallModel(startTimeMs = 34, notificationKey = notificationKey)
-            )
+            addOngoingCallState(key = notificationKey)
 
             val latest by collectLastValue(underTest.primaryChip)
 
@@ -190,12 +186,10 @@
 
     @Test
     fun primaryChip_higherPriorityChipAdded_lowerPriorityChipReplaced() =
-        testScope.runTest {
+        kosmos.runTest {
             // Start with just the lowest priority chip shown
             val callNotificationKey = "call"
-            callRepo.setOngoingCallState(
-                inCallModel(startTimeMs = 34, notificationKey = callNotificationKey)
-            )
+            addOngoingCallState(key = callNotificationKey)
             // And everything else hidden
             mediaProjectionState.value = MediaProjectionState.NotProjecting
             screenRecordState.value = ScreenRecordModel.DoingNothing
@@ -224,15 +218,13 @@
 
     @Test
     fun primaryChip_highestPriorityChipRemoved_showsNextPriorityChip() =
-        testScope.runTest {
+        kosmos.runTest {
             // WHEN all chips are active
             screenRecordState.value = ScreenRecordModel.Recording
             mediaProjectionState.value =
                 MediaProjectionState.Projecting.EntireScreen(NORMAL_PACKAGE)
             val callNotificationKey = "call"
-            callRepo.setOngoingCallState(
-                inCallModel(startTimeMs = 34, notificationKey = callNotificationKey)
-            )
+            addOngoingCallState(key = callNotificationKey)
 
             val latest by collectLastValue(underTest.primaryChip)
 
@@ -255,17 +247,15 @@
     /** Regression test for b/347726238. */
     @Test
     fun primaryChip_timerDoesNotResetAfterSubscribersRestart() =
-        testScope.runTest {
+        kosmos.runTest {
             var latest: OngoingActivityChipModel? = null
 
-            val job1 = underTest.primaryChip.onEach { latest = it }.launchIn(this)
+            val job1 = underTest.primaryChip.onEach { latest = it }.launchIn(kosmos.testScope)
 
             // Start a chip with a timer
             systemClock.setElapsedRealtime(1234)
             screenRecordState.value = ScreenRecordModel.Recording
 
-            runCurrent()
-
             assertThat((latest as OngoingActivityChipModel.Active.Timer).startTimeMs)
                 .isEqualTo(1234)
 
@@ -276,9 +266,7 @@
             systemClock.setElapsedRealtime(5678)
 
             // WHEN we re-subscribe to the chip flow
-            val job2 = underTest.primaryChip.onEach { latest = it }.launchIn(this)
-
-            runCurrent()
+            val job2 = underTest.primaryChip.onEach { latest = it }.launchIn(kosmos.testScope)
 
             // THEN the old start time is still used
             assertThat((latest as OngoingActivityChipModel.Active.Timer).startTimeMs)
@@ -289,14 +277,14 @@
 
     @Test
     fun primaryChip_screenRecordStoppedViaDialog_chipHiddenWithoutAnimation() =
-        testScope.runTest {
+        kosmos.runTest {
             screenRecordState.value = ScreenRecordModel.Recording
             mediaProjectionState.value =
                 MediaProjectionState.Projecting.EntireScreen(
                     NORMAL_PACKAGE,
                     hostDeviceName = "Recording Display",
                 )
-            callRepo.setOngoingCallState(OngoingCallModel.NoCall)
+            removeOngoingCallState("testKey")
 
             val latest by collectLastValue(underTest.primaryChip)
 
@@ -319,11 +307,11 @@
 
     @Test
     fun primaryChip_projectionStoppedViaDialog_chipHiddenWithoutAnimation() =
-        testScope.runTest {
+        kosmos.runTest {
             mediaProjectionState.value =
                 MediaProjectionState.Projecting.EntireScreen(NORMAL_PACKAGE)
             screenRecordState.value = ScreenRecordModel.DoingNothing
-            callRepo.setOngoingCallState(OngoingCallModel.NoCall)
+            removeOngoingCallState("testKey")
 
             val latest by collectLastValue(underTest.primaryChip)
 
diff --git a/packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/data/repository/StatusBarModeRepositoryImplTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/data/repository/StatusBarModeRepositoryImplTest.kt
index ab475c5..2f6bedb 100644
--- a/packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/data/repository/StatusBarModeRepositoryImplTest.kt
+++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/data/repository/StatusBarModeRepositoryImplTest.kt
@@ -37,6 +37,8 @@
 import com.android.systemui.statusbar.layout.LetterboxAppearanceCalculator
 import com.android.systemui.statusbar.layout.StatusBarBoundsProvider
 import com.android.systemui.statusbar.phone.fragment.dagger.HomeStatusBarComponent
+import com.android.systemui.statusbar.phone.ongoingcall.DisableChipsModernization
+import com.android.systemui.statusbar.phone.ongoingcall.EnableChipsModernization
 import com.android.systemui.statusbar.phone.ongoingcall.data.repository.ongoingCallRepository
 import com.android.systemui.statusbar.phone.ongoingcall.shared.model.OngoingCallModel
 import com.android.systemui.statusbar.phone.ongoingcall.shared.model.inCallModel
@@ -387,6 +389,7 @@
         }
 
     @Test
+    @DisableChipsModernization
     fun statusBarMode_ongoingCallAndFullscreen_semiTransparent() =
         testScope.runTest {
             val latest by collectLastValue(underTest.statusBarAppearance)
@@ -398,6 +401,19 @@
         }
 
     @Test
+    @EnableChipsModernization
+    fun statusBarMode_ongoingProcessRequiresStatusBarVisible_andFullscreen_semiTransparent() =
+        testScope.runTest {
+            val latest by collectLastValue(underTest.statusBarAppearance)
+
+            underTest.setOngoingProcessRequiresStatusBarVisible(true)
+            onSystemBarAttributesChanged(requestedVisibleTypes = WindowInsets.Type.navigationBars())
+
+            assertThat(latest!!.mode).isEqualTo(StatusBarMode.SEMI_TRANSPARENT)
+        }
+
+    @Test
+    @DisableChipsModernization
     fun statusBarMode_ongoingCallButNotFullscreen_matchesAppearance() =
         testScope.runTest {
             val latest by collectLastValue(underTest.statusBarAppearance)
@@ -413,6 +429,23 @@
         }
 
     @Test
+    @EnableChipsModernization
+    fun statusBarMode_ongoingProcessRequiresStatusBarVisible_butNotFullscreen_matchesAppearance() =
+        testScope.runTest {
+            val latest by collectLastValue(underTest.statusBarAppearance)
+
+            underTest.setOngoingProcessRequiresStatusBarVisible(true)
+
+            onSystemBarAttributesChanged(
+                requestedVisibleTypes = WindowInsets.Type.statusBars(),
+                appearance = APPEARANCE_OPAQUE_STATUS_BARS,
+            )
+
+            assertThat(latest!!.mode).isEqualTo(StatusBarMode.OPAQUE)
+        }
+
+    @Test
+    @DisableChipsModernization
     fun statusBarMode_fullscreenButNotOngoingCall_matchesAppearance() =
         testScope.runTest {
             val latest by collectLastValue(underTest.statusBarAppearance)
@@ -427,6 +460,21 @@
         }
 
     @Test
+    @EnableChipsModernization
+    fun statusBarMode_fullscreen_butNotOngoingProcessRequiresStatusBarVisible_matchesAppearance() =
+        testScope.runTest {
+            val latest by collectLastValue(underTest.statusBarAppearance)
+
+            underTest.setOngoingProcessRequiresStatusBarVisible(false)
+            onSystemBarAttributesChanged(
+                requestedVisibleTypes = WindowInsets.Type.navigationBars(),
+                appearance = APPEARANCE_OPAQUE_STATUS_BARS,
+            )
+
+            assertThat(latest!!.mode).isEqualTo(StatusBarMode.OPAQUE)
+        }
+
+    @Test
     fun statusBarMode_transientShown_semiTransparent() =
         testScope.runTest {
             val latest by collectLastValue(underTest.statusBarAppearance)
diff --git a/packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/notification/NotificationTransitionAnimatorControllerTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/notification/NotificationTransitionAnimatorControllerTest.kt
index a1772e3..510167d 100644
--- a/packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/notification/NotificationTransitionAnimatorControllerTest.kt
+++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/notification/NotificationTransitionAnimatorControllerTest.kt
@@ -10,6 +10,7 @@
 import com.android.systemui.jank.interactionJankMonitor
 import com.android.systemui.kosmos.testScope
 import com.android.systemui.res.R
+import com.android.systemui.statusbar.notification.collection.GroupEntry
 import com.android.systemui.statusbar.notification.collection.GroupEntryBuilder
 import com.android.systemui.statusbar.notification.collection.NotificationEntryBuilder
 import com.android.systemui.statusbar.notification.data.repository.NotificationLaunchAnimationRepository
@@ -165,7 +166,10 @@
             .setSummary(summary)
             .addChild(notification.entry)
             .build()
-        assertSame(summary, notification.entry.parent?.summary)
+
+        val parentSummary = if (notification.entry.parent is GroupEntry)
+            (notification.entry.parent as GroupEntry).summary else null
+        assertSame(summary, parentSummary)
 
         `when`(headsUpManager.isHeadsUpEntry(notificationKey)).thenReturn(false)
         `when`(headsUpManager.isHeadsUpEntry(summary.key)).thenReturn(true)
diff --git a/packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/notification/collection/render/NodeSpecBuilderTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/notification/collection/render/NodeSpecBuilderTest.kt
index 99bd4fc..d9c9177 100644
--- a/packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/notification/collection/render/NodeSpecBuilderTest.kt
+++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/notification/collection/render/NodeSpecBuilderTest.kt
@@ -26,6 +26,7 @@
 import com.android.systemui.statusbar.notification.collection.ListEntry
 import com.android.systemui.statusbar.notification.collection.NotificationEntry
 import com.android.systemui.statusbar.notification.collection.NotificationEntryBuilder
+import com.android.systemui.statusbar.notification.collection.PipelineEntry
 import com.android.systemui.statusbar.notification.collection.getAttachState
 import com.android.systemui.statusbar.notification.collection.listbuilder.NotifSection
 import com.android.systemui.statusbar.notification.collection.listbuilder.pluggable.NotifSectioner
@@ -409,7 +410,7 @@
 ): NotifSection {
     return NotifSection(object : NotifSectioner("Section $index (bucket=$bucket)", bucket) {
 
-        override fun isInSection(entry: ListEntry?): Boolean {
+        override fun isInSection(entry: PipelineEntry?): Boolean {
             throw NotImplementedError("This should never be called")
         }
 
diff --git a/packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/notification/collection/render/RenderStageManagerTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/notification/collection/render/RenderStageManagerTest.kt
index e6fbc72..4a05804 100644
--- a/packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/notification/collection/render/RenderStageManagerTest.kt
+++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/notification/collection/render/RenderStageManagerTest.kt
@@ -24,6 +24,7 @@
 import com.android.systemui.statusbar.notification.collection.ListEntry
 import com.android.systemui.statusbar.notification.collection.NotificationEntry
 import com.android.systemui.statusbar.notification.collection.NotificationEntryBuilder
+import com.android.systemui.statusbar.notification.collection.PipelineEntry
 import com.android.systemui.statusbar.notification.collection.ShadeListBuilder
 import com.android.systemui.statusbar.notification.collection.listbuilder.OnAfterRenderEntryListener
 import com.android.systemui.statusbar.notification.collection.listbuilder.OnAfterRenderGroupListener
@@ -40,6 +41,7 @@
 import org.mockito.kotlin.times
 import org.mockito.kotlin.verify
 import org.mockito.kotlin.verifyNoMoreInteractions
+import java.nio.channels.Pipe
 
 @SmallTest
 @RunWith(AndroidJUnit4::class)
@@ -198,7 +200,7 @@
         )
 
     private class FakeNotifViewRenderer : NotifViewRenderer {
-        override fun onRenderList(notifList: List<ListEntry>) {}
+        override fun onRenderList(notifList: List<PipelineEntry>) {}
 
         override fun getGroupController(group: GroupEntry): NotifGroupController = mock()
 
diff --git a/packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/notification/row/wrapper/NotificationConversationTemplateViewWrapperTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/notification/row/wrapper/NotificationConversationTemplateViewWrapperTest.kt
index 9a6a699..081f52c 100644
--- a/packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/notification/row/wrapper/NotificationConversationTemplateViewWrapperTest.kt
+++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/notification/row/wrapper/NotificationConversationTemplateViewWrapperTest.kt
@@ -135,6 +135,7 @@
                     .thenReturn(mock())
                 whenever(requireViewById<View>(R.id.app_name_text)).thenReturn(mock())
                 whenever(requireViewById<View>(R.id.conversation_text)).thenReturn(mock())
+                whenever(requireViewById<View>(R.id.title)).thenReturn(mock())
             }
         return mockView
     }
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/multivalentTests/src/com/android/systemui/statusbar/notification/stack/StackScrollAlgorithmTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/notification/stack/StackScrollAlgorithmTest.kt
index 01ba4df..7603eec 100644
--- a/packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/notification/stack/StackScrollAlgorithmTest.kt
+++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/notification/stack/StackScrollAlgorithmTest.kt
@@ -146,6 +146,20 @@
     }
 
     @Test
+    @EnableSceneContainer
+    fun resetViewStates_defaultHun_hasShadow() {
+        val headsUpTop = 200f
+        ambientState.headsUpTop = headsUpTop
+
+        whenever(notificationRow.isPinned).thenReturn(true)
+        whenever(notificationRow.isHeadsUp).thenReturn(true)
+
+        stackScrollAlgorithm.resetViewStates(ambientState, 0)
+
+        assertThat(notificationRow.viewState.zTranslation).isGreaterThan(baseZ)
+    }
+
+    @Test
     @DisableSceneContainer
     fun resetViewStates_defaultHunWhenShadeIsOpening_yTranslationIsInset() {
         whenever(notificationRow.isPinned).thenReturn(true)
diff --git a/packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/notification/stack/StackStateAnimatorTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/notification/stack/StackStateAnimatorTest.kt
index 67415de..e7be20e 100644
--- a/packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/notification/stack/StackStateAnimatorTest.kt
+++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/notification/stack/StackStateAnimatorTest.kt
@@ -88,6 +88,7 @@
                 /* delay= */ 0L,
                 /* duration= */ ANIMATION_DURATION_HEADS_UP_APPEAR.toLong(),
                 /* isHeadsUpAppear= */ true,
+                /* isHeadsUpCycling= */ false,
                 /* onEndRunnable= */ null,
             )
     }
@@ -111,6 +112,7 @@
                 /* delay= */ 0L,
                 /* duration= */ ANIMATION_DURATION_HEADS_UP_APPEAR.toLong(),
                 /* isHeadsUpAppear= */ true,
+                /* isHeadsUpCycling= */ false,
                 /* onEndRunnable= */ null,
             )
     }
@@ -128,6 +130,7 @@
                 /* delay= */ eq(0L),
                 /* translationDirection= */ eq(0f),
                 /* isHeadsUpAnimation= */ eq(true),
+                /* isHeadsUpCycling= */ eq(false),
                 /* onStartedRunnable= */ any(),
                 /* onFinishedRunnable= */ runnableCaptor.capture(),
                 /* animationListener= */ any(),
diff --git a/packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/notification/stack/ui/viewmodel/SharedNotificationContainerViewModelTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/notification/stack/ui/viewmodel/SharedNotificationContainerViewModelTest.kt
index 21297e3..c630a1c 100644
--- a/packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/notification/stack/ui/viewmodel/SharedNotificationContainerViewModelTest.kt
+++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/notification/stack/ui/viewmodel/SharedNotificationContainerViewModelTest.kt
@@ -17,14 +17,15 @@
 
 package com.android.systemui.statusbar.notification.stack.ui.viewmodel
 
+import android.platform.test.annotations.EnableFlags
 import android.platform.test.flag.junit.FlagsParameterization
 import androidx.test.filters.SmallTest
-import com.android.compose.animation.scene.ObservableTransitionState
+import com.android.systemui.Flags.FLAG_GLANCEABLE_HUB_V2
 import com.android.systemui.SysuiTestCase
 import com.android.systemui.bouncer.data.repository.keyguardBouncerRepository
 import com.android.systemui.common.shared.model.NotificationContainerBounds
 import com.android.systemui.common.ui.data.repository.fakeConfigurationRepository
-import com.android.systemui.communal.data.repository.communalSceneRepository
+import com.android.systemui.communal.domain.interactor.communalSceneInteractor
 import com.android.systemui.communal.shared.model.CommunalScenes
 import com.android.systemui.coroutines.collectLastValue
 import com.android.systemui.coroutines.collectValues
@@ -54,11 +55,14 @@
 import com.android.systemui.keyguard.ui.viewmodel.ViewStateAccessor
 import com.android.systemui.keyguard.ui.viewmodel.aodBurnInViewModel
 import com.android.systemui.keyguard.ui.viewmodel.keyguardRootViewModel
+import com.android.systemui.kosmos.collectLastValue
+import com.android.systemui.kosmos.runTest
 import com.android.systemui.kosmos.testScope
 import com.android.systemui.res.R
 import com.android.systemui.scene.data.repository.Idle
 import com.android.systemui.scene.data.repository.Transition
 import com.android.systemui.scene.data.repository.setTransition
+import com.android.systemui.scene.shared.flag.SceneContainerFlag
 import com.android.systemui.scene.shared.model.Scenes
 import com.android.systemui.shade.domain.interactor.enableDualShade
 import com.android.systemui.shade.domain.interactor.enableSingleShade
@@ -125,7 +129,6 @@
         kosmos.sharedNotificationContainerInteractor
     }
     private val largeScreenHeaderHelper by lazy { kosmos.mockLargeScreenHeaderHelper }
-    private val communalSceneRepository by lazy { kosmos.communalSceneRepository }
 
     lateinit var underTest: SharedNotificationContainerViewModel
 
@@ -591,6 +594,25 @@
         }
 
     @Test
+    @DisableSceneContainer
+    @EnableFlags(FLAG_GLANCEABLE_HUB_V2)
+    fun isOnLockscreenFalseWhenCommunalShowing() =
+        kosmos.runTest {
+            val isOnLockscreen by collectLastValue(underTest.isOnLockscreen)
+
+            setTransition(
+                sceneTransition = Idle(Scenes.Bouncer),
+                stateTransition = TransitionStep(from = LOCKSCREEN, to = PRIMARY_BOUNCER),
+            )
+            assertThat(isOnLockscreen).isTrue()
+
+            testScope.showCommunalScene()
+
+            // If bouncer is showing over the hub, it should not be considered on lockscreen
+            assertThat(isOnLockscreen).isFalse()
+        }
+
+    @Test
     fun isOnLockscreenWithoutShade() =
         testScope.runTest {
             val isOnLockscreenWithoutShade by collectLastValue(underTest.isOnLockscreenWithoutShade)
@@ -1472,20 +1494,24 @@
     }
 
     private fun TestScope.showCommunalScene() {
-        val transitionState =
-            MutableStateFlow<ObservableTransitionState>(
-                ObservableTransitionState.Idle(CommunalScenes.Communal)
-            )
-        communalSceneRepository.setTransitionState(transitionState)
+        val targetScene =
+            if (SceneContainerFlag.isEnabled) {
+                Scenes.Communal
+            } else {
+                CommunalScenes.Communal
+            }
+        kosmos.communalSceneInteractor.changeScene(targetScene, "test")
         runCurrent()
     }
 
     private fun TestScope.hideCommunalScene() {
-        val transitionState =
-            MutableStateFlow<ObservableTransitionState>(
-                ObservableTransitionState.Idle(CommunalScenes.Blank)
-            )
-        communalSceneRepository.setTransitionState(transitionState)
+        val targetScene =
+            if (SceneContainerFlag.isEnabled) {
+                Scenes.Lockscreen
+            } else {
+                CommunalScenes.Blank
+            }
+        kosmos.communalSceneInteractor.changeScene(targetScene, "test")
         runCurrent()
     }
 
diff --git a/packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/pipeline/mobile/domain/interactor/MobileIconsInteractorTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/pipeline/mobile/domain/interactor/MobileIconsInteractorTest.kt
index d17abd7..6066a38 100644
--- a/packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/pipeline/mobile/domain/interactor/MobileIconsInteractorTest.kt
+++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/pipeline/mobile/domain/interactor/MobileIconsInteractorTest.kt
@@ -17,6 +17,7 @@
 package com.android.systemui.statusbar.pipeline.mobile.domain.interactor
 
 import android.os.ParcelUuid
+import android.platform.test.annotations.EnableFlags
 import android.telephony.SubscriptionManager.INVALID_SUBSCRIPTION_ID
 import android.telephony.SubscriptionManager.PROFILE_CLASS_PROVISIONING
 import android.telephony.SubscriptionManager.PROFILE_CLASS_UNSET
@@ -33,9 +34,12 @@
 import com.android.systemui.kosmos.runCurrent
 import com.android.systemui.kosmos.runTest
 import com.android.systemui.kosmos.testScope
+import com.android.systemui.statusbar.core.NewStatusBarIcons
+import com.android.systemui.statusbar.core.StatusBarRootModernization
 import com.android.systemui.statusbar.pipeline.mobile.data.model.SubscriptionModel
 import com.android.systemui.statusbar.pipeline.mobile.data.repository.FakeMobileConnectionRepository
 import com.android.systemui.statusbar.pipeline.mobile.data.repository.fake
+import com.android.systemui.statusbar.pipeline.mobile.data.repository.fakeMobileConnectionsRepository
 import com.android.systemui.statusbar.pipeline.mobile.data.repository.mobileConnectionsRepository
 import com.android.systemui.statusbar.pipeline.mobile.data.repository.mobileConnectionsRepositoryLogbufferName
 import com.android.systemui.statusbar.pipeline.shared.data.model.ConnectivitySlot
@@ -897,6 +901,71 @@
             assertThat(latest).isEqualTo(2)
         }
 
+    @Test
+    @EnableFlags(NewStatusBarIcons.FLAG_NAME, StatusBarRootModernization.FLAG_NAME)
+    fun isStackable_tracksNumberOfSubscriptions() =
+        kosmos.runTest {
+            val latest by collectLastValue(underTest.isStackable)
+
+            connectionsRepository.setSubscriptions(listOf(SUB_1))
+            assertThat(latest).isFalse()
+
+            connectionsRepository.setSubscriptions(listOf(SUB_1, SUB_2))
+            assertThat(latest).isTrue()
+
+            connectionsRepository.setSubscriptions(listOf(SUB_1, SUB_2, SUB_3_OPP))
+            assertThat(latest).isFalse()
+        }
+
+    @Test
+    @EnableFlags(NewStatusBarIcons.FLAG_NAME, StatusBarRootModernization.FLAG_NAME)
+    fun isStackable_checksForTerrestrialConnections() =
+        kosmos.runTest {
+            val exclusivelyNonTerrestrialSub =
+                SubscriptionModel(
+                    isExclusivelyNonTerrestrial = true,
+                    subscriptionId = 5,
+                    carrierName = "Carrier 5",
+                    profileClass = PROFILE_CLASS_UNSET,
+                )
+
+            val latest by collectLastValue(underTest.isStackable)
+
+            connectionsRepository.setSubscriptions(listOf(SUB_1, SUB_2))
+            assertThat(latest).isTrue()
+
+            connectionsRepository.setSubscriptions(listOf(SUB_1, exclusivelyNonTerrestrialSub))
+            assertThat(latest).isFalse()
+        }
+
+    @Test
+    @EnableFlags(NewStatusBarIcons.FLAG_NAME, StatusBarRootModernization.FLAG_NAME)
+    fun isStackable_checksForNumberOfBars() =
+        kosmos.runTest {
+            val latest by collectLastValue(underTest.isStackable)
+
+            // Number of levels is the same for both
+            connectionsRepository.setSubscriptions(listOf(SUB_1, SUB_2))
+            setNumberOfLevelsForSubId(SUB_1_ID, 5)
+            setNumberOfLevelsForSubId(SUB_2_ID, 5)
+
+            assertThat(latest).isTrue()
+
+            // Change the number of levels to be different than SUB_2
+            setNumberOfLevelsForSubId(SUB_1_ID, 6)
+
+            assertThat(latest).isFalse()
+        }
+
+    private fun setNumberOfLevelsForSubId(subId: Int, numberOfLevels: Int) {
+        with(kosmos) {
+            (fakeMobileConnectionsRepository.getRepoForSubId(subId)
+                    as FakeMobileConnectionRepository)
+                .numberOfLevels
+                .value = numberOfLevels
+        }
+    }
+
     /**
      * Convenience method for creating a pair of subscriptions to test the filteredSubscriptions
      * flow.
diff --git a/packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/pipeline/mobile/ui/viewmodel/StackedMobileIconViewModelTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/pipeline/mobile/ui/viewmodel/StackedMobileIconViewModelTest.kt
new file mode 100644
index 0000000..20bdebd
--- /dev/null
+++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/pipeline/mobile/ui/viewmodel/StackedMobileIconViewModelTest.kt
@@ -0,0 +1,145 @@
+/*
+ * Copyright (C) 2025 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.systemui.statusbar.pipeline.mobile.ui.viewmodel
+
+import android.platform.test.annotations.EnableFlags
+import android.telephony.SubscriptionManager.PROFILE_CLASS_UNSET
+import androidx.test.ext.junit.runners.AndroidJUnit4
+import androidx.test.filters.SmallTest
+import com.android.systemui.SysuiTestCase
+import com.android.systemui.common.shared.model.Icon
+import com.android.systemui.flags.Flags
+import com.android.systemui.flags.fakeFeatureFlagsClassic
+import com.android.systemui.kosmos.Kosmos
+import com.android.systemui.kosmos.Kosmos.Fixture
+import com.android.systemui.kosmos.runTest
+import com.android.systemui.kosmos.testScope
+import com.android.systemui.kosmos.useUnconfinedTestDispatcher
+import com.android.systemui.lifecycle.activateIn
+import com.android.systemui.statusbar.core.NewStatusBarIcons
+import com.android.systemui.statusbar.core.StatusBarRootModernization
+import com.android.systemui.statusbar.pipeline.mobile.data.model.SubscriptionModel
+import com.android.systemui.statusbar.pipeline.mobile.domain.interactor.fakeMobileIconsInteractor
+import com.android.systemui.statusbar.pipeline.mobile.domain.model.SignalIconModel
+import com.android.systemui.testKosmos
+import com.google.common.truth.Truth.assertThat
+import org.junit.Before
+import org.junit.Test
+import org.junit.runner.RunWith
+
+@SmallTest
+@RunWith(AndroidJUnit4::class)
+class StackedMobileIconViewModelTest : SysuiTestCase() {
+    private val kosmos = testKosmos().useUnconfinedTestDispatcher()
+    private val testScope = kosmos.testScope
+
+    private val Kosmos.underTest: StackedMobileIconViewModel by Fixture {
+        stackedMobileIconViewModel
+    }
+
+    @Before
+    fun setUp() {
+        kosmos.fakeFeatureFlagsClassic.set(Flags.NEW_NETWORK_SLICE_UI, false)
+        kosmos.underTest.activateIn(testScope)
+    }
+
+    @Test
+    @EnableFlags(NewStatusBarIcons.FLAG_NAME, StatusBarRootModernization.FLAG_NAME)
+    fun dualSim_filtersOutNonDualConnections() =
+        kosmos.runTest {
+            fakeMobileIconsInteractor.filteredSubscriptions.value = listOf()
+            assertThat(underTest.dualSim).isNull()
+
+            fakeMobileIconsInteractor.filteredSubscriptions.value = listOf(SUB_1)
+            assertThat(underTest.dualSim).isNull()
+
+            fakeMobileIconsInteractor.filteredSubscriptions.value = listOf(SUB_1, SUB_2, SUB_3)
+            assertThat(underTest.dualSim).isNull()
+
+            fakeMobileIconsInteractor.filteredSubscriptions.value = listOf(SUB_1, SUB_2)
+            assertThat(underTest.dualSim).isNotNull()
+        }
+
+    @Test
+    @EnableFlags(NewStatusBarIcons.FLAG_NAME, StatusBarRootModernization.FLAG_NAME)
+    fun dualSim_filtersOutNonCellularIcons() =
+        kosmos.runTest {
+            fakeMobileIconsInteractor.filteredSubscriptions.value = listOf(SUB_1)
+            assertThat(underTest.dualSim).isNull()
+
+            fakeMobileIconsInteractor
+                .getInteractorForSubId(SUB_1.subscriptionId)!!
+                .signalLevelIcon
+                .value =
+                SignalIconModel.Satellite(
+                    level = 0,
+                    icon = Icon.Resource(res = 0, contentDescription = null),
+                )
+            fakeMobileIconsInteractor.filteredSubscriptions.value = listOf(SUB_1, SUB_2)
+            assertThat(underTest.dualSim).isNull()
+        }
+
+    @Test
+    @EnableFlags(NewStatusBarIcons.FLAG_NAME, StatusBarRootModernization.FLAG_NAME)
+    fun dualSim_tracksActiveSubId() =
+        kosmos.runTest {
+            // Active sub id is null, order is unchanged
+            fakeMobileIconsInteractor.filteredSubscriptions.value = listOf(SUB_1, SUB_2)
+            setIconLevel(SUB_1.subscriptionId, 1)
+            setIconLevel(SUB_2.subscriptionId, 2)
+
+            assertThat(underTest.dualSim!!.primary.level).isEqualTo(1)
+            assertThat(underTest.dualSim!!.secondary.level).isEqualTo(2)
+
+            // Active sub is 2, order is swapped
+            fakeMobileIconsInteractor.activeMobileDataSubscriptionId.value = SUB_2.subscriptionId
+
+            assertThat(underTest.dualSim!!.primary.level).isEqualTo(2)
+            assertThat(underTest.dualSim!!.secondary.level).isEqualTo(1)
+        }
+
+    private fun setIconLevel(subId: Int, level: Int) {
+        with(kosmos.fakeMobileIconsInteractor.getInteractorForSubId(subId)!!) {
+            signalLevelIcon.value =
+                (signalLevelIcon.value as SignalIconModel.Cellular).copy(level = level)
+        }
+    }
+
+    companion object {
+        private val SUB_1 =
+            SubscriptionModel(
+                subscriptionId = 1,
+                isOpportunistic = false,
+                carrierName = "Carrier 1",
+                profileClass = PROFILE_CLASS_UNSET,
+            )
+        private val SUB_2 =
+            SubscriptionModel(
+                subscriptionId = 2,
+                isOpportunistic = false,
+                carrierName = "Carrier 2",
+                profileClass = PROFILE_CLASS_UNSET,
+            )
+        private val SUB_3 =
+            SubscriptionModel(
+                subscriptionId = 3,
+                isOpportunistic = false,
+                carrierName = "Carrier 3",
+                profileClass = PROFILE_CLASS_UNSET,
+            )
+    }
+}
diff --git a/packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/pipeline/shared/ui/viewmodel/FakeHomeStatusBarViewModel.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/pipeline/shared/ui/viewmodel/FakeHomeStatusBarViewModel.kt
index eb961bd..f91e3a6 100644
--- a/packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/pipeline/shared/ui/viewmodel/FakeHomeStatusBarViewModel.kt
+++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/pipeline/shared/ui/viewmodel/FakeHomeStatusBarViewModel.kt
@@ -64,6 +64,8 @@
 
     override val isHomeStatusBarAllowedByScene = MutableStateFlow(false)
 
+    override val canShowOngoingActivityChips: Flow<Boolean> = MutableStateFlow(false)
+
     override val batteryViewModelFactory: BatteryViewModel.Factory =
         object : BatteryViewModel.Factory {
             override fun create(): BatteryViewModel = mock(BatteryViewModel::class.java)
diff --git a/packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/pipeline/shared/ui/viewmodel/HomeStatusBarViewModelImplTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/pipeline/shared/ui/viewmodel/HomeStatusBarViewModelImplTest.kt
index 51484ba..8a796fc 100644
--- a/packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/pipeline/shared/ui/viewmodel/HomeStatusBarViewModelImplTest.kt
+++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/pipeline/shared/ui/viewmodel/HomeStatusBarViewModelImplTest.kt
@@ -678,6 +678,60 @@
         }
 
     @Test
+    fun canShowOngoingActivityChips_statusBarHidden_noSecureCamera_noHun_false() =
+        kosmos.runTest {
+            val latest by collectLastValue(underTest.canShowOngoingActivityChips)
+
+            // home status bar not allowed
+            kosmos.sceneContainerRepository.snapToScene(Scenes.Lockscreen)
+            kosmos.keyguardOcclusionRepository.setShowWhenLockedActivityInfo(false, taskInfo = null)
+
+            assertThat(latest).isFalse()
+        }
+
+    @Test
+    fun canShowOngoingActivityChips_statusBarNotHidden_noSecureCamera_noHun_true() =
+        kosmos.runTest {
+            val latest by collectLastValue(underTest.canShowOngoingActivityChips)
+
+            transitionKeyguardToGone()
+
+            assertThat(latest).isTrue()
+        }
+
+    @Test
+    fun canShowOngoingActivityChips_statusBarNotHidden_secureCamera_noHun_false() =
+        kosmos.runTest {
+            val latest by collectLastValue(underTest.canShowOngoingActivityChips)
+
+            fakeKeyguardTransitionRepository.sendTransitionSteps(
+                from = KeyguardState.LOCKSCREEN,
+                to = KeyguardState.OCCLUDED,
+                testScope = testScope,
+            )
+            kosmos.keyguardInteractor.onCameraLaunchDetected(CAMERA_LAUNCH_SOURCE_POWER_DOUBLE_TAP)
+
+            assertThat(latest).isFalse()
+        }
+
+    @Test
+    fun canShowOngoingActivityChips_statusBarNotHidden_noSecureCamera_hun_false() =
+        kosmos.runTest {
+            val latest by collectLastValue(underTest.canShowOngoingActivityChips)
+
+            transitionKeyguardToGone()
+
+            headsUpNotificationRepository.setNotifications(
+                UnconfinedFakeHeadsUpRowRepository(
+                    key = "key",
+                    pinnedStatus = MutableStateFlow(PinnedStatus.PinnedByUser),
+                )
+            )
+
+            assertThat(latest).isFalse()
+        }
+
+    @Test
     fun isClockVisible_allowedByDisableFlags_visible() =
         kosmos.runTest {
             val latest by collectLastValue(underTest.isClockVisible)
diff --git a/packages/SystemUI/plugin/src/com/android/systemui/plugins/qs/QS.java b/packages/SystemUI/plugin/src/com/android/systemui/plugins/qs/QS.java
index d3218ad..a0bf7f0 100644
--- a/packages/SystemUI/plugin/src/com/android/systemui/plugins/qs/QS.java
+++ b/packages/SystemUI/plugin/src/com/android/systemui/plugins/qs/QS.java
@@ -203,6 +203,8 @@
      */
     void setIsNotificationPanelFullWidth(boolean isFullWidth);
 
+    default void setQSExpandingOrCollapsing(boolean isQSExpandingOrCollapsing) {}
+
     /**
      * Callback for when QSPanel container is scrolled
      */
diff --git a/packages/SystemUI/res/anim/volume_dialog_ringer_close.xml b/packages/SystemUI/res/anim/volume_dialog_ringer_close.xml
new file mode 100644
index 0000000..e7ba52c
--- /dev/null
+++ b/packages/SystemUI/res/anim/volume_dialog_ringer_close.xml
@@ -0,0 +1,22 @@
+<?xml version="1.0" encoding="utf-8"?><!--
+  ~ Copyright (C) 2025 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.
+  -->
+
+<pathInterpolator
+    xmlns:android="http://schemas.android.com/apk/res/android"
+    android:controlX1="0.20"
+    android:controlY1="0.00"
+    android:controlX2="0.00"
+    android:controlY2="1.00" />
\ No newline at end of file
diff --git a/packages/SystemUI/res/anim/volume_dialog_ringer_open.xml b/packages/SystemUI/res/anim/volume_dialog_ringer_open.xml
new file mode 100644
index 0000000..3b18eef
--- /dev/null
+++ b/packages/SystemUI/res/anim/volume_dialog_ringer_open.xml
@@ -0,0 +1,22 @@
+<?xml version="1.0" encoding="utf-8"?><!--
+  ~ Copyright (C) 2025 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.
+  -->
+
+<pathInterpolator
+    xmlns:android="http://schemas.android.com/apk/res/android"
+    android:controlX1="0.05"
+    android:controlY1="0.70"
+    android:controlX2="0.10"
+    android:controlY2="1.00" />
\ No newline at end of file
diff --git a/packages/SystemUI/res/layout/bindable_status_bar_compose_icon.xml b/packages/SystemUI/res/layout/bindable_status_bar_compose_icon.xml
index fa9318b..6b55ac2 100644
--- a/packages/SystemUI/res/layout/bindable_status_bar_compose_icon.xml
+++ b/packages/SystemUI/res/layout/bindable_status_bar_compose_icon.xml
@@ -27,7 +27,6 @@
         android:layout_height="@dimen/status_bar_bindable_icon_size"
         android:layout_width="wrap_content"
         android:layout_gravity="center_vertical"
-        android:padding="4sp"
         />
 
 </com.android.systemui.statusbar.pipeline.shared.ui.view.SingleBindableStatusBarComposeIconView>
diff --git a/packages/SystemUI/res/values-af/strings.xml b/packages/SystemUI/res/values-af/strings.xml
index 538d9e2..b4c5eed 100644
--- a/packages/SystemUI/res/values-af/strings.xml
+++ b/packages/SystemUI/res/values-af/strings.xml
@@ -328,7 +328,7 @@
     <string name="quick_settings_bluetooth_secondary_label_input" msgid="3887552721233148132">"Invoer"</string>
     <string name="quick_settings_bluetooth_secondary_label_hearing_aids" msgid="5553051568867097111">"Gehoortoestelle"</string>
     <string name="quick_settings_bluetooth_secondary_label_transient" msgid="3882884317600669650">"Skakel tans aan …"</string>
-    <string name="quick_settings_brightness_unable_adjust_msg" msgid="786478497970492300">"Kan nie helderheid verstel nie omdat dit\n deur die topapp beheer word"</string>
+    <string name="quick_settings_brightness_unable_adjust_msg" msgid="786478497970492300">"Kan nie helderheid verstel nie omdat dit deur die topapp beheer word"</string>
     <string name="quick_settings_rotation_unlocked_label" msgid="2359922767950346112">"Outodraai"</string>
     <string name="accessibility_quick_settings_rotation" msgid="4800050198392260738">"Outodraai skerm"</string>
     <string name="quick_settings_location_label" msgid="2621868789013389163">"Ligging"</string>
diff --git a/packages/SystemUI/res/values-am/strings.xml b/packages/SystemUI/res/values-am/strings.xml
index f1b9fca..8a1ba62 100644
--- a/packages/SystemUI/res/values-am/strings.xml
+++ b/packages/SystemUI/res/values-am/strings.xml
@@ -328,7 +328,7 @@
     <string name="quick_settings_bluetooth_secondary_label_input" msgid="3887552721233148132">"ግቤት"</string>
     <string name="quick_settings_bluetooth_secondary_label_hearing_aids" msgid="5553051568867097111">"መስሚያ አጋዥ መሣሪያዎች"</string>
     <string name="quick_settings_bluetooth_secondary_label_transient" msgid="3882884317600669650">"በማብራት ላይ..."</string>
-    <string name="quick_settings_brightness_unable_adjust_msg" msgid="786478497970492300">"ከላይ ባለው መተግበሪያ ቁጥጥር ላይ ስለሆነ\n ብሩህነትን ማስተካከል አልተቻለም"</string>
+    <string name="quick_settings_brightness_unable_adjust_msg" msgid="786478497970492300">"ከላይ ባለው መተግበሪያ ቁጥጥር ላይ ስለሆነ ብሩህነትን ማስተካከል አልተቻለም"</string>
     <string name="quick_settings_rotation_unlocked_label" msgid="2359922767950346112">"በራስ ሰር አሽከርክር"</string>
     <string name="accessibility_quick_settings_rotation" msgid="4800050198392260738">"ማያ ገጽን በራስ-አሽከርክር"</string>
     <string name="quick_settings_location_label" msgid="2621868789013389163">"አካባቢ"</string>
diff --git a/packages/SystemUI/res/values-ar/strings.xml b/packages/SystemUI/res/values-ar/strings.xml
index 7d55b85..538c961 100644
--- a/packages/SystemUI/res/values-ar/strings.xml
+++ b/packages/SystemUI/res/values-ar/strings.xml
@@ -328,7 +328,7 @@
     <string name="quick_settings_bluetooth_secondary_label_input" msgid="3887552721233148132">"الإدخال"</string>
     <string name="quick_settings_bluetooth_secondary_label_hearing_aids" msgid="5553051568867097111">"سماعات الأذن الطبية"</string>
     <string name="quick_settings_bluetooth_secondary_label_transient" msgid="3882884317600669650">"جارٍ التفعيل…"</string>
-    <string name="quick_settings_brightness_unable_adjust_msg" msgid="786478497970492300">"لا يمكن ضبط مستوى السطوع لأنّ\n التطبيق الأول يتحكّم فيه"</string>
+    <string name="quick_settings_brightness_unable_adjust_msg" msgid="786478497970492300">"لا يمكن ضبط مستوى السطوع لأنّ التطبيق الأول يتحكّم فيه"</string>
     <string name="quick_settings_rotation_unlocked_label" msgid="2359922767950346112">"التدوير التلقائي"</string>
     <string name="accessibility_quick_settings_rotation" msgid="4800050198392260738">"التدوير التلقائي للشاشة"</string>
     <string name="quick_settings_location_label" msgid="2621868789013389163">"الموقع الجغرافي"</string>
diff --git a/packages/SystemUI/res/values-az/strings.xml b/packages/SystemUI/res/values-az/strings.xml
index 4b316b6..e6b3023 100644
--- a/packages/SystemUI/res/values-az/strings.xml
+++ b/packages/SystemUI/res/values-az/strings.xml
@@ -328,7 +328,7 @@
     <string name="quick_settings_bluetooth_secondary_label_input" msgid="3887552721233148132">"Giriş"</string>
     <string name="quick_settings_bluetooth_secondary_label_hearing_aids" msgid="5553051568867097111">"Eşitmə aparatları"</string>
     <string name="quick_settings_bluetooth_secondary_label_transient" msgid="3882884317600669650">"Aktiv edilir..."</string>
-    <string name="quick_settings_brightness_unable_adjust_msg" msgid="786478497970492300">"Yuxarıdakı tətbiq tərəfindən idarə olunduğu üçün\n parlaqlığı tənzimləmək mümkün deyil"</string>
+    <string name="quick_settings_brightness_unable_adjust_msg" msgid="786478497970492300">"Yuxarıdakı tətbiq tərəfindən idarə olunduğu üçün parlaqlığı tənzimləmək mümkün deyil"</string>
     <string name="quick_settings_rotation_unlocked_label" msgid="2359922767950346112">"Avtodönüş"</string>
     <string name="accessibility_quick_settings_rotation" msgid="4800050198392260738">"Ekranın avtomatik dönməsi"</string>
     <string name="quick_settings_location_label" msgid="2621868789013389163">"Məkan"</string>
diff --git a/packages/SystemUI/res/values-b+sr+Latn/strings.xml b/packages/SystemUI/res/values-b+sr+Latn/strings.xml
index 9a5bc49..19e4076 100644
--- a/packages/SystemUI/res/values-b+sr+Latn/strings.xml
+++ b/packages/SystemUI/res/values-b+sr+Latn/strings.xml
@@ -328,7 +328,7 @@
     <string name="quick_settings_bluetooth_secondary_label_input" msgid="3887552721233148132">"Unos"</string>
     <string name="quick_settings_bluetooth_secondary_label_hearing_aids" msgid="5553051568867097111">"Slušni aparati"</string>
     <string name="quick_settings_bluetooth_secondary_label_transient" msgid="3882884317600669650">"Uključuje se..."</string>
-    <string name="quick_settings_brightness_unable_adjust_msg" msgid="786478497970492300">"Ne možete da prilagodite osvetljenost jer je\n kontroliše aplikacija u vrhu"</string>
+    <string name="quick_settings_brightness_unable_adjust_msg" msgid="786478497970492300">"Ne možete da prilagodite osvetljenost jer je kontroliše aplikacija u vrhu"</string>
     <string name="quick_settings_rotation_unlocked_label" msgid="2359922767950346112">"Automatska rotacija"</string>
     <string name="accessibility_quick_settings_rotation" msgid="4800050198392260738">"Automatsko rotiranje ekrana"</string>
     <string name="quick_settings_location_label" msgid="2621868789013389163">"Lokacija"</string>
diff --git a/packages/SystemUI/res/values-be/strings.xml b/packages/SystemUI/res/values-be/strings.xml
index 5fc56bc..aaed55b 100644
--- a/packages/SystemUI/res/values-be/strings.xml
+++ b/packages/SystemUI/res/values-be/strings.xml
@@ -328,7 +328,7 @@
     <string name="quick_settings_bluetooth_secondary_label_input" msgid="3887552721233148132">"Увод"</string>
     <string name="quick_settings_bluetooth_secondary_label_hearing_aids" msgid="5553051568867097111">"Слыхавыя апараты"</string>
     <string name="quick_settings_bluetooth_secondary_label_transient" msgid="3882884317600669650">"Уключэнне…"</string>
-    <string name="quick_settings_brightness_unable_adjust_msg" msgid="786478497970492300">"Не ўдаецца адрэгуляваць яркасць, бо яна\nкантралюецца асноўнай праграмай"</string>
+    <string name="quick_settings_brightness_unable_adjust_msg" msgid="786478497970492300">"Не ўдаецца адрэгуляваць яркасць, бо янакантралюецца асноўнай праграмай"</string>
     <string name="quick_settings_rotation_unlocked_label" msgid="2359922767950346112">"Аўтапаварот"</string>
     <string name="accessibility_quick_settings_rotation" msgid="4800050198392260738">"Аўтаматычны паварот экрана"</string>
     <string name="quick_settings_location_label" msgid="2621868789013389163">"Месцазнаходжанне"</string>
diff --git a/packages/SystemUI/res/values-bg/strings.xml b/packages/SystemUI/res/values-bg/strings.xml
index 339d751..22e30e2 100644
--- a/packages/SystemUI/res/values-bg/strings.xml
+++ b/packages/SystemUI/res/values-bg/strings.xml
@@ -328,7 +328,7 @@
     <string name="quick_settings_bluetooth_secondary_label_input" msgid="3887552721233148132">"Вход"</string>
     <string name="quick_settings_bluetooth_secondary_label_hearing_aids" msgid="5553051568867097111">"Слухови апарати"</string>
     <string name="quick_settings_bluetooth_secondary_label_transient" msgid="3882884317600669650">"Включва се..."</string>
-    <string name="quick_settings_brightness_unable_adjust_msg" msgid="786478497970492300">"Яркостта не може да се коригира, защото се контролира\n от приложението на екрана"</string>
+    <string name="quick_settings_brightness_unable_adjust_msg" msgid="786478497970492300">"Яркостта не може да се коригира, защото се контролира от приложението на екрана"</string>
     <string name="quick_settings_rotation_unlocked_label" msgid="2359922767950346112">"Авт. ориентация"</string>
     <string name="accessibility_quick_settings_rotation" msgid="4800050198392260738">"Автоматично завъртане на екрана"</string>
     <string name="quick_settings_location_label" msgid="2621868789013389163">"Местоположение"</string>
diff --git a/packages/SystemUI/res/values-bn/strings.xml b/packages/SystemUI/res/values-bn/strings.xml
index ef1253b..f0ae2ae 100644
--- a/packages/SystemUI/res/values-bn/strings.xml
+++ b/packages/SystemUI/res/values-bn/strings.xml
@@ -328,7 +328,7 @@
     <string name="quick_settings_bluetooth_secondary_label_input" msgid="3887552721233148132">"ইনপুট"</string>
     <string name="quick_settings_bluetooth_secondary_label_hearing_aids" msgid="5553051568867097111">"হিয়ারিং এড"</string>
     <string name="quick_settings_bluetooth_secondary_label_transient" msgid="3882884317600669650">"চালু করা হচ্ছে…"</string>
-    <string name="quick_settings_brightness_unable_adjust_msg" msgid="786478497970492300">"উজ্জ্বলতা টপ অ্যাপ নিয়ন্ত্রণ করায়\n এটিকে অ্যাডজাস্ট করা যাচ্ছে না"</string>
+    <string name="quick_settings_brightness_unable_adjust_msg" msgid="786478497970492300">"উজ্জ্বলতা টপ অ্যাপ নিয়ন্ত্রণ করায় এটিকে অ্যাডজাস্ট করা যাচ্ছে না"</string>
     <string name="quick_settings_rotation_unlocked_label" msgid="2359922767950346112">"নিজে থেকে ঘুরবে"</string>
     <string name="accessibility_quick_settings_rotation" msgid="4800050198392260738">"অটো-রোটেট স্ক্রিন"</string>
     <string name="quick_settings_location_label" msgid="2621868789013389163">"লোকেশন"</string>
diff --git a/packages/SystemUI/res/values-bs/strings.xml b/packages/SystemUI/res/values-bs/strings.xml
index aa036d4..a2df671 100644
--- a/packages/SystemUI/res/values-bs/strings.xml
+++ b/packages/SystemUI/res/values-bs/strings.xml
@@ -328,7 +328,7 @@
     <string name="quick_settings_bluetooth_secondary_label_input" msgid="3887552721233148132">"Ulaz"</string>
     <string name="quick_settings_bluetooth_secondary_label_hearing_aids" msgid="5553051568867097111">"Slušni aparati"</string>
     <string name="quick_settings_bluetooth_secondary_label_transient" msgid="3882884317600669650">"Uključivanje…"</string>
-    <string name="quick_settings_brightness_unable_adjust_msg" msgid="786478497970492300">"Nije moguće podesiti osvijetljenost\n jer njome upravlja aplikacija pri vrhu"</string>
+    <string name="quick_settings_brightness_unable_adjust_msg" msgid="786478497970492300">"Nije moguće podesiti osvijetljenost jer njome upravlja aplikacija pri vrhu"</string>
     <string name="quick_settings_rotation_unlocked_label" msgid="2359922767950346112">"Automatsko rotiranje"</string>
     <string name="accessibility_quick_settings_rotation" msgid="4800050198392260738">"Automatsko rotiranje ekrana"</string>
     <string name="quick_settings_location_label" msgid="2621868789013389163">"Lokacija"</string>
diff --git a/packages/SystemUI/res/values-ca/strings.xml b/packages/SystemUI/res/values-ca/strings.xml
index 15d9283..933ac96 100644
--- a/packages/SystemUI/res/values-ca/strings.xml
+++ b/packages/SystemUI/res/values-ca/strings.xml
@@ -328,7 +328,7 @@
     <string name="quick_settings_bluetooth_secondary_label_input" msgid="3887552721233148132">"Entrada"</string>
     <string name="quick_settings_bluetooth_secondary_label_hearing_aids" msgid="5553051568867097111">"Audiòfons"</string>
     <string name="quick_settings_bluetooth_secondary_label_transient" msgid="3882884317600669650">"S\'està activant…"</string>
-    <string name="quick_settings_brightness_unable_adjust_msg" msgid="786478497970492300">"No es pot ajustar la brillantor perquè\n està controlada per l\'aplicació superior"</string>
+    <string name="quick_settings_brightness_unable_adjust_msg" msgid="786478497970492300">"No es pot ajustar la brillantor perquè està controlada per l\'aplicació superior"</string>
     <string name="quick_settings_rotation_unlocked_label" msgid="2359922767950346112">"Gira automàticament"</string>
     <string name="accessibility_quick_settings_rotation" msgid="4800050198392260738">"Gira la pantalla automàticament"</string>
     <string name="quick_settings_location_label" msgid="2621868789013389163">"Ubicació"</string>
diff --git a/packages/SystemUI/res/values-cs/strings.xml b/packages/SystemUI/res/values-cs/strings.xml
index 031e59d..6c66fee 100644
--- a/packages/SystemUI/res/values-cs/strings.xml
+++ b/packages/SystemUI/res/values-cs/strings.xml
@@ -328,7 +328,7 @@
     <string name="quick_settings_bluetooth_secondary_label_input" msgid="3887552721233148132">"Vstup"</string>
     <string name="quick_settings_bluetooth_secondary_label_hearing_aids" msgid="5553051568867097111">"Naslouchátka"</string>
     <string name="quick_settings_bluetooth_secondary_label_transient" msgid="3882884317600669650">"Zapínání…"</string>
-    <string name="quick_settings_brightness_unable_adjust_msg" msgid="786478497970492300">"Jas nelze upravit, protože ho\n řídí hlavní aplikace"</string>
+    <string name="quick_settings_brightness_unable_adjust_msg" msgid="786478497970492300">"Jas nelze upravit, protože ho řídí hlavní aplikace"</string>
     <string name="quick_settings_rotation_unlocked_label" msgid="2359922767950346112">"Autom. otáčení"</string>
     <string name="accessibility_quick_settings_rotation" msgid="4800050198392260738">"Automatické otáčení obrazovky"</string>
     <string name="quick_settings_location_label" msgid="2621868789013389163">"Poloha"</string>
diff --git a/packages/SystemUI/res/values-da/strings.xml b/packages/SystemUI/res/values-da/strings.xml
index 1ddaf8c..35c07a7 100644
--- a/packages/SystemUI/res/values-da/strings.xml
+++ b/packages/SystemUI/res/values-da/strings.xml
@@ -328,7 +328,7 @@
     <string name="quick_settings_bluetooth_secondary_label_input" msgid="3887552721233148132">"Input"</string>
     <string name="quick_settings_bluetooth_secondary_label_hearing_aids" msgid="5553051568867097111">"Høreapparater"</string>
     <string name="quick_settings_bluetooth_secondary_label_transient" msgid="3882884317600669650">"Aktiverer…"</string>
-    <string name="quick_settings_brightness_unable_adjust_msg" msgid="786478497970492300">"Lysstyrken kan ikke justeres, fordi den\n styres af den øverste app"</string>
+    <string name="quick_settings_brightness_unable_adjust_msg" msgid="786478497970492300">"Lysstyrken kan ikke justeres, fordi den styres af den øverste app"</string>
     <string name="quick_settings_rotation_unlocked_label" msgid="2359922767950346112">"Roter automatisk"</string>
     <string name="accessibility_quick_settings_rotation" msgid="4800050198392260738">"Roter skærmen automatisk"</string>
     <string name="quick_settings_location_label" msgid="2621868789013389163">"Lokation"</string>
diff --git a/packages/SystemUI/res/values-de/strings.xml b/packages/SystemUI/res/values-de/strings.xml
index 16bbea1..badceeb 100644
--- a/packages/SystemUI/res/values-de/strings.xml
+++ b/packages/SystemUI/res/values-de/strings.xml
@@ -328,7 +328,7 @@
     <string name="quick_settings_bluetooth_secondary_label_input" msgid="3887552721233148132">"Eingabe"</string>
     <string name="quick_settings_bluetooth_secondary_label_hearing_aids" msgid="5553051568867097111">"Hörgerät"</string>
     <string name="quick_settings_bluetooth_secondary_label_transient" msgid="3882884317600669650">"Wird aktiviert…"</string>
-    <string name="quick_settings_brightness_unable_adjust_msg" msgid="786478497970492300">"Die Helligkeit kann nicht angepasst werden, weil sie\n von der obersten App gesteuert wird"</string>
+    <string name="quick_settings_brightness_unable_adjust_msg" msgid="786478497970492300">"Die Helligkeit kann nicht angepasst werden, weil sie von der obersten App gesteuert wird"</string>
     <string name="quick_settings_rotation_unlocked_label" msgid="2359922767950346112">"Autom. drehen"</string>
     <string name="accessibility_quick_settings_rotation" msgid="4800050198392260738">"Bildschirm automatisch drehen"</string>
     <string name="quick_settings_location_label" msgid="2621868789013389163">"Standort"</string>
diff --git a/packages/SystemUI/res/values-el/strings.xml b/packages/SystemUI/res/values-el/strings.xml
index 89c2cd2..e41c366 100644
--- a/packages/SystemUI/res/values-el/strings.xml
+++ b/packages/SystemUI/res/values-el/strings.xml
@@ -328,7 +328,7 @@
     <string name="quick_settings_bluetooth_secondary_label_input" msgid="3887552721233148132">"Είσοδος"</string>
     <string name="quick_settings_bluetooth_secondary_label_hearing_aids" msgid="5553051568867097111">"Βοηθήματα ακοής"</string>
     <string name="quick_settings_bluetooth_secondary_label_transient" msgid="3882884317600669650">"Ενεργοποίηση…"</string>
-    <string name="quick_settings_brightness_unable_adjust_msg" msgid="786478497970492300">"Δεν είναι δυνατή η προσαρμογή της φωτεινότητας, επειδή\n ελέγχεται από την εφαρμογή στην κορυφή"</string>
+    <string name="quick_settings_brightness_unable_adjust_msg" msgid="786478497970492300">"Δεν είναι δυνατή η προσαρμογή της φωτεινότητας, επειδή ελέγχεται από την εφαρμογή στην κορυφή"</string>
     <string name="quick_settings_rotation_unlocked_label" msgid="2359922767950346112">"Αυτόματη περιστροφή"</string>
     <string name="accessibility_quick_settings_rotation" msgid="4800050198392260738">"Αυτόματη περιστροφή οθόνης"</string>
     <string name="quick_settings_location_label" msgid="2621868789013389163">"Τοποθεσία"</string>
diff --git a/packages/SystemUI/res/values-en-rAU/strings.xml b/packages/SystemUI/res/values-en-rAU/strings.xml
index af0703c..ac1f233 100644
--- a/packages/SystemUI/res/values-en-rAU/strings.xml
+++ b/packages/SystemUI/res/values-en-rAU/strings.xml
@@ -328,7 +328,7 @@
     <string name="quick_settings_bluetooth_secondary_label_input" msgid="3887552721233148132">"Input"</string>
     <string name="quick_settings_bluetooth_secondary_label_hearing_aids" msgid="5553051568867097111">"Hearing aids"</string>
     <string name="quick_settings_bluetooth_secondary_label_transient" msgid="3882884317600669650">"Turning on…"</string>
-    <string name="quick_settings_brightness_unable_adjust_msg" msgid="786478497970492300">"Can\'t adjust brightness because it\'s being\n controlled by the top app"</string>
+    <string name="quick_settings_brightness_unable_adjust_msg" msgid="786478497970492300">"Can\'t adjust brightness because it\'s being controlled by the top app"</string>
     <string name="quick_settings_rotation_unlocked_label" msgid="2359922767950346112">"Auto-rotate"</string>
     <string name="accessibility_quick_settings_rotation" msgid="4800050198392260738">"Auto-rotate screen"</string>
     <string name="quick_settings_location_label" msgid="2621868789013389163">"Location"</string>
diff --git a/packages/SystemUI/res/values-en-rCA/strings.xml b/packages/SystemUI/res/values-en-rCA/strings.xml
index f171a78..e9e4e6e 100644
--- a/packages/SystemUI/res/values-en-rCA/strings.xml
+++ b/packages/SystemUI/res/values-en-rCA/strings.xml
@@ -328,7 +328,7 @@
     <string name="quick_settings_bluetooth_secondary_label_input" msgid="3887552721233148132">"Input"</string>
     <string name="quick_settings_bluetooth_secondary_label_hearing_aids" msgid="5553051568867097111">"Hearing aids"</string>
     <string name="quick_settings_bluetooth_secondary_label_transient" msgid="3882884317600669650">"Turning on…"</string>
-    <string name="quick_settings_brightness_unable_adjust_msg" msgid="786478497970492300">"Can\'t adjust brightness because it\'s being\n controlled by the top app"</string>
+    <string name="quick_settings_brightness_unable_adjust_msg" msgid="786478497970492300">"Can\'t adjust brightness because it\'s being controlled by the top app"</string>
     <string name="quick_settings_rotation_unlocked_label" msgid="2359922767950346112">"Auto-rotate"</string>
     <string name="accessibility_quick_settings_rotation" msgid="4800050198392260738">"Auto-rotate screen"</string>
     <string name="quick_settings_location_label" msgid="2621868789013389163">"Location"</string>
diff --git a/packages/SystemUI/res/values-en-rGB/strings.xml b/packages/SystemUI/res/values-en-rGB/strings.xml
index af0703c..ac1f233 100644
--- a/packages/SystemUI/res/values-en-rGB/strings.xml
+++ b/packages/SystemUI/res/values-en-rGB/strings.xml
@@ -328,7 +328,7 @@
     <string name="quick_settings_bluetooth_secondary_label_input" msgid="3887552721233148132">"Input"</string>
     <string name="quick_settings_bluetooth_secondary_label_hearing_aids" msgid="5553051568867097111">"Hearing aids"</string>
     <string name="quick_settings_bluetooth_secondary_label_transient" msgid="3882884317600669650">"Turning on…"</string>
-    <string name="quick_settings_brightness_unable_adjust_msg" msgid="786478497970492300">"Can\'t adjust brightness because it\'s being\n controlled by the top app"</string>
+    <string name="quick_settings_brightness_unable_adjust_msg" msgid="786478497970492300">"Can\'t adjust brightness because it\'s being controlled by the top app"</string>
     <string name="quick_settings_rotation_unlocked_label" msgid="2359922767950346112">"Auto-rotate"</string>
     <string name="accessibility_quick_settings_rotation" msgid="4800050198392260738">"Auto-rotate screen"</string>
     <string name="quick_settings_location_label" msgid="2621868789013389163">"Location"</string>
diff --git a/packages/SystemUI/res/values-en-rIN/strings.xml b/packages/SystemUI/res/values-en-rIN/strings.xml
index af0703c..ac1f233 100644
--- a/packages/SystemUI/res/values-en-rIN/strings.xml
+++ b/packages/SystemUI/res/values-en-rIN/strings.xml
@@ -328,7 +328,7 @@
     <string name="quick_settings_bluetooth_secondary_label_input" msgid="3887552721233148132">"Input"</string>
     <string name="quick_settings_bluetooth_secondary_label_hearing_aids" msgid="5553051568867097111">"Hearing aids"</string>
     <string name="quick_settings_bluetooth_secondary_label_transient" msgid="3882884317600669650">"Turning on…"</string>
-    <string name="quick_settings_brightness_unable_adjust_msg" msgid="786478497970492300">"Can\'t adjust brightness because it\'s being\n controlled by the top app"</string>
+    <string name="quick_settings_brightness_unable_adjust_msg" msgid="786478497970492300">"Can\'t adjust brightness because it\'s being controlled by the top app"</string>
     <string name="quick_settings_rotation_unlocked_label" msgid="2359922767950346112">"Auto-rotate"</string>
     <string name="accessibility_quick_settings_rotation" msgid="4800050198392260738">"Auto-rotate screen"</string>
     <string name="quick_settings_location_label" msgid="2621868789013389163">"Location"</string>
diff --git a/packages/SystemUI/res/values-es-rUS/strings.xml b/packages/SystemUI/res/values-es-rUS/strings.xml
index 0e15e45..7b66997 100644
--- a/packages/SystemUI/res/values-es-rUS/strings.xml
+++ b/packages/SystemUI/res/values-es-rUS/strings.xml
@@ -328,7 +328,7 @@
     <string name="quick_settings_bluetooth_secondary_label_input" msgid="3887552721233148132">"Entrada"</string>
     <string name="quick_settings_bluetooth_secondary_label_hearing_aids" msgid="5553051568867097111">"Audífonos"</string>
     <string name="quick_settings_bluetooth_secondary_label_transient" msgid="3882884317600669650">"Activando…"</string>
-    <string name="quick_settings_brightness_unable_adjust_msg" msgid="786478497970492300">"La app superior controla el brillo,\npor lo que no se puede ajustar"</string>
+    <string name="quick_settings_brightness_unable_adjust_msg" msgid="786478497970492300">"La app superior controla el brillo,por lo que no se puede ajustar"</string>
     <string name="quick_settings_rotation_unlocked_label" msgid="2359922767950346112">"Giro automático"</string>
     <string name="accessibility_quick_settings_rotation" msgid="4800050198392260738">"Girar la pantalla automáticamente"</string>
     <string name="quick_settings_location_label" msgid="2621868789013389163">"Ubicación"</string>
diff --git a/packages/SystemUI/res/values-es/strings.xml b/packages/SystemUI/res/values-es/strings.xml
index 1670ce6..e3dbc11 100644
--- a/packages/SystemUI/res/values-es/strings.xml
+++ b/packages/SystemUI/res/values-es/strings.xml
@@ -328,7 +328,7 @@
     <string name="quick_settings_bluetooth_secondary_label_input" msgid="3887552721233148132">"Entrada"</string>
     <string name="quick_settings_bluetooth_secondary_label_hearing_aids" msgid="5553051568867097111">"Audífonos"</string>
     <string name="quick_settings_bluetooth_secondary_label_transient" msgid="3882884317600669650">"Activando…"</string>
-    <string name="quick_settings_brightness_unable_adjust_msg" msgid="786478497970492300">"No se puede ajustar el brillo porque la aplicación superior lo está\n controlando"</string>
+    <string name="quick_settings_brightness_unable_adjust_msg" msgid="786478497970492300">"No se puede ajustar el brillo porque la aplicación superior lo está controlando"</string>
     <string name="quick_settings_rotation_unlocked_label" msgid="2359922767950346112">"Giro automático"</string>
     <string name="accessibility_quick_settings_rotation" msgid="4800050198392260738">"Girar pantalla automáticamente"</string>
     <string name="quick_settings_location_label" msgid="2621868789013389163">"Ubicación"</string>
diff --git a/packages/SystemUI/res/values-et/strings.xml b/packages/SystemUI/res/values-et/strings.xml
index 445b540..f13295a 100644
--- a/packages/SystemUI/res/values-et/strings.xml
+++ b/packages/SystemUI/res/values-et/strings.xml
@@ -328,7 +328,7 @@
     <string name="quick_settings_bluetooth_secondary_label_input" msgid="3887552721233148132">"Sisend"</string>
     <string name="quick_settings_bluetooth_secondary_label_hearing_aids" msgid="5553051568867097111">"Kuuldeaparaadid"</string>
     <string name="quick_settings_bluetooth_secondary_label_transient" msgid="3882884317600669650">"Sisselülitamine …"</string>
-    <string name="quick_settings_brightness_unable_adjust_msg" msgid="786478497970492300">"Heledust ei saa reguleerida, kuna seda\n juhib ülemine rakendus"</string>
+    <string name="quick_settings_brightness_unable_adjust_msg" msgid="786478497970492300">"Heledust ei saa reguleerida, kuna seda juhib ülemine rakendus"</string>
     <string name="quick_settings_rotation_unlocked_label" msgid="2359922767950346112">"Autom. pööramine"</string>
     <string name="accessibility_quick_settings_rotation" msgid="4800050198392260738">"Kuva automaatne pööramine"</string>
     <string name="quick_settings_location_label" msgid="2621868789013389163">"Asukoht"</string>
diff --git a/packages/SystemUI/res/values-eu/strings.xml b/packages/SystemUI/res/values-eu/strings.xml
index 1f7d697..7c047c0 100644
--- a/packages/SystemUI/res/values-eu/strings.xml
+++ b/packages/SystemUI/res/values-eu/strings.xml
@@ -328,7 +328,7 @@
     <string name="quick_settings_bluetooth_secondary_label_input" msgid="3887552721233148132">"Sarrera"</string>
     <string name="quick_settings_bluetooth_secondary_label_hearing_aids" msgid="5553051568867097111">"Audifonoak"</string>
     <string name="quick_settings_bluetooth_secondary_label_transient" msgid="3882884317600669650">"Aktibatzen…"</string>
-    <string name="quick_settings_brightness_unable_adjust_msg" msgid="786478497970492300">"Ezin da doitu argitasuna,\ngaineko aplikazioak kontrolatzen duelako"</string>
+    <string name="quick_settings_brightness_unable_adjust_msg" msgid="786478497970492300">"Ezin da doitu argitasuna,gaineko aplikazioak kontrolatzen duelako"</string>
     <string name="quick_settings_rotation_unlocked_label" msgid="2359922767950346112">"Biratze automatikoa"</string>
     <string name="accessibility_quick_settings_rotation" msgid="4800050198392260738">"Biratu pantaila automatikoki"</string>
     <string name="quick_settings_location_label" msgid="2621868789013389163">"Kokapena"</string>
diff --git a/packages/SystemUI/res/values-fa/strings.xml b/packages/SystemUI/res/values-fa/strings.xml
index c9392e3..e837ed3 100644
--- a/packages/SystemUI/res/values-fa/strings.xml
+++ b/packages/SystemUI/res/values-fa/strings.xml
@@ -328,7 +328,7 @@
     <string name="quick_settings_bluetooth_secondary_label_input" msgid="3887552721233148132">"ورودی"</string>
     <string name="quick_settings_bluetooth_secondary_label_hearing_aids" msgid="5553051568867097111">"سمعک"</string>
     <string name="quick_settings_bluetooth_secondary_label_transient" msgid="3882884317600669650">"روشن کردن…"</string>
-    <string name="quick_settings_brightness_unable_adjust_msg" msgid="786478497970492300">"نمی‌توان روشنایی را تنظیم کرد زیرا\n برنامه بالایی آن را کنترل می‌کند"</string>
+    <string name="quick_settings_brightness_unable_adjust_msg" msgid="786478497970492300">"نمی‌توان روشنایی را تنظیم کرد زیرا برنامه بالایی آن را کنترل می‌کند"</string>
     <string name="quick_settings_rotation_unlocked_label" msgid="2359922767950346112">"چرخش خودکار"</string>
     <string name="accessibility_quick_settings_rotation" msgid="4800050198392260738">"چرخش خودکار صفحه‌نمایش"</string>
     <string name="quick_settings_location_label" msgid="2621868789013389163">"مکان"</string>
diff --git a/packages/SystemUI/res/values-fi/strings.xml b/packages/SystemUI/res/values-fi/strings.xml
index 96162b2..ab551b3 100644
--- a/packages/SystemUI/res/values-fi/strings.xml
+++ b/packages/SystemUI/res/values-fi/strings.xml
@@ -330,7 +330,7 @@
     <string name="quick_settings_bluetooth_secondary_label_input" msgid="3887552721233148132">"Syöttölaite"</string>
     <string name="quick_settings_bluetooth_secondary_label_hearing_aids" msgid="5553051568867097111">"Kuulolaitteet"</string>
     <string name="quick_settings_bluetooth_secondary_label_transient" msgid="3882884317600669650">"Otetaan käyttöön…"</string>
-    <string name="quick_settings_brightness_unable_adjust_msg" msgid="786478497970492300">"Kirkkautta ei voi säätää, koska \n ensisijainen sovellus ohjaa sitä"</string>
+    <string name="quick_settings_brightness_unable_adjust_msg" msgid="786478497970492300">"Kirkkautta ei voi säätää, koska ensisijainen sovellus ohjaa sitä"</string>
     <string name="quick_settings_rotation_unlocked_label" msgid="2359922767950346112">"Automaattinen kääntö"</string>
     <string name="accessibility_quick_settings_rotation" msgid="4800050198392260738">"Käännä näyttöä automaattisesti."</string>
     <string name="quick_settings_location_label" msgid="2621868789013389163">"Sijainti"</string>
diff --git a/packages/SystemUI/res/values-fr-rCA/strings.xml b/packages/SystemUI/res/values-fr-rCA/strings.xml
index 8fd2ed2..bd3a7628 100644
--- a/packages/SystemUI/res/values-fr-rCA/strings.xml
+++ b/packages/SystemUI/res/values-fr-rCA/strings.xml
@@ -328,7 +328,7 @@
     <string name="quick_settings_bluetooth_secondary_label_input" msgid="3887552721233148132">"Entrée"</string>
     <string name="quick_settings_bluetooth_secondary_label_hearing_aids" msgid="5553051568867097111">"Prothèses auditives"</string>
     <string name="quick_settings_bluetooth_secondary_label_transient" msgid="3882884317600669650">"Activation en cours…"</string>
-    <string name="quick_settings_brightness_unable_adjust_msg" msgid="786478497970492300">"Impossible de régler la luminosité, car elle est\n contrôlée par l\'appli principale"</string>
+    <string name="quick_settings_brightness_unable_adjust_msg" msgid="786478497970492300">"Impossible de régler la luminosité, car elle est contrôlée par l\'appli principale"</string>
     <string name="quick_settings_rotation_unlocked_label" msgid="2359922767950346112">"Rotation auto"</string>
     <string name="accessibility_quick_settings_rotation" msgid="4800050198392260738">"Rotation automatique de l\'écran"</string>
     <string name="quick_settings_location_label" msgid="2621868789013389163">"Localisation"</string>
diff --git a/packages/SystemUI/res/values-fr/strings.xml b/packages/SystemUI/res/values-fr/strings.xml
index 89002d4..be21ba9 100644
--- a/packages/SystemUI/res/values-fr/strings.xml
+++ b/packages/SystemUI/res/values-fr/strings.xml
@@ -328,7 +328,7 @@
     <string name="quick_settings_bluetooth_secondary_label_input" msgid="3887552721233148132">"Entrée"</string>
     <string name="quick_settings_bluetooth_secondary_label_hearing_aids" msgid="5553051568867097111">"Appareils auditifs"</string>
     <string name="quick_settings_bluetooth_secondary_label_transient" msgid="3882884317600669650">"Activation…"</string>
-    <string name="quick_settings_brightness_unable_adjust_msg" msgid="786478497970492300">"Impossible d\'ajuster la luminosité, car celle-ci\n est contrôlée par l\'appli principale"</string>
+    <string name="quick_settings_brightness_unable_adjust_msg" msgid="786478497970492300">"Impossible d\'ajuster la luminosité, car celle-ci est contrôlée par l\'appli principale"</string>
     <string name="quick_settings_rotation_unlocked_label" msgid="2359922767950346112">"Rotation auto"</string>
     <string name="accessibility_quick_settings_rotation" msgid="4800050198392260738">"Rotation automatique de l\'écran"</string>
     <string name="quick_settings_location_label" msgid="2621868789013389163">"Localisation"</string>
diff --git a/packages/SystemUI/res/values-gl/strings.xml b/packages/SystemUI/res/values-gl/strings.xml
index 4e801ee..462b07a 100644
--- a/packages/SystemUI/res/values-gl/strings.xml
+++ b/packages/SystemUI/res/values-gl/strings.xml
@@ -328,7 +328,7 @@
     <string name="quick_settings_bluetooth_secondary_label_input" msgid="3887552721233148132">"Entrada"</string>
     <string name="quick_settings_bluetooth_secondary_label_hearing_aids" msgid="5553051568867097111">"Audiófonos"</string>
     <string name="quick_settings_bluetooth_secondary_label_transient" msgid="3882884317600669650">"Activando…"</string>
-    <string name="quick_settings_brightness_unable_adjust_msg" msgid="786478497970492300">"Non se pode axustar o brillo\n porque o controla a aplicación principal"</string>
+    <string name="quick_settings_brightness_unable_adjust_msg" msgid="786478497970492300">"Non se pode axustar o brillo porque o controla a aplicación principal"</string>
     <string name="quick_settings_rotation_unlocked_label" msgid="2359922767950346112">"Xirar automaticamente"</string>
     <string name="accessibility_quick_settings_rotation" msgid="4800050198392260738">"Xirar pantalla automaticamente"</string>
     <string name="quick_settings_location_label" msgid="2621868789013389163">"Localización"</string>
diff --git a/packages/SystemUI/res/values-gu/strings.xml b/packages/SystemUI/res/values-gu/strings.xml
index 08e1398..5b86eab 100644
--- a/packages/SystemUI/res/values-gu/strings.xml
+++ b/packages/SystemUI/res/values-gu/strings.xml
@@ -328,7 +328,7 @@
     <string name="quick_settings_bluetooth_secondary_label_input" msgid="3887552721233148132">"ઇનપુટ"</string>
     <string name="quick_settings_bluetooth_secondary_label_hearing_aids" msgid="5553051568867097111">"સાંભળવામાં મદદ આપતા યંત્રો"</string>
     <string name="quick_settings_bluetooth_secondary_label_transient" msgid="3882884317600669650">"ચાલુ કરી રહ્યાં છીએ…"</string>
-    <string name="quick_settings_brightness_unable_adjust_msg" msgid="786478497970492300">"બ્રાઇટનેસ ગોઠવી શકતા નથી કારણ કે તે\n લોકપ્રિય ઍપ દ્વારા નિયંત્રિત કરવામાં આવી રહી છે"</string>
+    <string name="quick_settings_brightness_unable_adjust_msg" msgid="786478497970492300">"બ્રાઇટનેસ ગોઠવી શકતા નથી કારણ કે તે લોકપ્રિય ઍપ દ્વારા નિયંત્રિત કરવામાં આવી રહી છે"</string>
     <string name="quick_settings_rotation_unlocked_label" msgid="2359922767950346112">"ઑટો રોટેટ"</string>
     <string name="accessibility_quick_settings_rotation" msgid="4800050198392260738">"ઑટો રોટેટ સ્ક્રીન"</string>
     <string name="quick_settings_location_label" msgid="2621868789013389163">"લોકેશન"</string>
diff --git a/packages/SystemUI/res/values-hi/strings.xml b/packages/SystemUI/res/values-hi/strings.xml
index d781585..b45d3e9 100644
--- a/packages/SystemUI/res/values-hi/strings.xml
+++ b/packages/SystemUI/res/values-hi/strings.xml
@@ -328,7 +328,7 @@
     <string name="quick_settings_bluetooth_secondary_label_input" msgid="3887552721233148132">"इनपुट"</string>
     <string name="quick_settings_bluetooth_secondary_label_hearing_aids" msgid="5553051568867097111">"कान की मशीनें"</string>
     <string name="quick_settings_bluetooth_secondary_label_transient" msgid="3882884317600669650">"ब्लूटूथ चालू हो रहा है…"</string>
-    <string name="quick_settings_brightness_unable_adjust_msg" msgid="786478497970492300">"स्क्रीन की रोशनी को एडजस्ट नहीं किया जा सकता, क्योंकि\n इसे टॉप ऐप्लिकेशन कंट्रोल कर रहा है"</string>
+    <string name="quick_settings_brightness_unable_adjust_msg" msgid="786478497970492300">"स्क्रीन की रोशनी को एडजस्ट नहीं किया जा सकता, क्योंकि इसे टॉप ऐप्लिकेशन कंट्रोल कर रहा है"</string>
     <string name="quick_settings_rotation_unlocked_label" msgid="2359922767950346112">"ऑटो-रोटेट"</string>
     <string name="accessibility_quick_settings_rotation" msgid="4800050198392260738">"स्क्रीन का अपने-आप दिशा बदलना (ऑटो-रोटेट)"</string>
     <string name="quick_settings_location_label" msgid="2621868789013389163">"जगह की जानकारी"</string>
diff --git a/packages/SystemUI/res/values-hr/strings.xml b/packages/SystemUI/res/values-hr/strings.xml
index 3a985a9..1b351bd 100644
--- a/packages/SystemUI/res/values-hr/strings.xml
+++ b/packages/SystemUI/res/values-hr/strings.xml
@@ -328,7 +328,7 @@
     <string name="quick_settings_bluetooth_secondary_label_input" msgid="3887552721233148132">"Unos"</string>
     <string name="quick_settings_bluetooth_secondary_label_hearing_aids" msgid="5553051568867097111">"Slušna pomagala"</string>
     <string name="quick_settings_bluetooth_secondary_label_transient" msgid="3882884317600669650">"Uključivanje…"</string>
-    <string name="quick_settings_brightness_unable_adjust_msg" msgid="786478497970492300">"Svjetlina se ne može prilagoditi jer njome\n upravlja aplikacija pri vrhu"</string>
+    <string name="quick_settings_brightness_unable_adjust_msg" msgid="786478497970492300">"Svjetlina se ne može prilagoditi jer njome upravlja aplikacija pri vrhu"</string>
     <string name="quick_settings_rotation_unlocked_label" msgid="2359922767950346112">"Automatsko zakretanje"</string>
     <string name="accessibility_quick_settings_rotation" msgid="4800050198392260738">"Automatsko zakretanje zaslona"</string>
     <string name="quick_settings_location_label" msgid="2621868789013389163">"Lokacija"</string>
diff --git a/packages/SystemUI/res/values-hu/strings.xml b/packages/SystemUI/res/values-hu/strings.xml
index 9b17567..ca32832 100644
--- a/packages/SystemUI/res/values-hu/strings.xml
+++ b/packages/SystemUI/res/values-hu/strings.xml
@@ -328,7 +328,7 @@
     <string name="quick_settings_bluetooth_secondary_label_input" msgid="3887552721233148132">"Bevitel"</string>
     <string name="quick_settings_bluetooth_secondary_label_hearing_aids" msgid="5553051568867097111">"Hallókészülék"</string>
     <string name="quick_settings_bluetooth_secondary_label_transient" msgid="3882884317600669650">"Bekapcsolás…"</string>
-    <string name="quick_settings_brightness_unable_adjust_msg" msgid="786478497970492300">"Nem lehet módosítani a fényerőt, mert a felső alkalmazás\n vezérli"</string>
+    <string name="quick_settings_brightness_unable_adjust_msg" msgid="786478497970492300">"Nem lehet módosítani a fényerőt, mert a felső alkalmazás vezérli"</string>
     <string name="quick_settings_rotation_unlocked_label" msgid="2359922767950346112">"Automatikus elforgatás"</string>
     <string name="accessibility_quick_settings_rotation" msgid="4800050198392260738">"Automatikus képernyőforgatás"</string>
     <string name="quick_settings_location_label" msgid="2621868789013389163">"Tartózkodási hely"</string>
diff --git a/packages/SystemUI/res/values-hy/strings.xml b/packages/SystemUI/res/values-hy/strings.xml
index 2aa72cc..b619708 100644
--- a/packages/SystemUI/res/values-hy/strings.xml
+++ b/packages/SystemUI/res/values-hy/strings.xml
@@ -328,7 +328,7 @@
     <string name="quick_settings_bluetooth_secondary_label_input" msgid="3887552721233148132">"Մուտքագրում"</string>
     <string name="quick_settings_bluetooth_secondary_label_hearing_aids" msgid="5553051568867097111">"Լսողական սարք"</string>
     <string name="quick_settings_bluetooth_secondary_label_transient" msgid="3882884317600669650">"Միացում…"</string>
-    <string name="quick_settings_brightness_unable_adjust_msg" msgid="786478497970492300">"Հնարավոր չէ կարգավորել պայծառությունը, քանի որ այն\n կառավարվում է գլխավոր հավելվածի կողմից"</string>
+    <string name="quick_settings_brightness_unable_adjust_msg" msgid="786478497970492300">"Հնարավոր չէ կարգավորել պայծառությունը, քանի որ այն կառավարվում է գլխավոր հավելվածի կողմից"</string>
     <string name="quick_settings_rotation_unlocked_label" msgid="2359922767950346112">"Ինքնապտտում"</string>
     <string name="accessibility_quick_settings_rotation" msgid="4800050198392260738">"Ավտոմատ պտտել էկրանը"</string>
     <string name="quick_settings_location_label" msgid="2621868789013389163">"Տեղորոշում"</string>
diff --git a/packages/SystemUI/res/values-in/strings.xml b/packages/SystemUI/res/values-in/strings.xml
index c57f40f..2a24134 100644
--- a/packages/SystemUI/res/values-in/strings.xml
+++ b/packages/SystemUI/res/values-in/strings.xml
@@ -328,7 +328,7 @@
     <string name="quick_settings_bluetooth_secondary_label_input" msgid="3887552721233148132">"Input"</string>
     <string name="quick_settings_bluetooth_secondary_label_hearing_aids" msgid="5553051568867097111">"Alat bantu dengar"</string>
     <string name="quick_settings_bluetooth_secondary_label_transient" msgid="3882884317600669650">"Mengaktifkan…"</string>
-    <string name="quick_settings_brightness_unable_adjust_msg" msgid="786478497970492300">"Tidak dapat menyesuaikan kecerahan karena sedang\n dikontrol oleh aplikasi atas"</string>
+    <string name="quick_settings_brightness_unable_adjust_msg" msgid="786478497970492300">"Tidak dapat menyesuaikan kecerahan karena sedang dikontrol oleh aplikasi atas"</string>
     <string name="quick_settings_rotation_unlocked_label" msgid="2359922767950346112">"Putar Otomatis"</string>
     <string name="accessibility_quick_settings_rotation" msgid="4800050198392260738">"Putar layar otomatis"</string>
     <string name="quick_settings_location_label" msgid="2621868789013389163">"Lokasi"</string>
diff --git a/packages/SystemUI/res/values-is/strings.xml b/packages/SystemUI/res/values-is/strings.xml
index ec284c2..5b33fdd 100644
--- a/packages/SystemUI/res/values-is/strings.xml
+++ b/packages/SystemUI/res/values-is/strings.xml
@@ -328,7 +328,7 @@
     <string name="quick_settings_bluetooth_secondary_label_input" msgid="3887552721233148132">"Inntak"</string>
     <string name="quick_settings_bluetooth_secondary_label_hearing_aids" msgid="5553051568867097111">"Heyrnartæki"</string>
     <string name="quick_settings_bluetooth_secondary_label_transient" msgid="3882884317600669650">"Kveikir…"</string>
-    <string name="quick_settings_brightness_unable_adjust_msg" msgid="786478497970492300">"Ekki er hægt að breyta birtustiginu vegna þess að \n efsta forritið stjórnar því"</string>
+    <string name="quick_settings_brightness_unable_adjust_msg" msgid="786478497970492300">"Ekki er hægt að breyta birtustiginu vegna þess að efsta forritið stjórnar því"</string>
     <string name="quick_settings_rotation_unlocked_label" msgid="2359922767950346112">"Sjálfvirkur snúningur"</string>
     <string name="accessibility_quick_settings_rotation" msgid="4800050198392260738">"Snúa skjá sjálfkrafa"</string>
     <string name="quick_settings_location_label" msgid="2621868789013389163">"Staðsetning"</string>
diff --git a/packages/SystemUI/res/values-it/strings.xml b/packages/SystemUI/res/values-it/strings.xml
index 2391fe2..4858cb0 100644
--- a/packages/SystemUI/res/values-it/strings.xml
+++ b/packages/SystemUI/res/values-it/strings.xml
@@ -328,7 +328,7 @@
     <string name="quick_settings_bluetooth_secondary_label_input" msgid="3887552721233148132">"Ingresso"</string>
     <string name="quick_settings_bluetooth_secondary_label_hearing_aids" msgid="5553051568867097111">"Apparecchi acustici"</string>
     <string name="quick_settings_bluetooth_secondary_label_transient" msgid="3882884317600669650">"Attivazione…"</string>
-    <string name="quick_settings_brightness_unable_adjust_msg" msgid="786478497970492300">"Impossibile regolare la luminosità perché è\n controllata dall\'app in primo piano"</string>
+    <string name="quick_settings_brightness_unable_adjust_msg" msgid="786478497970492300">"Impossibile regolare la luminosità perché è controllata dall\'app in primo piano"</string>
     <string name="quick_settings_rotation_unlocked_label" msgid="2359922767950346112">"Rotazione automatica"</string>
     <string name="accessibility_quick_settings_rotation" msgid="4800050198392260738">"Rotazione automatica dello schermo"</string>
     <string name="quick_settings_location_label" msgid="2621868789013389163">"Posizione"</string>
diff --git a/packages/SystemUI/res/values-iw/strings.xml b/packages/SystemUI/res/values-iw/strings.xml
index 0d7a620..ad5fe65 100644
--- a/packages/SystemUI/res/values-iw/strings.xml
+++ b/packages/SystemUI/res/values-iw/strings.xml
@@ -328,7 +328,7 @@
     <string name="quick_settings_bluetooth_secondary_label_input" msgid="3887552721233148132">"קלט"</string>
     <string name="quick_settings_bluetooth_secondary_label_hearing_aids" msgid="5553051568867097111">"מכשירי שמיעה"</string>
     <string name="quick_settings_bluetooth_secondary_label_transient" msgid="3882884317600669650">"ההפעלה מתבצעת…"</string>
-    <string name="quick_settings_brightness_unable_adjust_msg" msgid="786478497970492300">"לא ניתן להתאים את הבהירות כי היא\n נשלטת על ידי האפליקציה העליונה"</string>
+    <string name="quick_settings_brightness_unable_adjust_msg" msgid="786478497970492300">"לא ניתן להתאים את הבהירות כי היא נשלטת על ידי האפליקציה העליונה"</string>
     <string name="quick_settings_rotation_unlocked_label" msgid="2359922767950346112">"סיבוב אוטומטי"</string>
     <string name="accessibility_quick_settings_rotation" msgid="4800050198392260738">"סיבוב אוטומטי של המסך"</string>
     <string name="quick_settings_location_label" msgid="2621868789013389163">"מיקום"</string>
diff --git a/packages/SystemUI/res/values-ja/strings.xml b/packages/SystemUI/res/values-ja/strings.xml
index 7867633..bc3555a 100644
--- a/packages/SystemUI/res/values-ja/strings.xml
+++ b/packages/SystemUI/res/values-ja/strings.xml
@@ -328,7 +328,7 @@
     <string name="quick_settings_bluetooth_secondary_label_input" msgid="3887552721233148132">"入力"</string>
     <string name="quick_settings_bluetooth_secondary_label_hearing_aids" msgid="5553051568867097111">"補聴器"</string>
     <string name="quick_settings_bluetooth_secondary_label_transient" msgid="3882884317600669650">"ON にしています…"</string>
-    <string name="quick_settings_brightness_unable_adjust_msg" msgid="786478497970492300">"明るさはトップ アプリによって\n制御されているため、調整できません"</string>
+    <string name="quick_settings_brightness_unable_adjust_msg" msgid="786478497970492300">"明るさはトップ アプリによって制御されているため、調整できません"</string>
     <string name="quick_settings_rotation_unlocked_label" msgid="2359922767950346112">"自動回転"</string>
     <string name="accessibility_quick_settings_rotation" msgid="4800050198392260738">"画面を自動回転します"</string>
     <string name="quick_settings_location_label" msgid="2621868789013389163">"位置情報"</string>
diff --git a/packages/SystemUI/res/values-kk/strings.xml b/packages/SystemUI/res/values-kk/strings.xml
index bf12aca..5c1b563 100644
--- a/packages/SystemUI/res/values-kk/strings.xml
+++ b/packages/SystemUI/res/values-kk/strings.xml
@@ -328,7 +328,7 @@
     <string name="quick_settings_bluetooth_secondary_label_input" msgid="3887552721233148132">"Кіріс"</string>
     <string name="quick_settings_bluetooth_secondary_label_hearing_aids" msgid="5553051568867097111">"Есту аппараттары"</string>
     <string name="quick_settings_bluetooth_secondary_label_transient" msgid="3882884317600669650">"Қосылып жатыр…"</string>
-    <string name="quick_settings_brightness_unable_adjust_msg" msgid="786478497970492300">"Жарықтықты реттеу мүмкін емес, себебі ол\n жетекші қолданба арқылы басқарылады."</string>
+    <string name="quick_settings_brightness_unable_adjust_msg" msgid="786478497970492300">"Жарықтықты реттеу мүмкін емес, себебі ол жетекші қолданба арқылы басқарылады."</string>
     <string name="quick_settings_rotation_unlocked_label" msgid="2359922767950346112">"Автоматты түрде бұру"</string>
     <string name="accessibility_quick_settings_rotation" msgid="4800050198392260738">"Автоматты айналатын экран"</string>
     <string name="quick_settings_location_label" msgid="2621868789013389163">"Локация"</string>
diff --git a/packages/SystemUI/res/values-km/strings.xml b/packages/SystemUI/res/values-km/strings.xml
index e12fd13..0c2e9c2 100644
--- a/packages/SystemUI/res/values-km/strings.xml
+++ b/packages/SystemUI/res/values-km/strings.xml
@@ -328,7 +328,7 @@
     <string name="quick_settings_bluetooth_secondary_label_input" msgid="3887552721233148132">"បញ្ចូល"</string>
     <string name="quick_settings_bluetooth_secondary_label_hearing_aids" msgid="5553051568867097111">"ឧបករណ៍ជំនួយការស្ដាប់"</string>
     <string name="quick_settings_bluetooth_secondary_label_transient" msgid="3882884317600669650">"កំពុង​បើក..."</string>
-    <string name="quick_settings_brightness_unable_adjust_msg" msgid="786478497970492300">"មិនអាចកែតម្រូវកម្រិតពន្លឺបានទេ ដោយសារវាកំពុងស្ថិតក្រោម\nការគ្រប់គ្រងរបស់កម្មវិធីខាងលើគេ"</string>
+    <string name="quick_settings_brightness_unable_adjust_msg" msgid="786478497970492300">"មិនអាចកែតម្រូវកម្រិតពន្លឺបានទេ ដោយសារវាកំពុងស្ថិតក្រោមការគ្រប់គ្រងរបស់កម្មវិធីខាងលើគេ"</string>
     <string name="quick_settings_rotation_unlocked_label" msgid="2359922767950346112">"បង្វិល​ស្វ័យ​ប្រវត្តិ"</string>
     <string name="accessibility_quick_settings_rotation" msgid="4800050198392260738">"បង្វិលអេក្រង់ស្វ័យប្រវត្តិ"</string>
     <string name="quick_settings_location_label" msgid="2621868789013389163">"ទី​តាំង​"</string>
diff --git a/packages/SystemUI/res/values-kn/strings.xml b/packages/SystemUI/res/values-kn/strings.xml
index 3bb4e4d..2136599 100644
--- a/packages/SystemUI/res/values-kn/strings.xml
+++ b/packages/SystemUI/res/values-kn/strings.xml
@@ -328,7 +328,7 @@
     <string name="quick_settings_bluetooth_secondary_label_input" msgid="3887552721233148132">"ಇನ್‌ಪುಟ್"</string>
     <string name="quick_settings_bluetooth_secondary_label_hearing_aids" msgid="5553051568867097111">"ಶ್ರವಣ ಸಾಧನಗಳು"</string>
     <string name="quick_settings_bluetooth_secondary_label_transient" msgid="3882884317600669650">"ಆನ್ ಮಾಡಲಾಗುತ್ತಿದೆ..."</string>
-    <string name="quick_settings_brightness_unable_adjust_msg" msgid="786478497970492300">"ಬ್ರೈಟ್‌ನೆಸ್ ಅನ್ನು ಅಡ್ಜಸ್ಟ್ ಮಾಡಲು ಸಾಧ್ಯವಿಲ್ಲ,\n ಏಕೆಂದರೆ ಅದನ್ನು ಟಾಪ್ ಆ್ಯಪ್ ನಿಯಂತ್ರಿಸುತ್ತಿದೆ"</string>
+    <string name="quick_settings_brightness_unable_adjust_msg" msgid="786478497970492300">"ಬ್ರೈಟ್‌ನೆಸ್ ಅನ್ನು ಅಡ್ಜಸ್ಟ್ ಮಾಡಲು ಸಾಧ್ಯವಿಲ್ಲ, ಏಕೆಂದರೆ ಅದನ್ನು ಟಾಪ್ ಆ್ಯಪ್ ನಿಯಂತ್ರಿಸುತ್ತಿದೆ"</string>
     <string name="quick_settings_rotation_unlocked_label" msgid="2359922767950346112">"ಸ್ವಯಂ-ತಿರುಗುವಿಕೆ"</string>
     <string name="accessibility_quick_settings_rotation" msgid="4800050198392260738">"ಪರದೆಯನ್ನು ಸ್ವಯಂ-ತಿರುಗಿಸಿ"</string>
     <string name="quick_settings_location_label" msgid="2621868789013389163">"ಸ್ಥಳ"</string>
diff --git a/packages/SystemUI/res/values-ko/strings.xml b/packages/SystemUI/res/values-ko/strings.xml
index 33f54b6..9b26e97 100644
--- a/packages/SystemUI/res/values-ko/strings.xml
+++ b/packages/SystemUI/res/values-ko/strings.xml
@@ -328,7 +328,7 @@
     <string name="quick_settings_bluetooth_secondary_label_input" msgid="3887552721233148132">"입력"</string>
     <string name="quick_settings_bluetooth_secondary_label_hearing_aids" msgid="5553051568867097111">"보청기"</string>
     <string name="quick_settings_bluetooth_secondary_label_transient" msgid="3882884317600669650">"켜는 중..."</string>
-    <string name="quick_settings_brightness_unable_adjust_msg" msgid="786478497970492300">"상위 앱에서 밝기를 제어하고 있으므로\n 밝기를 조절할 수 없습니다."</string>
+    <string name="quick_settings_brightness_unable_adjust_msg" msgid="786478497970492300">"상위 앱에서 밝기를 제어하고 있으므로 밝기를 조절할 수 없습니다."</string>
     <string name="quick_settings_rotation_unlocked_label" msgid="2359922767950346112">"자동 회전"</string>
     <string name="accessibility_quick_settings_rotation" msgid="4800050198392260738">"화면 자동 회전"</string>
     <string name="quick_settings_location_label" msgid="2621868789013389163">"위치"</string>
diff --git a/packages/SystemUI/res/values-ky/strings.xml b/packages/SystemUI/res/values-ky/strings.xml
index e764bad..c1d5201 100644
--- a/packages/SystemUI/res/values-ky/strings.xml
+++ b/packages/SystemUI/res/values-ky/strings.xml
@@ -328,7 +328,7 @@
     <string name="quick_settings_bluetooth_secondary_label_input" msgid="3887552721233148132">"Киргизүү"</string>
     <string name="quick_settings_bluetooth_secondary_label_hearing_aids" msgid="5553051568867097111">"Угуу аппараттары"</string>
     <string name="quick_settings_bluetooth_secondary_label_transient" msgid="3882884317600669650">"Күйгүзүлүүдө…"</string>
-    <string name="quick_settings_brightness_unable_adjust_msg" msgid="786478497970492300">"Жарыктыкты тууралоого болбойт, анткени аны\n жогорку колдонмо көзөмөлдөйт"</string>
+    <string name="quick_settings_brightness_unable_adjust_msg" msgid="786478497970492300">"Жарыктыкты тууралоого болбойт, анткени аны жогорку колдонмо көзөмөлдөйт"</string>
     <string name="quick_settings_rotation_unlocked_label" msgid="2359922767950346112">"Авто буруу"</string>
     <string name="accessibility_quick_settings_rotation" msgid="4800050198392260738">"Экранды авто буруу"</string>
     <string name="quick_settings_location_label" msgid="2621868789013389163">"Жайгашкан жер"</string>
diff --git a/packages/SystemUI/res/values-lo/strings.xml b/packages/SystemUI/res/values-lo/strings.xml
index 3236b2e2..702c04f 100644
--- a/packages/SystemUI/res/values-lo/strings.xml
+++ b/packages/SystemUI/res/values-lo/strings.xml
@@ -328,7 +328,7 @@
     <string name="quick_settings_bluetooth_secondary_label_input" msgid="3887552721233148132">"ການປ້ອນຂໍ້ມູນ"</string>
     <string name="quick_settings_bluetooth_secondary_label_hearing_aids" msgid="5553051568867097111">"ເຄື່ອງຊ່ວຍຟັງ"</string>
     <string name="quick_settings_bluetooth_secondary_label_transient" msgid="3882884317600669650">"ກຳລັງເປີດ..."</string>
-    <string name="quick_settings_brightness_unable_adjust_msg" msgid="786478497970492300">"ບໍ່ສາມາດປັບຄວາມແຈ້ງເນື່ອງຈາກມັນ\n ຖືກຄວບຄຸມໂດຍແອັບຍອດນິຍົມ"</string>
+    <string name="quick_settings_brightness_unable_adjust_msg" msgid="786478497970492300">"ບໍ່ສາມາດປັບຄວາມແຈ້ງເນື່ອງຈາກມັນ ຖືກຄວບຄຸມໂດຍແອັບຍອດນິຍົມ"</string>
     <string name="quick_settings_rotation_unlocked_label" msgid="2359922767950346112">"ໝຸນ​ອັດ​ຕະ​ໂນ​ມັດ"</string>
     <string name="accessibility_quick_settings_rotation" msgid="4800050198392260738">"ໝຸນໜ້າຈໍອັດຕະໂນມັດ"</string>
     <string name="quick_settings_location_label" msgid="2621868789013389163">"ສະຖານທີ່"</string>
diff --git a/packages/SystemUI/res/values-lt/strings.xml b/packages/SystemUI/res/values-lt/strings.xml
index 25208b0..36b2b99 100644
--- a/packages/SystemUI/res/values-lt/strings.xml
+++ b/packages/SystemUI/res/values-lt/strings.xml
@@ -328,7 +328,7 @@
     <string name="quick_settings_bluetooth_secondary_label_input" msgid="3887552721233148132">"Įvestis"</string>
     <string name="quick_settings_bluetooth_secondary_label_hearing_aids" msgid="5553051568867097111">"Klausos aparatai"</string>
     <string name="quick_settings_bluetooth_secondary_label_transient" msgid="3882884317600669650">"Įjungiama…"</string>
-    <string name="quick_settings_brightness_unable_adjust_msg" msgid="786478497970492300">"Negalima koreguoti ryškumo, nes jį valdo\n viršuje esanti programa"</string>
+    <string name="quick_settings_brightness_unable_adjust_msg" msgid="786478497970492300">"Negalima koreguoti ryškumo, nes jį valdo viršuje esanti programa"</string>
     <string name="quick_settings_rotation_unlocked_label" msgid="2359922767950346112">"Automatinis pasukimas"</string>
     <string name="accessibility_quick_settings_rotation" msgid="4800050198392260738">"Automatiškai sukti ekraną"</string>
     <string name="quick_settings_location_label" msgid="2621868789013389163">"Vietovė"</string>
diff --git a/packages/SystemUI/res/values-lv/strings.xml b/packages/SystemUI/res/values-lv/strings.xml
index bf1ef96..dcb405b 100644
--- a/packages/SystemUI/res/values-lv/strings.xml
+++ b/packages/SystemUI/res/values-lv/strings.xml
@@ -328,7 +328,7 @@
     <string name="quick_settings_bluetooth_secondary_label_input" msgid="3887552721233148132">"Ievade"</string>
     <string name="quick_settings_bluetooth_secondary_label_hearing_aids" msgid="5553051568867097111">"Dzirdes aparāti"</string>
     <string name="quick_settings_bluetooth_secondary_label_transient" msgid="3882884317600669650">"Notiek ieslēgšana…"</string>
-    <string name="quick_settings_brightness_unable_adjust_msg" msgid="786478497970492300">"Nevar mainīt spilgtumu, jo to kontrolē\n aktīvā lietotne."</string>
+    <string name="quick_settings_brightness_unable_adjust_msg" msgid="786478497970492300">"Nevar mainīt spilgtumu, jo to kontrolē aktīvā lietotne."</string>
     <string name="quick_settings_rotation_unlocked_label" msgid="2359922767950346112">"Automātiska pagriešana"</string>
     <string name="accessibility_quick_settings_rotation" msgid="4800050198392260738">"Automātiska ekrāna pagriešana"</string>
     <string name="quick_settings_location_label" msgid="2621868789013389163">"Atrašanās vieta"</string>
diff --git a/packages/SystemUI/res/values-mk/strings.xml b/packages/SystemUI/res/values-mk/strings.xml
index e34d9d8..10435b8 100644
--- a/packages/SystemUI/res/values-mk/strings.xml
+++ b/packages/SystemUI/res/values-mk/strings.xml
@@ -328,7 +328,7 @@
     <string name="quick_settings_bluetooth_secondary_label_input" msgid="3887552721233148132">"Влез"</string>
     <string name="quick_settings_bluetooth_secondary_label_hearing_aids" msgid="5553051568867097111">"Слушни помагала"</string>
     <string name="quick_settings_bluetooth_secondary_label_transient" msgid="3882884317600669650">"Се вклучува…"</string>
-    <string name="quick_settings_brightness_unable_adjust_msg" msgid="786478497970492300">"Не може да се приспособи осветленоста бидејќи е\n контролирана од горната апликација"</string>
+    <string name="quick_settings_brightness_unable_adjust_msg" msgid="786478497970492300">"Не може да се приспособи осветленоста бидејќи е контролирана од горната апликација"</string>
     <string name="quick_settings_rotation_unlocked_label" msgid="2359922767950346112">"Автоматско ротирање"</string>
     <string name="accessibility_quick_settings_rotation" msgid="4800050198392260738">"Автоматско ротирање на екранот"</string>
     <string name="quick_settings_location_label" msgid="2621868789013389163">"Локација"</string>
diff --git a/packages/SystemUI/res/values-ml/strings.xml b/packages/SystemUI/res/values-ml/strings.xml
index 226822a..489f1ad 100644
--- a/packages/SystemUI/res/values-ml/strings.xml
+++ b/packages/SystemUI/res/values-ml/strings.xml
@@ -328,7 +328,7 @@
     <string name="quick_settings_bluetooth_secondary_label_input" msgid="3887552721233148132">"ഇൻപുട്ട്"</string>
     <string name="quick_settings_bluetooth_secondary_label_hearing_aids" msgid="5553051568867097111">"ശ്രവണ സഹായികൾ"</string>
     <string name="quick_settings_bluetooth_secondary_label_transient" msgid="3882884317600669650">"ഓണാക്കുന്നു…"</string>
-    <string name="quick_settings_brightness_unable_adjust_msg" msgid="786478497970492300">"തെളിച്ചം അഡ്‌ജസ്റ്റ് ചെയ്യാനാകില്ല, അത്\n നിയന്ത്രിക്കുന്നത് ഏറ്റവും മുകളിലുള്ള ആപ്പാണ്"</string>
+    <string name="quick_settings_brightness_unable_adjust_msg" msgid="786478497970492300">"തെളിച്ചം അഡ്‌ജസ്റ്റ് ചെയ്യാനാകില്ല, അത് നിയന്ത്രിക്കുന്നത് ഏറ്റവും മുകളിലുള്ള ആപ്പാണ്"</string>
     <string name="quick_settings_rotation_unlocked_label" msgid="2359922767950346112">"സ്‌ക്രീൻ സ്വയമേവ തിരിയൽ"</string>
     <string name="accessibility_quick_settings_rotation" msgid="4800050198392260738">"സ്‌ക്രീൻ സ്വയമേവ തിരിക്കുക"</string>
     <string name="quick_settings_location_label" msgid="2621868789013389163">"ലൊക്കേഷൻ"</string>
diff --git a/packages/SystemUI/res/values-mn/strings.xml b/packages/SystemUI/res/values-mn/strings.xml
index 06b62e9..776f1e9 100644
--- a/packages/SystemUI/res/values-mn/strings.xml
+++ b/packages/SystemUI/res/values-mn/strings.xml
@@ -328,7 +328,7 @@
     <string name="quick_settings_bluetooth_secondary_label_input" msgid="3887552721233148132">"Оролт"</string>
     <string name="quick_settings_bluetooth_secondary_label_hearing_aids" msgid="5553051568867097111">"Сонсголын төхөөрөмж"</string>
     <string name="quick_settings_bluetooth_secondary_label_transient" msgid="3882884317600669650">"Асааж байна…"</string>
-    <string name="quick_settings_brightness_unable_adjust_msg" msgid="786478497970492300">"Гэрэлтүүлгийг\nдавуу эрхтэй аппаас хянаж байгаа тул тохируулах боломжгүй"</string>
+    <string name="quick_settings_brightness_unable_adjust_msg" msgid="786478497970492300">"Гэрэлтүүлгийгдавуу эрхтэй аппаас хянаж байгаа тул тохируулах боломжгүй"</string>
     <string name="quick_settings_rotation_unlocked_label" msgid="2359922767950346112">"Автоматаар эргэх"</string>
     <string name="accessibility_quick_settings_rotation" msgid="4800050198392260738">"Дэлгэцийг автоматаар эргүүлэх"</string>
     <string name="quick_settings_location_label" msgid="2621868789013389163">"Байршил"</string>
diff --git a/packages/SystemUI/res/values-mr/strings.xml b/packages/SystemUI/res/values-mr/strings.xml
index 4c2fd29..5691410 100644
--- a/packages/SystemUI/res/values-mr/strings.xml
+++ b/packages/SystemUI/res/values-mr/strings.xml
@@ -328,7 +328,7 @@
     <string name="quick_settings_bluetooth_secondary_label_input" msgid="3887552721233148132">"इनपुट"</string>
     <string name="quick_settings_bluetooth_secondary_label_hearing_aids" msgid="5553051568867097111">"श्रवणयंत्रे"</string>
     <string name="quick_settings_bluetooth_secondary_label_transient" msgid="3882884317600669650">"सुरू करत आहे…"</string>
-    <string name="quick_settings_brightness_unable_adjust_msg" msgid="786478497970492300">"ब्राइटनेस ॲडजस्ट करू शकत नाही, कारण तो\n टॉप ॲपद्वारे नियंत्रित केला जात आहे"</string>
+    <string name="quick_settings_brightness_unable_adjust_msg" msgid="786478497970492300">"ब्राइटनेस ॲडजस्ट करू शकत नाही, कारण तो टॉप ॲपद्वारे नियंत्रित केला जात आहे"</string>
     <string name="quick_settings_rotation_unlocked_label" msgid="2359922767950346112">"ऑटो-रोटेट"</string>
     <string name="accessibility_quick_settings_rotation" msgid="4800050198392260738">"ऑटो-रोटेट स्क्रीन"</string>
     <string name="quick_settings_location_label" msgid="2621868789013389163">"स्थान"</string>
diff --git a/packages/SystemUI/res/values-ms/strings.xml b/packages/SystemUI/res/values-ms/strings.xml
index 93f25ff..997e4a0 100644
--- a/packages/SystemUI/res/values-ms/strings.xml
+++ b/packages/SystemUI/res/values-ms/strings.xml
@@ -328,7 +328,7 @@
     <string name="quick_settings_bluetooth_secondary_label_input" msgid="3887552721233148132">"Input"</string>
     <string name="quick_settings_bluetooth_secondary_label_hearing_aids" msgid="5553051568867097111">"Alat bantu pendengaran"</string>
     <string name="quick_settings_bluetooth_secondary_label_transient" msgid="3882884317600669650">"Menghidupkan…"</string>
-    <string name="quick_settings_brightness_unable_adjust_msg" msgid="786478497970492300">"Tidak dapat melaraskan kecerahan kerana peranti\n dikawal oleh apl popular"</string>
+    <string name="quick_settings_brightness_unable_adjust_msg" msgid="786478497970492300">"Tidak dapat melaraskan kecerahan kerana peranti dikawal oleh apl popular"</string>
     <string name="quick_settings_rotation_unlocked_label" msgid="2359922767950346112">"Autoputar"</string>
     <string name="accessibility_quick_settings_rotation" msgid="4800050198392260738">"Autoputar skrin"</string>
     <string name="quick_settings_location_label" msgid="2621868789013389163">"Lokasi"</string>
diff --git a/packages/SystemUI/res/values-my/strings.xml b/packages/SystemUI/res/values-my/strings.xml
index bed07ef..e4f7154 100644
--- a/packages/SystemUI/res/values-my/strings.xml
+++ b/packages/SystemUI/res/values-my/strings.xml
@@ -328,7 +328,7 @@
     <string name="quick_settings_bluetooth_secondary_label_input" msgid="3887552721233148132">"အဝင်"</string>
     <string name="quick_settings_bluetooth_secondary_label_hearing_aids" msgid="5553051568867097111">"နားကြားကိရိယာ"</string>
     <string name="quick_settings_bluetooth_secondary_label_transient" msgid="3882884317600669650">"ဖွင့်နေသည်…"</string>
-    <string name="quick_settings_brightness_unable_adjust_msg" msgid="786478497970492300">"၎င်းကို ထိပ်ဆုံးရှိအက်ပ်က\n ထိန်းချုပ်နေသဖြင့် တောက်ပမှုကို ပြင်ဆင်၍မရပါ"</string>
+    <string name="quick_settings_brightness_unable_adjust_msg" msgid="786478497970492300">"၎င်းကို ထိပ်ဆုံးရှိအက်ပ်က ထိန်းချုပ်နေသဖြင့် တောက်ပမှုကို ပြင်ဆင်၍မရပါ"</string>
     <string name="quick_settings_rotation_unlocked_label" msgid="2359922767950346112">"အော်တို-လည်"</string>
     <string name="accessibility_quick_settings_rotation" msgid="4800050198392260738">"မျက်နှာပြင်အား အလိုအလျောက်လှည့်ခြင်း"</string>
     <string name="quick_settings_location_label" msgid="2621868789013389163">"တည်နေရာ"</string>
diff --git a/packages/SystemUI/res/values-nb/strings.xml b/packages/SystemUI/res/values-nb/strings.xml
index 7e87efc..6ee6cc2 100644
--- a/packages/SystemUI/res/values-nb/strings.xml
+++ b/packages/SystemUI/res/values-nb/strings.xml
@@ -328,7 +328,7 @@
     <string name="quick_settings_bluetooth_secondary_label_input" msgid="3887552721233148132">"Innenhet"</string>
     <string name="quick_settings_bluetooth_secondary_label_hearing_aids" msgid="5553051568867097111">"Høreapparater"</string>
     <string name="quick_settings_bluetooth_secondary_label_transient" msgid="3882884317600669650">"Slår på …"</string>
-    <string name="quick_settings_brightness_unable_adjust_msg" msgid="786478497970492300">"Kan ikke justere lysstyrken, fordi den\n kontrolleres av appen på toppen"</string>
+    <string name="quick_settings_brightness_unable_adjust_msg" msgid="786478497970492300">"Kan ikke justere lysstyrken, fordi den kontrolleres av appen på toppen"</string>
     <string name="quick_settings_rotation_unlocked_label" msgid="2359922767950346112">"Rotér automatisk"</string>
     <string name="accessibility_quick_settings_rotation" msgid="4800050198392260738">"Rotér skjermen automatisk"</string>
     <string name="quick_settings_location_label" msgid="2621868789013389163">"Sted"</string>
diff --git a/packages/SystemUI/res/values-ne/strings.xml b/packages/SystemUI/res/values-ne/strings.xml
index cc07b7f..90a1923 100644
--- a/packages/SystemUI/res/values-ne/strings.xml
+++ b/packages/SystemUI/res/values-ne/strings.xml
@@ -328,7 +328,7 @@
     <string name="quick_settings_bluetooth_secondary_label_input" msgid="3887552721233148132">"इनपुट"</string>
     <string name="quick_settings_bluetooth_secondary_label_hearing_aids" msgid="5553051568867097111">"हियरिङ डिभाइसहरू"</string>
     <string name="quick_settings_bluetooth_secondary_label_transient" msgid="3882884317600669650">"सक्रिय गर्दै…"</string>
-    <string name="quick_settings_brightness_unable_adjust_msg" msgid="786478497970492300">"टप एपले चमक नियन्त्रण गरिरहेकाले\n चमक मिलाउन मिल्दैन"</string>
+    <string name="quick_settings_brightness_unable_adjust_msg" msgid="786478497970492300">"टप एपले चमक नियन्त्रण गरिरहेकाले चमक मिलाउन मिल्दैन"</string>
     <string name="quick_settings_rotation_unlocked_label" msgid="2359922767950346112">"अटो रोटेट"</string>
     <string name="accessibility_quick_settings_rotation" msgid="4800050198392260738">"स्क्रिन स्वतःघुम्ने"</string>
     <string name="quick_settings_location_label" msgid="2621868789013389163">"लोकेसन"</string>
diff --git a/packages/SystemUI/res/values-nl/strings.xml b/packages/SystemUI/res/values-nl/strings.xml
index b778cec..ee0ef9d 100644
--- a/packages/SystemUI/res/values-nl/strings.xml
+++ b/packages/SystemUI/res/values-nl/strings.xml
@@ -328,7 +328,7 @@
     <string name="quick_settings_bluetooth_secondary_label_input" msgid="3887552721233148132">"Invoer"</string>
     <string name="quick_settings_bluetooth_secondary_label_hearing_aids" msgid="5553051568867097111">"Hoortoestellen"</string>
     <string name="quick_settings_bluetooth_secondary_label_transient" msgid="3882884317600669650">"Aanzetten…"</string>
-    <string name="quick_settings_brightness_unable_adjust_msg" msgid="786478497970492300">"Kan de helderheid niet aanpassen omdat deze wordt\n beheerd door de bovenste app"</string>
+    <string name="quick_settings_brightness_unable_adjust_msg" msgid="786478497970492300">"Kan de helderheid niet aanpassen omdat deze wordt beheerd door de bovenste app"</string>
     <string name="quick_settings_rotation_unlocked_label" msgid="2359922767950346112">"Automatisch draaien"</string>
     <string name="accessibility_quick_settings_rotation" msgid="4800050198392260738">"Scherm automatisch draaien"</string>
     <string name="quick_settings_location_label" msgid="2621868789013389163">"Locatie"</string>
diff --git a/packages/SystemUI/res/values-or/strings.xml b/packages/SystemUI/res/values-or/strings.xml
index 0ef622b..d5ac685 100644
--- a/packages/SystemUI/res/values-or/strings.xml
+++ b/packages/SystemUI/res/values-or/strings.xml
@@ -328,7 +328,7 @@
     <string name="quick_settings_bluetooth_secondary_label_input" msgid="3887552721233148132">"ଇନପୁଟ୍"</string>
     <string name="quick_settings_bluetooth_secondary_label_hearing_aids" msgid="5553051568867097111">"ଶ୍ରବଣ ଯନ୍ତ୍ର"</string>
     <string name="quick_settings_bluetooth_secondary_label_transient" msgid="3882884317600669650">"ଅନ୍ ହେଉଛି…"</string>
-    <string name="quick_settings_brightness_unable_adjust_msg" msgid="786478497970492300">"ଟପ ଆପ ଦ୍ୱାରା ଉଜ୍ଜ୍ୱଳତା ନିୟନ୍ତ୍ରିତ\nହେଉଥିବା ଯୋଗୁଁ ଏହାକୁ ଆଡଜଷ୍ଟ କରିପାରିବେ ନାହିଁ"</string>
+    <string name="quick_settings_brightness_unable_adjust_msg" msgid="786478497970492300">"ଟପ ଆପ ଦ୍ୱାରା ଉଜ୍ଜ୍ୱଳତା ନିୟନ୍ତ୍ରିତହେଉଥିବା ଯୋଗୁଁ ଏହାକୁ ଆଡଜଷ୍ଟ କରିପାରିବେ ନାହିଁ"</string>
     <string name="quick_settings_rotation_unlocked_label" msgid="2359922767950346112">"ଅଟୋ-ରୋଟେଟ"</string>
     <string name="accessibility_quick_settings_rotation" msgid="4800050198392260738">"ଅଟୋ-ରୋଟେଟ ସ୍କ୍ରିନ"</string>
     <string name="quick_settings_location_label" msgid="2621868789013389163">"ଲୋକେସନ"</string>
diff --git a/packages/SystemUI/res/values-pa/strings.xml b/packages/SystemUI/res/values-pa/strings.xml
index 4be78a3..2499518 100644
--- a/packages/SystemUI/res/values-pa/strings.xml
+++ b/packages/SystemUI/res/values-pa/strings.xml
@@ -328,7 +328,7 @@
     <string name="quick_settings_bluetooth_secondary_label_input" msgid="3887552721233148132">"ਇਨਪੁੱਟ"</string>
     <string name="quick_settings_bluetooth_secondary_label_hearing_aids" msgid="5553051568867097111">"ਸੁਣਨ ਦੇ ਸਾਧਨ"</string>
     <string name="quick_settings_bluetooth_secondary_label_transient" msgid="3882884317600669650">"ਚਾਲੂ ਕੀਤਾ ਜਾ ਰਿਹਾ ਹੈ…"</string>
-    <string name="quick_settings_brightness_unable_adjust_msg" msgid="786478497970492300">"ਚਮਕ ਨੂੰ ਵਿਵਸਥਿਤ ਨਹੀਂ ਕੀਤਾ ਜਾ ਸਕਦਾ ਕਿਉਂਕਿ ਇਹ ਪਹਿਲਾਂ ਚੱਲ ਰਹੀ ਐਪ ਵੱਲੋਂ ਕੰਟਰੋਲ ਕਰਨ ਕਰਕੇ \n ਹੋ ਰਿਹਾ ਹੈ"</string>
+    <string name="quick_settings_brightness_unable_adjust_msg" msgid="786478497970492300">"ਚਮਕ ਨੂੰ ਵਿਵਸਥਿਤ ਨਹੀਂ ਕੀਤਾ ਜਾ ਸਕਦਾ ਕਿਉਂਕਿ ਇਹ ਪਹਿਲਾਂ ਚੱਲ ਰਹੀ ਐਪ ਵੱਲੋਂ ਕੰਟਰੋਲ ਕਰਨ ਕਰਕੇ ਹੋ ਰਿਹਾ ਹੈ"</string>
     <string name="quick_settings_rotation_unlocked_label" msgid="2359922767950346112">"ਸਵੈ-ਘੁਮਾਓ"</string>
     <string name="accessibility_quick_settings_rotation" msgid="4800050198392260738">"ਸਕ੍ਰੀਨ ਨੂੰ ਆਪਣੇ ਆਪ ਘੁੰਮਾਓ"</string>
     <string name="quick_settings_location_label" msgid="2621868789013389163">"ਟਿਕਾਣਾ"</string>
diff --git a/packages/SystemUI/res/values-pl/strings.xml b/packages/SystemUI/res/values-pl/strings.xml
index d358b9f..71b5ace 100644
--- a/packages/SystemUI/res/values-pl/strings.xml
+++ b/packages/SystemUI/res/values-pl/strings.xml
@@ -328,7 +328,7 @@
     <string name="quick_settings_bluetooth_secondary_label_input" msgid="3887552721233148132">"Wejście"</string>
     <string name="quick_settings_bluetooth_secondary_label_hearing_aids" msgid="5553051568867097111">"Aparaty słuchowe"</string>
     <string name="quick_settings_bluetooth_secondary_label_transient" msgid="3882884317600669650">"Włączam…"</string>
-    <string name="quick_settings_brightness_unable_adjust_msg" msgid="786478497970492300">"Nie można dostosować jasności, ponieważ jest ona\nkontrolowana przez aplikację na pierwszym planie"</string>
+    <string name="quick_settings_brightness_unable_adjust_msg" msgid="786478497970492300">"Nie można dostosować jasności, ponieważ jest onakontrolowana przez aplikację na pierwszym planie"</string>
     <string name="quick_settings_rotation_unlocked_label" msgid="2359922767950346112">"Autoobracanie"</string>
     <string name="accessibility_quick_settings_rotation" msgid="4800050198392260738">"Autoobracanie ekranu"</string>
     <string name="quick_settings_location_label" msgid="2621868789013389163">"Lokalizacja"</string>
diff --git a/packages/SystemUI/res/values-pt-rBR/strings.xml b/packages/SystemUI/res/values-pt-rBR/strings.xml
index 3a7d1f2..2a8aabe 100644
--- a/packages/SystemUI/res/values-pt-rBR/strings.xml
+++ b/packages/SystemUI/res/values-pt-rBR/strings.xml
@@ -328,7 +328,7 @@
     <string name="quick_settings_bluetooth_secondary_label_input" msgid="3887552721233148132">"Entrada"</string>
     <string name="quick_settings_bluetooth_secondary_label_hearing_aids" msgid="5553051568867097111">"Aparelhos auditivos"</string>
     <string name="quick_settings_bluetooth_secondary_label_transient" msgid="3882884317600669650">"Ativando…"</string>
-    <string name="quick_settings_brightness_unable_adjust_msg" msgid="786478497970492300">"Não é possível ajustar o brilho, porque ele está sendo\n controlado pelo app principal"</string>
+    <string name="quick_settings_brightness_unable_adjust_msg" msgid="786478497970492300">"Não é possível ajustar o brilho, porque ele está sendo controlado pelo app principal"</string>
     <string name="quick_settings_rotation_unlocked_label" msgid="2359922767950346112">"Giro automático"</string>
     <string name="accessibility_quick_settings_rotation" msgid="4800050198392260738">"Giro automático da tela"</string>
     <string name="quick_settings_location_label" msgid="2621868789013389163">"Localização"</string>
diff --git a/packages/SystemUI/res/values-pt-rPT/strings.xml b/packages/SystemUI/res/values-pt-rPT/strings.xml
index ff0ba36..bb68610 100644
--- a/packages/SystemUI/res/values-pt-rPT/strings.xml
+++ b/packages/SystemUI/res/values-pt-rPT/strings.xml
@@ -328,7 +328,7 @@
     <string name="quick_settings_bluetooth_secondary_label_input" msgid="3887552721233148132">"Entrada"</string>
     <string name="quick_settings_bluetooth_secondary_label_hearing_aids" msgid="5553051568867097111">"Aparelhos auditivos"</string>
     <string name="quick_settings_bluetooth_secondary_label_transient" msgid="3882884317600669650">"A ativar..."</string>
-    <string name="quick_settings_brightness_unable_adjust_msg" msgid="786478497970492300">"Não é possível ajustar o brilho porque está a ser\n controlado pela app principal"</string>
+    <string name="quick_settings_brightness_unable_adjust_msg" msgid="786478497970492300">"Não é possível ajustar o brilho porque está a ser controlado pela app principal"</string>
     <string name="quick_settings_rotation_unlocked_label" msgid="2359922767950346112">"Rotação auto."</string>
     <string name="accessibility_quick_settings_rotation" msgid="4800050198392260738">"Rodar o ecrã automaticamente"</string>
     <string name="quick_settings_location_label" msgid="2621868789013389163">"Localização"</string>
diff --git a/packages/SystemUI/res/values-pt/strings.xml b/packages/SystemUI/res/values-pt/strings.xml
index 3a7d1f2..2a8aabe 100644
--- a/packages/SystemUI/res/values-pt/strings.xml
+++ b/packages/SystemUI/res/values-pt/strings.xml
@@ -328,7 +328,7 @@
     <string name="quick_settings_bluetooth_secondary_label_input" msgid="3887552721233148132">"Entrada"</string>
     <string name="quick_settings_bluetooth_secondary_label_hearing_aids" msgid="5553051568867097111">"Aparelhos auditivos"</string>
     <string name="quick_settings_bluetooth_secondary_label_transient" msgid="3882884317600669650">"Ativando…"</string>
-    <string name="quick_settings_brightness_unable_adjust_msg" msgid="786478497970492300">"Não é possível ajustar o brilho, porque ele está sendo\n controlado pelo app principal"</string>
+    <string name="quick_settings_brightness_unable_adjust_msg" msgid="786478497970492300">"Não é possível ajustar o brilho, porque ele está sendo controlado pelo app principal"</string>
     <string name="quick_settings_rotation_unlocked_label" msgid="2359922767950346112">"Giro automático"</string>
     <string name="accessibility_quick_settings_rotation" msgid="4800050198392260738">"Giro automático da tela"</string>
     <string name="quick_settings_location_label" msgid="2621868789013389163">"Localização"</string>
diff --git a/packages/SystemUI/res/values-ro/strings.xml b/packages/SystemUI/res/values-ro/strings.xml
index 2417c84..6221d49 100644
--- a/packages/SystemUI/res/values-ro/strings.xml
+++ b/packages/SystemUI/res/values-ro/strings.xml
@@ -328,7 +328,7 @@
     <string name="quick_settings_bluetooth_secondary_label_input" msgid="3887552721233148132">"Intrare"</string>
     <string name="quick_settings_bluetooth_secondary_label_hearing_aids" msgid="5553051568867097111">"Aparate auditive"</string>
     <string name="quick_settings_bluetooth_secondary_label_transient" msgid="3882884317600669650">"Se activează..."</string>
-    <string name="quick_settings_brightness_unable_adjust_msg" msgid="786478497970492300">"Nu se poate ajusta luminozitatea deoarece este\n controlată de aplicația de top"</string>
+    <string name="quick_settings_brightness_unable_adjust_msg" msgid="786478497970492300">"Nu se poate ajusta luminozitatea deoarece este controlată de aplicația de top"</string>
     <string name="quick_settings_rotation_unlocked_label" msgid="2359922767950346112">"Rotire automată"</string>
     <string name="accessibility_quick_settings_rotation" msgid="4800050198392260738">"Rotirea automată a ecranului"</string>
     <string name="quick_settings_location_label" msgid="2621868789013389163">"Locație"</string>
diff --git a/packages/SystemUI/res/values-ru/strings.xml b/packages/SystemUI/res/values-ru/strings.xml
index f6968d5..4c14616 100644
--- a/packages/SystemUI/res/values-ru/strings.xml
+++ b/packages/SystemUI/res/values-ru/strings.xml
@@ -328,7 +328,7 @@
     <string name="quick_settings_bluetooth_secondary_label_input" msgid="3887552721233148132">"Устройство ввода"</string>
     <string name="quick_settings_bluetooth_secondary_label_hearing_aids" msgid="5553051568867097111">"Слуховые аппараты"</string>
     <string name="quick_settings_bluetooth_secondary_label_transient" msgid="3882884317600669650">"Включение…"</string>
-    <string name="quick_settings_brightness_unable_adjust_msg" msgid="786478497970492300">"Невозможно изменить яркость,\nтак как она регулируется общими настройками."</string>
+    <string name="quick_settings_brightness_unable_adjust_msg" msgid="786478497970492300">"Невозможно изменить яркость,так как она регулируется общими настройками."</string>
     <string name="quick_settings_rotation_unlocked_label" msgid="2359922767950346112">"Автоповорот"</string>
     <string name="accessibility_quick_settings_rotation" msgid="4800050198392260738">"Автоповорот экрана"</string>
     <string name="quick_settings_location_label" msgid="2621868789013389163">"Геолокация"</string>
diff --git a/packages/SystemUI/res/values-si/strings.xml b/packages/SystemUI/res/values-si/strings.xml
index eb816b8..fb8ce18 100644
--- a/packages/SystemUI/res/values-si/strings.xml
+++ b/packages/SystemUI/res/values-si/strings.xml
@@ -328,7 +328,7 @@
     <string name="quick_settings_bluetooth_secondary_label_input" msgid="3887552721233148132">"ආදානය"</string>
     <string name="quick_settings_bluetooth_secondary_label_hearing_aids" msgid="5553051568867097111">"ශ්‍රවණාධාරක"</string>
     <string name="quick_settings_bluetooth_secondary_label_transient" msgid="3882884317600669650">"ක්‍රියාත්මක කරමින්…"</string>
-    <string name="quick_settings_brightness_unable_adjust_msg" msgid="786478497970492300">"ඉහළ යෙදුම මඟින් එය පාලනය වන නිසා\nදීප්තිය ගැළපුම් කළ නොහැක"</string>
+    <string name="quick_settings_brightness_unable_adjust_msg" msgid="786478497970492300">"ඉහළ යෙදුම මඟින් එය පාලනය වන නිසාදීප්තිය ගැළපුම් කළ නොහැක"</string>
     <string name="quick_settings_rotation_unlocked_label" msgid="2359922767950346112">"ස්වයංක්‍රීය කරකැවීම"</string>
     <string name="accessibility_quick_settings_rotation" msgid="4800050198392260738">"ස්වයංක්‍රීයව-භ්‍රමණය වන තිරය"</string>
     <string name="quick_settings_location_label" msgid="2621868789013389163">"ස්ථානය"</string>
diff --git a/packages/SystemUI/res/values-sk/strings.xml b/packages/SystemUI/res/values-sk/strings.xml
index fcb281d..2dba462 100644
--- a/packages/SystemUI/res/values-sk/strings.xml
+++ b/packages/SystemUI/res/values-sk/strings.xml
@@ -328,7 +328,7 @@
     <string name="quick_settings_bluetooth_secondary_label_input" msgid="3887552721233148132">"Vstup"</string>
     <string name="quick_settings_bluetooth_secondary_label_hearing_aids" msgid="5553051568867097111">"Načúvadlá"</string>
     <string name="quick_settings_bluetooth_secondary_label_transient" msgid="3882884317600669650">"Zapína sa…"</string>
-    <string name="quick_settings_brightness_unable_adjust_msg" msgid="786478497970492300">"Jas sa nedá upraviť, pretože ho \n ovláda horná aplikácia"</string>
+    <string name="quick_settings_brightness_unable_adjust_msg" msgid="786478497970492300">"Jas sa nedá upraviť, pretože ho ovláda horná aplikácia"</string>
     <string name="quick_settings_rotation_unlocked_label" msgid="2359922767950346112">"Automatické otáčanie"</string>
     <string name="accessibility_quick_settings_rotation" msgid="4800050198392260738">"Automatické otáčanie obrazovky"</string>
     <string name="quick_settings_location_label" msgid="2621868789013389163">"Poloha"</string>
diff --git a/packages/SystemUI/res/values-sl/strings.xml b/packages/SystemUI/res/values-sl/strings.xml
index e999065..dd86710 100644
--- a/packages/SystemUI/res/values-sl/strings.xml
+++ b/packages/SystemUI/res/values-sl/strings.xml
@@ -328,7 +328,7 @@
     <string name="quick_settings_bluetooth_secondary_label_input" msgid="3887552721233148132">"Vhodna naprava"</string>
     <string name="quick_settings_bluetooth_secondary_label_hearing_aids" msgid="5553051568867097111">"Slušni aparati"</string>
     <string name="quick_settings_bluetooth_secondary_label_transient" msgid="3882884317600669650">"Vklapljanje …"</string>
-    <string name="quick_settings_brightness_unable_adjust_msg" msgid="786478497970492300">"Svetlosti ni mogoče prilagoditi, ker jo\n nadzoruje aplikacija na vrhu"</string>
+    <string name="quick_settings_brightness_unable_adjust_msg" msgid="786478497970492300">"Svetlosti ni mogoče prilagoditi, ker jo nadzoruje aplikacija na vrhu"</string>
     <string name="quick_settings_rotation_unlocked_label" msgid="2359922767950346112">"Samodejno sukanje"</string>
     <string name="accessibility_quick_settings_rotation" msgid="4800050198392260738">"Samodejno sukanje zaslona"</string>
     <string name="quick_settings_location_label" msgid="2621868789013389163">"Lokacija"</string>
diff --git a/packages/SystemUI/res/values-sq/strings.xml b/packages/SystemUI/res/values-sq/strings.xml
index 7e2b698..2532c03 100644
--- a/packages/SystemUI/res/values-sq/strings.xml
+++ b/packages/SystemUI/res/values-sq/strings.xml
@@ -328,7 +328,7 @@
     <string name="quick_settings_bluetooth_secondary_label_input" msgid="3887552721233148132">"Hyrja"</string>
     <string name="quick_settings_bluetooth_secondary_label_hearing_aids" msgid="5553051568867097111">"Aparatet e dëgjimit"</string>
     <string name="quick_settings_bluetooth_secondary_label_transient" msgid="3882884317600669650">"Po aktivizohet…"</string>
-    <string name="quick_settings_brightness_unable_adjust_msg" msgid="786478497970492300">"Ndriçimi nuk mund të rregullohet pasi\n po kontrollohet nga aplikacioni i sipërm"</string>
+    <string name="quick_settings_brightness_unable_adjust_msg" msgid="786478497970492300">"Ndriçimi nuk mund të rregullohet pasi po kontrollohet nga aplikacioni i sipërm"</string>
     <string name="quick_settings_rotation_unlocked_label" msgid="2359922767950346112">"Rrotullim automatik"</string>
     <string name="accessibility_quick_settings_rotation" msgid="4800050198392260738">"Rrotullimi automatik i ekranit"</string>
     <string name="quick_settings_location_label" msgid="2621868789013389163">"Vendndodhja"</string>
diff --git a/packages/SystemUI/res/values-sr/strings.xml b/packages/SystemUI/res/values-sr/strings.xml
index 1e44e0c..3ccad52 100644
--- a/packages/SystemUI/res/values-sr/strings.xml
+++ b/packages/SystemUI/res/values-sr/strings.xml
@@ -328,7 +328,7 @@
     <string name="quick_settings_bluetooth_secondary_label_input" msgid="3887552721233148132">"Унос"</string>
     <string name="quick_settings_bluetooth_secondary_label_hearing_aids" msgid="5553051568867097111">"Слушни апарати"</string>
     <string name="quick_settings_bluetooth_secondary_label_transient" msgid="3882884317600669650">"Укључује се..."</string>
-    <string name="quick_settings_brightness_unable_adjust_msg" msgid="786478497970492300">"Не можете да прилагодите осветљеност јер је\n контролише апликација у врху"</string>
+    <string name="quick_settings_brightness_unable_adjust_msg" msgid="786478497970492300">"Не можете да прилагодите осветљеност јер је контролише апликација у врху"</string>
     <string name="quick_settings_rotation_unlocked_label" msgid="2359922767950346112">"Аутоматска ротација"</string>
     <string name="accessibility_quick_settings_rotation" msgid="4800050198392260738">"Аутоматско ротирање екрана"</string>
     <string name="quick_settings_location_label" msgid="2621868789013389163">"Локација"</string>
diff --git a/packages/SystemUI/res/values-sv/strings.xml b/packages/SystemUI/res/values-sv/strings.xml
index d613922..cab91b1 100644
--- a/packages/SystemUI/res/values-sv/strings.xml
+++ b/packages/SystemUI/res/values-sv/strings.xml
@@ -328,7 +328,7 @@
     <string name="quick_settings_bluetooth_secondary_label_input" msgid="3887552721233148132">"Ingång"</string>
     <string name="quick_settings_bluetooth_secondary_label_hearing_aids" msgid="5553051568867097111">"Hörapparater"</string>
     <string name="quick_settings_bluetooth_secondary_label_transient" msgid="3882884317600669650">"Aktiverar …"</string>
-    <string name="quick_settings_brightness_unable_adjust_msg" msgid="786478497970492300">"Det går inte att justera ljusstyrkan eftersom den\n styrs av den översta appen"</string>
+    <string name="quick_settings_brightness_unable_adjust_msg" msgid="786478497970492300">"Det går inte att justera ljusstyrkan eftersom den styrs av den översta appen"</string>
     <string name="quick_settings_rotation_unlocked_label" msgid="2359922767950346112">"Rotera automatiskt"</string>
     <string name="accessibility_quick_settings_rotation" msgid="4800050198392260738">"Rotera skärmen automatiskt"</string>
     <string name="quick_settings_location_label" msgid="2621868789013389163">"Plats"</string>
diff --git a/packages/SystemUI/res/values-sw/strings.xml b/packages/SystemUI/res/values-sw/strings.xml
index 33d8511..6cb3abf 100644
--- a/packages/SystemUI/res/values-sw/strings.xml
+++ b/packages/SystemUI/res/values-sw/strings.xml
@@ -328,7 +328,7 @@
     <string name="quick_settings_bluetooth_secondary_label_input" msgid="3887552721233148132">"Vifaa vya kuingiza sauti"</string>
     <string name="quick_settings_bluetooth_secondary_label_hearing_aids" msgid="5553051568867097111">"Visaidizi vya kusikia"</string>
     <string name="quick_settings_bluetooth_secondary_label_transient" msgid="3882884317600669650">"Inawasha..."</string>
-    <string name="quick_settings_brightness_unable_adjust_msg" msgid="786478497970492300">"Imeshindwa kurekebisha mwangaza kwa sababu\n inadhibitiwa na programu inayotumika"</string>
+    <string name="quick_settings_brightness_unable_adjust_msg" msgid="786478497970492300">"Imeshindwa kurekebisha mwangaza kwa sababu inadhibitiwa na programu inayotumika"</string>
     <string name="quick_settings_rotation_unlocked_label" msgid="2359922767950346112">"Zungusha kiotomatiki"</string>
     <string name="accessibility_quick_settings_rotation" msgid="4800050198392260738">"Skrini ijizungushe kiotomatiki"</string>
     <string name="quick_settings_location_label" msgid="2621868789013389163">"Mahali"</string>
diff --git a/packages/SystemUI/res/values-ta/strings.xml b/packages/SystemUI/res/values-ta/strings.xml
index aa312c7..32fa794 100644
--- a/packages/SystemUI/res/values-ta/strings.xml
+++ b/packages/SystemUI/res/values-ta/strings.xml
@@ -328,7 +328,7 @@
     <string name="quick_settings_bluetooth_secondary_label_input" msgid="3887552721233148132">"உள்ளீடு"</string>
     <string name="quick_settings_bluetooth_secondary_label_hearing_aids" msgid="5553051568867097111">"செவித்துணைக் கருவி"</string>
     <string name="quick_settings_bluetooth_secondary_label_transient" msgid="3882884317600669650">"ஆன் செய்கிறது…"</string>
-    <string name="quick_settings_brightness_unable_adjust_msg" msgid="786478497970492300">"சிறந்த ஆப்ஸால் ஒளிர்வு கட்டுப்படுத்தப்படுவதால்\n இதைச் சரிசெய்ய முடியவில்லை"</string>
+    <string name="quick_settings_brightness_unable_adjust_msg" msgid="786478497970492300">"சிறந்த ஆப்ஸால் ஒளிர்வு கட்டுப்படுத்தப்படுவதால் இதைச் சரிசெய்ய முடியவில்லை"</string>
     <string name="quick_settings_rotation_unlocked_label" msgid="2359922767950346112">"தானாகச் சுழற்று"</string>
     <string name="accessibility_quick_settings_rotation" msgid="4800050198392260738">"திரையைத் தானாகச் சுழற்று"</string>
     <string name="quick_settings_location_label" msgid="2621868789013389163">"இருப்பிடம்"</string>
diff --git a/packages/SystemUI/res/values-te/strings.xml b/packages/SystemUI/res/values-te/strings.xml
index fc5361b..f3084a9 100644
--- a/packages/SystemUI/res/values-te/strings.xml
+++ b/packages/SystemUI/res/values-te/strings.xml
@@ -328,7 +328,7 @@
     <string name="quick_settings_bluetooth_secondary_label_input" msgid="3887552721233148132">"ఇన్‌పుట్"</string>
     <string name="quick_settings_bluetooth_secondary_label_hearing_aids" msgid="5553051568867097111">"వినికిడి పరికరాలు"</string>
     <string name="quick_settings_bluetooth_secondary_label_transient" msgid="3882884317600669650">"ఆన్ చేస్తోంది…"</string>
-    <string name="quick_settings_brightness_unable_adjust_msg" msgid="786478497970492300">"ఇది టాప్ యాప్ ద్వారా\n కంట్రోల్ చేయబడుతున్నందున బ్రైట్‌నెస్‌ను సర్దుబాటు చేయడం సాధ్యం కాదు"</string>
+    <string name="quick_settings_brightness_unable_adjust_msg" msgid="786478497970492300">"ఇది టాప్ యాప్ ద్వారా కంట్రోల్ చేయబడుతున్నందున బ్రైట్‌నెస్‌ను సర్దుబాటు చేయడం సాధ్యం కాదు"</string>
     <string name="quick_settings_rotation_unlocked_label" msgid="2359922767950346112">"ఆటో-రొటేట్‌"</string>
     <string name="accessibility_quick_settings_rotation" msgid="4800050198392260738">"స్క్రీన్ ఆటో-రొటేట్‌"</string>
     <string name="quick_settings_location_label" msgid="2621868789013389163">"లొకేషన్"</string>
diff --git a/packages/SystemUI/res/values-th/strings.xml b/packages/SystemUI/res/values-th/strings.xml
index 873635d..3fac78f 100644
--- a/packages/SystemUI/res/values-th/strings.xml
+++ b/packages/SystemUI/res/values-th/strings.xml
@@ -328,7 +328,7 @@
     <string name="quick_settings_bluetooth_secondary_label_input" msgid="3887552721233148132">"อินพุต"</string>
     <string name="quick_settings_bluetooth_secondary_label_hearing_aids" msgid="5553051568867097111">"เครื่องช่วยฟัง"</string>
     <string name="quick_settings_bluetooth_secondary_label_transient" msgid="3882884317600669650">"กำลังเปิด..."</string>
-    <string name="quick_settings_brightness_unable_adjust_msg" msgid="786478497970492300">"ปรับความสว่างไม่ได้เนื่องจาก\nควบคุมโดยแอปที่อยู่ด้านบน"</string>
+    <string name="quick_settings_brightness_unable_adjust_msg" msgid="786478497970492300">"ปรับความสว่างไม่ได้เนื่องจากควบคุมโดยแอปที่อยู่ด้านบน"</string>
     <string name="quick_settings_rotation_unlocked_label" msgid="2359922767950346112">"หมุนอัตโนมัติ"</string>
     <string name="accessibility_quick_settings_rotation" msgid="4800050198392260738">"หมุนหน้าจออัตโนมัติ"</string>
     <string name="quick_settings_location_label" msgid="2621868789013389163">"ตำแหน่ง"</string>
diff --git a/packages/SystemUI/res/values-tl/strings.xml b/packages/SystemUI/res/values-tl/strings.xml
index e6aa410..8e87e84 100644
--- a/packages/SystemUI/res/values-tl/strings.xml
+++ b/packages/SystemUI/res/values-tl/strings.xml
@@ -328,7 +328,7 @@
     <string name="quick_settings_bluetooth_secondary_label_input" msgid="3887552721233148132">"Input"</string>
     <string name="quick_settings_bluetooth_secondary_label_hearing_aids" msgid="5553051568867097111">"Mga hearing aid"</string>
     <string name="quick_settings_bluetooth_secondary_label_transient" msgid="3882884317600669650">"Ino-on…"</string>
-    <string name="quick_settings_brightness_unable_adjust_msg" msgid="786478497970492300">"Hindi ma-adjust ang liwanag dahil\n kinokontrol ito ng nangingibabaw na app"</string>
+    <string name="quick_settings_brightness_unable_adjust_msg" msgid="786478497970492300">"Hindi ma-adjust ang liwanag dahil kinokontrol ito ng nangingibabaw na app"</string>
     <string name="quick_settings_rotation_unlocked_label" msgid="2359922767950346112">"I-auto rotate"</string>
     <string name="accessibility_quick_settings_rotation" msgid="4800050198392260738">"Awtomatikong i-rotate ang screen"</string>
     <string name="quick_settings_location_label" msgid="2621868789013389163">"Lokasyon"</string>
diff --git a/packages/SystemUI/res/values-tr/strings.xml b/packages/SystemUI/res/values-tr/strings.xml
index 05fc1f2..474c53f 100644
--- a/packages/SystemUI/res/values-tr/strings.xml
+++ b/packages/SystemUI/res/values-tr/strings.xml
@@ -328,7 +328,7 @@
     <string name="quick_settings_bluetooth_secondary_label_input" msgid="3887552721233148132">"Giriş"</string>
     <string name="quick_settings_bluetooth_secondary_label_hearing_aids" msgid="5553051568867097111">"İşitme cihazları"</string>
     <string name="quick_settings_bluetooth_secondary_label_transient" msgid="3882884317600669650">"Açılıyor…"</string>
-    <string name="quick_settings_brightness_unable_adjust_msg" msgid="786478497970492300">"Parlaklık ayarlanamıyor, çünkü bu özellik\n en üstteki uygulama tarafından kontrol ediliyor"</string>
+    <string name="quick_settings_brightness_unable_adjust_msg" msgid="786478497970492300">"Parlaklık ayarlanamıyor, çünkü bu özellik en üstteki uygulama tarafından kontrol ediliyor"</string>
     <string name="quick_settings_rotation_unlocked_label" msgid="2359922767950346112">"Otomatik döndür"</string>
     <string name="accessibility_quick_settings_rotation" msgid="4800050198392260738">"Ekranı otomatik döndür"</string>
     <string name="quick_settings_location_label" msgid="2621868789013389163">"Konum"</string>
diff --git a/packages/SystemUI/res/values-uk/strings.xml b/packages/SystemUI/res/values-uk/strings.xml
index 0b0d6dc..27c2e53 100644
--- a/packages/SystemUI/res/values-uk/strings.xml
+++ b/packages/SystemUI/res/values-uk/strings.xml
@@ -328,7 +328,7 @@
     <string name="quick_settings_bluetooth_secondary_label_input" msgid="3887552721233148132">"Джерело сигналу"</string>
     <string name="quick_settings_bluetooth_secondary_label_hearing_aids" msgid="5553051568867097111">"Слухові апарати"</string>
     <string name="quick_settings_bluetooth_secondary_label_transient" msgid="3882884317600669650">"Увімкнення…"</string>
-    <string name="quick_settings_brightness_unable_adjust_msg" msgid="786478497970492300">"Не вдається змінити яскравість, оскільки\n нею керує основний додаток"</string>
+    <string name="quick_settings_brightness_unable_adjust_msg" msgid="786478497970492300">"Не вдається змінити яскравість, оскільки нею керує основний додаток"</string>
     <string name="quick_settings_rotation_unlocked_label" msgid="2359922767950346112">"Автообертання"</string>
     <string name="accessibility_quick_settings_rotation" msgid="4800050198392260738">"Автоматично обертати екран"</string>
     <string name="quick_settings_location_label" msgid="2621868789013389163">"Геодані"</string>
diff --git a/packages/SystemUI/res/values-ur/strings.xml b/packages/SystemUI/res/values-ur/strings.xml
index ab4dd77..28572b9 100644
--- a/packages/SystemUI/res/values-ur/strings.xml
+++ b/packages/SystemUI/res/values-ur/strings.xml
@@ -328,7 +328,7 @@
     <string name="quick_settings_bluetooth_secondary_label_input" msgid="3887552721233148132">"ان پٹ"</string>
     <string name="quick_settings_bluetooth_secondary_label_hearing_aids" msgid="5553051568867097111">"سماعتی آلات"</string>
     <string name="quick_settings_bluetooth_secondary_label_transient" msgid="3882884317600669650">"آن ہو رہا ہے…"</string>
-    <string name="quick_settings_brightness_unable_adjust_msg" msgid="786478497970492300">"چمک کو ایڈجسٹ نہیں کیا جا سکتا کیونکہ اسے سرفہرست ایپ کے ذریعے \n کنٹرول کیا جا رہا ہے"</string>
+    <string name="quick_settings_brightness_unable_adjust_msg" msgid="786478497970492300">"چمک کو ایڈجسٹ نہیں کیا جا سکتا کیونکہ اسے سرفہرست ایپ کے ذریعے کنٹرول کیا جا رہا ہے"</string>
     <string name="quick_settings_rotation_unlocked_label" msgid="2359922767950346112">"خود کار طور پر گھمائیں"</string>
     <string name="accessibility_quick_settings_rotation" msgid="4800050198392260738">"اسکرین کو خود کار طور پر گھمائیں"</string>
     <string name="quick_settings_location_label" msgid="2621868789013389163">"مقام"</string>
diff --git a/packages/SystemUI/res/values-uz/strings.xml b/packages/SystemUI/res/values-uz/strings.xml
index 55caf25..54c1412d 100644
--- a/packages/SystemUI/res/values-uz/strings.xml
+++ b/packages/SystemUI/res/values-uz/strings.xml
@@ -328,7 +328,7 @@
     <string name="quick_settings_bluetooth_secondary_label_input" msgid="3887552721233148132">"Kirish"</string>
     <string name="quick_settings_bluetooth_secondary_label_hearing_aids" msgid="5553051568867097111">"Eshitish moslamalari"</string>
     <string name="quick_settings_bluetooth_secondary_label_transient" msgid="3882884317600669650">"Yoqilmoqda…"</string>
-    <string name="quick_settings_brightness_unable_adjust_msg" msgid="786478497970492300">"Yorqinlik umumiy sozlamalar orqali boshqariladi.\nUni moslash imkonsiz"</string>
+    <string name="quick_settings_brightness_unable_adjust_msg" msgid="786478497970492300">"Yorqinlik umumiy sozlamalar orqali boshqariladi.Uni moslash imkonsiz"</string>
     <string name="quick_settings_rotation_unlocked_label" msgid="2359922767950346112">"Avto-burilish"</string>
     <string name="accessibility_quick_settings_rotation" msgid="4800050198392260738">"Ekranning avtomatik burilishi"</string>
     <string name="quick_settings_location_label" msgid="2621868789013389163">"Joylashuv"</string>
diff --git a/packages/SystemUI/res/values-vi/strings.xml b/packages/SystemUI/res/values-vi/strings.xml
index 35fe2f2..4414e09 100644
--- a/packages/SystemUI/res/values-vi/strings.xml
+++ b/packages/SystemUI/res/values-vi/strings.xml
@@ -328,7 +328,7 @@
     <string name="quick_settings_bluetooth_secondary_label_input" msgid="3887552721233148132">"Thiết bị đầu vào"</string>
     <string name="quick_settings_bluetooth_secondary_label_hearing_aids" msgid="5553051568867097111">"Thiết bị trợ thính"</string>
     <string name="quick_settings_bluetooth_secondary_label_transient" msgid="3882884317600669650">"Đang bật…"</string>
-    <string name="quick_settings_brightness_unable_adjust_msg" msgid="786478497970492300">"Không điều chỉnh được độ sáng vì độ sáng đang được\n ứng dụng trên cùng điều khiển"</string>
+    <string name="quick_settings_brightness_unable_adjust_msg" msgid="786478497970492300">"Không điều chỉnh được độ sáng vì độ sáng đang được ứng dụng trên cùng điều khiển"</string>
     <string name="quick_settings_rotation_unlocked_label" msgid="2359922767950346112">"Tự động xoay"</string>
     <string name="accessibility_quick_settings_rotation" msgid="4800050198392260738">"Tự động xoay màn hình"</string>
     <string name="quick_settings_location_label" msgid="2621868789013389163">"Vị trí"</string>
diff --git a/packages/SystemUI/res/values-zh-rCN/strings.xml b/packages/SystemUI/res/values-zh-rCN/strings.xml
index 530017c..f45ca2d 100644
--- a/packages/SystemUI/res/values-zh-rCN/strings.xml
+++ b/packages/SystemUI/res/values-zh-rCN/strings.xml
@@ -328,7 +328,7 @@
     <string name="quick_settings_bluetooth_secondary_label_input" msgid="3887552721233148132">"输入"</string>
     <string name="quick_settings_bluetooth_secondary_label_hearing_aids" msgid="5553051568867097111">"助听器"</string>
     <string name="quick_settings_bluetooth_secondary_label_transient" msgid="3882884317600669650">"正在开启…"</string>
-    <string name="quick_settings_brightness_unable_adjust_msg" msgid="786478497970492300">"亮度无法调整,因为它正在被\n顶层应用控制"</string>
+    <string name="quick_settings_brightness_unable_adjust_msg" msgid="786478497970492300">"亮度无法调整,因为它正在被顶层应用控制"</string>
     <string name="quick_settings_rotation_unlocked_label" msgid="2359922767950346112">"自动屏幕旋转"</string>
     <string name="accessibility_quick_settings_rotation" msgid="4800050198392260738">"自动旋转屏幕"</string>
     <string name="quick_settings_location_label" msgid="2621868789013389163">"位置信息"</string>
diff --git a/packages/SystemUI/res/values-zh-rHK/strings.xml b/packages/SystemUI/res/values-zh-rHK/strings.xml
index 496cbdb..ebf07ee 100644
--- a/packages/SystemUI/res/values-zh-rHK/strings.xml
+++ b/packages/SystemUI/res/values-zh-rHK/strings.xml
@@ -328,7 +328,7 @@
     <string name="quick_settings_bluetooth_secondary_label_input" msgid="3887552721233148132">"輸入"</string>
     <string name="quick_settings_bluetooth_secondary_label_hearing_aids" msgid="5553051568867097111">"助聽器"</string>
     <string name="quick_settings_bluetooth_secondary_label_transient" msgid="3882884317600669650">"正在開啟…"</string>
-    <string name="quick_settings_brightness_unable_adjust_msg" msgid="786478497970492300">"無法調整亮度,因為\n目前是由上層應用程式控制亮度"</string>
+    <string name="quick_settings_brightness_unable_adjust_msg" msgid="786478497970492300">"無法調整亮度,因為目前是由上層應用程式控制亮度"</string>
     <string name="quick_settings_rotation_unlocked_label" msgid="2359922767950346112">"自動旋轉"</string>
     <string name="accessibility_quick_settings_rotation" msgid="4800050198392260738">"自動旋轉螢幕"</string>
     <string name="quick_settings_location_label" msgid="2621868789013389163">"位置"</string>
diff --git a/packages/SystemUI/res/values-zh-rTW/strings.xml b/packages/SystemUI/res/values-zh-rTW/strings.xml
index c63473f..2e2a930 100644
--- a/packages/SystemUI/res/values-zh-rTW/strings.xml
+++ b/packages/SystemUI/res/values-zh-rTW/strings.xml
@@ -328,7 +328,7 @@
     <string name="quick_settings_bluetooth_secondary_label_input" msgid="3887552721233148132">"輸入"</string>
     <string name="quick_settings_bluetooth_secondary_label_hearing_aids" msgid="5553051568867097111">"助聽器"</string>
     <string name="quick_settings_bluetooth_secondary_label_transient" msgid="3882884317600669650">"開啟中…"</string>
-    <string name="quick_settings_brightness_unable_adjust_msg" msgid="786478497970492300">"無法調整亮度,因為\n目前是由上層應用程式控制亮度"</string>
+    <string name="quick_settings_brightness_unable_adjust_msg" msgid="786478497970492300">"無法調整亮度,因為目前是由上層應用程式控制亮度"</string>
     <string name="quick_settings_rotation_unlocked_label" msgid="2359922767950346112">"自動旋轉"</string>
     <string name="accessibility_quick_settings_rotation" msgid="4800050198392260738">"自動旋轉螢幕"</string>
     <string name="quick_settings_location_label" msgid="2621868789013389163">"定位"</string>
diff --git a/packages/SystemUI/res/values-zu/strings.xml b/packages/SystemUI/res/values-zu/strings.xml
index 2aad788..818b6e3 100644
--- a/packages/SystemUI/res/values-zu/strings.xml
+++ b/packages/SystemUI/res/values-zu/strings.xml
@@ -330,7 +330,7 @@
     <string name="quick_settings_bluetooth_secondary_label_input" msgid="3887552721233148132">"Okokufaka"</string>
     <string name="quick_settings_bluetooth_secondary_label_hearing_aids" msgid="5553051568867097111">"Imishini yendlebe"</string>
     <string name="quick_settings_bluetooth_secondary_label_transient" msgid="3882884317600669650">"Iyavula..."</string>
-    <string name="quick_settings_brightness_unable_adjust_msg" msgid="786478497970492300">"Ayikwazi ukulungisa ukukhanya ngoba ilawulwa\n yi-app ephezulu"</string>
+    <string name="quick_settings_brightness_unable_adjust_msg" msgid="786478497970492300">"Ayikwazi ukulungisa ukukhanya ngoba ilawulwa yi-app ephezulu"</string>
     <string name="quick_settings_rotation_unlocked_label" msgid="2359922767950346112">"Ukuphenduka okuzenzakalelayo"</string>
     <string name="accessibility_quick_settings_rotation" msgid="4800050198392260738">"Phendula iskrini ngokuzenzakalela"</string>
     <string name="quick_settings_location_label" msgid="2621868789013389163">"Indawo"</string>
diff --git a/packages/SystemUI/res/values/dimens.xml b/packages/SystemUI/res/values/dimens.xml
index 17a89b3..640e1fa 100644
--- a/packages/SystemUI/res/values/dimens.xml
+++ b/packages/SystemUI/res/values/dimens.xml
@@ -491,6 +491,9 @@
     <!-- some constraints use a negative margin. must be aligned with overlay_border_width, above;
          overlay_border_width_neg = overlay_border_width * -1 -->
     <dimen name="overlay_border_width_neg">-4dp</dimen>
+    <dimen name="overlay_shade_panel_shape_radius">
+        @dimen/aux_spacing_overlay_panel_shape_radius
+    </dimen>
 
     <dimen name="clipboard_preview_size">@dimen/overlay_x_scale</dimen>
     <dimen name="clipboard_overlay_min_font">10sp</dimen>
@@ -2215,4 +2218,8 @@
     <dimen name="rear_display_progress_width">231dp</dimen>
     <!-- Rear display mode end -->
 
+    <!-- Spacing attributes to overwrite -->
+    <dimen name="aux_spacing_overlay_panel_shape_radius">46dp</dimen>
+    <!-- Spacing attributes to overwrite end -->
+
 </resources>
diff --git a/packages/SystemUI/res/values/strings.xml b/packages/SystemUI/res/values/strings.xml
index 3fc46ed..e97919e 100644
--- a/packages/SystemUI/res/values/strings.xml
+++ b/packages/SystemUI/res/values/strings.xml
@@ -802,7 +802,7 @@
     <!-- QuickSettings: Bluetooth secondary label shown when bluetooth is being enabled [CHAR LIMIT=NONE] -->
     <string name="quick_settings_bluetooth_secondary_label_transient">Turning on&#8230;</string>
     <!-- QuickSettings: Brightness [CHAR LIMIT=NONE] -->
-    <string name="quick_settings_brightness_unable_adjust_msg">Can\'t adjust brightness because it\'s being\n controlled by the top app</string>
+    <string name="quick_settings_brightness_unable_adjust_msg">Can\'t adjust brightness because it\'s being controlled by the top app</string>
     <!-- QuickSettings: Rotation Unlocked [CHAR LIMIT=NONE] -->
     <string name="quick_settings_rotation_unlocked_label">Auto-rotate</string>
     <!-- Accessibility label for Auto-ratate QuickSettings tile [CHAR LIMIT=NONE] -->
@@ -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/recents/utilities/Utilities.java b/packages/SystemUI/shared/src/com/android/systemui/shared/recents/utilities/Utilities.java
index 9ebb15f..c822439 100644
--- a/packages/SystemUI/shared/src/com/android/systemui/shared/recents/utilities/Utilities.java
+++ b/packages/SystemUI/shared/src/com/android/systemui/shared/recents/utilities/Utilities.java
@@ -19,6 +19,7 @@
 import static android.app.StatusBarManager.NAVBAR_BACK_DISMISS_IME;
 import static android.app.StatusBarManager.NAVBAR_IME_SWITCHER_BUTTON_VISIBLE;
 import static android.app.StatusBarManager.NAVBAR_IME_VISIBLE;
+import static android.app.WindowConfiguration.WINDOWING_MODE_FREEFORM;
 
 import android.annotation.TargetApi;
 import android.app.StatusBarManager.NavbarFlags;
@@ -35,6 +36,8 @@
 import android.view.Surface;
 import android.view.WindowManager;
 
+import com.android.systemui.shared.recents.model.Task;
+
 /* Common code */
 public class Utilities {
 
@@ -165,4 +168,10 @@
         float densityRatio = (float) densityDpi / DisplayMetrics.DENSITY_DEFAULT;
         return (size / densityRatio);
     }
+
+    /** Whether a task is in freeform mode. */
+    public static boolean isFreeformTask(Task task) {
+        return task != null && task.getKey() != null
+                && task.getKey().windowingMode == WINDOWING_MODE_FREEFORM;
+    }
 }
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..487d1ce 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,10 +324,23 @@
     }
 
     /**
+     * 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) {
         return info.configuration.windowConfiguration.getActivityType()
                 == WindowConfiguration.ACTIVITY_TYPE_HOME;
     }
+
+    public boolean isRunningInTestHarness() {
+        return ActivityManager.isRunningInTestHarness()
+                || ActivityManager.isRunningInUserTestHarness();
+    }
 }
diff --git a/packages/SystemUI/src/com/android/keyguard/EmergencyButtonController.java b/packages/SystemUI/src/com/android/keyguard/EmergencyButtonController.java
index 5e36539..a7bb11e 100644
--- a/packages/SystemUI/src/com/android/keyguard/EmergencyButtonController.java
+++ b/packages/SystemUI/src/com/android/keyguard/EmergencyButtonController.java
@@ -156,7 +156,7 @@
                 mMainExecutor.execute(() -> mView.updateEmergencyCallButton(
                         /* isInCall= */ isInCall,
                         /* hasTelephonyRadio= */ getContext().getPackageManager()
-                                .hasSystemFeature(PackageManager.FEATURE_TELEPHONY),
+                                .hasSystemFeature(PackageManager.FEATURE_TELEPHONY_CALLING),
                         /* simLocked= */ mKeyguardUpdateMonitor.isSimPinVoiceSecure(),
                         /* isSecure= */ isSecure));
             });
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/keyguard/dagger/KeyguardModule.java b/packages/SystemUI/src/com/android/systemui/keyguard/dagger/KeyguardModule.java
index 97ad2d7..6b1248b 100644
--- a/packages/SystemUI/src/com/android/systemui/keyguard/dagger/KeyguardModule.java
+++ b/packages/SystemUI/src/com/android/systemui/keyguard/dagger/KeyguardModule.java
@@ -247,14 +247,13 @@
     @Provides
     @SysUISingleton
     static BlurConfig provideBlurConfig(@Main Resources resources) {
-        int minBlurRadius = resources.getDimensionPixelSize(R.dimen.min_window_blur_radius);
         int maxBlurRadius =
                 Flags.notificationShadeBlur() || Flags.bouncerUiRevamp()
                         || Flags.glanceableHubBlurredBackground()
                         ? resources.getDimensionPixelSize(R.dimen.max_shade_window_blur_radius)
                         : resources.getDimensionPixelSize(R.dimen.max_window_blur_radius);
 
-        return new BlurConfig(minBlurRadius, maxBlurRadius);
+        return new BlurConfig(0.0f, maxBlurRadius);
     }
 
     /** */
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/MediaHierarchyManager.kt b/packages/SystemUI/src/com/android/systemui/media/controls/ui/controller/MediaHierarchyManager.kt
index b64cb3d..f06c4cc 100644
--- a/packages/SystemUI/src/com/android/systemui/media/controls/ui/controller/MediaHierarchyManager.kt
+++ b/packages/SystemUI/src/com/android/systemui/media/controls/ui/controller/MediaHierarchyManager.kt
@@ -58,6 +58,7 @@
 import com.android.systemui.statusbar.CrossFadeHelper
 import com.android.systemui.statusbar.StatusBarState
 import com.android.systemui.statusbar.SysuiStatusBarStateController
+import com.android.systemui.statusbar.featurepods.popups.StatusBarPopupChips
 import com.android.systemui.statusbar.notification.stack.StackStateAnimator
 import com.android.systemui.statusbar.phone.KeyguardBypassController
 import com.android.systemui.statusbar.policy.ConfigurationController
@@ -229,7 +230,7 @@
         else result.setIntersect(animationStartClipping, targetClipping)
     }
 
-    private val mediaHosts = arrayOfNulls<MediaHost>(LOCATION_COMMUNAL_HUB + 1)
+    private val mediaHosts = arrayOfNulls<MediaHost>(LOCATION_STATUS_BAR_POPUP + 1)
 
     /**
      * The last location where this view was at before going to the desired location. This is useful
@@ -374,6 +375,15 @@
             }
         }
 
+    /** Is the Media Control StatusBarPopup showing */
+    var isMediaControlPopupShowing: Boolean = false
+        set(value) {
+            if (field != value && StatusBarPopupChips.isEnabled) {
+                field = value
+                updateDesiredLocation(forceNoAnimation = true)
+            }
+        }
+
     /** Are location changes currently blocked? */
     private val blockLocationChanges: Boolean
         get() {
@@ -1225,6 +1235,7 @@
             // Keep the current location until we're allowed to again
             return desiredLocation
         }
+
         val onLockscreen =
             (!bypassController.bypassEnabled && (statusbarState == StatusBarState.KEYGUARD))
 
@@ -1234,6 +1245,8 @@
             (onCommunalNotDreaming && qsExpansion == 0.0f) || onCommunalDreamingAndShadeExpanding
         val location =
             when {
+                isMediaControlPopupShowing && StatusBarPopupChips.isEnabled ->
+                    LOCATION_STATUS_BAR_POPUP
                 dreamOverlayActive && dreamMediaComplicationActive -> LOCATION_DREAM_OVERLAY
                 onCommunal -> LOCATION_COMMUNAL_HUB
                 (qsExpansion > 0.0f || inSplitShade) && !onLockscreen -> LOCATION_QS
@@ -1377,6 +1390,9 @@
         /** Attached to a view in the communal UI grid */
         const val LOCATION_COMMUNAL_HUB = 4
 
+        /** Attached to a popup that is shown with a media control chip in the status bar */
+        const val LOCATION_STATUS_BAR_POPUP = 5
+
         /** Attached at the root of the hierarchy in an overlay */
         const val IN_OVERLAY = -1000
 
@@ -1422,6 +1438,7 @@
             MediaHierarchyManager.LOCATION_LOCKSCREEN,
             MediaHierarchyManager.LOCATION_DREAM_OVERLAY,
             MediaHierarchyManager.LOCATION_COMMUNAL_HUB,
+            MediaHierarchyManager.LOCATION_STATUS_BAR_POPUP,
             MediaHierarchyManager.LOCATION_UNKNOWN,
         ],
 )
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/media/controls/util/MediaUiEventLogger.kt b/packages/SystemUI/src/com/android/systemui/media/controls/util/MediaUiEventLogger.kt
index 09f973c..f0da226 100644
--- a/packages/SystemUI/src/com/android/systemui/media/controls/util/MediaUiEventLogger.kt
+++ b/packages/SystemUI/src/com/android/systemui/media/controls/util/MediaUiEventLogger.kt
@@ -45,7 +45,7 @@
         uid: Int,
         packageName: String,
         instanceId: InstanceId,
-        playbackLocation: Int
+        playbackLocation: Int,
     ) {
         val event =
             when (playbackLocation) {
@@ -61,7 +61,7 @@
         uid: Int,
         packageName: String,
         instanceId: InstanceId,
-        playbackLocation: Int
+        playbackLocation: Int,
     ) {
         val event =
             when (playbackLocation) {
@@ -112,7 +112,7 @@
             MediaUiEvent.OPEN_SETTINGS_LONG_PRESS,
             uid,
             packageName,
-            instanceId
+            instanceId,
         )
     }
 
@@ -156,6 +156,8 @@
                     MediaUiEvent.MEDIA_CAROUSEL_LOCATION_DREAM
                 MediaHierarchyManager.LOCATION_COMMUNAL_HUB ->
                     MediaUiEvent.MEDIA_CAROUSEL_LOCATION_COMMUNAL
+                MediaHierarchyManager.LOCATION_STATUS_BAR_POPUP ->
+                    MediaUiEvent.MEDIA_CAROUSEL_LOCATION_STATUS_BAR_POPUP
                 else -> throw IllegalArgumentException("Unknown media carousel location $location")
             }
         logger.log(event)
@@ -166,7 +168,7 @@
             MediaUiEvent.MEDIA_RECOMMENDATION_ADDED,
             0,
             packageName,
-            instanceId
+            instanceId,
         )
     }
 
@@ -175,7 +177,7 @@
             MediaUiEvent.MEDIA_RECOMMENDATION_REMOVED,
             0,
             packageName,
-            instanceId
+            instanceId,
         )
     }
 
@@ -184,7 +186,7 @@
             MediaUiEvent.MEDIA_RECOMMENDATION_ACTIVATED,
             uid,
             packageName,
-            instanceId
+            instanceId,
         )
     }
 
@@ -194,7 +196,7 @@
             0,
             packageName,
             instanceId,
-            position
+            position,
         )
     }
 
@@ -203,7 +205,7 @@
             MediaUiEvent.MEDIA_RECOMMENDATION_CARD_TAP,
             0,
             packageName,
-            instanceId
+            instanceId,
         )
     }
 
@@ -212,7 +214,7 @@
             MediaUiEvent.MEDIA_OPEN_BROADCAST_DIALOG,
             uid,
             packageName,
-            instanceId
+            instanceId,
         )
     }
 
@@ -221,7 +223,7 @@
             MediaUiEvent.MEDIA_CAROUSEL_SINGLE_PLAYER,
             uid,
             packageName,
-            instanceId
+            instanceId,
         )
     }
 
@@ -230,7 +232,7 @@
             MediaUiEvent.MEDIA_CAROUSEL_MULTIPLE_PLAYERS,
             uid,
             packageName,
-            instanceId
+            instanceId,
         )
     }
 }
@@ -280,6 +282,8 @@
     MEDIA_CAROUSEL_LOCATION_DREAM(1040),
     @UiEvent(doc = "The media carousel moved to the communal hub UI")
     MEDIA_CAROUSEL_LOCATION_COMMUNAL(1520),
+    @UiEvent(doc = "The media carousel moved to the status bar popup")
+    MEDIA_CAROUSEL_LOCATION_STATUS_BAR_POPUP(2170),
     @UiEvent(doc = "A media recommendation card was added to the media carousel")
     MEDIA_RECOMMENDATION_ADDED(1041),
     @UiEvent(doc = "A media recommendation card was removed from the media carousel")
diff --git a/packages/SystemUI/src/com/android/systemui/media/dagger/MediaModule.java b/packages/SystemUI/src/com/android/systemui/media/dagger/MediaModule.java
index 45a3a8c..662a9c7 100644
--- a/packages/SystemUI/src/com/android/systemui/media/dagger/MediaModule.java
+++ b/packages/SystemUI/src/com/android/systemui/media/dagger/MediaModule.java
@@ -49,6 +49,7 @@
     String KEYGUARD = "media_keyguard";
     String DREAM = "dream";
     String COMMUNAL_HUB = "communal_Hub";
+    String POPUP = "popup";
 
     /** */
     @Provides
@@ -102,7 +103,26 @@
     @Provides
     @SysUISingleton
     @Named(COMMUNAL_HUB)
-    static MediaHost providesCommunalMediaHost(MediaHost.MediaHostStateHolder stateHolder,
+    static MediaHost providesCommunalMediaHost(
+            MediaHost.MediaHostStateHolder stateHolder,
+            MediaHierarchyManager hierarchyManager,
+            MediaDataManager dataManager,
+            MediaHostStatesManager statesManager,
+            MediaCarouselController carouselController,
+            MediaCarouselControllerLogger logger) {
+        return new MediaHost(
+                stateHolder,
+                hierarchyManager,
+                dataManager,
+                statesManager,
+                carouselController,
+                logger);
+    }
+
+    @Provides
+    @SysUISingleton
+    @Named(POPUP)
+    static MediaHost providesPopupMediaHost(MediaHost.MediaHostStateHolder stateHolder,
             MediaHierarchyManager hierarchyManager, MediaDataManager dataManager,
             MediaHostStatesManager statesManager, MediaCarouselController carouselController,
             MediaCarouselControllerLogger logger) {
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/composefragment/QSFragmentCompose.kt b/packages/SystemUI/src/com/android/systemui/qs/composefragment/QSFragmentCompose.kt
index 6ad8bae..5930a24 100644
--- a/packages/SystemUI/src/com/android/systemui/qs/composefragment/QSFragmentCompose.kt
+++ b/packages/SystemUI/src/com/android/systemui/qs/composefragment/QSFragmentCompose.kt
@@ -306,6 +306,7 @@
                 sceneState,
                 viewModel.containerViewModel.editModeViewModel.isEditing,
                 snapshotFlow { viewModel.expansionState }.map { it.progress },
+                snapshotFlow { viewModel.isQSExpandingOrCollapsing },
             )
         }
 
@@ -537,6 +538,10 @@
         return qqsVisible.value
     }
 
+    override fun setQSExpandingOrCollapsing(isQSExpandingOrCollapsing: Boolean) {
+        viewModel.isQSExpandingOrCollapsing = isQSExpandingOrCollapsing
+    }
+
     private fun setListenerCollections() {
         lifecycleScope.launch {
             lifecycle.repeatOnLifecycle(Lifecycle.State.STARTED) {
@@ -877,6 +882,7 @@
     state: MutableSceneTransitionLayoutState,
     editMode: Flow<Boolean>,
     expansion: Flow<Float>,
+    isQSExpandingOrCollapsing: Flow<Boolean>,
 ) {
     coroutineScope {
         val animationScope = this
@@ -888,31 +894,46 @@
             currentTransition = null
         }
 
-        editMode.combine(expansion, ::Pair).collectLatest { (editMode, progress) ->
+        var lastValidProgress = 0f
+        combine(editMode, expansion, isQSExpandingOrCollapsing, ::Triple).collectLatest {
+            (editMode, progress, isQSExpandingOrCollapsing) ->
             if (editMode && state.currentScene != SceneKeys.EditMode) {
                 state.setTargetScene(SceneKeys.EditMode, animationScope)?.second?.join()
             } else if (!editMode && state.currentScene == SceneKeys.EditMode) {
                 state.setTargetScene(SceneKeys.QuickSettings, animationScope)?.second?.join()
             }
-            if (!editMode) {
-                when (progress) {
-                    0f -> snapTo(QuickQuickSettings)
-                    1f -> snapTo(QuickSettings)
-                    else -> {
-                        val transition = currentTransition
-                        if (transition != null) {
-                            transition.progress = progress
-                            return@collectLatest
-                        }
 
-                        val newTransition =
-                            ExpansionTransition(progress).also { currentTransition = it }
-                        state.startTransitionImmediately(
-                            animationScope = animationScope,
-                            transition = newTransition,
-                        )
+            if (!editMode) {
+                if (!isQSExpandingOrCollapsing) {
+                    if (progress == 0f) {
+                        snapTo(QuickQuickSettings)
+                        return@collectLatest
+                    }
+
+                    if (progress == 1f) {
+                        snapTo(QuickSettings)
+                        return@collectLatest
                     }
                 }
+
+                var progress = progress
+                if (progress >= 0f || progress <= 1f) {
+                    lastValidProgress = progress
+                } else {
+                    progress = lastValidProgress
+                }
+
+                val transition = currentTransition
+                if (transition != null) {
+                    transition.progress = progress
+                    return@collectLatest
+                }
+
+                val newTransition = ExpansionTransition(progress).also { currentTransition = it }
+                state.startTransitionImmediately(
+                    animationScope = animationScope,
+                    transition = newTransition,
+                )
             }
         }
     }
diff --git a/packages/SystemUI/src/com/android/systemui/qs/composefragment/viewmodel/QSFragmentComposeViewModel.kt b/packages/SystemUI/src/com/android/systemui/qs/composefragment/viewmodel/QSFragmentComposeViewModel.kt
index ff84479..b829bbc 100644
--- a/packages/SystemUI/src/com/android/systemui/qs/composefragment/viewmodel/QSFragmentComposeViewModel.kt
+++ b/packages/SystemUI/src/com/android/systemui/qs/composefragment/viewmodel/QSFragmentComposeViewModel.kt
@@ -306,6 +306,8 @@
     val animateTilesExpansion: Boolean
         get() = inFirstPage && !mediaSuddenlyAppearingInLandscape
 
+    var isQSExpandingOrCollapsing by mutableStateOf(false)
+
     private val inFirstPage: Boolean
         get() = inFirstPageViewModel.inFirstPage
 
@@ -539,6 +541,7 @@
                 println("proposedTranslation", proposedTranslation)
                 println("expansionState", expansionState)
                 println("forceQS", forceQs)
+                println("isShadeExpandingOrCollapsing", isQSExpandingOrCollapsing)
                 printSection("Derived values") {
                     println("headerTranslation", headerTranslation)
                     println("translationScaleY", translationScaleY)
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/qs/tiles/BluetoothTile.java b/packages/SystemUI/src/com/android/systemui/qs/tiles/BluetoothTile.java
index fd5861f..7daac45 100644
--- a/packages/SystemUI/src/com/android/systemui/qs/tiles/BluetoothTile.java
+++ b/packages/SystemUI/src/com/android/systemui/qs/tiles/BluetoothTile.java
@@ -61,6 +61,8 @@
 import com.android.systemui.res.R;
 import com.android.systemui.statusbar.policy.BluetoothController;
 
+import dagger.Lazy;
+
 import kotlinx.coroutines.Job;
 
 import java.util.List;
@@ -84,7 +86,7 @@
 
     private final Executor mExecutor;
 
-    private final BluetoothDetailsContentViewModel mDetailsContentViewModel;
+    private final Lazy<BluetoothDetailsContentViewModel> mDetailsContentViewModel;
 
     private final FeatureFlags mFeatureFlags;
     @Nullable
@@ -104,7 +106,7 @@
             QSLogger qsLogger,
             BluetoothController bluetoothController,
             FeatureFlags featureFlags,
-            BluetoothDetailsContentViewModel detailsContentViewModel
+            Lazy<BluetoothDetailsContentViewModel> detailsContentViewModel
     ) {
         super(host, uiEventLogger, backgroundLooper, mainHandler, falsingManager, metricsLogger,
                 statusBarStateController, activityStarter, qsLogger);
@@ -133,7 +135,7 @@
                 callback.accept(new BluetoothDetailsViewModel(() -> {
                     longClick(null);
                     return null;
-                }, mDetailsContentViewModel))
+                }, mDetailsContentViewModel.get()))
         );
         return true;
     }
@@ -158,7 +160,7 @@
 
     private void handleClickEvent(@Nullable Expandable expandable) {
         if (mFeatureFlags.isEnabled(Flags.BLUETOOTH_QS_TILE_DIALOG)) {
-            mDetailsContentViewModel.showDetailsContent(expandable, /* view= */ null);
+            mDetailsContentViewModel.get().showDetailsContent(expandable, /* view= */ null);
         } else {
             // Secondary clicks are header clicks, just toggle.
             toggleBluetooth();
diff --git a/packages/SystemUI/src/com/android/systemui/qs/tiles/CastTile.java b/packages/SystemUI/src/com/android/systemui/qs/tiles/CastTile.java
index 30c2adf..c60e3da 100644
--- a/packages/SystemUI/src/com/android/systemui/qs/tiles/CastTile.java
+++ b/packages/SystemUI/src/com/android/systemui/qs/tiles/CastTile.java
@@ -54,6 +54,7 @@
 import com.android.systemui.qs.logging.QSLogger;
 import com.android.systemui.qs.tileimpl.QSTileImpl;
 import com.android.systemui.res.R;
+import com.android.systemui.shade.domain.interactor.ShadeDialogContextInteractor;
 import com.android.systemui.statusbar.connectivity.NetworkController;
 import com.android.systemui.statusbar.connectivity.SignalCallback;
 import com.android.systemui.statusbar.connectivity.WifiIndicators;
@@ -89,6 +90,7 @@
     private final Callback mCallback = new Callback();
     private final TileJavaAdapter mJavaAdapter;
     private final FeatureFlags mFeatureFlags;
+    private final ShadeDialogContextInteractor mShadeDialogContextInteractor;
     private boolean mCastTransportAllowed;
     private boolean mHotspotConnected;
 
@@ -110,7 +112,8 @@
             DialogTransitionAnimator dialogTransitionAnimator,
             ConnectivityRepository connectivityRepository,
             TileJavaAdapter javaAdapter,
-            FeatureFlags featureFlags
+            FeatureFlags featureFlags,
+            ShadeDialogContextInteractor shadeDialogContextInteractor
     ) {
         super(host, uiEventLogger, backgroundLooper, mainHandler, falsingManager, metricsLogger,
                 statusBarStateController, activityStarter, qsLogger);
@@ -120,6 +123,7 @@
         mDialogTransitionAnimator = dialogTransitionAnimator;
         mJavaAdapter = javaAdapter;
         mFeatureFlags = featureFlags;
+        mShadeDialogContextInteractor = shadeDialogContextInteractor;
         mController.observe(this, mCallback);
         mKeyguard.observe(this, mCallback);
         if (!mFeatureFlags.isEnabled(SIGNAL_CALLBACK_DEPRECATION)) {
@@ -220,7 +224,7 @@
         mUiHandler.post(() -> {
             final DialogHolder holder = new DialogHolder();
             final Dialog dialog = MediaRouteDialogPresenter.createDialog(
-                    mContext,
+                    mShadeDialogContextInteractor.getContext(),
                     ROUTE_TYPE_REMOTE_DISPLAY,
                     v -> {
                         ActivityTransitionAnimator.Controller controller =
diff --git a/packages/SystemUI/src/com/android/systemui/shade/QuickSettingsControllerImpl.java b/packages/SystemUI/src/com/android/systemui/shade/QuickSettingsControllerImpl.java
index d058372..34b3324 100644
--- a/packages/SystemUI/src/com/android/systemui/shade/QuickSettingsControllerImpl.java
+++ b/packages/SystemUI/src/com/android/systemui/shade/QuickSettingsControllerImpl.java
@@ -105,15 +105,14 @@
 
 import dalvik.annotation.optimization.NeverCompile;
 
-import dagger.Lazy;
-
-import kotlin.Unit;
-
 import java.io.PrintWriter;
 
 import javax.inject.Inject;
 import javax.inject.Provider;
 
+import dagger.Lazy;
+import kotlin.Unit;
+
 /** Handles QuickSettings touch handling, expansion and animation state. */
 @SysUISingleton
 public class QuickSettingsControllerImpl implements QuickSettingsController, Dumpable {
@@ -2366,8 +2365,16 @@
             return;
         }
         if (startTracing) {
+            if (mQs != null) {
+                mQs.setQSExpandingOrCollapsing(true);
+            }
+
             monitor.begin(mPanelView, Cuj.CUJ_NOTIFICATION_SHADE_QS_EXPAND_COLLAPSE);
         } else {
+            if (mQs != null) {
+                mQs.setQSExpandingOrCollapsing(false);
+            }
+
             if (wasCancelled) {
                 monitor.cancel(Cuj.CUJ_NOTIFICATION_SHADE_QS_EXPAND_COLLAPSE);
             } else {
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/BlurUtils.kt b/packages/SystemUI/src/com/android/systemui/statusbar/BlurUtils.kt
index f30043e..f45971b 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/BlurUtils.kt
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/BlurUtils.kt
@@ -45,7 +45,7 @@
     private val crossWindowBlurListeners: CrossWindowBlurListeners,
     dumpManager: DumpManager
 ) : Dumpable {
-    val minBlurRadius = blurConfig.minBlurRadiusPx
+    val minBlurRadius = resources.getDimensionPixelSize(R.dimen.min_window_blur_radius).toFloat();
     val maxBlurRadius = if (Flags.notificationShadeBlur()) {
         blurConfig.maxBlurRadiusPx
     } else {
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/NotificationGroupingUtil.java b/packages/SystemUI/src/com/android/systemui/statusbar/NotificationGroupingUtil.java
index c1b8d9d..6ebe024 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/NotificationGroupingUtil.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/NotificationGroupingUtil.java
@@ -16,6 +16,8 @@
 
 package com.android.systemui.statusbar;
 
+import static android.app.Flags.notificationsRedesignTemplates;
+
 import android.app.Flags;
 import android.app.Notification;
 import android.graphics.drawable.Drawable;
@@ -427,7 +429,8 @@
 
         @Override
         public void apply(View parent, View view, boolean apply, boolean reset) {
-            if (reset && parent instanceof ConversationLayout) {
+            if (!notificationsRedesignTemplates()
+                    && reset && parent instanceof ConversationLayout) {
                 ConversationLayout layout = (ConversationLayout) parent;
                 apply = layout.shouldHideAppName();
             }
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/NotificationLockscreenUserManagerImpl.java b/packages/SystemUI/src/com/android/systemui/statusbar/NotificationLockscreenUserManagerImpl.java
index f06565f..32da6ff 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/NotificationLockscreenUserManagerImpl.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/NotificationLockscreenUserManagerImpl.java
@@ -24,7 +24,7 @@
 import static android.os.Flags.allowPrivateProfile;
 import static android.os.UserHandle.USER_ALL;
 import static android.os.UserHandle.USER_NULL;
-import static android.provider.Settings.Secure.REDACT_OTP_NOTIFICATION_IMMEDIATELY;
+import static android.provider.Settings.Secure.OTP_NOTIFICATION_REDACTION_LOCK_TIME;
 import static android.provider.Settings.Secure.LOCK_SCREEN_ALLOW_PRIVATE_NOTIFICATIONS;
 import static android.provider.Settings.Secure.LOCK_SCREEN_SHOW_NOTIFICATIONS;
 import static android.provider.Settings.Secure.REDACT_OTP_NOTIFICATION_WHILE_CONNECTED_TO_WIFI;
@@ -124,10 +124,10 @@
     private static final Uri REDACT_OTP_ON_WIFI =
             Settings.Secure.getUriFor(REDACT_OTP_NOTIFICATION_WHILE_CONNECTED_TO_WIFI);
 
-    private static final Uri REDACT_OTP_IMMEDIATELY =
-            Settings.Secure.getUriFor(REDACT_OTP_NOTIFICATION_IMMEDIATELY);
+    private static final Uri OTP_REDACTION_LOCK_TIME =
+            Settings.Secure.getUriFor(OTP_NOTIFICATION_REDACTION_LOCK_TIME);
 
-    private static final long LOCK_TIME_FOR_SENSITIVE_REDACTION_MS =
+    private static final long DEFAULT_LOCK_TIME_FOR_SENSITIVE_REDACTION_MS =
             TimeUnit.MINUTES.toMillis(10);
     private final Lazy<NotificationVisibilityProvider> mVisibilityProviderLazy;
     private final Lazy<CommonNotifCollection> mCommonNotifCollectionLazy;
@@ -316,7 +316,8 @@
     protected final AtomicBoolean mConnectedToWifi = new AtomicBoolean(false);
 
     protected final AtomicBoolean mRedactOtpOnWifi = new AtomicBoolean(true);
-    protected final AtomicBoolean mRedactOtpImmediately = new AtomicBoolean(false);
+    protected final AtomicLong mOtpRedactionRequiredLockTimeMs =
+            new AtomicLong(DEFAULT_LOCK_TIME_FOR_SENSITIVE_REDACTION_MS);
 
     protected int mCurrentUserId = 0;
 
@@ -375,7 +376,7 @@
         mLockScreenUris.add(SHOW_LOCKSCREEN);
         mLockScreenUris.add(SHOW_PRIVATE_LOCKSCREEN);
         mLockScreenUris.add(REDACT_OTP_ON_WIFI);
-        mLockScreenUris.add(REDACT_OTP_IMMEDIATELY);
+        mLockScreenUris.add(OTP_REDACTION_LOCK_TIME);
 
         dumpManager.registerDumpable(this);
 
@@ -447,8 +448,8 @@
                         changed |= updateUserShowPrivateSettings(user.getIdentifier());
                     } else if (REDACT_OTP_ON_WIFI.equals(uri)) {
                         changed |= updateRedactOtpOnWifiSetting();
-                    } else if (REDACT_OTP_IMMEDIATELY.equals(uri)) {
-                        changed |= updateRedactOtpImmediatelySetting();
+                    } else if (OTP_REDACTION_LOCK_TIME.equals(uri)) {
+                        changed |= updateOtpLockTimeSetting();
                     }
                 }
 
@@ -487,7 +488,7 @@
                 mLockscreenSettingsObserver
         );
         mSecureSettings.registerContentObserverAsync(
-                REDACT_OTP_IMMEDIATELY,
+                OTP_REDACTION_LOCK_TIME,
                 mLockscreenSettingsObserver
         );
 
@@ -638,13 +639,13 @@
     }
 
     @WorkerThread
-    private boolean updateRedactOtpImmediatelySetting() {
-        boolean originalValue = mRedactOtpImmediately.get();
-        boolean newValue = mSecureSettings.getIntForUser(
-                REDACT_OTP_NOTIFICATION_IMMEDIATELY,
-                0,
-                Process.myUserHandle().getIdentifier()) != 0;
-        mRedactOtpImmediately.set(newValue);
+    private boolean updateOtpLockTimeSetting() {
+        long originalValue = mOtpRedactionRequiredLockTimeMs.get();
+        long newValue = mSecureSettings.getLongForUser(
+                OTP_NOTIFICATION_REDACTION_LOCK_TIME,
+                DEFAULT_LOCK_TIME_FOR_SENSITIVE_REDACTION_MS,
+                Process.myUserHandle().getIdentifier());
+        mOtpRedactionRequiredLockTimeMs.set(newValue);
         return originalValue != newValue;
     }
 
@@ -832,14 +833,9 @@
             return false;
         }
 
-        long latestTimeForRedaction;
-        if (mRedactOtpImmediately.get()) {
-            latestTimeForRedaction = mLastLockTime.get();
-        } else {
-            // If the lock screen was not already locked for LOCK_TIME_FOR_SENSITIVE_REDACTION_MS
-            // when this notification arrived, do not redact
-            latestTimeForRedaction = mLastLockTime.get() + LOCK_TIME_FOR_SENSITIVE_REDACTION_MS;
-        }
+        // If the lock screen was not already locked for at least mOtpRedactionRequiredLockTimeMs
+        // when this notification arrived, do not redact
+        long latestTimeForRedaction = mLastLockTime.get() + mOtpRedactionRequiredLockTimeMs.get();
 
         if (ent.getSbn().getPostTime() < latestTimeForRedaction) {
             return false;
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/NotificationShadeDepthController.kt b/packages/SystemUI/src/com/android/systemui/statusbar/NotificationShadeDepthController.kt
index 6aa2fe2..84266e8 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/NotificationShadeDepthController.kt
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/NotificationShadeDepthController.kt
@@ -38,13 +38,13 @@
 import com.android.systemui.Flags.spatialModelAppPushback
 import com.android.systemui.animation.ShadeInterpolation
 import com.android.systemui.dagger.SysUISingleton
+import com.android.systemui.dagger.qualifiers.Application
 import com.android.systemui.dump.DumpManager
 import com.android.systemui.keyguard.domain.interactor.KeyguardInteractor
 import com.android.systemui.plugins.statusbar.StatusBarStateController
 import com.android.systemui.shade.ShadeDisplayAware
 import com.android.systemui.shade.ShadeExpansionChangeEvent
 import com.android.systemui.shade.ShadeExpansionListener
-import com.android.systemui.shared.Flags.ambientAod
 import com.android.systemui.statusbar.phone.BiometricUnlockController
 import com.android.systemui.statusbar.phone.BiometricUnlockController.MODE_WAKE_AND_UNLOCK
 import com.android.systemui.statusbar.phone.DozeParameters
@@ -53,8 +53,11 @@
 import com.android.systemui.statusbar.policy.KeyguardStateController
 import com.android.systemui.statusbar.policy.SplitShadeStateController
 import com.android.systemui.util.WallpaperController
+import com.android.systemui.wallpapers.domain.interactor.WallpaperInteractor
 import com.android.systemui.window.domain.interactor.WindowRootViewBlurInteractor
 import com.android.wm.shell.appzoomout.AppZoomOut
+import kotlinx.coroutines.CoroutineScope
+import kotlinx.coroutines.launch
 import java.io.PrintWriter
 import java.util.Optional
 import javax.inject.Inject
@@ -76,12 +79,14 @@
     private val keyguardInteractor: KeyguardInteractor,
     private val choreographer: Choreographer,
     private val wallpaperController: WallpaperController,
+    private val wallpaperInteractor: WallpaperInteractor,
     private val notificationShadeWindowController: NotificationShadeWindowController,
     private val dozeParameters: DozeParameters,
     @ShadeDisplayAware private val context: Context,
     private val splitShadeStateController: SplitShadeStateController,
     private val windowRootViewBlurInteractor: WindowRootViewBlurInteractor,
     private val appZoomOutOptional: Optional<AppZoomOut>,
+    @Application private val applicationScope: CoroutineScope,
     dumpManager: DumpManager,
     configurationController: ConfigurationController,
 ) : ShadeExpansionListener, Dumpable {
@@ -110,6 +115,12 @@
     private var prevTimestamp: Long = -1
     private var prevShadeDirection = 0
     private var prevShadeVelocity = 0f
+    private var prevDozeAmount: Float = 0f
+    @VisibleForTesting var wallpaperSupportsAmbientMode: Boolean = false
+    // tracks whether app launch transition is in progress. This involves two independent factors
+    // that control blur, shade expansion and app launch animation from outside sysui.
+    // They can complete out of order, this flag will be reset by the animation that finishes later.
+    private var appLaunchTransitionIsInProgress = false
 
     // Only for dumpsys
     private var lastAppliedBlur = 0
@@ -158,6 +169,18 @@
             if (field == value) {
                 return
             }
+            // Set this to true now, this will be reset when the next shade expansion finishes or
+            // when the app launch finishes, whichever happens later.
+            if (value) {
+                appLaunchTransitionIsInProgress = true
+            } else {
+                // App was launching and now it has finished launching
+                if (shadeExpansion == 0.0f) {
+                    // this means shade expansion finished before app launch was done.
+                    // reset the flag here
+                    appLaunchTransitionIsInProgress = false
+                }
+            }
             field = value
             scheduleUpdate()
 
@@ -172,6 +195,12 @@
             shadeAnimation.animateTo(0)
             shadeAnimation.finishIfRunning()
         }
+        @Deprecated(
+            message =
+                "This might get reset to false before shade expansion is fully done, " +
+                    "consider using areBlursDisabledForAppLaunch"
+        )
+        get() = field
 
     private var zoomOutCalculatedFromShadeRadius: Float = 0.0f
 
@@ -183,6 +212,11 @@
             scheduleUpdate()
         }
 
+    private val areBlursDisabledForAppLaunch: Boolean
+        get() =
+            blursDisabledForAppLaunch ||
+                (Flags.bouncerUiRevamp() && appLaunchTransitionIsInProgress)
+
     /** Force stop blur effect when necessary. */
     private var scrimsVisible: Boolean = false
         set(value) {
@@ -192,7 +226,15 @@
         }
 
     /** Blur radius of the wake-up animation on this frame. */
-    private var wakeAndUnlockBlurRadius = 0f
+    private var wakeBlurRadius = 0f
+        set(value) {
+            if (field == value) return
+            field = value
+            scheduleUpdate()
+        }
+
+    /** Blur radius of the unlock animation on this frame. */
+    private var unlockBlurRadius = 0f
         set(value) {
             if (field == value) return
             field = value
@@ -219,14 +261,16 @@
             ShadeInterpolation.getNotificationScrimAlpha(qsPanelExpansion) * shadeExpansion
         combinedBlur = max(combinedBlur, blurUtils.blurRadiusOfRatio(qsExpandedRatio))
         combinedBlur = max(combinedBlur, blurUtils.blurRadiusOfRatio(transitionToFullShadeProgress))
-        var shadeRadius = max(combinedBlur, wakeAndUnlockBlurRadius)
+        var shadeRadius = max(combinedBlur, max(wakeBlurRadius, unlockBlurRadius))
 
-        if (blursDisabledForAppLaunch || blursDisabledForUnlock) {
+        if (areBlursDisabledForAppLaunch || blursDisabledForUnlock) {
             shadeRadius = 0f
         }
 
         var blur = shadeRadius.toInt()
-        val zoomOut = blurRadiusToZoomOut(blurRadius = shadeRadius)
+        // If the blur comes from waking up, we don't want to zoom out the background
+        val zoomOut =
+            if (shadeRadius != wakeBlurRadius) blurRadiusToZoomOut(blurRadius = shadeRadius) else 0f
         // Make blur be 0 if it is necessary to stop blur effect.
         if (scrimsVisible) {
             if (!Flags.notificationShadeBlur()) {
@@ -259,7 +303,7 @@
     private val shouldBlurBeOpaque: Boolean
         get() =
             if (Flags.notificationShadeBlur()) false
-            else scrimsVisible && !blursDisabledForAppLaunch
+            else scrimsVisible && !areBlursDisabledForAppLaunch
 
     /** Callback that updates the window blur value and is called only once per frame. */
     @VisibleForTesting
@@ -311,14 +355,14 @@
                         startDelay = keyguardStateController.keyguardFadingAwayDelay
                         interpolator = Interpolators.FAST_OUT_SLOW_IN
                         addUpdateListener { animation: ValueAnimator ->
-                            wakeAndUnlockBlurRadius =
+                            unlockBlurRadius =
                                 blurUtils.blurRadiusOfRatio(animation.animatedValue as Float)
                         }
                         addListener(
                             object : AnimatorListenerAdapter() {
                                 override fun onAnimationEnd(animation: Animator) {
                                     keyguardAnimator = null
-                                    wakeAndUnlockBlurRadius = 0f
+                                    unlockBlurRadius = 0f
                                 }
                             }
                         )
@@ -354,15 +398,20 @@
             }
 
             override fun onDozeAmountChanged(linear: Float, eased: Float) {
-                wakeAndUnlockBlurRadius =
-                    if (ambientAod()) {
-                        0f
-                    } else {
-                        blurUtils.blurRadiusOfRatio(eased)
-                    }
+                prevDozeAmount = eased
+                updateWakeBlurRadius(prevDozeAmount)
             }
         }
 
+    private fun updateWakeBlurRadius(ratio: Float) {
+        wakeBlurRadius =
+            if (!wallpaperSupportsAmbientMode) {
+                0f
+            } else {
+                blurUtils.blurRadiusOfRatio(ratio)
+            }
+    }
+
     init {
         dumpManager.registerCriticalDumpable(javaClass.name, this)
         if (WAKE_UP_ANIMATION_ENABLED) {
@@ -384,6 +433,12 @@
                 }
             }
         )
+        applicationScope.launch {
+            wallpaperInteractor.wallpaperSupportsAmbientMode.collect { supported ->
+                wallpaperSupportsAmbientMode = supported
+                updateWakeBlurRadius(prevDozeAmount)
+            }
+        }
         initBlurListeners()
     }
 
@@ -442,6 +497,13 @@
         val shadeDirection = sign(diff).toInt()
         val shadeVelocity =
             MathUtils.constrain(VELOCITY_SCALE * diff / deltaTime, MIN_VELOCITY, MAX_VELOCITY)
+        if (expansion == 0.0f && appLaunchTransitionIsInProgress && !blursDisabledForAppLaunch) {
+            // Shade expansion finished but the app launch is already done, then this should mark
+            // the transition as done.
+            Log.d(TAG, "appLaunchTransitionIsInProgress is now false from shade expansion event")
+            appLaunchTransitionIsInProgress = false
+        }
+
         updateShadeAnimationBlur(expansion, tracking, shadeVelocity, shadeDirection)
 
         prevShadeDirection = shadeDirection
@@ -551,8 +613,10 @@
             it.println("shouldApplyShadeBlur: ${shouldApplyShadeBlur()}")
             it.println("shadeAnimation: ${shadeAnimation.radius}")
             it.println("brightnessMirrorRadius: ${brightnessMirrorSpring.radius}")
-            it.println("wakeAndUnlockBlur: $wakeAndUnlockBlurRadius")
+            it.println("wakeBlur: $wakeBlurRadius")
+            it.println("unlockBlur: $wakeBlurRadius")
             it.println("blursDisabledForAppLaunch: $blursDisabledForAppLaunch")
+            it.println("appLaunchTransitionIsInProgress: $appLaunchTransitionIsInProgress")
             it.println("qsPanelExpansion: $qsPanelExpansion")
             it.println("transitionToFullShadeProgress: $transitionToFullShadeProgress")
             it.println("lastAppliedBlur: $lastAppliedBlur")
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/chips/call/domain/interactor/CallChipInteractor.kt b/packages/SystemUI/src/com/android/systemui/statusbar/chips/call/domain/interactor/CallChipInteractor.kt
index 2eae3eb..7548f6ff 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/chips/call/domain/interactor/CallChipInteractor.kt
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/chips/call/domain/interactor/CallChipInteractor.kt
@@ -22,9 +22,9 @@
 import com.android.systemui.log.core.LogLevel
 import com.android.systemui.statusbar.chips.StatusBarChipLogTags.pad
 import com.android.systemui.statusbar.chips.StatusBarChipsLog
+import com.android.systemui.statusbar.phone.ongoingcall.StatusBarChipsModernization
 import com.android.systemui.statusbar.phone.ongoingcall.data.repository.OngoingCallRepository
 import com.android.systemui.statusbar.phone.ongoingcall.domain.interactor.OngoingCallInteractor
-import com.android.systemui.statusbar.phone.ongoingcall.StatusBarChipsModernization
 import com.android.systemui.statusbar.phone.ongoingcall.shared.model.OngoingCallModel
 import javax.inject.Inject
 import kotlinx.coroutines.CoroutineScope
@@ -44,25 +44,17 @@
     @StatusBarChipsLog private val logger: LogBuffer,
 ) {
     val ongoingCallState: StateFlow<OngoingCallModel> =
-        (if (StatusBarChipsModernization.isEnabled)
-            ongoingCallInteractor.ongoingCallState
-        else
-            repository.ongoingCallState)
+        (if (StatusBarChipsModernization.isEnabled) {
+                ongoingCallInteractor.ongoingCallState
+            } else {
+                repository.ongoingCallState
+            })
             .onEach {
-                logger.log(
-                    TAG,
-                    LogLevel.INFO,
-                    { str1 = it::class.simpleName },
-                    { "State: $str1" }
-                )
+                logger.log(TAG, LogLevel.INFO, { str1 = it::class.simpleName }, { "State: $str1" })
             }
-            .stateIn(
-                scope,
-                SharingStarted.Lazily,
-                OngoingCallModel.NoCall
-            )
+            .stateIn(scope, SharingStarted.Lazily, OngoingCallModel.NoCall)
 
     companion object {
         private val TAG = "OngoingCall".pad()
     }
-}
\ No newline at end of file
+}
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/core/NewStatusBarIcons.kt b/packages/SystemUI/src/com/android/systemui/statusbar/core/NewStatusBarIcons.kt
index e3be953..402881d 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/core/NewStatusBarIcons.kt
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/core/NewStatusBarIcons.kt
@@ -29,10 +29,10 @@
     val token: FlagToken
         get() = FlagToken(FLAG_NAME, isEnabled)
 
-    /** Is the refactor enabled */
+    /** Is the refactor enabled. Dependency on [StatusBarRootModernization] */
     @JvmStatic
     inline val isEnabled
-        get() = Flags.newStatusBarIcons()
+        get() = Flags.newStatusBarIcons() && StatusBarRootModernization.isEnabled
 
     /**
      * Called to ensure code is only run when the flag is enabled. This protects users from the
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/featurepods/media/ui/compose/MediaControlPopup.kt b/packages/SystemUI/src/com/android/systemui/statusbar/featurepods/media/ui/compose/MediaControlPopup.kt
new file mode 100644
index 0000000..80bdb7f
--- /dev/null
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/featurepods/media/ui/compose/MediaControlPopup.kt
@@ -0,0 +1,56 @@
+/*
+ * Copyright (C) 2025 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.systemui.statusbar.featurepods.media.ui.compose
+
+import android.widget.FrameLayout
+import androidx.compose.foundation.layout.height
+import androidx.compose.foundation.layout.width
+import androidx.compose.foundation.shape.RoundedCornerShape
+import androidx.compose.runtime.Composable
+import androidx.compose.ui.Modifier
+import androidx.compose.ui.draw.clip
+import androidx.compose.ui.res.dimensionResource
+import androidx.compose.ui.unit.dp
+import androidx.compose.ui.viewinterop.AndroidView
+import com.android.systemui.media.controls.ui.view.MediaHost
+import com.android.systemui.res.R
+
+/** Displays a popup containing media controls. Embeds the MediaCarousel within a Compose popup. */
+@Composable
+fun MediaControlPopup(mediaHost: MediaHost, modifier: Modifier = Modifier) {
+    AndroidView(
+        modifier =
+            modifier
+                .width(400.dp)
+                .height(200.dp)
+                .clip(
+                    shape =
+                        RoundedCornerShape(dimensionResource(R.dimen.notification_corner_radius))
+                ),
+        factory = { _ ->
+            mediaHost.hostView.apply {
+                layoutParams =
+                    FrameLayout.LayoutParams(
+                        FrameLayout.LayoutParams.MATCH_PARENT,
+                        FrameLayout.LayoutParams.MATCH_PARENT,
+                    )
+            }
+            mediaHost.hostView
+        },
+        onReset = {},
+    )
+}
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/featurepods/popups/ui/compose/StatusBarPopup.kt b/packages/SystemUI/src/com/android/systemui/statusbar/featurepods/popups/ui/compose/StatusBarPopup.kt
index 8a66904..ead5148 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/featurepods/popups/ui/compose/StatusBarPopup.kt
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/featurepods/popups/ui/compose/StatusBarPopup.kt
@@ -28,7 +28,9 @@
 import androidx.compose.ui.unit.dp
 import androidx.compose.ui.window.Popup
 import androidx.compose.ui.window.PopupProperties
+import com.android.systemui.media.controls.ui.view.MediaHost
 import com.android.systemui.res.R
+import com.android.systemui.statusbar.featurepods.media.ui.compose.MediaControlPopup
 import com.android.systemui.statusbar.featurepods.popups.shared.model.PopupChipId
 import com.android.systemui.statusbar.featurepods.popups.shared.model.PopupChipModel
 
@@ -37,7 +39,7 @@
  * status bar.
  */
 @Composable
-fun StatusBarPopup(viewModel: PopupChipModel.Shown) {
+fun StatusBarPopup(viewModel: PopupChipModel.Shown, mediaHost: MediaHost) {
     val density = Density(LocalContext.current)
     Popup(
         properties =
@@ -56,7 +58,7 @@
         Box(modifier = Modifier.padding(8.dp).wrapContentSize()) {
             when (viewModel.chipId) {
                 is PopupChipId.MediaControl -> {
-                    // TODO(b/385202114): Populate MediaControlPopup contents.
+                    MediaControlPopup(mediaHost = mediaHost)
                 }
             }
             // Future popup types will be handled here.
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/featurepods/popups/ui/compose/StatusBarPopupChip.kt b/packages/SystemUI/src/com/android/systemui/statusbar/featurepods/popups/ui/compose/StatusBarPopupChip.kt
index eb85d2f..c77decd 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/featurepods/popups/ui/compose/StatusBarPopupChip.kt
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/featurepods/popups/ui/compose/StatusBarPopupChip.kt
@@ -16,33 +16,49 @@
 
 package com.android.systemui.statusbar.featurepods.popups.ui.compose
 
-import androidx.compose.animation.animateContentSize
+import androidx.compose.foundation.LocalIndication
 import androidx.compose.foundation.background
+import androidx.compose.foundation.border
 import androidx.compose.foundation.clickable
-import androidx.compose.foundation.hoverable
+import androidx.compose.foundation.indication
 import androidx.compose.foundation.interaction.MutableInteractionSource
 import androidx.compose.foundation.interaction.collectIsHoveredAsState
 import androidx.compose.foundation.layout.Arrangement
+import androidx.compose.foundation.layout.Box
 import androidx.compose.foundation.layout.Row
+import androidx.compose.foundation.layout.height
 import androidx.compose.foundation.layout.padding
+import androidx.compose.foundation.layout.width
 import androidx.compose.foundation.layout.widthIn
 import androidx.compose.foundation.shape.CircleShape
 import androidx.compose.foundation.shape.RoundedCornerShape
 import androidx.compose.material3.MaterialTheme
-import androidx.compose.material3.Surface
 import androidx.compose.material3.Text
 import androidx.compose.material3.contentColorFor
+import androidx.compose.material3.minimumInteractiveComponentSize
 import androidx.compose.material3.ripple
 import androidx.compose.runtime.Composable
 import androidx.compose.runtime.getValue
+import androidx.compose.runtime.mutableStateOf
 import androidx.compose.runtime.remember
+import androidx.compose.runtime.setValue
 import androidx.compose.ui.Alignment
 import androidx.compose.ui.Modifier
+import androidx.compose.ui.draw.clip
+import androidx.compose.ui.draw.drawWithCache
+import androidx.compose.ui.graphics.BlendMode
+import androidx.compose.ui.graphics.Brush
+import androidx.compose.ui.graphics.Color
+import androidx.compose.ui.graphics.CompositingStrategy
+import androidx.compose.ui.layout.layout
+import androidx.compose.ui.res.dimensionResource
 import androidx.compose.ui.semantics.Role
-import androidx.compose.ui.text.style.TextOverflow
+import androidx.compose.ui.text.rememberTextMeasurer
+import androidx.compose.ui.unit.Dp
 import androidx.compose.ui.unit.dp
 import com.android.compose.modifiers.thenIf
 import com.android.systemui.common.ui.compose.Icon
+import com.android.systemui.res.R
 import com.android.systemui.statusbar.featurepods.popups.shared.model.HoverBehavior
 import com.android.systemui.statusbar.featurepods.popups.shared.model.PopupChipModel
 
@@ -54,31 +70,48 @@
 @Composable
 fun StatusBarPopupChip(viewModel: PopupChipModel.Shown, modifier: Modifier = Modifier) {
     val hasHoverBehavior = viewModel.hoverBehavior !is HoverBehavior.None
-    val hoverInteractionSource = remember { MutableInteractionSource() }
-    val isHovered by hoverInteractionSource.collectIsHoveredAsState()
+    val interactionSource = remember { MutableInteractionSource() }
+    val hoveredState by interactionSource.collectIsHoveredAsState()
+    val isHovered = hasHoverBehavior && hoveredState
     val isPopupShown = viewModel.isPopupShown
-
+    val indication = if (hoveredState) null else LocalIndication.current
+    val chipShape =
+        RoundedCornerShape(dimensionResource(id = R.dimen.ongoing_activity_chip_corner_radius))
     val chipBackgroundColor =
         if (isPopupShown) {
-            MaterialTheme.colorScheme.primaryContainer
+            MaterialTheme.colorScheme.primary
         } else {
-            MaterialTheme.colorScheme.surfaceContainerHighest
+            MaterialTheme.colorScheme.surfaceDim
         }
-    Surface(
-        shape = RoundedCornerShape(16.dp),
+
+    // Use a Box with `fillMaxHeight` to create a larger click surface for the chip. The visible
+    // height of the chip is determined by the height of the background of the Row below. The
+    // `indication` for Clicks is applied in the Row below as well.
+    Box(
+        contentAlignment = Alignment.Center,
         modifier =
-            modifier
-                .widthIn(max = 120.dp)
-                .padding(vertical = 4.dp)
-                .animateContentSize()
-                .thenIf(hasHoverBehavior) { Modifier.hoverable(hoverInteractionSource) }
-                .thenIf(!isPopupShown) { Modifier.clickable { viewModel.showPopup() } },
-        color = chipBackgroundColor,
+            modifier.minimumInteractiveComponentSize().thenIf(!isPopupShown) {
+                Modifier.clickable(
+                    onClick = { viewModel.showPopup() },
+                    indication = null,
+                    interactionSource = interactionSource,
+                )
+            },
     ) {
         Row(
-            modifier = Modifier.padding(start = 4.dp, end = 8.dp),
-            verticalAlignment = Alignment.CenterVertically,
             horizontalArrangement = Arrangement.spacedBy(4.dp),
+            verticalAlignment = Alignment.CenterVertically,
+            modifier =
+                Modifier.height(dimensionResource(R.dimen.ongoing_appops_chip_height))
+                    .clip(chipShape)
+                    .background(chipBackgroundColor)
+                    .border(
+                        width = dimensionResource(id = R.dimen.ongoing_activity_chip_outline_width),
+                        color = MaterialTheme.colorScheme.outlineVariant,
+                        shape = chipShape,
+                    )
+                    .indication(interactionSource, indication)
+                    .padding(start = 4.dp, end = 8.dp),
         ) {
             val iconColor =
                 if (isHovered) chipBackgroundColor else contentColorFor(chipBackgroundColor)
@@ -92,9 +125,11 @@
                         else -> viewModel.icon
                     },
                 modifier =
-                    Modifier.thenIf(isHovered) {
-                            Modifier.padding(3.dp)
-                                .background(color = iconBackgroundColor, shape = CircleShape)
+                    Modifier.height(20.dp)
+                        .width(20.dp)
+                        .thenIf(isHovered) {
+                            Modifier.background(color = iconBackgroundColor, shape = CircleShape)
+                                .padding(2.dp)
                         }
                         .thenIf(hoverBehavior is HoverBehavior.Button) {
                             Modifier.clickable(
@@ -102,18 +137,67 @@
                                 onClick = (hoverBehavior as HoverBehavior.Button).onIconPressed,
                                 indication = ripple(),
                                 interactionSource = iconInteractionSource,
+                                enabled = isHovered,
                             )
-                        }
-                        .padding(3.dp),
+                        },
                 tint = iconColor,
             )
 
+            val text = viewModel.chipText
+            val textStyle = MaterialTheme.typography.labelLarge
+            val textMeasurer = rememberTextMeasurer()
+            var textOverflow by remember { mutableStateOf(false) }
+
             Text(
-                text = viewModel.chipText,
-                style = MaterialTheme.typography.labelLarge,
+                text = text,
+                style = textStyle,
                 softWrap = false,
-                overflow = TextOverflow.Ellipsis,
+                modifier =
+                    Modifier.widthIn(
+                            max =
+                                dimensionResource(id = R.dimen.ongoing_activity_chip_max_text_width)
+                        )
+                        .layout { measurables, constraints ->
+                            val placeable = measurables.measure(constraints)
+                            val intrinsicWidth =
+                                textMeasurer.measure(text, textStyle, softWrap = false).size.width
+                            textOverflow = intrinsicWidth > constraints.maxWidth
+
+                            layout(placeable.width, placeable.height) {
+                                if (textOverflow) {
+                                    placeable.placeWithLayer(0, 0) {
+                                        compositingStrategy = CompositingStrategy.Offscreen
+                                    }
+                                } else {
+                                    placeable.place(0, 0)
+                                }
+                            }
+                        }
+                        .overflowFadeOut(
+                            hasOverflow = { textOverflow },
+                            fadeLength =
+                                dimensionResource(
+                                    id = R.dimen.ongoing_activity_chip_text_fading_edge_length
+                                ),
+                        ),
             )
         }
     }
 }
+
+private fun Modifier.overflowFadeOut(hasOverflow: () -> Boolean, fadeLength: Dp): Modifier {
+    return drawWithCache {
+        val width = size.width
+        val start = (width - fadeLength.toPx()).coerceAtLeast(0f)
+        val gradient =
+            Brush.horizontalGradient(
+                colors = listOf(Color.Black, Color.Transparent),
+                startX = start,
+                endX = width,
+            )
+        onDrawWithContent {
+            drawContent()
+            if (hasOverflow()) drawRect(brush = gradient, blendMode = BlendMode.DstIn)
+        }
+    }
+}
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/featurepods/popups/ui/compose/StatusBarPopupChipsContainer.kt b/packages/SystemUI/src/com/android/systemui/statusbar/featurepods/popups/ui/compose/StatusBarPopupChipsContainer.kt
index 16538c9..f5f1f20 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/featurepods/popups/ui/compose/StatusBarPopupChipsContainer.kt
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/featurepods/popups/ui/compose/StatusBarPopupChipsContainer.kt
@@ -20,14 +20,37 @@
 import androidx.compose.foundation.layout.Row
 import androidx.compose.foundation.layout.padding
 import androidx.compose.runtime.Composable
+import androidx.compose.runtime.LaunchedEffect
+import androidx.compose.runtime.getValue
+import androidx.compose.runtime.remember
+import androidx.compose.runtime.setValue
 import androidx.compose.ui.Alignment
 import androidx.compose.ui.Modifier
 import androidx.compose.ui.unit.dp
+import com.android.systemui.media.controls.ui.view.MediaHost
+import com.android.systemui.scene.shared.flag.SceneContainerFlag
+import com.android.systemui.statusbar.featurepods.popups.shared.model.PopupChipId
 import com.android.systemui.statusbar.featurepods.popups.shared.model.PopupChipModel
 
 /** Container view that holds all right hand side chips in the status bar. */
 @Composable
-fun StatusBarPopupChipsContainer(chips: List<PopupChipModel.Shown>, modifier: Modifier = Modifier) {
+fun StatusBarPopupChipsContainer(
+    chips: List<PopupChipModel.Shown>,
+    mediaHost: MediaHost,
+    onMediaControlPopupVisibilityChanged: (Boolean) -> Unit,
+    modifier: Modifier = Modifier,
+) {
+    if (!SceneContainerFlag.isEnabled) {
+        val isMediaControlPopupShown =
+            remember(chips) {
+                chips.any { it.chipId == PopupChipId.MediaControl && it.isPopupShown }
+            }
+
+        LaunchedEffect(isMediaControlPopupShown) {
+            onMediaControlPopupVisibilityChanged(isMediaControlPopupShown)
+        }
+    }
+
     //    TODO(b/385353140): Add padding and spacing for this container according to UX specs.
     Box {
         Row(
@@ -37,7 +60,7 @@
             chips.forEach { chip ->
                 StatusBarPopupChip(chip)
                 if (chip.isPopupShown) {
-                    StatusBarPopup(chip)
+                    StatusBarPopup(viewModel = chip, mediaHost = mediaHost)
                 }
             }
         }
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/NotificationTransitionAnimatorController.kt b/packages/SystemUI/src/com/android/systemui/statusbar/notification/NotificationTransitionAnimatorController.kt
index 383227d..ab40582 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/NotificationTransitionAnimatorController.kt
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/NotificationTransitionAnimatorController.kt
@@ -21,6 +21,7 @@
 import com.android.internal.jank.InteractionJankMonitor
 import com.android.systemui.animation.ActivityTransitionAnimator
 import com.android.systemui.animation.TransitionAnimator
+import com.android.systemui.statusbar.notification.collection.GroupEntry
 import com.android.systemui.statusbar.notification.domain.interactor.NotificationLaunchAnimationInteractor
 import com.android.systemui.statusbar.notification.headsup.HeadsUpManager
 import com.android.systemui.statusbar.notification.headsup.HeadsUpUtil
@@ -157,8 +158,8 @@
 
     private val headsUpNotificationRow: ExpandableNotificationRow?
         get() {
-            val summaryEntry = notificationEntry.parent?.summary
-
+            val pipelineParent = notificationEntry.parent
+            val summaryEntry = (pipelineParent as? GroupEntry)?.summary
             return when {
                 headsUpManager.isHeadsUpEntry(notificationKey) -> notification
                 summaryEntry == null -> null
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/NotificationUtils.java b/packages/SystemUI/src/com/android/systemui/statusbar/notification/NotificationUtils.java
index c6775d6..31bcf2b 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/NotificationUtils.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/NotificationUtils.java
@@ -26,6 +26,7 @@
 import com.android.systemui.res.R;
 import com.android.systemui.statusbar.notification.collection.EntryAdapter;
 import com.android.systemui.statusbar.notification.collection.ListEntry;
+import com.android.systemui.statusbar.notification.collection.PipelineEntry;
 import com.android.systemui.statusbar.notification.row.ExpandableNotificationRow;
 import com.android.systemui.util.Compile;
 
@@ -80,7 +81,7 @@
     private static final boolean INCLUDE_HASH_CODE_IN_LIST_ENTRY_LOG_KEY = false;
 
     /** Get the notification key, reformatted for logging, for the (optional) entry */
-    public static String logKey(ListEntry entry) {
+    public static String logKey(PipelineEntry entry) {
         if (entry == null) {
             return "null";
         }
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/NotificationUtils.kt b/packages/SystemUI/src/com/android/systemui/statusbar/notification/NotificationUtils.kt
index 432bac4..2c29e30 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/NotificationUtils.kt
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/NotificationUtils.kt
@@ -17,10 +17,10 @@
 package com.android.systemui.statusbar.notification
 
 import android.service.notification.StatusBarNotification
-import com.android.systemui.statusbar.notification.collection.ListEntry
+import com.android.systemui.statusbar.notification.collection.PipelineEntry
 
 /** Get the notification key, reformatted for logging, for the (optional) entry  */
-val ListEntry?.logKey: String?
+val PipelineEntry?.logKey: String?
     get() = this?.let { NotificationUtils.logKey(it) }
 
 /** Get the notification key, reformatted for logging, for the (optional) sbn  */
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 24ab695..35a2828 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
@@ -29,26 +29,54 @@
 import androidx.annotation.VisibleForTesting;
 
 import com.android.systemui.statusbar.notification.icon.IconPack;
+import com.android.systemui.statusbar.notification.collection.listbuilder.NotifSection;
 import com.android.systemui.statusbar.notification.row.ExpandableNotificationRow;
 
 import java.util.List;
 
 /**
- * Abstract class to represent notification section bundled by AI.
+ * Class to represent notifications bundled by classification.
  */
 public class BundleEntry extends PipelineEntry {
 
-    private final String mKey;
     private final BundleEntryAdapter mEntryAdapter;
 
     // TODO (b/389839319): implement the row
     private ExpandableNotificationRow mRow;
 
     public BundleEntry(String key) {
-        mKey = key;
+        super(key);
         mEntryAdapter = new BundleEntryAdapter();
     }
 
+    @Nullable
+    @Override
+    public NotificationEntry getRepresentativeEntry() {
+        return null;
+    }
+
+    @Nullable
+    @Override
+    public NotifSection getSection() {
+        return null;
+    }
+
+    @Override
+    public int getSectionIndex() {
+        return 0;
+    }
+
+    @Nullable
+    @Override
+    public PipelineEntry getParent() {
+        return null;
+    }
+
+    @Override
+    public boolean wasAttachedInPreviousPass() {
+        return false;
+    }
+
     @VisibleForTesting
     public BundleEntryAdapter getEntryAdapter() {
         return mEntryAdapter;
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/GroupEntry.java b/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/GroupEntry.java
index 918843c..8726e83 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/GroupEntry.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/GroupEntry.java
@@ -75,6 +75,7 @@
         return mChildren;
     }
 
+    // TODO(b/394483200) Change ROOT_ENTRY to PipelineEntry
     public static final GroupEntry ROOT_ENTRY = new GroupEntry("<root>", 0);
 
 }
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/ListAttachState.kt b/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/ListAttachState.kt
index b5fce41..4a1b956 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/ListAttachState.kt
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/ListAttachState.kt
@@ -21,14 +21,14 @@
 import com.android.systemui.statusbar.notification.collection.listbuilder.pluggable.NotifPromoter
 
 /**
- * Stores the state that [ShadeListBuilder] assigns to this [ListEntry]
+ * Stores the state that [ShadeListBuilder] assigns to this [PipelineEntry]
  */
 data class ListAttachState private constructor(
     /**
      * Null if not attached to the current shade list. If top-level, then the shade list root. If
      * part of a group, then that group's GroupEntry.
      */
-    var parent: GroupEntry?,
+    var parent: PipelineEntry?,
 
     /**
      * The section that this ListEntry was sorted into. If the child of the group, this will be the
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/ListDumper.java b/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/ListDumper.java
index f6a572e..60b75b1 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/ListDumper.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/ListDumper.java
@@ -39,14 +39,14 @@
      *                             entry to be in its current state (ie: filter, lifeExtender)
      */
     public static String dumpTree(
-            List<ListEntry> entries,
+            List<PipelineEntry> entries,
             NotificationInteractionTracker interactionTracker,
             boolean includeRecordKeeping,
             String indent) {
         StringBuilder sb = new StringBuilder();
         final String childEntryIndent = indent + INDENT;
         for (int topEntryIndex = 0; topEntryIndex < entries.size(); topEntryIndex++) {
-            ListEntry entry = entries.get(topEntryIndex);
+            PipelineEntry entry = entries.get(topEntryIndex);
             dumpEntry(entry,
                     Integer.toString(topEntryIndex),
                     indent,
@@ -106,7 +106,7 @@
     }
 
     private static void dumpEntry(
-            ListEntry entry,
+            PipelineEntry entry,
             String index,
             String indent,
             StringBuilder sb,
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/ListEntry.java b/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/ListEntry.java
index c8e3be4..697d0a0 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/ListEntry.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/ListEntry.java
@@ -21,28 +21,18 @@
 
 import androidx.annotation.Nullable;
 
-import com.android.systemui.statusbar.notification.collection.listbuilder.NotifSection;
-
 /**
  * Abstract superclass for top-level entries, i.e. things that can appear in the final notification
  * list shown to users. In practice, this means either GroupEntries or NotificationEntries.
  */
 public abstract class ListEntry extends PipelineEntry {
-    private final String mKey;
     private final long mCreationTime;
 
-    private final ListAttachState mPreviousAttachState = ListAttachState.create();
-    private final ListAttachState mAttachState = ListAttachState.create();
-
     protected ListEntry(String key, long creationTime) {
-        mKey = key;
+        super(key);
         mCreationTime = creationTime;
     }
 
-    public String getKey() {
-        return mKey;
-    }
-
     /**
      * The SystemClock.uptimeMillis() when this object was created. In general, this means the
      * moment when NotificationManager notifies our listener about the existence of this entry.
@@ -64,34 +54,22 @@
      */
     public abstract @Nullable NotificationEntry getRepresentativeEntry();
 
-    @Nullable public GroupEntry getParent() {
+    @Nullable public PipelineEntry getParent() {
         return mAttachState.getParent();
     }
 
-    void setParent(@Nullable GroupEntry parent) {
+    void setParent(@Nullable PipelineEntry parent) {
         mAttachState.setParent(parent);
     }
 
-    @Nullable public GroupEntry getPreviousParent() {
+    @Nullable public PipelineEntry getPreviousParent() {
         return mPreviousAttachState.getParent();
     }
 
-    @Nullable public NotifSection getSection() {
-        return mAttachState.getSection();
-    }
-
     public int getSectionIndex() {
         return mAttachState.getSection() != null ? mAttachState.getSection().getIndex() : -1;
     }
 
-    ListAttachState getAttachState() {
-        return mAttachState;
-    }
-
-    ListAttachState getPreviousAttachState() {
-        return mPreviousAttachState;
-    }
-
     /**
      * Stores the current attach state into {@link #getPreviousAttachState()}} and then starts a
      * fresh attach state (all entries will be null/default-initialized).
@@ -100,11 +78,4 @@
         mPreviousAttachState.clone(mAttachState);
         mAttachState.reset();
     }
-
-    /**
-     * True if this entry was attached in the last pass, else false.
-     */
-    public boolean wasAttachedInPreviousPass() {
-        return getPreviousAttachState().getParent() != null;
-    }
 }
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 698a563..e5b72d4 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
@@ -305,8 +305,10 @@
             if (isTopLevelEntry() || getParent() == null) {
                 return null;
             }
-            if (NotificationEntry.this.getParent().getSummary() != null) {
-                return NotificationEntry.this.getParent().getSummary().mEntryAdapter;
+            if (NotificationEntry.this.getParent() instanceof GroupEntry parentGroupEntry) {
+                if (parentGroupEntry.getSummary() != null) {
+                    return parentGroupEntry.getSummary().mEntryAdapter;
+                }
             }
             return null;
         }
@@ -588,7 +590,7 @@
      * Get the children that are actually attached to this notification's row.
      *
      * TODO: Seems like most callers here should probably be using
-     * {@link GroupMembershipManager#getChildren(ListEntry)}
+     * {@link GroupMembershipManager#getChildren(PipelineEntry)}
      */
     public @Nullable List<NotificationEntry> getAttachedNotifChildren() {
         if (row == null) {
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/PipelineEntry.java b/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/PipelineEntry.java
index c5a4791..78652cc 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/PipelineEntry.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/PipelineEntry.java
@@ -16,8 +16,74 @@
 
 package com.android.systemui.statusbar.notification.collection;
 
+import androidx.annotation.NonNull;
+import androidx.annotation.Nullable;
+
+import com.android.systemui.statusbar.notification.collection.listbuilder.NotifSection;
+
 /**
  * Class to represent a notification, group, or bundle in the pipeline.
  */
 public abstract class PipelineEntry {
+
+    final String mKey;
+    final ListAttachState mAttachState = ListAttachState.create();
+    final ListAttachState mPreviousAttachState = ListAttachState.create();
+
+    public PipelineEntry(String key) {
+        this.mKey = key;
+    }
+
+    /**
+     * Key of the representative entry.
+     */
+    public @NonNull String getKey() {
+        return mKey;
+    }
+
+    /**
+     * @return The representative NotificationEntry:
+     *      for NotificationEntry, return itself
+     *      for GroupEntry, return the summary NotificationEntry, or null if it does not exist
+     *      for BundleEntry, return null
+     */
+    public abstract @Nullable NotificationEntry getRepresentativeEntry();
+
+    /**
+     * @return NotifSection that ShadeListBuilder assigned to this PipelineEntry.
+     */
+    @Nullable public NotifSection getSection() {
+        return mAttachState.getSection();
+    }
+
+    /**
+     * @return True if this entry was attached in the last pass, else false.
+     */
+    public boolean wasAttachedInPreviousPass() {
+        return getPreviousAttachState().getParent() != null;
+    }
+
+    /**
+     * @return Index of section assigned to this entry.
+     */
+    public abstract int getSectionIndex();
+
+    /**
+     * @return Parent PipelineEntry
+     */
+    public abstract @Nullable PipelineEntry getParent();
+
+    /**
+     * @return Current state that ShadeListBuilder assigned to this PipelineEntry.
+     */
+    final ListAttachState getAttachState() {
+        return mAttachState;
+    }
+
+    /**
+     * @return Previous state that ShadeListBuilder assigned to this PipelineEntry.
+     */
+    final ListAttachState getPreviousAttachState() {
+        return mPreviousAttachState;
+    }
 }
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 ac11c15..bb84ab8 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
@@ -100,15 +100,15 @@
     private final DumpManager mDumpManager;
     // used exclusivly by ShadeListBuilder#notifySectionEntriesUpdated
     // TODO replace temp with collection pool for readability
-    private final ArrayList<ListEntry> mTempSectionMembers = new ArrayList<>();
+    private final ArrayList<PipelineEntry> mTempSectionMembers = new ArrayList<>();
     private NotifPipelineFlags mFlags;
     private final boolean mAlwaysLogList;
 
-    private List<ListEntry> mNotifList = new ArrayList<>();
-    private List<ListEntry> mNewNotifList = new ArrayList<>();
+    private List<PipelineEntry> mNotifList = new ArrayList<>();
+    private List<PipelineEntry> mNewNotifList = new ArrayList<>();
 
     private final SemiStableSort mSemiStableSort = new SemiStableSort();
-    private final StableOrder<ListEntry> mStableOrder = this::getStableOrderRank;
+    private final StableOrder<PipelineEntry> mStableOrder = this::getStableOrderRank;
     private final PipelineState mPipelineState = new PipelineState();
     private final Map<String, GroupEntry> mGroups = new ArrayMap<>();
     private Collection<NotificationEntry> mAllEntries = Collections.emptyList();
@@ -133,8 +133,8 @@
             mOnBeforeRenderListListeners = new NamedListenerSet<>();
     @Nullable private OnRenderListListener mOnRenderListListener;
 
-    private List<ListEntry> mReadOnlyNotifList = Collections.unmodifiableList(mNotifList);
-    private List<ListEntry> mReadOnlyNewNotifList = Collections.unmodifiableList(mNewNotifList);
+    private List<PipelineEntry> mReadOnlyNotifList = Collections.unmodifiableList(mNotifList);
+    private List<PipelineEntry> mReadOnlyNewNotifList = Collections.unmodifiableList(mNewNotifList);
     private final NotifPipelineChoreographer mChoreographer;
 
     private int mConsecutiveReentrantRebuilds = 0;
@@ -308,7 +308,7 @@
         }
     }
 
-    List<ListEntry> getShadeList() {
+    List<PipelineEntry> getShadeList() {
         Assert.isMainThread();
         // NOTE: Accessing this method when the pipeline is running is generally going to provide
         //  incorrect results, and indicates a poorly behaved component of the pipeline.
@@ -493,7 +493,7 @@
         Trace.beginSection("ShadeListBuilder.notifySectionEntriesUpdated");
         mTempSectionMembers.clear();
         for (NotifSection section : mNotifSections) {
-            for (ListEntry entry : mNotifList) {
+            for (PipelineEntry entry : mNotifList) {
                 if (section == entry.getSection()) {
                     mTempSectionMembers.add(entry);
                 }
@@ -514,11 +514,11 @@
      */
     private void applyNewNotifList() {
         mNotifList.clear();
-        List<ListEntry> emptyList = mNotifList;
+        List<PipelineEntry> emptyList = mNotifList;
         mNotifList = mNewNotifList;
         mNewNotifList = emptyList;
 
-        List<ListEntry> readOnlyNotifList = mReadOnlyNotifList;
+        List<PipelineEntry> readOnlyNotifList = mReadOnlyNotifList;
         mReadOnlyNotifList = mReadOnlyNewNotifList;
         mReadOnlyNewNotifList = readOnlyNotifList;
     }
@@ -538,12 +538,12 @@
     }
 
     private void filterNotifs(
-            Collection<? extends ListEntry> entries,
-            List<ListEntry> out,
+            Collection<? extends PipelineEntry> entries,
+            List<PipelineEntry> out,
             List<NotifFilter> filters) {
         Trace.beginSection("ShadeListBuilder.filterNotifs");
         final long now = mSystemClock.uptimeMillis();
-        for (ListEntry entry : entries) {
+        for (PipelineEntry entry : entries) {
             if (entry instanceof GroupEntry) {
                 final GroupEntry groupEntry = (GroupEntry) entry;
 
@@ -576,11 +576,11 @@
         Trace.endSection();
     }
 
-    private void groupNotifs(List<ListEntry> entries, List<ListEntry> out) {
+    private void groupNotifs(List<PipelineEntry> entries, List<PipelineEntry> out) {
         Trace.beginSection("ShadeListBuilder.groupNotifs");
-        for (ListEntry listEntry : entries) {
+        for (PipelineEntry PipelineEntry : entries) {
             // since grouping hasn't happened yet, all notifs are NotificationEntries
-            NotificationEntry entry = (NotificationEntry) listEntry;
+            NotificationEntry entry = (NotificationEntry) PipelineEntry;
             if (entry.getSbn().isGroup()) {
                 final String topLevelKey = entry.getSbn().getGroupKey();
 
@@ -631,14 +631,14 @@
         Trace.endSection();
     }
 
-    private void stabilizeGroupingNotifs(List<ListEntry> topLevelList) {
+    private void stabilizeGroupingNotifs(List<PipelineEntry> topLevelList) {
         if (getStabilityManager().isEveryChangeAllowed()) {
             return;
         }
         Trace.beginSection("ShadeListBuilder.stabilizeGroupingNotifs");
 
         for (int i = 0; i < topLevelList.size(); i++) {
-            final ListEntry tle = topLevelList.get(i);
+            final PipelineEntry tle = topLevelList.get(i);
             if (tle instanceof GroupEntry) {
                 // maybe put children back into their old group (including moving back to top-level)
                 GroupEntry groupEntry = (GroupEntry) tle;
@@ -667,13 +667,13 @@
     /**
      * Returns true if the group change was suppressed, else false
      */
-    private boolean maybeSuppressGroupChange(NotificationEntry entry, List<ListEntry> out) {
-        final GroupEntry prevParent = entry.getPreviousAttachState().getParent();
+    private boolean maybeSuppressGroupChange(NotificationEntry entry, List<PipelineEntry> out) {
+        final PipelineEntry prevParent = entry.getPreviousAttachState().getParent();
         if (prevParent == null) {
             // New entries are always allowed.
             return false;
         }
-        final GroupEntry assignedParent = entry.getParent();
+        final PipelineEntry assignedParent = entry.getParent();
         if (prevParent == assignedParent) {
             // Nothing to change.
             return false;
@@ -691,21 +691,22 @@
             entry.setParent(prevParent);
             if (prevParent == ROOT_ENTRY) {
                 out.add(entry);
-            } else {
-                prevParent.addChild(entry);
+            } else if (prevParent instanceof GroupEntry) {
+                ((GroupEntry) prevParent).addChild(entry);
                 if (!mGroups.containsKey(prevParent.getKey())) {
-                    mGroups.put(prevParent.getKey(), prevParent);
+                    mGroups.put(prevParent.getKey(), (GroupEntry) prevParent);
                 }
             }
+            // TODO(b/394483200): Revisit group stability for BundleEntry
             return true;
         }
         return false;
     }
 
-    private void promoteNotifs(List<ListEntry> list) {
+    private void promoteNotifs(List<PipelineEntry> list) {
         Trace.beginSection("ShadeListBuilder.promoteNotifs");
         for (int i = 0; i < list.size(); i++) {
-            final ListEntry tle = list.get(i);
+            final PipelineEntry tle = list.get(i);
 
             if (tle instanceof GroupEntry) {
                 final GroupEntry group = (GroupEntry) tle;
@@ -725,7 +726,7 @@
         Trace.endSection();
     }
 
-    private void pruneIncompleteGroups(List<ListEntry> shadeList) {
+    private void pruneIncompleteGroups(List<PipelineEntry> shadeList) {
         Trace.beginSection("ShadeListBuilder.pruneIncompleteGroups");
         // Any group which lost a child on this run to stability is exempt from being pruned or
         //  having its summary promoted, regardless of how many children it has
@@ -742,7 +743,7 @@
         // Iterate backwards, so that we can remove elements without affecting indices of
         // yet-to-be-accessed entries.
         for (int i = shadeList.size() - 1; i >= 0; i--) {
-            final ListEntry tle = shadeList.get(i);
+            final PipelineEntry tle = shadeList.get(i);
 
             if (tle instanceof GroupEntry) {
                 final GroupEntry group = (GroupEntry) tle;
@@ -793,7 +794,7 @@
         Trace.endSection();
     }
 
-    private void pruneGroupAtIndexAndPromoteSummary(List<ListEntry> shadeList,
+    private void pruneGroupAtIndexAndPromoteSummary(List<PipelineEntry> shadeList,
             GroupEntry group, int index) {
         // Validate that the group has no children
         checkArgument(group.getChildren().isEmpty(), "group should have no children");
@@ -801,7 +802,7 @@
         NotificationEntry summary = group.getSummary();
         summary.setParent(ROOT_ENTRY);
         // The list may be sorted; replace the group with the summary, in its place
-        ListEntry oldEntry = shadeList.set(index, summary);
+        PipelineEntry oldEntry = shadeList.set(index, summary);
 
         // Validate that the replaced entry was the group entry
         checkState(oldEntry == group);
@@ -812,10 +813,10 @@
                 "SUMMARY with no children @ " + mPipelineState.getStateName());
     }
 
-    private void pruneGroupAtIndexAndPromoteAnyChildren(List<ListEntry> shadeList,
+    private void pruneGroupAtIndexAndPromoteAnyChildren(List<PipelineEntry> shadeList,
             GroupEntry group, int index) {
         // REMOVE the GroupEntry at this index
-        ListEntry oldEntry = shadeList.remove(index);
+        PipelineEntry oldEntry = shadeList.remove(index);
 
         // Validate that the replaced entry was the group entry
         checkState(oldEntry == group);
@@ -868,14 +869,14 @@
      * top level (ungrouped) notifications.
      */
     @NonNull
-    private Set<String> getGroupsWithChildrenLostToStability(List<ListEntry> shadeList) {
+    private Set<String> getGroupsWithChildrenLostToStability(List<PipelineEntry> shadeList) {
         if (getStabilityManager().isEveryChangeAllowed()) {
             return Collections.emptySet();
         }
         ArraySet<String> groupsWithChildrenLostToStability = new ArraySet<>();
         for (int i = 0; i < shadeList.size(); i++) {
-            final ListEntry tle = shadeList.get(i);
-            final GroupEntry suppressedParent =
+            final PipelineEntry tle = shadeList.get(i);
+            final PipelineEntry suppressedParent =
                     tle.getAttachState().getSuppressedChanges().getParent();
             if (suppressedParent != null) {
                 // This top-level-entry was supposed to be attached to this group,
@@ -892,9 +893,10 @@
      *
      * These groups will be exempt from appearing without any children.
      */
-    private void addGroupsWithChildrenLostToPromotion(List<ListEntry> shadeList, Set<String> out) {
+    private void addGroupsWithChildrenLostToPromotion(List<PipelineEntry> shadeList,
+            Set<String> out) {
         for (int i = 0; i < shadeList.size(); i++) {
-            final ListEntry tle = shadeList.get(i);
+            final PipelineEntry tle = shadeList.get(i);
             if (tle.getAttachState().getPromoter() != null) {
                 // This top-level-entry was part of a group, but was promoted out of it.
                 final String groupKey = tle.getRepresentativeEntry().getSbn().getGroupKey();
@@ -910,7 +912,7 @@
      * These groups will be exempt from appearing without any children.
      */
     private void addGroupsWithChildrenLostToFiltering(Set<String> out) {
-        for (ListEntry tle : mAllEntries) {
+        for (PipelineEntry tle : mAllEntries) {
             StatusBarNotification sbn = tle.getRepresentativeEntry().getSbn();
             if (sbn.isGroup()
                     && !sbn.getNotification().isGroupSummary()
@@ -921,14 +923,14 @@
     }
 
     /**
-     * If a ListEntry was added to the shade list and then later removed (e.g. because it was a
+     * If a PipelineEntry was added to the shade list and then later removed (e.g. because it was a
      * group that was broken up), this method will erase any bookkeeping traces of that addition
      * and/or check that they were already erased.
      *
      * Before calling this method, the entry must already have been removed from its parent. If
      * it's a group, its summary must be null and its children must be empty.
      */
-    private void annulAddition(ListEntry entry, List<ListEntry> shadeList) {
+    private void annulAddition(PipelineEntry entry, List<PipelineEntry> shadeList) {
 
         // This function does very little, but if any of its assumptions are violated (and it has a
         // lot of them), it will put the system into an inconsistent state. So we check all of them
@@ -956,9 +958,11 @@
                 throw new IllegalStateException(
                         "Cannot nullify group " + ge.getKey() + ": still has children");
             }
-        } else if (entry instanceof NotificationEntry) {
-            if (entry == entry.getParent().getSummary()
-                    || entry.getParent().getChildren().contains(entry)) {
+        } else if (entry instanceof NotificationEntry
+                && entry.getParent() instanceof GroupEntry) {
+            GroupEntry parentGroupEntry = (GroupEntry) entry.getParent();
+            if (entry == parentGroupEntry.getSummary()
+                    || parentGroupEntry.getChildren().contains(entry)) {
                 throw new IllegalStateException("Cannot nullify addition of child "
                         + entry.getKey() + ": it's still attached to its parent.");
             }
@@ -973,14 +977,14 @@
      * This can happen if the entry is removed from a group that was broken up or if the entry was
      * filtered out during any of the filtering steps.
      */
-    private void annulAddition(ListEntry entry) {
+    private void annulAddition(PipelineEntry entry) {
         entry.getAttachState().detach();
     }
 
     private void assignSections() {
         Trace.beginSection("ShadeListBuilder.assignSections");
         // Assign sections to top-level elements and their children
-        for (ListEntry entry : mNotifList) {
+        for (PipelineEntry entry : mNotifList) {
             NotifSection section = applySections(entry);
             if (entry instanceof GroupEntry) {
                 GroupEntry parent = (GroupEntry) entry;
@@ -1001,7 +1005,7 @@
     private void sortWithSemiStableSort() {
         // Sort each group's children
         boolean allSorted = true;
-        for (ListEntry entry : mNotifList) {
+        for (PipelineEntry entry : mNotifList) {
             if (entry instanceof GroupEntry) {
                 GroupEntry parent = (GroupEntry) entry;
                 allSorted &= sortGroupChildren(parent.getRawChildren());
@@ -1010,7 +1014,7 @@
         // Sort each section within the top level list
         mNotifList.sort(mTopLevelComparator);
         if (!getStabilityManager().isEveryChangeAllowed()) {
-            for (List<ListEntry> subList : getSectionSubLists(mNotifList)) {
+            for (List<PipelineEntry> subList : getSectionSubLists(mNotifList)) {
                 allSorted &= mSemiStableSort.stabilizeTo(subList, mStableOrder, mNewNotifList);
             }
             applyNewNotifList();
@@ -1022,7 +1026,7 @@
         }
     }
 
-    private Iterable<List<ListEntry>> getSectionSubLists(List<ListEntry> entries) {
+    private Iterable<List<PipelineEntry>> getSectionSubLists(List<PipelineEntry> entries) {
         return ShadeListBuilderHelper.INSTANCE.getSectionSubLists(entries);
     }
 
@@ -1057,12 +1061,12 @@
     /**
      * Assign the index of each notification relative to the total order
      */
-    private void assignIndexes(List<ListEntry> notifList) {
+    private void assignIndexes(List<PipelineEntry> notifList) {
         if (notifList.size() == 0) return;
         NotifSection currentSection = requireNonNull(notifList.get(0).getSection());
         int sectionMemberIndex = 0;
         for (int i = 0; i < notifList.size(); i++) {
-            final ListEntry entry = notifList.get(i);
+            final PipelineEntry entry = notifList.get(i);
             NotifSection section = requireNonNull(entry.getSection());
             if (section.getIndex() != currentSection.getIndex()) {
                 sectionMemberIndex = 0;
@@ -1099,7 +1103,7 @@
         Trace.endSection();
     }
 
-    private void logAttachStateChanges(ListEntry entry) {
+    private void logAttachStateChanges(PipelineEntry entry) {
 
         final ListAttachState curr = entry.getAttachState();
         final ListAttachState prev = entry.getPreviousAttachState();
@@ -1115,8 +1119,8 @@
                 mLogger.logParentChanged(mIterationCount, prev.getParent(), curr.getParent());
             }
 
-            GroupEntry currSuppressedParent = curr.getSuppressedChanges().getParent();
-            GroupEntry prevSuppressedParent = prev.getSuppressedChanges().getParent();
+            PipelineEntry currSuppressedParent = curr.getSuppressedChanges().getParent();
+            PipelineEntry prevSuppressedParent = prev.getSuppressedChanges().getParent();
             if (currSuppressedParent != null && (prevSuppressedParent == null
                     || !prevSuppressedParent.getKey().equals(currSuppressedParent.getKey()))) {
                 mLogger.logParentChangeSuppressedStarted(
@@ -1210,7 +1214,7 @@
 
     @Nullable
     private NotifComparator getSectionComparator(
-            @NonNull ListEntry o1, @NonNull ListEntry o2) {
+            @NonNull PipelineEntry o1, @NonNull PipelineEntry o2) {
         final NotifSection section = o1.getSection();
         if (section != o2.getSection()) {
             throw new RuntimeException("Entry ordering should only be done within sections");
@@ -1221,7 +1225,7 @@
         return null;
     }
 
-    private final Comparator<ListEntry> mTopLevelComparator = (o1, o2) -> {
+    private final Comparator<PipelineEntry> mTopLevelComparator = (o1, o2) -> {
         int cmp = Integer.compare(
                 o1.getSectionIndex(),
                 o2.getSectionIndex());
@@ -1264,7 +1268,7 @@
     };
 
     @Nullable
-    private Integer getStableOrderRank(ListEntry entry) {
+    private Integer getStableOrderRank(PipelineEntry entry) {
         if (getStabilityManager().isEntryReorderingAllowed(entry)) {
             // let the stability manager constrain or allow reordering
             return null;
@@ -1323,7 +1327,7 @@
         return null;
     }
 
-    private NotifSection applySections(ListEntry entry) {
+    private NotifSection applySections(PipelineEntry entry) {
         final NotifSection newSection = findSection(entry);
         final ListAttachState prevAttachState = entry.getPreviousAttachState();
 
@@ -1346,7 +1350,7 @@
         return finalSection;
     }
 
-    private void setEntrySection(ListEntry entry, NotifSection finalSection) {
+    private void setEntrySection(PipelineEntry entry, NotifSection finalSection) {
         entry.getAttachState().setSection(finalSection);
         NotificationEntry representativeEntry = entry.getRepresentativeEntry();
         if (representativeEntry != null) {
@@ -1358,7 +1362,7 @@
     }
 
     @NonNull
-    private NotifSection findSection(ListEntry entry) {
+    private NotifSection findSection(PipelineEntry entry) {
         for (int i = 0; i < mNotifSections.size(); i++) {
             NotifSection section = mNotifSections.get(i);
             if (section.getSectioner().isInSection(entry)) {
@@ -1428,10 +1432,10 @@
         mChoreographer.schedule();
     }
 
-    private static int countChildren(List<ListEntry> entries) {
+    private static int countChildren(List<PipelineEntry> entries) {
         int count = 0;
         for (int i = 0; i < entries.size(); i++) {
-            final ListEntry entry = entries.get(i);
+            final PipelineEntry entry = entries.get(i);
             if (entry instanceof GroupEntry) {
                 count += ((GroupEntry) entry).getChildren().size();
             }
@@ -1439,7 +1443,7 @@
         return count;
     }
 
-    private void dispatchOnBeforeTransformGroups(List<ListEntry> entries) {
+    private void dispatchOnBeforeTransformGroups(List<PipelineEntry> entries) {
         Trace.beginSection("ShadeListBuilder.dispatchOnBeforeTransformGroups");
         mOnBeforeTransformGroupsListeners.forEachTraced(listener -> {
             listener.onBeforeTransformGroups(entries);
@@ -1447,7 +1451,7 @@
         Trace.endSection();
     }
 
-    private void dispatchOnBeforeSort(List<ListEntry> entries) {
+    private void dispatchOnBeforeSort(List<PipelineEntry> entries) {
         Trace.beginSection("ShadeListBuilder.dispatchOnBeforeSort");
         mOnBeforeSortListeners.forEachTraced(listener -> {
             listener.onBeforeSort(entries);
@@ -1455,7 +1459,7 @@
         Trace.endSection();
     }
 
-    private void dispatchOnBeforeFinalizeFilter(List<ListEntry> entries) {
+    private void dispatchOnBeforeFinalizeFilter(List<PipelineEntry> entries) {
         Trace.beginSection("ShadeListBuilder.dispatchOnBeforeFinalizeFilter");
         mOnBeforeFinalizeFilterListeners.forEachTraced(listener -> {
             listener.onBeforeFinalizeFilter(entries);
@@ -1463,7 +1467,7 @@
         Trace.endSection();
     }
 
-    private void dispatchOnBeforeRenderList(List<ListEntry> entries) {
+    private void dispatchOnBeforeRenderList(List<PipelineEntry> entries) {
         Trace.beginSection("ShadeListBuilder.dispatchOnBeforeRenderList");
         mOnBeforeRenderListListeners.forEachTraced(listener -> {
             listener.onBeforeRenderList(entries);
@@ -1508,13 +1512,13 @@
          * @param entries A read-only view into the current notif list. Note that this list is
          *                backed by the live list and will change in response to new pipeline runs.
          */
-        void onRenderList(@NonNull List<ListEntry> entries);
+        void onRenderList(@NonNull List<PipelineEntry> entries);
     }
 
     private static final NotifSectioner DEFAULT_SECTIONER = new NotifSectioner("UnknownSection",
             NotificationPriorityBucketKt.BUCKET_UNKNOWN) {
         @Override
-        public boolean isInSection(ListEntry entry) {
+        public boolean isInSection(PipelineEntry entry) {
             return true;
         }
     };
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/SuppressedAttachState.kt b/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/SuppressedAttachState.kt
index 584563b..c942954 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/SuppressedAttachState.kt
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/SuppressedAttachState.kt
@@ -19,7 +19,7 @@
 import com.android.systemui.statusbar.notification.collection.listbuilder.NotifSection
 
 /**
- * Stores the suppressed state that [ShadeListBuilder] assigned to this [ListEntry] before the
+ * Stores the suppressed state that [ShadeListBuilder] assigned to this [PipelineEntry] before the
  * VisualStabilityManager suppressed group and section changes.
  */
 data class SuppressedAttachState private constructor(
@@ -35,7 +35,7 @@
      *  - Root if suppressing group change to top-level
      *  - GroupEntry if suppressing group change to a different group
      */
-    var parent: GroupEntry?,
+    var parent: PipelineEntry?,
 
     /**
      * Whether the ListEntry would have been pruned had its group change not been suppressed.
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/coordinator/BundleCoordinator.kt b/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/coordinator/BundleCoordinator.kt
index 244c594..e6d5f41 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/coordinator/BundleCoordinator.kt
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/coordinator/BundleCoordinator.kt
@@ -20,7 +20,7 @@
 import android.app.NotificationChannel.PROMOTIONS_ID
 import android.app.NotificationChannel.RECS_ID
 import android.app.NotificationChannel.SOCIAL_MEDIA_ID
-import com.android.systemui.statusbar.notification.collection.ListEntry
+import com.android.systemui.statusbar.notification.collection.PipelineEntry
 import com.android.systemui.statusbar.notification.collection.NotifPipeline
 import com.android.systemui.statusbar.notification.collection.coordinator.dagger.CoordinatorScope
 import com.android.systemui.statusbar.notification.collection.listbuilder.pluggable.NotifSectioner
@@ -48,7 +48,7 @@
 
     val newsSectioner =
             object : NotifSectioner("News", BUCKET_NEWS) {
-                override fun isInSection(entry: ListEntry): Boolean {
+                override fun isInSection(entry: PipelineEntry): Boolean {
                     return entry.representativeEntry?.channel?.id == NEWS_ID
                 }
 
@@ -59,7 +59,7 @@
 
     val socialSectioner =
         object : NotifSectioner("Social", BUCKET_SOCIAL) {
-            override fun isInSection(entry: ListEntry): Boolean {
+            override fun isInSection(entry: PipelineEntry): Boolean {
                 return entry.representativeEntry?.channel?.id == SOCIAL_MEDIA_ID
             }
 
@@ -70,7 +70,7 @@
 
     val recsSectioner =
         object : NotifSectioner("Recommendations", BUCKET_RECS) {
-            override fun isInSection(entry: ListEntry): Boolean {
+            override fun isInSection(entry: PipelineEntry): Boolean {
                 return entry.representativeEntry?.channel?.id == RECS_ID
             }
 
@@ -81,7 +81,7 @@
 
     val promoSectioner =
         object : NotifSectioner("Promotions", BUCKET_PROMO) {
-            override fun isInSection(entry: ListEntry): Boolean {
+            override fun isInSection(entry: PipelineEntry): Boolean {
                 return entry.representativeEntry?.channel?.id == PROMOTIONS_ID
             }
 
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/coordinator/ColorizedFgsCoordinator.java b/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/coordinator/ColorizedFgsCoordinator.java
index 9df4bf4..afba85b 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/coordinator/ColorizedFgsCoordinator.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/coordinator/ColorizedFgsCoordinator.java
@@ -25,6 +25,7 @@
 
 import com.android.systemui.dagger.qualifiers.Application;
 import com.android.systemui.statusbar.notification.collection.ListEntry;
+import com.android.systemui.statusbar.notification.collection.PipelineEntry;
 import com.android.systemui.statusbar.notification.collection.NotifPipeline;
 import com.android.systemui.statusbar.notification.collection.NotificationEntry;
 import com.android.systemui.statusbar.notification.collection.coordinator.dagger.CoordinatorScope;
@@ -96,7 +97,7 @@
     private final NotifSectioner mNotifSectioner = new NotifSectioner("ColorizedSectioner",
             NotificationPriorityBucketKt.BUCKET_FOREGROUND_SERVICE) {
         @Override
-        public boolean isInSection(ListEntry entry) {
+        public boolean isInSection(PipelineEntry entry) {
             NotificationEntry notificationEntry = entry.getRepresentativeEntry();
             if (notificationEntry != null) {
                 return isRichOngoing(notificationEntry);
@@ -117,7 +118,7 @@
         private final NotifComparator mOngoingComparator = new NotifComparator(
                 "OngoingComparator") {
             @Override
-            public int compare(@NonNull ListEntry o1, @NonNull ListEntry o2) {
+            public int compare(@NonNull PipelineEntry o1, @NonNull PipelineEntry o2) {
                 return Integer.compare(
                         getSortKey(o1.getRepresentativeEntry()),
                         getSortKey(o2.getRepresentativeEntry())
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/coordinator/ConversationCoordinator.kt b/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/coordinator/ConversationCoordinator.kt
index af2c197..248b528 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/coordinator/ConversationCoordinator.kt
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/coordinator/ConversationCoordinator.kt
@@ -16,7 +16,8 @@
 
 package com.android.systemui.statusbar.notification.collection.coordinator
 
-import com.android.systemui.statusbar.notification.collection.ListEntry
+import com.android.systemui.statusbar.notification.collection.GroupEntry
+import com.android.systemui.statusbar.notification.collection.PipelineEntry
 import com.android.systemui.statusbar.notification.collection.NotifPipeline
 import com.android.systemui.statusbar.notification.collection.NotificationEntry
 import com.android.systemui.statusbar.notification.collection.SortBySectionTimeFlag
@@ -61,6 +62,7 @@
                         originalGroup == null -> null
                         originalGroup == promoted.parent -> null
                         originalGroup.parent == null -> null
+                        originalGroup !is GroupEntry -> summary.key
                         originalGroup.summary != summary -> null
                         originalGroup.children.any { it.channel == summary.channel } -> null
                         else -> summary.key
@@ -74,7 +76,7 @@
         override fun shouldPromoteToTopLevel(entry: NotificationEntry): Boolean {
             val shouldPromote = entry.channel?.isImportantConversation == true
             if (shouldPromote) {
-                val summary = entry.parent?.summary
+                val summary = (entry.parent as? GroupEntry)?.summary
                 if (summary != null && entry.channel == summary.channel) {
                     promotedEntriesToSummaryOfSameChannel[entry] = summary
                 }
@@ -85,14 +87,14 @@
 
     val priorityPeopleSectioner =
             object : NotifSectioner("Priority People", BUCKET_PRIORITY_PEOPLE) {
-                override fun isInSection(entry: ListEntry): Boolean {
+                override fun isInSection(entry: PipelineEntry): Boolean {
                     return getPeopleType(entry) == TYPE_IMPORTANT_PERSON
                 }
             }
 
     // TODO(b/330193582): Rename to just "People"
     val peopleAlertingSectioner = object : NotifSectioner("People(alerting)", BUCKET_PEOPLE) {
-        override fun isInSection(entry: ListEntry): Boolean  {
+        override fun isInSection(entry: PipelineEntry): Boolean  {
             if (SortBySectionTimeFlag.isEnabled) {
                 return highPriorityProvider.isHighPriorityConversation(entry)
                         || isConversation(entry)
@@ -111,7 +113,7 @@
     val peopleSilentSectioner = object : NotifSectioner("People(silent)", BUCKET_PEOPLE) {
         // Because the peopleAlertingSectioner is above this one, it will claim all conversations that are alerting.
         // All remaining conversations must be silent.
-        override fun isInSection(entry: ListEntry): Boolean {
+        override fun isInSection(entry: PipelineEntry): Boolean {
             SortBySectionTimeFlag.assertInLegacyMode()
             return isConversation(entry)
         }
@@ -132,17 +134,17 @@
         pipeline.addOnBeforeRenderListListener(onBeforeRenderListListener)
     }
 
-    private fun isConversation(entry: ListEntry): Boolean =
+    private fun isConversation(entry: PipelineEntry): Boolean =
             getPeopleType(entry) != TYPE_NON_PERSON
 
     @PeopleNotificationType
-    private fun getPeopleType(entry: ListEntry): Int =
+    private fun getPeopleType(entry: PipelineEntry): Int =
             entry.representativeEntry?.let {
                 peopleNotificationIdentifier.getPeopleNotificationType(it)
             } ?: TYPE_NON_PERSON
 
     private val notifComparator: NotifComparator = object : NotifComparator("People") {
-        override fun compare(entry1: ListEntry, entry2: ListEntry): Int {
+        override fun compare(entry1: PipelineEntry, entry2: PipelineEntry): Int {
             val type1 = getPeopleType(entry1)
             val type2 = getPeopleType(entry2)
             return type2.compareTo(type1)
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/coordinator/DataStoreCoordinator.kt b/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/coordinator/DataStoreCoordinator.kt
index 034a4fd..e4ec76c 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/coordinator/DataStoreCoordinator.kt
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/coordinator/DataStoreCoordinator.kt
@@ -17,7 +17,7 @@
 package com.android.systemui.statusbar.notification.collection.coordinator
 
 import com.android.systemui.statusbar.notification.collection.GroupEntry
-import com.android.systemui.statusbar.notification.collection.ListEntry
+import com.android.systemui.statusbar.notification.collection.PipelineEntry
 import com.android.systemui.statusbar.notification.collection.NotifLiveDataStoreImpl
 import com.android.systemui.statusbar.notification.collection.NotifPipeline
 import com.android.systemui.statusbar.notification.collection.NotificationEntry
@@ -43,12 +43,12 @@
         d.dump("notifLiveDataStoreImpl", notifLiveDataStoreImpl)
     }
 
-    private fun onAfterRenderList(entries: List<ListEntry>) {
+    private fun onAfterRenderList(entries: List<PipelineEntry>) {
         val flatEntryList = flattenedEntryList(entries)
         notifLiveDataStoreImpl.setActiveNotifList(flatEntryList)
     }
 
-    private fun flattenedEntryList(entries: List<ListEntry>) =
+    private fun flattenedEntryList(entries: List<PipelineEntry>) =
         mutableListOf<NotificationEntry>().also { list ->
             entries.forEach { entry ->
                 when (entry) {
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/coordinator/DismissibilityCoordinator.kt b/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/coordinator/DismissibilityCoordinator.kt
index 0a9dddc..6bed0cf 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/coordinator/DismissibilityCoordinator.kt
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/coordinator/DismissibilityCoordinator.kt
@@ -17,7 +17,7 @@
 package com.android.systemui.statusbar.notification.collection.coordinator
 
 import com.android.systemui.statusbar.notification.collection.GroupEntry
-import com.android.systemui.statusbar.notification.collection.ListEntry
+import com.android.systemui.statusbar.notification.collection.PipelineEntry
 import com.android.systemui.statusbar.notification.collection.NotifPipeline
 import com.android.systemui.statusbar.notification.collection.coordinator.dagger.CoordinatorScope
 import com.android.systemui.statusbar.notification.collection.provider.NotificationDismissibilityProviderImpl
@@ -37,7 +37,7 @@
         pipeline.addOnBeforeRenderListListener(::onBeforeRenderListListener)
     }
 
-    private fun onBeforeRenderListListener(entries: List<ListEntry>) {
+    private fun onBeforeRenderListListener(entries: List<PipelineEntry>) {
         val isLocked = !keyguardStateController.isUnlocked
         val nonDismissableEntryKeys = mutableSetOf<String>()
         markNonDismissibleEntries(nonDismissableEntryKeys, entries, isLocked)
@@ -54,7 +54,7 @@
      */
     private fun markNonDismissibleEntries(
         markedKeys: MutableSet<String>,
-        entries: List<ListEntry>,
+        entries: List<PipelineEntry>,
         isLocked: Boolean
     ): Boolean {
         var anyNonDismissableEntries = false
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/coordinator/GroupCountCoordinator.kt b/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/coordinator/GroupCountCoordinator.kt
index 02bf3b3..2f0701f 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/coordinator/GroupCountCoordinator.kt
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/coordinator/GroupCountCoordinator.kt
@@ -18,7 +18,7 @@
 
 import android.util.ArrayMap
 import com.android.systemui.statusbar.notification.collection.GroupEntry
-import com.android.systemui.statusbar.notification.collection.ListEntry
+import com.android.systemui.statusbar.notification.collection.PipelineEntry
 import com.android.systemui.statusbar.notification.collection.NotifPipeline
 import com.android.systemui.statusbar.notification.collection.coordinator.dagger.CoordinatorScope
 import com.android.systemui.statusbar.notification.collection.render.NotifGroupController
@@ -34,7 +34,7 @@
         pipeline.addOnAfterRenderGroupListener(::onAfterRenderGroup)
     }
 
-    private fun onBeforeFinalizeFilter(entries: List<ListEntry>) {
+    private fun onBeforeFinalizeFilter(entries: List<PipelineEntry>) {
         // save untruncated child counts to our internal map
         untruncatedChildCounts.clear()
         entries.asSequence().filterIsInstance<GroupEntry>().forEach { groupEntry ->
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/coordinator/GroupWhenCoordinator.kt b/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/coordinator/GroupWhenCoordinator.kt
index f253100..d0411b5 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/coordinator/GroupWhenCoordinator.kt
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/coordinator/GroupWhenCoordinator.kt
@@ -18,7 +18,7 @@
 import android.util.ArrayMap
 import com.android.systemui.dagger.qualifiers.Main
 import com.android.systemui.statusbar.notification.collection.GroupEntry
-import com.android.systemui.statusbar.notification.collection.ListEntry
+import com.android.systemui.statusbar.notification.collection.PipelineEntry
 import com.android.systemui.statusbar.notification.collection.NotifPipeline
 import com.android.systemui.statusbar.notification.collection.coordinator.dagger.CoordinatorScope
 import com.android.systemui.statusbar.notification.collection.listbuilder.pluggable.Invalidator
@@ -52,7 +52,7 @@
         pipeline.addPreRenderInvalidator(invalidator)
     }
 
-    private fun onBeforeFinalizeFilterListener(entries: List<ListEntry>) {
+    private fun onBeforeFinalizeFilterListener(entries: List<PipelineEntry>) {
         cancelListInvalidation()
         notificationGroupTimes.clear()
 
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/coordinator/GutsCoordinator.kt b/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/coordinator/GutsCoordinator.kt
index b200136..9045aac 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/coordinator/GutsCoordinator.kt
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/coordinator/GutsCoordinator.kt
@@ -18,7 +18,7 @@
 import android.util.ArraySet
 import com.android.systemui.Dumpable
 import com.android.systemui.dump.DumpManager
-import com.android.systemui.statusbar.notification.collection.ListEntry
+import com.android.systemui.statusbar.notification.collection.PipelineEntry
 import com.android.systemui.statusbar.notification.collection.NotifPipeline
 import com.android.systemui.statusbar.notification.collection.NotificationEntry
 import com.android.systemui.statusbar.notification.collection.coordinator.dagger.CoordinatorScope
@@ -113,7 +113,7 @@
         }
     }
 
-    private fun isCurrentlyShowingGuts(entry: ListEntry) =
+    private fun isCurrentlyShowingGuts(entry: PipelineEntry) =
             notifsWithOpenGuts.contains(entry.key)
 
     private fun closeGutsAndEndLifetimeExtension(entry: NotificationEntry) {
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/coordinator/HeadsUpCoordinator.kt b/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/coordinator/HeadsUpCoordinator.kt
index eb5a370..611e0ef 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/coordinator/HeadsUpCoordinator.kt
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/coordinator/HeadsUpCoordinator.kt
@@ -27,7 +27,7 @@
 import com.android.systemui.statusbar.chips.notification.shared.StatusBarNotifChips
 import com.android.systemui.statusbar.notification.NotifPipelineFlags
 import com.android.systemui.statusbar.notification.collection.GroupEntry
-import com.android.systemui.statusbar.notification.collection.ListEntry
+import com.android.systemui.statusbar.notification.collection.PipelineEntry
 import com.android.systemui.statusbar.notification.collection.NotifCollection
 import com.android.systemui.statusbar.notification.collection.NotifPipeline
 import com.android.systemui.statusbar.notification.collection.NotificationEntry
@@ -103,7 +103,7 @@
         mNotifPipeline = pipeline
         mHeadsUpManager.addListener(mOnHeadsUpChangedListener)
         pipeline.addCollectionListener(mNotifCollectionListener)
-        pipeline.addOnBeforeTransformGroupsListener(::onBeforeTransformGroups)
+        pipeline.addOnBeforeTransformGroupsListener { onBeforeTransformGroups() }
         pipeline.addOnBeforeFinalizeFilterListener(::onBeforeFinalizeFilter)
         pipeline.addPromoter(mNotifPromoter)
         pipeline.addNotificationLifetimeExtender(mLifetimeExtender)
@@ -170,7 +170,7 @@
      * Once the pipeline starts running, we can look through posted entries and quickly process any
      * that don't have groups, and thus will never gave a group heads up edge case.
      */
-    fun onBeforeTransformGroups(list: List<ListEntry>) {
+    fun onBeforeTransformGroups() {
         mNow = mSystemClock.currentTimeMillis()
         if (mPostedEntries.isEmpty()) {
             return
@@ -191,7 +191,7 @@
      * we know that stability and [NotifPromoter]s have been applied, so we can use the location of
      * notifications in this list to determine what kind of group heads up behavior should happen.
      */
-    fun onBeforeFinalizeFilter(list: List<ListEntry>) =
+    fun onBeforeFinalizeFilter(list: List<PipelineEntry>) =
         mHeadsUpManager.modifyHuns { hunMutator ->
             // Nothing to do if there are no other adds/updates
             if (mPostedEntries.isEmpty()) {
@@ -410,7 +410,7 @@
             )
             .firstOrNull()
 
-    private fun getGroupLocationsByKey(list: List<ListEntry>): Map<String, GroupLocation> =
+    private fun getGroupLocationsByKey(list: List<PipelineEntry>): Map<String, GroupLocation> =
         mutableMapOf<String, GroupLocation>().also { map ->
             list.forEach { topLevelEntry ->
                 when (topLevelEntry) {
@@ -833,13 +833,13 @@
 
     val sectioner =
         object : NotifSectioner("HeadsUp", BUCKET_HEADS_UP) {
-            override fun isInSection(entry: ListEntry): Boolean =
+            override fun isInSection(entry: PipelineEntry): Boolean =
                 // TODO: This check won't notice if a child of the group is going to HUN...
                 isGoingToShowHunNoRetract(entry)
 
             override fun getComparator(): NotifComparator {
                 return object : NotifComparator("HeadsUp") {
-                    override fun compare(o1: ListEntry, o2: ListEntry): Int =
+                    override fun compare(o1: PipelineEntry, o2: PipelineEntry): Int =
                         mHeadsUpManager.compare(o1.representativeEntry, o2.representativeEntry)
                 }
             }
@@ -867,7 +867,7 @@
 
     private fun isSticky(entry: NotificationEntry) = mHeadsUpManager.isSticky(entry.key)
 
-    private fun isEntryBinding(entry: ListEntry): Boolean {
+    private fun isEntryBinding(entry: PipelineEntry): Boolean {
         val bindingUntil = mEntriesBindingUntil[entry.key]
         return bindingUntil != null && bindingUntil >= mNow
     }
@@ -875,12 +875,12 @@
     /**
      * Whether the notification is already heads up or binding so that it can imminently heads up
      */
-    private fun isAttemptingToShowHun(entry: ListEntry) =
+    private fun isAttemptingToShowHun(entry: PipelineEntry) =
         mHeadsUpManager.isHeadsUpEntry(entry.key) ||
             isEntryBinding(entry) ||
             isHeadsUpAnimatingAway(entry)
 
-    private fun isHeadsUpAnimatingAway(entry: ListEntry): Boolean {
+    private fun isHeadsUpAnimatingAway(entry: PipelineEntry): Boolean {
         if (!GroupHunAnimationFix.isEnabled) return false
         return entry.representativeEntry?.row?.isHeadsUpAnimatingAway ?: false
     }
@@ -891,7 +891,7 @@
      * returns `true` even if the update would (in isolation of its group) cause the heads up to be
      * retracted. This is important for not retracting transferred group heads ups.
      */
-    private fun isGoingToShowHunNoRetract(entry: ListEntry) =
+    private fun isGoingToShowHunNoRetract(entry: PipelineEntry) =
         mPostedEntries[entry.key]?.calculateShouldBeHeadsUpNoRetract ?: isAttemptingToShowHun(entry)
 
     /**
@@ -900,7 +900,7 @@
      * strict because any update which would revoke the heads up supersedes the current heads
      * up/binding state.
      */
-    private fun isGoingToShowHunStrict(entry: ListEntry) =
+    private fun isGoingToShowHunStrict(entry: PipelineEntry) =
         mPostedEntries[entry.key]?.calculateShouldBeHeadsUpStrict ?: isAttemptingToShowHun(entry)
 
     private fun endNotifLifetimeExtensionIfExtended(entry: NotificationEntry) {
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/coordinator/LockScreenMinimalismCoordinator.kt b/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/coordinator/LockScreenMinimalismCoordinator.kt
index e232849..56deb18 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/coordinator/LockScreenMinimalismCoordinator.kt
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/coordinator/LockScreenMinimalismCoordinator.kt
@@ -27,7 +27,7 @@
 import com.android.systemui.shade.domain.interactor.ShadeInteractor
 import com.android.systemui.statusbar.StatusBarState
 import com.android.systemui.statusbar.notification.collection.GroupEntry
-import com.android.systemui.statusbar.notification.collection.ListEntry
+import com.android.systemui.statusbar.notification.collection.PipelineEntry
 import com.android.systemui.statusbar.notification.collection.NotifPipeline
 import com.android.systemui.statusbar.notification.collection.NotificationEntry
 import com.android.systemui.statusbar.notification.collection.coordinator.dagger.CoordinatorScope
@@ -176,7 +176,7 @@
             }
         }
 
-    private fun pickOutTopUnseenNotifs(list: List<ListEntry>) {
+    private fun pickOutTopUnseenNotifs(list: List<PipelineEntry>) {
         if (NotificationMinimalism.isUnexpectedlyInLegacyMode()) return
         if (!minimalismEnabled) return
         // Only ever elevate a top unseen notification on keyguard, not even locked shade
@@ -224,7 +224,7 @@
 
     val topOngoingSectioner =
         object : NotifSectioner("TopOngoing", BUCKET_TOP_ONGOING) {
-            override fun isInSection(entry: ListEntry): Boolean {
+            override fun isInSection(entry: PipelineEntry): Boolean {
                 if (NotificationMinimalism.isUnexpectedlyInLegacyMode()) return false
                 if (!minimalismEnabled) return false
                 return entry.anyEntry { notificationEntry ->
@@ -235,7 +235,7 @@
 
     val topUnseenSectioner =
         object : NotifSectioner("TopUnseen", BUCKET_TOP_UNSEEN) {
-            override fun isInSection(entry: ListEntry): Boolean {
+            override fun isInSection(entry: PipelineEntry): Boolean {
                 if (NotificationMinimalism.isUnexpectedlyInLegacyMode()) return false
                 if (!minimalismEnabled) return false
                 return entry.anyEntry { notificationEntry ->
@@ -244,7 +244,7 @@
             }
         }
 
-    private fun ListEntry.anyEntry(predicate: (NotificationEntry?) -> Boolean) =
+    private fun PipelineEntry.anyEntry(predicate: (NotificationEntry?) -> Boolean) =
         when {
             predicate(representativeEntry) -> true
             this !is GroupEntry -> false
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/coordinator/OriginalUnseenKeyguardCoordinator.kt b/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/coordinator/OriginalUnseenKeyguardCoordinator.kt
index de6f257..660ee40 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/coordinator/OriginalUnseenKeyguardCoordinator.kt
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/coordinator/OriginalUnseenKeyguardCoordinator.kt
@@ -30,6 +30,7 @@
 import com.android.systemui.scene.shared.flag.SceneContainerFlag
 import com.android.systemui.scene.shared.model.Scenes
 import com.android.systemui.statusbar.expansionChanges
+import com.android.systemui.statusbar.notification.collection.GroupEntry
 import com.android.systemui.statusbar.notification.collection.NotifPipeline
 import com.android.systemui.statusbar.notification.collection.NotificationEntry
 import com.android.systemui.statusbar.notification.collection.coordinator.dagger.CoordinatorScope
@@ -313,7 +314,7 @@
                     unseenNotifications.contains(entry) -> false
                     // Don't apply the filter to (non-promoted) group summaries
                     //  - summary will be pruned if necessary, depending on if children are filtered
-                    entry.parent?.summary == entry -> false
+                    (entry.parent as? GroupEntry)?.summary == entry -> false
                     // Check that the entry satisfies certain characteristics that would bypass the
                     // filter
                     shouldIgnoreUnseenCheck(entry) -> false
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/coordinator/PreparationCoordinator.java b/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/coordinator/PreparationCoordinator.java
index 2ecce1f..20c6736 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/coordinator/PreparationCoordinator.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/coordinator/PreparationCoordinator.java
@@ -35,7 +35,7 @@
 import com.android.internal.annotations.VisibleForTesting;
 import com.android.internal.statusbar.IStatusBarService;
 import com.android.systemui.statusbar.notification.collection.GroupEntry;
-import com.android.systemui.statusbar.notification.collection.ListEntry;
+import com.android.systemui.statusbar.notification.collection.PipelineEntry;
 import com.android.systemui.statusbar.notification.collection.NotifPipeline;
 import com.android.systemui.statusbar.notification.collection.NotificationEntry;
 import com.android.systemui.statusbar.notification.collection.ShadeListBuilder;
@@ -232,9 +232,10 @@
          */
         @Override
         public boolean shouldFilterOut(NotificationEntry entry, long now) {
-            final GroupEntry parent = requireNonNull(entry.getParent());
-            Boolean isMemberOfDelayedGroup = mIsDelayedGroupCache.get(parent);
-            if (isMemberOfDelayedGroup == null) {
+            final PipelineEntry pipelineEntryParent = requireNonNull(entry.getParent());
+            Boolean isMemberOfDelayedGroup = mIsDelayedGroupCache.get(pipelineEntryParent);
+            if (isMemberOfDelayedGroup == null && pipelineEntryParent instanceof GroupEntry) {
+                GroupEntry parent = (GroupEntry) pipelineEntryParent;
                 isMemberOfDelayedGroup = shouldWaitForGroupToInflate(parent, now);
                 mIsDelayedGroupCache.put(parent, isMemberOfDelayedGroup);
             }
@@ -279,7 +280,7 @@
         }
     };
 
-    private void purgeCaches(Collection<ListEntry> entries) {
+    private void purgeCaches(Collection<PipelineEntry> entries) {
         Set<String> wantedPackages = getPackages(entries);
         mAppIconProvider.purgeCache(wantedPackages);
         mNotificationIconStyleProvider.purgeCache(wantedPackages);
@@ -288,9 +289,9 @@
     /**
      * Get all app packages present in {@param entries}.
      */
-    private static @NonNull Set<String> getPackages(Collection<ListEntry> entries) {
+    private static @NonNull Set<String> getPackages(Collection<PipelineEntry> entries) {
         Set<String> packages = new HashSet<>();
-        for (ListEntry entry : entries) {
+        for (PipelineEntry entry : entries) {
             NotificationEntry notificationEntry = entry.getRepresentativeEntry();
             if (notificationEntry == null) {
                 Log.wtf(TAG, "notification entry " + entry.getKey()
@@ -302,9 +303,9 @@
         return packages;
     }
 
-    private void inflateAllRequiredViews(List<ListEntry> entries) {
+    private void inflateAllRequiredViews(List<PipelineEntry> entries) {
         for (int i = 0, size = entries.size(); i < size; i++) {
-            ListEntry entry = entries.get(i);
+            PipelineEntry entry = entries.get(i);
             if (entry instanceof GroupEntry) {
                 GroupEntry groupEntry = (GroupEntry) entry;
                 inflateRequiredGroupViews(groupEntry);
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/coordinator/RankingCoordinator.java b/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/coordinator/RankingCoordinator.java
index 9226450..d1063d9 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/coordinator/RankingCoordinator.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/coordinator/RankingCoordinator.java
@@ -20,7 +20,7 @@
 import android.annotation.Nullable;
 
 import com.android.systemui.plugins.statusbar.StatusBarStateController;
-import com.android.systemui.statusbar.notification.collection.ListEntry;
+import com.android.systemui.statusbar.notification.collection.PipelineEntry;
 import com.android.systemui.statusbar.notification.collection.NotifPipeline;
 import com.android.systemui.statusbar.notification.collection.NotificationEntry;
 import com.android.systemui.statusbar.notification.collection.coordinator.dagger.CoordinatorScope;
@@ -97,7 +97,7 @@
     private final NotifSectioner mAlertingNotifSectioner = new NotifSectioner("Alerting",
             NotificationPriorityBucketKt.BUCKET_ALERTING) {
         @Override
-        public boolean isInSection(ListEntry entry) {
+        public boolean isInSection(PipelineEntry entry) {
             return mHighPriorityProvider.isHighPriority(entry);
         }
 
@@ -115,7 +115,7 @@
     private final NotifSectioner mSilentNotifSectioner = new NotifSectioner("Silent",
             NotificationPriorityBucketKt.BUCKET_SILENT) {
         @Override
-        public boolean isInSection(ListEntry entry) {
+        public boolean isInSection(PipelineEntry entry) {
             return !mHighPriorityProvider.isHighPriority(entry)
                     && entry.getRepresentativeEntry() != null
                     && !entry.getRepresentativeEntry().isAmbient();
@@ -129,7 +129,7 @@
 
         @Nullable
         @Override
-        public void onEntriesUpdated(@NonNull List<ListEntry> entries) {
+        public void onEntriesUpdated(@NonNull List<PipelineEntry> entries) {
             mHasSilentEntries = false;
             for (int i = 0; i < entries.size(); i++) {
                 if (entries.get(i).getRepresentativeEntry().getSbn().isClearable()) {
@@ -145,7 +145,7 @@
     private final NotifSectioner mMinimizedNotifSectioner = new NotifSectioner("Minimized",
             NotificationPriorityBucketKt.BUCKET_SILENT) {
         @Override
-        public boolean isInSection(ListEntry entry) {
+        public boolean isInSection(PipelineEntry entry) {
             return !mHighPriorityProvider.isHighPriority(entry)
                     && entry.getRepresentativeEntry().isAmbient();
         }
@@ -158,7 +158,7 @@
 
         @Nullable
         @Override
-        public void onEntriesUpdated(@NonNull List<ListEntry> entries) {
+        public void onEntriesUpdated(@NonNull List<PipelineEntry> entries) {
             mHasMinimizedEntries = false;
             for (int i = 0; i < entries.size(); i++) {
                 if (entries.get(i).getRepresentativeEntry().getSbn().isClearable()) {
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/coordinator/RowAlertTimeCoordinator.kt b/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/coordinator/RowAlertTimeCoordinator.kt
index 4a7b7ca..930e52a 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/coordinator/RowAlertTimeCoordinator.kt
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/coordinator/RowAlertTimeCoordinator.kt
@@ -17,7 +17,7 @@
 
 import android.util.ArrayMap
 import com.android.systemui.statusbar.notification.collection.GroupEntry
-import com.android.systemui.statusbar.notification.collection.ListEntry
+import com.android.systemui.statusbar.notification.collection.PipelineEntry
 import com.android.systemui.statusbar.notification.collection.NotifPipeline
 import com.android.systemui.statusbar.notification.collection.NotificationEntry
 import com.android.systemui.statusbar.notification.collection.coordinator.dagger.CoordinatorScope
@@ -39,7 +39,7 @@
         pipeline.addOnAfterRenderEntryListener(::onAfterRenderEntry)
     }
 
-    private fun onBeforeFinalizeFilterListener(entries: List<ListEntry>) {
+    private fun onBeforeFinalizeFilterListener(entries: List<PipelineEntry>) {
         latestAlertTimeBySummary.clear()
         entries.asSequence().filterIsInstance<GroupEntry>().forEach { groupEntry ->
             val summary = checkNotNull(groupEntry.summary)
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/coordinator/RowAppearanceCoordinator.kt b/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/coordinator/RowAppearanceCoordinator.kt
index df8e56e..a987c54 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/coordinator/RowAppearanceCoordinator.kt
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/coordinator/RowAppearanceCoordinator.kt
@@ -20,7 +20,7 @@
 import com.android.systemui.res.R
 import com.android.systemui.shade.ShadeDisplayAware
 import com.android.systemui.statusbar.notification.AssistantFeedbackController
-import com.android.systemui.statusbar.notification.collection.ListEntry
+import com.android.systemui.statusbar.notification.collection.PipelineEntry
 import com.android.systemui.statusbar.notification.collection.NotifPipeline
 import com.android.systemui.statusbar.notification.collection.NotificationEntry
 import com.android.systemui.statusbar.notification.collection.coordinator.dagger.CoordinatorScope
@@ -63,7 +63,7 @@
         pipeline.addOnAfterRenderEntryListener(::onAfterRenderEntry)
     }
 
-    private fun onBeforeRenderList(list: List<ListEntry>) {
+    private fun onBeforeRenderList(list: List<PipelineEntry>) {
         entryToExpand =
             list.firstOrNull()?.representativeEntry?.takeIf { entry ->
                 !mSectionStyleProvider.isMinimizedSection(entry.section!!)
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/coordinator/SensitiveContentCoordinator.kt b/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/coordinator/SensitiveContentCoordinator.kt
index 90916d1..db24e7d 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/coordinator/SensitiveContentCoordinator.kt
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/coordinator/SensitiveContentCoordinator.kt
@@ -32,7 +32,7 @@
 import com.android.systemui.statusbar.StatusBarState
 import com.android.systemui.statusbar.notification.DynamicPrivacyController
 import com.android.systemui.statusbar.notification.collection.GroupEntry
-import com.android.systemui.statusbar.notification.collection.ListEntry
+import com.android.systemui.statusbar.notification.collection.PipelineEntry
 import com.android.systemui.statusbar.notification.collection.NotifPipeline
 import com.android.systemui.statusbar.notification.collection.NotificationEntry
 import com.android.systemui.statusbar.notification.collection.coordinator.dagger.CoordinatorScope
@@ -155,7 +155,7 @@
             }
         }
 
-    override fun onBeforeRenderList(entries: List<ListEntry>) {
+    override fun onBeforeRenderList(entries: List<PipelineEntry>) {
         if (
             isKeyguardGoingAway ||
                 statusBarStateController.state == StatusBarState.KEYGUARD &&
@@ -220,13 +220,15 @@
     }
 }
 
-private fun extractAllRepresentativeEntries(entries: List<ListEntry>): Sequence<NotificationEntry> =
+private fun extractAllRepresentativeEntries(entries: List<PipelineEntry>): Sequence<NotificationEntry> =
     entries.asSequence().flatMap(::extractAllRepresentativeEntries)
 
-private fun extractAllRepresentativeEntries(listEntry: ListEntry): Sequence<NotificationEntry> =
+private fun extractAllRepresentativeEntries(
+    pipelineEntry: PipelineEntry,
+): Sequence<NotificationEntry> =
     sequence {
-        listEntry.representativeEntry?.let { yield(it) }
-        if (listEntry is GroupEntry) {
-            yieldAll(extractAllRepresentativeEntries(listEntry.children))
+        pipelineEntry.representativeEntry?.let { yield(it) }
+        if (pipelineEntry is GroupEntry) {
+            yieldAll(extractAllRepresentativeEntries(pipelineEntry.children))
         }
     }
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/coordinator/ShadeEventCoordinator.kt b/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/coordinator/ShadeEventCoordinator.kt
index 1c66db7..29a8eb0 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/coordinator/ShadeEventCoordinator.kt
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/coordinator/ShadeEventCoordinator.kt
@@ -19,7 +19,7 @@
 import android.service.notification.NotificationListenerService
 import com.android.systemui.dagger.SysUISingleton
 import com.android.systemui.dagger.qualifiers.Main
-import com.android.systemui.statusbar.notification.collection.ListEntry
+import com.android.systemui.statusbar.notification.collection.PipelineEntry
 import com.android.systemui.statusbar.notification.collection.NotifPipeline
 import com.android.systemui.statusbar.notification.collection.NotificationEntry
 import com.android.systemui.statusbar.notification.collection.notifcollection.NotifCollectionListener
@@ -67,7 +67,7 @@
         mShadeEmptiedCallback = callback
     }
 
-    private fun onBeforeRenderList(entries: List<ListEntry>) {
+    private fun onBeforeRenderList(entries: List<PipelineEntry>) {
         if (mEntryRemoved && entries.isEmpty()) {
             mLogger.logShadeEmptied()
             // TODO(b/206023518): This was bad. Do not copy this.
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/coordinator/StackCoordinator.kt b/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/coordinator/StackCoordinator.kt
index 1cb2366..53a73f4 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/coordinator/StackCoordinator.kt
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/coordinator/StackCoordinator.kt
@@ -19,7 +19,7 @@
 import com.android.app.tracing.traceSection
 import com.android.server.notification.Flags.screenshareNotificationHiding
 import com.android.systemui.Flags.screenshareNotificationHidingBugFix
-import com.android.systemui.statusbar.notification.collection.ListEntry
+import com.android.systemui.statusbar.notification.collection.PipelineEntry
 import com.android.systemui.statusbar.notification.collection.NotifPipeline
 import com.android.systemui.statusbar.notification.collection.coordinator.dagger.CoordinatorScope
 import com.android.systemui.statusbar.notification.collection.render.GroupExpansionManagerImpl
@@ -50,14 +50,14 @@
         groupExpansionManagerImpl.attach(pipeline)
     }
 
-    private fun onAfterRenderList(entries: List<ListEntry>) =
+    private fun onAfterRenderList(entries: List<PipelineEntry>) =
         traceSection("StackCoordinator.onAfterRenderList") {
             val notifStats = calculateNotifStats(entries)
             activeNotificationsInteractor.setNotifStats(notifStats)
             renderListInteractor.setRenderedList(entries)
         }
 
-    private fun calculateNotifStats(entries: List<ListEntry>): NotifStats {
+    private fun calculateNotifStats(entries: List<PipelineEntry>): NotifStats {
         var hasNonClearableAlertingNotifs = false
         var hasClearableAlertingNotifs = false
         var hasNonClearableSilentNotifs = false
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/coordinator/VisualStabilityCoordinator.java b/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/coordinator/VisualStabilityCoordinator.java
index 49d5029..3e5655a 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/coordinator/VisualStabilityCoordinator.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/coordinator/VisualStabilityCoordinator.java
@@ -39,7 +39,7 @@
 import com.android.systemui.shade.domain.interactor.ShadeInteractor;
 import com.android.systemui.statusbar.notification.VisibilityLocationProvider;
 import com.android.systemui.statusbar.notification.collection.GroupEntry;
-import com.android.systemui.statusbar.notification.collection.ListEntry;
+import com.android.systemui.statusbar.notification.collection.PipelineEntry;
 import com.android.systemui.statusbar.notification.collection.NotifPipeline;
 import com.android.systemui.statusbar.notification.collection.NotificationEntry;
 import com.android.systemui.statusbar.notification.collection.listbuilder.OnBeforeRenderListListener;
@@ -253,21 +253,20 @@
                         return false;
                     }
 
-                    final GroupEntry parent = entry.getParent();
+                    final PipelineEntry parent = entry.getParent();
 
                     if (parent == null) {
                         return false;
                     }
-
                     return isHeadsUpGroup(parent);
                 }
 
-                private boolean isHeadsUpGroup(GroupEntry groupEntry) {
-                    if (StabilizeHeadsUpGroup.isUnexpectedlyInLegacyMode()) {
+                private boolean isHeadsUpGroup(PipelineEntry pipelineEntry) {
+                    if (!(pipelineEntry instanceof GroupEntry groupEntry)) {
                         return false;
                     }
 
-                    if (groupEntry == null) {
+                    if (StabilizeHeadsUpGroup.isUnexpectedlyInLegacyMode()) {
                         return false;
                     }
 
@@ -370,7 +369,7 @@
                 }
 
                 @Override
-                public boolean isEntryReorderingAllowed(@NonNull ListEntry entry) {
+                public boolean isEntryReorderingAllowed(@NonNull PipelineEntry entry) {
                     if (StabilizeHeadsUpGroup.isEnabled()) {
                         if (isEveryChangeAllowed()) {
                             return true;
@@ -403,7 +402,7 @@
     private final OnBeforeRenderListListener mOnBeforeRenderListListener =
             new OnBeforeRenderListListener() {
                 @Override
-                public void onBeforeRenderList(List<ListEntry> entries) {
+                public void onBeforeRenderList(List<PipelineEntry> entries) {
                     if (StabilizeHeadsUpGroup.isUnexpectedlyInLegacyMode()) {
                         return;
                     }
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/inflation/NotifUiAdjustmentProvider.kt b/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/inflation/NotifUiAdjustmentProvider.kt
index 465bc28..adcc3ec 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/inflation/NotifUiAdjustmentProvider.kt
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/inflation/NotifUiAdjustmentProvider.kt
@@ -124,7 +124,7 @@
         val parent = entry.parent ?: error("Entry must have a parent to determine if minimized")
         val isMinimizedSection = sectionStyleProvider.isMinimizedSection(section)
         val isTopLevelEntry = parent == GroupEntry.ROOT_ENTRY
-        val isGroupSummary = parent.summary == entry
+        val isGroupSummary = (parent as? GroupEntry)?.summary == entry
         return isMinimizedSection && (isTopLevelEntry || isGroupSummary)
     }
 
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/listbuilder/OnAfterRenderListListener.java b/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/listbuilder/OnAfterRenderListListener.java
index ac450c0..e3ab81c 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/listbuilder/OnAfterRenderListListener.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/listbuilder/OnAfterRenderListListener.java
@@ -18,7 +18,7 @@
 
 import androidx.annotation.NonNull;
 
-import com.android.systemui.statusbar.notification.collection.ListEntry;
+import com.android.systemui.statusbar.notification.collection.PipelineEntry;
 import com.android.systemui.statusbar.notification.collection.NotifPipeline;
 
 import java.util.List;
@@ -31,5 +31,5 @@
      * @param entries The current list of top-level entries. Note that this is a live view into the
      * current list and will change whenever the pipeline is rerun.
      */
-    void onAfterRenderList(@NonNull List<ListEntry> entries);
+    void onAfterRenderList(@NonNull List<PipelineEntry> entries);
 }
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/listbuilder/OnBeforeFinalizeFilterListener.java b/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/listbuilder/OnBeforeFinalizeFilterListener.java
index 086661e..3c4f422 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/listbuilder/OnBeforeFinalizeFilterListener.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/listbuilder/OnBeforeFinalizeFilterListener.java
@@ -16,7 +16,7 @@
 
 package com.android.systemui.statusbar.notification.collection.listbuilder;
 
-import com.android.systemui.statusbar.notification.collection.ListEntry;
+import com.android.systemui.statusbar.notification.collection.PipelineEntry;
 import com.android.systemui.statusbar.notification.collection.NotifPipeline;
 
 import java.util.List;
@@ -30,5 +30,5 @@
      * @param entries The current list of top-level entries. Note that this is a live view into the
      *                current list and will change whenever the pipeline is rerun.
      */
-    void onBeforeFinalizeFilter(List<ListEntry> entries);
+    void onBeforeFinalizeFilter(List<PipelineEntry> entries);
 }
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/listbuilder/OnBeforeRenderListListener.java b/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/listbuilder/OnBeforeRenderListListener.java
index 44a27a4..6ceb7d5 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/listbuilder/OnBeforeRenderListListener.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/listbuilder/OnBeforeRenderListListener.java
@@ -16,7 +16,7 @@
 
 package com.android.systemui.statusbar.notification.collection.listbuilder;
 
-import com.android.systemui.statusbar.notification.collection.ListEntry;
+import com.android.systemui.statusbar.notification.collection.PipelineEntry;
 import com.android.systemui.statusbar.notification.collection.NotifPipeline;
 
 import java.util.List;
@@ -30,5 +30,5 @@
      * @param entries The current list of top-level entries. Note that this is a live view into the
      *                current list and will change whenever the pipeline is rerun.
      */
-    void onBeforeRenderList(List<ListEntry> entries);
+    void onBeforeRenderList(List<PipelineEntry> entries);
 }
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/listbuilder/OnBeforeSortListener.java b/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/listbuilder/OnBeforeSortListener.java
index 56cfe5c..858cec1 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/listbuilder/OnBeforeSortListener.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/listbuilder/OnBeforeSortListener.java
@@ -16,7 +16,7 @@
 
 package com.android.systemui.statusbar.notification.collection.listbuilder;
 
-import com.android.systemui.statusbar.notification.collection.ListEntry;
+import com.android.systemui.statusbar.notification.collection.PipelineEntry;
 import com.android.systemui.statusbar.notification.collection.NotifPipeline;
 
 import java.util.List;
@@ -30,5 +30,5 @@
      * @param entries The current list of top-level entries. Note that this is a live view into the
      *                current list and will change whenever the pipeline is rerun.
      */
-    void onBeforeSort(List<ListEntry> entries);
+    void onBeforeSort(List<PipelineEntry> entries);
 }
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/listbuilder/OnBeforeTransformGroupsListener.java b/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/listbuilder/OnBeforeTransformGroupsListener.java
index 0dc4df0..eb525c7 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/listbuilder/OnBeforeTransformGroupsListener.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/listbuilder/OnBeforeTransformGroupsListener.java
@@ -16,7 +16,7 @@
 
 package com.android.systemui.statusbar.notification.collection.listbuilder;
 
-import com.android.systemui.statusbar.notification.collection.ListEntry;
+import com.android.systemui.statusbar.notification.collection.PipelineEntry;
 import com.android.systemui.statusbar.notification.collection.NotifPipeline;
 import com.android.systemui.statusbar.notification.collection.listbuilder.pluggable.NotifPromoter;
 
@@ -35,5 +35,5 @@
      *             a live view into the current notif list and will change as the list moves through
      *             the pipeline.
      */
-    void onBeforeTransformGroups(List<ListEntry> list);
+    void onBeforeTransformGroups(List<PipelineEntry> list);
 }
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/listbuilder/ShadeListBuilderHelper.kt b/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/listbuilder/ShadeListBuilderHelper.kt
index d8f75f6..0d8a64a 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/listbuilder/ShadeListBuilderHelper.kt
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/listbuilder/ShadeListBuilderHelper.kt
@@ -16,10 +16,10 @@
 
 package com.android.systemui.statusbar.notification.collection.listbuilder
 
-import com.android.systemui.statusbar.notification.collection.ListEntry
+import com.android.systemui.statusbar.notification.collection.PipelineEntry
 
 object ShadeListBuilderHelper {
-    fun getSectionSubLists(entries: List<ListEntry>): Iterable<List<ListEntry>> =
+    fun getSectionSubLists(entries: List<PipelineEntry>): Iterable<List<PipelineEntry>> =
         getContiguousSubLists(entries, minLength = 1) { it.sectionIndex }
 
     inline fun <T : Any, K : Any> getContiguousSubLists(
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/listbuilder/ShadeListBuilderLogger.kt b/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/listbuilder/ShadeListBuilderLogger.kt
index a8409d0c..8a9548f 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/listbuilder/ShadeListBuilderLogger.kt
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/listbuilder/ShadeListBuilderLogger.kt
@@ -23,7 +23,7 @@
 import com.android.systemui.log.core.LogLevel.WARNING
 import com.android.systemui.statusbar.notification.NotifPipelineFlags
 import com.android.systemui.statusbar.notification.collection.GroupEntry
-import com.android.systemui.statusbar.notification.collection.ListEntry
+import com.android.systemui.statusbar.notification.collection.PipelineEntry
 import com.android.systemui.statusbar.notification.collection.NotificationEntry
 import com.android.systemui.statusbar.notification.collection.listbuilder.PipelineState.StateName
 import com.android.systemui.statusbar.notification.collection.listbuilder.PipelineState.getStateName
@@ -152,9 +152,9 @@
 
     fun logEntryAttachStateChanged(
         buildId: Int,
-        entry: ListEntry,
-        prevParent: GroupEntry?,
-        newParent: GroupEntry?
+        entry: PipelineEntry,
+        prevParent: PipelineEntry?,
+        newParent: PipelineEntry?
     ) {
         buffer.log(TAG, INFO, {
             long1 = buildId.toLong()
@@ -177,7 +177,7 @@
         })
     }
 
-    fun logParentChanged(buildId: Int, prevParent: GroupEntry?, newParent: GroupEntry?) {
+    fun logParentChanged(buildId: Int, prevParent: PipelineEntry?, newParent: PipelineEntry?) {
         buffer.log(TAG, INFO, {
             long1 = buildId.toLong()
             str1 = prevParent?.logKey
@@ -195,8 +195,8 @@
 
     fun logParentChangeSuppressedStarted(
         buildId: Int,
-        suppressedParent: GroupEntry?,
-        keepingParent: GroupEntry?
+        suppressedParent: PipelineEntry?,
+        keepingParent: PipelineEntry?
     ) {
         buffer.log(TAG, INFO, {
             long1 = buildId.toLong()
@@ -209,8 +209,8 @@
 
     fun logParentChangeSuppressedStopped(
             buildId: Int,
-            previouslySuppressedParent: GroupEntry?,
-            previouslyKeptParent: GroupEntry?
+            previouslySuppressedParent: PipelineEntry?,
+            previouslyKeptParent: PipelineEntry?
     ) {
         buffer.log(TAG, INFO, {
             long1 = buildId.toLong()
@@ -224,7 +224,7 @@
 
     fun logGroupPruningSuppressed(
         buildId: Int,
-        keepingParent: GroupEntry?
+        keepingParent: PipelineEntry?
     ) {
         buffer.log(TAG, INFO, {
             long1 = buildId.toLong()
@@ -310,7 +310,7 @@
 
     val logRankInFinalList = Compile.IS_DEBUG && notifPipelineFlags.isDevLoggingEnabled()
 
-    fun logFinalList(entries: List<ListEntry>) {
+    fun logFinalList(entries: List<PipelineEntry>) {
         if (entries.isEmpty()) {
             buffer.log(TAG, DEBUG, {}, { "(empty list)" })
         }
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/listbuilder/pluggable/NotifComparator.java b/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/listbuilder/pluggable/NotifComparator.java
index f7bbd28..f7c74c2 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/listbuilder/pluggable/NotifComparator.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/listbuilder/pluggable/NotifComparator.java
@@ -18,7 +18,7 @@
 
 import androidx.annotation.NonNull;
 
-import com.android.systemui.statusbar.notification.collection.ListEntry;
+import com.android.systemui.statusbar.notification.collection.PipelineEntry;
 import com.android.systemui.statusbar.notification.collection.NotifPipeline;
 
 import java.util.Comparator;
@@ -29,7 +29,7 @@
  */
 public abstract class NotifComparator
         extends Pluggable<NotifComparator>
-        implements Comparator<ListEntry> {
+        implements Comparator<PipelineEntry> {
 
     protected NotifComparator(String name) {
         super(name);
@@ -41,5 +41,5 @@
      * @return a negative integer, zero, or a positive integer as the first argument is less than
      *      equal to, or greater than the second (same as standard Comparator<> interface).
      */
-    public abstract int compare(@NonNull ListEntry o1, @NonNull ListEntry o2);
+    public abstract int compare(@NonNull PipelineEntry o1, @NonNull PipelineEntry o2);
 }
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/listbuilder/pluggable/NotifSectioner.java b/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/listbuilder/pluggable/NotifSectioner.java
index 8c52c53..4a856a5 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/listbuilder/pluggable/NotifSectioner.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/listbuilder/pluggable/NotifSectioner.java
@@ -18,7 +18,7 @@
 
 import android.annotation.Nullable;
 
-import com.android.systemui.statusbar.notification.collection.ListEntry;
+import com.android.systemui.statusbar.notification.collection.PipelineEntry;
 import com.android.systemui.statusbar.notification.collection.ShadeListBuilder;
 import com.android.systemui.statusbar.notification.collection.render.NodeController;
 import com.android.systemui.statusbar.notification.collection.render.NodeSpec;
@@ -52,11 +52,11 @@
      * However, this doesn't necessarily mean that your section will get called on each top-level
      * notification. The first section to return true determines the section of the notification.
      */
-    public abstract boolean isInSection(ListEntry entry);
+    public abstract boolean isInSection(PipelineEntry entry);
 
     /**
      * Returns an optional {@link NotifComparator} to sort entries only in this section.
-     * {@link ListEntry} instances passed to this comparator are guaranteed to have this section,
+     * {@link PipelineEntry} instances passed to this comparator are guaranteed to have this section,
      * and this ordering will take precedence over any global comparators supplied to {@link
      * com.android.systemui.statusbar.notification.collection.NotifPipeline#setComparators(List)}.
      *
@@ -80,5 +80,5 @@
      * Notify of children of this section being updated
      * @param entries of this section that are borrowed (must clone to store)
      */
-    public void onEntriesUpdated(List<ListEntry> entries) {}
+    public void onEntriesUpdated(List<PipelineEntry> entries) {}
 }
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/listbuilder/pluggable/NotifStabilityManager.kt b/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/listbuilder/pluggable/NotifStabilityManager.kt
index 58bbca8..e1e2bcf 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/listbuilder/pluggable/NotifStabilityManager.kt
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/listbuilder/pluggable/NotifStabilityManager.kt
@@ -16,7 +16,7 @@
 package com.android.systemui.statusbar.notification.collection.listbuilder.pluggable
 
 import com.android.systemui.statusbar.notification.collection.GroupEntry
-import com.android.systemui.statusbar.notification.collection.ListEntry
+import com.android.systemui.statusbar.notification.collection.PipelineEntry
 import com.android.systemui.statusbar.notification.collection.NotificationEntry
 
 /**
@@ -75,7 +75,7 @@
      * being suppressed. However, if an order change is suppressed, that will be reported to ths
      * implementation by calling [onEntryReorderSuppressed] after ordering is complete.
      */
-    abstract fun isEntryReorderingAllowed(entry: ListEntry): Boolean
+    abstract fun isEntryReorderingAllowed(entry: PipelineEntry): Boolean
 
     /**
      * Called by the pipeline to determine if every call to the other stability methods would
@@ -100,7 +100,7 @@
     override fun isGroupChangeAllowed(entry: NotificationEntry): Boolean = true
     override fun isGroupPruneAllowed(entry: GroupEntry): Boolean = true
     override fun isSectionChangeAllowed(entry: NotificationEntry): Boolean = true
-    override fun isEntryReorderingAllowed(entry: ListEntry): Boolean = true
+    override fun isEntryReorderingAllowed(entry: PipelineEntry): Boolean = true
     override fun isEveryChangeAllowed(): Boolean = true
     override fun onEntryReorderSuppressed() {}
 }
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/notifcollection/NotifCollectionLogger.kt b/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/notifcollection/NotifCollectionLogger.kt
index 014ac54..ae2c70a 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/notifcollection/NotifCollectionLogger.kt
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/notifcollection/NotifCollectionLogger.kt
@@ -27,6 +27,7 @@
 import com.android.systemui.log.core.LogLevel.INFO
 import com.android.systemui.log.core.LogLevel.WARNING
 import com.android.systemui.log.core.LogLevel.WTF
+import com.android.systemui.statusbar.notification.collection.GroupEntry
 import com.android.systemui.statusbar.notification.collection.NotifCollection
 import com.android.systemui.statusbar.notification.collection.NotifCollection.CancellationReason
 import com.android.systemui.statusbar.notification.collection.NotifCollection.FutureDismissal
@@ -421,6 +422,14 @@
         })
     }
 
+    private fun getParentLogKey(childEntry: NotificationEntry): String {
+        return if (childEntry.parent is GroupEntry) {
+            (childEntry.parent as? GroupEntry)?.summary?.logKey ?: "(null)"
+        } else {
+            "(null)"
+        }
+    }
+
     fun logDismissAlreadyParentDismissedNotif(
             childEntry: NotificationEntry,
             childIndex: Int,
@@ -430,7 +439,7 @@
             str1 = childEntry.logKey
             int1 = childIndex
             int2 = childCount
-            str2 = childEntry.parent?.summary?.logKey ?: "(null)"
+            str2 = getParentLogKey(childEntry)
         }, {
             "DISMISS Already Parent-Dismissed $str1 ($int1/$int2) with summary $str2"
         })
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/provider/HighPriorityProvider.java b/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/provider/HighPriorityProvider.java
index 2e3ab92..051bd3a 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/provider/HighPriorityProvider.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/provider/HighPriorityProvider.java
@@ -23,7 +23,7 @@
 
 import com.android.systemui.dagger.SysUISingleton;
 import com.android.systemui.statusbar.notification.collection.GroupEntry;
-import com.android.systemui.statusbar.notification.collection.ListEntry;
+import com.android.systemui.statusbar.notification.collection.PipelineEntry;
 import com.android.systemui.statusbar.notification.collection.NotificationEntry;
 import com.android.systemui.statusbar.notification.collection.render.GroupMembershipManager;
 import com.android.systemui.statusbar.notification.people.PeopleNotificationIdentifier;
@@ -53,7 +53,7 @@
     }
 
     /**
-     * @return true if the ListEntry is high priority, else false
+     * @return true if the PipelineEntry is high priority, else false
      *
      * A NotificationEntry is considered high priority if it:
      *  - has importance greater than or equal to IMPORTANCE_DEFAULT
@@ -67,12 +67,12 @@
      * A GroupEntry is considered high priority if its representativeEntry (summary) or any of its
      * children are high priority.
      */
-    public boolean isHighPriority(@Nullable ListEntry entry) {
+    public boolean isHighPriority(@Nullable PipelineEntry entry) {
         return isHighPriority(entry, /* allowImplicit = */ true);
     }
 
     /**
-     * @return true if the ListEntry is explicitly high priority, else false
+     * @return true if the PipelineEntry is explicitly high priority, else false
      *
      * A NotificationEntry is considered explicitly high priority if has importance greater than or
      * equal to IMPORTANCE_DEFAULT.
@@ -80,11 +80,11 @@
      * A GroupEntry is considered explicitly high priority if its representativeEntry (summary) or
      * any of its children are explicitly high priority.
      */
-    public boolean isExplicitlyHighPriority(@Nullable ListEntry entry) {
+    public boolean isExplicitlyHighPriority(@Nullable PipelineEntry entry) {
         return isHighPriority(entry, /* allowImplicit= */ false);
     }
 
-    private boolean isHighPriority(@Nullable ListEntry entry, boolean allowImplicit) {
+    private boolean isHighPriority(@Nullable PipelineEntry entry, boolean allowImplicit) {
         if (entry == null) {
             return false;
         }
@@ -100,9 +100,9 @@
     }
 
     /**
-     * @return true if the ListEntry is high priority conversation, else false
+     * @return true if the PipelineEntry is high priority conversation, else false
      */
-    public boolean isHighPriorityConversation(@NonNull ListEntry entry) {
+    public boolean isHighPriorityConversation(@NonNull PipelineEntry entry) {
         final NotificationEntry notifEntry = entry.getRepresentativeEntry();
         if (notifEntry == null) {
             return  false;
@@ -119,7 +119,7 @@
         return isNotificationEntryWithAtLeastOneImportantChild(entry);
     }
 
-    private boolean isNotificationEntryWithAtLeastOneImportantChild(@NonNull ListEntry entry) {
+    private boolean isNotificationEntryWithAtLeastOneImportantChild(@NonNull PipelineEntry entry) {
         if (!(entry instanceof GroupEntry)) {
             return false;
         }
@@ -134,7 +134,7 @@
      * Returns whether the given ListEntry has a high priority child or is in a group with a child
      * that's high priority
      */
-    private boolean hasHighPriorityChild(ListEntry entry, boolean allowImplicit) {
+    private boolean hasHighPriorityChild(PipelineEntry entry, boolean allowImplicit) {
         if (NotificationBundleUi.isEnabled()) {
             GroupEntry representativeGroupEntry = null;
             if (entry instanceof GroupEntry) {
@@ -142,9 +142,10 @@
             } else if (entry instanceof NotificationEntry){
                 final NotificationEntry notificationEntry = entry.getRepresentativeEntry();
                 if (notificationEntry.getParent() != null
-                        && notificationEntry.getParent().getSummary() != null
-                        && notificationEntry.getParent().getSummary() == notificationEntry) {
-                    representativeGroupEntry = notificationEntry.getParent();
+                        && notificationEntry.getParent() instanceof GroupEntry parent
+                        && parent.getSummary() != null
+                        && parent.getSummary() == notificationEntry) {
+                    representativeGroupEntry = parent;
                 }
             }
             return representativeGroupEntry != null &&
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/provider/SectionStyleProvider.kt b/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/provider/SectionStyleProvider.kt
index ea9f295..f418bb6 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/provider/SectionStyleProvider.kt
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/provider/SectionStyleProvider.kt
@@ -17,7 +17,7 @@
 package com.android.systemui.statusbar.notification.collection.provider
 
 import com.android.systemui.dagger.SysUISingleton
-import com.android.systemui.statusbar.notification.collection.ListEntry
+import com.android.systemui.statusbar.notification.collection.PipelineEntry
 import com.android.systemui.statusbar.notification.collection.SortBySectionTimeFlag
 import com.android.systemui.statusbar.notification.collection.listbuilder.NotifSection
 import com.android.systemui.statusbar.notification.collection.listbuilder.pluggable.NotifSectioner
@@ -53,7 +53,7 @@
      * Determine if the given entry is minimized.
      */
     @JvmOverloads
-    fun isMinimized(entry: ListEntry, ifNotInSection: Boolean = true): Boolean {
+    fun isMinimized(entry: PipelineEntry, ifNotInSection: Boolean = true): Boolean {
         val section = entry.section ?: return ifNotInSection
         return isMinimizedSection(section)
     }
@@ -77,7 +77,7 @@
      * Determine if the given entry is silent.
      */
     @JvmOverloads
-    fun isSilent(entry: ListEntry, ifNotInSection: Boolean = true): Boolean {
+    fun isSilent(entry: PipelineEntry, ifNotInSection: Boolean = true): Boolean {
         val section = entry.section ?: return ifNotInSection
         if (SortBySectionTimeFlag.isEnabled) {
             if (entry.section?.bucket == BUCKET_PEOPLE) {
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/render/GroupExpansionManagerImpl.java b/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/render/GroupExpansionManagerImpl.java
index 16b98e2..b179a69 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/render/GroupExpansionManagerImpl.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/render/GroupExpansionManagerImpl.java
@@ -25,7 +25,7 @@
 import com.android.systemui.dump.DumpManager;
 import com.android.systemui.statusbar.notification.collection.EntryAdapter;
 import com.android.systemui.statusbar.notification.collection.GroupEntry;
-import com.android.systemui.statusbar.notification.collection.ListEntry;
+import com.android.systemui.statusbar.notification.collection.PipelineEntry;
 import com.android.systemui.statusbar.notification.collection.NotifPipeline;
 import com.android.systemui.statusbar.notification.collection.NotificationEntry;
 import com.android.systemui.statusbar.notification.collection.listbuilder.OnBeforeRenderListListener;
@@ -81,7 +81,7 @@
         }
 
         final Set<NotificationEntry> renderingSummaries = new HashSet<>();
-        for (ListEntry entry : entries) {
+        for (PipelineEntry entry : entries) {
             if (entry instanceof GroupEntry) {
                 renderingSummaries.add(entry.getRepresentativeEntry());
             }
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/render/GroupMembershipManager.java b/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/render/GroupMembershipManager.java
index 69267e5..3edbfaf 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/render/GroupMembershipManager.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/render/GroupMembershipManager.java
@@ -22,6 +22,7 @@
 import com.android.systemui.statusbar.notification.collection.EntryAdapter;
 import com.android.systemui.statusbar.notification.collection.ListEntry;
 import com.android.systemui.statusbar.notification.collection.NotificationEntry;
+import com.android.systemui.statusbar.notification.collection.PipelineEntry;
 
 import java.util.List;
 
@@ -78,5 +79,5 @@
      * @return list of the children
      */
     @Nullable
-    List<NotificationEntry> getChildren(@NonNull ListEntry summary);
+    List<NotificationEntry> getChildren(@NonNull PipelineEntry summary);
 }
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/render/GroupMembershipManagerImpl.java b/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/render/GroupMembershipManagerImpl.java
index 80a9f8ad..a1a23e3 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/render/GroupMembershipManagerImpl.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/render/GroupMembershipManagerImpl.java
@@ -24,7 +24,7 @@
 import com.android.systemui.dagger.SysUISingleton;
 import com.android.systemui.statusbar.notification.collection.EntryAdapter;
 import com.android.systemui.statusbar.notification.collection.GroupEntry;
-import com.android.systemui.statusbar.notification.collection.ListEntry;
+import com.android.systemui.statusbar.notification.collection.PipelineEntry;
 import com.android.systemui.statusbar.notification.collection.NotificationEntry;
 import com.android.systemui.statusbar.notification.shared.NotificationBundleUi;
 
@@ -48,8 +48,13 @@
             // The entry is not attached, so it doesn't count.
             return false;
         }
+        PipelineEntry pipelineEntry = entry.getParent();
+        if (!(pipelineEntry instanceof GroupEntry groupEntry)) {
+            return false;
+        }
+
         // If entry is a summary, its parent is a GroupEntry with summary = entry.
-        return entry.getParent().getSummary() == entry;
+        return groupEntry.getSummary() == entry;
     }
 
     @Override
@@ -65,7 +70,10 @@
         if (isTopLevelEntry(entry) || entry.getParent() == null) {
             return null;
         }
-        return entry.getParent().getSummary();
+        if (entry.getParent() instanceof GroupEntry parent) {
+            return parent.getSummary();
+        }
+        return null;
     }
 
     @Nullable
@@ -91,8 +99,9 @@
 
     @Nullable
     @Override
-    public List<NotificationEntry> getChildren(@NonNull ListEntry entry) {
+    public List<NotificationEntry> getChildren(@NonNull PipelineEntry entry) {
         NotificationBundleUi.assertInLegacyMode();
+
         if (entry instanceof GroupEntry) {
             return ((GroupEntry) entry).getChildren();
         }
@@ -100,8 +109,7 @@
         NotificationEntry representativeEntry = entry.getRepresentativeEntry();
         if (representativeEntry != null && isGroupSummary(representativeEntry)) {
             // maybe we were actually passed the summary
-            GroupEntry parent = representativeEntry.getParent();
-            if (parent != null) {
+            if (representativeEntry.getParent() instanceof GroupEntry parent) {
                 return parent.getChildren();
             }
         }
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/render/NodeSpecBuilder.kt b/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/render/NodeSpecBuilder.kt
index 9fc4e40..e173194 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/render/NodeSpecBuilder.kt
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/render/NodeSpecBuilder.kt
@@ -18,12 +18,12 @@
 
 import com.android.systemui.statusbar.notification.NotificationSectionsFeatureManager
 import com.android.systemui.statusbar.notification.collection.GroupEntry
-import com.android.systemui.statusbar.notification.collection.ListEntry
 import com.android.systemui.statusbar.notification.collection.NotificationEntry
 import com.android.systemui.statusbar.notification.collection.listbuilder.NotifSection
 import com.android.systemui.statusbar.notification.collection.provider.SectionHeaderVisibilityProvider
 import com.android.systemui.util.Compile
 import com.android.app.tracing.traceSection
+import com.android.systemui.statusbar.notification.collection.PipelineEntry
 
 /**
  * Converts a notif list (the output of the ShadeListBuilder) into a NodeSpec, an abstract
@@ -45,7 +45,7 @@
 
     fun buildNodeSpec(
         rootController: NodeController,
-        notifList: List<ListEntry>
+        notifList: List<PipelineEntry>
     ): NodeSpec = traceSection("NodeSpecBuilder.buildNodeSpec") {
         val root = NodeSpecImpl(null, rootController)
 
@@ -100,7 +100,7 @@
         return@traceSection root
     }
 
-    private fun buildNotifNode(parent: NodeSpec, entry: ListEntry): NodeSpec = when (entry) {
+    private fun buildNotifNode(parent: NodeSpec, entry: PipelineEntry): NodeSpec = when (entry) {
         is NotificationEntry -> NodeSpecImpl(parent, viewBarn.requireNodeController(entry))
         is GroupEntry ->
             NodeSpecImpl(parent, viewBarn.requireNodeController(checkNotNull(entry.summary)))
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/render/NotifViewBarn.kt b/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/render/NotifViewBarn.kt
index fd91d5a..56662f1 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/render/NotifViewBarn.kt
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/render/NotifViewBarn.kt
@@ -18,18 +18,18 @@
 
 import android.view.textclassifier.Log
 import com.android.systemui.dagger.SysUISingleton
-import com.android.systemui.statusbar.notification.collection.ListEntry
+import com.android.systemui.statusbar.notification.collection.PipelineEntry
 import com.android.systemui.statusbar.notification.collection.NotificationEntry
 import javax.inject.Inject
 
 /**
- * The ViewBarn is just a map from [ListEntry] to an instance of a [NodeController].
+ * The ViewBarn is just a map from [PipelineEntry] to an instance of a [NodeController].
  */
 @SysUISingleton
 class NotifViewBarn @Inject constructor() {
     private val rowMap = mutableMapOf<String, NotifViewController>()
 
-    fun requireNodeController(entry: ListEntry): NodeController {
+    fun requireNodeController(entry: PipelineEntry): NodeController {
         if (DEBUG) {
             Log.d(TAG, "requireNodeController: ${entry.key}")
         }
@@ -50,14 +50,14 @@
         return rowMap[entry.key] ?: error("No view has been registered for entry: ${entry.key}")
     }
 
-    fun registerViewForEntry(entry: ListEntry, controller: NotifViewController) {
+    fun registerViewForEntry(entry: PipelineEntry, controller: NotifViewController) {
         if (DEBUG) {
             Log.d(TAG, "registerViewForEntry: ${entry.key}")
         }
         rowMap[entry.key] = controller
     }
 
-    fun removeViewForEntry(entry: ListEntry) {
+    fun removeViewForEntry(entry: PipelineEntry) {
         if (DEBUG) {
             Log.d(TAG, "removeViewForEntry: ${entry.key}")
         }
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/render/NotifViewRenderer.kt b/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/render/NotifViewRenderer.kt
index 8284022..396d47e 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/render/NotifViewRenderer.kt
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/render/NotifViewRenderer.kt
@@ -17,7 +17,7 @@
 package com.android.systemui.statusbar.notification.collection.render
 
 import com.android.systemui.statusbar.notification.collection.GroupEntry
-import com.android.systemui.statusbar.notification.collection.ListEntry
+import com.android.systemui.statusbar.notification.collection.PipelineEntry
 import com.android.systemui.statusbar.notification.collection.NotificationEntry
 
 /**
@@ -34,7 +34,7 @@
      * also ensure that future calls to [getStackController], [getGroupController], and
      * [getRowController] will provide valid results.
      */
-    fun onRenderList(notifList: List<ListEntry>)
+    fun onRenderList(notifList: List<PipelineEntry>)
 
     /**
      * Provides an interface for the pipeline to update individual groups. This will be called at
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/render/RenderStageManager.kt b/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/render/RenderStageManager.kt
index 21e6837..93e844d 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/render/RenderStageManager.kt
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/render/RenderStageManager.kt
@@ -19,7 +19,7 @@
 import com.android.app.tracing.traceSection
 import com.android.systemui.dagger.SysUISingleton
 import com.android.systemui.statusbar.notification.collection.GroupEntry
-import com.android.systemui.statusbar.notification.collection.ListEntry
+import com.android.systemui.statusbar.notification.collection.PipelineEntry
 import com.android.systemui.statusbar.notification.collection.NotificationEntry
 import com.android.systemui.statusbar.notification.collection.PipelineDumpable
 import com.android.systemui.statusbar.notification.collection.PipelineDumper
@@ -46,7 +46,7 @@
         listBuilder.setOnRenderListListener(::onRenderList)
     }
 
-    private fun onRenderList(notifList: List<ListEntry>) {
+    private fun onRenderList(notifList: List<PipelineEntry>) {
         traceSection("RenderStageManager.onRenderList") {
             val viewRenderer = viewRenderer ?: return
             viewRenderer.onRenderList(notifList)
@@ -85,7 +85,7 @@
             dump("onAfterRenderEntryListeners", onAfterRenderEntryListeners)
         }
 
-    private fun dispatchOnAfterRenderList(entries: List<ListEntry>) {
+    private fun dispatchOnAfterRenderList(entries: List<PipelineEntry>) {
         traceSection("RenderStageManager.dispatchOnAfterRenderList") {
             onAfterRenderListListeners.forEach { listener -> listener.onAfterRenderList(entries) }
         }
@@ -93,7 +93,7 @@
 
     private fun dispatchOnAfterRenderGroups(
         viewRenderer: NotifViewRenderer,
-        entries: List<ListEntry>,
+        entries: List<PipelineEntry>,
     ) {
         traceSection("RenderStageManager.dispatchOnAfterRenderGroups") {
             if (onAfterRenderGroupListeners.isEmpty()) {
@@ -110,7 +110,7 @@
 
     private fun dispatchOnAfterRenderEntries(
         viewRenderer: NotifViewRenderer,
-        entries: List<ListEntry>,
+        entries: List<PipelineEntry>,
     ) {
         traceSection("RenderStageManager.dispatchOnAfterRenderEntries") {
             if (onAfterRenderEntryListeners.isEmpty()) {
@@ -129,7 +129,7 @@
      * Performs a forward, depth-first traversal of the list where the group's summary immediately
      * precedes the group's children.
      */
-    private inline fun List<ListEntry>.forEachNotificationEntry(
+    private inline fun List<PipelineEntry>.forEachNotificationEntry(
         action: (NotificationEntry) -> Unit
     ) {
         forEach { entry ->
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/render/ShadeViewManager.kt b/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/render/ShadeViewManager.kt
index 72316bf..9532d6d 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/render/ShadeViewManager.kt
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/render/ShadeViewManager.kt
@@ -22,7 +22,7 @@
 import com.android.systemui.shade.ShadeDisplayAware
 import com.android.systemui.statusbar.notification.NotificationSectionsFeatureManager
 import com.android.systemui.statusbar.notification.collection.GroupEntry
-import com.android.systemui.statusbar.notification.collection.ListEntry
+import com.android.systemui.statusbar.notification.collection.PipelineEntry
 import com.android.systemui.statusbar.notification.collection.NotificationEntry
 import com.android.systemui.statusbar.notification.collection.PipelineDumpable
 import com.android.systemui.statusbar.notification.collection.PipelineDumper
@@ -76,7 +76,7 @@
     private val viewRenderer =
         object : NotifViewRenderer {
 
-            override fun onRenderList(notifList: List<ListEntry>) {
+            override fun onRenderList(notifList: List<PipelineEntry>) {
                 traceSection("ShadeViewManager.onRenderList") {
                     viewDiffer.applySpec(specBuilder.buildNodeSpec(rootController, notifList))
                 }
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/domain/interactor/RenderNotificationListInteractor.kt b/packages/SystemUI/src/com/android/systemui/statusbar/notification/domain/interactor/RenderNotificationListInteractor.kt
index bde3c4d..adc049e 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/domain/interactor/RenderNotificationListInteractor.kt
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/domain/interactor/RenderNotificationListInteractor.kt
@@ -29,7 +29,7 @@
 import com.android.systemui.dagger.qualifiers.Main
 import com.android.systemui.statusbar.StatusBarIconView
 import com.android.systemui.statusbar.notification.collection.GroupEntry
-import com.android.systemui.statusbar.notification.collection.ListEntry
+import com.android.systemui.statusbar.notification.collection.PipelineEntry
 import com.android.systemui.statusbar.notification.collection.NotificationEntry
 import com.android.systemui.statusbar.notification.collection.provider.SectionStyleProvider
 import com.android.systemui.statusbar.notification.data.repository.ActiveNotificationListRepository
@@ -57,11 +57,11 @@
     /**
      * Sets the current list of rendered notification entries as displayed in the notification list.
      */
-    fun setRenderedList(entries: List<ListEntry>) {
+    fun setRenderedList(entries: List<PipelineEntry>) {
         traceSection("RenderNotificationListInteractor.setRenderedList") {
             repository.activeNotifications.update { existingModels ->
                 buildActiveNotificationsStore(existingModels, sectionStyleProvider, context) {
-                    entries.forEach(::addListEntry)
+                    entries.forEach(::addPipelineEntry)
                     setRankingsMap(entries)
                 }
             }
@@ -89,11 +89,11 @@
     fun build(): ActiveNotificationsStore = builder.build()
 
     /**
-     * Convert a [ListEntry] into [ActiveNotificationEntryModel]s, and add them to the
+     * Convert a [PipelineEntry] into [ActiveNotificationEntryModel]s, and add them to the
      * [ActiveNotificationsStore]. Special care is taken to avoid re-allocating models if the result
      * would be identical to an existing model (at the expense of additional computations).
      */
-    fun addListEntry(entry: ListEntry) {
+    fun addPipelineEntry(entry: PipelineEntry) {
         when (entry) {
             is GroupEntry -> {
                 entry.summary?.let { summary ->
@@ -116,11 +116,11 @@
         }
     }
 
-    fun setRankingsMap(entries: List<ListEntry>) {
+    fun setRankingsMap(entries: List<PipelineEntry>) {
         builder.setRankingsMap(flatMapToRankingsMap(entries))
     }
 
-    fun flatMapToRankingsMap(entries: List<ListEntry>): Map<String, Int> {
+    fun flatMapToRankingsMap(entries: List<PipelineEntry>): Map<String, Int> {
         val result = ArrayMap<String, Int>()
         for (entry in entries) {
             if (entry is NotificationEntry) {
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/interruption/KeyguardNotificationVisibilityProvider.kt b/packages/SystemUI/src/com/android/systemui/statusbar/notification/interruption/KeyguardNotificationVisibilityProvider.kt
index c7548aa..5096894 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/interruption/KeyguardNotificationVisibilityProvider.kt
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/interruption/KeyguardNotificationVisibilityProvider.kt
@@ -19,7 +19,7 @@
 import com.android.systemui.statusbar.NotificationLockscreenUserManager
 import com.android.systemui.statusbar.StatusBarState
 import com.android.systemui.statusbar.SysuiStatusBarStateController
-import com.android.systemui.statusbar.notification.collection.ListEntry
+import com.android.systemui.statusbar.notification.collection.PipelineEntry
 import com.android.systemui.statusbar.notification.collection.NotificationEntry
 import com.android.systemui.statusbar.notification.collection.provider.HighPriorityProvider
 import com.android.systemui.statusbar.policy.KeyguardStateController
@@ -198,7 +198,7 @@
             else -> false
         }
 
-    private fun shouldHideIfEntrySilent(entry: ListEntry): Boolean =
+    private fun shouldHideIfEntrySilent(entry: PipelineEntry): Boolean =
         when {
             // Show if explicitly high priority (not hidden)
             highPriorityProvider.isExplicitlyHighPriority(entry) -> false
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/people/PeopleNotificationIdentifier.kt b/packages/SystemUI/src/com/android/systemui/statusbar/notification/people/PeopleNotificationIdentifier.kt
index f755dbb..002230e 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/people/PeopleNotificationIdentifier.kt
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/people/PeopleNotificationIdentifier.kt
@@ -20,6 +20,7 @@
 import android.service.notification.NotificationListenerService.Ranking
 import android.service.notification.StatusBarNotification
 import com.android.systemui.dagger.SysUISingleton
+import com.android.systemui.statusbar.notification.collection.GroupEntry
 import com.android.systemui.statusbar.notification.collection.NotificationEntry
 import com.android.systemui.statusbar.notification.collection.render.GroupMembershipManager
 import com.android.systemui.statusbar.notification.people.PeopleNotificationIdentifier.Companion.PeopleNotificationType
@@ -117,8 +118,8 @@
             if (!entry.sbn.notification.isGroupSummary) {
                 return TYPE_NON_PERSON;
             }
-
-            return getPeopleTypeForChildList(entry.parent?.children)
+            val parent = entry.parent as? GroupEntry ?: return TYPE_NON_PERSON
+            return getPeopleTypeForChildList(parent.children)
         } else {
             if (!groupManager.isGroupSummary(entry)) {
                 return TYPE_NON_PERSON
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/promoted/AODPromotedNotification.kt b/packages/SystemUI/src/com/android/systemui/statusbar/notification/promoted/AODPromotedNotification.kt
index 9aa5a2e..7959e99 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/promoted/AODPromotedNotification.kt
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/promoted/AODPromotedNotification.kt
@@ -17,6 +17,7 @@
 package com.android.systemui.statusbar.notification.promoted
 
 import android.app.Flags
+import android.app.Flags.notificationsRedesignTemplates
 import android.app.Notification
 import android.graphics.PorterDuff
 import android.view.LayoutInflater
@@ -166,7 +167,10 @@
     private val closeButton: View? = root.findViewById(R.id.close_button)
     private val conversationIconBadge: View? = root.findViewById(R.id.conversation_icon_badge)
     private val conversationIcon: CachingIconView? = root.findViewById(R.id.conversation_icon)
-    private val conversationText: TextView? = root.findViewById(R.id.conversation_text)
+    private val conversationText: TextView? =
+        root.findViewById(
+            if (notificationsRedesignTemplates()) R.id.title else R.id.conversation_text
+        )
     private val expandButton: NotificationExpandButton? = root.findViewById(R.id.expand_button)
     private val headerText: TextView? = root.findViewById(R.id.header_text)
     private val headerTextDivider: View? = root.findViewById(R.id.header_text_divider)
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/ActivatableNotificationView.java b/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/ActivatableNotificationView.java
index b077327..4ed9dce 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/ActivatableNotificationView.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/ActivatableNotificationView.java
@@ -16,8 +16,10 @@
 
 package com.android.systemui.statusbar.notification.row;
 
+import static com.android.systemui.Flags.notificationAppearNonlinear;
 import static com.android.systemui.Flags.notificationBackgroundTintOptimization;
 import static com.android.systemui.Flags.notificationRowTransparency;
+import static com.android.systemui.Flags.physicalNotificationMovement;
 import static com.android.systemui.statusbar.notification.row.ExpandableView.ClipSide.BOTTOM;
 import static com.android.systemui.statusbar.notification.row.ExpandableView.ClipSide.TOP;
 
@@ -71,7 +73,8 @@
      * #ALPHA_APPEAR_START_FRACTION.
      */
 
-    private static final float ALPHA_APPEAR_START_FRACTION = .7f;
+    private static final float ALPHA_APPEAR_START_FRACTION =
+            notificationAppearNonlinear() ? .55f : .7f;
     /**
      * The content should show fully with progress at #ALPHA_APPEAR_END_FRACTION
      * The start of the animation is at #ALPHA_APPEAR_START_FRACTION
@@ -111,6 +114,7 @@
     private float mOverrideAmount;
     private boolean mShadowHidden;
     private boolean mIsHeadsUpAnimation;
+    private boolean mIsHeadsUpCycling;
     /* In order to track headsup longpress coorindate. */
     protected Point mTargetPoint;
     private boolean mDismissed;
@@ -349,10 +353,12 @@
 
     @Override
     public long performRemoveAnimation(long duration, long delay, float translationDirection,
-            boolean isHeadsUpAnimation, Runnable onStartedRunnable, Runnable onFinishedRunnable,
-            AnimatorListenerAdapter animationListener, ClipSide clipSide) {
+            boolean isHeadsUpAnimation, boolean isHeadsUpCycling, Runnable onStartedRunnable,
+            Runnable onFinishedRunnable, AnimatorListenerAdapter animationListener,
+            ClipSide clipSide) {
         enableAppearDrawing(true);
         mIsHeadsUpAnimation = isHeadsUpAnimation;
+        mIsHeadsUpCycling = isHeadsUpCycling;
         if (mDrawingAppearAnimation) {
             startAppearAnimation(false /* isAppearing */, translationDirection,
                     delay, duration, onStartedRunnable, onFinishedRunnable, animationListener,
@@ -370,9 +376,10 @@
 
     @Override
     public void performAddAnimation(long delay, long duration, boolean isHeadsUpAppear,
-            Runnable onFinishRunnable) {
+            boolean isHeadsUpCycling, Runnable onFinishRunnable) {
         enableAppearDrawing(true);
         mIsHeadsUpAnimation = isHeadsUpAppear;
+        mIsHeadsUpCycling = isHeadsUpCycling;
         if (mDrawingAppearAnimation) {
             startAppearAnimation(true /* isAppearing */, isHeadsUpAppear ? 0.0f : -1.0f, delay,
                     duration, null, null, null, ClipSide.BOTTOM);
@@ -404,14 +411,14 @@
             targetValue = 0.0f;
         }
 
-        if (NotificationHeadsUpCycling.isEnabled()) {
-            // TODO(b/316404716): add avalanche filtering
+        if (NotificationHeadsUpCycling.isEnabled() && !useNonLinearAnimation()) {
             mCurrentAppearInterpolator = Interpolators.LINEAR;
         }
 
         mAppearAnimator = ValueAnimator.ofFloat(mAppearAnimationFraction,
                 targetValue);
-        mAppearAnimator.setInterpolator(mCurrentAppearInterpolator);
+        mAppearAnimator.setInterpolator(
+                useNonLinearAnimation() ? Interpolators.LINEAR : mCurrentAppearInterpolator);
         mAppearAnimator.setDuration(
                 (long) (duration * Math.abs(mAppearAnimationFraction - targetValue)));
         mAppearAnimator.addUpdateListener(animation -> {
@@ -530,7 +537,13 @@
      * @param clipSide Which side if view we want to clip from
      */
     private void updateAppearRect(ClipSide clipSide) {
-        float interpolatedFraction = mAppearAnimationFraction;
+        float interpolatedFraction;
+        if (useNonLinearAnimation()) {
+            interpolatedFraction = mCurrentAppearInterpolator.getInterpolation(
+                    mAppearAnimationFraction);
+        } else {
+            interpolatedFraction = mAppearAnimationFraction;
+        }
         mAppearAnimationTranslation = (1.0f - interpolatedFraction) * mAnimationTranslationY;
         final int fullHeight = getActualHeight();
         float height = fullHeight * interpolatedFraction;
@@ -558,6 +571,11 @@
         }
     }
 
+    private boolean useNonLinearAnimation() {
+        return notificationAppearNonlinear() && (!mIsHeadsUpCycling
+                || physicalNotificationMovement());
+    }
+
     private void updateAppearRect() {
         updateAppearRect(ClipSide.BOTTOM);
     }
@@ -567,7 +585,7 @@
                 mAppearAnimationFraction,
                 ALPHA_APPEAR_START_FRACTION,
                 ALPHA_APPEAR_END_FRACTION,
-                Interpolators.ALPHA_IN
+                notificationAppearNonlinear() ? mCurrentAppearInterpolator : Interpolators.ALPHA_IN
         );
     }
 
@@ -813,6 +831,7 @@
         pw.print("mDrawingAppearAnimation", mDrawingAppearAnimation);
         pw.print("mAppearAnimationFraction", mAppearAnimationFraction);
         pw.print("mIsHeadsUpAnimation", mIsHeadsUpAnimation);
+        pw.print("mIsHeadsUpCycling", mIsHeadsUpCycling);
         pw.print("mTargetPoint", mTargetPoint);
         pw.println();
     }
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 473460b..4c74408 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
@@ -2182,7 +2182,7 @@
                     R.dimen.notification_min_height);
         }
         mMaxSmallHeightWithSummarization = NotificationUtils.getFontScaledHeight(mContext,
-                com.android.internal.R.dimen.notification_collapsed_height_with_summarization);
+                com.android.internal.R.dimen.notification_min_height);
         mMaxExpandedHeight = NotificationUtils.getFontScaledHeight(mContext,
                 R.dimen.notification_max_height);
         mMaxExpandedHeightForPromotedOngoing = NotificationUtils.getFontScaledHeight(mContext,
@@ -3494,20 +3494,17 @@
 
     @Override
     public void performAddAnimation(long delay, long duration, boolean isHeadsUpAppear,
-            Runnable onFinishRunnable) {
+            boolean isHeadsUpCycling, Runnable onFinishRunnable) {
         mLogger.logStartAppearAnimation(mLoggingKey, /* isAppear = */ true);
-        super.performAddAnimation(delay, duration, isHeadsUpAppear, onFinishRunnable);
+        super.performAddAnimation(delay, duration, isHeadsUpAppear, isHeadsUpCycling,
+                onFinishRunnable);
     }
 
     @Override
-    public long performRemoveAnimation(
-            long duration,
-            long delay,
-            float translationDirection,
-            boolean isHeadsUpAnimation,
-            Runnable onStartedRunnable,
-            Runnable onFinishedRunnable,
-            AnimatorListenerAdapter animationListener, ClipSide clipSide) {
+    public long performRemoveAnimation(long duration, long delay, float translationDirection,
+            boolean isHeadsUpAnimation, boolean isHeadsUpCycling, Runnable onStartedRunnable,
+            Runnable onFinishedRunnable, AnimatorListenerAdapter animationListener,
+            ClipSide clipSide) {
         mLogger.logStartAppearAnimation(mLoggingKey, /* isAppear = */ false);
         if (mMenuRow != null && mMenuRow.isMenuVisible()) {
             Animator anim = getTranslateViewAnimator(0f, null /* listener */);
@@ -3522,9 +3519,9 @@
 
                     @Override
                     public void onAnimationEnd(Animator animation) {
-                        ExpandableNotificationRow.super.performRemoveAnimation(
-                                duration, delay, translationDirection, isHeadsUpAnimation,
-                                null, onFinishedRunnable, animationListener, ClipSide.BOTTOM);
+                        ExpandableNotificationRow.super.performRemoveAnimation(duration, delay,
+                                translationDirection, isHeadsUpAnimation, isHeadsUpCycling, null,
+                                onFinishedRunnable, animationListener, ClipSide.BOTTOM);
                     }
                 });
                 anim.start();
@@ -3532,8 +3529,8 @@
             }
         }
         return super.performRemoveAnimation(duration, delay, translationDirection,
-                isHeadsUpAnimation, onStartedRunnable, onFinishedRunnable, animationListener,
-                clipSide);
+                isHeadsUpAnimation, isHeadsUpCycling, onStartedRunnable, onFinishedRunnable,
+                animationListener, clipSide);
     }
 
     @Override
@@ -4265,6 +4262,7 @@
             pw.print(", isPinned: " + isPinned());
             pw.print(", expandedWhenPinned: " + mExpandedWhenPinned);
             pw.print(", isMinimized: " + mIsMinimized);
+            pw.print(", isAboveShelf: " + isAboveShelf());
 
             pw.println();
             if (NotificationContentView.INCLUDE_HEIGHTS_TO_DUMP) {
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/ExpandableView.java b/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/ExpandableView.java
index da664f8..292f74a 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/ExpandableView.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/ExpandableView.java
@@ -467,7 +467,8 @@
      *                             remove animation should be performed upwards,
      *                             such that the  child appears to be going away to the top. 1
      *                             Should mean the opposite.
-     * @param isHeadsUpAnimation   Is this a headsUp animation.
+     * @param isHeadsUpAnimation   Is this a headsUp animation
+     * @param isHeadsUpCycling     Is this the cycling heads up animation
      * @param onFinishedRunnable   A runnable which should be run when the animation is finished.
      * @param animationListener    An animation listener to add to the animation.
      * @return The additional delay, in milliseconds, that this view needs to add before the
@@ -475,7 +476,7 @@
      */
     public abstract long performRemoveAnimation(long duration,
             long delay, float translationDirection, boolean isHeadsUpAnimation,
-            Runnable onStartedRunnable,
+            boolean isHeadsUpCycling, Runnable onStartedRunnable,
             Runnable onFinishedRunnable,
             AnimatorListenerAdapter animationListener, ClipSide clipSide);
 
@@ -485,11 +486,12 @@
     }
 
     public void performAddAnimation(long delay, long duration, boolean isHeadsUpAppear) {
-        performAddAnimation(delay, duration, isHeadsUpAppear, null);
+        performAddAnimation(delay, duration, isHeadsUpAppear, false /* isHeadsUpCycling */,
+                null);
     }
 
     public abstract void performAddAnimation(long delay, long duration, boolean isHeadsUpAppear,
-            Runnable onEndRunnable);
+            boolean isHeadsUpCycling, Runnable onEndRunnable);
 
     public int getPinnedHeadsUpHeight() {
         return getIntrinsicHeight();
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/StackScrollerDecorView.java b/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/StackScrollerDecorView.java
index cd228e7..a064d1c 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/StackScrollerDecorView.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/StackScrollerDecorView.java
@@ -260,7 +260,7 @@
     @Override
     public long performRemoveAnimation(long duration, long delay,
             float translationDirection, boolean isHeadsUpAnimation,
-            Runnable onStartedRunnable,
+            boolean isHeadsUpCycling, Runnable onStartedRunnable,
             Runnable onFinishedRunnable,
             AnimatorListenerAdapter animationListener, ClipSide clipSide) {
         // TODO: Use duration
@@ -279,7 +279,7 @@
 
     @Override
     public void performAddAnimation(long delay, long duration, boolean isHeadsUpAppear,
-            Runnable endRunnable) {
+            boolean isHeadsUpCycling, Runnable endRunnable) {
         // TODO: use delay and duration
         setContentVisibleAnimated(true);
     }
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/wrapper/NotificationCallTemplateViewWrapper.kt b/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/wrapper/NotificationCallTemplateViewWrapper.kt
index 2d94694..e887e25 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/wrapper/NotificationCallTemplateViewWrapper.kt
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/wrapper/NotificationCallTemplateViewWrapper.kt
@@ -16,6 +16,7 @@
 
 package com.android.systemui.statusbar.notification.row.wrapper
 
+import android.app.Flags.notificationsRedesignTemplates
 import android.content.Context
 import android.view.View
 import com.android.internal.widget.CachingIconView
@@ -25,17 +26,13 @@
 import com.android.systemui.statusbar.notification.NotificationUtils
 import com.android.systemui.statusbar.notification.row.ExpandableNotificationRow
 
-/**
- * Wraps a notification containing a call template
- */
-class NotificationCallTemplateViewWrapper constructor(
-    ctx: Context,
-    view: View,
-    row: ExpandableNotificationRow
-) : NotificationTemplateViewWrapper(ctx, view, row) {
+/** Wraps a notification containing a call template */
+class NotificationCallTemplateViewWrapper
+constructor(ctx: Context, view: View, row: ExpandableNotificationRow) :
+    NotificationTemplateViewWrapper(ctx, view, row) {
 
     private val minHeightWithActions: Int =
-            NotificationUtils.getFontScaledHeight(ctx, R.dimen.notification_max_height)
+        NotificationUtils.getFontScaledHeight(ctx, R.dimen.notification_max_height)
     private val callLayout: CallLayout = view as CallLayout
 
     private lateinit var conversationIconContainer: View
@@ -48,13 +45,17 @@
     private fun resolveViews() {
         with(callLayout) {
             conversationIconContainer =
-                    requireViewById(com.android.internal.R.id.conversation_icon_container)
+                requireViewById(com.android.internal.R.id.conversation_icon_container)
             conversationIconView = requireViewById(com.android.internal.R.id.conversation_icon)
             conversationBadgeBg =
-                    requireViewById(com.android.internal.R.id.conversation_icon_badge_bg)
+                requireViewById(com.android.internal.R.id.conversation_icon_badge_bg)
             expandBtn = requireViewById(com.android.internal.R.id.expand_button)
             appName = requireViewById(com.android.internal.R.id.app_name_text)
-            conversationTitleView = requireViewById(com.android.internal.R.id.conversation_text)
+            conversationTitleView =
+                requireViewById(
+                    if (notificationsRedesignTemplates()) com.android.internal.R.id.title
+                    else com.android.internal.R.id.conversation_text
+                )
         }
     }
 
@@ -68,20 +69,12 @@
     override fun updateTransformedTypes() {
         // This also clears the existing types
         super.updateTransformedTypes()
-        addTransformedViews(
-                appName,
-                conversationTitleView
-        )
-        addViewsTransformingToSimilar(
-                conversationIconView,
-                conversationBadgeBg,
-                expandBtn
-        )
+        addTransformedViews(appName, conversationTitleView)
+        addViewsTransformingToSimilar(conversationIconView, conversationBadgeBg, expandBtn)
     }
 
     override fun disallowSingleClick(x: Float, y: Float): Boolean {
-        val isOnExpandButton = expandBtn.visibility == View.VISIBLE &&
-                isOnView(expandBtn, x, y)
+        val isOnExpandButton = expandBtn.visibility == View.VISIBLE && isOnView(expandBtn, x, y)
         return isOnExpandButton || super.disallowSingleClick(x, y)
     }
 
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/wrapper/NotificationConversationTemplateViewWrapper.kt b/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/wrapper/NotificationConversationTemplateViewWrapper.kt
index 9d13ab5..6a96fba 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/wrapper/NotificationConversationTemplateViewWrapper.kt
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/wrapper/NotificationConversationTemplateViewWrapper.kt
@@ -17,10 +17,12 @@
 package com.android.systemui.statusbar.notification.row.wrapper
 
 import android.app.Flags
+import android.app.Flags.notificationsRedesignTemplates
 import android.content.Context
 import android.graphics.drawable.AnimatedImageDrawable
 import android.view.View
 import android.view.ViewGroup
+import androidx.core.view.isVisible
 import com.android.internal.widget.CachingIconView
 import com.android.internal.widget.ConversationLayout
 import com.android.internal.widget.MessagingGroup
@@ -53,8 +55,8 @@
     private lateinit var badgeIconView: NotificationRowIconView
     private lateinit var conversationBadgeBg: View
     private lateinit var expandBtn: View
-    private lateinit var expandBtnContainer: View
-    private lateinit var imageMessageContainer: ViewGroup
+    private var expandBtnContainer: View? = null
+    private var imageMessageContainer: ViewGroup? = null
     private lateinit var messageContainers: ArrayList<MessagingGroup>
     private lateinit var messagingLinearLayout: MessagingLinearLayout
     private lateinit var conversationTitleView: View
@@ -78,10 +80,14 @@
             conversationBadgeBg =
                 requireViewById(com.android.internal.R.id.conversation_icon_badge_bg)
             expandBtn = requireViewById(com.android.internal.R.id.expand_button)
-            expandBtnContainer = requireViewById(com.android.internal.R.id.expand_button_container)
+            expandBtnContainer = findViewById(com.android.internal.R.id.expand_button_container)
             importanceRing = requireViewById(com.android.internal.R.id.conversation_icon_badge_ring)
             appName = requireViewById(com.android.internal.R.id.app_name_text)
-            conversationTitleView = requireViewById(com.android.internal.R.id.conversation_text)
+            conversationTitleView =
+                requireViewById(
+                    if (notificationsRedesignTemplates()) com.android.internal.R.id.title
+                    else com.android.internal.R.id.conversation_text
+                )
             facePileTop = findViewById(com.android.internal.R.id.conversation_face_pile_top)
             facePileBottom = findViewById(com.android.internal.R.id.conversation_face_pile_bottom)
             facePileBottomBg =
@@ -133,11 +139,21 @@
         expandable: Boolean,
         onClickListener: View.OnClickListener,
         requestLayout: Boolean,
-    ) = conversationLayout.updateExpandability(expandable, onClickListener)
+    ) {
+        if (notificationsRedesignTemplates()) {
+            super.updateExpandability(expandable, onClickListener, requestLayout)
+        } else {
+            conversationLayout.updateExpandability(expandable, onClickListener)
+        }
+    }
 
     override fun disallowSingleClick(x: Float, y: Float): Boolean {
         val isOnExpandButton =
-            expandBtnContainer.visibility == View.VISIBLE && isOnView(expandBtnContainer, x, y)
+            if (notificationsRedesignTemplates()) {
+                expandBtn.isVisible && isOnView(expandBtn, x, y)
+            } else {
+                expandBtnContainer?.visibility == View.VISIBLE && isOnView(expandBtnContainer, x, y)
+            }
         return isOnExpandButton || super.disallowSingleClick(x, y)
     }
 
@@ -158,7 +174,8 @@
         // and the top level image message container.
         val containers =
             messageContainers.asSequence().map { it.messageContainer } +
-                sequenceOf(imageMessageContainer)
+                if (notificationsRedesignTemplates()) emptySequence()
+                else sequenceOf(imageMessageContainer!!)
         val drawables =
             containers
                 .flatMap { it.children }
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/MediaContainerView.kt b/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/MediaContainerView.kt
index bd7bd59..4d10a52 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/MediaContainerView.kt
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/MediaContainerView.kt
@@ -70,14 +70,15 @@
     }
 
     override fun performRemoveAnimation(
-            duration: Long,
-            delay: Long,
-            translationDirection: Float,
-            isHeadsUpAnimation: Boolean,
-            onStartedRunnable: Runnable?,
-            onFinishedRunnable: Runnable?,
-            animationListener: AnimatorListenerAdapter?,
-            clipSide: ClipSide
+        duration: Long,
+        delay: Long,
+        translationDirection: Float,
+        isHeadsUpAnimation: Boolean,
+        isHeadsUpCycling: Boolean,
+        onStartedRunnable: Runnable?,
+        onFinishedRunnable: Runnable?,
+        animationListener: AnimatorListenerAdapter?,
+        clipSide: ClipSide,
     ): Long {
         return 0
     }
@@ -86,7 +87,8 @@
         delay: Long,
         duration: Long,
         isHeadsUpAppear: Boolean,
-        onEnd: Runnable?
+        isHeadsUpCycling: Boolean,
+        onEnd: Runnable?,
     ) {
         // No animation, it doesn't need it, this would be local
     }
@@ -103,9 +105,7 @@
         assertMediaContainerVisibility(visibility)
     }
 
-    /**
-     * visibility should be aligned with MediaContainerView visibility on the keyguard.
-     */
+    /** visibility should be aligned with MediaContainerView visibility on the keyguard. */
     private fun isVisibilityValid(visibility: Int): Boolean {
         val currentViewState = viewState as? MediaContainerViewState ?: return true
         val shouldBeGone = !currentViewState.shouldBeVisible
@@ -113,8 +113,7 @@
     }
 
     /**
-     * b/298213983
-     * MediaContainerView's visibility is changed to VISIBLE when it should be GONE.
+     * b/298213983 MediaContainerView's visibility is changed to VISIBLE when it should be GONE.
      * This method check this state and logs.
      */
     private fun assertMediaContainerVisibility(visibility: Int) {
@@ -122,8 +121,10 @@
 
         if (currentViewState is MediaContainerViewState) {
             if (!currentViewState.shouldBeVisible && visibility == VISIBLE) {
-                Log.wtf("MediaContainerView", "MediaContainerView should be GONE " +
-                        "but its visibility changed to VISIBLE")
+                Log.wtf(
+                    "MediaContainerView",
+                    "MediaContainerView should be GONE " + "but its visibility changed to VISIBLE",
+                )
             }
         }
     }
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/StackScrollAlgorithm.java b/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/StackScrollAlgorithm.java
index 06b989a..08692be 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/StackScrollAlgorithm.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/StackScrollAlgorithm.java
@@ -1227,13 +1227,17 @@
         float baseZ = ambientState.getBaseZHeight();
 
         if (SceneContainerFlag.isEnabled()) {
-            // SceneContainer flags off this logic, and just sets the baseZ because:
+            // SceneContainer simplifies this logic, because:
             // - there are no overlapping HUNs anymore, no need for multiplying their shadows
             // - shadows for HUNs overlapping with the stack are now set from updateHeadsUpStates
-            // - shadows for HUNs overlapping with the shelf are NOT set anymore, because it only
-            // happens on AOD/Pulsing, where they're displayed on a black background so a shadow
-            // wouldn't be visible.
-            childViewState.setZTranslation(baseZ);
+            if (child.isPinned() || ambientState.getTrackedHeadsUpRow() == child) {
+                // set a default elevation on the HUN, which would be overridden
+                // from updateHeadsUpStates if it is displayed in the shade
+                childViewState.setZTranslation(baseZ + mPinnedZTranslationExtra);
+            } else {
+                // set baseZ for every notification
+                childViewState.setZTranslation(baseZ);
+            }
         } else {
             if (child.mustStayOnScreen() && !childViewState.headsUpIsVisible
                     && !ambientState.isDozingAndNotPulsing(child)
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/StackStateAnimator.java b/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/StackStateAnimator.java
index 5e0d57e..2b05223 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/StackStateAnimator.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/StackStateAnimator.java
@@ -506,8 +506,8 @@
                 }
                 changingView.performRemoveAnimation(ANIMATION_DURATION_APPEAR_DISAPPEAR,
                         0 /* delay */, translationDirection, false /* isHeadsUpAppear */,
-                        startAnimation, postAnimation, getGlobalAnimationFinishedListener(),
-                        ExpandableView.ClipSide.BOTTOM);
+                        false /* isHeadsUpCycling */, startAnimation, postAnimation,
+                        getGlobalAnimationFinishedListener(), ExpandableView.ClipSide.BOTTOM);
                 needsCustomAnimation = true;
             } else if (event.animationType ==
                     NotificationStackScrollLayout.AnimationEvent.ANIMATION_TYPE_REMOVE_SWIPED_OUT) {
@@ -538,7 +538,7 @@
                     };
                 }
                 changingView.performAddAnimation(0, ANIMATION_DURATION_HEADS_UP_CYCLING,
-                        /* isHeadsUpAppear= */ true, onAnimationEnd);
+                        /* isHeadsUpAppear= */ true, /* isHeadsUpCycling= */ true, onAnimationEnd);
             } else if (event.animationType == ANIMATION_TYPE_HEADS_UP_APPEAR) {
                 mHeadsUpAppearChildren.add(changingView);
 
@@ -559,7 +559,7 @@
                     onAnimationEnd = () -> mLogger.appearAnimationEnded(finalKey);
                 }
                 changingView.performAddAnimation(0, ANIMATION_DURATION_HEADS_UP_APPEAR,
-                        /* isHeadsUpAppear= */ true, onAnimationEnd);
+                        /* isHeadsUpAppear= */ true, /* isHeadsUpCycling= */ false, onAnimationEnd);
             } else if (event.animationType == ANIMATION_TYPE_HEADS_UP_CYCLING_OUT) {
                 mHeadsUpDisappearChildren.add(changingView);
                 Runnable endRunnable = null;
@@ -629,6 +629,7 @@
                             // translation, the actual translation is in StackScrollAlgorithm.
                             /* translationDirection= */ 0.0f,
                             /* isHeadsUpAnimation= */ true,
+                            /* isHeadsUpCycling= */ true,
                             startAnimation, postAnimation,
                             getGlobalAnimationFinishedListener(), ExpandableView.ClipSide.TOP);
                     mAnimationProperties.delay += removeAnimationDelay;
@@ -706,6 +707,7 @@
                     long removeAnimationDelay = changingView.performRemoveAnimation(
                             ANIMATION_DURATION_HEADS_UP_DISAPPEAR,
                             0, 0.0f, true /* isHeadsUpAppear */,
+                            false /* isHeadsUpCycling */,
                             startAnimation, postAnimation,
                             getGlobalAnimationFinishedListener(), ExpandableView.ClipSide.BOTTOM);
                     mAnimationProperties.delay += removeAnimationDelay;
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/ui/viewmodel/SharedNotificationContainerViewModel.kt b/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/ui/viewmodel/SharedNotificationContainerViewModel.kt
index 5c81c8e..34b6560 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/ui/viewmodel/SharedNotificationContainerViewModel.kt
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/ui/viewmodel/SharedNotificationContainerViewModel.kt
@@ -20,6 +20,7 @@
 import android.content.Context
 import androidx.annotation.VisibleForTesting
 import com.android.app.tracing.coroutines.flow.flowName
+import com.android.systemui.Flags.glanceableHubV2
 import com.android.systemui.common.shared.model.NotificationContainerBounds
 import com.android.systemui.common.ui.domain.interactor.ConfigurationInteractor
 import com.android.systemui.communal.domain.interactor.CommunalSceneInteractor
@@ -87,7 +88,9 @@
 import com.android.systemui.statusbar.notification.stack.domain.interactor.NotificationStackAppearanceInteractor
 import com.android.systemui.statusbar.notification.stack.domain.interactor.SharedNotificationContainerInteractor
 import com.android.systemui.unfold.domain.interactor.UnfoldTransitionInteractor
+import com.android.systemui.util.kotlin.BooleanFlowOperators.allOf
 import com.android.systemui.util.kotlin.BooleanFlowOperators.anyOf
+import com.android.systemui.util.kotlin.BooleanFlowOperators.not
 import com.android.systemui.util.kotlin.FlowDumperImpl
 import com.android.systemui.util.kotlin.Utils.Companion.sample as sampleCombine
 import com.android.systemui.util.kotlin.sample
@@ -299,20 +302,40 @@
             .distinctUntilChanged()
             .dumpWhileCollecting("configurationBasedDimensions")
 
+    private val isOnAnyBouncer: Flow<Boolean> =
+        anyOf(
+            keyguardTransitionInteractor.transitionValue(ALTERNATE_BOUNCER).map { it > 0f },
+            keyguardTransitionInteractor
+                .transitionValue(
+                    scene = Scenes.Bouncer,
+                    stateWithoutSceneContainer = PRIMARY_BOUNCER,
+                )
+                .map { it > 0f },
+        )
+
     /** If the user is visually on one of the unoccluded lockscreen states. */
     val isOnLockscreen: Flow<Boolean> =
-        anyOf(
-                keyguardTransitionInteractor.transitionValue(AOD).map { it > 0f },
-                keyguardTransitionInteractor.transitionValue(DOZING).map { it > 0f },
-                keyguardTransitionInteractor.transitionValue(ALTERNATE_BOUNCER).map { it > 0f },
-                keyguardTransitionInteractor
-                    .transitionValue(
-                        scene = Scenes.Bouncer,
-                        stateWithoutSceneContainer = PRIMARY_BOUNCER,
-                    )
-                    .map { it > 0f },
-                keyguardTransitionInteractor.transitionValue(LOCKSCREEN).map { it > 0f },
-            )
+        if (glanceableHubV2()) {
+                anyOf(
+                    keyguardTransitionInteractor.transitionValue(AOD).map { it > 0f },
+                    keyguardTransitionInteractor.transitionValue(DOZING).map { it > 0f },
+                    keyguardTransitionInteractor.transitionValue(LOCKSCREEN).map { it > 0f },
+                    allOf(
+                        // Exclude bouncer showing over communal hub, as this should not be
+                        // considered
+                        // "lockscreen"
+                        not(communalSceneInteractor.isCommunalVisible),
+                        isOnAnyBouncer,
+                    ),
+                )
+            } else {
+                anyOf(
+                    keyguardTransitionInteractor.transitionValue(AOD).map { it > 0f },
+                    keyguardTransitionInteractor.transitionValue(DOZING).map { it > 0f },
+                    keyguardTransitionInteractor.transitionValue(LOCKSCREEN).map { it > 0f },
+                    isOnAnyBouncer,
+                )
+            }
             .flowName("isOnLockscreen")
             .stateIn(
                 scope = applicationScope,
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/mobile/data/repository/prod/MobileConnectionsRepositoryImpl.kt b/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/mobile/data/repository/prod/MobileConnectionsRepositoryImpl.kt
index d336903..2efc057 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/mobile/data/repository/prod/MobileConnectionsRepositoryImpl.kt
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/mobile/data/repository/prod/MobileConnectionsRepositoryImpl.kt
@@ -20,6 +20,7 @@
 import android.content.Context
 import android.content.Intent
 import android.content.IntentFilter
+import android.content.pm.PackageManager
 import android.telephony.CarrierConfigManager
 import android.telephony.SubscriptionInfo
 import android.telephony.SubscriptionManager
@@ -192,6 +193,19 @@
         serviceStateChangedEvent
             .mapLatest {
                 val modems = telephonyManager.activeModemCount
+
+                // Assume false for automotive devices which don't have the calling feature.
+                // TODO: b/398045526 to revisit the below.
+                val isAutomotive: Boolean =
+                    context.packageManager.hasSystemFeature(PackageManager.FEATURE_AUTOMOTIVE)
+                val hasFeatureCalling: Boolean =
+                    context.packageManager.hasSystemFeature(
+                        PackageManager.FEATURE_TELEPHONY_CALLING
+                    )
+                if (isAutomotive && !hasFeatureCalling) {
+                    return@mapLatest false
+                }
+
                 // Check the service state for every modem. If any state reports emergency calling
                 // capable, then consider the device to have emergency call capabilities
                 (0..<modems)
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/mobile/domain/interactor/MobileIconsInteractor.kt b/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/mobile/domain/interactor/MobileIconsInteractor.kt
index c52536d..10821df 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/mobile/domain/interactor/MobileIconsInteractor.kt
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/mobile/domain/interactor/MobileIconsInteractor.kt
@@ -21,7 +21,6 @@
 import android.telephony.SubscriptionManager
 import android.telephony.SubscriptionManager.PROFILE_CLASS_PROVISIONING
 import com.android.settingslib.SignalIcon.MobileIconGroup
-import com.android.settingslib.flags.Flags
 import com.android.settingslib.mobile.TelephonyIcons
 import com.android.systemui.dagger.SysUISingleton
 import com.android.systemui.dagger.qualifiers.Background
@@ -29,11 +28,13 @@
 import com.android.systemui.flags.Flags.FILTER_PROVISIONING_NETWORK_SUBSCRIPTIONS
 import com.android.systemui.log.table.TableLogBuffer
 import com.android.systemui.log.table.logDiffsForTable
+import com.android.systemui.statusbar.core.NewStatusBarIcons
 import com.android.systemui.statusbar.core.StatusBarRootModernization
 import com.android.systemui.statusbar.pipeline.dagger.MobileSummaryLog
 import com.android.systemui.statusbar.pipeline.mobile.data.model.SubscriptionModel
 import com.android.systemui.statusbar.pipeline.mobile.data.repository.MobileConnectionRepository
 import com.android.systemui.statusbar.pipeline.mobile.data.repository.MobileConnectionsRepository
+import com.android.systemui.statusbar.pipeline.mobile.domain.model.SignalIconModel
 import com.android.systemui.statusbar.pipeline.shared.data.model.ConnectivitySlot
 import com.android.systemui.statusbar.pipeline.shared.data.repository.ConnectivityRepository
 import com.android.systemui.statusbar.policy.data.repository.UserSetupRepository
@@ -85,6 +86,12 @@
     /** Whether the mobile icons can be stacked vertically. */
     val isStackable: StateFlow<Boolean>
 
+    /**
+     * Observable for the subscriptionId of the current mobile data connection. Null if we don't
+     * have a valid subscription id
+     */
+    val activeMobileDataSubscriptionId: StateFlow<Int?>
+
     /** True if the active mobile data subscription has data enabled */
     val activeDataConnectionHasDataEnabled: StateFlow<Boolean>
 
@@ -168,6 +175,9 @@
             )
             .stateIn(scope, SharingStarted.WhileSubscribed(), false)
 
+    override val activeMobileDataSubscriptionId: StateFlow<Int?> =
+        mobileConnectionsRepo.activeMobileDataSubscriptionId
+
     override val activeDataConnectionHasDataEnabled: StateFlow<Boolean> =
         mobileConnectionsRepo.activeMobileDataRepository
             .flatMapLatest { it?.dataEnabled ?: flowOf(false) }
@@ -298,10 +308,16 @@
             .stateIn(scope, SharingStarted.WhileSubscribed(), emptyList())
 
     override val isStackable =
-        if (Flags.newStatusBarIcons() && StatusBarRootModernization.isEnabled) {
+        if (NewStatusBarIcons.isEnabled && StatusBarRootModernization.isEnabled) {
                 icons.flatMapLatest { icons ->
-                    combine(icons.map { it.isNonTerrestrial }) {
-                        it.size == 2 && it.none { isNonTerrestrial -> isNonTerrestrial }
+                    combine(icons.map { it.signalLevelIcon }) { signalLevelIcons ->
+                        // These are only stackable if:
+                        // - They are cellular
+                        // - There's exactly two
+                        // - They have the same number of levels
+                        signalLevelIcons.filterIsInstance<SignalIconModel.Cellular>().let {
+                            it.size == 2 && it[0].numberOfLevels == it[1].numberOfLevels
+                        }
                     }
                 }
             } else {
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/mobile/ui/viewmodel/MobileIconsViewModel.kt b/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/mobile/ui/viewmodel/MobileIconsViewModel.kt
index 6176a3e..288e49e 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/mobile/ui/viewmodel/MobileIconsViewModel.kt
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/mobile/ui/viewmodel/MobileIconsViewModel.kt
@@ -63,6 +63,8 @@
     @VisibleForTesting
     val reuseCache = ConcurrentHashMap<Int, Pair<MobileIconViewModel, CoroutineScope>>()
 
+    val activeMobileDataSubscriptionId: StateFlow<Int?> = interactor.activeMobileDataSubscriptionId
+
     val subscriptionIdsFlow: StateFlow<List<Int>> =
         interactor.filteredSubscriptions
             .mapLatest { subscriptions ->
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/mobile/ui/viewmodel/StackedMobileIconViewModel.kt b/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/mobile/ui/viewmodel/StackedMobileIconViewModel.kt
index a2c2a3c..2c85a51 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/mobile/ui/viewmodel/StackedMobileIconViewModel.kt
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/mobile/ui/viewmodel/StackedMobileIconViewModel.kt
@@ -25,7 +25,7 @@
 import dagger.assisted.AssistedFactory
 import dagger.assisted.AssistedInject
 import kotlinx.coroutines.ExperimentalCoroutinesApi
-import kotlinx.coroutines.flow.StateFlow
+import kotlinx.coroutines.flow.Flow
 import kotlinx.coroutines.flow.combine
 import kotlinx.coroutines.flow.flatMapLatest
 import kotlinx.coroutines.flow.flowOf
@@ -43,8 +43,14 @@
             initialValue = false,
         )
 
-    private val iconViewModelFlow: StateFlow<List<MobileIconViewModelCommon>> =
-        mobileIconsViewModel.mobileSubViewModels
+    private val iconViewModelFlow: Flow<List<MobileIconViewModelCommon>> =
+        combine(
+            mobileIconsViewModel.mobileSubViewModels,
+            mobileIconsViewModel.activeMobileDataSubscriptionId,
+        ) { viewModels, activeSubId ->
+            // Sort to get the active subscription first, if it's set
+            viewModels.sortedByDescending { it.subscriptionId == activeSubId }
+        }
 
     val dualSim: DualSim? by
         hydrator.hydratedStateOf(
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/shared/ui/binder/HomeStatusBarViewBinder.kt b/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/shared/ui/binder/HomeStatusBarViewBinder.kt
index cd320a1..d734889 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/shared/ui/binder/HomeStatusBarViewBinder.kt
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/shared/ui/binder/HomeStatusBarViewBinder.kt
@@ -31,6 +31,8 @@
 import com.android.systemui.statusbar.chips.mediaprojection.domain.model.MediaProjectionStopDialogModel
 import com.android.systemui.statusbar.chips.notification.shared.StatusBarNotifChips
 import com.android.systemui.statusbar.chips.ui.binder.OngoingActivityChipBinder
+import com.android.systemui.statusbar.chips.ui.binder.OngoingActivityChipViewBinding
+import com.android.systemui.statusbar.chips.ui.model.MultipleOngoingActivityChipsModelLegacy
 import com.android.systemui.statusbar.chips.ui.model.OngoingActivityChipModel
 import com.android.systemui.statusbar.core.StatusBarConnectedDisplays
 import com.android.systemui.statusbar.core.StatusBarRootModernization
@@ -149,6 +151,7 @@
                 if (!StatusBarNotifChips.isEnabled && !StatusBarChipsModernization.isEnabled) {
                     val primaryChipViewBinding =
                         OngoingActivityChipBinder.createBinding(primaryChipView)
+
                     launch {
                         viewModel.primaryOngoingActivityChip.collect { primaryChipModel ->
                             OngoingActivityChipBinder.bind(
@@ -156,18 +159,14 @@
                                 primaryChipViewBinding,
                                 iconViewStore,
                             )
-                            if (StatusBarRootModernization.isEnabled) {
-                                when (primaryChipModel) {
-                                    is OngoingActivityChipModel.Active ->
-                                        primaryChipViewBinding.rootView.show(
-                                            shouldAnimateChange = true
-                                        )
 
-                                    is OngoingActivityChipModel.Inactive ->
-                                        primaryChipViewBinding.rootView.hide(
-                                            state = View.GONE,
-                                            shouldAnimateChange = primaryChipModel.shouldAnimate,
-                                        )
+                            if (StatusBarRootModernization.isEnabled) {
+                                launch {
+                                    bindLegacyPrimaryOngoingActivityChipWithVisibility(
+                                        viewModel,
+                                        primaryChipModel,
+                                        primaryChipViewBinding,
+                                    )
                                 }
                             } else {
                                 when (primaryChipModel) {
@@ -213,12 +212,14 @@
                             )
 
                             if (StatusBarRootModernization.isEnabled) {
-                                primaryChipViewBinding.rootView.adjustVisibility(
-                                    chips.primary.toVisibilityModel()
-                                )
-                                secondaryChipViewBinding.rootView.adjustVisibility(
-                                    chips.secondary.toVisibilityModel()
-                                )
+                                launch {
+                                    bindOngoingActivityChipsWithVisibility(
+                                        viewModel,
+                                        chips,
+                                        primaryChipViewBinding,
+                                        secondaryChipViewBinding,
+                                    )
+                                }
                             } else {
                                 listener?.onOngoingActivityStatusChanged(
                                     hasPrimaryOngoingActivity =
@@ -312,6 +313,52 @@
         }
     }
 
+    /** Bind the (legacy) single primary ongoing activity chip with the status bar visibility */
+    private suspend fun bindLegacyPrimaryOngoingActivityChipWithVisibility(
+        viewModel: HomeStatusBarViewModel,
+        primaryChipModel: OngoingActivityChipModel,
+        primaryChipViewBinding: OngoingActivityChipViewBinding,
+    ) {
+        viewModel.canShowOngoingActivityChips.collectLatest { visible ->
+            if (!visible) {
+                primaryChipViewBinding.rootView.hide(shouldAnimateChange = false)
+            } else {
+                when (primaryChipModel) {
+                    is OngoingActivityChipModel.Active -> {
+                        primaryChipViewBinding.rootView.show(shouldAnimateChange = true)
+                    }
+
+                    is OngoingActivityChipModel.Inactive -> {
+                        primaryChipViewBinding.rootView.hide(
+                            state = View.GONE,
+                            shouldAnimateChange = primaryChipModel.shouldAnimate,
+                        )
+                    }
+                }
+            }
+        }
+    }
+
+    /** Bind the primary/secondary chips along with the home status bar's visibility */
+    private suspend fun bindOngoingActivityChipsWithVisibility(
+        viewModel: HomeStatusBarViewModel,
+        chips: MultipleOngoingActivityChipsModelLegacy,
+        primaryChipViewBinding: OngoingActivityChipViewBinding,
+        secondaryChipViewBinding: OngoingActivityChipViewBinding,
+    ) {
+        viewModel.canShowOngoingActivityChips.collectLatest { canShow ->
+            if (!canShow) {
+                primaryChipViewBinding.rootView.hide(shouldAnimateChange = false)
+                secondaryChipViewBinding.rootView.hide(shouldAnimateChange = false)
+            } else {
+                primaryChipViewBinding.rootView.adjustVisibility(chips.primary.toVisibilityModel())
+                secondaryChipViewBinding.rootView.adjustVisibility(
+                    chips.secondary.toVisibilityModel()
+                )
+            }
+        }
+    }
+
     private fun SystemEventAnimationState.isAnimatingChip() =
         when (this) {
             AnimatingIn,
@@ -374,43 +421,42 @@
         if (visibility == View.INVISIBLE || visibility == View.GONE) {
             return
         }
+        alpha = 0f
         visibility = state
     }
 
     // See CollapsedStatusBarFragment#hide.
     private fun View.hide(state: Int = View.INVISIBLE, shouldAnimateChange: Boolean) {
+        animate().cancel()
         if (visibility == View.INVISIBLE || visibility == View.GONE) {
             return
         }
-        val v = this
-        v.animate().cancel()
         if (!shouldAnimateChange) {
-            v.alpha = 0f
-            v.visibility = state
+            alpha = 0f
+            visibility = state
             return
         }
 
-        v.animate()
+        animate()
             .alpha(0f)
             .setDuration(CollapsedStatusBarFragment.FADE_OUT_DURATION.toLong())
             .setStartDelay(0)
             .setInterpolator(Interpolators.ALPHA_OUT)
-            .withEndAction { v.visibility = state }
+            .withEndAction { visibility = state }
     }
 
     // See CollapsedStatusBarFragment#show.
     private fun View.show(shouldAnimateChange: Boolean) {
-        if (visibility == View.VISIBLE) {
+        animate().cancel()
+        if (visibility == View.VISIBLE && alpha >= 1f) {
             return
         }
-        val v = this
-        v.animate().cancel()
-        v.visibility = View.VISIBLE
+        visibility = View.VISIBLE
         if (!shouldAnimateChange) {
-            v.alpha = 1f
+            alpha = 1f
             return
         }
-        v.animate()
+        animate()
             .alpha(1f)
             .setDuration(CollapsedStatusBarFragment.FADE_IN_DURATION.toLong())
             .setInterpolator(Interpolators.ALPHA_IN)
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/shared/ui/composable/StackedMobileIcon.kt b/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/shared/ui/composable/StackedMobileIcon.kt
index 465a43f..a51da0e 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/shared/ui/composable/StackedMobileIcon.kt
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/shared/ui/composable/StackedMobileIcon.kt
@@ -17,9 +17,12 @@
 package com.android.systemui.statusbar.pipeline.shared.ui.composable
 
 import androidx.compose.foundation.Canvas
+import androidx.compose.foundation.layout.Arrangement.spacedBy
 import androidx.compose.foundation.layout.Row
+import androidx.compose.foundation.layout.fillMaxHeight
+import androidx.compose.foundation.layout.height
 import androidx.compose.foundation.layout.padding
-import androidx.compose.foundation.layout.size
+import androidx.compose.foundation.layout.width
 import androidx.compose.material3.LocalContentColor
 import androidx.compose.runtime.Composable
 import androidx.compose.ui.Alignment
@@ -27,23 +30,28 @@
 import androidx.compose.ui.geometry.CornerRadius
 import androidx.compose.ui.geometry.Offset
 import androidx.compose.ui.geometry.Size
+import androidx.compose.ui.graphics.BlendMode
 import androidx.compose.ui.graphics.Color
 import androidx.compose.ui.graphics.drawscope.DrawScope
 import androidx.compose.ui.platform.LocalDensity
 import androidx.compose.ui.unit.TextUnit
-import androidx.compose.ui.unit.dp
 import androidx.compose.ui.unit.sp
-import com.android.compose.modifiers.height
-import com.android.compose.modifiers.width
 import com.android.systemui.common.ui.compose.Icon
 import com.android.systemui.statusbar.pipeline.mobile.ui.viewmodel.StackedMobileIconViewModel
 import com.android.systemui.statusbar.pipeline.shared.ui.composable.StackedMobileIconDimensions.BarBaseHeightFiveBarsSp
 import com.android.systemui.statusbar.pipeline.shared.ui.composable.StackedMobileIconDimensions.BarBaseHeightFourBarsSp
 import com.android.systemui.statusbar.pipeline.shared.ui.composable.StackedMobileIconDimensions.BarsLevelIncrementSp
 import com.android.systemui.statusbar.pipeline.shared.ui.composable.StackedMobileIconDimensions.BarsVerticalPaddingSp
+import com.android.systemui.statusbar.pipeline.shared.ui.composable.StackedMobileIconDimensions.ExclamationCutoutRadiusSp
+import com.android.systemui.statusbar.pipeline.shared.ui.composable.StackedMobileIconDimensions.ExclamationDiameterSp
+import com.android.systemui.statusbar.pipeline.shared.ui.composable.StackedMobileIconDimensions.ExclamationHeightSp
+import com.android.systemui.statusbar.pipeline.shared.ui.composable.StackedMobileIconDimensions.ExclamationHorizontalOffset
+import com.android.systemui.statusbar.pipeline.shared.ui.composable.StackedMobileIconDimensions.ExclamationVerticalSpacing
 import com.android.systemui.statusbar.pipeline.shared.ui.composable.StackedMobileIconDimensions.HorizontalPaddingFiveBarsSp
 import com.android.systemui.statusbar.pipeline.shared.ui.composable.StackedMobileIconDimensions.HorizontalPaddingFourBarsSp
 import com.android.systemui.statusbar.pipeline.shared.ui.composable.StackedMobileIconDimensions.IconHeightSp
+import com.android.systemui.statusbar.pipeline.shared.ui.composable.StackedMobileIconDimensions.IconPaddingSp
+import com.android.systemui.statusbar.pipeline.shared.ui.composable.StackedMobileIconDimensions.IconSpacingSp
 import com.android.systemui.statusbar.pipeline.shared.ui.composable.StackedMobileIconDimensions.IconWidthFiveBarsSp
 import com.android.systemui.statusbar.pipeline.shared.ui.composable.StackedMobileIconDimensions.IconWidthFourBarsSp
 import com.android.systemui.statusbar.pipeline.shared.ui.composable.StackedMobileIconDimensions.SecondaryBarHeightSp
@@ -58,15 +66,16 @@
     val dualSim = viewModel.dualSim ?: return
 
     val contentColor = LocalContentColor.current
+    val padding = with(LocalDensity.current) { IconPaddingSp.toDp() }
+    val horizontalArrangement = with(LocalDensity.current) { spacedBy(IconSpacingSp.toDp()) }
 
-    Row(verticalAlignment = Alignment.CenterVertically, modifier = modifier) {
+    Row(
+        horizontalArrangement = horizontalArrangement,
+        verticalAlignment = Alignment.CenterVertically,
+        modifier = modifier.padding(horizontal = padding),
+    ) {
         viewModel.networkTypeIcon?.let {
-            Icon(
-                it,
-                tint = contentColor,
-                modifier =
-                    Modifier.height { IconHeightSp.roundToPx() }.padding(start = 1.dp, end = 2.dp),
-            )
+            Icon(it, tint = contentColor, modifier = Modifier.fillMaxHeight())
         }
 
         StackedMobileIcon(dualSim, contentColor)
@@ -79,23 +88,23 @@
     color: Color,
     modifier: Modifier = Modifier,
 ) {
-    val maxNumberOfLevels =
-        max(viewModel.primary.numberOfLevels, viewModel.secondary.numberOfLevels)
-    val dimensions = if (maxNumberOfLevels == 6) FiveBarsDimensions else FourBarsDimensions
+    // Removing 1 to get the real number of bars
+    val numberOfBars = max(viewModel.primary.numberOfLevels, viewModel.secondary.numberOfLevels) - 1
+    val dimensions = if (numberOfBars == 5) FiveBarsDimensions else FourBarsDimensions
     val iconSize =
         with(LocalDensity.current) { dimensions.totalWidth.toDp() to IconHeightSp.toDp() }
 
-    Canvas(modifier.size(width = iconSize.first, height = iconSize.second)) {
+    Canvas(modifier.width(iconSize.first).height(iconSize.second)) {
         val verticalPaddingPx = BarsVerticalPaddingSp.roundToPx()
         val horizontalPaddingPx = dimensions.barsHorizontalPadding.roundToPx()
-        val totalPaddingWidthPx = horizontalPaddingPx * (maxNumberOfLevels - 1)
+        val totalPaddingWidthPx = horizontalPaddingPx * (numberOfBars - 1)
 
-        val barWidthPx = (size.width - totalPaddingWidthPx) / maxNumberOfLevels
+        val barWidthPx = (size.width - totalPaddingWidthPx) / numberOfBars
         val dotHeightPx = SecondaryBarHeightSp.toPx()
         val baseBarHeightPx = dimensions.barBaseHeight.toPx()
 
         var xOffsetPx = 0f
-        for (bar in 1..maxNumberOfLevels) {
+        for (bar in 1..numberOfBars) {
             // Bottom dots representing secondary sim
             val dotYOffsetPx = size.height - dotHeightPx
             if (bar <= viewModel.secondary.numberOfLevels) {
@@ -123,6 +132,10 @@
 
             xOffsetPx += barWidthPx + horizontalPaddingPx
         }
+
+        if (viewModel.primary.showExclamationMark) {
+            drawExclamationCutout(color)
+        }
     }
 }
 
@@ -143,6 +156,39 @@
     )
 }
 
+private fun DrawScope.drawExclamationCutout(color: Color) {
+    // Exclamation mark is bottom aligned with the canvas
+    val exclamationDiameterPx = ExclamationDiameterSp.toPx()
+    val exclamationRadiusPx = ExclamationDiameterSp.toPx() / 2
+    val exclamationTotalHeight =
+        ExclamationHeightSp.toPx() + ExclamationVerticalSpacing.toPx() + exclamationDiameterPx
+    val exclamationDotCenter =
+        Offset(size.width - ExclamationHorizontalOffset.toPx(), size.height - exclamationRadiusPx)
+    val exclamationMarkTopLeft =
+        Offset(exclamationDotCenter.x - exclamationRadiusPx, size.height - exclamationTotalHeight)
+    val exclamationCornerRadius = CornerRadius(exclamationRadiusPx)
+    val cutoutCenter = Offset(exclamationDotCenter.x, size.height - (exclamationTotalHeight / 2))
+
+    // Transparent cutout
+    drawCircle(
+        color = Color.Transparent,
+        radius = ExclamationCutoutRadiusSp.toPx(),
+        center = cutoutCenter,
+        blendMode = BlendMode.SrcIn,
+    )
+
+    // Top bar for the exclamation mark
+    drawRoundRect(
+        color = color,
+        topLeft = exclamationMarkTopLeft,
+        size = Size(exclamationDiameterPx, ExclamationHeightSp.toPx()),
+        cornerRadius = exclamationCornerRadius,
+    )
+
+    // Bottom circle for the exclamation mark
+    drawCircle(color = color, center = exclamationDotCenter, radius = exclamationRadiusPx)
+}
+
 private abstract class BarsDependentDimensions(
     val totalWidth: TextUnit,
     val barsHorizontalPadding: TextUnit,
@@ -166,10 +212,19 @@
 private object StackedMobileIconDimensions {
     // Common dimensions
     val IconHeightSp = 12.sp
+    val IconPaddingSp = 4.sp
+    val IconSpacingSp = 2.sp
     val BarsVerticalPaddingSp = 1.5.sp
     val BarsLevelIncrementSp = 1.sp
     val SecondaryBarHeightSp = 3.sp
 
+    // Exclamation cutout dimensions
+    val ExclamationCutoutRadiusSp = 5.sp
+    val ExclamationDiameterSp = 1.5.sp
+    val ExclamationHeightSp = 4.5.sp
+    val ExclamationVerticalSpacing = 1.sp
+    val ExclamationHorizontalOffset = 1.sp
+
     // Dimensions dependant on the number of total bars
     val IconWidthFiveBarsSp = 18.5.sp
     val IconWidthFourBarsSp = 16.sp
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/shared/ui/composable/StatusBarRoot.kt b/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/shared/ui/composable/StatusBarRoot.kt
index 39a1b46..4189221 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/shared/ui/composable/StatusBarRoot.kt
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/shared/ui/composable/StatusBarRoot.kt
@@ -37,6 +37,10 @@
 import com.android.compose.theme.PlatformTheme
 import com.android.keyguard.AlphaOptimizedLinearLayout
 import com.android.systemui.lifecycle.rememberViewModel
+import com.android.systemui.media.controls.ui.controller.MediaHierarchyManager
+import com.android.systemui.media.controls.ui.view.MediaHost
+import com.android.systemui.media.controls.ui.view.MediaHostState
+import com.android.systemui.media.dagger.MediaModule.POPUP
 import com.android.systemui.plugins.DarkIconDispatcher
 import com.android.systemui.res.R
 import com.android.systemui.statusbar.chips.ui.compose.OngoingActivityChips
@@ -67,6 +71,7 @@
 import com.android.systemui.statusbar.pipeline.shared.ui.viewmodel.HomeStatusBarViewModel
 import com.android.systemui.statusbar.pipeline.shared.ui.viewmodel.HomeStatusBarViewModel.HomeStatusBarViewModelFactory
 import javax.inject.Inject
+import javax.inject.Named
 
 /** Factory to simplify the dependency management for [StatusBarRoot] */
 class StatusBarRootFactory
@@ -81,6 +86,8 @@
     private val ongoingCallController: OngoingCallController,
     private val darkIconDispatcherStore: DarkIconDispatcherStore,
     private val eventAnimationInteractor: SystemStatusEventAnimationInteractor,
+    private val mediaHierarchyManager: MediaHierarchyManager,
+    @Named(POPUP) private val mediaHost: MediaHost,
 ) {
     fun create(root: ViewGroup, andThen: (ViewGroup) -> Unit): ComposeView {
         val composeView = ComposeView(root.context)
@@ -99,6 +106,8 @@
                     ongoingCallController = ongoingCallController,
                     darkIconDispatcher = darkIconDispatcher,
                     eventAnimationInteractor = eventAnimationInteractor,
+                    mediaHierarchyManager = mediaHierarchyManager,
+                    mediaHost = mediaHost,
                     onViewCreated = andThen,
                 )
             }
@@ -130,6 +139,8 @@
     ongoingCallController: OngoingCallController,
     darkIconDispatcher: DarkIconDispatcher,
     eventAnimationInteractor: SystemStatusEventAnimationInteractor,
+    mediaHierarchyManager: MediaHierarchyManager,
+    mediaHost: MediaHost,
     onViewCreated: (ViewGroup) -> Unit,
 ) {
     val displayId = parent.context.displayId
@@ -237,6 +248,15 @@
 
                     // Add a composable container for `StatusBarPopupChip`s
                     if (StatusBarPopupChips.isEnabled) {
+                        with(mediaHost) {
+                            expansion = MediaHostState.EXPANDED
+                            expandedMatchesParentHeight = true
+                            showsOnlyActiveMedia = true
+                            falsingProtectionNeeded = false
+                            disablePagination = true
+                            init(MediaHierarchyManager.LOCATION_STATUS_BAR_POPUP)
+                        }
+
                         val endSideContent =
                             phoneStatusBarView.requireViewById<AlphaOptimizedLinearLayout>(
                                 R.id.status_bar_end_side_content
@@ -256,7 +276,12 @@
 
                                 setContent {
                                     StatusBarPopupChipsContainer(
-                                        chips = statusBarViewModel.popupChips
+                                        chips = statusBarViewModel.popupChips,
+                                        mediaHost = mediaHost,
+                                        onMediaControlPopupVisibilityChanged = { popupShowing ->
+                                            mediaHierarchyManager.isMediaControlPopupShowing =
+                                                popupShowing
+                                        },
                                     )
                                 }
                             }
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/shared/ui/viewmodel/HomeStatusBarViewModel.kt b/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/shared/ui/viewmodel/HomeStatusBarViewModel.kt
index 9ae2cb2..807e905 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/shared/ui/viewmodel/HomeStatusBarViewModel.kt
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/shared/ui/viewmodel/HomeStatusBarViewModel.kt
@@ -149,6 +149,9 @@
      */
     val isHomeStatusBarAllowedByScene: StateFlow<Boolean>
 
+    /** True if the home status bar is showing, and there is no HUN happening */
+    val canShowOngoingActivityChips: Flow<Boolean>
+
     /** True if the operator name view is not hidden due to HUN or other visibility state */
     val shouldShowOperatorNameView: Flow<Boolean>
     val isClockVisible: Flow<VisibilityModel>
@@ -412,6 +415,15 @@
             )
             .flowOn(bgDispatcher)
 
+    override val canShowOngoingActivityChips: Flow<Boolean> =
+        combine(
+            isHomeStatusBarAllowed,
+            keyguardInteractor.isSecureCameraActive,
+            headsUpNotificationInteractor.statusBarHeadsUpStatus,
+        ) { isHomeStatusBarAllowed, isSecureCameraActive, headsUpState ->
+            isHomeStatusBarAllowed && !isSecureCameraActive && !headsUpState.isPinned
+        }
+
     override val isClockVisible: Flow<VisibilityModel> =
         combine(
                 shouldHomeStatusBarBeVisible,
diff --git a/packages/SystemUI/src/com/android/systemui/user/domain/interactor/UserLockedInteractor.kt b/packages/SystemUI/src/com/android/systemui/user/domain/interactor/UserLockedInteractor.kt
index ef29a38..3bd8af6 100644
--- a/packages/SystemUI/src/com/android/systemui/user/domain/interactor/UserLockedInteractor.kt
+++ b/packages/SystemUI/src/com/android/systemui/user/domain/interactor/UserLockedInteractor.kt
@@ -18,12 +18,20 @@
 
 import android.os.UserHandle
 import com.android.systemui.dagger.SysUISingleton
+import com.android.systemui.dagger.qualifiers.Background
 import com.android.systemui.user.data.repository.UserRepository
 import javax.inject.Inject
+import kotlinx.coroutines.CoroutineDispatcher
 import kotlinx.coroutines.flow.Flow
+import kotlinx.coroutines.flow.flowOn
 
 @SysUISingleton
-class UserLockedInteractor @Inject constructor(val userRepository: UserRepository) {
+class UserLockedInteractor
+@Inject
+constructor(
+    @Background val backgroundDispatcher: CoroutineDispatcher,
+    val userRepository: UserRepository,
+) {
     fun isUserUnlocked(userHandle: UserHandle?): Flow<Boolean> =
-        userRepository.isUserUnlocked(userHandle)
+        userRepository.isUserUnlocked(userHandle).flowOn(backgroundDispatcher)
 }
diff --git a/packages/SystemUI/src/com/android/systemui/volume/dialog/ringer/ui/binder/VolumeDialogRingerViewBinder.kt b/packages/SystemUI/src/com/android/systemui/volume/dialog/ringer/ui/binder/VolumeDialogRingerViewBinder.kt
index 0bdf99e..14eede6 100644
--- a/packages/SystemUI/src/com/android/systemui/volume/dialog/ringer/ui/binder/VolumeDialogRingerViewBinder.kt
+++ b/packages/SystemUI/src/com/android/systemui/volume/dialog/ringer/ui/binder/VolumeDialogRingerViewBinder.kt
@@ -24,6 +24,7 @@
 import androidx.annotation.LayoutRes
 import androidx.compose.ui.util.fastForEachIndexed
 import androidx.constraintlayout.motion.widget.MotionLayout
+import androidx.constraintlayout.motion.widget.MotionScene
 import androidx.dynamicanimation.animation.FloatValueHolder
 import androidx.dynamicanimation.animation.SpringAnimation
 import androidx.dynamicanimation.animation.SpringForce
@@ -47,6 +48,7 @@
 import javax.inject.Inject
 import kotlin.properties.Delegates
 import kotlinx.coroutines.CoroutineScope
+import kotlinx.coroutines.ExperimentalCoroutinesApi
 import kotlinx.coroutines.coroutineScope
 import kotlinx.coroutines.delay
 import kotlinx.coroutines.flow.mapLatest
@@ -55,6 +57,7 @@
 // Ensure roundness and color of button is updated when progress is changed by a minimum fraction.
 private const val BUTTON_MIN_VISIBLE_CHANGE = 0.05F
 
+@OptIn(ExperimentalCoroutinesApi::class)
 @VolumeDialogScope
 class VolumeDialogRingerViewBinder
 @Inject
@@ -208,6 +211,13 @@
                                     ringerState.orientation,
                                     ringerBackgroundView,
                                 )
+                                drawerContainer
+                                    .getTransition(R.id.close_to_open_transition)
+                                    .setInterpolatorInfo(
+                                        MotionScene.Transition.INTERPOLATE_REFERENCE_ID,
+                                        null,
+                                        R.anim.volume_dialog_ringer_open,
+                                    )
                                 drawerContainer.transitionToState(
                                     R.id.volume_dialog_ringer_drawer_open
                                 )
@@ -370,6 +380,12 @@
         orientation: Int,
     ) {
         setTransition(R.id.close_to_open_transition)
+        getTransition(R.id.close_to_open_transition)
+            .setInterpolatorInfo(
+                MotionScene.Transition.INTERPOLATE_REFERENCE_ID,
+                null,
+                R.anim.volume_dialog_ringer_close,
+            )
         updateCloseState(this, selectedIndex, orientation, ringerBackground)
         transitionToState(R.id.volume_dialog_ringer_drawer_close)
     }
diff --git a/packages/SystemUI/tests/src/com/android/systemui/animation/FontInterpolatorTest.kt b/packages/SystemUI/tests/src/com/android/systemui/animation/FontInterpolatorTest.kt
index 2e63439..c2a495d 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/animation/FontInterpolatorTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/animation/FontInterpolatorTest.kt
@@ -64,6 +64,12 @@
             "'wght' 500, 'ital' 0.5, 'GRAD' 450",
             interp.lerp(startFont, endFont, 0.5f, 0.5f),
         )
+
+        // Ensure axes rounded correctly to nearest step
+        assertSameAxes(
+            "'wght' 490, 'ital' 0.5, 'GRAD' 446",
+            interp.lerp(startFont, endFont, 0.492f, 0.492f),
+        )
     }
 
     @Test
diff --git a/packages/SystemUI/tests/src/com/android/systemui/media/controls/ui/animation/ColorSchemeTransitionTest.kt b/packages/SystemUI/tests/src/com/android/systemui/media/controls/ui/animation/ColorSchemeTransitionTest.kt
index 4d0605f..90a8ad5 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/media/controls/ui/animation/ColorSchemeTransitionTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/media/controls/ui/animation/ColorSchemeTransitionTest.kt
@@ -18,9 +18,12 @@
 
 import android.animation.ValueAnimator
 import android.graphics.Color
+import android.platform.test.annotations.DisableFlags
+import android.platform.test.annotations.EnableFlags
 import android.testing.TestableLooper
 import androidx.test.ext.junit.runners.AndroidJUnit4
 import androidx.test.filters.SmallTest
+import com.android.systemui.Flags
 import com.android.systemui.SysuiTestCase
 import com.android.systemui.media.controls.ui.view.GutsViewHolder
 import com.android.systemui.media.controls.ui.view.MediaViewHolder
@@ -33,6 +36,7 @@
 import org.junit.Rule
 import org.junit.Test
 import org.junit.runner.RunWith
+import org.mockito.ArgumentMatchers.anyInt
 import org.mockito.Mock
 import org.mockito.Mockito.never
 import org.mockito.Mockito.times
@@ -49,7 +53,9 @@
 class ColorSchemeTransitionTest : SysuiTestCase() {
 
     private interface ExtractCB : (ColorScheme) -> Int
+
     private interface ApplyCB : (Int) -> Unit
+
     private lateinit var colorTransition: AnimatingColorTransition
     private lateinit var colorSchemeTransition: ColorSchemeTransition
 
@@ -79,7 +85,7 @@
                 mediaViewHolder,
                 multiRippleController,
                 turbulenceNoiseController,
-                animatingColorTransitionFactory
+                animatingColorTransitionFactory,
             )
 
         colorTransition =
@@ -154,10 +160,21 @@
         verify(applyColor).invoke(expectedColor)
     }
 
+    @DisableFlags(Flags.FLAG_MEDIA_CONTROLS_A11Y_COLORS)
     @Test
-    fun testColorSchemeTransition_update() {
+    fun testColorSchemeTransition_update_legacy() {
         colorSchemeTransition.updateColorScheme(colorScheme)
         verify(mockAnimatingTransition, times(8)).updateColorScheme(colorScheme)
         verify(gutsViewHolder).colorScheme = colorScheme
     }
+
+    @EnableFlags(Flags.FLAG_MEDIA_CONTROLS_A11Y_COLORS)
+    @Test
+    fun testColorSchemeTransition_update() {
+        colorSchemeTransition.updateColorScheme(colorScheme)
+        verify(mockAnimatingTransition, times(3)).updateColorScheme(colorScheme)
+        verify(gutsViewHolder).setColors(colorScheme)
+        verify(multiRippleController).updateColor(anyInt())
+        verify(turbulenceNoiseController).updateNoiseColor(anyInt())
+    }
 }
diff --git a/packages/SystemUI/tests/src/com/android/systemui/media/controls/ui/controller/MediaHierarchyManagerTest.kt b/packages/SystemUI/tests/src/com/android/systemui/media/controls/ui/controller/MediaHierarchyManagerTest.kt
index 6ca4ae2..14b1412 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/media/controls/ui/controller/MediaHierarchyManagerTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/media/controls/ui/controller/MediaHierarchyManagerTest.kt
@@ -17,6 +17,7 @@
 package com.android.systemui.media.controls.ui.controller
 
 import android.graphics.Rect
+import android.platform.test.annotations.EnableFlags
 import android.provider.Settings
 import android.testing.TestableLooper
 import android.view.ViewGroup
@@ -36,7 +37,6 @@
 import com.android.systemui.keyguard.WakefulnessLifecycle
 import com.android.systemui.keyguard.data.repository.fakeKeyguardRepository
 import com.android.systemui.keyguard.data.repository.fakeKeyguardTransitionRepository
-import com.android.systemui.keyguard.data.repository.keyguardRepository
 import com.android.systemui.keyguard.domain.interactor.keyguardInteractor
 import com.android.systemui.keyguard.shared.model.KeyguardState
 import com.android.systemui.kosmos.testScope
@@ -50,6 +50,7 @@
 import com.android.systemui.shade.domain.interactor.ShadeInteractor
 import com.android.systemui.statusbar.StatusBarState
 import com.android.systemui.statusbar.SysuiStatusBarStateController
+import com.android.systemui.statusbar.featurepods.popups.StatusBarPopupChips
 import com.android.systemui.statusbar.phone.KeyguardBypassController
 import com.android.systemui.statusbar.policy.FakeConfigurationController
 import com.android.systemui.statusbar.policy.KeyguardStateController
@@ -581,6 +582,36 @@
         }
 
     @Test
+    @EnableFlags(StatusBarPopupChips.FLAG_NAME)
+    fun testStatusBarPopupLocation() =
+        testScope.runTest {
+            mediaHierarchyManager.isMediaControlPopupShowing = true
+            runCurrent()
+
+            verify(mediaCarouselController)
+                .onDesiredLocationChanged(
+                    eq(MediaHierarchyManager.LOCATION_STATUS_BAR_POPUP),
+                    nullable(),
+                    eq(false),
+                    anyLong(),
+                    anyLong(),
+                )
+            clearInvocations(mediaCarouselController)
+
+            mediaHierarchyManager.isMediaControlPopupShowing = false
+            runCurrent()
+
+            verify(mediaCarouselController)
+                .onDesiredLocationChanged(
+                    eq(MediaHierarchyManager.LOCATION_QQS),
+                    any<MediaHostState>(),
+                    eq(false),
+                    anyLong(),
+                    anyLong(),
+                )
+        }
+
+    @Test
     fun testCommunalLocationVisibilityWithShadeShowing() =
         testScope.runTest {
             whenever(mediaDataManager.hasActiveMediaOrRecommendation()).thenReturn(true)
diff --git a/packages/SystemUI/tests/src/com/android/systemui/qs/tiles/BluetoothTileTest.kt b/packages/SystemUI/tests/src/com/android/systemui/qs/tiles/BluetoothTileTest.kt
index cfe34f4..a7464e6 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/qs/tiles/BluetoothTileTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/qs/tiles/BluetoothTileTest.kt
@@ -39,6 +39,7 @@
 import com.android.systemui.util.mockito.mock
 import com.android.systemui.util.mockito.whenever
 import com.google.common.truth.Truth.assertThat
+import dagger.Lazy
 import kotlin.test.assertTrue
 import kotlinx.coroutines.Job
 import org.junit.After
@@ -96,8 +97,7 @@
                 qsLogger,
                 bluetoothController,
                 featureFlags,
-                bluetoothDetailsContentViewModel,
-            )
+            ) { bluetoothDetailsContentViewModel }
 
         tile.initialize()
         testableLooper.processAllMessages()
@@ -308,7 +308,7 @@
         qsLogger: QSLogger,
         bluetoothController: BluetoothController,
         featureFlags: FeatureFlagsClassic,
-        bluetoothDetailsContentViewModel: BluetoothDetailsContentViewModel,
+        lazyBluetoothDetailsContentViewModel: Lazy<BluetoothDetailsContentViewModel>,
     ) :
         BluetoothTile(
             qsHost,
@@ -322,7 +322,7 @@
             qsLogger,
             bluetoothController,
             featureFlags,
-            bluetoothDetailsContentViewModel,
+            lazyBluetoothDetailsContentViewModel,
         ) {
         var restrictionChecked: String? = null
 
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 e8b50d5..81213ca 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
@@ -130,7 +130,7 @@
     private CollectionReadyForBuildListener mReadyForBuildListener;
     private List<NotificationEntryBuilder> mPendingSet = new ArrayList<>();
     private List<NotificationEntry> mEntrySet = new ArrayList<>();
-    private List<ListEntry> mBuiltList = new ArrayList<>();
+    private List<PipelineEntry> mBuiltList = new ArrayList<>();
     private TestableStabilityManager mStabilityManager;
     private TestableNotifFilter mFinalizeFilter;
 
@@ -723,26 +723,26 @@
 
     @Test
     public void testNotifSectionsChildrenUpdated() {
-        ArrayList<ListEntry> pkg1Entries = new ArrayList<>();
-        ArrayList<ListEntry> pkg2Entries = new ArrayList<>();
-        ArrayList<ListEntry> pkg3Entries = new ArrayList<>();
+        ArrayList<PipelineEntry> pkg1Entries = new ArrayList<>();
+        ArrayList<PipelineEntry> pkg2Entries = new ArrayList<>();
+        ArrayList<PipelineEntry> pkg3Entries = new ArrayList<>();
         final NotifSectioner pkg1Sectioner = spy(new PackageSectioner(PACKAGE_1) {
             @Override
-            public void onEntriesUpdated(List<ListEntry> entries) {
+            public void onEntriesUpdated(List<PipelineEntry> entries) {
                 super.onEntriesUpdated(entries);
                 pkg1Entries.addAll(entries);
             }
         });
         final NotifSectioner pkg2Sectioner = spy(new PackageSectioner(PACKAGE_2) {
             @Override
-            public void onEntriesUpdated(List<ListEntry> entries) {
+            public void onEntriesUpdated(List<PipelineEntry> entries) {
                 super.onEntriesUpdated(entries);
                 pkg2Entries.addAll(entries);
             }
         });
         final NotifSectioner pkg3Sectioner = spy(new PackageSectioner(PACKAGE_3) {
             @Override
-            public void onEntriesUpdated(List<ListEntry> entries) {
+            public void onEntriesUpdated(List<PipelineEntry> entries) {
                 super.onEntriesUpdated(entries);
                 pkg3Entries.addAll(entries);
             }
@@ -2478,7 +2478,7 @@
                     mBuiltList.size());
 
             for (int i = 0; i < expectedEntries.length; i++) {
-                ListEntry outEntry = mBuiltList.get(i);
+                PipelineEntry outEntry = mBuiltList.get(i);
                 ExpectedEntry expectedEntry = expectedEntries[i];
 
                 if (expectedEntry instanceof ExpectedNotif) {
@@ -2653,7 +2653,7 @@
         }
 
         @Override
-        public int compare(@NonNull ListEntry o1, @NonNull ListEntry o2) {
+        public int compare(@NonNull PipelineEntry o1, @NonNull PipelineEntry o2) {
             boolean contains1 = mPreferredPackages.contains(
                     o1.getRepresentativeEntry().getSbn().getPackageName());
             boolean contains2 = mPreferredPackages.contains(
@@ -2691,37 +2691,37 @@
         }
 
         @Override
-        public boolean isInSection(ListEntry entry) {
+        public boolean isInSection(PipelineEntry entry) {
             return mPackages.contains(entry.getRepresentativeEntry().getSbn().getPackageName());
         }
     }
 
     private static class RecordingOnBeforeTransformGroupsListener
             implements OnBeforeTransformGroupsListener {
-        List<ListEntry> mEntriesReceived;
+        List<PipelineEntry> mEntriesReceived;
 
         @Override
-        public void onBeforeTransformGroups(List<ListEntry> list) {
+        public void onBeforeTransformGroups(List<PipelineEntry> list) {
             mEntriesReceived = new ArrayList<>(list);
         }
     }
 
     private static class RecordingOnBeforeSortListener
             implements OnBeforeSortListener {
-        List<ListEntry> mEntriesReceived;
+        List<PipelineEntry> mEntriesReceived;
 
         @Override
-        public void onBeforeSort(List<ListEntry> list) {
+        public void onBeforeSort(List<PipelineEntry> list) {
             mEntriesReceived = new ArrayList<>(list);
         }
     }
 
     private static class RecordingOnBeforeRenderListener
             implements OnBeforeRenderListListener {
-        List<ListEntry> mEntriesReceived;
+        List<PipelineEntry> mEntriesReceived;
 
         @Override
-        public void onBeforeRenderList(List<ListEntry> list) {
+        public void onBeforeRenderList(List<PipelineEntry> list) {
             mEntriesReceived = new ArrayList<>(list);
         }
     }
@@ -2800,7 +2800,7 @@
         }
 
         @Override
-        public boolean isEntryReorderingAllowed(@NonNull ListEntry entry) {
+        public boolean isEntryReorderingAllowed(@NonNull PipelineEntry entry) {
             return mAllowEntryReodering;
         }
 
diff --git a/packages/SystemUI/tests/src/com/android/systemui/unfold/UnfoldLatencyTrackerTest.kt b/packages/SystemUI/tests/src/com/android/systemui/unfold/UnfoldLatencyTrackerTest.kt
index a1122c3..a553b17 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/unfold/UnfoldLatencyTrackerTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/unfold/UnfoldLatencyTrackerTest.kt
@@ -18,10 +18,12 @@
 
 import android.hardware.devicestate.DeviceStateManager
 import android.hardware.devicestate.DeviceStateManager.FoldStateListener
+import android.platform.test.annotations.DisableFlags
 import android.provider.Settings
 import androidx.test.ext.junit.runners.AndroidJUnit4
 import androidx.test.filters.SmallTest
 import com.android.internal.util.LatencyTracker
+import com.android.systemui.Flags
 import com.android.systemui.SysuiTestCase
 import com.android.systemui.foldedDeviceStateList
 import com.android.systemui.halfFoldedDeviceState
@@ -46,6 +48,7 @@
 
 @RunWith(AndroidJUnit4::class)
 @SmallTest
+@DisableFlags(Flags.FLAG_UNFOLD_LATENCY_TRACKING_FIX)
 class UnfoldLatencyTrackerTest : SysuiTestCase() {
 
     @Mock lateinit var latencyTracker: LatencyTracker
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/SystemUI/tests/utils/src/com/android/systemui/statusbar/notification/collection/NotificationEntryBuilder.java b/packages/SystemUI/tests/utils/src/com/android/systemui/statusbar/notification/collection/NotificationEntryBuilder.java
index 562ac0c..804ec9f 100644
--- a/packages/SystemUI/tests/utils/src/com/android/systemui/statusbar/notification/collection/NotificationEntryBuilder.java
+++ b/packages/SystemUI/tests/utils/src/com/android/systemui/statusbar/notification/collection/NotificationEntryBuilder.java
@@ -56,7 +56,7 @@
     private StatusBarNotification mSbn = null;
 
     /* ListEntry properties */
-    private GroupEntry mParent;
+    private PipelineEntry mParent;
     private NotifSection mNotifSection;
 
     /* If set, use this creation time instead of mClock.uptimeMillis */
@@ -91,7 +91,7 @@
     }
 
     /** Update an the parent on an existing entry */
-    public static void setNewParent(NotificationEntry entry, GroupEntry parent) {
+    public static void setNewParent(NotificationEntry entry, PipelineEntry parent) {
         entry.setParent(parent);
     }
 
@@ -135,7 +135,7 @@
     /**
      * Sets the parent.
      */
-    public NotificationEntryBuilder setParent(@Nullable GroupEntry parent) {
+    public NotificationEntryBuilder setParent(@Nullable PipelineEntry parent) {
         mParent = parent;
         return this;
     }
diff --git a/packages/SystemUI/tests/utils/src/com/android/systemui/statusbar/phone/ongoingcall/shared/model/OngoingCallTestHelper.kt b/packages/SystemUI/tests/utils/src/com/android/systemui/statusbar/phone/ongoingcall/shared/model/OngoingCallTestHelper.kt
index d09d010..8ff7c7d 100644
--- a/packages/SystemUI/tests/utils/src/com/android/systemui/statusbar/phone/ongoingcall/shared/model/OngoingCallTestHelper.kt
+++ b/packages/SystemUI/tests/utils/src/com/android/systemui/statusbar/phone/ongoingcall/shared/model/OngoingCallTestHelper.kt
@@ -76,6 +76,7 @@
         promotedContent: PromotedNotificationContentModel? = null,
         contentIntent: PendingIntent? = null,
         uid: Int = DEFAULT_UID,
+        appName: String = "Fake name",
     ) {
         if (StatusBarChipsModernization.isEnabled) {
             activeNotificationListRepository.addNotif(
@@ -87,6 +88,7 @@
                     contentIntent = contentIntent,
                     promotedContent = promotedContent,
                     uid = uid,
+                    appName = appName,
                 )
             )
         } else {
@@ -96,6 +98,7 @@
                     notificationIcon = statusBarChipIconView,
                     intent = contentIntent,
                     notificationKey = key,
+                    appName = appName,
                     promotedContent = promotedContent,
                 )
             )
diff --git a/packages/SystemUI/tests/utils/src/com/android/systemui/statusbar/pipeline/mobile/domain/interactor/FakeMobileIconsInteractor.kt b/packages/SystemUI/tests/utils/src/com/android/systemui/statusbar/pipeline/mobile/domain/interactor/FakeMobileIconsInteractor.kt
index 9b6f205..8fa82ca 100644
--- a/packages/SystemUI/tests/utils/src/com/android/systemui/statusbar/pipeline/mobile/domain/interactor/FakeMobileIconsInteractor.kt
+++ b/packages/SystemUI/tests/utils/src/com/android/systemui/statusbar/pipeline/mobile/domain/interactor/FakeMobileIconsInteractor.kt
@@ -58,6 +58,8 @@
 
     override val defaultDataSubId: MutableStateFlow<Int?> = MutableStateFlow(DEFAULT_DATA_SUB_ID)
 
+    override val activeMobileDataSubscriptionId: MutableStateFlow<Int?> = MutableStateFlow(null)
+
     private val _activeDataConnectionHasDataEnabled = MutableStateFlow(false)
     override val activeDataConnectionHasDataEnabled = _activeDataConnectionHasDataEnabled
 
diff --git a/packages/SystemUI/tests/utils/src/com/android/systemui/statusbar/pipeline/mobile/ui/viewmodel/StackedMobileIconViewModelKosmos.kt b/packages/SystemUI/tests/utils/src/com/android/systemui/statusbar/pipeline/mobile/ui/viewmodel/StackedMobileIconViewModelKosmos.kt
new file mode 100644
index 0000000..880ba5e
--- /dev/null
+++ b/packages/SystemUI/tests/utils/src/com/android/systemui/statusbar/pipeline/mobile/ui/viewmodel/StackedMobileIconViewModelKosmos.kt
@@ -0,0 +1,22 @@
+/*
+ * Copyright (C) 2025 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.systemui.statusbar.pipeline.mobile.ui.viewmodel
+
+import com.android.systemui.kosmos.Kosmos
+
+val Kosmos.stackedMobileIconViewModel: StackedMobileIconViewModel by
+    Kosmos.Fixture { StackedMobileIconViewModel(mobileIconsViewModel) }
diff --git a/packages/SystemUI/tests/utils/src/com/android/systemui/user/domain/interactor/UserLockedInteractorKosmos.kt b/packages/SystemUI/tests/utils/src/com/android/systemui/user/domain/interactor/UserLockedInteractorKosmos.kt
index fd95508..933c351 100644
--- a/packages/SystemUI/tests/utils/src/com/android/systemui/user/domain/interactor/UserLockedInteractorKosmos.kt
+++ b/packages/SystemUI/tests/utils/src/com/android/systemui/user/domain/interactor/UserLockedInteractorKosmos.kt
@@ -17,7 +17,10 @@
 package com.android.systemui.user.domain.interactor
 
 import com.android.systemui.kosmos.Kosmos
+import com.android.systemui.kosmos.testDispatcher
 import com.android.systemui.user.data.repository.userRepository
 
 val Kosmos.userLockedInteractor by
-    Kosmos.Fixture { UserLockedInteractor(userRepository = userRepository) }
+    Kosmos.Fixture {
+        UserLockedInteractor(backgroundDispatcher = testDispatcher, userRepository = userRepository)
+    }
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/ravenwood/Android.bp b/ravenwood/Android.bp
index 65550f2..ccbc46f 100644
--- a/ravenwood/Android.bp
+++ b/ravenwood/Android.bp
@@ -121,6 +121,7 @@
     name: "ravenwood-helper-framework-runtime",
     srcs: [
         "runtime-helper-src/framework/**/*.java",
+        ":framework-graphics-srcs",
     ],
     static_libs: [
         "ravenwood-runtime-common",
@@ -278,6 +279,7 @@
 cc_library_host_shared {
     name: "libravenwood_runtime",
     defaults: ["ravenwood_jni_defaults"],
+    header_libs: ["libicuuc_headers"],
     srcs: [
         "runtime-jni/ravenwood_runtime.cpp",
         "runtime-jni/ravenwood_os_constants.cpp",
@@ -372,6 +374,13 @@
     visibility: ["//visibility:private"],
 }
 
+java_library {
+    name: "ext-ravenwood",
+    installable: false,
+    static_libs: ["ext"],
+    visibility: ["//visibility:private"],
+}
+
 filegroup {
     name: "ravenwood-data",
     device_common_srcs: [
@@ -637,6 +646,7 @@
     libs: [
         "100-framework-minus-apex.ravenwood",
         "200-kxml2-android",
+        "ext-ravenwood",
 
         "ravenwood-runtime-common-ravenwood",
 
diff --git a/ravenwood/Framework.bp b/ravenwood/Framework.bp
index 71496b0..e366771 100644
--- a/ravenwood/Framework.bp
+++ b/ravenwood/Framework.bp
@@ -419,11 +419,13 @@
         "--out-impl-jar $(location ravenwood.jar) " +
         "--in-jar $(location :framework-graphics.impl{.jar}) " +
 
-        "--policy-override-file $(location :ravenwood-common-policies) ",
+        "--policy-override-file $(location :ravenwood-common-policies) " +
+        "--policy-override-file $(location :framework-graphics-ravenwood-policies) ",
     srcs: [
         ":framework-graphics.impl{.jar}",
 
         ":ravenwood-common-policies",
+        ":framework-graphics-ravenwood-policies",
         ":ravenwood-standard-options",
     ],
     out: [
diff --git a/ravenwood/junit-impl-src/android/platform/test/ravenwood/RavenwoodContext.java b/ravenwood/junit-impl-src/android/platform/test/ravenwood/RavenwoodContext.java
index a332633..9e6b12f 100644
--- a/ravenwood/junit-impl-src/android/platform/test/ravenwood/RavenwoodContext.java
+++ b/ravenwood/junit-impl-src/android/platform/test/ravenwood/RavenwoodContext.java
@@ -230,6 +230,16 @@
         return mAppContext;
     }
 
+    @Override
+    public boolean isRestricted() {
+        return false;
+    }
+
+    @Override
+    public boolean canLoadUnsafeResources() {
+        return true;
+    }
+
     /**
      * Wrap the given {@link Supplier} to become memoized.
      *
diff --git a/ravenwood/junit-impl-src/android/platform/test/ravenwood/RavenwoodNativeLoader.java b/ravenwood/junit-impl-src/android/platform/test/ravenwood/RavenwoodNativeLoader.java
index a208d6d..7e935d0 100644
--- a/ravenwood/junit-impl-src/android/platform/test/ravenwood/RavenwoodNativeLoader.java
+++ b/ravenwood/junit-impl-src/android/platform/test/ravenwood/RavenwoodNativeLoader.java
@@ -48,6 +48,7 @@
             android.content.res.AssetManager.class,
             android.content.res.StringBlock.class,
             android.content.res.XmlBlock.class,
+            android.text.AndroidCharacter.class,
     };
 
     /**
@@ -61,15 +62,49 @@
             android.graphics.Path.class,
             android.graphics.Color.class,
             android.graphics.ColorSpace.class,
+            android.graphics.Bitmap.class,
+            android.graphics.BitmapFactory.class,
+            android.graphics.BitmapRegionDecoder.class,
+            android.graphics.Camera.class,
+            android.graphics.Canvas.class,
+            android.graphics.CanvasProperty.class,
+            android.graphics.ColorFilter.class,
+            android.graphics.DrawFilter.class,
+            android.graphics.FontFamily.class,
+            android.graphics.Gainmap.class,
+            android.graphics.ImageDecoder.class,
+            android.graphics.MaskFilter.class,
+            android.graphics.NinePatch.class,
+            android.graphics.Paint.class,
+            android.graphics.PathEffect.class,
+            android.graphics.PathIterator.class,
+            android.graphics.PathMeasure.class,
+            android.graphics.Picture.class,
+            android.graphics.RecordingCanvas.class,
+            android.graphics.Region.class,
+            android.graphics.RenderNode.class,
+            android.graphics.Shader.class,
+            android.graphics.RenderEffect.class,
+            android.graphics.Typeface.class,
+            android.graphics.YuvImage.class,
+            android.graphics.fonts.Font.class,
+            android.graphics.fonts.FontFamily.class,
+            android.graphics.text.LineBreaker.class,
+            android.graphics.text.MeasuredText.class,
+            android.graphics.text.TextRunShaper.class,
+            android.graphics.text.GraphemeBreak.class,
+            android.util.PathParser.class,
     };
 
     /**
      * Extra strings needed to pass to register_android_graphics_classes().
      *
-     * `android.graphics.Graphics` is not actually a class, so we just hardcode it here.
+     * Several entries are not actually a class, so we just hardcode them here.
      */
     public final static String[] GRAPHICS_EXTRA_INIT_PARAMS = new String[] {
-            "android.graphics.Graphics"
+            "android.graphics.Graphics",
+            "android.graphics.ByteBufferStreamAdaptor",
+            "android.graphics.CreateJavaOutputStreamAdaptor"
     };
 
     private RavenwoodNativeLoader() {
diff --git a/ravenwood/junit-impl-src/android/platform/test/ravenwood/RavenwoodRuntimeEnvironmentController.java b/ravenwood/junit-impl-src/android/platform/test/ravenwood/RavenwoodRuntimeEnvironmentController.java
index ae88bb2..f205d23 100644
--- a/ravenwood/junit-impl-src/android/platform/test/ravenwood/RavenwoodRuntimeEnvironmentController.java
+++ b/ravenwood/junit-impl-src/android/platform/test/ravenwood/RavenwoodRuntimeEnvironmentController.java
@@ -43,6 +43,7 @@
 import android.content.Context;
 import android.content.pm.ApplicationInfo;
 import android.content.res.Resources;
+import android.graphics.Typeface;
 import android.icu.util.ULocale;
 import android.os.Binder;
 import android.os.Build;
@@ -246,6 +247,13 @@
         // Do the basic set up for the android sysprops.
         RavenwoodSystemProperties.initialize();
 
+        // Set ICU data file
+        String icuData = RavenwoodCommonUtils.getRavenwoodRuntimePath()
+                + "ravenwood-data/"
+                + RavenwoodRuntimeNative.getIcuDataName()
+                + ".dat";
+        RavenwoodRuntimeNative.setSystemProperty("ro.icu.data.path", icuData);
+
         // Enable all log levels for native logging, until we'll have a way to change the native
         // side log level at runtime.
         // Do this after loading RAVENWOOD_NATIVE_RUNTIME_NAME (which backs Os.setenv()),
@@ -268,6 +276,11 @@
         Objects.requireNonNull(Build.TYPE);
         Objects.requireNonNull(Build.VERSION.SDK);
 
+        // Fonts can only be initialized once
+        Typeface.init();
+        Typeface.loadPreinstalledSystemFontMap();
+        Typeface.loadNativeSystemFonts();
+
         System.setProperty(RAVENWOOD_VERSION_JAVA_SYSPROP, "1");
         // This will let AndroidJUnit4 use the original runner.
         System.setProperty("android.junit.runner",
diff --git a/ravenwood/runtime-helper-src/libcore-fake/com/android/ravenwood/RavenwoodJdkPatch.java b/ravenwood/runtime-helper-src/libcore-fake/com/android/ravenwood/RavenwoodJdkPatch.java
index 96aed4b..d5a96dd 100644
--- a/ravenwood/runtime-helper-src/libcore-fake/com/android/ravenwood/RavenwoodJdkPatch.java
+++ b/ravenwood/runtime-helper-src/libcore-fake/com/android/ravenwood/RavenwoodJdkPatch.java
@@ -20,6 +20,7 @@
 import java.io.FileDescriptor;
 import java.util.LinkedHashMap;
 import java.util.Map;
+import java.util.regex.Pattern;
 
 /**
  * Class to host APIs that exist in libcore, but not in standard JRE.
@@ -46,4 +47,22 @@
         final var it = map.entrySet().iterator();
         return it.hasNext() ? it.next() : null;
     }
+
+    /**
+     * Implements Pattern.compile(String)
+     *
+     * ART always assumes UNICODE_CHARACTER_CLASS is set.
+     */
+    public static Pattern compilePattern(String regex) {
+        return Pattern.compile(regex, Pattern.UNICODE_CHARACTER_CLASS);
+    }
+
+    /**
+     * Implements Pattern.compile(String, int)
+     *
+     * ART always assumes UNICODE_CHARACTER_CLASS is set.
+     */
+    public static Pattern compilePattern(String regex, int flag) {
+        return Pattern.compile(regex, flag | Pattern.UNICODE_CHARACTER_CLASS);
+    }
 }
diff --git a/ravenwood/runtime-helper-src/libcore-fake/com/android/ravenwood/RavenwoodRuntimeNative.java b/ravenwood/runtime-helper-src/libcore-fake/com/android/ravenwood/RavenwoodRuntimeNative.java
index acbcdf1..0d82a86 100644
--- a/ravenwood/runtime-helper-src/libcore-fake/com/android/ravenwood/RavenwoodRuntimeNative.java
+++ b/ravenwood/runtime-helper-src/libcore-fake/com/android/ravenwood/RavenwoodRuntimeNative.java
@@ -66,6 +66,8 @@
 
     public static native int gettid();
 
+    public static native String getIcuDataName();
+
     public static long lseek(FileDescriptor fd, long offset, int whence) throws ErrnoException {
         return nLseek(JvmWorkaround.getInstance().getFdInt(fd), offset, whence);
     }
diff --git a/ravenwood/runtime-helper-src/libcore-fake/dalvik/system/CloseGuard.java b/ravenwood/runtime-helper-src/libcore-fake/dalvik/system/CloseGuard.java
new file mode 100644
index 0000000..82bab64
--- /dev/null
+++ b/ravenwood/runtime-helper-src/libcore-fake/dalvik/system/CloseGuard.java
@@ -0,0 +1,198 @@
+/*
+ * Copyright (C) 2010 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package dalvik.system;
+
+/**
+ * A no-op copy of libcore/dalvik/src/main/java/dalvik/system/CloseGuard.java
+ */
+public final class CloseGuard {
+
+    /**
+     * Returns a CloseGuard instance. {@code #open(String)} can be used to set
+     * up the instance to warn on failure to close.
+     *
+     * @return {@link CloseGuard} instance.
+     *
+     * @hide
+     */
+    public static CloseGuard get() {
+        return new CloseGuard();
+    }
+
+    /**
+     * Enables/disables stack capture and tracking. A call stack is captured
+     * during open(), and open/close events are reported to the Tracker, only
+     * if enabled is true. If a stack trace was captured, the {@link
+     * #getReporter() reporter} is informed of unclosed resources; otherwise a
+     * one-line warning is logged.
+     *
+     * @param enabled whether stack capture and tracking is enabled.
+     *
+     * @hide
+     */
+    public static void setEnabled(boolean enabled) {
+    }
+
+    /**
+     * True if CloseGuard stack capture and tracking are enabled.
+     *
+     * @hide
+     */
+    public static boolean isEnabled() {
+        return false;
+    }
+
+    /**
+     * Used to replace default Reporter used to warn of CloseGuard
+     * violations when stack tracking is enabled. Must be non-null.
+     *
+     * @param rep replacement for default Reporter.
+     *
+     * @hide
+     */
+    public static void setReporter(Reporter rep) {
+        if (rep == null) {
+            throw new NullPointerException("reporter == null");
+        }
+    }
+
+    /**
+     * Returns non-null CloseGuard.Reporter.
+     *
+     * @return CloseGuard's Reporter.
+     *
+     * @hide
+     */
+    public static Reporter getReporter() {
+        return null;
+    }
+
+    /**
+     * Sets the {@link Tracker} that is notified when resources are allocated and released.
+     * The Tracker is invoked only if CloseGuard {@link #isEnabled()} held when {@link #open()}
+     * was called. A null argument disables tracking.
+     *
+     * <p>This is only intended for use by {@code dalvik.system.CloseGuardSupport} class and so
+     * MUST NOT be used for any other purposes.
+     *
+     * @hide
+     */
+    public static void setTracker(Tracker tracker) {
+    }
+
+    /**
+     * Returns {@link #setTracker(Tracker) last Tracker that was set}, or null to indicate
+     * there is none.
+     *
+     * <p>This is only intended for use by {@code dalvik.system.CloseGuardSupport} class and so
+     * MUST NOT be used for any other purposes.
+     *
+     * @hide
+     */
+    public static Tracker getTracker() {
+        return null;
+    }
+
+    private CloseGuard() {}
+
+    /**
+     * {@code open} initializes the instance with a warning that the caller
+     * should have explicitly called the {@code closer} method instead of
+     * relying on finalization.
+     *
+     * @param closer non-null name of explicit termination method. Printed by warnIfOpen.
+     * @throws NullPointerException if closer is null.
+     *
+     * @hide
+     */
+    public void open(String closer) {
+        openWithCallSite(closer, null /* callsite */);
+    }
+
+    /**
+     * Like {@link #open(String)}, but with explicit callsite string being passed in for better
+     * performance.
+     * <p>
+     * This only has better performance than {@link #open(String)} if {@link #isEnabled()} returns {@code true}, which
+     * usually shouldn't happen on release builds.
+     *
+     * @param closer Non-null name of explicit termination method. Printed by warnIfOpen.
+     * @param callsite Non-null string uniquely identifying the callsite.
+     *
+     * @hide
+     */
+    public void openWithCallSite(String closer, String callsite) {
+    }
+
+    // We keep either an allocation stack containing the closer String or, when
+    // in disabled state, just the closer String.
+    // We keep them in a single field only to minimize overhead.
+    private Object /* String or Throwable */ closerNameOrAllocationInfo;
+
+    /**
+     * Marks this CloseGuard instance as closed to avoid warnings on
+     * finalization.
+     *
+     * @hide
+     */
+    public void close() {
+    }
+
+    /**
+     * Logs a warning if the caller did not properly cleanup by calling an
+     * explicit close method before finalization. If CloseGuard was enabled
+     * when the CloseGuard was created, passes the stacktrace associated with
+     * the allocation to the current reporter. If it was not enabled, it just
+     * directly logs a brief message.
+     *
+     * @hide
+     */
+    public void warnIfOpen() {
+    }
+
+
+    /**
+     * Interface to allow customization of tracking behaviour.
+     *
+     * <p>This is only intended for use by {@code dalvik.system.CloseGuardSupport} class and so
+     * MUST NOT be used for any other purposes.
+     *
+     * @hide
+     */
+    public interface Tracker {
+        void open(Throwable allocationSite);
+        void close(Throwable allocationSite);
+    }
+
+    /**
+     * Interface to allow customization of reporting behavior.
+     * @hide
+     */
+    public interface Reporter {
+        /**
+         *
+         * @hide
+         */
+        void report(String message, Throwable allocationSite);
+
+        /**
+         *
+         * @hide
+         */
+        default void report(String message) {}
+    }
+}
diff --git a/ravenwood/runtime-helper-src/libcore-fake/libcore/io/IoBridge.java b/ravenwood/runtime-helper-src/libcore-fake/libcore/io/IoBridge.java
new file mode 100644
index 0000000..2a1ee25
--- /dev/null
+++ b/ravenwood/runtime-helper-src/libcore-fake/libcore/io/IoBridge.java
@@ -0,0 +1,63 @@
+/*
+ * Copyright (C) 2025 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package libcore.io;
+
+import android.system.ErrnoException;
+import android.system.Os;
+import android.system.OsConstants;
+
+import java.io.FileDescriptor;
+import java.io.FileNotFoundException;
+import java.io.IOException;
+
+public class IoBridge {
+
+    public static void closeAndSignalBlockedThreads(FileDescriptor fd) throws IOException {
+        if (fd == null) {
+            return;
+        }
+        try {
+            Os.close(fd);
+        } catch (ErrnoException errnoException) {
+            throw errnoException.rethrowAsIOException();
+        }
+    }
+
+    public static FileDescriptor open(String path, int flags) throws FileNotFoundException {
+        FileDescriptor fd = null;
+        try {
+            fd = Os.open(path, flags, 0666);
+            // Posix open(2) fails with EISDIR only if you ask for write permission.
+            // Java disallows reading directories too.f
+            if (OsConstants.S_ISDIR(Os.fstat(fd).st_mode)) {
+                throw new ErrnoException("open", OsConstants.EISDIR);
+            }
+            return fd;
+        } catch (ErrnoException errnoException) {
+            try {
+                if (fd != null) {
+                    closeAndSignalBlockedThreads(fd);
+                }
+            } catch (IOException ignored) {
+            }
+            FileNotFoundException ex = new FileNotFoundException(path + ": "
+                    + errnoException.getMessage());
+            ex.initCause(errnoException);
+            throw ex;
+        }
+    }
+}
diff --git a/ravenwood/runtime-helper-src/libcore-fake/libcore/util/NativeAllocationRegistry.java b/ravenwood/runtime-helper-src/libcore-fake/libcore/util/NativeAllocationRegistry.java
index ad86135..cf1a513 100644
--- a/ravenwood/runtime-helper-src/libcore-fake/libcore/util/NativeAllocationRegistry.java
+++ b/ravenwood/runtime-helper-src/libcore-fake/libcore/util/NativeAllocationRegistry.java
@@ -35,6 +35,11 @@
         return new NativeAllocationRegistry(classLoader, freeFunction, size);
     }
 
+    public static NativeAllocationRegistry createNonmalloced(
+            Class clazz, long freeFunction, long size) {
+        return new NativeAllocationRegistry(clazz.getClassLoader(), freeFunction, size);
+    }
+
     public static NativeAllocationRegistry createMalloced(
             ClassLoader classLoader, long freeFunction, long size) {
         return new NativeAllocationRegistry(classLoader, freeFunction, size);
@@ -45,6 +50,11 @@
         return new NativeAllocationRegistry(classLoader, freeFunction, 0);
     }
 
+    public static NativeAllocationRegistry createMalloced(
+            Class clazz, long freeFunction, long size) {
+        return new NativeAllocationRegistry(clazz.getClassLoader(), freeFunction, size);
+    }
+
     public NativeAllocationRegistry(ClassLoader classLoader, long freeFunction, long size) {
         if (size < 0) {
             throw new IllegalArgumentException("Invalid native allocation size: " + size);
@@ -52,6 +62,37 @@
         mFreeFunction = freeFunction;
     }
 
+    private class CleanerThunk implements Runnable {
+        private long nativePtr;
+
+        public CleanerThunk() {
+            nativePtr = 0;
+        }
+
+        public void setNativePtr(long ptr) {
+            nativePtr = ptr;
+        }
+
+        @Override
+        public void run() {
+            if (nativePtr != 0) {
+                applyFreeFunction(mFreeFunction, nativePtr);
+            }
+        }
+    }
+
+    private static class CleanableRunner implements Runnable {
+        private final Cleaner.Cleanable mCleanable;
+
+        public CleanableRunner(Cleaner.Cleanable cleanable) {
+            mCleanable = cleanable;
+        }
+
+        public void run() {
+            mCleanable.clean();
+        }
+    }
+
     public Runnable registerNativeAllocation(Object referent, long nativePtr) {
         if (referent == null) {
             throw new IllegalArgumentException("referent is null");
@@ -60,13 +101,25 @@
             throw new IllegalArgumentException("nativePtr is null");
         }
 
-        final Runnable releaser = () -> {
-            RavenwoodRuntimeNative.applyFreeFunction(mFreeFunction, nativePtr);
-        };
-        sCleaner.register(referent, releaser);
+        final CleanerThunk thunk;
+        final CleanableRunner result;
+        try {
+            thunk = new CleanerThunk();
+            final var cleanable = sCleaner.register(referent, thunk);
+            result = new CleanableRunner(cleanable);
+        } catch (VirtualMachineError vme /* probably OutOfMemoryError */) {
+            applyFreeFunction(mFreeFunction, nativePtr);
+            throw vme;
+        }
 
+        // Enable the cleaner only after we can no longer throw anything, including OOME.
+        thunk.setNativePtr(nativePtr);
         // Ensure that cleaner doesn't get invoked before we enable it.
         Reference.reachabilityFence(referent);
-        return releaser;
+        return result;
+    }
+
+    public static void applyFreeFunction(long freeFunction, long nativePtr) {
+        RavenwoodRuntimeNative.applyFreeFunction(freeFunction, nativePtr);
     }
 }
diff --git a/ravenwood/runtime-jni/ravenwood_runtime.cpp b/ravenwood/runtime-jni/ravenwood_runtime.cpp
index 8d8ed71..01ebdc9 100644
--- a/ravenwood/runtime-jni/ravenwood_runtime.cpp
+++ b/ravenwood/runtime-jni/ravenwood_runtime.cpp
@@ -20,6 +20,7 @@
 #include <sys/syscall.h>
 #include <unistd.h>
 #include <utils/misc.h>
+#include <unicode/utypes.h>
 
 #include <string>
 
@@ -183,6 +184,10 @@
     return syscall(__NR_gettid);
 }
 
+static jstring getIcuDataName(JNIEnv* env, jclass clazz) {
+    return env->NewStringUTF(U_ICUDATA_NAME);
+}
+
 // ---- Registration ----
 
 extern void register_android_system_OsConstants(JNIEnv* env);
@@ -201,6 +206,7 @@
     { "setenv", "(Ljava/lang/String;Ljava/lang/String;Z)V", (void*)Linux_setenv },
     { "getpid", "()I", (void*)Linux_getpid },
     { "gettid", "()I", (void*)Linux_gettid },
+    { "getIcuDataName", "()Ljava/lang/String;", (void*)getIcuDataName },
 };
 
 extern "C" jint JNI_OnLoad(JavaVM* vm, void* /* reserved */) {
diff --git a/ravenwood/texts/ravenwood-annotation-allowed-classes.txt b/ravenwood/texts/ravenwood-annotation-allowed-classes.txt
index e202d0e..7462cc2 100644
--- a/ravenwood/texts/ravenwood-annotation-allowed-classes.txt
+++ b/ravenwood/texts/ravenwood-annotation-allowed-classes.txt
@@ -259,6 +259,8 @@
 android.database.sqlite.SQLiteException
 
 android.text.TextUtils
+android.text.Html
+android.text.HtmlToSpannedConverter
 
 android.accounts.Account
 
@@ -278,6 +280,10 @@
 android.graphics.Rect
 android.graphics.RectF
 
+android.graphics.fonts.SystemFonts
+
+android.graphics.text.LineBreakConfig
+
 android.content.ContentProvider
 
 android.app.ActivityManager
@@ -383,3 +389,228 @@
 com.android.server.compat.*
 com.android.internal.compat.*
 android.app.AppCompatCallbacks
+android.graphics.AvoidXfermode
+android.graphics.BLASTBufferQueue
+android.graphics.BaseCanvas
+android.graphics.BaseRecordingCanvas
+android.graphics.Bitmap
+android.graphics.BitmapFactory
+android.graphics.BitmapRegionDecoder
+android.graphics.BitmapShader
+android.graphics.BlendMode
+android.graphics.BlendModeColorFilter
+android.graphics.BlurMaskFilter
+android.graphics.Camera
+android.graphics.Canvas
+android.graphics.CanvasProperty
+android.graphics.ColorFilter
+android.graphics.ColorMatrix
+android.graphics.ColorMatrixColorFilter
+android.graphics.Compatibility
+android.graphics.ComposePathEffect
+android.graphics.ComposeShader
+android.graphics.CornerPathEffect
+android.graphics.DashPathEffect
+android.graphics.DiscretePathEffect
+android.graphics.DrawFilter
+android.graphics.EmbossMaskFilter
+android.graphics.FontFamily
+android.graphics.FontListParser
+android.graphics.ForceDarkType
+android.graphics.FrameInfo
+android.graphics.Gainmap
+android.graphics.GraphicBuffer
+android.graphics.GraphicsProtos
+android.graphics.GraphicsStatsService
+android.graphics.HardwareBufferRenderer
+android.graphics.HardwareRenderer
+android.graphics.HardwareRendererObserver
+android.graphics.ImageDecoder
+android.graphics.ImageFormat
+android.graphics.LayerRasterizer
+android.graphics.LeakyTypefaceStorage
+android.graphics.LightingColorFilter
+android.graphics.LinearGradient
+android.graphics.MaskFilter
+android.graphics.Mesh
+android.graphics.MeshSpecification
+android.graphics.Movie
+android.graphics.NinePatch
+android.graphics.Paint
+android.graphics.PaintFlagsDrawFilter
+android.graphics.PathDashPathEffect
+android.graphics.PathEffect
+android.graphics.PathIterator
+android.graphics.PathMeasure
+android.graphics.Picture
+android.graphics.PixelXorXfermode
+android.graphics.PorterDuff
+android.graphics.PorterDuffColorFilter
+android.graphics.PorterDuffXfermode
+android.graphics.PostProcessor
+android.graphics.RadialGradient
+android.graphics.Rasterizer
+android.graphics.RecordingCanvas
+android.graphics.Region
+android.graphics.RegionIterator
+android.graphics.RenderEffect
+android.graphics.RenderNode
+android.graphics.RuntimeColorFilter
+android.graphics.RuntimeShader
+android.graphics.RuntimeXfermode
+android.graphics.Shader
+android.graphics.SumPathEffect
+android.graphics.SurfaceTexture
+android.graphics.SweepGradient
+android.graphics.TableMaskFilter
+android.graphics.TemporaryBuffer
+android.graphics.TextureLayer
+android.graphics.Typeface
+android.graphics.Xfermode
+android.graphics.YuvImage
+android.graphics.fonts.Font
+android.graphics.fonts.FontCustomizationParser
+android.graphics.fonts.FontFamily
+android.graphics.fonts.FontFamilyUpdateRequest
+android.graphics.fonts.FontFileUpdateRequest
+android.graphics.fonts.FontFileUtil
+android.graphics.fonts.FontStyle
+android.graphics.fonts.FontVariationAxis
+android.graphics.text.GraphemeBreak
+android.graphics.text.LineBreaker
+android.graphics.text.MeasuredText
+android.graphics.text.PositionedGlyphs
+android.graphics.text.TextRunShaper
+android.text.AlteredCharSequence
+android.text.AndroidBidi
+android.text.AndroidCharacter
+android.text.Annotation
+android.text.AutoGrowArray
+android.text.AutoText
+android.text.BidiFormatter
+android.text.BoringLayout
+android.text.CharSequenceCharacterIterator
+android.text.ClipboardManager
+android.text.DynamicLayout
+android.text.Editable
+android.text.Emoji
+android.text.EmojiConsistency
+android.text.FontConfig
+android.text.GetChars
+android.text.GraphemeClusterSegmentFinder
+android.text.GraphicsOperations
+android.text.Highlights
+android.text.Hyphenator
+android.text.InputFilter
+android.text.InputType
+android.text.Layout
+android.text.LoginFilter
+android.text.MeasuredParagraph
+android.text.NoCopySpan
+android.text.PackedIntVector
+android.text.PackedObjectVector
+android.text.ParcelableSpan
+android.text.PrecomputedText
+android.text.SegmentFinder
+android.text.Selection
+android.text.SpanColors
+android.text.SpanSet
+android.text.SpanWatcher
+android.text.Spannable
+android.text.SpannableString
+android.text.SpannableStringBuilder
+android.text.SpannableStringInternal
+android.text.Spanned
+android.text.SpannedString
+android.text.StaticLayout
+android.text.TextDirectionHeuristic
+android.text.TextDirectionHeuristics
+android.text.TextLine
+android.text.TextPaint
+android.text.TextShaper
+android.text.TextWatcher
+android.text.WordSegmentFinder
+android.text.format.DateFormat
+android.text.format.DateIntervalFormat
+android.text.format.DateTimeFormat
+android.text.format.DateUtils
+android.text.format.DateUtilsBridge
+android.text.format.Formatter
+android.text.format.RelativeDateTimeFormatter
+android.text.format.Time
+android.text.format.TimeFormatter
+android.text.format.TimeMigrationUtils
+android.text.method.AllCapsTransformationMethod
+android.text.method.ArrowKeyMovementMethod
+android.text.method.BaseKeyListener
+android.text.method.BaseMovementMethod
+android.text.method.CharacterPickerDialog
+android.text.method.DateKeyListener
+android.text.method.DateTimeKeyListener
+android.text.method.DialerKeyListener
+android.text.method.DigitsKeyListener
+android.text.method.HideReturnsTransformationMethod
+android.text.method.InsertModeTransformationMethod
+android.text.method.KeyListener
+android.text.method.LinkMovementMethod
+android.text.method.MetaKeyKeyListener
+android.text.method.MovementMethod
+android.text.method.MultiTapKeyListener
+android.text.method.NumberKeyListener
+android.text.method.OffsetMapping
+android.text.method.PasswordTransformationMethod
+android.text.method.QwertyKeyListener
+android.text.method.ReplacementTransformationMethod
+android.text.method.ScrollingMovementMethod
+android.text.method.SingleLineTransformationMethod
+android.text.method.TextKeyListener
+android.text.method.TimeKeyListener
+android.text.method.Touch
+android.text.method.TransformationMethod
+android.text.method.TransformationMethod2
+android.text.method.TranslationTransformationMethod
+android.text.method.WordIterator
+android.text.style.AbsoluteSizeSpan
+android.text.style.AccessibilityClickableSpan
+android.text.style.AccessibilityReplacementSpan
+android.text.style.AccessibilityURLSpan
+android.text.style.AlignmentSpan
+android.text.style.BackgroundColorSpan
+android.text.style.BulletSpan
+android.text.style.CharacterStyle
+android.text.style.ClickableSpan
+android.text.style.ForegroundColorSpan
+android.text.style.IconMarginSpan
+android.text.style.LeadingMarginSpan
+android.text.style.LineBackgroundSpan
+android.text.style.LineBreakConfigSpan
+android.text.style.LineHeightSpan
+android.text.style.LocaleSpan
+android.text.style.MaskFilterSpan
+android.text.style.MetricAffectingSpan
+android.text.style.NoWritingToolsSpan
+android.text.style.ParagraphStyle
+android.text.style.QuoteSpan
+android.text.style.RasterizerSpan
+android.text.style.RelativeSizeSpan
+android.text.style.ReplacementSpan
+android.text.style.ScaleXSpan
+android.text.style.SpanUtils
+android.text.style.SpellCheckSpan
+android.text.style.StrikethroughSpan
+android.text.style.StyleSpan
+android.text.style.SubscriptSpan
+android.text.style.SuggestionRangeSpan
+android.text.style.SuggestionSpan
+android.text.style.SuperscriptSpan
+android.text.style.TabStopSpan
+android.text.style.TextAppearanceSpan
+android.text.style.TtsSpan
+android.text.style.TypefaceSpan
+android.text.style.URLSpan
+android.text.style.UnderlineSpan
+android.text.style.UpdateAppearance
+android.text.style.UpdateLayout
+android.text.style.WrapTogetherSpan
+android.text.util.Rfc822Token
+android.text.util.Rfc822Tokenizer
diff --git a/ravenwood/texts/ravenwood-build.prop b/ravenwood/texts/ravenwood-build.prop
index 7cc4454..37c50f1 100644
--- a/ravenwood/texts/ravenwood-build.prop
+++ b/ravenwood/texts/ravenwood-build.prop
@@ -8,7 +8,11 @@
 ro.soc.model=Ravenwood
 ro.debuggable=1
 
-# The ones starting with "ro.product" or "ro.bild" will be copied to all "partitions" too.
+# For the graphics stack
+ro.hwui.max_texture_allocation_size=104857600
+persist.sys.locale=en-US
+
+# The ones starting with "ro.product" or "ro.build" will be copied to all "partitions" too.
 # See RavenwoodSystemProperties.
 ro.product.brand=Android
 ro.product.device=Ravenwood
diff --git a/ravenwood/texts/ravenwood-common-policies.txt b/ravenwood/texts/ravenwood-common-policies.txt
index fd4ea6c..f0f4b85 100644
--- a/ravenwood/texts/ravenwood-common-policies.txt
+++ b/ravenwood/texts/ravenwood-common-policies.txt
@@ -21,3 +21,7 @@
     method setInt$ @com.android.ravenwood.RavenwoodJdkPatch.setInt$
 class java.util.LinkedHashMap  # no-pta
     method eldest @com.android.ravenwood.RavenwoodJdkPatch.eldest
+
+# Always set flag UNICODE_CHARACTER_CLASS when compiling regex
+class java.util.regex.Pattern keep
+    method compile @com.android.ravenwood.RavenwoodJdkPatch.compilePattern
diff --git a/ravenwood/texts/ravenwood-framework-policies.txt b/ravenwood/texts/ravenwood-framework-policies.txt
index fff9e6a..0695316 100644
--- a/ravenwood/texts/ravenwood-framework-policies.txt
+++ b/ravenwood/texts/ravenwood-framework-policies.txt
@@ -63,3 +63,22 @@
 # Just enough to allow ResourcesManager to run
 class android.hardware.display.DisplayManagerGlobal keep # no-pta
     method getInstance ()Landroid/hardware/display/DisplayManagerGlobal; ignore # no-pta
+
+# Bare minimum to support running ImageDecoderTest
+class android.graphics.drawable.Drawable$ConstantState keepclass # no-pta
+class android.graphics.drawable.BitmapDrawable$BitmapState keepclass # no-pta
+class android.graphics.drawable.BitmapDrawable keep # no-pta
+    method <init> (Landroid/content/res/Resources;Landroid/graphics/Bitmap;)V keep
+    method init * keep
+    method updateLocalState * keep
+    method computeBitmapSize * keep
+    method getIntrinsicWidth * keep
+    method getIntrinsicHeight * keep
+    method getBitmap * keep
+class android.graphics.drawable.Drawable keep # no-pta
+    method <init> ()V keep
+    method resolveDensity * keep
+    method updateBlendModeFilter * ignore
+
+class android.os.StrictMode keep # no-pta
+    method noteSlowCall (Ljava/lang/String;)V ignore
diff --git a/services/accessibility/accessibility.aconfig b/services/accessibility/accessibility.aconfig
index 5e1fe8a6..b52b3dab 100644
--- a/services/accessibility/accessibility.aconfig
+++ b/services/accessibility/accessibility.aconfig
@@ -145,16 +145,6 @@
 }
 
 flag {
-    name: "enable_magnification_follows_mouse_bugfix"
-    namespace: "accessibility"
-    description: "Whether to enable mouse following for fullscreen magnification"
-    bug: "354696546"
-    metadata {
-      purpose: PURPOSE_BUGFIX
-    }
-}
-
-flag {
     name: "enable_magnification_follows_mouse_with_pointer_motion_filter"
     namespace: "accessibility"
     description: "Whether to enable mouse following using pointer motion filter"
diff --git a/services/accessibility/java/com/android/server/accessibility/AccessibilityInputFilter.java b/services/accessibility/java/com/android/server/accessibility/AccessibilityInputFilter.java
index 9b5f22a..4e41808 100644
--- a/services/accessibility/java/com/android/server/accessibility/AccessibilityInputFilter.java
+++ b/services/accessibility/java/com/android/server/accessibility/AccessibilityInputFilter.java
@@ -53,7 +53,6 @@
 import com.android.server.accessibility.magnification.FullScreenMagnificationVibrationHelper;
 import com.android.server.accessibility.magnification.MagnificationGestureHandler;
 import com.android.server.accessibility.magnification.MagnificationKeyHandler;
-import com.android.server.accessibility.magnification.MouseEventHandler;
 import com.android.server.accessibility.magnification.WindowMagnificationGestureHandler;
 import com.android.server.accessibility.magnification.WindowMagnificationPromptController;
 import com.android.server.policy.WindowManagerPolicy;
@@ -899,8 +898,7 @@
                             triggerable,
                             new WindowMagnificationPromptController(displayContext, mUserId),
                             displayId,
-                            fullScreenMagnificationVibrationHelper,
-                            new MouseEventHandler(controller));
+                            fullScreenMagnificationVibrationHelper);
         }
         return magnificationGestureHandler;
     }
diff --git a/services/accessibility/java/com/android/server/accessibility/autoclick/AutoclickController.java b/services/accessibility/java/com/android/server/accessibility/autoclick/AutoclickController.java
index 4fa0d50..aa82df4 100644
--- a/services/accessibility/java/com/android/server/accessibility/autoclick/AutoclickController.java
+++ b/services/accessibility/java/com/android/server/accessibility/autoclick/AutoclickController.java
@@ -101,8 +101,15 @@
                 }
 
                 @Override
-                public void toggleAutoclickPause() {
-                    // TODO(b/388872274): allows users to pause the autoclick.
+                public void toggleAutoclickPause(boolean paused) {
+                    if (paused) {
+                        if (mClickScheduler != null) {
+                            mClickScheduler.cancel();
+                        }
+                        if (mAutoclickIndicatorScheduler != null) {
+                            mAutoclickIndicatorScheduler.cancel();
+                        }
+                    }
                 }
             };
 
@@ -133,7 +140,9 @@
                         mAutoclickIndicatorScheduler);
             }
 
-            handleMouseMotion(event, policyFlags);
+            if (!isPaused()) {
+                handleMouseMotion(event, policyFlags);
+            }
         } else if (mClickScheduler != null) {
             mClickScheduler.cancel();
         }
@@ -216,6 +225,11 @@
         }
     }
 
+    private boolean isPaused() {
+        // TODO (b/397460424): Unpause when hovering over panel.
+        return Flags.enableAutoclickIndicator() && mAutoclickTypePanel.isPaused();
+    }
+
     /**
      * Observes autoclick setting values, and updates ClickScheduler delay and indicator size
      * whenever the setting value changes.
diff --git a/services/accessibility/java/com/android/server/accessibility/autoclick/AutoclickIndicatorView.java b/services/accessibility/java/com/android/server/accessibility/autoclick/AutoclickIndicatorView.java
index 01f359f..6beb47a 100644
--- a/services/accessibility/java/com/android/server/accessibility/autoclick/AutoclickIndicatorView.java
+++ b/services/accessibility/java/com/android/server/accessibility/autoclick/AutoclickIndicatorView.java
@@ -123,6 +123,7 @@
     @Override
     protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
         // Get the screen dimensions.
+        // TODO(b/397944891): Handle device rotation case.
         DisplayMetrics displayMetrics = getResources().getDisplayMetrics();
         int screenWidth = displayMetrics.widthPixels;
         int screenHeight = displayMetrics.heightPixels;
diff --git a/services/accessibility/java/com/android/server/accessibility/autoclick/AutoclickTypePanel.java b/services/accessibility/java/com/android/server/accessibility/autoclick/AutoclickTypePanel.java
index 23c5cc4..342675a 100644
--- a/services/accessibility/java/com/android/server/accessibility/autoclick/AutoclickTypePanel.java
+++ b/services/accessibility/java/com/android/server/accessibility/autoclick/AutoclickTypePanel.java
@@ -25,6 +25,7 @@
 import android.graphics.drawable.GradientDrawable;
 import android.view.Gravity;
 import android.view.LayoutInflater;
+import android.view.MotionEvent;
 import android.view.View;
 import android.view.WindowInsets;
 import android.view.WindowManager;
@@ -51,6 +52,16 @@
     public static final int CORNER_TOP_LEFT = 2;
     public static final int CORNER_TOP_RIGHT = 3;
 
+    // Distance between panel and screen edge.
+    // TODO(b/396402941): Finalize edge margin.
+    private static final int PANEL_EDGE_MARGIN = 15;
+
+    // Touch point when drag starts, it can be anywhere inside the panel.
+    private float mTouchStartX, mTouchStartY;
+    // Initial panel position in screen coordinates.
+    private int mPanelStartX, mPanelStartY;
+    private boolean mIsDragging = false;
+
     // Types of click the AutoclickTypePanel supports.
     @IntDef({
         AUTOCLICK_TYPE_LEFT_CLICK,
@@ -79,11 +90,20 @@
     // An interface exposed to {@link AutoclickController) to handle different actions on the panel,
     // including changing autoclick type, pausing/resuming autoclick.
     public interface ClickPanelControllerInterface {
-        // Allows users to change a different autoclick type.
+        /**
+         * Allows users to change a different autoclick type.
+         *
+         * @param clickType The new autoclick type to use. Should be one of the values defined in
+         *                  {@link AutoclickType}.
+         */
         void handleAutoclickTypeChange(@AutoclickType int clickType);
 
-        // Allows users to pause/resume the autoclick.
-        void toggleAutoclickPause();
+        /**
+         * Allows users to pause or resume autoclick.
+         *
+         * @param paused {@code true} to pause autoclick, {@code false} to resume.
+         */
+        void toggleAutoclickPause(boolean paused);
     }
 
     private final Context mContext;
@@ -92,6 +112,8 @@
 
     private final WindowManager mWindowManager;
 
+    private WindowManager.LayoutParams mParams;
+
     private final ClickPanelControllerInterface mClickPanelController;
 
     // Whether the panel is expanded or not.
@@ -124,6 +146,7 @@
         mContext = context;
         mWindowManager = windowManager;
         mClickPanelController = clickPanelController;
+        mParams = getDefaultLayoutParams();
 
         mPauseButtonDrawable = mContext.getDrawable(
                 R.drawable.accessibility_autoclick_pause);
@@ -145,6 +168,91 @@
         mPositionButton = mContentView.findViewById(R.id.accessibility_autoclick_position_layout);
 
         initializeButtonState();
+
+        // Set up touch event handling for the panel to allow the user to drag and reposition the
+        // panel by touching and moving it.
+        mContentView.setOnTouchListener(this::onPanelTouch);
+    }
+
+    /**
+     * Handles touch events on the panel, enabling the user to drag and reposition it.
+     * This function supports the draggable panel feature, allowing users to move the panel
+     * to different screen locations for better usability and customization.
+     */
+    private boolean onPanelTouch(View v, MotionEvent event) {
+        // TODO(b/397681794): Make sure this works on multiple screens.
+        switch (event.getAction()) {
+            case MotionEvent.ACTION_DOWN:
+                // Store initial touch positions.
+                mTouchStartX = event.getRawX();
+                mTouchStartY = event.getRawY();
+
+                // Store initial panel position relative to screen's top-left corner.
+                // getLocationOnScreen provides coordinates relative to the top-left corner of the
+                // screen's display. We are using this coordinate system to consistently track the
+                // panel's position during drag operations.
+                int[] location = new int[2];
+                v.getLocationOnScreen(location);
+                mPanelStartX = location[0];
+                mPanelStartY = location[1];
+                return true;
+            case MotionEvent.ACTION_MOVE:
+                mIsDragging = true;
+
+                // Set panel gravity to TOP|LEFT to match getLocationOnScreen's coordinate system
+                mParams.gravity = Gravity.LEFT | Gravity.TOP;
+
+                if (mIsDragging) {
+                    // Calculate touch distance moved from start position.
+                    float deltaX = event.getRawX() - mTouchStartX;
+                    float deltaY = event.getRawY() - mTouchStartY;
+
+                    // Update panel position, based on Top-Left absolute positioning.
+                    mParams.x = mPanelStartX + (int) deltaX;
+                    mParams.y = mPanelStartY + (int) deltaY;
+                    mWindowManager.updateViewLayout(mContentView, mParams);
+                }
+                return true;
+            case MotionEvent.ACTION_UP:
+            case MotionEvent.ACTION_CANCEL:
+                if (mIsDragging) {
+                    // When drag ends, snap panel to nearest edge.
+                    snapToNearestEdge(mParams);
+                }
+                mIsDragging = false;
+                return true;
+        }
+        return false;
+    }
+
+    private void snapToNearestEdge(WindowManager.LayoutParams params) {
+        // Get screen width to determine which side to snap to.
+        // TODO(b/397944891): Handle device rotation case.
+        int screenWidth = mContext.getResources().getDisplayMetrics().widthPixels;
+        int yPosition = params.y;
+
+        // Determine which half of the screen the panel is on.
+        boolean isOnLeftHalf = params.x < screenWidth / 2;
+
+        if (isOnLeftHalf) {
+            // Snap to left edge. Set params.gravity to make sure x, y offsets from correct anchor.
+            params.gravity = Gravity.START | Gravity.TOP;
+            // Set the current corner to be bottom-left to ensure that the subsequent reposition
+            // action rotates the panel clockwise from bottom-left towards top-left.
+            mCurrentCornerIndex = 1;
+        } else {
+            // Snap to right edge. Set params.gravity to make sure x, y offsets from correct anchor.
+            params.gravity = Gravity.END | Gravity.TOP;
+            // Set the current corner to be top-right to ensure that the subsequent reposition
+            // action rotates the panel clockwise from top-right towards bottom-right.
+            mCurrentCornerIndex = 3;
+        }
+
+        // Apply final position: set params.x to be edge margin, params.y to maintain vertical
+        // position.
+        params.x = PANEL_EDGE_MARGIN;
+        params.y = yPosition;
+        mWindowManager.updateViewLayout(mContentView, params);
     }
 
     private void initializeButtonState() {
@@ -200,7 +308,7 @@
     }
 
     public void show() {
-        mWindowManager.addView(mContentView, getLayoutParams());
+        mWindowManager.addView(mContentView, mParams);
     }
 
     public void hide() {
@@ -211,6 +319,10 @@
         mWindowManager.removeView(mContentView);
     }
 
+    public boolean isPaused() {
+        return mPaused;
+    }
+
     /** Toggles the panel expanded or collapsed state. */
     private void togglePanelExpansion(@AutoclickType int clickType) {
         final LinearLayout button = getButtonFromClickType(clickType);
@@ -234,6 +346,7 @@
 
     private void togglePause() {
         mPaused = !mPaused;
+        mClickPanelController.toggleAutoclickPause(mPaused);
 
         ImageButton imageButton = (ImageButton) mPauseButton.getChildAt(/* index= */ 0);
         if (mPaused) {
@@ -277,9 +390,8 @@
         @Corner int nextCornerIndex = (mCurrentCornerIndex + 1) % CORNER_ROTATION_ORDER.length;
         mCurrentCornerIndex = nextCornerIndex;
 
-        // getLayoutParams() will update the panel position based on current corner.
-        WindowManager.LayoutParams params = getLayoutParams();
-        mWindowManager.updateViewLayout(mContentView, params);
+        setPanelPositionForCorner(mParams, mCurrentCornerIndex);
+        mWindowManager.updateViewLayout(mContentView, mParams);
     }
 
     private void setPanelPositionForCorner(WindowManager.LayoutParams params, @Corner int corner) {
@@ -289,22 +401,22 @@
         switch (corner) {
             case CORNER_BOTTOM_RIGHT:
                 params.gravity = Gravity.END | Gravity.BOTTOM;
-                params.x = 15;
+                params.x = PANEL_EDGE_MARGIN;
                 params.y = 90;
                 break;
             case CORNER_BOTTOM_LEFT:
                 params.gravity = Gravity.START | Gravity.BOTTOM;
-                params.x = 15;
+                params.x = PANEL_EDGE_MARGIN;
                 params.y = 90;
                 break;
             case CORNER_TOP_LEFT:
                 params.gravity = Gravity.START | Gravity.TOP;
-                params.x = 15;
+                params.x = PANEL_EDGE_MARGIN;
                 params.y = 30;
                 break;
             case CORNER_TOP_RIGHT:
                 params.gravity = Gravity.END | Gravity.TOP;
-                params.x = 15;
+                params.x = PANEL_EDGE_MARGIN;
                 params.y = 30;
                 break;
             default:
@@ -329,13 +441,22 @@
         return mCurrentCornerIndex;
     }
 
+    @VisibleForTesting
+    WindowManager.LayoutParams getLayoutParamsForTesting() {
+        return mParams;
+    }
+
+    @VisibleForTesting
+    boolean getIsDraggingForTesting() {
+        return mIsDragging;
+    }
+
     /**
      * Retrieves the layout params for AutoclickIndicatorView, used when it's added to the Window
      * Manager.
      */
-    @VisibleForTesting
     @NonNull
-    WindowManager.LayoutParams getLayoutParams() {
+    private WindowManager.LayoutParams getDefaultLayoutParams() {
         final WindowManager.LayoutParams layoutParams = new WindowManager.LayoutParams();
         layoutParams.type = WindowManager.LayoutParams.TYPE_APPLICATION_OVERLAY;
         layoutParams.flags = WindowManager.LayoutParams.FLAG_NOT_TOUCH_MODAL;
@@ -348,7 +469,7 @@
                 mContext.getString(R.string.accessibility_autoclick_type_settings_panel_title);
         layoutParams.width = WindowManager.LayoutParams.WRAP_CONTENT;
         layoutParams.height = WindowManager.LayoutParams.WRAP_CONTENT;
-        setPanelPositionForCorner(layoutParams, mCurrentCornerIndex);
+        setPanelPositionForCorner(layoutParams, CORNER_BOTTOM_RIGHT);
         return layoutParams;
     }
 }
diff --git a/services/accessibility/java/com/android/server/accessibility/magnification/FullScreenMagnificationGestureHandler.java b/services/accessibility/java/com/android/server/accessibility/magnification/FullScreenMagnificationGestureHandler.java
index d11ae0a..e0dd8b6 100644
--- a/services/accessibility/java/com/android/server/accessibility/magnification/FullScreenMagnificationGestureHandler.java
+++ b/services/accessibility/java/com/android/server/accessibility/magnification/FullScreenMagnificationGestureHandler.java
@@ -182,7 +182,7 @@
     private final int mMinimumVelocity;
     private final int mMaximumVelocity;
 
-    private MouseEventHandler mMouseEventHandler;
+    private final MouseEventHandler mMouseEventHandler;
 
     public FullScreenMagnificationGestureHandler(
             @UiContext Context context,
@@ -194,8 +194,7 @@
             boolean detectShortcutTrigger,
             @NonNull WindowMagnificationPromptController promptController,
             int displayId,
-            FullScreenMagnificationVibrationHelper fullScreenMagnificationVibrationHelper,
-            MouseEventHandler mouseEventHandler) {
+            FullScreenMagnificationVibrationHelper fullScreenMagnificationVibrationHelper) {
         this(
                 context,
                 fullScreenMagnificationController,
@@ -210,8 +209,7 @@
                 /* magnificationLogger= */ null,
                 ViewConfiguration.get(context),
                 new OneFingerPanningSettingsProvider(
-                        context, Flags.enableMagnificationOneFingerPanningGesture()),
-                mouseEventHandler);
+                        context, Flags.enableMagnificationOneFingerPanningGesture()));
     }
 
     /** Constructor for tests. */
@@ -229,8 +227,7 @@
             FullScreenMagnificationVibrationHelper fullScreenMagnificationVibrationHelper,
             MagnificationLogger magnificationLogger,
             ViewConfiguration viewConfiguration,
-            OneFingerPanningSettingsProvider oneFingerPanningSettingsProvider,
-            MouseEventHandler mouseEventHandler) {
+            OneFingerPanningSettingsProvider oneFingerPanningSettingsProvider) {
         super(displayId, detectSingleFingerTripleTap, detectTwoFingerTripleTap,
                 detectShortcutTrigger, trace, callback);
         if (DEBUG_ALL) {
@@ -316,7 +313,7 @@
         mOverscrollEdgeSlop = context.getResources().getDimensionPixelSize(
                 R.dimen.accessibility_fullscreen_magnification_gesture_edge_slop);
         mIsWatch = context.getPackageManager().hasSystemFeature(PackageManager.FEATURE_WATCH);
-        mMouseEventHandler = mouseEventHandler;
+        mMouseEventHandler = new MouseEventHandler(mFullScreenMagnificationController);
 
         if (mDetectShortcutTrigger) {
             mScreenStateReceiver = new ScreenStateReceiver(context, this);
@@ -340,15 +337,14 @@
 
     @Override
     void handleMouseOrStylusEvent(MotionEvent event, MotionEvent rawEvent, int policyFlags) {
-        if (Flags.enableMagnificationFollowsMouseBugfix()) {
-            if (mFullScreenMagnificationController.isActivated(mDisplayId)) {
-                // TODO(b/354696546): Allow mouse/stylus to activate whichever display they are
-                // over, rather than only interacting with the current display.
-
-                // Send through the mouse/stylus event handler.
-                mMouseEventHandler.onEvent(event, mDisplayId);
-            }
+        if (!mFullScreenMagnificationController.isActivated(mDisplayId)) {
+            return;
         }
+        // TODO(b/354696546): Allow mouse/stylus to activate whichever display they are
+        // over, rather than only interacting with the current display.
+
+        // Send through the mouse/stylus event handler.
+        mMouseEventHandler.onEvent(event, mDisplayId);
     }
 
     private void handleTouchEventWith(
@@ -1170,8 +1166,7 @@
 
         protected void cacheDelayedMotionEvent(MotionEvent event, MotionEvent rawEvent,
                 int policyFlags) {
-            if (Flags.enableMagnificationFollowsMouseBugfix()
-                    && !event.isFromSource(SOURCE_TOUCHSCREEN)) {
+            if (!event.isFromSource(SOURCE_TOUCHSCREEN)) {
                 // Only touch events need to be cached and sent later.
                 return;
             }
diff --git a/services/accessibility/java/com/android/server/accessibility/magnification/MagnificationGestureHandler.java b/services/accessibility/java/com/android/server/accessibility/magnification/MagnificationGestureHandler.java
index fa86ba3..6b39c98 100644
--- a/services/accessibility/java/com/android/server/accessibility/magnification/MagnificationGestureHandler.java
+++ b/services/accessibility/java/com/android/server/accessibility/magnification/MagnificationGestureHandler.java
@@ -146,8 +146,7 @@
             } break;
             case SOURCE_MOUSE:
             case SOURCE_STYLUS: {
-                if (magnificationShortcutExists()
-                        && Flags.enableMagnificationFollowsMouseBugfix()) {
+                if (magnificationShortcutExists()) {
                     handleMouseOrStylusEvent(event, rawEvent, policyFlags);
                 }
             }
diff --git a/services/appwidget/java/com/android/server/appwidget/AppWidgetServiceImpl.java b/services/appwidget/java/com/android/server/appwidget/AppWidgetServiceImpl.java
index 74a87ed..4441db7 100644
--- a/services/appwidget/java/com/android/server/appwidget/AppWidgetServiceImpl.java
+++ b/services/appwidget/java/com/android/server/appwidget/AppWidgetServiceImpl.java
@@ -5232,7 +5232,7 @@
                 }
             }
             return singleCategoryKeyedEntries;
-        } catch (IOException e) {
+        } catch (Exception e) {
             Slog.e(TAG, "Failed to load generated previews for " + provider, e);
             return new SparseArray<>();
         }
@@ -5261,7 +5261,7 @@
             try {
                 provider.info.generatedPreviewCategories = readGeneratedPreviewCategoriesFromProto(
                         input);
-            } catch (IOException e) {
+            } catch (Exception e) {
                 Slog.e(TAG, "Failed to read generated previews from file for " + provider, e);
                 previewsFile.delete();
                 provider.info.generatedPreviewCategories = 0;
@@ -5314,7 +5314,7 @@
                     scheduleNotifyGroupHostsForProvidersChangedLocked(provider.getUserId());
                 }
             }
-        } catch (IOException e) {
+        } catch (Exception e) {
             if (file != null && stream != null) {
                 file.failWrite(stream);
             }
diff --git a/services/core/java/com/android/server/StorageManagerService.java b/services/core/java/com/android/server/StorageManagerService.java
index d2a5734..b6fe0ad 100644
--- a/services/core/java/com/android/server/StorageManagerService.java
+++ b/services/core/java/com/android/server/StorageManagerService.java
@@ -49,7 +49,6 @@
 import static org.xmlpull.v1.XmlPullParser.END_DOCUMENT;
 import static org.xmlpull.v1.XmlPullParser.START_TAG;
 
-import android.annotation.EnforcePermission;
 import android.annotation.NonNull;
 import android.annotation.Nullable;
 import android.annotation.UserIdInt;
@@ -2443,6 +2442,7 @@
             } catch (Installer.InstallerException e) {
                 Slog.e(TAG, "Failed unmount mirror data", e);
             }
+            extendWatchdogTimeout("#unmount might be slow");
             mVold.unmount(vol.getId());
             mStorageSessionController.onVolumeUnmount(vol.getImmutableVolumeInfo());
         } catch (Exception e) {
diff --git a/services/core/java/com/android/server/am/ActivityManagerService.java b/services/core/java/com/android/server/am/ActivityManagerService.java
index 07a4d52..8b701f0 100644
--- a/services/core/java/com/android/server/am/ActivityManagerService.java
+++ b/services/core/java/com/android/server/am/ActivityManagerService.java
@@ -198,6 +198,7 @@
 import android.annotation.PermissionMethod;
 import android.annotation.PermissionName;
 import android.annotation.RequiresPermission;
+import android.annotation.SpecialUsers.CanBeALL;
 import android.annotation.UserIdInt;
 import android.app.Activity;
 import android.app.ActivityClient;
@@ -3921,8 +3922,8 @@
      * The pkg name and app id have to be specified.
      */
     @Override
-    public void killApplication(String pkg, int appId, int userId, String reason,
-            int exitInfoReason) {
+    public void killApplication(String pkg, int appId, @CanBeALL @UserIdInt int userId,
+            String reason, int exitInfoReason) {
         if (pkg == null) {
             return;
         }
@@ -4307,7 +4308,7 @@
     final boolean forceStopPackageLocked(String packageName, int appId,
             boolean callerWillRestart, boolean purgeCache, boolean doit,
             boolean evenPersistent, boolean uninstalling, boolean packageStateStopped,
-            int userId, String reasonString, int reason) {
+            @CanBeALL @UserIdInt int userId, String reasonString, int reason) {
         return forceStopPackageInternalLocked(packageName, appId, callerWillRestart, purgeCache,
                 doit, evenPersistent, uninstalling, packageStateStopped, userId, reasonString,
                 reason, ProcessList.INVALID_ADJ);
@@ -4317,7 +4318,7 @@
     private boolean forceStopPackageInternalLocked(String packageName, int appId,
             boolean callerWillRestart, boolean purgeCache, boolean doit,
             boolean evenPersistent, boolean uninstalling, boolean packageStateStopped,
-            int userId, String reasonString, int reason, int minOomAdj) {
+            @CanBeALL @UserIdInt int userId, String reasonString, int reason, int minOomAdj) {
         int i;
 
         if (userId == UserHandle.USER_ALL && packageName == null) {
@@ -18093,7 +18094,7 @@
         }
 
         @Override
-        public void killApplicationSync(String pkgName, int appId, int userId,
+        public void killApplicationSync(String pkgName, int appId, @CanBeALL @UserIdInt int userId,
                 String reason, int exitInfoReason) {
             if (pkgName == null) {
                 return;
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/ProcessList.java b/services/core/java/com/android/server/am/ProcessList.java
index b8babe6..a61368c 100644
--- a/services/core/java/com/android/server/am/ProcessList.java
+++ b/services/core/java/com/android/server/am/ProcessList.java
@@ -67,6 +67,8 @@
 import android.Manifest;
 import android.annotation.NonNull;
 import android.annotation.Nullable;
+import android.annotation.SpecialUsers.CanBeALL;
+import android.annotation.UserIdInt;
 import android.app.ActivityManager;
 import android.app.ActivityManager.ProcessCapability;
 import android.app.ActivityThread;
@@ -2961,8 +2963,8 @@
     }
 
     @GuardedBy({"mService", "mProcLock"})
-    boolean killPackageProcessesLSP(String packageName, int appId, int userId, int minOomAdj,
-            int reasonCode, int subReason, String reason) {
+    boolean killPackageProcessesLSP(String packageName, int appId, @CanBeALL @UserIdInt int userId,
+            int minOomAdj, int reasonCode, int subReason, String reason) {
         return killPackageProcessesLSP(packageName, appId, userId, minOomAdj,
                 false /* callerWillRestart */, true /* allowRestart */, true /* doit */,
                 false /* evenPersistent */, false /* setRemoved */, false /* uninstalling */,
@@ -2970,7 +2972,8 @@
     }
 
     @GuardedBy("mService")
-    void killAppZygotesLocked(String packageName, int appId, int userId, boolean force) {
+    void killAppZygotesLocked(String packageName, int appId, @CanBeALL @UserIdInt int userId,
+            boolean force) {
         // See if there are any app zygotes running for this packageName / UID combination,
         // and kill it if so.
         final ArrayList<AppZygote> zygotesToKill = new ArrayList<>();
@@ -3050,9 +3053,9 @@
 
     @GuardedBy({"mService", "mProcLock"})
     boolean killPackageProcessesLSP(String packageName, int appId,
-            int userId, int minOomAdj, boolean callerWillRestart, boolean allowRestart,
-            boolean doit, boolean evenPersistent, boolean setRemoved, boolean uninstalling,
-            int reasonCode, int subReason, String reason) {
+            @CanBeALL @UserIdInt int userId, int minOomAdj, boolean callerWillRestart,
+            boolean allowRestart, boolean doit, boolean evenPersistent, boolean setRemoved,
+            boolean uninstalling, int reasonCode, int subReason, String reason) {
         final PackageManagerInternal pm = mService.getPackageManagerInternal();
         final ArrayList<Pair<ProcessRecord, Boolean>> procs = new ArrayList<>();
 
@@ -5220,7 +5223,7 @@
     }
 
     @GuardedBy("mService")
-    void sendPackageBroadcastLocked(int cmd, String[] packages, int userId) {
+    void sendPackageBroadcastLocked(int cmd, String[] packages, @CanBeALL @UserIdInt int userId) {
         boolean foundProcess = false;
         for (int i = mLruProcesses.size() - 1; i >= 0; i--) {
             ProcessRecord r = mLruProcesses.get(i);
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/UserController.java b/services/core/java/com/android/server/am/UserController.java
index e0fbaf4..18f3500 100644
--- a/services/core/java/com/android/server/am/UserController.java
+++ b/services/core/java/com/android/server/am/UserController.java
@@ -4059,7 +4059,7 @@
             synchronized (mUserSwitchingDialogLock) {
                 dismissUserSwitchingDialog(null);
                 mUserSwitchingDialog = new UserSwitchingDialog(mService.mContext, fromUser, toUser,
-                        switchingFromSystemUserMessage, switchingToSystemUserMessage);
+                        mHandler, switchingFromSystemUserMessage, switchingToSystemUserMessage);
                 mUserSwitchingDialog.show(onShown);
             }
         }
diff --git a/services/core/java/com/android/server/am/UserSwitchingDialog.java b/services/core/java/com/android/server/am/UserSwitchingDialog.java
index d1fcb9d..223e0b7 100644
--- a/services/core/java/com/android/server/am/UserSwitchingDialog.java
+++ b/services/core/java/com/android/server/am/UserSwitchingDialog.java
@@ -34,7 +34,6 @@
 import android.graphics.drawable.AnimatedVectorDrawable;
 import android.graphics.drawable.Drawable;
 import android.os.Handler;
-import android.os.Looper;
 import android.os.SystemProperties;
 import android.os.Trace;
 import android.os.UserHandle;
@@ -72,7 +71,7 @@
 
     // Time to wait for the onAnimationEnd() callbacks before moving on
     private static final int ANIMATION_TIMEOUT_MS = 1000;
-    private final Handler mHandler = new Handler(Looper.myLooper());
+    private final Handler mHandler;
 
     protected final UserInfo mOldUser;
     protected final UserInfo mNewUser;
@@ -81,13 +80,14 @@
     protected final Context mContext;
     private final int mTraceCookie;
 
-    UserSwitchingDialog(Context context, UserInfo oldUser, UserInfo newUser,
+    UserSwitchingDialog(Context context, UserInfo oldUser, UserInfo newUser, Handler handler,
             String switchingFromSystemUserMessage, String switchingToSystemUserMessage) {
         super(context, R.style.Theme_Material_NoActionBar_Fullscreen);
 
         mContext = context;
         mOldUser = oldUser;
         mNewUser = newUser;
+        mHandler = handler;
         mSwitchingFromSystemUserMessage = switchingFromSystemUserMessage;
         mSwitchingToSystemUserMessage = switchingToSystemUserMessage;
         mDisableAnimations = SystemProperties.getBoolean(
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/audio/AudioService.java b/services/core/java/com/android/server/audio/AudioService.java
index d800503..b85967f 100644
--- a/services/core/java/com/android/server/audio/AudioService.java
+++ b/services/core/java/com/android/server/audio/AudioService.java
@@ -954,7 +954,8 @@
     /**
      * Stores information about a device using absolute volume behavior.
      */
-    private static final class AbsoluteVolumeDeviceInfo {
+    private static final class AbsoluteVolumeDeviceInfo implements IBinder.DeathRecipient {
+        private final AudioService mParent;
         private final AudioDeviceAttributes mDevice;
         private final List<VolumeInfo> mVolumeInfos;
         private final IAudioDeviceVolumeDispatcher mCallback;
@@ -962,16 +963,26 @@
         private @AudioManager.AbsoluteDeviceVolumeBehavior int mDeviceVolumeBehavior;
 
         private AbsoluteVolumeDeviceInfo(
+                AudioService parent,
                 AudioDeviceAttributes device,
                 List<VolumeInfo> volumeInfos,
                 IAudioDeviceVolumeDispatcher callback,
                 boolean handlesVolumeAdjustment,
                 @AudioManager.AbsoluteDeviceVolumeBehavior int behavior) {
+            this.mParent = parent;
             this.mDevice = device;
             this.mVolumeInfos = volumeInfos;
             this.mCallback = callback;
             this.mHandlesVolumeAdjustment = handlesVolumeAdjustment;
             this.mDeviceVolumeBehavior = behavior;
+
+            try {
+                this.mCallback.asBinder().linkToDeath(this, 0);
+            } catch (RemoteException | NullPointerException e) {
+                // NPE can be raised when mocking the callback object
+                Slog.w(TAG, "Exception: " + e
+                        + "\nCannot listen to callback binder death for device " + mDevice);
+            }
         }
 
         /**
@@ -991,6 +1002,25 @@
             }
             return null;
         }
+
+        @Override
+        public void binderDied() {
+            if (mParent.removeAudioSystemDeviceOutFromAbsVolumeDevices(mDevice.getInternalType())
+                    != null) {
+                mParent.dispatchDeviceVolumeBehavior(mDevice,
+                        AudioManager.DEVICE_VOLUME_BEHAVIOR_VARIABLE);
+            }
+        }
+
+        public void unlinkToDeath() {
+            try {
+                mCallback.asBinder().unlinkToDeath(this, 0);
+            } catch (NullPointerException e) {
+                // NPE can be raised when mocking the callback object
+                Slog.w(TAG, "Exception: " + e
+                        + "\nCannot unlink to death, null binder object for device " + mDevice);
+            }
+        }
     }
 
     // Devices for the which use the "absolute volume" concept (framework sends audio signal
@@ -8142,16 +8172,16 @@
 
         int deviceOut = device.getInternalType();
         if (register) {
-            AbsoluteVolumeDeviceInfo info = new AbsoluteVolumeDeviceInfo(
+            AbsoluteVolumeDeviceInfo info = new AbsoluteVolumeDeviceInfo(this,
                     device, volumes, cb, handlesVolumeAdjustment, deviceVolumeBehavior);
             final AbsoluteVolumeDeviceInfo oldInfo = getAbsoluteVolumeDeviceInfo(deviceOut);
+            addAudioSystemDeviceOutToAbsVolumeDevices(deviceOut, info);
 
             boolean volumeBehaviorChanged = (oldInfo == null)
                     || (oldInfo.mDeviceVolumeBehavior != deviceVolumeBehavior);
             if (volumeBehaviorChanged) {
                 removeAudioSystemDeviceOutFromFullVolumeDevices(deviceOut);
                 removeAudioSystemDeviceOutFromFixedVolumeDevices(deviceOut);
-                addAudioSystemDeviceOutToAbsVolumeDevices(deviceOut, info);
 
                 dispatchDeviceVolumeBehavior(device, deviceVolumeBehavior);
             }
@@ -8177,8 +8207,10 @@
                 }
             }
         } else {
-            boolean wasAbsVol = removeAudioSystemDeviceOutFromAbsVolumeDevices(deviceOut) != null;
-            if (wasAbsVol) {
+            AbsoluteVolumeDeviceInfo deviceInfo = removeAudioSystemDeviceOutFromAbsVolumeDevices(
+                    deviceOut);
+            if (deviceInfo != null) {
+                deviceInfo.unlinkToDeath();
                 dispatchDeviceVolumeBehavior(device, AudioManager.DEVICE_VOLUME_BEHAVIOR_VARIABLE);
             }
         }
diff --git a/services/core/java/com/android/server/biometrics/BiometricService.java b/services/core/java/com/android/server/biometrics/BiometricService.java
index a5058dd..cf5fa96 100644
--- a/services/core/java/com/android/server/biometrics/BiometricService.java
+++ b/services/core/java/com/android/server/biometrics/BiometricService.java
@@ -540,9 +540,23 @@
                     DEFAULT_MANDATORY_BIOMETRICS_STATUS)
                     && mMandatoryBiometricsRequirementsSatisfied.getOrDefault(userId,
                     DEFAULT_MANDATORY_BIOMETRICS_REQUIREMENTS_SATISFIED_STATUS)
-                    && getEnabledForApps(userId, TYPE_ANY_BIOMETRIC)
-                    && (mFingerprintEnrolledForUser.getOrDefault(userId, false /* default */)
-                    || mFaceEnrolledForUser.getOrDefault(userId, false /* default */));
+                    && getBiometricStatusForIdentityCheck(userId);
+        }
+
+        private boolean getBiometricStatusForIdentityCheck(int userId) {
+            if (com.android.settings.flags.Flags.biometricsOnboardingEducation()) {
+                if (mFingerprintEnrolledForUser.getOrDefault(userId, false /* default */)
+                        && getEnabledForApps(userId, TYPE_FINGERPRINT)) {
+                    return true;
+                } else {
+                    return mFaceEnrolledForUser.getOrDefault(userId, false /* default */)
+                            && getEnabledForApps(userId, TYPE_FACE);
+                }
+            } else {
+                return (mFingerprintEnrolledForUser.getOrDefault(userId, false /* default */)
+                        || mFaceEnrolledForUser.getOrDefault(userId, false /* default */))
+                        && getEnabledForApps(userId, TYPE_ANY_BIOMETRIC);
+            }
         }
 
         void notifyEnabledOnKeyguardCallbacks(int userId, int modality) {
diff --git a/services/core/java/com/android/server/content/ContentService.java b/services/core/java/com/android/server/content/ContentService.java
index c393e92..79af6ed 100644
--- a/services/core/java/com/android/server/content/ContentService.java
+++ b/services/core/java/com/android/server/content/ContentService.java
@@ -24,6 +24,8 @@
 import android.annotation.NonNull;
 import android.annotation.Nullable;
 import android.annotation.RequiresPermission;
+import android.annotation.SpecialUsers.CanBeALL;
+import android.annotation.SpecialUsers.CanBeCURRENT;
 import android.annotation.UserIdInt;
 import android.app.ActivityManager;
 import android.app.ActivityManager.RestrictionLevel;
@@ -361,7 +363,8 @@
      */
     @Override
     public void registerContentObserver(Uri uri, boolean notifyForDescendants,
-            IContentObserver observer, int userHandle, int targetSdkVersion) {
+            IContentObserver observer, @CanBeALL @CanBeCURRENT @UserIdInt int userHandle,
+            int targetSdkVersion) {
         if (observer == null || uri == null) {
             throw new IllegalArgumentException("You must pass a valid uri and observer");
         }
@@ -1398,8 +1401,8 @@
         }
     }
 
-    private int handleIncomingUser(Uri uri, int pid, int uid, int modeFlags, boolean allowNonFull,
-            int userId) {
+    private @CanBeALL @UserIdInt int handleIncomingUser(Uri uri, int pid, int uid, int modeFlags,
+            boolean allowNonFull, @CanBeALL @CanBeCURRENT @UserIdInt int userId) {
         if (userId == UserHandle.USER_CURRENT) {
             userId = ActivityManager.getCurrentUser();
         }
diff --git a/services/core/java/com/android/server/display/LogicalDisplayMapper.java b/services/core/java/com/android/server/display/LogicalDisplayMapper.java
index 872f334..f4daf87 100644
--- a/services/core/java/com/android/server/display/LogicalDisplayMapper.java
+++ b/services/core/java/com/android/server/display/LogicalDisplayMapper.java
@@ -890,10 +890,12 @@
                 // still need to let WindowManager know so it can update its own internal state for
                 // things like display cutouts.
                 display.getNonOverrideDisplayInfoLocked(mTempDisplayInfo);
-                if (!mTempNonOverrideDisplayInfo.equals(mTempDisplayInfo)) {
-                    logicalDisplayEventMask |= LOGICAL_DISPLAY_EVENT_BASIC_CHANGED
-                            | LOGICAL_DISPLAY_EVENT_REFRESH_RATE_CHANGED;
+                if (!mTempNonOverrideDisplayInfo.equals(mTempDisplayInfo,
+                        /* compareOnlyBasicChanges */ true)) {
+                    logicalDisplayEventMask |= LOGICAL_DISPLAY_EVENT_BASIC_CHANGED;
                 }
+                logicalDisplayEventMask
+                        |= updateAndGetMaskForDisplayPropertyChanges(mTempNonOverrideDisplayInfo);
             }
             mLogicalDisplaysToUpdate.put(displayId, logicalDisplayEventMask);
             mUpdatedLogicalDisplays.put(displayId, UPDATE_STATE_UPDATED);
diff --git a/services/core/java/com/android/server/display/feature/DisplayManagerFlags.java b/services/core/java/com/android/server/display/feature/DisplayManagerFlags.java
index c3057de..7cc178d 100644
--- a/services/core/java/com/android/server/display/feature/DisplayManagerFlags.java
+++ b/services/core/java/com/android/server/display/feature/DisplayManagerFlags.java
@@ -280,6 +280,11 @@
             Flags::committedStateSeparateEvent
     );
 
+    private final FlagState mDelayImplicitRrRegistrationUntilRrAccessed = new FlagState(
+            Flags.FLAG_DELAY_IMPLICIT_RR_REGISTRATION_UNTIL_RR_ACCESSED,
+            Flags::delayImplicitRrRegistrationUntilRrAccessed
+    );
+
     /**
      * @return {@code true} if 'port' is allowed in display layout configuration file.
      */
@@ -586,7 +591,6 @@
         return mFramerateOverrideTriggersRrCallbacks.isEnabled();
     }
 
-
     /**
      * @return {@code true} if the flag for sending refresh rate events only for the apps in
      * foreground is enabled
@@ -604,6 +608,13 @@
     }
 
     /**
+     * @return {@code true} if the flag for only explicit subscription for RR changes is enabled
+     */
+    public boolean isDelayImplicitRrRegistrationUntilRrAccessedEnabled() {
+        return mDelayImplicitRrRegistrationUntilRrAccessed.isEnabled();
+    }
+
+    /**
      * dumps all flagstates
      * @param pw printWriter
      */
@@ -660,6 +671,7 @@
         pw.println(" " + mFramerateOverrideTriggersRrCallbacks);
         pw.println(" " + mRefreshRateEventForForegroundApps);
         pw.println(" " + mCommittedStateSeparateEvent);
+        pw.println(" " + mDelayImplicitRrRegistrationUntilRrAccessed);
     }
 
     private static class FlagState {
diff --git a/services/core/java/com/android/server/display/feature/display_flags.aconfig b/services/core/java/com/android/server/display/feature/display_flags.aconfig
index f545130..a0064a9 100644
--- a/services/core/java/com/android/server/display/feature/display_flags.aconfig
+++ b/services/core/java/com/android/server/display/feature/display_flags.aconfig
@@ -474,9 +474,9 @@
     description: "Feature flag to trigger the RR callbacks when framerate overridding happens."
     bug: "390113266"
     is_fixed_read_only: true
-        metadata {
-          purpose: PURPOSE_BUGFIX
-        }
+    metadata {
+      purpose: PURPOSE_BUGFIX
+    }
 }
 
 flag {
@@ -508,3 +508,14 @@
       purpose: PURPOSE_BUGFIX
     }
 }
+
+flag {
+    name: "delay_implicit_rr_registration_until_rr_accessed"
+    namespace: "display_manager"
+    description: "Feature flag for clients to subscribe to RR changes by either explicitly subscribing for refresh rate changes or request for refresh rate data"
+    bug: "391828526"
+    is_fixed_read_only: true
+    metadata {
+      purpose: PURPOSE_BUGFIX
+    }
+}
diff --git a/services/core/java/com/android/server/inputmethod/ClientController.java b/services/core/java/com/android/server/inputmethod/ClientController.java
index 0381a31..d03c086 100644
--- a/services/core/java/com/android/server/inputmethod/ClientController.java
+++ b/services/core/java/com/android/server/inputmethod/ClientController.java
@@ -43,21 +43,23 @@
     @GuardedBy("ImfLock.class")
     private final List<ClientControllerCallback> mCallbacks = new ArrayList<>();
 
+    @NonNull
     private final PackageManagerInternal mPackageManagerInternal;
 
     interface ClientControllerCallback {
 
-        void onClientRemoved(ClientState client);
+        void onClientRemoved(@NonNull ClientState client);
     }
 
-    ClientController(PackageManagerInternal packageManagerInternal) {
+    ClientController(@NonNull PackageManagerInternal packageManagerInternal) {
         mPackageManagerInternal = packageManagerInternal;
     }
 
     @GuardedBy("ImfLock.class")
-    ClientState addClient(IInputMethodClientInvoker clientInvoker,
-            IRemoteInputConnection inputConnection, int selfReportedDisplayId, int callerUid,
-            int callerPid) {
+    @NonNull
+    ClientState addClient(@NonNull IInputMethodClientInvoker clientInvoker,
+            @NonNull IRemoteInputConnection fallbackInputConnection, int selfReportedDisplayId,
+            int callerUid, int callerPid) {
         final IBinder.DeathRecipient deathRecipient = () -> {
             // Exceptionally holding ImfLock here since this is a internal lambda expression.
             synchronized (ImfLock.class) {
@@ -90,15 +92,15 @@
         // have the client crash.  Thus we do not verify the display ID at all here.  Instead we
         // later check the display ID every time the client needs to interact with the specified
         // display.
-        final ClientState cs = new ClientState(clientInvoker, inputConnection,
-                callerUid, callerPid, selfReportedDisplayId, deathRecipient);
+        final var cs = new ClientState(clientInvoker, fallbackInputConnection, callerUid, callerPid,
+                selfReportedDisplayId, deathRecipient);
         mClients.put(clientInvoker.asBinder(), cs);
         return cs;
     }
 
     @VisibleForTesting
     @GuardedBy("ImfLock.class")
-    boolean removeClient(IInputMethodClient client) {
+    boolean removeClient(@NonNull IInputMethodClient client) {
         return removeClientAsBinder(client.asBinder());
     }
 
@@ -116,7 +118,7 @@
     }
 
     @GuardedBy("ImfLock.class")
-    void addClientControllerCallback(ClientControllerCallback callback) {
+    void addClientControllerCallback(@NonNull ClientControllerCallback callback) {
         mCallbacks.add(callback);
     }
 
@@ -127,15 +129,15 @@
     }
 
     @GuardedBy("ImfLock.class")
-    void forAllClients(Consumer<ClientState> consumer) {
+    void forAllClients(@NonNull Consumer<ClientState> consumer) {
         for (int i = 0; i < mClients.size(); i++) {
             consumer.accept(mClients.valueAt(i));
         }
     }
 
     @GuardedBy("ImfLock.class")
-    boolean verifyClientAndPackageMatch(
-            @NonNull IInputMethodClient client, @NonNull String packageName) {
+    boolean verifyClientAndPackageMatch(@NonNull IInputMethodClient client,
+            @NonNull String packageName) {
         final ClientState cs = mClients.get(client.asBinder());
         if (cs == null) {
             throw new IllegalArgumentException("unknown client " + client.asBinder());
diff --git a/services/core/java/com/android/server/inputmethod/ClientState.java b/services/core/java/com/android/server/inputmethod/ClientState.java
index e98a5a7..ea80995 100644
--- a/services/core/java/com/android/server/inputmethod/ClientState.java
+++ b/services/core/java/com/android/server/inputmethod/ClientState.java
@@ -16,6 +16,8 @@
 
 package com.android.server.inputmethod;
 
+import android.annotation.NonNull;
+import android.annotation.Nullable;
 import android.os.IBinder;
 import android.util.SparseArray;
 import android.view.inputmethod.InputBinding;
@@ -24,12 +26,17 @@
 import com.android.internal.inputmethod.IRemoteInputConnection;
 
 final class ClientState {
+
+    @NonNull
     final IInputMethodClientInvoker mClient;
+    @NonNull
     final IRemoteInputConnection mFallbackInputConnection;
     final int mUid;
     final int mPid;
     final int mSelfReportedDisplayId;
+    @NonNull
     final InputBinding mBinding;
+    @NonNull
     final IBinder.DeathRecipient mClientDeathRecipient;
 
     @GuardedBy("ImfLock.class")
@@ -39,30 +46,31 @@
     boolean mSessionRequestedForAccessibility;
 
     @GuardedBy("ImfLock.class")
+    @Nullable
     InputMethodManagerService.SessionState mCurSession;
 
     @GuardedBy("ImfLock.class")
-    SparseArray<InputMethodManagerService.AccessibilitySessionState> mAccessibilitySessions =
+    @NonNull
+    final SparseArray<InputMethodManagerService.AccessibilitySessionState> mAccessibilitySessions =
             new SparseArray<>();
 
-    @Override
-    public String toString() {
-        return "ClientState{" + Integer.toHexString(
-                System.identityHashCode(this)) + " mUid=" + mUid
-                + " mPid=" + mPid + " mSelfReportedDisplayId=" + mSelfReportedDisplayId + "}";
-    }
-
-    ClientState(IInputMethodClientInvoker client,
-            IRemoteInputConnection fallbackInputConnection,
-            int uid, int pid, int selfReportedDisplayId,
-            IBinder.DeathRecipient clientDeathRecipient) {
+    ClientState(@NonNull IInputMethodClientInvoker client,
+            @NonNull IRemoteInputConnection fallbackInputConnection, int uid, int pid,
+            int selfReportedDisplayId, @NonNull IBinder.DeathRecipient clientDeathRecipient) {
         mClient = client;
         mFallbackInputConnection = fallbackInputConnection;
         mUid = uid;
         mPid = pid;
         mSelfReportedDisplayId = selfReportedDisplayId;
-        mBinding = new InputBinding(null /*conn*/, mFallbackInputConnection.asBinder(), mUid,
+        mBinding = new InputBinding(null /* conn */, fallbackInputConnection.asBinder(), mUid,
                 mPid);
         mClientDeathRecipient = clientDeathRecipient;
     }
+
+    @Override
+    public String toString() {
+        return "ClientState{" + Integer.toHexString(System.identityHashCode(this))
+                + " mUid=" + mUid + " mPid=" + mPid
+                + " mSelfReportedDisplayId=" + mSelfReportedDisplayId + "}";
+    }
 }
diff --git a/services/core/java/com/android/server/inputmethod/IInputMethodClientInvoker.java b/services/core/java/com/android/server/inputmethod/IInputMethodClientInvoker.java
index 650ea60..9d88983 100644
--- a/services/core/java/com/android/server/inputmethod/IInputMethodClientInvoker.java
+++ b/services/core/java/com/android/server/inputmethod/IInputMethodClientInvoker.java
@@ -58,23 +58,17 @@
     private final Handler mHandler;
 
     @AnyThread
-    @Nullable
-    static IInputMethodClientInvoker create(@Nullable IInputMethodClient inputMethodClient,
+    @NonNull
+    static IInputMethodClientInvoker create(@NonNull IInputMethodClient inputMethodClient,
             @NonNull Handler handler) {
-        if (inputMethodClient == null) {
-            return null;
-        }
         final boolean isProxy = Binder.isProxy(inputMethodClient);
         return new IInputMethodClientInvoker(inputMethodClient, isProxy, isProxy ? null : handler);
     }
 
     @AnyThread
-    @Nullable
-    static IInputMethodClientInvoker create$ravenwood(
-            @Nullable IInputMethodClient inputMethodClient, @NonNull Handler handler) {
-        if (inputMethodClient == null) {
-            return null;
-        }
+    @NonNull
+    static IInputMethodClientInvoker create$ravenwood(@NonNull IInputMethodClient inputMethodClient,
+            @NonNull Handler handler) {
         return new IInputMethodClientInvoker(inputMethodClient, true, null);
     }
 
diff --git a/services/core/java/com/android/server/inputmethod/IInputMethodManagerImpl.java b/services/core/java/com/android/server/inputmethod/IInputMethodManagerImpl.java
index a8b61af..02987a9 100644
--- a/services/core/java/com/android/server/inputmethod/IInputMethodManagerImpl.java
+++ b/services/core/java/com/android/server/inputmethod/IInputMethodManagerImpl.java
@@ -89,8 +89,8 @@
 
     @BinderThread
     interface Callback {
-        void addClient(IInputMethodClient client, IRemoteInputConnection inputConnection,
-                int selfReportedDisplayId);
+        void addClient(@NonNull IInputMethodClient client,
+                @NonNull IRemoteInputConnection inputConnection, int selfReportedDisplayId);
 
         InputMethodInfo getCurrentInputMethodInfoAsUser(@UserIdInt int userId);
 
@@ -242,9 +242,9 @@
     }
 
     @Override
-    public void addClient(IInputMethodClient client, IRemoteInputConnection inputmethod,
-            int untrustedDisplayId) {
-        mCallback.addClient(client, inputmethod, untrustedDisplayId);
+    public void addClient(@NonNull IInputMethodClient client,
+            @NonNull IRemoteInputConnection fallbackInputConnection, int untrustedDisplayId) {
+        mCallback.addClient(client, fallbackInputConnection, untrustedDisplayId);
     }
 
     @Override
@@ -414,7 +414,7 @@
     }
 
     @Override
-    public void reportPerceptibleAsync(IBinder windowToken, boolean perceptible) {
+    public void reportPerceptibleAsync(@NonNull IBinder windowToken, boolean perceptible) {
         mCallback.reportPerceptibleAsync(windowToken, perceptible);
     }
 
diff --git a/services/core/java/com/android/server/inputmethod/ImeBindingState.java b/services/core/java/com/android/server/inputmethod/ImeBindingState.java
index 5deed39..adfd991 100644
--- a/services/core/java/com/android/server/inputmethod/ImeBindingState.java
+++ b/services/core/java/com/android/server/inputmethod/ImeBindingState.java
@@ -43,9 +43,9 @@
     final int mUserId;
 
     /**
-     * The last window token that we confirmed to be focused.  This is always updated upon
-     * reports from the input method client. If the window state is already changed before the
-     * report is handled, this field just keeps the last value.
+     * The last window token that we confirmed to be focused. This is always updated upon reports
+     * from the input method client. If the window state is already changed before the report is
+     * handled, this field just keeps the last value.
      */
     @Nullable
     final IBinder mFocusedWindow;
@@ -59,11 +59,9 @@
     final int mFocusedWindowSoftInputMode;
 
     /**
-     * The client by which {@link #mFocusedWindow} was reported. This gets updated whenever
-     * an
-     * IME-focusable window gained focus (without necessarily starting an input connection),
-     * while {@link InputMethodManagerService#mClient} only gets updated when we actually start an
-     * input connection.
+     * The client by which {@link #mFocusedWindow} was reported. This gets updated whenever an
+     * IME-focusable window gained focus (without necessarily starting an input connection), while
+     * {@link UserData#mCurClient} only gets updated when we actually start an input connection.
      *
      * @see #mFocusedWindow
      */
@@ -72,15 +70,16 @@
 
     /**
      * The editor info by which {@link #mFocusedWindow} was reported. This differs from
-     * {@link InputMethodManagerService#mCurEditorInfo} the same way {@link #mFocusedWindowClient}
-     * differs from {@link InputMethodManagerService#mCurClient}.
+     * {@link UserData#mCurEditorInfo} the same way {@link #mFocusedWindowClient} differs from
+     * {@link UserData#mCurClient}.
      *
      * @see #mFocusedWindow
      */
     @Nullable
     final EditorInfo mFocusedWindowEditorInfo;
 
-    void dumpDebug(ProtoOutputStream proto, WindowManagerInternal windowManagerInternal) {
+    void dumpDebug(@NonNull ProtoOutputStream proto,
+            @NonNull WindowManagerInternal windowManagerInternal) {
         proto.write(CUR_FOCUSED_WINDOW_NAME,
                 windowManagerInternal.getWindowName(mFocusedWindow));
         proto.write(CUR_FOCUSED_WINDOW_SOFT_INPUT_MODE,
@@ -94,18 +93,14 @@
         p.println(prefix + "mFocusedWindowClient=" + mFocusedWindowClient);
     }
 
+    @NonNull
     static ImeBindingState newEmptyState() {
-        return new ImeBindingState(
-                /*userId=*/ UserHandle.USER_NULL,
-                /*focusedWindow=*/ null,
-                /*focusedWindowSoftInputMode=*/ SOFT_INPUT_STATE_UNSPECIFIED,
-                /*focusedWindowClient=*/ null,
-                /*focusedWindowEditorInfo=*/ null
-        );
+        return new ImeBindingState(UserHandle.USER_NULL /* userId */, null /* focusedWindow */,
+                SOFT_INPUT_STATE_UNSPECIFIED /* focusedWindowSoftInputMode */,
+                null /* focusedWindowClient */, null /* focusedWindowEditorInfo */);
     }
 
-    ImeBindingState(@UserIdInt int userId,
-            @Nullable IBinder focusedWindow,
+    ImeBindingState(@UserIdInt int userId, @Nullable IBinder focusedWindow,
             @SoftInputModeFlags int focusedWindowSoftInputMode,
             @Nullable ClientState focusedWindowClient,
             @Nullable EditorInfo focusedWindowEditorInfo) {
diff --git a/services/core/java/com/android/server/inputmethod/InputMethodManagerService.java b/services/core/java/com/android/server/inputmethod/InputMethodManagerService.java
index dfdd9e5..395d85e 100644
--- a/services/core/java/com/android/server/inputmethod/InputMethodManagerService.java
+++ b/services/core/java/com/android/server/inputmethod/InputMethodManagerService.java
@@ -482,10 +482,15 @@
     };
 
     static class SessionState {
+
+        @NonNull
         final ClientState mClient;
+        @NonNull
         final IInputMethodInvoker mMethod;
 
+        @Nullable
         IInputMethodSession mSession;
+        @Nullable
         InputChannel mChannel;
 
         @UserIdInt
@@ -496,14 +501,13 @@
             return "SessionState{uid=" + mClient.mUid + " pid=" + mClient.mPid
                     + " method=" + Integer.toHexString(
                     IInputMethodInvoker.getBinderIdentityHashCode(mMethod))
-                    + " session=" + Integer.toHexString(
-                    System.identityHashCode(mSession))
+                    + " session=" + Integer.toHexString(System.identityHashCode(mSession))
                     + " channel=" + mChannel
                     + " userId=" + mUserId
                     + "}";
         }
 
-        SessionState(ClientState client, IInputMethodInvoker method,
+        SessionState(@NonNull ClientState client, @NonNull IInputMethodInvoker method,
                 IInputMethodSession session, InputChannel channel, @UserIdInt int userId) {
             mClient = client;
             mMethod = method;
@@ -517,22 +521,24 @@
      * Record session state for an accessibility service.
      */
     static class AccessibilitySessionState {
+
+        @NonNull
         final ClientState mClient;
         // Id of the accessibility service.
         final int mId;
 
-        public IAccessibilityInputMethodSession mSession;
+        @Nullable
+        IAccessibilityInputMethodSession mSession;
 
         @Override
         public String toString() {
             return "AccessibilitySessionState{uid=" + mClient.mUid + " pid=" + mClient.mPid
                     + " id=" + Integer.toHexString(mId)
-                    + " session=" + Integer.toHexString(
-                    System.identityHashCode(mSession))
+                    + " session=" + Integer.toHexString(System.identityHashCode(mSession))
                     + "}";
         }
 
-        AccessibilitySessionState(ClientState client, int id,
+        AccessibilitySessionState(@NonNull ClientState client, int id,
                 IAccessibilityInputMethodSession session) {
             mClient = client;
             mId = id;
@@ -544,6 +550,7 @@
      * Manages the IME clients.
      */
     @SharedByAllUsersField
+    @NonNull
     private final ClientController mClientController;
 
     /**
@@ -1770,19 +1777,21 @@
      * <p>As a general principle, IPCs from the application process that take
      * {@link IInputMethodClient} will be rejected without this step.</p>
      *
-     * @param client                {@link android.os.Binder} proxy that is associated with the
-     *                              singleton instance of
-     *                              {@link android.view.inputmethod.InputMethodManager} that runs
-     *                              on the client process
-     * @param inputConnection       communication channel for the fallback {@link InputConnection}
-     * @param selfReportedDisplayId self-reported display ID to which the client is associated.
-     *                              Whether the client is still allowed to access to this display
-     *                              or not needs to be evaluated every time the client interacts
-     *                              with the display
+     * @param client                  {@link android.os.Binder} proxy that is associated with the
+     *                                singleton instance of
+     *                                {@link android.view.inputmethod.InputMethodManager} that runs
+     *                                on the client process
+     * @param fallbackInputConnection communication channel for the fallback {@link InputConnection}
+     * @param selfReportedDisplayId   self-reported display ID to which the client is associated.
+     *                                Whether the client is still allowed to access to this display
+     *                                or not needs to be evaluated every time the client interacts
+     *                                with the display
      */
     @Override
-    public void addClient(IInputMethodClient client, IRemoteInputConnection inputConnection,
-            int selfReportedDisplayId) {
+    public void addClient(@NonNull IInputMethodClient client,
+            @NonNull IRemoteInputConnection fallbackInputConnection, int selfReportedDisplayId) {
+        Objects.requireNonNull(client, "client must not be null");
+        Objects.requireNonNull(fallbackInputConnection, "fallbackInputConnection must not be null");
         // Here there are two scenarios where this method is called:
         // A. IMM is being instantiated in a different process and this is an IPC from that process
         // B. IMM is being instantiated in the same process but Binder.clearCallingIdentity() is
@@ -1791,16 +1800,15 @@
         // actually running.
         final int callerUid = Binder.getCallingUid();
         final int callerPid = Binder.getCallingPid();
-        final IInputMethodClientInvoker clientInvoker =
-                IInputMethodClientInvoker.create(client, mHandler);
+        final var clientInvoker = IInputMethodClientInvoker.create(client, mHandler);
         synchronized (ImfLock.class) {
-            mClientController.addClient(clientInvoker, inputConnection, selfReportedDisplayId,
-                    callerUid, callerPid);
+            mClientController.addClient(clientInvoker, fallbackInputConnection,
+                    selfReportedDisplayId, callerUid, callerPid);
         }
     }
 
     @GuardedBy("ImfLock.class")
-    private void onClientRemoved(ClientState client) {
+    private void onClientRemoved(@NonNull ClientState client) {
         clearClientSessionLocked(client);
         clearClientSessionForAccessibilityLocked(client);
         // TODO(b/324907325): Remove the suppress warnings once b/324907325 is fixed.
@@ -1814,7 +1822,8 @@
      */
     // TODO(b/325515685): Move this method to InputMethodBindingController
     @GuardedBy("ImfLock.class")
-    private void onClientRemovedInternalLocked(ClientState client, @NonNull UserData userData) {
+    private void onClientRemovedInternalLocked(@NonNull ClientState client,
+            @NonNull UserData userData) {
         final int userId = userData.mUserId;
         if (userData.mCurClient == client) {
             hideCurrentInputLocked(userData.mImeBindingState.mFocusedWindow, 0 /* flags */,
@@ -1962,7 +1971,7 @@
         session.mMethod.startInput(startInputToken, userData.mCurInputConnection,
                 userData.mCurEditorInfo, restarting, navButtonFlags, userData.mCurImeDispatcher);
         if (Flags.refactorInsetsController()) {
-            if (isShowRequestedForCurrentWindow(userId) && userData.mImeBindingState != null
+            if (isShowRequestedForCurrentWindow(userId)
                     && userData.mImeBindingState.mFocusedWindow != null) {
                 // Re-use current statsToken, if it exists.
                 final var statsToken = userData.mCurStatsToken != null ? userData.mCurStatsToken
@@ -2028,15 +2037,14 @@
         }
     }
 
+    @NonNull
     private SparseArray<IAccessibilityInputMethodSession> createAccessibilityInputMethodSessions(
-            SparseArray<AccessibilitySessionState> accessibilitySessions) {
-        final SparseArray<IAccessibilityInputMethodSession> accessibilityInputMethodSessions =
-                new SparseArray<>();
-        if (accessibilitySessions != null) {
-            for (int i = 0; i < accessibilitySessions.size(); i++) {
-                accessibilityInputMethodSessions.append(accessibilitySessions.keyAt(i),
-                        accessibilitySessions.valueAt(i).mSession);
-            }
+            @NonNull SparseArray<AccessibilitySessionState> accessibilitySessions) {
+        final var accessibilityInputMethodSessions =
+                new SparseArray<IAccessibilityInputMethodSession>();
+        for (int i = 0; i < accessibilitySessions.size(); i++) {
+            accessibilityInputMethodSessions.append(accessibilitySessions.keyAt(i),
+                    accessibilitySessions.valueAt(i).mSession);
         }
         return accessibilityInputMethodSessions;
     }
@@ -2547,14 +2555,14 @@
     }
 
     @GuardedBy("ImfLock.class")
-    void clearClientSessionLocked(ClientState cs) {
+    void clearClientSessionLocked(@NonNull ClientState cs) {
         finishSessionLocked(cs.mCurSession);
         cs.mCurSession = null;
         cs.mSessionRequested = false;
     }
 
     @GuardedBy("ImfLock.class")
-    void clearClientSessionForAccessibilityLocked(ClientState cs) {
+    void clearClientSessionForAccessibilityLocked(@NonNull ClientState cs) {
         for (int i = 0; i < cs.mAccessibilitySessions.size(); i++) {
             finishSessionForAccessibilityLocked(cs.mAccessibilitySessions.valueAt(i));
         }
@@ -2563,7 +2571,7 @@
     }
 
     @GuardedBy("ImfLock.class")
-    void clearClientSessionForAccessibilityLocked(ClientState cs, int id) {
+    void clearClientSessionForAccessibilityLocked(@NonNull ClientState cs, int id) {
         AccessibilitySessionState session = cs.mAccessibilitySessions.get(id);
         if (session != null) {
             finishSessionForAccessibilityLocked(session);
@@ -2572,7 +2580,7 @@
     }
 
     @GuardedBy("ImfLock.class")
-    private void finishSessionLocked(SessionState sessionState) {
+    private void finishSessionLocked(@Nullable SessionState sessionState) {
         if (sessionState != null) {
             if (sessionState.mSession != null) {
                 try {
@@ -2885,8 +2893,7 @@
         ProtoLog.v(IMMS_DEBUG, "IME window vis: %s active: %s visible: %s displayId: %s", vis,
                 (vis & InputMethodService.IME_ACTIVE), (vis & InputMethodService.IME_VISIBLE),
                 curTokenDisplayId);
-        final IBinder focusedWindowToken = userData.mImeBindingState != null
-                ? userData.mImeBindingState.mFocusedWindow : null;
+        final IBinder focusedWindowToken = userData.mImeBindingState.mFocusedWindow;
         final Boolean windowPerceptible = focusedWindowToken != null
                 ? mFocusedWindowPerceptible.get(focusedWindowToken) : null;
 
@@ -3474,7 +3481,7 @@
 
     @BinderThread
     @Override
-    public void reportPerceptibleAsync(IBinder windowToken, boolean perceptible) {
+    public void reportPerceptibleAsync(@NonNull IBinder windowToken, boolean perceptible) {
         Binder.withCleanCallingIdentity(() -> {
             Objects.requireNonNull(windowToken, "windowToken must not be null");
             synchronized (ImfLock.class) {
@@ -4868,7 +4875,7 @@
 
     @GuardedBy("ImfLock.class")
     void setEnabledSessionForAccessibilityLocked(
-            SparseArray<AccessibilitySessionState> accessibilitySessions,
+            @NonNull SparseArray<AccessibilitySessionState> accessibilitySessions,
             @NonNull UserData userData) {
         // mEnabledAccessibilitySessions could the same object as accessibilitySessions.
         SparseArray<IAccessibilityInputMethodSession> disabledSessions = new SparseArray<>();
@@ -5156,7 +5163,7 @@
                     interactive ? bindingController.getImeWindowVis() : 0,
                     bindingController.getBackDisposition(), userId);
             // Inform the current client of the change in active status
-            if (userData.mCurClient == null || userData.mCurClient.mClient == null) {
+            if (userData.mCurClient == null) {
                 return;
             }
             if (mImePlatformCompatUtils.shouldUseSetInteractiveProtocol(
@@ -5920,9 +5927,8 @@
                 // from all clients.
                 if (bindingController.getCurMethod() != null) {
                     // TODO(b/324907325): Remove the suppress warnings once b/324907325 is fixed.
-                    @SuppressWarnings("GuardedBy") Consumer<ClientState> clearClientSession =
-                            c -> clearClientSessionForAccessibilityLocked(c,
-                                    accessibilityConnectionId);
+                    @SuppressWarnings("GuardedBy") Consumer<ClientState> clearClientSession = c ->
+                            clearClientSessionForAccessibilityLocked(c, accessibilityConnectionId);
                     mClientController.forAllClients(clearClientSession);
 
                     AccessibilitySessionState session = userData.mEnabledAccessibilitySessions.get(
@@ -5998,7 +6004,7 @@
     @BinderThread
     @GuardedBy("ImfLock.class")
     private void reportFullscreenModeLocked(boolean fullscreen, @NonNull UserData userData) {
-        if (userData.mCurClient != null && userData.mCurClient.mClient != null) {
+        if (userData.mCurClient != null) {
             userData.mInFullscreenMode = fullscreen;
             userData.mCurClient.mClient.reportFullscreenMode(fullscreen);
         }
@@ -6732,9 +6738,7 @@
     boolean setImeVisibilityOnFocusedWindowClient(boolean visible, UserData userData,
             @NonNull ImeTracker.Token statsToken) {
         if (Flags.refactorInsetsController()) {
-            if (userData.mImeBindingState != null
-                    && userData.mImeBindingState.mFocusedWindowClient != null
-                    && userData.mImeBindingState.mFocusedWindowClient.mClient != null) {
+            if (userData.mImeBindingState.mFocusedWindowClient != null) {
                 userData.mImeBindingState.mFocusedWindowClient.mClient.setImeVisibility(visible,
                         statsToken);
                 return true;
diff --git a/services/core/java/com/android/server/inputmethod/UserData.java b/services/core/java/com/android/server/inputmethod/UserData.java
index 96da17e..7377908a 100644
--- a/services/core/java/com/android/server/inputmethod/UserData.java
+++ b/services/core/java/com/android/server/inputmethod/UserData.java
@@ -140,9 +140,9 @@
     InputMethodManagerService.SessionState mEnabledSession;
 
     @GuardedBy("ImfLock.class")
-    @Nullable
-    SparseArray<InputMethodManagerService.AccessibilitySessionState>
-            mEnabledAccessibilitySessions = new SparseArray<>();
+    @NonNull
+    SparseArray<InputMethodManagerService.AccessibilitySessionState> mEnabledAccessibilitySessions =
+            new SparseArray<>();
 
     /**
      * A per-user cache of {@link InputMethodSettings#getEnabledInputMethodsStr()}.
diff --git a/services/core/java/com/android/server/inputmethod/ZeroJankProxy.java b/services/core/java/com/android/server/inputmethod/ZeroJankProxy.java
index b863c96..7252925 100644
--- a/services/core/java/com/android/server/inputmethod/ZeroJankProxy.java
+++ b/services/core/java/com/android/server/inputmethod/ZeroJankProxy.java
@@ -128,9 +128,9 @@
     }
 
     @Override
-    public void addClient(IInputMethodClient client, IRemoteInputConnection inputConnection,
-            int selfReportedDisplayId) {
-        offload(() -> mInner.addClient(client, inputConnection, selfReportedDisplayId));
+    public void addClient(@NonNull IInputMethodClient client,
+            @NonNull IRemoteInputConnection fallbackInputConnection, int selfReportedDisplayId) {
+        offload(() -> mInner.addClient(client, fallbackInputConnection, selfReportedDisplayId));
     }
 
     @Override
@@ -331,7 +331,7 @@
     }
 
     @Override
-    public void reportPerceptibleAsync(IBinder windowToken, boolean perceptible) {
+    public void reportPerceptibleAsync(@NonNull IBinder windowToken, boolean perceptible) {
         // Already async TODO(b/293640003): ordering issues?
         mInner.reportPerceptibleAsync(windowToken, perceptible);
     }
@@ -468,7 +468,7 @@
             IInputMethodClient client, InputBindResult res, int startInputSeq) {
         synchronized (ImfLock.class) {
             final ClientState cs = mInner.getClientStateLocked(client);
-            if (cs != null && cs.mClient != null) {
+            if (cs != null) {
                 cs.mClient.onStartInputResult(res, startInputSeq);
             } else {
                 // client is unbound.
diff --git a/services/core/java/com/android/server/location/contexthub/ContextHubEndpointBroker.java b/services/core/java/com/android/server/location/contexthub/ContextHubEndpointBroker.java
index 1a29150..940bcb4 100644
--- a/services/core/java/com/android/server/location/contexthub/ContextHubEndpointBroker.java
+++ b/services/core/java/com/android/server/location/contexthub/ContextHubEndpointBroker.java
@@ -16,6 +16,7 @@
 
 package com.android.server.location.contexthub;
 
+import android.annotation.NonNull;
 import android.app.AppOpsManager;
 import android.content.Context;
 import android.hardware.contexthub.EndpointInfo;
@@ -64,7 +65,7 @@
      * Internal interface used to invoke client callbacks.
      */
     interface CallbackConsumer {
-        void accept(IContextHubEndpointCallback callback) throws RemoteException;
+        void accept(@NonNull IContextHubEndpointCallback callback) throws RemoteException;
     }
 
     /** The context of the service. */
@@ -86,7 +87,7 @@
     private final EndpointInfo mHalEndpointInfo;
 
     /** The remote callback interface for this endpoint. */
-    private final IContextHubEndpointCallback mContextHubEndpointCallback;
+    @NonNull private final IContextHubEndpointCallback mContextHubEndpointCallback;
 
     /** True if this endpoint is registered with the service/HAL. */
     @GuardedBy("mRegistrationLock")
@@ -158,7 +159,7 @@
             IEndpointCommunication hubInterface,
             ContextHubEndpointManager endpointManager,
             EndpointInfo halEndpointInfo,
-            IContextHubEndpointCallback callback,
+            @NonNull IContextHubEndpointCallback callback,
             String packageName,
             String attributionTag,
             ContextHubTransactionManager transactionManager) {
@@ -419,9 +420,7 @@
     }
 
     /* package */ void attachDeathRecipient() throws RemoteException {
-        if (mContextHubEndpointCallback != null) {
-            mContextHubEndpointCallback.asBinder().linkToDeath(this, 0 /* flags */);
-        }
+        mContextHubEndpointCallback.asBinder().linkToDeath(this, 0 /* flags */);
     }
 
     /* package */ void onEndpointSessionOpenRequest(
@@ -664,15 +663,13 @@
      * @return false if the callback threw a RemoteException
      */
     private boolean invokeCallback(CallbackConsumer consumer) {
-        if (mContextHubEndpointCallback != null) {
-            acquireWakeLock();
-            try {
-                consumer.accept(mContextHubEndpointCallback);
-            } catch (RemoteException e) {
-                Log.e(TAG, "RemoteException while calling endpoint callback", e);
-                releaseWakeLock();
-                return false;
-            }
+        acquireWakeLock();
+        try {
+            consumer.accept(mContextHubEndpointCallback);
+        } catch (RemoteException e) {
+            Log.e(TAG, "RemoteException while calling endpoint callback", e);
+            releaseWakeLock();
+            return false;
         }
         return true;
     }
diff --git a/services/core/java/com/android/server/location/contexthub/ContextHubEndpointManager.java b/services/core/java/com/android/server/location/contexthub/ContextHubEndpointManager.java
index 30bb8f3..8ab581e 100644
--- a/services/core/java/com/android/server/location/contexthub/ContextHubEndpointManager.java
+++ b/services/core/java/com/android/server/location/contexthub/ContextHubEndpointManager.java
@@ -17,6 +17,7 @@
 package com.android.server.location.contexthub;
 
 import android.annotation.IntDef;
+import android.annotation.NonNull;
 import android.content.Context;
 import android.hardware.contexthub.ContextHubInfo;
 import android.hardware.contexthub.EndpointInfo;
@@ -240,7 +241,7 @@
      */
     /* package */ IContextHubEndpoint registerEndpoint(
             HubEndpointInfo pendingEndpointInfo,
-            IContextHubEndpointCallback callback,
+            @NonNull IContextHubEndpointCallback callback,
             String packageName,
             String attributionTag)
             throws RemoteException {
diff --git a/services/core/java/com/android/server/location/contexthub/ContextHubService.java b/services/core/java/com/android/server/location/contexthub/ContextHubService.java
index bf7351c..2c0c55b 100644
--- a/services/core/java/com/android/server/location/contexthub/ContextHubService.java
+++ b/services/core/java/com/android/server/location/contexthub/ContextHubService.java
@@ -792,6 +792,10 @@
             Log.e(TAG, "Endpoint manager failed to initialize");
             throw new UnsupportedOperationException("Endpoint registration is not supported");
         }
+        if (callback == null) {
+            Log.e(TAG, "Endpoint callback is invalid");
+            throw new IllegalArgumentException("registerEndpoint must have a non-null callback");
+        }
         return mEndpointManager.registerEndpoint(
                 pendingHubEndpointInfo, callback, packageName, attributionTag);
     }
diff --git a/services/core/java/com/android/server/location/injector/SystemEmergencyHelper.java b/services/core/java/com/android/server/location/injector/SystemEmergencyHelper.java
index 177eefb..3f75b11 100644
--- a/services/core/java/com/android/server/location/injector/SystemEmergencyHelper.java
+++ b/services/core/java/com/android/server/location/injector/SystemEmergencyHelper.java
@@ -29,7 +29,6 @@
 import android.util.Log;
 
 import com.android.internal.telephony.TelephonyIntents;
-import com.android.internal.telephony.flags.Flags;
 import com.android.server.FgThread;
 
 import java.util.Objects;
@@ -107,26 +106,19 @@
         boolean isInExtensionTime = mEmergencyCallEndRealtimeMs != Long.MIN_VALUE
                 && (SystemClock.elapsedRealtime() - mEmergencyCallEndRealtimeMs) < extensionTimeMs;
 
-        if (!Flags.enforceTelephonyFeatureMapping()) {
-            return mIsInEmergencyCall
-                    || isInExtensionTime
-                    || mTelephonyManager.getEmergencyCallbackMode()
-                    || mTelephonyManager.isInEmergencySmsMode();
-        } else {
-            boolean emergencyCallbackMode = false;
-            boolean emergencySmsMode = false;
-            PackageManager pm = mContext.getPackageManager();
-            if (pm.hasSystemFeature(PackageManager.FEATURE_TELEPHONY_CALLING)) {
-                emergencyCallbackMode = mTelephonyManager.getEmergencyCallbackMode();
-            }
-            if (pm.hasSystemFeature(PackageManager.FEATURE_TELEPHONY_MESSAGING)) {
-                emergencySmsMode = mTelephonyManager.isInEmergencySmsMode();
-            }
-            return mIsInEmergencyCall
-                    || isInExtensionTime
-                    || emergencyCallbackMode
-                    || emergencySmsMode;
+        boolean emergencyCallbackMode = false;
+        boolean emergencySmsMode = false;
+        PackageManager pm = mContext.getPackageManager();
+        if (pm.hasSystemFeature(PackageManager.FEATURE_TELEPHONY_CALLING)) {
+            emergencyCallbackMode = mTelephonyManager.getEmergencyCallbackMode();
         }
+        if (pm.hasSystemFeature(PackageManager.FEATURE_TELEPHONY_MESSAGING)) {
+            emergencySmsMode = mTelephonyManager.isInEmergencySmsMode();
+        }
+        return mIsInEmergencyCall
+                || isInExtensionTime
+                || emergencyCallbackMode
+                || emergencySmsMode;
     }
 
     private class EmergencyCallTelephonyCallback extends TelephonyCallback implements
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/om/OverlayManagerSettings.java b/services/core/java/com/android/server/om/OverlayManagerSettings.java
index cb01727..1ac9711 100644
--- a/services/core/java/com/android/server/om/OverlayManagerSettings.java
+++ b/services/core/java/com/android/server/om/OverlayManagerSettings.java
@@ -544,6 +544,7 @@
                     // and overwritten.
                     throw new XmlPullParserException("old version " + oldVersion + "; ignoring");
                 case 3:
+                case 4:
                     // Upgrading from version 3 to 5 is not a breaking change so do not ignore the
                     // overlay file.
                     return;
diff --git a/services/core/java/com/android/server/pm/AppDataHelper.java b/services/core/java/com/android/server/pm/AppDataHelper.java
index 29f8243..ae41519 100644
--- a/services/core/java/com/android/server/pm/AppDataHelper.java
+++ b/services/core/java/com/android/server/pm/AppDataHelper.java
@@ -24,6 +24,7 @@
 
 import android.annotation.NonNull;
 import android.annotation.Nullable;
+import android.annotation.SpecialUsers.CanBeALL;
 import android.annotation.UserIdInt;
 import android.content.pm.PackageManager;
 import android.os.CreateAppDataArgs;
@@ -548,7 +549,7 @@
         return prepareAppDataFuture;
     }
 
-    void clearAppDataLIF(AndroidPackage pkg, int userId, int flags) {
+    void clearAppDataLIF(AndroidPackage pkg, @CanBeALL @UserIdInt int userId, int flags) {
         if (pkg == null) {
             return;
         }
@@ -559,7 +560,8 @@
         }
     }
 
-    void clearAppDataLeafLIF(String packageName, String volumeUuid, int userId, int flags) {
+    void clearAppDataLeafLIF(String packageName, String volumeUuid, @CanBeALL @UserIdInt int userId,
+            int flags) {
         final Computer snapshot = mPm.snapshotComputer();
         final PackageStateInternal packageStateInternal =
                 snapshot.getPackageStateInternal(packageName);
diff --git a/services/core/java/com/android/server/pm/BackgroundInstallControlService.java b/services/core/java/com/android/server/pm/BackgroundInstallControlService.java
index c3af578..463989a 100644
--- a/services/core/java/com/android/server/pm/BackgroundInstallControlService.java
+++ b/services/core/java/com/android/server/pm/BackgroundInstallControlService.java
@@ -332,7 +332,8 @@
                 userId)
                 != PERMISSION_GRANTED) {
             if(Build.IS_DEBUGGABLE) {
-                Slog.d(TAG, "handlePackageAdd " + packageName + ": installer doesn't "
+                Slog.d(TAG, "handlePackageAdd " + packageName + ": installer ("
+                    + installerPackageName + ") doesn't "
                     + "have INSTALL_PACKAGES permission, skipping");
             }
             return;
diff --git a/services/core/java/com/android/server/pm/DeletePackageHelper.java b/services/core/java/com/android/server/pm/DeletePackageHelper.java
index 90adb66..38aa57f 100644
--- a/services/core/java/com/android/server/pm/DeletePackageHelper.java
+++ b/services/core/java/com/android/server/pm/DeletePackageHelper.java
@@ -24,6 +24,7 @@
 import static android.content.pm.PackageManager.DELETE_SUCCEEDED;
 import static android.content.pm.PackageManager.MATCH_KNOWN_PACKAGES;
 import static android.content.pm.PackageManager.PERMISSION_GRANTED;
+import static android.os.UserHandle.USER_ALL;
 
 import static com.android.server.pm.InstructionSets.getAppDexInstructionSets;
 import static com.android.server.pm.PackageManagerService.DEBUG_COMPRESSION;
@@ -35,6 +36,8 @@
 import android.Manifest;
 import android.annotation.NonNull;
 import android.annotation.Nullable;
+import android.annotation.SpecialUsers.CanBeALL;
+import android.annotation.UserIdInt;
 import android.app.ApplicationExitInfo;
 import android.app.ApplicationPackageManager;
 import android.content.Intent;
@@ -120,7 +123,7 @@
         final boolean res;
 
         final int removeUser = (deleteFlags & PackageManager.DELETE_ALL_USERS) != 0
-                ? UserHandle.USER_ALL : userId;
+                ? USER_ALL : userId;
 
         final PackageSetting uninstalledPs;
         final PackageSetting disabledSystemPs;
@@ -181,7 +184,7 @@
                 if (libraryInfo != null) {
                     boolean flagSdkLibIndependence = Flags.sdkLibIndependence();
                     for (int currUserId : allUsers) {
-                        if (removeUser != UserHandle.USER_ALL && removeUser != currUserId) {
+                        if (removeUser != USER_ALL && removeUser != currUserId) {
                             continue;
                         }
                         var libClientPackagesPair = computer.getPackagesUsingSharedLibrary(
@@ -225,7 +228,7 @@
                     && ((deleteFlags & PackageManager.DELETE_SYSTEM_APP) == 0)) {
                 // We're downgrading a system app, which will apply to all users, so
                 // freeze them all during the downgrade
-                freezeUser = UserHandle.USER_ALL;
+                freezeUser = USER_ALL;
                 priorUserStates = new SparseArray<>();
                 for (int i = 0; i < allUsers.length; i++) {
                     PackageUserState userState = uninstalledPs.readUserState(allUsers[i]);
@@ -419,7 +422,7 @@
         if (PackageManagerServiceUtils.isSystemApp(ps)) {
             final boolean deleteSystem = (flags & PackageManager.DELETE_SYSTEM_APP) != 0;
             final boolean deleteAllUsers =
-                    user == null || user.getIdentifier() == UserHandle.USER_ALL;
+                    user == null || user.getIdentifier() == USER_ALL;
             if ((!deleteSystem || deleteAllUsers) && disabledPs == null) {
                 Slog.w(TAG, "Attempt to delete unknown system package "
                         + ps.getPkg().getPackageName());
@@ -462,9 +465,9 @@
                     Manifest.permission.SUSPEND_APPS, packageName, userId) == PERMISSION_GRANTED);
         }
 
-        final int userId = user == null ? UserHandle.USER_ALL : user.getIdentifier();
+        final int userId = user == null ? USER_ALL : user.getIdentifier();
         // Remember which users are affected, before the installed states are modified
-        outInfo.mRemovedUsers = userId == UserHandle.USER_ALL
+        outInfo.mRemovedUsers = userId == USER_ALL
                 ? ps.queryUsersInstalledOrHasData(allUserHandles)
                 : new int[]{userId};
         outInfo.populateBroadcastUsers(ps);
@@ -477,7 +480,7 @@
         outInfo.mRemovedPackageVersionCode = ps.getVersionCode();
 
         if ((!systemApp || (flags & PackageManager.DELETE_SYSTEM_APP) != 0)
-                && userId != UserHandle.USER_ALL) {
+                && userId != USER_ALL) {
             // The caller is asking that the package only be deleted for a single
             // user.  To do this, we just mark its uninstalled state and delete
             // its data. If this is a system app, we only allow this to happen if
@@ -550,7 +553,7 @@
         for (final int affectedUserId : outInfo.mRemovedUsers) {
             if (hadSuspendAppsPermission.get(affectedUserId)) {
                 mPm.unsuspendForSuspendingPackage(snapshot, packageName,
-                        affectedUserId /*suspendingUserId*/, true /*inAllUsers*/);
+                        affectedUserId /*suspendingUserId*/, USER_ALL);
                 mPm.removeAllDistractingPackageRestrictions(snapshot, affectedUserId);
             }
         }
@@ -562,7 +565,7 @@
     }
 
     @GuardedBy("mPm.mInstallLock")
-    private void deleteInstalledPackageLIF(PackageSetting ps, int userId,
+    private void deleteInstalledPackageLIF(PackageSetting ps, @CanBeALL @UserIdInt int userId,
             boolean deleteCodeAndResources, int flags, @NonNull int[] allUserHandles,
             @NonNull PackageRemovedInfo outInfo, boolean writeSettings) {
         synchronized (mPm.mLock) {
@@ -588,7 +591,7 @@
 
     @GuardedBy("mPm.mLock")
     private void markPackageUninstalledForUserLPw(PackageSetting ps, UserHandle user, int flags) {
-        final int[] userIds = (user == null || user.getIdentifier() == UserHandle.USER_ALL)
+        final int[] userIds = (user == null || user.getIdentifier() == USER_ALL)
                 ? mUserManagerInternal.getUserIds()
                 : new int[] {user.getIdentifier()};
         for (int nextUserId : userIds) {
@@ -685,7 +688,7 @@
             flags |= PackageManager.DELETE_KEEP_DATA;
         }
         try (PackageManagerTracedLock installLock = mPm.mInstallLock.acquireLock()) {
-            deleteInstalledPackageLIF(deletedPs, UserHandle.USER_ALL, true, flags, allUserHandles,
+            deleteInstalledPackageLIF(deletedPs, USER_ALL, true, flags, allUserHandles,
                     outInfo, writeSettings);
         }
     }
diff --git a/services/core/java/com/android/server/pm/PackageFreezer.java b/services/core/java/com/android/server/pm/PackageFreezer.java
index 11f2059..d66eb81 100644
--- a/services/core/java/com/android/server/pm/PackageFreezer.java
+++ b/services/core/java/com/android/server/pm/PackageFreezer.java
@@ -18,6 +18,8 @@
 
 import android.annotation.NonNull;
 import android.annotation.Nullable;
+import android.annotation.SpecialUsers.CanBeALL;
+import android.annotation.UserIdInt;
 import android.content.pm.Flags;
 import android.content.pm.PackageManager;
 
@@ -60,12 +62,12 @@
         }
     }
 
-    PackageFreezer(String packageName, int userId, String killReason,
+    PackageFreezer(String packageName, @CanBeALL @UserIdInt int userId, String killReason,
             PackageManagerService pm, int exitInfoReason, @Nullable InstallRequest request) {
         this(packageName, userId, killReason, pm, exitInfoReason, request, false);
     }
 
-    PackageFreezer(String packageName, int userId, String killReason,
+    PackageFreezer(String packageName, @CanBeALL @UserIdInt int userId, String killReason,
             PackageManagerService pm, int exitInfoReason, @Nullable InstallRequest request,
             boolean waitAppKilled) {
         mPm = pm;
diff --git a/services/core/java/com/android/server/pm/PackageManagerInternalBase.java b/services/core/java/com/android/server/pm/PackageManagerInternalBase.java
index ed568b8..f5230c5 100644
--- a/services/core/java/com/android/server/pm/PackageManagerInternalBase.java
+++ b/services/core/java/com/android/server/pm/PackageManagerInternalBase.java
@@ -689,7 +689,7 @@
         final int suspendingUserId =
                 crossUserSuspensionEnabledRo() ? UserHandle.USER_SYSTEM : affectedUser;
         mService.unsuspendForSuspendingPackage(
-                snapshot(), PLATFORM_PACKAGE_NAME, suspendingUserId, /* inAllUsers= */ false);
+                snapshot(), PLATFORM_PACKAGE_NAME, suspendingUserId, affectedUser);
     }
 
     @Override
diff --git a/services/core/java/com/android/server/pm/PackageManagerService.java b/services/core/java/com/android/server/pm/PackageManagerService.java
index 2464a29..021da6f 100644
--- a/services/core/java/com/android/server/pm/PackageManagerService.java
+++ b/services/core/java/com/android/server/pm/PackageManagerService.java
@@ -34,6 +34,7 @@
 import static android.crashrecovery.flags.Flags.refactorCrashrecovery;
 import static android.os.Process.INVALID_UID;
 import static android.os.Trace.TRACE_TAG_PACKAGE_MANAGER;
+import static android.os.UserHandle.USER_ALL;
 import static android.os.storage.StorageManager.FLAG_STORAGE_CE;
 import static android.os.storage.StorageManager.FLAG_STORAGE_DE;
 import static android.os.storage.StorageManager.FLAG_STORAGE_EXTERNAL;
@@ -50,6 +51,7 @@
 import android.annotation.IntDef;
 import android.annotation.NonNull;
 import android.annotation.Nullable;
+import android.annotation.SpecialUsers.CanBeALL;
 import android.annotation.StringRes;
 import android.annotation.UserIdInt;
 import android.annotation.WorkerThread;
@@ -1586,13 +1588,13 @@
     }
 
     void scheduleWritePackageRestrictions(UserHandle user) {
-        final int userId = user == null ? UserHandle.USER_ALL : user.getIdentifier();
+        final int userId = user == null ? USER_ALL : user.getIdentifier();
         scheduleWritePackageRestrictions(userId);
     }
 
-    void scheduleWritePackageRestrictions(int userId) {
+    void scheduleWritePackageRestrictions(@CanBeALL @UserIdInt int userId) {
         invalidatePackageInfoCache();
-        if (userId == UserHandle.USER_ALL) {
+        if (userId == USER_ALL) {
             synchronized (mDirtyUsers) {
                 for (int aUserId : mUserManager.getUserIds()) {
                     mDirtyUsers.add(aUserId);
@@ -1805,7 +1807,7 @@
     private void installAllowlistedSystemPackages() {
         if (mUserManager.installWhitelistedSystemPackages(isFirstBoot(), isDeviceUpgrading(),
                 mExistingPackages)) {
-            scheduleWritePackageRestrictions(UserHandle.USER_ALL);
+            scheduleWritePackageRestrictions(USER_ALL);
             scheduleWriteSettings();
         }
     }
@@ -2392,7 +2394,7 @@
                     final PackageSetting ps = packageSettings.valueAt(i);
                     if (Objects.equals(StorageManager.UUID_PRIVATE_INTERNAL, ps.getVolumeUuid())) {
                         // No apps are running this early, so no need to freeze
-                        mAppDataHelper.clearAppDataLIF(ps.getPkg(), UserHandle.USER_ALL,
+                        mAppDataHelper.clearAppDataLIF(ps.getPkg(), USER_ALL,
                                 FLAG_STORAGE_DE | FLAG_STORAGE_CE | FLAG_STORAGE_EXTERNAL
                                         | Installer.FLAG_CLEAR_CODE_CACHE_ONLY
                                         | Installer.FLAG_CLEAR_APP_DATA_KEEP_ART_PROFILES);
@@ -3074,8 +3076,8 @@
     }
 
     @NonNull
-    int[] resolveUserIds(int userId) {
-        return (userId == UserHandle.USER_ALL) ? mUserManager.getUserIds() : new int[] { userId };
+    int[] resolveUserIds(@CanBeALL @UserIdInt int userId) {
+        return (userId == USER_ALL) ? mUserManager.getUserIds() : new int[]{userId};
     }
 
     private void setUpInstantAppInstallerActivityLP(ActivityInfo installerActivity) {
@@ -3108,11 +3110,11 @@
     }
 
     void killApplication(String pkgName, @AppIdInt int appId, String reason, int exitInfoReason) {
-        killApplication(pkgName, appId, UserHandle.USER_ALL, reason, exitInfoReason);
+        killApplication(pkgName, appId, USER_ALL, reason, exitInfoReason);
     }
 
     void killApplication(String pkgName, @AppIdInt int appId,
-            @UserIdInt int userId, String reason, int exitInfoReason) {
+            @CanBeALL @UserIdInt int userId, String reason, int exitInfoReason) {
         // Request the ActivityManager to kill the process(only for existing packages)
         // so that we do not end up in a confused state while the user is still using the older
         // version of the application while the new one gets installed.
@@ -3131,7 +3133,7 @@
     }
 
     void killApplicationSync(String pkgName, @AppIdInt int appId,
-            @UserIdInt int userId, String reason, int exitInfoReason) {
+            @CanBeALL @UserIdInt int userId, String reason, int exitInfoReason) {
         ActivityManagerInternal mAmi = LocalServices.getService(ActivityManagerInternal.class);
         if (Thread.holdsLock(mLock) || mAmi == null) {
             // holds PM's lock, go back killApplication to avoid it run into watchdog reset.
@@ -3228,23 +3230,24 @@
     }
 
     /**
-     * @param inAllUsers Whether to unsuspend packages suspended by the given package in other
-     *                   users. This flag is only used when cross-user suspension is enabled.
+     * @param suspendingUserId The user that has suspended apps using the suspending package.
+     * @param targetUserId The user whose apps should be unsuspended. Pass {@code USER_ALL} to
+     *                     unsuspend for all users.
      */
     void unsuspendForSuspendingPackage(@NonNull Computer computer, String suspendingPackage,
-            @UserIdInt int suspendingUserId, boolean inAllUsers) {
+            @UserIdInt int suspendingUserId, @CanBeALL @UserIdInt int targetUserId) {
         // TODO: This can be replaced by a special parameter to iterate all packages, rather than
         //  this weird pre-collect of all packages.
         final String[] allPackages = computer.getPackageStates().keySet().toArray(new String[0]);
         final Predicate<UserPackage> suspenderPredicate =
                 UserPackage.of(suspendingUserId, suspendingPackage)::equals;
-        if (!crossUserSuspensionEnabledRo() || !inAllUsers) {
+        if (!crossUserSuspensionEnabledRo() || targetUserId != USER_ALL) {
             mSuspendPackageHelper.removeSuspensionsBySuspendingPackage(computer,
-                    allPackages, suspenderPredicate, suspendingUserId);
+                    allPackages, suspenderPredicate, targetUserId);
         } else {
-            for (int targetUserId: mUserManager.getUserIds()) {
+            for (int user : mUserManager.getUserIds()) {
                 mSuspendPackageHelper.removeSuspensionsBySuspendingPackage(
-                        computer, allPackages, suspenderPredicate, targetUserId);
+                        computer, allPackages, suspenderPredicate, user);
             }
         }
     }
@@ -3381,11 +3384,11 @@
                 && !snapshot.isCallerSameApp(packageName, callingUid)) {
             return false;
         }
-        return isPackageDeviceAdmin(packageName, UserHandle.USER_ALL);
+        return isPackageDeviceAdmin(packageName, USER_ALL);
     }
 
     // TODO(b/261957226): centralise this logic in DPM
-    boolean isPackageDeviceAdmin(String packageName, int userId) {
+    boolean isPackageDeviceAdmin(String packageName, @CanBeALL @UserIdInt int userId) {
         final IDevicePolicyManager dpm = getDevicePolicyManager();
         final DevicePolicyManagerInternal dpmi =
                 mInjector.getLocalService(DevicePolicyManagerInternal.class);
@@ -3405,7 +3408,7 @@
                 // Does it contain a device admin for any user?
                 int[] allUsers = mUserManager.getUserIds();
                 int[] targetUsers;
-                if (userId == UserHandle.USER_ALL) {
+                if (userId == USER_ALL) {
                     targetUsers = allUsers;
                 } else {
                     targetUsers = new int[]{userId};
@@ -3555,7 +3558,7 @@
     /** This method takes a specific user id as well as UserHandle.USER_ALL. */
     @GuardedBy("mLock")
     void clearPackagePreferredActivitiesLPw(String packageName,
-            @NonNull SparseBooleanArray outUserChanged, int userId) {
+            @NonNull SparseBooleanArray outUserChanged, @CanBeALL @UserIdInt int userId) {
         mSettings.clearPackagePreferredActivities(packageName, outUserChanged, userId);
     }
 
@@ -4152,7 +4155,7 @@
                 // This app should not generally be allowed to get disabled by the UI, but
                 // if it ever does, we don't want to end up with some of the user's apps
                 // permanently suspended.
-                unsuspendForSuspendingPackage(computer, packageName, userId, true /* inAllUsers */);
+                unsuspendForSuspendingPackage(computer, packageName, userId, USER_ALL);
                 removeAllDistractingPackageRestrictions(computer, userId);
             }
             success = true;
@@ -4243,9 +4246,9 @@
         };
         mContext.getContentResolver().registerContentObserver(android.provider.Settings.Global
                         .getUriFor(Global.ENABLE_EPHEMERAL_FEATURE),
-                false, co, UserHandle.USER_ALL);
+                false, co, USER_ALL);
         mContext.getContentResolver().registerContentObserver(android.provider.Settings.Secure
-                .getUriFor(Secure.INSTANT_APPS_ENABLED), false, co, UserHandle.USER_ALL);
+                .getUriFor(Secure.INSTANT_APPS_ENABLED), false, co, USER_ALL);
         co.onChange(true);
 
         mAppsFilter.onSystemReady(LocalServices.getService(PackageManagerInternal.class));
@@ -4388,14 +4391,14 @@
         }
     }
 
-    public PackageFreezer freezePackage(String packageName, int userId, String killReason,
-            int exitInfoReason, InstallRequest request) {
+    public PackageFreezer freezePackage(String packageName, @CanBeALL @UserIdInt int userId,
+            String killReason, int exitInfoReason, InstallRequest request) {
         return freezePackage(packageName, userId, killReason, exitInfoReason, request,
                 /* waitAppKilled= */ false);
     }
 
-    private PackageFreezer freezePackage(String packageName, int userId, String killReason,
-            int exitInfoReason, InstallRequest request, boolean waitAppKilled) {
+    private PackageFreezer freezePackage(String packageName, @CanBeALL @UserIdInt int userId,
+            String killReason, int exitInfoReason, InstallRequest request, boolean waitAppKilled) {
         return new PackageFreezer(packageName, userId, killReason, this, exitInfoReason, request,
                 waitAppKilled);
     }
@@ -4773,7 +4776,7 @@
             final Computer snapshot = snapshotComputer();
             final AndroidPackage pkg = snapshot.getPackage(packageName);
             try (PackageFreezer ignored =
-                            freezePackage(packageName, UserHandle.USER_ALL,
+                            freezePackage(packageName, USER_ALL,
                                     "clearApplicationProfileData",
                                     ApplicationExitInfo.REASON_OTHER, null /* request */)) {
                 try (PackageManagerTracedLock installLock = mInstallLock.acquireLock()) {
@@ -4819,7 +4822,7 @@
                 public void run() {
                     mHandler.removeCallbacks(this);
                     final boolean succeeded;
-                    try (PackageFreezer freezer = freezePackage(packageName, UserHandle.USER_ALL,
+                    try (PackageFreezer freezer = freezePackage(packageName, USER_ALL,
                             "clearApplicationUserData",
                             ApplicationExitInfo.REASON_USER_REQUESTED, null /* request */,
                             /* waitAppKilled= */ true)) {
@@ -4846,7 +4849,7 @@
                                 == PERMISSION_GRANTED) {
                             final Computer snapshot = snapshotComputer();
                             unsuspendForSuspendingPackage(
-                                    snapshot, packageName, userId, true /* inAllUsers */);
+                                    snapshot, packageName, userId, USER_ALL);
                             removeAllDistractingPackageRestrictions(snapshot, userId);
                             synchronized (mLock) {
                                 flushPackageRestrictionsAsUserInternalLocked(userId);
@@ -6371,13 +6374,13 @@
             if (mComponentResolver.updateMimeGroup(snapshotComputer(), packageName, mimeGroup)) {
                 Binder.withCleanCallingIdentity(() -> {
                     mPreferredActivityHelper.clearPackagePreferredActivities(packageName,
-                            UserHandle.USER_ALL);
+                            USER_ALL);
                     // Send the ACTION_PACKAGE_CHANGED when the mimeGroup has changes
                     final Computer snapShot = snapshotComputer();
                     final ArrayList<String> components = new ArrayList<>(
                             Collections.singletonList(packageName));
                     final int appId = packageState.getAppId();
-                    final int[] userIds = resolveUserIds(UserHandle.USER_ALL);
+                    final int[] userIds = resolveUserIds(USER_ALL);
                     final String reason = "The mimeGroup is changed";
                     for (int i = 0; i < userIds.length; i++) {
                         final PackageUserStateInternal pkgUserState =
diff --git a/services/core/java/com/android/server/pm/PreferredActivityHelper.java b/services/core/java/com/android/server/pm/PreferredActivityHelper.java
index 41d2aeb..fa56596 100644
--- a/services/core/java/com/android/server/pm/PreferredActivityHelper.java
+++ b/services/core/java/com/android/server/pm/PreferredActivityHelper.java
@@ -25,6 +25,7 @@
 import static com.android.server.pm.PackageManagerService.TAG;
 
 import android.annotation.NonNull;
+import android.annotation.SpecialUsers.CanBeALL;
 import android.annotation.UserIdInt;
 import android.content.ComponentName;
 import android.content.Intent;
@@ -115,7 +116,8 @@
     }
 
     /** This method takes a specific user id as well as UserHandle.USER_ALL. */
-    public void clearPackagePreferredActivities(String packageName, int userId) {
+    public void clearPackagePreferredActivities(String packageName,
+            @CanBeALL @UserIdInt int userId) {
         final SparseBooleanArray changedUsers = new SparseBooleanArray();
         synchronized (mPm.mLock) {
             mPm.clearPackagePreferredActivitiesLPw(packageName, changedUsers, userId);
diff --git a/services/core/java/com/android/server/pm/RemovePackageHelper.java b/services/core/java/com/android/server/pm/RemovePackageHelper.java
index f01a74e..22b4ec7 100644
--- a/services/core/java/com/android/server/pm/RemovePackageHelper.java
+++ b/services/core/java/com/android/server/pm/RemovePackageHelper.java
@@ -30,6 +30,8 @@
 
 import android.annotation.NonNull;
 import android.annotation.Nullable;
+import android.annotation.SpecialUsers.CanBeALL;
+import android.annotation.UserIdInt;
 import android.content.pm.PackageManager;
 import android.content.pm.parsing.ApkLiteParseUtils;
 import android.content.pm.parsing.PackageLite;
@@ -256,7 +258,8 @@
      * Make sure this flag is set for partially installed apps. If not it's meaningless to
      * delete a partially installed application.
      */
-    public void clearPackageStateForUserLIF(PackageSetting ps, int userId, int flags) {
+    public void clearPackageStateForUserLIF(PackageSetting ps, @CanBeALL @UserIdInt int userId,
+            int flags) {
         final String packageName = ps.getPackageName();
         // Step 1: always destroy app profiles except when explicitly preserved
         if ((flags & Installer.FLAG_CLEAR_APP_DATA_KEEP_ART_PROFILES) == 0) {
@@ -370,13 +373,13 @@
      * This method deletes the package from internal data structures such as mPackages / mSettings.
      *
      * @param targetUserId indicates the target user of the deletion. It equals to
-     *                     {@link UserHandle.USER_ALL} if the deletion was initiated for all users,
+     *                     {@link UserHandle#USER_ALL} if the deletion was initiated for all users,
      *                     otherwise it equals to the specific user id that the deletion was meant
      *                     for.
      */
     @GuardedBy("mPm.mInstallLock")
-    public void removePackageDataLIF(final PackageSetting deletedPs, int targetUserId,
-            @NonNull int[] allUserHandles,
+    public void removePackageDataLIF(final PackageSetting deletedPs,
+            @CanBeALL @UserIdInt int targetUserId, @NonNull int[] allUserHandles,
             @NonNull PackageRemovedInfo outInfo, int flags, boolean writeSettings) {
         String packageName = deletedPs.getPackageName();
         if (DEBUG_REMOVE) Slog.d(TAG, "removePackageDataLI: " + deletedPs);
@@ -482,7 +485,8 @@
         }
     }
 
-    private static boolean shouldDeletePackageSetting(PackageSetting deletedPs, int userId,
+    private static boolean shouldDeletePackageSetting(PackageSetting deletedPs,
+                                                      @CanBeALL @UserIdInt int userId,
                                                       int[] allUserHandles, int flags) {
         if ((flags & PackageManager.DELETE_KEEP_DATA) != 0) {
             return false;
diff --git a/services/core/java/com/android/server/pm/Settings.java b/services/core/java/com/android/server/pm/Settings.java
index 485a280..92257f1 100644
--- a/services/core/java/com/android/server/pm/Settings.java
+++ b/services/core/java/com/android/server/pm/Settings.java
@@ -35,6 +35,7 @@
 
 import android.annotation.NonNull;
 import android.annotation.Nullable;
+import android.annotation.SpecialUsers.CanBeALL;
 import android.annotation.UserIdInt;
 import android.app.compat.ChangeIdStateCache;
 import android.content.ComponentName;
@@ -6639,7 +6640,7 @@
 
     /** This method takes a specific user id as well as UserHandle.USER_ALL. */
     void clearPackagePreferredActivities(String packageName,
-            @NonNull SparseBooleanArray outUserChanged, int userId) {
+            @NonNull SparseBooleanArray outUserChanged, @CanBeALL @UserIdInt int userId) {
         boolean changed = false;
         ArrayList<PreferredActivity> removed = null;
         for (int i = 0; i < mPreferredActivities.size(); i++) {
diff --git a/services/core/java/com/android/server/pm/permission/PermissionManagerService.java b/services/core/java/com/android/server/pm/permission/PermissionManagerService.java
index 5c5a9c1..ac19ea1 100644
--- a/services/core/java/com/android/server/pm/permission/PermissionManagerService.java
+++ b/services/core/java/com/android/server/pm/permission/PermissionManagerService.java
@@ -37,6 +37,7 @@
 import android.annotation.AppIdInt;
 import android.annotation.NonNull;
 import android.annotation.Nullable;
+import android.annotation.SpecialUsers.CanBeALL;
 import android.annotation.UserIdInt;
 import android.app.ActivityManager;
 import android.app.AppOpsManager;
@@ -765,7 +766,7 @@
         @Override
         public void onPackageUninstalled(@NonNull String packageName, int appId,
                 @NonNull PackageState packageState, @Nullable AndroidPackage pkg,
-                @NonNull List<AndroidPackage> sharedUserPkgs, @UserIdInt int userId) {
+                @NonNull List<AndroidPackage> sharedUserPkgs, @CanBeALL @UserIdInt int userId) {
             if (userId != UserHandle.USER_ALL) {
                 final int[] userIds = getAllUserIds();
                 if (!ArrayUtils.contains(userIds, userId)) {
diff --git a/services/core/java/com/android/server/pm/permission/PermissionManagerServiceImpl.java b/services/core/java/com/android/server/pm/permission/PermissionManagerServiceImpl.java
index e51ec04..33d57d5 100644
--- a/services/core/java/com/android/server/pm/permission/PermissionManagerServiceImpl.java
+++ b/services/core/java/com/android/server/pm/permission/PermissionManagerServiceImpl.java
@@ -65,6 +65,7 @@
 import android.annotation.IntDef;
 import android.annotation.NonNull;
 import android.annotation.Nullable;
+import android.annotation.SpecialUsers.CanBeALL;
 import android.annotation.UserIdInt;
 import android.app.ActivityManager;
 import android.app.AppOpsManager;
@@ -5284,7 +5285,7 @@
     @Override
     public void onPackageUninstalled(@NonNull String packageName, int appId,
             @NonNull PackageState packageState, @Nullable AndroidPackage pkg,
-            @NonNull List<AndroidPackage> sharedUserPkgs, @UserIdInt int userId) {
+            @NonNull List<AndroidPackage> sharedUserPkgs, @CanBeALL @UserIdInt int userId) {
         Objects.requireNonNull(packageState, "packageState");
         Objects.requireNonNull(packageName, "packageName");
         Objects.requireNonNull(sharedUserPkgs, "sharedUserPkgs");
diff --git a/services/core/java/com/android/server/pm/permission/PermissionManagerServiceInterface.java b/services/core/java/com/android/server/pm/permission/PermissionManagerServiceInterface.java
index 3d295f7..f2491d9 100644
--- a/services/core/java/com/android/server/pm/permission/PermissionManagerServiceInterface.java
+++ b/services/core/java/com/android/server/pm/permission/PermissionManagerServiceInterface.java
@@ -19,6 +19,7 @@
 import android.annotation.AppIdInt;
 import android.annotation.NonNull;
 import android.annotation.Nullable;
+import android.annotation.SpecialUsers.CanBeALL;
 import android.annotation.UserIdInt;
 import android.content.Context;
 import android.content.pm.PackageManager;
@@ -662,5 +663,5 @@
      */
     void onPackageUninstalled(@NonNull String packageName, int appId,
             @NonNull PackageState packageState, @Nullable AndroidPackage pkg,
-            @NonNull List<AndroidPackage> sharedUserPkgs, @UserIdInt int userId);
+            @NonNull List<AndroidPackage> sharedUserPkgs, @CanBeALL @UserIdInt int userId);
 }
diff --git a/services/core/java/com/android/server/pm/permission/PermissionManagerServiceInternal.java b/services/core/java/com/android/server/pm/permission/PermissionManagerServiceInternal.java
index a5c1284..ad765c8 100644
--- a/services/core/java/com/android/server/pm/permission/PermissionManagerServiceInternal.java
+++ b/services/core/java/com/android/server/pm/permission/PermissionManagerServiceInternal.java
@@ -18,6 +18,7 @@
 
 import android.annotation.NonNull;
 import android.annotation.Nullable;
+import android.annotation.SpecialUsers.CanBeALL;
 import android.annotation.UserIdInt;
 import android.app.AppOpsManager;
 import android.content.pm.PackageInstaller.SessionParams;
@@ -325,7 +326,7 @@
     //@SystemApi(client = SystemApi.Client.SYSTEM_SERVER)
     void onPackageUninstalled(@NonNull String packageName, int appId,
             @Nullable PackageState packageState, @Nullable AndroidPackage pkg,
-            @NonNull List<AndroidPackage> sharedUserPkgs, @UserIdInt int userId);
+            @NonNull List<AndroidPackage> sharedUserPkgs, @CanBeALL @UserIdInt int userId);
 
     /**
      * The permission-related parameters passed in for package installation.
diff --git a/services/core/java/com/android/server/power/stats/BatteryStatsImpl.java b/services/core/java/com/android/server/power/stats/BatteryStatsImpl.java
index 1d62087..2cf6b7e 100644
--- a/services/core/java/com/android/server/power/stats/BatteryStatsImpl.java
+++ b/services/core/java/com/android/server/power/stats/BatteryStatsImpl.java
@@ -2911,19 +2911,7 @@
                 return false;
             }
 
-            mCounter.getCounts(counts, procState);
-
-            // Return counts only if at least one of the elements is non-zero.
-            for (int i = counts.length - 1; i >= 0; --i) {
-                if (counts[i] != 0) {
-                    return true;
-                }
-            }
-            return false;
-        }
-
-        public void logState(Printer pw, String prefix) {
-            pw.println(prefix + "mCounter=" + mCounter);
+            return mCounter.getCounts(counts, procState);
         }
 
         /**
diff --git a/services/core/java/com/android/server/power/stats/processor/AmbientDisplayPowerStatsProcessor.java b/services/core/java/com/android/server/power/stats/processor/AmbientDisplayPowerStatsProcessor.java
index 5f93bdf..a550f2d 100644
--- a/services/core/java/com/android/server/power/stats/processor/AmbientDisplayPowerStatsProcessor.java
+++ b/services/core/java/com/android/server/power/stats/processor/AmbientDisplayPowerStatsProcessor.java
@@ -68,11 +68,15 @@
         // processor. All that remains to be done is copy the estimates over.
         MultiStateStats.States.forEachTrackedStateCombination(deviceStateConfig,
                 states -> {
-                    screenStats.getDeviceStats(mTmpScreenStats, states);
+                    if (!screenStats.getDeviceStats(mTmpScreenStats, states)) {
+                        return;
+                    }
                     double power =
                             mScreenPowerStatsLayout.getScreenDozePowerEstimate(mTmpScreenStats);
-                    mStatsLayout.setDevicePowerEstimate(mTmpDeviceStats, power);
-                    stats.setDeviceStats(states, mTmpDeviceStats);
+                    if (power != 0) {
+                        mStatsLayout.setDevicePowerEstimate(mTmpDeviceStats, power);
+                        stats.setDeviceStats(states, mTmpDeviceStats);
+                    }
                 });
     }
 }
diff --git a/services/core/java/com/android/server/power/stats/processor/CpuPowerStatsProcessor.java b/services/core/java/com/android/server/power/stats/processor/CpuPowerStatsProcessor.java
index 6ce2cf7..98280be 100644
--- a/services/core/java/com/android/server/power/stats/processor/CpuPowerStatsProcessor.java
+++ b/services/core/java/com/android/server/power/stats/processor/CpuPowerStatsProcessor.java
@@ -119,6 +119,8 @@
         mTmpUidStatsArray = new long[descriptor.uidStatsArrayLength];
 
         mWakelockDescriptor = null;
+
+        initEnergyConsumerToPowerBracketMaps();
     }
 
     /**
@@ -157,9 +159,6 @@
 
         if (mPlan == null) {
             mPlan = new PowerEstimationPlan(stats.getConfig());
-            if (mStatsLayout.getEnergyConsumerCount() != 0) {
-                initEnergyConsumerToPowerBracketMaps();
-            }
         }
 
         Intermediates intermediates = new Intermediates();
@@ -255,6 +254,10 @@
      */
     private void initEnergyConsumerToPowerBracketMaps() {
         int energyConsumerCount = mStatsLayout.getEnergyConsumerCount();
+        if (energyConsumerCount == 0) {
+            return;
+        }
+
         int powerBracketCount = mStatsLayout.getCpuPowerBracketCount();
 
         mEnergyConsumerToCombinedEnergyConsumerMap = new int[energyConsumerCount];
@@ -404,7 +407,10 @@
             deviceStatsIntermediates.timeByBracket = new long[powerBracketCount];
             deviceStatsIntermediates.powerByBracket = new double[powerBracketCount];
 
-            stats.getDeviceStats(mTmpDeviceStatsArray, deviceStateEstimation.stateValues);
+            if (!stats.getDeviceStats(mTmpDeviceStatsArray, deviceStateEstimation.stateValues)) {
+                continue;
+            }
+
             for (int step = 0; step < cpuScalingStepCount; step++) {
                 if (intermediates.timeByScalingStep[step] == 0) {
                     continue;
@@ -429,16 +435,19 @@
             }
 
             if (wakelockStats != null) {
-                wakelockStats.getDeviceStats(mTmpWakelockDeviceStats,
-                        deviceStateEstimation.stateValues);
-                double wakelockPowerEstimate = mWakelockPowerStatsLayout.getDevicePowerEstimate(
-                        mTmpWakelockDeviceStats);
-                power = Math.max(0, power - wakelockPowerEstimate);
+                if (wakelockStats.getDeviceStats(mTmpWakelockDeviceStats,
+                        deviceStateEstimation.stateValues)) {
+                    double wakelockPowerEstimate = mWakelockPowerStatsLayout.getDevicePowerEstimate(
+                            mTmpWakelockDeviceStats);
+                    power = Math.max(0, power - wakelockPowerEstimate);
+                }
             }
 
-            deviceStatsIntermediates.power = power;
-            mStatsLayout.setDevicePowerEstimate(mTmpDeviceStatsArray, power);
-            stats.setDeviceStats(deviceStateEstimation.stateValues, mTmpDeviceStatsArray);
+            if (power != 0) {
+                deviceStatsIntermediates.power = power;
+                mStatsLayout.setDevicePowerEstimate(mTmpDeviceStatsArray, power);
+                stats.setDeviceStats(deviceStateEstimation.stateValues, mTmpDeviceStatsArray);
+            }
         }
     }
 
@@ -538,11 +547,12 @@
             }
 
             if (wakelockStats != null) {
-                wakelockStats.getUidStats(mTmpWakelockUidStats, uid,
-                        proportionalEstimate.stateValues);
-                double wakelockPowerEstimate = mWakelockPowerStatsLayout.getUidPowerEstimate(
-                        mTmpWakelockUidStats);
-                power = Math.max(0, power - wakelockPowerEstimate);
+                if (wakelockStats.getUidStats(mTmpWakelockUidStats, uid,
+                        proportionalEstimate.stateValues)) {
+                    double wakelockPowerEstimate = mWakelockPowerStatsLayout.getUidPowerEstimate(
+                            mTmpWakelockUidStats);
+                    power = Math.max(0, power - wakelockPowerEstimate);
+                }
             }
 
             if (power != 0) {
diff --git a/services/core/java/com/android/server/power/stats/processor/MobileRadioPowerStatsProcessor.java b/services/core/java/com/android/server/power/stats/processor/MobileRadioPowerStatsProcessor.java
index a544daa..58835053 100644
--- a/services/core/java/com/android/server/power/stats/processor/MobileRadioPowerStatsProcessor.java
+++ b/services/core/java/com/android/server/power/stats/processor/MobileRadioPowerStatsProcessor.java
@@ -259,7 +259,9 @@
 
         stats.forEachStateStatsKey(key -> {
             RxTxPowerEstimators estimators = mRxTxPowerEstimators.get(key);
-            stats.getStateStats(mTmpStateStatsArray, key, deviceStates);
+            if (!stats.getStateStats(mTmpStateStatsArray, key, deviceStates)) {
+                return;
+            }
             long rxTime = mStatsLayout.getStateRxTime(mTmpStateStatsArray);
             intermediates.rxPower += estimators.mRxPowerEstimator.calculatePower(rxTime);
             for (int txLevel = 0; txLevel < ModemActivityInfo.getNumTxPowerLevels(); txLevel++) {
diff --git a/services/core/java/com/android/server/power/stats/processor/MultiStateStats.java b/services/core/java/com/android/server/power/stats/processor/MultiStateStats.java
index 6932575..0038943 100644
--- a/services/core/java/com/android/server/power/stats/processor/MultiStateStats.java
+++ b/services/core/java/com/android/server/power/stats/processor/MultiStateStats.java
@@ -16,6 +16,7 @@
 
 package com.android.server.power.stats.processor;
 
+import android.annotation.CheckResult;
 import android.annotation.Nullable;
 import android.util.Slog;
 
@@ -342,10 +343,12 @@
     }
 
     /**
-     * Returns accumulated stats for the specified composite state.
+     * Returns accumulated stats for the specified composite state or false if the results are
+     * all zeros.
      */
-    void getStats(long[] outValues, int[] states) {
-        mCounter.getCounts(outValues, mFactory.getSerialState(states));
+    @CheckResult
+    boolean getStats(long[] outValues, int[] states) {
+        return mCounter.getCounts(outValues, mFactory.getSerialState(states));
     }
 
     /**
@@ -389,15 +392,7 @@
 
     private void writeXmlForStates(TypedXmlSerializer serializer, int[] states, long[] values)
             throws IOException {
-        mCounter.getCounts(values, mFactory.getSerialState(states));
-        boolean nonZero = false;
-        for (long value : values) {
-            if (value != 0) {
-                nonZero = true;
-                break;
-            }
-        }
-        if (!nonZero) {
+        if (!mCounter.getCounts(values, mFactory.getSerialState(states))) {
             return;
         }
 
@@ -470,15 +465,7 @@
         StringBuilder sb = new StringBuilder();
         long[] values = new long[mCounter.getArrayLength()];
         States.forEachTrackedStateCombination(mFactory.mStates, states -> {
-            mCounter.getCounts(values, mFactory.getSerialState(states));
-            boolean nonZero = false;
-            for (long value : values) {
-                if (value != 0) {
-                    nonZero = true;
-                    break;
-                }
-            }
-            if (!nonZero) {
+            if (!mCounter.getCounts(values, mFactory.getSerialState(states))) {
                 return;
             }
 
diff --git a/services/core/java/com/android/server/power/stats/processor/PhoneCallPowerStatsProcessor.java b/services/core/java/com/android/server/power/stats/processor/PhoneCallPowerStatsProcessor.java
index 3957ae0..ad628e4 100644
--- a/services/core/java/com/android/server/power/stats/processor/PhoneCallPowerStatsProcessor.java
+++ b/services/core/java/com/android/server/power/stats/processor/PhoneCallPowerStatsProcessor.java
@@ -69,12 +69,15 @@
         // processor. All that remains to be done is copy the estimates over.
         MultiStateStats.States.forEachTrackedStateCombination(deviceStateConfig,
                 states -> {
-                    mobileRadioStats.getDeviceStats(mTmpMobileRadioDeviceStats, states);
-                    double callPowerEstimate =
-                            mMobileRadioStatsLayout.getDeviceCallPowerEstimate(
-                                    mTmpMobileRadioDeviceStats);
-                    mStatsLayout.setDevicePowerEstimate(mTmpDeviceStats, callPowerEstimate);
-                    stats.setDeviceStats(states, mTmpDeviceStats);
+                    if (!mobileRadioStats.getDeviceStats(mTmpMobileRadioDeviceStats, states)) {
+                        return;
+                    }
+                    double callPowerEstimate = mMobileRadioStatsLayout.getDeviceCallPowerEstimate(
+                            mTmpMobileRadioDeviceStats);
+                    if (callPowerEstimate != 0) {
+                        mStatsLayout.setDevicePowerEstimate(mTmpDeviceStats, callPowerEstimate);
+                        stats.setDeviceStats(states, mTmpDeviceStats);
+                    }
                 });
     }
 }
diff --git a/services/core/java/com/android/server/power/stats/processor/PowerComponentAggregatedPowerStats.java b/services/core/java/com/android/server/power/stats/processor/PowerComponentAggregatedPowerStats.java
index f9b9da9..d285a59 100644
--- a/services/core/java/com/android/server/power/stats/processor/PowerComponentAggregatedPowerStats.java
+++ b/services/core/java/com/android/server/power/stats/processor/PowerComponentAggregatedPowerStats.java
@@ -16,6 +16,7 @@
 
 package com.android.server.power.stats.processor;
 
+import android.annotation.CheckResult;
 import android.annotation.NonNull;
 import android.annotation.Nullable;
 import android.os.BatteryStats;
@@ -312,6 +313,11 @@
         return uids;
     }
 
+    /**
+     * Populates outValues with the stats for the specified states. If the stats are all 0,
+     * returns false, leaving outValues unchanged.
+     */
+    @CheckResult
     boolean getDeviceStats(long[] outValues, int[] deviceStates) {
         if (deviceStates.length != mDeviceStateConfig.length) {
             throw new IllegalArgumentException(
@@ -319,12 +325,16 @@
                     + " expected: " + mDeviceStateConfig.length);
         }
         if (mDeviceStats != null) {
-            mDeviceStats.getStats(outValues, deviceStates);
-            return true;
+            return mDeviceStats.getStats(outValues, deviceStates);
         }
         return false;
     }
 
+    /**
+     * Populates outValues with the stats for the specified key and device states. If the stats
+     * are all 0, returns false, leaving outValues unchanged.
+     */
+    @CheckResult
     boolean getStateStats(long[] outValues, int key, int[] deviceStates) {
         if (deviceStates.length != mDeviceStateConfig.length) {
             throw new IllegalArgumentException(
@@ -333,8 +343,7 @@
         }
         MultiStateStats stateStats = mStateStats.get(key);
         if (stateStats != null) {
-            stateStats.getStats(outValues, deviceStates);
-            return true;
+            return stateStats.getStats(outValues, deviceStates);
         }
         return false;
     }
@@ -345,6 +354,11 @@
         }
     }
 
+    /**
+     * Populates outValues with the stats for the specified UID and UID states. If the stats are
+     *  all 0, returns false, leaving outValues unchanged.
+     */
+    @CheckResult
     boolean getUidStats(long[] outValues, int uid, int[] uidStates) {
         if (uidStates.length != mUidStateConfig.length) {
             throw new IllegalArgumentException(
@@ -353,8 +367,7 @@
         }
         UidStats uidStats = mUidStats.get(uid);
         if (uidStats != null && uidStats.stats != null) {
-            uidStats.stats.getStats(outValues, uidStates);
-            return true;
+            return uidStats.stats.getStats(outValues, uidStates);
         }
         return false;
     }
@@ -578,15 +591,7 @@
         long[] values = new long[stats.getDimensionCount()];
         MultiStateStats.States[] stateInfo = stats.getStates();
         MultiStateStats.States.forEachTrackedStateCombination(stateInfo, states -> {
-            stats.getStats(values, states);
-            boolean nonZero = false;
-            for (long value : values) {
-                if (value != 0) {
-                    nonZero = true;
-                    break;
-                }
-            }
-            if (!nonZero) {
+            if (!stats.getStats(values, states)) {
                 return;
             }
 
diff --git a/services/core/java/com/android/server/power/stats/processor/SensorPowerStatsProcessor.java b/services/core/java/com/android/server/power/stats/processor/SensorPowerStatsProcessor.java
index ba728d3..284e6a9 100644
--- a/services/core/java/com/android/server/power/stats/processor/SensorPowerStatsProcessor.java
+++ b/services/core/java/com/android/server/power/stats/processor/SensorPowerStatsProcessor.java
@@ -298,12 +298,16 @@
                 continue;
             }
 
-            if (!stats.getDeviceStats(mTmpDeviceStatsArray, estimation.stateValues)) {
+            double power = ((Intermediates) estimation.intermediates).power;
+            if (power == 0) {
                 continue;
             }
 
-            mStatsLayout.setDevicePowerEstimate(mTmpDeviceStatsArray,
-                    ((Intermediates) estimation.intermediates).power);
+            if (!stats.getDeviceStats(mTmpDeviceStatsArray, estimation.stateValues)) {
+                Arrays.fill(mTmpDeviceStatsArray, 0);
+            }
+
+            mStatsLayout.setDevicePowerEstimate(mTmpDeviceStatsArray, power);
             stats.setDeviceStats(estimation.stateValues, mTmpDeviceStatsArray);
         }
     }
diff --git a/services/core/java/com/android/server/storage/ImmutableVolumeInfo.java b/services/core/java/com/android/server/storage/ImmutableVolumeInfo.java
index 9d60a57..0f8212e 100644
--- a/services/core/java/com/android/server/storage/ImmutableVolumeInfo.java
+++ b/services/core/java/com/android/server/storage/ImmutableVolumeInfo.java
@@ -136,4 +136,9 @@
     public boolean isVisibleForWrite(int userId) {
         return mVolumeInfo.isVisibleForWrite(userId);
     }
+
+    @Override
+    public String toString() {
+        return mVolumeInfo.toString();
+    }
 }
diff --git a/services/core/java/com/android/server/storage/StorageSessionController.java b/services/core/java/com/android/server/storage/StorageSessionController.java
index 342b864..281aeb6 100644
--- a/services/core/java/com/android/server/storage/StorageSessionController.java
+++ b/services/core/java/com/android/server/storage/StorageSessionController.java
@@ -156,14 +156,15 @@
         StorageUserConnection connection = null;
         synchronized (mLock) {
             connection = mConnections.get(connectionUserId);
-            if (connection != null) {
-                Slog.i(TAG, "Notifying volume state changed for session with id: " + sessionId);
-                connection.notifyVolumeStateChanged(sessionId,
-                        vol.buildStorageVolume(mContext, vol.getMountUserId(), false));
-            } else {
-                Slog.w(TAG, "No available storage user connection for userId : "
-                        + connectionUserId);
-            }
+        }
+
+        if (connection != null) {
+            Slog.i(TAG, "Notifying volume state changed for session with id: " + sessionId);
+            connection.notifyVolumeStateChanged(sessionId,
+                    vol.buildStorageVolume(mContext, vol.getMountUserId(), false));
+        } else {
+            Slog.w(TAG, "No available storage user connection for userId : "
+                    + connectionUserId);
         }
     }
 
@@ -225,16 +226,18 @@
         String sessionId = vol.getId();
         int userId = getConnectionUserIdForVolume(vol);
 
+        StorageUserConnection connection = null;
         synchronized (mLock) {
-            StorageUserConnection connection = mConnections.get(userId);
-            if (connection != null) {
-                Slog.i(TAG, "Removed session for vol with id: " + sessionId);
-                connection.removeSession(sessionId);
-                return connection;
-            } else {
-                Slog.w(TAG, "Session already removed for vol with id: " + sessionId);
-                return null;
-            }
+            connection = mConnections.get(userId);
+        }
+
+        if (connection != null) {
+            Slog.i(TAG, "Removed session for vol with id: " + sessionId);
+            connection.removeSession(sessionId);
+            return connection;
+        } else {
+            Slog.w(TAG, "Session already removed for vol with id: " + sessionId);
+            return null;
         }
     }
 
diff --git a/services/core/java/com/android/server/storage/WatchedVolumeInfo.java b/services/core/java/com/android/server/storage/WatchedVolumeInfo.java
index 4124cfb..94e52cd 100644
--- a/services/core/java/com/android/server/storage/WatchedVolumeInfo.java
+++ b/services/core/java/com/android/server/storage/WatchedVolumeInfo.java
@@ -203,4 +203,9 @@
     public boolean isVisibleForWrite(int userId) {
         return mVolumeInfo.isVisibleForWrite(userId);
     }
+
+    @Override
+    public String toString() {
+        return mVolumeInfo.toString();
+    }
 }
\ No newline at end of file
diff --git a/services/core/java/com/android/server/vibrator/AbstractVibratorStep.java b/services/core/java/com/android/server/vibrator/AbstractVibratorStep.java
index 07d9ad1..da6478b 100644
--- a/services/core/java/com/android/server/vibrator/AbstractVibratorStep.java
+++ b/services/core/java/com/android/server/vibrator/AbstractVibratorStep.java
@@ -124,6 +124,8 @@
             Slog.d(VibrationThread.TAG,
                     "Turning off vibrator " + getVibratorId());
         }
+        // Make sure we ignore any pending callback from old vibration commands.
+        conductor.nextVibratorCallbackStepId(getVibratorId());
         controller.off();
         getVibration().stats.reportVibratorOff();
         mPendingVibratorOffDeadline = 0;
diff --git a/services/core/java/com/android/server/vibrator/ComposePrimitivesVibratorStep.java b/services/core/java/com/android/server/vibrator/ComposePrimitivesVibratorStep.java
index e495af5..b3eead1 100644
--- a/services/core/java/com/android/server/vibrator/ComposePrimitivesVibratorStep.java
+++ b/services/core/java/com/android/server/vibrator/ComposePrimitivesVibratorStep.java
@@ -73,7 +73,8 @@
 
             PrimitiveSegment[] primitivesArray =
                     primitives.toArray(new PrimitiveSegment[primitives.size()]);
-            long vibratorOnResult = controller.on(primitivesArray, getVibration().id);
+            int stepId = conductor.nextVibratorCallbackStepId(getVibratorId());
+            long vibratorOnResult = controller.on(primitivesArray, getVibration().id, stepId);
             handleVibratorOnResult(vibratorOnResult);
             getVibration().stats.reportComposePrimitives(vibratorOnResult, primitivesArray);
 
diff --git a/services/core/java/com/android/server/vibrator/ComposePwleV2VibratorStep.java b/services/core/java/com/android/server/vibrator/ComposePwleV2VibratorStep.java
index bb8e6ee..7b41457 100644
--- a/services/core/java/com/android/server/vibrator/ComposePwleV2VibratorStep.java
+++ b/services/core/java/com/android/server/vibrator/ComposePwleV2VibratorStep.java
@@ -72,7 +72,8 @@
                         + controller.getVibratorInfo().getId());
             }
             PwlePoint[] pwlesArray = pwles.toArray(new PwlePoint[pwles.size()]);
-            long vibratorOnResult = controller.on(pwlesArray, getVibration().id);
+            int stepId = conductor.nextVibratorCallbackStepId(getVibratorId());
+            long vibratorOnResult = controller.on(pwlesArray, getVibration().id, stepId);
             handleVibratorOnResult(vibratorOnResult);
             getVibration().stats.reportComposePwle(vibratorOnResult, pwlesArray);
 
diff --git a/services/core/java/com/android/server/vibrator/ComposePwleVibratorStep.java b/services/core/java/com/android/server/vibrator/ComposePwleVibratorStep.java
index e8952fa..d003251 100644
--- a/services/core/java/com/android/server/vibrator/ComposePwleVibratorStep.java
+++ b/services/core/java/com/android/server/vibrator/ComposePwleVibratorStep.java
@@ -71,7 +71,8 @@
                         + controller.getVibratorInfo().getId());
             }
             RampSegment[] pwlesArray = pwles.toArray(new RampSegment[pwles.size()]);
-            long vibratorOnResult = controller.on(pwlesArray, getVibration().id);
+            int stepId = conductor.nextVibratorCallbackStepId(getVibratorId());
+            long vibratorOnResult = controller.on(pwlesArray, getVibration().id, stepId);
             handleVibratorOnResult(vibratorOnResult);
             getVibration().stats.reportComposePwle(vibratorOnResult, pwlesArray);
 
diff --git a/services/core/java/com/android/server/vibrator/ExternalVibrationSession.java b/services/core/java/com/android/server/vibrator/ExternalVibrationSession.java
index a92ac67..b2cc1f6 100644
--- a/services/core/java/com/android/server/vibrator/ExternalVibrationSession.java
+++ b/services/core/java/com/android/server/vibrator/ExternalVibrationSession.java
@@ -148,7 +148,7 @@
     }
 
     @Override
-    public void notifyVibratorCallback(int vibratorId, long vibrationId) {
+    public void notifyVibratorCallback(int vibratorId, long vibrationId, long stepId) {
         // ignored, external control does not expect callbacks from the vibrator
     }
 
diff --git a/services/core/java/com/android/server/vibrator/PerformPrebakedVibratorStep.java b/services/core/java/com/android/server/vibrator/PerformPrebakedVibratorStep.java
index 4b23216..88bb181 100644
--- a/services/core/java/com/android/server/vibrator/PerformPrebakedVibratorStep.java
+++ b/services/core/java/com/android/server/vibrator/PerformPrebakedVibratorStep.java
@@ -64,7 +64,8 @@
             }
 
             VibrationEffect fallback = getVibration().getFallback(prebaked.getEffectId());
-            long vibratorOnResult = controller.on(prebaked, getVibration().id);
+            int stepId = conductor.nextVibratorCallbackStepId(getVibratorId());
+            long vibratorOnResult = controller.on(prebaked, getVibration().id, stepId);
             handleVibratorOnResult(vibratorOnResult);
             getVibration().stats.reportPerformEffect(vibratorOnResult, prebaked);
 
diff --git a/services/core/java/com/android/server/vibrator/PerformVendorEffectVibratorStep.java b/services/core/java/com/android/server/vibrator/PerformVendorEffectVibratorStep.java
index 407f3d9..d4bcc36 100644
--- a/services/core/java/com/android/server/vibrator/PerformVendorEffectVibratorStep.java
+++ b/services/core/java/com/android/server/vibrator/PerformVendorEffectVibratorStep.java
@@ -49,7 +49,8 @@
     public List<Step> play() {
         Trace.traceBegin(Trace.TRACE_TAG_VIBRATOR, "PerformVendorEffectVibratorStep");
         try {
-            long vibratorOnResult = controller.on(effect, getVibration().id);
+            int stepId = conductor.nextVibratorCallbackStepId(getVibratorId());
+            long vibratorOnResult = controller.on(effect, getVibration().id, stepId);
             vibratorOnResult = Math.min(vibratorOnResult, VENDOR_EFFECT_MAX_DURATION_MS);
             handleVibratorOnResult(vibratorOnResult);
             getVibration().stats.reportPerformVendorEffect(vibratorOnResult);
diff --git a/services/core/java/com/android/server/vibrator/SetAmplitudeVibratorStep.java b/services/core/java/com/android/server/vibrator/SetAmplitudeVibratorStep.java
index 8478e77..26b9595 100644
--- a/services/core/java/com/android/server/vibrator/SetAmplitudeVibratorStep.java
+++ b/services/core/java/com/android/server/vibrator/SetAmplitudeVibratorStep.java
@@ -20,6 +20,7 @@
 import android.os.SystemClock;
 import android.os.Trace;
 import android.os.VibrationEffect;
+import android.os.vibrator.Flags;
 import android.os.vibrator.StepSegment;
 import android.os.vibrator.VibrationEffectSegment;
 import android.util.Slog;
@@ -49,6 +50,10 @@
 
     @Override
     public boolean acceptVibratorCompleteCallback(int vibratorId) {
+        if (Flags.fixVibrationThreadCallbackHandling()) {
+            // TODO: remove this method once flag removed.
+            return super.acceptVibratorCompleteCallback(vibratorId);
+        }
         // Ensure the super method is called and will reset the off timeout and boolean flag.
         // This is true if the vibrator was ON and this callback has the same vibratorId.
         if (!super.acceptVibratorCompleteCallback(vibratorId)) {
@@ -161,7 +166,8 @@
                     "Turning on vibrator " + controller.getVibratorInfo().getId() + " for "
                             + duration + "ms");
         }
-        long vibratorOnResult = controller.on(duration, getVibration().id);
+        int stepId = conductor.nextVibratorCallbackStepId(getVibratorId());
+        long vibratorOnResult = controller.on(duration, getVibration().id, stepId);
         handleVibratorOnResult(vibratorOnResult);
         getVibration().stats.reportVibratorOn(vibratorOnResult);
         return vibratorOnResult;
diff --git a/services/core/java/com/android/server/vibrator/SingleVibrationSession.java b/services/core/java/com/android/server/vibrator/SingleVibrationSession.java
index 628221b..309eb8c 100644
--- a/services/core/java/com/android/server/vibrator/SingleVibrationSession.java
+++ b/services/core/java/com/android/server/vibrator/SingleVibrationSession.java
@@ -137,13 +137,13 @@
     }
 
     @Override
-    public void notifyVibratorCallback(int vibratorId, long vibrationId) {
+    public void notifyVibratorCallback(int vibratorId, long vibrationId, long stepId) {
         if (vibrationId != mVibration.id) {
             return;
         }
         synchronized (mLock) {
             if (mConductor != null) {
-                mConductor.notifyVibratorComplete(vibratorId);
+                mConductor.notifyVibratorComplete(vibratorId, stepId);
             }
         }
     }
diff --git a/services/core/java/com/android/server/vibrator/VendorVibrationSession.java b/services/core/java/com/android/server/vibrator/VendorVibrationSession.java
index 64b52b1..bda3d44 100644
--- a/services/core/java/com/android/server/vibrator/VendorVibrationSession.java
+++ b/services/core/java/com/android/server/vibrator/VendorVibrationSession.java
@@ -218,8 +218,8 @@
     }
 
     @Override
-    public void notifyVibratorCallback(int vibratorId, long vibrationId) {
-        Slog.d(TAG, "Vibration callback received for vibration " + vibrationId
+    public void notifyVibratorCallback(int vibratorId, long vibrationId, long stepId) {
+        Slog.d(TAG, "Vibration callback received for vibration " + vibrationId + " step " + stepId
                 + " on vibrator " + vibratorId + ", ignoring...");
     }
 
diff --git a/services/core/java/com/android/server/vibrator/VibrationSession.java b/services/core/java/com/android/server/vibrator/VibrationSession.java
index ae95a70..23715e39 100644
--- a/services/core/java/com/android/server/vibrator/VibrationSession.java
+++ b/services/core/java/com/android/server/vibrator/VibrationSession.java
@@ -106,7 +106,7 @@
      * complete (e.g. on(), perform(), compose()). This does not mean the vibration is complete,
      * since its playback might have one or more interactions with the vibrator hardware.
      */
-    void notifyVibratorCallback(int vibratorId, long vibrationId);
+    void notifyVibratorCallback(int vibratorId, long vibrationId, long stepId);
 
     /**
      * Notify all synced vibrators have completed the last synchronized command during the playback
diff --git a/services/core/java/com/android/server/vibrator/VibrationStepConductor.java b/services/core/java/com/android/server/vibrator/VibrationStepConductor.java
index 1e20deb..36e1322 100644
--- a/services/core/java/com/android/server/vibrator/VibrationStepConductor.java
+++ b/services/core/java/com/android/server/vibrator/VibrationStepConductor.java
@@ -30,6 +30,7 @@
 import android.util.IntArray;
 import android.util.Slog;
 import android.util.SparseArray;
+import android.util.SparseIntArray;
 
 import com.android.internal.annotations.GuardedBy;
 import com.android.server.vibrator.VibrationSession.Status;
@@ -93,6 +94,8 @@
     private final Object mLock = new Object();
     @GuardedBy("mLock")
     private final IntArray mSignalVibratorsComplete;
+    @GuardedBy("mLock")
+    private final SparseIntArray mSignalVibratorStepIds;
     @Nullable
     @GuardedBy("mLock")
     private Vibration.EndInfo mSignalCancel = null;
@@ -121,6 +124,8 @@
         this.vibratorManagerHooks = vibratorManagerHooks;
         this.mSignalVibratorsComplete =
                 new IntArray(mDeviceAdapter.getAvailableVibratorIds().length);
+        this.mSignalVibratorStepIds =
+                new SparseIntArray(mDeviceAdapter.getAvailableVibratorIds().length);
     }
 
     @Nullable
@@ -418,7 +423,7 @@
      * <p>This is a lightweight method intended to be called directly via native callbacks.
      * The state update is recorded for processing on the main execution thread (VibrationThread).
      */
-    public void notifyVibratorComplete(int vibratorId) {
+    public void notifyVibratorComplete(int vibratorId, long stepId) {
         // HAL callbacks may be triggered directly within HAL calls, so these notifications
         // could be on the VibrationThread as it calls the HAL, or some other executor later.
         // Therefore no thread assertion is made here.
@@ -428,6 +433,14 @@
         }
 
         synchronized (mLock) {
+            if (Flags.fixVibrationThreadCallbackHandling()
+                    && mSignalVibratorStepIds.get(vibratorId) != stepId) {
+                if (DEBUG) {
+                    Slog.d(TAG, "Vibrator " + vibratorId + " callback for step=" + stepId
+                            + " ignored, current step=" + mSignalVibratorStepIds.get(vibratorId));
+                }
+                return;
+            }
             mSignalVibratorsComplete.add(vibratorId);
             mLock.notify();
         }
@@ -645,6 +658,26 @@
         }
     }
 
+    /**
+     * Updates and returns the next step id value to be used in vibrator commands.
+     *
+     * <p>This new step id will be kept by this conductor to filter out old callbacks that might be
+     * triggered too late by the HAL, preventing them from affecting the ongoing vibration playback.
+     */
+    public int nextVibratorCallbackStepId(int vibratorId) {
+        if (!Flags.fixVibrationThreadCallbackHandling()) {
+            return 0;
+        }
+        if (Build.IS_DEBUGGABLE) {
+            expectIsVibrationThread(true);
+        }
+        synchronized (mLock) {
+            int stepId = mSignalVibratorStepIds.get(vibratorId) + 1;
+            mSignalVibratorStepIds.put(vibratorId, stepId);
+            return stepId;
+        }
+    }
+
     private static CombinedVibration.Sequential toSequential(CombinedVibration effect) {
         if (effect instanceof CombinedVibration.Sequential) {
             return (CombinedVibration.Sequential) effect;
diff --git a/services/core/java/com/android/server/vibrator/VibratorController.java b/services/core/java/com/android/server/vibrator/VibratorController.java
index acb31ce..ab13b0e 100644
--- a/services/core/java/com/android/server/vibrator/VibratorController.java
+++ b/services/core/java/com/android/server/vibrator/VibratorController.java
@@ -72,7 +72,7 @@
     public interface OnVibrationCompleteListener {
 
         /** Callback triggered when an active vibration command is complete. */
-        void onComplete(int vibratorId, long vibrationId);
+        void onComplete(int vibratorId, long vibrationId, long stepId);
     }
 
     /** Representation of the vibrator state based on the interactions through this controller. */
@@ -285,11 +285,11 @@
      * @return The positive duration of the vibration started, if successful, zero if the vibrator
      * do not support the input or a negative number if the operation failed.
      */
-    public long on(long milliseconds, long vibrationId) {
+    public long on(long milliseconds, long vibrationId, long stepId) {
         Trace.traceBegin(TRACE_TAG_VIBRATOR, "VibratorController#on");
         try {
             synchronized (mLock) {
-                long duration = mNativeWrapper.on(milliseconds, vibrationId);
+                long duration = mNativeWrapper.on(milliseconds, vibrationId, stepId);
                 if (duration > 0) {
                     mCurrentAmplitude = -1;
                     updateStateAndNotifyListenersLocked(VibratorState.VIBRATING);
@@ -310,7 +310,7 @@
      * @return The positive duration of the vibration started, if successful, zero if the vibrator
      * do not support the input or a negative number if the operation failed.
      */
-    public long on(VibrationEffect.VendorEffect vendorEffect, long vibrationId) {
+    public long on(VibrationEffect.VendorEffect vendorEffect, long vibrationId, long stepId) {
         Trace.traceBegin(TRACE_TAG_VIBRATOR, "VibratorController#on (vendor)");
         synchronized (mLock) {
             Parcel vendorData = Parcel.obtain();
@@ -319,7 +319,7 @@
                 vendorData.setDataPosition(0);
                 long duration = mNativeWrapper.performVendorEffect(vendorData,
                         vendorEffect.getEffectStrength(), vendorEffect.getScale(),
-                        vendorEffect.getAdaptiveScale(), vibrationId);
+                        vendorEffect.getAdaptiveScale(), vibrationId, stepId);
                 if (duration > 0) {
                     mCurrentAmplitude = -1;
                     updateStateAndNotifyListenersLocked(VibratorState.VIBRATING);
@@ -341,12 +341,12 @@
      * @return The positive duration of the vibration started, if successful, zero if the vibrator
      * do not support the input or a negative number if the operation failed.
      */
-    public long on(PrebakedSegment prebaked, long vibrationId) {
+    public long on(PrebakedSegment prebaked, long vibrationId, long stepId) {
         Trace.traceBegin(TRACE_TAG_VIBRATOR, "VibratorController#on (Prebaked)");
         try {
             synchronized (mLock) {
                 long duration = mNativeWrapper.perform(prebaked.getEffectId(),
-                        prebaked.getEffectStrength(), vibrationId);
+                        prebaked.getEffectStrength(), vibrationId, stepId);
                 if (duration > 0) {
                     mCurrentAmplitude = -1;
                     updateStateAndNotifyListenersLocked(VibratorState.VIBRATING);
@@ -367,14 +367,14 @@
      * @return The positive duration of the vibration started, if successful, zero if the vibrator
      * do not support the input or a negative number if the operation failed.
      */
-    public long on(PrimitiveSegment[] primitives, long vibrationId) {
+    public long on(PrimitiveSegment[] primitives, long vibrationId, long stepId) {
         Trace.traceBegin(TRACE_TAG_VIBRATOR, "VibratorController#on (Primitive)");
         try {
             if (!mVibratorInfo.hasCapability(IVibrator.CAP_COMPOSE_EFFECTS)) {
                 return 0;
             }
             synchronized (mLock) {
-                long duration = mNativeWrapper.compose(primitives, vibrationId);
+                long duration = mNativeWrapper.compose(primitives, vibrationId, stepId);
                 if (duration > 0) {
                     mCurrentAmplitude = -1;
                     updateStateAndNotifyListenersLocked(VibratorState.VIBRATING);
@@ -394,7 +394,7 @@
      *
      * @return The duration of the effect playing, or 0 if unsupported.
      */
-    public long on(RampSegment[] primitives, long vibrationId) {
+    public long on(RampSegment[] primitives, long vibrationId, long stepId) {
         Trace.traceBegin(TRACE_TAG_VIBRATOR, "VibratorController#on (PWLE)");
         try {
             if (!mVibratorInfo.hasCapability(IVibrator.CAP_COMPOSE_PWLE_EFFECTS)) {
@@ -402,7 +402,8 @@
             }
             synchronized (mLock) {
                 int braking = mVibratorInfo.getDefaultBraking();
-                long duration = mNativeWrapper.composePwle(primitives, braking, vibrationId);
+                long duration = mNativeWrapper.composePwle(
+                        primitives, braking, vibrationId, stepId);
                 if (duration > 0) {
                     mCurrentAmplitude = -1;
                     updateStateAndNotifyListenersLocked(VibratorState.VIBRATING);
@@ -422,14 +423,14 @@
      *
      * @return The duration of the effect playing, or 0 if unsupported.
      */
-    public long on(PwlePoint[] pwlePoints, long vibrationId) {
+    public long on(PwlePoint[] pwlePoints, long vibrationId, long stepId) {
         Trace.traceBegin(TRACE_TAG_VIBRATOR, "VibratorController#on (PWLE v2)");
         try {
             if (!mVibratorInfo.hasCapability(IVibrator.CAP_COMPOSE_PWLE_EFFECTS_V2)) {
                 return 0;
             }
             synchronized (mLock) {
-                long duration = mNativeWrapper.composePwleV2(pwlePoints, vibrationId);
+                long duration = mNativeWrapper.composePwleV2(pwlePoints, vibrationId, stepId);
                 if (duration > 0) {
                     mCurrentAmplitude = -1;
                     updateStateAndNotifyListenersLocked(VibratorState.VIBRATING);
@@ -544,26 +545,27 @@
 
         private static native boolean isAvailable(long nativePtr);
 
-        private static native long on(long nativePtr, long milliseconds, long vibrationId);
+        private static native long on(long nativePtr, long milliseconds, long vibrationId,
+                long stepId);
 
         private static native void off(long nativePtr);
 
         private static native void setAmplitude(long nativePtr, float amplitude);
 
         private static native long performEffect(long nativePtr, long effect, long strength,
-                long vibrationId);
+                long vibrationId, long stepId);
 
         private static native long performVendorEffect(long nativePtr, Parcel vendorData,
-                long strength, float scale, float adaptiveScale, long vibrationId);
+                long strength, float scale, float adaptiveScale, long vibrationId, long stepId);
 
         private static native long performComposedEffect(long nativePtr, PrimitiveSegment[] effect,
-                long vibrationId);
+                long vibrationId, long stepId);
 
         private static native long performPwleEffect(long nativePtr, RampSegment[] effect,
-                int braking, long vibrationId);
+                int braking, long vibrationId, long stepId);
 
         private static native long performPwleV2Effect(long nativePtr, PwlePoint[] effect,
-                long vibrationId);
+                long vibrationId, long stepId);
 
         private static native void setExternalControl(long nativePtr, boolean enabled);
 
@@ -595,8 +597,8 @@
         }
 
         /** Turns vibrator on for given time. */
-        public long on(long milliseconds, long vibrationId) {
-            return on(mNativePtr, milliseconds, vibrationId);
+        public long on(long milliseconds, long vibrationId, long stepId) {
+            return on(mNativePtr, milliseconds, vibrationId, stepId);
         }
 
         /** Turns vibrator off. */
@@ -610,30 +612,31 @@
         }
 
         /** Turns vibrator on to perform one of the supported effects. */
-        public long perform(long effect, long strength, long vibrationId) {
-            return performEffect(mNativePtr, effect, strength, vibrationId);
+        public long perform(long effect, long strength, long vibrationId, long stepId) {
+            return performEffect(mNativePtr, effect, strength, vibrationId, stepId);
         }
 
         /** Turns vibrator on to perform a vendor-specific effect. */
         public long performVendorEffect(Parcel vendorData, long strength, float scale,
-                float adaptiveScale, long vibrationId) {
+                float adaptiveScale, long vibrationId, long stepId) {
             return performVendorEffect(mNativePtr, vendorData, strength, scale, adaptiveScale,
-                    vibrationId);
+                    vibrationId, stepId);
         }
 
         /** Turns vibrator on to perform effect composed of give primitives effect. */
-        public long compose(PrimitiveSegment[] primitives, long vibrationId) {
-            return performComposedEffect(mNativePtr, primitives, vibrationId);
+        public long compose(PrimitiveSegment[] primitives, long vibrationId, long stepId) {
+            return performComposedEffect(mNativePtr, primitives, vibrationId, stepId);
         }
 
         /** Turns vibrator on to perform PWLE effect composed of given primitives. */
-        public long composePwle(RampSegment[] primitives, int braking, long vibrationId) {
-            return performPwleEffect(mNativePtr, primitives, braking, vibrationId);
+        public long composePwle(RampSegment[] primitives, int braking, long vibrationId,
+                long stepId) {
+            return performPwleEffect(mNativePtr, primitives, braking, vibrationId, stepId);
         }
 
         /** Turns vibrator on to perform PWLE effect composed of given points. */
-        public long composePwleV2(PwlePoint[] pwlePoints, long vibrationId) {
-            return performPwleV2Effect(mNativePtr, pwlePoints, vibrationId);
+        public long composePwleV2(PwlePoint[] pwlePoints, long vibrationId, long stepId) {
+            return performPwleV2Effect(mNativePtr, pwlePoints, vibrationId, stepId);
         }
 
         /** Enabled the device vibrator to be controlled by another service. */
diff --git a/services/core/java/com/android/server/vibrator/VibratorManagerService.java b/services/core/java/com/android/server/vibrator/VibratorManagerService.java
index 75b1b20..3f5fc33 100644
--- a/services/core/java/com/android/server/vibrator/VibratorManagerService.java
+++ b/services/core/java/com/android/server/vibrator/VibratorManagerService.java
@@ -1293,14 +1293,14 @@
         }
     }
 
-    private void onVibrationComplete(int vibratorId, long vibrationId) {
+    private void onVibrationComplete(int vibratorId, long vibrationId, long stepId) {
         synchronized (mLock) {
             if (mCurrentSession != null) {
                 if (DEBUG) {
-                    Slog.d(TAG, "Vibration " + vibrationId + " on vibrator " + vibratorId
-                            + " complete, notifying thread");
+                    Slog.d(TAG, "Vibration " + vibrationId + " step " + stepId
+                            + " on vibrator " + vibratorId + " complete, notifying thread");
                 }
-                mCurrentSession.notifyVibratorCallback(vibratorId, vibrationId);
+                mCurrentSession.notifyVibratorCallback(vibratorId, vibrationId, stepId);
             }
         }
     }
@@ -2100,10 +2100,10 @@
         }
 
         @Override
-        public void onComplete(int vibratorId, long vibrationId) {
+        public void onComplete(int vibratorId, long vibrationId, long stepId) {
             VibratorManagerService service = mServiceRef.get();
             if (service != null) {
-                service.onVibrationComplete(vibratorId, vibrationId);
+                service.onVibrationComplete(vibratorId, vibrationId, stepId);
             }
         }
     }
diff --git a/services/core/java/com/android/server/wallpaper/WallpaperCropper.java b/services/core/java/com/android/server/wallpaper/WallpaperCropper.java
index b3e68a35..8e8455a 100644
--- a/services/core/java/com/android/server/wallpaper/WallpaperCropper.java
+++ b/services/core/java/com/android/server/wallpaper/WallpaperCropper.java
@@ -16,6 +16,8 @@
 
 package com.android.server.wallpaper;
 
+import static android.app.WallpaperManager.ORIENTATION_LANDSCAPE;
+import static android.app.WallpaperManager.ORIENTATION_SQUARE_LANDSCAPE;
 import static android.app.WallpaperManager.ORIENTATION_UNKNOWN;
 import static android.app.WallpaperManager.getOrientation;
 import static android.app.WallpaperManager.getRotatedOrientation;
@@ -28,6 +30,7 @@
 import static com.android.server.wallpaper.WallpaperUtils.getWallpaperDir;
 import static com.android.window.flags.Flags.multiCrop;
 
+import android.app.WallpaperManager;
 import android.graphics.Bitmap;
 import android.graphics.BitmapFactory;
 import android.graphics.ImageDecoder;
@@ -821,4 +824,54 @@
             }
         }
     }
+
+    /**
+     * Returns true if a wallpaper is compatible with a given display with ID, {@code displayId}.
+     *
+     * <p>A wallpaper is compatible with a display if any of the following are true
+     * <ol>the display is a default display</o>
+     * <ol>the wallpaper is a stock wallpaper</ol>
+     * <ol>the wallpaper size is at least 3/4 of the display resolution and, in landscape displays,
+     * the wallpaper has an aspect ratio of at least 11:13.</ol>
+     */
+    @VisibleForTesting
+    boolean isWallpaperCompatibleForDisplay(int displayId, WallpaperData wallpaperData) {
+        if (displayId == DEFAULT_DISPLAY) {
+            return true;
+        }
+
+        File wallpaperFile = wallpaperData.getWallpaperFile();
+        if (!wallpaperFile.exists()) {
+            // Assumption: Stock wallpaper is suitable for all display sizes.
+            return true;
+        }
+
+        DisplayInfo displayInfo = mWallpaperDisplayHelper.getDisplayInfo(displayId);
+        Point displaySize = new Point(displayInfo.logicalWidth, displayInfo.logicalHeight);
+        int displayOrientation = WallpaperManager.getOrientation(displaySize);
+
+        Point wallpaperImageSize = new Point(
+                (int) Math.ceil(wallpaperData.cropHint.width() / wallpaperData.mSampleSize),
+                (int) Math.ceil(wallpaperData.cropHint.height() / wallpaperData.mSampleSize));
+        if (wallpaperImageSize.equals(0, 0)) {
+            BitmapFactory.Options options = new BitmapFactory.Options();
+            options.inJustDecodeBounds = true;
+            BitmapFactory.decodeFile(wallpaperFile.getAbsolutePath(), options);
+            wallpaperImageSize.set(options.outWidth, options.outHeight);
+        }
+
+        double maxDisplayToImageRatio = Math.max((double) displaySize.x / wallpaperImageSize.x,
+                (double) displaySize.y / wallpaperImageSize.y);
+        if (maxDisplayToImageRatio > 1.5) {
+            return false;
+        }
+
+        // For displays in landscape, we only support images with an aspect ratio >= 11:13
+        if (displayOrientation == ORIENTATION_LANDSCAPE) {
+            return ((double) wallpaperImageSize.x / wallpaperImageSize.y) >= 11.0 / 13;
+        }
+
+        // For other orientations, we don't enforce any aspect ratio.
+        return true;
+    }
 }
diff --git a/services/core/java/com/android/server/wallpaper/WallpaperManagerService.java b/services/core/java/com/android/server/wallpaper/WallpaperManagerService.java
index 69b2b9b..d620e98 100644
--- a/services/core/java/com/android/server/wallpaper/WallpaperManagerService.java
+++ b/services/core/java/com/android/server/wallpaper/WallpaperManagerService.java
@@ -118,7 +118,6 @@
 import android.system.ErrnoException;
 import android.system.Os;
 import android.text.TextUtils;
-import android.util.ArraySet;
 import android.util.EventLog;
 import android.util.IntArray;
 import android.util.Slog;
@@ -161,7 +160,6 @@
 import java.util.Locale;
 import java.util.Map;
 import java.util.Objects;
-import java.util.Set;
 import java.util.function.Consumer;
 import java.util.function.Predicate;
 
@@ -743,10 +741,8 @@
     final WallpaperDisplayHelper mWallpaperDisplayHelper;
     final WallpaperCropper mWallpaperCropper;
 
-    // TODO(b/384519749): Remove this set after we introduce the aspect ratio check.
-    private final Set<Integer> mWallpaperCompatibleDisplaysForTest = new ArraySet<>();
-
-    private boolean isWallpaperCompatibleForDisplay(int displayId, WallpaperConnection connection) {
+    @VisibleForTesting
+    boolean isWallpaperCompatibleForDisplay(int displayId, WallpaperConnection connection) {
         if (connection == null) {
             return false;
         }
@@ -757,26 +753,14 @@
 
         // Image wallpaper
         if (isDeviceEligibleForDesktopExperienceWallpaper(mContext)) {
-            // TODO(b/384519749): check display's resolution and image wallpaper cropped image
-            //  aspect ratio.
-            return displayId == DEFAULT_DISPLAY
-                    || mWallpaperCompatibleDisplaysForTest.contains(displayId);
+            return mWallpaperCropper.isWallpaperCompatibleForDisplay(displayId,
+                    connection.mWallpaper);
         }
         // When enableConnectedDisplaysWallpaper is off, we assume the image wallpaper supports all
         // usable displays.
         return true;
     }
 
-    @VisibleForTesting
-    void addWallpaperCompatibleDisplayForTest(int displayId) {
-        mWallpaperCompatibleDisplaysForTest.add(displayId);
-    }
-
-    @VisibleForTesting
-    void removeWallpaperCompatibleDisplayForTest(int displayId) {
-        mWallpaperCompatibleDisplaysForTest.remove(displayId);
-    }
-
     private void updateFallbackConnection(int clientUid) {
         if (mLastWallpaper == null || mFallbackWallpaper == null) return;
         final WallpaperConnection systemConnection = mLastWallpaper.connection;
@@ -844,7 +828,7 @@
                     // `which` flag.
                     DisplayConnector fallbackConnector =
                             mFallbackWallpaper.connection.getDisplayConnectorOrCreate(displayId);
-                    if (fallbackConnector != null && fallbackConnector.mEngine != null) {
+                    if (fallbackConnector != null) {
                         fallbackConnector.mWhich = which;
                         fallbackConnector.connectLocked(mFallbackWallpaper.connection,
                                 mFallbackWallpaper);
diff --git a/services/core/java/com/android/server/wm/ActivityRecord.java b/services/core/java/com/android/server/wm/ActivityRecord.java
index 9692b69..7b6d408f 100644
--- a/services/core/java/com/android/server/wm/ActivityRecord.java
+++ b/services/core/java/com/android/server/wm/ActivityRecord.java
@@ -109,7 +109,6 @@
 import static android.view.WindowManager.LayoutParams.TYPE_BASE_APPLICATION;
 import static android.view.WindowManager.PROPERTY_ACTIVITY_EMBEDDING_SPLITS_ENABLED;
 import static android.view.WindowManager.PROPERTY_ALLOW_UNTRUSTED_ACTIVITY_EMBEDDING_STATE_SHARING;
-import static android.view.WindowManager.TRANSIT_CLOSE;
 import static android.view.WindowManager.TRANSIT_OLD_UNSET;
 import static android.view.WindowManager.TRANSIT_RELAUNCH;
 import static android.view.WindowManager.hasWindowExtensionsEnabled;
@@ -321,9 +320,7 @@
 import android.util.Slog;
 import android.util.TimeUtils;
 import android.util.proto.ProtoOutputStream;
-import android.view.AppTransitionAnimationSpec;
 import android.view.DisplayInfo;
-import android.view.IAppTransitionAnimationSpecsFuture;
 import android.view.InputApplicationHandle;
 import android.view.RemoteAnimationAdapter;
 import android.view.RemoteAnimationDefinition;
@@ -493,6 +490,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.
@@ -2348,7 +2346,8 @@
                 // The snapshot of home is only used once because it won't be updated while screen
                 // is on (see {@link TaskSnapshotController#screenTurningOff}).
                 mWmService.mTaskSnapshotController.removeSnapshotCache(task.mTaskId);
-                if ((mDisplayContent.mAppTransition.getTransitFlags()
+                final Transition transition = mTransitionController.getCollectingTransition();
+                if (transition != null && (transition.getFlags()
                         & WindowManager.TRANSIT_FLAG_KEYGUARD_GOING_AWAY_NO_ANIMATION) == 0) {
                     // Only use snapshot of home as starting window when unlocking directly.
                     return false;
@@ -3637,7 +3636,6 @@
                 if (DEBUG_VISIBILITY || DEBUG_TRANSITION) {
                     Slog.v(TAG_TRANSITION, "Prepare close transition: finishing " + this);
                 }
-                mDisplayContent.prepareAppTransition(TRANSIT_CLOSE);
 
                 // When finishing the activity preemptively take the snapshot before the app window
                 // is marked as hidden and any configuration changes take place
@@ -3739,7 +3737,6 @@
 
     private void prepareActivityHideTransitionAnimation() {
         final DisplayContent dc = mDisplayContent;
-        dc.prepareAppTransition(TRANSIT_CLOSE);
         setVisibility(false);
         dc.executeAppTransition();
     }
@@ -4391,13 +4388,6 @@
             removeStartingWindow();
         }
 
-        // If app transition animation was running for this activity, then we need to ensure that
-        // the app transition notifies that animations have completed in
-        // DisplayContent.handleAnimatingStoppedAndTransition(), so add to that list now
-        if (isAnimating(TRANSITION | PARENTS, ANIMATION_TYPE_APP_TRANSITION)) {
-            getDisplayContent().mNoAnimationNotifyOnTransitionFinished.add(token);
-        }
-
         if (delayed && !isEmpty()) {
             // set the token aside because it has an active animation to be finished
             ProtoLog.v(WM_DEBUG_ADD_REMOVE,
@@ -5069,8 +5059,6 @@
     void applyOptionsAnimation() {
         if (DEBUG_TRANSITION) Slog.i(TAG, "Applying options for " + this);
         if (mPendingRemoteAnimation != null) {
-            mDisplayContent.mAppTransition.overridePendingAppTransitionRemote(
-                    mPendingRemoteAnimation);
             mTransitionController.setStatusBarTransitionDelay(
                     mPendingRemoteAnimation.getStatusBarTransitionDelay());
         } else {
@@ -5100,14 +5088,6 @@
         IRemoteCallback finishCallback = null;
         switch (animationType) {
             case ANIM_CUSTOM:
-                displayContent.mAppTransition.overridePendingAppTransition(
-                        pendingOptions.getPackageName(),
-                        pendingOptions.getCustomEnterResId(),
-                        pendingOptions.getCustomExitResId(),
-                        pendingOptions.getCustomBackgroundColor(),
-                        pendingOptions.getAnimationStartedListener(),
-                        pendingOptions.getAnimationFinishedListener(),
-                        pendingOptions.getOverrideTaskTransition());
                 options = AnimationOptions.makeCustomAnimOptions(pendingOptions.getPackageName(),
                         pendingOptions.getCustomEnterResId(), pendingOptions.getCustomExitResId(),
                         pendingOptions.getCustomBackgroundColor(),
@@ -5116,9 +5096,6 @@
                 finishCallback = pendingOptions.getAnimationFinishedListener();
                 break;
             case ANIM_CLIP_REVEAL:
-                displayContent.mAppTransition.overridePendingAppTransitionClipReveal(
-                        pendingOptions.getStartX(), pendingOptions.getStartY(),
-                        pendingOptions.getWidth(), pendingOptions.getHeight());
                 options = AnimationOptions.makeClipRevealAnimOptions(
                         pendingOptions.getStartX(), pendingOptions.getStartY(),
                         pendingOptions.getWidth(), pendingOptions.getHeight());
@@ -5130,9 +5107,6 @@
                 }
                 break;
             case ANIM_SCALE_UP:
-                displayContent.mAppTransition.overridePendingAppTransitionScaleUp(
-                        pendingOptions.getStartX(), pendingOptions.getStartY(),
-                        pendingOptions.getWidth(), pendingOptions.getHeight());
                 options = AnimationOptions.makeScaleUpAnimOptions(
                         pendingOptions.getStartX(), pendingOptions.getStartY(),
                         pendingOptions.getWidth(), pendingOptions.getHeight(),
@@ -5148,10 +5122,6 @@
             case ANIM_THUMBNAIL_SCALE_DOWN:
                 final boolean scaleUp = (animationType == ANIM_THUMBNAIL_SCALE_UP);
                 final HardwareBuffer buffer = pendingOptions.getThumbnail();
-                displayContent.mAppTransition.overridePendingAppTransitionThumb(buffer,
-                        pendingOptions.getStartX(), pendingOptions.getStartY(),
-                        pendingOptions.getAnimationStartedListener(),
-                        scaleUp);
                 options = AnimationOptions.makeThumbnailAnimOptions(buffer,
                         pendingOptions.getStartX(), pendingOptions.getStartY(), scaleUp);
                 startCallback = pendingOptions.getAnimationStartedListener();
@@ -5164,36 +5134,9 @@
                 break;
             case ANIM_THUMBNAIL_ASPECT_SCALE_UP:
             case ANIM_THUMBNAIL_ASPECT_SCALE_DOWN:
-                final AppTransitionAnimationSpec[] specs = pendingOptions.getAnimSpecs();
-                final IAppTransitionAnimationSpecsFuture specsFuture =
-                        pendingOptions.getSpecsFuture();
-                if (specsFuture != null) {
-                    displayContent.mAppTransition.overridePendingAppTransitionMultiThumbFuture(
-                            specsFuture, pendingOptions.getAnimationStartedListener(),
-                            animationType == ANIM_THUMBNAIL_ASPECT_SCALE_UP);
-                } else if (animationType == ANIM_THUMBNAIL_ASPECT_SCALE_DOWN
-                        && specs != null) {
-                    displayContent.mAppTransition.overridePendingAppTransitionMultiThumb(
-                            specs, pendingOptions.getAnimationStartedListener(),
-                            pendingOptions.getAnimationFinishedListener(), false);
-                } else {
-                    displayContent.mAppTransition.overridePendingAppTransitionAspectScaledThumb(
-                            pendingOptions.getThumbnail(),
-                            pendingOptions.getStartX(), pendingOptions.getStartY(),
-                            pendingOptions.getWidth(), pendingOptions.getHeight(),
-                            pendingOptions.getAnimationStartedListener(),
-                            (animationType == ANIM_THUMBNAIL_ASPECT_SCALE_UP));
-                    if (intent.getSourceBounds() == null) {
-                        intent.setSourceBounds(new Rect(pendingOptions.getStartX(),
-                                pendingOptions.getStartY(),
-                                pendingOptions.getStartX() + pendingOptions.getWidth(),
-                                pendingOptions.getStartY() + pendingOptions.getHeight()));
-                    }
-                }
+                // TODO(b/397847511): remove the related types from ActivityOptions.
                 break;
             case ANIM_OPEN_CROSS_PROFILE_APPS:
-                displayContent.mAppTransition
-                        .overridePendingAppTransitionStartCrossProfileApps();
                 options = AnimationOptions.makeCrossProfileAnimOptions();
                 break;
             case ANIM_NONE:
@@ -5450,8 +5393,6 @@
     }
 
     private void setVisibility(boolean visible, boolean deferHidingClient) {
-        final AppTransition appTransition = getDisplayContent().mAppTransition;
-
         // Don't set visibility to false if we were already not visible. This prevents WM from
         // adding the app to the closing app list which doesn't make sense for something that is
         // already not visible. However, set visibility to true even if we are already visible.
@@ -5471,8 +5412,8 @@
         }
 
         ProtoLog.v(WM_DEBUG_APP_TRANSITIONS,
-                "setAppVisibility(%s, visible=%b): %s visible=%b mVisibleRequested=%b Callers=%s",
-                token, visible, appTransition, isVisible(), mVisibleRequested,
+                "setAppVisibility(%s, visible=%b): visible=%b mVisibleRequested=%b Callers=%s",
+                token, visible, isVisible(), mVisibleRequested,
                 Debug.getCallers(6));
 
         // Before setting mVisibleRequested so we can track changes.
@@ -5569,15 +5510,6 @@
         updateReportedVisibilityLocked();
     }
 
-    @Override
-    boolean applyAnimation(LayoutParams lp, @TransitionOldType int transit, boolean enter,
-            boolean isVoiceInteraction, @Nullable ArrayList<WindowContainer> sources) {
-        if ((mTransitionChangeFlags & FLAG_STARTING_WINDOW_TRANSFER_RECIPIENT) != 0) {
-            return false;
-        }
-        return super.applyAnimation(lp, transit, enter, isVoiceInteraction, sources);
-    }
-
     /**
      * Update visibility to this {@link ActivityRecord}.
      *
@@ -6447,6 +6379,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 +6413,7 @@
 
         if (isStopping) {
             ProtoLog.v(WM_DEBUG_STATES, "Moving to STOPPED: %s (stop complete)", this);
+            mStoppedTime = SystemClock.uptimeMillis();
             setState(STOPPED, "activityStopped");
         }
 
@@ -6639,9 +6573,7 @@
         // starting window is drawn, the transition can start earlier. Exclude finishing and bubble
         // because it may be a trampoline.
         if (app == null && !finishing && !mLaunchedFromBubble
-                && mVisibleRequested && !mDisplayContent.mAppTransition.isReady()
-                && !mDisplayContent.mAppTransition.isRunning()
-                && mDisplayContent.isNextTransitionForward()) {
+                && mVisibleRequested && mDisplayContent.isNextTransitionForward()) {
             // The pending transition state will be cleared after the transition is started, so
             // save the state for launching the client later (used by LaunchActivityItem).
             mStartingData.mIsTransitionForward = true;
@@ -7523,7 +7455,6 @@
             }
         }
 
-        getDisplayContent().mAppTransition.notifyAppTransitionFinishedLocked(token);
         scheduleAnimation();
 
         // Schedule to handle the stopping and finishing activities which the animation is done
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/AppCompatAspectRatioPolicy.java b/services/core/java/com/android/server/wm/AppCompatAspectRatioPolicy.java
index 15c0789..3535a96 100644
--- a/services/core/java/com/android/server/wm/AppCompatAspectRatioPolicy.java
+++ b/services/core/java/com/android/server/wm/AppCompatAspectRatioPolicy.java
@@ -36,8 +36,7 @@
 import android.content.pm.ActivityInfo;
 import android.content.res.Configuration;
 import android.graphics.Rect;
-
-import com.android.window.flags.Flags;
+import android.window.DesktopModeFlags;
 
 /**
  * Encapsulate app compat policy logic related to aspect ratio.
@@ -298,7 +297,8 @@
                 // Camera compat mode is an exception to this, where the activity is letterboxed
                 // to an aspect ratio commonly found on phones, e.g. 16:9, to avoid issues like
                 // stretching of the camera preview.
-                || (Flags.ignoreAspectRatioRestrictionsForResizeableFreeformActivities()
+                || (DesktopModeFlags
+                    .IGNORE_ASPECT_RATIO_RESTRICTIONS_FOR_RESIZEABLE_FREEFORM_ACTIVITIES.isTrue()
                     && task.getWindowingMode() == WINDOWING_MODE_FREEFORM
                     && !mActivityRecord.shouldCreateAppCompatDisplayInsets()
                     && !AppCompatCameraPolicy.shouldCameraCompatControlAspectRatio(
diff --git a/services/core/java/com/android/server/wm/AppCompatCameraOverrides.java b/services/core/java/com/android/server/wm/AppCompatCameraOverrides.java
index 47d30c9..5eed5470 100644
--- a/services/core/java/com/android/server/wm/AppCompatCameraOverrides.java
+++ b/services/core/java/com/android/server/wm/AppCompatCameraOverrides.java
@@ -23,6 +23,7 @@
 import static android.content.pm.ActivityInfo.OVERRIDE_MIN_ASPECT_RATIO_ONLY_FOR_CAMERA;
 import static android.content.pm.ActivityInfo.OVERRIDE_ORIENTATION_ONLY_FOR_CAMERA;
 import static android.view.WindowManager.PROPERTY_CAMERA_COMPAT_ALLOW_FORCE_ROTATION;
+import static android.view.WindowManager.PROPERTY_CAMERA_COMPAT_ALLOW_SIMULATE_REQUESTED_ORIENTATION;
 import static android.view.WindowManager.PROPERTY_CAMERA_COMPAT_ALLOW_REFRESH;
 import static android.view.WindowManager.PROPERTY_CAMERA_COMPAT_ENABLE_REFRESH_VIA_PAUSE;
 import static android.view.WindowManager.PROPERTY_COMPAT_ALLOW_MIN_ASPECT_RATIO_OVERRIDE;
@@ -32,6 +33,7 @@
 import static com.android.server.wm.AppCompatUtils.isChangeEnabled;
 
 import android.annotation.NonNull;
+import android.annotation.Nullable;
 
 import com.android.server.wm.utils.OptPropFactory;
 import com.android.window.flags.Flags;
@@ -60,6 +62,8 @@
     private final OptPropFactory.OptProp mCameraCompatEnableRefreshViaPauseOptProp;
     @NonNull
     private final OptPropFactory.OptProp mCameraCompatAllowForceRotationOptProp;
+    @Nullable
+    private final OptPropFactory.OptProp mCameraCompatAllowOrientationTreatmentOptProp;
 
     AppCompatCameraOverrides(@NonNull ActivityRecord activityRecord,
             @NonNull AppCompatConfiguration appCompatConfiguration,
@@ -80,6 +84,10 @@
         mCameraCompatAllowForceRotationOptProp = optPropBuilder.create(
                 PROPERTY_CAMERA_COMPAT_ALLOW_FORCE_ROTATION,
                 isCameraCompatTreatmentEnabled);
+        mCameraCompatAllowOrientationTreatmentOptProp =
+                Flags.enableCameraCompatForDesktopWindowingOptOut() ? optPropBuilder.create(
+                PROPERTY_CAMERA_COMPAT_ALLOW_SIMULATE_REQUESTED_ORIENTATION,
+                isCameraCompatTreatmentEnabled) : null;
     }
 
     /**
@@ -168,10 +176,31 @@
      * </ul>
      */
     boolean shouldApplyFreeformTreatmentForCameraCompat() {
-        return Flags.enableCameraCompatForDesktopWindowing() && (isChangeEnabled(mActivityRecord,
-                OVERRIDE_CAMERA_COMPAT_ENABLE_FREEFORM_WINDOWING_TREATMENT)
-                || mActivityRecord.mWmService.mAppCompatConfiguration
-                    .isCameraCompatFreeformWindowingTreatmentEnabled());
+        return Flags.enableCameraCompatForDesktopWindowing()
+                && (shouldEnableCameraCompatFreeformTreatmentForApp()
+                || shouldEnableCameraCompatFreeformTreatmentForAllApps());
+    }
+
+    private boolean shouldEnableCameraCompatFreeformTreatmentForApp() {
+        if (mCameraCompatAllowOrientationTreatmentOptProp != null) {
+            return mCameraCompatAllowOrientationTreatmentOptProp
+                    .shouldEnableWithOptOutOverrideAndProperty(isChangeEnabled(mActivityRecord,
+                            OVERRIDE_CAMERA_COMPAT_ENABLE_FREEFORM_WINDOWING_TREATMENT));
+        } else {
+            return isChangeEnabled(mActivityRecord,
+                    OVERRIDE_CAMERA_COMPAT_ENABLE_FREEFORM_WINDOWING_TREATMENT);
+        }
+    }
+
+    /**
+     * Returns whether camera compat treatment should be enabled for all apps targeted for treatment
+     * (usually fixed-orientation apps).
+     *
+     * <p>This can be enabled via adb only.
+     */
+    private boolean shouldEnableCameraCompatFreeformTreatmentForAllApps() {
+        return mActivityRecord.mWmService.mAppCompatConfiguration
+                .isCameraCompatFreeformWindowingTreatmentEnabled();
     }
 
     boolean isOverrideOrientationOnlyForCameraEnabled() {
diff --git a/services/core/java/com/android/server/wm/DisplayArea.java b/services/core/java/com/android/server/wm/DisplayArea.java
index b932ef3..6718ae4 100644
--- a/services/core/java/com/android/server/wm/DisplayArea.java
+++ b/services/core/java/com/android/server/wm/DisplayArea.java
@@ -748,14 +748,9 @@
                         && policy.okToAnimate(true /* ignoreScreenOn */)) {
                     return false;
                 }
-                // Consider unoccluding only when all unknown visibilities have been
-                // resolved, as otherwise we just may be starting another occluding activity.
-                final boolean isUnoccluding =
-                        mDisplayContent.mAppTransition.isUnoccluding()
-                                && mDisplayContent.mUnknownAppVisibilityController.allResolved();
-                // If keyguard is showing, or we're unoccluding, force the keyguard's orientation,
+                // Use keyguard's orientation if it is showing and not occluded
                 // even if SystemUI hasn't updated the attrs yet.
-                if (policy.isKeyguardShowingAndNotOccluded() || isUnoccluding) {
+                if (policy.isKeyguardShowingAndNotOccluded()) {
                     return true;
                 }
             }
diff --git a/services/core/java/com/android/server/wm/DisplayContent.java b/services/core/java/com/android/server/wm/DisplayContent.java
index e30c24d..214a3a1 100644
--- a/services/core/java/com/android/server/wm/DisplayContent.java
+++ b/services/core/java/com/android/server/wm/DisplayContent.java
@@ -88,7 +88,6 @@
 import static android.window.DisplayAreaOrganizer.FEATURE_IME;
 import static android.window.DisplayAreaOrganizer.FEATURE_ROOT;
 
-import static com.android.internal.protolog.WmProtoLogGroups.WM_DEBUG_APP_TRANSITIONS;
 import static com.android.internal.protolog.WmProtoLogGroups.WM_DEBUG_BOOT;
 import static com.android.internal.protolog.WmProtoLogGroups.WM_DEBUG_CONTENT_RECORDING;
 import static com.android.internal.protolog.WmProtoLogGroups.WM_DEBUG_FOCUS;
@@ -369,12 +368,6 @@
 
     private MetricsLogger mMetricsLogger;
 
-    /**
-     * List of clients without a transtiton animation that we notify once we are done
-     * transitioning since they won't be notified through the app window animator.
-     */
-    final List<IBinder> mNoAnimationNotifyOnTransitionFinished = new ArrayList<>();
-
     // Mapping from a token IBinder to a WindowToken object on this display.
     private final HashMap<IBinder, WindowToken> mTokenMap = new HashMap();
 
@@ -829,28 +822,36 @@
      */
     private final ToBooleanFunction<WindowState> mFindFocusedWindow = w -> {
         final ActivityRecord focusedApp = mFocusedApp;
+        final boolean canReceiveKeys = w.canReceiveKeys();
         ProtoLog.v(WM_DEBUG_FOCUS, "Looking for focus: %s, flags=%d, canReceive=%b, reason=%s",
-                w, w.mAttrs.flags, w.canReceiveKeys(),
+                w, w.mAttrs.flags, canReceiveKeys,
                 w.canReceiveKeysReason(false /* fromUserTouch */));
 
-        if (!w.canReceiveKeys()) {
+        if (!canReceiveKeys) {
             return false;
         }
 
-        // When switching the app task, we keep the IME window visibility for better
-        // transitioning experiences.
-        // However, in case IME created a child window or the IME selection dialog without
-        // dismissing during the task switching to keep the window focus because IME window has
-        // higher window hierarchy, we don't give it focus if the next IME layering target
-        // doesn't request IME visible.
-        if (w.mIsImWindow && w.isChildWindow() && (mImeLayeringTarget == null
-                || !mImeLayeringTarget.isRequestedVisible(ime()))) {
-            return false;
-        }
-        if (w.mAttrs.type == TYPE_INPUT_METHOD_DIALOG && mImeLayeringTarget != null
-                && !(mImeLayeringTarget.isRequestedVisible(ime())
-                        && mImeLayeringTarget.isVisibleRequested())) {
-            return false;
+        // IME windows remain visibleRequested while switching apps to maintain a smooth animation.
+        // This persists until the new app is focused, so they can be visibleRequested despite not
+        // being visible to the user (i.e. occluded). These rank higher in the window hierarchy than
+        // app windows, so they will always be considered first. To avoid having the focus stuck,
+        // an IME window (child or not) cannot be focused if the IME parent is not visible. However,
+        // child windows also require the IME to be visible in the current app.
+        if (w.mIsImWindow) {
+            final boolean imeParentVisible = mInputMethodSurfaceParentWindow != null
+                    && mInputMethodSurfaceParentWindow.isVisibleRequested();
+            if (!imeParentVisible) {
+                ProtoLog.v(WM_DEBUG_FOCUS, "findFocusedWindow: IME window not focusable as"
+                        + " IME parent is not visible");
+                return false;
+            }
+
+            if (w.isChildWindow()
+                    && !getInsetsStateController().getImeSourceProvider().isImeShowing()) {
+                ProtoLog.v(WM_DEBUG_FOCUS, "findFocusedWindow: IME child window not focusable as"
+                        + " IME is not visible");
+                return false;
+            }
         }
 
         final ActivityRecord activity = w.mActivityRecord;
@@ -1171,8 +1172,6 @@
 
         mFixedRotationTransitionListener = new FixedRotationTransitionListener(mDisplayId);
         mAppTransition = new AppTransition(mWmService.mContext, mWmService, this);
-        mAppTransition.registerListenerLocked(mWmService.mActivityManagerAppTransitionNotifier);
-        mAppTransition.registerListenerLocked(mFixedRotationTransitionListener);
         mTransitionController.registerLegacyListener(mFixedRotationTransitionListener);
         mUnknownAppVisibilityController = new UnknownAppVisibilityController(mWmService, this);
         mRemoteDisplayChangeController = new RemoteDisplayChangeController(this);
@@ -2858,13 +2857,6 @@
         return isVisible() && !mRemoved && !mRemoving;
     }
 
-    @Override
-    void onAppTransitionDone() {
-        super.onAppTransitionDone();
-        mWmService.mWindowsChanged = true;
-        onTransitionFinished();
-    }
-
     void onTransitionFinished() {
         // If the transition finished callback cannot match the token for some reason, make sure the
         // rotated state is cleared if it is already invisible.
@@ -3385,9 +3377,7 @@
         mDeferredRemoval = false;
         try {
             mUnknownAppVisibilityController.clear();
-            mAppTransition.removeAppTransitionTimeoutCallbacks();
             mTransitionController.unregisterLegacyListener(mFixedRotationTransitionListener);
-            handleAnimatingStoppedAndTransition();
             mDeviceStateController.unregisterDeviceStateCallback(mDeviceStateConsumer);
             super.removeImmediately();
             if (DEBUG_DISPLAY) Slog.v(TAG_WM, "Removing display=" + this);
@@ -3570,11 +3560,7 @@
         mDisplayRotation.dumpDebug(proto, DISPLAY_ROTATION);
         mDisplayFrames.dumpDebug(proto, DISPLAY_FRAMES);
         proto.write(MIN_SIZE_OF_RESIZEABLE_TASK_DP, mMinSizeOfResizeableTaskDp);
-        if (mTransitionController.isShellTransitionsEnabled()) {
-            mTransitionController.dumpDebugLegacy(proto, APP_TRANSITION);
-        } else {
-            mAppTransition.dumpDebug(proto, APP_TRANSITION);
-        }
+        mTransitionController.dumpDebugLegacy(proto, APP_TRANSITION);
         if (mFocusedApp != null) {
             mFocusedApp.writeNameToProto(proto, FOCUSED_APP);
         }
@@ -5634,61 +5620,20 @@
      * @see AppTransition#prepareAppTransition
      */
     void requestTransitionAndLegacyPrepare(@WindowManager.TransitionType int transit,
-            @WindowManager.TransitionFlags int flags) {
-        prepareAppTransition(transit, flags);
-        mTransitionController.requestTransitionIfNeeded(transit, flags, null /* trigger */, this);
+            @WindowManager.TransitionFlags int flags, @Nullable WindowContainer trigger) {
+        mTransitionController.requestTransitionIfNeeded(transit, flags, trigger, this);
     }
 
     void executeAppTransition() {
         mTransitionController.setReady(this);
-        if (mAppTransition.isTransitionSet()) {
-            ProtoLog.w(WM_DEBUG_APP_TRANSITIONS,
-                    "Execute app transition: %s, displayId: %d Callers=%s",
-                    mAppTransition, mDisplayId, Debug.getCallers(5));
-            mAppTransition.setReady();
-            mWmService.mWindowPlacerLocked.requestTraversal();
-        }
-    }
-
-    /**
-     * Update pendingLayoutChanges after app transition has finished.
-     */
-    void handleAnimatingStoppedAndTransition() {
-        int changes = 0;
-
-        mAppTransition.setIdle();
-
-        for (int i = mNoAnimationNotifyOnTransitionFinished.size() - 1; i >= 0; i--) {
-            final IBinder token = mNoAnimationNotifyOnTransitionFinished.get(i);
-            mAppTransition.notifyAppTransitionFinishedLocked(token);
-        }
-        mNoAnimationNotifyOnTransitionFinished.clear();
-
-        mWallpaperController.hideDeferredWallpapersIfNeededLegacy();
-
-        onAppTransitionDone();
-
-        changes |= FINISH_LAYOUT_REDO_LAYOUT;
-        ProtoLog.v(WM_DEBUG_WALLPAPER, "Wallpaper layer changed: assigning layers + relayout");
-        computeImeTarget(true /* updateImeTarget */);
-        mWallpaperMayChange = true;
-        // Since the window list has been rebuilt, focus might have to be recomputed since the
-        // actual order of windows might have changed again.
-        mWmService.mFocusMayChange = true;
-
-        pendingLayoutChanges |= changes;
     }
 
     /** Check if pending app transition is for activity / task launch. */
     boolean isNextTransitionForward() {
         // TODO(b/191375840): decouple "forwardness" from transition system.
-        if (mTransitionController.isShellTransitionsEnabled()) {
-            @WindowManager.TransitionType int type =
-                    mTransitionController.getCollectingTransitionType();
-            return type == TRANSIT_OPEN || type == TRANSIT_TO_FRONT;
-        }
-        return mAppTransition.containsTransitRequest(TRANSIT_OPEN)
-                || mAppTransition.containsTransitRequest(TRANSIT_TO_FRONT);
+        final @WindowManager.TransitionType int type =
+                mTransitionController.getCollectingTransitionType();
+        return type == TRANSIT_OPEN || type == TRANSIT_TO_FRONT;
     }
 
     /**
diff --git a/services/core/java/com/android/server/wm/DisplayPolicy.java b/services/core/java/com/android/server/wm/DisplayPolicy.java
index fbe8501..7aa2101 100644
--- a/services/core/java/com/android/server/wm/DisplayPolicy.java
+++ b/services/core/java/com/android/server/wm/DisplayPolicy.java
@@ -631,7 +631,6 @@
                 mHandler.post(mAppTransitionFinished);
             }
         };
-        displayContent.mAppTransition.registerListenerLocked(mAppTransitionListener);
         displayContent.mTransitionController.registerLegacyListener(mAppTransitionListener);
 
         // TODO: Make it can take screenshot on external display
diff --git a/services/core/java/com/android/server/wm/KeyguardController.java b/services/core/java/com/android/server/wm/KeyguardController.java
index 6091b83..6d73739 100644
--- a/services/core/java/com/android/server/wm/KeyguardController.java
+++ b/services/core/java/com/android/server/wm/KeyguardController.java
@@ -250,7 +250,7 @@
                 // to the locked state before holding the sleep token again
                 if (!ENABLE_NEW_KEYGUARD_SHELL_TRANSITIONS) {
                     dc.requestTransitionAndLegacyPrepare(
-                            TRANSIT_TO_FRONT, TRANSIT_FLAG_KEYGUARD_APPEARING);
+                            TRANSIT_TO_FRONT, TRANSIT_FLAG_KEYGUARD_APPEARING, /* trigger= */ null);
                 }
                 dc.mWallpaperController.adjustWallpaperWindows();
                 dc.executeAppTransition();
diff --git a/services/core/java/com/android/server/wm/RootWindowContainer.java b/services/core/java/com/android/server/wm/RootWindowContainer.java
index 40f16c1..f309372 100644
--- a/services/core/java/com/android/server/wm/RootWindowContainer.java
+++ b/services/core/java/com/android/server/wm/RootWindowContainer.java
@@ -2103,10 +2103,6 @@
                 removeRootTasksInWindowingModes(WINDOWING_MODE_PINNED);
             }
 
-            // Set a transition to ensure that we don't immediately try and update the visibility
-            // of the activity entering PIP
-            r.getDisplayContent().prepareAppTransition(TRANSIT_NONE);
-
             transitionController.collect(task);
 
             // Defer the windowing mode change until after the transition to prevent the activity
diff --git a/services/core/java/com/android/server/wm/Task.java b/services/core/java/com/android/server/wm/Task.java
index 6b3499a..6cd1336 100644
--- a/services/core/java/com/android/server/wm/Task.java
+++ b/services/core/java/com/android/server/wm/Task.java
@@ -54,7 +54,6 @@
 import static android.view.WindowManager.PROPERTY_COMPAT_ALLOW_RESIZEABLE_ACTIVITY_OVERRIDES;
 import static android.view.WindowManager.TRANSIT_CLOSE;
 import static android.view.WindowManager.TRANSIT_FLAG_APP_CRASHED;
-import static android.view.WindowManager.TRANSIT_NONE;
 import static android.view.WindowManager.TRANSIT_OPEN;
 import static android.view.WindowManager.TRANSIT_TO_BACK;
 import static android.view.WindowManager.TRANSIT_TO_FRONT;
@@ -504,6 +503,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.
      */
@@ -1647,8 +1657,7 @@
                 // Prevent the transition from being executed too early if the top activity is
                 // resumed but the mVisibleRequested of any other activity is true, the transition
                 // should wait until next activity resumed.
-                if (r.isState(RESUMED) || (r.isVisible()
-                        && !mDisplayContent.mAppTransition.containsTransitRequest(TRANSIT_CLOSE))) {
+                if (r.isState(RESUMED) || r.isVisible()) {
                     r.finishIfPossible(reason, false /* oomAdj */);
                 } else {
                     r.destroyIfPossible(reason);
@@ -2324,11 +2333,6 @@
         mLastSurfaceSize.set(width, height);
     }
 
-    @VisibleForTesting
-    boolean isInChangeTransition() {
-        return AppTransition.isChangeTransitOld(mTransit);
-    }
-
     @Override
     void writeIdentifierToProto(ProtoOutputStream proto, long fieldId) {
         final long token = proto.start(fieldId);
@@ -3854,6 +3858,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);
@@ -5293,11 +5298,9 @@
 
         // Place a new activity at top of root task, so it is next to interact with the user.
         if ((r.intent.getFlags() & Intent.FLAG_ACTIVITY_NO_ANIMATION) != 0) {
-            mDisplayContent.prepareAppTransition(TRANSIT_NONE);
             mTaskSupervisor.mNoAnimActivities.add(r);
             mTransitionController.setNoAnimation(r);
         } else {
-            mDisplayContent.prepareAppTransition(TRANSIT_OPEN);
             mTaskSupervisor.mNoAnimActivities.remove(r);
         }
         if (newTask && !r.mLaunchTaskBehind) {
@@ -5477,7 +5480,8 @@
         Slog.w(TAG, "  Force finishing activity "
                 + r.intent.getComponent().flattenToShortString());
         Task finishedTask = r.getTask();
-        mDisplayContent.requestTransitionAndLegacyPrepare(TRANSIT_CLOSE, TRANSIT_FLAG_APP_CRASHED);
+        mDisplayContent.requestTransitionAndLegacyPrepare(TRANSIT_CLOSE, TRANSIT_FLAG_APP_CRASHED,
+                finishedTask);
         r.finishIfPossible(reason, false /* oomAdj */);
 
         // Also terminate any activities below it that aren't yet stopped, to avoid a situation
@@ -5695,7 +5699,6 @@
                 ActivityOptions.abort(options);
             }
         }
-        mDisplayContent.prepareAppTransition(transit);
     }
 
     final void moveTaskToFront(Task tr, boolean noAnimation, ActivityOptions options,
@@ -5747,12 +5750,9 @@
 
             if (DEBUG_TRANSITION) Slog.v(TAG_TRANSITION, "Prepare to front transition: task=" + tr);
             if (noAnimation) {
-                mDisplayContent.prepareAppTransition(TRANSIT_NONE);
                 mTaskSupervisor.mNoAnimActivities.add(top);
-                if (mTransitionController.isShellTransitionsEnabled()) {
-                    mTransitionController.collect(top);
-                    mTransitionController.setNoAnimation(top);
-                }
+                mTransitionController.collect(top);
+                mTransitionController.setNoAnimation(top);
                 ActivityOptions.abort(options);
             } else {
                 updateTransitLocked(TRANSIT_TO_FRONT, options);
@@ -5862,10 +5862,6 @@
                         moveTaskToBackInner(tr, transition);
                     });
         } else {
-            // Skip the transition for pinned task.
-            if (!inPinnedWindowingMode()) {
-                mDisplayContent.prepareAppTransition(TRANSIT_TO_BACK);
-            }
             moveTaskToBackInner(tr, null /* transition */);
         }
         return true;
diff --git a/services/core/java/com/android/server/wm/TaskFragment.java b/services/core/java/com/android/server/wm/TaskFragment.java
index bbda849..80095b3 100644
--- a/services/core/java/com/android/server/wm/TaskFragment.java
+++ b/services/core/java/com/android/server/wm/TaskFragment.java
@@ -44,10 +44,6 @@
 import static android.view.Surface.ROTATION_270;
 import static android.view.Surface.ROTATION_90;
 import static android.view.WindowManager.LayoutParams.FLAG_DIM_BEHIND;
-import static android.view.WindowManager.TRANSIT_CLOSE;
-import static android.view.WindowManager.TRANSIT_FLAG_OPEN_BEHIND;
-import static android.view.WindowManager.TRANSIT_NONE;
-import static android.view.WindowManager.TRANSIT_OPEN;
 
 import static com.android.internal.protolog.WmProtoLogGroups.WM_DEBUG_STATES;
 import static com.android.server.wm.ActivityRecord.State.PAUSED;
@@ -56,7 +52,6 @@
 import static com.android.server.wm.ActivityRecord.State.STOPPING;
 import static com.android.server.wm.ActivityTaskManagerDebugConfig.DEBUG_RESULTS;
 import static com.android.server.wm.ActivityTaskManagerDebugConfig.DEBUG_SWITCH;
-import static com.android.server.wm.ActivityTaskManagerDebugConfig.DEBUG_TRANSITION;
 import static com.android.server.wm.ActivityTaskManagerDebugConfig.POSTFIX_RESULTS;
 import static com.android.server.wm.ActivityTaskManagerDebugConfig.POSTFIX_SWITCH;
 import static com.android.server.wm.ActivityTaskManagerDebugConfig.POSTFIX_TRANSITION;
@@ -1682,36 +1677,15 @@
         final DisplayContent dc = taskDisplayArea.mDisplayContent;
         if (prev != null) {
             if (prev.finishing) {
-                if (DEBUG_TRANSITION) {
-                    Slog.v(TAG_TRANSITION, "Prepare close transition: prev=" + prev);
-                }
                 if (mTaskSupervisor.mNoAnimActivities.contains(prev)) {
                     anim = false;
-                    dc.prepareAppTransition(TRANSIT_NONE);
-                } else {
-                    dc.prepareAppTransition(TRANSIT_CLOSE);
                 }
                 prev.setVisibility(false);
-            } else {
-                if (DEBUG_TRANSITION) {
-                    Slog.v(TAG_TRANSITION, "Prepare open transition: prev=" + prev);
-                }
-                if (mTaskSupervisor.mNoAnimActivities.contains(next)) {
-                    anim = false;
-                    dc.prepareAppTransition(TRANSIT_NONE);
-                } else {
-                    dc.prepareAppTransition(TRANSIT_OPEN,
-                            next.mLaunchTaskBehind ? TRANSIT_FLAG_OPEN_BEHIND : 0);
-                }
-            }
-        } else {
-            if (DEBUG_TRANSITION) Slog.v(TAG_TRANSITION, "Prepare open transition: no previous");
-            if (mTaskSupervisor.mNoAnimActivities.contains(next)) {
+            } else if (mTaskSupervisor.mNoAnimActivities.contains(next)) {
                 anim = false;
-                dc.prepareAppTransition(TRANSIT_NONE);
-            } else {
-                dc.prepareAppTransition(TRANSIT_OPEN);
             }
+        } else if (mTaskSupervisor.mNoAnimActivities.contains(next)) {
+            anim = false;
         }
 
         if (anim) {
@@ -1915,10 +1889,12 @@
             // Even if the transient activity is occluded, defer pausing (addToStopping will still
             // be called) it until the transient transition is done. So the current resuming
             // activity won't need to wait for additional pause complete.
+            ProtoLog.d(WM_DEBUG_STATES, "startPausing: Skipping pause for transient "
+                            + "resumed activity=%s", mResumedActivity);
             return false;
         }
 
-        ProtoLog.d(WM_DEBUG_STATES, "startPausing: taskFrag =%s " + "mResumedActivity=%s", this,
+        ProtoLog.d(WM_DEBUG_STATES, "startPausing: taskFrag=%s mResumedActivity=%s", this,
                 mResumedActivity);
 
         if (mPausingActivity != null) {
diff --git a/services/core/java/com/android/server/wm/Transition.java b/services/core/java/com/android/server/wm/Transition.java
index 5217a75..fd7d96a 100644
--- a/services/core/java/com/android/server/wm/Transition.java
+++ b/services/core/java/com/android/server/wm/Transition.java
@@ -1640,6 +1640,12 @@
                     mSyncEngine.setSyncMethod(syncId, BLASTSyncEngine.METHOD_BLAST);
                 }
                 mSyncEngine.addToSyncSet(syncId, target);
+            } else {
+                // If there is an existing sync group for the commit-at-end activity,
+                // enforce BLAST sync method for its windows, before resuming config dispatch.
+                target.forAllWindows(windowState -> {
+                    windowState.mSyncMethodOverride = BLASTSyncEngine.METHOD_BLAST;
+                }, true /* traverseTopToBottom */);
             }
             // Reset surface state here (since it was skipped in buildFinishTransaction). Since
             // we are resuming config to the "current" state, we have to calculate the matching
@@ -2026,23 +2032,18 @@
         if (mOverrideOptions == null) {
             return;
         }
-
-        if (!Flags.moveAnimationOptionsToChange()) {
-            info.setAnimationOptions(mOverrideOptions);
-        } else {
-            final List<TransitionInfo.Change> changes = info.getChanges();
-            for (int i = changes.size() - 1; i >= 0; --i) {
-                final WindowContainer<?> container = mTargets.get(i).mContainer;
-                if (container.asActivityRecord() != null
-                        || shouldApplyAnimOptionsToTask(container.asTask())) {
-                    changes.get(i).setAnimationOptions(mOverrideOptions);
-                    // TODO(b/295805497): Extract mBackgroundColor from AnimationOptions.
-                    changes.get(i).setBackgroundColor(mOverrideOptions.getBackgroundColor());
-                } else if (shouldApplyAnimOptionsToEmbeddedTf(container.asTaskFragment())) {
-                    // We only override AnimationOptions because backgroundColor should be from
-                    // TaskFragmentAnimationParams.
-                    changes.get(i).setAnimationOptions(mOverrideOptions);
-                }
+        final List<TransitionInfo.Change> changes = info.getChanges();
+        for (int i = changes.size() - 1; i >= 0; --i) {
+            final WindowContainer<?> container = mTargets.get(i).mContainer;
+            if (container.asActivityRecord() != null
+                    || shouldApplyAnimOptionsToTask(container.asTask())) {
+                changes.get(i).setAnimationOptions(mOverrideOptions);
+                // TODO(b/295805497): Extract mBackgroundColor from AnimationOptions.
+                changes.get(i).setBackgroundColor(mOverrideOptions.getBackgroundColor());
+            } else if (shouldApplyAnimOptionsToEmbeddedTf(container.asTaskFragment())) {
+                // We only override AnimationOptions because backgroundColor should be from
+                // TaskFragmentAnimationParams.
+                changes.get(i).setAnimationOptions(mOverrideOptions);
             }
         }
         updateActivityTargetForCrossProfileAnimation(info);
@@ -2933,9 +2934,6 @@
 
         final AnimationOptions animOptionsForActivityTransition =
                 calculateAnimationOptionsForActivityTransition(type, sortedTargets);
-        if (!Flags.moveAnimationOptionsToChange() && animOptionsForActivityTransition != null) {
-            out.setAnimationOptions(animOptionsForActivityTransition);
-        }
 
         final ArraySet<WindowContainer> occludedAtEndContainers = new ArraySet<>();
         // Convert all the resolved ChangeInfos into TransactionInfo.Change objects in order.
@@ -3059,27 +3057,25 @@
             }
 
             AnimationOptions animOptions = null;
-            if (Flags.moveAnimationOptionsToChange()) {
-                if (activityRecord != null && animOptionsForActivityTransition != null) {
-                    animOptions = animOptionsForActivityTransition;
-                } else if (Flags.activityEmbeddingOverlayPresentationFlag()
-                        && isEmbeddedTaskFragment) {
-                    final TaskFragmentAnimationParams params = taskFragment.getAnimationParams();
-                    if (params.hasOverrideAnimation()) {
-                        // Only set AnimationOptions if there's any animation override.
-                        // We use separated field for backgroundColor, and
-                        // AnimationOptions#backgroundColor will be removed in long term.
-                        animOptions = AnimationOptions.makeCustomAnimOptions(
-                                taskFragment.getTask().getBasePackageName(),
-                                params.getOpenAnimationResId(), params.getChangeAnimationResId(),
-                                params.getCloseAnimationResId(), 0 /* backgroundColor */,
-                                false /* overrideTaskTransition */);
-                        animOptions.setUserId(taskFragment.getTask().mUserId);
-                    }
+            if (activityRecord != null && animOptionsForActivityTransition != null) {
+                animOptions = animOptionsForActivityTransition;
+            } else if (Flags.activityEmbeddingOverlayPresentationFlag()
+                    && isEmbeddedTaskFragment) {
+                final TaskFragmentAnimationParams params = taskFragment.getAnimationParams();
+                if (params.hasOverrideAnimation()) {
+                    // Only set AnimationOptions if there's any animation override.
+                    // We use separated field for backgroundColor, and
+                    // AnimationOptions#backgroundColor will be removed in long term.
+                    animOptions = AnimationOptions.makeCustomAnimOptions(
+                            taskFragment.getTask().getBasePackageName(),
+                            params.getOpenAnimationResId(), params.getChangeAnimationResId(),
+                            params.getCloseAnimationResId(), 0 /* backgroundColor */,
+                            false /* overrideTaskTransition */);
+                    animOptions.setUserId(taskFragment.getTask().mUserId);
                 }
-                if (animOptions != null) {
-                    change.setAnimationOptions(animOptions);
-                }
+            }
+            if (animOptions != null) {
+                change.setAnimationOptions(animOptions);
             }
 
             if (activityRecord != null) {
diff --git a/services/core/java/com/android/server/wm/TrustedPresentationListenerController.java b/services/core/java/com/android/server/wm/TrustedPresentationListenerController.java
index 2428836..e612d8e 100644
--- a/services/core/java/com/android/server/wm/TrustedPresentationListenerController.java
+++ b/services/core/java/com/android/server/wm/TrustedPresentationListenerController.java
@@ -20,6 +20,7 @@
 import static android.graphics.Matrix.MSCALE_Y;
 import static android.graphics.Matrix.MSKEW_X;
 import static android.graphics.Matrix.MSKEW_Y;
+import static android.view.Display.INVALID_DISPLAY;
 
 import static com.android.internal.protolog.WmProtoLogGroups.WM_DEBUG_TPL;
 
@@ -35,6 +36,7 @@
 import android.util.IntArray;
 import android.util.Pair;
 import android.util.Size;
+import android.util.SparseArray;
 import android.view.InputWindowHandle;
 import android.window.ITrustedPresentationListener;
 import android.window.TrustedPresentationThresholds;
@@ -251,7 +253,7 @@
         Rect tmpLogicalDisplaySize = new Rect();
         Matrix tmpInverseMatrix = new Matrix();
         float[] tmpMatrix = new float[9];
-        Region coveredRegionsAbove = new Region();
+        SparseArray<Region> coveredRegionsAboveByDisplay = new SparseArray<>();
         long currTimeMs = System.currentTimeMillis();
         ProtoLog.v(WM_DEBUG_TPL, "Checking %d windows", mLastWindowHandles.first.length);
 
@@ -262,7 +264,7 @@
                 ProtoLog.v(WM_DEBUG_TPL, "Skipping %s", windowHandle.name);
                 continue;
             }
-            var displayFound = false;
+            int displayId = INVALID_DISPLAY;
             tmpRectF.set(windowHandle.frame);
             for (var displayHandle : mLastWindowHandles.second) {
                 if (displayHandle.mDisplayId == windowHandle.displayId) {
@@ -273,17 +275,18 @@
                     tmpLogicalDisplaySize.set(0, 0, displayHandle.mLogicalSize.getWidth(),
                             displayHandle.mLogicalSize.getHeight());
                     tmpRect.intersect(tmpLogicalDisplaySize);
-                    displayFound = true;
+                    displayId = displayHandle.mDisplayId;
                     break;
                 }
             }
 
-            if (!displayFound) {
+            if (displayId == INVALID_DISPLAY) {
                 ProtoLog.v(WM_DEBUG_TPL, "Skipping %s, no associated display %d", windowHandle.name,
                         windowHandle.displayId);
                 continue;
             }
 
+            Region coveredRegionsAbove = coveredRegionsAboveByDisplay.get(displayId, new Region());
             var listeners = mRegisteredListeners.get(windowHandle.getWindowToken());
             if (listeners != null) {
                 Region region = new Region();
@@ -304,6 +307,7 @@
             }
 
             coveredRegionsAbove.op(tmpRect, Region.Op.UNION);
+            coveredRegionsAboveByDisplay.put(displayId, coveredRegionsAbove);
             ProtoLog.v(WM_DEBUG_TPL, "coveredRegionsAbove updated with %s frame:%s region:%s",
                     windowHandle.name, tmpRect.toShortString(), coveredRegionsAbove);
         }
diff --git a/services/core/java/com/android/server/wm/WallpaperWindowToken.java b/services/core/java/com/android/server/wm/WallpaperWindowToken.java
index 7c88abc..9506ffe 100644
--- a/services/core/java/com/android/server/wm/WallpaperWindowToken.java
+++ b/services/core/java/com/android/server/wm/WallpaperWindowToken.java
@@ -217,8 +217,7 @@
         }
 
         // If in a transition, defer commits for activities that are going invisible
-        if (!visible && (mTransitionController.inTransition()
-                || getDisplayContent().mAppTransition.isRunning())) {
+        if (!visible && mTransitionController.inTransition()) {
             return;
         }
 
diff --git a/services/core/java/com/android/server/wm/WindowContainer.java b/services/core/java/com/android/server/wm/WindowContainer.java
index 45202a2..d0d2067 100644
--- a/services/core/java/com/android/server/wm/WindowContainer.java
+++ b/services/core/java/com/android/server/wm/WindowContainer.java
@@ -16,9 +16,6 @@
 
 package com.android.server.wm;
 
-import static android.app.WindowConfiguration.WINDOWING_MODE_FREEFORM;
-import static android.app.WindowConfiguration.WINDOWING_MODE_FULLSCREEN;
-import static android.app.WindowConfiguration.WINDOWING_MODE_MULTI_WINDOW;
 import static android.app.WindowConfiguration.WINDOWING_MODE_PINNED;
 import static android.content.pm.ActivityInfo.SCREEN_ORIENTATION_BEHIND;
 import static android.content.pm.ActivityInfo.SCREEN_ORIENTATION_UNSET;
@@ -29,24 +26,15 @@
 import static android.content.res.Configuration.ORIENTATION_LANDSCAPE;
 import static android.content.res.Configuration.ORIENTATION_PORTRAIT;
 import static android.content.res.Configuration.ORIENTATION_UNDEFINED;
-import static android.os.Trace.TRACE_TAG_WINDOW_MANAGER;
 import static android.os.UserHandle.USER_NULL;
 import static android.view.SurfaceControl.Transaction;
 import static android.view.WindowInsets.Type.InsetsType;
 import static android.view.WindowManager.LayoutParams.INVALID_WINDOW_TYPE;
-import static android.window.TaskFragmentAnimationParams.DEFAULT_ANIMATION_BACKGROUND_COLOR;
 import static android.window.DesktopModeFlags.ENABLE_CAPTION_COMPAT_INSET_FORCE_CONSUMPTION;
 
 import static com.android.internal.protolog.WmProtoLogGroups.WM_DEBUG_ANIM;
-import static com.android.internal.protolog.WmProtoLogGroups.WM_DEBUG_APP_TRANSITIONS;
-import static com.android.internal.protolog.WmProtoLogGroups.WM_DEBUG_APP_TRANSITIONS_ANIM;
 import static com.android.internal.protolog.WmProtoLogGroups.WM_DEBUG_ORIENTATION;
 import static com.android.internal.protolog.WmProtoLogGroups.WM_DEBUG_SYNC_ENGINE;
-import static com.android.server.policy.WindowManagerPolicy.FINISH_LAYOUT_REDO_WALLPAPER;
-import static com.android.server.wm.AppTransition.MAX_APP_TRANSITION_DURATION;
-import static com.android.server.wm.AppTransition.isActivityTransitOld;
-import static com.android.server.wm.AppTransition.isTaskFragmentTransitOld;
-import static com.android.server.wm.DisplayContent.IME_TARGET_LAYERING;
 import static com.android.server.wm.IdentifierProto.HASH_CODE;
 import static com.android.server.wm.IdentifierProto.TITLE;
 import static com.android.server.wm.IdentifierProto.USER_ID;
@@ -65,7 +53,6 @@
 import static com.android.server.wm.WindowManagerDebugConfig.DEBUG;
 import static com.android.server.wm.WindowManagerDebugConfig.TAG_WITH_CLASS_NAME;
 import static com.android.server.wm.WindowManagerDebugConfig.TAG_WM;
-import static com.android.server.wm.WindowStateAnimator.ROOT_TASK_CLIP_AFTER_ANIM;
 
 import android.annotation.CallSuper;
 import android.annotation.ColorInt;
@@ -81,10 +68,8 @@
 import android.os.Debug;
 import android.os.IBinder;
 import android.os.RemoteException;
-import android.os.Trace;
 import android.util.ArrayMap;
 import android.util.ArraySet;
-import android.util.Pair;
 import android.util.Pools;
 import android.util.RotationUtils;
 import android.util.Slog;
@@ -102,14 +87,11 @@
 import android.view.SurfaceControlViewHost;
 import android.view.WindowManager;
 import android.view.WindowManager.TransitionOldType;
-import android.view.animation.Animation;
 import android.window.IWindowContainerToken;
 import android.window.WindowContainerToken;
 
 import com.android.internal.annotations.VisibleForTesting;
-import com.android.internal.graphics.ColorUtils;
 import com.android.internal.protolog.ProtoLog;
-import com.android.internal.protolog.common.LogLevel;
 import com.android.internal.util.ToBooleanFunction;
 import com.android.server.wm.SurfaceAnimator.Animatable;
 import com.android.server.wm.SurfaceAnimator.AnimationType;
@@ -1315,31 +1297,12 @@
     }
 
     /**
-     * Returns true if the container or one of its children as some content it can display or wants
-     * to display (e.g. app views or saved surface).
-     *
-     * NOTE: While this method will return true if the there is some content to display, it doesn't
-     * mean the container is visible. Use {@link #isVisible()} to determine if the container is
-     * visible.
-     */
-    boolean hasContentToDisplay() {
-        for (int i = mChildren.size() - 1; i >= 0; --i) {
-            final WindowContainer wc = mChildren.get(i);
-            if (wc.hasContentToDisplay()) {
-                return true;
-            }
-        }
-        return false;
-    }
-
-    /**
      * Returns true if the container or one of its children is considered visible from the
      * WindowManager perspective which usually means valid surface and some other internal state
      * are true.
      *
      * NOTE: While this method will return true if the surface is visible, it doesn't mean the
-     * client has actually displayed any content. Use {@link #hasContentToDisplay()} to determine if
-     * the container has any content to display.
+     * client has actually displayed any content.
      */
     boolean isVisible() {
         // TODO: Will this be more correct if it checks the visibility of its parents?
@@ -1480,13 +1443,6 @@
         }
     }
 
-    void onAppTransitionDone() {
-        for (int i = mChildren.size() - 1; i >= 0; --i) {
-            final WindowContainer wc = mChildren.get(i);
-            wc.onAppTransitionDone();
-        }
-    }
-
     /**
      * Called when this container or one of its descendants changed its requested orientation, and
      * wants this container to handle it or pass it to its parent.
@@ -3039,264 +2995,10 @@
         getRelativePosition(outPosition);
     }
 
-    /**
-     * Applies the app transition animation according the given the layout properties in the
-     * window hierarchy.
-     *
-     * @param lp The layout parameters of the window.
-     * @param transit The app transition type indicates what kind of transition to be applied.
-     * @param enter Whether the app transition is entering transition or not.
-     * @param isVoiceInteraction Whether the container is participating in voice interaction or not.
-     * @param sources {@link ActivityRecord}s which causes this app transition animation.
-     *
-     * @return {@code true} when the container applied the app transition, {@code false} if the
-     *         app transition is disabled or skipped.
-     *
-     * @see #getAnimationAdapter
-     */
-    boolean applyAnimation(WindowManager.LayoutParams lp, @TransitionOldType int transit,
-            boolean enter, boolean isVoiceInteraction,
-            @Nullable ArrayList<WindowContainer> sources) {
-        if (mWmService.mDisableTransitionAnimation) {
-            ProtoLog.v(WM_DEBUG_APP_TRANSITIONS_ANIM,
-                    "applyAnimation: transition animation is disabled or skipped. "
-                            + "container=%s", this);
-            cancelAnimation();
-            return false;
-        }
-
-        // Only apply an animation if the display isn't frozen. If it is frozen, there is no reason
-        // to animate and it can cause strange artifacts when we unfreeze the display if some
-        // different animation is running.
-        try {
-            Trace.traceBegin(TRACE_TAG_WINDOW_MANAGER, "WC#applyAnimation");
-            if (okToAnimate()) {
-                ProtoLog.v(WM_DEBUG_APP_TRANSITIONS_ANIM,
-                        "applyAnimation: transit=%s, enter=%b, wc=%s",
-                        AppTransition.appTransitionOldToString(transit), enter, this);
-                applyAnimationUnchecked(lp, enter, transit, isVoiceInteraction, sources);
-            } else {
-                cancelAnimation();
-            }
-        } finally {
-            Trace.traceEnd(TRACE_TAG_WINDOW_MANAGER);
-        }
-
-        return isAnimating();
-    }
-
-    /**
-     * Gets the {@link AnimationAdapter} according the given window layout properties in the window
-     * hierarchy.
-     *
-     * @return The return value will always contain two elements, one for normal animations and the
-     *         other for thumbnail animation, both can be {@code null}.
-     *
-     * @See com.android.server.wm.RemoteAnimationController.RemoteAnimationRecord
-     * @See LocalAnimationAdapter
-     */
-    Pair<AnimationAdapter, AnimationAdapter> getAnimationAdapter(WindowManager.LayoutParams lp,
-            @TransitionOldType int transit, boolean enter, boolean isVoiceInteraction) {
-        final Pair<AnimationAdapter, AnimationAdapter> resultAdapters;
-        final int appRootTaskClipMode = getDisplayContent().mAppTransition.getAppRootTaskClipMode();
-
-        // Separate position and size for use in animators.
-        final Rect screenBounds = getAnimationBounds(appRootTaskClipMode);
-        mTmpRect.set(screenBounds);
-        getAnimationPosition(mTmpPoint);
-        mTmpRect.offsetTo(0, 0);
-
-        final boolean isChanging = AppTransition.isChangeTransitOld(transit);
-
-        if (isChanging) {
-            final float durationScale = mWmService.getTransitionAnimationScaleLocked();
-            final DisplayInfo displayInfo = getDisplayContent().getDisplayInfo();
-            mTmpRect.offsetTo(mTmpPoint.x, mTmpPoint.y);
-
-            final AnimationAdapter adapter = new LocalAnimationAdapter(
-                    new WindowChangeAnimationSpec(null /* startBounds */, mTmpRect,
-                            displayInfo, durationScale, true /* isAppAnimation */,
-                            false /* isThumbnail */),
-                    getSurfaceAnimationRunner());
-
-            final AnimationAdapter thumbnailAdapter = null;
-            resultAdapters = new Pair<>(adapter, thumbnailAdapter);
-            mTransit = transit;
-            mTransitFlags = getDisplayContent().mAppTransition.getTransitFlags();
-        } else {
-            mNeedsAnimationBoundsLayer = (appRootTaskClipMode == ROOT_TASK_CLIP_AFTER_ANIM);
-            final Animation a = loadAnimation(lp, transit, enter, isVoiceInteraction);
-
-            if (a != null) {
-                // Only apply corner radius to animation if we're not in multi window mode.
-                // We don't want rounded corners when in pip or split screen.
-                final float windowCornerRadius = !inMultiWindowMode()
-                        ? getDisplayContent().getWindowCornerRadius()
-                        : 0;
-                if (asActivityRecord() != null
-                        && asActivityRecord().isNeedsLetterboxedAnimation()) {
-                    asActivityRecord().getLetterboxInnerBounds(mTmpRect);
-                }
-                AnimationAdapter adapter = new LocalAnimationAdapter(
-                        new WindowAnimationSpec(a, mTmpPoint, mTmpRect,
-                                getDisplayContent().mAppTransition.canSkipFirstFrame(),
-                                appRootTaskClipMode, true /* isAppAnimation */, windowCornerRadius),
-                        getSurfaceAnimationRunner());
-
-                resultAdapters = new Pair<>(adapter, null);
-                mNeedsZBoost = a.getZAdjustment() == Animation.ZORDER_TOP
-                        || AppTransition.isClosingTransitOld(transit);
-                mTransit = transit;
-                mTransitFlags = getDisplayContent().mAppTransition.getTransitFlags();
-            } else {
-                resultAdapters = new Pair<>(null, null);
-            }
-        }
-        return resultAdapters;
-    }
-
-    protected void applyAnimationUnchecked(WindowManager.LayoutParams lp, boolean enter,
-            @TransitionOldType int transit, boolean isVoiceInteraction,
-            @Nullable ArrayList<WindowContainer> sources) {
-        final Task task = asTask();
-        if (task != null && !enter && !task.isActivityTypeHomeOrRecents()) {
-            final InsetsControlTarget imeTarget = mDisplayContent.getImeTarget(IME_TARGET_LAYERING);
-            final boolean isImeLayeringTarget = imeTarget != null && imeTarget.getWindow() != null
-                    && imeTarget.getWindow().getTask() == task;
-            // Attach and show the IME screenshot when the task is the IME target and performing
-            // task closing transition to the next task.
-            if (isImeLayeringTarget && AppTransition.isTaskCloseTransitOld(transit)) {
-                mDisplayContent.showImeScreenshot();
-            }
-        }
-        final Pair<AnimationAdapter, AnimationAdapter> adapters = getAnimationAdapter(lp,
-                transit, enter, isVoiceInteraction);
-        AnimationAdapter adapter = adapters.first;
-        AnimationAdapter thumbnailAdapter = adapters.second;
-        if (adapter != null) {
-            if (sources != null) {
-                mSurfaceAnimationSources.addAll(sources);
-            }
-
-            AnimationRunnerBuilder animationRunnerBuilder = new AnimationRunnerBuilder();
-
-            // Check if the animation requests to show background color for Activity and embedded
-            // TaskFragment.
-            final ActivityRecord activityRecord = asActivityRecord();
-            final TaskFragment taskFragment = asTaskFragment();
-            if (adapter.getShowBackground()
-                    // Check if it is Activity transition.
-                    && ((activityRecord != null && isActivityTransitOld(transit))
-                    // Check if it is embedded TaskFragment transition.
-                    || (taskFragment != null && taskFragment.isEmbedded()
-                    && isTaskFragmentTransitOld(transit)))) {
-                final @ColorInt int backgroundColorForTransition;
-                if (adapter.getBackgroundColor() != 0) {
-                    // If available use the background color provided through getBackgroundColor
-                    // which if set originates from a call to overridePendingAppTransition.
-                    backgroundColorForTransition = adapter.getBackgroundColor();
-                } else {
-                    final TaskFragment organizedTf = activityRecord != null
-                            ? activityRecord.getOrganizedTaskFragment()
-                            : taskFragment.getOrganizedTaskFragment();
-                    if (organizedTf != null && organizedTf.getAnimationParams()
-                            .getAnimationBackgroundColor() != DEFAULT_ANIMATION_BACKGROUND_COLOR) {
-                        // This window is embedded and has an animation background color set on the
-                        // TaskFragment. Pass this color with this window, so the handler can use it
-                        // as the animation background color if needed,
-                        backgroundColorForTransition = organizedTf.getAnimationParams()
-                                .getAnimationBackgroundColor();
-                    } else {
-                        // Otherwise default to the window's background color if provided through
-                        // the theme as the background color for the animation - the top most window
-                        // with a valid background color and showBackground set takes precedence.
-                        final Task parentTask = activityRecord != null
-                                ? activityRecord.getTask()
-                                : taskFragment.getTask();
-                        backgroundColorForTransition = parentTask.getTaskDescription()
-                                .getBackgroundColor();
-                    }
-                }
-                // Set to opaque for animation background to prevent it from exposing the blank
-                // background or content below.
-                animationRunnerBuilder.setTaskBackgroundColor(ColorUtils.setAlphaComponent(
-                        backgroundColorForTransition, 255));
-            }
-
-            animationRunnerBuilder.build()
-                    .startAnimation(getPendingTransaction(), adapter, !isVisible(),
-                            ANIMATION_TYPE_APP_TRANSITION, thumbnailAdapter);
-
-            if (adapter.getShowWallpaper()) {
-                getDisplayContent().pendingLayoutChanges |= FINISH_LAYOUT_REDO_WALLPAPER;
-            }
-        }
-    }
-
     final SurfaceAnimationRunner getSurfaceAnimationRunner() {
         return mWmService.mSurfaceAnimationRunner;
     }
 
-    private Animation loadAnimation(WindowManager.LayoutParams lp, int transit, boolean enter,
-                                    boolean isVoiceInteraction) {
-        if ((isOrganized()
-                // TODO(b/161711458): Clean-up when moved to shell.
-                && getWindowingMode() != WINDOWING_MODE_FULLSCREEN
-                && getWindowingMode() != WINDOWING_MODE_FREEFORM
-                && getWindowingMode() != WINDOWING_MODE_MULTI_WINDOW)) {
-            return null;
-        }
-
-        final DisplayContent displayContent = getDisplayContent();
-        final DisplayInfo displayInfo = displayContent.getDisplayInfo();
-        final int width = displayInfo.appWidth;
-        final int height = displayInfo.appHeight;
-        ProtoLog.v(WM_DEBUG_APP_TRANSITIONS_ANIM, "applyAnimation: container=%s", this);
-
-        // Determine the visible rect to calculate the thumbnail clip with
-        // getAnimationFrames.
-        final Rect frame = new Rect(0, 0, width, height);
-        final Rect displayFrame = new Rect(0, 0,
-                displayInfo.logicalWidth, displayInfo.logicalHeight);
-        final Rect insets = new Rect();
-        final Rect stableInsets = new Rect();
-        final Rect surfaceInsets = new Rect();
-        getAnimationFrames(frame, insets, stableInsets, surfaceInsets);
-
-        if (mLaunchTaskBehind) {
-            // Differentiate the two animations. This one which is briefly on the screen
-            // gets the !enter animation, and the other one which remains on the
-            // screen gets the enter animation. Both appear in the mOpeningApps set.
-            enter = false;
-        }
-        ProtoLog.d(WM_DEBUG_APP_TRANSITIONS,
-                "Loading animation for app transition. transit=%s enter=%b frame=%s insets=%s "
-                        + "surfaceInsets=%s",
-                AppTransition.appTransitionOldToString(transit), enter, frame, insets,
-                surfaceInsets);
-        final Configuration displayConfig = displayContent.getConfiguration();
-        final Animation a = getDisplayContent().mAppTransition.loadAnimation(lp, transit, enter,
-                displayConfig.uiMode, displayConfig.orientation, frame, displayFrame, insets,
-                surfaceInsets, stableInsets, isVoiceInteraction, inFreeformWindowingMode(), this);
-        if (a != null) {
-            if (a != null) {
-                // Setup the maximum app transition duration to prevent malicious app may set a long
-                // animation duration or infinite repeat counts for the app transition through
-                // ActivityOption#makeCustomAnimation or WindowManager#overridePendingTransition.
-                a.restrictDuration(MAX_APP_TRANSITION_DURATION);
-            }
-            if (ProtoLog.isEnabled(WM_DEBUG_ANIM, LogLevel.DEBUG)) {
-                ProtoLog.i(WM_DEBUG_ANIM, "Loaded animation %s for %s, duration: %d, stack=%s",
-                        a, this, ((a != null) ? a.getDuration() : 0), Debug.getCallers(20));
-            }
-            final int containingWidth = frame.width();
-            final int containingHeight = frame.height();
-            a.initialize(containingWidth, containingHeight, width, height);
-            a.scaleCurrentDuration(mWmService.getTransitionAnimationScaleLocked());
-        }
-        return a;
-    }
-
     boolean canCreateRemoteAnimationTarget() {
         return false;
     }
diff --git a/services/core/java/com/android/server/wm/WindowProcessController.java b/services/core/java/com/android/server/wm/WindowProcessController.java
index 30dde54..270de01 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) {
@@ -475,8 +482,9 @@
             r.detachFromProcess();
             if (r.isVisibleRequested()) {
                 hasVisibleActivity = true;
+                Task finishingTask = r.getTask();
                 r.mDisplayContent.requestTransitionAndLegacyPrepare(TRANSIT_CLOSE,
-                        TRANSIT_FLAG_APP_CRASHED);
+                        TRANSIT_FLAG_APP_CRASHED, finishingTask);
             }
             r.destroyIfPossible("handleAppCrashed");
         }
@@ -1228,6 +1236,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 +1258,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 +1307,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 +1349,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 92ad2ce..bfedc90 100644
--- a/services/core/java/com/android/server/wm/WindowState.java
+++ b/services/core/java/com/android/server/wm/WindowState.java
@@ -1698,17 +1698,6 @@
                 || mActivityRecord.isStartingWindowDisplayed());
     }
 
-    @Override
-    boolean hasContentToDisplay() {
-        if (!isDrawn() && (mViewVisibility == View.VISIBLE
-                || (isAnimating(TRANSITION | PARENTS)
-                && !getDisplayContent().mAppTransition.isTransitionSet()))) {
-            return true;
-        }
-
-        return super.hasContentToDisplay();
-    }
-
     private boolean isVisibleByPolicyOrInsets() {
         return isVisibleByPolicy()
                 // If we don't have a provider, this window isn't used as a window generating
diff --git a/services/core/jni/Android.bp b/services/core/jni/Android.bp
index d26539c..9577608 100644
--- a/services/core/jni/Android.bp
+++ b/services/core/jni/Android.bp
@@ -204,6 +204,7 @@
         "android.frameworks.sensorservice-V1-ndk",
         "android.frameworks.stats@1.0",
         "android.frameworks.stats-V2-ndk",
+        "android.os.vibrator.flags-aconfig-cc",
         "android.system.suspend.control-V1-cpp",
         "android.system.suspend.control.internal-cpp",
         "android.system.suspend-V1-ndk",
diff --git a/services/core/jni/com_android_server_vibrator_VibratorController.cpp b/services/core/jni/com_android_server_vibrator_VibratorController.cpp
index abd4cd2..534dbb1 100644
--- a/services/core/jni/com_android_server_vibrator_VibratorController.cpp
+++ b/services/core/jni/com_android_server_vibrator_VibratorController.cpp
@@ -21,6 +21,7 @@
 #include <android/binder_parcel_jni.h>
 #include <android/hardware/vibrator/1.3/IVibrator.h>
 #include <android/persistable_bundle_aidl.h>
+#include <android_os_vibrator.h>
 #include <nativehelper/JNIHelp.h>
 #include <utils/Log.h>
 #include <utils/misc.h>
@@ -143,21 +144,23 @@
         return mHal->doWithRetry(fn, functionName);
     }
 
-    std::function<void()> createCallback(jlong vibrationId) {
+    std::function<void()> createCallback(jlong vibrationId, jlong stepId) {
         auto callbackId = ++mCallbackId;
-        return [vibrationId, callbackId, this]() {
+        return [vibrationId, stepId, callbackId, this]() {
             auto currentCallbackId = mCallbackId.load();
-            if (currentCallbackId != callbackId) {
-                // This callback is from an older HAL call that is no longer relevant to the service
+            if (!android_os_vibrator_fix_vibration_thread_callback_handling() &&
+                currentCallbackId != callbackId) {
+                // This callback is from an older HAL call that is no longer relevant
                 return;
             }
             auto jniEnv = GetOrAttachJNIEnvironment(sJvm);
-            jniEnv->CallVoidMethod(mCallbackListener, sMethodIdOnComplete, mVibratorId,
-                                   vibrationId);
+            jniEnv->CallVoidMethod(mCallbackListener, sMethodIdOnComplete, mVibratorId, vibrationId,
+                                   stepId);
         };
     }
 
     void disableOldCallbacks() {
+        // TODO remove this once android_os_vibrator_fix_vibration_thread_callback_handling removed
         mCallbackId++;
     }
 
@@ -165,6 +168,7 @@
     const std::shared_ptr<vibrator::HalController> mHal;
     const int32_t mVibratorId;
     const jobject mCallbackListener;
+    // TODO remove this once android_os_vibrator_fix_vibration_thread_callback_handling removed
     std::atomic<int64_t> mCallbackId;
 };
 
@@ -273,13 +277,13 @@
 }
 
 static jlong vibratorOn(JNIEnv* env, jclass /* clazz */, jlong ptr, jlong timeoutMs,
-                        jlong vibrationId) {
+                        jlong vibrationId, jlong stepId) {
     VibratorControllerWrapper* wrapper = reinterpret_cast<VibratorControllerWrapper*>(ptr);
     if (wrapper == nullptr) {
         ALOGE("vibratorOn failed because native wrapper was not initialized");
         return -1;
     }
-    auto callback = wrapper->createCallback(vibrationId);
+    auto callback = wrapper->createCallback(vibrationId, stepId);
     auto onFn = [timeoutMs, &callback](vibrator::HalWrapper* hal) {
         return hal->on(std::chrono::milliseconds(timeoutMs), callback);
     };
@@ -324,7 +328,7 @@
 }
 
 static jlong vibratorPerformEffect(JNIEnv* env, jclass /* clazz */, jlong ptr, jlong effect,
-                                   jlong strength, jlong vibrationId) {
+                                   jlong strength, jlong vibrationId, jlong stepId) {
     VibratorControllerWrapper* wrapper = reinterpret_cast<VibratorControllerWrapper*>(ptr);
     if (wrapper == nullptr) {
         ALOGE("vibratorPerformEffect failed because native wrapper was not initialized");
@@ -332,7 +336,7 @@
     }
     Aidl::Effect effectType = static_cast<Aidl::Effect>(effect);
     Aidl::EffectStrength effectStrength = static_cast<Aidl::EffectStrength>(strength);
-    auto callback = wrapper->createCallback(vibrationId);
+    auto callback = wrapper->createCallback(vibrationId, stepId);
     auto performEffectFn = [effectType, effectStrength, &callback](vibrator::HalWrapper* hal) {
         return hal->performEffect(effectType, effectStrength, callback);
     };
@@ -342,7 +346,7 @@
 
 static jlong vibratorPerformVendorEffect(JNIEnv* env, jclass /* clazz */, jlong ptr,
                                          jobject vendorData, jlong strength, jfloat scale,
-                                         jfloat adaptiveScale, jlong vibrationId) {
+                                         jfloat adaptiveScale, jlong vibrationId, jlong stepId) {
     VibratorControllerWrapper* wrapper = reinterpret_cast<VibratorControllerWrapper*>(ptr);
     if (wrapper == nullptr) {
         ALOGE("vibratorPerformVendorEffect failed because native wrapper was not initialized");
@@ -350,7 +354,7 @@
     }
     Aidl::VendorEffect effect =
             vendorEffectFromJavaParcel(env, vendorData, strength, scale, adaptiveScale);
-    auto callback = wrapper->createCallback(vibrationId);
+    auto callback = wrapper->createCallback(vibrationId, stepId);
     auto performVendorEffectFn = [&effect, &callback](vibrator::HalWrapper* hal) {
         return hal->performVendorEffect(effect, callback);
     };
@@ -359,7 +363,8 @@
 }
 
 static jlong vibratorPerformComposedEffect(JNIEnv* env, jclass /* clazz */, jlong ptr,
-                                           jobjectArray composition, jlong vibrationId) {
+                                           jobjectArray composition, jlong vibrationId,
+                                           jlong stepId) {
     VibratorControllerWrapper* wrapper = reinterpret_cast<VibratorControllerWrapper*>(ptr);
     if (wrapper == nullptr) {
         ALOGE("vibratorPerformComposedEffect failed because native wrapper was not initialized");
@@ -371,7 +376,7 @@
         jobject element = env->GetObjectArrayElement(composition, i);
         effects.push_back(effectFromJavaPrimitive(env, element));
     }
-    auto callback = wrapper->createCallback(vibrationId);
+    auto callback = wrapper->createCallback(vibrationId, stepId);
     auto performComposedEffectFn = [&effects, &callback](vibrator::HalWrapper* hal) {
         return hal->performComposedEffect(effects, callback);
     };
@@ -381,7 +386,8 @@
 }
 
 static jlong vibratorPerformPwleEffect(JNIEnv* env, jclass /* clazz */, jlong ptr,
-                                       jobjectArray waveform, jint brakingId, jlong vibrationId) {
+                                       jobjectArray waveform, jint brakingId, jlong vibrationId,
+                                       jlong stepId) {
     VibratorControllerWrapper* wrapper = reinterpret_cast<VibratorControllerWrapper*>(ptr);
     if (wrapper == nullptr) {
         ALOGE("vibratorPerformPwleEffect failed because native wrapper was not initialized");
@@ -406,7 +412,7 @@
         }
     }
 
-    auto callback = wrapper->createCallback(vibrationId);
+    auto callback = wrapper->createCallback(vibrationId, stepId);
     auto performPwleEffectFn = [&primitives, &callback](vibrator::HalWrapper* hal) {
         return hal->performPwleEffect(primitives, callback);
     };
@@ -415,7 +421,7 @@
 }
 
 static jlong vibratorPerformPwleV2Effect(JNIEnv* env, jclass /* clazz */, jlong ptr,
-                                         jobjectArray waveform, jlong vibrationId) {
+                                         jobjectArray waveform, jlong vibrationId, jlong stepId) {
     VibratorControllerWrapper* wrapper = reinterpret_cast<VibratorControllerWrapper*>(ptr);
     if (wrapper == nullptr) {
         ALOGE("vibratorPerformPwleV2Effect failed because native wrapper was not initialized");
@@ -431,7 +437,7 @@
     }
     composite.pwlePrimitives = primitives;
 
-    auto callback = wrapper->createCallback(vibrationId);
+    auto callback = wrapper->createCallback(vibrationId, stepId);
     auto composePwleV2Fn = [&composite, &callback](vibrator::HalWrapper* hal) {
         return hal->composePwleV2(composite, callback);
     };
@@ -610,16 +616,16 @@
          (void*)vibratorNativeInit},
         {"getNativeFinalizer", "()J", (void*)vibratorGetNativeFinalizer},
         {"isAvailable", "(J)Z", (void*)vibratorIsAvailable},
-        {"on", "(JJJ)J", (void*)vibratorOn},
+        {"on", "(JJJJ)J", (void*)vibratorOn},
         {"off", "(J)V", (void*)vibratorOff},
         {"setAmplitude", "(JF)V", (void*)vibratorSetAmplitude},
-        {"performEffect", "(JJJJ)J", (void*)vibratorPerformEffect},
-        {"performVendorEffect", "(JLandroid/os/Parcel;JFFJ)J", (void*)vibratorPerformVendorEffect},
-        {"performComposedEffect", "(J[Landroid/os/vibrator/PrimitiveSegment;J)J",
+        {"performEffect", "(JJJJJ)J", (void*)vibratorPerformEffect},
+        {"performVendorEffect", "(JLandroid/os/Parcel;JFFJJ)J", (void*)vibratorPerformVendorEffect},
+        {"performComposedEffect", "(J[Landroid/os/vibrator/PrimitiveSegment;JJ)J",
          (void*)vibratorPerformComposedEffect},
-        {"performPwleEffect", "(J[Landroid/os/vibrator/RampSegment;IJ)J",
+        {"performPwleEffect", "(J[Landroid/os/vibrator/RampSegment;IJJ)J",
          (void*)vibratorPerformPwleEffect},
-        {"performPwleV2Effect", "(J[Landroid/os/vibrator/PwlePoint;J)J",
+        {"performPwleV2Effect", "(J[Landroid/os/vibrator/PwlePoint;JJ)J",
          (void*)vibratorPerformPwleV2Effect},
         {"setExternalControl", "(JZ)V", (void*)vibratorSetExternalControl},
         {"alwaysOnEnable", "(JJJJ)V", (void*)vibratorAlwaysOnEnable},
@@ -632,7 +638,7 @@
     auto listenerClassName =
             "com/android/server/vibrator/VibratorController$OnVibrationCompleteListener";
     jclass listenerClass = FindClassOrDie(env, listenerClassName);
-    sMethodIdOnComplete = GetMethodIDOrDie(env, listenerClass, "onComplete", "(IJ)V");
+    sMethodIdOnComplete = GetMethodIDOrDie(env, listenerClass, "onComplete", "(IJJ)V");
 
     jclass primitiveClass = FindClassOrDie(env, "android/os/vibrator/PrimitiveSegment");
     sPrimitiveClassInfo.id = GetFieldIDOrDie(env, primitiveClass, "mPrimitiveId", "I");
diff --git a/services/devicepolicy/java/com/android/server/devicepolicy/DevicePolicyEngine.java b/services/devicepolicy/java/com/android/server/devicepolicy/DevicePolicyEngine.java
index 5b7e7f1..e3b9fdb 100644
--- a/services/devicepolicy/java/com/android/server/devicepolicy/DevicePolicyEngine.java
+++ b/services/devicepolicy/java/com/android/server/devicepolicy/DevicePolicyEngine.java
@@ -1454,11 +1454,12 @@
             }
 
             for (int i = 0; i < mLocalPolicies.size(); i++) {
-                Set<PolicyKey> localPolicies = new HashSet<>(
-                        mLocalPolicies.get(mLocalPolicies.keyAt(i)).keySet());
+                // New users are potentially added to mLocalPolicies during the loop body
+                // (see b/374511959).
+                int userId = mLocalPolicies.keyAt(i);
+                Set<PolicyKey> localPolicies = new HashSet<>(mLocalPolicies.get(userId).keySet());
                 for (PolicyKey policy : localPolicies) {
-                    PolicyState<?> policyState = mLocalPolicies.get(
-                            mLocalPolicies.keyAt(i)).get(policy);
+                    PolicyState<?> policyState = mLocalPolicies.get(userId).get(policy);
                     if (policyState.getPoliciesSetByAdmins().containsKey(admin)) {
                         removeLocalPolicy(
                                 policyState.getPolicyDefinition(), admin, mLocalPolicies.keyAt(i));
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/InputMethodSystemServerTests/src/com/android/inputmethodservice/AndroidManifest.xml b/services/tests/InputMethodSystemServerTests/src/com/android/inputmethodservice/AndroidManifest.xml
index 4552326..79dd129 100644
--- a/services/tests/InputMethodSystemServerTests/src/com/android/inputmethodservice/AndroidManifest.xml
+++ b/services/tests/InputMethodSystemServerTests/src/com/android/inputmethodservice/AndroidManifest.xml
@@ -16,7 +16,7 @@
   -->
 
 <manifest xmlns:android="http://schemas.android.com/apk/res/android"
-          package="com.android.inputmethod.imetests">
+          package="com.android.inputmethodservice">
 
     <!-- Permissions required for granting and logging -->
     <uses-permission android:name="android.permission.LOG_COMPAT_CHANGE"/>
@@ -33,7 +33,7 @@
     </application>
 
     <!-- The "targetPackage" reference the instruments APK package, which is the SimpleTestIme.apk,
-    while the test package is "com.android.inputmethod.imetests" (FrameworksImeTests.apk).-->
+    while the test package is "com.android.inputmethodservice" (FrameworksImeTests.apk).-->
     <instrumentation
         android:name="androidx.test.runner.AndroidJUnitRunner"
         android:targetPackage="com.android.apps.inputmethod.simpleime"
diff --git a/services/tests/InputMethodSystemServerTests/src/com/android/inputmethodservice/AndroidTest.xml b/services/tests/InputMethodSystemServerTests/src/com/android/inputmethodservice/AndroidTest.xml
index 8e6954b..5fe5b23 100644
--- a/services/tests/InputMethodSystemServerTests/src/com/android/inputmethodservice/AndroidTest.xml
+++ b/services/tests/InputMethodSystemServerTests/src/com/android/inputmethodservice/AndroidTest.xml
@@ -34,14 +34,15 @@
     <option name="test-tag" value="FrameworksImeTests" />
 
     <test class="com.android.tradefed.testtype.AndroidJUnitTest" >
-        <option name="package" value="com.android.inputmethod.imetests" />
+        <option name="package" value="com.android.inputmethodservice" />
         <option name="runner" value="androidx.test.runner.AndroidJUnitRunner" />
         <option name="hidden-api-checks" value="false"/>
     </test>
 
-    <!-- Collect the files in the dump directory for debugging -->
+    <!-- Collect output of DumpOnFailure -->
     <metrics_collector class="com.android.tradefed.device.metric.FilePullerLogCollector">
-        <option name="directory-keys" value="/sdcard/FrameworksImeTests/" />
+        <option name="directory-keys" value="/data/user/0/com.android.apps.inputmethod.simpleime/files" />
         <option name="collect-on-run-ended-only" value="true" />
+        <option name="clean-up" value="true" />
     </metrics_collector>
 </configuration>
diff --git a/services/tests/InputMethodSystemServerTests/src/com/android/inputmethodservice/InputMethodServiceTest.java b/services/tests/InputMethodSystemServerTests/src/com/android/inputmethodservice/InputMethodServiceTest.java
index ae5e851..6e16d29 100644
--- a/services/tests/InputMethodSystemServerTests/src/com/android/inputmethodservice/InputMethodServiceTest.java
+++ b/services/tests/InputMethodSystemServerTests/src/com/android/inputmethodservice/InputMethodServiceTest.java
@@ -40,10 +40,15 @@
 import android.graphics.Insets;
 import android.os.Build;
 import android.os.RemoteException;
+import android.platform.test.annotations.RequiresFlagsEnabled;
 import android.platform.test.flag.junit.DeviceFlagsValueProvider;
 import android.provider.Settings;
+import android.server.wm.DumpOnFailure;
 import android.server.wm.WindowManagerStateHelper;
 import android.util.Log;
+import android.view.View;
+import android.view.WindowInsets;
+import android.view.WindowInsetsAnimation;
 import android.view.WindowManagerGlobal;
 import android.view.inputmethod.EditorInfo;
 import android.view.inputmethod.Flags;
@@ -72,6 +77,7 @@
 import org.junit.rules.TestName;
 import org.junit.runner.RunWith;
 
+import java.util.List;
 import java.util.Objects;
 import java.util.concurrent.CountDownLatch;
 import java.util.concurrent.TimeUnit;
@@ -118,6 +124,9 @@
     @Rule
     public final TestName mName = new TestName();
 
+    @Rule
+    public final DumpOnFailure mDumpOnFailure = new DumpOnFailure();
+
     private Instrumentation mInstrumentation;
     private UiDevice mUiDevice;
     private InputMethodManager mImm;
@@ -139,10 +148,9 @@
         if (!mOriginalVerboseImeTrackerLoggingEnabled) {
             setVerboseImeTrackerLogging(true);
         }
+        mUiDevice.setOrientationNatural();
         prepareIme();
         prepareActivity();
-        mUiDevice.freezeRotation();
-        mUiDevice.setOrientationNatural();
         // Waits for input binding ready.
         eventually(() -> {
             mInputMethodService = InputMethodServiceWrapper.getInstance();
@@ -169,6 +177,9 @@
 
     @After
     public void tearDown() throws Exception {
+        if (!mUiDevice.isNaturalOrientation()) {
+            mUiDevice.setOrientationNatural();
+        }
         mUiDevice.unfreezeRotation();
         if (!mOriginalVerboseImeTrackerLoggingEnabled) {
             setVerboseImeTrackerLogging(false);
@@ -245,6 +256,61 @@
     }
 
     /**
+     * This checks that the surface is removed after the window was hidden in
+     * InputMethodService#hideSoftInput
+     */
+    @Test
+    @RequiresFlagsEnabled(Flags.FLAG_REFACTOR_INSETS_CONTROLLER)
+    public void testSurfaceRemovedAfterHideSoftInput() {
+        setShowImeWithHardKeyboard(true /* enabled */);
+
+        // Triggers to show IME via public API.
+        verifyInputViewStatusOnMainSync(() -> mActivity.showImeWithWindowInsetsController(),
+                EVENT_SHOW, true /* eventExpected */, true /* shown */, "IME is shown");
+        assertWithMessage("IME is shown").that(mInputMethodService.isInputViewShown()).isTrue();
+
+        final var window = mInputMethodService.getWindow().getWindow();
+        assertWithMessage("IME window exists").that(window).isNotNull();
+        eventually(() -> assertWithMessage("IME window showing").that(
+                window.getDecorView().getVisibility()).isEqualTo(View.VISIBLE));
+
+        mActivity.getWindow().getDecorView().setWindowInsetsAnimationCallback(
+                new WindowInsetsAnimation.Callback(
+                        WindowInsetsAnimation.Callback.DISPATCH_MODE_CONTINUE_ON_SUBTREE) {
+                    @NonNull
+                    @Override
+                    public WindowInsetsAnimation.Bounds onStart(
+                            @NonNull WindowInsetsAnimation animation,
+                            @NonNull WindowInsetsAnimation.Bounds bounds) {
+                        return super.onStart(animation, bounds);
+                    }
+
+                    @NonNull
+                    @Override
+                    public WindowInsets onProgress(@NonNull WindowInsets insets,
+                            @NonNull List<WindowInsetsAnimation> runningAnimations) {
+                        assertWithMessage("IME surface not removed during the animation").that(
+                                window.getDecorView().getVisibility()).isEqualTo(View.VISIBLE);
+                        return insets;
+                    }
+
+                    @Override
+                    public void onEnd(@NonNull WindowInsetsAnimation animation) {
+                        assertWithMessage(
+                                "IME surface not removed before the end of the animation").that(
+                                window.getDecorView().getVisibility()).isEqualTo(View.VISIBLE);
+                        super.onEnd(animation);
+                    }
+                });
+
+        // Triggers to hide IME via public API.
+        verifyInputViewStatusOnMainSync(() -> mActivity.hideImeWithWindowInsetsController(),
+                EVENT_HIDE, true /* eventExpected */, false /* shown */, "IME is not shown");
+        eventually(() -> assertWithMessage("IME window not showing").that(
+                window.getDecorView().getVisibility()).isEqualTo(View.GONE));
+    }
+
+    /**
      * This checks the result of calling IMS#requestShowSelf and IMS#requestHideSelf.
      */
     @Test
@@ -1222,8 +1288,13 @@
 
     @NonNull
     private UiObject2 getUiObject(@NonNull BySelector bySelector) {
+        final var preScreenshot = mInstrumentation.getUiAutomation().takeScreenshot();
+        mDumpOnFailure.dumpOnFailure("pre-getUiObject", preScreenshot);
         final var uiObject = mUiDevice.wait(Until.findObject(bySelector), TIMEOUT_MS);
-        assertWithMessage("UiObject with " + bySelector + " was found").that(uiObject).isNotNull();
+        mInstrumentation.waitForIdleSync();
+        final var postScreenshot = mInstrumentation.getUiAutomation().takeScreenshot();
+        mDumpOnFailure.dumpOnFailure("post-getUiObject", postScreenshot);
+        assertWithMessage("UiObject with " + bySelector + " was found").that(uiObject).isNull();
         return uiObject;
     }
 
diff --git a/services/tests/InputMethodSystemServerTests/src/com/android/server/inputmethod/ClientControllerTest.java b/services/tests/InputMethodSystemServerTests/src/com/android/server/inputmethod/ClientControllerTest.java
index 9e3d9ec..d98a0d1 100644
--- a/services/tests/InputMethodSystemServerTests/src/com/android/server/inputmethod/ClientControllerTest.java
+++ b/services/tests/InputMethodSystemServerTests/src/com/android/server/inputmethod/ClientControllerTest.java
@@ -28,6 +28,7 @@
 import static org.mockito.Mockito.verify;
 import static org.mockito.Mockito.when;
 
+import android.annotation.NonNull;
 import android.content.pm.PackageManagerInternal;
 import android.os.Handler;
 import android.os.IBinder;
@@ -55,8 +56,7 @@
     private static final String SOME_PACKAGE_NAME = "some.package";
 
     @Rule
-    public final RavenwoodRule mRavenwood = new RavenwoodRule.Builder()
-            .setProvideMainThread(true).build();
+    public final RavenwoodRule mRavenwood = new RavenwoodRule();
 
     @Mock
     private PackageManagerInternal mMockPackageManagerInternal;
@@ -65,7 +65,7 @@
     private IInputMethodClient mClient;
 
     @Mock
-    private IRemoteInputConnection mConnection;
+    private IRemoteInputConnection mFallbackConnection;
 
     private Handler mHandler;
 
@@ -81,23 +81,25 @@
     }
 
     // TODO(b/322895594): No need to directly invoke create$ravenwood once b/322895594 is fixed.
-    private IInputMethodClientInvoker createInvoker(IInputMethodClient client, Handler handler) {
+    @NonNull
+    private IInputMethodClientInvoker createInvoker(@NonNull IInputMethodClient client,
+            @NonNull Handler handler) {
         return RavenwoodRule.isOnRavenwood()
-                ? IInputMethodClientInvoker.create$ravenwood(client, handler) :
-                IInputMethodClientInvoker.create(client, handler);
+                ? IInputMethodClientInvoker.create$ravenwood(client, handler)
+                : IInputMethodClientInvoker.create(client, handler);
     }
 
     @Test
     public void testAddClient_cannotAddTheSameClientTwice() {
         final var invoker = createInvoker(mClient, mHandler);
         synchronized (ImfLock.class) {
-            mController.addClient(invoker, mConnection, ANY_DISPLAY_ID, ANY_CALLER_UID,
+            mController.addClient(invoker, mFallbackConnection, ANY_DISPLAY_ID, ANY_CALLER_UID,
                     ANY_CALLER_PID);
 
             SecurityException thrown = assertThrows(SecurityException.class,
                     () -> {
                         synchronized (ImfLock.class) {
-                            mController.addClient(invoker, mConnection, ANY_DISPLAY_ID,
+                            mController.addClient(invoker, mFallbackConnection, ANY_DISPLAY_ID,
                                     ANY_CALLER_UID, ANY_CALLER_PID);
                         }
                     });
@@ -111,9 +113,8 @@
     public void testAddClient() throws Exception {
         final var invoker = createInvoker(mClient, mHandler);
         synchronized (ImfLock.class) {
-            final var added = mController.addClient(invoker, mConnection, ANY_DISPLAY_ID,
-                    ANY_CALLER_UID,
-                    ANY_CALLER_PID);
+            final var added = mController.addClient(invoker, mFallbackConnection, ANY_DISPLAY_ID,
+                    ANY_CALLER_UID, ANY_CALLER_PID);
 
             verify(invoker.asBinder()).linkToDeath(any(IBinder.DeathRecipient.class), eq(0));
             assertThat(mController.getClient(invoker.asBinder())).isSameInstanceAs(added);
@@ -127,8 +128,8 @@
         ClientState added;
         synchronized (ImfLock.class) {
             mController.addClientControllerCallback(callback);
-            added = mController.addClient(invoker, mConnection, ANY_DISPLAY_ID, ANY_CALLER_UID,
-                    ANY_CALLER_PID);
+            added = mController.addClient(invoker, mFallbackConnection, ANY_DISPLAY_ID,
+                    ANY_CALLER_UID, ANY_CALLER_PID);
             assertThat(mController.getClient(invoker.asBinder())).isSameInstanceAs(added);
             assertThat(mController.removeClient(mClient)).isTrue();
         }
@@ -141,14 +142,14 @@
     @Test
     public void testVerifyClientAndPackageMatch() {
         final var invoker = createInvoker(mClient, mHandler);
-        when(mMockPackageManagerInternal.isSameApp(eq(SOME_PACKAGE_NAME),  /* flags= */
-                anyLong(), eq(ANY_CALLER_UID), /* userId= */ anyInt())).thenReturn(true);
+        when(mMockPackageManagerInternal.isSameApp(eq(SOME_PACKAGE_NAME), anyLong() /* flags */,
+                eq(ANY_CALLER_UID), anyInt() /* userId */)).thenReturn(true);
 
         synchronized (ImfLock.class) {
-            mController.addClient(invoker, mConnection, ANY_DISPLAY_ID, ANY_CALLER_UID,
+            mController.addClient(invoker, mFallbackConnection, ANY_DISPLAY_ID, ANY_CALLER_UID,
                     ANY_CALLER_PID);
-            assertThat(
-                    mController.verifyClientAndPackageMatch(mClient, SOME_PACKAGE_NAME)).isTrue();
+            assertThat(mController.verifyClientAndPackageMatch(mClient, SOME_PACKAGE_NAME))
+                    .isTrue();
         }
     }
 
@@ -171,7 +172,7 @@
         private ClientState mRemoved;
 
         @Override
-        public void onClientRemoved(ClientState removed) {
+        public void onClientRemoved(@NonNull ClientState removed) {
             mRemoved = removed;
             mLatch.countDown();
         }
diff --git a/services/tests/InputMethodSystemServerTests/src/com/android/server/inputmethod/DefaultImeVisibilityApplierTest.java b/services/tests/InputMethodSystemServerTests/src/com/android/server/inputmethod/DefaultImeVisibilityApplierTest.java
index 3aeab09..05615f6 100644
--- a/services/tests/InputMethodSystemServerTests/src/com/android/server/inputmethod/DefaultImeVisibilityApplierTest.java
+++ b/services/tests/InputMethodSystemServerTests/src/com/android/server/inputmethod/DefaultImeVisibilityApplierTest.java
@@ -196,7 +196,7 @@
     @RequiresFlagsDisabled(Flags.FLAG_REFACTOR_INSETS_CONTROLLER)
     public void testApplyImeVisibility_hideImeFromTargetOnSecondaryDisplay() {
         // Init a IME target client on the secondary display to show IME.
-        mInputMethodManagerService.addClient(mMockInputMethodClient, mMockRemoteInputConnection,
+        mInputMethodManagerService.addClient(mMockInputMethodClient, mMockFallbackInputConnection,
                 10 /* selfReportedDisplayId */);
         synchronized (ImfLock.class) {
             setAttachedClientLocked(null);
@@ -283,7 +283,7 @@
                 softInputMode /* softInputMode */,
                 0 /* windowFlags */,
                 mEditorInfo /* editorInfo */,
-                mMockRemoteInputConnection /* inputConnection */,
+                mMockFallbackInputConnection /* fallbackInputConnection */,
                 mMockRemoteAccessibilityInputConnection /* remoteAccessibilityInputConnection */,
                 mTargetSdkVersion /* unverifiedTargetSdkVersion */,
                 mUserId /* userId */,
diff --git a/services/tests/InputMethodSystemServerTests/src/com/android/server/inputmethod/InputMethodManagerServiceTestBase.java b/services/tests/InputMethodSystemServerTests/src/com/android/server/inputmethod/InputMethodManagerServiceTestBase.java
index 6af4064..9ce86ca 100644
--- a/services/tests/InputMethodSystemServerTests/src/com/android/server/inputmethod/InputMethodManagerServiceTestBase.java
+++ b/services/tests/InputMethodSystemServerTests/src/com/android/server/inputmethod/InputMethodManagerServiceTestBase.java
@@ -116,7 +116,7 @@
     @Mock protected IInputMethodClient mMockInputMethodClient;
     @Mock protected IInputMethodSession mMockInputMethodSession;
     @Mock protected IBinder mWindowToken;
-    @Mock protected IRemoteInputConnection mMockRemoteInputConnection;
+    @Mock protected IRemoteInputConnection mMockFallbackInputConnection;
     @Mock protected IRemoteAccessibilityInputConnection mMockRemoteAccessibilityInputConnection;
     @Mock protected ImeOnBackInvokedDispatcher mMockImeOnBackInvokedDispatcher;
     @Mock protected IInputMethodManager.Stub mMockIInputMethodManager;
@@ -300,7 +300,8 @@
         lifecycle.onBootPhase(SystemService.PHASE_ACTIVITY_MANAGER_READY);
 
         // Call InputMethodManagerService#addClient() as a preparation to start interacting with it.
-        mInputMethodManagerService.addClient(mMockInputMethodClient, mMockRemoteInputConnection, 0);
+        mInputMethodManagerService.addClient(mMockInputMethodClient, mMockFallbackInputConnection,
+                0 /* selfReportedDisplayId */);
         createSessionForClient(mMockInputMethodClient);
     }
 
diff --git a/services/tests/InputMethodSystemServerTests/src/com/android/server/inputmethod/InputMethodManagerServiceWindowGainedFocusTest.java b/services/tests/InputMethodSystemServerTests/src/com/android/server/inputmethod/InputMethodManagerServiceWindowGainedFocusTest.java
index c958bd3..11abc94 100644
--- a/services/tests/InputMethodSystemServerTests/src/com/android/server/inputmethod/InputMethodManagerServiceWindowGainedFocusTest.java
+++ b/services/tests/InputMethodSystemServerTests/src/com/android/server/inputmethod/InputMethodManagerServiceWindowGainedFocusTest.java
@@ -316,7 +316,7 @@
                 softInputMode /* softInputMode */,
                 0 /* windowFlags */,
                 mEditorInfo /* editorInfo */,
-                mMockRemoteInputConnection /* inputConnection */,
+                mMockFallbackInputConnection /* fallbackInputConnection */,
                 mMockRemoteAccessibilityInputConnection /* remoteAccessibilityInputConnection */,
                 mTargetSdkVersion /* unverifiedTargetSdkVersion */,
                 mUserId /* userId */,
diff --git a/services/tests/displayservicetests/src/com/android/server/display/DisplayManagerServiceTest.java b/services/tests/displayservicetests/src/com/android/server/display/DisplayManagerServiceTest.java
index e0e4425..c151732 100644
--- a/services/tests/displayservicetests/src/com/android/server/display/DisplayManagerServiceTest.java
+++ b/services/tests/displayservicetests/src/com/android/server/display/DisplayManagerServiceTest.java
@@ -984,8 +984,7 @@
 
         Handler handler = displayManager.getDisplayHandler();
         waitForIdleHandler(handler);
-        assertThat(callback.receivedEvents()).containsExactly(EVENT_DISPLAY_BASIC_CHANGED,
-                EVENT_DISPLAY_REFRESH_RATE_CHANGED);
+        assertThat(callback.receivedEvents()).containsExactly(EVENT_DISPLAY_BASIC_CHANGED);
     }
 
     /**
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/mockingservicestests/src/com/android/server/crashrecovery/AndroidTest.xml b/services/tests/mockingservicestests/src/com/android/server/crashrecovery/AndroidTest.xml
index 7b06ebe..5043c47 100644
--- a/services/tests/mockingservicestests/src/com/android/server/crashrecovery/AndroidTest.xml
+++ b/services/tests/mockingservicestests/src/com/android/server/crashrecovery/AndroidTest.xml
@@ -31,4 +31,9 @@
         <option name="runner" value="androidx.test.runner.AndroidJUnitRunner" />
         <option name="hidden-api-checks" value="false"/>
     </test>
+
+    <!-- Only run this Tests if the Crashrecovery Mainline module is installed. -->
+    <object type="module_controller" class="com.android.tradefed.testtype.suite.module.MainlineTestModuleController">
+        <option name="mainline-module-package-name" value="com.google.android.crashrecovery" />
+    </object>
 </configuration>
diff --git a/services/tests/mockingservicestests/src/com/android/server/rollback/AndroidTest.xml b/services/tests/mockingservicestests/src/com/android/server/rollback/AndroidTest.xml
index 635183c..6f6b017 100644
--- a/services/tests/mockingservicestests/src/com/android/server/rollback/AndroidTest.xml
+++ b/services/tests/mockingservicestests/src/com/android/server/rollback/AndroidTest.xml
@@ -31,4 +31,9 @@
         <option name="runner" value="androidx.test.runner.AndroidJUnitRunner" />
         <option name="hidden-api-checks" value="false"/>
     </test>
+
+    <!-- Only run this Tests if the Crashrecovery Mainline module is installed. -->
+    <object type="module_controller" class="com.android.tradefed.testtype.suite.module.MainlineTestModuleController">
+        <option name="mainline-module-package-name" value="com.google.android.crashrecovery" />
+    </object>
 </configuration>
diff --git a/services/tests/mockingservicestests/src/com/android/server/wallpaper/WallpaperCropperTest.java b/services/tests/mockingservicestests/src/com/android/server/wallpaper/WallpaperCropperTest.java
index b53f6fb..49c37f1 100644
--- a/services/tests/mockingservicestests/src/com/android/server/wallpaper/WallpaperCropperTest.java
+++ b/services/tests/mockingservicestests/src/com/android/server/wallpaper/WallpaperCropperTest.java
@@ -16,6 +16,7 @@
 
 package com.android.server.wallpaper;
 
+import static android.app.WallpaperManager.FLAG_SYSTEM;
 import static android.app.WallpaperManager.ORIENTATION_LANDSCAPE;
 import static android.app.WallpaperManager.ORIENTATION_UNKNOWN;
 import static android.app.WallpaperManager.ORIENTATION_PORTRAIT;
@@ -23,13 +24,20 @@
 import static android.app.WallpaperManager.ORIENTATION_SQUARE_PORTRAIT;
 import static android.app.WallpaperManager.getOrientation;
 import static android.app.WallpaperManager.getRotatedOrientation;
+import static android.os.UserHandle.USER_SYSTEM;
+import static android.view.Display.DEFAULT_DISPLAY;
 
+import static com.android.dx.mockito.inline.extended.ExtendedMockito.doReturn;
+import static com.android.dx.mockito.inline.extended.ExtendedMockito.mockitoSession;
 import static com.android.window.flags.Flags.FLAG_MULTI_CROP;
 
 import static com.google.common.truth.Truth.assertThat;
 
+import static org.mockito.ArgumentMatchers.any;
 import static org.mockito.ArgumentMatchers.anyInt;
+import static org.mockito.ArgumentMatchers.eq;
 import static org.mockito.Mockito.doAnswer;
+import static org.mockito.Mockito.mock;
 import static org.mockito.Mockito.when;
 import static org.mockito.MockitoAnnotations.initMocks;
 
@@ -37,15 +45,29 @@
 import android.graphics.Rect;
 import android.platform.test.annotations.Presubmit;
 import android.platform.test.annotations.RequiresFlagsEnabled;
+import android.util.Log;
 import android.util.SparseArray;
+import android.view.Display;
+import android.view.DisplayInfo;
 
 import androidx.test.runner.AndroidJUnit4;
 
+import com.android.dx.mockito.inline.extended.ExtendedMockito;
+import com.android.dx.mockito.inline.extended.StaticMockitoSession;
+
+import org.junit.AfterClass;
 import org.junit.Before;
+import org.junit.BeforeClass;
+import org.junit.Rule;
 import org.junit.Test;
+import org.junit.rules.RuleChain;
+import org.junit.rules.TemporaryFolder;
 import org.junit.runner.RunWith;
 import org.mockito.Mock;
+import org.mockito.quality.Strictness;
 
+import java.io.File;
+import java.io.IOException;
 import java.util.Comparator;
 import java.util.List;
 
@@ -57,6 +79,7 @@
 @RunWith(AndroidJUnit4.class)
 @RequiresFlagsEnabled(FLAG_MULTI_CROP)
 public class WallpaperCropperTest {
+    private static final String TAG = "WallpaperCropperTest";
 
     @Mock
     private WallpaperDisplayHelper mWallpaperDisplayHelper;
@@ -94,6 +117,8 @@
     private static final List<List<Point>> ALL_FOLDABLE_DISPLAYS = List.of(
             FOLDABLE_ONE, FOLDABLE_TWO, FOLDABLE_THREE, FOLDABLE_FOUR);
 
+    private static StaticMockitoSession sMockitoSession;
+
     private SparseArray<Point> mDisplaySizes = new SparseArray<>();
     private int mFolded = ORIENTATION_UNKNOWN;
     private int mFoldedRotated = ORIENTATION_UNKNOWN;
@@ -103,12 +128,53 @@
     private static final List<Integer> ALL_MODES = List.of(
             WallpaperCropper.ADD, WallpaperCropper.REMOVE, WallpaperCropper.BALANCE);
 
+    private final SparseArray<File> mTempDirs = new SparseArray<>();
+
+    private final TemporaryFolder mFolder = new TemporaryFolder();
+
+    @Rule
+    public RuleChain rules = RuleChain.outerRule(mFolder);
+
+    @BeforeClass
+    public static void setUpClass() {
+        sMockitoSession = mockitoSession()
+                .strictness(Strictness.LENIENT)
+                .spyStatic(WallpaperUtils.class)
+                .startMocking();
+    }
+
+    @AfterClass
+    public static void tearDownClass() {
+        if (sMockitoSession != null) {
+            sMockitoSession.finishMocking();
+            sMockitoSession = null;
+        }
+    }
+
     @Before
     public void setUp() {
         initMocks(this);
+        ExtendedMockito.doAnswer(invocation -> {
+            int userId = (invocation.getArgument(0));
+            return getWallpaperTestDir(userId);
+        }).when(() -> WallpaperUtils.getWallpaperDir(anyInt()));
+
         mWallpaperCropper = new WallpaperCropper(mWallpaperDisplayHelper);
     }
 
+    private File getWallpaperTestDir(int userId) {
+        File tempDir = mTempDirs.get(userId);
+        if (tempDir == null) {
+            try {
+                tempDir = mFolder.newFolder(String.valueOf(userId));
+                mTempDirs.append(userId, tempDir);
+            } catch (IOException e) {
+                Log.e(TAG, "getWallpaperTestDir failed at userId= " + userId);
+            }
+        }
+        return tempDir;
+    }
+
     private void setUpWithDisplays(List<Point> displaySizes) {
         mDisplaySizes = new SparseArray<>();
         displaySizes.forEach(size -> {
@@ -632,6 +698,134 @@
         }
     }
 
+    // Test isWallpaperCompatibleForDisplay always return true for the default display.
+    @Test
+    public void isWallpaperCompatibleForDisplay_defaultDisplay_returnTrue()
+            throws Exception {
+        DisplayInfo displayInfo = new DisplayInfo();
+        displayInfo.logicalWidth = 2560;
+        displayInfo.logicalHeight = 1044;
+        doReturn(displayInfo).when(mWallpaperDisplayHelper).getDisplayInfo(eq(DEFAULT_DISPLAY));
+        WallpaperData wallpaperData = createWallpaperData(/* isStockWallpaper = */ false,
+                new Point(100, 100));
+
+        assertThat(
+                mWallpaperCropper.isWallpaperCompatibleForDisplay(DEFAULT_DISPLAY,
+                        wallpaperData)).isTrue();
+    }
+
+    // Test isWallpaperCompatibleForDisplay always return true for the stock wallpaper.
+    @Test
+    public void isWallpaperCompatibleForDisplay_stockWallpaper_returnTrue()
+            throws Exception {
+        final int displayId = 2;
+        DisplayInfo displayInfo = new DisplayInfo();
+        displayInfo.logicalWidth = 2560;
+        displayInfo.logicalHeight = 1044;
+        doReturn(displayInfo).when(mWallpaperDisplayHelper).getDisplayInfo(eq(displayId));
+        WallpaperData wallpaperData = createWallpaperData(/* isStockWallpaper = */ true,
+                new Point(100, 100));
+
+        assertThat(
+                mWallpaperCropper.isWallpaperCompatibleForDisplay(displayId,
+                        wallpaperData)).isTrue();
+    }
+
+    // Test isWallpaperCompatibleForDisplay wallpaper is suitable for the display and wallpaper
+    // aspect ratio meets the hard-coded aspect ratio.
+    @Test
+    public void isWallpaperCompatibleForDisplay_wallpaperSizeSuitableForDisplayAndMeetAspectRatio_returnTrue()
+            throws Exception {
+        final int displayId = 2;
+        DisplayInfo displayInfo = new DisplayInfo();
+        displayInfo.logicalWidth = 2560;
+        displayInfo.logicalHeight = 1044;
+        doReturn(displayInfo).when(mWallpaperDisplayHelper).getDisplayInfo(eq(displayId));
+        WallpaperData wallpaperData = createWallpaperData(/* isStockWallpaper = */ false,
+                new Point(4000, 3000));
+
+        assertThat(
+                mWallpaperCropper.isWallpaperCompatibleForDisplay(displayId,
+                        wallpaperData)).isTrue();
+    }
+
+    // Test isWallpaperCompatibleForDisplay wallpaper is not suitable for the display and wallpaper
+    // aspect ratio meets the hard-coded aspect ratio.
+    @Test
+    public void isWallpaperCompatibleForDisplay_wallpaperSizeNotSuitableForDisplayAndMeetAspectRatio_returnFalse()
+            throws Exception {
+        final int displayId = 2;
+        DisplayInfo displayInfo = new DisplayInfo();
+        displayInfo.logicalWidth = 2560;
+        displayInfo.logicalHeight = 1044;
+        doReturn(displayInfo).when(mWallpaperDisplayHelper).getDisplayInfo(eq(displayId));
+        WallpaperData wallpaperData = createWallpaperData(/* isStockWallpaper = */ false,
+                new Point(1000, 500));
+
+        assertThat(
+                mWallpaperCropper.isWallpaperCompatibleForDisplay(displayId,
+                        wallpaperData)).isFalse();
+    }
+
+    // Test isWallpaperCompatibleForDisplay wallpaper is suitable for the display and wallpaper
+    // aspect ratio doesn't meet the hard-coded aspect ratio.
+    @Test
+    public void isWallpaperCompatibleForDisplay_wallpaperSizeSuitableForDisplayAndDoNotMeetAspectRatio_returnFalse()
+            throws Exception {
+        final int displayId = 2;
+        DisplayInfo displayInfo = new DisplayInfo();
+        displayInfo.logicalWidth = 2560;
+        displayInfo.logicalHeight = 1044;
+        doReturn(displayInfo).when(mWallpaperDisplayHelper).getDisplayInfo(eq(displayId));
+        WallpaperData wallpaperData = createWallpaperData(/* isStockWallpaper = */ false,
+                new Point(2000, 4000));
+
+        assertThat(
+                mWallpaperCropper.isWallpaperCompatibleForDisplay(displayId,
+                        wallpaperData)).isFalse();
+    }
+
+    // Test isWallpaperCompatibleForDisplay, portrait display, wallpaper is suitable for the display
+    // and wallpaper aspect ratio doesn't meet the hard-coded aspect ratio.
+    @Test
+    public void isWallpaperCompatibleForDisplay_portraitDisplay_wallpaperSizeSuitableForDisplayAndMeetAspectRatio_returnTrue()
+            throws Exception {
+        final int displayId = 2;
+        DisplayInfo displayInfo = new DisplayInfo();
+        displayInfo.logicalWidth = 1044;
+        displayInfo.logicalHeight = 2560;
+        doReturn(displayInfo).when(mWallpaperDisplayHelper).getDisplayInfo(eq(displayId));
+        WallpaperData wallpaperData = createWallpaperData(/* isStockWallpaper = */ false,
+                new Point(2000, 4000));
+
+        assertThat(
+                mWallpaperCropper.isWallpaperCompatibleForDisplay(displayId,
+                        wallpaperData)).isTrue();
+    }
+
+    private void mockDisplay(int displayId, Point displayResolution) {
+        final Display mockDisplay = mock(Display.class);
+        when(mockDisplay.getDisplayInfo(any(DisplayInfo.class))).thenAnswer(invocation -> {
+            DisplayInfo displayInfo = invocation.getArgument(0);
+            displayInfo.displayId = displayId;
+            displayInfo.logicalWidth = displayResolution.x;
+            displayInfo.logicalHeight = displayResolution.y;
+            return true;
+        });
+    }
+
+    private WallpaperData createWallpaperData(boolean isStockWallpaper, Point wallpaperSize)
+            throws Exception {
+        WallpaperData wallpaperData = new WallpaperData(USER_SYSTEM, FLAG_SYSTEM);
+        File wallpaperFile = wallpaperData.getWallpaperFile();
+        if (!isStockWallpaper) {
+            wallpaperFile.getParentFile().mkdirs();
+            wallpaperFile.createNewFile();
+        }
+        wallpaperData.cropHint.set(0, 0, wallpaperSize.x, wallpaperSize.y);
+        return wallpaperData;
+    }
+
     private static Rect centerOf(Rect container, Point point) {
         checkSubset(container, point);
         int diffWidth = container.width() - point.x;
diff --git a/services/tests/mockingservicestests/src/com/android/server/wallpaper/WallpaperManagerServiceTests.java b/services/tests/mockingservicestests/src/com/android/server/wallpaper/WallpaperManagerServiceTests.java
index bc04fd9..bada337 100644
--- a/services/tests/mockingservicestests/src/com/android/server/wallpaper/WallpaperManagerServiceTests.java
+++ b/services/tests/mockingservicestests/src/com/android/server/wallpaper/WallpaperManagerServiceTests.java
@@ -123,6 +123,7 @@
 import java.io.IOException;
 import java.nio.charset.StandardCharsets;
 import java.util.List;
+import java.util.Map;
 
 /**
  * Tests for the {@link WallpaperManagerService} class.
@@ -258,6 +259,8 @@
         spyOn(mIpm);
         spyOn(mResources);
         doReturn(true).when(mResources).getBoolean(eq(R.bool.config_isDesktopModeSupported));
+        doReturn(true).when(mResources).getBoolean(
+                eq(R.bool.config_canInternalDisplayHostDesktops));
         mService = new TestWallpaperManagerService(sContext);
         spyOn(mService);
         mService.systemReady();
@@ -742,7 +745,9 @@
         wallpaper.connection.mService = mockIWallpaperService;
         // GIVEN there are two displays: DEFAULT_DISPLAY, 2
         final int testDisplayId = 2;
-        setUpDisplays(List.of(DEFAULT_DISPLAY, testDisplayId));
+        setUpDisplays(Map.of(
+                DEFAULT_DISPLAY, true,
+                testDisplayId, true));
 
         // WHEN display ID, 2, is ready.
         WallpaperManagerInternal wallpaperManagerInternal = LocalServices.getService(
@@ -782,7 +787,9 @@
         lockWallpaper.connection.mService = mockIWallpaperService;
         // GIVEN there are two displays: DEFAULT_DISPLAY, 2
         final int testDisplayId = 2;
-        setUpDisplays(List.of(DEFAULT_DISPLAY, testDisplayId));
+        setUpDisplays(Map.of(
+                DEFAULT_DISPLAY, true,
+                testDisplayId, true));
 
         // WHEN display ID, 2, is ready.
         WallpaperManagerInternal wallpaperManagerInternal = LocalServices.getService(
@@ -823,13 +830,15 @@
             throws Exception {
         final int testUserId = USER_SYSTEM;
         mService.switchUser(testUserId, null);
+        mService.mLastWallpaper.connection.mWallpaper.cropHint.set(0, 0, 4000, 2250);
         IWallpaperService mockIWallpaperService = mock(IWallpaperService.class);
         mService.mFallbackWallpaper.connection.mService = mockIWallpaperService;
         // GIVEN there are two displays: DEFAULT_DISPLAY, 2
         final int testDisplayId = 2;
-        setUpDisplays(List.of(DEFAULT_DISPLAY, testDisplayId));
-        // GIVEN the wallpaper isn't compatible with display ID, 2
-        mService.removeWallpaperCompatibleDisplayForTest(testDisplayId);
+        setUpDisplays(Map.of(
+                DEFAULT_DISPLAY, true,
+                // Given the wallpaper is smaller thn the display resolution.
+                testDisplayId, false));
 
         // WHEN display ID, 2, is ready.
         WallpaperManagerInternal wallpaperManagerInternal = LocalServices.getService(
@@ -868,7 +877,10 @@
         wallpaper.connection.mService = mockIWallpaperService;
         // GIVEN there are two displays: DEFAULT_DISPLAY, 2
         final int testDisplayId = 2;
-        setUpDisplays(List.of(DEFAULT_DISPLAY, testDisplayId));
+        mService.mLastWallpaper.connection.mWallpaper.cropHint.set(0, 0, 4000, 2250);
+        setUpDisplays(Map.of(
+                DEFAULT_DISPLAY, true,
+                testDisplayId, true));
         // GIVEN wallpaper connections have been established for display ID, 2.
         WallpaperManagerInternal wallpaperManagerInternal = LocalServices.getService(
                 WallpaperManagerInternal.class);
@@ -906,7 +918,9 @@
         lockWallpaper.connection.mService = mockIWallpaperService;
         // GIVEN there are two displays: DEFAULT_DISPLAY, 2
         final int testDisplayId = 2;
-        setUpDisplays(List.of(DEFAULT_DISPLAY, testDisplayId));
+        setUpDisplays(Map.of(
+                DEFAULT_DISPLAY, true,
+                testDisplayId, true));
         // GIVEN wallpaper connections have been established for display ID, 2.
         WallpaperManagerInternal wallpaperManagerInternal = LocalServices.getService(
                 WallpaperManagerInternal.class);
@@ -940,9 +954,9 @@
         mService.mFallbackWallpaper.connection.mService = mockIWallpaperService;
         // GIVEN there are two displays: DEFAULT_DISPLAY, 2
         final int testDisplayId = 2;
-        setUpDisplays(List.of(DEFAULT_DISPLAY, testDisplayId));
-        // GIVEN display ID, 2, is incompatible with the wallpaper.
-        mService.removeWallpaperCompatibleDisplayForTest(testDisplayId);
+        setUpDisplays(Map.of(
+                DEFAULT_DISPLAY, true,
+                testDisplayId, false));
         // GIVEN wallpaper connections have been established for display ID, 2.
         WallpaperManagerInternal wallpaperManagerInternal = LocalServices.getService(
                 WallpaperManagerInternal.class);
@@ -989,7 +1003,9 @@
         wallpaper.connection.mService = mockIWallpaperService;
         // GIVEN there are two displays: DEFAULT_DISPLAY, 2
         final int testDisplayId = 2;
-        setUpDisplays(List.of(DEFAULT_DISPLAY, testDisplayId));
+        setUpDisplays(Map.of(
+                DEFAULT_DISPLAY, true,
+                testDisplayId, true));
         // GIVEN wallpaper connections have been established for displayID, 2.
         WallpaperManagerInternal wallpaperManagerInternal = LocalServices.getService(
                 WallpaperManagerInternal.class);
@@ -1023,7 +1039,9 @@
         lockWallpaper.connection.mService = mockIWallpaperService;
         // GIVEN there are two displays: DEFAULT_DISPLAY, 2
         final int testDisplayId = 2;
-        setUpDisplays(List.of(DEFAULT_DISPLAY, testDisplayId));
+        setUpDisplays(Map.of(
+                DEFAULT_DISPLAY, true,
+                testDisplayId, true));
         // GIVEN wallpaper connections have been established for displayID, 2.
         WallpaperManagerInternal wallpaperManagerInternal = LocalServices.getService(
                 WallpaperManagerInternal.class);
@@ -1051,12 +1069,14 @@
     @Test
     @EnableFlags(Flags.FLAG_ENABLE_CONNECTED_DISPLAYS_WALLPAPER)
     public void deviceBooted_multiDisplays_shouldHaveExpectedConnections() {
+        final int testUserId = USER_SYSTEM;
         final int incompatibleDisplayId = 2;
         final int compatibleDisplayId = 3;
-        setUpDisplays(List.of(DEFAULT_DISPLAY, incompatibleDisplayId, compatibleDisplayId));
-        mService.removeWallpaperCompatibleDisplayForTest(incompatibleDisplayId);
+        setUpDisplays(Map.of(
+                DEFAULT_DISPLAY, true,
+                compatibleDisplayId, true,
+                incompatibleDisplayId, false));
 
-        final int testUserId = USER_SYSTEM;
         // After reboot, a switch user triggers the wallpapers initialization.
         mService.switchUser(testUserId, null);
 
@@ -1087,15 +1107,18 @@
     public void setWallpaperComponent_multiDisplays_displayBecomeCompatible_shouldHaveExpectedConnections() {
         final int display2 = 2;
         final int display3 = 3;
-        setUpDisplays(List.of(DEFAULT_DISPLAY, display2, display3));
-        mService.removeWallpaperCompatibleDisplayForTest(display2);
         final int testUserId = USER_SYSTEM;
+        setUpDisplays(Map.of(
+                DEFAULT_DISPLAY, true,
+                display2, false,
+                display3, true));
+
         mService.switchUser(testUserId, null);
+        doReturn(true).when(mService.mWallpaperCropper).isWallpaperCompatibleForDisplay(
+                eq(display2), any());
         // Switch to a test wallpaper and then image wallpaper later to simulate a wallpaper change.
         mService.setWallpaperComponent(TEST_WALLPAPER_COMPONENT, sContext.getOpPackageName(),
                 FLAG_SYSTEM | FLAG_LOCK, testUserId);
-        mService.addWallpaperCompatibleDisplayForTest(display2);
-
         mService.setWallpaperComponent(sImageWallpaperComponentName, sContext.getOpPackageName(),
                 FLAG_SYSTEM | FLAG_LOCK, testUserId);
 
@@ -1124,15 +1147,19 @@
     public void setWallpaperComponent_multiDisplays_displayBecomeIncompatible_shouldHaveExpectedConnections() {
         final int display2 = 2;
         final int display3 = 3;
-        setUpDisplays(List.of(DEFAULT_DISPLAY, display2, display3));
-        mService.removeWallpaperCompatibleDisplayForTest(display2);
         final int testUserId = USER_SYSTEM;
+        setUpDisplays(Map.of(
+                DEFAULT_DISPLAY, true,
+                display2, true,
+                display3, true));
         mService.switchUser(testUserId, null);
+        doReturn(false).when(mService.mWallpaperCropper).isWallpaperCompatibleForDisplay(
+                eq(display2), any());
+        doReturn(false).when(mService.mWallpaperCropper).isWallpaperCompatibleForDisplay(
+                eq(display3), any());
         // Switch to a test wallpaper and then image wallpaper later to simulate a wallpaper change.
         mService.setWallpaperComponent(TEST_WALLPAPER_COMPONENT, sContext.getOpPackageName(),
                 FLAG_SYSTEM | FLAG_LOCK, testUserId);
-        mService.removeWallpaperCompatibleDisplayForTest(display3);
-
         mService.setWallpaperComponent(sImageWallpaperComponentName, sContext.getOpPackageName(),
                 FLAG_SYSTEM | FLAG_LOCK, testUserId);
 
@@ -1159,14 +1186,15 @@
     public void setWallpaperComponent_systemAndLockWallpapers_multiDisplays_shouldHaveExpectedConnections() {
         final int incompatibleDisplayId = 2;
         final int compatibleDisplayId = 3;
-        setUpDisplays(List.of(DEFAULT_DISPLAY, incompatibleDisplayId, compatibleDisplayId));
+        setUpDisplays(Map.of(
+                DEFAULT_DISPLAY, true,
+                incompatibleDisplayId, false,
+                compatibleDisplayId, true));
         final int testUserId = USER_SYSTEM;
         mService.switchUser(testUserId, null);
         // Switch to a test wallpaper and then image wallpaper later to simulate a wallpaper change.
         mService.setWallpaperComponent(TEST_WALLPAPER_COMPONENT, sContext.getOpPackageName(),
                 FLAG_SYSTEM | FLAG_LOCK, testUserId);
-        mService.removeWallpaperCompatibleDisplayForTest(incompatibleDisplayId);
-
         mService.setWallpaperComponent(sImageWallpaperComponentName, sContext.getOpPackageName(),
                 FLAG_SYSTEM, testUserId);
 
@@ -1258,21 +1286,27 @@
      * to return them. It also sets up the {@link WindowManagerInternal} to indicate that all
      * displays support home.
      *
-     * @param displayIds A list of display IDs to create mock displays for.
+     * @param displayIdsToWallpaperCompatibility A map of display IDs to wallpaper compatibility.
      */
-    private void setUpDisplays(List<Integer> displayIds) {
+    private void setUpDisplays(Map<Integer, Boolean> displayIdsToWallpaperCompatibility) {
+        spyOn(mService.mWallpaperCropper);
         doReturn(true).when(sWindowManagerInternal).isHomeSupportedOnDisplay(anyInt());
 
-        Display[] mockDisplays = new Display[displayIds.size()];
-        for (int i = 0; i < displayIds.size(); i++) {
-            final int displayId = displayIds.get(i);
+        Display[] mockDisplays = new Display[displayIdsToWallpaperCompatibility.size()];
+        int counter = 0;
+        for (Map.Entry<Integer, Boolean> entry : displayIdsToWallpaperCompatibility.entrySet()) {
+            final int displayId = entry.getKey();
+            final boolean compatibleWithWallpaper = entry.getValue();
             final Display mockDisplay = mock(Display.class);
-            mockDisplays[i] = mockDisplay;
+            mockDisplays[counter] = mockDisplay;
             doReturn(DISPLAY_SIZE_DIMENSION).when(mockDisplay).getMaximumSizeDimension();
             doReturn(mockDisplay).when(mDisplayManager).getDisplay(eq(displayId));
             doReturn(displayId).when(mockDisplay).getDisplayId();
             doReturn(true).when(mockDisplay).hasAccess(anyInt());
-            mService.addWallpaperCompatibleDisplayForTest(displayId);
+            doReturn(compatibleWithWallpaper).when(
+                    mService.mWallpaperCropper).isWallpaperCompatibleForDisplay(eq(displayId),
+                    any());
+            counter++;
         }
 
         doReturn(mockDisplays).when(mDisplayManager).getDisplays();
@@ -1295,6 +1329,4 @@
             assertEquals(pfdContents, fileContents);
         }
     }
-
-
 }
diff --git a/services/tests/powerstatstests/src/com/android/server/power/stats/processor/AggregatedPowerStatsTest.java b/services/tests/powerstatstests/src/com/android/server/power/stats/processor/AggregatedPowerStatsTest.java
index 0e73329..978fd1a 100644
--- a/services/tests/powerstatstests/src/com/android/server/power/stats/processor/AggregatedPowerStatsTest.java
+++ b/services/tests/powerstatstests/src/com/android/server/power/stats/processor/AggregatedPowerStatsTest.java
@@ -18,6 +18,7 @@
 
 import static com.google.common.truth.Truth.assertThat;
 
+import android.annotation.SuppressLint;
 import android.os.BatteryConsumer;
 import android.os.PersistableBundle;
 import android.util.SparseArray;
@@ -280,6 +281,7 @@
                 .isEqualTo(new long[]{250, 300});
     }
 
+    @SuppressLint("CheckResult")
     private static long[] getDeviceStats(
             AggregatedPowerStats stats, int powerComponentId,
             int... states) {
@@ -290,6 +292,7 @@
         return out;
     }
 
+    @SuppressLint("CheckResult")
     private static long[] getStateStats(
             AggregatedPowerStats stats, int key, int... states) {
         PowerComponentAggregatedPowerStats powerComponentStats =
@@ -299,6 +302,7 @@
         return out;
     }
 
+    @SuppressLint("CheckResult")
     private static long[] getUidDeviceStats(
             AggregatedPowerStats stats, int powerComponentId,
             int uid, int... states) {
diff --git a/services/tests/powerstatstests/src/com/android/server/power/stats/processor/AmbientDisplayPowerStatsProcessorTest.java b/services/tests/powerstatstests/src/com/android/server/power/stats/processor/AmbientDisplayPowerStatsProcessorTest.java
index 21e615f..58e9d1e 100644
--- a/services/tests/powerstatstests/src/com/android/server/power/stats/processor/AmbientDisplayPowerStatsProcessorTest.java
+++ b/services/tests/powerstatstests/src/com/android/server/power/stats/processor/AmbientDisplayPowerStatsProcessorTest.java
@@ -27,6 +27,7 @@
 
 import static org.mockito.Mockito.when;
 
+import android.annotation.SuppressLint;
 import android.hardware.power.stats.EnergyConsumerType;
 import android.os.BatteryConsumer;
 import android.os.Handler;
@@ -166,6 +167,7 @@
         return stats.getPowerComponentStats(BatteryConsumer.POWER_COMPONENT_AMBIENT_DISPLAY);
     }
 
+    @SuppressLint("CheckResult")
     private void assertPowerEstimate(
             PowerComponentAggregatedPowerStats aggregatedStats,
             int powerState, int screenState, double expectedPowerEstimate) {
diff --git a/services/tests/powerstatstests/src/com/android/server/power/stats/processor/BasePowerStatsProcessorTest.java b/services/tests/powerstatstests/src/com/android/server/power/stats/processor/BasePowerStatsProcessorTest.java
index cca6033..58784d7 100644
--- a/services/tests/powerstatstests/src/com/android/server/power/stats/processor/BasePowerStatsProcessorTest.java
+++ b/services/tests/powerstatstests/src/com/android/server/power/stats/processor/BasePowerStatsProcessorTest.java
@@ -34,6 +34,7 @@
 
 import static org.mockito.Mockito.mock;
 
+import android.annotation.SuppressLint;
 import android.os.BatteryConsumer;
 import android.os.BatteryUsageStats;
 import android.os.Process;
@@ -68,6 +69,7 @@
                 .setProcessorSupplier(() -> new BasePowerStatsProcessor(() -> 4000));
     }
 
+    @SuppressLint("CheckResult")
     @Test
     public void processPowerStats() {
         AggregatedPowerStats aggregatedPowerStats = prepareAggregatedPowerStats(true);
@@ -95,9 +97,11 @@
         stats.getUidStats(uidStats, APP_UID1,
                 states(POWER_STATE_BATTERY, SCREEN_STATE_OTHER, PROCESS_STATE_FOREGROUND_SERVICE));
         assertThat(statsLayout.getUidUsageDuration(uidStats)).isEqualTo(5000);
-        stats.getUidStats(uidStats, APP_UID1,
+        boolean nonZero = stats.getUidStats(uidStats, APP_UID1,
                 states(POWER_STATE_BATTERY, SCREEN_STATE_OTHER, PROCESS_STATE_UNSPECIFIED));
-        assertThat(statsLayout.getUidUsageDuration(uidStats)).isEqualTo(0);
+        if (nonZero) {
+            assertThat(statsLayout.getUidUsageDuration(uidStats)).isEqualTo(0);
+        }
 
         stats.getUidStats(uidStats, APP_UID2,
                 states(POWER_STATE_BATTERY, SCREEN_STATE_ON, PROCESS_STATE_CACHED));
@@ -105,11 +109,14 @@
         stats.getUidStats(uidStats, APP_UID2,
                 states(POWER_STATE_BATTERY, SCREEN_STATE_OTHER, PROCESS_STATE_CACHED));
         assertThat(statsLayout.getUidUsageDuration(uidStats)).isEqualTo(8500);
-        stats.getUidStats(uidStats, APP_UID2,
+        nonZero = stats.getUidStats(uidStats, APP_UID2,
                 states(POWER_STATE_BATTERY, SCREEN_STATE_OTHER, PROCESS_STATE_UNSPECIFIED));
-        assertThat(statsLayout.getUidUsageDuration(uidStats)).isEqualTo(0);
+        if (nonZero) {
+            assertThat(statsLayout.getUidUsageDuration(uidStats)).isEqualTo(0);
+        }
     }
 
+    @SuppressLint("CheckResult")
     @Test
     public void fuelgaugeAvailable() {
         AggregatedPowerStats aggregatedPowerStats = prepareAggregatedPowerStats(true);
@@ -138,6 +145,7 @@
         assertThat(dischargeDuration).isWithin(5).of(6000);
     }
 
+    @SuppressLint("CheckResult")
     @Test
     public void fuelgaugeUnavailable() {
         AggregatedPowerStats aggregatedPowerStats = prepareAggregatedPowerStats(false);
diff --git a/services/tests/powerstatstests/src/com/android/server/power/stats/processor/BinaryStatePowerStatsProcessorTest.java b/services/tests/powerstatstests/src/com/android/server/power/stats/processor/BinaryStatePowerStatsProcessorTest.java
index 2ff7dde..e6e7f6e 100644
--- a/services/tests/powerstatstests/src/com/android/server/power/stats/processor/BinaryStatePowerStatsProcessorTest.java
+++ b/services/tests/powerstatstests/src/com/android/server/power/stats/processor/BinaryStatePowerStatsProcessorTest.java
@@ -30,6 +30,7 @@
 
 import static com.google.common.truth.Truth.assertThat;
 
+import android.annotation.SuppressLint;
 import android.os.BatteryConsumer;
 import android.os.BatteryStats;
 import android.os.PersistableBundle;
@@ -74,6 +75,7 @@
         }
     }
 
+    @SuppressLint("CheckResult")
     @Test
     public void powerProfileModel() {
         BinaryStatePowerStatsLayout statsLayout = new BinaryStatePowerStatsLayout();
@@ -138,12 +140,14 @@
         assertThat(statsLayout.getUidPowerEstimate(uidStats))
                 .isWithin(PRECISION).of(expectedPower2);
 
-        stats.getUidStats(uidStats, APP_UID2,
-                states(POWER_STATE_OTHER, SCREEN_STATE_ON, PROCESS_STATE_CACHED));
-        assertThat(statsLayout.getUidPowerEstimate(uidStats))
-                .isWithin(PRECISION).of(0);
+        if (stats.getUidStats(uidStats, APP_UID2,
+                states(POWER_STATE_OTHER, SCREEN_STATE_ON, PROCESS_STATE_CACHED))) {
+            assertThat(statsLayout.getUidPowerEstimate(uidStats))
+                    .isWithin(PRECISION).of(0);
+        }
     }
 
+    @SuppressLint("CheckResult")
     @Test
     public void energyConsumerModel() {
         BinaryStatePowerStatsLayout
@@ -232,10 +236,11 @@
         assertThat(statsLayout.getUidPowerEstimate(uidStats))
                 .isWithin(PRECISION).of(expectedPower2);
 
-        stats.getUidStats(uidStats, APP_UID2,
-                states(POWER_STATE_OTHER, SCREEN_STATE_ON, PROCESS_STATE_CACHED));
-        assertThat(statsLayout.getUidPowerEstimate(uidStats))
-                .isWithin(PRECISION).of(0);
+        if (stats.getUidStats(uidStats, APP_UID2,
+                states(POWER_STATE_OTHER, SCREEN_STATE_ON, PROCESS_STATE_CACHED))) {
+            assertThat(statsLayout.getUidPowerEstimate(uidStats))
+                    .isWithin(PRECISION).of(0);
+        }
     }
 
 
diff --git a/services/tests/powerstatstests/src/com/android/server/power/stats/processor/BluetoothPowerStatsProcessorTest.java b/services/tests/powerstatstests/src/com/android/server/power/stats/processor/BluetoothPowerStatsProcessorTest.java
index 6013186..6d7119d 100644
--- a/services/tests/powerstatstests/src/com/android/server/power/stats/processor/BluetoothPowerStatsProcessorTest.java
+++ b/services/tests/powerstatstests/src/com/android/server/power/stats/processor/BluetoothPowerStatsProcessorTest.java
@@ -33,6 +33,7 @@
 import static org.mockito.Mockito.mock;
 import static org.mockito.Mockito.when;
 
+import android.annotation.SuppressLint;
 import android.bluetooth.BluetoothActivityEnergyInfo;
 import android.bluetooth.BluetoothAdapter;
 import android.bluetooth.UidTraffic;
@@ -161,6 +162,7 @@
         when(mPackageManager.hasSystemFeature(PackageManager.FEATURE_BLUETOOTH)).thenReturn(true);
     }
 
+    @SuppressLint("CheckResult")
     @Test
     public void powerProfileModel_mostlyDataTransfer() {
         // No power monitoring hardware
@@ -262,6 +264,7 @@
                 .isWithin(PRECISION).of(expectedPower2 * 0.75);
     }
 
+    @SuppressLint("CheckResult")
     @Test
     public void powerProfileModel_mostlyScan() {
         // No power monitoring hardware
@@ -361,6 +364,7 @@
                 .isWithin(PRECISION).of(expectedPower2 * 0.75);
     }
 
+    @SuppressLint("CheckResult")
     @Test
     public void consumedEnergyModel() {
         when(mConsumedEnergyRetriever.getVoltageMv()).thenReturn(VOLTAGE_MV);
diff --git a/services/tests/powerstatstests/src/com/android/server/power/stats/processor/CameraPowerStatsTest.java b/services/tests/powerstatstests/src/com/android/server/power/stats/processor/CameraPowerStatsTest.java
index 23642de..a959632 100644
--- a/services/tests/powerstatstests/src/com/android/server/power/stats/processor/CameraPowerStatsTest.java
+++ b/services/tests/powerstatstests/src/com/android/server/power/stats/processor/CameraPowerStatsTest.java
@@ -33,6 +33,7 @@
 import static org.mockito.ArgumentMatchers.eq;
 import static org.mockito.Mockito.when;
 
+import android.annotation.SuppressLint;
 import android.hardware.power.stats.EnergyConsumerResult;
 import android.hardware.power.stats.EnergyConsumerType;
 import android.os.BatteryConsumer;
@@ -117,6 +118,7 @@
         mMonotonicClock = new MonotonicClock(0, mStatsRule.getMockClock());
     }
 
+    @SuppressLint("CheckResult")
     @Test
     public void energyConsumerModel() {
         when(mConsumedEnergyRetriever.getVoltageMv()).thenReturn(VOLTAGE_MV);
@@ -211,10 +213,11 @@
         assertThat(statsLayout.getUidPowerEstimate(uidStats))
                 .isWithin(PRECISION).of(expectedPower2);
 
-        stats.getUidStats(uidStats, APP_UID2,
-                states(POWER_STATE_OTHER, SCREEN_STATE_ON, PROCESS_STATE_CACHED));
-        assertThat(statsLayout.getUidPowerEstimate(uidStats))
-                .isWithin(PRECISION).of(0);
+        if (stats.getUidStats(uidStats, APP_UID2,
+                states(POWER_STATE_OTHER, SCREEN_STATE_ON, PROCESS_STATE_CACHED))) {
+            assertThat(statsLayout.getUidPowerEstimate(uidStats))
+                    .isWithin(PRECISION).of(0);
+        }
     }
 
     private BatteryStats.HistoryItem buildHistoryItem(int timestamp, boolean stateOn,
diff --git a/services/tests/powerstatstests/src/com/android/server/power/stats/processor/CustomEnergyConsumerPowerStatsTest.java b/services/tests/powerstatstests/src/com/android/server/power/stats/processor/CustomEnergyConsumerPowerStatsTest.java
index 42baba7..a421675 100644
--- a/services/tests/powerstatstests/src/com/android/server/power/stats/processor/CustomEnergyConsumerPowerStatsTest.java
+++ b/services/tests/powerstatstests/src/com/android/server/power/stats/processor/CustomEnergyConsumerPowerStatsTest.java
@@ -33,6 +33,7 @@
 
 import static org.mockito.Mockito.when;
 
+import android.annotation.SuppressLint;
 import android.hardware.power.stats.EnergyConsumerAttribution;
 import android.hardware.power.stats.EnergyConsumerResult;
 import android.hardware.power.stats.EnergyConsumerType;
@@ -154,6 +155,7 @@
                 .isEqualTo(6000);
     }
 
+    @SuppressLint("CheckResult")
     @Test
     public void processStats() throws Exception {
         AggregatedPowerStats aggregatedPowerStats = createAggregatedPowerStats();
diff --git a/services/tests/powerstatstests/src/com/android/server/power/stats/processor/GnssPowerStatsTest.java b/services/tests/powerstatstests/src/com/android/server/power/stats/processor/GnssPowerStatsTest.java
index c63267e..b4f2113 100644
--- a/services/tests/powerstatstests/src/com/android/server/power/stats/processor/GnssPowerStatsTest.java
+++ b/services/tests/powerstatstests/src/com/android/server/power/stats/processor/GnssPowerStatsTest.java
@@ -33,6 +33,7 @@
 import static org.mockito.ArgumentMatchers.eq;
 import static org.mockito.Mockito.when;
 
+import android.annotation.SuppressLint;
 import android.hardware.power.stats.EnergyConsumerResult;
 import android.hardware.power.stats.EnergyConsumerType;
 import android.location.GnssSignalQuality;
@@ -122,6 +123,7 @@
         mHistoryItem.clear();
     }
 
+    @SuppressLint("CheckResult")
     @Test
     public void powerProfileModel() {
         // ODPM unsupported
@@ -206,12 +208,14 @@
         assertThat(statsLayout.getUidPowerEstimate(uidStats))
                 .isWithin(PRECISION).of(0.51111);
 
-        stats.getUidStats(uidStats, APP_UID2,
-                states(POWER_STATE_OTHER, SCREEN_STATE_ON, PROCESS_STATE_CACHED));
-        assertThat(statsLayout.getUidPowerEstimate(uidStats))
-                .isWithin(PRECISION).of(0);
+        if (stats.getUidStats(uidStats, APP_UID2,
+                states(POWER_STATE_OTHER, SCREEN_STATE_ON, PROCESS_STATE_CACHED))) {
+            assertThat(statsLayout.getUidPowerEstimate(uidStats))
+                    .isWithin(PRECISION).of(0);
+        }
     }
 
+    @SuppressLint("CheckResult")
     @Test
     public void initialStateGnssOn() {
         // ODPM unsupported
@@ -285,12 +289,14 @@
         assertThat(statsLayout.getUidPowerEstimate(uidStats))
                 .isWithin(PRECISION).of(0.51111);
 
-        stats.getUidStats(uidStats, APP_UID2,
-                states(POWER_STATE_OTHER, SCREEN_STATE_ON, PROCESS_STATE_CACHED));
-        assertThat(statsLayout.getUidPowerEstimate(uidStats))
-                .isWithin(PRECISION).of(0);
+        if (stats.getUidStats(uidStats, APP_UID2,
+                states(POWER_STATE_OTHER, SCREEN_STATE_ON, PROCESS_STATE_CACHED))) {
+            assertThat(statsLayout.getUidPowerEstimate(uidStats))
+                    .isWithin(PRECISION).of(0);
+        }
     }
 
+    @SuppressLint("CheckResult")
     @Test
     public void energyConsumerModel() {
         when(mConsumedEnergyRetriever.getVoltageMv()).thenReturn(VOLTAGE_MV);
@@ -386,10 +392,11 @@
         assertThat(statsLayout.getUidPowerEstimate(uidStats))
                 .isWithin(PRECISION).of(expectedPower2);
 
-        stats.getUidStats(uidStats, APP_UID2,
-                states(POWER_STATE_OTHER, SCREEN_STATE_ON, PROCESS_STATE_CACHED));
-        assertThat(statsLayout.getUidPowerEstimate(uidStats))
-                .isWithin(PRECISION).of(0);
+        if (stats.getUidStats(uidStats, APP_UID2,
+                states(POWER_STATE_OTHER, SCREEN_STATE_ON, PROCESS_STATE_CACHED))) {
+            assertThat(statsLayout.getUidPowerEstimate(uidStats))
+                    .isWithin(PRECISION).of(0);
+        }
     }
 
     private BatteryStats.HistoryItem buildHistoryItemInitialStateGpsOn(long timestamp) {
diff --git a/services/tests/powerstatstests/src/com/android/server/power/stats/processor/MobileRadioPowerStatsProcessorTest.java b/services/tests/powerstatstests/src/com/android/server/power/stats/processor/MobileRadioPowerStatsProcessorTest.java
index 6acd368..3dc4017 100644
--- a/services/tests/powerstatstests/src/com/android/server/power/stats/processor/MobileRadioPowerStatsProcessorTest.java
+++ b/services/tests/powerstatstests/src/com/android/server/power/stats/processor/MobileRadioPowerStatsProcessorTest.java
@@ -39,6 +39,7 @@
 import static org.mockito.Mockito.when;
 
 import android.annotation.Nullable;
+import android.annotation.SuppressLint;
 import android.content.Context;
 import android.content.pm.PackageManager;
 import android.hardware.power.stats.EnergyConsumerResult;
@@ -170,6 +171,7 @@
                 .thenAnswer(invocation -> invocation.getArgument(0));
     }
 
+    @SuppressLint("CheckResult")
     @Test
     public void powerProfileModel() {
         // No power monitoring hardware
diff --git a/services/tests/powerstatstests/src/com/android/server/power/stats/processor/MultiStateStatsTest.java b/services/tests/powerstatstests/src/com/android/server/power/stats/processor/MultiStateStatsTest.java
index 3b614bd..9abb06f 100644
--- a/services/tests/powerstatstests/src/com/android/server/power/stats/processor/MultiStateStatsTest.java
+++ b/services/tests/powerstatstests/src/com/android/server/power/stats/processor/MultiStateStatsTest.java
@@ -21,6 +21,7 @@
 
 import static org.junit.Assert.assertThrows;
 
+import android.annotation.SuppressLint;
 import android.os.BatteryConsumer;
 
 import androidx.test.filters.SmallTest;
@@ -124,6 +125,7 @@
         assertThat(e.getMessage()).contains("40");
     }
 
+    @SuppressLint("CheckResult")
     @Test
     public void multiStateStats_aggregation() {
         MultiStateStats.Factory factory = makeFactory(true, true, false);
@@ -159,9 +161,9 @@
         // (400 - 100) * 0 + (600 - 400) * 0.5
         assertThat(stats).isEqualTo(new long[]{100, 100});
 
-        multiStateStats.getStats(stats, new int[]{1, BatteryConsumer.PROCESS_STATE_BACKGROUND, 0});
         // Never been in this composite state
-        assertThat(stats).isEqualTo(new long[]{0, 0});
+        assertThat(multiStateStats.getStats(stats,
+                new int[]{1, BatteryConsumer.PROCESS_STATE_BACKGROUND, 0})).isFalse();
     }
 
     @Test
diff --git a/services/tests/powerstatstests/src/com/android/server/power/stats/processor/PhoneCallPowerStatsProcessorTest.java b/services/tests/powerstatstests/src/com/android/server/power/stats/processor/PhoneCallPowerStatsProcessorTest.java
index a20274f..2f742d7 100644
--- a/services/tests/powerstatstests/src/com/android/server/power/stats/processor/PhoneCallPowerStatsProcessorTest.java
+++ b/services/tests/powerstatstests/src/com/android/server/power/stats/processor/PhoneCallPowerStatsProcessorTest.java
@@ -29,6 +29,7 @@
 import static org.mockito.Mockito.doAnswer;
 import static org.mockito.Mockito.when;
 
+import android.annotation.SuppressLint;
 import android.content.Context;
 import android.content.pm.PackageManager;
 import android.hardware.power.stats.EnergyConsumerType;
@@ -159,6 +160,7 @@
         mStatsRule.setTestPowerProfile("power_profile_test_legacy_modem");
     }
 
+    @SuppressLint("CheckResult")
     @Test
     public void copyEstimatesFromMobileRadioPowerStats() {
         AggregatedPowerStatsConfig config = new AggregatedPowerStatsConfig();
diff --git a/services/tests/powerstatstests/src/com/android/server/power/stats/processor/ScreenPowerStatsProcessorTest.java b/services/tests/powerstatstests/src/com/android/server/power/stats/processor/ScreenPowerStatsProcessorTest.java
index 1852165..31456a15 100644
--- a/services/tests/powerstatstests/src/com/android/server/power/stats/processor/ScreenPowerStatsProcessorTest.java
+++ b/services/tests/powerstatstests/src/com/android/server/power/stats/processor/ScreenPowerStatsProcessorTest.java
@@ -29,6 +29,7 @@
 import static org.mockito.Mockito.doAnswer;
 import static org.mockito.Mockito.when;
 
+import android.annotation.SuppressLint;
 import android.hardware.power.stats.EnergyConsumerResult;
 import android.hardware.power.stats.EnergyConsumerType;
 import android.os.BatteryConsumer;
@@ -278,6 +279,7 @@
                 .of(expectedDozePowerEstimate);
     }
 
+    @SuppressLint("CheckResult")
     private void assertUidPowerEstimate(
             PowerComponentAggregatedPowerStats aggregatedStats, int uid,
             int powerState, int screenState, double expectedScreenPowerEstimate) {
diff --git a/services/tests/powerstatstests/src/com/android/server/power/stats/processor/SensorPowerStatsProcessorTest.java b/services/tests/powerstatstests/src/com/android/server/power/stats/processor/SensorPowerStatsProcessorTest.java
index d972604..c2f01d1 100644
--- a/services/tests/powerstatstests/src/com/android/server/power/stats/processor/SensorPowerStatsProcessorTest.java
+++ b/services/tests/powerstatstests/src/com/android/server/power/stats/processor/SensorPowerStatsProcessorTest.java
@@ -33,6 +33,7 @@
 import static org.mockito.Mockito.mock;
 import static org.mockito.Mockito.when;
 
+import android.annotation.SuppressLint;
 import android.hardware.Sensor;
 import android.hardware.SensorManager;
 import android.hardware.input.InputSensorInfo;
@@ -91,6 +92,7 @@
                 List.of(sensor1, sensor2, sensor3));
     }
 
+    @SuppressLint("CheckResult")
     @Test
     public void testPowerEstimation() {
         PowerComponentAggregatedPowerStats stats = createAggregatedPowerStats(
diff --git a/services/tests/powerstatstests/src/com/android/server/power/stats/processor/WakelockPowerStatsProcessorTest.java b/services/tests/powerstatstests/src/com/android/server/power/stats/processor/WakelockPowerStatsProcessorTest.java
index 8257d71..5ac7216 100644
--- a/services/tests/powerstatstests/src/com/android/server/power/stats/processor/WakelockPowerStatsProcessorTest.java
+++ b/services/tests/powerstatstests/src/com/android/server/power/stats/processor/WakelockPowerStatsProcessorTest.java
@@ -31,6 +31,7 @@
 
 import static org.mockito.Mockito.mock;
 
+import android.annotation.SuppressLint;
 import android.os.BatteryConsumer;
 import android.os.PersistableBundle;
 import android.os.Process;
@@ -123,6 +124,7 @@
         return history;
     }
 
+    @SuppressLint("CheckResult")
     private void assertAggregatedPowerStats(AggregatedPowerStats aggregatedPowerStats) {
         PowerComponentAggregatedPowerStats stats =
                 aggregatedPowerStats.getPowerComponentStats(POWER_COMPONENT_WAKELOCK);
diff --git a/services/tests/powerstatstests/src/com/android/server/power/stats/processor/WifiPowerStatsProcessorTest.java b/services/tests/powerstatstests/src/com/android/server/power/stats/processor/WifiPowerStatsProcessorTest.java
index bd92a84..e36056a 100644
--- a/services/tests/powerstatstests/src/com/android/server/power/stats/processor/WifiPowerStatsProcessorTest.java
+++ b/services/tests/powerstatstests/src/com/android/server/power/stats/processor/WifiPowerStatsProcessorTest.java
@@ -39,6 +39,7 @@
 import static org.mockito.Mockito.when;
 
 import android.annotation.Nullable;
+import android.annotation.SuppressLint;
 import android.content.Context;
 import android.content.pm.PackageManager;
 import android.hardware.power.stats.EnergyConsumerResult;
@@ -198,6 +199,7 @@
         mBatteryStats = mStatsRule.getBatteryStats();
     }
 
+    @SuppressLint("CheckResult")
     @Test
     public void powerProfileModel_powerController() {
         when(mWifiManager.isEnhancedPowerReportingSupported()).thenReturn(true);
@@ -310,6 +312,7 @@
                 .isWithin(PRECISION).of(expectedPower2 * 0.75);
     }
 
+    @SuppressLint("CheckResult")
     @Test
     public void consumedEnergyModel_powerController() {
         when(mWifiManager.isEnhancedPowerReportingSupported()).thenReturn(true);
@@ -424,6 +427,7 @@
                 .isWithin(PRECISION).of(expectedPower2 * 0.75);
     }
 
+    @SuppressLint("CheckResult")
     @Test
     public void powerProfileModel_noPowerController() {
         when(mWifiManager.isEnhancedPowerReportingSupported()).thenReturn(false);
diff --git a/services/tests/servicestests/Android.bp b/services/tests/servicestests/Android.bp
index d702cae..009ce88 100644
--- a/services/tests/servicestests/Android.bp
+++ b/services/tests/servicestests/Android.bp
@@ -33,6 +33,9 @@
         "test-apps/DisplayManagerTestApp/src/**/*.java",
     ],
 
+    kotlincflags: [
+        "-Werror",
+    ],
     static_libs: [
         "a11ychecker",
         "aatf",
diff --git a/services/tests/servicestests/src/com/android/server/accessibility/autoclick/AutoclickControllerTest.java b/services/tests/servicestests/src/com/android/server/accessibility/autoclick/AutoclickControllerTest.java
index 7f60caa..0745c68 100644
--- a/services/tests/servicestests/src/com/android/server/accessibility/autoclick/AutoclickControllerTest.java
+++ b/services/tests/servicestests/src/com/android/server/accessibility/autoclick/AutoclickControllerTest.java
@@ -25,6 +25,7 @@
 import static org.mockito.ArgumentMatchers.any;
 import static org.mockito.Mockito.mock;
 import static org.mockito.Mockito.verify;
+import static org.mockito.Mockito.when;
 
 import android.content.Context;
 import android.platform.test.annotations.DisableFlags;
@@ -400,6 +401,46 @@
                 .isNotEqualTo(initialScheduledTime);
     }
 
+    @Test
+    @EnableFlags(com.android.server.accessibility.Flags.FLAG_ENABLE_AUTOCLICK_INDICATOR)
+    public void pauseButton_flagOn_clickNotTriggeredWhenPaused() {
+        injectFakeMouseActionHoverMoveEvent();
+
+        // Pause autoclick.
+        AutoclickTypePanel mockAutoclickTypePanel = mock(AutoclickTypePanel.class);
+        when(mockAutoclickTypePanel.isPaused()).thenReturn(true);
+        mController.mAutoclickTypePanel = mockAutoclickTypePanel;
+
+        // Send hover move event.
+        MotionEvent hoverMove = MotionEvent.obtain(
+                /* downTime= */ 0,
+                /* eventTime= */ 100,
+                /* action= */ MotionEvent.ACTION_HOVER_MOVE,
+                /* x= */ 30f,
+                /* y= */ 0f,
+                /* metaState= */ 0);
+        hoverMove.setSource(InputDevice.SOURCE_MOUSE);
+        mController.onMotionEvent(hoverMove, hoverMove, /* policyFlags= */ 0);
+
+        // Verify there is not a pending click.
+        assertThat(mController.mClickScheduler.getIsActiveForTesting()).isFalse();
+        assertThat(mController.mClickScheduler.getScheduledClickTimeForTesting()).isEqualTo(-1);
+
+        // Resume autoclick.
+        when(mockAutoclickTypePanel.isPaused()).thenReturn(false);
+
+        // Send initial move event again. Because this is the first recorded move, a click won't be
+        // scheduled.
+        injectFakeMouseActionHoverMoveEvent();
+        assertThat(mController.mClickScheduler.getIsActiveForTesting()).isFalse();
+        assertThat(mController.mClickScheduler.getScheduledClickTimeForTesting()).isEqualTo(-1);
+
+        // Send move again to trigger click and verify there is now a pending click.
+        mController.onMotionEvent(hoverMove, hoverMove, /* policyFlags= */ 0);
+        assertThat(mController.mClickScheduler.getIsActiveForTesting()).isTrue();
+        assertThat(mController.mClickScheduler.getScheduledClickTimeForTesting()).isNotEqualTo(-1);
+    }
+
     private void injectFakeMouseActionHoverMoveEvent() {
         MotionEvent event = getFakeMotionHoverMoveEvent();
         event.setSource(InputDevice.SOURCE_MOUSE);
diff --git a/services/tests/servicestests/src/com/android/server/accessibility/autoclick/AutoclickTypePanelTest.java b/services/tests/servicestests/src/com/android/server/accessibility/autoclick/AutoclickTypePanelTest.java
index f3016f4..c60c4b6 100644
--- a/services/tests/servicestests/src/com/android/server/accessibility/autoclick/AutoclickTypePanelTest.java
+++ b/services/tests/servicestests/src/com/android/server/accessibility/autoclick/AutoclickTypePanelTest.java
@@ -21,6 +21,9 @@
 import static com.android.server.accessibility.autoclick.AutoclickTypePanel.AUTOCLICK_TYPE_LEFT_CLICK;
 import static com.android.server.accessibility.autoclick.AutoclickTypePanel.AUTOCLICK_TYPE_SCROLL;
 import static com.android.server.accessibility.autoclick.AutoclickTypePanel.AutoclickType;
+import static com.android.server.accessibility.autoclick.AutoclickTypePanel.CORNER_BOTTOM_LEFT;
+import static com.android.server.accessibility.autoclick.AutoclickTypePanel.CORNER_BOTTOM_RIGHT;
+import static com.android.server.accessibility.autoclick.AutoclickTypePanel.CORNER_TOP_RIGHT;
 import static com.android.server.accessibility.autoclick.AutoclickTypePanel.ClickPanelControllerInterface;
 
 import static com.google.common.truth.Truth.assertThat;
@@ -31,6 +34,7 @@
 import android.testing.TestableContext;
 import android.testing.TestableLooper;
 import android.view.Gravity;
+import android.view.MotionEvent;
 import android.view.View;
 import android.view.WindowManager;
 import android.widget.LinearLayout;
@@ -68,6 +72,7 @@
     private LinearLayout mPositionButton;
 
     private @AutoclickType int mActiveClickType = AUTOCLICK_TYPE_LEFT_CLICK;
+    private boolean mPaused;
 
     private final ClickPanelControllerInterface clickPanelController =
             new ClickPanelControllerInterface() {
@@ -77,7 +82,9 @@
                 }
 
                 @Override
-                public void toggleAutoclickPause() {}
+                public void toggleAutoclickPause(boolean paused) {
+                    mPaused = paused;
+                }
             };
 
     @Before
@@ -199,6 +206,116 @@
         }
     }
 
+    @Test
+    public void pauseButton_onClick() {
+        mPauseButton.callOnClick();
+        assertThat(mPaused).isTrue();
+        assertThat(mAutoclickTypePanel.isPaused()).isTrue();
+
+        mPauseButton.callOnClick();
+        assertThat(mPaused).isFalse();
+        assertThat(mAutoclickTypePanel.isPaused()).isFalse();
+    }
+
+    @Test
+    public void onTouch_dragMove_updatesPosition() {
+        View contentView = mAutoclickTypePanel.getContentViewForTesting();
+        WindowManager.LayoutParams params = mAutoclickTypePanel.getLayoutParamsForTesting();
+        int[] panelLocation = new int[2];
+        contentView.getLocationOnScreen(panelLocation);
+
+        // Define movement delta for both x and y directions.
+        int delta = 15;
+
+        // Dispatch initial down event.
+        float touchX = panelLocation[0] + 10;
+        float touchY = panelLocation[1] + 10;
+        MotionEvent downEvent = MotionEvent.obtain(
+                0, 0,
+                MotionEvent.ACTION_DOWN, touchX, touchY, 0);
+        contentView.dispatchTouchEvent(downEvent);
+
+        // Create move event with delta, move from (x, y) to (x + delta, y + delta)
+        MotionEvent moveEvent = MotionEvent.obtain(
+                0, 0,
+                MotionEvent.ACTION_MOVE, touchX + delta, touchY + delta, 0);
+        contentView.dispatchTouchEvent(moveEvent);
+
+        // Verify position update.
+        assertThat(mAutoclickTypePanel.getIsDraggingForTesting()).isTrue();
+        assertThat(params.gravity).isEqualTo(Gravity.LEFT | Gravity.TOP);
+        assertThat(params.x).isEqualTo(panelLocation[0] + delta);
+        assertThat(params.y).isEqualTo(panelLocation[1] + delta);
+    }
+
+    @Test
+    public void dragAndEndAtRight_snapsToRightSide() {
+        View contentView = mAutoclickTypePanel.getContentViewForTesting();
+        WindowManager.LayoutParams params = mAutoclickTypePanel.getLayoutParamsForTesting();
+        int[] panelLocation = new int[2];
+        contentView.getLocationOnScreen(panelLocation);
+
+        int screenWidth = mTestableContext.getResources().getDisplayMetrics().widthPixels;
+
+        // Verify initial corner is bottom-right.
+        assertThat(mAutoclickTypePanel.getCurrentCornerIndexForTesting())
+                .isEqualTo(CORNER_BOTTOM_RIGHT);
+
+        dispatchDragSequence(contentView,
+                /* startX =*/ panelLocation[0] + 10, /* startY =*/ panelLocation[1] + 10,
+                /* endX =*/ (float) (screenWidth * 3) / 4, /* endY =*/ panelLocation[1] + 10);
+
+        // Verify snapping to the right.
+        assertThat(params.gravity).isEqualTo(Gravity.END | Gravity.TOP);
+        assertThat(mAutoclickTypePanel.getCurrentCornerIndexForTesting())
+                .isEqualTo(CORNER_TOP_RIGHT);
+    }
+
+    @Test
+    public void dragAndEndAtLeft_snapsToLeftSide() {
+        View contentView = mAutoclickTypePanel.getContentViewForTesting();
+        WindowManager.LayoutParams params = mAutoclickTypePanel.getLayoutParamsForTesting();
+        int[] panelLocation = new int[2];
+        contentView.getLocationOnScreen(panelLocation);
+
+        int screenWidth = mTestableContext.getResources().getDisplayMetrics().widthPixels;
+
+        // Verify initial corner is bottom-right.
+        assertThat(mAutoclickTypePanel.getCurrentCornerIndexForTesting())
+                .isEqualTo(CORNER_BOTTOM_RIGHT);
+
+        dispatchDragSequence(contentView,
+                /* startX =*/ panelLocation[0] + 10, /* startY =*/ panelLocation[1] + 10,
+                /* endX =*/ (float) screenWidth / 4, /* endY =*/ panelLocation[1] + 10);
+
+        // Verify snapping to the left.
+        assertThat(params.gravity).isEqualTo(Gravity.START | Gravity.TOP);
+        assertThat(mAutoclickTypePanel.getCurrentCornerIndexForTesting())
+                .isEqualTo(CORNER_BOTTOM_LEFT);
+    }
+
+    // Helper method to handle drag event sequences
+    private void dispatchDragSequence(View view, float startX, float startY, float endX,
+            float endY) {
+        // Down event
+        MotionEvent downEvent = MotionEvent.obtain(0, 0, MotionEvent.ACTION_DOWN, startX, startY,
+                0);
+        view.dispatchTouchEvent(downEvent);
+
+        // Move event
+        MotionEvent moveEvent = MotionEvent.obtain(0, 0, MotionEvent.ACTION_MOVE, endX, endY, 0);
+        view.dispatchTouchEvent(moveEvent);
+
+        // Up event
+        MotionEvent upEvent = MotionEvent.obtain(0, 0, MotionEvent.ACTION_UP, endX, endY, 0);
+        view.dispatchTouchEvent(upEvent);
+
+        // Clean up
+        downEvent.recycle();
+        moveEvent.recycle();
+        upEvent.recycle();
+    }
+
     private void verifyButtonHasSelectedStyle(@NonNull LinearLayout button) {
         GradientDrawable gradientDrawable = (GradientDrawable) button.getBackground();
         assertThat(gradientDrawable.getColor().getDefaultColor())
@@ -206,7 +323,7 @@
     }
 
     private void verifyPanelPosition(int[] expectedPosition) {
-        WindowManager.LayoutParams params = mAutoclickTypePanel.getLayoutParams();
+        WindowManager.LayoutParams params = mAutoclickTypePanel.getLayoutParamsForTesting();
         assertThat(mAutoclickTypePanel.getCurrentCornerIndexForTesting()).isEqualTo(
                 expectedPosition[0]);
         assertThat(params.gravity).isEqualTo(expectedPosition[1]);
diff --git a/services/tests/servicestests/src/com/android/server/accessibility/magnification/FullScreenMagnificationGestureHandlerTest.java b/services/tests/servicestests/src/com/android/server/accessibility/magnification/FullScreenMagnificationGestureHandlerTest.java
index 9f5dd93..5c126d1 100644
--- a/services/tests/servicestests/src/com/android/server/accessibility/magnification/FullScreenMagnificationGestureHandlerTest.java
+++ b/services/tests/servicestests/src/com/android/server/accessibility/magnification/FullScreenMagnificationGestureHandlerTest.java
@@ -311,8 +311,7 @@
                         mMockFullScreenMagnificationVibrationHelper,
                         mMockMagnificationLogger,
                         ViewConfiguration.get(mContext),
-                        mMockOneFingerPanningSettingsProvider,
-                        new MouseEventHandler(mFullScreenMagnificationController));
+                        mMockOneFingerPanningSettingsProvider);
         // OverscrollHandler is only supported on watches.
         // @See config_enable_a11y_fullscreen_magnification_overscroll_handler
         if (isWatch()) {
@@ -482,8 +481,8 @@
     @Test
     @RequiresFlagsDisabled(Flags.FLAG_ENABLE_MAGNIFICATION_MULTIPLE_FINGER_MULTIPLE_TAP_GESTURE)
     public void testDisablingTripleTap_removesInputLag() {
-        mMgh = newInstance(/* detectSingleFingerTripleTap */ false,
-                /* detectTwoFingerTripleTap */ true, /* detectShortcut */ true);
+        mMgh = newInstance(/* detectSingleFingerTripleTap= */ false,
+                /* detectTwoFingerTripleTap= */ true, /* detectShortcutTrigger= */ true);
         goFromStateIdleTo(STATE_IDLE);
         allowEventDelegation();
         tap();
@@ -494,8 +493,8 @@
     @Test
     @RequiresFlagsEnabled(Flags.FLAG_ENABLE_MAGNIFICATION_MULTIPLE_FINGER_MULTIPLE_TAP_GESTURE)
     public void testDisablingSingleFingerTripleTapAndTwoFingerTripleTap_removesInputLag() {
-        mMgh = newInstance(/* detectSingleFingerTripleTap */ false,
-                /* detectTwoFingerTripleTap */ false, /* detectShortcut */ true);
+        mMgh = newInstance(/* detectSingleFingerTripleTap= */ false,
+                /* detectTwoFingerTripleTap= */ false, /* detectShortcutTrigger= */ true);
         goFromStateIdleTo(STATE_IDLE);
         allowEventDelegation();
         tap();
@@ -1420,12 +1419,6 @@
     }
 
     @Test
-    @RequiresFlagsDisabled(Flags.FLAG_ENABLE_MAGNIFICATION_FOLLOWS_MOUSE_BUGFIX)
-    public void testMouseMoveEventsDoNotMoveMagnifierViewport() {
-        runMoveEventsDoNotMoveMagnifierViewport(InputDevice.SOURCE_MOUSE);
-    }
-
-    @Test
     public void testStylusMoveEventsDoNotMoveMagnifierViewport() {
         runMoveEventsDoNotMoveMagnifierViewport(InputDevice.SOURCE_STYLUS);
     }
@@ -1441,7 +1434,7 @@
                 (INITIAL_MAGNIFICATION_BOUNDS.top + INITIAL_MAGNIFICATION_BOUNDS.height()) / 2.0f;
         float scale = 5.6f; // value is unimportant but unique among tests to increase coverage.
         mFullScreenMagnificationController.setScaleAndCenter(
-                DISPLAY_0, centerX, centerY, scale, true, /* animate= */ false, 1);
+                DISPLAY_0, scale, centerX, centerY, true, /* animate= */ false, 1);
         centerX = mFullScreenMagnificationController.getCenterX(DISPLAY_0);
         centerY = mFullScreenMagnificationController.getCenterY(DISPLAY_0);
 
@@ -1474,55 +1467,36 @@
     }
 
     @Test
-    @RequiresFlagsDisabled(Flags.FLAG_ENABLE_MAGNIFICATION_FOLLOWS_MOUSE_BUGFIX)
-    public void testMouseHoverMoveEventsDoNotMoveMagnifierViewport() {
-        runHoverMoveEventsDoNotMoveMagnifierViewport(InputDevice.SOURCE_MOUSE);
-    }
-
-    @Test
-    @RequiresFlagsDisabled(Flags.FLAG_ENABLE_MAGNIFICATION_FOLLOWS_MOUSE_BUGFIX)
-    public void testStylusHoverMoveEventsDoNotMoveMagnifierViewport() {
-        runHoverMoveEventsDoNotMoveMagnifierViewport(InputDevice.SOURCE_STYLUS);
-    }
-
-    @Test
-    @RequiresFlagsEnabled(Flags.FLAG_ENABLE_MAGNIFICATION_FOLLOWS_MOUSE_BUGFIX)
     public void testMouseHoverMoveEventsMoveMagnifierViewport() {
         runHoverMovesViewportTest(InputDevice.SOURCE_MOUSE);
     }
 
     @Test
-    @RequiresFlagsEnabled(Flags.FLAG_ENABLE_MAGNIFICATION_FOLLOWS_MOUSE_BUGFIX)
     public void testStylusHoverMoveEventsMoveMagnifierViewport() {
         runHoverMovesViewportTest(InputDevice.SOURCE_STYLUS);
     }
 
     @Test
-    @RequiresFlagsEnabled(Flags.FLAG_ENABLE_MAGNIFICATION_FOLLOWS_MOUSE_BUGFIX)
     public void testMouseDownEventsDoNotMoveMagnifierViewport() {
         runDownDoesNotMoveViewportTest(InputDevice.SOURCE_MOUSE);
     }
 
     @Test
-    @RequiresFlagsEnabled(Flags.FLAG_ENABLE_MAGNIFICATION_FOLLOWS_MOUSE_BUGFIX)
     public void testStylusDownEventsDoNotMoveMagnifierViewport() {
         runDownDoesNotMoveViewportTest(InputDevice.SOURCE_STYLUS);
     }
 
     @Test
-    @RequiresFlagsEnabled(Flags.FLAG_ENABLE_MAGNIFICATION_FOLLOWS_MOUSE_BUGFIX)
     public void testMouseUpEventsDoNotMoveMagnifierViewport() {
         runUpDoesNotMoveViewportTest(InputDevice.SOURCE_MOUSE);
     }
 
     @Test
-    @RequiresFlagsEnabled(Flags.FLAG_ENABLE_MAGNIFICATION_FOLLOWS_MOUSE_BUGFIX)
     public void testStylusUpEventsDoNotMoveMagnifierViewport() {
         runUpDoesNotMoveViewportTest(InputDevice.SOURCE_STYLUS);
     }
 
     @Test
-    @RequiresFlagsEnabled(Flags.FLAG_ENABLE_MAGNIFICATION_FOLLOWS_MOUSE_BUGFIX)
     public void testMouseMoveEventsMoveMagnifierViewport() {
         final EventCaptor eventCaptor = new EventCaptor();
         mMgh.setNext(eventCaptor);
@@ -1533,7 +1507,7 @@
                 (INITIAL_MAGNIFICATION_BOUNDS.top + INITIAL_MAGNIFICATION_BOUNDS.height()) / 2.0f;
         float scale = 6.2f; // value is unimportant but unique among tests to increase coverage.
         mFullScreenMagnificationController.setScaleAndCenter(
-                DISPLAY_0, centerX, centerY, scale, true, /* animate= */ false, 1);
+                DISPLAY_0, scale, centerX, centerY, true, /* animate= */ false, 1);
         MotionEvent event = mouseEvent(centerX, centerY, ACTION_HOVER_MOVE);
         send(event, InputDevice.SOURCE_MOUSE);
         fastForward(20);
@@ -1574,7 +1548,7 @@
                 (INITIAL_MAGNIFICATION_BOUNDS.top + INITIAL_MAGNIFICATION_BOUNDS.height()) / 2.0f;
         float scale = 4.0f; // value is unimportant but unique among tests to increase coverage.
         mFullScreenMagnificationController.setScaleAndCenter(
-                DISPLAY_0, centerX, centerY, scale, true, /* animate= */ false, 1);
+                DISPLAY_0, scale, centerX, centerY, true, /* animate= */ false, 1);
 
         // HOVER_MOVE should change magnifier viewport.
         MotionEvent event = motionEvent(centerX + 20, centerY, ACTION_HOVER_MOVE);
@@ -1618,7 +1592,7 @@
                 (INITIAL_MAGNIFICATION_BOUNDS.top + INITIAL_MAGNIFICATION_BOUNDS.height()) / 2.0f;
         float scale = 5.3f; // value is unimportant but unique among tests to increase coverage.
         mFullScreenMagnificationController.setScaleAndCenter(
-                DISPLAY_0, centerX, centerY, scale, true, /* animate= */ false, 1);
+                DISPLAY_0, scale, centerX, centerY, true, /* animate= */ false, 1);
         MotionEvent event = motionEvent(centerX, centerY, ACTION_HOVER_MOVE);
         send(event, source);
         fastForward(20);
@@ -1652,7 +1626,7 @@
                 (INITIAL_MAGNIFICATION_BOUNDS.top + INITIAL_MAGNIFICATION_BOUNDS.height()) / 2.0f;
         float scale = 2.7f; // value is unimportant but unique among tests to increase coverage.
         mFullScreenMagnificationController.setScaleAndCenter(
-                DISPLAY_0, centerX, centerY, scale, true, /* animate= */ false, 1);
+                DISPLAY_0, scale, centerX, centerY, true, /* animate= */ false, 1);
         MotionEvent event = motionEvent(centerX, centerY, ACTION_HOVER_MOVE);
         send(event, source);
         fastForward(20);
@@ -1688,7 +1662,7 @@
                 (INITIAL_MAGNIFICATION_BOUNDS.top + INITIAL_MAGNIFICATION_BOUNDS.height()) / 2.0f;
         float scale = 3.8f; // value is unimportant but unique among tests to increase coverage.
         mFullScreenMagnificationController.setScaleAndCenter(
-                DISPLAY_0, centerX, centerY, scale, true, /* animate= */ false, 1);
+                DISPLAY_0, scale, centerX, centerY, true, /* animate= */ false, 1);
         centerX = mFullScreenMagnificationController.getCenterX(DISPLAY_0);
         centerY = mFullScreenMagnificationController.getCenterY(DISPLAY_0);
 
@@ -1725,7 +1699,7 @@
                 (INITIAL_MAGNIFICATION_BOUNDS.top + INITIAL_MAGNIFICATION_BOUNDS.height()) / 2.0f;
         float scale = 4.0f; // value is unimportant but unique among tests to increase coverage.
         mFullScreenMagnificationController.setScaleAndCenter(
-                DISPLAY_0, centerX, centerY, scale, true, /* animate= */ false, 1);
+                DISPLAY_0, scale, centerX, centerY, true, /* animate= */ false, 1);
         centerX = mFullScreenMagnificationController.getCenterX(DISPLAY_0);
         centerY = mFullScreenMagnificationController.getCenterY(DISPLAY_0);
 
diff --git a/services/tests/servicestests/src/com/android/server/accessibility/magnification/MagnificationGestureHandlerTest.java b/services/tests/servicestests/src/com/android/server/accessibility/magnification/MagnificationGestureHandlerTest.java
index 45c157d..203e655 100644
--- a/services/tests/servicestests/src/com/android/server/accessibility/magnification/MagnificationGestureHandlerTest.java
+++ b/services/tests/servicestests/src/com/android/server/accessibility/magnification/MagnificationGestureHandlerTest.java
@@ -21,15 +21,11 @@
 import static android.view.MotionEvent.ACTION_HOVER_MOVE;
 import static android.view.MotionEvent.ACTION_UP;
 
-import static junit.framework.Assert.assertFalse;
-
 import static org.mockito.ArgumentMatchers.eq;
 import static org.mockito.Mockito.verify;
 import static org.testng.AssertJUnit.assertTrue;
 
 import android.annotation.NonNull;
-import android.platform.test.annotations.RequiresFlagsDisabled;
-import android.platform.test.annotations.RequiresFlagsEnabled;
 import android.platform.test.flag.junit.CheckFlagsRule;
 import android.platform.test.flag.junit.DeviceFlagsValueProvider;
 import android.provider.Settings;
@@ -39,7 +35,6 @@
 import androidx.test.runner.AndroidJUnit4;
 
 import com.android.server.accessibility.AccessibilityTraceManager;
-import com.android.server.accessibility.Flags;
 
 import org.junit.Before;
 import org.junit.Rule;
@@ -93,7 +88,6 @@
     }
 
     @Test
-    @RequiresFlagsEnabled(Flags.FLAG_ENABLE_MAGNIFICATION_FOLLOWS_MOUSE_BUGFIX)
     public void onMotionEvent_isFromMouse_handleMouseOrStylusEvent() {
         final MotionEvent mouseEvent = MotionEvent.obtain(0, 0, ACTION_HOVER_MOVE, 0, 0, 0);
         mouseEvent.setSource(InputDevice.SOURCE_MOUSE);
@@ -108,7 +102,6 @@
     }
 
     @Test
-    @RequiresFlagsEnabled(Flags.FLAG_ENABLE_MAGNIFICATION_FOLLOWS_MOUSE_BUGFIX)
     public void onMotionEvent_isFromStylus_handleMouseOrStylusEvent() {
         final MotionEvent stylusEvent = MotionEvent.obtain(0, 0, ACTION_HOVER_MOVE, 0, 0, 0);
         stylusEvent.setSource(InputDevice.SOURCE_STYLUS);
@@ -123,36 +116,6 @@
     }
 
     @Test
-    @RequiresFlagsDisabled(Flags.FLAG_ENABLE_MAGNIFICATION_FOLLOWS_MOUSE_BUGFIX)
-    public void onMotionEvent_isFromMouse_handleMouseOrStylusEventNotCalled() {
-        final MotionEvent mouseEvent = MotionEvent.obtain(0, 0, ACTION_HOVER_MOVE, 0, 0, 0);
-        mouseEvent.setSource(InputDevice.SOURCE_MOUSE);
-
-        mMgh.onMotionEvent(mouseEvent, mouseEvent, /* policyFlags= */ 0);
-
-        try {
-            assertFalse(mMgh.mIsHandleMouseOrStylusEventCalled);
-        } finally {
-            mouseEvent.recycle();
-        }
-    }
-
-    @Test
-    @RequiresFlagsDisabled(Flags.FLAG_ENABLE_MAGNIFICATION_FOLLOWS_MOUSE_BUGFIX)
-    public void onMotionEvent_isFromStylus_handleMouseOrStylusEventNotCalled() {
-        final MotionEvent stylusEvent = MotionEvent.obtain(0, 0, ACTION_HOVER_MOVE, 0, 0, 0);
-        stylusEvent.setSource(InputDevice.SOURCE_STYLUS);
-
-        mMgh.onMotionEvent(stylusEvent, stylusEvent, /* policyFlags= */ 0);
-
-        try {
-            assertFalse(mMgh.mIsHandleMouseOrStylusEventCalled);
-        } finally {
-            stylusEvent.recycle();
-        }
-    }
-
-    @Test
     public void onMotionEvent_downEvent_handleInteractionStart() {
         final MotionEvent downEvent = MotionEvent.obtain(0, 0, ACTION_DOWN, 0, 0, 0);
         downEvent.setSource(InputDevice.SOURCE_TOUCHSCREEN);
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/servicestests/src/com/android/server/om/OverlayManagerSettingsTests.java b/services/tests/servicestests/src/com/android/server/om/OverlayManagerSettingsTests.java
index ad3855f..b1cf905 100644
--- a/services/tests/servicestests/src/com/android/server/om/OverlayManagerSettingsTests.java
+++ b/services/tests/servicestests/src/com/android/server/om/OverlayManagerSettingsTests.java
@@ -37,10 +37,12 @@
 import android.util.Xml;
 
 import androidx.annotation.NonNull;
-import androidx.test.runner.AndroidJUnit4;
 
 import com.android.modules.utils.TypedXmlPullParser;
 
+import junitparams.JUnitParamsRunner;
+import junitparams.Parameters;
+
 import org.junit.Before;
 import org.junit.Test;
 import org.junit.runner.RunWith;
@@ -56,7 +58,7 @@
 
 import javax.annotation.Nullable;
 
-@RunWith(AndroidJUnit4.class)
+@RunWith(JUnitParamsRunner.class)
 public class OverlayManagerSettingsTests {
     private OverlayManagerSettings mSettings;
     private static final int USER_0 = 0;
@@ -439,6 +441,39 @@
     }
 
     @Test
+    @Parameters(method = "getPreviousVersions")
+    public void testRestoreWithPreviousVersion(int version) throws Exception {
+        final String xml =
+                "<?xml version='1.0' encoding='utf-8' standalone='yes'?>\n"
+                        + "<overlays version='" + version + "'>\n"
+                        + "<item packageName='com.test.overlay'\n"
+                        + "      overlayName='test'\n"
+                        + "      userId='1234'\n"
+                        + "      targetPackageName='com.test.target'\n"
+                        + "      baseCodePath='/data/app/com.test.overlay-1/base.apk'\n"
+                        + "      state='" + STATE_DISABLED + "'\n"
+                        + "      isEnabled='false'\n"
+                        + "      category='test-category'\n"
+                        + "      isStatic='false'\n"
+                        + "      priority='0' />\n"
+                        + "</overlays>\n";
+        ByteArrayInputStream is = new ByteArrayInputStream(xml.getBytes(UTF_8));
+
+        mSettings.restore(is);
+        final OverlayIdentifier identifier = new OverlayIdentifier("com.test.overlay", "test");
+        OverlayInfo oi = mSettings.getOverlayInfo(identifier, 1234);
+        assertNotNull(oi);
+        assertEquals("com.test.overlay", oi.packageName);
+        assertEquals("test", oi.overlayName);
+        assertEquals("com.test.target", oi.targetPackageName);
+        assertEquals("/data/app/com.test.overlay-1/base.apk", oi.baseCodePath);
+        assertEquals(1234, oi.userId);
+        assertEquals(STATE_DISABLED, oi.state);
+        assertFalse(mSettings.getEnabled(identifier, 1234));
+        assertTrue(oi.constraints.isEmpty());
+    }
+
+    @Test
     public void testPersistAndRestore() throws Exception {
         insertSetting(OVERLAY_A_USER0);
         insertSetting(OVERLAY_B_USER1);
@@ -585,4 +620,11 @@
                         TextUtils.join(",", expected), TextUtils.join(",", actual)));
         }
     }
+
+    private static Integer[] getPreviousVersions() {
+        return new Integer[]{
+                3,
+                4,
+        };
+    }
 }
diff --git a/services/tests/servicestests/src/com/android/server/pm/ShortcutManagerTest1.java b/services/tests/servicestests/src/com/android/server/pm/ShortcutManagerTest1.java
index 58f7622..ad1537e 100644
--- a/services/tests/servicestests/src/com/android/server/pm/ShortcutManagerTest1.java
+++ b/services/tests/servicestests/src/com/android/server/pm/ShortcutManagerTest1.java
@@ -261,7 +261,7 @@
     // === Test for app side APIs ===
 
     /** Test for {@link android.content.pm.ShortcutManager#getMaxShortcutCountForActivity()} */
-    public void testGetMaxDynamicShortcutCount() {
+    public void disabled_testGetMaxDynamicShortcutCount() {
         assertEquals(MAX_SHORTCUTS, mManager.getMaxShortcutCountForActivity());
     }
 
@@ -793,7 +793,7 @@
         assertEquals(2, mManager.getRemainingCallCount());
     }
 
-    public void testDeleteAllDynamicShortcuts() {
+    public void disabled_testDeleteAllDynamicShortcuts() {
         final ShortcutInfo si1 = makeShortcut("shortcut1");
         final ShortcutInfo si2 = makeShortcut("shortcut2");
         final ShortcutInfo si3 = makeShortcut("shortcut3");
@@ -1036,7 +1036,7 @@
 */
     }
 
-    public void testCleanupDanglingBitmaps() throws Exception {
+    public void disabled_testCleanupDanglingBitmaps() throws Exception {
         assertBitmapDirectories(USER_10, EMPTY_STRINGS);
         assertBitmapDirectories(USER_11, EMPTY_STRINGS);
 
@@ -1702,7 +1702,7 @@
                 "s2");
     }
 
-    public void testCachedShortcuts_accessShortcutsPermission() {
+    public void disabled_testCachedShortcuts_accessShortcutsPermission() {
         runWithCaller(CALLING_PACKAGE_1, USER_10, () -> {
             assertTrue(mManager.setDynamicShortcuts(list(makeShortcut("s1"),
                     makeLongLivedShortcut("s2"), makeLongLivedShortcut("s3"),
@@ -1744,7 +1744,7 @@
         assertShortcutIds(mManager.getShortcuts(ShortcutManager.FLAG_MATCH_CACHED), "s3");
     }
 
-    public void testCachedShortcuts_canPassShortcutLimit() {
+    public void disabled_testCachedShortcuts_canPassShortcutLimit() {
         // Change the max number of shortcuts.
         mService.updateConfigurationLocked(ConfigConstants.KEY_MAX_SHORTCUTS + "=4");
 
@@ -2362,7 +2362,7 @@
      * This is similar to the above test, except it used "disable" instead of "remove".  It also
      * does "enable".
      */
-    public void testDisableAndEnableShortcuts() {
+    public void disabled_testDisableAndEnableShortcuts() {
         runWithCaller(CALLING_PACKAGE_1, USER_10, () -> {
             final ShortcutInfo s1_1 = makeShortcutWithTimestamp("s1", 1000);
             final ShortcutInfo s1_2 = makeShortcutWithTimestamp("s2", 2000);
@@ -2487,7 +2487,7 @@
         });
     }
 
-    public void testDisableShortcuts_thenRepublish() {
+    public void disabled_testDisableShortcuts_thenRepublish() {
         runWithCaller(CALLING_PACKAGE_1, USER_10, () -> {
             assertTrue(mManager.setDynamicShortcuts(list(
                     makeShortcut("s1"), makeShortcut("s2"), makeShortcut("s3"))));
@@ -4230,7 +4230,7 @@
         // TODO Check all other fields
     }
 
-    public void testCleanupPackage() {
+    public void disabled_testCleanupPackage() {
         runWithCaller(CALLING_PACKAGE_1, USER_10, () -> {
             assertTrue(mManager.setDynamicShortcuts(list(
                     makeShortcut("s0_1"))));
@@ -4507,7 +4507,7 @@
         mService.saveDirtyInfo();
     }
 
-    public void testCleanupPackage_republishManifests() {
+    public void disabled_testCleanupPackage_republishManifests() {
         addManifestShortcutResource(
                 new ComponentName(CALLING_PACKAGE_1, ShortcutActivity.class.getName()),
                 R.xml.shortcut_2);
@@ -4847,7 +4847,7 @@
         assertEquals(expected, spi.canRestoreTo(mService, pi, true));
     }
 
-    public void testCanRestoreTo() {
+    public void disabled_testCanRestoreTo() {
         addPackage(CALLING_PACKAGE_1, CALLING_UID_1, 10, "sig1");
         addPackage(CALLING_PACKAGE_2, CALLING_UID_2, 10, "sig1", "sig2");
         addPackage(CALLING_PACKAGE_3, CALLING_UID_3, 10, "sig1");
@@ -5785,7 +5785,7 @@
         checkBackupAndRestore_success(/*firstRestore=*/ true);
     }
 
-    public void testBackupAndRestore_restoreToSuperSetSignatures() {
+    public void disabled_testBackupAndRestore_restoreToSuperSetSignatures() {
         prepareForBackupTest();
 
         // Change package signatures.
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/uiservicestests/src/com/android/server/notification/ZenModeHelperTest.java b/services/tests/uiservicestests/src/com/android/server/notification/ZenModeHelperTest.java
index 0ab11e0..f8387a4 100644
--- a/services/tests/uiservicestests/src/com/android/server/notification/ZenModeHelperTest.java
+++ b/services/tests/uiservicestests/src/com/android/server/notification/ZenModeHelperTest.java
@@ -300,7 +300,7 @@
 
     @Parameters(name = "{0}")
     public static List<FlagsParameterization> getParams() {
-        return FlagsParameterization.allCombinationsOf(FLAG_MODES_UI, FLAG_BACKUP_RESTORE_LOGGING,
+        return FlagsParameterization.allCombinationsOf(FLAG_BACKUP_RESTORE_LOGGING,
                 com.android.server.notification.Flags.FLAG_FIX_CALLING_UID_FROM_CPS);
     }
 
diff --git a/services/tests/vibrator/src/com/android/server/vibrator/DeviceAdapterTest.java b/services/tests/vibrator/src/com/android/server/vibrator/DeviceAdapterTest.java
index d5548a4..f091a65 100644
--- a/services/tests/vibrator/src/com/android/server/vibrator/DeviceAdapterTest.java
+++ b/services/tests/vibrator/src/com/android/server/vibrator/DeviceAdapterTest.java
@@ -497,19 +497,19 @@
 
     private VibratorController createEmptyVibratorController(int vibratorId) {
         return new FakeVibratorControllerProvider(mTestLooper.getLooper())
-                .newVibratorController(vibratorId, (id, vibrationId)  -> {});
+                .newVibratorController(vibratorId, (id, vibrationId, stepId)  -> {});
     }
 
     private VibratorController createBasicVibratorController(int vibratorId) {
         FakeVibratorControllerProvider provider = createVibratorProviderWithEffects(
                 IVibrator.CAP_COMPOSE_EFFECTS);
-        return provider.newVibratorController(vibratorId, (id, vibrationId)  -> {});
+        return provider.newVibratorController(vibratorId, (id, vibrationId, stepId)  -> {});
     }
 
     private VibratorController createPwleWithoutFrequenciesVibratorController(int vibratorId) {
         FakeVibratorControllerProvider provider = createVibratorProviderWithEffects(
                 IVibrator.CAP_COMPOSE_EFFECTS, IVibrator.CAP_COMPOSE_PWLE_EFFECTS);
-        return provider.newVibratorController(vibratorId, (id, vibrationId)  -> {});
+        return provider.newVibratorController(vibratorId, (id, vibrationId, stepId)  -> {});
     }
 
     private VibratorController createPwleVibratorController(int vibratorId) {
@@ -519,7 +519,7 @@
         provider.setMinFrequency(TEST_MIN_FREQUENCY);
         provider.setFrequencyResolution(TEST_FREQUENCY_RESOLUTION);
         provider.setMaxAmplitudes(TEST_AMPLITUDE_MAP);
-        return provider.newVibratorController(vibratorId, (id, vibrationId)  -> {});
+        return provider.newVibratorController(vibratorId, (id, vibrationId, stepId)  -> {});
     }
 
     private VibratorController createPwleV2VibratorController(int vibratorId) {
@@ -538,7 +538,7 @@
         provider.setMinEnvelopeEffectControlPointDurationMillis(
                 TEST_MIN_ENVELOPE_EFFECT_CONTROL_POINT_DURATION_MILLIS);
 
-        return provider.newVibratorController(vibratorId, (id, vibrationId)  -> {});
+        return provider.newVibratorController(vibratorId, (id, vibrationId, stepId)  -> {});
     }
 
     private FakeVibratorControllerProvider createVibratorProviderWithEffects(int... capabilities) {
diff --git a/services/tests/vibrator/src/com/android/server/vibrator/VibrationThreadTest.java b/services/tests/vibrator/src/com/android/server/vibrator/VibrationThreadTest.java
index 5f2af0a..04335df 100644
--- a/services/tests/vibrator/src/com/android/server/vibrator/VibrationThreadTest.java
+++ b/services/tests/vibrator/src/com/android/server/vibrator/VibrationThreadTest.java
@@ -17,14 +17,17 @@
 package com.android.server.vibrator;
 
 import static android.os.VibrationAttributes.USAGE_RINGTONE;
+import static android.os.VibrationEffect.Composition.PRIMITIVE_CLICK;
+import static android.os.VibrationEffect.Composition.PRIMITIVE_SPIN;
+import static android.os.VibrationEffect.Composition.PRIMITIVE_TICK;
+import static android.os.VibrationEffect.EFFECT_CLICK;
+import static android.os.VibrationEffect.EFFECT_TICK;
 import static android.os.VibrationEffect.VibrationParameter.targetAmplitude;
 import static android.os.VibrationEffect.VibrationParameter.targetFrequency;
 
 import static com.google.common.truth.Truth.assertThat;
+import static com.google.common.truth.Truth.assertWithMessage;
 
-import static org.junit.Assert.assertEquals;
-import static org.junit.Assert.assertFalse;
-import static org.junit.Assert.assertTrue;
 import static org.mockito.ArgumentMatchers.any;
 import static org.mockito.ArgumentMatchers.anyInt;
 import static org.mockito.ArgumentMatchers.anyLong;
@@ -66,7 +69,6 @@
 import android.os.vibrator.VibrationEffectSegment;
 import android.platform.test.annotations.DisableFlags;
 import android.platform.test.annotations.EnableFlags;
-import android.platform.test.annotations.RequiresFlagsEnabled;
 import android.platform.test.flag.junit.CheckFlagsRule;
 import android.platform.test.flag.junit.DeviceFlagsValueProvider;
 import android.platform.test.flag.junit.SetFlagsRule;
@@ -153,9 +155,10 @@
         when(mPackageManagerInternalMock.getSystemUiServiceComponent())
                 .thenReturn(new ComponentName("", ""));
         doAnswer(answer -> {
-            mVibrationConductor.notifyVibratorComplete(answer.getArgument(0));
+            mVibrationConductor.notifyVibratorComplete(
+                    answer.getArgument(0), answer.getArgument(2));
             return null;
-        }).when(mControllerCallbacks).onComplete(anyInt(), anyLong());
+        }).when(mControllerCallbacks).onComplete(anyInt(), anyLong(), anyLong());
 
         LocalServices.removeServiceForTest(PackageManagerInternal.class);
         LocalServices.addService(PackageManagerInternal.class, mPackageManagerInternalMock);
@@ -186,24 +189,24 @@
     public void vibrate_noVibrator_ignoresVibration() {
         mVibratorProviders.clear();
         CombinedVibration effect = CombinedVibration.createParallel(
-                VibrationEffect.get(VibrationEffect.EFFECT_CLICK));
+                VibrationEffect.get(EFFECT_CLICK));
         HalVibration vibration = startThreadAndDispatcher(effect);
         waitForCompletion();
 
-        verify(mControllerCallbacks, never()).onComplete(anyInt(), eq(vibration.id));
+        verify(mControllerCallbacks, never()).onComplete(anyInt(), eq(vibration.id), anyLong());
         verifyCallbacksTriggered(vibration, Status.IGNORED_UNSUPPORTED);
     }
 
     @Test
     public void vibrate_missingVibrators_ignoresVibration() {
         CombinedVibration effect = CombinedVibration.startSequential()
-                .addNext(2, VibrationEffect.get(VibrationEffect.EFFECT_CLICK))
-                .addNext(3, VibrationEffect.get(VibrationEffect.EFFECT_TICK))
+                .addNext(2, VibrationEffect.get(EFFECT_CLICK))
+                .addNext(3, VibrationEffect.get(EFFECT_TICK))
                 .combine();
         HalVibration vibration = startThreadAndDispatcher(effect);
         waitForCompletion();
 
-        verify(mControllerCallbacks, never()).onComplete(anyInt(), eq(vibration.id));
+        verify(mControllerCallbacks, never()).onComplete(anyInt(), eq(vibration.id), anyLong());
         verifyCallbacksTriggered(vibration, Status.IGNORED_UNSUPPORTED);
     }
 
@@ -217,13 +220,14 @@
 
         verify(mManagerHooks).noteVibratorOn(eq(UID), eq(10L));
         verify(mManagerHooks).noteVibratorOff(eq(UID));
-        verify(mControllerCallbacks).onComplete(eq(VIBRATOR_ID), eq(vibration.id));
+        verify(mControllerCallbacks).onComplete(eq(VIBRATOR_ID), eq(vibration.id), anyLong());
         verifyCallbacksTriggered(vibration, Status.FINISHED);
-        assertFalse(mControllers.get(VIBRATOR_ID).isVibrating());
+        assertThat(mControllers.get(VIBRATOR_ID).isVibrating()).isFalse();
 
-        assertEquals(Arrays.asList(expectedOneShot(10)),
-                mVibratorProviders.get(VIBRATOR_ID).getEffectSegments(vibration.id));
-        assertEquals(expectedAmplitudes(100), mVibratorProviders.get(VIBRATOR_ID).getAmplitudes());
+        assertThat(mVibratorProviders.get(VIBRATOR_ID).getEffectSegments(vibration.id))
+                .containsExactly(expectedOneShot(10)).inOrder();
+        assertThat(mVibratorProviders.get(VIBRATOR_ID).getAmplitudes())
+                .containsExactlyElementsIn(expectedAmplitudes(100)).inOrder();
     }
 
     @Test
@@ -234,13 +238,13 @@
 
         verify(mManagerHooks).noteVibratorOn(eq(UID), eq(10L));
         verify(mManagerHooks).noteVibratorOff(eq(UID));
-        verify(mControllerCallbacks).onComplete(eq(VIBRATOR_ID), eq(vibration.id));
+        verify(mControllerCallbacks).onComplete(eq(VIBRATOR_ID), eq(vibration.id), anyLong());
         verifyCallbacksTriggered(vibration, Status.FINISHED);
-        assertFalse(mControllers.get(VIBRATOR_ID).isVibrating());
+        assertThat(mControllers.get(VIBRATOR_ID).isVibrating()).isFalse();
 
-        assertEquals(Arrays.asList(expectedOneShot(10)),
-                mVibratorProviders.get(VIBRATOR_ID).getEffectSegments(vibration.id));
-        assertTrue(mVibratorProviders.get(VIBRATOR_ID).getAmplitudes().isEmpty());
+        assertThat(mVibratorProviders.get(VIBRATOR_ID).getEffectSegments(vibration.id))
+                .containsExactly(expectedOneShot(10)).inOrder();
+        assertThat(mVibratorProviders.get(VIBRATOR_ID).getAmplitudes()).isEmpty();
     }
 
     @Test
@@ -254,18 +258,18 @@
 
         verify(mManagerHooks).noteVibratorOn(eq(UID), eq(15L));
         verify(mManagerHooks).noteVibratorOff(eq(UID));
-        verify(mControllerCallbacks).onComplete(eq(VIBRATOR_ID), eq(vibration.id));
+        verify(mControllerCallbacks).onComplete(eq(VIBRATOR_ID), eq(vibration.id), anyLong());
         verifyCallbacksTriggered(vibration, Status.FINISHED);
-        assertFalse(mControllers.get(VIBRATOR_ID).isVibrating());
+        assertThat(mControllers.get(VIBRATOR_ID).isVibrating()).isFalse();
 
-        assertEquals(Arrays.asList(expectedOneShot(15)),
-                mVibratorProviders.get(VIBRATOR_ID).getEffectSegments(vibration.id));
-        assertEquals(expectedAmplitudes(1, 2, 3),
-                mVibratorProviders.get(VIBRATOR_ID).getAmplitudes());
+        assertThat(mVibratorProviders.get(VIBRATOR_ID).getEffectSegments(vibration.id))
+                .containsExactly(expectedOneShot(15)).inOrder();
+        assertThat(mVibratorProviders.get(VIBRATOR_ID).getAmplitudes())
+                .containsExactlyElementsIn(expectedAmplitudes(1, 2, 3)).inOrder();
     }
 
     @Test
-    @RequiresFlagsEnabled(Flags.FLAG_ADAPTIVE_HAPTICS_ENABLED)
+    @EnableFlags(Flags.FLAG_ADAPTIVE_HAPTICS_ENABLED)
     public void vibrate_singleWaveformWithAdaptiveHapticsScaling_scalesAmplitudesProperly() {
         // No user settings scale.
         setUserSetting(Settings.System.RING_VIBRATION_INTENSITY,
@@ -282,16 +286,17 @@
         waitForCompletion();
 
         verify(mStatsLoggerMock, never()).logVibrationParamRequestTimeout(UID);
-        assertEquals(Arrays.asList(expectedOneShot(15)),
-                mVibratorProviders.get(VIBRATOR_ID).getEffectSegments(vibration.id));
+        assertThat(mVibratorProviders.get(VIBRATOR_ID).getEffectSegments(vibration.id))
+                .containsExactly(expectedOneShot(15)).inOrder();
         List<Float> amplitudes = mVibratorProviders.get(VIBRATOR_ID).getAmplitudes();
         for (int i = 0; i < amplitudes.size(); i++) {
-            assertTrue(amplitudes.get(i) < 1 / 255f);
+            assertWithMessage("For amplitude index %s", i)
+                    .that(amplitudes.get(i)).isLessThan(1 / 255f);
         }
     }
 
     @Test
-    @RequiresFlagsEnabled(Flags.FLAG_ADAPTIVE_HAPTICS_ENABLED)
+    @EnableFlags(Flags.FLAG_ADAPTIVE_HAPTICS_ENABLED)
     public void vibrate_withVibrationParamsRequestStalling_timeoutRequestAndApplyNoScaling() {
         // No user settings scale.
         setUserSetting(Settings.System.RING_VIBRATION_INTENSITY,
@@ -306,10 +311,10 @@
         waitForCompletion();
 
         verify(mStatsLoggerMock).logVibrationParamRequestTimeout(UID);
-        assertEquals(Arrays.asList(expectedOneShot(15)),
-                mVibratorProviders.get(VIBRATOR_ID).getEffectSegments(vibration.id));
-        assertEquals(expectedAmplitudes(1, 1, 1),
-                mVibratorProviders.get(VIBRATOR_ID).getAmplitudes());
+        assertThat(mVibratorProviders.get(VIBRATOR_ID).getEffectSegments(vibration.id))
+                .containsExactly(expectedOneShot(15)).inOrder();
+        assertThat(mVibratorProviders.get(VIBRATOR_ID).getAmplitudes())
+                .containsExactlyElementsIn(expectedAmplitudes(1, 1, 1)).inOrder();
     }
 
     @Test
@@ -322,31 +327,33 @@
         VibrationEffect effect = VibrationEffect.createWaveform(new long[]{5, 5, 5}, amplitudes, 0);
         HalVibration vibration = startThreadAndDispatcher(effect);
 
-        assertTrue(
+        assertThat(
                 waitUntil(() -> fakeVibrator.getAmplitudes().size() > 2 * amplitudes.length,
-                        TEST_TIMEOUT_MILLIS));
+                        TEST_TIMEOUT_MILLIS)).isTrue();
         // Vibration still running after 2 cycles.
-        assertTrue(mThread.isRunningVibrationId(vibration.id));
-        assertTrue(mControllers.get(VIBRATOR_ID).isVibrating());
+        assertThat(mThread.isRunningVibrationId(vibration.id)).isTrue();
+        assertThat(mControllers.get(VIBRATOR_ID).isVibrating()).isTrue();
 
         Vibration.EndInfo cancelVibrationInfo = new Vibration.EndInfo(Status.CANCELLED_SUPERSEDED,
                 new CallerInfo(VibrationAttributes.createForUsage(VibrationAttributes.USAGE_ALARM),
                         /* uid= */ 1, /* deviceId= */ -1, /* opPkg= */ null, /* reason= */ null));
         mVibrationConductor.notifyCancelled(cancelVibrationInfo, /* immediate= */ false);
         waitForCompletion();
-        assertFalse(mThread.isRunningVibrationId(vibration.id));
+        assertThat(mThread.isRunningVibrationId(vibration.id)).isFalse();
 
         verify(mManagerHooks).noteVibratorOn(eq(UID), anyLong());
         verify(mManagerHooks).noteVibratorOff(eq(UID));
         verifyCallbacksTriggered(vibration, Status.CANCELLED_SUPERSEDED);
-        assertFalse(mControllers.get(VIBRATOR_ID).isVibrating());
+        assertThat(mControllers.get(VIBRATOR_ID).isVibrating()).isFalse();
 
         List<Float> playedAmplitudes = fakeVibrator.getAmplitudes();
-        assertFalse(fakeVibrator.getEffectSegments(vibration.id).isEmpty());
-        assertFalse(playedAmplitudes.isEmpty());
+        assertThat(fakeVibrator.getEffectSegments(vibration.id)).isNotEmpty();
+        assertThat(playedAmplitudes).isNotEmpty();
 
         for (int i = 0; i < playedAmplitudes.size(); i++) {
-            assertEquals(amplitudes[i % amplitudes.length] / 255f, playedAmplitudes.get(i), 1e-5);
+            assertWithMessage("For amplitude index %s", i)
+                    .that(amplitudes[i % amplitudes.length] / 255f)
+                    .isWithin(1e-5f).of(playedAmplitudes.get(i));
         }
     }
 
@@ -361,15 +368,16 @@
                 new long[]{1, 10, 100}, amplitudes, 0);
         HalVibration vibration = startThreadAndDispatcher(effect);
 
-        assertTrue(waitUntil(() -> !fakeVibrator.getAmplitudes().isEmpty(), TEST_TIMEOUT_MILLIS));
+        assertThat(waitUntil(() -> !fakeVibrator.getAmplitudes().isEmpty(), TEST_TIMEOUT_MILLIS))
+                .isTrue();
         mVibrationConductor.notifyCancelled(
                 new Vibration.EndInfo(Status.CANCELLED_BY_USER), /* immediate= */ false);
         waitForCompletion();
 
         verifyCallbacksTriggered(vibration, Status.CANCELLED_BY_USER);
-        assertFalse(mControllers.get(VIBRATOR_ID).isVibrating());
-        assertEquals(Arrays.asList(expectedOneShot(5000)),
-                fakeVibrator.getEffectSegments(vibration.id));
+        assertThat(mControllers.get(VIBRATOR_ID).isVibrating()).isFalse();
+        assertThat(fakeVibrator.getEffectSegments(vibration.id))
+                .containsExactly(expectedOneShot(5000)).inOrder();
     }
 
     @Test
@@ -388,7 +396,7 @@
         assertThat(mControllers.get(VIBRATOR_ID).isVibrating()).isFalse();
 
         assertThat(mVibratorProviders.get(VIBRATOR_ID).getEffectSegments(vibration.id))
-                .isEqualTo(expectedOneShots(100L, 150L));
+                .containsExactlyElementsIn(expectedOneShots(100L, 150L)).inOrder();
     }
 
     @Test
@@ -409,9 +417,10 @@
         assertThat(mControllers.get(VIBRATOR_ID).isVibrating()).isFalse();
 
         assertThat(mVibratorProviders.get(VIBRATOR_ID).getEffectSegments(vibration.id))
-                .isEqualTo(expectedOneShots(200L, 50L));
+                .containsExactlyElementsIn(expectedOneShots(200L, 50L)).inOrder();
     }
 
+    @EnableFlags(Flags.FLAG_FIX_VIBRATION_THREAD_CALLBACK_HANDLING)
     @LargeTest
     @Test
     public void vibrate_singleVibratorRepeatingPatternWithZeroDurationSteps_repeatsEffectCorrectly()
@@ -429,8 +438,8 @@
         // 300ms ON (100ms + 200ms looping to the start and skipping first 0ms)
         // 150ms ON (100ms + 50ms, skips 0ms)
         // 300ms ON (100ms + 200ms looping to the start and skipping first 0ms)
-        assertTrue(waitUntil(() -> fakeVibrator.getEffectSegments(vibration.id).size() >= 5,
-                5000L + TEST_TIMEOUT_MILLIS));
+        assertThat(waitUntil(() -> fakeVibrator.getEffectSegments(vibration.id).size() >= 5,
+                5000L + TEST_TIMEOUT_MILLIS)).isTrue();
         mVibrationConductor.notifyCancelled(
                 new Vibration.EndInfo(Status.CANCELLED_BY_USER), /* immediate= */ false);
         waitForCompletion();
@@ -440,7 +449,32 @@
 
         assertThat(
                 mVibratorProviders.get(VIBRATOR_ID).getEffectSegments(vibration.id).subList(0, 5))
-                .isEqualTo(expectedOneShots(200L, 150L, 300L, 150L, 300L));
+                .containsExactlyElementsIn(expectedOneShots(200L, 150L, 300L, 150L, 300L))
+                .inOrder();
+    }
+
+    @EnableFlags(Flags.FLAG_FIX_VIBRATION_THREAD_CALLBACK_HANDLING)
+    @Test
+    public void vibrate_singleVibratorPatternWithCallbackDelay_oldCallbacksIgnored() {
+        FakeVibratorControllerProvider fakeVibrator = mVibratorProviders.get(VIBRATOR_ID);
+        fakeVibrator.setCompletionCallbackDelay(100); // 100ms delay to notify service.
+        fakeVibrator.setCapabilities(IVibrator.CAP_AMPLITUDE_CONTROL);
+
+        VibrationEffect effect = VibrationEffect.createWaveform(
+                /* timings= */ new long[]{0, 200, 50, 400}, /* repeat= */ -1);
+        HalVibration vibration = startThreadAndDispatcher(effect);
+        waitForCompletion(800 + TEST_TIMEOUT_MILLIS); // 200 + 50 + 400 + 100ms delay
+
+        verifyCallbacksTriggered(vibration, Status.FINISHED);
+        assertThat(mControllers.get(VIBRATOR_ID).isVibrating()).isFalse();
+
+        verify(mControllerCallbacks).onComplete(eq(VIBRATOR_ID), eq(vibration.id), eq(1L));
+        // Step id = 2 skipped by the 50ms OFF step after the 200ms ON step.
+        verify(mControllerCallbacks).onComplete(eq(VIBRATOR_ID), eq(vibration.id), eq(3L));
+
+        // First callback ignored, did not cause the vibrator to turn back on during the 400ms step.
+        assertThat(fakeVibrator.getEffectSegments(vibration.id))
+                .containsExactlyElementsIn(expectedOneShots(200L, 400L)).inOrder();
     }
 
     @Test
@@ -462,16 +496,16 @@
                 .compose();
         HalVibration vibration = startThreadAndDispatcher(repeatingEffect);
 
-        assertTrue(waitUntil(() -> !fakeVibrator.getEffectSegments(vibration.id).isEmpty(),
-                TEST_TIMEOUT_MILLIS));
+        assertThat(waitUntil(() -> !fakeVibrator.getEffectSegments(vibration.id).isEmpty(),
+                TEST_TIMEOUT_MILLIS)).isTrue();
         mVibrationConductor.notifyCancelled(
                 new Vibration.EndInfo(Status.CANCELLED_BY_USER), /* immediate= */ false);
         waitForCompletion();
 
         // PWLE size max was used to generate a single vibrate call with 10 segments.
         verifyCallbacksTriggered(vibration, Status.CANCELLED_BY_USER);
-        assertFalse(mControllers.get(VIBRATOR_ID).isVibrating());
-        assertEquals(10, fakeVibrator.getEffectSegments(vibration.id).size());
+        assertThat(mControllers.get(VIBRATOR_ID).isVibrating()).isFalse();
+        assertThat(fakeVibrator.getEffectSegments(vibration.id)).hasSize(10);
     }
 
     @Test
@@ -479,28 +513,28 @@
             throws Exception {
         FakeVibratorControllerProvider fakeVibrator = mVibratorProviders.get(VIBRATOR_ID);
         fakeVibrator.setCapabilities(IVibrator.CAP_COMPOSE_EFFECTS);
-        fakeVibrator.setSupportedPrimitives(VibrationEffect.Composition.PRIMITIVE_CLICK);
+        fakeVibrator.setSupportedPrimitives(PRIMITIVE_CLICK);
         fakeVibrator.setCompositionSizeMax(10);
 
         VibrationEffect effect = VibrationEffect.startComposition()
                 // Very long delay so thread will be cancelled after first PWLE is triggered.
-                .addPrimitive(VibrationEffect.Composition.PRIMITIVE_CLICK, 1f, 100)
+                .addPrimitive(PRIMITIVE_CLICK, 1f, 100)
                 .compose();
         VibrationEffect repeatingEffect = VibrationEffect.startComposition()
                 .repeatEffectIndefinitely(effect)
                 .compose();
         HalVibration vibration = startThreadAndDispatcher(repeatingEffect);
 
-        assertTrue(waitUntil(() -> !fakeVibrator.getEffectSegments(vibration.id).isEmpty(),
-                TEST_TIMEOUT_MILLIS));
+        assertThat(waitUntil(() -> !fakeVibrator.getEffectSegments(vibration.id).isEmpty(),
+                TEST_TIMEOUT_MILLIS)).isTrue();
         mVibrationConductor.notifyCancelled(
                 new Vibration.EndInfo(Status.CANCELLED_BY_SCREEN_OFF), /* immediate= */ false);
         waitForCompletion();
 
         // Composition size max was used to generate a single vibrate call with 10 primitives.
         verifyCallbacksTriggered(vibration, Status.CANCELLED_BY_SCREEN_OFF);
-        assertFalse(mControllers.get(VIBRATOR_ID).isVibrating());
-        assertEquals(10, fakeVibrator.getEffectSegments(vibration.id).size());
+        assertThat(mControllers.get(VIBRATOR_ID).isVibrating()).isFalse();
+        assertThat(fakeVibrator.getEffectSegments(vibration.id)).hasSize(10);
     }
 
     @Test
@@ -514,15 +548,16 @@
                 new long[]{5000, 500, 50}, amplitudes, 0);
         HalVibration vibration = startThreadAndDispatcher(effect);
 
-        assertTrue(waitUntil(() -> !fakeVibrator.getAmplitudes().isEmpty(), TEST_TIMEOUT_MILLIS));
+        assertThat(waitUntil(() -> !fakeVibrator.getAmplitudes().isEmpty(), TEST_TIMEOUT_MILLIS))
+                .isTrue();
         mVibrationConductor.notifyCancelled(
                 new Vibration.EndInfo(Status.CANCELLED_BY_USER), /* immediate= */ false);
         waitForCompletion();
 
         verifyCallbacksTriggered(vibration, Status.CANCELLED_BY_USER);
-        assertFalse(mControllers.get(VIBRATOR_ID).isVibrating());
-        assertEquals(Arrays.asList(expectedOneShot(5550)),
-                fakeVibrator.getEffectSegments(vibration.id));
+        assertThat(mControllers.get(VIBRATOR_ID).isVibrating()).isFalse();
+        assertThat(fakeVibrator.getEffectSegments(vibration.id))
+                .containsExactly(expectedOneShot(5550)).inOrder();
     }
 
     @LargeTest
@@ -538,42 +573,42 @@
                 /* amplitudes= */ new int[]{1, 2}, /* repeat= */ 0);
         HalVibration vibration = startThreadAndDispatcher(effect);
 
-        assertTrue(waitUntil(() -> fakeVibrator.getEffectSegments(vibration.id).size() > 1,
-                expectedOnDuration + TEST_TIMEOUT_MILLIS));
+        assertThat(waitUntil(() -> fakeVibrator.getEffectSegments(vibration.id).size() > 1,
+                expectedOnDuration + TEST_TIMEOUT_MILLIS)).isTrue();
         mVibrationConductor.notifyCancelled(
                 new Vibration.EndInfo(Status.CANCELLED_BY_USER), /* immediate= */ false);
         waitForCompletion();
 
         verifyCallbacksTriggered(vibration, Status.CANCELLED_BY_USER);
-        assertFalse(mControllers.get(VIBRATOR_ID).isVibrating());
+        assertThat(mControllers.get(VIBRATOR_ID).isVibrating()).isFalse();
         List<VibrationEffectSegment> effectSegments = fakeVibrator.getEffectSegments(vibration.id);
         // First time, turn vibrator ON for the expected fixed duration.
-        assertEquals(expectedOnDuration, effectSegments.get(0).getDuration());
+        assertThat(effectSegments.get(0).getDuration()).isEqualTo(expectedOnDuration);
         // Vibrator turns off in the middle of the second execution of the first step. Expect it to
         // be turned back ON at least for the fixed duration + the remaining duration of the step.
-        assertTrue(expectedOnDuration < effectSegments.get(1).getDuration());
+        assertThat(effectSegments.get(1).getDuration()).isGreaterThan(expectedOnDuration);
         // Set amplitudes for a cycle {1, 2}, start second loop then turn it back on to same value.
-        assertEquals(expectedAmplitudes(1, 2, 1, 1),
-                mVibratorProviders.get(VIBRATOR_ID).getAmplitudes().subList(0, 4));
+        assertThat(mVibratorProviders.get(VIBRATOR_ID).getAmplitudes().subList(0, 4))
+                .containsExactlyElementsIn(expectedAmplitudes(1, 2, 1, 1))
+                .inOrder();
     }
 
     @Test
     public void vibrate_singleVibratorPredefinedCancel_cancelsVibrationImmediately()
             throws Exception {
         mVibratorProviders.get(VIBRATOR_ID).setCapabilities(IVibrator.CAP_COMPOSE_EFFECTS);
-        mVibratorProviders.get(VIBRATOR_ID).setSupportedPrimitives(
-                VibrationEffect.Composition.PRIMITIVE_CLICK);
+        mVibratorProviders.get(VIBRATOR_ID).setSupportedPrimitives(PRIMITIVE_CLICK);
 
         VibrationEffect effect = VibrationEffect.startComposition()
-                .addPrimitive(VibrationEffect.Composition.PRIMITIVE_CLICK, 1f, 100)
-                .addPrimitive(VibrationEffect.Composition.PRIMITIVE_CLICK, 1f, 100)
-                .addPrimitive(VibrationEffect.Composition.PRIMITIVE_CLICK, 1f, 100)
+                .addPrimitive(PRIMITIVE_CLICK, 1f, 100)
+                .addPrimitive(PRIMITIVE_CLICK, 1f, 100)
+                .addPrimitive(PRIMITIVE_CLICK, 1f, 100)
                 .compose();
         HalVibration vibration = startThreadAndDispatcher(effect);
 
-        assertTrue(waitUntil(() -> mControllers.get(VIBRATOR_ID).isVibrating(),
-                TEST_TIMEOUT_MILLIS));
-        assertTrue(mThread.isRunningVibrationId(vibration.id));
+        assertThat(waitUntil(() -> mControllers.get(VIBRATOR_ID).isVibrating(),
+                TEST_TIMEOUT_MILLIS)).isTrue();
+        assertThat(mThread.isRunningVibrationId(vibration.id)).isTrue();
 
         // Run cancel in a separate thread so if VibrationThread.cancel blocks then this test should
         // fail at waitForCompletion(vibrationThread) if the vibration not cancelled immediately.
@@ -587,11 +622,11 @@
         cancellingThread.join();
 
         verifyCallbacksTriggered(vibration, Status.CANCELLED_BY_SETTINGS_UPDATE);
-        assertFalse(mControllers.get(VIBRATOR_ID).isVibrating());
+        assertThat(mControllers.get(VIBRATOR_ID).isVibrating()).isFalse();
     }
 
     @Test
-    @RequiresFlagsEnabled(android.os.vibrator.Flags.FLAG_VENDOR_VIBRATION_EFFECTS)
+    @EnableFlags(Flags.FLAG_VENDOR_VIBRATION_EFFECTS)
     public void vibrate_singleVibratorVendorEffectCancel_cancelsVibrationImmediately()
             throws Exception {
         mVibratorProviders.get(VIBRATOR_ID).setCapabilities(IVibrator.CAP_PERFORM_VENDOR_EFFECTS);
@@ -601,9 +636,9 @@
         VibrationEffect effect = VibrationEffect.createVendorEffect(createTestVendorData());
         HalVibration vibration = startThreadAndDispatcher(effect);
 
-        assertTrue(waitUntil(() -> mControllers.get(VIBRATOR_ID).isVibrating(),
-                TEST_TIMEOUT_MILLIS));
-        assertTrue(mThread.isRunningVibrationId(vibration.id));
+        assertThat(waitUntil(() -> mControllers.get(VIBRATOR_ID).isVibrating(),
+                TEST_TIMEOUT_MILLIS)).isTrue();
+        assertThat(mThread.isRunningVibrationId(vibration.id)).isTrue();
 
         // Run cancel in a separate thread so if VibrationThread.cancel blocks then this test should
         // fail at waitForCompletion(vibrationThread) if the vibration not cancelled immediately.
@@ -617,7 +652,7 @@
         cancellingThread.join();
 
         verifyCallbacksTriggered(vibration, Status.CANCELLED_BY_SETTINGS_UPDATE);
-        assertFalse(mControllers.get(VIBRATOR_ID).isVibrating());
+        assertThat(mControllers.get(VIBRATOR_ID).isVibrating()).isFalse();
     }
 
     @Test
@@ -628,9 +663,9 @@
         VibrationEffect effect = VibrationEffect.createWaveform(new long[]{100}, new int[]{100}, 0);
         HalVibration vibration = startThreadAndDispatcher(effect);
 
-        assertTrue(waitUntil(() -> mControllers.get(VIBRATOR_ID).isVibrating(),
-                TEST_TIMEOUT_MILLIS));
-        assertTrue(mThread.isRunningVibrationId(vibration.id));
+        assertThat(waitUntil(() -> mControllers.get(VIBRATOR_ID).isVibrating(),
+                TEST_TIMEOUT_MILLIS)).isTrue();
+        assertThat(mThread.isRunningVibrationId(vibration.id)).isTrue();
 
         // Run cancel in a separate thread so if VibrationThread.cancel blocks then this test should
         // fail at waitForCompletion(vibrationThread) if the vibration not cancelled immediately.
@@ -644,7 +679,7 @@
         cancellingThread.join();
 
         verifyCallbacksTriggered(vibration, Status.CANCELLED_BY_SCREEN_OFF);
-        assertFalse(mControllers.get(VIBRATOR_ID).isVibrating());
+        assertThat(mControllers.get(VIBRATOR_ID).isVibrating()).isFalse();
     }
 
     @Test
@@ -657,12 +692,12 @@
 
         verify(mManagerHooks).noteVibratorOn(eq(UID), eq(20L));
         verify(mManagerHooks).noteVibratorOff(eq(UID));
-        verify(mControllerCallbacks).onComplete(eq(VIBRATOR_ID), eq(vibration.id));
+        verify(mControllerCallbacks).onComplete(eq(VIBRATOR_ID), eq(vibration.id), anyLong());
         verifyCallbacksTriggered(vibration, Status.FINISHED);
-        assertFalse(mControllers.get(VIBRATOR_ID).isVibrating());
+        assertThat(mControllers.get(VIBRATOR_ID).isVibrating()).isFalse();
 
-        assertEquals(Arrays.asList(expectedPrebaked(VibrationEffect.EFFECT_THUD)),
-                mVibratorProviders.get(VIBRATOR_ID).getEffectSegments(vibration.id));
+        assertThat(mVibratorProviders.get(VIBRATOR_ID).getEffectSegments(vibration.id))
+                .containsExactly(expectedPrebaked(VibrationEffect.EFFECT_THUD)).inOrder();
     }
 
     @Test
@@ -671,37 +706,39 @@
 
         VibrationEffect fallback = VibrationEffect.createOneShot(10, 100);
         HalVibration vibration = createVibration(CombinedVibration.createParallel(
-                VibrationEffect.get(VibrationEffect.EFFECT_CLICK)));
+                VibrationEffect.get(EFFECT_CLICK)));
         vibration.fillFallbacks(unused -> fallback);
         startThreadAndDispatcher(vibration);
         waitForCompletion();
 
         verify(mManagerHooks).noteVibratorOn(eq(UID), eq(10L));
         verify(mManagerHooks).noteVibratorOff(eq(UID));
-        verify(mControllerCallbacks).onComplete(eq(VIBRATOR_ID), eq(vibration.id));
+        verify(mControllerCallbacks).onComplete(eq(VIBRATOR_ID), eq(vibration.id), anyLong());
         verifyCallbacksTriggered(vibration, Status.FINISHED);
-        assertFalse(mControllers.get(VIBRATOR_ID).isVibrating());
+        assertThat(mControllers.get(VIBRATOR_ID).isVibrating()).isFalse();
 
-        assertEquals(Arrays.asList(expectedOneShot(10)),
-                mVibratorProviders.get(VIBRATOR_ID).getEffectSegments(vibration.id));
-        assertEquals(expectedAmplitudes(100), mVibratorProviders.get(VIBRATOR_ID).getAmplitudes());
+        assertThat(mVibratorProviders.get(VIBRATOR_ID).getEffectSegments(vibration.id))
+                .containsExactly(expectedOneShot(10)).inOrder();
+        assertThat(mVibratorProviders.get(VIBRATOR_ID).getAmplitudes())
+                .containsExactlyElementsIn(expectedAmplitudes(100)).inOrder();
     }
 
     @Test
     public void vibrate_singleVibratorPrebakedAndUnsupportedEffect_ignoresVibration() {
-        VibrationEffect effect = VibrationEffect.get(VibrationEffect.EFFECT_CLICK);
+        VibrationEffect effect = VibrationEffect.get(EFFECT_CLICK);
         HalVibration vibration = startThreadAndDispatcher(effect);
         waitForCompletion();
 
         verify(mManagerHooks).noteVibratorOn(eq(UID), eq(0L));
         verify(mManagerHooks, never()).noteVibratorOff(eq(UID));
-        verify(mControllerCallbacks, never()).onComplete(eq(VIBRATOR_ID), eq(vibration.id));
+        verify(mControllerCallbacks, never())
+                .onComplete(eq(VIBRATOR_ID), eq(vibration.id), anyLong());
         verifyCallbacksTriggered(vibration, Status.IGNORED_UNSUPPORTED);
-        assertTrue(mVibratorProviders.get(VIBRATOR_ID).getEffectSegments(vibration.id).isEmpty());
+        assertThat(mVibratorProviders.get(VIBRATOR_ID).getEffectSegments(vibration.id)).isEmpty();
     }
 
     @Test
-    @RequiresFlagsEnabled(android.os.vibrator.Flags.FLAG_VENDOR_VIBRATION_EFFECTS)
+    @EnableFlags(Flags.FLAG_VENDOR_VIBRATION_EFFECTS)
     public void vibrate_singleVibratorVendorEffect_runsVibration() {
         mVibratorProviders.get(1).setCapabilities(IVibrator.CAP_PERFORM_VENDOR_EFFECTS);
 
@@ -712,104 +749,101 @@
         verify(mManagerHooks).noteVibratorOn(eq(UID),
                 eq(PerformVendorEffectVibratorStep.VENDOR_EFFECT_MAX_DURATION_MS));
         verify(mManagerHooks).noteVibratorOff(eq(UID));
-        verify(mControllerCallbacks).onComplete(eq(VIBRATOR_ID), eq(vibration.id));
+        verify(mControllerCallbacks).onComplete(eq(VIBRATOR_ID), eq(vibration.id), anyLong());
         verifyCallbacksTriggered(vibration, Status.FINISHED);
         assertThat(mControllers.get(VIBRATOR_ID).isVibrating()).isFalse();
 
         assertThat(mVibratorProviders.get(VIBRATOR_ID).getVendorEffects(vibration.id))
-                .containsExactly(effect)
-                .inOrder();
+                .containsExactly(effect).inOrder();
     }
 
     @Test
     public void vibrate_singleVibratorComposed_runsVibration() {
         FakeVibratorControllerProvider fakeVibrator = mVibratorProviders.get(VIBRATOR_ID);
         fakeVibrator.setCapabilities(IVibrator.CAP_COMPOSE_EFFECTS);
-        fakeVibrator.setSupportedPrimitives(VibrationEffect.Composition.PRIMITIVE_CLICK,
-                VibrationEffect.Composition.PRIMITIVE_TICK);
+        fakeVibrator.setSupportedPrimitives(PRIMITIVE_CLICK, PRIMITIVE_TICK);
 
         VibrationEffect effect = VibrationEffect.startComposition()
-                .addPrimitive(VibrationEffect.Composition.PRIMITIVE_CLICK, 1f)
-                .addPrimitive(VibrationEffect.Composition.PRIMITIVE_TICK, 0.5f)
+                .addPrimitive(PRIMITIVE_CLICK, 1f)
+                .addPrimitive(PRIMITIVE_TICK, 0.5f)
                 .compose();
         HalVibration vibration = startThreadAndDispatcher(effect);
         waitForCompletion();
 
         verify(mManagerHooks).noteVibratorOn(eq(UID), eq(40L));
         verify(mManagerHooks).noteVibratorOff(eq(UID));
-        verify(mControllerCallbacks).onComplete(eq(VIBRATOR_ID), eq(vibration.id));
+        verify(mControllerCallbacks).onComplete(eq(VIBRATOR_ID), eq(vibration.id), anyLong());
         verifyCallbacksTriggered(vibration, Status.FINISHED);
-        assertFalse(mControllers.get(VIBRATOR_ID).isVibrating());
-        assertEquals(Arrays.asList(
-                expectedPrimitive(VibrationEffect.Composition.PRIMITIVE_CLICK, 1, 0),
-                expectedPrimitive(VibrationEffect.Composition.PRIMITIVE_TICK, 0.5f, 0)),
-                fakeVibrator.getEffectSegments(vibration.id));
+        assertThat(mControllers.get(VIBRATOR_ID).isVibrating()).isFalse();
+        assertThat(fakeVibrator.getEffectSegments(vibration.id))
+                .containsExactly(
+                        expectedPrimitive(PRIMITIVE_CLICK, 1, 0),
+                        expectedPrimitive(PRIMITIVE_TICK, 0.5f, 0))
+                .inOrder();
     }
 
     @Test
     @DisableFlags(Flags.FLAG_PRIMITIVE_COMPOSITION_ABSOLUTE_DELAY)
     public void vibrate_singleVibratorComposedAndNoCapability_triggersHalAndReturnsUnsupported() {
         VibrationEffect effect = VibrationEffect.startComposition()
-                .addPrimitive(VibrationEffect.Composition.PRIMITIVE_CLICK, 1f)
+                .addPrimitive(PRIMITIVE_CLICK, 1f)
                 .compose();
         HalVibration vibration = startThreadAndDispatcher(effect);
         waitForCompletion();
 
         verify(mManagerHooks).noteVibratorOn(eq(UID), eq(0L));
         verify(mManagerHooks, never()).noteVibratorOff(eq(UID));
-        verify(mControllerCallbacks, never()).onComplete(eq(VIBRATOR_ID), eq(vibration.id));
+        verify(mControllerCallbacks, never())
+                .onComplete(eq(VIBRATOR_ID), eq(vibration.id), anyLong());
         verifyCallbacksTriggered(vibration, Status.IGNORED_UNSUPPORTED);
-        assertTrue(mVibratorProviders.get(VIBRATOR_ID).getEffectSegments(vibration.id).isEmpty());
+        assertThat(mVibratorProviders.get(VIBRATOR_ID).getEffectSegments(vibration.id)).isEmpty();
     }
 
     @Test
     @EnableFlags(Flags.FLAG_PRIMITIVE_COMPOSITION_ABSOLUTE_DELAY)
     public void vibrate_singleVibratorComposedAndNoCapability_ignoresVibration() {
         VibrationEffect effect = VibrationEffect.startComposition()
-                .addPrimitive(VibrationEffect.Composition.PRIMITIVE_CLICK, 1f)
+                .addPrimitive(PRIMITIVE_CLICK, 1f)
                 .compose();
         HalVibration vibration = startThreadAndDispatcher(effect);
         waitForCompletion();
 
         verify(mManagerHooks, never()).noteVibratorOn(eq(UID), anyLong());
         verify(mManagerHooks, never()).noteVibratorOff(eq(UID));
-        verify(mControllerCallbacks, never()).onComplete(eq(VIBRATOR_ID), eq(vibration.id));
+        verify(mControllerCallbacks, never())
+                .onComplete(eq(VIBRATOR_ID), eq(vibration.id), anyLong());
         verifyCallbacksTriggered(vibration, Status.IGNORED_UNSUPPORTED);
-        assertTrue(mVibratorProviders.get(VIBRATOR_ID).getEffectSegments(vibration.id).isEmpty());
+        assertThat(mVibratorProviders.get(VIBRATOR_ID).getEffectSegments(vibration.id)).isEmpty();
     }
 
     @Test
     public void vibrate_singleVibratorLargeComposition_splitsVibratorComposeCalls() {
         FakeVibratorControllerProvider fakeVibrator = mVibratorProviders.get(VIBRATOR_ID);
         fakeVibrator.setCapabilities(IVibrator.CAP_COMPOSE_EFFECTS);
-        fakeVibrator.setSupportedPrimitives(
-                VibrationEffect.Composition.PRIMITIVE_CLICK,
-                VibrationEffect.Composition.PRIMITIVE_TICK,
-                VibrationEffect.Composition.PRIMITIVE_SPIN);
+        fakeVibrator.setSupportedPrimitives(PRIMITIVE_CLICK, PRIMITIVE_TICK, PRIMITIVE_SPIN);
         fakeVibrator.setCompositionSizeMax(2);
 
         VibrationEffect effect = VibrationEffect.startComposition()
-                .addPrimitive(VibrationEffect.Composition.PRIMITIVE_CLICK, 1f)
-                .addPrimitive(VibrationEffect.Composition.PRIMITIVE_TICK, 0.5f)
-                .addPrimitive(VibrationEffect.Composition.PRIMITIVE_SPIN, 0.8f)
+                .addPrimitive(PRIMITIVE_CLICK, 1f)
+                .addPrimitive(PRIMITIVE_TICK, 0.5f)
+                .addPrimitive(PRIMITIVE_SPIN, 0.8f)
                 .compose();
         HalVibration vibration = startThreadAndDispatcher(effect);
         waitForCompletion();
 
         verifyCallbacksTriggered(vibration, Status.FINISHED);
         // Vibrator compose called twice.
-        verify(mControllerCallbacks, times(2)).onComplete(eq(VIBRATOR_ID), eq(vibration.id));
-        assertEquals(3, fakeVibrator.getEffectSegments(vibration.id).size());
+        verify(mControllerCallbacks, times(2))
+                .onComplete(eq(VIBRATOR_ID), eq(vibration.id), anyLong());
+        assertThat(fakeVibrator.getEffectSegments(vibration.id)).hasSize(3);
     }
 
     @Test
-    @DisableFlags(android.os.vibrator.Flags.FLAG_NORMALIZED_PWLE_EFFECTS)
+    @DisableFlags(Flags.FLAG_NORMALIZED_PWLE_EFFECTS)
     public void vibrate_singleVibratorComposedEffects_runsDifferentVibrations() {
         FakeVibratorControllerProvider fakeVibrator = mVibratorProviders.get(VIBRATOR_ID);
-        fakeVibrator.setSupportedEffects(VibrationEffect.EFFECT_CLICK);
-        fakeVibrator.setSupportedPrimitives(
-                VibrationEffect.Composition.PRIMITIVE_CLICK,
-                VibrationEffect.Composition.PRIMITIVE_TICK);
+        fakeVibrator.setSupportedEffects(EFFECT_CLICK);
+        fakeVibrator.setSupportedPrimitives(PRIMITIVE_CLICK, PRIMITIVE_TICK);
         fakeVibrator.setCapabilities(IVibrator.CAP_COMPOSE_EFFECTS,
                 IVibrator.CAP_COMPOSE_PWLE_EFFECTS, IVibrator.CAP_AMPLITUDE_CONTROL);
         fakeVibrator.setMinFrequency(100);
@@ -820,15 +854,15 @@
 
         VibrationEffect effect = VibrationEffect.startComposition()
                 .addEffect(VibrationEffect.createOneShot(10, 100))
-                .addPrimitive(VibrationEffect.Composition.PRIMITIVE_CLICK, 1f)
-                .addPrimitive(VibrationEffect.Composition.PRIMITIVE_TICK, 0.5f)
-                .addEffect(VibrationEffect.get(VibrationEffect.EFFECT_CLICK))
+                .addPrimitive(PRIMITIVE_CLICK, 1f)
+                .addPrimitive(PRIMITIVE_TICK, 0.5f)
+                .addEffect(VibrationEffect.get(EFFECT_CLICK))
                 .addEffect(VibrationEffect.startWaveform()
                         .addTransition(Duration.ofMillis(10),
                                 targetAmplitude(1), targetFrequency(100))
                         .addTransition(Duration.ofMillis(20), targetFrequency(120))
                         .build())
-                .addEffect(VibrationEffect.get(VibrationEffect.EFFECT_CLICK))
+                .addEffect(VibrationEffect.get(EFFECT_CLICK))
                 .compose();
         HalVibration vibration = startThreadAndDispatcher(effect);
         waitForCompletion();
@@ -836,38 +870,41 @@
         // Use first duration the vibrator is turned on since we cannot estimate the clicks.
         verify(mManagerHooks).noteVibratorOn(eq(UID), eq(10L));
         verify(mManagerHooks).noteVibratorOff(eq(UID));
-        verify(mControllerCallbacks, times(5)).onComplete(eq(VIBRATOR_ID), eq(vibration.id));
+        verify(mControllerCallbacks, times(5))
+                .onComplete(eq(VIBRATOR_ID), eq(vibration.id), anyLong());
         verifyCallbacksTriggered(vibration, Status.FINISHED);
-        assertFalse(mControllers.get(VIBRATOR_ID).isVibrating());
-        assertEquals(Arrays.asList(
-                expectedOneShot(10),
-                expectedPrimitive(VibrationEffect.Composition.PRIMITIVE_CLICK, 1, 0),
-                expectedPrimitive(VibrationEffect.Composition.PRIMITIVE_TICK, 0.5f, 0),
-                expectedPrebaked(VibrationEffect.EFFECT_CLICK),
-                expectedRamp(/* startAmplitude= */ 0, /* endAmplitude= */ 0.5f,
-                        /* startFrequencyHz= */ 150, /* endFrequencyHz= */ 100, /* duration= */ 10),
-                expectedRamp(/* startAmplitude= */ 0.5f, /* endAmplitude= */ 0.7f,
-                        /* startFrequencyHz= */ 100, /* endFrequencyHz= */ 120, /* duration= */ 20),
-                expectedPrebaked(VibrationEffect.EFFECT_CLICK)),
-                mVibratorProviders.get(VIBRATOR_ID).getEffectSegments(vibration.id));
-        assertEquals(expectedAmplitudes(100), mVibratorProviders.get(VIBRATOR_ID).getAmplitudes());
+        assertThat(mControllers.get(VIBRATOR_ID).isVibrating()).isFalse();
+        assertThat(mVibratorProviders.get(VIBRATOR_ID).getEffectSegments(vibration.id))
+                .containsExactly(
+                        expectedOneShot(10),
+                        expectedPrimitive(PRIMITIVE_CLICK, 1, 0),
+                        expectedPrimitive(PRIMITIVE_TICK, 0.5f, 0),
+                        expectedPrebaked(EFFECT_CLICK),
+                        expectedRamp(/* startAmplitude= */ 0, /* endAmplitude= */ 0.5f,
+                                /* startFrequencyHz= */ 150, /* endFrequencyHz= */ 100,
+                                /* duration= */ 10),
+                        expectedRamp(/* startAmplitude= */ 0.5f, /* endAmplitude= */ 0.7f,
+                                /* startFrequencyHz= */ 100, /* endFrequencyHz= */ 120,
+                                /* duration= */ 20),
+                        expectedPrebaked(EFFECT_CLICK))
+                .inOrder();
+        assertThat(mVibratorProviders.get(VIBRATOR_ID).getAmplitudes())
+                .containsExactlyElementsIn(expectedAmplitudes(100)).inOrder();
     }
 
     @Test
     public void vibrate_singleVibratorComposedWithFallback_replacedInTheMiddleOfComposition() {
         FakeVibratorControllerProvider fakeVibrator = mVibratorProviders.get(VIBRATOR_ID);
-        fakeVibrator.setSupportedEffects(VibrationEffect.EFFECT_CLICK);
-        fakeVibrator.setSupportedPrimitives(
-                VibrationEffect.Composition.PRIMITIVE_CLICK,
-                VibrationEffect.Composition.PRIMITIVE_TICK);
+        fakeVibrator.setSupportedEffects(EFFECT_CLICK);
+        fakeVibrator.setSupportedPrimitives(PRIMITIVE_CLICK, PRIMITIVE_TICK);
         fakeVibrator.setCapabilities(IVibrator.CAP_COMPOSE_EFFECTS);
 
         VibrationEffect fallback = VibrationEffect.createOneShot(10, 100);
         VibrationEffect effect = VibrationEffect.startComposition()
-                .addEffect(VibrationEffect.get(VibrationEffect.EFFECT_CLICK))
-                .addPrimitive(VibrationEffect.Composition.PRIMITIVE_CLICK, 1f)
-                .addEffect(VibrationEffect.get(VibrationEffect.EFFECT_TICK))
-                .addPrimitive(VibrationEffect.Composition.PRIMITIVE_TICK, 0.5f)
+                .addEffect(VibrationEffect.get(EFFECT_CLICK))
+                .addPrimitive(PRIMITIVE_CLICK, 1f)
+                .addEffect(VibrationEffect.get(EFFECT_TICK))
+                .addPrimitive(PRIMITIVE_TICK, 0.5f)
                 .compose();
         HalVibration vibration = createVibration(CombinedVibration.createParallel(effect));
         vibration.fillFallbacks(unused -> fallback);
@@ -877,24 +914,26 @@
         // Use first duration the vibrator is turned on since we cannot estimate the clicks.
         verify(mManagerHooks).noteVibratorOn(eq(UID), anyLong());
         verify(mManagerHooks).noteVibratorOff(eq(UID));
-        verify(mControllerCallbacks, times(4)).onComplete(eq(VIBRATOR_ID), eq(vibration.id));
+        verify(mControllerCallbacks, times(4))
+                .onComplete(eq(VIBRATOR_ID), eq(vibration.id), anyLong());
         verifyCallbacksTriggered(vibration, Status.FINISHED);
-        assertFalse(mControllers.get(VIBRATOR_ID).isVibrating());
+        assertThat(mControllers.get(VIBRATOR_ID).isVibrating()).isFalse();
 
         List<VibrationEffectSegment> segments =
                 mVibratorProviders.get(VIBRATOR_ID).getEffectSegments(vibration.id);
-        assertTrue("Wrong segments: " + segments, segments.size() >= 4);
-        assertTrue(segments.get(0) instanceof PrebakedSegment);
-        assertTrue(segments.get(1) instanceof PrimitiveSegment);
+        assertWithMessage("Wrong segments: %s", segments).that(segments.size()).isGreaterThan(3);
+        assertThat(segments.get(0)).isInstanceOf(PrebakedSegment.class);
+        assertThat(segments.get(1)).isInstanceOf(PrimitiveSegment.class);
         for (int i = 2; i < segments.size() - 1; i++) {
             // One or more step segments as fallback for the EFFECT_TICK.
-            assertTrue(segments.get(i) instanceof StepSegment);
+            assertWithMessage("For segment index %s", i)
+                    .that(segments.get(i)).isInstanceOf(StepSegment.class);
         }
-        assertTrue(segments.get(segments.size() - 1) instanceof PrimitiveSegment);
+        assertThat(segments.get(segments.size() - 1)).isInstanceOf(PrimitiveSegment.class);
     }
 
     @Test
-    @EnableFlags(android.os.vibrator.Flags.FLAG_NORMALIZED_PWLE_EFFECTS)
+    @EnableFlags(Flags.FLAG_NORMALIZED_PWLE_EFFECTS)
     public void vibrate_singleVibratorPwle_runsComposePwleV2() {
         FakeVibratorControllerProvider fakeVibrator = mVibratorProviders.get(VIBRATOR_ID);
         fakeVibrator.setCapabilities(IVibrator.CAP_COMPOSE_PWLE_EFFECTS_V2);
@@ -915,21 +954,22 @@
 
         verify(mManagerHooks).noteVibratorOn(eq(UID), eq(100L));
         verify(mManagerHooks).noteVibratorOff(eq(UID));
-        verify(mControllerCallbacks).onComplete(eq(VIBRATOR_ID), eq(vibration.id));
+        verify(mControllerCallbacks).onComplete(eq(VIBRATOR_ID), eq(vibration.id), anyLong());
         verifyCallbacksTriggered(vibration, Status.FINISHED);
-        assertFalse(mControllers.get(VIBRATOR_ID).isVibrating());
-        assertEquals(Arrays.asList(
-                expectedPwle(/*amplitude=*/ 0.0f, /*frequencyHz=*/ 60f, /*timeMillis=*/ 0),
-                expectedPwle(/*amplitude=*/ 0.1f, /*frequencyHz=*/ 60f, /*timeMillis=*/ 20),
-                expectedPwle(/*amplitude=*/ 0.3f, /*frequencyHz=*/ 100f, /*timeMillis=*/ 30),
-                expectedPwle(/*amplitude=*/ 0.4f, /*frequencyHz=*/ 120f, /*timeMillis=*/ 20),
-                expectedPwle(/*amplitude=*/ 0.0f, /*frequencyHz=*/ 120f, /*timeMillis=*/ 30)
-        ), fakeVibrator.getEffectPwlePoints(vibration.id));
+        assertThat(mControllers.get(VIBRATOR_ID).isVibrating()).isFalse();
+        assertThat(fakeVibrator.getEffectPwlePoints(vibration.id))
+                .containsExactly(
+                        expectedPwle(0.0f, 60f, 0),
+                        expectedPwle(0.1f, 60f, 20),
+                        expectedPwle(0.3f, 100f, 30),
+                        expectedPwle(0.4f, 120f, 20),
+                        expectedPwle(0.0f, 120f, 30))
+                .inOrder();
 
     }
 
     @Test
-    @EnableFlags(android.os.vibrator.Flags.FLAG_NORMALIZED_PWLE_EFFECTS)
+    @EnableFlags(Flags.FLAG_NORMALIZED_PWLE_EFFECTS)
     public void vibrate_singleVibratorBasicPwle_runsComposePwleV2() {
         FakeVibratorControllerProvider fakeVibrator = mVibratorProviders.get(VIBRATOR_ID);
         fakeVibrator.setCapabilities(IVibrator.CAP_COMPOSE_PWLE_EFFECTS_V2);
@@ -951,20 +991,21 @@
 
         verify(mManagerHooks).noteVibratorOn(eq(UID), eq(220L));
         verify(mManagerHooks).noteVibratorOff(eq(UID));
-        verify(mControllerCallbacks).onComplete(eq(VIBRATOR_ID), eq(vibration.id));
+        verify(mControllerCallbacks).onComplete(eq(VIBRATOR_ID), eq(vibration.id), anyLong());
         verifyCallbacksTriggered(vibration, Status.FINISHED);
-        assertFalse(mControllers.get(VIBRATOR_ID).isVibrating());
-        assertEquals(Arrays.asList(
-                expectedPwle(/*amplitude=*/ 0.0f, /*frequencyHz=*/ 150f, /*timeMillis=*/ 0),
-                expectedPwle(/*amplitude=*/ 1.0f, /*frequencyHz=*/ 150f, /*timeMillis=*/ 20),
-                expectedPwle(/*amplitude=*/ 1.0f, /*frequencyHz=*/ 150f, /*timeMillis=*/ 100),
-                expectedPwle(/*amplitude=*/ 0.0f, /*frequencyHz=*/ 150f, /*timeMillis=*/ 100)
-        ), fakeVibrator.getEffectPwlePoints(vibration.id));
+        assertThat(mControllers.get(VIBRATOR_ID).isVibrating()).isFalse();
+        assertThat(fakeVibrator.getEffectPwlePoints(vibration.id))
+                .containsExactly(
+                        expectedPwle(0.0f, 150f, 0),
+                        expectedPwle(1.0f, 150f, 20),
+                        expectedPwle(1.0f, 150f, 100),
+                        expectedPwle(0.0f, 150f, 100))
+                .inOrder();
 
     }
 
     @Test
-    @EnableFlags(android.os.vibrator.Flags.FLAG_NORMALIZED_PWLE_EFFECTS)
+    @EnableFlags(Flags.FLAG_NORMALIZED_PWLE_EFFECTS)
     public void vibrate_singleVibratorPwle_withInitialFrequency_runsComposePwleV2() {
         FakeVibratorControllerProvider fakeVibrator = mVibratorProviders.get(VIBRATOR_ID);
         fakeVibrator.setCapabilities(IVibrator.CAP_COMPOSE_PWLE_EFFECTS_V2);
@@ -987,21 +1028,21 @@
 
         verify(mManagerHooks).noteVibratorOn(eq(UID), eq(100L));
         verify(mManagerHooks).noteVibratorOff(eq(UID));
-        verify(mControllerCallbacks).onComplete(eq(VIBRATOR_ID), eq(vibration.id));
+        verify(mControllerCallbacks).onComplete(eq(VIBRATOR_ID), eq(vibration.id), anyLong());
         verifyCallbacksTriggered(vibration, Status.FINISHED);
-        assertFalse(mControllers.get(VIBRATOR_ID).isVibrating());
-        assertEquals(Arrays.asList(
-                expectedPwle(/*amplitude=*/ 0.0f, /*frequencyHz=*/ 30f, /*timeMillis=*/ 0),
-                expectedPwle(/*amplitude=*/ 0.1f, /*frequencyHz=*/ 60f, /*timeMillis=*/ 20),
-                expectedPwle(/*amplitude=*/ 0.3f, /*frequencyHz=*/ 100f, /*timeMillis=*/ 30),
-                expectedPwle(/*amplitude=*/ 0.4f, /*frequencyHz=*/ 120f, /*timeMillis=*/ 20),
-                expectedPwle(/*amplitude=*/ 0.0f, /*frequencyHz=*/ 120f, /*timeMillis=*/ 30)
-        ), fakeVibrator.getEffectPwlePoints(vibration.id));
-
+        assertThat(mControllers.get(VIBRATOR_ID).isVibrating()).isFalse();
+        assertThat(fakeVibrator.getEffectPwlePoints(vibration.id))
+                .containsExactly(
+                        expectedPwle(0.0f, 30f, 0),
+                        expectedPwle(0.1f, 60f, 20),
+                        expectedPwle(0.3f, 100f, 30),
+                        expectedPwle(0.4f, 120f, 20),
+                        expectedPwle(0.0f, 120f, 30))
+                .inOrder();
     }
 
     @Test
-    @EnableFlags(android.os.vibrator.Flags.FLAG_NORMALIZED_PWLE_EFFECTS)
+    @EnableFlags(Flags.FLAG_NORMALIZED_PWLE_EFFECTS)
     public void vibrate_singleVibratorPwle_TooManyControlPoints_splitsAndRunsComposePwleV2() {
         FakeVibratorControllerProvider fakeVibrator = mVibratorProviders.get(VIBRATOR_ID);
         fakeVibrator.setCapabilities(IVibrator.CAP_COMPOSE_PWLE_EFFECTS_V2);
@@ -1027,23 +1068,23 @@
         verifyCallbacksTriggered(vibration, Status.FINISHED);
         // Vibrator compose called 3 times with 2 segments instead of 2 times with 3 segments.
         // Using best split points instead of max-packing PWLEs.
-        verify(mControllerCallbacks, times(3)).onComplete(eq(VIBRATOR_ID), eq(vibration.id));
-        assertFalse(mControllers.get(VIBRATOR_ID).isVibrating());
-
-        assertEquals(Arrays.asList(
-                expectedPwle(/*amplitude=*/ 0.0f, /*frequencyHz=*/ 100f, /*timeMillis=*/ 0),
-                expectedPwle(/*amplitude=*/ 0.8f, /*frequencyHz=*/ 100f, /*timeMillis=*/ 30),
-                expectedPwle(/*amplitude=*/ 0.0f, /*frequencyHz=*/ 100f, /*timeMillis=*/ 30),
-                expectedPwle(/*amplitude=*/ 0.9f, /*frequencyHz=*/ 100f, /*timeMillis=*/ 0),
-                expectedPwle(/*amplitude=*/ 0.4f, /*frequencyHz=*/ 100f, /*timeMillis=*/ 30),
-                expectedPwle(/*amplitude=*/ 0.6f, /*frequencyHz=*/ 100f, /*timeMillis=*/ 0),
-                expectedPwle(/*amplitude=*/ 0.7f, /*frequencyHz=*/ 100f, /*timeMillis=*/ 30)
-        ), fakeVibrator.getEffectPwlePoints(vibration.id));
-
+        verify(mControllerCallbacks, times(3))
+                .onComplete(eq(VIBRATOR_ID), eq(vibration.id), anyLong());
+        assertThat(mControllers.get(VIBRATOR_ID).isVibrating()).isFalse();
+        assertThat(fakeVibrator.getEffectPwlePoints(vibration.id))
+                .containsExactly(
+                        expectedPwle(0.0f, 100f, 0),
+                        expectedPwle(0.8f, 100f, 30),
+                        expectedPwle(0.0f, 100f, 30),
+                        expectedPwle(0.9f, 100f, 0),
+                        expectedPwle(0.4f, 100f, 30),
+                        expectedPwle(0.6f, 100f, 0),
+                        expectedPwle(0.7f, 100f, 30))
+                .inOrder();
     }
 
     @Test
-    @DisableFlags(android.os.vibrator.Flags.FLAG_NORMALIZED_PWLE_EFFECTS)
+    @DisableFlags(Flags.FLAG_NORMALIZED_PWLE_EFFECTS)
     public void vibrate_singleVibratorPwle_runsComposePwle() {
         FakeVibratorControllerProvider fakeVibrator = mVibratorProviders.get(VIBRATOR_ID);
         fakeVibrator.setCapabilities(IVibrator.CAP_COMPOSE_PWLE_EFFECTS);
@@ -1066,19 +1107,23 @@
 
         verify(mManagerHooks).noteVibratorOn(eq(UID), eq(100L));
         verify(mManagerHooks).noteVibratorOff(eq(UID));
-        verify(mControllerCallbacks).onComplete(eq(VIBRATOR_ID), eq(vibration.id));
+        verify(mControllerCallbacks).onComplete(eq(VIBRATOR_ID), eq(vibration.id), anyLong());
         verifyCallbacksTriggered(vibration, Status.FINISHED);
-        assertFalse(mControllers.get(VIBRATOR_ID).isVibrating());
-        assertEquals(Arrays.asList(
-                expectedRamp(/* amplitude= */ 1, /* frequencyHz= */ 150, /* duration= */ 10),
-                expectedRamp(/* startAmplitude= */ 1, /* endAmplitude= */ 0,
-                        /* startFrequencyHz= */ 150, /* endFrequencyHz= */ 150, /* duration= */ 20),
-                expectedRamp(/* amplitude= */ 0.5f, /* frequencyHz= */ 100, /* duration= */ 30),
-                expectedRamp(/* startAmplitude= */ 0.5f, /* endAmplitude= */ 0.6f,
-                        /* startFrequencyHz= */ 100, /* endFrequencyHz= */ 200,
-                        /* duration= */ 40)),
-                fakeVibrator.getEffectSegments(vibration.id));
-        assertEquals(Arrays.asList(Braking.CLAB), fakeVibrator.getBraking(vibration.id));
+        assertThat(mControllers.get(VIBRATOR_ID).isVibrating()).isFalse();
+        assertThat(fakeVibrator.getEffectSegments(vibration.id))
+                .containsExactly(
+                        expectedRamp(/* amplitude= */ 1, /* frequencyHz= */ 150,
+                                /* duration= */ 10),
+                        expectedRamp(/* startAmplitude= */ 1, /* endAmplitude= */ 0,
+                                /* startFrequencyHz= */ 150, /* endFrequencyHz= */ 150,
+                                /* duration= */ 20),
+                        expectedRamp(/* amplitude= */ 0.5f, /* frequencyHz= */ 100,
+                                /* duration= */ 30),
+                        expectedRamp(/* startAmplitude= */ 0.5f, /* endAmplitude= */ 0.6f,
+                                /* startFrequencyHz= */ 100, /* endFrequencyHz= */ 200,
+                                /* duration= */ 40))
+                .inOrder();
+        assertThat(fakeVibrator.getBraking(vibration.id)).containsExactly(Braking.CLAB).inOrder();
     }
 
     @Test
@@ -1109,8 +1154,9 @@
 
         // Vibrator compose called 3 times with 2 segments instead of 2 times with 3 segments.
         // Using best split points instead of max-packing PWLEs.
-        verify(mControllerCallbacks, times(3)).onComplete(eq(VIBRATOR_ID), eq(vibration.id));
-        assertEquals(6, fakeVibrator.getEffectSegments(vibration.id).size());
+        verify(mControllerCallbacks, times(3))
+                .onComplete(eq(VIBRATOR_ID), eq(vibration.id), anyLong());
+        assertThat(fakeVibrator.getEffectSegments(vibration.id)).hasSize(6);
     }
 
     @Test
@@ -1121,15 +1167,16 @@
         VibrationEffect effect = VibrationEffect.createWaveform(new long[]{5}, new int[]{100}, 0);
         HalVibration vibration = startThreadAndDispatcher(effect);
 
-        assertTrue(waitUntil(() -> fakeVibrator.getAmplitudes().size() > 2, TEST_TIMEOUT_MILLIS));
+        assertThat(waitUntil(() -> fakeVibrator.getAmplitudes().size() > 2, TEST_TIMEOUT_MILLIS))
+                .isTrue();
         // Vibration still running after 2 cycles.
-        assertTrue(mThread.isRunningVibrationId(vibration.id));
-        assertTrue(mControllers.get(VIBRATOR_ID).isVibrating());
+        assertThat(mThread.isRunningVibrationId(vibration.id)).isTrue();
+        assertThat(mControllers.get(VIBRATOR_ID).isVibrating()).isTrue();
 
         mVibrationConductor.notifyCancelled(
                 new Vibration.EndInfo(Status.CANCELLED_BINDER_DIED), /* immediate= */ false);
         waitForCompletion();
-        assertFalse(mControllers.get(VIBRATOR_ID).isVibrating());
+        assertThat(mControllers.get(VIBRATOR_ID).isVibrating()).isFalse();
 
         verifyCallbacksTriggered(vibration, Status.CANCELLED_BINDER_DIED);
     }
@@ -1149,72 +1196,71 @@
 
     @Test
     public void vibrate_multipleExistingAndMissingVibrators_vibratesOnlyExistingOnes() {
-        mVibratorProviders.get(1).setSupportedEffects(VibrationEffect.EFFECT_TICK);
+        mVibratorProviders.get(1).setSupportedEffects(EFFECT_TICK);
 
         CombinedVibration effect = CombinedVibration.startParallel()
-                .addVibrator(VIBRATOR_ID, VibrationEffect.get(VibrationEffect.EFFECT_TICK))
-                .addVibrator(2, VibrationEffect.get(VibrationEffect.EFFECT_TICK))
+                .addVibrator(VIBRATOR_ID, VibrationEffect.get(EFFECT_TICK))
+                .addVibrator(2, VibrationEffect.get(EFFECT_TICK))
                 .combine();
         HalVibration vibration = startThreadAndDispatcher(effect);
         waitForCompletion();
 
         verify(mManagerHooks).noteVibratorOn(eq(UID), eq(20L));
         verify(mManagerHooks).noteVibratorOff(eq(UID));
-        verify(mControllerCallbacks).onComplete(eq(VIBRATOR_ID), eq(vibration.id));
-        verify(mControllerCallbacks, never()).onComplete(eq(2), eq(vibration.id));
+        verify(mControllerCallbacks).onComplete(eq(VIBRATOR_ID), eq(vibration.id), anyLong());
+        verify(mControllerCallbacks, never()).onComplete(eq(2), eq(vibration.id), anyLong());
         verifyCallbacksTriggered(vibration, Status.FINISHED);
-        assertFalse(mControllers.get(VIBRATOR_ID).isVibrating());
+        assertThat(mControllers.get(VIBRATOR_ID).isVibrating()).isFalse();
 
-        assertEquals(Arrays.asList(expectedPrebaked(VibrationEffect.EFFECT_TICK)),
-                mVibratorProviders.get(VIBRATOR_ID).getEffectSegments(vibration.id));
+        assertThat(mVibratorProviders.get(VIBRATOR_ID).getEffectSegments(vibration.id))
+                .containsExactly(expectedPrebaked(EFFECT_TICK)).inOrder();
     }
 
     @Test
     public void vibrate_multipleMono_runsSameEffectInAllVibrators() {
         mockVibrators(1, 2, 3);
-        mVibratorProviders.get(1).setSupportedEffects(VibrationEffect.EFFECT_CLICK);
-        mVibratorProviders.get(2).setSupportedEffects(VibrationEffect.EFFECT_CLICK);
-        mVibratorProviders.get(3).setSupportedEffects(VibrationEffect.EFFECT_CLICK);
+        mVibratorProviders.get(1).setSupportedEffects(EFFECT_CLICK);
+        mVibratorProviders.get(2).setSupportedEffects(EFFECT_CLICK);
+        mVibratorProviders.get(3).setSupportedEffects(EFFECT_CLICK);
 
         CombinedVibration effect = CombinedVibration.createParallel(
-                VibrationEffect.get(VibrationEffect.EFFECT_CLICK));
+                VibrationEffect.get(EFFECT_CLICK));
         HalVibration vibration = startThreadAndDispatcher(effect);
         waitForCompletion();
 
         verify(mManagerHooks).noteVibratorOn(eq(UID), eq(20L));
         verify(mManagerHooks).noteVibratorOff(eq(UID));
-        verify(mControllerCallbacks).onComplete(eq(1), eq(vibration.id));
-        verify(mControllerCallbacks).onComplete(eq(2), eq(vibration.id));
-        verify(mControllerCallbacks).onComplete(eq(3), eq(vibration.id));
+        verify(mControllerCallbacks).onComplete(eq(1), eq(vibration.id), anyLong());
+        verify(mControllerCallbacks).onComplete(eq(2), eq(vibration.id), anyLong());
+        verify(mControllerCallbacks).onComplete(eq(3), eq(vibration.id), anyLong());
         verifyCallbacksTriggered(vibration, Status.FINISHED);
-        assertFalse(mControllers.get(1).isVibrating());
-        assertFalse(mControllers.get(2).isVibrating());
-        assertFalse(mControllers.get(3).isVibrating());
+        assertThat(mControllers.get(1).isVibrating()).isFalse();
+        assertThat(mControllers.get(2).isVibrating()).isFalse();
+        assertThat(mControllers.get(3).isVibrating()).isFalse();
 
-        VibrationEffectSegment expected = expectedPrebaked(VibrationEffect.EFFECT_CLICK);
-        assertEquals(Arrays.asList(expected),
-                mVibratorProviders.get(1).getEffectSegments(vibration.id));
-        assertEquals(Arrays.asList(expected),
-                mVibratorProviders.get(2).getEffectSegments(vibration.id));
-        assertEquals(Arrays.asList(expected),
-                mVibratorProviders.get(3).getEffectSegments(vibration.id));
+        VibrationEffectSegment expected = expectedPrebaked(EFFECT_CLICK);
+        assertThat(mVibratorProviders.get(1).getEffectSegments(vibration.id))
+                .containsExactly(expected).inOrder();
+        assertThat(mVibratorProviders.get(2).getEffectSegments(vibration.id))
+                .containsExactly(expected).inOrder();
+        assertThat(mVibratorProviders.get(3).getEffectSegments(vibration.id))
+                .containsExactly(expected).inOrder();
     }
 
     @Test
     public void vibrate_multipleStereo_runsVibrationOnRightVibrators() {
         mockVibrators(1, 2, 3, 4);
-        mVibratorProviders.get(1).setSupportedEffects(VibrationEffect.EFFECT_CLICK);
+        mVibratorProviders.get(1).setSupportedEffects(EFFECT_CLICK);
         mVibratorProviders.get(2).setCapabilities(IVibrator.CAP_AMPLITUDE_CONTROL);
         mVibratorProviders.get(3).setCapabilities(IVibrator.CAP_AMPLITUDE_CONTROL);
         mVibratorProviders.get(4).setCapabilities(IVibrator.CAP_COMPOSE_EFFECTS);
-        mVibratorProviders.get(4).setSupportedPrimitives(
-                VibrationEffect.Composition.PRIMITIVE_CLICK);
+        mVibratorProviders.get(4).setSupportedPrimitives(PRIMITIVE_CLICK);
 
         VibrationEffect composed = VibrationEffect.startComposition()
-                .addPrimitive(VibrationEffect.Composition.PRIMITIVE_CLICK)
+                .addPrimitive(PRIMITIVE_CLICK)
                 .compose();
         CombinedVibration effect = CombinedVibration.startParallel()
-                .addVibrator(1, VibrationEffect.get(VibrationEffect.EFFECT_CLICK))
+                .addVibrator(1, VibrationEffect.get(EFFECT_CLICK))
                 .addVibrator(2, VibrationEffect.createOneShot(10, 100))
                 .addVibrator(3, VibrationEffect.createWaveform(
                         new long[]{10, 10}, new int[]{1, 2}, -1))
@@ -1225,27 +1271,28 @@
 
         verify(mManagerHooks).noteVibratorOn(eq(UID), eq(20L));
         verify(mManagerHooks).noteVibratorOff(eq(UID));
-        verify(mControllerCallbacks).onComplete(eq(1), eq(vibration.id));
-        verify(mControllerCallbacks).onComplete(eq(2), eq(vibration.id));
-        verify(mControllerCallbacks).onComplete(eq(3), eq(vibration.id));
-        verify(mControllerCallbacks).onComplete(eq(4), eq(vibration.id));
+        verify(mControllerCallbacks).onComplete(eq(1), eq(vibration.id), anyLong());
+        verify(mControllerCallbacks).onComplete(eq(2), eq(vibration.id), anyLong());
+        verify(mControllerCallbacks).onComplete(eq(3), eq(vibration.id), anyLong());
+        verify(mControllerCallbacks).onComplete(eq(4), eq(vibration.id), anyLong());
         verifyCallbacksTriggered(vibration, Status.FINISHED);
-        assertFalse(mControllers.get(1).isVibrating());
-        assertFalse(mControllers.get(2).isVibrating());
-        assertFalse(mControllers.get(3).isVibrating());
-        assertFalse(mControllers.get(4).isVibrating());
+        assertThat(mControllers.get(1).isVibrating()).isFalse();
+        assertThat(mControllers.get(2).isVibrating()).isFalse();
+        assertThat(mControllers.get(3).isVibrating()).isFalse();
+        assertThat(mControllers.get(4).isVibrating()).isFalse();
 
-        assertEquals(Arrays.asList(expectedPrebaked(VibrationEffect.EFFECT_CLICK)),
-                mVibratorProviders.get(1).getEffectSegments(vibration.id));
-        assertEquals(Arrays.asList(expectedOneShot(10)),
-                mVibratorProviders.get(2).getEffectSegments(vibration.id));
-        assertEquals(expectedAmplitudes(100), mVibratorProviders.get(2).getAmplitudes());
-        assertEquals(Arrays.asList(expectedOneShot(20)),
-                mVibratorProviders.get(3).getEffectSegments(vibration.id));
-        assertEquals(expectedAmplitudes(1, 2), mVibratorProviders.get(3).getAmplitudes());
-        assertEquals(Arrays.asList(
-                expectedPrimitive(VibrationEffect.Composition.PRIMITIVE_CLICK, 1, 0)),
-                mVibratorProviders.get(4).getEffectSegments(vibration.id));
+        assertThat(mVibratorProviders.get(1).getEffectSegments(vibration.id))
+                .containsExactly(expectedPrebaked(EFFECT_CLICK)).inOrder();
+        assertThat(mVibratorProviders.get(2).getEffectSegments(vibration.id))
+                .containsExactly(expectedOneShot(10)).inOrder();
+        assertThat(mVibratorProviders.get(2).getAmplitudes())
+                .containsExactlyElementsIn(expectedAmplitudes(100)).inOrder();
+        assertThat(mVibratorProviders.get(3).getEffectSegments(vibration.id))
+                .containsExactly(expectedOneShot(20)).inOrder();
+        assertThat(mVibratorProviders.get(3).getAmplitudes())
+                .containsExactlyElementsIn(expectedAmplitudes(1, 2)).inOrder();
+        assertThat(mVibratorProviders.get(4).getEffectSegments(vibration.id))
+                .containsExactly(expectedPrimitive(PRIMITIVE_CLICK, 1, 0)).inOrder();
     }
 
     @Test
@@ -1253,25 +1300,24 @@
         mockVibrators(1, 2, 3);
         mVibratorProviders.get(1).setCapabilities(IVibrator.CAP_AMPLITUDE_CONTROL);
         mVibratorProviders.get(2).setCapabilities(IVibrator.CAP_COMPOSE_EFFECTS);
-        mVibratorProviders.get(2).setSupportedPrimitives(
-                VibrationEffect.Composition.PRIMITIVE_CLICK);
-        mVibratorProviders.get(3).setSupportedEffects(VibrationEffect.EFFECT_CLICK);
+        mVibratorProviders.get(2).setSupportedPrimitives(PRIMITIVE_CLICK);
+        mVibratorProviders.get(3).setSupportedEffects(EFFECT_CLICK);
 
         VibrationEffect composed = VibrationEffect.startComposition()
-                .addPrimitive(VibrationEffect.Composition.PRIMITIVE_CLICK)
+                .addPrimitive(PRIMITIVE_CLICK)
                 .compose();
         CombinedVibration effect = CombinedVibration.startSequential()
-                .addNext(3, VibrationEffect.get(VibrationEffect.EFFECT_CLICK), /* delay= */ 50)
+                .addNext(3, VibrationEffect.get(EFFECT_CLICK), /* delay= */ 50)
                 .addNext(1, VibrationEffect.createOneShot(10, 100), /* delay= */ 50)
                 .addNext(2, composed, /* delay= */ 50)
                 .combine();
         HalVibration vibration = startThreadAndDispatcher(effect);
 
         waitForCompletion();
-        InOrder controllerVerifier = inOrder(mControllerCallbacks);
-        controllerVerifier.verify(mControllerCallbacks).onComplete(eq(3), eq(vibration.id));
-        controllerVerifier.verify(mControllerCallbacks).onComplete(eq(1), eq(vibration.id));
-        controllerVerifier.verify(mControllerCallbacks).onComplete(eq(2), eq(vibration.id));
+        InOrder verifier = inOrder(mControllerCallbacks);
+        verifier.verify(mControllerCallbacks).onComplete(eq(3), eq(vibration.id), anyLong());
+        verifier.verify(mControllerCallbacks).onComplete(eq(1), eq(vibration.id), anyLong());
+        verifier.verify(mControllerCallbacks).onComplete(eq(2), eq(vibration.id), anyLong());
 
         InOrder batteryVerifier = inOrder(mManagerHooks);
         batteryVerifier.verify(mManagerHooks).noteVibratorOn(eq(UID), eq(20L));
@@ -1282,18 +1328,18 @@
         batteryVerifier.verify(mManagerHooks).noteVibratorOff(eq(UID));
 
         verifyCallbacksTriggered(vibration, Status.FINISHED);
-        assertFalse(mControllers.get(1).isVibrating());
-        assertFalse(mControllers.get(2).isVibrating());
-        assertFalse(mControllers.get(3).isVibrating());
+        assertThat(mControllers.get(1).isVibrating()).isFalse();
+        assertThat(mControllers.get(2).isVibrating()).isFalse();
+        assertThat(mControllers.get(3).isVibrating()).isFalse();
 
-        assertEquals(Arrays.asList(expectedOneShot(10)),
-                mVibratorProviders.get(1).getEffectSegments(vibration.id));
-        assertEquals(expectedAmplitudes(100), mVibratorProviders.get(1).getAmplitudes());
-        assertEquals(Arrays.asList(
-                expectedPrimitive(VibrationEffect.Composition.PRIMITIVE_CLICK, 1, 0)),
-                mVibratorProviders.get(2).getEffectSegments(vibration.id));
-        assertEquals(Arrays.asList(expectedPrebaked(VibrationEffect.EFFECT_CLICK)),
-                mVibratorProviders.get(3).getEffectSegments(vibration.id));
+        assertThat(mVibratorProviders.get(1).getEffectSegments(vibration.id))
+                .containsExactly(expectedOneShot(10)).inOrder();
+        assertThat(mVibratorProviders.get(1).getAmplitudes())
+                .containsExactlyElementsIn(expectedAmplitudes(100)).inOrder();
+        assertThat(mVibratorProviders.get(2).getEffectSegments(vibration.id))
+                .containsExactly(expectedPrimitive(PRIMITIVE_CLICK, 1, 0)).inOrder();
+        assertThat(mVibratorProviders.get(3).getEffectSegments(vibration.id))
+                .containsExactly(expectedPrebaked(EFFECT_CLICK)).inOrder();
     }
 
     @Test
@@ -1301,15 +1347,13 @@
         int[] vibratorIds = new int[]{1, 2};
         mockVibrators(vibratorIds);
         mVibratorProviders.get(1).setCapabilities(IVibrator.CAP_COMPOSE_EFFECTS);
-        mVibratorProviders.get(1).setSupportedPrimitives(
-                VibrationEffect.Composition.PRIMITIVE_CLICK);
+        mVibratorProviders.get(1).setSupportedPrimitives(PRIMITIVE_CLICK);
         mVibratorProviders.get(2).setCapabilities(IVibrator.CAP_COMPOSE_EFFECTS);
-        mVibratorProviders.get(2).setSupportedPrimitives(
-                VibrationEffect.Composition.PRIMITIVE_CLICK);
+        mVibratorProviders.get(2).setSupportedPrimitives(PRIMITIVE_CLICK);
         when(mManagerHooks.prepareSyncedVibration(anyLong(), eq(vibratorIds))).thenReturn(true);
 
         VibrationEffect composed = VibrationEffect.startComposition()
-                .addPrimitive(VibrationEffect.Composition.PRIMITIVE_CLICK, 1, 100)
+                .addPrimitive(PRIMITIVE_CLICK, 1, 100)
                 .compose();
         CombinedVibration effect = CombinedVibration.createParallel(composed);
         // We create the HalVibration here to obtain the vibration id and use it to mock the
@@ -1318,10 +1362,10 @@
         when(mManagerHooks.triggerSyncedVibration(eq(vibration.id))).thenReturn(true);
         startThreadAndDispatcher(vibration);
 
-        assertTrue(waitUntil(
+        assertThat(waitUntil(
                 () -> !mVibratorProviders.get(1).getEffectSegments(vibration.id).isEmpty()
                         && !mVibratorProviders.get(2).getEffectSegments(vibration.id).isEmpty(),
-                TEST_TIMEOUT_MILLIS));
+                TEST_TIMEOUT_MILLIS)).isTrue();
         mVibrationConductor.notifySyncedVibrationComplete();
         waitForCompletion();
 
@@ -1331,29 +1375,27 @@
         verify(mManagerHooks, never()).cancelSyncedVibration();
         verifyCallbacksTriggered(vibration, Status.FINISHED);
 
-        VibrationEffectSegment expected = expectedPrimitive(
-                VibrationEffect.Composition.PRIMITIVE_CLICK, 1, 100);
-        assertEquals(Arrays.asList(expected),
-                mVibratorProviders.get(1).getEffectSegments(vibration.id));
-        assertEquals(Arrays.asList(expected),
-                mVibratorProviders.get(2).getEffectSegments(vibration.id));
+        VibrationEffectSegment expected = expectedPrimitive(PRIMITIVE_CLICK, 1, 100);
+        assertThat(mVibratorProviders.get(1).getEffectSegments(vibration.id))
+                .containsExactly(expected).inOrder();
+        assertThat(mVibratorProviders.get(2).getEffectSegments(vibration.id))
+                .containsExactly(expected).inOrder();
     }
 
     @Test
     public void vibrate_multipleSynced_callsPrepareAndTriggerCallbacks() {
         int[] vibratorIds = new int[]{1, 2, 3, 4};
         mockVibrators(vibratorIds);
-        mVibratorProviders.get(1).setSupportedEffects(VibrationEffect.EFFECT_CLICK);
+        mVibratorProviders.get(1).setSupportedEffects(EFFECT_CLICK);
         mVibratorProviders.get(4).setCapabilities(IVibrator.CAP_COMPOSE_EFFECTS);
-        mVibratorProviders.get(4).setSupportedPrimitives(
-                VibrationEffect.Composition.PRIMITIVE_CLICK);
+        mVibratorProviders.get(4).setSupportedPrimitives(PRIMITIVE_CLICK);
         when(mManagerHooks.prepareSyncedVibration(anyLong(), any())).thenReturn(true);
 
         VibrationEffect composed = VibrationEffect.startComposition()
-                .addPrimitive(VibrationEffect.Composition.PRIMITIVE_CLICK)
+                .addPrimitive(PRIMITIVE_CLICK)
                 .compose();
         CombinedVibration effect = CombinedVibration.startParallel()
-                .addVibrator(1, VibrationEffect.get(VibrationEffect.EFFECT_CLICK))
+                .addVibrator(1, VibrationEffect.get(EFFECT_CLICK))
                 .addVibrator(2, VibrationEffect.createOneShot(10, 100))
                 .addVibrator(3, VibrationEffect.createWaveform(new long[]{10}, new int[]{100}, -1))
                 .addVibrator(4, composed)
@@ -1398,24 +1440,26 @@
         verify(mManagerHooks, never()).triggerSyncedVibration(eq(vibration.id));
         verify(mManagerHooks, never()).cancelSyncedVibration();
 
-        assertEquals(Arrays.asList(expectedOneShot(10)),
-                mVibratorProviders.get(1).getEffectSegments(vibration.id));
-        assertEquals(expectedAmplitudes(100), mVibratorProviders.get(1).getAmplitudes());
-        assertEquals(Arrays.asList(expectedOneShot(5)),
-                mVibratorProviders.get(2).getEffectSegments(vibration.id));
-        assertEquals(expectedAmplitudes(200), mVibratorProviders.get(2).getAmplitudes());
+        assertThat(mVibratorProviders.get(1).getEffectSegments(vibration.id))
+                .containsExactly(expectedOneShot(10)).inOrder();
+        assertThat(mVibratorProviders.get(1).getAmplitudes())
+                .containsExactlyElementsIn(expectedAmplitudes(100)).inOrder();
+        assertThat(mVibratorProviders.get(2).getEffectSegments(vibration.id))
+                .containsExactly(expectedOneShot(5)).inOrder();
+        assertThat(mVibratorProviders.get(2).getAmplitudes())
+                .containsExactlyElementsIn(expectedAmplitudes(200)).inOrder();
     }
 
     @Test
     public void vibrate_multipleSyncedTriggerFailed_cancelPreparedVibrationAndSkipSetAmplitude() {
         int[] vibratorIds = new int[]{1, 2};
         mockVibrators(vibratorIds);
-        mVibratorProviders.get(2).setSupportedEffects(VibrationEffect.EFFECT_CLICK);
+        mVibratorProviders.get(2).setSupportedEffects(EFFECT_CLICK);
         when(mManagerHooks.prepareSyncedVibration(anyLong(), any())).thenReturn(true);
 
         CombinedVibration effect = CombinedVibration.startParallel()
                 .addVibrator(1, VibrationEffect.createOneShot(10, 100))
-                .addVibrator(2, VibrationEffect.get(VibrationEffect.EFFECT_CLICK))
+                .addVibrator(2, VibrationEffect.get(EFFECT_CLICK))
                 .combine();
         // We create the HalVibration here to obtain the vibration id and use it to mock the
         // required response when calling triggerSyncedVibration.
@@ -1432,7 +1476,7 @@
         verify(mManagerHooks).prepareSyncedVibration(eq(expectedCap), eq(vibratorIds));
         verify(mManagerHooks).triggerSyncedVibration(eq(vibration.id));
         verify(mManagerHooks).cancelSyncedVibration();
-        assertTrue(mVibratorProviders.get(1).getAmplitudes().isEmpty());
+        assertThat(mVibratorProviders.get(1).getAmplitudes()).isEmpty();
     }
 
     @Test
@@ -1453,33 +1497,37 @@
         HalVibration vibration = startThreadAndDispatcher(effect);
 
         // All vibrators are turned on in parallel.
-        assertTrue(waitUntil(
+        assertThat(waitUntil(
                 () -> mControllers.get(1).isVibrating()
                         && mControllers.get(2).isVibrating()
                         && mControllers.get(3).isVibrating(),
-                TEST_TIMEOUT_MILLIS));
+                TEST_TIMEOUT_MILLIS)).isTrue();
 
         waitForCompletion();
 
         verify(mManagerHooks).noteVibratorOn(eq(UID), eq(80L));
         verify(mManagerHooks).noteVibratorOff(eq(UID));
-        verify(mControllerCallbacks).onComplete(eq(1), eq(vibration.id));
-        verify(mControllerCallbacks).onComplete(eq(2), eq(vibration.id));
-        verify(mControllerCallbacks).onComplete(eq(3), eq(vibration.id));
+        verify(mControllerCallbacks).onComplete(eq(1), eq(vibration.id), anyLong());
+        verify(mControllerCallbacks).onComplete(eq(2), eq(vibration.id), anyLong());
+        verify(mControllerCallbacks).onComplete(eq(3), eq(vibration.id), anyLong());
         verifyCallbacksTriggered(vibration, Status.FINISHED);
-        assertFalse(mControllers.get(1).isVibrating());
-        assertFalse(mControllers.get(2).isVibrating());
-        assertFalse(mControllers.get(3).isVibrating());
+        assertThat(mControllers.get(1).isVibrating()).isFalse();
+        assertThat(mControllers.get(2).isVibrating()).isFalse();
+        assertThat(mControllers.get(3).isVibrating()).isFalse();
 
-        assertEquals(Arrays.asList(expectedOneShot(25)),
-                mVibratorProviders.get(1).getEffectSegments(vibration.id));
-        assertEquals(Arrays.asList(expectedOneShot(80)),
-                mVibratorProviders.get(2).getEffectSegments(vibration.id));
-        assertEquals(Arrays.asList(expectedOneShot(60)),
-                mVibratorProviders.get(3).getEffectSegments(vibration.id));
-        assertEquals(expectedAmplitudes(1, 2, 3), mVibratorProviders.get(1).getAmplitudes());
-        assertEquals(expectedAmplitudes(4, 5), mVibratorProviders.get(2).getAmplitudes());
-        assertEquals(expectedAmplitudes(6), mVibratorProviders.get(3).getAmplitudes());
+        assertThat(mVibratorProviders.get(1).getEffectSegments(vibration.id))
+                .containsExactly(expectedOneShot(25)).inOrder();
+        assertThat(mVibratorProviders.get(2).getEffectSegments(vibration.id))
+                .containsExactly(expectedOneShot(80)).inOrder();
+        assertThat(mVibratorProviders.get(3).getEffectSegments(vibration.id))
+                .containsExactly(expectedOneShot(60)).inOrder();
+        assertThat(mVibratorProviders.get(1).getAmplitudes())
+                .containsExactlyElementsIn(expectedAmplitudes(1, 2, 3)).inOrder();
+        assertThat(mVibratorProviders.get(2).getAmplitudes())
+                .containsExactlyElementsIn(expectedAmplitudes(4, 5)).inOrder();
+        assertThat(mVibratorProviders.get(3).getAmplitudes())
+                .containsExactlyElementsIn(expectedAmplitudes(6)).inOrder();
+
     }
 
     @Test
@@ -1505,7 +1553,7 @@
         waitForCompletion(rampDownDuration + TEST_TIMEOUT_MILLIS);
         long completionTime = SystemClock.elapsedRealtime();
 
-        verify(mControllerCallbacks).onComplete(VIBRATOR_ID, vibration.id);
+        verify(mControllerCallbacks).onComplete(eq(VIBRATOR_ID), eq(vibration.id), anyLong());
         // Vibration ends after duration, thread completed after ramp down
         assertThat(vibrationEndTime - startTime).isAtLeast(expectedDuration);
         assertThat(vibrationEndTime - startTime).isLessThan(expectedDuration + rampDownDuration);
@@ -1526,15 +1574,15 @@
                         VibrationEffect.createOneShot(
                                 expectedDuration, VibrationEffect.DEFAULT_AMPLITUDE)));
 
-        startThreadAndDispatcher(vibration);
         long startTime = SystemClock.elapsedRealtime();
+        startThreadAndDispatcher(vibration);
 
         vibration.waitForEnd();
         long vibrationEndTime = SystemClock.elapsedRealtime();
 
         waitForCompletion(TEST_TIMEOUT_MILLIS);
 
-        verify(mControllerCallbacks).onComplete(VIBRATOR_ID, vibration.id);
+        verify(mControllerCallbacks).onComplete(eq(VIBRATOR_ID), eq(vibration.id), anyLong());
         assertThat(vibrationEndTime - startTime).isAtLeast(expectedDuration + callbackDelay);
     }
 
@@ -1563,7 +1611,8 @@
         waitForCompletion(callbackDelay + TEST_TIMEOUT_MILLIS);
         long completionTime = SystemClock.elapsedRealtime();
 
-        verify(mControllerCallbacks, never()).onComplete(VIBRATOR_ID, vibration.id);
+        verify(mControllerCallbacks, never())
+                .onComplete(eq(VIBRATOR_ID), eq(vibration.id), anyLong());
         // Vibration ends and thread completes after timeout, before the HAL callback
         assertThat(vibrationEndTime - startTime).isAtLeast(expectedDuration + callbackTimeout);
         assertThat(vibrationEndTime - startTime).isLessThan(expectedDuration + callbackDelay);
@@ -1596,26 +1645,24 @@
 
         // Allow some delay for thread scheduling and callback triggering.
         int maxDelay = (int) (0.05 * totalDuration); // < 5% of total duration
-        assertTrue("Waveform with perceived delay of " + delay + "ms,"
-                        + " expected less than " + maxDelay + "ms",
-                delay < maxDelay);
+        assertThat(delay).isLessThan(maxDelay);
     }
 
     @LargeTest
     @Test
     public void vibrate_cancelSlowVibrator_cancelIsNotBlockedByVibrationThread() throws Exception {
         FakeVibratorControllerProvider fakeVibrator = mVibratorProviders.get(VIBRATOR_ID);
-        fakeVibrator.setSupportedEffects(VibrationEffect.EFFECT_CLICK);
+        fakeVibrator.setSupportedEffects(EFFECT_CLICK);
 
         long latency = 5_000; // 5s
         fakeVibrator.setOnLatency(latency);
 
-        VibrationEffect effect = VibrationEffect.get(VibrationEffect.EFFECT_CLICK);
+        VibrationEffect effect = VibrationEffect.get(EFFECT_CLICK);
         HalVibration vibration = startThreadAndDispatcher(effect);
 
-        assertTrue(waitUntil(() -> !fakeVibrator.getEffectSegments(vibration.id).isEmpty(),
-                TEST_TIMEOUT_MILLIS));
-        assertTrue(mThread.isRunningVibrationId(vibration.id));
+        assertThat(waitUntil(() -> !fakeVibrator.getEffectSegments(vibration.id).isEmpty(),
+                TEST_TIMEOUT_MILLIS)).isTrue();
+        assertThat(mThread.isRunningVibrationId(vibration.id)).isTrue();
 
         // Run cancel in a separate thread so if VibrationThread.cancel blocks then this test should
         // fail at waitForCompletion(cancellingThread).
@@ -1631,29 +1678,29 @@
         // After the vibrator call ends the vibration is cancelled and the vibrator is turned off.
         waitForCompletion(/* timeout= */ latency + TEST_TIMEOUT_MILLIS);
         verifyCallbacksTriggered(vibration, Status.CANCELLED_BY_USER);
-        assertFalse(mControllers.get(VIBRATOR_ID).isVibrating());
+        assertThat(mControllers.get(VIBRATOR_ID).isVibrating()).isFalse();
     }
 
     @Test
     public void vibrate_multiplePredefinedCancel_cancelsVibrationImmediately() throws Exception {
         mockVibrators(1, 2);
-        mVibratorProviders.get(1).setSupportedEffects(VibrationEffect.EFFECT_CLICK);
+        mVibratorProviders.get(1).setSupportedEffects(EFFECT_CLICK);
         mVibratorProviders.get(2).setCapabilities(IVibrator.CAP_COMPOSE_EFFECTS);
-        mVibratorProviders.get(2).setSupportedPrimitives(
-                VibrationEffect.Composition.PRIMITIVE_CLICK);
+        mVibratorProviders.get(2).setSupportedPrimitives(PRIMITIVE_CLICK);
 
         CombinedVibration effect = CombinedVibration.startParallel()
-                .addVibrator(1, VibrationEffect.get(VibrationEffect.EFFECT_CLICK))
+                .addVibrator(1, VibrationEffect.get(EFFECT_CLICK))
                 .addVibrator(2, VibrationEffect.startComposition()
-                        .addPrimitive(VibrationEffect.Composition.PRIMITIVE_CLICK, 1f, 100)
-                        .addPrimitive(VibrationEffect.Composition.PRIMITIVE_CLICK, 1f, 100)
-                        .addPrimitive(VibrationEffect.Composition.PRIMITIVE_CLICK, 1f, 100)
+                        .addPrimitive(PRIMITIVE_CLICK, 1f, 100)
+                        .addPrimitive(PRIMITIVE_CLICK, 1f, 100)
+                        .addPrimitive(PRIMITIVE_CLICK, 1f, 100)
                         .compose())
                 .combine();
         HalVibration vibration = startThreadAndDispatcher(effect);
 
-        assertTrue(waitUntil(() -> mControllers.get(2).isVibrating(), TEST_TIMEOUT_MILLIS));
-        assertTrue(mThread.isRunningVibrationId(vibration.id));
+        assertThat(waitUntil(() -> mControllers.get(2).isVibrating(), TEST_TIMEOUT_MILLIS))
+                .isTrue();
+        assertThat(mThread.isRunningVibrationId(vibration.id)).isTrue();
 
         // Run cancel in a separate thread so if VibrationThread.cancel blocks then this test should
         // fail at waitForCompletion(vibrationThread) if the vibration not cancelled immediately.
@@ -1667,12 +1714,12 @@
         cancellingThread.join();
 
         verifyCallbacksTriggered(vibration, Status.CANCELLED_BY_SCREEN_OFF);
-        assertFalse(mControllers.get(1).isVibrating());
-        assertFalse(mControllers.get(2).isVibrating());
+        assertThat(mControllers.get(1).isVibrating()).isFalse();
+        assertThat(mControllers.get(2).isVibrating()).isFalse();
     }
 
     @Test
-    @RequiresFlagsEnabled(android.os.vibrator.Flags.FLAG_VENDOR_VIBRATION_EFFECTS)
+    @EnableFlags(Flags.FLAG_VENDOR_VIBRATION_EFFECTS)
     public void vibrate_multipleVendorEffectCancel_cancelsVibrationImmediately() throws Exception {
         mockVibrators(1, 2);
         mVibratorProviders.get(1).setCapabilities(IVibrator.CAP_PERFORM_VENDOR_EFFECTS);
@@ -1686,8 +1733,9 @@
                 .combine();
         HalVibration vibration = startThreadAndDispatcher(effect);
 
-        assertTrue(waitUntil(() -> mControllers.get(2).isVibrating(), TEST_TIMEOUT_MILLIS));
-        assertTrue(mThread.isRunningVibrationId(vibration.id));
+        assertThat(waitUntil(() -> mControllers.get(2).isVibrating(), TEST_TIMEOUT_MILLIS))
+                .isTrue();
+        assertThat(mThread.isRunningVibrationId(vibration.id)).isTrue();
 
         // Run cancel in a separate thread so if VibrationThread.cancel blocks then this test should
         // fail at waitForCompletion(vibrationThread) if the vibration not cancelled immediately.
@@ -1701,8 +1749,8 @@
         cancellingThread.join();
 
         verifyCallbacksTriggered(vibration, Status.CANCELLED_BY_SCREEN_OFF);
-        assertFalse(mControllers.get(1).isVibrating());
-        assertFalse(mControllers.get(2).isVibrating());
+        assertThat(mControllers.get(1).isVibrating()).isFalse();
+        assertThat(mControllers.get(2).isVibrating()).isFalse();
     }
 
     @Test
@@ -1718,10 +1766,10 @@
                 .combine();
         HalVibration vibration = startThreadAndDispatcher(effect);
 
-        assertTrue(waitUntil(() -> mControllers.get(1).isVibrating()
+        assertThat(waitUntil(() -> mControllers.get(1).isVibrating()
                         && mControllers.get(2).isVibrating(),
-                TEST_TIMEOUT_MILLIS));
-        assertTrue(mThread.isRunningVibrationId(vibration.id));
+                TEST_TIMEOUT_MILLIS)).isTrue();
+        assertThat(mThread.isRunningVibrationId(vibration.id)).isTrue();
 
         // Run cancel in a separate thread so if VibrationThread.cancel blocks then this test should
         // fail at waitForCompletion(vibrationThread) if the vibration not cancelled immediately.
@@ -1735,8 +1783,8 @@
         cancellingThread.join();
 
         verifyCallbacksTriggered(vibration, Status.CANCELLED_BY_SCREEN_OFF);
-        assertFalse(mControllers.get(1).isVibrating());
-        assertFalse(mControllers.get(2).isVibrating());
+        assertThat(mControllers.get(1).isVibrating()).isFalse();
+        assertThat(mControllers.get(2).isVibrating()).isFalse();
     }
 
     @Test
@@ -1744,17 +1792,18 @@
         VibrationEffect effect = VibrationEffect.createWaveform(new long[]{5}, new int[]{100}, 0);
         HalVibration vibration = startThreadAndDispatcher(effect);
 
-        assertTrue(waitUntil(() -> mControllers.get(VIBRATOR_ID).isVibrating(),
-                TEST_TIMEOUT_MILLIS));
-        assertTrue(mThread.isRunningVibrationId(vibration.id));
+        assertThat(waitUntil(() -> mControllers.get(VIBRATOR_ID).isVibrating(),
+                TEST_TIMEOUT_MILLIS)).isTrue();
+        assertThat(mThread.isRunningVibrationId(vibration.id)).isTrue();
 
         mVibrationConductor.notifyCancelled(
                 new Vibration.EndInfo(Status.CANCELLED_BINDER_DIED), /* immediate= */ false);
         waitForCompletion();
 
         verifyCallbacksTriggered(vibration, Status.CANCELLED_BINDER_DIED);
-        assertFalse(mVibratorProviders.get(VIBRATOR_ID).getEffectSegments(vibration.id).isEmpty());
-        assertFalse(mControllers.get(VIBRATOR_ID).isVibrating());
+        assertThat(mVibratorProviders.get(VIBRATOR_ID).getEffectSegments(vibration.id))
+                .isNotEmpty();
+        assertThat(mControllers.get(VIBRATOR_ID).isVibrating()).isFalse();
     }
 
     @Test
@@ -1767,17 +1816,20 @@
         HalVibration vibration = startThreadAndDispatcher(effect);
         waitForCompletion();
 
-        verify(mControllerCallbacks).onComplete(eq(VIBRATOR_ID), eq(vibration.id));
+        verify(mControllerCallbacks).onComplete(eq(VIBRATOR_ID), eq(vibration.id), anyLong());
         verifyCallbacksTriggered(vibration, Status.FINISHED);
 
         // Duration extended for 5 + 5 + 5 + 15.
-        assertEquals(Arrays.asList(expectedOneShot(30)),
-                mVibratorProviders.get(VIBRATOR_ID).getEffectSegments(vibration.id));
+        assertThat(mVibratorProviders.get(VIBRATOR_ID).getEffectSegments(vibration.id))
+                .containsExactly(expectedOneShot(30)).inOrder();
         List<Float> amplitudes = mVibratorProviders.get(VIBRATOR_ID).getAmplitudes();
-        assertTrue(amplitudes.size() > 3);
-        assertEquals(expectedAmplitudes(60, 120, 240), amplitudes.subList(0, 3));
+        assertThat(amplitudes.size()).isGreaterThan(3);
+        assertThat(amplitudes.subList(0, 3))
+                .containsExactlyElementsIn(expectedAmplitudes(60, 120, 240))
+                .inOrder();
         for (int i = 3; i < amplitudes.size(); i++) {
-            assertTrue(amplitudes.get(i) < amplitudes.get(i - 1));
+            assertWithMessage("For amplitude index %s", i)
+                    .that(amplitudes.get(i)).isLessThan(amplitudes.get(i - 1));
         }
     }
 
@@ -1795,11 +1847,11 @@
         verify(mManagerHooks, never()).onVibrationThreadReleased(anyLong());
 
         // Thread still running ramp down.
-        assertTrue(mThread.isRunningVibrationId(vibration.id));
+        assertThat(mThread.isRunningVibrationId(vibration.id)).isTrue();
 
         // Duration extended for 10 + 10000.
-        assertEquals(Arrays.asList(expectedOneShot(10_010)),
-                mVibratorProviders.get(VIBRATOR_ID).getEffectSegments(vibration.id));
+        assertThat(mVibratorProviders.get(VIBRATOR_ID).getEffectSegments(vibration.id))
+                .containsExactly(expectedOneShot(10_010)).inOrder();
 
         // Will stop the ramp down right away.
         mVibrationConductor.notifyCancelled(
@@ -1819,8 +1871,8 @@
 
         VibrationEffect effect = VibrationEffect.createOneShot(10_000, 240);
         HalVibration vibration = startThreadAndDispatcher(effect);
-        assertTrue(waitUntil(() -> mControllers.get(VIBRATOR_ID).isVibrating(),
-                TEST_TIMEOUT_MILLIS));
+        assertThat(waitUntil(() -> mControllers.get(VIBRATOR_ID).isVibrating(),
+                TEST_TIMEOUT_MILLIS)).isTrue();
         mVibrationConductor.notifyCancelled(
                 new Vibration.EndInfo(Status.CANCELLED_BY_USER), /* immediate= */ false);
         waitForCompletion();
@@ -1828,12 +1880,13 @@
         verifyCallbacksTriggered(vibration, Status.CANCELLED_BY_USER);
 
         // Duration extended for 10000 + 15.
-        assertEquals(Arrays.asList(expectedOneShot(10_015)),
-                mVibratorProviders.get(VIBRATOR_ID).getEffectSegments(vibration.id));
+        assertThat(mVibratorProviders.get(VIBRATOR_ID).getEffectSegments(vibration.id))
+                .containsExactly(expectedOneShot(10_015)).inOrder();
         List<Float> amplitudes = mVibratorProviders.get(VIBRATOR_ID).getAmplitudes();
-        assertTrue(amplitudes.size() > 1);
+        assertThat(amplitudes.size()).isGreaterThan(1);
         for (int i = 1; i < amplitudes.size(); i++) {
-            assertTrue(amplitudes.get(i) < amplitudes.get(i - 1));
+            assertWithMessage("For amplitude index %s", i)
+                    .that(amplitudes.get(i)).isLessThan(amplitudes.get(i - 1));
         }
     }
 
@@ -1841,22 +1894,22 @@
     public void vibrate_predefinedWithRampDown_doesNotAddRampDown() {
         when(mVibrationConfigMock.getRampDownDurationMs()).thenReturn(15);
         mVibratorProviders.get(VIBRATOR_ID).setCapabilities(IVibrator.CAP_AMPLITUDE_CONTROL);
-        mVibratorProviders.get(VIBRATOR_ID).setSupportedEffects(VibrationEffect.EFFECT_CLICK);
+        mVibratorProviders.get(VIBRATOR_ID).setSupportedEffects(EFFECT_CLICK);
 
-        VibrationEffect effect = VibrationEffect.get(VibrationEffect.EFFECT_CLICK);
+        VibrationEffect effect = VibrationEffect.get(EFFECT_CLICK);
         HalVibration vibration = startThreadAndDispatcher(effect);
         waitForCompletion();
 
-        verify(mControllerCallbacks).onComplete(eq(VIBRATOR_ID), eq(vibration.id));
+        verify(mControllerCallbacks).onComplete(eq(VIBRATOR_ID), eq(vibration.id), anyLong());
         verifyCallbacksTriggered(vibration, Status.FINISHED);
 
-        assertEquals(Arrays.asList(expectedPrebaked(VibrationEffect.EFFECT_CLICK)),
-                mVibratorProviders.get(VIBRATOR_ID).getEffectSegments(vibration.id));
-        assertTrue(mVibratorProviders.get(VIBRATOR_ID).getAmplitudes().isEmpty());
+        assertThat(mVibratorProviders.get(VIBRATOR_ID).getEffectSegments(vibration.id))
+                .containsExactly(expectedPrebaked(EFFECT_CLICK)).inOrder();
+        assertThat(mVibratorProviders.get(VIBRATOR_ID).getAmplitudes()).isEmpty();
     }
 
     @Test
-    @RequiresFlagsEnabled(android.os.vibrator.Flags.FLAG_VENDOR_VIBRATION_EFFECTS)
+    @EnableFlags(Flags.FLAG_VENDOR_VIBRATION_EFFECTS)
     public void vibrate_vendorEffectWithRampDown_doesNotAddRampDown() {
         when(mVibrationConfigMock.getRampDownDurationMs()).thenReturn(15);
         mVibratorProviders.get(VIBRATOR_ID).setCapabilities(IVibrator.CAP_PERFORM_VENDOR_EFFECTS);
@@ -1865,12 +1918,11 @@
         HalVibration vibration = startThreadAndDispatcher(effect);
         waitForCompletion();
 
-        verify(mControllerCallbacks).onComplete(eq(VIBRATOR_ID), eq(vibration.id));
+        verify(mControllerCallbacks).onComplete(eq(VIBRATOR_ID), eq(vibration.id), anyLong());
         verifyCallbacksTriggered(vibration, Status.FINISHED);
 
         assertThat(mVibratorProviders.get(VIBRATOR_ID).getVendorEffects(vibration.id))
-                .containsExactly(effect)
-                .inOrder();
+                .containsExactly(effect).inOrder();
         assertThat(mVibratorProviders.get(VIBRATOR_ID).getAmplitudes()).isEmpty();
     }
 
@@ -1879,26 +1931,24 @@
         when(mVibrationConfigMock.getRampDownDurationMs()).thenReturn(15);
         mVibratorProviders.get(VIBRATOR_ID).setCapabilities(IVibrator.CAP_AMPLITUDE_CONTROL,
                 IVibrator.CAP_COMPOSE_EFFECTS);
-        mVibratorProviders.get(VIBRATOR_ID).setSupportedPrimitives(
-                VibrationEffect.Composition.PRIMITIVE_CLICK);
+        mVibratorProviders.get(VIBRATOR_ID).setSupportedPrimitives(PRIMITIVE_CLICK);
 
         VibrationEffect effect = VibrationEffect.startComposition()
-                .addPrimitive(VibrationEffect.Composition.PRIMITIVE_CLICK)
+                .addPrimitive(PRIMITIVE_CLICK)
                 .compose();
         HalVibration vibration = startThreadAndDispatcher(effect);
         waitForCompletion();
 
-        verify(mControllerCallbacks).onComplete(eq(VIBRATOR_ID), eq(vibration.id));
+        verify(mControllerCallbacks).onComplete(eq(VIBRATOR_ID), eq(vibration.id), anyLong());
         verifyCallbacksTriggered(vibration, Status.FINISHED);
 
-        assertEquals(
-                Arrays.asList(expectedPrimitive(VibrationEffect.Composition.PRIMITIVE_CLICK, 1, 0)),
-                mVibratorProviders.get(VIBRATOR_ID).getEffectSegments(vibration.id));
-        assertTrue(mVibratorProviders.get(VIBRATOR_ID).getAmplitudes().isEmpty());
+        assertThat(mVibratorProviders.get(VIBRATOR_ID).getEffectSegments(vibration.id))
+                .containsExactly(expectedPrimitive(PRIMITIVE_CLICK, 1, 0)).inOrder();
+        assertThat(mVibratorProviders.get(VIBRATOR_ID).getAmplitudes()).isEmpty();
     }
 
     @Test
-    @DisableFlags(android.os.vibrator.Flags.FLAG_NORMALIZED_PWLE_EFFECTS)
+    @DisableFlags(Flags.FLAG_NORMALIZED_PWLE_EFFECTS)
     public void vibrate_pwleWithRampDown_doesNotAddRampDown() {
         when(mVibrationConfigMock.getRampDownDurationMs()).thenReturn(15);
         FakeVibratorControllerProvider fakeVibrator = mVibratorProviders.get(VIBRATOR_ID);
@@ -1916,34 +1966,32 @@
         HalVibration vibration = startThreadAndDispatcher(effect);
         waitForCompletion();
 
-        verify(mControllerCallbacks).onComplete(eq(VIBRATOR_ID), eq(vibration.id));
+        verify(mControllerCallbacks).onComplete(eq(VIBRATOR_ID), eq(vibration.id), anyLong());
         verifyCallbacksTriggered(vibration, Status.FINISHED);
 
-        assertEquals(Arrays.asList(expectedRamp(0, 1, 150, 150, 1)),
-                fakeVibrator.getEffectSegments(vibration.id));
-        assertTrue(fakeVibrator.getAmplitudes().isEmpty());
+        assertThat(fakeVibrator.getEffectSegments(vibration.id))
+                .containsExactly(expectedRamp(0, 1, 150, 150, 1)).inOrder();
+        assertThat(fakeVibrator.getAmplitudes()).isEmpty();
     }
 
     @Test
     public void vibrate_multipleVibrations_withCancel() throws Exception {
-        mVibratorProviders.get(VIBRATOR_ID).setSupportedEffects(
-                VibrationEffect.EFFECT_CLICK, VibrationEffect.EFFECT_TICK);
-        mVibratorProviders.get(VIBRATOR_ID).setSupportedPrimitives(
-                VibrationEffect.Composition.PRIMITIVE_CLICK);
+        mVibratorProviders.get(VIBRATOR_ID).setSupportedEffects(EFFECT_CLICK, EFFECT_TICK);
+        mVibratorProviders.get(VIBRATOR_ID).setSupportedPrimitives(PRIMITIVE_CLICK);
         mVibratorProviders.get(VIBRATOR_ID).setCapabilities(IVibrator.CAP_AMPLITUDE_CONTROL,
                 IVibrator.CAP_COMPOSE_EFFECTS);
 
         // A simple effect, followed by a repeating effect that gets cancelled, followed by another
         // simple effect.
-        VibrationEffect effect1 = VibrationEffect.get(VibrationEffect.EFFECT_CLICK);
+        VibrationEffect effect1 = VibrationEffect.get(EFFECT_CLICK);
         VibrationEffect effect2 = VibrationEffect.startComposition()
-                .repeatEffectIndefinitely(VibrationEffect.get(VibrationEffect.EFFECT_TICK))
+                .repeatEffectIndefinitely(VibrationEffect.get(EFFECT_TICK))
                 .compose();
         VibrationEffect effect3 = VibrationEffect.startComposition()
-                .addPrimitive(VibrationEffect.Composition.PRIMITIVE_CLICK)
+                .addPrimitive(PRIMITIVE_CLICK)
                 .compose();
         VibrationEffect effect4 = VibrationEffect.createOneShot(8000, 100);
-        VibrationEffect effect5 = VibrationEffect.get(VibrationEffect.EFFECT_CLICK);
+        VibrationEffect effect5 = VibrationEffect.get(EFFECT_CLICK);
 
         HalVibration vibration1 = startThreadAndDispatcher(effect1);
         waitForCompletion();
@@ -1971,42 +2019,42 @@
         waitForCompletion();
 
         FakeVibratorControllerProvider fakeVibrator = mVibratorProviders.get(VIBRATOR_ID);
-        assertFalse(mControllers.get(VIBRATOR_ID).isVibrating());
+        assertThat(mControllers.get(VIBRATOR_ID).isVibrating()).isFalse();
 
         // Effect1
-        verify(mControllerCallbacks).onComplete(VIBRATOR_ID, vibration1.id);
+        verify(mControllerCallbacks).onComplete(eq(VIBRATOR_ID), eq(vibration1.id), anyLong());
         verifyCallbacksTriggered(vibration1, Status.FINISHED);
 
-        assertEquals(Arrays.asList(expectedPrebaked(VibrationEffect.EFFECT_CLICK)),
-                fakeVibrator.getEffectSegments(vibration1.id));
+        assertThat(fakeVibrator.getEffectSegments(vibration1.id))
+                .containsExactly(expectedPrebaked(EFFECT_CLICK)).inOrder();
 
         // Effect2: repeating, cancelled.
-        verify(mControllerCallbacks, atLeast(2)).onComplete(VIBRATOR_ID, vibration2.id);
+        verify(mControllerCallbacks, atLeast(2))
+                .onComplete(eq(VIBRATOR_ID), eq(vibration2.id), anyLong());
         verifyCallbacksTriggered(vibration2, Status.CANCELLED_BY_USER);
 
         // The exact count of segments might vary, so just check that there's more than 2 and
         // all elements are the same segment.
         List<VibrationEffectSegment> actualSegments2 =
                 fakeVibrator.getEffectSegments(vibration2.id);
-        assertTrue(actualSegments2.size() + " > 2", actualSegments2.size() > 2);
+        assertThat(actualSegments2.size()).isGreaterThan(2);
         for (VibrationEffectSegment segment : actualSegments2) {
-            assertEquals(expectedPrebaked(VibrationEffect.EFFECT_TICK), segment);
+            assertThat(segment).isEqualTo(expectedPrebaked(EFFECT_TICK));
         }
 
         // Effect3
-        verify(mControllerCallbacks).onComplete(eq(VIBRATOR_ID), eq(vibration3.id));
+        verify(mControllerCallbacks).onComplete(eq(VIBRATOR_ID), eq(vibration3.id), anyLong());
         verifyCallbacksTriggered(vibration3, Status.FINISHED);
-        assertEquals(Arrays.asList(
-                        expectedPrimitive(VibrationEffect.Composition.PRIMITIVE_CLICK, 1, 0)),
-                fakeVibrator.getEffectSegments(vibration3.id));
+        assertThat(fakeVibrator.getEffectSegments(vibration3.id))
+                .containsExactly(expectedPrimitive(PRIMITIVE_CLICK, 1, 0)).inOrder();
 
         // Effect4: cancelled quickly.
         verifyCallbacksTriggered(vibration4, Status.CANCELLED_BY_SCREEN_OFF);
-        assertTrue("Tested duration=" + duration4, duration4 < 2000);
+        assertThat(duration4).isLessThan(2000);
 
         // Effect5: played normally after effect4, which may or may not have played.
-        assertEquals(Arrays.asList(expectedPrebaked(VibrationEffect.EFFECT_CLICK)),
-                fakeVibrator.getEffectSegments(vibration5.id));
+        assertThat(fakeVibrator.getEffectSegments(vibration5.id))
+                .containsExactly(expectedPrebaked(EFFECT_CLICK)).inOrder();
     }
 
     @Test
@@ -2014,23 +2062,18 @@
         mockVibrators(1, 2, 3);
         mVibratorProviders.get(1).setCapabilities(IVibrator.CAP_AMPLITUDE_CONTROL);
         mVibratorProviders.get(2).setCapabilities(IVibrator.CAP_COMPOSE_EFFECTS);
-        mVibratorProviders.get(2).setSupportedPrimitives(
-                VibrationEffect.Composition.PRIMITIVE_CLICK);
-        mVibratorProviders.get(3).setSupportedEffects(VibrationEffect.EFFECT_CLICK);
+        mVibratorProviders.get(2).setSupportedPrimitives(PRIMITIVE_CLICK);
+        mVibratorProviders.get(3).setSupportedEffects(EFFECT_CLICK);
 
         CombinedVibration effect = CombinedVibration.startSequential()
-                .addNext(3,
-                        VibrationEffect.get(VibrationEffect.EFFECT_CLICK),
-                        /* delay= */ TEST_TIMEOUT_MILLIS)
+                .addNext(3, VibrationEffect.get(EFFECT_CLICK), /* delay= */ TEST_TIMEOUT_MILLIS)
                 .addNext(1,
                         VibrationEffect.createWaveform(
-                                new long[] {TEST_TIMEOUT_MILLIS, TEST_TIMEOUT_MILLIS},
-                                /* repeat= */ -1),
+                                new long[] {TEST_TIMEOUT_MILLIS, TEST_TIMEOUT_MILLIS}, -1),
                         /* delay= */ TEST_TIMEOUT_MILLIS)
                 .addNext(2,
                         VibrationEffect.startComposition()
-                                .addPrimitive(VibrationEffect.Composition.PRIMITIVE_CLICK, 1,
-                                        /* delay= */ TEST_TIMEOUT_MILLIS)
+                                .addPrimitive(PRIMITIVE_CLICK, 1, /* delay= */ TEST_TIMEOUT_MILLIS)
                                 .compose(),
                         /* delay= */ TEST_TIMEOUT_MILLIS)
                 .combine();
@@ -2041,21 +2084,22 @@
 
         // Vibrating state remains ON until session resets it.
         verifyCallbacksTriggered(vibration, Status.FINISHED);
-        assertTrue(mControllers.get(1).isVibrating());
-        assertTrue(mControllers.get(2).isVibrating());
-        assertTrue(mControllers.get(3).isVibrating());
+        assertThat(mControllers.get(1).isVibrating()).isTrue();
+        assertThat(mControllers.get(2).isVibrating()).isTrue();
+        assertThat(mControllers.get(3).isVibrating()).isTrue();
 
-        assertEquals(0, mVibratorProviders.get(1).getOffCount());
-        assertEquals(0, mVibratorProviders.get(2).getOffCount());
-        assertEquals(0, mVibratorProviders.get(3).getOffCount());
-        assertEquals(Arrays.asList(expectedOneShot(TEST_TIMEOUT_MILLIS)),
-                mVibratorProviders.get(1).getEffectSegments(vibration.id));
-        assertEquals(expectedAmplitudes(255), mVibratorProviders.get(1).getAmplitudes());
-        assertEquals(Arrays.asList(expectedPrimitive(
-                VibrationEffect.Composition.PRIMITIVE_CLICK, 1, TEST_TIMEOUT_MILLIS)),
-                mVibratorProviders.get(2).getEffectSegments(vibration.id));
-        assertEquals(Arrays.asList(expectedPrebaked(VibrationEffect.EFFECT_CLICK)),
-                mVibratorProviders.get(3).getEffectSegments(vibration.id));
+        assertThat(mVibratorProviders.get(1).getOffCount()).isEqualTo(0);
+        assertThat(mVibratorProviders.get(2).getOffCount()).isEqualTo(0);
+        assertThat(mVibratorProviders.get(3).getOffCount()).isEqualTo(0);
+        assertThat(mVibratorProviders.get(1).getEffectSegments(vibration.id))
+                .containsExactly(expectedOneShot(TEST_TIMEOUT_MILLIS)).inOrder();
+        assertThat(mVibratorProviders.get(1).getAmplitudes())
+                .containsExactlyElementsIn(expectedAmplitudes(255)).inOrder();
+        assertThat(mVibratorProviders.get(2).getEffectSegments(vibration.id))
+                .containsExactly(expectedPrimitive(PRIMITIVE_CLICK, 1, TEST_TIMEOUT_MILLIS))
+                .inOrder();
+        assertThat(mVibratorProviders.get(3).getEffectSegments(vibration.id))
+                .containsExactly(expectedPrebaked(EFFECT_CLICK)).inOrder();
     }
 
     private void mockVibrators(int... vibratorIds) {
@@ -2109,7 +2153,7 @@
         mVibrationConductor = new VibrationStepConductor(vib, isInSession, mVibrationSettings,
                 deviceAdapter, mVibrationScaler, mStatsLoggerMock, requestVibrationParamsFuture,
                 mManagerHooks);
-        assertTrue(mThread.runVibrationOnVibrationThread(mVibrationConductor));
+        assertThat(mThread.runVibrationOnVibrationThread(mVibrationConductor)).isTrue();
         return mVibrationConductor.getVibration();
     }
 
@@ -2129,8 +2173,8 @@
     }
 
     private void waitForCompletion(long timeout) {
-        assertTrue("Timed out waiting for VibrationThread to become idle",
-                mThread.waitForThreadIdle(timeout));
+        assertWithMessage("Timed out waiting for VibrationThread to become idle")
+                .that(mThread.waitForThreadIdle(timeout)).isTrue();
         mTestLooper.dispatchAll();  // Flush callbacks
     }
 
@@ -2205,7 +2249,7 @@
 
     private void verifyCallbacksTriggered(HalVibration vibration, Status expectedStatus) {
         assertThat(vibration.getStatus()).isEqualTo(expectedStatus);
-        verify(mManagerHooks).onVibrationThreadReleased(vibration.id);
+        verify(mManagerHooks).onVibrationThreadReleased(eq(vibration.id));
     }
 
     private static final class TestLooperAutoDispatcher extends Thread {
diff --git a/services/tests/vibrator/src/com/android/server/vibrator/VibratorControllerTest.java b/services/tests/vibrator/src/com/android/server/vibrator/VibratorControllerTest.java
index 0978f48..4df13de 100644
--- a/services/tests/vibrator/src/com/android/server/vibrator/VibratorControllerTest.java
+++ b/services/tests/vibrator/src/com/android/server/vibrator/VibratorControllerTest.java
@@ -203,9 +203,10 @@
 
     @Test
     public void setAmplitude_vibratorVibrating_setsAmplitude() {
-        when(mNativeWrapperMock.on(anyLong(), anyLong())).thenAnswer(args -> args.getArgument(0));
+        when(mNativeWrapperMock.on(anyLong(), anyLong(), anyLong()))
+                .thenAnswer(args -> args.getArgument(0));
         VibratorController controller = createController();
-        controller.on(100, /* vibrationId= */ 1);
+        controller.on(100, 1, 1);
         assertTrue(controller.isVibrating());
         assertEquals(-1, controller.getCurrentAmplitude(), /* delta= */ 0);
 
@@ -215,81 +216,84 @@
 
     @Test
     public void on_withDuration_turnsVibratorOn() {
-        when(mNativeWrapperMock.on(anyLong(), anyLong())).thenAnswer(args -> args.getArgument(0));
+        when(mNativeWrapperMock.on(anyLong(), anyLong(), anyLong()))
+                .thenAnswer(args -> args.getArgument(0));
         VibratorController controller = createController();
-        controller.on(100, 10);
+        controller.on(100, 10, 20);
 
         assertTrue(controller.isVibrating());
-        verify(mNativeWrapperMock).on(eq(100L), eq(10L));
+        verify(mNativeWrapperMock).on(eq(100L), eq(10L), eq(20L));
     }
 
     @Test
     public void on_withPrebaked_performsEffect() {
-        when(mNativeWrapperMock.perform(anyLong(), anyLong(), anyLong())).thenReturn(10L);
+        when(mNativeWrapperMock.perform(anyLong(), anyLong(), anyLong(), anyLong()))
+                .thenReturn(10L);
         VibratorController controller = createController();
 
         PrebakedSegment prebaked = createPrebaked(VibrationEffect.EFFECT_CLICK,
                 VibrationEffect.EFFECT_STRENGTH_MEDIUM);
-        assertEquals(10L, controller.on(prebaked, 11));
+        assertEquals(10L, controller.on(prebaked, 11, 23));
 
         assertTrue(controller.isVibrating());
         verify(mNativeWrapperMock).perform(eq((long) VibrationEffect.EFFECT_CLICK),
-                eq((long) VibrationEffect.EFFECT_STRENGTH_MEDIUM), eq(11L));
+                eq((long) VibrationEffect.EFFECT_STRENGTH_MEDIUM), eq(11L), eq(23L));
     }
 
     @Test
     public void on_withComposed_performsEffect() {
         mockVibratorCapabilities(IVibrator.CAP_COMPOSE_EFFECTS);
-        when(mNativeWrapperMock.compose(any(), anyLong())).thenReturn(15L);
+        when(mNativeWrapperMock.compose(any(), anyLong(), anyLong())).thenReturn(15L);
         VibratorController controller = createController();
 
         PrimitiveSegment[] primitives = new PrimitiveSegment[]{
                 new PrimitiveSegment(VibrationEffect.Composition.PRIMITIVE_CLICK, 0.5f, 10)
         };
-        assertEquals(15L, controller.on(primitives, 12));
+        assertEquals(15L, controller.on(primitives, 12, 34));
 
         assertTrue(controller.isVibrating());
-        verify(mNativeWrapperMock).compose(eq(primitives), eq(12L));
+        verify(mNativeWrapperMock).compose(eq(primitives), eq(12L), eq(34L));
     }
 
     @Test
     public void on_withComposedPwle_performsEffect() {
         mockVibratorCapabilities(IVibrator.CAP_COMPOSE_PWLE_EFFECTS);
-        when(mNativeWrapperMock.composePwle(any(), anyInt(), anyLong())).thenReturn(15L);
+        when(mNativeWrapperMock.composePwle(any(), anyInt(), anyLong(), anyLong())).thenReturn(15L);
         VibratorController controller = createController();
 
         RampSegment[] primitives = new RampSegment[]{
                 new RampSegment(/* startAmplitude= */ 0, /* endAmplitude= */ 1,
                         /* startFrequencyHz= */ 100, /* endFrequencyHz= */ 200, /* duration= */ 10)
         };
-        assertEquals(15L, controller.on(primitives, 12));
+        assertEquals(15L, controller.on(primitives, 12, 45));
         assertTrue(controller.isVibrating());
 
-        verify(mNativeWrapperMock).composePwle(eq(primitives), eq(Braking.NONE), eq(12L));
+        verify(mNativeWrapperMock).composePwle(eq(primitives), eq(Braking.NONE), eq(12L), eq(45L));
     }
 
     @Test
     public void on_withComposedPwleV2_performsEffect() {
         mockVibratorCapabilities(IVibrator.CAP_COMPOSE_PWLE_EFFECTS_V2);
-        when(mNativeWrapperMock.composePwleV2(any(), anyLong())).thenReturn(15L);
+        when(mNativeWrapperMock.composePwleV2(any(), anyLong(), anyLong())).thenReturn(15L);
         VibratorController controller = createController();
 
         PwlePoint[] primitives = new PwlePoint[]{
                 new PwlePoint(/*amplitude=*/ 0, /*frequencyHz=*/ 100, /*timeMillis=*/ 0),
                 new PwlePoint(/*amplitude=*/ 1, /*frequencyHz=*/ 200, /*timeMillis=*/ 10)
         };
-        assertEquals(15L, controller.on(primitives, 12));
+        assertEquals(15L, controller.on(primitives, 12, 53));
         assertTrue(controller.isVibrating());
 
-        verify(mNativeWrapperMock).composePwleV2(eq(primitives), eq(12L));
+        verify(mNativeWrapperMock).composePwleV2(eq(primitives), eq(12L), eq(53L));
     }
 
     @Test
     public void off_turnsOffVibrator() {
-        when(mNativeWrapperMock.on(anyLong(), anyLong())).thenAnswer(args -> args.getArgument(0));
+        when(mNativeWrapperMock.on(anyLong(), anyLong(), anyLong()))
+                .thenAnswer(args -> args.getArgument(0));
         VibratorController controller = createController();
 
-        controller.on(100, 1);
+        controller.on(100, 1, 1);
         assertTrue(controller.isVibrating());
 
         controller.off();
@@ -301,10 +305,11 @@
     @Test
     public void reset_turnsOffVibratorAndDisablesExternalControl() {
         mockVibratorCapabilities(IVibrator.CAP_EXTERNAL_CONTROL);
-        when(mNativeWrapperMock.on(anyLong(), anyLong())).thenAnswer(args -> args.getArgument(0));
+        when(mNativeWrapperMock.on(anyLong(), anyLong(), anyLong()))
+                .thenAnswer(args -> args.getArgument(0));
         VibratorController controller = createController();
 
-        controller.on(100, 1);
+        controller.on(100, 1, 1);
         assertTrue(controller.isVibrating());
 
         controller.reset();
@@ -315,12 +320,13 @@
 
     @Test
     public void registerVibratorStateListener_callbacksAreTriggered() throws Exception {
-        when(mNativeWrapperMock.on(anyLong(), anyLong())).thenAnswer(args -> args.getArgument(0));
+        when(mNativeWrapperMock.on(anyLong(), anyLong(), anyLong()))
+                .thenAnswer(args -> args.getArgument(0));
         VibratorController controller = createController();
 
         controller.registerVibratorStateListener(mVibratorStateListenerMock);
-        controller.on(10, 1);
-        controller.on(100, 2);
+        controller.on(10, 1, 1);
+        controller.on(100, 2, 1);
         controller.off();
         controller.off();
 
@@ -334,19 +340,20 @@
 
     @Test
     public void unregisterVibratorStateListener_callbackNotTriggeredAfter() throws Exception {
-        when(mNativeWrapperMock.on(anyLong(), anyLong())).thenAnswer(args -> args.getArgument(0));
+        when(mNativeWrapperMock.on(anyLong(), anyLong(), anyLong()))
+                .thenAnswer(args -> args.getArgument(0));
         VibratorController controller = createController();
 
         controller.registerVibratorStateListener(mVibratorStateListenerMock);
         verify(mVibratorStateListenerMock).onVibrating(false);
 
-        controller.on(10, 1);
+        controller.on(10, 1, 1);
         verify(mVibratorStateListenerMock).onVibrating(true);
 
         controller.unregisterVibratorStateListener(mVibratorStateListenerMock);
         Mockito.clearInvocations(mVibratorStateListenerMock);
 
-        controller.on(10, 1);
+        controller.on(10, 1, 1);
         verifyNoMoreInteractions(mVibratorStateListenerMock);
     }
 
diff --git a/services/tests/vibrator/utils/com/android/server/vibrator/FakeVibratorControllerProvider.java b/services/tests/vibrator/utils/com/android/server/vibrator/FakeVibratorControllerProvider.java
index 3f34767..bd806b7 100644
--- a/services/tests/vibrator/utils/com/android/server/vibrator/FakeVibratorControllerProvider.java
+++ b/services/tests/vibrator/utils/com/android/server/vibrator/FakeVibratorControllerProvider.java
@@ -118,11 +118,11 @@
         }
 
         @Override
-        public long on(long milliseconds, long vibrationId) {
+        public long on(long milliseconds, long vibrationId, long stepId) {
             recordEffectSegment(vibrationId, new StepSegment(VibrationEffect.DEFAULT_AMPLITUDE,
                     /* frequencyHz= */ 0, (int) milliseconds));
             applyLatency(mOnLatency);
-            scheduleListener(milliseconds, vibrationId);
+            scheduleListener(milliseconds, vibrationId, stepId);
             return milliseconds;
         }
 
@@ -139,7 +139,7 @@
         }
 
         @Override
-        public long perform(long effect, long strength, long vibrationId) {
+        public long perform(long effect, long strength, long vibrationId, long stepId) {
             if (mSupportedEffects == null
                     || Arrays.binarySearch(mSupportedEffects, (int) effect) < 0) {
                 return 0;
@@ -147,13 +147,13 @@
             recordEffectSegment(vibrationId,
                     new PrebakedSegment((int) effect, false, (int) strength));
             applyLatency(mOnLatency);
-            scheduleListener(EFFECT_DURATION, vibrationId);
+            scheduleListener(EFFECT_DURATION, vibrationId, stepId);
             return EFFECT_DURATION;
         }
 
         @Override
         public long performVendorEffect(Parcel vendorData, long strength, float scale,
-                float adaptiveScale, long vibrationId) {
+                float adaptiveScale, long vibrationId, long stepId) {
             if ((mCapabilities & IVibrator.CAP_PERFORM_VENDOR_EFFECTS) == 0) {
                 return 0;
             }
@@ -161,13 +161,13 @@
             recordVendorEffect(vibrationId,
                     new VibrationEffect.VendorEffect(bundle, (int) strength, scale, adaptiveScale));
             applyLatency(mOnLatency);
-            scheduleListener(mVendorEffectDuration, vibrationId);
+            scheduleListener(mVendorEffectDuration, vibrationId, stepId);
             // HAL has unknown duration for vendor effects.
             return Long.MAX_VALUE;
         }
 
         @Override
-        public long compose(PrimitiveSegment[] primitives, long vibrationId) {
+        public long compose(PrimitiveSegment[] primitives, long vibrationId, long stepId) {
             if (mSupportedPrimitives == null) {
                 return 0;
             }
@@ -182,12 +182,13 @@
                 recordEffectSegment(vibrationId, primitive);
             }
             applyLatency(mOnLatency);
-            scheduleListener(duration, vibrationId);
+            scheduleListener(duration, vibrationId, stepId);
             return duration;
         }
 
         @Override
-        public long composePwle(RampSegment[] primitives, int braking, long vibrationId) {
+        public long composePwle(RampSegment[] primitives, int braking, long vibrationId,
+                long stepId) {
             long duration = 0;
             for (RampSegment primitive : primitives) {
                 duration += primitive.getDuration();
@@ -195,19 +196,19 @@
             }
             recordBraking(vibrationId, braking);
             applyLatency(mOnLatency);
-            scheduleListener(duration, vibrationId);
+            scheduleListener(duration, vibrationId, stepId);
             return duration;
         }
 
         @Override
-        public long composePwleV2(PwlePoint[] pwlePoints, long vibrationId) {
+        public long composePwleV2(PwlePoint[] pwlePoints, long vibrationId, long stepId) {
             long duration = 0;
             for (PwlePoint pwlePoint: pwlePoints) {
                 duration += pwlePoint.getTimeMillis();
                 recordEffectPwlePoint(vibrationId, pwlePoint);
             }
             applyLatency(mOnLatency);
-            scheduleListener(duration, vibrationId);
+            scheduleListener(duration, vibrationId, stepId);
 
             return duration;
         }
@@ -263,8 +264,8 @@
             }
         }
 
-        private void scheduleListener(long vibrationDuration, long vibrationId) {
-            mHandler.postDelayed(() -> listener.onComplete(vibratorId, vibrationId),
+        private void scheduleListener(long vibrationDuration, long vibrationId, long stepId) {
+            mHandler.postDelayed(() -> listener.onComplete(vibratorId, vibrationId, stepId),
                     vibrationDuration + mCompletionCallbackDelay);
         }
     }
diff --git a/services/tests/wmtests/src/com/android/server/wm/ActivityRecordTests.java b/services/tests/wmtests/src/com/android/server/wm/ActivityRecordTests.java
index d53ba1d..301a754 100644
--- a/services/tests/wmtests/src/com/android/server/wm/ActivityRecordTests.java
+++ b/services/tests/wmtests/src/com/android/server/wm/ActivityRecordTests.java
@@ -56,8 +56,6 @@
 import static android.view.WindowManager.LayoutParams.TYPE_APPLICATION;
 import static android.view.WindowManager.LayoutParams.TYPE_APPLICATION_STARTING;
 import static android.view.WindowManager.LayoutParams.TYPE_BASE_APPLICATION;
-import static android.view.WindowManager.TRANSIT_CLOSE;
-import static android.view.WindowManager.TRANSIT_OLD_ACTIVITY_OPEN;
 import static android.view.WindowManager.TRANSIT_PIP;
 import static android.window.StartingWindowInfo.TYPE_PARAMETER_LEGACY_SPLASH_SCREEN;
 
@@ -921,12 +919,6 @@
         // animation and AR#takeSceneTransitionInfo also clear the AR#mPendingOptions
         assertNull(activity.takeSceneTransitionInfo());
         assertNull(activity.getOptions());
-
-        final AppTransition appTransition = activity.mDisplayContent.mAppTransition;
-        spyOn(appTransition);
-        activity.applyOptionsAnimation();
-
-        verify(appTransition).overridePendingAppTransitionRemote(any());
     }
 
     @Test
@@ -1190,7 +1182,6 @@
                 FINISH_RESULT_REQUESTED, activity.finishIfPossible("test", false /* oomAdj */));
         assertEquals(PAUSING, activity.getState());
         verify(activity).setVisibility(eq(false));
-        verify(activity.mDisplayContent).prepareAppTransition(eq(TRANSIT_CLOSE));
     }
 
     /**
@@ -1237,7 +1228,6 @@
         activity.finishIfPossible("test", false /* oomAdj */);
 
         verify(activity).setVisibility(eq(false));
-        verify(activity.mDisplayContent).prepareAppTransition(eq(TRANSIT_CLOSE));
         verify(activity.mDisplayContent, never()).executeAppTransition();
     }
 
@@ -1254,7 +1244,6 @@
         activity.finishIfPossible("test", false /* oomAdj */);
 
         verify(activity, atLeast(1)).setVisibility(eq(false));
-        verify(activity.mDisplayContent).prepareAppTransition(eq(TRANSIT_CLOSE));
         verify(activity.mDisplayContent).executeAppTransition();
     }
 
@@ -1275,7 +1264,6 @@
 
         activity.finishIfPossible("test", false /* oomAdj */);
 
-        verify(activity.mDisplayContent, never()).prepareAppTransition(eq(TRANSIT_CLOSE));
         assertFalse(activity.inTransition());
 
         // finishIfPossible -> completeFinishing -> addToFinishingAndWaitForIdle
@@ -2657,10 +2645,6 @@
     @Presubmit
     public void testGetOrientation() {
         mDisplayContent.setIgnoreOrientationRequest(false);
-        // ActivityBuilder will resume top activities and cause the activity been added into
-        // opening apps list. Since this test is focus on the effect of visible on getting
-        // orientation, we skip app transition to avoid interference.
-        doNothing().when(mDisplayContent).prepareAppTransition(anyInt());
         final ActivityRecord activity = new ActivityBuilder(mAtm).setCreateTask(true).build();
         activity.setVisible(true);
 
@@ -2925,7 +2909,6 @@
         sources.add(activity2);
         doReturn(true).when(activity2).okToAnimate();
         doReturn(true).when(activity2).isAnimating();
-        assertTrue(activity2.applyAnimation(null, TRANSIT_OLD_ACTIVITY_OPEN, true, false, sources));
     }
     @Test
     public void testTrackingStartingWindowThroughTrampoline() {
diff --git a/services/tests/wmtests/src/com/android/server/wm/AppCompatCameraOverridesTest.java b/services/tests/wmtests/src/com/android/server/wm/AppCompatCameraOverridesTest.java
index 4ad1cd1..d4be7f8 100644
--- a/services/tests/wmtests/src/com/android/server/wm/AppCompatCameraOverridesTest.java
+++ b/services/tests/wmtests/src/com/android/server/wm/AppCompatCameraOverridesTest.java
@@ -23,12 +23,14 @@
 import static android.content.pm.ActivityInfo.OVERRIDE_MIN_ASPECT_RATIO_ONLY_FOR_CAMERA;
 import static android.content.pm.ActivityInfo.OVERRIDE_ORIENTATION_ONLY_FOR_CAMERA;
 import static android.view.WindowManager.PROPERTY_CAMERA_COMPAT_ALLOW_FORCE_ROTATION;
+import static android.view.WindowManager.PROPERTY_CAMERA_COMPAT_ALLOW_SIMULATE_REQUESTED_ORIENTATION;
 import static android.view.WindowManager.PROPERTY_CAMERA_COMPAT_ALLOW_REFRESH;
 import static android.view.WindowManager.PROPERTY_CAMERA_COMPAT_ENABLE_REFRESH_VIA_PAUSE;
 import static android.view.WindowManager.PROPERTY_COMPAT_ALLOW_MIN_ASPECT_RATIO_OVERRIDE;
 
 import static com.android.dx.mockito.inline.extended.ExtendedMockito.spyOn;
 import static com.android.window.flags.Flags.FLAG_ENABLE_CAMERA_COMPAT_FOR_DESKTOP_WINDOWING;
+import static com.android.window.flags.Flags.FLAG_ENABLE_CAMERA_COMPAT_FOR_DESKTOP_WINDOWING_OPT_OUT;
 
 import android.compat.testing.PlatformCompatChangeRule;
 import android.platform.test.annotations.DisableFlags;
@@ -239,6 +241,34 @@
 
     @Test
     @EnableCompatChanges({OVERRIDE_CAMERA_COMPAT_ENABLE_FREEFORM_WINDOWING_TREATMENT})
+    @EnableFlags({FLAG_ENABLE_CAMERA_COMPAT_FOR_DESKTOP_WINDOWING,
+            FLAG_ENABLE_CAMERA_COMPAT_FOR_DESKTOP_WINDOWING_OPT_OUT})
+    public void testShouldApplyCameraCompatFreeformTreatment_propertyFalse_returnsFalse() {
+        runTestScenario((robot) -> {
+            robot.activity().createActivityWithComponentInNewTask();
+
+            robot.prop().disable(PROPERTY_CAMERA_COMPAT_ALLOW_SIMULATE_REQUESTED_ORIENTATION);
+
+            robot.checkShouldApplyFreeformTreatmentForCameraCompat(false);
+        });
+    }
+
+    @Test
+    @EnableCompatChanges({OVERRIDE_CAMERA_COMPAT_ENABLE_FREEFORM_WINDOWING_TREATMENT})
+    @EnableFlags(FLAG_ENABLE_CAMERA_COMPAT_FOR_DESKTOP_WINDOWING)
+    @DisableFlags(FLAG_ENABLE_CAMERA_COMPAT_FOR_DESKTOP_WINDOWING_OPT_OUT)
+    public void testShouldApplyCameraCompatFreeformTreatment_optOutFlagNotEnabled_optOutIgnored() {
+        runTestScenario((robot) -> {
+            robot.activity().createActivityWithComponentInNewTask();
+
+            robot.prop().disable(PROPERTY_CAMERA_COMPAT_ALLOW_SIMULATE_REQUESTED_ORIENTATION);
+
+            robot.checkShouldApplyFreeformTreatmentForCameraCompat(true);
+        });
+    }
+
+    @Test
+    @EnableCompatChanges({OVERRIDE_CAMERA_COMPAT_ENABLE_FREEFORM_WINDOWING_TREATMENT})
     @EnableFlags(FLAG_ENABLE_CAMERA_COMPAT_FOR_DESKTOP_WINDOWING)
     public void testShouldApplyCameraCompatFreeformTreatment_overrideAndFlagEnabled_returnsTrue() {
         runTestScenario((robot) -> {
diff --git a/services/tests/wmtests/src/com/android/server/wm/DisplayContentTests.java b/services/tests/wmtests/src/com/android/server/wm/DisplayContentTests.java
index d5b9751..a99bc49 100644
--- a/services/tests/wmtests/src/com/android/server/wm/DisplayContentTests.java
+++ b/services/tests/wmtests/src/com/android/server/wm/DisplayContentTests.java
@@ -37,7 +37,6 @@
 import static android.view.Surface.ROTATION_180;
 import static android.view.Surface.ROTATION_270;
 import static android.view.Surface.ROTATION_90;
-import static android.view.WindowInsets.Type.ime;
 import static android.view.WindowInsets.Type.navigationBars;
 import static android.view.WindowInsets.Type.statusBars;
 import static android.view.WindowInsetsController.BEHAVIOR_SHOW_TRANSIENT_BARS_BY_SWIPE;
@@ -56,6 +55,7 @@
 import static android.view.WindowManager.LayoutParams.TYPE_APPLICATION_OVERLAY;
 import static android.view.WindowManager.LayoutParams.TYPE_APPLICATION_STARTING;
 import static android.view.WindowManager.LayoutParams.TYPE_BASE_APPLICATION;
+import static android.view.WindowManager.LayoutParams.TYPE_INPUT_METHOD;
 import static android.view.WindowManager.LayoutParams.TYPE_INPUT_METHOD_DIALOG;
 import static android.view.WindowManager.LayoutParams.TYPE_NAVIGATION_BAR;
 import static android.view.WindowManager.LayoutParams.TYPE_NOTIFICATION_SHADE;
@@ -63,9 +63,6 @@
 import static android.view.WindowManager.LayoutParams.TYPE_STATUS_BAR;
 import static android.view.WindowManager.LayoutParams.TYPE_VOICE_INTERACTION;
 import static android.view.WindowManager.LayoutParams.TYPE_WALLPAPER;
-import static android.view.WindowManager.TRANSIT_CLOSE;
-import static android.view.WindowManager.TRANSIT_OLD_TASK_CLOSE;
-import static android.view.WindowManager.TRANSIT_OLD_TRANSLUCENT_ACTIVITY_CLOSE;
 import static android.window.DisplayAreaOrganizer.FEATURE_WINDOWED_MAGNIFICATION;
 
 import static com.android.dx.mockito.inline.extended.ExtendedMockito.any;
@@ -1742,8 +1739,6 @@
     public void testFixedRotationWithPip() {
         final DisplayContent displayContent = mDefaultDisplay;
         displayContent.setIgnoreOrientationRequest(false);
-        // Unblock the condition in PinnedTaskController#continueOrientationChangeIfNeeded.
-        doNothing().when(displayContent).prepareAppTransition(anyInt());
         // Make resume-top really update the activity state.
         setBooted(mAtm);
         clearInvocations(mWm);
@@ -1826,7 +1821,7 @@
                 .setTask(nonTopVisible.getTask()).setVisible(false)
                 .setActivityTheme(android.R.style.Theme_Translucent).build();
         final TestTransitionPlayer player = registerTestTransitionPlayer();
-        mDisplayContent.requestTransitionAndLegacyPrepare(WindowManager.TRANSIT_OPEN, 0);
+        mDisplayContent.requestTransitionAndLegacyPrepare(WindowManager.TRANSIT_OPEN, 0, null);
         translucentTop.setVisibility(true);
         mDisplayContent.updateOrientation();
         assertEquals("Non-top visible activity must be portrait",
@@ -2373,33 +2368,6 @@
 
     @SetupWindows(addWindows = W_INPUT_METHOD)
     @Test
-    public void testShowImeScreenshot() {
-        final Task rootTask = createTask(mDisplayContent);
-        final Task task = createTaskInRootTask(rootTask, 0 /* userId */);
-        final ActivityRecord activity = createActivityRecord(mDisplayContent, task);
-        final WindowState win = newWindowBuilder("win", TYPE_BASE_APPLICATION).setWindowToken(
-                activity).build();
-        task.getDisplayContent().prepareAppTransition(TRANSIT_CLOSE);
-        doReturn(true).when(task).okToAnimate();
-        ArrayList<WindowContainer> sources = new ArrayList<>();
-        sources.add(activity);
-
-        mDisplayContent.setImeLayeringTarget(win);
-        spyOn(mDisplayContent);
-
-        // Expecting the IME screenshot only be attached when performing task closing transition.
-        task.applyAnimation(null, TRANSIT_OLD_TASK_CLOSE, false /* enter */,
-                false /* isVoiceInteraction */, sources);
-        verify(mDisplayContent).showImeScreenshot();
-
-        clearInvocations(mDisplayContent);
-        activity.applyAnimation(null, TRANSIT_OLD_TRANSLUCENT_ACTIVITY_CLOSE, false /* enter */,
-                false /* isVoiceInteraction */, sources);
-        verify(mDisplayContent, never()).showImeScreenshot();
-    }
-
-    @SetupWindows(addWindows = W_INPUT_METHOD)
-    @Test
     public void testShowImeScreenshot_removeCurSnapshotBeforeCreateNext() {
         final Task rootTask = createTask(mDisplayContent);
         final Task task = createTaskInRootTask(rootTask, 0 /* userId */);
@@ -2427,32 +2395,6 @@
 
     @UseTestDisplay(addWindows = {W_INPUT_METHOD})
     @Test
-    public void testRemoveImeScreenshot_whenTargetSurfaceWasInvisible() {
-        final Task rootTask = createTask(mDisplayContent);
-        final Task task = createTaskInRootTask(rootTask, 0 /* userId */);
-        final ActivityRecord activity = createActivityRecord(mDisplayContent, task);
-        final WindowState win = newWindowBuilder("win", TYPE_BASE_APPLICATION).setWindowToken(
-                activity).build();
-        win.onSurfaceShownChanged(true);
-        makeWindowVisible(win, mDisplayContent.mInputMethodWindow);
-        task.getDisplayContent().prepareAppTransition(TRANSIT_CLOSE);
-        doReturn(true).when(task).okToAnimate();
-        ArrayList<WindowContainer> sources = new ArrayList<>();
-        sources.add(activity);
-
-        mDisplayContent.setImeLayeringTarget(win);
-        mDisplayContent.setImeInputTarget(win);
-        mDisplayContent.getInsetsStateController().getImeSourceProvider().setImeShowing(true);
-        task.applyAnimation(null, TRANSIT_OLD_TASK_CLOSE, false /* enter */,
-                false /* isVoiceInteraction */, sources);
-        assertNotNull(mDisplayContent.mImeScreenshot);
-
-        win.onSurfaceShownChanged(false);
-        assertNull(mDisplayContent.mImeScreenshot);
-    }
-
-    @UseTestDisplay(addWindows = {W_INPUT_METHOD})
-    @Test
     public void testRemoveImeScreenshot_whenWindowRemoveImmediately() {
         final Task rootTask = createTask(mDisplayContent);
         final Task task = createTaskInRootTask(rootTask, 0 /* userId */);
@@ -2751,55 +2693,77 @@
 
     @SetupWindows(addWindows = W_INPUT_METHOD)
     @Test
-    public void testImeChildWindowFocusWhenImeLayeringTargetChanges() {
-        final WindowState imeChildWindow = newWindowBuilder("imeChildWindow",
+    public void testImeChildWindowFocusWhenImeParentWindowChanges() {
+        final var imeChildWindow = newWindowBuilder("imeChildWindow",
                 TYPE_APPLICATION_ATTACHED_DIALOG).setParent(mImeWindow).build();
-        makeWindowVisibleAndDrawn(imeChildWindow, mImeWindow);
-        assertTrue(imeChildWindow.canReceiveKeys());
-        mDisplayContent.setInputMethodWindowLocked(mImeWindow);
-
-        // Verify imeChildWindow can be focused window if the next IME target requests IME visible.
-        final WindowState imeAppTarget = newWindowBuilder("imeAppTarget",
-                TYPE_BASE_APPLICATION).setDisplay(mDisplayContent).build();
-        mDisplayContent.setImeLayeringTarget(imeAppTarget);
-        spyOn(imeAppTarget);
-        doReturn(true).when(imeAppTarget).isRequestedVisible(ime());
-        assertEquals(imeChildWindow, mDisplayContent.findFocusedWindow());
-
-        // Verify imeChildWindow doesn't be focused window if the next IME target does not
-        // request IME visible.
-        final WindowState nextImeAppTarget = newWindowBuilder("nextImeAppTarget",
-                TYPE_BASE_APPLICATION).setDisplay(mDisplayContent).build();
-        mDisplayContent.setImeLayeringTarget(nextImeAppTarget);
-        assertNotEquals(imeChildWindow, mDisplayContent.findFocusedWindow());
+        doTestImeWindowFocusWhenImeParentWindowChanged(imeChildWindow);
     }
 
     @SetupWindows(addWindows = W_INPUT_METHOD)
     @Test
-    public void testImeMenuDialogFocusWhenImeLayeringTargetChanges() {
-        final WindowState imeMenuDialog = newWindowBuilder("imeMenuDialog",
+    public void testImeDialogWindowFocusWhenImeParentWindowChanges() {
+        final var imeDialogWindow = newWindowBuilder("imeMenuDialog",
                 TYPE_INPUT_METHOD_DIALOG).build();
-        makeWindowVisibleAndDrawn(imeMenuDialog, mImeWindow);
-        assertTrue(imeMenuDialog.canReceiveKeys());
+        doTestImeWindowFocusWhenImeParentWindowChanged(imeDialogWindow);
+    }
+
+    @SetupWindows(addWindows = W_INPUT_METHOD)
+    @Test
+    public void testImeWindowFocusWhenImeParentWindowChanges() {
+        // Verify focusable, non-child IME windows.
+        final var otherImeWindow = newWindowBuilder("otherImeWindow",
+                TYPE_INPUT_METHOD).build();
+        doTestImeWindowFocusWhenImeParentWindowChanged(otherImeWindow);
+    }
+
+    private void doTestImeWindowFocusWhenImeParentWindowChanged(@NonNull WindowState window) {
+        makeWindowVisibleAndDrawn(window, mImeWindow);
+        assertTrue("Window canReceiveKeys", window.canReceiveKeys());
         mDisplayContent.setInputMethodWindowLocked(mImeWindow);
 
-        // Verify imeMenuDialog can be focused window if the next IME target requests IME visible.
+        // Verify window can be focused if the IME parent is visible and the IME is visible.
         final WindowState imeAppTarget = newWindowBuilder("imeAppTarget",
                 TYPE_BASE_APPLICATION).setDisplay(mDisplayContent).build();
         mDisplayContent.setImeLayeringTarget(imeAppTarget);
-        imeAppTarget.setRequestedVisibleTypes(ime());
-        assertEquals(imeMenuDialog, mDisplayContent.findFocusedWindow());
+        mDisplayContent.updateImeInputAndControlTarget(imeAppTarget);
+        final var imeProvider = mDisplayContent.getInsetsStateController().getImeSourceProvider();
+        imeProvider.setImeShowing(true);
+        final var imeParentWindow = mDisplayContent.getImeParentWindow();
+        assertNotNull("IME parent window is not null", imeParentWindow);
+        assertTrue("IME parent window is visible", imeParentWindow.isVisibleRequested());
+        assertTrue("IME is visible", imeProvider.isImeShowing());
+        assertEquals("Window is the focused one", window, mDisplayContent.findFocusedWindow());
 
-        // Verify imeMenuDialog doesn't be focused window if the next IME target is closing.
+        // Verify window can't be focused if the IME parent is not visible.
         final WindowState nextImeAppTarget = newWindowBuilder("nextImeAppTarget",
                 TYPE_BASE_APPLICATION).setDisplay(mDisplayContent).build();
         makeWindowVisibleAndDrawn(nextImeAppTarget);
-        // Even if the app still requests IME, the ime dialog should not gain focus if the target
-        // app is invisible.
-        nextImeAppTarget.setRequestedVisibleTypes(ime());
-        nextImeAppTarget.mActivityRecord.setVisibility(false);
+        // Change layering target but keep input target (and thus imeParent) the same.
         mDisplayContent.setImeLayeringTarget(nextImeAppTarget);
-        assertNotEquals(imeMenuDialog, mDisplayContent.findFocusedWindow());
+        // IME parent window is not visible, occluded by new layering target.
+        imeParentWindow.setVisibleRequested(false);
+        assertEquals("IME parent window did not change", imeParentWindow,
+                mDisplayContent.getImeParentWindow());
+        assertFalse("IME parent window is not visible", imeParentWindow.isVisibleRequested());
+        assertTrue("IME is visible", imeProvider.isImeShowing());
+        assertNotEquals("Window is not the focused one when imeParent is not visible", window,
+                mDisplayContent.findFocusedWindow());
+
+        // Verify window can be focused if the IME is not visible.
+        mDisplayContent.updateImeInputAndControlTarget(nextImeAppTarget);
+        imeProvider.setImeShowing(false);
+        final var nextImeParentWindow = mDisplayContent.getImeParentWindow();
+        assertNotNull("Next IME parent window is not null", nextImeParentWindow);
+        assertNotEquals("IME parent window changed", imeParentWindow, nextImeParentWindow);
+        assertTrue("Next IME parent window is visible", nextImeParentWindow.isVisibleRequested());
+        assertFalse("IME is not visible", imeProvider.isImeShowing());
+        if (window.isChildWindow()) {
+            assertNotEquals("Child window is not the focused on when the IME is not visible",
+                    window, mDisplayContent.findFocusedWindow());
+        } else {
+            assertEquals("Window is the focused one when the IME is not visible",
+                    window, mDisplayContent.findFocusedWindow());
+        }
     }
 
     @Test
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 7c8a883..2d4101e 100644
--- a/services/tests/wmtests/src/com/android/server/wm/PresentationControllerTests.java
+++ b/services/tests/wmtests/src/com/android/server/wm/PresentationControllerTests.java
@@ -31,7 +31,7 @@
 import android.annotation.NonNull;
 import android.graphics.Rect;
 import android.os.UserHandle;
-import android.os.UserManager;
+import android.platform.test.annotations.DisableFlags;
 import android.platform.test.annotations.EnableFlags;
 import android.platform.test.annotations.Presubmit;
 import android.view.DisplayInfo;
@@ -68,32 +68,14 @@
     @EnableFlags(FLAG_ENABLE_PRESENTATION_FOR_CONNECTED_DISPLAYS)
     @Test
     public void testPresentationShowAndHide() {
-        final DisplayInfo displayInfo = new DisplayInfo();
-        displayInfo.copyFrom(mDisplayInfo);
-        displayInfo.flags = FLAG_PRESENTATION;
-        final DisplayContent dc = createNewDisplay(displayInfo);
-        final int displayId = dc.getDisplayId();
-        doReturn(dc).when(mWm.mRoot).getDisplayContentOrCreate(displayId);
+        final DisplayContent dc = createPresentationDisplay();
         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);
-        final int userId = UserHandle.getUserId(uid);
-        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);
+        // Add a presentation window, which requests the activity to stop.
+        final WindowState window = addPresentationWindow(100000, dc.mDisplayId);
         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));
@@ -117,6 +99,51 @@
         assertTrue(activity.isVisible());
     }
 
+    @DisableFlags(FLAG_ENABLE_PRESENTATION_FOR_CONNECTED_DISPLAYS)
+    @Test
+    public void testPresentationShowAndHide_flagDisabled() {
+        final DisplayContent dc = createPresentationDisplay();
+        final ActivityRecord activity = createActivityRecord(createTask(dc));
+        assertTrue(activity.isVisible());
+
+        final WindowState window = addPresentationWindow(100000, dc.mDisplayId);
+        assertFalse(window.mTransitionController.isCollecting());
+        assertTrue(activity.isVisibleRequested());
+        assertTrue(activity.isVisible());
+
+        window.removeIfPossible();
+        assertFalse(window.mTransitionController.isCollecting());
+        assertTrue(activity.isVisibleRequested());
+        assertTrue(activity.isVisible());
+        assertFalse(window.isAttached());
+    }
+
+    private WindowState addPresentationWindow(int uid, int displayId) {
+        final Session session = createTestSession(mAtm, 1234 /* pid */, uid);
+        final int userId = UserHandle.getUserId(uid);
+        doReturn(true).when(mWm.mUmInternal).isUserVisible(eq(userId), eq(displayId));
+        final WindowManager.LayoutParams params = new WindowManager.LayoutParams(
+                WindowManager.LayoutParams.TYPE_PRESENTATION);
+        final IWindow clientWindow = new TestIWindow();
+        final int res = mWm.addWindow(session, clientWindow, params, View.VISIBLE, displayId,
+                userId, WindowInsets.Type.defaultVisible(), null, new InsetsState(),
+                new InsetsSourceControl.Array(), new Rect(), new float[1]);
+        assertTrue(res >= WindowManagerGlobal.ADD_OKAY);
+        final WindowState window = mWm.windowForClientLocked(session, clientWindow, false);
+        window.mHasSurface = true;
+        return window;
+    }
+
+    private DisplayContent createPresentationDisplay() {
+        final DisplayInfo displayInfo = new DisplayInfo();
+        displayInfo.copyFrom(mDisplayInfo);
+        displayInfo.flags = FLAG_PRESENTATION;
+        final DisplayContent dc = createNewDisplay(displayInfo);
+        final int displayId = dc.getDisplayId();
+        doReturn(dc).when(mWm.mRoot).getDisplayContentOrCreate(displayId);
+        return dc;
+    }
+
     private void completeTransition(@NonNull Transition transition, boolean abortSync) {
         final ActionChain chain = ActionChain.testFinish(transition);
         if (abortSync) {
diff --git a/services/tests/wmtests/src/com/android/server/wm/SystemServicesTestRule.java b/services/tests/wmtests/src/com/android/server/wm/SystemServicesTestRule.java
index 9406779..3776b036 100644
--- a/services/tests/wmtests/src/com/android/server/wm/SystemServicesTestRule.java
+++ b/services/tests/wmtests/src/com/android/server/wm/SystemServicesTestRule.java
@@ -264,7 +264,7 @@
         final DisplayManagerGlobal dmg = DisplayManagerGlobal.getInstance();
         spyOn(dmg);
         doNothing().when(dmg).registerDisplayListener(
-                any(), any(Executor.class), anyLong(), anyString());
+                any(), any(Executor.class), anyLong(), anyString(), anyBoolean());
         doNothing().when(dmg).registerTopologyListener(any(Executor.class), any(), anyString());
     }
 
diff --git a/services/tests/wmtests/src/com/android/server/wm/TransitionTests.java b/services/tests/wmtests/src/com/android/server/wm/TransitionTests.java
index 2ee34d3..c0cb09f 100644
--- a/services/tests/wmtests/src/com/android/server/wm/TransitionTests.java
+++ b/services/tests/wmtests/src/com/android/server/wm/TransitionTests.java
@@ -82,8 +82,6 @@
 import android.graphics.Point;
 import android.graphics.Rect;
 import android.os.IBinder;
-import android.platform.test.annotations.DisableFlags;
-import android.platform.test.annotations.EnableFlags;
 import android.platform.test.annotations.Presubmit;
 import android.util.ArrayMap;
 import android.util.ArraySet;
@@ -104,7 +102,6 @@
 import androidx.test.filters.SmallTest;
 
 import com.android.internal.graphics.ColorUtils;
-import com.android.window.flags.Flags;
 
 import org.junit.Test;
 import org.junit.runner.RunWith;
@@ -683,7 +680,7 @@
         app.onStartingWindowDrawn();
         // The task appeared event should be deferred until transition ready.
         assertFalse(task.taskAppearedReady());
-        testPlayer.onTransactionReady(app.getSyncTransaction());
+        testPlayer.onTransactionReady();
         assertTrue(task.taskAppearedReady());
         assertTrue(playerProc.isRunningRemoteTransition());
         assertTrue(controller.mRemotePlayer.reportRunning(delegateProc.getThread()));
@@ -1162,7 +1159,8 @@
         screenDecor.updateSurfacePosition(mMockT);
         assertEquals(prevPos, screenDecor.mLastSurfacePosition);
 
-        final SurfaceControl.Transaction startTransaction = mock(SurfaceControl.Transaction.class);
+        final SurfaceControl.Transaction startTransaction = mTransaction;
+        clearInvocations(startTransaction);
         final SurfaceControl.TransactionCommittedListener transactionCommittedListener =
                 onRotationTransactionReady(player, startTransaction);
 
@@ -1213,7 +1211,8 @@
         assertFalse(statusBar.mToken.inTransition());
         assertTrue(app.getTask().inTransition());
 
-        final SurfaceControl.Transaction startTransaction = mock(SurfaceControl.Transaction.class);
+        final SurfaceControl.Transaction startTransaction = mTransaction;
+        clearInvocations(startTransaction);
         final SurfaceControl leash = statusBar.mToken.getAnimationLeash();
         doReturn(true).when(leash).isValid();
         final SurfaceControl.TransactionCommittedListener transactionCommittedListener =
@@ -1287,7 +1286,8 @@
         // Avoid DeviceStateController disturbing the test by triggering another rotation change.
         doReturn(false).when(mDisplayContent).updateRotationUnchecked();
 
-        onRotationTransactionReady(player, mWm.mTransactionFactory.get()).onTransactionCommitted();
+        clearInvocations(mTransaction);
+        onRotationTransactionReady(player, mTransaction).onTransactionCommitted();
         assertEquals(ROTATION_ANIMATION_SEAMLESS, player.mLastReady.getChange(
                 mDisplayContent.mRemoteToken.toWindowContainerToken()).getRotationAnimation());
         spyOn(navBarInsetsProvider);
@@ -1350,7 +1350,7 @@
         mDisplayContent.setFixedRotationLaunchingAppUnchecked(home);
         doReturn(true).when(home).hasFixedRotationTransform(any());
         player.startTransition();
-        player.onTransactionReady(mDisplayContent.getSyncTransaction());
+        player.onTransactionReady();
 
         final DisplayRotation displayRotation = mDisplayContent.getDisplayRotation();
         final RemoteDisplayChangeController displayChangeController = mDisplayContent
@@ -2009,21 +2009,6 @@
         assertEquals(expectedBackgroundColor, info.getChanges().get(1).getBackgroundColor());
     }
 
-    @DisableFlags(Flags.FLAG_MOVE_ANIMATION_OPTIONS_TO_CHANGE)
-    @Test
-    public void testOverrideAnimationOptionsToInfoIfNecessary_disableAnimOptionsPerChange() {
-        ActivityRecord r = initializeOverrideAnimationOptionsTest();
-        TransitionInfo.AnimationOptions options = TransitionInfo.AnimationOptions
-                .makeCommonAnimOptions("testPackage");
-        mTransition.setOverrideAnimation(options, r, null /* startCallback */,
-                null /* finishCallback */);
-
-        mTransition.overrideAnimationOptionsToInfoIfNecessary(mInfo);
-
-        assertEquals(options, mInfo.getAnimationOptions());
-    }
-
-    @EnableFlags(Flags.FLAG_MOVE_ANIMATION_OPTIONS_TO_CHANGE)
     @Test
     public void testOverrideAnimationOptionsToInfoIfNecessary_fromStyleAnimOptions() {
         ActivityRecord r = initializeOverrideAnimationOptionsTest();
@@ -2049,7 +2034,6 @@
                 options, activityChange.getAnimationOptions());
     }
 
-    @EnableFlags(Flags.FLAG_MOVE_ANIMATION_OPTIONS_TO_CHANGE)
     @Test
     public void testOverrideAnimationOptionsToInfoIfNecessary_sceneAnimOptions() {
         ActivityRecord r = initializeOverrideAnimationOptionsTest();
@@ -2075,7 +2059,6 @@
                 options, activityChange.getAnimationOptions());
     }
 
-    @EnableFlags(Flags.FLAG_MOVE_ANIMATION_OPTIONS_TO_CHANGE)
     @Test
     public void testOverrideAnimationOptionsToInfoIfNecessary_crossProfileAnimOptions() {
         ActivityRecord r = initializeOverrideAnimationOptionsTest();
@@ -2103,7 +2086,6 @@
         assertTrue(activityChange.hasFlags(FLAG_CROSS_PROFILE_OWNER_THUMBNAIL));
     }
 
-    @EnableFlags(Flags.FLAG_MOVE_ANIMATION_OPTIONS_TO_CHANGE)
     @Test
     public void testOverrideAnimationOptionsToInfoIfNecessary_customAnimOptions() {
         ActivityRecord r = initializeOverrideAnimationOptionsTest();
@@ -2136,7 +2118,6 @@
                 options.getBackgroundColor(), activityChange.getBackgroundColor());
     }
 
-    @EnableFlags(Flags.FLAG_MOVE_ANIMATION_OPTIONS_TO_CHANGE)
     @Test
     public void testOverrideAnimationOptionsToInfoIfNecessary_haveTaskFragmentAnimParams() {
         ActivityRecord r = initializeOverrideAnimationOptionsTest();
@@ -2185,7 +2166,6 @@
                 options.getBackgroundColor(), activityChange.getBackgroundColor());
     }
 
-    @EnableFlags(Flags.FLAG_MOVE_ANIMATION_OPTIONS_TO_CHANGE)
     @Test
     public void testOverrideAnimationOptionsToInfoIfNecessary_customAnimOptionsWithTaskOverride() {
         ActivityRecord r = initializeOverrideAnimationOptionsTest();
@@ -2415,7 +2395,6 @@
     }
 
     @Test
-    @EnableFlags(Flags.FLAG_ENABLE_DISPLAY_FOCUS_IN_SHELL_TRANSITIONS)
     public void testMoveDisplayToTop() {
         // Set up two displays, each of which has a task.
         DisplayContent otherDisplay = createNewDisplay();
@@ -3071,8 +3050,11 @@
             TestTransitionPlayer player, SurfaceControl.Transaction startTransaction) {
         final ArgumentCaptor<SurfaceControl.TransactionCommittedListener> listenerCaptor =
                 ArgumentCaptor.forClass(SurfaceControl.TransactionCommittedListener.class);
-        player.onTransactionReady(startTransaction);
-        verify(startTransaction).addTransactionCommittedListener(any(), listenerCaptor.capture());
+        player.onTransactionReady();
+        // The startTransaction is from mWm.mTransactionFactory.get() in SyncGroup#finishNow.
+        // 2 times are from SyncGroup#finishNow and AsyncRotationController#setupStartTransaction.
+        verify(startTransaction, times(2)).addTransactionCommittedListener(
+                any(), listenerCaptor.capture());
         return listenerCaptor.getValue();
     }
 }
diff --git a/services/tests/wmtests/src/com/android/server/wm/WindowContainerTests.java b/services/tests/wmtests/src/com/android/server/wm/WindowContainerTests.java
index cee98fb..4f310de 100644
--- a/services/tests/wmtests/src/com/android/server/wm/WindowContainerTests.java
+++ b/services/tests/wmtests/src/com/android/server/wm/WindowContainerTests.java
@@ -30,8 +30,6 @@
 import static android.view.WindowInsets.Type.systemOverlays;
 import static android.view.WindowManager.LayoutParams.TYPE_APPLICATION_OVERLAY;
 import static android.view.WindowManager.LayoutParams.TYPE_BASE_APPLICATION;
-import static android.view.WindowManager.TRANSIT_CLOSE;
-import static android.view.WindowManager.TRANSIT_OLD_TASK_CLOSE;
 import static android.window.DisplayAreaOrganizer.FEATURE_DEFAULT_TASK_CONTAINER;
 
 import static com.android.dx.mockito.inline.extended.ExtendedMockito.any;
@@ -1121,7 +1119,6 @@
         final ActivityRecord activity = createActivityRecord(mDisplayContent, task);
         final WindowState win = newWindowBuilder("win", TYPE_BASE_APPLICATION).setWindowToken(
                 activity).build();
-        task.getDisplayContent().prepareAppTransition(TRANSIT_CLOSE);
         spyOn(win);
         doReturn(true).when(task).okToAnimate();
         ArrayList<WindowContainer> sources = new ArrayList<>();
@@ -1130,8 +1127,6 @@
         // Simulate the task applying the exit transition, verify the main window of the task
         // will be set the frozen insets state before the animation starts
         activity.setVisibility(false);
-        task.applyAnimation(null, TRANSIT_OLD_TASK_CLOSE, false /* enter */,
-                false /* isVoiceInteraction */, sources);
         verify(win).freezeInsetsState();
 
         // Simulate the task transition finished.
diff --git a/services/tests/wmtests/src/com/android/server/wm/WindowTestsBase.java b/services/tests/wmtests/src/com/android/server/wm/WindowTestsBase.java
index 97c6ac6..c641685 100644
--- a/services/tests/wmtests/src/com/android/server/wm/WindowTestsBase.java
+++ b/services/tests/wmtests/src/com/android/server/wm/WindowTestsBase.java
@@ -2159,13 +2159,14 @@
             mOrganizer.startTransition(mLastTransit.getToken(), null);
         }
 
-        void onTransactionReady(SurfaceControl.Transaction t) {
-            mLastTransit.onTransactionReady(mLastTransit.getSyncId(), t);
+        void onTransactionReady() {
+            // SyncGroup#finishNow -> Transition#onTransactionReady.
+            mController.mSyncEngine.abort(mLastTransit.getSyncId());
         }
 
         void start() {
             startTransition();
-            onTransactionReady(mock(SurfaceControl.Transaction.class));
+            onTransactionReady();
         }
 
         public void finish() {
diff --git a/services/voiceinteraction/java/com/android/server/soundtrigger/PhoneCallStateHandler.java b/services/voiceinteraction/java/com/android/server/soundtrigger/PhoneCallStateHandler.java
index 49ad461..df43ed9 100644
--- a/services/voiceinteraction/java/com/android/server/soundtrigger/PhoneCallStateHandler.java
+++ b/services/voiceinteraction/java/com/android/server/soundtrigger/PhoneCallStateHandler.java
@@ -24,7 +24,6 @@
 import android.util.Slog;
 
 import com.android.internal.annotations.GuardedBy;
-import com.android.internal.telephony.flags.Flags;
 
 import java.util.ArrayList;
 import java.util.List;
@@ -119,28 +118,18 @@
     private boolean checkCallStatus() {
         List<SubscriptionInfo> infoList = mSubscriptionManager.getActiveSubscriptionInfoList();
         if (infoList == null) return false;
-        if (!Flags.enforceTelephonyFeatureMapping()) {
-            return infoList.stream()
-                    .filter(s -> (s.getSubscriptionId()
-                            != SubscriptionManager.INVALID_SUBSCRIPTION_ID))
-                    .anyMatch(s -> isCallOngoingFromState(
-                            mTelephonyManager
-                                    .createForSubscriptionId(s.getSubscriptionId())
-                                    .getCallStateForSubscription()));
-        } else {
-            return infoList.stream()
-                    .filter(s -> (s.getSubscriptionId()
-                            != SubscriptionManager.INVALID_SUBSCRIPTION_ID))
-                    .anyMatch(s -> {
-                        try {
-                            return isCallOngoingFromState(mTelephonyManager
-                                    .createForSubscriptionId(s.getSubscriptionId())
-                                    .getCallStateForSubscription());
-                        } catch (UnsupportedOperationException e) {
-                            return false;
-                        }
-                    });
-        }
+        return infoList.stream()
+                .filter(s -> (s.getSubscriptionId()
+                        != SubscriptionManager.INVALID_SUBSCRIPTION_ID))
+                .anyMatch(s -> {
+                    try {
+                        return isCallOngoingFromState(mTelephonyManager
+                                .createForSubscriptionId(s.getSubscriptionId())
+                                .getCallStateForSubscription());
+                    } catch (UnsupportedOperationException e) {
+                        return false;
+                    }
+                });
     }
 
     private void updateTelephonyListeners() {
diff --git a/tests/FlickerTests/FlickerService/src/com/android/server/wm/flicker/service/transitions/flicker/LaunchTaskPortrait.kt b/tests/FlickerTests/FlickerService/src/com/android/server/wm/flicker/service/transitions/flicker/LaunchTaskPortrait.kt
new file mode 100644
index 0000000..c82ce8a
--- /dev/null
+++ b/tests/FlickerTests/FlickerService/src/com/android/server/wm/flicker/service/transitions/flicker/LaunchTaskPortrait.kt
@@ -0,0 +1,45 @@
+/*
+ * Copyright (C) 2025 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.server.wm.flicker.service.transitions.flicker
+
+import android.tools.Rotation
+import android.tools.flicker.FlickerConfig
+import android.tools.flicker.annotation.ExpectedScenarios
+import android.tools.flicker.annotation.FlickerConfigProvider
+import android.tools.flicker.config.FlickerConfig
+import android.tools.flicker.config.FlickerServiceConfig
+import android.tools.flicker.junit.FlickerServiceJUnit4ClassRunner
+import com.android.server.wm.flicker.service.transitions.scenarios.LaunchTask
+import org.junit.Test
+import org.junit.runner.RunWith
+
+@RunWith(FlickerServiceJUnit4ClassRunner::class)
+class LaunchTaskPortrait : LaunchTask(Rotation.ROTATION_0) {
+    @ExpectedScenarios(["TASK_TRANSITION_SCENARIO", "OPEN_NEW_TASK_APP_SCENARIO"])
+    @Test
+    override fun openNewTask() = super.openNewTask()
+
+    companion object {
+        @JvmStatic
+        @FlickerConfigProvider
+        fun flickerConfigProvider(): FlickerConfig =
+            FlickerConfig()
+                .use(FlickerServiceConfig.DEFAULT)
+                .use(TASK_TRANSITION_SCENARIO)
+                .use(OPEN_NEW_TASK_APP_SCENARIO)
+    }
+}
\ No newline at end of file
diff --git a/tests/FlickerTests/FlickerService/src/com/android/server/wm/flicker/service/transitions/scenarios/LaunchTask.kt b/tests/FlickerTests/FlickerService/src/com/android/server/wm/flicker/service/transitions/scenarios/LaunchTask.kt
new file mode 100644
index 0000000..147477d
--- /dev/null
+++ b/tests/FlickerTests/FlickerService/src/com/android/server/wm/flicker/service/transitions/scenarios/LaunchTask.kt
@@ -0,0 +1,130 @@
+/*
+ * Copyright (C) 2025 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.server.wm.flicker.service.transitions.scenarios
+
+import android.app.Instrumentation
+import android.tools.Rotation
+import android.tools.flicker.AssertionInvocationGroup
+import android.tools.flicker.assertors.assertions.AppWindowCoversFullScreenAtStart
+import android.tools.flicker.assertors.assertions.AppWindowOnTopAtEnd
+import android.tools.flicker.assertors.assertions.AppWindowOnTopAtStart
+import android.tools.flicker.assertors.assertions.BackgroundShowsInTransition
+import android.tools.flicker.assertors.assertions.LayerBecomesInvisible
+import android.tools.flicker.assertors.assertions.LayerBecomesVisible
+import android.tools.flicker.assertors.assertions.LayerIsNeverVisible
+import android.tools.flicker.assertors.assertions.AppWindowIsNeverVisible
+import android.tools.flicker.config.AssertionTemplates
+import android.tools.flicker.config.FlickerConfigEntry
+import android.tools.flicker.config.ScenarioId
+import android.tools.flicker.config.appclose.Components.CLOSING_APPS
+import android.tools.flicker.config.appclose.Components.CLOSING_CHANGES
+import android.tools.flicker.config.applaunch.Components.OPENING_CHANGES
+import android.tools.flicker.config.common.Components.LAUNCHER
+import android.tools.flicker.config.common.Components.WALLPAPER
+import android.tools.flicker.extractors.TaggedCujTransitionMatcher
+import android.tools.flicker.extractors.TaggedScenarioExtractorBuilder
+import android.tools.flicker.rules.ChangeDisplayOrientationRule
+import android.tools.traces.events.CujType
+import android.tools.traces.parsers.WindowManagerStateHelper
+import androidx.test.platform.app.InstrumentationRegistry
+import androidx.test.uiautomator.UiDevice
+import com.android.launcher3.tapl.LauncherInstrumentation
+import com.android.server.wm.flicker.helpers.NewTasksAppHelper
+import org.junit.After
+import org.junit.Before
+import org.junit.Test
+
+
+/**
+ * This tests performs a transition between tasks
+ */
+abstract class LaunchTask(val rotation: Rotation = Rotation.ROTATION_0) {
+    protected val instrumentation: Instrumentation = InstrumentationRegistry.getInstrumentation()
+    private val wmHelper = WindowManagerStateHelper(instrumentation)
+    private val device = UiDevice.getInstance(instrumentation)
+    private val tapl = LauncherInstrumentation()
+
+    private val launchNewTaskApp = NewTasksAppHelper(instrumentation)
+
+    @Before
+    fun setup() {
+        tapl.setEnableRotation(true)
+        ChangeDisplayOrientationRule.setRotation(rotation)
+        tapl.setExpectedRotation(rotation.value)
+        launchNewTaskApp.launchViaIntent(wmHelper)
+    }
+
+    @Test
+    open fun openNewTask() {
+        launchNewTaskApp.openNewTask(device, wmHelper)
+    }
+
+    @After
+    fun tearDown() {
+        launchNewTaskApp.exit(wmHelper)
+    }
+
+    companion object {
+        /**
+         * General task transition scenario that can be reused for any trace
+         */
+        val TASK_TRANSITION_SCENARIO =
+            FlickerConfigEntry(
+                scenarioId = ScenarioId("TASK_TRANSITION_SCENARIO"),
+                extractor = TaggedScenarioExtractorBuilder()
+                    .setTargetTag(CujType.CUJ_DEFAULT_TASK_TO_TASK_ANIMATION)
+                    .setTransitionMatcher(
+                        TaggedCujTransitionMatcher(associatedTransitionRequired = true)
+                    )
+                    .build(),
+                assertions = listOf(
+                    // Opening changes replace the closing ones
+                    LayerBecomesInvisible(CLOSING_CHANGES),
+                    AppWindowOnTopAtStart(CLOSING_CHANGES),
+                    LayerBecomesVisible(OPENING_CHANGES),
+                    AppWindowOnTopAtEnd(OPENING_CHANGES),
+
+                    // There is a background color and it's covering the transition area
+                    BackgroundShowsInTransition(CLOSING_CHANGES)
+                ).associateBy({ it }, { AssertionInvocationGroup.BLOCKING })
+            )
+
+        /**
+         * Scenario that is making assertions that are valid for the new task app but that do not
+         * apply to other task transitions in general
+         */
+        val OPEN_NEW_TASK_APP_SCENARIO =
+            FlickerConfigEntry(
+                scenarioId = ScenarioId("OPEN_NEW_TASK_APP_SCENARIO"),
+                extractor = TaggedScenarioExtractorBuilder()
+                    .setTargetTag(CujType.CUJ_DEFAULT_TASK_TO_TASK_ANIMATION)
+                    .setTransitionMatcher(
+                        TaggedCujTransitionMatcher(associatedTransitionRequired = true)
+                    )
+                    .build(),
+                assertions = AssertionTemplates.COMMON_ASSERTIONS +
+                        listOf(
+                            // Wallpaper and launcher never visible
+                            LayerIsNeverVisible(WALLPAPER, mustExist = true),
+                            LayerIsNeverVisible(LAUNCHER, mustExist = true),
+                            AppWindowIsNeverVisible(LAUNCHER, mustExist = true),
+                            // App window covers the display at start
+                            AppWindowCoversFullScreenAtStart(CLOSING_APPS)
+                        ).associateBy({ it }, { AssertionInvocationGroup.BLOCKING })
+            )
+    }
+}
\ No newline at end of file
diff --git a/tests/Input/Android.bp b/tests/Input/Android.bp
index 1f0bd61..168141b 100644
--- a/tests/Input/Android.bp
+++ b/tests/Input/Android.bp
@@ -19,6 +19,9 @@
         "src/**/*.kt",
     ],
     asset_dirs: ["assets"],
+    kotlincflags: [
+        "-Werror",
+    ],
     platform_apis: true,
     certificate: "platform",
     static_libs: [
diff --git a/tests/PackageWatchdog/Android.bp b/tests/PackageWatchdog/Android.bp
index 44e545b..16c6e3b 100644
--- a/tests/PackageWatchdog/Android.bp
+++ b/tests/PackageWatchdog/Android.bp
@@ -28,6 +28,7 @@
     static_libs: [
         "PlatformProperties",
         "androidx.test.rules",
+        "androidx.test.runner",
         "flag-junit",
         "frameworks-base-testutils",
         "junit",
diff --git a/tests/PackageWatchdog/AndroidManifest.xml b/tests/PackageWatchdog/AndroidManifest.xml
index 540edb4..334d50f 100644
--- a/tests/PackageWatchdog/AndroidManifest.xml
+++ b/tests/PackageWatchdog/AndroidManifest.xml
@@ -15,7 +15,7 @@
 -->
 
 <manifest xmlns:android="http://schemas.android.com/apk/res/android"
-    package="com.android.tests.packagewatchdog" >
+    package="com.android.server" >
 
     <application android:debuggable="true">
         <uses-library android:name="android.test.runner" />
@@ -23,6 +23,6 @@
 
 
     <instrumentation android:name="androidx.test.runner.AndroidJUnitRunner"
-                     android:targetPackage="com.android.tests.packagewatchdog"
-                     android:label="PackageWatchdog Test"/>
+        android:targetPackage="com.android.server"
+        android:label="PackageWatchdog Test"/>
 </manifest>
diff --git a/tests/PackageWatchdog/AndroidTest.xml b/tests/PackageWatchdog/AndroidTest.xml
new file mode 100644
index 0000000..45a88cd
--- /dev/null
+++ b/tests/PackageWatchdog/AndroidTest.xml
@@ -0,0 +1,36 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!--
+  ~ Copyright (C) 2024 The Android Open Source Project
+  ~
+  ~ Licensed under the Apache License, Version 2.0 (the "License");
+  ~ you may not use this file except in compliance with the License.
+  ~ You may obtain a copy of the License at
+  ~
+  ~      http://www.apache.org/licenses/LICENSE-2.0
+  ~
+  ~ Unless required by applicable law or agreed to in writing, software
+  ~ distributed under the License is distributed on an "AS IS" BASIS,
+  ~ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+  ~ See the License for the specific language governing permissions and
+  ~ limitations under the License.
+  -->
+<configuration description="Runs PackageWatchdog Tests.">
+    <target_preparer class="com.android.tradefed.targetprep.suite.SuiteApkInstaller">
+        <option name="cleanup-apks" value="true" />
+        <option name="test-file-name" value="PackageWatchdogTest.apk" />
+    </target_preparer>
+
+    <option name="test-suite-tag" value="apct" />
+    <option name="test-tag" value="PackageWatchdogTest" />
+
+    <!-- Only run this tests in MTS if the Crashrecovery Mainline module is installed. -->
+    <object type="module_controller" class="com.android.tradefed.testtype.suite.module.MainlineTestModuleController">
+        <option name="mainline-module-package-name" value="com.google.android.crashrecovery" />
+    </object>
+
+    <test class="com.android.tradefed.testtype.AndroidJUnitTest" >
+        <option name="package" value="com.android.server" />
+        <option name="runner" value="androidx.test.runner.AndroidJUnitRunner" />
+        <option name="hidden-api-checks" value="false"/>
+    </test>
+</configuration>
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/link/FlaggedResources_test.cpp b/tools/aapt2/link/FlaggedResources_test.cpp
index adf711e..7bea96c 100644
--- a/tools/aapt2/link/FlaggedResources_test.cpp
+++ b/tools/aapt2/link/FlaggedResources_test.cpp
@@ -169,4 +169,18 @@
   ASSERT_TRUE(output.contains("test.package.readWriteFlag"));
 }
 
+TEST_F(FlaggedResourcesTest, ReadWriteFlagInPathFails) {
+  test::TestDiagnosticsImpl diag;
+  const std::string compiled_files_dir = GetTestPath("compiled");
+  ASSERT_FALSE(CompileFile(GetTestPath("res/values/flag(!test.package.rwFlag)/bools.xml"),
+                           R"(<resources>
+                                <bool name="bool1">false</bool>
+                              </resources>)",
+                           compiled_files_dir, &diag,
+                           {"--feature-flags", "test.package.rwFlag=false"}));
+
+  ASSERT_TRUE(diag.GetLog().contains(
+      "Only read only flags may be used with resources: test.package.rwFlag"));
+}
+
 }  // namespace aapt