Merge "Fix missing notification footer." into sc-v2-dev
diff --git a/core/java/android/appwidget/AppWidgetHostView.java b/core/java/android/appwidget/AppWidgetHostView.java
index eb10f09..7c2b1b7 100644
--- a/core/java/android/appwidget/AppWidgetHostView.java
+++ b/core/java/android/appwidget/AppWidgetHostView.java
@@ -626,7 +626,7 @@
 
         // If our stale view has been prepared to match active, and the new
         // layout matches, try recycling it
-        if (layoutId == mLayoutId && mView != null) {
+        if (remoteViews.canRecycleView(mView)) {
             try {
                 mLastExecutionSignal = remoteViews.reapplyAsync(mContext,
                         mView,
diff --git a/core/java/android/hardware/biometrics/BiometricOverlayConstants.java b/core/java/android/hardware/biometrics/BiometricOverlayConstants.java
index 065ae64a..603b06d 100644
--- a/core/java/android/hardware/biometrics/BiometricOverlayConstants.java
+++ b/core/java/android/hardware/biometrics/BiometricOverlayConstants.java
@@ -38,16 +38,13 @@
     int REASON_AUTH_KEYGUARD = 4;
     /** Non-specific usage (from FingerprintManager). */
     int REASON_AUTH_OTHER = 5;
-    /** Usage from Settings. */
-    int REASON_AUTH_SETTINGS = 6;
 
     @IntDef({REASON_UNKNOWN,
             REASON_ENROLL_FIND_SENSOR,
             REASON_ENROLL_ENROLLING,
             REASON_AUTH_BP,
             REASON_AUTH_KEYGUARD,
-            REASON_AUTH_OTHER,
-            REASON_AUTH_SETTINGS})
+            REASON_AUTH_OTHER})
     @Retention(RetentionPolicy.SOURCE)
     @interface ShowReason {}
 }
diff --git a/core/java/android/widget/RemoteViews.java b/core/java/android/widget/RemoteViews.java
index 0f309f1..2357d13 100644
--- a/core/java/android/widget/RemoteViews.java
+++ b/core/java/android/widget/RemoteViews.java
@@ -5795,7 +5795,7 @@
         // across orientation change, and has the RemoteViews re-applied in the new orientation,
         // we throw an exception, since the layouts may be completely unrelated.
         if (hasMultipleLayouts()) {
-            if ((Integer) v.getTag(R.id.widget_frame) != rvToApply.getLayoutId()) {
+            if (!rvToApply.canRecycleView(v)) {
                 throw new RuntimeException("Attempting to re-apply RemoteViews to a view that" +
                         " that does not share the same root layout id.");
             }
diff --git a/libs/WindowManager/Shell/res/values-land/styles.xml b/libs/WindowManager/Shell/res/values-land/styles.xml
index 9eddac4..0ed9368 100644
--- a/libs/WindowManager/Shell/res/values-land/styles.xml
+++ b/libs/WindowManager/Shell/res/values-land/styles.xml
@@ -23,7 +23,7 @@
     </style>
 
     <style name="DockedDividerHandle">
-        <item name="android:layout_gravity">center_vertical</item>
+        <item name="android:layout_gravity">center</item>
         <item name="android:layout_width">48dp</item>
         <item name="android:layout_height">96dp</item>
     </style>
diff --git a/libs/WindowManager/Shell/res/values/styles.xml b/libs/WindowManager/Shell/res/values/styles.xml
index cb6d4de..7733201 100644
--- a/libs/WindowManager/Shell/res/values/styles.xml
+++ b/libs/WindowManager/Shell/res/values/styles.xml
@@ -43,7 +43,7 @@
     </style>
 
     <style name="DockedDividerHandle">
-        <item name="android:layout_gravity">center_horizontal</item>
+        <item name="android:layout_gravity">center</item>
         <item name="android:layout_width">96dp</item>
         <item name="android:layout_height">48dp</item>
     </style>
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/common/split/DividerView.java b/libs/WindowManager/Shell/src/com/android/wm/shell/common/split/DividerView.java
index 6ea806b..4b125b1 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/common/split/DividerView.java
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/common/split/DividerView.java
@@ -19,6 +19,8 @@
 import static android.content.res.Configuration.ORIENTATION_LANDSCAPE;
 import static android.view.WindowManager.LayoutParams.FLAG_SLIPPERY;
 
+import android.animation.Animator;
+import android.animation.AnimatorListenerAdapter;
 import android.animation.ObjectAnimator;
 import android.content.Context;
 import android.graphics.Rect;
@@ -57,6 +59,7 @@
     private final int mTouchSlop = ViewConfiguration.get(getContext()).getScaledTouchSlop();
 
     private SplitLayout mSplitLayout;
+    private SplitWindowManager mSplitWindowManager;
     private SurfaceControlViewHost mViewHost;
     private DividerHandleView mHandle;
     private View mBackground;
@@ -67,6 +70,7 @@
     private int mStartPos;
     private GestureDetector mDoubleTapDetector;
     private boolean mInteractive;
+    private boolean mSetTouchRegion = true;
 
     /**
      * Tracks divider bar visible bounds in screen-based coordination. Used to calculate with
@@ -93,6 +97,18 @@
                 }
             };
 
+    private AnimatorListenerAdapter mAnimatorListener = new AnimatorListenerAdapter() {
+        @Override
+        public void onAnimationEnd(Animator animation) {
+            mSetTouchRegion = true;
+        }
+
+        @Override
+        public void onAnimationCancel(Animator animation) {
+            mSetTouchRegion = true;
+        }
+    };
+
     public DividerView(@NonNull Context context) {
         super(context);
     }
@@ -114,9 +130,11 @@
     /** Sets up essential dependencies of the divider bar. */
     public void setup(
             SplitLayout layout,
+            SplitWindowManager splitWindowManager,
             SurfaceControlViewHost viewHost,
             InsetsState insetsState) {
         mSplitLayout = layout;
+        mSplitWindowManager = splitWindowManager;
         mViewHost = viewHost;
         mDividerBounds.set(layout.getDividerBounds());
         onInsetsChanged(insetsState, false /* animate */);
@@ -138,9 +156,11 @@
                         DIVIDER_HEIGHT_PROPERTY, mDividerBounds.height(), mTempRect.height());
                 animator.setInterpolator(InsetsController.RESIZE_INTERPOLATOR);
                 animator.setDuration(InsetsController.ANIMATION_DURATION_RESIZE);
+                animator.addListener(mAnimatorListener);
                 animator.start();
             } else {
                 DIVIDER_HEIGHT_PROPERTY.set(this, mTempRect.height());
+                mSetTouchRegion = true;
             }
             mDividerBounds.set(mTempRect);
         }
@@ -162,6 +182,17 @@
     }
 
     @Override
+    protected void onLayout(boolean changed, int left, int top, int right, int bottom) {
+        super.onLayout(changed, left, top, right, bottom);
+        if (mSetTouchRegion) {
+            mTempRect.set(mHandle.getLeft(), mHandle.getTop(), mHandle.getRight(),
+                    mHandle.getBottom());
+            mSplitWindowManager.setTouchRegion(mTempRect);
+            mSetTouchRegion = false;
+        }
+    }
+
+    @Override
     public boolean onTouch(View v, MotionEvent event) {
         if (mSplitLayout == null || !mInteractive) {
             return false;
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/common/split/SplitLayout.java b/libs/WindowManager/Shell/src/com/android/wm/shell/common/split/SplitLayout.java
index a9ed64c..625bcee 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/common/split/SplitLayout.java
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/common/split/SplitLayout.java
@@ -40,8 +40,10 @@
 import android.content.res.Resources;
 import android.graphics.Point;
 import android.graphics.Rect;
+import android.view.Display;
 import android.view.InsetsSourceControl;
 import android.view.InsetsState;
+import android.view.RoundedCorner;
 import android.view.SurfaceControl;
 import android.view.WindowInsets;
 import android.view.WindowManager;
@@ -53,6 +55,7 @@
 import com.android.internal.annotations.VisibleForTesting;
 import com.android.internal.policy.DividerSnapAlgorithm;
 import com.android.internal.policy.DockedDividerUtils;
+import com.android.wm.shell.R;
 import com.android.wm.shell.ShellTaskOrganizer;
 import com.android.wm.shell.animation.Interpolators;
 import com.android.wm.shell.common.DisplayImeController;
@@ -133,17 +136,32 @@
         mDismissingEffectPolicy = new DismissingEffectPolicy(applyDismissingParallax);
 
         final Resources resources = context.getResources();
-        mDividerWindowWidth = resources.getDimensionPixelSize(
-                com.android.internal.R.dimen.docked_stack_divider_thickness);
-        mDividerInsets = resources.getDimensionPixelSize(
-                com.android.internal.R.dimen.docked_stack_divider_insets);
-        mDividerSize = mDividerWindowWidth - mDividerInsets * 2;
+        mDividerSize = resources.getDimensionPixelSize(R.dimen.split_divider_bar_width);
+        mDividerInsets = getDividerInsets(resources, context.getDisplay());
+        mDividerWindowWidth = mDividerSize + 2 * mDividerInsets;
 
         mRootBounds.set(configuration.windowConfiguration.getBounds());
         mDividerSnapAlgorithm = getSnapAlgorithm(mContext, mRootBounds);
         resetDividerPosition();
     }
 
+    private int getDividerInsets(Resources resources, Display display) {
+        final int dividerInset = resources.getDimensionPixelSize(
+                com.android.internal.R.dimen.docked_stack_divider_insets);
+
+        int radius = 0;
+        RoundedCorner corner = display.getRoundedCorner(RoundedCorner.POSITION_TOP_LEFT);
+        radius = corner != null ? Math.max(radius, corner.getRadius()) : radius;
+        corner = display.getRoundedCorner(RoundedCorner.POSITION_TOP_RIGHT);
+        radius = corner != null ? Math.max(radius, corner.getRadius()) : radius;
+        corner = display.getRoundedCorner(RoundedCorner.POSITION_BOTTOM_RIGHT);
+        radius = corner != null ? Math.max(radius, corner.getRadius()) : radius;
+        corner = display.getRoundedCorner(RoundedCorner.POSITION_BOTTOM_LEFT);
+        radius = corner != null ? Math.max(radius, corner.getRadius()) : radius;
+
+        return Math.max(dividerInset, radius);
+    }
+
     /** Gets bounds of the primary split. */
     public Rect getBounds1() {
         return new Rect(mBounds1);
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/common/split/SplitWindowManager.java b/libs/WindowManager/Shell/src/com/android/wm/shell/common/split/SplitWindowManager.java
index 08754d3..4903f9d 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/common/split/SplitWindowManager.java
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/common/split/SplitWindowManager.java
@@ -31,7 +31,6 @@
 import android.graphics.Rect;
 import android.graphics.Region;
 import android.os.Binder;
-import android.os.IBinder;
 import android.view.IWindow;
 import android.view.InsetsState;
 import android.view.LayoutInflater;
@@ -41,6 +40,7 @@
 import android.view.WindowManager;
 import android.view.WindowlessWindowManager;
 
+import androidx.annotation.NonNull;
 import androidx.annotation.Nullable;
 
 import com.android.wm.shell.R;
@@ -71,9 +71,10 @@
         mWindowName = windowName;
     }
 
-    @Override
-    public void setTouchRegion(IBinder window, Region region) {
-        super.setTouchRegion(window, region);
+    void setTouchRegion(@NonNull Rect region) {
+        if (mViewHost != null) {
+            setTouchRegion(mViewHost.getWindowToken().asBinder(), new Region(region));
+        }
     }
 
     @Override
@@ -122,7 +123,7 @@
         lp.setTitle(mWindowName);
         lp.privateFlags |= PRIVATE_FLAG_NO_MOVE_ANIMATION | PRIVATE_FLAG_TRUSTED_OVERLAY;
         mViewHost.setView(mDividerView, lp);
-        mDividerView.setup(splitLayout, mViewHost, insetsState);
+        mDividerView.setup(splitLayout, this, mViewHost, insetsState);
     }
 
     /**
diff --git a/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/CommonAssertions.kt b/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/CommonAssertions.kt
index c07f0eb..c4be785 100644
--- a/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/CommonAssertions.kt
+++ b/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/CommonAssertions.kt
@@ -78,7 +78,7 @@
     assertLayersEnd {
         val dividerRegion = layer(APP_PAIR_SPLIT_DIVIDER_COMPONENT).visibleRegion.region
         visibleRegion(primaryComponent)
-            .coversExactly(getPrimaryRegion(dividerRegion, rotation))
+            .overlaps(getPrimaryRegion(dividerRegion, rotation))
     }
 }
 
@@ -89,7 +89,7 @@
     assertLayersEnd {
         val dividerRegion = layer(DOCKED_STACK_DIVIDER_COMPONENT).visibleRegion.region
         visibleRegion(primaryComponent)
-            .coversExactly(getPrimaryRegion(dividerRegion, rotation))
+            .overlaps(getPrimaryRegion(dividerRegion, rotation))
     }
 }
 
@@ -100,7 +100,7 @@
     assertLayersEnd {
         val dividerRegion = layer(APP_PAIR_SPLIT_DIVIDER_COMPONENT).visibleRegion.region
         visibleRegion(secondaryComponent)
-            .coversExactly(getSecondaryRegion(dividerRegion, rotation))
+            .overlaps(getSecondaryRegion(dividerRegion, rotation))
     }
 }
 
@@ -111,7 +111,7 @@
     assertLayersEnd {
         val dividerRegion = layer(DOCKED_STACK_DIVIDER_COMPONENT).visibleRegion.region
         visibleRegion(secondaryComponent)
-            .coversExactly(getSecondaryRegion(dividerRegion, rotation))
+            .overlaps(getSecondaryRegion(dividerRegion, rotation))
     }
 }
 
diff --git a/packages/SystemUI/animation/src/com/android/systemui/animation/DialogLaunchAnimator.kt b/packages/SystemUI/animation/src/com/android/systemui/animation/DialogLaunchAnimator.kt
index c2b3608..669a054 100644
--- a/packages/SystemUI/animation/src/com/android/systemui/animation/DialogLaunchAnimator.kt
+++ b/packages/SystemUI/animation/src/com/android/systemui/animation/DialogLaunchAnimator.kt
@@ -21,11 +21,16 @@
 import android.graphics.Color
 import android.os.Looper
 import android.util.Log
+import android.view.GhostView
 import android.view.Gravity
 import android.view.View
 import android.view.ViewGroup
-import android.view.ViewTreeObserver
+import android.view.ViewTreeObserver.OnPreDrawListener
+import android.view.WindowInsets
 import android.view.WindowManager
+import android.view.WindowManager.LayoutParams.FLAG_LAYOUT_INSET_DECOR
+import android.view.WindowManager.LayoutParams.FLAG_LAYOUT_IN_SCREEN
+import android.view.WindowManagerPolicyConstants
 import android.widget.FrameLayout
 
 private const val TAG = "DialogLaunchAnimator"
@@ -221,10 +226,12 @@
     private var isDismissing = false
 
     private var dismissRequested = false
-    private var drawHostDialog = false
     var ignoreNextCallToHide = false
     var exitAnimationDisabled = false
 
+    private var isTouchSurfaceGhostDrawn = false
+    private var isOriginalDialogViewLaidOut = false
+
     fun start() {
         // Show the host (fullscreen) dialog, to which we will add the stolen dialog view.
         hostDialog.show()
@@ -252,19 +259,76 @@
             WindowManager.LayoutParams.MATCH_PARENT
         )
 
-        // Prevent the host dialog from drawing until the animation starts.
-        hostDialogRoot.viewTreeObserver.addOnPreDrawListener(
-            object : ViewTreeObserver.OnPreDrawListener {
-                override fun onPreDraw(): Boolean {
-                    if (drawHostDialog) {
-                        hostDialogRoot.viewTreeObserver.removeOnPreDrawListener(this)
-                        return true
-                    }
+        // If we are using gesture navigation, then we can overlay the navigation/task bars with
+        // the host dialog.
+        val navigationMode = context.resources.getInteger(
+            com.android.internal.R.integer.config_navBarInteractionMode)
+        if (navigationMode == WindowManagerPolicyConstants.NAV_BAR_MODE_GESTURAL) {
+            window.attributes.fitInsetsTypes = window.attributes.fitInsetsTypes and
+                WindowInsets.Type.navigationBars().inv()
+            window.addFlags(FLAG_LAYOUT_IN_SCREEN or FLAG_LAYOUT_INSET_DECOR)
+            window.setDecorFitsSystemWindows(false)
+        }
 
-                    return false
-                }
+        // Disable the dim. We will enable it once we start the animation.
+        window.clearFlags(WindowManager.LayoutParams.FLAG_DIM_BEHIND)
+
+        // Add a temporary touch surface ghost as soon as the window is ready to draw. This
+        // temporary ghost will be drawn together with the touch surface, but in the host dialog
+        // window. Once it is drawn, we will make the touch surface invisible, and then start the
+        // animation. We do all this synchronization to avoid flicker that would occur if we made
+        // the touch surface invisible too early (before its ghost is drawn), leading to one or more
+        // frames with a hole instead of the touch surface (or its ghost).
+        hostDialogRoot.viewTreeObserver.addOnPreDrawListener(object : OnPreDrawListener {
+            override fun onPreDraw(): Boolean {
+                hostDialogRoot.viewTreeObserver.removeOnPreDrawListener(this)
+                addTemporaryTouchSurfaceGhost()
+                return true
             }
-        )
+        })
+        hostDialogRoot.invalidate()
+    }
+
+    private fun addTemporaryTouchSurfaceGhost() {
+        // Create a ghost of the touch surface (which will make the touch surface invisible) and add
+        // it to the host dialog. We will wait for this ghost to be drawn before starting the
+        // animation.
+        val ghost = GhostView.addGhost(touchSurface, hostDialogRoot)
+
+        // The ghost of the touch surface was just created, so the touch surface was made invisible.
+        // We make it visible again until the ghost is actually drawn.
+        touchSurface.visibility = View.VISIBLE
+
+        // Wait for the ghost to be drawn before continuing.
+        ghost.viewTreeObserver.addOnPreDrawListener(object : OnPreDrawListener {
+            override fun onPreDraw(): Boolean {
+                ghost.viewTreeObserver.removeOnPreDrawListener(this)
+                onTouchSurfaceGhostDrawn()
+                return true
+            }
+        })
+        ghost.invalidate()
+    }
+
+    private fun onTouchSurfaceGhostDrawn() {
+        // Make the touch surface invisible and make sure that it stays invisible as long as the
+        // dialog is shown or animating.
+        touchSurface.visibility = View.INVISIBLE
+        if (touchSurface is LaunchableView) {
+            touchSurface.setShouldBlockVisibilityChanges(true)
+        }
+
+        // Add a pre draw listener to (maybe) start the animation once the touch surface is
+        // actually invisible.
+        touchSurface.viewTreeObserver.addOnPreDrawListener(object : OnPreDrawListener {
+            override fun onPreDraw(): Boolean {
+                touchSurface.viewTreeObserver.removeOnPreDrawListener(this)
+                isTouchSurfaceGhostDrawn = true
+                maybeStartLaunchAnimation()
+                return true
+            }
+        })
+        touchSurface.invalidate()
     }
 
     /** Get the content view of [originalDialog] and pass it to [then]. */
@@ -276,7 +340,7 @@
             ?: throw IllegalStateException("Dialog does not have any android.R.id.content view")
 
         androidContent.viewTreeObserver.addOnPreDrawListener(
-            object : ViewTreeObserver.OnPreDrawListener {
+            object : OnPreDrawListener {
                 override fun onPreDraw(): Boolean {
                     if (androidContent.childCount == 1) {
                         androidContent.viewTreeObserver.removeOnPreDrawListener(this)
@@ -354,32 +418,47 @@
                 oldBottom: Int
             ) {
                 dialogView.removeOnLayoutChangeListener(this)
-                startAnimation(
-                    isLaunching = true,
-                    onLaunchAnimationStart = { drawHostDialog = true },
-                    onLaunchAnimationEnd = {
-                        touchSurface.setTag(R.id.launch_animation_running, null)
 
-                        // We hide the touch surface when the dialog is showing. We will make this
-                        // view visible again when dismissing the dialog.
-                        // TODO(b/193634619): Provide an easy way for views to check if they should
-                        // be hidden because of a dialog launch so that they don't override this
-                        // visibility when updating/refreshing itself.
-                        touchSurface.visibility = View.INVISIBLE
-
-                        isLaunching = false
-
-                        // dismiss was called during the animation, dismiss again now to actually
-                        // dismiss.
-                        if (dismissRequested) {
-                            hostDialog.dismiss()
-                        }
-                    }
-                )
+                isOriginalDialogViewLaidOut = true
+                maybeStartLaunchAnimation()
             }
         })
     }
 
+    private fun maybeStartLaunchAnimation() {
+        if (!isTouchSurfaceGhostDrawn || !isOriginalDialogViewLaidOut) {
+            return
+        }
+
+        // Show the background dim.
+        hostDialog.window.addFlags(WindowManager.LayoutParams.FLAG_DIM_BEHIND)
+
+        startAnimation(
+            isLaunching = true,
+            onLaunchAnimationStart = {
+                // Remove the temporary ghost. Another ghost (that ghosts only the touch surface
+                // content, and not its background) will be added right after this and will be
+                // animated.
+                GhostView.removeGhost(touchSurface)
+            },
+            onLaunchAnimationEnd = {
+                touchSurface.setTag(R.id.launch_animation_running, null)
+
+                // We hide the touch surface when the dialog is showing. We will make this
+                // view visible again when dismissing the dialog.
+                touchSurface.visibility = View.INVISIBLE
+
+                isLaunching = false
+
+                // dismiss was called during the animation, dismiss again now to actually
+                // dismiss.
+                if (dismissRequested) {
+                    hostDialog.dismiss()
+                }
+            }
+        )
+    }
+
     private fun onHostDialogDismissed(actualDismiss: () -> Unit) {
         if (Looper.myLooper() != Looper.getMainLooper()) {
             context.mainExecutor.execute { onHostDialogDismissed(actualDismiss) }
@@ -417,6 +496,11 @@
         if (!shouldAnimateDialogIntoView()) {
             Log.i(TAG, "Skipping animation of dialog into the touch surface")
 
+            // Make sure we allow the touch surface to change its visibility again.
+            if (touchSurface is LaunchableView) {
+                touchSurface.setShouldBlockVisibilityChanges(false)
+            }
+
             // If the view is invisible it's probably because of us, so we make it visible again.
             if (touchSurface.visibility == View.INVISIBLE) {
                 touchSurface.visibility = View.VISIBLE
@@ -434,10 +518,33 @@
                 hostDialog.window.clearFlags(WindowManager.LayoutParams.FLAG_DIM_BEHIND)
             },
             onLaunchAnimationEnd = {
+                // Make sure we allow the touch surface to change its visibility again.
+                if (touchSurface is LaunchableView) {
+                    touchSurface.setShouldBlockVisibilityChanges(false)
+                }
+
                 touchSurface.visibility = View.VISIBLE
                 originalDialogView!!.visibility = View.INVISIBLE
-                dismissDialogs(true /* instantDismiss */)
-                onDialogDismissed(this@DialogLaunchAnimation)
+
+                // The animated ghost was just removed. We create a temporary ghost that will be
+                // removed only once we draw the touch surface, to avoid flickering that would
+                // happen when removing the ghost too early (before the touch surface is drawn).
+                GhostView.addGhost(touchSurface, hostDialogRoot)
+
+                touchSurface.viewTreeObserver.addOnPreDrawListener(object : OnPreDrawListener {
+                    override fun onPreDraw(): Boolean {
+                        touchSurface.viewTreeObserver.removeOnPreDrawListener(this)
+
+                        // Now that the touch surface was drawn, we can remove the temporary ghost
+                        // and instantly dismiss the dialog.
+                        GhostView.removeGhost(touchSurface)
+                        dismissDialogs(true /* instantDismiss */)
+                        onDialogDismissed(this@DialogLaunchAnimation)
+
+                        return true
+                    }
+                })
+                touchSurface.invalidate()
             }
         )
     }
@@ -472,10 +579,13 @@
             }
 
             override fun onLaunchAnimationStart(isExpandingFullyAbove: Boolean) {
+                // During launch, onLaunchAnimationStart will be used to remove the temporary touch
+                // surface ghost so it is important to call this before calling
+                // onLaunchAnimationStart on the controller (which will create its own ghost).
+                onLaunchAnimationStart()
+
                 startViewController.onLaunchAnimationStart(isExpandingFullyAbove)
                 endViewController.onLaunchAnimationStart(isExpandingFullyAbove)
-
-                onLaunchAnimationStart()
             }
 
             override fun onLaunchAnimationEnd(isExpandingFullyAbove: Boolean) {
diff --git a/packages/SystemUI/animation/src/com/android/systemui/animation/LaunchableView.kt b/packages/SystemUI/animation/src/com/android/systemui/animation/LaunchableView.kt
new file mode 100644
index 0000000..80a3eb8
--- /dev/null
+++ b/packages/SystemUI/animation/src/com/android/systemui/animation/LaunchableView.kt
@@ -0,0 +1,30 @@
+/*
+ * Copyright (C) 2021 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.systemui.animation
+
+/** A view that can expand/launch into an app or a dialog. */
+interface LaunchableView {
+    /**
+     * Set whether this view should block/prevent all visibility changes. This ensures that this
+     * view remains invisible during the launch animation given that it is ghosted and already drawn
+     * somewhere else.
+     *
+     * Note that when this is set to true, both the [normal][android.view.View.setVisibility] and
+     * [transition][android.view.View.setTransitionVisibility] visibility changes must be blocked.
+     */
+    fun setShouldBlockVisibilityChanges(block: Boolean)
+}
\ No newline at end of file
diff --git a/packages/SystemUI/plugin/src/com/android/systemui/plugins/statusbar/NotificationMenuRowPlugin.java b/packages/SystemUI/plugin/src/com/android/systemui/plugins/statusbar/NotificationMenuRowPlugin.java
index 883f4de..94fdbae 100644
--- a/packages/SystemUI/plugin/src/com/android/systemui/plugins/statusbar/NotificationMenuRowPlugin.java
+++ b/packages/SystemUI/plugin/src/com/android/systemui/plugins/statusbar/NotificationMenuRowPlugin.java
@@ -183,12 +183,6 @@
     public boolean canBeDismissed();
 
     /**
-     * Informs the menu whether dismiss gestures are left-to-right or right-to-left.
-     */
-    default void setDismissRtl(boolean dismissRtl) {
-    }
-
-    /**
      * Determines whether the menu should remain open given its current state, or snap closed.
      * @return true if the menu should remain open, false otherwise.
      */
diff --git a/packages/SystemUI/res-keyguard/drawable/super_lock_icon.xml b/packages/SystemUI/res-keyguard/drawable/super_lock_icon.xml
index 7f0f68f..c58e2e3 100644
--- a/packages/SystemUI/res-keyguard/drawable/super_lock_icon.xml
+++ b/packages/SystemUI/res-keyguard/drawable/super_lock_icon.xml
@@ -37,7 +37,7 @@
         android:id="@+id/locked_fp"
         android:state_middle="true"
         android:state_single="false"
-        android:drawable="@drawable/ic_fingerprint" />
+        android:drawable="@drawable/ic_kg_fingerprint" />
 
     <item
         android:id="@+id/unlocked"
diff --git a/packages/SystemUI/res/drawable/ic_fingerprint.xml b/packages/SystemUI/res/drawable/ic_kg_fingerprint.xml
similarity index 100%
rename from packages/SystemUI/res/drawable/ic_fingerprint.xml
rename to packages/SystemUI/res/drawable/ic_kg_fingerprint.xml
diff --git a/packages/SystemUI/res/drawable/internet_dialog_background.xml b/packages/SystemUI/res/drawable/internet_dialog_background.xml
deleted file mode 100644
index 3ceb0f6..0000000
--- a/packages/SystemUI/res/drawable/internet_dialog_background.xml
+++ /dev/null
@@ -1,23 +0,0 @@
-<?xml version="1.0" encoding="utf-8"?>
-<!--
-  ~ Copyright (C) 2020 The Android Open Source Project
-  ~
-  ~ Licensed under the Apache License, Version 2.0 (the "License");
-  ~ you may not use this file except in compliance with the License.
-  ~ You may obtain a copy of the License at
-  ~
-  ~      http://www.apache.org/licenses/LICENSE-2.0
-  ~
-  ~ Unless required by applicable law or agreed to in writing, software
-  ~ distributed under the License is distributed on an "AS IS" BASIS,
-  ~ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
-  ~ See the License for the specific language governing permissions and
-  ~ limitations under the License.
-  -->
-
-<inset xmlns:android="http://schemas.android.com/apk/res/android">
-    <shape android:shape="rectangle">
-        <corners android:radius="8dp" />
-        <solid android:color="?android:attr/colorBackground" />
-    </shape>
-</inset>
diff --git a/packages/SystemUI/res/drawable/internet_dialog_rounded_top_corner_background.xml b/packages/SystemUI/res/drawable/internet_dialog_rounded_top_corner_background.xml
deleted file mode 100644
index 14672ef..0000000
--- a/packages/SystemUI/res/drawable/internet_dialog_rounded_top_corner_background.xml
+++ /dev/null
@@ -1,22 +0,0 @@
-<?xml version="1.0" encoding="utf-8"?>
-<!-- Copyright (C) 2021 The Android Open Source Project
-     Licensed under the Apache License, Version 2.0 (the "License");
-     you may not use this file except in compliance with the License.
-     You may obtain a copy of the License at
-          http://www.apache.org/licenses/LICENSE-2.0
-     Unless required by applicable law or agreed to in writing, software
-     distributed under the License is distributed on an "AS IS" BASIS,
-     WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
-     See the License for the specific language governing permissions and
-     limitations under the License.
--->
-<inset xmlns:android="http://schemas.android.com/apk/res/android">
-    <shape android:shape="rectangle">
-        <corners
-            android:topLeftRadius="@dimen/internet_dialog_corner_radius"
-            android:topRightRadius="@dimen/internet_dialog_corner_radius"
-            android:bottomLeftRadius="@dimen/internet_dialog_corner_radius"
-            android:bottomRightRadius="@dimen/internet_dialog_corner_radius"/>
-        <solid android:color="?android:attr/colorBackground" />
-    </shape>
-</inset>
diff --git a/packages/SystemUI/res/layout/internet_connectivity_dialog.xml b/packages/SystemUI/res/layout/internet_connectivity_dialog.xml
index 79ac737..f4faa62 100644
--- a/packages/SystemUI/res/layout/internet_connectivity_dialog.xml
+++ b/packages/SystemUI/res/layout/internet_connectivity_dialog.xml
@@ -20,8 +20,7 @@
     xmlns:androidprv="http://schemas.android.com/apk/prv/res/android"
     android:id="@+id/internet_connectivity_dialog"
     android:layout_width="@dimen/large_dialog_width"
-    android:layout_height="@dimen/internet_dialog_list_max_height"
-    android:background="@drawable/internet_dialog_rounded_top_corner_background"
+    android:layout_height="wrap_content"
     android:orientation="vertical">
 
     <LinearLayout
diff --git a/packages/SystemUI/res/values-land/styles.xml b/packages/SystemUI/res/values-land/styles.xml
index 82cba58..f3d83645 100644
--- a/packages/SystemUI/res/values-land/styles.xml
+++ b/packages/SystemUI/res/values-land/styles.xml
@@ -25,12 +25,6 @@
         <item name="android:layout_gravity">center_horizontal</item>
     </style>
 
-    <style name="DockedDividerHandle">
-        <item name="android:layout_gravity">center_vertical</item>
-        <item name="android:layout_width">48dp</item>
-        <item name="android:layout_height">96dp</item>
-    </style>
-
     <style name="DockedDividerMinimizedShadow">
         <item name="android:layout_width">8dp</item>
         <item name="android:layout_height">match_parent</item>
diff --git a/packages/SystemUI/res/values-sw600dp/dimens.xml b/packages/SystemUI/res/values-sw600dp/dimens.xml
index 85f8f09..4e57861 100644
--- a/packages/SystemUI/res/values-sw600dp/dimens.xml
+++ b/packages/SystemUI/res/values-sw600dp/dimens.xml
@@ -105,5 +105,5 @@
     <dimen name="qs_detail_margin_top">0dp</dimen>
 
     <!-- The width of large/content heavy dialogs (e.g. Internet, Media output, etc) -->
-    <dimen name="large_dialog_width">624dp</dimen>
+    <dimen name="large_dialog_width">504dp</dimen>
 </resources>
diff --git a/packages/SystemUI/res/values/dimens.xml b/packages/SystemUI/res/values/dimens.xml
index 7293f31..db6985d 100644
--- a/packages/SystemUI/res/values/dimens.xml
+++ b/packages/SystemUI/res/values/dimens.xml
@@ -1602,7 +1602,6 @@
 
     <!-- Internet panel related dimensions -->
     <dimen name="internet_dialog_list_margin">12dp</dimen>
-    <dimen name="internet_dialog_list_max_height">662dp</dimen>
 
     <!-- The width of large/content heavy dialogs (e.g. Internet, Media output, etc) -->
     <dimen name="large_dialog_width">@dimen/match_parent</dimen>
diff --git a/packages/SystemUI/res/values/styles.xml b/packages/SystemUI/res/values/styles.xml
index ff299ea..9bdd572 100644
--- a/packages/SystemUI/res/values/styles.xml
+++ b/packages/SystemUI/res/values/styles.xml
@@ -956,10 +956,6 @@
         <item name="android:textColor">?android:attr/textColorPrimary</item>
     </style>
 
-    <style name="Theme.SystemUI.Dialog.Internet">
-        <item name="android:windowBackground">@drawable/internet_dialog_background</item>
-    </style>
-
     <style name="MainSwitch.Settingslib" parent="@android:style/Theme.DeviceDefault">
         <item name="android:switchMinWidth">@dimen/settingslib_min_switch_width</item>
     </style>
diff --git a/packages/SystemUI/src-debug/com/android/systemui/flags/FeatureFlagManager.java b/packages/SystemUI/src-debug/com/android/systemui/flags/FeatureFlagManager.java
index 5b6845f..3a8ee29 100644
--- a/packages/SystemUI/src-debug/com/android/systemui/flags/FeatureFlagManager.java
+++ b/packages/SystemUI/src-debug/com/android/systemui/flags/FeatureFlagManager.java
@@ -23,11 +23,18 @@
 import android.os.Bundle;
 import android.util.Log;
 
+import androidx.annotation.NonNull;
+
+import com.android.systemui.Dumpable;
 import com.android.systemui.dagger.SysUISingleton;
+import com.android.systemui.dump.DumpManager;
 
 import org.json.JSONException;
 import org.json.JSONObject;
 
+import java.io.FileDescriptor;
+import java.io.PrintWriter;
+import java.util.ArrayList;
 import java.util.HashMap;
 import java.util.Map;
 
@@ -43,7 +50,7 @@
  * To restore a flag back to its default, leave the `--ez value <0|1>` off of the command.
  */
 @SysUISingleton
-public class FeatureFlagManager implements FlagReader, FlagWriter {
+public class FeatureFlagManager implements FlagReader, FlagWriter, Dumpable {
     private static final String TAG = "SysUIFlags";
 
     private static final String SYSPROP_PREFIX = "persist.systemui.flag_";
@@ -58,11 +65,13 @@
     private final Map<Integer, Boolean> mBooleanFlagCache = new HashMap<>();
 
     @Inject
-    public FeatureFlagManager(SystemPropertiesHelper systemPropertiesHelper, Context context) {
+    public FeatureFlagManager(SystemPropertiesHelper systemPropertiesHelper, Context context,
+            DumpManager dumpManager) {
         mSystemPropertiesHelper = systemPropertiesHelper;
 
         IntentFilter filter = new IntentFilter(ACTION_SET_FLAG);
         context.registerReceiver(mReceiver, filter, FLAGS_PERMISSION, null);
+        dumpManager.registerDumpable(TAG, this);
     }
 
     /** Return a {@link BooleanFlag}'s value. */
@@ -186,4 +195,16 @@
             }
         }
     };
+
+    @Override
+    public void dump(@NonNull FileDescriptor fd, @NonNull PrintWriter pw, @NonNull String[] args) {
+        ArrayList<String> flagStrings = new ArrayList<>(mBooleanFlagCache.size());
+        for (Map.Entry<Integer, Boolean> entry : mBooleanFlagCache.entrySet()) {
+            flagStrings.add("  sysui_flag_" + entry.getKey() + ": " + entry.getValue());
+        }
+        flagStrings.sort(String.CASE_INSENSITIVE_ORDER);
+        for (String flagString : flagStrings) {
+            pw.println(flagString);
+        }
+    }
 }
diff --git a/packages/SystemUI/src/com/android/systemui/biometrics/SidefpsController.kt b/packages/SystemUI/src/com/android/systemui/biometrics/SidefpsController.kt
index e2a2d07..b7398d8 100644
--- a/packages/SystemUI/src/com/android/systemui/biometrics/SidefpsController.kt
+++ b/packages/SystemUI/src/com/android/systemui/biometrics/SidefpsController.kt
@@ -22,7 +22,6 @@
 import android.graphics.Rect
 import android.hardware.biometrics.BiometricOverlayConstants
 import android.hardware.biometrics.BiometricOverlayConstants.REASON_AUTH_KEYGUARD
-import android.hardware.biometrics.BiometricOverlayConstants.REASON_AUTH_SETTINGS
 import android.hardware.display.DisplayManager
 import android.hardware.fingerprint.FingerprintManager
 import android.hardware.fingerprint.FingerprintSensorPropertiesInternal
@@ -182,7 +181,6 @@
 @BiometricOverlayConstants.ShowReason
 private fun Int.isReasonToShow(): Boolean = when (this) {
     REASON_AUTH_KEYGUARD -> false
-    REASON_AUTH_SETTINGS -> false
     else -> true
 }
 
diff --git a/packages/SystemUI/src/com/android/systemui/flags/FeatureFlagManager.java b/packages/SystemUI/src/com/android/systemui/flags/FeatureFlagManager.java
index 85baed4..78f0b5f 100644
--- a/packages/SystemUI/src/com/android/systemui/flags/FeatureFlagManager.java
+++ b/packages/SystemUI/src/com/android/systemui/flags/FeatureFlagManager.java
@@ -16,23 +16,48 @@
 
 package com.android.systemui.flags;
 
+import android.util.SparseBooleanArray;
+
+import androidx.annotation.NonNull;
+
+import com.android.systemui.Dumpable;
 import com.android.systemui.dagger.SysUISingleton;
+import com.android.systemui.dump.DumpManager;
+
+import java.io.FileDescriptor;
+import java.io.PrintWriter;
 
 import javax.inject.Inject;
 
 /**
  * Default implementation of the a Flag manager that returns default values for release builds
+ *
+ * There's a version of this file in src-debug which allows overriding, and has documentation about
+ * how to set flags.
  */
 @SysUISingleton
-public class FeatureFlagManager implements FlagReader, FlagWriter {
+public class FeatureFlagManager implements FlagReader, FlagWriter, Dumpable {
+    SparseBooleanArray mAccessedFlags = new SparseBooleanArray();
     @Inject
-    public FeatureFlagManager() {}
+    public FeatureFlagManager(DumpManager dumpManager) {
+        dumpManager.registerDumpable("SysUIFlags", this);
+    }
     public boolean isEnabled(String key, boolean defaultValue) {
         return defaultValue;
     }
     public boolean isEnabled(int key, boolean defaultValue) {
+        mAccessedFlags.append(key, defaultValue);
         return defaultValue;
     }
     public void setEnabled(String key, boolean value) {}
     public void setEnabled(int key, boolean value) {}
+
+    @Override
+    public void dump(@NonNull FileDescriptor fd, @NonNull PrintWriter pw, @NonNull String[] args) {
+        int size = mAccessedFlags.size();
+        for (int i = 0; i < size; i++) {
+            pw.println("  sysui_flag_" + mAccessedFlags.keyAt(i)
+                    + ": " + mAccessedFlags.valueAt(i));
+        }
+    }
 }
diff --git a/packages/SystemUI/src/com/android/systemui/media/PlayerViewHolder.kt b/packages/SystemUI/src/com/android/systemui/media/PlayerViewHolder.kt
index f32dad6..042a337 100644
--- a/packages/SystemUI/src/com/android/systemui/media/PlayerViewHolder.kt
+++ b/packages/SystemUI/src/com/android/systemui/media/PlayerViewHolder.kt
@@ -106,6 +106,7 @@
          */
         @JvmStatic fun create(inflater: LayoutInflater, parent: ViewGroup): PlayerViewHolder {
             val mediaView = inflater.inflate(R.layout.media_view, parent, false)
+            mediaView.setLayerType(View.LAYER_TYPE_HARDWARE, null)
             // Because this media view (a TransitionLayout) is used to measure and layout the views
             // in various states before being attached to its parent, we can't depend on the default
             // LAYOUT_DIRECTION_INHERIT to correctly resolve the ltr direction.
diff --git a/packages/SystemUI/src/com/android/systemui/media/dialog/MediaOutputBaseDialog.java b/packages/SystemUI/src/com/android/systemui/media/dialog/MediaOutputBaseDialog.java
index 6895ef1..26ce645 100644
--- a/packages/SystemUI/src/com/android/systemui/media/dialog/MediaOutputBaseDialog.java
+++ b/packages/SystemUI/src/com/android/systemui/media/dialog/MediaOutputBaseDialog.java
@@ -104,8 +104,6 @@
         lp.setFitInsetsIgnoringVisibility(true);
         window.setAttributes(lp);
         window.setContentView(mDialogView);
-        window.setLayout(mContext.getResources().getDimensionPixelSize(R.dimen.large_dialog_width),
-                ViewGroup.LayoutParams.WRAP_CONTENT);
 
         mHeaderTitle = mDialogView.requireViewById(R.id.header_title);
         mHeaderSubtitle = mDialogView.requireViewById(R.id.header_subtitle);
diff --git a/packages/SystemUI/src/com/android/systemui/navigationbar/buttons/KeyButtonRipple.java b/packages/SystemUI/src/com/android/systemui/navigationbar/buttons/KeyButtonRipple.java
index e3e2367..00124ac 100644
--- a/packages/SystemUI/src/com/android/systemui/navigationbar/buttons/KeyButtonRipple.java
+++ b/packages/SystemUI/src/com/android/systemui/navigationbar/buttons/KeyButtonRipple.java
@@ -37,6 +37,8 @@
 import com.android.systemui.R;
 import com.android.systemui.animation.Interpolators;
 
+import androidx.annotation.Keep;
+
 import java.util.ArrayList;
 import java.util.HashSet;
 
@@ -184,19 +186,27 @@
         }
     }
 
+    /** Gets the glow alpha, used by {@link android.animation.ObjectAnimator} via reflection. */
+    @Keep
     public float getGlowAlpha() {
         return mGlowAlpha;
     }
 
+    /** Sets the glow alpha, used by {@link android.animation.ObjectAnimator} via reflection. */
+    @Keep
     public void setGlowAlpha(float x) {
         mGlowAlpha = x;
         invalidateSelf();
     }
 
+    /** Gets the glow scale, used by {@link android.animation.ObjectAnimator} via reflection. */
+    @Keep
     public float getGlowScale() {
         return mGlowScale;
     }
 
+    /** Sets the glow scale, used by {@link android.animation.ObjectAnimator} via reflection. */
+    @Keep
     public void setGlowScale(float x) {
         mGlowScale = x;
         invalidateSelf();
diff --git a/packages/SystemUI/src/com/android/systemui/privacy/PrivacyDialog.kt b/packages/SystemUI/src/com/android/systemui/privacy/PrivacyDialog.kt
index 9e8f6b8..23482677 100644
--- a/packages/SystemUI/src/com/android/systemui/privacy/PrivacyDialog.kt
+++ b/packages/SystemUI/src/com/android/systemui/privacy/PrivacyDialog.kt
@@ -24,7 +24,6 @@
 import android.view.LayoutInflater
 import android.view.View
 import android.view.ViewGroup
-import android.view.ViewGroup.LayoutParams.WRAP_CONTENT
 import android.view.WindowInsets
 import android.widget.ImageView
 import android.widget.TextView
@@ -65,7 +64,6 @@
         window?.apply {
             attributes.fitInsetsTypes = attributes.fitInsetsTypes or WindowInsets.Type.statusBars()
             attributes.receiveInsetsIgnoringZOrder = true
-            setLayout(context.resources.getDimensionPixelSize(R.dimen.qs_panel_width), WRAP_CONTENT)
             setGravity(Gravity.TOP or Gravity.CENTER_HORIZONTAL)
         }
 
diff --git a/packages/SystemUI/src/com/android/systemui/qs/QSPanel.java b/packages/SystemUI/src/com/android/systemui/qs/QSPanel.java
index 2665f3a..71eb4a2 100644
--- a/packages/SystemUI/src/com/android/systemui/qs/QSPanel.java
+++ b/packages/SystemUI/src/com/android/systemui/qs/QSPanel.java
@@ -41,7 +41,7 @@
 import com.android.systemui.R;
 import com.android.systemui.plugins.qs.DetailAdapter;
 import com.android.systemui.plugins.qs.QSTile;
-import com.android.systemui.settings.brightness.BrightnessSlider;
+import com.android.systemui.settings.brightness.BrightnessSliderController;
 import com.android.systemui.statusbar.policy.BrightnessMirrorController;
 import com.android.systemui.tuner.TunerService;
 import com.android.systemui.tuner.TunerService.Tunable;
@@ -69,7 +69,7 @@
     @Nullable
     protected View mBrightnessView;
     @Nullable
-    protected BrightnessSlider mToggleSliderController;
+    protected BrightnessSliderController mToggleSliderController;
 
     private final H mHandler = new H();
     /** Whether or not the QS media player feature is enabled. */
diff --git a/packages/SystemUI/src/com/android/systemui/qs/QSPanelController.java b/packages/SystemUI/src/com/android/systemui/qs/QSPanelController.java
index 70892a7..6794d5b 100644
--- a/packages/SystemUI/src/com/android/systemui/qs/QSPanelController.java
+++ b/packages/SystemUI/src/com/android/systemui/qs/QSPanelController.java
@@ -41,7 +41,7 @@
 import com.android.systemui.qs.logging.QSLogger;
 import com.android.systemui.settings.brightness.BrightnessController;
 import com.android.systemui.settings.brightness.BrightnessMirrorHandler;
-import com.android.systemui.settings.brightness.BrightnessSlider;
+import com.android.systemui.settings.brightness.BrightnessSliderController;
 import com.android.systemui.statusbar.CommandQueue;
 import com.android.systemui.statusbar.policy.BrightnessMirrorController;
 import com.android.systemui.tuner.TunerService;
@@ -63,7 +63,7 @@
     private final FalsingManager mFalsingManager;
     private final CommandQueue mCommandQueue;
     private final BrightnessController mBrightnessController;
-    private final BrightnessSlider mBrightnessSlider;
+    private final BrightnessSliderController mBrightnessSliderController;
     private final BrightnessMirrorHandler mBrightnessMirrorHandler;
 
     private boolean mGridContentVisible = true;
@@ -99,8 +99,8 @@
             QSTileRevealController.Factory qsTileRevealControllerFactory,
             DumpManager dumpManager, MetricsLogger metricsLogger, UiEventLogger uiEventLogger,
             QSLogger qsLogger, BrightnessController.Factory brightnessControllerFactory,
-            BrightnessSlider.Factory brightnessSliderFactory, FalsingManager falsingManager,
-            CommandQueue commandQueue) {
+            BrightnessSliderController.Factory brightnessSliderFactory,
+            FalsingManager falsingManager, CommandQueue commandQueue) {
         super(view, qstileHost, qsCustomizerController, usingMediaPlayer, mediaHost,
                 metricsLogger, uiEventLogger, qsLogger, dumpManager);
         mQsSecurityFooter = qsSecurityFooter;
@@ -111,10 +111,10 @@
         mCommandQueue = commandQueue;
         mQsSecurityFooter.setHostEnvironment(qstileHost);
 
-        mBrightnessSlider = brightnessSliderFactory.create(getContext(), mView);
-        mView.setBrightnessView(mBrightnessSlider.getRootView());
+        mBrightnessSliderController = brightnessSliderFactory.create(getContext(), mView);
+        mView.setBrightnessView(mBrightnessSliderController.getRootView());
 
-        mBrightnessController = brightnessControllerFactory.create(mBrightnessSlider);
+        mBrightnessController = brightnessControllerFactory.create(mBrightnessSliderController);
         mBrightnessMirrorHandler = new BrightnessMirrorHandler(mBrightnessController);
     }
 
@@ -125,7 +125,7 @@
         mMediaHost.setShowsOnlyActiveMedia(false);
         mMediaHost.init(MediaHierarchyManager.LOCATION_QS);
         mQsCustomizerController.init();
-        mBrightnessSlider.init();
+        mBrightnessSliderController.init();
     }
 
     @Override
diff --git a/packages/SystemUI/src/com/android/systemui/qs/QuickQSBrightnessController.kt b/packages/SystemUI/src/com/android/systemui/qs/QuickQSBrightnessController.kt
index 14374ff..65889d7 100644
--- a/packages/SystemUI/src/com/android/systemui/qs/QuickQSBrightnessController.kt
+++ b/packages/SystemUI/src/com/android/systemui/qs/QuickQSBrightnessController.kt
@@ -18,7 +18,7 @@
 
 import androidx.annotation.VisibleForTesting
 import com.android.systemui.settings.brightness.BrightnessController
-import com.android.systemui.settings.brightness.BrightnessSlider
+import com.android.systemui.settings.brightness.BrightnessSliderController
 import com.android.systemui.settings.brightness.MirroredBrightnessController
 import com.android.systemui.statusbar.policy.BrightnessMirrorController
 import javax.inject.Inject
@@ -33,10 +33,11 @@
 
     @Inject constructor(
         brightnessControllerFactory: BrightnessController.Factory,
-        brightnessSliderFactory: BrightnessSlider.Factory,
+        brightnessSliderControllerFactory: BrightnessSliderController.Factory,
         quickQSPanel: QuickQSPanel
     ) : this(brightnessControllerFactory = {
-            val slider = brightnessSliderFactory.create(quickQSPanel.context, quickQSPanel)
+            val slider = brightnessSliderControllerFactory.create(quickQSPanel.context,
+                    quickQSPanel)
             slider.init()
             quickQSPanel.setBrightnessView(slider.rootView)
             brightnessControllerFactory.create(slider)
diff --git a/packages/SystemUI/src/com/android/systemui/qs/tileimpl/QSTileViewImpl.kt b/packages/SystemUI/src/com/android/systemui/qs/tileimpl/QSTileViewImpl.kt
index 36101c9..69be3326 100644
--- a/packages/SystemUI/src/com/android/systemui/qs/tileimpl/QSTileViewImpl.kt
+++ b/packages/SystemUI/src/com/android/systemui/qs/tileimpl/QSTileViewImpl.kt
@@ -42,6 +42,7 @@
 import com.android.settingslib.Utils
 import com.android.systemui.FontSizeUtils
 import com.android.systemui.R
+import com.android.systemui.animation.LaunchableView
 import com.android.systemui.plugins.qs.QSIconView
 import com.android.systemui.plugins.qs.QSTile
 import com.android.systemui.plugins.qs.QSTile.BooleanState
@@ -54,7 +55,7 @@
     context: Context,
     private val _icon: QSIconView,
     private val collapsed: Boolean = false
-) : QSTileView(context), HeightOverrideable {
+) : QSTileView(context), HeightOverrideable, LaunchableView {
 
     companion object {
         private const val INVALID = -1
@@ -130,6 +131,8 @@
     private var lastStateDescription: CharSequence? = null
     private var tileState = false
     private var lastState = INVALID
+    private var blockVisibilityChanges = false
+    private var lastVisibility = View.VISIBLE
 
     private val locInScreen = IntArray(2)
 
@@ -319,6 +322,36 @@
         return sideView
     }
 
+    override fun setShouldBlockVisibilityChanges(block: Boolean) {
+        blockVisibilityChanges = block
+
+        if (block) {
+            lastVisibility = visibility
+        } else {
+            visibility = lastVisibility
+        }
+    }
+
+    override fun setVisibility(visibility: Int) {
+        if (blockVisibilityChanges) {
+            lastVisibility = visibility
+            return
+        }
+
+        super.setVisibility(visibility)
+    }
+
+    override fun setTransitionVisibility(visibility: Int) {
+        if (blockVisibilityChanges) {
+            // View.setTransitionVisibility just sets the visibility flag, so we don't have to save
+            // the transition visibility separately from the normal visibility.
+            lastVisibility = visibility
+            return
+        }
+
+        super.setTransitionVisibility(visibility)
+    }
+
     // Accessibility
 
     override fun onInitializeAccessibilityEvent(event: AccessibilityEvent) {
@@ -484,7 +517,7 @@
     }
 
     private fun setColor(color: Int) {
-        colorBackgroundDrawable.setTint(color)
+        colorBackgroundDrawable.mutate().setTint(color)
         paintColor = color
     }
 
diff --git a/packages/SystemUI/src/com/android/systemui/qs/tiles/InternetTile.java b/packages/SystemUI/src/com/android/systemui/qs/tiles/InternetTile.java
index 98d0a72..23b2a76 100644
--- a/packages/SystemUI/src/com/android/systemui/qs/tiles/InternetTile.java
+++ b/packages/SystemUI/src/com/android/systemui/qs/tiles/InternetTile.java
@@ -124,7 +124,7 @@
     protected void handleClick(@Nullable View view) {
         mHandler.post(() -> mInternetDialogFactory.create(true,
                 mAccessPointController.canConfigMobileData(),
-                mAccessPointController.canConfigWifi()));
+                mAccessPointController.canConfigWifi(), view));
     }
 
     @Override
diff --git a/packages/SystemUI/src/com/android/systemui/qs/tiles/dialog/InternetDialog.java b/packages/SystemUI/src/com/android/systemui/qs/tiles/dialog/InternetDialog.java
index 58e8992..1dab263 100644
--- a/packages/SystemUI/src/com/android/systemui/qs/tiles/dialog/InternetDialog.java
+++ b/packages/SystemUI/src/com/android/systemui/qs/tiles/dialog/InternetDialog.java
@@ -15,14 +15,10 @@
  */
 package com.android.systemui.qs.tiles.dialog;
 
-import static android.view.WindowManager.LayoutParams.FLAG_LAYOUT_NO_LIMITS;
-
 import static com.android.systemui.Prefs.Key.QS_HAS_TURNED_OFF_MOBILE_DATA;
 
 import android.app.AlertDialog;
 import android.content.Context;
-import android.graphics.Color;
-import android.graphics.drawable.ColorDrawable;
 import android.graphics.drawable.Drawable;
 import android.net.Network;
 import android.net.NetworkCapabilities;
@@ -41,10 +37,7 @@
 import android.view.Gravity;
 import android.view.LayoutInflater;
 import android.view.View;
-import android.view.ViewGroup;
-import android.view.ViewTreeObserver;
 import android.view.Window;
-import android.view.WindowInsets;
 import android.view.WindowManager;
 import android.widget.FrameLayout;
 import android.widget.ImageView;
@@ -87,7 +80,6 @@
 
     private final Handler mHandler;
     private final Executor mBackgroundExecutor;
-    private final LinearLayoutManager mLayoutManager;
 
     @VisibleForTesting
     protected InternetAdapter mAdapter;
@@ -130,7 +122,6 @@
     private Switch mWiFiToggle;
     private FrameLayout mDoneLayout;
     private Drawable mBackgroundOn;
-    private int mListMaxHeight;
     private int mDefaultDataSubId = SubscriptionManager.INVALID_SUBSCRIPTION_ID;
     private boolean mCanConfigMobileData;
 
@@ -149,20 +140,11 @@
         mInternetDialogSubTitle.setText(getSubtitleText());
     };
 
