Merge "Add the screen when there's no create option for the default provider but there's remoteEntry"
diff --git a/core/java/android/os/ServiceManager.java b/core/java/android/os/ServiceManager.java
index 9ea4278..394927e 100644
--- a/core/java/android/os/ServiceManager.java
+++ b/core/java/android/os/ServiceManager.java
@@ -252,10 +252,12 @@
     }
 
     /**
-     * Returns the list of declared instances for an interface.
+     * Returns an array of all declared instances for a particular interface.
      *
-     * @return true if the service is declared somewhere (eg. VINTF manifest) and
-     * waitForService should always be able to return the service.
+     * For instance, if 'android.foo.IFoo/foo' is declared (e.g. in VINTF
+     * manifest), and 'android.foo.IFoo' is passed here, then ["foo"] would be
+     * returned.
+     *
      * @hide
      */
     @SystemApi(client = SystemApi.Client.MODULE_LIBRARIES)
diff --git a/core/java/android/util/IntArray.java b/core/java/android/util/IntArray.java
index 7b28b8a..bc0e35d 100644
--- a/core/java/android/util/IntArray.java
+++ b/core/java/android/util/IntArray.java
@@ -234,4 +234,23 @@
     public int[] toArray() {
         return Arrays.copyOf(mValues, mSize);
     }
+
+    @Override
+    public String toString() {
+        // Code below is copied from Arrays.toString(), but uses mSize in the lopp (it cannot call
+        // Arrays.toString() directly as it would return the unused elements as well)
+        int iMax = mSize - 1;
+        if (iMax == -1) {
+            return "[]";
+        }
+        StringBuilder b = new StringBuilder();
+        b.append('[');
+        for (int i = 0;; i++) {
+            b.append(mValues[i]);
+            if (i == iMax) {
+                return b.append(']').toString();
+            }
+            b.append(", ");
+        }
+    }
 }
diff --git a/core/java/android/view/SurfaceView.java b/core/java/android/view/SurfaceView.java
index 57b2d39..33ea92d 100644
--- a/core/java/android/view/SurfaceView.java
+++ b/core/java/android/view/SurfaceView.java
@@ -947,108 +947,112 @@
                     + " left=" + (mWindowSpaceLeft != mLocation[0])
                     + " top=" + (mWindowSpaceTop != mLocation[1]));
 
