Merge "Unable to drag and edit the Accessibility shortcut" into main
diff --git a/core/proto/android/server/inputmethod/inputmethodmanagerservice.proto b/core/proto/android/server/inputmethod/inputmethodmanagerservice.proto
index b75d545..eddf1d2 100644
--- a/core/proto/android/server/inputmethod/inputmethodmanagerservice.proto
+++ b/core/proto/android/server/inputmethod/inputmethodmanagerservice.proto
@@ -47,4 +47,5 @@
     optional int32 ime_window_visibility = 22;
     optional bool show_ime_with_hard_keyboard = 23;
     optional bool accessibility_requesting_no_soft_keyboard = 24;
-}
\ No newline at end of file
+    optional bool concurrent_multi_user_mode_enabled = 25;
+}
diff --git a/libs/WindowManager/Shell/aconfig/multitasking.aconfig b/libs/WindowManager/Shell/aconfig/multitasking.aconfig
index 3ff40e0..56ea7c2 100644
--- a/libs/WindowManager/Shell/aconfig/multitasking.aconfig
+++ b/libs/WindowManager/Shell/aconfig/multitasking.aconfig
@@ -113,16 +113,6 @@
 }
 
 flag {
-    name: "animate_bubble_size_change"
-    namespace: "multitasking"
-    description: "Turns on the animation for bubble bar icons size change"
-    bug: "335575529"
-    metadata {
-        purpose: PURPOSE_BUGFIX
-    }
-}
-
-flag {
     name: "enable_taskbar_on_phones"
     namespace: "multitasking"
     description: "Enables taskbar on phones"
diff --git a/libs/WindowManager/Shell/res/layout/desktop_mode_window_decor_handle_menu.xml b/libs/WindowManager/Shell/res/layout/desktop_mode_window_decor_handle_menu.xml
index 419d5c0..864f7cd 100644
--- a/libs/WindowManager/Shell/res/layout/desktop_mode_window_decor_handle_menu.xml
+++ b/libs/WindowManager/Shell/res/layout/desktop_mode_window_decor_handle_menu.xml
@@ -19,6 +19,10 @@
     xmlns:tools="http://schemas.android.com/tools"
     android:layout_width="@dimen/desktop_mode_handle_menu_width"
     android:layout_height="wrap_content"
+    android:clipChildren="false"
+    android:clipToPadding="false"
+    android:paddingBottom="@dimen/desktop_mode_handle_menu_pill_elevation"
+    android:paddingRight="@dimen/desktop_mode_handle_menu_pill_elevation"
     android:orientation="vertical">
 
     <LinearLayout
@@ -27,7 +31,7 @@
         android:layout_height="@dimen/desktop_mode_handle_menu_app_info_pill_height"
         android:layout_marginTop="@dimen/desktop_mode_handle_menu_margin_top"
         android:layout_marginStart="1dp"
-        android:elevation="1dp"
+        android:elevation="@dimen/desktop_mode_handle_menu_pill_elevation"
         android:orientation="horizontal"
         android:background="@drawable/desktop_mode_decor_handle_menu_background"
         android:gravity="center_vertical">
@@ -73,7 +77,7 @@
         android:layout_marginTop="@dimen/desktop_mode_handle_menu_pill_spacing_margin"
         android:layout_marginStart="1dp"
         android:orientation="horizontal"
-        android:elevation="1dp"
+        android:elevation="@dimen/desktop_mode_handle_menu_pill_elevation"
         android:background="@drawable/desktop_mode_decor_handle_menu_background"
         android:gravity="center_vertical">
 
@@ -124,7 +128,7 @@
         android:layout_marginTop="@dimen/desktop_mode_handle_menu_pill_spacing_margin"
         android:layout_marginStart="1dp"
         android:orientation="vertical"
-        android:elevation="1dp"
+        android:elevation="@dimen/desktop_mode_handle_menu_pill_elevation"
         android:background="@drawable/desktop_mode_decor_handle_menu_background">
 
         <Button
@@ -143,7 +147,7 @@
         android:layout_marginTop="@dimen/desktop_mode_handle_menu_pill_spacing_margin"
         android:layout_marginStart="1dp"
         android:orientation="vertical"
-        android:elevation="1dp"
+        android:elevation="@dimen/desktop_mode_handle_menu_pill_elevation"
         android:background="@drawable/desktop_mode_decor_handle_menu_background">
 
         <Button
diff --git a/libs/WindowManager/Shell/res/values/dimen.xml b/libs/WindowManager/Shell/res/values/dimen.xml
index d143263..269a586 100644
--- a/libs/WindowManager/Shell/res/values/dimen.xml
+++ b/libs/WindowManager/Shell/res/values/dimen.xml
@@ -495,9 +495,16 @@
     <!-- The radius of the Maximize menu shadow. -->
     <dimen name="desktop_mode_maximize_menu_shadow_radius">8dp</dimen>
 
-    <!-- The width of the handle menu in desktop mode. -->
+    <!-- The width of the handle menu in desktop mode.  -->
     <dimen name="desktop_mode_handle_menu_width">216dp</dimen>
 
+    <!-- The maximum height of the handle menu in desktop mode. Four pills (52dp each) plus 2dp
+        spacing between them plus 4dp top padding. -->
+    <dimen name="desktop_mode_handle_menu_height">218dp</dimen>
+
+    <!-- The elevation set on the handle menu pills. -->
+    <dimen name="desktop_mode_handle_menu_pill_elevation">1dp</dimen>
+
     <!-- The height of the handle menu's "App Info" pill in desktop mode. -->
     <dimen name="desktop_mode_handle_menu_app_info_pill_height">52dp</dimen>
 
@@ -510,8 +517,8 @@
     <!-- The height of the handle menu's "Open in browser" pill in desktop mode. -->
     <dimen name="desktop_mode_handle_menu_open_in_browser_pill_height">52dp</dimen>
 
-    <!-- The height of the handle menu in desktop mode. -->
-    <dimen name="desktop_mode_handle_menu_height">380dp</dimen>
+    <!-- The margin between pills of the handle menu in desktop mode. -->
+    <dimen name="desktop_mode_handle_menu_pill_spacing_margin">2dp</dimen>
 
     <!-- The top margin of the handle menu in desktop mode. -->
     <dimen name="desktop_mode_handle_menu_margin_top">4dp</dimen>
@@ -519,9 +526,6 @@
     <!-- The start margin of the handle menu in desktop mode. -->
     <dimen name="desktop_mode_handle_menu_margin_start">6dp</dimen>
 
-    <!-- The margin between pills of the handle menu in desktop mode. -->
-    <dimen name="desktop_mode_handle_menu_pill_spacing_margin">2dp</dimen>
-
     <!-- The radius of the caption menu corners. -->
     <dimen name="desktop_mode_handle_menu_corner_radius">26dp</dimen>
 
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/back/BackAnimationController.java b/libs/WindowManager/Shell/src/com/android/wm/shell/back/BackAnimationController.java
index ece0271..8467e97 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/back/BackAnimationController.java
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/back/BackAnimationController.java
@@ -422,6 +422,12 @@
     @VisibleForTesting
     public void onThresholdCrossed() {
         mThresholdCrossed = true;
+        // There was no focus window when calling startBackNavigation, still pilfer pointers so
+        // the next focus window won't receive motion events.
+        if (mBackNavigationInfo == null) {
+            tryPilferPointers();
+            return;
+        }
         // Dispatch onBackStarted, only to app callbacks.
         // System callbacks will receive onBackStarted when the remote animation starts.
         final boolean shouldDispatchToAnimator = shouldDispatchToAnimator();
@@ -542,6 +548,7 @@
         if (backNavigationInfo == null) {
             ProtoLog.e(WM_SHELL_BACK_PREVIEW, "Received BackNavigationInfo is null.");
             cancelLatencyTracking();
+            tryPilferPointers();
             return;
         }
         final int backType = backNavigationInfo.getType();
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/dagger/pip/Pip1Module.java b/libs/WindowManager/Shell/src/com/android/wm/shell/dagger/pip/Pip1Module.java
index 240cf3b..037fbb2 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/dagger/pip/Pip1Module.java
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/dagger/pip/Pip1Module.java
@@ -38,12 +38,10 @@
 import com.android.wm.shell.common.pip.PipPerfHintController;
 import com.android.wm.shell.common.pip.PipSnapAlgorithm;
 import com.android.wm.shell.common.pip.PipUiEventLogger;
-import com.android.wm.shell.common.pip.PipUtils;
 import com.android.wm.shell.common.pip.SizeSpecSource;
 import com.android.wm.shell.dagger.WMShellBaseModule;
 import com.android.wm.shell.dagger.WMSingleton;
 import com.android.wm.shell.onehanded.OneHandedController;
-import com.android.wm.shell.pip.Pip;
 import com.android.wm.shell.pip.PipAnimationController;
 import com.android.wm.shell.pip.PipParamsChangedForwarder;
 import com.android.wm.shell.pip.PipSurfaceTransactionHelper;
@@ -79,7 +77,7 @@
 public abstract class Pip1Module {
     @WMSingleton
     @Provides
-    static Optional<Pip> providePip1(Context context,
+    static Optional<PipController.PipImpl> providePip1(Context context,
             ShellInit shellInit,
             ShellCommandHandler shellCommandHandler,
             ShellController shellController,
@@ -104,20 +102,16 @@
             TabletopModeController pipTabletopController,
             Optional<OneHandedController> oneHandedController,
             @ShellMainThread ShellExecutor mainExecutor) {
-        if (PipUtils.isPip2ExperimentEnabled()) {
-            return Optional.empty();
-        } else {
-            return Optional.ofNullable(PipController.create(
-                    context, shellInit, shellCommandHandler, shellController,
-                    displayController, pipAnimationController, pipAppOpsListener,
-                    pipBoundsAlgorithm,
-                    pipKeepClearAlgorithm, pipBoundsState, pipDisplayLayoutState,
-                    pipMotionHelper, pipMediaController, phonePipMenuController, pipTaskOrganizer,
-                    pipTransitionState, pipTouchHandler, pipTransitionController,
-                    windowManagerShellWrapper, taskStackListener, pipParamsChangedForwarder,
-                    displayInsetsController, pipTabletopController, oneHandedController,
-                    mainExecutor));
-        }
+        return Optional.ofNullable(PipController.create(
+                context, shellInit, shellCommandHandler, shellController,
+                displayController, pipAnimationController, pipAppOpsListener,
+                pipBoundsAlgorithm,
+                pipKeepClearAlgorithm, pipBoundsState, pipDisplayLayoutState,
+                pipMotionHelper, pipMediaController, phonePipMenuController, pipTaskOrganizer,
+                pipTransitionState, pipTouchHandler, pipTransitionController,
+                windowManagerShellWrapper, taskStackListener, pipParamsChangedForwarder,
+                displayInsetsController, pipTabletopController, oneHandedController,
+                mainExecutor));
     }
 
     // Handler is used by Icon.loadDrawableAsync
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/dagger/pip/Pip2Module.java b/libs/WindowManager/Shell/src/com/android/wm/shell/dagger/pip/Pip2Module.java
index 6968317..ea7e968 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/dagger/pip/Pip2Module.java
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/dagger/pip/Pip2Module.java
@@ -81,6 +81,16 @@
 
     @WMSingleton
     @Provides
+    static Optional<PipController.PipImpl> providePip2(Optional<PipController> pipController) {
+        if (pipController.isEmpty()) {
+            return Optional.empty();
+        } else {
+            return Optional.ofNullable(pipController.get().getPipImpl());
+        }
+    }
+
+    @WMSingleton
+    @Provides
     static Optional<PipController> providePipController(Context context,
             ShellInit shellInit,
             ShellCommandHandler shellCommandHandler,
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/dagger/pip/PipModule.java b/libs/WindowManager/Shell/src/com/android/wm/shell/dagger/pip/PipModule.java
index f2631ef..a3afe78 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/dagger/pip/PipModule.java
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/dagger/pip/PipModule.java
@@ -18,12 +18,16 @@
 
 import com.android.wm.shell.common.pip.PipUtils;
 import com.android.wm.shell.dagger.WMSingleton;
+import com.android.wm.shell.pip.Pip;
 import com.android.wm.shell.pip.PipTransitionController;
+import com.android.wm.shell.pip2.phone.PipController;
 import com.android.wm.shell.pip2.phone.PipTransition;
 
 import dagger.Module;
 import dagger.Provides;
 
+import java.util.Optional;
+
 /**
  * Provides dependencies for external components / modules reference PiP and extracts away the
  * selection of legacy and new PiP implementation.
@@ -44,4 +48,17 @@
             return legacyPipTransition;
         }
     }
+
+    @WMSingleton
+    @Provides
+    static Optional<Pip> providePip(
+            Optional<com.android.wm.shell.pip.phone.PipController.PipImpl> pip1,
+            Optional<PipController.PipImpl> pip2) {
+        if (PipUtils.isPip2ExperimentEnabled()) {
+            return Optional.ofNullable(pip2.orElse(null));
+
+        } else {
+            return Optional.ofNullable(pip1.orElse(null));
+        }
+    }
 }
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/desktopmode/DesktopTasksController.kt b/libs/WindowManager/Shell/src/com/android/wm/shell/desktopmode/DesktopTasksController.kt
index dc3e2d0..6315e69 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
@@ -217,6 +217,15 @@
         dragToDesktopTransitionHandler.setSplitScreenController(controller)
     }
 
+    /** Returns the transition type for the given remote transition. */
+    private fun transitionType(remoteTransition: RemoteTransition?): Int {
+        if (remoteTransition == null) {
+            ProtoLog.v(WM_SHELL_DESKTOP_MODE, "DesktopTasksController: remoteTransition is null")
+            return TRANSIT_NONE
+        }
+        return TRANSIT_TO_FRONT
+    }
+
     /** Show all tasks, that are part of the desktop, on top of launcher */
     fun showDesktopApps(displayId: Int, remoteTransition: RemoteTransition? = null) {
         ProtoLog.v(WM_SHELL_DESKTOP_MODE, "DesktopTasksController: showDesktopApps")
@@ -224,8 +233,7 @@
         bringDesktopAppsToFront(displayId, wct)
 
         if (Transitions.ENABLE_SHELL_TRANSITIONS) {
-            // TODO(b/309014605): ensure remote transition is supplied once state is introduced
-            val transitionType = if (remoteTransition == null) TRANSIT_NONE else TRANSIT_TO_FRONT
+            val transitionType = transitionType(remoteTransition)
             val handler =
                 remoteTransition?.let {
                     OneShotRemoteHandler(transitions.mainExecutor, remoteTransition)
@@ -764,15 +772,13 @@
             newTaskIdInFront ?: "null"
         )
 
+        // Move home to front, ensures that we go back home when all desktop windows are closed
+        moveHomeTask(wct, toTop = true)
+
         // Currently, we only handle the desktop on the default display really.
-        if (displayId == DEFAULT_DISPLAY) {
-            if (Flags.enableDesktopWindowingWallpaperActivity()) {
-                // Add translucent wallpaper activity to show the wallpaper underneath
-                addWallpaperActivity(wct)
-            } else {
-                // Move home to front
-                moveHomeTask(wct, toTop = true)
-            }
+        if (displayId == DEFAULT_DISPLAY && Flags.enableDesktopWindowingWallpaperActivity()) {
+            // Add translucent wallpaper activity to show the wallpaper underneath
+            addWallpaperActivity(wct)
         }
 
         val nonMinimizedTasksOrderedFrontToBack =
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/pip/phone/PipController.java b/libs/WindowManager/Shell/src/com/android/wm/shell/pip/phone/PipController.java
index d1d8275..0cb7e17 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/pip/phone/PipController.java
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/pip/phone/PipController.java
@@ -374,7 +374,7 @@
      * Instantiates {@link PipController}, returns {@code null} if the feature not supported.
      */
     @Nullable
-    public static Pip create(Context context,
+    public static PipImpl create(Context context,
             ShellInit shellInit,
             ShellCommandHandler shellCommandHandler,
             ShellController shellController,
@@ -1177,7 +1177,7 @@
     /**
      * The interface for calls from outside the Shell, within the host process.
      */
-    private class PipImpl implements Pip {
+    public class PipImpl implements Pip {
         @Override
         public void expandPip() {
             mMainExecutor.execute(() -> {
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/pip2/phone/PipController.java b/libs/WindowManager/Shell/src/com/android/wm/shell/pip2/phone/PipController.java
index 06adad6..c12219c 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/pip2/phone/PipController.java
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/pip2/phone/PipController.java
@@ -55,6 +55,8 @@
 import com.android.wm.shell.common.pip.PipBoundsState;
 import com.android.wm.shell.common.pip.PipDisplayLayoutState;
 import com.android.wm.shell.common.pip.PipUtils;
+import com.android.wm.shell.pip.Pip;
+import com.android.wm.shell.pip.PipTransitionController;
 import com.android.wm.shell.protolog.ShellProtoLogGroup;
 import com.android.wm.shell.sysui.ConfigurationChangeListener;
 import com.android.wm.shell.sysui.ShellCommandHandler;
@@ -62,6 +64,8 @@
 import com.android.wm.shell.sysui.ShellInit;
 
 import java.io.PrintWriter;
+import java.util.concurrent.Executor;
+import java.util.function.Consumer;
 
 /**
  * Manages the picture-in-picture (PIP) UI and states for Phones.
@@ -86,6 +90,8 @@
     private final ShellTaskOrganizer mShellTaskOrganizer;
     private final PipTransitionState mPipTransitionState;
     private final ShellExecutor mMainExecutor;
+    private final PipImpl mImpl;
+    private Consumer<Boolean> mOnIsInPipStateChangedListener;
 
     // Wrapper for making Binder calls into PiP animation listener hosted in launcher's Recents.
     private PipAnimationListener mPipRecentsAnimationListener;
@@ -140,6 +146,7 @@
         mPipTransitionState = pipTransitionState;
         mPipTransitionState.addPipTransitionStateChangedListener(this);
         mMainExecutor = mainExecutor;
+        mImpl = new PipImpl();
 
         if (PipUtils.isPip2ExperimentEnabled()) {
             shellInit.addInitCallback(this::onInit, this);
@@ -174,6 +181,10 @@
                 pipTransitionState, mainExecutor);
     }
 
+    public PipImpl getPipImpl() {
+        return mImpl;
+    }
+
     private void onInit() {
         mShellCommandHandler.addDumpCallback(this::dump, this);
         // Ensure that we have the display info in case we get calls to update the bounds before the
@@ -310,22 +321,29 @@
     @Override
     public void onPipTransitionStateChanged(@PipTransitionState.TransitionState int oldState,
             @PipTransitionState.TransitionState int newState, @Nullable Bundle extra) {
-        if (newState == PipTransitionState.SWIPING_TO_PIP) {
-            Preconditions.checkState(extra != null,
-                    "No extra bundle for " + mPipTransitionState);
+        switch (newState) {
+            case PipTransitionState.SWIPING_TO_PIP:
+                Preconditions.checkState(extra != null,
+                        "No extra bundle for " + mPipTransitionState);
 
-            SurfaceControl overlay = extra.getParcelable(
-                    SWIPE_TO_PIP_OVERLAY, SurfaceControl.class);
-            Rect appBounds = extra.getParcelable(
-                    SWIPE_TO_PIP_APP_BOUNDS, Rect.class);
+                SurfaceControl overlay = extra.getParcelable(
+                        SWIPE_TO_PIP_OVERLAY, SurfaceControl.class);
+                Rect appBounds = extra.getParcelable(
+                        SWIPE_TO_PIP_APP_BOUNDS, Rect.class);
 
-            Preconditions.checkState(appBounds != null,
-                    "App bounds can't be null for " + mPipTransitionState);
-            mPipTransitionState.setSwipePipToHomeState(overlay, appBounds);
-        } else if (newState == PipTransitionState.ENTERED_PIP) {
-            if (mPipTransitionState.isInSwipePipToHomeTransition()) {
-                mPipTransitionState.resetSwipePipToHomeState();
-            }
+                Preconditions.checkState(appBounds != null,
+                        "App bounds can't be null for " + mPipTransitionState);
+                mPipTransitionState.setSwipePipToHomeState(overlay, appBounds);
+                break;
+            case PipTransitionState.ENTERED_PIP:
+                if (mPipTransitionState.isInSwipePipToHomeTransition()) {
+                    mPipTransitionState.resetSwipePipToHomeState();
+                }
+                mOnIsInPipStateChangedListener.accept(true /* inPip */);
+                break;
+            case PipTransitionState.EXITED_PIP:
+                mOnIsInPipStateChangedListener.accept(false /* inPip */);
+                break;
         }
     }
 
@@ -355,6 +373,53 @@
         mPipTransitionState.dump(pw, innerPrefix);
     }
 
+    private void setOnIsInPipStateChangedListener(Consumer<Boolean> callback) {
+        mOnIsInPipStateChangedListener = callback;
+        if (mOnIsInPipStateChangedListener != null) {
+            callback.accept(mPipTransitionState.isInPip());
+        }
+    }
+
+    /**
+     * The interface for calls from outside the Shell, within the host process.
+     */
+    public class PipImpl implements Pip {
+        @Override
+        public void expandPip() {}
+
+        @Override
+        public void onSystemUiStateChanged(boolean isSysUiStateValid, long flag) {}
+
+        @Override
+        public void setOnIsInPipStateChangedListener(Consumer<Boolean> callback) {
+            mMainExecutor.execute(() -> {
+                PipController.this.setOnIsInPipStateChangedListener(callback);
+            });
+        }
+
+        @Override
+        public void addPipExclusionBoundsChangeListener(Consumer<Rect> listener) {
+            mMainExecutor.execute(() -> {
+                mPipBoundsState.addPipExclusionBoundsChangeCallback(listener);
+            });
+        }
+
+        @Override
+        public void removePipExclusionBoundsChangeListener(Consumer<Rect> listener) {
+            mMainExecutor.execute(() -> {
+                mPipBoundsState.removePipExclusionBoundsChangeCallback(listener);
+            });
+        }
+
+        @Override
+        public void showPictureInPictureMenu() {}
+
+        @Override
+        public void registerPipTransitionCallback(
+                PipTransitionController.PipTransitionCallback callback,
+                Executor executor) {}
+    }
+
     /**
      * The interface for calls from outside the host process.
      */
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/transition/DefaultTransitionHandler.java b/libs/WindowManager/Shell/src/com/android/wm/shell/transition/DefaultTransitionHandler.java
index f33a573..7784784 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/transition/DefaultTransitionHandler.java
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/transition/DefaultTransitionHandler.java
@@ -41,6 +41,7 @@
 import static android.view.WindowManager.TRANSIT_CHANGE;
 import static android.view.WindowManager.TRANSIT_KEYGUARD_UNOCCLUDE;
 import static android.view.WindowManager.TRANSIT_RELAUNCH;
+import static android.view.WindowManager.TRANSIT_TO_BACK;
 import static android.window.TransitionInfo.FLAG_CROSS_PROFILE_OWNER_THUMBNAIL;
 import static android.window.TransitionInfo.FLAG_CROSS_PROFILE_WORK_THUMBNAIL;
 import static android.window.TransitionInfo.FLAG_DISPLAY_HAS_ALERT_WINDOWS;
@@ -63,6 +64,7 @@
 import static com.android.wm.shell.transition.TransitionAnimationHelper.edgeExtendWindow;
 import static com.android.wm.shell.transition.TransitionAnimationHelper.getTransitionBackgroundColorIfSet;
 import static com.android.wm.shell.transition.TransitionAnimationHelper.getTransitionTypeFromInfo;
+import static com.android.wm.shell.transition.TransitionAnimationHelper.isCoveredByOpaqueFullscreenChange;
 import static com.android.wm.shell.transition.TransitionAnimationHelper.loadAttributeAnimation;
 
 import android.animation.Animator;
@@ -353,6 +355,7 @@
                 continue;
             }
             final boolean isTask = change.getTaskInfo() != null;
+            final boolean isFreeform = isTask && change.getTaskInfo().isFreeform();
             final int mode = change.getMode();
             boolean isSeamlessDisplayChange = false;
 
@@ -459,6 +462,16 @@
                             final int layer = zSplitLine + numChanges - i;
                             startTransaction.setLayer(change.getLeash(), layer);
                         }
+                    } else if (!isCoveredByOpaqueFullscreenChange(info, change)
+                            && isFreeform
+                            && TransitionUtil.isOpeningMode(type)
+                            && change.getMode() == TRANSIT_TO_BACK) {
+                        // Reparent the minimize-change to the root task so the minimizing Task
+                        // isn't shown in front of other Tasks.
+                        mRootTDAOrganizer.reparentToDisplayArea(
+                                change.getTaskInfo().displayId,
+                                change.getLeash(),
+                                startTransaction);
                     } else if (isOnlyTranslucent && TransitionUtil.isOpeningType(info.getType())
                                 && TransitionUtil.isClosingType(mode)) {
                         // If there is a closing translucent task in an OPENING transition, we will
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/transition/TransitionAnimationHelper.java b/libs/WindowManager/Shell/src/com/android/wm/shell/transition/TransitionAnimationHelper.java
index a5f071a..75e7ddf 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/transition/TransitionAnimationHelper.java
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/transition/TransitionAnimationHelper.java
@@ -36,6 +36,7 @@
 import android.annotation.ColorInt;
 import android.annotation.NonNull;
 import android.annotation.Nullable;
+import android.app.WindowConfiguration;
 import android.graphics.BitmapShader;
 import android.graphics.Canvas;
 import android.graphics.Color;
@@ -72,6 +73,9 @@
         final int changeFlags = change.getFlags();
         final boolean enter = TransitionUtil.isOpeningType(changeMode);
         final boolean isTask = change.getTaskInfo() != null;
+        final boolean isFreeform = isTask && change.getTaskInfo().isFreeform();
+        final boolean isCoveredByOpaqueFullscreenChange =
+                isCoveredByOpaqueFullscreenChange(info, change);
         final TransitionInfo.AnimationOptions options;
         if (Flags.moveAnimationOptionsToChange()) {
             options = change.getAnimationOptions();
@@ -107,6 +111,24 @@
             animAttr = enter
                     ? R.styleable.WindowAnimation_wallpaperCloseEnterAnimation
                     : R.styleable.WindowAnimation_wallpaperCloseExitAnimation;
+        } else if (!isCoveredByOpaqueFullscreenChange
+                && isFreeform
+                && TransitionUtil.isOpeningMode(type)
+                && change.getMode() == TRANSIT_TO_BACK) {
+            // Set translucent here so TransitionAnimation loads the appropriate animations for
+            // translucent activities and tasks later
+            translucent = (changeFlags & FLAG_TRANSLUCENT) != 0;
+            // The main Task is launching or being brought to front, this Task is being minimized
+            animAttr = R.styleable.WindowAnimation_activityCloseExitAnimation;
+        } else if (!isCoveredByOpaqueFullscreenChange
+                && isFreeform
+                && type == TRANSIT_TO_FRONT
+                && change.getMode() == TRANSIT_TO_FRONT) {
+            // Set translucent here so TransitionAnimation loads the appropriate animations for
+            // translucent activities and tasks later
+            translucent = (changeFlags & FLAG_TRANSLUCENT) != 0;
+            // Bring the minimized Task back to front
+            animAttr = R.styleable.WindowAnimation_activityOpenEnterAnimation;
         } else if (type == TRANSIT_OPEN) {
             // We will translucent open animation for translucent activities and tasks. Choose
             // WindowAnimation_activityOpenEnterAnimation and set translucent here, then
@@ -417,4 +439,25 @@
 
         return edgeExtensionLayer;
     }
+
+    /**
+     * Returns whether there is an opaque fullscreen Change positioned in front of the given Change
+     * in the given TransitionInfo.
+     */
+    static boolean isCoveredByOpaqueFullscreenChange(
+            TransitionInfo info, TransitionInfo.Change change) {
+        // TransitionInfo#getChanges() are ordered from front to back
+        for (TransitionInfo.Change coveringChange : info.getChanges()) {
+            if (coveringChange == change) {
+                return false;
+            }
+            if ((coveringChange.getFlags() & FLAG_TRANSLUCENT) == 0
+                    && coveringChange.getTaskInfo() != null
+                    && coveringChange.getTaskInfo().getWindowingMode()
+                    == WindowConfiguration.WINDOWING_MODE_FULLSCREEN) {
+                return true;
+            }
+        }
+        return false;
+    }
 }
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/windowdecor/HandleMenu.kt b/libs/WindowManager/Shell/src/com/android/wm/shell/windowdecor/HandleMenu.kt
index 39595cf..bce233f 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/windowdecor/HandleMenu.kt
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/windowdecor/HandleMenu.kt
@@ -15,6 +15,7 @@
  */
 package com.android.wm.shell.windowdecor
 
+import android.annotation.DimenRes
 import android.app.ActivityManager
 import android.app.WindowConfiguration
 import android.app.WindowConfiguration.WINDOWING_MODE_FREEFORM
@@ -75,10 +76,17 @@
     private val isViewAboveStatusBar: Boolean
         get() = (Flags.enableAdditionalWindowsAboveStatusBar() && !taskInfo.isFreeform)
 
-    private var marginMenuTop = 0
-    private var marginMenuStart = 0
-    private var menuHeight = 0
-    private var menuWidth = 0
+    private val pillElevation: Int = loadDimensionPixelSize(
+        R.dimen.desktop_mode_handle_menu_pill_elevation)
+    private val pillTopMargin: Int = loadDimensionPixelSize(
+        R.dimen.desktop_mode_handle_menu_pill_spacing_margin)
+    private val menuWidth = loadDimensionPixelSize(
+        R.dimen.desktop_mode_handle_menu_width) + pillElevation
+    private val menuHeight = getHandleMenuHeight()
+    private val marginMenuTop = loadDimensionPixelSize(R.dimen.desktop_mode_handle_menu_margin_top)
+    private val marginMenuStart = loadDimensionPixelSize(
+        R.dimen.desktop_mode_handle_menu_margin_start)
+
     private var handleMenuAnimator: HandleMenuAnimator? = null
 
     @VisibleForTesting
@@ -120,7 +128,6 @@
         }
 
     init {
-        loadHandleMenuDimensions()
         updateHandleMenuPillPositions()
     }
 
@@ -426,49 +433,35 @@
      */
     private fun viewsLaidOut(): Boolean = handleMenuViewContainer?.view?.isLaidOut ?: false
 
-    private fun loadHandleMenuDimensions() {
-        val resources = context.resources
-        menuWidth = loadDimensionPixelSize(resources, R.dimen.desktop_mode_handle_menu_width)
-        menuHeight = getHandleMenuHeight(resources)
-        marginMenuTop = loadDimensionPixelSize(
-            resources,
-            R.dimen.desktop_mode_handle_menu_margin_top
-        )
-        marginMenuStart = loadDimensionPixelSize(
-            resources,
-            R.dimen.desktop_mode_handle_menu_margin_start
-        )
-    }
-
     /**
-     * Determines handle menu height based on if windowing pill should be shown.
+     * Determines handle menu height based the max size and the visibility of pills.
      */
-    private fun getHandleMenuHeight(resources: Resources): Int {
-        var menuHeight = loadDimensionPixelSize(resources, R.dimen.desktop_mode_handle_menu_height)
+    private fun getHandleMenuHeight(): Int {
+        var menuHeight = loadDimensionPixelSize(
+            R.dimen.desktop_mode_handle_menu_height) + pillElevation
         if (!shouldShowWindowingPill) {
             menuHeight -= loadDimensionPixelSize(
-                resources,
-                R.dimen.desktop_mode_handle_menu_windowing_pill_height
-            )
+                R.dimen.desktop_mode_handle_menu_windowing_pill_height)
+            menuHeight -= pillTopMargin
         }
         if (!SHOULD_SHOW_MORE_ACTIONS_PILL) {
             menuHeight -= loadDimensionPixelSize(
-                resources,
-                R.dimen.desktop_mode_handle_menu_more_actions_pill_height
-            )
+                R.dimen.desktop_mode_handle_menu_more_actions_pill_height)
+            menuHeight -= pillTopMargin
         }
         if (!shouldShowBrowserPill) {
-            menuHeight -= loadDimensionPixelSize(resources,
+            menuHeight -= loadDimensionPixelSize(
                 R.dimen.desktop_mode_handle_menu_open_in_browser_pill_height)
+            menuHeight -= pillTopMargin
         }
         return menuHeight
     }
 
-    private fun loadDimensionPixelSize(resources: Resources, resourceId: Int): Int {
+    private fun loadDimensionPixelSize(@DimenRes resourceId: Int): Int {
         if (resourceId == Resources.ID_NULL) {
             return 0
         }
-        return resources.getDimensionPixelSize(resourceId)
+        return context.resources.getDimensionPixelSize(resourceId)
     }
 
     fun close() {
diff --git a/libs/WindowManager/Shell/tests/flicker/service/src/com/android/wm/shell/flicker/service/desktopmode/scenarios/ExitDesktopWithDragToTopDragZone.kt b/libs/WindowManager/Shell/tests/flicker/service/src/com/android/wm/shell/flicker/service/desktopmode/scenarios/ExitDesktopWithDragToTopDragZone.kt
new file mode 100644
index 0000000..0b6c9af
--- /dev/null
+++ b/libs/WindowManager/Shell/tests/flicker/service/src/com/android/wm/shell/flicker/service/desktopmode/scenarios/ExitDesktopWithDragToTopDragZone.kt
@@ -0,0 +1,68 @@
+/*
+ * Copyright (C) 2024 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.wm.shell.flicker.service.desktopmode.scenarios
+
+import android.app.Instrumentation
+import android.tools.NavBar
+import android.tools.Rotation
+import android.tools.traces.parsers.WindowManagerStateHelper
+import androidx.test.platform.app.InstrumentationRegistry
+import androidx.test.uiautomator.UiDevice
+import com.android.launcher3.tapl.LauncherInstrumentation
+import com.android.server.wm.flicker.helpers.DesktopModeAppHelper
+import com.android.server.wm.flicker.helpers.SimpleAppHelper
+import com.android.window.flags.Flags
+import com.android.wm.shell.flicker.service.common.Utils
+import org.junit.After
+import org.junit.Assume
+import org.junit.Before
+import org.junit.Ignore
+import org.junit.Rule
+import org.junit.Test
+
+
+@Ignore("Base Test Class")
+abstract class ExitDesktopWithDragToTopDragZone
+@JvmOverloads
+constructor(val rotation: Rotation = Rotation.ROTATION_0) {
+
+    private val instrumentation: Instrumentation = InstrumentationRegistry.getInstrumentation()
+    private val tapl = LauncherInstrumentation()
+    private val wmHelper = WindowManagerStateHelper(instrumentation)
+    private val device = UiDevice.getInstance(instrumentation)
+    private val testApp = DesktopModeAppHelper(SimpleAppHelper(instrumentation))
+
+    @Rule @JvmField val testSetupRule = Utils.testSetupRule(NavBar.MODE_GESTURAL, rotation)
+
+    @Before
+    fun setup() {
+        Assume.assumeTrue(Flags.enableDesktopWindowingMode() && tapl.isTablet)
+        tapl.setEnableRotation(true)
+        tapl.setExpectedRotation(rotation.value)
+        testApp.enterDesktopWithDrag(wmHelper, device)
+    }
+
+    @Test
+    open fun exitDesktopWithDragToTopDragZone() {
+        testApp.exitDesktopWithDragToTopDragZone(wmHelper, device)
+    }
+
+    @After
+    fun teardown() {
+        testApp.exit(wmHelper)
+    }
+}
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 4d1b6ba..f670434 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
@@ -313,6 +313,7 @@
   @Test
   @EnableFlags(Flags.FLAG_ENABLE_DESKTOP_WINDOWING_WALLPAPER_ACTIVITY)
   fun showDesktopApps_onSecondaryDisplay_desktopWallpaperEnabled_shouldNotShowWallpaper() {
+    val homeTask = setUpHomeTask(SECOND_DISPLAY)
     val task1 = setUpFreeformTask(SECOND_DISPLAY)
     val task2 = setUpFreeformTask(SECOND_DISPLAY)
     markTaskHidden(task1)
@@ -321,10 +322,11 @@
     controller.showDesktopApps(SECOND_DISPLAY, RemoteTransition(TestRemoteTransition()))
 
     val wct = getLatestWct(type = TRANSIT_TO_FRONT, handlerClass = OneShotRemoteHandler::class.java)
-    assertThat(wct.hierarchyOps).hasSize(2)
-    // Expect order to be from bottom: task1, task2 (no wallpaper intent)
-    wct.assertReorderAt(index = 0, task1)
-    wct.assertReorderAt(index = 1, task2)
+    assertThat(wct.hierarchyOps).hasSize(3)
+    // Expect order to be from bottom: home, task1, task2 (no wallpaper intent)
+    wct.assertReorderAt(index = 0, homeTask)
+    wct.assertReorderAt(index = 1, task1)
+    wct.assertReorderAt(index = 2, task2)
   }
 
   @Test
@@ -349,6 +351,7 @@
   @Test
   @DisableFlags(Flags.FLAG_ENABLE_DESKTOP_WINDOWING_WALLPAPER_ACTIVITY)
   fun showDesktopApps_onSecondaryDisplay_desktopWallpaperDisabled_shouldNotMoveLauncher() {
+    val homeTask = setUpHomeTask(SECOND_DISPLAY)
     val task1 = setUpFreeformTask(SECOND_DISPLAY)
     val task2 = setUpFreeformTask(SECOND_DISPLAY)
     markTaskHidden(task1)
@@ -357,9 +360,11 @@
     controller.showDesktopApps(SECOND_DISPLAY, RemoteTransition(TestRemoteTransition()))
 
     val wct = getLatestWct(type = TRANSIT_TO_FRONT, handlerClass = OneShotRemoteHandler::class.java)
-    assertThat(wct.hierarchyOps).hasSize(2)
-    wct.assertReorderAt(index = 0, task1)
-    wct.assertReorderAt(index = 1, task2)
+    assertThat(wct.hierarchyOps).hasSize(3)
+    // Expect order to be from bottom: home, task1, task2
+    wct.assertReorderAt(index = 0, homeTask)
+    wct.assertReorderAt(index = 1, task1)
+    wct.assertReorderAt(index = 2, task2)
   }
 
   @Test
@@ -460,6 +465,7 @@
   @Test
   @EnableFlags(Flags.FLAG_ENABLE_DESKTOP_WINDOWING_WALLPAPER_ACTIVITY)
   fun showDesktopApps_twoDisplays_bringsToFrontOnlyOneDisplay_desktopWallpaperEnabled() {
+    val homeTaskDefaultDisplay = setUpHomeTask(DEFAULT_DISPLAY)
     val taskDefaultDisplay = setUpFreeformTask(DEFAULT_DISPLAY)
     setUpHomeTask(SECOND_DISPLAY)
     val taskSecondDisplay = setUpFreeformTask(SECOND_DISPLAY)
@@ -469,10 +475,13 @@
     controller.showDesktopApps(DEFAULT_DISPLAY, RemoteTransition(TestRemoteTransition()))
 
     val wct = getLatestWct(type = TRANSIT_TO_FRONT, handlerClass = OneShotRemoteHandler::class.java)
-    assertThat(wct.hierarchyOps).hasSize(2)
-    // Expect order to be from bottom: wallpaper intent, task
-    wct.assertPendingIntentAt(index = 0, desktopWallpaperIntent)
-    wct.assertReorderAt(index = 1, taskDefaultDisplay)
+    assertThat(wct.hierarchyOps).hasSize(3)
+    // Move home to front
+    wct.assertReorderAt(index = 0, homeTaskDefaultDisplay)
+    // Add desktop wallpaper activity
+    wct.assertPendingIntentAt(index = 1, desktopWallpaperIntent)
+    // Move freeform task to front
+    wct.assertReorderAt(index = 2, taskDefaultDisplay)
   }
 
   @Test
@@ -497,7 +506,7 @@
   @Test
   @EnableFlags(Flags.FLAG_ENABLE_DESKTOP_WINDOWING_WALLPAPER_ACTIVITY)
   fun showDesktopApps_desktopWallpaperEnabled_dontReorderMinimizedTask() {
-    setUpHomeTask()
+    val homeTask = setUpHomeTask()
     val freeformTask = setUpFreeformTask()
     val minimizedTask = setUpFreeformTask()
 
@@ -507,11 +516,13 @@
     controller.showDesktopApps(DEFAULT_DISPLAY, RemoteTransition(TestRemoteTransition()))
 
     val wct = getLatestWct(type = TRANSIT_TO_FRONT, handlerClass = OneShotRemoteHandler::class.java)
-    assertThat(wct.hierarchyOps).hasSize(2)
+    assertThat(wct.hierarchyOps).hasSize(3)
+    // Move home to front
+    wct.assertReorderAt(index = 0, homeTask, toTop = true)
     // Add desktop wallpaper activity
-    wct.assertPendingIntentAt(index = 0, desktopWallpaperIntent)
+    wct.assertPendingIntentAt(index = 1, desktopWallpaperIntent)
     // Reorder freeform task to top, don't reorder the minimized task
-    wct.assertReorderAt(index = 1, freeformTask, toTop = true)
+    wct.assertReorderAt(index = 2, freeformTask, toTop = true)
   }
 
   @Test
@@ -894,16 +905,19 @@
     val taskLimit = desktopTasksLimiter.getMaxTaskLimit()
     val freeformTasks = (1..taskLimit).map { _ -> setUpFreeformTask() }
     val newTask = setUpFullscreenTask()
-    setUpHomeTask()
+    val homeTask = setUpHomeTask()
 
     controller.moveToDesktop(newTask, transitionSource = UNKNOWN)
 
     val wct = getLatestEnterDesktopWct()
-    assertThat(wct.hierarchyOps.size).isEqualTo(taskLimit + 1) // visible tasks + wallpaper
+    assertThat(wct.hierarchyOps.size).isEqualTo(taskLimit + 2) // tasks + home + wallpaper
+    // Move home to front
+    wct.assertReorderAt(0, homeTask)
     // Add desktop wallpaper activity
-    wct.assertPendingIntentAt(0, desktopWallpaperIntent)
+    wct.assertPendingIntentAt(1, desktopWallpaperIntent)
+    // Bring freeform tasks to front
     wct.assertReorderSequenceInRange(
-      range = 1..<(taskLimit + 1),
+      range = 2..<(taskLimit + 2),
       *freeformTasks.drop(1).toTypedArray(), // Skipping freeformTasks[0]
       newTask
     )
diff --git a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/transition/DefaultTransitionHandlerTest.java b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/transition/DefaultTransitionHandlerTest.java
index 754a173..6bc7e49 100644
--- a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/transition/DefaultTransitionHandlerTest.java
+++ b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/transition/DefaultTransitionHandlerTest.java
@@ -17,6 +17,7 @@
 package com.android.wm.shell.transition;
 
 import static android.app.WindowConfiguration.ACTIVITY_TYPE_STANDARD;
+import static android.app.WindowConfiguration.WINDOWING_MODE_FREEFORM;
 import static android.app.WindowConfiguration.WINDOWING_MODE_FULLSCREEN;
 import static android.view.WindowManager.TRANSIT_OPEN;
 import static android.view.WindowManager.TRANSIT_SLEEP;
@@ -185,6 +186,73 @@
         verify(startT, never()).setColor(any(), any());
     }
 
+    @Test
+    public void startAnimation_freeformOpenChange_doesntReparentTask() {
+        final TransitionInfo.Change openChange = new ChangeBuilder(TRANSIT_OPEN)
+                .setTask(createTaskInfo(
+                        /* taskId= */ 1, /* windowingMode= */ WINDOWING_MODE_FULLSCREEN))
+                .build();
+        final TransitionInfo info = new TransitionInfoBuilder(TRANSIT_OPEN)
+                .addChange(openChange)
+                .build();
+        final IBinder token = new Binder();
+        final SurfaceControl.Transaction startT = MockTransactionPool.create();
+        final SurfaceControl.Transaction finishT = MockTransactionPool.create();
+
+        mTransitionHandler.startAnimation(token, info, startT, finishT,
+                mock(Transitions.TransitionFinishCallback.class));
+
+        verify(startT, never()).reparent(any(), any());
+    }
+
+    @Test
+    public void startAnimation_freeformMinimizeChange_underFullscreenChange_doesntReparentTask() {
+        final TransitionInfo.Change openChange = new ChangeBuilder(TRANSIT_OPEN)
+                .setTask(createTaskInfo(
+                        /* taskId= */ 1, /* windowingMode= */ WINDOWING_MODE_FULLSCREEN))
+                .build();
+        final TransitionInfo.Change toBackChange = new ChangeBuilder(TRANSIT_TO_BACK)
+                .setTask(createTaskInfo(
+                        /* taskId= */ 2, /* windowingMode= */ WINDOWING_MODE_FREEFORM))
+                .build();
+        final TransitionInfo info = new TransitionInfoBuilder(TRANSIT_OPEN)
+                .addChange(openChange)
+                .addChange(toBackChange)
+                .build();
+        final IBinder token = new Binder();
+        final SurfaceControl.Transaction startT = MockTransactionPool.create();
+        final SurfaceControl.Transaction finishT = MockTransactionPool.create();
+
+        mTransitionHandler.startAnimation(token, info, startT, finishT,
+                mock(Transitions.TransitionFinishCallback.class));
+
+        verify(startT, never()).reparent(any(), any());
+    }
+
+    @Test
+    public void startAnimation_freeform_minimizeAnimation_reparentsTask() {
+        final TransitionInfo.Change openChange = new ChangeBuilder(TRANSIT_OPEN)
+                .setTask(createTaskInfo(
+                        /* taskId= */ 1, /* windowingMode= */ WINDOWING_MODE_FREEFORM))
+                .build();
+        final TransitionInfo.Change toBackChange = new ChangeBuilder(TRANSIT_TO_BACK)
+                .setTask(createTaskInfo(
+                        /* taskId= */ 2, /* windowingMode= */ WINDOWING_MODE_FREEFORM))
+                .build();
+        final TransitionInfo info = new TransitionInfoBuilder(TRANSIT_OPEN)
+                .addChange(openChange)
+                .addChange(toBackChange)
+                .build();
+        final IBinder token = new Binder();
+        final SurfaceControl.Transaction startT = MockTransactionPool.create();
+        final SurfaceControl.Transaction finishT = MockTransactionPool.create();
+
+        mTransitionHandler.startAnimation(token, info, startT, finishT,
+                mock(Transitions.TransitionFinishCallback.class));
+
+        verify(startT).reparent(any(), any());
+    }
+
     private static void mergeSync(Transitions.TransitionHandler handler, IBinder token) {
         handler.mergeAnimation(
                 new Binder(),
@@ -195,10 +263,14 @@
     }
 
     private static RunningTaskInfo createTaskInfo(int taskId) {
+        return createTaskInfo(taskId, WINDOWING_MODE_FULLSCREEN);
+    }
+
+    private static RunningTaskInfo createTaskInfo(int taskId, int windowingMode) {
         RunningTaskInfo taskInfo = new RunningTaskInfo();
         taskInfo.taskId = taskId;
         taskInfo.topActivityType = ACTIVITY_TYPE_STANDARD;
-        taskInfo.configuration.windowConfiguration.setWindowingMode(WINDOWING_MODE_FULLSCREEN);
+        taskInfo.configuration.windowConfiguration.setWindowingMode(windowingMode);
         taskInfo.configuration.windowConfiguration.setActivityType(taskInfo.topActivityType);
         taskInfo.token = mock(WindowContainerToken.class);
         return taskInfo;
diff --git a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/transition/TransitionAnimationHelperTest.kt b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/transition/TransitionAnimationHelperTest.kt
new file mode 100644
index 0000000..bad14bb
--- /dev/null
+++ b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/transition/TransitionAnimationHelperTest.kt
@@ -0,0 +1,126 @@
+/*
+ * Copyright (C) 2024 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.wm.shell.transition
+
+import android.app.ActivityManager.RunningTaskInfo
+import android.app.WindowConfiguration
+import android.view.WindowManager
+import android.window.TransitionInfo
+import android.window.TransitionInfo.FLAG_TRANSLUCENT
+import com.android.internal.R
+import com.android.internal.policy.TransitionAnimation
+import com.android.wm.shell.ShellTestCase
+import com.android.wm.shell.TestRunningTaskInfoBuilder
+import org.junit.Test
+import org.mockito.ArgumentMatchers.anyBoolean
+import org.mockito.ArgumentMatchers.eq
+import org.mockito.Mock
+import org.mockito.Mockito.verify
+
+class TransitionAnimationHelperTest : ShellTestCase() {
+
+    @Mock
+    lateinit var transitionAnimation: TransitionAnimation
+
+    @Test
+    fun loadAttributeAnimation_freeform_taskOpen_taskToBackChange_returnsMinimizeAnim() {
+        val openChange = ChangeBuilder(WindowManager.TRANSIT_OPEN)
+            .setTask(createTaskInfo(WindowConfiguration.WINDOWING_MODE_FREEFORM))
+            .build()
+        val toBackChange = ChangeBuilder(WindowManager.TRANSIT_TO_BACK)
+            .setTask(createTaskInfo(WindowConfiguration.WINDOWING_MODE_FREEFORM))
+            .build()
+        val info = TransitionInfoBuilder(WindowManager.TRANSIT_OPEN)
+            .addChange(openChange)
+            .addChange(toBackChange)
+            .build()
+
+        loadAttributeAnimation(WindowManager.TRANSIT_OPEN, info, toBackChange)
+
+        verify(transitionAnimation).loadDefaultAnimationAttr(
+            eq(R.styleable.WindowAnimation_activityCloseExitAnimation), anyBoolean())
+    }
+
+    @Test
+    fun loadAttributeAnimation_freeform_taskToFront_taskToFrontChange_returnsUnminimizeAnim() {
+        val toFrontChange = ChangeBuilder(WindowManager.TRANSIT_TO_FRONT)
+            .setTask(createTaskInfo(WindowConfiguration.WINDOWING_MODE_FREEFORM))
+            .build()
+        val info = TransitionInfoBuilder(WindowManager.TRANSIT_TO_FRONT)
+            .addChange(toFrontChange)
+            .build()
+
+        loadAttributeAnimation(WindowManager.TRANSIT_TO_FRONT, info, toFrontChange)
+
+        verify(transitionAnimation).loadDefaultAnimationAttr(
+            eq(R.styleable.WindowAnimation_activityOpenEnterAnimation),
+            /* translucent= */ anyBoolean())
+    }
+
+    @Test
+    fun loadAttributeAnimation_fullscreen_taskOpen_returnsTaskOpenEnterAnim() {
+        val openChange = ChangeBuilder(WindowManager.TRANSIT_OPEN)
+            .setTask(createTaskInfo(WindowConfiguration.WINDOWING_MODE_FULLSCREEN))
+            .build()
+        val info = TransitionInfoBuilder(WindowManager.TRANSIT_OPEN).addChange(openChange).build()
+
+        loadAttributeAnimation(WindowManager.TRANSIT_OPEN, info, openChange)
+
+        verify(transitionAnimation).loadDefaultAnimationAttr(
+            eq(R.styleable.WindowAnimation_taskOpenEnterAnimation),
+            /* translucent= */ anyBoolean())
+    }
+
+    @Test
+    fun loadAttributeAnimation_freeform_taskOpen_taskToBackChange_passesTranslucent() {
+        val openChange = ChangeBuilder(WindowManager.TRANSIT_OPEN)
+            .setTask(createTaskInfo(WindowConfiguration.WINDOWING_MODE_FREEFORM))
+            .build()
+        val toBackChange = ChangeBuilder(WindowManager.TRANSIT_TO_BACK)
+            .setTask(createTaskInfo(WindowConfiguration.WINDOWING_MODE_FREEFORM))
+            .setFlags(FLAG_TRANSLUCENT)
+            .build()
+        val info = TransitionInfoBuilder(WindowManager.TRANSIT_OPEN)
+            .addChange(openChange)
+            .addChange(toBackChange)
+            .build()
+
+        loadAttributeAnimation(WindowManager.TRANSIT_OPEN, info, toBackChange)
+
+        verify(transitionAnimation).loadDefaultAnimationAttr(
+            eq(R.styleable.WindowAnimation_activityCloseExitAnimation),
+            /* translucent= */ eq(true))
+    }
+
+    private fun loadAttributeAnimation(
+        @WindowManager.TransitionType type: Int,
+        info: TransitionInfo,
+        change: TransitionInfo.Change,
+        wallpaperTransit: Int = TransitionAnimation.WALLPAPER_TRANSITION_NONE,
+        isDreamTransition: Boolean = false,
+    ) {
+        TransitionAnimationHelper.loadAttributeAnimation(
+            type, info, change, wallpaperTransit, transitionAnimation, isDreamTransition)
+    }
+
+    private fun createTaskInfo(windowingMode: Int): RunningTaskInfo {
+        val taskInfo = TestRunningTaskInfoBuilder()
+            .setWindowingMode(windowingMode)
+            .build()
+        return taskInfo
+    }
+}
\ No newline at end of file
diff --git a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/windowdecor/HandleMenuTest.kt b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/windowdecor/HandleMenuTest.kt
index e0e603ff..adda9a6 100644
--- a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/windowdecor/HandleMenuTest.kt
+++ b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/windowdecor/HandleMenuTest.kt
@@ -94,6 +94,8 @@
 
     private lateinit var handleMenu: HandleMenu
 
+    private val menuWidthWithElevation = MENU_WIDTH + MENU_PILL_ELEVATION
+
     @Before
     fun setUp() {
         val mockAdditionalViewHostViewContainer = AdditionalViewHostViewContainer(
@@ -117,6 +119,9 @@
             addOverride(R.dimen.desktop_mode_handle_menu_height, MENU_HEIGHT)
             addOverride(R.dimen.desktop_mode_handle_menu_margin_top, MENU_TOP_MARGIN)
             addOverride(R.dimen.desktop_mode_handle_menu_margin_start, MENU_START_MARGIN)
+            addOverride(R.dimen.desktop_mode_handle_menu_pill_elevation, MENU_PILL_ELEVATION)
+            addOverride(
+                R.dimen.desktop_mode_handle_menu_pill_spacing_margin, MENU_PILL_SPACING_MARGIN)
         }
         mockDesktopWindowDecoration.mDecorWindowContext = mContext
     }
@@ -129,7 +134,7 @@
         assertTrue(handleMenu.handleMenuViewContainer is AdditionalSystemViewContainer)
         // Verify menu is created at coordinates that, when added to WindowManager,
         // show at the top-center of display.
-        val expected = Point(DISPLAY_BOUNDS.centerX() - MENU_WIDTH / 2, MENU_TOP_MARGIN)
+        val expected = Point(DISPLAY_BOUNDS.centerX() - menuWidthWithElevation / 2, MENU_TOP_MARGIN)
         assertEquals(expected.toPointF(), handleMenu.handleMenuPosition)
     }
 
@@ -152,7 +157,10 @@
         assertTrue(handleMenu.handleMenuViewContainer is AdditionalSystemViewContainer)
         // Verify menu is created at coordinates that, when added to WindowManager,
         // show at the top-center of split left task.
-        val expected = Point(SPLIT_LEFT_BOUNDS.centerX() - MENU_WIDTH / 2, MENU_TOP_MARGIN)
+        val expected = Point(
+            SPLIT_LEFT_BOUNDS.centerX() - menuWidthWithElevation / 2,
+            MENU_TOP_MARGIN
+        )
         assertEquals(expected.toPointF(), handleMenu.handleMenuPosition)
     }
 
@@ -164,7 +172,10 @@
         assertTrue(handleMenu.handleMenuViewContainer is AdditionalSystemViewContainer)
         // Verify menu is created at coordinates that, when added to WindowManager,
         // show at the top-center of split right task.
-        val expected = Point(SPLIT_RIGHT_BOUNDS.centerX() - MENU_WIDTH / 2, MENU_TOP_MARGIN)
+        val expected = Point(
+            SPLIT_RIGHT_BOUNDS.centerX() - menuWidthWithElevation / 2,
+            MENU_TOP_MARGIN
+        )
         assertEquals(expected.toPointF(), handleMenu.handleMenuPosition)
     }
 
@@ -220,5 +231,7 @@
         private const val MENU_HEIGHT = 400
         private const val MENU_TOP_MARGIN = 10
         private const val MENU_START_MARGIN = 20
+        private const val MENU_PILL_ELEVATION = 2
+        private const val MENU_PILL_SPACING_MARGIN = 4
     }
 }
diff --git a/packages/SystemUI/Android.bp b/packages/SystemUI/Android.bp
index 8b33afe..db71d72 100644
--- a/packages/SystemUI/Android.bp
+++ b/packages/SystemUI/Android.bp
@@ -669,6 +669,7 @@
     ],
     asset_dirs: [
         "tests/goldens",
+        "schemas",
     ],
     static_libs: [
         "SystemUI-res",
@@ -708,6 +709,7 @@
         "androidx-constraintlayout_constraintlayout",
         "androidx.exifinterface_exifinterface",
         "androidx.room_room-runtime",
+        "androidx.room_room-testing",
         "androidx.room_room-ktx",
         "androidx.datastore_datastore-preferences",
         "device_state_flags_lib",
diff --git a/packages/SystemUI/compose/features/src/com/android/systemui/keyguard/ui/composable/section/StatusBarSection.kt b/packages/SystemUI/compose/features/src/com/android/systemui/keyguard/ui/composable/section/StatusBarSection.kt
index 166aa70..f9e2252 100644
--- a/packages/SystemUI/compose/features/src/com/android/systemui/keyguard/ui/composable/section/StatusBarSection.kt
+++ b/packages/SystemUI/compose/features/src/com/android/systemui/keyguard/ui/composable/section/StatusBarSection.kt
@@ -28,7 +28,6 @@
 import androidx.compose.ui.platform.LocalContext
 import androidx.compose.ui.unit.dp
 import androidx.compose.ui.viewinterop.AndroidView
-import com.android.compose.animation.scene.ElementKey
 import com.android.compose.animation.scene.SceneScope
 import com.android.compose.modifiers.height
 import com.android.keyguard.dagger.KeyguardStatusBarViewComponent
@@ -83,29 +82,20 @@
                 componentFactory.build(view, provider).keyguardStatusBarViewController
             }
 
-        MovableElement(
-            key = StatusBarElementKey,
-            modifier = modifier,
-        ) {
-            content {
-                AndroidView(
-                    factory = {
-                        notificationPanelView.get().findViewById<View>(R.id.keyguard_header)?.let {
-                            (it.parent as ViewGroup).removeView(it)
-                        }
+        AndroidView(
+            factory = {
+                notificationPanelView.get().findViewById<View>(R.id.keyguard_header)?.let {
+                    (it.parent as ViewGroup).removeView(it)
+                }
 
-                        viewController.init()
-                        view
-                    },
-                    modifier =
-                        Modifier.fillMaxWidth().padding(horizontal = 16.dp).height {
-                            Utils.getStatusBarHeaderHeightKeyguard(context)
-                        },
-                    update = { viewController.setDisplayCutout(viewDisplayCutout) }
-                )
-            }
-        }
+                viewController.init()
+                view
+            },
+            modifier =
+                Modifier.fillMaxWidth().padding(horizontal = 16.dp).height {
+                    Utils.getStatusBarHeaderHeightKeyguard(context)
+                },
+            update = { viewController.setDisplayCutout(viewDisplayCutout) }
+        )
     }
 }
-
-private val StatusBarElementKey = ElementKey("StatusBar")
diff --git a/packages/SystemUI/multivalentTests/src/com/android/systemui/communal/CommunalBackupRestoreStartableTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/communal/CommunalBackupRestoreStartableTest.kt
index 722eb2b..60aea92 100644
--- a/packages/SystemUI/multivalentTests/src/com/android/systemui/communal/CommunalBackupRestoreStartableTest.kt
+++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/communal/CommunalBackupRestoreStartableTest.kt
@@ -20,6 +20,9 @@
 import android.content.Context
 import android.content.Intent
 import android.content.mockedContext
+import android.os.Handler
+import android.os.fakeExecutorHandler
+import android.provider.Settings.Secure.USER_SETUP_COMPLETE
 import androidx.test.ext.junit.runners.AndroidJUnit4
 import androidx.test.filters.SmallTest
 import com.android.systemui.SysuiTestCase
@@ -27,11 +30,13 @@
 import com.android.systemui.broadcast.broadcastDispatcher
 import com.android.systemui.communal.domain.interactor.CommunalInteractor
 import com.android.systemui.communal.widgets.CommunalWidgetModule
+import com.android.systemui.concurrency.fakeExecutor
 import com.android.systemui.kosmos.testScope
 import com.android.systemui.log.logcatLogBuffer
 import com.android.systemui.testKosmos
-import com.android.systemui.util.mockito.any
-import com.android.systemui.util.mockito.kotlinArgumentCaptor
+import com.android.systemui.util.concurrency.FakeExecutor
+import com.android.systemui.util.settings.SecureSettings
+import com.android.systemui.util.settings.fakeSettings
 import com.google.common.truth.Truth.assertThat
 import kotlinx.coroutines.test.runTest
 import org.junit.Before
@@ -41,6 +46,8 @@
 import org.mockito.Mockito.never
 import org.mockito.Mockito.verify
 import org.mockito.MockitoAnnotations
+import org.mockito.kotlin.any
+import org.mockito.kotlin.argumentCaptor
 
 @SmallTest
 @RunWith(AndroidJUnit4::class)
@@ -50,10 +57,13 @@
 
     @Mock private lateinit var communalInteractor: CommunalInteractor
 
-    private val mapCaptor = kotlinArgumentCaptor<Map<Int, Int>>()
+    private val mapCaptor = argumentCaptor<Map<Int, Int>>()
 
     private lateinit var context: Context
     private lateinit var broadcastDispatcher: FakeBroadcastDispatcher
+    private lateinit var secureSettings: SecureSettings
+    private lateinit var handler: Handler
+    private lateinit var fakeExecutor: FakeExecutor
     private lateinit var underTest: CommunalBackupRestoreStartable
 
     @Before
@@ -62,18 +72,28 @@
 
         context = kosmos.mockedContext
         broadcastDispatcher = kosmos.broadcastDispatcher
+        secureSettings = kosmos.fakeSettings
+        handler = kosmos.fakeExecutorHandler
+        fakeExecutor = kosmos.fakeExecutor
+
+        secureSettings.putInt(USER_SETUP_COMPLETE, 0)
 
         underTest =
             CommunalBackupRestoreStartable(
                 broadcastDispatcher,
                 communalInteractor,
                 logcatLogBuffer("CommunalBackupRestoreStartable"),
+                secureSettings,
+                handler,
             )
     }
 
     @Test
-    fun testRestoreWidgetsUponHostRestored() =
+    fun restoreWidgets_userSetUpComplete_performRestore() =
         testScope.runTest {
+            // User set up complete
+            secureSettings.putInt(USER_SETUP_COMPLETE, 1)
+
             underTest.start()
 
             // Verify restore widgets not called
@@ -94,7 +114,7 @@
 
             // Verify restore widgets called
             verify(communalInteractor).restoreWidgets(mapCaptor.capture())
-            val oldToNewWidgetIdMap = mapCaptor.value
+            val oldToNewWidgetIdMap = mapCaptor.firstValue
             assertThat(oldToNewWidgetIdMap)
                 .containsExactlyEntriesIn(
                     mapOf(
@@ -106,10 +126,54 @@
         }
 
     @Test
-    fun testDoNotRestoreWidgetsIfNotForCommunalWidgetHost() =
+    fun restoreWidgets_userSetUpNotComplete_restoreWhenUserSetupComplete() =
         testScope.runTest {
             underTest.start()
 
+            // Verify restore widgets not called
+            verify(communalInteractor, never()).restoreWidgets(any())
+
+            // Trigger app widget host restored
+            val intent =
+                Intent().apply {
+                    action = AppWidgetManager.ACTION_APPWIDGET_HOST_RESTORED
+                    putExtra(
+                        AppWidgetManager.EXTRA_HOST_ID,
+                        CommunalWidgetModule.APP_WIDGET_HOST_ID
+                    )
+                    putExtra(AppWidgetManager.EXTRA_APPWIDGET_OLD_IDS, intArrayOf(1, 2, 3))
+                    putExtra(AppWidgetManager.EXTRA_APPWIDGET_IDS, intArrayOf(7, 8, 9))
+                }
+            broadcastDispatcher.sendIntentToMatchingReceiversOnly(context, intent)
+
+            // Verify restore widgets not called because user setup not complete
+            verify(communalInteractor, never()).restoreWidgets(any())
+
+            // User setup complete
+            secureSettings.putInt(USER_SETUP_COMPLETE, 1)
+            fakeExecutor.runAllReady()
+
+            // Verify restore widgets called
+            verify(communalInteractor).restoreWidgets(mapCaptor.capture())
+            val oldToNewWidgetIdMap = mapCaptor.firstValue
+            assertThat(oldToNewWidgetIdMap)
+                .containsExactlyEntriesIn(
+                    mapOf(
+                        Pair(1, 7),
+                        Pair(2, 8),
+                        Pair(3, 9),
+                    )
+                )
+        }
+
+    @Test
+    fun restoreWidgets_broadcastNotForCommunalWidgetHost_doNotPerformRestore() =
+        testScope.runTest {
+            // User set up complete
+            secureSettings.putInt(USER_SETUP_COMPLETE, 1)
+
+            underTest.start()
+
             // Trigger app widget host restored, but for another host
             val hostId = CommunalWidgetModule.APP_WIDGET_HOST_ID + 1
             val intent =
@@ -126,8 +190,11 @@
         }
 
     @Test
-    fun testAbortRestoreWidgetsIfOldToNewIdsMappingInvalid() =
+    fun restoreWidgets_oldToNewIdsMappingInvalid_abortRestore() =
         testScope.runTest {
+            // User set up complete
+            secureSettings.putInt(USER_SETUP_COMPLETE, 1)
+
             underTest.start()
 
             // Trigger app widget host restored, but new ids list is one too many for old ids
diff --git a/packages/SystemUI/multivalentTests/src/com/android/systemui/communal/CommunalSceneStartableTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/communal/CommunalSceneStartableTest.kt
index cf14547..0de0369 100644
--- a/packages/SystemUI/multivalentTests/src/com/android/systemui/communal/CommunalSceneStartableTest.kt
+++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/communal/CommunalSceneStartableTest.kt
@@ -27,6 +27,9 @@
 import com.android.systemui.coroutines.collectLastValue
 import com.android.systemui.dock.dockManager
 import com.android.systemui.dock.fakeDockManager
+import com.android.systemui.flags.Flags.COMMUNAL_SERVICE_ENABLED
+import com.android.systemui.flags.fakeFeatureFlagsClassic
+import com.android.systemui.flags.featureFlagsClassic
 import com.android.systemui.keyguard.data.repository.fakeKeyguardRepository
 import com.android.systemui.keyguard.data.repository.fakeKeyguardTransitionRepository
 import com.android.systemui.keyguard.domain.interactor.keyguardInteractor
@@ -66,6 +69,7 @@
     fun setUp() {
         with(kosmos) {
             fakeSettings.putInt(Settings.System.SCREEN_OFF_TIMEOUT, SCREEN_TIMEOUT)
+            kosmos.fakeFeatureFlagsClassic.set(COMMUNAL_SERVICE_ENABLED, true)
 
             underTest =
                 CommunalSceneStartable(
@@ -76,6 +80,7 @@
                         keyguardInteractor = keyguardInteractor,
                         systemSettings = fakeSettings,
                         notificationShadeWindowController = notificationShadeWindowController,
+                        featureFlagsClassic = kosmos.fakeFeatureFlagsClassic,
                         applicationScope = applicationCoroutineScope,
                         bgScope = applicationCoroutineScope,
                         mainDispatcher = testDispatcher,
@@ -451,6 +456,24 @@
             }
         }
 
+    @Test
+    fun transitionFromDozingToGlanceableHub_forcesCommunal() =
+        with(kosmos) {
+            testScope.runTest {
+                val scene by collectLastValue(communalSceneInteractor.currentScene)
+                communalSceneInteractor.changeScene(CommunalScenes.Blank)
+                assertThat(scene).isEqualTo(CommunalScenes.Blank)
+
+                fakeKeyguardTransitionRepository.sendTransitionSteps(
+                    from = KeyguardState.DOZING,
+                    to = KeyguardState.GLANCEABLE_HUB,
+                    testScope = this
+                )
+
+                assertThat(scene).isEqualTo(CommunalScenes.Communal)
+            }
+        }
+
     private fun TestScope.updateDocked(docked: Boolean) =
         with(kosmos) {
             runCurrent()
diff --git a/packages/SystemUI/multivalentTests/src/com/android/systemui/communal/data/repository/CommunalWidgetRepositoryImplTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/communal/data/repository/CommunalWidgetRepositoryImplTest.kt
index 6ce6cdb..17234a90 100644
--- a/packages/SystemUI/multivalentTests/src/com/android/systemui/communal/data/repository/CommunalWidgetRepositoryImplTest.kt
+++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/communal/data/repository/CommunalWidgetRepositoryImplTest.kt
@@ -24,6 +24,7 @@
 import android.content.applicationContext
 import android.graphics.Bitmap
 import android.os.UserHandle
+import android.os.userManager
 import androidx.test.ext.junit.runners.AndroidJUnit4
 import androidx.test.filters.SmallTest
 import com.android.systemui.SysuiTestCase
@@ -47,10 +48,6 @@
 import com.android.systemui.log.logcatLogBuffer
 import com.android.systemui.res.R
 import com.android.systemui.testKosmos
-import com.android.systemui.util.mockito.any
-import com.android.systemui.util.mockito.mock
-import com.android.systemui.util.mockito.whenever
-import com.android.systemui.util.mockito.withArgCaptor
 import com.google.common.truth.Truth.assertThat
 import kotlinx.coroutines.ExperimentalCoroutinesApi
 import kotlinx.coroutines.flow.MutableStateFlow
@@ -59,11 +56,16 @@
 import org.junit.Before
 import org.junit.Test
 import org.junit.runner.RunWith
+import org.mockito.ArgumentMatchers.anyInt
 import org.mockito.Mock
 import org.mockito.Mockito.eq
 import org.mockito.Mockito.never
 import org.mockito.Mockito.verify
 import org.mockito.MockitoAnnotations
+import org.mockito.kotlin.any
+import org.mockito.kotlin.argumentCaptor
+import org.mockito.kotlin.mock
+import org.mockito.kotlin.whenever
 
 @OptIn(ExperimentalCoroutinesApi::class)
 @SmallTest
@@ -77,6 +79,9 @@
     @Mock private lateinit var communalWidgetDao: CommunalWidgetDao
     @Mock private lateinit var backupManager: BackupManager
 
+    private val communalHubStateCaptor = argumentCaptor<CommunalHubState>()
+    private val componentNameCaptor = argumentCaptor<ComponentName>()
+
     private lateinit var backupUtils: CommunalBackupUtils
     private lateinit var logBuffer: LogBuffer
     private lateinit var fakeWidgets: MutableStateFlow<Map<CommunalItemRank, CommunalWidgetItem>>
@@ -85,6 +90,10 @@
     private val kosmos = testKosmos()
     private val testScope = kosmos.testScope
     private val packageChangeRepository = kosmos.fakePackageChangeRepository
+    private val userManager = kosmos.userManager
+
+    private val mainUser = UserHandle(0)
+    private val workProfile = UserHandle(10)
 
     private val fakeAllowlist =
         listOf(
@@ -109,6 +118,9 @@
 
         whenever(communalWidgetDao.getWidgets()).thenReturn(fakeWidgets)
         whenever(communalWidgetHost.appWidgetProviders).thenReturn(fakeProviders)
+        whenever(userManager.mainUser).thenReturn(mainUser)
+
+        restoreUser(mainUser)
 
         underTest =
             CommunalWidgetRepositoryImpl(
@@ -121,6 +133,7 @@
                 backupManager,
                 backupUtils,
                 packageChangeRepository,
+                userManager,
             )
     }
 
@@ -128,7 +141,7 @@
     fun communalWidgets_queryWidgetsFromDb() =
         testScope.runTest {
             val communalItemRankEntry = CommunalItemRank(uid = 1L, rank = 1)
-            val communalWidgetItemEntry = CommunalWidgetItem(uid = 1L, 1, "pk_name/cls_name", 1L)
+            val communalWidgetItemEntry = CommunalWidgetItem(uid = 1L, 1, "pk_name/cls_name", 1L, 0)
             fakeWidgets.value = mapOf(communalItemRankEntry to communalWidgetItemEntry)
             fakeProviders.value = mapOf(1 to providerInfoA)
 
@@ -154,13 +167,13 @@
             fakeWidgets.value =
                 mapOf(
                     CommunalItemRank(uid = 1L, rank = 1) to
-                        CommunalWidgetItem(uid = 1L, 1, "pk_1/cls_1", 1L),
+                        CommunalWidgetItem(uid = 1L, 1, "pk_1/cls_1", 1L, 0),
                     CommunalItemRank(uid = 2L, rank = 2) to
-                        CommunalWidgetItem(uid = 2L, 2, "pk_2/cls_2", 2L),
+                        CommunalWidgetItem(uid = 2L, 2, "pk_2/cls_2", 2L, 0),
                     CommunalItemRank(uid = 3L, rank = 3) to
-                        CommunalWidgetItem(uid = 3L, 3, "pk_3/cls_3", 3L),
+                        CommunalWidgetItem(uid = 3L, 3, "pk_3/cls_3", 3L, 0),
                     CommunalItemRank(uid = 4L, rank = 4) to
-                        CommunalWidgetItem(uid = 4L, 4, "pk_4/cls_4", 4L),
+                        CommunalWidgetItem(uid = 4L, 4, "pk_4/cls_4", 4L, 0),
                 )
             fakeProviders.value =
                 mapOf(
@@ -192,9 +205,9 @@
             fakeWidgets.value =
                 mapOf(
                     CommunalItemRank(uid = 1L, rank = 1) to
-                        CommunalWidgetItem(uid = 1L, 1, "pk_1/cls_1", 1L),
+                        CommunalWidgetItem(uid = 1L, 1, "pk_1/cls_1", 1L, 0),
                     CommunalItemRank(uid = 2L, rank = 2) to
-                        CommunalWidgetItem(uid = 2L, 2, "pk_2/cls_2", 2L),
+                        CommunalWidgetItem(uid = 2L, 2, "pk_2/cls_2", 2L, 0),
                 )
             fakeProviders.value =
                 mapOf(
@@ -249,7 +262,6 @@
             val provider = ComponentName("pkg_name", "cls_name")
             val id = 1
             val priority = 1
-            val user = UserHandle(0)
             whenever(communalWidgetHost.getAppWidgetInfo(id))
                 .thenReturn(PROVIDER_INFO_REQUIRES_CONFIGURATION)
             whenever(
@@ -259,11 +271,12 @@
                     )
                 )
                 .thenReturn(id)
-            underTest.addWidget(provider, user, priority, kosmos.widgetConfiguratorSuccess)
+            underTest.addWidget(provider, mainUser, priority, kosmos.widgetConfiguratorSuccess)
             runCurrent()
 
-            verify(communalWidgetHost).allocateIdAndBindWidget(provider, user)
-            verify(communalWidgetDao).addWidget(id, provider, priority)
+            verify(communalWidgetHost).allocateIdAndBindWidget(provider, mainUser)
+            verify(communalWidgetDao)
+                .addWidget(id, provider, priority, testUserSerialNumber(mainUser))
 
             // Verify backup requested
             verify(backupManager).dataChanged()
@@ -275,7 +288,6 @@
             val provider = ComponentName("pkg_name", "cls_name")
             val id = 1
             val priority = 1
-            val user = UserHandle(0)
             whenever(communalWidgetHost.getAppWidgetInfo(id))
                 .thenReturn(PROVIDER_INFO_REQUIRES_CONFIGURATION)
             whenever(
@@ -285,11 +297,12 @@
                     )
                 )
                 .thenReturn(id)
-            underTest.addWidget(provider, user, priority, kosmos.widgetConfiguratorFail)
+            underTest.addWidget(provider, mainUser, priority, kosmos.widgetConfiguratorFail)
             runCurrent()
 
-            verify(communalWidgetHost).allocateIdAndBindWidget(provider, user)
-            verify(communalWidgetDao, never()).addWidget(id, provider, priority)
+            verify(communalWidgetHost).allocateIdAndBindWidget(provider, mainUser)
+            verify(communalWidgetDao, never())
+                .addWidget(anyInt(), any<ComponentName>(), anyInt(), anyInt())
             verify(appWidgetHost).deleteAppWidgetId(id)
 
             // Verify backup not requested
@@ -302,7 +315,6 @@
             val provider = ComponentName("pkg_name", "cls_name")
             val id = 1
             val priority = 1
-            val user = UserHandle(0)
             whenever(communalWidgetHost.getAppWidgetInfo(id))
                 .thenReturn(PROVIDER_INFO_REQUIRES_CONFIGURATION)
             whenever(
@@ -312,13 +324,14 @@
                     )
                 )
                 .thenReturn(id)
-            underTest.addWidget(provider, user, priority) {
+            underTest.addWidget(provider, mainUser, priority) {
                 throw IllegalStateException("some error")
             }
             runCurrent()
 
-            verify(communalWidgetHost).allocateIdAndBindWidget(provider, user)
-            verify(communalWidgetDao, never()).addWidget(id, provider, priority)
+            verify(communalWidgetHost).allocateIdAndBindWidget(provider, mainUser)
+            verify(communalWidgetDao, never())
+                .addWidget(anyInt(), any<ComponentName>(), anyInt(), anyInt())
             verify(appWidgetHost).deleteAppWidgetId(id)
 
             // Verify backup not requested
@@ -331,7 +344,6 @@
             val provider = ComponentName("pkg_name", "cls_name")
             val id = 1
             val priority = 1
-            val user = UserHandle(0)
             whenever(communalWidgetHost.getAppWidgetInfo(id))
                 .thenReturn(PROVIDER_INFO_CONFIGURATION_OPTIONAL)
             whenever(
@@ -341,11 +353,12 @@
                     )
                 )
                 .thenReturn(id)
-            underTest.addWidget(provider, user, priority, kosmos.widgetConfiguratorFail)
+            underTest.addWidget(provider, mainUser, priority, kosmos.widgetConfiguratorFail)
             runCurrent()
 
-            verify(communalWidgetHost).allocateIdAndBindWidget(provider, user)
-            verify(communalWidgetDao).addWidget(id, provider, priority)
+            verify(communalWidgetHost).allocateIdAndBindWidget(provider, mainUser)
+            verify(communalWidgetDao)
+                .addWidget(id, provider, priority, testUserSerialNumber(mainUser))
 
             // Verify backup requested
             verify(backupManager).dataChanged()
@@ -444,11 +457,8 @@
             runCurrent()
 
             // Verify state restored, and widget 2 skipped
-            val restoredState =
-                withArgCaptor<CommunalHubState> {
-                    verify(communalWidgetDao).restoreCommunalHubState(capture())
-                }
-            val restoredWidgets = restoredState.widgets.toList()
+            verify(communalWidgetDao).restoreCommunalHubState(communalHubStateCaptor.capture())
+            val restoredWidgets = communalHubStateCaptor.firstValue.widgets.toList()
             assertThat(restoredWidgets).hasSize(1)
 
             val restoredWidget = restoredWidgets.first()
@@ -474,11 +484,8 @@
             runCurrent()
 
             // Verify widget 1 and 2 are restored, and are now 11 and 12.
-            val restoredState =
-                withArgCaptor<CommunalHubState> {
-                    verify(communalWidgetDao).restoreCommunalHubState(capture())
-                }
-            val restoredWidgets = restoredState.widgets.toList()
+            verify(communalWidgetDao).restoreCommunalHubState(communalHubStateCaptor.capture())
+            val restoredWidgets = communalHubStateCaptor.firstValue.widgets.toList()
             assertThat(restoredWidgets).hasSize(2)
 
             val restoredWidget1 = restoredWidgets[0]
@@ -512,11 +519,8 @@
             runCurrent()
 
             // Verify widget 1 and 2 are restored, and are now 1 and 12.
-            val restoredState =
-                withArgCaptor<CommunalHubState> {
-                    verify(communalWidgetDao).restoreCommunalHubState(capture())
-                }
-            val restoredWidgets = restoredState.widgets.toList()
+            verify(communalWidgetDao).restoreCommunalHubState(communalHubStateCaptor.capture())
+            val restoredWidgets = communalHubStateCaptor.firstValue.widgets.toList()
             assertThat(restoredWidgets).hasSize(2)
 
             val restoredWidget1 = restoredWidgets[0]
@@ -533,14 +537,134 @@
         }
 
     @Test
+    fun restoreWidgets_undefinedUser_restoredAsMain() =
+        testScope.runTest {
+            // Write two widgets to file, both of which have user serial number undefined.
+            val fakeState =
+                CommunalHubState().apply {
+                    widgets =
+                        listOf(
+                                CommunalHubState.CommunalWidgetItem().apply {
+                                    widgetId = 1
+                                    componentName = "pk_name/fake_widget_1"
+                                    rank = 1
+                                    userSerialNumber =
+                                        CommunalWidgetItem.USER_SERIAL_NUMBER_UNDEFINED
+                                },
+                                CommunalHubState.CommunalWidgetItem().apply {
+                                    widgetId = 2
+                                    componentName = "pk_name/fake_widget_2"
+                                    rank = 2
+                                    userSerialNumber =
+                                        CommunalWidgetItem.USER_SERIAL_NUMBER_UNDEFINED
+                                },
+                            )
+                            .toTypedArray()
+                }
+            backupUtils.writeBytesToDisk(fakeState.toByteArray())
+
+            // Set up app widget host with widget ids.
+            setAppWidgetIds(listOf(11, 12))
+
+            // Restore widgets.
+            underTest.restoreWidgets(mapOf(Pair(1, 11), Pair(2, 12)))
+            runCurrent()
+
+            // Verify widget 1 and 2 are restored with the main user.
+            verify(communalWidgetDao).restoreCommunalHubState(communalHubStateCaptor.capture())
+            val restoredWidgets = communalHubStateCaptor.firstValue.widgets.toList()
+            assertThat(restoredWidgets).hasSize(2)
+
+            val restoredWidget1 = restoredWidgets[0]
+            assertThat(restoredWidget1.widgetId).isEqualTo(11)
+            assertThat(restoredWidget1.userSerialNumber).isEqualTo(testUserSerialNumber(mainUser))
+
+            val restoredWidget2 = restoredWidgets[1]
+            assertThat(restoredWidget2.widgetId).isEqualTo(12)
+            assertThat(restoredWidget2.userSerialNumber).isEqualTo(testUserSerialNumber(mainUser))
+        }
+
+    @Test
+    fun restoreWidgets_workProfileNotRestored_widgetSkipped() =
+        testScope.runTest {
+            // Write fake state to file
+            backupUtils.writeBytesToDisk(fakeStateWithWorkProfile.toByteArray())
+
+            // Set up app widget host with widget ids.
+            // (b/349852237) It's possible that the platform restores widgets even though their user
+            // is not restored.
+            setAppWidgetIds(listOf(11, 12))
+
+            // Restore widgets.
+            underTest.restoreWidgets(mapOf(Pair(1, 11), Pair(2, 12)))
+            runCurrent()
+
+            // Verify only widget 1 is restored. Widget 2 is skipped because it belongs to a work
+            // profile, which is not restored.
+            verify(communalWidgetDao).restoreCommunalHubState(communalHubStateCaptor.capture())
+            val restoredWidgets = communalHubStateCaptor.firstValue.widgets.toList()
+            assertThat(restoredWidgets).hasSize(1)
+
+            val restoredWidget = restoredWidgets[0]
+            assertThat(restoredWidget.widgetId).isEqualTo(11)
+            assertThat(restoredWidget.userSerialNumber).isEqualTo(testUserSerialNumber(mainUser))
+        }
+
+    @Test
+    fun restoreWidgets_workProfileRestored_manuallyBindWidget() =
+        testScope.runTest {
+            // Write fake state to file
+            backupUtils.writeBytesToDisk(fakeStateWithWorkProfile.toByteArray())
+
+            // Set up app widget host with widget ids.
+            // (b/349852237) It's possible that the platform restores widgets even though their user
+            // is not restored.
+            setAppWidgetIds(listOf(11, 12))
+
+            // Restore work profile.
+            restoreUser(workProfile)
+
+            val newWidgetId = 13
+            whenever(communalWidgetHost.allocateIdAndBindWidget(any(), any()))
+                .thenReturn(newWidgetId)
+
+            // Restore widgets.
+            underTest.restoreWidgets(mapOf(Pair(1, 11), Pair(2, 12)))
+            runCurrent()
+
+            // Verify widget 1 is restored.
+            verify(communalWidgetDao).restoreCommunalHubState(communalHubStateCaptor.capture())
+            val restoredWidgets = communalHubStateCaptor.firstValue.widgets.toList()
+            assertThat(restoredWidgets).hasSize(1)
+
+            val restoredWidget = restoredWidgets[0]
+            assertThat(restoredWidget.widgetId).isEqualTo(11)
+            assertThat(restoredWidget.userSerialNumber).isEqualTo(testUserSerialNumber(mainUser))
+
+            // Verify widget 2 (now 12) is removed from platform
+            verify(appWidgetHost).deleteAppWidgetId(12)
+
+            // Verify work profile widget is manually bound
+            verify(communalWidgetDao)
+                .addWidget(
+                    eq(newWidgetId),
+                    componentNameCaptor.capture(),
+                    eq(2),
+                    eq(testUserSerialNumber(workProfile))
+                )
+            assertThat(componentNameCaptor.firstValue)
+                .isEqualTo(ComponentName("pk_name", "fake_widget_2"))
+        }
+
+    @Test
     fun pendingWidgets() =
         testScope.runTest {
             fakeWidgets.value =
                 mapOf(
                     CommunalItemRank(uid = 1L, rank = 1) to
-                        CommunalWidgetItem(uid = 1L, 1, "pk_1/cls_1", 1L),
+                        CommunalWidgetItem(uid = 1L, 1, "pk_1/cls_1", 1L, 0),
                     CommunalItemRank(uid = 2L, rank = 2) to
-                        CommunalWidgetItem(uid = 2L, 2, "pk_2/cls_2", 2L),
+                        CommunalWidgetItem(uid = 2L, 2, "pk_2/cls_2", 2L, 0),
                 )
 
             // Widget 1 is installed
@@ -554,7 +678,7 @@
                         sessionId = 1,
                         packageName = "pk_2",
                         icon = fakeIcon,
-                        user = UserHandle.CURRENT,
+                        user = mainUser,
                     )
                 )
             )
@@ -572,7 +696,7 @@
                         priority = 2,
                         packageName = "pk_2",
                         icon = fakeIcon,
-                        user = UserHandle.CURRENT,
+                        user = mainUser,
                     ),
                 )
         }
@@ -583,7 +707,7 @@
             fakeWidgets.value =
                 mapOf(
                     CommunalItemRank(uid = 1L, rank = 1) to
-                        CommunalWidgetItem(uid = 1L, 1, "pk_1/cls_1", 1L),
+                        CommunalWidgetItem(uid = 1L, 1, "pk_1/cls_1", 1L, 0),
                 )
 
             // Widget 1 is pending install
@@ -594,7 +718,7 @@
                         sessionId = 1,
                         packageName = "pk_1",
                         icon = fakeIcon,
-                        user = UserHandle.CURRENT,
+                        user = mainUser,
                     )
                 )
             )
@@ -607,7 +731,7 @@
                         priority = 1,
                         packageName = "pk_1",
                         icon = fakeIcon,
-                        user = UserHandle.CURRENT,
+                        user = mainUser,
                     ),
                 )
 
@@ -633,6 +757,20 @@
         whenever(appWidgetHost.appWidgetIds).thenReturn(ids.toIntArray())
     }
 
+    // Commonly the user id and user serial number are the same, but for testing purposes use a
+    // simple algorithm to map a user id to a different user serial number to make sure the correct
+    // value is used.
+    private fun testUserSerialNumber(user: UserHandle): Int {
+        return user.identifier + 100
+    }
+
+    private fun restoreUser(user: UserHandle) {
+        whenever(backupManager.getUserForAncestralSerialNumber(user.identifier.toLong()))
+            .thenReturn(user)
+        whenever(userManager.getUserSerialNumber(user.identifier))
+            .thenReturn(testUserSerialNumber(user))
+    }
+
     private companion object {
         val PROVIDER_INFO_REQUIRES_CONFIGURATION =
             AppWidgetProviderInfo().apply { configure = ComponentName("test.pkg", "test.cmp") }
@@ -650,11 +788,32 @@
                                 widgetId = 1
                                 componentName = "pk_name/fake_widget_1"
                                 rank = 1
+                                userSerialNumber = 0
                             },
                             CommunalHubState.CommunalWidgetItem().apply {
                                 widgetId = 2
                                 componentName = "pk_name/fake_widget_2"
                                 rank = 2
+                                userSerialNumber = 0
+                            },
+                        )
+                        .toTypedArray()
+            }
+        val fakeStateWithWorkProfile =
+            CommunalHubState().apply {
+                widgets =
+                    listOf(
+                            CommunalHubState.CommunalWidgetItem().apply {
+                                widgetId = 1
+                                componentName = "pk_name/fake_widget_1"
+                                rank = 1
+                                userSerialNumber = 0
+                            },
+                            CommunalHubState.CommunalWidgetItem().apply {
+                                widgetId = 2
+                                componentName = "pk_name/fake_widget_2"
+                                rank = 2
+                                userSerialNumber = 10
                             },
                         )
                         .toTypedArray()
diff --git a/packages/SystemUI/multivalentTests/src/com/android/systemui/keyguard/domain/interactor/FromDozingTransitionInteractorTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/keyguard/domain/interactor/FromDozingTransitionInteractorTest.kt
index 612f2e7..ec4fd79 100644
--- a/packages/SystemUI/multivalentTests/src/com/android/systemui/keyguard/domain/interactor/FromDozingTransitionInteractorTest.kt
+++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/keyguard/domain/interactor/FromDozingTransitionInteractorTest.kt
@@ -34,12 +34,14 @@
 
 import android.os.PowerManager
 import android.platform.test.annotations.EnableFlags
+import android.service.dream.dreamManager
 import androidx.test.ext.junit.runners.AndroidJUnit4
 import androidx.test.filters.SmallTest
 import com.android.compose.animation.scene.ObservableTransitionState
 import com.android.systemui.Flags
 import com.android.systemui.SysuiTestCase
 import com.android.systemui.communal.data.repository.fakeCommunalSceneRepository
+import com.android.systemui.communal.domain.interactor.setCommunalAvailable
 import com.android.systemui.communal.shared.model.CommunalScenes
 import com.android.systemui.keyguard.data.repository.FakeKeyguardTransitionRepository
 import com.android.systemui.keyguard.data.repository.fakeKeyguardRepository
@@ -64,8 +66,10 @@
 import org.junit.Before
 import org.junit.Test
 import org.junit.runner.RunWith
+import org.mockito.Mockito.anyBoolean
 import org.mockito.Mockito.reset
 import org.mockito.Mockito.spy
+import org.mockito.kotlin.whenever
 
 @OptIn(ExperimentalCoroutinesApi::class)
 @SmallTest
@@ -120,6 +124,66 @@
 
     @Test
     @EnableFlags(Flags.FLAG_KEYGUARD_WM_STATE_REFACTOR)
+    fun testTransitionToLockscreen_onPowerButtonPress_canDream_glanceableHubAvailable() =
+        testScope.runTest {
+            whenever(kosmos.dreamManager.canStartDreaming(anyBoolean())).thenReturn(true)
+            kosmos.setCommunalAvailable(true)
+            runCurrent()
+
+            powerInteractor.setAwakeForTest(reason = PowerManager.WAKE_REASON_POWER_BUTTON)
+            runCurrent()
+
+            // If dreaming is possible and communal is available, then we should transition to
+            // GLANCEABLE_HUB when waking up due to power button press.
+            assertThat(transitionRepository)
+                .startedTransition(
+                    from = KeyguardState.DOZING,
+                    to = KeyguardState.GLANCEABLE_HUB,
+                )
+        }
+
+    @Test
+    @EnableFlags(Flags.FLAG_KEYGUARD_WM_STATE_REFACTOR)
+    fun testTransitionToLockscreen_onPowerButtonPress_canNotDream_glanceableHubAvailable() =
+        testScope.runTest {
+            whenever(kosmos.dreamManager.canStartDreaming(anyBoolean())).thenReturn(false)
+            kosmos.setCommunalAvailable(true)
+            runCurrent()
+
+            powerInteractor.setAwakeForTest(reason = PowerManager.WAKE_REASON_POWER_BUTTON)
+            runCurrent()
+
+            // If dreaming is NOT possible but communal is available, then we should transition to
+            // LOCKSCREEN when waking up due to power button press.
+            assertThat(transitionRepository)
+                .startedTransition(
+                    from = KeyguardState.DOZING,
+                    to = KeyguardState.LOCKSCREEN,
+                )
+        }
+
+    @Test
+    @EnableFlags(Flags.FLAG_KEYGUARD_WM_STATE_REFACTOR)
+    fun testTransitionToLockscreen_onPowerButtonPress_canNDream_glanceableHubNotAvailable() =
+        testScope.runTest {
+            whenever(kosmos.dreamManager.canStartDreaming(anyBoolean())).thenReturn(true)
+            kosmos.setCommunalAvailable(false)
+            runCurrent()
+
+            powerInteractor.setAwakeForTest(reason = PowerManager.WAKE_REASON_POWER_BUTTON)
+            runCurrent()
+
+            // If dreaming is possible but communal is NOT available, then we should transition to
+            // LOCKSCREEN when waking up due to power button press.
+            assertThat(transitionRepository)
+                .startedTransition(
+                    from = KeyguardState.DOZING,
+                    to = KeyguardState.LOCKSCREEN,
+                )
+        }
+
+    @Test
+    @EnableFlags(Flags.FLAG_KEYGUARD_WM_STATE_REFACTOR)
     fun testTransitionToGlanceableHub_onWakeup_ifIdleOnCommunal_noOccludingActivity() =
         testScope.runTest {
             kosmos.fakeCommunalSceneRepository.setTransitionState(
diff --git a/packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/policy/HeadsUpManagerPhoneTest.java b/packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/policy/HeadsUpManagerPhoneTest.java
deleted file mode 100644
index 3d3438e..0000000
--- a/packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/policy/HeadsUpManagerPhoneTest.java
+++ /dev/null
@@ -1,243 +0,0 @@
-/*
- * Copyright (C) 2018 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- *      http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-package com.android.systemui.statusbar.policy;
-
-import static com.android.systemui.log.LogBufferHelperKt.logcatLogBuffer;
-import static com.android.systemui.util.concurrency.MockExecutorHandlerKt.mockExecutorHandler;
-
-import static junit.framework.Assert.assertFalse;
-import static junit.framework.Assert.assertTrue;
-
-import static org.mockito.ArgumentMatchers.anyInt;
-import static org.mockito.Mockito.when;
-
-import android.content.Context;
-import android.os.Handler;
-import android.platform.test.flag.junit.FlagsParameterization;
-import android.testing.TestableLooper;
-
-import androidx.test.filters.SmallTest;
-
-import com.android.internal.logging.UiEventLogger;
-import com.android.systemui.dump.DumpManager;
-import com.android.systemui.plugins.statusbar.StatusBarStateController;
-import com.android.systemui.res.R;
-import com.android.systemui.shade.domain.interactor.ShadeInteractor;
-import com.android.systemui.statusbar.NotificationShadeWindowController;
-import com.android.systemui.statusbar.notification.collection.NotificationEntry;
-import com.android.systemui.statusbar.notification.collection.provider.VisualStabilityProvider;
-import com.android.systemui.statusbar.notification.collection.render.GroupMembershipManager;
-import com.android.systemui.statusbar.notification.shared.NotificationThrottleHun;
-import com.android.systemui.statusbar.phone.ConfigurationControllerImpl;
-import com.android.systemui.statusbar.phone.HeadsUpManagerPhone;
-import com.android.systemui.statusbar.phone.KeyguardBypassController;
-import com.android.systemui.util.concurrency.DelayableExecutor;
-import com.android.systemui.util.kotlin.JavaAdapter;
-import com.android.systemui.util.settings.GlobalSettings;
-import com.android.systemui.util.time.SystemClock;
-
-import kotlinx.coroutines.flow.StateFlowKt;
-
-import org.junit.Before;
-import org.junit.Ignore;
-import org.junit.Rule;
-import org.junit.Test;
-import org.junit.runner.RunWith;
-import org.mockito.Mock;
-import org.mockito.junit.MockitoJUnit;
-import org.mockito.junit.MockitoRule;
-
-import platform.test.runner.parameterized.ParameterizedAndroidJunit4;
-import platform.test.runner.parameterized.Parameters;
-
-import java.util.List;
-
-@SmallTest
-@RunWith(ParameterizedAndroidJunit4.class)
-@TestableLooper.RunWithLooper
-public class HeadsUpManagerPhoneTest extends BaseHeadsUpManagerTest {
-    @Rule public MockitoRule rule = MockitoJUnit.rule();
-
-    private final HeadsUpManagerLogger mHeadsUpManagerLogger = new HeadsUpManagerLogger(
-            logcatLogBuffer());
-    @Mock private GroupMembershipManager mGroupManager;
-    @Mock private VisualStabilityProvider mVSProvider;
-    @Mock private StatusBarStateController mStatusBarStateController;
-    @Mock private KeyguardBypassController mBypassController;
-    @Mock private ConfigurationControllerImpl mConfigurationController;
-    @Mock private AccessibilityManagerWrapper mAccessibilityManagerWrapper;
-    @Mock private UiEventLogger mUiEventLogger;
-    @Mock private JavaAdapter mJavaAdapter;
-    @Mock private ShadeInteractor mShadeInteractor;
-    @Mock private DumpManager dumpManager;
-    private AvalancheController mAvalancheController;
-
-    @Mock private Handler mBgHandler;
-
-    private static final class TestableHeadsUpManagerPhone extends HeadsUpManagerPhone {
-        TestableHeadsUpManagerPhone(
-                Context context,
-                HeadsUpManagerLogger headsUpManagerLogger,
-                GroupMembershipManager groupManager,
-                VisualStabilityProvider visualStabilityProvider,
-                StatusBarStateController statusBarStateController,
-                KeyguardBypassController keyguardBypassController,
-                ConfigurationController configurationController,
-                GlobalSettings globalSettings,
-                SystemClock systemClock,
-                DelayableExecutor executor,
-                AccessibilityManagerWrapper accessibilityManagerWrapper,
-                UiEventLogger uiEventLogger,
-                JavaAdapter javaAdapter,
-                ShadeInteractor shadeInteractor,
-                AvalancheController avalancheController
-        ) {
-            super(
-                    context,
-                    headsUpManagerLogger,
-                    statusBarStateController,
-                    keyguardBypassController,
-                    groupManager,
-                    visualStabilityProvider,
-                    configurationController,
-                    mockExecutorHandler(executor),
-                    globalSettings,
-                    systemClock,
-                    executor,
-                    accessibilityManagerWrapper,
-                    uiEventLogger,
-                    javaAdapter,
-                    shadeInteractor,
-                    avalancheController
-            );
-            mMinimumDisplayTime = TEST_MINIMUM_DISPLAY_TIME;
-            mAutoDismissTime = TEST_AUTO_DISMISS_TIME;
-        }
-    }
-
-    private HeadsUpManagerPhone createHeadsUpManagerPhone() {
-        return new TestableHeadsUpManagerPhone(
-                mContext,
-                mHeadsUpManagerLogger,
-                mGroupManager,
-                mVSProvider,
-                mStatusBarStateController,
-                mBypassController,
-                mConfigurationController,
-                mGlobalSettings,
-                mSystemClock,
-                mExecutor,
-                mAccessibilityManagerWrapper,
-                mUiEventLogger,
-                mJavaAdapter,
-                mShadeInteractor,
-                mAvalancheController
-        );
-    }
-
-    @Parameters(name = "{0}")
-    public static List<FlagsParameterization> getFlags() {
-        return FlagsParameterization.allCombinationsOf(NotificationThrottleHun.FLAG_NAME);
-    }
-
-    public HeadsUpManagerPhoneTest(FlagsParameterization flags) {
-        super(flags);
-    }
-
-    @Before
-    public void setUp() {
-        when(mShadeInteractor.isAnyExpanded()).thenReturn(StateFlowKt.MutableStateFlow(false));
-        final AccessibilityManagerWrapper accessibilityMgr =
-                mDependency.injectMockDependency(AccessibilityManagerWrapper.class);
-        when(accessibilityMgr.getRecommendedTimeoutMillis(anyInt(), anyInt()))
-                .thenReturn(TEST_AUTO_DISMISS_TIME);
-        when(mVSProvider.isReorderingAllowed()).thenReturn(true);
-        mDependency.injectMockDependency(NotificationShadeWindowController.class);
-        mContext.getOrCreateTestableResources().addOverride(
-                R.integer.ambient_notification_extension_time, 500);
-
-        mAvalancheController = new AvalancheController(dumpManager, mUiEventLogger, mBgHandler);
-    }
-
-    @Test
-    public void testSnooze() {
-        final HeadsUpManager hmp = createHeadsUpManagerPhone();
-        final NotificationEntry entry = HeadsUpManagerTestUtil.createEntry(/* id = */ 0, mContext);
-
-        hmp.showNotification(entry);
-        hmp.snooze();
-
-        assertTrue(hmp.isSnoozed(entry.getSbn().getPackageName()));
-    }
-
-    @Test
-    public void testSwipedOutNotification() {
-        final HeadsUpManager hmp = createHeadsUpManagerPhone();
-        final NotificationEntry entry = HeadsUpManagerTestUtil.createEntry(/* id = */ 0, mContext);
-
-        hmp.showNotification(entry);
-        hmp.addSwipedOutNotification(entry.getKey());
-
-        // Remove should succeed because the notification is swiped out
-        final boolean removedImmediately = hmp.removeNotification(entry.getKey(),
-                /* releaseImmediately = */ false);
-
-        assertTrue(removedImmediately);
-        assertFalse(hmp.isHeadsUpEntry(entry.getKey()));
-    }
-
-    @Test
-    public void testCanRemoveImmediately_swipedOut() {
-        final HeadsUpManager hmp = createHeadsUpManagerPhone();
-        final NotificationEntry entry = HeadsUpManagerTestUtil.createEntry(/* id = */ 0, mContext);
-
-        hmp.showNotification(entry);
-        hmp.addSwipedOutNotification(entry.getKey());
-
-        // Notification is swiped so it can be immediately removed.
-        assertTrue(hmp.canRemoveImmediately(entry.getKey()));
-    }
-
-    @Ignore("b/141538055")
-    @Test
-    public void testCanRemoveImmediately_notTopEntry() {
-        final HeadsUpManager hmp = createHeadsUpManagerPhone();
-        final NotificationEntry earlierEntry =
-                HeadsUpManagerTestUtil.createEntry(/* id = */ 0, mContext);
-        final NotificationEntry laterEntry =
-                HeadsUpManagerTestUtil.createEntry(/* id = */ 1, mContext);
-        laterEntry.setRow(mRow);
-
-        hmp.showNotification(earlierEntry);
-        hmp.showNotification(laterEntry);
-
-        // Notification is "behind" a higher priority notification so we can remove it immediately.
-        assertTrue(hmp.canRemoveImmediately(earlierEntry.getKey()));
-    }
-
-    @Test
-    public void testExtendHeadsUp() {
-        final HeadsUpManagerPhone hmp = createHeadsUpManagerPhone();
-        final NotificationEntry entry = HeadsUpManagerTestUtil.createEntry(/* id = */ 0, mContext);
-
-        hmp.showNotification(entry);
-        hmp.extendHeadsUp();
-        mSystemClock.advanceTime(TEST_AUTO_DISMISS_TIME + hmp.mExtensionTime / 2);
-
-        assertTrue(hmp.isHeadsUpEntry(entry.getKey()));
-    }
-}
diff --git a/packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/policy/HeadsUpManagerPhoneTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/policy/HeadsUpManagerPhoneTest.kt
new file mode 100644
index 0000000..663cf1c
--- /dev/null
+++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/policy/HeadsUpManagerPhoneTest.kt
@@ -0,0 +1,232 @@
+/*
+ * Copyright (C) 2018 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package com.android.systemui.statusbar.policy
+
+import android.content.Context
+import android.os.Handler
+import android.platform.test.flag.junit.FlagsParameterization
+import android.testing.TestableLooper.RunWithLooper
+import androidx.test.filters.SmallTest
+import com.android.internal.logging.UiEventLogger
+import com.android.systemui.dump.DumpManager
+import com.android.systemui.log.logcatLogBuffer
+import com.android.systemui.plugins.statusbar.StatusBarStateController
+import com.android.systemui.res.R
+import com.android.systemui.shade.domain.interactor.ShadeInteractor
+import com.android.systemui.statusbar.NotificationShadeWindowController
+import com.android.systemui.statusbar.notification.collection.provider.VisualStabilityProvider
+import com.android.systemui.statusbar.notification.collection.render.GroupMembershipManager
+import com.android.systemui.statusbar.notification.shared.NotificationThrottleHun
+import com.android.systemui.statusbar.notification.shared.NotificationsHeadsUpRefactor
+import com.android.systemui.statusbar.phone.ConfigurationControllerImpl
+import com.android.systemui.statusbar.phone.HeadsUpManagerPhone
+import com.android.systemui.statusbar.phone.KeyguardBypassController
+import com.android.systemui.util.concurrency.DelayableExecutor
+import com.android.systemui.util.concurrency.mockExecutorHandler
+import com.android.systemui.util.kotlin.JavaAdapter
+import com.android.systemui.util.settings.GlobalSettings
+import com.android.systemui.util.time.SystemClock
+import junit.framework.Assert
+import kotlinx.coroutines.flow.MutableStateFlow
+import org.junit.Before
+import org.junit.Ignore
+import org.junit.Test
+import org.junit.runner.RunWith
+import org.mockito.ArgumentMatchers
+import org.mockito.Mock
+import org.mockito.kotlin.whenever
+import platform.test.runner.parameterized.ParameterizedAndroidJunit4
+import platform.test.runner.parameterized.Parameters
+
+@SmallTest
+@RunWith(ParameterizedAndroidJunit4::class)
+@RunWithLooper
+class HeadsUpManagerPhoneTest(flags: FlagsParameterization) : BaseHeadsUpManagerTest(flags) {
+
+    private val mHeadsUpManagerLogger = HeadsUpManagerLogger(logcatLogBuffer())
+
+    @Mock private lateinit var mGroupManager: GroupMembershipManager
+
+    @Mock private lateinit var mVSProvider: VisualStabilityProvider
+
+    @Mock private lateinit var mStatusBarStateController: StatusBarStateController
+
+    @Mock private lateinit var mBypassController: KeyguardBypassController
+
+    @Mock private lateinit var mConfigurationController: ConfigurationControllerImpl
+
+    @Mock private lateinit var mAccessibilityManagerWrapper: AccessibilityManagerWrapper
+
+    @Mock private lateinit var mUiEventLogger: UiEventLogger
+
+    @Mock private lateinit var mJavaAdapter: JavaAdapter
+
+    @Mock private lateinit var mShadeInteractor: ShadeInteractor
+
+    @Mock private lateinit var dumpManager: DumpManager
+    private lateinit var mAvalancheController: AvalancheController
+
+    @Mock private lateinit var mBgHandler: Handler
+
+    private class TestableHeadsUpManagerPhone(
+        context: Context,
+        headsUpManagerLogger: HeadsUpManagerLogger,
+        groupManager: GroupMembershipManager,
+        visualStabilityProvider: VisualStabilityProvider,
+        statusBarStateController: StatusBarStateController,
+        keyguardBypassController: KeyguardBypassController,
+        configurationController: ConfigurationController,
+        globalSettings: GlobalSettings,
+        systemClock: SystemClock,
+        executor: DelayableExecutor,
+        accessibilityManagerWrapper: AccessibilityManagerWrapper,
+        uiEventLogger: UiEventLogger,
+        javaAdapter: JavaAdapter,
+        shadeInteractor: ShadeInteractor,
+        avalancheController: AvalancheController
+    ) :
+        HeadsUpManagerPhone(
+            context,
+            headsUpManagerLogger,
+            statusBarStateController,
+            keyguardBypassController,
+            groupManager,
+            visualStabilityProvider,
+            configurationController,
+            mockExecutorHandler(executor),
+            globalSettings,
+            systemClock,
+            executor,
+            accessibilityManagerWrapper,
+            uiEventLogger,
+            javaAdapter,
+            shadeInteractor,
+            avalancheController
+        ) {
+        init {
+            mMinimumDisplayTime = TEST_MINIMUM_DISPLAY_TIME
+            mAutoDismissTime = TEST_AUTO_DISMISS_TIME
+        }
+    }
+
+    private fun createHeadsUpManagerPhone(): HeadsUpManagerPhone {
+        return TestableHeadsUpManagerPhone(
+            mContext,
+            mHeadsUpManagerLogger,
+            mGroupManager,
+            mVSProvider,
+            mStatusBarStateController,
+            mBypassController,
+            mConfigurationController,
+            mGlobalSettings,
+            mSystemClock,
+            mExecutor,
+            mAccessibilityManagerWrapper,
+            mUiEventLogger,
+            mJavaAdapter,
+            mShadeInteractor,
+            mAvalancheController
+        )
+    }
+
+    @Before
+    fun setUp() {
+        whenever(mShadeInteractor.isAnyExpanded).thenReturn(MutableStateFlow(false))
+        whenever(mVSProvider.isReorderingAllowed).thenReturn(true)
+        val accessibilityMgr =
+            mDependency.injectMockDependency(AccessibilityManagerWrapper::class.java)
+        whenever(
+                accessibilityMgr.getRecommendedTimeoutMillis(
+                    ArgumentMatchers.anyInt(),
+                    ArgumentMatchers.anyInt()
+                )
+            )
+            .thenReturn(TEST_AUTO_DISMISS_TIME)
+        mDependency.injectMockDependency(NotificationShadeWindowController::class.java)
+        mContext
+            .getOrCreateTestableResources()
+            .addOverride(R.integer.ambient_notification_extension_time, 500)
+        mAvalancheController = AvalancheController(dumpManager, mUiEventLogger, mBgHandler)
+    }
+
+    @Test
+    fun testSnooze() {
+        val hmp: HeadsUpManager = createHeadsUpManagerPhone()
+        val entry = HeadsUpManagerTestUtil.createEntry(/* id= */ 0, mContext)
+        hmp.showNotification(entry)
+        hmp.snooze()
+        Assert.assertTrue(hmp.isSnoozed(entry.sbn.packageName))
+    }
+
+    @Test
+    fun testSwipedOutNotification() {
+        val hmp: HeadsUpManager = createHeadsUpManagerPhone()
+        val entry = HeadsUpManagerTestUtil.createEntry(/* id= */ 0, mContext)
+        hmp.showNotification(entry)
+        hmp.addSwipedOutNotification(entry.key)
+
+        // Remove should succeed because the notification is swiped out
+        val removedImmediately = hmp.removeNotification(entry.key, /* releaseImmediately= */ false)
+        Assert.assertTrue(removedImmediately)
+        Assert.assertFalse(hmp.isHeadsUpEntry(entry.key))
+    }
+
+    @Test
+    fun testCanRemoveImmediately_swipedOut() {
+        val hmp: HeadsUpManager = createHeadsUpManagerPhone()
+        val entry = HeadsUpManagerTestUtil.createEntry(/* id= */ 0, mContext)
+        hmp.showNotification(entry)
+        hmp.addSwipedOutNotification(entry.key)
+
+        // Notification is swiped so it can be immediately removed.
+        Assert.assertTrue(hmp.canRemoveImmediately(entry.key))
+    }
+
+    @Ignore("b/141538055")
+    @Test
+    fun testCanRemoveImmediately_notTopEntry() {
+        val hmp: HeadsUpManager = createHeadsUpManagerPhone()
+        val earlierEntry = HeadsUpManagerTestUtil.createEntry(/* id= */ 0, mContext)
+        val laterEntry = HeadsUpManagerTestUtil.createEntry(/* id= */ 1, mContext)
+        laterEntry.row = mRow
+        hmp.showNotification(earlierEntry)
+        hmp.showNotification(laterEntry)
+
+        // Notification is "behind" a higher priority notification so we can remove it immediately.
+        Assert.assertTrue(hmp.canRemoveImmediately(earlierEntry.key))
+    }
+
+    @Test
+    fun testExtendHeadsUp() {
+        val hmp = createHeadsUpManagerPhone()
+        val entry = HeadsUpManagerTestUtil.createEntry(/* id= */ 0, mContext)
+        hmp.showNotification(entry)
+        hmp.extendHeadsUp()
+        mSystemClock.advanceTime((TEST_AUTO_DISMISS_TIME + hmp.mExtensionTime / 2).toLong())
+        Assert.assertTrue(hmp.isHeadsUpEntry(entry.key))
+    }
+
+    companion object {
+        @get:Parameters(name = "{0}")
+        val flags: List<FlagsParameterization>
+            get() = buildList {
+                addAll(FlagsParameterization.allCombinationsOf(NotificationThrottleHun.FLAG_NAME))
+                addAll(
+                    FlagsParameterization.allCombinationsOf(NotificationsHeadsUpRefactor.FLAG_NAME)
+                )
+            }
+    }
+}
diff --git a/packages/SystemUI/res/layout/screenshot.xml b/packages/SystemUI/res/layout/screenshot.xml
deleted file mode 100644
index c134c8e..0000000
--- a/packages/SystemUI/res/layout/screenshot.xml
+++ /dev/null
@@ -1,40 +0,0 @@
-<?xml version="1.0" encoding="utf-8"?>
-<!--
-  ~ Copyright (C) 2011 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.
-  -->
-<com.android.systemui.screenshot.ScreenshotView
-    xmlns:android="http://schemas.android.com/apk/res/android"
-    android:id="@+id/screenshot_frame"
-    android:theme="@style/FloatingOverlay"
-    android:layout_width="match_parent"
-    android:layout_height="match_parent"
-    android:importantForAccessibility="no">
-    <ImageView
-        android:id="@+id/screenshot_scrolling_scrim"
-        android:layout_width="match_parent"
-        android:layout_height="match_parent"
-        android:visibility="gone"
-        android:clickable="true"
-        android:importantForAccessibility="no"/>
-    <ImageView
-        android:id="@+id/screenshot_flash"
-        android:layout_width="match_parent"
-        android:layout_height="match_parent"
-        android:visibility="gone"
-        android:elevation="7dp"
-        android:src="@android:color/white"/>
-    <include layout="@layout/screenshot_static"
-             android:id="@+id/screenshot_static"/>
-</com.android.systemui.screenshot.ScreenshotView>
diff --git a/packages/SystemUI/schemas/com.android.systemui.communal.data.db.CommunalDatabase/2.json b/packages/SystemUI/schemas/com.android.systemui.communal.data.db.CommunalDatabase/2.json
new file mode 100644
index 0000000..f10d92a
--- /dev/null
+++ b/packages/SystemUI/schemas/com.android.systemui.communal.data.db.CommunalDatabase/2.json
@@ -0,0 +1,81 @@
+{
+  "formatVersion": 1,
+  "database": {
+    "version": 2,
+    "identityHash": "02e2da2d36e6955200edd5fb49e63c72",
+    "entities": [
+      {
+        "tableName": "communal_widget_table",
+        "createSql": "CREATE TABLE IF NOT EXISTS `${TABLE_NAME}` (`uid` INTEGER PRIMARY KEY AUTOINCREMENT NOT NULL, `widget_id` INTEGER NOT NULL, `component_name` TEXT NOT NULL, `item_id` INTEGER NOT NULL, `user_serial_number` INTEGER NOT NULL DEFAULT -1)",
+        "fields": [
+          {
+            "fieldPath": "uid",
+            "columnName": "uid",
+            "affinity": "INTEGER",
+            "notNull": true
+          },
+          {
+            "fieldPath": "widgetId",
+            "columnName": "widget_id",
+            "affinity": "INTEGER",
+            "notNull": true
+          },
+          {
+            "fieldPath": "componentName",
+            "columnName": "component_name",
+            "affinity": "TEXT",
+            "notNull": true
+          },
+          {
+            "fieldPath": "itemId",
+            "columnName": "item_id",
+            "affinity": "INTEGER",
+            "notNull": true
+          },
+          {
+            "fieldPath": "userSerialNumber",
+            "columnName": "user_serial_number",
+            "affinity": "INTEGER",
+            "notNull": true,
+            "defaultValue": "-1"
+          }
+        ],
+        "primaryKey": {
+          "autoGenerate": true,
+          "columnNames": [
+            "uid"
+          ]
+        }
+      },
+      {
+        "tableName": "communal_item_rank_table",
+        "createSql": "CREATE TABLE IF NOT EXISTS `${TABLE_NAME}` (`uid` INTEGER PRIMARY KEY AUTOINCREMENT NOT NULL, `rank` INTEGER NOT NULL DEFAULT 0)",
+        "fields": [
+          {
+            "fieldPath": "uid",
+            "columnName": "uid",
+            "affinity": "INTEGER",
+            "notNull": true
+          },
+          {
+            "fieldPath": "rank",
+            "columnName": "rank",
+            "affinity": "INTEGER",
+            "notNull": true,
+            "defaultValue": "0"
+          }
+        ],
+        "primaryKey": {
+          "autoGenerate": true,
+          "columnNames": [
+            "uid"
+          ]
+        }
+      }
+    ],
+    "setupQueries": [
+      "CREATE TABLE IF NOT EXISTS room_master_table (id INTEGER PRIMARY KEY,identity_hash TEXT)",
+      "INSERT OR REPLACE INTO room_master_table (id,identity_hash) VALUES(42, '02e2da2d36e6955200edd5fb49e63c72')"
+    ]
+  }
+}
\ No newline at end of file
diff --git a/packages/SystemUI/src/com/android/systemui/communal/CommunalBackupRestoreStartable.kt b/packages/SystemUI/src/com/android/systemui/communal/CommunalBackupRestoreStartable.kt
index cdeeb6f..7abad14 100644
--- a/packages/SystemUI/src/com/android/systemui/communal/CommunalBackupRestoreStartable.kt
+++ b/packages/SystemUI/src/com/android/systemui/communal/CommunalBackupRestoreStartable.kt
@@ -21,6 +21,9 @@
 import android.content.Context
 import android.content.Intent
 import android.content.IntentFilter
+import android.database.ContentObserver
+import android.os.Handler
+import android.provider.Settings.Secure.USER_SETUP_COMPLETE
 import com.android.systemui.CoreStartable
 import com.android.systemui.broadcast.BroadcastDispatcher
 import com.android.systemui.communal.domain.interactor.CommunalInteractor
@@ -29,6 +32,7 @@
 import com.android.systemui.log.LogBuffer
 import com.android.systemui.log.core.Logger
 import com.android.systemui.log.dagger.CommunalLog
+import com.android.systemui.util.settings.SecureSettings
 import javax.inject.Inject
 
 @SysUISingleton
@@ -38,10 +42,15 @@
     private val broadcastDispatcher: BroadcastDispatcher,
     private val communalInteractor: CommunalInteractor,
     @CommunalLog logBuffer: LogBuffer,
+    private val secureSettings: SecureSettings,
+    handler: Handler,
 ) : CoreStartable, BroadcastReceiver() {
 
     private val logger = Logger(logBuffer, TAG)
 
+    private var oldToNewWidgetIdMap = emptyMap<Int, Int>()
+    private var userSetupComplete = false
+
     override fun start() {
         broadcastDispatcher.registerReceiver(
             receiver = this,
@@ -73,8 +82,53 @@
             return
         }
 
-        val oldToNewWidgetIdMap = oldIds.zip(newIds).toMap()
-        communalInteractor.restoreWidgets(oldToNewWidgetIdMap)
+        oldToNewWidgetIdMap = oldIds.zip(newIds).toMap()
+
+        logger.i({ "On old to new widget ids mapping updated: $str1" }) {
+            str1 = oldToNewWidgetIdMap.toString()
+        }
+
+        maybeRestoreWidgets()
+
+        // Start observing if user setup is not complete
+        if (!userSetupComplete) {
+            startObservingUserSetupComplete()
+        }
+    }
+
+    private val userSetupObserver =
+        object : ContentObserver(handler) {
+            override fun onChange(selfChange: Boolean) {
+                maybeRestoreWidgets()
+
+                // Stop observing once user setup is complete
+                if (userSetupComplete) {
+                    stopObservingUserSetupComplete()
+                }
+            }
+        }
+
+    private fun maybeRestoreWidgets() {
+        val newValue = secureSettings.getInt(USER_SETUP_COMPLETE) > 0
+
+        if (userSetupComplete != newValue) {
+            userSetupComplete = newValue
+            logger.i({ "User setup complete: $bool1" }) { bool1 = userSetupComplete }
+        }
+
+        if (userSetupComplete && oldToNewWidgetIdMap.isNotEmpty()) {
+            logger.i("Starting to restore widgets")
+            communalInteractor.restoreWidgets(oldToNewWidgetIdMap.toMap())
+            oldToNewWidgetIdMap = emptyMap()
+        }
+    }
+
+    private fun startObservingUserSetupComplete() {
+        secureSettings.registerContentObserverSync(USER_SETUP_COMPLETE, userSetupObserver)
+    }
+
+    private fun stopObservingUserSetupComplete() {
+        secureSettings.unregisterContentObserverSync(userSetupObserver)
     }
 
     companion object {
diff --git a/packages/SystemUI/src/com/android/systemui/communal/CommunalSceneStartable.kt b/packages/SystemUI/src/com/android/systemui/communal/CommunalSceneStartable.kt
index 88c3f9f6..bde6f42 100644
--- a/packages/SystemUI/src/com/android/systemui/communal/CommunalSceneStartable.kt
+++ b/packages/SystemUI/src/com/android/systemui/communal/CommunalSceneStartable.kt
@@ -18,7 +18,9 @@
 
 import android.provider.Settings
 import com.android.compose.animation.scene.SceneKey
+import com.android.compose.animation.scene.TransitionKey
 import com.android.systemui.CoreStartable
+import com.android.systemui.Flags.communalHub
 import com.android.systemui.communal.domain.interactor.CommunalInteractor
 import com.android.systemui.communal.domain.interactor.CommunalSceneInteractor
 import com.android.systemui.communal.shared.model.CommunalScenes
@@ -28,6 +30,8 @@
 import com.android.systemui.dagger.qualifiers.Background
 import com.android.systemui.dagger.qualifiers.Main
 import com.android.systemui.dock.DockManager
+import com.android.systemui.flags.FeatureFlagsClassic
+import com.android.systemui.flags.Flags
 import com.android.systemui.keyguard.domain.interactor.KeyguardInteractor
 import com.android.systemui.keyguard.domain.interactor.KeyguardTransitionInteractor
 import com.android.systemui.keyguard.shared.model.KeyguardState
@@ -74,6 +78,7 @@
     private val systemSettings: SystemSettings,
     centralSurfacesOpt: Optional<CentralSurfaces>,
     private val notificationShadeWindowController: NotificationShadeWindowController,
+    private val featureFlagsClassic: FeatureFlagsClassic,
     @Application private val applicationScope: CoroutineScope,
     @Background private val bgScope: CoroutineScope,
     @Main private val mainDispatcher: CoroutineDispatcher,
@@ -86,13 +91,21 @@
 
     private val centralSurfaces: CentralSurfaces? by centralSurfacesOpt
 
+    private val flagEnabled: Boolean by lazy {
+        featureFlagsClassic.isEnabled(Flags.COMMUNAL_SERVICE_ENABLED) && communalHub()
+    }
+
     override fun start() {
+        if (!flagEnabled) {
+            return
+        }
+
         // Handle automatically switching based on keyguard state.
         keyguardTransitionInteractor.startedKeyguardTransitionStep
             .mapLatest(::determineSceneAfterTransition)
             .filterNotNull()
-            .onEach { nextScene ->
-                communalSceneInteractor.changeScene(nextScene, CommunalTransitionKeys.SimpleFade)
+            .onEach { (nextScene, nextTransition) ->
+                communalSceneInteractor.changeScene(nextScene, nextTransition)
             }
             .launchIn(applicationScope)
 
@@ -188,7 +201,7 @@
 
     private suspend fun determineSceneAfterTransition(
         lastStartedTransition: TransitionStep,
-    ): SceneKey? {
+    ): Pair<SceneKey, TransitionKey>? {
         val to = lastStartedTransition.to
         val from = lastStartedTransition.from
         val docked = dockManager.isDocked
@@ -201,22 +214,27 @@
                 // underneath the hub is shown. When launching activities over lockscreen, we only
                 // change scenes once the activity launch animation is finished, so avoid
                 // changing the scene here.
-                CommunalScenes.Blank
+                Pair(CommunalScenes.Blank, CommunalTransitionKeys.SimpleFade)
             }
             to == KeyguardState.GLANCEABLE_HUB && from == KeyguardState.OCCLUDED -> {
                 // When transitioning to the hub from an occluded state, fade out the hub without
                 // doing any translation.
-                CommunalScenes.Communal
+                Pair(CommunalScenes.Communal, CommunalTransitionKeys.SimpleFade)
             }
             // Transitioning to Blank scene when entering the edit mode will be handled separately
             // with custom animations.
             to == KeyguardState.GONE && !communalInteractor.editModeOpen.value ->
-                CommunalScenes.Blank
+                Pair(CommunalScenes.Blank, CommunalTransitionKeys.SimpleFade)
             !docked && !KeyguardState.deviceIsAwakeInState(to) -> {
                 // If the user taps the screen and wakes the device within this timeout, we don't
                 // want to dismiss the hub
                 delay(AWAKE_DEBOUNCE_DELAY)
-                CommunalScenes.Blank
+                Pair(CommunalScenes.Blank, CommunalTransitionKeys.SimpleFade)
+            }
+            from == KeyguardState.DOZING && to == KeyguardState.GLANCEABLE_HUB -> {
+                // Make sure the communal hub is showing (immediately, not fading in) when
+                // transitioning from dozing to hub.
+                Pair(CommunalScenes.Communal, CommunalTransitionKeys.Immediately)
             }
             else -> null
         }
diff --git a/packages/SystemUI/src/com/android/systemui/communal/data/backup/CommunalBackupUtils.kt b/packages/SystemUI/src/com/android/systemui/communal/data/backup/CommunalBackupUtils.kt
index a8e5174..c3d2683 100644
--- a/packages/SystemUI/src/com/android/systemui/communal/data/backup/CommunalBackupUtils.kt
+++ b/packages/SystemUI/src/com/android/systemui/communal/data/backup/CommunalBackupUtils.kt
@@ -43,11 +43,13 @@
         val widgetsFromDb = runBlocking { database.communalWidgetDao().getWidgets().first() }
         val widgetsState = mutableListOf<CommunalHubState.CommunalWidgetItem>()
         widgetsFromDb.keys.forEach { rankItem ->
+            val widget = widgetsFromDb[rankItem]!!
             widgetsState.add(
                 CommunalHubState.CommunalWidgetItem().apply {
                     rank = rankItem.rank
-                    widgetId = widgetsFromDb[rankItem]!!.widgetId
-                    componentName = widgetsFromDb[rankItem]?.componentName
+                    widgetId = widget.widgetId
+                    componentName = widget.componentName
+                    userSerialNumber = widget.userSerialNumber
                 }
             )
         }
diff --git a/packages/SystemUI/src/com/android/systemui/communal/data/db/CommunalDatabase.kt b/packages/SystemUI/src/com/android/systemui/communal/data/db/CommunalDatabase.kt
index 3ce8109..dff6352 100644
--- a/packages/SystemUI/src/com/android/systemui/communal/data/db/CommunalDatabase.kt
+++ b/packages/SystemUI/src/com/android/systemui/communal/data/db/CommunalDatabase.kt
@@ -17,17 +17,21 @@
 package com.android.systemui.communal.data.db
 
 import android.content.Context
+import android.util.Log
 import androidx.annotation.VisibleForTesting
 import androidx.room.Database
 import androidx.room.Room
 import androidx.room.RoomDatabase
+import androidx.room.migration.Migration
+import androidx.sqlite.db.SupportSQLiteDatabase
 import com.android.systemui.res.R
 
-@Database(entities = [CommunalWidgetItem::class, CommunalItemRank::class], version = 1)
+@Database(entities = [CommunalWidgetItem::class, CommunalItemRank::class], version = 2)
 abstract class CommunalDatabase : RoomDatabase() {
     abstract fun communalWidgetDao(): CommunalWidgetDao
 
     companion object {
+        private const val TAG = "CommunalDatabase"
         private var instance: CommunalDatabase? = null
 
         /**
@@ -51,7 +55,8 @@
                             context.resources.getString(R.string.config_communalDatabase)
                         )
                         .also { builder ->
-                            builder.fallbackToDestructiveMigration(dropAllTables = false)
+                            builder.addMigrations(MIGRATION_1_2)
+                            builder.fallbackToDestructiveMigration(dropAllTables = true)
                             callback?.let { callback -> builder.addCallback(callback) }
                         }
                         .build()
@@ -64,5 +69,23 @@
         fun setInstance(database: CommunalDatabase) {
             instance = database
         }
+
+        /**
+         * This migration adds a user_serial_number column and sets its default value as
+         * [CommunalWidgetItem.USER_SERIAL_NUMBER_UNDEFINED]. Work profile widgets added before the
+         * migration still work as expected, but they would be backed up as personal.
+         */
+        @VisibleForTesting
+        val MIGRATION_1_2 =
+            object : Migration(1, 2) {
+                override fun migrate(db: SupportSQLiteDatabase) {
+                    Log.i(TAG, "Migrating from version 1 to 2")
+                    db.execSQL(
+                        "ALTER TABLE communal_widget_table " +
+                            "ADD COLUMN user_serial_number INTEGER NOT NULL DEFAULT " +
+                            "${CommunalWidgetItem.USER_SERIAL_NUMBER_UNDEFINED}"
+                    )
+                }
+            }
     }
 }
diff --git a/packages/SystemUI/src/com/android/systemui/communal/data/db/CommunalEntities.kt b/packages/SystemUI/src/com/android/systemui/communal/data/db/CommunalEntities.kt
index 0d5336a..e33aead 100644
--- a/packages/SystemUI/src/com/android/systemui/communal/data/db/CommunalEntities.kt
+++ b/packages/SystemUI/src/com/android/systemui/communal/data/db/CommunalEntities.kt
@@ -29,7 +29,28 @@
     @ColumnInfo(name = "component_name") val componentName: String,
     /** Reference the id of an item persisted in the glanceable hub */
     @ColumnInfo(name = "item_id") val itemId: Long,
-)
+    /**
+     * A serial number of the user that the widget provider is associated with. For example, a work
+     * profile widget.
+     *
+     * A serial number may be different from its user id in that user ids may be recycled but serial
+     * numbers are unique until the device is wiped.
+     *
+     * Most commonly, this value would be 0 for the primary user, and 10 for the work profile.
+     */
+    @ColumnInfo(name = "user_serial_number", defaultValue = "$USER_SERIAL_NUMBER_UNDEFINED")
+    val userSerialNumber: Int,
+) {
+    companion object {
+        /**
+         * The user serial number associated with the widget is undefined.
+         *
+         * This should only happen for widgets migrated from V1 before user serial number was
+         * included in the schema.
+         */
+        const val USER_SERIAL_NUMBER_UNDEFINED = -1
+    }
+}
 
 @Entity(tableName = "communal_item_rank_table")
 data class CommunalItemRank(
diff --git a/packages/SystemUI/src/com/android/systemui/communal/data/db/CommunalWidgetDao.kt b/packages/SystemUI/src/com/android/systemui/communal/data/db/CommunalWidgetDao.kt
index d174fd1..4dcd9bf 100644
--- a/packages/SystemUI/src/com/android/systemui/communal/data/db/CommunalWidgetDao.kt
+++ b/packages/SystemUI/src/com/android/systemui/communal/data/db/CommunalWidgetDao.kt
@@ -17,6 +17,7 @@
 package com.android.systemui.communal.data.db
 
 import android.content.ComponentName
+import android.os.UserManager
 import androidx.room.Dao
 import androidx.room.Delete
 import androidx.room.Query
@@ -53,23 +54,34 @@
     private val communalWidgetDaoProvider: Provider<CommunalWidgetDao>,
     @Named(DEFAULT_WIDGETS) private val defaultWidgets: Array<String>,
     @CommunalLog logBuffer: LogBuffer,
+    private val userManager: UserManager,
 ) : RoomDatabase.Callback() {
     companion object {
         private const val TAG = "DefaultWidgetPopulation"
     }
+
     private val logger = Logger(logBuffer, TAG)
 
     override fun onCreate(db: SupportSQLiteDatabase) {
         super.onCreate(db)
-        applicationScope.launch {
-            addDefaultWidgets()
-            logger.i("Default widgets were populated in the database.")
-        }
+        applicationScope.launch { addDefaultWidgets() }
     }
 
     // Read default widgets from config.xml and populate the database.
     private suspend fun addDefaultWidgets() =
         withContext(bgDispatcher) {
+            // Default widgets should be associated with the main user.
+            val userSerialNumber =
+                userManager.mainUser?.let { mainUser ->
+                    userManager.getUserSerialNumber(mainUser.identifier)
+                }
+            if (userSerialNumber == null) {
+                logger.w(
+                    "Skipped populating default widgets because device does not have a main user"
+                )
+                return@withContext
+            }
+
             defaultWidgets.forEachIndexed { index, name ->
                 val provider = ComponentName.unflattenFromString(name)
                 provider?.let {
@@ -80,11 +92,14 @@
                             .addWidget(
                                 widgetId = id,
                                 provider = provider,
-                                priority = defaultWidgets.size - index
+                                priority = defaultWidgets.size - index,
+                                userSerialNumber = userSerialNumber,
                             )
                     }
                 }
             }
+
+            logger.i("Populated default widgets in the database.")
         }
 }
 
@@ -106,10 +121,16 @@
     fun deleteItemRankById(itemId: Long)
 
     @Query(
-        "INSERT INTO communal_widget_table(widget_id, component_name, item_id) " +
-            "VALUES(:widgetId, :componentName, :itemId)"
+        "INSERT INTO communal_widget_table" +
+            "(widget_id, component_name, item_id, user_serial_number) " +
+            "VALUES(:widgetId, :componentName, :itemId, :userSerialNumber)"
     )
-    fun insertWidget(widgetId: Int, componentName: String, itemId: Long): Long
+    fun insertWidget(
+        widgetId: Int,
+        componentName: String,
+        itemId: Long,
+        userSerialNumber: Int,
+    ): Long
 
     @Query("INSERT INTO communal_item_rank_table(rank) VALUES(:rank)")
     fun insertItemRank(rank: Int): Long
@@ -132,28 +153,41 @@
     }
 
     @Transaction
-    fun addWidget(widgetId: Int, provider: ComponentName, priority: Int): Long {
+    fun addWidget(
+        widgetId: Int,
+        provider: ComponentName,
+        priority: Int,
+        userSerialNumber: Int,
+    ): Long {
         return addWidget(
             widgetId = widgetId,
             componentName = provider.flattenToString(),
             priority = priority,
+            userSerialNumber = userSerialNumber,
         )
     }
 
     @Transaction
-    fun addWidget(widgetId: Int, componentName: String, priority: Int): Long {
+    fun addWidget(
+        widgetId: Int,
+        componentName: String,
+        priority: Int,
+        userSerialNumber: Int,
+    ): Long {
         return insertWidget(
             widgetId = widgetId,
             componentName = componentName,
             itemId = insertItemRank(priority),
+            userSerialNumber = userSerialNumber,
         )
     }
 
     @Transaction
     fun deleteWidgetById(widgetId: Int): Boolean {
         val widget =
-            getWidgetByIdNow(widgetId) ?: // no entry to delete from db
-            return false
+            getWidgetByIdNow(widgetId)
+                ?: // no entry to delete from db
+                return false
 
         deleteItemRankById(widget.itemId)
         deleteWidgets(widget)
@@ -166,6 +200,8 @@
         clearCommunalWidgetsTable()
         clearCommunalItemRankTable()
 
-        state.widgets.forEach { addWidget(it.widgetId, it.componentName, it.rank) }
+        state.widgets.forEach {
+            addWidget(it.widgetId, it.componentName, it.rank, it.userSerialNumber)
+        }
     }
 }
diff --git a/packages/SystemUI/src/com/android/systemui/communal/data/repository/CommunalWidgetRepository.kt b/packages/SystemUI/src/com/android/systemui/communal/data/repository/CommunalWidgetRepository.kt
index fdb797d..ab4c9d2 100644
--- a/packages/SystemUI/src/com/android/systemui/communal/data/repository/CommunalWidgetRepository.kt
+++ b/packages/SystemUI/src/com/android/systemui/communal/data/repository/CommunalWidgetRepository.kt
@@ -20,10 +20,12 @@
 import android.appwidget.AppWidgetProviderInfo
 import android.content.ComponentName
 import android.os.UserHandle
+import android.os.UserManager
 import com.android.systemui.common.data.repository.PackageChangeRepository
 import com.android.systemui.common.shared.model.PackageInstallSession
 import com.android.systemui.communal.data.backup.CommunalBackupUtils
 import com.android.systemui.communal.data.db.CommunalWidgetDao
+import com.android.systemui.communal.data.db.CommunalWidgetItem
 import com.android.systemui.communal.nano.CommunalHubState
 import com.android.systemui.communal.proto.toCommunalHubState
 import com.android.systemui.communal.shared.model.CommunalWidgetContentModel
@@ -98,6 +100,7 @@
     private val backupManager: BackupManager,
     private val backupUtils: CommunalBackupUtils,
     packageChangeRepository: PackageChangeRepository,
+    private val userManager: UserManager,
 ) : CommunalWidgetRepository {
     companion object {
         const val TAG = "CommunalWidgetRepository"
@@ -185,6 +188,7 @@
                     widgetId = id,
                     provider = provider,
                     priority = priority,
+                    userSerialNumber = userManager.getUserSerialNumber(user.identifier),
                 )
                 backupManager.dataChanged()
             } else {
@@ -228,9 +232,38 @@
                 return@launch
             }
 
+            // Abort restoring widgets if this code is somehow run on a device that does not have
+            // a main user, e.g. auto.
+            val mainUser = userManager.mainUser
+            if (mainUser == null) {
+                logger.w("Skipped restoring widgets because device does not have a main user")
+                return@launch
+            }
+
             val widgetsWithHost = appWidgetHost.appWidgetIds.toList()
             val widgetsToRemove = widgetsWithHost.toMutableList()
 
+            val oldUserSerialNumbers = state.widgets.map { it.userSerialNumber }.distinct()
+            val usersMap =
+                oldUserSerialNumbers.associateWith { oldUserSerialNumber ->
+                    if (oldUserSerialNumber == CommunalWidgetItem.USER_SERIAL_NUMBER_UNDEFINED) {
+                        // If user serial number from the backup is undefined, the widget was added
+                        // to the hub before user serial numbers are stored in the database. In this
+                        // case, we restore the widget with the main user.
+                        mainUser
+                    } else {
+                        // If the user serial number is defined, look up whether the user is
+                        // restored. This API returns a user handle matching its backed up user
+                        // serial number, if the user is restored. Otherwise, null is returned.
+                        backupManager.getUserForAncestralSerialNumber(oldUserSerialNumber.toLong())
+                            ?: null
+                    }
+                }
+            logger.d({ "Restored users map: $str1" }) { str1 = usersMap.toString() }
+
+            // A set to hold all widgets that belong to non-main users
+            val secondaryUserWidgets = mutableSetOf<CommunalHubState.CommunalWidgetItem>()
+
             // Produce a new state to be restored, skipping invalid widgets
             val newWidgets =
                 state.widgets.mapNotNull { restoredWidget ->
@@ -249,20 +282,64 @@
                         return@mapNotNull null
                     }
 
+                    // Skip if user / profile is not registered
+                    val newUser = usersMap[restoredWidget.userSerialNumber]
+                    if (newUser == null) {
+                        logger.d({
+                            "Skipped restoring widget $int1 because its user $int2 is not " +
+                                "registered"
+                        }) {
+                            int1 = restoredWidget.widgetId
+                            int2 = restoredWidget.userSerialNumber
+                        }
+                        return@mapNotNull null
+                    }
+
+                    // Place secondary user widgets in a bucket to be manually bound later because
+                    // of a platform bug (b/349852237) that backs up work profile widgets as
+                    // personal.
+                    if (newUser.identifier != mainUser.identifier) {
+                        logger.d({
+                            "Skipped restoring widget $int1 for now because its new user $int2 " +
+                                "is secondary. This widget will be bound later."
+                        }) {
+                            int1 = restoredWidget.widgetId
+                            int2 = newUser.identifier
+                        }
+                        secondaryUserWidgets.add(restoredWidget)
+                        return@mapNotNull null
+                    }
+
                     widgetsToRemove.remove(newWidgetId)
 
                     CommunalHubState.CommunalWidgetItem().apply {
                         widgetId = newWidgetId
                         componentName = restoredWidget.componentName
                         rank = restoredWidget.rank
+                        userSerialNumber = userManager.getUserSerialNumber(newUser.identifier)
                     }
                 }
             val newState = CommunalHubState().apply { widgets = newWidgets.toTypedArray() }
 
             // Restore database
-            logger.i("Restoring communal database $newState")
+            logger.i("Restoring communal database:\n$newState")
             communalWidgetDao.restoreCommunalHubState(newState)
 
+            // Manually bind each secondary user widget due to platform bug b/349852237
+            secondaryUserWidgets.forEach { widget ->
+                val newUser = usersMap[widget.userSerialNumber]!!
+                logger.i({ "Binding secondary user ($int1) widget $int2: $str1" }) {
+                    int1 = newUser.identifier
+                    int2 = widget.widgetId
+                    str1 = widget.componentName
+                }
+                addWidget(
+                    provider = ComponentName.unflattenFromString(widget.componentName)!!,
+                    user = newUser,
+                    priority = widget.rank,
+                )
+            }
+
             // Delete restored state file from disk
             backupUtils.clear()
 
diff --git a/packages/SystemUI/src/com/android/systemui/communal/proto/communal_hub_state.proto b/packages/SystemUI/src/com/android/systemui/communal/proto/communal_hub_state.proto
index 0816259..bc14ae1 100644
--- a/packages/SystemUI/src/com/android/systemui/communal/proto/communal_hub_state.proto
+++ b/packages/SystemUI/src/com/android/systemui/communal/proto/communal_hub_state.proto
@@ -35,5 +35,8 @@
 
         // Rank or order of the widget in the communal hub.
         int32 rank = 3;
+
+        // Serial number of the user associated with the widget.
+        int32 user_serial_number = 4;
     }
 }
diff --git a/packages/SystemUI/src/com/android/systemui/communal/widgets/CommunalWidgetModule.kt b/packages/SystemUI/src/com/android/systemui/communal/widgets/CommunalWidgetModule.kt
index 2000f96..684303ae 100644
--- a/packages/SystemUI/src/com/android/systemui/communal/widgets/CommunalWidgetModule.kt
+++ b/packages/SystemUI/src/com/android/systemui/communal/widgets/CommunalWidgetModule.kt
@@ -43,12 +43,6 @@
 
         @SysUISingleton
         @Provides
-        fun provideAppWidgetManager(@Application context: Context): Optional<AppWidgetManager> {
-            return Optional.ofNullable(AppWidgetManager.getInstance(context))
-        }
-
-        @SysUISingleton
-        @Provides
         fun provideCommunalAppWidgetHost(
             @Application context: Context,
             @Background backgroundScope: CoroutineScope,
diff --git a/packages/SystemUI/src/com/android/systemui/dagger/FrameworkServicesModule.java b/packages/SystemUI/src/com/android/systemui/dagger/FrameworkServicesModule.java
index a6deca7..2ea27b7 100644
--- a/packages/SystemUI/src/com/android/systemui/dagger/FrameworkServicesModule.java
+++ b/packages/SystemUI/src/com/android/systemui/dagger/FrameworkServicesModule.java
@@ -42,6 +42,7 @@
 import android.app.role.RoleManager;
 import android.app.smartspace.SmartspaceManager;
 import android.app.trust.TrustManager;
+import android.appwidget.AppWidgetManager;
 import android.bluetooth.BluetoothAdapter;
 import android.bluetooth.BluetoothManager;
 import android.companion.virtual.VirtualDeviceManager;
@@ -374,6 +375,12 @@
 
     @Provides
     @Singleton
+    static Optional<AppWidgetManager> provideAppWidgetManager(@Application Context context) {
+        return Optional.ofNullable(AppWidgetManager.getInstance(context));
+    }
+
+    @Provides
+    @Singleton
     static IAppWidgetService provideIAppWidgetService() {
         return IAppWidgetService.Stub.asInterface(
                 ServiceManager.getService(Context.APPWIDGET_SERVICE));
diff --git a/packages/SystemUI/src/com/android/systemui/keyguard/domain/interactor/FromDozingTransitionInteractor.kt b/packages/SystemUI/src/com/android/systemui/keyguard/domain/interactor/FromDozingTransitionInteractor.kt
index aee65a8..cd28bec 100644
--- a/packages/SystemUI/src/com/android/systemui/keyguard/domain/interactor/FromDozingTransitionInteractor.kt
+++ b/packages/SystemUI/src/com/android/systemui/keyguard/domain/interactor/FromDozingTransitionInteractor.kt
@@ -17,8 +17,11 @@
 package com.android.systemui.keyguard.domain.interactor
 
 import android.animation.ValueAnimator
+import android.annotation.SuppressLint
+import android.app.DreamManager
 import com.android.app.animation.Interpolators
 import com.android.systemui.communal.domain.interactor.CommunalInteractor
+import com.android.systemui.communal.domain.interactor.CommunalSceneInteractor
 import com.android.systemui.dagger.SysUISingleton
 import com.android.systemui.dagger.qualifiers.Background
 import com.android.systemui.dagger.qualifiers.Main
@@ -28,6 +31,7 @@
 import com.android.systemui.keyguard.shared.model.BiometricUnlockMode.Companion.isWakeAndUnlock
 import com.android.systemui.keyguard.shared.model.KeyguardState
 import com.android.systemui.power.domain.interactor.PowerInteractor
+import com.android.systemui.power.shared.model.WakeSleepReason
 import com.android.systemui.scene.shared.flag.SceneContainerFlag
 import com.android.systemui.util.kotlin.Utils.Companion.sample
 import com.android.systemui.util.kotlin.sample
@@ -53,9 +57,11 @@
     keyguardInteractor: KeyguardInteractor,
     powerInteractor: PowerInteractor,
     private val communalInteractor: CommunalInteractor,
+    private val communalSceneInteractor: CommunalSceneInteractor,
     keyguardOcclusionInteractor: KeyguardOcclusionInteractor,
     val deviceEntryRepository: DeviceEntryRepository,
     private val wakeToGoneInteractor: KeyguardWakeDirectlyToGoneInteractor,
+    private val dreamManager: DreamManager,
 ) :
     TransitionInteractor(
         fromState = KeyguardState.DOZING,
@@ -115,6 +121,7 @@
         }
     }
 
+    @SuppressLint("MissingPermission")
     private fun listenForDozingToAny() {
         if (KeyguardWmStateRefactor.isEnabled) {
             return
@@ -126,7 +133,8 @@
                 .filterRelevantKeyguardStateAnd { isAwake -> isAwake }
                 .sample(
                     keyguardInteractor.isKeyguardOccluded,
-                    communalInteractor.isIdleOnCommunal,
+                    communalInteractor.isCommunalAvailable,
+                    communalSceneInteractor.isIdleOnCommunal,
                     canTransitionToGoneOnWake,
                     keyguardInteractor.primaryBouncerShowing,
                 )
@@ -134,6 +142,7 @@
                     (
                         _,
                         occluded,
+                        isCommunalAvailable,
                         isIdleOnCommunal,
                         canTransitionToGoneOnWake,
                         primaryBouncerShowing) ->
@@ -163,6 +172,19 @@
                         } else {
                             startTransitionTo(KeyguardState.GLANCEABLE_HUB)
                         }
+                    } else if (
+                        powerInteractor.detailedWakefulness.value.lastWakeReason ==
+                            WakeSleepReason.POWER_BUTTON &&
+                            isCommunalAvailable &&
+                            dreamManager.canStartDreaming(true)
+                    ) {
+                        // This case handles tapping the power button to transition through
+                        // dream -> off -> hub.
+                        if (SceneContainerFlag.isEnabled) {
+                            // TODO(b/336576536): Check if adaptation for scene framework is needed
+                        } else {
+                            startTransitionTo(KeyguardState.GLANCEABLE_HUB)
+                        }
                     } else {
                         startTransitionTo(KeyguardState.LOCKSCREEN)
                     }
@@ -171,6 +193,7 @@
     }
 
     /** Figure out what state to transition to when we awake from DOZING. */
+    @SuppressLint("MissingPermission")
     private fun listenForWakeFromDozing() {
         if (!KeyguardWmStateRefactor.isEnabled) {
             return
@@ -180,7 +203,8 @@
             powerInteractor.detailedWakefulness
                 .filterRelevantKeyguardStateAnd { it.isAwake() }
                 .sample(
-                    communalInteractor.isIdleOnCommunal,
+                    communalInteractor.isCommunalAvailable,
+                    communalSceneInteractor.isIdleOnCommunal,
                     keyguardInteractor.biometricUnlockState,
                     wakeToGoneInteractor.canWakeDirectlyToGone,
                     keyguardInteractor.primaryBouncerShowing,
@@ -188,6 +212,7 @@
                 .collect {
                     (
                         _,
+                        isCommunalAvailable,
                         isIdleOnCommunal,
                         biometricUnlockState,
                         canWakeDirectlyToGone,
@@ -227,6 +252,23 @@
                                     ownerReason = "waking from dozing"
                                 )
                             }
+                        } else if (
+                            powerInteractor.detailedWakefulness.value.lastWakeReason ==
+                                WakeSleepReason.POWER_BUTTON &&
+                                isCommunalAvailable &&
+                                dreamManager.canStartDreaming(true)
+                        ) {
+                            // This case handles tapping the power button to transition through
+                            // dream -> off -> hub.
+                            if (SceneContainerFlag.isEnabled) {
+                                // TODO(b/336576536): Check if adaptation for scene framework is
+                                // needed
+                            } else {
+                                startTransitionTo(
+                                    KeyguardState.GLANCEABLE_HUB,
+                                    ownerReason = "waking from dozing"
+                                )
+                            }
                         } else {
                             startTransitionTo(
                                 KeyguardState.LOCKSCREEN,
diff --git a/packages/SystemUI/src/com/android/systemui/people/widget/PeopleBackupHelper.java b/packages/SystemUI/src/com/android/systemui/people/widget/PeopleBackupHelper.java
index d8c96dd..eadbffe 100644
--- a/packages/SystemUI/src/com/android/systemui/people/widget/PeopleBackupHelper.java
+++ b/packages/SystemUI/src/com/android/systemui/people/widget/PeopleBackupHelper.java
@@ -41,6 +41,8 @@
 import android.text.TextUtils;
 import android.util.Log;
 
+import androidx.annotation.Nullable;
+
 import com.android.internal.annotations.VisibleForTesting;
 import com.android.systemui.people.PeopleBackupFollowUpJob;
 import com.android.systemui.people.SharedPreferencesHelper;
@@ -67,6 +69,7 @@
     private final UserHandle mUserHandle;
     private final PackageManager mPackageManager;
     private final IPeopleManager mIPeopleManager;
+    @Nullable
     private final AppWidgetManager mAppWidgetManager;
 
     /**
@@ -404,6 +407,9 @@
 
     private List<String> getExistingWidgetsForUser(int userId) {
         List<String> existingWidgets = new ArrayList<>();
+        if (mAppWidgetManager == null) {
+            return existingWidgets;
+        }
         int[] ids = mAppWidgetManager.getAppWidgetIds(
                 new ComponentName(mContext, PeopleSpaceWidgetProvider.class));
         for (int id : ids) {
@@ -491,7 +497,11 @@
 
     /** Sends a broadcast to update the existing Conversation widgets. */
     public static void updateWidgets(Context context) {
-        int[] widgetIds = AppWidgetManager.getInstance(context)
+        AppWidgetManager manager = AppWidgetManager.getInstance(context);
+        if (manager == null) {
+            return;
+        }
+        int[] widgetIds = manager
                 .getAppWidgetIds(new ComponentName(context, PeopleSpaceWidgetProvider.class));
         if (DEBUG) {
             for (int id : widgetIds) {
diff --git a/packages/SystemUI/src/com/android/systemui/people/widget/PeopleSpaceWidgetManager.java b/packages/SystemUI/src/com/android/systemui/people/widget/PeopleSpaceWidgetManager.java
index 0a880293..b0de80c 100644
--- a/packages/SystemUI/src/com/android/systemui/people/widget/PeopleSpaceWidgetManager.java
+++ b/packages/SystemUI/src/com/android/systemui/people/widget/PeopleSpaceWidgetManager.java
@@ -140,7 +140,7 @@
     private final Object mLock = new Object();
     private final Context mContext;
     private LauncherApps mLauncherApps;
-    private AppWidgetManager mAppWidgetManager;
+    private Optional<AppWidgetManager> mAppWidgetManagerOptional;
     private IPeopleManager mIPeopleManager;
     private SharedPreferences mSharedPrefs;
     private PeopleManager mPeopleManager;
@@ -183,8 +183,9 @@
             };
 
     @Inject
-    public PeopleSpaceWidgetManager(Context context, LauncherApps launcherApps,
-            CommonNotifCollection notifCollection,
+    public PeopleSpaceWidgetManager(Context context,
+            Optional<AppWidgetManager> appWidgetManagerOptional,
+            LauncherApps launcherApps, CommonNotifCollection notifCollection,
             PackageManager packageManager, Optional<Bubbles> bubblesOptional,
             UserManager userManager, NotificationManager notificationManager,
             BroadcastDispatcher broadcastDispatcher, @Background Executor bgExecutor,
@@ -192,7 +193,7 @@
             @NonNull KeyguardUpdateMonitor keyguardUpdateMonitor) {
         if (DEBUG) Log.d(TAG, "constructor");
         mContext = context;
-        mAppWidgetManager = AppWidgetManager.getInstance(context);
+        mAppWidgetManagerOptional = appWidgetManagerOptional;
         mIPeopleManager = IPeopleManager.Stub.asInterface(
                 ServiceManager.getService(Context.PEOPLE_SERVICE));
         mLauncherApps = launcherApps;
@@ -266,14 +267,14 @@
      */
     @VisibleForTesting
     PeopleSpaceWidgetManager(Context context,
-            AppWidgetManager appWidgetManager, IPeopleManager iPeopleManager,
+            Optional<AppWidgetManager> appWidgetManager, IPeopleManager iPeopleManager,
             PeopleManager peopleManager, LauncherApps launcherApps,
             CommonNotifCollection notifCollection, PackageManager packageManager,
             Optional<Bubbles> bubblesOptional, UserManager userManager, BackupManager backupManager,
             INotificationManager iNotificationManager, NotificationManager notificationManager,
             @Background Executor executor, UserTracker userTracker) {
         mContext = context;
-        mAppWidgetManager = appWidgetManager;
+        mAppWidgetManagerOptional = appWidgetManager;
         mIPeopleManager = iPeopleManager;
         mPeopleManager = peopleManager;
         mLauncherApps = launcherApps;
@@ -337,6 +338,10 @@
 
     /** Updates the current widget view with provided {@link PeopleSpaceTile}. */
     private void updateAppWidgetViews(int appWidgetId, PeopleSpaceTile tile, Bundle options) {
+        if (mAppWidgetManagerOptional.isEmpty()) {
+            return;
+        }
+
         PeopleTileKey key = getKeyFromStorageByWidgetId(appWidgetId);
         if (DEBUG) Log.d(TAG, "Widget: " + appWidgetId + " for: " + key.toString());
 
@@ -349,7 +354,7 @@
 
         // Tell the AppWidgetManager to perform an update on the current app widget.
         if (DEBUG) Log.d(TAG, "Calling update widget for widgetId: " + appWidgetId);
-        mAppWidgetManager.updateAppWidget(appWidgetId, views);
+        mAppWidgetManagerOptional.get().updateAppWidget(appWidgetId, views);
     }
 
     /** Updates tile in app widget options and the current view. */
@@ -362,13 +367,17 @@
 
     /** Updates tile in app widget options and the current view. */
     public void updateAppWidgetOptionsAndView(int appWidgetId, PeopleSpaceTile tile) {
+        if (mAppWidgetManagerOptional.isEmpty()) {
+            return;
+        }
+
         if (tile == null) {
             Log.w(TAG, "Storing null tile for widget " + appWidgetId);
         }
         synchronized (mTiles) {
             mTiles.put(appWidgetId, tile);
         }
-        Bundle options = mAppWidgetManager.getAppWidgetOptions(appWidgetId);
+        Bundle options = mAppWidgetManagerOptional.get().getAppWidgetOptions(appWidgetId);
         updateAppWidgetViews(appWidgetId, tile, options);
     }
 
@@ -484,6 +493,10 @@
     private void updateWidgetsWithNotificationChangedInBackground(StatusBarNotification sbn,
             PeopleSpaceUtils.NotificationAction action,
             Collection<NotificationEntry> notifications) {
+        if (mAppWidgetManagerOptional.isEmpty()) {
+            return;
+        }
+
         try {
             PeopleTileKey key = new PeopleTileKey(
                     sbn.getShortcutId(), sbn.getUser().getIdentifier(), sbn.getPackageName());
@@ -491,7 +504,7 @@
                 if (DEBUG) Log.d(TAG, "Sbn doesn't contain valid PeopleTileKey: " + key.toString());
                 return;
             }
-            int[] widgetIds = mAppWidgetManager.getAppWidgetIds(
+            int[] widgetIds = mAppWidgetManagerOptional.get().getAppWidgetIds(
                     new ComponentName(mContext, PeopleSpaceWidgetProvider.class)
             );
             if (widgetIds.length == 0) {
@@ -807,10 +820,14 @@
                 UserHandle user,
                 NotificationChannel channel,
                 int modificationType) {
+            if (mAppWidgetManagerOptional.isEmpty()) {
+                return;
+            }
+
             if (channel.isConversation()) {
                 mBgExecutor.execute(() -> {
                     if (mUserManager.isUserUnlocked(user)) {
-                        updateWidgets(mAppWidgetManager.getAppWidgetIds(
+                        updateWidgets(mAppWidgetManagerOptional.get().getAppWidgetIds(
                                 new ComponentName(mContext, PeopleSpaceWidgetProvider.class)
                         ));
                     }
@@ -829,13 +846,18 @@
         // learning about the widget. If so, the widget adder should have populated options with
         // PeopleTileKey arguments.
         if (DEBUG) Log.d(TAG, "onAppWidgetOptionsChanged called for widget: " + appWidgetId);
+        if (mAppWidgetManagerOptional.isEmpty()) {
+            return;
+        }
+
         PeopleTileKey optionsKey = AppWidgetOptionsHelper.getPeopleTileKeyFromBundle(newOptions);
         if (PeopleTileKey.isValid(optionsKey)) {
             if (DEBUG) {
                 Log.d(TAG, "PeopleTileKey was present in Options, shortcutId: "
                         + optionsKey.getShortcutId());
             }
-            AppWidgetOptionsHelper.removePeopleTileKey(mAppWidgetManager, appWidgetId);
+            AppWidgetOptionsHelper.removePeopleTileKey(mAppWidgetManagerOptional.get(),
+                    appWidgetId);
             addNewWidget(appWidgetId, optionsKey);
         }
         // Update views for new widget dimensions.
@@ -1004,6 +1026,10 @@
     public boolean requestPinAppWidget(ShortcutInfo shortcutInfo, Bundle options) {
         if (DEBUG) Log.d(TAG, "Requesting pin widget, shortcutId: " + shortcutInfo.getId());
 
+        if (mAppWidgetManagerOptional.isEmpty()) {
+            return false;
+        }
+
         RemoteViews widgetPreview = getPreview(shortcutInfo.getId(),
                 shortcutInfo.getUserHandle(), shortcutInfo.getPackage(), options);
         if (widgetPreview == null) {
@@ -1017,7 +1043,8 @@
                 PeopleSpaceWidgetPinnedReceiver.getPendingIntent(mContext, shortcutInfo);
 
         ComponentName componentName = new ComponentName(mContext, PeopleSpaceWidgetProvider.class);
-        return mAppWidgetManager.requestPinAppWidget(componentName, extras, successCallback);
+        return mAppWidgetManagerOptional.get().requestPinAppWidget(componentName, extras,
+                successCallback);
     }
 
     /** Returns a list of map entries corresponding to user's priority conversations. */
@@ -1104,7 +1131,11 @@
     /** Updates any app widget to the current state, triggered by a broadcast update. */
     @VisibleForTesting
     void updateWidgetsFromBroadcastInBackground(String entryPoint) {
-        int[] appWidgetIds = mAppWidgetManager.getAppWidgetIds(
+        if (mAppWidgetManagerOptional.isEmpty()) {
+            return;
+        }
+
+        int[] appWidgetIds = mAppWidgetManagerOptional.get().getAppWidgetIds(
                 new ComponentName(mContext, PeopleSpaceWidgetProvider.class));
         if (appWidgetIds == null) {
             return;
@@ -1272,13 +1303,17 @@
         remapSharedFile(widgets);
         remapFollowupFile(widgets);
 
-        int[] widgetIds = mAppWidgetManager.getAppWidgetIds(
+        if (mAppWidgetManagerOptional.isEmpty()) {
+            return;
+        }
+
+        int[] widgetIds = mAppWidgetManagerOptional.get().getAppWidgetIds(
                 new ComponentName(mContext, PeopleSpaceWidgetProvider.class));
         Bundle b = new Bundle();
         b.putBoolean(AppWidgetManager.OPTION_APPWIDGET_RESTORE_COMPLETED, true);
         for (int id : widgetIds) {
             if (DEBUG) Log.d(TAG, "Setting widget as restored, widget id:" + id);
-            mAppWidgetManager.updateAppWidgetOptions(id, b);
+            mAppWidgetManagerOptional.get().updateAppWidgetOptions(id, b);
         }
 
         updateWidgets(widgetIds);
@@ -1437,14 +1472,15 @@
     @VisibleForTesting
     void updateGeneratedPreviewForUser(UserHandle user) {
         if (!generatedPreviews() || mUpdatedPreviews.get(user.getIdentifier())
-                || !mUserManager.isUserUnlocked(user)) {
+                || !mUserManager.isUserUnlocked(user) || mAppWidgetManagerOptional.isEmpty()) {
             return;
         }
 
         // The widget provider may be disabled on SystemUI implementers, e.g. TvSystemUI.
         ComponentName provider = new ComponentName(mContext, PeopleSpaceWidgetProvider.class);
-        List<AppWidgetProviderInfo> infos = mAppWidgetManager.getInstalledProvidersForPackage(
-                mContext.getPackageName(), user);
+        List<AppWidgetProviderInfo> infos =
+                mAppWidgetManagerOptional.get().getInstalledProvidersForPackage(
+                        mContext.getPackageName(), user);
         if (infos.stream().noneMatch(info -> info.provider.equals(provider))) {
             return;
         }
@@ -1452,7 +1488,7 @@
         if (DEBUG) {
             Log.d(TAG, "Updating People Space widget preview for user " + user.getIdentifier());
         }
-        boolean success = mAppWidgetManager.setWidgetPreview(
+        boolean success = mAppWidgetManagerOptional.get().setWidgetPreview(
                 provider, WIDGET_CATEGORY_HOME_SCREEN | WIDGET_CATEGORY_KEYGUARD,
                 new RemoteViews(mContext.getPackageName(),
                         R.layout.people_space_placeholder_layout));
diff --git a/packages/SystemUI/src/com/android/systemui/screenshot/ActionExecutor.kt b/packages/SystemUI/src/com/android/systemui/screenshot/ActionExecutor.kt
index 1868b4a..54e0319 100644
--- a/packages/SystemUI/src/com/android/systemui/screenshot/ActionExecutor.kt
+++ b/packages/SystemUI/src/com/android/systemui/screenshot/ActionExecutor.kt
@@ -40,7 +40,7 @@
     private val intentExecutor: ActionIntentExecutor,
     @Application private val applicationScope: CoroutineScope,
     @Assisted val window: Window,
-    @Assisted val viewProxy: ScreenshotViewProxy,
+    @Assisted val viewProxy: ScreenshotShelfViewProxy,
     @Assisted val finishDismiss: () -> Unit,
 ) {
 
@@ -109,7 +109,7 @@
     interface Factory {
         fun create(
             window: Window,
-            viewProxy: ScreenshotViewProxy,
+            viewProxy: ScreenshotShelfViewProxy,
             finishDismiss: (() -> Unit)
         ): ActionExecutor
     }
diff --git a/packages/SystemUI/src/com/android/systemui/screenshot/LegacyScreenshotViewProxy.kt b/packages/SystemUI/src/com/android/systemui/screenshot/LegacyScreenshotViewProxy.kt
deleted file mode 100644
index 3d024a6..0000000
--- a/packages/SystemUI/src/com/android/systemui/screenshot/LegacyScreenshotViewProxy.kt
+++ /dev/null
@@ -1,242 +0,0 @@
-/*
- * Copyright (C) 2024 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- *      http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-package com.android.systemui.screenshot
-
-import android.animation.Animator
-import android.app.Notification
-import android.content.Context
-import android.graphics.Bitmap
-import android.graphics.Rect
-import android.util.Log
-import android.view.KeyEvent
-import android.view.LayoutInflater
-import android.view.ScrollCaptureResponse
-import android.view.View
-import android.view.ViewTreeObserver
-import android.view.WindowInsets
-import android.window.OnBackInvokedCallback
-import android.window.OnBackInvokedDispatcher
-import androidx.appcompat.content.res.AppCompatResources
-import com.android.internal.logging.UiEventLogger
-import com.android.systemui.flags.FeatureFlags
-import com.android.systemui.res.R
-import com.android.systemui.screenshot.LogConfig.DEBUG_DISMISS
-import com.android.systemui.screenshot.ScreenshotEvent.SCREENSHOT_DISMISSED_OTHER
-import com.android.systemui.screenshot.scroll.ScrollCaptureController
-import dagger.assisted.Assisted
-import dagger.assisted.AssistedFactory
-import dagger.assisted.AssistedInject
-
-/**
- * Legacy implementation of screenshot view methods. Just proxies the calls down into the original
- * ScreenshotView.
- */
-class LegacyScreenshotViewProxy
-@AssistedInject
-constructor(
-    private val logger: UiEventLogger,
-    flags: FeatureFlags,
-    @Assisted private val context: Context,
-    @Assisted private val displayId: Int
-) : ScreenshotViewProxy {
-    override val view: ScreenshotView =
-        LayoutInflater.from(context).inflate(R.layout.screenshot, null) as ScreenshotView
-    override val screenshotPreview: View
-    override var packageName: String = ""
-        set(value) {
-            field = value
-            view.setPackageName(value)
-        }
-    override var callbacks: ScreenshotView.ScreenshotViewCallback? = null
-        set(value) {
-            field = value
-            view.setCallbacks(value)
-        }
-    override var screenshot: ScreenshotData? = null
-        set(value) {
-            field = value
-            value?.let {
-                val badgeBg =
-                    AppCompatResources.getDrawable(context, R.drawable.overlay_badge_background)
-                val user = it.userHandle
-                if (badgeBg != null && user != null) {
-                    view.badgeScreenshot(context.packageManager.getUserBadgedIcon(badgeBg, user))
-                }
-                view.setScreenshot(it)
-            }
-        }
-
-    override val isAttachedToWindow
-        get() = view.isAttachedToWindow
-    override val isDismissing
-        get() = view.isDismissing
-    override val isPendingSharedTransition
-        get() = view.isPendingSharedTransition
-
-    init {
-        view.setUiEventLogger(logger)
-        view.setDefaultDisplay(displayId)
-        view.setFlags(flags)
-        addPredictiveBackListener { requestDismissal(SCREENSHOT_DISMISSED_OTHER) }
-        setOnKeyListener { requestDismissal(SCREENSHOT_DISMISSED_OTHER) }
-        if (LogConfig.DEBUG_WINDOW) {
-            Log.d(TAG, "adding OnComputeInternalInsetsListener")
-        }
-        view.viewTreeObserver.addOnComputeInternalInsetsListener(view)
-        screenshotPreview = view.screenshotPreview
-    }
-
-    override fun reset() = view.reset()
-    override fun updateInsets(insets: WindowInsets) = view.updateInsets(insets)
-    override fun updateOrientation(insets: WindowInsets) = view.updateOrientation(insets)
-
-    override fun createScreenshotDropInAnimation(screenRect: Rect, showFlash: Boolean): Animator =
-        view.createScreenshotDropInAnimation(screenRect, showFlash)
-
-    override fun addQuickShareChip(quickShareAction: Notification.Action) =
-        view.addQuickShareChip(quickShareAction)
-
-    override fun setChipIntents(imageData: ScreenshotController.SavedImageData) =
-        view.setChipIntents(imageData)
-
-    override fun requestDismissal(event: ScreenshotEvent?) {
-        if (DEBUG_DISMISS) {
-            Log.d(TAG, "screenshot dismissal requested")
-        }
-        // If we're already animating out, don't restart the animation
-        if (view.isDismissing) {
-            if (DEBUG_DISMISS) {
-                Log.v(TAG, "Already dismissing, ignoring duplicate command $event")
-            }
-            return
-        }
-        event?.let { logger.log(event, 0, packageName) }
-        view.animateDismissal()
-    }
-
-    override fun showScrollChip(packageName: String, onClick: Runnable) =
-        view.showScrollChip(packageName, onClick)
-
-    override fun hideScrollChip() = view.hideScrollChip()
-
-    override fun prepareScrollingTransition(
-        response: ScrollCaptureResponse,
-        screenBitmap: Bitmap,
-        newScreenshot: Bitmap,
-        screenshotTakenInPortrait: Boolean,
-        onTransitionPrepared: Runnable,
-    ) {
-        view.prepareScrollingTransition(
-            response,
-            screenBitmap,
-            newScreenshot,
-            screenshotTakenInPortrait
-        )
-        view.post { onTransitionPrepared.run() }
-    }
-
-    override fun startLongScreenshotTransition(
-        transitionDestination: Rect,
-        onTransitionEnd: Runnable,
-        longScreenshot: ScrollCaptureController.LongScreenshot
-    ) = view.startLongScreenshotTransition(transitionDestination, onTransitionEnd, longScreenshot)
-
-    override fun restoreNonScrollingUi() = view.restoreNonScrollingUi()
-
-    override fun fadeForSharedTransition() {} // unused
-
-    override fun stopInputListening() = view.stopInputListening()
-
-    override fun requestFocus() {
-        view.requestFocus()
-    }
-
-    override fun announceForAccessibility(string: String) = view.announceForAccessibility(string)
-
-    override fun prepareEntranceAnimation(runnable: Runnable) {
-        view.viewTreeObserver.addOnPreDrawListener(
-            object : ViewTreeObserver.OnPreDrawListener {
-                override fun onPreDraw(): Boolean {
-                    if (LogConfig.DEBUG_WINDOW) {
-                        Log.d(TAG, "onPreDraw: startAnimation")
-                    }
-                    view.viewTreeObserver.removeOnPreDrawListener(this)
-                    runnable.run()
-                    return true
-                }
-            }
-        )
-    }
-
-    private fun addPredictiveBackListener(onDismissRequested: (ScreenshotEvent) -> Unit) {
-        val onBackInvokedCallback = OnBackInvokedCallback {
-            if (LogConfig.DEBUG_INPUT) {
-                Log.d(TAG, "Predictive Back callback dispatched")
-            }
-            onDismissRequested.invoke(SCREENSHOT_DISMISSED_OTHER)
-        }
-        view.addOnAttachStateChangeListener(
-            object : View.OnAttachStateChangeListener {
-                override fun onViewAttachedToWindow(v: View) {
-                    if (LogConfig.DEBUG_INPUT) {
-                        Log.d(TAG, "Registering Predictive Back callback")
-                    }
-                    view
-                        .findOnBackInvokedDispatcher()
-                        ?.registerOnBackInvokedCallback(
-                            OnBackInvokedDispatcher.PRIORITY_DEFAULT,
-                            onBackInvokedCallback
-                        )
-                }
-
-                override fun onViewDetachedFromWindow(view: View) {
-                    if (LogConfig.DEBUG_INPUT) {
-                        Log.d(TAG, "Unregistering Predictive Back callback")
-                    }
-                    view
-                        .findOnBackInvokedDispatcher()
-                        ?.unregisterOnBackInvokedCallback(onBackInvokedCallback)
-                }
-            }
-        )
-    }
-    private fun setOnKeyListener(onDismissRequested: (ScreenshotEvent) -> Unit) {
-        view.setOnKeyListener(
-            object : View.OnKeyListener {
-                override fun onKey(view: View, keyCode: Int, event: KeyEvent): Boolean {
-                    if (keyCode == KeyEvent.KEYCODE_BACK || keyCode == KeyEvent.KEYCODE_ESCAPE) {
-                        if (LogConfig.DEBUG_INPUT) {
-                            Log.d(TAG, "onKeyEvent: $keyCode")
-                        }
-                        onDismissRequested.invoke(SCREENSHOT_DISMISSED_OTHER)
-                        return true
-                    }
-                    return false
-                }
-            }
-        )
-    }
-
-    @AssistedFactory
-    interface Factory : ScreenshotViewProxy.Factory {
-        override fun getProxy(context: Context, displayId: Int): LegacyScreenshotViewProxy
-    }
-
-    companion object {
-        private const val TAG = "LegacyScreenshotViewProxy"
-    }
-}
diff --git a/packages/SystemUI/src/com/android/systemui/screenshot/MessageContainerController.kt b/packages/SystemUI/src/com/android/systemui/screenshot/MessageContainerController.kt
index 5960462..474afa8b 100644
--- a/packages/SystemUI/src/com/android/systemui/screenshot/MessageContainerController.kt
+++ b/packages/SystemUI/src/com/android/systemui/screenshot/MessageContainerController.kt
@@ -137,7 +137,7 @@
         val offset = container.height + params.topMargin + params.bottomMargin
         val anim = if (animateIn) ValueAnimator.ofFloat(0f, 1f) else ValueAnimator.ofFloat(1f, 0f)
         with(anim) {
-            duration = ScreenshotView.SCREENSHOT_ACTIONS_EXPANSION_DURATION_MS
+            duration = MESSAGE_EXPANSION_DURATION_MS
             interpolator = AccelerateDecelerateInterpolator()
             addUpdateListener { valueAnimator: ValueAnimator ->
                 val interpolation = valueAnimator.animatedValue as Float
@@ -147,4 +147,8 @@
         }
         return anim
     }
+
+    companion object {
+        const val MESSAGE_EXPANSION_DURATION_MS: Long = 400
+    }
 }
diff --git a/packages/SystemUI/src/com/android/systemui/screenshot/ScreenshotController.java b/packages/SystemUI/src/com/android/systemui/screenshot/ScreenshotController.java
index 0cbf8f9..7739009 100644
--- a/packages/SystemUI/src/com/android/systemui/screenshot/ScreenshotController.java
+++ b/packages/SystemUI/src/com/android/systemui/screenshot/ScreenshotController.java
@@ -191,7 +191,7 @@
 
     private final WindowContext mContext;
     private final FeatureFlags mFlags;
-    private final ScreenshotViewProxy mViewProxy;
+    private final ScreenshotShelfViewProxy mViewProxy;
     private final ScreenshotNotificationsController mNotificationsController;
     private final ScreenshotSmartActions mScreenshotSmartActions;
     private final UiEventLogger mUiEventLogger;
@@ -246,7 +246,7 @@
             Context context,
             WindowManager windowManager,
             FeatureFlags flags,
-            ScreenshotViewProxy.Factory viewProxyFactory,
+            ScreenshotShelfViewProxy.Factory viewProxyFactory,
             ScreenshotSmartActions screenshotSmartActions,
             ScreenshotNotificationsController.Factory screenshotNotificationsControllerFactory,
             UiEventLogger uiEventLogger,
@@ -477,9 +477,6 @@
         }
 
         mViewProxy.setPackageName(mPackageName);
-
-        mViewProxy.updateOrientation(
-                mWindowManager.getCurrentWindowMetrics().getWindowInsets());
     }
 
     /**
@@ -528,7 +525,7 @@
         }
 
         mMessageContainerController.setView(mViewProxy.getView());
-        mViewProxy.setCallbacks(new ScreenshotView.ScreenshotViewCallback() {
+        mViewProxy.setCallbacks(new ScreenshotShelfViewProxy.ScreenshotViewCallback() {
             @Override
             public void onUserInteraction() {
                 if (DEBUG_INPUT) {
@@ -538,13 +535,6 @@
             }
 
             @Override
-            public void onAction(Intent intent, UserHandle owner, boolean overrideTransition) {
-                Pair<ActivityOptions, ExitTransitionCoordinator> exit = createWindowTransition();
-                mActionIntentExecutor.launchIntentAsync(
-                        intent, owner, overrideTransition, exit.first, exit.second);
-            }
-
-            @Override
             public void onDismiss() {
                 finishDismiss();
             }
@@ -871,62 +861,6 @@
         mSaveInBgTask.execute();
     }
 
-
-    /**
-     * Sets up the action shade and its entrance animation, once we get the screenshot URI.
-     */
-    private void showUiOnActionsReady(ScreenshotController.SavedImageData imageData) {
-        logSuccessOnActionsReady(imageData);
-        mScreenshotHandler.resetTimeout();
-
-        if (imageData.uri != null) {
-            if (DEBUG_UI) {
-                Log.d(TAG, "Showing UI actions");
-            }
-            if (!imageData.owner.equals(Process.myUserHandle())) {
-                Log.d(TAG, "Screenshot saved to user " + imageData.owner + " as "
-                        + imageData.uri);
-            }
-            mScreenshotHandler.post(() -> {
-                if (mScreenshotAnimation != null && mScreenshotAnimation.isRunning()) {
-                    mScreenshotAnimation.addListener(new AnimatorListenerAdapter() {
-                        @Override
-                        public void onAnimationEnd(Animator animation) {
-                            super.onAnimationEnd(animation);
-                            mViewProxy.setChipIntents(imageData);
-                        }
-                    });
-                } else {
-                    mViewProxy.setChipIntents(imageData);
-                }
-            });
-        }
-    }
-
-    /**
-     * Sets up the action shade and its entrance animation, once we get the Quick Share action data.
-     */
-    private void showUiOnQuickShareActionReady(ScreenshotController.QuickShareData quickShareData) {
-        if (DEBUG_UI) {
-            Log.d(TAG, "Showing UI for Quick Share action");
-        }
-        if (quickShareData.quickShareAction != null) {
-            mScreenshotHandler.post(() -> {
-                if (mScreenshotAnimation != null && mScreenshotAnimation.isRunning()) {
-                    mScreenshotAnimation.addListener(new AnimatorListenerAdapter() {
-                        @Override
-                        public void onAnimationEnd(Animator animation) {
-                            super.onAnimationEnd(animation);
-                            mViewProxy.addQuickShareChip(quickShareData.quickShareAction);
-                        }
-                    });
-                } else {
-                    mViewProxy.addQuickShareChip(quickShareData.quickShareAction);
-                }
-            });
-        }
-    }
-
     /**
      * Logs success/failure of the screenshot saving task, and shows an error if it failed.
      */
diff --git a/packages/SystemUI/src/com/android/systemui/screenshot/ScreenshotShelfViewProxy.kt b/packages/SystemUI/src/com/android/systemui/screenshot/ScreenshotShelfViewProxy.kt
index 1b5fa34..50215af 100644
--- a/packages/SystemUI/src/com/android/systemui/screenshot/ScreenshotShelfViewProxy.kt
+++ b/packages/SystemUI/src/com/android/systemui/screenshot/ScreenshotShelfViewProxy.kt
@@ -18,7 +18,6 @@
 
 import android.animation.Animator
 import android.animation.AnimatorListenerAdapter
-import android.app.Notification
 import android.content.Context
 import android.graphics.Bitmap
 import android.graphics.Rect
@@ -45,7 +44,6 @@
 import com.android.systemui.screenshot.LogConfig.DEBUG_DISMISS
 import com.android.systemui.screenshot.LogConfig.DEBUG_INPUT
 import com.android.systemui.screenshot.LogConfig.DEBUG_WINDOW
-import com.android.systemui.screenshot.ScreenshotController.SavedImageData
 import com.android.systemui.screenshot.ScreenshotEvent.SCREENSHOT_DISMISSED_OTHER
 import com.android.systemui.screenshot.scroll.ScrollCaptureController
 import com.android.systemui.screenshot.ui.ScreenshotAnimationController
@@ -70,13 +68,23 @@
     private val thumbnailObserver: ThumbnailObserver,
     @Assisted private val context: Context,
     @Assisted private val displayId: Int
-) : ScreenshotViewProxy {
-    override val view: ScreenshotShelfView =
+) {
+
+    interface ScreenshotViewCallback {
+        fun onUserInteraction()
+
+        fun onDismiss()
+
+        /** DOWN motion event was observed outside of the touchable areas of this view. */
+        fun onTouchOutside()
+    }
+
+    val view: ScreenshotShelfView =
         LayoutInflater.from(context).inflate(R.layout.screenshot_shelf, null) as ScreenshotShelfView
-    override val screenshotPreview: View
-    override var packageName: String = ""
-    override var callbacks: ScreenshotView.ScreenshotViewCallback? = null
-    override var screenshot: ScreenshotData? = null
+    val screenshotPreview: View
+    var packageName: String = ""
+    var callbacks: ScreenshotViewCallback? = null
+    var screenshot: ScreenshotData? = null
         set(value) {
             value?.let {
                 viewModel.setScreenshotBitmap(it.bitmap)
@@ -92,10 +100,11 @@
             field = value
         }
 
-    override val isAttachedToWindow
+    val isAttachedToWindow
         get() = view.isAttachedToWindow
-    override var isDismissing = false
-    override var isPendingSharedTransition = false
+
+    var isDismissing = false
+    var isPendingSharedTransition = false
 
     private val animationController = ScreenshotAnimationController(view, viewModel)
     private var inputMonitor: InputMonitorCompat? = null
@@ -136,17 +145,17 @@
         )
     }
 
-    override fun reset() {
+    fun reset() {
         animationController.cancel()
         isPendingSharedTransition = false
         viewModel.reset()
     }
-    override fun updateInsets(insets: WindowInsets) {
+
+    fun updateInsets(insets: WindowInsets) {
         view.updateInsets(insets)
     }
-    override fun updateOrientation(insets: WindowInsets) {}
 
-    override fun createScreenshotDropInAnimation(screenRect: Rect, showFlash: Boolean): Animator {
+    fun createScreenshotDropInAnimation(screenRect: Rect, showFlash: Boolean): Animator {
         val entrance =
             animationController.getEntranceAnimation(screenRect, showFlash) {
                 viewModel.setAnimationState(AnimationState.ENTRANCE_REVEAL)
@@ -164,11 +173,7 @@
         return entrance
     }
 
-    override fun addQuickShareChip(quickShareAction: Notification.Action) {}
-
-    override fun setChipIntents(imageData: SavedImageData) {}
-
-    override fun requestDismissal(event: ScreenshotEvent?) {
+    fun requestDismissal(event: ScreenshotEvent?) {
         requestDismissal(event, null)
     }
 
@@ -187,6 +192,7 @@
                 override fun onAnimationStart(animator: Animator) {
                     isDismissing = true
                 }
+
                 override fun onAnimationEnd(animator: Animator) {
                     isDismissing = false
                     callbacks?.onDismiss()
@@ -196,11 +202,7 @@
         animator.start()
     }
 
-    override fun showScrollChip(packageName: String, onClick: Runnable) {}
-
-    override fun hideScrollChip() {}
-
-    override fun prepareScrollingTransition(
+    fun prepareScrollingTransition(
         response: ScrollCaptureResponse,
         screenBitmap: Bitmap, // unused
         newScreenshot: Bitmap,
@@ -228,7 +230,7 @@
         return r
     }
 
-    override fun startLongScreenshotTransition(
+    fun startLongScreenshotTransition(
         transitionDestination: Rect,
         onTransitionEnd: Runnable,
         longScreenshot: ScrollCaptureController.LongScreenshot,
@@ -243,27 +245,27 @@
         transitionAnimation.start()
     }
 
-    override fun restoreNonScrollingUi() {
+    fun restoreNonScrollingUi() {
         viewModel.setScrollableRect(null)
         viewModel.setScrollingScrimBitmap(null)
         animationController.restoreUI()
         callbacks?.onUserInteraction() // reset the timeout
     }
 
-    override fun stopInputListening() {
+    fun stopInputListening() {
         inputMonitor?.dispose()
         inputMonitor = null
         inputEventReceiver?.dispose()
         inputEventReceiver = null
     }
 
-    override fun requestFocus() {
+    fun requestFocus() {
         view.requestFocus()
     }
 
-    override fun announceForAccessibility(string: String) = view.announceForAccessibility(string)
+    fun announceForAccessibility(string: String) = view.announceForAccessibility(string)
 
-    override fun prepareEntranceAnimation(runnable: Runnable) {
+    fun prepareEntranceAnimation(runnable: Runnable) {
         view.viewTreeObserver.addOnPreDrawListener(
             object : ViewTreeObserver.OnPreDrawListener {
                 override fun onPreDraw(): Boolean {
@@ -276,7 +278,7 @@
         )
     }
 
-    override fun fadeForSharedTransition() {
+    fun fadeForSharedTransition() {
         animationController.fadeForSharedTransition()
     }
 
@@ -349,7 +351,7 @@
     }
 
     @AssistedFactory
-    interface Factory : ScreenshotViewProxy.Factory {
-        override fun getProxy(context: Context, displayId: Int): ScreenshotShelfViewProxy
+    interface Factory {
+        fun getProxy(context: Context, displayId: Int): ScreenshotShelfViewProxy
     }
 }
diff --git a/packages/SystemUI/src/com/android/systemui/screenshot/ScreenshotView.java b/packages/SystemUI/src/com/android/systemui/screenshot/ScreenshotView.java
deleted file mode 100644
index 59e38a8..0000000
--- a/packages/SystemUI/src/com/android/systemui/screenshot/ScreenshotView.java
+++ /dev/null
@@ -1,1126 +0,0 @@
-/*
- * Copyright (C) 2020 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- *      http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-package com.android.systemui.screenshot;
-
-import static android.content.res.Configuration.ORIENTATION_PORTRAIT;
-
-import static com.android.internal.jank.InteractionJankMonitor.CUJ_TAKE_SCREENSHOT;
-import static com.android.systemui.screenshot.LogConfig.DEBUG_ANIM;
-import static com.android.systemui.screenshot.LogConfig.DEBUG_DISMISS;
-import static com.android.systemui.screenshot.LogConfig.DEBUG_INPUT;
-import static com.android.systemui.screenshot.LogConfig.DEBUG_SCROLL;
-import static com.android.systemui.screenshot.LogConfig.DEBUG_UI;
-import static com.android.systemui.screenshot.LogConfig.DEBUG_WINDOW;
-import static com.android.systemui.screenshot.LogConfig.logTag;
-import static com.android.systemui.screenshot.ScreenshotController.SCREENSHOT_CORNER_DEFAULT_TIMEOUT_MILLIS;
-
-import static java.util.Objects.requireNonNull;
-
-import android.animation.Animator;
-import android.animation.AnimatorListenerAdapter;
-import android.animation.AnimatorSet;
-import android.animation.ValueAnimator;
-import android.annotation.Nullable;
-import android.app.ActivityManager;
-import android.app.BroadcastOptions;
-import android.app.Notification;
-import android.content.Context;
-import android.content.Intent;
-import android.content.res.ColorStateList;
-import android.content.res.Resources;
-import android.graphics.Bitmap;
-import android.graphics.BlendMode;
-import android.graphics.Color;
-import android.graphics.Insets;
-import android.graphics.Matrix;
-import android.graphics.PointF;
-import android.graphics.Rect;
-import android.graphics.Region;
-import android.graphics.drawable.BitmapDrawable;
-import android.graphics.drawable.ColorDrawable;
-import android.graphics.drawable.Drawable;
-import android.graphics.drawable.Icon;
-import android.graphics.drawable.InsetDrawable;
-import android.graphics.drawable.LayerDrawable;
-import android.os.Bundle;
-import android.os.Looper;
-import android.os.RemoteException;
-import android.os.UserHandle;
-import android.util.AttributeSet;
-import android.util.DisplayMetrics;
-import android.util.Log;
-import android.util.MathUtils;
-import android.view.Choreographer;
-import android.view.Display;
-import android.view.DisplayCutout;
-import android.view.GestureDetector;
-import android.view.LayoutInflater;
-import android.view.MotionEvent;
-import android.view.ScrollCaptureResponse;
-import android.view.View;
-import android.view.ViewGroup;
-import android.view.ViewTreeObserver;
-import android.view.WindowInsets;
-import android.view.WindowManager;
-import android.view.WindowMetrics;
-import android.view.accessibility.AccessibilityManager;
-import android.view.animation.AnimationUtils;
-import android.view.animation.Interpolator;
-import android.widget.FrameLayout;
-import android.widget.HorizontalScrollView;
-import android.widget.ImageView;
-import android.widget.LinearLayout;
-
-import androidx.constraintlayout.widget.ConstraintLayout;
-
-import com.android.internal.jank.InteractionJankMonitor;
-import com.android.internal.logging.UiEventLogger;
-import com.android.systemui.flags.FeatureFlags;
-import com.android.systemui.res.R;
-import com.android.systemui.screenshot.scroll.ScrollCaptureController;
-import com.android.systemui.shared.system.InputChannelCompat;
-import com.android.systemui.shared.system.InputMonitorCompat;
-import com.android.systemui.shared.system.QuickStepContract;
-
-import java.util.ArrayList;
-
-/**
- * Handles the visual elements and animations for the screenshot flow.
- */
-public class ScreenshotView extends FrameLayout implements
-        ViewTreeObserver.OnComputeInternalInsetsListener {
-
-    public interface ScreenshotViewCallback {
-        void onUserInteraction();
-
-        void onAction(Intent intent, UserHandle owner, boolean overrideTransition);
-
-        void onDismiss();
-
-        /** DOWN motion event was observed outside of the touchable areas of this view. */
-        void onTouchOutside();
-    }
-
-    private static final String TAG = logTag(ScreenshotView.class);
-
-    private static final long SCREENSHOT_FLASH_IN_DURATION_MS = 133;
-    private static final long SCREENSHOT_FLASH_OUT_DURATION_MS = 217;
-    // delay before starting to fade in dismiss button
-    private static final long SCREENSHOT_TO_CORNER_DISMISS_DELAY_MS = 200;
-    private static final long SCREENSHOT_TO_CORNER_X_DURATION_MS = 234;
-    private static final long SCREENSHOT_TO_CORNER_Y_DURATION_MS = 500;
-    private static final long SCREENSHOT_TO_CORNER_SCALE_DURATION_MS = 234;
-    public static final long SCREENSHOT_ACTIONS_EXPANSION_DURATION_MS = 400;
-    private static final long SCREENSHOT_ACTIONS_ALPHA_DURATION_MS = 100;
-    private static final float SCREENSHOT_ACTIONS_START_SCALE_X = .7f;
-
-    private final Resources mResources;
-    private final Interpolator mFastOutSlowIn;
-    private final DisplayMetrics mDisplayMetrics;
-    private final float mFixedSize;
-    private final AccessibilityManager mAccessibilityManager;
-    private final GestureDetector mSwipeDetector;
-
-    private int mDefaultDisplay = Display.DEFAULT_DISPLAY;
-    private int mNavMode;
-    private boolean mOrientationPortrait;
-    private boolean mDirectionLTR;
-
-    private ImageView mScrollingScrim;
-    private DraggableConstraintLayout mScreenshotStatic;
-    private ImageView mScreenshotPreview;
-    private ImageView mScreenshotBadge;
-    private View mScreenshotPreviewBorder;
-    private ImageView mScrollablePreview;
-    private ImageView mScreenshotFlash;
-    private ImageView mActionsContainerBackground;
-    private HorizontalScrollView mActionsContainer;
-    private LinearLayout mActionsView;
-    private FrameLayout mDismissButton;
-    private OverlayActionChip mShareChip;
-    private OverlayActionChip mEditChip;
-    private OverlayActionChip mScrollChip;
-    private OverlayActionChip mQuickShareChip;
-
-    private UiEventLogger mUiEventLogger;
-    private ScreenshotViewCallback mCallbacks;
-    private boolean mPendingSharedTransition;
-    private InputMonitorCompat mInputMonitor;
-    private InputChannelCompat.InputEventReceiver mInputEventReceiver;
-    private boolean mShowScrollablePreview;
-    private String mPackageName = "";
-
-    private final ArrayList<OverlayActionChip> mSmartChips = new ArrayList<>();
-    private PendingInteraction mPendingInteraction;
-    // Should only be set/used if the SCREENSHOT_METADATA flag is set.
-    private ScreenshotData mScreenshotData;
-
-    private final InteractionJankMonitor mInteractionJankMonitor;
-    private FeatureFlags mFlags;
-    private final Bundle mInteractiveBroadcastOption;
-
-    private enum PendingInteraction {
-        PREVIEW,
-        EDIT,
-        SHARE,
-        QUICK_SHARE
-    }
-
-    public ScreenshotView(Context context) {
-        this(context, null);
-    }
-
-    public ScreenshotView(Context context, AttributeSet attrs) {
-        this(context, attrs, 0);
-    }
-
-    public ScreenshotView(Context context, AttributeSet attrs, int defStyleAttr) {
-        this(context, attrs, defStyleAttr, 0);
-    }
-
-    public ScreenshotView(
-            Context context, AttributeSet attrs, int defStyleAttr, int defStyleRes) {
-        super(context, attrs, defStyleAttr, defStyleRes);
-        mResources = mContext.getResources();
-        mInteractionJankMonitor = getInteractionJankMonitorInstance();
-
-        BroadcastOptions options = BroadcastOptions.makeBasic();
-        options.setInteractive(true);
-        mInteractiveBroadcastOption = options.toBundle();
-
-        mFixedSize = mResources.getDimensionPixelSize(R.dimen.overlay_x_scale);
-
-        // standard material ease
-        mFastOutSlowIn =
-                AnimationUtils.loadInterpolator(mContext, android.R.interpolator.fast_out_slow_in);
-
-        mDisplayMetrics = new DisplayMetrics();
-        mContext.getDisplay().getRealMetrics(mDisplayMetrics);
-
-        mAccessibilityManager = AccessibilityManager.getInstance(mContext);
-
-        mSwipeDetector = new GestureDetector(mContext,
-                new GestureDetector.SimpleOnGestureListener() {
-                    final Rect mActionsRect = new Rect();
-
-                    @Override
-                    public boolean onScroll(
-                            MotionEvent ev1, MotionEvent ev2, float distanceX, float distanceY) {
-                        mActionsContainer.getBoundsOnScreen(mActionsRect);
-                        // return true if we aren't in the actions bar, or if we are but it isn't
-                        // scrollable in the direction of movement
-                        return !mActionsRect.contains((int) ev2.getRawX(), (int) ev2.getRawY())
-                                || !mActionsContainer.canScrollHorizontally((int) distanceX);
-                    }
-                });
-        mSwipeDetector.setIsLongpressEnabled(false);
-        addOnAttachStateChangeListener(new OnAttachStateChangeListener() {
-            @Override
-            public void onViewAttachedToWindow(View v) {
-                startInputListening();
-            }
-
-            @Override
-            public void onViewDetachedFromWindow(View v) {
-                stopInputListening();
-            }
-        });
-    }
-
-    private InteractionJankMonitor getInteractionJankMonitorInstance() {
-        return InteractionJankMonitor.getInstance();
-    }
-
-    public void hideScrollChip() {
-        mScrollChip.setVisibility(View.GONE);
-    }
-
-    /**
-     * Called to display the scroll action chip when support is detected.
-     *
-     * @param packageName the owning package of the window to be captured
-     * @param onClick     the action to take when the chip is clicked.
-     */
-    public void showScrollChip(String packageName, Runnable onClick) {
-        if (DEBUG_SCROLL) {
-            Log.d(TAG, "Showing Scroll option");
-        }
-        mScrollChip.setVisibility(VISIBLE);
-        mScrollChip.setOnClickListener((v) -> onClick.run());
-    }
-
-    @Override // ViewTreeObserver.OnComputeInternalInsetsListener
-    public void onComputeInternalInsets(ViewTreeObserver.InternalInsetsInfo inoutInfo) {
-        inoutInfo.setTouchableInsets(ViewTreeObserver.InternalInsetsInfo.TOUCHABLE_INSETS_REGION);
-        inoutInfo.touchableRegion.set(getTouchRegion(true));
-    }
-
-    private Region getSwipeRegion() {
-        Region swipeRegion = new Region();
-
-        final Rect tmpRect = new Rect();
-        int swipePadding = (int) FloatingWindowUtil.dpToPx(
-                mDisplayMetrics, DraggableConstraintLayout.SWIPE_PADDING_DP * -1);
-        mScreenshotPreview.getBoundsOnScreen(tmpRect);
-        tmpRect.inset(swipePadding, swipePadding);
-        swipeRegion.op(tmpRect, Region.Op.UNION);
-        mActionsContainerBackground.getBoundsOnScreen(tmpRect);
-        tmpRect.inset(swipePadding, swipePadding);
-        swipeRegion.op(tmpRect, Region.Op.UNION);
-        mDismissButton.getBoundsOnScreen(tmpRect);
-        swipeRegion.op(tmpRect, Region.Op.UNION);
-
-        View messageContainer = findViewById(R.id.screenshot_message_container);
-        if (messageContainer != null) {
-            messageContainer.getBoundsOnScreen(tmpRect);
-            swipeRegion.op(tmpRect, Region.Op.UNION);
-        }
-        View messageDismiss = findViewById(R.id.message_dismiss_button);
-        if (messageDismiss != null) {
-            messageDismiss.getBoundsOnScreen(tmpRect);
-            swipeRegion.op(tmpRect, Region.Op.UNION);
-        }
-
-        return swipeRegion;
-    }
-
-    private Region getTouchRegion(boolean includeScrim) {
-        Region touchRegion = getSwipeRegion();
-
-        if (includeScrim && mScrollingScrim.getVisibility() == View.VISIBLE) {
-            final Rect tmpRect = new Rect();
-            mScrollingScrim.getBoundsOnScreen(tmpRect);
-            touchRegion.op(tmpRect, Region.Op.UNION);
-        }
-
-        if (QuickStepContract.isGesturalMode(mNavMode)) {
-            final WindowManager wm = mContext.getSystemService(WindowManager.class);
-            final WindowMetrics windowMetrics = wm.getCurrentWindowMetrics();
-            final Insets gestureInsets = windowMetrics.getWindowInsets().getInsets(
-                    WindowInsets.Type.systemGestures());
-            // Receive touches in gesture insets such that they don't cause TOUCH_OUTSIDE
-            Rect inset = new Rect(0, 0, gestureInsets.left, mDisplayMetrics.heightPixels);
-            touchRegion.op(inset, Region.Op.UNION);
-            inset.set(mDisplayMetrics.widthPixels - gestureInsets.right, 0,
-                    mDisplayMetrics.widthPixels, mDisplayMetrics.heightPixels);
-            touchRegion.op(inset, Region.Op.UNION);
-        }
-        return touchRegion;
-    }
-
-    private void startInputListening() {
-        stopInputListening();
-        mInputMonitor = new InputMonitorCompat("Screenshot", mDefaultDisplay);
-        mInputEventReceiver = mInputMonitor.getInputReceiver(
-                Looper.getMainLooper(), Choreographer.getInstance(), ev -> {
-                    if (ev instanceof MotionEvent) {
-                        MotionEvent event = (MotionEvent) ev;
-                        if (event.getActionMasked() == MotionEvent.ACTION_DOWN
-                                && !getTouchRegion(false).contains(
-                                (int) event.getRawX(), (int) event.getRawY())) {
-                            mCallbacks.onTouchOutside();
-                        }
-                    }
-                });
-    }
-
-    void stopInputListening() {
-        if (mInputMonitor != null) {
-            mInputMonitor.dispose();
-            mInputMonitor = null;
-        }
-        if (mInputEventReceiver != null) {
-            mInputEventReceiver.dispose();
-            mInputEventReceiver = null;
-        }
-    }
-
-    @Override // View
-    protected void onFinishInflate() {
-        super.onFinishInflate();
-        mScrollingScrim = requireNonNull(findViewById(R.id.screenshot_scrolling_scrim));
-        mScreenshotStatic = requireNonNull(findViewById(R.id.screenshot_static));
-        mScreenshotPreview = requireNonNull(findViewById(R.id.screenshot_preview));
-
-        mScreenshotPreviewBorder = requireNonNull(
-                findViewById(R.id.screenshot_preview_border));
-        mScreenshotPreview.setClipToOutline(true);
-        mScreenshotBadge = requireNonNull(findViewById(R.id.screenshot_badge));
-
-        mActionsContainerBackground = requireNonNull(findViewById(
-                R.id.actions_container_background));
-        mActionsContainer = requireNonNull(findViewById(R.id.actions_container));
-        mActionsView = requireNonNull(findViewById(R.id.screenshot_actions));
-        mDismissButton = requireNonNull(findViewById(R.id.screenshot_dismiss_button));
-        mScrollablePreview = requireNonNull(findViewById(R.id.screenshot_scrollable_preview));
-        mScreenshotFlash = requireNonNull(findViewById(R.id.screenshot_flash));
-        mShareChip = requireNonNull(mActionsContainer.findViewById(R.id.screenshot_share_chip));
-        mEditChip = requireNonNull(mActionsContainer.findViewById(R.id.screenshot_edit_chip));
-        mScrollChip = requireNonNull(mActionsContainer.findViewById(R.id.screenshot_scroll_chip));
-
-        setFocusable(true);
-        mActionsContainer.setScrollX(0);
-
-        mNavMode = getResources().getInteger(
-                com.android.internal.R.integer.config_navBarInteractionMode);
-        mOrientationPortrait =
-                getResources().getConfiguration().orientation == ORIENTATION_PORTRAIT;
-        mDirectionLTR =
-                getResources().getConfiguration().getLayoutDirection() == View.LAYOUT_DIRECTION_LTR;
-
-        // Get focus so that the key events go to the layout.
-        setFocusableInTouchMode(true);
-        requestFocus();
-
-        mScreenshotStatic.setCallbacks(new DraggableConstraintLayout.SwipeDismissCallbacks() {
-            @Override
-            public void onInteraction() {
-                mCallbacks.onUserInteraction();
-            }
-
-            @Override
-            public void onSwipeDismissInitiated(Animator animator) {
-                if (DEBUG_DISMISS) {
-                    Log.d(ScreenshotView.TAG, "dismiss triggered via swipe gesture");
-                }
-                mUiEventLogger.log(ScreenshotEvent.SCREENSHOT_SWIPE_DISMISSED, 0,
-                        mPackageName);
-            }
-
-            @Override
-            public void onDismissComplete() {
-                if (mInteractionJankMonitor.isInstrumenting(CUJ_TAKE_SCREENSHOT)) {
-                    mInteractionJankMonitor.end(CUJ_TAKE_SCREENSHOT);
-                }
-                mCallbacks.onDismiss();
-            }
-        });
-    }
-
-    View getScreenshotPreview() {
-        return mScreenshotPreview;
-    }
-
-    void setUiEventLogger(UiEventLogger uiEventLogger) {
-        mUiEventLogger = uiEventLogger;
-    }
-
-    void setCallbacks(ScreenshotViewCallback callbacks) {
-        mCallbacks = callbacks;
-    }
-
-    void setFlags(FeatureFlags flags) {
-        mFlags = flags;
-    }
-
-    void setScreenshot(Bitmap bitmap, Insets screenInsets) {
-        mScreenshotPreview.setImageDrawable(createScreenDrawable(mResources, bitmap, screenInsets));
-    }
-
-    void setScreenshot(ScreenshotData screenshot) {
-        mScreenshotData = screenshot;
-        setScreenshot(screenshot.getBitmap(), screenshot.getInsets());
-        mScreenshotPreview.setImageDrawable(createScreenDrawable(mResources, screenshot.getBitmap(),
-                screenshot.getInsets()));
-    }
-
-    void setPackageName(String packageName) {
-        mPackageName = packageName;
-    }
-
-    void setDefaultDisplay(int displayId) {
-        mDefaultDisplay = displayId;
-    }
-
-    void updateInsets(WindowInsets insets) {
-        int orientation = mContext.getResources().getConfiguration().orientation;
-        mOrientationPortrait = (orientation == ORIENTATION_PORTRAIT);
-        FrameLayout.LayoutParams p =
-                (FrameLayout.LayoutParams) mScreenshotStatic.getLayoutParams();
-        DisplayCutout cutout = insets.getDisplayCutout();
-        Insets navBarInsets = insets.getInsets(WindowInsets.Type.navigationBars());
-        if (cutout == null) {
-            p.setMargins(0, 0, 0, navBarInsets.bottom);
-        } else {
-            Insets waterfall = cutout.getWaterfallInsets();
-            if (mOrientationPortrait) {
-                p.setMargins(
-                        waterfall.left,
-                        Math.max(cutout.getSafeInsetTop(), waterfall.top),
-                        waterfall.right,
-                        Math.max(cutout.getSafeInsetBottom(),
-                                Math.max(navBarInsets.bottom, waterfall.bottom)));
-            } else {
-                p.setMargins(
-                        Math.max(cutout.getSafeInsetLeft(), waterfall.left),
-                        waterfall.top,
-                        Math.max(cutout.getSafeInsetRight(), waterfall.right),
-                        Math.max(navBarInsets.bottom, waterfall.bottom));
-            }
-        }
-        mScreenshotStatic.setLayoutParams(p);
-        mScreenshotStatic.requestLayout();
-    }
-
-    void updateOrientation(WindowInsets insets) {
-        int orientation = mContext.getResources().getConfiguration().orientation;
-        mOrientationPortrait = (orientation == ORIENTATION_PORTRAIT);
-        updateInsets(insets);
-        ViewGroup.LayoutParams params = mScreenshotPreview.getLayoutParams();
-        if (mOrientationPortrait) {
-            params.width = (int) mFixedSize;
-            params.height = LayoutParams.WRAP_CONTENT;
-            mScreenshotPreview.setScaleType(ImageView.ScaleType.FIT_START);
-        } else {
-            params.width = LayoutParams.WRAP_CONTENT;
-            params.height = (int) mFixedSize;
-            mScreenshotPreview.setScaleType(ImageView.ScaleType.FIT_END);
-        }
-
-        mScreenshotPreview.setLayoutParams(params);
-    }
-
-    AnimatorSet createScreenshotDropInAnimation(Rect bounds, boolean showFlash) {
-        if (DEBUG_ANIM) {
-            Log.d(TAG, "createAnim: bounds=" + bounds + " showFlash=" + showFlash);
-        }
-
-        Rect targetPosition = new Rect();
-        mScreenshotPreview.getHitRect(targetPosition);
-
-        // ratio of preview width, end vs. start size
-        float cornerScale =
-                mFixedSize / (mOrientationPortrait ? bounds.width() : bounds.height());
-        final float currentScale = 1 / cornerScale;
-
-        AnimatorSet dropInAnimation = new AnimatorSet();
-        ValueAnimator flashInAnimator = ValueAnimator.ofFloat(0, 1);
-        flashInAnimator.setDuration(SCREENSHOT_FLASH_IN_DURATION_MS);
-        flashInAnimator.setInterpolator(mFastOutSlowIn);
-        flashInAnimator.addUpdateListener(animation ->
-                mScreenshotFlash.setAlpha((float) animation.getAnimatedValue()));
-
-        ValueAnimator flashOutAnimator = ValueAnimator.ofFloat(1, 0);
-        flashOutAnimator.setDuration(SCREENSHOT_FLASH_OUT_DURATION_MS);
-        flashOutAnimator.setInterpolator(mFastOutSlowIn);
-        flashOutAnimator.addUpdateListener(animation ->
-                mScreenshotFlash.setAlpha((float) animation.getAnimatedValue()));
-
-        // animate from the current location, to the static preview location
-        final PointF startPos = new PointF(bounds.centerX(), bounds.centerY());
-        final PointF finalPos = new PointF(targetPosition.exactCenterX(),
-                targetPosition.exactCenterY());
-
-        // Shift to screen coordinates so that the animation runs on top of the entire screen,
-        // including e.g. bars covering the display cutout.
-        int[] locInScreen = mScreenshotPreview.getLocationOnScreen();
-        startPos.offset(targetPosition.left - locInScreen[0], targetPosition.top - locInScreen[1]);
-
-        if (DEBUG_ANIM) {
-            Log.d(TAG, "toCorner: startPos=" + startPos);
-            Log.d(TAG, "toCorner: finalPos=" + finalPos);
-        }
-
-        ValueAnimator toCorner = ValueAnimator.ofFloat(0, 1);
-        toCorner.setDuration(SCREENSHOT_TO_CORNER_Y_DURATION_MS);
-
-        toCorner.addListener(new AnimatorListenerAdapter() {
-            @Override
-            public void onAnimationStart(Animator animation) {
-                mScreenshotPreview.setScaleX(currentScale);
-                mScreenshotPreview.setScaleY(currentScale);
-                mScreenshotPreview.setVisibility(View.VISIBLE);
-                if (mAccessibilityManager.isEnabled()) {
-                    mDismissButton.setAlpha(0);
-                    mDismissButton.setVisibility(View.VISIBLE);
-                }
-            }
-        });
-
-        float xPositionPct =
-                SCREENSHOT_TO_CORNER_X_DURATION_MS / (float) SCREENSHOT_TO_CORNER_Y_DURATION_MS;
-        float dismissPct =
-                SCREENSHOT_TO_CORNER_DISMISS_DELAY_MS / (float) SCREENSHOT_TO_CORNER_Y_DURATION_MS;
-        float scalePct =
-                SCREENSHOT_TO_CORNER_SCALE_DURATION_MS / (float) SCREENSHOT_TO_CORNER_Y_DURATION_MS;
-        toCorner.addUpdateListener(animation -> {
-            float t = animation.getAnimatedFraction();
-            if (t < scalePct) {
-                float scale = MathUtils.lerp(
-                        currentScale, 1, mFastOutSlowIn.getInterpolation(t / scalePct));
-                mScreenshotPreview.setScaleX(scale);
-                mScreenshotPreview.setScaleY(scale);
-            } else {
-                mScreenshotPreview.setScaleX(1);
-                mScreenshotPreview.setScaleY(1);
-            }
-
-            if (t < xPositionPct) {
-                float xCenter = MathUtils.lerp(startPos.x, finalPos.x,
-                        mFastOutSlowIn.getInterpolation(t / xPositionPct));
-                mScreenshotPreview.setX(xCenter - mScreenshotPreview.getWidth() / 2f);
-            } else {
-                mScreenshotPreview.setX(finalPos.x - mScreenshotPreview.getWidth() / 2f);
-            }
-            float yCenter = MathUtils.lerp(
-                    startPos.y, finalPos.y, mFastOutSlowIn.getInterpolation(t));
-            mScreenshotPreview.setY(yCenter - mScreenshotPreview.getHeight() / 2f);
-
-            if (t >= dismissPct) {
-                mDismissButton.setAlpha((t - dismissPct) / (1 - dismissPct));
-                float currentX = mScreenshotPreview.getX();
-                float currentY = mScreenshotPreview.getY();
-                mDismissButton.setY(currentY - mDismissButton.getHeight() / 2f);
-                if (mDirectionLTR) {
-                    mDismissButton.setX(currentX + mScreenshotPreview.getWidth()
-                            - mDismissButton.getWidth() / 2f);
-                } else {
-                    mDismissButton.setX(currentX - mDismissButton.getWidth() / 2f);
-                }
-            }
-        });
-
-        mScreenshotFlash.setAlpha(0f);
-        mScreenshotFlash.setVisibility(View.VISIBLE);
-
-        ValueAnimator borderFadeIn = ValueAnimator.ofFloat(0, 1);
-        borderFadeIn.setDuration(100);
-        borderFadeIn.addUpdateListener((animation) -> {
-            float borderAlpha = animation.getAnimatedFraction();
-            mScreenshotPreviewBorder.setAlpha(borderAlpha);
-            mScreenshotBadge.setAlpha(borderAlpha);
-        });
-
-        if (showFlash) {
-            dropInAnimation.play(flashOutAnimator).after(flashInAnimator);
-            dropInAnimation.play(flashOutAnimator).with(toCorner);
-        } else {
-            dropInAnimation.play(toCorner);
-        }
-        dropInAnimation.play(borderFadeIn).after(toCorner);
-
-        dropInAnimation.addListener(new AnimatorListenerAdapter() {
-            @Override
-            public void onAnimationCancel(Animator animation) {
-                mInteractionJankMonitor.cancel(CUJ_TAKE_SCREENSHOT);
-            }
-
-            @Override
-            public void onAnimationStart(Animator animation) {
-                InteractionJankMonitor.Configuration.Builder builder =
-                        InteractionJankMonitor.Configuration.Builder.withView(
-                                        CUJ_TAKE_SCREENSHOT, mScreenshotPreview)
-                                .setTag("DropIn");
-                mInteractionJankMonitor.begin(builder);
-            }
-
-            @Override
-            public void onAnimationEnd(Animator animation) {
-                if (DEBUG_ANIM) {
-                    Log.d(TAG, "drop-in animation ended");
-                }
-                mDismissButton.setOnClickListener(view -> {
-                    if (DEBUG_INPUT) {
-                        Log.d(TAG, "dismiss button clicked");
-                    }
-                    mUiEventLogger.log(
-                            ScreenshotEvent.SCREENSHOT_EXPLICIT_DISMISSAL, 0, mPackageName);
-                    animateDismissal();
-                });
-                mDismissButton.setAlpha(1);
-                float dismissOffset = mDismissButton.getWidth() / 2f;
-                float finalDismissX = mDirectionLTR
-                        ? finalPos.x - dismissOffset + bounds.width() * cornerScale / 2f
-                        : finalPos.x - dismissOffset - bounds.width() * cornerScale / 2f;
-                mDismissButton.setX(finalDismissX);
-                mDismissButton.setY(
-                        finalPos.y - dismissOffset - bounds.height() * cornerScale / 2f);
-                mScreenshotPreview.setScaleX(1);
-                mScreenshotPreview.setScaleY(1);
-                mScreenshotPreview.setX(finalPos.x - mScreenshotPreview.getWidth() / 2f);
-                mScreenshotPreview.setY(finalPos.y - mScreenshotPreview.getHeight() / 2f);
-                requestLayout();
-                mInteractionJankMonitor.end(CUJ_TAKE_SCREENSHOT);
-                createScreenshotActionsShadeAnimation().start();
-            }
-        });
-
-        return dropInAnimation;
-    }
-
-    ValueAnimator createScreenshotActionsShadeAnimation() {
-        // By default the activities won't be able to start immediately; override this to keep
-        // the same behavior as if started from a notification
-        try {
-            ActivityManager.getService().resumeAppSwitches();
-        } catch (RemoteException e) {
-        }
-
-        ArrayList<OverlayActionChip> chips = new ArrayList<>();
-
-        mShareChip.setContentDescription(mContext.getString(R.string.screenshot_share_description));
-        mShareChip.setIcon(Icon.createWithResource(mContext, R.drawable.ic_screenshot_share), true);
-        mShareChip.setOnClickListener(v -> {
-            mShareChip.setIsPending(true);
-            mEditChip.setIsPending(false);
-            if (mQuickShareChip != null) {
-                mQuickShareChip.setIsPending(false);
-            }
-            mPendingInteraction = PendingInteraction.SHARE;
-        });
-        chips.add(mShareChip);
-
-        mEditChip.setContentDescription(
-                mContext.getString(R.string.screenshot_edit_description));
-        mEditChip.setIcon(Icon.createWithResource(mContext, R.drawable.ic_screenshot_edit),
-                true);
-        mEditChip.setOnClickListener(v -> {
-            mEditChip.setIsPending(true);
-            mShareChip.setIsPending(false);
-            if (mQuickShareChip != null) {
-                mQuickShareChip.setIsPending(false);
-            }
-            mPendingInteraction = PendingInteraction.EDIT;
-        });
-        chips.add(mEditChip);
-
-        mScreenshotPreview.setOnClickListener(v -> {
-            mShareChip.setIsPending(false);
-            mEditChip.setIsPending(false);
-            if (mQuickShareChip != null) {
-                mQuickShareChip.setIsPending(false);
-            }
-            mPendingInteraction = PendingInteraction.PREVIEW;
-        });
-
-        mScrollChip.setText(mContext.getString(R.string.screenshot_scroll_label));
-        mScrollChip.setIcon(Icon.createWithResource(mContext,
-                R.drawable.ic_screenshot_scroll), true);
-        chips.add(mScrollChip);
-
-        // remove the margin from the last chip so that it's correctly aligned with the end
-        LinearLayout.LayoutParams params = (LinearLayout.LayoutParams)
-                mActionsView.getChildAt(0).getLayoutParams();
-        params.setMarginEnd(0);
-        mActionsView.getChildAt(0).setLayoutParams(params);
-
-        ValueAnimator animator = ValueAnimator.ofFloat(0, 1);
-        animator.setDuration(SCREENSHOT_ACTIONS_EXPANSION_DURATION_MS);
-        float alphaFraction = (float) SCREENSHOT_ACTIONS_ALPHA_DURATION_MS
-                / SCREENSHOT_ACTIONS_EXPANSION_DURATION_MS;
-        mActionsContainer.setAlpha(0f);
-        mActionsContainerBackground.setAlpha(0f);
-        mActionsContainer.setVisibility(View.VISIBLE);
-        mActionsContainerBackground.setVisibility(View.VISIBLE);
-
-        animator.addListener(new AnimatorListenerAdapter() {
-            @Override
-            public void onAnimationCancel(Animator animation) {
-                mInteractionJankMonitor.cancel(CUJ_TAKE_SCREENSHOT);
-            }
-
-            @Override
-            public void onAnimationEnd(Animator animation) {
-                mInteractionJankMonitor.end(CUJ_TAKE_SCREENSHOT);
-            }
-
-            @Override
-            public void onAnimationStart(Animator animation) {
-                InteractionJankMonitor.Configuration.Builder builder =
-                        InteractionJankMonitor.Configuration.Builder.withView(
-                                        CUJ_TAKE_SCREENSHOT, mScreenshotStatic)
-                                .setTag("Actions")
-                                .setTimeout(SCREENSHOT_CORNER_DEFAULT_TIMEOUT_MILLIS);
-                mInteractionJankMonitor.begin(builder);
-            }
-        });
-
-        animator.addUpdateListener(animation -> {
-            float t = animation.getAnimatedFraction();
-            float containerAlpha = t < alphaFraction ? t / alphaFraction : 1;
-            mActionsContainer.setAlpha(containerAlpha);
-            mActionsContainerBackground.setAlpha(containerAlpha);
-            float containerScale = SCREENSHOT_ACTIONS_START_SCALE_X
-                    + (t * (1 - SCREENSHOT_ACTIONS_START_SCALE_X));
-            mActionsContainer.setScaleX(containerScale);
-            mActionsContainerBackground.setScaleX(containerScale);
-            for (OverlayActionChip chip : chips) {
-                chip.setAlpha(t);
-                chip.setScaleX(1 / containerScale); // invert to keep size of children constant
-            }
-            mActionsContainer.setScrollX(mDirectionLTR ? 0 : mActionsContainer.getWidth());
-            mActionsContainer.setPivotX(mDirectionLTR ? 0 : mActionsContainer.getWidth());
-            mActionsContainerBackground.setPivotX(
-                    mDirectionLTR ? 0 : mActionsContainerBackground.getWidth());
-        });
-        return animator;
-    }
-
-    void badgeScreenshot(@Nullable Drawable badge) {
-        mScreenshotBadge.setImageDrawable(badge);
-        mScreenshotBadge.setVisibility(badge != null ? View.VISIBLE : View.GONE);
-    }
-
-    void setChipIntents(ScreenshotController.SavedImageData imageData) {
-        mShareChip.setOnClickListener(v -> {
-            mUiEventLogger.log(ScreenshotEvent.SCREENSHOT_SHARE_TAPPED, 0, mPackageName);
-            prepareSharedTransition();
-
-            Intent shareIntent = ActionIntentCreator.INSTANCE.createShareWithSubject(
-                    imageData.uri, imageData.subject);
-            mCallbacks.onAction(shareIntent, imageData.owner, false);
-
-        });
-        mEditChip.setOnClickListener(v -> {
-            mUiEventLogger.log(ScreenshotEvent.SCREENSHOT_EDIT_TAPPED, 0, mPackageName);
-            prepareSharedTransition();
-            mCallbacks.onAction(
-                    ActionIntentCreator.INSTANCE.createEdit(imageData.uri, mContext),
-                    imageData.owner, true);
-        });
-        mScreenshotPreview.setOnClickListener(v -> {
-            mUiEventLogger.log(ScreenshotEvent.SCREENSHOT_PREVIEW_TAPPED, 0, mPackageName);
-            prepareSharedTransition();
-            mCallbacks.onAction(
-                    ActionIntentCreator.INSTANCE.createEdit(imageData.uri, mContext),
-                    imageData.owner, true);
-        });
-        if (mQuickShareChip != null) {
-            if (imageData.quickShareAction != null) {
-                mQuickShareChip.setPendingIntent(imageData.quickShareAction.actionIntent,
-                        () -> {
-                            mUiEventLogger.log(
-                                    ScreenshotEvent.SCREENSHOT_SMART_ACTION_TAPPED, 0,
-                                    mPackageName);
-                            animateDismissal();
-                        });
-            } else {
-                // hide chip and unset pending interaction if necessary, since we don't actually
-                // have a useable quick share intent
-                Log.wtf(TAG, "Showed quick share chip, but quick share intent was null");
-                if (mPendingInteraction == PendingInteraction.QUICK_SHARE) {
-                    mPendingInteraction = null;
-                }
-                mQuickShareChip.setVisibility(GONE);
-            }
-        }
-
-        if (mPendingInteraction != null) {
-            switch (mPendingInteraction) {
-                case PREVIEW:
-                    mScreenshotPreview.callOnClick();
-                    break;
-                case SHARE:
-                    mShareChip.callOnClick();
-                    break;
-                case EDIT:
-                    mEditChip.callOnClick();
-                    break;
-                case QUICK_SHARE:
-                    mQuickShareChip.callOnClick();
-                    break;
-            }
-        } else {
-            LayoutInflater inflater = LayoutInflater.from(mContext);
-
-            for (Notification.Action smartAction : imageData.smartActions) {
-                OverlayActionChip actionChip = (OverlayActionChip) inflater.inflate(
-                        R.layout.overlay_action_chip, mActionsView, false);
-                actionChip.setText(smartAction.title);
-                actionChip.setIcon(smartAction.getIcon(), false);
-                actionChip.setPendingIntent(smartAction.actionIntent,
-                        () -> {
-                            mUiEventLogger.log(ScreenshotEvent.SCREENSHOT_SMART_ACTION_TAPPED,
-                                    0, mPackageName);
-                            animateDismissal();
-                        });
-                actionChip.setAlpha(1);
-                mActionsView.addView(actionChip, mActionsView.getChildCount() - 1);
-                mSmartChips.add(actionChip);
-            }
-        }
-    }
-
-    void addQuickShareChip(Notification.Action quickShareAction) {
-        if (mQuickShareChip != null) {
-            mSmartChips.remove(mQuickShareChip);
-            mActionsView.removeView(mQuickShareChip);
-        }
-        if (mPendingInteraction == PendingInteraction.QUICK_SHARE) {
-            mPendingInteraction = null;
-        }
-        if (mPendingInteraction == null) {
-            LayoutInflater inflater = LayoutInflater.from(mContext);
-            mQuickShareChip = (OverlayActionChip) inflater.inflate(
-                    R.layout.overlay_action_chip, mActionsView, false);
-            mQuickShareChip.setText(quickShareAction.title);
-            mQuickShareChip.setIcon(quickShareAction.getIcon(), false);
-            mQuickShareChip.setOnClickListener(v -> {
-                mShareChip.setIsPending(false);
-                mEditChip.setIsPending(false);
-                mQuickShareChip.setIsPending(true);
-                mPendingInteraction = PendingInteraction.QUICK_SHARE;
-            });
-            mQuickShareChip.setAlpha(1);
-            mActionsView.addView(mQuickShareChip);
-            mSmartChips.add(mQuickShareChip);
-        }
-    }
-
-    private Rect scrollableAreaOnScreen(ScrollCaptureResponse response) {
-        Rect r = new Rect(response.getBoundsInWindow());
-        Rect windowInScreen = response.getWindowBounds();
-        r.offset(windowInScreen.left, windowInScreen.top);
-        r.intersect(new Rect(0, 0, mDisplayMetrics.widthPixels, mDisplayMetrics.heightPixels));
-        return r;
-    }
-
-    void startLongScreenshotTransition(Rect destination, Runnable onTransitionEnd,
-            ScrollCaptureController.LongScreenshot longScreenshot) {
-        mPendingSharedTransition = true;
-        AnimatorSet animSet = new AnimatorSet();
-
-        ValueAnimator scrimAnim = ValueAnimator.ofFloat(0, 1);
-        scrimAnim.addUpdateListener(animation ->
-                mScrollingScrim.setAlpha(1 - animation.getAnimatedFraction()));
-
-        if (mShowScrollablePreview) {
-            mScrollablePreview.setImageBitmap(longScreenshot.toBitmap());
-            float startX = mScrollablePreview.getX();
-            float startY = mScrollablePreview.getY();
-            int[] locInScreen = mScrollablePreview.getLocationOnScreen();
-            destination.offset((int) startX - locInScreen[0], (int) startY - locInScreen[1]);
-            mScrollablePreview.setPivotX(0);
-            mScrollablePreview.setPivotY(0);
-            mScrollablePreview.setAlpha(1f);
-            float currentScale = mScrollablePreview.getWidth() / (float) longScreenshot.getWidth();
-            Matrix matrix = new Matrix();
-            matrix.setScale(currentScale, currentScale);
-            matrix.postTranslate(
-                    longScreenshot.getLeft() * currentScale,
-                    longScreenshot.getTop() * currentScale);
-            mScrollablePreview.setImageMatrix(matrix);
-            float destinationScale = destination.width() / (float) mScrollablePreview.getWidth();
-
-            ValueAnimator previewAnim = ValueAnimator.ofFloat(0, 1);
-            previewAnim.addUpdateListener(animation -> {
-                float t = animation.getAnimatedFraction();
-                float currScale = MathUtils.lerp(1, destinationScale, t);
-                mScrollablePreview.setScaleX(currScale);
-                mScrollablePreview.setScaleY(currScale);
-                mScrollablePreview.setX(MathUtils.lerp(startX, destination.left, t));
-                mScrollablePreview.setY(MathUtils.lerp(startY, destination.top, t));
-            });
-            ValueAnimator previewFadeAnim = ValueAnimator.ofFloat(1, 0);
-            previewFadeAnim.addUpdateListener(animation ->
-                    mScrollablePreview.setAlpha(1 - animation.getAnimatedFraction()));
-            animSet.play(previewAnim).with(scrimAnim).before(previewFadeAnim);
-            previewAnim.addListener(new AnimatorListenerAdapter() {
-                @Override
-                public void onAnimationEnd(Animator animation) {
-                    super.onAnimationEnd(animation);
-                    onTransitionEnd.run();
-                }
-            });
-        } else {
-            // if we switched orientations between the original screenshot and the long screenshot
-            // capture, just fade out the scrim instead of running the preview animation
-            animSet.play(scrimAnim);
-            animSet.addListener(new AnimatorListenerAdapter() {
-                @Override
-                public void onAnimationEnd(Animator animation) {
-                    super.onAnimationEnd(animation);
-                    onTransitionEnd.run();
-                }
-            });
-        }
-        animSet.addListener(new AnimatorListenerAdapter() {
-            @Override
-            public void onAnimationEnd(Animator animation) {
-                super.onAnimationEnd(animation);
-                mCallbacks.onDismiss();
-            }
-        });
-        animSet.start();
-    }
-
-    void prepareScrollingTransition(ScrollCaptureResponse response, Bitmap screenBitmap,
-            Bitmap newBitmap, boolean screenshotTakenInPortrait) {
-        mShowScrollablePreview = (screenshotTakenInPortrait == mOrientationPortrait);
-
-        mScrollingScrim.setImageBitmap(newBitmap);
-        mScrollingScrim.setVisibility(View.VISIBLE);
-
-        if (mShowScrollablePreview) {
-            Rect scrollableArea = scrollableAreaOnScreen(response);
-
-            float scale = mFixedSize
-                    / (mOrientationPortrait ? screenBitmap.getWidth() : screenBitmap.getHeight());
-            ConstraintLayout.LayoutParams params =
-                    (ConstraintLayout.LayoutParams) mScrollablePreview.getLayoutParams();
-
-            params.width = (int) (scale * scrollableArea.width());
-            params.height = (int) (scale * scrollableArea.height());
-            Matrix matrix = new Matrix();
-            matrix.setScale(scale, scale);
-            matrix.postTranslate(-scrollableArea.left * scale, -scrollableArea.top * scale);
-
-            mScrollablePreview.setTranslationX(scale
-                    * (mDirectionLTR ? scrollableArea.left : scrollableArea.right - getWidth()));
-            mScrollablePreview.setTranslationY(scale * scrollableArea.top);
-            mScrollablePreview.setImageMatrix(matrix);
-            mScrollablePreview.setImageBitmap(screenBitmap);
-            mScrollablePreview.setVisibility(View.VISIBLE);
-        }
-        mDismissButton.setVisibility(View.GONE);
-        mActionsContainer.setVisibility(View.GONE);
-        // set these invisible, but not gone, so that the views are laid out correctly
-        mActionsContainerBackground.setVisibility(View.INVISIBLE);
-        mScreenshotPreviewBorder.setVisibility(View.INVISIBLE);
-        mScreenshotPreview.setVisibility(View.INVISIBLE);
-        mScrollingScrim.setImageTintBlendMode(BlendMode.SRC_ATOP);
-        ValueAnimator anim = ValueAnimator.ofFloat(0, .3f);
-        anim.addUpdateListener(animation -> mScrollingScrim.setImageTintList(
-                ColorStateList.valueOf(Color.argb((float) animation.getAnimatedValue(), 0, 0, 0))));
-        anim.setDuration(200);
-        anim.start();
-    }
-
-    void restoreNonScrollingUi() {
-        mScrollChip.setVisibility(View.GONE);
-        mScrollablePreview.setVisibility(View.GONE);
-        mScrollingScrim.setVisibility(View.GONE);
-
-        if (mAccessibilityManager.isEnabled()) {
-            mDismissButton.setVisibility(View.VISIBLE);
-        }
-        mActionsContainer.setVisibility(View.VISIBLE);
-        mActionsContainerBackground.setVisibility(View.VISIBLE);
-        mScreenshotPreviewBorder.setVisibility(View.VISIBLE);
-        mScreenshotPreview.setVisibility(View.VISIBLE);
-        // reset the timeout
-        mCallbacks.onUserInteraction();
-    }
-
-    boolean isDismissing() {
-        return mScreenshotStatic.isDismissing();
-    }
-
-    boolean isPendingSharedTransition() {
-        return mPendingSharedTransition;
-    }
-
-    void animateDismissal() {
-        mScreenshotStatic.dismiss();
-    }
-
-    void reset() {
-        if (DEBUG_UI) {
-            Log.d(TAG, "reset screenshot view");
-        }
-        mScreenshotStatic.cancelDismissal();
-        if (DEBUG_WINDOW) {
-            Log.d(TAG, "removing OnComputeInternalInsetsListener");
-        }
-        // Make sure we clean up the view tree observer
-        getViewTreeObserver().removeOnComputeInternalInsetsListener(this);
-        // Clear any references to the bitmap
-        mScreenshotPreview.setImageDrawable(null);
-        mScreenshotPreview.setVisibility(View.INVISIBLE);
-        mScreenshotPreview.setAlpha(1f);
-        mScreenshotPreviewBorder.setAlpha(0);
-        mScreenshotBadge.setAlpha(0f);
-        mScreenshotBadge.setVisibility(View.GONE);
-        mScreenshotBadge.setImageDrawable(null);
-        mPendingSharedTransition = false;
-        mActionsContainerBackground.setVisibility(View.INVISIBLE);
-        mActionsContainer.setVisibility(View.GONE);
-        mDismissButton.setVisibility(View.GONE);
-        mScrollingScrim.setVisibility(View.GONE);
-        mScrollablePreview.setVisibility(View.GONE);
-        mScreenshotStatic.setTranslationX(0);
-        mScreenshotPreview.setContentDescription(
-                mContext.getResources().getString(R.string.screenshot_preview_description));
-        mScreenshotPreview.setOnClickListener(null);
-        mShareChip.setOnClickListener(null);
-        mScrollingScrim.setVisibility(View.GONE);
-        mEditChip.setOnClickListener(null);
-        mShareChip.setIsPending(false);
-        mEditChip.setIsPending(false);
-        mPendingInteraction = null;
-        for (OverlayActionChip chip : mSmartChips) {
-            mActionsView.removeView(chip);
-        }
-        mSmartChips.clear();
-        mQuickShareChip = null;
-        setAlpha(1);
-        mScreenshotStatic.setAlpha(1);
-        mScreenshotData = null;
-    }
-
-    private void prepareSharedTransition() {
-        mPendingSharedTransition = true;
-        // fade out non-preview UI
-        createScreenshotFadeDismissAnimation().start();
-    }
-
-    ValueAnimator createScreenshotFadeDismissAnimation() {
-        ValueAnimator alphaAnim = ValueAnimator.ofFloat(0, 1);
-        alphaAnim.addUpdateListener(animation -> {
-            float alpha = 1 - animation.getAnimatedFraction();
-            mDismissButton.setAlpha(alpha);
-            mActionsContainerBackground.setAlpha(alpha);
-            mActionsContainer.setAlpha(alpha);
-            mScreenshotPreviewBorder.setAlpha(alpha);
-            mScreenshotBadge.setAlpha(alpha);
-        });
-        alphaAnim.setDuration(600);
-        return alphaAnim;
-    }
-
-    /**
-     * Create a drawable using the size of the bitmap and insets as the fractional inset parameters.
-     */
-    private static Drawable createScreenDrawable(Resources res, Bitmap bitmap, Insets insets) {
-        int insettedWidth = bitmap.getWidth() - insets.left - insets.right;
-        int insettedHeight = bitmap.getHeight() - insets.top - insets.bottom;
-
-        BitmapDrawable bitmapDrawable = new BitmapDrawable(res, bitmap);
-        if (insettedHeight == 0 || insettedWidth == 0 || bitmap.getWidth() == 0
-                || bitmap.getHeight() == 0) {
-            Log.e(TAG, "Can't create inset drawable, using 0 insets bitmap and insets create "
-                    + "degenerate region: " + bitmap.getWidth() + "x" + bitmap.getHeight() + " "
-                    + bitmapDrawable);
-            return bitmapDrawable;
-        }
-
-        InsetDrawable insetDrawable = new InsetDrawable(bitmapDrawable,
-                -1f * insets.left / insettedWidth,
-                -1f * insets.top / insettedHeight,
-                -1f * insets.right / insettedWidth,
-                -1f * insets.bottom / insettedHeight);
-
-        if (insets.left < 0 || insets.top < 0 || insets.right < 0 || insets.bottom < 0) {
-            // Are any of the insets negative, meaning the bitmap is smaller than the bounds so need
-            // to fill in the background of the drawable.
-            return new LayerDrawable(new Drawable[]{
-                    new ColorDrawable(Color.BLACK), insetDrawable});
-        } else {
-            return insetDrawable;
-        }
-    }
-}
diff --git a/packages/SystemUI/src/com/android/systemui/screenshot/ScreenshotViewProxy.kt b/packages/SystemUI/src/com/android/systemui/screenshot/ScreenshotViewProxy.kt
deleted file mode 100644
index df93a5e..0000000
--- a/packages/SystemUI/src/com/android/systemui/screenshot/ScreenshotViewProxy.kt
+++ /dev/null
@@ -1,76 +0,0 @@
-/*
- * Copyright (C) 2024 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- *      http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-package com.android.systemui.screenshot
-
-import android.animation.Animator
-import android.app.Notification
-import android.content.Context
-import android.graphics.Bitmap
-import android.graphics.Rect
-import android.view.ScrollCaptureResponse
-import android.view.View
-import android.view.ViewGroup
-import android.view.WindowInsets
-import com.android.systemui.screenshot.scroll.ScrollCaptureController
-
-/** Abstraction of the surface between ScreenshotController and ScreenshotView */
-interface ScreenshotViewProxy {
-    val view: ViewGroup
-    val screenshotPreview: View
-
-    var packageName: String
-    var callbacks: ScreenshotView.ScreenshotViewCallback?
-    var screenshot: ScreenshotData?
-
-    val isAttachedToWindow: Boolean
-    val isDismissing: Boolean
-    val isPendingSharedTransition: Boolean
-
-    fun reset()
-    fun updateInsets(insets: WindowInsets)
-    fun updateOrientation(insets: WindowInsets)
-    fun createScreenshotDropInAnimation(screenRect: Rect, showFlash: Boolean): Animator
-    fun addQuickShareChip(quickShareAction: Notification.Action)
-    fun setChipIntents(imageData: ScreenshotController.SavedImageData)
-    fun requestDismissal(event: ScreenshotEvent?)
-
-    fun showScrollChip(packageName: String, onClick: Runnable)
-    fun hideScrollChip()
-    fun prepareScrollingTransition(
-        response: ScrollCaptureResponse,
-        screenBitmap: Bitmap,
-        newScreenshot: Bitmap,
-        screenshotTakenInPortrait: Boolean,
-        onTransitionPrepared: Runnable,
-    )
-    fun startLongScreenshotTransition(
-        transitionDestination: Rect,
-        onTransitionEnd: Runnable,
-        longScreenshot: ScrollCaptureController.LongScreenshot
-    )
-    fun restoreNonScrollingUi()
-    fun fadeForSharedTransition()
-
-    fun stopInputListening()
-    fun requestFocus()
-    fun announceForAccessibility(string: String)
-    fun prepareEntranceAnimation(runnable: Runnable)
-
-    interface Factory {
-        fun getProxy(context: Context, displayId: Int): ScreenshotViewProxy
-    }
-}
diff --git a/packages/SystemUI/src/com/android/systemui/screenshot/dagger/ScreenshotModule.java b/packages/SystemUI/src/com/android/systemui/screenshot/dagger/ScreenshotModule.java
index 56ba1af4..682f848 100644
--- a/packages/SystemUI/src/com/android/systemui/screenshot/dagger/ScreenshotModule.java
+++ b/packages/SystemUI/src/com/android/systemui/screenshot/dagger/ScreenshotModule.java
@@ -24,12 +24,10 @@
 import com.android.systemui.screenshot.ImageCaptureImpl;
 import com.android.systemui.screenshot.ScreenshotPolicy;
 import com.android.systemui.screenshot.ScreenshotPolicyImpl;
-import com.android.systemui.screenshot.ScreenshotShelfViewProxy;
 import com.android.systemui.screenshot.ScreenshotSoundController;
 import com.android.systemui.screenshot.ScreenshotSoundControllerImpl;
 import com.android.systemui.screenshot.ScreenshotSoundProvider;
 import com.android.systemui.screenshot.ScreenshotSoundProviderImpl;
-import com.android.systemui.screenshot.ScreenshotViewProxy;
 import com.android.systemui.screenshot.TakeScreenshotExecutor;
 import com.android.systemui.screenshot.TakeScreenshotExecutorImpl;
 import com.android.systemui.screenshot.TakeScreenshotService;
@@ -92,8 +90,4 @@
             AccessibilityManager accessibilityManager) {
         return new ScreenshotViewModel(accessibilityManager);
     }
-
-    @Binds
-    abstract ScreenshotViewProxy.Factory bindScreenshotViewProxyFactory(
-            ScreenshotShelfViewProxy.Factory shelfScreenshotViewProxyFactory);
 }
diff --git a/packages/SystemUI/tests/src/com/android/systemui/communal/data/backup/CommunalBackupHelperTest.kt b/packages/SystemUI/tests/src/com/android/systemui/communal/data/backup/CommunalBackupHelperTest.kt
index 7094848..e60848b 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/communal/data/backup/CommunalBackupHelperTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/communal/data/backup/CommunalBackupHelperTest.kt
@@ -120,11 +120,26 @@
 
     private fun setUpDatabase(): List<FakeWidgetMetadata> {
         return listOf(
-                FakeWidgetMetadata(11, "com.android.fakePackage1/fakeWidget1", 3),
-                FakeWidgetMetadata(12, "com.android.fakePackage2/fakeWidget2", 2),
-                FakeWidgetMetadata(13, "com.android.fakePackage3/fakeWidget3", 1),
+                FakeWidgetMetadata(
+                    widgetId = 11,
+                    componentName = "com.android.fakePackage1/fakeWidget1",
+                    rank = 3,
+                    userSerialNumber = 0,
+                ),
+                FakeWidgetMetadata(
+                    widgetId = 12,
+                    componentName = "com.android.fakePackage2/fakeWidget2",
+                    rank = 2,
+                    userSerialNumber = 0,
+                ),
+                FakeWidgetMetadata(
+                    widgetId = 13,
+                    componentName = "com.android.fakePackage3/fakeWidget3",
+                    rank = 1,
+                    userSerialNumber = 10,
+                ),
             )
-            .onEach { dao.addWidget(it.widgetId, it.componentName, it.rank) }
+            .onEach { dao.addWidget(it.widgetId, it.componentName, it.rank, it.userSerialNumber) }
     }
 
     private fun getBackupDataInputStream(): BackupDataInputStream {
diff --git a/packages/SystemUI/tests/src/com/android/systemui/communal/data/backup/CommunalBackupUtilsTest.kt b/packages/SystemUI/tests/src/com/android/systemui/communal/data/backup/CommunalBackupUtilsTest.kt
index cde7a0e..983a435 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/communal/data/backup/CommunalBackupUtilsTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/communal/data/backup/CommunalBackupUtilsTest.kt
@@ -66,11 +66,28 @@
         // Set up database
         val expectedWidgets =
             listOf(
-                FakeWidgetMetadata(11, "com.android.fakePackage1/fakeWidget1", 3),
-                FakeWidgetMetadata(12, "com.android.fakePackage2/fakeWidget2", 2),
-                FakeWidgetMetadata(13, "com.android.fakePackage3/fakeWidget3", 1),
+                FakeWidgetMetadata(
+                    widgetId = 11,
+                    componentName = "com.android.fakePackage1/fakeWidget1",
+                    rank = 3,
+                    userSerialNumber = 0,
+                ),
+                FakeWidgetMetadata(
+                    widgetId = 12,
+                    componentName = "com.android.fakePackage2/fakeWidget2",
+                    rank = 2,
+                    userSerialNumber = 0,
+                ),
+                FakeWidgetMetadata(
+                    widgetId = 13,
+                    componentName = "com.android.fakePackage3/fakeWidget3",
+                    rank = 1,
+                    userSerialNumber = 10,
+                ),
             )
-        expectedWidgets.forEach { dao.addWidget(it.widgetId, it.componentName, it.rank) }
+        expectedWidgets.forEach {
+            dao.addWidget(it.widgetId, it.componentName, it.rank, it.userSerialNumber)
+        }
 
         // Get communal hub state
         val state = underTest.getCommunalHubState()
@@ -128,7 +145,12 @@
         assertThat(underTest.fileExists()).isFalse()
     }
 
-    data class FakeWidgetMetadata(val widgetId: Int, val componentName: String, val rank: Int)
+    data class FakeWidgetMetadata(
+        val widgetId: Int,
+        val componentName: String,
+        val rank: Int,
+        val userSerialNumber: Int,
+    )
 
     companion object {
         /**
@@ -140,7 +162,8 @@
                 { actual, expected ->
                     actual?.widgetId == expected?.widgetId &&
                         actual?.componentName == expected?.componentName &&
-                        actual?.rank == expected?.rank
+                        actual?.rank == expected?.rank &&
+                        actual?.userSerialNumber == expected?.userSerialNumber
                 },
                 "represents",
             )
diff --git a/packages/SystemUI/tests/src/com/android/systemui/communal/data/db/CommunalDatabaseMigrationsTest.kt b/packages/SystemUI/tests/src/com/android/systemui/communal/data/db/CommunalDatabaseMigrationsTest.kt
new file mode 100644
index 0000000..eb0ab78
--- /dev/null
+++ b/packages/SystemUI/tests/src/com/android/systemui/communal/data/db/CommunalDatabaseMigrationsTest.kt
@@ -0,0 +1,149 @@
+/*
+ * Copyright (C) 2024 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.systemui.communal.data.db
+
+import androidx.room.testing.MigrationTestHelper
+import androidx.sqlite.db.SupportSQLiteDatabase
+import androidx.test.ext.junit.runners.AndroidJUnit4
+import androidx.test.filters.SmallTest
+import androidx.test.platform.app.InstrumentationRegistry
+import com.android.systemui.SysuiTestCase
+import com.android.systemui.lifecycle.InstantTaskExecutorRule
+import com.google.common.truth.Truth.assertThat
+import org.junit.Rule
+import org.junit.Test
+import org.junit.runner.RunWith
+
+@SmallTest
+@RunWith(AndroidJUnit4::class)
+class CommunalDatabaseMigrationsTest : SysuiTestCase() {
+
+    @JvmField @Rule val instantTaskExecutor = InstantTaskExecutorRule()
+
+    @get:Rule
+    val migrationTestHelper =
+        MigrationTestHelper(
+            InstrumentationRegistry.getInstrumentation(),
+            CommunalDatabase::class.java.canonicalName,
+        )
+
+    @Test
+    fun migrate1To2() {
+        // Create a communal database in version 1
+        val databaseV1 = migrationTestHelper.createDatabase(DATABASE_NAME, version = 1)
+
+        // Populate some fake data
+        val fakeWidgetsV1 =
+            listOf(
+                FakeCommunalWidgetItemV1(1, "test_widget_1", 11),
+                FakeCommunalWidgetItemV1(2, "test_widget_2", 12),
+                FakeCommunalWidgetItemV1(3, "test_widget_3", 13),
+            )
+        databaseV1.insertWidgetsV1(fakeWidgetsV1)
+
+        // Verify fake widgets populated
+        databaseV1.verifyWidgetsV1(fakeWidgetsV1)
+
+        // Run migration and get database V2, the migration test helper verifies that the schema is
+        // updated correctly
+        val databaseV2 =
+            migrationTestHelper.runMigrationsAndValidate(
+                name = DATABASE_NAME,
+                version = 2,
+                validateDroppedTables = false,
+                CommunalDatabase.MIGRATION_1_2,
+            )
+
+        // Verify data is migrated correctly
+        databaseV2.verifyWidgetsV2(fakeWidgetsV1.map { it.getV2() })
+    }
+
+    private fun SupportSQLiteDatabase.insertWidgetsV1(widgets: List<FakeCommunalWidgetItemV1>) {
+        widgets.forEach { widget ->
+            execSQL(
+                "INSERT INTO communal_widget_table(widget_id, component_name, item_id) " +
+                    "VALUES(${widget.widgetId}, '${widget.componentName}', ${widget.itemId})"
+            )
+        }
+    }
+
+    private fun SupportSQLiteDatabase.verifyWidgetsV1(widgets: List<FakeCommunalWidgetItemV1>) {
+        val cursor = query("SELECT * FROM communal_widget_table")
+        assertThat(cursor.moveToFirst()).isTrue()
+
+        widgets.forEach { widget ->
+            assertThat(cursor.getInt(cursor.getColumnIndex("widget_id"))).isEqualTo(widget.widgetId)
+            assertThat(cursor.getString(cursor.getColumnIndex("component_name")))
+                .isEqualTo(widget.componentName)
+            assertThat(cursor.getInt(cursor.getColumnIndex("item_id"))).isEqualTo(widget.itemId)
+
+            cursor.moveToNext()
+        }
+
+        // Verify there is no more columns
+        assertThat(cursor.isAfterLast).isTrue()
+    }
+
+    private fun SupportSQLiteDatabase.verifyWidgetsV2(widgets: List<FakeCommunalWidgetItemV2>) {
+        val cursor = query("SELECT * FROM communal_widget_table")
+        assertThat(cursor.moveToFirst()).isTrue()
+
+        widgets.forEach { widget ->
+            assertThat(cursor.getInt(cursor.getColumnIndex("widget_id"))).isEqualTo(widget.widgetId)
+            assertThat(cursor.getString(cursor.getColumnIndex("component_name")))
+                .isEqualTo(widget.componentName)
+            assertThat(cursor.getInt(cursor.getColumnIndex("item_id"))).isEqualTo(widget.itemId)
+            assertThat(cursor.getInt(cursor.getColumnIndex("user_serial_number")))
+                .isEqualTo(widget.userSerialNumber)
+
+            cursor.moveToNext()
+        }
+
+        // Verify there is no more columns
+        assertThat(cursor.isAfterLast).isTrue()
+    }
+
+    /**
+     * Returns the expected data after migration from V1 to V2, which is simply that the new user
+     * serial number field is now set to [CommunalWidgetItem.USER_SERIAL_NUMBER_UNDEFINED].
+     */
+    private fun FakeCommunalWidgetItemV1.getV2(): FakeCommunalWidgetItemV2 {
+        return FakeCommunalWidgetItemV2(
+            widgetId,
+            componentName,
+            itemId,
+            CommunalWidgetItem.USER_SERIAL_NUMBER_UNDEFINED,
+        )
+    }
+
+    private data class FakeCommunalWidgetItemV1(
+        val widgetId: Int,
+        val componentName: String,
+        val itemId: Int,
+    )
+
+    private data class FakeCommunalWidgetItemV2(
+        val widgetId: Int,
+        val componentName: String,
+        val itemId: Int,
+        val userSerialNumber: Int,
+    )
+
+    companion object {
+        private const val DATABASE_NAME = "communal_db"
+    }
+}
diff --git a/packages/SystemUI/tests/src/com/android/systemui/communal/data/db/CommunalWidgetDaoTest.kt b/packages/SystemUI/tests/src/com/android/systemui/communal/data/db/CommunalWidgetDaoTest.kt
index f77c7a6..d670508 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/communal/data/db/CommunalWidgetDaoTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/communal/data/db/CommunalWidgetDaoTest.kt
@@ -67,11 +67,12 @@
     @Test
     fun addWidget_readValueInDb() =
         testScope.runTest {
-            val (widgetId, provider, priority) = widgetInfo1
+            val (widgetId, provider, priority, userSerialNumber) = widgetInfo1
             communalWidgetDao.addWidget(
                 widgetId = widgetId,
                 provider = provider,
                 priority = priority,
+                userSerialNumber = userSerialNumber,
             )
             val entry = communalWidgetDao.getWidgetByIdNow(id = 1)
             assertThat(entry).isEqualTo(communalWidgetItemEntry1)
@@ -80,11 +81,12 @@
     @Test
     fun deleteWidget_notInDb_returnsFalse() =
         testScope.runTest {
-            val (widgetId, provider, priority) = widgetInfo1
+            val (widgetId, provider, priority, userSerialNumber) = widgetInfo1
             communalWidgetDao.addWidget(
                 widgetId = widgetId,
                 provider = provider,
                 priority = priority,
+                userSerialNumber = userSerialNumber,
             )
             assertThat(communalWidgetDao.deleteWidgetById(widgetId = 123)).isFalse()
         }
@@ -95,11 +97,12 @@
             val widgetsToAdd = listOf(widgetInfo1, widgetInfo2)
             val widgets = collectLastValue(communalWidgetDao.getWidgets())
             widgetsToAdd.forEach {
-                val (widgetId, provider, priority) = it
+                val (widgetId, provider, priority, userSerialNumber) = it
                 communalWidgetDao.addWidget(
                     widgetId = widgetId,
                     provider = provider,
                     priority = priority,
+                    userSerialNumber = userSerialNumber
                 )
             }
             assertThat(widgets())
@@ -118,11 +121,12 @@
             val widgets = collectLastValue(communalWidgetDao.getWidgets())
 
             widgetsToAdd.forEach {
-                val (widgetId, provider, priority) = it
+                val (widgetId, provider, priority, userSerialNumber) = it
                 communalWidgetDao.addWidget(
                     widgetId = widgetId,
                     provider = provider,
                     priority = priority,
+                    userSerialNumber = userSerialNumber,
                 )
             }
             assertThat(widgets())
@@ -144,11 +148,12 @@
             val widgets = collectLastValue(communalWidgetDao.getWidgets())
 
             widgetsToAdd.forEach {
-                val (widgetId, provider, priority) = it
+                val (widgetId, provider, priority, userSerialNumber) = it
                 communalWidgetDao.addWidget(
                     widgetId = widgetId,
                     provider = provider,
                     priority = priority,
+                    userSerialNumber = userSerialNumber,
                 )
             }
             assertThat(widgets())
@@ -180,11 +185,12 @@
             val widgets = collectLastValue(communalWidgetDao.getWidgets())
 
             existingWidgets.forEach {
-                val (widgetId, provider, priority) = it
+                val (widgetId, provider, priority, userSerialNumber) = it
                 communalWidgetDao.addWidget(
                     widgetId = widgetId,
                     provider = provider,
                     priority = priority,
+                    userSerialNumber = userSerialNumber,
                 )
             }
             assertThat(widgets())
@@ -212,6 +218,7 @@
                 widgetId = widgetInfo3.widgetId,
                 provider = widgetInfo3.provider,
                 priority = 2,
+                userSerialNumber = widgetInfo3.userSerialNumber,
             )
             assertThat(widgets())
                 .containsExactly(
@@ -246,6 +253,7 @@
                         widgetId = fakeWidget.widgetId,
                         componentName = fakeWidget.componentName,
                         itemId = rank.uid,
+                        userSerialNumber = fakeWidget.userSerialNumber,
                     )
                 expected[rank] = widget
             }
@@ -258,13 +266,15 @@
             widgetId = metadata.widgetId,
             provider = metadata.provider,
             priority = priority ?: metadata.priority,
+            userSerialNumber = metadata.userSerialNumber,
         )
     }
 
     data class FakeWidgetMetadata(
         val widgetId: Int,
         val provider: ComponentName,
-        val priority: Int
+        val priority: Int,
+        val userSerialNumber: Int,
     )
 
     companion object {
@@ -272,19 +282,22 @@
             FakeWidgetMetadata(
                 widgetId = 1,
                 provider = ComponentName("pk_name", "cls_name_1"),
-                priority = 1
+                priority = 1,
+                userSerialNumber = 0,
             )
         val widgetInfo2 =
             FakeWidgetMetadata(
                 widgetId = 2,
                 provider = ComponentName("pk_name", "cls_name_2"),
-                priority = 2
+                priority = 2,
+                userSerialNumber = 0,
             )
         val widgetInfo3 =
             FakeWidgetMetadata(
                 widgetId = 3,
                 provider = ComponentName("pk_name", "cls_name_3"),
-                priority = 3
+                priority = 3,
+                userSerialNumber = 10,
             )
         val communalItemRankEntry1 = CommunalItemRank(uid = 1L, rank = widgetInfo1.priority)
         val communalItemRankEntry2 = CommunalItemRank(uid = 2L, rank = widgetInfo2.priority)
@@ -295,6 +308,7 @@
                 widgetId = widgetInfo1.widgetId,
                 componentName = widgetInfo1.provider.flattenToString(),
                 itemId = communalItemRankEntry1.uid,
+                userSerialNumber = widgetInfo1.userSerialNumber,
             )
         val communalWidgetItemEntry2 =
             CommunalWidgetItem(
@@ -302,6 +316,7 @@
                 widgetId = widgetInfo2.widgetId,
                 componentName = widgetInfo2.provider.flattenToString(),
                 itemId = communalItemRankEntry2.uid,
+                userSerialNumber = widgetInfo2.userSerialNumber,
             )
         val communalWidgetItemEntry3 =
             CommunalWidgetItem(
@@ -309,6 +324,7 @@
                 widgetId = widgetInfo3.widgetId,
                 componentName = widgetInfo3.provider.flattenToString(),
                 itemId = communalItemRankEntry3.uid,
+                userSerialNumber = widgetInfo3.userSerialNumber,
             )
         val fakeState =
             CommunalHubState().apply {
@@ -318,11 +334,13 @@
                                 widgetId = 1
                                 componentName = "pk_name/fake_widget_1"
                                 rank = 1
+                                userSerialNumber = 0
                             },
                             CommunalHubState.CommunalWidgetItem().apply {
                                 widgetId = 2
                                 componentName = "pk_name/fake_widget_2"
                                 rank = 2
+                                userSerialNumber = 10
                             },
                         )
                         .toTypedArray()
diff --git a/packages/SystemUI/tests/src/com/android/systemui/people/widget/PeopleSpaceWidgetManagerTest.java b/packages/SystemUI/tests/src/com/android/systemui/people/widget/PeopleSpaceWidgetManagerTest.java
index 16a022f..8681123 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/people/widget/PeopleSpaceWidgetManagerTest.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/people/widget/PeopleSpaceWidgetManagerTest.java
@@ -276,8 +276,8 @@
     public void setUp() throws Exception {
         MockitoAnnotations.initMocks(this);
         mLauncherApps = mock(LauncherApps.class);
-        mManager = new PeopleSpaceWidgetManager(mContext, mAppWidgetManager, mIPeopleManager,
-                mPeopleManager, mLauncherApps, mNotifCollection, mPackageManager,
+        mManager = new PeopleSpaceWidgetManager(mContext, Optional.of(mAppWidgetManager),
+                mIPeopleManager, mPeopleManager, mLauncherApps, mNotifCollection, mPackageManager,
                 Optional.of(mBubbles), mUserManager, mBackupManager, mINotificationManager,
                 mNotificationManager, mFakeExecutor, mUserTracker);
         mManager.attach(mListenerService);
diff --git a/packages/SystemUI/tests/utils/src/com/android/systemui/TestMocksModule.kt b/packages/SystemUI/tests/utils/src/com/android/systemui/TestMocksModule.kt
index 7c53639..0f8833c 100644
--- a/packages/SystemUI/tests/utils/src/com/android/systemui/TestMocksModule.kt
+++ b/packages/SystemUI/tests/utils/src/com/android/systemui/TestMocksModule.kt
@@ -16,6 +16,7 @@
 package com.android.systemui
 
 import android.app.ActivityManager
+import android.app.DreamManager
 import android.app.admin.DevicePolicyManager
 import android.app.trust.TrustManager
 import android.hardware.fingerprint.FingerprintManager
@@ -33,6 +34,7 @@
 import com.android.systemui.biometrics.AuthController
 import com.android.systemui.bouncer.domain.interactor.PrimaryBouncerInteractor
 import com.android.systemui.communal.domain.interactor.CommunalInteractor
+import com.android.systemui.communal.domain.interactor.CommunalSceneInteractor
 import com.android.systemui.demomode.DemoModeController
 import com.android.systemui.dump.DumpManager
 import com.android.systemui.keyguard.ScreenLifecycle
@@ -94,6 +96,7 @@
     @get:Provides val demoModeController: DemoModeController = mock(),
     @get:Provides val deviceProvisionedController: DeviceProvisionedController = mock(),
     @get:Provides val dozeParameters: DozeParameters = mock(),
+    @get:Provides val dreamManager: DreamManager = mock(),
     @get:Provides val dumpManager: DumpManager = mock(),
     @get:Provides val fingerprintManager: FingerprintManager = mock(),
     @get:Provides val headsUpManager: HeadsUpManager = mock(),
@@ -132,6 +135,7 @@
     @get:Provides val systemUIDialogManager: SystemUIDialogManager = mock(),
     @get:Provides val deviceEntryIconTransitions: Set<DeviceEntryIconTransition> = emptySet(),
     @get:Provides val communalInteractor: CommunalInteractor = mock(),
+    @get:Provides val communalSceneInteractor: CommunalSceneInteractor = mock(),
     @get:Provides val sceneLogger: SceneLogger = mock(),
     @get:Provides val trustManager: TrustManager = mock(),
     @get:Provides val primaryBouncerInteractor: PrimaryBouncerInteractor = mock(),
diff --git a/packages/SystemUI/tests/utils/src/com/android/systemui/keyguard/domain/interactor/FromDozingTransitionInteractorKosmos.kt b/packages/SystemUI/tests/utils/src/com/android/systemui/keyguard/domain/interactor/FromDozingTransitionInteractorKosmos.kt
index 446652c..126d858 100644
--- a/packages/SystemUI/tests/utils/src/com/android/systemui/keyguard/domain/interactor/FromDozingTransitionInteractorKosmos.kt
+++ b/packages/SystemUI/tests/utils/src/com/android/systemui/keyguard/domain/interactor/FromDozingTransitionInteractorKosmos.kt
@@ -16,7 +16,9 @@
 
 package com.android.systemui.keyguard.domain.interactor
 
+import android.service.dream.dreamManager
 import com.android.systemui.communal.domain.interactor.communalInteractor
+import com.android.systemui.communal.domain.interactor.communalSceneInteractor
 import com.android.systemui.deviceentry.data.repository.deviceEntryRepository
 import com.android.systemui.keyguard.data.repository.keyguardTransitionRepository
 import com.android.systemui.kosmos.Kosmos
@@ -36,9 +38,11 @@
             mainDispatcher = testDispatcher,
             keyguardInteractor = keyguardInteractor,
             communalInteractor = communalInteractor,
+            communalSceneInteractor = communalSceneInteractor,
             powerInteractor = powerInteractor,
             keyguardOcclusionInteractor = keyguardOcclusionInteractor,
             deviceEntryRepository = deviceEntryRepository,
             wakeToGoneInteractor = keyguardWakeDirectlyToGoneInteractor,
+            dreamManager = dreamManager
         )
     }
diff --git a/services/core/java/com/android/server/am/ActivityManagerService.java b/services/core/java/com/android/server/am/ActivityManagerService.java
index cc476a3..69ee8fc 100644
--- a/services/core/java/com/android/server/am/ActivityManagerService.java
+++ b/services/core/java/com/android/server/am/ActivityManagerService.java
@@ -2076,7 +2076,8 @@
                 app.setPersistent(true);
                 app.setPid(MY_PID);
                 app.mState.setMaxAdj(ProcessList.SYSTEM_ADJ);
-                app.makeActive(mSystemThread.getApplicationThread(), mProcessStats);
+                app.makeActive(new ApplicationThreadDeferred(mSystemThread.getApplicationThread()),
+                        mProcessStats);
                 app.mProfile.addHostingComponentType(HOSTING_COMPONENT_TYPE_SYSTEM);
                 addPidLocked(app);
                 updateLruProcessLocked(app, false, null);
@@ -4874,7 +4875,7 @@
             // Make app active after binding application or client may be running requests (e.g
             // starting activities) before it is ready.
             synchronized (mProcLock) {
-                app.makeActive(thread, mProcessStats);
+                app.makeActive(new ApplicationThreadDeferred(thread), mProcessStats);
                 checkTime(startTime, "attachApplicationLocked: immediately after bindApplication");
             }
             app.setPendingFinishAttach(true);
diff --git a/services/core/java/com/android/server/am/ApplicationThreadDeferred.java b/services/core/java/com/android/server/am/ApplicationThreadDeferred.java
new file mode 100644
index 0000000..b0f9b53
--- /dev/null
+++ b/services/core/java/com/android/server/am/ApplicationThreadDeferred.java
@@ -0,0 +1,173 @@
+/*
+ * Copyright (C) 2024 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.server.am;
+
+import static com.android.server.am.ActivityManagerDebugConfig.TAG_AM;
+import static com.android.server.am.ActivityManagerDebugConfig.TAG_WITH_CLASS_NAME;
+
+import android.annotation.IntDef;
+import android.app.IApplicationThread;
+import android.os.RemoteException;
+
+import com.android.internal.annotations.GuardedBy;
+import com.android.internal.annotations.VisibleForTesting;
+
+import java.lang.annotation.Retention;
+import java.lang.annotation.RetentionPolicy;
+import java.util.Arrays;
+
+/**
+ * A subclass of {@link IApplicationThread} that defers certain binder calls while the process is
+ * paused (frozen).  Any deferred calls are executed when the process is unpaused.  In some cases,
+ * multiple instances of deferred calls are collapsed into a single call when the process is
+ * unpaused.
+ *
+ * {@hide}
+ */
+class ApplicationThreadDeferred extends ApplicationThreadFilter {
+
+    static final String TAG = TAG_WITH_CLASS_NAME ? "ApplicationThreadDeferred" : TAG_AM;
+
+    // The flag that enables the deferral behavior of this class.  If the flag is disabled then
+    // the class behaves exactly like an ApplicationThreadFilter.
+    private static boolean deferBindersWhenPaused() {
+        return Flags.deferBindersWhenPaused();
+    }
+
+    // The list of notifications that may be deferred.
+    private static final int CLEAR_DNS_CACHE = 0;
+    private static final int UPDATE_TIME_ZONE = 1;
+    private static final int SCHEDULE_LOW_MEMORY = 2;
+    private static final int UPDATE_HTTP_PROXY = 3;
+    private static final int NOTIFICATION_COUNT = 4;
+
+    @IntDef(value = {
+                CLEAR_DNS_CACHE,
+                UPDATE_TIME_ZONE,
+                SCHEDULE_LOW_MEMORY,
+                UPDATE_HTTP_PROXY
+            })
+    @Retention(RetentionPolicy.SOURCE)
+    private @interface NotificationType {};
+
+    private final Object mLock = new Object();
+
+    // If this is true, notifications should be queued for later delivery.  If this is false,
+    // notifications should be delivered immediately.
+    @GuardedBy("mLock")
+    private boolean mPaused = false;
+
+    // An operation is a lambda that throws an exception.
+    private interface Operation {
+        void run() throws RemoteException;
+    }
+
+    // The array of operations.
+    @GuardedBy("mLock")
+    private final Operation[] mOperations = new Operation[NOTIFICATION_COUNT];
+
+    // The array of operations that actually pending right now.
+    @GuardedBy("mLock")
+    private final boolean[] mPending = new boolean[NOTIFICATION_COUNT];
+
+    // When true, binder calls to paused processes will be deferred until the process is unpaused.
+    private final boolean mDefer;
+
+    /** Create an instance with a base thread and a deferral enable flag. */
+    @VisibleForTesting
+    public ApplicationThreadDeferred(IApplicationThread thread, boolean defer) {
+        super(thread);
+
+        mDefer = defer;
+
+        mOperations[CLEAR_DNS_CACHE] = () -> { super.clearDnsCache(); };
+        mOperations[UPDATE_TIME_ZONE] = () -> { super.updateTimeZone(); };
+        mOperations[SCHEDULE_LOW_MEMORY] = () -> { super.scheduleLowMemory(); };
+        mOperations[UPDATE_HTTP_PROXY] = () -> { super.updateHttpProxy(); };
+    }
+
+    /** Create an instance with a base flag, using the system deferral enable flag. */
+    public ApplicationThreadDeferred(IApplicationThread thread) {
+        this(thread, deferBindersWhenPaused());
+    }
+
+    /** The process is being paused.  Start deferring calls. */
+    void onProcessPaused() {
+        synchronized (mLock) {
+            mPaused = true;
+        }
+    }
+
+    /** The process is no longer paused.  Drain any deferred calls. */
+    void onProcessUnpaused() {
+        synchronized (mLock) {
+            mPaused = false;
+            try {
+                for (int i = 0; i < mOperations.length; i++) {
+                    if (mPending[i]) {
+                        mOperations[i].run();
+                    }
+                }
+            } catch (RemoteException e) {
+                // Swallow the exception.  The caller is not expecting it.  Remote exceptions
+                // happen if a has process died; there is no need to report it here.
+            } finally {
+                Arrays.fill(mPending, false);
+            }
+        }
+    }
+
+    /** The pause operation has been canceled.  Drain any deferred calls. */
+    void onProcessPausedCancelled() {
+        onProcessUnpaused();
+    }
+
+    /**
+     * If the thread is not paused, execute the operation.  Otherwise, save it to the pending
+     * list.
+     */
+    private void execute(@NotificationType int tag) throws RemoteException {
+        synchronized (mLock) {
+            if (mPaused && mDefer) {
+                mPending[tag] = true;
+                return;
+            }
+        }
+        // Outside the synchronization block to avoid contention.
+        mOperations[tag].run();
+    }
+
+    @Override
+    public void clearDnsCache() throws RemoteException {
+        execute(CLEAR_DNS_CACHE);
+    }
+
+    @Override
+    public void updateTimeZone() throws RemoteException {
+        execute(UPDATE_TIME_ZONE);
+    }
+
+    @Override
+    public void scheduleLowMemory() throws RemoteException {
+        execute(SCHEDULE_LOW_MEMORY);
+    }
+
+    @Override
+    public void updateHttpProxy() throws RemoteException {
+        execute(UPDATE_HTTP_PROXY);
+    }
+}
diff --git a/services/core/java/com/android/server/am/ApplicationThreadFilter.java b/services/core/java/com/android/server/am/ApplicationThreadFilter.java
new file mode 100644
index 0000000..d049305
--- /dev/null
+++ b/services/core/java/com/android/server/am/ApplicationThreadFilter.java
@@ -0,0 +1,603 @@
+/*
+ * Copyright (C) 2024 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.server.am;
+
+
+class ApplicationThreadFilter implements android.app.IApplicationThread {
+    private final android.app.IApplicationThread mBase;
+    public ApplicationThreadFilter(android.app.IApplicationThread base) { mBase = base; }
+    android.app.IApplicationThread getBase() { return mBase; }
+    public android.os.IBinder asBinder() {
+        return mBase.asBinder();
+    }
+
+    @Override
+    public void scheduleReceiver(android.content.Intent intent,
+            android.content.pm.ActivityInfo info,
+            android.content.res.CompatibilityInfo compatInfo,
+            int resultCode,
+            String data,
+            android.os.Bundle extras,
+            boolean ordered,
+            boolean assumeDelivered,
+            int sendingUser,
+            int processState,
+            int sentFromUid,
+            String sentFromPackage)
+            throws android.os.RemoteException {
+        mBase.scheduleReceiver(intent,
+                info,
+                compatInfo,
+                resultCode,
+                data,
+                extras,
+                ordered,
+                assumeDelivered,
+                sendingUser,
+                processState,
+                sentFromUid,
+                sentFromPackage);
+    }
+    @Override
+    public void scheduleReceiverList(java.util.List<android.app.ReceiverInfo> info)
+            throws android.os.RemoteException {
+        mBase.scheduleReceiverList(info);
+    }
+    @Override
+    public void scheduleCreateService(android.os.IBinder token,
+            android.content.pm.ServiceInfo info,
+            android.content.res.CompatibilityInfo compatInfo,
+            int processState)
+            throws android.os.RemoteException {
+        mBase.scheduleCreateService(token,
+                info,
+                compatInfo,
+                processState);
+    }
+    @Override
+    public void scheduleStopService(android.os.IBinder token)
+            throws android.os.RemoteException {
+        mBase.scheduleStopService(token);
+    }
+    @Override
+    public void bindApplication(String packageName,
+            android.content.pm.ApplicationInfo info,
+            String sdkSandboxClientAppVolumeUuid,
+            String sdkSandboxClientAppPackage,
+            boolean isSdkInSandbox,
+            android.content.pm.ProviderInfoList providerList,
+            android.content.ComponentName testName,
+            android.app.ProfilerInfo profilerInfo,
+            android.os.Bundle testArguments,
+            android.app.IInstrumentationWatcher testWatcher,
+            android.app.IUiAutomationConnection uiAutomationConnection,
+            int debugMode,
+            boolean enableBinderTracking,
+            boolean trackAllocation,
+            boolean restrictedBackupMode,
+            boolean persistent,
+            android.content.res.Configuration config,
+            android.content.res.CompatibilityInfo compatInfo,
+            java.util.Map services,
+            android.os.Bundle coreSettings,
+            String buildSerial,
+            android.content.AutofillOptions autofillOptions,
+            android.content.ContentCaptureOptions contentCaptureOptions,
+            long[] disabledCompatChanges,
+            long[] loggableCompatChanges,
+            android.os.SharedMemory serializedSystemFontMap,
+            long startRequestedElapsedTime,
+            long startRequestedUptime)
+            throws android.os.RemoteException {
+        mBase.bindApplication(packageName,
+                info,
+                sdkSandboxClientAppVolumeUuid,
+                sdkSandboxClientAppPackage,
+                isSdkInSandbox,
+                providerList,
+                testName,
+                profilerInfo,
+                testArguments,
+                testWatcher,
+                uiAutomationConnection,
+                debugMode,
+                enableBinderTracking,
+                trackAllocation,
+                restrictedBackupMode,
+                persistent,
+                config,
+                compatInfo,
+                services,
+                coreSettings,
+                buildSerial,
+                autofillOptions,
+                contentCaptureOptions,
+                disabledCompatChanges,
+                loggableCompatChanges,
+                serializedSystemFontMap,
+                startRequestedElapsedTime,
+                startRequestedUptime);
+    }
+    @Override
+    public void runIsolatedEntryPoint(String entryPoint,
+            String[] entryPointArgs)
+            throws android.os.RemoteException {
+        mBase.runIsolatedEntryPoint(entryPoint,
+                entryPointArgs);
+    }
+    @Override
+    public void scheduleExit()
+            throws android.os.RemoteException {
+        mBase.scheduleExit();
+    }
+    @Override
+    public void scheduleServiceArgs(android.os.IBinder token,
+            android.content.pm.ParceledListSlice args)
+            throws android.os.RemoteException {
+        mBase.scheduleServiceArgs(token,
+                args);
+    }
+    @Override
+    public void updateTimeZone()
+            throws android.os.RemoteException {
+        mBase.updateTimeZone();
+    }
+    @Override
+    public void processInBackground()
+            throws android.os.RemoteException {
+        mBase.processInBackground();
+    }
+    @Override
+    public void scheduleBindService(android.os.IBinder token,
+            android.content.Intent intent,
+            boolean rebind,
+            int processState,
+            long bindSeq)
+            throws android.os.RemoteException {
+        mBase.scheduleBindService(token,
+                intent,
+                rebind,
+                processState,
+                bindSeq);
+    }
+    @Override
+    public void scheduleUnbindService(android.os.IBinder token,
+            android.content.Intent intent)
+            throws android.os.RemoteException {
+        mBase.scheduleUnbindService(token,
+                intent);
+    }
+    @Override
+    public void dumpService(android.os.ParcelFileDescriptor fd,
+            android.os.IBinder servicetoken,
+            String[] args)
+            throws android.os.RemoteException {
+        mBase.dumpService(fd,
+                servicetoken,
+                args);
+    }
+    @Override
+    public void scheduleRegisteredReceiver(android.content.IIntentReceiver receiver,
+            android.content.Intent intent,
+            int resultCode,
+            String data,
+            android.os.Bundle extras,
+            boolean ordered,
+            boolean sticky,
+            boolean assumeDelivered,
+            int sendingUser,
+            int processState,
+            int sentFromUid,
+            String sentFromPackage)
+            throws android.os.RemoteException {
+        mBase.scheduleRegisteredReceiver(receiver,
+                intent,
+                resultCode,
+                data,
+                extras,
+                ordered,
+                sticky,
+                assumeDelivered,
+                sendingUser,
+                processState,
+                sentFromUid,
+                sentFromPackage);
+    }
+    @Override
+    public void scheduleLowMemory()
+            throws android.os.RemoteException {
+        mBase.scheduleLowMemory();
+    }
+    @Override
+    public void profilerControl(boolean start,
+            android.app.ProfilerInfo profilerInfo,
+            int profileType)
+            throws android.os.RemoteException {
+        mBase.profilerControl(start,
+                profilerInfo,
+                profileType);
+    }
+    @Override
+    public void setSchedulingGroup(int group)
+            throws android.os.RemoteException {
+        mBase.setSchedulingGroup(group);
+    }
+    @Override
+    public void scheduleCreateBackupAgent(android.content.pm.ApplicationInfo app,
+            int backupMode,
+            int userId,
+            int operationType)
+            throws android.os.RemoteException {
+        mBase.scheduleCreateBackupAgent(app,
+                backupMode,
+                userId,
+                operationType);
+    }
+    @Override
+    public void scheduleDestroyBackupAgent(android.content.pm.ApplicationInfo app,
+            int userId)
+            throws android.os.RemoteException {
+        mBase.scheduleDestroyBackupAgent(app,
+                userId);
+    }
+    @Override
+    public void scheduleOnNewSceneTransitionInfo(android.os.IBinder token,
+            android.app.ActivityOptions.SceneTransitionInfo info)
+            throws android.os.RemoteException {
+        mBase.scheduleOnNewSceneTransitionInfo(token,
+                info);
+    }
+    @Override
+    public void scheduleSuicide()
+            throws android.os.RemoteException {
+        mBase.scheduleSuicide();
+    }
+    @Override
+    public void dispatchPackageBroadcast(int cmd,
+            String[] packages)
+            throws android.os.RemoteException {
+        mBase.dispatchPackageBroadcast(cmd,
+                packages);
+    }
+    @Override
+    public void scheduleCrash(String msg,
+            int typeId,
+            android.os.Bundle extras)
+            throws android.os.RemoteException {
+        mBase.scheduleCrash(msg,
+                typeId,
+                extras);
+    }
+    @Override
+    public void dumpHeap(boolean managed,
+            boolean mallocInfo,
+            boolean runGc,
+            String dumpBitmaps,
+            String path,
+            android.os.ParcelFileDescriptor fd,
+            android.os.RemoteCallback finishCallback)
+            throws android.os.RemoteException {
+        mBase.dumpHeap(managed,
+                mallocInfo,
+                runGc,
+                dumpBitmaps,
+                path,
+                fd,
+                finishCallback);
+    }
+    @Override
+    public void dumpActivity(android.os.ParcelFileDescriptor fd,
+            android.os.IBinder servicetoken,
+            String prefix,
+            String[] args)
+            throws android.os.RemoteException {
+        mBase.dumpActivity(fd,
+                servicetoken,
+                prefix,
+                args);
+    }
+    @Override
+    public void dumpResources(android.os.ParcelFileDescriptor fd,
+            android.os.RemoteCallback finishCallback)
+            throws android.os.RemoteException {
+        mBase.dumpResources(fd,
+                finishCallback);
+    }
+    @Override
+    public void clearDnsCache()
+            throws android.os.RemoteException {
+        mBase.clearDnsCache();
+    }
+    @Override
+    public void updateHttpProxy()
+            throws android.os.RemoteException {
+        mBase.updateHttpProxy();
+    }
+    @Override
+    public void setCoreSettings(android.os.Bundle coreSettings)
+            throws android.os.RemoteException {
+        mBase.setCoreSettings(coreSettings);
+    }
+    @Override
+    public void updatePackageCompatibilityInfo(String pkg,
+            android.content.res.CompatibilityInfo info)
+            throws android.os.RemoteException {
+        mBase.updatePackageCompatibilityInfo(pkg,
+                info);
+    }
+    @Override
+    public void scheduleTrimMemory(int level)
+            throws android.os.RemoteException {
+        mBase.scheduleTrimMemory(level);
+    }
+    @Override
+    public void dumpMemInfo(android.os.ParcelFileDescriptor fd,
+            android.os.Debug.MemoryInfo mem,
+            boolean checkin,
+            boolean dumpInfo,
+            boolean dumpDalvik,
+            boolean dumpSummaryOnly,
+            boolean dumpUnreachable,
+            boolean dumpAllocatorLogs,
+            String[] args)
+            throws android.os.RemoteException {
+        mBase.dumpMemInfo(fd,
+                mem,
+                checkin,
+                dumpInfo,
+                dumpDalvik,
+                dumpSummaryOnly,
+                dumpUnreachable,
+                dumpAllocatorLogs,
+                args);
+    }
+    @Override
+    public void dumpMemInfoProto(android.os.ParcelFileDescriptor fd,
+            android.os.Debug.MemoryInfo mem,
+            boolean dumpInfo,
+            boolean dumpDalvik,
+            boolean dumpSummaryOnly,
+            boolean dumpUnreachable,
+            String[] args)
+            throws android.os.RemoteException {
+        mBase.dumpMemInfoProto(fd,
+                mem,
+                dumpInfo,
+                dumpDalvik,
+                dumpSummaryOnly,
+                dumpUnreachable,
+                args);
+    }
+    @Override
+    public void dumpGfxInfo(android.os.ParcelFileDescriptor fd,
+            String[] args)
+            throws android.os.RemoteException {
+        mBase.dumpGfxInfo(fd,
+                args);
+    }
+    @Override
+    public void dumpCacheInfo(android.os.ParcelFileDescriptor fd,
+            String[] args)
+            throws android.os.RemoteException {
+        mBase.dumpCacheInfo(fd,
+                args);
+    }
+    @Override
+    public void dumpProvider(android.os.ParcelFileDescriptor fd,
+            android.os.IBinder servicetoken,
+            String[] args)
+            throws android.os.RemoteException {
+        mBase.dumpProvider(fd,
+                servicetoken,
+                args);
+    }
+    @Override
+    public void dumpDbInfo(android.os.ParcelFileDescriptor fd,
+            String[] args)
+            throws android.os.RemoteException {
+        mBase.dumpDbInfo(fd,
+                args);
+    }
+    @Override
+    public void unstableProviderDied(android.os.IBinder provider)
+            throws android.os.RemoteException {
+        mBase.unstableProviderDied(provider);
+    }
+    @Override
+    public void requestAssistContextExtras(android.os.IBinder activityToken,
+            android.os.IBinder requestToken,
+            int requestType,
+            int sessionId,
+            int flags)
+            throws android.os.RemoteException {
+        mBase.requestAssistContextExtras(activityToken,
+                requestToken,
+                requestType,
+                sessionId,
+                flags);
+    }
+    @Override
+    public void scheduleTranslucentConversionComplete(android.os.IBinder token,
+            boolean timeout)
+            throws android.os.RemoteException {
+        mBase.scheduleTranslucentConversionComplete(token,
+                timeout);
+    }
+    @Override
+    public void setProcessState(int state)
+            throws android.os.RemoteException {
+        mBase.setProcessState(state);
+    }
+    @Override
+    public void scheduleInstallProvider(android.content.pm.ProviderInfo provider)
+            throws android.os.RemoteException {
+        mBase.scheduleInstallProvider(provider);
+    }
+    @Override
+    public void updateTimePrefs(int timeFormatPreference)
+            throws android.os.RemoteException {
+        mBase.updateTimePrefs(timeFormatPreference);
+    }
+    @Override
+    public void scheduleEnterAnimationComplete(android.os.IBinder token)
+            throws android.os.RemoteException {
+        mBase.scheduleEnterAnimationComplete(token);
+    }
+    @Override
+    public void notifyCleartextNetwork(byte[] firstPacket)
+            throws android.os.RemoteException {
+        mBase.notifyCleartextNetwork(firstPacket);
+    }
+    @Override
+    public void startBinderTracking()
+            throws android.os.RemoteException {
+        mBase.startBinderTracking();
+    }
+    @Override
+    public void stopBinderTrackingAndDump(android.os.ParcelFileDescriptor fd)
+            throws android.os.RemoteException {
+        mBase.stopBinderTrackingAndDump(fd);
+    }
+    @Override
+    public void scheduleLocalVoiceInteractionStarted(android.os.IBinder token,
+            com.android.internal.app.IVoiceInteractor voiceInteractor)
+            throws android.os.RemoteException {
+        mBase.scheduleLocalVoiceInteractionStarted(token,
+                voiceInteractor);
+    }
+    @Override
+    public void handleTrustStorageUpdate()
+            throws android.os.RemoteException {
+        mBase.handleTrustStorageUpdate();
+    }
+    @Override
+    public void attachAgent(String path)
+            throws android.os.RemoteException {
+        mBase.attachAgent(path);
+    }
+    @Override
+    public void attachStartupAgents(String dataDir)
+            throws android.os.RemoteException {
+        mBase.attachStartupAgents(dataDir);
+    }
+    @Override
+    public void scheduleApplicationInfoChanged(android.content.pm.ApplicationInfo ai)
+            throws android.os.RemoteException {
+        mBase.scheduleApplicationInfoChanged(ai);
+    }
+    @Override
+    public void setNetworkBlockSeq(long procStateSeq)
+            throws android.os.RemoteException {
+        mBase.setNetworkBlockSeq(procStateSeq);
+    }
+    @Override
+    public void scheduleTransaction(android.app.servertransaction.ClientTransaction transaction)
+            throws android.os.RemoteException {
+        mBase.scheduleTransaction(transaction);
+    }
+    @Override
+    public void scheduleTaskFragmentTransaction(android.window.ITaskFragmentOrganizer organizer,
+            android.window.TaskFragmentTransaction transaction)
+            throws android.os.RemoteException {
+        mBase.scheduleTaskFragmentTransaction(organizer,
+                transaction);
+    }
+    @Override
+    public void requestDirectActions(android.os.IBinder activityToken,
+            com.android.internal.app.IVoiceInteractor intractor,
+            android.os.RemoteCallback cancellationCallback,
+            android.os.RemoteCallback callback)
+            throws android.os.RemoteException {
+        mBase.requestDirectActions(activityToken,
+                intractor,
+                cancellationCallback,
+                callback);
+    }
+    @Override
+    public void performDirectAction(android.os.IBinder activityToken,
+            String actionId,
+            android.os.Bundle arguments,
+            android.os.RemoteCallback cancellationCallback,
+            android.os.RemoteCallback resultCallback)
+            throws android.os.RemoteException {
+        mBase.performDirectAction(activityToken,
+                actionId,
+                arguments,
+                cancellationCallback,
+                resultCallback);
+    }
+    @Override
+    public void notifyContentProviderPublishStatus(android.app.ContentProviderHolder holder,
+            String authorities,
+            int userId,
+            boolean published)
+            throws android.os.RemoteException {
+        mBase.notifyContentProviderPublishStatus(holder,
+                authorities,
+                userId,
+                published);
+    }
+    @Override
+    public void instrumentWithoutRestart(android.content.ComponentName instrumentationName,
+            android.os.Bundle instrumentationArgs,
+            android.app.IInstrumentationWatcher instrumentationWatcher,
+            android.app.IUiAutomationConnection instrumentationUiConnection,
+            android.content.pm.ApplicationInfo targetInfo)
+            throws android.os.RemoteException {
+        mBase.instrumentWithoutRestart(instrumentationName,
+                instrumentationArgs,
+                instrumentationWatcher,
+                instrumentationUiConnection,
+                targetInfo);
+    }
+    @Override
+    public void updateUiTranslationState(android.os.IBinder activityToken,
+            int state,
+            android.view.translation.TranslationSpec sourceSpec,
+            android.view.translation.TranslationSpec targetSpec,
+            java.util.List<android.view.autofill.AutofillId> viewIds,
+            android.view.translation.UiTranslationSpec uiTranslationSpec)
+            throws android.os.RemoteException {
+        mBase.updateUiTranslationState(activityToken,
+                state,
+                sourceSpec,
+                targetSpec,
+                viewIds,
+                uiTranslationSpec);
+    }
+    @Override
+    public void scheduleTimeoutService(android.os.IBinder token,
+            int startId)
+            throws android.os.RemoteException {
+        mBase.scheduleTimeoutService(token,
+                startId);
+    }
+    @Override
+    public void scheduleTimeoutServiceForType(android.os.IBinder token,
+            int startId,
+            int fgsType)
+            throws android.os.RemoteException {
+        mBase.scheduleTimeoutServiceForType(token,
+                startId,
+                fgsType);
+    }
+    @Override
+    public void schedulePing(android.os.RemoteCallback pong)
+            throws android.os.RemoteException {
+        mBase.schedulePing(pong);
+    }
+}
diff --git a/services/core/java/com/android/server/am/CachedAppOptimizer.java b/services/core/java/com/android/server/am/CachedAppOptimizer.java
index 6433f2c..1c4ffbb 100644
--- a/services/core/java/com/android/server/am/CachedAppOptimizer.java
+++ b/services/core/java/com/android/server/am/CachedAppOptimizer.java
@@ -2656,6 +2656,7 @@
         // PIDs that run out of async binder buffer when being frozen
         ArraySet<Integer> pidsAsync = (mFreezerBinderAsyncThreshold < 0) ? null : new ArraySet<>();
 
+        Trace.traceBegin(Trace.TRACE_TAG_ACTIVITY_MANAGER, "binderErrorSync");
         for (int i = 0; i < pids.size(); i++) {
             int current = pids.get(i);
             try {
@@ -2684,6 +2685,7 @@
                 Slog.w(TAG_AM, "Unable to query binder frozen stats for pid " + current);
             }
         }
+        Trace.traceEnd(Trace.TRACE_TAG_ACTIVITY_MANAGER);
 
         // TODO: when kernel binder driver supports, poll the binder status directly.
         // Binderfs stats, like other debugfs files, is not a reliable interface. But it's the
@@ -2693,6 +2695,8 @@
         if (pidsAsync == null || pidsAsync.size() == 0) {
             return;
         }
+
+        Trace.traceBegin(Trace.TRACE_TAG_ACTIVITY_MANAGER, "binderErrorAsync");
         new BinderfsStatsReader().handleFreeAsyncSpace(
                 // Check if the frozen process has pending async calls
                 pidsAsync::contains,
@@ -2710,5 +2714,6 @@
 
                 // Log the error if binderfs stats can't be accesses or correctly parsed
                 exception -> Slog.e(TAG_AM, "Unable to parse binderfs stats"));
+        Trace.traceEnd(Trace.TRACE_TAG_ACTIVITY_MANAGER);
     }
 }
diff --git a/services/core/java/com/android/server/am/ProcessRecord.java b/services/core/java/com/android/server/am/ProcessRecord.java
index a74c489..3e71d00 100644
--- a/services/core/java/com/android/server/am/ProcessRecord.java
+++ b/services/core/java/com/android/server/am/ProcessRecord.java
@@ -151,7 +151,7 @@
      * (in which case we are in the process of launching the app).
      */
     @CompositeRWLock({"mService", "mProcLock"})
-    private IApplicationThread mThread;
+    private ApplicationThreadDeferred mThread;
 
     /**
      * Instance of {@link #mThread} that will always meet the {@code oneway}
@@ -737,15 +737,15 @@
     }
 
     @GuardedBy({"mService", "mProcLock"})
-    public void makeActive(IApplicationThread thread, ProcessStatsService tracker) {
+    public void makeActive(ApplicationThreadDeferred thread, ProcessStatsService tracker) {
         mProfile.onProcessActive(thread, tracker);
         mThread = thread;
         if (mPid == Process.myPid()) {
-            mOnewayThread = new SameProcessApplicationThread(thread, FgThread.getHandler());
+            mOnewayThread = new SameProcessApplicationThread(mThread, FgThread.getHandler());
         } else {
-            mOnewayThread = thread;
+            mOnewayThread = mThread;
         }
-        mWindowProcessController.setThread(thread);
+        mWindowProcessController.setThread(mThread);
         if (mWindowProcessController.useFifoUiScheduling()) {
             mService.mSpecifiedFifoProcesses.add(this);
         }
@@ -1436,14 +1436,17 @@
 
     void onProcessFrozen() {
         mProfile.onProcessFrozen();
+        if (mThread != null) mThread.onProcessPaused();
     }
 
     void onProcessUnfrozen() {
+        if (mThread != null) mThread.onProcessUnpaused();
         mProfile.onProcessUnfrozen();
         mServices.onProcessUnfrozen();
     }
 
     void onProcessFrozenCancelled() {
+        if (mThread != null) mThread.onProcessPausedCancelled();
         mServices.onProcessFrozenCancelled();
     }
 
diff --git a/services/core/java/com/android/server/am/flags.aconfig b/services/core/java/com/android/server/am/flags.aconfig
index 9b380ff..d214788 100644
--- a/services/core/java/com/android/server/am/flags.aconfig
+++ b/services/core/java/com/android/server/am/flags.aconfig
@@ -176,3 +176,11 @@
     bug: "330682397"
     is_fixed_read_only: true
 }
+
+flag {
+    name: "defer_binders_when_paused"
+    namespace: "system_performance"
+    is_fixed_read_only: true
+    description: "Defer submitting binder calls to paused processes."
+    bug: "327038797"
+}
diff --git a/services/core/java/com/android/server/inputmethod/AdditionalSubtypeMapRepository.java b/services/core/java/com/android/server/inputmethod/AdditionalSubtypeMapRepository.java
index 82ecb4a..91ab872 100644
--- a/services/core/java/com/android/server/inputmethod/AdditionalSubtypeMapRepository.java
+++ b/services/core/java/com/android/server/inputmethod/AdditionalSubtypeMapRepository.java
@@ -225,17 +225,17 @@
         sWriter.startThread();
     }
 
-    static void initialize(@NonNull Handler handler, @NonNull Context context) {
+    static void initialize(@NonNull Handler ioHandler, @NonNull Context context) {
         final UserManagerInternal userManagerInternal =
                 LocalServices.getService(UserManagerInternal.class);
-        handler.post(() -> {
+        ioHandler.post(() -> {
             userManagerInternal.addUserLifecycleListener(
                     new UserManagerInternal.UserLifecycleListener() {
                         @Override
                         public void onUserCreated(UserInfo user, @Nullable Object token) {
                             final int userId = user.id;
                             sWriter.onUserCreated(userId);
-                            handler.post(() -> {
+                            ioHandler.post(() -> {
                                 synchronized (ImfLock.class) {
                                     if (!sPerUserMap.contains(userId)) {
                                         final AdditionalSubtypeMap additionalSubtypeMap =
@@ -257,7 +257,7 @@
                         public void onUserRemoved(UserInfo user) {
                             final int userId = user.id;
                             sWriter.onUserRemoved(userId);
-                            handler.post(() -> {
+                            ioHandler.post(() -> {
                                 synchronized (ImfLock.class) {
                                     sPerUserMap.remove(userId);
                                 }
diff --git a/services/core/java/com/android/server/inputmethod/InputMethodManagerService.java b/services/core/java/com/android/server/inputmethod/InputMethodManagerService.java
index 875380f..5665738 100644
--- a/services/core/java/com/android/server/inputmethod/InputMethodManagerService.java
+++ b/services/core/java/com/android/server/inputmethod/InputMethodManagerService.java
@@ -26,6 +26,7 @@
 import static android.provider.Settings.Secure.STYLUS_HANDWRITING_ENABLED;
 import static android.server.inputmethod.InputMethodManagerServiceProto.BACK_DISPOSITION;
 import static android.server.inputmethod.InputMethodManagerServiceProto.BOUND_TO_METHOD;
+import static android.server.inputmethod.InputMethodManagerServiceProto.CONCURRENT_MULTI_USER_MODE_ENABLED;
 import static android.server.inputmethod.InputMethodManagerServiceProto.CUR_ATTRIBUTE;
 import static android.server.inputmethod.InputMethodManagerServiceProto.CUR_CLIENT;
 import static android.server.inputmethod.InputMethodManagerServiceProto.CUR_FOCUSED_WINDOW_SOFT_INPUT_MODE;
@@ -78,6 +79,7 @@
 import android.content.pm.PackageManagerInternal;
 import android.content.pm.ResolveInfo;
 import android.content.pm.ServiceInfo;
+import android.content.pm.UserInfo;
 import android.content.res.Resources;
 import android.hardware.input.InputManager;
 import android.inputmethodservice.InputMethodService;
@@ -345,8 +347,8 @@
     private int mCurrentUserId;
 
     /** Holds all user related data */
-    @GuardedBy("ImfLock.class")
-    private UserDataRepository mUserDataRepository;
+    @SharedByAllUsersField
+    private final UserDataRepository mUserDataRepository;
 
     final WindowManagerInternal mWindowManagerInternal;
     private final ActivityManagerInternal mActivityManagerInternal;
@@ -922,11 +924,15 @@
      * {@link SystemService} used to publish and manage the lifecycle of
      * {@link InputMethodManagerService}.
      */
-    public static final class Lifecycle extends SystemService {
+    public static final class Lifecycle extends SystemService
+            implements UserManagerInternal.UserLifecycleListener {
         private final InputMethodManagerService mService;
 
         public Lifecycle(Context context) {
             this(context, createServiceForProduction(context));
+
+            // For production code, hook up user lifecycle
+            mService.mUserManagerInternal.addUserLifecycleListener(this);
         }
 
         @VisibleForTesting
@@ -1005,6 +1011,18 @@
         }
 
         @Override
+        public void onUserCreated(UserInfo user, @Nullable Object token) {
+            // Called directly from UserManagerService. Do not block the calling thread.
+        }
+
+        @Override
+        public void onUserRemoved(UserInfo user) {
+            // Called directly from UserManagerService. Do not block the calling thread.
+            final int userId = user.id;
+            mService.mUserDataRepository.remove(userId);
+        }
+
+        @Override
         public void onUserUnlocking(@NonNull TargetUser user) {
             // Called on ActivityManager thread.
             SecureSettingsWrapper.onUserUnlocking(user.getUserIdentifier());
@@ -1106,14 +1124,14 @@
             // Search for InputMethodSettingsRepository.put() to find where and when it's actually
             // being updated. In general IMMS should refrain from exposing the existence of IMEs
             // until systemReady().
-            InputMethodSettingsRepository.initialize(mHandler, mContext);
-            AdditionalSubtypeMapRepository.initialize(mHandler, mContext);
+            InputMethodSettingsRepository.initialize(mIoHandler, mContext);
+            AdditionalSubtypeMapRepository.initialize(mIoHandler, mContext);
 
             mCurrentUserId = mActivityManagerInternal.getCurrentUserId();
             @SuppressWarnings("GuardedBy") final IntFunction<InputMethodBindingController>
                     bindingControllerFactory = userId -> new InputMethodBindingController(userId,
                     InputMethodManagerService.this);
-            mUserDataRepository = new UserDataRepository(mHandler, mUserManagerInternal,
+            mUserDataRepository = new UserDataRepository(
                     bindingControllerForTesting != null ? bindingControllerForTesting
                             : bindingControllerFactory);
 
@@ -4560,6 +4578,7 @@
             proto.write(BACK_DISPOSITION, bindingController.getBackDisposition());
             proto.write(IME_WINDOW_VISIBILITY, bindingController.getImeWindowVis());
             proto.write(SHOW_IME_WITH_HARD_KEYBOARD, mMenuController.getShowImeWithHardKeyboard());
+            proto.write(CONCURRENT_MULTI_USER_MODE_ENABLED, mConcurrentMultiUserModeEnabled);
             proto.end(token);
         }
     }
@@ -6013,6 +6032,7 @@
             final InputMethodSettings settings = InputMethodSettingsRepository.get(userId);
             final var userData = getUserData(userId);
             p.println("Current Input Method Manager state:");
+            p.println("  concurrentMultiUserModeEnabled" + mConcurrentMultiUserModeEnabled);
             final List<InputMethodInfo> methodList = settings.getMethodList();
             int numImes = methodList.size();
             p.println("  Input Methods:");
diff --git a/services/core/java/com/android/server/inputmethod/InputMethodSettingsRepository.java b/services/core/java/com/android/server/inputmethod/InputMethodSettingsRepository.java
index 68924b5..a4d8ee5 100644
--- a/services/core/java/com/android/server/inputmethod/InputMethodSettingsRepository.java
+++ b/services/core/java/com/android/server/inputmethod/InputMethodSettingsRepository.java
@@ -54,16 +54,16 @@
         sPerUserMap.put(userId, obj);
     }
 
-    static void initialize(@NonNull Handler handler, @NonNull Context context) {
+    static void initialize(@NonNull Handler ioHandler, @NonNull Context context) {
         final UserManagerInternal userManagerInternal =
                 LocalServices.getService(UserManagerInternal.class);
-        handler.post(() -> {
+        ioHandler.post(() -> {
             userManagerInternal.addUserLifecycleListener(
                     new UserManagerInternal.UserLifecycleListener() {
                         @Override
                         public void onUserRemoved(UserInfo user) {
                             final int userId = user.id;
-                            handler.post(() -> {
+                            ioHandler.post(() -> {
                                 synchronized (ImfLock.class) {
                                     sPerUserMap.remove(userId);
                                 }
diff --git a/services/core/java/com/android/server/inputmethod/UserDataRepository.java b/services/core/java/com/android/server/inputmethod/UserDataRepository.java
index 5cd980f..98d7548 100644
--- a/services/core/java/com/android/server/inputmethod/UserDataRepository.java
+++ b/services/core/java/com/android/server/inputmethod/UserDataRepository.java
@@ -16,11 +16,10 @@
 
 package com.android.server.inputmethod;
 
+import android.annotation.AnyThread;
 import android.annotation.NonNull;
 import android.annotation.Nullable;
 import android.annotation.UserIdInt;
-import android.content.pm.UserInfo;
-import android.os.Handler;
 import android.util.SparseArray;
 import android.view.inputmethod.EditorInfo;
 import android.view.inputmethod.ImeTracker;
@@ -29,52 +28,63 @@
 import com.android.internal.annotations.GuardedBy;
 import com.android.internal.inputmethod.IRemoteAccessibilityInputConnection;
 import com.android.internal.inputmethod.IRemoteInputConnection;
-import com.android.server.pm.UserManagerInternal;
 
+import java.util.concurrent.locks.ReentrantReadWriteLock;
 import java.util.function.Consumer;
 import java.util.function.IntFunction;
 
 final class UserDataRepository {
 
-    @GuardedBy("ImfLock.class")
+    private final ReentrantReadWriteLock mUserDataLock = new ReentrantReadWriteLock();
+
+    @GuardedBy("mUserDataLock")
     private final SparseArray<UserData> mUserData = new SparseArray<>();
 
     private final IntFunction<InputMethodBindingController> mBindingControllerFactory;
 
-    @GuardedBy("ImfLock.class")
+    @AnyThread
     @NonNull
     UserData getOrCreate(@UserIdInt int userId) {
-        UserData userData = mUserData.get(userId);
-        if (userData == null) {
-            userData = new UserData(userId, mBindingControllerFactory.apply(userId));
-            mUserData.put(userId, userData);
+        mUserDataLock.writeLock().lock();
+        try {
+            UserData userData = mUserData.get(userId);
+            if (userData == null) {
+                userData = new UserData(userId, mBindingControllerFactory.apply(userId));
+                mUserData.put(userId, userData);
+            }
+            return userData;
+        } finally {
+            mUserDataLock.writeLock().unlock();
         }
-        return userData;
     }
 
-    @GuardedBy("ImfLock.class")
+    @AnyThread
     void forAllUserData(Consumer<UserData> consumer) {
-        for (int i = 0; i < mUserData.size(); i++) {
-            consumer.accept(mUserData.valueAt(i));
+        final SparseArray<UserData> copiedArray;
+        mUserDataLock.readLock().lock();
+        try {
+            copiedArray = mUserData.clone();
+        } finally {
+            mUserDataLock.readLock().unlock();
+        }
+        for (int i = 0; i < copiedArray.size(); i++) {
+            consumer.accept(copiedArray.valueAt(i));
         }
     }
 
     UserDataRepository(
-            @NonNull Handler handler, @NonNull UserManagerInternal userManagerInternal,
             @NonNull IntFunction<InputMethodBindingController> bindingControllerFactory) {
         mBindingControllerFactory = bindingControllerFactory;
-        userManagerInternal.addUserLifecycleListener(
-                new UserManagerInternal.UserLifecycleListener() {
-                    @Override
-                    public void onUserRemoved(UserInfo user) {
-                        final int userId = user.id;
-                        handler.post(() -> {
-                            synchronized (ImfLock.class) {
-                                mUserData.remove(userId);
-                            }
-                        });
-                    }
-                });
+    }
+
+    @AnyThread
+    void remove(@UserIdInt int userId) {
+        mUserDataLock.writeLock().lock();
+        try {
+            mUserData.remove(userId);
+        } finally {
+            mUserDataLock.writeLock().unlock();
+        }
     }
 
     /** Placeholder for all IMMS user specific fields */
diff --git a/services/core/java/com/android/server/notification/ZenModeEventLogger.java b/services/core/java/com/android/server/notification/ZenModeEventLogger.java
index ec5d96d..4a82057 100644
--- a/services/core/java/com/android/server/notification/ZenModeEventLogger.java
+++ b/services/core/java/com/android/server/notification/ZenModeEventLogger.java
@@ -106,11 +106,11 @@
     /**
      * Potentially log a zen mode change if the provided config and policy changes warrant it.
      *
-     * @param prevInfo    ZenModeInfo (zen mode setting, config, policy) prior to this change
-     * @param newInfo     ZenModeInfo after this change takes effect
-     * @param callingUid  the calling UID associated with the change; may be used to attribute the
-     *                    change to a particular package or determine if this is a user action
-     * @param origin      The origin of the Zen change.
+     * @param prevInfo   ZenModeInfo (zen mode setting, config, policy) prior to this change
+     * @param newInfo    ZenModeInfo after this change takes effect
+     * @param callingUid the calling UID associated with the change; may be used to attribute the
+     *                   change to a particular package or determine if this is a user action
+     * @param origin     The origin of the Zen change.
      */
     public final void maybeLogZenChange(ZenModeInfo prevInfo, ZenModeInfo newInfo, int callingUid,
             @ConfigChangeOrigin int origin) {
@@ -127,6 +127,9 @@
     /**
      * Reassign callingUid in mChangeState if we have more specific information that warrants it
      * (for instance, if the change is automatic and due to an automatic rule change).
+     *
+     * <p>When Flags.modesUi() is enabled, we reassign the calling UID to the package UID in all
+     * changes whose source is not system or system UI, as long as there is only one rule changed.
      */
     private void maybeReassignCallingUid() {
         int userId = Process.INVALID_UID;
@@ -145,12 +148,23 @@
             userId = mChangeState.mNewConfig.user;  // mNewConfig must not be null if enabler exists
         }
 
-        // The conditions where we should consider reassigning UID for an automatic rule change:
+        // The conditions where we should consider reassigning UID for an automatic rule change
+        // (pre-modes_ui):
         //   - we've determined it's not a user action
         //   - our current best guess is that the calling uid is system/sysui
+        // When Flags.modesUi() is true, we get the package UID for the changed rule, as long as:
+        //   - the change does not originate from the system based on change origin
+        //   - there is only one rule changed
         if (mChangeState.getChangedRuleType() == RULE_TYPE_AUTOMATIC) {
-            if (mChangeState.getIsUserAction() || !mChangeState.isFromSystemOrSystemUi()) {
-                return;
+            if (Flags.modesUi()) {
+                // ignore anything whose origin is system
+                if (mChangeState.isFromSystemOrSystemUi()) {
+                    return;
+                }
+            } else {
+                if (mChangeState.getIsUserAction() || !mChangeState.isFromSystemOrSystemUi()) {
+                    return;
+                }
             }
 
             // Only try to get the package UID if there's exactly one changed automatic rule. If
@@ -202,7 +216,8 @@
                 /* int32 package_uid = 7 */ mChangeState.getPackageUid(),
                 /* DNDPolicyProto current_policy = 8 */ mChangeState.getDNDPolicyProto(),
                 /* bool are_channels_bypassing = 9 */ mChangeState.getAreChannelsBypassing(),
-                /* ActiveRuleType active_rule_types = 10 */ mChangeState.getActiveRuleTypes());
+                /* ActiveRuleType active_rule_types = 10 */ mChangeState.getActiveRuleTypes(),
+                /* ChangeOrigin change_origin = 11 */ mChangeState.getChangeOrigin());
     }
 
     /**
@@ -235,7 +250,8 @@
         ZenModeConfig mPrevConfig, mNewConfig;
         NotificationManager.Policy mPrevPolicy, mNewPolicy;
         int mCallingUid = Process.INVALID_UID;
-        @ConfigChangeOrigin int mOrigin = ZenModeConfig.UPDATE_ORIGIN_UNKNOWN;
+        @ConfigChangeOrigin
+        int mOrigin = ZenModeConfig.UPDATE_ORIGIN_UNKNOWN;
 
         private void init(ZenModeInfo prevInfo, ZenModeInfo newInfo, int callingUid,
                 @ConfigChangeOrigin int origin) {
@@ -388,7 +404,8 @@
          * rules available.
          */
         @SuppressLint("WrongConstant")  // special case for log-only type on manual rule
-        @NonNull List<ZenRule> activeRulesList(ZenModeConfig config) {
+        @NonNull
+        List<ZenRule> activeRulesList(ZenModeConfig config) {
             ArrayList<ZenRule> rules = new ArrayList<>();
             if (config == null) {
                 return rules;
@@ -548,6 +565,17 @@
         }
 
         /**
+         * Get the config change origin associated with this change, which is stored in mOrigin.
+         * Only useable if modes_ui is true.
+         */
+        int getChangeOrigin() {
+            if (Flags.modesUi()) {
+                return mOrigin;
+            }
+            return 0;
+        }
+
+        /**
          * Convert the new policy to a DNDPolicyProto format for output in logs.
          *
          * <p>If {@code mNewZenMode} is {@code ZEN_MODE_OFF} (which can mean either no rules
diff --git a/services/core/java/com/android/server/power/LowPowerStandbyController.java b/services/core/java/com/android/server/power/LowPowerStandbyController.java
index fa94b43..421471e 100644
--- a/services/core/java/com/android/server/power/LowPowerStandbyController.java
+++ b/services/core/java/com/android/server/power/LowPowerStandbyController.java
@@ -1380,10 +1380,7 @@
             Slog.d(TAG, "notifyStandbyPortsChanged");
         }
 
-        final Intent intent = new Intent(PowerManager.ACTION_LOW_POWER_STANDBY_PORTS_CHANGED);
-        intent.addFlags(Intent.FLAG_RECEIVER_FOREGROUND);
-        mContext.sendBroadcastAsUser(intent, UserHandle.ALL,
-                Manifest.permission.MANAGE_LOW_POWER_STANDBY);
+        sendExplicitBroadcast(PowerManager.ACTION_LOW_POWER_STANDBY_PORTS_CHANGED);
     }
 
     /**
diff --git a/services/core/java/com/android/server/power/hint/HintManagerService.java b/services/core/java/com/android/server/power/hint/HintManagerService.java
index 7e27407..f6c3d8e 100644
--- a/services/core/java/com/android/server/power/hint/HintManagerService.java
+++ b/services/core/java/com/android/server/power/hint/HintManagerService.java
@@ -76,6 +76,7 @@
 import java.util.Map;
 import java.util.NoSuchElementException;
 import java.util.Objects;
+import java.util.PriorityQueue;
 import java.util.Set;
 import java.util.TreeMap;
 import java.util.concurrent.TimeUnit;
@@ -109,12 +110,31 @@
     @GuardedBy("mChannelMapLock")
     private ArrayMap<Integer, TreeMap<Integer, ChannelItem>> mChannelMap;
 
+    /*
+     * Multi-level map storing the session statistics since last pull from StatsD.
+     * The first level is keyed by the UID of the process owning the session.
+     * The second level is keyed by the tag of the session. The point of separating different
+     * tags is that since different categories (e.g. HWUI vs APP) of the sessions may have different
+     * behaviors.
+     */
+    @GuardedBy("mSessionSnapshotMapLock")
+    private ArrayMap<Integer, ArrayMap<Integer, AppHintSessionSnapshot>> mSessionSnapshotMap;
+
     /** Lock to protect mActiveSessions and the UidObserver. */
     private final Object mLock = new Object();
 
     /** Lock to protect mChannelMap. */
     private final Object mChannelMapLock = new Object();
 
+    /*
+     * Lock to protect mSessionSnapshotMap.
+     * Nested acquisition of mSessionSnapshotMapLock and mLock should be avoided.
+     * We should grab these separately.
+     * When we need to have nested acquisitions, we should always follow the order of acquiring
+     * mSessionSnapshotMapLock first then mLock.
+     */
+    private final Object mSessionSnapshotMapLock = new Object();
+
     @GuardedBy("mNonIsolatedTidsLock")
     private final Map<Integer, Set<Long>> mNonIsolatedTids;
 
@@ -135,6 +155,8 @@
     private int mPowerHalVersion;
     private final PackageManager mPackageManager;
 
+    private boolean mUsesFmq;
+
     private static final String PROPERTY_SF_ENABLE_CPU_HINT = "debug.sf.enable_adpf_cpu_hint";
     private static final String PROPERTY_HWUI_ENABLE_HINT_MANAGER = "debug.hwui.use_hint_manager";
 
@@ -162,6 +184,7 @@
         }
         mActiveSessions = new ArrayMap<>();
         mChannelMap = new ArrayMap<>();
+        mSessionSnapshotMap = new ArrayMap<>();
         mNativeWrapper = injector.createNativeWrapper();
         mNativeWrapper.halInit();
         mHintSessionPreferredRate = mNativeWrapper.halGetHintSessionPreferredRate();
@@ -170,6 +193,7 @@
                 LocalServices.getService(ActivityManagerInternal.class));
         mPowerHal = injector.createIPower();
         mPowerHalVersion = 0;
+        mUsesFmq = false;
         if (mPowerHal != null) {
             try {
                 mPowerHalVersion = mPowerHal.getInterfaceVersion();
@@ -197,6 +221,134 @@
         }
     }
 
+    private class AppHintSessionSnapshot {
+        /*
+         * Per-Uid and Per-SessionTag snapshot that tracks metrics including
+         * number of created sessions, number of power efficienct sessions, and
+         * maximum number of threads in a session.
+         * Given that it's Per-SessionTag, each uid can have multiple snapshots.
+         */
+        int mCurrentSessionCount;
+        int mMaxConcurrentSession;
+        int mMaxThreadCount;
+        int mPowerEfficientSessionCount;
+
+        final int mTargetDurationNsCountPQSize = 100;
+        PriorityQueue<TargetDurationRecord> mTargetDurationNsCountPQ;
+
+        class TargetDurationRecord implements Comparable<TargetDurationRecord> {
+            long mTargetDurationNs;
+            long mTimestamp;
+            int mCount;
+            TargetDurationRecord(long targetDurationNs) {
+                mTargetDurationNs = targetDurationNs;
+                mTimestamp = System.currentTimeMillis();
+                mCount = 1;
+            }
+
+            @Override
+            public int compareTo(TargetDurationRecord t) {
+                int tCount = t.getCount();
+                int thisCount = this.getCount();
+                // Here we sort in the order of number of count in ascending order.
+                // i.e. the lowest count of target duration is at the head of the queue.
+                // Upon same count, the tiebreaker is the timestamp, the older item will be at the
+                // front of the queue.
+                if (tCount == thisCount) {
+                    return (t.getTimestamp() < this.getTimestamp()) ? 1 : -1;
+                }
+                return (tCount < thisCount) ? 1 : -1;
+            }
+            long getTargetDurationNs() {
+                return mTargetDurationNs;
+            }
+
+            int getCount() {
+                return mCount;
+            }
+
+            long getTimestamp() {
+                return mTimestamp;
+            }
+
+            void setCount(int count) {
+                mCount = count;
+            }
+
+            void setTimestamp() {
+                mTimestamp = System.currentTimeMillis();
+            }
+
+            void setTargetDurationNs(long targetDurationNs) {
+                mTargetDurationNs = targetDurationNs;
+            }
+        }
+
+        AppHintSessionSnapshot() {
+            mCurrentSessionCount = 0;
+            mMaxConcurrentSession = 0;
+            mMaxThreadCount = 0;
+            mPowerEfficientSessionCount = 0;
+            mTargetDurationNsCountPQ = new PriorityQueue<>(1);
+        }
+
+        void updateUponSessionCreation(int threadCount, long targetDuration) {
+            mCurrentSessionCount += 1;
+            mMaxConcurrentSession = Math.max(mMaxConcurrentSession, mCurrentSessionCount);
+            mMaxThreadCount = Math.max(mMaxThreadCount, threadCount);
+            updateTargetDurationNs(targetDuration);
+        }
+
+        void updateUponSessionClose() {
+            mCurrentSessionCount -= 1;
+        }
+
+        void logPowerEfficientSession() {
+            mPowerEfficientSessionCount += 1;
+        }
+
+        void updateThreadCount(int threadCount) {
+            mMaxThreadCount = Math.max(mMaxThreadCount, threadCount);
+        }
+
+        void updateTargetDurationNs(long targetDurationNs) {
+            for (TargetDurationRecord t : mTargetDurationNsCountPQ) {
+                if (t.getTargetDurationNs() == targetDurationNs) {
+                    t.setCount(t.getCount() + 1);
+                    t.setTimestamp();
+                    return;
+                }
+            }
+            if (mTargetDurationNsCountPQ.size() == mTargetDurationNsCountPQSize) {
+                mTargetDurationNsCountPQ.poll();
+            }
+            mTargetDurationNsCountPQ.add(new TargetDurationRecord(targetDurationNs));
+        }
+
+        int getMaxConcurrentSession() {
+            return mMaxConcurrentSession;
+        }
+
+        int getMaxThreadCount() {
+            return mMaxThreadCount;
+        }
+
+        int getPowerEfficientSessionCount() {
+            return mPowerEfficientSessionCount;
+        }
+
+        long[] targetDurationNsList() {
+            final int listSize = 5;
+            long[] targetDurations = new long[listSize];
+            while (mTargetDurationNsCountPQ.size() > listSize) {
+                mTargetDurationNsCountPQ.poll();
+            }
+            for (int i = 0; i < listSize && !mTargetDurationNsCountPQ.isEmpty(); ++i) {
+                targetDurations[i] = mTargetDurationNsCountPQ.poll().getTargetDurationNs();
+            }
+            return targetDurations;
+        }
+    }
     private boolean isHalSupported() {
         return mHintSessionPreferredRate != -1;
     }
@@ -235,6 +387,11 @@
                 null, // use default PullAtomMetadata values
                 DIRECT_EXECUTOR,
                 this::onPullAtom);
+        statsManager.setPullAtomCallback(
+                FrameworkStatsLog.ADPF_SESSION_SNAPSHOT,
+                null, // use default PullAtomMetadata values
+                DIRECT_EXECUTOR,
+                this::onPullAtom);
     }
 
     private int onPullAtom(int atomTag, @NonNull List<StatsEvent> data) {
@@ -247,11 +404,82 @@
             data.add(FrameworkStatsLog.buildStatsEvent(
                     FrameworkStatsLog.ADPF_SYSTEM_COMPONENT_INFO,
                     isSurfaceFlingerUsingCpuHint,
-                    isHwuiHintManagerEnabled));
+                    isHwuiHintManagerEnabled,
+                    getFmqUsage()));
+        }
+        if (atomTag == FrameworkStatsLog.ADPF_SESSION_SNAPSHOT) {
+            synchronized (mSessionSnapshotMapLock) {
+                for (int i = 0; i < mSessionSnapshotMap.size(); ++i) {
+                    final int uid = mSessionSnapshotMap.keyAt(i);
+                    final ArrayMap<Integer, AppHintSessionSnapshot> sessionSnapshots =
+                            mSessionSnapshotMap.valueAt(i);
+                    for (int j = 0; j < sessionSnapshots.size(); ++j) {
+                        final int sessionTag = sessionSnapshots.keyAt(j);
+                        final AppHintSessionSnapshot sessionSnapshot = sessionSnapshots.valueAt(j);
+                        data.add(FrameworkStatsLog.buildStatsEvent(
+                                FrameworkStatsLog.ADPF_SESSION_SNAPSHOT,
+                                uid,
+                                sessionTag,
+                                sessionSnapshot.getMaxConcurrentSession(),
+                                sessionSnapshot.getMaxThreadCount(),
+                                sessionSnapshot.getPowerEfficientSessionCount(),
+                                sessionSnapshot.targetDurationNsList()
+                        ));
+                    }
+                }
+            }
+            restoreSessionSnapshot();
         }
         return android.app.StatsManager.PULL_SUCCESS;
     }
 
+    private int getFmqUsage() {
+        if (mUsesFmq) {
+            return FrameworkStatsLog.ADPFSYSTEM_COMPONENT_INFO__FMQ_SUPPORTED__SUPPORTED;
+        } else if (mPowerHalVersion < 5) {
+            return FrameworkStatsLog.ADPFSYSTEM_COMPONENT_INFO__FMQ_SUPPORTED__HAL_VERSION_NOT_MET;
+        } else {
+            return FrameworkStatsLog.ADPFSYSTEM_COMPONENT_INFO__FMQ_SUPPORTED__UNSUPPORTED;
+        }
+    }
+
+    private void restoreSessionSnapshot() {
+        // clean up snapshot map and rebuild with current active sessions
+        synchronized (mSessionSnapshotMapLock) {
+            mSessionSnapshotMap.clear();
+            synchronized (mLock) {
+                for (int i = 0; i < mActiveSessions.size(); i++) {
+                    ArrayMap<IBinder, ArraySet<AppHintSession>> tokenMap =
+                            mActiveSessions.valueAt(i);
+                    for (int j = 0; j < tokenMap.size(); j++) {
+                        ArraySet<AppHintSession> sessionSet = tokenMap.valueAt(j);
+                        for (int k = 0; k < sessionSet.size(); ++k) {
+                            AppHintSession appHintSession = sessionSet.valueAt(k);
+                            final int tag = appHintSession.getTag();
+                            final int uid = appHintSession.getUid();
+                            final long targetDuationNs =
+                                    appHintSession.getTargetDurationNs();
+                            final int threadCount = appHintSession.getThreadIds().length;
+                            ArrayMap<Integer, AppHintSessionSnapshot> snapshots =
+                                    mSessionSnapshotMap.get(uid);
+                            if (snapshots == null) {
+                                snapshots = new ArrayMap<>();
+                                mSessionSnapshotMap.put(uid, snapshots);
+                            }
+                            AppHintSessionSnapshot snapshot = snapshots.get(tag);
+                            if (snapshot == null) {
+                                snapshot = new AppHintSessionSnapshot();
+                                snapshots.put(tag, snapshot);
+                            }
+                            snapshot.updateUponSessionCreation(threadCount,
+                                    targetDuationNs);
+                        }
+                    }
+                }
+            }
+        }
+    }
+
     /**
      * Wrapper around the static-native methods from native.
      *
@@ -833,17 +1061,13 @@
                     // we change the session tag to SessionTag.GAME
                     // as it was not previously classified
                     switch (getUidApplicationCategory(callingUid)) {
-                        case ApplicationInfo.CATEGORY_GAME:
-                            tag = SessionTag.GAME;
-                            break;
-                        case ApplicationInfo.CATEGORY_UNDEFINED:
+                        case ApplicationInfo.CATEGORY_GAME -> tag = SessionTag.GAME;
+                        case ApplicationInfo.CATEGORY_UNDEFINED ->
                             // We use CATEGORY_UNDEFINED to filter the case when
                             // PackageManager.NameNotFoundException is caught,
                             // which should not happen.
                             tag = SessionTag.APP;
-                            break;
-                        default:
-                            tag = SessionTag.APP;
+                        default -> tag = SessionTag.APP;
                     }
                 }
 
@@ -889,9 +1113,15 @@
                 logPerformanceHintSessionAtom(
                         callingUid, sessionId, durationNanos, tids, tag);
 
+                synchronized (mSessionSnapshotMapLock) {
+                    // Update session snapshot upon session creation
+                    mSessionSnapshotMap.computeIfAbsent(callingUid, k -> new ArrayMap<>())
+                            .computeIfAbsent(tag, k -> new AppHintSessionSnapshot())
+                            .updateUponSessionCreation(tids.length, durationNanos);
+                }
                 synchronized (mLock) {
-                    AppHintSession hs = new AppHintSession(callingUid, callingTgid, tids, token,
-                            halSessionPtr, durationNanos);
+                    AppHintSession hs = new AppHintSession(callingUid, callingTgid, tag, tids,
+                            token, halSessionPtr, durationNanos);
                     ArrayMap<IBinder, ArraySet<AppHintSession>> tokenMap =
                             mActiveSessions.get(callingUid);
                     if (tokenMap == null) {
@@ -904,6 +1134,8 @@
                         tokenMap.put(token, sessionSet);
                     }
                     sessionSet.add(hs);
+                    mUsesFmq = mUsesFmq || hasChannel(callingTgid, callingUid);
+
                     return hs;
                 }
             } finally {
@@ -996,6 +1228,7 @@
     final class AppHintSession extends IHintSession.Stub implements IBinder.DeathRecipient {
         protected final int mUid;
         protected final int mPid;
+        protected final int mTag;
         protected int[] mThreadIds;
         protected final IBinder mToken;
         protected long mHalSessionPtr;
@@ -1003,6 +1236,7 @@
         protected boolean mUpdateAllowedByProcState;
         protected int[] mNewThreadIds;
         protected boolean mPowerEfficient;
+        protected boolean mHasBeenPowerEfficient;
         protected boolean mShouldForcePause;
 
         private enum SessionModes {
@@ -1010,16 +1244,18 @@
         };
 
         protected AppHintSession(
-                int uid, int pid, int[] threadIds, IBinder token,
+                int uid, int pid, int sessionTag, int[] threadIds, IBinder token,
                 long halSessionPtr, long durationNanos) {
             mUid = uid;
             mPid = pid;
+            mTag = sessionTag;
             mToken = token;
             mThreadIds = threadIds;
             mHalSessionPtr = halSessionPtr;
             mTargetDurationNanos = durationNanos;
             mUpdateAllowedByProcState = true;
             mPowerEfficient = false;
+            mHasBeenPowerEfficient = false;
             mShouldForcePause = false;
             final boolean allowed = mUidObserver.isUidForeground(mUid);
             updateHintAllowedByProcState(allowed);
@@ -1056,6 +1292,20 @@
                 mNativeWrapper.halUpdateTargetWorkDuration(mHalSessionPtr, targetDurationNanos);
                 mTargetDurationNanos = targetDurationNanos;
             }
+            synchronized (mSessionSnapshotMapLock) {
+                ArrayMap<Integer, AppHintSessionSnapshot> sessionSnapshots =
+                        mSessionSnapshotMap.get(mUid);
+                if (sessionSnapshots == null) {
+                    Slogf.w(TAG, "Session snapshot map is null for uid " + mUid);
+                    return;
+                }
+                AppHintSessionSnapshot sessionSnapshot = sessionSnapshots.get(mTag);
+                if (sessionSnapshot == null) {
+                    Slogf.w(TAG, "Session snapshot is null for uid " + mUid + " and tag " + mTag);
+                    return;
+                }
+                sessionSnapshot.updateTargetDurationNs(mTargetDurationNanos);
+            }
         }
 
         @Override
@@ -1108,6 +1358,20 @@
                 if (sessionSet.isEmpty()) tokenMap.remove(mToken);
                 if (tokenMap.isEmpty()) mActiveSessions.remove(mUid);
             }
+            synchronized (mSessionSnapshotMapLock) {
+                ArrayMap<Integer, AppHintSessionSnapshot> sessionSnapshots =
+                        mSessionSnapshotMap.get(mUid);
+                if (sessionSnapshots == null) {
+                    Slogf.w(TAG, "Session snapshot map is null for uid " + mUid);
+                    return;
+                }
+                AppHintSessionSnapshot sessionSnapshot = sessionSnapshots.get(mTag);
+                if (sessionSnapshot == null) {
+                    Slogf.w(TAG, "Session snapshot is null for uid " + mUid + " and tag " + mTag);
+                    return;
+                }
+                sessionSnapshot.updateUponSessionClose();
+            }
             if (powerhintThreadCleanup()) {
                 synchronized (mNonIsolatedTidsLock) {
                     final int[] tids = getTidsInternal();
@@ -1191,6 +1455,21 @@
                     mShouldForcePause = false;
                 }
             }
+            synchronized (mSessionSnapshotMapLock) {
+                ArrayMap<Integer, AppHintSessionSnapshot> sessionSnapshots =
+                        mSessionSnapshotMap.get(mUid);
+                if (sessionSnapshots == null) {
+                    Slogf.w(TAG, "Session snapshot map is null for uid " + mUid);
+                    return;
+                }
+                AppHintSessionSnapshot sessionSnapshot = sessionSnapshots.get(mTag);
+                if (sessionSnapshot == null) {
+                    Slogf.w(TAG, "Session snapshot is null for uid " + mUid + " and tag "
+                            + mTag);
+                    return;
+                }
+                sessionSnapshot.updateThreadCount(tids.length);
+            }
         }
 
         public int[] getThreadIds() {
@@ -1231,6 +1510,26 @@
                 }
                 mNativeWrapper.halSetMode(mHalSessionPtr, mode, enabled);
             }
+            if (enabled && (mode == SessionModes.POWER_EFFICIENCY.ordinal())) {
+                if (!mHasBeenPowerEfficient) {
+                    mHasBeenPowerEfficient = true;
+                    synchronized (mSessionSnapshotMapLock) {
+                        ArrayMap<Integer, AppHintSessionSnapshot> sessionSnapshots =
+                                mSessionSnapshotMap.get(mUid);
+                        if (sessionSnapshots == null) {
+                            Slogf.w(TAG, "Session snapshot map is null for uid " + mUid);
+                            return;
+                        }
+                        AppHintSessionSnapshot sessionSnapshot = sessionSnapshots.get(mTag);
+                        if (sessionSnapshot == null) {
+                            Slogf.w(TAG, "Session snapshot is null for uid " + mUid
+                                    + " and tag " + mTag);
+                            return;
+                        }
+                        sessionSnapshot.logPowerEfficientSession();
+                    }
+                }
+            }
         }
 
         @Override
@@ -1254,6 +1553,20 @@
             }
         }
 
+        public int getUid() {
+            return mUid;
+        }
+
+        public int getTag() {
+            return mTag;
+        }
+
+        public long getTargetDurationNs() {
+            synchronized (this) {
+                return mTargetDurationNanos;
+            }
+        }
+
         void validateWorkDuration(WorkDuration workDuration) {
             if (DEBUG) {
                 Slogf.d(TAG, "WorkDuration("
diff --git a/services/core/java/com/android/server/wm/ActivityTaskManagerService.java b/services/core/java/com/android/server/wm/ActivityTaskManagerService.java
index 5b17875..59b5da8 100644
--- a/services/core/java/com/android/server/wm/ActivityTaskManagerService.java
+++ b/services/core/java/com/android/server/wm/ActivityTaskManagerService.java
@@ -5183,6 +5183,10 @@
             String hostingType) {
         if (!mStartingProcessActivities.contains(activity)) {
             mStartingProcessActivities.add(activity);
+            // Let the activity with higher z-order be started first.
+            if (mStartingProcessActivities.size() > 1) {
+                mStartingProcessActivities.sort(null /* by WindowContainer#compareTo */);
+            }
         } else if (mProcessNames.get(
                 activity.processName, activity.info.applicationInfo.uid) != null) {
             // The process is already starting. Wait for it to attach.
diff --git a/services/core/java/com/android/server/wm/BackgroundActivityStartController.java b/services/core/java/com/android/server/wm/BackgroundActivityStartController.java
index 3f4bda7..54024e9 100644
--- a/services/core/java/com/android/server/wm/BackgroundActivityStartController.java
+++ b/services/core/java/com/android/server/wm/BackgroundActivityStartController.java
@@ -128,21 +128,24 @@
 
     // TODO(b/263368846) Rename when ASM logic is moved in
     @Retention(SOURCE)
-    @IntDef({BAL_BLOCK,
-            BAL_ALLOW_DEFAULT,
-            BAL_ALLOW_ALLOWLISTED_UID,
+    @IntDef({
             BAL_ALLOW_ALLOWLISTED_COMPONENT,
-            BAL_ALLOW_VISIBLE_WINDOW,
+            BAL_ALLOW_ALLOWLISTED_UID,
+            BAL_ALLOW_BOUND_BY_FOREGROUND,
+            BAL_ALLOW_DEFAULT,
+            BAL_ALLOW_FOREGROUND,
+            BAL_ALLOW_GRACE_PERIOD,
             BAL_ALLOW_PENDING_INTENT,
             BAL_ALLOW_PERMISSION,
             BAL_ALLOW_SAW_PERMISSION,
-            BAL_ALLOW_GRACE_PERIOD,
-            BAL_ALLOW_FOREGROUND,
-            BAL_ALLOW_SDK_SANDBOX
+            BAL_ALLOW_SDK_SANDBOX,
+            BAL_ALLOW_TOKEN,
+            BAL_ALLOW_VISIBLE_WINDOW,
+            BAL_BLOCK
     })
     public @interface BalCode {}
 
-    static final int BAL_BLOCK = 0;
+    static final int BAL_BLOCK = FrameworkStatsLog.BAL_ALLOWED__ALLOWED_REASON__BAL_BLOCKED;
 
     static final int BAL_ALLOW_DEFAULT =
             FrameworkStatsLog.BAL_ALLOWED__ALLOWED_REASON__BAL_ALLOW_DEFAULT;
@@ -195,10 +198,19 @@
     static final int BAL_ALLOW_NON_APP_VISIBLE_WINDOW =
             FrameworkStatsLog.BAL_ALLOWED__ALLOWED_REASON__BAL_ALLOW_NON_APP_VISIBLE_WINDOW;
 
+    /** Process belongs to a SDK sandbox */
+    static final int BAL_ALLOW_TOKEN =
+            FrameworkStatsLog.BAL_ALLOWED__ALLOWED_REASON__BAL_ALLOW_TOKEN;
+
+    /** Process belongs to a SDK sandbox */
+    static final int BAL_ALLOW_BOUND_BY_FOREGROUND =
+            FrameworkStatsLog.BAL_ALLOWED__ALLOWED_REASON__BAL_ALLOW_BOUND_BY_FOREGROUND;
+
     static String balCodeToString(@BalCode int balCode) {
         return switch (balCode) {
             case BAL_ALLOW_ALLOWLISTED_COMPONENT -> "BAL_ALLOW_ALLOWLISTED_COMPONENT";
             case BAL_ALLOW_ALLOWLISTED_UID -> "BAL_ALLOW_ALLOWLISTED_UID";
+            case BAL_ALLOW_BOUND_BY_FOREGROUND -> "BAL_ALLOW_BOUND_BY_FOREGROUND";
             case BAL_ALLOW_DEFAULT -> "BAL_ALLOW_DEFAULT";
             case BAL_ALLOW_FOREGROUND -> "BAL_ALLOW_FOREGROUND";
             case BAL_ALLOW_GRACE_PERIOD -> "BAL_ALLOW_GRACE_PERIOD";
@@ -207,6 +219,7 @@
             case BAL_ALLOW_PERMISSION -> "BAL_ALLOW_PERMISSION";
             case BAL_ALLOW_SAW_PERMISSION -> "BAL_ALLOW_SAW_PERMISSION";
             case BAL_ALLOW_SDK_SANDBOX -> "BAL_ALLOW_SDK_SANDBOX";
+            case BAL_ALLOW_TOKEN -> "BAL_ALLOW_TOKEN";
             case BAL_ALLOW_VISIBLE_WINDOW -> "BAL_ALLOW_VISIBLE_WINDOW";
             case BAL_BLOCK -> "BAL_BLOCK";
             default -> throw new IllegalArgumentException("Unexpected value: " + balCode);
@@ -1042,7 +1055,9 @@
                     || balCode == BAL_ALLOW_PENDING_INTENT
                     || balCode == BAL_ALLOW_SAW_PERMISSION
                     || balCode == BAL_ALLOW_VISIBLE_WINDOW
-                    || balCode == BAL_ALLOW_NON_APP_VISIBLE_WINDOW) {
+                    || balCode == BAL_ALLOW_NON_APP_VISIBLE_WINDOW
+                    || balCode == BAL_ALLOW_TOKEN
+                    || balCode == BAL_ALLOW_BOUND_BY_FOREGROUND) {
                 return true;
             }
         }
@@ -1266,7 +1281,8 @@
                 || balCode == BAL_ALLOW_PERMISSION
                 || balCode == BAL_ALLOW_SAW_PERMISSION
                 || balCode == BAL_ALLOW_VISIBLE_WINDOW
-                || balCode == BAL_ALLOW_NON_APP_VISIBLE_WINDOW) {
+                || balCode == BAL_ALLOW_NON_APP_VISIBLE_WINDOW
+                || balCode == BAL_ALLOW_BOUND_BY_FOREGROUND) {
             return;
         }
 
@@ -1572,7 +1588,7 @@
         }
 
         if (balCode == BAL_ALLOW_VISIBLE_WINDOW || balCode == BAL_ALLOW_NON_APP_VISIBLE_WINDOW
-                || balCode == BAL_ALLOW_FOREGROUND) {
+                || balCode == BAL_ALLOW_FOREGROUND || balCode == BAL_ALLOW_BOUND_BY_FOREGROUND) {
             Task task = sourceRecord != null ? sourceRecord.getTask() : targetTask;
             if (task != null && task.getDisplayArea() != null) {
                 joiner.add(prefix + "Tasks: ");
diff --git a/services/core/java/com/android/server/wm/BackgroundLaunchProcessController.java b/services/core/java/com/android/server/wm/BackgroundLaunchProcessController.java
index 478524b..4a870a3 100644
--- a/services/core/java/com/android/server/wm/BackgroundLaunchProcessController.java
+++ b/services/core/java/com/android/server/wm/BackgroundLaunchProcessController.java
@@ -23,10 +23,13 @@
 import static com.android.server.wm.ActivityTaskManagerService.ACTIVITY_BG_START_GRACE_PERIOD_MS;
 import static com.android.server.wm.ActivityTaskManagerService.APP_SWITCH_ALLOW;
 import static com.android.server.wm.ActivityTaskManagerService.APP_SWITCH_DISALLOW;
+import static com.android.server.wm.BackgroundActivityStartController.BAL_ALLOW_BOUND_BY_FOREGROUND;
 import static com.android.server.wm.BackgroundActivityStartController.BAL_ALLOW_FOREGROUND;
 import static com.android.server.wm.BackgroundActivityStartController.BAL_ALLOW_GRACE_PERIOD;
 import static com.android.server.wm.BackgroundActivityStartController.BAL_ALLOW_PERMISSION;
+import static com.android.server.wm.BackgroundActivityStartController.BAL_ALLOW_TOKEN;
 import static com.android.server.wm.BackgroundActivityStartController.BAL_ALLOW_VISIBLE_WINDOW;
+import static com.android.window.flags.Flags.balImprovedMetrics;
 
 import static java.util.Objects.requireNonNull;
 
@@ -110,8 +113,8 @@
         }
         // Allow if the flag was explicitly set.
         if (isBackgroundStartAllowedByToken(uid, packageName, isCheckingForFgsStart)) {
-            return new BalVerdict(BAL_ALLOW_PERMISSION, /*background*/ true,
-                    "process allowed by token");
+            return new BalVerdict(balImprovedMetrics() ? BAL_ALLOW_TOKEN : BAL_ALLOW_PERMISSION,
+                    /*background*/ true, "process allowed by token");
         }
         // Allow if the caller is bound by a UID that's currently foreground.
         // But still respect the appSwitchState.
@@ -120,7 +123,8 @@
                 ? appSwitchState != APP_SWITCH_DISALLOW && isBoundByForegroundUid()
                 : isBoundByForegroundUid();
         if (allowBoundByForegroundUid) {
-            return new BalVerdict(BAL_ALLOW_VISIBLE_WINDOW, /*background*/ false,
+            return new BalVerdict(balImprovedMetrics() ? BAL_ALLOW_BOUND_BY_FOREGROUND
+                    : BAL_ALLOW_VISIBLE_WINDOW, /*background*/ false,
                     "process bound by foreground uid");
         }
         // Allow if the caller has an activity in any foreground task.
diff --git a/services/core/java/com/android/server/wm/WindowContainer.java b/services/core/java/com/android/server/wm/WindowContainer.java
index 4da8bbf..eb1a80b 100644
--- a/services/core/java/com/android/server/wm/WindowContainer.java
+++ b/services/core/java/com/android/server/wm/WindowContainer.java
@@ -2583,8 +2583,12 @@
 
             // Containers don't belong to the same hierarchy???
             if (commonAncestor == null) {
-                throw new IllegalArgumentException("No in the same hierarchy this="
-                        + thisParentChain + " other=" + otherParentChain);
+                final int thisZ = getPrefixOrderIndex();
+                final int otherZ = other.getPrefixOrderIndex();
+                Slog.w(TAG, "Compare not in the same hierarchy this="
+                        + thisParentChain + " thisZ=" + thisZ + " other="
+                        + otherParentChain + " otherZ=" + otherZ);
+                return Integer.compare(thisZ, otherZ);
             }
 
             // Children are always considered greater than their parents, so if one of the containers
diff --git a/services/tests/InputMethodSystemServerTests/src/com/android/server/inputmethod/InputMethodManagerServiceTestBase.java b/services/tests/InputMethodSystemServerTests/src/com/android/server/inputmethod/InputMethodManagerServiceTestBase.java
index 6a86379..961fb9a7 100644
--- a/services/tests/InputMethodSystemServerTests/src/com/android/server/inputmethod/InputMethodManagerServiceTestBase.java
+++ b/services/tests/InputMethodSystemServerTests/src/com/android/server/inputmethod/InputMethodManagerServiceTestBase.java
@@ -25,6 +25,7 @@
 import static org.mockito.ArgumentMatchers.any;
 import static org.mockito.ArgumentMatchers.anyBoolean;
 import static org.mockito.ArgumentMatchers.anyInt;
+import static org.mockito.ArgumentMatchers.anyList;
 import static org.mockito.ArgumentMatchers.anyLong;
 import static org.mockito.ArgumentMatchers.anyString;
 import static org.mockito.ArgumentMatchers.notNull;
@@ -38,6 +39,7 @@
 import android.content.BroadcastReceiver;
 import android.content.Context;
 import android.content.IntentFilter;
+import android.content.pm.PackageManager;
 import android.content.pm.PackageManagerInternal;
 import android.content.res.Configuration;
 import android.hardware.input.IInputManager;
@@ -157,6 +159,7 @@
                 mockitoSession()
                         .initMocks(this)
                         .strictness(Strictness.LENIENT)
+                        .spyStatic(InputMethodUtils.class)
                         .mockStatic(ServiceManager.class)
                         .mockStatic(SystemServerInitThreadPool.class)
                         .startMocking();
@@ -227,6 +230,10 @@
                 .thenReturn(TEST_IME_TARGET_INFO);
         when(mMockInputMethodClient.asBinder()).thenReturn(mMockInputMethodBinder);
 
+        // This changes the real IME component state. Not appropriate to do in tests.
+        doNothing().when(() -> InputMethodUtils.setNonSelectedSystemImesDisabledUntilUsed(
+                        any(PackageManager.class), anyList()));
+
         // Used by lazy initializing draw IMS nav bar at InputMethodManagerService#systemRunning(),
         // which is ok to be mocked out for now.
         doReturn(null).when(() -> SystemServerInitThreadPool.submit(any(), anyString()));
@@ -243,9 +250,13 @@
                         Process.THREAD_PRIORITY_FOREGROUND,
                         true /* allowIo */);
         mIoThread.start();
+
+        final var ioHandler = spy(Handler.createAsync(mIoThread.getLooper()));
+        doReturn(true).when(ioHandler).post(any());
+
         mInputMethodManagerService = new InputMethodManagerService(mContext,
                 InputMethodManagerService.shouldEnableConcurrentMultiUserMode(mContext),
-                mServiceThread.getLooper(), Handler.createAsync(mIoThread.getLooper()),
+                mServiceThread.getLooper(), ioHandler,
                 unusedUserId -> mMockInputMethodBindingController);
         spyOn(mInputMethodManagerService);
 
diff --git a/services/tests/InputMethodSystemServerTests/src/com/android/server/inputmethod/UserDataRepositoryTest.java b/services/tests/InputMethodSystemServerTests/src/com/android/server/inputmethod/UserDataRepositoryTest.java
index 9ca4f1d..81fb1a0 100644
--- a/services/tests/InputMethodSystemServerTests/src/com/android/server/inputmethod/UserDataRepositoryTest.java
+++ b/services/tests/InputMethodSystemServerTests/src/com/android/server/inputmethod/UserDataRepositoryTest.java
@@ -18,23 +18,12 @@
 
 import static com.google.common.truth.Truth.assertThat;
 
-import static org.mockito.Mockito.spy;
-import static org.mockito.Mockito.times;
-import static org.mockito.Mockito.verify;
-
-import android.content.pm.UserInfo;
-import android.os.ConditionVariable;
-import android.os.Handler;
-import android.os.Looper;
 import android.platform.test.ravenwood.RavenwoodRule;
 
-import com.android.server.pm.UserManagerInternal;
-
 import org.junit.After;
 import org.junit.Before;
 import org.junit.Rule;
 import org.junit.Test;
-import org.mockito.ArgumentCaptor;
 import org.mockito.Mock;
 import org.mockito.MockitoAnnotations;
 
@@ -52,13 +41,8 @@
             .setProvideMainThread(true).build();
 
     @Mock
-    private UserManagerInternal mMockUserManagerInternal;
-
-    @Mock
     private InputMethodManagerService mMockInputMethodManagerService;
 
-    private Handler mHandler;
-
     private IntFunction<InputMethodBindingController> mBindingControllerFactory;
 
     @Before
@@ -66,7 +50,6 @@
         MockitoAnnotations.initMocks(this);
         SecureSettingsWrapper.startTestMode();
 
-        mHandler = new Handler(Looper.getMainLooper());
         mBindingControllerFactory = new IntFunction<InputMethodBindingController>() {
 
             @Override
@@ -81,51 +64,20 @@
         SecureSettingsWrapper.endTestMode();
     }
 
-    @Test
-    public void testUserDataRepository_addsNewUserInfoOnUserCreatedEvent() {
-        // Create UserDataRepository and capture the user lifecycle listener
-        final var captor = ArgumentCaptor.forClass(UserManagerInternal.UserLifecycleListener.class);
-        final var bindingControllerFactorySpy = spy(mBindingControllerFactory);
-        final var repository = new UserDataRepository(mHandler,
-                mMockUserManagerInternal, bindingControllerFactorySpy);
-
-        verify(mMockUserManagerInternal, times(1)).addUserLifecycleListener(captor.capture());
-        final var listener = captor.getValue();
-
-        // Assert that UserDataRepository is empty and then call onUserCreated
-        assertThat(collectUserData(repository)).isEmpty();
-        final var userInfo = new UserInfo();
-        userInfo.id = ANY_USER_ID;
-        listener.onUserCreated(userInfo, /* unused token */ new Object());
-        waitForIdle();
-
-        // Assert UserDataRepository remains to be empty.
-        assertThat(collectUserData(repository)).isEmpty();
-    }
-
+    // TODO(b/352615651): Move this to end-to-end test.
     @Test
     public void testUserDataRepository_removesUserInfoOnUserRemovedEvent() {
-        // Create UserDataRepository and capture the user lifecycle listener
-        final var captor = ArgumentCaptor.forClass(UserManagerInternal.UserLifecycleListener.class);
-        final var repository = new UserDataRepository(mHandler,
-                mMockUserManagerInternal,
+        // Create UserDataRepository
+        final var repository = new UserDataRepository(
                 userId -> new InputMethodBindingController(userId, mMockInputMethodManagerService));
 
-        verify(mMockUserManagerInternal, times(1)).addUserLifecycleListener(captor.capture());
-        final var listener = captor.getValue();
-
         // Add one UserData ...
-        synchronized (ImfLock.class) {
-            final var userData = repository.getOrCreate(ANY_USER_ID);
-            assertThat(userData.mUserId).isEqualTo(ANY_USER_ID);
-        }
+        final var userData = repository.getOrCreate(ANY_USER_ID);
+        assertThat(userData.mUserId).isEqualTo(ANY_USER_ID);
 
         // ... and then call onUserRemoved
         assertThat(collectUserData(repository)).hasSize(1);
-        final var userInfo = new UserInfo();
-        userInfo.id = ANY_USER_ID;
-        listener.onUserRemoved(userInfo);
-        waitForIdle();
+        repository.remove(ANY_USER_ID);
 
         // Assert UserDataRepository is now empty
         assertThat(collectUserData(repository)).isEmpty();
@@ -133,13 +85,10 @@
 
     @Test
     public void testGetOrCreate() {
-        final var repository = new UserDataRepository(mHandler,
-                mMockUserManagerInternal, mBindingControllerFactory);
+        final var repository = new UserDataRepository(mBindingControllerFactory);
 
-        synchronized (ImfLock.class) {
-            final var userData = repository.getOrCreate(ANY_USER_ID);
-            assertThat(userData.mUserId).isEqualTo(ANY_USER_ID);
-        }
+        final var userData = repository.getOrCreate(ANY_USER_ID);
+        assertThat(userData.mUserId).isEqualTo(ANY_USER_ID);
 
         final var allUserData = collectUserData(repository);
         assertThat(allUserData).hasSize(1);
@@ -151,15 +100,8 @@
 
     private List<UserDataRepository.UserData> collectUserData(UserDataRepository repository) {
         final var collected = new ArrayList<UserDataRepository.UserData>();
-        synchronized (ImfLock.class) {
-            repository.forAllUserData(userData -> collected.add(userData));
-        }
+        repository.forAllUserData(userData -> collected.add(userData));
         return collected;
     }
 
-    private void waitForIdle() {
-        final var done = new ConditionVariable();
-        mHandler.post(done::open);
-        done.block();
-    }
 }
diff --git a/services/tests/mockingservicestests/src/com/android/server/am/ActivityManagerServiceTest.java b/services/tests/mockingservicestests/src/com/android/server/am/ActivityManagerServiceTest.java
index 419bcb8..e610a32 100644
--- a/services/tests/mockingservicestests/src/com/android/server/am/ActivityManagerServiceTest.java
+++ b/services/tests/mockingservicestests/src/com/android/server/am/ActivityManagerServiceTest.java
@@ -527,7 +527,7 @@
 
         final ProcessRecord appRec = new ProcessRecord(mAms, info, info.processName, uid);
         final ProcessStatsService tracker = mAms.mProcessStats;
-        final IApplicationThread appThread = mock(IApplicationThread.class);
+        final ApplicationThreadDeferred appThread = mock(ApplicationThreadDeferred.class);
         doReturn(mock(IBinder.class)).when(appThread).asBinder();
         appRec.makeActive(appThread, tracker);
         mAms.mProcessList.addProcessNameLocked(appRec);
@@ -701,7 +701,8 @@
         final var wpc = fifoProc.getWindowProcessController();
         spyOn(wpc);
         doReturn(true).when(wpc).useFifoUiScheduling();
-        fifoProc.makeActive(fifoProc.getThread(), mAms.mProcessStats);
+        fifoProc.makeActive(new ApplicationThreadDeferred(fifoProc.getThread()),
+                mAms.mProcessStats);
         assertTrue(fifoProc.useFifoUiScheduling());
         assertTrue(mAms.mSpecifiedFifoProcesses.contains(fifoProc));
 
diff --git a/services/tests/mockingservicestests/src/com/android/server/am/ApplicationThreadDeferredTest.java b/services/tests/mockingservicestests/src/com/android/server/am/ApplicationThreadDeferredTest.java
new file mode 100644
index 0000000..8f8c1ac
--- /dev/null
+++ b/services/tests/mockingservicestests/src/com/android/server/am/ApplicationThreadDeferredTest.java
@@ -0,0 +1,99 @@
+/*
+ * Copyright (C) 2024 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.server.am;
+
+import static org.mockito.Mockito.mock;
+import static org.mockito.Mockito.never;
+import static org.mockito.Mockito.times;
+import static org.mockito.Mockito.verify;
+
+import android.app.IApplicationThread;
+
+import androidx.test.filters.SmallTest;
+
+import org.junit.Test;
+
+
+/**
+ * Tests to verify that the ApplicationThreadDeferred properly defers binder calls to paused
+ * processes.
+ */
+@SmallTest
+public class ApplicationThreadDeferredTest {
+    private static final String TAG = "ApplicationThreadDeferredTest";
+
+    private void callDeferredApis(IApplicationThread thread) throws Exception {
+        thread.clearDnsCache();
+        thread.updateHttpProxy();
+        thread.updateTimeZone();
+        thread.scheduleLowMemory();
+    }
+
+    // Verify that the special APIs have been called count times.
+    private void verifyDeferredApis(IApplicationThread thread, int count) throws Exception {
+        verify(thread, times(count)).clearDnsCache();
+        verify(thread, times(count)).updateHttpProxy();
+        verify(thread, times(count)).updateTimeZone();
+        verify(thread, times(count)).scheduleLowMemory();
+    }
+
+    // Test the baseline behavior of IApplicationThread.  If this test fails, all other tests are
+    // suspect.
+    @Test
+    public void testBaseline() throws Exception {
+        IApplicationThread thread = mock(IApplicationThread.class);
+        callDeferredApis(thread);
+        verifyDeferredApis(thread, 1);
+    }
+
+    // Test the baseline behavior of IApplicationThreadDeferred.  If this test fails, all other
+    // tests are suspect.
+    @Test
+    public void testBaselineDeferred() throws Exception {
+        IApplicationThread thread = mock(ApplicationThreadDeferred.class);
+        callDeferredApis(thread);
+        verifyDeferredApis(thread, 1);
+    }
+
+    // Verify that a deferred thread behaves like a regular thread when it is not paused.
+    @Test
+    public void testDeferredUnpaused() throws Exception {
+        IApplicationThread base = mock(IApplicationThread.class);
+        ApplicationThreadDeferred thread = new ApplicationThreadDeferred(base, true);
+        callDeferredApis(thread);
+        verifyDeferredApis(base, 1);
+    }
+
+    // Verify that a paused deferred thread thread does not deliver any calls to its parent.  Then
+    // unpause the thread and verify that the collapsed calls are forwarded.
+    @Test
+    public void testDeferredPaused() throws Exception {
+        IApplicationThread base = mock(IApplicationThread.class);
+        ApplicationThreadDeferred thread = new ApplicationThreadDeferred(base, true);
+        thread.onProcessPaused();
+        callDeferredApis(thread);
+        callDeferredApis(thread);
+        verifyDeferredApis(base, 0);
+        thread.onProcessUnpaused();
+        verifyDeferredApis(base, 1);
+    }
+
+    // TODO: [b/302724778] Remove manual JNI load
+    static {
+        System.loadLibrary("mockingservicestestjni");
+    }
+}
diff --git a/services/tests/mockingservicestests/src/com/android/server/am/AsyncProcessStartTest.java b/services/tests/mockingservicestests/src/com/android/server/am/AsyncProcessStartTest.java
index 80f7a06..93066d8 100644
--- a/services/tests/mockingservicestests/src/com/android/server/am/AsyncProcessStartTest.java
+++ b/services/tests/mockingservicestests/src/com/android/server/am/AsyncProcessStartTest.java
@@ -189,7 +189,7 @@
 
     private ProcessRecord makeActiveProcessRecord(ApplicationInfo ai, boolean wedge)
             throws Exception {
-        final IApplicationThread thread = mock(IApplicationThread.class);
+        final ApplicationThreadDeferred thread = mock(ApplicationThreadDeferred.class);
         final IBinder threadBinder = new Binder();
         doReturn(threadBinder).when(thread).asBinder();
         doAnswer((invocation) -> {
diff --git a/services/tests/mockingservicestests/src/com/android/server/am/BroadcastQueueTest.java b/services/tests/mockingservicestests/src/com/android/server/am/BroadcastQueueTest.java
index 13ba1e5..3aaf2e5 100644
--- a/services/tests/mockingservicestests/src/com/android/server/am/BroadcastQueueTest.java
+++ b/services/tests/mockingservicestests/src/com/android/server/am/BroadcastQueueTest.java
@@ -325,13 +325,13 @@
         ProcessRecord.updateProcessRecordNodes(r);
         mActiveProcesses.add(r);
 
-        final IApplicationThread thread;
+        final ApplicationThreadDeferred thread;
         if (dead) {
-            thread = mock(IApplicationThread.class, (invocation) -> {
+            thread = mock(ApplicationThreadDeferred.class, (invocation) -> {
                 throw new DeadObjectException();
             });
         } else {
-            thread = mock(IApplicationThread.class);
+            thread = mock(ApplicationThreadDeferred.class);
         }
         final IBinder threadBinder = new Binder();
         doReturn(threadBinder).when(thread).asBinder();
diff --git a/services/tests/mockingservicestests/src/com/android/server/am/CacheOomRankerTest.java b/services/tests/mockingservicestests/src/com/android/server/am/CacheOomRankerTest.java
index 240ddf5..0796f41 100644
--- a/services/tests/mockingservicestests/src/com/android/server/am/CacheOomRankerTest.java
+++ b/services/tests/mockingservicestests/src/com/android/server/am/CacheOomRankerTest.java
@@ -760,7 +760,7 @@
         ProcessStatsService processStatsService = new ProcessStatsService(
                 mock(ActivityManagerService.class), new File(Environment.getDataSystemCeDirectory(),
                 "procstats"));
-        app.makeActive(mock(IApplicationThread.class), processStatsService);
+        app.makeActive(mock(ApplicationThreadDeferred.class), processStatsService);
         return app;
     }
 
diff --git a/services/tests/mockingservicestests/src/com/android/server/am/MockingOomAdjusterTests.java b/services/tests/mockingservicestests/src/com/android/server/am/MockingOomAdjusterTests.java
index 6366f24..1dbd532 100644
--- a/services/tests/mockingservicestests/src/com/android/server/am/MockingOomAdjusterTests.java
+++ b/services/tests/mockingservicestests/src/com/android/server/am/MockingOomAdjusterTests.java
@@ -3209,7 +3209,7 @@
             final ProcessReceiverRecord receivers = app.mReceivers;
             final ProcessProfileRecord profile = app.mProfile;
             final ProcessProviderRecord providers = app.mProviders;
-            app.makeActive(mock(IApplicationThread.class), mService.mProcessStats);
+            app.makeActive(mock(ApplicationThreadDeferred.class), mService.mProcessStats);
             app.setLastActivityTime(mLastActivityTime);
             app.setKilledByAm(mKilledByAm);
             app.setIsolatedEntryPoint(mIsolatedEntryPoint);
diff --git a/services/tests/mockingservicestests/src/com/android/server/am/ProcessObserverTest.java b/services/tests/mockingservicestests/src/com/android/server/am/ProcessObserverTest.java
index 89c67d5..3572d23 100644
--- a/services/tests/mockingservicestests/src/com/android/server/am/ProcessObserverTest.java
+++ b/services/tests/mockingservicestests/src/com/android/server/am/ProcessObserverTest.java
@@ -193,7 +193,7 @@
 
     private ProcessRecord makeActiveProcessRecord(ApplicationInfo ai)
             throws Exception {
-        final IApplicationThread thread = mock(IApplicationThread.class);
+        final ApplicationThreadDeferred thread = mock(ApplicationThreadDeferred.class);
         final IBinder threadBinder = new Binder();
         doReturn(threadBinder).when(thread).asBinder();
         doAnswer((invocation) -> {
diff --git a/services/tests/mockingservicestests/src/com/android/server/am/ServiceBindingOomAdjPolicyTest.java b/services/tests/mockingservicestests/src/com/android/server/am/ServiceBindingOomAdjPolicyTest.java
index 5f12677..1ff4a27 100644
--- a/services/tests/mockingservicestests/src/com/android/server/am/ServiceBindingOomAdjPolicyTest.java
+++ b/services/tests/mockingservicestests/src/com/android/server/am/ServiceBindingOomAdjPolicyTest.java
@@ -529,7 +529,7 @@
     @SuppressWarnings("GuardedBy")
     private ProcessRecord addProcessRecord(int pid, int uid, int procState, int adj, int cap,
                 String packageName) {
-        final IApplicationThread appThread = mock(IApplicationThread.class);
+        final ApplicationThreadDeferred appThread = mock(ApplicationThreadDeferred.class);
         final IBinder threadBinder = mock(IBinder.class);
         final ProcessRecord app = makeProcessRecord(pid, uid, uid, null, 0,
                 procState, adj, cap, 0L, 0L, packageName, packageName, mAms);
diff --git a/services/tests/mockingservicestests/src/com/android/server/am/ServiceTimeoutTest.java b/services/tests/mockingservicestests/src/com/android/server/am/ServiceTimeoutTest.java
index 7ec27be..3c43636 100644
--- a/services/tests/mockingservicestests/src/com/android/server/am/ServiceTimeoutTest.java
+++ b/services/tests/mockingservicestests/src/com/android/server/am/ServiceTimeoutTest.java
@@ -145,7 +145,7 @@
                 name,                  // processName
                 name,                  // packageName
                 mAms);
-        app.makeActive(mock(IApplicationThread.class), mAms.mProcessStats);
+        app.makeActive(mock(ApplicationThreadDeferred.class), mAms.mProcessStats);
         mProcessList.updateLruProcessLocked(app, false, null);
 
         final long now = SystemClock.uptimeMillis();
diff --git a/services/tests/servicestests/src/com/android/server/power/hint/HintManagerServiceTest.java b/services/tests/servicestests/src/com/android/server/power/hint/HintManagerServiceTest.java
index bf47816..1decd36 100644
--- a/services/tests/servicestests/src/com/android/server/power/hint/HintManagerServiceTest.java
+++ b/services/tests/servicestests/src/com/android/server/power/hint/HintManagerServiceTest.java
@@ -151,7 +151,6 @@
 
     private HintManagerService mService;
     private ChannelConfig mConfig;
-    private ApplicationInfo mApplicationInfo;
 
     private static Answer<Long> fakeCreateWithConfig(Long ptr, Long sessionId) {
         return new Answer<Long>() {
@@ -168,12 +167,12 @@
         mConfig = new ChannelConfig();
         mConfig.readFlagBitmask = 1;
         mConfig.writeFlagBitmask = 2;
-        mApplicationInfo = new ApplicationInfo();
-        mApplicationInfo.category = ApplicationInfo.CATEGORY_GAME;
+        ApplicationInfo applicationInfo = new ApplicationInfo();
+        applicationInfo.category = ApplicationInfo.CATEGORY_GAME;
         when(mContext.getPackageManager()).thenReturn(mMockPackageManager);
         when(mMockPackageManager.getNameForUid(anyInt())).thenReturn(TEST_APP_NAME);
         when(mMockPackageManager.getApplicationInfo(eq(TEST_APP_NAME), anyInt()))
-                .thenReturn(mApplicationInfo);
+                .thenReturn(applicationInfo);
         when(mNativeWrapperMock.halGetHintSessionPreferredRate())
                 .thenReturn(DEFAULT_HINT_PREFERRED_RATE);
         when(mNativeWrapperMock.halCreateHintSession(eq(TGID), eq(UID), eq(SESSION_TIDS_A),
diff --git a/services/tests/uiservicestests/src/com/android/server/notification/ZenModeEventLoggerFake.java b/services/tests/uiservicestests/src/com/android/server/notification/ZenModeEventLoggerFake.java
index ff1308c..1c8cb8f 100644
--- a/services/tests/uiservicestests/src/com/android/server/notification/ZenModeEventLoggerFake.java
+++ b/services/tests/uiservicestests/src/com/android/server/notification/ZenModeEventLoggerFake.java
@@ -137,4 +137,9 @@
         checkInRange(i);
         return mChanges.get(i).getActiveRuleTypes();
     }
+
+    public int getChangeOrigin(int i) throws IllegalArgumentException {
+        checkInRange(i);
+        return mChanges.get(i).getChangeOrigin();
+    }
 }
diff --git a/services/tests/uiservicestests/src/com/android/server/notification/ZenModeHelperTest.java b/services/tests/uiservicestests/src/com/android/server/notification/ZenModeHelperTest.java
index 7bb633e..4bbbc2b 100644
--- a/services/tests/uiservicestests/src/com/android/server/notification/ZenModeHelperTest.java
+++ b/services/tests/uiservicestests/src/com/android/server/notification/ZenModeHelperTest.java
@@ -818,7 +818,7 @@
         // 1. Current ringer is normal
         when(mAudioManager.getRingerModeInternal()).thenReturn(AudioManager.RINGER_MODE_NORMAL);
         // Set zen to priority-only with all notification sounds muted (so ringer will be muted)
-        Policy totalSilence = new Policy(0,0,0);
+        Policy totalSilence = new Policy(0, 0, 0);
         mZenModeHelper.setNotificationPolicy(totalSilence, UPDATE_ORIGIN_APP, 1);
         mZenModeHelper.mZenMode = ZEN_MODE_IMPORTANT_INTERRUPTIONS;
 
@@ -873,7 +873,7 @@
 
         // even when ringer is muted (since all ringer sounds cannot bypass DND),
         // system stream is still affected by ringer mode
-        mZenModeHelper.setNotificationPolicy(new Policy(0,0,0), UPDATE_ORIGIN_APP, 1);
+        mZenModeHelper.setNotificationPolicy(new Policy(0, 0, 0), UPDATE_ORIGIN_APP, 1);
         mZenModeHelper.setManualZenMode(ZEN_MODE_IMPORTANT_INTERRUPTIONS, Uri.EMPTY,
                 UPDATE_ORIGIN_APP, "test", "caller", 1);
         ZenModeHelper.RingerModeDelegate ringerModeDelegateRingerNotMuted =
@@ -1065,9 +1065,10 @@
     @Test
     public void testParcelConfig() {
         mZenModeHelper.setNotificationPolicy(new Policy(PRIORITY_CATEGORY_EVENTS
-                | PRIORITY_CATEGORY_MESSAGES | PRIORITY_CATEGORY_REPEAT_CALLERS
-                | PRIORITY_CATEGORY_CONVERSATIONS, PRIORITY_SENDERS_STARRED,
-                PRIORITY_SENDERS_STARRED, 0, CONVERSATION_SENDERS_ANYONE), UPDATE_ORIGIN_UNKNOWN,
+                        | PRIORITY_CATEGORY_MESSAGES | PRIORITY_CATEGORY_REPEAT_CALLERS
+                        | PRIORITY_CATEGORY_CONVERSATIONS, PRIORITY_SENDERS_STARRED,
+                        PRIORITY_SENDERS_STARRED, 0, CONVERSATION_SENDERS_ANYONE),
+                UPDATE_ORIGIN_UNKNOWN,
                 1);
         mZenModeHelper.setManualZenRuleDeviceEffects(new ZenDeviceEffects.Builder()
                 .setShouldDimWallpaper(true)
@@ -1085,13 +1086,14 @@
     @Test
     public void testWriteXml() throws Exception {
         mZenModeHelper.setNotificationPolicy(new Policy(PRIORITY_CATEGORY_EVENTS
-                | PRIORITY_CATEGORY_MESSAGES | PRIORITY_CATEGORY_REPEAT_CALLERS
-                | PRIORITY_CATEGORY_CONVERSATIONS, PRIORITY_SENDERS_STARRED,
-                PRIORITY_SENDERS_STARRED, SUPPRESSED_EFFECT_BADGE, CONVERSATION_SENDERS_ANYONE),
+                        | PRIORITY_CATEGORY_MESSAGES | PRIORITY_CATEGORY_REPEAT_CALLERS
+                        | PRIORITY_CATEGORY_CONVERSATIONS, PRIORITY_SENDERS_STARRED,
+                        PRIORITY_SENDERS_STARRED, SUPPRESSED_EFFECT_BADGE,
+                        CONVERSATION_SENDERS_ANYONE),
                 UPDATE_ORIGIN_UNKNOWN, 1);
         mZenModeHelper.setManualZenRuleDeviceEffects(new ZenDeviceEffects.Builder()
-                        .setShouldDimWallpaper(true)
-                        .setShouldDisplayGrayscale(true)
+                .setShouldDimWallpaper(true)
+                .setShouldDisplayGrayscale(true)
                 .build(), UPDATE_ORIGIN_UNKNOWN, "test", 1);
         mZenModeHelper.setManualZenMode(ZEN_MODE_IMPORTANT_INTERRUPTIONS, Uri.EMPTY,
                 UPDATE_ORIGIN_UNKNOWN, "test", "me", 1);
@@ -2210,7 +2212,7 @@
         customDefaultRule.name = "Schedule Default Rule";
         customDefaultRule.zenMode = ZEN_MODE_IMPORTANT_INTERRUPTIONS;
         ScheduleInfo scheduleInfo = new ScheduleInfo();
-        scheduleInfo.days = new int[] { Calendar.SUNDAY };
+        scheduleInfo.days = new int[]{Calendar.SUNDAY};
         scheduleInfo.startHour = 18;
         scheduleInfo.endHour = 19;
         customDefaultRule.conditionId = ZenModeConfig.toScheduleConditionId(scheduleInfo);
@@ -3027,7 +3029,7 @@
         // Turn zen mode on (to important_interruptions)
         // Need to additionally call the looper in order to finish the post-apply-config process
         mZenModeHelper.setManualZenMode(ZEN_MODE_IMPORTANT_INTERRUPTIONS, null,
-                Flags.modesApi() ? UPDATE_ORIGIN_USER: UPDATE_ORIGIN_SYSTEM_OR_SYSTEMUI, "", null,
+                Flags.modesApi() ? UPDATE_ORIGIN_USER : UPDATE_ORIGIN_SYSTEM_OR_SYSTEMUI, "", null,
                 Process.SYSTEM_UID);
 
         // Now turn zen mode off, but via a different package UID -- this should get registered as
@@ -3060,6 +3062,9 @@
         assertTrue(mZenModeEventLogger.getIsUserAction(0));
         assertEquals(Process.SYSTEM_UID, mZenModeEventLogger.getPackageUid(0));
         checkDndProtoMatchesSetupZenConfig(mZenModeEventLogger.getPolicyProto(0));
+        // change origin should be populated only under modes_ui
+        assertThat(mZenModeEventLogger.getChangeOrigin(0)).isEqualTo(
+                (Flags.modesApi() && Flags.modesUi()) ? UPDATE_ORIGIN_USER : 0);
 
         // and from turning zen mode off:
         //   - event ID: DND_TURNED_OFF
@@ -3082,6 +3087,8 @@
         } else {
             checkDndProtoMatchesSetupZenConfig(mZenModeEventLogger.getPolicyProto(1));
         }
+        assertThat(mZenModeEventLogger.getChangeOrigin(1)).isEqualTo(
+                Flags.modesUi() ? UPDATE_ORIGIN_APP : 0);
     }
 
     @Test
@@ -3098,17 +3105,21 @@
                 null,
                 NotificationManager.INTERRUPTION_FILTER_PRIORITY, true);
         String id = mZenModeHelper.addAutomaticZenRule(mContext.getPackageName(), zenRule,
-                UPDATE_ORIGIN_SYSTEM_OR_SYSTEMUI, "test", Process.SYSTEM_UID);
+                UPDATE_ORIGIN_APP, "test", CUSTOM_PKG_UID);
 
         // Event 1: Mimic the rule coming on automatically by setting the Condition to STATE_TRUE
+        // Note that pre-modes_ui, this event serves as a test that automatic changes to an app's
+        // that look like they're coming from the system are attributed to the app, but when
+        // modes_ui is true, we opt to trust the provided change origin.
         mZenModeHelper.setAutomaticZenRuleState(id,
                 new Condition(zenRule.getConditionId(), "", STATE_TRUE),
-                UPDATE_ORIGIN_SYSTEM_OR_SYSTEMUI, Process.SYSTEM_UID);
+                Flags.modesUi() ? UPDATE_ORIGIN_APP : UPDATE_ORIGIN_SYSTEM_OR_SYSTEMUI,
+                CUSTOM_PKG_UID);
 
         // Event 2: "User" turns off the automatic rule (sets it to not enabled)
         zenRule.setEnabled(false);
         mZenModeHelper.updateAutomaticZenRule(id, zenRule,
-                Flags.modesApi() ? UPDATE_ORIGIN_USER: UPDATE_ORIGIN_SYSTEM_OR_SYSTEMUI, "",
+                Flags.modesApi() ? UPDATE_ORIGIN_USER : UPDATE_ORIGIN_SYSTEM_OR_SYSTEMUI, "",
                 Process.SYSTEM_UID);
 
         AutomaticZenRule systemRule = new AutomaticZenRule("systemRule",
@@ -3118,7 +3129,7 @@
                 null,
                 NotificationManager.INTERRUPTION_FILTER_PRIORITY, true);
         String systemId = mZenModeHelper.addAutomaticZenRule(mContext.getPackageName(), systemRule,
-                Flags.modesApi() ? UPDATE_ORIGIN_USER: UPDATE_ORIGIN_SYSTEM_OR_SYSTEMUI, "test",
+                Flags.modesApi() ? UPDATE_ORIGIN_USER : UPDATE_ORIGIN_SYSTEM_OR_SYSTEMUI, "test",
                 Process.SYSTEM_UID);
 
         // Event 3: turn on the system rule
@@ -3128,7 +3139,7 @@
 
         // Event 4: "User" deletes the rule
         mZenModeHelper.removeAutomaticZenRule(systemId,
-                Flags.modesApi() ? UPDATE_ORIGIN_USER: UPDATE_ORIGIN_SYSTEM_OR_SYSTEMUI, "",
+                Flags.modesApi() ? UPDATE_ORIGIN_USER : UPDATE_ORIGIN_SYSTEM_OR_SYSTEMUI, "",
                 Process.SYSTEM_UID);
         // In total, this represents 4 events
         assertEquals(4, mZenModeEventLogger.numLoggedChanges());
@@ -3151,9 +3162,13 @@
         assertFalse(mZenModeEventLogger.getIsUserAction(0));
         assertEquals(CUSTOM_PKG_UID, mZenModeEventLogger.getPackageUid(0));
         checkDndProtoMatchesDefaultZenConfig(mZenModeEventLogger.getPolicyProto(0));
+        assertThat(mZenModeEventLogger.getChangeOrigin(0)).isEqualTo(
+                Flags.modesUi() ? UPDATE_ORIGIN_APP : 0);
 
         // When the automatic rule is disabled, this should turn off zen mode and also count as a
         // user action. We don't care what the consolidated policy is when DND turns off.
+        // When modes_ui is true, this event should look like a user action attributed to the
+        // specific app.
         assertEquals(ZenModeEventLogger.ZenStateChangedEvent.DND_TURNED_OFF.getId(),
                 mZenModeEventLogger.getEventId(1));
         assertEquals(ZEN_MODE_IMPORTANT_INTERRUPTIONS, mZenModeEventLogger.getPrevZenMode(1));
@@ -3161,12 +3176,15 @@
         assertEquals(DNDProtoEnums.AUTOMATIC_RULE, mZenModeEventLogger.getChangedRuleType(1));
         assertEquals(0, mZenModeEventLogger.getNumRulesActive(1));
         assertTrue(mZenModeEventLogger.getIsUserAction(1));
-        assertEquals(Process.SYSTEM_UID, mZenModeEventLogger.getPackageUid(1));
+        assertThat(mZenModeEventLogger.getPackageUid(1)).isEqualTo(
+                Flags.modesUi() ? CUSTOM_PKG_UID : Process.SYSTEM_UID);
         if (Flags.modesApi()) {
             assertThat(mZenModeEventLogger.getPolicyProto(1)).isNull();
         } else {
             checkDndProtoMatchesSetupZenConfig(mZenModeEventLogger.getPolicyProto(1));
         }
+        assertThat(mZenModeEventLogger.getChangeOrigin(1)).isEqualTo(
+                Flags.modesUi() ? UPDATE_ORIGIN_USER : 0);
 
         // When the system rule is enabled, this counts as an automatic action that comes from the
         // system and turns on DND
@@ -3176,6 +3194,8 @@
         assertEquals(1, mZenModeEventLogger.getNumRulesActive(2));
         assertFalse(mZenModeEventLogger.getIsUserAction(2));
         assertEquals(Process.SYSTEM_UID, mZenModeEventLogger.getPackageUid(2));
+        assertThat(mZenModeEventLogger.getChangeOrigin(2)).isEqualTo(
+                Flags.modesUi() ? UPDATE_ORIGIN_SYSTEM_OR_SYSTEMUI : 0);
 
         // When the system rule is deleted, we consider this a user action that turns DND off
         // (again)
@@ -3185,6 +3205,8 @@
         assertEquals(0, mZenModeEventLogger.getNumRulesActive(3));
         assertTrue(mZenModeEventLogger.getIsUserAction(3));
         assertEquals(Process.SYSTEM_UID, mZenModeEventLogger.getPackageUid(3));
+        assertThat(mZenModeEventLogger.getChangeOrigin(3)).isEqualTo(
+                Flags.modesUi() ? UPDATE_ORIGIN_USER : 0);
     }
 
     @Test
@@ -3238,6 +3260,8 @@
         assertEquals(1, mZenModeEventLogger.getNumRulesActive(0));
         assertTrue(mZenModeEventLogger.getIsUserAction(0));
         assertEquals(CUSTOM_PKG_UID, mZenModeEventLogger.getPackageUid(0));
+        assertThat(mZenModeEventLogger.getChangeOrigin(0)).isEqualTo(
+                Flags.modesUi() ? UPDATE_ORIGIN_USER : 0);
 
         // Automatic rule turned off automatically by app:
         //   - event ID: DND_TURNED_OFF
@@ -3249,6 +3273,8 @@
         assertEquals(0, mZenModeEventLogger.getNumRulesActive(1));
         assertFalse(mZenModeEventLogger.getIsUserAction(1));
         assertEquals(CUSTOM_PKG_UID, mZenModeEventLogger.getPackageUid(1));
+        assertThat(mZenModeEventLogger.getChangeOrigin(1)).isEqualTo(
+                Flags.modesUi() ? UPDATE_ORIGIN_APP : 0);
 
         // Automatic rule turned on automatically by app:
         //   - event ID: DND_TURNED_ON
@@ -3261,6 +3287,8 @@
         assertEquals(1, mZenModeEventLogger.getNumRulesActive(2));
         assertFalse(mZenModeEventLogger.getIsUserAction(2));
         assertEquals(CUSTOM_PKG_UID, mZenModeEventLogger.getPackageUid(2));
+        assertThat(mZenModeEventLogger.getChangeOrigin(2)).isEqualTo(
+                Flags.modesUi() ? UPDATE_ORIGIN_APP : 0);
 
         // Automatic rule turned off automatically by the user:
         //   - event ID: DND_TURNED_ON
@@ -3272,6 +3300,8 @@
         assertEquals(0, mZenModeEventLogger.getNumRulesActive(3));
         assertTrue(mZenModeEventLogger.getIsUserAction(3));
         assertEquals(CUSTOM_PKG_UID, mZenModeEventLogger.getPackageUid(3));
+        assertThat(mZenModeEventLogger.getChangeOrigin(3)).isEqualTo(
+                Flags.modesUi() ? UPDATE_ORIGIN_USER : 0);
     }
 
     @Test
@@ -3335,7 +3365,7 @@
 
         AutomaticZenRule zenRule = new AutomaticZenRule("name",
                 null,
-                new ComponentName(CUSTOM_PKG_NAME, "ScheduleConditionProvider"),
+                new ComponentName("android", "ScheduleConditionProvider"),
                 ZenModeConfig.toScheduleConditionId(new ScheduleInfo()),
                 null,
                 NotificationManager.INTERRUPTION_FILTER_PRIORITY, true);
@@ -3345,7 +3375,7 @@
         // Rule 2, same as rule 1
         AutomaticZenRule zenRule2 = new AutomaticZenRule("name2",
                 null,
-                new ComponentName(CUSTOM_PKG_NAME, "ScheduleConditionProvider"),
+                new ComponentName("android", "ScheduleConditionProvider"),
                 ZenModeConfig.toScheduleConditionId(new ScheduleInfo()),
                 null,
                 NotificationManager.INTERRUPTION_FILTER_PRIORITY, true);
@@ -3395,7 +3425,7 @@
         assertEquals(ZEN_MODE_IMPORTANT_INTERRUPTIONS, mZenModeEventLogger.getNewZenMode(0));
         assertEquals(1, mZenModeEventLogger.getNumRulesActive(0));
         assertFalse(mZenModeEventLogger.getIsUserAction(0));
-        assertEquals(CUSTOM_PKG_UID, mZenModeEventLogger.getPackageUid(0));
+        assertEquals(Process.SYSTEM_UID, mZenModeEventLogger.getPackageUid(0));
         checkDndProtoMatchesDefaultZenConfig(mZenModeEventLogger.getPolicyProto(0));
 
         // Event 2: rule 2 turns on. This should not change anything about the policy, so the only
@@ -3404,7 +3434,7 @@
                 mZenModeEventLogger.getEventId(1));
         assertEquals(2, mZenModeEventLogger.getNumRulesActive(1));
         assertFalse(mZenModeEventLogger.getIsUserAction(1));
-        assertEquals(CUSTOM_PKG_UID, mZenModeEventLogger.getPackageUid(1));
+        assertEquals(Process.SYSTEM_UID, mZenModeEventLogger.getPackageUid(1));
         checkDndProtoMatchesDefaultZenConfig(mZenModeEventLogger.getPolicyProto(1));
 
         // Event 3: rule 3 turns on. This should trigger a policy change, and be classified as such,
@@ -3482,9 +3512,11 @@
         // Turn on rule 1; call looks like it's from the system. Because setting a condition is
         // typically an automatic (non-user-initiated) action, expect the calling UID to be
         // re-evaluated to the one associated with CUSTOM_PKG_NAME.
+        // When modes_ui is true: we expect the change origin to be the source of truth.
         mZenModeHelper.setAutomaticZenRuleState(id,
                 new Condition(zenRule.getConditionId(), "", STATE_TRUE),
-                UPDATE_ORIGIN_SYSTEM_OR_SYSTEMUI, Process.SYSTEM_UID);
+                Flags.modesUi() ? UPDATE_ORIGIN_APP : UPDATE_ORIGIN_SYSTEM_OR_SYSTEMUI,
+                Process.SYSTEM_UID);
 
         // Second: turn on rule 2. This is a system-owned rule and the UID should not be modified
         // (nor even looked up; the mock PackageManager won't handle "android" as input).
@@ -3493,7 +3525,7 @@
                 UPDATE_ORIGIN_SYSTEM_OR_SYSTEMUI, Process.SYSTEM_UID);
 
         // Disable rule 1. Because this looks like a user action, the UID should not be modified
-        // from the system-provided one.
+        // from the system-provided one unless modes_ui is true.
         zenRule.setEnabled(false);
         mZenModeHelper.updateAutomaticZenRule(id, zenRule,
                 UPDATE_ORIGIN_USER, "", Process.SYSTEM_UID);
@@ -3504,6 +3536,7 @@
 
         // Change rule 2's condition, but from some other UID. Since it doesn't look like it's from
         // the system, we keep the UID info.
+        // Note that this probably shouldn't be able to occur in real scenarios.
         mZenModeHelper.setAutomaticZenRuleState(id2,
                 new Condition(zenRule2.getConditionId(), "", STATE_FALSE),
                 UPDATE_ORIGIN_APP, 12345);
@@ -3528,11 +3561,13 @@
         assertEquals(Process.SYSTEM_UID, mZenModeEventLogger.getPackageUid(1));
 
         // Third event: disable rule 1. This looks like a user action so UID should be left alone.
+        // When modes_ui is true, we assign log this user action with the app that owns the rule.
         assertEquals(ZenModeEventLogger.ZenStateChangedEvent.DND_ACTIVE_RULES_CHANGED.getId(),
                 mZenModeEventLogger.getEventId(2));
         assertEquals(DNDProtoEnums.AUTOMATIC_RULE, mZenModeEventLogger.getChangedRuleType(2));
         assertTrue(mZenModeEventLogger.getIsUserAction(2));
-        assertEquals(Process.SYSTEM_UID, mZenModeEventLogger.getPackageUid(2));
+        assertThat(mZenModeEventLogger.getPackageUid(2)).isEqualTo(
+                Flags.modesUi() ? CUSTOM_PKG_UID : Process.SYSTEM_UID);
 
         // Fourth event: turns on manual mode. Doesn't change effective policy so this is just a
         // change in active rules. Confirm that the package UID is left unchanged.
@@ -6202,7 +6237,7 @@
     public void setManualZenRuleDeviceEffects_noPreexistingMode() {
         ZenDeviceEffects effects = new ZenDeviceEffects.Builder()
                 .setShouldDimWallpaper(true)
-                        .build();
+                .build();
         mZenModeHelper.setManualZenRuleDeviceEffects(effects, UPDATE_ORIGIN_USER, "settings", 1000);
 
         assertThat(mZenModeHelper.getConfig().manualRule).isNotNull();
@@ -6339,21 +6374,21 @@
 
     private static final Correspondence<ZenRule, ZenRule> IGNORE_METADATA =
             Correspondence.transforming(zr -> {
-                Parcel p = Parcel.obtain();
-                try {
-                    zr.writeToParcel(p, 0);
-                    p.setDataPosition(0);
-                    ZenRule copy = new ZenRule(p);
-                    copy.creationTime = 0;
-                    copy.userModifiedFields = 0;
-                    copy.zenPolicyUserModifiedFields = 0;
-                    copy.zenDeviceEffectsUserModifiedFields = 0;
-                    return copy;
-                } finally {
-                    p.recycle();
-                }
-            },
-              "Ignoring timestamp and userModifiedFields");
+                        Parcel p = Parcel.obtain();
+                        try {
+                            zr.writeToParcel(p, 0);
+                            p.setDataPosition(0);
+                            ZenRule copy = new ZenRule(p);
+                            copy.creationTime = 0;
+                            copy.userModifiedFields = 0;
+                            copy.zenPolicyUserModifiedFields = 0;
+                            copy.zenDeviceEffectsUserModifiedFields = 0;
+                            return copy;
+                        } finally {
+                            p.recycle();
+                        }
+                    },
+                    "Ignoring timestamp and userModifiedFields");
 
     private ZenRule expectedImplicitRule(String ownerPkg, int zenMode, ZenPolicy policy,
             @Nullable Boolean conditionActive) {
diff --git a/services/tests/wmtests/src/com/android/server/wm/BackgroundLaunchProcessControllerTests.java b/services/tests/wmtests/src/com/android/server/wm/BackgroundLaunchProcessControllerTests.java
index a4df034..c9c7e92 100644
--- a/services/tests/wmtests/src/com/android/server/wm/BackgroundLaunchProcessControllerTests.java
+++ b/services/tests/wmtests/src/com/android/server/wm/BackgroundLaunchProcessControllerTests.java
@@ -18,9 +18,11 @@
 
 import static com.android.server.wm.ActivityTaskManagerService.APP_SWITCH_ALLOW;
 import static com.android.server.wm.ActivityTaskManagerService.APP_SWITCH_DISALLOW;
+import static com.android.server.wm.BackgroundActivityStartController.BAL_ALLOW_BOUND_BY_FOREGROUND;
 import static com.android.server.wm.BackgroundActivityStartController.BAL_ALLOW_FOREGROUND;
 import static com.android.server.wm.BackgroundActivityStartController.BAL_ALLOW_GRACE_PERIOD;
 import static com.android.server.wm.BackgroundActivityStartController.BAL_ALLOW_PERMISSION;
+import static com.android.server.wm.BackgroundActivityStartController.BAL_ALLOW_TOKEN;
 import static com.android.server.wm.BackgroundActivityStartController.BAL_ALLOW_VISIBLE_WINDOW;
 import static com.android.server.wm.BackgroundActivityStartController.BAL_BLOCK;
 
@@ -30,12 +32,18 @@
 import android.content.Context;
 import android.os.Binder;
 import android.os.IBinder;
+import android.platform.test.annotations.DisableFlags;
+import android.platform.test.annotations.EnableFlags;
 import android.platform.test.annotations.Presubmit;
+import android.platform.test.flag.junit.SetFlagsRule;
 
 import androidx.test.filters.SmallTest;
 
 import com.android.server.wm.BackgroundActivityStartController.BalVerdict;
+import com.android.window.flags.Flags;
 
+import org.junit.ClassRule;
+import org.junit.Rule;
 import org.junit.Test;
 import org.junit.runner.RunWith;
 import org.junit.runners.JUnit4;
@@ -44,6 +52,7 @@
 import java.util.HashSet;
 import java.util.Set;
 
+
 /**
  * Tests for the {@link BackgroundLaunchProcessController} class.
  *
@@ -55,6 +64,10 @@
 @RunWith(JUnit4.class)
 public class BackgroundLaunchProcessControllerTests {
 
+
+    @ClassRule public static final SetFlagsRule.ClassRule mClassRule = new SetFlagsRule.ClassRule();
+    @Rule public final SetFlagsRule mSetFlagsRule = mClassRule.createSetFlagsRule();
+
     Set<IBinder> mActivityStartAllowed = new HashSet<>();
     Set<Integer> mHasActiveVisibleWindow = new HashSet<>();
 
@@ -113,7 +126,8 @@
     }
 
     @Test
-    public void testAllowedByTokenNoCallback() {
+    @DisableFlags(Flags.FLAG_BAL_IMPROVED_METRICS)
+    public void testAllowedByTokenNoCallbackOld() {
         mController = new BackgroundLaunchProcessController(mHasActiveVisibleWindow::contains,
                 null);
         Binder token = new Binder();
@@ -130,7 +144,26 @@
     }
 
     @Test
-    public void testAllowedByToken() {
+    @EnableFlags(Flags.FLAG_BAL_IMPROVED_METRICS)
+    public void testAllowedByTokenNoCallback() {
+        mController = new BackgroundLaunchProcessController(mHasActiveVisibleWindow::contains,
+                null);
+        Binder token = new Binder();
+        mActivityStartAllowed.add(token);
+        mController.addOrUpdateAllowBackgroundStartPrivileges(token,
+                BackgroundStartPrivileges.ALLOW_BAL);
+        BalVerdict balVerdict = mController.areBackgroundActivityStartsAllowed(
+                mPid, mUid, mPackageName,
+                mAppSwitchState, mIsCheckingForFgsStart,
+                mHasActivityInVisibleTask, mHasBackgroundActivityStartPrivileges,
+                mLastStopAppSwitchesTime, mLastActivityLaunchTime,
+                mLastActivityFinishTime);
+        assertThat(balVerdict.getCode()).isEqualTo(BAL_ALLOW_TOKEN);
+    }
+
+    @Test
+    @DisableFlags(Flags.FLAG_BAL_IMPROVED_METRICS)
+    public void testAllowedByTokenOld() {
         Binder token = new Binder();
         mActivityStartAllowed.add(token);
         mController.addOrUpdateAllowBackgroundStartPrivileges(token,
@@ -145,7 +178,24 @@
     }
 
     @Test
-    public void testBoundByForeground() {
+    @EnableFlags(Flags.FLAG_BAL_IMPROVED_METRICS)
+    public void testAllowedByToken() {
+        Binder token = new Binder();
+        mActivityStartAllowed.add(token);
+        mController.addOrUpdateAllowBackgroundStartPrivileges(token,
+                BackgroundStartPrivileges.ALLOW_BAL);
+        BalVerdict balVerdict = mController.areBackgroundActivityStartsAllowed(
+                mPid, mUid, mPackageName,
+                mAppSwitchState, mIsCheckingForFgsStart,
+                mHasActivityInVisibleTask, mHasBackgroundActivityStartPrivileges,
+                mLastStopAppSwitchesTime, mLastActivityLaunchTime,
+                mLastActivityFinishTime);
+        assertThat(balVerdict.getCode()).isEqualTo(BAL_ALLOW_TOKEN);
+    }
+
+    @Test
+    @DisableFlags(Flags.FLAG_BAL_IMPROVED_METRICS)
+    public void testBoundByForegroundOld() {
         mAppSwitchState = APP_SWITCH_ALLOW;
         mController.addBoundClientUid(999, "visible.package", Context.BIND_ALLOW_ACTIVITY_STARTS);
         mHasActiveVisibleWindow.add(999);
@@ -159,6 +209,21 @@
     }
 
     @Test
+    @EnableFlags(Flags.FLAG_BAL_IMPROVED_METRICS)
+    public void testBoundByForeground() {
+        mAppSwitchState = APP_SWITCH_ALLOW;
+        mController.addBoundClientUid(999, "visible.package", Context.BIND_ALLOW_ACTIVITY_STARTS);
+        mHasActiveVisibleWindow.add(999);
+        BalVerdict balVerdict = mController.areBackgroundActivityStartsAllowed(
+                mPid, mUid, mPackageName,
+                mAppSwitchState, mIsCheckingForFgsStart,
+                mHasActivityInVisibleTask, mHasBackgroundActivityStartPrivileges,
+                mLastStopAppSwitchesTime, mLastActivityLaunchTime,
+                mLastActivityFinishTime);
+        assertThat(balVerdict.getCode()).isEqualTo(BAL_ALLOW_BOUND_BY_FOREGROUND);
+    }
+
+    @Test
     public void testForegroundTask() {
         mAppSwitchState = APP_SWITCH_ALLOW;
         mHasActivityInVisibleTask = true;
diff --git a/services/tests/wmtests/src/com/android/server/wm/RootWindowContainerTests.java b/services/tests/wmtests/src/com/android/server/wm/RootWindowContainerTests.java
index d6d8339..d29505f 100644
--- a/services/tests/wmtests/src/com/android/server/wm/RootWindowContainerTests.java
+++ b/services/tests/wmtests/src/com/android/server/wm/RootWindowContainerTests.java
@@ -274,17 +274,28 @@
 
     @Test
     public void testAttachApplication() {
-        final ActivityRecord activity = new ActivityBuilder(mAtm).setCreateTask(true).build();
+        final ActivityRecord activity = new ActivityBuilder(mAtm).setProcessName("testAttach")
+                .setCreateTask(true).build();
+        final ActivityRecord topActivity = new ActivityBuilder(mAtm).setProcessName("testAttach")
+                .setUseProcess(activity.app).setTask(activity.getTask()).build();
         activity.detachFromProcess();
-        mAtm.startProcessAsync(activity, false /* knownToBeDead */,
+        topActivity.detachFromProcess();
+        mAtm.startProcessAsync(topActivity, false /* knownToBeDead */,
                 true /* isTop */, "test" /* hostingType */);
+        // Even if the activity is added after topActivity, the start order should still follow
+        // z-order, i.e. the topActivity will be started first.
+        mAtm.startProcessAsync(activity, false /* knownToBeDead */,
+                false /* isTop */, "test" /* hostingType */);
+        assertEquals(2, mAtm.mStartingProcessActivities.size());
+        assertEquals("Top record must be at the tail to start first",
+                topActivity, mAtm.mStartingProcessActivities.get(1));
         final WindowProcessController proc = mSystemServicesTestRule.addProcess(
                 activity.packageName, activity.processName,
                 6789 /* pid */, activity.info.applicationInfo.uid);
         try {
             mRootWindowContainer.attachApplication(proc);
-            verify(mSupervisor).realStartActivityLocked(eq(activity), eq(proc), anyBoolean(),
-                    anyBoolean());
+            verify(mSupervisor).realStartActivityLocked(eq(topActivity), eq(proc),
+                    anyBoolean(), anyBoolean());
         } catch (RemoteException e) {
             e.rethrowAsRuntimeException();
         }
diff --git a/services/tests/wmtests/src/com/android/server/wm/WindowContainerTests.java b/services/tests/wmtests/src/com/android/server/wm/WindowContainerTests.java
index 45dbea2..401964c 100644
--- a/services/tests/wmtests/src/com/android/server/wm/WindowContainerTests.java
+++ b/services/tests/wmtests/src/com/android/server/wm/WindowContainerTests.java
@@ -805,18 +805,11 @@
 
         final TestWindowContainer root2 = builder.setLayer(0).build();
 
+        assertEquals("Roots have the same z-order", 0, root.compareTo(root2));
         assertEquals(0, root.compareTo(root));
         assertEquals(-1, child1.compareTo(child2));
         assertEquals(1, child2.compareTo(child1));
 
-        boolean inTheSameTree = true;
-        try {
-            root.compareTo(root2);
-        } catch (IllegalArgumentException e) {
-            inTheSameTree = false;
-        }
-        assertFalse(inTheSameTree);
-
         assertEquals(-1, child1.compareTo(child11));
         assertEquals(1, child21.compareTo(root));
         assertEquals(1, child21.compareTo(child12));
diff --git a/telephony/java/android/telephony/satellite/SatelliteManager.java b/telephony/java/android/telephony/satellite/SatelliteManager.java
index b518c60d..aebae4e 100644
--- a/telephony/java/android/telephony/satellite/SatelliteManager.java
+++ b/telephony/java/android/telephony/satellite/SatelliteManager.java
@@ -50,7 +50,6 @@
 import java.time.Duration;
 import java.util.ArrayList;
 import java.util.Arrays;
-import java.util.Collections;
 import java.util.HashSet;
 import java.util.List;
 import java.util.Objects;
@@ -2636,9 +2635,9 @@
                         if (resultCode == SATELLITE_RESULT_SUCCESS) {
                             if (resultData.containsKey(KEY_REQUEST_PROVISION_SUBSCRIBER_ID_TOKEN)) {
                                 List<ProvisionSubscriberId> list =
-                                        Collections.singletonList(resultData.getParcelable(
+                                        resultData.getParcelableArrayList(
                                                 KEY_REQUEST_PROVISION_SUBSCRIBER_ID_TOKEN,
-                                                ProvisionSubscriberId.class));
+                                                ProvisionSubscriberId.class);
                                 executor.execute(() -> Binder.withCleanCallingIdentity(() ->
                                         callback.onResult(list)));
                             } else {
@@ -2692,13 +2691,13 @@
                     @Override
                     protected void onReceiveResult(int resultCode, Bundle resultData) {
                         if (resultCode == SATELLITE_RESULT_SUCCESS) {
-                            if (resultData.containsKey(KEY_SATELLITE_PROVISIONED)) {
+                            if (resultData.containsKey(KEY_IS_SATELLITE_PROVISIONED)) {
                                 boolean isIsProvisioned =
-                                        resultData.getBoolean(KEY_SATELLITE_PROVISIONED);
+                                        resultData.getBoolean(KEY_IS_SATELLITE_PROVISIONED);
                                 executor.execute(() -> Binder.withCleanCallingIdentity(() ->
                                         callback.onResult(isIsProvisioned)));
                             } else {
-                                loge("KEY_REQUEST_PROVISION_TOKENS does not exist.");
+                                loge("KEY_IS_SATELLITE_PROVISIONED does not exist.");
                                 executor.execute(() -> Binder.withCleanCallingIdentity(() ->
                                         callback.onError(new SatelliteException(
                                                 SATELLITE_RESULT_REQUEST_FAILED))));
diff --git a/telephony/java/com/android/internal/telephony/ITelephony.aidl b/telephony/java/com/android/internal/telephony/ITelephony.aidl
index 7be52ea..3dbda7a 100644
--- a/telephony/java/com/android/internal/telephony/ITelephony.aidl
+++ b/telephony/java/com/android/internal/telephony/ITelephony.aidl
@@ -3411,7 +3411,7 @@
      */
     @JavaPassthrough(annotation="@android.annotation.RequiresPermission("
             + "android.Manifest.permission.SATELLITE_COMMUNICATION)")
-    void requestProvisionSubscriberIds(in ResultReceiver receiver);
+    void requestProvisionSubscriberIds(in ResultReceiver result);
 
     /**
      * Request to get provisioned status for given a satellite subscriber id.
diff --git a/tests/FlickerTests/test-apps/app-helpers/src/com/android/server/wm/flicker/helpers/DesktopModeAppHelper.kt b/tests/FlickerTests/test-apps/app-helpers/src/com/android/server/wm/flicker/helpers/DesktopModeAppHelper.kt
index 9a5e88b..238f2af 100644
--- a/tests/FlickerTests/test-apps/app-helpers/src/com/android/server/wm/flicker/helpers/DesktopModeAppHelper.kt
+++ b/tests/FlickerTests/test-apps/app-helpers/src/com/android/server/wm/flicker/helpers/DesktopModeAppHelper.kt
@@ -165,4 +165,37 @@
             Corners.RIGHT_BOTTOM -> Pair(windowRect.right, windowRect.bottom)
         }
     }
+
+    /** Exit desktop mode by dragging the app handle to the top drag zone. */
+    fun exitDesktopWithDragToTopDragZone(
+        wmHelper: WindowManagerStateHelper,
+        device: UiDevice,
+    ) {
+        dragAppWindowToTopDragZone(wmHelper, device)
+        waitForTransitionToFullscreen(wmHelper)
+    }
+
+    private fun dragAppWindowToTopDragZone(wmHelper: WindowManagerStateHelper, device: UiDevice) {
+        val windowRect = wmHelper.getWindowRegion(innerHelper).bounds
+        val displayRect =
+            wmHelper.currentState.wmState.getDefaultDisplay()?.displayRect
+                ?: throw IllegalStateException("Default display is null")
+
+        val startX = windowRect.centerX()
+        val endX = displayRect.centerX()
+        val startY = windowRect.top
+        val endY = 0 // top of the screen
+
+        // drag the app window to top drag zone
+        device.drag(startX, startY, endX, endY, 100)
+    }
+
+    /** Wait for transition to full screen to finish. */
+    private fun waitForTransitionToFullscreen(wmHelper: WindowManagerStateHelper) {
+        wmHelper
+            .StateSyncBuilder()
+            .withFullScreenApp(innerHelper)
+            .withAppTransitionIdle()
+            .waitForAndVerify()
+    }
 }
diff --git a/tests/inputmethod/ConcurrentMultiSessionImeTest/src/com/android/server/inputmethod/multisessiontest/ConcurrentMultiUserTest.java b/tests/inputmethod/ConcurrentMultiSessionImeTest/src/com/android/server/inputmethod/multisessiontest/ConcurrentMultiUserTest.java
index 8433071..f385179 100644
--- a/tests/inputmethod/ConcurrentMultiSessionImeTest/src/com/android/server/inputmethod/multisessiontest/ConcurrentMultiUserTest.java
+++ b/tests/inputmethod/ConcurrentMultiSessionImeTest/src/com/android/server/inputmethod/multisessiontest/ConcurrentMultiUserTest.java
@@ -54,7 +54,6 @@
 import org.junit.After;
 import org.junit.Before;
 import org.junit.ClassRule;
-import org.junit.Ignore;
 import org.junit.Rule;
 import org.junit.Test;
 import org.junit.runner.RunWith;
@@ -72,7 +71,6 @@
     private static final ComponentName TEST_ACTIVITY = new ComponentName(
             getInstrumentation().getTargetContext().getPackageName(),
             MainActivity.class.getName());
-    private static final long WAIT_TIME_MS = 3000L;
     private final Context mContext = getInstrumentation().getTargetContext();
     private final InputMethodManager mInputMethodManager =
             mContext.getSystemService(InputMethodManager.class);
@@ -111,7 +109,6 @@
         assertPassengerImeHidden();
     }
 
-    @Ignore("b/350562427")
     @Test
     public void passengerShowImeNotAffectDriver() throws Exception {
         assertDriverImeHidden();
@@ -259,8 +256,6 @@
         float[] driverEditTextCenter = mActivity.getEditTextCenter();
         SystemUtil.runShellCommand(mUiAutomation, String.format("input tap %f %f",
                 driverEditTextCenter[0], driverEditTextCenter[1]));
-        // TODO(b/350562427): get rid of Thread.sleep().
-        Thread.sleep(WAIT_TIME_MS);
     }
 
     private void movePassengerDisplayToTop() throws Exception {
@@ -276,8 +271,6 @@
         final int passengerDisplayId = receivedBundle.getInt(KEY_DISPLAY_ID);
         SystemUtil.runShellCommand(mUiAutomation, String.format("input -d %d tap %f %f",
                 passengerDisplayId, passengerEditTextCenter[0], passengerEditTextCenter[1]));
-        // TODO(b/350562427): get rid of Thread.sleep().
-        Thread.sleep(WAIT_TIME_MS);
     }
 
     /**
diff --git a/tools/aapt2/util/Files.cpp b/tools/aapt2/util/Files.cpp
index 02e4beae..8ae55b8 100644
--- a/tools/aapt2/util/Files.cpp
+++ b/tools/aapt2/util/Files.cpp
@@ -189,7 +189,7 @@
   base->append(part.data(), part.size());
 }
 
-std::string BuildPath(std::vector<const StringPiece>&& args) {
+std::string BuildPath(const std::vector<StringPiece>& args) {
   if (args.empty()) {
     return "";
   }
diff --git a/tools/aapt2/util/Files.h b/tools/aapt2/util/Files.h
index 42eeaf2..c1a42446 100644
--- a/tools/aapt2/util/Files.h
+++ b/tools/aapt2/util/Files.h
@@ -60,7 +60,7 @@
 void AppendPath(std::string* base, android::StringPiece part);
 
 // Concatenates the list of paths and separates each part with the directory separator.
-std::string BuildPath(std::vector<const android::StringPiece>&& args);
+std::string BuildPath(const std::vector<android::StringPiece>& args);
 
 // Makes all the directories in `path`. The last element in the path is interpreted as a directory.
 bool mkdirs(const std::string& path);