-    private final ViewTreeObserver.OnGlobalLayoutListener mInternetListLayoutListener = () -> {
-        // Set max height for list
-        if (mInternetDialogLayout.getHeight() > mListMaxHeight) {
-            ViewGroup.LayoutParams params = mInternetDialogLayout.getLayoutParams();
-            params.height = mListMaxHeight;
-            mInternetDialogLayout.setLayoutParams(params);
-        }
-    };
-
     public InternetDialog(Context context, InternetDialogFactory internetDialogFactory,
             InternetDialogController internetDialogController, boolean canConfigMobileData,
             boolean canConfigWifi, boolean aboveStatusBar, UiEventLogger uiEventLogger,
             @Main Handler handler, @Background Executor executor) {
-        super(context, R.style.Theme_SystemUI_Dialog_Internet);
+        super(context);
         if (DEBUG) {
             Log.d(TAG, "Init InternetDialog");
         }
@@ -178,14 +160,6 @@
         mCanConfigMobileData = canConfigMobileData;
         mCanConfigWifi = canConfigWifi;
 
-        mLayoutManager = new LinearLayoutManager(mContext) {
-            @Override
-            public boolean canScrollVertically() {
-                return false;
-            }
-        };
-        mListMaxHeight = context.getResources().getDimensionPixelSize(
-                R.dimen.internet_dialog_list_max_height);
         mUiEventLogger = uiEventLogger;
         mAdapter = new InternetAdapter(mInternetDialogController);
         if (!aboveStatusBar) {
@@ -203,21 +177,9 @@
         mDialogView = LayoutInflater.from(mContext).inflate(R.layout.internet_connectivity_dialog,
                 null);
         final Window window = getWindow();
-        final WindowManager.LayoutParams layoutParams = window.getAttributes();
-        layoutParams.gravity = Gravity.BOTTOM;
-        // Move down the dialog to overlay the navigation bar.
-        layoutParams.setFitInsetsTypes(
-                layoutParams.getFitInsetsTypes() & ~WindowInsets.Type.navigationBars());
-        layoutParams.setFitInsetsSides(WindowInsets.Side.all());
-        layoutParams.setFitInsetsIgnoringVisibility(true);
-        window.setAttributes(layoutParams);
         window.setContentView(mDialogView);
-        //Only fix the width for large screen or tablet.
-        window.setLayout(mContext.getResources().getDimensionPixelSize(
-                R.dimen.large_dialog_width), ViewGroup.LayoutParams.WRAP_CONTENT);
+
         window.setWindowAnimations(R.style.Animation_InternetDialog);
-        window.setBackgroundDrawable(new ColorDrawable(Color.TRANSPARENT));
-        window.addFlags(FLAG_LAYOUT_NO_LIMITS);
 
         mInternetDialogLayout = mDialogView.requireViewById(R.id.internet_connectivity_dialog);
         mInternetDialogTitle = mDialogView.requireViewById(R.id.internet_dialog_title);
@@ -244,14 +206,12 @@
         mMobileDataToggle = mDialogView.requireViewById(R.id.mobile_toggle);
         mWiFiToggle = mDialogView.requireViewById(R.id.wifi_toggle);
         mBackgroundOn = mContext.getDrawable(R.drawable.settingslib_switch_bar_bg_on);
-        mInternetDialogLayout.getViewTreeObserver().addOnGlobalLayoutListener(
-                mInternetListLayoutListener);
         mInternetDialogTitle.setText(getDialogTitleText());
         mInternetDialogTitle.setGravity(Gravity.START | Gravity.CENTER_VERTICAL);
 
         setOnClickListener();
         mTurnWifiOnLayout.setBackground(null);
-        mWifiRecyclerView.setLayoutManager(mLayoutManager);
+        mWifiRecyclerView.setLayoutManager(new LinearLayoutManager(mContext));
         mWifiRecyclerView.setAdapter(mAdapter);
     }
 
@@ -502,10 +462,6 @@
     }
 
     private void setProgressBarVisible(boolean visible) {
-        if (mWifiManager.isWifiEnabled() && mAdapter.mHolderView != null
-                && mAdapter.mHolderView.isAttachedToWindow()) {
-            mIsProgressBarVisible = true;
-        }
         mIsProgressBarVisible = visible;
         mProgressBar.setVisibility(mIsProgressBarVisible ? View.VISIBLE : View.GONE);
         mDivider.setVisibility(mIsProgressBarVisible ? View.GONE : View.VISIBLE);
diff --git a/packages/SystemUI/src/com/android/systemui/qs/tiles/dialog/InternetDialogController.java b/packages/SystemUI/src/com/android/systemui/qs/tiles/dialog/InternetDialogController.java
index 40590a7..5673136 100644
--- a/packages/SystemUI/src/com/android/systemui/qs/tiles/dialog/InternetDialogController.java
+++ b/packages/SystemUI/src/com/android/systemui/qs/tiles/dialog/InternetDialogController.java
@@ -71,6 +71,7 @@
 import com.android.settingslib.net.SignalStrengthUtil;
 import com.android.settingslib.wifi.WifiUtils;
 import com.android.systemui.R;
+import com.android.systemui.animation.DialogLaunchAnimator;
 import com.android.systemui.broadcast.BroadcastDispatcher;
 import com.android.systemui.dagger.qualifiers.Background;
 import com.android.systemui.dagger.qualifiers.Main;
@@ -152,6 +153,7 @@
     private ToastFactory mToastFactory;
     private SignalDrawable mSignalDrawable;
     private LocationController mLocationController;
+    private DialogLaunchAnimator mDialogLaunchAnimator;
 
     @VisibleForTesting
     static final float TOAST_PARAMS_HORIZONTAL_WEIGHT = 1.0f;
@@ -202,7 +204,8 @@
             WindowManager windowManager, ToastFactory toastFactory,
             @Background Handler workerHandler,
             CarrierConfigTracker carrierConfigTracker,
-            LocationController locationController) {
+            LocationController locationController,
+            DialogLaunchAnimator dialogLaunchAnimator) {
         if (DEBUG) {
             Log.d(TAG, "Init InternetDialogController");
         }
@@ -231,6 +234,7 @@
         mToastFactory = toastFactory;
         mSignalDrawable = new SignalDrawable(mContext);
         mLocationController = locationController;
+        mDialogLaunchAnimator = dialogLaunchAnimator;
     }
 
     void onStart(@NonNull InternetDialogCallback callback, boolean canConfigWifi) {
@@ -596,20 +600,32 @@
     }
 
     void launchNetworkSetting() {
+        // Dismissing a dialog into its touch surface and starting an activity at the same time
+        // looks bad, so let's make sure the dialog just fades out quickly.
+        mDialogLaunchAnimator.disableAllCurrentDialogsExitAnimations();
         mCallback.dismissDialog();
+
         mActivityStarter.postStartActivityDismissingKeyguard(getSettingsIntent(), 0);
     }
 
     void launchWifiNetworkDetailsSetting(String key) {
         Intent intent = getWifiDetailsSettingsIntent(key);
         if (intent != null) {
+            // Dismissing a dialog into its touch surface and starting an activity at the same time
+            // looks bad, so let's make sure the dialog just fades out quickly.
+            mDialogLaunchAnimator.disableAllCurrentDialogsExitAnimations();
             mCallback.dismissDialog();
+
             mActivityStarter.postStartActivityDismissingKeyguard(intent, 0);
         }
     }
 
     void launchWifiScanningSetting() {
+        // Dismissing a dialog into its touch surface and starting an activity at the same time
+        // looks bad, so let's make sure the dialog just fades out quickly.
+        mDialogLaunchAnimator.disableAllCurrentDialogsExitAnimations();
         mCallback.dismissDialog();
+
         final Intent intent = new Intent(ACTION_WIFI_SCANNING_SETTINGS);
         intent.addFlags(Intent.FLAG_ACTIVITY_NEW_TASK);
         mActivityStarter.postStartActivityDismissingKeyguard(intent, 0);
diff --git a/packages/SystemUI/src/com/android/systemui/qs/tiles/dialog/InternetDialogFactory.kt b/packages/SystemUI/src/com/android/systemui/qs/tiles/dialog/InternetDialogFactory.kt
index ea5df17..93828b3 100644
--- a/packages/SystemUI/src/com/android/systemui/qs/tiles/dialog/InternetDialogFactory.kt
+++ b/packages/SystemUI/src/com/android/systemui/qs/tiles/dialog/InternetDialogFactory.kt
@@ -18,9 +18,11 @@
 import android.content.Context
 import android.os.Handler
 import android.util.Log
+import android.view.View
 import com.android.internal.logging.UiEventLogger
+import com.android.systemui.animation.DialogLaunchAnimator
 import com.android.systemui.dagger.SysUISingleton
-import com.android.systemui.dagger.qualifiers.Background;
+import com.android.systemui.dagger.qualifiers.Background
 import com.android.systemui.dagger.qualifiers.Main
 import java.util.concurrent.Executor
 import javax.inject.Inject
@@ -37,14 +39,20 @@
     @Background private val executor: Executor,
     private val internetDialogController: InternetDialogController,
     private val context: Context,
-    private val uiEventLogger: UiEventLogger
+    private val uiEventLogger: UiEventLogger,
+    private val dialogLaunchAnimator: DialogLaunchAnimator
 ) {
     companion object {
         var internetDialog: InternetDialog? = null
     }
 
-    /** Creates a [InternetDialog]. */
-    fun create(aboveStatusBar: Boolean, canConfigMobileData: Boolean, canConfigWifi: Boolean) {
+    /** Creates a [InternetDialog]. The dialog will be animated from [view] if it is not null. */
+    fun create(
+        aboveStatusBar: Boolean,
+        canConfigMobileData: Boolean,
+        canConfigWifi: Boolean,
+        view: View?
+    ) {
         if (internetDialog != null) {
             if (DEBUG) {
                 Log.d(TAG, "InternetDialog is showing, do not create it twice.")
@@ -54,7 +62,11 @@
             internetDialog = InternetDialog(context, this, internetDialogController,
                     canConfigMobileData, canConfigWifi, aboveStatusBar, uiEventLogger, handler,
                     executor)
-            internetDialog?.show()
+            if (view != null) {
+                dialogLaunchAnimator.showFromView(internetDialog!!, view)
+            } else {
+                internetDialog?.show()
+            }
         }
     }
 
diff --git a/packages/SystemUI/src/com/android/systemui/qs/user/UserDialog.kt b/packages/SystemUI/src/com/android/systemui/qs/user/UserDialog.kt
index 01afa56..26d1bbd 100644
--- a/packages/SystemUI/src/com/android/systemui/qs/user/UserDialog.kt
+++ b/packages/SystemUI/src/com/android/systemui/qs/user/UserDialog.kt
@@ -71,10 +71,6 @@
             setType(WindowManager.LayoutParams.TYPE_STATUS_BAR_SUB_PANEL)
             attributes.fitInsetsTypes = attributes.fitInsetsTypes or WindowInsets.Type.statusBars()
             attributes.receiveInsetsIgnoringZOrder = true
-            setLayout(
-                    context.resources.getDimensionPixelSize(R.dimen.notification_panel_width),
-                    ViewGroup.LayoutParams.WRAP_CONTENT
-            )
             setGravity(Gravity.CENTER)
         }
         setContentView(R.layout.qs_user_dialog_content)
diff --git a/packages/SystemUI/src/com/android/systemui/settings/brightness/BrightnessController.java b/packages/SystemUI/src/com/android/systemui/settings/brightness/BrightnessController.java
index acc6ee1..d7d1de0 100644
--- a/packages/SystemUI/src/com/android/systemui/settings/brightness/BrightnessController.java
+++ b/packages/SystemUI/src/com/android/systemui/settings/brightness/BrightnessController.java
@@ -51,8 +51,6 @@
 import com.android.systemui.settings.CurrentUserTracker;
 import com.android.systemui.statusbar.policy.BrightnessMirrorController;
 
-import java.util.ArrayList;
-
 import javax.inject.Inject;
 
 public class BrightnessController implements ToggleSlider.Listener, MirroredBrightnessController {
@@ -92,13 +90,9 @@
         @Override
         public void onDisplayChanged(int displayId) {
             mBackgroundHandler.post(mUpdateSliderRunnable);
-            notifyCallbacks();
         }
     };
 
-    private ArrayList<BrightnessStateChangeCallback> mChangeCallbacks =
-            new ArrayList<BrightnessStateChangeCallback>();
-
     private volatile boolean mAutomatic;  // Brightness adjusted automatically using ambient light.
     private volatile boolean mIsVrModeEnabled;
     private boolean mListening;
@@ -114,11 +108,6 @@
         mControl.setMirrorControllerAndMirror(controller);
     }
 
-    public interface BrightnessStateChangeCallback {
-        /** Indicates that some of the brightness settings have changed */
-        void onBrightnessLevelChanged();
-    }
-
     /** ContentObserver to watch brightness */
     private class BrightnessObserver extends ContentObserver {
 
@@ -139,7 +128,6 @@
                 mBackgroundHandler.post(mUpdateModeRunnable);
                 mBackgroundHandler.post(mUpdateSliderRunnable);
             }
-            notifyCallbacks();
         }
 
         public void startObserving() {
@@ -317,14 +305,6 @@
                 Context.VR_SERVICE));
     }
 