-            mVisible = mRequestedVisible;
-            mWindowSpaceLeft = mLocation[0];
-            mWindowSpaceTop = mLocation[1];
-            mSurfaceWidth = myWidth;
-            mSurfaceHeight = myHeight;
-            mFormat = mRequestedFormat;
-            mAlpha = alpha;
-            mLastWindowVisibility = mWindowVisibility;
-            mTransformHint = viewRoot.getBufferTransformHint();
-            mSubLayer = mRequestedSubLayer;
-
-            mScreenRect.left = mWindowSpaceLeft;
-            mScreenRect.top = mWindowSpaceTop;
-            mScreenRect.right = mWindowSpaceLeft + getWidth();
-            mScreenRect.bottom = mWindowSpaceTop + getHeight();
-            if (translator != null) {
-                translator.translateRectInAppWindowToScreen(mScreenRect);
-            }
-
-            final Rect surfaceInsets = viewRoot.mWindowAttributes.surfaceInsets;
-            mScreenRect.offset(surfaceInsets.left, surfaceInsets.top);
-            // Collect all geometry changes and apply these changes on the RenderThread worker
-            // via the RenderNode.PositionUpdateListener.
-            final Transaction surfaceUpdateTransaction = new Transaction();
-            if (creating) {
-                updateOpaqueFlag();
-                final String name = "SurfaceView[" + viewRoot.getTitle().toString() + "]";
-                createBlastSurfaceControls(viewRoot, name, surfaceUpdateTransaction);
-            } else if (mSurfaceControl == null) {
-                return;
-            }
-
-            final boolean redrawNeeded = sizeChanged || creating || hintChanged
-                    || (mVisible && !mDrawFinished) || alphaChanged || relativeZChanged;
-            boolean shouldSyncBuffer =
-                    redrawNeeded && viewRoot.wasRelayoutRequested() && viewRoot.isInLocalSync();
-            SyncBufferTransactionCallback syncBufferTransactionCallback = null;
-            if (shouldSyncBuffer) {
-                syncBufferTransactionCallback = new SyncBufferTransactionCallback();
-                mBlastBufferQueue.syncNextTransaction(
-                        false /* acquireSingleBuffer */,
-                        syncBufferTransactionCallback::onTransactionReady);
-            }
-
-            final boolean realSizeChanged = performSurfaceTransaction(viewRoot, translator,
-                    creating, sizeChanged, hintChanged, relativeZChanged,
-                    surfaceUpdateTransaction);
-
             try {
-                SurfaceHolder.Callback[] callbacks = null;
+                mVisible = mRequestedVisible;
+                mWindowSpaceLeft = mLocation[0];
+                mWindowSpaceTop = mLocation[1];
+                mSurfaceWidth = myWidth;
+                mSurfaceHeight = myHeight;
+                mFormat = mRequestedFormat;
+                mAlpha = alpha;
+                mLastWindowVisibility = mWindowVisibility;
+                mTransformHint = viewRoot.getBufferTransformHint();
+                mSubLayer = mRequestedSubLayer;
 
-                final boolean surfaceChanged = creating;
-                if (mSurfaceCreated && (surfaceChanged || (!mVisible && visibleChanged))) {
-                    mSurfaceCreated = false;
-                    notifySurfaceDestroyed();
+                mScreenRect.left = mWindowSpaceLeft;
+                mScreenRect.top = mWindowSpaceTop;
+                mScreenRect.right = mWindowSpaceLeft + getWidth();
+                mScreenRect.bottom = mWindowSpaceTop + getHeight();
+                if (translator != null) {
+                    translator.translateRectInAppWindowToScreen(mScreenRect);
                 }
 
-                copySurface(creating /* surfaceControlCreated */, sizeChanged);
+                final Rect surfaceInsets = viewRoot.mWindowAttributes.surfaceInsets;
+                mScreenRect.offset(surfaceInsets.left, surfaceInsets.top);
+                // Collect all geometry changes and apply these changes on the RenderThread worker
+                // via the RenderNode.PositionUpdateListener.
+                final Transaction surfaceUpdateTransaction = new Transaction();
+                if (creating) {
+                    updateOpaqueFlag();
+                    final String name = "SurfaceView[" + viewRoot.getTitle().toString() + "]";
+                    createBlastSurfaceControls(viewRoot, name, surfaceUpdateTransaction);
+                } else if (mSurfaceControl == null) {
+                    return;
+                }
 
-                if (mVisible && mSurface.isValid()) {
-                    if (!mSurfaceCreated && (surfaceChanged || visibleChanged)) {
-                        mSurfaceCreated = true;
-                        mIsCreating = true;
-                        if (DEBUG) Log.i(TAG, System.identityHashCode(this) + " "
-                                + "visibleChanged -- surfaceCreated");
-                        callbacks = getSurfaceCallbacks();
-                        for (SurfaceHolder.Callback c : callbacks) {
-                            c.surfaceCreated(mSurfaceHolder);
-                        }
+                final boolean redrawNeeded = sizeChanged || creating || hintChanged
+                        || (mVisible && !mDrawFinished) || alphaChanged || relativeZChanged;
+                boolean shouldSyncBuffer =
+                        redrawNeeded && viewRoot.wasRelayoutRequested() && viewRoot.isInLocalSync();
+                SyncBufferTransactionCallback syncBufferTransactionCallback = null;
+                if (shouldSyncBuffer) {
+                    syncBufferTransactionCallback = new SyncBufferTransactionCallback();
+                    mBlastBufferQueue.syncNextTransaction(
+                            false /* acquireSingleBuffer */,
+                            syncBufferTransactionCallback::onTransactionReady);
+                }
+
+                final boolean realSizeChanged = performSurfaceTransaction(viewRoot, translator,
+                        creating, sizeChanged, hintChanged, relativeZChanged,
+                        surfaceUpdateTransaction);
+
+                try {
+                    SurfaceHolder.Callback[] callbacks = null;
+
+                    final boolean surfaceChanged = creating;
+                    if (mSurfaceCreated && (surfaceChanged || (!mVisible && visibleChanged))) {
+                        mSurfaceCreated = false;
+                        notifySurfaceDestroyed();
                     }
-                    if (creating || formatChanged || sizeChanged || hintChanged
-                            || visibleChanged || realSizeChanged) {
-                        if (DEBUG) Log.i(TAG, System.identityHashCode(this) + " "
-                                + "surfaceChanged -- format=" + mFormat
-                                + " w=" + myWidth + " h=" + myHeight);
-                        if (callbacks == null) {
+
+                    copySurface(creating /* surfaceControlCreated */, sizeChanged);
+
+                    if (mVisible && mSurface.isValid()) {
+                        if (!mSurfaceCreated && (surfaceChanged || visibleChanged)) {
+                            mSurfaceCreated = true;
+                            mIsCreating = true;
+                            if (DEBUG) Log.i(TAG, System.identityHashCode(this) + " "
+                                    + "visibleChanged -- surfaceCreated");
                             callbacks = getSurfaceCallbacks();
+                            for (SurfaceHolder.Callback c : callbacks) {
+                                c.surfaceCreated(mSurfaceHolder);
+                            }
                         }
-                        for (SurfaceHolder.Callback c : callbacks) {
-                            c.surfaceChanged(mSurfaceHolder, mFormat, myWidth, myHeight);
+                        if (creating || formatChanged || sizeChanged || hintChanged
+                                || visibleChanged || realSizeChanged) {
+                            if (DEBUG) Log.i(TAG, System.identityHashCode(this) + " "
+                                    + "surfaceChanged -- format=" + mFormat
+                                    + " w=" + myWidth + " h=" + myHeight);
+                            if (callbacks == null) {
+                                callbacks = getSurfaceCallbacks();
+                            }
+                            for (SurfaceHolder.Callback c : callbacks) {
+                                c.surfaceChanged(mSurfaceHolder, mFormat, myWidth, myHeight);
+                            }
                         }
-                    }
-                    if (redrawNeeded) {
-                        if (DEBUG) {
-                            Log.i(TAG, System.identityHashCode(this) + " surfaceRedrawNeeded");
-                        }
-                        if (callbacks == null) {
-                            callbacks = getSurfaceCallbacks();
-                        }
+                        if (redrawNeeded) {
+                            if (DEBUG) {
+                                Log.i(TAG, System.identityHashCode(this) + " surfaceRedrawNeeded");
+                            }
+                            if (callbacks == null) {
+                                callbacks = getSurfaceCallbacks();
+                            }
 
-                        if (shouldSyncBuffer) {
-                            handleSyncBufferCallback(callbacks, syncBufferTransactionCallback);
-                        } else {
-                            handleSyncNoBuffer(callbacks);
+                            if (shouldSyncBuffer) {
+                                handleSyncBufferCallback(callbacks, syncBufferTransactionCallback);
+                            } else {
+                                handleSyncNoBuffer(callbacks);
+                            }
                         }
                     }
+                } finally {
+                    mIsCreating = false;
+                    if (mSurfaceControl != null && !mSurfaceCreated) {
+                        releaseSurfaces(false /* releaseSurfacePackage*/);
+                    }
                 }
-            } finally {
-                mIsCreating = false;
-                if (mSurfaceControl != null && !mSurfaceCreated) {
-                    releaseSurfaces(false /* releaseSurfacePackage*/);
-                }
+            } catch (Exception ex) {
+                Log.e(TAG, "Exception configuring surface", ex);
             }
             if (DEBUG) Log.v(
                 TAG, "Layout: x=" + mScreenRect.left + " y=" + mScreenRect.top
diff --git a/core/java/android/window/TransitionInfo.java b/core/java/android/window/TransitionInfo.java
index c2da638..a35e13e 100644
--- a/core/java/android/window/TransitionInfo.java
+++ b/core/java/android/window/TransitionInfo.java
@@ -421,6 +421,53 @@
         return false;
     }
 
+    /**
+     * Releases temporary-for-animation surfaces referenced by this to potentially free up memory.
+     * This includes root-leash and snapshots.
+     */
+    public void releaseAnimSurfaces() {
+        for (int i = mChanges.size() - 1; i >= 0; --i) {
+            final Change c = mChanges.get(i);
+            if (c.mSnapshot != null) {
+                c.mSnapshot.release();
+                c.mSnapshot = null;
+            }
+        }
+        if (mRootLeash != null) {
+            mRootLeash.release();
+        }
+    }
+
+    /**
+     * Releases ALL the surfaces referenced by this to potentially free up memory. Do NOT use this
+     * if the surface-controls get stored and used elsewhere in the process. To just release
+     * temporary-for-animation surfaces, use {@link #releaseAnimSurfaces}.
+     */
+    public void releaseAllSurfaces() {
+        releaseAnimSurfaces();
+        for (int i = mChanges.size() - 1; i >= 0; --i) {
+            mChanges.get(i).getLeash().release();
+        }
+    }
+
+    /**
+     * Makes a copy of this as if it were parcel'd and unparcel'd. This implies that surfacecontrol
+     * refcounts are incremented which allows the "remote" receiver to release them without breaking
+     * the caller's references. Use this only if you need to "send" this to a local function which
+     * assumes it is being called from a remote caller.
+     */
+    public TransitionInfo localRemoteCopy() {
+        final TransitionInfo out = new TransitionInfo(mType, mFlags);
+        for (int i = 0; i < mChanges.size(); ++i) {
+            out.mChanges.add(mChanges.get(i).localRemoteCopy());
+        }
+        out.mRootLeash = mRootLeash != null ? new SurfaceControl(mRootLeash, "localRemote") : null;
+        // Doesn't have any native stuff, so no need for actual copy
+        out.mOptions = mOptions;
+        out.mRootOffset.set(mRootOffset);
+        return out;
+    }
+
     /** Represents the change a WindowContainer undergoes during a transition */
     public static final class Change implements Parcelable {
         private final WindowContainerToken mContainer;
@@ -473,6 +520,27 @@
             mSnapshotLuma = in.readFloat();
         }
 
+        private Change localRemoteCopy() {
+            final Change out = new Change(mContainer, new SurfaceControl(mLeash, "localRemote"));
+            out.mParent = mParent;
+            out.mLastParent = mLastParent;
+            out.mMode = mMode;
+            out.mFlags = mFlags;
+            out.mStartAbsBounds.set(mStartAbsBounds);
+            out.mEndAbsBounds.set(mEndAbsBounds);
+            out.mEndRelOffset.set(mEndRelOffset);
+            out.mTaskInfo = mTaskInfo;
+            out.mAllowEnterPip = mAllowEnterPip;
+            out.mStartRotation = mStartRotation;
+            out.mEndRotation = mEndRotation;
+            out.mEndFixedRotation = mEndFixedRotation;
+            out.mRotationAnimation = mRotationAnimation;
+            out.mBackgroundColor = mBackgroundColor;
+            out.mSnapshot = mSnapshot != null ? new SurfaceControl(mSnapshot, "localRemote") : null;
+            out.mSnapshotLuma = mSnapshotLuma;
+            return out;
+        }
+
         /** Sets the parent of this change's container. The parent must be a participant or null. */
         public void setParent(@Nullable WindowContainerToken parent) {
             mParent = parent;
diff --git a/core/jni/Android.bp b/core/jni/Android.bp
index f140e79..1bc903a 100644
--- a/core/jni/Android.bp
+++ b/core/jni/Android.bp
@@ -40,6 +40,8 @@
 
     cppflags: ["-Wno-conversion-null"],
 
+    cpp_std: "gnu++20",
+
     srcs: [
         "android_animation_PropertyValuesHolder.cpp",
         "android_os_SystemClock.cpp",
diff --git a/core/tests/utiltests/src/android/util/IntArrayTest.java b/core/tests/utiltests/src/android/util/IntArrayTest.java
index a76c640..caa7312 100644
--- a/core/tests/utiltests/src/android/util/IntArrayTest.java
+++ b/core/tests/utiltests/src/android/util/IntArrayTest.java
@@ -16,8 +16,8 @@
 
 package android.util;
 
-import static org.junit.Assert.assertArrayEquals;
-import static org.junit.Assert.assertEquals;
+import static com.google.common.truth.Truth.assertThat;
+import static com.google.common.truth.Truth.assertWithMessage;
 
 import androidx.test.filters.SmallTest;
 import androidx.test.runner.AndroidJUnit4;
@@ -25,6 +25,8 @@
 import org.junit.Test;
 import org.junit.runner.RunWith;
 
+import java.util.Arrays;
+
 @RunWith(AndroidJUnit4.class)
 @SmallTest
 public class IntArrayTest {
@@ -35,51 +37,65 @@
         a.add(1);
         a.add(2);
         a.add(3);
-        verify(new int[]{1, 2, 3}, a);
+        verify(a, 1, 2, 3);
 
         IntArray b = IntArray.fromArray(new int[]{4, 5, 6, 7, 8}, 3);
         a.addAll(b);
-        verify(new int[]{1, 2, 3, 4, 5, 6}, a);
+        verify(a, 1, 2, 3, 4, 5, 6);
 
         a.resize(2);
-        verify(new int[]{1, 2}, a);
+        verify(a, 1, 2);
 
         a.resize(8);
-        verify(new int[]{1, 2, 0, 0, 0, 0, 0, 0}, a);
+        verify(a, 1, 2, 0, 0, 0, 0, 0, 0);
 
         a.set(5, 10);
-        verify(new int[]{1, 2, 0, 0, 0, 10, 0, 0}, a);
+        verify(a, 1, 2, 0, 0, 0, 10, 0, 0);
 
         a.add(5, 20);
-        assertEquals(20, a.get(5));
-        assertEquals(5, a.indexOf(20));
-        verify(new int[]{1, 2, 0, 0, 0, 20, 10, 0, 0}, a);
+        assertThat(a.get(5)).isEqualTo(20);
+        assertThat(a.indexOf(20)).isEqualTo(5);
+        verify(a, 1, 2, 0, 0, 0, 20, 10, 0, 0);
 
-        assertEquals(-1, a.indexOf(99));
+        assertThat(a.indexOf(99)).isEqualTo(-1);
 
         a.resize(15);
         a.set(14, 30);
-        verify(new int[]{1, 2, 0, 0, 0, 20, 10, 0, 0, 0, 0, 0, 0, 0, 30}, a);
+        verify(a, 1, 2, 0, 0, 0, 20, 10, 0, 0, 0, 0, 0, 0, 0, 30);
 
         int[] backingArray = new int[]{1, 2, 3, 4};
         a = IntArray.wrap(backingArray);
         a.set(0, 10);
-        assertEquals(10, backingArray[0]);
+        assertThat(backingArray[0]).isEqualTo(10);
         backingArray[1] = 20;
         backingArray[2] = 30;
-        verify(backingArray, a);
-        assertEquals(2, a.indexOf(30));
+        verify(a, backingArray);
+        assertThat(a.indexOf(30)).isEqualTo(2);
 
         a.resize(2);
-        assertEquals(0, backingArray[2]);
-        assertEquals(0, backingArray[3]);
+        assertThat(backingArray[2]).isEqualTo(0);
+        assertThat(backingArray[3]).isEqualTo(0);
 
         a.add(50);
-        verify(new int[]{10, 20, 50}, a);
+        verify(a, 10, 20, 50);
     }
 
-    public void verify(int[] expected, IntArray intArray) {
-        assertEquals(expected.length, intArray.size());
-        assertArrayEquals(expected, intArray.toArray());
+    @Test
+    public void testToString() {
+        IntArray a = new IntArray(10);
+        a.add(4);
+        a.add(8);
+        a.add(15);
+        a.add(16);
+        a.add(23);
+        a.add(42);
+
+        assertWithMessage("toString()").that(a.toString()).contains("4, 8, 15, 16, 23, 42");
+        assertWithMessage("toString()").that(a.toString()).doesNotContain("0");
+    }
+
+    public void verify(IntArray intArray, int... expected) {
+        assertWithMessage("contents of %s", intArray).that(intArray.toArray()).asList()
+                .containsExactlyElementsIn(Arrays.stream(expected).boxed().toList());
     }
 }
diff --git a/graphics/java/android/view/PixelCopy.java b/graphics/java/android/view/PixelCopy.java
index 82ced43..0e198d5 100644
--- a/graphics/java/android/view/PixelCopy.java
+++ b/graphics/java/android/view/PixelCopy.java
@@ -382,9 +382,9 @@
             }
 
             /**
-             * Creates a PixelCopy request for the given {@link Window}
+             * Creates a PixelCopy Builder for the given {@link Window}
              * @param source The Window to copy from
-             * @return A {@link Builder} builder to set the optional params & execute the request
+             * @return A {@link Builder} builder to set the optional params & build the request
              */
             @SuppressLint("BuilderSetStyle")
             public static @NonNull Builder ofWindow(@NonNull Window source) {
@@ -394,7 +394,7 @@
             }
 
             /**
-             * Creates a PixelCopy request for the {@link Window} that the given {@link View} is
+             * Creates a PixelCopy Builder for the {@link Window} that the given {@link View} is
              * attached to.
              *
              * Note that this copy request is not cropped to the area the View occupies by default.
@@ -404,7 +404,7 @@
              *
              * @param source A View that {@link View#isAttachedToWindow() is attached} to a window
              *               that will be used to retrieve the window to copy from.
-             * @return A {@link Builder} builder to set the optional params & execute the request
+             * @return A {@link Builder} builder to set the optional params & build the request
              */
             @SuppressLint("BuilderSetStyle")
             public static @NonNull Builder ofWindow(@NonNull View source) {
@@ -427,10 +427,10 @@
             }
 
             /**
-             * Creates a PixelCopy request for the given {@link Surface}
+             * Creates a PixelCopy Builder for the given {@link Surface}
              *
              * @param source The Surface to copy from. Must be {@link Surface#isValid() valid}.
-             * @return A {@link Builder} builder to set the optional params & execute the request
+             * @return A {@link Builder} builder to set the optional params & build the request
              */
             @SuppressLint("BuilderSetStyle")
             public static @NonNull Builder ofSurface(@NonNull Surface source) {
@@ -441,12 +441,12 @@
             }
 
             /**
-             * Creates a PixelCopy request for the {@link Surface} belonging to the
+             * Creates a PixelCopy Builder for the {@link Surface} belonging to the
              * given {@link SurfaceView}
              *
              * @param source The SurfaceView to copy from. The backing surface must be
              *               {@link Surface#isValid() valid}
-             * @return A {@link Builder} builder to set the optional params & execute the request
+             * @return A {@link Builder} builder to set the optional params & build the request
              */
             @SuppressLint("BuilderSetStyle")
             public static @NonNull Builder ofSurface(@NonNull SurfaceView source) {
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/pip/PipTaskOrganizer.java b/libs/WindowManager/Shell/src/com/android/wm/shell/pip/PipTaskOrganizer.java
index f170e77..8ba2583 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/pip/PipTaskOrganizer.java
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/pip/PipTaskOrganizer.java
@@ -63,6 +63,7 @@
 import android.os.RemoteException;
 import android.os.SystemClock;
 import android.util.Log;
+import android.view.Choreographer;
 import android.view.Display;
 import android.view.Surface;
 import android.view.SurfaceControl;
@@ -179,8 +180,10 @@
                 // This is necessary in case there was a resize animation ongoing when exit PIP
                 // started, in which case the first resize will be skipped to let the exit
                 // operation handle the final resize out of PIP mode. See b/185306679.
-                finishResize(tx, destinationBounds, direction, animationType);
-                sendOnPipTransitionFinished(direction);
+                finishResizeDelayedIfNeeded(() -> {
+                    finishResize(tx, destinationBounds, direction, animationType);
+                    sendOnPipTransitionFinished(direction);
+                });
             }
         }
 
@@ -196,6 +199,39 @@
         }
     };
 
+    /**
+     * Finishes resizing the PiP, delaying the operation if it has to be synced with the PiP menu.
+     *
+     * This is done to avoid a race condition between the last transaction applied in
+     * onPipAnimationUpdate and the finishResize in onPipAnimationEnd. The transaction in
+     * onPipAnimationUpdate is applied directly from WmShell, while onPipAnimationEnd creates a
+     * WindowContainerTransaction in finishResize, which is to be applied by WmCore later. Normally,
+     * the WCT should be the last transaction to finish the animation. However, it  may happen that
+     * it gets applied *before* the transaction created by the last onPipAnimationUpdate. This
+     * happens only when the PiP surface transaction has to be synced with the PiP menu due to the
+     * necessity for a delay when syncing the PiP surface animation with the PiP menu surface
+     * animation and redrawing the PiP menu contents. As a result, the PiP surface gets scaled after
+     * the new bounds are applied by WmCore, which makes the PiP surface have unexpected bounds.
+     *
+     * To avoid this, we delay the finishResize operation until
+     * the next frame. This aligns the last onAnimationUpdate transaction with the WCT application.
+     */
+    private void finishResizeDelayedIfNeeded(Runnable finishResizeRunnable) {
+        if (!shouldSyncPipTransactionWithMenu()) {
+            finishResizeRunnable.run();
+            return;
+        }
+
+        // Delay the finishResize to the next frame
+        Choreographer.getInstance().postCallback(Choreographer.CALLBACK_COMMIT, () -> {
+            mMainExecutor.execute(finishResizeRunnable);
+        }, null);
+    }
+
+    private boolean shouldSyncPipTransactionWithMenu() {
+        return mPipMenuController.isMenuVisible();
+    }
+
     @VisibleForTesting
     final PipTransitionController.PipTransitionCallback mPipTransitionCallback =
             new PipTransitionController.PipTransitionCallback() {
@@ -221,7 +257,7 @@
                 @Override
                 public boolean handlePipTransaction(SurfaceControl leash,
                         SurfaceControl.Transaction tx, Rect destinationBounds) {
-                    if (mPipMenuController.isMenuVisible()) {
+                    if (shouldSyncPipTransactionWithMenu()) {
                         mPipMenuController.movePipMenu(leash, tx, destinationBounds);
                         return true;
                     }
@@ -1223,7 +1259,7 @@
         mSurfaceTransactionHelper
                 .crop(tx, mLeash, toBounds)
                 .round(tx, mLeash, mPipTransitionState.isInPip());
-        if (mPipMenuController.isMenuVisible()) {
+        if (shouldSyncPipTransactionWithMenu()) {
             mPipMenuController.resizePipMenu(mLeash, tx, toBounds);
         } else {
             tx.apply();
@@ -1265,7 +1301,7 @@
         mSurfaceTransactionHelper
                 .scale(tx, mLeash, startBounds, toBounds, degrees)
                 .round(tx, mLeash, startBounds, toBounds);
-        if (mPipMenuController.isMenuVisible()) {
+        if (shouldSyncPipTransactionWithMenu()) {
             mPipMenuController.movePipMenu(mLeash, tx, toBounds);
         } else {
             tx.apply();
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/transition/OneShotRemoteHandler.java b/libs/WindowManager/Shell/src/com/android/wm/shell/transition/OneShotRemoteHandler.java
index 4e1fa29..485b400 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/transition/OneShotRemoteHandler.java
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/transition/OneShotRemoteHandler.java
@@ -77,10 +77,10 @@
                 if (mRemote.asBinder() != null) {
                     mRemote.asBinder().unlinkToDeath(remoteDied, 0 /* flags */);
                 }
+                if (sct != null) {
+                    finishTransaction.merge(sct);
+                }
                 mMainExecutor.execute(() -> {
-                    if (sct != null) {
-                        finishTransaction.merge(sct);
-                    }
                     finishCallback.onTransitionFinished(wct, null /* wctCB */);
                 });
             }
@@ -90,7 +90,13 @@
             if (mRemote.asBinder() != null) {
                 mRemote.asBinder().linkToDeath(remoteDied, 0 /* flags */);
             }
-            mRemote.getRemoteTransition().startAnimation(transition, info, startTransaction, cb);
+            // If the remote is actually in the same process, then make a copy of parameters since
+            // remote impls assume that they have to clean-up native references.
+            final SurfaceControl.Transaction remoteStartT = RemoteTransitionHandler.copyIfLocal(
+                    startTransaction, mRemote.getRemoteTransition());
+            final TransitionInfo remoteInfo =
+                    remoteStartT == startTransaction ? info : info.localRemoteCopy();
+            mRemote.getRemoteTransition().startAnimation(transition, remoteInfo, remoteStartT, cb);
             // assume that remote will apply the start transaction.
             startTransaction.clear();
         } catch (RemoteException e) {
@@ -124,7 +130,13 @@
             }
         };
         try {
-            mRemote.getRemoteTransition().mergeAnimation(transition, info, t, mergeTarget, cb);
+            // If the remote is actually in the same process, then make a copy of parameters since
+            // remote impls assume that they have to clean-up native references.
+            final SurfaceControl.Transaction remoteT =
+                    RemoteTransitionHandler.copyIfLocal(t, mRemote.getRemoteTransition());
+            final TransitionInfo remoteInfo = remoteT == t ? info : info.localRemoteCopy();
+            mRemote.getRemoteTransition().mergeAnimation(
+                    transition, remoteInfo, remoteT, mergeTarget, cb);
         } catch (RemoteException e) {
             Log.e(Transitions.TAG, "Error merging remote transition.", e);
         }
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/transition/RemoteTransitionHandler.java b/libs/WindowManager/Shell/src/com/android/wm/shell/transition/RemoteTransitionHandler.java
index 9469529..b4e0584 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/transition/RemoteTransitionHandler.java
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/transition/RemoteTransitionHandler.java
@@ -19,6 +19,7 @@
 import android.annotation.NonNull;
 import android.annotation.Nullable;
 import android.os.IBinder;
+import android.os.Parcel;
 import android.os.RemoteException;
 import android.util.ArrayMap;
 import android.util.Log;
@@ -120,10 +121,10 @@
             public void onTransitionFinished(WindowContainerTransaction wct,
                     SurfaceControl.Transaction sct) {
                 unhandleDeath(remote.asBinder(), finishCallback);
+                if (sct != null) {
+                    finishTransaction.merge(sct);
+                }
                 mMainExecutor.execute(() -> {
-                    if (sct != null) {
-                        finishTransaction.merge(sct);
-                    }
                     mRequestedRemotes.remove(transition);
                     finishCallback.onTransitionFinished(wct, null /* wctCB */);
                 });
@@ -131,8 +132,14 @@
         };
         Transitions.setRunningRemoteTransitionDelegate(remote.getAppThread());
         try {
+            // If the remote is actually in the same process, then make a copy of parameters since
+            // remote impls assume that they have to clean-up native references.
+            final SurfaceControl.Transaction remoteStartT =
+                    copyIfLocal(startTransaction, remote.getRemoteTransition());
+            final TransitionInfo remoteInfo =
+                    remoteStartT == startTransaction ? info : info.localRemoteCopy();
             handleDeath(remote.asBinder(), finishCallback);
-            remote.getRemoteTransition().startAnimation(transition, info, startTransaction, cb);
+            remote.getRemoteTransition().startAnimation(transition, remoteInfo, remoteStartT, cb);
             // assume that remote will apply the start transaction.
             startTransaction.clear();
         } catch (RemoteException e) {
@@ -145,6 +152,28 @@
         return true;
     }
 
+    static SurfaceControl.Transaction copyIfLocal(SurfaceControl.Transaction t,
+            IRemoteTransition remote) {
+        // We care more about parceling than local (though they should be the same); so, use
+        // queryLocalInterface since that's what Binder uses to decide if it needs to parcel.
+        if (remote.asBinder().queryLocalInterface(IRemoteTransition.DESCRIPTOR) == null) {
+            // No local interface, so binder itself will parcel and thus we don't need to.
+            return t;
+        }
+        // Binder won't be parceling; however, the remotes assume they have their own native
+        // objects (and don't know if caller is local or not), so we need to make a COPY here so
+        // that the remote can clean it up without clearing the original transaction.
+        // Since there's no direct `copy` for Transaction, we have to parcel/unparcel instead.
+        final Parcel p = Parcel.obtain();
+        try {
+            t.writeToParcel(p, 0);
+            p.setDataPosition(0);
+            return SurfaceControl.Transaction.CREATOR.createFromParcel(p);
+        } finally {
+            p.recycle();
+        }
+    }
+
     @Override
     public void mergeAnimation(@NonNull IBinder transition, @NonNull TransitionInfo info,
             @NonNull SurfaceControl.Transaction t, @NonNull IBinder mergeTarget,
@@ -175,7 +204,11 @@
             }
         };
         try {
-            remote.mergeAnimation(transition, info, t, mergeTarget, cb);
+            // If the remote is actually in the same process, then make a copy of parameters since
+            // remote impls assume that they have to clean-up native references.
+            final SurfaceControl.Transaction remoteT = copyIfLocal(t, remote);
+            final TransitionInfo remoteInfo = remoteT == t ? info : info.localRemoteCopy();
+            remote.mergeAnimation(transition, remoteInfo, remoteT, mergeTarget, cb);
         } catch (RemoteException e) {
             Log.e(Transitions.TAG, "Error attempting to merge remote transition.", e);
         }
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/transition/Transitions.java b/libs/WindowManager/Shell/src/com/android/wm/shell/transition/Transitions.java
index 56d51bd..c6935c0 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/transition/Transitions.java
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/transition/Transitions.java
@@ -503,6 +503,7 @@
             // Treat this as an abort since we are bypassing any merge logic and effectively
             // finishing immediately.
             onAbort(transitionToken);
+            releaseSurfaces(info);
             return;
         }
 
@@ -607,6 +608,15 @@
         onFinish(transition, wct, wctCB, false /* abort */);
     }
 
+    /**
+     * Releases an info's animation-surfaces. These don't need to persist and we need to release
+     * them asap so that SF can free memory sooner.
+     */
+    private void releaseSurfaces(@Nullable TransitionInfo info) {
+        if (info == null) return;
+        info.releaseAnimSurfaces();
+    }
+
     private void onFinish(IBinder transition,
             @Nullable WindowContainerTransaction wct,
             @Nullable WindowContainerTransactionCallback wctCB,
@@ -645,6 +655,11 @@
         }
         ProtoLog.v(ShellProtoLogGroup.WM_SHELL_TRANSITIONS,
                 "Transition animation finished (abort=%b), notifying core %s", abort, transition);
+        if (active.mStartT != null) {
+            // Applied by now, so close immediately. Do not set to null yet, though, since nullness
+            // is used later to disambiguate malformed transitions.
+            active.mStartT.close();
+        }
         // Merge all relevant transactions together
         SurfaceControl.Transaction fullFinish = active.mFinishT;
         for (int iA = activeIdx + 1; iA < mActiveTransitions.size(); ++iA) {
@@ -664,12 +679,14 @@
             fullFinish.apply();
         }
         // Now perform all the finishes.
+        releaseSurfaces(active.mInfo);
         mActiveTransitions.remove(activeIdx);
         mOrganizer.finishTransition(transition, wct, wctCB);
         while (activeIdx < mActiveTransitions.size()) {
             if (!mActiveTransitions.get(activeIdx).mMerged) break;
             ActiveTransition merged = mActiveTransitions.remove(activeIdx);
             mOrganizer.finishTransition(merged.mToken, null /* wct */, null /* wctCB */);
+            releaseSurfaces(merged.mInfo);
         }
         // sift through aborted transitions
         while (mActiveTransitions.size() > activeIdx
@@ -682,8 +699,9 @@
             }
             mOrganizer.finishTransition(aborted.mToken, null /* wct */, null /* wctCB */);
             for (int i = 0; i < mObservers.size(); ++i) {
-                mObservers.get(i).onTransitionFinished(active.mToken, true);
+                mObservers.get(i).onTransitionFinished(aborted.mToken, true);
             }
+            releaseSurfaces(aborted.mInfo);
         }
         if (mActiveTransitions.size() <= activeIdx) {
             ProtoLog.v(ShellProtoLogGroup.WM_SHELL_TRANSITIONS, "All active transition animations "
diff --git a/libs/androidfw/ApkAssets.cpp b/libs/androidfw/ApkAssets.cpp
index 9aa3787..c0fa63a 100755
--- a/libs/androidfw/ApkAssets.cpp
+++ b/libs/androidfw/ApkAssets.cpp
@@ -83,15 +83,16 @@
     return {};
   }
 
+  std::string overlay_path(loaded_idmap->OverlayApkPath());
+  auto fd = unique_fd(::open(overlay_path.c_str(), O_RDONLY|O_CLOEXEC));
   std::unique_ptr<AssetsProvider> overlay_assets;
-  const std::string overlay_path(loaded_idmap->OverlayApkPath());
-  if (IsFabricatedOverlay(overlay_path)) {
+  if (IsFabricatedOverlay(fd)) {
     // Fabricated overlays do not contain resource definitions. All of the overlay resource values
     // are defined inline in the idmap.
-    overlay_assets = EmptyAssetsProvider::Create(overlay_path);
+    overlay_assets = EmptyAssetsProvider::Create(std::move(overlay_path));
   } else {
     // The overlay should be an APK.
-    overlay_assets = ZipAssetsProvider::Create(overlay_path, flags);
+    overlay_assets = ZipAssetsProvider::Create(std::move(fd), std::move(overlay_path), flags);
   }
   if (overlay_assets == nullptr) {
     return {};
diff --git a/libs/androidfw/AssetManager2.cpp b/libs/androidfw/AssetManager2.cpp
index 61e842a..c3d153d 100644
--- a/libs/androidfw/AssetManager2.cpp
+++ b/libs/androidfw/AssetManager2.cpp
@@ -112,7 +112,7 @@
   // A mapping from path of apk assets that could be target packages of overlays to the runtime
   // package id of its first loaded package. Overlays currently can only override resources in the
   // first package in the target resource table.
-  std::unordered_map<std::string, uint8_t> target_assets_package_ids;
+  std::unordered_map<std::string_view, uint8_t> target_assets_package_ids;
 
   // Overlay resources are not directly referenced by an application so their resource ids
   // can change throughout the application's lifetime. Assign overlay package ids last.
@@ -135,7 +135,7 @@
     if (auto loaded_idmap = apk_assets->GetLoadedIdmap(); loaded_idmap != nullptr) {
       // The target package must precede the overlay package in the apk assets paths in order
       // to take effect.
-      auto iter = target_assets_package_ids.find(std::string(loaded_idmap->TargetApkPath()));
+      auto iter = target_assets_package_ids.find(loaded_idmap->TargetApkPath());
       if (iter == target_assets_package_ids.end()) {
          LOG(INFO) << "failed to find target package for overlay "
                    << loaded_idmap->OverlayApkPath();
@@ -180,7 +180,7 @@
         if (overlay_ref_table != nullptr) {
           // If this package is from an overlay, use a dynamic reference table that can rewrite
           // overlay resource ids to their corresponding target resource ids.
-          new_group.dynamic_ref_table = overlay_ref_table;
+          new_group.dynamic_ref_table = std::move(overlay_ref_table);
         }
 
         DynamicRefTable* ref_table = new_group.dynamic_ref_table.get();
@@ -188,9 +188,9 @@
         ref_table->mAppAsLib = package->IsDynamic() && package->GetPackageId() == 0x7f;
       }
 
-      // Add the package and to the set of packages with the same ID.
+      // Add the package to the set of packages with the same ID.
       PackageGroup* package_group = &package_groups_[idx];
-      package_group->packages_.push_back(ConfiguredPackage{package.get(), {}});
+      package_group->packages_.emplace_back().loaded_package_ = package.get();
       package_group->cookies_.push_back(apk_assets_cookies[apk_assets]);
 
       // Add the package name -> build time ID mappings.
@@ -202,7 +202,7 @@
 
       if (auto apk_assets_path = apk_assets->GetPath()) {
         // Overlay target ApkAssets must have been created using path based load apis.
-        target_assets_package_ids.insert(std::make_pair(std::string(*apk_assets_path), package_id));
+        target_assets_package_ids.emplace(*apk_assets_path, package_id);
       }
     }
   }
@@ -219,11 +219,13 @@
 
     for (const auto& package : group.packages_) {
       const auto& package_aliases = package.loaded_package_->GetAliasResourceIdMap();
-      aliases.insert(package_aliases.begin(), package_aliases.end());
+      aliases.insert(aliases.end(), package_aliases.begin(), package_aliases.end());
     }
   }
 
   if (!aliases.empty()) {
+    std::sort(aliases.begin(), aliases.end(), [](auto&& l, auto&& r) { return l.first < r.first; });
+
     // Add the alias resources to the dynamic reference table of every package group. Since
     // staging aliases can only be defined by the framework package (which is not a shared
     // library), the compile-time package id of the framework is the same across all packages
diff --git a/libs/androidfw/AssetsProvider.cpp b/libs/androidfw/AssetsProvider.cpp
index 289d7e6..80e5607 100644
--- a/libs/androidfw/AssetsProvider.cpp
+++ b/libs/androidfw/AssetsProvider.cpp
@@ -393,8 +393,8 @@
   return std::unique_ptr<EmptyAssetsProvider>(new EmptyAssetsProvider({}));
 }
 
-std::unique_ptr<AssetsProvider> EmptyAssetsProvider::Create(const std::string& path) {
-  return std::unique_ptr<EmptyAssetsProvider>(new EmptyAssetsProvider(path));
+std::unique_ptr<AssetsProvider> EmptyAssetsProvider::Create(std::string path) {
+  return std::unique_ptr<EmptyAssetsProvider>(new EmptyAssetsProvider(std::move(path)));
 }
 
 std::unique_ptr<Asset> EmptyAssetsProvider::OpenInternal(const std::string& /* path */,
diff --git a/libs/androidfw/ResourceTypes.cpp b/libs/androidfw/ResourceTypes.cpp
index 035ed4f..31516dc 100644
--- a/libs/androidfw/ResourceTypes.cpp
+++ b/libs/androidfw/ResourceTypes.cpp
@@ -33,7 +33,9 @@
 #include <type_traits>
 #include <vector>
 
+#include <android-base/file.h>
 #include <android-base/macros.h>
+#include <android-base/utf8.h>
 #include <androidfw/ByteBucketArray.h>
 #include <androidfw/ResourceTypes.h>
 #include <androidfw/TypeWrappers.h>
@@ -236,12 +238,23 @@
 }
 
 bool IsFabricatedOverlay(const std::string& path) {
-  std::ifstream fin(path);
-  uint32_t magic;
-  if (fin.read(reinterpret_cast<char*>(&magic), sizeof(uint32_t))) {
-    return magic == kFabricatedOverlayMagic;
+  return IsFabricatedOverlay(path.c_str());
+}
+
+bool IsFabricatedOverlay(const char* path) {
+  auto fd = base::unique_fd(base::utf8::open(path, O_RDONLY|O_CLOEXEC));
+  if (fd < 0) {
+    return false;
   }
-  return false;
+  return IsFabricatedOverlay(fd);
+}
+
+bool IsFabricatedOverlay(base::borrowed_fd fd) {
+  uint32_t magic;
+  if (!base::ReadFullyAtOffset(fd, &magic, sizeof(magic), 0)) {
+    return false;
+  }
+  return magic == kFabricatedOverlayMagic;
 }
 
 static bool assertIdmapHeader(const void* idmap, size_t size) {
@@ -6988,11 +7001,10 @@
 DynamicRefTable::DynamicRefTable() : DynamicRefTable(0, false) {}
 
 DynamicRefTable::DynamicRefTable(uint8_t packageId, bool appAsLib)
-    : mAssignedPackageId(packageId)
+    : mLookupTable()
+    , mAssignedPackageId(packageId)
     , mAppAsLib(appAsLib)
 {
-    memset(mLookupTable, 0, sizeof(mLookupTable));
-
     // Reserved package ids
     mLookupTable[APP_PACKAGE_ID] = APP_PACKAGE_ID;
     mLookupTable[SYS_PACKAGE_ID] = SYS_PACKAGE_ID;
@@ -7073,10 +7085,6 @@
     mLookupTable[buildPackageId] = runtimePackageId;
 }
 
-void DynamicRefTable::addAlias(uint32_t stagedId, uint32_t finalizedId) {
-    mAliasId[stagedId] = finalizedId;
-}
-
 status_t DynamicRefTable::lookupResourceId(uint32_t* resId) const {
     uint32_t res = *resId;
     size_t packageId = Res_GETPACKAGE(res) + 1;
@@ -7086,11 +7094,12 @@
         return NO_ERROR;
     }
 
-    auto alias_id = mAliasId.find(res);
-    if (alias_id != mAliasId.end()) {
+    const auto alias_it = std::lower_bound(mAliasId.begin(), mAliasId.end(), res,
+        [](const AliasMap::value_type& pair, uint32_t val) { return pair.first < val; });
+    if (alias_it != mAliasId.end() && alias_it->first == res) {
       // Rewrite the resource id to its alias resource id. Since the alias resource id is a
       // compile-time id, it still needs to be resolved further.
-      res = alias_id->second;
+      res = alias_it->second;
     }
 
     if (packageId == SYS_PACKAGE_ID || (packageId == APP_PACKAGE_ID && !mAppAsLib)) {
diff --git a/libs/androidfw/include/androidfw/AssetsProvider.h b/libs/androidfw/include/androidfw/AssetsProvider.h
index 13cbe3b..af6e7f4 100644
--- a/libs/androidfw/include/androidfw/AssetsProvider.h
+++ b/libs/androidfw/include/androidfw/AssetsProvider.h
@@ -181,7 +181,7 @@
 // Does not provide any assets.
 struct EmptyAssetsProvider : public AssetsProvider {
   static std::unique_ptr<AssetsProvider> Create();
-  static std::unique_ptr<AssetsProvider> Create(const std::string& path);
+  static std::unique_ptr<AssetsProvider> Create(std::string path);
 
   bool ForEachFile(const std::string& path,
                    const std::function<void(StringPiece, FileType)>& f) const override;
diff --git a/libs/androidfw/include/androidfw/ResourceTypes.h b/libs/androidfw/include/androidfw/ResourceTypes.h
index d98e97a..52321da 100644
--- a/libs/androidfw/include/androidfw/ResourceTypes.h
+++ b/libs/androidfw/include/androidfw/ResourceTypes.h
@@ -21,6 +21,7 @@
 #define _LIBS_UTILS_RESOURCE_TYPES_H
 
 #include <android-base/expected.h>
+#include <android-base/unique_fd.h>
 
 #include <androidfw/Asset.h>
 #include <androidfw/Errors.h>
@@ -58,6 +59,8 @@
 
 // Returns whether or not the path represents a fabricated overlay.
 bool IsFabricatedOverlay(const std::string& path);
+bool IsFabricatedOverlay(const char* path);
+bool IsFabricatedOverlay(android::base::borrowed_fd fd);
 
 /**
  * In C++11, char16_t is defined as *at least* 16 bits. We do a lot of
@@ -1882,11 +1885,10 @@
 
     void addMapping(uint8_t buildPackageId, uint8_t runtimePackageId);
 
-    using AliasMap = std::map<uint32_t, uint32_t>;
+    using AliasMap = std::vector<std::pair<uint32_t, uint32_t>>;
     void setAliases(AliasMap aliases) {
         mAliasId = std::move(aliases);
     }
-    void addAlias(uint32_t stagedId, uint32_t finalizedId);
 
     // Returns whether or not the value must be looked up.
     bool requiresLookup(const Res_value* value) const;
diff --git a/native/android/Android.bp b/native/android/Android.bp
index 8594ba5..f1b1d79 100644
--- a/native/android/Android.bp
+++ b/native/android/Android.bp
@@ -34,6 +34,7 @@
 
 cc_defaults {
     name: "libandroid_defaults",
+    cpp_std: "gnu++20",
     cflags: [
         "-Wall",
         "-Werror",
diff --git a/native/android/system_fonts.cpp b/native/android/system_fonts.cpp
index 30d0c35..fe3132e 100644
--- a/native/android/system_fonts.cpp
+++ b/native/android/system_fonts.cpp
@@ -66,9 +66,6 @@
         return mFilePath == o.mFilePath && mLocale == o.mLocale && mWeight == o.mWeight &&
                 mItalic == o.mItalic && mCollectionIndex == o.mCollectionIndex && mAxes == o.mAxes;
     }
-
-    AFont() = default;
-    AFont(const AFont&) = default;
 };
 
 struct FontHasher {
diff --git a/packages/SystemUI/Android.bp b/packages/SystemUI/Android.bp
index 7c3948a..87354c7 100644
--- a/packages/SystemUI/Android.bp
+++ b/packages/SystemUI/Android.bp
@@ -168,25 +168,14 @@
 }
 
 android_library {
-    name: "SystemUI-tests",
+    name: "SystemUI-tests-base",
     manifest: "tests/AndroidManifest-base.xml",
-    additional_manifests: ["tests/AndroidManifest.xml"],
-
     resource_dirs: [
         "tests/res",
         "res-product",
         "res-keyguard",
         "res",
     ],
-    srcs: [
-        "tests/src/**/*.kt",
-        "tests/src/**/*.java",
-        "src/**/*.kt",
-        "src/**/*.java",
-        "src/**/I*.aidl",
-        ":ReleaseJavaFiles",
-        ":SystemUI-tests-utils",
-    ],
     static_libs: [
         "WifiTrackerLib",
         "SystemUIAnimationLib",
@@ -225,9 +214,6 @@
         "metrics-helper-lib",
         "hamcrest-library",
         "androidx.test.rules",
-        "androidx.test.uiautomator_uiautomator",
-        "mockito-target-extended-minus-junit4",
-        "androidx.test.ext.junit",
         "testables",
         "truth-prebuilt",
         "monet",
@@ -237,6 +223,27 @@
         "LowLightDreamLib",
         "motion_tool_lib",
     ],
+}
+
+android_library {
+    name: "SystemUI-tests",
+    manifest: "tests/AndroidManifest-base.xml",
+    additional_manifests: ["tests/AndroidManifest.xml"],
+    srcs: [
+        "tests/src/**/*.kt",
+        "tests/src/**/*.java",
+        "src/**/*.kt",
+        "src/**/*.java",
+        "src/**/I*.aidl",
+        ":ReleaseJavaFiles",
+        ":SystemUI-tests-utils",
+    ],
+    static_libs: [
+        "SystemUI-tests-base",
+        "androidx.test.uiautomator_uiautomator",
+        "mockito-target-extended-minus-junit4",
+        "androidx.test.ext.junit",
+    ],
     libs: [
         "android.test.runner",
         "android.test.base",
@@ -253,6 +260,45 @@
     },
 }
 
+android_app {
+    name: "SystemUIRobo-stub",
+    defaults: [
+        "platform_app_defaults",
+        "SystemUI_app_defaults",
+    ],
+    manifest: "tests/AndroidManifest-base.xml",
+    static_libs: [
+        "SystemUI-tests-base",
+    ],
+    aaptflags: [
+        "--extra-packages",
+        "com.android.systemui",
+    ],
+    dont_merge_manifests: true,
+    platform_apis: true,
+    system_ext_specific: true,
+    certificate: "platform",
+    privileged: true,
+    resource_dirs: [],
+}
+
+android_robolectric_test {
+    name: "SystemUiRoboTests",
+    srcs: [
+        "tests/robolectric/src/**/*.kt",
+        "tests/robolectric/src/**/*.java",
+    ],
+    libs: [
+        "android.test.runner",
+        "android.test.base",
+        "android.test.mock",
+        "truth-prebuilt",
+    ],
+    kotlincflags: ["-Xjvm-default=enable"],
+    instrumentation_for: "SystemUIRobo-stub",
+    java_resource_dirs: ["tests/robolectric/config"],
+}
+
 // Opt-out config for optimizing the SystemUI target using R8.
 // Disabled via `export SYSTEMUI_OPTIMIZE_JAVA=false`, or explicitly in Make via
 // `SYSTEMUI_OPTIMIZE_JAVA := false`.
diff --git a/packages/SystemUI/animation/src/com/android/systemui/animation/RemoteTransitionAdapter.kt b/packages/SystemUI/animation/src/com/android/systemui/animation/RemoteTransitionAdapter.kt
index f9c6841..43bfa74 100644
--- a/packages/SystemUI/animation/src/com/android/systemui/animation/RemoteTransitionAdapter.kt
+++ b/packages/SystemUI/animation/src/com/android/systemui/animation/RemoteTransitionAdapter.kt
@@ -320,9 +320,7 @@
                                 counterWallpaper.cleanUp(finishTransaction)
                                 // Release surface references now. This is apparently to free GPU
                                 // memory while doing quick operations (eg. during CTS).
-                                for (i in info.changes.indices.reversed()) {
-                                    info.changes[i].leash.release()
-                                }
+                                info.releaseAllSurfaces()
                                 for (i in leashMap.size - 1 downTo 0) {
                                     leashMap.valueAt(i).release()
                                 }
@@ -331,6 +329,7 @@
                                         null /* wct */,
                                         finishTransaction
                                     )
+                                    finishTransaction.close()
                                 } catch (e: RemoteException) {
                                     Log.e(
                                         "ActivityOptionsCompat",
@@ -364,6 +363,9 @@
                 ) {
                     // TODO: hook up merge to recents onTaskAppeared if applicable. Until then,
                     //       ignore any incoming merges.
+                    // Clean up stuff though cuz GC takes too long for benchmark tests.
+                    t.close()
+                    info.releaseAllSurfaces()
                 }
             }
         }
diff --git a/packages/SystemUI/shared/src/com/android/systemui/shared/system/RemoteAnimationRunnerCompat.java b/packages/SystemUI/shared/src/com/android/systemui/shared/system/RemoteAnimationRunnerCompat.java
index 93c8073..1b0dacc 100644
--- a/packages/SystemUI/shared/src/com/android/systemui/shared/system/RemoteAnimationRunnerCompat.java
+++ b/packages/SystemUI/shared/src/com/android/systemui/shared/system/RemoteAnimationRunnerCompat.java
@@ -166,15 +166,14 @@
                     counterLauncher.cleanUp(finishTransaction);
                     counterWallpaper.cleanUp(finishTransaction);
                     // Release surface references now. This is apparently to free GPU memory
-                    // while doing quick operations (eg. during CTS).
-                    for (int i = info.getChanges().size() - 1; i >= 0; --i) {
-                        info.getChanges().get(i).getLeash().release();
-                    }
+                    // before GC would.
+                    info.releaseAllSurfaces();
                     // Don't release here since launcher might still be using them. Instead
                     // let launcher release them (eg. via RemoteAnimationTargets)
                     leashMap.clear();
                     try {
                         finishCallback.onTransitionFinished(null /* wct */, finishTransaction);
+                        finishTransaction.close();
                     } catch (RemoteException e) {
                         Log.e("ActivityOptionsCompat", "Failed to call app controlled animation"
                                 + " finished callback", e);
@@ -203,10 +202,13 @@
                 synchronized (mFinishRunnables) {
                     finishRunnable = mFinishRunnables.remove(mergeTarget);
                 }
+                // Since we're not actually animating, release native memory now
+                t.close();
+                info.releaseAllSurfaces();
                 if (finishRunnable == null) return;
                 onAnimationCancelled(false /* isKeyguardOccluded */);
                 finishRunnable.run();
             }
         };
     }
-}
\ No newline at end of file
+}
diff --git a/packages/SystemUI/shared/src/com/android/systemui/shared/system/RemoteTransitionCompat.java b/packages/SystemUI/shared/src/com/android/systemui/shared/system/RemoteTransitionCompat.java
index d4d3d25..b7e2494 100644
--- a/packages/SystemUI/shared/src/com/android/systemui/shared/system/RemoteTransitionCompat.java
+++ b/packages/SystemUI/shared/src/com/android/systemui/shared/system/RemoteTransitionCompat.java
@@ -126,15 +126,18 @@
             public void mergeAnimation(IBinder transition, TransitionInfo info,
                     SurfaceControl.Transaction t, IBinder mergeTarget,
                     IRemoteTransitionFinishedCallback finishedCallback) {
-                if (!mergeTarget.equals(mToken)) return;
-                if (!mRecentsSession.merge(info, t, recents)) return;
-                try {
-                    finishedCallback.onTransitionFinished(null /* wct */, null /* sct */);
-                } catch (RemoteException e) {
-                    Log.e(TAG, "Error merging transition.", e);
+                if (mergeTarget.equals(mToken) && mRecentsSession.merge(info, t, recents)) {
+                    try {
+                        finishedCallback.onTransitionFinished(null /* wct */, null /* sct */);
+                    } catch (RemoteException e) {
+                        Log.e(TAG, "Error merging transition.", e);
+                    }
+                    // commit taskAppeared after merge transition finished.
+                    mRecentsSession.commitTasksAppearedIfNeeded(recents);
+                } else {
+                    t.close();
+                    info.releaseAllSurfaces();
                 }
-                // commit taskAppeared after merge transition finished.
-                mRecentsSession.commitTasksAppearedIfNeeded(recents);
             }
         };
         return new RemoteTransition(remote, appThread);
@@ -248,6 +251,8 @@
                 }
                 // In this case, we are "returning" to an already running app, so just consume
                 // the merge and do nothing.
+                info.releaseAllSurfaces();
+                t.close();
                 return true;
             }
             final int layer = mInfo.getChanges().size() * 3;
@@ -264,6 +269,8 @@
                 t.setLayer(targets[i].leash, layer);
             }
             t.apply();
