Merge "Cancel divider dragging if IME showing" into tm-qpr-dev
diff --git a/core/java/android/app/DisabledWallpaperManager.java b/core/java/android/app/DisabledWallpaperManager.java
index 0d14c0b..fae6887 100644
--- a/core/java/android/app/DisabledWallpaperManager.java
+++ b/core/java/android/app/DisabledWallpaperManager.java
@@ -188,17 +188,17 @@
     }
 
     @Override
-    public WallpaperInfo getWallpaperInfo(int userId) {
+    public WallpaperInfo getWallpaperInfoForUser(int userId) {
         return unsupported();
     }
 
     @Override
-    public WallpaperInfo getWallpaperInfoWithFlags(@SetWallpaperFlags int which) {
+    public WallpaperInfo getWallpaperInfo(@SetWallpaperFlags int which) {
         return unsupported();
     }
 
     @Override
-    public WallpaperInfo getWallpaperInfoWithFlags(@SetWallpaperFlags int which, int userId) {
+    public WallpaperInfo getWallpaperInfo(@SetWallpaperFlags int which, int userId) {
         return unsupported();
     }
 
diff --git a/core/java/android/app/WallpaperManager.java b/core/java/android/app/WallpaperManager.java
index c99fa3d..14fe522 100644
--- a/core/java/android/app/WallpaperManager.java
+++ b/core/java/android/app/WallpaperManager.java
@@ -1324,7 +1324,7 @@
      * wallpaper component. Otherwise, if the wallpaper is a static image, this returns null.
      */
     public WallpaperInfo getWallpaperInfo() {
-        return getWallpaperInfo(mContext.getUserId());
+        return getWallpaperInfoForUser(mContext.getUserId());
     }
 
     /**
@@ -1334,7 +1334,7 @@
      * @param userId Owner of the wallpaper.
      * @hide
      */
-    public WallpaperInfo getWallpaperInfo(int userId) {
+    public WallpaperInfo getWallpaperInfoForUser(int userId) {
         try {
             if (sGlobals.mService == null) {
                 Log.w(TAG, "WallpaperService not running");
@@ -1349,25 +1349,31 @@
 
     /**
      * Returns the information about the home screen wallpaper if its current wallpaper is a live
-     * wallpaper component. Otherwise, if the wallpaper is a static image, this returns null.
+     * wallpaper component. Otherwise, if the wallpaper is a static image or is not set, this
+     * returns null.
      *
-     * @param which Specifies wallpaper destination (home or lock).
+     * @param which Specifies wallpaper to request (home or lock).
+     * @throws IllegalArgumentException if {@code which} is not exactly one of
+     * {{@link #FLAG_SYSTEM},{@link #FLAG_LOCK}}.
      * @hide
      */
-    public WallpaperInfo getWallpaperInfoWithFlags(@SetWallpaperFlags int which) {
+    public WallpaperInfo getWallpaperInfo(@SetWallpaperFlags int which) {
         return getWallpaperInfo();
     }
 
     /**
      * Returns the information about the designated wallpaper if its current wallpaper is a live
-     * wallpaper component. Otherwise, if the wallpaper is a static image, this returns null.
+     * wallpaper component. Otherwise, if the wallpaper is a static image or is not set, this
+     * returns null.
      *
-     * @param which Specifies wallpaper destination (home or lock).
+     * @param which Specifies wallpaper to request (home or lock).
      * @param userId Owner of the wallpaper.
+     * @throws IllegalArgumentException if {@code which} is not exactly one of
+     * {{@link #FLAG_SYSTEM},{@link #FLAG_LOCK}}.
      * @hide
      */
-    public WallpaperInfo getWallpaperInfoWithFlags(@SetWallpaperFlags int which, int userId) {
-        return getWallpaperInfo(userId);
+    public WallpaperInfo getWallpaperInfo(@SetWallpaperFlags int which, int userId) {
+        return getWallpaperInfoForUser(userId);
     }
 
     /**
diff --git a/core/java/android/os/BatteryManagerInternal.java b/core/java/android/os/BatteryManagerInternal.java
index 97ec594..9bad0de 100644
--- a/core/java/android/os/BatteryManagerInternal.java
+++ b/core/java/android/os/BatteryManagerInternal.java
@@ -47,6 +47,14 @@
     public abstract int getBatteryLevel();
 
     /**
+     * Returns battery health status as an integer representing the current battery health constant.
+     *
+     * This is a simple accessor that's safe to be called from any locks, but internally it may
+     * wait on the battery service lock.
+     */
+    public abstract int getBatteryHealth();
+
+    /**
      * Instantaneous battery capacity in uA-h, as defined in the HealthInfo HAL struct.
      * Please note apparently it could be bigger than {@link #getBatteryFullCharge}.
      *
diff --git a/core/java/android/service/dreams/DreamManagerInternal.java b/core/java/android/service/dreams/DreamManagerInternal.java
index cd38e8a..7c2f952 100644
--- a/core/java/android/service/dreams/DreamManagerInternal.java
+++ b/core/java/android/service/dreams/DreamManagerInternal.java
@@ -58,4 +58,39 @@
      * @param isScreenOn True if the screen is currently on.
      */
     public abstract boolean canStartDreaming(boolean isScreenOn);
+
+    /**
+     * Return whether dreams can continue when undocking by default. Even if the default is true,
+     * it can be overridden temporarily, in which case {@link DreamManagerStateListener} will be
+     * informed of any changes.
+     */
+    public abstract boolean keepDreamingWhenUndockedDefault();
+
+    /**
+     * Register a {@link DreamManagerStateListener}, which will be called when there are changes to
+     * dream state.
+     *
+     * @param listener The listener to register.
+     */
+    public abstract void registerDreamManagerStateListener(DreamManagerStateListener listener);
+
+    /**
+     * Unregister a {@link DreamManagerStateListener}, which will be called when there are changes
+     * to dream state.
+     *
+     * @param listener The listener to unregister.
+     */
+    public abstract void unregisterDreamManagerStateListener(DreamManagerStateListener listener);
+
+    /**
+     * Called when there are changes to dream state.
+     */
+    public interface DreamManagerStateListener {
+        /**
+         * Called when keep dreaming when undocked has changed.
+         *
+         * @param keepDreaming True if the current dream should continue when undocking.
+         */
+        void onKeepDreamingWhenUndockedChanged(boolean keepDreaming);
+    }
 }
diff --git a/core/java/android/window/ITaskFragmentOrganizerController.aidl b/core/java/android/window/ITaskFragmentOrganizerController.aidl
index 3250dd8..d25c8a8 100644
--- a/core/java/android/window/ITaskFragmentOrganizerController.aidl
+++ b/core/java/android/window/ITaskFragmentOrganizerController.aidl
@@ -39,13 +39,13 @@
      * animations if the transition only contains windows that belong to the organized
      * TaskFragments in the given Task.
      */
-    void registerRemoteAnimations(in ITaskFragmentOrganizer organizer, int taskId,
+    void registerRemoteAnimations(in ITaskFragmentOrganizer organizer,
         in RemoteAnimationDefinition definition);
 
     /**
      * Unregisters remote animations per transition type for the organizer.
      */
-    void unregisterRemoteAnimations(in ITaskFragmentOrganizer organizer, int taskId);
+    void unregisterRemoteAnimations(in ITaskFragmentOrganizer organizer);
 
     /**
      * Checks if an activity organized by a {@link android.window.TaskFragmentOrganizer} and
diff --git a/core/java/android/window/TaskFragmentOrganizer.java b/core/java/android/window/TaskFragmentOrganizer.java
index 648541b..ab7d616 100644
--- a/core/java/android/window/TaskFragmentOrganizer.java
+++ b/core/java/android/window/TaskFragmentOrganizer.java
@@ -140,16 +140,13 @@
     /**
      * Registers remote animations per transition type for the organizer. It will override the
      * animations if the transition only contains windows that belong to the organized
-     * TaskFragments in the given Task.
-     *
-     * @param taskId overrides if the transition only contains windows belonging to this Task.
+     * TaskFragments, and at least one of the transition window is embedded (not filling the Task).
      * @hide
      */
     @CallSuper
-    public void registerRemoteAnimations(int taskId,
-            @NonNull RemoteAnimationDefinition definition) {
+    public void registerRemoteAnimations(@NonNull RemoteAnimationDefinition definition) {
         try {
-            getController().registerRemoteAnimations(mInterface, taskId, definition);
+            getController().registerRemoteAnimations(mInterface, definition);
         } catch (RemoteException e) {
             throw e.rethrowFromSystemServer();
         }
@@ -160,9 +157,9 @@
      * @hide
      */
     @CallSuper
-    public void unregisterRemoteAnimations(int taskId) {
+    public void unregisterRemoteAnimations() {
         try {
-            getController().unregisterRemoteAnimations(mInterface, taskId);
+            getController().unregisterRemoteAnimations(mInterface);
         } catch (RemoteException e) {
             throw e.rethrowFromSystemServer();
         }
diff --git a/core/proto/android/server/powermanagerservice.proto b/core/proto/android/server/powermanagerservice.proto
index bd4f990..004d5d6 100644
--- a/core/proto/android/server/powermanagerservice.proto
+++ b/core/proto/android/server/powermanagerservice.proto
@@ -189,6 +189,8 @@
     optional bool is_enhanced_discharge_prediction_personalized = 54;
     optional bool is_low_power_standby_active = 55;
     optional LowPowerStandbyControllerDumpProto low_power_standby_controller = 56;
+    // The battery level drained by the dream.
+    optional int32 battery_level_drained_while_dreaming = 57;
 }
 
 // A com.android.server.power.PowerManagerService.SuspendBlockerImpl object.
diff --git a/libs/WindowManager/Jetpack/src/androidx/window/extensions/embedding/JetpackTaskFragmentOrganizer.java b/libs/WindowManager/Jetpack/src/androidx/window/extensions/embedding/JetpackTaskFragmentOrganizer.java
index 9d841ea..d7d43aa 100644
--- a/libs/WindowManager/Jetpack/src/androidx/window/extensions/embedding/JetpackTaskFragmentOrganizer.java
+++ b/libs/WindowManager/Jetpack/src/androidx/window/extensions/embedding/JetpackTaskFragmentOrganizer.java
@@ -37,7 +37,6 @@
 import android.window.TaskFragmentTransaction;
 import android.window.WindowContainerTransaction;
 
-import androidx.annotation.GuardedBy;
 import androidx.annotation.NonNull;
 import androidx.annotation.Nullable;
 
@@ -85,26 +84,20 @@
     @Override
     public void unregisterOrganizer() {
         if (mAnimationController != null) {
-            mAnimationController.unregisterAllRemoteAnimations();
+            mAnimationController.unregisterRemoteAnimations();
             mAnimationController = null;
         }
         super.unregisterOrganizer();
     }
 
-    /** Overrides the animation if the transition is on the given Task. */
-    void startOverrideSplitAnimation(int taskId) {
+    /**
+     * Overrides the animation for transitions of embedded activities organized by this organizer.
+     */
+    void overrideSplitAnimation() {
         if (mAnimationController == null) {
             mAnimationController = new TaskFragmentAnimationController(this);
         }
-        mAnimationController.registerRemoteAnimations(taskId);
-    }
-
-    /** No longer overrides the animation if the transition is on the given Task. */
-    @GuardedBy("mLock")
-    void stopOverrideSplitAnimation(int taskId) {
-        if (mAnimationController != null) {
-            mAnimationController.unregisterRemoteAnimations(taskId);
-        }
+        mAnimationController.registerRemoteAnimations();
     }
 
     /**
diff --git a/libs/WindowManager/Jetpack/src/androidx/window/extensions/embedding/SplitController.java b/libs/WindowManager/Jetpack/src/androidx/window/extensions/embedding/SplitController.java
index d52caaf..c06548a 100644
--- a/libs/WindowManager/Jetpack/src/androidx/window/extensions/embedding/SplitController.java
+++ b/libs/WindowManager/Jetpack/src/androidx/window/extensions/embedding/SplitController.java
@@ -193,7 +193,6 @@
                         continue;
                     }
                     updateContainersInTask(wct, taskContainer);
-                    updateAnimationOverride(taskContainer);
                 }
                 // The WCT should be applied and merged to the device state change transition if
                 // there is one.
@@ -208,9 +207,6 @@
         synchronized (mLock) {
             mSplitRules.clear();
             mSplitRules.addAll(rules);
-            for (int i = mTaskContainers.size() - 1; i >= 0; i--) {
-                updateAnimationOverride(mTaskContainers.valueAt(i));
-            }
         }
     }
 
@@ -612,7 +608,6 @@
             }
             if (taskContainer.isEmpty()) {
                 // Cleanup the TaskContainer if it becomes empty.
-                mPresenter.stopOverrideSplitAnimation(taskContainer.getTaskId());
                 mTaskContainers.remove(taskContainer.getTaskId());
             }
             return;
@@ -622,43 +617,7 @@
     @GuardedBy("mLock")
     private void onTaskContainerInfoChanged(@NonNull TaskContainer taskContainer,
             @NonNull Configuration config) {
-        final boolean wasInPip = taskContainer.isInPictureInPicture();
-        final boolean isInPIp = isInPictureInPicture(config);
-
-        // We need to check the animation override when enter/exit PIP or has bounds changed.
-        boolean shouldUpdateAnimationOverride = wasInPip != isInPIp;
-        if (taskContainer.setTaskBounds(config.windowConfiguration.getBounds())
-                && !isInPIp) {
-            // We don't care the bounds change when it has already entered PIP.
-            shouldUpdateAnimationOverride = true;
-        }
-        if (shouldUpdateAnimationOverride) {
-            updateAnimationOverride(taskContainer);
-        }
-    }
-
-    /**
-     * Updates if we should override transition animation. We only want to override if the Task
-     * bounds is large enough for at least one split rule.
-     */
-    @GuardedBy("mLock")
-    private void updateAnimationOverride(@NonNull TaskContainer taskContainer) {
-        if (ENABLE_SHELL_TRANSITIONS) {
-            // TODO(b/207070762): cleanup with legacy app transition
-            // Animation will be handled by WM Shell with Shell transition enabled.
-            return;
-        }
-        if (!taskContainer.isTaskBoundsInitialized()) {
-            // We don't know about the Task bounds/windowingMode yet.
-            return;
-        }
-
-        // We only want to override if the TaskContainer may show split.
-        if (mayShowSplit(taskContainer)) {
-            mPresenter.startOverrideSplitAnimation(taskContainer.getTaskId());
-        } else {
-            mPresenter.stopOverrideSplitAnimation(taskContainer.getTaskId());
-        }
+        taskContainer.setTaskBounds(config.windowConfiguration.getBounds());
     }
 
     /** Returns whether the given {@link TaskContainer} may show in split. */
@@ -1283,7 +1242,6 @@
                 Log.w(TAG, "Can't find bounds from activity=" + activityInTask);
             }
         }
-        updateAnimationOverride(taskContainer);
         return container;
     }
 
diff --git a/libs/WindowManager/Jetpack/src/androidx/window/extensions/embedding/SplitPresenter.java b/libs/WindowManager/Jetpack/src/androidx/window/extensions/embedding/SplitPresenter.java
index cb470ba..f494b32 100644
--- a/libs/WindowManager/Jetpack/src/androidx/window/extensions/embedding/SplitPresenter.java
+++ b/libs/WindowManager/Jetpack/src/androidx/window/extensions/embedding/SplitPresenter.java
@@ -139,6 +139,11 @@
         super(executor, controller);
         mController = controller;
         registerOrganizer();
+        if (!SplitController.ENABLE_SHELL_TRANSITIONS) {
+            // TODO(b/207070762): cleanup with legacy app transition
+            // Animation will be handled by WM Shell when Shell transition is enabled.
+            overrideSplitAnimation();
+        }
     }
 
     /**
diff --git a/libs/WindowManager/Jetpack/src/androidx/window/extensions/embedding/TaskFragmentAnimationController.java b/libs/WindowManager/Jetpack/src/androidx/window/extensions/embedding/TaskFragmentAnimationController.java
index ee2e139..d7eb9a0 100644
--- a/libs/WindowManager/Jetpack/src/androidx/window/extensions/embedding/TaskFragmentAnimationController.java
+++ b/libs/WindowManager/Jetpack/src/androidx/window/extensions/embedding/TaskFragmentAnimationController.java
@@ -18,13 +18,10 @@
 
 import static android.view.WindowManager.TRANSIT_OLD_ACTIVITY_CLOSE;
 import static android.view.WindowManager.TRANSIT_OLD_ACTIVITY_OPEN;
-import static android.view.WindowManager.TRANSIT_OLD_TASK_CLOSE;
 import static android.view.WindowManager.TRANSIT_OLD_TASK_FRAGMENT_CHANGE;
 import static android.view.WindowManager.TRANSIT_OLD_TASK_FRAGMENT_CLOSE;
 import static android.view.WindowManager.TRANSIT_OLD_TASK_FRAGMENT_OPEN;
-import static android.view.WindowManager.TRANSIT_OLD_TASK_OPEN;
 
-import android.util.ArraySet;
 import android.util.Log;
 import android.view.RemoteAnimationAdapter;
 import android.view.RemoteAnimationDefinition;
@@ -44,8 +41,7 @@
     private final TaskFragmentAnimationRunner mRemoteRunner = new TaskFragmentAnimationRunner();
     @VisibleForTesting
     final RemoteAnimationDefinition mDefinition;
-    /** Task Ids that we have registered for remote animation. */
-    private final ArraySet<Integer> mRegisterTasks = new ArraySet<>();
+    private boolean mIsRegistered;
 
     TaskFragmentAnimationController(@NonNull TaskFragmentOrganizer organizer) {
         mOrganizer = organizer;
@@ -54,39 +50,30 @@
                 new RemoteAnimationAdapter(mRemoteRunner, 0, 0, true /* changeNeedsSnapshot */);
         mDefinition.addRemoteAnimation(TRANSIT_OLD_ACTIVITY_OPEN, animationAdapter);
         mDefinition.addRemoteAnimation(TRANSIT_OLD_TASK_FRAGMENT_OPEN, animationAdapter);
-        mDefinition.addRemoteAnimation(TRANSIT_OLD_TASK_OPEN, animationAdapter);
         mDefinition.addRemoteAnimation(TRANSIT_OLD_ACTIVITY_CLOSE, animationAdapter);
         mDefinition.addRemoteAnimation(TRANSIT_OLD_TASK_FRAGMENT_CLOSE, animationAdapter);
-        mDefinition.addRemoteAnimation(TRANSIT_OLD_TASK_CLOSE, animationAdapter);
         mDefinition.addRemoteAnimation(TRANSIT_OLD_TASK_FRAGMENT_CHANGE, animationAdapter);
     }
 
-    void registerRemoteAnimations(int taskId) {
+    void registerRemoteAnimations() {
         if (DEBUG) {
             Log.v(TAG, "registerRemoteAnimations");
         }
-        if (mRegisterTasks.contains(taskId)) {
+        if (mIsRegistered) {
             return;
         }
-        mOrganizer.registerRemoteAnimations(taskId, mDefinition);
-        mRegisterTasks.add(taskId);
+        mOrganizer.registerRemoteAnimations(mDefinition);
+        mIsRegistered = true;
     }
 
-    void unregisterRemoteAnimations(int taskId) {
+    void unregisterRemoteAnimations() {
         if (DEBUG) {
             Log.v(TAG, "unregisterRemoteAnimations");
         }
-        if (!mRegisterTasks.contains(taskId)) {
+        if (!mIsRegistered) {
             return;
         }
-        mOrganizer.unregisterRemoteAnimations(taskId);
-        mRegisterTasks.remove(taskId);
-    }
-
-    void unregisterAllRemoteAnimations() {
-        final ArraySet<Integer> tasks = new ArraySet<>(mRegisterTasks);
-        for (int taskId : tasks) {
-            unregisterRemoteAnimations(taskId);
-        }
+        mOrganizer.unregisterRemoteAnimations();
+        mIsRegistered = false;
     }
 }
diff --git a/libs/WindowManager/Jetpack/src/androidx/window/extensions/embedding/TaskFragmentAnimationRunner.java b/libs/WindowManager/Jetpack/src/androidx/window/extensions/embedding/TaskFragmentAnimationRunner.java
index 8c416e8..0e13c59 100644
--- a/libs/WindowManager/Jetpack/src/androidx/window/extensions/embedding/TaskFragmentAnimationRunner.java
+++ b/libs/WindowManager/Jetpack/src/androidx/window/extensions/embedding/TaskFragmentAnimationRunner.java
@@ -20,11 +20,9 @@
 import static android.view.RemoteAnimationTarget.MODE_CLOSING;
 import static android.view.WindowManager.TRANSIT_OLD_ACTIVITY_CLOSE;
 import static android.view.WindowManager.TRANSIT_OLD_ACTIVITY_OPEN;
-import static android.view.WindowManager.TRANSIT_OLD_TASK_CLOSE;
 import static android.view.WindowManager.TRANSIT_OLD_TASK_FRAGMENT_CHANGE;
 import static android.view.WindowManager.TRANSIT_OLD_TASK_FRAGMENT_CLOSE;
 import static android.view.WindowManager.TRANSIT_OLD_TASK_FRAGMENT_OPEN;
-import static android.view.WindowManager.TRANSIT_OLD_TASK_OPEN;
 import static android.view.WindowManagerPolicyConstants.TYPE_LAYER_OFFSET;
 
 import android.animation.Animator;
@@ -169,11 +167,9 @@
         switch (transit) {
             case TRANSIT_OLD_ACTIVITY_OPEN:
             case TRANSIT_OLD_TASK_FRAGMENT_OPEN:
-            case TRANSIT_OLD_TASK_OPEN:
                 return createOpenAnimationAdapters(targets);
             case TRANSIT_OLD_ACTIVITY_CLOSE:
             case TRANSIT_OLD_TASK_FRAGMENT_CLOSE:
-            case TRANSIT_OLD_TASK_CLOSE:
                 return createCloseAnimationAdapters(targets);
             case TRANSIT_OLD_TASK_FRAGMENT_CHANGE:
                 return createChangeAnimationAdapters(targets);
diff --git a/libs/WindowManager/Jetpack/tests/unittest/src/androidx/window/extensions/embedding/JetpackTaskFragmentOrganizerTest.java b/libs/WindowManager/Jetpack/tests/unittest/src/androidx/window/extensions/embedding/JetpackTaskFragmentOrganizerTest.java
index 79813c7..31aa09c 100644
--- a/libs/WindowManager/Jetpack/tests/unittest/src/androidx/window/extensions/embedding/JetpackTaskFragmentOrganizerTest.java
+++ b/libs/WindowManager/Jetpack/tests/unittest/src/androidx/window/extensions/embedding/JetpackTaskFragmentOrganizerTest.java
@@ -18,7 +18,6 @@
 
 import static android.app.WindowConfiguration.WINDOWING_MODE_UNDEFINED;
 
-import static androidx.window.extensions.embedding.EmbeddingTestUtils.TASK_ID;
 import static androidx.window.extensions.embedding.EmbeddingTestUtils.createTestTaskContainer;
 
 import static com.android.dx.mockito.inline.extended.ExtendedMockito.spyOn;
@@ -26,10 +25,8 @@
 
 import static org.junit.Assert.assertNotNull;
 import static org.junit.Assert.assertNull;
-import static org.mockito.ArgumentMatchers.anyInt;
 import static org.mockito.Mockito.doReturn;
 import static org.mockito.Mockito.mock;
-import static org.mockito.Mockito.never;
 
 import android.content.Intent;
 import android.content.res.Configuration;
@@ -85,35 +82,20 @@
 
     @Test
     public void testUnregisterOrganizer() {
-        mOrganizer.startOverrideSplitAnimation(TASK_ID);
-        mOrganizer.startOverrideSplitAnimation(TASK_ID + 1);
+        mOrganizer.overrideSplitAnimation();
         mOrganizer.unregisterOrganizer();
 
-        verify(mOrganizer).unregisterRemoteAnimations(TASK_ID);
-        verify(mOrganizer).unregisterRemoteAnimations(TASK_ID + 1);
+        verify(mOrganizer).unregisterRemoteAnimations();
     }
 
     @Test
-    public void testStartOverrideSplitAnimation() {
+    public void testOverrideSplitAnimation() {
         assertNull(mOrganizer.mAnimationController);
 
-        mOrganizer.startOverrideSplitAnimation(TASK_ID);
+        mOrganizer.overrideSplitAnimation();
 
         assertNotNull(mOrganizer.mAnimationController);
-        verify(mOrganizer).registerRemoteAnimations(TASK_ID,
-                mOrganizer.mAnimationController.mDefinition);
-    }
-
-    @Test
-    public void testStopOverrideSplitAnimation() {
-        mOrganizer.stopOverrideSplitAnimation(TASK_ID);
-
-        verify(mOrganizer, never()).unregisterRemoteAnimations(anyInt());
-
-        mOrganizer.startOverrideSplitAnimation(TASK_ID);
-        mOrganizer.stopOverrideSplitAnimation(TASK_ID);
-
-        verify(mOrganizer).unregisterRemoteAnimations(TASK_ID);
+        verify(mOrganizer).registerRemoteAnimations(mOrganizer.mAnimationController.mDefinition);
     }
 
     @Test
diff --git a/libs/WindowManager/Jetpack/tests/unittest/src/androidx/window/extensions/embedding/TaskFragmentAnimationControllerTest.java b/libs/WindowManager/Jetpack/tests/unittest/src/androidx/window/extensions/embedding/TaskFragmentAnimationControllerTest.java
index d31342b..379ea0c 100644
--- a/libs/WindowManager/Jetpack/tests/unittest/src/androidx/window/extensions/embedding/TaskFragmentAnimationControllerTest.java
+++ b/libs/WindowManager/Jetpack/tests/unittest/src/androidx/window/extensions/embedding/TaskFragmentAnimationControllerTest.java
@@ -16,11 +16,8 @@
 
 package androidx.window.extensions.embedding;
 
-import static androidx.window.extensions.embedding.EmbeddingTestUtils.TASK_ID;
-
 import static com.android.dx.mockito.inline.extended.ExtendedMockito.verify;
 
-import static org.mockito.ArgumentMatchers.anyInt;
 import static org.mockito.Mockito.never;
 
 import android.platform.test.annotations.Presubmit;
@@ -57,41 +54,31 @@
 
     @Test
     public void testRegisterRemoteAnimations() {
-        mAnimationController.registerRemoteAnimations(TASK_ID);
+        mAnimationController.registerRemoteAnimations();
 
-        verify(mOrganizer).registerRemoteAnimations(TASK_ID, mAnimationController.mDefinition);
+        verify(mOrganizer).registerRemoteAnimations(mAnimationController.mDefinition);
 
-        mAnimationController.registerRemoteAnimations(TASK_ID);
+        mAnimationController.registerRemoteAnimations();
 
         // No extra call if it has been registered.
-        verify(mOrganizer).registerRemoteAnimations(TASK_ID, mAnimationController.mDefinition);
+        verify(mOrganizer).registerRemoteAnimations(mAnimationController.mDefinition);
     }
 
     @Test
     public void testUnregisterRemoteAnimations() {
-        mAnimationController.unregisterRemoteAnimations(TASK_ID);
+        mAnimationController.unregisterRemoteAnimations();
 
         // No call if it is not registered.
-        verify(mOrganizer, never()).unregisterRemoteAnimations(anyInt());
+        verify(mOrganizer, never()).unregisterRemoteAnimations();
 
-        mAnimationController.registerRemoteAnimations(TASK_ID);
-        mAnimationController.unregisterRemoteAnimations(TASK_ID);
+        mAnimationController.registerRemoteAnimations();
+        mAnimationController.unregisterRemoteAnimations();
 
-        verify(mOrganizer).unregisterRemoteAnimations(TASK_ID);
+        verify(mOrganizer).unregisterRemoteAnimations();
 
-        mAnimationController.unregisterRemoteAnimations(TASK_ID);
+        mAnimationController.unregisterRemoteAnimations();
 
         // No extra call if it has been unregistered.
-        verify(mOrganizer).unregisterRemoteAnimations(TASK_ID);
-    }
-
-    @Test
-    public void testUnregisterAllRemoteAnimations() {
-        mAnimationController.registerRemoteAnimations(TASK_ID);
-        mAnimationController.registerRemoteAnimations(TASK_ID + 1);
-        mAnimationController.unregisterAllRemoteAnimations();
-
-        verify(mOrganizer).unregisterRemoteAnimations(TASK_ID);
-        verify(mOrganizer).unregisterRemoteAnimations(TASK_ID + 1);
+        verify(mOrganizer).unregisterRemoteAnimations();
     }
 }
diff --git a/libs/WindowManager/Shell/res/drawable/decor_caption_menu_background.xml b/libs/WindowManager/Shell/res/drawable/decor_caption_menu_background.xml
new file mode 100644
index 0000000..416287d
--- /dev/null
+++ b/libs/WindowManager/Shell/res/drawable/decor_caption_menu_background.xml
@@ -0,0 +1,21 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!--
+  ~ Copyright (C) 2022 The Android Open Source Project
+  ~
+  ~ Licensed under the Apache License, Version 2.0 (the "License");
+  ~ you may not use this file except in compliance with the License.
+  ~ You may obtain a copy of the License at
+  ~
+  ~      http://www.apache.org/licenses/LICENSE-2.0
+  ~
+  ~ Unless required by applicable law or agreed to in writing, software
+  ~ distributed under the License is distributed on an "AS IS" BASIS,
+  ~ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+  ~ See the License for the specific language governing permissions and
+  ~ limitations under the License.
+  -->
+<shape android:shape="rectangle"
+       xmlns:android="http://schemas.android.com/apk/res/android">
+    <solid android:color="@android:color/white" />
+    <corners android:radius="20dp" />
+</shape>
diff --git a/libs/WindowManager/Shell/res/layout/caption_handle_menu.xml b/libs/WindowManager/Shell/res/layout/caption_handle_menu.xml
index d9a140b..582a11c 100644
--- a/libs/WindowManager/Shell/res/layout/caption_handle_menu.xml
+++ b/libs/WindowManager/Shell/res/layout/caption_handle_menu.xml
@@ -20,7 +20,7 @@
 android:layout_width="wrap_content"
 android:layout_height="wrap_content"
 android:gravity="center_horizontal"
-android:background="@drawable/decor_caption_title">
+android:background="@drawable/decor_caption_menu_background">
     <Button
         style="@style/CaptionButtonStyle"
         android:id="@+id/fullscreen_button"
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/windowdecor/CaptionWindowDecorViewModel.java b/libs/WindowManager/Shell/src/com/android/wm/shell/windowdecor/CaptionWindowDecorViewModel.java
index ebe5c5e..8369569 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/windowdecor/CaptionWindowDecorViewModel.java
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/windowdecor/CaptionWindowDecorViewModel.java
@@ -44,6 +44,8 @@
 import android.window.WindowContainerToken;
 import android.window.WindowContainerTransaction;
 
+import androidx.annotation.Nullable;
+
 import com.android.wm.shell.R;
 import com.android.wm.shell.ShellTaskOrganizer;
 import com.android.wm.shell.common.DisplayController;
@@ -71,6 +73,7 @@
     private DesktopModeController mDesktopModeController;
     private EventReceiver mEventReceiver;
     private InputMonitor mInputMonitor;
+    private boolean mTransitionDragActive;
 
     private final SparseArray<CaptionWindowDecoration> mWindowDecorByTaskId = new SparseArray<>();
     private final DragStartListenerImpl mDragStartListener = new DragStartListenerImpl();
@@ -91,6 +94,7 @@
         mDisplayController = displayController;
         mSyncQueue = syncQueue;
         mDesktopModeController = desktopModeController;
+        mTransitionDragActive = false;
     }
 
     @Override
@@ -288,7 +292,7 @@
                     mDragResizeCallback.onDragResizeEnd(
                             e.getRawX(dragPointerIdx), e.getRawY(dragPointerIdx));
                     if (e.getRawY(dragPointerIdx) <= statusBarHeight
-                            && windowingMode == WINDOWING_MODE_FREEFORM) {
+                            && DesktopModeStatus.isActive(mContext)) {
                         mDesktopModeController.setDesktopModeActive(false);
                     }
                     break;
@@ -306,26 +310,97 @@
         @Override
         public void onInputEvent(InputEvent event) {
             boolean handled = false;
-            if (event instanceof MotionEvent
-                    && ((MotionEvent) event).getActionMasked() == MotionEvent.ACTION_UP) {
+            if (event instanceof MotionEvent) {
                 handled = true;
-                CaptionWindowDecorViewModel.this.handleMotionEvent((MotionEvent) event);
+                CaptionWindowDecorViewModel.this.handleReceivedMotionEvent((MotionEvent) event);
             }
             finishInputEvent(event, handled);
         }
     }
 
-    // If any input received is outside of caption bounds, turn off handle menu
-    private void handleMotionEvent(MotionEvent ev) {
-        int size = mWindowDecorByTaskId.size();
-        for (int i = 0; i < size; i++) {
-            CaptionWindowDecoration decoration = mWindowDecorByTaskId.valueAt(i);
-            if (decoration != null) {
-                decoration.closeHandleMenuIfNeeded(ev);
+    /**
+     * Handle MotionEvents relevant to focused task's caption that don't directly touch it
+     * @param ev the {@link MotionEvent} received by {@link EventReceiver}
+     */
+    private void handleReceivedMotionEvent(MotionEvent ev) {
+        if (!DesktopModeStatus.isActive(mContext)) {
+            handleCaptionThroughStatusBar(ev);
+        }
+        handleEventOutsideFocusedCaption(ev);
+        // Prevent status bar from reacting to a caption drag.
+        if (mTransitionDragActive && !DesktopModeStatus.isActive(mContext)) {
+            mInputMonitor.pilferPointers();
+        }
+    }
+
+    // If an UP/CANCEL action is received outside of caption bounds, turn off handle menu
+    private void handleEventOutsideFocusedCaption(MotionEvent ev) {
+        int action = ev.getActionMasked();
+        if (action == MotionEvent.ACTION_UP || action == MotionEvent.ACTION_CANCEL) {
+            CaptionWindowDecoration focusedDecor = getFocusedDecor();
+            if (focusedDecor == null) {
+                return;
+            }
+
+            if (!mTransitionDragActive) {
+                focusedDecor.closeHandleMenuIfNeeded(ev);
             }
         }
     }
 
+    /**
+     * Perform caption actions if not able to through normal means.
+     * Turn on desktop mode if handle is dragged below status bar.
+     */
+    private void handleCaptionThroughStatusBar(MotionEvent ev) {
+        switch (ev.getActionMasked()) {
+            case MotionEvent.ACTION_DOWN: {
+                // Begin drag through status bar if applicable.
+                CaptionWindowDecoration focusedDecor = getFocusedDecor();
+                if (focusedDecor != null && !DesktopModeStatus.isActive(mContext)
+                        && focusedDecor.checkTouchEventInHandle(ev)) {
+                    mTransitionDragActive = true;
+                }
+                break;
+            }
+            case MotionEvent.ACTION_UP: {
+                CaptionWindowDecoration focusedDecor = getFocusedDecor();
+                if (focusedDecor == null) {
+                    mTransitionDragActive = false;
+                    return;
+                }
+                if (mTransitionDragActive) {
+                    mTransitionDragActive = false;
+                    int statusBarHeight = mDisplayController
+                            .getDisplayLayout(focusedDecor.mTaskInfo.displayId).stableInsets().top;
+                    if (ev.getY() > statusBarHeight) {
+                        mDesktopModeController.setDesktopModeActive(true);
+                        return;
+                    }
+                }
+                focusedDecor.checkClickEvent(ev);
+                break;
+            }
+            case MotionEvent.ACTION_CANCEL: {
+                mTransitionDragActive = false;
+            }
+        }
+    }
+
+    @Nullable
+    private CaptionWindowDecoration getFocusedDecor() {
+        int size = mWindowDecorByTaskId.size();
+        CaptionWindowDecoration focusedDecor = null;
+        for (int i = 0; i < size; i++) {
+            CaptionWindowDecoration decor = mWindowDecorByTaskId.valueAt(i);
+            if (decor != null && decor.isFocused()) {
+                focusedDecor = decor;
+                break;
+            }
+        }
+        return focusedDecor;
+    }
+
 
     private boolean shouldShowWindowDecor(RunningTaskInfo taskInfo) {
         if (taskInfo.getWindowingMode() == WINDOWING_MODE_FREEFORM) return true;
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/windowdecor/CaptionWindowDecoration.java b/libs/WindowManager/Shell/src/com/android/wm/shell/windowdecor/CaptionWindowDecoration.java
index affde30..59576cd 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/windowdecor/CaptionWindowDecoration.java
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/windowdecor/CaptionWindowDecoration.java
@@ -23,6 +23,7 @@
 import android.content.res.Resources;
 import android.graphics.Color;
 import android.graphics.Point;
+import android.graphics.PointF;
 import android.graphics.Rect;
 import android.graphics.drawable.VectorDrawable;
 import android.os.Handler;
@@ -243,7 +244,7 @@
      * Sets the visibility of buttons and color of caption based on desktop mode status
      *
      */
-    public void setButtonVisibility() {
+    void setButtonVisibility() {
         mDesktopActive = DesktopModeStatus.isActive(mContext);
         int v = mDesktopActive ? View.VISIBLE : View.GONE;
         View caption = mResult.mRootView.findViewById(R.id.caption);
@@ -262,7 +263,7 @@
         caption.getBackground().setTint(v == View.VISIBLE ? Color.WHITE : Color.TRANSPARENT);
     }
 
-    public boolean isHandleMenuActive() {
+    boolean isHandleMenuActive() {
         return mHandleMenu != null;
     }
 
@@ -277,7 +278,7 @@
     /**
      * Create and display handle menu window
      */
-    public void createHandleMenu() {
+    void createHandleMenu() {
         SurfaceControl.Transaction t = new SurfaceControl.Transaction();
         final Resources resources = mDecorWindowContext.getResources();
         int x = mRelayoutParams.mCaptionX;
@@ -298,7 +299,7 @@
     /**
      * Close the handle menu window
      */
-    public void closeHandleMenu() {
+    void closeHandleMenu() {
         if (!isHandleMenuActive()) return;
         mHandleMenu.releaseView();
         mHandleMenu = null;
@@ -313,24 +314,85 @@
     /**
      * Close an open handle menu if input is outside of menu coordinates
      * @param ev the tapped point to compare against
-     * @return
      */
-    public void closeHandleMenuIfNeeded(MotionEvent ev) {
-        if (mHandleMenu != null) {
-            Point positionInParent = mTaskOrganizer.getRunningTaskInfo(mTaskInfo.taskId)
-                    .positionInParent;
-            final Resources resources = mDecorWindowContext.getResources();
-            ev.offsetLocation(-mRelayoutParams.mCaptionX, -mRelayoutParams.mCaptionY);
-            ev.offsetLocation(-positionInParent.x, -positionInParent.y);
-            int width = loadDimensionPixelSize(resources, mRelayoutParams.mCaptionWidthId);
-            int height = loadDimensionPixelSize(resources, mRelayoutParams.mCaptionHeightId);
-            if (!(ev.getX() >= 0 && ev.getY()  >= 0
-                    && ev.getX()  <= width && ev.getY()  <= height)) {
+    void closeHandleMenuIfNeeded(MotionEvent ev) {
+        if (isHandleMenuActive()) {
+            if (!checkEventInCaptionView(ev, R.id.caption)) {
                 closeHandleMenu();
             }
         }
     }
 
+    boolean isFocused() {
+        return mTaskInfo.isFocused;
+    }
+
+    /**
+     * Offset the coordinates of a {@link MotionEvent} to be in the same coordinate space as caption
+     * @param ev the {@link MotionEvent} to offset
+     * @return the point of the input in local space
+     */
+    private PointF offsetCaptionLocation(MotionEvent ev) {
+        PointF result = new PointF(ev.getX(), ev.getY());
+        Point positionInParent = mTaskOrganizer.getRunningTaskInfo(mTaskInfo.taskId)
+                .positionInParent;
+        result.offset(-mRelayoutParams.mCaptionX, -mRelayoutParams.mCaptionY);
+        result.offset(-positionInParent.x, -positionInParent.y);
+        return result;
+    }
+
+    /**
+     * Determine if a passed MotionEvent is in a view in caption
+     * @param ev the {@link MotionEvent} to check
+     * @param layoutId the id of the view
+     * @return {@code true} if event is inside the specified view, {@code false} if not
+     */
+    private boolean checkEventInCaptionView(MotionEvent ev, int layoutId) {
+        if (mResult.mRootView == null) return false;
+        PointF inputPoint = offsetCaptionLocation(ev);
+        View view = mResult.mRootView.findViewById(layoutId);
+        return view != null && view.pointInView(inputPoint.x, inputPoint.y, 0);
+    }
+
+    boolean checkTouchEventInHandle(MotionEvent ev) {
+        if (isHandleMenuActive()) return false;
+        return checkEventInCaptionView(ev, R.id.caption_handle);
+    }
+
+    /**
+     * Check a passed MotionEvent if a click has occurred on any button on this caption
+     * Note this should only be called when a regular onClick is not possible
+     * (i.e. the button was clicked through status bar layer)
+     * @param ev the MotionEvent to compare
+     */
+    void checkClickEvent(MotionEvent ev) {
+        if (mResult.mRootView == null) return;
+        View caption = mResult.mRootView.findViewById(R.id.caption);
+        PointF inputPoint = offsetCaptionLocation(ev);
+        if (!isHandleMenuActive()) {
+            View handle = caption.findViewById(R.id.caption_handle);
+            clickIfPointInView(inputPoint, handle);
+        } else {
+            View menu = mHandleMenu.mWindowViewHost.getView();
+            View fullscreen = menu.findViewById(R.id.fullscreen_button);
+            if (clickIfPointInView(inputPoint, fullscreen)) return;
+            View desktop = menu.findViewById(R.id.desktop_button);
+            if (clickIfPointInView(inputPoint, desktop)) return;
+            View split = menu.findViewById(R.id.split_screen_button);
+            if (clickIfPointInView(inputPoint, split)) return;
+            View more = menu.findViewById(R.id.more_button);
+            clickIfPointInView(inputPoint, more);
+        }
+    }
+
+    private boolean clickIfPointInView(PointF inputPoint, View v) {
+        if (v.pointInView(inputPoint.x - v.getLeft(), inputPoint.y, 0)) {
+            mOnCaptionButtonClickListener.onClick(v);
+            return true;
+        }
+        return false;
+    }
+
     @Override
     public void close() {
         closeDragResizeListener();
diff --git a/packages/SystemUI/Android.bp b/packages/SystemUI/Android.bp
index 9d7a9e7..3c6f18c 100644
--- a/packages/SystemUI/Android.bp
+++ b/packages/SystemUI/Android.bp
@@ -91,6 +91,7 @@
         "SystemUIAnimationLib",
         "SystemUIPluginLib",
         "SystemUISharedLib",
+        "SystemUICustomizationLib",
         "SystemUI-statsd",
         "SettingsLib",
         "androidx.core_core-ktx",
@@ -190,6 +191,7 @@
         "SystemUIAnimationLib",
         "SystemUIPluginLib",
         "SystemUISharedLib",
+        "SystemUICustomizationLib",
         "SystemUI-statsd",
         "SettingsLib",
         "androidx.viewpager2_viewpager2",
diff --git a/packages/SystemUI/customization/Android.bp b/packages/SystemUI/customization/Android.bp
new file mode 100644
index 0000000..dc450bb
--- /dev/null
+++ b/packages/SystemUI/customization/Android.bp
@@ -0,0 +1,52 @@
+// Copyright (C) 2017 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 {
+    // See: http://go/android-license-faq
+    // A large-scale-change added 'default_applicable_licenses' to import
+    // all of the 'license_kinds' from "frameworks_base_packages_SystemUI_license"
+    // to get the below license kinds:
+    //   SPDX-license-identifier-Apache-2.0
+    default_applicable_licenses: ["frameworks_base_packages_SystemUI_license"],
+}
+
+android_library {
+    name: "SystemUICustomizationLib",
+    srcs: [
+        "src/**/*.java",
+        "src/**/*.kt",
+        "src/**/*.aidl",
+    ],
+    static_libs: [
+        "PluginCoreLib",
+        "SystemUIAnimationLib",
+        "SystemUIPluginLib",
+        "SystemUIUnfoldLib",
+        "androidx.dynamicanimation_dynamicanimation",
+        "androidx.concurrent_concurrent-futures",
+        "androidx.lifecycle_lifecycle-runtime-ktx",
+        "androidx.lifecycle_lifecycle-viewmodel-ktx",
+        "androidx.recyclerview_recyclerview",
+        "kotlinx_coroutines_android",
+        "kotlinx_coroutines",
+        "dagger2",
+        "jsr330",
+    ],
+    resource_dirs: [
+        "res",
+    ],
+    min_sdk_version: "current",
+    plugins: ["dagger2-compiler"],
+    kotlincflags: ["-Xjvm-default=enable"],
+}
diff --git a/packages/SystemUI/customization/AndroidManifest.xml b/packages/SystemUI/customization/AndroidManifest.xml
new file mode 100644
index 0000000..3277bff
--- /dev/null
+++ b/packages/SystemUI/customization/AndroidManifest.xml
@@ -0,0 +1,21 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!--
+     Copyright (C) 2017 The Android Open Source Project
+
+     Licensed under the Apache License, Version 2.0 (the "License");
+     you may not use this file except in compliance with the License.
+     You may obtain a copy of the License at
+
+          http://www.apache.org/licenses/LICENSE-2.0
+
+     Unless required by applicable law or agreed to in writing, software
+     distributed under the License is distributed on an "AS IS" BASIS,
+     WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+     See the License for the specific language governing permissions and
+     limitations under the License.
+-->
+
+<manifest xmlns:android="http://schemas.android.com/apk/res/android"
+    package="com.android.systemui.customization">
+
+</manifest>
diff --git a/packages/SystemUI/shared/res/drawable/clock_default_thumbnail.xml b/packages/SystemUI/customization/res/drawable/clock_default_thumbnail.xml
similarity index 100%
rename from packages/SystemUI/shared/res/drawable/clock_default_thumbnail.xml
rename to packages/SystemUI/customization/res/drawable/clock_default_thumbnail.xml
diff --git a/packages/SystemUI/shared/res/layout/clock_default_large.xml b/packages/SystemUI/customization/res/layout/clock_default_large.xml
similarity index 100%
rename from packages/SystemUI/shared/res/layout/clock_default_large.xml
rename to packages/SystemUI/customization/res/layout/clock_default_large.xml
diff --git a/packages/SystemUI/shared/res/layout/clock_default_small.xml b/packages/SystemUI/customization/res/layout/clock_default_small.xml
similarity index 100%
rename from packages/SystemUI/shared/res/layout/clock_default_small.xml
rename to packages/SystemUI/customization/res/layout/clock_default_small.xml
diff --git a/packages/SystemUI/customization/res/values/attrs.xml b/packages/SystemUI/customization/res/values/attrs.xml
new file mode 100644
index 0000000..f9d66ee
--- /dev/null
+++ b/packages/SystemUI/customization/res/values/attrs.xml
@@ -0,0 +1,28 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!-- Copyright (C) 2022 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.
+-->
+
+<!-- Formatting note: terminate all comments with a period, to avoid breaking
+     the documentation output. To suppress comment lines from the documentation
+     output, insert an eat-comment element after the comment lines.
+-->
+
+<resources>
+    <declare-styleable name="AnimatableClockView">
+        <attr name="dozeWeight" format="integer" />
+        <attr name="lockScreenWeight" format="integer" />
+        <attr name="chargeAnimationDelay" format="integer" />
+    </declare-styleable>
+</resources>
diff --git a/packages/SystemUI/shared/res/values/dimens.xml b/packages/SystemUI/customization/res/values/dimens.xml
similarity index 100%
rename from packages/SystemUI/shared/res/values/dimens.xml
rename to packages/SystemUI/customization/res/values/dimens.xml
diff --git a/packages/SystemUI/shared/res/values/donottranslate.xml b/packages/SystemUI/customization/res/values/donottranslate.xml
similarity index 100%
rename from packages/SystemUI/shared/res/values/donottranslate.xml
rename to packages/SystemUI/customization/res/values/donottranslate.xml
diff --git a/packages/SystemUI/shared/src/com/android/systemui/shared/clocks/AnimatableClockView.kt b/packages/SystemUI/customization/src/com/android/systemui/shared/clocks/AnimatableClockView.kt
similarity index 98%
rename from packages/SystemUI/shared/src/com/android/systemui/shared/clocks/AnimatableClockView.kt
rename to packages/SystemUI/customization/src/com/android/systemui/shared/clocks/AnimatableClockView.kt
index 236aa66..22944b8 100644
--- a/packages/SystemUI/shared/src/com/android/systemui/shared/clocks/AnimatableClockView.kt
+++ b/packages/SystemUI/customization/src/com/android/systemui/shared/clocks/AnimatableClockView.kt
@@ -33,9 +33,9 @@
 import com.android.systemui.animation.GlyphCallback
 import com.android.systemui.animation.Interpolators
 import com.android.systemui.animation.TextAnimator
+import com.android.systemui.customization.R
 import com.android.systemui.plugins.log.LogBuffer
 import com.android.systemui.plugins.log.LogLevel.DEBUG
-import com.android.systemui.shared.R
 import java.io.PrintWriter
 import java.util.Calendar
 import java.util.Locale
@@ -613,7 +613,7 @@
         private const val CHARGE_ANIM_DURATION_PHASE_1: Long = 1000
 
         // Constants for the animation
-        private val MOVE_INTERPOLATOR = Interpolators.STANDARD
+        private val MOVE_INTERPOLATOR = Interpolators.EMPHASIZED
 
         // Calculate the positions of all of the digits...
         // Offset each digit by, say, 0.1
@@ -637,7 +637,10 @@
         // How much delay to apply to each subsequent digit. This is measured in terms of "fraction"
         // (i.e. a value of 0.1 would cause a digit to wait until fraction had hit 0.1, or 0.2 etc
         // before moving).
-        private const val MOVE_DIGIT_STEP = 0.1f
+        //
+        // The current specs dictate that each digit should have a 33ms gap between them. The
+        // overall time is 1s right now.
+        private const val MOVE_DIGIT_STEP = 0.033f
 
         // Total available transition time for each digit, taking into account the step. If step is
         // 0.1, then digit 0 would animate over 0.0 - 0.7, making availableTime 0.7.
diff --git a/packages/SystemUI/shared/src/com/android/systemui/shared/clocks/ClockRegistry.kt b/packages/SystemUI/customization/src/com/android/systemui/shared/clocks/ClockRegistry.kt
similarity index 97%
rename from packages/SystemUI/shared/src/com/android/systemui/shared/clocks/ClockRegistry.kt
rename to packages/SystemUI/customization/src/com/android/systemui/shared/clocks/ClockRegistry.kt
index 5c2c27a..59b4848 100644
--- a/packages/SystemUI/shared/src/com/android/systemui/shared/clocks/ClockRegistry.kt
+++ b/packages/SystemUI/customization/src/com/android/systemui/shared/clocks/ClockRegistry.kt
@@ -20,6 +20,7 @@
 import android.os.Handler
 import android.provider.Settings
 import android.util.Log
+import androidx.annotation.OpenForTesting
 import com.android.internal.annotations.Keep
 import com.android.systemui.plugins.ClockController
 import com.android.systemui.plugins.ClockId
@@ -27,7 +28,7 @@
 import com.android.systemui.plugins.ClockProvider
 import com.android.systemui.plugins.ClockProviderPlugin
 import com.android.systemui.plugins.PluginListener
-import com.android.systemui.shared.plugins.PluginManager
+import com.android.systemui.plugins.PluginManager
 import org.json.JSONObject
 
 private val TAG = ClockRegistry::class.simpleName
@@ -157,7 +158,8 @@
         }
     }
 
-    fun getClocks(): List<ClockMetadata> {
+    @OpenForTesting
+    open fun getClocks(): List<ClockMetadata> {
         if (!isEnabled) {
             return listOf(availableClocks[DEFAULT_CLOCK_ID]!!.metadata)
         }
diff --git a/packages/SystemUI/shared/src/com/android/systemui/shared/clocks/DefaultClockController.kt b/packages/SystemUI/customization/src/com/android/systemui/shared/clocks/DefaultClockController.kt
similarity index 99%
rename from packages/SystemUI/shared/src/com/android/systemui/shared/clocks/DefaultClockController.kt
rename to packages/SystemUI/customization/src/com/android/systemui/shared/clocks/DefaultClockController.kt
index 23a7271..8698844 100644
--- a/packages/SystemUI/shared/src/com/android/systemui/shared/clocks/DefaultClockController.kt
+++ b/packages/SystemUI/customization/src/com/android/systemui/shared/clocks/DefaultClockController.kt
@@ -23,13 +23,13 @@
 import android.view.View
 import android.widget.FrameLayout
 import androidx.annotation.VisibleForTesting
+import com.android.systemui.customization.R
 import com.android.systemui.plugins.ClockAnimations
 import com.android.systemui.plugins.ClockController
 import com.android.systemui.plugins.ClockEvents
 import com.android.systemui.plugins.ClockFaceController
 import com.android.systemui.plugins.ClockFaceEvents
 import com.android.systemui.plugins.log.LogBuffer
-import com.android.systemui.shared.R
 import java.io.PrintWriter
 import java.util.Locale
 import java.util.TimeZone
diff --git a/packages/SystemUI/shared/src/com/android/systemui/shared/clocks/DefaultClockProvider.kt b/packages/SystemUI/customization/src/com/android/systemui/shared/clocks/DefaultClockProvider.kt
similarity index 90%
rename from packages/SystemUI/shared/src/com/android/systemui/shared/clocks/DefaultClockProvider.kt
rename to packages/SystemUI/customization/src/com/android/systemui/shared/clocks/DefaultClockProvider.kt
index 6627c5d..4c0504b 100644
--- a/packages/SystemUI/shared/src/com/android/systemui/shared/clocks/DefaultClockProvider.kt
+++ b/packages/SystemUI/customization/src/com/android/systemui/shared/clocks/DefaultClockProvider.kt
@@ -17,23 +17,21 @@
 import android.content.res.Resources
 import android.graphics.drawable.Drawable
 import android.view.LayoutInflater
-import com.android.systemui.dagger.qualifiers.Main
+import com.android.systemui.customization.R
 import com.android.systemui.plugins.ClockController
 import com.android.systemui.plugins.ClockId
 import com.android.systemui.plugins.ClockMetadata
 import com.android.systemui.plugins.ClockProvider
-import com.android.systemui.shared.R
-import javax.inject.Inject
 
 private val TAG = DefaultClockProvider::class.simpleName
 const val DEFAULT_CLOCK_NAME = "Default Clock"
 const val DEFAULT_CLOCK_ID = "DEFAULT"
 
 /** Provides the default system clock */
-class DefaultClockProvider @Inject constructor(
+class DefaultClockProvider constructor(
     val ctx: Context,
     val layoutInflater: LayoutInflater,
-    @Main val resources: Resources
+    val resources: Resources
 ) : ClockProvider {
     override fun getClocks(): List<ClockMetadata> =
         listOf(ClockMetadata(DEFAULT_CLOCK_ID, DEFAULT_CLOCK_NAME))
diff --git a/packages/SystemUI/ktfmt_includes.txt b/packages/SystemUI/ktfmt_includes.txt
index 553b86b..0abbb1e 100644
--- a/packages/SystemUI/ktfmt_includes.txt
+++ b/packages/SystemUI/ktfmt_includes.txt
@@ -23,9 +23,9 @@
 -packages/SystemUI/shared/src/com/android/systemui/flags/FlagSerializer.kt
 -packages/SystemUI/shared/src/com/android/systemui/flags/FlagSettingsHelper.kt
 -packages/SystemUI/shared/src/com/android/systemui/shared/animation/UnfoldMoveFromCenterAnimator.kt
--packages/SystemUI/shared/src/com/android/systemui/shared/clocks/AnimatableClockView.kt
--packages/SystemUI/shared/src/com/android/systemui/shared/clocks/ClockRegistry.kt
--packages/SystemUI/shared/src/com/android/systemui/shared/clocks/DefaultClockProvider.kt
+-packages/SystemUI/customization/src/com/android/systemui/shared/clocks/AnimatableClockView.kt
+-packages/SystemUI/customization/src/com/android/systemui/shared/clocks/ClockRegistry.kt
+-packages/SystemUI/customization/src/com/android/systemui/shared/clocks/DefaultClockProvider.kt
 -packages/SystemUI/shared/src/com/android/systemui/shared/rotation/FloatingRotationButtonPositionCalculator.kt
 -packages/SystemUI/shared/src/com/android/systemui/shared/system/UncaughtExceptionPreHandlerManager.kt
 -packages/SystemUI/shared/src/com/android/systemui/shared/system/smartspace/SmartspaceState.kt
diff --git a/packages/SystemUI/shared/src/com/android/systemui/shared/plugins/PluginManager.java b/packages/SystemUI/plugin_core/src/com/android/systemui/plugins/PluginManager.java
similarity index 93%
rename from packages/SystemUI/shared/src/com/android/systemui/shared/plugins/PluginManager.java
rename to packages/SystemUI/plugin_core/src/com/android/systemui/plugins/PluginManager.java
index c89be86..80c64cd 100644
--- a/packages/SystemUI/shared/src/com/android/systemui/shared/plugins/PluginManager.java
+++ b/packages/SystemUI/plugin_core/src/com/android/systemui/plugins/PluginManager.java
@@ -12,12 +12,10 @@
  * permissions and limitations under the License.
  */
 
-package com.android.systemui.shared.plugins;
+package com.android.systemui.plugins;
 
 import android.text.TextUtils;
 
-import com.android.systemui.plugins.Plugin;
-import com.android.systemui.plugins.PluginListener;
 import com.android.systemui.plugins.annotations.ProvidesInterface;
 
 public interface PluginManager {
diff --git a/packages/SystemUI/res/drawable/ic_ring_volume.xml b/packages/SystemUI/res/drawable/ic_ring_volume.xml
new file mode 100644
index 0000000..343fe5d
--- /dev/null
+++ b/packages/SystemUI/res/drawable/ic_ring_volume.xml
@@ -0,0 +1,26 @@
+<!--
+    Copyright (C) 2022 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.
+-->
+<vector xmlns:android="http://schemas.android.com/apk/res/android"
+    android:width="24dp"
+    android:height="24dp"
+    android:viewportWidth="24"
+    android:viewportHeight="24"
+    android:tint="?android:attr/colorControlNormal">
+  <path
+      android:pathData="M11,7V2H13V7ZM17.6,9.85 L16.2,8.4 19.75,4.85 21.15,6.3ZM6.4,9.85 L2.85,6.3 4.25,4.85 7.8,8.4ZM12,12Q14.95,12 17.812,13.188Q20.675,14.375 22.9,16.75Q23.2,17.05 23.2,17.45Q23.2,17.85 22.9,18.15L20.6,20.4Q20.325,20.675 19.963,20.7Q19.6,20.725 19.3,20.5L16.4,18.3Q16.2,18.15 16.1,17.95Q16,17.75 16,17.5V14.65Q15.05,14.35 14.05,14.175Q13.05,14 12,14Q10.95,14 9.95,14.175Q8.95,14.35 8,14.65V17.5Q8,17.75 7.9,17.95Q7.8,18.15 7.6,18.3L4.7,20.5Q4.4,20.725 4.038,20.7Q3.675,20.675 3.4,20.4L1.1,18.15Q0.8,17.85 0.8,17.45Q0.8,17.05 1.1,16.75Q3.3,14.375 6.175,13.188Q9.05,12 12,12ZM6,15.35Q5.275,15.725 4.6,16.212Q3.925,16.7 3.2,17.3L4.2,18.3L6,16.9ZM18,15.4V16.9L19.8,18.3L20.8,17.35Q20.075,16.7 19.4,16.225Q18.725,15.75 18,15.4ZM6,15.35Q6,15.35 6,15.35Q6,15.35 6,15.35ZM18,15.4Q18,15.4 18,15.4Q18,15.4 18,15.4Z"
+      android:fillColor="?android:attr/colorPrimary"/>
+
+</vector>
\ No newline at end of file
diff --git a/packages/SystemUI/res/drawable/ic_ring_volume_off.xml b/packages/SystemUI/res/drawable/ic_ring_volume_off.xml
new file mode 100644
index 0000000..74f30d1
--- /dev/null
+++ b/packages/SystemUI/res/drawable/ic_ring_volume_off.xml
@@ -0,0 +1,34 @@
+<!--
+    Copyright (C) 2022 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.
+-->
+<vector xmlns:android="http://schemas.android.com/apk/res/android"
+    android:width="24dp"
+    android:height="24dp"
+    android:viewportWidth="24"
+    android:viewportHeight="24"
+    android:tint="?android:attr/colorControlNormal">
+<path
+      android:pathData="M0.8,4.2l8.1,8.1c-2.2,0.5 -5.2,1.6 -7.8,4.4c-0.4,0.4 -0.4,1 0,1.4l2.3,2.3c0.3,0.3 0.9,0.4 1.3,0.1l2.9,-2.2C7.8,18.1 8,17.8 8,17.5v-2.9c0.9,-0.3 1.7,-0.5 2.7,-0.6l8.5,8.5l1.4,-1.4L2.2,2.8L0.8,4.2z"
+    android:fillColor="?android:attr/colorPrimary"/>
+  <path
+      android:pathData="M11,2h2v5h-2z"
+      android:fillColor="?android:attr/colorPrimary"/>
+  <path
+      android:pathData="M21.2,6.3l-1.4,-1.4l-3.6,3.6l1.4,1.4C17.6,9.8 21,6.3 21.2,6.3z"
+      android:fillColor="?android:attr/colorPrimary"/>
+  <path
+      android:pathData="M22.9,16.7c-2.8,-3 -6.2,-4.1 -8.4,-4.5l7.2,7.2l1.3,-1.3C23.3,17.7 23.3,17.1 22.9,16.7z"
+      android:fillColor="?android:attr/colorPrimary"/>
+</vector>
diff --git a/packages/SystemUI/res/drawable/ic_speaker_mute.xml b/packages/SystemUI/res/drawable/ic_speaker_mute.xml
new file mode 100644
index 0000000..4e402cf
--- /dev/null
+++ b/packages/SystemUI/res/drawable/ic_speaker_mute.xml
@@ -0,0 +1,25 @@
+<!--
+  ~ Copyright (C) 2022 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.
+  -->
+<vector xmlns:android="http://schemas.android.com/apk/res/android"
+    android:width="24dp"
+    android:height="24dp"
+    android:viewportWidth="24"
+    android:viewportHeight="24"
+    android:tint="?android:attr/textColorPrimary"
+    android:autoMirrored="true">
+    <path android:fillColor="#FFFFFFFF"
+        android:pathData="M19.8,22.6 L16.775,19.575Q16.15,19.975 15.45,20.263Q14.75,20.55 14,20.725V18.675Q14.35,18.55 14.688,18.425Q15.025,18.3 15.325,18.125L12,14.8V20L7,15H3V9H6.2L1.4,4.2L2.8,2.8L21.2,21.2ZM19.6,16.8 L18.15,15.35Q18.575,14.575 18.788,13.725Q19,12.875 19,11.975Q19,9.625 17.625,7.775Q16.25,5.925 14,5.275V3.225Q17.1,3.925 19.05,6.362Q21,8.8 21,11.975Q21,13.3 20.638,14.525Q20.275,15.75 19.6,16.8ZM16.25,13.45 L14,11.2V7.95Q15.175,8.5 15.838,9.6Q16.5,10.7 16.5,12Q16.5,12.375 16.438,12.738Q16.375,13.1 16.25,13.45ZM12,9.2 L9.4,6.6 12,4ZM10,15.15V12.8L8.2,11H5V13H7.85ZM9.1,11.9Z"/>
+</vector>
\ No newline at end of file
diff --git a/packages/SystemUI/res/drawable/ic_speaker_on.xml b/packages/SystemUI/res/drawable/ic_speaker_on.xml
new file mode 100644
index 0000000..2a90e05
--- /dev/null
+++ b/packages/SystemUI/res/drawable/ic_speaker_on.xml
@@ -0,0 +1,25 @@
+<!--
+  ~ Copyright (C) 2022 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.
+  -->
+<vector xmlns:android="http://schemas.android.com/apk/res/android"
+    android:width="24dp"
+    android:height="24dp"
+    android:viewportWidth="24"
+    android:viewportHeight="24"
+    android:tint="?android:attr/textColorPrimary"
+    android:autoMirrored="true">
+    <path android:fillColor="#FFFFFFFF"
+        android:pathData="M14,20.725V18.675Q16.25,18.025 17.625,16.175Q19,14.325 19,11.975Q19,9.625 17.625,7.775Q16.25,5.925 14,5.275V3.225Q17.1,3.925 19.05,6.362Q21,8.8 21,11.975Q21,15.15 19.05,17.587Q17.1,20.025 14,20.725ZM3,15V9H7L12,4V20L7,15ZM14,16V7.95Q15.175,8.5 15.838,9.6Q16.5,10.7 16.5,12Q16.5,13.275 15.838,14.362Q15.175,15.45 14,16ZM10,8.85 L7.85,11H5V13H7.85L10,15.15ZM7.5,12Z"/>
+</vector>
\ No newline at end of file
diff --git a/packages/SystemUI/res/values/dimens.xml b/packages/SystemUI/res/values/dimens.xml
index 6fedb4f..f223eb7 100644
--- a/packages/SystemUI/res/values/dimens.xml
+++ b/packages/SystemUI/res/values/dimens.xml
@@ -1489,7 +1489,7 @@
 
     <!-- Dream overlay complications related dimensions -->
     <dimen name="dream_overlay_complication_clock_time_text_size">86sp</dimen>
-    <dimen name="dream_overlay_complication_clock_time_padding">20dp</dimen>
+    <dimen name="dream_overlay_complication_home_controls_padding">28dp</dimen>
     <dimen name="dream_overlay_complication_clock_subtitle_text_size">24sp</dimen>
     <dimen name="dream_overlay_complication_preview_text_size">36sp</dimen>
     <dimen name="dream_overlay_complication_preview_icon_padding">28dp</dimen>
diff --git a/packages/SystemUI/res/xml/qs_header.xml b/packages/SystemUI/res/xml/qs_header.xml
index 8248fcd..982c422 100644
--- a/packages/SystemUI/res/xml/qs_header.xml
+++ b/packages/SystemUI/res/xml/qs_header.xml
@@ -22,50 +22,104 @@
 >
 
     <Constraint
+        android:id="@+id/privacy_container">
+        <Layout
+            android:layout_width="wrap_content"
+            android:layout_height="@dimen/large_screen_shade_header_min_height"
+            app:layout_constraintEnd_toEndOf="@id/end_guide"
+            app:layout_constraintTop_toTopOf="parent"
+            app:layout_constraintBottom_toTopOf="@id/carrier_group"
+            app:layout_constraintHorizontal_bias="1"
+            />
+    </Constraint>
+
+    <Constraint
         android:id="@+id/clock">
         <Layout
             android:layout_width="wrap_content"
-            android:layout_height="48dp"
+            android:layout_height="@dimen/large_screen_shade_header_min_height"
             app:layout_constraintStart_toStartOf="parent"
-            app:layout_constraintTop_toBottomOf="@id/date"
-            app:layout_constraintBottom_toBottomOf="parent"
+            app:layout_constraintTop_toBottomOf="@id/privacy_container"
+            app:layout_constraintBottom_toBottomOf="@id/carrier_group"
             app:layout_constraintEnd_toStartOf="@id/carrier_group"
             app:layout_constraintHorizontal_bias="0"
+            app:layout_constraintHorizontal_chainStyle="spread_inside"
         />
+        <Transform
+            android:scaleX="2.57"
+            android:scaleY="2.57"
+            />
     </Constraint>
 
     <Constraint
         android:id="@+id/date">
         <Layout
             android:layout_width="wrap_content"
-            android:layout_height="48dp"
+            android:layout_height="@dimen/new_qs_header_non_clickable_element_height"
             app:layout_constraintStart_toStartOf="parent"
-            app:layout_constraintEnd_toEndOf="parent"
-            app:layout_constraintTop_toTopOf="parent"
-            app:layout_constraintBottom_toTopOf="@id/clock"
+            app:layout_constraintEnd_toStartOf="@id/space"
+            app:layout_constraintBottom_toBottomOf="parent"
+            app:layout_constraintTop_toBottomOf="@id/carrier_group"
             app:layout_constraintHorizontal_bias="0"
-        />
-        <Motion
-            app:motionStagger="0.5"
+            app:layout_constraintHorizontal_chainStyle="spread_inside"
         />
     </Constraint>
 
     <Constraint
         android:id="@+id/carrier_group">
-        <CustomAttribute
-            app:attributeName="alpha"
-            app:customFloatValue="1"
-        />
+        <Layout
+            app:layout_constraintWidth_min="48dp"
+            android:layout_width="wrap_content"
+            android:layout_height="@dimen/large_screen_shade_header_min_height"
+            app:layout_constraintStart_toEndOf="@id/clock"
+            app:layout_constraintTop_toBottomOf="@id/privacy_container"
+            app:layout_constraintEnd_toEndOf="parent"
+            app:layout_constraintHorizontal_bias="1"
+            app:layout_constraintBottom_toTopOf="@id/batteryRemainingIcon"
+            app:layout_constraintHorizontal_chainStyle="spread_inside"
+            />
+        <PropertySet
+            android:alpha="1"
+            />
     </Constraint>
 
     <Constraint
-        android:id="@+id/privacy_container">
+        android:id="@+id/statusIcons">
         <Layout
             android:layout_width="wrap_content"
-            android:layout_height="48dp"
+            android:layout_height="@dimen/new_qs_header_non_clickable_element_height"
+            app:layout_constrainedWidth="true"
+            app:layout_constraintStart_toEndOf="@id/space"
+            app:layout_constraintEnd_toStartOf="@id/batteryRemainingIcon"
+            app:layout_constraintTop_toTopOf="@id/date"
+            app:layout_constraintBottom_toBottomOf="parent"
+            app:layout_constraintHorizontal_bias="1"
+            />
+    </Constraint>
+
+    <Constraint
+        android:id="@+id/batteryRemainingIcon">
+        <Layout
+            android:layout_width="wrap_content"
+            android:layout_height="@dimen/new_qs_header_non_clickable_element_height"
+            app:layout_constraintHeight_min="@dimen/new_qs_header_non_clickable_element_height"
+            app:layout_constraintStart_toEndOf="@id/statusIcons"
             app:layout_constraintEnd_toEndOf="parent"
             app:layout_constraintTop_toTopOf="@id/date"
-            app:layout_constraintBottom_toBottomOf="@id/date"
-        />
+            app:layout_constraintBottom_toBottomOf="parent"
+            app:layout_constraintHorizontal_bias="1"
+            app:layout_constraintHorizontal_chainStyle="spread_inside"
+            />
+    </Constraint>
+
+
+    <Constraint
+        android:id="@id/space">
+        <Layout
+            android:layout_width="0dp"
+            android:layout_height="0dp"
+            app:layout_constraintStart_toEndOf="@id/date"
+            app:layout_constraintEnd_toStartOf="@id/statusIcons"
+            />
     </Constraint>
 </ConstraintSet>
\ No newline at end of file
diff --git a/packages/SystemUI/res/xml/qs_header_new.xml b/packages/SystemUI/res/xml/qs_header_new.xml
deleted file mode 100644
index 982c422..0000000
--- a/packages/SystemUI/res/xml/qs_header_new.xml
+++ /dev/null
@@ -1,125 +0,0 @@
-<?xml version="1.0" encoding="utf-8"?>
-<!--
-  ~ Copyright (C) 2021 The Android Open Source Project
-  ~
-  ~ Licensed under the Apache License, Version 2.0 (the "License");
-  ~ you may not use this file except in compliance with the License.
-  ~ You may obtain a copy of the License at
-  ~
-  ~      http://www.apache.org/licenses/LICENSE-2.0
-  ~
-  ~ Unless required by applicable law or agreed to in writing, software
-  ~ distributed under the License is distributed on an "AS IS" BASIS,
-  ~ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
-  ~ See the License for the specific language governing permissions and
-  ~ limitations under the License.
-  -->
-
-<ConstraintSet
-    xmlns:android="http://schemas.android.com/apk/res/android"
-    xmlns:app="http://schemas.android.com/apk/res-auto"
-    android:id="@+id/qs_header_constraint"
->
-
-    <Constraint
-        android:id="@+id/privacy_container">
-        <Layout
-            android:layout_width="wrap_content"
-            android:layout_height="@dimen/large_screen_shade_header_min_height"
-            app:layout_constraintEnd_toEndOf="@id/end_guide"
-            app:layout_constraintTop_toTopOf="parent"
-            app:layout_constraintBottom_toTopOf="@id/carrier_group"
-            app:layout_constraintHorizontal_bias="1"
-            />
-    </Constraint>
-
-    <Constraint
-        android:id="@+id/clock">
-        <Layout
-            android:layout_width="wrap_content"
-            android:layout_height="@dimen/large_screen_shade_header_min_height"
-            app:layout_constraintStart_toStartOf="parent"
-            app:layout_constraintTop_toBottomOf="@id/privacy_container"
-            app:layout_constraintBottom_toBottomOf="@id/carrier_group"
-            app:layout_constraintEnd_toStartOf="@id/carrier_group"
-            app:layout_constraintHorizontal_bias="0"
-            app:layout_constraintHorizontal_chainStyle="spread_inside"
-        />
-        <Transform
-            android:scaleX="2.57"
-            android:scaleY="2.57"
-            />
-    </Constraint>
-
-    <Constraint
-        android:id="@+id/date">
-        <Layout
-            android:layout_width="wrap_content"
-            android:layout_height="@dimen/new_qs_header_non_clickable_element_height"
-            app:layout_constraintStart_toStartOf="parent"
-            app:layout_constraintEnd_toStartOf="@id/space"
-            app:layout_constraintBottom_toBottomOf="parent"
-            app:layout_constraintTop_toBottomOf="@id/carrier_group"
-            app:layout_constraintHorizontal_bias="0"
-            app:layout_constraintHorizontal_chainStyle="spread_inside"
-        />
-    </Constraint>
-
-    <Constraint
-        android:id="@+id/carrier_group">
-        <Layout
-            app:layout_constraintWidth_min="48dp"
-            android:layout_width="wrap_content"
-            android:layout_height="@dimen/large_screen_shade_header_min_height"
-            app:layout_constraintStart_toEndOf="@id/clock"
-            app:layout_constraintTop_toBottomOf="@id/privacy_container"
-            app:layout_constraintEnd_toEndOf="parent"
-            app:layout_constraintHorizontal_bias="1"
-            app:layout_constraintBottom_toTopOf="@id/batteryRemainingIcon"
-            app:layout_constraintHorizontal_chainStyle="spread_inside"
-            />
-        <PropertySet
-            android:alpha="1"
-            />
-    </Constraint>
-
-    <Constraint
-        android:id="@+id/statusIcons">
-        <Layout
-            android:layout_width="wrap_content"
-            android:layout_height="@dimen/new_qs_header_non_clickable_element_height"
-            app:layout_constrainedWidth="true"
-            app:layout_constraintStart_toEndOf="@id/space"
-            app:layout_constraintEnd_toStartOf="@id/batteryRemainingIcon"
-            app:layout_constraintTop_toTopOf="@id/date"
-            app:layout_constraintBottom_toBottomOf="parent"
-            app:layout_constraintHorizontal_bias="1"
-            />
-    </Constraint>
-
-    <Constraint
-        android:id="@+id/batteryRemainingIcon">
-        <Layout
-            android:layout_width="wrap_content"
-            android:layout_height="@dimen/new_qs_header_non_clickable_element_height"
-            app:layout_constraintHeight_min="@dimen/new_qs_header_non_clickable_element_height"
-            app:layout_constraintStart_toEndOf="@id/statusIcons"
-            app:layout_constraintEnd_toEndOf="parent"
-            app:layout_constraintTop_toTopOf="@id/date"
-            app:layout_constraintBottom_toBottomOf="parent"
-            app:layout_constraintHorizontal_bias="1"
-            app:layout_constraintHorizontal_chainStyle="spread_inside"
-            />
-    </Constraint>
-
-
-    <Constraint
-        android:id="@id/space">
-        <Layout
-            android:layout_width="0dp"
-            android:layout_height="0dp"
-            app:layout_constraintStart_toEndOf="@id/date"
-            app:layout_constraintEnd_toStartOf="@id/statusIcons"
-            />
-    </Constraint>
-</ConstraintSet>
\ No newline at end of file
diff --git a/packages/SystemUI/shared/res/values/attrs.xml b/packages/SystemUI/shared/res/values/attrs.xml
index 96a5840..f3aeaef 100644
--- a/packages/SystemUI/shared/res/values/attrs.xml
+++ b/packages/SystemUI/shared/res/values/attrs.xml
@@ -20,12 +20,6 @@
 -->
 
 <resources>
-    <declare-styleable name="AnimatableClockView">
-        <attr name="dozeWeight" format="integer" />
-        <attr name="lockScreenWeight" format="integer" />
-        <attr name="chargeAnimationDelay" format="integer" />
-    </declare-styleable>
-
     <declare-styleable name="DoubleShadowAttrDeclare">
         <attr name="keyShadowBlur" format="dimension" />
         <attr name="keyShadowOffsetX" format="dimension" />
diff --git a/packages/SystemUI/shared/src/com/android/systemui/shared/keyguard/data/content/KeyguardQuickAffordanceProviderContract.kt b/packages/SystemUI/shared/src/com/android/systemui/shared/keyguard/data/content/KeyguardQuickAffordanceProviderContract.kt
index f60db2a..71469a3 100644
--- a/packages/SystemUI/shared/src/com/android/systemui/shared/keyguard/data/content/KeyguardQuickAffordanceProviderContract.kt
+++ b/packages/SystemUI/shared/src/com/android/systemui/shared/keyguard/data/content/KeyguardQuickAffordanceProviderContract.kt
@@ -106,6 +106,8 @@
             const val SLOT_ID = "slot_id"
             /** String. Unique ID for the selected affordance. */
             const val AFFORDANCE_ID = "affordance_id"
+            /** String. Human-readable name for the affordance. */
+            const val AFFORDANCE_NAME = "affordance_name"
         }
     }
 
diff --git a/packages/SystemUI/shared/src/com/android/systemui/shared/plugins/PluginActionManager.java b/packages/SystemUI/shared/src/com/android/systemui/shared/plugins/PluginActionManager.java
index 9ea4b57..e226d58 100644
--- a/packages/SystemUI/shared/src/com/android/systemui/shared/plugins/PluginActionManager.java
+++ b/packages/SystemUI/shared/src/com/android/systemui/shared/plugins/PluginActionManager.java
@@ -38,6 +38,7 @@
 import com.android.internal.messages.nano.SystemMessageProto.SystemMessage;
 import com.android.systemui.plugins.Plugin;
 import com.android.systemui.plugins.PluginListener;
+import com.android.systemui.plugins.PluginManager;
 import com.android.systemui.shared.plugins.VersionInfo.InvalidVersionException;
 
 import java.util.ArrayList;
diff --git a/packages/SystemUI/shared/src/com/android/systemui/shared/plugins/PluginManagerImpl.java b/packages/SystemUI/shared/src/com/android/systemui/shared/plugins/PluginManagerImpl.java
index 131f728..2f9f5b2 100644
--- a/packages/SystemUI/shared/src/com/android/systemui/shared/plugins/PluginManagerImpl.java
+++ b/packages/SystemUI/shared/src/com/android/systemui/shared/plugins/PluginManagerImpl.java
@@ -31,6 +31,7 @@
 import com.android.internal.messages.nano.SystemMessageProto.SystemMessage;
 import com.android.systemui.plugins.Plugin;
 import com.android.systemui.plugins.PluginListener;
+import com.android.systemui.plugins.PluginManager;
 import com.android.systemui.shared.system.UncaughtExceptionPreHandlerManager;
 
 import java.io.FileDescriptor;
diff --git a/packages/SystemUI/shared/src/com/android/systemui/shared/recents/utilities/PreviewPositionHelper.java b/packages/SystemUI/shared/src/com/android/systemui/shared/recents/utilities/PreviewPositionHelper.java
index f45887c..f6c75a2 100644
--- a/packages/SystemUI/shared/src/com/android/systemui/shared/recents/utilities/PreviewPositionHelper.java
+++ b/packages/SystemUI/shared/src/com/android/systemui/shared/recents/utilities/PreviewPositionHelper.java
@@ -82,7 +82,8 @@
                 taskPercent = mDesiredStagePosition != STAGE_POSITION_TOP_OR_LEFT
                         ? mSplitBounds.topTaskPercent
                         : (1 - (mSplitBounds.topTaskPercent + mSplitBounds.dividerHeightPercent));
-                fullscreenTaskHeight = screenHeightPx * taskPercent;
+                // Scale portrait height to that of (actual screen - taskbar inset)
+                fullscreenTaskHeight = (screenHeightPx - taskbarSize) * taskPercent;
                 canvasScreenRatio = canvasHeight / fullscreenTaskHeight;
             } else {
                 // For landscape, scale the width
diff --git a/packages/SystemUI/src/com/android/keyguard/clock/ClockManager.java b/packages/SystemUI/src/com/android/keyguard/clock/ClockManager.java
index ad9609f..122c521 100644
--- a/packages/SystemUI/src/com/android/keyguard/clock/ClockManager.java
+++ b/packages/SystemUI/src/com/android/keyguard/clock/ClockManager.java
@@ -39,8 +39,8 @@
 import com.android.systemui.dock.DockManager.DockEventListener;
 import com.android.systemui.plugins.ClockPlugin;
 import com.android.systemui.plugins.PluginListener;
+import com.android.systemui.plugins.PluginManager;
 import com.android.systemui.settings.UserTracker;
-import com.android.systemui.shared.plugins.PluginManager;
 
 import java.util.ArrayList;
 import java.util.Collection;
diff --git a/packages/SystemUI/src/com/android/keyguard/dagger/ClockRegistryModule.java b/packages/SystemUI/src/com/android/keyguard/dagger/ClockRegistryModule.java
index b514f60..676979c 100644
--- a/packages/SystemUI/src/com/android/keyguard/dagger/ClockRegistryModule.java
+++ b/packages/SystemUI/src/com/android/keyguard/dagger/ClockRegistryModule.java
@@ -17,8 +17,10 @@
 package com.android.keyguard.dagger;
 
 import android.content.Context;
+import android.content.res.Resources;
 import android.os.Handler;
 import android.os.UserHandle;
+import android.view.LayoutInflater;
 
 import com.android.systemui.R;
 import com.android.systemui.dagger.SysUISingleton;
@@ -26,9 +28,9 @@
 import com.android.systemui.dagger.qualifiers.Main;
 import com.android.systemui.flags.FeatureFlags;
 import com.android.systemui.flags.Flags;
+import com.android.systemui.plugins.PluginManager;
 import com.android.systemui.shared.clocks.ClockRegistry;
 import com.android.systemui.shared.clocks.DefaultClockProvider;
-import com.android.systemui.shared.plugins.PluginManager;
 
 import dagger.Module;
 import dagger.Provides;
@@ -43,15 +45,16 @@
             @Application Context context,
             PluginManager pluginManager,
             @Main Handler handler,
-            DefaultClockProvider defaultClockProvider,
-            FeatureFlags featureFlags) {
+            FeatureFlags featureFlags,
+            @Main Resources resources,
+            LayoutInflater layoutInflater) {
         return new ClockRegistry(
                 context,
                 pluginManager,
                 handler,
                 featureFlags.isEnabled(Flags.LOCKSCREEN_CUSTOM_CLOCKS),
                 UserHandle.USER_ALL,
-                defaultClockProvider,
+                new DefaultClockProvider(context, layoutInflater, resources),
                 context.getString(R.string.lockscreen_clock_id_fallback));
     }
 }
diff --git a/packages/SystemUI/src/com/android/systemui/Dependency.java b/packages/SystemUI/src/com/android/systemui/Dependency.java
index 51bcd6b..ef16a3a 100644
--- a/packages/SystemUI/src/com/android/systemui/Dependency.java
+++ b/packages/SystemUI/src/com/android/systemui/Dependency.java
@@ -61,6 +61,7 @@
 import com.android.systemui.plugins.ActivityStarter;
 import com.android.systemui.plugins.DarkIconDispatcher;
 import com.android.systemui.plugins.PluginDependencyProvider;
+import com.android.systemui.plugins.PluginManager;
 import com.android.systemui.plugins.VolumeDialogController;
 import com.android.systemui.plugins.statusbar.StatusBarStateController;
 import com.android.systemui.power.EnhancedEstimates;
@@ -72,7 +73,6 @@
 import com.android.systemui.screenrecord.RecordingController;
 import com.android.systemui.settings.UserTracker;
 import com.android.systemui.shade.ShadeController;
-import com.android.systemui.shared.plugins.PluginManager;
 import com.android.systemui.shared.system.ActivityManagerWrapper;
 import com.android.systemui.shared.system.DevicePolicyManagerWrapper;
 import com.android.systemui.shared.system.PackageManagerWrapper;
diff --git a/packages/SystemUI/src/com/android/systemui/PluginInflateContainer.java b/packages/SystemUI/src/com/android/systemui/PluginInflateContainer.java
index 95f666c..1bb0329 100644
--- a/packages/SystemUI/src/com/android/systemui/PluginInflateContainer.java
+++ b/packages/SystemUI/src/com/android/systemui/PluginInflateContainer.java
@@ -21,8 +21,8 @@
 import android.view.View;
 
 import com.android.systemui.plugins.PluginListener;
+import com.android.systemui.plugins.PluginManager;
 import com.android.systemui.plugins.ViewProvider;
-import com.android.systemui.shared.plugins.PluginManager;
 
 /**
  * Define an interface or abstract class as follows that includes the
diff --git a/packages/SystemUI/src/com/android/systemui/SystemUIApplication.java b/packages/SystemUI/src/com/android/systemui/SystemUIApplication.java
index 83747b4..191ac76 100644
--- a/packages/SystemUI/src/com/android/systemui/SystemUIApplication.java
+++ b/packages/SystemUI/src/com/android/systemui/SystemUIApplication.java
@@ -115,7 +115,8 @@
         setTheme(R.style.Theme_SystemUI);
 
         if (Process.myUserHandle().equals(UserHandle.SYSTEM)) {
-            IntentFilter bootCompletedFilter = new IntentFilter(Intent.ACTION_BOOT_COMPLETED);
+            IntentFilter bootCompletedFilter = new
+                    IntentFilter(Intent.ACTION_LOCKED_BOOT_COMPLETED);
             bootCompletedFilter.setPriority(IntentFilter.SYSTEM_HIGH_PRIORITY);
 
             // If SF GPU context priority is set to realtime, then SysUI should run at high.
diff --git a/packages/SystemUI/src/com/android/systemui/biometrics/AuthContainerView.java b/packages/SystemUI/src/com/android/systemui/biometrics/AuthContainerView.java
index 94f7158..68e1f72 100644
--- a/packages/SystemUI/src/com/android/systemui/biometrics/AuthContainerView.java
+++ b/packages/SystemUI/src/com/android/systemui/biometrics/AuthContainerView.java
@@ -129,6 +129,7 @@
     private final float mTranslationY;
     @VisibleForTesting @ContainerState int mContainerState = STATE_UNKNOWN;
     private final Set<Integer> mFailedModalities = new HashSet<Integer>();
+    private OnBackInvokedDispatcher mOnBackInvokedDispatcher;
     private final OnBackInvokedCallback mBackCallback = this::onBackInvoked;
 
     private final @Background DelayableExecutor mBackgroundExecutor;
@@ -497,9 +498,9 @@
                         .start();
             });
         }
-        OnBackInvokedDispatcher dispatcher = findOnBackInvokedDispatcher();
-        if (dispatcher != null) {
-            dispatcher.registerOnBackInvokedCallback(
+        mOnBackInvokedDispatcher = findOnBackInvokedDispatcher();
+        if (mOnBackInvokedDispatcher != null) {
+            mOnBackInvokedDispatcher.registerOnBackInvokedCallback(
                     OnBackInvokedDispatcher.PRIORITY_DEFAULT, mBackCallback);
         }
     }
@@ -600,11 +601,11 @@
 
     @Override
     public void onDetachedFromWindow() {
-        OnBackInvokedDispatcher dispatcher = findOnBackInvokedDispatcher();
-        if (dispatcher != null) {
-            findOnBackInvokedDispatcher().unregisterOnBackInvokedCallback(mBackCallback);
-        }
         super.onDetachedFromWindow();
+        if (mOnBackInvokedDispatcher != null) {
+            mOnBackInvokedDispatcher.unregisterOnBackInvokedCallback(mBackCallback);
+            mOnBackInvokedDispatcher = null;
+        }
         mWakefulnessLifecycle.removeObserver(this);
     }
 
diff --git a/packages/SystemUI/src/com/android/systemui/biometrics/AuthCredentialPasswordView.java b/packages/SystemUI/src/com/android/systemui/biometrics/AuthCredentialPasswordView.java
index 76cd3f4..e43c0b9 100644
--- a/packages/SystemUI/src/com/android/systemui/biometrics/AuthCredentialPasswordView.java
+++ b/packages/SystemUI/src/com/android/systemui/biometrics/AuthCredentialPasswordView.java
@@ -35,6 +35,8 @@
 import android.view.inputmethod.InputMethodManager;
 import android.widget.ImeAwareEditText;
 import android.widget.TextView;
+import android.window.OnBackInvokedCallback;
+import android.window.OnBackInvokedDispatcher;
 
 import com.android.internal.widget.LockPatternChecker;
 import com.android.internal.widget.LockPatternUtils;
@@ -58,6 +60,8 @@
     private ViewGroup mAuthCredentialHeader;
     private ViewGroup mAuthCredentialInput;
     private int mBottomInset = 0;
+    private OnBackInvokedDispatcher mOnBackInvokedDispatcher;
+    private final OnBackInvokedCallback mBackCallback = this::onBackInvoked;
 
     public AuthCredentialPasswordView(Context context,
             AttributeSet attrs) {
@@ -79,8 +83,7 @@
                 return false;
             }
             if (event.getAction() == KeyEvent.ACTION_UP) {
-                mContainerView.sendEarlyUserCanceled();
-                mContainerView.animateAway(AuthDialogCallback.DISMISSED_USER_CANCELED);
+                onBackInvoked();
             }
             return true;
         });
@@ -88,6 +91,11 @@
         setOnApplyWindowInsetsListener(this);
     }
 
+    private void onBackInvoked() {
+        mContainerView.sendEarlyUserCanceled();
+        mContainerView.animateAway(AuthDialogCallback.DISMISSED_USER_CANCELED);
+    }
+
     @Override
     protected void onAttachedToWindow() {
         super.onAttachedToWindow();
@@ -100,6 +108,12 @@
 
         mPasswordField.requestFocus();
         mPasswordField.scheduleShowSoftInput();
+
+        mOnBackInvokedDispatcher = findOnBackInvokedDispatcher();
+        if (mOnBackInvokedDispatcher != null) {
+            mOnBackInvokedDispatcher.registerOnBackInvokedCallback(
+                    OnBackInvokedDispatcher.PRIORITY_DEFAULT, mBackCallback);
+        }
     }
 
     @Override
@@ -137,6 +151,15 @@
     }
 
     @Override
+    public void onDetachedFromWindow() {
+        super.onDetachedFromWindow();
+        if (mOnBackInvokedDispatcher != null) {
+            mOnBackInvokedDispatcher.unregisterOnBackInvokedCallback(mBackCallback);
+            mOnBackInvokedDispatcher = null;
+        }
+    }
+
+    @Override
     protected void onCredentialVerified(@NonNull VerifyCredentialResponse response,
             int timeoutMs) {
         super.onCredentialVerified(response, timeoutMs);
diff --git a/packages/SystemUI/src/com/android/systemui/classifier/FalsingManagerProxy.java b/packages/SystemUI/src/com/android/systemui/classifier/FalsingManagerProxy.java
index c4723e8..5c905df 100644
--- a/packages/SystemUI/src/com/android/systemui/classifier/FalsingManagerProxy.java
+++ b/packages/SystemUI/src/com/android/systemui/classifier/FalsingManagerProxy.java
@@ -29,7 +29,7 @@
 import com.android.systemui.plugins.FalsingManager;
 import com.android.systemui.plugins.FalsingPlugin;
 import com.android.systemui.plugins.PluginListener;
-import com.android.systemui.shared.plugins.PluginManager;
+import com.android.systemui.plugins.PluginManager;
 import com.android.systemui.util.DeviceConfigProxy;
 
 import java.io.PrintWriter;
diff --git a/packages/SystemUI/src/com/android/systemui/doze/DozeService.java b/packages/SystemUI/src/com/android/systemui/doze/DozeService.java
index e8d7e46..f8bd1e7 100644
--- a/packages/SystemUI/src/com/android/systemui/doze/DozeService.java
+++ b/packages/SystemUI/src/com/android/systemui/doze/DozeService.java
@@ -27,7 +27,7 @@
 import com.android.systemui.plugins.DozeServicePlugin;
 import com.android.systemui.plugins.DozeServicePlugin.RequestDoze;
 import com.android.systemui.plugins.PluginListener;
-import com.android.systemui.shared.plugins.PluginManager;
+import com.android.systemui.plugins.PluginManager;
 
 import java.io.FileDescriptor;
 import java.io.PrintWriter;
diff --git a/packages/SystemUI/src/com/android/systemui/doze/DozeTransitionListener.kt b/packages/SystemUI/src/com/android/systemui/doze/DozeTransitionListener.kt
new file mode 100644
index 0000000..12ceedd
--- /dev/null
+++ b/packages/SystemUI/src/com/android/systemui/doze/DozeTransitionListener.kt
@@ -0,0 +1,48 @@
+/*
+ * Copyright (C) 2022 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.doze
+
+import com.android.systemui.dagger.SysUISingleton
+import com.android.systemui.statusbar.policy.CallbackController
+import javax.inject.Inject
+
+/** Receives doze transition events, and passes those events to registered callbacks. */
+@SysUISingleton
+class DozeTransitionListener @Inject constructor() :
+    DozeMachine.Part, CallbackController<DozeTransitionCallback> {
+    val callbacks = mutableSetOf<DozeTransitionCallback>()
+    var oldState = DozeMachine.State.UNINITIALIZED
+    var newState = DozeMachine.State.UNINITIALIZED
+
+    override fun transitionTo(oldState: DozeMachine.State, newState: DozeMachine.State) {
+        this.oldState = oldState
+        this.newState = newState
+        callbacks.forEach { it.onDozeTransition(oldState, newState) }
+    }
+
+    override fun addCallback(callback: DozeTransitionCallback) {
+        callbacks.add(callback)
+    }
+
+    override fun removeCallback(callback: DozeTransitionCallback) {
+        callbacks.remove(callback)
+    }
+}
+
+interface DozeTransitionCallback {
+    fun onDozeTransition(oldState: DozeMachine.State, newState: DozeMachine.State)
+}
diff --git a/packages/SystemUI/src/com/android/systemui/doze/dagger/DozeModule.java b/packages/SystemUI/src/com/android/systemui/doze/dagger/DozeModule.java
index 98cd2d7..069344f 100644
--- a/packages/SystemUI/src/com/android/systemui/doze/dagger/DozeModule.java
+++ b/packages/SystemUI/src/com/android/systemui/doze/dagger/DozeModule.java
@@ -35,6 +35,7 @@
 import com.android.systemui.doze.DozeSensors;
 import com.android.systemui.doze.DozeSuppressor;
 import com.android.systemui.doze.DozeSuspendScreenStatePreventingAdapter;
+import com.android.systemui.doze.DozeTransitionListener;
 import com.android.systemui.doze.DozeTriggers;
 import com.android.systemui.doze.DozeUi;
 import com.android.systemui.doze.DozeWallpaperState;
@@ -83,7 +84,7 @@
             DozeUi dozeUi, DozeScreenState dozeScreenState,
             DozeScreenBrightness dozeScreenBrightness, DozeWallpaperState dozeWallpaperState,
             DozeDockHandler dozeDockHandler, DozeAuthRemover dozeAuthRemover,
-            DozeSuppressor dozeSuppressor) {
+            DozeSuppressor dozeSuppressor, DozeTransitionListener dozeTransitionListener) {
         return new DozeMachine.Part[]{
                 dozePauser,
                 dozeFalsingManagerAdapter,
@@ -94,7 +95,8 @@
                 dozeWallpaperState,
                 dozeDockHandler,
                 dozeAuthRemover,
-                dozeSuppressor
+                dozeSuppressor,
+                dozeTransitionListener
         };
     }
 
diff --git a/packages/SystemUI/src/com/android/systemui/dreams/complication/dagger/RegisteredComplicationsModule.java b/packages/SystemUI/src/com/android/systemui/dreams/complication/dagger/RegisteredComplicationsModule.java
index 69b85b5..a514c47 100644
--- a/packages/SystemUI/src/com/android/systemui/dreams/complication/dagger/RegisteredComplicationsModule.java
+++ b/packages/SystemUI/src/com/android/systemui/dreams/complication/dagger/RegisteredComplicationsModule.java
@@ -81,7 +81,8 @@
                 ComplicationLayoutParams.DIRECTION_UP,
                 DREAM_HOME_CONTROLS_CHIP_COMPLICATION_WEIGHT,
                 // Add margin to the bottom of home controls to horizontally align with smartspace.
-                res.getDimensionPixelSize(R.dimen.dream_overlay_complication_clock_time_padding));
+                res.getDimensionPixelSize(
+                        R.dimen.dream_overlay_complication_home_controls_padding));
     }
 
     /**
diff --git a/packages/SystemUI/src/com/android/systemui/flags/FeatureFlagsDebug.java b/packages/SystemUI/src/com/android/systemui/flags/FeatureFlagsDebug.java
index d1a14a1..5dae0a2 100644
--- a/packages/SystemUI/src/com/android/systemui/flags/FeatureFlagsDebug.java
+++ b/packages/SystemUI/src/com/android/systemui/flags/FeatureFlagsDebug.java
@@ -86,7 +86,7 @@
             new ServerFlagReader.ChangeListener() {
                 @Override
                 public void onChange() {
-                    mRestarter.restartSystemUI();
+                    mRestarter.restart();
                 }
             };
 
@@ -326,7 +326,9 @@
             Log.i(TAG, "SystemUI Restart Suppressed");
             return;
         }
-        mRestarter.restartSystemUI();
+        Log.i(TAG, "Restarting SystemUI");
+        // SysUI starts back when up exited. Is there a better way to do this?
+        System.exit(0);
     }
 
     private void restartAndroid(boolean requestSuppress) {
@@ -334,7 +336,7 @@
             Log.i(TAG, "Android Restart Suppressed");
             return;
         }
-        mRestarter.restartAndroid();
+        mRestarter.restart();
     }
 
     void setBooleanFlagInternal(Flag<?> flag, boolean value) {
diff --git a/packages/SystemUI/src/com/android/systemui/flags/FeatureFlagsDebugRestarter.kt b/packages/SystemUI/src/com/android/systemui/flags/FeatureFlagsDebugRestarter.kt
index 069e612..3d9f627 100644
--- a/packages/SystemUI/src/com/android/systemui/flags/FeatureFlagsDebugRestarter.kt
+++ b/packages/SystemUI/src/com/android/systemui/flags/FeatureFlagsDebugRestarter.kt
@@ -28,8 +28,6 @@
     private val systemExitRestarter: SystemExitRestarter,
 ) : Restarter {
 
-    private var androidRestartRequested = false
-
     val observer =
         object : WakefulnessLifecycle.Observer {
             override fun onFinishedGoingToSleep() {
@@ -38,18 +36,8 @@
             }
         }
 
-    override fun restartSystemUI() {
-        Log.d(FeatureFlagsDebug.TAG, "SystemUI Restart requested. Restarting on next screen off.")
-        scheduleRestart()
-    }
-
-    override fun restartAndroid() {
-        Log.d(FeatureFlagsDebug.TAG, "Android Restart requested. Restarting on next screen off.")
-        androidRestartRequested = true
-        scheduleRestart()
-    }
-
-    fun scheduleRestart() {
+    override fun restart() {
+        Log.d(FeatureFlagsDebug.TAG, "Restart requested. Restarting on next screen off.")
         if (wakefulnessLifecycle.wakefulness == WakefulnessLifecycle.WAKEFULNESS_ASLEEP) {
             restartNow()
         } else {
@@ -58,10 +46,6 @@
     }
 
     private fun restartNow() {
-        if (androidRestartRequested) {
-            systemExitRestarter.restartAndroid()
-        } else {
-            systemExitRestarter.restartSystemUI()
-        }
+        systemExitRestarter.restart()
     }
 }
diff --git a/packages/SystemUI/src/com/android/systemui/flags/FeatureFlagsRelease.java b/packages/SystemUI/src/com/android/systemui/flags/FeatureFlagsRelease.java
index 8bddacc..3c83682 100644
--- a/packages/SystemUI/src/com/android/systemui/flags/FeatureFlagsRelease.java
+++ b/packages/SystemUI/src/com/android/systemui/flags/FeatureFlagsRelease.java
@@ -61,7 +61,7 @@
             new ServerFlagReader.ChangeListener() {
                 @Override
                 public void onChange() {
-                    mRestarter.restartSystemUI();
+                    mRestarter.restart();
                 }
             };
 
diff --git a/packages/SystemUI/src/com/android/systemui/flags/FeatureFlagsReleaseRestarter.kt b/packages/SystemUI/src/com/android/systemui/flags/FeatureFlagsReleaseRestarter.kt
index 7ff3876..a3f0f66 100644
--- a/packages/SystemUI/src/com/android/systemui/flags/FeatureFlagsReleaseRestarter.kt
+++ b/packages/SystemUI/src/com/android/systemui/flags/FeatureFlagsReleaseRestarter.kt
@@ -34,48 +34,35 @@
     @Background private val bgExecutor: DelayableExecutor,
     private val systemExitRestarter: SystemExitRestarter
 ) : Restarter {
-    var listenersAdded = false
+    var shouldRestart = false
     var pendingRestart: Runnable? = null
-    var androidRestartRequested = false
 
     val observer =
         object : WakefulnessLifecycle.Observer {
             override fun onFinishedGoingToSleep() {
-                scheduleRestart()
+                maybeScheduleRestart()
             }
         }
 
     val batteryCallback =
         object : BatteryController.BatteryStateChangeCallback {
             override fun onBatteryLevelChanged(level: Int, pluggedIn: Boolean, charging: Boolean) {
-                scheduleRestart()
+                maybeScheduleRestart()
             }
         }
 
-    override fun restartSystemUI() {
-        Log.d(
-            FeatureFlagsDebug.TAG,
-            "SystemUI Restart requested. Restarting when plugged in and idle."
-        )
-        scheduleRestart()
-    }
-
-    override fun restartAndroid() {
-        Log.d(
-            FeatureFlagsDebug.TAG,
-            "Android Restart requested. Restarting when plugged in and idle."
-        )
-        androidRestartRequested = true
-        scheduleRestart()
-    }
-
-    private fun scheduleRestart() {
-        // Don't bother adding listeners twice.
-        if (!listenersAdded) {
-            listenersAdded = true
+    override fun restart() {
+        Log.d(FeatureFlagsDebug.TAG, "Restart requested. Restarting when plugged in and idle.")
+        if (!shouldRestart) {
+            // Don't bother scheduling twice.
+            shouldRestart = true
             wakefulnessLifecycle.addObserver(observer)
             batteryController.addCallback(batteryCallback)
+            maybeScheduleRestart()
         }
+    }
+
+    private fun maybeScheduleRestart() {
         if (
             wakefulnessLifecycle.wakefulness == WAKEFULNESS_ASLEEP && batteryController.isPluggedIn
         ) {
@@ -90,10 +77,6 @@
 
     private fun restartNow() {
         Log.d(FeatureFlagsRelease.TAG, "Restarting due to systemui flag change")
-        if (androidRestartRequested) {
-            systemExitRestarter.restartAndroid()
-        } else {
-            systemExitRestarter.restartSystemUI()
-        }
+        systemExitRestarter.restart()
     }
 }
diff --git a/packages/SystemUI/src/com/android/systemui/flags/Flags.kt b/packages/SystemUI/src/com/android/systemui/flags/Flags.kt
index 81ee578..7558b41 100644
--- a/packages/SystemUI/src/com/android/systemui/flags/Flags.kt
+++ b/packages/SystemUI/src/com/android/systemui/flags/Flags.kt
@@ -75,7 +75,11 @@
     @JvmField
     val NOTIFICATION_DISMISSAL_FADE =
         unreleasedFlag(113, "notification_dismissal_fade", teamfood = true)
-    val STABILITY_INDEX_FIX = unreleasedFlag(114, "stability_index_fix", teamfood = true)
+
+    // TODO(b/259558771): Tracking Bug
+    val STABILITY_INDEX_FIX = releasedFlag(114, "stability_index_fix")
+
+    // TODO(b/259559750): Tracking Bug
     val SEMI_STABLE_SORT = unreleasedFlag(115, "semi_stable_sort", teamfood = true)
 
     @JvmField
@@ -90,6 +94,10 @@
     // TODO(b/257506350): Tracking Bug
     val FSI_CHROME = unreleasedFlag(117, "fsi_chrome")
 
+    @JvmField
+    val SIMPLIFIED_APPEAR_FRACTION =
+        unreleasedFlag(259395680, "simplified_appear_fraction", teamfood = true)
+
     // TODO(b/257315550): Tracking Bug
     val NO_HUN_FOR_OLD_WHEN = unreleasedFlag(118, "no_hun_for_old_when")
 
@@ -153,8 +161,8 @@
         unreleasedFlag(216, "customizable_lock_screen_quick_affordances", teamfood = false)
 
     /** Shows chipbar UI whenever the device is unlocked by ActiveUnlock (watch). */
-    // TODO(b/240196500): Tracking Bug
-    @JvmField val ACTIVE_UNLOCK_CHIPBAR = unreleasedFlag(217, "active_unlock_chipbar")
+    // TODO(b/256513609): Tracking Bug
+    @JvmField val ACTIVE_UNLOCK_CHIPBAR = releasedFlag(217, "active_unlock_chipbar")
 
     // 300 - power menu
     // TODO(b/254512600): Tracking Bug
@@ -181,9 +189,6 @@
             "qs_user_detail_shortcut"
         )
 
-    // TODO(b/254512747): Tracking Bug
-    val NEW_HEADER = releasedFlag(505, "new_header")
-
     // TODO(b/254512383): Tracking Bug
     @JvmField
     val FULL_SCREEN_USER_SWITCHER =
@@ -414,4 +419,7 @@
     @JvmField val UDFPS_NEW_TOUCH_DETECTION = unreleasedFlag(2200, "udfps_new_touch_detection")
     @JvmField val UDFPS_ELLIPSE_DEBUG_UI = unreleasedFlag(2201, "udfps_ellipse_debug")
     @JvmField val UDFPS_ELLIPSE_DETECTION = unreleasedFlag(2202, "udfps_ellipse_detection")
+
+    // TODO(b259590361): Tracking bug
+    val EXPERIMENTAL_FLAG = unreleasedFlag(2, "exp_flag_release")
 }
diff --git a/packages/SystemUI/src/com/android/systemui/flags/Restarter.kt b/packages/SystemUI/src/com/android/systemui/flags/Restarter.kt
index ce8b821..8f095a2 100644
--- a/packages/SystemUI/src/com/android/systemui/flags/Restarter.kt
+++ b/packages/SystemUI/src/com/android/systemui/flags/Restarter.kt
@@ -16,7 +16,5 @@
 package com.android.systemui.flags
 
 interface Restarter {
-    fun restartSystemUI()
-
-    fun restartAndroid()
-}
+    fun restart()
+}
\ No newline at end of file
diff --git a/packages/SystemUI/src/com/android/systemui/flags/SystemExitRestarter.kt b/packages/SystemUI/src/com/android/systemui/flags/SystemExitRestarter.kt
index 89daa64..f1b1be4 100644
--- a/packages/SystemUI/src/com/android/systemui/flags/SystemExitRestarter.kt
+++ b/packages/SystemUI/src/com/android/systemui/flags/SystemExitRestarter.kt
@@ -16,19 +16,10 @@
 
 package com.android.systemui.flags
 
-import com.android.internal.statusbar.IStatusBarService
 import javax.inject.Inject
 
-class SystemExitRestarter
-@Inject
-constructor(
-    private val barService: IStatusBarService,
-) : Restarter {
-    override fun restartAndroid() {
-        barService.restart()
-    }
-
-    override fun restartSystemUI() {
+class SystemExitRestarter @Inject constructor() : Restarter {
+    override fun restart() {
         System.exit(0)
     }
 }
diff --git a/packages/SystemUI/src/com/android/systemui/keyguard/KeyguardQuickAffordanceProvider.kt b/packages/SystemUI/src/com/android/systemui/keyguard/KeyguardQuickAffordanceProvider.kt
index 1f1ed00..bfc60c1 100644
--- a/packages/SystemUI/src/com/android/systemui/keyguard/KeyguardQuickAffordanceProvider.kt
+++ b/packages/SystemUI/src/com/android/systemui/keyguard/KeyguardQuickAffordanceProvider.kt
@@ -199,16 +199,19 @@
                 arrayOf(
                     Contract.SelectionTable.Columns.SLOT_ID,
                     Contract.SelectionTable.Columns.AFFORDANCE_ID,
+                    Contract.SelectionTable.Columns.AFFORDANCE_NAME,
                 )
             )
             .apply {
-                val affordanceIdsBySlotId = interactor.getSelections()
-                affordanceIdsBySlotId.entries.forEach { (slotId, affordanceIds) ->
-                    affordanceIds.forEach { affordanceId ->
+                val affordanceRepresentationsBySlotId = interactor.getSelections()
+                affordanceRepresentationsBySlotId.entries.forEach {
+                    (slotId, affordanceRepresentations) ->
+                    affordanceRepresentations.forEach { affordanceRepresentation ->
                         addRow(
                             arrayOf(
                                 slotId,
-                                affordanceId,
+                                affordanceRepresentation.id,
+                                affordanceRepresentation.name,
                             )
                         )
                     }
diff --git a/packages/SystemUI/src/com/android/systemui/keyguard/KeyguardViewMediator.java b/packages/SystemUI/src/com/android/systemui/keyguard/KeyguardViewMediator.java
index 250317b..8403fe6 100644
--- a/packages/SystemUI/src/com/android/systemui/keyguard/KeyguardViewMediator.java
+++ b/packages/SystemUI/src/com/android/systemui/keyguard/KeyguardViewMediator.java
@@ -137,6 +137,7 @@
 import com.android.systemui.statusbar.phone.DozeParameters;
 import com.android.systemui.statusbar.phone.KeyguardBypassController;
 import com.android.systemui.statusbar.phone.ScreenOffAnimationController;
+import com.android.systemui.statusbar.phone.ScrimController;
 import com.android.systemui.statusbar.policy.KeyguardStateController;
 import com.android.systemui.statusbar.policy.UserSwitcherController;
 import com.android.systemui.util.DeviceConfigProxy;
@@ -851,6 +852,7 @@
                 @Override
                 public void onLaunchAnimationStart(boolean isExpandingFullyAbove) {
                     mOccludeAnimationPlaying = true;
+                    mScrimControllerLazy.get().setOccludeAnimationPlaying(true);
                 }
 
                 @Override
@@ -861,6 +863,7 @@
 
                     // Ensure keyguard state is set correctly if we're cancelled.
                     mCentralSurfaces.updateIsKeyguard();
+                    mScrimControllerLazy.get().setOccludeAnimationPlaying(false);
                 }
 
                 @Override
@@ -874,6 +877,7 @@
                     // Hide the keyguard now that we're done launching the occluding activity over
                     // it.
                     mCentralSurfaces.updateIsKeyguard();
+                    mScrimControllerLazy.get().setOccludeAnimationPlaying(false);
 
                     mInteractionJankMonitor.end(CUJ_LOCKSCREEN_OCCLUSION);
                 }
@@ -895,25 +899,32 @@
                 @NonNull
                 @Override
                 public LaunchAnimator.State createAnimatorState() {
-                    final int width = getLaunchContainer().getWidth();
-                    final int height = getLaunchContainer().getHeight();
-
-                    final float initialHeight = height / 3f;
-                    final float initialWidth = width / 3f;
+                    final int fullWidth = getLaunchContainer().getWidth();
+                    final int fullHeight = getLaunchContainer().getHeight();
 
                     if (mUpdateMonitor.isSecureCameraLaunchedOverKeyguard()) {
+                        final float initialHeight = fullHeight / 3f;
+                        final float initialWidth = fullWidth / 3f;
+
                         // Start the animation near the power button, at one-third size, since the
                         // camera was launched from the power button.
                         return new LaunchAnimator.State(
                                 (int) (mPowerButtonY - initialHeight / 2f) /* top */,
                                 (int) (mPowerButtonY + initialHeight / 2f) /* bottom */,
-                                (int) (width - initialWidth) /* left */,
-                                width /* right */,
+                                (int) (fullWidth - initialWidth) /* left */,
+                                fullWidth /* right */,
                                 mWindowCornerRadius, mWindowCornerRadius);
                     } else {
-                        // Start the animation in the center of the screen, scaled down.
+                        final float initialHeight = fullHeight / 2f;
+                        final float initialWidth = fullWidth / 2f;
+
+                        // Start the animation in the center of the screen, scaled down to half
+                        // size.
                         return new LaunchAnimator.State(
-                                height / 2, height / 2, width / 2, width / 2,
+                                (int) (fullHeight - initialHeight) / 2,
+                                (int) (initialHeight + (fullHeight - initialHeight) / 2),
+                                (int) (fullWidth - initialWidth) / 2,
+                                (int) (initialWidth + (fullWidth - initialWidth) / 2),
                                 mWindowCornerRadius, mWindowCornerRadius);
                     }
                 }
@@ -1131,6 +1142,7 @@
     private ScreenOnCoordinator mScreenOnCoordinator;
 
     private Lazy<ActivityLaunchAnimator> mActivityLaunchAnimator;
+    private Lazy<ScrimController> mScrimControllerLazy;
 
     /**
      * Injected constructor. See {@link KeyguardModule}.
@@ -1161,7 +1173,8 @@
             DreamOverlayStateController dreamOverlayStateController,
             Lazy<ShadeController> shadeControllerLazy,
             Lazy<NotificationShadeWindowController> notificationShadeWindowControllerLazy,
-            Lazy<ActivityLaunchAnimator> activityLaunchAnimator) {
+            Lazy<ActivityLaunchAnimator> activityLaunchAnimator,
+            Lazy<ScrimController> scrimControllerLazy) {
         mContext = context;
         mUserTracker = userTracker;
         mFalsingCollector = falsingCollector;
@@ -1206,6 +1219,7 @@
         mDreamOverlayStateController = dreamOverlayStateController;
 
         mActivityLaunchAnimator = activityLaunchAnimator;
+        mScrimControllerLazy = scrimControllerLazy;
 
         mPowerButtonY = context.getResources().getDimensionPixelSize(
                 R.dimen.physical_power_button_center_screen_location_y);
diff --git a/packages/SystemUI/src/com/android/systemui/keyguard/dagger/KeyguardModule.java b/packages/SystemUI/src/com/android/systemui/keyguard/dagger/KeyguardModule.java
index ef3c443..47ef0fa 100644
--- a/packages/SystemUI/src/com/android/systemui/keyguard/dagger/KeyguardModule.java
+++ b/packages/SystemUI/src/com/android/systemui/keyguard/dagger/KeyguardModule.java
@@ -54,6 +54,7 @@
 import com.android.systemui.statusbar.SysuiStatusBarStateController;
 import com.android.systemui.statusbar.phone.DozeParameters;
 import com.android.systemui.statusbar.phone.ScreenOffAnimationController;
+import com.android.systemui.statusbar.phone.ScrimController;
 import com.android.systemui.statusbar.policy.KeyguardStateController;
 import com.android.systemui.statusbar.policy.UserSwitcherController;
 import com.android.systemui.util.DeviceConfigProxy;
@@ -113,7 +114,8 @@
             DreamOverlayStateController dreamOverlayStateController,
             Lazy<ShadeController> shadeController,
             Lazy<NotificationShadeWindowController> notificationShadeWindowController,
-            Lazy<ActivityLaunchAnimator> activityLaunchAnimator) {
+            Lazy<ActivityLaunchAnimator> activityLaunchAnimator,
+            Lazy<ScrimController> scrimControllerLazy) {
         return new KeyguardViewMediator(
                 context,
                 userTracker,
@@ -142,7 +144,8 @@
                 dreamOverlayStateController,
                 shadeController,
                 notificationShadeWindowController,
-                activityLaunchAnimator);
+                activityLaunchAnimator,
+                scrimControllerLazy);
     }
 
     /** */
diff --git a/packages/SystemUI/src/com/android/systemui/keyguard/data/repository/KeyguardBouncerRepository.kt b/packages/SystemUI/src/com/android/systemui/keyguard/data/repository/KeyguardBouncerRepository.kt
index 9a90fe7..783f752 100644
--- a/packages/SystemUI/src/com/android/systemui/keyguard/data/repository/KeyguardBouncerRepository.kt
+++ b/packages/SystemUI/src/com/android/systemui/keyguard/data/repository/KeyguardBouncerRepository.kt
@@ -23,10 +23,7 @@
 import com.android.systemui.keyguard.shared.model.KeyguardBouncerModel
 import com.android.systemui.statusbar.phone.KeyguardBouncer
 import javax.inject.Inject
-import kotlinx.coroutines.channels.BufferOverflow
-import kotlinx.coroutines.flow.MutableSharedFlow
 import kotlinx.coroutines.flow.MutableStateFlow
-import kotlinx.coroutines.flow.asSharedFlow
 import kotlinx.coroutines.flow.asStateFlow
 
 /** Encapsulates app state for the lock screen primary and alternate bouncer. */
@@ -71,12 +68,8 @@
     private val _keyguardAuthenticated = MutableStateFlow<Boolean?>(null)
     /** Determines if user is already unlocked */
     val keyguardAuthenticated = _keyguardAuthenticated.asStateFlow()
-    private val _showMessage =
-        MutableSharedFlow<BouncerShowMessageModel?>(
-            replay = 1,
-            onBufferOverflow = BufferOverflow.DROP_OLDEST
-        )
-    val showMessage = _showMessage.asSharedFlow()
+    private val _showMessage = MutableStateFlow<BouncerShowMessageModel?>(null)
+    val showMessage = _showMessage.asStateFlow()
     private val _resourceUpdateRequests = MutableStateFlow(false)
     val resourceUpdateRequests = _resourceUpdateRequests.asStateFlow()
     val bouncerPromptReason: Int
@@ -125,7 +118,7 @@
     }
 
     fun setShowMessage(bouncerShowMessageModel: BouncerShowMessageModel?) {
-        _showMessage.tryEmit(bouncerShowMessageModel)
+        _showMessage.value = bouncerShowMessageModel
     }
 
     fun setKeyguardAuthenticated(keyguardAuthenticated: Boolean?) {
diff --git a/packages/SystemUI/src/com/android/systemui/keyguard/data/repository/KeyguardRepository.kt b/packages/SystemUI/src/com/android/systemui/keyguard/data/repository/KeyguardRepository.kt
index 9d5d8bb..796f2b4 100644
--- a/packages/SystemUI/src/com/android/systemui/keyguard/data/repository/KeyguardRepository.kt
+++ b/packages/SystemUI/src/com/android/systemui/keyguard/data/repository/KeyguardRepository.kt
@@ -23,9 +23,14 @@
 import com.android.systemui.common.shared.model.Position
 import com.android.systemui.dagger.SysUISingleton
 import com.android.systemui.doze.DozeHost
+import com.android.systemui.doze.DozeMachine
+import com.android.systemui.doze.DozeTransitionCallback
+import com.android.systemui.doze.DozeTransitionListener
 import com.android.systemui.keyguard.WakefulnessLifecycle
 import com.android.systemui.keyguard.WakefulnessLifecycle.Wakefulness
 import com.android.systemui.keyguard.shared.model.BiometricUnlockModel
+import com.android.systemui.keyguard.shared.model.DozeStateModel
+import com.android.systemui.keyguard.shared.model.DozeTransitionModel
 import com.android.systemui.keyguard.shared.model.StatusBarState
 import com.android.systemui.keyguard.shared.model.WakefulnessModel
 import com.android.systemui.plugins.statusbar.StatusBarStateController
@@ -108,6 +113,9 @@
      */
     val dozeAmount: Flow<Float>
 
+    /** Doze state information, as it transitions */
+    val dozeTransitionModel: Flow<DozeTransitionModel>
+
     /** Observable for the [StatusBarState] */
     val statusBarState: Flow<StatusBarState>
 
@@ -154,6 +162,7 @@
     biometricUnlockController: BiometricUnlockController,
     private val keyguardStateController: KeyguardStateController,
     private val keyguardUpdateMonitor: KeyguardUpdateMonitor,
+    private val dozeTransitionListener: DozeTransitionListener,
 ) : KeyguardRepository {
     private val _animateBottomAreaDozingTransitions = MutableStateFlow(false)
     override val animateBottomAreaDozingTransitions =
@@ -286,6 +295,37 @@
         awaitClose { statusBarStateController.removeCallback(callback) }
     }
 
+    override val dozeTransitionModel: Flow<DozeTransitionModel> = conflatedCallbackFlow {
+        val callback =
+            object : DozeTransitionCallback {
+                override fun onDozeTransition(
+                    oldState: DozeMachine.State,
+                    newState: DozeMachine.State
+                ) {
+                    trySendWithFailureLogging(
+                        DozeTransitionModel(
+                            from = dozeMachineStateToModel(oldState),
+                            to = dozeMachineStateToModel(newState),
+                        ),
+                        TAG,
+                        "doze transition model"
+                    )
+                }
+            }
+
+        dozeTransitionListener.addCallback(callback)
+        trySendWithFailureLogging(
+            DozeTransitionModel(
+                from = dozeMachineStateToModel(dozeTransitionListener.oldState),
+                to = dozeMachineStateToModel(dozeTransitionListener.newState),
+            ),
+            TAG,
+            "initial doze transition model"
+        )
+
+        awaitClose { dozeTransitionListener.removeCallback(callback) }
+    }
+
     override fun isKeyguardShowing(): Boolean {
         return keyguardStateController.isShowing
     }
@@ -407,6 +447,25 @@
         }
     }
 
+    private fun dozeMachineStateToModel(state: DozeMachine.State): DozeStateModel {
+        return when (state) {
+            DozeMachine.State.UNINITIALIZED -> DozeStateModel.UNINITIALIZED
+            DozeMachine.State.INITIALIZED -> DozeStateModel.INITIALIZED
+            DozeMachine.State.DOZE -> DozeStateModel.DOZE
+            DozeMachine.State.DOZE_SUSPEND_TRIGGERS -> DozeStateModel.DOZE_SUSPEND_TRIGGERS
+            DozeMachine.State.DOZE_AOD -> DozeStateModel.DOZE_AOD
+            DozeMachine.State.DOZE_REQUEST_PULSE -> DozeStateModel.DOZE_REQUEST_PULSE
+            DozeMachine.State.DOZE_PULSING -> DozeStateModel.DOZE_PULSING
+            DozeMachine.State.DOZE_PULSING_BRIGHT -> DozeStateModel.DOZE_PULSING_BRIGHT
+            DozeMachine.State.DOZE_PULSE_DONE -> DozeStateModel.DOZE_PULSE_DONE
+            DozeMachine.State.FINISH -> DozeStateModel.FINISH
+            DozeMachine.State.DOZE_AOD_PAUSED -> DozeStateModel.DOZE_AOD_PAUSED
+            DozeMachine.State.DOZE_AOD_PAUSING -> DozeStateModel.DOZE_AOD_PAUSING
+            DozeMachine.State.DOZE_AOD_DOCKED -> DozeStateModel.DOZE_AOD_DOCKED
+            else -> throw IllegalArgumentException("Invalid DozeMachine.State: state")
+        }
+    }
+
     companion object {
         private const val TAG = "KeyguardRepositoryImpl"
     }
diff --git a/packages/SystemUI/src/com/android/systemui/keyguard/domain/interactor/AodLockscreenTransitionInteractor.kt b/packages/SystemUI/src/com/android/systemui/keyguard/domain/interactor/AodLockscreenTransitionInteractor.kt
index e5521c7..2dbacd5 100644
--- a/packages/SystemUI/src/com/android/systemui/keyguard/domain/interactor/AodLockscreenTransitionInteractor.kt
+++ b/packages/SystemUI/src/com/android/systemui/keyguard/domain/interactor/AodLockscreenTransitionInteractor.kt
@@ -21,10 +21,9 @@
 import com.android.systemui.dagger.SysUISingleton
 import com.android.systemui.dagger.qualifiers.Application
 import com.android.systemui.keyguard.data.repository.KeyguardTransitionRepository
+import com.android.systemui.keyguard.shared.model.DozeStateModel
 import com.android.systemui.keyguard.shared.model.KeyguardState
 import com.android.systemui.keyguard.shared.model.TransitionInfo
-import com.android.systemui.keyguard.shared.model.WakefulnessModel.Companion.isSleepingOrStartingToSleep
-import com.android.systemui.keyguard.shared.model.WakefulnessModel.Companion.isWakingOrStartingToWake
 import com.android.systemui.util.kotlin.sample
 import javax.inject.Inject
 import kotlinx.coroutines.CoroutineScope
@@ -39,27 +38,24 @@
     private val keyguardInteractor: KeyguardInteractor,
     private val keyguardTransitionRepository: KeyguardTransitionRepository,
     private val keyguardTransitionInteractor: KeyguardTransitionInteractor,
-) : TransitionInteractor("AOD<->LOCKSCREEN") {
+) : TransitionInteractor(AodLockscreenTransitionInteractor::class.simpleName!!) {
 
     override fun start() {
+        listenForTransitionToAodFromLockscreen()
+        listenForTransitionToLockscreenFromAod()
+    }
+
+    private fun listenForTransitionToAodFromLockscreen() {
         scope.launch {
-            /*
-             * Listening to the startedKeyguardTransitionStep (last started step) allows this code
-             * to interrupt an active transition, as long as they were either going to LOCKSCREEN or
-             * AOD state. One example is when the user presses the power button in the middle of an
-             * active transition.
-             */
-            keyguardInteractor.wakefulnessState
+            keyguardInteractor
+                .dozeTransitionTo(DozeStateModel.DOZE_AOD)
                 .sample(
                     keyguardTransitionInteractor.startedKeyguardTransitionStep,
                     { a, b -> Pair(a, b) }
                 )
                 .collect { pair ->
-                    val (wakefulnessState, lastStartedStep) = pair
-                    if (
-                        isSleepingOrStartingToSleep(wakefulnessState) &&
-                            lastStartedStep.to == KeyguardState.LOCKSCREEN
-                    ) {
+                    val (dozeToAod, lastStartedStep) = pair
+                    if (lastStartedStep.to == KeyguardState.LOCKSCREEN) {
                         keyguardTransitionRepository.startTransition(
                             TransitionInfo(
                                 name,
@@ -68,10 +64,22 @@
                                 getAnimator(),
                             )
                         )
-                    } else if (
-                        isWakingOrStartingToWake(wakefulnessState) &&
-                            lastStartedStep.to == KeyguardState.AOD
-                    ) {
+                    }
+                }
+        }
+    }
+
+    private fun listenForTransitionToLockscreenFromAod() {
+        scope.launch {
+            keyguardInteractor
+                .dozeTransitionTo(DozeStateModel.FINISH)
+                .sample(
+                    keyguardTransitionInteractor.startedKeyguardTransitionStep,
+                    { a, b -> Pair(a, b) }
+                )
+                .collect { pair ->
+                    val (dozeToAod, lastStartedStep) = pair
+                    if (lastStartedStep.to == KeyguardState.AOD) {
                         keyguardTransitionRepository.startTransition(
                             TransitionInfo(
                                 name,
diff --git a/packages/SystemUI/src/com/android/systemui/keyguard/domain/interactor/AodToGoneTransitionInteractor.kt b/packages/SystemUI/src/com/android/systemui/keyguard/domain/interactor/AodToGoneTransitionInteractor.kt
index 7e01db3..2a220fc 100644
--- a/packages/SystemUI/src/com/android/systemui/keyguard/domain/interactor/AodToGoneTransitionInteractor.kt
+++ b/packages/SystemUI/src/com/android/systemui/keyguard/domain/interactor/AodToGoneTransitionInteractor.kt
@@ -40,7 +40,7 @@
     private val keyguardInteractor: KeyguardInteractor,
     private val keyguardTransitionRepository: KeyguardTransitionRepository,
     private val keyguardTransitionInteractor: KeyguardTransitionInteractor,
-) : TransitionInteractor("AOD->GONE") {
+) : TransitionInteractor(AodToGoneTransitionInteractor::class.simpleName!!) {
 
     private val wakeAndUnlockModes =
         setOf(WAKE_AND_UNLOCK, WAKE_AND_UNLOCK_FROM_DREAM, WAKE_AND_UNLOCK_PULSING)
diff --git a/packages/SystemUI/src/com/android/systemui/keyguard/domain/interactor/BouncerToGoneTransitionInteractor.kt b/packages/SystemUI/src/com/android/systemui/keyguard/domain/interactor/BouncerToGoneTransitionInteractor.kt
index dd29673..056c44d 100644
--- a/packages/SystemUI/src/com/android/systemui/keyguard/domain/interactor/BouncerToGoneTransitionInteractor.kt
+++ b/packages/SystemUI/src/com/android/systemui/keyguard/domain/interactor/BouncerToGoneTransitionInteractor.kt
@@ -40,7 +40,7 @@
     private val shadeRepository: ShadeRepository,
     private val keyguardTransitionRepository: KeyguardTransitionRepository,
     private val keyguardTransitionInteractor: KeyguardTransitionInteractor
-) : TransitionInteractor("BOUNCER->GONE") {
+) : TransitionInteractor(BouncerToGoneTransitionInteractor::class.simpleName!!) {
 
     private var transitionId: UUID? = null
 
diff --git a/packages/SystemUI/src/com/android/systemui/keyguard/domain/interactor/DreamingLockscreenTransitionInteractor.kt b/packages/SystemUI/src/com/android/systemui/keyguard/domain/interactor/DreamingLockscreenTransitionInteractor.kt
index c44cda4..9cbf9ea 100644
--- a/packages/SystemUI/src/com/android/systemui/keyguard/domain/interactor/DreamingLockscreenTransitionInteractor.kt
+++ b/packages/SystemUI/src/com/android/systemui/keyguard/domain/interactor/DreamingLockscreenTransitionInteractor.kt
@@ -21,12 +21,14 @@
 import com.android.systemui.dagger.SysUISingleton
 import com.android.systemui.dagger.qualifiers.Application
 import com.android.systemui.keyguard.data.repository.KeyguardTransitionRepository
+import com.android.systemui.keyguard.shared.model.DozeStateModel
 import com.android.systemui.keyguard.shared.model.KeyguardState
 import com.android.systemui.keyguard.shared.model.TransitionInfo
 import com.android.systemui.util.kotlin.sample
 import javax.inject.Inject
 import kotlinx.coroutines.CoroutineScope
 import kotlinx.coroutines.flow.collect
+import kotlinx.coroutines.flow.combine
 import kotlinx.coroutines.launch
 
 @SysUISingleton
@@ -37,32 +39,43 @@
     private val keyguardInteractor: KeyguardInteractor,
     private val keyguardTransitionRepository: KeyguardTransitionRepository,
     private val keyguardTransitionInteractor: KeyguardTransitionInteractor,
-) : TransitionInteractor("DREAMING<->LOCKSCREEN") {
+) : TransitionInteractor(DreamingLockscreenTransitionInteractor::class.simpleName!!) {
 
     override fun start() {
         scope.launch {
             keyguardInteractor.isDreaming
-                .sample(keyguardTransitionInteractor.finishedKeyguardState, { a, b -> Pair(a, b) })
-                .collect { pair ->
-                    val (isDreaming, keyguardState) = pair
-                    if (isDreaming && keyguardState == KeyguardState.LOCKSCREEN) {
-                        keyguardTransitionRepository.startTransition(
-                            TransitionInfo(
-                                name,
-                                KeyguardState.LOCKSCREEN,
-                                KeyguardState.DREAMING,
-                                getAnimator(),
+                .sample(
+                    combine(
+                        keyguardInteractor.dozeTransitionModel,
+                        keyguardTransitionInteractor.finishedKeyguardState
+                    ) { a, b -> Pair(a, b) },
+                    { a, bc -> Triple(a, bc.first, bc.second) }
+                )
+                .collect { triple ->
+                    val (isDreaming, dozeTransitionModel, keyguardState) = triple
+                    // Dozing/AOD and dreaming have overlapping events. If the state remains in
+                    // FINISH, it means that doze mode is not running and DREAMING is ok to
+                    // commence.
+                    if (dozeTransitionModel.to == DozeStateModel.FINISH) {
+                        if (isDreaming && keyguardState == KeyguardState.LOCKSCREEN) {
+                            keyguardTransitionRepository.startTransition(
+                                TransitionInfo(
+                                    name,
+                                    KeyguardState.LOCKSCREEN,
+                                    KeyguardState.DREAMING,
+                                    getAnimator(),
+                                )
                             )
-                        )
-                    } else if (!isDreaming && keyguardState == KeyguardState.DREAMING) {
-                        keyguardTransitionRepository.startTransition(
-                            TransitionInfo(
-                                name,
-                                KeyguardState.DREAMING,
-                                KeyguardState.LOCKSCREEN,
-                                getAnimator(),
+                        } else if (!isDreaming && keyguardState == KeyguardState.DREAMING) {
+                            keyguardTransitionRepository.startTransition(
+                                TransitionInfo(
+                                    name,
+                                    KeyguardState.DREAMING,
+                                    KeyguardState.LOCKSCREEN,
+                                    getAnimator(),
+                                )
                             )
-                        )
+                        }
                     }
                 }
         }
diff --git a/packages/SystemUI/src/com/android/systemui/keyguard/domain/interactor/KeyguardInteractor.kt b/packages/SystemUI/src/com/android/systemui/keyguard/domain/interactor/KeyguardInteractor.kt
index 5a1c702..7cfd117 100644
--- a/packages/SystemUI/src/com/android/systemui/keyguard/domain/interactor/KeyguardInteractor.kt
+++ b/packages/SystemUI/src/com/android/systemui/keyguard/domain/interactor/KeyguardInteractor.kt
@@ -20,10 +20,13 @@
 import com.android.systemui.dagger.SysUISingleton
 import com.android.systemui.keyguard.data.repository.KeyguardRepository
 import com.android.systemui.keyguard.shared.model.BiometricUnlockModel
+import com.android.systemui.keyguard.shared.model.DozeStateModel
+import com.android.systemui.keyguard.shared.model.DozeTransitionModel
 import com.android.systemui.keyguard.shared.model.StatusBarState
 import com.android.systemui.keyguard.shared.model.WakefulnessModel
 import javax.inject.Inject
 import kotlinx.coroutines.flow.Flow
+import kotlinx.coroutines.flow.filter
 
 /**
  * Encapsulates business-logic related to the keyguard but not to a more specific part within it.
@@ -41,6 +44,8 @@
     val dozeAmount: Flow<Float> = repository.dozeAmount
     /** Whether the system is in doze mode. */
     val isDozing: Flow<Boolean> = repository.isDozing
+    /** Doze transition information. */
+    val dozeTransitionModel: Flow<DozeTransitionModel> = repository.dozeTransitionModel
     /**
      * Whether the system is dreaming. [isDreaming] will be always be true when [isDozing] is true,
      * but not vice-versa.
@@ -62,6 +67,10 @@
      */
     val biometricUnlockState: Flow<BiometricUnlockModel> = repository.biometricUnlockState
 
+    fun dozeTransitionTo(state: DozeStateModel): Flow<DozeTransitionModel> {
+        return dozeTransitionModel.filter { it.to == state }
+    }
+
     fun isKeyguardShowing(): Boolean {
         return repository.isKeyguardShowing()
     }
diff --git a/packages/SystemUI/src/com/android/systemui/keyguard/domain/interactor/KeyguardQuickAffordanceInteractor.kt b/packages/SystemUI/src/com/android/systemui/keyguard/domain/interactor/KeyguardQuickAffordanceInteractor.kt
index 45eb6f5..c8216c5 100644
--- a/packages/SystemUI/src/com/android/systemui/keyguard/domain/interactor/KeyguardQuickAffordanceInteractor.kt
+++ b/packages/SystemUI/src/com/android/systemui/keyguard/domain/interactor/KeyguardQuickAffordanceInteractor.kt
@@ -189,12 +189,18 @@
     }
 
     /** Returns affordance IDs indexed by slot ID, for all known slots. */
-    fun getSelections(): Map<String, List<String>> {
+    fun getSelections(): Map<String, List<KeyguardQuickAffordancePickerRepresentation>> {
         check(isUsingRepository)
 
+        val slots = repository.get().getSlotPickerRepresentations()
         val selections = repository.get().getSelections()
-        return repository.get().getSlotPickerRepresentations().associate { slotRepresentation ->
-            slotRepresentation.id to (selections[slotRepresentation.id] ?: emptyList())
+        val affordanceById =
+            getAffordancePickerRepresentations().associateBy { affordance -> affordance.id }
+        return slots.associate { slot ->
+            slot.id to
+                (selections[slot.id] ?: emptyList()).mapNotNull { affordanceId ->
+                    affordanceById[affordanceId]
+                }
         }
     }
 
diff --git a/packages/SystemUI/src/com/android/systemui/keyguard/domain/interactor/LockscreenBouncerTransitionInteractor.kt b/packages/SystemUI/src/com/android/systemui/keyguard/domain/interactor/LockscreenBouncerTransitionInteractor.kt
index cca2d56..3bb8241 100644
--- a/packages/SystemUI/src/com/android/systemui/keyguard/domain/interactor/LockscreenBouncerTransitionInteractor.kt
+++ b/packages/SystemUI/src/com/android/systemui/keyguard/domain/interactor/LockscreenBouncerTransitionInteractor.kt
@@ -44,7 +44,7 @@
     private val shadeRepository: ShadeRepository,
     private val keyguardTransitionRepository: KeyguardTransitionRepository,
     private val keyguardTransitionInteractor: KeyguardTransitionInteractor
-) : TransitionInteractor("LOCKSCREEN<->BOUNCER") {
+) : TransitionInteractor(LockscreenBouncerTransitionInteractor::class.simpleName!!) {
 
     private var transitionId: UUID? = null
 
diff --git a/packages/SystemUI/src/com/android/systemui/keyguard/domain/interactor/PrimaryBouncerInteractor.kt b/packages/SystemUI/src/com/android/systemui/keyguard/domain/interactor/PrimaryBouncerInteractor.kt
index 910cdf2..3b31dcf 100644
--- a/packages/SystemUI/src/com/android/systemui/keyguard/domain/interactor/PrimaryBouncerInteractor.kt
+++ b/packages/SystemUI/src/com/android/systemui/keyguard/domain/interactor/PrimaryBouncerInteractor.kt
@@ -271,6 +271,11 @@
         repository.setKeyguardAuthenticated(null)
     }
 
+    /** Notifies that the message was shown. */
+    fun onMessageShown() {
+        repository.setShowMessage(null)
+    }
+
     /** Notify that view visibility has changed. */
     fun notifyBouncerVisibilityHasChanged(visibility: Int) {
         primaryBouncerCallbackInteractor.dispatchVisibilityChanged(visibility)
diff --git a/packages/SystemUI/src/com/android/systemui/keyguard/shared/model/DozeStateModel.kt b/packages/SystemUI/src/com/android/systemui/keyguard/shared/model/DozeStateModel.kt
new file mode 100644
index 0000000..7039188
--- /dev/null
+++ b/packages/SystemUI/src/com/android/systemui/keyguard/shared/model/DozeStateModel.kt
@@ -0,0 +1,46 @@
+/*
+ * Copyright (C) 2022 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.keyguard.shared.model
+
+/** Model device doze states. */
+enum class DozeStateModel {
+    /** Default state. Transition to INITIALIZED to get Doze going. */
+    UNINITIALIZED,
+    /** Doze components are set up. Followed by transition to DOZE or DOZE_AOD. */
+    INITIALIZED,
+    /** Regular doze. Device is asleep and listening for pulse triggers. */
+    DOZE,
+    /** Deep doze. Device is asleep and is not listening for pulse triggers. */
+    DOZE_SUSPEND_TRIGGERS,
+    /** Always-on doze. Device is asleep, showing UI and listening for pulse triggers. */
+    DOZE_AOD,
+    /** Pulse has been requested. Device is awake and preparing UI */
+    DOZE_REQUEST_PULSE,
+    /** Pulse is showing. Device is awake and showing UI. */
+    DOZE_PULSING,
+    /** Pulse is showing with bright wallpaper. Device is awake and showing UI. */
+    DOZE_PULSING_BRIGHT,
+    /** Pulse is done showing. Followed by transition to DOZE or DOZE_AOD. */
+    DOZE_PULSE_DONE,
+    /** Doze is done. DozeService is finished. */
+    FINISH,
+    /** AOD, but the display is temporarily off. */
+    DOZE_AOD_PAUSED,
+    /** AOD, prox is near, transitions to DOZE_AOD_PAUSED after a timeout. */
+    DOZE_AOD_PAUSING,
+    /** Always-on doze. Device is awake, showing docking UI and listening for pulse triggers. */
+    DOZE_AOD_DOCKED
+}
diff --git a/packages/SystemUI/src/com/android/systemui/keyguard/shared/model/DozeTransitionModel.kt b/packages/SystemUI/src/com/android/systemui/keyguard/shared/model/DozeTransitionModel.kt
new file mode 100644
index 0000000..e96ace2
--- /dev/null
+++ b/packages/SystemUI/src/com/android/systemui/keyguard/shared/model/DozeTransitionModel.kt
@@ -0,0 +1,22 @@
+/*
+ * Copyright (C) 2022 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.keyguard.shared.model
+
+/** Doze transition information. */
+data class DozeTransitionModel(
+    val from: DozeStateModel = DozeStateModel.UNINITIALIZED,
+    val to: DozeStateModel = DozeStateModel.UNINITIALIZED,
+)
diff --git a/packages/SystemUI/src/com/android/systemui/keyguard/ui/binder/KeyguardBouncerViewBinder.kt b/packages/SystemUI/src/com/android/systemui/keyguard/ui/binder/KeyguardBouncerViewBinder.kt
index 7739a45..3c927ee 100644
--- a/packages/SystemUI/src/com/android/systemui/keyguard/ui/binder/KeyguardBouncerViewBinder.kt
+++ b/packages/SystemUI/src/com/android/systemui/keyguard/ui/binder/KeyguardBouncerViewBinder.kt
@@ -32,7 +32,6 @@
 import com.android.systemui.plugins.ActivityStarter
 import com.android.systemui.statusbar.phone.KeyguardBouncer.EXPANSION_VISIBLE
 import kotlinx.coroutines.awaitCancellation
-import kotlinx.coroutines.flow.collect
 import kotlinx.coroutines.flow.filter
 import kotlinx.coroutines.launch
 
@@ -182,6 +181,7 @@
                     launch {
                         viewModel.bouncerShowMessage.collect {
                             hostViewController.showMessage(it.message, it.colorStateList)
+                            viewModel.onMessageShown()
                         }
                     }
 
diff --git a/packages/SystemUI/src/com/android/systemui/keyguard/ui/viewmodel/KeyguardBouncerViewModel.kt b/packages/SystemUI/src/com/android/systemui/keyguard/ui/viewmodel/KeyguardBouncerViewModel.kt
index 526ae74..503c8ba 100644
--- a/packages/SystemUI/src/com/android/systemui/keyguard/ui/viewmodel/KeyguardBouncerViewModel.kt
+++ b/packages/SystemUI/src/com/android/systemui/keyguard/ui/viewmodel/KeyguardBouncerViewModel.kt
@@ -86,6 +86,11 @@
         interactor.notifyKeyguardAuthenticatedHandled()
     }
 
+    /** Notifies that the message was shown. */
+    fun onMessageShown() {
+        interactor.onMessageShown()
+    }
+
     /** Observe whether back button is enabled. */
     fun observeOnIsBackButtonEnabled(systemUiVisibility: () -> Int): Flow<Int> {
         return interactor.isBackButtonEnabled.map { enabled ->
diff --git a/packages/SystemUI/src/com/android/systemui/navigationbar/gestural/EdgeBackGestureHandler.java b/packages/SystemUI/src/com/android/systemui/navigationbar/gestural/EdgeBackGestureHandler.java
index ed1e018..cb0f3e2 100644
--- a/packages/SystemUI/src/com/android/systemui/navigationbar/gestural/EdgeBackGestureHandler.java
+++ b/packages/SystemUI/src/com/android/systemui/navigationbar/gestural/EdgeBackGestureHandler.java
@@ -69,9 +69,9 @@
 import com.android.systemui.plugins.FalsingManager;
 import com.android.systemui.plugins.NavigationEdgeBackPlugin;
 import com.android.systemui.plugins.PluginListener;
+import com.android.systemui.plugins.PluginManager;
 import com.android.systemui.recents.OverviewProxyService;
 import com.android.systemui.settings.UserTracker;
-import com.android.systemui.shared.plugins.PluginManager;
 import com.android.systemui.shared.system.ActivityManagerWrapper;
 import com.android.systemui.shared.system.InputChannelCompat;
 import com.android.systemui.shared.system.QuickStepContract;
diff --git a/packages/SystemUI/src/com/android/systemui/plugins/PluginDependencyProvider.java b/packages/SystemUI/src/com/android/systemui/plugins/PluginDependencyProvider.java
index 0b565ea..e6575d5a 100644
--- a/packages/SystemUI/src/com/android/systemui/plugins/PluginDependencyProvider.java
+++ b/packages/SystemUI/src/com/android/systemui/plugins/PluginDependencyProvider.java
@@ -18,7 +18,6 @@
 
 import com.android.systemui.Dependency;
 import com.android.systemui.plugins.PluginDependency.DependencyProvider;
-import com.android.systemui.shared.plugins.PluginManager;
 
 import javax.inject.Inject;
 import javax.inject.Singleton;
diff --git a/packages/SystemUI/src/com/android/systemui/plugins/PluginsModule.java b/packages/SystemUI/src/com/android/systemui/plugins/PluginsModule.java
index 638f81b..146633d 100644
--- a/packages/SystemUI/src/com/android/systemui/plugins/PluginsModule.java
+++ b/packages/SystemUI/src/com/android/systemui/plugins/PluginsModule.java
@@ -27,7 +27,6 @@
 import com.android.systemui.shared.plugins.PluginActionManager;
 import com.android.systemui.shared.plugins.PluginEnabler;
 import com.android.systemui.shared.plugins.PluginInstance;
-import com.android.systemui.shared.plugins.PluginManager;
 import com.android.systemui.shared.plugins.PluginManagerImpl;
 import com.android.systemui.shared.plugins.PluginPrefs;
 import com.android.systemui.shared.system.UncaughtExceptionPreHandlerManager;
diff --git a/packages/SystemUI/src/com/android/systemui/qs/QSTileHost.java b/packages/SystemUI/src/com/android/systemui/qs/QSTileHost.java
index f37d668..6240c10 100644
--- a/packages/SystemUI/src/com/android/systemui/qs/QSTileHost.java
+++ b/packages/SystemUI/src/com/android/systemui/qs/QSTileHost.java
@@ -41,6 +41,7 @@
 import com.android.systemui.dump.DumpManager;
 import com.android.systemui.dump.nano.SystemUIProtoDump;
 import com.android.systemui.plugins.PluginListener;
+import com.android.systemui.plugins.PluginManager;
 import com.android.systemui.plugins.qs.QSFactory;
 import com.android.systemui.plugins.qs.QSTile;
 import com.android.systemui.plugins.qs.QSTileView;
@@ -53,7 +54,6 @@
 import com.android.systemui.qs.nano.QsTileState;
 import com.android.systemui.settings.UserFileManager;
 import com.android.systemui.settings.UserTracker;
-import com.android.systemui.shared.plugins.PluginManager;
 import com.android.systemui.statusbar.phone.AutoTileManager;
 import com.android.systemui.statusbar.phone.CentralSurfaces;
 import com.android.systemui.statusbar.phone.StatusBarIconController;
diff --git a/packages/SystemUI/src/com/android/systemui/sensorprivacy/SensorUseStartedActivity.kt b/packages/SystemUI/src/com/android/systemui/sensorprivacy/SensorUseStartedActivity.kt
index 2d1d8b7..d33d113 100644
--- a/packages/SystemUI/src/com/android/systemui/sensorprivacy/SensorUseStartedActivity.kt
+++ b/packages/SystemUI/src/com/android/systemui/sensorprivacy/SensorUseStartedActivity.kt
@@ -29,6 +29,8 @@
 import android.hardware.SensorPrivacyManager.Sources.DIALOG
 import android.os.Bundle
 import android.os.Handler
+import android.window.OnBackInvokedDispatcher
+import androidx.annotation.OpenForTesting
 import com.android.internal.util.FrameworkStatsLog.PRIVACY_TOGGLE_DIALOG_INTERACTION
 import com.android.internal.util.FrameworkStatsLog.PRIVACY_TOGGLE_DIALOG_INTERACTION__ACTION__CANCEL
 import com.android.internal.util.FrameworkStatsLog.PRIVACY_TOGGLE_DIALOG_INTERACTION__ACTION__ENABLE
@@ -45,7 +47,8 @@
  *
  * <p>The dialog is started for the user the app is running for which might be a secondary users.
  */
-class SensorUseStartedActivity @Inject constructor(
+@OpenForTesting
+open class SensorUseStartedActivity @Inject constructor(
     private val sensorPrivacyController: IndividualSensorPrivacyController,
     private val keyguardStateController: KeyguardStateController,
     private val keyguardDismissUtil: KeyguardDismissUtil,
@@ -67,9 +70,10 @@
     private lateinit var sensorUsePackageName: String
     private var unsuppressImmediately = false
 
-    private lateinit var sensorPrivacyListener: IndividualSensorPrivacyController.Callback
+    private var sensorPrivacyListener: IndividualSensorPrivacyController.Callback? = null
 
     private var mDialog: AlertDialog? = null
+    private val mBackCallback = this::onBackInvoked
 
     override fun onCreate(savedInstanceState: Bundle?) {
         super.onCreate(savedInstanceState)
@@ -84,15 +88,14 @@
 
         if (intent.getBooleanExtra(EXTRA_ALL_SENSORS, false)) {
             sensor = ALL_SENSORS
-            sensorPrivacyListener =
-                    IndividualSensorPrivacyController.Callback { _, _ ->
-                        if (!sensorPrivacyController.isSensorBlocked(MICROPHONE) &&
-                                !sensorPrivacyController.isSensorBlocked(CAMERA)) {
-                            finish()
-                        }
-                    }
-
-            sensorPrivacyController.addCallback(sensorPrivacyListener)
+            val callback = IndividualSensorPrivacyController.Callback { _, _ ->
+                if (!sensorPrivacyController.isSensorBlocked(MICROPHONE) &&
+                        !sensorPrivacyController.isSensorBlocked(CAMERA)) {
+                    finish()
+                }
+            }
+            sensorPrivacyListener = callback
+            sensorPrivacyController.addCallback(callback)
             if (!sensorPrivacyController.isSensorBlocked(MICROPHONE) &&
                     !sensorPrivacyController.isSensorBlocked(CAMERA)) {
                 finish()
@@ -105,14 +108,14 @@
                     return
                 }
             }
-            sensorPrivacyListener =
-                    IndividualSensorPrivacyController.Callback { whichSensor: Int,
-                                                                 isBlocked: Boolean ->
-                        if (whichSensor == sensor && !isBlocked) {
-                            finish()
-                        }
-                    }
-            sensorPrivacyController.addCallback(sensorPrivacyListener)
+            val callback = IndividualSensorPrivacyController.Callback {
+                whichSensor: Int, isBlocked: Boolean ->
+                if (whichSensor == sensor && !isBlocked) {
+                    finish()
+                }
+            }
+            sensorPrivacyListener = callback
+            sensorPrivacyController.addCallback(callback)
 
             if (!sensorPrivacyController.isSensorBlocked(sensor)) {
                 finish()
@@ -122,6 +125,10 @@
 
         mDialog = SensorUseDialog(this, sensor, this, this)
         mDialog!!.show()
+
+        onBackInvokedDispatcher.registerOnBackInvokedCallback(
+                OnBackInvokedDispatcher.PRIORITY_DEFAULT,
+                mBackCallback)
     }
 
     override fun onStart() {
@@ -180,10 +187,15 @@
     override fun onDestroy() {
         super.onDestroy()
         mDialog?.dismiss()
-        sensorPrivacyController.removeCallback(sensorPrivacyListener)
+        sensorPrivacyListener?.also { sensorPrivacyController.removeCallback(it) }
+        onBackInvokedDispatcher.unregisterOnBackInvokedCallback(mBackCallback)
     }
 
     override fun onBackPressed() {
+        onBackInvoked()
+    }
+
+    fun onBackInvoked() {
         // do not allow backing out
     }
 
diff --git a/packages/SystemUI/src/com/android/systemui/shade/LargeScreenShadeHeaderController.kt b/packages/SystemUI/src/com/android/systemui/shade/LargeScreenShadeHeaderController.kt
index 63d0d16..31e4464 100644
--- a/packages/SystemUI/src/com/android/systemui/shade/LargeScreenShadeHeaderController.kt
+++ b/packages/SystemUI/src/com/android/systemui/shade/LargeScreenShadeHeaderController.kt
@@ -331,13 +331,8 @@
             // Use resources.getXml instead of passing the resource id due to bug b/205018300
             header.getConstraintSet(QQS_HEADER_CONSTRAINT)
                 .load(context, resources.getXml(R.xml.qqs_header))
-            val qsConstraints = if (featureFlags.isEnabled(Flags.NEW_HEADER)) {
-                R.xml.qs_header_new
-            } else {
-                R.xml.qs_header
-            }
             header.getConstraintSet(QS_HEADER_CONSTRAINT)
-                .load(context, resources.getXml(qsConstraints))
+                .load(context, resources.getXml(R.xml.qs_header))
             header.getConstraintSet(LARGE_SCREEN_HEADER_CONSTRAINT)
                 .load(context, resources.getXml(R.xml.large_screen_shade_header))
         }
diff --git a/packages/SystemUI/src/com/android/systemui/shade/NotificationPanelViewController.java b/packages/SystemUI/src/com/android/systemui/shade/NotificationPanelViewController.java
index 6f7b741..e68182e 100644
--- a/packages/SystemUI/src/com/android/systemui/shade/NotificationPanelViewController.java
+++ b/packages/SystemUI/src/com/android/systemui/shade/NotificationPanelViewController.java
@@ -279,6 +279,11 @@
     private static final String COUNTER_PANEL_OPEN_PEEK = "panel_open_peek";
     private static final Rect M_DUMMY_DIRTY_RECT = new Rect(0, 0, 1, 1);
     private static final Rect EMPTY_RECT = new Rect();
+    /**
+     * Duration to use for the animator when the keyguard status view alignment changes, and a
+     * custom clock animation is in use.
+     */
+    private static final int KEYGUARD_STATUS_VIEW_CUSTOM_CLOCK_MOVE_DURATION = 1000;
 
     private final StatusBarTouchableRegionManager mStatusBarTouchableRegionManager;
     private final Resources mResources;
@@ -675,7 +680,7 @@
     };
     private final Runnable mMaybeHideExpandedRunnable = () -> {
         if (getExpansionFraction() == 0.0f) {
-            getView().post(mHideExpandedRunnable);
+            postToView(mHideExpandedRunnable);
         }
     };
 
@@ -1592,7 +1597,7 @@
 
                         // Use linear here, so the actual clock can pick its own interpolator.
                         adapter.setInterpolator(Interpolators.LINEAR);
-                        adapter.setDuration(StackStateAnimator.ANIMATION_DURATION_STANDARD);
+                        adapter.setDuration(KEYGUARD_STATUS_VIEW_CUSTOM_CLOCK_MOVE_DURATION);
                         adapter.addTarget(clockView);
                         set.addTransition(adapter);
 
@@ -2810,7 +2815,7 @@
             return top + mNotificationStackScrollLayoutController.getHeight()
                     + mSplitShadeNotificationsScrimMarginBottom;
         } else {
-            return getView().getBottom();
+            return mView.getBottom();
         }
     }
 
@@ -2825,7 +2830,7 @@
 
     private int calculateRightQsClippingBound() {
         if (mIsFullWidth) {
-            return getView().getRight() + mDisplayRightInset;
+            return mView.getRight() + mDisplayRightInset;
         } else {
             return mNotificationStackScrollLayoutController.getRight();
         }
@@ -3509,13 +3514,17 @@
     }
 
     private float getHeaderTranslation() {
+        if (mSplitShadeEnabled) {
+            // in split shade QS don't translate, just (un)squish and overshoot
+            return 0;
+        }
         if (mBarState == KEYGUARD && !mKeyguardBypassController.getBypassEnabled()) {
             return -mQs.getQsMinExpansionHeight();
         }
         float appearAmount = mNotificationStackScrollLayoutController
                 .calculateAppearFraction(mExpandedHeight);
         float startHeight = -mQsExpansionHeight;
-        if (!mSplitShadeEnabled && mBarState == StatusBarState.SHADE) {
+        if (mBarState == StatusBarState.SHADE) {
             // Small parallax as we pull down and clip QS
             startHeight = -mQsExpansionHeight * QS_PARALLAX_AMOUNT;
         }
@@ -5190,6 +5199,26 @@
         return mView;
     }
 
+    /** */
+    public boolean postToView(Runnable action) {
+        return mView.post(action);
+    }
+
+    /** */
+    public boolean sendInterceptTouchEventToView(MotionEvent event) {
+        return mView.onInterceptTouchEvent(event);
+    }
+
+    /** */
+    public void requestLayoutOnView() {
+        mView.requestLayout();
+    }
+
+    /** */
+    public void resetViewAlphas() {
+        ViewGroupFadeHelper.reset(mView);
+    }
+
     private void beginJankMonitoring() {
         if (mInteractionJankMonitor == null) {
             return;
diff --git a/packages/SystemUI/src/com/android/systemui/shade/NotificationShadeWindowControllerImpl.java b/packages/SystemUI/src/com/android/systemui/shade/NotificationShadeWindowControllerImpl.java
index b719177..8698c04 100644
--- a/packages/SystemUI/src/com/android/systemui/shade/NotificationShadeWindowControllerImpl.java
+++ b/packages/SystemUI/src/com/android/systemui/shade/NotificationShadeWindowControllerImpl.java
@@ -246,7 +246,6 @@
         mLp.token = new Binder();
         mLp.gravity = Gravity.TOP;
         mLp.setFitInsetsTypes(0 /* types */);
-        mLp.softInputMode = LayoutParams.SOFT_INPUT_ADJUST_RESIZE;
         mLp.setTitle("NotificationShade");
         mLp.packageName = mContext.getPackageName();
         mLp.layoutInDisplayCutoutMode = LAYOUT_IN_DISPLAY_CUTOUT_MODE_ALWAYS;
@@ -384,8 +383,6 @@
             mLpChanged.flags |= LayoutParams.FLAG_NOT_FOCUSABLE;
             mLpChanged.flags &= ~LayoutParams.FLAG_ALT_FOCUSABLE_IM;
         }
-
-        mLpChanged.softInputMode = LayoutParams.SOFT_INPUT_ADJUST_RESIZE;
     }
 
     private void applyForceShowNavigationFlag(State state) {
diff --git a/packages/SystemUI/src/com/android/systemui/shade/NotificationShadeWindowViewController.java b/packages/SystemUI/src/com/android/systemui/shade/NotificationShadeWindowViewController.java
index 8379e51..d773c01 100644
--- a/packages/SystemUI/src/com/android/systemui/shade/NotificationShadeWindowViewController.java
+++ b/packages/SystemUI/src/com/android/systemui/shade/NotificationShadeWindowViewController.java
@@ -316,7 +316,7 @@
                 MotionEvent cancellation = MotionEvent.obtain(ev);
                 cancellation.setAction(MotionEvent.ACTION_CANCEL);
                 mStackScrollLayout.onInterceptTouchEvent(cancellation);
-                mNotificationPanelViewController.getView().onInterceptTouchEvent(cancellation);
+                mNotificationPanelViewController.sendInterceptTouchEventToView(cancellation);
                 cancellation.recycle();
             }
 
diff --git a/packages/SystemUI/src/com/android/systemui/shade/ShadeControllerImpl.java b/packages/SystemUI/src/com/android/systemui/shade/ShadeControllerImpl.java
index eaf7fae..d783293 100644
--- a/packages/SystemUI/src/com/android/systemui/shade/ShadeControllerImpl.java
+++ b/packages/SystemUI/src/com/android/systemui/shade/ShadeControllerImpl.java
@@ -160,7 +160,7 @@
                         if (getCentralSurfaces().getNotificationShadeWindowView()
                                 .isVisibleToUser()) {
                             getNotificationPanelViewController().removeOnGlobalLayoutListener(this);
-                            getNotificationPanelViewController().getView().post(executable);
+                            getNotificationPanelViewController().postToView(executable);
                         }
                     }
                 });
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/NotificationListener.java b/packages/SystemUI/src/com/android/systemui/statusbar/NotificationListener.java
index 824d3a3..56b689e 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/NotificationListener.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/NotificationListener.java
@@ -31,7 +31,7 @@
 
 import com.android.systemui.dagger.SysUISingleton;
 import com.android.systemui.dagger.qualifiers.Main;
-import com.android.systemui.shared.plugins.PluginManager;
+import com.android.systemui.plugins.PluginManager;
 import com.android.systemui.statusbar.dagger.CentralSurfacesModule;
 import com.android.systemui.statusbar.notification.collection.NotifCollection;
 import com.android.systemui.statusbar.notification.collection.PipelineDumpable;
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/NotificationEntry.java b/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/NotificationEntry.java
index 585d871..37d82ca 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/NotificationEntry.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/NotificationEntry.java
@@ -676,9 +676,6 @@
         return row != null && row.areChildrenExpanded();
     }
 
-    public boolean keepInParent() {
-        return row != null && row.keepInParent();
-    }
 
     //TODO: probably less confusing to say "is group fully visible"
     public boolean isGroupNotFullyVisible() {
@@ -698,10 +695,6 @@
         return row != null && row.isSummaryWithChildren();
     }
 
-    public void setKeepInParent(boolean keep) {
-        if (row != null) row.setKeepInParent(keep);
-    }
-
     public void onDensityOrFontScaleChanged() {
         if (row != null) row.onDensityOrFontScaleChanged();
     }
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/ShadeListBuilder.java b/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/ShadeListBuilder.java
index 3ae2545..65a21a4 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/ShadeListBuilder.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/ShadeListBuilder.java
@@ -1160,12 +1160,21 @@
                 mLogger.logParentChanged(mIterationCount, prev.getParent(), curr.getParent());
             }
 
-            if (curr.getSuppressedChanges().getParent() != null) {
-                mLogger.logParentChangeSuppressed(
+            GroupEntry currSuppressedParent = curr.getSuppressedChanges().getParent();
+            GroupEntry prevSuppressedParent = prev.getSuppressedChanges().getParent();
+            if (currSuppressedParent != null && (prevSuppressedParent == null
+                    || !prevSuppressedParent.getKey().equals(currSuppressedParent.getKey()))) {
+                mLogger.logParentChangeSuppressedStarted(
                         mIterationCount,
-                        curr.getSuppressedChanges().getParent(),
+                        currSuppressedParent,
                         curr.getParent());
             }
+            if (prevSuppressedParent != null && currSuppressedParent == null) {
+                mLogger.logParentChangeSuppressedStopped(
+                        mIterationCount,
+                        prevSuppressedParent,
+                        prev.getParent());
+            }
 
             if (curr.getSuppressedChanges().getSection() != null) {
                 mLogger.logSectionChangeSuppressed(
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/listbuilder/ShadeListBuilderLogger.kt b/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/listbuilder/ShadeListBuilderLogger.kt
index 8e052c7..4adc90a 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/listbuilder/ShadeListBuilderLogger.kt
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/listbuilder/ShadeListBuilderLogger.kt
@@ -193,7 +193,7 @@
         })
     }
 
-    fun logParentChangeSuppressed(
+    fun logParentChangeSuppressedStarted(
         buildId: Int,
         suppressedParent: GroupEntry?,
         keepingParent: GroupEntry?
@@ -207,6 +207,21 @@
         })
     }
 
+    fun logParentChangeSuppressedStopped(
+            buildId: Int,
+            previouslySuppressedParent: GroupEntry?,
+            previouslyKeptParent: GroupEntry?
+    ) {
+        buffer.log(TAG, INFO, {
+            long1 = buildId.toLong()
+            str1 = previouslySuppressedParent?.logKey
+            str2 = previouslyKeptParent?.logKey
+        }, {
+            "(Build $long1)     Change of parent to '$str1' no longer suppressed; " +
+                    "replaced parent '$str2'"
+        })
+    }
+
     fun logGroupPruningSuppressed(
         buildId: Int,
         keepingParent: GroupEntry?
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/render/MediaContainerController.kt b/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/render/MediaContainerController.kt
index f949af0..8de0381 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/render/MediaContainerController.kt
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/render/MediaContainerController.kt
@@ -55,4 +55,8 @@
 
     override val view: View
         get() = mediaContainerView!!
+
+    override fun offerToKeepInParentForAnimation(): Boolean = false
+    override fun removeFromParentIfKeptForAnimation(): Boolean = false
+    override fun resetKeepInParentForAnimation() {}
 }
\ No newline at end of file
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/render/NodeController.kt b/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/render/NodeController.kt
index 26ba12c..ae72a3c 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/render/NodeController.kt
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/render/NodeController.kt
@@ -30,6 +30,7 @@
  * below.
  */
 interface NodeController {
+
     /** A string that uniquely(ish) represents the node in the tree. Used for debugging. */
     val nodeLabel: String
 
@@ -64,6 +65,27 @@
 
     /** Called when this view has been removed */
     fun onViewRemoved() {}
+
+    /**
+     * Called before removing a node from its parent
+     *
+     * If returned true, the ShadeViewDiffer won't detach this row and the view system is
+     * responsible for ensuring the row is in eventually removed from the parent.
+     *
+     * @return false to opt out from this feature
+     */
+    fun offerToKeepInParentForAnimation(): Boolean
+
+    /**
+     * Called before a node is reattached. Removes the view from its parent
+     * if it was flagged to be kept before.
+     *
+     * @return whether it did a removal
+     */
+    fun removeFromParentIfKeptForAnimation(): Boolean
+
+    /** Called when a node is being reattached */
+    fun resetKeepInParentForAnimation()
 }
 
 /**
@@ -90,7 +112,7 @@
 }
 
 private fun treeSpecToStrHelper(tree: NodeSpec, sb: StringBuilder, indent: String) {
-    sb.append("${indent}{${tree.controller.nodeLabel}}\n")
+    sb.append("$indent{${tree.controller.nodeLabel}}\n")
     if (tree.children.isNotEmpty()) {
         val childIndent = "$indent  "
         for (child in tree.children) {
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/render/RootNodeController.kt b/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/render/RootNodeController.kt
index 2073e92..5ff686a 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/render/RootNodeController.kt
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/render/RootNodeController.kt
@@ -32,6 +32,9 @@
     override val view: View
 ) : NodeController, PipelineDumpable {
     override val nodeLabel: String = "<root>"
+    override fun offerToKeepInParentForAnimation(): Boolean = false
+    override fun removeFromParentIfKeptForAnimation(): Boolean = false
+    override fun resetKeepInParentForAnimation() {}
 
     override fun getChildAt(index: Int): View? {
         return listContainer.getContainerChildAt(index)
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/render/SectionHeaderController.kt b/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/render/SectionHeaderController.kt
index 2c9508e..7b59266 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/render/SectionHeaderController.kt
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/render/SectionHeaderController.kt
@@ -100,4 +100,7 @@
 
     override val view: View
         get() = _view!!
+    override fun offerToKeepInParentForAnimation(): Boolean = false
+    override fun removeFromParentIfKeptForAnimation(): Boolean = false
+    override fun resetKeepInParentForAnimation() {}
 }
\ No newline at end of file
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/render/ShadeViewDiffer.kt b/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/render/ShadeViewDiffer.kt
index 9a9941e..18ee481 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/render/ShadeViewDiffer.kt
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/render/ShadeViewDiffer.kt
@@ -86,10 +86,10 @@
     }
 
     private fun maybeDetachChild(
-        parentNode: ShadeNode,
-        parentSpec: NodeSpec?,
-        childNode: ShadeNode,
-        childSpec: NodeSpec?
+            parentNode: ShadeNode,
+            parentSpec: NodeSpec?,
+            childNode: ShadeNode,
+            childSpec: NodeSpec?
     ) {
         val newParentNode = childSpec?.parent?.let { getNode(it) }
 
@@ -100,14 +100,27 @@
                 nodes.remove(childNode.controller)
             }
 
-            logger.logDetachingChild(
-                key = childNode.label,
-                isTransfer = !childCompletelyRemoved,
-                isParentRemoved = parentSpec == null,
-                oldParent = parentNode.label,
-                newParent = newParentNode?.label)
-            parentNode.removeChild(childNode, isTransfer = !childCompletelyRemoved)
-            childNode.parent = null
+            if (childCompletelyRemoved && parentSpec == null &&
+                    childNode.offerToKeepInParentForAnimation()) {
+                // If both the child and the parent are being removed at the same time, then
+                // keep the child attached to the parent for animation purposes
+                logger.logSkipDetachingChild(
+                        key = childNode.label,
+                        parentKey = parentNode.label,
+                        isTransfer = !childCompletelyRemoved,
+                        isParentRemoved = true
+                )
+            } else {
+                logger.logDetachingChild(
+                        key = childNode.label,
+                        isTransfer = !childCompletelyRemoved,
+                        isParentRemoved = parentSpec == null,
+                        oldParent = parentNode.label,
+                        newParent = newParentNode?.label
+                )
+                parentNode.removeChild(childNode, isTransfer = !childCompletelyRemoved)
+                childNode.parent = null
+            }
         }
     }
 
@@ -119,6 +132,16 @@
             val childNode = getNode(childSpec)
 
             if (childNode.view != currView) {
+                val removedFromParent = childNode.removeFromParentIfKeptForAnimation()
+                if (removedFromParent) {
+                    logger.logDetachingChild(
+                            key = childNode.label,
+                            isTransfer = false,
+                            isParentRemoved = true,
+                            oldParent = null,
+                            newParent = null
+                    )
+                }
 
                 when (childNode.parent) {
                     null -> {
@@ -142,6 +165,8 @@
                 }
             }
 
+            childNode.resetKeepInParentForAnimation()
+
             if (childSpec.children.isNotEmpty()) {
                 attachChildren(childNode, specMap)
             }
@@ -213,4 +238,16 @@
         controller.removeChild(child.controller, isTransfer)
         child.controller.onViewRemoved()
     }
+
+    fun offerToKeepInParentForAnimation(): Boolean {
+        return controller.offerToKeepInParentForAnimation()
+    }
+
+    fun removeFromParentIfKeptForAnimation(): Boolean {
+        return controller.removeFromParentIfKeptForAnimation()
+    }
+
+    fun resetKeepInParentForAnimation() {
+        controller.resetKeepInParentForAnimation()
+    }
 }
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/render/ShadeViewDifferLogger.kt b/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/render/ShadeViewDifferLogger.kt
index b4b9438..1e22c2c 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/render/ShadeViewDifferLogger.kt
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/render/ShadeViewDifferLogger.kt
@@ -43,6 +43,20 @@
         })
     }
 
+    fun logSkipDetachingChild(
+            key: String,
+            parentKey: String?,
+            isTransfer: Boolean,
+            isParentRemoved: Boolean
+    ) {
+        buffer.log(TAG, LogLevel.DEBUG, {
+            str1 = key
+            str2 = parentKey
+            bool1 = isTransfer
+            bool2 = isParentRemoved
+        }, { "Skip detaching $str1 from $str2 isTransfer=$bool1 isParentRemoved=$bool2" })
+    }
+
     fun logAttachingChild(key: String, parent: String, atIndex: Int) {
         buffer.log(TAG, LogLevel.DEBUG, {
             str1 = key
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/ExpandableNotificationRow.java b/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/ExpandableNotificationRow.java
index b93e150..1eccc98 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/ExpandableNotificationRow.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/ExpandableNotificationRow.java
@@ -240,7 +240,7 @@
     private NotificationContentView mPrivateLayout;
     private NotificationContentView[] mLayouts;
     private int mNotificationColor;
-    private ExpansionLogger mLogger;
+    private ExpandableNotificationRowLogger mLogger;
     private String mLoggingKey;
     private NotificationGuts mGuts;
     private NotificationEntry mEntry;
@@ -339,7 +339,7 @@
             }
         }
     };
-    private boolean mKeepInParent;
+    private boolean mKeepInParentForDismissAnimation;
     private boolean mRemoved;
     private static final Property<ExpandableNotificationRow, Float> TRANSLATE_CONTENT =
             new FloatProperty<ExpandableNotificationRow>("translate") {
@@ -825,6 +825,12 @@
         if (mChildrenContainer == null) {
             mChildrenContainerStub.inflate();
         }
+
+        if (row.keepInParentForDismissAnimation()) {
+            logSkipAttachingKeepInParentChild(row);
+            return;
+        }
+
         mChildrenContainer.addNotification(row, childIndex);
         onAttachedChildrenCountChanged();
         row.setIsChildInGroup(true, this);
@@ -833,6 +839,7 @@
     public void removeChildNotification(ExpandableNotificationRow row) {
         if (mChildrenContainer != null) {
             mChildrenContainer.removeNotification(row);
+            row.setKeepInParentForDismissAnimation(false);
         }
         onAttachedChildrenCountChanged();
         row.setIsChildInGroup(false, null);
@@ -840,6 +847,31 @@
     }
 
     /**
+     * Removes the children notifications which were marked to keep for the dismissal animation.
+     */
+    public void removeChildrenWithKeepInParent() {
+        if (mChildrenContainer == null) return;
+
+        List<ExpandableNotificationRow> clonedList = new ArrayList<>(
+                mChildrenContainer.getAttachedChildren());
+        boolean childCountChanged = false;
+        for (ExpandableNotificationRow child : clonedList) {
+            if (child.keepInParentForDismissAnimation()) {
+                mChildrenContainer.removeNotification(child);
+                child.setIsChildInGroup(false, null);
+                child.requestBottomRoundness(0.0f, /* animate = */ false, SourceType.DefaultValue);
+                child.setKeepInParentForDismissAnimation(false);
+                logKeepInParentChildDetached(child);
+                childCountChanged = true;
+            }
+        }
+
+        if (childCountChanged) {
+            onAttachedChildrenCountChanged();
+        }
+    }
+
+    /**
      * Returns the child notification at [index], or null if no such child.
      */
     @Nullable
@@ -1361,12 +1393,15 @@
         }
     }
 
-    public boolean keepInParent() {
-        return mKeepInParent;
+    /**
+     * @return  if this entry should be kept in its parent during removal.
+     */
+    public boolean keepInParentForDismissAnimation() {
+        return mKeepInParentForDismissAnimation;
     }
 
-    public void setKeepInParent(boolean keepInParent) {
-        mKeepInParent = keepInParent;
+    public void setKeepInParentForDismissAnimation(boolean keepInParent) {
+        mKeepInParentForDismissAnimation = keepInParent;
     }
 
     @Override
@@ -1537,8 +1572,29 @@
         mUseIncreasedHeadsUpHeight = use;
     }
 
-    public interface ExpansionLogger {
+    /**
+     * Interface for logging {{@link ExpandableNotificationRow} events.}
+     */
+    public interface ExpandableNotificationRowLogger {
+        /**
+         * Called when the notification is expanded / collapsed.
+         */
         void logNotificationExpansion(String key, boolean userAction, boolean expanded);
+
+        /**
+         * Called when a notification which was previously kept in its parent for the
+         * dismiss animation is finally detached from its parent.
+         */
+        void logKeepInParentChildDetached(NotificationEntry child, NotificationEntry oldParent);
+
+        /**
+         * Called when we want to attach a notification to a new parent,
+         * but it still has the keepInParent flag set, so we skip it.
+         */
+        void logSkipAttachingKeepInParentChild(
+                NotificationEntry child,
+                NotificationEntry newParent
+        );
     }
 
     public ExpandableNotificationRow(Context context, AttributeSet attrs) {
@@ -1556,7 +1612,7 @@
             RemoteInputViewSubcomponent.Factory rivSubcomponentFactory,
             String appName,
             String notificationKey,
-            ExpansionLogger logger,
+            ExpandableNotificationRowLogger logger,
             KeyguardBypassController bypassController,
             GroupMembershipManager groupMembershipManager,
             GroupExpansionManager groupExpansionManager,
@@ -3567,6 +3623,18 @@
         });
     }
 
+    private void logKeepInParentChildDetached(ExpandableNotificationRow child) {
+        if (mLogger != null) {
+            mLogger.logKeepInParentChildDetached(child.getEntry(), getEntry());
+        }
+    }
+
+    private void logSkipAttachingKeepInParentChild(ExpandableNotificationRow child) {
+        if (mLogger != null) {
+            mLogger.logSkipAttachingKeepInParentChild(child.getEntry(), getEntry());
+        }
+    }
+
     private void setTargetPoint(Point p) {
         mTargetPoint = p;
     }
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/ExpandableNotificationRowController.java b/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/ExpandableNotificationRowController.java
index 842526e..f9e9a2d 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/ExpandableNotificationRowController.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/ExpandableNotificationRowController.java
@@ -33,9 +33,9 @@
 import com.android.systemui.flags.FeatureFlags;
 import com.android.systemui.flags.Flags;
 import com.android.systemui.plugins.FalsingManager;
+import com.android.systemui.plugins.PluginManager;
 import com.android.systemui.plugins.statusbar.NotificationMenuRowPlugin;
 import com.android.systemui.plugins.statusbar.StatusBarStateController;
-import com.android.systemui.shared.plugins.PluginManager;
 import com.android.systemui.statusbar.NotificationMediaManager;
 import com.android.systemui.statusbar.SmartReplyController;
 import com.android.systemui.statusbar.notification.FeedbackIcon;
@@ -83,13 +83,11 @@
     private final GroupExpansionManager mGroupExpansionManager;
     private final RowContentBindStage mRowContentBindStage;
     private final NotificationLogger mNotificationLogger;
+    private final NotificationRowLogger mLogBufferLogger;
     private final HeadsUpManager mHeadsUpManager;
     private final ExpandableNotificationRow.OnExpandClickListener mOnExpandClickListener;
     private final StatusBarStateController mStatusBarStateController;
     private final MetricsLogger mMetricsLogger;
-
-    private final ExpandableNotificationRow.ExpansionLogger mExpansionLogger =
-            this::logNotificationExpansion;
     private final ExpandableNotificationRow.CoordinateOnClickListener mOnFeedbackClickListener;
     private final NotificationGutsManager mNotificationGutsManager;
     private final OnUserInteractionCallback mOnUserInteractionCallback;
@@ -101,8 +99,32 @@
     private final Optional<BubblesManager> mBubblesManagerOptional;
     private final SmartReplyConstants mSmartReplyConstants;
     private final SmartReplyController mSmartReplyController;
-
     private final ExpandableNotificationRowDragController mDragController;
+    private final ExpandableNotificationRow.ExpandableNotificationRowLogger mLoggerCallback =
+            new ExpandableNotificationRow.ExpandableNotificationRowLogger() {
+                @Override
+                public void logNotificationExpansion(String key, boolean userAction,
+                        boolean expanded) {
+                    mNotificationLogger.onExpansionChanged(key, userAction, expanded);
+                }
+
+                @Override
+                public void logKeepInParentChildDetached(
+                        NotificationEntry child,
+                        NotificationEntry oldParent
+                ) {
+                    mLogBufferLogger.logKeepInParentChildDetached(child, oldParent);
+                }
+
+                @Override
+                public void logSkipAttachingKeepInParentChild(
+                        NotificationEntry child,
+                        NotificationEntry newParent
+                ) {
+                    mLogBufferLogger.logSkipAttachingKeepInParentChild(child, newParent);
+                }
+            };
+
 
     @Inject
     public ExpandableNotificationRowController(
@@ -110,6 +132,7 @@
             ActivatableNotificationViewController activatableNotificationViewController,
             RemoteInputViewSubcomponent.Factory rivSubcomponentFactory,
             MetricsLogger metricsLogger,
+            NotificationRowLogger logBufferLogger,
             NotificationListContainer listContainer,
             NotificationMediaManager mediaManager,
             SmartReplyConstants smartReplyConstants,
@@ -163,6 +186,7 @@
         mBubblesManagerOptional = bubblesManagerOptional;
         mDragController = dragController;
         mMetricsLogger = metricsLogger;
+        mLogBufferLogger = logBufferLogger;
         mSmartReplyConstants = smartReplyConstants;
         mSmartReplyController = smartReplyController;
     }
@@ -177,7 +201,7 @@
                 mRemoteInputViewSubcomponentFactory,
                 mAppName,
                 mNotificationKey,
-                mExpansionLogger,
+                mLoggerCallback,
                 mKeyguardBypassController,
                 mGroupMembershipManager,
                 mGroupExpansionManager,
@@ -243,10 +267,6 @@
                 }
             };
 
-    private void logNotificationExpansion(String key, boolean userAction, boolean expanded) {
-        mNotificationLogger.onExpansionChanged(key, userAction, expanded);
-    }
-
     @Override
     @NonNull
     public String getNodeLabel() {
@@ -336,4 +356,29 @@
     public void setFeedbackIcon(@Nullable FeedbackIcon icon) {
         mView.setFeedbackIcon(icon);
     }
+
+    @Override
+    public boolean offerToKeepInParentForAnimation() {
+        if (mFeatureFlags.isEnabled(Flags.NOTIFICATION_GROUP_DISMISSAL_ANIMATION)) {
+            mView.setKeepInParentForDismissAnimation(true);
+            return true;
+        }
+        return false;
+    }
+
+    @Override
+    public boolean removeFromParentIfKeptForAnimation() {
+        ExpandableNotificationRow parent = mView.getNotificationParent();
+        if (mView.keepInParentForDismissAnimation() && parent != null) {
+            parent.removeChildNotification(mView);
+            return true;
+        }
+
+        return false;
+    }
+
+    @Override
+    public void resetKeepInParentForAnimation() {
+        mView.setKeepInParentForDismissAnimation(false);
+    }
 }
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/NotificationRowLogger.kt b/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/NotificationRowLogger.kt
new file mode 100644
index 0000000..ce11be3
--- /dev/null
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/NotificationRowLogger.kt
@@ -0,0 +1,53 @@
+/*
+ * Copyright (c) 2022 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ *
+ */
+
+package com.android.systemui.statusbar.notification.row
+
+import com.android.systemui.log.dagger.NotificationLog
+import com.android.systemui.plugins.log.LogBuffer
+import com.android.systemui.plugins.log.LogLevel
+import com.android.systemui.statusbar.notification.collection.NotificationEntry
+import com.android.systemui.statusbar.notification.logKey
+import javax.inject.Inject
+
+class NotificationRowLogger @Inject constructor(@NotificationLog private val buffer: LogBuffer) {
+    fun logKeepInParentChildDetached(child: NotificationEntry, oldParent: NotificationEntry?) {
+        buffer.log(
+            TAG,
+            LogLevel.DEBUG,
+            {
+                str1 = child.logKey
+                str2 = oldParent.logKey
+            },
+            { "Detach child $str1 kept in parent $str2" }
+        )
+    }
+
+    fun logSkipAttachingKeepInParentChild(child: NotificationEntry, newParent: NotificationEntry?) {
+        buffer.log(
+            TAG,
+            LogLevel.WARNING,
+            {
+                str1 = child.logKey
+                str2 = newParent.logKey
+            },
+            { "Skipping to attach $str1 to $str2, because it still flagged to keep in parent" }
+        )
+    }
+}
+
+private const val TAG = "NotifRow"
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/NotificationStackScrollLayout.java b/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/NotificationStackScrollLayout.java
index 962eeb9..2c096f5 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/NotificationStackScrollLayout.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/NotificationStackScrollLayout.java
@@ -32,6 +32,7 @@
 import android.animation.TimeAnimator;
 import android.animation.ValueAnimator;
 import android.annotation.ColorInt;
+import android.annotation.FloatRange;
 import android.annotation.IntDef;
 import android.annotation.NonNull;
 import android.annotation.Nullable;
@@ -191,6 +192,7 @@
     /** Used to track the Y positions that were already used to draw debug text labels. */
     private Set<Integer> mDebugTextUsedYPositions;
     private final boolean mDebugRemoveAnimation;
+    private final boolean mSimplifiedAppearFraction;
 
     private int mContentHeight;
     private float mIntrinsicContentHeight;
@@ -571,6 +573,7 @@
         FeatureFlags featureFlags = Dependency.get(FeatureFlags.class);
         mDebugLines = featureFlags.isEnabled(Flags.NSSL_DEBUG_LINES);
         mDebugRemoveAnimation = featureFlags.isEnabled(Flags.NSSL_DEBUG_REMOVE_ANIMATION);
+        mSimplifiedAppearFraction = featureFlags.isEnabled(Flags.SIMPLIFIED_APPEAR_FRACTION);
         mSectionsManager = Dependency.get(NotificationSectionsManager.class);
         mScreenOffAnimationController =
                 Dependency.get(ScreenOffAnimationController.class);
@@ -1411,10 +1414,8 @@
         }
         int stackHeight;
         float translationY;
-        float appearEndPosition = getAppearEndPosition();
-        float appearStartPosition = getAppearStartPosition();
         float appearFraction = 1.0f;
-        boolean appearing = height < appearEndPosition;
+        boolean appearing = calculateAppearFraction(height) < 1;
         mAmbientState.setAppearing(appearing);
         if (!appearing) {
             translationY = 0;
@@ -1445,11 +1446,14 @@
             } else {
                 // This may happen when pushing up a heads up. We linearly push it up from the
                 // start
-                translationY = height - appearStartPosition + getExpandTranslationStart();
+                translationY = height - getAppearStartPosition() + getExpandTranslationStart();
             }
             stackHeight = (int) (height - translationY);
-            if (isHeadsUpTransition()) {
-                translationY = MathUtils.lerp(mHeadsUpInset - mTopPadding, 0, appearFraction);
+            if (isHeadsUpTransition() && appearFraction >= 0) {
+                int topSpacing = mShouldUseSplitNotificationShade
+                        ? mAmbientState.getStackTopMargin() : mTopPadding;
+                float startPos = mHeadsUpInset - topSpacing;
+                translationY = MathUtils.lerp(startPos, 0, appearFraction);
             }
         }
         mAmbientState.setAppearFraction(appearFraction);
@@ -1581,7 +1585,7 @@
      */
     @ShadeViewRefactor(RefactorComponent.COORDINATOR)
     private float getAppearEndPosition() {
-        int appearPosition = 0;
+        int appearPosition = mAmbientState.getStackTopMargin();
         int visibleNotifCount = mController.getVisibleNotificationCount();
         if (mEmptyShadeView.getVisibility() == GONE && visibleNotifCount > 0) {
             if (isHeadsUpTransition()
@@ -1605,18 +1609,49 @@
         return mAmbientState.getTrackedHeadsUpRow() != null;
     }
 
-    /**
-     * @param height the height of the panel
-     * @return the fraction of the appear animation that has been performed
-     */
+    // TODO(b/246353296): remove it when Flags.SIMPLIFIED_APPEAR_FRACTION is removed
     @ShadeViewRefactor(RefactorComponent.COORDINATOR)
-    public float calculateAppearFraction(float height) {
+    public float calculateAppearFractionOld(float height) {
         float appearEndPosition = getAppearEndPosition();
         float appearStartPosition = getAppearStartPosition();
         return (height - appearStartPosition)
                 / (appearEndPosition - appearStartPosition);
     }
 
+    /**
+     * @param height the height of the panel
+     * @return Fraction of the appear animation that has been performed. Normally follows expansion
+     * fraction so goes from 0 to 1, the only exception is HUN where it can go negative, down to -1,
+     * when HUN is swiped up.
+     */
+    @FloatRange(from = -1.0, to = 1.0)
+    public float simplifiedAppearFraction(float height) {
+        if (isHeadsUpTransition()) {
+            // HUN is a special case because fraction can go negative if swiping up. And for now
+            // it must go negative as other pieces responsible for proper translation up assume
+            // negative value for HUN going up.
+            // This can't use expansion fraction as that goes only from 0 to 1. Also when
+            // appear fraction for HUN is 0, expansion fraction will be already around 0.2-0.3
+            // and that makes translation jump immediately. Let's use old implementation for now and
+            // see if we can figure out something better
+            return MathUtils.constrain(calculateAppearFractionOld(height), -1, 1);
+        } else {
+            return mAmbientState.getExpansionFraction();
+        }
+    }
+
+    public float calculateAppearFraction(float height) {
+        if (mSimplifiedAppearFraction) {
+            return simplifiedAppearFraction(height);
+        } else if (mShouldUseSplitNotificationShade) {
+            // for split shade we want to always use the new way of calculating appear fraction
+            // because without it heads-up experience is very broken and it's less risky change
+            return simplifiedAppearFraction(height);
+        } else {
+            return calculateAppearFractionOld(height);
+        }
+    }
+
     @ShadeViewRefactor(RefactorComponent.COORDINATOR)
     public float getStackTranslation() {
         return mStackTranslation;
@@ -2739,6 +2774,10 @@
             }
         } else {
             mSwipedOutViews.remove(child);
+
+            if (child instanceof ExpandableNotificationRow) {
+                ((ExpandableNotificationRow) child).removeChildrenWithKeepInParent();
+            }
         }
         updateAnimationState(false, child);
 
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/NotificationStackScrollLayoutController.java b/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/NotificationStackScrollLayoutController.java
index 0240bbc..ad4501a 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/NotificationStackScrollLayoutController.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/NotificationStackScrollLayoutController.java
@@ -443,7 +443,11 @@
                     if (!row.isDismissed()) {
                         handleChildViewDismissed(view);
                     }
+
                     row.removeFromTransientContainer();
+                    if (row instanceof ExpandableNotificationRow) {
+                        ((ExpandableNotificationRow) row).removeChildrenWithKeepInParent();
+                    }
                 }
 
                 /**
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/phone/BiometricUnlockController.java b/packages/SystemUI/src/com/android/systemui/statusbar/phone/BiometricUnlockController.java
index f72f1bc..182d397 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/BiometricUnlockController.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/BiometricUnlockController.java
@@ -763,6 +763,15 @@
     }
 
     @Override
+    public void onKeyguardBouncerStateChanged(boolean bouncerIsOrWillBeShowing) {
+        // When the bouncer is dismissed, treat this as a reset of the unlock mode. The user
+        // may have gone back instead of successfully unlocking
+        if (!bouncerIsOrWillBeShowing) {
+            resetMode();
+        }
+    }
+
+    @Override
     public void dump(PrintWriter pw, String[] args) {
         pw.println(" BiometricUnlockController:");
         pw.print("   mMode="); pw.println(mMode);
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/phone/CentralSurfacesImpl.java b/packages/SystemUI/src/com/android/systemui/statusbar/phone/CentralSurfacesImpl.java
index a592da4..bc2ee1f 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/CentralSurfacesImpl.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/CentralSurfacesImpl.java
@@ -168,6 +168,7 @@
 import com.android.systemui.plugins.OverlayPlugin;
 import com.android.systemui.plugins.PluginDependencyProvider;
 import com.android.systemui.plugins.PluginListener;
+import com.android.systemui.plugins.PluginManager;
 import com.android.systemui.plugins.qs.QS;
 import com.android.systemui.plugins.statusbar.NotificationSwipeActionHelper.SnoozeOption;
 import com.android.systemui.plugins.statusbar.StatusBarStateController;
@@ -183,7 +184,6 @@
 import com.android.systemui.shade.ShadeController;
 import com.android.systemui.shade.ShadeExpansionChangeEvent;
 import com.android.systemui.shade.ShadeExpansionStateManager;
-import com.android.systemui.shared.plugins.PluginManager;
 import com.android.systemui.statusbar.AutoHideUiElement;
 import com.android.systemui.statusbar.BackDropView;
 import com.android.systemui.statusbar.CircleReveal;
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/phone/NotificationListenerWithPlugins.java b/packages/SystemUI/src/com/android/systemui/statusbar/phone/NotificationListenerWithPlugins.java
index 3811689..4fe03017 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/NotificationListenerWithPlugins.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/NotificationListenerWithPlugins.java
@@ -25,7 +25,7 @@
 import com.android.systemui.plugins.NotificationListenerController;
 import com.android.systemui.plugins.NotificationListenerController.NotificationProvider;
 import com.android.systemui.plugins.PluginListener;
-import com.android.systemui.shared.plugins.PluginManager;
+import com.android.systemui.plugins.PluginManager;
 
 import java.util.ArrayList;
 
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/phone/ScrimController.java b/packages/SystemUI/src/com/android/systemui/statusbar/phone/ScrimController.java
index c527f30..fb0d3e4 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/ScrimController.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/ScrimController.java
@@ -110,6 +110,12 @@
     private boolean mClipsQsScrim;
 
     /**
+     * Whether an activity is launching over the lockscreen. During the launch animation, we want to
+     * delay certain scrim changes until after the animation ends.
+     */
+    private boolean mOccludeAnimationPlaying = false;
+
+    /**
      * The amount of progress we are currently in if we're transitioning to the full shade.
      * 0.0f means we're not transitioning yet, while 1 means we're all the way in the full
      * shade.
@@ -733,6 +739,11 @@
         return mClipsQsScrim;
     }
 
+    public void setOccludeAnimationPlaying(boolean occludeAnimationPlaying) {
+        mOccludeAnimationPlaying = occludeAnimationPlaying;
+        applyAndDispatchState();
+    }
+
     private void setOrAdaptCurrentAnimation(@Nullable View scrim) {
         if (scrim == null) {
             return;
@@ -772,11 +783,15 @@
         }
 
         if (mState == ScrimState.UNLOCKED || mState == ScrimState.DREAMING) {
-            // Darken scrim as you pull down the shade when unlocked, unless the shade is expanding
-            // because we're doing the screen off animation OR the shade is collapsing because
-            // we're playing the unlock animation
+            final boolean occluding =
+                    mOccludeAnimationPlaying || mState.mLaunchingAffordanceWithPreview;
+
+            // Darken scrim as it's pulled down while unlocked. If we're unlocked but playing the
+            // screen off/occlusion animations, ignore expansion changes while those animations
+            // play.
             if (!mScreenOffAnimationController.shouldExpandNotifications()
-                    && !mAnimatingPanelExpansionOnUnlock) {
+                    && !mAnimatingPanelExpansionOnUnlock
+                    && !occluding) {
                 float behindFraction = getInterpolatedFraction();
                 behindFraction = (float) Math.pow(behindFraction, 0.8f);
                 if (mClipsQsScrim) {
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/phone/StatusBarHeadsUpChangeListener.java b/packages/SystemUI/src/com/android/systemui/statusbar/phone/StatusBarHeadsUpChangeListener.java
index 5512bed..3d6bebb 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/StatusBarHeadsUpChangeListener.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/StatusBarHeadsUpChangeListener.java
@@ -72,9 +72,9 @@
                 //resize the layout. Let's
                 // make sure that the window stays small for one frame until the
                 //touchableRegion is set.
-                mNotificationPanelViewController.getView().requestLayout();
+                mNotificationPanelViewController.requestLayoutOnView();
                 mNotificationShadeWindowController.setForceWindowCollapsed(true);
-                mNotificationPanelViewController.getView().post(() -> {
+                mNotificationPanelViewController.postToView(() -> {
                     mNotificationShadeWindowController.setForceWindowCollapsed(false);
                 });
             }
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/phone/StatusBarKeyguardViewManager.java b/packages/SystemUI/src/com/android/systemui/statusbar/phone/StatusBarKeyguardViewManager.java
index 01a1ebe..fcf33b4 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/StatusBarKeyguardViewManager.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/StatusBarKeyguardViewManager.java
@@ -20,6 +20,7 @@
 
 import static com.android.systemui.plugins.ActivityStarter.OnDismissAction;
 import static com.android.systemui.statusbar.phone.BiometricUnlockController.MODE_DISMISS_BOUNCER;
+import static com.android.systemui.statusbar.phone.BiometricUnlockController.MODE_SHOW_BOUNCER;
 import static com.android.systemui.statusbar.phone.BiometricUnlockController.MODE_UNLOCK_COLLAPSING;
 import static com.android.systemui.statusbar.phone.BiometricUnlockController.MODE_WAKE_AND_UNLOCK;
 import static com.android.systemui.statusbar.phone.BiometricUnlockController.MODE_WAKE_AND_UNLOCK_PULSING;
@@ -77,7 +78,6 @@
 import com.android.systemui.statusbar.RemoteInputController;
 import com.android.systemui.statusbar.StatusBarState;
 import com.android.systemui.statusbar.SysuiStatusBarStateController;
-import com.android.systemui.statusbar.notification.ViewGroupFadeHelper;
 import com.android.systemui.statusbar.phone.KeyguardBouncer.PrimaryBouncerExpansionCallback;
 import com.android.systemui.statusbar.policy.ConfigurationController;
 import com.android.systemui.statusbar.policy.KeyguardStateController;
@@ -218,7 +218,7 @@
 
     protected LockPatternUtils mLockPatternUtils;
     protected ViewMediatorCallback mViewMediatorCallback;
-    protected CentralSurfaces mCentralSurfaces;
+    @Nullable protected CentralSurfaces mCentralSurfaces;
     private NotificationPanelViewController mNotificationPanelViewController;
     private BiometricUnlockController mBiometricUnlockController;
     private boolean mCentralSurfacesRegistered;
@@ -266,7 +266,7 @@
     private final KeyguardUpdateMonitor mKeyguardUpdateManager;
     private final LatencyTracker mLatencyTracker;
     private final KeyguardSecurityModel mKeyguardSecurityModel;
-    private KeyguardBypassController mBypassController;
+    @Nullable private KeyguardBypassController mBypassController;
     @Nullable private AlternateBouncer mAlternateBouncer;
 
     private final KeyguardUpdateMonitorCallback mUpdateMonitorCallback =
@@ -478,6 +478,7 @@
         } else if (mKeyguardStateController.isShowing()  && !hideBouncerOverDream) {
             if (!isWakeAndUnlocking()
                     && !(mBiometricUnlockController.getMode() == MODE_DISMISS_BOUNCER)
+                    && !(mBiometricUnlockController.getMode() == MODE_SHOW_BOUNCER)
                     && !isUnlockCollapsing()) {
                 if (mPrimaryBouncer != null) {
                     mPrimaryBouncer.setExpansion(fraction);
@@ -742,6 +743,12 @@
     }
 
     private void updateAlternateBouncerShowing(boolean updateScrim) {
+        if (!mCentralSurfacesRegistered) {
+            // if CentralSurfaces hasn't been registered yet, then the controllers below haven't
+            // been initialized yet so there's no need to attempt to forward them events.
+            return;
+        }
+
         final boolean isShowingAlternateBouncer = isShowingAlternateBouncer();
         if (mKeyguardMessageAreaController != null) {
             mKeyguardMessageAreaController.setIsVisible(isShowingAlternateBouncer);
@@ -1009,7 +1016,7 @@
     public void onKeyguardFadedAway() {
         mNotificationContainer.postDelayed(() -> mNotificationShadeWindowController
                         .setKeyguardFadingAway(false), 100);
-        ViewGroupFadeHelper.reset(mNotificationPanelViewController.getView());
+        mNotificationPanelViewController.resetViewAlphas();
         mCentralSurfaces.finishKeyguardFadingAway();
         mBiometricUnlockController.finishKeyguardFadingAway();
         WindowManagerGlobal.getInstance().trimMemory(
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/policy/ExtensionControllerImpl.java b/packages/SystemUI/src/com/android/systemui/statusbar/policy/ExtensionControllerImpl.java
index 4c6c7e0..3d0e69c 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/policy/ExtensionControllerImpl.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/policy/ExtensionControllerImpl.java
@@ -22,7 +22,7 @@
 import com.android.systemui.dagger.SysUISingleton;
 import com.android.systemui.plugins.Plugin;
 import com.android.systemui.plugins.PluginListener;
-import com.android.systemui.shared.plugins.PluginManager;
+import com.android.systemui.plugins.PluginManager;
 import com.android.systemui.statusbar.policy.ConfigurationController.ConfigurationListener;
 import com.android.systemui.tuner.TunerService;
 import com.android.systemui.tuner.TunerService.Tunable;
diff --git a/packages/SystemUI/src/com/android/systemui/toast/ToastFactory.java b/packages/SystemUI/src/com/android/systemui/toast/ToastFactory.java
index 26cc6ba3..f3b9cc1 100644
--- a/packages/SystemUI/src/com/android/systemui/toast/ToastFactory.java
+++ b/packages/SystemUI/src/com/android/systemui/toast/ToastFactory.java
@@ -25,8 +25,8 @@
 import com.android.systemui.dagger.SysUISingleton;
 import com.android.systemui.dump.DumpManager;
 import com.android.systemui.plugins.PluginListener;
+import com.android.systemui.plugins.PluginManager;
 import com.android.systemui.plugins.ToastPlugin;
-import com.android.systemui.shared.plugins.PluginManager;
 
 import java.io.PrintWriter;
 
diff --git a/packages/SystemUI/src/com/android/systemui/tuner/PluginFragment.java b/packages/SystemUI/src/com/android/systemui/tuner/PluginFragment.java
index fe183fc..4999515 100644
--- a/packages/SystemUI/src/com/android/systemui/tuner/PluginFragment.java
+++ b/packages/SystemUI/src/com/android/systemui/tuner/PluginFragment.java
@@ -38,9 +38,9 @@
 import com.android.systemui.Dependency;
 import com.android.systemui.R;
 import com.android.systemui.plugins.PluginEnablerImpl;
+import com.android.systemui.plugins.PluginManager;
 import com.android.systemui.shared.plugins.PluginActionManager;
 import com.android.systemui.shared.plugins.PluginEnabler;
-import com.android.systemui.shared.plugins.PluginManager;
 import com.android.systemui.shared.plugins.PluginPrefs;
 
 import java.util.List;
diff --git a/packages/SystemUI/src/com/android/systemui/unfold/FoldAodAnimationController.kt b/packages/SystemUI/src/com/android/systemui/unfold/FoldAodAnimationController.kt
index 0f06144..6216acd 100644
--- a/packages/SystemUI/src/com/android/systemui/unfold/FoldAodAnimationController.kt
+++ b/packages/SystemUI/src/com/android/systemui/unfold/FoldAodAnimationController.kt
@@ -92,6 +92,7 @@
         deviceStateManager.registerCallback(executor, FoldListener())
         wakefulnessLifecycle.addObserver(this)
 
+        // TODO(b/254878364): remove this call to NPVC.getView()
         centralSurfaces.notificationPanelViewController.view.repeatWhenAttached {
             repeatOnLifecycle(Lifecycle.State.STARTED) { listenForDozing(this) }
         }
@@ -157,6 +158,7 @@
             // We don't need to wait for the scrim as it is already displayed
             // but we should wait for the initial animation preparations to be drawn
             // (setting initial alpha/translation)
+            // TODO(b/254878364): remove this call to NPVC.getView()
             OneShotPreDrawListener.add(
                 centralSurfaces.notificationPanelViewController.view,
                 onReady
diff --git a/packages/SystemUI/src/com/android/systemui/unfold/UnfoldLightRevealOverlayAnimation.kt b/packages/SystemUI/src/com/android/systemui/unfold/UnfoldLightRevealOverlayAnimation.kt
index 6ed3a09..ae30ca0 100644
--- a/packages/SystemUI/src/com/android/systemui/unfold/UnfoldLightRevealOverlayAnimation.kt
+++ b/packages/SystemUI/src/com/android/systemui/unfold/UnfoldLightRevealOverlayAnimation.kt
@@ -15,7 +15,7 @@
  */
 package com.android.systemui.unfold
 
-import android.animation.ValueAnimator
+import android.content.ContentResolver
 import android.content.Context
 import android.graphics.PixelFormat
 import android.hardware.devicestate.DeviceStateManager
@@ -39,6 +39,7 @@
 import com.android.systemui.statusbar.LinearLightRevealEffect
 import com.android.systemui.unfold.UnfoldTransitionProgressProvider.TransitionProgressListener
 import com.android.systemui.unfold.updates.RotationChangeProvider
+import com.android.systemui.unfold.util.ScaleAwareTransitionProgressProvider.Companion.areAnimationsEnabled
 import com.android.systemui.util.traceSection
 import com.android.wm.shell.displayareahelper.DisplayAreaHelper
 import java.util.Optional
@@ -52,6 +53,7 @@
 constructor(
     private val context: Context,
     private val deviceStateManager: DeviceStateManager,
+    private val contentResolver: ContentResolver,
     private val displayManager: DisplayManager,
     private val unfoldTransitionProgressProvider: UnfoldTransitionProgressProvider,
     private val displayAreaHelper: Optional<DisplayAreaHelper>,
@@ -92,7 +94,7 @@
                 overlayContainer = builder.build()
 
                 SurfaceControl.Transaction()
-                    .setLayer(overlayContainer, Integer.MAX_VALUE)
+                    .setLayer(overlayContainer, UNFOLD_OVERLAY_LAYER_Z_INDEX)
                     .show(overlayContainer)
                     .apply()
 
@@ -117,7 +119,7 @@
         Trace.beginSection("UnfoldLightRevealOverlayAnimation#onScreenTurningOn")
         try {
             // Add the view only if we are unfolding and this is the first screen on
-            if (!isFolded && !isUnfoldHandled && ValueAnimator.areAnimatorsEnabled()) {
+            if (!isFolded && !isUnfoldHandled && contentResolver.areAnimationsEnabled()) {
                 addView(onOverlayReady)
                 isUnfoldHandled = true
             } else {
@@ -162,11 +164,10 @@
                 // blocker (turn on the brightness) only when the content is actually visible as it
                 // might be presented only in the next frame.
                 // See b/197538198
-                transaction
-                    .setFrameTimelineVsync(vsyncId)
-                    .apply()
+                transaction.setFrameTimelineVsync(vsyncId).apply()
 
-                transaction.setFrameTimelineVsync(vsyncId + 1)
+                transaction
+                    .setFrameTimelineVsync(vsyncId + 1)
                     .addTransactionCommittedListener(backgroundExecutor) {
                         Trace.endAsyncSection("UnfoldLightRevealOverlayAnimation#relayout", 0)
                         callback.run()
@@ -218,8 +219,7 @@
     }
 
     private fun getUnfoldedDisplayInfo(): DisplayInfo =
-        displayManager
-            .displays
+        displayManager.displays
             .asSequence()
             .map { DisplayInfo().apply { it.getDisplayInfo(this) } }
             .filter { it.type == Display.TYPE_INTERNAL }
@@ -266,5 +266,14 @@
                     isUnfoldHandled = false
                 }
                 this.isFolded = isFolded
-            })
+            }
+        )
+
+    private companion object {
+        private const val ROTATION_ANIMATION_OVERLAY_Z_INDEX = Integer.MAX_VALUE
+
+        // Put the unfold overlay below the rotation animation screenshot to hide the moment
+        // when it is rotated but the rotation of the other windows hasn't happen yet
+        private const val UNFOLD_OVERLAY_LAYER_Z_INDEX = ROTATION_ANIMATION_OVERLAY_Z_INDEX - 1
+    }
 }
diff --git a/packages/SystemUI/src/com/android/systemui/user/domain/interactor/UserInteractor.kt b/packages/SystemUI/src/com/android/systemui/user/domain/interactor/UserInteractor.kt
index 83f0711..6672469 100644
--- a/packages/SystemUI/src/com/android/systemui/user/domain/interactor/UserInteractor.kt
+++ b/packages/SystemUI/src/com/android/systemui/user/domain/interactor/UserInteractor.kt
@@ -66,7 +66,6 @@
 import kotlinx.coroutines.flow.asStateFlow
 import kotlinx.coroutines.flow.combine
 import kotlinx.coroutines.flow.distinctUntilChanged
-import kotlinx.coroutines.flow.filter
 import kotlinx.coroutines.flow.launchIn
 import kotlinx.coroutines.flow.map
 import kotlinx.coroutines.flow.onEach
@@ -117,7 +116,7 @@
     private val callbacks = mutableSetOf<UserCallback>()
     private val userInfos =
         combine(repository.userSwitcherSettings, repository.userInfos) { settings, userInfos ->
-            userInfos.filter { !it.isGuest || canCreateGuestUser(settings) }
+            userInfos.filter { !it.isGuest || canCreateGuestUser(settings) }.filter { it.isFull }
         }
 
     /** List of current on-device users to select from. */
@@ -138,18 +137,12 @@
     /** The currently-selected user. */
     val selectedUser: Flow<UserModel>
         get() =
-            combine(
-                repository.selectedUserInfo,
-                repository.userSwitcherSettings,
-            ) { selectedUserInfo, settings ->
+            repository.selectedUserInfo.map { selectedUserInfo ->
                 val selectedUserId = selectedUserInfo.id
-                checkNotNull(
-                    toUserModel(
-                        userInfo = selectedUserInfo,
-                        selectedUserId = selectedUserId,
-                        canSwitchUsers = canSwitchUsers(selectedUserId),
-                        isUserSwitcherEnabled = settings.isUserSwitcherEnabled,
-                    )
+                toUserModel(
+                    userInfo = selectedUserInfo,
+                    selectedUserId = selectedUserId,
+                    canSwitchUsers = canSwitchUsers(selectedUserId)
                 )
             }
 
@@ -629,7 +622,7 @@
             // The guest user should go in the last position.
             .sortedBy { it.isGuest }
             .mapNotNull { userInfo ->
-                toUserModel(
+                filterAndMapToUserModel(
                     userInfo = userInfo,
                     selectedUserId = selectedUserId,
                     canSwitchUsers = canSwitchUsers,
@@ -638,51 +631,65 @@
             }
     }
 
-    private suspend fun toUserModel(
+    /**
+     * Maps UserInfo to UserModel based on some parameters and return null under certain conditions
+     * to be filtered out.
+     */
+    private suspend fun filterAndMapToUserModel(
         userInfo: UserInfo,
         selectedUserId: Int,
         canSwitchUsers: Boolean,
         isUserSwitcherEnabled: Boolean,
     ): UserModel? {
-        val userId = userInfo.id
-        val isSelected = userId == selectedUserId
-
         return when {
             // When the user switcher is not enabled in settings, we only show the primary user.
             !isUserSwitcherEnabled && !userInfo.isPrimary -> null
-
             // We avoid showing disabled users.
             !userInfo.isEnabled -> null
-            userInfo.isGuest ->
-                UserModel(
-                    id = userId,
-                    name = Text.Loaded(userInfo.name),
-                    image =
-                        getUserImage(
-                            isGuest = true,
-                            userId = userId,
-                        ),
-                    isSelected = isSelected,
-                    isSelectable = canSwitchUsers,
-                    isGuest = true,
-                )
-            userInfo.supportsSwitchToByUser() ->
-                UserModel(
-                    id = userId,
-                    name = Text.Loaded(userInfo.name),
-                    image =
-                        getUserImage(
-                            isGuest = false,
-                            userId = userId,
-                        ),
-                    isSelected = isSelected,
-                    isSelectable = canSwitchUsers || isSelected,
-                    isGuest = false,
-                )
+            // We meet the conditions to return the UserModel.
+            userInfo.isGuest || userInfo.supportsSwitchToByUser() ->
+                toUserModel(userInfo, selectedUserId, canSwitchUsers)
             else -> null
         }
     }
 
+    /** Maps UserInfo to UserModel based on some parameters. */
+    private suspend fun toUserModel(
+        userInfo: UserInfo,
+        selectedUserId: Int,
+        canSwitchUsers: Boolean
+    ): UserModel {
+        val userId = userInfo.id
+        val isSelected = userId == selectedUserId
+        return if (userInfo.isGuest) {
+            UserModel(
+                id = userId,
+                name = Text.Loaded(userInfo.name),
+                image =
+                    getUserImage(
+                        isGuest = true,
+                        userId = userId,
+                    ),
+                isSelected = isSelected,
+                isSelectable = canSwitchUsers,
+                isGuest = true,
+            )
+        } else {
+            UserModel(
+                id = userId,
+                name = Text.Loaded(userInfo.name),
+                image =
+                    getUserImage(
+                        isGuest = false,
+                        userId = userId,
+                    ),
+                isSelected = isSelected,
+                isSelectable = canSwitchUsers || isSelected,
+                isGuest = false,
+            )
+        }
+    }
+
     private suspend fun canSwitchUsers(selectedUserId: Int): Boolean {
         return withContext(backgroundDispatcher) {
             manager.getUserSwitchability(UserHandle.of(selectedUserId))
diff --git a/packages/SystemUI/src/com/android/systemui/util/sensors/AsyncSensorManager.java b/packages/SystemUI/src/com/android/systemui/util/sensors/AsyncSensorManager.java
index 4875982..9b06a37 100644
--- a/packages/SystemUI/src/com/android/systemui/util/sensors/AsyncSensorManager.java
+++ b/packages/SystemUI/src/com/android/systemui/util/sensors/AsyncSensorManager.java
@@ -31,8 +31,8 @@
 import com.android.internal.util.Preconditions;
 import com.android.systemui.dagger.SysUISingleton;
 import com.android.systemui.plugins.PluginListener;
+import com.android.systemui.plugins.PluginManager;
 import com.android.systemui.plugins.SensorManagerPlugin;
-import com.android.systemui.shared.plugins.PluginManager;
 import com.android.systemui.util.concurrency.ThreadFactory;
 
 import java.util.ArrayList;
diff --git a/packages/SystemUI/src/com/android/systemui/volume/VolumeDialogImpl.java b/packages/SystemUI/src/com/android/systemui/volume/VolumeDialogImpl.java
index 0d96272..63b98bb 100644
--- a/packages/SystemUI/src/com/android/systemui/volume/VolumeDialogImpl.java
+++ b/packages/SystemUI/src/com/android/systemui/volume/VolumeDialogImpl.java
@@ -72,6 +72,7 @@
 import android.os.SystemClock;
 import android.os.Trace;
 import android.os.VibrationEffect;
+import android.provider.DeviceConfig;
 import android.provider.Settings;
 import android.provider.Settings.Global;
 import android.text.InputFilter;
@@ -108,6 +109,8 @@
 import androidx.annotation.NonNull;
 import androidx.annotation.Nullable;
 
+import com.android.internal.annotations.VisibleForTesting;
+import com.android.internal.config.sysui.SystemUiDeviceConfigFlags;
 import com.android.internal.graphics.drawable.BackgroundBlurDrawable;
 import com.android.internal.jank.InteractionJankMonitor;
 import com.android.internal.view.RotationPolicy;
@@ -125,11 +128,15 @@
 import com.android.systemui.statusbar.policy.ConfigurationController;
 import com.android.systemui.statusbar.policy.DeviceProvisionedController;
 import com.android.systemui.util.AlphaTintDrawableWrapper;
+import com.android.systemui.util.DeviceConfigProxy;
 import com.android.systemui.util.RoundedCornerProgressDrawable;
 
 import java.io.PrintWriter;
 import java.util.ArrayList;
 import java.util.List;
+import java.util.Optional;
+import java.util.Set;
+import java.util.concurrent.Executor;
 import java.util.function.Consumer;
 
 /**
@@ -186,6 +193,9 @@
     private ViewGroup mDialogRowsView;
     private ViewGroup mRinger;
 
+    private DeviceConfigProxy mDeviceConfigProxy;
+    private Executor mExecutor;
+
     /**
      * Container for the top part of the dialog, which contains the ringer, the ringer drawer, the
      * volume rows, and the ellipsis button. This does not include the live caption button.
@@ -274,6 +284,13 @@
     private BackgroundBlurDrawable mDialogRowsViewBackground;
     private final InteractionJankMonitor mInteractionJankMonitor;
 
+    private boolean mSeparateNotification;
+
+    @VisibleForTesting
+    int mVolumeRingerIconDrawableId;
+    @VisibleForTesting
+    int mVolumeRingerMuteIconDrawableId;
+
     public VolumeDialogImpl(
             Context context,
             VolumeDialogController volumeDialogController,
@@ -283,7 +300,9 @@
             MediaOutputDialogFactory mediaOutputDialogFactory,
             VolumePanelFactory volumePanelFactory,
             ActivityStarter activityStarter,
-            InteractionJankMonitor interactionJankMonitor) {
+            InteractionJankMonitor interactionJankMonitor,
+            DeviceConfigProxy deviceConfigProxy,
+            Executor executor) {
         mContext =
                 new ContextThemeWrapper(context, R.style.volume_dialog_theme);
         mController = volumeDialogController;
@@ -323,6 +342,50 @@
         }
 
         initDimens();
+
+        mDeviceConfigProxy = deviceConfigProxy;
+        mExecutor = executor;
+        mSeparateNotification = mDeviceConfigProxy.getBoolean(DeviceConfig.NAMESPACE_SYSTEMUI,
+                SystemUiDeviceConfigFlags.VOLUME_SEPARATE_NOTIFICATION, false);
+        updateRingerModeIconSet();
+    }
+
+    /**
+     * If ringer and notification are the same stream (T and earlier), use notification-like bell
+     * icon set.
+     * If ringer and notification are separated, then use generic speaker icons.
+     */
+    private void updateRingerModeIconSet() {
+        if (mSeparateNotification) {
+            mVolumeRingerIconDrawableId = R.drawable.ic_speaker_on;
+            mVolumeRingerMuteIconDrawableId = R.drawable.ic_speaker_mute;
+        } else {
+            mVolumeRingerIconDrawableId = R.drawable.ic_volume_ringer;
+            mVolumeRingerMuteIconDrawableId = R.drawable.ic_volume_ringer_mute;
+        }
+
+        if (mRingerDrawerMuteIcon != null) {
+            mRingerDrawerMuteIcon.setImageResource(mVolumeRingerMuteIconDrawableId);
+        }
+        if (mRingerDrawerNormalIcon != null) {
+            mRingerDrawerNormalIcon.setImageResource(mVolumeRingerIconDrawableId);
+        }
+    }
+
+    /**
+     * Change icon for ring stream (not ringer mode icon)
+     */
+    private void updateRingRowIcon() {
+        Optional<VolumeRow> volumeRow = mRows.stream().filter(row -> row.stream == STREAM_RING)
+                .findFirst();
+        if (volumeRow.isPresent()) {
+            VolumeRow volRow = volumeRow.get();
+            volRow.iconRes = mSeparateNotification ? R.drawable.ic_ring_volume
+                    : R.drawable.ic_volume_ringer;
+            volRow.iconMuteRes = mSeparateNotification ? R.drawable.ic_ring_volume_off
+                    : R.drawable.ic_volume_ringer_mute;
+            volRow.setIcon(volRow.iconRes, mContext.getTheme());
+        }
     }
 
     @Override
@@ -339,6 +402,9 @@
         mController.getState();
 
         mConfigurationController.addCallback(this);
+
+        mDeviceConfigProxy.addOnPropertiesChangedListener(DeviceConfig.NAMESPACE_SYSTEMUI,
+                mExecutor, this::onDeviceConfigChange);
     }
 
     @Override
@@ -346,6 +412,24 @@
         mController.removeCallback(mControllerCallbackH);
         mHandler.removeCallbacksAndMessages(null);
         mConfigurationController.removeCallback(this);
+        mDeviceConfigProxy.removeOnPropertiesChangedListener(this::onDeviceConfigChange);
+    }
+
+    /**
+     * Update ringer mode icon based on the config
+     */
+    private void onDeviceConfigChange(DeviceConfig.Properties properties) {
+        Set<String> changeSet = properties.getKeyset();
+        if (changeSet.contains(SystemUiDeviceConfigFlags.VOLUME_SEPARATE_NOTIFICATION)) {
+            boolean newVal = properties.getBoolean(
+                    SystemUiDeviceConfigFlags.VOLUME_SEPARATE_NOTIFICATION, false);
+            if (newVal != mSeparateNotification) {
+                mSeparateNotification = newVal;
+                updateRingerModeIconSet();
+                updateRingRowIcon();
+
+            }
+        }
     }
 
     @Override
@@ -552,6 +636,9 @@
         mRingerDrawerNormalIcon = mDialog.findViewById(R.id.volume_drawer_normal_icon);
         mRingerDrawerNewSelectionBg = mDialog.findViewById(R.id.volume_drawer_selection_background);
 
+        mRingerDrawerMuteIcon.setImageResource(mVolumeRingerMuteIconDrawableId);
+        mRingerDrawerNormalIcon.setImageResource(mVolumeRingerIconDrawableId);
+
         setupRingerDrawer();
 
         mODICaptionsView = mDialog.findViewById(R.id.odi_captions);
@@ -575,8 +662,14 @@
             addRow(AudioManager.STREAM_MUSIC,
                     R.drawable.ic_volume_media, R.drawable.ic_volume_media_mute, true, true);
             if (!AudioSystem.isSingleVolume(mContext)) {
-                addRow(AudioManager.STREAM_RING,
-                        R.drawable.ic_volume_ringer, R.drawable.ic_volume_ringer_mute, true, false);
+                if (mSeparateNotification) {
+                    addRow(AudioManager.STREAM_RING, R.drawable.ic_ring_volume,
+                            R.drawable.ic_ring_volume_off, true, false);
+                } else {
+                    addRow(AudioManager.STREAM_RING, R.drawable.ic_volume_ringer,
+                            R.drawable.ic_volume_ringer, true, false);
+                }
+
                 addRow(STREAM_ALARM,
                         R.drawable.ic_alarm, R.drawable.ic_volume_alarm_mute, true, false);
                 addRow(AudioManager.STREAM_VOICE_CALL,
@@ -1532,8 +1625,8 @@
                     mRingerIcon.setTag(Events.ICON_STATE_VIBRATE);
                     break;
                 case AudioManager.RINGER_MODE_SILENT:
-                    mRingerIcon.setImageResource(R.drawable.ic_volume_ringer_mute);
-                    mSelectedRingerIcon.setImageResource(R.drawable.ic_volume_ringer_mute);
+                    mRingerIcon.setImageResource(mVolumeRingerMuteIconDrawableId);
+                    mSelectedRingerIcon.setImageResource(mVolumeRingerMuteIconDrawableId);
                     mRingerIcon.setTag(Events.ICON_STATE_MUTE);
                     addAccessibilityDescription(mRingerIcon, RINGER_MODE_SILENT,
                             mContext.getString(R.string.volume_ringer_hint_unmute));
@@ -1542,14 +1635,14 @@
                 default:
                     boolean muted = (mAutomute && ss.level == 0) || ss.muted;
                     if (!isZenMuted && muted) {
-                        mRingerIcon.setImageResource(R.drawable.ic_volume_ringer_mute);
-                        mSelectedRingerIcon.setImageResource(R.drawable.ic_volume_ringer_mute);
+                        mRingerIcon.setImageResource(mVolumeRingerMuteIconDrawableId);
+                        mSelectedRingerIcon.setImageResource(mVolumeRingerMuteIconDrawableId);
                         addAccessibilityDescription(mRingerIcon, RINGER_MODE_NORMAL,
                                 mContext.getString(R.string.volume_ringer_hint_unmute));
                         mRingerIcon.setTag(Events.ICON_STATE_MUTE);
                     } else {
-                        mRingerIcon.setImageResource(R.drawable.ic_volume_ringer);
-                        mSelectedRingerIcon.setImageResource(R.drawable.ic_volume_ringer);
+                        mRingerIcon.setImageResource(mVolumeRingerIconDrawableId);
+                        mSelectedRingerIcon.setImageResource(mVolumeRingerIconDrawableId);
                         if (mController.hasVibrator()) {
                             addAccessibilityDescription(mRingerIcon, RINGER_MODE_NORMAL,
                                     mContext.getString(R.string.volume_ringer_hint_vibrate));
diff --git a/packages/SystemUI/src/com/android/systemui/volume/dagger/VolumeModule.java b/packages/SystemUI/src/com/android/systemui/volume/dagger/VolumeModule.java
index c5792b9..8f10fa6 100644
--- a/packages/SystemUI/src/com/android/systemui/volume/dagger/VolumeModule.java
+++ b/packages/SystemUI/src/com/android/systemui/volume/dagger/VolumeModule.java
@@ -20,6 +20,7 @@
 import android.media.AudioManager;
 
 import com.android.internal.jank.InteractionJankMonitor;
+import com.android.systemui.dagger.qualifiers.Main;
 import com.android.systemui.media.dialog.MediaOutputDialogFactory;
 import com.android.systemui.plugins.ActivityStarter;
 import com.android.systemui.plugins.VolumeDialog;
@@ -27,11 +28,14 @@
 import com.android.systemui.statusbar.policy.AccessibilityManagerWrapper;
 import com.android.systemui.statusbar.policy.ConfigurationController;
 import com.android.systemui.statusbar.policy.DeviceProvisionedController;
+import com.android.systemui.util.DeviceConfigProxy;
 import com.android.systemui.volume.VolumeComponent;
 import com.android.systemui.volume.VolumeDialogComponent;
 import com.android.systemui.volume.VolumeDialogImpl;
 import com.android.systemui.volume.VolumePanelFactory;
 
+import java.util.concurrent.Executor;
+
 import dagger.Binds;
 import dagger.Module;
 import dagger.Provides;
@@ -55,7 +59,9 @@
             MediaOutputDialogFactory mediaOutputDialogFactory,
             VolumePanelFactory volumePanelFactory,
             ActivityStarter activityStarter,
-            InteractionJankMonitor interactionJankMonitor) {
+            InteractionJankMonitor interactionJankMonitor,
+            DeviceConfigProxy deviceConfigProxy,
+            @Main Executor executor) {
         VolumeDialogImpl impl = new VolumeDialogImpl(
                 context,
                 volumeDialogController,
@@ -65,7 +71,9 @@
                 mediaOutputDialogFactory,
                 volumePanelFactory,
                 activityStarter,
-                interactionJankMonitor);
+                interactionJankMonitor,
+                deviceConfigProxy,
+                executor);
         impl.setStreamImportant(AudioManager.STREAM_SYSTEM, false);
         impl.setAutomute(true);
         impl.setSilentMode(false);
diff --git a/packages/SystemUI/tests/AndroidManifest.xml b/packages/SystemUI/tests/AndroidManifest.xml
index 78c28ea..d8331ab 100644
--- a/packages/SystemUI/tests/AndroidManifest.xml
+++ b/packages/SystemUI/tests/AndroidManifest.xml
@@ -127,6 +127,12 @@
             android:finishOnCloseSystemDialogs="true"
             android:excludeFromRecents="true" />
 
+        <activity android:name=".sensorprivacy.SensorUseStartedActivityTest$SensorUseStartedActivityTestable"
+                  android:exported="false"
+                  android:theme="@style/Theme.SystemUI.Dialog.Alert"
+                  android:finishOnCloseSystemDialogs="true"
+                  android:excludeFromRecents="true" />
+
         <provider
             android:name="androidx.startup.InitializationProvider"
             tools:replace="android:authorities"
diff --git a/packages/SystemUI/tests/src/com/android/keyguard/clock/ClockManagerTest.java b/packages/SystemUI/tests/src/com/android/keyguard/clock/ClockManagerTest.java
index 27701be..7a5b772 100644
--- a/packages/SystemUI/tests/src/com/android/keyguard/clock/ClockManagerTest.java
+++ b/packages/SystemUI/tests/src/com/android/keyguard/clock/ClockManagerTest.java
@@ -36,8 +36,8 @@
 import com.android.systemui.dock.DockManager;
 import com.android.systemui.dock.DockManagerFake;
 import com.android.systemui.plugins.ClockPlugin;
+import com.android.systemui.plugins.PluginManager;
 import com.android.systemui.settings.UserTracker;
-import com.android.systemui.shared.plugins.PluginManager;
 import com.android.systemui.util.concurrency.FakeExecutor;
 import com.android.systemui.util.time.FakeSystemClock;
 
diff --git a/packages/SystemUI/tests/src/com/android/systemui/biometrics/AuthContainerViewTest.kt b/packages/SystemUI/tests/src/com/android/systemui/biometrics/AuthContainerViewTest.kt
index eaef159..5e6acd2 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/biometrics/AuthContainerViewTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/biometrics/AuthContainerViewTest.kt
@@ -111,6 +111,21 @@
     }
 
     @Test
+    fun testCredentialPasswordDismissesOnBack() {
+        val container = initializeCredentialPasswordContainer(addToView = true)
+        assertThat(container.parent).isNotNull()
+        val root = container.rootView
+
+        // Simulate back invocation
+        container.dispatchKeyEvent(KeyEvent(KeyEvent.ACTION_DOWN, KeyEvent.KEYCODE_BACK))
+        container.dispatchKeyEvent(KeyEvent(KeyEvent.ACTION_UP, KeyEvent.KEYCODE_BACK))
+        waitForIdleSync()
+
+        assertThat(container.parent).isNull()
+        assertThat(root.isAttachedToWindow).isFalse()
+    }
+
+    @Test
     fun testIgnoresAnimatedInWhenDismissed() {
         val container = initializeFingerprintContainer(addToView = false)
         container.dismissFromSystemServer()
@@ -355,20 +370,7 @@
 
     @Test
     fun testCredentialUI_disablesClickingOnBackground() {
-        whenever(userManager.getCredentialOwnerProfile(anyInt())).thenReturn(20)
-        whenever(lockPatternUtils.getKeyguardStoredPasswordQuality(eq(20))).thenReturn(
-            DevicePolicyManager.PASSWORD_QUALITY_NUMERIC
-        )
-
-        // In the credential view, clicking on the background (to cancel authentication) is not
-        // valid. Thus, the listener should be null, and it should not be in the accessibility
-        // hierarchy.
-        val container = initializeFingerprintContainer(
-            authenticators = BiometricManager.Authenticators.DEVICE_CREDENTIAL
-        )
-        waitForIdleSync()
-
-        assertThat(container.hasCredentialPasswordView()).isTrue()
+        val container = initializeCredentialPasswordContainer()
         assertThat(container.hasBiometricPrompt()).isFalse()
         assertThat(
             container.findViewById<View>(R.id.background)?.isImportantForAccessibility
@@ -428,6 +430,27 @@
         verify(callback).onTryAgainPressed(authContainer?.requestId ?: 0L)
     }
 
+    private fun initializeCredentialPasswordContainer(
+            addToView: Boolean = true,
+    ): TestAuthContainerView {
+        whenever(userManager.getCredentialOwnerProfile(anyInt())).thenReturn(20)
+        whenever(lockPatternUtils.getKeyguardStoredPasswordQuality(eq(20))).thenReturn(
+                DevicePolicyManager.PASSWORD_QUALITY_NUMERIC
+        )
+
+        // In the credential view, clicking on the background (to cancel authentication) is not
+        // valid. Thus, the listener should be null, and it should not be in the accessibility
+        // hierarchy.
+        val container = initializeFingerprintContainer(
+                authenticators = BiometricManager.Authenticators.DEVICE_CREDENTIAL,
+                addToView = addToView,
+        )
+        waitForIdleSync()
+
+        assertThat(container.hasCredentialPasswordView()).isTrue()
+        return container
+    }
+
     private fun initializeFingerprintContainer(
         authenticators: Int = BiometricManager.Authenticators.BIOMETRIC_WEAK,
         addToView: Boolean = true
diff --git a/packages/SystemUI/tests/src/com/android/systemui/flags/FeatureFlagsDebugRestarterTest.kt b/packages/SystemUI/tests/src/com/android/systemui/flags/FeatureFlagsDebugRestarterTest.kt
index ed16721..1e7b1f2 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/flags/FeatureFlagsDebugRestarterTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/flags/FeatureFlagsDebugRestarterTest.kt
@@ -48,22 +48,22 @@
     @Test
     fun testRestart_ImmediateWhenAsleep() {
         whenever(wakefulnessLifecycle.wakefulness).thenReturn(WAKEFULNESS_ASLEEP)
-        restarter.restartSystemUI()
-        verify(systemExitRestarter).restartSystemUI()
+        restarter.restart()
+        verify(systemExitRestarter).restart()
     }
 
     @Test
     fun testRestart_WaitsForSceenOff() {
         whenever(wakefulnessLifecycle.wakefulness).thenReturn(WAKEFULNESS_AWAKE)
 
-        restarter.restartSystemUI()
-        verify(systemExitRestarter, never()).restartSystemUI()
+        restarter.restart()
+        verify(systemExitRestarter, never()).restart()
 
         val captor = ArgumentCaptor.forClass(WakefulnessLifecycle.Observer::class.java)
         verify(wakefulnessLifecycle).addObserver(captor.capture())
 
         captor.value.onFinishedGoingToSleep()
 
-        verify(systemExitRestarter).restartSystemUI()
+        verify(systemExitRestarter).restart()
     }
 }
diff --git a/packages/SystemUI/tests/src/com/android/systemui/flags/FeatureFlagsReleaseRestarterTest.kt b/packages/SystemUI/tests/src/com/android/systemui/flags/FeatureFlagsReleaseRestarterTest.kt
index 7d807e2..68ca48d 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/flags/FeatureFlagsReleaseRestarterTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/flags/FeatureFlagsReleaseRestarterTest.kt
@@ -63,7 +63,7 @@
         whenever(batteryController.isPluggedIn).thenReturn(true)
 
         assertThat(executor.numPending()).isEqualTo(0)
-        restarter.restartSystemUI()
+        restarter.restart()
         assertThat(executor.numPending()).isEqualTo(1)
     }
 
@@ -72,11 +72,11 @@
         whenever(wakefulnessLifecycle.wakefulness).thenReturn(WAKEFULNESS_ASLEEP)
         whenever(batteryController.isPluggedIn).thenReturn(true)
 
-        restarter.restartSystemUI()
-        verify(systemExitRestarter, never()).restartSystemUI()
+        restarter.restart()
+        verify(systemExitRestarter, never()).restart()
         executor.advanceClockToLast()
         executor.runAllReady()
-        verify(systemExitRestarter).restartSystemUI()
+        verify(systemExitRestarter).restart()
     }
 
     @Test
@@ -85,7 +85,7 @@
         whenever(batteryController.isPluggedIn).thenReturn(true)
 
         assertThat(executor.numPending()).isEqualTo(0)
-        restarter.restartSystemUI()
+        restarter.restart()
         assertThat(executor.numPending()).isEqualTo(0)
     }
 
@@ -95,7 +95,7 @@
         whenever(batteryController.isPluggedIn).thenReturn(false)
 
         assertThat(executor.numPending()).isEqualTo(0)
-        restarter.restartSystemUI()
+        restarter.restart()
         assertThat(executor.numPending()).isEqualTo(0)
     }
 
@@ -105,8 +105,8 @@
         whenever(batteryController.isPluggedIn).thenReturn(true)
 
         assertThat(executor.numPending()).isEqualTo(0)
-        restarter.restartSystemUI()
-        restarter.restartSystemUI()
+        restarter.restart()
+        restarter.restart()
         assertThat(executor.numPending()).isEqualTo(1)
     }
 
@@ -115,7 +115,7 @@
         whenever(wakefulnessLifecycle.wakefulness).thenReturn(WAKEFULNESS_AWAKE)
         whenever(batteryController.isPluggedIn).thenReturn(true)
         assertThat(executor.numPending()).isEqualTo(0)
-        restarter.restartSystemUI()
+        restarter.restart()
 
         val captor = ArgumentCaptor.forClass(WakefulnessLifecycle.Observer::class.java)
         verify(wakefulnessLifecycle).addObserver(captor.capture())
@@ -131,7 +131,7 @@
         whenever(wakefulnessLifecycle.wakefulness).thenReturn(WAKEFULNESS_ASLEEP)
         whenever(batteryController.isPluggedIn).thenReturn(false)
         assertThat(executor.numPending()).isEqualTo(0)
-        restarter.restartSystemUI()
+        restarter.restart()
 
         val captor =
             ArgumentCaptor.forClass(BatteryController.BatteryStateChangeCallback::class.java)
diff --git a/packages/SystemUI/tests/src/com/android/systemui/keyguard/KeyguardQuickAffordanceProviderTest.kt b/packages/SystemUI/tests/src/com/android/systemui/keyguard/KeyguardQuickAffordanceProviderTest.kt
index 8395f02..5e27a50 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/keyguard/KeyguardQuickAffordanceProviderTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/keyguard/KeyguardQuickAffordanceProviderTest.kt
@@ -99,10 +99,12 @@
                     setOf(
                         FakeKeyguardQuickAffordanceConfig(
                             key = AFFORDANCE_1,
+                            pickerName = AFFORDANCE_1_NAME,
                             pickerIconResourceId = 1,
                         ),
                         FakeKeyguardQuickAffordanceConfig(
                             key = AFFORDANCE_2,
+                            pickerName = AFFORDANCE_2_NAME,
                             pickerIconResourceId = 2,
                         ),
                     ),
@@ -176,6 +178,7 @@
         runBlocking(IMMEDIATE) {
             val slotId = KeyguardQuickAffordanceSlots.SLOT_ID_BOTTOM_START
             val affordanceId = AFFORDANCE_2
+            val affordanceName = AFFORDANCE_2_NAME
 
             insertSelection(
                 slotId = slotId,
@@ -188,6 +191,7 @@
                         Selection(
                             slotId = slotId,
                             affordanceId = affordanceId,
+                            affordanceName = affordanceName,
                         )
                     )
                 )
@@ -219,12 +223,12 @@
                     listOf(
                         Affordance(
                             id = AFFORDANCE_1,
-                            name = AFFORDANCE_1,
+                            name = AFFORDANCE_1_NAME,
                             iconResourceId = 1,
                         ),
                         Affordance(
                             id = AFFORDANCE_2,
-                            name = AFFORDANCE_2,
+                            name = AFFORDANCE_2_NAME,
                             iconResourceId = 2,
                         ),
                     )
@@ -259,6 +263,7 @@
                         Selection(
                             slotId = KeyguardQuickAffordanceSlots.SLOT_ID_BOTTOM_START,
                             affordanceId = AFFORDANCE_1,
+                            affordanceName = AFFORDANCE_1_NAME,
                         )
                     )
                 )
@@ -290,6 +295,7 @@
                         Selection(
                             slotId = KeyguardQuickAffordanceSlots.SLOT_ID_BOTTOM_START,
                             affordanceId = AFFORDANCE_1,
+                            affordanceName = AFFORDANCE_1_NAME,
                         )
                     )
                 )
@@ -323,7 +329,13 @@
                         cursor.getColumnIndex(Contract.SelectionTable.Columns.SLOT_ID)
                     val affordanceIdColumnIndex =
                         cursor.getColumnIndex(Contract.SelectionTable.Columns.AFFORDANCE_ID)
-                    if (slotIdColumnIndex == -1 || affordanceIdColumnIndex == -1) {
+                    val affordanceNameColumnIndex =
+                        cursor.getColumnIndex(Contract.SelectionTable.Columns.AFFORDANCE_NAME)
+                    if (
+                        slotIdColumnIndex == -1 ||
+                            affordanceIdColumnIndex == -1 ||
+                            affordanceNameColumnIndex == -1
+                    ) {
                         return@buildList
                     }
 
@@ -332,6 +344,7 @@
                             Selection(
                                 slotId = cursor.getString(slotIdColumnIndex),
                                 affordanceId = cursor.getString(affordanceIdColumnIndex),
+                                affordanceName = cursor.getString(affordanceNameColumnIndex),
                             )
                         )
                     }
@@ -419,11 +432,14 @@
     data class Selection(
         val slotId: String,
         val affordanceId: String,
+        val affordanceName: String,
     )
 
     companion object {
         private val IMMEDIATE = Dispatchers.Main.immediate
         private const val AFFORDANCE_1 = "affordance_1"
         private const val AFFORDANCE_2 = "affordance_2"
+        private const val AFFORDANCE_1_NAME = "affordance_1_name"
+        private const val AFFORDANCE_2_NAME = "affordance_2_name"
     }
 }
diff --git a/packages/SystemUI/tests/src/com/android/systemui/keyguard/KeyguardViewMediatorTest.java b/packages/SystemUI/tests/src/com/android/systemui/keyguard/KeyguardViewMediatorTest.java
index 45aaaa2..d17e374 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/keyguard/KeyguardViewMediatorTest.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/keyguard/KeyguardViewMediatorTest.java
@@ -65,6 +65,7 @@
 import com.android.systemui.statusbar.phone.CentralSurfaces;
 import com.android.systemui.statusbar.phone.DozeParameters;
 import com.android.systemui.statusbar.phone.ScreenOffAnimationController;
+import com.android.systemui.statusbar.phone.ScrimController;
 import com.android.systemui.statusbar.phone.StatusBarKeyguardViewManager;
 import com.android.systemui.statusbar.policy.KeyguardStateController;
 import com.android.systemui.statusbar.policy.UserSwitcherController;
@@ -112,6 +113,7 @@
     private @Mock Lazy<NotificationShadeWindowController> mNotificationShadeWindowControllerLazy;
     private @Mock DreamOverlayStateController mDreamOverlayStateController;
     private @Mock ActivityLaunchAnimator mActivityLaunchAnimator;
+    private @Mock ScrimController mScrimController;
     private DeviceConfigProxy mDeviceConfig = new DeviceConfigProxyFake();
     private FakeExecutor mUiBgExecutor = new FakeExecutor(new FakeSystemClock());
 
@@ -314,7 +316,8 @@
                 mDreamOverlayStateController,
                 () -> mShadeController,
                 mNotificationShadeWindowControllerLazy,
-                () -> mActivityLaunchAnimator);
+                () -> mActivityLaunchAnimator,
+                () -> mScrimController);
         mViewMediator.start();
 
         mViewMediator.registerCentralSurfaces(mCentralSurfaces, null, null, null, null, null);
diff --git a/packages/SystemUI/tests/src/com/android/systemui/keyguard/data/repository/KeyguardRepositoryImplTest.kt b/packages/SystemUI/tests/src/com/android/systemui/keyguard/data/repository/KeyguardRepositoryImplTest.kt
index 6ba0634..13fc9fc 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/keyguard/data/repository/KeyguardRepositoryImplTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/keyguard/data/repository/KeyguardRepositoryImplTest.kt
@@ -22,18 +22,25 @@
 import com.android.systemui.SysuiTestCase
 import com.android.systemui.common.shared.model.Position
 import com.android.systemui.doze.DozeHost
+import com.android.systemui.doze.DozeMachine
+import com.android.systemui.doze.DozeTransitionCallback
+import com.android.systemui.doze.DozeTransitionListener
 import com.android.systemui.keyguard.WakefulnessLifecycle
 import com.android.systemui.keyguard.shared.model.BiometricUnlockModel
+import com.android.systemui.keyguard.shared.model.DozeStateModel
+import com.android.systemui.keyguard.shared.model.DozeTransitionModel
 import com.android.systemui.keyguard.shared.model.WakefulnessModel
 import com.android.systemui.plugins.statusbar.StatusBarStateController
 import com.android.systemui.statusbar.phone.BiometricUnlockController
 import com.android.systemui.statusbar.policy.KeyguardStateController
 import com.android.systemui.util.mockito.argumentCaptor
 import com.android.systemui.util.mockito.whenever
+import com.android.systemui.util.mockito.withArgCaptor
 import com.google.common.truth.Truth.assertThat
 import kotlinx.coroutines.flow.launchIn
 import kotlinx.coroutines.flow.onEach
-import kotlinx.coroutines.test.runBlockingTest
+import kotlinx.coroutines.test.UnconfinedTestDispatcher
+import kotlinx.coroutines.test.runTest
 import org.junit.Before
 import org.junit.Test
 import org.junit.runner.RunWith
@@ -52,6 +59,7 @@
     @Mock private lateinit var wakefulnessLifecycle: WakefulnessLifecycle
     @Mock private lateinit var keyguardUpdateMonitor: KeyguardUpdateMonitor
     @Mock private lateinit var biometricUnlockController: BiometricUnlockController
+    @Mock private lateinit var dozeTransitionListener: DozeTransitionListener
 
     private lateinit var underTest: KeyguardRepositoryImpl
 
@@ -67,272 +75,349 @@
                 biometricUnlockController,
                 keyguardStateController,
                 keyguardUpdateMonitor,
+                dozeTransitionListener,
             )
     }
 
     @Test
-    fun animateBottomAreaDozingTransitions() = runBlockingTest {
-        assertThat(underTest.animateBottomAreaDozingTransitions.value).isEqualTo(false)
+    fun animateBottomAreaDozingTransitions() =
+        runTest(UnconfinedTestDispatcher()) {
+            assertThat(underTest.animateBottomAreaDozingTransitions.value).isEqualTo(false)
 
-        underTest.setAnimateDozingTransitions(true)
-        assertThat(underTest.animateBottomAreaDozingTransitions.value).isTrue()
+            underTest.setAnimateDozingTransitions(true)
+            assertThat(underTest.animateBottomAreaDozingTransitions.value).isTrue()
 
-        underTest.setAnimateDozingTransitions(false)
-        assertThat(underTest.animateBottomAreaDozingTransitions.value).isFalse()
+            underTest.setAnimateDozingTransitions(false)
+            assertThat(underTest.animateBottomAreaDozingTransitions.value).isFalse()
 
-        underTest.setAnimateDozingTransitions(true)
-        assertThat(underTest.animateBottomAreaDozingTransitions.value).isTrue()
-    }
+            underTest.setAnimateDozingTransitions(true)
+            assertThat(underTest.animateBottomAreaDozingTransitions.value).isTrue()
+        }
 
     @Test
-    fun bottomAreaAlpha() = runBlockingTest {
-        assertThat(underTest.bottomAreaAlpha.value).isEqualTo(1f)
+    fun bottomAreaAlpha() =
+        runTest(UnconfinedTestDispatcher()) {
+            assertThat(underTest.bottomAreaAlpha.value).isEqualTo(1f)
 
-        underTest.setBottomAreaAlpha(0.1f)
-        assertThat(underTest.bottomAreaAlpha.value).isEqualTo(0.1f)
+            underTest.setBottomAreaAlpha(0.1f)
+            assertThat(underTest.bottomAreaAlpha.value).isEqualTo(0.1f)
 
-        underTest.setBottomAreaAlpha(0.2f)
-        assertThat(underTest.bottomAreaAlpha.value).isEqualTo(0.2f)
+            underTest.setBottomAreaAlpha(0.2f)
+            assertThat(underTest.bottomAreaAlpha.value).isEqualTo(0.2f)
 
-        underTest.setBottomAreaAlpha(0.3f)
-        assertThat(underTest.bottomAreaAlpha.value).isEqualTo(0.3f)
+            underTest.setBottomAreaAlpha(0.3f)
+            assertThat(underTest.bottomAreaAlpha.value).isEqualTo(0.3f)
 
-        underTest.setBottomAreaAlpha(0.5f)
-        assertThat(underTest.bottomAreaAlpha.value).isEqualTo(0.5f)
+            underTest.setBottomAreaAlpha(0.5f)
+            assertThat(underTest.bottomAreaAlpha.value).isEqualTo(0.5f)
 
-        underTest.setBottomAreaAlpha(1.0f)
-        assertThat(underTest.bottomAreaAlpha.value).isEqualTo(1f)
-    }
+            underTest.setBottomAreaAlpha(1.0f)
+            assertThat(underTest.bottomAreaAlpha.value).isEqualTo(1f)
+        }
 
     @Test
-    fun clockPosition() = runBlockingTest {
-        assertThat(underTest.clockPosition.value).isEqualTo(Position(0, 0))
+    fun clockPosition() =
+        runTest(UnconfinedTestDispatcher()) {
+            assertThat(underTest.clockPosition.value).isEqualTo(Position(0, 0))
 
-        underTest.setClockPosition(0, 1)
-        assertThat(underTest.clockPosition.value).isEqualTo(Position(0, 1))
+            underTest.setClockPosition(0, 1)
+            assertThat(underTest.clockPosition.value).isEqualTo(Position(0, 1))
 
-        underTest.setClockPosition(1, 9)
-        assertThat(underTest.clockPosition.value).isEqualTo(Position(1, 9))
+            underTest.setClockPosition(1, 9)
+            assertThat(underTest.clockPosition.value).isEqualTo(Position(1, 9))
 
-        underTest.setClockPosition(1, 0)
-        assertThat(underTest.clockPosition.value).isEqualTo(Position(1, 0))
+            underTest.setClockPosition(1, 0)
+            assertThat(underTest.clockPosition.value).isEqualTo(Position(1, 0))
 
-        underTest.setClockPosition(3, 1)
-        assertThat(underTest.clockPosition.value).isEqualTo(Position(3, 1))
-    }
+            underTest.setClockPosition(3, 1)
+            assertThat(underTest.clockPosition.value).isEqualTo(Position(3, 1))
+        }
 
     @Test
-    fun isKeyguardShowing() = runBlockingTest {
-        whenever(keyguardStateController.isShowing).thenReturn(false)
-        var latest: Boolean? = null
-        val job = underTest.isKeyguardShowing.onEach { latest = it }.launchIn(this)
+    fun isKeyguardShowing() =
+        runTest(UnconfinedTestDispatcher()) {
+            whenever(keyguardStateController.isShowing).thenReturn(false)
+            var latest: Boolean? = null
+            val job = underTest.isKeyguardShowing.onEach { latest = it }.launchIn(this)
 
-        assertThat(latest).isFalse()
-        assertThat(underTest.isKeyguardShowing()).isFalse()
+            assertThat(latest).isFalse()
+            assertThat(underTest.isKeyguardShowing()).isFalse()
 
-        val captor = argumentCaptor<KeyguardStateController.Callback>()
-        verify(keyguardStateController).addCallback(captor.capture())
+            val captor = argumentCaptor<KeyguardStateController.Callback>()
+            verify(keyguardStateController).addCallback(captor.capture())
 
-        whenever(keyguardStateController.isShowing).thenReturn(true)
-        captor.value.onKeyguardShowingChanged()
-        assertThat(latest).isTrue()
-        assertThat(underTest.isKeyguardShowing()).isTrue()
+            whenever(keyguardStateController.isShowing).thenReturn(true)
+            captor.value.onKeyguardShowingChanged()
+            assertThat(latest).isTrue()
+            assertThat(underTest.isKeyguardShowing()).isTrue()
 
-        whenever(keyguardStateController.isShowing).thenReturn(false)
-        captor.value.onKeyguardShowingChanged()
-        assertThat(latest).isFalse()
-        assertThat(underTest.isKeyguardShowing()).isFalse()
+            whenever(keyguardStateController.isShowing).thenReturn(false)
+            captor.value.onKeyguardShowingChanged()
+            assertThat(latest).isFalse()
+            assertThat(underTest.isKeyguardShowing()).isFalse()
 
-        job.cancel()
-    }
+            job.cancel()
+        }
 
     @Test
-    fun isDozing() = runBlockingTest {
-        var latest: Boolean? = null
-        val job = underTest.isDozing.onEach { latest = it }.launchIn(this)
+    fun isDozing() =
+        runTest(UnconfinedTestDispatcher()) {
+            var latest: Boolean? = null
+            val job = underTest.isDozing.onEach { latest = it }.launchIn(this)
 
-        val captor = argumentCaptor<DozeHost.Callback>()
-        verify(dozeHost).addCallback(captor.capture())
+            val captor = argumentCaptor<DozeHost.Callback>()
+            verify(dozeHost).addCallback(captor.capture())
 
-        captor.value.onDozingChanged(true)
-        assertThat(latest).isTrue()
+            captor.value.onDozingChanged(true)
+            assertThat(latest).isTrue()
 
-        captor.value.onDozingChanged(false)
-        assertThat(latest).isFalse()
+            captor.value.onDozingChanged(false)
+            assertThat(latest).isFalse()
 
-        job.cancel()
-        verify(dozeHost).removeCallback(captor.value)
-    }
+            job.cancel()
+            verify(dozeHost).removeCallback(captor.value)
+        }
 
     @Test
-    fun `isDozing - starts with correct initial value for isDozing`() = runBlockingTest {
-        var latest: Boolean? = null
+    fun `isDozing - starts with correct initial value for isDozing`() =
+        runTest(UnconfinedTestDispatcher()) {
+            var latest: Boolean? = null
 
-        whenever(statusBarStateController.isDozing).thenReturn(true)
-        var job = underTest.isDozing.onEach { latest = it }.launchIn(this)
-        assertThat(latest).isTrue()
-        job.cancel()
+            whenever(statusBarStateController.isDozing).thenReturn(true)
+            var job = underTest.isDozing.onEach { latest = it }.launchIn(this)
+            assertThat(latest).isTrue()
+            job.cancel()
 
-        whenever(statusBarStateController.isDozing).thenReturn(false)
-        job = underTest.isDozing.onEach { latest = it }.launchIn(this)
-        assertThat(latest).isFalse()
-        job.cancel()
-    }
+            whenever(statusBarStateController.isDozing).thenReturn(false)
+            job = underTest.isDozing.onEach { latest = it }.launchIn(this)
+            assertThat(latest).isFalse()
+            job.cancel()
+        }
 
     @Test
-    fun dozeAmount() = runBlockingTest {
-        val values = mutableListOf<Float>()
-        val job = underTest.dozeAmount.onEach(values::add).launchIn(this)
+    fun dozeAmount() =
+        runTest(UnconfinedTestDispatcher()) {
+            val values = mutableListOf<Float>()
+            val job = underTest.dozeAmount.onEach(values::add).launchIn(this)
 
-        val captor = argumentCaptor<StatusBarStateController.StateListener>()
-        verify(statusBarStateController).addCallback(captor.capture())
+            val captor = argumentCaptor<StatusBarStateController.StateListener>()
+            verify(statusBarStateController).addCallback(captor.capture())
 
-        captor.value.onDozeAmountChanged(0.433f, 0.4f)
-        captor.value.onDozeAmountChanged(0.498f, 0.5f)
-        captor.value.onDozeAmountChanged(0.661f, 0.65f)
+            captor.value.onDozeAmountChanged(0.433f, 0.4f)
+            captor.value.onDozeAmountChanged(0.498f, 0.5f)
+            captor.value.onDozeAmountChanged(0.661f, 0.65f)
 
-        assertThat(values).isEqualTo(listOf(0f, 0.4f, 0.5f, 0.65f))
+            assertThat(values).isEqualTo(listOf(0f, 0.4f, 0.5f, 0.65f))
 
-        job.cancel()
-        verify(statusBarStateController).removeCallback(captor.value)
-    }
+            job.cancel()
+            verify(statusBarStateController).removeCallback(captor.value)
+        }
 
     @Test
-    fun wakefulness() = runBlockingTest {
-        val values = mutableListOf<WakefulnessModel>()
-        val job = underTest.wakefulnessState.onEach(values::add).launchIn(this)
+    fun wakefulness() =
+        runTest(UnconfinedTestDispatcher()) {
+            val values = mutableListOf<WakefulnessModel>()
+            val job = underTest.wakefulnessState.onEach(values::add).launchIn(this)
 
-        val captor = argumentCaptor<WakefulnessLifecycle.Observer>()
-        verify(wakefulnessLifecycle).addObserver(captor.capture())
+            val captor = argumentCaptor<WakefulnessLifecycle.Observer>()
+            verify(wakefulnessLifecycle).addObserver(captor.capture())
 
-        captor.value.onStartedWakingUp()
-        captor.value.onFinishedWakingUp()
-        captor.value.onStartedGoingToSleep()
-        captor.value.onFinishedGoingToSleep()
+            captor.value.onStartedWakingUp()
+            captor.value.onFinishedWakingUp()
+            captor.value.onStartedGoingToSleep()
+            captor.value.onFinishedGoingToSleep()
 
-        assertThat(values)
-            .isEqualTo(
-                listOf(
-                    // Initial value will be ASLEEP
-                    WakefulnessModel.ASLEEP,
-                    WakefulnessModel.STARTING_TO_WAKE,
-                    WakefulnessModel.AWAKE,
-                    WakefulnessModel.STARTING_TO_SLEEP,
-                    WakefulnessModel.ASLEEP,
+            assertThat(values)
+                .isEqualTo(
+                    listOf(
+                        // Initial value will be ASLEEP
+                        WakefulnessModel.ASLEEP,
+                        WakefulnessModel.STARTING_TO_WAKE,
+                        WakefulnessModel.AWAKE,
+                        WakefulnessModel.STARTING_TO_SLEEP,
+                        WakefulnessModel.ASLEEP,
+                    )
                 )
-            )
 
-        job.cancel()
-        verify(wakefulnessLifecycle).removeObserver(captor.value)
-    }
+            job.cancel()
+            verify(wakefulnessLifecycle).removeObserver(captor.value)
+        }
 
     @Test
-    fun isUdfpsSupported() = runBlockingTest {
-        whenever(keyguardUpdateMonitor.isUdfpsSupported).thenReturn(true)
-        assertThat(underTest.isUdfpsSupported()).isTrue()
+    fun isUdfpsSupported() =
+        runTest(UnconfinedTestDispatcher()) {
+            whenever(keyguardUpdateMonitor.isUdfpsSupported).thenReturn(true)
+            assertThat(underTest.isUdfpsSupported()).isTrue()
 
-        whenever(keyguardUpdateMonitor.isUdfpsSupported).thenReturn(false)
-        assertThat(underTest.isUdfpsSupported()).isFalse()
-    }
+            whenever(keyguardUpdateMonitor.isUdfpsSupported).thenReturn(false)
+            assertThat(underTest.isUdfpsSupported()).isFalse()
+        }
 
     @Test
-    fun isBouncerShowing() = runBlockingTest {
-        whenever(keyguardStateController.isBouncerShowing).thenReturn(false)
-        var latest: Boolean? = null
-        val job = underTest.isBouncerShowing.onEach { latest = it }.launchIn(this)
+    fun isBouncerShowing() =
+        runTest(UnconfinedTestDispatcher()) {
+            whenever(keyguardStateController.isBouncerShowing).thenReturn(false)
+            var latest: Boolean? = null
+            val job = underTest.isBouncerShowing.onEach { latest = it }.launchIn(this)
 
-        assertThat(latest).isFalse()
+            assertThat(latest).isFalse()
 
-        val captor = argumentCaptor<KeyguardStateController.Callback>()
-        verify(keyguardStateController).addCallback(captor.capture())
+            val captor = argumentCaptor<KeyguardStateController.Callback>()
+            verify(keyguardStateController).addCallback(captor.capture())
 
-        whenever(keyguardStateController.isBouncerShowing).thenReturn(true)
-        captor.value.onBouncerShowingChanged()
-        assertThat(latest).isTrue()
+            whenever(keyguardStateController.isBouncerShowing).thenReturn(true)
+            captor.value.onBouncerShowingChanged()
+            assertThat(latest).isTrue()
 
-        whenever(keyguardStateController.isBouncerShowing).thenReturn(false)
-        captor.value.onBouncerShowingChanged()
-        assertThat(latest).isFalse()
+            whenever(keyguardStateController.isBouncerShowing).thenReturn(false)
+            captor.value.onBouncerShowingChanged()
+            assertThat(latest).isFalse()
 
-        job.cancel()
-    }
+            job.cancel()
+        }
 
     @Test
-    fun isKeyguardGoingAway() = runBlockingTest {
-        whenever(keyguardStateController.isKeyguardGoingAway).thenReturn(false)
-        var latest: Boolean? = null
-        val job = underTest.isKeyguardGoingAway.onEach { latest = it }.launchIn(this)
+    fun isKeyguardGoingAway() =
+        runTest(UnconfinedTestDispatcher()) {
+            whenever(keyguardStateController.isKeyguardGoingAway).thenReturn(false)
+            var latest: Boolean? = null
+            val job = underTest.isKeyguardGoingAway.onEach { latest = it }.launchIn(this)
 
-        assertThat(latest).isFalse()
+            assertThat(latest).isFalse()
 
-        val captor = argumentCaptor<KeyguardStateController.Callback>()
-        verify(keyguardStateController).addCallback(captor.capture())
+            val captor = argumentCaptor<KeyguardStateController.Callback>()
+            verify(keyguardStateController).addCallback(captor.capture())
 
-        whenever(keyguardStateController.isKeyguardGoingAway).thenReturn(true)
-        captor.value.onKeyguardGoingAwayChanged()
-        assertThat(latest).isTrue()
+            whenever(keyguardStateController.isKeyguardGoingAway).thenReturn(true)
+            captor.value.onKeyguardGoingAwayChanged()
+            assertThat(latest).isTrue()
 
-        whenever(keyguardStateController.isKeyguardGoingAway).thenReturn(false)
-        captor.value.onKeyguardGoingAwayChanged()
-        assertThat(latest).isFalse()
+            whenever(keyguardStateController.isKeyguardGoingAway).thenReturn(false)
+            captor.value.onKeyguardGoingAwayChanged()
+            assertThat(latest).isFalse()
 
-        job.cancel()
-    }
+            job.cancel()
+        }
 
     @Test
-    fun isDreaming() = runBlockingTest {
-        whenever(keyguardUpdateMonitor.isDreaming()).thenReturn(false)
-        var latest: Boolean? = null
-        val job = underTest.isDreaming.onEach { latest = it }.launchIn(this)
+    fun isDreaming() =
+        runTest(UnconfinedTestDispatcher()) {
+            whenever(keyguardUpdateMonitor.isDreaming()).thenReturn(false)
+            var latest: Boolean? = null
+            val job = underTest.isDreaming.onEach { latest = it }.launchIn(this)
 
-        assertThat(latest).isFalse()
+            assertThat(latest).isFalse()
 
-        val captor = argumentCaptor<KeyguardUpdateMonitorCallback>()
-        verify(keyguardUpdateMonitor).registerCallback(captor.capture())
+            val captor = argumentCaptor<KeyguardUpdateMonitorCallback>()
+            verify(keyguardUpdateMonitor).registerCallback(captor.capture())
 
-        captor.value.onDreamingStateChanged(true)
-        assertThat(latest).isTrue()
+            captor.value.onDreamingStateChanged(true)
+            assertThat(latest).isTrue()
 
-        captor.value.onDreamingStateChanged(false)
-        assertThat(latest).isFalse()
+            captor.value.onDreamingStateChanged(false)
+            assertThat(latest).isFalse()
 
-        job.cancel()
-    }
+            job.cancel()
+        }
 
     @Test
-    fun biometricUnlockState() = runBlockingTest {
-        val values = mutableListOf<BiometricUnlockModel>()
-        val job = underTest.biometricUnlockState.onEach(values::add).launchIn(this)
+    fun biometricUnlockState() =
+        runTest(UnconfinedTestDispatcher()) {
+            val values = mutableListOf<BiometricUnlockModel>()
+            val job = underTest.biometricUnlockState.onEach(values::add).launchIn(this)
 
-        val captor = argumentCaptor<BiometricUnlockController.BiometricModeListener>()
-        verify(biometricUnlockController).addBiometricModeListener(captor.capture())
+            val captor = argumentCaptor<BiometricUnlockController.BiometricModeListener>()
+            verify(biometricUnlockController).addBiometricModeListener(captor.capture())
 
-        captor.value.onModeChanged(BiometricUnlockController.MODE_NONE)
-        captor.value.onModeChanged(BiometricUnlockController.MODE_WAKE_AND_UNLOCK)
-        captor.value.onModeChanged(BiometricUnlockController.MODE_WAKE_AND_UNLOCK_PULSING)
-        captor.value.onModeChanged(BiometricUnlockController.MODE_SHOW_BOUNCER)
-        captor.value.onModeChanged(BiometricUnlockController.MODE_ONLY_WAKE)
-        captor.value.onModeChanged(BiometricUnlockController.MODE_UNLOCK_COLLAPSING)
-        captor.value.onModeChanged(BiometricUnlockController.MODE_DISMISS_BOUNCER)
-        captor.value.onModeChanged(BiometricUnlockController.MODE_WAKE_AND_UNLOCK_FROM_DREAM)
+            captor.value.onModeChanged(BiometricUnlockController.MODE_NONE)
+            captor.value.onModeChanged(BiometricUnlockController.MODE_WAKE_AND_UNLOCK)
+            captor.value.onModeChanged(BiometricUnlockController.MODE_WAKE_AND_UNLOCK_PULSING)
+            captor.value.onModeChanged(BiometricUnlockController.MODE_SHOW_BOUNCER)
+            captor.value.onModeChanged(BiometricUnlockController.MODE_ONLY_WAKE)
+            captor.value.onModeChanged(BiometricUnlockController.MODE_UNLOCK_COLLAPSING)
+            captor.value.onModeChanged(BiometricUnlockController.MODE_DISMISS_BOUNCER)
+            captor.value.onModeChanged(BiometricUnlockController.MODE_WAKE_AND_UNLOCK_FROM_DREAM)
 
-        assertThat(values)
-            .isEqualTo(
-                listOf(
-                    // Initial value will be NONE, followed by onModeChanged() call
-                    BiometricUnlockModel.NONE,
-                    BiometricUnlockModel.NONE,
-                    BiometricUnlockModel.WAKE_AND_UNLOCK,
-                    BiometricUnlockModel.WAKE_AND_UNLOCK_PULSING,
-                    BiometricUnlockModel.SHOW_BOUNCER,
-                    BiometricUnlockModel.ONLY_WAKE,
-                    BiometricUnlockModel.UNLOCK_COLLAPSING,
-                    BiometricUnlockModel.DISMISS_BOUNCER,
-                    BiometricUnlockModel.WAKE_AND_UNLOCK_FROM_DREAM,
+            assertThat(values)
+                .isEqualTo(
+                    listOf(
+                        // Initial value will be NONE, followed by onModeChanged() call
+                        BiometricUnlockModel.NONE,
+                        BiometricUnlockModel.NONE,
+                        BiometricUnlockModel.WAKE_AND_UNLOCK,
+                        BiometricUnlockModel.WAKE_AND_UNLOCK_PULSING,
+                        BiometricUnlockModel.SHOW_BOUNCER,
+                        BiometricUnlockModel.ONLY_WAKE,
+                        BiometricUnlockModel.UNLOCK_COLLAPSING,
+                        BiometricUnlockModel.DISMISS_BOUNCER,
+                        BiometricUnlockModel.WAKE_AND_UNLOCK_FROM_DREAM,
+                    )
                 )
+
+            job.cancel()
+            verify(biometricUnlockController).removeBiometricModeListener(captor.value)
+        }
+
+    @Test
+    fun dozeTransitionModel() =
+        runTest(UnconfinedTestDispatcher()) {
+            // For the initial state
+            whenever(dozeTransitionListener.oldState).thenReturn(DozeMachine.State.UNINITIALIZED)
+            whenever(dozeTransitionListener.newState).thenReturn(DozeMachine.State.UNINITIALIZED)
+
+            val values = mutableListOf<DozeTransitionModel>()
+            val job = underTest.dozeTransitionModel.onEach(values::add).launchIn(this)
+
+            val listener =
+                withArgCaptor<DozeTransitionCallback> {
+                    verify(dozeTransitionListener).addCallback(capture())
+                }
+
+            // These don't have to reflect real transitions from the DozeMachine. Only that the
+            // transitions are properly emitted
+            listener.onDozeTransition(DozeMachine.State.INITIALIZED, DozeMachine.State.DOZE)
+            listener.onDozeTransition(DozeMachine.State.DOZE, DozeMachine.State.DOZE_AOD)
+            listener.onDozeTransition(DozeMachine.State.DOZE_AOD_DOCKED, DozeMachine.State.FINISH)
+            listener.onDozeTransition(
+                DozeMachine.State.DOZE_REQUEST_PULSE,
+                DozeMachine.State.DOZE_PULSING
+            )
+            listener.onDozeTransition(
+                DozeMachine.State.DOZE_SUSPEND_TRIGGERS,
+                DozeMachine.State.DOZE_PULSE_DONE
+            )
+            listener.onDozeTransition(
+                DozeMachine.State.DOZE_AOD_PAUSING,
+                DozeMachine.State.DOZE_AOD_PAUSED
             )
 
-        job.cancel()
-        verify(biometricUnlockController).removeBiometricModeListener(captor.value)
-    }
+            assertThat(values)
+                .isEqualTo(
+                    listOf(
+                        // Initial value will be UNINITIALIZED
+                        DozeTransitionModel(
+                            DozeStateModel.UNINITIALIZED,
+                            DozeStateModel.UNINITIALIZED
+                        ),
+                        DozeTransitionModel(DozeStateModel.INITIALIZED, DozeStateModel.DOZE),
+                        DozeTransitionModel(DozeStateModel.DOZE, DozeStateModel.DOZE_AOD),
+                        DozeTransitionModel(DozeStateModel.DOZE_AOD_DOCKED, DozeStateModel.FINISH),
+                        DozeTransitionModel(
+                            DozeStateModel.DOZE_REQUEST_PULSE,
+                            DozeStateModel.DOZE_PULSING
+                        ),
+                        DozeTransitionModel(
+                            DozeStateModel.DOZE_SUSPEND_TRIGGERS,
+                            DozeStateModel.DOZE_PULSE_DONE
+                        ),
+                        DozeTransitionModel(
+                            DozeStateModel.DOZE_AOD_PAUSING,
+                            DozeStateModel.DOZE_AOD_PAUSED
+                        ),
+                    )
+                )
+
+            job.cancel()
+            verify(dozeTransitionListener).removeCallback(listener)
+        }
 }
diff --git a/packages/SystemUI/tests/src/com/android/systemui/keyguard/domain/interactor/KeyguardQuickAffordanceInteractorTest.kt b/packages/SystemUI/tests/src/com/android/systemui/keyguard/domain/interactor/KeyguardQuickAffordanceInteractorTest.kt
index c47e6f5..4850ea5 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/keyguard/domain/interactor/KeyguardQuickAffordanceInteractorTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/keyguard/domain/interactor/KeyguardQuickAffordanceInteractorTest.kt
@@ -33,6 +33,7 @@
 import com.android.systemui.keyguard.data.repository.KeyguardQuickAffordanceRepository
 import com.android.systemui.keyguard.domain.model.KeyguardQuickAffordanceModel
 import com.android.systemui.keyguard.domain.quickaffordance.FakeKeyguardQuickAffordanceRegistry
+import com.android.systemui.keyguard.shared.model.KeyguardQuickAffordancePickerRepresentation
 import com.android.systemui.keyguard.shared.quickaffordance.ActivationState
 import com.android.systemui.keyguard.shared.quickaffordance.KeyguardQuickAffordancePosition
 import com.android.systemui.plugins.ActivityStarter
@@ -314,7 +315,13 @@
                 .isEqualTo(
                     mapOf(
                         KeyguardQuickAffordanceSlots.SLOT_ID_BOTTOM_START to
-                            listOf(homeControls.key),
+                            listOf(
+                                KeyguardQuickAffordancePickerRepresentation(
+                                    id = homeControls.key,
+                                    name = homeControls.pickerName,
+                                    iconResourceId = homeControls.pickerIconResourceId,
+                                ),
+                            ),
                         KeyguardQuickAffordanceSlots.SLOT_ID_BOTTOM_END to emptyList(),
                     )
                 )
@@ -343,7 +350,13 @@
                 .isEqualTo(
                     mapOf(
                         KeyguardQuickAffordanceSlots.SLOT_ID_BOTTOM_START to
-                            listOf(quickAccessWallet.key),
+                            listOf(
+                                KeyguardQuickAffordancePickerRepresentation(
+                                    id = quickAccessWallet.key,
+                                    name = quickAccessWallet.pickerName,
+                                    iconResourceId = quickAccessWallet.pickerIconResourceId,
+                                ),
+                            ),
                         KeyguardQuickAffordanceSlots.SLOT_ID_BOTTOM_END to emptyList(),
                     )
                 )
@@ -375,9 +388,21 @@
                 .isEqualTo(
                     mapOf(
                         KeyguardQuickAffordanceSlots.SLOT_ID_BOTTOM_START to
-                            listOf(quickAccessWallet.key),
+                            listOf(
+                                KeyguardQuickAffordancePickerRepresentation(
+                                    id = quickAccessWallet.key,
+                                    name = quickAccessWallet.pickerName,
+                                    iconResourceId = quickAccessWallet.pickerIconResourceId,
+                                ),
+                            ),
                         KeyguardQuickAffordanceSlots.SLOT_ID_BOTTOM_END to
-                            listOf(qrCodeScanner.key),
+                            listOf(
+                                KeyguardQuickAffordancePickerRepresentation(
+                                    id = qrCodeScanner.key,
+                                    name = qrCodeScanner.pickerName,
+                                    iconResourceId = qrCodeScanner.pickerIconResourceId,
+                                ),
+                            ),
                     )
                 )
 
@@ -441,7 +466,13 @@
                     mapOf(
                         KeyguardQuickAffordanceSlots.SLOT_ID_BOTTOM_START to emptyList(),
                         KeyguardQuickAffordanceSlots.SLOT_ID_BOTTOM_END to
-                            listOf(quickAccessWallet.key),
+                            listOf(
+                                KeyguardQuickAffordancePickerRepresentation(
+                                    id = quickAccessWallet.key,
+                                    name = quickAccessWallet.pickerName,
+                                    iconResourceId = quickAccessWallet.pickerIconResourceId,
+                                ),
+                            ),
                     )
                 )
 
@@ -502,7 +533,13 @@
                     mapOf(
                         KeyguardQuickAffordanceSlots.SLOT_ID_BOTTOM_START to emptyList(),
                         KeyguardQuickAffordanceSlots.SLOT_ID_BOTTOM_END to
-                            listOf(quickAccessWallet.key),
+                            listOf(
+                                KeyguardQuickAffordancePickerRepresentation(
+                                    id = quickAccessWallet.key,
+                                    name = quickAccessWallet.pickerName,
+                                    iconResourceId = quickAccessWallet.pickerIconResourceId,
+                                ),
+                            ),
                     )
                 )
 
diff --git a/packages/SystemUI/tests/src/com/android/systemui/keyguard/domain/interactor/PrimaryBouncerInteractorTest.kt b/packages/SystemUI/tests/src/com/android/systemui/keyguard/domain/interactor/PrimaryBouncerInteractorTest.kt
index 3269f5a..559f183 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/keyguard/domain/interactor/PrimaryBouncerInteractorTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/keyguard/domain/interactor/PrimaryBouncerInteractorTest.kt
@@ -43,6 +43,7 @@
 import org.junit.Test
 import org.junit.runner.RunWith
 import org.mockito.Answers
+import org.mockito.ArgumentCaptor
 import org.mockito.Mock
 import org.mockito.Mockito.mock
 import org.mockito.Mockito.never
@@ -170,8 +171,10 @@
 
     @Test
     fun testShowMessage() {
+        val argCaptor = ArgumentCaptor.forClass(BouncerShowMessageModel::class.java)
         mPrimaryBouncerInteractor.showMessage("abc", null)
-        verify(repository).setShowMessage(BouncerShowMessageModel("abc", null))
+        verify(repository).setShowMessage(argCaptor.capture())
+        assertThat(argCaptor.value.message).isEqualTo("abc")
     }
 
     @Test
@@ -195,6 +198,12 @@
     }
 
     @Test
+    fun testNotifyShowedMessage() {
+        mPrimaryBouncerInteractor.onMessageShown()
+        verify(repository).setShowMessage(null)
+    }
+
+    @Test
     fun testOnScreenTurnedOff() {
         mPrimaryBouncerInteractor.onScreenTurnedOff()
         verify(repository).setOnScreenTurnedOff(true)
diff --git a/packages/SystemUI/tests/src/com/android/systemui/keyguard/ui/viewmodel/KeyguardBouncerViewModelTest.kt b/packages/SystemUI/tests/src/com/android/systemui/keyguard/ui/viewmodel/KeyguardBouncerViewModelTest.kt
new file mode 100644
index 0000000..3727134
--- /dev/null
+++ b/packages/SystemUI/tests/src/com/android/systemui/keyguard/ui/viewmodel/KeyguardBouncerViewModelTest.kt
@@ -0,0 +1,68 @@
+/*
+ * Copyright (C) 2022 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.keyguard.ui.viewmodel
+
+import androidx.test.filters.SmallTest
+import com.android.systemui.SysuiTestCase
+import com.android.systemui.keyguard.data.BouncerView
+import com.android.systemui.keyguard.domain.interactor.PrimaryBouncerInteractor
+import com.android.systemui.keyguard.shared.model.BouncerShowMessageModel
+import com.google.common.truth.Truth.assertThat
+import kotlinx.coroutines.Dispatchers
+import kotlinx.coroutines.flow.Flow
+import kotlinx.coroutines.flow.MutableStateFlow
+import kotlinx.coroutines.flow.launchIn
+import kotlinx.coroutines.flow.onEach
+import kotlinx.coroutines.runBlocking
+import org.junit.Before
+import org.junit.Test
+import org.junit.runner.RunWith
+import org.junit.runners.JUnit4
+import org.mockito.Mock
+import org.mockito.Mockito
+import org.mockito.MockitoAnnotations
+
+@SmallTest
+@RunWith(JUnit4::class)
+class KeyguardBouncerViewModelTest : SysuiTestCase() {
+    lateinit var underTest: KeyguardBouncerViewModel
+    @Mock lateinit var bouncerView: BouncerView
+    @Mock lateinit var bouncerInteractor: PrimaryBouncerInteractor
+
+    @Before
+    fun setup() {
+        MockitoAnnotations.initMocks(this)
+        underTest = KeyguardBouncerViewModel(bouncerView, bouncerInteractor)
+    }
+
+    @Test
+    fun setMessage() =
+        runBlocking(Dispatchers.Main.immediate) {
+            val flow = MutableStateFlow<BouncerShowMessageModel?>(null)
+            var message: BouncerShowMessageModel? = null
+            Mockito.`when`(bouncerInteractor.showMessage)
+                .thenReturn(flow as Flow<BouncerShowMessageModel>)
+            // Reinitialize the view model.
+            underTest = KeyguardBouncerViewModel(bouncerView, bouncerInteractor)
+
+            flow.value = BouncerShowMessageModel(message = "abc", colorStateList = null)
+
+            val job = underTest.bouncerShowMessage.onEach { message = it }.launchIn(this)
+            assertThat(message?.message).isEqualTo("abc")
+            job.cancel()
+        }
+}
diff --git a/packages/SystemUI/tests/src/com/android/systemui/qs/QSTileHostTest.java b/packages/SystemUI/tests/src/com/android/systemui/qs/QSTileHostTest.java
index c452872..fb1a720 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/qs/QSTileHostTest.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/qs/QSTileHostTest.java
@@ -54,6 +54,7 @@
 import com.android.systemui.dump.DumpManager;
 import com.android.systemui.dump.nano.SystemUIProtoDump;
 import com.android.systemui.plugins.ActivityStarter;
+import com.android.systemui.plugins.PluginManager;
 import com.android.systemui.plugins.qs.QSFactory;
 import com.android.systemui.plugins.qs.QSTile;
 import com.android.systemui.plugins.statusbar.StatusBarStateController;
@@ -66,7 +67,6 @@
 import com.android.systemui.qs.tileimpl.QSTileImpl;
 import com.android.systemui.settings.UserFileManager;
 import com.android.systemui.settings.UserTracker;
-import com.android.systemui.shared.plugins.PluginManager;
 import com.android.systemui.statusbar.phone.AutoTileManager;
 import com.android.systemui.statusbar.phone.CentralSurfaces;
 import com.android.systemui.statusbar.phone.StatusBarIconController;
diff --git a/packages/SystemUI/tests/src/com/android/systemui/qs/external/TileServicesTest.java b/packages/SystemUI/tests/src/com/android/systemui/qs/external/TileServicesTest.java
index 213eca8..25c95ef 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/qs/external/TileServicesTest.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/qs/external/TileServicesTest.java
@@ -42,12 +42,12 @@
 import com.android.systemui.SysuiTestCase;
 import com.android.systemui.broadcast.BroadcastDispatcher;
 import com.android.systemui.dump.DumpManager;
+import com.android.systemui.plugins.PluginManager;
 import com.android.systemui.qs.QSTileHost;
 import com.android.systemui.qs.logging.QSLogger;
 import com.android.systemui.qs.tileimpl.QSFactoryImpl;
 import com.android.systemui.settings.UserFileManager;
 import com.android.systemui.settings.UserTracker;
-import com.android.systemui.shared.plugins.PluginManager;
 import com.android.systemui.statusbar.CommandQueue;
 import com.android.systemui.statusbar.phone.AutoTileManager;
 import com.android.systemui.statusbar.phone.CentralSurfaces;
diff --git a/packages/SystemUI/tests/src/com/android/systemui/sensorprivacy/SensorUseStartedActivityTest.kt b/packages/SystemUI/tests/src/com/android/systemui/sensorprivacy/SensorUseStartedActivityTest.kt
new file mode 100644
index 0000000..333e634
--- /dev/null
+++ b/packages/SystemUI/tests/src/com/android/systemui/sensorprivacy/SensorUseStartedActivityTest.kt
@@ -0,0 +1,38 @@
+package com.android.systemui.sensorprivacy
+
+import android.testing.AndroidTestingRunner
+import android.testing.TestableLooper
+import androidx.test.ext.junit.rules.ActivityScenarioRule
+import androidx.test.filters.SmallTest
+import com.android.systemui.SysuiTestCase
+import com.android.systemui.util.mockito.mock
+import com.google.common.truth.Truth.assertThat
+import org.junit.Rule
+import org.junit.Test
+import org.junit.runner.RunWith
+
+@RunWith(AndroidTestingRunner::class)
+@SmallTest
+@TestableLooper.RunWithLooper
+class SensorUseStartedActivityTest : SysuiTestCase() {
+    open class SensorUseStartedActivityTestable :
+        SensorUseStartedActivity(
+            sensorPrivacyController = mock(),
+            keyguardStateController = mock(),
+            keyguardDismissUtil = mock(),
+            bgHandler = mock(),
+        )
+
+    @get:Rule val activityRule = ActivityScenarioRule(SensorUseStartedActivityTestable::class.java)
+
+    @Test
+    fun onBackPressed_doNothing() {
+        activityRule.scenario.onActivity { activity ->
+            assertThat(activity.isFinishing).isFalse()
+
+            activity.onBackPressed()
+
+            assertThat(activity.isFinishing).isFalse()
+        }
+    }
+}
diff --git a/packages/SystemUI/tests/src/com/android/systemui/shade/CombinedShadeHeaderConstraintsTest.kt b/packages/SystemUI/tests/src/com/android/systemui/shade/CombinedShadeHeaderConstraintsTest.kt
index bc17c19..9c36be6 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/shade/CombinedShadeHeaderConstraintsTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/shade/CombinedShadeHeaderConstraintsTest.kt
@@ -43,7 +43,7 @@
             load(context, context.resources.getXml(R.xml.qqs_header))
         }
         qsConstraint = ConstraintSet().apply {
-            load(context, context.resources.getXml(R.xml.qs_header_new))
+            load(context, context.resources.getXml(R.xml.qs_header))
         }
         largeScreenConstraint = ConstraintSet().apply {
             load(context, context.resources.getXml(R.xml.large_screen_shade_header))
@@ -344,26 +344,6 @@
     }
 
     @Test
-    fun testCheckViewsDontChangeSizeBetweenAnimationConstraints() {
-        val views = mapOf(
-                R.id.clock to "clock",
-                R.id.date to "date",
-                R.id.statusIcons to "icons",
-                R.id.privacy_container to "privacy",
-                R.id.carrier_group to "carriers",
-                R.id.batteryRemainingIcon to "battery",
-        )
-        views.forEach { (id, name) ->
-            assertWithMessage("$name changes height")
-                    .that(qqsConstraint.getConstraint(id).layout.mHeight)
-                    .isEqualTo(qsConstraint.getConstraint(id).layout.mHeight)
-            assertWithMessage("$name changes width")
-                    .that(qqsConstraint.getConstraint(id).layout.mWidth)
-                    .isEqualTo(qsConstraint.getConstraint(id).layout.mWidth)
-        }
-    }
-
-    @Test
     fun testEmptyCutoutDateIconsAreConstrainedWidth() {
         CombinedShadeHeadersConstraintManagerImpl.emptyCutoutConstraints()()
 
diff --git a/packages/SystemUI/tests/src/com/android/systemui/shade/LargeScreenShadeHeaderControllerCombinedTest.kt b/packages/SystemUI/tests/src/com/android/systemui/shade/LargeScreenShadeHeaderControllerCombinedTest.kt
index 14a3bc1..e1007fa 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/shade/LargeScreenShadeHeaderControllerCombinedTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/shade/LargeScreenShadeHeaderControllerCombinedTest.kt
@@ -179,7 +179,6 @@
         whenever(iconManagerFactory.create(any(), any())).thenReturn(iconManager)
 
         whenever(featureFlags.isEnabled(Flags.COMBINED_QS_HEADERS)).thenReturn(true)
-        whenever(featureFlags.isEnabled(Flags.NEW_HEADER)).thenReturn(true)
 
         setUpDefaultInsets()
         setUpMotionLayout(view)
@@ -212,7 +211,7 @@
         assertThat(captor.value.getResId()).isEqualTo(R.xml.qqs_header)
 
         verify(qsConstraints).load(eq(context), capture(captor))
-        assertThat(captor.value.getResId()).isEqualTo(R.xml.qs_header_new)
+        assertThat(captor.value.getResId()).isEqualTo(R.xml.qs_header)
 
         verify(largeScreenConstraints).load(eq(context), capture(captor))
         assertThat(captor.value.getResId()).isEqualTo(R.xml.large_screen_shade_header)
diff --git a/packages/SystemUI/tests/src/com/android/systemui/shared/clocks/ClockRegistryTest.kt b/packages/SystemUI/tests/src/com/android/systemui/shared/clocks/ClockRegistryTest.kt
index 28bd26a..4d7741ad 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/shared/clocks/ClockRegistryTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/shared/clocks/ClockRegistryTest.kt
@@ -28,7 +28,7 @@
 import com.android.systemui.plugins.ClockMetadata
 import com.android.systemui.plugins.ClockProviderPlugin
 import com.android.systemui.plugins.PluginListener
-import com.android.systemui.shared.plugins.PluginManager
+import com.android.systemui.plugins.PluginManager
 import com.android.systemui.util.mockito.argumentCaptor
 import com.android.systemui.util.mockito.eq
 import junit.framework.Assert.assertEquals
diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/NotificationListenerTest.java b/packages/SystemUI/tests/src/com/android/systemui/statusbar/NotificationListenerTest.java
index 5e11858..3412679 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/NotificationListenerTest.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/NotificationListenerTest.java
@@ -37,7 +37,7 @@
 import androidx.test.filters.SmallTest;
 
 import com.android.systemui.SysuiTestCase;
-import com.android.systemui.shared.plugins.PluginManager;
+import com.android.systemui.plugins.PluginManager;
 import com.android.systemui.statusbar.NotificationListener.NotificationHandler;
 import com.android.systemui.util.concurrency.FakeExecutor;
 import com.android.systemui.util.time.FakeSystemClock;
diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/collection/NotifCollectionTest.java b/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/collection/NotifCollectionTest.java
index 3b05321..94e3e6c 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/collection/NotifCollectionTest.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/collection/NotifCollectionTest.java
@@ -92,6 +92,7 @@
 import com.android.systemui.statusbar.notification.collection.notifcollection.NotifCollectionLogger;
 import com.android.systemui.statusbar.notification.collection.notifcollection.NotifDismissInterceptor;
 import com.android.systemui.statusbar.notification.collection.notifcollection.NotifLifetimeExtender;
+import com.android.systemui.statusbar.notification.row.ExpandableNotificationRow;
 import com.android.systemui.util.concurrency.FakeExecutor;
 import com.android.systemui.util.time.FakeSystemClock;
 
@@ -741,22 +742,24 @@
     @Test
     public void testGroupChildrenAreDismissedLocallyWhenSummaryIsDismissed() {
         // GIVEN a collection with two grouped notifs in it
-        CollectionEvent notif0 = postNotif(
+        CollectionEvent groupNotif = postNotif(
                 buildNotif(TEST_PACKAGE, 0)
                         .setGroup(mContext, GROUP_1)
                         .setGroupSummary(mContext, true));
-        CollectionEvent notif1 = postNotif(
+        CollectionEvent childNotif = postNotif(
                 buildNotif(TEST_PACKAGE, 1)
                         .setGroup(mContext, GROUP_1));
-        NotificationEntry entry0 = mCollectionListener.getEntry(notif0.key);
-        NotificationEntry entry1 = mCollectionListener.getEntry(notif1.key);
+        NotificationEntry groupEntry = mCollectionListener.getEntry(groupNotif.key);
+        NotificationEntry childEntry = mCollectionListener.getEntry(childNotif.key);
+        ExpandableNotificationRow childRow = mock(ExpandableNotificationRow.class);
+        childEntry.setRow(childRow);
 
         // WHEN the summary is dismissed
-        mCollection.dismissNotification(entry0, defaultStats(entry0));
+        mCollection.dismissNotification(groupEntry, defaultStats(groupEntry));
 
         // THEN all members of the group are marked as dismissed locally
-        assertEquals(DISMISSED, entry0.getDismissState());
-        assertEquals(PARENT_DISMISSED, entry1.getDismissState());
+        assertEquals(DISMISSED, groupEntry.getDismissState());
+        assertEquals(PARENT_DISMISSED, childEntry.getDismissState());
     }
 
     @Test
diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/collection/render/ShadeViewDifferTest.kt b/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/collection/render/ShadeViewDifferTest.kt
index 15cf17d..6167b46 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/collection/render/ShadeViewDifferTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/collection/render/ShadeViewDifferTest.kt
@@ -18,6 +18,7 @@
 import android.content.Context
 import android.testing.AndroidTestingRunner
 import android.view.View
+import android.view.ViewGroup
 import android.widget.FrameLayout
 import androidx.test.filters.SmallTest
 import com.android.systemui.SysuiTestCase
@@ -26,6 +27,10 @@
 import org.junit.Before
 import org.junit.Test
 import org.junit.runner.RunWith
+import org.mockito.ArgumentMatchers.isNull
+import org.mockito.Mockito.anyBoolean
+import org.mockito.Mockito.matches
+import org.mockito.Mockito.verify
 
 @SmallTest
 @RunWith(AndroidTestingRunner::class)
@@ -124,6 +129,64 @@
         Assert.assertNull(controller3.view.parent)
         Assert.assertNull(controller4.view.parent)
         Assert.assertNull(controller5.view.parent)
+        verifyDetachingChildLogged(controller3, oldParent = controller2)
+        verifyDetachingChildLogged(controller4, oldParent = controller2)
+        verifyDetachingChildLogged(controller5, oldParent = controller2)
+    }
+
+    @Test
+    fun testRemovedGroupsWithKeepInParentAreKeptTogether() {
+        // GIVEN a preexisting tree with a group
+        // AND the group children supports keepInParent
+        applySpecAndCheck(
+            node(controller1),
+            node(controller2, node(controller3), node(controller4), node(controller5))
+        )
+        controller3.supportsKeepInParent = true
+        controller4.supportsKeepInParent = true
+        controller5.supportsKeepInParent = true
+
+        // WHEN the new spec removes the entire group
+        applySpecAndCheck(node(controller1))
+
+        // THEN the group children are still attached to their parent
+        Assert.assertEquals(controller2.view, controller3.view.parent)
+        Assert.assertEquals(controller2.view, controller4.view.parent)
+        Assert.assertEquals(controller2.view, controller5.view.parent)
+        verifySkipDetachingChildLogged(controller3, parent = controller2)
+        verifySkipDetachingChildLogged(controller4, parent = controller2)
+        verifySkipDetachingChildLogged(controller5, parent = controller2)
+    }
+
+    @Test
+    fun testReuseRemovedGroupsWithKeepInParent() {
+        // GIVEN a preexisting tree with a dismissed group
+        // AND the group children supports keepInParent
+        controller3.supportsKeepInParent = true
+        controller4.supportsKeepInParent = true
+        controller5.supportsKeepInParent = true
+        applySpecAndCheck(
+            node(controller1),
+            node(controller2, node(controller3), node(controller4), node(controller5))
+        )
+        applySpecAndCheck(node(controller1))
+
+        // WHEN a new spec is applied which reuses the dismissed views
+        applySpecAndCheck(
+            node(controller1),
+            node(controller2),
+            node(controller3),
+            node(controller4),
+            node(controller5)
+        )
+
+        // THEN the dismissed views can be reused
+        Assert.assertEquals(rootController.view, controller3.view.parent)
+        Assert.assertEquals(rootController.view, controller4.view.parent)
+        Assert.assertEquals(rootController.view, controller5.view.parent)
+        verifyDetachingChildLogged(controller3, oldParent = null)
+        verifyDetachingChildLogged(controller4, oldParent = null)
+        verifyDetachingChildLogged(controller5, oldParent = null)
     }
 
     @Test
@@ -184,7 +247,30 @@
         }
     }
 
+    private fun verifySkipDetachingChildLogged(child: NodeController, parent: NodeController) {
+        verify(logger)
+            .logSkipDetachingChild(
+                key = matches(child.nodeLabel),
+                parentKey = matches(parent.nodeLabel),
+                anyBoolean(),
+                anyBoolean()
+            )
+    }
+
+    private fun verifyDetachingChildLogged(child: NodeController, oldParent: NodeController?) {
+        verify(logger)
+            .logDetachingChild(
+                key = matches(child.nodeLabel),
+                isTransfer = anyBoolean(),
+                isParentRemoved = anyBoolean(),
+                oldParent = oldParent?.let { matches(it.nodeLabel) } ?: isNull(),
+                newParent = isNull()
+            )
+    }
+
     private class FakeController(context: Context, label: String) : NodeController {
+        var supportsKeepInParent: Boolean = false
+
         override val view: FrameLayout = FrameLayout(context)
         override val nodeLabel: String = label
         override fun getChildCount(): Int = view.childCount
@@ -209,6 +295,22 @@
         override fun onViewAdded() {}
         override fun onViewMoved() {}
         override fun onViewRemoved() {}
+        override fun offerToKeepInParentForAnimation(): Boolean {
+            return supportsKeepInParent
+        }
+
+        override fun removeFromParentIfKeptForAnimation(): Boolean {
+            if (supportsKeepInParent) {
+                (view.parent as? ViewGroup)?.removeView(view)
+                return true
+            }
+
+            return false
+        }
+
+        override fun resetKeepInParentForAnimation() {
+            supportsKeepInParent = false
+        }
     }
 
     private class SpecBuilder(
diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/row/ExpandableNotificationRowTest.java b/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/row/ExpandableNotificationRowTest.java
index 12cc114..ee8db18 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/row/ExpandableNotificationRowTest.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/row/ExpandableNotificationRowTest.java
@@ -38,6 +38,7 @@
 import static org.mockito.Mockito.spy;
 import static org.mockito.Mockito.times;
 import static org.mockito.Mockito.verify;
+import static org.mockito.Mockito.verifyNoMoreInteractions;
 import static org.mockito.Mockito.when;
 
 import android.app.Notification;
@@ -458,4 +459,79 @@
         verify(mNotificationTestHelper.mOnUserInteractionCallback, never())
                 .registerFutureDismissal(any(), anyInt());
     }
+
+    @Test
+    public void testAddChildNotification() throws Exception {
+        ExpandableNotificationRow group = mNotificationTestHelper.createGroup(0);
+        ExpandableNotificationRow child = mNotificationTestHelper.createRow();
+
+        group.addChildNotification(child);
+
+        Assert.assertEquals(child, group.getChildNotificationAt(0));
+        Assert.assertEquals(group, child.getNotificationParent());
+        Assert.assertTrue(child.isChildInGroup());
+    }
+
+    @Test
+    public void testAddChildNotification_childSkipped() throws Exception {
+        ExpandableNotificationRow group = mNotificationTestHelper.createGroup(0);
+        ExpandableNotificationRow child = mNotificationTestHelper.createRow();
+        child.setKeepInParentForDismissAnimation(true);
+
+        group.addChildNotification(child);
+
+        Assert.assertTrue(group.getAttachedChildren().isEmpty());
+        Assert.assertNotEquals(group, child.getNotificationParent());
+        verify(mNotificationTestHelper.getMockLogger()).logSkipAttachingKeepInParentChild(
+                /*child=*/ child.getEntry(),
+                /*newParent=*/ group.getEntry()
+        );
+    }
+
+    @Test
+    public void testRemoveChildNotification() throws Exception {
+        ExpandableNotificationRow group = mNotificationTestHelper.createGroup(1);
+        ExpandableNotificationRow child = group.getAttachedChildren().get(0);
+        child.setKeepInParentForDismissAnimation(true);
+
+        group.removeChildNotification(child);
+
+        Assert.assertNull(child.getParent());
+        Assert.assertNull(child.getNotificationParent());
+        Assert.assertFalse(child.keepInParentForDismissAnimation());
+        verifyNoMoreInteractions(mNotificationTestHelper.getMockLogger());
+    }
+
+    @Test
+    public void testRemoveChildrenWithKeepInParent_removesChildWithKeepInParent() throws Exception {
+        ExpandableNotificationRow group = mNotificationTestHelper.createGroup(1);
+        ExpandableNotificationRow child = group.getAttachedChildren().get(0);
+        child.setKeepInParentForDismissAnimation(true);
+
+        group.removeChildrenWithKeepInParent();
+
+        Assert.assertNull(child.getParent());
+        Assert.assertNull(child.getNotificationParent());
+        Assert.assertFalse(child.keepInParentForDismissAnimation());
+        verify(mNotificationTestHelper.getMockLogger()).logKeepInParentChildDetached(
+                /*child=*/ child.getEntry(),
+                /*oldParent=*/ group.getEntry()
+        );
+    }
+
+    @Test
+    public void testRemoveChildrenWithKeepInParent_skipsChildrenWithoutKeepInParent()
+            throws Exception {
+        ExpandableNotificationRow group = mNotificationTestHelper.createGroup(1);
+        ExpandableNotificationRow child = group.getAttachedChildren().get(0);
+
+        group.removeChildrenWithKeepInParent();
+
+        Assert.assertEquals(group, child.getNotificationParent());
+        Assert.assertFalse(child.keepInParentForDismissAnimation());
+        verify(mNotificationTestHelper.getMockLogger(), never()).logKeepInParentChildDetached(
+                /*child=*/ any(),
+                /*oldParent=*/ any()
+        );
+    }
 }
diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/row/NotificationTestHelper.java b/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/row/NotificationTestHelper.java
index ab4ae6a..5fd9448 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/row/NotificationTestHelper.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/row/NotificationTestHelper.java
@@ -73,7 +73,7 @@
 import com.android.systemui.statusbar.notification.icon.IconBuilder;
 import com.android.systemui.statusbar.notification.icon.IconManager;
 import com.android.systemui.statusbar.notification.people.PeopleNotificationIdentifier;
-import com.android.systemui.statusbar.notification.row.ExpandableNotificationRow.ExpansionLogger;
+import com.android.systemui.statusbar.notification.row.ExpandableNotificationRow.ExpandableNotificationRowLogger;
 import com.android.systemui.statusbar.notification.row.ExpandableNotificationRow.OnExpandClickListener;
 import com.android.systemui.statusbar.notification.row.NotificationRowContentBinder.InflationFlag;
 import com.android.systemui.statusbar.phone.ConfigurationControllerImpl;
@@ -116,6 +116,7 @@
     private final Context mContext;
     private final TestableLooper mTestLooper;
     private int mId;
+    private final ExpandableNotificationRowLogger mMockLogger;
     private final GroupMembershipManager mGroupMembershipManager;
     private final GroupExpansionManager mGroupExpansionManager;
     private ExpandableNotificationRow mRow;
@@ -139,6 +140,7 @@
         dependency.injectMockDependency(NotificationMediaManager.class);
         dependency.injectMockDependency(NotificationShadeWindowController.class);
         dependency.injectMockDependency(MediaOutputDialogFactory.class);
+        mMockLogger = mock(ExpandableNotificationRowLogger.class);
         mStatusBarStateController = mock(StatusBarStateController.class);
         mGroupMembershipManager = mock(GroupMembershipManager.class);
         mGroupExpansionManager = mock(GroupExpansionManager.class);
@@ -197,6 +199,10 @@
         mDefaultInflationFlags = defaultInflationFlags;
     }
 
+    public ExpandableNotificationRowLogger getMockLogger() {
+        return mMockLogger;
+    }
+
     /**
      * Creates a generic row with rounded border.
      *
@@ -527,7 +533,7 @@
                 mock(RemoteInputViewSubcomponent.Factory.class),
                 APP_NAME,
                 entry.getKey(),
-                mock(ExpansionLogger.class),
+                mMockLogger,
                 mock(KeyguardBypassController.class),
                 mGroupMembershipManager,
                 mGroupExpansionManager,
diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/CentralSurfacesImplTest.java b/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/CentralSurfacesImplTest.java
index 41912f5..013e727 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/CentralSurfacesImplTest.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/CentralSurfacesImplTest.java
@@ -109,6 +109,7 @@
 import com.android.systemui.navigationbar.NavigationBarController;
 import com.android.systemui.plugins.ActivityStarter.OnDismissAction;
 import com.android.systemui.plugins.PluginDependencyProvider;
+import com.android.systemui.plugins.PluginManager;
 import com.android.systemui.plugins.statusbar.StatusBarStateController;
 import com.android.systemui.recents.ScreenPinningRequest;
 import com.android.systemui.settings.brightness.BrightnessSliderController;
@@ -120,7 +121,6 @@
 import com.android.systemui.shade.ShadeController;
 import com.android.systemui.shade.ShadeControllerImpl;
 import com.android.systemui.shade.ShadeExpansionStateManager;
-import com.android.systemui.shared.plugins.PluginManager;
 import com.android.systemui.statusbar.CommandQueue;
 import com.android.systemui.statusbar.KeyguardIndicationController;
 import com.android.systemui.statusbar.LockscreenShadeTransitionController;
@@ -355,7 +355,6 @@
 
         when(mStackScrollerController.getView()).thenReturn(mStackScroller);
         when(mStackScroller.generateLayoutParams(any())).thenReturn(new LayoutParams(0, 0));
-        when(mNotificationPanelViewController.getView()).thenReturn(mNotificationPanelView);
         when(mNotificationPanelView.getLayoutParams()).thenReturn(new LayoutParams(0, 0));
         when(powerManagerService.isInteractive()).thenReturn(true);
         when(mStackScroller.getActivatedChild()).thenReturn(null);
diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/PhoneStatusBarViewControllerTest.kt b/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/PhoneStatusBarViewControllerTest.kt
index 320a083..e2843a1 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/PhoneStatusBarViewControllerTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/PhoneStatusBarViewControllerTest.kt
@@ -76,7 +76,6 @@
     @Before
     fun setUp() {
         MockitoAnnotations.initMocks(this)
-        `when`(notificationPanelViewController.view).thenReturn(panelView)
         `when`(sysuiUnfoldComponent.getStatusBarMoveFromCenterAnimationController())
             .thenReturn(moveFromCenterAnimation)
         // create the view and controller on main thread as it requires main looper
diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/PhoneStatusBarViewTest.kt b/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/PhoneStatusBarViewTest.kt
index 5aa7f92..27b1da0 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/PhoneStatusBarViewTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/PhoneStatusBarViewTest.kt
@@ -25,7 +25,6 @@
 import org.junit.Before
 import org.junit.Test
 import org.mockito.Mock
-import org.mockito.Mockito.`when`
 import org.mockito.MockitoAnnotations
 
 @SmallTest
@@ -41,9 +40,6 @@
     @Before
     fun setUp() {
         MockitoAnnotations.initMocks(this)
-        // TODO(b/197137564): Setting up a panel view and its controller feels unnecessary when
-        //   testing just [PhoneStatusBarView].
-        `when`(notificationPanelViewController.view).thenReturn(panelView)
 
         view = PhoneStatusBarView(mContext, null)
     }
diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/StatusBarKeyguardViewManagerTest.java b/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/StatusBarKeyguardViewManagerTest.java
index 9f70565..bf5186b 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/StatusBarKeyguardViewManagerTest.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/StatusBarKeyguardViewManagerTest.java
@@ -307,6 +307,23 @@
     }
 
     @Test
+    public void onPanelExpansionChanged_neverTranslatesBouncerWhenShowBouncer() {
+        // Since KeyguardBouncer.EXPANSION_VISIBLE = 0 panel expansion, if the unlock is dismissing
+        // the bouncer, there may be an onPanelExpansionChanged(0) call to collapse the panel
+        // which would mistakenly cause the bouncer to show briefly before its visibility
+        // is set to hide. Therefore, we don't want to propagate panelExpansionChanged to the
+        // bouncer if the bouncer is dismissing as a result of a biometric unlock.
+        when(mBiometricUnlockController.getMode())
+                .thenReturn(BiometricUnlockController.MODE_SHOW_BOUNCER);
+        mStatusBarKeyguardViewManager.onPanelExpansionChanged(
+                expansionEvent(
+                        /* fraction= */ KeyguardBouncer.EXPANSION_VISIBLE,
+                        /* expanded= */ true,
+                        /* tracking= */ false));
+        verify(mPrimaryBouncer, never()).setExpansion(anyFloat());
+    }
+
+    @Test
     public void onPanelExpansionChanged_neverTranslatesBouncerWhenShadeLocked() {
         when(mStatusBarStateController.getState()).thenReturn(StatusBarState.SHADE_LOCKED);
         mStatusBarKeyguardViewManager.onPanelExpansionChanged(
@@ -570,4 +587,40 @@
         mStatusBarKeyguardViewManager.hideBouncer(false);
         verify(mPrimaryBouncerInteractor, never()).hide();
     }
+
+    @Test
+    public void hideAlternateBouncer_beforeCentralSurfacesRegistered() {
+        mStatusBarKeyguardViewManager =
+                new StatusBarKeyguardViewManager(
+                        getContext(),
+                        mViewMediatorCallback,
+                        mLockPatternUtils,
+                        mStatusBarStateController,
+                        mock(ConfigurationController.class),
+                        mKeyguardUpdateMonitor,
+                        mDreamOverlayStateController,
+                        mock(NavigationModeController.class),
+                        mock(DockManager.class),
+                        mock(NotificationShadeWindowController.class),
+                        mKeyguardStateController,
+                        mock(NotificationMediaManager.class),
+                        mKeyguardBouncerFactory,
+                        mKeyguardMessageAreaFactory,
+                        Optional.of(mSysUiUnfoldComponent),
+                        () -> mShadeController,
+                        mLatencyTracker,
+                        mKeyguardSecurityModel,
+                        mFeatureFlags,
+                        mPrimaryBouncerCallbackInteractor,
+                        mPrimaryBouncerInteractor,
+                        mBouncerView) {
+                    @Override
+                    public ViewRootImpl getViewRootImpl() {
+                        return mViewRootImpl;
+                    }
+                };
+
+        // the following call before registering centralSurfaces should NOT throw a NPE:
+        mStatusBarKeyguardViewManager.hideAlternateBouncer(true);
+    }
 }
diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/policy/ExtensionControllerImplTest.java b/packages/SystemUI/tests/src/com/android/systemui/statusbar/policy/ExtensionControllerImplTest.java
index 14cc032..71ac7c4 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/policy/ExtensionControllerImplTest.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/policy/ExtensionControllerImplTest.java
@@ -33,7 +33,7 @@
 import com.android.systemui.plugins.OverlayPlugin;
 import com.android.systemui.plugins.Plugin;
 import com.android.systemui.plugins.PluginListener;
-import com.android.systemui.shared.plugins.PluginManager;
+import com.android.systemui.plugins.PluginManager;
 import com.android.systemui.statusbar.policy.ConfigurationController.ConfigurationListener;
 import com.android.systemui.statusbar.policy.ExtensionController.Extension;
 import com.android.systemui.statusbar.policy.ExtensionController.TunerFactory;
diff --git a/packages/SystemUI/tests/src/com/android/systemui/toast/ToastUITest.java b/packages/SystemUI/tests/src/com/android/systemui/toast/ToastUITest.java
index 797f86a..27957ed 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/toast/ToastUITest.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/toast/ToastUITest.java
@@ -62,7 +62,7 @@
 import com.android.systemui.SysuiTestCase;
 import com.android.systemui.dump.DumpManager;
 import com.android.systemui.flags.FeatureFlags;
-import com.android.systemui.shared.plugins.PluginManager;
+import com.android.systemui.plugins.PluginManager;
 import com.android.systemui.statusbar.CommandQueue;
 
 import org.junit.Before;
diff --git a/packages/SystemUI/tests/src/com/android/systemui/unfold/FoldAodAnimationControllerTest.kt b/packages/SystemUI/tests/src/com/android/systemui/unfold/FoldAodAnimationControllerTest.kt
index 8645298..89402de 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/unfold/FoldAodAnimationControllerTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/unfold/FoldAodAnimationControllerTest.kt
@@ -88,6 +88,7 @@
 
         deviceStates = FoldableTestUtils.findDeviceStates(context)
 
+        // TODO(b/254878364): remove this call to NPVC.getView()
         whenever(notificationPanelViewController.view).thenReturn(viewGroup)
         whenever(viewGroup.viewTreeObserver).thenReturn(viewTreeObserver)
         whenever(wakefulnessLifecycle.lastSleepReason)
diff --git a/packages/SystemUI/tests/src/com/android/systemui/unfold/util/ScaleAwareUnfoldProgressProviderTest.kt b/packages/SystemUI/tests/src/com/android/systemui/unfold/util/ScaleAwareUnfoldProgressProviderTest.kt
index fc2a78a..e1e54a9 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/unfold/util/ScaleAwareUnfoldProgressProviderTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/unfold/util/ScaleAwareUnfoldProgressProviderTest.kt
@@ -15,14 +15,13 @@
  */
 package com.android.systemui.unfold.util
 
-import android.animation.ValueAnimator
 import android.content.ContentResolver
 import android.database.ContentObserver
+import android.provider.Settings
 import android.testing.AndroidTestingRunner
 import androidx.test.filters.SmallTest
 import com.android.systemui.SysuiTestCase
 import com.android.systemui.unfold.TestUnfoldTransitionProvider
-import com.android.systemui.unfold.UnfoldTransitionProgressProvider
 import com.android.systemui.unfold.UnfoldTransitionProgressProvider.TransitionProgressListener
 import com.android.systemui.util.mockito.any
 import org.junit.Before
@@ -30,6 +29,7 @@
 import org.junit.runner.RunWith
 import org.mockito.ArgumentCaptor
 import org.mockito.Mock
+import org.mockito.Mockito.spy
 import org.mockito.Mockito.verify
 import org.mockito.Mockito.verifyNoMoreInteractions
 import org.mockito.MockitoAnnotations
@@ -38,30 +38,25 @@
 @SmallTest
 class ScaleAwareUnfoldProgressProviderTest : SysuiTestCase() {
 
-    @Mock
-    lateinit var contentResolver: ContentResolver
-
-    @Mock
-    lateinit var sinkProvider: TransitionProgressListener
+    @Mock lateinit var sinkProvider: TransitionProgressListener
 
     private val sourceProvider = TestUnfoldTransitionProvider()
 
-    lateinit var progressProvider: ScaleAwareTransitionProgressProvider
+    private lateinit var contentResolver: ContentResolver
+    private lateinit var progressProvider: ScaleAwareTransitionProgressProvider
 
     private val animatorDurationScaleListenerCaptor =
-            ArgumentCaptor.forClass(ContentObserver::class.java)
+        ArgumentCaptor.forClass(ContentObserver::class.java)
 
     @Before
     fun setUp() {
         MockitoAnnotations.initMocks(this)
+        contentResolver = spy(context.contentResolver)
 
-        progressProvider = ScaleAwareTransitionProgressProvider(
-                sourceProvider,
-                contentResolver
-        )
+        progressProvider = ScaleAwareTransitionProgressProvider(sourceProvider, contentResolver)
 
-        verify(contentResolver).registerContentObserver(any(), any(),
-                animatorDurationScaleListenerCaptor.capture())
+        verify(contentResolver)
+            .registerContentObserver(any(), any(), animatorDurationScaleListenerCaptor.capture())
 
         progressProvider.addCallback(sinkProvider)
     }
@@ -121,12 +116,20 @@
     }
 
     private fun setAnimationsEnabled(enabled: Boolean) {
-        val durationScale = if (enabled) {
-            1f
-        } else {
-            0f
-        }
-        ValueAnimator.setDurationScale(durationScale)
+        val durationScale =
+            if (enabled) {
+                1f
+            } else {
+                0f
+            }
+
+        // It uses [TestableSettingsProvider] and it will be cleared after the test
+        Settings.Global.putString(
+            contentResolver,
+            Settings.Global.ANIMATOR_DURATION_SCALE,
+            durationScale.toString()
+        )
+
         animatorDurationScaleListenerCaptor.value.dispatchChange(/* selfChange= */false)
     }
 }
diff --git a/packages/SystemUI/tests/src/com/android/systemui/user/domain/interactor/UserInteractorTest.kt b/packages/SystemUI/tests/src/com/android/systemui/user/domain/interactor/UserInteractorTest.kt
index 4b49420..47efcd9 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/user/domain/interactor/UserInteractorTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/user/domain/interactor/UserInteractorTest.kt
@@ -771,6 +771,41 @@
             )
     }
 
+    @Test
+    fun `users - secondary user - managed profile is not included`() =
+        runBlocking(IMMEDIATE) {
+            var userInfos = createUserInfos(count = 3, includeGuest = false).toMutableList()
+            userInfos.add(
+                UserInfo(
+                    50,
+                    "Work Profile",
+                    /* iconPath= */ "",
+                    /* flags= */ UserInfo.FLAG_MANAGED_PROFILE
+                )
+            )
+            userRepository.setUserInfos(userInfos)
+            userRepository.setSelectedUserInfo(userInfos[1])
+            userRepository.setSettings(UserSwitcherSettingsModel(isUserSwitcherEnabled = true))
+
+            var res: List<UserModel>? = null
+            val job = underTest.users.onEach { res = it }.launchIn(this)
+            assertThat(res?.size == 3).isTrue()
+            job.cancel()
+        }
+
+    @Test
+    fun `current user is not primary and user switcher is disabled`() =
+        runBlocking(IMMEDIATE) {
+            val userInfos = createUserInfos(count = 2, includeGuest = false)
+            userRepository.setUserInfos(userInfos)
+            userRepository.setSelectedUserInfo(userInfos[1])
+            userRepository.setSettings(UserSwitcherSettingsModel(isUserSwitcherEnabled = false))
+            var selectedUser: UserModel? = null
+            val job = underTest.selectedUser.onEach { selectedUser = it }.launchIn(this)
+            assertThat(selectedUser).isNotNull()
+            job.cancel()
+        }
+
     private fun assertUsers(
         models: List<UserModel>?,
         count: Int,
@@ -893,9 +928,9 @@
             name,
             /* iconPath= */ "",
             /* flags= */ if (isPrimary) {
-                UserInfo.FLAG_PRIMARY or UserInfo.FLAG_ADMIN
+                UserInfo.FLAG_PRIMARY or UserInfo.FLAG_ADMIN or UserInfo.FLAG_FULL
             } else {
-                0
+                UserInfo.FLAG_FULL
             },
             if (isGuest) {
                 UserManager.USER_TYPE_FULL_GUEST
diff --git a/packages/SystemUI/tests/src/com/android/systemui/user/ui/viewmodel/StatusBarUserChipViewModelTest.kt b/packages/SystemUI/tests/src/com/android/systemui/user/ui/viewmodel/StatusBarUserChipViewModelTest.kt
index db348b80..795ff17 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/user/ui/viewmodel/StatusBarUserChipViewModelTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/user/ui/viewmodel/StatusBarUserChipViewModelTest.kt
@@ -281,7 +281,7 @@
                 USER_ID_0,
                 USER_NAME_0.text!!,
                 /* iconPath */ "",
-                /* flags */ 0,
+                /* flags */ UserInfo.FLAG_FULL,
                 /* userType */ UserManager.USER_TYPE_FULL_SYSTEM
             )
 
@@ -290,7 +290,7 @@
                 USER_ID_1,
                 USER_NAME_1.text!!,
                 /* iconPath */ "",
-                /* flags */ 0,
+                /* flags */ UserInfo.FLAG_FULL,
                 /* userType */ UserManager.USER_TYPE_FULL_SYSTEM
             )
 
@@ -299,7 +299,7 @@
                 USER_ID_2,
                 USER_NAME_2.text!!,
                 /* iconPath */ "",
-                /* flags */ 0,
+                /* flags */ UserInfo.FLAG_FULL,
                 /* userType */ UserManager.USER_TYPE_FULL_SYSTEM
             )
     }
diff --git a/packages/SystemUI/tests/src/com/android/systemui/user/ui/viewmodel/UserSwitcherViewModelTest.kt b/packages/SystemUI/tests/src/com/android/systemui/user/ui/viewmodel/UserSwitcherViewModelTest.kt
index eac7fc2..1730b75 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/user/ui/viewmodel/UserSwitcherViewModelTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/user/ui/viewmodel/UserSwitcherViewModelTest.kt
@@ -178,21 +178,21 @@
                     /* id= */ 0,
                     /* name= */ "zero",
                     /* iconPath= */ "",
-                    /* flags= */ UserInfo.FLAG_PRIMARY or UserInfo.FLAG_ADMIN,
+                    /* flags= */ UserInfo.FLAG_PRIMARY or UserInfo.FLAG_ADMIN or UserInfo.FLAG_FULL,
                     UserManager.USER_TYPE_FULL_SYSTEM,
                 ),
                 UserInfo(
                     /* id= */ 1,
                     /* name= */ "one",
                     /* iconPath= */ "",
-                    /* flags= */ 0,
+                    /* flags= */ UserInfo.FLAG_FULL,
                     UserManager.USER_TYPE_FULL_SYSTEM,
                 ),
                 UserInfo(
                     /* id= */ 2,
                     /* name= */ "two",
                     /* iconPath= */ "",
-                    /* flags= */ 0,
+                    /* flags= */ UserInfo.FLAG_FULL,
                     UserManager.USER_TYPE_FULL_SYSTEM,
                 ),
             )
@@ -361,10 +361,10 @@
                     /* iconPath= */ "",
                     /* flags= */ if (index == 0) {
                         // This is the primary user.
-                        UserInfo.FLAG_PRIMARY or UserInfo.FLAG_ADMIN
+                        UserInfo.FLAG_PRIMARY or UserInfo.FLAG_ADMIN or UserInfo.FLAG_FULL
                     } else {
                         // This isn't the primary user.
-                        0
+                        UserInfo.FLAG_FULL
                     },
                     UserManager.USER_TYPE_FULL_SYSTEM,
                 )
diff --git a/packages/SystemUI/tests/src/com/android/systemui/util/sensors/AsyncSensorManagerTest.java b/packages/SystemUI/tests/src/com/android/systemui/util/sensors/AsyncSensorManagerTest.java
index 0d8dd2c..df08efa 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/util/sensors/AsyncSensorManagerTest.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/util/sensors/AsyncSensorManagerTest.java
@@ -28,8 +28,8 @@
 import androidx.test.filters.SmallTest;
 
 import com.android.systemui.SysuiTestCase;
+import com.android.systemui.plugins.PluginManager;
 import com.android.systemui.plugins.SensorManagerPlugin;
-import com.android.systemui.shared.plugins.PluginManager;
 import com.android.systemui.util.concurrency.FakeExecutor;
 import com.android.systemui.util.concurrency.FakeThreadFactory;
 import com.android.systemui.util.time.FakeSystemClock;
diff --git a/packages/SystemUI/tests/src/com/android/systemui/volume/VolumeDialogImplTest.java b/packages/SystemUI/tests/src/com/android/systemui/volume/VolumeDialogImplTest.java
index 2e74bf5..a0b4eab 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/volume/VolumeDialogImplTest.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/volume/VolumeDialogImplTest.java
@@ -18,6 +18,7 @@
 
 import static com.android.systemui.volume.VolumeDialogControllerImpl.STREAMS;
 
+import static junit.framework.Assert.assertEquals;
 import static junit.framework.Assert.assertTrue;
 
 import static org.mockito.ArgumentMatchers.any;
@@ -28,6 +29,7 @@
 import android.app.KeyguardManager;
 import android.media.AudioManager;
 import android.os.SystemClock;
+import android.provider.DeviceConfig;
 import android.testing.AndroidTestingRunner;
 import android.testing.TestableLooper;
 import android.view.InputDevice;
@@ -38,6 +40,7 @@
 
 import androidx.test.filters.SmallTest;
 
+import com.android.internal.config.sysui.SystemUiDeviceConfigFlags;
 import com.android.internal.jank.InteractionJankMonitor;
 import com.android.systemui.Prefs;
 import com.android.systemui.R;
@@ -49,6 +52,9 @@
 import com.android.systemui.statusbar.policy.AccessibilityManagerWrapper;
 import com.android.systemui.statusbar.policy.ConfigurationController;
 import com.android.systemui.statusbar.policy.DeviceProvisionedController;
+import com.android.systemui.util.DeviceConfigProxyFake;
+import com.android.systemui.util.concurrency.FakeExecutor;
+import com.android.systemui.util.time.FakeSystemClock;
 
 import org.junit.Before;
 import org.junit.Test;
@@ -71,6 +77,8 @@
     View mDrawerVibrate;
     View mDrawerMute;
     View mDrawerNormal;
+    private DeviceConfigProxyFake mDeviceConfigProxy;
+    private FakeExecutor mExecutor;
 
     @Mock
     VolumeDialogController mVolumeDialogController;
@@ -97,6 +105,9 @@
 
         getContext().addMockSystemService(KeyguardManager.class, mKeyguard);
 
+        mDeviceConfigProxy = new DeviceConfigProxyFake();
+        mExecutor = new FakeExecutor(new FakeSystemClock());
+
         mDialog = new VolumeDialogImpl(
                 getContext(),
                 mVolumeDialogController,
@@ -106,7 +117,9 @@
                 mMediaOutputDialogFactory,
                 mVolumePanelFactory,
                 mActivityStarter,
-                mInteractionJankMonitor);
+                mInteractionJankMonitor,
+                mDeviceConfigProxy,
+                mExecutor);
         mDialog.init(0, null);
         State state = createShellState();
         mDialog.onStateChangedH(state);
@@ -123,6 +136,9 @@
                 VolumePrefs.SHOW_RINGER_TOAST_COUNT + 1);
 
         Prefs.putBoolean(mContext, Prefs.Key.HAS_SEEN_ODI_CAPTIONS_TOOLTIP, false);
+
+        mDeviceConfigProxy.setProperty(DeviceConfig.NAMESPACE_SYSTEMUI,
+                SystemUiDeviceConfigFlags.VOLUME_SEPARATE_NOTIFICATION, "false", false);
     }
 
     private State createShellState() {
@@ -292,6 +308,35 @@
                 AudioManager.RINGER_MODE_NORMAL, false);
     }
 
+    /**
+     * Ideally we would look at the ringer ImageView and check its assigned drawable id, but that
+     * API does not exist. So we do the next best thing; we check the cached icon id.
+     */
+    @Test
+    public void notificationVolumeSeparated_theRingerIconChanges() {
+        mDeviceConfigProxy.setProperty(DeviceConfig.NAMESPACE_SYSTEMUI,
+                SystemUiDeviceConfigFlags.VOLUME_SEPARATE_NOTIFICATION, "true", false);
+
+        mExecutor.runAllReady(); // for the config change to take effect
+
+        // assert icon is new based on res id
+        assertEquals(mDialog.mVolumeRingerIconDrawableId,
+                R.drawable.ic_speaker_on);
+        assertEquals(mDialog.mVolumeRingerMuteIconDrawableId,
+                R.drawable.ic_speaker_mute);
+    }
+
+    @Test
+    public void notificationVolumeNotSeparated_theRingerIconRemainsTheSame() {
+        mDeviceConfigProxy.setProperty(DeviceConfig.NAMESPACE_SYSTEMUI,
+                SystemUiDeviceConfigFlags.VOLUME_SEPARATE_NOTIFICATION, "false", false);
+
+        mExecutor.runAllReady();
+
+        assertEquals(mDialog.mVolumeRingerIconDrawableId, R.drawable.ic_volume_ringer);
+        assertEquals(mDialog.mVolumeRingerMuteIconDrawableId, R.drawable.ic_volume_ringer_mute);
+    }
+
 /*
     @Test
     public void testContentDescriptions() {
diff --git a/packages/SystemUI/tests/utils/src/com/android/systemui/keyguard/data/repository/FakeKeyguardRepository.kt b/packages/SystemUI/tests/utils/src/com/android/systemui/keyguard/data/repository/FakeKeyguardRepository.kt
index a798f40..3601667 100644
--- a/packages/SystemUI/tests/utils/src/com/android/systemui/keyguard/data/repository/FakeKeyguardRepository.kt
+++ b/packages/SystemUI/tests/utils/src/com/android/systemui/keyguard/data/repository/FakeKeyguardRepository.kt
@@ -19,6 +19,7 @@
 
 import com.android.systemui.common.shared.model.Position
 import com.android.systemui.keyguard.shared.model.BiometricUnlockModel
+import com.android.systemui.keyguard.shared.model.DozeTransitionModel
 import com.android.systemui.keyguard.shared.model.StatusBarState
 import com.android.systemui.keyguard.shared.model.WakefulnessModel
 import kotlinx.coroutines.flow.Flow
@@ -53,6 +54,9 @@
     private val _statusBarState = MutableStateFlow(StatusBarState.SHADE)
     override val statusBarState: Flow<StatusBarState> = _statusBarState
 
+    private val _dozeTransitionModel = MutableStateFlow(DozeTransitionModel())
+    override val dozeTransitionModel: Flow<DozeTransitionModel> = _dozeTransitionModel
+
     private val _wakefulnessState = MutableStateFlow(WakefulnessModel.ASLEEP)
     override val wakefulnessState: Flow<WakefulnessModel> = _wakefulnessState
 
diff --git a/packages/SystemUI/tests/utils/src/com/android/systemui/utils/leaks/FakePluginManager.java b/packages/SystemUI/tests/utils/src/com/android/systemui/utils/leaks/FakePluginManager.java
index d245c72..63756c6 100644
--- a/packages/SystemUI/tests/utils/src/com/android/systemui/utils/leaks/FakePluginManager.java
+++ b/packages/SystemUI/tests/utils/src/com/android/systemui/utils/leaks/FakePluginManager.java
@@ -18,7 +18,7 @@
 
 import com.android.systemui.plugins.Plugin;
 import com.android.systemui.plugins.PluginListener;
-import com.android.systemui.shared.plugins.PluginManager;
+import com.android.systemui.plugins.PluginManager;
 
 public class FakePluginManager implements PluginManager {
 
diff --git a/packages/SystemUI/tests/utils/src/com/android/systemui/utils/leaks/LeakCheckedTest.java b/packages/SystemUI/tests/utils/src/com/android/systemui/utils/leaks/LeakCheckedTest.java
index dc6a8fb..ec1f352 100644
--- a/packages/SystemUI/tests/utils/src/com/android/systemui/utils/leaks/LeakCheckedTest.java
+++ b/packages/SystemUI/tests/utils/src/com/android/systemui/utils/leaks/LeakCheckedTest.java
@@ -18,7 +18,7 @@
 import android.util.ArrayMap;
 
 import com.android.systemui.SysuiTestCase;
-import com.android.systemui.shared.plugins.PluginManager;
+import com.android.systemui.plugins.PluginManager;
 import com.android.systemui.statusbar.connectivity.NetworkController;
 import com.android.systemui.statusbar.phone.ManagedProfileController;
 import com.android.systemui.statusbar.phone.StatusBarIconController;
diff --git a/packages/SystemUI/unfold/src/com/android/systemui/unfold/util/ScaleAwareTransitionProgressProvider.kt b/packages/SystemUI/unfold/src/com/android/systemui/unfold/util/ScaleAwareTransitionProgressProvider.kt
index 5c92b34..06ca153 100644
--- a/packages/SystemUI/unfold/src/com/android/systemui/unfold/util/ScaleAwareTransitionProgressProvider.kt
+++ b/packages/SystemUI/unfold/src/com/android/systemui/unfold/util/ScaleAwareTransitionProgressProvider.kt
@@ -14,7 +14,6 @@
  */
 package com.android.systemui.unfold.util
 
-import android.animation.ValueAnimator
 import android.content.ContentResolver
 import android.database.ContentObserver
 import android.provider.Settings
@@ -46,13 +45,15 @@
         contentResolver.registerContentObserver(
             Settings.Global.getUriFor(Settings.Global.ANIMATOR_DURATION_SCALE),
             /* notifyForDescendants= */ false,
-            animatorDurationScaleObserver)
+            animatorDurationScaleObserver
+        )
         onAnimatorScaleChanged()
     }
 
     private fun onAnimatorScaleChanged() {
-        val animationsEnabled = ValueAnimator.areAnimatorsEnabled()
-        scopedUnfoldTransitionProgressProvider.setReadyToHandleTransition(animationsEnabled)
+        scopedUnfoldTransitionProgressProvider.setReadyToHandleTransition(
+            contentResolver.areAnimationsEnabled()
+        )
     }
 
     override fun addCallback(listener: TransitionProgressListener) {
@@ -74,4 +75,18 @@
             progressProvider: UnfoldTransitionProgressProvider
         ): ScaleAwareTransitionProgressProvider
     }
+
+    companion object {
+        fun ContentResolver.areAnimationsEnabled(): Boolean {
+            val animationScale =
+                Settings.Global.getStringForUser(
+                        this,
+                        Settings.Global.ANIMATOR_DURATION_SCALE,
+                        this.userId
+                    )
+                    ?.toFloatOrNull()
+                    ?: 1f
+            return animationScale != 0f
+        }
+    }
 }
diff --git a/services/core/java/com/android/server/BatteryService.java b/services/core/java/com/android/server/BatteryService.java
index 2f8dea7..e282679 100644
--- a/services/core/java/com/android/server/BatteryService.java
+++ b/services/core/java/com/android/server/BatteryService.java
@@ -1287,6 +1287,13 @@
         }
 
         @Override
+        public int getBatteryHealth() {
+            synchronized (mLock) {
+                return mHealthInfo.batteryHealth;
+            }
+        }
+
+        @Override
         public boolean getBatteryLevelLow() {
             synchronized (mLock) {
                 return mBatteryLevelLow;
diff --git a/services/core/java/com/android/server/display/DisplayModeDirector.java b/services/core/java/com/android/server/display/DisplayModeDirector.java
index 912b1b2..c131ed6 100644
--- a/services/core/java/com/android/server/display/DisplayModeDirector.java
+++ b/services/core/java/com/android/server/display/DisplayModeDirector.java
@@ -523,8 +523,10 @@
      * changed
      */
     public void defaultDisplayDeviceUpdated(DisplayDeviceConfig displayDeviceConfig) {
-        mSettingsObserver.setRefreshRates(displayDeviceConfig);
-        mBrightnessObserver.updateBlockingZoneThresholds(displayDeviceConfig);
+        mSettingsObserver.setRefreshRates(displayDeviceConfig,
+            /* attemptLoadingFromDeviceConfig= */ true);
+        mBrightnessObserver.updateBlockingZoneThresholds(displayDeviceConfig,
+            /* attemptLoadingFromDeviceConfig= */ true);
     }
 
     /**
@@ -1142,19 +1144,25 @@
         SettingsObserver(@NonNull Context context, @NonNull Handler handler) {
             super(handler);
             mContext = context;
-            setRefreshRates(/* displayDeviceConfig= */ null);
+            // We don't want to load from the DeviceConfig while constructing since this leads to
+            // a spike in the latency of DisplayManagerService startup. This happens because
+            // reading from the DeviceConfig is an intensive IO operation and having it in the
+            // startup phase where we thrive to keep the latency very low has significant impact.
+            setRefreshRates(/* displayDeviceConfig= */ null,
+                /* attemptLoadingFromDeviceConfig= */ false);
         }
 
         /**
          * This is used to update the refresh rate configs from the DeviceConfig, which
          * if missing from DisplayDeviceConfig, and finally fallback to config.xml.
          */
-        public void setRefreshRates(DisplayDeviceConfig displayDeviceConfig) {
-            setDefaultPeakRefreshRate(displayDeviceConfig);
+        public void setRefreshRates(DisplayDeviceConfig displayDeviceConfig,
+                boolean attemptLoadingFromDeviceConfig) {
+            setDefaultPeakRefreshRate(displayDeviceConfig, attemptLoadingFromDeviceConfig);
             mDefaultRefreshRate =
                     (displayDeviceConfig == null) ? (float) mContext.getResources().getInteger(
-                            R.integer.config_defaultRefreshRate)
-                            : (float) displayDeviceConfig.getDefaultRefreshRate();
+                        R.integer.config_defaultRefreshRate)
+                        : (float) displayDeviceConfig.getDefaultRefreshRate();
         }
 
         public void observe() {
@@ -1215,13 +1223,27 @@
             }
         }
 
-        private void setDefaultPeakRefreshRate(DisplayDeviceConfig displayDeviceConfig) {
+        @VisibleForTesting
+        float getDefaultRefreshRate() {
+            return mDefaultRefreshRate;
+        }
+
+        @VisibleForTesting
+        float getDefaultPeakRefreshRate() {
+            return mDefaultPeakRefreshRate;
+        }
+
+        private void setDefaultPeakRefreshRate(DisplayDeviceConfig displayDeviceConfig,
+                boolean attemptLoadingFromDeviceConfig) {
             Float defaultPeakRefreshRate = null;
-            try {
-                defaultPeakRefreshRate =
+
+            if (attemptLoadingFromDeviceConfig) {
+                try {
+                    defaultPeakRefreshRate =
                         mDeviceConfigDisplaySettings.getDefaultPeakRefreshRate();
-            } catch (Exception exception) {
-                // Do nothing
+                } catch (Exception exception) {
+                    // Do nothing
+                }
             }
             if (defaultPeakRefreshRate == null) {
                 defaultPeakRefreshRate =
@@ -1544,7 +1566,8 @@
             mContext = context;
             mHandler = handler;
             mInjector = injector;
-            updateBlockingZoneThresholds(/* displayDeviceConfig= */ null);
+            updateBlockingZoneThresholds(/* displayDeviceConfig= */ null,
+                /* attemptLoadingFromDeviceConfig= */ false);
             mRefreshRateInHighZone = context.getResources().getInteger(
                     R.integer.config_fixedRefreshRateInHighZone);
         }
@@ -1553,22 +1576,44 @@
          * This is used to update the blocking zone thresholds from the DeviceConfig, which
          * if missing from DisplayDeviceConfig, and finally fallback to config.xml.
          */
-        public void updateBlockingZoneThresholds(DisplayDeviceConfig displayDeviceConfig) {
-            loadLowBrightnessThresholds(displayDeviceConfig);
-            loadHighBrightnessThresholds(displayDeviceConfig);
+        public void updateBlockingZoneThresholds(DisplayDeviceConfig displayDeviceConfig,
+                boolean attemptLoadingFromDeviceConfig) {
+            loadLowBrightnessThresholds(displayDeviceConfig, attemptLoadingFromDeviceConfig);
+            loadHighBrightnessThresholds(displayDeviceConfig, attemptLoadingFromDeviceConfig);
         }
 
-        private void loadLowBrightnessThresholds(DisplayDeviceConfig displayDeviceConfig) {
+        @VisibleForTesting
+        int[] getLowDisplayBrightnessThreshold() {
+            return mLowDisplayBrightnessThresholds;
+        }
+
+        @VisibleForTesting
+        int[] getLowAmbientBrightnessThreshold() {
+            return mLowAmbientBrightnessThresholds;
+        }
+
+        @VisibleForTesting
+        int[] getHighDisplayBrightnessThreshold() {
+            return mHighDisplayBrightnessThresholds;
+        }
+
+        @VisibleForTesting
+        int[] getHighAmbientBrightnessThreshold() {
+            return mHighAmbientBrightnessThresholds;
+        }
+
+        private void loadLowBrightnessThresholds(DisplayDeviceConfig displayDeviceConfig,
+                boolean attemptLoadingFromDeviceConfig) {
             mLowDisplayBrightnessThresholds = loadBrightnessThresholds(
                     () -> mDeviceConfigDisplaySettings.getLowDisplayBrightnessThresholds(),
                     () -> displayDeviceConfig.getLowDisplayBrightnessThresholds(),
                     R.array.config_brightnessThresholdsOfPeakRefreshRate,
-                    displayDeviceConfig);
+                    displayDeviceConfig, attemptLoadingFromDeviceConfig);
             mLowAmbientBrightnessThresholds = loadBrightnessThresholds(
                     () -> mDeviceConfigDisplaySettings.getLowAmbientBrightnessThresholds(),
                     () -> displayDeviceConfig.getLowAmbientBrightnessThresholds(),
                     R.array.config_ambientThresholdsOfPeakRefreshRate,
-                    displayDeviceConfig);
+                    displayDeviceConfig, attemptLoadingFromDeviceConfig);
             if (mLowDisplayBrightnessThresholds.length != mLowAmbientBrightnessThresholds.length) {
                 throw new RuntimeException("display low brightness threshold array and ambient "
                         + "brightness threshold array have different length: "
@@ -1579,17 +1624,18 @@
             }
         }
 
-        private void loadHighBrightnessThresholds(DisplayDeviceConfig displayDeviceConfig) {
+        private void loadHighBrightnessThresholds(DisplayDeviceConfig displayDeviceConfig,
+                boolean attemptLoadingFromDeviceConfig) {
             mHighDisplayBrightnessThresholds = loadBrightnessThresholds(
                     () -> mDeviceConfigDisplaySettings.getHighDisplayBrightnessThresholds(),
                     () -> displayDeviceConfig.getHighDisplayBrightnessThresholds(),
                     R.array.config_highDisplayBrightnessThresholdsOfFixedRefreshRate,
-                    displayDeviceConfig);
+                    displayDeviceConfig, attemptLoadingFromDeviceConfig);
             mHighAmbientBrightnessThresholds = loadBrightnessThresholds(
                     () -> mDeviceConfigDisplaySettings.getHighAmbientBrightnessThresholds(),
                     () -> displayDeviceConfig.getHighAmbientBrightnessThresholds(),
                     R.array.config_highAmbientBrightnessThresholdsOfFixedRefreshRate,
-                    displayDeviceConfig);
+                    displayDeviceConfig, attemptLoadingFromDeviceConfig);
             if (mHighDisplayBrightnessThresholds.length
                     != mHighAmbientBrightnessThresholds.length) {
                 throw new RuntimeException("display high brightness threshold array and ambient "
@@ -1605,13 +1651,16 @@
                 Callable<int[]> loadFromDeviceConfigDisplaySettingsCallable,
                 Callable<int[]> loadFromDisplayDeviceConfigCallable,
                 int brightnessThresholdOfFixedRefreshRateKey,
-                DisplayDeviceConfig displayDeviceConfig) {
+                DisplayDeviceConfig displayDeviceConfig, boolean attemptLoadingFromDeviceConfig) {
             int[] brightnessThresholds = null;
-            try {
-                brightnessThresholds =
+
+            if (attemptLoadingFromDeviceConfig) {
+                try {
+                    brightnessThresholds =
                         loadFromDeviceConfigDisplaySettingsCallable.call();
-            } catch (Exception exception) {
-                // Do nothing
+                } catch (Exception exception) {
+                    // Do nothing
+                }
             }
             if (brightnessThresholds == null) {
                 try {
diff --git a/services/core/java/com/android/server/dreams/DreamManagerService.java b/services/core/java/com/android/server/dreams/DreamManagerService.java
index 8f6df0f..3a49d86 100644
--- a/services/core/java/com/android/server/dreams/DreamManagerService.java
+++ b/services/core/java/com/android/server/dreams/DreamManagerService.java
@@ -81,6 +81,7 @@
 import java.util.ArrayList;
 import java.util.List;
 import java.util.Objects;
+import java.util.concurrent.CopyOnWriteArrayList;
 
 /**
  * Service api for managing dreams.
@@ -120,6 +121,10 @@
     private final boolean mDreamsEnabledByDefaultConfig;
     private final boolean mDreamsActivatedOnChargeByDefault;
     private final boolean mDreamsActivatedOnDockByDefault;
+    private final boolean mKeepDreamingWhenUndockedDefault;
+
+    private final CopyOnWriteArrayList<DreamManagerInternal.DreamManagerStateListener>
+            mDreamManagerStateListeners = new CopyOnWriteArrayList<>();
 
     @GuardedBy("mLock")
     private DreamRecord mCurrentDream;
@@ -226,6 +231,8 @@
         mDreamsActivatedOnDockByDefault = mContext.getResources().getBoolean(
                 com.android.internal.R.bool.config_dreamsActivatedOnDockByDefault);
         mSettingsObserver = new SettingsObserver(mHandler);
+        mKeepDreamingWhenUndockedDefault = mContext.getResources().getBoolean(
+                com.android.internal.R.bool.config_keepDreamingWhenUndocking);
     }
 
     @Override
@@ -300,6 +307,7 @@
             pw.println("mIsDocked=" + mIsDocked);
             pw.println("mIsCharging=" + mIsCharging);
             pw.println("mWhenToDream=" + mWhenToDream);
+            pw.println("mKeepDreamingWhenUndockedDefault=" + mKeepDreamingWhenUndockedDefault);
             pw.println("getDozeComponent()=" + getDozeComponent());
             pw.println();
 
@@ -328,7 +336,16 @@
         }
     }
 
-        /** Whether a real dream is occurring. */
+    private void reportKeepDreamingWhenUndockedChanged(boolean keepDreaming) {
+        mHandler.post(() -> {
+            for (DreamManagerInternal.DreamManagerStateListener listener
+                    : mDreamManagerStateListeners) {
+                listener.onKeepDreamingWhenUndockedChanged(keepDreaming);
+            }
+        });
+    }
+
+    /** Whether a real dream is occurring. */
     private boolean isDreamingInternal() {
         synchronized (mLock) {
             return mCurrentDream != null && !mCurrentDream.isPreview
@@ -571,6 +588,8 @@
             }
 
             mSystemDreamComponent = componentName;
+            reportKeepDreamingWhenUndockedChanged(
+                    mKeepDreamingWhenUndockedDefault && mSystemDreamComponent == null);
 
             // Switch dream if currently dreaming and not dozing.
             if (isDreamingInternal() && !isDozingInternal()) {
@@ -1012,6 +1031,22 @@
         public void requestDream() {
             requestDreamInternal();
         }
+
+        @Override
+        public boolean keepDreamingWhenUndockedDefault() {
+            // This value does not change, so a lock should not be needed.
+            return mKeepDreamingWhenUndockedDefault;
+        }
+
+        @Override
+        public void registerDreamManagerStateListener(DreamManagerStateListener listener) {
+            mDreamManagerStateListeners.add(listener);
+        }
+
+        @Override
+        public void unregisterDreamManagerStateListener(DreamManagerStateListener listener) {
+            mDreamManagerStateListeners.remove(listener);
+        }
     }
 
     private static final class DreamRecord {
diff --git a/services/core/java/com/android/server/policy/DeviceStateProviderImpl.java b/services/core/java/com/android/server/policy/DeviceStateProviderImpl.java
index f867808..c9b3597 100644
--- a/services/core/java/com/android/server/policy/DeviceStateProviderImpl.java
+++ b/services/core/java/com/android/server/policy/DeviceStateProviderImpl.java
@@ -367,7 +367,7 @@
                 return;
             }
 
-            int newState = mOrderedStates[0].getIdentifier();
+            int newState = INVALID_DEVICE_STATE;
             for (int i = 0; i < mOrderedStates.length; i++) {
                 int state = mOrderedStates[i].getIdentifier();
                 if (DEBUG) {
@@ -396,7 +396,7 @@
                 }
             }
 
-            if (newState != mLastReportedState) {
+            if (newState != INVALID_DEVICE_STATE && newState != mLastReportedState) {
                 mLastReportedState = newState;
                 stateToReport = newState;
             }
diff --git a/services/core/java/com/android/server/power/PowerManagerService.java b/services/core/java/com/android/server/power/PowerManagerService.java
index 377a651..0d03133 100644
--- a/services/core/java/com/android/server/power/PowerManagerService.java
+++ b/services/core/java/com/android/server/power/PowerManagerService.java
@@ -419,6 +419,9 @@
     // The current battery level percentage.
     private int mBatteryLevel;
 
+    // The amount of battery drained while the device has been in a dream state.
+    private int mDreamsBatteryLevelDrain;
+
     // True if updatePowerStateLocked() is already in progress.
     // TODO(b/215518989): Remove this once transactions are in place
     private boolean mUpdatePowerStateInProgress;
@@ -451,11 +454,6 @@
     @GuardedBy("mEnhancedDischargeTimeLock")
     private boolean mEnhancedDischargePredictionIsPersonalized;
 
-    // The battery level percentage at the time the dream started.
-    // This is used to terminate a dream and go to sleep if the battery is
-    // draining faster than it is charging and the user activity timeout has expired.
-    private int mBatteryLevelWhenDreamStarted;
-
     // The current dock state.
     private int mDockState = Intent.EXTRA_DOCK_STATE_UNDOCKED;
 
@@ -468,9 +466,6 @@
     // True if the device should wake up when plugged or unplugged.
     private boolean mWakeUpWhenPluggedOrUnpluggedConfig;
 
-    // True if the device should keep dreaming when undocked.
-    private boolean mKeepDreamingWhenUndockingConfig;
-
     // True if the device should wake up when plugged or unplugged in theater mode.
     private boolean mWakeUpWhenPluggedOrUnpluggedInTheaterModeConfig;
 
@@ -677,6 +672,19 @@
     // but the DreamService has not yet been told to start (it's an async process).
     private boolean mDozeStartInProgress;
 
+    // Whether to keep dreaming when the device is undocked.
+    private boolean mKeepDreamingWhenUndocked;
+
+    private final class DreamManagerStateListener implements
+            DreamManagerInternal.DreamManagerStateListener {
+        @Override
+        public void onKeepDreamingWhenUndockedChanged(boolean keepDreaming) {
+            synchronized (mLock) {
+                mKeepDreamingWhenUndocked = keepDreaming;
+            }
+        }
+    }
+
     private final class PowerGroupWakefulnessChangeListener implements
             PowerGroup.PowerGroupListener {
         @GuardedBy("mLock")
@@ -1265,6 +1273,12 @@
                     new DisplayGroupPowerChangeListener();
             mDisplayManagerInternal.registerDisplayGroupListener(displayGroupPowerChangeListener);
 
+            // These DreamManager methods do not acquire locks, so they should be safe to call.
+            mKeepDreamingWhenUndocked = mDreamManager.keepDreamingWhenUndockedDefault();
+            if (mKeepDreamingWhenUndocked) {
+                mDreamManager.registerDreamManagerStateListener(new DreamManagerStateListener());
+            }
+
             mWirelessChargerDetector = mInjector.createWirelessChargerDetector(sensorManager,
                     mInjector.createSuspendBlocker(
                             this, "PowerManagerService.WirelessChargerDetector"),
@@ -1382,8 +1396,6 @@
                 com.android.internal.R.bool.config_powerDecoupleInteractiveModeFromDisplay);
         mWakeUpWhenPluggedOrUnpluggedConfig = resources.getBoolean(
                 com.android.internal.R.bool.config_unplugTurnsOnScreen);
-        mKeepDreamingWhenUndockingConfig = resources.getBoolean(
-                com.android.internal.R.bool.config_keepDreamingWhenUndocking);
         mWakeUpWhenPluggedOrUnpluggedInTheaterModeConfig = resources.getBoolean(
                 com.android.internal.R.bool.config_allowTheaterModeWakeFromUnplug);
         mSuspendWhenScreenOffDueToProximityConfig = resources.getBoolean(
@@ -2448,15 +2460,25 @@
             final int oldPlugType = mPlugType;
             mIsPowered = mBatteryManagerInternal.isPowered(BatteryManager.BATTERY_PLUGGED_ANY);
             mPlugType = mBatteryManagerInternal.getPlugType();
+            final int oldBatteryLevel = mBatteryLevel;
             mBatteryLevel = mBatteryManagerInternal.getBatteryLevel();
             mBatteryLevelLow = mBatteryManagerInternal.getBatteryLevelLow();
+            final boolean isOverheat = mBatteryManagerInternal.getBatteryHealth()
+                    == BatteryManager.BATTERY_HEALTH_OVERHEAT;
 
             if (DEBUG_SPEW) {
                 Slog.d(TAG, "updateIsPoweredLocked: wasPowered=" + wasPowered
                         + ", mIsPowered=" + mIsPowered
                         + ", oldPlugType=" + oldPlugType
                         + ", mPlugType=" + mPlugType
-                        + ", mBatteryLevel=" + mBatteryLevel);
+                        + ", oldBatteryLevel=" + oldBatteryLevel
+                        + ", mBatteryLevel=" + mBatteryLevel
+                        + ", isOverheat=" + isOverheat);
+            }
+
+            if (!isOverheat && oldBatteryLevel > 0
+                    && getGlobalWakefulnessLocked() == WAKEFULNESS_DREAMING) {
+                mDreamsBatteryLevelDrain += (oldBatteryLevel - mBatteryLevel);
             }
 
             if (wasPowered != mIsPowered || oldPlugType != mPlugType) {
@@ -2508,7 +2530,7 @@
         }
 
         // Don't wake when undocking while dreaming if configured not to.
-        if (mKeepDreamingWhenUndockingConfig
+        if (mKeepDreamingWhenUndocked
                 && getGlobalWakefulnessLocked() == WAKEFULNESS_DREAMING
                 && wasPowered && !mIsPowered
                 && oldPlugType == BatteryManager.BATTERY_PLUGGED_DOCK) {
@@ -3278,7 +3300,7 @@
 
             // Remember the initial battery level when the dream started.
             if (startDreaming && isDreaming) {
-                mBatteryLevelWhenDreamStarted = mBatteryLevel;
+                mDreamsBatteryLevelDrain = 0;
                 if (wakefulness == WAKEFULNESS_DOZING) {
                     Slog.i(TAG, "Dozing...");
                 } else {
@@ -3299,16 +3321,15 @@
             if (wakefulness == WAKEFULNESS_DREAMING) {
                 if (isDreaming && canDreamLocked(powerGroup)) {
                     if (mDreamsBatteryLevelDrainCutoffConfig >= 0
-                            && mBatteryLevel < mBatteryLevelWhenDreamStarted
-                                    - mDreamsBatteryLevelDrainCutoffConfig
+                            && mDreamsBatteryLevelDrain > mDreamsBatteryLevelDrainCutoffConfig
                             && !isBeingKeptAwakeLocked(powerGroup)) {
                         // If the user activity timeout expired and the battery appears
                         // to be draining faster than it is charging then stop dreaming
                         // and go to sleep.
                         Slog.i(TAG, "Stopping dream because the battery appears to "
                                 + "be draining faster than it is charging.  "
-                                + "Battery level when dream started: "
-                                + mBatteryLevelWhenDreamStarted + "%.  "
+                                + "Battery level drained while dreaming: "
+                                + mDreamsBatteryLevelDrain + "%.  "
                                 + "Battery level now: " + mBatteryLevel + "%.");
                     } else {
                         return; // continue dreaming
@@ -3539,6 +3560,11 @@
                 mScreenBrightnessBoostInProgress);
     }
 
+    @VisibleForTesting
+    int getDreamsBatteryLevelDrain() {
+        return mDreamsBatteryLevelDrain;
+    }
+
     private final DisplayManagerInternal.DisplayPowerCallbacks mDisplayPowerCallbacks =
             new DisplayManagerInternal.DisplayPowerCallbacks() {
 
@@ -4383,7 +4409,7 @@
             pw.println("  mIsPowered=" + mIsPowered);
             pw.println("  mPlugType=" + mPlugType);
             pw.println("  mBatteryLevel=" + mBatteryLevel);
-            pw.println("  mBatteryLevelWhenDreamStarted=" + mBatteryLevelWhenDreamStarted);
+            pw.println("  mDreamsBatteryLevelDrain=" + mDreamsBatteryLevelDrain);
             pw.println("  mDockState=" + mDockState);
             pw.println("  mStayOn=" + mStayOn);
             pw.println("  mProximityPositive=" + mProximityPositive);
@@ -4457,8 +4483,7 @@
                     + mWakeUpWhenPluggedOrUnpluggedInTheaterModeConfig);
             pw.println("  mTheaterModeEnabled="
                     + mTheaterModeEnabled);
-            pw.println("  mKeepDreamingWhenUndockingConfig="
-                    + mKeepDreamingWhenUndockingConfig);
+            pw.println("  mKeepDreamingWhenUndocked=" + mKeepDreamingWhenUndocked);
             pw.println("  mSuspendWhenScreenOffDueToProximityConfig="
                     + mSuspendWhenScreenOffDueToProximityConfig);
             pw.println("  mDreamsSupportedConfig=" + mDreamsSupportedConfig);
@@ -4625,8 +4650,8 @@
             proto.write(PowerManagerServiceDumpProto.PLUG_TYPE, mPlugType);
             proto.write(PowerManagerServiceDumpProto.BATTERY_LEVEL, mBatteryLevel);
             proto.write(
-                    PowerManagerServiceDumpProto.BATTERY_LEVEL_WHEN_DREAM_STARTED,
-                    mBatteryLevelWhenDreamStarted);
+                    PowerManagerServiceDumpProto.BATTERY_LEVEL_DRAINED_WHILE_DREAMING,
+                    mDreamsBatteryLevelDrain);
             proto.write(PowerManagerServiceDumpProto.DOCK_STATE, mDockState);
             proto.write(PowerManagerServiceDumpProto.IS_STAY_ON, mStayOn);
             proto.write(PowerManagerServiceDumpProto.IS_PROXIMITY_POSITIVE, mProximityPositive);
diff --git a/services/core/java/com/android/server/wm/ActivityRecord.java b/services/core/java/com/android/server/wm/ActivityRecord.java
index fc15890..66992aa 100644
--- a/services/core/java/com/android/server/wm/ActivityRecord.java
+++ b/services/core/java/com/android/server/wm/ActivityRecord.java
@@ -5261,42 +5261,7 @@
         }
         // If we are preparing an app transition, then delay changing
         // the visibility of this token until we execute that transition.
-        // Note that we ignore display frozen since we want the opening / closing transition type
-        // can be updated correctly even display frozen, and it's safe since in applyAnimation will
-        // still check DC#okToAnimate again if the transition animation is fine to apply.
-        // TODO(new-app-transition): Rewrite this logic using WM Shell.
-        final boolean recentsAnimating = isAnimating(PARENTS, ANIMATION_TYPE_RECENTS);
-        final boolean isEnteringPipWithoutVisibleChange = mWaitForEnteringPinnedMode
-                && mVisible == visible;
-        if (okToAnimate(true /* ignoreFrozen */, canTurnScreenOn())
-                && (appTransition.isTransitionSet()
-                || (recentsAnimating && !isActivityTypeHome()))
-                // If the visibility is not changed during enter PIP, we don't want to include it in
-                // app transition to affect the animation theme, because the Pip organizer will
-                // animate the entering PIP instead.
-                && !isEnteringPipWithoutVisibleChange) {
-            if (visible) {
-                displayContent.mOpeningApps.add(this);
-                mEnteringAnimation = true;
-            } else if (mVisible) {
-                displayContent.mClosingApps.add(this);
-                mEnteringAnimation = false;
-            }
-            if ((appTransition.getTransitFlags() & TRANSIT_FLAG_OPEN_BEHIND) != 0) {
-                // We're launchingBehind, add the launching activity to mOpeningApps.
-                final WindowState win = getDisplayContent().findFocusedWindow();
-                if (win != null) {
-                    final ActivityRecord focusedActivity = win.mActivityRecord;
-                    if (focusedActivity != null) {
-                        ProtoLog.d(WM_DEBUG_APP_TRANSITIONS,
-                                "TRANSIT_FLAG_OPEN_BEHIND,  adding %s to mOpeningApps",
-                                focusedActivity);
-
-                        // Force animation to be loaded.
-                        displayContent.mOpeningApps.add(focusedActivity);
-                    }
-                }
-            }
+        if (deferCommitVisibilityChange(visible)) {
             return;
         }
 
@@ -5304,6 +5269,61 @@
         updateReportedVisibilityLocked();
     }
 
+    /**
+     * Returns {@code true} if this activity is either added to opening-apps or closing-apps.
+     * Then its visibility will be committed until the transition is ready.
+     */
+    private boolean deferCommitVisibilityChange(boolean visible) {
+        if (!mDisplayContent.mAppTransition.isTransitionSet()) {
+            if (mTransitionController.isShellTransitionsEnabled()) {
+                // Shell transition doesn't use opening/closing sets.
+                return false;
+            }
+            // Defer committing visibility for non-home app which is animating by recents.
+            if (isActivityTypeHome() || !isAnimating(PARENTS, ANIMATION_TYPE_RECENTS)) {
+                return false;
+            }
+        }
+        if (mWaitForEnteringPinnedMode && mVisible == visible) {
+            // If the visibility is not changed during enter PIP, we don't want to include it in
+            // app transition to affect the animation theme, because the Pip organizer will
+            // animate the entering PIP instead.
+            return false;
+        }
+
+        // The animation will be visible soon so do not skip by screen off.
+        final boolean ignoreScreenOn = canTurnScreenOn() || mTaskSupervisor.getKeyguardController()
+                .isKeyguardGoingAway(mDisplayContent.mDisplayId);
+        // Ignore display frozen so the opening / closing transition type can be updated correctly
+        // even if the display is frozen. And it's safe since in applyAnimation will still check
+        // DC#okToAnimate again if the transition animation is fine to apply.
+        if (!okToAnimate(true /* ignoreFrozen */, ignoreScreenOn)) {
+            return false;
+        }
+        if (visible) {
+            mDisplayContent.mOpeningApps.add(this);
+            mEnteringAnimation = true;
+        } else if (mVisible) {
+            mDisplayContent.mClosingApps.add(this);
+            mEnteringAnimation = false;
+        }
+        if ((mDisplayContent.mAppTransition.getTransitFlags() & TRANSIT_FLAG_OPEN_BEHIND) != 0) {
+            // Add the launching-behind activity to mOpeningApps.
+            final WindowState win = mDisplayContent.findFocusedWindow();
+            if (win != null) {
+                final ActivityRecord focusedActivity = win.mActivityRecord;
+                if (focusedActivity != null) {
+                    ProtoLog.d(WM_DEBUG_APP_TRANSITIONS,
+                            "TRANSIT_FLAG_OPEN_BEHIND,  adding %s to mOpeningApps",
+                            focusedActivity);
+                    // Force animation to be loaded.
+                    mDisplayContent.mOpeningApps.add(focusedActivity);
+                }
+            }
+        }
+        return true;
+    }
+
     @Override
     boolean applyAnimation(LayoutParams lp, @TransitionOldType int transit, boolean enter,
             boolean isVoiceInteraction, @Nullable ArrayList<WindowContainer> sources) {
diff --git a/services/core/java/com/android/server/wm/ActivityTaskSupervisor.java b/services/core/java/com/android/server/wm/ActivityTaskSupervisor.java
index 54664f5..6eaeb15e 100644
--- a/services/core/java/com/android/server/wm/ActivityTaskSupervisor.java
+++ b/services/core/java/com/android/server/wm/ActivityTaskSupervisor.java
@@ -2593,7 +2593,8 @@
                         // Apply options to prevent pendingOptions be taken when scheduling
                         // activity lifecycle transaction to make sure the override pending app
                         // transition will be applied immediately.
-                        if (activityOptions.getAnimationType() == ANIM_REMOTE_ANIMATION) {
+                        if (activityOptions != null
+                                && activityOptions.getAnimationType() == ANIM_REMOTE_ANIMATION) {
                             targetActivity.mPendingRemoteAnimation =
                                     activityOptions.getRemoteAnimationAdapter();
                         }
diff --git a/services/core/java/com/android/server/wm/AppTransition.java b/services/core/java/com/android/server/wm/AppTransition.java
index 27370bf..adf862b 100644
--- a/services/core/java/com/android/server/wm/AppTransition.java
+++ b/services/core/java/com/android/server/wm/AppTransition.java
@@ -169,7 +169,8 @@
     private final WindowManagerService mService;
     private final DisplayContent mDisplayContent;
 
-    private final TransitionAnimation mTransitionAnimation;
+    @VisibleForTesting
+    final TransitionAnimation mTransitionAnimation;
 
     private @TransitionFlags int mNextAppTransitionFlags = 0;
     private final ArrayList<Integer> mNextAppTransitionRequests = new ArrayList<>();
@@ -315,10 +316,33 @@
         setAppTransitionState(APP_STATE_TIMEOUT);
     }
 
+    /**
+     * Gets the animation overridden by app via {@link #overridePendingAppTransition}.
+     */
+    @Nullable
+    Animation getNextAppRequestedAnimation(boolean enter) {
+        final Animation a = mTransitionAnimation.loadAppTransitionAnimation(
+                mNextAppTransitionPackage,
+                enter ? mNextAppTransitionEnter : mNextAppTransitionExit);
+        if (mNextAppTransitionBackgroundColor != 0 && a != null) {
+            a.setBackdropColor(mNextAppTransitionBackgroundColor);
+        }
+        return a;
+    }
+
+    /**
+     * Gets the animation background color overridden by app via
+     * {@link #overridePendingAppTransition}.
+     */
     @ColorInt int getNextAppTransitionBackgroundColor() {
         return mNextAppTransitionBackgroundColor;
     }
 
+    @VisibleForTesting
+    boolean isNextAppTransitionOverrideRequested() {
+        return mNextAppTransitionOverrideRequested;
+    }
+
     HardwareBuffer getAppTransitionThumbnailHeader(WindowContainer container) {
         AppTransitionAnimationSpec spec = mNextAppTransitionAnimationsSpecs.get(
                 container.hashCode());
@@ -411,9 +435,12 @@
     }
 
     void clear() {
+        clear(true /* clearAppOverride */);
+    }
+
+    private void clear(boolean clearAppOverride) {
         mNextAppTransitionType = NEXT_TRANSIT_TYPE_NONE;
         mNextAppTransitionOverrideRequested = false;
-        mNextAppTransitionPackage = null;
         mNextAppTransitionAnimationsSpecs.clear();
         mRemoteAnimationController = null;
         mNextAppTransitionAnimationsSpecsFuture = null;
@@ -421,6 +448,12 @@
         mAnimationFinishedCallback = null;
         mOverrideTaskTransition = false;
         mNextAppTransitionIsSync = false;
+        if (clearAppOverride) {
+            mNextAppTransitionPackage = null;
+            mNextAppTransitionEnter = 0;
+            mNextAppTransitionExit = 0;
+            mNextAppTransitionBackgroundColor = 0;
+        }
     }
 
     void freeze() {
@@ -528,7 +561,7 @@
         return TransitionAnimation.loadAnimationSafely(context, resId, TAG);
     }
 
-    static int mapOpenCloseTransitTypes(int transit, boolean enter) {
+    private static int mapOpenCloseTransitTypes(int transit, boolean enter) {
         int animAttr = 0;
         switch (transit) {
             case TRANSIT_OLD_ACTIVITY_OPEN:
@@ -788,11 +821,7 @@
                     "applyAnimation: anim=%s transit=%s Callers=%s", a,
                     appTransitionOldToString(transit), Debug.getCallers(3));
         } else if (mNextAppTransitionType == NEXT_TRANSIT_TYPE_CUSTOM) {
-            a = mTransitionAnimation.loadAppTransitionAnimation(mNextAppTransitionPackage,
-                    enter ? mNextAppTransitionEnter : mNextAppTransitionExit);
-            if (mNextAppTransitionBackgroundColor != 0) {
-                a.setBackdropColor(mNextAppTransitionBackgroundColor);
-            }
+            a = getNextAppRequestedAnimation(enter);
             ProtoLog.v(WM_DEBUG_APP_TRANSITIONS_ANIM,
                     "applyAnimation: anim=%s nextAppTransition=ANIM_CUSTOM transit=%s "
                             + "isEntrance=%b Callers=%s",
@@ -1032,7 +1061,9 @@
         ProtoLog.i(WM_DEBUG_APP_TRANSITIONS, "Override pending remote transitionSet=%b adapter=%s",
                         isTransitionSet(), remoteAnimationAdapter);
         if (isTransitionSet() && !mNextAppTransitionIsSync) {
-            clear();
+            // ActivityEmbedding animation will run by the app process for which we want to respect
+            // the app override for whether or not to show background color.
+            clear(!isActivityEmbedding /* clearAppOverride */);
             mNextAppTransitionType = NEXT_TRANSIT_TYPE_REMOTE;
             mRemoteAnimationController = new RemoteAnimationController(mService, mDisplayContent,
                     remoteAnimationAdapter, mHandler, isActivityEmbedding);
diff --git a/services/core/java/com/android/server/wm/AppTransitionController.java b/services/core/java/com/android/server/wm/AppTransitionController.java
index 12133bc..d5c9e66 100644
--- a/services/core/java/com/android/server/wm/AppTransitionController.java
+++ b/services/core/java/com/android/server/wm/AppTransitionController.java
@@ -562,6 +562,34 @@
     }
 
     /**
+     * Whether the transition contains any embedded {@link TaskFragment} that does not fill the
+     * parent {@link Task} before or after the transition.
+     */
+    private boolean transitionContainsTaskFragmentWithBoundsOverride() {
+        for (int i = mDisplayContent.mChangingContainers.size() - 1; i >= 0; i--) {
+            final WindowContainer wc = mDisplayContent.mChangingContainers.valueAt(i);
+            if (wc.isEmbedded()) {
+                // Contains embedded TaskFragment with bounds changed.
+                return true;
+            }
+        }
+        mTempTransitionWindows.clear();
+        mTempTransitionWindows.addAll(mDisplayContent.mClosingApps);
+        mTempTransitionWindows.addAll(mDisplayContent.mOpeningApps);
+        boolean containsTaskFragmentWithBoundsOverride = false;
+        for (int i = mTempTransitionWindows.size() - 1; i >= 0; i--) {
+            final ActivityRecord r = mTempTransitionWindows.get(i).asActivityRecord();
+            final TaskFragment tf = r.getTaskFragment();
+            if (tf != null && tf.isEmbeddedWithBoundsOverride()) {
+                containsTaskFragmentWithBoundsOverride = true;
+                break;
+            }
+        }
+        mTempTransitionWindows.clear();
+        return containsTaskFragmentWithBoundsOverride;
+    }
+
+    /**
      * Finds the common parent {@link Task} that is parent of all embedded app windows in the
      * current transition.
      * @return {@code null} if app windows in the transition are not children of the same Task, or
@@ -664,12 +692,17 @@
         if (transitionMayContainNonAppWindows(transit)) {
             return false;
         }
+        if (!transitionContainsTaskFragmentWithBoundsOverride()) {
+            // No need to play TaskFragment remote animation if all embedded TaskFragment in the
+            // transition fill the Task.
+            return false;
+        }
 
         final Task task = findParentTaskForAllEmbeddedWindows();
         final ITaskFragmentOrganizer organizer = findTaskFragmentOrganizer(task);
         final RemoteAnimationDefinition definition = organizer != null
                 ? mDisplayContent.mAtmService.mTaskFragmentOrganizerController
-                    .getRemoteAnimationDefinition(organizer, task.mTaskId)
+                    .getRemoteAnimationDefinition(organizer)
                 : null;
         final RemoteAnimationAdapter adapter = definition != null
                 ? definition.getAdapter(transit, activityTypes)
diff --git a/services/core/java/com/android/server/wm/DisplayContent.java b/services/core/java/com/android/server/wm/DisplayContent.java
index 38f6a53..3a936a5 100644
--- a/services/core/java/com/android/server/wm/DisplayContent.java
+++ b/services/core/java/com/android/server/wm/DisplayContent.java
@@ -4935,9 +4935,8 @@
     @Override
     boolean okToAnimate(boolean ignoreFrozen, boolean ignoreScreenOn) {
         return okToDisplay(ignoreFrozen, ignoreScreenOn)
-                && (mDisplayId != DEFAULT_DISPLAY
-                || mWmService.mPolicy.okToAnimate(ignoreScreenOn))
-                && getDisplayPolicy().isScreenOnFully();
+                && (mDisplayId != DEFAULT_DISPLAY || mWmService.mPolicy.okToAnimate(ignoreScreenOn))
+                && (ignoreFrozen || mDisplayPolicy.isScreenOnFully());
     }
 
     static final class TaskForResizePointSearchResult implements Predicate<Task> {
diff --git a/services/core/java/com/android/server/wm/LaunchParamsUtil.java b/services/core/java/com/android/server/wm/LaunchParamsUtil.java
index 4122992..09a17e1 100644
--- a/services/core/java/com/android/server/wm/LaunchParamsUtil.java
+++ b/services/core/java/com/android/server/wm/LaunchParamsUtil.java
@@ -26,6 +26,7 @@
 import android.content.pm.ActivityInfo;
 import android.graphics.Rect;
 import android.util.Size;
+import android.view.View;
 
 /**
  * The static class that defines some utility constants and functions that are shared among launch
@@ -43,6 +44,10 @@
     private static final int DEFAULT_LANDSCAPE_FREEFORM_WIDTH_DP = 1064;
     private static final int DEFAULT_LANDSCAPE_FREEFORM_HEIGHT_DP = 600;
 
+    private static final int DISPLAY_EDGE_OFFSET_DP = 27;
+
+    private static final Rect TMP_STABLE_BOUNDS = new Rect();
+
     private LaunchParamsUtil() {}
 
     /**
@@ -126,4 +131,68 @@
 
         return new Size(adjWidth, adjHeight);
     }
+
+    static void adjustBoundsToFitInDisplayArea(@NonNull TaskDisplayArea displayArea,
+                                               int layoutDirection,
+                                               @NonNull ActivityInfo.WindowLayout layout,
+                                               @NonNull Rect inOutBounds) {
+        // Give a small margin between the window bounds and the display bounds.
+        final Rect stableBounds = TMP_STABLE_BOUNDS;
+        displayArea.getStableRect(stableBounds);
+        final float density = (float) displayArea.getConfiguration().densityDpi / DENSITY_DEFAULT;
+        final int displayEdgeOffset = (int) (DISPLAY_EDGE_OFFSET_DP * density + 0.5f);
+        stableBounds.inset(displayEdgeOffset, displayEdgeOffset);
+
+        if (stableBounds.width() < inOutBounds.width()
+                || stableBounds.height() < inOutBounds.height()) {
+            final float heightShrinkRatio = stableBounds.width() / (float) inOutBounds.width();
+            final float widthShrinkRatio =
+                    stableBounds.height() / (float) inOutBounds.height();
+            final float shrinkRatio = Math.min(heightShrinkRatio, widthShrinkRatio);
+            // Minimum layout requirements.
+            final int layoutMinWidth = (layout == null) ? -1 : layout.minWidth;
+            final int layoutMinHeight = (layout == null) ? -1 : layout.minHeight;
+            int adjustedWidth = Math.max(layoutMinWidth, (int) (inOutBounds.width() * shrinkRatio));
+            int adjustedHeight = Math.max(layoutMinHeight,
+                    (int) (inOutBounds.height() * shrinkRatio));
+            if (stableBounds.width() < adjustedWidth
+                    || stableBounds.height() < adjustedHeight) {
+                // There is no way for us to fit the bounds in the displayArea without breaking min
+                // size constraints. Set the min size to make visible as much content as possible.
+                final int left = layoutDirection == View.LAYOUT_DIRECTION_RTL
+                        ? stableBounds.right - adjustedWidth
+                        : stableBounds.left;
+                inOutBounds.set(left, stableBounds.top, left + adjustedWidth,
+                        stableBounds.top + adjustedHeight);
+                return;
+            }
+            inOutBounds.set(inOutBounds.left, inOutBounds.top,
+                    inOutBounds.left + adjustedWidth, inOutBounds.top + adjustedHeight);
+        }
+
+        final int dx;
+        if (inOutBounds.right > stableBounds.right) {
+            // Right edge is out of displayArea.
+            dx = stableBounds.right - inOutBounds.right;
+        } else if (inOutBounds.left < stableBounds.left) {
+            // Left edge is out of displayArea.
+            dx = stableBounds.left - inOutBounds.left;
+        } else {
+            // Vertical edges are all in displayArea.
+            dx = 0;
+        }
+
+        final int dy;
+        if (inOutBounds.top < stableBounds.top) {
+            // Top edge is out of displayArea.
+            dy = stableBounds.top - inOutBounds.top;
+        } else if (inOutBounds.bottom > stableBounds.bottom) {
+            // Bottom edge is out of displayArea.
+            dy = stableBounds.bottom - inOutBounds.bottom;
+        } else {
+            // Horizontal edges are all in displayArea.
+            dy = 0;
+        }
+        inOutBounds.offset(dx, dy);
+    }
 }
diff --git a/services/core/java/com/android/server/wm/TaskDisplayArea.java b/services/core/java/com/android/server/wm/TaskDisplayArea.java
index ff09163..0cadc25 100644
--- a/services/core/java/com/android/server/wm/TaskDisplayArea.java
+++ b/services/core/java/com/android/server/wm/TaskDisplayArea.java
@@ -929,6 +929,7 @@
             // Update windowing mode if necessary, e.g. launch into a different windowing mode.
             if (windowingMode != WINDOWING_MODE_UNDEFINED && candidateTask.isRootTask()
                     && candidateTask.getWindowingMode() != windowingMode) {
+                candidateTask.mTransitionController.collect(candidateTask);
                 candidateTask.setWindowingMode(windowingMode);
             }
             return candidateTask.getRootTask();
diff --git a/services/core/java/com/android/server/wm/TaskFragment.java b/services/core/java/com/android/server/wm/TaskFragment.java
index 52df3ca..273b4c4 100644
--- a/services/core/java/com/android/server/wm/TaskFragment.java
+++ b/services/core/java/com/android/server/wm/TaskFragment.java
@@ -581,15 +581,7 @@
 
     @Override
     boolean isEmbedded() {
-        if (mIsEmbedded) {
-            return true;
-        }
-        final WindowContainer<?> parent = getParent();
-        if (parent != null) {
-            final TaskFragment taskFragment = parent.asTaskFragment();
-            return taskFragment != null && taskFragment.isEmbedded();
-        }
-        return false;
+        return mIsEmbedded;
     }
 
     @EmbeddingCheckResult
@@ -2519,6 +2511,22 @@
         return mTaskFragmentOrganizer != null;
     }
 
+    /**
+     * Whether this is an embedded {@link TaskFragment} that does not fill the parent {@link Task}.
+     */
+    boolean isEmbeddedWithBoundsOverride() {
+        if (!mIsEmbedded) {
+            return false;
+        }
+        final Task task = getTask();
+        if (task == null) {
+            return false;
+        }
+        final Rect taskBounds = task.getBounds();
+        final Rect taskFragBounds = getBounds();
+        return !taskBounds.equals(taskFragBounds) && taskBounds.contains(taskFragBounds);
+    }
+
     /** Whether the Task should be visible. */
     boolean isTaskVisibleRequested() {
         final Task task = getTask();
diff --git a/services/core/java/com/android/server/wm/TaskFragmentOrganizerController.java b/services/core/java/com/android/server/wm/TaskFragmentOrganizerController.java
index 2e716ae..6e4df79 100644
--- a/services/core/java/com/android/server/wm/TaskFragmentOrganizerController.java
+++ b/services/core/java/com/android/server/wm/TaskFragmentOrganizerController.java
@@ -36,7 +36,6 @@
 import android.annotation.Nullable;
 import android.content.Intent;
 import android.content.res.Configuration;
-import android.graphics.Rect;
 import android.os.Binder;
 import android.os.Bundle;
 import android.os.IBinder;
@@ -133,12 +132,11 @@
                 new WeakHashMap<>();
 
         /**
-         * Map from Task Id to {@link RemoteAnimationDefinition}.
-         * @see android.window.TaskFragmentOrganizer#registerRemoteAnimations(int,
-         * RemoteAnimationDefinition) )
+         * {@link RemoteAnimationDefinition} for embedded activities transition animation that is
+         * organized by this organizer.
          */
-        private final SparseArray<RemoteAnimationDefinition> mRemoteAnimationDefinitions =
-                new SparseArray<>();
+        @Nullable
+        private RemoteAnimationDefinition mRemoteAnimationDefinition;
 
         /**
          * Map from {@link TaskFragmentTransaction#getTransactionToken()} to the
@@ -427,7 +425,7 @@
             ProtoLog.v(WM_DEBUG_WINDOW_ORGANIZER,
                     "Register task fragment organizer=%s uid=%d pid=%d",
                     organizer.asBinder(), uid, pid);
-            if (mTaskFragmentOrganizerState.containsKey(organizer.asBinder())) {
+            if (isOrganizerRegistered(organizer)) {
                 throw new IllegalStateException(
                         "Replacing existing organizer currently unsupported");
             }
@@ -455,7 +453,7 @@
     }
 
     @Override
-    public void registerRemoteAnimations(@NonNull ITaskFragmentOrganizer organizer, int taskId,
+    public void registerRemoteAnimations(@NonNull ITaskFragmentOrganizer organizer,
             @NonNull RemoteAnimationDefinition definition) {
         final int pid = Binder.getCallingPid();
         final int uid = Binder.getCallingUid();
@@ -468,20 +466,19 @@
             if (organizerState == null) {
                 throw new IllegalStateException("The organizer hasn't been registered.");
             }
-            if (organizerState.mRemoteAnimationDefinitions.contains(taskId)) {
+            if (organizerState.mRemoteAnimationDefinition != null) {
                 throw new IllegalStateException(
                         "The organizer has already registered remote animations="
-                                + organizerState.mRemoteAnimationDefinitions.get(taskId)
-                                + " for TaskId=" + taskId);
+                                + organizerState.mRemoteAnimationDefinition);
             }
 
             definition.setCallingPidUid(pid, uid);
-            organizerState.mRemoteAnimationDefinitions.put(taskId, definition);
+            organizerState.mRemoteAnimationDefinition = definition;
         }
     }
 
     @Override
-    public void unregisterRemoteAnimations(@NonNull ITaskFragmentOrganizer organizer, int taskId) {
+    public void unregisterRemoteAnimations(@NonNull ITaskFragmentOrganizer organizer) {
         final int pid = Binder.getCallingPid();
         final long uid = Binder.getCallingUid();
         synchronized (mGlobalLock) {
@@ -495,7 +492,7 @@
                 return;
             }
 
-            organizerState.mRemoteAnimationDefinitions.remove(taskId);
+            organizerState.mRemoteAnimationDefinition = null;
         }
     }
 
@@ -505,10 +502,18 @@
             @WindowManager.TransitionType int transitionType, boolean shouldApplyIndependently) {
         // Keep the calling identity to avoid unsecure change.
         synchronized (mGlobalLock) {
-            applyTransaction(wct, transitionType, shouldApplyIndependently);
-            final TaskFragmentOrganizerState state = validateAndGetState(
-                    wct.getTaskFragmentOrganizer());
-            state.onTransactionFinished(transactionToken);
+            if (isValidTransaction(wct)) {
+                applyTransaction(wct, transitionType, shouldApplyIndependently);
+            }
+            // Even if the transaction is empty, we still need to invoke #onTransactionFinished
+            // unless the organizer has been unregistered.
+            final ITaskFragmentOrganizer organizer = wct.getTaskFragmentOrganizer();
+            final TaskFragmentOrganizerState state = organizer != null
+                    ? mTaskFragmentOrganizerState.get(organizer.asBinder())
+                    : null;
+            if (state != null) {
+                state.onTransactionFinished(transactionToken);
+            }
         }
     }
 
@@ -517,7 +522,7 @@
             @WindowManager.TransitionType int transitionType, boolean shouldApplyIndependently) {
         // Keep the calling identity to avoid unsecure change.
         synchronized (mGlobalLock) {
-            if (wct.isEmpty()) {
+            if (!isValidTransaction(wct)) {
                 return;
             }
             mWindowOrganizerController.applyTaskFragmentTransactionLocked(wct, transitionType,
@@ -527,16 +532,16 @@
 
     /**
      * Gets the {@link RemoteAnimationDefinition} set on the given organizer if exists. Returns
-     * {@code null} if it doesn't, or if the organizer has activity(ies) embedded in untrusted mode.
+     * {@code null} if it doesn't.
      */
     @Nullable
     public RemoteAnimationDefinition getRemoteAnimationDefinition(
-            @NonNull ITaskFragmentOrganizer organizer, int taskId) {
+            @NonNull ITaskFragmentOrganizer organizer) {
         synchronized (mGlobalLock) {
             final TaskFragmentOrganizerState organizerState =
                     mTaskFragmentOrganizerState.get(organizer.asBinder());
             return organizerState != null
-                    ? organizerState.mRemoteAnimationDefinitions.get(taskId)
+                    ? organizerState.mRemoteAnimationDefinition
                     : null;
         }
     }
@@ -658,7 +663,7 @@
             }
             organizer = organizedTf[0].getTaskFragmentOrganizer();
         }
-        if (!mTaskFragmentOrganizerState.containsKey(organizer.asBinder())) {
+        if (!isOrganizerRegistered(organizer)) {
             Slog.w(TAG, "The last TaskFragmentOrganizer no longer exists");
             return;
         }
@@ -704,7 +709,7 @@
         mPendingTaskFragmentEvents.get(event.mTaskFragmentOrg.asBinder()).remove(event);
     }
 
-    boolean isOrganizerRegistered(@NonNull ITaskFragmentOrganizer organizer) {
+    private boolean isOrganizerRegistered(@NonNull ITaskFragmentOrganizer organizer) {
         return mTaskFragmentOrganizerState.containsKey(organizer.asBinder());
     }
 
@@ -741,6 +746,20 @@
         return state;
     }
 
+    boolean isValidTransaction(@NonNull WindowContainerTransaction t) {
+        if (t.isEmpty()) {
+            return false;
+        }
+        final ITaskFragmentOrganizer organizer = t.getTaskFragmentOrganizer();
+        if (t.getTaskFragmentOrganizer() == null || !isOrganizerRegistered(organizer)) {
+            // Transaction from an unregistered organizer should not be applied. This can happen
+            // when the organizer process died before the transaction is applied.
+            Slog.e(TAG, "Caller organizer=" + organizer + " is no longer registered");
+            return false;
+        }
+        return true;
+    }
+
     /**
      * A class to store {@link ITaskFragmentOrganizer} and its organized
      * {@link TaskFragment TaskFragments} with different pending event request.
@@ -1085,16 +1104,7 @@
                 return false;
             }
             final TaskFragment taskFragment = activity.getOrganizedTaskFragment();
-            if (taskFragment == null) {
-                return false;
-            }
-            final Task parentTask = taskFragment.getTask();
-            if (parentTask != null) {
-                final Rect taskBounds = parentTask.getBounds();
-                final Rect taskFragBounds = taskFragment.getBounds();
-                return !taskBounds.equals(taskFragBounds) && taskBounds.contains(taskFragBounds);
-            }
-            return false;
+            return taskFragment != null && taskFragment.isEmbeddedWithBoundsOverride();
         }
     }
 
diff --git a/services/core/java/com/android/server/wm/TaskLaunchParamsModifier.java b/services/core/java/com/android/server/wm/TaskLaunchParamsModifier.java
index 4edda74..14a2d03 100644
--- a/services/core/java/com/android/server/wm/TaskLaunchParamsModifier.java
+++ b/services/core/java/com/android/server/wm/TaskLaunchParamsModifier.java
@@ -50,7 +50,6 @@
 import android.util.Size;
 import android.util.Slog;
 import android.view.Gravity;
-import android.view.View;
 import android.window.WindowContainerToken;
 
 import com.android.internal.annotations.VisibleForTesting;
@@ -181,26 +180,34 @@
         // is set with the suggestedDisplayArea. If it is set, but the eventual TaskDisplayArea is
         // different, we should recalculating the bounds.
         boolean hasInitialBoundsForSuggestedDisplayAreaInFreeformWindow = false;
-        final boolean canApplyFreeformPolicy =
+        // Note that initial bounds needs to be set to fullscreen tasks too as it's used as restore
+        // bounds.
+        final boolean canCalculateBoundsForFullscreenTask =
+                canCalculateBoundsForFullscreenTask(suggestedDisplayArea, launchMode);
+        final boolean canApplyFreeformWindowPolicy =
                 canApplyFreeformWindowPolicy(suggestedDisplayArea, launchMode);
-        if (mSupervisor.canUseActivityOptionsLaunchBounds(options)
-                && (canApplyFreeformPolicy || canApplyPipWindowPolicy(launchMode))) {
+        final boolean canApplyWindowLayout = layout != null
+                && (canApplyFreeformWindowPolicy || canCalculateBoundsForFullscreenTask);
+        final boolean canApplyBoundsFromActivityOptions =
+                mSupervisor.canUseActivityOptionsLaunchBounds(options)
+                        && (canApplyFreeformWindowPolicy
+                        || canApplyPipWindowPolicy(launchMode)
+                        || canCalculateBoundsForFullscreenTask);
+
+        if (canApplyBoundsFromActivityOptions) {
             hasInitialBounds = true;
-            launchMode = launchMode == WINDOWING_MODE_UNDEFINED
+            // |launchMode| at this point can be fullscreen, PIP, MultiWindow, etc. Only set
+            // freeform windowing mode if appropriate by checking |canApplyFreeformWindowPolicy|.
+            launchMode = launchMode == WINDOWING_MODE_UNDEFINED && canApplyFreeformWindowPolicy
                     ? WINDOWING_MODE_FREEFORM
                     : launchMode;
             outParams.mBounds.set(options.getLaunchBounds());
             if (DEBUG) appendLog("activity-options-bounds=" + outParams.mBounds);
-        } else if (launchMode == WINDOWING_MODE_PINNED) {
-            // System controls PIP window's bounds, so don't apply launch bounds.
-            if (DEBUG) appendLog("empty-window-layout-for-pip");
-        } else if (launchMode == WINDOWING_MODE_FULLSCREEN) {
-            if (DEBUG) appendLog("activity-options-fullscreen=" + outParams.mBounds);
-        } else if (layout != null && canApplyFreeformPolicy) {
+        } else if (canApplyWindowLayout) {
             mTmpBounds.set(currentParams.mBounds);
             getLayoutBounds(suggestedDisplayArea, root, layout, mTmpBounds);
             if (!mTmpBounds.isEmpty()) {
-                launchMode = WINDOWING_MODE_FREEFORM;
+                launchMode = canApplyFreeformWindowPolicy ? WINDOWING_MODE_FREEFORM : launchMode;
                 outParams.mBounds.set(mTmpBounds);
                 hasInitialBounds = true;
                 hasInitialBoundsForSuggestedDisplayAreaInFreeformWindow = true;
@@ -210,6 +217,8 @@
             }
         } else if (launchMode == WINDOWING_MODE_MULTI_WINDOW
                 && options != null && options.getLaunchBounds() != null) {
+            // TODO: Investigate whether we can migrate this clause to the
+            //  |canApplyBoundsFromActivityOptions| case above.
             outParams.mBounds.set(options.getLaunchBounds());
             hasInitialBounds = true;
             if (DEBUG) appendLog("multiwindow-activity-options-bounds=" + outParams.mBounds);
@@ -249,11 +258,9 @@
             if (!currentParams.mBounds.isEmpty()) {
                 // Carry over bounds from callers regardless of launch mode because bounds is still
                 // used to restore last non-fullscreen bounds when launch mode is not freeform.
-                // Therefore it's not a resolution step for non-freeform launch mode and only
-                // consider it fully resolved only when launch mode is freeform.
                 outParams.mBounds.set(currentParams.mBounds);
+                fullyResolvedCurrentParam = true;
                 if (launchMode == WINDOWING_MODE_FREEFORM) {
-                    fullyResolvedCurrentParam = true;
                     if (DEBUG) appendLog("inherit-bounds=" + outParams.mBounds);
                 }
             }
@@ -362,13 +369,13 @@
             if (resolvedMode == WINDOWING_MODE_FREEFORM) {
                 // Make sure bounds are in the displayArea.
                 if (currentParams.mPreferredTaskDisplayArea != taskDisplayArea) {
-                    adjustBoundsToFitInDisplayArea(taskDisplayArea, outParams.mBounds);
+                    adjustBoundsToFitInDisplayArea(taskDisplayArea, layout, outParams.mBounds);
                 }
                 // Even though we want to keep original bounds, we still don't want it to stomp on
                 // an existing task.
                 adjustBoundsToAvoidConflictInDisplayArea(taskDisplayArea, outParams.mBounds);
             }
-        } else if (taskDisplayArea.inFreeformWindowingMode()) {
+        } else {
             if (source != null && source.inFreeformWindowingMode()
                     && resolvedMode == WINDOWING_MODE_FREEFORM
                     && outParams.mBounds.isEmpty()
@@ -545,10 +552,19 @@
         return display.getDisplayId() == source.getDisplayId();
     }
 
+    private boolean canCalculateBoundsForFullscreenTask(@NonNull TaskDisplayArea displayArea,
+                                                        int launchMode) {
+        return mSupervisor.mService.mSupportsFreeformWindowManagement
+                && ((displayArea.getWindowingMode() == WINDOWING_MODE_FULLSCREEN
+                && launchMode == WINDOWING_MODE_UNDEFINED)
+                || launchMode == WINDOWING_MODE_FULLSCREEN);
+    }
+
     private boolean canApplyFreeformWindowPolicy(@NonNull TaskDisplayArea suggestedDisplayArea,
             int launchMode) {
         return mSupervisor.mService.mSupportsFreeformWindowManagement
-                && (suggestedDisplayArea.inFreeformWindowingMode()
+                && ((suggestedDisplayArea.inFreeformWindowingMode()
+                && launchMode == WINDOWING_MODE_UNDEFINED)
                 || launchMode == WINDOWING_MODE_FREEFORM);
     }
 
@@ -710,16 +726,10 @@
     private void getTaskBounds(@NonNull ActivityRecord root, @NonNull TaskDisplayArea displayArea,
             @NonNull ActivityInfo.WindowLayout layout, int resolvedMode, boolean hasInitialBounds,
             @NonNull Rect inOutBounds) {
-        if (resolvedMode == WINDOWING_MODE_FULLSCREEN) {
-            // We don't handle letterboxing here. Letterboxing will be handled by valid checks
-            // later.
-            inOutBounds.setEmpty();
-            if (DEBUG) appendLog("maximized-bounds");
-            return;
-        }
-
-        if (resolvedMode != WINDOWING_MODE_FREEFORM) {
-            // We don't apply freeform bounds adjustment to other windowing modes.
+        if (resolvedMode != WINDOWING_MODE_FREEFORM
+                && resolvedMode != WINDOWING_MODE_FULLSCREEN) {
+            // This function should be used only for freeform bounds adjustment. Freeform bounds
+            // needs to be set to fullscreen tasks too as restore bounds.
             if (DEBUG) {
                 appendLog("skip-bounds-" + WindowConfiguration.windowingModeToString(resolvedMode));
             }
@@ -758,9 +768,10 @@
             // to the center of suggested bounds (or the displayArea if no suggested bounds). The
             // default size might be too big to center to source activity bounds in displayArea, so
             // we may need to move it back to the displayArea.
+            adjustBoundsToFitInDisplayArea(displayArea, layout, mTmpBounds);
+            inOutBounds.setEmpty();
             LaunchParamsUtil.centerBounds(displayArea, mTmpBounds.width(), mTmpBounds.height(),
                     inOutBounds);
-            adjustBoundsToFitInDisplayArea(displayArea, inOutBounds);
             if (DEBUG) appendLog("freeform-size-mismatch=" + inOutBounds);
         }
 
@@ -807,47 +818,12 @@
     }
 
     private void adjustBoundsToFitInDisplayArea(@NonNull TaskDisplayArea displayArea,
-            @NonNull Rect inOutBounds) {
-        final Rect stableBounds = mTmpStableBounds;
-        displayArea.getStableRect(stableBounds);
-
-        if (stableBounds.width() < inOutBounds.width()
-                || stableBounds.height() < inOutBounds.height()) {
-            // There is no way for us to fit the bounds in the displayArea without changing width
-            // or height. Just move the start to align with the displayArea.
-            final int layoutDirection =
-                    mSupervisor.mRootWindowContainer.getConfiguration().getLayoutDirection();
-            final int left = layoutDirection == View.LAYOUT_DIRECTION_RTL
-                    ? stableBounds.right - inOutBounds.right + inOutBounds.left
-                    : stableBounds.left;
-            inOutBounds.offsetTo(left, stableBounds.top);
-            return;
-        }
-
-        final int dx;
-        if (inOutBounds.right > stableBounds.right) {
-            // Right edge is out of displayArea.
-            dx = stableBounds.right - inOutBounds.right;
-        } else if (inOutBounds.left < stableBounds.left) {
-            // Left edge is out of displayArea.
-            dx = stableBounds.left - inOutBounds.left;
-        } else {
-            // Vertical edges are all in displayArea.
-            dx = 0;
-        }
-
-        final int dy;
-        if (inOutBounds.top < stableBounds.top) {
-            // Top edge is out of displayArea.
-            dy = stableBounds.top - inOutBounds.top;
-        } else if (inOutBounds.bottom > stableBounds.bottom) {
-            // Bottom edge is out of displayArea.
-            dy = stableBounds.bottom - inOutBounds.bottom;
-        } else {
-            // Horizontal edges are all in displayArea.
-            dy = 0;
-        }
-        inOutBounds.offset(dx, dy);
+                                                @NonNull ActivityInfo.WindowLayout layout,
+                                                @NonNull Rect inOutBounds) {
+        final int layoutDirection = mSupervisor.mRootWindowContainer.getConfiguration()
+                .getLayoutDirection();
+        LaunchParamsUtil.adjustBoundsToFitInDisplayArea(displayArea, layoutDirection, layout,
+                inOutBounds);
     }
 
     /**
diff --git a/services/core/java/com/android/server/wm/WindowContainer.java b/services/core/java/com/android/server/wm/WindowContainer.java
index fa1bc54..80357eb 100644
--- a/services/core/java/com/android/server/wm/WindowContainer.java
+++ b/services/core/java/com/android/server/wm/WindowContainer.java
@@ -2995,10 +2995,9 @@
                     // screen empty. Show background color to cover that.
                     showBackdrop = getDisplayContent().mChangingContainers.size() > 1;
                 } else {
-                    // Check whether or not to show backdrop for open/close transition.
-                    final int animAttr = AppTransition.mapOpenCloseTransitTypes(transit, enter);
-                    final Animation a = animAttr != 0
-                            ? appTransition.loadAnimationAttr(lp, animAttr, transit) : null;
+                    // Check whether the app has requested to show backdrop for open/close
+                    // transition.
+                    final Animation a = appTransition.getNextAppRequestedAnimation(enter);
                     showBackdrop = a != null && a.getShowBackdrop();
                 }
                 backdropColor = appTransition.getNextAppTransitionBackgroundColor();
diff --git a/services/core/java/com/android/server/wm/WindowOrganizerController.java b/services/core/java/com/android/server/wm/WindowOrganizerController.java
index 557c0ef..b30fd07 100644
--- a/services/core/java/com/android/server/wm/WindowOrganizerController.java
+++ b/services/core/java/com/android/server/wm/WindowOrganizerController.java
@@ -403,9 +403,6 @@
      */
     void applyTaskFragmentTransactionLocked(@NonNull WindowContainerTransaction wct,
             @WindowManager.TransitionType int type, boolean shouldApplyIndependently) {
-        if (!isValidTransaction(wct)) {
-            return;
-        }
         enforceTaskFragmentOrganizerPermission("applyTaskFragmentTransaction()",
                 Objects.requireNonNull(wct.getTaskFragmentOrganizer()),
                 Objects.requireNonNull(wct));
@@ -459,7 +456,7 @@
                     // calls startSyncSet.
                     () -> mTransitionController.moveToCollecting(nextTransition),
                     () -> {
-                        if (isValidTransaction(wct)) {
+                        if (mTaskFragmentOrganizerController.isValidTransaction(wct)) {
                             applyTransaction(wct, -1 /*syncId*/, nextTransition, caller);
                             mTransitionController.requestStartTransition(nextTransition,
                                     null /* startTask */, null /* remoteTransition */,
@@ -1620,18 +1617,6 @@
         return (cfgChanges & CONTROLLABLE_CONFIGS) == 0;
     }
 
-    private boolean isValidTransaction(@NonNull WindowContainerTransaction t) {
-        if (t.getTaskFragmentOrganizer() != null && !mTaskFragmentOrganizerController
-                .isOrganizerRegistered(t.getTaskFragmentOrganizer())) {
-            // Transaction from an unregistered organizer should not be applied. This can happen
-            // when the organizer process died before the transaction is applied.
-            Slog.e(TAG, "Caller organizer=" + t.getTaskFragmentOrganizer()
-                    + " is no longer registered");
-            return false;
-        }
-        return true;
-    }
-
     /**
      * Makes sure that the transaction only contains operations that are allowed for the
      * {@link WindowContainerTransaction#getTaskFragmentOrganizer()}.
diff --git a/services/tests/servicestests/src/com/android/server/display/DisplayModeDirectorTest.java b/services/tests/servicestests/src/com/android/server/display/DisplayModeDirectorTest.java
index 18dd264..fb0cdfa 100644
--- a/services/tests/servicestests/src/com/android/server/display/DisplayModeDirectorTest.java
+++ b/services/tests/servicestests/src/com/android/server/display/DisplayModeDirectorTest.java
@@ -21,6 +21,7 @@
 import static android.hardware.display.DisplayManager.DeviceConfig.KEY_FIXED_REFRESH_RATE_HIGH_DISPLAY_BRIGHTNESS_THRESHOLDS;
 import static android.hardware.display.DisplayManager.DeviceConfig.KEY_FIXED_REFRESH_RATE_LOW_AMBIENT_BRIGHTNESS_THRESHOLDS;
 import static android.hardware.display.DisplayManager.DeviceConfig.KEY_FIXED_REFRESH_RATE_LOW_DISPLAY_BRIGHTNESS_THRESHOLDS;
+import static android.hardware.display.DisplayManager.DeviceConfig.KEY_PEAK_REFRESH_RATE_DEFAULT;
 import static android.hardware.display.DisplayManager.DeviceConfig.KEY_REFRESH_RATE_IN_HBM_HDR;
 import static android.hardware.display.DisplayManager.DeviceConfig.KEY_REFRESH_RATE_IN_HBM_SUNLIGHT;
 import static android.hardware.display.DisplayManager.DeviceConfig.KEY_REFRESH_RATE_IN_HIGH_ZONE;
@@ -31,6 +32,8 @@
 
 import static com.google.common.truth.Truth.assertThat;
 
+import static org.junit.Assert.assertArrayEquals;
+import static org.junit.Assert.assertEquals;
 import static org.junit.Assert.assertNotNull;
 import static org.junit.Assert.assertNull;
 import static org.junit.Assert.assertTrue;
@@ -48,6 +51,7 @@
 import android.content.ContentResolver;
 import android.content.Context;
 import android.content.ContextWrapper;
+import android.content.res.Resources;
 import android.database.ContentObserver;
 import android.hardware.Sensor;
 import android.hardware.SensorEventListener;
@@ -76,6 +80,7 @@
 import androidx.test.filters.SmallTest;
 import androidx.test.runner.AndroidJUnit4;
 
+import com.android.internal.R;
 import com.android.internal.display.BrightnessSynchronizer;
 import com.android.internal.util.Preconditions;
 import com.android.internal.util.test.FakeSettingsProvider;
@@ -1855,16 +1860,83 @@
 
     @Test
     public void testNotifyDefaultDisplayDeviceUpdated() {
-        DisplayDeviceConfig displayDeviceConfig = mock(DisplayDeviceConfig.class);
-        when(displayDeviceConfig.getLowDisplayBrightnessThresholds()).thenReturn(new int[]{});
-        when(displayDeviceConfig.getLowAmbientBrightnessThresholds()).thenReturn(new int[]{});
-        when(displayDeviceConfig.getHighDisplayBrightnessThresholds()).thenReturn(new int[]{});
-        when(displayDeviceConfig.getHighAmbientBrightnessThresholds()).thenReturn(new int[]{});
+        Resources resources = mock(Resources.class);
+        when(mContext.getResources()).thenReturn(resources);
+        when(resources.getInteger(com.android.internal.R.integer.config_defaultPeakRefreshRate))
+            .thenReturn(75);
+        when(resources.getInteger(R.integer.config_defaultRefreshRate))
+            .thenReturn(45);
+        when(resources.getIntArray(R.array.config_brightnessThresholdsOfPeakRefreshRate))
+            .thenReturn(new int[]{5});
+        when(resources.getIntArray(R.array.config_ambientThresholdsOfPeakRefreshRate))
+            .thenReturn(new int[]{10});
+        when(
+            resources.getIntArray(R.array.config_highDisplayBrightnessThresholdsOfFixedRefreshRate))
+            .thenReturn(new int[]{250});
+        when(
+            resources.getIntArray(R.array.config_highAmbientBrightnessThresholdsOfFixedRefreshRate))
+            .thenReturn(new int[]{7000});
         DisplayModeDirector director =
                 createDirectorFromRefreshRateArray(new float[]{60.0f, 90.0f}, 0);
+        // We don't expect any interaction with DeviceConfig when the director is initialized
+        // because we explicitly avoid doing this as this can lead to a latency spike in the
+        // startup of DisplayManagerService
+        // Verify all the loaded values are from DisplayDeviceConfig
+        assertEquals(director.getSettingsObserver().getDefaultRefreshRate(), 45, 0.0);
+        assertEquals(director.getSettingsObserver().getDefaultPeakRefreshRate(), 75,
+                0.0);
+        assertArrayEquals(director.getBrightnessObserver().getHighDisplayBrightnessThreshold(),
+                new int[]{250});
+        assertArrayEquals(director.getBrightnessObserver().getHighAmbientBrightnessThreshold(),
+                new int[]{7000});
+        assertArrayEquals(director.getBrightnessObserver().getLowDisplayBrightnessThreshold(),
+                new int[]{5});
+        assertArrayEquals(director.getBrightnessObserver().getLowAmbientBrightnessThreshold(),
+                new int[]{10});
+
+        // Notify that the default display is updated, such that DisplayDeviceConfig has new values
+        DisplayDeviceConfig displayDeviceConfig = mock(DisplayDeviceConfig.class);
+        when(displayDeviceConfig.getDefaultRefreshRate()).thenReturn(50);
+        when(displayDeviceConfig.getDefaultPeakRefreshRate()).thenReturn(55);
+        when(displayDeviceConfig.getLowDisplayBrightnessThresholds()).thenReturn(new int[]{25});
+        when(displayDeviceConfig.getLowAmbientBrightnessThresholds()).thenReturn(new int[]{30});
+        when(displayDeviceConfig.getHighDisplayBrightnessThresholds()).thenReturn(new int[]{210});
+        when(displayDeviceConfig.getHighAmbientBrightnessThresholds()).thenReturn(new int[]{2100});
         director.defaultDisplayDeviceUpdated(displayDeviceConfig);
-        verify(displayDeviceConfig).getDefaultRefreshRate();
-        verify(displayDeviceConfig).getDefaultPeakRefreshRate();
+
+        assertEquals(director.getSettingsObserver().getDefaultRefreshRate(), 50, 0.0);
+        assertEquals(director.getSettingsObserver().getDefaultPeakRefreshRate(), 55,
+                0.0);
+        assertArrayEquals(director.getBrightnessObserver().getHighDisplayBrightnessThreshold(),
+                new int[]{210});
+        assertArrayEquals(director.getBrightnessObserver().getHighAmbientBrightnessThreshold(),
+                new int[]{2100});
+        assertArrayEquals(director.getBrightnessObserver().getLowDisplayBrightnessThreshold(),
+                new int[]{25});
+        assertArrayEquals(director.getBrightnessObserver().getLowAmbientBrightnessThreshold(),
+                new int[]{30});
+
+        // Notify that the default display is updated, such that DeviceConfig has new values
+        FakeDeviceConfig config = mInjector.getDeviceConfig();
+        config.setDefaultPeakRefreshRate(60);
+        config.setLowAmbientBrightnessThresholds(new int[]{20});
+        config.setLowDisplayBrightnessThresholds(new int[]{10});
+        config.setHighDisplayBrightnessThresholds(new int[]{255});
+        config.setHighAmbientBrightnessThresholds(new int[]{8000});
+
+        director.defaultDisplayDeviceUpdated(displayDeviceConfig);
+
+        assertEquals(director.getSettingsObserver().getDefaultRefreshRate(), 50, 0.0);
+        assertEquals(director.getSettingsObserver().getDefaultPeakRefreshRate(), 60,
+                0.0);
+        assertArrayEquals(director.getBrightnessObserver().getHighDisplayBrightnessThreshold(),
+                new int[]{255});
+        assertArrayEquals(director.getBrightnessObserver().getHighAmbientBrightnessThreshold(),
+                new int[]{8000});
+        assertArrayEquals(director.getBrightnessObserver().getLowDisplayBrightnessThreshold(),
+                new int[]{10});
+        assertArrayEquals(director.getBrightnessObserver().getLowAmbientBrightnessThreshold(),
+                new int[]{20});
     }
 
     private Temperature getSkinTemp(@Temperature.ThrottlingStatus int status) {
@@ -1954,6 +2026,12 @@
                     String.valueOf(fps));
         }
 
+        void setDefaultPeakRefreshRate(int fps) {
+            putPropertyAndNotify(
+                    DeviceConfig.NAMESPACE_DISPLAY_MANAGER, KEY_PEAK_REFRESH_RATE_DEFAULT,
+                    String.valueOf(fps));
+        }
+
         void setHighDisplayBrightnessThresholds(int[] brightnessThresholds) {
             String thresholds = toPropertyValue(brightnessThresholds);
 
diff --git a/services/tests/servicestests/src/com/android/server/policy/DeviceStateProviderImplTest.java b/services/tests/servicestests/src/com/android/server/policy/DeviceStateProviderImplTest.java
index 90b19a4..6d2631a 100644
--- a/services/tests/servicestests/src/com/android/server/policy/DeviceStateProviderImplTest.java
+++ b/services/tests/servicestests/src/com/android/server/policy/DeviceStateProviderImplTest.java
@@ -65,6 +65,7 @@
     private final ArgumentCaptor<DeviceState[]> mDeviceStateArrayCaptor = ArgumentCaptor.forClass(
             DeviceState[].class);
     private final ArgumentCaptor<Integer> mIntegerCaptor = ArgumentCaptor.forClass(Integer.class);
+    private static final int MAX_HINGE_ANGLE_EXCLUSIVE = 360;
 
     private Context mContext;
     private SensorManager mSensorManager;
@@ -268,11 +269,7 @@
         assertEquals(1, mIntegerCaptor.getValue().intValue());
     }
 
-    @Test
-    public void create_sensor() throws Exception {
-        Sensor sensor = newSensor("sensor", Sensor.STRING_TYPE_HINGE_ANGLE);
-        when(mSensorManager.getSensorList(anyInt())).thenReturn(List.of(sensor));
-
+    private DeviceStateProviderImpl create_sensorBasedProvider(Sensor sensor) {
         String configString = "<device-state-config>\n"
                 + "    <device-state>\n"
                 + "        <identifier>1</identifier>\n"
@@ -310,14 +307,22 @@
                 + "                <name>" + sensor.getName() + "</name>\n"
                 + "                <value>\n"
                 + "                    <min-inclusive>180</min-inclusive>\n"
+                + "                    <max>" + MAX_HINGE_ANGLE_EXCLUSIVE + "</max>\n"
                 + "                </value>\n"
                 + "            </sensor>\n"
                 + "        </conditions>\n"
                 + "    </device-state>\n"
                 + "</device-state-config>\n";
         DeviceStateProviderImpl.ReadableConfig config = new TestReadableConfig(configString);
-        DeviceStateProviderImpl provider = DeviceStateProviderImpl.createFromConfig(mContext,
+        return DeviceStateProviderImpl.createFromConfig(mContext,
                 config);
+    }
+
+    @Test
+    public void create_sensor() throws Exception {
+        Sensor sensor = newSensor("sensor", Sensor.STRING_TYPE_HINGE_ANGLE);
+        when(mSensorManager.getSensorList(anyInt())).thenReturn(List.of(sensor));
+        DeviceStateProviderImpl provider = create_sensorBasedProvider(sensor);
 
         DeviceStateProvider.Listener listener = mock(DeviceStateProvider.Listener.class);
         provider.setListener(listener);
@@ -371,6 +376,40 @@
     }
 
     @Test
+    public void test_invalidSensorValues() throws Exception {
+        // onStateChanged() should not be triggered by invalid sensor values.
+
+        Sensor sensor = newSensor("sensor", Sensor.STRING_TYPE_HINGE_ANGLE);
+        when(mSensorManager.getSensorList(anyInt())).thenReturn(List.of(sensor));
+        DeviceStateProviderImpl provider = create_sensorBasedProvider(sensor);
+
+        DeviceStateProvider.Listener listener = mock(DeviceStateProvider.Listener.class);
+        provider.setListener(listener);
+        Mockito.clearInvocations(listener);
+
+        // First, switch to a non-default state.
+        SensorEvent event1 = mock(SensorEvent.class);
+        event1.sensor = sensor;
+        FieldSetter.setField(event1, event1.getClass().getField("values"), new float[]{90});
+        provider.onSensorChanged(event1);
+        verify(listener).onStateChanged(mIntegerCaptor.capture());
+        assertEquals(2, mIntegerCaptor.getValue().intValue());
+
+        Mockito.clearInvocations(listener);
+
+        // Then, send an invalid sensor event, verify that onStateChanged() is not triggered.
+        SensorEvent event2 = mock(SensorEvent.class);
+        event2.sensor = sensor;
+        FieldSetter.setField(event2, event2.getClass().getField("values"),
+                new float[]{MAX_HINGE_ANGLE_EXCLUSIVE});
+
+        provider.onSensorChanged(event2);
+
+        verify(listener, never()).onSupportedDeviceStatesChanged(mDeviceStateArrayCaptor.capture());
+        verify(listener, never()).onStateChanged(mIntegerCaptor.capture());
+    }
+
+    @Test
     public void create_invalidSensor() throws Exception {
         Sensor sensor = newSensor("sensor", Sensor.STRING_TYPE_HINGE_ANGLE);
         when(mSensorManager.getSensorList(anyInt())).thenReturn(List.of());
diff --git a/services/tests/servicestests/src/com/android/server/power/PowerManagerServiceTest.java b/services/tests/servicestests/src/com/android/server/power/PowerManagerServiceTest.java
index f5ed41a..a42d009 100644
--- a/services/tests/servicestests/src/com/android/server/power/PowerManagerServiceTest.java
+++ b/services/tests/servicestests/src/com/android/server/power/PowerManagerServiceTest.java
@@ -375,6 +375,18 @@
         mBatteryReceiver.onReceive(mContextSpy, new Intent(Intent.ACTION_BATTERY_CHANGED));
     }
 
+    private void setBatteryLevel(int batteryLevel) {
+        when(mBatteryManagerInternalMock.getBatteryLevel())
+                .thenReturn(batteryLevel);
+        mBatteryReceiver.onReceive(mContextSpy, new Intent(Intent.ACTION_BATTERY_CHANGED));
+    }
+
+    private void setBatteryHealth(int batteryHealth) {
+        when(mBatteryManagerInternalMock.getBatteryHealth())
+                .thenReturn(batteryHealth);
+        mBatteryReceiver.onReceive(mContextSpy, new Intent(Intent.ACTION_BATTERY_CHANGED));
+    }
+
     private void setAttentiveTimeout(int attentiveTimeoutMillis) {
         Settings.Secure.putInt(
                 mContextSpy.getContentResolver(), Settings.Secure.ATTENTIVE_TIMEOUT,
@@ -399,6 +411,12 @@
                 .thenReturn(disable);
     }
 
+    private void setDreamsBatteryLevelDrainConfig(int threshold) {
+        when(mResourcesSpy.getInteger(
+                com.android.internal.R.integer.config_dreamsBatteryLevelDrainCutoff)).thenReturn(
+                threshold);
+    }
+
     private void advanceTime(long timeMs) {
         mClock.fastForward(timeMs);
         mTestLooper.dispatchAll();
@@ -619,17 +637,42 @@
     }
 
     /**
-     * Tests that dreaming continues when undocking and configured to do so.
+     * Tests that dreaming stops when undocking and not configured to keep dreaming.
      */
     @Test
-    public void testWakefulnessDream_shouldKeepDreamingWhenUndocked() {
+    public void testWakefulnessDream_shouldStopDreamingWhenUndocked_whenNotConfigured() {
+        // Make sure "unplug turns on screen" is configured to true.
+        when(mResourcesSpy.getBoolean(com.android.internal.R.bool.config_unplugTurnsOnScreen))
+                .thenReturn(true);
+        when(mDreamManagerInternalMock.keepDreamingWhenUndockedDefault()).thenReturn(false);
+
         createService();
         startSystem();
 
-        when(mResourcesSpy.getBoolean(
-                com.android.internal.R.bool.config_keepDreamingWhenUndocking))
+        when(mBatteryManagerInternalMock.getPlugType())
+                .thenReturn(BatteryManager.BATTERY_PLUGGED_DOCK);
+        setPluggedIn(true);
+
+        forceAwake();  // Needs to be awake first before it can dream.
+        forceDream();
+        when(mBatteryManagerInternalMock.getPlugType()).thenReturn(0);
+        setPluggedIn(false);
+
+        assertThat(mService.getGlobalWakefulnessLocked()).isEqualTo(WAKEFULNESS_AWAKE);
+    }
+
+    /**
+     * Tests that dreaming continues when undocking and configured to do so.
+     */
+    @Test
+    public void testWakefulnessDream_shouldKeepDreamingWhenUndocked_whenConfigured() {
+        // Make sure "unplug turns on screen" is configured to true.
+        when(mResourcesSpy.getBoolean(com.android.internal.R.bool.config_unplugTurnsOnScreen))
                 .thenReturn(true);
-        mService.readConfigurationLocked();
+        when(mDreamManagerInternalMock.keepDreamingWhenUndockedDefault()).thenReturn(true);
+
+        createService();
+        startSystem();
 
         when(mBatteryManagerInternalMock.getPlugType())
                 .thenReturn(BatteryManager.BATTERY_PLUGGED_DOCK);
@@ -643,6 +686,37 @@
         assertThat(mService.getGlobalWakefulnessLocked()).isEqualTo(WAKEFULNESS_DREAMING);
     }
 
+    /**
+     * Tests that dreaming stops when undocking while showing a dream that prevents it.
+     */
+    @Test
+    public void testWakefulnessDream_shouldStopDreamingWhenUndocked_whenDreamPrevents() {
+        // Make sure "unplug turns on screen" is configured to true.
+        when(mResourcesSpy.getBoolean(com.android.internal.R.bool.config_unplugTurnsOnScreen))
+                .thenReturn(true);
+        when(mDreamManagerInternalMock.keepDreamingWhenUndockedDefault()).thenReturn(true);
+
+        createService();
+        startSystem();
+
+        ArgumentCaptor<DreamManagerInternal.DreamManagerStateListener> dreamManagerStateListener =
+                ArgumentCaptor.forClass(DreamManagerInternal.DreamManagerStateListener.class);
+        verify(mDreamManagerInternalMock).registerDreamManagerStateListener(
+                dreamManagerStateListener.capture());
+
+        when(mBatteryManagerInternalMock.getPlugType())
+                .thenReturn(BatteryManager.BATTERY_PLUGGED_DOCK);
+        setPluggedIn(true);
+
+        forceAwake();  // Needs to be awake first before it can dream.
+        forceDream();
+        dreamManagerStateListener.getValue().onKeepDreamingWhenUndockedChanged(false);
+        when(mBatteryManagerInternalMock.getPlugType()).thenReturn(0);
+        setPluggedIn(false);
+
+        assertThat(mService.getGlobalWakefulnessLocked()).isEqualTo(WAKEFULNESS_AWAKE);
+    }
+
     @Test
     public void testWakefulnessDoze_goToSleep() {
         createService();
@@ -882,6 +956,41 @@
     }
 
     @Test
+    public void testBatteryDrainDuringDream() {
+        Settings.Secure.putInt(mContextSpy.getContentResolver(),
+                Settings.Secure.SCREENSAVER_ACTIVATE_ON_SLEEP, 1);
+        Settings.Secure.putInt(mContextSpy.getContentResolver(),
+                Settings.Secure.SCREENSAVER_ENABLED, 1);
+
+        setMinimumScreenOffTimeoutConfig(100);
+        setDreamsBatteryLevelDrainConfig(5);
+        createService();
+        startSystem();
+
+        doAnswer(inv -> {
+            when(mDreamManagerInternalMock.isDreaming()).thenReturn(true);
+            return null;
+        }).when(mDreamManagerInternalMock).startDream(anyBoolean(), anyString());
+
+        setBatteryLevel(100);
+        setPluggedIn(true);
+
+        forceAwake();  // Needs to be awake first before it can dream.
+        forceDream();
+        advanceTime(10); // Allow async calls to happen
+        assertThat(mService.getGlobalWakefulnessLocked()).isEqualTo(WAKEFULNESS_DREAMING);
+        setBatteryLevel(90);
+        advanceTime(10); // Allow async calls to happen
+        assertThat(mService.getDreamsBatteryLevelDrain()).isEqualTo(10);
+
+        // If battery overheat protection is enabled, we shouldn't count battery drain
+        setBatteryHealth(BatteryManager.BATTERY_HEALTH_OVERHEAT);
+        setBatteryLevel(70);
+        advanceTime(10); // Allow async calls to happen
+        assertThat(mService.getDreamsBatteryLevelDrain()).isEqualTo(10);
+    }
+
+    @Test
     public void testSetDozeOverrideFromDreamManager_triggersSuspendBlocker() {
         final String suspendBlockerName = "PowerManagerService.Display";
         final String tag = "acq_causes_wakeup";
diff --git a/services/tests/wmtests/src/com/android/server/wm/ActivityRecordTests.java b/services/tests/wmtests/src/com/android/server/wm/ActivityRecordTests.java
index ff5622f..d3e638f 100644
--- a/services/tests/wmtests/src/com/android/server/wm/ActivityRecordTests.java
+++ b/services/tests/wmtests/src/com/android/server/wm/ActivityRecordTests.java
@@ -3091,6 +3091,17 @@
         assertTrue(activity.mVisibleRequested);
         assertTrue(activity.mDisplayContent.mOpeningApps.contains(activity));
         assertFalse(activity.mDisplayContent.mClosingApps.contains(activity));
+
+        // There should still be animation (add to opening) if keyguard is going away while the
+        // screen is off because it will be visible after screen is turned on by unlocking.
+        mDisplayContent.mOpeningApps.remove(activity);
+        mDisplayContent.mClosingApps.remove(activity);
+        activity.commitVisibility(false /* visible */, false /* performLayout */);
+        mDisplayContent.getDisplayPolicy().screenTurnedOff();
+        final KeyguardController controller = mSupervisor.getKeyguardController();
+        doReturn(true).when(controller).isKeyguardGoingAway(anyInt());
+        activity.setVisibility(true);
+        assertTrue(mDisplayContent.mOpeningApps.contains(activity));
     }
 
     @Test
diff --git a/services/tests/wmtests/src/com/android/server/wm/AppTransitionControllerTest.java b/services/tests/wmtests/src/com/android/server/wm/AppTransitionControllerTest.java
index 0332c4b..43e79f9 100644
--- a/services/tests/wmtests/src/com/android/server/wm/AppTransitionControllerTest.java
+++ b/services/tests/wmtests/src/com/android/server/wm/AppTransitionControllerTest.java
@@ -25,6 +25,7 @@
 import static android.view.WindowManager.LayoutParams.TYPE_BASE_APPLICATION;
 import static android.view.WindowManager.TRANSIT_CHANGE;
 import static android.view.WindowManager.TRANSIT_CLOSE;
+import static android.view.WindowManager.TRANSIT_OLD_ACTIVITY_CLOSE;
 import static android.view.WindowManager.TRANSIT_OLD_ACTIVITY_OPEN;
 import static android.view.WindowManager.TRANSIT_OLD_DREAM_ACTIVITY_CLOSE;
 import static android.view.WindowManager.TRANSIT_OLD_DREAM_ACTIVITY_OPEN;
@@ -56,6 +57,7 @@
 import static org.mockito.Mockito.verify;
 
 import android.annotation.Nullable;
+import android.graphics.Rect;
 import android.gui.DropInputMode;
 import android.os.Binder;
 import android.os.IBinder;
@@ -918,7 +920,7 @@
         final Task task = createTask(mDisplayContent);
         final TaskFragmentOrganizer organizer = new TaskFragmentOrganizer(Runnable::run);
         final TestRemoteAnimationRunner remoteAnimationRunner = new TestRemoteAnimationRunner();
-        setupTaskFragmentRemoteAnimation(organizer, task.mTaskId, remoteAnimationRunner);
+        setupTaskFragmentRemoteAnimation(organizer, remoteAnimationRunner);
 
         // Create a TaskFragment with embedded activity.
         final TaskFragment taskFragment = createTaskFragmentWithEmbeddedActivity(task, organizer);
@@ -935,11 +937,77 @@
     }
 
     @Test
+    public void testOverrideTaskFragmentAdapter_noOverrideWithOnlyTaskFragmentFillingTask() {
+        final Task task = createTask(mDisplayContent);
+        final ActivityRecord closingActivity = createActivityRecord(task);
+        final TaskFragmentOrganizer organizer = new TaskFragmentOrganizer(Runnable::run);
+        final TestRemoteAnimationRunner remoteAnimationRunner = new TestRemoteAnimationRunner();
+        setupTaskFragmentRemoteAnimation(organizer, remoteAnimationRunner);
+
+        // Create a TaskFragment with embedded activity.
+        final TaskFragment taskFragment = createTaskFragmentWithEmbeddedActivity(task, organizer);
+
+        // Make sure the TaskFragment is not embedded.
+        assertFalse(taskFragment.isEmbeddedWithBoundsOverride());
+        final ActivityRecord openingActivity = taskFragment.getTopMostActivity();
+        prepareActivityForAppTransition(closingActivity);
+        prepareActivityForAppTransition(openingActivity);
+        final int uid = 12345;
+        closingActivity.info.applicationInfo.uid = uid;
+        openingActivity.info.applicationInfo.uid = uid;
+        task.effectiveUid = uid;
+        spyOn(mDisplayContent.mAppTransition);
+
+        // Prepare and start transition.
+        prepareAndTriggerAppTransition(openingActivity, closingActivity,
+                null /* changingTaskFragment */);
+        mWm.mAnimator.executeAfterPrepareSurfacesRunnables();
+
+        // Animation is not run by the remote handler because the activity is filling the Task.
+        assertFalse(remoteAnimationRunner.isAnimationStarted());
+    }
+
+    @Test
+    public void testOverrideTaskFragmentAdapter_overrideWithTaskFragmentNotFillingTask() {
+        final Task task = createTask(mDisplayContent);
+        final ActivityRecord closingActivity = createActivityRecord(task);
+        final TaskFragmentOrganizer organizer = new TaskFragmentOrganizer(Runnable::run);
+        final TestRemoteAnimationRunner remoteAnimationRunner = new TestRemoteAnimationRunner();
+        setupTaskFragmentRemoteAnimation(organizer, remoteAnimationRunner);
+
+        // Create a TaskFragment with embedded activity.
+        final TaskFragment taskFragment = createTaskFragmentWithEmbeddedActivity(task, organizer);
+
+        // Make sure the TaskFragment is embedded.
+        taskFragment.setWindowingMode(WINDOWING_MODE_MULTI_WINDOW);
+        final Rect embeddedBounds = new Rect(task.getBounds());
+        embeddedBounds.right = embeddedBounds.left + embeddedBounds.width() / 2;
+        taskFragment.setBounds(embeddedBounds);
+        assertTrue(taskFragment.isEmbeddedWithBoundsOverride());
+        final ActivityRecord openingActivity = taskFragment.getTopMostActivity();
+        prepareActivityForAppTransition(closingActivity);
+        prepareActivityForAppTransition(openingActivity);
+        final int uid = 12345;
+        closingActivity.info.applicationInfo.uid = uid;
+        openingActivity.info.applicationInfo.uid = uid;
+        task.effectiveUid = uid;
+        spyOn(mDisplayContent.mAppTransition);
+
+        // Prepare and start transition.
+        prepareAndTriggerAppTransition(openingActivity, closingActivity,
+                null /* changingTaskFragment */);
+        mWm.mAnimator.executeAfterPrepareSurfacesRunnables();
+
+        // Animation run by the remote handler.
+        assertTrue(remoteAnimationRunner.isAnimationStarted());
+    }
+
+    @Test
     public void testOverrideTaskFragmentAdapter_overrideWithNonEmbeddedActivity() {
         final Task task = createTask(mDisplayContent);
         final TaskFragmentOrganizer organizer = new TaskFragmentOrganizer(Runnable::run);
         final TestRemoteAnimationRunner remoteAnimationRunner = new TestRemoteAnimationRunner();
-        setupTaskFragmentRemoteAnimation(organizer, task.mTaskId, remoteAnimationRunner);
+        setupTaskFragmentRemoteAnimation(organizer, remoteAnimationRunner);
 
         // Closing non-embedded activity.
         final ActivityRecord closingActivity = createActivityRecord(task);
@@ -964,7 +1032,7 @@
         final Task task = createTask(mDisplayContent);
         final TaskFragmentOrganizer organizer = new TaskFragmentOrganizer(Runnable::run);
         final TestRemoteAnimationRunner remoteAnimationRunner = new TestRemoteAnimationRunner();
-        setupTaskFragmentRemoteAnimation(organizer, task.mTaskId, remoteAnimationRunner);
+        setupTaskFragmentRemoteAnimation(organizer, remoteAnimationRunner);
 
         // Closing TaskFragment with embedded activity.
         final TaskFragment taskFragment1 = createTaskFragmentWithEmbeddedActivity(task, organizer);
@@ -991,7 +1059,7 @@
         final Task task = createTask(mDisplayContent);
         final TaskFragmentOrganizer organizer = new TaskFragmentOrganizer(Runnable::run);
         final TestRemoteAnimationRunner remoteAnimationRunner = new TestRemoteAnimationRunner();
-        setupTaskFragmentRemoteAnimation(organizer, task.mTaskId, remoteAnimationRunner);
+        setupTaskFragmentRemoteAnimation(organizer, remoteAnimationRunner);
 
         // Closing activity in Task1.
         final ActivityRecord closingActivity = createActivityRecord(mDisplayContent);
@@ -1015,7 +1083,7 @@
         final Task task = createTask(mDisplayContent);
         final TaskFragmentOrganizer organizer = new TaskFragmentOrganizer(Runnable::run);
         final TestRemoteAnimationRunner remoteAnimationRunner = new TestRemoteAnimationRunner();
-        setupTaskFragmentRemoteAnimation(organizer, task.mTaskId, remoteAnimationRunner);
+        setupTaskFragmentRemoteAnimation(organizer, remoteAnimationRunner);
 
         // Closing TaskFragment with embedded activity.
         final TaskFragment taskFragment = createTaskFragmentWithEmbeddedActivity(task, organizer);
@@ -1043,7 +1111,7 @@
         final Task task = createTask(mDisplayContent);
         final TaskFragmentOrganizer organizer = new TaskFragmentOrganizer(Runnable::run);
         final TestRemoteAnimationRunner remoteAnimationRunner = new TestRemoteAnimationRunner();
-        setupTaskFragmentRemoteAnimation(organizer, task.mTaskId, remoteAnimationRunner);
+        setupTaskFragmentRemoteAnimation(organizer, remoteAnimationRunner);
 
         // Create a TaskFragment with embedded activity.
         final TaskFragment taskFragment = createTaskFragmentWithEmbeddedActivity(task, organizer);
@@ -1069,7 +1137,7 @@
         final Task task = createTask(mDisplayContent);
         final TaskFragmentOrganizer organizer = new TaskFragmentOrganizer(Runnable::run);
         final TestRemoteAnimationRunner remoteAnimationRunner = new TestRemoteAnimationRunner();
-        setupTaskFragmentRemoteAnimation(organizer, task.mTaskId, remoteAnimationRunner);
+        setupTaskFragmentRemoteAnimation(organizer, remoteAnimationRunner);
 
         // Create a TaskFragment with embedded activities, one is trusted embedded, and the other
         // one is untrusted embedded.
@@ -1128,7 +1196,7 @@
         final Task task = createTask(mDisplayContent);
         final TaskFragmentOrganizer organizer = new TaskFragmentOrganizer(Runnable::run);
         final TestRemoteAnimationRunner remoteAnimationRunner = new TestRemoteAnimationRunner();
-        setupTaskFragmentRemoteAnimation(organizer, task.mTaskId, remoteAnimationRunner);
+        setupTaskFragmentRemoteAnimation(organizer, remoteAnimationRunner);
 
         // Create a TaskFragment with only trusted embedded activity
         final TaskFragment taskFragment = new TaskFragmentBuilder(mAtm)
@@ -1168,7 +1236,7 @@
         final Task task = createTask(mDisplayContent);
         final TaskFragmentOrganizer organizer = new TaskFragmentOrganizer(Runnable::run);
         final TestRemoteAnimationRunner remoteAnimationRunner = new TestRemoteAnimationRunner();
-        setupTaskFragmentRemoteAnimation(organizer, task.mTaskId, remoteAnimationRunner);
+        setupTaskFragmentRemoteAnimation(organizer, remoteAnimationRunner);
 
         // Create a TaskFragment with only trusted embedded activity
         final TaskFragment taskFragment = new TaskFragmentBuilder(mAtm)
@@ -1259,7 +1327,7 @@
     }
 
     /** Registers remote animation for the organizer. */
-    private void setupTaskFragmentRemoteAnimation(TaskFragmentOrganizer organizer, int taskId,
+    private void setupTaskFragmentRemoteAnimation(TaskFragmentOrganizer organizer,
             TestRemoteAnimationRunner remoteAnimationRunner) {
         final RemoteAnimationAdapter adapter = new RemoteAnimationAdapter(
                 remoteAnimationRunner, 10, 1);
@@ -1268,9 +1336,10 @@
         definition.addRemoteAnimation(TRANSIT_OLD_TASK_FRAGMENT_CHANGE, adapter);
         definition.addRemoteAnimation(TRANSIT_OLD_TASK_FRAGMENT_OPEN, adapter);
         definition.addRemoteAnimation(TRANSIT_OLD_TASK_FRAGMENT_CLOSE, adapter);
+        definition.addRemoteAnimation(TRANSIT_OLD_ACTIVITY_OPEN, adapter);
+        definition.addRemoteAnimation(TRANSIT_OLD_ACTIVITY_CLOSE, adapter);
         mAtm.mTaskFragmentOrganizerController.registerOrganizer(iOrganizer);
-        mAtm.mTaskFragmentOrganizerController.registerRemoteAnimations(iOrganizer, taskId,
-                definition);
+        mAtm.mTaskFragmentOrganizerController.registerRemoteAnimations(iOrganizer, definition);
     }
 
     private static ITaskFragmentOrganizer getITaskFragmentOrganizer(
diff --git a/services/tests/wmtests/src/com/android/server/wm/AppTransitionTests.java b/services/tests/wmtests/src/com/android/server/wm/AppTransitionTests.java
index 32c95fa..8cfe503 100644
--- a/services/tests/wmtests/src/com/android/server/wm/AppTransitionTests.java
+++ b/services/tests/wmtests/src/com/android/server/wm/AppTransitionTests.java
@@ -49,6 +49,7 @@
 import static org.junit.Assert.assertFalse;
 import static org.junit.Assert.assertNull;
 import static org.junit.Assert.assertTrue;
+import static org.junit.Assume.assumeFalse;
 import static org.mockito.ArgumentMatchers.any;
 import static org.mockito.ArgumentMatchers.anyBoolean;
 import static org.mockito.Mockito.mock;
@@ -67,11 +68,14 @@
 import android.view.RemoteAnimationTarget;
 import android.view.SurfaceControl;
 import android.view.WindowManager;
+import android.view.animation.Animation;
 import android.window.ITaskFragmentOrganizer;
 import android.window.TaskFragmentOrganizer;
 
 import androidx.test.filters.SmallTest;
 
+import com.android.internal.policy.TransitionAnimation;
+
 import org.junit.Before;
 import org.junit.Test;
 import org.junit.runner.RunWith;
@@ -495,6 +499,80 @@
         assertEquals(startBounds, taskFragment.mSurfaceFreezer.mFreezeBounds);
     }
 
+    @Test
+    public void testGetNextAppTransitionBackgroundColor() {
+        assumeFalse(WindowManagerService.sEnableShellTransitions);
+
+        // No override by default.
+        assertEquals(0, mDc.mAppTransition.getNextAppTransitionBackgroundColor());
+
+        // Override with a custom color.
+        mDc.mAppTransition.prepareAppTransition(TRANSIT_OPEN, 0);
+        final int testColor = 123;
+        mDc.mAppTransition.overridePendingAppTransition("testPackage", 0 /* enterAnim */,
+                0 /* exitAnim */, testColor, null /* startedCallback */, null /* endedCallback */,
+                false /* overrideTaskTransaction */);
+
+        assertEquals(testColor, mDc.mAppTransition.getNextAppTransitionBackgroundColor());
+        assertTrue(mDc.mAppTransition.isNextAppTransitionOverrideRequested());
+
+        // Override with ActivityEmbedding remote animation. Background color should be kept.
+        mDc.mAppTransition.overridePendingAppTransitionRemote(mock(RemoteAnimationAdapter.class),
+                false /* sync */, true /* isActivityEmbedding */);
+
+        assertEquals(testColor, mDc.mAppTransition.getNextAppTransitionBackgroundColor());
+        assertFalse(mDc.mAppTransition.isNextAppTransitionOverrideRequested());
+
+        // Background color should not be cleared anymore after #clear().
+        mDc.mAppTransition.clear();
+        assertEquals(0, mDc.mAppTransition.getNextAppTransitionBackgroundColor());
+        assertFalse(mDc.mAppTransition.isNextAppTransitionOverrideRequested());
+    }
+
+    @Test
+    public void testGetNextAppRequestedAnimation() {
+        assumeFalse(WindowManagerService.sEnableShellTransitions);
+        final String packageName = "testPackage";
+        final int enterAnimResId = 1;
+        final int exitAnimResId = 2;
+        final int testColor = 123;
+        final Animation enterAnim = mock(Animation.class);
+        final Animation exitAnim = mock(Animation.class);
+        final TransitionAnimation transitionAnimation = mDc.mAppTransition.mTransitionAnimation;
+        spyOn(transitionAnimation);
+        doReturn(enterAnim).when(transitionAnimation)
+                .loadAppTransitionAnimation(packageName, enterAnimResId);
+        doReturn(exitAnim).when(transitionAnimation)
+                .loadAppTransitionAnimation(packageName, exitAnimResId);
+
+        // No override by default.
+        assertNull(mDc.mAppTransition.getNextAppRequestedAnimation(true /* enter */));
+        assertNull(mDc.mAppTransition.getNextAppRequestedAnimation(false /* enter */));
+
+        // Override with a custom animation.
+        mDc.mAppTransition.prepareAppTransition(TRANSIT_OPEN, 0);
+        mDc.mAppTransition.overridePendingAppTransition(packageName, enterAnimResId, exitAnimResId,
+                testColor, null /* startedCallback */, null /* endedCallback */,
+                false /* overrideTaskTransaction */);
+
+        assertEquals(enterAnim, mDc.mAppTransition.getNextAppRequestedAnimation(true /* enter */));
+        assertEquals(exitAnim, mDc.mAppTransition.getNextAppRequestedAnimation(false /* enter */));
+        assertTrue(mDc.mAppTransition.isNextAppTransitionOverrideRequested());
+
+        // Override with ActivityEmbedding remote animation. Custom animation should be kept.
+        mDc.mAppTransition.overridePendingAppTransitionRemote(mock(RemoteAnimationAdapter.class),
+                false /* sync */, true /* isActivityEmbedding */);
+
+        assertEquals(enterAnim, mDc.mAppTransition.getNextAppRequestedAnimation(true /* enter */));
+        assertEquals(exitAnim, mDc.mAppTransition.getNextAppRequestedAnimation(false /* enter */));
+        assertFalse(mDc.mAppTransition.isNextAppTransitionOverrideRequested());
+
+        // Custom animation should not be cleared anymore after #clear().
+        mDc.mAppTransition.clear();
+        assertNull(mDc.mAppTransition.getNextAppRequestedAnimation(true /* enter */));
+        assertNull(mDc.mAppTransition.getNextAppRequestedAnimation(false /* enter */));
+    }
+
     private class TestRemoteAnimationRunner implements IRemoteAnimationRunner {
         boolean mCancelled = false;
         @Override
diff --git a/services/tests/wmtests/src/com/android/server/wm/TaskFragmentOrganizerControllerTest.java b/services/tests/wmtests/src/com/android/server/wm/TaskFragmentOrganizerControllerTest.java
index c535182..2b49314 100644
--- a/services/tests/wmtests/src/com/android/server/wm/TaskFragmentOrganizerControllerTest.java
+++ b/services/tests/wmtests/src/com/android/server/wm/TaskFragmentOrganizerControllerTest.java
@@ -474,13 +474,13 @@
 
     @Test
     public void testRegisterRemoteAnimations() {
-        mController.registerRemoteAnimations(mIOrganizer, TASK_ID, mDefinition);
+        mController.registerRemoteAnimations(mIOrganizer, mDefinition);
 
-        assertEquals(mDefinition, mController.getRemoteAnimationDefinition(mIOrganizer, TASK_ID));
+        assertEquals(mDefinition, mController.getRemoteAnimationDefinition(mIOrganizer));
 
-        mController.unregisterRemoteAnimations(mIOrganizer, TASK_ID);
+        mController.unregisterRemoteAnimations(mIOrganizer);
 
-        assertNull(mController.getRemoteAnimationDefinition(mIOrganizer, TASK_ID));
+        assertNull(mController.getRemoteAnimationDefinition(mIOrganizer));
     }
 
     @Test
@@ -822,6 +822,21 @@
     }
 
     @Test
+    public void testOnTransactionHandled_skipTransactionForUnregisterOrganizer() {
+        mController.unregisterOrganizer(mIOrganizer);
+        final ActivityRecord ownerActivity = createActivityRecord(mDisplayContent);
+        final IBinder fragmentToken = new Binder();
+
+        // Allow organizer to create TaskFragment and start/reparent activity to TaskFragment.
+        createTaskFragmentFromOrganizer(mTransaction, ownerActivity, fragmentToken);
+        mController.onTransactionHandled(new Binder(), mTransaction,
+                getTransitionType(mTransaction), false /* shouldApplyIndependently */);
+
+        // Nothing should happen as the organizer is not registered.
+        assertNull(mWindowOrganizerController.getTaskFragment(fragmentToken));
+    }
+
+    @Test
     public void testOrganizerRemovedWithPendingEvents() {
         final TaskFragment tf0 = new TaskFragmentBuilder(mAtm)
                 .setCreateParentTask()
diff --git a/services/tests/wmtests/src/com/android/server/wm/TaskLaunchParamsModifierTests.java b/services/tests/wmtests/src/com/android/server/wm/TaskLaunchParamsModifierTests.java
index a6c5fd8..1188f49 100644
--- a/services/tests/wmtests/src/com/android/server/wm/TaskLaunchParamsModifierTests.java
+++ b/services/tests/wmtests/src/com/android/server/wm/TaskLaunchParamsModifierTests.java
@@ -85,6 +85,11 @@
     private static final Rect DISPLAY_STABLE_BOUNDS = new Rect(/* left */ 100,
             /* top */ 200, /* right */ 1620, /* bottom */ 680);
 
+    private static final Rect SMALL_DISPLAY_BOUNDS = new Rect(/* left */ 0, /* top */ 0,
+            /* right */ 1000, /* bottom */ 500);
+    private static final Rect SMALL_DISPLAY_STABLE_BOUNDS = new Rect(/* left */ 100,
+            /* top */ 50, /* right */ 900, /* bottom */ 450);
+
     private ActivityRecord mActivity;
 
     private TaskLaunchParamsModifier mTarget;
@@ -571,6 +576,29 @@
     }
 
     @Test
+    public void testBoundsInOptionsInfersFullscreenWithBoundsOnFreeformSupportFullscreenDisplay() {
+        final TestDisplayContent fullscreenDisplay = createNewDisplayContent(
+                WINDOWING_MODE_FULLSCREEN);
+        mAtm.mTaskSupervisor.mService.mSupportsFreeformWindowManagement = true;
+
+        final ActivityOptions options = ActivityOptions.makeBasic();
+        final Rect expectedBounds = new Rect(0, 0, 100, 100);
+        options.setLaunchBounds(expectedBounds);
+
+        mCurrent.mPreferredTaskDisplayArea = fullscreenDisplay.getDefaultTaskDisplayArea();
+
+        assertEquals(RESULT_CONTINUE,
+                new CalculateRequestBuilder().setOptions(options).calculate());
+
+        // Setting bounds shouldn't lead to freeform windowing mode on fullscreen display by
+        // default (even with freeform support), but we need to check here if the bounds is set even
+        // with fullscreen windowing mode in case it's restored later.
+        assertEquivalentWindowingMode(WINDOWING_MODE_FULLSCREEN, mResult.mWindowingMode,
+                WINDOWING_MODE_FULLSCREEN);
+        assertEquals(expectedBounds, mResult.mBounds);
+    }
+
+    @Test
     public void testInheritsFreeformModeFromSourceOnFullscreenDisplay() {
         final TestDisplayContent fullscreenDisplay = createNewDisplayContent(
                 WINDOWING_MODE_FULLSCREEN);
@@ -952,6 +980,8 @@
                 WINDOWING_MODE_FULLSCREEN);
         final ActivityRecord source = createSourceActivity(fullscreenDisplay);
         source.getTask().setWindowingMode(WINDOWING_MODE_FREEFORM);
+        // Set some bounds to avoid conflict with the other activity.
+        source.setBounds(100, 100, 200, 200);
 
         final ActivityOptions options = ActivityOptions.makeBasic();
         final Rect expected = new Rect(0, 0, 150, 150);
@@ -1321,6 +1351,20 @@
     }
 
     @Test
+    public void testDefaultFreeformSizeShrinksOnSmallDisplay() {
+        final TestDisplayContent freeformDisplay = createNewDisplayContent(
+                WINDOWING_MODE_FREEFORM, SMALL_DISPLAY_BOUNDS, SMALL_DISPLAY_STABLE_BOUNDS);
+
+        final ActivityOptions options = ActivityOptions.makeBasic();
+        options.setLaunchDisplayId(freeformDisplay.mDisplayId);
+
+        assertEquals(RESULT_CONTINUE, new CalculateRequestBuilder().setOptions(options)
+                .calculate());
+
+        assertEquals(new Rect(414, 77, 587, 423), mResult.mBounds);
+    }
+
+    @Test
     public void testDefaultFreeformSizeRespectsMinAspectRatio() {
         final TestDisplayContent freeformDisplay = createNewDisplayContent(
                 WINDOWING_MODE_FREEFORM);
@@ -1510,16 +1554,15 @@
         options.setLaunchDisplayId(freeformDisplay.mDisplayId);
 
         mCurrent.mWindowingMode = WINDOWING_MODE_FREEFORM;
-        mCurrent.mBounds.set(100, 300, 1820, 1380);
+        mCurrent.mBounds.set(0, 0, 3000, 2000);
 
         mActivity.info.applicationInfo.targetSdkVersion = Build.VERSION_CODES.LOLLIPOP;
 
         assertEquals(RESULT_CONTINUE,
                 new CalculateRequestBuilder().setOptions(options).calculate());
 
-        assertTrue("Result bounds should start from app bounds's origin, but it's "
-                        + mResult.mBounds,
-                mResult.mBounds.left == 100 && mResult.mBounds.top == 200);
+        // Must shrink to fit the display while reserving aspect ratio.
+        assertEquals(new Rect(127, 227, 766, 653), mResult.mBounds);
     }
 
     @Test
@@ -1535,18 +1578,19 @@
 
         final ActivityOptions options = ActivityOptions.makeBasic();
         options.setLaunchDisplayId(freeformDisplay.mDisplayId);
+        final ActivityInfo.WindowLayout layout = new WindowLayoutBuilder()
+                .setMinWidth(500).setMinHeight(500).build();
 
         mCurrent.mWindowingMode = WINDOWING_MODE_FREEFORM;
-        mCurrent.mBounds.set(100, 300, 1820, 1380);
+        mCurrent.mBounds.set(0, 0, 2000, 3000);
 
         mActivity.info.applicationInfo.targetSdkVersion = Build.VERSION_CODES.LOLLIPOP;
 
         assertEquals(RESULT_CONTINUE,
-                new CalculateRequestBuilder().setOptions(options).calculate());
+                new CalculateRequestBuilder().setOptions(options).setLayout(layout).calculate());
 
-        assertTrue("Result bounds should start from top-right corner of app bounds, but "
-                        + "it's " + mResult.mBounds,
-                mResult.mBounds.left == -100 && mResult.mBounds.top == 200);
+        // Must shrink to fit the display while reserving aspect ratio.
+        assertEquals(new Rect(1093, 227, 1593, 727), mResult.mBounds);
     }
 
     @Test
@@ -1721,7 +1765,7 @@
         assertEquals(RESULT_CONTINUE,
                 new CalculateRequestBuilder().setOptions(options).calculate());
 
-        assertEquals(new Rect(100, 200, 400, 500), mResult.mBounds);
+        assertEquals(new Rect(127, 227, 427, 527), mResult.mBounds);
     }
 
     @Test
@@ -1774,13 +1818,18 @@
     }
 
     private TestDisplayContent createNewDisplayContent(int windowingMode) {
+        return createNewDisplayContent(windowingMode, DISPLAY_BOUNDS, DISPLAY_STABLE_BOUNDS);
+    }
+
+    private TestDisplayContent createNewDisplayContent(int windowingMode, Rect displayBounds,
+                                                       Rect displayStableBounds) {
         final TestDisplayContent display = addNewDisplayContentAt(DisplayContent.POSITION_TOP);
         display.getDefaultTaskDisplayArea().setWindowingMode(windowingMode);
-        display.setBounds(DISPLAY_BOUNDS);
+        display.setBounds(displayBounds);
         display.getConfiguration().densityDpi = DENSITY_DEFAULT;
         display.getConfiguration().orientation = ORIENTATION_LANDSCAPE;
         configInsetsState(display.getInsetsStateController().getRawInsetsState(), display,
-                DISPLAY_STABLE_BOUNDS);
+                displayStableBounds);
         return display;
     }