-    public void addStateChangedCallback(BrightnessStateChangeCallback cb) {
-        mChangeCallbacks.add(cb);
-    }
-
-    public boolean removeStateChangedCallback(BrightnessStateChangeCallback cb) {
-        return mChangeCallbacks.remove(cb);
-    }
-
     public void registerCallbacks() {
         mBackgroundHandler.post(mStartListeningRunnable);
     }
@@ -375,10 +355,6 @@
                     }
                 });
         }
-
-        for (BrightnessStateChangeCallback cb : mChangeCallbacks) {
-            cb.onBrightnessLevelChanged();
-        }
     }
 
     public void checkRestrictionAndSetEnabled() {
@@ -435,8 +411,12 @@
     }
 
     private void animateSliderTo(int target) {
-        if (!mControlValueInitialized) {
+        if (!mControlValueInitialized || !mControl.isVisible()) {
             // Don't animate the first value since its default state isn't meaningful to users.
+            // We also don't want to animate slider if it's not visible - especially important when
+            // two sliders are active at the same time in split shade (one in QS and one in QQS),
+            // as this negatively affects transition between them and they share mirror slider -
+            // animating it from two different sources causes janky motion
             mControl.setValue(target);
             mControlValueInitialized = true;
         }
@@ -455,13 +435,6 @@
         mSliderAnimator.start();
     }
 
-    private void notifyCallbacks() {
-        final int size = mChangeCallbacks.size();
-        for (int i = 0; i < size; i++) {
-            mChangeCallbacks.get(i).onBrightnessLevelChanged();
-        }
-    }
-
     /** Factory for creating a {@link BrightnessController}. */
     public static class Factory {
         private final Context mContext;
diff --git a/packages/SystemUI/src/com/android/systemui/settings/brightness/BrightnessDialog.java b/packages/SystemUI/src/com/android/systemui/settings/brightness/BrightnessDialog.java
index 8fc831a..c9c1a9b 100644
--- a/packages/SystemUI/src/com/android/systemui/settings/brightness/BrightnessDialog.java
+++ b/packages/SystemUI/src/com/android/systemui/settings/brightness/BrightnessDialog.java
@@ -41,14 +41,14 @@
 public class BrightnessDialog extends Activity {
 
     private BrightnessController mBrightnessController;
-    private final BrightnessSlider.Factory mToggleSliderFactory;
+    private final BrightnessSliderController.Factory mToggleSliderFactory;
     private final BroadcastDispatcher mBroadcastDispatcher;
     private final Handler mBackgroundHandler;
 
     @Inject
     public BrightnessDialog(
             BroadcastDispatcher broadcastDispatcher,
-            BrightnessSlider.Factory factory,
+            BrightnessSliderController.Factory factory,
             @Background Handler bgHandler) {
         mBroadcastDispatcher = broadcastDispatcher;
         mToggleSliderFactory = factory;
@@ -77,7 +77,7 @@
         // The brightness mirror container is INVISIBLE by default.
         frame.setVisibility(View.VISIBLE);
 
-        BrightnessSlider controller = mToggleSliderFactory.create(this, frame);
+        BrightnessSliderController controller = mToggleSliderFactory.create(this, frame);
         controller.init();
         frame.addView(controller.getRootView(), MATCH_PARENT, WRAP_CONTENT);
 
diff --git a/packages/SystemUI/src/com/android/systemui/settings/brightness/BrightnessSlider.java b/packages/SystemUI/src/com/android/systemui/settings/brightness/BrightnessSliderController.java
similarity index 89%
rename from packages/SystemUI/src/com/android/systemui/settings/brightness/BrightnessSlider.java
rename to packages/SystemUI/src/com/android/systemui/settings/brightness/BrightnessSliderController.java
index b0e320a..6c8190a 100644
--- a/packages/SystemUI/src/com/android/systemui/settings/brightness/BrightnessSlider.java
+++ b/packages/SystemUI/src/com/android/systemui/settings/brightness/BrightnessSliderController.java
@@ -44,7 +44,8 @@
  *
  * @see BrightnessMirrorController
  */
-public class BrightnessSlider extends ViewController<BrightnessSliderView> implements ToggleSlider {
+public class BrightnessSliderController extends ViewController<BrightnessSliderView> implements
+        ToggleSlider {
 
     private Listener mListener;
     private ToggleSlider mMirror;
@@ -69,7 +70,7 @@
         }
     };
 
-    BrightnessSlider(
+    BrightnessSliderController(
             BrightnessSliderView brightnessSliderView,
             FalsingManager falsingManager) {
         super(brightnessSliderView);
@@ -184,6 +185,15 @@
         mView.setVisibility(View.VISIBLE);
     }
 
+    @Override
+    public boolean isVisible() {
+        // this should be called rarely - once or twice per slider's value change, but not for
+        // every value change when user slides finger - only the final one.
+        // If view is not visible this call is quick (around 50 µs) as it sees parent is not visible
+        // otherwise it's slightly longer (70 µs) because there are more checks to be done
+        return mView.isVisibleToUser();
+    }
+
     private final SeekBar.OnSeekBarChangeListener mSeekListener =
             new SeekBar.OnSeekBarChangeListener() {
         @Override
@@ -222,7 +232,7 @@
     };
 
     /**
-     * Creates a {@link BrightnessSlider} with its associated view.
+     * Creates a {@link BrightnessSliderController} with its associated view.
      */
     public static class Factory {
 
@@ -240,11 +250,11 @@
          * @param viewRoot the {@link ViewGroup} that will contain the hierarchy. The inflated
          *                 hierarchy will not be attached
          */
-        public BrightnessSlider create(Context context, @Nullable ViewGroup viewRoot) {
+        public BrightnessSliderController create(Context context, @Nullable ViewGroup viewRoot) {
             int layout = getLayout();
             BrightnessSliderView root = (BrightnessSliderView) LayoutInflater.from(context)
                     .inflate(layout, viewRoot, false);
-            return new BrightnessSlider(root, mFalsingManager);
+            return new BrightnessSliderController(root, mFalsingManager);
         }
 
         /** Get the layout to inflate based on what slider to use */
diff --git a/packages/SystemUI/src/com/android/systemui/settings/brightness/BrightnessSliderView.java b/packages/SystemUI/src/com/android/systemui/settings/brightness/BrightnessSliderView.java
index 15aa2b7..0e037ad 100644
--- a/packages/SystemUI/src/com/android/systemui/settings/brightness/BrightnessSliderView.java
+++ b/packages/SystemUI/src/com/android/systemui/settings/brightness/BrightnessSliderView.java
@@ -60,6 +60,7 @@
     @Override
     protected void onFinishInflate() {
         super.onFinishInflate();
+        setLayerType(LAYER_TYPE_HARDWARE, null);
 
         mSlider = requireViewById(R.id.slider);
         mSlider.setAccessibilityLabel(getContentDescription().toString());
diff --git a/packages/SystemUI/src/com/android/systemui/settings/brightness/ToggleSlider.java b/packages/SystemUI/src/com/android/systemui/settings/brightness/ToggleSlider.java
index 5de22d4..648e33b 100644
--- a/packages/SystemUI/src/com/android/systemui/settings/brightness/ToggleSlider.java
+++ b/packages/SystemUI/src/com/android/systemui/settings/brightness/ToggleSlider.java
@@ -38,4 +38,5 @@
 
     void showView();
     void hideView();
+    boolean isVisible();
 }
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/NotificationLockscreenUserManagerImpl.java b/packages/SystemUI/src/com/android/systemui/statusbar/NotificationLockscreenUserManagerImpl.java
index db7d5c1..856052e 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/NotificationLockscreenUserManagerImpl.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/NotificationLockscreenUserManagerImpl.java
@@ -19,8 +19,8 @@
 import static android.app.admin.DevicePolicyManager.ACTION_DEVICE_POLICY_MANAGER_STATE_CHANGED;
 
 import static com.android.systemui.DejankUtils.whitelistIpcs;
-import static com.android.systemui.statusbar.notification.stack.NotificationSectionsManagerKt.BUCKET_MEDIA_CONTROLS;
-import static com.android.systemui.statusbar.notification.stack.NotificationSectionsManagerKt.BUCKET_SILENT;
+import static com.android.systemui.statusbar.notification.stack.NotificationPriorityBucketKt.BUCKET_MEDIA_CONTROLS;
+import static com.android.systemui.statusbar.notification.stack.NotificationPriorityBucketKt.BUCKET_SILENT;
 
 import android.app.ActivityManager;
 import android.app.KeyguardManager;
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/StatusBarStateControllerImpl.java b/packages/SystemUI/src/com/android/systemui/statusbar/StatusBarStateControllerImpl.java
index 6da981b..cbb3aba 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/StatusBarStateControllerImpl.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/StatusBarStateControllerImpl.java
@@ -28,7 +28,6 @@
 import android.animation.ObjectAnimator;
 import android.animation.ValueAnimator;
 import android.os.SystemProperties;
-import android.os.Trace;
 import android.text.format.DateFormat;
 import android.util.FloatProperty;
 import android.util.Log;
@@ -182,7 +181,6 @@
         }
 
         synchronized (mListeners) {
-            Trace.beginSection(TAG + "#setState(" + StatusBarState.toShortString(state) + ")");
             String tag = getClass().getSimpleName() + "#setState(" + state + ")";
             DejankUtils.startDetectingBlockingIpcs(tag);
             for (RankedListener rl : new ArrayList<>(mListeners)) {
@@ -200,7 +198,6 @@
                 rl.mListener.onStatePostChange();
             }
             DejankUtils.stopDetectingBlockingIpcs(tag);
-            Trace.endSection();
         }
 
         return true;
@@ -265,14 +262,12 @@
         mIsDozing = isDozing;
 
         synchronized (mListeners) {
-            Trace.beginSection(TAG + "#setDozing(" + isDozing + ")");
             String tag = getClass().getSimpleName() + "#setIsDozing";
             DejankUtils.startDetectingBlockingIpcs(tag);
             for (RankedListener rl : new ArrayList<>(mListeners)) {
                 rl.mListener.onDozingChanged(isDozing);
             }
             DejankUtils.stopDetectingBlockingIpcs(tag);
-            Trace.endSection();
         }
 
         return true;
@@ -338,14 +333,12 @@
         mDozeAmount = dozeAmount;
         float interpolatedAmount = mDozeInterpolator.getInterpolation(dozeAmount);
         synchronized (mListeners) {
-            Trace.beginSection(TAG + "#setDozeAmount");
             String tag = getClass().getSimpleName() + "#setDozeAmount";
             DejankUtils.startDetectingBlockingIpcs(tag);
             for (RankedListener rl : new ArrayList<>(mListeners)) {
                 rl.mListener.onDozeAmountChanged(mDozeAmount, interpolatedAmount);
             }
             DejankUtils.stopDetectingBlockingIpcs(tag);
-            Trace.endSection();
         }
     }
 
@@ -476,13 +469,11 @@
     public void setPulsing(boolean pulsing) {
         if (mPulsing != pulsing) {
             mPulsing = pulsing;
-            Trace.beginSection(TAG + "#setPulsing(" + pulsing + ")");
             synchronized (mListeners) {
                 for (RankedListener rl : new ArrayList<>(mListeners)) {
                     rl.mListener.onPulsingChanged(pulsing);
                 }
             }
-            Trace.endSection();
         }
     }
 
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/connectivity/NetworkControllerImpl.java b/packages/SystemUI/src/com/android/systemui/statusbar/connectivity/NetworkControllerImpl.java
index f72178f..daae43f 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/connectivity/NetworkControllerImpl.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/connectivity/NetworkControllerImpl.java
@@ -811,7 +811,8 @@
                 break;
             case Settings.Panel.ACTION_INTERNET_CONNECTIVITY:
                 mMainHandler.post(() -> mInternetDialogFactory.create(true,
-                        mAccessPoints.canConfigMobileData(), mAccessPoints.canConfigWifi()));
+                        mAccessPoints.canConfigMobileData(), mAccessPoints.canConfigWifi(),
+                        null /* view */));
                 break;
             default:
                 int subId = intent.getIntExtra(SubscriptionManager.EXTRA_SUBSCRIPTION_INDEX,
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/lockscreen/LockscreenSmartspaceController.kt b/packages/SystemUI/src/com/android/systemui/statusbar/lockscreen/LockscreenSmartspaceController.kt
index cf9daf6..4e5bc8e 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/lockscreen/LockscreenSmartspaceController.kt
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/lockscreen/LockscreenSmartspaceController.kt
@@ -34,7 +34,6 @@
 import android.view.ViewGroup
 import com.android.settingslib.Utils
 import com.android.systemui.R
-import com.android.systemui.animation.ActivityLaunchAnimator
 import com.android.systemui.dagger.SysUISingleton
 import com.android.systemui.dagger.qualifiers.Main
 import com.android.systemui.plugins.ActivityStarter
@@ -190,24 +189,19 @@
         val ssView = plugin.getView(parent)
         ssView.registerDataProvider(plugin)
 
-        val animationController = ActivityLaunchAnimator.Controller.fromView(
-            ssView as View,
-            null /* cujType */
-        )
-
         ssView.setIntentStarter(object : BcSmartspaceDataPlugin.IntentStarter {
-            override fun startIntent(v: View?, i: Intent?, showOnLockscreen: Boolean) {
+            override fun startIntent(view: View, intent: Intent, showOnLockscreen: Boolean) {
                 activityStarter.startActivity(
-                    i,
+                    intent,
                     true, /* dismissShade */
-                    animationController,
+                    null, /* launch animator - looks bad with the transparent smartspace bg */
                     showOnLockscreen
                 )
             }
 
-            override fun startPendingIntent(pi: PendingIntent?, showOnLockscreen: Boolean) {
+            override fun startPendingIntent(pi: PendingIntent, showOnLockscreen: Boolean) {
                 if (showOnLockscreen) {
-                    pi?.send()
+                    pi.send()
                 } else {
                     activityStarter.startPendingIntentDismissingKeyguard(pi)
                 }
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/ListEntry.java b/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/ListEntry.java
index fd0476b..37eacad 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/ListEntry.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/ListEntry.java
@@ -36,7 +36,7 @@
     private final ListAttachState mPreviousAttachState = ListAttachState.create();
     private final ListAttachState mAttachState = ListAttachState.create();
 
-    ListEntry(String key, long creationTime) {
+    protected ListEntry(String key, long creationTime) {
         mKey = key;
         mCreationTime = creationTime;
     }
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/NotifCollection.java b/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/NotifCollection.java
index 277b4ac..dfdc548 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/NotifCollection.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/NotifCollection.java
@@ -523,7 +523,9 @@
         }
     }
 
-    private void onEndLifetimeExtension(NotifLifetimeExtender extender, NotificationEntry entry) {
+    private void onEndLifetimeExtension(
+            @NonNull NotifLifetimeExtender extender,
+            @NonNull NotificationEntry entry) {
         Assert.isMainThread();
         if (!mAttached) {
             return;
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/NotificationEntry.java b/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/NotificationEntry.java
index 94ee868..66d019e 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/NotificationEntry.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/NotificationEntry.java
@@ -31,7 +31,7 @@
 import static android.app.NotificationManager.Policy.SUPPRESSED_EFFECT_STATUS_BAR;
 
 import static com.android.systemui.statusbar.notification.collection.NotifCollection.REASON_NOT_CANCELED;
-import static com.android.systemui.statusbar.notification.stack.NotificationSectionsManagerKt.BUCKET_ALERTING;
+import static com.android.systemui.statusbar.notification.stack.NotificationPriorityBucketKt.BUCKET_ALERTING;
 
 import static java.util.Objects.requireNonNull;
 
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/ShadeListBuilder.java b/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/ShadeListBuilder.java
index e26fa04..3730524 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/ShadeListBuilder.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/ShadeListBuilder.java
@@ -52,6 +52,7 @@
 import com.android.systemui.statusbar.notification.collection.listbuilder.pluggable.NotifStabilityManager;
 import com.android.systemui.statusbar.notification.collection.listbuilder.pluggable.Pluggable;
 import com.android.systemui.statusbar.notification.collection.notifcollection.CollectionReadyForBuildListener;
+import com.android.systemui.statusbar.notification.stack.NotificationPriorityBucketKt;
 import com.android.systemui.util.Assert;
 import com.android.systemui.util.time.SystemClock;
 
@@ -78,6 +79,8 @@
     private final SystemClock mSystemClock;
     private final ShadeListBuilderLogger mLogger;
     private final NotificationInteractionTracker mInteractionTracker;
+    // used exclusivly by ShadeListBuilder#notifySectionEntriesUpdated
+    private final ArrayList<ListEntry> mTempSectionMembers = new ArrayList<>();
 
     private List<ListEntry> mNotifList = new ArrayList<>();
     private List<ListEntry> mNewNotifList = new ArrayList<>();
@@ -356,7 +359,7 @@
         // section by our list of custom comparators
         dispatchOnBeforeSort(mReadOnlyNotifList);
         mPipelineState.incrementTo(STATE_SORTING);
-        sortList();
+        sortListAndNotifySections();
 
         // Step 7: Lock in our group structure and log anything that's changed since the last run
         mPipelineState.incrementTo(STATE_FINALIZING);
@@ -382,6 +385,22 @@
         mIterationCount++;
     }
 
+    private void notifySectionEntriesUpdated() {
+        NotifSection currentSection = null;
+        mTempSectionMembers.clear();
+        for (int i = 0; i < mNotifList.size(); i++) {
+            ListEntry currentEntry = mNotifList.get(i);
+            if (currentSection != currentEntry.getSection()) {
+                if (currentSection != null) {
+                    currentSection.getSectioner().onEntriesUpdated(mTempSectionMembers);
+                    mTempSectionMembers.clear();
+                }
+                currentSection = currentEntry.getSection();
+            }
+            mTempSectionMembers.add(currentEntry);
+        }
+    }
+
     /**
      * Points mNotifList to the list stored in mNewNotifList.
      * Reuses the (emptied) mNotifList as mNewNotifList.
@@ -713,14 +732,14 @@
         }
     }
 
-    private void sortList() {
+    private void sortListAndNotifySections() {
         // Assign sections to top-level elements and sort their children
         for (ListEntry entry : mNotifList) {
             NotifSection section = applySections(entry);
             if (entry instanceof GroupEntry) {
                 GroupEntry parent = (GroupEntry) entry;
                 for (NotificationEntry child : parent.getChildren()) {
-                    child.getAttachState().setSection(section);
+                    setEntrySection(child, section);
                 }
                 parent.sortChildren(sChildComparator);
             }
@@ -728,6 +747,9 @@
 
         // Finally, sort all top-level elements
         mNotifList.sort(mTopLevelComparator);
+
+        // notify sections since the list is sorted now
+        notifySectionEntriesUpdated();
     }
 
     private void freeEmptyGroups() {
@@ -936,11 +958,18 @@
             }
         }
 
-        entry.getAttachState().setSection(finalSection);
-
+        setEntrySection(entry, finalSection);
         return finalSection;
     }
 
+    private void setEntrySection(ListEntry entry, NotifSection finalSection) {
+        entry.getAttachState().setSection(finalSection);
+        NotificationEntry representativeEntry = entry.getRepresentativeEntry();
+        if (representativeEntry != null && finalSection != null) {
+            representativeEntry.setBucket(finalSection.getBucket());
+        }
+    }
+
     @NonNull
     private NotifSection findSection(ListEntry entry) {
         for (int i = 0; i < mNotifSections.size(); i++) {
@@ -1019,13 +1048,13 @@
         void onRenderList(@NonNull List<ListEntry> entries);
     }
 
-    private static final NotifSectioner DEFAULT_SECTIONER =
-            new NotifSectioner("UnknownSection") {
-                @Override
-                public boolean isInSection(ListEntry entry) {
-                    return true;
-                }
-            };
+    private static final NotifSectioner DEFAULT_SECTIONER = new NotifSectioner("UnknownSection",
+            NotificationPriorityBucketKt.BUCKET_UNKNOWN) {
+        @Override
+        public boolean isInSection(ListEntry entry) {
+            return true;
+        }
+    };
 
     private static final int MIN_CHILDREN_FOR_GROUP = 2;
 
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/coordinator/AppOpsCoordinator.java b/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/coordinator/AppOpsCoordinator.java
index 3a87f68..301b185 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/coordinator/AppOpsCoordinator.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/coordinator/AppOpsCoordinator.java
@@ -30,6 +30,7 @@
 import com.android.systemui.statusbar.notification.collection.NotificationEntry;
 import com.android.systemui.statusbar.notification.collection.listbuilder.pluggable.NotifFilter;
 import com.android.systemui.statusbar.notification.collection.listbuilder.pluggable.NotifSectioner;
+import com.android.systemui.statusbar.notification.stack.NotificationPriorityBucketKt;
 import com.android.systemui.util.concurrency.DelayableExecutor;
 
 import javax.inject.Inject;
@@ -102,7 +103,8 @@
     /**
      * Puts foreground service notifications into its own section.
      */
-    private final NotifSectioner mNotifSectioner = new NotifSectioner("ForegroundService") {
+    private final NotifSectioner mNotifSectioner = new NotifSectioner("ForegroundService",
+            NotificationPriorityBucketKt.BUCKET_FOREGROUND_SERVICE) {
         @Override
         public boolean isInSection(ListEntry entry) {
             NotificationEntry notificationEntry = entry.getRepresentativeEntry();
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/coordinator/ConversationCoordinator.kt b/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/coordinator/ConversationCoordinator.kt
index f0eb084..c385836 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/coordinator/ConversationCoordinator.kt
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/coordinator/ConversationCoordinator.kt
@@ -26,6 +26,7 @@
 import com.android.systemui.statusbar.notification.dagger.PeopleHeader
 import com.android.systemui.statusbar.notification.people.PeopleNotificationIdentifier
 import com.android.systemui.statusbar.notification.people.PeopleNotificationIdentifier.Companion.TYPE_NON_PERSON
+import com.android.systemui.statusbar.notification.stack.BUCKET_PEOPLE
 import javax.inject.Inject
 
 /**
@@ -45,10 +46,12 @@
         }
     }
 
-    val sectioner = object : NotifSectioner("People") {
+    val sectioner = object : NotifSectioner("People", BUCKET_PEOPLE) {
         override fun isInSection(entry: ListEntry): Boolean =
                 isConversation(entry.representativeEntry!!)
-        override fun getHeaderNodeController() = peopleHeaderController
+        override fun getHeaderNodeController() =
+                // TODO: remove SHOW_ALL_SECTIONS, this redundant method, and peopleHeaderController
+                if (RankingCoordinator.SHOW_ALL_SECTIONS) peopleHeaderController else null
     }
 
     override fun attach(pipeline: NotifPipeline) {
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/coordinator/GutsCoordinator.kt b/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/coordinator/GutsCoordinator.kt
new file mode 100644
index 0000000..8948969
--- /dev/null
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/coordinator/GutsCoordinator.kt
@@ -0,0 +1,126 @@
+/*
+ * Copyright (C) 2021 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package com.android.systemui.statusbar.notification.collection.coordinator
+
+import android.util.ArraySet
+import com.android.systemui.Dumpable
+import com.android.systemui.dagger.SysUISingleton
+import com.android.systemui.dump.DumpManager
+import com.android.systemui.statusbar.notification.collection.ListEntry
+import com.android.systemui.statusbar.notification.collection.NotifPipeline
+import com.android.systemui.statusbar.notification.collection.NotificationEntry
+import com.android.systemui.statusbar.notification.collection.notifcollection.NotifLifetimeExtender
+import com.android.systemui.statusbar.notification.collection.notifcollection.NotifLifetimeExtender.OnEndLifetimeExtensionCallback
+import com.android.systemui.statusbar.notification.row.NotificationGuts
+import com.android.systemui.statusbar.notification.row.NotificationGutsManager
+import com.android.systemui.statusbar.notification.collection.render.NotifGutsViewListener
+import com.android.systemui.statusbar.notification.collection.render.NotifGutsViewManager
+import java.io.FileDescriptor
+import java.io.PrintWriter
+import javax.inject.Inject
+
+private const val TAG = "GutsCoordinator"
+
+/**
+ * Coordinates the guts displayed by the [NotificationGutsManager] with the pipeline.
+ * Specifically, this just adds the lifetime extension necessary to keep guts from disappearing.
+ */
+@SysUISingleton
+class GutsCoordinator @Inject constructor(
+    private val notifGutsViewManager: NotifGutsViewManager,
+    private val logger: GutsCoordinatorLogger,
+    dumpManager: DumpManager
+) : Coordinator, Dumpable {
+
+    /** Keys of any Notifications for which we've been told the guts are open  */
+    private val notifsWithOpenGuts = ArraySet<String>()
+
+    /** Keys of any Notifications we've extended the lifetime for, based on guts  */
+    private val notifsExtendingLifetime = ArraySet<String>()
+
+    /** Callback for ending lifetime extension  */
+    private var onEndLifetimeExtensionCallback: OnEndLifetimeExtensionCallback? = null
+
+    init {
+        dumpManager.registerDumpable(TAG, this)
+    }
+
+    override fun attach(pipeline: NotifPipeline) {
+        notifGutsViewManager.setGutsListener(mGutsListener)
+        pipeline.addNotificationLifetimeExtender(mLifetimeExtender)
+    }
+
+    override fun dump(fd: FileDescriptor, pw: PrintWriter, args: Array<String>) {
+        pw.println("  notifsWithOpenGuts: ${notifsWithOpenGuts.size}")
+        for (key in notifsWithOpenGuts) {
+            pw.println("   * $key")
+        }
+        pw.println("  notifsExtendingLifetime: ${notifsExtendingLifetime.size}")
+        for (key in notifsExtendingLifetime) {
+            pw.println("   * $key")
+        }
+        pw.println("  onEndLifetimeExtensionCallback: $onEndLifetimeExtensionCallback")
+    }
+
+    private val mLifetimeExtender: NotifLifetimeExtender = object : NotifLifetimeExtender {
+        override fun getName(): String {
+            return TAG
+        }
+
+        override fun setCallback(callback: OnEndLifetimeExtensionCallback) {
+            onEndLifetimeExtensionCallback = callback
+        }
+
+        override fun shouldExtendLifetime(entry: NotificationEntry, reason: Int): Boolean {
+            val isShowingGuts = isCurrentlyShowingGuts(entry)
+            if (isShowingGuts) {
+                notifsExtendingLifetime.add(entry.key)
+            }
+            return isShowingGuts
+        }
+
+        override fun cancelLifetimeExtension(entry: NotificationEntry) {
+            notifsExtendingLifetime.remove(entry.key)
+        }
+    }
+
+    private val mGutsListener: NotifGutsViewListener = object : NotifGutsViewListener {
+        override fun onGutsOpen(entry: NotificationEntry, guts: NotificationGuts) {
+            logger.logGutsOpened(entry.key, guts)
+            if (guts.isLeavebehind) {
+                // leave-behind guts should not extend the lifetime of the notification
+                closeGutsAndEndLifetimeExtension(entry)
+            } else {
+                notifsWithOpenGuts.add(entry.key)
+            }
+        }
+
+        override fun onGutsClose(entry: NotificationEntry) {
+            logger.logGutsClosed(entry.key)
+            closeGutsAndEndLifetimeExtension(entry)
+        }
+    }
+
+    private fun isCurrentlyShowingGuts(entry: ListEntry) =
+            notifsWithOpenGuts.contains(entry.key)
+
+    private fun closeGutsAndEndLifetimeExtension(entry: NotificationEntry) {
+        notifsWithOpenGuts.remove(entry.key)
+        if (notifsExtendingLifetime.remove(entry.key)) {
+            onEndLifetimeExtensionCallback?.onEndLifetimeExtension(mLifetimeExtender, entry)
+        }
+    }
+}
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/coordinator/GutsCoordinatorLogger.kt b/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/coordinator/GutsCoordinatorLogger.kt
new file mode 100644
index 0000000..e8f352f
--- /dev/null
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/coordinator/GutsCoordinatorLogger.kt
@@ -0,0 +1,32 @@
+package com.android.systemui.statusbar.notification.collection.coordinator
+
+import com.android.systemui.log.LogBuffer
+import com.android.systemui.log.LogLevel
+import com.android.systemui.log.dagger.NotificationLog
+import com.android.systemui.statusbar.notification.row.NotificationGuts
+import javax.inject.Inject
+
+private const val TAG = "GutsCoordinator"
+
+class GutsCoordinatorLogger @Inject constructor(
+    @NotificationLog private val buffer: LogBuffer
+) {
+
+    fun logGutsOpened(key: String, guts: NotificationGuts) {
+        buffer.log(TAG, LogLevel.DEBUG, {
+            str1 = key
+            str2 = guts.gutsContent::class.simpleName
+            bool1 = guts.isLeavebehind
+        }, {
+            "Guts of type $str2 (leave behind: $bool1) opened for class $str1"
+        })
+    }
+
+    fun logGutsClosed(key: String) {
+        buffer.log(TAG, LogLevel.DEBUG, {
+            str1 = key
+        }, {
+            "Guts closed for class $str1"
+        })
+    }
+}
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/coordinator/HeadsUpCoordinator.java b/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/coordinator/HeadsUpCoordinator.java
index 6e98c27..8c8a8a9 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/coordinator/HeadsUpCoordinator.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/coordinator/HeadsUpCoordinator.java
@@ -19,7 +19,8 @@
 import static com.android.systemui.statusbar.NotificationRemoteInputManager.FORCE_REMOTE_INPUT_HISTORY;
 import static com.android.systemui.statusbar.notification.interruption.HeadsUpController.alertAgain;
 
-import android.annotation.Nullable;
+import androidx.annotation.NonNull;
+import androidx.annotation.Nullable;
 
 import com.android.systemui.dagger.SysUISingleton;
 import com.android.systemui.statusbar.NotificationRemoteInputManager;
@@ -34,6 +35,7 @@
 import com.android.systemui.statusbar.notification.dagger.IncomingHeader;
 import com.android.systemui.statusbar.notification.interruption.HeadsUpViewBinder;
 import com.android.systemui.statusbar.notification.interruption.NotificationInterruptStateProvider;
+import com.android.systemui.statusbar.notification.stack.NotificationPriorityBucketKt;
 import com.android.systemui.statusbar.policy.HeadsUpManager;
 import com.android.systemui.statusbar.policy.OnHeadsUpChangedListener;
 
@@ -163,17 +165,17 @@
 
     private final NotifLifetimeExtender mLifetimeExtender = new NotifLifetimeExtender() {
         @Override
-        public String getName() {
+        public @NonNull String getName() {
             return TAG;
         }
 
         @Override
-        public void setCallback(OnEndLifetimeExtensionCallback callback) {
+        public void setCallback(@NonNull OnEndLifetimeExtensionCallback callback) {
             mEndLifetimeExtension = callback;
         }
 
         @Override
-        public boolean shouldExtendLifetime(NotificationEntry entry, int reason) {
+        public boolean shouldExtendLifetime(@NonNull NotificationEntry entry, int reason) {
             boolean isShowingHun = isCurrentlyShowingHun(entry);
             if (isShowingHun) {
                 mNotifExtendingLifetime = entry;
@@ -182,7 +184,7 @@
         }
 
         @Override
-        public void cancelLifetimeExtension(NotificationEntry entry) {
+        public void cancelLifetimeExtension(@NonNull NotificationEntry entry) {
             if (Objects.equals(mNotifExtendingLifetime, entry)) {
                 mNotifExtendingLifetime = null;
             }
@@ -196,7 +198,8 @@
         }
     };
 
-    private final NotifSectioner mNotifSectioner = new NotifSectioner("HeadsUp") {
+    private final NotifSectioner mNotifSectioner = new NotifSectioner("HeadsUp",
+            NotificationPriorityBucketKt.BUCKET_HEADS_UP) {
         @Override
         public boolean isInSection(ListEntry entry) {
             return isCurrentlyShowingHun(entry);
@@ -205,7 +208,11 @@
         @Nullable
         @Override
         public NodeController getHeaderNodeController() {
-            return mIncomingHeaderController;
+            // TODO: remove SHOW_ALL_SECTIONS, this redundant method, and mIncomingHeaderController
+            if (RankingCoordinator.SHOW_ALL_SECTIONS) {
+                return mIncomingHeaderController;
+            }
+            return null;
         }
     };
 
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/coordinator/NotifCoordinators.java b/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/coordinator/NotifCoordinators.java
index 25b2019..9305900 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/coordinator/NotifCoordinators.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/coordinator/NotifCoordinators.java
@@ -57,10 +57,13 @@
             DeviceProvisionedCoordinator deviceProvisionedCoordinator,
             BubbleCoordinator bubbleCoordinator,
             HeadsUpCoordinator headsUpCoordinator,
+            GutsCoordinator gutsCoordinator,
             ConversationCoordinator conversationCoordinator,
             PreparationCoordinator preparationCoordinator,
             MediaCoordinator mediaCoordinator,
+            ShadeEventCoordinator shadeEventCoordinator,
             SmartspaceDedupingCoordinator smartspaceDedupingCoordinator,
+            ViewConfigCoordinator viewConfigCoordinator,
             VisualStabilityCoordinator visualStabilityCoordinator) {
         dumpManager.registerDumpable(TAG, this);
 
@@ -73,6 +76,8 @@
         mCoordinators.add(bubbleCoordinator);
         mCoordinators.add(conversationCoordinator);
         mCoordinators.add(mediaCoordinator);
+        mCoordinators.add(shadeEventCoordinator);
+        mCoordinators.add(viewConfigCoordinator);
         mCoordinators.add(visualStabilityCoordinator);
 
         if (featureFlags.isSmartspaceDedupingEnabled()) {
@@ -81,6 +86,7 @@
 
         if (featureFlags.isNewNotifPipelineRenderingEnabled()) {
             mCoordinators.add(headsUpCoordinator);
+            mCoordinators.add(gutsCoordinator);
             mCoordinators.add(preparationCoordinator);
         }
 
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/coordinator/RankingCoordinator.java b/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/coordinator/RankingCoordinator.java
index 6da4d8b..1a6a63a 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/coordinator/RankingCoordinator.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/coordinator/RankingCoordinator.java
@@ -16,6 +16,7 @@
 
 package com.android.systemui.statusbar.notification.collection.coordinator;
 
+import android.annotation.NonNull;
 import android.annotation.Nullable;
 
 import com.android.systemui.dagger.SysUISingleton;
@@ -27,8 +28,12 @@
 import com.android.systemui.statusbar.notification.collection.listbuilder.pluggable.NotifSectioner;
 import com.android.systemui.statusbar.notification.collection.provider.HighPriorityProvider;
 import com.android.systemui.statusbar.notification.collection.render.NodeController;
+import com.android.systemui.statusbar.notification.collection.render.SectionHeaderController;
 import com.android.systemui.statusbar.notification.dagger.AlertingHeader;
 import com.android.systemui.statusbar.notification.dagger.SilentHeader;
+import com.android.systemui.statusbar.notification.stack.NotificationPriorityBucketKt;
+
+import java.util.List;
 
 import javax.inject.Inject;
 
@@ -41,9 +46,11 @@
  */
 @SysUISingleton
 public class RankingCoordinator implements Coordinator {
+    public static final boolean SHOW_ALL_SECTIONS = false;
     private final StatusBarStateController mStatusBarStateController;
     private final HighPriorityProvider mHighPriorityProvider;
-    private final NodeController mSilentHeaderController;
+    private final NodeController mSilentNodeController;
+    private final SectionHeaderController mSilentHeaderController;
     private final NodeController mAlertingHeaderController;
 
     @Inject
@@ -51,10 +58,12 @@
             StatusBarStateController statusBarStateController,
             HighPriorityProvider highPriorityProvider,
             @AlertingHeader NodeController alertingHeaderController,
-            @SilentHeader NodeController silentHeaderController) {
+            @SilentHeader SectionHeaderController silentHeaderController,
+            @SilentHeader NodeController silentNodeController) {
         mStatusBarStateController = statusBarStateController;
         mHighPriorityProvider = highPriorityProvider;
         mAlertingHeaderController = alertingHeaderController;
+        mSilentNodeController = silentNodeController;
         mSilentHeaderController = silentHeaderController;
     }
 
@@ -74,7 +83,8 @@
         return mSilentNotifSectioner;
     }
 
-    private final NotifSectioner mAlertingNotifSectioner = new NotifSectioner("Alerting") {
+    private final NotifSectioner mAlertingNotifSectioner = new NotifSectioner("Alerting",
+            NotificationPriorityBucketKt.BUCKET_ALERTING) {
         @Override
         public boolean isInSection(ListEntry entry) {
             return mHighPriorityProvider.isHighPriority(entry);
@@ -83,11 +93,16 @@
         @Nullable
         @Override
         public NodeController getHeaderNodeController() {
-            return mAlertingHeaderController;
+            // TODO: remove SHOW_ALL_SECTIONS, this redundant method, and mAlertingHeaderController
+            if (SHOW_ALL_SECTIONS) {
+                return mAlertingHeaderController;
+            }
+            return null;
         }
     };
 
-    private final NotifSectioner mSilentNotifSectioner = new NotifSectioner("Silent") {
+    private final NotifSectioner mSilentNotifSectioner = new NotifSectioner("Silent",
+            NotificationPriorityBucketKt.BUCKET_SILENT) {
         @Override
         public boolean isInSection(ListEntry entry) {
             return !mHighPriorityProvider.isHighPriority(entry);
@@ -96,7 +111,19 @@
         @Nullable
         @Override
         public NodeController getHeaderNodeController() {
-            return mSilentHeaderController;
+            return mSilentNodeController;
+        }
+
+        @Nullable
+        @Override
+        public void onEntriesUpdated(@NonNull List<ListEntry> entries) {
+            for (int i = 0; i < entries.size(); i++) {
+                if (entries.get(i).getRepresentativeEntry().getSbn().isClearable()) {
+                    mSilentHeaderController.setClearSectionEnabled(true);
+                    return;
+                }
+            }
+            mSilentHeaderController.setClearSectionEnabled(false);
         }
     };
 
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/coordinator/ShadeEventCoordinator.kt b/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/coordinator/ShadeEventCoordinator.kt
new file mode 100644
index 0000000..f9648a0
--- /dev/null
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/coordinator/ShadeEventCoordinator.kt
@@ -0,0 +1,78 @@
+/*
+ * Copyright (C) 2021 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.systemui.statusbar.notification.collection.coordinator
+
+import android.service.notification.NotificationListenerService
+import com.android.systemui.dagger.SysUISingleton
+import com.android.systemui.statusbar.notification.collection.ListEntry
+import com.android.systemui.statusbar.notification.collection.NotifPipeline
+import com.android.systemui.statusbar.notification.collection.NotificationEntry
+import com.android.systemui.statusbar.notification.collection.notifcollection.NotifCollectionListener
+import com.android.systemui.statusbar.notification.collection.render.NotifShadeEventSource
+import javax.inject.Inject
+
+/**
+ * A coordinator which provides callbacks to a view surfaces for various events relevant to the
+ * shade, such as when the user removes a notification, or when the shade is emptied.
+ */
+@SysUISingleton
+class ShadeEventCoordinator @Inject internal constructor(
+    private val mLogger: ShadeEventCoordinatorLogger
+) : Coordinator, NotifShadeEventSource {
+    private var mNotifRemovedByUserCallback: Runnable? = null
+    private var mShadeEmptiedCallback: Runnable? = null
+    private var mEntryRemoved = false
+    private var mEntryRemovedByUser = false
+
+    override fun attach(pipeline: NotifPipeline) {
+        pipeline.addCollectionListener(mNotifCollectionListener)
+        pipeline.addOnBeforeRenderListListener(this::onBeforeRenderList)
+    }
+
+    private val mNotifCollectionListener = object : NotifCollectionListener {
+        override fun onEntryRemoved(entry: NotificationEntry, reason: Int) {
+            mEntryRemoved = true
+            mEntryRemovedByUser =
+                    reason == NotificationListenerService.REASON_CLICK ||
+                    reason == NotificationListenerService.REASON_CANCEL_ALL ||
+                    reason == NotificationListenerService.REASON_CANCEL
+        }
+    }
+
+    override fun setNotifRemovedByUserCallback(callback: Runnable) {
+        check(mNotifRemovedByUserCallback == null) { "mNotifRemovedByUserCallback already set" }
+        mNotifRemovedByUserCallback = callback
+    }
+
+    override fun setShadeEmptiedCallback(callback: Runnable) {
+        check(mShadeEmptiedCallback == null) { "mShadeEmptiedCallback already set" }
+        mShadeEmptiedCallback = callback
+    }
+
+    private fun onBeforeRenderList(entries: List<ListEntry>) {
+        if (mEntryRemoved && entries.isEmpty()) {
+            mLogger.logShadeEmptied()
+            mShadeEmptiedCallback?.run()
+        }
+        if (mEntryRemoved && mEntryRemovedByUser) {
+            mLogger.logNotifRemovedByUser()
+            mNotifRemovedByUserCallback?.run()
+        }
+        mEntryRemoved = false
+        mEntryRemovedByUser = false
+    }
+}
\ No newline at end of file
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/coordinator/ShadeEventCoordinatorLogger.kt b/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/coordinator/ShadeEventCoordinatorLogger.kt
new file mode 100644
index 0000000..c687e1b
--- /dev/null
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/coordinator/ShadeEventCoordinatorLogger.kt
@@ -0,0 +1,38 @@
+/*
+ * Copyright (C) 2021 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.systemui.statusbar.notification.collection.coordinator
+
+import com.android.systemui.log.LogBuffer
+import com.android.systemui.log.LogLevel
+import com.android.systemui.log.dagger.NotificationLog
+import javax.inject.Inject
+
+private const val TAG = "ShadeEventCoordinator"
+
+/** Logger for the [ShadeEventCoordinator] */
+class ShadeEventCoordinatorLogger @Inject constructor(
+    @NotificationLog private val buffer: LogBuffer
+) {
+
+    fun logShadeEmptied() {
+        buffer.log(TAG, LogLevel.DEBUG, { }, { "Shade emptied" })
+    }
+
+    fun logNotifRemovedByUser() {
+        buffer.log(TAG, LogLevel.DEBUG, { }, { "Notification removed by user" })
+    }
+}
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/coordinator/ViewConfigCoordinator.kt b/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/coordinator/ViewConfigCoordinator.kt
new file mode 100644
index 0000000..df1132b
--- /dev/null
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/coordinator/ViewConfigCoordinator.kt
@@ -0,0 +1,109 @@
+/*
+ * Copyright (C) 2021 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.systemui.statusbar.notification.collection.coordinator
+
+import com.android.internal.widget.MessagingGroup
+import com.android.internal.widget.MessagingMessage
+import com.android.keyguard.KeyguardUpdateMonitor
+import com.android.systemui.dagger.SysUISingleton
+import com.android.systemui.flags.FeatureFlags
+import com.android.systemui.statusbar.NotificationLockscreenUserManager.UserChangedListener
+import com.android.systemui.statusbar.NotificationLockscreenUserManagerImpl
+import com.android.systemui.statusbar.notification.collection.NotifPipeline
+import com.android.systemui.statusbar.notification.row.NotificationGutsManager
+import com.android.systemui.statusbar.policy.ConfigurationController
+import javax.inject.Inject
+
+/**
+ * A coordinator which ensures that notifications within the new pipeline are correctly inflated
+ * for the current uiMode and screen properties; additionally deferring those changes when a user
+ * change is in progress until that process has completed.
+ */
+@SysUISingleton
+class ViewConfigCoordinator @Inject internal constructor(
+    configurationController: ConfigurationController,
+    lockscreenUserManager: NotificationLockscreenUserManagerImpl,
+    featureFlags: FeatureFlags,
+    private val mGutsManager: NotificationGutsManager,
+    private val mKeyguardUpdateMonitor: KeyguardUpdateMonitor
+) : Coordinator, UserChangedListener, ConfigurationController.ConfigurationListener {
+
+    private var mReinflateNotificationsOnUserSwitched = false
+    private var mDispatchUiModeChangeOnUserSwitched = false
+    private var mPipeline: NotifPipeline? = null
+
+    init {
+        if (featureFlags.isNewNotifPipelineRenderingEnabled) {
+            lockscreenUserManager.addUserChangedListener(this)
+            configurationController.addCallback(this)
+        }
+    }
+
+    override fun attach(pipeline: NotifPipeline) {
+        mPipeline = pipeline
+    }
+
+    override fun onDensityOrFontScaleChanged() {
+        MessagingMessage.dropCache()
+        MessagingGroup.dropCache()
+        if (!mKeyguardUpdateMonitor.isSwitchingUser) {
+            updateNotificationsOnDensityOrFontScaleChanged()
+        } else {
+            mReinflateNotificationsOnUserSwitched = true
+        }
+    }
+
+    override fun onUiModeChanged() {
+        if (!mKeyguardUpdateMonitor.isSwitchingUser) {
+            updateNotificationsOnUiModeChanged()
+        } else {
+            mDispatchUiModeChangeOnUserSwitched = true
+        }
+    }
+
+    override fun onThemeChanged() {
+        onDensityOrFontScaleChanged()
+    }
+
+    override fun onUserChanged(userId: Int) {
+        if (mReinflateNotificationsOnUserSwitched) {
+            updateNotificationsOnDensityOrFontScaleChanged()
+            mReinflateNotificationsOnUserSwitched = false
+        }
+        if (mDispatchUiModeChangeOnUserSwitched) {
+            updateNotificationsOnUiModeChanged()
+            mDispatchUiModeChangeOnUserSwitched = false
+        }
+    }
+
+    private fun updateNotificationsOnUiModeChanged() {
+        mPipeline?.allNotifs?.forEach { entry ->
+            val row = entry.row
+            row?.onUiModeChanged()
+        }
+    }
+
+    private fun updateNotificationsOnDensityOrFontScaleChanged() {
+        mPipeline?.allNotifs?.forEach { entry ->
+            entry.onDensityOrFontScaleChanged()
+            val exposedGuts = entry.areGutsExposed()
+            if (exposedGuts) {
+                mGutsManager.onDensityOrFontScaleChanged(entry)
+            }
+        }
+    }
+}
\ No newline at end of file
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/legacy/LegacyNotificationPresenterExtensions.java b/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/legacy/LegacyNotificationPresenterExtensions.java
new file mode 100644
index 0000000..4ee08ed
--- /dev/null
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/legacy/LegacyNotificationPresenterExtensions.java
@@ -0,0 +1,100 @@
+/*
+ * Copyright (C) 2010 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.systemui.statusbar.notification.collection.legacy;
+
+import static com.android.systemui.statusbar.phone.StatusBar.SPEW;
+
+import android.service.notification.StatusBarNotification;
+import android.util.Log;
+
+import androidx.annotation.NonNull;
+
+import com.android.internal.statusbar.NotificationVisibility;
+import com.android.systemui.statusbar.notification.NotificationEntryListener;
+import com.android.systemui.statusbar.notification.NotificationEntryManager;
+import com.android.systemui.statusbar.notification.collection.NotificationEntry;
+import com.android.systemui.statusbar.notification.collection.render.NotifShadeEventSource;
+
+import org.jetbrains.annotations.NotNull;
+
+import javax.inject.Inject;
+
+/**
+ * This is some logic extracted from the
+ * {@link com.android.systemui.statusbar.phone.StatusBarNotificationPresenter}
+ * into a class that implements a new-pipeline interface so that the new pipeline can implement it
+ * correctly.
+ *
+ * Specifically, this is the logic which updates notifications when uiMode and screen properties
+ * change, and which closes the shade when the last notification disappears.
+ */
+public class LegacyNotificationPresenterExtensions implements NotifShadeEventSource {
+    private static final String TAG = "LegacyNotifPresenter";
+    private final NotificationEntryManager mEntryManager;
+    private boolean mEntryListenerAdded;
+    private Runnable mShadeEmptiedCallback;
+    private Runnable mNotifRemovedByUserCallback;
+
+    @Inject
+    public LegacyNotificationPresenterExtensions(NotificationEntryManager entryManager) {
+        mEntryManager = entryManager;
+    }
+
+    private void ensureEntryListenerAdded() {
+        if (mEntryListenerAdded) return;
+        mEntryListenerAdded = true;
+        mEntryManager.addNotificationEntryListener(new NotificationEntryListener() {
+            @Override
+            public void onEntryRemoved(
+                    @NotNull NotificationEntry entry,
+                    NotificationVisibility visibility,
+                    boolean removedByUser,
+                    int reason) {
+                StatusBarNotification old = entry.getSbn();
+                if (SPEW) {
+                    Log.d(TAG, "removeNotification key=" + entry.getKey()
+                            + " old=" + old + " reason=" + reason);
+                }
+
+                if (old != null && !mEntryManager.hasActiveNotifications()) {
+                    if (mShadeEmptiedCallback != null) mShadeEmptiedCallback.run();
+                }
+                if (removedByUser) {
+                    if (mNotifRemovedByUserCallback != null) mNotifRemovedByUserCallback.run();
+                }
+            }
+        });
+    }
+
+    @Override
+    public void setNotifRemovedByUserCallback(@NonNull Runnable callback) {
+        if (mNotifRemovedByUserCallback != null) {
+            throw new IllegalStateException("mNotifRemovedByUserCallback already set");
+        }
+        mNotifRemovedByUserCallback = callback;
+        ensureEntryListenerAdded();
+    }
+
+    @Override
+    public void setShadeEmptiedCallback(@NonNull Runnable callback) {
+        if (mShadeEmptiedCallback != null) {
+            throw new IllegalStateException("mShadeEmptiedCallback already set");
+        }
+        mShadeEmptiedCallback = callback;
+        ensureEntryListenerAdded();
+    }
+}
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/listbuilder/NotifSection.kt b/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/listbuilder/NotifSection.kt
index c9fc992..6424e37 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/listbuilder/NotifSection.kt
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/listbuilder/NotifSection.kt
@@ -18,14 +18,17 @@
 
 import com.android.systemui.statusbar.notification.collection.listbuilder.pluggable.NotifSectioner
 import com.android.systemui.statusbar.notification.collection.render.NodeController
+import com.android.systemui.statusbar.notification.stack.PriorityBucket
 
 data class NotifSection(
     val sectioner: NotifSectioner,
     val index: Int
 ) {
     val label: String
-        get() = "Section($index, \"${sectioner.name}\")"
+        get() = "Section($index, $bucket, \"${sectioner.name}\")"
 
     val headerController: NodeController?
         get() = sectioner.headerNodeController
+
+    @PriorityBucket val bucket: Int = sectioner.bucket
 }
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/listbuilder/pluggable/NotifSectioner.java b/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/listbuilder/pluggable/NotifSectioner.java
index c8982d3..ef9ee11 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/listbuilder/pluggable/NotifSectioner.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/listbuilder/pluggable/NotifSectioner.java
@@ -22,13 +22,28 @@
 import com.android.systemui.statusbar.notification.collection.ShadeListBuilder;
 import com.android.systemui.statusbar.notification.collection.render.NodeController;
 import com.android.systemui.statusbar.notification.collection.render.NodeSpec;
+import com.android.systemui.statusbar.notification.stack.PriorityBucket;
+
+import java.util.List;
 
 /**
- * Pluggable for participating in notif sectioning. See {@link ShadeListBuilder#setSections}.
+ * Pluggable for participating in notif sectioning. See {@link ShadeListBuilder#setSectioners}.
  */
 public abstract class NotifSectioner extends Pluggable<NotifSectioner> {
-    protected NotifSectioner(String name) {
+    @PriorityBucket
+    private final int mBucket;
+
+    protected NotifSectioner(String name, @PriorityBucket int bucket) {
         super(name);
+        mBucket = bucket;
+    }
+
+    /**
+     * @return the "bucket" value to apply to entries in this section
+     */
+    @PriorityBucket
+    public final int getBucket() {
+        return mBucket;
     }
 
     /**
@@ -46,4 +61,10 @@
     public @Nullable NodeController getHeaderNodeController() {
         return null;
     }
+
+    /**
+     * Notify of children of this section being updated
+     * @param entries of this section that are borrowed (must clone to store)
+     */
+    public void onEntriesUpdated(List<ListEntry> entries) {}
 }
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/notifcollection/NotifLifetimeExtender.java b/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/notifcollection/NotifLifetimeExtender.java
index f8fe067..2fe3bd6 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/notifcollection/NotifLifetimeExtender.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/notifcollection/NotifLifetimeExtender.java
@@ -16,6 +16,8 @@
 
 package com.android.systemui.statusbar.notification.collection.notifcollection;
 
+import androidx.annotation.NonNull;
+
 import com.android.systemui.statusbar.notification.collection.NotifCollection;
 import com.android.systemui.statusbar.notification.collection.NotifCollection.CancellationReason;
 import com.android.systemui.statusbar.notification.collection.NotificationEntry;
@@ -26,14 +28,14 @@
  */
 public interface NotifLifetimeExtender {
     /** Name to associate with this extender (for the purposes of debugging) */
-    String getName();
+    @NonNull String getName();
 
     /**
      * Called on the extender immediately after it has been registered. The extender should hang on
      * to this callback and execute it whenever it no longer needs to extend the lifetime of a
      * notification.
      */
-    void setCallback(OnEndLifetimeExtensionCallback callback);
+    void setCallback(@NonNull OnEndLifetimeExtensionCallback callback);
 
     /**
      * Called by the NotifCollection whenever a notification has been retracted (by the app) or
@@ -43,7 +45,7 @@
      * called on all lifetime extenders even if earlier ones return true (in other words, multiple
      * lifetime extenders can be extending a notification at the same time).
      */
-    boolean shouldExtendLifetime(NotificationEntry entry, @CancellationReason int reason);
+    boolean shouldExtendLifetime(@NonNull NotificationEntry entry, @CancellationReason int reason);
 
     /**
      * Called by the NotifCollection to inform a lifetime extender that its extension of a notif
@@ -51,7 +53,7 @@
      * lifetime extension). The extender should clean up any references it has to the notif in
      * question.
      */
-    void cancelLifetimeExtension(NotificationEntry entry);
+    void cancelLifetimeExtension(@NonNull NotificationEntry entry);
 
     /** Callback for notifying the NotifCollection that a lifetime extension has expired.*/
     interface OnEndLifetimeExtensionCallback {
@@ -59,6 +61,8 @@
          * Stop extending the lifetime of `entry` with `extender` and then immediately re-evaluates
          * whether to continue lifetime extending this notification or to remove it.
          */
-        void onEndLifetimeExtension(NotifLifetimeExtender extender, NotificationEntry entry);
+        void onEndLifetimeExtension(
+                @NonNull NotifLifetimeExtender extender,
+                @NonNull NotificationEntry entry);
     }
 }
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/render/NotifGutsViewListener.kt b/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/render/NotifGutsViewListener.kt
new file mode 100644
index 0000000..129f6b1
--- /dev/null
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/render/NotifGutsViewListener.kt
@@ -0,0 +1,30 @@
+/*
+ * Copyright (C) 2021 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package com.android.systemui.statusbar.notification.collection.render
+
+import com.android.systemui.statusbar.notification.collection.NotificationEntry
+import com.android.systemui.statusbar.notification.row.NotificationGuts
+
+/**
+ * Interface for listening to guts open and close events.
+ */
+interface NotifGutsViewListener {
+    /** A notification's guts are being opened */
+    fun onGutsOpen(entry: NotificationEntry, guts: NotificationGuts)
+
+    /** A notification's guts are being closed */
+    fun onGutsClose(entry: NotificationEntry)
+}
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/render/NotifGutsViewManager.kt b/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/render/NotifGutsViewManager.kt
new file mode 100644
index 0000000..0260f89
--- /dev/null
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/render/NotifGutsViewManager.kt
@@ -0,0 +1,24 @@
+/*
+ * Copyright (C) 2021 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package com.android.systemui.statusbar.notification.collection.render
+
+/** A type which provides open and close guts events to a single listener */
+interface NotifGutsViewManager {
+    /**
+     * @param listener the object that will listen to open and close guts events
+     */
+    fun setGutsListener(listener: NotifGutsViewListener?)
+}
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/render/NotifShadeEventSource.kt b/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/render/NotifShadeEventSource.kt
new file mode 100644
index 0000000..e24f6a0
--- /dev/null
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/render/NotifShadeEventSource.kt
@@ -0,0 +1,35 @@
+/*
+ * Copyright (C) 2021 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.systemui.statusbar.notification.collection.render
+
+/**
+ * This is an object which provides callbacks for certain important events related to the
+ * notification shade, such as notifications being removed by the user, or the shade becoming empty.
+ */
+interface NotifShadeEventSource {
+    /**
+     * Registers a callback to be invoked when the last notification has been removed from
+     * the shade for any reason
+     */
+    fun setShadeEmptiedCallback(callback: Runnable)
+
+    /**
+     * Registers a callback to be invoked when a notification has been removed from
+     * the shade by a user action
+     */
+    fun setNotifRemovedByUserCallback(callback: Runnable)
+}
\ No newline at end of file
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/render/SectionHeaderController.kt b/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/render/SectionHeaderController.kt
index 1311e3e..8c15647 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/render/SectionHeaderController.kt
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/render/SectionHeaderController.kt
@@ -33,7 +33,8 @@
 interface SectionHeaderController {
     fun reinflateView(parent: ViewGroup)
     val headerView: SectionHeaderView?
-    fun setOnClearAllClickListener(listener: View.OnClickListener)
+    fun setClearSectionEnabled(enabled: Boolean)
+    fun setOnClearSectionClickListener(listener: View.OnClickListener)
 }
 
 @SectionHeaderScope
@@ -46,6 +47,7 @@
 ) : NodeController, SectionHeaderController {
 
     private var _view: SectionHeaderView? = null
+    private var clearAllButtonEnabled = false
     private var clearAllClickListener: View.OnClickListener? = null
     private val onHeaderClickListener = View.OnClickListener {
         activityStarter.startActivity(
@@ -76,12 +78,18 @@
             parent.addView(inflated, oldPos)
         }
         _view = inflated
+        _view?.setClearSectionButtonEnabled(clearAllButtonEnabled)
     }
 
     override val headerView: SectionHeaderView?
         get() = _view
 
-    override fun setOnClearAllClickListener(listener: View.OnClickListener) {
+    override fun setClearSectionEnabled(enabled: Boolean) {
+        clearAllButtonEnabled = enabled
+        _view?.setClearSectionButtonEnabled(enabled)
+    }
+
+    override fun setOnClearSectionClickListener(listener: View.OnClickListener) {
         clearAllClickListener = listener
         _view?.setOnClearAllClickListener(listener)
     }
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/dagger/NotificationsModule.java b/packages/SystemUI/src/com/android/systemui/statusbar/notification/dagger/NotificationsModule.java
index 94f5c44..540216c 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/dagger/NotificationsModule.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/dagger/NotificationsModule.java
@@ -45,10 +45,12 @@
 import com.android.systemui.statusbar.notification.collection.NotifCollection;
 import com.android.systemui.statusbar.notification.collection.NotifInflaterImpl;
 import com.android.systemui.statusbar.notification.collection.NotifPipeline;
+import com.android.systemui.statusbar.notification.collection.coordinator.ShadeEventCoordinator;
 import com.android.systemui.statusbar.notification.collection.coordinator.VisualStabilityCoordinator;
 import com.android.systemui.statusbar.notification.collection.inflation.NotifInflater;
 import com.android.systemui.statusbar.notification.collection.inflation.NotificationRowBinder;
 import com.android.systemui.statusbar.notification.collection.inflation.OnUserInteractionCallbackImpl;
+import com.android.systemui.statusbar.notification.collection.legacy.LegacyNotificationPresenterExtensions;
 import com.android.systemui.statusbar.notification.collection.legacy.NotificationGroupManagerLegacy;
 import com.android.systemui.statusbar.notification.collection.legacy.OnUserInteractionCallbackImplLegacy;
 import com.android.systemui.statusbar.notification.collection.legacy.VisualStabilityManager;
@@ -58,6 +60,8 @@
 import com.android.systemui.statusbar.notification.collection.render.GroupExpansionManagerImpl;
 import com.android.systemui.statusbar.notification.collection.render.GroupMembershipManager;
 import com.android.systemui.statusbar.notification.collection.render.GroupMembershipManagerImpl;
+import com.android.systemui.statusbar.notification.collection.render.NotifGutsViewManager;
+import com.android.systemui.statusbar.notification.collection.render.NotifShadeEventSource;
 import com.android.systemui.statusbar.notification.init.NotificationsController;
 import com.android.systemui.statusbar.notification.init.NotificationsControllerImpl;
 import com.android.systemui.statusbar.notification.init.NotificationsControllerStub;
@@ -89,7 +93,7 @@
 /**
  * Dagger Module for classes found within the com.android.systemui.statusbar.notification package.
  */
-@Module(includes = { NotificationSectionHeadersModule.class })
+@Module(includes = {NotificationSectionHeadersModule.class})
 public interface NotificationsModule {
     @Binds
     StackScrollAlgorithm.SectionProvider bindSectionProvider(
@@ -169,6 +173,14 @@
                 dumpManager);
     }
 
+    /** Provides an instance of {@link NotifGutsViewManager} */
+    @SysUISingleton
+    @Provides
+    static NotifGutsViewManager provideNotifGutsViewManager(
+            NotificationGutsManager notificationGutsManager) {
+        return notificationGutsManager;
+    }
+
     /** Provides an instance of {@link VisualStabilityManager} */
     @SysUISingleton
     @Provides
@@ -262,6 +274,20 @@
     }
 
     /**
+     * Provide the active implementation for presenting notifications.
+     */
+    @Provides
+    @SysUISingleton
+    static NotifShadeEventSource provideNotifShadeEventSource(
+            FeatureFlags featureFlags,
+            Lazy<ShadeEventCoordinator> shadeEventCoordinatorLazy,
+            Lazy<LegacyNotificationPresenterExtensions> legacyNotificationPresenterExtensionsLazy) {
+        return featureFlags.isNewNotifPipelineRenderingEnabled()
+                ? shadeEventCoordinatorLazy.get()
+                : legacyNotificationPresenterExtensionsLazy.get();
+    }
+
+    /**
      * Provide a dismissal callback that's triggered when a user manually dismissed a notification
      * from the notification shade or it gets auto-cancelled by click.
      */
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/logging/NotificationPanelLogger.java b/packages/SystemUI/src/com/android/systemui/statusbar/notification/logging/NotificationPanelLogger.java
index c147023..9faef1b 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/logging/NotificationPanelLogger.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/logging/NotificationPanelLogger.java
@@ -16,12 +16,12 @@
 
 package com.android.systemui.statusbar.notification.logging;
 
-import static com.android.systemui.statusbar.notification.stack.NotificationSectionsManagerKt.BUCKET_ALERTING;
-import static com.android.systemui.statusbar.notification.stack.NotificationSectionsManagerKt.BUCKET_FOREGROUND_SERVICE;
-import static com.android.systemui.statusbar.notification.stack.NotificationSectionsManagerKt.BUCKET_HEADS_UP;
-import static com.android.systemui.statusbar.notification.stack.NotificationSectionsManagerKt.BUCKET_MEDIA_CONTROLS;
-import static com.android.systemui.statusbar.notification.stack.NotificationSectionsManagerKt.BUCKET_PEOPLE;
-import static com.android.systemui.statusbar.notification.stack.NotificationSectionsManagerKt.BUCKET_SILENT;
+import static com.android.systemui.statusbar.notification.stack.NotificationPriorityBucketKt.BUCKET_ALERTING;
+import static com.android.systemui.statusbar.notification.stack.NotificationPriorityBucketKt.BUCKET_FOREGROUND_SERVICE;
+import static com.android.systemui.statusbar.notification.stack.NotificationPriorityBucketKt.BUCKET_HEADS_UP;
+import static com.android.systemui.statusbar.notification.stack.NotificationPriorityBucketKt.BUCKET_MEDIA_CONTROLS;
+import static com.android.systemui.statusbar.notification.stack.NotificationPriorityBucketKt.BUCKET_PEOPLE;
+import static com.android.systemui.statusbar.notification.stack.NotificationPriorityBucketKt.BUCKET_SILENT;
 
 import android.annotation.Nullable;
 import android.service.notification.StatusBarNotification;
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/ExpandableNotificationRow.java b/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/ExpandableNotificationRow.java
index 0d8e850..02b1210 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/ExpandableNotificationRow.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/ExpandableNotificationRow.java
@@ -1249,6 +1249,7 @@
 
     @Override
     public void onConfigurationChanged(Configuration newConfig) {
+        super.onConfigurationChanged(newConfig);
         if (mMenuRow != null && mMenuRow.getMenuView() != null) {
             mMenuRow.onConfigurationChanged();
         }
@@ -3201,13 +3202,6 @@
         }
     }
 
-    /** Sets whether dismiss gestures are right-to-left (instead of left-to-right). */
-    public void setDismissRtl(boolean dismissRtl) {
-        if (mMenuRow != null) {
-            mMenuRow.setDismissRtl(dismissRtl);
-        }
-    }
-
     private static class NotificationViewState extends ExpandableViewState {
 
         @Override
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/NotificationGutsManager.java b/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/NotificationGutsManager.java
index 7eec95a..8e02d9f 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/NotificationGutsManager.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/NotificationGutsManager.java
@@ -65,6 +65,8 @@
 import com.android.systemui.statusbar.notification.NotificationEntryManager;
 import com.android.systemui.statusbar.notification.collection.NotificationEntry;
 import com.android.systemui.statusbar.notification.collection.provider.HighPriorityProvider;
+import com.android.systemui.statusbar.notification.collection.render.NotifGutsViewListener;
+import com.android.systemui.statusbar.notification.collection.render.NotifGutsViewManager;
 import com.android.systemui.statusbar.notification.dagger.NotificationsModule;
 import com.android.systemui.statusbar.notification.row.NotificationInfo.CheckSaveListener;
 import com.android.systemui.statusbar.notification.stack.NotificationListContainer;
@@ -83,7 +85,8 @@
  * Handles various NotificationGuts related tasks, such as binding guts to a row, opening and
  * closing guts, and keeping track of the currently exposed notification guts.
  */
-public class NotificationGutsManager implements Dumpable, NotificationLifetimeExtender {
+public class NotificationGutsManager implements Dumpable, NotificationLifetimeExtender,
+        NotifGutsViewManager {
     private static final String TAG = "NotificationGutsManager";
 
     // Must match constant in Settings. Used to highlight preferences when linking to Settings.
@@ -123,7 +126,6 @@
     private final Optional<BubblesManager> mBubblesManagerOptional;
     private Runnable mOpenRunnable;
     private final INotificationManager mNotificationManager;
-    private final NotificationEntryManager mNotificationEntryManager;
     private final PeopleSpaceWidgetManager mPeopleSpaceWidgetManager;
     private final LauncherApps mLauncherApps;
     private final ShortcutManager mShortcutManager;
@@ -131,6 +133,7 @@
     private final UiEventLogger mUiEventLogger;
     private final ShadeController mShadeController;
     private final AppWidgetManager mAppWidgetManager;
+    private NotifGutsViewListener mGutsListener;
 
     /**
      * Injected constructor. See {@link NotificationsModule}.
@@ -161,7 +164,6 @@
         mAccessibilityManager = accessibilityManager;
         mHighPriorityProvider = highPriorityProvider;
         mNotificationManager = notificationManager;
-        mNotificationEntryManager = notificationEntryManager;
         mPeopleSpaceWidgetManager = peopleSpaceWidgetManager;
         mLauncherApps = launcherApps;
         mShortcutManager = shortcutManager;
@@ -258,10 +260,10 @@
     @VisibleForTesting
     protected boolean bindGuts(final ExpandableNotificationRow row,
             NotificationMenuRowPlugin.MenuItem item) {
-        StatusBarNotification sbn = row.getEntry().getSbn();
+        NotificationEntry entry = row.getEntry();
 
         row.setGutsView(item);
-        row.setTag(sbn.getPackageName());
+        row.setTag(entry.getSbn().getPackageName());
         row.getGuts().setClosedListener((NotificationGuts g) -> {
             row.onGutsClosed();
             if (!g.willBeRemoved() && !row.isRemoved()) {
@@ -272,7 +274,10 @@
                 mNotificationGutsExposed = null;
                 mGutsMenuItem = null;
             }
-            String key = sbn.getKey();
+            if (mGutsListener != null) {
+                mGutsListener.onGutsClose(entry);
+            }
+            String key = entry.getKey();
             if (key.equals(mKeyToRemoveOnGutsClosed)) {
                 mKeyToRemoveOnGutsClosed = null;
                 if (mNotificationLifetimeFinishedCallback != null) {
@@ -650,6 +655,10 @@
                         needsFalsingProtection,
                         row::onGutsOpened);
 
+                if (mGutsListener != null) {
+                    mGutsListener.onGutsOpen(row.getEntry(), guts);
+                }
+
                 row.closeRemoteInput();
                 mListContainer.onHeightChanged(row, true /* needsAnimation */);
                 mGutsMenuItem = menuItem;
@@ -695,10 +704,17 @@
     @Override
     public void dump(FileDescriptor fd, PrintWriter pw, String[] args) {
         pw.println("NotificationGutsManager state:");
-        pw.print("  mKeyToRemoveOnGutsClosed: ");
+        pw.print("  mKeyToRemoveOnGutsClosed (legacy): ");
         pw.println(mKeyToRemoveOnGutsClosed);
     }
 
+    /**
+     * @param gutsListener the listener for open and close guts events
+     */
+    public void setGutsListener(NotifGutsViewListener gutsListener) {
+        mGutsListener = gutsListener;
+    }
+
     public interface OnSettingsClickListener {
         public void onSettingsClick(String key);
     }
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/NotificationMenuRow.java b/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/NotificationMenuRow.java
index d59318e..3a37fb4 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/NotificationMenuRow.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/NotificationMenuRow.java
@@ -85,7 +85,6 @@
     private ArrayList<MenuItem> mRightMenuItems;
     private final Map<View, MenuItem> mMenuItemsByView = new ArrayMap<>();
     private OnMenuEventListener mMenuListener;
-    private boolean mDismissRtl;
 
     private ValueAnimator mFadeAnimator;
     private boolean mAnimating;
@@ -790,14 +789,6 @@
         return getParent().canViewBeDismissed();
     }
 
-    @Override
-    public void setDismissRtl(boolean dismissRtl) {
-        mDismissRtl = dismissRtl;
-        if (mMenuContainer != null) {
-            createMenuViews(true);
-        }
-    }
-
     public static class NotificationMenuItem implements MenuItem {
         View mMenuView;
         GutsContent mGutsContent;
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/NotificationPriorityBucket.kt b/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/NotificationPriorityBucket.kt
new file mode 100644
index 0000000..31f4857
--- /dev/null
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/NotificationPriorityBucket.kt
@@ -0,0 +1,25 @@
+package com.android.systemui.statusbar.notification.stack
+
+import android.annotation.IntDef
+
+/**
+ * For now, declare the available notification buckets (sections) here so that other
+ * presentation code can decide what to do based on an entry's buckets
+ */
+@Retention(AnnotationRetention.SOURCE)
+@IntDef(
+        prefix = ["BUCKET_"],
+        value = [
+            BUCKET_UNKNOWN, BUCKET_MEDIA_CONTROLS, BUCKET_HEADS_UP, BUCKET_FOREGROUND_SERVICE,
+            BUCKET_PEOPLE, BUCKET_ALERTING, BUCKET_SILENT
+        ]
+)
+annotation class PriorityBucket
+
+const val BUCKET_UNKNOWN = 0
+const val BUCKET_MEDIA_CONTROLS = 1
+const val BUCKET_HEADS_UP = 2
+const val BUCKET_FOREGROUND_SERVICE = 3
+const val BUCKET_PEOPLE = 4
+const val BUCKET_ALERTING = 5
+const val BUCKET_SILENT = 6
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/NotificationSection.java b/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/NotificationSection.java
index ab39de0..bc172ce 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/NotificationSection.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/NotificationSection.java
@@ -16,7 +16,7 @@
 
 package com.android.systemui.statusbar.notification.stack;
 
-import static com.android.systemui.statusbar.notification.stack.NotificationSectionsManagerKt.BUCKET_MEDIA_CONTROLS;
+import static com.android.systemui.statusbar.notification.stack.NotificationPriorityBucketKt.BUCKET_MEDIA_CONTROLS;
 
 import android.animation.Animator;
 import android.animation.AnimatorListenerAdapter;
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/NotificationSectionsManager.kt b/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/NotificationSectionsManager.kt
index 45ce20a..5f157a7 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/NotificationSectionsManager.kt
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/NotificationSectionsManager.kt
@@ -16,7 +16,6 @@
 package com.android.systemui.statusbar.notification.stack
 
 import android.annotation.ColorInt
-import android.annotation.IntDef
 import android.annotation.LayoutRes
 import android.util.Log
 import android.view.LayoutInflater
@@ -350,7 +349,7 @@
         silentHeaderView?.run {
             val hasActiveClearableNotifications = this@NotificationSectionsManager.parent
                     .hasActiveClearableNotifications(NotificationStackScrollLayout.ROWS_GENTLE)
-            setAreThereDismissableGentleNotifs(hasActiveClearableNotifications)
+            setClearSectionButtonEnabled(hasActiveClearableNotifications)
         }
     }
 
@@ -448,25 +447,3 @@
         private const val DEBUG = false
     }
 }
-
-/**
- * For now, declare the available notification buckets (sections) here so that other
- * presentation code can decide what to do based on an entry's buckets
- */
-@Retention(AnnotationRetention.SOURCE)
-@IntDef(
-        prefix = ["BUCKET_"],
-        value = [
-            BUCKET_UNKNOWN, BUCKET_MEDIA_CONTROLS, BUCKET_HEADS_UP, BUCKET_FOREGROUND_SERVICE,
-            BUCKET_PEOPLE, BUCKET_ALERTING, BUCKET_SILENT
-        ]
-)
-annotation class PriorityBucket
-
-const val BUCKET_UNKNOWN = 0
-const val BUCKET_MEDIA_CONTROLS = 1
-const val BUCKET_HEADS_UP = 2
-const val BUCKET_FOREGROUND_SERVICE = 3
-const val BUCKET_PEOPLE = 4
-const val BUCKET_ALERTING = 5
-const val BUCKET_SILENT = 6
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 58e4479..6aa5f07 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
@@ -17,7 +17,7 @@
 package com.android.systemui.statusbar.notification.stack;
 
 import static com.android.internal.jank.InteractionJankMonitor.CUJ_NOTIFICATION_SHADE_SCROLL_FLING;
-import static com.android.systemui.statusbar.notification.stack.NotificationSectionsManagerKt.BUCKET_SILENT;
+import static com.android.systemui.statusbar.notification.stack.NotificationPriorityBucketKt.BUCKET_SILENT;
 import static com.android.systemui.statusbar.notification.stack.StackStateAnimator.ANIMATION_DURATION_SWIPE;
 import static com.android.systemui.util.Utils.shouldUseSplitNotificationShade;
 
@@ -161,7 +161,6 @@
     private final Paint mBackgroundPaint = new Paint();
     private final boolean mShouldDrawNotificationBackground;
     private boolean mHighPriorityBeforeSpeedBump;
-    private boolean mDismissRtl;
 
     private float mExpandedHeight;
     private int mOwnScrollY;
@@ -613,16 +612,6 @@
         addView(mFgsSectionView, -1);
     }
 
-    void updateDismissRtlSetting(boolean dismissRtl) {
-        mDismissRtl = dismissRtl;
-        for (int i = 0; i < getChildCount(); i++) {
-            View child = getChildAt(i);
-            if (child instanceof ExpandableNotificationRow) {
-                ((ExpandableNotificationRow) child).setDismissRtl(dismissRtl);
-            }
-        }
-    }
-
     /**
      * Set the overexpansion of the panel to be applied to the view.
      */
@@ -2915,7 +2904,6 @@
         updateChronometerForChild(child);
         if (child instanceof ExpandableNotificationRow) {
             ExpandableNotificationRow row = (ExpandableNotificationRow) child;
-            row.setDismissRtl(mDismissRtl);
             row.setDismissUsingRowTranslationX(mDismissUsingRowTranslationX);
 
         }
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/NotificationStackScrollLayoutController.java b/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/NotificationStackScrollLayoutController.java
index 229e364..03fc767 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/NotificationStackScrollLayoutController.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/NotificationStackScrollLayoutController.java
@@ -783,9 +783,6 @@
         mTunerService.addTunable(
                 (key, newValue) -> {
                     switch (key) {
-                        case Settings.Secure.NOTIFICATION_DISMISS_RTL:
-                            mView.updateDismissRtlSetting("1".equals(newValue));
-                            break;
                         case Settings.Secure.NOTIFICATION_HISTORY_ENABLED:
                             updateFooter();
                             break;
@@ -795,7 +792,6 @@
                     }
                 },
                 HIGH_PRIORITY,
-                Settings.Secure.NOTIFICATION_DISMISS_RTL,
                 Settings.Secure.NOTIFICATION_HISTORY_ENABLED);
 
         mKeyguardMediaController.setVisibilityChangedListener(visible -> {
@@ -818,7 +814,7 @@
             mOnAttachStateChangeListener.onViewAttachedToWindow(mView);
         }
         mView.addOnAttachStateChangeListener(mOnAttachStateChangeListener);
-        mSilentHeaderController.setOnClearAllClickListener(v -> clearSilentNotifications());
+        mSilentHeaderController.setOnClearSectionClickListener(v -> clearSilentNotifications());
     }
 
     private boolean isInVisibleLocation(NotificationEntry entry) {
@@ -1305,6 +1301,9 @@
     }
 
     public void updateSectionBoundaries(String reason) {
+        if (mFeatureFlags.isNewNotifPipelineRenderingEnabled()) {
+            return;
+        }
         mView.updateSectionBoundaries(reason);
     }
 
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/SectionHeaderView.java b/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/SectionHeaderView.java
index 99ec7548..baf09c7 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/SectionHeaderView.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/SectionHeaderView.java
@@ -85,8 +85,12 @@
         return true;
     }
 
-    void setAreThereDismissableGentleNotifs(boolean areThereDismissableGentleNotifs) {
-        mClearAllButton.setVisibility(areThereDismissableGentleNotifs ? View.VISIBLE : View.GONE);
+    /**
+     * Show the clear section [X] button
+     * @param enabled
+     */
+    public void setClearSectionButtonEnabled(boolean enabled) {
+        mClearAllButton.setVisibility(enabled ? View.VISIBLE : View.GONE);
     }
 
     @Override
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/phone/KeyguardBouncer.java b/packages/SystemUI/src/com/android/systemui/statusbar/phone/KeyguardBouncer.java
index b9b663c..353868b 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/KeyguardBouncer.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/KeyguardBouncer.java
@@ -45,6 +45,7 @@
 import com.android.systemui.keyguard.DismissCallbackRegistry;
 import com.android.systemui.shared.system.SysUiStatsLog;
 import com.android.systemui.statusbar.policy.KeyguardStateController;
+import com.android.systemui.util.ListenerSet;
 
 import java.io.PrintWriter;
 import java.util.ArrayList;
@@ -84,11 +85,11 @@
     private final Runnable mRemoveViewRunnable = this::removeView;
     private final KeyguardBypassController mKeyguardBypassController;
     private KeyguardHostViewController mKeyguardViewController;
-    private final List<KeyguardResetCallback> mResetCallbacks = new ArrayList<>();
+    private final ListenerSet<KeyguardResetCallback> mResetCallbacks = new ListenerSet<>();
     private final Runnable mResetRunnable = ()-> {
         if (mKeyguardViewController != null) {
             mKeyguardViewController.resetSecurityContainer();
-            for (KeyguardResetCallback callback : new ArrayList<>(mResetCallbacks)) {
+            for (KeyguardResetCallback callback : mResetCallbacks) {
                 callback.onKeyguardReset();
             }
         }
@@ -602,7 +603,7 @@
     }
 
     public void addKeyguardResetCallback(KeyguardResetCallback callback) {
-        mResetCallbacks.add(callback);
+        mResetCallbacks.addIfAbsent(callback);
     }
 
     public void removeKeyguardResetCallback(KeyguardResetCallback callback) {
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/phone/KeyguardClockPositionAlgorithm.java b/packages/SystemUI/src/com/android/systemui/statusbar/phone/KeyguardClockPositionAlgorithm.java
index e368aad..4f3bbdb 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/KeyguardClockPositionAlgorithm.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/KeyguardClockPositionAlgorithm.java
@@ -195,12 +195,21 @@
                 1.0f /* panelExpansion */, 1.0f /* darkAmount */);
         result.clockAlpha = getClockAlpha(y);
         result.stackScrollerPadding = getStackScrollerPadding(y);
-        result.stackScrollerPaddingExpanded = mBypassEnabled ? mUnlockedStackScrollerPadding
-                : getClockY(1.0f, mDarkAmount) + mKeyguardStatusHeight;
+        result.stackScrollerPaddingExpanded = getStackScrollerPaddingExpanded();
         result.clockX = (int) interpolate(0, burnInPreventionOffsetX(), mDarkAmount);
         result.clockScale = interpolate(getBurnInScale(), 1.0f, 1.0f - mDarkAmount);
     }
 
+    private int getStackScrollerPaddingExpanded() {
+        if (mBypassEnabled) {
+            return mUnlockedStackScrollerPadding;
+        } else if (mIsSplitShade) {
+            return getClockY(1.0f, mDarkAmount);
+        } else {
+            return getClockY(1.0f, mDarkAmount) + mKeyguardStatusHeight;
+        }
+    }
+
     private int getStackScrollerPadding(int clockYPosition) {
         if (mBypassEnabled) {
             return (int) (mUnlockedStackScrollerPadding + mOverStretchAmount);
@@ -212,8 +221,13 @@
     }
 
     public float getMinStackScrollerPadding() {
-        return mBypassEnabled ? mUnlockedStackScrollerPadding
-                : mMinTopMargin + mKeyguardStatusHeight;
+        if (mBypassEnabled) {
+            return mUnlockedStackScrollerPadding;
+        } else if (mIsSplitShade) {
+            return mMinTopMargin;
+        } else {
+            return mMinTopMargin + mKeyguardStatusHeight;
+        }
     }
 
     private int getExpandedPreferredClockY() {
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/phone/StatusBar.java b/packages/SystemUI/src/com/android/systemui/statusbar/phone/StatusBar.java
index bc50893..4ad2af4 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/StatusBar.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/StatusBar.java
@@ -174,7 +174,7 @@
 import com.android.systemui.qs.QSPanelController;
 import com.android.systemui.recents.ScreenPinningRequest;
 import com.android.systemui.scrim.ScrimView;
-import com.android.systemui.settings.brightness.BrightnessSlider;
+import com.android.systemui.settings.brightness.BrightnessSliderController;
 import com.android.systemui.shared.plugins.PluginManager;
 import com.android.systemui.statusbar.AutoHideUiElement;
 import com.android.systemui.statusbar.BackDropView;
@@ -207,6 +207,7 @@
 import com.android.systemui.statusbar.notification.NotificationLaunchAnimatorControllerProvider;
 import com.android.systemui.statusbar.notification.NotificationWakeUpCoordinator;
 import com.android.systemui.statusbar.notification.collection.legacy.VisualStabilityManager;
+import com.android.systemui.statusbar.notification.collection.render.NotifShadeEventSource;
 import com.android.systemui.statusbar.notification.init.NotificationsController;
 import com.android.systemui.statusbar.notification.interruption.BypassHeadsUpNotifier;
 import com.android.systemui.statusbar.notification.interruption.NotificationInterruptStateProvider;
@@ -535,13 +536,14 @@
 
     private final int[] mAbsPos = new int[2];
 
+    private final NotifShadeEventSource mNotifShadeEventSource;
     protected final NotificationEntryManager mEntryManager;
     private final NotificationGutsManager mGutsManager;
     private final NotificationLogger mNotificationLogger;
     private final NotificationViewHierarchyManager mViewHierarchyManager;
     private final KeyguardViewMediator mKeyguardViewMediator;
     protected final NotificationInterruptStateProvider mNotificationInterruptStateProvider;
-    private final BrightnessSlider.Factory mBrightnessSliderFactory;
+    private final BrightnessSliderController.Factory mBrightnessSliderFactory;
     private final FeatureFlags mFeatureFlags;
     private final UnfoldTransitionConfig mUnfoldTransitionConfig;
     private final Lazy<UnfoldLightRevealOverlayAnimation> mUnfoldLightRevealOverlayAnimation;
@@ -718,6 +720,7 @@
             FalsingManager falsingManager,
             FalsingCollector falsingCollector,
             BroadcastDispatcher broadcastDispatcher,
+            NotifShadeEventSource notifShadeEventSource,
             NotificationEntryManager notificationEntryManager,
             NotificationGutsManager notificationGutsManager,
             NotificationLogger notificationLogger,
@@ -781,7 +784,7 @@
             Lazy<NotificationShadeDepthController> notificationShadeDepthControllerLazy,
             StatusBarTouchableRegionManager statusBarTouchableRegionManager,
             NotificationIconAreaController notificationIconAreaController,
-            BrightnessSlider.Factory brightnessSliderFactory,
+            BrightnessSliderController.Factory brightnessSliderFactory,
             UnfoldTransitionConfig unfoldTransitionConfig,
             Lazy<UnfoldLightRevealOverlayAnimation> unfoldLightRevealOverlayAnimation,
             Lazy<UnfoldTransitionWallpaperController> unfoldTransitionWallpaperController,
@@ -823,6 +826,7 @@
         mFalsingCollector = falsingCollector;
         mFalsingManager = falsingManager;
         mBroadcastDispatcher = broadcastDispatcher;
+        mNotifShadeEventSource = notifShadeEventSource;
         mEntryManager = notificationEntryManager;
         mGutsManager = notificationGutsManager;
         mNotificationLogger = notificationLogger;
@@ -1506,6 +1510,7 @@
                 mDynamicPrivacyController,
                 mKeyguardStateController,
                 mKeyguardIndicationController,
+                mFeatureFlags,
                 this /* statusBar */,
                 mShadeController,
                 mLockscreenShadeTransitionController,
@@ -1513,6 +1518,7 @@
                 mViewHierarchyManager,
                 mLockscreenUserManager,
                 mStatusBarStateController,
+                mNotifShadeEventSource,
                 mEntryManager,
                 mMediaManager,
                 mGutsManager,
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 cd95cfd..cac66a3 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/StatusBarKeyguardViewManager.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/StatusBarKeyguardViewManager.java
@@ -907,7 +907,7 @@
 
     @Override
     public boolean bouncerIsOrWillBeShowing() {
-        return mBouncer.isShowing() || mBouncer.getShowingSoon();
+        return isBouncerShowing() || mBouncer.getShowingSoon();
     }
 
     public boolean isFullscreenBouncer() {
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/phone/StatusBarNotificationPresenter.java b/packages/SystemUI/src/com/android/systemui/statusbar/phone/StatusBarNotificationPresenter.java
index c655964..cf9b2c6 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/StatusBarNotificationPresenter.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/StatusBarNotificationPresenter.java
@@ -17,9 +17,7 @@
 import static com.android.systemui.statusbar.phone.StatusBar.CLOSE_PANEL_WHEN_EMPTIED;
 import static com.android.systemui.statusbar.phone.StatusBar.DEBUG;
 import static com.android.systemui.statusbar.phone.StatusBar.MULTIUSER_DEBUG;
-import static com.android.systemui.statusbar.phone.StatusBar.SPEW;
 
-import android.annotation.Nullable;
 import android.app.KeyguardManager;
 import android.content.Context;
 import android.os.RemoteException;
@@ -36,7 +34,6 @@
 
 import com.android.internal.logging.nano.MetricsProto.MetricsEvent;
 import com.android.internal.statusbar.IStatusBarService;
-import com.android.internal.statusbar.NotificationVisibility;
 import com.android.internal.widget.MessagingGroup;
 import com.android.internal.widget.MessagingMessage;
 import com.android.keyguard.KeyguardUpdateMonitor;
@@ -44,6 +41,7 @@
 import com.android.systemui.ForegroundServiceNotificationListener;
 import com.android.systemui.InitController;
 import com.android.systemui.R;
+import com.android.systemui.flags.FeatureFlags;
 import com.android.systemui.plugins.ActivityStarter;
 import com.android.systemui.plugins.ActivityStarter.OnDismissAction;
 import com.android.systemui.statusbar.CommandQueue;
@@ -59,10 +57,10 @@
 import com.android.systemui.statusbar.SysuiStatusBarStateController;
 import com.android.systemui.statusbar.notification.AboveShelfObserver;
 import com.android.systemui.statusbar.notification.DynamicPrivacyController;
-import com.android.systemui.statusbar.notification.NotificationEntryListener;
 import com.android.systemui.statusbar.notification.NotificationEntryManager;
 import com.android.systemui.statusbar.notification.collection.NotificationEntry;
 import com.android.systemui.statusbar.notification.collection.inflation.NotificationRowBinderImpl;
+import com.android.systemui.statusbar.notification.collection.render.NotifShadeEventSource;
 import com.android.systemui.statusbar.notification.interruption.NotificationInterruptStateProvider;
 import com.android.systemui.statusbar.notification.interruption.NotificationInterruptSuppressor;
 import com.android.systemui.statusbar.notification.row.ActivatableNotificationView;
@@ -88,6 +86,7 @@
     private final NotificationViewHierarchyManager mViewHierarchyManager;
     private final NotificationLockscreenUserManager mLockscreenUserManager;
     private final SysuiStatusBarStateController mStatusBarStateController;
+    private final NotifShadeEventSource mNotifShadeEventSource;
     private final NotificationEntryManager mEntryManager;
     private final NotificationMediaManager mMediaManager;
     private final NotificationGutsManager mGutsManager;
@@ -100,6 +99,7 @@
     private final DozeScrimController mDozeScrimController;
     private final ScrimController mScrimController;
     private final KeyguardIndicationController mKeyguardIndicationController;
+    private final FeatureFlags mFeatureFlags;
     private final StatusBar mStatusBar;
     private final ShadeController mShadeController;
     private final LockscreenShadeTransitionController mShadeTransitionController;
@@ -127,6 +127,7 @@
             DynamicPrivacyController dynamicPrivacyController,
             KeyguardStateController keyguardStateController,
             KeyguardIndicationController keyguardIndicationController,
+            FeatureFlags featureFlags,
             StatusBar statusBar,
             ShadeController shadeController,
             LockscreenShadeTransitionController shadeTransitionController,
@@ -134,6 +135,7 @@
             NotificationViewHierarchyManager notificationViewHierarchyManager,
             NotificationLockscreenUserManager lockscreenUserManager,
             SysuiStatusBarStateController sysuiStatusBarStateController,
+            NotifShadeEventSource notifShadeEventSource,
             NotificationEntryManager notificationEntryManager,
             NotificationMediaManager notificationMediaManager,
             NotificationGutsManager notificationGutsManager,
@@ -148,6 +150,7 @@
         mHeadsUpManager = headsUp;
         mDynamicPrivacyController = dynamicPrivacyController;
         mKeyguardIndicationController = keyguardIndicationController;
+        mFeatureFlags = featureFlags;
         // TODO: use KeyguardStateController#isOccluded to remove this dependency
         mStatusBar = statusBar;
         mShadeController = shadeController;
@@ -156,6 +159,7 @@
         mViewHierarchyManager = notificationViewHierarchyManager;
         mLockscreenUserManager = lockscreenUserManager;
         mStatusBarStateController = sysuiStatusBarStateController;
+        mNotifShadeEventSource = notifShadeEventSource;
         mEntryManager = notificationEntryManager;
         mMediaManager = notificationMediaManager;
         mGutsManager = notificationGutsManager;
@@ -186,30 +190,18 @@
                 mNotificationPanel.createRemoteInputDelegate());
 
         initController.addPostInitTask(() -> {
-            NotificationEntryListener notificationEntryListener = new NotificationEntryListener() {
-                @Override
-                public void onEntryRemoved(
-                        @Nullable NotificationEntry entry,
-                        NotificationVisibility visibility,
-                        boolean removedByUser,
-                        int reason) {
-                    StatusBarNotificationPresenter.this.onNotificationRemoved(
-                            entry.getKey(), entry.getSbn(), reason);
-                    if (removedByUser) {
-                        maybeEndAmbientPulse();
-                    }
-                }
-            };
-
             mKeyguardIndicationController.init();
             mViewHierarchyManager.setUpWithPresenter(this,
                     stackScrollerController.getNotificationListContainer());
-            mEntryManager.setUpWithPresenter(this);
-            mEntryManager.addNotificationEntryListener(notificationEntryListener);
-            mEntryManager.addNotificationLifetimeExtender(mHeadsUpManager);
-            mEntryManager.addNotificationLifetimeExtender(mGutsManager);
-            mEntryManager.addNotificationLifetimeExtenders(
-                    remoteInputManager.getLifetimeExtenders());
+            mNotifShadeEventSource.setShadeEmptiedCallback(this::maybeClosePanelForShadeEmptied);
+            mNotifShadeEventSource.setNotifRemovedByUserCallback(this::maybeEndAmbientPulse);
+            if (!mFeatureFlags.isNewNotifPipelineRenderingEnabled()) {
+                mEntryManager.setUpWithPresenter(this);
+                mEntryManager.addNotificationLifetimeExtender(mHeadsUpManager);
+                mEntryManager.addNotificationLifetimeExtender(mGutsManager);
+                mEntryManager.addNotificationLifetimeExtenders(
+                        remoteInputManager.getLifetimeExtenders());
+            }
             notificationInterruptStateProvider.addSuppressor(mInterruptSuppressor);
             mLockscreenUserManager.setUpWithPresenter(this);
             mMediaManager.setUpWithPresenter(this);
@@ -226,8 +218,21 @@
         configurationController.addCallback(this);
     }
 
+    /** Called when the shade has been emptied to attempt to close the shade */
+    private void maybeClosePanelForShadeEmptied() {
+        if (CLOSE_PANEL_WHEN_EMPTIED
+                && !mNotificationPanel.isTracking()
+                && !mNotificationPanel.isQsExpanded()
+                && mStatusBarStateController.getState() == StatusBarState.SHADE_LOCKED
+                && !isCollapsing()) {
+            mStatusBarStateController.setState(StatusBarState.KEYGUARD);
+        }
+    }
+
     @Override
     public void onDensityOrFontScaleChanged() {
+        // TODO(b/145659174): Remove legacy pipeline code
+        if (mFeatureFlags.isNewNotifPipelineRenderingEnabled()) return;
         MessagingMessage.dropCache();
         MessagingGroup.dropCache();
         if (!mKeyguardUpdateMonitor.isSwitchingUser()) {
@@ -239,8 +244,10 @@
 
     @Override
     public void onUiModeChanged() {
+        // TODO(b/145659174): Remove legacy pipeline code
+        if (mFeatureFlags.isNewNotifPipelineRenderingEnabled()) return;
         if (!mKeyguardUpdateMonitor.isSwitchingUser()) {
-            updateNotificationOnUiModeChanged();
+            updateNotificationsOnUiModeChanged();
         } else {
             mDispatchUiModeChangeOnUserSwitched = true;
         }
@@ -251,7 +258,9 @@
         onDensityOrFontScaleChanged();
     }
 
-    private void updateNotificationOnUiModeChanged() {
+    private void updateNotificationsOnUiModeChanged() {
+        // TODO(b/145659174): Remove legacy pipeline code
+        if (mFeatureFlags.isNewNotifPipelineRenderingEnabled()) return;
         List<NotificationEntry> userNotifications =
                 mEntryManager.getActiveNotificationsForCurrentUser();
         for (int i = 0; i < userNotifications.size(); i++) {
@@ -264,6 +273,8 @@
     }
 
     private void updateNotificationsOnDensityOrFontScaleChanged() {
+        // TODO(b/145659174): Remove legacy pipeline code
+        if (mFeatureFlags.isNewNotifPipelineRenderingEnabled()) return;
         List<NotificationEntry> userNotifications =
                 mEntryManager.getActiveNotificationsForCurrentUser();
         for (int i = 0; i < userNotifications.size(); i++) {
@@ -276,6 +287,7 @@
         }
     }
 
+
     @Override
     public boolean isCollapsing() {
         return mNotificationPanel.isCollapsing()
@@ -308,21 +320,6 @@
         mNotificationPanel.updateNotificationViews(reason);
     }
 
-    private void onNotificationRemoved(String key, StatusBarNotification old, int reason) {
-        if (SPEW) Log.d(TAG, "removeNotification key=" + key + " old=" + old);
-
-        if (old != null && CLOSE_PANEL_WHEN_EMPTIED && !hasActiveNotifications()
-                && !mNotificationPanel.isTracking() && !mNotificationPanel.isQsExpanded()
-                && mStatusBarStateController.getState() == StatusBarState.SHADE_LOCKED
-                        && !isCollapsing()) {
-                mStatusBarStateController.setState(StatusBarState.KEYGUARD);
-        }
-    }
-
-    public boolean hasActiveNotifications() {
-        return mEntryManager.hasActiveNotifications();
-    }
-
     @Override
     public void onUserSwitched(int newUserId) {
         // Begin old BaseStatusBar.userSwitched
@@ -335,7 +332,7 @@
             mReinflateNotificationsOnUserSwitched = false;
         }
         if (mDispatchUiModeChangeOnUserSwitched) {
-            updateNotificationOnUiModeChanged();
+            updateNotificationsOnUiModeChanged();
             mDispatchUiModeChangeOnUserSwitched = false;
         }
         updateNotificationViews("user switched");
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/phone/SystemUIDialog.java b/packages/SystemUI/src/com/android/systemui/statusbar/phone/SystemUIDialog.java
index 9415d50..18aa689 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/SystemUIDialog.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/SystemUIDialog.java
@@ -22,7 +22,11 @@
 import android.content.Context;
 import android.content.Intent;
 import android.content.IntentFilter;
+import android.os.Bundle;
+import android.os.SystemProperties;
 import android.os.UserHandle;
+import android.util.TypedValue;
+import android.view.ViewGroup;
 import android.view.Window;
 import android.view.WindowInsets.Type;
 import android.view.WindowManager;
@@ -45,6 +49,10 @@
  * and dismisses itself when it receives the broadcast.
  */
 public class SystemUIDialog extends AlertDialog implements ListenableDialog {
+    // TODO(b/203389579): Remove this once the dialog width on large screens has been agreed on.
+    private static final String FLAG_TABLET_DIALOG_WIDTH =
+            "persist.systemui.flag_tablet_dialog_width";
+
     private final Context mContext;
     private final DismissReceiver mDismissReceiver;
     private final Set<DialogListener> mDialogListeners = new LinkedHashSet<>();
@@ -66,6 +74,41 @@
     }
 
     @Override
+    protected void onCreate(Bundle savedInstanceState) {
+        super.onCreate(savedInstanceState);
+
+        // Set the dialog window size.
+        getWindow().setLayout(getDialogWidth(), ViewGroup.LayoutParams.WRAP_CONTENT);
+    }
+
+    private int getDialogWidth() {
+        boolean isOnTablet =
+                mContext.getResources().getConfiguration().smallestScreenWidthDp >= 600;
+        if (!isOnTablet) {
+            return ViewGroup.LayoutParams.MATCH_PARENT;
+        }
+
+        int flagValue = SystemProperties.getInt(FLAG_TABLET_DIALOG_WIDTH, 0);
+        if (flagValue == -1) {
+            // The width of bottom sheets (624dp).
+            return Math.round(TypedValue.applyDimension(TypedValue.COMPLEX_UNIT_DIP, 624,
+                    mContext.getResources().getDisplayMetrics()));
+        } else if (flagValue == -2) {
+            // The suggested small width for all dialogs (348dp)
+            return Math.round(TypedValue.applyDimension(TypedValue.COMPLEX_UNIT_DIP, 348,
+                    mContext.getResources().getDisplayMetrics()));
+        } else if (flagValue > 0) {
+            // Any given width.
+            return Math.round(
+                    TypedValue.applyDimension(TypedValue.COMPLEX_UNIT_DIP, flagValue,
+                            mContext.getResources().getDisplayMetrics()));
+        } else {
+            // By default we use the same width as the notification shade in portrait mode (504dp).
+            return mContext.getResources().getDimensionPixelSize(R.dimen.large_dialog_width);
+        }
+    }
+
+    @Override
     protected void onStart() {
         super.onStart();
         mDismissReceiver.register();
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/phone/UnlockedScreenOffAnimationController.kt b/packages/SystemUI/src/com/android/systemui/statusbar/phone/UnlockedScreenOffAnimationController.kt
index e2332e9..cddde64 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/UnlockedScreenOffAnimationController.kt
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/UnlockedScreenOffAnimationController.kt
@@ -161,6 +161,8 @@
 
                         // Done going to sleep, reset this flag.
                         decidedToAnimateGoingToSleep = null
+                        // We need to unset the listener. These are persistent for future animators
+                        keyguardView.animate().setListener(null)
                     }
                 })
                 .start()
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/phone/dagger/StatusBarPhoneModule.java b/packages/SystemUI/src/com/android/systemui/statusbar/phone/dagger/StatusBarPhoneModule.java
index c452a48..959c673 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/dagger/StatusBarPhoneModule.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/dagger/StatusBarPhoneModule.java
@@ -48,7 +48,7 @@
 import com.android.systemui.plugins.FalsingManager;
 import com.android.systemui.plugins.PluginDependencyProvider;
 import com.android.systemui.recents.ScreenPinningRequest;
-import com.android.systemui.settings.brightness.BrightnessSlider;
+import com.android.systemui.settings.brightness.BrightnessSliderController;
 import com.android.systemui.shared.plugins.PluginManager;
 import com.android.systemui.statusbar.CommandQueue;
 import com.android.systemui.statusbar.KeyguardIndicationController;
@@ -68,6 +68,7 @@
 import com.android.systemui.statusbar.notification.NotificationEntryManager;
 import com.android.systemui.statusbar.notification.NotificationWakeUpCoordinator;
 import com.android.systemui.statusbar.notification.collection.legacy.VisualStabilityManager;
+import com.android.systemui.statusbar.notification.collection.render.NotifShadeEventSource;
 import com.android.systemui.statusbar.notification.init.NotificationsController;
 import com.android.systemui.statusbar.notification.interruption.BypassHeadsUpNotifier;
 import com.android.systemui.statusbar.notification.interruption.NotificationInterruptStateProvider;
@@ -157,6 +158,7 @@
             FalsingManager falsingManager,
             FalsingCollector falsingCollector,
             BroadcastDispatcher broadcastDispatcher,
+            NotifShadeEventSource notifShadeEventSource,
             NotificationEntryManager notificationEntryManager,
             NotificationGutsManager notificationGutsManager,
             NotificationLogger notificationLogger,
@@ -220,7 +222,7 @@
             Lazy<NotificationShadeDepthController> notificationShadeDepthController,
             StatusBarTouchableRegionManager statusBarTouchableRegionManager,
             NotificationIconAreaController notificationIconAreaController,
-            BrightnessSlider.Factory brightnessSliderFactory,
+            BrightnessSliderController.Factory brightnessSliderFactory,
             UnfoldTransitionConfig unfoldTransitionConfig,
             Lazy<UnfoldLightRevealOverlayAnimation> unfoldLightRevealOverlayAnimation,
             Lazy<NaturalRotationUnfoldProgressProvider> naturalRotationUnfoldProgressProvider,
@@ -260,6 +262,7 @@
                 falsingManager,
                 falsingCollector,
                 broadcastDispatcher,
+                notifShadeEventSource,
                 notificationEntryManager,
                 notificationGutsManager,
                 notificationLogger,
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/policy/BrightnessMirrorController.java b/packages/SystemUI/src/com/android/systemui/statusbar/policy/BrightnessMirrorController.java
index 1e52511..5bd20ff 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/policy/BrightnessMirrorController.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/policy/BrightnessMirrorController.java
@@ -26,7 +26,7 @@
 import android.widget.FrameLayout;
 
 import com.android.systemui.R;
-import com.android.systemui.settings.brightness.BrightnessSlider;
+import com.android.systemui.settings.brightness.BrightnessSliderController;
 import com.android.systemui.settings.brightness.ToggleSlider;
 import com.android.systemui.statusbar.NotificationShadeDepthController;
 import com.android.systemui.statusbar.phone.NotificationPanelViewController;
@@ -46,8 +46,8 @@
     private final NotificationPanelViewController mNotificationPanel;
     private final NotificationShadeDepthController mDepthController;
     private final ArraySet<BrightnessMirrorListener> mBrightnessMirrorListeners = new ArraySet<>();
-    private final BrightnessSlider.Factory mToggleSliderFactory;
-    private BrightnessSlider mToggleSliderController;
+    private final BrightnessSliderController.Factory mToggleSliderFactory;
+    private BrightnessSliderController mToggleSliderController;
     private final int[] mInt2Cache = new int[2];
     private FrameLayout mBrightnessMirror;
     private int mBrightnessMirrorBackgroundPadding;
@@ -56,7 +56,7 @@
     public BrightnessMirrorController(NotificationShadeWindowView statusBarWindow,
             NotificationPanelViewController notificationPanelViewController,
             NotificationShadeDepthController notificationShadeDepthController,
-            BrightnessSlider.Factory factory,
+            BrightnessSliderController.Factory factory,
             @NonNull Consumer<Boolean> visibilityCallback) {
         mStatusBarWindow = statusBarWindow;
         mToggleSliderFactory = factory;
@@ -135,9 +135,10 @@
         reinflate();
     }
 
-    private BrightnessSlider setMirrorLayout() {
+    private BrightnessSliderController setMirrorLayout() {
         Context context = mBrightnessMirror.getContext();
-        BrightnessSlider controller = mToggleSliderFactory.create(context, mBrightnessMirror);
+        BrightnessSliderController controller = mToggleSliderFactory.create(context,
+                mBrightnessMirror);
         controller.init();
 
         mBrightnessMirror.addView(controller.getRootView(), ViewGroup.LayoutParams.MATCH_PARENT,
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/policy/HeadsUpManager.java b/packages/SystemUI/src/com/android/systemui/statusbar/policy/HeadsUpManager.java
index a8097c4..e0b0dd36 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/policy/HeadsUpManager.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/policy/HeadsUpManager.java
@@ -38,11 +38,10 @@
 import com.android.systemui.statusbar.AlertingNotificationManager;
 import com.android.systemui.statusbar.notification.collection.NotificationEntry;
 import com.android.systemui.statusbar.notification.row.NotificationRowContentBinder.InflationFlag;
+import com.android.systemui.util.ListenerSet;
 
 import java.io.FileDescriptor;
 import java.io.PrintWriter;
-import java.util.ArrayList;
-import java.util.HashSet;
 
 /**
  * A manager which handles heads up notifications which is a special mode where
@@ -52,7 +51,7 @@
     private static final String TAG = "HeadsUpManager";
     private static final String SETTING_HEADS_UP_SNOOZE_LENGTH_MS = "heads_up_snooze_length_ms";
 
-    protected final HashSet<OnHeadsUpChangedListener> mListeners = new HashSet<>();
+    protected final ListenerSet<OnHeadsUpChangedListener> mListeners = new ListenerSet<>();
 
     protected final Context mContext;
 
@@ -118,7 +117,7 @@
      * Adds an OnHeadUpChangedListener to observe events.
      */
     public void addListener(@NonNull OnHeadsUpChangedListener listener) {
-        mListeners.add(listener);
+        mListeners.addIfAbsent(listener);
     }
 
     /**
@@ -158,7 +157,7 @@
                         NotificationPeekEvent.NOTIFICATION_PEEK, entry.getSbn().getUid(),
                         entry.getSbn().getPackageName(), entry.getSbn().getInstanceId());
             }
-            for (OnHeadsUpChangedListener listener : new ArrayList<>(mListeners)) {
+            for (OnHeadsUpChangedListener listener : mListeners) {
                 if (isPinned) {
                     listener.onHeadsUpPinned(entry);
                 } else {
@@ -178,7 +177,7 @@
         entry.setHeadsUp(true);
         setEntryPinned((HeadsUpEntry) alertEntry, shouldHeadsUpBecomePinned(entry));
         EventLogTags.writeSysuiHeadsUpStatus(entry.getKey(), 1 /* visible */);
-        for (OnHeadsUpChangedListener listener : new ArrayList<>(mListeners)) {
+        for (OnHeadsUpChangedListener listener : mListeners) {
             listener.onHeadsUpStateChanged(entry, true);
         }
     }
@@ -189,7 +188,7 @@
         entry.setHeadsUp(false);
         setEntryPinned((HeadsUpEntry) alertEntry, false /* isPinned */);
         EventLogTags.writeSysuiHeadsUpStatus(entry.getKey(), 0 /* visible */);
-        for (OnHeadsUpChangedListener listener : new ArrayList<>(mListeners)) {
+        for (OnHeadsUpChangedListener listener : mListeners) {
             listener.onHeadsUpStateChanged(entry, false);
         }
     }
@@ -207,7 +206,7 @@
         if (mHasPinnedNotification) {
             MetricsLogger.count(mContext, "note_peek", 1);
         }
-        for (OnHeadsUpChangedListener listener : new ArrayList<>(mListeners)) {
+        for (OnHeadsUpChangedListener listener : mListeners) {
             listener.onHeadsUpPinnedModeChanged(hasPinnedNotification);
         }
     }
diff --git a/packages/SystemUI/src/com/android/systemui/util/ListenerSet.kt b/packages/SystemUI/src/com/android/systemui/util/ListenerSet.kt
new file mode 100644
index 0000000..0f4193e9
--- /dev/null
+++ b/packages/SystemUI/src/com/android/systemui/util/ListenerSet.kt
@@ -0,0 +1,47 @@
+/*
+ * Copyright (C) 2021 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.systemui.util
+
+import java.util.concurrent.CopyOnWriteArrayList
+
+/**
+ * A collection of listeners, observers, callbacks, etc.
+ *
+ * This container is optimized for infrequent mutation and frequent iteration, with thread safety
+ * and reentrant-safety guarantees as well.
+ */
+class ListenerSet<E> : Iterable<E> {
+    private val listeners: CopyOnWriteArrayList<E> = CopyOnWriteArrayList()
+
+    /**
+     * A thread-safe, reentrant-safe method to add a listener.
+     * Does nothing if the listener is already in the set.
+     */
+    fun addIfAbsent(element: E): Boolean = listeners.addIfAbsent(element)
+
+    /**
+     * A thread-safe, reentrant-safe method to remove a listener.
+     */
+    fun remove(element: E): Boolean = listeners.remove(element)
+
+    /**
+     * Returns an iterator over the listeners currently in the set.  Note that to ensure
+     * [ConcurrentModificationException] is never thrown, this iterator will not reflect changes
+     * made to the set after the iterator is constructed.
+     */
+    override fun iterator(): Iterator<E> = listeners.iterator()
+}
diff --git a/packages/SystemUI/tests/src/com/android/systemui/biometrics/SidefpsControllerTest.kt b/packages/SystemUI/tests/src/com/android/systemui/biometrics/SidefpsControllerTest.kt
index ca7d506..5fee7fb 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/biometrics/SidefpsControllerTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/biometrics/SidefpsControllerTest.kt
@@ -18,7 +18,6 @@
 
 import android.graphics.Rect
 import android.hardware.biometrics.BiometricOverlayConstants.REASON_AUTH_KEYGUARD
-import android.hardware.biometrics.BiometricOverlayConstants.REASON_AUTH_SETTINGS
 import android.hardware.biometrics.BiometricOverlayConstants.REASON_UNKNOWN
 import android.hardware.biometrics.SensorProperties
 import android.hardware.display.DisplayManager
@@ -183,16 +182,7 @@
 
     @Test
     fun testIgnoredForKeyguard() {
-        testIgnoredFor(REASON_AUTH_KEYGUARD)
-    }
-
-    @Test
-    fun testIgnoredForSettings() {
-        testIgnoredFor(REASON_AUTH_SETTINGS)
-    }
-
-    private fun testIgnoredFor(reason: Int) {
-        overlayController.show(SENSOR_ID, reason)
+        overlayController.show(SENSOR_ID, REASON_AUTH_KEYGUARD)
         executor.runAllReady()
 
         verify(windowManager, never()).addView(any(), any())
diff --git a/packages/SystemUI/tests/src/com/android/systemui/flags/FeatureFlagManagerTest.java b/packages/SystemUI/tests/src/com/android/systemui/flags/FeatureFlagManagerTest.java
index 172dcda..b3c098c 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/flags/FeatureFlagManagerTest.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/flags/FeatureFlagManagerTest.java
@@ -18,23 +18,35 @@
 
 import static com.google.common.truth.Truth.assertThat;
 
+import static org.junit.Assert.assertFalse;
+import static org.junit.Assert.assertTrue;
+import static org.mockito.Mockito.mock;
+
 import androidx.test.filters.SmallTest;
 
 import com.android.systemui.SysuiTestCase;
+import com.android.systemui.dump.DumpManager;
 
 import org.junit.Before;
 import org.junit.Test;
+import org.mockito.Mock;
 import org.mockito.MockitoAnnotations;
 
+import java.io.FileDescriptor;
+import java.io.PrintWriter;
+import java.io.StringWriter;
+
 @SmallTest
 public class FeatureFlagManagerTest extends SysuiTestCase {
     FeatureFlagManager mFeatureFlagManager;
 
+    @Mock private DumpManager mDumpManager;
+
     @Before
     public void setup() {
         MockitoAnnotations.initMocks(this);
 
-        mFeatureFlagManager = new FeatureFlagManager();
+        mFeatureFlagManager = new FeatureFlagManager(mDumpManager);
     }
 
     @Test
@@ -43,4 +55,31 @@
         // Again, nothing changes.
         assertThat(mFeatureFlagManager.isEnabled(1, false)).isFalse();
     }
+
+    @Test
+    public void testDump() {
+        // Even if a flag is set before
+        mFeatureFlagManager.setEnabled(1, true);
+
+        // WHEN the flags have been accessed
+        assertFalse(mFeatureFlagManager.isEnabled(1, false));
+        assertTrue(mFeatureFlagManager.isEnabled(2, true));
+
+        // Even if a flag is set after
+        mFeatureFlagManager.setEnabled(2, false);
+
+        // THEN the dump contains the flags and the default values
+        String dump = dumpToString();
+        assertThat(dump).contains(" sysui_flag_1: false\n");
+        assertThat(dump).contains(" sysui_flag_2: true\n");
+    }
+
+    private String dumpToString() {
+        StringWriter sw = new StringWriter();
+        PrintWriter pw = new PrintWriter(sw);
+        mFeatureFlagManager.dump(mock(FileDescriptor.class), pw, new String[0]);
+        pw.flush();
+        String dump = sw.toString();
+        return dump;
+    }
 }
diff --git a/packages/SystemUI/tests/src/com/android/systemui/qs/QSPanelControllerTest.java b/packages/SystemUI/tests/src/com/android/systemui/qs/QSPanelControllerTest.java
index 06a4ae0..3242adb 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/qs/QSPanelControllerTest.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/qs/QSPanelControllerTest.java
@@ -42,7 +42,7 @@
 import com.android.systemui.qs.logging.QSLogger;
 import com.android.systemui.qs.tileimpl.QSTileImpl;
 import com.android.systemui.settings.brightness.BrightnessController;
-import com.android.systemui.settings.brightness.BrightnessSlider;
+import com.android.systemui.settings.brightness.BrightnessSliderController;
 import com.android.systemui.settings.brightness.ToggleSlider;
 import com.android.systemui.statusbar.CommandQueue;
 import com.android.systemui.tuner.TunerService;
@@ -88,9 +88,9 @@
     @Mock
     private BrightnessController mBrightnessController;
     @Mock
-    private BrightnessSlider.Factory mToggleSliderViewControllerFactory;
+    private BrightnessSliderController.Factory mToggleSliderViewControllerFactory;
     @Mock
-    private BrightnessSlider mBrightnessSlider;
+    private BrightnessSliderController mBrightnessSliderController;
     @Mock
     QSTileImpl mQSTile;
     @Mock
@@ -120,7 +120,7 @@
         when(mQSTileHost.getTiles()).thenReturn(Collections.singleton(mQSTile));
         when(mQSTileHost.createTileView(any(), eq(mQSTile), anyBoolean())).thenReturn(mQSTileView);
         when(mToggleSliderViewControllerFactory.create(any(), any()))
-                .thenReturn(mBrightnessSlider);
+                .thenReturn(mBrightnessSliderController);
         when(mBrightnessControllerFactory.create(any(ToggleSlider.class)))
                 .thenReturn(mBrightnessController);
         when(mQSTileRevealControllerFactory.create(any(), any()))
diff --git a/packages/SystemUI/tests/src/com/android/systemui/qs/tiles/dialog/InternetDialogControllerTest.java b/packages/SystemUI/tests/src/com/android/systemui/qs/tiles/dialog/InternetDialogControllerTest.java
index 5cea763..eb03b5f 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/qs/tiles/dialog/InternetDialogControllerTest.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/qs/tiles/dialog/InternetDialogControllerTest.java
@@ -45,6 +45,7 @@
 import com.android.settingslib.wifi.WifiUtils;
 import com.android.systemui.R;
 import com.android.systemui.SysuiTestCase;
+import com.android.systemui.animation.DialogLaunchAnimator;
 import com.android.systemui.broadcast.BroadcastDispatcher;
 import com.android.systemui.dagger.qualifiers.Main;
 import com.android.systemui.plugins.ActivityStarter;
@@ -138,6 +139,8 @@
     private CarrierConfigTracker mCarrierConfigTracker;
     @Mock
     private LocationController mLocationController;
+    @Mock
+    private DialogLaunchAnimator mDialogLaunchAnimator;
 
     private TestableResources mTestableResources;
     private MockInternetDialogController mInternetDialogController;
@@ -174,7 +177,7 @@
                 mock(ConnectivityManager.class), mHandler, mExecutor, mBroadcastDispatcher,
                 mock(KeyguardUpdateMonitor.class), mGlobalSettings, mKeyguardStateController,
                 mWindowManager, mToastFactory, mWorkerHandler, mCarrierConfigTracker,
-                mLocationController);
+                mLocationController, mDialogLaunchAnimator);
         mSubscriptionManager.addOnSubscriptionsChangedListener(mExecutor,
                 mInternetDialogController.mOnSubscriptionsChangedListener);
         mInternetDialogController.onStart(mInternetDialogCallback, true);
@@ -654,12 +657,13 @@
                 KeyguardStateController keyguardStateController, WindowManager windowManager,
                 ToastFactory toastFactory, Handler workerHandler,
                 CarrierConfigTracker carrierConfigTracker,
-                LocationController locationController) {
+                LocationController locationController,
+                DialogLaunchAnimator dialogLaunchAnimator) {
             super(context, uiEventLogger, starter, accessPointController, subscriptionManager,
                     telephonyManager, wifiManager, connectivityManager, handler, mainExecutor,
                     broadcastDispatcher, keyguardUpdateMonitor, globalSettings,
                     keyguardStateController, windowManager, toastFactory, workerHandler,
-                    carrierConfigTracker, locationController);
+                    carrierConfigTracker, locationController, dialogLaunchAnimator);
             mGlobalSettings = globalSettings;
         }
 
diff --git a/packages/SystemUI/tests/src/com/android/systemui/settings/brightness/BrightnessSliderTest.kt b/packages/SystemUI/tests/src/com/android/systemui/settings/brightness/BrightnessSliderControllerTest.kt
similarity index 96%
rename from packages/SystemUI/tests/src/com/android/systemui/settings/brightness/BrightnessSliderTest.kt
rename to packages/SystemUI/tests/src/com/android/systemui/settings/brightness/BrightnessSliderControllerTest.kt
index bceb928..2b39354 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/settings/brightness/BrightnessSliderTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/settings/brightness/BrightnessSliderControllerTest.kt
@@ -45,7 +45,7 @@
 
 @SmallTest
 @RunWith(AndroidTestingRunner::class)
-class BrightnessSliderTest : SysuiTestCase() {
+class BrightnessSliderControllerTest : SysuiTestCase() {
 
     @Mock
     private lateinit var brightnessSliderView: BrightnessSliderView
@@ -66,7 +66,7 @@
     private lateinit var seekBar: SeekBar
     private var mFalsingManager: FalsingManagerFake = FalsingManagerFake()
 
-    private lateinit var mController: BrightnessSlider
+    private lateinit var mController: BrightnessSliderController
 
     @Before
     fun setUp() {
@@ -75,7 +75,7 @@
         whenever(mirrorController.toggleSlider).thenReturn(mirror)
         whenever(motionEvent.copy()).thenReturn(motionEvent)
 
-        mController = BrightnessSlider(brightnessSliderView, mFalsingManager)
+        mController = BrightnessSliderController(brightnessSliderView, mFalsingManager)
         mController.init()
         mController.setOnChangedListener(listener)
     }
diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/NotificationLockscreenUserManagerTest.java b/packages/SystemUI/tests/src/com/android/systemui/statusbar/NotificationLockscreenUserManagerTest.java
index ea21aa9..23cca72 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/NotificationLockscreenUserManagerTest.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/NotificationLockscreenUserManagerTest.java
@@ -20,10 +20,10 @@
 import static android.app.NotificationManager.IMPORTANCE_LOW;
 import static android.content.Intent.ACTION_USER_SWITCHED;
 
-import static com.android.systemui.statusbar.notification.stack.NotificationSectionsManagerKt.BUCKET_ALERTING;
-import static com.android.systemui.statusbar.notification.stack.NotificationSectionsManagerKt.BUCKET_MEDIA_CONTROLS;
-import static com.android.systemui.statusbar.notification.stack.NotificationSectionsManagerKt.BUCKET_PEOPLE;
-import static com.android.systemui.statusbar.notification.stack.NotificationSectionsManagerKt.BUCKET_SILENT;
+import static com.android.systemui.statusbar.notification.stack.NotificationPriorityBucketKt.BUCKET_ALERTING;
+import static com.android.systemui.statusbar.notification.stack.NotificationPriorityBucketKt.BUCKET_MEDIA_CONTROLS;
+import static com.android.systemui.statusbar.notification.stack.NotificationPriorityBucketKt.BUCKET_PEOPLE;
+import static com.android.systemui.statusbar.notification.stack.NotificationPriorityBucketKt.BUCKET_SILENT;
 
 import static junit.framework.Assert.assertFalse;
 import static junit.framework.Assert.assertTrue;
diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/collection/NotifCollectionTest.java b/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/collection/NotifCollectionTest.java
index 39d794d..ebeb591 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/collection/NotifCollectionTest.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/collection/NotifCollectionTest.java
@@ -60,6 +60,7 @@
 import android.util.ArraySet;
 import android.util.Pair;
 
+import androidx.annotation.NonNull;
 import androidx.test.filters.SmallTest;
 
 import com.android.internal.statusbar.IStatusBarService;
@@ -1405,25 +1406,26 @@
             mName = name;
         }
 
+        @NonNull
         @Override
         public String getName() {
             return mName;
         }
 
         @Override
-        public void setCallback(OnEndLifetimeExtensionCallback callback) {
+        public void setCallback(@NonNull OnEndLifetimeExtensionCallback callback) {
             this.callback = callback;
         }
 
         @Override
         public boolean shouldExtendLifetime(
-                NotificationEntry entry,
+                @NonNull NotificationEntry entry,
                 @CancellationReason int reason) {
             return shouldExtendLifetime;
         }
 
         @Override
-        public void cancelLifetimeExtension(NotificationEntry entry) {
+        public void cancelLifetimeExtension(@NonNull NotificationEntry entry) {
             if (onCancelLifetimeExtension != null) {
                 onCancelLifetimeExtension.run();
             }
diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/collection/ShadeListBuilderTest.java b/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/collection/ShadeListBuilderTest.java
index 3378003..e9e6718 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/collection/ShadeListBuilderTest.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/collection/ShadeListBuilderTest.java
@@ -33,6 +33,7 @@
 import static org.mockito.Mockito.mock;
 import static org.mockito.Mockito.never;
 import static org.mockito.Mockito.spy;
+import static org.mockito.Mockito.times;
 import static org.mockito.Mockito.verify;
 
 import static java.util.Collections.singletonList;
@@ -42,6 +43,7 @@
 import android.testing.TestableLooper;
 import android.util.ArrayMap;
 
+import androidx.annotation.Nullable;
 import androidx.test.filters.SmallTest;
 
 import com.android.systemui.SysuiTestCase;
@@ -78,6 +80,7 @@
 import java.util.List;
 import java.util.Map;
 import java.util.Objects;
+import java.util.concurrent.atomic.AtomicBoolean;
 import java.util.stream.Collectors;
 
 @SmallTest
@@ -608,6 +611,30 @@
     }
 
     @Test
+    public void testNotifSectionsChildrenUpdated() {
+        AtomicBoolean validChildren = new AtomicBoolean(false);
+        final NotifSectioner pkg1Sectioner = spy(new PackageSectioner(PACKAGE_1) {
+            @Nullable
+            @Override
+            public void onEntriesUpdated(List<ListEntry> entries) {
+                super.onEntriesUpdated(entries);
+                validChildren.set(entries.size() == 2);
+            }
+        });
+        mListBuilder.setSectioners(Arrays.asList(pkg1Sectioner));
+
+        addNotif(0, PACKAGE_4);
+        addNotif(1, PACKAGE_1);
+        addNotif(2, PACKAGE_1);
+        addNotif(3, PACKAGE_3);
+
+        dispatchBuild();
+
+        verify(pkg1Sectioner, times(1)).onEntriesUpdated(any());
+        assertTrue(validChildren.get());
+    }
+
+    @Test
     public void testNotifSections() {
         // GIVEN a filter that removes all PACKAGE_4 notifs and sections that divide
         // notifs based on package name
@@ -1633,7 +1660,7 @@
         private final String mPackage;
 
         PackageSectioner(String pkg) {
-            super("PackageSection_" + pkg);
+            super("PackageSection_" + pkg, 0);
             mPackage = pkg;
         }
 
diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/collection/coordinator/GutsCoordinatorTest.kt b/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/collection/coordinator/GutsCoordinatorTest.kt
new file mode 100644
index 0000000..0cba0703
--- /dev/null
+++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/collection/coordinator/GutsCoordinatorTest.kt
@@ -0,0 +1,117 @@
+/*
+ * Copyright (C) 2021 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package com.android.systemui.statusbar.notification.collection.coordinator
+
+import android.testing.AndroidTestingRunner
+import android.testing.TestableLooper.RunWithLooper
+import androidx.test.filters.SmallTest
+import com.android.systemui.SysuiTestCase
+import com.android.systemui.dump.DumpManager
+import com.android.systemui.statusbar.notification.collection.NotifPipeline
+import com.android.systemui.statusbar.notification.collection.NotificationEntry
+import com.android.systemui.statusbar.notification.collection.NotificationEntryBuilder
+import com.android.systemui.statusbar.notification.collection.notifcollection.NotifLifetimeExtender
+import com.android.systemui.statusbar.notification.collection.notifcollection.NotifLifetimeExtender.OnEndLifetimeExtensionCallback
+import com.android.systemui.statusbar.notification.collection.render.NotifGutsViewListener
+import com.android.systemui.statusbar.notification.collection.render.NotifGutsViewManager
+import com.android.systemui.statusbar.notification.row.NotificationGuts
+import com.android.systemui.util.mockito.argumentCaptor
+import com.google.common.truth.Truth.assertThat
+import org.junit.Before
+import org.junit.Test
+import org.junit.runner.RunWith
+import org.mockito.Mock
+import org.mockito.Mockito.mock
+import org.mockito.Mockito.verify
+import org.mockito.MockitoAnnotations.initMocks
+
+@SmallTest
+@RunWith(AndroidTestingRunner::class)
+@RunWithLooper
+class GutsCoordinatorTest : SysuiTestCase() {
+    private lateinit var coordinator: GutsCoordinator
+    private lateinit var notifLifetimeExtender: NotifLifetimeExtender
+    private lateinit var notifGutsViewListener: NotifGutsViewListener
+
+    private lateinit var entry1: NotificationEntry
+    private lateinit var entry2: NotificationEntry
+
+    @Mock private lateinit var notifGutsViewManager: NotifGutsViewManager
+    @Mock private lateinit var pipeline: NotifPipeline
+    @Mock private lateinit var dumpManager: DumpManager
+    @Mock private lateinit var logger: GutsCoordinatorLogger
+    @Mock private lateinit var lifetimeExtenderCallback: OnEndLifetimeExtensionCallback
+
+    @Before
+    fun setUp() {
+        initMocks(this)
+        coordinator = GutsCoordinator(notifGutsViewManager, logger, dumpManager)
+        coordinator.attach(pipeline)
+        notifLifetimeExtender = argumentCaptor<NotifLifetimeExtender>().let {
+            verify(pipeline).addNotificationLifetimeExtender(it.capture())
+            it.value!!
+        }
+        notifGutsViewListener = argumentCaptor<NotifGutsViewListener>().let {
+            verify(notifGutsViewManager).setGutsListener(it.capture())
+            it.value!!
+        }
+        notifLifetimeExtender.setCallback(lifetimeExtenderCallback)
+        entry1 = NotificationEntryBuilder().setId(1).build()
+        entry2 = NotificationEntryBuilder().setId(2).build()
+    }
+
+    @Test
+    fun testSimpleLifetimeExtension() {
+        assertThat(notifLifetimeExtender.shouldExtendLifetime(entry1, 0)).isFalse()
+        notifGutsViewListener.onGutsOpen(entry1, mock(NotificationGuts::class.java))
+        assertThat(notifLifetimeExtender.shouldExtendLifetime(entry1, 0)).isTrue()
+        notifGutsViewListener.onGutsClose(entry1)
+        verify(lifetimeExtenderCallback).onEndLifetimeExtension(notifLifetimeExtender, entry1)
+        assertThat(notifLifetimeExtender.shouldExtendLifetime(entry1, 0)).isFalse()
+    }
+
+    @Test
+    fun testDoubleOpenLifetimeExtension() {
+        assertThat(notifLifetimeExtender.shouldExtendLifetime(entry1, 0)).isFalse()
+        notifGutsViewListener.onGutsOpen(entry1, mock(NotificationGuts::class.java))
+        assertThat(notifLifetimeExtender.shouldExtendLifetime(entry1, 0)).isTrue()
+        notifGutsViewListener.onGutsOpen(entry1, mock(NotificationGuts::class.java))
+        assertThat(notifLifetimeExtender.shouldExtendLifetime(entry1, 0)).isTrue()
+        notifGutsViewListener.onGutsClose(entry1)
+        verify(lifetimeExtenderCallback).onEndLifetimeExtension(notifLifetimeExtender, entry1)
+        assertThat(notifLifetimeExtender.shouldExtendLifetime(entry1, 0)).isFalse()
+    }
+
+    @Test
+    fun testTwoEntryLifetimeExtension() {
+        assertThat(notifLifetimeExtender.shouldExtendLifetime(entry1, 0)).isFalse()
+        assertThat(notifLifetimeExtender.shouldExtendLifetime(entry2, 0)).isFalse()
+        notifGutsViewListener.onGutsOpen(entry1, mock(NotificationGuts::class.java))
+        assertThat(notifLifetimeExtender.shouldExtendLifetime(entry1, 0)).isTrue()
+        assertThat(notifLifetimeExtender.shouldExtendLifetime(entry2, 0)).isFalse()
+        notifGutsViewListener.onGutsOpen(entry2, mock(NotificationGuts::class.java))
+        assertThat(notifLifetimeExtender.shouldExtendLifetime(entry1, 0)).isTrue()
+        assertThat(notifLifetimeExtender.shouldExtendLifetime(entry2, 0)).isTrue()
+        notifGutsViewListener.onGutsClose(entry1)
+        verify(lifetimeExtenderCallback).onEndLifetimeExtension(notifLifetimeExtender, entry1)
+        assertThat(notifLifetimeExtender.shouldExtendLifetime(entry1, 0)).isFalse()
+        assertThat(notifLifetimeExtender.shouldExtendLifetime(entry2, 0)).isTrue()
+        notifGutsViewListener.onGutsClose(entry2)
+        verify(lifetimeExtenderCallback).onEndLifetimeExtension(notifLifetimeExtender, entry2)
+        assertThat(notifLifetimeExtender.shouldExtendLifetime(entry1, 0)).isFalse()
+        assertThat(notifLifetimeExtender.shouldExtendLifetime(entry2, 0)).isFalse()
+    }
+}
diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/collection/coordinator/RankingCoordinatorTest.java b/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/collection/coordinator/RankingCoordinatorTest.java
index 1031d6b..8f241a3 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/collection/coordinator/RankingCoordinatorTest.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/collection/coordinator/RankingCoordinatorTest.java
@@ -21,17 +21,22 @@
 
 import static org.junit.Assert.assertFalse;
 import static org.junit.Assert.assertTrue;
+import static org.mockito.ArgumentMatchers.eq;
 import static org.mockito.Mockito.times;
 import static org.mockito.Mockito.verify;
 import static org.mockito.Mockito.when;
 
+import android.app.Notification;
+import android.service.notification.StatusBarNotification;
 import android.testing.AndroidTestingRunner;
 
+import androidx.annotation.Nullable;
 import androidx.test.filters.SmallTest;
 
 import com.android.systemui.SysuiTestCase;
 import com.android.systemui.plugins.statusbar.StatusBarStateController;
 import com.android.systemui.statusbar.RankingBuilder;
+import com.android.systemui.statusbar.notification.collection.ListEntry;
 import com.android.systemui.statusbar.notification.collection.NotifPipeline;
 import com.android.systemui.statusbar.notification.collection.NotificationEntry;
 import com.android.systemui.statusbar.notification.collection.NotificationEntryBuilder;
@@ -39,6 +44,7 @@
 import com.android.systemui.statusbar.notification.collection.listbuilder.pluggable.NotifSectioner;
 import com.android.systemui.statusbar.notification.collection.provider.HighPriorityProvider;
 import com.android.systemui.statusbar.notification.collection.render.NodeController;
+import com.android.systemui.statusbar.notification.collection.render.SectionHeaderController;
 
 import org.junit.Before;
 import org.junit.Test;
@@ -46,8 +52,11 @@
 import org.mockito.ArgumentCaptor;
 import org.mockito.Captor;
 import org.mockito.Mock;
+import org.mockito.Mockito;
 import org.mockito.MockitoAnnotations;
 
+import java.util.Arrays;
+
 @SmallTest
 @RunWith(AndroidTestingRunner.class)
 public class RankingCoordinatorTest extends SysuiTestCase {
@@ -56,7 +65,8 @@
     @Mock private HighPriorityProvider mHighPriorityProvider;
     @Mock private NotifPipeline mNotifPipeline;
     @Mock private NodeController mAlertingHeaderController;
-    @Mock private NodeController mSilentHeaderController;
+    @Mock private NodeController mSilentNodeController;
+    @Mock private SectionHeaderController mSilentHeaderController;
 
     @Captor private ArgumentCaptor<NotifFilter> mNotifFilterCaptor;
 
@@ -72,7 +82,7 @@
         MockitoAnnotations.initMocks(this);
         RankingCoordinator rankingCoordinator = new RankingCoordinator(
                 mStatusBarStateController, mHighPriorityProvider, mAlertingHeaderController,
-                mSilentHeaderController);
+                mSilentHeaderController, mSilentNodeController);
         mEntry = new NotificationEntryBuilder().build();
 
         rankingCoordinator.attach(mNotifPipeline);
@@ -85,6 +95,28 @@
     }
 
     @Test
+    public void testSilentHeaderClearableChildrenUpdate() {
+        StatusBarNotification sbn = Mockito.mock(StatusBarNotification.class);
+        Mockito.doReturn("key").when(sbn).getKey();
+        Mockito.doReturn(Mockito.mock(Notification.class)).when(sbn).getNotification();
+        NotificationEntry entry = new NotificationEntryBuilder().setSbn(sbn).build();
+        ListEntry listEntry = new ListEntry("key", 0L) {
+            @Nullable
+            @Override
+            public NotificationEntry getRepresentativeEntry() {
+                return entry;
+            }
+        };
+        Mockito.doReturn(true).when(sbn).isClearable();
+        mSilentSectioner.onEntriesUpdated(Arrays.asList(listEntry));
+        verify(mSilentHeaderController).setClearSectionEnabled(eq(true));
+
+        Mockito.doReturn(false).when(sbn).isClearable();
+        mSilentSectioner.onEntriesUpdated(Arrays.asList(listEntry));
+        verify(mSilentHeaderController).setClearSectionEnabled(eq(false));
+    }
+
+    @Test
     public void testUnfilteredState() {
         // GIVEN no suppressed visual effects + app not suspended
         mEntry.setRanking(getRankingForUnfilteredNotif().build());
diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/collection/coordinator/ShadeEventCoordinatorTest.kt b/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/collection/coordinator/ShadeEventCoordinatorTest.kt
new file mode 100644
index 0000000..5915cd7
--- /dev/null
+++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/collection/coordinator/ShadeEventCoordinatorTest.kt
@@ -0,0 +1,106 @@
+/*
+ * Copyright (C) 2021 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package com.android.systemui.statusbar.notification.collection.coordinator
+
+import android.service.notification.NotificationListenerService.REASON_APP_CANCEL
+import android.service.notification.NotificationListenerService.REASON_CANCEL
+import android.testing.AndroidTestingRunner
+import android.testing.TestableLooper.RunWithLooper
+import androidx.test.filters.SmallTest
+import com.android.systemui.SysuiTestCase
+import com.android.systemui.statusbar.notification.collection.NotifPipeline
+import com.android.systemui.statusbar.notification.collection.NotificationEntry
+import com.android.systemui.statusbar.notification.collection.NotificationEntryBuilder
+import com.android.systemui.statusbar.notification.collection.listbuilder.OnBeforeRenderListListener
+import com.android.systemui.statusbar.notification.collection.notifcollection.NotifCollectionListener
+import com.android.systemui.util.mockito.argumentCaptor
+import org.junit.Before
+import org.junit.Test
+import org.junit.runner.RunWith
+import org.mockito.Mock
+import org.mockito.Mockito.never
+import org.mockito.Mockito.verify
+import org.mockito.MockitoAnnotations.initMocks
+
+@SmallTest
+@RunWith(AndroidTestingRunner::class)
+@RunWithLooper
+class ShadeEventCoordinatorTest : SysuiTestCase() {
+    private lateinit var coordinator: ShadeEventCoordinator
+    private lateinit var notifCollectionListener: NotifCollectionListener
+    private lateinit var onBeforeRenderListListener: OnBeforeRenderListListener
+
+    private lateinit var entry1: NotificationEntry
+    private lateinit var entry2: NotificationEntry
+
+    @Mock private lateinit var pipeline: NotifPipeline
+    @Mock private lateinit var logger: ShadeEventCoordinatorLogger
+    @Mock private lateinit var notifRemovedByUserCallback: Runnable
+    @Mock private lateinit var shadeEmptiedCallback: Runnable
+
+    @Before
+    fun setUp() {
+        initMocks(this)
+        coordinator = ShadeEventCoordinator(logger)
+        coordinator.attach(pipeline)
+        notifCollectionListener = argumentCaptor<NotifCollectionListener>().let {
+            verify(pipeline).addCollectionListener(it.capture())
+            it.value!!
+        }
+        onBeforeRenderListListener = argumentCaptor<OnBeforeRenderListListener>().let {
+            verify(pipeline).addOnBeforeRenderListListener(it.capture())
+            it.value!!
+        }
+        coordinator.setNotifRemovedByUserCallback(notifRemovedByUserCallback)
+        coordinator.setShadeEmptiedCallback(shadeEmptiedCallback)
+        entry1 = NotificationEntryBuilder().setId(1).build()
+        entry2 = NotificationEntryBuilder().setId(2).build()
+    }
+
+    @Test
+    fun testUserCancelLastNotification() {
+        notifCollectionListener.onEntryRemoved(entry1, REASON_CANCEL)
+        verify(shadeEmptiedCallback, never()).run()
+        verify(notifRemovedByUserCallback, never()).run()
+        onBeforeRenderListListener.onBeforeRenderList(listOf())
+        verify(shadeEmptiedCallback).run()
+        verify(notifRemovedByUserCallback).run()
+    }
+
+    @Test
+    fun testAppCancelLastNotification() {
+        notifCollectionListener.onEntryRemoved(entry1, REASON_APP_CANCEL)
+        onBeforeRenderListListener.onBeforeRenderList(listOf())
+        verify(shadeEmptiedCallback).run()
+        verify(notifRemovedByUserCallback, never()).run()
+    }
+
+    @Test
+    fun testUserCancelOneOfTwoNotifications() {
+        notifCollectionListener.onEntryRemoved(entry1, REASON_CANCEL)
+        onBeforeRenderListListener.onBeforeRenderList(listOf(entry2))
+        verify(shadeEmptiedCallback, never()).run()
+        verify(notifRemovedByUserCallback).run()
+    }
+
+    @Test
+    fun testAppCancelOneOfTwoNotifications() {
+        notifCollectionListener.onEntryRemoved(entry1, REASON_APP_CANCEL)
+        onBeforeRenderListListener.onBeforeRenderList(listOf(entry2))
+        verify(shadeEmptiedCallback, never()).run()
+        verify(notifRemovedByUserCallback, never()).run()
+    }
+}
diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/collection/render/NodeSpecBuilderTest.kt b/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/collection/render/NodeSpecBuilderTest.kt
index 2e676bb..ed48452 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/collection/render/NodeSpecBuilderTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/collection/render/NodeSpecBuilderTest.kt
@@ -26,6 +26,10 @@
 import com.android.systemui.statusbar.notification.collection.getAttachState
 import com.android.systemui.statusbar.notification.collection.listbuilder.NotifSection
 import com.android.systemui.statusbar.notification.collection.listbuilder.pluggable.NotifSectioner
+import com.android.systemui.statusbar.notification.stack.BUCKET_ALERTING
+import com.android.systemui.statusbar.notification.stack.BUCKET_PEOPLE
+import com.android.systemui.statusbar.notification.stack.BUCKET_SILENT
+import com.android.systemui.statusbar.notification.stack.PriorityBucket
 import com.android.systemui.util.mockito.any
 import org.junit.Before
 import org.junit.Test
@@ -45,11 +49,15 @@
     private var headerController1: NodeController = buildFakeController("header1")
     private var headerController2: NodeController = buildFakeController("header2")
 
-    private val section0 = buildSection(0, headerController0)
-    private val section0NoHeader = buildSection(0, null)
-    private val section1 = buildSection(1, headerController1)
-    private val section1NoHeader = buildSection(1, null)
-    private val section2 = buildSection(2, headerController2)
+    private val section0Bucket = BUCKET_PEOPLE
+    private val section1Bucket = BUCKET_ALERTING
+    private val section2Bucket = BUCKET_SILENT
+
+    private val section0 = buildSection(0, section0Bucket, headerController0)
+    private val section0NoHeader = buildSection(0, section0Bucket, null)
+    private val section1 = buildSection(1, section1Bucket, headerController1)
+    private val section1NoHeader = buildSection(1, section1Bucket, null)
+    private val section2 = buildSection(2, section2Bucket, headerController2)
 
     private val fakeViewBarn = FakeViewBarn()
 
@@ -297,8 +305,12 @@
     return controller
 }
 
-private fun buildSection(index: Int, nodeController: NodeController?): NotifSection {
-    return NotifSection(object : NotifSectioner("Section $index") {
+private fun buildSection(
+    index: Int,
+    @PriorityBucket bucket: Int,
+    nodeController: NodeController?
+): NotifSection {
+    return NotifSection(object : NotifSectioner("Section $index (bucket=$bucket)", bucket) {
 
         override fun isInSection(entry: ListEntry?): Boolean {
             throw NotImplementedError("This should never be called")
diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/stack/NotificationSectionsManagerTest.java b/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/stack/NotificationSectionsManagerTest.java
index c1d2ea8..f11f8c4 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/stack/NotificationSectionsManagerTest.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/stack/NotificationSectionsManagerTest.java
@@ -18,11 +18,11 @@
 
 import static android.view.ViewGroup.LayoutParams.WRAP_CONTENT;
 
-import static com.android.systemui.statusbar.notification.stack.NotificationSectionsManagerKt.BUCKET_ALERTING;
-import static com.android.systemui.statusbar.notification.stack.NotificationSectionsManagerKt.BUCKET_FOREGROUND_SERVICE;
-import static com.android.systemui.statusbar.notification.stack.NotificationSectionsManagerKt.BUCKET_HEADS_UP;
-import static com.android.systemui.statusbar.notification.stack.NotificationSectionsManagerKt.BUCKET_PEOPLE;
-import static com.android.systemui.statusbar.notification.stack.NotificationSectionsManagerKt.BUCKET_SILENT;
+import static com.android.systemui.statusbar.notification.stack.NotificationPriorityBucketKt.BUCKET_ALERTING;
+import static com.android.systemui.statusbar.notification.stack.NotificationPriorityBucketKt.BUCKET_FOREGROUND_SERVICE;
+import static com.android.systemui.statusbar.notification.stack.NotificationPriorityBucketKt.BUCKET_HEADS_UP;
+import static com.android.systemui.statusbar.notification.stack.NotificationPriorityBucketKt.BUCKET_PEOPLE;
+import static com.android.systemui.statusbar.notification.stack.NotificationPriorityBucketKt.BUCKET_SILENT;
 
 import static com.google.common.truth.Truth.assertThat;
 
@@ -64,7 +64,6 @@
 import org.junit.Rule;
 import org.junit.Test;
 import org.junit.runner.RunWith;
-import org.mockito.Answers;
 import org.mockito.Mock;
 import org.mockito.junit.MockitoJUnit;
 import org.mockito.junit.MockitoRule;
@@ -608,7 +607,7 @@
         }
     }
 
-    private View mockNotification(int bucket, boolean isGone) {
+    private View mockNotification(@PriorityBucket int bucket, boolean isGone) {
         ExpandableNotificationRow notifRow =
                 mock(ExpandableNotificationRow.class, RETURNS_DEEP_STUBS);
         when(notifRow.getVisibility()).thenReturn(View.VISIBLE);
diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/KeyguardClockPositionAlgorithmTest.java b/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/KeyguardClockPositionAlgorithmTest.java
index d098e1a..624bedc 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/KeyguardClockPositionAlgorithmTest.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/KeyguardClockPositionAlgorithmTest.java
@@ -58,7 +58,6 @@
     private KeyguardClockPositionAlgorithm.Result mClockPosition;
 
     private MockitoSession mStaticMockSession;
-    private int mNotificationStackHeight;
 
     private float mPanelExpansion;
     private int mKeyguardStatusBarHeaderHeight;
@@ -264,6 +263,30 @@
     }
 
     @Test
+    public void notifPaddingExpandedAlignedWithClockInSplitShadeMode() {
+        givenLockScreen();
+        mIsSplitShade = true;
+        mKeyguardStatusHeight = 200;
+        // WHEN the position algorithm is run
+        positionClock();
+        // THEN the padding DOESN'T adjust for keyguard status height.
+        assertThat(mClockPosition.stackScrollerPaddingExpanded)
+                .isEqualTo(mClockPosition.clockYFullyDozing);
+    }
+
+    @Test
+    public void notifMinPaddingAlignedWithClockInSplitShadeMode() {
+        givenLockScreen();
+        mIsSplitShade = true;
+        mKeyguardStatusHeight = 200;
+        // WHEN the position algorithm is run
+        positionClock();
+        // THEN the padding DOESN'T adjust for keyguard status height.
+        assertThat(mClockPositionAlgorithm.getMinStackScrollerPadding())
+                .isEqualTo(mKeyguardStatusBarHeaderHeight);
+    }
+
+    @Test
     public void notifPositionWithLargeClockOnLockScreen() {
         // GIVEN on lock screen and clock has a nonzero height
         givenLockScreen();
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 35d15af..2d944aa 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
@@ -16,6 +16,7 @@
 
 package com.android.systemui.statusbar.phone;
 
+import static org.junit.Assert.assertTrue;
 import static org.mockito.ArgumentMatchers.any;
 import static org.mockito.ArgumentMatchers.anyBoolean;
 import static org.mockito.ArgumentMatchers.anyFloat;
@@ -97,6 +98,8 @@
     @Mock
     private UnlockedScreenOffAnimationController mUnlockedScreenOffAnimationController;
     @Mock
+    private StatusBarKeyguardViewManager.AlternateAuthInterceptor mAlternateAuthInterceptor;
+    @Mock
     private KeyguardMessageArea mKeyguardMessageArea;
     @Mock
     private Lazy<ShadeController> mShadeController;
@@ -287,6 +290,24 @@
     }
 
     @Test
+    public void testShowing_whenAlternateAuthShowing() {
+        mStatusBarKeyguardViewManager.setAlternateAuthInterceptor(mAlternateAuthInterceptor);
+        when(mBouncer.isShowing()).thenReturn(false);
+        when(mAlternateAuthInterceptor.isShowingAlternateAuthBouncer()).thenReturn(true);
+        assertTrue("Is showing not accurate when alternative auth showing",
+                mStatusBarKeyguardViewManager.isShowing());
+    }
+
+    @Test
+    public void testWillBeShowing_whenAlternateAuthShowing() {
+        mStatusBarKeyguardViewManager.setAlternateAuthInterceptor(mAlternateAuthInterceptor);
+        when(mBouncer.isShowing()).thenReturn(false);
+        when(mAlternateAuthInterceptor.isShowingAlternateAuthBouncer()).thenReturn(true);
+        assertTrue("Is or will be showing not accurate when alternative auth showing",
+                mStatusBarKeyguardViewManager.bouncerIsOrWillBeShowing());
+    }
+
+    @Test
     public void testUpdateResources_delegatesToBouncer() {
         mStatusBarKeyguardViewManager.updateResources();
 
diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/StatusBarNotificationPresenterTest.java b/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/StatusBarNotificationPresenterTest.java
index c80c072..4e6b0a2 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/StatusBarNotificationPresenterTest.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/StatusBarNotificationPresenterTest.java
@@ -37,6 +37,7 @@
 import com.android.systemui.ForegroundServiceNotificationListener;
 import com.android.systemui.InitController;
 import com.android.systemui.SysuiTestCase;
+import com.android.systemui.flags.FeatureFlags;
 import com.android.systemui.plugins.statusbar.StatusBarStateController;
 import com.android.systemui.statusbar.CommandQueue;
 import com.android.systemui.statusbar.KeyguardIndicationController;
@@ -51,6 +52,7 @@
 import com.android.systemui.statusbar.notification.NotificationEntryManager;
 import com.android.systemui.statusbar.notification.collection.NotificationEntry;
 import com.android.systemui.statusbar.notification.collection.NotificationEntryBuilder;
+import com.android.systemui.statusbar.notification.collection.render.NotifShadeEventSource;
 import com.android.systemui.statusbar.notification.interruption.NotificationInterruptStateProvider;
 import com.android.systemui.statusbar.notification.interruption.NotificationInterruptSuppressor;
 import com.android.systemui.statusbar.notification.row.ActivatableNotificationView;
@@ -109,12 +111,15 @@
                 mock(DozeScrimController.class), mock(ScrimController.class),
                 mock(NotificationShadeWindowController.class), mock(DynamicPrivacyController.class),
                 mock(KeyguardStateController.class),
-                mock(KeyguardIndicationController.class), mStatusBar,
+                mock(KeyguardIndicationController.class),
+                mock(FeatureFlags.class),
+                mStatusBar,
                 mock(ShadeControllerImpl.class), mock(LockscreenShadeTransitionController.class),
                 mCommandQueue,
                 mock(NotificationViewHierarchyManager.class),
                 mock(NotificationLockscreenUserManager.class),
                 mock(SysuiStatusBarStateController.class),
+                mock(NotifShadeEventSource.class),
                 mock(NotificationEntryManager.class),
                 mock(NotificationMediaManager.class),
                 mock(NotificationGutsManager.class),
diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/StatusBarTest.java b/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/StatusBarTest.java
index 943d3c7..f14b126 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/StatusBarTest.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/StatusBarTest.java
@@ -96,7 +96,7 @@
 import com.android.systemui.plugins.PluginDependencyProvider;
 import com.android.systemui.plugins.statusbar.StatusBarStateController;
 import com.android.systemui.recents.ScreenPinningRequest;
-import com.android.systemui.settings.brightness.BrightnessSlider;
+import com.android.systemui.settings.brightness.BrightnessSliderController;
 import com.android.systemui.shared.plugins.PluginManager;
 import com.android.systemui.statusbar.CommandQueue;
 import com.android.systemui.statusbar.KeyguardIndicationController;
@@ -121,6 +121,7 @@
 import com.android.systemui.statusbar.notification.collection.NotificationEntry;
 import com.android.systemui.statusbar.notification.collection.NotificationEntryBuilder;
 import com.android.systemui.statusbar.notification.collection.legacy.VisualStabilityManager;
+import com.android.systemui.statusbar.notification.collection.render.NotifShadeEventSource;
 import com.android.systemui.statusbar.notification.init.NotificationsController;
 import com.android.systemui.statusbar.notification.interruption.BypassHeadsUpNotifier;
 import com.android.systemui.statusbar.notification.interruption.NotificationInterruptStateProviderImpl;
@@ -210,6 +211,7 @@
     @Mock private NotificationShadeWindowView mNotificationShadeWindowView;
     @Mock private BroadcastDispatcher mBroadcastDispatcher;
     @Mock private AssistManager mAssistManager;
+    @Mock private NotifShadeEventSource mNotifShadeEventSource;
     @Mock private NotificationEntryManager mNotificationEntryManager;
     @Mock private NotificationGutsManager mNotificationGutsManager;
     @Mock private NotificationMediaManager mNotificationMediaManager;
@@ -257,7 +259,7 @@
     @Mock private PhoneStatusBarPolicy mPhoneStatusBarPolicy;
     @Mock private DemoModeController mDemoModeController;
     @Mock private Lazy<NotificationShadeDepthController> mNotificationShadeDepthControllerLazy;
-    @Mock private BrightnessSlider.Factory mBrightnessSliderFactory;
+    @Mock private BrightnessSliderController.Factory mBrightnessSliderFactory;
     @Mock private UnfoldTransitionConfig mUnfoldTransitionConfig;
     @Mock private Lazy<UnfoldLightRevealOverlayAnimation> mUnfoldLightRevealOverlayAnimationLazy;
     @Mock private Lazy<NaturalRotationUnfoldProgressProvider> mNaturalRotationProgressProvider;
@@ -377,6 +379,7 @@
                 new FalsingManagerFake(),
                 new FalsingCollectorFake(),
                 mBroadcastDispatcher,
+                mNotifShadeEventSource,
                 mNotificationEntryManager,
                 mNotificationGutsManager,
                 notificationLogger,
diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/UnlockedScreenOffAnimationControllerTest.kt b/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/UnlockedScreenOffAnimationControllerTest.kt
new file mode 100644
index 0000000..a8a33da
--- /dev/null
+++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/UnlockedScreenOffAnimationControllerTest.kt
@@ -0,0 +1,92 @@
+/*
+ * Copyright (C) 2021 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.systemui.statusbar.phone
+
+import android.animation.Animator
+import android.testing.AndroidTestingRunner
+import android.testing.TestableLooper.RunWithLooper
+import android.view.View
+import androidx.test.filters.SmallTest
+import com.android.systemui.SysuiTestCase
+import com.android.systemui.keyguard.KeyguardViewMediator
+import com.android.systemui.keyguard.WakefulnessLifecycle
+import com.android.systemui.statusbar.LightRevealScrim
+import com.android.systemui.statusbar.StatusBarStateControllerImpl
+import com.android.systemui.statusbar.policy.KeyguardStateController
+import com.android.systemui.util.settings.GlobalSettings
+import org.junit.Before
+import org.junit.Test
+import org.junit.runner.RunWith
+import org.mockito.ArgumentCaptor
+import org.mockito.Mock
+import org.mockito.Mockito
+import org.mockito.Mockito.spy
+import org.mockito.MockitoAnnotations
+
+@SmallTest
+@RunWith(AndroidTestingRunner::class)
+@RunWithLooper(setAsMainLooper = true)
+class UnlockedScreenOffAnimationControllerTest : SysuiTestCase() {
+
+    private lateinit var controller: UnlockedScreenOffAnimationController
+    @Mock
+    private lateinit var keyguardViewMediator: KeyguardViewMediator
+    @Mock
+    private lateinit var dozeParameters: DozeParameters
+    @Mock
+    private lateinit var keyguardStateController: KeyguardStateController
+    @Mock
+    private lateinit var globalSettings: GlobalSettings
+    @Mock
+    private lateinit var statusbar: StatusBar
+    @Mock
+    private lateinit var lightRevealScrim: LightRevealScrim
+    @Mock
+    private lateinit var wakefulnessLifecycle: WakefulnessLifecycle
+    @Mock
+    private lateinit var statusBarStateController: StatusBarStateControllerImpl
+
+    @Before
+    fun setUp() {
+        MockitoAnnotations.initMocks(this)
+
+        controller = UnlockedScreenOffAnimationController(
+                context,
+                wakefulnessLifecycle,
+                statusBarStateController,
+                dagger.Lazy<KeyguardViewMediator> { keyguardViewMediator },
+                keyguardStateController,
+                dagger.Lazy<DozeParameters> { dozeParameters },
+                globalSettings
+        )
+        controller.initialize(statusbar, lightRevealScrim)
+    }
+
+    @Test
+    fun testAnimClearsEndListener() {
+        val keyguardView = View(context)
+        val animator = spy(keyguardView.animate())
+        val keyguardSpy = spy(keyguardView)
+        Mockito.`when`(keyguardSpy.animate()).thenReturn(animator)
+        val listener = ArgumentCaptor.forClass(Animator.AnimatorListener::class.java)
+        controller.animateInKeyguard(keyguardSpy, Runnable {})
+        Mockito.verify(animator).setListener(listener.capture())
+        // Verify that the listener is cleared when it ends
+        listener.value.onAnimationEnd(null)
+        Mockito.verify(animator).setListener(null)
+    }
+}
\ No newline at end of file
diff --git a/packages/SystemUI/tests/src/com/android/systemui/util/ListenerSetTest.kt b/packages/SystemUI/tests/src/com/android/systemui/util/ListenerSetTest.kt
new file mode 100644
index 0000000..2662da2
--- /dev/null
+++ b/packages/SystemUI/tests/src/com/android/systemui/util/ListenerSetTest.kt
@@ -0,0 +1,135 @@
+/*
+ * Copyright (C) 2021 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.systemui.util
+
+import android.test.suitebuilder.annotation.SmallTest
+import androidx.test.runner.AndroidJUnit4
+import com.android.systemui.SysuiTestCase
+import com.google.common.truth.Truth.assertThat
+import org.junit.Before
+import org.junit.Test
+import org.junit.runner.RunWith
+
+@SmallTest
+@RunWith(AndroidJUnit4::class)
+class ListenerSetTest : SysuiTestCase() {
+
+    var runnableSet: ListenerSet<Runnable> = ListenerSet()
+
+    @Before
+    fun setup() {
+        runnableSet = ListenerSet()
+    }
+
+    @Test
+    fun addIfAbsent_doesNotDoubleAdd() {
+        // setup & preconditions
+        val runnable1 = Runnable { }
+        val runnable2 = Runnable { }
+        assertThat(runnableSet.toList()).isEmpty()
+
+        // Test that an element can be added
+        assertThat(runnableSet.addIfAbsent(runnable1)).isTrue()
+        assertThat(runnableSet.toList()).containsExactly(runnable1)
+
+        // Test that a second element can be added
+        assertThat(runnableSet.addIfAbsent(runnable2)).isTrue()
+        assertThat(runnableSet.toList()).containsExactly(runnable1, runnable2)
+
+        // Test that re-adding the first element does nothing and returns false
+        assertThat(runnableSet.addIfAbsent(runnable1)).isFalse()
+        assertThat(runnableSet.toList()).containsExactly(runnable1, runnable2)
+    }
+
+    @Test
+    fun remove_removesListener() {
+        // setup and preconditions
+        val runnable1 = Runnable { }
+        val runnable2 = Runnable { }
+        assertThat(runnableSet.toList()).isEmpty()
+        runnableSet.addIfAbsent(runnable1)
+        runnableSet.addIfAbsent(runnable2)
+        assertThat(runnableSet.toList()).containsExactly(runnable1, runnable2)
+
+        // Test that removing the first runnable only removes that one runnable
+        assertThat(runnableSet.remove(runnable1)).isTrue()
+        assertThat(runnableSet.toList()).containsExactly(runnable2)
+
+        // Test that removing a non-present runnable does not error
+        assertThat(runnableSet.remove(runnable1)).isFalse()
+        assertThat(runnableSet.toList()).containsExactly(runnable2)
+
+        // Test that removing the other runnable succeeds
+        assertThat(runnableSet.remove(runnable2)).isTrue()
+        assertThat(runnableSet.toList()).isEmpty()
+    }
+
+    @Test
+    fun remove_isReentrantSafe() {
+        // Setup and preconditions
+        val runnablesCalled = mutableListOf<Int>()
+        // runnable1 is configured to remove itself
+        val runnable1 = object : Runnable {
+            override fun run() {
+                runnableSet.remove(this)
+                runnablesCalled.add(1)
+            }
+        }
+        val runnable2 = Runnable {
+            runnablesCalled.add(2)
+        }
+        assertThat(runnableSet.toList()).isEmpty()
+        runnableSet.addIfAbsent(runnable1)
+        runnableSet.addIfAbsent(runnable2)
+        assertThat(runnableSet.toList()).containsExactly(runnable1, runnable2)
+
+        // Test that both runnables are called and 1 was removed
+        for (runnable in runnableSet) {
+            runnable.run()
+        }
+        assertThat(runnablesCalled).containsExactly(1, 2)
+        assertThat(runnableSet.toList()).containsExactly(runnable2)
+    }
+
+    @Test
+    fun addIfAbsent_isReentrantSafe() {
+        // Setup and preconditions
+        val runnablesCalled = mutableListOf<Int>()
+        val runnable99 = Runnable {
+            runnablesCalled.add(99)
+        }
+        // runnable1 is configured to add runnable99
+        val runnable1 = Runnable {
+            runnableSet.addIfAbsent(runnable99)
+            runnablesCalled.add(1)
+        }
+        val runnable2 = Runnable {
+            runnablesCalled.add(2)
+        }
+        assertThat(runnableSet.toList()).isEmpty()
+        runnableSet.addIfAbsent(runnable1)
+        runnableSet.addIfAbsent(runnable2)
+        assertThat(runnableSet.toList()).containsExactly(runnable1, runnable2)
+
+        // Test that both original runnables are called and 99 was added but not called
+        for (runnable in runnableSet) {
+            runnable.run()
+        }
+        assertThat(runnablesCalled).containsExactly(1, 2)
+        assertThat(runnableSet.toList()).containsExactly(runnable1, runnable2, runnable99)
+    }
+}
\ No newline at end of file
diff --git a/packages/SystemUI/tests/src/com/android/systemui/util/mockito/KotlinMockitoHelpers.kt b/packages/SystemUI/tests/src/com/android/systemui/util/mockito/KotlinMockitoHelpers.kt
index bff99bf..483dc9f 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/util/mockito/KotlinMockitoHelpers.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/util/mockito/KotlinMockitoHelpers.kt
@@ -58,3 +58,24 @@
  */
 inline fun <reified T : Any> argumentCaptor(): ArgumentCaptor<T> =
         ArgumentCaptor.forClass(T::class.java)
+
+/**
+ * Helper function for creating new mocks, without the need to pass in a [Class] instance.
+ *
+ * Generic T is nullable because implicitly bounded by Any?.
+ */
+inline fun <reified T : Any> mock(): T = Mockito.mock(T::class.java)
+
+/**
+ * Helper function for creating and using a single-use ArgumentCaptor in kotlin.
+ *
+ *    val captor = argumentCaptor<Foo>()
+ *    verify(...).someMethod(captor.capture())
+ *    val captured = captor.value
+ *
+ * becomes:
+ *
+ *    val captured = withArgCaptor<Foo> { verify(...).someMethod(capture()) }
+ */
+inline fun <reified T : Any> withArgCaptor(block: ArgumentCaptor<T>.() -> Unit): T =
+        argumentCaptor<T>().apply { block() }.value
\ No newline at end of file
diff --git a/services/core/java/com/android/server/biometrics/sensors/AuthenticationClient.java b/services/core/java/com/android/server/biometrics/sensors/AuthenticationClient.java
index 61b8ded..031f6ee 100644
--- a/services/core/java/com/android/server/biometrics/sensors/AuthenticationClient.java
+++ b/services/core/java/com/android/server/biometrics/sensors/AuthenticationClient.java
@@ -168,10 +168,6 @@
         return Utils.isKeyguard(getContext(), getOwnerString());
     }
 
-    private boolean isSettings() {
-        return Utils.isSettings(getContext(), getOwnerString());
-    }
-
     @Override
     protected boolean isCryptoOperation() {
         return mOperationId != 0;
@@ -503,8 +499,6 @@
     protected int getShowOverlayReason() {
         if (isKeyguard()) {
             return BiometricOverlayConstants.REASON_AUTH_KEYGUARD;
-        } else if (isSettings()) {
-            return BiometricOverlayConstants.REASON_AUTH_SETTINGS;
         } else if (isBiometricPrompt()) {
             return BiometricOverlayConstants.REASON_AUTH_BP;
         } else {
diff --git a/services/core/java/com/android/server/biometrics/sensors/fingerprint/FingerprintStateCallback.java b/services/core/java/com/android/server/biometrics/sensors/fingerprint/FingerprintStateCallback.java
index 0050a89..be0e6ed 100644
--- a/services/core/java/com/android/server/biometrics/sensors/fingerprint/FingerprintStateCallback.java
+++ b/services/core/java/com/android/server/biometrics/sensors/fingerprint/FingerprintStateCallback.java
@@ -23,7 +23,6 @@
 import static android.hardware.fingerprint.FingerprintStateListener.STATE_KEYGUARD_AUTH;
 
 import android.annotation.NonNull;
-import android.content.Context;
 import android.hardware.fingerprint.FingerprintStateListener;
 import android.hardware.fingerprint.IFingerprintStateListener;
 import android.os.RemoteException;
@@ -34,8 +33,6 @@
 import com.android.server.biometrics.sensors.BaseClientMonitor;
 import com.android.server.biometrics.sensors.EnrollClient;
 import com.android.server.biometrics.sensors.EnrollmentModifier;
-import com.android.server.biometrics.sensors.RemovalConsumer;
-import com.android.server.biometrics.sensors.fingerprint.hidl.FingerprintEnrollClient;
 
 import java.util.concurrent.CopyOnWriteArrayList;
 
@@ -70,7 +67,7 @@
             } else {
                 mFingerprintState = STATE_AUTH_OTHER;
             }
-        } else if (client instanceof FingerprintEnrollClient) {
+        } else if (client instanceof EnrollClient) {
             mFingerprintState = STATE_ENROLLING;
         } else {
             Slog.w(FingerprintService.TAG,
@@ -143,6 +140,7 @@
     /**
      * Enables clients to register a FingerprintStateListener. Used by FingerprintService to forward
      * updates in fingerprint sensor state to the SideFpNsEventHandler
+     *
      * @param listener
      */
     public void registerFingerprintStateListener(@NonNull IFingerprintStateListener listener) {
diff --git a/services/core/java/com/android/server/wm/ActivityRecord.java b/services/core/java/com/android/server/wm/ActivityRecord.java
index 84e62d4..8ace5e4 100644
--- a/services/core/java/com/android/server/wm/ActivityRecord.java
+++ b/services/core/java/com/android/server/wm/ActivityRecord.java
@@ -3081,9 +3081,6 @@
 
         mAtmService.deferWindowLayout();
         try {
-            final Transition newTransition = (!mTransitionController.isCollecting()
-                    && mTransitionController.getTransitionPlayer() != null)
-                    ? mTransitionController.createTransition(TRANSIT_CLOSE) : null;
             mTaskSupervisor.mNoHistoryActivities.remove(this);
             makeFinishingLocked();
             // Make a local reference to its task since this.task could be set to null once this
@@ -3115,10 +3112,7 @@
 
             final boolean endTask = task.getTopNonFinishingActivity() == null
                     && !task.isClearingToReuseTask();
-            if (newTransition != null) {
-                mTransitionController.requestStartTransition(newTransition,
-                        endTask ? task : null, null /* remote */);
-            }
+            mTransitionController.requestCloseTransitionIfNeeded(endTask ? task : this);
             if (isState(RESUMED)) {
                 if (endTask) {
                     mAtmService.getTaskChangeNotificationController().notifyTaskRemovalStarted(
@@ -3544,13 +3538,6 @@
         if (stopped) {
             abortAndClearOptionsAnimation();
         }
-        if (mTransitionController.isCollecting()) {
-            // We don't want the finishing to change the transition ready state since there will not
-            // be corresponding setReady for finishing.
-            mTransitionController.collectExistenceChange(this);
-        } else {
-            mTransitionController.requestTransitionIfNeeded(TRANSIT_CLOSE, this);
-        }
     }
 
     /**
@@ -3732,6 +3719,7 @@
             // to the restarted activity.
             nowVisible = mVisibleRequested;
         }
+        mTransitionController.requestCloseTransitionIfNeeded(this);
         cleanUp(true /* cleanServices */, true /* setState */);
         if (remove) {
             if (mStartingData != null && mVisible && task != null) {
diff --git a/services/core/java/com/android/server/wm/ActivityTaskSupervisor.java b/services/core/java/com/android/server/wm/ActivityTaskSupervisor.java
index ba30592..7c5f059 100644
--- a/services/core/java/com/android/server/wm/ActivityTaskSupervisor.java
+++ b/services/core/java/com/android/server/wm/ActivityTaskSupervisor.java
@@ -44,7 +44,6 @@
 import static android.os.Process.INVALID_UID;
 import static android.os.Trace.TRACE_TAG_WINDOW_MANAGER;
 import static android.view.Display.DEFAULT_DISPLAY;
-import static android.view.WindowManager.TRANSIT_CLOSE;
 import static android.view.WindowManager.TRANSIT_TO_FRONT;
 
 import static com.android.internal.protolog.ProtoLogGroup.WM_DEBUG_STATES;
@@ -1562,19 +1561,7 @@
             // Prevent recursion.
             return;
         }
-        if (task.isVisible()) {
-            if (task.mTransitionController.isCollecting()) {
-                // We don't want the finishing to change the transition ready state since there will
-                // not be corresponding setReady for finishing.
-                task.mTransitionController.collectExistenceChange(task);
-            } else {
-                task.mTransitionController.requestTransitionIfNeeded(TRANSIT_CLOSE, task);
-            }
-        } else {
-            // Removing a non-visible task doesn't require a transition, but if there is one
-            // collecting, this should be a member just in case.
-            task.mTransitionController.collect(task);
-        }
+        task.mTransitionController.requestCloseTransitionIfNeeded(task);
         task.mInRemoveTask = true;
         try {
             task.performClearTask(reason);
diff --git a/services/core/java/com/android/server/wm/TaskDisplayArea.java b/services/core/java/com/android/server/wm/TaskDisplayArea.java
index 275ed0e..f5e7967 100644
--- a/services/core/java/com/android/server/wm/TaskDisplayArea.java
+++ b/services/core/java/com/android/server/wm/TaskDisplayArea.java
@@ -864,12 +864,9 @@
         int layer = 0;
         // Place root home tasks to the bottom.
         layer = adjustRootTaskLayer(t, mTmpHomeChildren, layer);
-        adjustRootTaskLayer(t, mTmpNormalChildren, layer);
-
-        // Always on top tasks layer should higher than split divider layer so set it as start.
-        t.setLayer(mSplitScreenDividerAnchor, SPLIT_DIVIDER_LAYER);
-        layer = SPLIT_DIVIDER_LAYER + 1;
+        layer = adjustRootTaskLayer(t, mTmpNormalChildren, layer);
         adjustRootTaskLayer(t, mTmpAlwaysOnTopChildren, layer);
+        t.setLayer(mSplitScreenDividerAnchor, SPLIT_DIVIDER_LAYER);
     }
 
     /**
@@ -884,19 +881,33 @@
             ArrayList<WindowContainer> children, int startLayer) {
         mTmpNeedsZBoostIndexes.clear();
         final int childCount = children.size();
+        boolean hasAdjacentTask = false;
         for (int i = 0; i < childCount; i++) {
             final WindowContainer child = children.get(i);
             final TaskDisplayArea childTda = child.asTaskDisplayArea();
-
-            boolean childNeedsZBoost = childTda != null
+            final boolean childNeedsZBoost = childTda != null
                     ? childTda.childrenNeedZBoost()
                     : child.needsZBoost();
 
-            if (!childNeedsZBoost) {
-                child.assignLayer(t, startLayer++);
-            } else {
+            if (childNeedsZBoost) {
                 mTmpNeedsZBoostIndexes.add(i);
+                continue;
             }
+
+            final Task childTask = child.asTask();
+            final boolean inAdjacentTask = childTask != null
+                    && child.inMultiWindowMode()
+                    && childTask.getRootTask().getAdjacentTaskFragment() != null;
+
+            if (inAdjacentTask || child.inSplitScreenWindowingMode()) {
+                hasAdjacentTask = true;
+            } else if (hasAdjacentTask && startLayer < SPLIT_DIVIDER_LAYER) {
+                // Task on top of adjacent tasks should be higher than split divider layer so
+                // set it as start.
+                startLayer = SPLIT_DIVIDER_LAYER + 1;
+            }
+
+            child.assignLayer(t, startLayer++);
         }
 
         final int zBoostSize = mTmpNeedsZBoostIndexes.size();
diff --git a/services/core/java/com/android/server/wm/Transition.java b/services/core/java/com/android/server/wm/Transition.java
index 5d82553..4db8ef4 100644
--- a/services/core/java/com/android/server/wm/Transition.java
+++ b/services/core/java/com/android/server/wm/Transition.java
@@ -338,6 +338,11 @@
         applyReady();
     }
 
+    @VisibleForTesting
+    boolean allReady() {
+        return mReadyTracker.allReady();
+    }
+
     /**
      * Build a transaction that "resets" all the re-parenting and layer changes. This is
      * intended to be applied at the end of the transition but before the finish callback. This
diff --git a/services/core/java/com/android/server/wm/TransitionController.java b/services/core/java/com/android/server/wm/TransitionController.java
index 91825cc..a21e4f2 100644
--- a/services/core/java/com/android/server/wm/TransitionController.java
+++ b/services/core/java/com/android/server/wm/TransitionController.java
@@ -35,7 +35,6 @@
 import android.util.Slog;
 import android.util.proto.ProtoOutputStream;
 import android.view.WindowManager;
-import android.window.IRemoteTransition;
 import android.window.ITransitionMetricsReporter;
 import android.window.ITransitionPlayer;
 import android.window.RemoteTransition;
@@ -226,7 +225,7 @@
     }
 
     /**
-     * @see #requestTransitionIfNeeded(int, int, WindowContainer, IRemoteTransition)
+     * @see #requestTransitionIfNeeded(int, int, WindowContainer, WindowContainer, RemoteTransition)
      */
     @Nullable
     Transition requestTransitionIfNeeded(@WindowManager.TransitionType int type,
@@ -235,7 +234,7 @@
     }
 
     /**
-     * @see #requestTransitionIfNeeded(int, int, WindowContainer, IRemoteTransition)
+     * @see #requestTransitionIfNeeded(int, int, WindowContainer, WindowContainer, RemoteTransition)
      */
     @Nullable
     Transition requestTransitionIfNeeded(@WindowManager.TransitionType int type,
@@ -306,6 +305,22 @@
         return transition;
     }
 
+    /** Requests transition for a window container which will be removed or invisible. */
+    void requestCloseTransitionIfNeeded(@NonNull WindowContainer<?> wc) {
+        if (mTransitionPlayer == null) return;
+        if (wc.isVisibleRequested()) {
+            if (!isCollecting()) {
+                requestStartTransition(createTransition(TRANSIT_CLOSE, 0 /* flags */),
+                        wc.asTask(), null /* remoteTransition */);
+            }
+            collectExistenceChange(wc);
+        } else {
+            // Removing a non-visible window doesn't require a transition, but if there is one
+            // collecting, this should be a member just in case.
+            collect(wc);
+        }
+    }
+
     /** @see Transition#collect */
     void collect(@NonNull WindowContainer wc) {
         if (mCollectingTransition == null) return;
diff --git a/services/core/java/com/android/server/wm/WindowContainer.java b/services/core/java/com/android/server/wm/WindowContainer.java
index 51ecce0..a68b09e 100644
--- a/services/core/java/com/android/server/wm/WindowContainer.java
+++ b/services/core/java/com/android/server/wm/WindowContainer.java
@@ -32,10 +32,6 @@
 import static android.view.SurfaceControl.Transaction;
 import static android.view.WindowManager.LayoutParams.INVALID_WINDOW_TYPE;
 import static android.view.WindowManager.TRANSIT_CHANGE;
-import static android.view.WindowManager.TRANSIT_OLD_TASK_CLOSE;
-import static android.view.WindowManager.TRANSIT_OLD_TASK_OPEN;
-import static android.view.WindowManager.TRANSIT_OLD_TASK_TO_BACK;
-import static android.view.WindowManager.TRANSIT_OLD_TASK_TO_FRONT;
 
 import static com.android.internal.protolog.ProtoLogGroup.WM_DEBUG_APP_TRANSITIONS;
 import static com.android.internal.protolog.ProtoLogGroup.WM_DEBUG_APP_TRANSITIONS_ANIM;
@@ -43,6 +39,7 @@
 import static com.android.internal.protolog.ProtoLogGroup.WM_DEBUG_SYNC_ENGINE;
 import static com.android.server.policy.WindowManagerPolicy.FINISH_LAYOUT_REDO_WALLPAPER;
 import static com.android.server.wm.AppTransition.MAX_APP_TRANSITION_DURATION;
+import static com.android.server.wm.AppTransition.isTaskTransitOld;
 import static com.android.server.wm.DisplayContent.IME_TARGET_LAYERING;
 import static com.android.server.wm.IdentifierProto.HASH_CODE;
 import static com.android.server.wm.IdentifierProto.TITLE;
@@ -70,7 +67,6 @@
 import android.annotation.IntDef;
 import android.annotation.NonNull;
 import android.annotation.Nullable;
-import android.app.ActivityThread;
 import android.content.Context;
 import android.content.pm.ActivityInfo;
 import android.content.res.Configuration;
@@ -91,6 +87,7 @@
 import android.view.SurfaceControl;
 import android.view.SurfaceControl.Builder;
 import android.view.SurfaceSession;
+import android.view.TaskTransitionSpec;
 import android.view.WindowManager;
 import android.view.WindowManager.TransitionOldType;
 import android.view.animation.Animation;
@@ -2825,33 +2822,15 @@
                 mSurfaceAnimationSources.addAll(sources);
             }
 
-            TaskDisplayArea taskDisplayArea = getTaskDisplayArea();
-            boolean isSettingBackgroundColor = taskDisplayArea != null
-                    && isTransitionWithBackgroundColor(transit);
+            AnimationRunnerBuilder animationRunnerBuilder = new AnimationRunnerBuilder();
 
-            if (isSettingBackgroundColor) {
-                Context uiContext = ActivityThread.currentActivityThread().getSystemUiContext();
-                @ColorInt int backgroundColor = uiContext.getColor(R.color.overview_background);
-
-                taskDisplayArea.setBackgroundColor(backgroundColor);
+            if (isTaskTransitOld(transit)) {
+                animationRunnerBuilder.setTaskBackgroundColor(getTaskAnimationBackgroundColor());
             }
 
-            // Atomic counter to make sure the clearColor callback is only called one.
-            // It will be called twice in the case we cancel the animation without restart
-            // (in that case it will run as the cancel and finished callbacks).
-            final AtomicInteger callbackCounter = new AtomicInteger(0);
-            final Runnable clearBackgroundColorHandler = () -> {
-                if (callbackCounter.getAndIncrement() == 0) {
-                    taskDisplayArea.clearBackgroundColor();
-                }
-            };
-
-            final Runnable cleanUpCallback = isSettingBackgroundColor
-                    ? clearBackgroundColorHandler : () -> {};
-
-            startAnimation(getPendingTransaction(), adapter, !isVisible(),
-                    ANIMATION_TYPE_APP_TRANSITION, (type, anim) -> cleanUpCallback.run(),
-                    cleanUpCallback, thumbnailAdapter);
+            animationRunnerBuilder.build()
+                    .startAnimation(getPendingTransaction(), adapter, !isVisible(),
+                            ANIMATION_TYPE_APP_TRANSITION, thumbnailAdapter);
 
             if (adapter.getShowWallpaper()) {
                 getDisplayContent().pendingLayoutChanges |= FINISH_LAYOUT_REDO_WALLPAPER;
@@ -2859,11 +2838,16 @@
         }
     }
 
-    private boolean isTransitionWithBackgroundColor(@TransitionOldType int transit) {
-        return transit == TRANSIT_OLD_TASK_OPEN
-                || transit == TRANSIT_OLD_TASK_CLOSE
-                || transit == TRANSIT_OLD_TASK_TO_FRONT
-                || transit == TRANSIT_OLD_TASK_TO_BACK;
+    private @ColorInt int getTaskAnimationBackgroundColor() {
+        Context uiContext = mDisplayContent.getDisplayPolicy().getSystemUiContext();
+        TaskTransitionSpec customSpec = mWmService.mTaskTransitionSpec;
+        @ColorInt int defaultFallbackColor = uiContext.getColor(R.color.overview_background);
+
+        if (customSpec != null && customSpec.backgroundColor != 0) {
+            return customSpec.backgroundColor;
+        }
+
+        return defaultFallbackColor;
     }
 
     final SurfaceAnimationRunner getSurfaceAnimationRunner() {
@@ -3551,4 +3535,53 @@
         getPendingTransaction().setSecure(mSurfaceControl, !canScreenshot);
         return true;
     }
+
+    private class AnimationRunnerBuilder {
+        /**
+         * Runs when the surface stops animating
+         */
+        private final List<Runnable> mOnAnimationFinished = new LinkedList<>();
+        /**
+         * Runs when the animation is cancelled but the surface is still animating
+         */
+        private final List<Runnable> mOnAnimationCancelled = new LinkedList<>();
+
+        private void setTaskBackgroundColor(@ColorInt int backgroundColor) {
+            TaskDisplayArea taskDisplayArea = getTaskDisplayArea();
+
+            if (taskDisplayArea != null) {
+                taskDisplayArea.setBackgroundColor(backgroundColor);
+
+                // Atomic counter to make sure the clearColor callback is only called one.
+                // It will be called twice in the case we cancel the animation without restart
+                // (in that case it will run as the cancel and finished callbacks).
+                final AtomicInteger callbackCounter = new AtomicInteger(0);
+                final Runnable clearBackgroundColorHandler = () -> {
+                    if (callbackCounter.getAndIncrement() == 0) {
+                        taskDisplayArea.clearBackgroundColor();
+                    }
+                };
+
+                // We want to make sure this is called both when the surface stops animating and
+                // also when an animation is cancelled (i.e. animation is replaced by another
+                // animation but and so the surface is still animating)
+                mOnAnimationFinished.add(clearBackgroundColorHandler);
+                mOnAnimationCancelled.add(clearBackgroundColorHandler);
+            }
+        }
+
+        private IAnimationStarter build() {
+            return (Transaction t, AnimationAdapter adapter, boolean hidden,
+                    @AnimationType int type, @Nullable AnimationAdapter snapshotAnim) -> {
+                startAnimation(getPendingTransaction(), adapter, !isVisible(), type,
+                        (animType, anim) -> mOnAnimationFinished.forEach(Runnable::run),
+                        () -> mOnAnimationCancelled.forEach(Runnable::run), snapshotAnim);
+            };
+        }
+    }
+
+    private interface IAnimationStarter {
+        void startAnimation(Transaction t, AnimationAdapter anim, boolean hidden,
+                @AnimationType int type, @Nullable AnimationAdapter snapshotAnim);
+    }
 }
diff --git a/services/tests/wmtests/src/com/android/server/wm/ActivityRecordTests.java b/services/tests/wmtests/src/com/android/server/wm/ActivityRecordTests.java
index 44cff33..128bfa8 100644
--- a/services/tests/wmtests/src/com/android/server/wm/ActivityRecordTests.java
+++ b/services/tests/wmtests/src/com/android/server/wm/ActivityRecordTests.java
@@ -166,6 +166,8 @@
     @Before
     public void setUp() throws Exception {
         setBooted(mAtm);
+        // Because the booted state is set, avoid starting real home if there is no task.
+        doReturn(false).when(mRootWindowContainer).resumeHomeActivity(any(), anyString(), any());
     }
 
     private TestStartingWindowOrganizer registerTestStartingWindowOrganizer() {
@@ -1083,6 +1085,7 @@
      */
     @Test
     public void testFinishActivityIfPossible_nonVisibleNoAppTransition() {
+        registerTestTransitionPlayer();
         final ActivityRecord activity = createActivityWithTask();
         // Put an activity on top of test activity to make it invisible and prevent us from
         // accidentally resuming the topmost one again.
@@ -1093,6 +1096,7 @@
         activity.finishIfPossible("test", false /* oomAdj */);
 
         verify(activity.mDisplayContent, never()).prepareAppTransition(eq(TRANSIT_CLOSE));
+        assertFalse(activity.inTransition());
     }
 
     /**
@@ -1101,11 +1105,7 @@
      */
     @Test
     public void testFinishActivityIfPossible_lastInTaskRequestsTransitionWithTrigger() {
-        // Set-up mock shell transitions
-        final TestTransitionPlayer testPlayer = new TestTransitionPlayer(
-                mAtm.getTransitionController(), mAtm.mWindowOrganizerController);
-        mAtm.getTransitionController().registerTransitionPlayer(testPlayer);
-
+        final TestTransitionPlayer testPlayer = registerTestTransitionPlayer();
         final ActivityRecord activity = createActivityWithTask();
         activity.finishing = false;
         activity.mVisibleRequested = true;
@@ -1117,6 +1117,29 @@
     }
 
     /**
+     * Verify that when collecting activity to the existing close transition, it should not affect
+     * ready state.
+     */
+    @Test
+    public void testFinishActivityIfPossible_collectToExistingTransition() {
+        final TestTransitionPlayer testPlayer = registerTestTransitionPlayer();
+        final ActivityRecord activity = createActivityWithTask();
+        activity.setState(PAUSED, "test");
+        activity.finishIfPossible("test", false /* oomAdj */);
+        final Transition lastTransition = testPlayer.mLastTransit;
+        assertTrue(lastTransition.allReady());
+        assertTrue(activity.inTransition());
+
+        // Collect another activity to the existing transition without changing ready state.
+        final ActivityRecord activity2 = createActivityRecord(activity.getTask());
+        activity2.setState(PAUSING, "test");
+        activity2.finishIfPossible("test", false /* oomAdj */);
+        assertTrue(activity2.inTransition());
+        assertEquals(lastTransition, testPlayer.mLastTransit);
+        assertTrue(lastTransition.allReady());
+    }
+
+    /**
      * Verify that complete finish request for non-finishing activity is invalid.
      */
     @Test(expected = IllegalArgumentException.class)
diff --git a/services/tests/wmtests/src/com/android/server/wm/DisplayContentTests.java b/services/tests/wmtests/src/com/android/server/wm/DisplayContentTests.java
index 24bbf46..f3c1ec5 100644
--- a/services/tests/wmtests/src/com/android/server/wm/DisplayContentTests.java
+++ b/services/tests/wmtests/src/com/android/server/wm/DisplayContentTests.java
@@ -1674,11 +1674,7 @@
     public void testShellTransitRotation() {
         DisplayContent dc = createNewDisplay();
 
-        // Set-up mock shell transitions
-        final TestTransitionPlayer testPlayer = new TestTransitionPlayer(
-                mAtm.getTransitionController(), mAtm.mWindowOrganizerController);
-        mAtm.getTransitionController().registerTransitionPlayer(testPlayer);
-
+        final TestTransitionPlayer testPlayer = registerTestTransitionPlayer();
         final DisplayRotation dr = dc.getDisplayRotation();
         doCallRealMethod().when(dr).updateRotationUnchecked(anyBoolean());
         // Rotate 180 degree so the display doesn't have configuration change. This condition is
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 ac61bb1..81b00ea 100644
--- a/services/tests/wmtests/src/com/android/server/wm/WindowTestsBase.java
+++ b/services/tests/wmtests/src/com/android/server/wm/WindowTestsBase.java
@@ -798,6 +798,14 @@
         };
     }
 
+    /** Sets up a simple implementation of transition player for shell transitions. */
+    TestTransitionPlayer registerTestTransitionPlayer() {
+        final TestTransitionPlayer testPlayer = new TestTransitionPlayer(
+                mAtm.getTransitionController(), mAtm.mWindowOrganizerController);
+        testPlayer.mController.registerTransitionPlayer(testPlayer);
+        return testPlayer;
+    }
+
     /**
      * Avoids rotating screen disturbed by some conditions. It is usually used for the default
      * display that is not the instance of {@link TestDisplayContent} (it bypasses the conditions).
@@ -1606,7 +1614,7 @@
         }
     }
 
-    class TestTransitionPlayer extends ITransitionPlayer.Stub {
+    static class TestTransitionPlayer extends ITransitionPlayer.Stub {
         final TransitionController mController;
         final WindowOrganizerController mOrganizer;
         Transition mLastTransit = null;
diff --git a/services/tests/wmtests/src/com/android/server/wm/ZOrderingTests.java b/services/tests/wmtests/src/com/android/server/wm/ZOrderingTests.java
index 22ea3d5..72b05c0 100644
--- a/services/tests/wmtests/src/com/android/server/wm/ZOrderingTests.java
+++ b/services/tests/wmtests/src/com/android/server/wm/ZOrderingTests.java
@@ -23,7 +23,6 @@
 import static android.app.WindowConfiguration.WINDOWING_MODE_MULTI_WINDOW;
 import static android.app.WindowConfiguration.WINDOWING_MODE_PINNED;
 import static android.app.WindowConfiguration.WINDOWING_MODE_SPLIT_SCREEN_PRIMARY;
-import static android.app.WindowConfiguration.WINDOWING_MODE_SPLIT_SCREEN_SECONDARY;
 import static android.view.WindowManager.LayoutParams.TYPE_APPLICATION_ABOVE_SUB_PANEL;
 import static android.view.WindowManager.LayoutParams.TYPE_APPLICATION_ATTACHED_DIALOG;
 import static android.view.WindowManager.LayoutParams.TYPE_APPLICATION_MEDIA;
@@ -442,24 +441,42 @@
 
     @Test
     public void testDockedDividerPosition() {
-        final WindowState pinnedStackWindow = createWindow(null, WINDOWING_MODE_PINNED,
-                ACTIVITY_TYPE_STANDARD, TYPE_BASE_APPLICATION, mDisplayContent,
-                "pinnedStackWindow");
-        final WindowState splitScreenWindow = createWindow(null,
-                WINDOWING_MODE_SPLIT_SCREEN_PRIMARY, ACTIVITY_TYPE_STANDARD, TYPE_BASE_APPLICATION,
-                mDisplayContent, "splitScreenWindow");
-        final WindowState splitScreenSecondaryWindow = createWindow(null,
-                WINDOWING_MODE_SPLIT_SCREEN_SECONDARY, ACTIVITY_TYPE_STANDARD,
-                TYPE_BASE_APPLICATION, mDisplayContent, "splitScreenSecondaryWindow");
-        final WindowState assistantStackWindow = createWindow(null,
-                WINDOWING_MODE_FULLSCREEN, ACTIVITY_TYPE_ASSISTANT, TYPE_BASE_APPLICATION,
-                mDisplayContent, "assistantStackWindow");
+        final Task pinnedTask =
+                createTask(mDisplayContent, WINDOWING_MODE_PINNED, ACTIVITY_TYPE_STANDARD);
+        final WindowState pinnedWindow =
+                createAppWindow(pinnedTask, ACTIVITY_TYPE_STANDARD, "pinnedWindow");
+
+        final Task belowTask =
+                createTask(mDisplayContent, WINDOWING_MODE_FULLSCREEN, ACTIVITY_TYPE_STANDARD);
+        final WindowState belowTaskWindow =
+                createAppWindow(belowTask, ACTIVITY_TYPE_STANDARD, "belowTaskWindow");
+
+        final Task splitScreenTask1 =
+                createTask(mDisplayContent, WINDOWING_MODE_MULTI_WINDOW, ACTIVITY_TYPE_STANDARD);
+        final WindowState splitWindow1 =
+                createAppWindow(splitScreenTask1, ACTIVITY_TYPE_STANDARD, "splitWindow1");
+        final Task splitScreenTask2 =
+                createTask(mDisplayContent, WINDOWING_MODE_MULTI_WINDOW, ACTIVITY_TYPE_STANDARD);
+        final WindowState splitWindow2 =
+                createAppWindow(splitScreenTask2, ACTIVITY_TYPE_STANDARD, "splitWindow2");
+        splitScreenTask1.setAdjacentTaskFragment(splitScreenTask2);
+        splitScreenTask2.setAdjacentTaskFragment(splitScreenTask1);
+
+        final Task aboveTask =
+                createTask(mDisplayContent, WINDOWING_MODE_FULLSCREEN, ACTIVITY_TYPE_STANDARD);
+        final WindowState aboveTaskWindow =
+                createAppWindow(aboveTask, ACTIVITY_TYPE_STANDARD, "aboveTaskWindow");
 
         mDisplayContent.assignChildLayers(mTransaction);
 
-        assertWindowHigher(mDockedDividerWindow, splitScreenWindow);
-        assertWindowHigher(mDockedDividerWindow, splitScreenSecondaryWindow);
-        assertWindowHigher(pinnedStackWindow, mDockedDividerWindow);
+        assertWindowHigher(splitWindow1, belowTaskWindow);
+        assertWindowHigher(splitWindow1, belowTaskWindow);
+        assertWindowHigher(splitWindow2, belowTaskWindow);
+        assertWindowHigher(splitWindow2, belowTaskWindow);
+        assertWindowHigher(mDockedDividerWindow, splitWindow1);
+        assertWindowHigher(mDockedDividerWindow, splitWindow2);
+        assertWindowHigher(aboveTaskWindow, mDockedDividerWindow);
+        assertWindowHigher(pinnedWindow, aboveTaskWindow);
     }
 
     @Test