+            // not using the incoming anim-only surfaces
+            info.releaseAnimSurfaces();
             mAppearedTargets = targets;
             return true;
         }
@@ -380,9 +387,7 @@
             }
             // Only release the non-local created surface references. The animator is responsible
             // for releasing the leashes created by local.
-            for (int i = 0; i < mInfo.getChanges().size(); ++i) {
-                mInfo.getChanges().get(i).getLeash().release();
-            }
+            mInfo.releaseAllSurfaces();
             // Reset all members.
             mWrapped = null;
             mFinishCB = null;
diff --git a/packages/SystemUI/src/com/android/keyguard/EmergencyButtonController.java b/packages/SystemUI/src/com/android/keyguard/EmergencyButtonController.java
index c5190e8..ea808eb 100644
--- a/packages/SystemUI/src/com/android/keyguard/EmergencyButtonController.java
+++ b/packages/SystemUI/src/com/android/keyguard/EmergencyButtonController.java
@@ -135,7 +135,7 @@
             mPowerManager.userActivity(SystemClock.uptimeMillis(), true);
         }
         mActivityTaskManager.stopSystemLockTaskMode();
-        mShadeController.collapsePanel(false);
+        mShadeController.collapseShade(false);
         if (mTelecomManager != null && mTelecomManager.isInCall()) {
             mTelecomManager.showInCallScreen(false);
             if (mEmergencyButtonCallback != null) {
diff --git a/packages/SystemUI/src/com/android/systemui/accessibility/SystemActions.java b/packages/SystemUI/src/com/android/systemui/accessibility/SystemActions.java
index d60cc75..50449b0 100644
--- a/packages/SystemUI/src/com/android/systemui/accessibility/SystemActions.java
+++ b/packages/SystemUI/src/com/android/systemui/accessibility/SystemActions.java
@@ -52,6 +52,7 @@
 import com.android.systemui.CoreStartable;
 import com.android.systemui.dagger.SysUISingleton;
 import com.android.systemui.recents.Recents;
+import com.android.systemui.shade.ShadeController;
 import com.android.systemui.statusbar.CommandQueue;
 import com.android.systemui.statusbar.NotificationShadeWindowController;
 import com.android.systemui.statusbar.phone.CentralSurfaces;
@@ -183,15 +184,18 @@
     private final AccessibilityManager mA11yManager;
     private final Lazy<Optional<CentralSurfaces>> mCentralSurfacesOptionalLazy;
     private final NotificationShadeWindowController mNotificationShadeController;
+    private final ShadeController mShadeController;
     private final StatusBarWindowCallback mNotificationShadeCallback;
     private boolean mDismissNotificationShadeActionRegistered;
 
     @Inject
     public SystemActions(Context context,
             NotificationShadeWindowController notificationShadeController,
+            ShadeController shadeController,
             Lazy<Optional<CentralSurfaces>> centralSurfacesOptionalLazy,
             Optional<Recents> recentsOptional) {
         mContext = context;
+        mShadeController = shadeController;
         mRecentsOptional = recentsOptional;
         mReceiver = new SystemActionsBroadcastReceiver();
         mLocale = mContext.getResources().getConfiguration().getLocales().get(0);
@@ -529,9 +533,7 @@
     }
 
     private void handleAccessibilityDismissNotificationShade() {
-        mCentralSurfacesOptionalLazy.get().ifPresent(
-                centralSurfaces -> centralSurfaces.animateCollapsePanels(
-                        CommandQueue.FLAG_EXCLUDE_NONE, false /* force */));
+        mShadeController.animateCollapseShade(CommandQueue.FLAG_EXCLUDE_NONE);
     }
 
     private void handleDpadUp() {
diff --git a/packages/SystemUI/src/com/android/systemui/keyguard/KeyguardService.java b/packages/SystemUI/src/com/android/systemui/keyguard/KeyguardService.java
index 0214313..e631816 100644
--- a/packages/SystemUI/src/com/android/systemui/keyguard/KeyguardService.java
+++ b/packages/SystemUI/src/com/android/systemui/keyguard/KeyguardService.java
@@ -220,6 +220,7 @@
                                 synchronized (mFinishCallbacks) {
                                     if (mFinishCallbacks.remove(transition) == null) return;
                                 }
+                                info.releaseAllSurfaces();
                                 Slog.d(TAG, "Finish IRemoteAnimationRunner.");
                                 finishCallback.onTransitionFinished(null /* wct */, null /* t */);
                             }
@@ -235,6 +236,8 @@
                     synchronized (mFinishCallbacks) {
                         origFinishCB = mFinishCallbacks.remove(transition);
                     }
+                    info.releaseAllSurfaces();
+                    t.close();
                     if (origFinishCB == null) {
                         // already finished (or not started yet), so do nothing.
                         return;
@@ -423,12 +426,15 @@
             t.apply();
             mBinder.setOccluded(true /* isOccluded */, true /* animate */);
             finishCallback.onTransitionFinished(null /* wct */, null /* wctCB */);
+            info.releaseAllSurfaces();
         }
 
         @Override
         public void mergeAnimation(IBinder transition, TransitionInfo info,
                 SurfaceControl.Transaction t, IBinder mergeTarget,
                 IRemoteTransitionFinishedCallback finishCallback) {
+            t.close();
+            info.releaseAllSurfaces();
         }
     };
 
@@ -440,12 +446,15 @@
             t.apply();
             mBinder.setOccluded(false /* isOccluded */, true /* animate */);
             finishCallback.onTransitionFinished(null /* wct */, null /* wctCB */);
+            info.releaseAllSurfaces();
         }
 
         @Override
         public void mergeAnimation(IBinder transition, TransitionInfo info,
                 SurfaceControl.Transaction t, IBinder mergeTarget,
                 IRemoteTransitionFinishedCallback finishCallback) {
+            t.close();
+            info.releaseAllSurfaces();
         }
     };
 
