Merge "Create a utility function in WM Shell to set the text font and style of the TextView." into main
diff --git a/cmds/bmgr/src/com/android/commands/bmgr/Bmgr.java b/cmds/bmgr/src/com/android/commands/bmgr/Bmgr.java
index 696bc82..ae150ae 100644
--- a/cmds/bmgr/src/com/android/commands/bmgr/Bmgr.java
+++ b/cmds/bmgr/src/com/android/commands/bmgr/Bmgr.java
@@ -1309,6 +1309,8 @@
                 return "AGENT_FAILURE_DURING_RESTORE";
             case BackupManagerMonitor.LOG_EVENT_ID_FAILED_TO_READ_DATA_FROM_TRANSPORT:
                 return "FAILED_TO_READ_DATA_FROM_TRANSPORT";
+            case BackupManagerMonitor.LOG_EVENT_ID_FULL_BACKUP_AGENT_PIPE_BROKEN:
+                return "LOG_EVENT_ID_FULL_BACKUP_AGENT_PIPE_BROKEN";
             default:
                 return "UNKNOWN_ID";
         }
diff --git a/core/java/android/app/ActivityManager.java b/core/java/android/app/ActivityManager.java
index b38f5da..54ab3b8 100644
--- a/core/java/android/app/ActivityManager.java
+++ b/core/java/android/app/ActivityManager.java
@@ -55,9 +55,7 @@
 import android.graphics.Color;
 import android.graphics.Matrix;
 import android.graphics.Point;
-import android.graphics.Rect;
 import android.graphics.drawable.Icon;
-import android.hardware.HardwareBuffer;
 import android.os.BatteryStats;
 import android.os.Binder;
 import android.os.Build;
@@ -86,7 +84,6 @@
 import android.util.Singleton;
 import android.util.Size;
 import android.view.WindowInsetsController.Appearance;
-import android.window.TaskSnapshot;
 
 import com.android.internal.annotations.GuardedBy;
 import com.android.internal.app.LocalePicker;
@@ -5420,10 +5417,11 @@
      *
      * @hide
      */
+    @Nullable
     @RequiresPermission(Manifest.permission.MANAGE_USERS)
-    public @Nullable String getSwitchingFromUserMessage() {
+    public String getSwitchingFromUserMessage(@UserIdInt int userId) {
         try {
-            return getService().getSwitchingFromUserMessage();
+            return getService().getSwitchingFromUserMessage(userId);
         } catch (RemoteException re) {
             throw re.rethrowFromSystemServer();
         }
@@ -5434,10 +5432,11 @@
      *
      * @hide
      */
+    @Nullable
     @RequiresPermission(Manifest.permission.MANAGE_USERS)
-    public @Nullable String getSwitchingToUserMessage() {
+    public String getSwitchingToUserMessage(@UserIdInt int userId) {
         try {
-            return getService().getSwitchingToUserMessage();
+            return getService().getSwitchingToUserMessage(userId);
         } catch (RemoteException re) {
             throw re.rethrowFromSystemServer();
         }
diff --git a/core/java/android/app/ActivityManagerInternal.java b/core/java/android/app/ActivityManagerInternal.java
index a12c067..e5f7889 100644
--- a/core/java/android/app/ActivityManagerInternal.java
+++ b/core/java/android/app/ActivityManagerInternal.java
@@ -292,14 +292,14 @@
     public abstract boolean canStartMoreUsers();
 
     /**
-     * Sets the user switcher message for switching from {@link android.os.UserHandle#SYSTEM}.
+     * Sets the user switcher message for switching from a user.
      */
-    public abstract void setSwitchingFromSystemUserMessage(String switchingFromSystemUserMessage);
+    public abstract void setSwitchingFromUserMessage(@UserIdInt int user, @Nullable String message);
 
     /**
-     * Sets the user switcher message for switching to {@link android.os.UserHandle#SYSTEM}.
+     * Sets the user switcher message for switching to a user.
      */
-    public abstract void setSwitchingToSystemUserMessage(String switchingToSystemUserMessage);
+    public abstract void setSwitchingToUserMessage(@UserIdInt int user, @Nullable String message);
 
     /**
      * Returns maximum number of users that can run simultaneously.
diff --git a/core/java/android/app/AppCompatTaskInfo.java b/core/java/android/app/AppCompatTaskInfo.java
index 599f1a8..ea4646a 100644
--- a/core/java/android/app/AppCompatTaskInfo.java
+++ b/core/java/android/app/AppCompatTaskInfo.java
@@ -102,6 +102,8 @@
     private static final int FLAG_FULLSCREEN_OVERRIDE_USER = FLAG_BASE << 8;
     /** Top activity flag for whether min aspect ratio of the activity has been overridden.*/
     public static final int FLAG_HAS_MIN_ASPECT_RATIO_OVERRIDE = FLAG_BASE << 9;
+    /** Top activity flag for whether restart menu is shown due to display move. */
+    private static final int FLAG_ENABLE_RESTART_MENU_FOR_DISPLAY_MOVE = FLAG_BASE << 10;
 
     @Retention(RetentionPolicy.SOURCE)
     @IntDef(flag = true, value = {
@@ -115,7 +117,8 @@
             FLAG_ELIGIBLE_FOR_USER_ASPECT_RATIO_BUTTON,
             FLAG_FULLSCREEN_OVERRIDE_SYSTEM,
             FLAG_FULLSCREEN_OVERRIDE_USER,
-            FLAG_HAS_MIN_ASPECT_RATIO_OVERRIDE
+            FLAG_HAS_MIN_ASPECT_RATIO_OVERRIDE,
+            FLAG_ENABLE_RESTART_MENU_FOR_DISPLAY_MOVE
     })
     public @interface TopActivityFlag {}
 
@@ -133,7 +136,8 @@
 
     @TopActivityFlag
     private static final int FLAGS_COMPAT_UI_INTERESTED = FLAGS_ORGANIZER_INTERESTED
-            | FLAG_IN_SIZE_COMPAT | FLAG_ELIGIBLE_FOR_LETTERBOX_EDU | FLAG_LETTERBOX_EDU_ENABLED;
+            | FLAG_IN_SIZE_COMPAT | FLAG_ELIGIBLE_FOR_LETTERBOX_EDU | FLAG_LETTERBOX_EDU_ENABLED
+            | FLAG_ENABLE_RESTART_MENU_FOR_DISPLAY_MOVE;
 
     private AppCompatTaskInfo() {
         // Do nothing
@@ -300,6 +304,21 @@
     }
 
     /**
+     * @return {@code true} if the restart menu is enabled for the top activity due to display move.
+     */
+    public boolean isRestartMenuEnabledForDisplayMove() {
+        return isTopActivityFlagEnabled(FLAG_ENABLE_RESTART_MENU_FOR_DISPLAY_MOVE);
+    }
+
+    /**
+     * Sets the top activity flag for whether the restart menu is enabled for the top activity due
+     * to display move.
+     */
+    public void setRestartMenuEnabledForDisplayMove(boolean enable) {
+        setTopActivityFlag(FLAG_ENABLE_RESTART_MENU_FOR_DISPLAY_MOVE, enable);
+    }
+
+    /**
      * @return {@code true} if the top activity bounds are letterboxed.
      */
     public boolean isTopActivityLetterboxed() {
diff --git a/core/java/android/app/IActivityManager.aidl b/core/java/android/app/IActivityManager.aidl
index ad01ad5..6cdfb97 100644
--- a/core/java/android/app/IActivityManager.aidl
+++ b/core/java/android/app/IActivityManager.aidl
@@ -403,8 +403,8 @@
     void setPackageScreenCompatMode(in String packageName, int mode);
     @UnsupportedAppUsage
     boolean switchUser(int userid);
-    String getSwitchingFromUserMessage();
-    String getSwitchingToUserMessage();
+    String getSwitchingFromUserMessage(int userId);
+    String getSwitchingToUserMessage(int userId);
     @UnsupportedAppUsage
     void setStopUserOnSwitch(int value);
     boolean removeTask(int taskId);
diff --git a/core/java/android/app/TaskInfo.java b/core/java/android/app/TaskInfo.java
index 6936ddc..5924793 100644
--- a/core/java/android/app/TaskInfo.java
+++ b/core/java/android/app/TaskInfo.java
@@ -655,8 +655,10 @@
                 + " effectiveUid=" + effectiveUid
                 + " displayId=" + displayId
                 + " isRunning=" + isRunning
-                + " baseIntent=" + baseIntent + " baseActivity=" + baseActivity
-                + " topActivity=" + topActivity + " origActivity=" + origActivity
+                + " baseIntent=" + baseIntent
+                + " baseActivity=" + baseActivity
+                + " topActivity=" + topActivity
+                + " origActivity=" + origActivity
                 + " realActivity=" + realActivity
                 + " numActivities=" + numActivities
                 + " lastActiveTime=" + lastActiveTime
diff --git a/core/java/android/app/backup/BackupManagerMonitor.java b/core/java/android/app/backup/BackupManagerMonitor.java
index e741bc2..19c24cd 100644
--- a/core/java/android/app/backup/BackupManagerMonitor.java
+++ b/core/java/android/app/backup/BackupManagerMonitor.java
@@ -297,6 +297,9 @@
    @hide */
   public static final int LOG_EVENT_ID_FAILED_TO_READ_DATA_FROM_TRANSPORT = 81;
 
+  /** The pipe between the BackupAgent and the framework was broken during full backup. @hide */
+  public static final int LOG_EVENT_ID_FULL_BACKUP_AGENT_PIPE_BROKEN = 82;
+
   /**
    * This method will be called each time something important happens on BackupManager.
    *
diff --git a/core/java/android/content/pm/RegisteredServicesCache.java b/core/java/android/content/pm/RegisteredServicesCache.java
index 74da62c..10d3051 100644
--- a/core/java/android/content/pm/RegisteredServicesCache.java
+++ b/core/java/android/content/pm/RegisteredServicesCache.java
@@ -166,7 +166,15 @@
     @UnsupportedAppUsage
     public RegisteredServicesCache(Context context, String interfaceName, String metaDataName,
             String attributeName, XmlSerializerAndParser<V> serializerAndParser) {
-        mContext = context;
+        this(new Injector<V>(context), interfaceName, metaDataName, attributeName,
+                serializerAndParser);
+    }
+
+    /** Provides the basic functionality for unit tests. */
+    @VisibleForTesting
+    public RegisteredServicesCache(Injector<V> injector, String interfaceName, String metaDataName,
+            String attributeName, XmlSerializerAndParser<V> serializerAndParser) {
+        mContext = injector.getContext();
         mInterfaceName = interfaceName;
         mMetaDataName = metaDataName;
         mAttributesName = attributeName;
@@ -184,7 +192,7 @@
         if (isCore) {
             intentFilter.setPriority(IntentFilter.SYSTEM_HIGH_PRIORITY);
         }
-        mBackgroundHandler = BackgroundThread.getHandler();
+        mBackgroundHandler = injector.getBackgroundHandler();
         mContext.registerReceiverAsUser(
                 mPackageReceiver, UserHandle.ALL, intentFilter, null, mBackgroundHandler);
 
@@ -918,4 +926,25 @@
             return null;
         }
     }
+
+    /**
+     * Point of injection for test dependencies.
+     * @param <V> The type of the value.
+     */
+    @VisibleForTesting
+    public static class Injector<V> {
+        private final Context mContext;
+
+        public Injector(Context context) {
+            mContext = context;
+        }
+
+        public Context getContext() {
+            return mContext;
+        }
+
+        public Handler getBackgroundHandler() {
+            return BackgroundThread.getHandler();
+        }
+    }
 }
diff --git a/core/java/android/os/vibrator/flags.aconfig b/core/java/android/os/vibrator/flags.aconfig
index 414f2749..176c0c8 100644
--- a/core/java/android/os/vibrator/flags.aconfig
+++ b/core/java/android/os/vibrator/flags.aconfig
@@ -165,3 +165,14 @@
       purpose: PURPOSE_BUGFIX
     }
 }
+
+flag {
+    namespace: "haptics"
+    name: "remove_hidl_support"
+    description: "Remove framework code to support HIDL vibrator HALs."
+    bug: "308452413"
+    is_fixed_read_only: true
+    metadata {
+      purpose: PURPOSE_FEATURE
+    }
+}
diff --git a/core/java/android/service/dreams/DreamService.java b/core/java/android/service/dreams/DreamService.java
index f026614..ce31e1e 100644
--- a/core/java/android/service/dreams/DreamService.java
+++ b/core/java/android/service/dreams/DreamService.java
@@ -455,7 +455,7 @@
 
                         // Simply wake up in the case the device is not locked.
                         if (!keyguardManager.isKeyguardLocked()) {
-                            wakeUp(false);
+                            wakeUp();
                             return true;
                         }
 
@@ -477,11 +477,11 @@
 
         if (!mInteractive) {
             if (mDebug) Slog.v(mTag, "Waking up on keyEvent");
-            wakeUp(false);
+            wakeUp();
             return true;
         } else if (event.getKeyCode() == KeyEvent.KEYCODE_BACK) {
             if (mDebug) Slog.v(mTag, "Waking up on back key");
-            wakeUp(false);
+            wakeUp();
             return true;
         }
         return mWindow.superDispatchKeyEvent(event);
@@ -492,7 +492,7 @@
     public boolean dispatchKeyShortcutEvent(KeyEvent event) {
         if (!mInteractive) {
             if (mDebug) Slog.v(mTag, "Waking up on keyShortcutEvent");
-            wakeUp(false);
+            wakeUp();
             return true;
         }
         return mWindow.superDispatchKeyShortcutEvent(event);
@@ -505,7 +505,7 @@
         // but finish()es on any other kind of activity
         if (!mInteractive && event.getActionMasked() == MotionEvent.ACTION_UP) {
             if (mDebug) Slog.v(mTag, "Waking up on touchEvent");
-            wakeUp(false);
+            wakeUp();
             return true;
         }
         return mWindow.superDispatchTouchEvent(event);
@@ -516,7 +516,7 @@
     public boolean dispatchTrackballEvent(MotionEvent event) {
         if (!mInteractive) {
             if (mDebug) Slog.v(mTag, "Waking up on trackballEvent");
-            wakeUp(false);
+            wakeUp();
             return true;
         }
         return mWindow.superDispatchTrackballEvent(event);
@@ -527,7 +527,7 @@
     public boolean dispatchGenericMotionEvent(MotionEvent event) {
         if (!mInteractive) {
             if (mDebug) Slog.v(mTag, "Waking up on genericMotionEvent");
-            wakeUp(false);
+            wakeUp();
             return true;
         }
         return mWindow.superDispatchGenericMotionEvent(event);
@@ -925,37 +925,32 @@
         }
     }
 
-    /**
-     * Updates doze state. Note that this must be called on the mHandler.
-     */
-    private void updateDoze() {
-        mHandler.post(() -> {
-            if (mDreamToken == null) {
-                Slog.w(mTag, "Updating doze without a dream token.");
-                return;
-            }
+    private synchronized void updateDoze() {
+        if (mDreamToken == null) {
+            Slog.w(mTag, "Updating doze without a dream token.");
+            return;
+        }
 
-            if (mDozing) {
-                try {
-                    Slog.v(mTag, "UpdateDoze mDozeScreenState=" + mDozeScreenState
-                            + " mDozeScreenBrightness=" + mDozeScreenBrightness
-                            + " mDozeScreenBrightnessFloat=" + mDozeScreenBrightnessFloat);
-                    if (startAndStopDozingInBackground()) {
-                        mDreamManager.startDozingOneway(
-                                mDreamToken, mDozeScreenState, mDozeScreenStateReason,
-                                mDozeScreenBrightnessFloat, mDozeScreenBrightness,
-                                mUseNormalBrightnessForDoze);
-                    } else {
-                        mDreamManager.startDozing(
-                                mDreamToken, mDozeScreenState, mDozeScreenStateReason,
-                                mDozeScreenBrightnessFloat, mDozeScreenBrightness,
-                                mUseNormalBrightnessForDoze);
-                    }
-                } catch (RemoteException ex) {
-                    // system server died
+        if (mDozing) {
+            try {
+                Slog.v(mTag, "UpdateDoze mDozeScreenState=" + mDozeScreenState
+                        + " mDozeScreenBrightness=" + mDozeScreenBrightness
+                        + " mDozeScreenBrightnessFloat=" + mDozeScreenBrightnessFloat);
+                if (startAndStopDozingInBackground()) {
+                    mDreamManager.startDozingOneway(
+                            mDreamToken, mDozeScreenState, mDozeScreenStateReason,
+                            mDozeScreenBrightnessFloat, mDozeScreenBrightness,
+                            mUseNormalBrightnessForDoze);
+                } else {
+                    mDreamManager.startDozing(
+                            mDreamToken, mDozeScreenState, mDozeScreenStateReason,
+                            mDozeScreenBrightnessFloat, mDozeScreenBrightness,
+                            mUseNormalBrightnessForDoze);
                 }
+            } catch (RemoteException ex) {
+                // system server died
             }
-        });
+        }
     }
 
     /**
@@ -971,16 +966,14 @@
      */
     @UnsupportedAppUsage(maxTargetSdk = Build.VERSION_CODES.P, trackingBug = 115609023)
     public void stopDozing() {
-        mHandler.post(() -> {
-            if (mDozing) {
-                mDozing = false;
-                try {
-                    mDreamManager.stopDozing(mDreamToken);
-                } catch (RemoteException ex) {
-                    // system server died
-                }
+        if (mDozing) {
+            mDozing = false;
+            try {
+                mDreamManager.stopDozing(mDreamToken);
+            } catch (RemoteException ex) {
+                // system server died
             }
-        });
+        }
     }
 
     /**
@@ -1208,7 +1201,7 @@
             @Override
             public void onExitRequested() {
                 // Simply finish dream when exit is requested.
-                mHandler.post(() -> finishInternal());
+                mHandler.post(() -> finish());
             }
 
             @Override
@@ -1306,13 +1299,9 @@
      * </p>
      */
     public final void finish() {
-        mHandler.post(this::finishInternal);
-    }
-
-    private void finishInternal() {
         // If there is an active overlay connection, signal that the dream is ending before
-        // continuing. Note that the overlay cannot rely on the unbound state, since another
-        // dream might have bound to it in the meantime.
+        // continuing. Note that the overlay cannot rely on the unbound state, since another dream
+        // might have bound to it in the meantime.
         if (mOverlayConnection != null) {
             mOverlayConnection.addConsumer(overlay -> {
                 try {
@@ -1368,7 +1357,7 @@
      * </p>
      */
     public final void wakeUp() {
-        mHandler.post(()-> wakeUp(false));
+        wakeUp(false);
     }
 
     /**
@@ -1570,7 +1559,7 @@
         if (mActivity != null && !mActivity.isFinishing()) {
             mActivity.finishAndRemoveTask();
         } else {
-            finishInternal();
+            finish();
         }
 
         mDreamToken = null;
@@ -1730,7 +1719,7 @@
                             // the window reference in order to fully release the DreamActivity.
                             mWindow = null;
                             mActivity = null;
-                            finishInternal();
+                            finish();
                         }
 
                         if (mOverlayConnection != null && mDreamStartOverlayConsumer != null) {
diff --git a/core/java/android/view/LayoutInflater.java b/core/java/android/view/LayoutInflater.java
index aad3bf2..901fc02 100644
--- a/core/java/android/view/LayoutInflater.java
+++ b/core/java/android/view/LayoutInflater.java
@@ -516,8 +516,9 @@
             mConstructorArgs[0] = inflaterContext;
             View result = root;
 
-            if (root != null && root.getViewRootImpl() != null) {
-                root.getViewRootImpl().notifyRendererOfExpensiveFrame();
+            ViewRootImpl viewRootImpl = root != null ? root.getViewRootImpl() : null;
+            if (viewRootImpl != null) {
+                viewRootImpl.notifyRendererOfExpensiveFrame();
             }
 
             try {
diff --git a/core/java/android/window/DesktopExperienceFlags.java b/core/java/android/window/DesktopExperienceFlags.java
index 0b52687..4f89c83 100644
--- a/core/java/android/window/DesktopExperienceFlags.java
+++ b/core/java/android/window/DesktopExperienceFlags.java
@@ -47,17 +47,17 @@
             com.android.server.display.feature.flags.Flags::baseDensityForExternalDisplays, true),
     CONNECTED_DISPLAYS_CURSOR(com.android.input.flags.Flags::connectedDisplaysCursor, true),
     DISPLAY_TOPOLOGY(com.android.server.display.feature.flags.Flags::displayTopology, true),
-    ENABLE_BUG_FIXES_FOR_SECONDARY_DISPLAY(Flags::enableBugFixesForSecondaryDisplay, false),
+    ENABLE_BUG_FIXES_FOR_SECONDARY_DISPLAY(Flags::enableBugFixesForSecondaryDisplay, true),
     ENABLE_CONNECTED_DISPLAYS_DND(Flags::enableConnectedDisplaysDnd, false),
     ENABLE_CONNECTED_DISPLAYS_PIP(Flags::enableConnectedDisplaysPip, false),
-    ENABLE_CONNECTED_DISPLAYS_WINDOW_DRAG(Flags::enableConnectedDisplaysWindowDrag, false),
+    ENABLE_CONNECTED_DISPLAYS_WINDOW_DRAG(Flags::enableConnectedDisplaysWindowDrag, true),
     ENABLE_DISPLAY_CONTENT_MODE_MANAGEMENT(
             com.android.server.display.feature.flags.Flags::enableDisplayContentModeManagement,
             true),
-    ENABLE_DISPLAY_FOCUS_IN_SHELL_TRANSITIONS(Flags::enableDisplayFocusInShellTransitions, false),
-    ENABLE_DISPLAY_WINDOWING_MODE_SWITCHING(Flags::enableDisplayWindowingModeSwitching, false),
+    ENABLE_DISPLAY_FOCUS_IN_SHELL_TRANSITIONS(Flags::enableDisplayFocusInShellTransitions, true),
+    ENABLE_DISPLAY_WINDOWING_MODE_SWITCHING(Flags::enableDisplayWindowingModeSwitching, true),
     ENABLE_DRAG_TO_MAXIMIZE(Flags::enableDragToMaximize, true),
-    ENABLE_MOVE_TO_NEXT_DISPLAY_SHORTCUT(Flags::enableMoveToNextDisplayShortcut, false),
+    ENABLE_MOVE_TO_NEXT_DISPLAY_SHORTCUT(Flags::enableMoveToNextDisplayShortcut, true),
     ENABLE_MULTIPLE_DESKTOPS_BACKEND(Flags::enableMultipleDesktopsBackend, false),
     ENABLE_MULTIPLE_DESKTOPS_FRONTEND(Flags::enableMultipleDesktopsFrontend, false),
     ENABLE_PERSISTING_DISPLAY_SIZE_FOR_CONNECTED_DISPLAYS(
@@ -67,9 +67,9 @@
     ENABLE_PER_DISPLAY_PACKAGE_CONTEXT_CACHE_IN_STATUSBAR_NOTIF(
             Flags::enablePerDisplayPackageContextCacheInStatusbarNotif, false),
     ENABLE_PROJECTED_DISPLAY_DESKTOP_MODE(Flags::enableProjectedDisplayDesktopMode, false),
-    ENABLE_TASKBAR_CONNECTED_DISPLAYS(Flags::enableTaskbarConnectedDisplays, false),
+    ENABLE_TASKBAR_CONNECTED_DISPLAYS(Flags::enableTaskbarConnectedDisplays, true),
     ENTER_DESKTOP_BY_DEFAULT_ON_FREEFORM_DISPLAYS(Flags::enterDesktopByDefaultOnFreeformDisplays,
-            false),
+            true),
     FORM_FACTOR_BASED_DESKTOP_FIRST_SWITCH(Flags::formFactorBasedDesktopFirstSwitch, false),
     REPARENT_WINDOW_TOKEN_API(Flags::reparentWindowTokenApi, true)
     // go/keep-sorted end
diff --git a/core/java/android/window/WindowContainerTransaction.java b/core/java/android/window/WindowContainerTransaction.java
index 1156503..ea345a5 100644
--- a/core/java/android/window/WindowContainerTransaction.java
+++ b/core/java/android/window/WindowContainerTransaction.java
@@ -688,12 +688,6 @@
     @NonNull
     public WindowContainerTransaction setAdjacentRoots(
             @NonNull WindowContainerToken root1, @NonNull WindowContainerToken root2) {
-        if (!Flags.allowMultipleAdjacentTaskFragments()) {
-            mHierarchyOps.add(HierarchyOp.createForAdjacentRoots(
-                    root1.asBinder(),
-                    root2.asBinder()));
-            return this;
-        }
         return setAdjacentRootSet(root1, root2);
     }
 
@@ -714,10 +708,6 @@
      */
     @NonNull
     public WindowContainerTransaction setAdjacentRootSet(@NonNull WindowContainerToken... roots) {
-        if (!Flags.allowMultipleAdjacentTaskFragments()) {
-            throw new IllegalArgumentException("allowMultipleAdjacentTaskFragments is not enabled."
-                    + " Use #setAdjacentRoots instead.");
-        }
         if (roots.length < 2) {
             throw new IllegalArgumentException("setAdjacentRootSet must have size >= 2");
         }
@@ -1973,13 +1963,6 @@
             return mContainers;
         }
 
-        /** @deprecated b/373709676 replace with {@link #getContainers()}. */
-        @Deprecated
-        @NonNull
-        public IBinder getAdjacentRoot() {
-            return mReparent;
-        }
-
         public boolean getToTop() {
             return mToTop;
         }
@@ -2127,17 +2110,12 @@
                     sb.append(mContainer).append(" to ").append(mToTop ? "top" : "bottom");
                     break;
                 case HIERARCHY_OP_TYPE_SET_ADJACENT_ROOTS:
-                    if (Flags.allowMultipleAdjacentTaskFragments()) {
-                        for (IBinder container : mContainers) {
-                            if (container == mContainers[0]) {
-                                sb.append("adjacentRoots=").append(container);
-                            } else {
-                                sb.append(", ").append(container);
-                            }
+                    for (IBinder container : mContainers) {
+                        if (container == mContainers[0]) {
+                            sb.append("adjacentRoots=").append(container);
+                        } else {
+                            sb.append(", ").append(container);
                         }
-                    } else {
-                        sb.append("container=").append(mContainer)
-                                .append(" adjacentRoot=").append(mReparent);
                     }
                     break;
                 case HIERARCHY_OP_TYPE_LAUNCH_TASK:
diff --git a/core/tests/coretests/src/android/content/pm/RegisteredServicesCacheTest.java b/core/tests/coretests/src/android/content/pm/RegisteredServicesCacheTest.java
index ee4761b..dccbf40 100644
--- a/core/tests/coretests/src/android/content/pm/RegisteredServicesCacheTest.java
+++ b/core/tests/coretests/src/android/content/pm/RegisteredServicesCacheTest.java
@@ -245,6 +245,12 @@
                     SERVICE_INTERFACE, SERVICE_META_DATA, ATTRIBUTES_NAME, new TestSerializer());
         }
 
+        TestServicesCache(Injector<TestServiceType> injector,
+                XmlSerializerAndParser<TestServiceType> serializerAndParser) {
+            super(injector, SERVICE_INTERFACE, SERVICE_META_DATA, ATTRIBUTES_NAME,
+                    serializerAndParser);
+        }
+
         @Override
         public TestServiceType parseServiceAttributes(Resources res, String packageName,
                 AttributeSet attrs) {
diff --git a/libs/WindowManager/Shell/shared/src/com/android/wm/shell/shared/ShellSharedConstants.java b/libs/WindowManager/Shell/shared/src/com/android/wm/shell/shared/ShellSharedConstants.java
index 01d2201..8bcbd2a 100644
--- a/libs/WindowManager/Shell/shared/src/com/android/wm/shell/shared/ShellSharedConstants.java
+++ b/libs/WindowManager/Shell/shared/src/com/android/wm/shell/shared/ShellSharedConstants.java
@@ -22,4 +22,11 @@
 public class ShellSharedConstants {
     public static final String KEY_EXTRA_SHELL_CAN_HAND_OFF_ANIMATION =
             "extra_shell_can_hand_off_animation";
+
+    /**
+     * Defines the max screen width or height in dp for a device to be considered a small tablet.
+     *
+     * @see android.view.WindowManager#LARGE_SCREEN_SMALLEST_SCREEN_WIDTH_DP
+     */
+    public static final int SMALL_TABLET_MAX_EDGE_DP = 960;
 }
diff --git a/libs/WindowManager/Shell/shared/src/com/android/wm/shell/shared/bubbles/DeviceConfig.kt b/libs/WindowManager/Shell/shared/src/com/android/wm/shell/shared/bubbles/DeviceConfig.kt
index 1b7c9c2..ad2671b 100644
--- a/libs/WindowManager/Shell/shared/src/com/android/wm/shell/shared/bubbles/DeviceConfig.kt
+++ b/libs/WindowManager/Shell/shared/src/com/android/wm/shell/shared/bubbles/DeviceConfig.kt
@@ -26,6 +26,8 @@
 import android.view.WindowManager
 import kotlin.math.max
 
+import com.android.wm.shell.shared.ShellSharedConstants.SMALL_TABLET_MAX_EDGE_DP
+
 /** Contains device configuration used for positioning bubbles on the screen. */
 data class DeviceConfig(
         val isLargeScreen: Boolean,
@@ -38,7 +40,6 @@
     companion object {
 
         private const val LARGE_SCREEN_MIN_EDGE_DP = 600
-        private const val SMALL_TABLET_MAX_EDGE_DP = 960
 
         @JvmStatic
         fun create(context: Context, windowManager: WindowManager): DeviceConfig {
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/bubbles/BubbleController.java b/libs/WindowManager/Shell/src/com/android/wm/shell/bubbles/BubbleController.java
index 50e2f4d..3e95a0b 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/bubbles/BubbleController.java
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/bubbles/BubbleController.java
@@ -1604,7 +1604,7 @@
             @Nullable BubbleTransitions.DragData dragData) {
         if (!BubbleAnythingFlagHelper.enableBubbleToFullscreen()) return;
         Bubble b = mBubbleData.getOrCreateBubble(taskInfo); // Removes from overflow
-        ProtoLog.v(WM_SHELL_BUBBLES, "expandStackAndSelectBubble - intent=%s", taskInfo.taskId);
+        ProtoLog.v(WM_SHELL_BUBBLES, "expandStackAndSelectBubble - taskId=%s", taskInfo.taskId);
         BubbleBarLocation location = null;
         if (dragData != null) {
             location =
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/compatui/CompatUIController.java b/libs/WindowManager/Shell/src/com/android/wm/shell/compatui/CompatUIController.java
index 9b11e4a..4413c87 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/compatui/CompatUIController.java
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/compatui/CompatUIController.java
@@ -18,6 +18,8 @@
 
 import static android.app.WindowConfiguration.WINDOWING_MODE_FULLSCREEN;
 
+import static com.android.wm.shell.compatui.impl.CompatUIRequestsKt.DISPLAY_COMPAT_SHOW_RESTART_DIALOG;
+
 import android.annotation.NonNull;
 import android.annotation.Nullable;
 import android.app.TaskInfo;
@@ -53,7 +55,9 @@
 import com.android.wm.shell.compatui.api.CompatUIEvent;
 import com.android.wm.shell.compatui.api.CompatUIHandler;
 import com.android.wm.shell.compatui.api.CompatUIInfo;
+import com.android.wm.shell.compatui.api.CompatUIRequest;
 import com.android.wm.shell.compatui.impl.CompatUIEvents.SizeCompatRestartButtonClicked;
+import com.android.wm.shell.compatui.impl.CompatUIRequests;
 import com.android.wm.shell.desktopmode.DesktopUserRepositories;
 import com.android.wm.shell.shared.desktopmode.DesktopModeStatus;
 import com.android.wm.shell.sysui.KeyguardChangeListener;
@@ -245,6 +249,21 @@
         mCallback = callback;
     }
 
+    @Override
+    public void sendCompatUIRequest(CompatUIRequest compatUIRequest) {
+        switch(compatUIRequest.getRequestId()) {
+            case DISPLAY_COMPAT_SHOW_RESTART_DIALOG:
+                handleDisplayCompatShowRestartDialog(compatUIRequest.asType());
+                break;
+            default:
+        }
+    }
+
+    private void handleDisplayCompatShowRestartDialog(
+            CompatUIRequests.DisplayCompatShowRestartDialog request) {
+        onRestartButtonClicked(new Pair<>(request.getTaskInfo(), request.getTaskListener()));
+    }
+
     /**
      * Called when the Task info changed. Creates and updates the compat UI if there is an
      * activity in size compat, or removes the UI if there is no size compat activity.
@@ -254,13 +273,17 @@
     public void onCompatInfoChanged(@NonNull CompatUIInfo compatUIInfo) {
         final TaskInfo taskInfo = compatUIInfo.getTaskInfo();
         final ShellTaskOrganizer.TaskListener taskListener = compatUIInfo.getListener();
-        if (taskInfo != null && !taskInfo.appCompatTaskInfo.isTopActivityInSizeCompat()) {
+        final boolean isInDisplayCompatMode =
+                taskInfo.appCompatTaskInfo.isRestartMenuEnabledForDisplayMove();
+        if (taskInfo != null && !taskInfo.appCompatTaskInfo.isTopActivityInSizeCompat()
+                && !isInDisplayCompatMode) {
             mSetOfTaskIdsShowingRestartDialog.remove(taskInfo.taskId);
         }
         mIsInDesktopMode = isInDesktopMode(taskInfo);
         // We close all the Compat UI educations in case TaskInfo has no configuration or
         // TaskListener or in desktop mode.
-        if (taskInfo.configuration == null || taskListener == null || mIsInDesktopMode) {
+        if (taskInfo.configuration == null || taskListener == null
+                || (mIsInDesktopMode && !isInDisplayCompatMode)) {
             // Null token means the current foreground activity is not in compatibility mode.
             removeLayouts(taskInfo.taskId);
             return;
@@ -552,8 +575,11 @@
             @Nullable ShellTaskOrganizer.TaskListener taskListener) {
         RestartDialogWindowManager layout =
                 mTaskIdToRestartDialogWindowManagerMap.get(taskInfo.taskId);
+        final boolean isInNonDisplayCompatDesktopMode = mIsInDesktopMode
+                && !taskInfo.appCompatTaskInfo.isRestartMenuEnabledForDisplayMove();
         if (layout != null) {
-            if (layout.needsToBeRecreated(taskInfo, taskListener) || mIsInDesktopMode) {
+            if (layout.needsToBeRecreated(taskInfo, taskListener)
+                    || isInNonDisplayCompatDesktopMode) {
                 mTaskIdToRestartDialogWindowManagerMap.remove(taskInfo.taskId);
                 layout.release();
             } else {
@@ -568,8 +594,9 @@
                 return;
             }
         }
-        if (mIsInDesktopMode) {
-            // Return if in desktop mode.
+        if (isInNonDisplayCompatDesktopMode) {
+            // No restart dialog can be shown in desktop mode unless the task is in display compat
+            // mode.
             return;
         }
         // Create a new UI layout.
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/compatui/api/CompatUIHandler.kt b/libs/WindowManager/Shell/src/com/android/wm/shell/compatui/api/CompatUIHandler.kt
index 817e554..f71f809 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/compatui/api/CompatUIHandler.kt
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/compatui/api/CompatUIHandler.kt
@@ -28,6 +28,11 @@
     fun onCompatInfoChanged(compatUIInfo: CompatUIInfo)
 
     /**
+     * Invoked when another component in Shell requests a CompatUI state change.
+     */
+    fun sendCompatUIRequest(compatUIRequest: CompatUIRequest)
+
+    /**
      * Optional reference to the object responsible to send {@link CompatUIEvent}
      */
     fun setCallback(compatUIEventSender: Consumer<CompatUIEvent>?)
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/compatui/api/CompatUIRequest.kt b/libs/WindowManager/Shell/src/com/android/wm/shell/compatui/api/CompatUIRequest.kt
new file mode 100644
index 0000000..069fd9b
--- /dev/null
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/compatui/api/CompatUIRequest.kt
@@ -0,0 +1,34 @@
+/*
+ * 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.compatui.api
+
+/**
+ * Abstraction for all the possible Compat UI Component requests.
+ */
+interface CompatUIRequest {
+    /**
+     * Unique request identifier
+     */
+    val requestId: Int
+
+    @Suppress("UNCHECKED_CAST")
+    fun <T : CompatUIRequest> asType(): T? = this as? T
+
+    fun <T : CompatUIRequest> asType(clazz: Class<T>): T? {
+        return if (clazz.isInstance(this)) clazz.cast(this) else null
+    }
+}
\ No newline at end of file
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/compatui/impl/CompatUIRequests.kt b/libs/WindowManager/Shell/src/com/android/wm/shell/compatui/impl/CompatUIRequests.kt
new file mode 100644
index 0000000..da4fc99
--- /dev/null
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/compatui/impl/CompatUIRequests.kt
@@ -0,0 +1,33 @@
+/*
+ * 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.compatui.impl
+
+import android.app.TaskInfo
+import com.android.wm.shell.ShellTaskOrganizer
+import com.android.wm.shell.compatui.api.CompatUIRequest
+
+internal const val DISPLAY_COMPAT_SHOW_RESTART_DIALOG = 0
+
+/**
+ * All the {@link CompatUIRequest} the Compat UI Framework can handle
+ */
+sealed class CompatUIRequests(override val requestId: Int) : CompatUIRequest {
+    /** Sent when the restart handle menu is clicked, and a restart dialog is requested. */
+    data class DisplayCompatShowRestartDialog(val taskInfo: TaskInfo,
+        val taskListener: ShellTaskOrganizer.TaskListener) :
+        CompatUIRequests(DISPLAY_COMPAT_SHOW_RESTART_DIALOG)
+}
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/compatui/impl/DefaultCompatUIHandler.kt b/libs/WindowManager/Shell/src/com/android/wm/shell/compatui/impl/DefaultCompatUIHandler.kt
index 02db85a..7dcb16c 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/compatui/impl/DefaultCompatUIHandler.kt
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/compatui/impl/DefaultCompatUIHandler.kt
@@ -23,6 +23,7 @@
 import com.android.wm.shell.compatui.api.CompatUIHandler
 import com.android.wm.shell.compatui.api.CompatUIInfo
 import com.android.wm.shell.compatui.api.CompatUIRepository
+import com.android.wm.shell.compatui.api.CompatUIRequest
 import com.android.wm.shell.compatui.api.CompatUIState
 import java.util.function.Consumer
 
@@ -102,4 +103,6 @@
     override fun setCallback(compatUIEventSender: Consumer<CompatUIEvent>?) {
         this.compatUIEventSender = compatUIEventSender
     }
+
+    override fun sendCompatUIRequest(compatUIRequest: CompatUIRequest) {}
 }
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 73be2d7..478fd6d 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
@@ -1247,7 +1247,7 @@
             @DynamicOverride DesktopUserRepositories desktopUserRepositories,
             @NonNull DesksOrganizer desksOrganizer
     ) {
-        if (DesktopModeStatus.canEnterDesktopMode(context)) {
+        if (DesktopModeStatus.canEnterDesktopModeOrShowAppHandle(context)) {
             return Optional.of(
                     new DesksTransitionObserver(desktopUserRepositories, desksOrganizer));
         }
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/desktopmode/DesktopMixedTransitionHandler.kt b/libs/WindowManager/Shell/src/com/android/wm/shell/desktopmode/DesktopMixedTransitionHandler.kt
index 1f7edb4..4646662 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/desktopmode/DesktopMixedTransitionHandler.kt
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/desktopmode/DesktopMixedTransitionHandler.kt
@@ -452,6 +452,11 @@
     private fun findTaskChange(info: TransitionInfo, taskId: Int): TransitionInfo.Change? =
         info.changes.firstOrNull { change -> change.taskInfo?.taskId == taskId }
 
+    private fun findLaunchChange(info: TransitionInfo): TransitionInfo.Change? =
+        info.changes.firstOrNull { change ->
+            change.mode == TRANSIT_OPEN && change.taskInfo != null && change.taskInfo!!.isFreeform
+        }
+
     private fun findDesktopTaskLaunchChange(
         info: TransitionInfo,
         launchTaskId: Int?,
@@ -459,14 +464,18 @@
         return if (launchTaskId != null) {
             // Launching a known task (probably from background or moving to front), so
             // specifically look for it.
-            findTaskChange(info, launchTaskId)
+            val launchChange = findTaskChange(info, launchTaskId)
+            if (
+                DesktopModeFlags.ENABLE_DESKTOP_OPENING_DEEPLINK_MINIMIZE_ANIMATION_BUGFIX.isTrue &&
+                    launchChange == null
+            ) {
+                findLaunchChange(info)
+            } else {
+                launchChange
+            }
         } else {
             // Launching a new task, so the first opening freeform task.
-            info.changes.firstOrNull { change ->
-                change.mode == TRANSIT_OPEN &&
-                    change.taskInfo != null &&
-                    change.taskInfo!!.isFreeform
-            }
+            findLaunchChange(info)
         }
     }
 
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/desktopmode/DesktopModeUtils.kt b/libs/WindowManager/Shell/src/com/android/wm/shell/desktopmode/DesktopModeUtils.kt
index c5ee313..fa98d03 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/desktopmode/DesktopModeUtils.kt
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/desktopmode/DesktopModeUtils.kt
@@ -22,6 +22,13 @@
 import android.app.ActivityManager.RunningTaskInfo
 import android.app.TaskInfo
 import android.content.Context
+import android.content.Intent.FLAG_ACTIVITY_CLEAR_TASK
+import android.content.Intent.FLAG_ACTIVITY_MULTIPLE_TASK
+import android.content.Intent.FLAG_ACTIVITY_NEW_TASK
+import android.content.pm.ActivityInfo.LAUNCH_MULTIPLE
+import android.content.pm.ActivityInfo.LAUNCH_SINGLE_INSTANCE
+import android.content.pm.ActivityInfo.LAUNCH_SINGLE_INSTANCE_PER_TASK
+import android.content.pm.ActivityInfo.LAUNCH_SINGLE_TASK
 import android.content.pm.ActivityInfo.SCREEN_ORIENTATION_UNSPECIFIED
 import android.content.pm.ActivityInfo.isFixedOrientationLandscape
 import android.content.pm.ActivityInfo.isFixedOrientationPortrait
@@ -30,7 +37,9 @@
 import android.graphics.Rect
 import android.os.SystemProperties
 import android.util.Size
+import android.window.DesktopModeFlags
 import com.android.wm.shell.R
+import com.android.wm.shell.ShellTaskOrganizer
 import com.android.wm.shell.common.DisplayController
 import com.android.wm.shell.common.DisplayLayout
 import kotlin.math.ceil
@@ -264,6 +273,58 @@
 @DimenRes fun getAppHeaderHeightId(): Int = R.dimen.desktop_mode_freeform_decor_caption_height
 
 /**
+ * Returns the task bounds a launching task should inherit from an existing running instance.
+ * Returns null if there are no bounds to inherit.
+ */
+fun getInheritedExistingTaskBounds(
+    taskRepository: DesktopRepository,
+    shellTaskOrganizer: ShellTaskOrganizer,
+    task: RunningTaskInfo,
+    deskId: Int,
+): Rect? {
+    if (!DesktopModeFlags.INHERIT_TASK_BOUNDS_FOR_TRAMPOLINE_TASK_LAUNCHES.isTrue) return null
+    val activeTask = taskRepository.getExpandedTasksIdsInDeskOrdered(deskId).firstOrNull()
+    if (activeTask == null) return null
+    val lastTask = shellTaskOrganizer.getRunningTaskInfo(activeTask)
+    val lastTaskTopActivity = lastTask?.topActivity
+    val currentTaskTopActivity = task.topActivity
+    val intentFlags = task.baseIntent.flags
+    val launchMode = task.topActivityInfo?.launchMode ?: LAUNCH_MULTIPLE
+    return when {
+        // No running task activity to inherit bounds from.
+        lastTaskTopActivity == null -> null
+        // No current top activity to set bounds for.
+        currentTaskTopActivity == null -> null
+        // Top task is not an instance of the launching activity, do not inherit its bounds.
+        lastTaskTopActivity.packageName != currentTaskTopActivity.packageName -> null
+        // Top task is an instance of launching activity. Activity will be launching in a new
+        // task with the existing task also being closed. Inherit existing task bounds to
+        // prevent new task jumping.
+        (isLaunchingNewTask(launchMode, intentFlags) && isClosingExitingInstance(intentFlags)) ->
+            lastTask.configuration.windowConfiguration.bounds
+        else -> null
+    }
+}
+
+/**
+ * Returns true if the launch mode or intent will result in a new task being created for the
+ * activity.
+ */
+private fun isLaunchingNewTask(launchMode: Int, intentFlags: Int) =
+    launchMode == LAUNCH_SINGLE_TASK ||
+        launchMode == LAUNCH_SINGLE_INSTANCE ||
+        launchMode == LAUNCH_SINGLE_INSTANCE_PER_TASK ||
+        (intentFlags and FLAG_ACTIVITY_NEW_TASK) != 0
+
+/**
+ * Returns true if the intent will result in an existing task instance being closed if a new one
+ * appears.
+ */
+private fun isClosingExitingInstance(intentFlags: Int) =
+    (intentFlags and FLAG_ACTIVITY_CLEAR_TASK) != 0 ||
+        (intentFlags and FLAG_ACTIVITY_MULTIPLE_TASK) == 0
+
+/**
  * Calculates the desired initial bounds for applications in desktop windowing. This is done as a
  * scale of the screen bounds.
  */
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 99606d0..dbc599b 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
@@ -32,6 +32,8 @@
 import android.app.WindowConfiguration.WindowingMode
 import android.content.Context
 import android.content.Intent
+import android.content.Intent.FLAG_ACTIVITY_MULTIPLE_TASK
+import android.content.Intent.FLAG_ACTIVITY_NEW_TASK
 import android.graphics.Point
 import android.graphics.PointF
 import android.graphics.Rect
@@ -238,6 +240,10 @@
                 removeVisualIndicator()
             }
 
+            override fun onTransitionInterrupted() {
+                removeVisualIndicator()
+            }
+
             private fun removeVisualIndicator() {
                 visualIndicator?.fadeOutIndicator { releaseVisualIndicator() }
             }
@@ -2280,11 +2286,19 @@
             wct.reorder(task.token, true)
             return wct
         }
+        val inheritedTaskBounds =
+            getInheritedExistingTaskBounds(taskRepository, shellTaskOrganizer, task, deskId)
+        if (!taskRepository.isActiveTask(task.taskId) && inheritedTaskBounds != null) {
+            // Inherit bounds from closing task instance to prevent application jumping different
+            // cascading positions.
+            wct.setBounds(task.token, inheritedTaskBounds)
+        }
         // TODO(b/365723620): Handle non running tasks that were launched after reboot.
         // If task is already visible, it must have been handled already and added to desktop mode.
-        // Cascade task only if it's not visible yet.
+        // Cascade task only if it's not visible yet and has no inherited bounds.
         if (
-            DesktopModeFlags.ENABLE_CASCADING_WINDOWS.isTrue() &&
+            inheritedTaskBounds == null &&
+                DesktopModeFlags.ENABLE_CASCADING_WINDOWS.isTrue() &&
                 !taskRepository.isVisibleTask(task.taskId)
         ) {
             val displayLayout = displayController.getDisplayLayout(task.displayId)
@@ -2520,9 +2534,17 @@
     ) {
         val targetDisplayId = taskRepository.getDisplayForDesk(deskId)
         val displayLayout = displayController.getDisplayLayout(targetDisplayId) ?: return
-        val initialBounds = getInitialBounds(displayLayout, task, targetDisplayId)
-        if (canChangeTaskPosition(task)) {
-            wct.setBounds(task.token, initialBounds)
+        val inheritedTaskBounds =
+            getInheritedExistingTaskBounds(taskRepository, shellTaskOrganizer, task, deskId)
+        if (inheritedTaskBounds != null) {
+            // Inherit bounds from closing task instance to prevent application jumping different
+            // cascading positions.
+            wct.setBounds(task.token, inheritedTaskBounds)
+        } else {
+            val initialBounds = getInitialBounds(displayLayout, task, targetDisplayId)
+            if (canChangeTaskPosition(task)) {
+                wct.setBounds(task.token, initialBounds)
+            }
         }
         if (DesktopExperienceFlags.ENABLE_MULTIPLE_DESKTOPS_BACKEND.isTrue) {
             desksOrganizer.moveTaskToDesk(wct = wct, deskId = deskId, task = task)
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/desktopmode/DragToDesktopTransitionHandler.kt b/libs/WindowManager/Shell/src/com/android/wm/shell/desktopmode/DragToDesktopTransitionHandler.kt
index 4f511a9..24b2e48 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/desktopmode/DragToDesktopTransitionHandler.kt
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/desktopmode/DragToDesktopTransitionHandler.kt
@@ -24,8 +24,10 @@
 import android.os.SystemProperties
 import android.os.UserHandle
 import android.view.SurfaceControl
+import android.view.SurfaceControl.Transaction
 import android.view.WindowManager.TRANSIT_CLOSE
 import android.window.DesktopModeFlags
+import android.window.DesktopModeFlags.ENABLE_DRAG_TO_DESKTOP_INCOMING_TRANSITIONS_BUGFIX
 import android.window.TransitionInfo
 import android.window.TransitionInfo.Change
 import android.window.TransitionRequestInfo
@@ -185,18 +187,29 @@
      */
     fun finishDragToDesktopTransition(wct: WindowContainerTransaction): IBinder? {
         if (!inProgress) {
+            logV("finishDragToDesktop: not in progress, returning")
             // Don't attempt to finish a drag to desktop transition since there is no transition in
             // progress which means that the drag to desktop transition was never successfully
             // started.
             return null
         }
-        if (requireTransitionState().startAborted) {
+        val state = requireTransitionState()
+        if (state.startAborted) {
+            logV("finishDragToDesktop: start was aborted, clearing state")
             // Don't attempt to complete the drag-to-desktop since the start transition didn't
             // succeed as expected. Just reset the state as if nothing happened.
             clearState()
             return null
         }
-        return transitions.startTransition(TRANSIT_DESKTOP_MODE_END_DRAG_TO_DESKTOP, wct, this)
+        if (state.startInterrupted) {
+            logV("finishDragToDesktop: start was interrupted, returning")
+            // We should only have interrupted the start transition after receiving a cancel/end
+            // request, let that existing request play out and just return here.
+            return null
+        }
+        state.endTransitionToken =
+            transitions.startTransition(TRANSIT_DESKTOP_MODE_END_DRAG_TO_DESKTOP, wct, this)
+        return state.endTransitionToken
     }
 
     /**
@@ -220,6 +233,11 @@
             clearState()
             return
         }
+        if (state.startInterrupted) {
+            // We should only have interrupted the start transition after receiving a cancel/end
+            // request, let that existing request play out and just return here.
+            return
+        }
         state.cancelState = cancelState
 
         if (state.draggedTaskChange != null && cancelState == CancelState.STANDARD_CANCEL) {
@@ -227,7 +245,7 @@
             // transient to start and merge. Animate the cancellation (scale back to original
             // bounds) first before actually starting the cancel transition so that the wallpaper
             // is visible behind the animating task.
-            startCancelAnimation()
+            state.activeCancelAnimation = startCancelAnimation()
         } else if (
             state.draggedTaskChange != null &&
                 (cancelState == CancelState.CANCEL_SPLIT_LEFT ||
@@ -255,7 +273,7 @@
         ) {
             if (bubbleController.isEmpty || state !is TransitionState.FromFullscreen) {
                 // TODO(b/388853233): add support for dragging split task to bubble
-                startCancelAnimation()
+                state.activeCancelAnimation = startCancelAnimation()
             } else {
                 // Animation is handled by BubbleController
                 val wct = WindowContainerTransaction()
@@ -357,6 +375,19 @@
     ): Boolean {
         val state = requireTransitionState()
 
+        if (
+            handleCancelOrExitAfterInterrupt(
+                transition,
+                info,
+                startTransaction,
+                finishTransaction,
+                finishCallback,
+                state,
+            )
+        ) {
+            return true
+        }
+
         val isStartDragToDesktop =
             info.type == TRANSIT_DESKTOP_MODE_START_DRAG_TO_DESKTOP &&
                 transition == state.startTransitionToken
@@ -539,6 +570,58 @@
         }
     }
 
+    private fun handleCancelOrExitAfterInterrupt(
+        transition: IBinder,
+        info: TransitionInfo,
+        startTransaction: Transaction,
+        finishTransaction: Transaction,
+        finishCallback: Transitions.TransitionFinishCallback,
+        state: TransitionState,
+    ): Boolean {
+        if (!ENABLE_DRAG_TO_DESKTOP_INCOMING_TRANSITIONS_BUGFIX.isTrue) {
+            return false
+        }
+        val isCancelDragToDesktop =
+            info.type == TRANSIT_DESKTOP_MODE_CANCEL_DRAG_TO_DESKTOP &&
+                transition == state.cancelTransitionToken
+        val isEndDragToDesktop =
+            info.type == TRANSIT_DESKTOP_MODE_END_DRAG_TO_DESKTOP &&
+                transition == state.endTransitionToken
+        // We should only receive cancel or end transitions through startAnimation() if the
+        // start transition was interrupted while a cancel- or end-transition had already
+        // been requested. Finish the cancel/end transition to avoid having to deal with more
+        // incoming transitions, and clear the state for the next start-drag transition.
+        if (!isCancelDragToDesktop && !isEndDragToDesktop) {
+            return false
+        }
+        if (!state.startInterrupted) {
+            logW(
+                "Not interrupted, but received startAnimation for cancel/end drag." +
+                    "isCancel=$isCancelDragToDesktop, isEnd=$isEndDragToDesktop"
+            )
+            return false
+        }
+        logV(
+            "startAnimation: interrupted -> " +
+                "isCancel=$isCancelDragToDesktop, isEnd=$isEndDragToDesktop"
+        )
+        if (isEndDragToDesktop) {
+            setupEndDragToDesktop(info, startTransaction, finishTransaction)
+            animateEndDragToDesktop(startTransaction = startTransaction, finishCallback)
+        } else { // isCancelDragToDesktop
+            // Similar to when we merge the cancel transition: ensure all tasks involved in the
+            // cancel transition are shown, and finish the transition immediately.
+            info.changes.forEach { change ->
+                startTransaction.show(change.leash)
+                finishTransaction.show(change.leash)
+            }
+        }
+        startTransaction.apply()
+        finishCallback.onTransitionFinished(/* wct= */ null)
+        clearState()
+        return true
+    }
+
     /**
      * Calculates start drag to desktop layers for transition [info]. The leash layer is calculated
      * based on its change position in the transition, e.g. `appLayer = appLayers - i`, where i is
@@ -590,6 +673,7 @@
                 ?: error("Start transition expected to be waiting for merge but wasn't")
         if (isEndTransition) {
             logV("mergeAnimation: end-transition, target=$mergeTarget")
+            state.mergedEndTransition = true
             setupEndDragToDesktop(
                 info,
                 startTransaction = startT,
@@ -617,6 +701,41 @@
             return
         }
         logW("unhandled merge transition: transitionInfo=$info")
+        // Handle unknown incoming transitions by finishing the start transition. For now, only do
+        // this if we've already requested a cancel- or end transition. If we've already merged the
+        // end-transition, or if the end-transition is running on its own, then just wait until that
+        // finishes instead. If we've merged the cancel-transition we've finished the
+        // start-transition and won't reach this code.
+        if (
+            mergeTarget == state.startTransitionToken &&
+                isCancelOrEndTransitionRequested(state) &&
+                !state.mergedEndTransition
+        ) {
+            interruptStartTransition(state)
+        }
+    }
+
+    private fun isCancelOrEndTransitionRequested(state: TransitionState): Boolean =
+        state.cancelTransitionToken != null || state.endTransitionToken != null
+
+    private fun interruptStartTransition(state: TransitionState) {
+        if (!ENABLE_DRAG_TO_DESKTOP_INCOMING_TRANSITIONS_BUGFIX.isTrue) {
+            return
+        }
+        logV("interruptStartTransition")
+        state.startTransitionFinishCb?.onTransitionFinished(/* wct= */ null)
+        state.dragAnimator.cancelAnimator()
+        state.activeCancelAnimation?.removeAllListeners()
+        state.activeCancelAnimation?.cancel()
+        state.activeCancelAnimation = null
+        // Keep the transition state so we can deal with Cancel/End properly in #startAnimation.
+        state.startInterrupted = true
+        dragToDesktopStateListener?.onTransitionInterrupted()
+        // Cancel CUJs here as they won't be accurate now that an incoming transition is playing.
+        interactionJankMonitor.cancel(CUJ_DESKTOP_MODE_ENTER_APP_HANDLE_DRAG_HOLD)
+        interactionJankMonitor.cancel(CUJ_DESKTOP_MODE_ENTER_APP_HANDLE_DRAG_RELEASE)
+        LatencyTracker.getInstance(context)
+            .onActionCancel(LatencyTracker.ACTION_DESKTOP_MODE_ENTER_APP_HANDLE_DRAG)
     }
 
     protected open fun setupEndDragToDesktop(
@@ -783,7 +902,7 @@
         } ?: false
     }
 
-    private fun startCancelAnimation() {
+    private fun startCancelAnimation(): Animator {
         val state = requireTransitionState()
         val dragToDesktopAnimator = state.dragAnimator
 
@@ -800,7 +919,7 @@
         val dx = targetX - x
         val dy = targetY - y
         val tx: SurfaceControl.Transaction = transactionSupplier.get()
-        ValueAnimator.ofFloat(DRAG_FREEFORM_SCALE, 1f)
+        return ValueAnimator.ofFloat(DRAG_FREEFORM_SCALE, 1f)
             .setDuration(DRAG_TO_DESKTOP_FINISH_ANIM_DURATION_MS)
             .apply {
                 addUpdateListener { animator ->
@@ -818,6 +937,7 @@
                 addListener(
                     object : AnimatorListenerAdapter() {
                         override fun onAnimationEnd(animation: Animator) {
+                            state.activeCancelAnimation = null
                             dragToDesktopStateListener?.onCancelToDesktopAnimationEnd()
                             // Start the cancel transition to restore order.
                             startCancelDragToDesktopTransition()
@@ -910,10 +1030,16 @@
         val dragLayer: Int,
     )
 
+    /** Listener for various events happening during the DragToDesktop transition. */
     interface DragToDesktopStateListener {
+        /** Indicates that the animation into Desktop has started. */
         fun onCommitToDesktopAnimationStart()
 
+        /** Called when the animation to cancel the desktop-drag has finished. */
         fun onCancelToDesktopAnimationEnd()
+
+        /** Indicates that the drag-to-desktop transition has been interrupted. */
+        fun onTransitionInterrupted()
     }
 
     sealed class TransitionState {
@@ -930,6 +1056,10 @@
         abstract var cancelState: CancelState
         abstract var startAborted: Boolean
         abstract val visualIndicator: DesktopModeVisualIndicator?
+        abstract var startInterrupted: Boolean
+        abstract var endTransitionToken: IBinder?
+        abstract var mergedEndTransition: Boolean
+        abstract var activeCancelAnimation: Animator?
 
         data class FromFullscreen(
             override val draggedTaskId: Int,
@@ -945,6 +1075,10 @@
             override var cancelState: CancelState = CancelState.NO_CANCEL,
             override var startAborted: Boolean = false,
             override val visualIndicator: DesktopModeVisualIndicator?,
+            override var startInterrupted: Boolean = false,
+            override var endTransitionToken: IBinder? = null,
+            override var mergedEndTransition: Boolean = false,
+            override var activeCancelAnimation: Animator? = null,
             var otherRootChanges: MutableList<Change> = mutableListOf(),
         ) : TransitionState()
 
@@ -962,6 +1096,10 @@
             override var cancelState: CancelState = CancelState.NO_CANCEL,
             override var startAborted: Boolean = false,
             override val visualIndicator: DesktopModeVisualIndicator?,
+            override var startInterrupted: Boolean = false,
+            override var endTransitionToken: IBinder? = null,
+            override var mergedEndTransition: Boolean = false,
+            override var activeCancelAnimation: Animator? = null,
             var splitRootChange: Change? = null,
             var otherSplitTask: Int,
         ) : TransitionState()
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/windowdecor/WindowDecoration.java b/libs/WindowManager/Shell/src/com/android/wm/shell/windowdecor/WindowDecoration.java
index 7baef2b..bde46a1 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/windowdecor/WindowDecoration.java
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/windowdecor/WindowDecoration.java
@@ -361,6 +361,9 @@
         outResult.mRootView = rootView;
         final boolean fontScaleChanged = mWindowDecorConfig != null
                 && mWindowDecorConfig.fontScale != mTaskInfo.configuration.fontScale;
+        final boolean localeListChanged = mWindowDecorConfig != null
+                && !mWindowDecorConfig.getLocales()
+                    .equals(mTaskInfo.getConfiguration().getLocales());
         final int oldDensityDpi = mWindowDecorConfig != null
                 ? mWindowDecorConfig.densityDpi : DENSITY_DPI_UNDEFINED;
         final int oldNightMode =  mWindowDecorConfig != null
@@ -376,7 +379,8 @@
                 || oldLayoutResId != mLayoutResId
                 || oldNightMode != newNightMode
                 || mDecorWindowContext == null
-                || fontScaleChanged) {
+                || fontScaleChanged
+                || localeListChanged) {
             releaseViews(wct);
 
             if (!obtainDisplayOrRegisterListener()) {
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/windowdecor/common/WindowDecorTaskResourceLoader.kt b/libs/WindowManager/Shell/src/com/android/wm/shell/windowdecor/common/WindowDecorTaskResourceLoader.kt
index 801048a..957898f 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/windowdecor/common/WindowDecorTaskResourceLoader.kt
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/windowdecor/common/WindowDecorTaskResourceLoader.kt
@@ -21,6 +21,7 @@
 import android.content.pm.ActivityInfo
 import android.content.pm.PackageManager
 import android.graphics.Bitmap
+import android.os.LocaleList
 import android.os.UserHandle
 import androidx.tracing.Trace
 import com.android.internal.annotations.VisibleForTesting
@@ -80,6 +81,13 @@
      */
     private val existingTasks = mutableSetOf<Int>()
 
+    /**
+     * A map of task -> localeList to keep track of the language of app name that's currently
+     * cached in |taskToResourceCache|.
+     */
+    @VisibleForTesting
+    val localeListOnCache = ConcurrentHashMap<Int, LocaleList>()
+
     init {
         shellInit.addInitCallback(this::onInit, this)
     }
@@ -99,11 +107,14 @@
     fun getName(taskInfo: RunningTaskInfo): CharSequence {
         checkWindowDecorExists(taskInfo)
         val cachedResources = taskToResourceCache[taskInfo.taskId]
-        if (cachedResources != null) {
+        val localeListActiveOnCacheTime = localeListOnCache[taskInfo.taskId]
+        if (cachedResources != null &&
+            taskInfo.getConfiguration().getLocales().equals(localeListActiveOnCacheTime)) {
             return cachedResources.appName
         }
         val resources = loadAppResources(taskInfo)
         taskToResourceCache[taskInfo.taskId] = resources
+        localeListOnCache[taskInfo.taskId] = taskInfo.getConfiguration().getLocales()
         return resources.appName
     }
 
@@ -117,6 +128,7 @@
         }
         val resources = loadAppResources(taskInfo)
         taskToResourceCache[taskInfo.taskId] = resources
+        localeListOnCache[taskInfo.taskId] = taskInfo.getConfiguration().getLocales()
         return resources.appIcon
     }
 
@@ -130,6 +142,7 @@
         }
         val resources = loadAppResources(taskInfo)
         taskToResourceCache[taskInfo.taskId] = resources
+        localeListOnCache[taskInfo.taskId] = taskInfo.getConfiguration().getLocales()
         return resources.veilIcon
     }
 
@@ -142,6 +155,7 @@
     fun onWindowDecorClosed(taskInfo: RunningTaskInfo) {
         existingTasks.remove(taskInfo.taskId)
         taskToResourceCache.remove(taskInfo.taskId)
+        localeListOnCache.remove(taskInfo.taskId)
     }
 
     private fun checkWindowDecorExists(taskInfo: RunningTaskInfo) {
diff --git a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/compatui/CompatUIControllerTest.java b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/compatui/CompatUIControllerTest.java
index 598a101..597e4a5 100644
--- a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/compatui/CompatUIControllerTest.java
+++ b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/compatui/CompatUIControllerTest.java
@@ -59,6 +59,7 @@
 import com.android.wm.shell.common.ShellExecutor;
 import com.android.wm.shell.common.SyncTransactionQueue;
 import com.android.wm.shell.compatui.api.CompatUIInfo;
+import com.android.wm.shell.compatui.impl.CompatUIRequests;
 import com.android.wm.shell.desktopmode.DesktopRepository;
 import com.android.wm.shell.desktopmode.DesktopUserRepositories;
 import com.android.wm.shell.sysui.ShellController;
@@ -738,6 +739,22 @@
         verify(mController, never()).removeLayouts(taskInfo.taskId);
     }
 
+    @Test
+    @RequiresFlagsDisabled(Flags.FLAG_APP_COMPAT_UI_FRAMEWORK)
+    public void testSendCompatUIRequest_createRestartDialog() {
+        TaskInfo taskInfo = createTaskInfo(DISPLAY_ID, TASK_ID, /* hasSizeCompat= */ false);
+        doReturn(true).when(mMockRestartDialogLayout)
+                .needsToBeRecreated(any(TaskInfo.class),
+                        any(ShellTaskOrganizer.TaskListener.class));
+        doReturn(true).when(mCompatUIConfiguration).isRestartDialogEnabled();
+        doReturn(true).when(mCompatUIConfiguration).shouldShowRestartDialogAgain(eq(taskInfo));
+
+        mController.sendCompatUIRequest(new CompatUIRequests.DisplayCompatShowRestartDialog(
+                taskInfo, mMockTaskListener));
+        verify(mController).createRestartDialogWindowManager(any(), eq(taskInfo),
+                eq(mMockTaskListener));
+    }
+
     private static TaskInfo createTaskInfo(int displayId, int taskId, boolean hasSizeCompat) {
         return createTaskInfo(displayId, taskId, hasSizeCompat, /* isVisible */ false,
                 /* isFocused */ false, /* isTopActivityTransparent */ false);
diff --git a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/desktopmode/DesktopMixedTransitionHandlerTest.kt b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/desktopmode/DesktopMixedTransitionHandlerTest.kt
index e9f92cf..0c585b3 100644
--- a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/desktopmode/DesktopMixedTransitionHandlerTest.kt
+++ b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/desktopmode/DesktopMixedTransitionHandlerTest.kt
@@ -431,6 +431,38 @@
     }
 
     @Test
+    @EnableFlags(
+        Flags.FLAG_ENABLE_DESKTOP_APP_LAUNCH_TRANSITIONS_BUGFIX,
+        Flags.FLAG_ENABLE_DESKTOP_OPENING_DEEPLINK_MINIMIZE_ANIMATION_BUGFIX,
+    )
+    fun startAndAnimateLaunchTransition_withMinimizeChange_wrongTaskId_reparentsMinimizeChange() {
+        val wct = WindowContainerTransaction()
+        val launchingTask = createTask(WINDOWING_MODE_FREEFORM)
+        val minimizingTask = createTask(WINDOWING_MODE_FREEFORM)
+        val launchTaskChange = createChange(launchingTask, mode = TRANSIT_OPEN)
+        val minimizeChange = createChange(minimizingTask)
+        val transition = Binder()
+        whenever(transitions.startTransition(eq(TRANSIT_OPEN), eq(wct), anyOrNull()))
+            .thenReturn(transition)
+
+        mixedHandler.startLaunchTransition(
+            transitionType = TRANSIT_OPEN,
+            wct = wct,
+            taskId = Int.MAX_VALUE,
+            minimizingTaskId = minimizingTask.taskId,
+        )
+        mixedHandler.startAnimation(
+            transition,
+            createCloseTransitionInfo(TRANSIT_OPEN, listOf(launchTaskChange, minimizeChange)),
+            SurfaceControl.Transaction(),
+            SurfaceControl.Transaction(),
+        ) {}
+
+        verify(rootTaskDisplayAreaOrganizer)
+            .reparentToDisplayArea(anyInt(), eq(minimizeChange.leash), any())
+    }
+
+    @Test
     @EnableFlags(Flags.FLAG_ENABLE_DESKTOP_APP_LAUNCH_TRANSITIONS_BUGFIX)
     fun startAnimation_pendingTransition_noLaunchChange_returnsFalse() {
         val wct = WindowContainerTransaction()
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 d093629..7530844 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
@@ -31,6 +31,7 @@
 import android.content.ComponentName
 import android.content.Context
 import android.content.Intent
+import android.content.Intent.FLAG_ACTIVITY_NEW_TASK
 import android.content.pm.ActivityInfo
 import android.content.pm.ActivityInfo.CONFIG_DENSITY
 import android.content.pm.ActivityInfo.SCREEN_ORIENTATION_LANDSCAPE
@@ -1133,6 +1134,54 @@
     }
 
     @Test
+    @EnableFlags(Flags.FLAG_INHERIT_TASK_BOUNDS_FOR_TRAMPOLINE_TASK_LAUNCHES)
+    fun addMoveToDeskTaskChanges_newTaskInstance_inheritsClosingInstanceBounds() {
+        // Setup existing task.
+        val existingTask = setUpFreeformTask(active = true)
+        val testComponent = ComponentName(/* package */ "test.package", /* class */ "test.class")
+        existingTask.topActivity = testComponent
+        existingTask.configuration.windowConfiguration.setBounds(Rect(0, 0, 500, 500))
+        // Set up new instance of already existing task.
+        val launchingTask = setUpFullscreenTask()
+        launchingTask.topActivity = testComponent
+        launchingTask.baseIntent.addFlags(FLAG_ACTIVITY_NEW_TASK)
+
+        // Move new instance to desktop. By default multi instance is not supported so first
+        // instance will close.
+        val wct = WindowContainerTransaction()
+        controller.addMoveToDeskTaskChanges(wct, launchingTask, deskId = 0)
+
+        // New instance should inherit task bounds of old instance.
+        assertThat(findBoundsChange(wct, launchingTask))
+            .isEqualTo(existingTask.configuration.windowConfiguration.bounds)
+    }
+
+    @Test
+    @EnableFlags(Flags.FLAG_INHERIT_TASK_BOUNDS_FOR_TRAMPOLINE_TASK_LAUNCHES)
+    fun handleRequest_newTaskInstance_inheritsClosingInstanceBounds() {
+        setUpLandscapeDisplay()
+        // Setup existing task.
+        val existingTask = setUpFreeformTask(active = true)
+        val testComponent = ComponentName(/* package */ "test.package", /* class */ "test.class")
+        existingTask.topActivity = testComponent
+        existingTask.configuration.windowConfiguration.setBounds(Rect(0, 0, 500, 500))
+        // Set up new instance of already existing task.
+        val launchingTask = setUpFreeformTask(active = false)
+        taskRepository.removeTask(launchingTask.displayId, launchingTask.taskId)
+        launchingTask.topActivity = testComponent
+        launchingTask.baseIntent.addFlags(FLAG_ACTIVITY_NEW_TASK)
+
+        // Move new instance to desktop. By default multi instance is not supported so first
+        // instance will close.
+        val wct = controller.handleRequest(Binder(), createTransition(launchingTask))
+
+        assertNotNull(wct, "should handle request")
+        val finalBounds = findBoundsChange(wct, launchingTask)
+        // New instance should inherit task bounds of old instance.
+        assertThat(finalBounds).isEqualTo(existingTask.configuration.windowConfiguration.bounds)
+    }
+
+    @Test
     @EnableFlags(Flags.FLAG_ENABLE_CASCADING_WINDOWS)
     fun handleRequest_newFreeformTaskLaunch_cascadeApplied() {
         setUpLandscapeDisplay()
diff --git a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/desktopmode/DragToDesktopTransitionHandlerTest.kt b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/desktopmode/DragToDesktopTransitionHandlerTest.kt
index 0871d38..6e7adf3 100644
--- a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/desktopmode/DragToDesktopTransitionHandlerTest.kt
+++ b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/desktopmode/DragToDesktopTransitionHandlerTest.kt
@@ -26,6 +26,7 @@
 import com.android.internal.jank.Cuj.CUJ_DESKTOP_MODE_ENTER_APP_HANDLE_DRAG_RELEASE
 import com.android.internal.jank.InteractionJankMonitor
 import com.android.window.flags.Flags
+import com.android.window.flags.Flags.FLAG_ENABLE_DRAG_TO_DESKTOP_INCOMING_TRANSITIONS_BUGFIX
 import com.android.wm.shell.RootTaskDisplayAreaOrganizer
 import com.android.wm.shell.ShellTestCase
 import com.android.wm.shell.TestRunningTaskInfoBuilder
@@ -34,6 +35,7 @@
 import com.android.wm.shell.desktopmode.DesktopModeTransitionTypes.TRANSIT_DESKTOP_MODE_CANCEL_DRAG_TO_DESKTOP
 import com.android.wm.shell.desktopmode.DesktopModeTransitionTypes.TRANSIT_DESKTOP_MODE_END_DRAG_TO_DESKTOP
 import com.android.wm.shell.desktopmode.DesktopModeTransitionTypes.TRANSIT_DESKTOP_MODE_START_DRAG_TO_DESKTOP
+import com.android.wm.shell.desktopmode.DragToDesktopTransitionHandler.CancelState
 import com.android.wm.shell.desktopmode.DragToDesktopTransitionHandler.Companion.DRAG_TO_DESKTOP_FINISH_ANIM_DURATION_MS
 import com.android.wm.shell.shared.split.SplitScreenConstants.SPLIT_POSITION_BOTTOM_OR_RIGHT
 import com.android.wm.shell.shared.split.SplitScreenConstants.SPLIT_POSITION_TOP_OR_LEFT
@@ -56,6 +58,7 @@
 import org.mockito.ArgumentMatchers.eq
 import org.mockito.Mock
 import org.mockito.MockitoSession
+import org.mockito.kotlin.anyOrNull
 import org.mockito.kotlin.argThat
 import org.mockito.kotlin.mock
 import org.mockito.kotlin.never
@@ -118,6 +121,14 @@
                 .strictness(Strictness.LENIENT)
                 .mockStatic(SystemProperties::class.java)
                 .startMocking()
+        whenever(
+                transitions.startTransition(
+                    eq(TRANSIT_DESKTOP_MODE_END_DRAG_TO_DESKTOP),
+                    /* wct= */ any(),
+                    eq(defaultHandler),
+                )
+            )
+            .thenReturn(mock<IBinder>())
     }
 
     @After
@@ -679,17 +690,11 @@
         val startTransition = startDrag(defaultHandler, task)
         val endTransition = mock<IBinder>()
         defaultHandler.onTaskResizeAnimationListener = mock()
-        defaultHandler.mergeAnimation(
+        mergeAnimation(
             transition = endTransition,
-            info =
-                createTransitionInfo(
-                    type = TRANSIT_DESKTOP_MODE_END_DRAG_TO_DESKTOP,
-                    draggedTask = task,
-                ),
-            startT = mock<SurfaceControl.Transaction>(),
-            finishT = mock<SurfaceControl.Transaction>(),
+            type = TRANSIT_DESKTOP_MODE_END_DRAG_TO_DESKTOP,
+            task = task,
             mergeTarget = startTransition,
-            finishCallback = mock<Transitions.TransitionFinishCallback>(),
         )
 
         defaultHandler.onTransitionConsumed(endTransition, aborted = true, mock())
@@ -701,6 +706,123 @@
     }
 
     @Test
+    @EnableFlags(FLAG_ENABLE_DRAG_TO_DESKTOP_INCOMING_TRANSITIONS_BUGFIX)
+    fun mergeOtherTransition_cancelAndEndNotYetRequested_doesntInterruptsStartDrag() {
+        val finishCallback = mock<Transitions.TransitionFinishCallback>()
+        val task = createTask()
+        defaultHandler.onTaskResizeAnimationListener = mock()
+        val startTransition = startDrag(defaultHandler, task, finishCallback = finishCallback)
+
+        mergeInterruptingTransition(mergeTarget = startTransition)
+
+        verify(finishCallback, never()).onTransitionFinished(anyOrNull())
+        verify(dragAnimator, never()).cancelAnimator()
+    }
+
+    @Test
+    @EnableFlags(FLAG_ENABLE_DRAG_TO_DESKTOP_INCOMING_TRANSITIONS_BUGFIX)
+    fun mergeOtherTransition_endDragAlreadyMerged_doesNotInterruptStartDrag() {
+        val startDragFinishCallback = mock<Transitions.TransitionFinishCallback>()
+        val task = createTask()
+        val startTransition =
+            startDrag(defaultHandler, task, finishCallback = startDragFinishCallback)
+        defaultHandler.onTaskResizeAnimationListener = mock()
+        mergeAnimation(
+            type = TRANSIT_DESKTOP_MODE_END_DRAG_TO_DESKTOP,
+            task = task,
+            mergeTarget = startTransition,
+        )
+
+        mergeInterruptingTransition(mergeTarget = startTransition)
+
+        verify(startDragFinishCallback, never()).onTransitionFinished(anyOrNull())
+    }
+
+    @Test
+    @EnableFlags(FLAG_ENABLE_DRAG_TO_DESKTOP_INCOMING_TRANSITIONS_BUGFIX)
+    fun startEndAnimation_otherTransitionInterruptedStartAfterEndRequest_finishImmediately() {
+        val task1 = createTask()
+        val startTransition = startDrag(defaultHandler, task1)
+        val endTransition =
+            defaultHandler.finishDragToDesktopTransition(WindowContainerTransaction())
+        val startTransaction = mock<SurfaceControl.Transaction>()
+        val endDragFinishCallback = mock<Transitions.TransitionFinishCallback>()
+        defaultHandler.onTaskResizeAnimationListener = mock()
+        mergeInterruptingTransition(mergeTarget = startTransition)
+
+        val didAnimate =
+            defaultHandler.startAnimation(
+                transition = requireNotNull(endTransition),
+                info =
+                    createTransitionInfo(
+                        type = TRANSIT_DESKTOP_MODE_END_DRAG_TO_DESKTOP,
+                        draggedTask = task1,
+                    ),
+                startTransaction = startTransaction,
+                finishTransaction = mock(),
+                finishCallback = endDragFinishCallback,
+            )
+
+        assertThat(didAnimate).isTrue()
+        verify(startTransaction).apply()
+        verify(endDragFinishCallback).onTransitionFinished(anyOrNull())
+    }
+
+    @Test
+    @EnableFlags(FLAG_ENABLE_DRAG_TO_DESKTOP_INCOMING_TRANSITIONS_BUGFIX)
+    fun startDrag_otherTransitionInterruptedStartAfterEndRequested_animatesDragWhenReady() {
+        val task1 = createTask()
+        val startTransition = startDrag(defaultHandler, task1)
+        verify(dragAnimator).startAnimation()
+        val endTransition =
+            defaultHandler.finishDragToDesktopTransition(WindowContainerTransaction())
+        defaultHandler.onTaskResizeAnimationListener = mock()
+        mergeInterruptingTransition(mergeTarget = startTransition)
+        defaultHandler.startAnimation(
+            transition = requireNotNull(endTransition),
+            info =
+                createTransitionInfo(
+                    type = TRANSIT_DESKTOP_MODE_END_DRAG_TO_DESKTOP,
+                    draggedTask = task1,
+                ),
+            startTransaction = mock(),
+            finishTransaction = mock(),
+            finishCallback = mock(),
+        )
+
+        startDrag(defaultHandler, createTask())
+
+        verify(dragAnimator, times(2)).startAnimation()
+    }
+
+    private fun mergeInterruptingTransition(mergeTarget: IBinder) {
+        defaultHandler.mergeAnimation(
+            transition = mock<IBinder>(),
+            info = createTransitionInfo(type = TRANSIT_OPEN, draggedTask = createTask()),
+            startT = mock(),
+            finishT = mock(),
+            mergeTarget = mergeTarget,
+            finishCallback = mock(),
+        )
+    }
+
+    private fun mergeAnimation(
+        transition: IBinder = mock(),
+        type: Int,
+        mergeTarget: IBinder,
+        task: RunningTaskInfo,
+    ) {
+        defaultHandler.mergeAnimation(
+            transition = transition,
+            info = createTransitionInfo(type = type, draggedTask = task),
+            startT = mock(),
+            finishT = mock(),
+            mergeTarget = mergeTarget,
+            finishCallback = mock(),
+        )
+    }
+
+    @Test
     fun getAnimationFraction_returnsFraction() {
         val fraction =
             SpringDragToDesktopTransitionHandler.getAnimationFraction(
@@ -785,6 +907,7 @@
         finishTransaction: SurfaceControl.Transaction = mock(),
         homeChange: TransitionInfo.Change? = createHomeChange(),
         transitionRootLeash: SurfaceControl = mock(),
+        finishCallback: Transitions.TransitionFinishCallback = mock(),
     ): IBinder {
         whenever(dragAnimator.position).thenReturn(PointF())
         // Simulate transition is started and is ready to animate.
@@ -800,7 +923,7 @@
                 ),
             startTransaction = startTransaction,
             finishTransaction = finishTransaction,
-            finishCallback = {},
+            finishCallback = finishCallback,
         )
         return transition
     }
diff --git a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/recents/GroupedTaskInfoTest.kt b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/recents/GroupedTaskInfoTest.kt
index 6ecebd7..75f6bda 100644
--- a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/recents/GroupedTaskInfoTest.kt
+++ b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/recents/GroupedTaskInfoTest.kt
@@ -203,7 +203,7 @@
         assertThat(taskInfoFromParcel.taskInfoList).hasSize(3)
         // Only compare task ids
         val taskIdComparator = Correspondence.transforming<TaskInfo, Int>(
-            { it?.taskId }, "has taskId of"
+            { it.taskId }, "has taskId of"
         )
         assertThat(taskInfoFromParcel.taskInfoList).comparingElementsUsing(taskIdComparator)
             .containsExactly(1, 2, 3).inOrder()
diff --git a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/windowdecor/WindowDecorationTests.java b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/windowdecor/WindowDecorationTests.java
index a2927fa..9a2e2fa 100644
--- a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/windowdecor/WindowDecorationTests.java
+++ b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/windowdecor/WindowDecorationTests.java
@@ -60,6 +60,7 @@
 import android.graphics.Rect;
 import android.graphics.Region;
 import android.os.Handler;
+import android.os.LocaleList;
 import android.testing.AndroidTestingRunner;
 import android.util.DisplayMetrics;
 import android.view.AttachedSurfaceControl;
@@ -97,6 +98,7 @@
 
 import java.util.ArrayList;
 import java.util.List;
+import java.util.Locale;
 import java.util.function.Supplier;
 
 /**
@@ -475,6 +477,50 @@
     }
 
     @Test
+    public void testReinflateViewsOnLocaleListChange() {
+        final Display defaultDisplay = mock(Display.class);
+        doReturn(defaultDisplay).when(mMockDisplayController)
+                .getDisplay(Display.DEFAULT_DISPLAY);
+
+        final ActivityManager.RunningTaskInfo taskInfo = new TestRunningTaskInfoBuilder()
+                .setVisible(true)
+                .setDisplayId(Display.DEFAULT_DISPLAY)
+                .build();
+        taskInfo.configuration.setLocales(new LocaleList(Locale.FRANCE, Locale.US));
+        final TestWindowDecoration windowDecor = spy(createWindowDecoration(taskInfo));
+        windowDecor.relayout(taskInfo, true /* hasGlobalFocus */, Region.obtain());
+        clearInvocations(windowDecor);
+
+        final ActivityManager.RunningTaskInfo taskInfo2 = new TestRunningTaskInfoBuilder()
+                .setVisible(true)
+                .setDisplayId(Display.DEFAULT_DISPLAY)
+                .build();
+        taskInfo2.configuration.setLocales(new LocaleList(Locale.US, Locale.FRANCE));
+        windowDecor.relayout(taskInfo2, true /* hasGlobalFocus */, Region.obtain());
+        // WindowDecoration#releaseViews should be called since the locale list has changed.
+        verify(windowDecor, times(1)).releaseViews(any());
+    }
+
+    @Test
+    public void testViewNotReinflatedWhenLocaleListNotChanged() {
+        final Display defaultDisplay = mock(Display.class);
+        doReturn(defaultDisplay).when(mMockDisplayController)
+                .getDisplay(Display.DEFAULT_DISPLAY);
+
+        final ActivityManager.RunningTaskInfo taskInfo = new TestRunningTaskInfoBuilder()
+                .setVisible(true)
+                .setDisplayId(Display.DEFAULT_DISPLAY)
+                .build();
+        taskInfo.configuration.setLocales(new LocaleList(Locale.FRANCE, Locale.US));
+        final TestWindowDecoration windowDecor = spy(createWindowDecoration(taskInfo));
+        windowDecor.relayout(taskInfo, true /* hasGlobalFocus */, Region.obtain());
+        clearInvocations(windowDecor);
+        windowDecor.relayout(taskInfo, true /* hasGlobalFocus */, Region.obtain());
+        // WindowDecoration#releaseViews should not be called since nothing has changed.
+        verify(windowDecor, never()).releaseViews(any());
+    }
+
+    @Test
     public void testLayoutResultCalculation_fullWidthCaption() {
         final Display defaultDisplay = mock(Display.class);
         doReturn(defaultDisplay).when(mMockDisplayController)
diff --git a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/windowdecor/common/WindowDecorTaskResourceLoaderTest.kt b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/windowdecor/common/WindowDecorTaskResourceLoaderTest.kt
index c61e0eb..c8ccac3 100644
--- a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/windowdecor/common/WindowDecorTaskResourceLoaderTest.kt
+++ b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/windowdecor/common/WindowDecorTaskResourceLoaderTest.kt
@@ -23,6 +23,7 @@
 import android.content.pm.ApplicationInfo
 import android.content.pm.PackageManager
 import android.graphics.drawable.Drawable
+import android.os.LocaleList
 import android.os.UserHandle
 import android.testing.AndroidTestingRunner
 import android.testing.TestableContext
@@ -39,6 +40,7 @@
 import com.android.wm.shell.sysui.UserChangeListener
 import com.android.wm.shell.windowdecor.common.WindowDecorTaskResourceLoader.AppResources
 import com.google.common.truth.Truth.assertThat
+import java.util.Locale
 import org.junit.Assert.assertThrows
 import org.junit.Before
 import org.junit.Test
@@ -116,8 +118,10 @@
     @Test
     fun testGetName_cached_returnsFromCache() {
         val task = createTaskInfo(context.userId)
+        task.configuration.setLocales(LocaleList(Locale.US))
         loader.onWindowDecorCreated(task)
         loader.taskToResourceCache[task.taskId] = AppResources("App Name", mock(), mock())
+        loader.localeListOnCache[task.taskId] = LocaleList(Locale.US)
 
         loader.getName(task)
 
@@ -130,6 +134,19 @@
     }
 
     @Test
+    fun testGetName_cached_localesChanged_loadsResourceAndCaches() {
+        val task = createTaskInfo(context.userId)
+        loader.onWindowDecorCreated(task)
+        loader.taskToResourceCache[task.taskId] = AppResources("App Name", mock(), mock())
+        loader.localeListOnCache[task.taskId] = LocaleList(Locale.US, Locale.FRANCE)
+        task.configuration.setLocales(LocaleList(Locale.FRANCE, Locale.US))
+        doReturn("App Name but in French").whenever(mockPackageManager).getApplicationLabel(any())
+
+        assertThat(loader.getName(task)).isEqualTo("App Name but in French")
+        assertThat(loader.taskToResourceCache[task.taskId]?.appName).isEqualTo("App Name but in French")
+    }
+
+    @Test
     fun testGetHeaderIcon_notCached_loadsResourceAndCaches() {
         val task = createTaskInfo(context.userId)
         loader.onWindowDecorCreated(task)
diff --git a/libs/hostgraphics/HostBufferQueue.cpp b/libs/hostgraphics/HostBufferQueue.cpp
index 7e14b88..ef54062 100644
--- a/libs/hostgraphics/HostBufferQueue.cpp
+++ b/libs/hostgraphics/HostBufferQueue.cpp
@@ -29,6 +29,7 @@
     }
 
     virtual status_t detachBuffer(int slot) {
+        mBuffer.clear();
         return OK;
     }
 
diff --git a/media/java/android/media/MediaCodecInfo.java b/media/java/android/media/MediaCodecInfo.java
index 4e86eac..f3b21bf 100644
--- a/media/java/android/media/MediaCodecInfo.java
+++ b/media/java/android/media/MediaCodecInfo.java
@@ -3836,6 +3836,151 @@
                         maxBlocks, maxBlocksPerSecond,
                         blockSize, blockSize,
                         1 /* widthAlignment */, 1 /* heightAlignment */);
+            } else if (GetFlag(() -> android.media.codec.Flags.apvSupport())
+                        && mime.equalsIgnoreCase(MediaFormat.MIMETYPE_VIDEO_APV)) {
+                maxBlocksPerSecond = 11880;
+                maxBps = 7000000;
+
+                // Sample rate, and Bit rate for APV Codec,
+                // corresponding to the definitions in
+                // "10.1.4. Levels and bands"
+                // found at https://www.ietf.org/archive/id/draft-lim-apv-03.html
+                for (CodecProfileLevel profileLevel: profileLevels) {
+                    long SR = 0; // luma sample rate
+                    int BR = 0;  // bit rate bps
+                    switch (profileLevel.level) {
+                        case CodecProfileLevel.APVLevel1Band0:
+                            SR =      3041280; BR =    7000000; break;
+                        case CodecProfileLevel.APVLevel1Band1:
+                            SR =      3041280; BR =   11000000; break;
+                        case CodecProfileLevel.APVLevel1Band2:
+                            SR =      3041280; BR =   14000000; break;
+                        case CodecProfileLevel.APVLevel1Band3:
+                            SR =      3041280; BR =   21000000; break;
+                        case CodecProfileLevel.APVLevel11Band0:
+                            SR =      6082560; BR =   14000000; break;
+                        case CodecProfileLevel.APVLevel11Band1:
+                            SR =      6082560; BR =   21000000; break;
+                        case CodecProfileLevel.APVLevel11Band2:
+                            SR =      6082560; BR =   28000000; break;
+                        case CodecProfileLevel.APVLevel11Band3:
+                            SR =      6082560; BR =   42000000; break;
+                        case CodecProfileLevel.APVLevel2Band0:
+                            SR =     15667200; BR =   36000000; break;
+                        case CodecProfileLevel.APVLevel2Band1:
+                            SR =     15667200; BR =   53000000; break;
+                        case CodecProfileLevel.APVLevel2Band2:
+                            SR =     15667200; BR =   71000000; break;
+                        case CodecProfileLevel.APVLevel2Band3:
+                            SR =     15667200; BR =  106000000; break;
+                        case CodecProfileLevel.APVLevel21Band0:
+                            SR =     31334400; BR =   71000000; break;
+                        case CodecProfileLevel.APVLevel21Band1:
+                            SR =     31334400; BR =  106000000; break;
+                        case CodecProfileLevel.APVLevel21Band2:
+                            SR =     31334400; BR =  141000000; break;
+                        case CodecProfileLevel.APVLevel21Band3:
+                            SR =     31334400; BR =  212000000; break;
+                        case CodecProfileLevel.APVLevel3Band0:
+                            SR =     66846720; BR =  101000000; break;
+                        case CodecProfileLevel.APVLevel3Band1:
+                            SR =     66846720; BR =  151000000; break;
+                        case CodecProfileLevel.APVLevel3Band2:
+                            SR =     66846720; BR =  201000000; break;
+                        case CodecProfileLevel.APVLevel3Band3:
+                            SR =     66846720; BR =  301000000; break;
+                        case CodecProfileLevel.APVLevel31Band0:
+                            SR =    133693440; BR =  201000000; break;
+                        case CodecProfileLevel.APVLevel31Band1:
+                            SR =    133693440; BR =  301000000; break;
+                        case CodecProfileLevel.APVLevel31Band2:
+                            SR =    133693440; BR =  401000000; break;
+                        case CodecProfileLevel.APVLevel31Band3:
+                            SR =    133693440; BR =  602000000; break;
+                        case CodecProfileLevel.APVLevel4Band0:
+                            SR =    265420800; BR =  401000000; break;
+                        case CodecProfileLevel.APVLevel4Band1:
+                            SR =    265420800; BR =  602000000; break;
+                        case CodecProfileLevel.APVLevel4Band2:
+                            SR =    265420800; BR =  780000000; break;
+                        case CodecProfileLevel.APVLevel4Band3:
+                            SR =    265420800; BR = 1170000000; break;
+                        case CodecProfileLevel.APVLevel41Band0:
+                            SR =    530841600; BR =  780000000; break;
+                        case CodecProfileLevel.APVLevel41Band1:
+                            SR =    530841600; BR = 1170000000; break;
+                        case CodecProfileLevel.APVLevel41Band2:
+                            SR =    530841600; BR = 1560000000; break;
+                        case CodecProfileLevel.APVLevel41Band3:
+                            // Current API allows bitrates only up to Max Integer
+                            // Hence we are limiting internal limits to Integer.MAX_VALUE
+                            // even when actual Level/Band limits are higher
+                            SR =    530841600; BR = Integer.MAX_VALUE; break;
+                        case CodecProfileLevel.APVLevel5Band0:
+                            SR =   1061683200; BR = 1560000000; break;
+                        case CodecProfileLevel.APVLevel5Band1:
+                            SR =   1061683200; BR = Integer.MAX_VALUE; break;
+                        case CodecProfileLevel.APVLevel5Band2:
+                            SR =   1061683200; BR = Integer.MAX_VALUE; break;
+                        case CodecProfileLevel.APVLevel5Band3:
+                            SR =   1061683200; BR = Integer.MAX_VALUE; break;
+                        case CodecProfileLevel.APVLevel51Band0:
+                        case CodecProfileLevel.APVLevel51Band1:
+                        case CodecProfileLevel.APVLevel51Band2:
+                        case CodecProfileLevel.APVLevel51Band3:
+                            SR =   2123366400; BR = Integer.MAX_VALUE; break;
+                        case CodecProfileLevel.APVLevel6Band0:
+                        case CodecProfileLevel.APVLevel6Band1:
+                        case CodecProfileLevel.APVLevel6Band2:
+                        case CodecProfileLevel.APVLevel6Band3:
+                            SR =  4777574400L; BR = Integer.MAX_VALUE; break;
+                        case CodecProfileLevel.APVLevel61Band0:
+                        case CodecProfileLevel.APVLevel61Band1:
+                        case CodecProfileLevel.APVLevel61Band2:
+                        case CodecProfileLevel.APVLevel61Band3:
+                            SR =  8493465600L; BR = Integer.MAX_VALUE; break;
+                        case CodecProfileLevel.APVLevel7Band0:
+                        case CodecProfileLevel.APVLevel7Band1:
+                        case CodecProfileLevel.APVLevel7Band2:
+                        case CodecProfileLevel.APVLevel7Band3:
+                            SR = 16986931200L; BR = Integer.MAX_VALUE; break;
+                        case CodecProfileLevel.APVLevel71Band0:
+                        case CodecProfileLevel.APVLevel71Band1:
+                        case CodecProfileLevel.APVLevel71Band2:
+                        case CodecProfileLevel.APVLevel71Band3:
+                            SR = 33973862400L; BR = Integer.MAX_VALUE; break;
+                        default:
+                            Log.w(TAG, "Unrecognized level "
+                                    + profileLevel.level + " for " + mime);
+                            errors |= ERROR_UNRECOGNIZED;
+                    }
+                    switch (profileLevel.profile) {
+                        case CodecProfileLevel.APVProfile422_10:
+                        case CodecProfileLevel.APVProfile422_10HDR10:
+                        case CodecProfileLevel.APVProfile422_10HDR10Plus:
+                            break;
+                        default:
+                            Log.w(TAG, "Unrecognized profile "
+                                    + profileLevel.profile + " for " + mime);
+                            errors |= ERROR_UNRECOGNIZED;
+                    }
+                    errors &= ~ERROR_NONE_SUPPORTED;
+                    maxBlocksPerSecond = Math.max(SR, maxBlocksPerSecond);
+                    maxBps = Math.max(BR, maxBps);
+                }
+
+                final int blockSize = 16;
+                maxBlocks = Integer.MAX_VALUE;
+                maxBlocksPerSecond = Utils.divUp(maxBlocksPerSecond, blockSize * blockSize);
+                maxBlocks = (int) Math.min((long) maxBlocks, maxBlocksPerSecond);
+                // Max frame size in APV is 2^24
+                int maxLengthInBlocks = Utils.divUp((int) Math.pow(2, 24), blockSize);
+                maxLengthInBlocks = Math.min(maxLengthInBlocks, maxBlocks);
+                applyMacroBlockLimits(
+                        maxLengthInBlocks, maxLengthInBlocks,
+                        maxBlocks, maxBlocksPerSecond,
+                        blockSize, blockSize,
+                        2 /* widthAlignment */, 1 /* heightAlignment */);
             } else {
                 Log.w(TAG, "Unsupported mime " + mime);
                 // using minimal bitrate here.  should be overriden by
diff --git a/packages/SettingsLib/IllustrationPreference/src/com/android/settingslib/widget/IllustrationPreference.java b/packages/SettingsLib/IllustrationPreference/src/com/android/settingslib/widget/IllustrationPreference.java
index 0c64fa7..d55bbb3 100644
--- a/packages/SettingsLib/IllustrationPreference/src/com/android/settingslib/widget/IllustrationPreference.java
+++ b/packages/SettingsLib/IllustrationPreference/src/com/android/settingslib/widget/IllustrationPreference.java
@@ -447,6 +447,10 @@
         illustrationView.setMaxWidth((int) (restrictedMaxHeight * aspectRatio));
     }
 
+    public boolean isAnimatable() {
+        return mIsAnimatable;
+    }
+
     private void startAnimation(Drawable drawable) {
         if (!(drawable instanceof Animatable)) {
             return;
diff --git a/packages/SystemUI/aconfig/systemui.aconfig b/packages/SystemUI/aconfig/systemui.aconfig
index 42f576a..3c86f28 100644
--- a/packages/SystemUI/aconfig/systemui.aconfig
+++ b/packages/SystemUI/aconfig/systemui.aconfig
@@ -2121,3 +2121,10 @@
     description: "Enables moving the launching window on top of the origin window in the Animation library."
     bug: "390422470"
 }
+
+flag {
+    name: "status_bar_chips_return_animations"
+    namespace: "systemui"
+    description: "Enables return animations for status bar chips"
+    bug: "202516970"
+}
diff --git a/packages/SystemUI/compose/core/src/com/android/compose/ui/graphics/DrawInContainer.kt b/packages/SystemUI/compose/core/src/com/android/compose/ui/graphics/DrawInContainer.kt
index d08d859..fc4d53a 100644
--- a/packages/SystemUI/compose/core/src/com/android/compose/ui/graphics/DrawInContainer.kt
+++ b/packages/SystemUI/compose/core/src/com/android/compose/ui/graphics/DrawInContainer.kt
@@ -22,7 +22,6 @@
 import androidx.compose.runtime.mutableStateOf
 import androidx.compose.runtime.setValue
 import androidx.compose.ui.Modifier
-import androidx.compose.ui.draw.drawWithContent
 import androidx.compose.ui.geometry.Offset
 import androidx.compose.ui.graphics.Path
 import androidx.compose.ui.graphics.drawscope.ContentDrawScope
@@ -32,7 +31,6 @@
 import androidx.compose.ui.graphics.layer.GraphicsLayer
 import androidx.compose.ui.graphics.layer.drawLayer
 import androidx.compose.ui.layout.LayoutCoordinates
-import androidx.compose.ui.layout.onPlaced
 import androidx.compose.ui.layout.positionInWindow
 import androidx.compose.ui.modifier.ModifierLocalModifierNode
 import androidx.compose.ui.node.DrawModifierNode
@@ -50,11 +48,7 @@
  * The elements redirected to this container will be drawn above the content of this composable.
  */
 fun Modifier.container(state: ContainerState): Modifier {
-    return onPlaced { state.lastOffsetInWindow = it.positionInWindow() }
-        .drawWithContent {
-            drawContent()
-            state.drawInOverlay(this)
-        }
+    return this then ContainerElement(state)
 }
 
 /**
@@ -105,6 +99,30 @@
     fun drawInOverlay(drawScope: DrawScope)
 }
 
+private data class ContainerElement(private val state: ContainerState) :
+    ModifierNodeElement<ContainerNode>() {
+    override fun create(): ContainerNode {
+        return ContainerNode(state)
+    }
+
+    override fun update(node: ContainerNode) {
+        node.state = state
+    }
+}
+
+/** A node implementing [container] that can be delegated to. */
+class ContainerNode(var state: ContainerState) :
+    Modifier.Node(), LayoutAwareModifierNode, DrawModifierNode {
+    override fun onPlaced(coordinates: LayoutCoordinates) {
+        state.lastOffsetInWindow = coordinates.positionInWindow()
+    }
+
+    override fun ContentDrawScope.draw() {
+        drawContent()
+        state.drawInOverlay(this)
+    }
+}
+
 private data class DrawInContainerElement(
     var state: ContainerState,
     var enabled: () -> Boolean,
diff --git a/packages/SystemUI/compose/features/src/com/android/systemui/qs/ui/composable/QuickSettingsScene.kt b/packages/SystemUI/compose/features/src/com/android/systemui/qs/ui/composable/QuickSettingsScene.kt
index 061fdd9..0a71148 100644
--- a/packages/SystemUI/compose/features/src/com/android/systemui/qs/ui/composable/QuickSettingsScene.kt
+++ b/packages/SystemUI/compose/features/src/com/android/systemui/qs/ui/composable/QuickSettingsScene.kt
@@ -356,7 +356,8 @@
                                     modifier = Modifier.padding(horizontal = 16.dp),
                                 )
                             }
-                        else -> CollapsedShadeHeader(viewModel = headerViewModel)
+                        else ->
+                            CollapsedShadeHeader(viewModel = headerViewModel, isSplitShade = false)
                     }
                     Spacer(modifier = Modifier.height(16.dp))
                     // This view has its own horizontal padding
diff --git a/packages/SystemUI/compose/features/src/com/android/systemui/qs/ui/composable/QuickSettingsShadeOverlay.kt b/packages/SystemUI/compose/features/src/com/android/systemui/qs/ui/composable/QuickSettingsShadeOverlay.kt
index 542f081..8f0fb20 100644
--- a/packages/SystemUI/compose/features/src/com/android/systemui/qs/ui/composable/QuickSettingsShadeOverlay.kt
+++ b/packages/SystemUI/compose/features/src/com/android/systemui/qs/ui/composable/QuickSettingsShadeOverlay.kt
@@ -258,15 +258,18 @@
                 modifier = Modifier.padding(horizontal = QuickSettingsShade.Dimensions.Padding),
             )
 
-            BrightnessSliderContainer(
-                viewModel = viewModel.brightnessSliderViewModel,
-                containerColors = ContainerColors.singleColor(OverlayShade.Colors.PanelBackground),
-                modifier =
-                    Modifier.systemGestureExclusionInShade(
-                            enabled = { layoutState.transitionState is TransitionState.Idle }
-                        )
-                        .fillMaxWidth(),
-            )
+            Box(
+                Modifier.systemGestureExclusionInShade(
+                    enabled = { layoutState.transitionState is TransitionState.Idle }
+                )
+            ) {
+                BrightnessSliderContainer(
+                    viewModel = viewModel.brightnessSliderViewModel,
+                    containerColors =
+                        ContainerColors.singleColor(OverlayShade.Colors.PanelBackground),
+                    modifier = Modifier.fillMaxWidth(),
+                )
+            }
 
             Box {
                 GridAnchor()
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 23baeac..86c8fc3 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
@@ -127,6 +127,7 @@
 @Composable
 fun ContentScope.CollapsedShadeHeader(
     viewModel: ShadeHeaderViewModel,
+    isSplitShade: Boolean,
     modifier: Modifier = Modifier,
 ) {
     val cutoutLocation = LocalDisplayCutout.current.location
@@ -141,8 +142,6 @@
             }
         }
 
-    val isShadeLayoutWide = viewModel.isShadeLayoutWide
-
     val isPrivacyChipVisible by viewModel.isPrivacyChipVisible.collectAsStateWithLifecycle()
 
     // This layout assumes it is globally positioned at (0, 0) and is the same size as the screen.
@@ -154,7 +153,7 @@
                 horizontalArrangement = Arrangement.spacedBy(5.dp),
                 modifier = Modifier.padding(horizontal = horizontalPadding),
             ) {
-                Clock(scale = 1f, onClick = viewModel::onClockClicked)
+                Clock(onClick = viewModel::onClockClicked)
                 VariableDayDate(
                     longerDateText = viewModel.longerDateText,
                     shorterDateText = viewModel.shorterDateText,
@@ -184,11 +183,11 @@
                         Modifier.element(ShadeHeader.Elements.CollapsedContentEnd)
                             .padding(horizontal = horizontalPadding),
                 ) {
-                    if (isShadeLayoutWide) {
+                    if (isSplitShade) {
                         ShadeCarrierGroup(viewModel = viewModel)
                     }
                     SystemIconChip(
-                        onClick = viewModel::onSystemIconChipClicked.takeIf { isShadeLayoutWide }
+                        onClick = viewModel::onSystemIconChipClicked.takeIf { isSplitShade }
                     ) {
                         StatusIcons(
                             viewModel = viewModel,
@@ -233,13 +232,11 @@
                     .defaultMinSize(minHeight = ShadeHeader.Dimensions.ExpandedHeight),
         ) {
             Box(modifier = Modifier.fillMaxWidth()) {
-                Box {
-                    Clock(
-                        scale = 2.57f,
-                        onClick = viewModel::onClockClicked,
-                        modifier = Modifier.align(Alignment.CenterStart),
-                    )
-                }
+                Clock(
+                    onClick = viewModel::onClockClicked,
+                    modifier = Modifier.align(Alignment.CenterStart),
+                    scale = 2.57f,
+                )
                 Box(
                     modifier =
                         Modifier.element(ShadeHeader.Elements.ShadeCarrierGroup).fillMaxWidth()
@@ -291,8 +288,6 @@
     val horizontalPadding =
         max(LocalScreenCornerRadius.current / 2f, Shade.Dimensions.HorizontalPadding)
 
-    val isShadeLayoutWide = viewModel.isShadeLayoutWide
-
     val isPrivacyChipVisible by viewModel.isPrivacyChipVisible.collectAsStateWithLifecycle()
 
     // This layout assumes it is globally positioned at (0, 0) and is the same size as the screen.
@@ -301,16 +296,15 @@
         startContent = {
             Row(
                 verticalAlignment = Alignment.CenterVertically,
+                horizontalArrangement = Arrangement.spacedBy(5.dp),
                 modifier = Modifier.padding(horizontal = horizontalPadding),
             ) {
                 val chipHighlight = viewModel.notificationsChipHighlight
-                if (isShadeLayoutWide) {
+                if (viewModel.showClock) {
                     Clock(
-                        scale = 1f,
                         onClick = viewModel::onClockClicked,
                         modifier = Modifier.padding(horizontal = 4.dp),
                     )
-                    Spacer(modifier = Modifier.width(5.dp))
                 }
                 NotificationsChip(
                     onClick = viewModel::onNotificationIconChipClicked,
@@ -437,7 +431,11 @@
 }
 
 @Composable
-private fun ContentScope.Clock(scale: Float, onClick: () -> Unit, modifier: Modifier = Modifier) {
+private fun ContentScope.Clock(
+    onClick: () -> Unit,
+    modifier: Modifier = Modifier,
+    scale: Float = 1f,
+) {
     val layoutDirection = LocalLayoutDirection.current
 
     ElementWithValues(key = ShadeHeader.Elements.Clock, modifier = modifier) {
diff --git a/packages/SystemUI/compose/features/src/com/android/systemui/shade/ui/composable/ShadeScene.kt b/packages/SystemUI/compose/features/src/com/android/systemui/shade/ui/composable/ShadeScene.kt
index 5040490..885d34f 100644
--- a/packages/SystemUI/compose/features/src/com/android/systemui/shade/ui/composable/ShadeScene.kt
+++ b/packages/SystemUI/compose/features/src/com/android/systemui/shade/ui/composable/ShadeScene.kt
@@ -56,11 +56,11 @@
 import androidx.compose.ui.layout.layoutId
 import androidx.compose.ui.platform.LocalContext
 import androidx.compose.ui.platform.LocalDensity
-import androidx.compose.ui.platform.LocalLifecycleOwner
 import androidx.compose.ui.res.colorResource
 import androidx.compose.ui.res.dimensionResource
 import androidx.compose.ui.unit.dp
 import androidx.compose.ui.zIndex
+import androidx.lifecycle.compose.LocalLifecycleOwner
 import androidx.lifecycle.compose.collectAsStateWithLifecycle
 import com.android.compose.animation.scene.ContentScope
 import com.android.compose.animation.scene.ElementKey
@@ -68,6 +68,7 @@
 import com.android.compose.animation.scene.UserAction
 import com.android.compose.animation.scene.UserActionResult
 import com.android.compose.animation.scene.animateContentDpAsState
+import com.android.compose.animation.scene.animateContentFloatAsState
 import com.android.compose.animation.scene.animateSceneFloatAsState
 import com.android.compose.animation.scene.content.state.TransitionState
 import com.android.compose.modifiers.padding
@@ -223,9 +224,6 @@
                 viewModel = viewModel,
                 headerViewModel = headerViewModel,
                 notificationsPlaceholderViewModel = notificationsPlaceholderViewModel,
-                createTintedIconManager = createTintedIconManager,
-                createBatteryMeterViewController = createBatteryMeterViewController,
-                statusBarIconController = statusBarIconController,
                 mediaCarouselController = mediaCarouselController,
                 mediaHost = qqsMediaHost,
                 modifier = modifier,
@@ -253,9 +251,6 @@
     viewModel: ShadeSceneContentViewModel,
     headerViewModel: ShadeHeaderViewModel,
     notificationsPlaceholderViewModel: NotificationsPlaceholderViewModel,
-    createTintedIconManager: (ViewGroup, StatusBarLocation) -> TintedIconManager,
-    createBatteryMeterViewController: (ViewGroup, StatusBarLocation) -> BatteryMeterViewController,
-    statusBarIconController: StatusBarIconController,
     mediaCarouselController: MediaCarouselController,
     mediaHost: MediaHost,
     modifier: Modifier = Modifier,
@@ -340,6 +335,7 @@
             content = {
                 CollapsedShadeHeader(
                     viewModel = headerViewModel,
+                    isSplitShade = false,
                     modifier = Modifier.layoutId(SingleShadeMeasurePolicy.LayoutId.ShadeHeader),
                 )
 
@@ -434,15 +430,13 @@
     val footerActionsViewModel =
         remember(lifecycleOwner, viewModel) { viewModel.getFooterActionsViewModel(lifecycleOwner) }
     val tileSquishiness by
-        animateSceneFloatAsState(
+        animateContentFloatAsState(
             value = 1f,
             key = QuickSettings.SharedValues.TilesSquishiness,
             canOverflow = false,
         )
     val unfoldTranslationXForStartSide by
         viewModel.unfoldTranslationX(isOnStartSide = true).collectAsStateWithLifecycle(0f)
-    val unfoldTranslationXForEndSide by
-        viewModel.unfoldTranslationX(isOnStartSide = false).collectAsStateWithLifecycle(0f)
 
     val notificationStackPadding = dimensionResource(id = R.dimen.notification_side_paddings)
     val navBarBottomHeight = WindowInsets.navigationBars.asPaddingValues().calculateBottomPadding()
@@ -512,6 +506,7 @@
         Column(modifier = Modifier.fillMaxSize()) {
             CollapsedShadeHeader(
                 viewModel = headerViewModel,
+                isSplitShade = true,
                 modifier =
                     Modifier.then(brightnessMirrorShowingModifier)
                         .padding(horizontal = { unfoldTranslationXForStartSide.roundToInt() }),
diff --git a/packages/SystemUI/compose/scene/src/com/android/compose/animation/scene/content/Content.kt b/packages/SystemUI/compose/scene/src/com/android/compose/animation/scene/content/Content.kt
index 72ee75a..90bf92a 100644
--- a/packages/SystemUI/compose/scene/src/com/android/compose/animation/scene/content/Content.kt
+++ b/packages/SystemUI/compose/scene/src/com/android/compose/animation/scene/content/Content.kt
@@ -32,12 +32,17 @@
 import androidx.compose.runtime.remember
 import androidx.compose.runtime.setValue
 import androidx.compose.ui.Modifier
+import androidx.compose.ui.layout.ApproachLayoutModifierNode
+import androidx.compose.ui.layout.ApproachMeasureScope
 import androidx.compose.ui.layout.LookaheadScope
-import androidx.compose.ui.layout.approachLayout
-import androidx.compose.ui.layout.layout
+import androidx.compose.ui.layout.Measurable
+import androidx.compose.ui.layout.MeasureResult
+import androidx.compose.ui.layout.MeasureScope
+import androidx.compose.ui.node.DelegatingNode
+import androidx.compose.ui.node.ModifierNodeElement
 import androidx.compose.ui.platform.testTag
+import androidx.compose.ui.unit.Constraints
 import androidx.compose.ui.unit.IntSize
-import androidx.compose.ui.zIndex
 import com.android.compose.animation.scene.Ancestor
 import com.android.compose.animation.scene.AnimatedState
 import com.android.compose.animation.scene.ContentKey
@@ -68,8 +73,8 @@
 import com.android.compose.gesture.NestedScrollableBound
 import com.android.compose.gesture.nestedScrollController
 import com.android.compose.modifiers.thenIf
+import com.android.compose.ui.graphics.ContainerNode
 import com.android.compose.ui.graphics.ContainerState
-import com.android.compose.ui.graphics.container
 import kotlin.math.pow
 
 /** A content defined in a [SceneTransitionLayout], i.e. a scene or an overlay. */
@@ -158,24 +163,14 @@
     fun Content(modifier: Modifier = Modifier, isInvisible: Boolean = false) {
         // If this content has a custom factory, provide it to the content so that the factory is
         // automatically used when calling rememberOverscrollEffect().
+        val isElevationPossible =
+            layoutImpl.state.isElevationPossible(content = key, element = null)
         Box(
-            modifier
-                .thenIf(isInvisible) { InvisibleModifier }
-                .zIndex(zIndex)
-                .approachLayout(
-                    isMeasurementApproachInProgress = { layoutImpl.state.isTransitioning() }
-                ) { measurable, constraints ->
-                    // TODO(b/353679003): Use the ModifierNode API to set this *before* the
-                    // approach
-                    // pass is started.
-                    targetSize = lookaheadSize
-                    val placeable = measurable.measure(constraints)
-                    layout(placeable.width, placeable.height) { placeable.place(0, 0) }
-                }
-                .thenIf(layoutImpl.state.isElevationPossible(content = key, element = null)) {
-                    Modifier.container(containerState)
-                }
-                .thenIf(layoutImpl.implicitTestTags) { Modifier.testTag(key.testTag) }
+            modifier.then(ContentElement(this, isElevationPossible, isInvisible)).thenIf(
+                layoutImpl.implicitTestTags
+            ) {
+                Modifier.testTag(key.testTag)
+            }
         ) {
             CompositionLocalProvider(LocalOverscrollFactory provides lastFactory) {
                 scope.content()
@@ -194,6 +189,72 @@
     }
 }
 
+private data class ContentElement(
+    private val content: Content,
+    private val isElevationPossible: Boolean,
+    private val isInvisible: Boolean,
+) : ModifierNodeElement<ContentNode>() {
+    override fun create(): ContentNode = ContentNode(content, isElevationPossible, isInvisible)
+
+    override fun update(node: ContentNode) {
+        node.update(content, isElevationPossible, isInvisible)
+    }
+}
+
+private class ContentNode(
+    private var content: Content,
+    private var isElevationPossible: Boolean,
+    private var isInvisible: Boolean,
+) : DelegatingNode(), ApproachLayoutModifierNode {
+    private var containerDelegate = containerDelegate(isElevationPossible)
+
+    private fun containerDelegate(isElevationPossible: Boolean): ContainerNode? {
+        return if (isElevationPossible) delegate(ContainerNode(content.containerState)) else null
+    }
+
+    fun update(content: Content, isElevationPossible: Boolean, isInvisible: Boolean) {
+        if (content != this.content || isElevationPossible != this.isElevationPossible) {
+            this.content = content
+            this.isElevationPossible = isElevationPossible
+
+            containerDelegate?.let { undelegate(it) }
+            containerDelegate = containerDelegate(isElevationPossible)
+        }
+
+        this.isInvisible = isInvisible
+    }
+
+    override fun isMeasurementApproachInProgress(lookaheadSize: IntSize): Boolean = false
+
+    override fun MeasureScope.measure(
+        measurable: Measurable,
+        constraints: Constraints,
+    ): MeasureResult {
+        check(isLookingAhead)
+        return measurable.measure(constraints).run {
+            content.targetSize = IntSize(width, height)
+            layout(width, height) {
+                if (!isInvisible) {
+                    place(0, 0, zIndex = content.zIndex)
+                }
+            }
+        }
+    }
+
+    override fun ApproachMeasureScope.approachMeasure(
+        measurable: Measurable,
+        constraints: Constraints,
+    ): MeasureResult {
+        return measurable.measure(constraints).run {
+            layout(width, height) {
+                if (!isInvisible) {
+                    place(0, 0, zIndex = content.zIndex)
+                }
+            }
+        }
+    }
+}
+
 internal class ContentEffects(factory: OverscrollFactory) {
     val overscrollEffect = factory.createOverscrollEffect()
     val gestureEffect = GestureEffect(overscrollEffect)
@@ -307,8 +368,3 @@
         )
     }
 }
-
-private val InvisibleModifier =
-    Modifier.layout { measurable, constraints ->
-        measurable.measure(constraints).run { layout(width, height) {} }
-    }
diff --git a/packages/SystemUI/compose/scene/tests/goldens/verticalReveal_gesture_dragFullyClose.json b/packages/SystemUI/compose/scene/tests/goldens/verticalReveal_gesture_dragFullyClose.json
index 0fcdfa3..57f6766 100644
--- a/packages/SystemUI/compose/scene/tests/goldens/verticalReveal_gesture_dragFullyClose.json
+++ b/packages/SystemUI/compose/scene/tests/goldens/verticalReveal_gesture_dragFullyClose.json
@@ -60,7 +60,9 @@
     912,
     928,
     944,
-    960
+    960,
+    976,
+    992
   ],
   "features": [
     {
@@ -310,6 +312,14 @@
         {
           "x": 64,
           "y": 50
+        },
+        {
+          "x": 64,
+          "y": 50
+        },
+        {
+          "x": 64,
+          "y": 50
         }
       ]
     },
@@ -491,67 +501,75 @@
         },
         {
           "width": 170.4,
-          "height": 39.2
+          "height": 28.8
         },
         {
           "width": 166.8,
-          "height": 36
+          "height": 15.2
         },
         {
           "width": 164,
-          "height": 31.6
+          "height": 6.4
         },
         {
           "width": 162.4,
-          "height": 26.8
-        },
-        {
-          "width": 161.2,
-          "height": 22
-        },
-        {
-          "width": 160.4,
-          "height": 17.6
-        },
-        {
-          "width": 160,
-          "height": 14
-        },
-        {
-          "width": 160,
-          "height": 10.8
-        },
-        {
-          "width": 160,
-          "height": 8
-        },
-        {
-          "width": 160,
-          "height": 6
-        },
-        {
-          "width": 160,
-          "height": 4.4
-        },
-        {
-          "width": 160,
-          "height": 2.8
-        },
-        {
-          "width": 160,
-          "height": 2
-        },
-        {
-          "width": 160,
-          "height": 1.2
-        },
-        {
-          "width": 160,
           "height": 0.8
         },
         {
+          "width": 161.2,
+          "height": 0
+        },
+        {
+          "width": 160.4,
+          "height": 0
+        },
+        {
           "width": 160,
-          "height": 0.4
+          "height": 0
+        },
+        {
+          "width": 160,
+          "height": 0
+        },
+        {
+          "width": 160,
+          "height": 0
+        },
+        {
+          "width": 160,
+          "height": 0
+        },
+        {
+          "width": 160,
+          "height": 0
+        },
+        {
+          "width": 160,
+          "height": 0
+        },
+        {
+          "width": 160,
+          "height": 0
+        },
+        {
+          "width": 160,
+          "height": 0
+        },
+        {
+          "width": 160,
+          "height": 0
+        },
+        {
+          "width": 160,
+          "height": 0
+        },
+        {
+          "width": 160,
+          "height": 0
+        },
+        {
+          "width": 160,
+          "height": 0
         },
         {
           "width": 160,
@@ -627,6 +645,8 @@
         0,
         0,
         0,
+        0,
+        0,
         0
       ]
     }
diff --git a/packages/SystemUI/compose/scene/tests/goldens/verticalReveal_gesture_dragHalfClose.json b/packages/SystemUI/compose/scene/tests/goldens/verticalReveal_gesture_dragHalfClose.json
index 3196334..01bc852 100644
--- a/packages/SystemUI/compose/scene/tests/goldens/verticalReveal_gesture_dragHalfClose.json
+++ b/packages/SystemUI/compose/scene/tests/goldens/verticalReveal_gesture_dragHalfClose.json
@@ -59,7 +59,9 @@
     896,
     912,
     928,
-    944
+    944,
+    960,
+    976
   ],
   "features": [
     {
@@ -305,6 +307,14 @@
         {
           "x": 64,
           "y": 50
+        },
+        {
+          "x": 64,
+          "y": 50
+        },
+        {
+          "x": 64,
+          "y": 50
         }
       ]
     },
@@ -482,71 +492,79 @@
         },
         {
           "width": 171.6,
-          "height": 39.6
+          "height": 32
         },
         {
           "width": 167.6,
-          "height": 36.8
+          "height": 18
         },
         {
           "width": 164.8,
-          "height": 32.4
+          "height": 8.8
         },
         {
           "width": 162.8,
-          "height": 27.6
+          "height": 2.8
         },
         {
           "width": 161.6,
-          "height": 22.8
+          "height": 0
         },
         {
           "width": 160.8,
-          "height": 18.4
+          "height": 0
         },
         {
           "width": 160.4,
-          "height": 14.4
+          "height": 0
         },
         {
           "width": 160,
-          "height": 11.2
+          "height": 0
         },
         {
           "width": 160,
-          "height": 8.4
+          "height": 0
         },
         {
           "width": 160,
-          "height": 6.4
+          "height": 0
         },
         {
           "width": 160,
-          "height": 4.4
+          "height": 0
         },
         {
           "width": 160,
-          "height": 3.2
+          "height": 0
         },
         {
           "width": 160,
-          "height": 2
+          "height": 0
         },
         {
           "width": 160,
-          "height": 1.6
+          "height": 0
         },
         {
           "width": 160,
-          "height": 0.8
+          "height": 0
         },
         {
           "width": 160,
-          "height": 0.4
+          "height": 0
         },
         {
           "width": 160,
-          "height": 0.4
+          "height": 0
+        },
+        {
+          "width": 160,
+          "height": 0
+        },
+        {
+          "width": 160,
+          "height": 0
         },
         {
           "width": 160,
@@ -617,6 +635,8 @@
         0,
         0,
         0,
+        0,
+        0,
         0
       ]
     }
diff --git a/packages/SystemUI/compose/scene/tests/goldens/verticalReveal_gesture_dragOpen.json b/packages/SystemUI/compose/scene/tests/goldens/verticalReveal_gesture_dragOpen.json
index 4b03068..b6e423a 100644
--- a/packages/SystemUI/compose/scene/tests/goldens/verticalReveal_gesture_dragOpen.json
+++ b/packages/SystemUI/compose/scene/tests/goldens/verticalReveal_gesture_dragOpen.json
@@ -336,55 +336,55 @@
         },
         {
           "width": 188,
-          "height": 24.8
+          "height": 25.6
         },
         {
           "width": 188,
-          "height": 32.8
+          "height": 36.4
         },
         {
           "width": 188,
-          "height": 44
+          "height": 45.6
         },
         {
           "width": 188,
-          "height": 57.2
+          "height": 59.2
         },
         {
           "width": 188,
-          "height": 70.8
+          "height": 72.8
         },
         {
           "width": 188,
-          "height": 78
+          "height": 79.6
         },
         {
           "width": 188,
-          "height": 91.2
+          "height": 92.8
         },
         {
           "width": 188,
-          "height": 103.2
+          "height": 104.4
         },
         {
           "width": 188,
-          "height": 114.4
+          "height": 115.2
         },
         {
           "width": 188,
-          "height": 124.4
+          "height": 125.2
         },
         {
           "width": 188,
-          "height": 134
+          "height": 134.8
         },
         {
           "width": 188,
-          "height": 142.8
+          "height": 143.2
         },
         {
           "width": 188,
-          "height": 150.8
+          "height": 151.2
         },
         {
           "width": 188,
@@ -392,7 +392,7 @@
         },
         {
           "width": 188,
-          "height": 159.6
+          "height": 160
         },
         {
           "width": 188,
@@ -400,7 +400,7 @@
         },
         {
           "width": 188,
-          "height": 174
+          "height": 174.4
         },
         {
           "width": 188,
@@ -412,7 +412,7 @@
         },
         {
           "width": 188,
-          "height": 187.6
+          "height": 188
         },
         {
           "width": 188,
diff --git a/packages/SystemUI/compose/scene/tests/goldens/verticalReveal_gesture_flingClose.json b/packages/SystemUI/compose/scene/tests/goldens/verticalReveal_gesture_flingClose.json
index 10a9ba7..a82db34 100644
--- a/packages/SystemUI/compose/scene/tests/goldens/verticalReveal_gesture_flingClose.json
+++ b/packages/SystemUI/compose/scene/tests/goldens/verticalReveal_gesture_flingClose.json
@@ -39,7 +39,9 @@
     576,
     592,
     608,
-    624
+    624,
+    640,
+    656
   ],
   "features": [
     {
@@ -205,6 +207,14 @@
         {
           "x": 64,
           "y": 50
+        },
+        {
+          "x": 64,
+          "y": 50
+        },
+        {
+          "x": 64,
+          "y": 50
         }
       ]
     },
@@ -302,67 +312,75 @@
         },
         {
           "width": 170.8,
-          "height": 39.6
+          "height": 29.6
         },
         {
           "width": 166.8,
-          "height": 36.8
+          "height": 12.8
         },
         {
           "width": 164,
-          "height": 32.4
+          "height": 2.4
         },
         {
           "width": 162,
-          "height": 27.6
+          "height": 0
         },
         {
           "width": 160.8,
-          "height": 22.8
+          "height": 0
         },
         {
           "width": 160.4,
-          "height": 18.4
+          "height": 0
         },
         {
           "width": 160,
-          "height": 14.4
+          "height": 0
         },
         {
           "width": 160,
-          "height": 11.2
+          "height": 0
         },
         {
           "width": 160,
-          "height": 8.4
+          "height": 0
         },
         {
           "width": 160,
-          "height": 6
+          "height": 0
         },
         {
           "width": 160,
-          "height": 4.4
+          "height": 0
         },
         {
           "width": 160,
-          "height": 3.2
+          "height": 0
         },
         {
           "width": 160,
-          "height": 2
+          "height": 0
         },
         {
           "width": 160,
-          "height": 1.2
+          "height": 0
         },
         {
           "width": 160,
-          "height": 0.8
+          "height": 0
         },
         {
           "width": 160,
-          "height": 0.4
+          "height": 0
+        },
+        {
+          "width": 160,
+          "height": 0
+        },
+        {
+          "width": 160,
+          "height": 0
         },
         {
           "width": 160,
@@ -417,6 +435,8 @@
         0,
         0,
         0,
+        0,
+        0,
         0
       ]
     }
diff --git a/packages/SystemUI/compose/scene/tests/goldens/verticalReveal_gesture_flingOpen.json b/packages/SystemUI/compose/scene/tests/goldens/verticalReveal_gesture_flingOpen.json
index d8bf48d..6dc5a0e 100644
--- a/packages/SystemUI/compose/scene/tests/goldens/verticalReveal_gesture_flingOpen.json
+++ b/packages/SystemUI/compose/scene/tests/goldens/verticalReveal_gesture_flingOpen.json
@@ -32,8 +32,7 @@
     464,
     480,
     496,
-    512,
-    528
+    512
   ],
   "features": [
     {
@@ -163,10 +162,6 @@
         {
           "x": 50,
           "y": 50
-        },
-        {
-          "x": 50,
-          "y": 50
         }
       ]
     },
@@ -228,63 +223,59 @@
         },
         {
           "width": 188,
-          "height": 42.4
+          "height": 44.4
         },
         {
           "width": 188,
-          "height": 100
+          "height": 103.6
         },
         {
           "width": 188,
-          "height": 161.6
+          "height": 166
         },
         {
           "width": 188,
-          "height": 218
+          "height": 222.4
         },
         {
           "width": 188,
-          "height": 265.6
+          "height": 270
         },
         {
           "width": 188,
-          "height": 303.6
+          "height": 307.2
         },
         {
           "width": 188,
-          "height": 332.4
+          "height": 335.6
         },
         {
           "width": 188,
-          "height": 354
+          "height": 356.4
         },
         {
           "width": 188,
-          "height": 369.2
+          "height": 371.2
         },
         {
           "width": 188,
-          "height": 380
+          "height": 381.6
         },
         {
           "width": 188,
-          "height": 387.2
+          "height": 388.8
         },
         {
           "width": 188,
-          "height": 392
+          "height": 393.2
         },
         {
           "width": 188,
-          "height": 395.2
+          "height": 396
         },
         {
           "width": 188,
-          "height": 397.6
-        },
-        {
-          "width": 188,
-          "height": 398.4
+          "height": 398
         },
         {
           "width": 188,
@@ -356,7 +347,6 @@
         1,
         1,
         1,
-        1,
         1
       ]
     }
diff --git a/packages/SystemUI/compose/scene/tests/goldens/verticalReveal_gesture_magneticDetachAndReattach.json b/packages/SystemUI/compose/scene/tests/goldens/verticalReveal_gesture_magneticDetachAndReattach.json
index 57bdf3e..1cd971a 100644
--- a/packages/SystemUI/compose/scene/tests/goldens/verticalReveal_gesture_magneticDetachAndReattach.json
+++ b/packages/SystemUI/compose/scene/tests/goldens/verticalReveal_gesture_magneticDetachAndReattach.json
@@ -69,9 +69,7 @@
     1056,
     1072,
     1088,
-    1104,
-    1120,
-    1136
+    1104
   ],
   "features": [
     {
@@ -353,14 +351,6 @@
         {
           "x": 64,
           "y": 50
-        },
-        {
-          "x": 64,
-          "y": 50
-        },
-        {
-          "x": 64,
-          "y": 50
         }
       ]
     },
@@ -466,43 +456,43 @@
         },
         {
           "width": 188,
-          "height": 24
+          "height": 24.8
         },
         {
           "width": 188,
-          "height": 29.6
+          "height": 30
         },
         {
           "width": 188,
-          "height": 37.2
+          "height": 38
         },
         {
           "width": 188,
-          "height": 45.6
+          "height": 46
         },
         {
           "width": 188,
-          "height": 53.6
+          "height": 54
         },
         {
           "width": 188,
-          "height": 60.4
+          "height": 61.2
         },
         {
           "width": 188,
-          "height": 66.4
+          "height": 66.8
         },
         {
           "width": 188,
-          "height": 71.2
+          "height": 71.6
         },
         {
           "width": 188,
-          "height": 75.2
+          "height": 75.6
         },
         {
           "width": 188,
-          "height": 77.6
+          "height": 78
         },
         {
           "width": 188,
@@ -510,7 +500,7 @@
         },
         {
           "width": 188,
-          "height": 80.4
+          "height": 80.8
         },
         {
           "width": 188,
@@ -522,7 +512,7 @@
         },
         {
           "width": 188,
-          "height": 79.2
+          "height": 79.6
         },
         {
           "width": 187.6,
@@ -530,7 +520,7 @@
         },
         {
           "width": 186.8,
-          "height": 76
+          "height": 76.4
         },
         {
           "width": 186,
@@ -578,43 +568,39 @@
         },
         {
           "width": 172.4,
-          "height": 39.2
+          "height": 37.6
         },
         {
           "width": 170.8,
-          "height": 38.4
+          "height": 38
         },
         {
           "width": 169.2,
-          "height": 34.8
+          "height": 30.4
         },
         {
           "width": 167.6,
-          "height": 30
-        },
-        {
-          "width": 166,
           "height": 25.2
         },
         {
-          "width": 164,
+          "width": 166,
           "height": 20.4
         },
         {
+          "width": 164,
+          "height": 16
+        },
+        {
           "width": 162.4,
-          "height": 16.4
+          "height": 12.4
         },
         {
           "width": 160.8,
-          "height": 12.8
+          "height": 9.2
         },
         {
           "width": 160,
-          "height": 9.6
-        },
-        {
-          "width": 160,
-          "height": 7.2
+          "height": 6.8
         },
         {
           "width": 160,
@@ -626,7 +612,7 @@
         },
         {
           "width": 160,
-          "height": 2.8
+          "height": 2.4
         },
         {
           "width": 160,
@@ -634,10 +620,6 @@
         },
         {
           "width": 160,
-          "height": 1.2
-        },
-        {
-          "width": 160,
           "height": 0.8
         },
         {
@@ -646,7 +628,7 @@
         },
         {
           "width": 160,
-          "height": 0
+          "height": 0.4
         },
         {
           "width": 160,
@@ -735,8 +717,6 @@
         0.03147719,
         0.019312752,
         0.011740655,
-        0,
-        0,
         0
       ]
     }
diff --git a/packages/SystemUI/compose/scene/tests/goldens/verticalReveal_triggeredRevealCloseTransition.json b/packages/SystemUI/compose/scene/tests/goldens/verticalReveal_triggeredRevealCloseTransition.json
index 9aa91c2..1030455 100644
--- a/packages/SystemUI/compose/scene/tests/goldens/verticalReveal_triggeredRevealCloseTransition.json
+++ b/packages/SystemUI/compose/scene/tests/goldens/verticalReveal_triggeredRevealCloseTransition.json
@@ -27,7 +27,9 @@
     384,
     400,
     416,
-    432
+    432,
+    448,
+    464
   ],
   "features": [
     {
@@ -145,6 +147,14 @@
         {
           "x": 64,
           "y": 50
+        },
+        {
+          "x": 64,
+          "y": 50
+        },
+        {
+          "x": 64,
+          "y": 50
         }
       ]
     },
@@ -194,67 +204,75 @@
         },
         {
           "width": 168.8,
-          "height": 38.4
+          "height": 22.4
         },
         {
           "width": 165.2,
-          "height": 34.8
+          "height": 10
         },
         {
           "width": 162.8,
-          "height": 30
+          "height": 2.4
         },
         {
           "width": 161.2,
-          "height": 25.2
+          "height": 0
         },
         {
           "width": 160.4,
-          "height": 20.4
+          "height": 0
         },
         {
           "width": 160,
-          "height": 16.4
+          "height": 0
         },
         {
           "width": 160,
-          "height": 12.8
+          "height": 0
         },
         {
           "width": 160,
-          "height": 9.6
+          "height": 0
         },
         {
           "width": 160,
-          "height": 7.2
+          "height": 0
         },
         {
           "width": 160,
-          "height": 5.2
+          "height": 0
         },
         {
           "width": 160,
-          "height": 3.6
+          "height": 0
         },
         {
           "width": 160,
-          "height": 2.8
+          "height": 0
         },
         {
           "width": 160,
-          "height": 1.6
+          "height": 0
         },
         {
           "width": 160,
-          "height": 1.2
+          "height": 0
         },
         {
           "width": 160,
-          "height": 0.8
+          "height": 0
         },
         {
           "width": 160,
-          "height": 0.4
+          "height": 0
+        },
+        {
+          "width": 160,
+          "height": 0
+        },
+        {
+          "width": 160,
+          "height": 0
         },
         {
           "width": 160,
@@ -297,6 +315,8 @@
         0,
         0,
         0,
+        0,
+        0,
         0
       ]
     }
diff --git a/packages/SystemUI/multivalentTests/src/com/android/keyguard/KeyguardPinBasedInputViewControllerTest.java b/packages/SystemUI/multivalentTests/src/com/android/keyguard/KeyguardPinBasedInputViewControllerTest.java
index e26e19d..29647cd 100644
--- a/packages/SystemUI/multivalentTests/src/com/android/keyguard/KeyguardPinBasedInputViewControllerTest.java
+++ b/packages/SystemUI/multivalentTests/src/com/android/keyguard/KeyguardPinBasedInputViewControllerTest.java
@@ -16,12 +16,18 @@
 
 package com.android.keyguard;
 
+import static com.android.internal.widget.flags.Flags.FLAG_HIDE_LAST_CHAR_WITH_PHYSICAL_INPUT;
+
 import static com.google.common.truth.Truth.assertThat;
 
 import static org.mockito.ArgumentMatchers.any;
+import static org.mockito.ArgumentMatchers.anyInt;
 import static org.mockito.Mockito.verify;
+import static org.mockito.Mockito.times;
 import static org.mockito.Mockito.when;
 
+import android.hardware.input.InputManager;
+import android.platform.test.annotations.EnableFlags;
 import android.testing.TestableLooper.RunWithLooper;
 import android.view.View;
 import android.view.ViewGroup;
@@ -90,6 +96,8 @@
     @Mock
     private UserActivityNotifier mUserActivityNotifier;
     private NumPadKey[] mButtons = new NumPadKey[]{};
+    @Mock
+    private InputManager mInputManager;
 
     private KeyguardPinBasedInputViewController mKeyguardPinViewController;
 
@@ -118,12 +126,13 @@
                 new KeyguardKeyboardInteractor(new FakeKeyboardRepository());
         FakeFeatureFlags featureFlags = new FakeFeatureFlags();
         mSetFlagsRule.enableFlags(com.android.systemui.Flags.FLAG_REVAMPED_BOUNCER_MESSAGES);
+        when(mLockPatternUtils.isPinEnhancedPrivacyEnabled(anyInt())).thenReturn(false);
         mKeyguardPinViewController = new KeyguardPinBasedInputViewController(mPinBasedInputView,
                 mKeyguardUpdateMonitor, mSecurityMode, mLockPatternUtils, mKeyguardSecurityCallback,
                 mKeyguardMessageAreaControllerFactory, mLatencyTracker,
                 mEmergencyButtonController, mFalsingCollector, featureFlags,
                 mSelectedUserInteractor, keyguardKeyboardInteractor, mBouncerHapticPlayer,
-                mUserActivityNotifier) {
+                mUserActivityNotifier, mInputManager) {
             @Override
             public void onResume(int reason) {
                 super.onResume(reason);
@@ -148,4 +157,112 @@
         mKeyguardPinViewController.resetState();
         verify(mKeyguardMessageAreaController).setMessage(R.string.keyguard_enter_your_pin);
     }
+
+    @Test
+    @EnableFlags(FLAG_HIDE_LAST_CHAR_WITH_PHYSICAL_INPUT)
+    public void updateAnimations_addDevice_notKeyboard() {
+        when(mLockPatternUtils.isPinEnhancedPrivacyEnabled(anyInt())).thenReturn(false);
+        verify(mPasswordEntry, times(1)).setShowPassword(true);
+        mKeyguardPinViewController.onViewAttached();
+        mKeyguardPinViewController.onInputDeviceAdded(1);
+        verify(mPasswordEntry, times(1)).setShowPassword(true);
+    }
+
+    @Test
+    @EnableFlags(FLAG_HIDE_LAST_CHAR_WITH_PHYSICAL_INPUT)
+    public void updateAnimations_addDevice_keyboard() {
+        when(mLockPatternUtils.isPinEnhancedPrivacyEnabled(anyInt())).thenReturn(true);
+        verify(mPasswordEntry, times(1)).setShowPassword(true);
+        mKeyguardPinViewController.onViewAttached();
+        mKeyguardPinViewController.onInputDeviceAdded(1);
+        verify(mPasswordEntry, times(1)).setShowPassword(false);
+    }
+
+    @Test
+    @EnableFlags(FLAG_HIDE_LAST_CHAR_WITH_PHYSICAL_INPUT)
+    public void updateAnimations_addDevice_multipleKeyboards() {
+        when(mLockPatternUtils.isPinEnhancedPrivacyEnabled(anyInt())).thenReturn(true);
+        verify(mPasswordEntry, times(1)).setShowPassword(true);
+        mKeyguardPinViewController.onViewAttached();
+        mKeyguardPinViewController.onInputDeviceAdded(1);
+        verify(mPasswordEntry, times(1)).setShowPassword(false);
+        mKeyguardPinViewController.onInputDeviceAdded(1);
+        verify(mPasswordEntry, times(1)).setShowPassword(false);
+    }
+
+    @Test
+    @EnableFlags(FLAG_HIDE_LAST_CHAR_WITH_PHYSICAL_INPUT)
+    public void updateAnimations_removeDevice_notKeyboard() {
+        when(mLockPatternUtils.isPinEnhancedPrivacyEnabled(anyInt())).thenReturn(false);
+        verify(mPasswordEntry, times(1)).setShowPassword(true);
+        mKeyguardPinViewController.onViewAttached();
+        mKeyguardPinViewController.onInputDeviceRemoved(1);
+        verify(mPasswordEntry, times(1)).setShowPassword(true);
+    }
+
+    @Test
+    @EnableFlags(FLAG_HIDE_LAST_CHAR_WITH_PHYSICAL_INPUT)
+    public void updateAnimations_removeDevice_keyboard() {
+        when(mLockPatternUtils.isPinEnhancedPrivacyEnabled(anyInt())).thenReturn(true, false);
+        verify(mPasswordEntry, times(1)).setShowPassword(true);
+        verify(mPasswordEntry, times(0)).setShowPassword(false);
+        mKeyguardPinViewController.onViewAttached();
+        verify(mPasswordEntry, times(1)).setShowPassword(true);
+        verify(mPasswordEntry, times(1)).setShowPassword(false);
+        mKeyguardPinViewController.onInputDeviceRemoved(1);
+        verify(mPasswordEntry, times(2)).setShowPassword(true);
+        verify(mPasswordEntry, times(1)).setShowPassword(false);
+    }
+
+    @Test
+    @EnableFlags(FLAG_HIDE_LAST_CHAR_WITH_PHYSICAL_INPUT)
+    public void updateAnimations_removeDevice_multipleKeyboards() {
+        when(mLockPatternUtils.isPinEnhancedPrivacyEnabled(anyInt())).thenReturn(true, true);
+        verify(mPasswordEntry, times(1)).setShowPassword(true);
+        verify(mPasswordEntry, times(0)).setShowPassword(false);
+        mKeyguardPinViewController.onViewAttached();
+        verify(mPasswordEntry, times(1)).setShowPassword(true);
+        verify(mPasswordEntry, times(1)).setShowPassword(false);
+        mKeyguardPinViewController.onInputDeviceRemoved(1);
+        verify(mPasswordEntry, times(1)).setShowPassword(true);
+        verify(mPasswordEntry, times(1)).setShowPassword(false);
+    }
+
+    @Test
+    @EnableFlags(FLAG_HIDE_LAST_CHAR_WITH_PHYSICAL_INPUT)
+    public void updateAnimations_updateDevice_notKeyboard() {
+        when(mLockPatternUtils.isPinEnhancedPrivacyEnabled(anyInt())).thenReturn(false);
+        verify(mPasswordEntry, times(1)).setShowPassword(true);
+        mKeyguardPinViewController.onViewAttached();
+        mKeyguardPinViewController.onInputDeviceChanged(1);
+        verify(mPasswordEntry, times(1)).setShowPassword(true);
+    }
+
+    @Test
+    @EnableFlags(FLAG_HIDE_LAST_CHAR_WITH_PHYSICAL_INPUT)
+    public void updateAnimations_updateDevice_keyboard() {
+        when(mLockPatternUtils.isPinEnhancedPrivacyEnabled(anyInt())).thenReturn(true, false);
+        verify(mPasswordEntry, times(1)).setShowPassword(true);
+        verify(mPasswordEntry, times(0)).setShowPassword(false);
+        mKeyguardPinViewController.onViewAttached();
+        verify(mPasswordEntry, times(1)).setShowPassword(true);
+        verify(mPasswordEntry, times(1)).setShowPassword(false);
+        mKeyguardPinViewController.onInputDeviceChanged(1);
+        verify(mPasswordEntry, times(2)).setShowPassword(true);
+        verify(mPasswordEntry, times(1)).setShowPassword(false);
+    }
+
+    @Test
+    @EnableFlags(FLAG_HIDE_LAST_CHAR_WITH_PHYSICAL_INPUT)
+    public void updateAnimations_updateDevice_multipleKeyboards() {
+        when(mLockPatternUtils.isPinEnhancedPrivacyEnabled(anyInt())).thenReturn(true, true);
+        verify(mPasswordEntry, times(1)).setShowPassword(true);
+        verify(mPasswordEntry, times(0)).setShowPassword(false);
+        mKeyguardPinViewController.onViewAttached();
+        verify(mPasswordEntry, times(1)).setShowPassword(true);
+        verify(mPasswordEntry, times(1)).setShowPassword(false);
+        mKeyguardPinViewController.onInputDeviceChanged(1);
+        verify(mPasswordEntry, times(1)).setShowPassword(true);
+        verify(mPasswordEntry, times(1)).setShowPassword(false);
+    }
 }
diff --git a/packages/SystemUI/multivalentTests/src/com/android/keyguard/KeyguardPinViewControllerTest.kt b/packages/SystemUI/multivalentTests/src/com/android/keyguard/KeyguardPinViewControllerTest.kt
index 142a286..7fea06e 100644
--- a/packages/SystemUI/multivalentTests/src/com/android/keyguard/KeyguardPinViewControllerTest.kt
+++ b/packages/SystemUI/multivalentTests/src/com/android/keyguard/KeyguardPinViewControllerTest.kt
@@ -16,6 +16,7 @@
 
 package com.android.keyguard
 
+import android.hardware.input.InputManager
 import android.testing.TestableLooper
 import android.view.View
 import android.view.ViewGroup
@@ -104,6 +105,7 @@
     @Mock lateinit var enterButton: View
     @Mock lateinit var uiEventLogger: UiEventLogger
     @Mock lateinit var mUserActivityNotifier: UserActivityNotifier
+    @Mock lateinit var inputManager: InputManager
 
     @Captor lateinit var postureCallbackCaptor: ArgumentCaptor<DevicePostureController.Callback>
 
@@ -154,6 +156,7 @@
             keyguardKeyboardInteractor,
             kosmos.bouncerHapticPlayer,
             mUserActivityNotifier,
+            inputManager,
         )
     }
 
diff --git a/packages/SystemUI/multivalentTests/src/com/android/keyguard/KeyguardSimPinViewControllerTest.kt b/packages/SystemUI/multivalentTests/src/com/android/keyguard/KeyguardSimPinViewControllerTest.kt
index c751a7d..003669d 100644
--- a/packages/SystemUI/multivalentTests/src/com/android/keyguard/KeyguardSimPinViewControllerTest.kt
+++ b/packages/SystemUI/multivalentTests/src/com/android/keyguard/KeyguardSimPinViewControllerTest.kt
@@ -16,6 +16,7 @@
 
 package com.android.keyguard
 
+import android.hardware.input.InputManager
 import android.telephony.TelephonyManager
 import android.testing.TestableLooper
 import android.view.LayoutInflater
@@ -73,6 +74,7 @@
     @Mock private lateinit var mUserActivityNotifier: UserActivityNotifier
     private val updateMonitorCallbackArgumentCaptor =
         ArgumentCaptor.forClass(KeyguardUpdateMonitorCallback::class.java)
+    @Mock private lateinit var inputManager: InputManager
 
     private val kosmos = testKosmos()
 
@@ -107,6 +109,7 @@
                 keyguardKeyboardInteractor,
                 kosmos.bouncerHapticPlayer,
                 mUserActivityNotifier,
+                inputManager,
             )
         underTest.init()
         underTest.onViewAttached()
diff --git a/packages/SystemUI/multivalentTests/src/com/android/keyguard/KeyguardSimPukViewControllerTest.kt b/packages/SystemUI/multivalentTests/src/com/android/keyguard/KeyguardSimPukViewControllerTest.kt
index c346825..85cb388 100644
--- a/packages/SystemUI/multivalentTests/src/com/android/keyguard/KeyguardSimPukViewControllerTest.kt
+++ b/packages/SystemUI/multivalentTests/src/com/android/keyguard/KeyguardSimPukViewControllerTest.kt
@@ -16,6 +16,7 @@
 
 package com.android.keyguard
 
+import android.hardware.input.InputManager
 import android.telephony.PinResult
 import android.telephony.TelephonyManager
 import android.testing.TestableLooper
@@ -65,6 +66,7 @@
     private lateinit var keyguardMessageAreaController:
         KeyguardMessageAreaController<BouncerKeyguardMessageArea>
     @Mock private lateinit var mUserActivityNotifier: UserActivityNotifier
+    @Mock private lateinit var inputManager: InputManager
 
     private val kosmos = testKosmos()
 
@@ -102,6 +104,7 @@
                 keyguardKeyboardInteractor,
                 kosmos.bouncerHapticPlayer,
                 mUserActivityNotifier,
+                inputManager,
             )
         underTest.init()
     }
diff --git a/packages/SystemUI/multivalentTests/src/com/android/systemui/keyevent/domain/interactor/KeyEventInteractorTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/keyevent/domain/interactor/KeyEventInteractorTest.kt
index 684af6f..efc68f3 100644
--- a/packages/SystemUI/multivalentTests/src/com/android/systemui/keyevent/domain/interactor/KeyEventInteractorTest.kt
+++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/keyevent/domain/interactor/KeyEventInteractorTest.kt
@@ -64,10 +64,10 @@
             val isPowerButtonLongPressed by collectLastValue(
                 underTest.isPowerButtonLongPressed)
 
-            repository.setPowerButtonBeingLongPressed(false)
+            repository.setPowerButtonLongPressed(false)
             assertThat(isPowerButtonLongPressed).isFalse()
 
-            repository.setPowerButtonBeingLongPressed(true)
+            repository.setPowerButtonLongPressed(true)
             assertThat(isPowerButtonLongPressed).isTrue()
         }
 }
diff --git a/packages/SystemUI/multivalentTests/src/com/android/systemui/shade/ui/viewmodel/ShadeHeaderViewModelTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/shade/ui/viewmodel/ShadeHeaderViewModelTest.kt
index a832f48..04eb709 100644
--- a/packages/SystemUI/multivalentTests/src/com/android/systemui/shade/ui/viewmodel/ShadeHeaderViewModelTest.kt
+++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/shade/ui/viewmodel/ShadeHeaderViewModelTest.kt
@@ -94,6 +94,36 @@
         }
 
     @Test
+    fun showClock_wideLayout_returnsTrue() =
+        testScope.runTest {
+            kosmos.enableDualShade(wideLayout = true)
+
+            setupDualShadeState(scene = Scenes.Lockscreen, overlay = Overlays.NotificationsShade)
+            assertThat(underTest.showClock).isTrue()
+
+            setupDualShadeState(scene = Scenes.Lockscreen, overlay = Overlays.QuickSettingsShade)
+            assertThat(underTest.showClock).isTrue()
+        }
+
+    @Test
+    fun showClock_narrowLayoutOnNotificationsShade_returnsFalse() =
+        testScope.runTest {
+            kosmos.enableDualShade(wideLayout = false)
+            setupDualShadeState(scene = Scenes.Lockscreen, overlay = Overlays.NotificationsShade)
+
+            assertThat(underTest.showClock).isFalse()
+        }
+
+    @Test
+    fun showClock_narrowLayoutOnQuickSettingsShade_returnsTrue() =
+        testScope.runTest {
+            kosmos.enableDualShade(wideLayout = false)
+            setupDualShadeState(scene = Scenes.Lockscreen, overlay = Overlays.QuickSettingsShade)
+
+            assertThat(underTest.showClock).isTrue()
+        }
+
+    @Test
     fun onShadeCarrierGroupClicked_launchesNetworkSettings() =
         testScope.runTest {
             val activityStarter = kosmos.activityStarter
diff --git a/packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/NotificationGroupingUtilTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/NotificationGroupingUtilTest.kt
new file mode 100644
index 0000000..e04162b
--- /dev/null
+++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/NotificationGroupingUtilTest.kt
@@ -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 com.android.systemui.statusbar
+
+import android.platform.test.flag.junit.FlagsParameterization
+import androidx.test.filters.SmallTest
+import com.android.systemui.SysuiTestCase
+import com.android.systemui.statusbar.notification.row.NotificationTestHelper
+import com.android.systemui.statusbar.notification.shared.NotificationBundleUi
+import com.google.common.truth.Truth.assertThat
+import org.junit.Before
+import org.junit.Test
+import org.junit.runner.RunWith
+import platform.test.runner.parameterized.ParameterizedAndroidJunit4
+import platform.test.runner.parameterized.Parameters
+
+@SmallTest
+@RunWith(ParameterizedAndroidJunit4::class)
+class NotificationGroupingUtilTest(flags: FlagsParameterization) : SysuiTestCase() {
+
+    private lateinit var underTest: NotificationGroupingUtil
+
+    private lateinit var testHelper: NotificationTestHelper
+
+    companion object {
+        @JvmStatic
+        @Parameters(name = "{0}")
+        fun getParams(): List<FlagsParameterization> {
+            return FlagsParameterization.allCombinationsOf(NotificationBundleUi.FLAG_NAME)
+        }
+    }
+
+    init {
+        mSetFlagsRule.setFlagsParameterization(flags)
+    }
+
+    @Before
+    fun setup() {
+        testHelper = NotificationTestHelper(mContext, mDependency)
+    }
+
+    @Test
+    fun showsTime() {
+        val row = testHelper.createRow()
+
+        underTest = NotificationGroupingUtil(row)
+        assertThat(underTest.showsTime(row)).isTrue()
+    }
+}
\ No newline at end of file
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 b085ba4..485b9fe 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
@@ -19,8 +19,8 @@
 import android.app.PendingIntent
 import android.platform.test.annotations.DisableFlags
 import android.platform.test.annotations.EnableFlags
+import android.platform.test.flag.junit.FlagsParameterization
 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
@@ -33,13 +33,16 @@
 import com.android.systemui.plugins.activityStarter
 import com.android.systemui.res.R
 import com.android.systemui.statusbar.StatusBarIconView
+import com.android.systemui.statusbar.chips.StatusBarChipsReturnAnimations
 import com.android.systemui.statusbar.chips.ui.model.ColorsModel
 import com.android.systemui.statusbar.chips.ui.model.OngoingActivityChipModel
 import com.android.systemui.statusbar.chips.ui.view.ChipBackgroundContainer
 import com.android.systemui.statusbar.core.StatusBarConnectedDisplays
+import com.android.systemui.statusbar.core.StatusBarRootModernization
 import com.android.systemui.statusbar.notification.promoted.shared.model.PromotedNotificationContentModel
 import com.android.systemui.statusbar.phone.ongoingcall.DisableChipsModernization
 import com.android.systemui.statusbar.phone.ongoingcall.EnableChipsModernization
+import com.android.systemui.statusbar.phone.ongoingcall.StatusBarChipsModernization
 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
@@ -51,10 +54,16 @@
 import org.mockito.kotlin.mock
 import org.mockito.kotlin.verify
 import org.mockito.kotlin.whenever
+import platform.test.runner.parameterized.ParameterizedAndroidJunit4
+import platform.test.runner.parameterized.Parameters
 
 @SmallTest
-@RunWith(AndroidJUnit4::class)
-class CallChipViewModelTest : SysuiTestCase() {
+@RunWith(ParameterizedAndroidJunit4::class)
+class CallChipViewModelTest(flags: FlagsParameterization) : SysuiTestCase() {
+    init {
+        mSetFlagsRule.setFlagsParameterization(flags)
+    }
+
     private val kosmos = testKosmos().useUnconfinedTestDispatcher()
 
     private val chipBackgroundView = mock<ChipBackgroundContainer>()
@@ -87,9 +96,10 @@
         kosmos.runTest {
             val latest by collectLastValue(underTest.chip)
 
-            addOngoingCallState(startTimeMs = 0)
+            addOngoingCallState(startTimeMs = 0, isAppVisible = false)
 
             assertThat(latest).isInstanceOf(OngoingActivityChipModel.Active.IconOnly::class.java)
+            assertThat((latest as OngoingActivityChipModel.Active).isHidden).isFalse()
         }
 
     @Test
@@ -97,9 +107,10 @@
         kosmos.runTest {
             val latest by collectLastValue(underTest.chip)
 
-            addOngoingCallState(startTimeMs = -2)
+            addOngoingCallState(startTimeMs = -2, isAppVisible = false)
 
             assertThat(latest).isInstanceOf(OngoingActivityChipModel.Active.IconOnly::class.java)
+            assertThat((latest as OngoingActivityChipModel.Active).isHidden).isFalse()
         }
 
     @Test
@@ -107,9 +118,82 @@
         kosmos.runTest {
             val latest by collectLastValue(underTest.chip)
 
-            addOngoingCallState(startTimeMs = 345)
+            addOngoingCallState(startTimeMs = 345, isAppVisible = false)
 
             assertThat(latest).isInstanceOf(OngoingActivityChipModel.Active.Timer::class.java)
+            assertThat((latest as OngoingActivityChipModel.Active).isHidden).isFalse()
+        }
+
+    @Test
+    @DisableFlags(StatusBarChipsReturnAnimations.FLAG_NAME)
+    fun chipLegacy_inCallWithVisibleApp_zeroStartTime_isHiddenAsInactive() =
+        kosmos.runTest {
+            val latest by collectLastValue(underTest.chip)
+
+            addOngoingCallState(startTimeMs = 0, isAppVisible = true)
+
+            assertThat(latest).isInstanceOf(OngoingActivityChipModel.Inactive::class.java)
+        }
+
+    @Test
+    @EnableFlags(StatusBarChipsReturnAnimations.FLAG_NAME)
+    @EnableChipsModernization
+    fun chipWithReturnAnimation_inCallWithVisibleApp_zeroStartTime_isHiddenAsIconOnly() =
+        kosmos.runTest {
+            val latest by collectLastValue(underTest.chip)
+
+            addOngoingCallState(startTimeMs = 0, isAppVisible = true)
+
+            assertThat(latest).isInstanceOf(OngoingActivityChipModel.Active.IconOnly::class.java)
+            assertThat((latest as OngoingActivityChipModel.Active).isHidden).isTrue()
+        }
+
+    @Test
+    @DisableFlags(StatusBarChipsReturnAnimations.FLAG_NAME)
+    fun chipLegacy_inCallWithVisibleApp_negativeStartTime_isHiddenAsInactive() =
+        kosmos.runTest {
+            val latest by collectLastValue(underTest.chip)
+
+            addOngoingCallState(startTimeMs = -2, isAppVisible = true)
+
+            assertThat(latest).isInstanceOf(OngoingActivityChipModel.Inactive::class.java)
+        }
+
+    @Test
+    @EnableFlags(StatusBarChipsReturnAnimations.FLAG_NAME)
+    @EnableChipsModernization
+    fun chipWithReturnAnimation_inCallWithVisibleApp_negativeStartTime_isHiddenAsIconOnly() =
+        kosmos.runTest {
+            val latest by collectLastValue(underTest.chip)
+
+            addOngoingCallState(startTimeMs = -2, isAppVisible = true)
+
+            assertThat(latest).isInstanceOf(OngoingActivityChipModel.Active.IconOnly::class.java)
+            assertThat((latest as OngoingActivityChipModel.Active).isHidden).isTrue()
+        }
+
+    @Test
+    @DisableFlags(StatusBarChipsReturnAnimations.FLAG_NAME)
+    fun chipLegacy_inCallWithVisibleApp_positiveStartTime_isHiddenAsInactive() =
+        kosmos.runTest {
+            val latest by collectLastValue(underTest.chip)
+
+            addOngoingCallState(startTimeMs = 345, isAppVisible = true)
+
+            assertThat(latest).isInstanceOf(OngoingActivityChipModel.Inactive::class.java)
+        }
+
+    @Test
+    @EnableFlags(StatusBarChipsReturnAnimations.FLAG_NAME)
+    @EnableChipsModernization
+    fun chipWithReturnAnimation_inCallWithVisibleApp_positiveStartTime_isHiddenAsTimer() =
+        kosmos.runTest {
+            val latest by collectLastValue(underTest.chip)
+
+            addOngoingCallState(startTimeMs = 345, isAppVisible = true)
+
+            assertThat(latest).isInstanceOf(OngoingActivityChipModel.Active.Timer::class.java)
+            assertThat((latest as OngoingActivityChipModel.Active).isHidden).isTrue()
         }
 
     @Test
@@ -418,5 +502,18 @@
 
         private const val PROMOTED_BACKGROUND_COLOR = 65
         private const val PROMOTED_PRIMARY_TEXT_COLOR = 98
+
+        @get:Parameters(name = "{0}")
+        @JvmStatic
+        val flags: List<FlagsParameterization>
+            get() = buildList {
+                addAll(
+                    FlagsParameterization.allCombinationsOf(
+                        StatusBarRootModernization.FLAG_NAME,
+                        StatusBarChipsModernization.FLAG_NAME,
+                        StatusBarChipsReturnAnimations.FLAG_NAME,
+                    )
+                )
+            }
     }
 }
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 e2d1498..e39fa70 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
@@ -137,7 +137,7 @@
         kosmos.runTest {
             screenRecordState.value = ScreenRecordModel.Recording
 
-            addOngoingCallState()
+            addOngoingCallState(isAppVisible = false)
 
             val latest by collectLastValue(underTest.primaryChip)
 
@@ -163,7 +163,7 @@
             screenRecordState.value = ScreenRecordModel.DoingNothing
             mediaProjectionState.value =
                 MediaProjectionState.Projecting.EntireScreen(NORMAL_PACKAGE)
-            addOngoingCallState()
+            addOngoingCallState(isAppVisible = false)
 
             val latest by collectLastValue(underTest.primaryChip)
 
@@ -178,7 +178,7 @@
             // MediaProjection covers both share-to-app and cast-to-other-device
             mediaProjectionState.value = MediaProjectionState.NotProjecting
 
-            addOngoingCallState(key = notificationKey)
+            addOngoingCallState(key = notificationKey, isAppVisible = false)
 
             val latest by collectLastValue(underTest.primaryChip)
 
@@ -190,7 +190,7 @@
         kosmos.runTest {
             // Start with just the lowest priority chip shown
             val callNotificationKey = "call"
-            addOngoingCallState(key = callNotificationKey)
+            addOngoingCallState(key = callNotificationKey, isAppVisible = false)
             // And everything else hidden
             mediaProjectionState.value = MediaProjectionState.NotProjecting
             screenRecordState.value = ScreenRecordModel.DoingNothing
@@ -225,7 +225,7 @@
             mediaProjectionState.value =
                 MediaProjectionState.Projecting.EntireScreen(NORMAL_PACKAGE)
             val callNotificationKey = "call"
-            addOngoingCallState(key = callNotificationKey)
+            addOngoingCallState(key = callNotificationKey, isAppVisible = false)
 
             val latest by collectLastValue(underTest.primaryChip)
 
diff --git a/packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/chips/ui/viewmodel/OngoingActivityChipsWithNotifsViewModelTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/chips/ui/viewmodel/OngoingActivityChipsWithNotifsViewModelTest.kt
index 670ebadc..54e9f5f 100644
--- a/packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/chips/ui/viewmodel/OngoingActivityChipsWithNotifsViewModelTest.kt
+++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/chips/ui/viewmodel/OngoingActivityChipsWithNotifsViewModelTest.kt
@@ -236,7 +236,7 @@
     fun primaryChip_screenRecordShowAndCallShow_screenRecordShown() =
         kosmos.runTest {
             screenRecordState.value = ScreenRecordModel.Recording
-            addOngoingCallState("call")
+            addOngoingCallState("call", isAppVisible = false)
 
             val latest by collectLastValue(underTest.primaryChip)
 
@@ -249,7 +249,7 @@
         kosmos.runTest {
             val callNotificationKey = "call"
             screenRecordState.value = ScreenRecordModel.Recording
-            addOngoingCallState(callNotificationKey)
+            addOngoingCallState(callNotificationKey, isAppVisible = false)
 
             val latest by collectLastValue(underTest.chipsLegacy)
             val unused by collectLastValue(underTest.chips)
@@ -296,7 +296,7 @@
     @Test
     fun chipsLegacy_oneChip_notSquished() =
         kosmos.runTest {
-            addOngoingCallState()
+            addOngoingCallState(isAppVisible = false)
 
             val latest by collectLastValue(underTest.chipsLegacy)
 
@@ -323,7 +323,7 @@
     fun chipsLegacy_twoTimerChips_isSmallPortrait_bothSquished() =
         kosmos.runTest {
             screenRecordState.value = ScreenRecordModel.Recording
-            addOngoingCallState(key = "call")
+            addOngoingCallState(key = "call", isAppVisible = false)
 
             val latest by collectLastValue(underTest.chipsLegacy)
 
@@ -385,7 +385,7 @@
     fun chipsLegacy_countdownChipAndTimerChip_countdownNotSquished_butTimerSquished() =
         kosmos.runTest {
             screenRecordState.value = ScreenRecordModel.Starting(millisUntilStarted = 2000)
-            addOngoingCallState(key = "call")
+            addOngoingCallState(key = "call", isAppVisible = false)
 
             val latest by collectLastValue(underTest.chipsLegacy)
 
@@ -431,7 +431,7 @@
                 .isInstanceOf(OngoingActivityChipModel.Inactive::class.java)
 
             // WHEN there's 2 chips
-            addOngoingCallState(key = "call")
+            addOngoingCallState(key = "call", isAppVisible = false)
 
             // THEN they both become squished
             assertThat(latest!!.primary)
@@ -487,7 +487,7 @@
     fun chipsLegacy_twoChips_isLandscape_notSquished() =
         kosmos.runTest {
             screenRecordState.value = ScreenRecordModel.Recording
-            addOngoingCallState(key = "call")
+            addOngoingCallState(key = "call", isAppVisible = false)
 
             // WHEN we're in landscape
             val config =
@@ -533,7 +533,7 @@
     fun chipsLegacy_twoChips_isLargeScreen_notSquished() =
         kosmos.runTest {
             screenRecordState.value = ScreenRecordModel.Recording
-            addOngoingCallState(key = "call")
+            addOngoingCallState(key = "call", isAppVisible = false)
 
             // WHEN we're on a large screen
             kosmos.displayStateRepository.setIsLargeScreen(true)
@@ -627,7 +627,7 @@
             screenRecordState.value = ScreenRecordModel.DoingNothing
             mediaProjectionState.value =
                 MediaProjectionState.Projecting.EntireScreen(NORMAL_PACKAGE)
-            addOngoingCallState(key = "call")
+            addOngoingCallState(key = "call", isAppVisible = false)
 
             val latest by collectLastValue(underTest.primaryChip)
 
@@ -642,7 +642,7 @@
             screenRecordState.value = ScreenRecordModel.DoingNothing
             mediaProjectionState.value =
                 MediaProjectionState.Projecting.EntireScreen(NORMAL_PACKAGE)
-            addOngoingCallState(key = "call")
+            addOngoingCallState(key = "call", isAppVisible = false)
 
             val latest by collectLastValue(underTest.chipsLegacy)
             val unused by collectLastValue(underTest.chips)
@@ -681,7 +681,7 @@
             mediaProjectionState.value = MediaProjectionState.NotProjecting
 
             val callNotificationKey = "call"
-            addOngoingCallState(key = callNotificationKey)
+            addOngoingCallState(key = callNotificationKey, isAppVisible = false)
 
             val latest by collectLastValue(underTest.primaryChip)
 
@@ -697,7 +697,7 @@
             // MediaProjection covers both share-to-app and cast-to-other-device
             mediaProjectionState.value = MediaProjectionState.NotProjecting
 
-            addOngoingCallState(key = callNotificationKey)
+            addOngoingCallState(key = callNotificationKey, isAppVisible = false)
 
             val latest by collectLastValue(underTest.chipsLegacy)
             val unused by collectLastValue(underTest.chips)
@@ -975,7 +975,7 @@
             val unused by collectLastValue(underTest.chips)
 
             val callNotificationKey = "call"
-            addOngoingCallState(callNotificationKey)
+            addOngoingCallState(callNotificationKey, isAppVisible = false)
 
             val firstIcon = createStatusBarIconViewOrNull()
             activeNotificationListRepository.addNotifs(
@@ -1053,7 +1053,7 @@
             val latest by collectLastValue(underTest.chipsLegacy)
             val unused by collectLastValue(underTest.chips)
 
-            addOngoingCallState(callNotificationKey)
+            addOngoingCallState(callNotificationKey, isAppVisible = false)
             screenRecordState.value = ScreenRecordModel.Recording
             activeNotificationListRepository.addNotif(
                 activeNotificationModel(
@@ -1160,7 +1160,7 @@
             assertIsNotifChip(latest, context, notifIcon, "notif")
 
             // WHEN the higher priority call chip is added
-            addOngoingCallState(callNotificationKey)
+            addOngoingCallState(callNotificationKey, isAppVisible = false)
 
             // THEN the higher priority call chip is used
             assertIsCallChip(latest, callNotificationKey, context)
@@ -1191,7 +1191,7 @@
             screenRecordState.value = ScreenRecordModel.Recording
             mediaProjectionState.value =
                 MediaProjectionState.Projecting.EntireScreen(NORMAL_PACKAGE)
-            addOngoingCallState(callNotificationKey)
+            addOngoingCallState(callNotificationKey, isAppVisible = false)
             val notifIcon = createStatusBarIconViewOrNull()
             activeNotificationListRepository.addNotif(
                 activeNotificationModel(
@@ -1253,7 +1253,7 @@
             assertThat(unused).isEqualTo(MultipleOngoingActivityChipsModel())
 
             // WHEN the higher priority call chip is added
-            addOngoingCallState(callNotificationKey)
+            addOngoingCallState(callNotificationKey, isAppVisible = false)
 
             // THEN the higher priority call chip is used as primary and notif is demoted to
             // secondary
diff --git a/packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/notification/collection/coordinator/BundleCoordinatorTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/notification/collection/coordinator/BundleCoordinatorTest.kt
index 8a9720e..7321808 100644
--- a/packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/notification/collection/coordinator/BundleCoordinatorTest.kt
+++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/notification/collection/coordinator/BundleCoordinatorTest.kt
@@ -34,6 +34,7 @@
 import org.junit.runner.RunWith
 import org.mockito.Mock
 import org.mockito.MockitoAnnotations
+import kotlin.test.assertEquals
 
 @SmallTest
 @RunWith(AndroidJUnit4::class)
@@ -87,6 +88,18 @@
         isFalse()
     }
 
+    @Test
+    fun testBundler_getBundleIdOrNull_returnBundleId() {
+        val classifiedEntry = makeEntryOfChannelType(PROMOTIONS_ID)
+        assertEquals(coordinator.bundler.getBundleIdOrNull(classifiedEntry), PROMOTIONS_ID)
+    }
+
+    @Test
+    fun testBundler_getBundleIdOrNull_returnNull() {
+        val unclassifiedEntry = makeEntryOfChannelType("not system channel")
+        assertEquals(coordinator.bundler.getBundleIdOrNull(unclassifiedEntry), null)
+    }
+
     private fun makeEntryOfChannelType(
         type: String,
         buildBlock: NotificationEntryBuilder.() -> Unit = {}
diff --git a/packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/notification/collection/coordinator/LockScreenMinimalismCoordinatorTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/notification/collection/coordinator/LockScreenMinimalismCoordinatorTest.kt
index a905394..e28e587 100644
--- a/packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/notification/collection/coordinator/LockScreenMinimalismCoordinatorTest.kt
+++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/notification/collection/coordinator/LockScreenMinimalismCoordinatorTest.kt
@@ -28,6 +28,7 @@
 import com.android.systemui.plugins.statusbar.statusBarStateController
 import com.android.systemui.shade.shadeTestUtil
 import com.android.systemui.statusbar.SysuiStatusBarStateController
+import com.android.systemui.statusbar.notification.collection.BundleEntry
 import com.android.systemui.statusbar.notification.collection.GroupEntryBuilder
 import com.android.systemui.statusbar.notification.collection.NotifPipeline
 import com.android.systemui.statusbar.notification.collection.NotificationEntry
@@ -280,6 +281,8 @@
         val group = GroupEntryBuilder().setSummary(parent).addChild(child1).addChild(child2).build()
         val listEntryList = listOf(group, solo1, solo2)
         val notificationEntryList = listOf(solo1, solo2, parent, child1, child2)
+        val bundle = BundleEntry("bundleKey")
+        val bundleList = listOf(bundle)
 
         runCoordinatorTest {
             // All entries are added (and now unseen)
@@ -300,6 +303,11 @@
             assertThatTopOngoingKey().isEqualTo(null)
             assertThatTopUnseenKey().isEqualTo(solo1.key)
 
+            // TEST: bundle is not picked
+            onBeforeTransformGroupsListener.onBeforeTransformGroups(bundleList)
+            assertThatTopOngoingKey().isEqualTo(null)
+            assertThatTopUnseenKey().isEqualTo(null)
+
             // TEST: if top-ranked unseen is colorized, fall back to #2 ranked unseen
             solo1.setColorizedFgs(true)
             onBeforeTransformGroupsListener.onBeforeTransformGroups(listEntryList)
diff --git a/packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/phone/ongoingcall/domain/interactor/OngoingCallInteractorTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/phone/ongoingcall/domain/interactor/OngoingCallInteractorTest.kt
index 398f3fb..f4204af7 100644
--- a/packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/phone/ongoingcall/domain/interactor/OngoingCallInteractorTest.kt
+++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/phone/ongoingcall/domain/interactor/OngoingCallInteractorTest.kt
@@ -82,6 +82,7 @@
                 statusBarChipIconView = testIconView,
                 contentIntent = testIntent,
                 promotedContent = testPromotedContent,
+                isAppVisible = false,
             )
 
             // Verify model is InCall and has the correct icon, intent, and promoted content.
@@ -98,7 +99,6 @@
     @Test
     fun ongoingCallNotification_setsAllFields_withAppVisible() =
         kosmos.runTest {
-            kosmos.activityManagerRepository.fake.startingIsAppVisibleValue = true
             val latest by collectLastValue(underTest.ongoingCallState)
 
             // Set up notification with icon view and intent
@@ -113,6 +113,7 @@
                 statusBarChipIconView = testIconView,
                 contentIntent = testIntent,
                 promotedContent = testPromotedContent,
+                isAppVisible = true,
             )
 
             // Verify model is InCall with visible app and has the correct icon, intent, and
@@ -141,10 +142,9 @@
     @Test
     fun ongoingCallNotification_appVisibleInitially_emitsInCallWithVisibleApp() =
         kosmos.runTest {
-            kosmos.activityManagerRepository.fake.startingIsAppVisibleValue = true
             val latest by collectLastValue(underTest.ongoingCallState)
 
-            addOngoingCallState(uid = UID)
+            addOngoingCallState(uid = UID, isAppVisible = true)
 
             assertThat(latest).isInstanceOf(OngoingCallModel.InCall::class.java)
             assertThat((latest as OngoingCallModel.InCall).isAppVisible).isTrue()
@@ -153,10 +153,9 @@
     @Test
     fun ongoingCallNotification_appNotVisibleInitially_emitsInCall() =
         kosmos.runTest {
-            kosmos.activityManagerRepository.fake.startingIsAppVisibleValue = false
             val latest by collectLastValue(underTest.ongoingCallState)
 
-            addOngoingCallState(uid = UID)
+            addOngoingCallState(uid = UID, isAppVisible = false)
 
             assertThat(latest).isInstanceOf(OngoingCallModel.InCall::class.java)
             assertThat((latest as OngoingCallModel.InCall).isAppVisible).isFalse()
@@ -168,8 +167,7 @@
             val latest by collectLastValue(underTest.ongoingCallState)
 
             // Start with notification and app not visible
-            kosmos.activityManagerRepository.fake.startingIsAppVisibleValue = false
-            addOngoingCallState(uid = UID)
+            addOngoingCallState(uid = UID, isAppVisible = false)
             assertThat(latest).isInstanceOf(OngoingCallModel.InCall::class.java)
             assertThat((latest as OngoingCallModel.InCall).isAppVisible).isFalse()
 
@@ -245,9 +243,7 @@
                         .ongoingProcessRequiresStatusBarVisible
                 )
 
-            kosmos.activityManagerRepository.fake.startingIsAppVisibleValue = false
-
-            addOngoingCallState(uid = UID)
+            addOngoingCallState(uid = UID, isAppVisible = false)
 
             assertThat(ongoingCallState).isInstanceOf(OngoingCallModel.InCall::class.java)
             assertThat((ongoingCallState as OngoingCallModel.InCall).isAppVisible).isFalse()
diff --git a/packages/SystemUI/multivalentTests/src/com/android/systemui/topwindoweffects/SqueezeEffectInteractorTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/topwindoweffects/SqueezeEffectInteractorTest.kt
new file mode 100644
index 0000000..fb19a88
--- /dev/null
+++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/topwindoweffects/SqueezeEffectInteractorTest.kt
@@ -0,0 +1,64 @@
+/*
+ * 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.topwindoweffects
+
+import androidx.test.ext.junit.runners.AndroidJUnit4
+import androidx.test.filters.SmallTest
+import com.android.systemui.SysuiTestCase
+import com.android.systemui.kosmos.Kosmos
+import com.android.systemui.kosmos.collectLastValue
+import com.android.systemui.kosmos.runTest
+import com.android.systemui.kosmos.useUnconfinedTestDispatcher
+import com.android.systemui.testKosmos
+import com.android.systemui.topwindoweffects.data.repository.fakeSqueezeEffectRepository
+import com.android.systemui.topwindoweffects.domain.interactor.SqueezeEffectInteractor
+import com.google.common.truth.Truth.assertThat
+import org.junit.Test
+import org.junit.runner.RunWith
+
+@SmallTest
+@RunWith(AndroidJUnit4::class)
+class SqueezeEffectInteractorTest : SysuiTestCase() {
+
+    private val kosmos = testKosmos().useUnconfinedTestDispatcher()
+
+    private val Kosmos.underTest by Kosmos.Fixture {
+        SqueezeEffectInteractor(
+            squeezeEffectRepository = fakeSqueezeEffectRepository
+        )
+    }
+
+    @Test
+    fun testIsSqueezeEffectDisabled_whenDisabledInRepository() =
+        kosmos.runTest {
+            fakeSqueezeEffectRepository.isSqueezeEffectEnabled.value = false
+
+            val isSqueezeEffectEnabled by collectLastValue(underTest.isSqueezeEffectEnabled)
+
+            assertThat(isSqueezeEffectEnabled).isFalse()
+        }
+
+    @Test
+    fun testIsSqueezeEffectEnabled_whenEnabledInRepository() =
+        kosmos.runTest {
+            fakeSqueezeEffectRepository.isSqueezeEffectEnabled.value = true
+
+            val isSqueezeEffectEnabled by collectLastValue(underTest.isSqueezeEffectEnabled)
+
+            assertThat(isSqueezeEffectEnabled).isTrue()
+        }
+}
\ No newline at end of file
diff --git a/packages/SystemUI/multivalentTests/src/com/android/systemui/topwindoweffects/SqueezeEffectRepositoryTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/topwindoweffects/SqueezeEffectRepositoryTest.kt
new file mode 100644
index 0000000..5d8d3f9
--- /dev/null
+++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/topwindoweffects/SqueezeEffectRepositoryTest.kt
@@ -0,0 +1,96 @@
+/*
+ * 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.topwindoweffects
+
+import android.os.Handler
+import android.platform.test.annotations.DisableFlags
+import android.platform.test.annotations.EnableFlags
+import android.provider.Settings.Global.POWER_BUTTON_LONG_PRESS
+import androidx.test.ext.junit.runners.AndroidJUnit4
+import androidx.test.filters.SmallTest
+import com.android.systemui.SysuiTestCase
+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.shared.Flags
+import com.android.systemui.testKosmos
+import com.android.systemui.topwindoweffects.data.repository.SqueezeEffectRepositoryImpl
+import com.android.systemui.util.settings.FakeGlobalSettings
+import com.google.common.truth.Truth.assertThat
+import kotlinx.coroutines.test.StandardTestDispatcher
+import org.junit.Before
+import org.junit.Test
+import org.junit.runner.RunWith
+import org.mockito.Mock
+import org.mockito.MockitoAnnotations
+
+@SmallTest
+@RunWith(AndroidJUnit4::class)
+class SqueezeEffectRepositoryTest : SysuiTestCase() {
+
+    private val kosmos = testKosmos().useUnconfinedTestDispatcher()
+    private val globalSettings = FakeGlobalSettings(StandardTestDispatcher())
+
+    @Mock
+    private lateinit var bgHandler: Handler
+
+    private val Kosmos.underTest by Kosmos.Fixture {
+        SqueezeEffectRepositoryImpl(
+            bgHandler = bgHandler,
+            bgCoroutineContext = testScope.testScheduler,
+            globalSettings = globalSettings
+        )
+    }
+
+    @Before
+    fun setup() {
+        MockitoAnnotations.initMocks(this)
+    }
+
+    @DisableFlags(Flags.FLAG_ENABLE_LPP_SQUEEZE_EFFECT)
+    @Test
+    fun testSqueezeEffectDisabled_WhenFlagDisabled() =
+        kosmos.runTest {
+            val isSqueezeEffectEnabled by collectLastValue(underTest.isSqueezeEffectEnabled)
+
+            assertThat(isSqueezeEffectEnabled).isFalse()
+        }
+
+    @EnableFlags(Flags.FLAG_ENABLE_LPP_SQUEEZE_EFFECT)
+    @Test
+    fun testSqueezeEffectDisabled_WhenFlagEnabled_GlobalSettingsDisabled() =
+        kosmos.runTest {
+            globalSettings.putInt(POWER_BUTTON_LONG_PRESS, 0)
+
+            val isSqueezeEffectEnabled by collectLastValue(underTest.isSqueezeEffectEnabled)
+
+            assertThat(isSqueezeEffectEnabled).isFalse()
+        }
+
+    @EnableFlags(Flags.FLAG_ENABLE_LPP_SQUEEZE_EFFECT)
+    @Test
+    fun testSqueezeEffectEnabled_WhenFlagEnabled_GlobalSettingEnabled() =
+        kosmos.runTest {
+            globalSettings.putInt(POWER_BUTTON_LONG_PRESS, 5)
+
+            val isSqueezeEffectEnabled by collectLastValue(underTest.isSqueezeEffectEnabled)
+
+            assertThat(isSqueezeEffectEnabled).isTrue()
+        }
+}
\ No newline at end of file
diff --git a/packages/SystemUI/multivalentTests/src/com/android/systemui/topwindoweffects/TopLevelWindowEffectsTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/topwindoweffects/TopLevelWindowEffectsTest.kt
new file mode 100644
index 0000000..83dc45c8
--- /dev/null
+++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/topwindoweffects/TopLevelWindowEffectsTest.kt
@@ -0,0 +1,102 @@
+/*
+ * 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.topwindoweffects
+
+import android.view.View
+import android.view.WindowManager
+import androidx.test.ext.junit.runners.AndroidJUnit4
+import androidx.test.filters.SmallTest
+import com.android.app.viewcapture.ViewCapture
+import com.android.app.viewcapture.ViewCaptureAwareWindowManager
+import com.android.systemui.SysuiTestCase
+import com.android.systemui.kosmos.Kosmos
+import com.android.systemui.kosmos.runTest
+import com.android.systemui.kosmos.testScope
+import com.android.systemui.kosmos.useUnconfinedTestDispatcher
+import com.android.systemui.testKosmos
+import com.android.systemui.topwindoweffects.data.repository.fakeSqueezeEffectRepository
+import com.android.systemui.topwindoweffects.domain.interactor.SqueezeEffectInteractor
+import com.android.systemui.topwindoweffects.ui.compose.EffectsWindowRoot
+import org.junit.Before
+import org.junit.Test
+import org.junit.runner.RunWith
+import org.mockito.Mock
+import org.mockito.MockitoAnnotations
+import org.mockito.kotlin.any
+import org.mockito.kotlin.doNothing
+import org.mockito.kotlin.never
+import org.mockito.kotlin.times
+import org.mockito.kotlin.verify
+import org.mockito.kotlin.whenever
+
+@SmallTest
+@RunWith(AndroidJUnit4::class)
+class TopLevelWindowEffectsTest : SysuiTestCase() {
+
+    private val kosmos = testKosmos().useUnconfinedTestDispatcher()
+
+    @Mock
+    private lateinit var windowManager: WindowManager
+
+    @Mock
+    private lateinit var viewCapture: Lazy<ViewCapture>
+
+    private val Kosmos.underTest by Kosmos.Fixture {
+        TopLevelWindowEffects(
+            context = mContext,
+            applicationScope = testScope.backgroundScope,
+            windowManager = ViewCaptureAwareWindowManager(
+                windowManager = windowManager,
+                lazyViewCapture = viewCapture,
+                isViewCaptureEnabled = false
+            ),
+            squeezeEffectInteractor = SqueezeEffectInteractor(
+                squeezeEffectRepository = fakeSqueezeEffectRepository
+            )
+        )
+    }
+
+    @Before
+    fun setup() {
+        MockitoAnnotations.initMocks(this)
+
+        doNothing().whenever(windowManager).addView(any<View>(), any<WindowManager.LayoutParams>())
+        doNothing().whenever(windowManager).removeView(any<View>())
+        doNothing().whenever(windowManager).removeView(any<EffectsWindowRoot>())
+    }
+
+    @Test
+    fun noWindowWhenSqueezeEffectDisabled() =
+        kosmos.runTest {
+            fakeSqueezeEffectRepository.isSqueezeEffectEnabled.value = false
+
+            underTest.start()
+
+            verify(windowManager, never()).addView(any<View>(), any<WindowManager.LayoutParams>())
+        }
+
+    @Test
+    fun addViewToWindowWhenSqueezeEffectEnabled() =
+        kosmos.runTest {
+            fakeSqueezeEffectRepository.isSqueezeEffectEnabled.value = true
+
+            underTest.start()
+
+            verify(windowManager, times(1)).addView(any<View>(),
+                any<WindowManager.LayoutParams>())
+        }
+}
\ No newline at end of file
diff --git a/packages/SystemUI/res/drawable/notification_2025_smart_reply_button_background.xml b/packages/SystemUI/res/drawable/notification_2025_smart_reply_button_background.xml
new file mode 100644
index 0000000..d398f60
--- /dev/null
+++ b/packages/SystemUI/res/drawable/notification_2025_smart_reply_button_background.xml
@@ -0,0 +1,35 @@
+<?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
+  -->
+
+<ripple xmlns:android="http://schemas.android.com/apk/res/android"
+        android:color="@color/notification_ripple_untinted_color">
+    <item>
+        <inset
+            android:insetLeft="0dp"
+            android:insetTop="8dp"
+            android:insetRight="0dp"
+            android:insetBottom="8dp">
+            <shape android:shape="rectangle">
+              <corners android:radius="@dimen/notification_2025_smart_reply_button_corner_radius" />
+                <stroke android:width="@dimen/smart_reply_button_stroke_width"
+                        android:color="@color/smart_reply_button_stroke" />
+                <solid android:color="@color/smart_reply_button_background"/>
+            </shape>
+        </inset>
+    </item>
+</ripple>
diff --git a/packages/SystemUI/res/layout/clipboard_overlay.xml b/packages/SystemUI/res/layout/clipboard_overlay.xml
index 915563b..c7add16 100644
--- a/packages/SystemUI/res/layout/clipboard_overlay.xml
+++ b/packages/SystemUI/res/layout/clipboard_overlay.xml
@@ -85,6 +85,7 @@
         android:paddingVertical="@dimen/overlay_action_container_padding_vertical"
         android:elevation="4dp"
         android:scrollbars="none"
+        android:importantForAccessibility="no"
         app:layout_constraintHorizontal_bias="0"
         app:layout_constraintWidth_percent="1.0"
         app:layout_constraintWidth_max="wrap"
@@ -176,6 +177,8 @@
         app:layout_constraintStart_toStartOf="parent"
         android:layout_marginStart="4dp"
         android:layout_marginBottom="2dp"
+        android:importantForAccessibility="yes"
+        android:contentDescription="@string/clipboard_overlay_window_name"
         android:background="@drawable/clipboard_minimized_background_inset">
         <ImageView
             android:src="@drawable/ic_content_paste"
diff --git a/packages/SystemUI/res/layout/notification_2025_smart_action_button.xml b/packages/SystemUI/res/layout/notification_2025_smart_action_button.xml
new file mode 100644
index 0000000..ed90588
--- /dev/null
+++ b/packages/SystemUI/res/layout/notification_2025_smart_action_button.xml
@@ -0,0 +1,35 @@
+<!--
+  ~ 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
+  -->
+
+<!-- android:paddingHorizontal is set dynamically in SmartReplyView. -->
+<Button xmlns:android="http://schemas.android.com/apk/res/android"
+        style="@android:style/Widget.Material.Button"
+        android:stateListAnimator="@null"
+        android:layout_width="wrap_content"
+        android:layout_height="match_parent"
+        android:minWidth="0dp"
+        android:minHeight="@dimen/notification_2025_smart_reply_button_min_height"
+        android:paddingVertical="@dimen/smart_reply_button_padding_vertical"
+        android:background="@drawable/notification_2025_smart_reply_button_background"
+        android:gravity="center"
+        android:fontFamily="google-sans-flex"
+        android:textSize="@dimen/smart_reply_button_font_size"
+        android:textColor="@color/smart_reply_button_text"
+        android:paddingStart="@dimen/smart_reply_button_action_padding_left"
+        android:paddingEnd="@dimen/smart_reply_button_padding_horizontal"
+        android:drawablePadding="@dimen/smart_action_button_icon_padding"
+        android:textStyle="normal"
+        android:ellipsize="none"/>
diff --git a/packages/SystemUI/res/layout/notification_2025_smart_reply_button.xml b/packages/SystemUI/res/layout/notification_2025_smart_reply_button.xml
new file mode 100644
index 0000000..4f543e5
--- /dev/null
+++ b/packages/SystemUI/res/layout/notification_2025_smart_reply_button.xml
@@ -0,0 +1,36 @@
+<?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
+  -->
+
+<!-- android:paddingHorizontal is set dynamically in SmartReplyView. -->
+<Button xmlns:android="http://schemas.android.com/apk/res/android"
+        style="@android:style/Widget.Material.Button"
+        android:stateListAnimator="@null"
+        android:layout_width="wrap_content"
+        android:layout_height="match_parent"
+        android:minWidth="0dp"
+        android:minHeight="@dimen/notification_2025_smart_reply_button_min_height"
+        android:paddingVertical="@dimen/smart_reply_button_padding_vertical"
+        android:background="@drawable/notification_2025_smart_reply_button_background"
+        android:gravity="center"
+        android:fontFamily="google-sans-flex"
+        android:textSize="@dimen/smart_reply_button_font_size"
+        android:textColor="@color/smart_reply_button_text"
+        android:paddingStart="@dimen/smart_reply_button_padding_horizontal"
+        android:paddingEnd="@dimen/smart_reply_button_padding_horizontal"
+        android:textStyle="normal"
+        android:ellipsize="none"/>
diff --git a/packages/SystemUI/res/values/dimens.xml b/packages/SystemUI/res/values/dimens.xml
index d547bd0..d0ae307 100644
--- a/packages/SystemUI/res/values/dimens.xml
+++ b/packages/SystemUI/res/values/dimens.xml
@@ -1157,6 +1157,8 @@
     <dimen name="smart_action_button_icon_size">18dp</dimen>
     <dimen name="smart_action_button_icon_padding">8dp</dimen>
     <dimen name="smart_action_button_outline_stroke_width">2dp</dimen>
+    <dimen name="notification_2025_smart_reply_button_corner_radius">18dp</dimen>
+    <dimen name="notification_2025_smart_reply_button_min_height">48dp</dimen>
 
     <!-- Magic Action params. -->
     <!-- Corner radius = half of min_height to create rounded sides. -->
diff --git a/packages/SystemUI/res/values/strings.xml b/packages/SystemUI/res/values/strings.xml
index c06c17a..8c1fd65 100644
--- a/packages/SystemUI/res/values/strings.xml
+++ b/packages/SystemUI/res/values/strings.xml
@@ -2357,8 +2357,8 @@
     <string name="system_multitasking_lhs">Use split screen with app on the left</string>
     <!-- User visible title for the keyboard shortcut that switches from split screen to full screen [CHAR LIMIT=70] -->
     <string name="system_multitasking_full_screen">Use full screen</string>
-    <!-- User visible title for the keyboard shortcut that switches to desktop view [CHAR LIMIT=70] -->
-    <string name="system_multitasking_desktop_view">Use desktop view</string>
+    <!-- User visible title for the keyboard shortcut that switches to desktop windowing [CHAR LIMIT=70] -->
+    <string name="system_multitasking_desktop_view">Use desktop windowing</string>
     <!-- User visible title for the keyboard shortcut that switches to app on right or below while using split screen [CHAR LIMIT=70] -->
     <string name="system_multitasking_splitscreen_focus_rhs">Switch to app on right or below while using split screen</string>
     <!-- User visible title for the keyboard shortcut that switches to app on left or above while using split screen [CHAR LIMIT=70] -->
diff --git a/packages/SystemUI/src/com/android/keyguard/KeyguardInputViewController.java b/packages/SystemUI/src/com/android/keyguard/KeyguardInputViewController.java
index ec97b8a..b872610 100644
--- a/packages/SystemUI/src/com/android/keyguard/KeyguardInputViewController.java
+++ b/packages/SystemUI/src/com/android/keyguard/KeyguardInputViewController.java
@@ -21,6 +21,7 @@
 import android.annotation.Nullable;
 import android.content.res.ColorStateList;
 import android.content.res.Resources;
+import android.hardware.input.InputManager;
 import android.telephony.TelephonyManager;
 import android.text.TextUtils;
 import android.util.Log;
@@ -219,6 +220,7 @@
         private final KeyguardKeyboardInteractor mKeyguardKeyboardInteractor;
         private final BouncerHapticPlayer mBouncerHapticPlayer;
         private final UserActivityNotifier mUserActivityNotifier;
+        private final InputManager mInputManager;
 
         @Inject
         public Factory(KeyguardUpdateMonitor keyguardUpdateMonitor,
@@ -235,7 +237,8 @@
                 UiEventLogger uiEventLogger,
                 KeyguardKeyboardInteractor keyguardKeyboardInteractor,
                 BouncerHapticPlayer bouncerHapticPlayer,
-                UserActivityNotifier userActivityNotifier) {
+                UserActivityNotifier userActivityNotifier,
+                InputManager inputManager) {
             mKeyguardUpdateMonitor = keyguardUpdateMonitor;
             mLockPatternUtils = lockPatternUtils;
             mLatencyTracker = latencyTracker;
@@ -254,6 +257,7 @@
             mKeyguardKeyboardInteractor = keyguardKeyboardInteractor;
             mBouncerHapticPlayer = bouncerHapticPlayer;
             mUserActivityNotifier = userActivityNotifier;
+            mInputManager = inputManager;
         }
 
         /** Create a new {@link KeyguardInputViewController}. */
@@ -285,22 +289,23 @@
                         emergencyButtonController, mFalsingCollector,
                         mDevicePostureController, mFeatureFlags, mSelectedUserInteractor,
                         mUiEventLogger, mKeyguardKeyboardInteractor, mBouncerHapticPlayer,
-                        mUserActivityNotifier);
+                        mUserActivityNotifier, mInputManager);
             } else if (keyguardInputView instanceof KeyguardSimPinView) {
                 return new KeyguardSimPinViewController((KeyguardSimPinView) keyguardInputView,
                         mKeyguardUpdateMonitor, securityMode, mLockPatternUtils,
                         keyguardSecurityCallback, mMessageAreaControllerFactory, mLatencyTracker,
                         mTelephonyManager, mFalsingCollector,
                         emergencyButtonController, mFeatureFlags, mSelectedUserInteractor,
-                        mKeyguardKeyboardInteractor, mBouncerHapticPlayer, mUserActivityNotifier);
+                        mKeyguardKeyboardInteractor, mBouncerHapticPlayer, mUserActivityNotifier,
+                        mInputManager);
             } else if (keyguardInputView instanceof KeyguardSimPukView) {
                 return new KeyguardSimPukViewController((KeyguardSimPukView) keyguardInputView,
                         mKeyguardUpdateMonitor, securityMode, mLockPatternUtils,
                         keyguardSecurityCallback, mMessageAreaControllerFactory, mLatencyTracker,
                         mTelephonyManager, mFalsingCollector,
                         emergencyButtonController, mFeatureFlags, mSelectedUserInteractor,
-                        mKeyguardKeyboardInteractor, mBouncerHapticPlayer, mUserActivityNotifier
-                );
+                        mKeyguardKeyboardInteractor, mBouncerHapticPlayer, mUserActivityNotifier,
+                        mInputManager);
             }
 
             throw new RuntimeException("Unable to find controller for " + keyguardInputView);
diff --git a/packages/SystemUI/src/com/android/keyguard/KeyguardPinBasedInputViewController.java b/packages/SystemUI/src/com/android/keyguard/KeyguardPinBasedInputViewController.java
index 0e9d8fe..ec9aedf 100644
--- a/packages/SystemUI/src/com/android/keyguard/KeyguardPinBasedInputViewController.java
+++ b/packages/SystemUI/src/com/android/keyguard/KeyguardPinBasedInputViewController.java
@@ -18,12 +18,14 @@
 
 import static android.view.accessibility.AccessibilityEvent.TYPE_VIEW_FOCUSED;
 
+import static com.android.internal.widget.flags.Flags.hideLastCharWithPhysicalInput;
 import static com.android.systemui.Flags.pinInputFieldStyledFocusState;
 import static com.android.systemui.util.kotlin.JavaAdapterKt.collectFlow;
 
 import android.graphics.drawable.Drawable;
 import android.graphics.drawable.GradientDrawable;
 import android.graphics.drawable.StateListDrawable;
+import android.hardware.input.InputManager;
 import android.util.TypedValue;
 import android.view.KeyEvent;
 import android.view.MotionEvent;
@@ -43,11 +45,13 @@
 import com.android.systemui.user.domain.interactor.SelectedUserInteractor;
 
 public abstract class KeyguardPinBasedInputViewController<T extends KeyguardPinBasedInputView>
-        extends KeyguardAbsKeyInputViewController<T> {
+        extends KeyguardAbsKeyInputViewController<T> implements InputManager.InputDeviceListener {
 
     private final FalsingCollector mFalsingCollector;
     private final KeyguardKeyboardInteractor mKeyguardKeyboardInteractor;
     protected PasswordTextView mPasswordEntry;
+    private Boolean mShowAnimations;
+    private InputManager mInputManager;
 
     private final OnKeyListener mOnKeyListener = (v, keyCode, event) -> {
         if (event.getAction() == KeyEvent.ACTION_DOWN) {
@@ -79,7 +83,8 @@
             SelectedUserInteractor selectedUserInteractor,
             KeyguardKeyboardInteractor keyguardKeyboardInteractor,
             BouncerHapticPlayer bouncerHapticPlayer,
-            UserActivityNotifier userActivityNotifier) {
+            UserActivityNotifier userActivityNotifier,
+            InputManager inputManager) {
         super(view, keyguardUpdateMonitor, securityMode, lockPatternUtils, keyguardSecurityCallback,
                 messageAreaControllerFactory, latencyTracker, falsingCollector,
                 emergencyButtonController, featureFlags, selectedUserInteractor,
@@ -87,6 +92,51 @@
         mFalsingCollector = falsingCollector;
         mKeyguardKeyboardInteractor = keyguardKeyboardInteractor;
         mPasswordEntry = mView.findViewById(mView.getPasswordTextViewId());
+        mInputManager = inputManager;
+        mShowAnimations = null;
+    }
+
+    private void updateAnimations(Boolean showAnimations) {
+        if (!hideLastCharWithPhysicalInput()) return;
+
+        if (showAnimations == null) {
+            showAnimations = !mLockPatternUtils
+                .isPinEnhancedPrivacyEnabled(mSelectedUserInteractor.getSelectedUserId());
+        }
+        if (mShowAnimations != null && showAnimations.equals(mShowAnimations)) return;
+        mShowAnimations = showAnimations;
+
+        for (NumPadKey button : mView.getButtons()) {
+            button.setAnimationEnabled(mShowAnimations);
+        }
+        mPasswordEntry.setShowPassword(mShowAnimations);
+    }
+
+    @Override
+    public void onInputDeviceAdded(int deviceId) {
+        if (!hideLastCharWithPhysicalInput()) return;
+
+        // If we were showing animations before maybe the new device is a keyboard.
+        if (mShowAnimations) {
+            updateAnimations(null);
+        }
+    }
+
+    @Override
+    public void onInputDeviceRemoved(int deviceId) {
+        if (!hideLastCharWithPhysicalInput()) return;
+
+        // If we were hiding animations because of a keyboard the keyboard may have been unplugged.
+        if (!mShowAnimations) {
+            updateAnimations(null);
+        }
+    }
+
+    @Override
+    public void onInputDeviceChanged(int deviceId) {
+        if (!hideLastCharWithPhysicalInput()) return;
+
+        updateAnimations(null);
     }
 
     @Override
@@ -95,7 +145,13 @@
 
         boolean showAnimations = !mLockPatternUtils
                 .isPinEnhancedPrivacyEnabled(mSelectedUserInteractor.getSelectedUserId());
-        mPasswordEntry.setShowPassword(showAnimations);
+        if (hideLastCharWithPhysicalInput()) {
+            mInputManager.registerInputDeviceListener(this, null);
+            updateAnimations(showAnimations);
+        } else {
+            mPasswordEntry.setShowPassword(showAnimations);
+        }
+
         for (NumPadKey button : mView.getButtons()) {
             button.setOnTouchListener((v, event) -> {
                 if (event.getActionMasked() == MotionEvent.ACTION_DOWN) {
@@ -103,7 +159,9 @@
                 }
                 return false;
             });
-            button.setAnimationEnabled(showAnimations);
+            if (!hideLastCharWithPhysicalInput()) {
+                button.setAnimationEnabled(showAnimations);
+            }
             button.setBouncerHapticHelper(mBouncerHapticPlayer);
         }
         mPasswordEntry.setOnKeyListener(mOnKeyListener);
@@ -191,6 +249,10 @@
     protected void onViewDetached() {
         super.onViewDetached();
 
+        if (hideLastCharWithPhysicalInput()) {
+            mInputManager.unregisterInputDeviceListener(this);
+        }
+
         for (NumPadKey button : mView.getButtons()) {
             button.setOnTouchListener(null);
             button.setBouncerHapticHelper(null);
diff --git a/packages/SystemUI/src/com/android/keyguard/KeyguardPinViewController.java b/packages/SystemUI/src/com/android/keyguard/KeyguardPinViewController.java
index 9ae4cc6..eefcab3 100644
--- a/packages/SystemUI/src/com/android/keyguard/KeyguardPinViewController.java
+++ b/packages/SystemUI/src/com/android/keyguard/KeyguardPinViewController.java
@@ -18,6 +18,7 @@
 
 import static com.android.systemui.flags.Flags.LOCKSCREEN_ENABLE_LANDSCAPE;
 
+import android.hardware.input.InputManager;
 import android.view.View;
 
 import com.android.internal.logging.UiEvent;
@@ -63,11 +64,13 @@
             SelectedUserInteractor selectedUserInteractor, UiEventLogger uiEventLogger,
             KeyguardKeyboardInteractor keyguardKeyboardInteractor,
             BouncerHapticPlayer bouncerHapticPlayer,
-            UserActivityNotifier userActivityNotifier) {
+            UserActivityNotifier userActivityNotifier,
+            InputManager inputManager) {
         super(view, keyguardUpdateMonitor, securityMode, lockPatternUtils, keyguardSecurityCallback,
                 messageAreaControllerFactory, latencyTracker,
                 emergencyButtonController, falsingCollector, featureFlags, selectedUserInteractor,
-                keyguardKeyboardInteractor, bouncerHapticPlayer, userActivityNotifier);
+                keyguardKeyboardInteractor, bouncerHapticPlayer, userActivityNotifier, inputManager
+        );
         mKeyguardUpdateMonitor = keyguardUpdateMonitor;
         mPostureController = postureController;
         mLockPatternUtils = lockPatternUtils;
diff --git a/packages/SystemUI/src/com/android/keyguard/KeyguardSimPinViewController.java b/packages/SystemUI/src/com/android/keyguard/KeyguardSimPinViewController.java
index 24f77d7..a5bb62c 100644
--- a/packages/SystemUI/src/com/android/keyguard/KeyguardSimPinViewController.java
+++ b/packages/SystemUI/src/com/android/keyguard/KeyguardSimPinViewController.java
@@ -29,6 +29,7 @@
 import android.content.res.Resources;
 import android.content.res.TypedArray;
 import android.graphics.Color;
+import android.hardware.input.InputManager;
 import android.telephony.PinResult;
 import android.telephony.SubscriptionInfo;
 import android.telephony.SubscriptionManager;
@@ -97,11 +98,13 @@
             SelectedUserInteractor selectedUserInteractor,
             KeyguardKeyboardInteractor keyguardKeyboardInteractor,
             BouncerHapticPlayer bouncerHapticPlayer,
-            UserActivityNotifier userActivityNotifier) {
+            UserActivityNotifier userActivityNotifier,
+            InputManager inputManager) {
         super(view, keyguardUpdateMonitor, securityMode, lockPatternUtils, keyguardSecurityCallback,
                 messageAreaControllerFactory, latencyTracker,
                 emergencyButtonController, falsingCollector, featureFlags, selectedUserInteractor,
-                keyguardKeyboardInteractor, bouncerHapticPlayer, userActivityNotifier);
+                keyguardKeyboardInteractor, bouncerHapticPlayer, userActivityNotifier, inputManager
+        );
         mKeyguardUpdateMonitor = keyguardUpdateMonitor;
         mTelephonyManager = telephonyManager;
         mSimImageView = mView.findViewById(R.id.keyguard_sim);
diff --git a/packages/SystemUI/src/com/android/keyguard/KeyguardSimPukViewController.java b/packages/SystemUI/src/com/android/keyguard/KeyguardSimPukViewController.java
index e17e8cc..adede3d 100644
--- a/packages/SystemUI/src/com/android/keyguard/KeyguardSimPukViewController.java
+++ b/packages/SystemUI/src/com/android/keyguard/KeyguardSimPukViewController.java
@@ -25,6 +25,7 @@
 import android.content.res.Resources;
 import android.content.res.TypedArray;
 import android.graphics.Color;
+import android.hardware.input.InputManager;
 import android.telephony.PinResult;
 import android.telephony.SubscriptionInfo;
 import android.telephony.SubscriptionManager;
@@ -95,11 +96,13 @@
             SelectedUserInteractor selectedUserInteractor,
             KeyguardKeyboardInteractor keyguardKeyboardInteractor,
             BouncerHapticPlayer bouncerHapticPlayer,
-            UserActivityNotifier userActivityNotifier) {
+            UserActivityNotifier userActivityNotifier,
+            InputManager inputManager) {
         super(view, keyguardUpdateMonitor, securityMode, lockPatternUtils, keyguardSecurityCallback,
                 messageAreaControllerFactory, latencyTracker,
                 emergencyButtonController, falsingCollector, featureFlags, selectedUserInteractor,
-                keyguardKeyboardInteractor, bouncerHapticPlayer, userActivityNotifier);
+                keyguardKeyboardInteractor, bouncerHapticPlayer, userActivityNotifier, inputManager
+        );
         mKeyguardUpdateMonitor = keyguardUpdateMonitor;
         mTelephonyManager = telephonyManager;
         mSimImageView = mView.findViewById(R.id.keyguard_sim);
diff --git a/packages/SystemUI/src/com/android/systemui/display/data/repository/FakePerDisplayRepository.kt b/packages/SystemUI/src/com/android/systemui/display/data/repository/FakePerDisplayRepository.kt
index 083191c..a56710e 100644
--- a/packages/SystemUI/src/com/android/systemui/display/data/repository/FakePerDisplayRepository.kt
+++ b/packages/SystemUI/src/com/android/systemui/display/data/repository/FakePerDisplayRepository.kt
@@ -32,9 +32,6 @@
         return instances[displayId]
     }
 
-    override val displayIds: Set<Int>
-        get() = instances.keys
-
     override val debugName: String
         get() = "FakePerDisplayRepository"
 }
diff --git a/packages/SystemUI/src/com/android/systemui/display/data/repository/PerDisplayRepository.kt b/packages/SystemUI/src/com/android/systemui/display/data/repository/PerDisplayRepository.kt
index d27e33e..d1d0135 100644
--- a/packages/SystemUI/src/com/android/systemui/display/data/repository/PerDisplayRepository.kt
+++ b/packages/SystemUI/src/com/android/systemui/display/data/repository/PerDisplayRepository.kt
@@ -86,9 +86,6 @@
     /** Gets the cached instance or create a new one for a given display. */
     operator fun get(displayId: Int): T?
 
-    /** List of display ids for which this repository has an instance. */
-    val displayIds: Set<Int>
-
     /** Debug name for this repository, mainly for tracing and logging. */
     val debugName: String
 }
@@ -122,9 +119,6 @@
         backgroundApplicationScope.launch("$debugName#start") { start() }
     }
 
-    override val displayIds: Set<Int>
-        get() = perDisplayInstances.keys
-
     private suspend fun start() {
         dumpManager.registerNormalDumpable("PerDisplayRepository-${debugName}", this)
         displayRepository.displayIds.collectLatest { displayIds ->
@@ -199,8 +193,6 @@
     private val lazyDefaultDisplayInstance by lazy {
         instanceProvider.createInstance(Display.DEFAULT_DISPLAY)
     }
-    override val displayIds: Set<Int> = setOf(Display.DEFAULT_DISPLAY)
-
     override fun get(displayId: Int): T? = lazyDefaultDisplayInstance
 }
 
@@ -214,7 +206,5 @@
  */
 class SingleInstanceRepositoryImpl<T>(override val debugName: String, private val instance: T) :
     PerDisplayRepository<T> {
-    override val displayIds: Set<Int> = setOf(Display.DEFAULT_DISPLAY)
-
     override fun get(displayId: Int): T? = instance
 }
diff --git a/packages/SystemUI/src/com/android/systemui/keyguard/ui/binder/KeyguardPreviewSmartspaceViewBinder.kt b/packages/SystemUI/src/com/android/systemui/keyguard/ui/binder/KeyguardPreviewSmartspaceViewBinder.kt
index 741b149..92b9da679 100644
--- a/packages/SystemUI/src/com/android/systemui/keyguard/ui/binder/KeyguardPreviewSmartspaceViewBinder.kt
+++ b/packages/SystemUI/src/com/android/systemui/keyguard/ui/binder/KeyguardPreviewSmartspaceViewBinder.kt
@@ -31,6 +31,37 @@
 object KeyguardPreviewSmartspaceViewBinder {
 
     @JvmStatic
+    fun bind(parentView: View, viewModel: KeyguardPreviewSmartspaceViewModel) {
+        if (com.android.systemui.shared.Flags.clockReactiveSmartspaceLayout()) {
+            val largeDateView =
+                parentView.findViewById<View>(
+                    com.android.systemui.shared.R.id.date_smartspace_view_large
+                )
+            val smallDateView =
+                parentView.findViewById<View>(com.android.systemui.shared.R.id.date_smartspace_view)
+            parentView.repeatWhenAttached {
+                repeatOnLifecycle(Lifecycle.State.STARTED) {
+                    launch("$TAG#viewModel.selectedClockSize") {
+                        viewModel.previewingClockSize.collect {
+                            when (it) {
+                                ClockSizeSetting.DYNAMIC -> {
+                                    smallDateView?.visibility = View.GONE
+                                    largeDateView?.visibility = View.VISIBLE
+                                }
+
+                                ClockSizeSetting.SMALL -> {
+                                    smallDateView?.visibility = View.VISIBLE
+                                    largeDateView?.visibility = View.GONE
+                                }
+                            }
+                        }
+                    }
+                }
+            }
+        }
+    }
+
+    @JvmStatic
     fun bind(
         smartspace: View,
         viewModel: KeyguardPreviewSmartspaceViewModel,
@@ -44,6 +75,7 @@
                             when (it) {
                                 ClockSizeSetting.DYNAMIC ->
                                     viewModel.getLargeClockSmartspaceTopPadding(clockPreviewConfig)
+
                                 ClockSizeSetting.SMALL ->
                                     viewModel.getSmallClockSmartspaceTopPadding(clockPreviewConfig)
                             }
diff --git a/packages/SystemUI/src/com/android/systemui/keyguard/ui/preview/KeyguardPreviewRenderer.kt b/packages/SystemUI/src/com/android/systemui/keyguard/ui/preview/KeyguardPreviewRenderer.kt
index 242926b..d749e3c 100644
--- a/packages/SystemUI/src/com/android/systemui/keyguard/ui/preview/KeyguardPreviewRenderer.kt
+++ b/packages/SystemUI/src/com/android/systemui/keyguard/ui/preview/KeyguardPreviewRenderer.kt
@@ -68,6 +68,7 @@
 import com.android.systemui.monet.Style
 import com.android.systemui.plugins.clocks.ClockController
 import com.android.systemui.plugins.clocks.ClockPreviewConfig
+import com.android.systemui.plugins.clocks.ContextExt.getId
 import com.android.systemui.plugins.clocks.ThemeConfig
 import com.android.systemui.plugins.clocks.WeatherData
 import com.android.systemui.res.R
@@ -126,6 +127,7 @@
 
     private val displayId = bundle.getInt(KEY_DISPLAY_ID, DEFAULT_DISPLAY)
     private val display: Display? = displayManager.getDisplay(displayId)
+
     /**
      * Returns a key that should make the KeyguardPreviewRenderer unique and if two of them have the
      * same key they will be treated as the same KeyguardPreviewRenderer. Primary this is used to
@@ -144,6 +146,8 @@
         get() = checkNotNull(host.surfacePackage)
 
     private var smartSpaceView: View? = null
+    private var largeDateView: View? = null
+    private var smallDateView: View? = null
 
     private val disposables = DisposableHandles()
     private var isDestroyed = false
@@ -181,7 +185,7 @@
                     ContextThemeWrapper(context.createDisplayContext(it), context.getTheme())
                 } ?: context
 
-            val rootView = FrameLayout(previewContext)
+            val rootView = ConstraintLayout(previewContext)
 
             setupKeyguardRootView(previewContext, rootView)
 
@@ -252,6 +256,24 @@
 
     fun onClockSizeSelected(clockSize: ClockSizeSetting) {
         smartspaceViewModel.setOverrideClockSize(clockSize)
+        if (com.android.systemui.shared.Flags.clockReactiveSmartspaceLayout()) {
+            when (clockSize) {
+                ClockSizeSetting.DYNAMIC -> {
+                    largeDateView?.post {
+                        smallDateView?.visibility = View.GONE
+                        largeDateView?.visibility = View.VISIBLE
+                    }
+                }
+
+                ClockSizeSetting.SMALL -> {
+                    largeDateView?.post {
+                        smallDateView?.visibility = View.VISIBLE
+                        largeDateView?.visibility = View.GONE
+                    }
+                }
+            }
+            smartSpaceView?.post { smartSpaceView?.visibility = View.GONE }
+        }
     }
 
     fun destroy() {
@@ -280,7 +302,7 @@
      *
      * The end padding is as follows: Below clock padding end
      */
-    private fun setUpSmartspace(previewContext: Context, parentView: ViewGroup) {
+    private fun setUpSmartspace(previewContext: Context, parentView: ConstraintLayout) {
         if (
             !lockscreenSmartspaceController.isEnabled ||
                 !lockscreenSmartspaceController.isDateWeatherDecoupled
@@ -292,40 +314,90 @@
             parentView.removeView(smartSpaceView)
         }
 
-        smartSpaceView =
-            lockscreenSmartspaceController.buildAndConnectDateView(
-                parent = parentView,
-                isLargeClock = false,
-            )
-
-        val topPadding: Int =
-            smartspaceViewModel.getLargeClockSmartspaceTopPadding(
-                ClockPreviewConfig(
-                    previewContext,
-                    getPreviewShadeLayoutWide(display!!),
-                    SceneContainerFlag.isEnabled,
+        if (com.android.systemui.shared.Flags.clockReactiveSmartspaceLayout()) {
+            val cs = ConstraintSet()
+            cs.clone(parentView)
+            cs.apply {
+                val largeClockViewId = previewContext.getId("lockscreen_clock_view_large")
+                val smallClockViewId = previewContext.getId("lockscreen_clock_view")
+                largeDateView =
+                    lockscreenSmartspaceController
+                        .buildAndConnectDateView(parentView, true)
+                        ?.also { view ->
+                            constrainWidth(view.id, ConstraintSet.WRAP_CONTENT)
+                            constrainHeight(view.id, ConstraintSet.WRAP_CONTENT)
+                            connect(view.id, START, largeClockViewId, START)
+                            connect(view.id, ConstraintSet.END, largeClockViewId, ConstraintSet.END)
+                            connect(
+                                view.id,
+                                TOP,
+                                largeClockViewId,
+                                ConstraintSet.BOTTOM,
+                                smartspaceViewModel.getDateWeatherEndPadding(previewContext),
+                            )
+                        }
+                smallDateView =
+                    lockscreenSmartspaceController
+                        .buildAndConnectDateView(parentView, false)
+                        ?.also { view ->
+                            constrainWidth(view.id, ConstraintSet.WRAP_CONTENT)
+                            constrainHeight(view.id, ConstraintSet.WRAP_CONTENT)
+                            connect(
+                                view.id,
+                                START,
+                                smallClockViewId,
+                                ConstraintSet.END,
+                                context.resources.getDimensionPixelSize(
+                                    R.dimen.smartspace_padding_horizontal
+                                ),
+                            )
+                            connect(view.id, TOP, smallClockViewId, TOP)
+                            connect(
+                                view.id,
+                                ConstraintSet.BOTTOM,
+                                smallClockViewId,
+                                ConstraintSet.BOTTOM,
+                            )
+                        }
+                parentView.addView(largeDateView)
+                parentView.addView(smallDateView)
+            }
+            cs.applyTo(parentView)
+        } else {
+            smartSpaceView =
+                lockscreenSmartspaceController.buildAndConnectDateView(
+                    parent = parentView,
+                    isLargeClock = false,
                 )
-            )
-        val startPadding: Int = smartspaceViewModel.getDateWeatherStartPadding(previewContext)
-        val endPadding: Int = smartspaceViewModel.getDateWeatherEndPadding(previewContext)
 
-        smartSpaceView?.let {
-            it.setPaddingRelative(startPadding, topPadding, endPadding, 0)
-            it.isClickable = false
-            it.isInvisible = true
-            parentView.addView(
-                it,
-                FrameLayout.LayoutParams(
-                    FrameLayout.LayoutParams.MATCH_PARENT,
-                    FrameLayout.LayoutParams.WRAP_CONTENT,
-                ),
-            )
+            val topPadding: Int =
+                smartspaceViewModel.getLargeClockSmartspaceTopPadding(
+                    ClockPreviewConfig(
+                        previewContext,
+                        getPreviewShadeLayoutWide(display!!),
+                        SceneContainerFlag.isEnabled,
+                    )
+                )
+            val startPadding: Int = smartspaceViewModel.getDateWeatherStartPadding(previewContext)
+            val endPadding: Int = smartspaceViewModel.getDateWeatherEndPadding(previewContext)
+
+            smartSpaceView?.let {
+                it.setPaddingRelative(startPadding, topPadding, endPadding, 0)
+                it.isClickable = false
+                it.isInvisible = true
+                parentView.addView(
+                    it,
+                    FrameLayout.LayoutParams(
+                        FrameLayout.LayoutParams.MATCH_PARENT,
+                        FrameLayout.LayoutParams.WRAP_CONTENT,
+                    ),
+                )
+            }
+            smartSpaceView?.alpha = if (shouldHighlightSelectedAffordance) DIM_ALPHA else 1.0f
         }
-
-        smartSpaceView?.alpha = if (shouldHighlightSelectedAffordance) DIM_ALPHA else 1.0f
     }
 
-    private fun setupKeyguardRootView(previewContext: Context, rootView: FrameLayout) {
+    private fun setupKeyguardRootView(previewContext: Context, rootView: ConstraintLayout) {
         val keyguardRootView = KeyguardRootView(previewContext, null)
         rootView.addView(
             keyguardRootView,
@@ -341,6 +413,13 @@
 
         if (!shouldHideClock) {
             setUpClock(previewContext, rootView)
+            if (com.android.systemui.shared.Flags.clockReactiveSmartspaceLayout()) {
+                setUpSmartspace(previewContext, keyguardRootView)
+                KeyguardPreviewSmartspaceViewBinder.bind(
+                    keyguardRootView,
+                    smartspaceViewModel,
+                )
+            }
             KeyguardPreviewClockViewBinder.bind(
                 keyguardRootView,
                 clockViewModel,
@@ -354,19 +433,22 @@
             )
         }
 
-        setUpSmartspace(previewContext, rootView)
-
-        smartSpaceView?.let {
-            KeyguardPreviewSmartspaceViewBinder.bind(
-                it,
-                smartspaceViewModel,
-                clockPreviewConfig =
-                    ClockPreviewConfig(
-                        previewContext,
-                        getPreviewShadeLayoutWide(display!!),
-                        SceneContainerFlag.isEnabled,
-                    ),
-            )
+        if (!com.android.systemui.shared.Flags.clockReactiveSmartspaceLayout()) {
+            setUpSmartspace(previewContext, keyguardRootView)
+            smartSpaceView?.let {
+                KeyguardPreviewSmartspaceViewBinder.bind(
+                    it,
+                    smartspaceViewModel,
+                    clockPreviewConfig =
+                        ClockPreviewConfig(
+                            previewContext,
+                            getPreviewShadeLayoutWide(display!!),
+                            SceneContainerFlag.isEnabled,
+                            lockId = null,
+                            udfpsTop = null,
+                        ),
+                )
+            }
         }
     }
 
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 ef57978..8920c86 100644
--- a/packages/SystemUI/src/com/android/systemui/qs/composefragment/QSFragmentCompose.kt
+++ b/packages/SystemUI/src/com/android/systemui/qs/composefragment/QSFragmentCompose.kt
@@ -747,22 +747,24 @@
                         val BrightnessSlider =
                             @Composable {
                                 AlwaysDarkMode {
-                                    BrightnessSliderContainer(
-                                        viewModel = containerViewModel.brightnessSliderViewModel,
-                                        containerColors =
-                                            ContainerColors(
-                                                Color.Transparent,
-                                                ContainerColors.defaultContainerColor,
-                                            ),
-                                        modifier =
-                                            Modifier.systemGestureExclusionInShade(
-                                                    enabled = {
-                                                        layoutState.transitionState is
-                                                            TransitionState.Idle
-                                                    }
-                                                )
-                                                .fillMaxWidth(),
-                                    )
+                                    Box(
+                                        Modifier.systemGestureExclusionInShade(
+                                            enabled = {
+                                                layoutState.transitionState is TransitionState.Idle
+                                            }
+                                        )
+                                    ) {
+                                        BrightnessSliderContainer(
+                                            viewModel =
+                                                containerViewModel.brightnessSliderViewModel,
+                                            containerColors =
+                                                ContainerColors(
+                                                    Color.Transparent,
+                                                    ContainerColors.defaultContainerColor,
+                                                ),
+                                            modifier = Modifier.fillMaxWidth(),
+                                        )
+                                    }
                                 }
                             }
                         val TileGrid =
diff --git a/packages/SystemUI/src/com/android/systemui/qs/panels/ui/compose/selection/Selection.kt b/packages/SystemUI/src/com/android/systemui/qs/panels/ui/compose/selection/Selection.kt
index 153238fc..a66b51f 100644
--- a/packages/SystemUI/src/com/android/systemui/qs/panels/ui/compose/selection/Selection.kt
+++ b/packages/SystemUI/src/com/android/systemui/qs/panels/ui/compose/selection/Selection.kt
@@ -59,12 +59,14 @@
 import androidx.compose.ui.unit.Constraints
 import androidx.compose.ui.unit.Dp
 import androidx.compose.ui.unit.dp
+import androidx.compose.ui.unit.sp
 import androidx.compose.ui.unit.toSize
 import androidx.compose.ui.zIndex
 import com.android.compose.modifiers.size
 import com.android.compose.modifiers.thenIf
 import com.android.systemui.qs.panels.ui.compose.infinitegrid.CommonTileDefaults.InactiveCornerRadius
 import com.android.systemui.qs.panels.ui.compose.selection.SelectionDefaults.BADGE_ANGLE_RAD
+import com.android.systemui.qs.panels.ui.compose.selection.SelectionDefaults.BadgeIconSize
 import com.android.systemui.qs.panels.ui.compose.selection.SelectionDefaults.BadgeSize
 import com.android.systemui.qs.panels.ui.compose.selection.SelectionDefaults.BadgeXOffset
 import com.android.systemui.qs.panels.ui.compose.selection.SelectionDefaults.BadgeYOffset
@@ -149,16 +151,14 @@
                         onClick = onClick,
                     )
             ) {
+                val size = with(LocalDensity.current) { BadgeIconSize.toDp() }
                 Icon(
                     Icons.Default.Remove,
                     contentDescription = null,
                     modifier =
-                        Modifier.size(
-                                width = { decorationSize.width.roundToInt() },
-                                height = { decorationSize.height.roundToInt() },
-                            )
-                            .align(Alignment.Center)
-                            .graphicsLayer { this.alpha = badgeIconAlpha },
+                        Modifier.size(size).align(Alignment.Center).graphicsLayer {
+                            this.alpha = badgeIconAlpha
+                        },
                 )
             }
         }
@@ -219,12 +219,13 @@
                 }
         ) {
             val secondaryColor = MaterialTheme.colorScheme.secondary
+            val size = with(LocalDensity.current) { BadgeIconSize.toDp() }
             Icon(
                 icon,
                 contentDescription = contentDescription,
                 modifier =
-                    Modifier.size(BadgeSize).align(Alignment.Center).drawBehind {
-                        drawCircle(secondaryColor)
+                    Modifier.size(size).align(Alignment.Center).drawBehind {
+                        drawCircle(secondaryColor, radius = BadgeSize.toPx() / 2)
                     },
             )
         }
@@ -338,6 +339,7 @@
 private object SelectionDefaults {
     val SelectedBorderWidth = 2.dp
     val BadgeSize = 24.dp
+    val BadgeIconSize = 16.sp
     val BadgeXOffset = -4.dp
     val BadgeYOffset = 4.dp
     val ResizingPillWidth = 8.dp
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 20b44d7..5609326 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
@@ -26,6 +26,7 @@
 import androidx.compose.runtime.getValue
 import androidx.compose.ui.graphics.Color
 import com.android.app.tracing.coroutines.launchTraced as launch
+import com.android.compose.animation.scene.OverlayKey
 import com.android.systemui.battery.BatteryMeterViewController
 import com.android.systemui.lifecycle.ExclusiveActivatable
 import com.android.systemui.lifecycle.Hydrator
@@ -86,6 +87,22 @@
         (ViewGroup, StatusBarLocation) -> BatteryMeterViewController =
         batteryMeterViewControllerFactory::create
 
+    val showClock: Boolean by
+        hydrator.hydratedStateOf(
+            traceName = "showClock",
+            initialValue =
+                shouldShowClock(
+                    isShadeLayoutWide = shadeInteractor.isShadeLayoutWide.value,
+                    overlays = sceneInteractor.currentOverlays.value,
+                ),
+            source =
+                combine(
+                    shadeInteractor.isShadeLayoutWide,
+                    sceneInteractor.currentOverlays,
+                    ::shouldShowClock,
+                ),
+        )
+
     val notificationsChipHighlight: HeaderChipHighlight by
         hydrator.hydratedStateOf(
             traceName = "notificationsChipHighlight",
@@ -114,13 +131,6 @@
                 },
         )
 
-    val isShadeLayoutWide: Boolean by
-        hydrator.hydratedStateOf(
-            traceName = "isShadeLayoutWide",
-            initialValue = shadeInteractor.isShadeLayoutWide.value,
-            source = shadeInteractor.isShadeLayoutWide,
-        )
-
     /** True if there is exactly one mobile connection. */
     val isSingleCarrier: StateFlow<Boolean> = mobileIconsInteractor.isSingleCarrier
 
@@ -271,6 +281,11 @@
         }
     }
 
+    private fun shouldShowClock(isShadeLayoutWide: Boolean, overlays: Set<OverlayKey>): Boolean {
+        // Notifications shade on narrow layout renders its own clock. Hide the header clock.
+        return isShadeLayoutWide || Overlays.NotificationsShade !in overlays
+    }
+
     private fun getFormatFromPattern(pattern: String?): DateFormat {
         val format = DateFormat.getInstanceForSkeleton(pattern, Locale.getDefault())
         format.setContext(DisplayContext.CAPITALIZATION_FOR_STANDALONE)
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/NotificationGroupingUtil.java b/packages/SystemUI/src/com/android/systemui/statusbar/NotificationGroupingUtil.java
index c2e355d..03c191e 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/NotificationGroupingUtil.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/NotificationGroupingUtil.java
@@ -22,6 +22,7 @@
 import android.app.Notification;
 import android.graphics.drawable.Drawable;
 import android.graphics.drawable.Icon;
+import android.service.notification.StatusBarNotification;
 import android.text.TextUtils;
 import android.util.DisplayMetrics;
 import android.util.TypedValue;
@@ -31,6 +32,8 @@
 import android.widget.ImageView;
 import android.widget.TextView;
 
+import androidx.annotation.VisibleForTesting;
+
 import com.android.internal.R;
 import com.android.internal.widget.CachingIconView;
 import com.android.internal.widget.ConversationLayout;
@@ -39,6 +42,7 @@
 import com.android.systemui.statusbar.notification.row.NotificationContentView;
 import com.android.systemui.statusbar.notification.row.shared.AsyncGroupHeaderViewInflation;
 import com.android.systemui.statusbar.notification.row.wrapper.NotificationViewWrapper;
+import com.android.systemui.statusbar.notification.shared.NotificationBundleUi;
 
 import java.util.ArrayList;
 import java.util.HashSet;
@@ -214,7 +218,7 @@
         }
         // in case no view is visible we make sure the time is visible
         int timeVisibility = !hasVisibleText
-                || row.getEntry().getSbn().getNotification().showsTime()
+                || showsTime(row)
                 ? View.VISIBLE : View.GONE;
         time.setVisibility(timeVisibility);
         View left = null;
@@ -243,6 +247,17 @@
         }
     }
 
+    @VisibleForTesting
+    boolean showsTime(ExpandableNotificationRow row) {
+        StatusBarNotification sbn;
+        if (NotificationBundleUi.isEnabled()) {
+            sbn = row.getEntryAdapter() != null ? row.getEntryAdapter().getSbn() : null;
+        } else {
+            sbn = row.getEntry().getSbn();
+        }
+        return (sbn != null && sbn.getNotification().showsTime());
+    }
+
     /**
      * Reset the modifications to this row for removing it from the group.
      */
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/chips/StatusBarChipsReturnAnimations.kt b/packages/SystemUI/src/com/android/systemui/statusbar/chips/StatusBarChipsReturnAnimations.kt
new file mode 100644
index 0000000..1764194
--- /dev/null
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/chips/StatusBarChipsReturnAnimations.kt
@@ -0,0 +1,31 @@
+/*
+ * 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.chips
+
+import com.android.systemui.Flags
+import com.android.systemui.statusbar.phone.ongoingcall.StatusBarChipsModernization
+
+/** Helper for reading or using the status_bar_chips_return_animations flag state. */
+object StatusBarChipsReturnAnimations {
+    /** The aconfig flag name */
+    const val FLAG_NAME = Flags.FLAG_STATUS_BAR_CHIPS_RETURN_ANIMATIONS
+
+    /** Is the feature enabled. */
+    @JvmStatic
+    inline val isEnabled
+        get() = StatusBarChipsModernization.isEnabled && Flags.statusBarChipsReturnAnimations()
+}
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/chips/call/ui/viewmodel/CallChipViewModel.kt b/packages/SystemUI/src/com/android/systemui/statusbar/chips/call/ui/viewmodel/CallChipViewModel.kt
index 16bf43b..7e70312 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/chips/call/ui/viewmodel/CallChipViewModel.kt
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/chips/call/ui/viewmodel/CallChipViewModel.kt
@@ -32,6 +32,7 @@
 import com.android.systemui.res.R
 import com.android.systemui.statusbar.chips.StatusBarChipLogTags.pad
 import com.android.systemui.statusbar.chips.StatusBarChipsLog
+import com.android.systemui.statusbar.chips.StatusBarChipsReturnAnimations
 import com.android.systemui.statusbar.chips.call.domain.interactor.CallChipInteractor
 import com.android.systemui.statusbar.chips.ui.model.ColorsModel
 import com.android.systemui.statusbar.chips.ui.model.OngoingActivityChipModel
@@ -43,8 +44,10 @@
 import com.android.systemui.util.time.SystemClock
 import javax.inject.Inject
 import kotlinx.coroutines.CoroutineScope
+import kotlinx.coroutines.flow.MutableStateFlow
 import kotlinx.coroutines.flow.SharingStarted
 import kotlinx.coroutines.flow.StateFlow
+import kotlinx.coroutines.flow.asStateFlow
 import kotlinx.coroutines.flow.map
 import kotlinx.coroutines.flow.stateIn
 
@@ -60,25 +63,60 @@
     private val activityStarter: ActivityStarter,
     @StatusBarChipsLog private val logger: LogBuffer,
 ) : OngoingActivityChipViewModel {
-    override val chip: StateFlow<OngoingActivityChipModel> =
-        interactor.ongoingCallState
-            .map { state ->
-                when (state) {
-                    is OngoingCallModel.NoCall -> OngoingActivityChipModel.Inactive()
-                    is OngoingCallModel.InCall ->
-                        if (state.isAppVisible) {
-                            OngoingActivityChipModel.Inactive()
-                        } else {
-                            prepareChip(state, systemClock)
-                        }
+    private val chipWithReturnAnimation: StateFlow<OngoingActivityChipModel> =
+        if (StatusBarChipsReturnAnimations.isEnabled) {
+            interactor.ongoingCallState
+                .map { state ->
+                    when (state) {
+                        is OngoingCallModel.NoCall -> OngoingActivityChipModel.Inactive()
+                        is OngoingCallModel.InCall ->
+                            prepareChip(state, systemClock, isHidden = state.isAppVisible)
+                    }
                 }
-            }
-            .stateIn(scope, SharingStarted.WhileSubscribed(), OngoingActivityChipModel.Inactive())
+                .stateIn(
+                    scope,
+                    SharingStarted.WhileSubscribed(),
+                    OngoingActivityChipModel.Inactive(),
+                )
+        } else {
+            MutableStateFlow(OngoingActivityChipModel.Inactive()).asStateFlow()
+        }
+
+    private val chipLegacy: StateFlow<OngoingActivityChipModel> =
+        if (!StatusBarChipsReturnAnimations.isEnabled) {
+            interactor.ongoingCallState
+                .map { state ->
+                    when (state) {
+                        is OngoingCallModel.NoCall -> OngoingActivityChipModel.Inactive()
+                        is OngoingCallModel.InCall ->
+                            if (state.isAppVisible) {
+                                OngoingActivityChipModel.Inactive()
+                            } else {
+                                prepareChip(state, systemClock, isHidden = false)
+                            }
+                    }
+                }
+                .stateIn(
+                    scope,
+                    SharingStarted.WhileSubscribed(),
+                    OngoingActivityChipModel.Inactive(),
+                )
+        } else {
+            MutableStateFlow(OngoingActivityChipModel.Inactive()).asStateFlow()
+        }
+
+    override val chip: StateFlow<OngoingActivityChipModel> =
+        if (StatusBarChipsReturnAnimations.isEnabled) {
+            chipWithReturnAnimation
+        } else {
+            chipLegacy
+        }
 
     /** Builds an [OngoingActivityChipModel.Active] from all the relevant information. */
     private fun prepareChip(
         state: OngoingCallModel.InCall,
         systemClock: SystemClock,
+        isHidden: Boolean,
     ): OngoingActivityChipModel.Active {
         val key = state.notificationKey
         val contentDescription = getContentDescription(state.appName)
@@ -110,6 +148,7 @@
                 colors = colors,
                 onClickListenerLegacy = getOnClickListener(state.intent),
                 clickBehavior = getClickBehavior(state.intent),
+                isHidden = isHidden,
             )
         } else {
             val startTimeInElapsedRealtime =
@@ -121,6 +160,7 @@
                 startTimeMs = startTimeInElapsedRealtime,
                 onClickListenerLegacy = getOnClickListener(state.intent),
                 clickBehavior = getClickBehavior(state.intent),
+                isHidden = isHidden,
             )
         }
     }
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 41353b9..4d68f2e 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
@@ -63,17 +63,6 @@
 
     @Nullable
     @Override
-    public NotifSection getSection() {
-        return null;
-    }
-
-    @Override
-    public int getSectionIndex() {
-        return 0;
-    }
-
-    @Nullable
-    @Override
     public PipelineEntry getParent() {
         return null;
     }
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 4a1b956..04dc7d5 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
@@ -31,8 +31,8 @@
     var parent: PipelineEntry?,
 
     /**
-     * The section that this ListEntry was sorted into. If the child of the group, this will be the
-     * parent's section. Null if not attached to the list.
+     * The section that this PipelineEntry was sorted into. If the child of the group, this will be
+     * the parent's section. Null if not attached to the list.
      */
     var section: NotifSection?,
 
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 697d0a0..caa7abb 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
@@ -66,10 +66,6 @@
         return mPreviousAttachState.getParent();
     }
 
-    public int getSectionIndex() {
-        return mAttachState.getSection() != null ? mAttachState.getSection().getIndex() : -1;
-    }
-
     /**
      * Stores the current attach state into {@link #getPreviousAttachState()}} and then starts a
      * fresh attach state (all entries will be null/default-initialized).
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/NotifPipeline.kt b/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/NotifPipeline.kt
index f662a04..0fc0e9c 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/NotifPipeline.kt
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/NotifPipeline.kt
@@ -24,6 +24,7 @@
 import com.android.systemui.statusbar.notification.collection.listbuilder.OnBeforeSortListener
 import com.android.systemui.statusbar.notification.collection.listbuilder.OnBeforeTransformGroupsListener
 import com.android.systemui.statusbar.notification.collection.listbuilder.pluggable.Invalidator
+import com.android.systemui.statusbar.notification.collection.listbuilder.pluggable.NotifBundler
 import com.android.systemui.statusbar.notification.collection.listbuilder.pluggable.NotifComparator
 import com.android.systemui.statusbar.notification.collection.listbuilder.pluggable.NotifFilter
 import com.android.systemui.statusbar.notification.collection.listbuilder.pluggable.NotifPromoter
@@ -169,6 +170,14 @@
     }
 
     /**
+     * NotifBundler that is used to determine whether a notification should be bundled according to
+     * classification.
+     */
+    fun setNotifBundler(bundler: NotifBundler) {
+        mShadeListBuilder.setBundler(bundler)
+    }
+
+    /**
      * StabilityManager that is used to determine whether to suppress group and section changes.
      * This should only be set once.
      */
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 84de77b..872cd68 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
@@ -70,7 +70,9 @@
     /**
      * @return Index of section assigned to this entry.
      */
-    public abstract int getSectionIndex();
+    public int getSectionIndex() {
+        return mAttachState.getSection() != null ? mAttachState.getSection().getIndex() : -1;
+    }
 
     /**
      * @return Parent PipelineEntry
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 bb84ab8..238ba8d 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
@@ -48,6 +48,7 @@
 import com.android.systemui.dump.DumpManager;
 import com.android.systemui.statusbar.NotificationInteractionTracker;
 import com.android.systemui.statusbar.notification.NotifPipelineFlags;
+import com.android.systemui.statusbar.notification.collection.coordinator.BundleCoordinator;
 import com.android.systemui.statusbar.notification.collection.listbuilder.NotifSection;
 import com.android.systemui.statusbar.notification.collection.listbuilder.OnBeforeFinalizeFilterListener;
 import com.android.systemui.statusbar.notification.collection.listbuilder.OnBeforeRenderListListener;
@@ -58,8 +59,10 @@
 import com.android.systemui.statusbar.notification.collection.listbuilder.SemiStableSort.StableOrder;
 import com.android.systemui.statusbar.notification.collection.listbuilder.ShadeListBuilderHelper;
 import com.android.systemui.statusbar.notification.collection.listbuilder.ShadeListBuilderLogger;
+import com.android.systemui.statusbar.notification.collection.listbuilder.pluggable.DefaultNotifBundler;
 import com.android.systemui.statusbar.notification.collection.listbuilder.pluggable.DefaultNotifStabilityManager;
 import com.android.systemui.statusbar.notification.collection.listbuilder.pluggable.Invalidator;
+import com.android.systemui.statusbar.notification.collection.listbuilder.pluggable.NotifBundler;
 import com.android.systemui.statusbar.notification.collection.listbuilder.pluggable.NotifComparator;
 import com.android.systemui.statusbar.notification.collection.listbuilder.pluggable.NotifFilter;
 import com.android.systemui.statusbar.notification.collection.listbuilder.pluggable.NotifPromoter;
@@ -78,6 +81,7 @@
 import java.util.Collection;
 import java.util.Collections;
 import java.util.Comparator;
+import java.util.HashMap;
 import java.util.Iterator;
 import java.util.List;
 import java.util.Map;
@@ -122,7 +126,8 @@
     private final List<NotifComparator> mNotifComparators = new ArrayList<>();
     private final List<NotifSection> mNotifSections = new ArrayList<>();
     private NotifStabilityManager mNotifStabilityManager;
-
+    private NotifBundler mNotifBundler;
+    private Map<String, BundleEntry> mIdToBundleEntry = new HashMap<>();
     private final NamedListenerSet<OnBeforeTransformGroupsListener>
             mOnBeforeTransformGroupsListeners = new NamedListenerSet<>();
     private final NamedListenerSet<OnBeforeSortListener>
@@ -273,6 +278,21 @@
         }
     }
 
+    void setBundler(NotifBundler bundler) {
+        Assert.isMainThread();
+        mPipelineState.requireState(STATE_IDLE);
+
+        mNotifBundler = bundler;
+        if (mNotifBundler == null) {
+            throw new IllegalStateException(TAG + ".setBundler: null");
+        }
+
+        mIdToBundleEntry.clear();
+        for (String id: mNotifBundler.getBundleIds()) {
+            mIdToBundleEntry.put(id, new BundleEntry(id));
+        }
+    }
+
     void setNotifStabilityManager(@NonNull NotifStabilityManager notifStabilityManager) {
         Assert.isMainThread();
         mPipelineState.requireState(STATE_IDLE);
@@ -297,6 +317,14 @@
         return mNotifStabilityManager;
     }
 
+    @NonNull
+    private NotifBundler getNotifBundler() {
+        if (mNotifBundler == null) {
+            return DefaultNotifBundler.INSTANCE;
+        }
+        return mNotifBundler;
+    }
+
     void setComparators(List<NotifComparator> comparators) {
         Assert.isMainThread();
         mPipelineState.requireState(STATE_IDLE);
@@ -651,7 +679,7 @@
                         j--;
                     }
                 }
-            } else {
+            } else if (tle instanceof NotificationEntry) {
                 // maybe put top-level-entries back into their previous groups
                 if (maybeSuppressGroupChange(tle.getRepresentativeEntry(), topLevelList)) {
                     // entry was put back into its previous group, so we remove it from the list of
@@ -659,7 +687,7 @@
                     topLevelList.remove(i);
                     i--;
                 }
-            }
+            } // Promoters ignore bundles so we don't have to demote any here.
         }
         Trace.endSection();
     }
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 e6d5f41..8833ff1 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,15 +20,19 @@
 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.PipelineEntry
+import android.app.NotificationChannel.SYSTEM_RESERVED_IDS
 import com.android.systemui.statusbar.notification.collection.NotifPipeline
+import com.android.systemui.statusbar.notification.collection.NotificationEntry
+import com.android.systemui.statusbar.notification.collection.PipelineEntry
 import com.android.systemui.statusbar.notification.collection.coordinator.dagger.CoordinatorScope
+import com.android.systemui.statusbar.notification.collection.listbuilder.pluggable.NotifBundler
 import com.android.systemui.statusbar.notification.collection.listbuilder.pluggable.NotifSectioner
 import com.android.systemui.statusbar.notification.collection.render.NodeController
 import com.android.systemui.statusbar.notification.dagger.NewsHeader
 import com.android.systemui.statusbar.notification.dagger.PromoHeader
 import com.android.systemui.statusbar.notification.dagger.RecsHeader
 import com.android.systemui.statusbar.notification.dagger.SocialHeader
+import com.android.systemui.statusbar.notification.shared.NotificationBundleUi
 import com.android.systemui.statusbar.notification.stack.BUCKET_NEWS
 import com.android.systemui.statusbar.notification.stack.BUCKET_PROMO
 import com.android.systemui.statusbar.notification.stack.BUCKET_RECS
@@ -90,6 +94,20 @@
             }
         }
 
+    val bundler =
+        object : NotifBundler("NotifBundler") {
+
+            // Use list instead of set to keep fixed order
+            override val bundleIds: List<String> = SYSTEM_RESERVED_IDS
+
+            override fun getBundleIdOrNull(entry: NotificationEntry?): String? {
+                return entry?.representativeEntry?.channel?.id?.takeIf { it in this.bundleIds }
+            }
+        }
+
     override fun attach(pipeline: NotifPipeline) {
+        if (NotificationBundleUi.isEnabled) {
+            pipeline.setNotifBundler(bundler)
+        }
     }
 }
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 fdb8cd8..5d981b3 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
@@ -26,6 +26,7 @@
 import com.android.systemui.statusbar.chips.notification.domain.interactor.StatusBarNotificationChipsInteractor
 import com.android.systemui.statusbar.chips.notification.shared.StatusBarNotifChips
 import com.android.systemui.statusbar.notification.NotifPipelineFlags
+import com.android.systemui.statusbar.notification.collection.BundleEntry
 import com.android.systemui.statusbar.notification.collection.GroupEntry
 import com.android.systemui.statusbar.notification.collection.NotifCollection
 import com.android.systemui.statusbar.notification.collection.NotifPipeline
@@ -423,6 +424,7 @@
                             map[child.key] = GroupLocation.Child
                         }
                     }
+                    is BundleEntry -> map[topLevelEntry.key] = GroupLocation.Bundle
                     else -> error("unhandled type $topLevelEntry")
                 }
             }
@@ -950,6 +952,7 @@
     Isolated,
     Summary,
     Child,
+    Bundle,
 }
 
 private fun Map<String, GroupLocation>.getLocation(key: String): GroupLocation =
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 56deb18..d542e67 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
@@ -26,6 +26,7 @@
 import com.android.systemui.plugins.statusbar.StatusBarStateController
 import com.android.systemui.shade.domain.interactor.ShadeInteractor
 import com.android.systemui.statusbar.StatusBarState
+import com.android.systemui.statusbar.notification.collection.BundleEntry
 import com.android.systemui.statusbar.notification.collection.GroupEntry
 import com.android.systemui.statusbar.notification.collection.PipelineEntry
 import com.android.systemui.statusbar.notification.collection.NotifPipeline
@@ -193,6 +194,7 @@
                     when (it) {
                         is NotificationEntry -> listOfNotNull(it)
                         is GroupEntry -> it.children
+                        is BundleEntry -> emptyList()
                         else -> error("unhandled type of $it")
                     }
                 }
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/coordinator/NotifCoordinators.kt b/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/coordinator/NotifCoordinators.kt
index df0cde5..818ef6b 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/coordinator/NotifCoordinators.kt
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/coordinator/NotifCoordinators.kt
@@ -24,6 +24,7 @@
 import com.android.systemui.statusbar.notification.collection.listbuilder.pluggable.NotifSectioner
 import com.android.systemui.statusbar.notification.collection.provider.SectionStyleProvider
 import com.android.systemui.statusbar.notification.promoted.AutomaticPromotionCoordinator
+import com.android.systemui.statusbar.notification.shared.NotificationBundleUi
 import com.android.systemui.statusbar.notification.shared.NotificationMinimalism
 import com.android.systemui.statusbar.notification.shared.NotificationsLiveDataStoreRefactor
 import com.android.systemui.statusbar.notification.shared.PriorityPeopleSection
@@ -113,11 +114,12 @@
         mCoordinators.add(remoteInputCoordinator)
         mCoordinators.add(dismissibilityCoordinator)
         mCoordinators.add(automaticPromotionCoordinator)
-
+        if (NotificationBundleUi.isEnabled) {
+            mCoordinators.add(bundleCoordinator)
+        }
         if (NotificationsLiveDataStoreRefactor.isEnabled) {
             mCoordinators.add(statsLoggerCoordinator)
         }
-
         // Manually add Ordered Sections
         if (NotificationMinimalism.isEnabled) {
             mOrderedSections.add(lockScreenMinimalismCoordinator.topOngoingSectioner) // Top Ongoing
@@ -135,7 +137,7 @@
             mOrderedSections.add(conversationCoordinator.peopleSilentSectioner) // People Silent
         }
         mOrderedSections.add(rankingCoordinator.alertingSectioner) // Alerting
-        if (NotificationClassificationFlag.isEnabled) {
+        if (NotificationClassificationFlag.isEnabled && !NotificationBundleUi.isEnabled) {
             mOrderedSections.add(bundleCoordinator.newsSectioner)
             mOrderedSections.add(bundleCoordinator.socialSectioner)
             mOrderedSections.add(bundleCoordinator.recsSectioner)
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 20c6736..b54f21b 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
@@ -34,6 +34,7 @@
 
 import com.android.internal.annotations.VisibleForTesting;
 import com.android.internal.statusbar.IStatusBarService;
+import com.android.systemui.statusbar.notification.collection.BundleEntry;
 import com.android.systemui.statusbar.notification.collection.GroupEntry;
 import com.android.systemui.statusbar.notification.collection.PipelineEntry;
 import com.android.systemui.statusbar.notification.collection.NotifPipeline;
@@ -54,6 +55,7 @@
 import com.android.systemui.statusbar.notification.row.icon.NotificationIconStyleProvider;
 import com.android.systemui.statusbar.notification.row.shared.AsyncGroupHeaderViewInflation;
 import com.android.systemui.statusbar.notification.row.shared.AsyncHybridViewInflation;
+import com.android.systemui.statusbar.notification.shared.NotificationBundleUi;
 
 import java.lang.annotation.Retention;
 import java.lang.annotation.RetentionPolicy;
@@ -306,7 +308,9 @@
     private void inflateAllRequiredViews(List<PipelineEntry> entries) {
         for (int i = 0, size = entries.size(); i < size; i++) {
             PipelineEntry entry = entries.get(i);
-            if (entry instanceof GroupEntry) {
+            if (NotificationBundleUi.isEnabled() && entry instanceof BundleEntry) {
+                // TODO(b/399738511) Inflate bundle views.
+            } else if (entry instanceof GroupEntry) {
                 GroupEntry groupEntry = (GroupEntry) entry;
                 inflateRequiredGroupViews(groupEntry);
             } else {
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 d1063d9..3fad8f0 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
@@ -132,7 +132,12 @@
         public void onEntriesUpdated(@NonNull List<PipelineEntry> entries) {
             mHasSilentEntries = false;
             for (int i = 0; i < entries.size(); i++) {
-                if (entries.get(i).getRepresentativeEntry().getSbn().isClearable()) {
+                NotificationEntry notifEntry = entries.get(i).getRepresentativeEntry();
+                if (notifEntry == null) {
+                    // TODO(b/395698521) Handle BundleEntry
+                    continue;
+                }
+                if (notifEntry.getSbn().isClearable()) {
                     mHasSilentEntries = true;
                     break;
                 }
@@ -147,6 +152,7 @@
         @Override
         public boolean isInSection(PipelineEntry entry) {
             return !mHighPriorityProvider.isHighPriority(entry)
+                    && entry.getRepresentativeEntry() != null
                     && entry.getRepresentativeEntry().isAmbient();
         }
 
@@ -161,7 +167,12 @@
         public void onEntriesUpdated(@NonNull List<PipelineEntry> entries) {
             mHasMinimizedEntries = false;
             for (int i = 0; i < entries.size(); i++) {
-                if (entries.get(i).getRepresentativeEntry().getSbn().isClearable()) {
+                NotificationEntry notifEntry = entries.get(i).getRepresentativeEntry();
+                if (notifEntry == null) {
+                    // TODO(b/395698521) Handle BundleEntry
+                    continue;
+                }
+                if (notifEntry.getSbn().isClearable()) {
                     mHasMinimizedEntries = true;
                     break;
                 }
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/listbuilder/pluggable/NotifBundler.kt b/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/listbuilder/pluggable/NotifBundler.kt
new file mode 100644
index 0000000..14a9113
--- /dev/null
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/listbuilder/pluggable/NotifBundler.kt
@@ -0,0 +1,35 @@
+/*
+ * 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.notification.collection.listbuilder.pluggable
+
+import com.android.systemui.statusbar.notification.collection.NotificationEntry
+
+/** Pluggable for bundling notifications according to classification. */
+abstract class NotifBundler protected constructor(name: String?) : Pluggable<NotifBundler?>(name) {
+    abstract val bundleIds: List<String>
+
+    abstract fun getBundleIdOrNull(entry: NotificationEntry?): String?
+}
+
+/** The default, no-op instance of NotifBundler which does not bundle anything. */
+object DefaultNotifBundler : NotifBundler("DefaultNotifBundler") {
+    override val bundleIds: List<String>
+        get() = listOf()
+
+    override fun getBundleIdOrNull(entry: NotificationEntry?): String? {
+        return null
+    }
+}
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/ExpandableNotificationRow.java b/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/ExpandableNotificationRow.java
index c877bee..b481455 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
@@ -68,6 +68,7 @@
 import android.view.ViewParent;
 import android.view.ViewStub;
 import android.view.accessibility.AccessibilityEvent;
+import android.view.accessibility.AccessibilityManager;
 import android.view.accessibility.AccessibilityNodeInfo;
 import android.view.accessibility.AccessibilityNodeInfo.AccessibilityAction;
 import android.widget.Chronometer;
@@ -114,7 +115,6 @@
 import com.android.systemui.statusbar.notification.SourceType;
 import com.android.systemui.statusbar.notification.collection.EntryAdapter;
 import com.android.systemui.statusbar.notification.collection.NotificationEntry;
-import com.android.systemui.statusbar.notification.collection.NotificationEntryAdapter;
 import com.android.systemui.statusbar.notification.collection.PipelineEntry;
 import com.android.systemui.statusbar.notification.collection.provider.NotificationDismissibilityProvider;
 import com.android.systemui.statusbar.notification.collection.render.GroupExpansionManager;
@@ -123,6 +123,7 @@
 import com.android.systemui.statusbar.notification.headsup.PinnedStatus;
 import com.android.systemui.statusbar.notification.logging.NotificationCounters;
 import com.android.systemui.statusbar.notification.people.PeopleNotificationIdentifier;
+import com.android.systemui.statusbar.notification.promoted.PromotedNotificationUi;
 import com.android.systemui.statusbar.notification.promoted.PromotedNotificationUiForceExpanded;
 import com.android.systemui.statusbar.notification.row.shared.AsyncGroupHeaderViewInflation;
 import com.android.systemui.statusbar.notification.row.shared.LockscreenOtpRedaction;
@@ -185,7 +186,6 @@
     private boolean mShowSnooze = false;
     private boolean mIsFaded;
 
-    private boolean mIsPromotedOngoing = false;
     private boolean mHasStatusBarChipDuringHeadsUpAnimation = false;
 
     @Nullable
@@ -871,7 +871,7 @@
 
     private void updateLimitsForView(NotificationContentView layout) {
         final int maxExpandedHeight;
-        if (isPromotedOngoing()) {
+        if (PromotedNotificationUiForceExpanded.isEnabled() && isPromotedOngoing()) {
             maxExpandedHeight = mMaxExpandedHeightForPromotedOngoing;
         } else {
             maxExpandedHeight = mMaxExpandedHeight;
@@ -1350,7 +1350,7 @@
         if (mIsSummaryWithChildren) {
             return mChildrenContainer.getIntrinsicHeight();
         }
-        if (isPromotedOngoing()) {
+        if (PromotedNotificationUiForceExpanded.isEnabled() && isPromotedOngoing()) {
             return getMaxExpandHeight();
         }
         if (mExpandedWhenPinned) {
@@ -2939,7 +2939,7 @@
         if (mIsSummaryWithChildren && !shouldShowPublic()) {
             return !mChildrenExpanded;
         }
-        if (isPromotedOngoing()) {
+        if (PromotedNotificationUiForceExpanded.isEnabled() && isPromotedOngoing()) {
             return false;
         }
         return mEnableNonGroupedNotificationExpand && mExpandable;
@@ -2950,17 +2950,6 @@
         mPrivateLayout.updateExpandButtons(isExpandable());
     }
 
-    /**
-     * Set this notification to be promoted ongoing
-     */
-    public void setPromotedOngoing(boolean promotedOngoing) {
-        if (PromotedNotificationUiForceExpanded.isUnexpectedlyInLegacyMode()) {
-            return;
-        }
-
-        mIsPromotedOngoing = promotedOngoing;
-        setExpandable(!mIsPromotedOngoing);
-    }
 
     /**
      * Sets whether the status bar is showing a chip corresponding to this notification.
@@ -3061,7 +3050,7 @@
     }
 
     public void setUserLocked(boolean userLocked) {
-        if (isPromotedOngoing()) return;
+        if (PromotedNotificationUiForceExpanded.isEnabled() && isPromotedOngoing()) return;
 
         mUserLocked = userLocked;
         mPrivateLayout.setUserExpanding(userLocked);
@@ -3247,7 +3236,16 @@
     }
 
     public boolean isPromotedOngoing() {
-        return PromotedNotificationUiForceExpanded.isEnabled() && mIsPromotedOngoing;
+        if (!PromotedNotificationUi.isEnabled()) {
+            return false;
+        }
+
+        final NotificationEntry entry = mEntry;
+        if (entry == null) {
+            return false;
+        }
+
+        return entry.isPromotedOngoing();
     }
 
     private boolean isPromotedNotificationExpanded(boolean allowOnKeyguard) {
@@ -3309,7 +3307,7 @@
     }
 
     public boolean isExpanded(boolean allowOnKeyguard) {
-        if (isPromotedOngoing()) {
+        if (PromotedNotificationUiForceExpanded.isEnabled() && isPromotedOngoing()) {
             return isPromotedNotificationExpanded(allowOnKeyguard);
         }
 
@@ -4038,10 +4036,12 @@
     }
 
     private void notifyAccessibilityContentExpansionChanged() {
-        AccessibilityEvent event = AccessibilityEvent.obtain(TYPE_WINDOW_CONTENT_CHANGED);
-        onPopulateAccessibilityEvent(event);
-        event.setContentChangeTypes(CONTENT_CHANGE_TYPE_EXPANDED);
-        sendAccessibilityEventUnchecked(event);
+        if (AccessibilityManager.getInstance(mContext).isEnabled()) {
+            AccessibilityEvent event = AccessibilityEvent.obtain();
+            event.setEventType(TYPE_WINDOW_CONTENT_CHANGED);
+            event.setContentChangeTypes(CONTENT_CHANGE_TYPE_EXPANDED);
+            sendAccessibilityEventUnchecked(event);
+        }
     }
 
     public void setOnExpansionChangedListener(@Nullable OnExpansionChangedListener listener) {
@@ -4396,9 +4396,7 @@
                     + (!shouldShowPublic() && mIsSummaryWithChildren));
             pw.print(", mShowNoBackground: " + mShowNoBackground);
             pw.print(", clipBounds: " + getClipBounds());
-            if (PromotedNotificationUiForceExpanded.isEnabled()) {
-                pw.print(", isPromotedOngoing: " + isPromotedOngoing());
-            }
+            pw.print(", isPromotedOngoing: " + isPromotedOngoing());
             if (notificationRowAccessibilityExpanded()) {
                 pw.print(", isShowingExpanded: " + isShowingExpanded());
                 pw.print(", isAccessibilityExpandable: " + isAccessibilityExpandable());
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/NotificationContentInflater.java b/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/NotificationContentInflater.java
index 51569c1..3ffc052 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/NotificationContentInflater.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/NotificationContentInflater.java
@@ -59,7 +59,6 @@
 import com.android.systemui.statusbar.notification.NotificationUtils;
 import com.android.systemui.statusbar.notification.collection.NotificationEntry;
 import com.android.systemui.statusbar.notification.promoted.PromotedNotificationContentExtractor;
-import com.android.systemui.statusbar.notification.promoted.PromotedNotificationUiForceExpanded;
 import com.android.systemui.statusbar.notification.promoted.shared.model.PromotedNotificationContentModel;
 import com.android.systemui.statusbar.notification.row.shared.AsyncGroupHeaderViewInflation;
 import com.android.systemui.statusbar.notification.row.shared.AsyncHybridViewInflation;
@@ -1006,10 +1005,6 @@
             entry.setPromotedNotificationContentModel(result.mPromotedContent);
         }
 
-        if (PromotedNotificationUiForceExpanded.isEnabled()) {
-            row.setPromotedOngoing(entry.isOngoingPromoted());
-        }
-
         boolean setRepliesAndActions = true;
         if ((reInflateFlags & FLAG_CONTENT_VIEW_CONTRACTED) != 0) {
             if (result.inflatedContentView != null) {
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/NotificationRowContentBinderImpl.kt b/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/NotificationRowContentBinderImpl.kt
index 482b315..b1c145e 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/NotificationRowContentBinderImpl.kt
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/NotificationRowContentBinderImpl.kt
@@ -53,7 +53,6 @@
 import com.android.systemui.statusbar.notification.collection.NotificationEntry
 import com.android.systemui.statusbar.notification.logKey
 import com.android.systemui.statusbar.notification.promoted.PromotedNotificationContentExtractor
-import com.android.systemui.statusbar.notification.promoted.PromotedNotificationUiForceExpanded
 import com.android.systemui.statusbar.notification.promoted.shared.model.PromotedNotificationContentModel
 import com.android.systemui.statusbar.notification.row.NotificationContentView.VISIBLE_TYPE_CONTRACTED
 import com.android.systemui.statusbar.notification.row.NotificationContentView.VISIBLE_TYPE_EXPANDED
@@ -1293,6 +1292,7 @@
                         runningInflations,
                         e,
                         row,
+                        entry,
                         callback,
                         logger,
                         "applying view synchronously",
@@ -1318,6 +1318,7 @@
                                 runningInflations,
                                 InflationException(invalidReason),
                                 row,
+                                entry,
                                 callback,
                                 logger,
                                 "applied invalid view",
@@ -1377,6 +1378,7 @@
                                 runningInflations,
                                 e,
                                 row,
+                                entry,
                                 callback,
                                 logger,
                                 "applying view",
@@ -1480,6 +1482,7 @@
             runningInflations: HashMap<Int, CancellationSignal>,
             e: Exception,
             notification: ExpandableNotificationRow?,
+            entry: NotificationEntry,
             callback: InflationCallback?,
             logger: NotificationRowContentBinderLogger,
             logContext: String,
@@ -1487,7 +1490,7 @@
             Assert.isMainThread()
             logger.logAsyncTaskException(notification?.loggingKey, logContext, e)
             runningInflations.values.forEach(Consumer { obj: CancellationSignal -> obj.cancel() })
-            callback?.handleInflationException(notification?.entry, e)
+            callback?.handleInflationException(entry, e)
         }
 
         /**
@@ -1520,10 +1523,6 @@
                 entry.promotedNotificationContentModel = result.promotedContent
             }
 
-            if (PromotedNotificationUiForceExpanded.isEnabled) {
-                row.setPromotedOngoing(entry.isOngoingPromoted())
-            }
-
             result.inflatedSmartReplyState?.let { row.privateLayout.setInflatedSmartReplyState(it) }
 
             setContentViewsFromRemoteViews(
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/shared/ActiveNotificationModel.kt b/packages/SystemUI/src/com/android/systemui/statusbar/notification/shared/ActiveNotificationModel.kt
index d7dd7ec..53728c7 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/shared/ActiveNotificationModel.kt
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/shared/ActiveNotificationModel.kt
@@ -17,6 +17,7 @@
 
 import android.app.PendingIntent
 import android.graphics.drawable.Icon
+import android.util.Log
 import com.android.systemui.statusbar.StatusBarIconView
 import com.android.systemui.statusbar.notification.promoted.shared.model.PromotedNotificationContentModel
 import com.android.systemui.statusbar.notification.stack.PriorityBucket
@@ -85,6 +86,15 @@
      */
     val promotedContent: PromotedNotificationContentModel?,
 ) : ActiveNotificationEntryModel() {
+    init {
+        if (!PromotedNotificationContentModel.featureFlagEnabled()) {
+            if (promotedContent != null) {
+                // TODO(b/401018545): convert to Log.wtf and fix tests (see: ag/32114199)
+                Log.e(TAG, "passing non-null promoted content without feature flag enabled")
+            }
+        }
+    }
+
     companion object {
         private const val TAG = "ActiveNotificationEntryModel"
     }
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/policy/SmartReplyStateInflater.kt b/packages/SystemUI/src/com/android/systemui/statusbar/policy/SmartReplyStateInflater.kt
index d3af1e5..6d959be 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/policy/SmartReplyStateInflater.kt
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/policy/SmartReplyStateInflater.kt
@@ -17,6 +17,7 @@
 package com.android.systemui.statusbar.policy
 
 import android.app.ActivityOptions
+import android.app.Flags.notificationsRedesignTemplates
 import android.app.Notification
 import android.app.Notification.Action.SEMANTIC_ACTION_MARK_CONVERSATION_AS_PRIORITY
 import android.app.PendingIntent
@@ -53,7 +54,6 @@
 import com.android.systemui.statusbar.notification.collection.NotificationEntry
 import com.android.systemui.statusbar.notification.headsup.HeadsUpManager
 import com.android.systemui.statusbar.notification.logging.NotificationLogger
-import com.android.systemui.statusbar.notification.row.MagicActionBackgroundDrawable
 import com.android.systemui.statusbar.phone.KeyguardDismissUtil
 import com.android.systemui.statusbar.policy.InflatedSmartReplyState.SuppressedActions
 import com.android.systemui.statusbar.policy.SmartReplyView.SmartActions
@@ -397,16 +397,21 @@
         delayOnClickListener: Boolean,
         packageContext: Context,
     ): Button {
-        val isMagicAction = Flags.notificationMagicActionsTreatment() &&
+        val isMagicAction =
+            Flags.notificationMagicActionsTreatment() &&
                 smartActions.fromAssistant &&
                 action.extras.getBoolean(Notification.Action.EXTRA_IS_MAGIC, false)
-        val layoutRes = if (isMagicAction) {
-            R.layout.magic_action_button
-        } else {
-            R.layout.smart_action_button
-        }
-        return (LayoutInflater.from(parent.context).inflate(layoutRes, parent, false)
-                as Button)
+        val layoutRes =
+            if (isMagicAction) {
+                R.layout.magic_action_button
+            } else {
+                if (notificationsRedesignTemplates()) {
+                    R.layout.notification_2025_smart_action_button
+                } else {
+                    R.layout.smart_action_button
+                }
+            }
+        return (LayoutInflater.from(parent.context).inflate(layoutRes, parent, false) as Button)
             .apply {
                 text = action.title
 
@@ -435,7 +440,6 @@
                 // Mark this as an Action button
                 (layoutParams as SmartReplyView.LayoutParams).mButtonType = SmartButtonType.ACTION
             }
-
     }
 
     private fun onSmartActionClick(
@@ -499,9 +503,11 @@
         replyIndex: Int,
         choice: CharSequence,
         delayOnClickListener: Boolean,
-    ): Button =
-        (LayoutInflater.from(parent.context).inflate(R.layout.smart_reply_button, parent, false)
-                as Button)
+    ): Button {
+        val layoutRes =
+            if (notificationsRedesignTemplates()) R.layout.notification_2025_smart_reply_button
+            else R.layout.smart_reply_button
+        return (LayoutInflater.from(parent.context).inflate(layoutRes, parent, false) as Button)
             .apply {
                 text = choice
                 val onClickListener =
@@ -531,6 +537,7 @@
                 // Mark this as a Reply button
                 (layoutParams as SmartReplyView.LayoutParams).mButtonType = SmartButtonType.REPLY
             }
+    }
 
     private fun onSmartReplyClick(
         entry: NotificationEntry,
diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/row/ExpandableNotificationRowTest.java b/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/row/ExpandableNotificationRowTest.java
index cd2ea7d..4315c0f 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/row/ExpandableNotificationRowTest.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/row/ExpandableNotificationRowTest.java
@@ -73,6 +73,7 @@
 import com.android.systemui.statusbar.notification.SourceType;
 import com.android.systemui.statusbar.notification.collection.NotificationEntry;
 import com.android.systemui.statusbar.notification.headsup.PinnedStatus;
+import com.android.systemui.statusbar.notification.promoted.PromotedNotificationUi;
 import com.android.systemui.statusbar.notification.promoted.PromotedNotificationUiForceExpanded;
 import com.android.systemui.statusbar.notification.row.ExpandableView.OnHeightChangedListener;
 import com.android.systemui.statusbar.notification.row.wrapper.NotificationViewWrapper;
@@ -936,11 +937,13 @@
     }
 
     @Test
-    @EnableFlags(PromotedNotificationUiForceExpanded.FLAG_NAME)
+    @EnableFlags({PromotedNotificationUi.FLAG_NAME, PromotedNotificationUiForceExpanded.FLAG_NAME})
     public void isExpanded_sensitivePromotedNotification_notExpanded() throws Exception {
         // GIVEN
         final ExpandableNotificationRow row = mNotificationTestHelper.createRow();
-        row.setPromotedOngoing(true);
+        NotificationEntry entry = mock(NotificationEntry.class);
+        when(entry.isPromotedOngoing()).thenReturn(true);
+        row.setEntry(entry);
         row.setSensitive(/* sensitive= */true, /* hideSensitive= */false);
         row.setHideSensitiveForIntrinsicHeight(/* hideSensitive= */true);
 
@@ -949,11 +952,13 @@
     }
 
     @Test
-    @EnableFlags(PromotedNotificationUiForceExpanded.FLAG_NAME)
+    @EnableFlags({PromotedNotificationUi.FLAG_NAME, PromotedNotificationUiForceExpanded.FLAG_NAME})
     public void isExpanded_promotedNotificationNotOnKeyguard_expanded() throws Exception {
         // GIVEN
         final ExpandableNotificationRow row = mNotificationTestHelper.createRow();
-        row.setPromotedOngoing(true);
+        NotificationEntry entry = mock(NotificationEntry.class);
+        when(entry.isPromotedOngoing()).thenReturn(true);
+        row.setEntry(entry);
         row.setOnKeyguard(false);
 
         // THEN
@@ -961,11 +966,13 @@
     }
 
     @Test
-    @EnableFlags(PromotedNotificationUiForceExpanded.FLAG_NAME)
+    @EnableFlags({PromotedNotificationUi.FLAG_NAME, PromotedNotificationUiForceExpanded.FLAG_NAME})
     public void isExpanded_promotedNotificationAllowOnKeyguard_expanded() throws Exception {
         // GIVEN
         final ExpandableNotificationRow row = mNotificationTestHelper.createRow();
-        row.setPromotedOngoing(true);
+        NotificationEntry entry = mock(NotificationEntry.class);
+        when(entry.isPromotedOngoing()).thenReturn(true);
+        row.setEntry(entry);
         row.setOnKeyguard(true);
 
         // THEN
@@ -973,12 +980,14 @@
     }
 
     @Test
-    @EnableFlags(PromotedNotificationUiForceExpanded.FLAG_NAME)
+    @EnableFlags({PromotedNotificationUi.FLAG_NAME, PromotedNotificationUiForceExpanded.FLAG_NAME})
     public void isExpanded_promotedNotificationIgnoreLockscreenConstraints_expanded()
             throws Exception {
         // GIVEN
         final ExpandableNotificationRow row = mNotificationTestHelper.createRow();
-        row.setPromotedOngoing(true);
+        NotificationEntry entry = mock(NotificationEntry.class);
+        when(entry.isPromotedOngoing()).thenReturn(true);
+        row.setEntry(entry);
         row.setOnKeyguard(true);
         row.setIgnoreLockscreenConstraints(true);
 
@@ -987,12 +996,14 @@
     }
 
     @Test
-    @EnableFlags(PromotedNotificationUiForceExpanded.FLAG_NAME)
+    @EnableFlags({PromotedNotificationUi.FLAG_NAME, PromotedNotificationUiForceExpanded.FLAG_NAME})
     public void isExpanded_promotedNotificationSaveSpaceOnLockScreen_notExpanded()
             throws Exception {
         // GIVEN
         final ExpandableNotificationRow row = mNotificationTestHelper.createRow();
-        row.setPromotedOngoing(true);
+        NotificationEntry entry = mock(NotificationEntry.class);
+        when(entry.isPromotedOngoing()).thenReturn(true);
+        row.setEntry(entry);
         row.setOnKeyguard(true);
         row.setSaveSpaceOnLockscreen(true);
 
@@ -1001,12 +1012,14 @@
     }
 
     @Test
-    @EnableFlags(PromotedNotificationUiForceExpanded.FLAG_NAME)
+    @EnableFlags({PromotedNotificationUi.FLAG_NAME, PromotedNotificationUiForceExpanded.FLAG_NAME})
     public void isExpanded_promotedNotificationNotSaveSpaceOnLockScreen_expanded()
             throws Exception {
         // GIVEN
         final ExpandableNotificationRow row = mNotificationTestHelper.createRow();
-        row.setPromotedOngoing(true);
+        NotificationEntry entry = mock(NotificationEntry.class);
+        when(entry.isPromotedOngoing()).thenReturn(true);
+        row.setEntry(entry);
         row.setOnKeyguard(true);
         row.setSaveSpaceOnLockscreen(false);
 
diff --git a/packages/SystemUI/tests/utils/src/com/android/systemui/keyevent/data/repository/FakeKeyEventRepository.kt b/packages/SystemUI/tests/utils/src/com/android/systemui/keyevent/data/repository/FakeKeyEventRepository.kt
index 807bc82..c9e25c3 100644
--- a/packages/SystemUI/tests/utils/src/com/android/systemui/keyevent/data/repository/FakeKeyEventRepository.kt
+++ b/packages/SystemUI/tests/utils/src/com/android/systemui/keyevent/data/repository/FakeKeyEventRepository.kt
@@ -34,7 +34,7 @@
         _isPowerButtonDown.value = isDown
     }
 
-    fun setPowerButtonBeingLongPressed(isLongPressed: Boolean) {
+    fun setPowerButtonLongPressed(isLongPressed: Boolean) {
         _isPowerButtonLongPressed.value = isLongPressed
     }
 }
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 08cfd9f..3e96fd7 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
@@ -17,6 +17,8 @@
 package com.android.systemui.statusbar.phone.ongoingcall.shared.model
 
 import android.app.PendingIntent
+import com.android.systemui.activity.data.repository.activityManagerRepository
+import com.android.systemui.activity.data.repository.fake
 import com.android.systemui.kosmos.Kosmos
 import com.android.systemui.statusbar.StatusBarIconView
 import com.android.systemui.statusbar.core.StatusBarConnectedDisplays
@@ -79,8 +81,10 @@
         contentIntent: PendingIntent? = null,
         uid: Int = DEFAULT_UID,
         appName: String = "Fake name",
+        isAppVisible: Boolean = false,
     ) {
         if (StatusBarChipsModernization.isEnabled) {
+            activityManagerRepository.fake.startingIsAppVisibleValue = isAppVisible
             activeNotificationListRepository.addNotif(
                 activeNotificationModel(
                     key = key,
@@ -102,6 +106,7 @@
                     notificationKey = key,
                     appName = appName,
                     promotedContent = promotedContent,
+                    isAppVisible = isAppVisible,
                 )
             )
         }
diff --git a/packages/SystemUI/tests/utils/src/com/android/systemui/topwindoweffects/data/repository/FakeSqueezeEffectRepository.kt b/packages/SystemUI/tests/utils/src/com/android/systemui/topwindoweffects/data/repository/FakeSqueezeEffectRepository.kt
new file mode 100644
index 0000000..2d2a8158
--- /dev/null
+++ b/packages/SystemUI/tests/utils/src/com/android/systemui/topwindoweffects/data/repository/FakeSqueezeEffectRepository.kt
@@ -0,0 +1,23 @@
+/*
+ * 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.topwindoweffects.data.repository
+
+import kotlinx.coroutines.flow.MutableStateFlow
+
+class FakeSqueezeEffectRepository : SqueezeEffectRepository {
+    override val isSqueezeEffectEnabled = MutableStateFlow(false)
+}
\ No newline at end of file
diff --git a/packages/SystemUI/tests/utils/src/com/android/systemui/topwindoweffects/data/repository/SqueezeEffectRepositoryKosmos.kt b/packages/SystemUI/tests/utils/src/com/android/systemui/topwindoweffects/data/repository/SqueezeEffectRepositoryKosmos.kt
new file mode 100644
index 0000000..aa8bb6b
--- /dev/null
+++ b/packages/SystemUI/tests/utils/src/com/android/systemui/topwindoweffects/data/repository/SqueezeEffectRepositoryKosmos.kt
@@ -0,0 +1,21 @@
+/*
+ * 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.topwindoweffects.data.repository
+
+import com.android.systemui.kosmos.Kosmos
+
+val Kosmos.fakeSqueezeEffectRepository by Kosmos.Fixture { FakeSqueezeEffectRepository() }
\ No newline at end of file
diff --git a/ravenwood/junit-src/android/platform/test/ravenwood/RavenwoodUtils.java b/ravenwood/junit-src/android/platform/test/ravenwood/RavenwoodUtils.java
index 3e2c405..f25ae6a 100644
--- a/ravenwood/junit-src/android/platform/test/ravenwood/RavenwoodUtils.java
+++ b/ravenwood/junit-src/android/platform/test/ravenwood/RavenwoodUtils.java
@@ -128,28 +128,6 @@
         runOnHandlerSync(getMainHandler(), r);
     }
 
-    public static class MockitoHelper {
-        private MockitoHelper() {
-        }
-
-        /**
-         * Allow verifyZeroInteractions to work on ravenwood. It was replaced with a different
-         * method on. (Maybe we should do it in Ravenizer.)
-         */
-        public static void verifyZeroInteractions(Object... mocks) {
-            if (RavenwoodRule.isOnRavenwood()) {
-                // Mockito 4 or later
-                reflectMethod("org.mockito.Mockito", "verifyNoInteractions", Object[].class)
-                        .callStatic(new Object[]{mocks});
-            } else {
-                // Mockito 2
-                reflectMethod("org.mockito.Mockito", "verifyZeroInteractions", Object[].class)
-                        .callStatic(new Object[]{mocks});
-            }
-        }
-    }
-
-
     /**
      * Wrap the given {@link Supplier} to become memoized.
      *
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 739ea0d..cc93d08 100644
--- a/services/accessibility/java/com/android/server/accessibility/autoclick/AutoclickController.java
+++ b/services/accessibility/java/com/android/server/accessibility/autoclick/AutoclickController.java
@@ -248,7 +248,11 @@
 
     private boolean isPaused() {
         return Flags.enableAutoclickIndicator() && mAutoclickTypePanel.isPaused()
-                && !mAutoclickTypePanel.isHovered();
+                && !isHovered();
+    }
+
+    private boolean isHovered() {
+        return Flags.enableAutoclickIndicator() && mAutoclickTypePanel.isHovered();
     }
 
     private void cancelPendingClick() {
@@ -495,6 +499,8 @@
         private int mEventPolicyFlags;
         /** Current meta state. This value will be used as meta state for click event sequence. */
         private int mMetaState;
+        /** Last observed panel hovered state when click was scheduled. */
+        private boolean mHoveredState;
 
         /**
          * The current anchor's coordinates. Should be ignored if #mLastMotionEvent is null.
@@ -648,6 +654,7 @@
             }
             mLastMotionEvent = MotionEvent.obtain(event);
             mEventPolicyFlags = policyFlags;
+            mHoveredState = isHovered();
 
             if (useAsAnchor) {
                 final int pointerIndex = mLastMotionEvent.getActionIndex();
@@ -729,14 +736,18 @@
 
             final long now = SystemClock.uptimeMillis();
 
-            // TODO(b/395094903): always triggers left-click when the cursor hovers over the
-            // autoclick type panel, to always allow users to change a different click type.
-            // Otherwise, if one chooses the right-click, this user won't be able to rely on
-            // autoclick to select other click types.
-            final int actionButton =
-                    mActiveClickType == AUTOCLICK_TYPE_RIGHT_CLICK
-                            ? BUTTON_SECONDARY
-                            : BUTTON_PRIMARY;
+            int actionButton;
+            if (mHoveredState) {
+                // Always triggers left-click when the cursor hovers over the autoclick type
+                // panel, to always allow users to change a different click type. Otherwise, if
+                // one chooses the right-click, this user won't be able to rely on autoclick to
+                // select other click types.
+                actionButton = BUTTON_PRIMARY;
+            } else {
+                actionButton = mActiveClickType == AUTOCLICK_TYPE_RIGHT_CLICK
+                        ? BUTTON_SECONDARY
+                        : BUTTON_PRIMARY;
+            }
 
             MotionEvent downEvent =
                     MotionEvent.obtain(
diff --git a/services/backup/java/com/android/server/backup/fullbackup/FullBackupEngine.java b/services/backup/java/com/android/server/backup/fullbackup/FullBackupEngine.java
index ebb1194..b173f76 100644
--- a/services/backup/java/com/android/server/backup/fullbackup/FullBackupEngine.java
+++ b/services/backup/java/com/android/server/backup/fullbackup/FullBackupEngine.java
@@ -25,6 +25,7 @@
 import android.annotation.UserIdInt;
 import android.app.ApplicationThreadConstants;
 import android.app.IBackupAgent;
+import android.app.backup.BackupManagerMonitor;
 import android.app.backup.BackupTransport;
 import android.app.backup.FullBackupDataOutput;
 import android.content.pm.ApplicationInfo;
@@ -268,6 +269,12 @@
                 mBackupManagerMonitorEventSender.monitorAgentLoggingResults(mPkg, mAgent);
             } catch (IOException e) {
                 Slog.e(TAG, "Error backing up " + mPkg.packageName + ": " + e.getMessage());
+                // This is likely due to the app process dying.
+                mBackupManagerMonitorEventSender.monitorEvent(
+                        BackupManagerMonitor.LOG_EVENT_ID_FULL_BACKUP_AGENT_PIPE_BROKEN,
+                        mPkg,
+                        BackupManagerMonitor.LOG_EVENT_CATEGORY_AGENT,
+                        /* extras= */ null);
                 result = BackupTransport.AGENT_ERROR;
             } finally {
                 try {
diff --git a/services/backup/java/com/android/server/backup/fullbackup/PerformFullTransportBackupTask.java b/services/backup/java/com/android/server/backup/fullbackup/PerformFullTransportBackupTask.java
index f677c9d..48d21c3 100644
--- a/services/backup/java/com/android/server/backup/fullbackup/PerformFullTransportBackupTask.java
+++ b/services/backup/java/com/android/server/backup/fullbackup/PerformFullTransportBackupTask.java
@@ -294,6 +294,14 @@
             // SinglePackageBackupPreflight.
             if (cancellationReason == CancellationReason.TIMEOUT) {
                 Slog.wtf(TAG, "This task cannot time out");
+                return;
+            }
+
+            // We don't cancel the entire operation if a single agent is disconnected unexpectedly.
+            // SinglePackageBackupRunner and SinglePackageBackupPreflight will receive the same
+            // callback and fail gracefully. The operation should then continue to the next package.
+            if (cancellationReason == CancellationReason.AGENT_DISCONNECTED) {
+                return;
             }
 
             if (mCancelled) {
diff --git a/services/backup/java/com/android/server/backup/restore/PerformUnifiedRestoreTask.java b/services/backup/java/com/android/server/backup/restore/PerformUnifiedRestoreTask.java
index 1263146..20f103c 100644
--- a/services/backup/java/com/android/server/backup/restore/PerformUnifiedRestoreTask.java
+++ b/services/backup/java/com/android/server/backup/restore/PerformUnifiedRestoreTask.java
@@ -589,6 +589,7 @@
                         monitoringExtras);
                 Slog.e(TAG, "Failure getting next package name");
                 EventLog.writeEvent(EventLogTags.RESTORE_TRANSPORT_FAILURE);
+                mStatus = BackupTransport.TRANSPORT_ERROR;
                 nextState = UnifiedRestoreState.FINAL;
                 return;
             } else if (mRestoreDescription == RestoreDescription.NO_MORE_PACKAGES) {
diff --git a/services/backup/java/com/android/server/backup/utils/BackupManagerMonitorDumpsysUtils.java b/services/backup/java/com/android/server/backup/utils/BackupManagerMonitorDumpsysUtils.java
index fad59d2..855c72a 100644
--- a/services/backup/java/com/android/server/backup/utils/BackupManagerMonitorDumpsysUtils.java
+++ b/services/backup/java/com/android/server/backup/utils/BackupManagerMonitorDumpsysUtils.java
@@ -389,6 +389,8 @@
                     "Agent failure during restore";
             case BackupManagerMonitor.LOG_EVENT_ID_FAILED_TO_READ_DATA_FROM_TRANSPORT ->
                     "Failed to read data from Transport";
+            case BackupManagerMonitor.LOG_EVENT_ID_FULL_BACKUP_AGENT_PIPE_BROKEN ->
+                "LOG_EVENT_ID_FULL_BACKUP_AGENT_PIPE_BROKEN";
             default -> "Unknown log event ID: " + code;
         };
         return id;
diff --git a/services/companion/java/com/android/server/companion/association/AssociationRequestsProcessor.java b/services/companion/java/com/android/server/companion/association/AssociationRequestsProcessor.java
index cd9285c..cbee839 100644
--- a/services/companion/java/com/android/server/companion/association/AssociationRequestsProcessor.java
+++ b/services/companion/java/com/android/server/companion/association/AssociationRequestsProcessor.java
@@ -58,13 +58,16 @@
 import android.os.RemoteException;
 import android.os.ResultReceiver;
 import android.os.UserHandle;
+import android.util.ArraySet;
 import android.util.Slog;
 
 import com.android.internal.R;
 import com.android.server.companion.CompanionDeviceManagerService;
 import com.android.server.companion.utils.PackageUtils;
 
+import java.util.Arrays;
 import java.util.List;
+import java.util.Set;
 
 /**
  * Class responsible for handling incoming {@link AssociationRequest}s.
@@ -130,6 +133,12 @@
     private static final int ASSOCIATE_WITHOUT_PROMPT_MAX_PER_TIME_WINDOW = 5;
     private static final long ASSOCIATE_WITHOUT_PROMPT_WINDOW_MS = 60 * 60 * 1000; // 60 min;
 
+    // Set of profiles for which the association dialog cannot be skipped.
+    private static final Set<String> DEVICE_PROFILES_WITH_REQUIRED_CONFIRMATION = new ArraySet<>(
+            Arrays.asList(
+                    AssociationRequest.DEVICE_PROFILE_APP_STREAMING,
+                    AssociationRequest.DEVICE_PROFILE_NEARBY_DEVICE_STREAMING));
+
     private final @NonNull Context mContext;
     private final @NonNull PackageManagerInternal mPackageManagerInternal;
     private final @NonNull AssociationStore mAssociationStore;
@@ -174,6 +183,7 @@
         // 2a. Check if association can be created without launching UI (i.e. CDM needs NEITHER
         // to perform discovery NOR to collect user consent).
         if (request.isSelfManaged() && !request.isForceConfirmation()
+                && !DEVICE_PROFILES_WITH_REQUIRED_CONFIRMATION.contains(request.getDeviceProfile())
                 && !willAddRoleHolder(request, packageName, userId)) {
             // 2a.1. Create association right away.
             createAssociationAndNotifyApplication(request, packageName, userId,
diff --git a/services/core/java/com/android/server/am/ActivityManagerService.java b/services/core/java/com/android/server/am/ActivityManagerService.java
index b0b34d0..76ba005 100644
--- a/services/core/java/com/android/server/am/ActivityManagerService.java
+++ b/services/core/java/com/android/server/am/ActivityManagerService.java
@@ -16190,14 +16190,16 @@
         return mUserController.switchUser(targetUserId);
     }
 
+    @Nullable
     @Override
-    public String getSwitchingFromUserMessage() {
-        return mUserController.getSwitchingFromSystemUserMessage();
+    public String getSwitchingFromUserMessage(@UserIdInt int userId) {
+        return mUserController.getSwitchingFromUserMessage(userId);
     }
 
+    @Nullable
     @Override
-    public String getSwitchingToUserMessage() {
-        return mUserController.getSwitchingToSystemUserMessage();
+    public String getSwitchingToUserMessage(@UserIdInt int userId) {
+        return mUserController.getSwitchingToUserMessage(userId);
     }
 
     @Override
@@ -16938,13 +16940,13 @@
         }
 
         @Override
-        public void setSwitchingFromSystemUserMessage(String switchingFromSystemUserMessage) {
-            mUserController.setSwitchingFromSystemUserMessage(switchingFromSystemUserMessage);
+        public void setSwitchingFromUserMessage(@UserIdInt int userId, @Nullable String message) {
+            mUserController.setSwitchingFromUserMessage(userId, message);
         }
 
         @Override
-        public void setSwitchingToSystemUserMessage(String switchingToSystemUserMessage) {
-            mUserController.setSwitchingToSystemUserMessage(switchingToSystemUserMessage);
+        public void setSwitchingToUserMessage(@UserIdInt int userId, @Nullable String message) {
+            mUserController.setSwitchingToUserMessage(userId, message);
         }
 
         @Override
diff --git a/services/core/java/com/android/server/am/UserController.java b/services/core/java/com/android/server/am/UserController.java
index 18f3500..40a9bbe 100644
--- a/services/core/java/com/android/server/am/UserController.java
+++ b/services/core/java/com/android/server/am/UserController.java
@@ -340,16 +340,16 @@
     private volatile ArraySet<String> mCurWaitingUserSwitchCallbacks;
 
     /**
-     * Messages for switching from {@link android.os.UserHandle#SYSTEM}.
+     * Message shown when switching from a user.
      */
     @GuardedBy("mLock")
-    private String mSwitchingFromSystemUserMessage;
+    private final SparseArray<String> mSwitchingFromUserMessage = new SparseArray<>();
 
     /**
-     * Messages for switching to {@link android.os.UserHandle#SYSTEM}.
+     * Message shown when switching to a user.
      */
     @GuardedBy("mLock")
-    private String mSwitchingToSystemUserMessage;
+    private final SparseArray<String> mSwitchingToUserMessage = new SparseArray<>();
 
     /**
      * Callbacks that are still active after {@link #getUserSwitchTimeoutMs}
@@ -2271,8 +2271,8 @@
     private void showUserSwitchDialog(Pair<UserInfo, UserInfo> fromToUserPair) {
         // The dialog will show and then initiate the user switch by calling startUserInForeground
         mInjector.showUserSwitchingDialog(fromToUserPair.first, fromToUserPair.second,
-                getSwitchingFromSystemUserMessageUnchecked(),
-                getSwitchingToSystemUserMessageUnchecked(),
+                getSwitchingFromUserMessageUnchecked(fromToUserPair.first.id),
+                getSwitchingToUserMessageUnchecked(fromToUserPair.second.id),
                 /* onShown= */ () -> sendStartUserSwitchFgMessage(fromToUserPair.second.id));
     }
 
@@ -3388,41 +3388,45 @@
         return mLockPatternUtils.isLockScreenDisabled(userId);
     }
 
-    void setSwitchingFromSystemUserMessage(String switchingFromSystemUserMessage) {
+    void setSwitchingFromUserMessage(@UserIdInt int user, @Nullable String message) {
         synchronized (mLock) {
-            mSwitchingFromSystemUserMessage = switchingFromSystemUserMessage;
+            mSwitchingFromUserMessage.put(user, message);
         }
     }
 
-    void setSwitchingToSystemUserMessage(String switchingToSystemUserMessage) {
+    void setSwitchingToUserMessage(@UserIdInt int user, @Nullable String message) {
         synchronized (mLock) {
-            mSwitchingToSystemUserMessage = switchingToSystemUserMessage;
+            mSwitchingToUserMessage.put(user, message);
         }
     }
 
     // Called by AMS, must check permission
-    String getSwitchingFromSystemUserMessage() {
-        checkHasManageUsersPermission("getSwitchingFromSystemUserMessage()");
+    @Nullable
+    String getSwitchingFromUserMessage(@UserIdInt int userId) {
+        checkHasManageUsersPermission("getSwitchingFromUserMessage()");
 
-        return getSwitchingFromSystemUserMessageUnchecked();
+        return getSwitchingFromUserMessageUnchecked(userId);
     }
 
     // Called by AMS, must check permission
-    String getSwitchingToSystemUserMessage() {
-        checkHasManageUsersPermission("getSwitchingToSystemUserMessage()");
+    @Nullable
+    String getSwitchingToUserMessage(@UserIdInt int userId) {
+        checkHasManageUsersPermission("getSwitchingToUserMessage()");
 
-        return getSwitchingToSystemUserMessageUnchecked();
+        return getSwitchingToUserMessageUnchecked(userId);
     }
 
-    private String getSwitchingFromSystemUserMessageUnchecked() {
+    @Nullable
+    private String getSwitchingFromUserMessageUnchecked(@UserIdInt int userId) {
         synchronized (mLock) {
-            return mSwitchingFromSystemUserMessage;
+            return mSwitchingFromUserMessage.get(userId);
         }
     }
 
-    private String getSwitchingToSystemUserMessageUnchecked() {
+    @Nullable
+    private String getSwitchingToUserMessageUnchecked(@UserIdInt int userId) {
         synchronized (mLock) {
-            return mSwitchingToSystemUserMessage;
+            return mSwitchingToUserMessage.get(userId);
         }
     }
 
@@ -3518,12 +3522,8 @@
                     + mIsBroadcastSentForSystemUserStarted);
             pw.println("  mIsBroadcastSentForSystemUserStarting:"
                     + mIsBroadcastSentForSystemUserStarting);
-            if (mSwitchingFromSystemUserMessage != null) {
-                pw.println("  mSwitchingFromSystemUserMessage: " + mSwitchingFromSystemUserMessage);
-            }
-            if (mSwitchingToSystemUserMessage != null) {
-                pw.println("  mSwitchingToSystemUserMessage: " + mSwitchingToSystemUserMessage);
-            }
+            pw.println("  mSwitchingFromUserMessage:" + mSwitchingFromUserMessage);
+            pw.println("  mSwitchingToUserMessage:" + mSwitchingToUserMessage);
             pw.println("  mLastUserUnlockingUptime: " + mLastUserUnlockingUptime);
         }
     }
@@ -4046,7 +4046,7 @@
         }
 
         void showUserSwitchingDialog(UserInfo fromUser, UserInfo toUser,
-                String switchingFromSystemUserMessage, String switchingToSystemUserMessage,
+                @Nullable String switchingFromUserMessage, @Nullable String switchingToUserMessage,
                 @NonNull Runnable onShown) {
             if (mService.mContext.getPackageManager()
                     .hasSystemFeature(PackageManager.FEATURE_AUTOMOTIVE)) {
@@ -4059,7 +4059,7 @@
             synchronized (mUserSwitchingDialogLock) {
                 dismissUserSwitchingDialog(null);
                 mUserSwitchingDialog = new UserSwitchingDialog(mService.mContext, fromUser, toUser,
-                        mHandler, switchingFromSystemUserMessage, switchingToSystemUserMessage);
+                        mHandler, switchingFromUserMessage, switchingToUserMessage);
                 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 223e0b7..f4e733a 100644
--- a/services/core/java/com/android/server/am/UserSwitchingDialog.java
+++ b/services/core/java/com/android/server/am/UserSwitchingDialog.java
@@ -52,6 +52,7 @@
 import com.android.internal.util.ObjectUtils;
 import com.android.internal.util.UserIcons;
 
+import java.util.Objects;
 import java.util.concurrent.atomic.AtomicBoolean;
 
 /**
@@ -75,21 +76,23 @@
 
     protected final UserInfo mOldUser;
     protected final UserInfo mNewUser;
-    private final String mSwitchingFromSystemUserMessage;
-    private final String mSwitchingToSystemUserMessage;
+    @Nullable
+    private final String mSwitchingFromUserMessage;
+    @Nullable
+    private final String mSwitchingToUserMessage;
     protected final Context mContext;
     private final int mTraceCookie;
 
     UserSwitchingDialog(Context context, UserInfo oldUser, UserInfo newUser, Handler handler,
-            String switchingFromSystemUserMessage, String switchingToSystemUserMessage) {
+            @Nullable String switchingFromUserMessage, @Nullable String switchingToUserMessage) {
         super(context, R.style.Theme_Material_NoActionBar_Fullscreen);
 
         mContext = context;
         mOldUser = oldUser;
         mNewUser = newUser;
         mHandler = handler;
-        mSwitchingFromSystemUserMessage = switchingFromSystemUserMessage;
-        mSwitchingToSystemUserMessage = switchingToSystemUserMessage;
+        mSwitchingFromUserMessage = switchingFromUserMessage;
+        mSwitchingToUserMessage = switchingToUserMessage;
         mDisableAnimations = SystemProperties.getBoolean(
                 "debug.usercontroller.disable_user_switching_dialog_animations", false);
         mTraceCookie = UserHandle.MAX_SECONDARY_USER_ID * oldUser.id + newUser.id;
@@ -166,14 +169,14 @@
                     : R.string.demo_starting_message);
         }
 
-        final String message =
-                mOldUser.id == UserHandle.USER_SYSTEM ? mSwitchingFromSystemUserMessage
-                : mNewUser.id == UserHandle.USER_SYSTEM ? mSwitchingToSystemUserMessage : null;
+        if (mSwitchingFromUserMessage != null || mSwitchingToUserMessage != null) {
+            if (mSwitchingFromUserMessage != null && mSwitchingToUserMessage != null) {
+                return mSwitchingFromUserMessage + " " + mSwitchingToUserMessage;
+            }
+            return Objects.requireNonNullElse(mSwitchingFromUserMessage, mSwitchingToUserMessage);
+        }
 
-        return message != null ? message
-                // If switchingFromSystemUserMessage or switchingToSystemUserMessage is null,
-                // fallback to system message.
-                : res.getString(R.string.user_switching_message, mNewUser.name);
+        return res.getString(R.string.user_switching_message, mNewUser.name);
     }
 
     @Override
diff --git a/services/core/java/com/android/server/wallpaper/WallpaperCropper.java b/services/core/java/com/android/server/wallpaper/WallpaperCropper.java
index 8e8455a..6e640d8 100644
--- a/services/core/java/com/android/server/wallpaper/WallpaperCropper.java
+++ b/services/core/java/com/android/server/wallpaper/WallpaperCropper.java
@@ -17,7 +17,6 @@
 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;
@@ -85,20 +84,11 @@
 
     private final WallpaperDisplayHelper mWallpaperDisplayHelper;
 
-    /**
-     * Helpers exposed to the window manager part (WallpaperController)
-     */
-    public interface WallpaperCropUtils {
-
-        /**
-         * Equivalent to {@link WallpaperCropper#getCrop(Point, Point, SparseArray, boolean)}
-         */
-        Rect getCrop(Point displaySize, Point bitmapSize,
-                SparseArray<Rect> suggestedCrops, boolean rtl);
-    }
+    private final WallpaperDefaultDisplayInfo mDefaultDisplayInfo;
 
     WallpaperCropper(WallpaperDisplayHelper wallpaperDisplayHelper) {
         mWallpaperDisplayHelper = wallpaperDisplayHelper;
+        mDefaultDisplayInfo = mWallpaperDisplayHelper.getDefaultDisplayInfo();
     }
 
     /**
@@ -116,16 +106,16 @@
      *     {@link #getAdjustedCrop}.
      * </ul>
      *
-     * @param displaySize     The dimensions of the surface where we want to render the wallpaper
-     * @param bitmapSize      The dimensions of the wallpaper bitmap
-     * @param rtl             Whether the device is right-to-left
-     * @param suggestedCrops  An optional list of user-defined crops for some orientations.
-     *                        If there is a suggested crop for
+     * @param displaySize        The dimensions of the surface where we want to render the wallpaper
+     * @param defaultDisplayInfo The default display info
+     * @param bitmapSize         The dimensions of the wallpaper bitmap
+     * @param rtl                Whether the device is right-to-left
+     * @param suggestedCrops     An optional list of user-defined crops for some orientations.
      *
      * @return  A Rect indicating how to crop the bitmap for the current display.
      */
-    public Rect getCrop(Point displaySize, Point bitmapSize,
-            SparseArray<Rect> suggestedCrops, boolean rtl) {
+    public static Rect getCrop(Point displaySize, WallpaperDefaultDisplayInfo defaultDisplayInfo,
+            Point bitmapSize, SparseArray<Rect> suggestedCrops, boolean rtl) {
 
         int orientation = getOrientation(displaySize);
 
@@ -135,23 +125,24 @@
 
             // The first exception is if the device is a foldable and we're on the folded screen.
             // In that case, show the center of what's on the unfolded screen.
-            int unfoldedOrientation = mWallpaperDisplayHelper.getUnfoldedOrientation(orientation);
+            int unfoldedOrientation = defaultDisplayInfo.getUnfoldedOrientation(orientation);
             if (unfoldedOrientation != ORIENTATION_UNKNOWN) {
                 // Let the system know that we're showing the full image on the unfolded screen
                 SparseArray<Rect> newSuggestedCrops = new SparseArray<>();
                 newSuggestedCrops.put(unfoldedOrientation, crop);
                 // This will fall into "Case 4" of this function and center the folded screen
-                return getCrop(displaySize, bitmapSize, newSuggestedCrops, rtl);
+                return getCrop(displaySize, defaultDisplayInfo, bitmapSize, newSuggestedCrops,
+                        rtl);
             }
 
             // The second exception is if we're on tablet and we're on portrait mode.
             // In that case, center the wallpaper relatively to landscape and put some parallax.
-            boolean isTablet = mWallpaperDisplayHelper.isLargeScreen()
-                    && !mWallpaperDisplayHelper.isFoldable();
+            boolean isTablet = defaultDisplayInfo.isLargeScreen && !defaultDisplayInfo.isFoldable;
             if (isTablet && displaySize.x < displaySize.y) {
                 Point rotatedDisplaySize = new Point(displaySize.y, displaySize.x);
                 // compute the crop on landscape (without parallax)
-                Rect landscapeCrop = getCrop(rotatedDisplaySize, bitmapSize, suggestedCrops, rtl);
+                Rect landscapeCrop = getCrop(rotatedDisplaySize, defaultDisplayInfo, bitmapSize,
+                        suggestedCrops, rtl);
                 landscapeCrop = noParallax(landscapeCrop, rotatedDisplaySize, bitmapSize, rtl);
                 // compute the crop on portrait at the center of the landscape crop
                 crop = getAdjustedCrop(landscapeCrop, bitmapSize, displaySize, false, rtl, ADD);
@@ -173,7 +164,8 @@
             if (testCrop == null || testCrop.left < 0 || testCrop.top < 0
                     || testCrop.right > bitmapSize.x || testCrop.bottom > bitmapSize.y) {
                 Slog.w(TAG, "invalid crop: " + testCrop + " for bitmap size: " + bitmapSize);
-                return getCrop(displaySize, bitmapSize, new SparseArray<>(), rtl);
+                return getCrop(displaySize, defaultDisplayInfo, bitmapSize, new SparseArray<>(),
+                        rtl);
             }
         }
 
@@ -185,10 +177,9 @@
 
         // Case 3: if we have the 90° rotated orientation in the suggested crops, reuse it and
         // trying to preserve the zoom level and the center of the image
-        SparseArray<Point> defaultDisplaySizes = mWallpaperDisplayHelper.getDefaultDisplaySizes();
         int rotatedOrientation = getRotatedOrientation(orientation);
         suggestedCrop = suggestedCrops.get(rotatedOrientation);
-        Point suggestedDisplaySize = defaultDisplaySizes.get(rotatedOrientation);
+        Point suggestedDisplaySize = defaultDisplayInfo.defaultDisplaySizes.get(rotatedOrientation);
         if (suggestedCrop != null) {
             // only keep the visible part (without parallax)
             Rect adjustedCrop = noParallax(suggestedCrop, suggestedDisplaySize, bitmapSize, rtl);
@@ -197,9 +188,9 @@
 
         // Case 4: if the device is a foldable, if we're looking for a folded orientation and have
         // the suggested crop of the relative unfolded orientation, reuse it by removing content.
-        int unfoldedOrientation = mWallpaperDisplayHelper.getUnfoldedOrientation(orientation);
+        int unfoldedOrientation = defaultDisplayInfo.getUnfoldedOrientation(orientation);
         suggestedCrop = suggestedCrops.get(unfoldedOrientation);
-        suggestedDisplaySize = defaultDisplaySizes.get(unfoldedOrientation);
+        suggestedDisplaySize = defaultDisplayInfo.defaultDisplaySizes.get(unfoldedOrientation);
         if (suggestedCrop != null) {
             // compute the visible part (without parallax) of the unfolded screen
             Rect adjustedCrop = noParallax(suggestedCrop, suggestedDisplaySize, bitmapSize, rtl);
@@ -207,8 +198,11 @@
             Rect res = getAdjustedCrop(adjustedCrop, bitmapSize, displaySize, false, rtl, REMOVE);
             // if we removed some width, add it back to add a parallax effect
             if (res.width() < adjustedCrop.width()) {
-                if (rtl) res.left = Math.min(res.left, adjustedCrop.left);
-                else res.right = Math.max(res.right, adjustedCrop.right);
+                if (rtl) {
+                    res.left = Math.min(res.left, adjustedCrop.left);
+                } else {
+                    res.right = Math.max(res.right, adjustedCrop.right);
+                }
                 // use getAdjustedCrop(parallax=true) to make sure we don't exceed MAX_PARALLAX
                 res = getAdjustedCrop(res, bitmapSize, displaySize, true, rtl, ADD);
             }
@@ -218,9 +212,9 @@
 
         // Case 5: if the device is a foldable, if we're looking for an unfolded orientation and
         // have the suggested crop of the relative folded orientation, reuse it by adding content.
-        int foldedOrientation = mWallpaperDisplayHelper.getFoldedOrientation(orientation);
+        int foldedOrientation = defaultDisplayInfo.getFoldedOrientation(orientation);
         suggestedCrop = suggestedCrops.get(foldedOrientation);
-        suggestedDisplaySize = defaultDisplaySizes.get(foldedOrientation);
+        suggestedDisplaySize = defaultDisplayInfo.defaultDisplaySizes.get(foldedOrientation);
         if (suggestedCrop != null) {
             // only keep the visible part (without parallax)
             Rect adjustedCrop = noParallax(suggestedCrop, suggestedDisplaySize, bitmapSize, rtl);
@@ -229,17 +223,19 @@
 
         // Case 6: for a foldable device, try to combine case 3 + case 4 or 5:
         // rotate, then fold or unfold
-        Point rotatedDisplaySize = defaultDisplaySizes.get(rotatedOrientation);
+        Point rotatedDisplaySize = defaultDisplayInfo.defaultDisplaySizes.get(rotatedOrientation);
         if (rotatedDisplaySize != null) {
-            int rotatedFolded = mWallpaperDisplayHelper.getFoldedOrientation(rotatedOrientation);
-            int rotateUnfolded = mWallpaperDisplayHelper.getUnfoldedOrientation(rotatedOrientation);
+            int rotatedFolded = defaultDisplayInfo.getFoldedOrientation(rotatedOrientation);
+            int rotateUnfolded = defaultDisplayInfo.getUnfoldedOrientation(rotatedOrientation);
             for (int suggestedOrientation : new int[]{rotatedFolded, rotateUnfolded}) {
                 suggestedCrop = suggestedCrops.get(suggestedOrientation);
                 if (suggestedCrop != null) {
-                    Rect rotatedCrop = getCrop(rotatedDisplaySize, bitmapSize, suggestedCrops, rtl);
+                    Rect rotatedCrop = getCrop(rotatedDisplaySize, defaultDisplayInfo, bitmapSize,
+                            suggestedCrops, rtl);
                     SparseArray<Rect> rotatedCropMap = new SparseArray<>();
                     rotatedCropMap.put(rotatedOrientation, rotatedCrop);
-                    return getCrop(displaySize, bitmapSize, rotatedCropMap, rtl);
+                    return getCrop(displaySize, defaultDisplayInfo, bitmapSize, rotatedCropMap,
+                            rtl);
                 }
             }
         }
@@ -248,8 +244,8 @@
         Slog.w(TAG, "Could not find a proper default crop for display: " + displaySize
                 + ", bitmap size: " + bitmapSize + ", suggested crops: " + suggestedCrops
                 + ", orientation: " + orientation + ", rtl: " + rtl
-                + ", defaultDisplaySizes: " + defaultDisplaySizes);
-        return getCrop(displaySize, bitmapSize, new SparseArray<>(), rtl);
+                + ", defaultDisplaySizes: " + defaultDisplayInfo.defaultDisplaySizes);
+        return getCrop(displaySize, defaultDisplayInfo, bitmapSize, new SparseArray<>(), rtl);
     }
 
     /**
@@ -445,7 +441,7 @@
             Rect suggestedCrop = suggestedCrops.get(orientation);
             if (suggestedCrop != null) {
                 adjustedSuggestedCrops.put(orientation,
-                        getCrop(displaySize, bitmapSize, suggestedCrops, rtl));
+                        getCrop(displaySize, mDefaultDisplayInfo, bitmapSize, suggestedCrops, rtl));
             }
         }
 
@@ -455,7 +451,8 @@
             int orientation = defaultDisplaySizes.keyAt(i);
             if (result.contains(orientation)) continue;
             Point displaySize = defaultDisplaySizes.valueAt(i);
-            Rect newCrop = getCrop(displaySize, bitmapSize, adjustedSuggestedCrops, rtl);
+            Rect newCrop = getCrop(displaySize, mDefaultDisplayInfo, bitmapSize,
+                    adjustedSuggestedCrops, rtl);
             result.put(orientation, newCrop);
         }
         return result;
diff --git a/services/core/java/com/android/server/wallpaper/WallpaperDataParser.java b/services/core/java/com/android/server/wallpaper/WallpaperDataParser.java
index ba0262a..69f0ef7 100644
--- a/services/core/java/com/android/server/wallpaper/WallpaperDataParser.java
+++ b/services/core/java/com/android/server/wallpaper/WallpaperDataParser.java
@@ -542,9 +542,11 @@
                 // to support back compatibility in B&R, save the crops for one orientation in the
                 // legacy "cropLeft", "cropTop", "cropRight", "cropBottom" entries
                 int orientationToPutInLegacyCrop = wallpaper.mOrientationWhenSet;
-                if (mWallpaperDisplayHelper.isFoldable()) {
-                    int unfoldedOrientation = mWallpaperDisplayHelper
-                            .getUnfoldedOrientation(orientationToPutInLegacyCrop);
+                WallpaperDefaultDisplayInfo defaultDisplayInfo =
+                        mWallpaperDisplayHelper.getDefaultDisplayInfo();
+                if (defaultDisplayInfo.isFoldable) {
+                    int unfoldedOrientation = defaultDisplayInfo.getUnfoldedOrientation(
+                            orientationToPutInLegacyCrop);
                     if (unfoldedOrientation != ORIENTATION_UNKNOWN) {
                         orientationToPutInLegacyCrop = unfoldedOrientation;
                     }
diff --git a/services/core/java/com/android/server/wallpaper/WallpaperDefaultDisplayInfo.java b/services/core/java/com/android/server/wallpaper/WallpaperDefaultDisplayInfo.java
new file mode 100644
index 0000000..dabe919
--- /dev/null
+++ b/services/core/java/com/android/server/wallpaper/WallpaperDefaultDisplayInfo.java
@@ -0,0 +1,207 @@
+/*
+ * 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.wallpaper;
+
+import static android.app.WallpaperManager.ORIENTATION_UNKNOWN;
+import static android.app.WallpaperManager.getRotatedOrientation;
+import static android.view.Display.DEFAULT_DISPLAY;
+import static android.view.WindowManager.LARGE_SCREEN_SMALLEST_SCREEN_WIDTH_DP;
+
+import android.app.WallpaperManager;
+import android.content.res.Resources;
+import android.graphics.Point;
+import android.graphics.Rect;
+import android.util.SparseArray;
+import android.view.WindowManager;
+import android.view.WindowMetrics;
+
+import com.android.internal.R;
+import com.android.internal.annotations.VisibleForTesting;
+
+import java.util.ArrayList;
+import java.util.Collections;
+import java.util.List;
+import java.util.Objects;
+import java.util.Set;
+
+
+/**  A data class for the default display attributes used in wallpaper related operations. */
+public final class WallpaperDefaultDisplayInfo {
+    /**
+     * A data class representing the screen orientations for a foldable device in the folded and
+     * unfolded states.
+     */
+    @VisibleForTesting
+    static final class FoldableOrientations {
+        @WallpaperManager.ScreenOrientation
+        public final int foldedOrientation;
+        @WallpaperManager.ScreenOrientation
+        public final int unfoldedOrientation;
+
+        FoldableOrientations(int foldedOrientation, int unfoldedOrientation) {
+            this.foldedOrientation = foldedOrientation;
+            this.unfoldedOrientation = unfoldedOrientation;
+        }
+
+        @Override
+        public boolean equals(Object other) {
+            if (this == other) return true;
+            if (!(other instanceof FoldableOrientations that)) return false;
+            return foldedOrientation == that.foldedOrientation
+                    && unfoldedOrientation == that.unfoldedOrientation;
+        }
+
+        @Override
+        public int hashCode() {
+            return Objects.hash(foldedOrientation, unfoldedOrientation);
+        }
+    }
+
+    public final SparseArray<Point> defaultDisplaySizes;
+    public final boolean isLargeScreen;
+    public final boolean isFoldable;
+    @VisibleForTesting
+    final List<FoldableOrientations> foldableOrientations;
+
+    public WallpaperDefaultDisplayInfo() {
+        this.defaultDisplaySizes = new SparseArray<>();
+        this.isLargeScreen = false;
+        this.isFoldable = false;
+        this.foldableOrientations = Collections.emptyList();
+    }
+
+    public WallpaperDefaultDisplayInfo(WindowManager windowManager, Resources resources) {
+        Set<WindowMetrics> metrics = windowManager.getPossibleMaximumWindowMetrics(DEFAULT_DISPLAY);
+        boolean isFoldable = resources.getIntArray(R.array.config_foldedDeviceStates).length > 0;
+        if (isFoldable) {
+            this.foldableOrientations = getFoldableOrientations(metrics);
+        } else {
+            this.foldableOrientations = Collections.emptyList();
+        }
+        this.defaultDisplaySizes = getDisplaySizes(metrics);
+        this.isLargeScreen = isLargeScreen(metrics);
+        this.isFoldable = isFoldable;
+    }
+
+    @Override
+    public boolean equals(Object other) {
+        if (this == other) return true;
+        if (!(other instanceof WallpaperDefaultDisplayInfo that)) return false;
+        return isLargeScreen == that.isLargeScreen && isFoldable == that.isFoldable
+                && defaultDisplaySizes.contentEquals(that.defaultDisplaySizes)
+                && Objects.equals(foldableOrientations, that.foldableOrientations);
+    }
+
+    @Override
+    public int hashCode() {
+        return 31 * Objects.hash(isLargeScreen, isFoldable, foldableOrientations)
+                + defaultDisplaySizes.contentHashCode();
+    }
+
+    /**
+     * Returns the folded orientation corresponds to the {@code unfoldedOrientation} found in
+     * {@link #foldableOrientations}. If not found, returns
+     * {@link WallpaperManager.ORIENTATION_UNKNOWN}.
+     */
+    public int getFoldedOrientation(int unfoldedOrientation) {
+        for (FoldableOrientations orientations : foldableOrientations) {
+            if (orientations.unfoldedOrientation == unfoldedOrientation) {
+                return orientations.foldedOrientation;
+            }
+        }
+        return ORIENTATION_UNKNOWN;
+    }
+
+    /**
+     * Returns the unfolded orientation corresponds to the {@code foldedOrientation} found in
+     * {@link #foldableOrientations}. If not found, returns
+     * {@link WallpaperManager.ORIENTATION_UNKNOWN}.
+     */
+    public int getUnfoldedOrientation(int foldedOrientation) {
+        for (FoldableOrientations orientations : foldableOrientations) {
+            if (orientations.foldedOrientation == foldedOrientation) {
+                return orientations.unfoldedOrientation;
+            }
+        }
+        return ORIENTATION_UNKNOWN;
+    }
+
+    private static SparseArray<Point> getDisplaySizes(Set<WindowMetrics> displayMetrics) {
+        SparseArray<Point> displaySizes = new SparseArray<>();
+        for (WindowMetrics metric : displayMetrics) {
+            Rect bounds = metric.getBounds();
+            Point displaySize = new Point(bounds.width(), bounds.height());
+            Point reversedDisplaySize = new Point(displaySize.y, displaySize.x);
+            for (Point point : List.of(displaySize, reversedDisplaySize)) {
+                int orientation = WallpaperManager.getOrientation(point);
+                // don't add an entry if there is already a larger display of the same orientation
+                Point display = displaySizes.get(orientation);
+                if (display == null || display.x * display.y < point.x * point.y) {
+                    displaySizes.put(orientation, point);
+                }
+            }
+        }
+        return displaySizes;
+    }
+
+    private static boolean isLargeScreen(Set<WindowMetrics> displayMetrics) {
+        float smallestWidth = Float.MAX_VALUE;
+        for (WindowMetrics metric : displayMetrics) {
+            Rect bounds = metric.getBounds();
+            smallestWidth = Math.min(smallestWidth, bounds.width() / metric.getDensity());
+        }
+        return smallestWidth >= LARGE_SCREEN_SMALLEST_SCREEN_WIDTH_DP;
+    }
+
+    /**
+     * Determines all potential foldable orientations, populating {@code
+     * outFoldableOrientationPairs} with pairs of (folded orientation, unfolded orientation). If
+     * {@code defaultDisplayMetrics} isn't for foldable, {@code outFoldableOrientationPairs} will
+     * not be populated.
+     */
+    private static List<FoldableOrientations> getFoldableOrientations(
+            Set<WindowMetrics> defaultDisplayMetrics) {
+        if (defaultDisplayMetrics.size() != 2) {
+            return Collections.emptyList();
+        }
+        List<FoldableOrientations> foldableOrientations = new ArrayList<>();
+        float surface = 0;
+        int firstOrientation = -1;
+        for (WindowMetrics metric : defaultDisplayMetrics) {
+            Rect bounds = metric.getBounds();
+            Point displaySize = new Point(bounds.width(), bounds.height());
+
+            int orientation = WallpaperManager.getOrientation(displaySize);
+            float newSurface = displaySize.x * displaySize.y
+                    / (metric.getDensity() * metric.getDensity());
+            if (surface <= 0) {
+                surface = newSurface;
+                firstOrientation = orientation;
+            } else {
+                FoldableOrientations orientations = (newSurface > surface)
+                        ? new FoldableOrientations(firstOrientation, orientation)
+                        : new FoldableOrientations(orientation, firstOrientation);
+                FoldableOrientations rotatedOrientations = new FoldableOrientations(
+                        getRotatedOrientation(orientations.foldedOrientation),
+                        getRotatedOrientation(orientations.unfoldedOrientation));
+                foldableOrientations.add(orientations);
+                foldableOrientations.add(rotatedOrientations);
+            }
+        }
+        return Collections.unmodifiableList(foldableOrientations);
+    }
+}
diff --git a/services/core/java/com/android/server/wallpaper/WallpaperDisplayHelper.java b/services/core/java/com/android/server/wallpaper/WallpaperDisplayHelper.java
index 3636f5a..bff5fc9 100644
--- a/services/core/java/com/android/server/wallpaper/WallpaperDisplayHelper.java
+++ b/services/core/java/com/android/server/wallpaper/WallpaperDisplayHelper.java
@@ -16,31 +16,25 @@
 
 package com.android.server.wallpaper;
 
-import static android.app.WallpaperManager.ORIENTATION_UNKNOWN;
-import static android.app.WallpaperManager.getRotatedOrientation;
 import static android.view.Display.DEFAULT_DISPLAY;
 
 import static com.android.window.flags.Flags.multiCrop;
 
 import android.app.WallpaperManager;
+import android.content.res.Resources;
 import android.graphics.Point;
 import android.graphics.Rect;
 import android.hardware.display.DisplayManager;
 import android.os.Binder;
 import android.os.Debug;
-import android.util.Pair;
 import android.util.Slog;
 import android.util.SparseArray;
 import android.view.Display;
 import android.view.DisplayInfo;
 import android.view.WindowManager;
-import android.view.WindowMetrics;
 
 import com.android.server.wm.WindowManagerInternal;
 
-import java.util.ArrayList;
-import java.util.List;
-import java.util.Set;
 import java.util.function.Consumer;
 
 /**
@@ -59,65 +53,25 @@
     }
 
     private static final String TAG = WallpaperDisplayHelper.class.getSimpleName();
-    private static final float LARGE_SCREEN_MIN_DP = 600f;
 
     private final SparseArray<DisplayData> mDisplayDatas = new SparseArray<>();
     private final DisplayManager mDisplayManager;
     private final WindowManagerInternal mWindowManagerInternal;
-    private final SparseArray<Point> mDefaultDisplaySizes = new SparseArray<>();
 
-    // related orientations pairs for foldable (folded orientation, unfolded orientation)
-    private final List<Pair<Integer, Integer>> mFoldableOrientationPairs = new ArrayList<>();
-
-    private final boolean mIsFoldable;
-    private boolean mIsLargeScreen = false;
+    private final WallpaperDefaultDisplayInfo mDefaultDisplayInfo;
 
     WallpaperDisplayHelper(
             DisplayManager displayManager,
             WindowManager windowManager,
             WindowManagerInternal windowManagerInternal,
-            boolean isFoldable) {
+            Resources resources) {
         mDisplayManager = displayManager;
         mWindowManagerInternal = windowManagerInternal;
-        mIsFoldable = isFoldable;
-        if (!multiCrop()) return;
-        Set<WindowMetrics> metrics = windowManager.getPossibleMaximumWindowMetrics(DEFAULT_DISPLAY);
-        boolean populateOrientationPairs = isFoldable && metrics.size() == 2;
-        float surface = 0;
-        int firstOrientation = -1;
-        for (WindowMetrics metric: metrics) {
-            Rect bounds = metric.getBounds();
-            Point displaySize = new Point(bounds.width(), bounds.height());
-            Point reversedDisplaySize = new Point(displaySize.y, displaySize.x);
-            for (Point point : List.of(displaySize, reversedDisplaySize)) {
-                int orientation = WallpaperManager.getOrientation(point);
-                // don't add an entry if there is already a larger display of the same orientation
-                Point display = mDefaultDisplaySizes.get(orientation);
-                if (display == null || display.x * display.y < point.x * point.y) {
-                    mDefaultDisplaySizes.put(orientation, point);
-                }
-            }
-
-            mIsLargeScreen |= (displaySize.x / metric.getDensity() >= LARGE_SCREEN_MIN_DP);
-
-            if (populateOrientationPairs) {
-                int orientation = WallpaperManager.getOrientation(displaySize);
-                float newSurface = displaySize.x * displaySize.y
-                        / (metric.getDensity() * metric.getDensity());
-                if (surface <= 0) {
-                    surface = newSurface;
-                    firstOrientation = orientation;
-                } else {
-                    Pair<Integer, Integer> pair = (newSurface > surface)
-                            ? new Pair<>(firstOrientation, orientation)
-                            : new Pair<>(orientation, firstOrientation);
-                    Pair<Integer, Integer> rotatedPair = new Pair<>(
-                            getRotatedOrientation(pair.first), getRotatedOrientation(pair.second));
-                    mFoldableOrientationPairs.add(pair);
-                    mFoldableOrientationPairs.add(rotatedPair);
-                }
-            }
+        if (!multiCrop()) {
+            mDefaultDisplayInfo = new WallpaperDefaultDisplayInfo();
+            return;
         }
+        mDefaultDisplayInfo = new WallpaperDefaultDisplayInfo(windowManager, resources);
     }
 
     DisplayData getDisplayDataOrCreate(int displayId) {
@@ -203,51 +157,21 @@
     }
 
     SparseArray<Point> getDefaultDisplaySizes() {
-        return mDefaultDisplaySizes;
+        return mDefaultDisplayInfo.defaultDisplaySizes;
     }
 
     /** Return the number of pixel of the largest dimension of the default display */
     int getDefaultDisplayLargestDimension() {
+        SparseArray<Point> defaultDisplaySizes = mDefaultDisplayInfo.defaultDisplaySizes;
         int result = -1;
-        for (int i = 0; i < mDefaultDisplaySizes.size(); i++) {
-            Point size = mDefaultDisplaySizes.valueAt(i);
+        for (int i = 0; i < defaultDisplaySizes.size(); i++) {
+            Point size = defaultDisplaySizes.valueAt(i);
             result = Math.max(result, Math.max(size.x, size.y));
         }
         return result;
     }
 
-    boolean isFoldable() {
-        return mIsFoldable;
-    }
-
-    /**
-     * Return true if any of the screens of the default display is considered large (DP >= 600)
-     */
-    boolean isLargeScreen() {
-        return mIsLargeScreen;
-    }
-
-    /**
-     * If a given orientation corresponds to an unfolded orientation on foldable, return the
-     * corresponding folded orientation. Otherwise, return UNKNOWN. Always return UNKNOWN if the
-     * device is not a foldable.
-     */
-    int getFoldedOrientation(int orientation) {
-        for (Pair<Integer, Integer> pair : mFoldableOrientationPairs) {
-            if (pair.second.equals(orientation)) return pair.first;
-        }
-        return ORIENTATION_UNKNOWN;
-    }
-
-    /**
-     * If a given orientation corresponds to a folded orientation on foldable, return the
-     * corresponding unfolded orientation. Otherwise, return UNKNOWN. Always return UNKNOWN if the
-     * device is not a foldable.
-     */
-    int getUnfoldedOrientation(int orientation) {
-        for (Pair<Integer, Integer> pair : mFoldableOrientationPairs) {
-            if (pair.first.equals(orientation)) return pair.second;
-        }
-        return ORIENTATION_UNKNOWN;
+    public WallpaperDefaultDisplayInfo getDefaultDisplayInfo() {
+        return mDefaultDisplayInfo;
     }
 }
diff --git a/services/core/java/com/android/server/wallpaper/WallpaperManagerService.java b/services/core/java/com/android/server/wallpaper/WallpaperManagerService.java
index bac7326..e7da33d 100644
--- a/services/core/java/com/android/server/wallpaper/WallpaperManagerService.java
+++ b/services/core/java/com/android/server/wallpaper/WallpaperManagerService.java
@@ -1666,12 +1666,9 @@
         DisplayManager displayManager = mContext.getSystemService(DisplayManager.class);
         displayManager.registerDisplayListener(mDisplayListener, null /* handler */);
         WindowManager windowManager = mContext.getSystemService(WindowManager.class);
-        boolean isFoldable = mContext.getResources()
-                .getIntArray(R.array.config_foldedDeviceStates).length > 0;
         mWallpaperDisplayHelper = new WallpaperDisplayHelper(
-                displayManager, windowManager, mWindowManagerInternal, isFoldable);
+                displayManager, windowManager, mWindowManagerInternal, mContext.getResources());
         mWallpaperCropper = new WallpaperCropper(mWallpaperDisplayHelper);
-        mWindowManagerInternal.setWallpaperCropUtils(mWallpaperCropper::getCrop);
         mActivityManager = mContext.getSystemService(ActivityManager.class);
 
         if (mContext.getResources().getBoolean(
@@ -2510,9 +2507,11 @@
             List<Rect> result = new ArrayList<>();
             boolean rtl = TextUtils.getLayoutDirectionFromLocale(Locale.getDefault())
                     == View.LAYOUT_DIRECTION_RTL;
+            WallpaperDefaultDisplayInfo defaultDisplayInfo =
+                    mWallpaperDisplayHelper.getDefaultDisplayInfo();
             for (Point displaySize : displaySizes) {
-                result.add(mWallpaperCropper.getCrop(
-                        displaySize, croppedBitmapSize, adjustedRelativeSuggestedCrops, rtl));
+                result.add(WallpaperCropper.getCrop(displaySize, defaultDisplayInfo,
+                        croppedBitmapSize, adjustedRelativeSuggestedCrops, rtl));
             }
             if (originalBitmap) result = WallpaperCropper.getOriginalCropHints(wallpaper, result);
             return result;
@@ -2548,8 +2547,11 @@
         List<Rect> result = new ArrayList<>();
         boolean rtl = TextUtils.getLayoutDirectionFromLocale(Locale.getDefault())
                 == View.LAYOUT_DIRECTION_RTL;
+        WallpaperDefaultDisplayInfo defaultDisplayInfo =
+                mWallpaperDisplayHelper.getDefaultDisplayInfo();
         for (Point displaySize : displaySizes) {
-            result.add(mWallpaperCropper.getCrop(displaySize, bitmapSize, defaultCrops, rtl));
+            result.add(WallpaperCropper.getCrop(displaySize, defaultDisplayInfo, bitmapSize,
+                    defaultCrops, rtl));
         }
         return result;
     }
diff --git a/services/core/java/com/android/server/wm/ActivityMetricsLogger.java b/services/core/java/com/android/server/wm/ActivityMetricsLogger.java
index a941838..e2b47b9 100644
--- a/services/core/java/com/android/server/wm/ActivityMetricsLogger.java
+++ b/services/core/java/com/android/server/wm/ActivityMetricsLogger.java
@@ -112,7 +112,6 @@
 import com.android.server.LocalServices;
 import com.android.server.apphibernation.AppHibernationManagerInternal;
 import com.android.server.apphibernation.AppHibernationService;
-import com.android.window.flags.Flags;
 
 import java.util.ArrayList;
 import java.util.concurrent.TimeUnit;
@@ -807,14 +806,8 @@
             }
             final Task otherTask = otherInfo.mLastLaunchedActivity.getTask();
             // The adjacent task is the split root in which activities are started
-            final boolean isDescendantOfAdjacent;
-            if (Flags.allowMultipleAdjacentTaskFragments()) {
-                isDescendantOfAdjacent = launchedSplitRootTask.forOtherAdjacentTasks(
-                        otherTask::isDescendantOf);
-            } else {
-                isDescendantOfAdjacent = otherTask.isDescendantOf(
-                        launchedSplitRootTask.getAdjacentTask());
-            }
+            final boolean isDescendantOfAdjacent = launchedSplitRootTask.forOtherAdjacentTasks(
+                    otherTask::isDescendantOf);
             if (isDescendantOfAdjacent) {
                 if (DEBUG_METRICS) {
                     Slog.i(TAG, "Found adjacent tasks t1=" + launchedActivityTask.mTaskId
diff --git a/services/core/java/com/android/server/wm/ActivityRecord.java b/services/core/java/com/android/server/wm/ActivityRecord.java
index 3cd4db7..e91d889 100644
--- a/services/core/java/com/android/server/wm/ActivityRecord.java
+++ b/services/core/java/com/android/server/wm/ActivityRecord.java
@@ -1716,6 +1716,7 @@
         }
 
         mAppCompatController.getLetterboxPolicy().onMovedToDisplay(mDisplayContent.getDisplayId());
+        mAppCompatController.getDisplayCompatModePolicy().onMovedToDisplay();
     }
 
     void layoutLetterboxIfNeeded(WindowState winHint) {
@@ -3801,19 +3802,10 @@
         final TaskFragment taskFragment = getTaskFragment();
         if (next != null && taskFragment != null && taskFragment.isEmbedded()) {
             final TaskFragment organized = taskFragment.getOrganizedTaskFragment();
-            if (Flags.allowMultipleAdjacentTaskFragments()) {
-                delayRemoval = organized != null
-                        && organized.topRunningActivity() == null
-                        && organized.isDelayLastActivityRemoval()
-                        && organized.forOtherAdjacentTaskFragments(next::isDescendantOf);
-            } else {
-                final TaskFragment adjacent =
-                        organized != null ? organized.getAdjacentTaskFragment() : null;
-                if (adjacent != null && next.isDescendantOf(adjacent)
-                        && organized.topRunningActivity() == null) {
-                    delayRemoval = organized.isDelayLastActivityRemoval();
-                }
-            }
+            delayRemoval = organized != null
+                    && organized.topRunningActivity() == null
+                    && organized.isDelayLastActivityRemoval()
+                    && organized.forOtherAdjacentTaskFragments(next::isDescendantOf);
         }
 
         // isNextNotYetVisible is to check if the next activity is invisible, or it has been
@@ -4787,11 +4779,6 @@
         }
 
         // Make sure the embedded adjacent can also be shown.
-        if (!Flags.allowMultipleAdjacentTaskFragments()) {
-            final ActivityRecord adjacentActivity = taskFragment.getAdjacentTaskFragment()
-                    .getTopNonFinishingActivity();
-            return canShowWhenLocked(adjacentActivity);
-        }
         final boolean hasAdjacentNotAllowToShow = taskFragment.forOtherAdjacentTaskFragments(
                 adjacentTF -> !canShowWhenLocked(adjacentTF.getTopNonFinishingActivity()));
         return !hasAdjacentNotAllowToShow;
@@ -8980,6 +8967,7 @@
         // Reset the existing override configuration so it can be updated according to the latest
         // configuration.
         mAppCompatController.getSizeCompatModePolicy().clearSizeCompatMode();
+        mAppCompatController.getDisplayCompatModePolicy().onProcessRestarted();
 
         if (!attachedToProcess()) {
             return;
diff --git a/services/core/java/com/android/server/wm/ActivitySnapshotController.java b/services/core/java/com/android/server/wm/ActivitySnapshotController.java
index cb122f2..0f1939b 100644
--- a/services/core/java/com/android/server/wm/ActivitySnapshotController.java
+++ b/services/core/java/com/android/server/wm/ActivitySnapshotController.java
@@ -34,7 +34,6 @@
 
 import com.android.internal.annotations.VisibleForTesting;
 import com.android.server.wm.BaseAppSnapshotPersister.PersistInfoProvider;
-import com.android.window.flags.Flags;
 
 import java.io.File;
 import java.io.PrintWriter;
@@ -532,26 +531,6 @@
         final int currentIndex = currTF.asTask() != null
                 ? currentTask.mChildren.indexOf(currentActivity)
                 : currentTask.mChildren.indexOf(currTF);
-        if (!Flags.allowMultipleAdjacentTaskFragments()) {
-            final int prevAdjacentIndex = currentTask.mChildren.indexOf(
-                    prevTF.getAdjacentTaskFragment());
-            if (prevAdjacentIndex > currentIndex) {
-                // PrevAdjacentTF already above currentActivity
-                return;
-            }
-            // Add both the one below, and its adjacent.
-            if (!inTransition || isInParticipant(initPrev, mTmpTransitionParticipants)) {
-                result.add(initPrev);
-            }
-            final ActivityRecord prevAdjacentActivity = prevTF.getAdjacentTaskFragment()
-                    .getTopMostActivity();
-            if (prevAdjacentActivity != null && (!inTransition
-                    || isInParticipant(prevAdjacentActivity, mTmpTransitionParticipants))) {
-                result.add(prevAdjacentActivity);
-            }
-            return;
-        }
-
         final boolean hasAdjacentAboveCurrent = prevTF.forOtherAdjacentTaskFragments(
                 prevAdjacentTF -> {
                     final int prevAdjacentIndex = currentTask.mChildren.indexOf(prevAdjacentTF);
diff --git a/services/core/java/com/android/server/wm/ActivityTaskSupervisor.java b/services/core/java/com/android/server/wm/ActivityTaskSupervisor.java
index a7f2153..b056312 100644
--- a/services/core/java/com/android/server/wm/ActivityTaskSupervisor.java
+++ b/services/core/java/com/android/server/wm/ActivityTaskSupervisor.java
@@ -164,7 +164,6 @@
 import com.android.server.pm.SaferIntentUtils;
 import com.android.server.utils.Slogf;
 import com.android.server.wm.ActivityMetricsLogger.LaunchingState;
-import com.android.window.flags.Flags;
 
 import java.io.FileDescriptor;
 import java.io.PrintWriter;
@@ -2991,17 +2990,9 @@
 
                 if (child.asTaskFragment() != null
                         && child.asTaskFragment().hasAdjacentTaskFragment()) {
-                    final boolean isAnyTranslucent;
-                    if (Flags.allowMultipleAdjacentTaskFragments()) {
-                        final TaskFragment.AdjacentSet set =
-                                child.asTaskFragment().getAdjacentTaskFragments();
-                        isAnyTranslucent = set.forAllTaskFragments(
-                                tf -> !isOpaque(tf), null);
-                    } else {
-                        final TaskFragment adjacent = child.asTaskFragment()
-                                .getAdjacentTaskFragment();
-                        isAnyTranslucent = !isOpaque(child) || !isOpaque(adjacent);
-                    }
+                    final boolean isAnyTranslucent = !isOpaque(child)
+                            || child.asTaskFragment().forOtherAdjacentTaskFragments(
+                                    tf -> !isOpaque(tf));
                     if (!isAnyTranslucent) {
                         // This task fragment and all its adjacent task fragments are opaque,
                         // consider it opaque even if it doesn't fill its parent.
diff --git a/services/core/java/com/android/server/wm/AppCompatController.java b/services/core/java/com/android/server/wm/AppCompatController.java
index 48f08e9..c479591 100644
--- a/services/core/java/com/android/server/wm/AppCompatController.java
+++ b/services/core/java/com/android/server/wm/AppCompatController.java
@@ -46,6 +46,8 @@
     private final AppCompatSizeCompatModePolicy mSizeCompatModePolicy;
     @NonNull
     private final AppCompatSandboxingPolicy mSandboxingPolicy;
+    @NonNull
+    private final AppCompatDisplayCompatModePolicy mDisplayCompatModePolicy;
 
     AppCompatController(@NonNull WindowManagerService wmService,
                         @NonNull ActivityRecord activityRecord) {
@@ -69,6 +71,7 @@
         mSizeCompatModePolicy = new AppCompatSizeCompatModePolicy(activityRecord,
                 mAppCompatOverrides);
         mSandboxingPolicy = new AppCompatSandboxingPolicy(activityRecord);
+        mDisplayCompatModePolicy = new AppCompatDisplayCompatModePolicy();
     }
 
     @NonNull
@@ -151,6 +154,11 @@
         return mSandboxingPolicy;
     }
 
+    @NonNull
+    AppCompatDisplayCompatModePolicy getDisplayCompatModePolicy() {
+        return mDisplayCompatModePolicy;
+    }
+
     void dump(@NonNull PrintWriter pw, @NonNull String prefix) {
         getTransparentPolicy().dump(pw, prefix);
         getLetterboxPolicy().dump(pw, prefix);
diff --git a/services/core/java/com/android/server/wm/AppCompatDisplayCompatModePolicy.java b/services/core/java/com/android/server/wm/AppCompatDisplayCompatModePolicy.java
new file mode 100644
index 0000000..acf5170
--- /dev/null
+++ b/services/core/java/com/android/server/wm/AppCompatDisplayCompatModePolicy.java
@@ -0,0 +1,39 @@
+/*
+ * 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;
+
+import com.android.window.flags.Flags;
+
+/**
+ * Encapsulate app-compat logic for multi-display environments.
+ */
+class AppCompatDisplayCompatModePolicy {
+
+    private boolean mIsRestartMenuEnabledForDisplayMove;
+
+    boolean isRestartMenuEnabledForDisplayMove() {
+        return Flags.enableRestartMenuForConnectedDisplays() && mIsRestartMenuEnabledForDisplayMove;
+    }
+
+    void onMovedToDisplay() {
+        mIsRestartMenuEnabledForDisplayMove = true;
+    }
+
+    void onProcessRestarted() {
+        mIsRestartMenuEnabledForDisplayMove = false;
+    }
+}
diff --git a/services/core/java/com/android/server/wm/AppCompatUtils.java b/services/core/java/com/android/server/wm/AppCompatUtils.java
index 1460440..b91a125 100644
--- a/services/core/java/com/android/server/wm/AppCompatUtils.java
+++ b/services/core/java/com/android/server/wm/AppCompatUtils.java
@@ -161,6 +161,9 @@
                 top.mAppCompatController.getLetterboxOverrides()
                         .isLetterboxEducationEnabled());
 
+        appCompatTaskInfo.setRestartMenuEnabledForDisplayMove(top.mAppCompatController
+                .getDisplayCompatModePolicy().isRestartMenuEnabledForDisplayMove());
+
         final AppCompatAspectRatioOverrides aspectRatioOverrides =
                 top.mAppCompatController.getAspectRatioOverrides();
         appCompatTaskInfo.setUserFullscreenOverrideEnabled(
diff --git a/services/core/java/com/android/server/wm/BackNavigationController.java b/services/core/java/com/android/server/wm/BackNavigationController.java
index e9b7649..dfe323c 100644
--- a/services/core/java/com/android/server/wm/BackNavigationController.java
+++ b/services/core/java/com/android/server/wm/BackNavigationController.java
@@ -479,21 +479,16 @@
                 }
             } else {
                 // If adjacent TF has companion to current TF, those two TF will be closed together.
-                final TaskFragment adjacentTF;
-                if (Flags.allowMultipleAdjacentTaskFragments()) {
-                    if (currTF.getAdjacentTaskFragments().size() > 2) {
-                        throw new IllegalStateException(
-                                "Not yet support 3+ adjacent for non-Task TFs");
-                    }
-                    final TaskFragment[] tmpAdjacent = new TaskFragment[1];
-                    currTF.forOtherAdjacentTaskFragments(tf -> {
-                        tmpAdjacent[0] = tf;
-                        return true;
-                    });
-                    adjacentTF = tmpAdjacent[0];
-                } else {
-                    adjacentTF = currTF.getAdjacentTaskFragment();
+                if (currTF.getAdjacentTaskFragments().size() > 2) {
+                    throw new IllegalStateException(
+                            "Not yet support 3+ adjacent for non-Task TFs");
                 }
+                final TaskFragment[] tmpAdjacent = new TaskFragment[1];
+                currTF.forOtherAdjacentTaskFragments(tf -> {
+                    tmpAdjacent[0] = tf;
+                    return true;
+                });
+                final TaskFragment adjacentTF = tmpAdjacent[0];
                 if (isSecondCompanionToFirst(currTF, adjacentTF)) {
                     // The two TFs are adjacent (visually displayed side-by-side), search if any
                     // activity below the lowest one.
@@ -553,15 +548,6 @@
         if (!prevTF.hasAdjacentTaskFragment()) {
             return;
         }
-        if (!Flags.allowMultipleAdjacentTaskFragments()) {
-            final TaskFragment prevTFAdjacent = prevTF.getAdjacentTaskFragment();
-            final ActivityRecord prevActivityAdjacent =
-                    prevTFAdjacent.getTopNonFinishingActivity();
-            if (prevActivityAdjacent != null) {
-                outPrevActivities.add(prevActivityAdjacent);
-            }
-            return;
-        }
         prevTF.forOtherAdjacentTaskFragments(prevTFAdjacent -> {
             final ActivityRecord prevActivityAdjacent =
                     prevTFAdjacent.getTopNonFinishingActivity();
@@ -577,14 +563,6 @@
         if (mainTF == null || !mainTF.hasAdjacentTaskFragment()) {
             return;
         }
-        if (!Flags.allowMultipleAdjacentTaskFragments()) {
-            final TaskFragment adjacentTF = mainTF.getAdjacentTaskFragment();
-            final ActivityRecord topActivity = adjacentTF.getTopNonFinishingActivity();
-            if (topActivity != null) {
-                outList.add(topActivity);
-            }
-            return;
-        }
         mainTF.forOtherAdjacentTaskFragments(adjacentTF -> {
             final ActivityRecord topActivity = adjacentTF.getTopNonFinishingActivity();
             if (topActivity != null) {
diff --git a/services/core/java/com/android/server/wm/BackgroundActivityStartController.java b/services/core/java/com/android/server/wm/BackgroundActivityStartController.java
index 2287a68..f50a68c 100644
--- a/services/core/java/com/android/server/wm/BackgroundActivityStartController.java
+++ b/services/core/java/com/android/server/wm/BackgroundActivityStartController.java
@@ -49,8 +49,8 @@
 import static com.android.window.flags.Flags.balImprovedMetrics;
 import static com.android.window.flags.Flags.balRequireOptInByPendingIntentCreator;
 import static com.android.window.flags.Flags.balShowToastsBlocked;
-import static com.android.window.flags.Flags.balStrictModeRo;
 import static com.android.window.flags.Flags.balStrictModeGracePeriod;
+import static com.android.window.flags.Flags.balStrictModeRo;
 
 import static java.lang.annotation.RetentionPolicy.SOURCE;
 import static java.util.Objects.requireNonNull;
@@ -91,7 +91,6 @@
 import com.android.server.UiThread;
 import com.android.server.am.PendingIntentRecord;
 import com.android.server.wm.BackgroundLaunchProcessController.BalCheckConfiguration;
-import com.android.window.flags.Flags;
 
 import java.lang.annotation.Retention;
 import java.util.ArrayList;
@@ -1687,14 +1686,6 @@
         }
 
         // Check the adjacent fragment.
-        if (!Flags.allowMultipleAdjacentTaskFragments()) {
-            TaskFragment adjacentTaskFragment = taskFragment.getAdjacentTaskFragment();
-            topActivity = adjacentTaskFragment.getActivity(topOfStackPredicate);
-            if (topActivity == null) {
-                return bas;
-            }
-            return checkCrossUidActivitySwitchFromBelow(topActivity, uid, bas);
-        }
         final BlockActivityStart[] out = { bas };
         taskFragment.forOtherAdjacentTaskFragments(adjacentTaskFragment -> {
             final ActivityRecord top = adjacentTaskFragment.getActivity(topOfStackPredicate);
diff --git a/services/core/java/com/android/server/wm/DeferredDisplayUpdater.java b/services/core/java/com/android/server/wm/DeferredDisplayUpdater.java
index f473b7b..fcc6972 100644
--- a/services/core/java/com/android/server/wm/DeferredDisplayUpdater.java
+++ b/services/core/java/com/android/server/wm/DeferredDisplayUpdater.java
@@ -18,7 +18,7 @@
 
 import static android.view.WindowManager.TRANSIT_CHANGE;
 
-import static com.android.internal.protolog.WmProtoLogGroups.WM_DEBUG_WINDOW_TRANSITIONS;
+import static com.android.internal.protolog.WmProtoLogGroups.WM_DEBUG_WINDOW_TRANSITIONS_MIN;
 import static com.android.server.wm.ActivityTaskManagerService.POWER_MODE_REASON_CHANGE_DISPLAY;
 import static com.android.server.wm.utils.DisplayInfoOverrides.WM_OVERRIDE_FIELDS;
 import static com.android.server.wm.utils.DisplayInfoOverrides.copyDisplayInfoFields;
@@ -140,8 +140,9 @@
         if (displayInfoDiff == DIFF_EVERYTHING
                 || !mDisplayContent.getLastHasContent()
                 || !mDisplayContent.mTransitionController.isShellTransitionsEnabled()) {
-            ProtoLog.d(WM_DEBUG_WINDOW_TRANSITIONS,
-                    "DeferredDisplayUpdater: applying DisplayInfo immediately");
+            ProtoLog.d(WM_DEBUG_WINDOW_TRANSITIONS_MIN,
+                    "DeferredDisplayUpdater: applying DisplayInfo(%d x %d) immediately",
+                    displayInfo.logicalWidth, displayInfo.logicalHeight);
 
             mLastWmDisplayInfo = displayInfo;
             applyLatestDisplayInfo();
@@ -151,17 +152,23 @@
 
         // If there are non WM-specific display info changes, apply only these fields immediately
         if ((displayInfoDiff & DIFF_NOT_WM_DEFERRABLE) > 0) {
-            ProtoLog.d(WM_DEBUG_WINDOW_TRANSITIONS,
-                    "DeferredDisplayUpdater: partially applying DisplayInfo immediately");
+            ProtoLog.d(WM_DEBUG_WINDOW_TRANSITIONS_MIN,
+                    "DeferredDisplayUpdater: partially applying DisplayInfo(%d x %d) immediately",
+                    displayInfo.logicalWidth, displayInfo.logicalHeight);
             applyLatestDisplayInfo();
         }
 
         // If there are WM-specific display info changes, apply them through a Shell transition
         if ((displayInfoDiff & DIFF_WM_DEFERRABLE) > 0) {
-            ProtoLog.d(WM_DEBUG_WINDOW_TRANSITIONS,
-                    "DeferredDisplayUpdater: deferring DisplayInfo update");
+            ProtoLog.d(WM_DEBUG_WINDOW_TRANSITIONS_MIN,
+                    "DeferredDisplayUpdater: deferring DisplayInfo(%d x %d) update",
+                    displayInfo.logicalWidth, displayInfo.logicalHeight);
 
             requestDisplayChangeTransition(physicalDisplayUpdated, () -> {
+                ProtoLog.d(WM_DEBUG_WINDOW_TRANSITIONS_MIN,
+                        "DeferredDisplayUpdater: applying DisplayInfo(%d x %d) after deferring",
+                        displayInfo.logicalWidth, displayInfo.logicalHeight);
+
                 // Apply deferrable fields to DisplayContent only when the transition
                 // starts collecting, non-deferrable fields are ignored in mLastWmDisplayInfo
                 mLastWmDisplayInfo = displayInfo;
@@ -199,7 +206,7 @@
                         mDisplayContent.getDisplayPolicy().getNotificationShade();
                 if (notificationShade != null && notificationShade.isVisible()
                         && mDisplayContent.mAtmService.mKeyguardController.isKeyguardOrAodShowing(
-                                mDisplayContent.mDisplayId)) {
+                        mDisplayContent.mDisplayId)) {
                     Slog.i(TAG, notificationShade + " uses blast for display switch");
                     notificationShade.mSyncMethodOverride = BLASTSyncEngine.METHOD_BLAST;
                 }
@@ -209,9 +216,6 @@
             try {
                 onStartCollect.run();
 
-                ProtoLog.d(WM_DEBUG_WINDOW_TRANSITIONS,
-                        "DeferredDisplayUpdater: applied DisplayInfo after deferring");
-
                 if (physicalDisplayUpdated) {
                     onDisplayUpdated(transition, fromRotation, startBounds);
                 } else {
diff --git a/services/core/java/com/android/server/wm/DesktopModeLaunchParamsModifier.java b/services/core/java/com/android/server/wm/DesktopModeLaunchParamsModifier.java
index d466a64..ddcb5ec 100644
--- a/services/core/java/com/android/server/wm/DesktopModeLaunchParamsModifier.java
+++ b/services/core/java/com/android/server/wm/DesktopModeLaunchParamsModifier.java
@@ -19,6 +19,12 @@
 import static android.app.WindowConfiguration.WINDOWING_MODE_FREEFORM;
 import static android.app.WindowConfiguration.WINDOWING_MODE_FULLSCREEN;
 import static android.app.WindowConfiguration.WINDOWING_MODE_UNDEFINED;
+import static android.content.Intent.FLAG_ACTIVITY_CLEAR_TASK;
+import static android.content.Intent.FLAG_ACTIVITY_MULTIPLE_TASK;
+import static android.content.Intent.FLAG_ACTIVITY_NEW_TASK;
+import static android.content.pm.ActivityInfo.LAUNCH_SINGLE_INSTANCE;
+import static android.content.pm.ActivityInfo.LAUNCH_SINGLE_INSTANCE_PER_TASK;
+import static android.content.pm.ActivityInfo.LAUNCH_SINGLE_TASK;
 
 import static com.android.server.wm.ActivityTaskManagerDebugConfig.TAG_ATM;
 import static com.android.server.wm.ActivityTaskManagerDebugConfig.TAG_WITH_CLASS_NAME;
@@ -131,6 +137,18 @@
             return RESULT_SKIP;
         }
 
+        if (DesktopModeFlags.INHERIT_TASK_BOUNDS_FOR_TRAMPOLINE_TASK_LAUNCHES.isTrue()) {
+            ActivityRecord topVisibleFreeformActivity =
+                    task.getDisplayContent().getTopMostVisibleFreeformActivity();
+            if (shouldInheritExistingTaskBounds(topVisibleFreeformActivity, activity, task)) {
+                appendLog("inheriting bounds from existing closing instance");
+                outParams.mBounds.set(topVisibleFreeformActivity.getBounds());
+                appendLog("final desktop mode task bounds set to %s", outParams.mBounds);
+                // Return result done to prevent other modifiers from changing or cascading bounds.
+                return RESULT_DONE;
+            }
+        }
+
         DesktopModeBoundsCalculator.updateInitialBounds(task, layout, activity, options,
                 outParams.mBounds, this::appendLog);
         appendLog("final desktop mode task bounds set to %s", outParams.mBounds);
@@ -159,7 +177,7 @@
         //  activity will also enter desktop mode. On this same relationship, we can also assume
         //  if there are not visible freeform tasks but a freeform activity is now launching, it
         //  will force the device into desktop mode.
-        return (task.getDisplayContent().getTopMostVisibleFreeformActivity() != null
+        return (task.getDisplayContent().getTopMostFreeformActivity() != null
                     && checkSourceWindowModesCompatible(task, options, currentParams))
                 || isRequestingFreeformWindowMode(task, options, currentParams);
     }
@@ -201,6 +219,40 @@
         };
     }
 
+    /**
+     * Whether the launching task should inherit the task bounds of an existing closing instance.
+     */
+    private boolean shouldInheritExistingTaskBounds(
+            @Nullable ActivityRecord existingTaskActivity,
+            @Nullable ActivityRecord launchingActivity,
+            @NonNull Task launchingTask) {
+        if (existingTaskActivity == null || launchingActivity == null) return false;
+        return (existingTaskActivity.packageName == launchingActivity.packageName)
+                && isLaunchingNewTask(launchingActivity.launchMode,
+                    launchingTask.getBaseIntent().getFlags())
+                && isClosingExitingInstance(launchingTask.getBaseIntent().getFlags());
+    }
+
+    /**
+     * Returns true if the launch mode or intent will result in a new task being created for the
+     * activity.
+     */
+    private boolean isLaunchingNewTask(int launchMode, int intentFlags) {
+        return launchMode == LAUNCH_SINGLE_TASK
+                || launchMode == LAUNCH_SINGLE_INSTANCE
+                || launchMode == LAUNCH_SINGLE_INSTANCE_PER_TASK
+                || (intentFlags & FLAG_ACTIVITY_NEW_TASK) != 0;
+    }
+
+    /**
+     * Returns true if the intent will result in an existing task instance being closed if a new
+     * one appears.
+     */
+    private boolean isClosingExitingInstance(int intentFlags) {
+        return (intentFlags & FLAG_ACTIVITY_CLEAR_TASK) != 0
+            || (intentFlags & FLAG_ACTIVITY_MULTIPLE_TASK) == 0;
+    }
+
     private void initLogBuilder(Task task, ActivityRecord activity) {
         if (DEBUG) {
             mLogBuilder = new StringBuilder(
diff --git a/services/core/java/com/android/server/wm/EnsureActivitiesVisibleHelper.java b/services/core/java/com/android/server/wm/EnsureActivitiesVisibleHelper.java
index a017a11..e508a6d 100644
--- a/services/core/java/com/android/server/wm/EnsureActivitiesVisibleHelper.java
+++ b/services/core/java/com/android/server/wm/EnsureActivitiesVisibleHelper.java
@@ -23,8 +23,6 @@
 import android.annotation.Nullable;
 import android.util.Slog;
 
-import com.android.window.flags.Flags;
-
 import java.util.ArrayList;
 
 /** Helper class to ensure activities are in the right visible state for a container. */
@@ -112,18 +110,11 @@
 
                 if (adjacentTaskFragments != null && adjacentTaskFragments.contains(
                         childTaskFragment)) {
-                    final boolean isTranslucent;
-                    if (Flags.allowMultipleAdjacentTaskFragments()) {
-                        isTranslucent = childTaskFragment.isTranslucent(starting)
-                                || childTaskFragment.forOtherAdjacentTaskFragments(
-                                        adjacentTaskFragment -> {
-                                            return adjacentTaskFragment.isTranslucent(starting);
-                                        });
-                    } else {
-                        isTranslucent = childTaskFragment.isTranslucent(starting)
-                                || childTaskFragment.getAdjacentTaskFragment()
-                                .isTranslucent(starting);
-                    }
+                    final boolean isTranslucent = childTaskFragment.isTranslucent(starting)
+                            || childTaskFragment.forOtherAdjacentTaskFragments(
+                                    adjacentTaskFragment -> {
+                                        return adjacentTaskFragment.isTranslucent(starting);
+                                    });
                     if (!isTranslucent) {
                         // Everything behind two adjacent TaskFragments are occluded.
                         mBehindFullyOccludedContainer = true;
@@ -135,14 +126,10 @@
                     if (adjacentTaskFragments == null) {
                         adjacentTaskFragments = new ArrayList<>();
                     }
-                    if (Flags.allowMultipleAdjacentTaskFragments()) {
-                        final ArrayList<TaskFragment> adjacentTfs = adjacentTaskFragments;
-                        childTaskFragment.forOtherAdjacentTaskFragments(adjacentTf -> {
-                            adjacentTfs.add(adjacentTf);
-                        });
-                    } else {
-                        adjacentTaskFragments.add(childTaskFragment.getAdjacentTaskFragment());
-                    }
+                    final ArrayList<TaskFragment> adjacentTfs = adjacentTaskFragments;
+                    childTaskFragment.forOtherAdjacentTaskFragments(adjacentTf -> {
+                        adjacentTfs.add(adjacentTf);
+                    });
                 }
             } else if (child.asActivityRecord() != null) {
                 setActivityVisibilityState(child.asActivityRecord(), starting, resumeTopActivity);
diff --git a/services/core/java/com/android/server/wm/RootWindowContainer.java b/services/core/java/com/android/server/wm/RootWindowContainer.java
index cf201c9..609302c 100644
--- a/services/core/java/com/android/server/wm/RootWindowContainer.java
+++ b/services/core/java/com/android/server/wm/RootWindowContainer.java
@@ -1732,26 +1732,14 @@
                     activityAssistInfos.clear();
                     activityAssistInfos.add(new ActivityAssistInfo(top));
                     // Check if the activity on the split screen.
-                    if (Flags.allowMultipleAdjacentTaskFragments()) {
-                        top.getTask().forOtherAdjacentTasks(task -> {
-                            final ActivityRecord adjacentActivityRecord =
-                                    task.getTopNonFinishingActivity();
-                            if (adjacentActivityRecord != null) {
-                                activityAssistInfos.add(
-                                        new ActivityAssistInfo(adjacentActivityRecord));
-                            }
-                        });
-                    } else {
-                        final Task adjacentTask = top.getTask().getAdjacentTask();
-                        if (adjacentTask != null) {
-                            final ActivityRecord adjacentActivityRecord =
-                                    adjacentTask.getTopNonFinishingActivity();
-                            if (adjacentActivityRecord != null) {
-                                activityAssistInfos.add(
-                                        new ActivityAssistInfo(adjacentActivityRecord));
-                            }
+                    top.getTask().forOtherAdjacentTasks(task -> {
+                        final ActivityRecord adjacentActivityRecord =
+                                task.getTopNonFinishingActivity();
+                        if (adjacentActivityRecord != null) {
+                            activityAssistInfos.add(
+                                    new ActivityAssistInfo(adjacentActivityRecord));
                         }
-                    }
+                    });
                     if (rootTask == topFocusedRootTask) {
                         topVisibleActivities.addAll(0, activityAssistInfos);
                     } else {
diff --git a/services/core/java/com/android/server/wm/Task.java b/services/core/java/com/android/server/wm/Task.java
index d16c301..e98b2b7 100644
--- a/services/core/java/com/android/server/wm/Task.java
+++ b/services/core/java/com/android/server/wm/Task.java
@@ -2461,21 +2461,6 @@
         return parentTask == null ? null : parentTask.getCreatedByOrganizerTask();
     }
 
-    /** @deprecated b/373709676 replace with {@link #forOtherAdjacentTasks(Consumer)} ()}. */
-    @Deprecated
-    @Nullable
-    Task getAdjacentTask() {
-        if (Flags.allowMultipleAdjacentTaskFragments()) {
-            throw new IllegalStateException("allowMultipleAdjacentTaskFragments is enabled. "
-                    + "Use #forOtherAdjacentTasks instead");
-        }
-        final Task taskWithAdjacent = getTaskWithAdjacent();
-        if (taskWithAdjacent == null) {
-            return null;
-        }
-        return taskWithAdjacent.getAdjacentTaskFragment().asTask();
-    }
-
     /** Finds the first Task parent (or itself) that has adjacent. */
     @Nullable
     Task getTaskWithAdjacent() {
@@ -2499,11 +2484,6 @@
      * Tasks. The invoke order is not guaranteed.
      */
     void forOtherAdjacentTasks(@NonNull Consumer<Task> callback) {
-        if (!Flags.allowMultipleAdjacentTaskFragments()) {
-            throw new IllegalStateException("allowMultipleAdjacentTaskFragments is not enabled. "
-                    + "Use #getAdjacentTask instead");
-        }
-
         final Task taskWithAdjacent = getTaskWithAdjacent();
         if (taskWithAdjacent == null) {
             return;
@@ -2521,10 +2501,6 @@
      * guaranteed.
      */
     boolean forOtherAdjacentTasks(@NonNull Predicate<Task> callback) {
-        if (!Flags.allowMultipleAdjacentTaskFragments()) {
-            throw new IllegalStateException("allowMultipleAdjacentTaskFragments is not enabled. "
-                    + "Use getAdjacentTask instead");
-        }
         final Task taskWithAdjacent = getTaskWithAdjacent();
         if (taskWithAdjacent == null) {
             return false;
@@ -3651,20 +3627,13 @@
                 final TaskFragment taskFragment = wc.asTaskFragment();
                 if (taskFragment != null && taskFragment.isEmbedded()
                         && taskFragment.hasAdjacentTaskFragment()) {
-                    if (Flags.allowMultipleAdjacentTaskFragments()) {
-                        final int[] nextLayer = { layer };
-                        taskFragment.forOtherAdjacentTaskFragments(adjacentTf -> {
-                            if (adjacentTf.shouldBoostDimmer()) {
-                                adjacentTf.assignLayer(t, nextLayer[0]++);
-                            }
-                        });
-                        layer = nextLayer[0];
-                    } else {
-                        final TaskFragment adjacentTf = taskFragment.getAdjacentTaskFragment();
+                    final int[] nextLayer = { layer };
+                    taskFragment.forOtherAdjacentTaskFragments(adjacentTf -> {
                         if (adjacentTf.shouldBoostDimmer()) {
-                            adjacentTf.assignLayer(t, layer++);
+                            adjacentTf.assignLayer(t, nextLayer[0]++);
                         }
-                    }
+                    });
+                    layer = nextLayer[0];
                 }
 
                 // Place the decor surface just above the owner TaskFragment.
diff --git a/services/core/java/com/android/server/wm/TaskDisplayArea.java b/services/core/java/com/android/server/wm/TaskDisplayArea.java
index 1966ecf..fb7bab4 100644
--- a/services/core/java/com/android/server/wm/TaskDisplayArea.java
+++ b/services/core/java/com/android/server/wm/TaskDisplayArea.java
@@ -60,7 +60,6 @@
 import com.android.internal.util.function.pooled.PooledPredicate;
 import com.android.server.pm.UserManagerInternal;
 import com.android.server.wm.LaunchParamsController.LaunchParams;
-import com.android.window.flags.Flags;
 
 import java.io.PrintWriter;
 import java.util.ArrayList;
@@ -1089,19 +1088,14 @@
         // Use launch-adjacent-flag-root if launching with launch-adjacent flag.
         if ((launchFlags & FLAG_ACTIVITY_LAUNCH_ADJACENT) != 0
                 && mLaunchAdjacentFlagRootTask != null) {
-            final Task launchAdjacentRootAdjacentTask;
-            if (Flags.allowMultipleAdjacentTaskFragments()) {
-                final Task[] tmpTask = new Task[1];
-                mLaunchAdjacentFlagRootTask.forOtherAdjacentTasks(task -> {
-                    // TODO(b/382208145): enable FLAG_ACTIVITY_LAUNCH_ADJACENT for 3+.
-                    // Find the first adjacent for now.
-                    tmpTask[0] = task;
-                    return true;
-                });
-                launchAdjacentRootAdjacentTask = tmpTask[0];
-            } else {
-                launchAdjacentRootAdjacentTask = mLaunchAdjacentFlagRootTask.getAdjacentTask();
-            }
+            final Task[] tmpTask = new Task[1];
+            mLaunchAdjacentFlagRootTask.forOtherAdjacentTasks(task -> {
+                // TODO(b/382208145): enable FLAG_ACTIVITY_LAUNCH_ADJACENT for 3+.
+                // Find the first adjacent for now.
+                tmpTask[0] = task;
+                return true;
+            });
+            final Task launchAdjacentRootAdjacentTask = tmpTask[0];
             if (sourceTask != null && (sourceTask == candidateTask
                     || sourceTask.topRunningActivity() == null)) {
                 // Do nothing when task that is getting opened is same as the source or when
@@ -1129,14 +1123,6 @@
                 if (launchRootTask == null || sourceTask == null) {
                     return launchRootTask;
                 }
-                if (!Flags.allowMultipleAdjacentTaskFragments()) {
-                    final Task adjacentRootTask = launchRootTask.getAdjacentTask();
-                    if (adjacentRootTask != null && (sourceTask == adjacentRootTask
-                            || sourceTask.isDescendantOf(adjacentRootTask))) {
-                        return adjacentRootTask;
-                    }
-                    return launchRootTask;
-                }
                 final Task[] adjacentRootTask = new Task[1];
                 launchRootTask.forOtherAdjacentTasks(task -> {
                     if (sourceTask == task || sourceTask.isDescendantOf(task)) {
@@ -1163,24 +1149,16 @@
                     return sourceTask.getCreatedByOrganizerTask();
                 }
                 // Check if the candidate is already positioned in the adjacent Task.
-                if (Flags.allowMultipleAdjacentTaskFragments()) {
-                    final Task[] adjacentRootTask = new Task[1];
-                    sourceTask.forOtherAdjacentTasks(task -> {
-                        if (candidateTask == task || candidateTask.isDescendantOf(task)) {
-                            adjacentRootTask[0] = task;
-                            return true;
-                        }
-                        return false;
-                    });
-                    if (adjacentRootTask[0] != null) {
-                        return adjacentRootTask[0];
+                final Task[] adjacentRootTask = new Task[1];
+                sourceTask.forOtherAdjacentTasks(task -> {
+                    if (candidateTask == task || candidateTask.isDescendantOf(task)) {
+                        adjacentRootTask[0] = task;
+                        return true;
                     }
-                } else {
-                    final Task adjacentTarget = taskWithAdjacent.getAdjacentTask();
-                    if (candidateTask == adjacentTarget
-                            || candidateTask.isDescendantOf(adjacentTarget)) {
-                        return adjacentTarget;
-                    }
+                    return false;
+                });
+                if (adjacentRootTask[0] != null) {
+                    return adjacentRootTask[0];
                 }
                 return sourceTask.getCreatedByOrganizerTask();
             }
diff --git a/services/core/java/com/android/server/wm/TaskFragment.java b/services/core/java/com/android/server/wm/TaskFragment.java
index 5183c6b..2dabb25 100644
--- a/services/core/java/com/android/server/wm/TaskFragment.java
+++ b/services/core/java/com/android/server/wm/TaskFragment.java
@@ -235,11 +235,6 @@
     /** This task fragment will be removed when the cleanup of its children are done. */
     private boolean mIsRemovalRequested;
 
-    /** @deprecated b/373709676 replace with {@link #mAdjacentTaskFragments} */
-    @Deprecated
-    @Nullable
-    private TaskFragment mAdjacentTaskFragment;
-
     /**
      * The TaskFragments that are adjacent to each other, including this TaskFragment.
      * All TaskFragments in this set share the same set instance.
@@ -455,22 +450,6 @@
         return service.mWindowOrganizerController.getTaskFragment(token);
     }
 
-    /** @deprecated b/373709676 replace with {@link #setAdjacentTaskFragments}. */
-    @Deprecated
-    void setAdjacentTaskFragment(@NonNull TaskFragment taskFragment) {
-        if (!Flags.allowMultipleAdjacentTaskFragments()) {
-            if (mAdjacentTaskFragment == taskFragment) {
-                return;
-            }
-            resetAdjacentTaskFragment();
-            mAdjacentTaskFragment = taskFragment;
-            taskFragment.setAdjacentTaskFragment(this);
-            return;
-        }
-
-        setAdjacentTaskFragments(new AdjacentSet(this, taskFragment));
-    }
-
     void setAdjacentTaskFragments(@NonNull AdjacentSet adjacentTaskFragments) {
         adjacentTaskFragments.setAsAdjacent();
     }
@@ -483,56 +462,18 @@
         return mCompanionTaskFragment;
     }
 
-    /** @deprecated b/373709676 replace with {@link #clearAdjacentTaskFragments()}. */
-    @Deprecated
-    private void resetAdjacentTaskFragment() {
-        if (Flags.allowMultipleAdjacentTaskFragments()) {
-            throw new IllegalStateException("resetAdjacentTaskFragment shouldn't be called when"
-                    + " allowMultipleAdjacentTaskFragments is enabled. Use either"
-                    + " #clearAdjacentTaskFragments or #removeFromAdjacentTaskFragments");
-        }
-        // Reset the adjacent TaskFragment if its adjacent TaskFragment is also this TaskFragment.
-        if (mAdjacentTaskFragment != null && mAdjacentTaskFragment.mAdjacentTaskFragment == this) {
-            mAdjacentTaskFragment.mAdjacentTaskFragment = null;
-            mAdjacentTaskFragment.mDelayLastActivityRemoval = false;
-        }
-        mAdjacentTaskFragment = null;
-        mDelayLastActivityRemoval = false;
-    }
-
     void clearAdjacentTaskFragments() {
-        if (!Flags.allowMultipleAdjacentTaskFragments()) {
-            resetAdjacentTaskFragment();
-            return;
-        }
-
         if (mAdjacentTaskFragments != null) {
             mAdjacentTaskFragments.clear();
         }
     }
 
     void removeFromAdjacentTaskFragments() {
-        if (!Flags.allowMultipleAdjacentTaskFragments()) {
-            resetAdjacentTaskFragment();
-            return;
-        }
-
         if (mAdjacentTaskFragments != null) {
             mAdjacentTaskFragments.remove(this);
         }
     }
 
-    /** @deprecated b/373709676 replace with {@link #getAdjacentTaskFragments()}. */
-    @Deprecated
-    @Nullable
-    TaskFragment getAdjacentTaskFragment() {
-        if (Flags.allowMultipleAdjacentTaskFragments()) {
-            throw new IllegalStateException("allowMultipleAdjacentTaskFragments is enabled. "
-                    + "Use #getAdjacentTaskFragments instead");
-        }
-        return mAdjacentTaskFragment;
-    }
-
     @Nullable
     AdjacentSet getAdjacentTaskFragments() {
         return mAdjacentTaskFragments;
@@ -561,16 +502,10 @@
     }
 
     boolean hasAdjacentTaskFragment() {
-        if (!Flags.allowMultipleAdjacentTaskFragments()) {
-            return mAdjacentTaskFragment != null;
-        }
         return mAdjacentTaskFragments != null;
     }
 
     boolean isAdjacentTo(@NonNull TaskFragment other) {
-        if (!Flags.allowMultipleAdjacentTaskFragments()) {
-            return mAdjacentTaskFragment == other;
-        }
         return other != this
                 && mAdjacentTaskFragments != null
                 && mAdjacentTaskFragments.contains(other);
@@ -1377,21 +1312,12 @@
                         if (taskFragment.isAdjacentTo(this)) {
                             continue;
                         }
-                        if (Flags.allowMultipleAdjacentTaskFragments()) {
-                            final boolean isOccluding = mTmpRect.intersect(taskFragment.getBounds())
-                                    || taskFragment.forOtherAdjacentTaskFragments(adjacentTf -> {
-                                        return mTmpRect.intersect(adjacentTf.getBounds());
-                                    });
-                            if (isOccluding) {
-                                return TASK_FRAGMENT_VISIBILITY_INVISIBLE;
-                            }
-                        } else {
-                            final TaskFragment adjacentTaskFragment =
-                                    taskFragment.mAdjacentTaskFragment;
-                            if (mTmpRect.intersect(taskFragment.getBounds())
-                                    || mTmpRect.intersect(adjacentTaskFragment.getBounds())) {
-                                return TASK_FRAGMENT_VISIBILITY_INVISIBLE;
-                            }
+                        final boolean isOccluding = mTmpRect.intersect(taskFragment.getBounds())
+                                || taskFragment.forOtherAdjacentTaskFragments(adjacentTf -> {
+                                    return mTmpRect.intersect(adjacentTf.getBounds());
+                                });
+                        if (isOccluding) {
+                            return TASK_FRAGMENT_VISIBILITY_INVISIBLE;
                         }
                     }
                 }
@@ -1427,37 +1353,22 @@
                 // 2. Adjacent TaskFragments do not overlap, so that if this TaskFragment is behind
                 // any translucent TaskFragment in the adjacent set, then this TaskFragment is
                 // visible behind translucent.
-                if (Flags.allowMultipleAdjacentTaskFragments()) {
-                    final boolean hasTraversedAdj = otherTaskFrag.forOtherAdjacentTaskFragments(
-                            adjacentTaskFragments::contains);
-                    if (hasTraversedAdj) {
-                        final boolean isTranslucent =
-                                isBehindTransparentTaskFragment(otherTaskFrag, starting)
-                                || otherTaskFrag.forOtherAdjacentTaskFragments(
-                                        (Predicate<TaskFragment>) tf ->
-                                                isBehindTransparentTaskFragment(tf, starting));
-                        if (isTranslucent) {
-                            // Can be visible behind a translucent adjacent TaskFragments.
-                            gotTranslucentFullscreen = true;
-                            gotTranslucentAdjacent = true;
-                            continue;
-                        }
-                        // Can not be visible behind adjacent TaskFragments.
-                        return TASK_FRAGMENT_VISIBILITY_INVISIBLE;
+                final boolean hasTraversedAdj = otherTaskFrag.forOtherAdjacentTaskFragments(
+                        adjacentTaskFragments::contains);
+                if (hasTraversedAdj) {
+                    final boolean isTranslucent =
+                            isBehindTransparentTaskFragment(otherTaskFrag, starting)
+                                    || otherTaskFrag.forOtherAdjacentTaskFragments(
+                                    (Predicate<TaskFragment>) tf ->
+                                            isBehindTransparentTaskFragment(tf, starting));
+                    if (isTranslucent) {
+                        // Can be visible behind a translucent adjacent TaskFragments.
+                        gotTranslucentFullscreen = true;
+                        gotTranslucentAdjacent = true;
+                        continue;
                     }
-                } else {
-                    if (adjacentTaskFragments.contains(otherTaskFrag.mAdjacentTaskFragment)) {
-                        if (isBehindTransparentTaskFragment(otherTaskFrag, starting)
-                                || isBehindTransparentTaskFragment(
-                                        otherTaskFrag.mAdjacentTaskFragment, starting)) {
-                            // Can be visible behind a translucent adjacent TaskFragments.
-                            gotTranslucentFullscreen = true;
-                            gotTranslucentAdjacent = true;
-                            continue;
-                        }
-                        // Can not be visible behind adjacent TaskFragments.
-                        return TASK_FRAGMENT_VISIBILITY_INVISIBLE;
-                    }
+                    // Can not be visible behind adjacent TaskFragments.
+                    return TASK_FRAGMENT_VISIBILITY_INVISIBLE;
                 }
                 adjacentTaskFragments.add(otherTaskFrag);
             }
@@ -3299,40 +3210,23 @@
 
         final ArrayList<WindowContainer> siblings = getParent().mChildren;
         final int zOrder = siblings.indexOf(this);
-
-        if (!Flags.allowMultipleAdjacentTaskFragments()) {
-            if (siblings.indexOf(getAdjacentTaskFragment()) < zOrder) {
-                // early return if this TF already has higher z-ordering.
-                return false;
-            }
-        } else {
-            final boolean hasAdjacentOnTop = forOtherAdjacentTaskFragments(
-                    tf -> siblings.indexOf(tf) > zOrder);
-            if (!hasAdjacentOnTop) {
-                // early return if this TF already has higher z-ordering.
-                return false;
-            }
+        final boolean hasAdjacentOnTop = forOtherAdjacentTaskFragments(
+                tf -> siblings.indexOf(tf) > zOrder);
+        if (!hasAdjacentOnTop) {
+            // early return if this TF already has higher z-ordering.
+            return false;
         }
 
         final ToBooleanFunction<WindowState> getDimBehindWindow =
                 (w) -> (w.mAttrs.flags & FLAG_DIM_BEHIND) != 0 && w.mActivityRecord != null
                         && w.mActivityRecord.isEmbedded() && (w.mActivityRecord.isVisibleRequested()
                         || w.mActivityRecord.isVisible());
-
-        if (!Flags.allowMultipleAdjacentTaskFragments()) {
-            final TaskFragment adjacentTf = getAdjacentTaskFragment();
-            if (adjacentTf.forAllWindows(getDimBehindWindow, true)) {
-                // early return if the adjacent Tf has a dimming window.
-                return false;
-            }
-        } else {
-            final boolean adjacentHasDimmingWindow = forOtherAdjacentTaskFragments(tf -> {
-                return tf.forAllWindows(getDimBehindWindow, true);
-            });
-            if (adjacentHasDimmingWindow) {
-                // early return if the adjacent Tf has a dimming window.
-                return false;
-            }
+        final boolean adjacentHasDimmingWindow = forOtherAdjacentTaskFragments(tf -> {
+            return tf.forAllWindows(getDimBehindWindow, true);
+        });
+        if (adjacentHasDimmingWindow) {
+            // early return if the adjacent Tf has a dimming window.
+            return false;
         }
 
         // boost if there's an Activity window that has FLAG_DIM_BEHIND flag.
@@ -3456,16 +3350,9 @@
             sb.append(" organizerProc=");
             sb.append(mTaskFragmentOrganizerProcessName);
         }
-        if (Flags.allowMultipleAdjacentTaskFragments()) {
-            if (mAdjacentTaskFragments != null) {
-                sb.append(" adjacent=");
-                sb.append(mAdjacentTaskFragments);
-            }
-        } else {
-            if (mAdjacentTaskFragment != null) {
-                sb.append(" adjacent=");
-                sb.append(mAdjacentTaskFragment);
-            }
+        if (mAdjacentTaskFragments != null) {
+            sb.append(" adjacent=");
+            sb.append(mAdjacentTaskFragments);
         }
         sb.append('}');
         return sb.toString();
@@ -3591,10 +3478,6 @@
         }
 
         AdjacentSet(@NonNull ArraySet<TaskFragment> taskFragments) {
-            if (!Flags.allowMultipleAdjacentTaskFragments()) {
-                throw new IllegalStateException("allowMultipleAdjacentTaskFragments must be"
-                        + " enabled to set more than two TaskFragments adjacent to each other.");
-            }
             final int size = taskFragments.size();
             if (size < 2) {
                 throw new IllegalArgumentException("Adjacent TaskFragments must contain at least"
diff --git a/services/core/java/com/android/server/wm/Transition.java b/services/core/java/com/android/server/wm/Transition.java
index c78cdaa..803c21c 100644
--- a/services/core/java/com/android/server/wm/Transition.java
+++ b/services/core/java/com/android/server/wm/Transition.java
@@ -2589,9 +2589,6 @@
         }
         // When the TaskFragment has an adjacent TaskFragment, sibling behind them should be
         // hidden unless any of them are translucent.
-        if (!Flags.allowMultipleAdjacentTaskFragments()) {
-            return taskFragment.getAdjacentTaskFragment().isTranslucentForTransition();
-        }
         return taskFragment.forOtherAdjacentTaskFragments(TaskFragment::isTranslucentForTransition);
     }
 
diff --git a/services/core/java/com/android/server/wm/WallpaperController.java b/services/core/java/com/android/server/wm/WallpaperController.java
index 3a4d9d2..e1553cd 100644
--- a/services/core/java/com/android/server/wm/WallpaperController.java
+++ b/services/core/java/com/android/server/wm/WallpaperController.java
@@ -57,7 +57,8 @@
 import com.android.internal.annotations.VisibleForTesting;
 import com.android.internal.protolog.ProtoLog;
 import com.android.internal.util.ToBooleanFunction;
-import com.android.server.wallpaper.WallpaperCropper.WallpaperCropUtils;
+import com.android.server.wallpaper.WallpaperCropper;
+import com.android.server.wallpaper.WallpaperDefaultDisplayInfo;
 
 import java.io.PrintWriter;
 import java.util.ArrayList;
@@ -71,7 +72,6 @@
 class WallpaperController {
     private static final String TAG = TAG_WITH_CLASS_NAME ? "WallpaperController" : TAG_WM;
     private WindowManagerService mService;
-    private WallpaperCropUtils mWallpaperCropUtils = null;
     private DisplayContent mDisplayContent;
 
     // Larger index has higher z-order.
@@ -116,6 +116,10 @@
 
     private boolean mShouldOffsetWallpaperCenter;
 
+    // This is for WallpaperCropper, which has cropping logic for the default display only.
+    // TODO(b/400685784) make the WallpaperCropper operate on every display independently
+    private final WallpaperDefaultDisplayInfo mDefaultDisplayInfo;
+
     private final ToBooleanFunction<WindowState> mFindWallpaperTargetFunction = w -> {
         final ActivityRecord ar = w.mActivityRecord;
         // The animating window can still be visible on screen if it is in transition, so we
@@ -198,12 +202,14 @@
     WallpaperController(WindowManagerService service, DisplayContent displayContent) {
         mService = service;
         mDisplayContent = displayContent;
+        WindowManager windowManager = service.mContext.getSystemService(WindowManager.class);
         Resources resources = service.mContext.getResources();
         mMinWallpaperScale =
                 resources.getFloat(com.android.internal.R.dimen.config_wallpaperMinScale);
         mMaxWallpaperScale = resources.getFloat(R.dimen.config_wallpaperMaxScale);
         mShouldOffsetWallpaperCenter = resources.getBoolean(
                 com.android.internal.R.bool.config_offsetWallpaperToCenterOfLargestDisplay);
+        mDefaultDisplayInfo = new WallpaperDefaultDisplayInfo(windowManager, resources);
     }
 
     void resetLargestDisplay(Display display) {
@@ -246,10 +252,6 @@
         return largestDisplaySize;
     }
 
-    void setWallpaperCropUtils(WallpaperCropUtils wallpaperCropUtils) {
-        mWallpaperCropUtils = wallpaperCropUtils;
-    }
-
     WindowState getWallpaperTarget() {
         return mWallpaperTarget;
     }
@@ -352,16 +354,12 @@
         int offsetY;
 
         if (multiCrop()) {
-            if (mWallpaperCropUtils == null) {
-                Slog.e(TAG, "Update wallpaper offsets before the system is ready. Aborting");
-                return false;
-            }
             Point bitmapSize = new Point(
                     wallpaperWin.mRequestedWidth, wallpaperWin.mRequestedHeight);
             SparseArray<Rect> cropHints = token.getCropHints();
             wallpaperFrame = bitmapSize.x <= 0 || bitmapSize.y <= 0 ? wallpaperWin.getFrame()
-                    : mWallpaperCropUtils.getCrop(screenSize, bitmapSize, cropHints,
-                            wallpaperWin.isRtl());
+                    : WallpaperCropper.getCrop(screenSize, mDefaultDisplayInfo, bitmapSize,
+                            cropHints, wallpaperWin.isRtl());
             int frameWidth = wallpaperFrame.width();
             int frameHeight = wallpaperFrame.height();
             float frameRatio = (float) frameWidth / frameHeight;
diff --git a/services/core/java/com/android/server/wm/WindowContainer.java b/services/core/java/com/android/server/wm/WindowContainer.java
index 466ed78..772a7fd 100644
--- a/services/core/java/com/android/server/wm/WindowContainer.java
+++ b/services/core/java/com/android/server/wm/WindowContainer.java
@@ -2006,11 +2006,16 @@
         return getActivity(r -> !r.finishing, true /* traverseTopToBottom */);
     }
 
-    ActivityRecord getTopMostVisibleFreeformActivity() {
+    ActivityRecord getTopMostFreeformActivity() {
         return getActivity(r -> r.isVisibleRequested() && r.inFreeformWindowingMode(),
                 true /* traverseTopToBottom */);
     }
 
+    ActivityRecord getTopMostVisibleFreeformActivity() {
+        return getActivity(r -> r.isVisible() && r.inFreeformWindowingMode(),
+                true /* traverseTopToBottom */);
+    }
+
     ActivityRecord getTopActivity(boolean includeFinishing, boolean includeOverlays) {
         // Break down into 4 calls to avoid object creation due to capturing input params.
         if (includeFinishing) {
diff --git a/services/core/java/com/android/server/wm/WindowManagerInternal.java b/services/core/java/com/android/server/wm/WindowManagerInternal.java
index 4b5a3a0..5f2a2ad 100644
--- a/services/core/java/com/android/server/wm/WindowManagerInternal.java
+++ b/services/core/java/com/android/server/wm/WindowManagerInternal.java
@@ -54,7 +54,6 @@
 import com.android.internal.policy.KeyInterceptionInfo;
 import com.android.server.input.InputManagerService;
 import com.android.server.policy.WindowManagerPolicy;
-import com.android.server.wallpaper.WallpaperCropper.WallpaperCropUtils;
 import com.android.server.wm.SensitiveContentPackages.PackageInfo;
 
 import java.lang.annotation.Retention;
@@ -772,12 +771,6 @@
     public abstract void setWallpaperCropHints(IBinder windowToken, SparseArray<Rect> cropHints);
 
     /**
-     * Transmits the {@link WallpaperCropUtils} instance to {@link WallpaperController}.
-     * {@link WallpaperCropUtils} contains the helpers to properly position the wallpaper.
-     */
-    public abstract void setWallpaperCropUtils(WallpaperCropUtils wallpaperCropUtils);
-
-    /**
      * Returns {@code true} if a Window owned by {@code uid} has focus.
      */
     public abstract boolean isUidFocused(int uid);
diff --git a/services/core/java/com/android/server/wm/WindowManagerService.java b/services/core/java/com/android/server/wm/WindowManagerService.java
index 9fc0339..c078d67b 100644
--- a/services/core/java/com/android/server/wm/WindowManagerService.java
+++ b/services/core/java/com/android/server/wm/WindowManagerService.java
@@ -356,7 +356,6 @@
 import com.android.server.policy.WindowManagerPolicy.ScreenOffListener;
 import com.android.server.power.ShutdownThread;
 import com.android.server.utils.PriorityDump;
-import com.android.server.wallpaper.WallpaperCropper.WallpaperCropUtils;
 import com.android.window.flags.Flags;
 
 import dalvik.annotation.optimization.NeverCompile;
@@ -8100,12 +8099,6 @@
         }
 
         @Override
-        public void setWallpaperCropUtils(WallpaperCropUtils wallpaperCropUtils) {
-            mRoot.getDisplayContent(DEFAULT_DISPLAY).mWallpaperController
-                    .setWallpaperCropUtils(wallpaperCropUtils);
-        }
-
-        @Override
         public boolean isUidFocused(int uid) {
             synchronized (mGlobalLock) {
                 for (int i = mRoot.getChildCount() - 1; i >= 0; i--) {
@@ -9374,23 +9367,6 @@
             return focusedActivity;
         }
 
-        if (!Flags.allowMultipleAdjacentTaskFragments()) {
-            final TaskFragment adjacentTaskFragment = taskFragment.getAdjacentTaskFragment();
-            final ActivityRecord adjacentTopActivity = adjacentTaskFragment.topRunningActivity();
-            if (adjacentTopActivity == null) {
-                // Return if no adjacent activity.
-                return focusedActivity;
-            }
-
-            if (adjacentTopActivity.getLastWindowCreateTime()
-                    < focusedActivity.getLastWindowCreateTime()) {
-                // Return if the current focus activity has more recently active window.
-                return focusedActivity;
-            }
-
-            return adjacentTopActivity;
-        }
-
         // Find the adjacent activity with more recently active window.
         final ActivityRecord[] mostRecentActiveActivity = { focusedActivity };
         final long[] mostRecentActiveTime = { focusedActivity.getLastWindowCreateTime() };
@@ -9461,20 +9437,15 @@
             // No adjacent window.
             return false;
         }
-        final TaskFragment adjacentFragment;
-        if (Flags.allowMultipleAdjacentTaskFragments()) {
-            if (fromFragment.getAdjacentTaskFragments().size() > 2) {
-                throw new IllegalStateException("Not yet support 3+ adjacent for non-Task TFs");
-            }
-            final TaskFragment[] tmpAdjacent = new TaskFragment[1];
-            fromFragment.forOtherAdjacentTaskFragments(adjacentTF -> {
-                tmpAdjacent[0] = adjacentTF;
-                return true;
-            });
-            adjacentFragment = tmpAdjacent[0];
-        } else {
-            adjacentFragment = fromFragment.getAdjacentTaskFragment();
+        if (fromFragment.getAdjacentTaskFragments().size() > 2) {
+            throw new IllegalStateException("Not yet support 3+ adjacent for non-Task TFs");
         }
+        final TaskFragment[] tmpAdjacent = new TaskFragment[1];
+        fromFragment.forOtherAdjacentTaskFragments(adjacentTF -> {
+            tmpAdjacent[0] = adjacentTF;
+            return true;
+        });
+        final TaskFragment adjacentFragment = tmpAdjacent[0];
         if (adjacentFragment.isIsolatedNav()) {
             // Don't move the focus if the adjacent TF is isolated navigation.
             return false;
diff --git a/services/core/java/com/android/server/wm/WindowOrganizerController.java b/services/core/java/com/android/server/wm/WindowOrganizerController.java
index ea1f35a..a012ec1 100644
--- a/services/core/java/com/android/server/wm/WindowOrganizerController.java
+++ b/services/core/java/com/android/server/wm/WindowOrganizerController.java
@@ -1671,13 +1671,9 @@
                 }
                 if (!taskFragment.isAdjacentTo(secondaryTaskFragment)) {
                     // Only have lifecycle effect if the adjacent changed.
-                    if (Flags.allowMultipleAdjacentTaskFragments()) {
-                        // Activity Embedding only set two TFs adjacent.
-                        taskFragment.setAdjacentTaskFragments(
-                                new TaskFragment.AdjacentSet(taskFragment, secondaryTaskFragment));
-                    } else {
-                        taskFragment.setAdjacentTaskFragment(secondaryTaskFragment);
-                    }
+                    // Activity Embedding only set two TFs adjacent.
+                    taskFragment.setAdjacentTaskFragments(
+                            new TaskFragment.AdjacentSet(taskFragment, secondaryTaskFragment));
                     effects |= TRANSACT_EFFECTS_LIFECYCLE;
                 }
 
@@ -2220,30 +2216,6 @@
     }
 
     private int setAdjacentRootsHierarchyOp(WindowContainerTransaction.HierarchyOp hop) {
-        if (!Flags.allowMultipleAdjacentTaskFragments()) {
-            final WindowContainer wc1 = WindowContainer.fromBinder(hop.getContainer());
-            if (wc1 == null || !wc1.isAttached()) {
-                Slog.e(TAG, "Attempt to operate on unknown or detached container: " + wc1);
-                return TRANSACT_EFFECTS_NONE;
-            }
-            final TaskFragment root1 = wc1.asTaskFragment();
-            final WindowContainer wc2 = WindowContainer.fromBinder(hop.getAdjacentRoot());
-            if (wc2 == null || !wc2.isAttached()) {
-                Slog.e(TAG, "Attempt to operate on unknown or detached container: " + wc2);
-                return TRANSACT_EFFECTS_NONE;
-            }
-            final TaskFragment root2 = wc2.asTaskFragment();
-            if (!root1.mCreatedByOrganizer || !root2.mCreatedByOrganizer) {
-                throw new IllegalArgumentException("setAdjacentRootsHierarchyOp: Not created by"
-                        + " organizer root1=" + root1 + " root2=" + root2);
-            }
-            if (root1.isAdjacentTo(root2)) {
-                return TRANSACT_EFFECTS_NONE;
-            }
-            root1.setAdjacentTaskFragment(root2);
-            return TRANSACT_EFFECTS_LIFECYCLE;
-        }
-
         final IBinder[] containers = hop.getContainers();
         final ArraySet<TaskFragment> adjacentRoots = new ArraySet<>();
         for (IBinder container : containers) {
diff --git a/services/devicepolicy/java/com/android/server/devicepolicy/DevicePolicyManagerService.java b/services/devicepolicy/java/com/android/server/devicepolicy/DevicePolicyManagerService.java
index 0ad976c..51ed6bb 100644
--- a/services/devicepolicy/java/com/android/server/devicepolicy/DevicePolicyManagerService.java
+++ b/services/devicepolicy/java/com/android/server/devicepolicy/DevicePolicyManagerService.java
@@ -3806,9 +3806,10 @@
                 // Update user switcher message to activity manager.
                 ActivityManagerInternal activityManagerInternal =
                         mInjector.getActivityManagerInternal();
-                activityManagerInternal.setSwitchingFromSystemUserMessage(
+                int deviceOwnerUserId = UserHandle.getUserId(deviceOwner.getUid());
+                activityManagerInternal.setSwitchingFromUserMessage(deviceOwnerUserId,
                         deviceOwner.startUserSessionMessage);
-                activityManagerInternal.setSwitchingToSystemUserMessage(
+                activityManagerInternal.setSwitchingToUserMessage(deviceOwnerUserId,
                         deviceOwner.endUserSessionMessage);
             }
 
@@ -19716,7 +19717,7 @@
         }
 
         mInjector.getActivityManagerInternal()
-                .setSwitchingFromSystemUserMessage(startUserSessionMessageString);
+                .setSwitchingFromUserMessage(caller.getUserId(), startUserSessionMessageString);
     }
 
     @Override
@@ -19741,7 +19742,7 @@
         }
 
         mInjector.getActivityManagerInternal()
-                .setSwitchingToSystemUserMessage(endUserSessionMessageString);
+                .setSwitchingToUserMessage(caller.getUserId(), endUserSessionMessageString);
     }
 
     @Override
diff --git a/services/tests/dreamservicetests/src/com/android/server/dreams/TestDreamEnvironment.java b/services/tests/dreamservicetests/src/com/android/server/dreams/TestDreamEnvironment.java
index 586ff52..7c239ef 100644
--- a/services/tests/dreamservicetests/src/com/android/server/dreams/TestDreamEnvironment.java
+++ b/services/tests/dreamservicetests/src/com/android/server/dreams/TestDreamEnvironment.java
@@ -328,7 +328,6 @@
                 case DREAM_STATE_STARTED -> startDream();
                 case DREAM_STATE_WOKEN -> wakeDream();
             }
-            mTestableLooper.processAllMessages();
         } while (mCurrentDreamState < state);
 
         return true;
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 49c37f1..241ffdc 100644
--- a/services/tests/mockingservicestests/src/com/android/server/wallpaper/WallpaperCropperTest.java
+++ b/services/tests/mockingservicestests/src/com/android/server/wallpaper/WallpaperCropperTest.java
@@ -36,24 +36,29 @@
 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;
 
+import android.content.res.Resources;
 import android.graphics.Point;
 import android.graphics.Rect;
 import android.platform.test.annotations.Presubmit;
 import android.platform.test.annotations.RequiresFlagsEnabled;
+import android.util.ArraySet;
 import android.util.Log;
 import android.util.SparseArray;
 import android.view.Display;
 import android.view.DisplayInfo;
+import android.view.WindowInsets;
+import android.view.WindowManager;
+import android.view.WindowMetrics;
 
 import androidx.test.runner.AndroidJUnit4;
 
 import com.android.dx.mockito.inline.extended.ExtendedMockito;
 import com.android.dx.mockito.inline.extended.StaticMockitoSession;
+import com.android.internal.R;
 
 import org.junit.AfterClass;
 import org.junit.Before;
@@ -70,10 +75,11 @@
 import java.io.IOException;
 import java.util.Comparator;
 import java.util.List;
+import java.util.Set;
 
 /**
  * Unit tests for the most important helpers of {@link WallpaperCropper}, in particular
- * {@link WallpaperCropper#getCrop(Point, Point, SparseArray, boolean)}.
+ * {@link WallpaperCropper#getCrop(Point, WallpaperDefaultDisplayInfo, Point, SparseArray, boolean)}.
  */
 @Presubmit
 @RunWith(AndroidJUnit4.class)
@@ -83,6 +89,12 @@
 
     @Mock
     private WallpaperDisplayHelper mWallpaperDisplayHelper;
+
+    @Mock
+    private WindowManager mWindowManager;
+
+    @Mock
+    private Resources mResources;
     private WallpaperCropper mWallpaperCropper;
 
     private static final Point PORTRAIT_ONE = new Point(500, 800);
@@ -175,14 +187,21 @@
         return tempDir;
     }
 
-    private void setUpWithDisplays(List<Point> displaySizes) {
+    private WallpaperDefaultDisplayInfo setUpWithDisplays(List<Point> displaySizes) {
         mDisplaySizes = new SparseArray<>();
         displaySizes.forEach(size -> {
             mDisplaySizes.put(getOrientation(size), size);
             Point rotated = new Point(size.y, size.x);
             mDisplaySizes.put(getOrientation(rotated), rotated);
         });
+        Set<WindowMetrics> windowMetrics = new ArraySet<>();
+        for (Point displaySize : displaySizes) {
+            windowMetrics.add(
+                    new WindowMetrics(new Rect(0, 0, displaySize.x, displaySize.y),
+                            new WindowInsets.Builder().build()));
+        }
         when(mWallpaperDisplayHelper.getDefaultDisplaySizes()).thenReturn(mDisplaySizes);
+        when(mWindowManager.getPossibleMaximumWindowMetrics(anyInt())).thenReturn(windowMetrics);
         if (displaySizes.size() == 2) {
             Point largestDisplay = displaySizes.stream().max(
                     Comparator.comparingInt(p -> p.x * p.y)).get();
@@ -192,11 +211,16 @@
             mFolded = getOrientation(smallestDisplay);
             mUnfoldedRotated = getRotatedOrientation(mUnfolded);
             mFoldedRotated = getRotatedOrientation(mFolded);
+            // foldable
+            doReturn(new int[]{0}).when(mResources).getIntArray(R.array.config_foldedDeviceStates);
+        } else {
+            // no foldable
+            doReturn(new int[]{}).when(mResources).getIntArray(R.array.config_foldedDeviceStates);
         }
-        doAnswer(invocation -> getFoldedOrientation(invocation.getArgument(0)))
-                .when(mWallpaperDisplayHelper).getFoldedOrientation(anyInt());
-        doAnswer(invocation -> getUnfoldedOrientation(invocation.getArgument(0)))
-                .when(mWallpaperDisplayHelper).getUnfoldedOrientation(anyInt());
+        WallpaperDefaultDisplayInfo defaultDisplayInfo = new WallpaperDefaultDisplayInfo(
+                mWindowManager, mResources);
+        when(mWallpaperDisplayHelper.getDefaultDisplayInfo()).thenReturn(defaultDisplayInfo);
+        return defaultDisplayInfo;
     }
 
     private int getFoldedOrientation(int orientation) {
@@ -435,7 +459,7 @@
      */
     @Test
     public void testGetCrop_noSuggestedCrops() {
-        setUpWithDisplays(STANDARD_DISPLAY);
+        WallpaperDefaultDisplayInfo defaultDisplayInfo = setUpWithDisplays(STANDARD_DISPLAY);
         Point bitmapSize = new Point(800, 1000);
         Rect bitmapRect = new Rect(0, 0, bitmapSize.x, bitmapSize.y);
         SparseArray<Rect> suggestedCrops = new SparseArray<>();
@@ -455,8 +479,9 @@
             for (boolean rtl : List.of(false, true)) {
                 Rect expectedCrop = rtl ? rightOf(bitmapRect, expectedCropSize)
                         : leftOf(bitmapRect, expectedCropSize);
-                assertThat(mWallpaperCropper.getCrop(
-                        displaySize, bitmapSize, suggestedCrops, rtl))
+                assertThat(
+                        WallpaperCropper.getCrop(
+                                displaySize, defaultDisplayInfo, bitmapSize, suggestedCrops, rtl))
                         .isEqualTo(expectedCrop);
             }
         }
@@ -469,7 +494,7 @@
      */
     @Test
     public void testGetCrop_hasSuggestedCrop() {
-        setUpWithDisplays(STANDARD_DISPLAY);
+        WallpaperDefaultDisplayInfo defaultDisplayInfo = setUpWithDisplays(STANDARD_DISPLAY);
         Point bitmapSize = new Point(800, 1000);
         SparseArray<Rect> suggestedCrops = new SparseArray<>();
         suggestedCrops.put(ORIENTATION_PORTRAIT, new Rect(0, 0, 400, 800));
@@ -479,11 +504,13 @@
         }
 
         for (boolean rtl : List.of(false, true)) {
-            assertThat(mWallpaperCropper.getCrop(
-                    new Point(300, 800), bitmapSize, suggestedCrops, rtl))
+            assertThat(
+                    WallpaperCropper.getCrop(new Point(300, 800), defaultDisplayInfo, bitmapSize,
+                            suggestedCrops, rtl))
                     .isEqualTo(suggestedCrops.get(ORIENTATION_PORTRAIT));
-            assertThat(mWallpaperCropper.getCrop(
-                    new Point(500, 800), bitmapSize, suggestedCrops, rtl))
+            assertThat(
+                    WallpaperCropper.getCrop(new Point(500, 800), defaultDisplayInfo, bitmapSize,
+                            suggestedCrops, rtl))
                     .isEqualTo(new Rect(0, 0, 500, 800));
         }
     }
@@ -499,7 +526,7 @@
      */
     @Test
     public void testGetCrop_hasRotatedSuggestedCrop() {
-        setUpWithDisplays(STANDARD_DISPLAY);
+        WallpaperDefaultDisplayInfo defaultDisplayInfo = setUpWithDisplays(STANDARD_DISPLAY);
         Point bitmapSize = new Point(2000, 1800);
         Rect bitmapRect = new Rect(0, 0, bitmapSize.x, bitmapSize.y);
         SparseArray<Rect> suggestedCrops = new SparseArray<>();
@@ -510,12 +537,14 @@
         suggestedCrops.put(ORIENTATION_PORTRAIT, centerOf(bitmapRect, portrait));
         suggestedCrops.put(ORIENTATION_SQUARE_LANDSCAPE, centerOf(bitmapRect, squareLandscape));
         for (boolean rtl : List.of(false, true)) {
-            assertThat(mWallpaperCropper.getCrop(
-                    landscape, bitmapSize, suggestedCrops, rtl))
+            assertThat(
+                    WallpaperCropper.getCrop(landscape, defaultDisplayInfo, bitmapSize,
+                            suggestedCrops, rtl))
                     .isEqualTo(centerOf(bitmapRect, landscape));
 
-            assertThat(mWallpaperCropper.getCrop(
-                    squarePortrait, bitmapSize, suggestedCrops, rtl))
+            assertThat(
+                    WallpaperCropper.getCrop(squarePortrait, defaultDisplayInfo, bitmapSize,
+                            suggestedCrops, rtl))
                     .isEqualTo(centerOf(bitmapRect, squarePortrait));
         }
     }
@@ -532,7 +561,7 @@
     @Test
     public void testGetCrop_hasUnfoldedSuggestedCrop() {
         for (List<Point> displaySizes : ALL_FOLDABLE_DISPLAYS) {
-            setUpWithDisplays(displaySizes);
+            WallpaperDefaultDisplayInfo defaultDisplayInfo = setUpWithDisplays(displaySizes);
             Point bitmapSize = new Point(2000, 2400);
             Rect bitmapRect = new Rect(0, 0, bitmapSize.x, bitmapSize.y);
 
@@ -569,8 +598,9 @@
                         expectedCrop.right = Math.min(
                                 unfoldedCrop.right, unfoldedCrop.right + maxParallax);
                     }
-                    assertThat(mWallpaperCropper.getCrop(
-                            foldedDisplay, bitmapSize, suggestedCrops, rtl))
+                    assertThat(
+                            WallpaperCropper.getCrop(foldedDisplay, defaultDisplayInfo, bitmapSize,
+                                    suggestedCrops, rtl))
                             .isEqualTo(expectedCrop);
                 }
             }
@@ -588,7 +618,7 @@
     @Test
     public void testGetCrop_hasFoldedSuggestedCrop() {
         for (List<Point> displaySizes : ALL_FOLDABLE_DISPLAYS) {
-            setUpWithDisplays(displaySizes);
+            WallpaperDefaultDisplayInfo defaultDisplayInfo = setUpWithDisplays(displaySizes);
             Point bitmapSize = new Point(2000, 2000);
             Rect bitmapRect = new Rect(0, 0, 2000, 2000);
 
@@ -610,12 +640,14 @@
             Point unfoldedDisplayTwo = mDisplaySizes.get(unfoldedTwo);
 
             for (boolean rtl : List.of(false, true)) {
-                assertThat(centerOf(mWallpaperCropper.getCrop(
-                        unfoldedDisplayOne, bitmapSize, suggestedCrops, rtl), foldedDisplayOne))
+                assertThat(centerOf(
+                        WallpaperCropper.getCrop(unfoldedDisplayOne, defaultDisplayInfo, bitmapSize,
+                                suggestedCrops, rtl), foldedDisplayOne))
                         .isEqualTo(foldedCropOne);
 
-                assertThat(centerOf(mWallpaperCropper.getCrop(
-                        unfoldedDisplayTwo, bitmapSize, suggestedCrops, rtl), foldedDisplayTwo))
+                assertThat(centerOf(
+                        WallpaperCropper.getCrop(unfoldedDisplayTwo, defaultDisplayInfo, bitmapSize,
+                                suggestedCrops, rtl), foldedDisplayTwo))
                         .isEqualTo(foldedCropTwo);
             }
         }
@@ -633,7 +665,7 @@
     @Test
     public void testGetCrop_hasRotatedUnfoldedSuggestedCrop() {
         for (List<Point> displaySizes : ALL_FOLDABLE_DISPLAYS) {
-            setUpWithDisplays(displaySizes);
+            WallpaperDefaultDisplayInfo defaultDisplayInfo = setUpWithDisplays(displaySizes);
             Point bitmapSize = new Point(2000, 2000);
             Rect bitmapRect = new Rect(0, 0, 2000, 2000);
             Point largestDisplay = displaySizes.stream().max(
@@ -650,8 +682,9 @@
                 Point rotatedFoldedDisplay = mDisplaySizes.get(rotatedFolded);
 
                 for (boolean rtl : List.of(false, true)) {
-                    assertThat(mWallpaperCropper.getCrop(
-                            rotatedFoldedDisplay, bitmapSize, suggestedCrops, rtl))
+                    assertThat(
+                            WallpaperCropper.getCrop(rotatedFoldedDisplay, defaultDisplayInfo,
+                                    bitmapSize, suggestedCrops, rtl))
                             .isEqualTo(centerOf(rotatedUnfoldedCrop, rotatedFoldedDisplay));
                 }
             }
@@ -670,7 +703,7 @@
     @Test
     public void testGetCrop_hasRotatedFoldedSuggestedCrop() {
         for (List<Point> displaySizes : ALL_FOLDABLE_DISPLAYS) {
-            setUpWithDisplays(displaySizes);
+            WallpaperDefaultDisplayInfo defaultDisplayInfo = setUpWithDisplays(displaySizes);
             Point bitmapSize = new Point(2000, 2000);
             Rect bitmapRect = new Rect(0, 0, 2000, 2000);
 
@@ -689,8 +722,8 @@
                 Point rotatedUnfoldedDisplay = mDisplaySizes.get(rotatedUnfolded);
 
                 for (boolean rtl : List.of(false, true)) {
-                    Rect rotatedUnfoldedCrop = mWallpaperCropper.getCrop(
-                            rotatedUnfoldedDisplay, bitmapSize, suggestedCrops, rtl);
+                    Rect rotatedUnfoldedCrop = WallpaperCropper.getCrop(rotatedUnfoldedDisplay,
+                            defaultDisplayInfo, bitmapSize, suggestedCrops, rtl);
                     assertThat(centerOf(rotatedUnfoldedCrop, rotatedFoldedDisplay))
                             .isEqualTo(rotatedFoldedCrop);
                 }
diff --git a/services/tests/mockingservicestests/src/com/android/server/wallpaper/WallpaperDefaultDisplayInfoTest.java b/services/tests/mockingservicestests/src/com/android/server/wallpaper/WallpaperDefaultDisplayInfoTest.java
new file mode 100644
index 0000000..312db91
--- /dev/null
+++ b/services/tests/mockingservicestests/src/com/android/server/wallpaper/WallpaperDefaultDisplayInfoTest.java
@@ -0,0 +1,340 @@
+/*
+ * 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.wallpaper;
+
+import static android.app.WallpaperManager.ORIENTATION_LANDSCAPE;
+import static android.app.WallpaperManager.ORIENTATION_PORTRAIT;
+import static android.app.WallpaperManager.ORIENTATION_SQUARE_LANDSCAPE;
+import static android.app.WallpaperManager.ORIENTATION_SQUARE_PORTRAIT;
+import static android.app.WallpaperManager.ORIENTATION_UNKNOWN;
+
+import static com.google.common.truth.Truth.assertThat;
+
+import static org.mockito.ArgumentMatchers.anyInt;
+import static org.mockito.ArgumentMatchers.eq;
+import static org.mockito.Mockito.doReturn;
+import static org.mockito.Mockito.when;
+import static org.mockito.MockitoAnnotations.initMocks;
+
+import android.content.res.Resources;
+import android.graphics.Point;
+import android.graphics.Rect;
+import android.platform.test.annotations.Presubmit;
+import android.util.SparseArray;
+import android.view.WindowInsets;
+import android.view.WindowManager;
+import android.view.WindowMetrics;
+
+import androidx.test.runner.AndroidJUnit4;
+
+import com.android.internal.R;
+import com.android.server.wallpaper.WallpaperDefaultDisplayInfo.FoldableOrientations;
+
+import org.junit.Before;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+import org.mockito.Mock;
+
+import java.util.Set;
+
+/** Unit tests for {@link WallpaperDefaultDisplayInfo}. */
+@Presubmit
+@RunWith(AndroidJUnit4.class)
+public class WallpaperDefaultDisplayInfoTest {
+    @Mock
+    private WindowManager mWindowManager;
+
+    @Mock
+    private Resources mResources;
+
+    @Before
+    public void setUp() {
+        initMocks(this);
+    }
+
+    @Test
+    public void defaultDisplayInfo_foldable_shouldHaveExpectedContent() {
+        doReturn(new int[]{0}).when(mResources).getIntArray(eq(R.array.config_foldedDeviceStates));
+        Rect innerDisplayBounds = new Rect(0, 0, 2076, 2152);
+        Rect outerDisplayBounds = new Rect(0, 0, 1080, 2424);
+        WindowMetrics innerDisplayMetrics =
+                new WindowMetrics(innerDisplayBounds, new WindowInsets.Builder().build(),
+                        /* density= */ 2.4375f);
+        WindowMetrics outerDisplayMetrics =
+                new WindowMetrics(outerDisplayBounds, new WindowInsets.Builder().build(),
+                        /* density= */ 2.4375f);
+        when(mWindowManager.getPossibleMaximumWindowMetrics(anyInt()))
+                .thenReturn(Set.of(innerDisplayMetrics, outerDisplayMetrics));
+
+        WallpaperDefaultDisplayInfo defaultDisplayInfo = new WallpaperDefaultDisplayInfo(
+                mWindowManager, mResources);
+
+        SparseArray<Point> displaySizes = new SparseArray<>();
+        displaySizes.put(ORIENTATION_PORTRAIT, new Point(1080, 2424));
+        displaySizes.put(ORIENTATION_LANDSCAPE, new Point(2424, 1080));
+        displaySizes.put(ORIENTATION_SQUARE_PORTRAIT, new Point(2076, 2152));
+        displaySizes.put(ORIENTATION_SQUARE_LANDSCAPE, new Point(2152, 2076));
+        assertThat(defaultDisplayInfo.defaultDisplaySizes.contentEquals(displaySizes)).isTrue();
+        assertThat(defaultDisplayInfo.isFoldable).isTrue();
+        assertThat(defaultDisplayInfo.isLargeScreen).isFalse();
+        assertThat(defaultDisplayInfo.foldableOrientations).containsExactly(
+                new FoldableOrientations(
+                        /* foldedOrientation= */ ORIENTATION_PORTRAIT,
+                        /* unfoldedOrientation= */ ORIENTATION_SQUARE_PORTRAIT),
+                new FoldableOrientations(
+                        /* foldedOrientation= */ ORIENTATION_LANDSCAPE,
+                        /* unfoldedOrientation= */ ORIENTATION_SQUARE_LANDSCAPE));
+    }
+
+    @Test
+    public void defaultDisplayInfo_tablet_shouldHaveExpectedContent() {
+        doReturn(new int[]{}).when(mResources).getIntArray(eq(R.array.config_foldedDeviceStates));
+        Rect displayBounds = new Rect(0, 0, 2560, 1600);
+        WindowMetrics displayMetrics =
+                new WindowMetrics(displayBounds, new WindowInsets.Builder().build(),
+                        /* density= */ 2f);
+        when(mWindowManager.getPossibleMaximumWindowMetrics(anyInt()))
+                .thenReturn(Set.of(displayMetrics));
+
+        WallpaperDefaultDisplayInfo defaultDisplayInfo = new WallpaperDefaultDisplayInfo(
+                mWindowManager, mResources);
+
+        SparseArray<Point> displaySizes = new SparseArray<>();
+        displaySizes.put(ORIENTATION_PORTRAIT, new Point(1600, 2560));
+        displaySizes.put(ORIENTATION_LANDSCAPE, new Point(2560, 1600));
+        assertThat(defaultDisplayInfo.defaultDisplaySizes.contentEquals(displaySizes)).isTrue();
+        assertThat(defaultDisplayInfo.isFoldable).isFalse();
+        assertThat(defaultDisplayInfo.isLargeScreen).isTrue();
+        assertThat(defaultDisplayInfo.foldableOrientations).isEmpty();
+    }
+
+    @Test
+    public void defaultDisplayInfo_phone_shouldHaveExpectedContent() {
+        doReturn(new int[]{}).when(mResources).getIntArray(eq(R.array.config_foldedDeviceStates));
+        Rect displayBounds = new Rect(0, 0, 1280, 2856);
+        WindowMetrics displayMetrics =
+                new WindowMetrics(displayBounds, new WindowInsets.Builder().build(),
+                        /* density= */ 3f);
+        when(mWindowManager.getPossibleMaximumWindowMetrics(anyInt()))
+                .thenReturn(Set.of(displayMetrics));
+
+        WallpaperDefaultDisplayInfo defaultDisplayInfo = new WallpaperDefaultDisplayInfo(
+                mWindowManager, mResources);
+
+        SparseArray<Point> displaySizes = new SparseArray<>();
+        displaySizes.put(ORIENTATION_PORTRAIT, new Point(1280, 2856));
+        displaySizes.put(ORIENTATION_LANDSCAPE, new Point(2856, 1280));
+        assertThat(defaultDisplayInfo.defaultDisplaySizes.contentEquals(displaySizes)).isTrue();
+        assertThat(defaultDisplayInfo.isFoldable).isFalse();
+        assertThat(defaultDisplayInfo.isLargeScreen).isFalse();
+        assertThat(defaultDisplayInfo.foldableOrientations).isEmpty();
+    }
+
+    @Test
+    public void defaultDisplayInfo_equals_sameContent_shouldEqual() {
+        doReturn(new int[]{0}).when(mResources).getIntArray(eq(R.array.config_foldedDeviceStates));
+        Rect innerDisplayBounds = new Rect(0, 0, 2076, 2152);
+        Rect outerDisplayBounds = new Rect(0, 0, 1080, 2424);
+        WindowMetrics innerDisplayMetrics =
+                new WindowMetrics(innerDisplayBounds, new WindowInsets.Builder().build(),
+                        /* density= */ 2.4375f);
+        WindowMetrics outerDisplayMetrics =
+                new WindowMetrics(outerDisplayBounds, new WindowInsets.Builder().build(),
+                        /* density= */ 2.4375f);
+        when(mWindowManager.getPossibleMaximumWindowMetrics(anyInt()))
+                .thenReturn(Set.of(innerDisplayMetrics, outerDisplayMetrics));
+
+        WallpaperDefaultDisplayInfo defaultDisplayInfo = new WallpaperDefaultDisplayInfo(
+                mWindowManager, mResources);
+        WallpaperDefaultDisplayInfo otherDefaultDisplayInfo = new WallpaperDefaultDisplayInfo(
+                mWindowManager, mResources);
+
+        assertThat(defaultDisplayInfo).isEqualTo(otherDefaultDisplayInfo);
+    }
+
+    @Test
+    public void defaultDisplayInfo_equals_differentBounds_shouldNotEqual() {
+        doReturn(new int[]{0}).when(mResources).getIntArray(eq(R.array.config_foldedDeviceStates));
+        Rect innerDisplayBounds = new Rect(0, 0, 2076, 2152);
+        Rect outerDisplayBounds = new Rect(0, 0, 1080, 2424);
+        WindowMetrics innerDisplayMetrics =
+                new WindowMetrics(innerDisplayBounds, new WindowInsets.Builder().build(),
+                        /* density= */ 2.4375f);
+        WindowMetrics outerDisplayMetrics =
+                new WindowMetrics(outerDisplayBounds, new WindowInsets.Builder().build(),
+                        /* density= */ 2.4375f);
+        when(mWindowManager.getPossibleMaximumWindowMetrics(anyInt()))
+                // For the first call
+                .thenReturn(Set.of(innerDisplayMetrics, outerDisplayMetrics))
+                // For the second+ call
+                .thenReturn(Set.of(innerDisplayMetrics));
+
+        WallpaperDefaultDisplayInfo defaultDisplayInfo = new WallpaperDefaultDisplayInfo(
+                mWindowManager, mResources);
+        WallpaperDefaultDisplayInfo otherDefaultDisplayInfo = new WallpaperDefaultDisplayInfo(
+                mWindowManager, mResources);
+
+        assertThat(defaultDisplayInfo).isNotEqualTo(otherDefaultDisplayInfo);
+    }
+
+    @Test
+    public void defaultDisplayInfo_hashCode_sameContent_shouldEqual() {
+        doReturn(new int[]{0}).when(mResources).getIntArray(eq(R.array.config_foldedDeviceStates));
+        Rect innerDisplayBounds = new Rect(0, 0, 2076, 2152);
+        Rect outerDisplayBounds = new Rect(0, 0, 1080, 2424);
+        WindowMetrics innerDisplayMetrics =
+                new WindowMetrics(innerDisplayBounds, new WindowInsets.Builder().build(),
+                        /* density= */ 2.4375f);
+        WindowMetrics outerDisplayMetrics =
+                new WindowMetrics(outerDisplayBounds, new WindowInsets.Builder().build(),
+                        /* density= */ 2.4375f);
+        when(mWindowManager.getPossibleMaximumWindowMetrics(anyInt()))
+                .thenReturn(Set.of(innerDisplayMetrics, outerDisplayMetrics));
+
+        WallpaperDefaultDisplayInfo defaultDisplayInfo = new WallpaperDefaultDisplayInfo(
+                mWindowManager, mResources);
+        WallpaperDefaultDisplayInfo otherDefaultDisplayInfo = new WallpaperDefaultDisplayInfo(
+                mWindowManager, mResources);
+
+        assertThat(defaultDisplayInfo.hashCode()).isEqualTo(otherDefaultDisplayInfo.hashCode());
+    }
+
+    @Test
+    public void defaultDisplayInfo_hashCode_differentBounds_shouldNotEqual() {
+        doReturn(new int[]{0}).when(mResources).getIntArray(eq(R.array.config_foldedDeviceStates));
+        Rect innerDisplayBounds = new Rect(0, 0, 2076, 2152);
+        Rect outerDisplayBounds = new Rect(0, 0, 1080, 2424);
+        WindowMetrics innerDisplayMetrics =
+                new WindowMetrics(innerDisplayBounds, new WindowInsets.Builder().build(),
+                        /* density= */ 2.4375f);
+        WindowMetrics outerDisplayMetrics =
+                new WindowMetrics(outerDisplayBounds, new WindowInsets.Builder().build(),
+                        /* density= */ 2.4375f);
+        when(mWindowManager.getPossibleMaximumWindowMetrics(anyInt()))
+                // For the first call
+                .thenReturn(Set.of(innerDisplayMetrics, outerDisplayMetrics))
+                // For the second+ call
+                .thenReturn(Set.of(innerDisplayMetrics));
+
+        WallpaperDefaultDisplayInfo defaultDisplayInfo = new WallpaperDefaultDisplayInfo(
+                mWindowManager, mResources);
+        WallpaperDefaultDisplayInfo otherDefaultDisplayInfo = new WallpaperDefaultDisplayInfo(
+                mWindowManager, mResources);
+
+        assertThat(defaultDisplayInfo.hashCode()).isNotEqualTo(otherDefaultDisplayInfo.hashCode());
+    }
+
+    @Test
+    public void getFoldedOrientation_foldable_shouldReturnExpectedOrientation() {
+        doReturn(new int[]{0}).when(mResources).getIntArray(eq(R.array.config_foldedDeviceStates));
+        Rect innerDisplayBounds = new Rect(0, 0, 2076, 2152);
+        Rect outerDisplayBounds = new Rect(0, 0, 1080, 2424);
+        WindowMetrics innerDisplayMetrics =
+                new WindowMetrics(innerDisplayBounds, new WindowInsets.Builder().build(),
+                        /* density= */ 2.4375f);
+        WindowMetrics outerDisplayMetrics =
+                new WindowMetrics(outerDisplayBounds, new WindowInsets.Builder().build(),
+                        /* density= */ 2.4375f);
+        when(mWindowManager.getPossibleMaximumWindowMetrics(anyInt()))
+                .thenReturn(Set.of(innerDisplayMetrics, outerDisplayMetrics));
+        WallpaperDefaultDisplayInfo defaultDisplayInfo = new WallpaperDefaultDisplayInfo(
+                mWindowManager, mResources);
+
+        assertThat(defaultDisplayInfo.getFoldedOrientation(ORIENTATION_SQUARE_PORTRAIT))
+                .isEqualTo(ORIENTATION_PORTRAIT);
+        assertThat(defaultDisplayInfo.getFoldedOrientation(ORIENTATION_SQUARE_LANDSCAPE))
+                .isEqualTo(ORIENTATION_LANDSCAPE);
+        // Use a folded orientation for a folded orientation should return unknown.
+        assertThat(defaultDisplayInfo.getFoldedOrientation(ORIENTATION_PORTRAIT))
+                .isEqualTo(ORIENTATION_UNKNOWN);
+        assertThat(defaultDisplayInfo.getFoldedOrientation(ORIENTATION_LANDSCAPE))
+                .isEqualTo(ORIENTATION_UNKNOWN);
+    }
+
+    @Test
+    public void getUnfoldedOrientation_foldable_shouldReturnExpectedOrientation() {
+        doReturn(new int[]{0}).when(mResources).getIntArray(eq(R.array.config_foldedDeviceStates));
+        Rect innerDisplayBounds = new Rect(0, 0, 2076, 2152);
+        Rect outerDisplayBounds = new Rect(0, 0, 1080, 2424);
+        WindowMetrics innerDisplayMetrics =
+                new WindowMetrics(innerDisplayBounds, new WindowInsets.Builder().build(),
+                        /* density= */ 2.4375f);
+        WindowMetrics outerDisplayMetrics =
+                new WindowMetrics(outerDisplayBounds, new WindowInsets.Builder().build(),
+                        /* density= */ 2.4375f);
+        when(mWindowManager.getPossibleMaximumWindowMetrics(anyInt()))
+                .thenReturn(Set.of(innerDisplayMetrics, outerDisplayMetrics));
+        WallpaperDefaultDisplayInfo defaultDisplayInfo = new WallpaperDefaultDisplayInfo(
+                mWindowManager, mResources);
+
+        assertThat(defaultDisplayInfo.getUnfoldedOrientation(ORIENTATION_PORTRAIT))
+                .isEqualTo(ORIENTATION_SQUARE_PORTRAIT);
+        assertThat(defaultDisplayInfo.getUnfoldedOrientation(ORIENTATION_LANDSCAPE))
+                .isEqualTo(ORIENTATION_SQUARE_LANDSCAPE);
+        // Use an unfolded orientation for an unfolded orientation should return unknown.
+        assertThat(defaultDisplayInfo.getUnfoldedOrientation(ORIENTATION_SQUARE_PORTRAIT))
+                .isEqualTo(ORIENTATION_UNKNOWN);
+        assertThat(defaultDisplayInfo.getUnfoldedOrientation(ORIENTATION_SQUARE_LANDSCAPE))
+                .isEqualTo(ORIENTATION_UNKNOWN);
+    }
+
+    @Test
+    public void getFoldedOrientation_nonFoldable_shouldReturnUnknown() {
+        doReturn(new int[]{}).when(mResources).getIntArray(eq(R.array.config_foldedDeviceStates));
+        Rect displayBounds = new Rect(0, 0, 2560, 1600);
+        WindowMetrics displayMetrics =
+                new WindowMetrics(displayBounds, new WindowInsets.Builder().build(),
+                        /* density= */ 2f);
+        when(mWindowManager.getPossibleMaximumWindowMetrics(anyInt()))
+                .thenReturn(Set.of(displayMetrics));
+
+        WallpaperDefaultDisplayInfo defaultDisplayInfo = new WallpaperDefaultDisplayInfo(
+                mWindowManager, mResources);
+
+        assertThat(defaultDisplayInfo.getFoldedOrientation(ORIENTATION_SQUARE_PORTRAIT))
+                .isEqualTo(ORIENTATION_UNKNOWN);
+        assertThat(defaultDisplayInfo.getFoldedOrientation(ORIENTATION_SQUARE_LANDSCAPE))
+                .isEqualTo(ORIENTATION_UNKNOWN);
+        assertThat(defaultDisplayInfo.getFoldedOrientation(ORIENTATION_PORTRAIT))
+                .isEqualTo(ORIENTATION_UNKNOWN);
+        assertThat(defaultDisplayInfo.getFoldedOrientation(ORIENTATION_LANDSCAPE))
+                .isEqualTo(ORIENTATION_UNKNOWN);
+    }
+
+    @Test
+    public void getUnFoldedOrientation_nonFoldable_shouldReturnUnknown() {
+        doReturn(new int[]{}).when(mResources).getIntArray(eq(R.array.config_foldedDeviceStates));
+        Rect displayBounds = new Rect(0, 0, 2560, 1600);
+        WindowMetrics displayMetrics =
+                new WindowMetrics(displayBounds, new WindowInsets.Builder().build(),
+                        /* density= */ 2f);
+        when(mWindowManager.getPossibleMaximumWindowMetrics(anyInt()))
+                .thenReturn(Set.of(displayMetrics));
+
+        WallpaperDefaultDisplayInfo defaultDisplayInfo = new WallpaperDefaultDisplayInfo(
+                mWindowManager, mResources);
+
+        assertThat(defaultDisplayInfo.getUnfoldedOrientation(ORIENTATION_SQUARE_PORTRAIT))
+                .isEqualTo(ORIENTATION_UNKNOWN);
+        assertThat(defaultDisplayInfo.getUnfoldedOrientation(ORIENTATION_SQUARE_LANDSCAPE))
+                .isEqualTo(ORIENTATION_UNKNOWN);
+        assertThat(defaultDisplayInfo.getUnfoldedOrientation(ORIENTATION_PORTRAIT))
+                .isEqualTo(ORIENTATION_UNKNOWN);
+        assertThat(defaultDisplayInfo.getUnfoldedOrientation(ORIENTATION_LANDSCAPE))
+                .isEqualTo(ORIENTATION_UNKNOWN);
+    }
+}
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 69877c3..ea25e79 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
@@ -43,6 +43,7 @@
 
 import com.android.internal.accessibility.util.AccessibilityUtils;
 import com.android.server.accessibility.AccessibilityTraceManager;
+import com.android.server.accessibility.BaseEventStreamTransformation;
 
 import org.junit.After;
 import org.junit.Before;
@@ -70,6 +71,19 @@
     @Mock private WindowManager mMockWindowManager;
     private AutoclickController mController;
 
+    private static class MotionEventCaptor extends BaseEventStreamTransformation {
+        public MotionEvent downEvent;
+
+        @Override
+        public void onMotionEvent(MotionEvent event, MotionEvent rawEvent, int policyFlags) {
+            switch (event.getAction()) {
+                case MotionEvent.ACTION_DOWN:
+                    downEvent = event;
+                    break;
+            }
+        }
+    }
+
     @Before
     public void setUp() {
         mTestableLooper = TestableLooper.get(this);
@@ -713,6 +727,100 @@
         assertThat(mController.mAutoclickScrollPanel.isVisible()).isFalse();
     }
 
+    @Test
+    public void sendClick_clickType_leftClick() {
+        MotionEventCaptor motionEventCaptor = new MotionEventCaptor();
+        mController.setNext(motionEventCaptor);
+
+        injectFakeMouseActionHoverMoveEvent();
+        // Set delay to zero so click is scheduled to run immediately.
+        mController.mClickScheduler.updateDelay(0);
+
+        // 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);
+        mTestableLooper.processAllMessages();
+
+        // Verify left click sent.
+        assertThat(motionEventCaptor.downEvent).isNotNull();
+        assertThat(motionEventCaptor.downEvent.getButtonState()).isEqualTo(
+                MotionEvent.BUTTON_PRIMARY);
+    }
+
+    @Test
+    public void sendClick_clickType_rightClick() {
+        MotionEventCaptor motionEventCaptor = new MotionEventCaptor();
+        mController.setNext(motionEventCaptor);
+
+        injectFakeMouseActionHoverMoveEvent();
+        // Set delay to zero so click is scheduled to run immediately.
+        mController.mClickScheduler.updateDelay(0);
+
+        // Set click type to right click.
+        mController.clickPanelController.handleAutoclickTypeChange(
+                AutoclickTypePanel.AUTOCLICK_TYPE_RIGHT_CLICK);
+
+        // 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);
+        mTestableLooper.processAllMessages();
+
+        // Verify right click sent.
+        assertThat(motionEventCaptor.downEvent).isNotNull();
+        assertThat(motionEventCaptor.downEvent.getButtonState()).isEqualTo(
+                MotionEvent.BUTTON_SECONDARY);
+    }
+
+    @Test
+    @EnableFlags(com.android.server.accessibility.Flags.FLAG_ENABLE_AUTOCLICK_INDICATOR)
+    public void hoverOnAutoclickPanel_rightClickType_forceTriggerLeftClick() {
+        MotionEventCaptor motionEventCaptor = new MotionEventCaptor();
+        mController.setNext(motionEventCaptor);
+
+        injectFakeMouseActionHoverMoveEvent();
+        // Set delay to zero so click is scheduled to run immediately.
+        mController.mClickScheduler.updateDelay(0);
+
+        // Set click type to right click.
+        mController.clickPanelController.handleAutoclickTypeChange(
+                AutoclickTypePanel.AUTOCLICK_TYPE_RIGHT_CLICK);
+        // Set mouse to hover panel.
+        AutoclickTypePanel mockAutoclickTypePanel = mock(AutoclickTypePanel.class);
+        when(mockAutoclickTypePanel.isHovered()).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);
+        mTestableLooper.processAllMessages();
+
+        // Verify left click is sent due to the mouse hovering the panel.
+        assertThat(motionEventCaptor.downEvent).isNotNull();
+        assertThat(motionEventCaptor.downEvent.getButtonState()).isEqualTo(
+                MotionEvent.BUTTON_PRIMARY);
+    }
+
     private void injectFakeMouseActionHoverMoveEvent() {
         MotionEvent event = getFakeMotionHoverMoveEvent();
         event.setSource(InputDevice.SOURCE_MOUSE);
diff --git a/services/tests/wmtests/src/com/android/server/wm/ActivityStarterTests.java b/services/tests/wmtests/src/com/android/server/wm/ActivityStarterTests.java
index e3e9cc4..08b0077 100644
--- a/services/tests/wmtests/src/com/android/server/wm/ActivityStarterTests.java
+++ b/services/tests/wmtests/src/com/android/server/wm/ActivityStarterTests.java
@@ -797,7 +797,7 @@
         // Create adjacent tasks and put one activity under it
         final Task parent = new TaskBuilder(mSupervisor).build();
         final Task adjacentParent = new TaskBuilder(mSupervisor).build();
-        parent.setAdjacentTaskFragment(adjacentParent);
+        parent.setAdjacentTaskFragments(new TaskFragment.AdjacentSet(parent, adjacentParent));
         final ActivityRecord activity = new ActivityBuilder(mAtm)
                 .setParentTask(parent)
                 .setCreateTask(true).build();
diff --git a/services/tests/wmtests/src/com/android/server/wm/ActivityTaskSupervisorTests.java b/services/tests/wmtests/src/com/android/server/wm/ActivityTaskSupervisorTests.java
index a9be47d..86d901b 100644
--- a/services/tests/wmtests/src/com/android/server/wm/ActivityTaskSupervisorTests.java
+++ b/services/tests/wmtests/src/com/android/server/wm/ActivityTaskSupervisorTests.java
@@ -488,14 +488,13 @@
                 WINDOWING_MODE_MULTI_WINDOW, /* opaque */ true, /* filling */ false);
         final TaskFragment tf2 = createChildTaskFragment(/* parent */ rootTask,
                 WINDOWING_MODE_MULTI_WINDOW, /* opaque */ true, /* filling */ false);
-        tf1.setAdjacentTaskFragment(tf2);
+        tf1.setAdjacentTaskFragments(new TaskFragment.AdjacentSet(tf1, tf2));
 
         assertThat(mSupervisor.mOpaqueContainerHelper.isOpaque(rootTask)).isTrue();
     }
 
     @Test
-    @EnableFlags({Flags.FLAG_ENABLE_MULTIPLE_DESKTOPS_BACKEND,
-            Flags.FLAG_ALLOW_MULTIPLE_ADJACENT_TASK_FRAGMENTS})
+    @EnableFlags(Flags.FLAG_ENABLE_MULTIPLE_DESKTOPS_BACKEND)
     public void testOpaque_rootTask_nonFillingOpaqueAdjacentChildren_multipleAdjacent_isOpaque() {
         final Task rootTask = new TaskBuilder(mSupervisor).setOnTop(true).build();
         final TaskFragment tf1 = createChildTaskFragment(/* parent */ rootTask,
diff --git a/services/tests/wmtests/src/com/android/server/wm/AppCompatActivityRobot.java b/services/tests/wmtests/src/com/android/server/wm/AppCompatActivityRobot.java
index 8fe0855..cb98b9a 100644
--- a/services/tests/wmtests/src/com/android/server/wm/AppCompatActivityRobot.java
+++ b/services/tests/wmtests/src/com/android/server/wm/AppCompatActivityRobot.java
@@ -243,6 +243,11 @@
                 .getAspectRatioOverrides()).getUserMinAspectRatio();
     }
 
+    void setShouldRefreshActivityForCameraCompat(boolean enabled) {
+        doReturn(enabled).when(mActivityStack.top().mAppCompatController.getCameraOverrides())
+                .shouldRefreshActivityForCameraCompat();
+    }
+
     void setIgnoreOrientationRequest(boolean enabled) {
         mDisplayContent.setIgnoreOrientationRequest(enabled);
     }
diff --git a/services/tests/wmtests/src/com/android/server/wm/AppCompatConfigurationRobot.java b/services/tests/wmtests/src/com/android/server/wm/AppCompatConfigurationRobot.java
index 05f6ed6..7ef85262d 100644
--- a/services/tests/wmtests/src/com/android/server/wm/AppCompatConfigurationRobot.java
+++ b/services/tests/wmtests/src/com/android/server/wm/AppCompatConfigurationRobot.java
@@ -64,6 +64,19 @@
                 .isCameraCompatTreatmentEnabledAtBuildTime();
     }
 
+    void setCameraCompatAspectRatio(float aspectRatio) {
+        doReturn(aspectRatio).when(mAppCompatConfiguration).getCameraCompatAspectRatio();
+    }
+
+    void enableCameraCompatRefresh(boolean enabled) {
+        doReturn(enabled).when(mAppCompatConfiguration).isCameraCompatRefreshEnabled();
+    }
+
+    void enableCameraCompatRefreshCycleThroughStop(boolean enabled) {
+        doReturn(enabled).when(mAppCompatConfiguration)
+                .isCameraCompatRefreshCycleThroughStopEnabled();
+    }
+
     void enableUserAppAspectRatioFullscreen(boolean enabled) {
         doReturn(enabled).when(mAppCompatConfiguration).isUserAppAspectRatioFullscreenEnabled();
     }
diff --git a/services/tests/wmtests/src/com/android/server/wm/BackNavigationControllerTests.java b/services/tests/wmtests/src/com/android/server/wm/BackNavigationControllerTests.java
index bdee3c3..dd3e9fc 100644
--- a/services/tests/wmtests/src/com/android/server/wm/BackNavigationControllerTests.java
+++ b/services/tests/wmtests/src/com/android/server/wm/BackNavigationControllerTests.java
@@ -343,8 +343,7 @@
 
         // Adjacent + no companion => unable to predict
         // TF1 | TF2
-        tf1.setAdjacentTaskFragment(tf2);
-        tf2.setAdjacentTaskFragment(tf1);
+        tf1.setAdjacentTaskFragments(new TaskFragment.AdjacentSet(tf1, tf2));
         predictable = BackNavigationController.getAnimatablePrevActivities(task, topAr,
                 outPrevActivities);
         assertTrue(outPrevActivities.isEmpty());
@@ -393,8 +392,7 @@
         // Adjacent => predict for previous activity.
         // TF2 | TF3
         // TF1
-        tf2.setAdjacentTaskFragment(tf3);
-        tf3.setAdjacentTaskFragment(tf2);
+        tf2.setAdjacentTaskFragments(new TaskFragment.AdjacentSet(tf2, tf3));
         predictable = BackNavigationController.getAnimatablePrevActivities(task, topAr,
                 outPrevActivities);
         assertTrue(outPrevActivities.contains(prevAr));
@@ -657,8 +655,7 @@
         final TaskFragment secondaryTf = createTaskFragmentWithEmbeddedActivity(task, organizer);
         final ActivityRecord primaryActivity = primaryTf.getTopMostActivity();
         final ActivityRecord secondaryActivity = secondaryTf.getTopMostActivity();
-        primaryTf.setAdjacentTaskFragment(secondaryTf);
-        secondaryTf.setAdjacentTaskFragment(primaryTf);
+        primaryTf.setAdjacentTaskFragments(new TaskFragment.AdjacentSet(primaryTf, secondaryTf));
 
         final WindowState primaryWindow = mock(WindowState.class);
         final WindowState secondaryWindow = mock(WindowState.class);
diff --git a/services/tests/wmtests/src/com/android/server/wm/CameraCompatFreeformPolicyTests.java b/services/tests/wmtests/src/com/android/server/wm/CameraCompatFreeformPolicyTests.java
index f5bec04..6f95981 100644
--- a/services/tests/wmtests/src/com/android/server/wm/CameraCompatFreeformPolicyTests.java
+++ b/services/tests/wmtests/src/com/android/server/wm/CameraCompatFreeformPolicyTests.java
@@ -21,13 +21,13 @@
 import static android.app.CameraCompatTaskInfo.CAMERA_COMPAT_FREEFORM_NONE;
 import static android.app.CameraCompatTaskInfo.CAMERA_COMPAT_FREEFORM_PORTRAIT_DEVICE_IN_LANDSCAPE;
 import static android.app.CameraCompatTaskInfo.CAMERA_COMPAT_FREEFORM_PORTRAIT_DEVICE_IN_PORTRAIT;
+import static android.app.CameraCompatTaskInfo.FreeformCameraCompatMode;
 import static android.app.WindowConfiguration.WINDOWING_MODE_FREEFORM;
 import static android.app.WindowConfiguration.WINDOWING_MODE_FULLSCREEN;
 import static android.app.servertransaction.ActivityLifecycleItem.ON_PAUSE;
 import static android.app.servertransaction.ActivityLifecycleItem.ON_STOP;
 import static android.content.pm.ActivityInfo.OVERRIDE_CAMERA_COMPAT_DISABLE_SIMULATE_REQUESTED_ORIENTATION;
 import static android.content.pm.ActivityInfo.OVERRIDE_CAMERA_COMPAT_ENABLE_FREEFORM_WINDOWING_TREATMENT;
-import static android.content.pm.ActivityInfo.RESIZE_MODE_RESIZEABLE;
 import static android.content.pm.ActivityInfo.SCREEN_ORIENTATION_FULL_USER;
 import static android.content.pm.ActivityInfo.SCREEN_ORIENTATION_LANDSCAPE;
 import static android.content.pm.ActivityInfo.SCREEN_ORIENTATION_PORTRAIT;
@@ -40,18 +40,15 @@
 import static com.android.dx.mockito.inline.extended.ExtendedMockito.doAnswer;
 import static com.android.dx.mockito.inline.extended.ExtendedMockito.doReturn;
 import static com.android.dx.mockito.inline.extended.ExtendedMockito.spyOn;
-import static com.android.dx.mockito.inline.extended.ExtendedMockito.when;
 import static com.android.server.wm.AppCompatConfiguration.MIN_FIXED_ORIENTATION_LETTERBOX_ASPECT_RATIO;
 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 static org.junit.Assert.assertEquals;
-import static org.junit.Assert.assertFalse;
 import static org.junit.Assert.assertNull;
 import static org.junit.Assert.assertTrue;
+import static org.junit.Assert.fail;
 import static org.mockito.ArgumentMatchers.any;
-import static org.mockito.ArgumentMatchers.anyBoolean;
-import static org.mockito.ArgumentMatchers.anyInt;
 import static org.mockito.ArgumentMatchers.anyLong;
 import static org.mockito.ArgumentMatchers.eq;
 import static org.mockito.Mockito.mock;
@@ -59,13 +56,11 @@
 import static org.mockito.Mockito.verify;
 
 import android.annotation.NonNull;
-import android.app.CameraCompatTaskInfo;
 import android.app.IApplicationThread;
 import android.app.WindowConfiguration.WindowingMode;
 import android.app.servertransaction.RefreshCallbackItem;
 import android.app.servertransaction.ResumeActivityItem;
 import android.compat.testing.PlatformCompatChangeRule;
-import android.content.ComponentName;
 import android.content.pm.ActivityInfo.ScreenOrientation;
 import android.content.res.CompatibilityInfo;
 import android.content.res.Configuration;
@@ -73,17 +68,16 @@
 import android.graphics.Rect;
 import android.hardware.camera2.CameraManager;
 import android.os.Handler;
+import android.os.RemoteException;
 import android.platform.test.annotations.DisableFlags;
 import android.platform.test.annotations.EnableFlags;
 import android.platform.test.annotations.Presubmit;
-import android.view.DisplayInfo;
 import android.view.Surface;
 
 import androidx.test.filters.SmallTest;
 
 import libcore.junit.util.compat.CoreCompatChangeRule.EnableCompatChanges;
 
-import org.junit.Before;
 import org.junit.Rule;
 import org.junit.Test;
 import org.junit.rules.TestRule;
@@ -91,6 +85,7 @@
 import org.mockito.ArgumentCaptor;
 
 import java.util.concurrent.Executor;
+import java.util.function.Consumer;
 
 /**
  * Tests for {@link CameraCompatFreeformPolicy}.
@@ -109,30 +104,18 @@
     private static final String TEST_PACKAGE_1 = "com.android.frameworks.wmtests";
     private static final String TEST_PACKAGE_2 = "com.test.package.two";
     private static final String CAMERA_ID_1 = "camera-1";
-    private AppCompatConfiguration mAppCompatConfiguration;
-
-    private CameraManager.AvailabilityCallback mCameraAvailabilityCallback;
-    private CameraCompatFreeformPolicy mCameraCompatFreeformPolicy;
-    private ActivityRecord mActivity;
-
-    // TODO(b/384465100): use a robot structure.
-    @Before
-    public void setUp() throws Exception {
-        setupAppCompatConfiguration();
-        setupCameraManager();
-        setupHandler();
-        doReturn(true).when(() -> DesktopModeHelper.canEnterDesktopMode(any()));
-    }
 
     @Test
     @DisableFlags(FLAG_ENABLE_CAMERA_COMPAT_FOR_DESKTOP_WINDOWING)
     @EnableCompatChanges({OVERRIDE_CAMERA_COMPAT_ENABLE_FREEFORM_WINDOWING_TREATMENT})
     public void testFeatureDisabled_cameraCompatFreeformPolicyNotCreated() {
-        configureActivity(SCREEN_ORIENTATION_PORTRAIT);
+        runTestScenario((robot) -> {
+            robot.configureActivity(SCREEN_ORIENTATION_PORTRAIT);
 
-        mCameraAvailabilityCallback.onCameraOpened(CAMERA_ID_1, TEST_PACKAGE_1);
+            robot.onCameraOpened(CAMERA_ID_1, TEST_PACKAGE_1);
 
-        assertNull(mCameraCompatFreeformPolicy);
+            robot.checkCameraCompatPolicyNotCreated();
+        });
     }
 
     @Test
@@ -140,31 +123,37 @@
             FLAG_ENABLE_CAMERA_COMPAT_FOR_DESKTOP_WINDOWING_OPT_OUT})
     @EnableCompatChanges({OVERRIDE_CAMERA_COMPAT_DISABLE_SIMULATE_REQUESTED_ORIENTATION})
     public void testIsCameraRunningAndWindowingModeEligible_disabledViaOverride_returnsFalse() {
-        configureActivity(SCREEN_ORIENTATION_PORTRAIT);
+        runTestScenario((robot) -> {
+            robot.configureActivity(SCREEN_ORIENTATION_PORTRAIT);
 
-        mCameraAvailabilityCallback.onCameraOpened(CAMERA_ID_1, TEST_PACKAGE_1);
+            robot.onCameraOpened(CAMERA_ID_1, TEST_PACKAGE_1);
 
-        assertFalse(mCameraCompatFreeformPolicy.isCameraRunningAndWindowingModeEligible(mActivity));
+            robot.checkIsCameraRunningAndWindowingModeEligible(false);
+        });
     }
 
     @Test
     @EnableFlags(FLAG_ENABLE_CAMERA_COMPAT_FOR_DESKTOP_WINDOWING)
     @EnableCompatChanges({OVERRIDE_CAMERA_COMPAT_ENABLE_FREEFORM_WINDOWING_TREATMENT})
     public void testIsCameraRunningAndWindowingModeEligible_cameraNotRunning_returnsFalse() {
-        configureActivity(SCREEN_ORIENTATION_PORTRAIT);
+        runTestScenario((robot) -> {
+            robot.configureActivity(SCREEN_ORIENTATION_PORTRAIT);
 
-        assertFalse(mCameraCompatFreeformPolicy.isCameraRunningAndWindowingModeEligible(mActivity));
+            robot.checkIsCameraRunningAndWindowingModeEligible(false);
+        });
     }
 
     @Test
     @EnableFlags(FLAG_ENABLE_CAMERA_COMPAT_FOR_DESKTOP_WINDOWING)
     @EnableCompatChanges({OVERRIDE_CAMERA_COMPAT_ENABLE_FREEFORM_WINDOWING_TREATMENT})
     public void testIsCameraRunningAndWindowingModeEligible_notFreeformWindowing_returnsFalse() {
-        configureActivity(SCREEN_ORIENTATION_PORTRAIT, WINDOWING_MODE_FULLSCREEN);
+        runTestScenario((robot) -> {
+            robot.configureActivity(SCREEN_ORIENTATION_PORTRAIT, WINDOWING_MODE_FULLSCREEN);
 
-        onCameraOpened(CAMERA_ID_1, TEST_PACKAGE_1);
+            robot.onCameraOpened(CAMERA_ID_1, TEST_PACKAGE_1);
 
-        assertFalse(mCameraCompatFreeformPolicy.isCameraRunningAndWindowingModeEligible(mActivity));
+            robot.checkIsCameraRunningAndWindowingModeEligible(false);
+        });
     }
 
     @Test
@@ -172,64 +161,76 @@
     @DisableFlags(FLAG_ENABLE_CAMERA_COMPAT_FOR_DESKTOP_WINDOWING_OPT_OUT)
     @EnableCompatChanges({OVERRIDE_CAMERA_COMPAT_ENABLE_FREEFORM_WINDOWING_TREATMENT})
     public void testIsCameraRunningAndWindowingModeEligible_optInFreeformCameraRunning_true() {
-        configureActivity(SCREEN_ORIENTATION_PORTRAIT);
+        runTestScenario((robot) -> {
+            robot.configureActivity(SCREEN_ORIENTATION_PORTRAIT);
 
-        onCameraOpened(CAMERA_ID_1, TEST_PACKAGE_1);
+            robot.onCameraOpened(CAMERA_ID_1, TEST_PACKAGE_1);
 
-        assertTrue(mCameraCompatFreeformPolicy.isCameraRunningAndWindowingModeEligible(mActivity));
+            robot.checkIsCameraRunningAndWindowingModeEligible(true);
+        });
     }
 
     @Test
     @EnableFlags({FLAG_ENABLE_CAMERA_COMPAT_FOR_DESKTOP_WINDOWING,
             FLAG_ENABLE_CAMERA_COMPAT_FOR_DESKTOP_WINDOWING_OPT_OUT})
     public void testIsCameraRunningAndWindowingModeEligible_freeformCameraRunning_true() {
-        configureActivity(SCREEN_ORIENTATION_PORTRAIT);
+        runTestScenario((robot) -> {
+            robot.configureActivity(SCREEN_ORIENTATION_PORTRAIT);
 
-        onCameraOpened(CAMERA_ID_1, TEST_PACKAGE_1);
+            robot.onCameraOpened(CAMERA_ID_1, TEST_PACKAGE_1);
 
-        assertTrue(mCameraCompatFreeformPolicy.isCameraRunningAndWindowingModeEligible(mActivity));
+            robot.checkIsCameraRunningAndWindowingModeEligible(true);
+        });
     }
 
     @Test
     @EnableFlags(FLAG_ENABLE_CAMERA_COMPAT_FOR_DESKTOP_WINDOWING)
     @DisableFlags(FLAG_ENABLE_CAMERA_COMPAT_FOR_DESKTOP_WINDOWING_OPT_OUT)
     public void testIsFreeformLetterboxingForCameraAllowed_optInMechanism_notOptedIn_retFalse() {
-        configureActivity(SCREEN_ORIENTATION_PORTRAIT);
+        runTestScenario((robot) -> {
+            robot.configureActivity(SCREEN_ORIENTATION_PORTRAIT);
 
-        onCameraOpened(CAMERA_ID_1, TEST_PACKAGE_1);
+            robot.onCameraOpened(CAMERA_ID_1, TEST_PACKAGE_1);
 
-        assertFalse(mCameraCompatFreeformPolicy.isFreeformLetterboxingForCameraAllowed(mActivity));
+            robot.checkIsFreeformLetterboxingForCameraAllowed(false);
+        });
     }
 
     @Test
     @EnableFlags({FLAG_ENABLE_CAMERA_COMPAT_FOR_DESKTOP_WINDOWING,
             FLAG_ENABLE_CAMERA_COMPAT_FOR_DESKTOP_WINDOWING_OPT_OUT})
     public void testIsFreeformLetterboxingForCameraAllowed_notOptedOut_returnsTrue() {
-        configureActivity(SCREEN_ORIENTATION_PORTRAIT);
+        runTestScenario((robot) -> {
+            robot.configureActivity(SCREEN_ORIENTATION_PORTRAIT);
 
-        onCameraOpened(CAMERA_ID_1, TEST_PACKAGE_1);
+            robot.onCameraOpened(CAMERA_ID_1, TEST_PACKAGE_1);
 
-        assertTrue(mCameraCompatFreeformPolicy.isFreeformLetterboxingForCameraAllowed(mActivity));
+            robot.checkIsFreeformLetterboxingForCameraAllowed(true);
+        });
     }
 
     @Test
     @EnableFlags(FLAG_ENABLE_CAMERA_COMPAT_FOR_DESKTOP_WINDOWING)
     @EnableCompatChanges({OVERRIDE_CAMERA_COMPAT_ENABLE_FREEFORM_WINDOWING_TREATMENT})
     public void testIsFreeformLetterboxingForCameraAllowed_cameraNotRunning_returnsFalse() {
-        configureActivity(SCREEN_ORIENTATION_PORTRAIT);
+        runTestScenario((robot) -> {
+            robot.configureActivity(SCREEN_ORIENTATION_PORTRAIT);
 
-        assertFalse(mCameraCompatFreeformPolicy.isFreeformLetterboxingForCameraAllowed(mActivity));
+            robot.checkIsFreeformLetterboxingForCameraAllowed(false);
+        });
     }
 
     @Test
     @EnableFlags(FLAG_ENABLE_CAMERA_COMPAT_FOR_DESKTOP_WINDOWING)
     @EnableCompatChanges({OVERRIDE_CAMERA_COMPAT_ENABLE_FREEFORM_WINDOWING_TREATMENT})
     public void testIsFreeformLetterboxingForCameraAllowed_notFreeformWindowing_returnsFalse() {
-        configureActivity(SCREEN_ORIENTATION_PORTRAIT, WINDOWING_MODE_FULLSCREEN);
+        runTestScenario((robot) -> {
+            robot.configureActivity(SCREEN_ORIENTATION_PORTRAIT, WINDOWING_MODE_FULLSCREEN);
 
-        onCameraOpened(CAMERA_ID_1, TEST_PACKAGE_1);
+            robot.onCameraOpened(CAMERA_ID_1, TEST_PACKAGE_1);
 
-        assertFalse(mCameraCompatFreeformPolicy.isFreeformLetterboxingForCameraAllowed(mActivity));
+            robot.checkIsFreeformLetterboxingForCameraAllowed(false);
+        });
     }
 
     @Test
@@ -237,519 +238,603 @@
     @DisableFlags(FLAG_ENABLE_CAMERA_COMPAT_FOR_DESKTOP_WINDOWING_OPT_OUT)
     @EnableCompatChanges({OVERRIDE_CAMERA_COMPAT_ENABLE_FREEFORM_WINDOWING_TREATMENT})
     public void testIsFreeformLetterboxingForCameraAllowed_optInFreeformCameraRunning_true() {
-        configureActivity(SCREEN_ORIENTATION_PORTRAIT);
+        runTestScenario((robot) -> {
+            robot.configureActivity(SCREEN_ORIENTATION_PORTRAIT);
 
-        onCameraOpened(CAMERA_ID_1, TEST_PACKAGE_1);
+            robot.onCameraOpened(CAMERA_ID_1, TEST_PACKAGE_1);
 
-        assertTrue(mCameraCompatFreeformPolicy.isFreeformLetterboxingForCameraAllowed(mActivity));
+            robot.checkIsFreeformLetterboxingForCameraAllowed(true);
+        });
     }
 
     @Test
     @EnableFlags(FLAG_ENABLE_CAMERA_COMPAT_FOR_DESKTOP_WINDOWING)
     @EnableCompatChanges({OVERRIDE_CAMERA_COMPAT_ENABLE_FREEFORM_WINDOWING_TREATMENT})
     public void testFullscreen_doesNotActivateCameraCompatMode() {
-        configureActivity(SCREEN_ORIENTATION_PORTRAIT, WINDOWING_MODE_FULLSCREEN);
-        doReturn(false).when(mActivity).inFreeformWindowingMode();
+        runTestScenario((robot) -> {
+            robot.configureActivity(SCREEN_ORIENTATION_PORTRAIT, WINDOWING_MODE_FULLSCREEN);
+            robot.setInFreeformWindowingMode(false);
 
-        onCameraOpened(CAMERA_ID_1, TEST_PACKAGE_1);
+            robot.onCameraOpened(CAMERA_ID_1, TEST_PACKAGE_1);
 
-        assertNotInCameraCompatMode();
+            robot.assertNotInCameraCompatMode();
+        });
     }
 
     @Test
     @EnableFlags(FLAG_ENABLE_CAMERA_COMPAT_FOR_DESKTOP_WINDOWING)
     @EnableCompatChanges({OVERRIDE_CAMERA_COMPAT_ENABLE_FREEFORM_WINDOWING_TREATMENT})
     public void testOrientationUnspecified_doesNotActivateCameraCompatMode() {
-        configureActivity(SCREEN_ORIENTATION_UNSPECIFIED);
+        runTestScenario((robot) -> {
+            robot.configureActivity(SCREEN_ORIENTATION_UNSPECIFIED);
 
-        assertNotInCameraCompatMode();
+            robot.assertNotInCameraCompatMode();
+        });
     }
 
     @Test
     @EnableFlags(FLAG_ENABLE_CAMERA_COMPAT_FOR_DESKTOP_WINDOWING)
     @EnableCompatChanges({OVERRIDE_CAMERA_COMPAT_ENABLE_FREEFORM_WINDOWING_TREATMENT})
     public void testNoCameraConnection_doesNotActivateCameraCompatMode() {
-        configureActivity(SCREEN_ORIENTATION_PORTRAIT);
-        assertNotInCameraCompatMode();
+        runTestScenario((robot) -> {
+            robot.configureActivity(SCREEN_ORIENTATION_PORTRAIT);
+
+            robot.assertNotInCameraCompatMode();
+        });
     }
 
     @Test
     @EnableFlags(FLAG_ENABLE_CAMERA_COMPAT_FOR_DESKTOP_WINDOWING)
     @EnableCompatChanges({OVERRIDE_CAMERA_COMPAT_ENABLE_FREEFORM_WINDOWING_TREATMENT})
-    public void testCameraConnected_deviceInPortrait_portraitCameraCompatMode() throws Exception {
-        configureActivity(SCREEN_ORIENTATION_PORTRAIT);
-        setDisplayRotation(ROTATION_0);
+    public void testCameraConnected_deviceInPortrait_portraitCameraCompatMode() {
+        runTestScenario((robot) -> {
+            robot.configureActivity(SCREEN_ORIENTATION_PORTRAIT);
+            robot.activity().rotateDisplayForTopActivity(ROTATION_0);
 
-        onCameraOpened(CAMERA_ID_1, TEST_PACKAGE_1);
+            robot.onCameraOpened(CAMERA_ID_1, TEST_PACKAGE_1);
 
-        assertInCameraCompatMode(CAMERA_COMPAT_FREEFORM_PORTRAIT_DEVICE_IN_PORTRAIT);
-        assertActivityRefreshRequested(/* refreshRequested */ false);
+            robot.assertInCameraCompatMode(CAMERA_COMPAT_FREEFORM_PORTRAIT_DEVICE_IN_PORTRAIT);
+            robot.assertActivityRefreshRequested(/* refreshRequested */ false);
+        });
     }
 
     @Test
     @EnableFlags(FLAG_ENABLE_CAMERA_COMPAT_FOR_DESKTOP_WINDOWING)
     @EnableCompatChanges({OVERRIDE_CAMERA_COMPAT_ENABLE_FREEFORM_WINDOWING_TREATMENT})
-    public void testCameraConnected_deviceInLandscape_portraitCameraCompatMode() throws Exception {
-        configureActivity(SCREEN_ORIENTATION_PORTRAIT);
-        setDisplayRotation(ROTATION_270);
+    public void testCameraConnected_deviceInLandscape_portraitCameraCompatMode() {
+        runTestScenario((robot) -> {
+            robot.configureActivity(SCREEN_ORIENTATION_PORTRAIT);
+            robot.activity().rotateDisplayForTopActivity(ROTATION_270);
 
-        onCameraOpened(CAMERA_ID_1, TEST_PACKAGE_1);
+            robot.onCameraOpened(CAMERA_ID_1, TEST_PACKAGE_1);
 
-        assertInCameraCompatMode(CAMERA_COMPAT_FREEFORM_PORTRAIT_DEVICE_IN_LANDSCAPE);
-        assertActivityRefreshRequested(/* refreshRequested */ false);
+            robot.assertInCameraCompatMode(CAMERA_COMPAT_FREEFORM_PORTRAIT_DEVICE_IN_LANDSCAPE);
+            robot.assertActivityRefreshRequested(/* refreshRequested */ false);
+        });
     }
 
     @Test
     @EnableFlags(FLAG_ENABLE_CAMERA_COMPAT_FOR_DESKTOP_WINDOWING)
     @EnableCompatChanges({OVERRIDE_CAMERA_COMPAT_ENABLE_FREEFORM_WINDOWING_TREATMENT})
-    public void testCameraConnected_deviceInPortrait_landscapeCameraCompatMode() throws Exception {
-        configureActivity(SCREEN_ORIENTATION_LANDSCAPE);
-        setDisplayRotation(ROTATION_0);
+    public void testCameraConnected_deviceInPortrait_landscapeCameraCompatMode() {
+        runTestScenario((robot) -> {
+            robot.configureActivity(SCREEN_ORIENTATION_LANDSCAPE);
+            robot.activity().rotateDisplayForTopActivity(ROTATION_0);
 
-        onCameraOpened(CAMERA_ID_1, TEST_PACKAGE_1);
+            robot.onCameraOpened(CAMERA_ID_1, TEST_PACKAGE_1);
 
-        assertInCameraCompatMode(CAMERA_COMPAT_FREEFORM_LANDSCAPE_DEVICE_IN_PORTRAIT);
-        assertActivityRefreshRequested(/* refreshRequested */ false);
+            robot.assertInCameraCompatMode(CAMERA_COMPAT_FREEFORM_LANDSCAPE_DEVICE_IN_PORTRAIT);
+            robot.assertActivityRefreshRequested(/* refreshRequested */ false);
+        });
     }
 
     @Test
     @EnableFlags(FLAG_ENABLE_CAMERA_COMPAT_FOR_DESKTOP_WINDOWING)
     @EnableCompatChanges({OVERRIDE_CAMERA_COMPAT_ENABLE_FREEFORM_WINDOWING_TREATMENT})
-    public void testCameraConnected_deviceInLandscape_landscapeCameraCompatMode() throws Exception {
-        configureActivity(SCREEN_ORIENTATION_LANDSCAPE);
-        setDisplayRotation(ROTATION_270);
+    public void testCameraConnected_deviceInLandscape_landscapeCameraCompatMode() {
+        runTestScenario((robot) -> {
+            robot.configureActivity(SCREEN_ORIENTATION_LANDSCAPE);
+            robot.activity().rotateDisplayForTopActivity(ROTATION_270);
 
-        onCameraOpened(CAMERA_ID_1, TEST_PACKAGE_1);
+            robot.onCameraOpened(CAMERA_ID_1, TEST_PACKAGE_1);
 
-        assertInCameraCompatMode(CAMERA_COMPAT_FREEFORM_LANDSCAPE_DEVICE_IN_LANDSCAPE);
-        assertActivityRefreshRequested(/* refreshRequested */ false);
+            robot.assertInCameraCompatMode(CAMERA_COMPAT_FREEFORM_LANDSCAPE_DEVICE_IN_LANDSCAPE);
+            robot.assertActivityRefreshRequested(/* refreshRequested */ false);
+        });
     }
 
     @Test
     @EnableFlags(FLAG_ENABLE_CAMERA_COMPAT_FOR_DESKTOP_WINDOWING)
     @EnableCompatChanges({OVERRIDE_CAMERA_COMPAT_ENABLE_FREEFORM_WINDOWING_TREATMENT})
-    public void testCameraReconnected_cameraCompatModeAndRefresh() throws Exception {
-        configureActivity(SCREEN_ORIENTATION_PORTRAIT);
-        setDisplayRotation(ROTATION_270);
+    public void testCameraReconnected_cameraCompatModeAndRefresh() {
+        runTestScenario((robot) -> {
+            robot.configureActivity(SCREEN_ORIENTATION_PORTRAIT);
+            robot.activity().rotateDisplayForTopActivity(ROTATION_270);
 
-        onCameraOpened(CAMERA_ID_1, TEST_PACKAGE_1);
-        callOnActivityConfigurationChanging(mActivity, /* letterboxNew= */ true,
+            robot.onCameraOpened(CAMERA_ID_1, TEST_PACKAGE_1);
+            robot.callOnActivityConfigurationChanging(/* letterboxNew= */ true,
                 /* lastLetterbox= */ false);
-        assertActivityRefreshRequested(/* refreshRequested */ true);
-        onCameraClosed(CAMERA_ID_1);
-        onCameraOpened(CAMERA_ID_1, TEST_PACKAGE_1);
-        // Activity is letterboxed from the previous configuration change.
-        callOnActivityConfigurationChanging(mActivity, /* letterboxNew= */ true,
-                /* lastLetterbox= */ true);
+            robot.assertActivityRefreshRequested(/* refreshRequested */ true);
+            robot.onCameraClosed(CAMERA_ID_1);
+            robot.onCameraOpened(CAMERA_ID_1, TEST_PACKAGE_1);
+            // Activity is letterboxed from the previous configuration change.
+            robot.callOnActivityConfigurationChanging(/* letterboxNew= */ true,
+                    /* lastLetterbox= */ true);
 
-        assertInCameraCompatMode(CAMERA_COMPAT_FREEFORM_PORTRAIT_DEVICE_IN_LANDSCAPE);
-        assertActivityRefreshRequested(/* refreshRequested */ true);
+            robot.assertInCameraCompatMode(CAMERA_COMPAT_FREEFORM_PORTRAIT_DEVICE_IN_LANDSCAPE);
+            robot.assertActivityRefreshRequested(/* refreshRequested */ true);
+        });
     }
 
     @Test
     @EnableFlags(FLAG_ENABLE_CAMERA_COMPAT_FOR_DESKTOP_WINDOWING)
     @EnableCompatChanges({OVERRIDE_CAMERA_COMPAT_ENABLE_FREEFORM_WINDOWING_TREATMENT})
     public void testCameraOpenedForDifferentPackage_notInCameraCompatMode() {
-        configureActivity(SCREEN_ORIENTATION_PORTRAIT);
+        runTestScenario((robot) -> {
+            robot.configureActivity(SCREEN_ORIENTATION_PORTRAIT);
 
-        onCameraOpened(CAMERA_ID_1, TEST_PACKAGE_2);
+            robot.onCameraOpened(CAMERA_ID_1, TEST_PACKAGE_2);
 
-        assertNotInCameraCompatMode();
+            robot.assertNotInCameraCompatMode();
+        });
     }
 
     @Test
     @EnableFlags(FLAG_ENABLE_CAMERA_COMPAT_FOR_DESKTOP_WINDOWING)
     @DisableFlags(FLAG_ENABLE_CAMERA_COMPAT_FOR_DESKTOP_WINDOWING_OPT_OUT)
     public void testShouldApplyCameraCompatFreeformTreatment_overrideNotEnabled_returnsFalse() {
-        configureActivity(SCREEN_ORIENTATION_PORTRAIT);
+        runTestScenario((robot) -> {
+            robot.configureActivity(SCREEN_ORIENTATION_PORTRAIT);
 
-        onCameraOpened(CAMERA_ID_1, TEST_PACKAGE_1);
+            robot.onCameraOpened(CAMERA_ID_1, TEST_PACKAGE_1);
 
-        assertFalse(mCameraCompatFreeformPolicy.isTreatmentEnabledForActivity(mActivity,
-                /* checkOrientation */ true));
+            robot.checkIsCameraCompatTreatmentActiveForTopActivity(false);
+        });
     }
 
     @Test
     @EnableFlags({FLAG_ENABLE_CAMERA_COMPAT_FOR_DESKTOP_WINDOWING,
             FLAG_ENABLE_CAMERA_COMPAT_FOR_DESKTOP_WINDOWING_OPT_OUT})
     public void testShouldApplyCameraCompatFreeformTreatment_notOptedOut_returnsTrue() {
-        configureActivity(SCREEN_ORIENTATION_PORTRAIT);
+        runTestScenario((robot) -> {
+            robot.configureActivity(SCREEN_ORIENTATION_PORTRAIT);
 
-        onCameraOpened(CAMERA_ID_1, TEST_PACKAGE_1);
+            robot.onCameraOpened(CAMERA_ID_1, TEST_PACKAGE_1);
 
-        assertTrue(mCameraCompatFreeformPolicy.isTreatmentEnabledForActivity(mActivity,
-                /* checkOrientation */ true));
+            robot.checkIsCameraCompatTreatmentActiveForTopActivity(true);
+        });
     }
 
     @Test
     @EnableFlags(FLAG_ENABLE_CAMERA_COMPAT_FOR_DESKTOP_WINDOWING)
     @EnableCompatChanges(OVERRIDE_CAMERA_COMPAT_ENABLE_FREEFORM_WINDOWING_TREATMENT)
     public void testShouldApplyCameraCompatFreeformTreatment_enabledByOverride_returnsTrue() {
-        configureActivity(SCREEN_ORIENTATION_PORTRAIT);
+        runTestScenario((robot) -> {
+            robot.configureActivity(SCREEN_ORIENTATION_PORTRAIT);
 
-        onCameraOpened(CAMERA_ID_1, TEST_PACKAGE_1);
+            robot.onCameraOpened(CAMERA_ID_1, TEST_PACKAGE_1);
 
-        assertTrue(mActivity.info
-                .isChangeEnabled(OVERRIDE_CAMERA_COMPAT_ENABLE_FREEFORM_WINDOWING_TREATMENT));
-        assertTrue(mCameraCompatFreeformPolicy.isTreatmentEnabledForActivity(mActivity,
-                /* checkOrientation */ true));
+            robot.checkIsCameraCompatTreatmentActiveForTopActivity(true);
+        });
     }
 
     @Test
     @EnableFlags(FLAG_ENABLE_CAMERA_COMPAT_FOR_DESKTOP_WINDOWING)
     @EnableCompatChanges({OVERRIDE_CAMERA_COMPAT_ENABLE_FREEFORM_WINDOWING_TREATMENT})
     public void testShouldRefreshActivity_appBoundsChanged_returnsTrue() {
-        configureActivity(SCREEN_ORIENTATION_PORTRAIT);
-        Configuration oldConfiguration = createConfiguration(/* letterbox= */ false);
-        Configuration newConfiguration = createConfiguration(/* letterbox= */ true);
-        onCameraOpened(CAMERA_ID_1, TEST_PACKAGE_1);
+        runTestScenario((robot) -> {
+            robot.configureActivity(SCREEN_ORIENTATION_PORTRAIT);
 
-        assertTrue(mCameraCompatFreeformPolicy.shouldRefreshActivity(mActivity, newConfiguration,
-                oldConfiguration));
+            robot.onCameraOpened(CAMERA_ID_1, TEST_PACKAGE_1);
+
+            robot.checkShouldRefreshActivity(/* expected= */ true,
+                    robot.createConfiguration(/* letterbox= */ true, /* rotation= */ 0),
+                    robot.createConfiguration(/* letterbox= */ false, /* rotation= */ 0));
+        });
     }
 
     @Test
     @EnableFlags(FLAG_ENABLE_CAMERA_COMPAT_FOR_DESKTOP_WINDOWING)
     @EnableCompatChanges({OVERRIDE_CAMERA_COMPAT_ENABLE_FREEFORM_WINDOWING_TREATMENT})
     public void testShouldRefreshActivity_displayRotationChanged_returnsTrue() {
-        configureActivity(SCREEN_ORIENTATION_PORTRAIT);
-        Configuration oldConfiguration = createConfiguration(/* letterbox= */ true);
-        Configuration newConfiguration = createConfiguration(/* letterbox= */ true);
+        runTestScenario((robot) -> {
+            robot.configureActivity(SCREEN_ORIENTATION_PORTRAIT);
 
-        oldConfiguration.windowConfiguration.setDisplayRotation(0);
-        newConfiguration.windowConfiguration.setDisplayRotation(90);
-        onCameraOpened(CAMERA_ID_1, TEST_PACKAGE_1);
+            robot.onCameraOpened(CAMERA_ID_1, TEST_PACKAGE_1);
 
-        assertTrue(mCameraCompatFreeformPolicy.shouldRefreshActivity(mActivity, newConfiguration,
-                oldConfiguration));
+            robot.checkShouldRefreshActivity(/* expected= */ true,
+                    robot.createConfiguration(/* letterbox= */ true, /* rotation= */ 90),
+                    robot.createConfiguration(/* letterbox= */ true, /* rotation= */ 0));
+        });
     }
 
     @Test
     @EnableFlags(FLAG_ENABLE_CAMERA_COMPAT_FOR_DESKTOP_WINDOWING)
     @EnableCompatChanges({OVERRIDE_CAMERA_COMPAT_ENABLE_FREEFORM_WINDOWING_TREATMENT})
     public void testShouldRefreshActivity_appBoundsNorDisplayChanged_returnsFalse() {
-        configureActivity(SCREEN_ORIENTATION_PORTRAIT);
-        Configuration oldConfiguration = createConfiguration(/* letterbox= */ true);
-        Configuration newConfiguration = createConfiguration(/* letterbox= */ true);
+        runTestScenario((robot) -> {
+            robot.configureActivity(SCREEN_ORIENTATION_PORTRAIT);
 
-        oldConfiguration.windowConfiguration.setDisplayRotation(0);
-        newConfiguration.windowConfiguration.setDisplayRotation(0);
-        onCameraOpened(CAMERA_ID_1, TEST_PACKAGE_1);
+            robot.onCameraOpened(CAMERA_ID_1, TEST_PACKAGE_1);
 
-        assertFalse(mCameraCompatFreeformPolicy.shouldRefreshActivity(mActivity, newConfiguration,
-                oldConfiguration));
+            robot.checkShouldRefreshActivity(/* expected= */ false,
+                    robot.createConfiguration(/* letterbox= */ true, /* rotation= */ 0),
+                    robot.createConfiguration(/* letterbox= */ true, /* rotation= */ 0));
+        });
     }
 
     @Test
     @EnableFlags(FLAG_ENABLE_CAMERA_COMPAT_FOR_DESKTOP_WINDOWING)
     @EnableCompatChanges({OVERRIDE_CAMERA_COMPAT_ENABLE_FREEFORM_WINDOWING_TREATMENT})
-    public void testOnActivityConfigurationChanging_refreshDisabledViaFlag_noRefresh()
-            throws Exception {
-        configureActivity(SCREEN_ORIENTATION_PORTRAIT);
+    public void testOnActivityConfigurationChanging_refreshDisabledViaFlag_noRefresh() {
+        runTestScenario((robot) -> {
+            robot.configureActivity(SCREEN_ORIENTATION_PORTRAIT);
+            robot.activity().setShouldRefreshActivityForCameraCompat(false);
 
-        doReturn(false).when(mActivity.mAppCompatController.getCameraOverrides())
-                .shouldRefreshActivityForCameraCompat();
+            robot.onCameraOpened(CAMERA_ID_1, TEST_PACKAGE_1);
+            robot.callOnActivityConfigurationChanging();
 
-        onCameraOpened(CAMERA_ID_1, TEST_PACKAGE_1);
-        callOnActivityConfigurationChanging(mActivity);
-
-        assertActivityRefreshRequested(/* refreshRequested */ false);
+            robot.assertActivityRefreshRequested(/* refreshRequested */ false);
+        });
     }
 
     @Test
     @EnableFlags(FLAG_ENABLE_CAMERA_COMPAT_FOR_DESKTOP_WINDOWING)
     @EnableCompatChanges({OVERRIDE_CAMERA_COMPAT_ENABLE_FREEFORM_WINDOWING_TREATMENT})
-    public void testOnActivityConfigurationChanging_cycleThroughStopDisabled() throws Exception {
-        when(mAppCompatConfiguration.isCameraCompatRefreshCycleThroughStopEnabled())
-                .thenReturn(false);
+    public void testOnActivityConfigurationChanging_cycleThroughStopDisabled() {
+        runTestScenario((robot) -> {
+            robot.configureActivity(SCREEN_ORIENTATION_PORTRAIT);
+            robot.conf().enableCameraCompatRefreshCycleThroughStop(false);
 
-        configureActivity(SCREEN_ORIENTATION_PORTRAIT);
+            robot.onCameraOpened(CAMERA_ID_1, TEST_PACKAGE_1);
+            robot.callOnActivityConfigurationChanging();
 
-        onCameraOpened(CAMERA_ID_1, TEST_PACKAGE_1);
-        callOnActivityConfigurationChanging(mActivity);
-
-        assertActivityRefreshRequested(/* refreshRequested */ true, /* cycleThroughStop */ false);
+            robot.assertActivityRefreshRequested(/* refreshRequested */ true,
+                    /* cycleThroughStop */ false);
+        });
     }
 
     @Test
     @EnableFlags(FLAG_ENABLE_CAMERA_COMPAT_FOR_DESKTOP_WINDOWING)
     @EnableCompatChanges({OVERRIDE_CAMERA_COMPAT_ENABLE_FREEFORM_WINDOWING_TREATMENT})
-    public void testOnActivityConfigurationChanging_cycleThroughStopDisabledForApp()
-            throws Exception {
-        configureActivity(SCREEN_ORIENTATION_PORTRAIT);
-        doReturn(true).when(mActivity.mAppCompatController.getCameraOverrides())
-                .shouldRefreshActivityViaPauseForCameraCompat();
+    public void testOnActivityConfigurationChanging_cycleThroughStopDisabledForApp() {
+        runTestScenario((robot) -> {
+            robot.configureActivity(SCREEN_ORIENTATION_PORTRAIT);
+            robot.setShouldRefreshActivityViaPause(true);
 
-        onCameraOpened(CAMERA_ID_1, TEST_PACKAGE_1);
-        callOnActivityConfigurationChanging(mActivity);
+            robot.onCameraOpened(CAMERA_ID_1, TEST_PACKAGE_1);
+            robot.callOnActivityConfigurationChanging();
 
-        assertActivityRefreshRequested(/* refreshRequested */ true, /* cycleThroughStop */ false);
+            robot.assertActivityRefreshRequested(/* refreshRequested */ true,
+                    /* cycleThroughStop */ false);
+        });
     }
 
     @Test
     @EnableFlags(FLAG_ENABLE_CAMERA_COMPAT_FOR_DESKTOP_WINDOWING)
     @EnableCompatChanges({OVERRIDE_CAMERA_COMPAT_ENABLE_FREEFORM_WINDOWING_TREATMENT})
     public void testGetCameraCompatAspectRatio_activityNotInCameraCompat_returnsDefaultAspRatio() {
-        configureActivity(SCREEN_ORIENTATION_FULL_USER);
+        runTestScenario((robot) -> {
+            robot.configureActivity(SCREEN_ORIENTATION_FULL_USER);
 
-        onCameraOpened(CAMERA_ID_1, TEST_PACKAGE_1);
-        callOnActivityConfigurationChanging(mActivity);
+            robot.onCameraOpened(CAMERA_ID_1, TEST_PACKAGE_1);
+            robot.callOnActivityConfigurationChanging();
 
-        assertEquals(MIN_FIXED_ORIENTATION_LETTERBOX_ASPECT_RATIO,
-                mCameraCompatFreeformPolicy.getCameraCompatAspectRatio(mActivity),
-                /* delta= */ 0.001);
+            robot.checkCameraCompatAspectRatioEquals(MIN_FIXED_ORIENTATION_LETTERBOX_ASPECT_RATIO);
+        });
     }
 
     @Test
     @EnableFlags(FLAG_ENABLE_CAMERA_COMPAT_FOR_DESKTOP_WINDOWING)
     @EnableCompatChanges({OVERRIDE_CAMERA_COMPAT_ENABLE_FREEFORM_WINDOWING_TREATMENT})
     public void testGetCameraCompatAspectRatio_activityInCameraCompat_returnsConfigAspectRatio() {
-        configureActivity(SCREEN_ORIENTATION_PORTRAIT);
-        final float configAspectRatio = 1.5f;
-        mWm.mAppCompatConfiguration.setCameraCompatAspectRatio(configAspectRatio);
+        runTestScenario((robot) -> {
+            robot.configureActivity(SCREEN_ORIENTATION_PORTRAIT);
+            final float configAspectRatio = 1.5f;
+            robot.conf().setCameraCompatAspectRatio(configAspectRatio);
 
-        onCameraOpened(CAMERA_ID_1, TEST_PACKAGE_1);
-        callOnActivityConfigurationChanging(mActivity);
+            robot.onCameraOpened(CAMERA_ID_1, TEST_PACKAGE_1);
+            robot.callOnActivityConfigurationChanging();
 
-        assertEquals(configAspectRatio,
-                mCameraCompatFreeformPolicy.getCameraCompatAspectRatio(mActivity),
-                /* delta= */ 0.001);
+            robot.checkCameraCompatAspectRatioEquals(configAspectRatio);
+        });
     }
 
     @Test
     @EnableFlags(FLAG_ENABLE_CAMERA_COMPAT_FOR_DESKTOP_WINDOWING)
     @EnableCompatChanges({OVERRIDE_CAMERA_COMPAT_ENABLE_FREEFORM_WINDOWING_TREATMENT})
     public void testGetCameraCompatAspectRatio_inCameraCompatPerAppOverride_returnDefAspectRatio() {
-        configureActivity(SCREEN_ORIENTATION_PORTRAIT);
-        final float configAspectRatio = 1.5f;
-        mWm.mAppCompatConfiguration.setCameraCompatAspectRatio(configAspectRatio);
-        doReturn(true).when(mActivity.mAppCompatController.getCameraOverrides())
-                .isOverrideMinAspectRatioForCameraEnabled();
+        runTestScenario((robot) -> {
+            robot.configureActivity(SCREEN_ORIENTATION_PORTRAIT);
+            robot.conf().setCameraCompatAspectRatio(1.5f);
+            robot.setOverrideMinAspectRatioEnabled(true);
 
-        onCameraOpened(CAMERA_ID_1, TEST_PACKAGE_1);
-        callOnActivityConfigurationChanging(mActivity);
+            robot.onCameraOpened(CAMERA_ID_1, TEST_PACKAGE_1);
+            robot.callOnActivityConfigurationChanging();
 
-        assertEquals(MIN_FIXED_ORIENTATION_LETTERBOX_ASPECT_RATIO,
-                mCameraCompatFreeformPolicy.getCameraCompatAspectRatio(mActivity),
-                /* delta= */ 0.001);
+            robot.checkCameraCompatAspectRatioEquals(MIN_FIXED_ORIENTATION_LETTERBOX_ASPECT_RATIO);
+        });
     }
 
     @Test
     @EnableFlags(FLAG_ENABLE_CAMERA_COMPAT_FOR_DESKTOP_WINDOWING)
     @EnableCompatChanges({OVERRIDE_CAMERA_COMPAT_ENABLE_FREEFORM_WINDOWING_TREATMENT})
-    public void testOnCameraOpened_portraitActivity_sandboxesDisplayRotationAndUpdatesApp() throws
-            Exception {
-        configureActivity(SCREEN_ORIENTATION_PORTRAIT);
-        setDisplayRotation(ROTATION_270);
+    public void testOnCameraOpened_portraitActivity_sandboxesDisplayRotationAndUpdatesApp() {
+        runTestScenario((robot) -> {
+            robot.configureActivity(SCREEN_ORIENTATION_PORTRAIT);
+            robot.activity().rotateDisplayForTopActivity(ROTATION_270);
 
-        onCameraOpened(CAMERA_ID_1, TEST_PACKAGE_1);
+            robot.onCameraOpened(CAMERA_ID_1, TEST_PACKAGE_1);
 
-        // This is a portrait rotation for a device with portrait natural orientation (most common,
-        // currently the only one supported).
-        assertCompatibilityInfoSentWithDisplayRotation(ROTATION_0);
+            // This is a portrait rotation for a device with portrait natural orientation (most
+            // common, currently the only one supported).
+            robot.assertCompatibilityInfoSentWithDisplayRotation(ROTATION_0);
+        });
     }
 
     @Test
     @EnableFlags(FLAG_ENABLE_CAMERA_COMPAT_FOR_DESKTOP_WINDOWING)
     @EnableCompatChanges({OVERRIDE_CAMERA_COMPAT_ENABLE_FREEFORM_WINDOWING_TREATMENT})
-    public void testOnCameraOpened_landscapeActivity_sandboxesDisplayRotationAndUpdatesApp() throws
-            Exception {
-        configureActivity(SCREEN_ORIENTATION_LANDSCAPE);
-        setDisplayRotation(ROTATION_0);
+    public void testOnCameraOpened_landscapeActivity_sandboxesDisplayRotationAndUpdatesApp() {
+        runTestScenario((robot) -> {
+            robot.configureActivity(SCREEN_ORIENTATION_LANDSCAPE);
+            robot.activity().rotateDisplayForTopActivity(ROTATION_0);
 
-        onCameraOpened(CAMERA_ID_1, TEST_PACKAGE_1);
+            robot.onCameraOpened(CAMERA_ID_1, TEST_PACKAGE_1);
 
-        // This is a landscape rotation for a device with portrait natural orientation (most common,
-        // currently the only one supported).
-        assertCompatibilityInfoSentWithDisplayRotation(ROTATION_90);
+            // This is a landscape rotation for a device with portrait natural orientation (most
+            // common, currently the only one supported).
+            robot.assertCompatibilityInfoSentWithDisplayRotation(ROTATION_90);
+        });
     }
 
-    private void setupAppCompatConfiguration() {
-        mAppCompatConfiguration = mDisplayContent.mWmService.mAppCompatConfiguration;
-        spyOn(mAppCompatConfiguration);
-        when(mAppCompatConfiguration.isCameraCompatTreatmentEnabled()).thenReturn(true);
-        when(mAppCompatConfiguration.isCameraCompatTreatmentEnabledAtBuildTime()).thenReturn(true);
-        when(mAppCompatConfiguration.isCameraCompatRefreshEnabled()).thenReturn(true);
-        when(mAppCompatConfiguration.isCameraCompatSplitScreenAspectRatioEnabled())
-                .thenReturn(false);
-        when(mAppCompatConfiguration.isCameraCompatRefreshCycleThroughStopEnabled())
-                .thenReturn(true);
+    /**
+     * Runs a test scenario providing a Robot.
+     */
+    void runTestScenario(@NonNull Consumer<CameraCompatFreeformPolicyRobotTests> consumer) {
+        final CameraCompatFreeformPolicyRobotTests robot =
+                new CameraCompatFreeformPolicyRobotTests(mWm, mAtm, mSupervisor, this);
+        consumer.accept(robot);
     }
 
-    private void setupCameraManager() {
-        final CameraManager mockCameraManager = mock(CameraManager.class);
-        doAnswer(invocation -> {
-            mCameraAvailabilityCallback = invocation.getArgument(1);
-            return null;
-        }).when(mockCameraManager).registerAvailabilityCallback(
-                any(Executor.class), any(CameraManager.AvailabilityCallback.class));
+    private static class CameraCompatFreeformPolicyRobotTests extends AppCompatRobotBase {
+        private final WindowTestsBase mWindowTestsBase;
 
-        when(mContext.getSystemService(CameraManager.class)).thenReturn(mockCameraManager);
-    }
+        private CameraManager.AvailabilityCallback mCameraAvailabilityCallback;
 
-    private void setupHandler() {
-        final Handler handler = mDisplayContent.mWmService.mH;
-        spyOn(handler);
+        CameraCompatFreeformPolicyRobotTests(@NonNull WindowManagerService wm,
+                @NonNull ActivityTaskManagerService atm,
+                @NonNull ActivityTaskSupervisor supervisor,
+                @NonNull WindowTestsBase windowTestsBase) {
+            super(wm, atm, supervisor);
+            mWindowTestsBase = windowTestsBase;
+            setupCameraManager();
+            setupAppCompatConfiguration();
+        }
 
-        when(handler.postDelayed(any(Runnable.class), anyLong())).thenAnswer(
-                invocation -> {
-                    ((Runnable) invocation.getArgument(0)).run();
-                    return null;
-                });
-    }
+        @Override
+        void onPostDisplayContentCreation(@NonNull DisplayContent displayContent) {
+            super.onPostDisplayContentCreation(displayContent);
+            spyOn(displayContent.mAppCompatCameraPolicy);
+            if (displayContent.mAppCompatCameraPolicy.mCameraCompatFreeformPolicy != null) {
+                spyOn(displayContent.mAppCompatCameraPolicy.mCameraCompatFreeformPolicy);
+            }
+        }
 
-    private void configureActivity(@ScreenOrientation int activityOrientation) {
-        configureActivity(activityOrientation, WINDOWING_MODE_FREEFORM);
-    }
+        @Override
+        void onPostActivityCreation(@NonNull ActivityRecord activity) {
+            super.onPostActivityCreation(activity);
+            setupCameraManager();
+            setupHandler();
+            setupMockApplicationThread();
+        }
 
-    private void configureActivity(@ScreenOrientation int activityOrientation,
-            @WindowingMode int windowingMode) {
-        configureActivityAndDisplay(activityOrientation, ORIENTATION_PORTRAIT, windowingMode);
-    }
+        private void setupMockApplicationThread() {
+            IApplicationThread mockApplicationThread = mock(IApplicationThread.class);
+            spyOn(activity().top().app);
+            doReturn(mockApplicationThread).when(activity().top().app).getThread();
+        }
 
-    private void configureActivityAndDisplay(@ScreenOrientation int activityOrientation,
-            @Orientation int naturalOrientation, @WindowingMode int windowingMode) {
-        setupDisplayContent(naturalOrientation);
-        final Task task = setupTask(windowingMode);
-        setupActivity(task, activityOrientation, windowingMode);
-        setupMockApplicationThread();
+        private Configuration createConfiguration(boolean letterbox, int rotation) {
+            final Configuration configuration = createConfiguration(letterbox);
+            configuration.windowConfiguration.setDisplayRotation(rotation);
+            return configuration;
+        }
 
-        mCameraCompatFreeformPolicy = mDisplayContent.mAppCompatCameraPolicy
-                .mCameraCompatFreeformPolicy;
-    }
+        private Configuration createConfiguration(boolean letterbox) {
+            final Configuration configuration = new Configuration();
+            Rect bounds = letterbox ? new Rect(/*left*/ 300, /*top*/ 0, /*right*/ 700, /*bottom*/
+                    600)
+                    : new Rect(/*left*/ 0, /*top*/ 0, /*right*/ 1000, /*bottom*/ 600);
+            configuration.windowConfiguration.setAppBounds(bounds);
+            return configuration;
+        }
 
-    private void setupDisplayContent(@Orientation int naturalOrientation) {
-        // Create a new DisplayContent so that the flag values create the camera freeform policy.
-        mDisplayContent = new TestDisplayContent.Builder(mAtm, mDisplayContent.getSurfaceWidth(),
-                mDisplayContent.getSurfaceHeight()).build();
-        mDisplayContent.setIgnoreOrientationRequest(true);
-        setDisplayRotation(ROTATION_90);
-        doReturn(naturalOrientation).when(mDisplayContent).getNaturalOrientation();
-    }
+        private void setupAppCompatConfiguration() {
+            applyOnConf((c) -> {
+                c.enableCameraCompatTreatment(true);
+                c.enableCameraCompatTreatmentAtBuildTime(true);
+                c.enableCameraCompatRefresh(true);
+                c.enableCameraCompatRefreshCycleThroughStop(true);
+                c.enableCameraCompatSplitScreenAspectRatio(false);
+            });
+        }
 
-    private Task setupTask(@WindowingMode int windowingMode) {
-        final TaskDisplayArea tda = mDisplayContent.getDefaultTaskDisplayArea();
-        spyOn(tda);
-        doReturn(true).when(tda).supportsNonResizableMultiWindow();
+        private void setupCameraManager() {
+            final CameraManager mockCameraManager = mock(CameraManager.class);
+            doAnswer(invocation -> {
+                mCameraAvailabilityCallback = invocation.getArgument(1);
+                return null;
+            }).when(mockCameraManager).registerAvailabilityCallback(
+                    any(Executor.class), any(CameraManager.AvailabilityCallback.class));
 
-        final Task task = new TaskBuilder(mSupervisor)
-                .setDisplay(mDisplayContent)
-                .setWindowingMode(windowingMode)
-                .build();
-        task.setBounds(0, 0, 1000, 500);
-        return task;
-    }
+            doReturn(mockCameraManager).when(mWindowTestsBase.mWm.mContext).getSystemService(
+                    CameraManager.class);
+        }
 
-    private void setupActivity(@NonNull Task task, @ScreenOrientation int activityOrientation,
-            @WindowingMode int windowingMode) {
-        mActivity = new ActivityBuilder(mAtm)
-                // Set the component to be that of the test class in order to enable compat changes
-                .setComponent(ComponentName.createRelative(mContext,
-                        com.android.server.wm.CameraCompatFreeformPolicyTests.class.getName()))
-                .setScreenOrientation(activityOrientation)
-                .setResizeMode(RESIZE_MODE_RESIZEABLE)
-                .setCreateTask(true)
-                .setOnTop(true)
-                .setTask(task)
-                .build();
-        mActivity.mAppCompatController.getSizeCompatModePolicy().clearSizeCompatMode();
+        private void setupHandler() {
+            final Handler handler = activity().top().mWmService.mH;
+            spyOn(handler);
 
-        spyOn(mActivity.mAppCompatController.getCameraOverrides());
-        spyOn(mActivity.info);
+            doAnswer(invocation -> {
+                ((Runnable) invocation.getArgument(0)).run();
+                return null;
+            }).when(handler).postDelayed(any(Runnable.class), anyLong());
+        }
 
-        doReturn(mActivity).when(mDisplayContent).topRunningActivity(anyBoolean());
-        doReturn(windowingMode == WINDOWING_MODE_FREEFORM).when(mActivity)
-                .inFreeformWindowingMode();
-    }
+        private void configureActivity(@ScreenOrientation int activityOrientation) {
+            configureActivity(activityOrientation, WINDOWING_MODE_FREEFORM);
+        }
 
-    private void onCameraOpened(@NonNull String cameraId, @NonNull String packageName) {
-        mCameraAvailabilityCallback.onCameraOpened(cameraId, packageName);
-        waitHandlerIdle(mDisplayContent.mWmService.mH);
-    }
+        private void configureActivity(@ScreenOrientation int activityOrientation,
+                @WindowingMode int windowingMode) {
+            configureActivityAndDisplay(activityOrientation, ORIENTATION_PORTRAIT, windowingMode);
+        }
 
-    private void onCameraClosed(@NonNull String cameraId) {
-        mCameraAvailabilityCallback.onCameraClosed(cameraId);
-        waitHandlerIdle(mDisplayContent.mWmService.mH);
-    }
+        private void configureActivityAndDisplay(@ScreenOrientation int activityOrientation,
+                @Orientation int naturalOrientation, @WindowingMode int windowingMode) {
+            applyOnActivity(a -> {
+                dw().allowEnterDesktopMode(true);
+                a.createActivityWithComponentInNewTaskAndDisplay();
+                a.setIgnoreOrientationRequest(true);
+                a.rotateDisplayForTopActivity(ROTATION_90);
+                a.configureTopActivity(/* minAspect */ -1, /* maxAspect */ -1,
+                        activityOrientation, /* isUnresizable */ false);
+                a.top().setWindowingMode(windowingMode);
+                a.displayContent().setWindowingMode(windowingMode);
+                a.setDisplayNaturalOrientation(naturalOrientation);
+                spyOn(a.top().mAppCompatController.getCameraOverrides());
+                spyOn(a.top().info);
+                doReturn(a.displayContent().getDisplayInfo()).when(
+                        a.displayContent().mWmService.mDisplayManagerInternal).getDisplayInfo(
+                        a.displayContent().mDisplayId);
+            });
+        }
 
-    private void assertInCameraCompatMode(@CameraCompatTaskInfo.FreeformCameraCompatMode int mode) {
-        assertEquals(mode, mCameraCompatFreeformPolicy.getCameraCompatMode(mActivity));
-    }
+        private void onCameraOpened(@NonNull String cameraId, @NonNull String packageName) {
+            mCameraAvailabilityCallback.onCameraOpened(cameraId, packageName);
+            waitHandlerIdle();
+        }
 
-    private void assertNotInCameraCompatMode() {
-        assertInCameraCompatMode(CAMERA_COMPAT_FREEFORM_NONE);
-    }
+        private void onCameraClosed(@NonNull String cameraId) {
+            mCameraAvailabilityCallback.onCameraClosed(cameraId);
+        }
 
-    private void assertActivityRefreshRequested(boolean refreshRequested) throws Exception {
-        assertActivityRefreshRequested(refreshRequested, /* cycleThroughStop*/ true);
-    }
+        private void waitHandlerIdle() {
+            mWindowTestsBase.waitHandlerIdle(activity().displayContent().mWmService.mH);
+        }
 
-    private void assertActivityRefreshRequested(boolean refreshRequested,
-            boolean cycleThroughStop) throws Exception {
-        verify(mActivity.mAppCompatController.getCameraOverrides(),
-                times(refreshRequested ? 1 : 0)).setIsRefreshRequested(true);
+        void setInFreeformWindowingMode(boolean inFreeform) {
+            doReturn(inFreeform).when(activity().top()).inFreeformWindowingMode();
+        }
 
-        final RefreshCallbackItem refreshCallbackItem =
-                new RefreshCallbackItem(mActivity.token, cycleThroughStop ? ON_STOP : ON_PAUSE);
-        final ResumeActivityItem resumeActivityItem = new ResumeActivityItem(mActivity.token,
-                /* isForward */ false, /* shouldSendCompatFakeFocus */ false);
+        void setShouldRefreshActivityViaPause(boolean enabled) {
+            doReturn(enabled).when(activity().top().mAppCompatController.getCameraOverrides())
+                    .shouldRefreshActivityViaPauseForCameraCompat();
+        }
 
-        verify(mActivity.mAtmService.getLifecycleManager(), times(refreshRequested ? 1 : 0))
-                .scheduleTransactionItems(mActivity.app.getThread(),
-                        refreshCallbackItem, resumeActivityItem);
-    }
+        void checkShouldRefreshActivity(boolean expected, Configuration newConfig,
+                Configuration oldConfig) {
+            assertEquals(expected, cameraCompatFreeformPolicy().shouldRefreshActivity(
+                    activity().top(),  newConfig, oldConfig));
+        }
 
-    private void callOnActivityConfigurationChanging(ActivityRecord activity) {
-        callOnActivityConfigurationChanging(activity, /* letterboxNew= */ true,
-                /* lastLetterbox= */false);
-    }
+        void checkCameraCompatPolicyNotCreated() {
+            assertNull(cameraCompatFreeformPolicy());
+        }
 
-    private void callOnActivityConfigurationChanging(ActivityRecord activity, boolean letterboxNew,
-            boolean lastLetterbox) {
-        mDisplayContent.mAppCompatCameraPolicy.mActivityRefresher
-                .onActivityConfigurationChanging(activity,
-                /* newConfig */ createConfiguration(letterboxNew),
-                /* lastReportedConfig */ createConfiguration(lastLetterbox));
-    }
+        void checkIsCameraRunningAndWindowingModeEligible(boolean expected) {
+            assertEquals(expected, cameraCompatFreeformPolicy()
+                    .isCameraRunningAndWindowingModeEligible(activity().top()));
+        }
 
-    private Configuration createConfiguration(boolean letterbox) {
-        final Configuration configuration = new Configuration();
-        Rect bounds = letterbox ? new Rect(/*left*/ 300, /*top*/ 0, /*right*/ 700, /*bottom*/ 600)
-                : new Rect(/*left*/ 0, /*top*/ 0, /*right*/ 1000, /*bottom*/ 600);
-        configuration.windowConfiguration.setAppBounds(bounds);
-        return configuration;
-    }
+        void checkIsFreeformLetterboxingForCameraAllowed(boolean expected) {
+            assertEquals(expected, cameraCompatFreeformPolicy()
+                    .isFreeformLetterboxingForCameraAllowed(activity().top()));
+        }
 
-    private void setDisplayRotation(@Surface.Rotation int displayRotation) {
-        doAnswer(invocation -> {
-            DisplayInfo displayInfo = new DisplayInfo();
-            mDisplayContent.getDisplay().getDisplayInfo(displayInfo);
-            displayInfo.rotation = displayRotation;
-            // Set height so that the natural orientation (rotation is 0) is portrait. This is the
-            // case for most standard phones and tablets.
-            // TODO(b/365725400): handle landscape natural orientation.
-            displayInfo.logicalHeight = displayRotation % 180 == 0 ? 800 : 600;
-            displayInfo.logicalWidth = displayRotation % 180 == 0 ? 600 : 800;
-            return displayInfo;
-        }).when(mDisplayContent.mWmService.mDisplayManagerInternal)
-                .getDisplayInfo(anyInt());
-    }
+        void checkCameraCompatAspectRatioEquals(float aspectRatio) {
+            assertEquals(aspectRatio,
+                    cameraCompatFreeformPolicy().getCameraCompatAspectRatio(activity().top()),
+                    /* delta= */ 0.001);
+        }
 
-    private void setupMockApplicationThread() {
-        IApplicationThread mockApplicationThread = mock(IApplicationThread.class);
-        spyOn(mActivity.app);
-        doReturn(mockApplicationThread).when(mActivity.app).getThread();
-    }
+        private void assertInCameraCompatMode(@FreeformCameraCompatMode int mode) {
+            assertEquals(mode, cameraCompatFreeformPolicy().getCameraCompatMode(activity().top()));
+        }
 
-    private void assertCompatibilityInfoSentWithDisplayRotation(@Surface.Rotation int
-            expectedRotation) throws Exception {
-        final ArgumentCaptor<CompatibilityInfo> compatibilityInfoArgumentCaptor =
-                ArgumentCaptor.forClass(CompatibilityInfo.class);
-        verify(mActivity.app.getThread()).updatePackageCompatibilityInfo(eq(mActivity.packageName),
-                compatibilityInfoArgumentCaptor.capture());
+        private void assertNotInCameraCompatMode() {
+            assertInCameraCompatMode(CAMERA_COMPAT_FREEFORM_NONE);
+        }
 
-        final CompatibilityInfo compatInfo = compatibilityInfoArgumentCaptor.getValue();
-        assertTrue(compatInfo.isOverrideDisplayRotationRequired());
-        assertEquals(expectedRotation, compatInfo.applicationDisplayRotation);
+        private void assertActivityRefreshRequested(boolean refreshRequested) {
+            assertActivityRefreshRequested(refreshRequested, /* cycleThroughStop*/ true);
+        }
+
+        private void assertActivityRefreshRequested(boolean refreshRequested,
+                boolean cycleThroughStop) {
+            verify(activity().top().mAppCompatController.getCameraOverrides(),
+                    times(refreshRequested ? 1 : 0)).setIsRefreshRequested(true);
+
+            final RefreshCallbackItem refreshCallbackItem =
+                    new RefreshCallbackItem(activity().top().token,
+                            cycleThroughStop ? ON_STOP : ON_PAUSE);
+            final ResumeActivityItem resumeActivityItem = new ResumeActivityItem(
+                    activity().top().token,
+                    /* isForward */ false, /* shouldSendCompatFakeFocus */ false);
+            try {
+                verify(activity().top().mAtmService.getLifecycleManager(),
+                        times(refreshRequested ? 1 : 0))
+                        .scheduleTransactionItems(activity().top().app.getThread(),
+                                refreshCallbackItem, resumeActivityItem);
+            } catch (RemoteException e) {
+                fail(e.getMessage());
+            }
+        }
+
+        private void callOnActivityConfigurationChanging() {
+            callOnActivityConfigurationChanging(/* letterboxNew= */ true,
+                    /* lastLetterbox= */false);
+        }
+
+        private void callOnActivityConfigurationChanging(boolean letterboxNew,
+                boolean lastLetterbox) {
+            activity().displayContent().mAppCompatCameraPolicy.mActivityRefresher
+                    .onActivityConfigurationChanging(activity().top(),
+                            /* newConfig */ createConfiguration(letterboxNew),
+                            /* lastReportedConfig */ createConfiguration(lastLetterbox));
+        }
+
+        void checkIsCameraCompatTreatmentActiveForTopActivity(boolean active) {
+            assertEquals(active,
+                    cameraCompatFreeformPolicy().isTreatmentEnabledForActivity(activity().top(),
+                            /* checkOrientation */ true));
+        }
+
+        void setOverrideMinAspectRatioEnabled(boolean enabled) {
+            doReturn(enabled).when(activity().top().mAppCompatController.getCameraOverrides())
+                    .isOverrideMinAspectRatioForCameraEnabled();
+        }
+
+        void assertCompatibilityInfoSentWithDisplayRotation(@Surface.Rotation int
+                expectedRotation) {
+            final ArgumentCaptor<CompatibilityInfo> compatibilityInfoArgumentCaptor =
+                    ArgumentCaptor.forClass(CompatibilityInfo.class);
+            try {
+                verify(activity().top().app.getThread()).updatePackageCompatibilityInfo(
+                        eq(activity().top().packageName),
+                        compatibilityInfoArgumentCaptor.capture());
+            } catch (RemoteException e) {
+                fail(e.getMessage());
+            }
+
+            final CompatibilityInfo compatInfo = compatibilityInfoArgumentCaptor.getValue();
+            assertTrue(compatInfo.isOverrideDisplayRotationRequired());
+            assertEquals(expectedRotation, compatInfo.applicationDisplayRotation);
+        }
+
+        CameraCompatFreeformPolicy cameraCompatFreeformPolicy() {
+            return activity().displayContent().mAppCompatCameraPolicy.mCameraCompatFreeformPolicy;
+        }
     }
 }
diff --git a/services/tests/wmtests/src/com/android/server/wm/DesktopModeLaunchParamsModifierTests.java b/services/tests/wmtests/src/com/android/server/wm/DesktopModeLaunchParamsModifierTests.java
index bc37496..e87e107 100644
--- a/services/tests/wmtests/src/com/android/server/wm/DesktopModeLaunchParamsModifierTests.java
+++ b/services/tests/wmtests/src/com/android/server/wm/DesktopModeLaunchParamsModifierTests.java
@@ -21,6 +21,7 @@
 import static android.app.WindowConfiguration.ACTIVITY_TYPE_UNDEFINED;
 import static android.app.WindowConfiguration.WINDOWING_MODE_FREEFORM;
 import static android.app.WindowConfiguration.WINDOWING_MODE_FULLSCREEN;
+import static android.content.Intent.FLAG_ACTIVITY_NEW_TASK;
 import static android.content.pm.ActivityInfo.OVERRIDE_MIN_ASPECT_RATIO_LARGE_VALUE;
 import static android.content.pm.ActivityInfo.OVERRIDE_MIN_ASPECT_RATIO_MEDIUM_VALUE;
 import static android.content.pm.ActivityInfo.OVERRIDE_MIN_ASPECT_RATIO_SMALL_VALUE;
@@ -157,7 +158,7 @@
     @Test
     @EnableFlags({Flags.FLAG_ENABLE_DESKTOP_WINDOWING_MODE,
             Flags.FLAG_DISABLE_DESKTOP_LAUNCH_PARAMS_OUTSIDE_DESKTOP_BUG_FIX})
-    public void testReturnsContinueIfVisibleFreeformTaskExists() {
+    public void testReturnsContinueIfFreeformTaskExists() {
         setupDesktopModeLaunchParamsModifier();
         when(mTarget.isEnteringDesktopMode(any(), any(), any())).thenCallRealMethod();
 
@@ -165,7 +166,7 @@
         final Task existingFreeformTask = new TaskBuilder(mSupervisor).setCreateActivity(true)
                 .setWindowingMode(WINDOWING_MODE_FREEFORM).build();
         doReturn(existingFreeformTask.getRootActivity()).when(dc)
-                .getTopMostVisibleFreeformActivity();
+                .getTopMostFreeformActivity();
         final Task launchingTask = new TaskBuilder(mSupervisor).build();
         launchingTask.onDisplayChanged(dc);
 
@@ -269,6 +270,38 @@
     }
 
     @Test
+    @EnableFlags({Flags.FLAG_ENABLE_DESKTOP_WINDOWING_MODE,
+            Flags.FLAG_INHERIT_TASK_BOUNDS_FOR_TRAMPOLINE_TASK_LAUNCHES})
+    public void testInheritTaskBoundsFromExistingInstanceIfClosing() {
+        setupDesktopModeLaunchParamsModifier();
+
+        final String packageName = "com.same.package";
+        // Setup existing task.
+        final DisplayContent dc = spy(createNewDisplay());
+        final Task existingFreeformTask = new TaskBuilder(mSupervisor).setCreateActivity(true)
+                .setWindowingMode(WINDOWING_MODE_FREEFORM).setPackage(packageName).build();
+        existingFreeformTask.setBounds(
+                /* left */ 0,
+                /* top */ 0,
+                /* right */ 500,
+                /* bottom */ 500);
+        doReturn(existingFreeformTask.getRootActivity()).when(dc)
+                .getTopMostVisibleFreeformActivity();
+        // Set up new instance of already existing task. By default multi instance is not supported
+        // so first instance will close.
+        final Task launchingTask = new TaskBuilder(mSupervisor).setPackage(packageName)
+                .setCreateActivity(true).build();
+        launchingTask.onDisplayChanged(dc);
+        launchingTask.intent.addFlags(FLAG_ACTIVITY_NEW_TASK);
+
+        // New instance should inherit task bounds of old instance.
+        assertEquals(RESULT_DONE,
+                new CalculateRequestBuilder().setTask(launchingTask)
+                        .setActivity(launchingTask.getRootActivity()).calculate());
+        assertEquals(existingFreeformTask.getBounds(), mResult.mBounds);
+    }
+
+    @Test
     @EnableFlags(Flags.FLAG_ENABLE_DESKTOP_WINDOWING_MODE)
     @DisableFlags(Flags.FLAG_ENABLE_WINDOWING_DYNAMIC_INITIAL_BOUNDS)
     public void testUsesDesiredBoundsIfEmptyLayoutAndActivityOptionsBounds() {
diff --git a/services/tests/wmtests/src/com/android/server/wm/DisplayCompatTests.java b/services/tests/wmtests/src/com/android/server/wm/DisplayCompatTests.java
new file mode 100644
index 0000000..1445a69
--- /dev/null
+++ b/services/tests/wmtests/src/com/android/server/wm/DisplayCompatTests.java
@@ -0,0 +1,71 @@
+/*
+ * Copyright (C) 2019 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;
+
+import static android.view.Display.DEFAULT_DISPLAY;
+
+import static com.android.window.flags.Flags.FLAG_ENABLE_RESTART_MENU_FOR_CONNECTED_DISPLAYS;
+
+import static junit.framework.Assert.assertFalse;
+
+import static org.junit.Assert.assertTrue;
+
+import android.compat.testing.PlatformCompatChangeRule;
+import android.platform.test.annotations.EnableFlags;
+import android.platform.test.annotations.Presubmit;
+import android.view.DisplayInfo;
+
+import androidx.test.filters.MediumTest;
+
+import org.junit.Rule;
+import org.junit.Test;
+import org.junit.rules.TestRule;
+import org.junit.runner.RunWith;
+
+/**
+ * Build/Install/Run:
+ *  atest WmTests:DisplayCompatTests
+ */
+@MediumTest
+@Presubmit
+@RunWith(WindowTestRunner.class)
+public class DisplayCompatTests extends WindowTestsBase {
+
+    @Rule
+    public TestRule compatChangeRule = new PlatformCompatChangeRule();
+
+    @EnableFlags(FLAG_ENABLE_RESTART_MENU_FOR_CONNECTED_DISPLAYS)
+    @Test
+    public void testFixedMiscConfigurationWhenMovingToDisplay() {
+        // Create an app on the default display, at which point the restart menu isn't enabled.
+        final Task task = createTask(mDefaultDisplay);
+        final ActivityRecord activity = createActivityRecord(task);
+        assertFalse(task.getTaskInfo().appCompatTaskInfo.isRestartMenuEnabledForDisplayMove());
+
+        // Move the app to a secondary display, and the restart menu must get enabled.
+        final DisplayInfo displayInfo = new DisplayInfo();
+        displayInfo.copyFrom(mDisplayInfo);
+        displayInfo.displayId = DEFAULT_DISPLAY + 1;
+        final DisplayContent secondaryDisplay = createNewDisplay(displayInfo);
+        task.reparent(secondaryDisplay.getDefaultTaskDisplayArea(), true);
+        assertTrue(task.getTaskInfo().appCompatTaskInfo.isRestartMenuEnabledForDisplayMove());
+
+        // Once the app gets restarted, the restart menu must be gone.
+        activity.restartProcessIfVisible();
+        assertFalse(task.getTaskInfo().appCompatTaskInfo.isRestartMenuEnabledForDisplayMove());
+    }
+}
diff --git a/services/tests/wmtests/src/com/android/server/wm/InsetsPolicyTest.java b/services/tests/wmtests/src/com/android/server/wm/InsetsPolicyTest.java
index 71e34ef..3c6a898 100644
--- a/services/tests/wmtests/src/com/android/server/wm/InsetsPolicyTest.java
+++ b/services/tests/wmtests/src/com/android/server/wm/InsetsPolicyTest.java
@@ -93,7 +93,7 @@
 
         final Task task1 = createTask(mDisplayContent);
         final Task task2 = createTask(mDisplayContent);
-        task1.setAdjacentTaskFragment(task2);
+        task1.setAdjacentTaskFragments(new TaskFragment.AdjacentSet(task1, task2));
         final WindowState win = createAppWindow(task1, WINDOWING_MODE_MULTI_WINDOW, "app");
         final InsetsSourceControl[] controls = addWindowAndGetControlsForDispatch(win);
 
diff --git a/services/tests/wmtests/src/com/android/server/wm/TaskDisplayAreaTests.java b/services/tests/wmtests/src/com/android/server/wm/TaskDisplayAreaTests.java
index 986532c..ec83c50 100644
--- a/services/tests/wmtests/src/com/android/server/wm/TaskDisplayAreaTests.java
+++ b/services/tests/wmtests/src/com/android/server/wm/TaskDisplayAreaTests.java
@@ -87,7 +87,8 @@
                 mDisplayContent, WINDOWING_MODE_MULTI_WINDOW, ACTIVITY_TYPE_STANDARD);
         adjacentRootTask.mCreatedByOrganizer = true;
         final TaskDisplayArea taskDisplayArea = rootTask.getDisplayArea();
-        adjacentRootTask.setAdjacentTaskFragment(rootTask);
+        adjacentRootTask.setAdjacentTaskFragments(
+                new TaskFragment.AdjacentSet(adjacentRootTask, rootTask));
 
         taskDisplayArea.setLaunchAdjacentFlagRootTask(adjacentRootTask);
         Task actualRootTask = taskDisplayArea.getLaunchRootTask(
@@ -113,7 +114,8 @@
         final Task adjacentRootTask = createTask(
                 mDisplayContent, WINDOWING_MODE_MULTI_WINDOW, ACTIVITY_TYPE_STANDARD);
         adjacentRootTask.mCreatedByOrganizer = true;
-        adjacentRootTask.setAdjacentTaskFragment(rootTask);
+        adjacentRootTask.setAdjacentTaskFragments(
+                new TaskFragment.AdjacentSet(adjacentRootTask, rootTask));
 
         taskDisplayArea.setLaunchRootTask(rootTask,
                 new int[]{WINDOWING_MODE_MULTI_WINDOW}, new int[]{ACTIVITY_TYPE_STANDARD});
@@ -135,7 +137,8 @@
         adjacentRootTask.mCreatedByOrganizer = true;
         createActivityRecord(adjacentRootTask);
         final TaskDisplayArea taskDisplayArea = rootTask.getDisplayArea();
-        adjacentRootTask.setAdjacentTaskFragment(rootTask);
+        adjacentRootTask.setAdjacentTaskFragments(
+                new TaskFragment.AdjacentSet(adjacentRootTask, rootTask));
 
         taskDisplayArea.setLaunchAdjacentFlagRootTask(adjacentRootTask);
         final Task actualRootTask = taskDisplayArea.getLaunchRootTask(
@@ -821,7 +824,8 @@
         adjacentRootTask.mCreatedByOrganizer = true;
         final Task candidateTask = createTaskInRootTask(rootTask, 0 /* userId*/);
         final TaskDisplayArea taskDisplayArea = rootTask.getDisplayArea();
-        adjacentRootTask.setAdjacentTaskFragment(rootTask);
+        adjacentRootTask.setAdjacentTaskFragments(
+                new TaskFragment.AdjacentSet(adjacentRootTask, rootTask));
 
         // Verify the launch root with candidate task
         Task actualRootTask = taskDisplayArea.getLaunchRootTask(WINDOWING_MODE_UNDEFINED,
diff --git a/services/tests/wmtests/src/com/android/server/wm/TaskFragmentOrganizerControllerTest.java b/services/tests/wmtests/src/com/android/server/wm/TaskFragmentOrganizerControllerTest.java
index ab76ae8..76660bd 100644
--- a/services/tests/wmtests/src/com/android/server/wm/TaskFragmentOrganizerControllerTest.java
+++ b/services/tests/wmtests/src/com/android/server/wm/TaskFragmentOrganizerControllerTest.java
@@ -784,7 +784,8 @@
                 .setFragmentToken(fragmentToken2)
                 .build();
         mWindowOrganizerController.mLaunchTaskFragments.put(fragmentToken2, taskFragment2);
-        mTaskFragment.setAdjacentTaskFragment(taskFragment2);
+        mTaskFragment.setAdjacentTaskFragments(
+                new TaskFragment.AdjacentSet(mTaskFragment, taskFragment2));
 
         mTransaction.clearAdjacentTaskFragments(mFragmentToken);
         mOrganizer.applyTransaction(mTransaction, TASK_FRAGMENT_TRANSIT_CHANGE,
@@ -1267,7 +1268,7 @@
     }
 
     @Test
-    public void testTaskFragmentInPip_setAdjacentTaskFragment() {
+    public void testTaskFragmentInPip_setAdjacentTaskFragments() {
         setupTaskFragmentInPip();
         spyOn(mWindowOrganizerController);
 
@@ -1279,7 +1280,7 @@
         verify(mWindowOrganizerController).sendTaskFragmentOperationFailure(eq(mIOrganizer),
                 eq(mErrorToken), eq(mTaskFragment), eq(OP_TYPE_SET_ADJACENT_TASK_FRAGMENTS),
                 any(IllegalArgumentException.class));
-        verify(mTaskFragment, never()).setAdjacentTaskFragment(any());
+        verify(mTaskFragment, never()).setAdjacentTaskFragments(any());
     }
 
     @Test
diff --git a/services/tests/wmtests/src/com/android/server/wm/TaskFragmentTest.java b/services/tests/wmtests/src/com/android/server/wm/TaskFragmentTest.java
index cc2a76d..7c1d7fec 100644
--- a/services/tests/wmtests/src/com/android/server/wm/TaskFragmentTest.java
+++ b/services/tests/wmtests/src/com/android/server/wm/TaskFragmentTest.java
@@ -67,7 +67,6 @@
 import android.graphics.Color;
 import android.graphics.Rect;
 import android.os.Binder;
-import android.platform.test.annotations.EnableFlags;
 import android.platform.test.annotations.Presubmit;
 import android.view.SurfaceControl;
 import android.view.View;
@@ -363,7 +362,7 @@
         doReturn(true).when(primaryActivity).supportsPictureInPicture();
         doReturn(false).when(secondaryActivity).supportsPictureInPicture();
 
-        primaryTf.setAdjacentTaskFragment(secondaryTf);
+        primaryTf.setAdjacentTaskFragments(new TaskFragment.AdjacentSet(primaryTf, secondaryTf));
         primaryActivity.setState(RESUMED, "test");
         secondaryActivity.setState(RESUMED, "test");
 
@@ -390,7 +389,8 @@
         task.setWindowingMode(WINDOWING_MODE_FULLSCREEN);
         taskFragment0.setWindowingMode(WINDOWING_MODE_MULTI_WINDOW);
         taskFragment0.setBounds(taskFragmentBounds);
-        taskFragment0.setAdjacentTaskFragment(taskFragment1);
+        taskFragment0.setAdjacentTaskFragments(
+                new TaskFragment.AdjacentSet(taskFragment0, taskFragment1));
         taskFragment0.setCompanionTaskFragment(taskFragment1);
         taskFragment0.setAnimationParams(new TaskFragmentAnimationParams.Builder()
                 .setAnimationBackgroundColor(Color.GREEN)
@@ -779,7 +779,7 @@
                 .setOrganizer(mOrganizer)
                 .setFragmentToken(new Binder())
                 .build();
-        tf0.setAdjacentTaskFragment(tf1);
+        tf0.setAdjacentTaskFragments(new TaskFragment.AdjacentSet(tf0, tf1));
         tf0.setWindowingMode(WINDOWING_MODE_MULTI_WINDOW);
         tf1.setWindowingMode(WINDOWING_MODE_MULTI_WINDOW);
         task.setBounds(0, 0, 1200, 1000);
@@ -834,7 +834,7 @@
         final Task task = createTask(mDisplayContent);
         final TaskFragment tf0 = createTaskFragmentWithActivity(task);
         final TaskFragment tf1 = createTaskFragmentWithActivity(task);
-        tf0.setAdjacentTaskFragment(tf1);
+        tf0.setAdjacentTaskFragments(new TaskFragment.AdjacentSet(tf0, tf1));
         tf0.setWindowingMode(WINDOWING_MODE_MULTI_WINDOW);
         tf1.setWindowingMode(WINDOWING_MODE_MULTI_WINDOW);
         task.setBounds(0, 0, 1200, 1000);
@@ -982,7 +982,8 @@
                 .setOrganizer(mOrganizer)
                 .setFragmentToken(new Binder())
                 .build();
-        taskFragmentLeft.setAdjacentTaskFragment(taskFragmentRight);
+        taskFragmentLeft.setAdjacentTaskFragments(
+                new TaskFragment.AdjacentSet(taskFragmentLeft, taskFragmentRight));
         taskFragmentLeft.setWindowingMode(WINDOWING_MODE_MULTI_WINDOW);
         taskFragmentRight.setWindowingMode(WINDOWING_MODE_MULTI_WINDOW);
         task.setBounds(0, 0, 1200, 1000);
@@ -1051,8 +1052,8 @@
                 .setParentTask(task)
                 .createActivityCount(1)
                 .build();
-        taskFragmentRight.setAdjacentTaskFragment(taskFragmentLeft);
-        taskFragmentLeft.setAdjacentTaskFragment(taskFragmentRight);
+        taskFragmentRight.setAdjacentTaskFragments(
+                new TaskFragment.AdjacentSet(taskFragmentLeft, taskFragmentRight));
         final ActivityRecord appLeftTop = taskFragmentLeft.getTopMostActivity();
         final ActivityRecord appRightTop = taskFragmentRight.getTopMostActivity();
 
@@ -1103,7 +1104,6 @@
                 Math.min(outConfig.screenWidthDp, outConfig.screenHeightDp));
     }
 
-    @EnableFlags(Flags.FLAG_ALLOW_MULTIPLE_ADJACENT_TASK_FRAGMENTS)
     @Test
     public void testAdjacentSetForTaskFragments() {
         final Task task = createTask(mDisplayContent);
@@ -1119,7 +1119,6 @@
                 () -> new TaskFragment.AdjacentSet(tf0, tf1, tf2));
     }
 
-    @EnableFlags(Flags.FLAG_ALLOW_MULTIPLE_ADJACENT_TASK_FRAGMENTS)
     @Test
     public void testSetAdjacentTaskFragments() {
         final Task task0 = createTask(mDisplayContent);
@@ -1148,7 +1147,6 @@
         assertFalse(task2.hasAdjacentTaskFragment());
     }
 
-    @EnableFlags(Flags.FLAG_ALLOW_MULTIPLE_ADJACENT_TASK_FRAGMENTS)
     @Test
     public void testClearAdjacentTaskFragments() {
         final Task task0 = createTask(mDisplayContent);
@@ -1167,7 +1165,6 @@
         assertFalse(task2.hasAdjacentTaskFragment());
     }
 
-    @EnableFlags(Flags.FLAG_ALLOW_MULTIPLE_ADJACENT_TASK_FRAGMENTS)
     @Test
     public void testRemoveFromAdjacentTaskFragments() {
         final Task task0 = createTask(mDisplayContent);
@@ -1190,7 +1187,6 @@
         assertFalse(task1.isAdjacentTo(task1));
     }
 
-    @EnableFlags(Flags.FLAG_ALLOW_MULTIPLE_ADJACENT_TASK_FRAGMENTS)
     @Test
     public void testRemoveFromAdjacentTaskFragmentsWhenRemove() {
         final Task task0 = createTask(mDisplayContent);
diff --git a/services/tests/wmtests/src/com/android/server/wm/TaskTests.java b/services/tests/wmtests/src/com/android/server/wm/TaskTests.java
index 724d7e7..044aacc 100644
--- a/services/tests/wmtests/src/com/android/server/wm/TaskTests.java
+++ b/services/tests/wmtests/src/com/android/server/wm/TaskTests.java
@@ -1750,8 +1750,7 @@
 
         primary.mVisibleRequested = true;
         secondary.mVisibleRequested = true;
-        primary.setAdjacentTaskFragment(secondary);
-        secondary.setAdjacentTaskFragment(primary);
+        primary.setAdjacentTaskFragments(new TaskFragment.AdjacentSet(primary, secondary));
         primary.setEmbeddedDimArea(EMBEDDED_DIM_AREA_PARENT_TASK);
         doReturn(true).when(primary).shouldBoostDimmer();
         task.assignChildLayers(t);
diff --git a/services/tests/wmtests/src/com/android/server/wm/WindowOrganizerTests.java b/services/tests/wmtests/src/com/android/server/wm/WindowOrganizerTests.java
index 7030d986..ae61447 100644
--- a/services/tests/wmtests/src/com/android/server/wm/WindowOrganizerTests.java
+++ b/services/tests/wmtests/src/com/android/server/wm/WindowOrganizerTests.java
@@ -929,7 +929,6 @@
         assertEquals(dc.getDefaultTaskDisplayArea().mLaunchAdjacentFlagRootTask, null);
     }
 
-    @EnableFlags(Flags.FLAG_ALLOW_MULTIPLE_ADJACENT_TASK_FRAGMENTS)
     @Test
     public void testSetAdjacentLaunchRootSet() {
         final DisplayContent dc = mWm.mRoot.getDisplayContent(Display.DEFAULT_DISPLAY);
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 57ab13f..471b065 100644
--- a/services/tests/wmtests/src/com/android/server/wm/WindowTestsBase.java
+++ b/services/tests/wmtests/src/com/android/server/wm/WindowTestsBase.java
@@ -122,7 +122,6 @@
 import com.android.internal.policy.AttributeCache;
 import com.android.internal.util.ArrayUtils;
 import com.android.internal.util.test.FakeSettingsProvider;
-import com.android.server.wallpaper.WallpaperCropper.WallpaperCropUtils;
 import com.android.server.wm.DisplayWindowSettings.SettingsProvider.SettingsEntry;
 
 import org.junit.After;
@@ -289,18 +288,6 @@
         mAtm.mWindowManager.mAppCompatConfiguration
                 .setIsDisplayAspectRatioEnabledForFixedOrientationLetterbox(false);
 
-        // Setup WallpaperController crop utils with a simple center-align strategy
-        WallpaperCropUtils cropUtils = (displaySize, bitmapSize, suggestedCrops, rtl) -> {
-            Rect crop = new Rect(0, 0, displaySize.x, displaySize.y);
-            crop.scale(Math.min(
-                    ((float) bitmapSize.x) / displaySize.x,
-                    ((float) bitmapSize.y) / displaySize.y));
-            crop.offset((bitmapSize.x - crop.width()) / 2, (bitmapSize.y - crop.height()) / 2);
-            return crop;
-        };
-        mDisplayContent.mWallpaperController.setWallpaperCropUtils(cropUtils);
-        mDefaultDisplay.mWallpaperController.setWallpaperCropUtils(cropUtils);
-
         checkDeviceSpecificOverridesNotApplied();
     }
 
@@ -1890,7 +1877,7 @@
             mSecondary = mService.mTaskOrganizerController.createRootTask(
                     display, WINDOWING_MODE_MULTI_WINDOW, null);
 
-            mPrimary.setAdjacentTaskFragment(mSecondary);
+            mPrimary.setAdjacentTaskFragments(new TaskFragment.AdjacentSet(mPrimary, mSecondary));
             display.getDefaultTaskDisplayArea().setLaunchAdjacentFlagRootTask(mSecondary);
 
             final Rect primaryBounds = new Rect();
diff --git a/tests/Input/src/com/android/server/input/BatteryControllerTests.kt b/tests/Input/src/com/android/server/input/BatteryControllerTests.kt
index 044f11d..890c346 100644
--- a/tests/Input/src/com/android/server/input/BatteryControllerTests.kt
+++ b/tests/Input/src/com/android/server/input/BatteryControllerTests.kt
@@ -184,6 +184,8 @@
     @get:Rule
     val rule = MockitoJUnit.rule()!!
     @get:Rule
+    val context = TestableContext(ApplicationProvider.getApplicationContext())
+    @get:Rule
     val inputManagerRule = MockInputManagerRule()
 
     @Mock
@@ -194,7 +196,6 @@
     private lateinit var bluetoothBatteryManager: BluetoothBatteryManager
 
     private lateinit var batteryController: BatteryController
-    private lateinit var context: TestableContext
     private lateinit var testLooper: TestLooper
     private lateinit var devicesChangedListener: IInputDevicesChangedListener
     private lateinit var inputManagerGlobalSession: InputManagerGlobal.TestSession
@@ -202,7 +203,6 @@
 
     @Before
     fun setup() {
-        context = TestableContext(ApplicationProvider.getApplicationContext())
         testLooper = TestLooper()
         val inputManager = InputManager(context)
         context.addMockSystemService(InputManager::class.java, inputManager)
diff --git a/tests/Input/src/com/android/server/input/debug/TouchpadDebugViewControllerTests.java b/tests/Input/src/com/android/server/input/debug/TouchpadDebugViewControllerTests.java
index 5875520..20528f23 100644
--- a/tests/Input/src/com/android/server/input/debug/TouchpadDebugViewControllerTests.java
+++ b/tests/Input/src/com/android/server/input/debug/TouchpadDebugViewControllerTests.java
@@ -23,7 +23,6 @@
 import static org.mockito.Mockito.verify;
 import static org.mockito.Mockito.when;
 
-import android.content.Context;
 import android.graphics.Rect;
 import android.hardware.input.InputManager;
 import android.testing.AndroidTestingRunner;
@@ -60,9 +59,12 @@
     private static final String TAG = "TouchpadDebugViewController";
 
     @Rule
+    public final TestableContext mTestableContext =
+            new TestableContext(InstrumentationRegistry.getInstrumentation().getContext());
+
+    @Rule
     public final MockitoRule mockito = MockitoJUnit.rule();
 
-    private Context mContext;
     private TouchpadDebugViewController mTouchpadDebugViewController;
     @Mock
     private InputManager mInputManagerMock;
@@ -74,8 +76,6 @@
 
     @Before
     public void setup() throws Exception {
-        mContext = InstrumentationRegistry.getInstrumentation().getContext();
-        TestableContext mTestableContext = new TestableContext(mContext);
         mTestableContext.addMockSystemService(WindowManager.class, mWindowManagerMock);
 
         Rect bounds = new Rect(0, 0, 2560, 1600);
diff --git a/tests/Input/src/com/android/server/input/debug/TouchpadDebugViewTest.java b/tests/Input/src/com/android/server/input/debug/TouchpadDebugViewTest.java
index 60fa52f..1c366a1 100644
--- a/tests/Input/src/com/android/server/input/debug/TouchpadDebugViewTest.java
+++ b/tests/Input/src/com/android/server/input/debug/TouchpadDebugViewTest.java
@@ -26,7 +26,6 @@
 import static org.mockito.Mockito.verify;
 import static org.mockito.Mockito.when;
 
-import android.content.Context;
 import android.graphics.Color;
 import android.graphics.Rect;
 import android.graphics.drawable.ColorDrawable;
@@ -51,6 +50,7 @@
 import com.android.server.input.TouchpadHardwareState;
 
 import org.junit.Before;
+import org.junit.Rule;
 import org.junit.Test;
 import org.junit.runner.RunWith;
 import org.mockito.ArgumentCaptor;
@@ -70,6 +70,10 @@
     private TouchpadDebugView mTouchpadDebugView;
     private WindowManager.LayoutParams mWindowLayoutParams;
 
+    @Rule
+    public final TestableContext mTestableContext =
+            new TestableContext(InstrumentationRegistry.getInstrumentation().getContext());
+
     @Mock
     WindowManager mWindowManager;
     @Mock
@@ -77,14 +81,10 @@
 
     Rect mWindowBounds;
     WindowMetrics mWindowMetrics;
-    TestableContext mTestableContext;
 
     @Before
     public void setUp() {
         MockitoAnnotations.initMocks(this);
-        Context context = InstrumentationRegistry.getInstrumentation().getContext();
-        mTestableContext = new TestableContext(context);
-
         mTestableContext.addMockSystemService(WindowManager.class, mWindowManager);
         mTestableContext.addMockSystemService(InputManager.class, mInputManager);