diff --git a/packages/SystemUI/src/com/android/systemui/keyguard/KeyguardViewMediator.java b/packages/SystemUI/src/com/android/systemui/keyguard/KeyguardViewMediator.java
index 5ed3ba7..948239a 100644
--- a/packages/SystemUI/src/com/android/systemui/keyguard/KeyguardViewMediator.java
+++ b/packages/SystemUI/src/com/android/systemui/keyguard/KeyguardViewMediator.java
@@ -870,7 +870,7 @@
                 @Override
                 public void onLaunchAnimationEnd(boolean launchIsFullScreen) {
                     if (launchIsFullScreen) {
-                        mCentralSurfaces.instantCollapseNotificationPanel();
+                        mShadeController.get().instantCollapseShade();
                     }
 
                     mOccludeAnimationPlaying = false;
diff --git a/packages/SystemUI/src/com/android/systemui/media/controls/ui/MediaHierarchyManager.kt b/packages/SystemUI/src/com/android/systemui/media/controls/ui/MediaHierarchyManager.kt
index b252be1..f7a9bc7 100644
--- a/packages/SystemUI/src/com/android/systemui/media/controls/ui/MediaHierarchyManager.kt
+++ b/packages/SystemUI/src/com/android/systemui/media/controls/ui/MediaHierarchyManager.kt
@@ -1053,18 +1053,9 @@
                     rootOverlay!!.add(mediaFrame)
                 } else {
                     val targetHost = getHost(newLocation)!!.hostView
-                    // When adding back to the host, let's make sure to reset the bounds.
-                    // Usually adding the view will trigger a layout that does this automatically,
-                    // but we sometimes suppress this.
+                    // This will either do a full layout pass and remeasure, or it will bypass
+                    // that and directly set the mediaFrame's bounds within the premeasured host.
                     targetHost.addView(mediaFrame)
-                    val left = targetHost.paddingLeft
-                    val top = targetHost.paddingTop
-                    mediaFrame.setLeftTopRightBottom(
-                        left,
-                        top,
-                        left + currentBounds.width(),
-                        top + currentBounds.height()
-                    )
 
                     if (mediaFrame.childCount > 0) {
                         val child = mediaFrame.getChildAt(0)
diff --git a/packages/SystemUI/src/com/android/systemui/media/controls/ui/MediaViewController.kt b/packages/SystemUI/src/com/android/systemui/media/controls/ui/MediaViewController.kt
index 4bf3031..4feb984 100644
--- a/packages/SystemUI/src/com/android/systemui/media/controls/ui/MediaViewController.kt
+++ b/packages/SystemUI/src/com/android/systemui/media/controls/ui/MediaViewController.kt
@@ -420,7 +420,9 @@
      */
     fun getMeasurementsForState(hostState: MediaHostState): MeasurementOutput? =
         traceSection("MediaViewController#getMeasurementsForState") {
-            val viewState = obtainViewState(hostState) ?: return null
+            // measurements should never factor in the squish fraction
+            val viewState =
+                obtainViewState(hostState.copy().also { it.squishFraction = 1.0f }) ?: return null
             measurement.measuredWidth = viewState.width
             measurement.measuredHeight = viewState.height
             return measurement
diff --git a/packages/SystemUI/src/com/android/systemui/screenshot/ScreenshotController.java b/packages/SystemUI/src/com/android/systemui/screenshot/ScreenshotController.java
index 10d31ea..57b256e 100644
--- a/packages/SystemUI/src/com/android/systemui/screenshot/ScreenshotController.java
+++ b/packages/SystemUI/src/com/android/systemui/screenshot/ScreenshotController.java
@@ -84,6 +84,8 @@
 import android.view.WindowManagerGlobal;
 import android.view.accessibility.AccessibilityManager;
 import android.widget.Toast;
+import android.window.OnBackInvokedCallback;
+import android.window.OnBackInvokedDispatcher;
 import android.window.WindowContext;
 
 import androidx.concurrent.futures.CallbackToFutureAdapter;
@@ -279,6 +281,13 @@
     private final ActionIntentExecutor mActionExecutor;
     private final UserManager mUserManager;
 
+    private final OnBackInvokedCallback mOnBackInvokedCallback = () -> {
+        if (DEBUG_INPUT) {
+            Log.d(TAG, "Predictive Back callback dispatched");
+        }
+        respondToBack();
+    };
+
     private ScreenshotView mScreenshotView;
     private Bitmap mScreenBitmap;
     private SaveImageInBackgroundTask mSaveInBgTask;
@@ -465,6 +474,10 @@
         }
     }
 
+    private void respondToBack() {
+        dismissScreenshot(SCREENSHOT_DISMISSED_OTHER);
+    }
+
     /**
      * Update resources on configuration change. Reinflate for theme/color changes.
      */
@@ -476,6 +489,26 @@
         // Inflate the screenshot layout
         mScreenshotView = (ScreenshotView)
                 LayoutInflater.from(mContext).inflate(R.layout.screenshot, null);
+        mScreenshotView.addOnAttachStateChangeListener(
+                new View.OnAttachStateChangeListener() {
+                    @Override
+                    public void onViewAttachedToWindow(@NonNull View v) {
+                        if (DEBUG_INPUT) {
+                            Log.d(TAG, "Registering Predictive Back callback");
+                        }
+                        mScreenshotView.findOnBackInvokedDispatcher().registerOnBackInvokedCallback(
+                                OnBackInvokedDispatcher.PRIORITY_DEFAULT, mOnBackInvokedCallback);
+                    }
+
+                    @Override
+                    public void onViewDetachedFromWindow(@NonNull View v) {
+                        if (DEBUG_INPUT) {
+                            Log.d(TAG, "Unregistering Predictive Back callback");
+                        }
+                        mScreenshotView.findOnBackInvokedDispatcher()
+                                .unregisterOnBackInvokedCallback(mOnBackInvokedCallback);
+                    }
+                });
         mScreenshotView.init(mUiEventLogger, new ScreenshotView.ScreenshotViewCallback() {
             @Override
             public void onUserInteraction() {
@@ -503,7 +536,7 @@
                 if (DEBUG_INPUT) {
                     Log.d(TAG, "onKeyEvent: KeyEvent.KEYCODE_BACK");
                 }
-                dismissScreenshot(SCREENSHOT_DISMISSED_OTHER);
+                respondToBack();
                 return true;
             }
             return false;
diff --git a/packages/SystemUI/src/com/android/systemui/shade/ShadeController.java b/packages/SystemUI/src/com/android/systemui/shade/ShadeController.java
index aa610bd..de9dcf9 100644
--- a/packages/SystemUI/src/com/android/systemui/shade/ShadeController.java
+++ b/packages/SystemUI/src/com/android/systemui/shade/ShadeController.java
@@ -16,6 +16,9 @@
 
 package com.android.systemui.shade;
 
+import android.view.MotionEvent;
+
+import com.android.systemui.statusbar.NotificationPresenter;
 import com.android.systemui.statusbar.StatusBarState;
 import com.android.systemui.statusbar.phone.CentralSurfaces;
 import com.android.systemui.statusbar.phone.StatusBarKeyguardViewManager;
@@ -29,31 +32,32 @@
  */
 public interface ShadeController {
 
-    /**
-     * Make our window larger and the panel expanded
-     */
-    void instantExpandNotificationsPanel();
+    /** Make our window larger and the shade expanded */
+    void instantExpandShade();
 
-    /** See {@link #animateCollapsePanels(int, boolean)}. */
-    void animateCollapsePanels();
+    /** Collapse the shade instantly with no animation. */
+    void instantCollapseShade();
 
-    /** See {@link #animateCollapsePanels(int, boolean)}. */
-    void animateCollapsePanels(int flags);
+    /** See {@link #animateCollapsePanels(int, boolean, boolean, float)}. */
+    void animateCollapseShade();
+
+    /** See {@link #animateCollapsePanels(int, boolean, boolean, float)}. */
+    void animateCollapseShade(int flags);
+
+    /** See {@link #animateCollapsePanels(int, boolean, boolean, float)}. */
+    void animateCollapseShadeForced();
+
+    /** See {@link #animateCollapsePanels(int, boolean, boolean, float)}. */
+    void animateCollapseShadeDelayed();
 
     /**
      * Collapse the shade animated, showing the bouncer when on {@link StatusBarState#KEYGUARD} or
-     * dismissing {@link CentralSurfaces} when on {@link StatusBarState#SHADE}.
+     * dismissing status bar when on {@link StatusBarState#SHADE}.
      */
-    void animateCollapsePanels(int flags, boolean force);
-
-    /** See {@link #animateCollapsePanels(int, boolean)}. */
-    void animateCollapsePanels(int flags, boolean force, boolean delayed);
-
-    /** See {@link #animateCollapsePanels(int, boolean)}. */
     void animateCollapsePanels(int flags, boolean force, boolean delayed, float speedUpFactor);
 
     /**
-     * If the notifications panel is not fully expanded, collapse it animated.
+     * If the shade is not fully expanded, collapse it animated.
      *
      * @return Seems to always return false
      */
@@ -77,9 +81,7 @@
      */
     void addPostCollapseAction(Runnable action);
 
-    /**
-     * Run all of the runnables added by {@link #addPostCollapseAction}.
-     */
+    /** Run all of the runnables added by {@link #addPostCollapseAction}. */
     void runPostCollapseRunnables();
 
     /**
@@ -87,13 +89,48 @@
      *
      * @return true if the shade was open, else false
      */
-    boolean collapsePanel();
+    boolean collapseShade();
 
     /**
-     * If animate is true, does the same as {@link #collapsePanel()}. Otherwise, instantly collapse
-     * the panel. Post collapse runnables will be executed
+     * If animate is true, does the same as {@link #collapseShade()}. Otherwise, instantly collapse
+     * the shade. Post collapse runnables will be executed
      *
      * @param animate true to animate the collapse, false for instantaneous collapse
      */
-    void collapsePanel(boolean animate);
+    void collapseShade(boolean animate);
+
+    /** Makes shade expanded but not visible. */
+    void makeExpandedInvisible();
+
+    /** Makes shade expanded and visible. */
+    void makeExpandedVisible(boolean force);
+
+    /** Returns whether the shade is expanded and visible. */
+    boolean isExpandedVisible();
+
+    /** Handle status bar touch event. */
+    void onStatusBarTouch(MotionEvent event);
+
+    /** Sets the listener for when the visibility of the shade changes. */
+    void setVisibilityListener(ShadeVisibilityListener listener);
+
+    /** */
+    void setNotificationPresenter(NotificationPresenter presenter);
+
+    /** */
+    void setNotificationShadeWindowViewController(
+            NotificationShadeWindowViewController notificationShadeWindowViewController);
+
+    /** */
+    void setNotificationPanelViewController(
+            NotificationPanelViewController notificationPanelViewController);
+
+    /** Listens for shade visibility changes. */
+    interface ShadeVisibilityListener {
+        /** Called when the visibility of the shade changes. */
+        void visibilityChanged(boolean visible);
+
+        /** Called when shade expanded and visible state changed. */
+        void expandedVisibleChanged(boolean expandedVisible);
+    }
 }
diff --git a/packages/SystemUI/src/com/android/systemui/shade/ShadeControllerImpl.java b/packages/SystemUI/src/com/android/systemui/shade/ShadeControllerImpl.java
index d783293..807e2e6 100644
--- a/packages/SystemUI/src/com/android/systemui/shade/ShadeControllerImpl.java
+++ b/packages/SystemUI/src/com/android/systemui/shade/ShadeControllerImpl.java
@@ -16,9 +16,12 @@
 
 package com.android.systemui.shade;
 
+import android.content.ComponentCallbacks2;
 import android.util.Log;
+import android.view.MotionEvent;
 import android.view.ViewTreeObserver;
 import android.view.WindowManager;
+import android.view.WindowManagerGlobal;
 
 import com.android.systemui.assist.AssistManager;
 import com.android.systemui.dagger.SysUISingleton;
@@ -27,11 +30,12 @@
 import com.android.systemui.statusbar.NotificationPresenter;
 import com.android.systemui.statusbar.NotificationShadeWindowController;
 import com.android.systemui.statusbar.StatusBarState;
-import com.android.systemui.statusbar.phone.CentralSurfaces;
+import com.android.systemui.statusbar.notification.row.NotificationGutsManager;
 import com.android.systemui.statusbar.phone.StatusBarKeyguardViewManager;
+import com.android.systemui.statusbar.policy.KeyguardStateController;
+import com.android.systemui.statusbar.window.StatusBarWindowController;
 
 import java.util.ArrayList;
-import java.util.Optional;
 
 import javax.inject.Inject;
 
@@ -39,68 +43,81 @@
 
 /** An implementation of {@link ShadeController}. */
 @SysUISingleton
-public class ShadeControllerImpl implements ShadeController {
+public final class ShadeControllerImpl implements ShadeController {
 
     private static final String TAG = "ShadeControllerImpl";
     private static final boolean SPEW = false;
 
-    private final CommandQueue mCommandQueue;
-    private final StatusBarStateController mStatusBarStateController;
-    protected final NotificationShadeWindowController mNotificationShadeWindowController;
-    private final StatusBarKeyguardViewManager mStatusBarKeyguardViewManager;
     private final int mDisplayId;
-    protected final Lazy<Optional<CentralSurfaces>> mCentralSurfacesOptionalLazy;
+
+    private final CommandQueue mCommandQueue;
+    private final KeyguardStateController mKeyguardStateController;
+    private final NotificationShadeWindowController mNotificationShadeWindowController;
+    private final StatusBarStateController mStatusBarStateController;
+    private final StatusBarKeyguardViewManager mStatusBarKeyguardViewManager;
+    private final StatusBarWindowController mStatusBarWindowController;
+
     private final Lazy<AssistManager> mAssistManagerLazy;
+    private final Lazy<NotificationGutsManager> mGutsManager;
 
     private final ArrayList<Runnable> mPostCollapseRunnables = new ArrayList<>();
 
+    private boolean mExpandedVisible;
+
+    private NotificationPanelViewController mNotificationPanelViewController;
+    private NotificationPresenter mPresenter;
+    private NotificationShadeWindowViewController mNotificationShadeWindowViewController;
+    private ShadeVisibilityListener mShadeVisibilityListener;
+
     @Inject
     public ShadeControllerImpl(
             CommandQueue commandQueue,
+            KeyguardStateController keyguardStateController,
             StatusBarStateController statusBarStateController,
-            NotificationShadeWindowController notificationShadeWindowController,
             StatusBarKeyguardViewManager statusBarKeyguardViewManager,
+            StatusBarWindowController statusBarWindowController,
+            NotificationShadeWindowController notificationShadeWindowController,
             WindowManager windowManager,
-            Lazy<Optional<CentralSurfaces>> centralSurfacesOptionalLazy,
-            Lazy<AssistManager> assistManagerLazy
+            Lazy<AssistManager> assistManagerLazy,
+            Lazy<NotificationGutsManager> gutsManager
     ) {
         mCommandQueue = commandQueue;
         mStatusBarStateController = statusBarStateController;
+        mStatusBarWindowController = statusBarWindowController;
+        mGutsManager = gutsManager;
         mNotificationShadeWindowController = notificationShadeWindowController;
         mStatusBarKeyguardViewManager = statusBarKeyguardViewManager;
         mDisplayId = windowManager.getDefaultDisplay().getDisplayId();
-        // TODO: Remove circular reference to CentralSurfaces when possible.
-        mCentralSurfacesOptionalLazy = centralSurfacesOptionalLazy;
+        mKeyguardStateController = keyguardStateController;
         mAssistManagerLazy = assistManagerLazy;
     }
 
     @Override
-    public void instantExpandNotificationsPanel() {
+    public void instantExpandShade() {
         // Make our window larger and the panel expanded.
-        getCentralSurfaces().makeExpandedVisible(true /* force */);
-        getNotificationPanelViewController().expand(false /* animate */);
+        makeExpandedVisible(true /* force */);
+        mNotificationPanelViewController.expand(false /* animate */);
         mCommandQueue.recomputeDisableFlags(mDisplayId, false /* animate */);
     }
 
     @Override
-    public void animateCollapsePanels() {
-        animateCollapsePanels(CommandQueue.FLAG_EXCLUDE_NONE);
+    public void animateCollapseShade() {
+        animateCollapseShade(CommandQueue.FLAG_EXCLUDE_NONE);
     }
 
     @Override
-    public void animateCollapsePanels(int flags) {
-        animateCollapsePanels(flags, false /* force */, false /* delayed */,
-                1.0f /* speedUpFactor */);
+    public void animateCollapseShade(int flags) {
+        animateCollapsePanels(flags, false, false, 1.0f);
     }
 
     @Override
-    public void animateCollapsePanels(int flags, boolean force) {
-        animateCollapsePanels(flags, force, false /* delayed */, 1.0f /* speedUpFactor */);
+    public void animateCollapseShadeForced() {
+        animateCollapsePanels(CommandQueue.FLAG_EXCLUDE_NONE, true, false, 1.0f);
     }
 
     @Override
-    public void animateCollapsePanels(int flags, boolean force, boolean delayed) {
-        animateCollapsePanels(flags, force, delayed, 1.0f /* speedUpFactor */);
+    public void animateCollapseShadeDelayed() {
+        animateCollapsePanels(CommandQueue.FLAG_EXCLUDE_RECENTS_PANEL, true, true, 1.0f);
     }
 
     @Override
@@ -111,34 +128,26 @@
             return;
         }
         if (SPEW) {
-            Log.d(TAG, "animateCollapse():"
-                    + " mExpandedVisible=" + getCentralSurfaces().isExpandedVisible()
-                    + " flags=" + flags);
+            Log.d(TAG,
+                    "animateCollapse(): mExpandedVisible=" + mExpandedVisible + "flags=" + flags);
         }
-
-        // TODO(b/62444020): remove when this bug is fixed
-        Log.v(TAG, "NotificationShadeWindow: " + getNotificationShadeWindowView()
-                + " canPanelBeCollapsed(): "
-                + getNotificationPanelViewController().canPanelBeCollapsed());
         if (getNotificationShadeWindowView() != null
-                && getNotificationPanelViewController().canPanelBeCollapsed()
+                && mNotificationPanelViewController.canPanelBeCollapsed()
                 && (flags & CommandQueue.FLAG_EXCLUDE_NOTIFICATION_PANEL) == 0) {
             // release focus immediately to kick off focus change transition
             mNotificationShadeWindowController.setNotificationShadeFocusable(false);
 
-            getCentralSurfaces().getNotificationShadeWindowViewController().cancelExpandHelper();
-            getNotificationPanelViewController()
-                    .collapsePanel(true /* animate */, delayed, speedUpFactor);
+            mNotificationShadeWindowViewController.cancelExpandHelper();
+            mNotificationPanelViewController.collapsePanel(true, delayed, speedUpFactor);
         }
     }
 
-
     @Override
     public boolean closeShadeIfOpen() {
-        if (!getNotificationPanelViewController().isFullyCollapsed()) {
+        if (!mNotificationPanelViewController.isFullyCollapsed()) {
             mCommandQueue.animateCollapsePanels(
                     CommandQueue.FLAG_EXCLUDE_RECENTS_PANEL, true /* force */);
-            getCentralSurfaces().visibilityChanged(false);
+            notifyVisibilityChanged(false);
             mAssistManagerLazy.get().hideAssist();
         }
         return false;
@@ -146,21 +155,19 @@
 
     @Override
     public boolean isShadeOpen() {
-        NotificationPanelViewController controller =
-                getNotificationPanelViewController();
-        return controller.isExpanding() || controller.isFullyExpanded();
+        return mNotificationPanelViewController.isExpanding()
+                || mNotificationPanelViewController.isFullyExpanded();
     }
 
     @Override
     public void postOnShadeExpanded(Runnable executable) {
-        getNotificationPanelViewController().addOnGlobalLayoutListener(
+        mNotificationPanelViewController.addOnGlobalLayoutListener(
                 new ViewTreeObserver.OnGlobalLayoutListener() {
                     @Override
                     public void onGlobalLayout() {
-                        if (getCentralSurfaces().getNotificationShadeWindowView()
-                                .isVisibleToUser()) {
-                            getNotificationPanelViewController().removeOnGlobalLayoutListener(this);
-                            getNotificationPanelViewController().postToView(executable);
+                        if (getNotificationShadeWindowView().isVisibleToUser()) {
+                            mNotificationPanelViewController.removeOnGlobalLayoutListener(this);
+                            mNotificationPanelViewController.postToView(executable);
                         }
                     }
                 });
@@ -183,12 +190,11 @@
     }
 
     @Override
-    public boolean collapsePanel() {
-        if (!getNotificationPanelViewController().isFullyCollapsed()) {
+    public boolean collapseShade() {
+        if (!mNotificationPanelViewController.isFullyCollapsed()) {
             // close the shade if it was open
-            animateCollapsePanels(CommandQueue.FLAG_EXCLUDE_RECENTS_PANEL,
-                    true /* force */, true /* delayed */);
-            getCentralSurfaces().visibilityChanged(false);
+            animateCollapseShadeDelayed();
+            notifyVisibilityChanged(false);
 
             return true;
         } else {
@@ -197,33 +203,131 @@
     }
 
     @Override
-    public void collapsePanel(boolean animate) {
+    public void collapseShade(boolean animate) {
         if (animate) {
-            boolean willCollapse = collapsePanel();
+            boolean willCollapse = collapseShade();
             if (!willCollapse) {
                 runPostCollapseRunnables();
             }
-        } else if (!getPresenter().isPresenterFullyCollapsed()) {
-            getCentralSurfaces().instantCollapseNotificationPanel();
-            getCentralSurfaces().visibilityChanged(false);
+        } else if (!mPresenter.isPresenterFullyCollapsed()) {
+            instantCollapseShade();
+            notifyVisibilityChanged(false);
         } else {
             runPostCollapseRunnables();
         }
     }
 
-    private CentralSurfaces getCentralSurfaces() {
-        return mCentralSurfacesOptionalLazy.get().get();
+    @Override
+    public void onStatusBarTouch(MotionEvent event) {
+        if (event.getAction() == MotionEvent.ACTION_UP) {
+            if (mExpandedVisible) {
+                animateCollapseShade();
+            }
+        }
     }
 
-    private NotificationPresenter getPresenter() {
-        return getCentralSurfaces().getPresenter();
+    @Override
+    public void instantCollapseShade() {
+        mNotificationPanelViewController.instantCollapse();
+        runPostCollapseRunnables();
     }
 
-    protected NotificationShadeWindowView getNotificationShadeWindowView() {
-        return getCentralSurfaces().getNotificationShadeWindowView();
+    @Override
+    public void makeExpandedVisible(boolean force) {
+        if (SPEW) Log.d(TAG, "Make expanded visible: expanded visible=" + mExpandedVisible);
+        if (!force && (mExpandedVisible || !mCommandQueue.panelsEnabled())) {
+            return;
+        }
+
+        mExpandedVisible = true;
+
+        // Expand the window to encompass the full screen in anticipation of the drag.
+        // It's only possible to do atomically because the status bar is at the top of the screen!
+        mNotificationShadeWindowController.setPanelVisible(true);
+
+        notifyVisibilityChanged(true);
+        mCommandQueue.recomputeDisableFlags(mDisplayId, !force /* animate */);
+        notifyExpandedVisibleChanged(true);
     }
 
-    private NotificationPanelViewController getNotificationPanelViewController() {
-        return getCentralSurfaces().getNotificationPanelViewController();
+    @Override
+    public void makeExpandedInvisible() {
+        if (SPEW) Log.d(TAG, "makeExpandedInvisible: mExpandedVisible=" + mExpandedVisible);
+
+        if (!mExpandedVisible || getNotificationShadeWindowView() == null) {
+            return;
+        }
+
+        // Ensure the panel is fully collapsed (just in case; bug 6765842, 7260868)
+        mNotificationPanelViewController.collapsePanel(false, false, 1.0f);
+
+        mNotificationPanelViewController.closeQs();
+
+        mExpandedVisible = false;
+        notifyVisibilityChanged(false);
+
+        // Update the visibility of notification shade and status bar window.
+        mNotificationShadeWindowController.setPanelVisible(false);
+        mStatusBarWindowController.setForceStatusBarVisible(false);
+
+        // Close any guts that might be visible
+        mGutsManager.get().closeAndSaveGuts(
+                true /* removeLeavebehind */,
+                true /* force */,
+                true /* removeControls */,
+                -1 /* x */,
+                -1 /* y */,
+                true /* resetMenu */);
+
+        runPostCollapseRunnables();
+        notifyExpandedVisibleChanged(false);
+        mCommandQueue.recomputeDisableFlags(
+                mDisplayId,
+                mNotificationPanelViewController.hideStatusBarIconsWhenExpanded() /* animate */);
+
+        // Trimming will happen later if Keyguard is showing - doing it here might cause a jank in
+        // the bouncer appear animation.
+        if (!mKeyguardStateController.isShowing()) {
+            WindowManagerGlobal.getInstance().trimMemory(ComponentCallbacks2.TRIM_MEMORY_UI_HIDDEN);
+        }
+    }
+
+    @Override
+    public boolean isExpandedVisible() {
+        return mExpandedVisible;
+    }
+
+    @Override
+    public void setVisibilityListener(ShadeVisibilityListener listener) {
+        mShadeVisibilityListener = listener;
+    }
+
+    private void notifyVisibilityChanged(boolean visible) {
+        mShadeVisibilityListener.visibilityChanged(visible);
+    }
+
+    private void notifyExpandedVisibleChanged(boolean expandedVisible) {
+        mShadeVisibilityListener.expandedVisibleChanged(expandedVisible);
+    }
+
+    @Override
+    public void setNotificationPresenter(NotificationPresenter presenter) {
+        mPresenter = presenter;
+    }
+
+    @Override
+    public void setNotificationShadeWindowViewController(
+            NotificationShadeWindowViewController controller) {
+        mNotificationShadeWindowViewController = controller;
+    }
+
+    private NotificationShadeWindowView getNotificationShadeWindowView() {
+        return mNotificationShadeWindowViewController.getView();
+    }
+
+    @Override
+    public void setNotificationPanelViewController(
+            NotificationPanelViewController notificationPanelViewController) {
+        mNotificationPanelViewController = notificationPanelViewController;
     }
 }
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/ExpandableNotificationRowDragController.java b/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/ExpandableNotificationRowDragController.java
index 64f87ca..b56bae1 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/ExpandableNotificationRowDragController.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/ExpandableNotificationRowDragController.java
@@ -54,8 +54,6 @@
 import com.android.systemui.statusbar.notification.logging.NotificationPanelLogger;
 import com.android.systemui.statusbar.policy.HeadsUpManager;
 
-import java.util.Collections;
-
 import javax.inject.Inject;
 
 /**
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/NotificationConversationInfo.java b/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/NotificationConversationInfo.java
index 0ce9656..f21db0b 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/NotificationConversationInfo.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/NotificationConversationInfo.java
@@ -154,7 +154,7 @@
         // If the user selected Priority and the previous selection was not priority, show a
         // People Tile add request.
         if (mSelectedAction == ACTION_FAVORITE && getPriority() != mSelectedAction) {
-            mShadeController.animateCollapsePanels();
+            mShadeController.animateCollapseShade();
             mPeopleSpaceWidgetManager.requestPinAppWidget(mShortcutInfo, new Bundle());
         }
         mGutsContainer.closeControls(v, /* save= */ true);
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 073bd4b..b519aef 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
@@ -1401,10 +1401,10 @@
             mExpandedHeight = height;
             setIsExpanded(height > 0);
             int minExpansionHeight = getMinExpansionHeight();
-            if (height < minExpansionHeight) {
+            if (height < minExpansionHeight && !mShouldUseSplitNotificationShade) {
                 mClipRect.left = 0;
                 mClipRect.right = getWidth();
-                mClipRect.top = getNotificationsClippingTopBound();
+                mClipRect.top = 0;
                 mClipRect.bottom = (int) height;
                 height = minExpansionHeight;
                 setRequestedClipBounds(mClipRect);
@@ -1466,17 +1466,6 @@
         notifyAppearChangedListeners();
     }
 
-    private int getNotificationsClippingTopBound() {
-        if (isHeadsUpTransition()) {
-            // HUN in split shade can go higher than bottom of NSSL when swiping up so we want
-            // to give it extra clipping margin. Because clipping has rounded corners, we also
-            // need to account for that corner clipping.
-            return -mAmbientState.getStackTopMargin() - mCornerRadius;
-        } else {
-            return 0;
-        }
-    }
-
     private void notifyAppearChangedListeners() {
         float appear;
         float expandAmount;
@@ -4236,7 +4225,7 @@
                 mShadeNeedsToClose = false;
                 postDelayed(
                         () -> {
-                            mShadeController.animateCollapsePanels(CommandQueue.FLAG_EXCLUDE_NONE);
+                            mShadeController.animateCollapseShade(CommandQueue.FLAG_EXCLUDE_NONE);
                         },
                         DELAY_BEFORE_SHADE_CLOSE /* delayMillis */);
             }
@@ -5139,6 +5128,7 @@
             println(pw, "intrinsicPadding", mIntrinsicPadding);
             println(pw, "topPadding", mTopPadding);
             println(pw, "bottomPadding", mBottomPadding);
+            mNotificationStackSizeCalculator.dump(pw, args);
         });
         pw.println();
         pw.println("Contents:");
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/NotificationStackSizeCalculator.kt b/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/NotificationStackSizeCalculator.kt
index ae854e2..25f99c6 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/NotificationStackSizeCalculator.kt
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/NotificationStackSizeCalculator.kt
@@ -30,6 +30,7 @@
 import com.android.systemui.statusbar.notification.row.ExpandableView
 import com.android.systemui.util.Compile
 import com.android.systemui.util.children
+import java.io.PrintWriter
 import javax.inject.Inject
 import kotlin.math.max
 import kotlin.math.min
@@ -53,6 +54,8 @@
     @Main private val resources: Resources
 ) {
 
+    private lateinit var lastComputeHeightLog : String
+
     /**
      * Maximum # notifications to show on Keyguard; extras will be collapsed in an overflow shelf.
      * If there are exactly 1 + mMaxKeyguardNotifications, and they fit in the available space
@@ -114,7 +117,9 @@
         shelfIntrinsicHeight: Float
     ): Int {
         log { "\n" }
-        val stackHeightSequence = computeHeightPerNotificationLimit(stack, shelfIntrinsicHeight)
+
+        val stackHeightSequence = computeHeightPerNotificationLimit(stack, shelfIntrinsicHeight,
+            /* computeHeight= */ false)
 
         var maxNotifications =
             stackHeightSequence.lastIndexWhile { heightResult ->
@@ -157,18 +162,21 @@
         shelfIntrinsicHeight: Float
     ): Float {
         log { "\n" }
+        lastComputeHeightLog = ""
         val heightPerMaxNotifications =
-            computeHeightPerNotificationLimit(stack, shelfIntrinsicHeight)
+            computeHeightPerNotificationLimit(stack, shelfIntrinsicHeight,
+                    /* computeHeight= */ true)
 
         val (notificationsHeight, shelfHeightWithSpaceBefore) =
             heightPerMaxNotifications.elementAtOrElse(maxNotifications) {
                 heightPerMaxNotifications.last() // Height with all notifications visible.
             }
-        log {
-            "computeHeight(maxNotifications=$maxNotifications," +
+        lastComputeHeightLog += "\ncomputeHeight(maxNotifications=$maxNotifications," +
                 "shelfIntrinsicHeight=$shelfIntrinsicHeight) -> " +
                 "${notificationsHeight + shelfHeightWithSpaceBefore}" +
                 " = ($notificationsHeight + $shelfHeightWithSpaceBefore)"
+        log {
+            lastComputeHeightLog
         }
         return notificationsHeight + shelfHeightWithSpaceBefore
     }
@@ -184,7 +192,8 @@
 
     private fun computeHeightPerNotificationLimit(
         stack: NotificationStackScrollLayout,
-        shelfHeight: Float
+        shelfHeight: Float,
+        computeHeight: Boolean
     ): Sequence<StackHeight> = sequence {
         log { "computeHeightPerNotificationLimit" }
 
@@ -213,9 +222,14 @@
                             currentIndex = firstViewInShelfIndex)
                     spaceBeforeShelf + shelfHeight
                 }
+
+            val currentLog = "computeHeight | i=$i notificationsHeight=$notifications " +
+                "shelfHeightWithSpaceBefore=$shelfWithSpaceBefore"
+            if (computeHeight) {
+                lastComputeHeightLog += "\n" + currentLog
+            }
             log {
-                "i=$i notificationsHeight=$notifications " +
-                    "shelfHeightWithSpaceBefore=$shelfWithSpaceBefore"
+                currentLog
             }
             yield(
                 StackHeight(
@@ -260,6 +274,10 @@
         return size
     }
 
+    fun dump(pw: PrintWriter, args: Array<out String>) {
+        pw.println("NotificationStackSizeCalculator lastComputeHeightLog = $lastComputeHeightLog")
+    }
+
     private fun ExpandableView.isShowable(onLockscreen: Boolean): Boolean {
         if (visibility == GONE || hasNoContentHeight()) return false
         if (onLockscreen) {
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/phone/CentralSurfaces.java b/packages/SystemUI/src/com/android/systemui/statusbar/phone/CentralSurfaces.java
index 01ca667..0ec7c62 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/CentralSurfaces.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/CentralSurfaces.java
@@ -193,8 +193,6 @@
 
     void animateExpandSettingsPanel(@Nullable String subpanel);
 
-    void animateCollapsePanels(int flags, boolean force);
-
     void collapsePanelOnMainThread();
 
     void togglePanel();
@@ -282,8 +280,6 @@
 
     void postAnimateOpenPanels();
 
-    boolean isExpandedVisible();
-
     boolean isPanelExpanded();
 
     void onInputFocusTransfer(boolean start, boolean cancel, float velocity);
@@ -495,12 +491,13 @@
 
     void updateNotificationPanelTouchState();
 
+    /**
+     * TODO(b/257041702) delete this
+     * @deprecated Use ShadeController#makeExpandedVisible
+     */
+    @Deprecated
     void makeExpandedVisible(boolean force);
 
-    void instantCollapseNotificationPanel();
-
-    void visibilityChanged(boolean visible);
-
     int getDisplayId();
 
     int getRotation();
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/phone/CentralSurfacesCommandQueueCallbacks.java b/packages/SystemUI/src/com/android/systemui/statusbar/phone/CentralSurfacesCommandQueueCallbacks.java
index f3482f4..6b72e96 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/CentralSurfacesCommandQueueCallbacks.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/CentralSurfacesCommandQueueCallbacks.java
@@ -209,7 +209,7 @@
     public void animateExpandNotificationsPanel() {
         if (CentralSurfaces.SPEW) {
             Log.d(CentralSurfaces.TAG,
-                    "animateExpand: mExpandedVisible=" + mCentralSurfaces.isExpandedVisible());
+                    "animateExpand: mExpandedVisible=" + mShadeController.isExpandedVisible());
         }
         if (!mCommandQueue.panelsEnabled()) {
             return;
@@ -222,7 +222,7 @@
     public void animateExpandSettingsPanel(@Nullable String subPanel) {
         if (CentralSurfaces.SPEW) {
             Log.d(CentralSurfaces.TAG,
-                    "animateExpand: mExpandedVisible=" + mCentralSurfaces.isExpandedVisible());
+                    "animateExpand: mExpandedVisible=" + mShadeController.isExpandedVisible());
         }
         if (!mCommandQueue.panelsEnabled()) {
             return;
@@ -276,7 +276,7 @@
 
         if ((diff1 & StatusBarManager.DISABLE_EXPAND) != 0) {
             if ((state1 & StatusBarManager.DISABLE_EXPAND) != 0) {
-                mShadeController.animateCollapsePanels();
+                mShadeController.animateCollapseShade();
             }
         }
 
@@ -293,7 +293,7 @@
         if ((diff2 & StatusBarManager.DISABLE2_NOTIFICATION_SHADE) != 0) {
             mCentralSurfaces.updateQsExpansionEnabled();
             if ((state2 & StatusBarManager.DISABLE2_NOTIFICATION_SHADE) != 0) {
-                mShadeController.animateCollapsePanels();
+                mShadeController.animateCollapseShade();
             }
         }
 
@@ -550,7 +550,7 @@
     @Override
     public void togglePanel() {
         if (mCentralSurfaces.isPanelExpanded()) {
-            mShadeController.animateCollapsePanels();
+            mShadeController.animateCollapseShade();
         } else {
             animateExpandNotificationsPanel();
         }
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 32ea8d6..d988772 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/CentralSurfacesImpl.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/CentralSurfacesImpl.java
@@ -58,7 +58,6 @@
 import android.app.WallpaperManager;
 import android.app.admin.DevicePolicyManager;
 import android.content.BroadcastReceiver;
-import android.content.ComponentCallbacks2;
 import android.content.ComponentName;
 import android.content.Context;
 import android.content.Intent;
@@ -406,12 +405,6 @@
 
     /** */
     @Override
-    public void animateCollapsePanels(int flags, boolean force) {
-        mCommandQueueCallbacks.animateCollapsePanels(flags, force);
-    }
-
-    /** */
-    @Override
     public void togglePanel() {
         mCommandQueueCallbacks.togglePanel();
     }
@@ -493,8 +486,6 @@
 
     private View mReportRejectedTouch;
 
-    private boolean mExpandedVisible;
-
     private final NotificationGutsManager mGutsManager;
     private final NotificationLogger mNotificationLogger;
     private final ShadeExpansionStateManager mShadeExpansionStateManager;
@@ -893,6 +884,8 @@
         updateDisplaySize();
         mStatusBarHideIconsForBouncerManager.setDisplayId(mDisplayId);
 
+        initShadeVisibilityListener();
+
         // start old BaseStatusBar.start().
         mWindowManagerService = WindowManagerGlobal.getWindowManagerService();
         mDevicePolicyManager = (DevicePolicyManager) mContext.getSystemService(
@@ -1083,6 +1076,25 @@
                                 requestTopUi, componentTag))));
     }
 
+    @VisibleForTesting
+    void initShadeVisibilityListener() {
+        mShadeController.setVisibilityListener(new ShadeController.ShadeVisibilityListener() {
+            @Override
+            public void visibilityChanged(boolean visible) {
+                onShadeVisibilityChanged(visible);
+            }
+
+            @Override
+            public void expandedVisibleChanged(boolean expandedVisible) {
+                if (expandedVisible) {
+                    setInteracting(StatusBarManager.WINDOW_STATUS_BAR, true);
+                } else {
+                    onExpandedInvisible();
+                }
+            }
+        });
+    }
+
     private void onFoldedStateChanged(boolean isFolded, boolean willGoToSleep) {
         Trace.beginSection("CentralSurfaces#onFoldedStateChanged");
         onFoldedStateChangedInternal(isFolded, willGoToSleep);
@@ -1228,7 +1240,7 @@
 
         mNotificationPanelViewController.initDependencies(
                 this,
-                this::makeExpandedInvisible,
+                mShadeController::makeExpandedInvisible,
                 mNotificationShelfController);
 
         BackDropView backdrop = mNotificationShadeWindowView.findViewById(R.id.backdrop);
@@ -1431,6 +1443,7 @@
         mRemoteInputManager.addControllerCallback(mNotificationShadeWindowController);
         mStackScrollerController.setNotificationActivityStarter(mNotificationActivityStarter);
         mGutsManager.setNotificationActivityStarter(mNotificationActivityStarter);
+        mShadeController.setNotificationPresenter(mPresenter);
         mNotificationsController.initialize(
                 this,
                 mPresenter,
@@ -1480,11 +1493,7 @@
         return (v, event) -> {
             mAutoHideController.checkUserAutoHide(event);
             mRemoteInputManager.checkRemoteInputOutside(event);
-            if (event.getAction() == MotionEvent.ACTION_UP) {
-                if (mExpandedVisible) {
-                    mShadeController.animateCollapsePanels();
-                }
-            }
+            mShadeController.onStatusBarTouch(event);
             return mNotificationShadeWindowView.onTouchEvent(event);
         };
     }
@@ -1506,6 +1515,9 @@
         mNotificationShadeWindowViewController.setupExpandedStatusBar();
         mNotificationPanelViewController =
                 mCentralSurfacesComponent.getNotificationPanelViewController();
+        mShadeController.setNotificationPanelViewController(mNotificationPanelViewController);
+        mShadeController.setNotificationShadeWindowViewController(
+                mNotificationShadeWindowViewController);
         mCentralSurfacesComponent.getLockIconViewController().init();
         mStackScrollerController =
                 mCentralSurfacesComponent.getNotificationStackScrollLayoutController();
@@ -1827,7 +1839,7 @@
                 && isLaunchForActivity) {
             onClosingFinished();
         } else {
-            mShadeController.collapsePanel(true /* animate */);
+            mShadeController.collapseShade(true /* animate */);
         }
     }
 
@@ -1838,7 +1850,7 @@
             onClosingFinished();
         }
         if (launchIsFullScreen) {
-            instantCollapseNotificationPanel();
+            mShadeController.instantCollapseShade();
         }
     }
 
@@ -1928,33 +1940,13 @@
     }
 
     @Override
-    public void makeExpandedVisible(boolean force) {
-        if (SPEW) Log.d(TAG, "Make expanded visible: expanded visible=" + mExpandedVisible);
-        if (!force && (mExpandedVisible || !mCommandQueue.panelsEnabled())) {
-            return;
-        }
-
-        mExpandedVisible = true;
-
-        // Expand the window to encompass the full screen in anticipation of the drag.
-        // This is only possible to do atomically because the status bar is at the top of the screen!
-        mNotificationShadeWindowController.setPanelVisible(true);
-
-        visibilityChanged(true);
-        mCommandQueue.recomputeDisableFlags(mDisplayId, !force /* animate */);
-        setInteracting(StatusBarManager.WINDOW_STATUS_BAR, true);
-    }
-
-    @Override
     public void postAnimateCollapsePanels() {
-        mMainExecutor.execute(mShadeController::animateCollapsePanels);
+        mMainExecutor.execute(mShadeController::animateCollapseShade);
     }
 
     @Override
     public void postAnimateForceCollapsePanels() {
-        mMainExecutor.execute(
-                () -> mShadeController.animateCollapsePanels(CommandQueue.FLAG_EXCLUDE_NONE,
-                true /* force */));
+        mMainExecutor.execute(mShadeController::animateCollapseShadeForced);
     }
 
     @Override
@@ -1963,11 +1955,6 @@
     }
 
     @Override
-    public boolean isExpandedVisible() {
-        return mExpandedVisible;
-    }
-
-    @Override
     public boolean isPanelExpanded() {
         return mPanelExpanded;
     }
@@ -1996,46 +1983,13 @@
         }
     }
 
-    void makeExpandedInvisible() {
-        if (SPEW) Log.d(TAG, "makeExpandedInvisible: mExpandedVisible=" + mExpandedVisible);
-
-        if (!mExpandedVisible || mNotificationShadeWindowView == null) {
-            return;
-        }
-
-        // Ensure the panel is fully collapsed (just in case; bug 6765842, 7260868)
-        mNotificationPanelViewController.collapsePanel(/*animate=*/ false, false /* delayed*/,
-                1.0f /* speedUpFactor */);
-
-        mNotificationPanelViewController.closeQs();
-
-        mExpandedVisible = false;
-        visibilityChanged(false);
-
-        // Update the visibility of notification shade and status bar window.
-        mNotificationShadeWindowController.setPanelVisible(false);
-        mStatusBarWindowController.setForceStatusBarVisible(false);
-
-        // Close any guts that might be visible
-        mGutsManager.closeAndSaveGuts(true /* removeLeavebehind */, true /* force */,
-                true /* removeControls */, -1 /* x */, -1 /* y */, true /* resetMenu */);
-
-        mShadeController.runPostCollapseRunnables();
+    private void onExpandedInvisible() {
         setInteracting(StatusBarManager.WINDOW_STATUS_BAR, false);
         if (!mNotificationActivityStarter.isCollapsingToShowActivityOverLockscreen()) {
             showBouncerOrLockScreenIfKeyguard();
         } else if (DEBUG) {
             Log.d(TAG, "Not showing bouncer due to activity showing over lockscreen");
         }
-        mCommandQueue.recomputeDisableFlags(
-                mDisplayId,
-                mNotificationPanelViewController.hideStatusBarIconsWhenExpanded() /* animate */);
-
-        // Trimming will happen later if Keyguard is showing - doing it here might cause a jank in
-        // the bouncer appear animation.
-        if (!mKeyguardStateController.isShowing()) {
-            WindowManagerGlobal.getInstance().trimMemory(ComponentCallbacks2.TRIM_MEMORY_UI_HIDDEN);
-        }
     }
 
     /** Called when a touch event occurred on {@link PhoneStatusBarView}. */
@@ -2072,7 +2026,8 @@
             final boolean upOrCancel =
                     event.getAction() == MotionEvent.ACTION_UP ||
                     event.getAction() == MotionEvent.ACTION_CANCEL;
-            setInteracting(StatusBarManager.WINDOW_STATUS_BAR, !upOrCancel || mExpandedVisible);
+            setInteracting(StatusBarManager.WINDOW_STATUS_BAR,
+                    !upOrCancel || mShadeController.isExpandedVisible());
         }
     }
 
@@ -2221,7 +2176,7 @@
         IndentingPrintWriter pw = DumpUtilsKt.asIndenting(pwOriginal);
         synchronized (mQueueLock) {
             pw.println("Current Status Bar state:");
-            pw.println("  mExpandedVisible=" + mExpandedVisible);
+            pw.println("  mExpandedVisible=" + mShadeController.isExpandedVisible());
             pw.println("  mDisplayMetrics=" + mDisplayMetrics);
             pw.println("  mStackScroller: " + CentralSurfaces.viewInfo(mStackScroller));
             pw.println("  mStackScroller: " + CentralSurfaces.viewInfo(mStackScroller)
@@ -2536,10 +2491,8 @@
                     }
                 }
                 if (dismissShade) {
-                    if (mExpandedVisible && !mBouncerShowing) {
-                        mShadeController.animateCollapsePanels(
-                                CommandQueue.FLAG_EXCLUDE_RECENTS_PANEL,
-                                true /* force */, true /* delayed*/);
+                    if (mShadeController.isExpandedVisible() && !mBouncerShowing) {
+                        mShadeController.animateCollapseShadeDelayed();
                     } else {
                         // Do it after DismissAction has been processed to conserve the needed
                         // ordering.
@@ -2581,7 +2534,7 @@
                             flags |= CommandQueue.FLAG_EXCLUDE_NOTIFICATION_PANEL;
                         }
                     }
-                    mShadeController.animateCollapsePanels(flags);
+                    mShadeController.animateCollapseShade(flags);
                 }
             } else if (Intent.ACTION_SCREEN_OFF.equals(action)) {
                 if (mNotificationShadeWindowController != null) {
@@ -2696,10 +2649,9 @@
                 com.android.systemui.R.dimen.physical_power_button_center_screen_location_y));
     }
 
-    // Visibility reporting
     protected void handleVisibleToUserChanged(boolean visibleToUser) {
         if (visibleToUser) {
-            handleVisibleToUserChangedImpl(visibleToUser);
+            onVisibleToUser();
             mNotificationLogger.startNotificationLogging();
 
             if (!mIsBackCallbackRegistered) {
@@ -2716,7 +2668,7 @@
             }
         } else {
             mNotificationLogger.stopNotificationLogging();
-            handleVisibleToUserChangedImpl(visibleToUser);
+            onInvisibleToUser();
 
             if (mIsBackCallbackRegistered) {
                 ViewRootImpl viewRootImpl = getViewRootImpl();
@@ -2736,41 +2688,38 @@
         }
     }
 
-    // Visibility reporting
-    void handleVisibleToUserChangedImpl(boolean visibleToUser) {
-        if (visibleToUser) {
-            /* The LEDs are turned off when the notification panel is shown, even just a little bit.
-             * See also CentralSurfaces.setPanelExpanded for another place where we attempt to do
-             * this.
-             */
-            boolean pinnedHeadsUp = mHeadsUpManager.hasPinnedHeadsUp();
-            boolean clearNotificationEffects =
-                    !mPresenter.isPresenterFullyCollapsed() &&
-                            (mState == StatusBarState.SHADE
-                                    || mState == StatusBarState.SHADE_LOCKED);
-            int notificationLoad = mNotificationsController.getActiveNotificationsCount();
-            if (pinnedHeadsUp && mPresenter.isPresenterFullyCollapsed()) {
-                notificationLoad = 1;
-            }
-            final int finalNotificationLoad = notificationLoad;
-            mUiBgExecutor.execute(() -> {
-                try {
-                    mBarService.onPanelRevealed(clearNotificationEffects,
-                            finalNotificationLoad);
-                } catch (RemoteException ex) {
-                    // Won't fail unless the world has ended.
-                }
-            });
-        } else {
-            mUiBgExecutor.execute(() -> {
-                try {
-                    mBarService.onPanelHidden();
-                } catch (RemoteException ex) {
-                    // Won't fail unless the world has ended.
-                }
-            });
+    void onVisibleToUser() {
+        /* The LEDs are turned off when the notification panel is shown, even just a little bit.
+         * See also CentralSurfaces.setPanelExpanded for another place where we attempt to do
+         * this.
+         */
+        boolean pinnedHeadsUp = mHeadsUpManager.hasPinnedHeadsUp();
+        boolean clearNotificationEffects =
+                !mPresenter.isPresenterFullyCollapsed() && (mState == StatusBarState.SHADE
+                        || mState == StatusBarState.SHADE_LOCKED);
+        int notificationLoad = mNotificationsController.getActiveNotificationsCount();
+        if (pinnedHeadsUp && mPresenter.isPresenterFullyCollapsed()) {
+            notificationLoad = 1;
         }
+        final int finalNotificationLoad = notificationLoad;
+        mUiBgExecutor.execute(() -> {
+            try {
+                mBarService.onPanelRevealed(clearNotificationEffects,
+                        finalNotificationLoad);
+            } catch (RemoteException ex) {
+                // Won't fail unless the world has ended.
+            }
+        });
+    }
 
+    void onInvisibleToUser() {
+        mUiBgExecutor.execute(() -> {
+            try {
+                mBarService.onPanelHidden();
+            } catch (RemoteException ex) {
+                // Won't fail unless the world has ended.
+            }
+        });
     }
 
     private void logStateToEventlog() {
@@ -2948,7 +2897,7 @@
     private void updatePanelExpansionForKeyguard() {
         if (mState == StatusBarState.KEYGUARD && mBiometricUnlockController.getMode()
                 != BiometricUnlockController.MODE_WAKE_AND_UNLOCK && !mBouncerShowing) {
-            mShadeController.instantExpandNotificationsPanel();
+            mShadeController.instantExpandShade();
         }
     }
 
@@ -3067,7 +3016,7 @@
             // too heavy for the CPU and GPU on any device.
             mNavigationBarController.disableAnimationsDuringHide(mDisplayId, delay);
         } else if (!mNotificationPanelViewController.isCollapsing()) {
-            instantCollapseNotificationPanel();
+            mShadeController.instantCollapseShade();
         }
 
         // Keyguard state has changed, but QS is not listening anymore. Make sure to update the tile
@@ -3225,8 +3174,7 @@
     @Override
     public boolean onMenuPressed() {
         if (shouldUnlockOnMenuPressed()) {
-            mShadeController.animateCollapsePanels(
-                    CommandQueue.FLAG_EXCLUDE_RECENTS_PANEL /* flags */, true /* force */);
+            mShadeController.animateCollapseShadeForced();
             return true;
         }
         return false;
@@ -3271,7 +3219,7 @@
         if (mState != StatusBarState.KEYGUARD && mState != StatusBarState.SHADE_LOCKED
                 && !isBouncerShowingOverDream()) {
             if (mNotificationPanelViewController.canPanelBeCollapsed()) {
-                mShadeController.animateCollapsePanels();
+                mShadeController.animateCollapseShade();
             }
             return true;
         }
@@ -3281,8 +3229,7 @@
     @Override
     public boolean onSpacePressed() {
         if (mDeviceInteractive && mState != StatusBarState.SHADE) {
-            mShadeController.animateCollapsePanels(
-                    CommandQueue.FLAG_EXCLUDE_RECENTS_PANEL /* flags */, true /* force */);
+            mShadeController.animateCollapseShadeForced();
             return true;
         }
         return false;
@@ -3322,12 +3269,6 @@
         }
     }
 
-    @Override
-    public void instantCollapseNotificationPanel() {
-        mNotificationPanelViewController.instantCollapse();
-        mShadeController.runPostCollapseRunnables();
-    }
-
     /**
      * Collapse the panel directly if we are on the main thread, post the collapsing on the main
      * thread if we are not.
@@ -3335,9 +3276,9 @@
     @Override
     public void collapsePanelOnMainThread() {
         if (Looper.getMainLooper().isCurrentThread()) {
-            mShadeController.collapsePanel();
+            mShadeController.collapseShade();
         } else {
-            mContext.getMainExecutor().execute(mShadeController::collapsePanel);
+            mContext.getMainExecutor().execute(mShadeController::collapseShade);
         }
     }
 
@@ -3477,7 +3418,7 @@
             mNotificationShadeWindowViewController.cancelCurrentTouch();
         }
         if (mPanelExpanded && mState == StatusBarState.SHADE) {
-            mShadeController.animateCollapsePanels();
+            mShadeController.animateCollapseShade();
         }
     }
 
@@ -3540,7 +3481,7 @@
             // The unlocked screen off and fold to aod animations might use our LightRevealScrim -
             // we need to be expanded for it to be visible.
             if (mDozeParameters.shouldShowLightRevealScrim()) {
-                makeExpandedVisible(true);
+                mShadeController.makeExpandedVisible(true);
             }
 
             DejankUtils.stopDetectingBlockingIpcs(tag);
@@ -3569,7 +3510,7 @@
                 // If we are waking up during the screen off animation, we should undo making the
                 // expanded visible (we did that so the LightRevealScrim would be visible).
                 if (mScreenOffAnimationController.shouldHideLightRevealScrimOnWakeUp()) {
-                    makeExpandedInvisible();
+                    mShadeController.makeExpandedInvisible();
                 }
 
             });
@@ -3624,6 +3565,12 @@
         mNotificationIconAreaController.setAnimationsEnabled(!disabled);
     }
 
+    //TODO(b/257041702) delete
+    @Override
+    public void makeExpandedVisible(boolean force) {
+        mShadeController.makeExpandedVisible(force);
+    }
+
     final ScreenLifecycle.Observer mScreenObserver = new ScreenLifecycle.Observer() {
         @Override
         public void onScreenTurningOn(Runnable onDrawn) {
@@ -3904,8 +3851,7 @@
                 Settings.Secure.putInt(mContext.getContentResolver(),
                         Settings.Secure.SHOW_NOTE_ABOUT_NOTIFICATION_HIDING, 0);
                 if (BANNER_ACTION_SETUP.equals(action)) {
-                    mShadeController.animateCollapsePanels(CommandQueue.FLAG_EXCLUDE_RECENTS_PANEL,
-                            true /* force */);
+                    mShadeController.animateCollapseShadeForced();
                     mContext.startActivity(new Intent(Settings.ACTION_APP_NOTIFICATION_REDACTION)
                             .addFlags(Intent.FLAG_ACTIVITY_NEW_TASK)
 
@@ -3967,7 +3913,7 @@
                     action.run();
                 }).start();
 
-                return collapsePanel ? mShadeController.collapsePanel() : willAnimateOnKeyguard;
+                return collapsePanel ? mShadeController.collapseShade() : willAnimateOnKeyguard;
             }
 
             @Override
@@ -4062,8 +4008,7 @@
         mMainExecutor.execute(runnable);
     }
 
-    @Override
-    public void visibilityChanged(boolean visible) {
+    private void onShadeVisibilityChanged(boolean visible) {
         if (mVisible != visible) {
             mVisible = visible;
             if (!visible) {
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 44ad604..f9d316b 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/StatusBarKeyguardViewManager.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/StatusBarKeyguardViewManager.java
@@ -469,6 +469,9 @@
             // Don't expand to the bouncer. Instead transition back to the lock screen (see
             // CentralSurfaces#showBouncerOrLockScreenIfKeyguard)
             return;
+        } else if (mKeyguardStateController.isOccluded()
+                && !mDreamOverlayStateController.isOverlayActive()) {
+            return;
         } else if (needsFullscreenBouncer()) {
             if (mPrimaryBouncer != null) {
                 mPrimaryBouncer.setExpansion(KeyguardBouncer.EXPANSION_VISIBLE);
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/phone/StatusBarNotificationActivityStarter.java b/packages/SystemUI/src/com/android/systemui/statusbar/phone/StatusBarNotificationActivityStarter.java
index b6ae4a0..05bf860 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/StatusBarNotificationActivityStarter.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/StatusBarNotificationActivityStarter.java
@@ -260,11 +260,11 @@
 
         if (showOverLockscreen) {
             mShadeController.addPostCollapseAction(runnable);
-            mShadeController.collapsePanel(true /* animate */);
+            mShadeController.collapseShade(true /* animate */);
         } else if (mKeyguardStateController.isShowing()
                 && mCentralSurfaces.isOccluded()) {
             mStatusBarKeyguardViewManager.addAfterKeyguardGoneRunnable(runnable);
-            mShadeController.collapsePanel();
+            mShadeController.collapseShade();
         } else {
             runnable.run();
         }
@@ -406,7 +406,7 @@
 
     private void expandBubbleStack(NotificationEntry entry) {
         mBubblesManagerOptional.get().expandStackAndSelectBubble(entry);
-        mShadeController.collapsePanel();
+        mShadeController.collapseShade();
     }
 
     private void startNotificationIntent(
@@ -593,9 +593,9 @@
 
     private void collapseOnMainThread() {
         if (Looper.getMainLooper().isCurrentThread()) {
-            mShadeController.collapsePanel();
+            mShadeController.collapseShade();
         } else {
-            mMainThreadHandler.post(mShadeController::collapsePanel);
+            mMainThreadHandler.post(mShadeController::collapseShade);
         }
     }
 }
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/phone/StatusBarRemoteInputCallback.java b/packages/SystemUI/src/com/android/systemui/statusbar/phone/StatusBarRemoteInputCallback.java
index 8a49850..7fe01825 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/StatusBarRemoteInputCallback.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/StatusBarRemoteInputCallback.java
@@ -180,7 +180,7 @@
                 }
             };
             mShadeController.postOnShadeExpanded(clickPendingViewRunnable);
-            mShadeController.instantExpandNotificationsPanel();
+            mShadeController.instantExpandShade();
         }
     }
 
diff --git a/packages/SystemUI/src/com/android/systemui/wmshell/BubblesManager.java b/packages/SystemUI/src/com/android/systemui/wmshell/BubblesManager.java
index a4384d5..7033ccd 100644
--- a/packages/SystemUI/src/com/android/systemui/wmshell/BubblesManager.java
+++ b/packages/SystemUI/src/com/android/systemui/wmshell/BubblesManager.java
@@ -549,7 +549,7 @@
         } catch (RemoteException e) {
             Log.e(TAG, e.getMessage());
         }
-        mShadeController.collapsePanel(true);
+        mShadeController.collapseShade(true);
         if (entry.getRow() != null) {
             entry.getRow().updateBubbleButton();
         }
@@ -597,7 +597,7 @@
         }
 
         if (shouldBubble) {
-            mShadeController.collapsePanel(true);
+            mShadeController.collapseShade(true);
             if (entry.getRow() != null) {
                 entry.getRow().updateBubbleButton();
             }
diff --git a/packages/SystemUI/tests/robolectric/config/robolectric.properties b/packages/SystemUI/tests/robolectric/config/robolectric.properties
new file mode 100644
index 0000000..2a75bd9
--- /dev/null
+++ b/packages/SystemUI/tests/robolectric/config/robolectric.properties
@@ -0,0 +1,16 @@
+#
+# 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.
+#
+sdk=NEWEST_SDK
\ No newline at end of file
diff --git a/packages/SystemUI/tests/robolectric/src/com/android/systemui/robotests/SysuiResourceLoadingTest.java b/packages/SystemUI/tests/robolectric/src/com/android/systemui/robotests/SysuiResourceLoadingTest.java
new file mode 100644
index 0000000..188dff2
--- /dev/null
+++ b/packages/SystemUI/tests/robolectric/src/com/android/systemui/robotests/SysuiResourceLoadingTest.java
@@ -0,0 +1,35 @@
+/*
+ * 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.robotests;
+
+import androidx.test.filters.SmallTest;
+import androidx.test.runner.AndroidJUnit4;
+import static com.google.common.truth.Truth.assertThat;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+
+@SmallTest
+@RunWith(AndroidJUnit4.class)
+public class SysuiResourceLoadingTest extends SysuiRoboBase {
+    @Test
+    public void testResources() {
+        assertThat(getContext().getString(com.android.systemui.R.string.app_label))
+                .isEqualTo("System UI");
+        assertThat(getContext().getString(com.android.systemui.tests.R.string.test_content))
+                .isNotEmpty();
+    }
+}
diff --git a/packages/SystemUI/tests/robolectric/src/com/android/systemui/robotests/SysuiRoboBase.java b/packages/SystemUI/tests/robolectric/src/com/android/systemui/robotests/SysuiRoboBase.java
new file mode 100644
index 0000000..d9686bb
--- /dev/null
+++ b/packages/SystemUI/tests/robolectric/src/com/android/systemui/robotests/SysuiRoboBase.java
@@ -0,0 +1,27 @@
+/*
+ * 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.robotests;
+
+import android.content.Context;
+
+import androidx.test.InstrumentationRegistry;
+
+public class SysuiRoboBase {
+    public Context getContext() {
+        return InstrumentationRegistry.getContext();
+    }
+}
diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/CommandQueueTest.java b/packages/SystemUI/tests/src/com/android/systemui/statusbar/CommandQueueTest.java
index e1346ea..0000c32 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/CommandQueueTest.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/CommandQueueTest.java
@@ -118,13 +118,6 @@
     }
 
     @Test
-    public void testCollapsePanels() {
-        mCommandQueue.animateCollapsePanels();
-        waitForIdleSync();
-        verify(mCallbacks).animateCollapsePanels(eq(0), eq(false));
-    }
-
-    @Test
     public void testExpandSettings() {
         String panel = "some_panel";
         mCommandQueue.animateExpandSettingsPanel(panel);
diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/row/ExpandableNotificationRowDragControllerTest.java b/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/row/ExpandableNotificationRowDragControllerTest.java
index ed2afe7..915924f1 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/row/ExpandableNotificationRowDragControllerTest.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/row/ExpandableNotificationRowDragControllerTest.java
@@ -41,7 +41,6 @@
 import com.android.systemui.plugins.statusbar.NotificationMenuRowPlugin;
 import com.android.systemui.shade.ShadeController;
 import com.android.systemui.statusbar.notification.logging.NotificationPanelLogger;
-import com.android.systemui.statusbar.notification.logging.NotificationPanelLoggerFake;
 import com.android.systemui.statusbar.policy.HeadsUpManager;
 
 import org.junit.Before;
diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/CentralSurfacesCommandQueueCallbacksTest.java b/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/CentralSurfacesCommandQueueCallbacksTest.java
index d5bfe1f..c17c5b0 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/CentralSurfacesCommandQueueCallbacksTest.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/CentralSurfacesCommandQueueCallbacksTest.java
@@ -136,7 +136,7 @@
                 StatusBarManager.DISABLE2_NOTIFICATION_SHADE, false);
 
         verify(mCentralSurfaces).updateQsExpansionEnabled();
-        verify(mShadeController).animateCollapsePanels();
+        verify(mShadeController).animateCollapseShade();
 
         // Trying to open it does nothing.
         mSbcqCallbacks.animateExpandNotificationsPanel();
@@ -154,7 +154,7 @@
         mSbcqCallbacks.disable(DEFAULT_DISPLAY, StatusBarManager.DISABLE_NONE,
                 StatusBarManager.DISABLE2_NONE, false);
         verify(mCentralSurfaces).updateQsExpansionEnabled();
-        verify(mShadeController, never()).animateCollapsePanels();
+        verify(mShadeController, never()).animateCollapseShade();
 
         // Can now be opened.
         mSbcqCallbacks.animateExpandNotificationsPanel();
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 013e727..ed84e42 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
@@ -392,10 +392,21 @@
             return null;
         }).when(mNotificationShadeWindowController).batchApplyWindowLayoutParams(any());
 
-        mShadeController = spy(new ShadeControllerImpl(mCommandQueue,
-                mStatusBarStateController, mNotificationShadeWindowController,
-                mStatusBarKeyguardViewManager, mContext.getSystemService(WindowManager.class),
-                () -> Optional.of(mCentralSurfaces), () -> mAssistManager));
+        mShadeController = spy(new ShadeControllerImpl(
+                mCommandQueue,
+                mKeyguardStateController,
+                mStatusBarStateController,
+                mStatusBarKeyguardViewManager,
+                mStatusBarWindowController,
+                mNotificationShadeWindowController,
+                mContext.getSystemService(WindowManager.class),
+                () -> mAssistManager,
+                () -> mNotificationGutsManager
+        ));
+        mShadeController.setNotificationPanelViewController(mNotificationPanelViewController);
+        mShadeController.setNotificationShadeWindowViewController(
+                mNotificationShadeWindowViewController);
+        mShadeController.setNotificationPresenter(mNotificationPresenter);
 
         when(mOperatorNameViewControllerFactory.create(any()))
                 .thenReturn(mOperatorNameViewController);
@@ -492,6 +503,7 @@
                 return mViewRootImpl;
             }
         };
+        mCentralSurfaces.initShadeVisibilityListener();
         when(mViewRootImpl.getOnBackInvokedDispatcher())
                 .thenReturn(mOnBackInvokedDispatcher);
         when(mKeyguardViewMediator.registerCentralSurfaces(
@@ -807,7 +819,7 @@
 
         when(mNotificationPanelViewController.canPanelBeCollapsed()).thenReturn(true);
         mOnBackInvokedCallback.getValue().onBackInvoked();
-        verify(mShadeController).animateCollapsePanels();
+        verify(mShadeController).animateCollapseShade();
     }
 
     @Test
@@ -1030,7 +1042,7 @@
     }
 
     @Test
-    public void collapseShade_callsAnimateCollapsePanels_whenExpanded() {
+    public void collapseShade_callsanimateCollapseShade_whenExpanded() {
         // GIVEN the shade is expanded
         mCentralSurfaces.onShadeExpansionFullyChanged(true);
         mCentralSurfaces.setBarStateForTest(StatusBarState.SHADE);
@@ -1038,12 +1050,12 @@
         // WHEN collapseShade is called
         mCentralSurfaces.collapseShade();
 
-        // VERIFY that animateCollapsePanels is called
-        verify(mShadeController).animateCollapsePanels();
+        // VERIFY that animateCollapseShade is called
+        verify(mShadeController).animateCollapseShade();
     }
 
     @Test
-    public void collapseShade_doesNotCallAnimateCollapsePanels_whenCollapsed() {
+    public void collapseShade_doesNotCallanimateCollapseShade_whenCollapsed() {
         // GIVEN the shade is collapsed
         mCentralSurfaces.onShadeExpansionFullyChanged(false);
         mCentralSurfaces.setBarStateForTest(StatusBarState.SHADE);
@@ -1051,12 +1063,12 @@
         // WHEN collapseShade is called
         mCentralSurfaces.collapseShade();
 
-        // VERIFY that animateCollapsePanels is NOT called
-        verify(mShadeController, never()).animateCollapsePanels();
+        // VERIFY that animateCollapseShade is NOT called
+        verify(mShadeController, never()).animateCollapseShade();
     }
 
     @Test
-    public void collapseShadeForBugReport_callsAnimateCollapsePanels_whenFlagDisabled() {
+    public void collapseShadeForBugReport_callsanimateCollapseShade_whenFlagDisabled() {
         // GIVEN the shade is expanded & flag enabled
         mCentralSurfaces.onShadeExpansionFullyChanged(true);
         mCentralSurfaces.setBarStateForTest(StatusBarState.SHADE);
@@ -1065,12 +1077,12 @@
         // WHEN collapseShadeForBugreport is called
         mCentralSurfaces.collapseShadeForBugreport();
 
-        // VERIFY that animateCollapsePanels is called
-        verify(mShadeController).animateCollapsePanels();
+        // VERIFY that animateCollapseShade is called
+        verify(mShadeController).animateCollapseShade();
     }
 
     @Test
-    public void collapseShadeForBugReport_doesNotCallAnimateCollapsePanels_whenFlagEnabled() {
+    public void collapseShadeForBugReport_doesNotCallanimateCollapseShade_whenFlagEnabled() {
         // GIVEN the shade is expanded & flag enabled
         mCentralSurfaces.onShadeExpansionFullyChanged(true);
         mCentralSurfaces.setBarStateForTest(StatusBarState.SHADE);
@@ -1079,8 +1091,8 @@
         // WHEN collapseShadeForBugreport is called
         mCentralSurfaces.collapseShadeForBugreport();
 
-        // VERIFY that animateCollapsePanels is called
-        verify(mShadeController, never()).animateCollapsePanels();
+        // VERIFY that animateCollapseShade is called
+        verify(mShadeController, never()).animateCollapseShade();
     }
 
     @Test
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 bf5186b..e467d93 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,17 @@
     }
 
     @Test
+    public void onPanelExpansionChanged_neverTranslatesBouncerWhenOccluded() {
+        when(mKeyguardStateController.isOccluded()).thenReturn(true);
+        mStatusBarKeyguardViewManager.onPanelExpansionChanged(
+                expansionEvent(
+                        /* fraction= */ KeyguardBouncer.EXPANSION_VISIBLE,
+                        /* expanded= */ true,
+                        /* tracking= */ false));
+        verify(mPrimaryBouncer, never()).setExpansion(anyFloat());
+    }
+
+    @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
diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/StatusBarNotificationActivityStarterTest.java b/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/StatusBarNotificationActivityStarterTest.java
index ce54d78..cae414a 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/StatusBarNotificationActivityStarterTest.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/StatusBarNotificationActivityStarterTest.java
@@ -263,7 +263,7 @@
         while (!runnables.isEmpty()) runnables.remove(0).run();
 
         // Then
-        verify(mShadeController, atLeastOnce()).collapsePanel();
+        verify(mShadeController, atLeastOnce()).collapseShade();
 
         verify(mActivityLaunchAnimator).startPendingIntentWithAnimation(any(),
                 eq(false) /* animate */, any(), any());
@@ -296,7 +296,7 @@
         verify(mBubblesManager).expandStackAndSelectBubble(eq(mBubbleNotificationRow.getEntry()));
 
         // This is called regardless, and simply short circuits when there is nothing to do.
-        verify(mShadeController, atLeastOnce()).collapsePanel();
+        verify(mShadeController, atLeastOnce()).collapseShade();
 
         verify(mAssistManager).hideAssist();
 
@@ -329,7 +329,7 @@
         // Then
         verify(mBubblesManager).expandStackAndSelectBubble(eq(mBubbleNotificationRow.getEntry()));
 
-        verify(mShadeController, atLeastOnce()).collapsePanel();
+        verify(mShadeController, atLeastOnce()).collapseShade();
 
         verify(mAssistManager).hideAssist();
 
@@ -357,7 +357,7 @@
         // Then
         verify(mBubblesManager).expandStackAndSelectBubble(mBubbleNotificationRow.getEntry());
 
-        verify(mShadeController, atLeastOnce()).collapsePanel();
+        verify(mShadeController, atLeastOnce()).collapseShade();
 
         verify(mAssistManager).hideAssist();
 
diff --git a/services/core/java/com/android/server/audio/AudioService.java b/services/core/java/com/android/server/audio/AudioService.java
index 2e81067..8aa898e 100644
--- a/services/core/java/com/android/server/audio/AudioService.java
+++ b/services/core/java/com/android/server/audio/AudioService.java
@@ -7099,9 +7099,10 @@
 
     private @AudioManager.DeviceVolumeBehavior
             int getDeviceVolumeBehaviorInt(@NonNull AudioDeviceAttributes device) {
-        // translate Java device type to native device type (for the devices masks for full / fixed)
-        final int audioSystemDeviceOut = AudioDeviceInfo.convertDeviceTypeToInternalDevice(
-                device.getType());
+        // Get the internal type set by the AudioDeviceAttributes constructor which is always more
+        // exact (avoids double conversions) than a conversion from SDK type via
+        // AudioDeviceInfo.convertDeviceTypeToInternalDevice()
+        final int audioSystemDeviceOut = device.getInternalType();
 
         int setDeviceVolumeBehavior = retrieveStoredDeviceVolumeBehavior(audioSystemDeviceOut);
         if (setDeviceVolumeBehavior != AudioManager.DEVICE_VOLUME_BEHAVIOR_UNSET) {
diff --git a/services/core/java/com/android/server/display/DisplayManagerService.java b/services/core/java/com/android/server/display/DisplayManagerService.java
index 05cd67f..c5cb08d 100644
--- a/services/core/java/com/android/server/display/DisplayManagerService.java
+++ b/services/core/java/com/android/server/display/DisplayManagerService.java
@@ -105,7 +105,6 @@
 import android.os.UserManager;
 import android.provider.DeviceConfig;
 import android.provider.Settings;
-import android.sysprop.DisplayProperties;
 import android.text.TextUtils;
 import android.util.ArraySet;
 import android.util.EventLog;
@@ -451,8 +450,6 @@
         }
     };
 
-    private final boolean mAllowNonNativeRefreshRateOverride;
-
     private final BrightnessSynchronizer mBrightnessSynchronizer;
 
     /**
@@ -506,7 +503,6 @@
         ColorSpace[] colorSpaces = SurfaceControl.getCompositionColorSpaces();
         mWideColorSpace = colorSpaces[1];
         mOverlayProperties = SurfaceControl.getOverlaySupport();
-        mAllowNonNativeRefreshRateOverride = mInjector.getAllowNonNativeRefreshRateOverride();
         mSystemReady = false;
     }
 
@@ -930,24 +926,20 @@
             }
         }
 
-        if (mAllowNonNativeRefreshRateOverride) {
-            overriddenInfo.refreshRateOverride = frameRateHz;
-            if (!CompatChanges.isChangeEnabled(DISPLAY_MODE_RETURNS_PHYSICAL_REFRESH_RATE,
-                    callingUid)) {
-                overriddenInfo.supportedModes = Arrays.copyOf(info.supportedModes,
-                        info.supportedModes.length + 1);
-                overriddenInfo.supportedModes[overriddenInfo.supportedModes.length - 1] =
-                        new Display.Mode(Display.DISPLAY_MODE_ID_FOR_FRAME_RATE_OVERRIDE,
-                                currentMode.getPhysicalWidth(), currentMode.getPhysicalHeight(),
-                                overriddenInfo.refreshRateOverride);
-                overriddenInfo.modeId =
-                        overriddenInfo.supportedModes[overriddenInfo.supportedModes.length - 1]
-                                .getModeId();
-            }
-            return overriddenInfo;
+        overriddenInfo.refreshRateOverride = frameRateHz;
+        if (!CompatChanges.isChangeEnabled(DISPLAY_MODE_RETURNS_PHYSICAL_REFRESH_RATE,
+                callingUid)) {
+            overriddenInfo.supportedModes = Arrays.copyOf(info.supportedModes,
+                    info.supportedModes.length + 1);
+            overriddenInfo.supportedModes[overriddenInfo.supportedModes.length - 1] =
+                    new Display.Mode(Display.DISPLAY_MODE_ID_FOR_FRAME_RATE_OVERRIDE,
+                            currentMode.getPhysicalWidth(), currentMode.getPhysicalHeight(),
+                            overriddenInfo.refreshRateOverride);
+            overriddenInfo.modeId =
+                    overriddenInfo.supportedModes[overriddenInfo.supportedModes.length - 1]
+                            .getModeId();
         }
-
-        return info;
+        return overriddenInfo;
     }
 
     private DisplayInfo getDisplayInfoInternal(int displayId, int callingUid) {
@@ -2602,11 +2594,6 @@
         long getDefaultDisplayDelayTimeout() {
             return WAIT_FOR_DEFAULT_DISPLAY_TIMEOUT;
         }
-
-        boolean getAllowNonNativeRefreshRateOverride() {
-            return DisplayProperties
-                    .debug_allow_non_native_refresh_rate_override().orElse(true);
-        }
     }
 
     @VisibleForTesting
diff --git a/services/core/java/com/android/server/pm/UserVisibilityMediator.java b/services/core/java/com/android/server/pm/UserVisibilityMediator.java
index 2650b23..878855a 100644
--- a/services/core/java/com/android/server/pm/UserVisibilityMediator.java
+++ b/services/core/java/com/android/server/pm/UserVisibilityMediator.java
@@ -575,9 +575,7 @@
             ipw.println(mCurrentUserId);
 
             ipw.print("Visible users: ");
-            // TODO: merge 2 lines below if/when IntArray implements toString()...
-            IntArray visibleUsers = getVisibleUsers();
-            ipw.println(java.util.Arrays.toString(visibleUsers.toArray()));
+            ipw.println(getVisibleUsers());
 
             dumpSparseIntArray(ipw, mStartedProfileGroupIds, "started user / profile group",
                     "u", "pg");
diff --git a/services/core/java/com/android/server/power/PowerManagerService.java b/services/core/java/com/android/server/power/PowerManagerService.java
index 9281f4b..1ea0988 100644
--- a/services/core/java/com/android/server/power/PowerManagerService.java
+++ b/services/core/java/com/android/server/power/PowerManagerService.java
@@ -2204,6 +2204,15 @@
                     if (sQuiescent) {
                         mDirty |= DIRTY_QUIESCENT;
                     }
+                    PowerGroup defaultGroup = mPowerGroups.get(Display.DEFAULT_DISPLAY_GROUP);
+                    if (defaultGroup.getWakefulnessLocked() == WAKEFULNESS_DOZING) {
+                        // Workaround for b/187231320 where the AOD can get stuck in a "half on /
+                        // half off" state when a non-default-group VirtualDisplay causes the global
+                        // wakefulness to change to awake, even though the default display is
+                        // dozing. We set sandman summoned to restart dreaming to get it unstuck.
+                        // TODO(b/255688811) - fix this so that AOD never gets interrupted at all.
+                        defaultGroup.setSandmanSummonedLocked(true);
+                    }
                     break;
 
                 case WAKEFULNESS_ASLEEP:
diff --git a/services/core/java/com/android/server/trust/TrustAgentWrapper.java b/services/core/java/com/android/server/trust/TrustAgentWrapper.java
index 0b1f6b9..f971db9 100644
--- a/services/core/java/com/android/server/trust/TrustAgentWrapper.java
+++ b/services/core/java/com/android/server/trust/TrustAgentWrapper.java
@@ -107,6 +107,7 @@
     // Trust state
     private boolean mTrusted;
     private boolean mWaitingForTrustableDowngrade = false;
+    private boolean mWithinSecurityLockdownWindow = false;
     private boolean mTrustable;
     private CharSequence mMessage;
     private boolean mDisplayTrustGrantedMessage;
@@ -160,6 +161,7 @@
                     mDisplayTrustGrantedMessage = (flags & FLAG_GRANT_TRUST_DISPLAY_MESSAGE) != 0;
                     if ((flags & FLAG_GRANT_TRUST_TEMPORARY_AND_RENEWABLE) != 0) {
                         mWaitingForTrustableDowngrade = true;
+                        setSecurityWindowTimer();
                     } else {
                         mWaitingForTrustableDowngrade = false;
                     }
@@ -452,6 +454,9 @@
             if (mBound) {
                 scheduleRestart();
             }
+            if (mWithinSecurityLockdownWindow) {
+                mTrustManagerService.lockUser(mUserId);
+            }
             // mTrustDisabledByDpm maintains state
         }
     };
@@ -673,6 +678,22 @@
         }
     }
 
+    private void setSecurityWindowTimer() {
+        mWithinSecurityLockdownWindow = true;
+        long expiration = SystemClock.elapsedRealtime() + (15 * 1000); // timer for 15 seconds
+        mAlarmManager.setExact(
+                AlarmManager.ELAPSED_REALTIME_WAKEUP,
+                expiration,
+                TAG,
+                new AlarmManager.OnAlarmListener() {
+                    @Override
+                    public void onAlarm() {
+                        mWithinSecurityLockdownWindow = false;
+                    }
+                },
+                Handler.getMain());
+    }
+
     public boolean isManagingTrust() {
         return mManagingTrust && !mTrustDisabledByDpm;
     }
@@ -691,7 +712,6 @@
 
     public void destroy() {
         mHandler.removeMessages(MSG_RESTART_TIMEOUT);
-
         if (!mBound) {
             return;
         }
diff --git a/services/core/java/com/android/server/wm/DisplayContent.java b/services/core/java/com/android/server/wm/DisplayContent.java
index f452093..9c920f53 100644
--- a/services/core/java/com/android/server/wm/DisplayContent.java
+++ b/services/core/java/com/android/server/wm/DisplayContent.java
@@ -3380,7 +3380,7 @@
                     }
                 }
                 mWmService.mLatencyTracker.onActionStart(ACTION_ROTATE_SCREEN);
-                controller.mTransitionMetricsReporter.associate(t,
+                controller.mTransitionMetricsReporter.associate(t.getToken(),
                         startTime -> mWmService.mLatencyTracker.onActionEnd(ACTION_ROTATE_SCREEN));
                 startAsyncRotation(false /* shouldDebounce */);
             }
diff --git a/services/core/java/com/android/server/wm/LetterboxUiController.java b/services/core/java/com/android/server/wm/LetterboxUiController.java
index 2dbccae..bb4c482 100644
--- a/services/core/java/com/android/server/wm/LetterboxUiController.java
+++ b/services/core/java/com/android/server/wm/LetterboxUiController.java
@@ -87,10 +87,6 @@
     private final LetterboxConfiguration mLetterboxConfiguration;
     private final ActivityRecord mActivityRecord;
 
-    // Taskbar expanded height. Used to determine whether to crop an app window to display rounded
-    // corners above the taskbar.
-    private final float mExpandedTaskBarHeight;
-
     private boolean mShowWallpaperForLetterboxBackground;
 
     @Nullable
@@ -102,8 +98,6 @@
         // is created in its constructor. It shouldn't be used in this constructor but it's safe
         // to use it after since controller is only used in ActivityRecord.
         mActivityRecord = activityRecord;
-        mExpandedTaskBarHeight =
-                getResources().getDimensionPixelSize(R.dimen.taskbar_frame_height);
     }
 
     /** Cleans up {@link Letterbox} if it exists.*/
@@ -285,14 +279,17 @@
     }
 
     float getSplitScreenAspectRatio() {
+        // Getting the same aspect ratio that apps get in split screen.
+        final DisplayContent displayContent = mActivityRecord.getDisplayContent();
+        if (displayContent == null) {
+            return getDefaultMinAspectRatioForUnresizableApps();
+        }
         int dividerWindowWidth =
                 getResources().getDimensionPixelSize(R.dimen.docked_stack_divider_thickness);
         int dividerInsets =
                 getResources().getDimensionPixelSize(R.dimen.docked_stack_divider_insets);
         int dividerSize = dividerWindowWidth - dividerInsets * 2;
-
-        // Getting the same aspect ratio that apps get in split screen.
-        Rect bounds = new Rect(mActivityRecord.getDisplayContent().getBounds());
+        final Rect bounds = new Rect(displayContent.getBounds());
         if (bounds.width() >= bounds.height()) {
             bounds.inset(/* dx */ dividerSize / 2, /* dy */ 0);
             bounds.right = bounds.centerX();
@@ -555,7 +552,6 @@
         final InsetsSource taskbarInsetsSource = getTaskbarInsetsSource(mainWindow);
 
         return taskbarInsetsSource != null
-                && taskbarInsetsSource.getFrame().height() >= mExpandedTaskBarHeight
                 && taskbarInsetsSource.isVisible();
     }
 
diff --git a/services/core/java/com/android/server/wm/Transition.java b/services/core/java/com/android/server/wm/Transition.java
index b277804..9cb13e4 100644
--- a/services/core/java/com/android/server/wm/Transition.java
+++ b/services/core/java/com/android/server/wm/Transition.java
@@ -91,6 +91,7 @@
 
 import java.lang.annotation.Retention;
 import java.lang.annotation.RetentionPolicy;
+import java.lang.ref.WeakReference;
 import java.util.ArrayList;
 import java.util.List;
 import java.util.Objects;
@@ -100,7 +101,7 @@
  * Represents a logical transition.
  * @see TransitionController
  */
-class Transition extends Binder implements BLASTSyncEngine.TransactionReadyListener {
+class Transition implements BLASTSyncEngine.TransactionReadyListener {
     private static final String TAG = "Transition";
     private static final String TRACE_NAME_PLAY_TRANSITION = "PlayTransition";
 
@@ -151,6 +152,7 @@
     private @TransitionFlags int mFlags;
     private final TransitionController mController;
     private final BLASTSyncEngine mSyncEngine;
+    private final Token mToken;
     private RemoteTransition mRemoteTransition = null;
 
     /** Only use for clean-up after binder death! */
@@ -213,10 +215,26 @@
         mFlags = flags;
         mController = controller;
         mSyncEngine = syncEngine;
+        mToken = new Token(this);
 
         controller.mTransitionTracer.logState(this);
     }
 
+    @Nullable
+    static Transition fromBinder(@NonNull IBinder token) {
+        try {
+            return ((Token) token).mTransition.get();
+        } catch (ClassCastException e) {
+            Slog.w(TAG, "Invalid transition token: " + token, e);
+            return null;
+        }
+    }
+
+    @NonNull
+    IBinder getToken() {
+        return mToken;
+    }
+
     void addFlag(int flag) {
         mFlags |= flag;
     }
@@ -726,6 +744,11 @@
             Trace.asyncTraceEnd(TRACE_TAG_WINDOW_MANAGER, TRACE_NAME_PLAY_TRANSITION,
                     System.identityHashCode(this));
         }
+        // Close the transactions now. They were originally copied to Shell in case we needed to
+        // apply them due to a remote failure. Since we don't need to apply them anymore, free them
+        // immediately.
+        if (mStartTransaction != null) mStartTransaction.close();
+        if (mFinishTransaction != null) mFinishTransaction.close();
         mStartTransaction = mFinishTransaction = null;
         if (mState < STATE_PLAYING) {
             throw new IllegalStateException("Can't finish a non-playing transition " + mSyncId);
@@ -867,6 +890,7 @@
             mController.mAtm.mWindowManager.updateRotation(false /* alwaysSendConfiguration */,
                     false /* forceRelayout */);
         }
+        cleanUpInternal();
     }
 
     void abort() {
@@ -909,6 +933,7 @@
             dc.getPendingTransaction().merge(transaction);
             mSyncId = -1;
             mOverrideOptions = null;
+            cleanUpInternal();
             return;
         }
 
@@ -1026,7 +1051,9 @@
                 ProtoLog.v(ProtoLogGroup.WM_DEBUG_WINDOW_TRANSITIONS,
                         "Calling onTransitionReady: %s", info);
                 mController.getTransitionPlayer().onTransitionReady(
-                        this, info, transaction, mFinishTransaction);
+                        mToken, info, transaction, mFinishTransaction);
+                // Since we created root-leash but no longer reference it from core, release it now
+                info.releaseAnimSurfaces();
                 if (Trace.isTagEnabled(TRACE_TAG_WINDOW_MANAGER)) {
                     Trace.asyncTraceBegin(TRACE_TAG_WINDOW_MANAGER, TRACE_NAME_PLAY_TRANSITION,
                             System.identityHashCode(this));
@@ -1059,7 +1086,17 @@
         if (mFinishTransaction != null) {
             mFinishTransaction.apply();
         }
-        mController.finishTransition(this);
+        mController.finishTransition(mToken);
+    }
+
+    private void cleanUpInternal() {
+        // Clean-up any native references.
+        for (int i = 0; i < mChanges.size(); ++i) {
+            final ChangeInfo ci = mChanges.valueAt(i);
+            if (ci.mSnapshot != null) {
+                ci.mSnapshot.release();
+            }
+        }
     }
 
     /** @see RecentsAnimationController#attachNavigationBarToApp */
@@ -1815,10 +1852,6 @@
         return isCollecting() && mSyncId >= 0;
     }
 
-    static Transition fromBinder(IBinder binder) {
-        return (Transition) binder;
-    }
-
     @VisibleForTesting
     static class ChangeInfo {
         private static final int FLAG_NONE = 0;
@@ -2325,4 +2358,18 @@
             }
         }
     }
+
+    private static class Token extends Binder {
+        final WeakReference<Transition> mTransition;
+
+        Token(Transition transition) {
+            mTransition = new WeakReference<>(transition);
+        }
+
+        @Override
+        public String toString() {
+            return "Token{" + Integer.toHexString(System.identityHashCode(this)) + " "
+                    + mTransition.get() + "}";
+        }
+    }
 }
diff --git a/services/core/java/com/android/server/wm/TransitionController.java b/services/core/java/com/android/server/wm/TransitionController.java
index 25df511..99527b1 100644
--- a/services/core/java/com/android/server/wm/TransitionController.java
+++ b/services/core/java/com/android/server/wm/TransitionController.java
@@ -458,8 +458,9 @@
                 info = new ActivityManager.RunningTaskInfo();
                 startTask.fillTaskInfo(info);
             }
-            mTransitionPlayer.requestStartTransition(transition, new TransitionRequestInfo(
-                    transition.mType, info, remoteTransition, displayChange));
+            mTransitionPlayer.requestStartTransition(transition.getToken(),
+                    new TransitionRequestInfo(transition.mType, info, remoteTransition,
+                            displayChange));
             transition.setRemoteTransition(remoteTransition);
         } catch (RemoteException e) {
             Slog.e(TAG, "Error requesting transition", e);
diff --git a/services/core/java/com/android/server/wm/WindowOrganizerController.java b/services/core/java/com/android/server/wm/WindowOrganizerController.java
index 4c35178..aa1cf56 100644
--- a/services/core/java/com/android/server/wm/WindowOrganizerController.java
+++ b/services/core/java/com/android/server/wm/WindowOrganizerController.java
@@ -306,7 +306,7 @@
                                         nextTransition.setAllReady();
                                     }
                                 });
-                        return nextTransition;
+                        return nextTransition.getToken();
                     }
                     transition = mTransitionController.createTransition(type);
                 }
@@ -315,7 +315,7 @@
                 if (needsSetReady) {
                     transition.setAllReady();
                 }
-                return transition;
+                return transition.getToken();
             }
         } finally {
             Binder.restoreCallingIdentity(ident);
diff --git a/services/tests/mockingservicestests/src/com/android/server/pm/BackgroundDexOptServiceUnitTest.java b/services/tests/mockingservicestests/src/com/android/server/pm/BackgroundDexOptServiceUnitTest.java
index 5c3d695..01674bb 100644
--- a/services/tests/mockingservicestests/src/com/android/server/pm/BackgroundDexOptServiceUnitTest.java
+++ b/services/tests/mockingservicestests/src/com/android/server/pm/BackgroundDexOptServiceUnitTest.java
@@ -27,6 +27,7 @@
 import static org.mockito.Mockito.atLeastOnce;
 import static org.mockito.Mockito.doThrow;
 import static org.mockito.Mockito.inOrder;
+import static org.mockito.Mockito.mock;
 import static org.mockito.Mockito.never;
 import static org.mockito.Mockito.reset;
 import static org.mockito.Mockito.timeout;
@@ -107,12 +108,16 @@
     @Mock
     private BackgroundDexOptJobService mJobServiceForIdle;
 
-    private final JobParameters mJobParametersForPostBoot = new JobParameters(null,
-            BackgroundDexOptService.JOB_POST_BOOT_UPDATE, null, null, null,
-            0, false, false, null, null, null);
-    private final JobParameters mJobParametersForIdle = new JobParameters(null,
-            BackgroundDexOptService.JOB_IDLE_OPTIMIZE, null, null, null,
-            0, false, false, null, null, null);
+    private final JobParameters mJobParametersForPostBoot =
+            createJobParameters(BackgroundDexOptService.JOB_POST_BOOT_UPDATE);
+    private final JobParameters mJobParametersForIdle =
+            createJobParameters(BackgroundDexOptService.JOB_IDLE_OPTIMIZE);
+
+    private static JobParameters createJobParameters(int jobId) {
+        JobParameters params = mock(JobParameters.class);
+        when(params.getJobId()).thenReturn(jobId);
+        return params;
+    }
 
     private BackgroundDexOptService mService;
 
diff --git a/services/tests/servicestests/src/com/android/server/display/DisplayManagerServiceTest.java b/services/tests/servicestests/src/com/android/server/display/DisplayManagerServiceTest.java
index 9853b7a..ce35626 100644
--- a/services/tests/servicestests/src/com/android/server/display/DisplayManagerServiceTest.java
+++ b/services/tests/servicestests/src/com/android/server/display/DisplayManagerServiceTest.java
@@ -171,22 +171,6 @@
 
     private final DisplayManagerService.Injector mBasicInjector = new BasicInjector();
 
-    private final DisplayManagerService.Injector mAllowNonNativeRefreshRateOverrideInjector =
-            new BasicInjector() {
-                @Override
-                boolean getAllowNonNativeRefreshRateOverride() {
-                    return true;
-                }
-            };
-
-    private final DisplayManagerService.Injector mDenyNonNativeRefreshRateOverrideInjector =
-            new BasicInjector() {
-                @Override
-                boolean getAllowNonNativeRefreshRateOverride() {
-                    return false;
-                }
-            };
-
     @Mock InputManagerInternal mMockInputManagerInternal;
     @Mock VirtualDeviceManagerInternal mMockVirtualDeviceManagerInternal;
     @Mock IVirtualDisplayCallback.Stub mMockAppToken;
@@ -1113,13 +1097,32 @@
     }
 
     /**
-     * Tests that the frame rate override is updated accordingly to the
-     * allowNonNativeRefreshRateOverride policy.
+     * Tests that the frame rate override is returning the correct value from
+     * DisplayInfo#getRefreshRate
      */
     @Test
     public void testDisplayInfoNonNativeFrameRateOverride() throws Exception {
-        testDisplayInfoNonNativeFrameRateOverride(mDenyNonNativeRefreshRateOverrideInjector);
-        testDisplayInfoNonNativeFrameRateOverride(mAllowNonNativeRefreshRateOverrideInjector);
+        DisplayManagerService displayManager =
+                new DisplayManagerService(mContext, mBasicInjector);
+        DisplayManagerService.BinderService displayManagerBinderService =
+                displayManager.new BinderService();
+        registerDefaultDisplays(displayManager);
+        displayManager.onBootPhase(SystemService.PHASE_WAIT_FOR_DEFAULT_DISPLAY);
+
+        FakeDisplayDevice displayDevice = createFakeDisplayDevice(displayManager,
+                new float[]{60f});
+        int displayId = getDisplayIdForDisplayDevice(displayManager, displayManagerBinderService,
+                displayDevice);
+        DisplayInfo displayInfo = displayManagerBinderService.getDisplayInfo(displayId);
+        assertEquals(60f, displayInfo.getRefreshRate(), 0.01f);
+
+        updateFrameRateOverride(displayManager, displayDevice,
+                new DisplayEventReceiver.FrameRateOverride[]{
+                        new DisplayEventReceiver.FrameRateOverride(
+                                Process.myUid(), 20f)
+                });
+        displayInfo = displayManagerBinderService.getDisplayInfo(displayId);
+        assertEquals(20f, displayInfo.getRefreshRate(), 0.01f);
     }
 
     /**
@@ -1147,10 +1150,7 @@
     @Test
     @DisableCompatChanges({DisplayManagerService.DISPLAY_MODE_RETURNS_PHYSICAL_REFRESH_RATE})
     public void testDisplayInfoNonNativeFrameRateOverrideModeCompat() throws Exception {
-        testDisplayInfoNonNativeFrameRateOverrideMode(mDenyNonNativeRefreshRateOverrideInjector,
-                /*compatChangeEnabled*/ false);
-        testDisplayInfoNonNativeFrameRateOverrideMode(mAllowNonNativeRefreshRateOverrideInjector,
-                /*compatChangeEnabled*/  false);
+        testDisplayInfoNonNativeFrameRateOverrideMode(/*compatChangeEnabled*/ false);
     }
 
     /**
@@ -1159,10 +1159,7 @@
     @Test
     @EnableCompatChanges({DisplayManagerService.DISPLAY_MODE_RETURNS_PHYSICAL_REFRESH_RATE})
     public void testDisplayInfoNonNativeFrameRateOverrideMode() throws Exception {
-        testDisplayInfoNonNativeFrameRateOverrideMode(mDenyNonNativeRefreshRateOverrideInjector,
-                /*compatChangeEnabled*/  true);
-        testDisplayInfoNonNativeFrameRateOverrideMode(mAllowNonNativeRefreshRateOverrideInjector,
-                /*compatChangeEnabled*/  true);
+        testDisplayInfoNonNativeFrameRateOverrideMode(/*compatChangeEnabled*/ true);
     }
 
     /**
@@ -1385,10 +1382,9 @@
         assertEquals(expectedMode, displayInfo.getMode());
     }
 
-    private void testDisplayInfoNonNativeFrameRateOverrideMode(
-            DisplayManagerService.Injector injector, boolean compatChangeEnabled) {
+    private void testDisplayInfoNonNativeFrameRateOverrideMode(boolean compatChangeEnabled) {
         DisplayManagerService displayManager =
-                new DisplayManagerService(mContext, injector);
+                new DisplayManagerService(mContext, mBasicInjector);
         DisplayManagerService.BinderService displayManagerBinderService =
                 displayManager.new BinderService();
         registerDefaultDisplays(displayManager);
@@ -1410,40 +1406,12 @@
         Display.Mode expectedMode;
         if (compatChangeEnabled) {
             expectedMode = new Display.Mode(1, 100, 200, 60f);
-        } else if (injector.getAllowNonNativeRefreshRateOverride()) {
-            expectedMode = new Display.Mode(255, 100, 200, 20f);
         } else {
-            expectedMode = new Display.Mode(1, 100, 200, 60f);
+            expectedMode = new Display.Mode(255, 100, 200, 20f);
         }
         assertEquals(expectedMode, displayInfo.getMode());
     }
 
-    private void testDisplayInfoNonNativeFrameRateOverride(
-            DisplayManagerService.Injector injector) {
-        DisplayManagerService displayManager =
-                new DisplayManagerService(mContext, injector);
-        DisplayManagerService.BinderService displayManagerBinderService =
-                displayManager.new BinderService();
-        registerDefaultDisplays(displayManager);
-        displayManager.onBootPhase(SystemService.PHASE_WAIT_FOR_DEFAULT_DISPLAY);
-
-        FakeDisplayDevice displayDevice = createFakeDisplayDevice(displayManager,
-                new float[]{60f});
-        int displayId = getDisplayIdForDisplayDevice(displayManager, displayManagerBinderService,
-                displayDevice);
-        DisplayInfo displayInfo = displayManagerBinderService.getDisplayInfo(displayId);
-        assertEquals(60f, displayInfo.getRefreshRate(), 0.01f);
-
-        updateFrameRateOverride(displayManager, displayDevice,
-                new DisplayEventReceiver.FrameRateOverride[]{
-                        new DisplayEventReceiver.FrameRateOverride(
-                                Process.myUid(), 20f)
-                });
-        displayInfo = displayManagerBinderService.getDisplayInfo(displayId);
-        float expectedRefreshRate = injector.getAllowNonNativeRefreshRateOverride() ? 20f : 60f;
-        assertEquals(expectedRefreshRate, displayInfo.getRefreshRate(), 0.01f);
-    }
-
     private int getDisplayIdForDisplayDevice(
             DisplayManagerService displayManager,
             DisplayManagerService.BinderService displayManagerBinderService,
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 c81db92..6258d6d 100644
--- a/services/tests/servicestests/src/com/android/server/power/PowerManagerServiceTest.java
+++ b/services/tests/servicestests/src/com/android/server/power/PowerManagerServiceTest.java
@@ -2014,6 +2014,50 @@
     }
 
     @Test
+    public void testMultiDisplay_defaultDozing_addNewDisplayDefaultGoesBackToDoze() {
+        final int nonDefaultDisplayGroupId = Display.DEFAULT_DISPLAY_GROUP + 1;
+        final int nonDefaultDisplay = Display.DEFAULT_DISPLAY + 1;
+        final AtomicReference<DisplayManagerInternal.DisplayGroupListener> listener =
+                new AtomicReference<>();
+        doAnswer((Answer<Void>) invocation -> {
+            listener.set(invocation.getArgument(0));
+            return null;
+        }).when(mDisplayManagerInternalMock).registerDisplayGroupListener(any());
+        final DisplayInfo info = new DisplayInfo();
+        info.displayGroupId = nonDefaultDisplayGroupId;
+        when(mDisplayManagerInternalMock.getDisplayInfo(nonDefaultDisplay)).thenReturn(info);
+
+        doAnswer(inv -> {
+            when(mDreamManagerInternalMock.isDreaming()).thenReturn(true);
+            return null;
+        }).when(mDreamManagerInternalMock).startDream(anyBoolean(), anyString());
+
+        createService();
+        startSystem();
+
+        assertThat(mService.getWakefulnessLocked(Display.DEFAULT_DISPLAY_GROUP)).isEqualTo(
+                WAKEFULNESS_AWAKE);
+
+        forceDozing();
+        advanceTime(500);
+
+        assertThat(mService.getWakefulnessLocked(Display.DEFAULT_DISPLAY_GROUP)).isEqualTo(
+                WAKEFULNESS_DOZING);
+        assertThat(mService.getGlobalWakefulnessLocked()).isEqualTo(WAKEFULNESS_DOZING);
+        verify(mDreamManagerInternalMock).startDream(eq(true), anyString());
+
+        listener.get().onDisplayGroupAdded(nonDefaultDisplayGroupId);
+        advanceTime(500);
+
+        assertThat(mService.getGlobalWakefulnessLocked()).isEqualTo(WAKEFULNESS_AWAKE);
+        assertThat(mService.getWakefulnessLocked(nonDefaultDisplayGroupId)).isEqualTo(
+                WAKEFULNESS_AWAKE);
+        assertThat(mService.getWakefulnessLocked(Display.DEFAULT_DISPLAY_GROUP)).isEqualTo(
+                WAKEFULNESS_DOZING);
+        verify(mDreamManagerInternalMock, times(2)).startDream(eq(true), anyString());
+    }
+
+    @Test
     public void testLastSleepTime_notUpdatedWhenDreaming() {
         createService();
         startSystem();
diff --git a/services/tests/wmtests/src/com/android/server/wm/WallpaperControllerTests.java b/services/tests/wmtests/src/com/android/server/wm/WallpaperControllerTests.java
index 9090c55..aab70b5 100644
--- a/services/tests/wmtests/src/com/android/server/wm/WallpaperControllerTests.java
+++ b/services/tests/wmtests/src/com/android/server/wm/WallpaperControllerTests.java
@@ -369,7 +369,7 @@
         final SurfaceControl.Transaction t = mock(SurfaceControl.Transaction.class);
         token.finishSync(t, false /* cancel */);
         transit.onTransactionReady(transit.getSyncId(), t);
-        dc.mTransitionController.finishTransition(transit);
+        dc.mTransitionController.finishTransition(transit.getToken());
         assertFalse(wallpaperWindow.isVisible());
         assertFalse(token.isVisible());
     }
diff --git a/services/tests/wmtests/src/com/android/server/wm/WindowTestsBase.java b/services/tests/wmtests/src/com/android/server/wm/WindowTestsBase.java
index 1348770..5a261bc65 100644
--- a/services/tests/wmtests/src/com/android/server/wm/WindowTestsBase.java
+++ b/services/tests/wmtests/src/com/android/server/wm/WindowTestsBase.java
@@ -1746,7 +1746,7 @@
         }
 
         void startTransition() {
-            mOrganizer.startTransition(mLastTransit, null);
+            mOrganizer.startTransition(mLastTransit.getToken(), null);
         }
 
         void onTransactionReady(SurfaceControl.Transaction t) {
@@ -1759,7 +1759,7 @@
         }
 
         public void finish() {
-            mController.finishTransition(mLastTransit);
+            mController.finishTransition(mLastTransit.getToken());
         }
     }
 }
diff --git a/tests/FlickerTests/src/com/android/server/wm/flicker/BaseTest.kt b/tests/FlickerTests/src/com/android/server/wm/flicker/BaseTest.kt
index 8a1e1fa..3f6a75d 100644
--- a/tests/FlickerTests/src/com/android/server/wm/flicker/BaseTest.kt
+++ b/tests/FlickerTests/src/com/android/server/wm/flicker/BaseTest.kt
@@ -172,4 +172,17 @@
     open fun visibleWindowsShownMoreThanOneConsecutiveEntry() {
         testSpec.assertWm { this.visibleWindowsShownMoreThanOneConsecutiveEntry() }
     }
+
+    open fun cujCompleted() {
+        entireScreenCovered()
+        navBarLayerIsVisibleAtStartAndEnd()
+        navBarWindowIsAlwaysVisible()
+        taskBarLayerIsVisibleAtStartAndEnd()
+        taskBarWindowIsAlwaysVisible()
+        statusBarLayerIsVisibleAtStartAndEnd()
+        statusBarLayerPositionAtStartAndEnd()
+        statusBarWindowIsAlwaysVisible()
+        visibleLayersShownMoreThanOneConsecutiveEntry()
+        visibleWindowsShownMoreThanOneConsecutiveEntry()
+    }
 }
diff --git a/tests/FlickerTests/src/com/android/server/wm/flicker/TEST_MAPPING b/tests/FlickerTests/src/com/android/server/wm/flicker/TEST_MAPPING
new file mode 100644
index 0000000..945de33
--- /dev/null
+++ b/tests/FlickerTests/src/com/android/server/wm/flicker/TEST_MAPPING
@@ -0,0 +1,15 @@
+{
+  "ironwood-postsubmit": [
+    {
+      "name": "FlickerTests",
+      "options": [
+        {
+          "include-annotation": "android.platform.test.annotations.IwTest"
+        },
+        {
+          "exclude-annotation": "org.junit.Ignore"
+        }
+      ]
+    }
+  ]
+}
\ No newline at end of file
diff --git a/tests/FlickerTests/src/com/android/server/wm/flicker/ime/CloseImeWindowToAppTest.kt b/tests/FlickerTests/src/com/android/server/wm/flicker/ime/CloseImeWindowToAppTest.kt
index b9c875a..ef42766 100644
--- a/tests/FlickerTests/src/com/android/server/wm/flicker/ime/CloseImeWindowToAppTest.kt
+++ b/tests/FlickerTests/src/com/android/server/wm/flicker/ime/CloseImeWindowToAppTest.kt
@@ -17,6 +17,7 @@
 package com.android.server.wm.flicker.ime
 
 import android.platform.test.annotations.FlakyTest
+import android.platform.test.annotations.IwTest
 import android.platform.test.annotations.Presubmit
 import androidx.test.filters.RequiresDevice
 import com.android.server.wm.flicker.BaseTest
@@ -101,6 +102,16 @@
         testSpec.assertWm { this.isAppWindowOnTop(testApp) }
     }
 
+    @Test
+    @IwTest(focusArea = "ime")
+    override fun cujCompleted() {
+        super.cujCompleted()
+        navBarLayerPositionAtStartAndEnd()
+        imeLayerBecomesInvisible()
+        imeAppLayerIsAlwaysVisible()
+        imeAppWindowIsAlwaysVisible()
+    }
+
     companion object {
         @Parameterized.Parameters(name = "{0}")
         @JvmStatic
diff --git a/tests/FlickerTests/src/com/android/server/wm/flicker/ime/CloseImeWindowToHomeTest.kt b/tests/FlickerTests/src/com/android/server/wm/flicker/ime/CloseImeWindowToHomeTest.kt
index 1dc3ca5..c92fce3 100644
--- a/tests/FlickerTests/src/com/android/server/wm/flicker/ime/CloseImeWindowToHomeTest.kt
+++ b/tests/FlickerTests/src/com/android/server/wm/flicker/ime/CloseImeWindowToHomeTest.kt
@@ -16,6 +16,7 @@
 
 package com.android.server.wm.flicker.ime
 
+import android.platform.test.annotations.IwTest
 import android.platform.test.annotations.Presubmit
 import android.view.Surface
 import android.view.WindowManagerPolicyConstants
@@ -100,6 +101,17 @@
         testSpec.assertLayers { this.isVisible(testApp).then().isInvisible(testApp) }
     }
 
+    @Test
+    @IwTest(focusArea = "ime")
+    override fun cujCompleted() {
+        super.cujCompleted()
+        navBarLayerPositionAtStartAndEnd()
+        imeLayerBecomesInvisible()
+        imeAppWindowBecomesInvisible()
+        imeWindowBecomesInvisible()
+        imeLayerBecomesInvisible()
+    }
+
     companion object {
         @Parameterized.Parameters(name = "{0}")
         @JvmStatic
diff --git a/tests/FlickerTests/src/com/android/server/wm/flicker/ime/OpenImeWindowAndCloseTest.kt b/tests/FlickerTests/src/com/android/server/wm/flicker/ime/OpenImeWindowAndCloseTest.kt
index a6bd791..7d7953b 100644
--- a/tests/FlickerTests/src/com/android/server/wm/flicker/ime/OpenImeWindowAndCloseTest.kt
+++ b/tests/FlickerTests/src/com/android/server/wm/flicker/ime/OpenImeWindowAndCloseTest.kt
@@ -17,6 +17,7 @@
 package com.android.server.wm.flicker.ime
 
 import android.platform.test.annotations.FlakyTest
+import android.platform.test.annotations.IwTest
 import android.platform.test.annotations.Presubmit
 import android.view.Surface
 import android.view.WindowManagerPolicyConstants
@@ -79,6 +80,14 @@
         super.visibleLayersShownMoreThanOneConsecutiveEntry()
     }
 
+    @Test
+    @IwTest(focusArea = "ime")
+    override fun cujCompleted() {
+        super.cujCompleted()
+        imeLayerBecomesInvisible()
+        imeWindowBecomesInvisible()
+    }
+
     companion object {
         @Parameterized.Parameters(name = "{0}")
         @JvmStatic
diff --git a/tests/FlickerTests/src/com/android/server/wm/flicker/ime/OpenImeWindowTest.kt b/tests/FlickerTests/src/com/android/server/wm/flicker/ime/OpenImeWindowTest.kt
index b43efea..9919d87 100644
--- a/tests/FlickerTests/src/com/android/server/wm/flicker/ime/OpenImeWindowTest.kt
+++ b/tests/FlickerTests/src/com/android/server/wm/flicker/ime/OpenImeWindowTest.kt
@@ -16,6 +16,7 @@
 
 package com.android.server.wm.flicker.ime
 
+import android.platform.test.annotations.IwTest
 import android.platform.test.annotations.Presubmit
 import android.view.Surface
 import android.view.WindowManagerPolicyConstants
@@ -50,6 +51,15 @@
         }
     }
 
+    @Test
+    @IwTest(focusArea = "ime")
+    override fun cujCompleted() {
+        super.cujCompleted()
+        imeWindowBecomesVisible()
+        appWindowAlwaysVisibleOnTop()
+        layerAlwaysVisible()
+    }
+
     @Presubmit @Test fun imeWindowBecomesVisible() = testSpec.imeWindowBecomesVisible()
 
     @Presubmit
diff --git a/tests/FlickerTests/src/com/android/server/wm/flicker/rotation/ChangeAppRotationTest.kt b/tests/FlickerTests/src/com/android/server/wm/flicker/rotation/ChangeAppRotationTest.kt
index 1973ec0..ad14d0d 100644
--- a/tests/FlickerTests/src/com/android/server/wm/flicker/rotation/ChangeAppRotationTest.kt
+++ b/tests/FlickerTests/src/com/android/server/wm/flicker/rotation/ChangeAppRotationTest.kt
@@ -17,6 +17,7 @@
 package com.android.server.wm.flicker.rotation
 
 import android.platform.test.annotations.FlakyTest
+import android.platform.test.annotations.IwTest
 import android.platform.test.annotations.Presubmit
 import androidx.test.filters.RequiresDevice
 import com.android.server.wm.flicker.FlickerParametersRunnerFactory
@@ -125,6 +126,14 @@
     @Test
     override fun navBarLayerPositionAtStartAndEnd() = super.navBarLayerPositionAtStartAndEnd()
 
+    @Test
+    @IwTest(focusArea = "ime")
+    override fun cujCompleted() {
+        super.cujCompleted()
+        focusChanges()
+        rotationLayerAppearsAndVanishes()
+    }
+
     companion object {
         /**
          * Creates the test configurations.
diff --git a/tests/FlickerTests/src/com/android/server/wm/flicker/rotation/RotationTransition.kt b/tests/FlickerTests/src/com/android/server/wm/flicker/rotation/RotationTransition.kt
index 4faeb24..8e3fd40 100644
--- a/tests/FlickerTests/src/com/android/server/wm/flicker/rotation/RotationTransition.kt
+++ b/tests/FlickerTests/src/com/android/server/wm/flicker/rotation/RotationTransition.kt
@@ -73,4 +73,10 @@
             }
         }
     }
+
+    override fun cujCompleted() {
+        super.cujCompleted()
+        appLayerRotates_StartingPos()
+        appLayerRotates_EndingPos()
+    }
 }
diff --git a/tests/FlickerTests/src/com/android/server/wm/flicker/rotation/SeamlessAppRotationTest.kt b/tests/FlickerTests/src/com/android/server/wm/flicker/rotation/SeamlessAppRotationTest.kt
index a08db29..d0d4122 100644
--- a/tests/FlickerTests/src/com/android/server/wm/flicker/rotation/SeamlessAppRotationTest.kt
+++ b/tests/FlickerTests/src/com/android/server/wm/flicker/rotation/SeamlessAppRotationTest.kt
@@ -17,6 +17,7 @@
 package com.android.server.wm.flicker.rotation
 
 import android.platform.test.annotations.FlakyTest
+import android.platform.test.annotations.IwTest
 import android.platform.test.annotations.Presubmit
 import android.platform.test.annotations.RequiresDevice
 import android.view.WindowManager
@@ -204,6 +205,31 @@
     @Test
     override fun navBarLayerPositionAtStartAndEnd() = super.navBarLayerPositionAtStartAndEnd()
 
+    @Test
+    @IwTest(focusArea = "ime")
+    override fun cujCompleted() {
+        if (!testSpec.isTablet) {
+            // not yet tablet compatible
+            appLayerRotates()
+            appLayerAlwaysVisible()
+        }
+
+        appWindowFullScreen()
+        appWindowSeamlessRotation()
+        focusDoesNotChange()
+        statusBarLayerIsAlwaysInvisible()
+        statusBarWindowIsAlwaysInvisible()
+        appLayerRotates_StartingPos()
+        appLayerRotates_EndingPos()
+        entireScreenCovered()
+        navBarLayerIsVisibleAtStartAndEnd()
+        navBarWindowIsAlwaysVisible()
+        taskBarLayerIsVisibleAtStartAndEnd()
+        taskBarWindowIsAlwaysVisible()
+        visibleLayersShownMoreThanOneConsecutiveEntry()
+        visibleWindowsShownMoreThanOneConsecutiveEntry()
+    }
+
     companion object {
         private val FlickerTestParameter.starveUiThread
             get() =