Merge "Remove stack scroller references from StatusBar"
diff --git a/packages/SystemUI/src/com/android/systemui/Dependency.java b/packages/SystemUI/src/com/android/systemui/Dependency.java
index e7fc3c9..2dd54aa 100644
--- a/packages/SystemUI/src/com/android/systemui/Dependency.java
+++ b/packages/SystemUI/src/com/android/systemui/Dependency.java
@@ -23,6 +23,7 @@
 import android.os.Process;
 import android.os.ServiceManager;
 import android.util.ArrayMap;
+import android.util.DisplayMetrics;
 import android.view.IWindowManager;
 import android.view.WindowManagerGlobal;
 
@@ -51,6 +52,7 @@
 import com.android.systemui.statusbar.phone.ConfigurationControllerImpl;
 import com.android.systemui.statusbar.phone.DarkIconDispatcherImpl;
 import com.android.systemui.statusbar.phone.LightBarController;
+import com.android.systemui.statusbar.phone.LockscreenGestureLogger;
 import com.android.systemui.statusbar.phone.ManagedProfileController;
 import com.android.systemui.statusbar.phone.ManagedProfileControllerImpl;
 import com.android.systemui.statusbar.phone.StatusBarIconController;
@@ -328,6 +330,12 @@
         mProviders.put(IStatusBarService.class, () -> IStatusBarService.Stub.asInterface(
                 ServiceManager.getService(Context.STATUS_BAR_SERVICE)));
 
+        // Single instance of DisplayMetrics, gets updated by StatusBar, but can be used
+        // anywhere it is needed.
+        mProviders.put(DisplayMetrics.class, () -> new DisplayMetrics());
+
+        mProviders.put(LockscreenGestureLogger.class, () -> new LockscreenGestureLogger());
+
         // Put all dependencies above here so the factory can override them if it wants.
         SystemUIFactory.getInstance().injectDependencies(mProviders, mContext);
 
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/StatusBarStateController.java b/packages/SystemUI/src/com/android/systemui/statusbar/StatusBarStateController.java
index 187d2d07..7a69f2e 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/StatusBarStateController.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/StatusBarStateController.java
@@ -43,12 +43,12 @@
             return;
         }
         synchronized (mListeners) {
-            for (StateListener listener : mListeners) {
+            for (StateListener listener : new ArraySet<>(mListeners)) {
                 listener.onStatePreChange(mState, state);
             }
             mLastState = mState;
             mState = state;
-            for (StateListener listener : mListeners) {
+            for (StateListener listener : new ArraySet<>(mListeners)) {
                 listener.onStateChanged(mState);
             }
         }
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/car/CarStatusBar.java b/packages/SystemUI/src/com/android/systemui/statusbar/car/CarStatusBar.java
index b5a51d7..ed06752 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/car/CarStatusBar.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/car/CarStatusBar.java
@@ -91,7 +91,7 @@
         mActivityManagerWrapper = ActivityManagerWrapper.getInstance();
         mActivityManagerWrapper.registerTaskStackListener(mTaskStackListener);
 
-        mStackScroller.setScrollingEnabled(true);
+        mNotificationPanel.setScrollingEnabled(true);
 
         createBatteryController();
         mCarBatteryController.startListening();
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/ExpandableView.java b/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/ExpandableView.java
index 46019e3..4c1938a 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/ExpandableView.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/ExpandableView.java
@@ -417,6 +417,7 @@
 
     public void setWillBeGone(boolean willBeGone) {
         mWillBeGone = willBeGone;
+        invalidate();
     }
 
     public int getMinClipTopAmount() {
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 b2b415c..4f554b6 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
@@ -28,7 +28,9 @@
 import android.animation.ValueAnimator.AnimatorUpdateListener;
 import android.annotation.FloatRange;
 import android.annotation.Nullable;
+import android.app.WallpaperManager;
 import android.content.Context;
+import android.content.Intent;
 import android.content.res.Configuration;
 import android.content.res.Resources;
 import android.graphics.Canvas;
@@ -40,16 +42,22 @@
 import android.graphics.Rect;
 import android.os.Bundle;
 import android.os.Handler;
+import android.os.ServiceManager;
+import android.provider.Settings;
 import android.service.notification.StatusBarNotification;
+
 import androidx.annotation.NonNull;
 import androidx.annotation.VisibleForTesting;
 import androidx.core.graphics.ColorUtils;
+
 import android.util.AttributeSet;
+import android.util.DisplayMetrics;
 import android.util.Log;
 import android.util.MathUtils;
 import android.util.Pair;
 import android.view.ContextThemeWrapper;
 import android.view.InputDevice;
+import android.view.LayoutInflater;
 import android.view.MotionEvent;
 import android.view.VelocityTracker;
 import android.view.View;
@@ -66,24 +74,36 @@
 
 import com.android.internal.logging.MetricsLogger;
 import com.android.internal.logging.nano.MetricsProto.MetricsEvent;
+import com.android.internal.statusbar.IStatusBarService;
 import com.android.keyguard.KeyguardSliceView;
 import com.android.settingslib.Utils;
 import com.android.systemui.Dependency;
+import com.android.systemui.Dumpable;
 import com.android.systemui.ExpandHelper;
 import com.android.systemui.Interpolators;
 import com.android.systemui.R;
 import com.android.systemui.SwipeHelper;
+import com.android.systemui.SwipeHelper.Callback;
 import com.android.systemui.classifier.FalsingManager;
+import com.android.systemui.colorextraction.SysuiColorExtractor;
 import com.android.systemui.plugins.statusbar.NotificationMenuRowPlugin;
 import com.android.systemui.plugins.statusbar.NotificationMenuRowPlugin.MenuItem;
+import com.android.systemui.plugins.statusbar.NotificationMenuRowPlugin.OnMenuEventListener;
 import com.android.systemui.plugins.statusbar.NotificationSwipeActionHelper;
+import com.android.systemui.statusbar.CommandQueue;
+import com.android.systemui.statusbar.DragDownHelper.DragDownCallback;
 import com.android.systemui.statusbar.NotificationLockscreenUserManager;
 import com.android.systemui.statusbar.StatusBarState;
+import com.android.systemui.statusbar.NotificationRemoteInputManager;
+import com.android.systemui.statusbar.RemoteInputController;
 import com.android.systemui.statusbar.StatusBarStateController.StateListener;
+import com.android.systemui.statusbar.notification.NotificationEntryManager;
+import com.android.systemui.statusbar.notification.VisualStabilityManager;
 import com.android.systemui.statusbar.notification.row.ActivatableNotificationView;
 import com.android.systemui.statusbar.EmptyShadeView;
 import com.android.systemui.statusbar.notification.row.ExpandableNotificationRow;
 import com.android.systemui.statusbar.notification.row.ExpandableView;
+import com.android.systemui.statusbar.notification.row.ExpandableView.OnHeightChangedListener;
 import com.android.systemui.statusbar.notification.row.FooterView;
 import com.android.systemui.statusbar.notification.row.NotificationBlockingHelperManager;
 import com.android.systemui.statusbar.notification.NotificationData;
@@ -99,10 +119,16 @@
 import com.android.systemui.statusbar.phone.DozeParameters;
 import com.android.systemui.statusbar.phone.HeadsUpAppearanceController;
 import com.android.systemui.statusbar.phone.HeadsUpManagerPhone;
+import com.android.systemui.statusbar.phone.HeadsUpManagerPhone.AnimationStateHandler;
+import com.android.systemui.statusbar.phone.LockscreenGestureLogger;
 import com.android.systemui.statusbar.phone.NotificationGroupManager;
+import com.android.systemui.statusbar.phone.NotificationGroupManager.OnGroupChangeListener;
 import com.android.systemui.statusbar.phone.NotificationIconAreaController;
+import com.android.systemui.statusbar.phone.NotificationPanelView;
 import com.android.systemui.statusbar.phone.ScrimController;
 import com.android.systemui.statusbar.phone.StatusBar;
+import com.android.systemui.statusbar.policy.ConfigurationController;
+import com.android.systemui.statusbar.policy.ConfigurationController.ConfigurationListener;
 import com.android.systemui.statusbar.policy.HeadsUpUtil;
 import com.android.systemui.statusbar.policy.ScrollAdapter;
 
@@ -119,10 +145,11 @@
  * A layout which handles a dynamic amount of notifications and presents them in a scrollable stack.
  */
 public class NotificationStackScrollLayout extends ViewGroup
-        implements SwipeHelper.Callback, ExpandHelper.Callback, ScrollAdapter,
-        ExpandableView.OnHeightChangedListener, NotificationGroupManager.OnGroupChangeListener,
-        NotificationMenuRowPlugin.OnMenuEventListener, VisibilityLocationProvider,
-        NotificationListContainer {
+        implements Callback, ExpandHelper.Callback, ScrollAdapter,
+        OnHeightChangedListener, OnGroupChangeListener,
+        OnMenuEventListener, VisibilityLocationProvider,
+        NotificationListContainer, ConfigurationListener, DragDownCallback, AnimationStateHandler,
+        Dumpable {
 
     public static final float BACKGROUND_ALPHA_DIMMED = 0.7f;
     private static final String TAG = "StackScroller";
@@ -421,10 +448,25 @@
     private float mVerticalPanelTranslation;
     private final NotificationLockscreenUserManager mLockscreenUserManager =
             Dependency.get(NotificationLockscreenUserManager.class);
+    private final Rect mTmpRect = new Rect();
+    private final NotificationEntryManager mEntryManager =
+            Dependency.get(NotificationEntryManager.class);
+    private final IStatusBarService mBarService = IStatusBarService.Stub.asInterface(
+            ServiceManager.getService(Context.STATUS_BAR_SERVICE));
+    private final MetricsLogger mMetricsLogger = Dependency.get(MetricsLogger.class);
+    private final NotificationRemoteInputManager mRemoteInputManager =
+            Dependency.get(NotificationRemoteInputManager.class);
+    private final SysuiColorExtractor mColorExtractor = Dependency.get(SysuiColorExtractor.class);
+
+    private final DisplayMetrics mDisplayMetrics = Dependency.get(DisplayMetrics.class);
+    private final LockscreenGestureLogger mLockscreenGestureLogger =
+            Dependency.get(LockscreenGestureLogger.class);
+    private final VisualStabilityManager mVisualStabilityManager =
+            Dependency.get(VisualStabilityManager.class);
+    protected boolean mClearAllEnabled;
 
     private Interpolator mDarkXInterpolator = Interpolators.FAST_OUT_SLOW_IN;
-
-    private final StateListener mStateListener = this::setStatusBarState;
+    private NotificationPanelView mNotificationPanel;
 
     public NotificationStackScrollLayout(Context context) {
         this(context, null);
@@ -481,18 +523,102 @@
             mDebugPaint.setStrokeWidth(2);
             mDebugPaint.setStyle(Paint.Style.STROKE);
         }
+        mClearAllEnabled = res.getBoolean(R.bool.config_enableNotificationsClearAll);
+    }
+
+    @Override
+    protected void onFinishInflate() {
+        super.onFinishInflate();
+
+        inflateEmptyShadeView();
+        inflateFooterView();
+        mVisualStabilityManager.setVisibilityLocationProvider(this);
+        setLongPressListener(mEntryManager.getNotificationLongClicker());
+    }
+
+    @Override
+    public void onDensityOrFontScaleChanged() {
+        inflateFooterView();
+        inflateEmptyShadeView();
+        updateFooter();
+    }
+
+    @Override
+    public void onThemeChanged() {
+        int which;
+        if (mStatusBarState == StatusBarState.KEYGUARD
+                || mStatusBarState == StatusBarState.SHADE_LOCKED) {
+            which = WallpaperManager.FLAG_LOCK;
+        } else {
+            which = WallpaperManager.FLAG_SYSTEM;
+        }
+        final boolean useDarkText = mColorExtractor.getColors(which,
+                true /* ignoreVisibility */).supportsDarkText();
+        updateDecorViews(useDarkText);
+
+        updateFooter();
+    }
+
+    @VisibleForTesting
+    public void updateFooter() {
+        boolean showDismissView = mClearAllEnabled && hasActiveClearableNotifications();
+        boolean showFooterView = (showDismissView ||
+                mEntryManager.getNotificationData().getActiveNotifications().size() != 0)
+                && mStatusBarState != StatusBarState.KEYGUARD
+                && !mRemoteInputManager.getController().isRemoteInputActive();
+
+        updateFooterView(showFooterView, showDismissView);
+    }
+
+    /**
+     * Return whether there are any clearable notifications
+     */
+    public boolean hasActiveClearableNotifications() {
+        int childCount = getChildCount();
+        for (int i = 0; i < childCount; i++) {
+            View child = getChildAt(i);
+            if (!(child instanceof ExpandableNotificationRow)) {
+                continue;
+            }
+            if (((ExpandableNotificationRow) child).canViewBeDismissed()) {
+                return true;
+            }
+        }
+        return false;
+    }
+
+    public RemoteInputController.Delegate createDelegate() {
+        return new RemoteInputController.Delegate() {
+            public void setRemoteInputActive(NotificationData.Entry entry,
+                    boolean remoteInputActive) {
+                mHeadsUpManager.setRemoteInputActive(entry, remoteInputActive);
+                entry.row.notifyHeightChanged(true /* needsAnimation */);
+                updateFooter();
+            }
+
+            public void lockScrollTo(NotificationData.Entry entry) {
+                NotificationStackScrollLayout.this.lockScrollTo(entry.row);
+            }
+
+            public void requestDisallowLongPressAndDismiss() {
+                requestDisallowLongPress();
+                requestDisallowDismiss();
+            }
+        };
     }
 
     @Override
     protected void onAttachedToWindow() {
         super.onAttachedToWindow();
         Dependency.get(StatusBarStateController.class).addListener(mStateListener);
+        Dependency.get(ConfigurationController.class).addCallback(this);
     }
 
     @Override
     protected void onDetachedFromWindow() {
         super.onDetachedFromWindow();
         Dependency.get(StatusBarStateController.class).removeListener(mStateListener);
+        Dependency.get(ConfigurationController.class).removeCallback(this);
     }
 
     @Override
@@ -532,6 +658,7 @@
         mSwipeHelper.onMenuShown(row);
     }
 
+    @Override
     public void onUiModeChanged() {
         mBgColor = mContext.getColor(R.color.notification_shade_background_color);
         updateBackgroundDimming();
@@ -809,8 +936,8 @@
 
     private void updateClippingToTopRoundedCorner() {
         Float clipStart = (float) mTopPadding
-                                 + mStackTranslation
-                                 + mAmbientState.getExpandAnimationTopChange();
+                + mStackTranslation
+                + mAmbientState.getExpandAnimationTopChange();
         Float clipEnd = clipStart + mCornerRadius;
         boolean first = true;
         for (int i = 0; i < getChildCount(); i++) {
@@ -842,7 +969,8 @@
                         : increasedPaddingAmount == -1.0f ? 0 : mPaddingBetweenElements;
                 int childHeight = getIntrinsicHeight(child) + padding;
                 if (startingPosition < mOwnScrollY) {
-                    // This child starts off screen, so let's keep it offscreen to keep the others visible
+                    // This child starts off screen, so let's keep it offscreen to keep the
+                    // others visible
 
                     setOwnScrollY(mOwnScrollY + childHeight);
                 }
@@ -904,7 +1032,7 @@
             updateContentHeight();
             if (animate && mAnimationsEnabled && mIsExpanded) {
                 mTopPaddingNeedsAnimation = true;
-                mNeedsAnimation =  true;
+                mNeedsAnimation = true;
             }
             requestChildrenUpdate();
             notifyHeightChangeListener(null, animate);
@@ -1014,7 +1142,7 @@
 
     /**
      * @return The translation at the beginning when expanding.
-     *         Measured relative to the resting position.
+     * Measured relative to the resting position.
      */
     private float getExpandTranslationStart() {
         return -mTopPadding + getMinExpansionHeight();
@@ -1022,7 +1150,7 @@
 
     /**
      * @return the position from where the appear transition starts when expanding.
-     *         Measured in absolute height.
+     * Measured in absolute height.
      */
     private float getAppearStartPosition() {
         if (isHeadsUpTransition()) {
@@ -1033,8 +1161,8 @@
 
     /**
      * @return the height of the top heads up notification when pinned. This is different from the
-     *         intrinsic height, which also includes whether the notification is system expanded and
-     *         is mainly used when dragging down from a heads up notification.
+     * intrinsic height, which also includes whether the notification is system expanded and
+     * is mainly used when dragging down from a heads up notification.
      */
     private int getTopHeadsUpPinnedHeight() {
         NotificationData.Entry topEntry = mHeadsUpManager.getTopEntry();
@@ -1054,7 +1182,7 @@
 
     /**
      * @return the position from where the appear transition ends when expanding.
-     *         Measured in absolute height.
+     * Measured in absolute height.
      */
     private float getAppearEndPosition() {
         int appearPosition;
@@ -1260,9 +1388,9 @@
             ExpandableNotificationRow parent = row.getNotificationParent();
             if (parent != null && parent.areChildrenExpanded()
                     && (parent.areGutsExposed()
-                            || mMenuExposedView == parent
-                        || (parent.getNotificationChildren().size() == 1
-                                && parent.isClearable()))) {
+                    || mMenuExposedView == parent
+                    || (parent.getNotificationChildren().size() == 1
+                    && parent.isClearable()))) {
                 // In this case the group is expanded and showing the menu for the
                 // group, further interaction should apply to the group, not any
                 // child notifications so we use the parent of the child. We also do the same
@@ -1317,8 +1445,8 @@
     /**
      * Get the child at a certain screen location.
      *
-     * @param touchX the x coordinate
-     * @param touchY the y coordinate
+     * @param touchX           the x coordinate
+     * @param touchY           the y coordinate
      * @param requireMinHeight Whether a minimum height is required for a child to be returned.
      * @return the child at the given location.
      */
@@ -1349,8 +1477,8 @@
                     if (!mIsExpanded && row.isHeadsUp() && row.isPinned()
                             && mHeadsUpManager.getTopEntry().row != row
                             && mGroupManager.getGroupSummary(
-                                mHeadsUpManager.getTopEntry().row.getStatusBarNotification())
-                                != row) {
+                            mHeadsUpManager.getTopEntry().row.getStatusBarNotification())
+                            != row) {
                         continue;
                     }
                     return row.getViewAtPosition(touchY - childTop);
@@ -1453,7 +1581,7 @@
 
     /**
      * @return the scroll necessary to make the bottom edge of {@param v} align with the top of
-     *         the IME.
+     * the IME.
      */
     private int targetScrollForView(ExpandableView v, int positionInLinearLayout) {
         return positionInLinearLayout + v.getIntrinsicHeight() +
@@ -1514,7 +1642,7 @@
 
     private void setSwipingInProgress(boolean isSwiped) {
         mSwipingInProgress = isSwiped;
-        if(isSwiped) {
+        if (isSwiped) {
             requestDisallowInterceptTouchEvent(true);
         }
     }
@@ -1551,7 +1679,7 @@
     @Override
     public boolean onTouchEvent(MotionEvent ev) {
         boolean isCancelOrUp = ev.getActionMasked() == MotionEvent.ACTION_CANCEL
-                || ev.getActionMasked()== MotionEvent.ACTION_UP;
+                || ev.getActionMasked() == MotionEvent.ACTION_UP;
         handleEmptySpaceClick(ev);
         boolean expandWantsIt = false;
         if (mIsExpanded && !mSwipingInProgress && !mOnlyScrollingInThisMotion) {
@@ -1790,7 +1918,7 @@
      *
      * @param deltaY The amount to scroll upwards, has to be positive.
      * @return The amount of scrolling to be performed by the scroller,
-     *         not handled by the overScroll amount.
+     * not handled by the overScroll amount.
      */
     private float overScrollUp(int deltaY, int range) {
         deltaY = Math.max(deltaY, 0);
@@ -1823,7 +1951,7 @@
      *
      * @param deltaY The amount to scroll downwards, has to be negative.
      * @return The amount of scrolling to be performed by the scroller,
-     *         not handled by the overScroll amount.
+     * not handled by the overScroll amount.
      */
     private float overScrollDown(int deltaY) {
         deltaY = Math.min(deltaY, 0);
@@ -1949,8 +2077,8 @@
      *
      * @param numPixels The amount of pixels to overScroll by. These will be scaled according to
      *                  the rubber-banding logic.
-     * @param onTop Should the effect be applied on top of the scroller.
-     * @param animate Should an animation be performed.
+     * @param onTop     Should the effect be applied on top of the scroller.
+     * @param animate   Should an animation be performed.
      */
     public void setOverScrolledPixels(float numPixels, boolean onTop, boolean animate) {
         setOverScrollAmount(numPixels * getRubberBandFactor(onTop), onTop, animate, true);
@@ -1960,8 +2088,8 @@
      * Set the effective overScroll amount which will be directly reflected in the layout.
      * By default this will also cancel animations on the same overScroll edge.
      *
-     * @param amount The amount to overScroll by.
-     * @param onTop Should the effect be applied on top of the scroller.
+     * @param amount  The amount to overScroll by.
+     * @param onTop   Should the effect be applied on top of the scroller.
      * @param animate Should an animation be performed.
      */
     public void setOverScrollAmount(float amount, boolean onTop, boolean animate) {
@@ -1971,9 +2099,9 @@
     /**
      * Set the effective overScroll amount which will be directly reflected in the layout.
      *
-     * @param amount The amount to overScroll by.
-     * @param onTop Should the effect be applied on top of the scroller.
-     * @param animate Should an animation be performed.
+     * @param amount          The amount to overScroll by.
+     * @param onTop           Should the effect be applied on top of the scroller.
+     * @param animate         Should an animation be performed.
      * @param cancelAnimators Should running animations be cancelled.
      */
     public void setOverScrollAmount(float amount, boolean onTop, boolean animate,
@@ -1984,12 +2112,12 @@
     /**
      * Set the effective overScroll amount which will be directly reflected in the layout.
      *
-     * @param amount The amount to overScroll by.
-     * @param onTop Should the effect be applied on top of the scroller.
-     * @param animate Should an animation be performed.
+     * @param amount          The amount to overScroll by.
+     * @param onTop           Should the effect be applied on top of the scroller.
+     * @param animate         Should an animation be performed.
      * @param cancelAnimators Should running animations be cancelled.
-     * @param isRubberbanded The value which will be passed to
-     *                     {@link OnOverscrollTopChangedListener#onOverscrollTopChanged}
+     * @param isRubberbanded  The value which will be passed to
+     *                        {@link OnOverscrollTopChangedListener#onOverscrollTopChanged}
      */
     public void setOverScrollAmount(float amount, boolean onTop, boolean animate,
             boolean cancelAnimators, boolean isRubberbanded) {
@@ -2035,7 +2163,7 @@
     }
 
     public float getCurrentOverScrolledPixels(boolean top) {
-        return top? mOverScrolledTopPixels : mOverScrolledBottomPixels;
+        return top ? mOverScrolledTopPixels : mOverScrolledBottomPixels;
     }
 
     private void setOverScrolledPixels(float amount, boolean onTop) {
@@ -2139,7 +2267,7 @@
 
     /**
      * @return The first child which has visibility unequal to GONE which is currently below the
-     *         given translationY or equal to it.
+     * given translationY or equal to it.
      */
     private View getFirstChildBelowTranlsationY(float translationY, boolean ignoreChildren) {
         int childCount = getChildCount();
@@ -2596,7 +2724,7 @@
                 setOverScrollAmount(0, false, false);
                 mMaxOverScroll = Math.abs(velocityY) / 1000f
                         * getRubberBandFactor(false /* onTop */) * mOverflingDistance
-                        +  bottomAmount;
+                        + bottomAmount;
             } else {
                 // it will be set once we reach the boundary
                 mMaxOverScroll = 0.0f;
@@ -2628,8 +2756,8 @@
      * Updates the top padding of the notifications, taking {@link #getIntrinsicPadding()} into
      * account.
      *
-     * @param qsHeight the top padding imposed by the quick settings panel
-     * @param animate whether to animate the change
+     * @param qsHeight               the top padding imposed by the quick settings panel
+     * @param animate                whether to animate the change
      * @param ignoreIntrinsicPadding if true, {@link #getIntrinsicPadding()} is ignored and
      *                               {@code qsHeight} is the final top padding
      */
@@ -2772,7 +2900,7 @@
         switch (ev.getActionMasked()) {
             case MotionEvent.ACTION_MOVE:
                 if (mTouchIsClick && (Math.abs(ev.getY() - mInitialTouchY) > mTouchSlop
-                        || Math.abs(ev.getX() - mInitialTouchX) > mTouchSlop )) {
+                        || Math.abs(ev.getX() - mInitialTouchX) > mTouchSlop)) {
                     mTouchIsClick = false;
                 }
                 break;
@@ -2873,7 +3001,7 @@
     private boolean isChildInGroup(View child) {
         return child instanceof ExpandableNotificationRow
                 && mGroupManager.isChildInGroupWithSummary(
-                        ((ExpandableNotificationRow) child).getStatusBarNotification());
+                ((ExpandableNotificationRow) child).getStatusBarNotification());
     }
 
     /**
@@ -2929,7 +3057,7 @@
         if (hasAddEvent) {
             // This child was just added lets remove all events.
             mHeadsUpChangeAnimations.removeAll(mTmpList);
-            ((ExpandableNotificationRow ) child).setHeadsUpAnimatingAway(false);
+            ((ExpandableNotificationRow) child).setHeadsUpAnimatingAway(false);
         }
         mTmpList.clear();
         return hasAddEvent;
@@ -2938,7 +3066,7 @@
     /**
      * @param child the child to query
      * @return whether a view is not a top level child but a child notification and that group is
-     *         not expanded
+     * not expanded
      */
     private boolean isChildInInvisibleGroup(View child) {
         if (child instanceof ExpandableNotificationRow) {
@@ -3183,7 +3311,7 @@
         if (currentIndex == -1) {
             boolean isTransient = false;
             if (child instanceof ExpandableNotificationRow
-                    && ((ExpandableNotificationRow)child).getTransientContainer() != null) {
+                    && ((ExpandableNotificationRow) child).getTransientContainer() != null) {
                 isTransient = true;
             }
             Log.e(TAG, "Attempting to re-position "
@@ -3196,10 +3324,10 @@
 
         if (child != null && child.getParent() == this && currentIndex != newIndex) {
             mChangePositionInProgress = true;
-            ((ExpandableView)child).setChangingPosition(true);
+            ((ExpandableView) child).setChangingPosition(true);
             removeView(child);
             addView(child, newIndex);
-            ((ExpandableView)child).setChangingPosition(false);
+            ((ExpandableView) child).setChangingPosition(false);
             mChangePositionInProgress = false;
             if (mIsExpanded && mAnimationsEnabled && child.getVisibility() != View.GONE) {
                 mChildrenChangingPositions.add(child);
@@ -3310,7 +3438,7 @@
             for (AnimationEvent animationEvent : mAnimationEvents) {
                 final int type = animationEvent.animationType;
                 if (type == AnimationEvent.ANIMATION_TYPE_HEADS_UP_DISAPPEAR_CLICK
-                    || type == AnimationEvent.ANIMATION_TYPE_HEADS_UP_DISAPPEAR) {
+                        || type == AnimationEvent.ANIMATION_TYPE_HEADS_UP_DISAPPEAR) {
                     hasDisappearAnimation = true;
                     break;
                 }
@@ -3489,10 +3617,10 @@
          */
 
         /*
-        * Shortcut the most recurring case: the user is in the dragging
-        * state and is moving their finger.  We want to intercept this
-        * motion.
-        */
+         * Shortcut the most recurring case: the user is in the dragging
+         * state and is moving their finger.  We want to intercept this
+         * motion.
+         */
         final int action = ev.getAction();
         if ((action == MotionEvent.ACTION_MOVE) && (mIsBeingDragged)) {
             return true;
@@ -3506,9 +3634,9 @@
                  */
 
                 /*
-                * Locally do absolute value. mLastMotionY is set to the y value
-                * of the down event.
-                */
+                 * Locally do absolute value. mLastMotionY is set to the y value
+                 * of the down event.
+                 */
                 final int activePointerId = mActivePointerId;
                 if (activePointerId == INVALID_POINTER) {
                     // If we don't have a valid id, the touch down wasn't on content.
@@ -3556,10 +3684,10 @@
                 initOrResetVelocityTracker();
                 mVelocityTracker.addMovement(ev);
                 /*
-                * If being flinged and user touches the screen, initiate drag;
-                * otherwise don't.  mScroller.isFinished should be false when
-                * being flinged.
-                */
+                 * If being flinged and user touches the screen, initiate drag;
+                 * otherwise don't.  mScroller.isFinished should be false when
+                 * being flinged.
+                 */
                 boolean isBeingDragged = !mScroller.isFinished();
                 setIsBeingDragged(isBeingDragged);
                 break;
@@ -3581,9 +3709,9 @@
         }
 
         /*
-        * The only time we want to intercept motion events is if we are in the
-        * drag mode.
-        */
+         * The only time we want to intercept motion events is if we are in the
+         * drag mode.
+         */
         return mIsBeingDragged;
     }
 
@@ -3723,6 +3851,7 @@
         mPanelTracking = true;
         mAmbientState.setPanelTracking(true);
     }
+
     public void onPanelTrackingStopped() {
         mPanelTracking = false;
         mAmbientState.setPanelTracking(false);
@@ -3865,7 +3994,7 @@
         mAmbientState.setDimmed(dimmed);
         if (animate && mAnimationsEnabled) {
             mDimmedNeedsAnimation = true;
-            mNeedsAnimation =  true;
+            mNeedsAnimation = true;
             animateDimmed(dimmed);
         } else {
             setDimAmount(dimmed ? 1.0f : 0.0f);
@@ -3909,7 +4038,7 @@
             mAmbientState.setHideSensitive(hideSensitive);
             if (animate && mAnimationsEnabled) {
                 mHideSensitiveNeedsAnimation = true;
-                mNeedsAnimation =  true;
+                mNeedsAnimation = true;
             }
             updateContentHeight();
             requestChildrenUpdate();
@@ -3923,7 +4052,7 @@
         mAmbientState.setActivatedChild(activatedChild);
         if (mAnimationsEnabled) {
             mActivateNeedsAnimation = true;
-            mNeedsAnimation =  true;
+            mNeedsAnimation = true;
         }
         requestChildrenUpdate();
     }
@@ -4078,10 +4207,11 @@
     /**
      * Sets the current dark amount.
      *
-     * @param linearDarkAmount The dark amount that follows linear interpoloation in the animation,
-     *                         i.e. animates from 0 to 1 or vice-versa in a linear manner.
+     * @param linearDarkAmount       The dark amount that follows linear interpoloation in the
+     *                               animation,
+     *                               i.e. animates from 0 to 1 or vice-versa in a linear manner.
      * @param interpolatedDarkAmount The dark amount that follows the actual interpolation of the
-     *                                animation curve.
+     *                               animation curve.
      */
     public void setDarkAmount(float linearDarkAmount, float interpolatedDarkAmount) {
         mLinearDarkAmount = linearDarkAmount;
@@ -4259,10 +4389,7 @@
 
     public void setGroupManager(NotificationGroupManager groupManager) {
         this.mGroupManager = groupManager;
-    }
-
-    public void onGoToKeyguard() {
-        requestAnimateEverything();
+        mGroupManager.setOnGroupChangeListener(this);
     }
 
     private void requestAnimateEverything() {
@@ -4286,15 +4413,15 @@
                 boolean belowChild = touchY > childTop + child.getActualHeight()
                         - child.getClipBottomAmount();
                 if (child == mFooterView) {
-                    if(!belowChild && !mFooterView.isOnEmptySpace(touchX - mFooterView.getX(),
-                                    touchY - childTop)) {
+                    if (!belowChild && !mFooterView.isOnEmptySpace(touchX - mFooterView.getX(),
+                            touchY - childTop)) {
                         // We clicked on the dismiss button
                         return false;
                     }
                 } else if (child == mEmptyShadeView) {
                     // We arrived at the empty shade view, for which we accept all clicks
                     return true;
-                } else if (!belowChild){
+                } else if (!belowChild) {
                     // We are on a child
                     return false;
                 }
@@ -4432,6 +4559,7 @@
         mHeadsUpManager = headsUpManager;
         mAmbientState.setHeadsUpManager(headsUpManager);
         mHeadsUpManager.addListener(mRoundnessManager);
+        mHeadsUpManager.setAnimationStateHandler(this);
     }
 
     public void generateHeadsUpAnimation(ExpandableNotificationRow row, boolean isHeadsUp) {
@@ -4454,7 +4582,7 @@
      * Set the boundary for the bottom heads up position. The heads up will always be above this
      * position.
      *
-     * @param height the height of the screen
+     * @param height          the height of the screen
      * @param bottomBarHeight the height of the bar on the bottom
      */
     public void setHeadsUpBoundaries(int height, int bottomBarHeight) {
@@ -4597,7 +4725,7 @@
     }
 
     public void setShouldShowShelfOnly(boolean shouldShowShelfOnly) {
-        mShouldShowShelfOnly =  shouldShowShelfOnly;
+        mShouldShowShelfOnly = shouldShowShelfOnly;
         updateAlgorithmLayoutMinHeight();
     }
 
@@ -4633,6 +4761,9 @@
         if (activatedChild != null) {
             activatedChild.makeInactive(false /* animate */);
         }
+        updateFooter();
+        updateChildren();
+        onUpdateRowStates();
     }
 
     public void setExpandingVelocity(float expandingVelocity) {
@@ -4660,6 +4791,7 @@
         requestChildrenUpdate();
     }
 
+    @Override
     public void setHeadsUpGoingAwayAnimationsAllowed(boolean headsUpGoingAwayAnimationsAllowed) {
         mHeadsUpGoingAwayAnimationsAllowed = headsUpGoingAwayAnimationsAllowed;
     }
@@ -4674,15 +4806,15 @@
                         + " alpha:%f scrollY:%d maxTopPadding:%d showShelfOnly=%s"
                         + " qsExpandFraction=%f]",
                 this.getClass().getSimpleName(),
-                mPulsing ? "T":"f",
-                mAmbientState.isQsCustomizerShowing() ? "T":"f",
+                mPulsing ? "T" : "f",
+                mAmbientState.isQsCustomizerShowing() ? "T" : "f",
                 getVisibility() == View.VISIBLE ? "visible"
                         : getVisibility() == View.GONE ? "gone"
                                 : "invisible",
                 getAlpha(),
                 mAmbientState.getScrollY(),
                 mMaxTopPadding,
-                mShouldShowShelfOnly ? "T":"f",
+                mShouldShowShelfOnly ? "T" : "f",
                 mQsExpansionFraction));
     }
 
@@ -4716,6 +4848,150 @@
         mIconAreaController = controller;
     }
 
+    public void manageNotifications(View v) {
+        Intent intent = new Intent(Settings.ACTION_ALL_APPS_NOTIFICATION_SETTINGS);
+        mStatusBar.startActivity(intent, true, true, Intent.FLAG_ACTIVITY_SINGLE_TOP);
+    }
+
+    public void clearAllNotifications() {
+        // animate-swipe all dismissable notifications, then animate the shade closed
+        int numChildren = getChildCount();
+
+        final ArrayList<View> viewsToHide = new ArrayList<>(numChildren);
+        final ArrayList<ExpandableNotificationRow> viewsToRemove = new ArrayList<>(numChildren);
+        for (int i = 0; i < numChildren; i++) {
+            final View child = getChildAt(i);
+            if (child instanceof ExpandableNotificationRow) {
+                ExpandableNotificationRow row = (ExpandableNotificationRow) child;
+                boolean parentVisible = false;
+                boolean hasClipBounds = child.getClipBounds(mTmpRect);
+                if (canChildBeDismissed(child)) {
+                    viewsToRemove.add(row);
+                    if (child.getVisibility() == View.VISIBLE
+                            && (!hasClipBounds || mTmpRect.height() > 0)) {
+                        viewsToHide.add(child);
+                        parentVisible = true;
+                    }
+                } else if (child.getVisibility() == View.VISIBLE
+                        && (!hasClipBounds || mTmpRect.height() > 0)) {
+                    parentVisible = true;
+                }
+                List<ExpandableNotificationRow> children = row.getNotificationChildren();
+                if (children != null) {
+                    for (ExpandableNotificationRow childRow : children) {
+                        viewsToRemove.add(childRow);
+                        if (parentVisible && row.areChildrenExpanded()
+                                && canChildBeDismissed(childRow)) {
+                            hasClipBounds = childRow.getClipBounds(mTmpRect);
+                            if (childRow.getVisibility() == View.VISIBLE
+                                    && (!hasClipBounds || mTmpRect.height() > 0)) {
+                                viewsToHide.add(childRow);
+                            }
+                        }
+                    }
+                }
+            }
+        }
+        if (viewsToRemove.isEmpty()) {
+            mStatusBar.animateCollapsePanels(CommandQueue.FLAG_EXCLUDE_NONE);
+            return;
+        }
+
+        mStatusBar.addPostCollapseAction(() -> {
+            setDismissAllInProgress(false);
+            for (ExpandableNotificationRow rowToRemove : viewsToRemove) {
+                if (canChildBeDismissed(rowToRemove)) {
+                    mEntryManager.removeNotification(rowToRemove.getEntry().key, null);
+                } else {
+                    rowToRemove.resetTranslation();
+                }
+            }
+            try {
+                mBarService.onClearAllNotifications(mLockscreenUserManager.getCurrentUserId());
+            } catch (Exception ex) {
+            }
+        });
+
+        performDismissAllAnimations(viewsToHide);
+    }
+
+    public void performDismissAllAnimations(ArrayList<View> hideAnimatedList) {
+        Runnable animationFinishAction = () -> {
+            mStatusBar.animateCollapsePanels(CommandQueue.FLAG_EXCLUDE_NONE);
+        };
+
+        if (hideAnimatedList.isEmpty()) {
+            animationFinishAction.run();
+            return;
+        }
+
+        // let's disable our normal animations
+        setDismissAllInProgress(true);
+
+        // Decrease the delay for every row we animate to give the sense of
+        // accelerating the swipes
+        int rowDelayDecrement = 10;
+        int currentDelay = 140;
+        int totalDelay = 180;
+        int numItems = hideAnimatedList.size();
+        for (int i = numItems - 1; i >= 0; i--) {
+            View view = hideAnimatedList.get(i);
+            Runnable endRunnable = null;
+            if (i == 0) {
+                endRunnable = animationFinishAction;
+            }
+            dismissViewAnimated(view, endRunnable, totalDelay, 260);
+            currentDelay = Math.max(50, currentDelay - rowDelayDecrement);
+            totalDelay += currentDelay;
+        }
+    }
+
+    @VisibleForTesting
+    protected void inflateFooterView() {
+        FooterView footerView = (FooterView) LayoutInflater.from(mContext).inflate(
+                R.layout.status_bar_notification_footer, this, false);
+        footerView.setDismissButtonClickListener(v -> {
+            mMetricsLogger.action(MetricsEvent.ACTION_DISMISS_ALL_NOTES);
+            clearAllNotifications();
+        });
+        footerView.setManageButtonClickListener(this::manageNotifications);
+        setFooterView(footerView);
+    }
+
+    private void inflateEmptyShadeView() {
+        EmptyShadeView view = (EmptyShadeView) LayoutInflater.from(mContext).inflate(
+                R.layout.status_bar_no_notifications, this, false);
+        view.setText(R.string.empty_shade_text);
+        setEmptyShadeView(view);
+    }
+
+    /**
+     * Updates expanded, dimmed and locked states of notification rows.
+     */
+    public void onUpdateRowStates() {
+        changeViewPosition(mFooterView, -1);
+
+        // The following views will be moved to the end of mStackScroller. This counter represents
+        // the offset from the last child. Initialized to 1 for the very last position. It is post-
+        // incremented in the following "changeViewPosition" calls so that its value is correct for
+        // subsequent calls.
+        int offsetFromEnd = 1;
+        changeViewPosition(mEmptyShadeView,
+                getChildCount() - offsetFromEnd++);
+
+        // No post-increment for this call because it is the last one. Make sure to add one if
+        // another "changeViewPosition" call is ever added.
+        changeViewPosition(mShelf,
+                getChildCount() - offsetFromEnd);
+
+        // Scrim opacity varies based on notification count
+        mScrimController.setNotificationCount(getNotGoneChildCount());
+    }
+
+    public void setNotificationPanel(NotificationPanelView notificationPanelView) {
+        mNotificationPanel = notificationPanelView;
+    }
+
     /**
      * A listener that is notified when the empty space below the notifications is clicked on
      */
@@ -4731,10 +5007,11 @@
         /**
          * Notifies a listener that the overscroll has changed.
          *
-         * @param amount the amount of overscroll, in pixels
+         * @param amount         the amount of overscroll, in pixels
          * @param isRubberbanded if true, this is a rubberbanded overscroll; if false, this is an
-         *                     unrubberbanded motion to directly expand overscroll view (e.g expand
-         *                     QS)
+         *                       unrubberbanded motion to directly expand overscroll view (e.g
+         *                       expand
+         *                       QS)
          */
         void onOverscrollTopChanged(float amount, boolean isRubberbanded);
 
@@ -4743,7 +5020,7 @@
          * start a fling animation to the expanded or collapsed overscroll view (e.g expand the QS)
          *
          * @param velocity The velocity that the Scroller had when over flinging
-         * @param open Should the fling open or close the overscroll view.
+         * @param open     Should the fling open or close the overscroll view.
          */
         void flingTopOverscroll(float velocity, boolean open);
     }
@@ -4944,6 +5221,84 @@
         }
     }
 
+    public boolean hasActiveNotifications() {
+        return !mEntryManager.getNotificationData().getActiveNotifications().isEmpty();
+    }
+
+    // ---------------------- DragDownHelper.OnDragDownListener ------------------------------------
+
+
+    /* Only ever called as a consequence of a lockscreen expansion gesture. */
+    @Override
+    public boolean onDraggedDown(View startingChild, int dragLengthY) {
+        if (mStatusBarState == StatusBarState.KEYGUARD
+                && hasActiveNotifications() && (!mStatusBar.isDozing() || mStatusBar.isPulsing())) {
+            mLockscreenGestureLogger.write(
+                    MetricsEvent.ACTION_LS_SHADE,
+                    (int) (dragLengthY / mDisplayMetrics.density),
+                    0 /* velocityDp - N/A */);
+
+            // We have notifications, go to locked shade.
+            mStatusBar.goToLockedShade(startingChild);
+            if (startingChild instanceof ExpandableNotificationRow) {
+                ExpandableNotificationRow row = (ExpandableNotificationRow) startingChild;
+                row.onExpandedByGesture(true /* drag down is always an open */);
+            }
+            return true;
+        } else {
+            // abort gesture.
+            return false;
+        }
+    }
+
+    @Override
+    public void onDragDownReset() {
+        setDimmed(true /* dimmed */, true /* animated */);
+        resetScrollPosition();
+        resetCheckSnoozeLeavebehind();
+    }
+
+    @Override
+    public void onCrossedThreshold(boolean above) {
+        setDimmed(!above /* dimmed */, true /* animate */);
+    }
+
+    @Override
+    public void onTouchSlopExceeded() {
+        cancelLongPress();
+        checkSnoozeLeavebehind();
+    }
+
+    @Override
+    public void setEmptyDragAmount(float amount) {
+        mNotificationPanel.setEmptyDragAmount(amount);
+    }
+
+    @Override
+    public boolean isFalsingCheckNeeded() {
+        return mStatusBarState == StatusBarState.KEYGUARD;
+    }
+
+    public void updateSpeedBumpIndex() {
+        int speedBumpIndex = 0;
+        int currentIndex = 0;
+        final int N = getChildCount();
+        for (int i = 0; i < N; i++) {
+            View view = getChildAt(i);
+            if (view.getVisibility() == View.GONE || !(view instanceof ExpandableNotificationRow)) {
+                continue;
+            }
+            ExpandableNotificationRow row = (ExpandableNotificationRow) view;
+            currentIndex++;
+            if (!mEntryManager.getNotificationData().isAmbient(
+                    row.getStatusBarNotification().getKey())) {
+                speedBumpIndex = currentIndex;
+            }
+        }
+        boolean noAmbient = speedBumpIndex == N;
+        updateSpeedBumpIndex(speedBumpIndex, noAmbient);
+    }
+
     private boolean isTouchInView(MotionEvent ev, View view) {
         if (view == null) {
             return false;
@@ -4985,7 +5340,7 @@
 
     static class AnimationEvent {
 
-        static AnimationFilter[] FILTERS = new AnimationFilter[] {
+        static AnimationFilter[] FILTERS = new AnimationFilter[]{
 
                 // ANIMATION_TYPE_ADD
                 new AnimationFilter()
@@ -5142,7 +5497,7 @@
                         .animateY(),
         };
 
-        static int[] LENGTHS = new int[] {
+        static int[] LENGTHS = new int[]{
 
                 // ANIMATION_TYPE_ADD
                 StackStateAnimator.ANIMATION_DURATION_APPEAR_DISAPPEAR,
@@ -5267,7 +5622,7 @@
          *
          * @param events The events of the lengths to combine.
          * @return The combined length. Depending on the event types, this might be the maximum of
-         *         all events or the length of a specific event.
+         * all events or the length of a specific event.
          */
         static long combineLength(ArrayList<AnimationEvent> events) {
             long length = 0;
@@ -5282,4 +5637,18 @@
             return length;
         }
     }
+
+    private final StateListener mStateListener = new StateListener() {
+        @Override
+        public void onStatePreChange(int oldState, int newState) {
+            if (oldState == StatusBarState.SHADE_LOCKED && newState == StatusBarState.KEYGUARD) {
+                requestAnimateEverything();
+            }
+        }
+
+        @Override
+        public void onStateChanged(int newState) {
+            setStatusBarState(newState);
+        }
+    };
 }
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/phone/ConfigurationControllerImpl.java b/packages/SystemUI/src/com/android/systemui/statusbar/phone/ConfigurationControllerImpl.java
index 7ddca17..9d40f17 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/ConfigurationControllerImpl.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/ConfigurationControllerImpl.java
@@ -52,6 +52,17 @@
     }
 
     @Override
+    public void notifyThemeChanged() {
+        ArrayList<ConfigurationListener> listeners = new ArrayList<>(mListeners);
+
+        listeners.forEach(l -> {
+            if (mListeners.contains(l)) {
+                l.onThemeChanged();
+            }
+        });
+    }
+
+    @Override
     public void onConfigurationChanged(Configuration newConfig) {
         // Avoid concurrent modification exception
         ArrayList<ConfigurationListener> listeners = new ArrayList<>(mListeners);
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/phone/HeadsUpManagerPhone.java b/packages/SystemUI/src/com/android/systemui/statusbar/phone/HeadsUpManagerPhone.java
index fc755fe..6150c2f 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/HeadsUpManagerPhone.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/HeadsUpManagerPhone.java
@@ -80,6 +80,7 @@
     private int mStatusBarState;
 
     private final StateListener mStateListener = this::setStatusBarState;
+    private AnimationStateHandler mAnimationStateHandler;
 
     private final Pools.Pool<HeadsUpEntryPhone> mEntryPool = new Pools.Pool<HeadsUpEntryPhone>() {
         private Stack<HeadsUpEntryPhone> mPoolObjects = new Stack<>();
@@ -126,6 +127,10 @@
         Dependency.get(StatusBarStateController.class).addListener(mStateListener);
     }
 
+    public void setAnimationStateHandler(AnimationStateHandler handler) {
+        mAnimationStateHandler = handler;
+    }
+
     public void destroy() {
         Dependency.get(StatusBarStateController.class).removeListener(mStateListener);
     }
@@ -350,7 +355,7 @@
 
     @Override
     public void onReorderingAllowed() {
-        mBar.getNotificationScrollLayout().setHeadsUpGoingAwayAnimationsAllowed(false);
+        mAnimationStateHandler.setHeadsUpGoingAwayAnimationsAllowed(false);
         for (NotificationData.Entry entry : mEntriesToRemoveWhenReorderingAllowed) {
             if (contains(entry.key)) {
                 // Maybe the heads-up was removed already
@@ -358,7 +363,7 @@
             }
         }
         mEntriesToRemoveWhenReorderingAllowed.clear();
-        mBar.getNotificationScrollLayout().setHeadsUpGoingAwayAnimationsAllowed(true);
+        mAnimationStateHandler.setHeadsUpGoingAwayAnimationsAllowed(true);
     }
 
     ///////////////////////////////////////////////////////////////////////////////////////////////
@@ -494,4 +499,8 @@
             }
         }
     }
+
+    public interface AnimationStateHandler {
+        void setHeadsUpGoingAwayAnimationsAllowed(boolean allowed);
+    }
 }
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/phone/NotificationIconAreaController.java b/packages/SystemUI/src/com/android/systemui/statusbar/phone/NotificationIconAreaController.java
index cb3a0d7..b19f57d 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/NotificationIconAreaController.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/NotificationIconAreaController.java
@@ -8,6 +8,7 @@
 import androidx.collection.ArrayMap;
 import android.view.LayoutInflater;
 import android.view.View;
+import android.view.ViewGroup;
 import android.widget.FrameLayout;
 
 import com.android.internal.statusbar.StatusBarIcon;
@@ -45,7 +46,7 @@
     private NotificationIconContainer mNotificationIcons;
     private NotificationIconContainer mShelfIcons;
     private final Rect mTintArea = new Rect();
-    private NotificationStackScrollLayout mNotificationScrollLayout;
+    private ViewGroup mNotificationScrollLayout;
     private Context mContext;
     private boolean mFullyDark;
     private boolean mHasShelfIconsWhenFullyDark;
@@ -71,8 +72,7 @@
 
         LayoutInflater layoutInflater = LayoutInflater.from(context);
         mNotificationIconArea = inflateIconArea(layoutInflater);
-        mNotificationIcons = (NotificationIconContainer) mNotificationIconArea.findViewById(
-                R.id.notificationIcons);
+        mNotificationIcons = mNotificationIconArea.findViewById(R.id.notificationIcons);
 
         mNotificationScrollLayout = mStatusBar.getNotificationScrollLayout();
     }
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/phone/NotificationPanelView.java b/packages/SystemUI/src/com/android/systemui/statusbar/phone/NotificationPanelView.java
index b4989c0..5d65b56 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/NotificationPanelView.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/NotificationPanelView.java
@@ -33,6 +33,7 @@
 import android.graphics.Canvas;
 import android.graphics.Color;
 import android.graphics.Paint;
+import android.graphics.PointF;
 import android.graphics.PorterDuff;
 import android.graphics.PorterDuffXfermode;
 import android.graphics.Rect;
@@ -67,20 +68,25 @@
 import com.android.systemui.statusbar.KeyguardAffordanceView;
 import com.android.systemui.statusbar.KeyguardIndicationController;
 import com.android.systemui.statusbar.NotificationShelf;
+import com.android.systemui.statusbar.RemoteInputController;
 import com.android.systemui.statusbar.StatusBarState;
 import com.android.systemui.statusbar.StatusBarStateController;
 import com.android.systemui.statusbar.StatusBarStateController.StateListener;
 import com.android.systemui.statusbar.notification.ActivityLaunchAnimator;
 import com.android.systemui.statusbar.notification.AnimatableProperty;
 import com.android.systemui.statusbar.notification.NotificationData;
+import com.android.systemui.statusbar.notification.NotificationEntryManager;
 import com.android.systemui.statusbar.notification.PropertyAnimator;
+import com.android.systemui.statusbar.notification.row.ActivatableNotificationView;
 import com.android.systemui.statusbar.notification.row.ExpandableNotificationRow;
 import com.android.systemui.statusbar.notification.row.ExpandableView;
 import com.android.systemui.statusbar.notification.stack.AnimationProperties;
 import com.android.systemui.statusbar.notification.stack.NotificationStackScrollLayout;
 import com.android.systemui.statusbar.notification.stack.StackStateAnimator;
+import com.android.systemui.statusbar.policy.ConfigurationController;
 import com.android.systemui.statusbar.policy.KeyguardUserSwitcher;
 import com.android.systemui.statusbar.policy.OnHeadsUpChangedListener;
+import com.android.systemui.statusbar.policy.ZenModeController;
 
 import java.io.FileDescriptor;
 import java.io.PrintWriter;
@@ -92,7 +98,8 @@
         ExpandableView.OnHeightChangedListener,
         View.OnClickListener, NotificationStackScrollLayout.OnOverscrollTopChangedListener,
         KeyguardAffordanceHelper.Callback, NotificationStackScrollLayout.OnEmptySpaceClickListener,
-        OnHeadsUpChangedListener, QS.HeightListener {
+        OnHeadsUpChangedListener, QS.HeightListener, ZenModeController.Callback,
+        ConfigurationController.ConfigurationListener {
 
     private static final boolean DEBUG = false;
 
@@ -315,6 +322,8 @@
             .setDuration(200)
             .setAnimationFinishListener(mAnimatorListenerAdapter)
             .setCustomInterpolator(PANEL_ALPHA.getProperty(), Interpolators.ALPHA_IN);
+    private final NotificationEntryManager mEntryManager =
+            Dependency.get(NotificationEntryManager.class);
 
     private final StateListener mListener = this::setBarState;
 
@@ -329,7 +338,7 @@
         setPanelAlpha(255, false /* animate */);
     }
 
-    public void setStatusBar(StatusBar bar) {
+    private void setStatusBar(StatusBar bar) {
         mStatusBar = bar;
         mKeyguardBottomArea.setStatusBar(mStatusBar);
     }
@@ -360,6 +369,8 @@
         super.onAttachedToWindow();
         FragmentHostManager.get(this).addTagListener(QS.TAG, mFragmentListener);
         Dependency.get(StatusBarStateController.class).addListener(mListener);
+        Dependency.get(ZenModeController.class).addCallback(this);
+        Dependency.get(ConfigurationController.class).addCallback(this);
     }
 
     @Override
@@ -367,6 +378,8 @@
         super.onDetachedFromWindow();
         FragmentHostManager.get(this).removeTagListener(QS.TAG, mFragmentListener);
         Dependency.get(StatusBarStateController.class).removeListener(mListener);
+        Dependency.get(ZenModeController.class).removeCallback(this);
+        Dependency.get(ConfigurationController.class).removeCallback(this);
     }
 
     @Override
@@ -413,7 +426,15 @@
         }
     }
 
+    @Override
+    public void onDensityOrFontScaleChanged() {
+        updateShowEmptyShadeView();
+    }
+
+    @Override
     public void onThemeChanged() {
+        updateShowEmptyShadeView();
+
         // Re-inflate the status view group.
         int index = indexOfChild(mKeyguardStatusView);
         removeView(mKeyguardStatusView);
@@ -589,8 +610,8 @@
                 continue;
             }
             ExpandableNotificationRow row = (ExpandableNotificationRow) child;
-            boolean suppressedSummary = mGroupManager.isSummaryOfSuppressedGroup(
-                    row.getStatusBarNotification());
+            boolean suppressedSummary = mGroupManager != null
+                    && mGroupManager.isSummaryOfSuppressedGroup(row.getStatusBarNotification());
             if (suppressedSummary) {
                 continue;
             }
@@ -2349,7 +2370,6 @@
     }
 
     private void updateEmptyShadeView() {
-
         // Hide "No notifications" in QS.
         mNotificationStackScroller.updateEmptyShadeView(mShowEmptyShadeView && !mQsExpanded);
     }
@@ -2720,7 +2740,7 @@
         return !tasks.isEmpty() && pkgName.equals(tasks.get(0).topActivity.getPackageName());
     }
 
-    public void setGroupManager(NotificationGroupManager groupManager) {
+    private void setGroupManager(NotificationGroupManager groupManager) {
         mGroupManager = groupManager;
     }
 
@@ -2770,14 +2790,17 @@
     };
 
     @Override
-    public void setTouchDisabled(boolean disabled) {
-        super.setTouchDisabled(disabled);
+    public void setTouchAndAnimationDisabled(boolean disabled) {
+        super.setTouchAndAnimationDisabled(disabled);
         if (disabled && mAffordanceHelper.isSwipingInProgress() && !mIsLaunchTransitionRunning) {
             mAffordanceHelper.reset(false /* animate */);
         }
+        mNotificationStackScroller.setAnimationsEnabled(!disabled);
     }
 
-    public void setDozing(boolean dozing, boolean animate) {
+    public void setDozing(boolean dozing, boolean animate,
+            PointF wakeUpTouchLocation) {
+        mNotificationStackScroller.setDark(mDozing, animate, wakeUpTouchLocation);
         if (dozing == mDozing) return;
         mDozing = dozing;
 
@@ -2922,4 +2945,84 @@
             mKeyguardStatusBar.dump(fd, pw, args);
         }
     }
+
+    public boolean hasActiveClearableNotifications() {
+        return mNotificationStackScroller.hasActiveClearableNotifications();
+    }
+
+    @Override
+    public void onZenChanged(int zen) {
+        updateShowEmptyShadeView();
+    }
+
+    private void updateShowEmptyShadeView() {
+        boolean showEmptyShadeView =
+                mBarState != StatusBarState.KEYGUARD &&
+                        mEntryManager.getNotificationData().getActiveNotifications().size() == 0;
+        showEmptyShadeView(showEmptyShadeView);
+    }
+
+    public RemoteInputController.Delegate createRemoteInputDelegate() {
+        return mNotificationStackScroller.createDelegate();
+    }
+
+    public void updateNotificationViews() {
+        if (mNotificationStackScroller == null) return;
+        mNotificationStackScroller.updateSpeedBumpIndex();
+        mNotificationStackScroller.updateFooter();
+        updateShowEmptyShadeView();
+    }
+
+    public void onUpdateRowStates() {
+        mNotificationStackScroller.onUpdateRowStates();
+    }
+
+    public boolean hasPulsingNotifications() {
+        return mNotificationStackScroller.hasPulsingNotifications();
+    }
+
+    public boolean isFullyDark() {
+        return mNotificationStackScroller.isFullyDark();
+    }
+
+    public ActivatableNotificationView getActivatedChild() {
+        return mNotificationStackScroller.getActivatedChild();
+    }
+
+    public void setActivatedChild(ActivatableNotificationView o) {
+        mNotificationStackScroller.setActivatedChild(o);
+    }
+
+    public void setParentNotFullyVisible(boolean parent) {
+        mNotificationStackScroller.setParentNotFullyVisible(parent);
+    }
+
+    public void runAfterAnimationFinished(Runnable r) {
+        mNotificationStackScroller.runAfterAnimationFinished(r);
+    }
+
+    public void setScrollingEnabled(boolean b) {
+        mNotificationStackScroller.setScrollingEnabled(b);
+    }
+
+    public void initDependencies(StatusBar statusBar, NotificationGroupManager groupManager,
+            NotificationShelf notificationShelf,
+            HeadsUpManagerPhone headsUpManager,
+            NotificationIconAreaController notificationIconAreaController,
+            ScrimController scrimController) {
+        setStatusBar(statusBar);
+        setGroupManager(mGroupManager);
+        mNotificationStackScroller.setNotificationPanel(this);
+        mNotificationStackScroller.setIconAreaController(notificationIconAreaController);
+        mNotificationStackScroller.setStatusBar(statusBar);
+        mNotificationStackScroller.setGroupManager(groupManager);
+        mNotificationStackScroller.setHeadsUpManager(headsUpManager);
+        mNotificationStackScroller.setShelf(notificationShelf);
+        mNotificationStackScroller.setScrimController(scrimController);
+        updateShowEmptyShadeView();
+    }
+
+    public void setDrawBackgroundAsSrc(boolean asSrc) {
+        mNotificationStackScroller.setDrawBackgroundAsSrc(asSrc);
+    }
 }
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/phone/PanelView.java b/packages/SystemUI/src/com/android/systemui/statusbar/phone/PanelView.java
index 7b9dc0e..bef34f6 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/PanelView.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/PanelView.java
@@ -230,7 +230,7 @@
         if (mVelocityTracker != null) mVelocityTracker.addMovement(event);
     }
 
-    public void setTouchDisabled(boolean disabled) {
+    public void setTouchAndAnimationDisabled(boolean disabled) {
         mTouchDisabled = disabled;
         if (mTouchDisabled) {
             cancelHeightAnimator();
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 068dc76..a318e15 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/StatusBar.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/StatusBar.java
@@ -148,6 +148,7 @@
 import com.android.systemui.AutoReinflateContainer;
 import com.android.systemui.DemoMode;
 import com.android.systemui.Dependency;
+import com.android.systemui.Dumpable;
 import com.android.systemui.EventLogTags;
 import com.android.systemui.Interpolators;
 import com.android.systemui.Prefs;
@@ -191,10 +192,8 @@
 import com.android.systemui.statusbar.BackDropView;
 import com.android.systemui.statusbar.CommandQueue;
 import com.android.systemui.statusbar.CrossFadeHelper;
-import com.android.systemui.statusbar.DragDownHelper;
 import com.android.systemui.statusbar.EmptyShadeView;
 import com.android.systemui.statusbar.notification.row.ExpandableNotificationRow;
-import com.android.systemui.statusbar.notification.row.FooterView;
 import com.android.systemui.statusbar.GestureRecorder;
 import com.android.systemui.statusbar.KeyboardShortcuts;
 import com.android.systemui.statusbar.KeyguardIndicationController;
@@ -218,6 +217,7 @@
 import com.android.systemui.statusbar.notification.AboveShelfObserver;
 import com.android.systemui.statusbar.notification.ActivityLaunchAnimator;
 import com.android.systemui.statusbar.notification.VisualStabilityManager;
+import com.android.systemui.statusbar.notification.stack.NotificationListContainer;
 import com.android.systemui.statusbar.notification.stack.NotificationStackScrollLayout;
 import com.android.systemui.statusbar.phone.UnlockMethodCache.OnUnlockMethodChangedListener;
 import com.android.systemui.statusbar.policy.BatteryController;
@@ -248,11 +248,10 @@
 import java.io.PrintWriter;
 import java.io.StringWriter;
 import java.util.ArrayList;
-import java.util.List;
 import java.util.Map;
 
 public class StatusBar extends SystemUI implements DemoMode,
-        DragDownHelper.DragDownCallback, ActivityStarter, OnUnlockMethodChangedListener,
+        ActivityStarter, OnUnlockMethodChangedListener,
         OnHeadsUpChangedListener, CommandQueue.Callbacks, ZenModeController.Callback,
         ColorExtractor.OnColorsChangedListener, ConfigurationListener, NotificationPresenter,
         StatusBarStateController.StateListener {
@@ -425,12 +424,11 @@
     private int mSystemUiVisibility = View.SYSTEM_UI_FLAG_VISIBLE;
     private final Rect mLastFullscreenStackBounds = new Rect();
     private final Rect mLastDockedStackBounds = new Rect();
-    private final Rect mTmpRect = new Rect();
 
     // last value sent to window manager
     private int mLastDispatchedSystemUiVisibility = ~View.SYSTEM_UI_FLAG_VISIBLE;
 
-    private final DisplayMetrics mDisplayMetrics = new DisplayMetrics();
+    private final DisplayMetrics mDisplayMetrics = Dependency.get(DisplayMetrics.class);
 
     // XXX: gesture research
     private final GestureRecorder mGestureRec = DEBUG_GESTURES
@@ -571,10 +569,10 @@
     private boolean mKeyguardRequested;
     private boolean mIsKeyguard;
     private LogMaker mStatusBarStateLog;
-    private final LockscreenGestureLogger mLockscreenGestureLogger = new LockscreenGestureLogger();
+    private final LockscreenGestureLogger mLockscreenGestureLogger =
+            Dependency.get(LockscreenGestureLogger.class);
     protected NotificationIconAreaController mNotificationIconAreaController;
     private boolean mReinflateNotificationsOnUserSwitched;
-    protected boolean mClearAllEnabled;
     @Nullable private View mAmbientIndicationContainer;
     private SysuiColorExtractor mColorExtractor;
     private ScreenLifecycle mScreenLifecycle;
@@ -655,7 +653,6 @@
                 R.bool.config_vibrateOnIconAnimation);
         mVibratorHelper = Dependency.get(VibratorHelper.class);
         mScrimSrcModeEnabled = res.getBoolean(R.bool.config_status_bar_scrim_behind_use_src);
-        mClearAllEnabled = res.getBoolean(R.bool.config_enableNotificationsClearAll);
 
         DateTimeView.setReceiverHandler(Dependency.get(Dependency.TIME_TICK_HANDLER));
         putComponent(StatusBar.class, this);
@@ -797,12 +794,13 @@
         // into fragments, but the rest here, it leaves some awkward lifecycle and whatnot.
         mNotificationPanel = mStatusBarWindow.findViewById(R.id.notification_panel);
         mStackScroller = mStatusBarWindow.findViewById(R.id.notification_stack_scroller);
+        NotificationListContainer notifListContainer = (NotificationListContainer) mStackScroller;
         mZenController.addCallback(this);
         mActivityLaunchAnimator = new ActivityLaunchAnimator(mStatusBarWindow,
                 this,
                 mNotificationPanel,
-                mStackScroller);
-        mGutsManager.setUpWithPresenter(this, mEntryManager, mStackScroller, mCheckSaveListener,
+                notifListContainer);
+        mGutsManager.setUpWithPresenter(this, mEntryManager, notifListContainer, mCheckSaveListener,
                 key -> {
                     try {
                         mBarService.onNotificationSettingsViewed(key);
@@ -810,9 +808,7 @@
                         // if we're here we're dead
                     }
                 });
-        mNotificationLogger.setUpWithEntryManager(mEntryManager, mStackScroller);
-        mNotificationPanel.setStatusBar(this);
-        mNotificationPanel.setGroupManager(mGroupManager);
+        mNotificationLogger.setUpWithEntryManager(mEntryManager, notifListContainer);
         mAboveShelfObserver = new AboveShelfObserver(mStackScroller);
         mAboveShelfObserver.setListener(mStatusBarWindow.findViewById(
                 R.id.notification_container_parent));
@@ -822,7 +818,7 @@
                 .createNotificationIconAreaController(context, this);
         inflateShelf();
         mNotificationIconAreaController.setupShelf(mNotificationShelf);
-        mStackScroller.setIconAreaController(mNotificationIconAreaController);
+
         Dependency.get(DarkIconDispatcher.class).addDarkReceiver(mNotificationIconAreaController);
         FragmentHostManager.get(mStatusBarWindow)
                 .addTagListener(CollapsedStatusBarFragment.TAG, (tag, fragment) -> {
@@ -881,8 +877,8 @@
         mGroupManager.setHeadsUpManager(mHeadsUpManager);
         putComponent(HeadsUpManager.class, mHeadsUpManager);
 
-        mEntryManager.setUpWithPresenter(this, mStackScroller, this, mHeadsUpManager);
-        mViewHierarchyManager.setUpWithPresenter(this, mEntryManager, mStackScroller);
+        mEntryManager.setUpWithPresenter(this, notifListContainer, this, mHeadsUpManager);
+        mViewHierarchyManager.setUpWithPresenter(this, mEntryManager, notifListContainer);
 
         if (MULTIUSER_DEBUG) {
             mNotificationPanelDebugText = mNotificationPanel.findViewById(R.id.header_debug_info);
@@ -898,15 +894,6 @@
         } catch (RemoteException ex) {
             // no window manager? good luck with that
         }
-        mStackScroller.setLongPressListener(mEntryManager.getNotificationLongClicker());
-        mStackScroller.setStatusBar(this);
-        mStackScroller.setGroupManager(mGroupManager);
-        mStackScroller.setHeadsUpManager(mHeadsUpManager);
-        mGroupManager.setOnGroupChangeListener(mStackScroller);
-        mVisualStabilityManager.setVisibilityLocationProvider(mStackScroller);
-
-        inflateEmptyShadeView();
-        inflateFooterView();
 
         mBackdrop = mStatusBarWindow.findViewById(R.id.backdrop);
         mBackdropFront = mBackdrop.findViewById(R.id.backdrop_front);
@@ -965,12 +952,13 @@
             Runnable runnable = () -> {
                 boolean asSrc = mBackdrop.getVisibility() != View.VISIBLE;
                 mScrimController.setDrawBehindAsSrc(asSrc);
-                mStackScroller.setDrawBackgroundAsSrc(asSrc);
+                mNotificationPanel.setDrawBackgroundAsSrc(asSrc);
             };
             mBackdrop.setOnVisibilityChangedRunnable(runnable);
             runnable.run();
         }
-        mStackScroller.setScrimController(mScrimController);
+        mNotificationPanel.initDependencies(this, mGroupManager, mNotificationShelf,
+                mHeadsUpManager, mNotificationIconAreaController, mScrimController);
         mDozeScrimController = new DozeScrimController(mScrimController, context,
                 DozeParameters.getInstance(context));
 
@@ -1112,10 +1100,10 @@
                 (NotificationShelf) LayoutInflater.from(mContext).inflate(
                         R.layout.status_bar_notification_shelf, mStackScroller, false);
         mNotificationShelf.setOnActivatedListener(this);
-        mStackScroller.setShelf(mNotificationShelf);
         mNotificationShelf.setOnClickListener(mGoToLockedShadeListener);
     }
 
+    @Override
     public void onDensityOrFontScaleChanged() {
         MessagingMessage.dropCache();
         MessagingGroup.dropCache();
@@ -1140,17 +1128,10 @@
         }
         mNotificationIconAreaController.onDensityOrFontScaleChanged(mContext);
         mHeadsUpManager.onDensityOrFontScaleChanged();
-
-        inflateFooterView();
-        inflateEmptyShadeView();
-        reevaluateStyles();
     }
 
-    private void onThemeChanged() {
-        reevaluateStyles();
-
-        // Clock and bottom icons
-        mNotificationPanel.onThemeChanged();
+    @Override
+    public void onThemeChanged() {
         // The status bar on the keyguard is a special layout.
         if (mKeyguardStatusBar != null) mKeyguardStatusBar.onThemeChanged();
         // Recreate Indication controller because internal references changed
@@ -1171,11 +1152,6 @@
         }
     }
 
-    private void reevaluateStyles() {
-        updateFooter();
-        updateEmptyShadeView();
-    }
-
     @Override
     public void onOverlayChanged() {
         if (mBrightnessMirrorController != null) {
@@ -1193,37 +1169,6 @@
         if (mBrightnessMirrorController != null) {
             mBrightnessMirrorController.onUiModeChanged();
         }
-        if (mStackScroller != null) {
-            mStackScroller.onUiModeChanged();
-        }
-    }
-
-    private void inflateEmptyShadeView() {
-        if (mStackScroller == null) {
-            return;
-        }
-        mEmptyShadeView = (EmptyShadeView) LayoutInflater.from(mContext).inflate(
-                R.layout.status_bar_no_notifications, mStackScroller, false);
-        mEmptyShadeView.setText(R.string.empty_shade_text);
-        mStackScroller.setEmptyShadeView(mEmptyShadeView);
-    }
-
-    @VisibleForTesting
-    protected void inflateFooterView() {
-        if (mStackScroller == null) {
-            return;
-        }
-
-        mFooterView = (FooterView) LayoutInflater.from(mContext).inflate(
-                R.layout.status_bar_notification_footer, mStackScroller, false);
-        mFooterView.setDismissButtonClickListener(v -> {
-            mMetricsLogger.action(MetricsEvent.ACTION_DISMISS_ALL_NOTES);
-            clearAllNotifications();
-        });
-        mFooterView.setManageButtonClickListener(v -> {
-            manageNotifications();
-        });
-        mStackScroller.setFooterView(mFooterView);
     }
 
     protected void createUserSwitcher() {
@@ -1237,105 +1182,6 @@
                 R.layout.super_status_bar, null);
     }
 
-    public void manageNotifications() {
-        Intent intent = new Intent(Settings.ACTION_ALL_APPS_NOTIFICATION_SETTINGS);
-        startActivity(intent, true, true, Intent.FLAG_ACTIVITY_SINGLE_TOP);
-    }
-
-    public void clearAllNotifications() {
-        // animate-swipe all dismissable notifications, then animate the shade closed
-        int numChildren = mStackScroller.getChildCount();
-
-        final ArrayList<View> viewsToHide = new ArrayList<>(numChildren);
-        final ArrayList<ExpandableNotificationRow> viewsToRemove = new ArrayList<>(numChildren);
-        for (int i = 0; i < numChildren; i++) {
-            final View child = mStackScroller.getChildAt(i);
-            if (child instanceof ExpandableNotificationRow) {
-                ExpandableNotificationRow row = (ExpandableNotificationRow) child;
-                boolean parentVisible = false;
-                boolean hasClipBounds = child.getClipBounds(mTmpRect);
-                if (mStackScroller.canChildBeDismissed(child)) {
-                    viewsToRemove.add(row);
-                    if (child.getVisibility() == View.VISIBLE
-                            && (!hasClipBounds || mTmpRect.height() > 0)) {
-                        viewsToHide.add(child);
-                        parentVisible = true;
-                    }
-                } else if (child.getVisibility() == View.VISIBLE
-                        && (!hasClipBounds || mTmpRect.height() > 0)) {
-                    parentVisible = true;
-                }
-                List<ExpandableNotificationRow> children = row.getNotificationChildren();
-                if (children != null) {
-                    for (ExpandableNotificationRow childRow : children) {
-                        viewsToRemove.add(childRow);
-                        if (parentVisible && row.areChildrenExpanded()
-                                && mStackScroller.canChildBeDismissed(childRow)) {
-                            hasClipBounds = childRow.getClipBounds(mTmpRect);
-                            if (childRow.getVisibility() == View.VISIBLE
-                                    && (!hasClipBounds || mTmpRect.height() > 0)) {
-                                viewsToHide.add(childRow);
-                            }
-                        }
-                    }
-                }
-            }
-        }
-        if (viewsToRemove.isEmpty()) {
-            animateCollapsePanels(CommandQueue.FLAG_EXCLUDE_NONE);
-            return;
-        }
-
-        addPostCollapseAction(() -> {
-            mStackScroller.setDismissAllInProgress(false);
-            for (ExpandableNotificationRow rowToRemove : viewsToRemove) {
-                if (mStackScroller.canChildBeDismissed(rowToRemove)) {
-                    mEntryManager.removeNotification(rowToRemove.getEntry().key, null);
-                } else {
-                    rowToRemove.resetTranslation();
-                }
-            }
-            try {
-                mBarService.onClearAllNotifications(mLockscreenUserManager.getCurrentUserId());
-            } catch (Exception ex) {
-            }
-        });
-
-        performDismissAllAnimations(viewsToHide);
-
-    }
-
-    private void performDismissAllAnimations(ArrayList<View> hideAnimatedList) {
-        Runnable animationFinishAction = () -> {
-            animateCollapsePanels(CommandQueue.FLAG_EXCLUDE_NONE);
-        };
-
-        if (hideAnimatedList.isEmpty()) {
-            animationFinishAction.run();
-            return;
-        }
-
-        // let's disable our normal animations
-        mStackScroller.setDismissAllInProgress(true);
-
-        // Decrease the delay for every row we animate to give the sense of
-        // accelerating the swipes
-        int rowDelayDecrement = 10;
-        int currentDelay = 140;
-        int totalDelay = 180;
-        int numItems = hideAnimatedList.size();
-        for (int i = numItems - 1; i >= 0; i--) {
-            View view = hideAnimatedList.get(i);
-            Runnable endRunnable = null;
-            if (i == 0) {
-                endRunnable = animationFinishAction;
-            }
-            mStackScroller.dismissViewAnimated(view, endRunnable, totalDelay, 260);
-            currentDelay = Math.max(50, currentDelay - rowDelayDecrement);
-            totalDelay += currentDelay;
-        }
-    }
-
     protected void startKeyguard() {
         Trace.beginSection("StatusBar#startKeyguard");
         KeyguardViewMediator keyguardViewMediator = getComponent(KeyguardViewMediator.class);
@@ -1408,7 +1254,7 @@
 
     @Override
     public void onPerformRemoveNotification(StatusBarNotification n) {
-        if (mStackScroller.hasPulsingNotifications() &&
+        if (mNotificationPanel.hasPulsingNotifications() &&
                     !mHeadsUpManager.hasNotifications()) {
             // We were showing a pulse for a notification, but no notifications are pulsing anymore.
             // Finish the pulse.
@@ -1420,7 +1266,7 @@
     public void updateNotificationViews() {
         // The function updateRowStates depends on both of these being non-null, so check them here.
         // We may be called before they are set from DeviceProvisionedController's callback.
-        if (mStackScroller == null || mScrimController == null) return;
+        if (mScrimController == null) return;
 
         // Do not modify the notifications during collapse.
         if (isCollapsing()) {
@@ -1430,9 +1276,7 @@
 
         mViewHierarchyManager.updateNotificationViews();
 
-        updateSpeedBumpIndex();
-        updateFooter();
-        updateEmptyShadeView();
+        mNotificationPanel.updateNotificationViews();
 
         updateQsExpansionEnabled();
 
@@ -1499,61 +1343,6 @@
         mQSPanel.clickTile(tile);
     }
 
-    @VisibleForTesting
-    protected void updateFooter() {
-        boolean showDismissView = mClearAllEnabled && hasActiveClearableNotifications();
-        boolean showFooterView = (showDismissView ||
-                        mEntryManager.getNotificationData().getActiveNotifications().size() != 0)
-                && mState != StatusBarState.KEYGUARD
-                && !mRemoteInputManager.getController().isRemoteInputActive();
-
-        mStackScroller.updateFooterView(showFooterView, showDismissView);
-    }
-
-    /**
-     * Return whether there are any clearable notifications
-     */
-    private boolean hasActiveClearableNotifications() {
-        int childCount = mStackScroller.getChildCount();
-        for (int i = 0; i < childCount; i++) {
-            View child = mStackScroller.getChildAt(i);
-            if (!(child instanceof ExpandableNotificationRow)) {
-                continue;
-            }
-            if (((ExpandableNotificationRow) child).canViewBeDismissed()) {
-                    return true;
-            }
-        }
-        return false;
-    }
-
-    private void updateEmptyShadeView() {
-        boolean showEmptyShadeView =
-                mState != StatusBarState.KEYGUARD &&
-                        mEntryManager.getNotificationData().getActiveNotifications().size() == 0;
-        mNotificationPanel.showEmptyShadeView(showEmptyShadeView);
-    }
-
-    private void updateSpeedBumpIndex() {
-        int speedBumpIndex = 0;
-        int currentIndex = 0;
-        final int N = mStackScroller.getChildCount();
-        for (int i = 0; i < N; i++) {
-            View view = mStackScroller.getChildAt(i);
-            if (view.getVisibility() == View.GONE || !(view instanceof ExpandableNotificationRow)) {
-                continue;
-            }
-            ExpandableNotificationRow row = (ExpandableNotificationRow) view;
-            currentIndex++;
-            if (!mEntryManager.getNotificationData().isAmbient(
-                    row.getStatusBarNotification().getKey())) {
-                speedBumpIndex = currentIndex;
-            }
-        }
-        boolean noAmbient = speedBumpIndex == N;
-        mStackScroller.updateSpeedBumpIndex(speedBumpIndex, noAmbient);
-    }
-
     public static boolean isTopLevelChild(Entry entry) {
         return entry.row.getParent() instanceof NotificationStackScrollLayout;
     }
@@ -1570,7 +1359,7 @@
 
         if (SPEW) {
             final boolean clearable = hasActiveNotifications() &&
-                    hasActiveClearableNotifications();
+                    mNotificationPanel.hasActiveClearableNotifications();
             Log.d(TAG, "setAreThereNotifications: N=" +
                     mEntryManager.getNotificationData().getActiveNotifications().size() + " any=" +
                     hasActiveNotifications() + " clearable=" + clearable);
@@ -1884,7 +1673,7 @@
     /**
      * Reapplies the disable flags as last requested by StatusBarManager.
      *
-     * This needs to be called if state used by {@link #adjustDisableFlags} changes.
+     * This needs to be called if state used by adjustDisableFlags changes.
      */
     public void recomputeDisableFlags(boolean animate) {
         mCommandQueue.recomputeDisableFlags(animate);
@@ -1894,7 +1683,7 @@
         return new StatusBar.H();
     }
 
-    private void startActivity(Intent intent, boolean onlyProvisioned, boolean dismissShade,
+    public void startActivity(Intent intent, boolean onlyProvisioned, boolean dismissShade,
             int flags) {
         startActivityDismissingKeyguard(intent, onlyProvisioned, dismissShade, flags);
     }
@@ -1932,7 +1721,7 @@
 
     @Override
     public boolean isDozing() {
-        return mDozing && mStackScroller.isFullyDark();
+        return mDozing && mNotificationPanel.isFullyDark();
     }
 
     @Override
@@ -2017,7 +1806,7 @@
                 // we need to keep the panel open artificially, let's wait until the animation
                 // is finished.
                 mHeadsUpManager.setHeadsUpGoingAway(true);
-                mStackScroller.runAfterAnimationFinished(() -> {
+                mNotificationPanel.runAfterAnimationFinished(() -> {
                     if (!mHeadsUpManager.hasPinnedHeadsUp()) {
                         mStatusBarWindowController.setHeadsUpShowing(false);
                         mHeadsUpManager.setHeadsUpGoingAway(false);
@@ -2073,7 +1862,7 @@
         }
     }
 
-    public NotificationStackScrollLayout getNotificationScrollLayout() {
+    public ViewGroup getNotificationScrollLayout() {
         return mStackScroller;
     }
 
@@ -2796,9 +2585,9 @@
             mNotificationPanel.dump(fd, pw, args);
         }
         pw.println("  mStackScroller: ");
-        if (mStackScroller != null) {
+        if (mStackScroller instanceof Dumpable) {
             pw.print  ("      ");
-            mStackScroller.dump(fd, pw, args);
+            ((Dumpable) mStackScroller).dump(fd, pw, args);
         }
         pw.println("  Theme:");
         String nightMode = mUiModeManager == null ? "null" : mUiModeManager.getNightMode() + "";
@@ -2890,21 +2679,7 @@
         makeStatusBarView();
         mStatusBarWindowController = Dependency.get(StatusBarWindowController.class);
         mRemoteInputManager.setUpWithPresenter(this, mEntryManager, this,
-                new RemoteInputController.Delegate() {
-                    public void setRemoteInputActive(NotificationData.Entry entry,
-                            boolean remoteInputActive) {
-                        mHeadsUpManager.setRemoteInputActive(entry, remoteInputActive);
-                        entry.row.notifyHeightChanged(true /* needsAnimation */);
-                        updateFooter();
-                    }
-                    public void lockScrollTo(NotificationData.Entry entry) {
-                        mStackScroller.lockScrollTo(entry.row);
-                    }
-                    public void requestDisallowLongPressAndDismiss() {
-                        mStackScroller.requestDisallowLongPress();
-                        mStackScroller.requestDisallowDismiss();
-                    }
-                });
+                mNotificationPanel.createRemoteInputDelegate());
         mRemoteInputManager.getController().addCallback(mStatusBarWindowController);
         mStatusBarWindowController.add(mStatusBarWindow, getStatusBarHeight());
     }
@@ -3619,7 +3394,7 @@
             updateScrimController();
             updateMediaMetaData(false, true);
             mNotificationPanel.setAlpha(1);
-            mStackScroller.setParentNotFullyVisible(true);
+            mNotificationPanel.setParentNotFullyVisible(true);
             mNotificationPanel.animate()
                     .alpha(0)
                     .setStartDelay(FADE_KEYGUARD_START_DELAY)
@@ -3814,7 +3589,6 @@
      * Switches theme from light to dark and vice-versa.
      */
     protected void updateTheme() {
-        final boolean inflated = mStackScroller != null && mStatusBarWindowController != null;
 
         // Lock wallpaper defines the color of the majority of the views, hence we'll use it
         // to set our default theme.
@@ -3823,24 +3597,7 @@
         final int themeResId = lockDarkText ? R.style.Theme_SystemUI_Light : R.style.Theme_SystemUI;
         if (mContext.getThemeResId() != themeResId) {
             mContext.setTheme(themeResId);
-            if (inflated) {
-                onThemeChanged();
-            }
-        }
-
-        if (inflated) {
-            int which;
-            if (mState == StatusBarState.KEYGUARD || mState == StatusBarState.SHADE_LOCKED) {
-                which = WallpaperManager.FLAG_LOCK;
-            } else {
-                which = WallpaperManager.FLAG_SYSTEM;
-            }
-            final boolean useDarkText = mColorExtractor.getColors(which,
-                    true /* ignoreVisibility */).supportsDarkText();
-            mStackScroller.updateDecorViews(useDarkText);
-
-            // Make sure we have the correct navbar/statusbar colors.
-            mStatusBarWindowController.setKeyguardDark(useDarkText);
+            Dependency.get(ConfigurationController.class).notifyThemeChanged();
         }
     }
 
@@ -3853,10 +3610,9 @@
         boolean animate = (!mDozing && mDozeServiceHost.shouldAnimateWakeup())
                 || (mDozing && mDozeServiceHost.shouldAnimateScreenOff() && sleepingFromKeyguard);
 
-        mStackScroller.setDark(mDozing, animate, mWakeUpTouchLocation);
         mDozeScrimController.setDozing(mDozing);
         mKeyguardIndicationController.setDozing(mDozing);
-        mNotificationPanel.setDozing(mDozing, animate);
+        mNotificationPanel.setDozing(mDozing, animate, mWakeUpTouchLocation);
         mNotificationLogger.setDozing(mDozing);
         updateQsExpansionEnabled();
         Trace.endSection();
@@ -3953,7 +3709,7 @@
     @Override
     public void onActivated(ActivatableNotificationView view) {
         onActivated((View) view);
-        mStackScroller.setActivatedChild(view);
+        mNotificationPanel.setActivatedChild(view);
     }
 
     public void onActivated(View view) {
@@ -3961,7 +3717,7 @@
                 MetricsEvent.ACTION_LS_NOTE,
                 0 /* lengthDp - N/A */, 0 /* velocityDp - N/A */);
         mKeyguardIndicationController.showTransientIndication(R.string.notification_tap_again);
-        ActivatableNotificationView previousView = mStackScroller.getActivatedChild();
+        ActivatableNotificationView previousView = mNotificationPanel.getActivatedChild();
         if (previousView != null) {
             previousView.makeInactive(true /* animate */);
         }
@@ -4031,8 +3787,8 @@
 
     @Override
     public void onActivationReset(ActivatableNotificationView view) {
-        if (view == mStackScroller.getActivatedChild()) {
-            mStackScroller.setActivatedChild(null);
+        if (view == mNotificationPanel.getActivatedChild()) {
+            mNotificationPanel.setActivatedChild(null);
             onActivationReset((View)view);
         }
     }
@@ -4098,8 +3854,9 @@
         return mMaxKeyguardNotifications;
     }
 
-    public int getMaxNotificationsWhileLocked() {
-        return getMaxNotificationsWhileLocked(false /* recompute */);
+    @Override
+    public void onUpdateRowStates() {
+        mNotificationPanel.onUpdateRowStates();
     }
 
     // TODO: Figure out way to remove these.
@@ -4119,60 +3876,6 @@
         return mNotificationPanel.getKeyguardBottomAreaView();
     }
 
-    // ---------------------- DragDownHelper.OnDragDownListener ------------------------------------
-
-
-    /* Only ever called as a consequence of a lockscreen expansion gesture. */
-    @Override
-    public boolean onDraggedDown(View startingChild, int dragLengthY) {
-        if (mState == StatusBarState.KEYGUARD
-                && hasActiveNotifications() && (!isDozing() || isPulsing())) {
-            mLockscreenGestureLogger.write(
-                    MetricsEvent.ACTION_LS_SHADE,
-                    (int) (dragLengthY / mDisplayMetrics.density),
-                    0 /* velocityDp - N/A */);
-
-            // We have notifications, go to locked shade.
-            goToLockedShade(startingChild);
-            if (startingChild instanceof ExpandableNotificationRow) {
-                ExpandableNotificationRow row = (ExpandableNotificationRow) startingChild;
-                row.onExpandedByGesture(true /* drag down is always an open */);
-            }
-            return true;
-        } else {
-            // abort gesture.
-            return false;
-        }
-    }
-
-    @Override
-    public void onDragDownReset() {
-        mStackScroller.setDimmed(true /* dimmed */, true /* animated */);
-        mStackScroller.resetScrollPosition();
-        mStackScroller.resetCheckSnoozeLeavebehind();
-    }
-
-    @Override
-    public void onCrossedThreshold(boolean above) {
-        mStackScroller.setDimmed(!above /* dimmed */, true /* animate */);
-    }
-
-    @Override
-    public void onTouchSlopExceeded() {
-        mStackScroller.cancelLongPress();
-        mStackScroller.checkSnoozeLeavebehind();
-    }
-
-    @Override
-    public void setEmptyDragAmount(float amount) {
-        mNotificationPanel.setEmptyDragAmount(amount);
-    }
-
-    @Override
-    public boolean isFalsingCheckNeeded() {
-        return mState == StatusBarState.KEYGUARD;
-    }
-
     /**
      * If secure with redaction: Show bouncer, go to unlocked shade.
      *
@@ -4397,7 +4100,6 @@
      */
     public void goToKeyguard() {
         if (mState == StatusBarState.SHADE_LOCKED) {
-            mStackScroller.onGoToKeyguard();
             mStatusBarStateController.setState(StatusBarState.KEYGUARD);
         }
     }
@@ -4431,14 +4133,13 @@
             mDeviceInteractive = false;
             mWakeUpComingFromTouch = false;
             mWakeUpTouchLocation = null;
-            mStackScroller.setAnimationsEnabled(false);
             mVisualStabilityManager.setScreenOn(false);
             updateVisibleToUser();
 
             // We need to disable touch events because these might
             // collapse the panel after we expanded it, and thus we would end up with a blank
             // Keyguard.
-            mNotificationPanel.setTouchDisabled(true);
+            mNotificationPanel.setTouchAndAnimationDisabled(true);
             mStatusBarWindow.cancelCurrentTouch();
             if (mLaunchCameraOnFinishedGoingToSleep) {
                 mLaunchCameraOnFinishedGoingToSleep = false;
@@ -4459,9 +4160,8 @@
         @Override
         public void onStartedWakingUp() {
             mDeviceInteractive = true;
-            mStackScroller.setAnimationsEnabled(true);
             mVisualStabilityManager.setScreenOn(true);
-            mNotificationPanel.setTouchDisabled(false);
+            mNotificationPanel.setTouchAndAnimationDisabled(false);
             mDozeServiceHost.stopDozing();
             updateVisibleToUser();
             updateIsKeyguard();
@@ -4931,7 +4631,7 @@
     protected IStatusBarService mBarService;
 
     // all notifications
-    protected NotificationStackScrollLayout mStackScroller;
+    protected ViewGroup mStackScroller;
 
     protected NotificationGroupManager mGroupManager;
 
@@ -4972,7 +4672,6 @@
     protected RecentsComponent mRecents;
 
     protected NotificationShelf mNotificationShelf;
-    protected FooterView mFooterView;
     protected EmptyShadeView mEmptyShadeView;
 
     protected AssistManager mAssistManager;
@@ -5168,7 +4867,7 @@
         if (Looper.getMainLooper().isCurrentThread()) {
             collapsePanel();
         } else {
-            mStackScroller.post(this::collapsePanel);
+            Dependency.get(Dependency.MAIN_HANDLER).post(this::collapsePanel);
         }
     }
 
@@ -5447,33 +5146,6 @@
         }
     }
 
-    /**
-     * Updates expanded, dimmed and locked states of notification rows.
-     */
-    @Override
-    public void onUpdateRowStates() {
-        // The following views will be moved to the end of mStackScroller. This counter represents
-        // the offset from the last child. Initialized to 1 for the very last position. It is post-
-        // incremented in the following "changeViewPosition" calls so that its value is correct for
-        // subsequent calls.
-        int offsetFromEnd = 1;
-        if (mFooterView != null) {
-            mStackScroller.changeViewPosition(mFooterView,
-                    mStackScroller.getChildCount() - offsetFromEnd++);
-        }
-
-        mStackScroller.changeViewPosition(mEmptyShadeView,
-                mStackScroller.getChildCount() - offsetFromEnd++);
-
-        // No post-increment for this call because it is the last one. Make sure to add one if
-        // another "changeViewPosition" call is ever added.
-        mStackScroller.changeViewPosition(mNotificationShelf,
-                mStackScroller.getChildCount() - offsetFromEnd);
-
-        // Scrim opacity varies based on notification count
-        mScrimController.setNotificationCount(mStackScroller.getNotGoneChildCount());
-    }
-
     protected void notifyHeadsUpGoingToSleep() {
         maybeEscalateHeadsUp();
     }
@@ -5520,11 +5192,6 @@
     }
 
     @Override
-    public void onZenChanged(int zen) {
-        updateEmptyShadeView();
-    }
-
-    @Override
     public void showAssistDisclosure() {
         if (mAssistManager != null) {
             mAssistManager.showDisclosure();
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/phone/StatusBarWindowController.java b/packages/SystemUI/src/com/android/systemui/statusbar/phone/StatusBarWindowController.java
index c30e190..46264f9 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/StatusBarWindowController.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/StatusBarWindowController.java
@@ -22,6 +22,7 @@
 
 import android.app.ActivityManager;
 import android.app.IActivityManager;
+import android.app.WallpaperManager;
 import android.content.Context;
 import android.content.pm.ActivityInfo;
 import android.content.res.Resources;
@@ -40,11 +41,14 @@
 import com.android.keyguard.R;
 import com.android.systemui.Dependency;
 import com.android.systemui.Dumpable;
+import com.android.systemui.colorextraction.SysuiColorExtractor;
 import com.android.systemui.keyguard.KeyguardViewMediator;
-import com.android.systemui.statusbar.RemoteInputController;
+import com.android.systemui.statusbar.RemoteInputController.Callback;
 import com.android.systemui.statusbar.StatusBarState;
 import com.android.systemui.statusbar.StatusBarStateController;
 import com.android.systemui.statusbar.StatusBarStateController.StateListener;
+import com.android.systemui.statusbar.policy.ConfigurationController;
+import com.android.systemui.statusbar.policy.ConfigurationController.ConfigurationListener;
 
 import java.io.FileDescriptor;
 import java.io.PrintWriter;
@@ -53,7 +57,7 @@
 /**
  * Encapsulates all logic for the status bar window state management.
  */
-public class StatusBarWindowController implements RemoteInputController.Callback, Dumpable {
+public class StatusBarWindowController implements Callback, Dumpable, ConfigurationListener {
 
     private static final String TAG = "StatusBarWindowController";
 
@@ -73,6 +77,7 @@
     private OtherwisedCollapsedListener mListener;
 
     private final StateListener mStateListener = this::setStatusBarState;
+    private final SysuiColorExtractor mColorExtractor = Dependency.get(SysuiColorExtractor.class);
 
     public StatusBarWindowController(Context context) {
         this(context, context.getSystemService(WindowManager.class), ActivityManager.getService(),
@@ -89,6 +94,7 @@
         mDozeParameters = dozeParameters;
         mScreenBrightnessDoze = mDozeParameters.getScreenBrightnessDoze();
         Dependency.get(StatusBarStateController.class).addListener(mStateListener);
+        Dependency.get(ConfigurationController.class).addCallback(this);
     }
 
     private boolean shouldEnableKeyguardScreenRotation() {
@@ -135,7 +141,7 @@
         mScreenBrightnessDoze = value / 255f;
     }
 
-    public void setKeyguardDark(boolean dark) {
+    private void setKeyguardDark(boolean dark) {
         int vis = mStatusBarView.getSystemUiVisibility();
         if (dark) {
             vis = vis | View.SYSTEM_UI_FLAG_LIGHT_NAVIGATION_BAR;
@@ -461,6 +467,23 @@
         return !mCurrentState.backdropShowing;
     }
 
+    @Override
+    public void onThemeChanged() {
+        StatusBarStateController state = Dependency.get(StatusBarStateController.class);
+        int which;
+        if (state.getState() == StatusBarState.KEYGUARD
+                || state.getState() == StatusBarState.SHADE_LOCKED) {
+            which = WallpaperManager.FLAG_LOCK;
+        } else {
+            which = WallpaperManager.FLAG_SYSTEM;
+        }
+        final boolean useDarkText = mColorExtractor.getColors(which,
+                true /* ignoreVisibility */).supportsDarkText();
+
+        // Make sure we have the correct navbar/statusbar colors.
+        setKeyguardDark(useDarkText);
+    }
+
     private static class State {
         boolean keyguardShowing;
         boolean keyguardOccluded;
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/phone/StatusBarWindowView.java b/packages/SystemUI/src/com/android/systemui/statusbar/phone/StatusBarWindowView.java
index 765c788..98f1a36 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/StatusBarWindowView.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/StatusBarWindowView.java
@@ -215,7 +215,8 @@
 
     public void setService(StatusBar service) {
         mService = service;
-        setDragDownHelper(new DragDownHelper(getContext(), this, mStackScrollLayout, mService));
+        setDragDownHelper(new DragDownHelper(getContext(), this, mStackScrollLayout,
+                mStackScrollLayout));
     }
 
     @VisibleForTesting
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/policy/ConfigurationController.java b/packages/SystemUI/src/com/android/systemui/statusbar/policy/ConfigurationController.java
index 8c631d9..0e5c8c1 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/policy/ConfigurationController.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/policy/ConfigurationController.java
@@ -24,11 +24,14 @@
  */
 public interface ConfigurationController extends CallbackController<ConfigurationListener> {
 
+    public void notifyThemeChanged();
+
     interface ConfigurationListener {
         default void onConfigChanged(Configuration newConfig) {}
         default void onDensityOrFontScaleChanged() {}
         default void onOverlayChanged() {}
         default void onUiModeChanged() {}
+        default void onThemeChanged() {}
         default void onLocaleListChanged() {}
     }
 }
diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/stack/NotificationStackScrollLayoutTest.java b/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/stack/NotificationStackScrollLayoutTest.java
index 1560f6f..c19188c 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/stack/NotificationStackScrollLayoutTest.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/stack/NotificationStackScrollLayoutTest.java
@@ -3,10 +3,7 @@
  *
  * 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
- *
+ * 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.
@@ -16,9 +13,13 @@
 
 package com.android.systemui.statusbar.notification.stack;
 
+import static junit.framework.Assert.assertEquals;
+import static junit.framework.Assert.assertNotNull;
 import static org.mockito.ArgumentMatchers.any;
 import static org.mockito.ArgumentMatchers.anyBoolean;
+import static org.mockito.ArgumentMatchers.anyInt;
 import static org.mockito.ArgumentMatchers.eq;
+import static org.mockito.Mockito.clearInvocations;
 import static org.mockito.Mockito.doNothing;
 import static org.mockito.Mockito.mock;
 import static org.mockito.Mockito.reset;
@@ -26,6 +27,10 @@
 import static org.mockito.Mockito.verify;
 import static org.mockito.Mockito.when;
 
+import android.os.Handler;
+import android.os.IPowerManager;
+import android.os.Looper;
+import android.os.PowerManager;
 import android.support.test.annotation.UiThreadTest;
 import android.support.test.filters.SmallTest;
 import android.support.test.runner.AndroidJUnit4;
@@ -33,22 +38,34 @@
 import com.android.systemui.ExpandHelper;
 import com.android.systemui.R;
 import com.android.systemui.SysuiTestCase;
+import com.android.systemui.recents.misc.SystemServicesProxy;
 import com.android.systemui.statusbar.EmptyShadeView;
 import com.android.systemui.statusbar.StatusBarState;
+import com.android.systemui.statusbar.NotificationPresenter;
+import com.android.systemui.statusbar.notification.NotificationData;
+import com.android.systemui.statusbar.notification.NotificationData.Entry;
+import com.android.systemui.statusbar.notification.NotificationEntryManager;
+import com.android.systemui.statusbar.notification.row.ExpandableNotificationRow;
 import com.android.systemui.statusbar.notification.row.FooterView;
 import com.android.systemui.statusbar.notification.row.NotificationBlockingHelperManager;
+import com.android.systemui.statusbar.NotificationRemoteInputManager;
 import com.android.systemui.statusbar.NotificationShelf;
+import com.android.systemui.statusbar.RemoteInputController;
 import com.android.systemui.statusbar.StatusBarStateController;
 import com.android.systemui.statusbar.phone.HeadsUpManagerPhone;
 import com.android.systemui.statusbar.phone.NotificationGroupManager;
 import com.android.systemui.statusbar.phone.ScrimController;
 import com.android.systemui.statusbar.phone.StatusBar;
+import com.android.systemui.statusbar.phone.StatusBarTest.TestableNotificationEntryManager;
+
+import java.util.ArrayList;
 
 import org.junit.Assert;
 import org.junit.Before;
 import org.junit.Rule;
 import org.junit.Test;
 import org.junit.runner.RunWith;
+import org.mockito.ArgumentCaptor;
 import org.mockito.Mock;
 import org.mockito.junit.MockitoJUnit;
 import org.mockito.junit.MockitoRule;
@@ -70,6 +87,12 @@
     @Mock private NotificationGroupManager mGroupManager;
     @Mock private ExpandHelper mExpandHelper;
     @Mock private EmptyShadeView mEmptyShadeView;
+    @Mock private NotificationData mNotificationData;
+    @Mock private NotificationRemoteInputManager mRemoteInputManager;
+    @Mock private RemoteInputController mRemoteInputController;
+    @Mock private SystemServicesProxy mSystemServicesProxy;
+    private PowerManager mPowerManager;
+    private TestableNotificationEntryManager mEntryManager;
 
     @Before
     @UiThreadTest
@@ -79,9 +102,21 @@
                 NotificationBlockingHelperManager.class,
                 mBlockingHelperManager);
         mDependency.injectTestDependency(StatusBarStateController.class, mBarState);
+        mDependency.injectTestDependency(NotificationRemoteInputManager.class,
+                mRemoteInputManager);
+        when(mRemoteInputManager.getController()).thenReturn(mRemoteInputController);
+
+        IPowerManager powerManagerService = mock(IPowerManager.class);
+        mPowerManager = new PowerManager(mContext, powerManagerService,
+                Handler.createAsync(Looper.myLooper()));
+
+        mEntryManager = new TestableNotificationEntryManager(mSystemServicesProxy, mPowerManager,
+                mContext);
+        mEntryManager.setUpForTest(mock(NotificationPresenter.class), null, null, null, mNotificationData);
+        mDependency.injectTestDependency(NotificationEntryManager.class, mEntryManager);
 
         NotificationShelf notificationShelf = spy(new NotificationShelf(getContext(), null));
-        mStackScroller = new NotificationStackScrollLayout(getContext());
+        mStackScroller = spy(new NotificationStackScrollLayout(getContext()));
         mStackScroller.setShelf(notificationShelf);
         mStackScroller.setStatusBar(mBar);
         mStackScroller.setScrimController(mock(ScrimController.class));
@@ -189,4 +224,98 @@
         verify(view).setVisible(eq(true), anyBoolean());
         verify(view).setSecondaryVisible(eq(true), anyBoolean());
     }
+
+    @Test
+    public void testInflateFooterView() {
+        mStackScroller.inflateFooterView();
+        ArgumentCaptor<FooterView> captor = ArgumentCaptor.forClass(FooterView.class);
+        verify(mStackScroller).setFooterView(captor.capture());
+
+        assertNotNull(captor.getValue().findViewById(R.id.manage_text).hasOnClickListeners());
+        assertNotNull(captor.getValue().findViewById(R.id.dismiss_text).hasOnClickListeners());
+    }
+
+    @Test
+    public void testUpdateFooter_noNotifications() {
+        setBarStateForTest(StatusBarState.SHADE);
+        assertEquals(0, mNotificationData.getActiveNotifications().size());
+
+        mStackScroller.updateFooter();
+        verify(mStackScroller).updateFooterView(false, false);
+    }
+
+    @Test
+    public void testUpdateFooter_remoteInput() {
+        setBarStateForTest(StatusBarState.SHADE);
+        ArrayList<Entry> entries = new ArrayList<>();
+        entries.add(mock(Entry.class));
+        when(mNotificationData.getActiveNotifications()).thenReturn(entries);
+
+        ExpandableNotificationRow row = mock(ExpandableNotificationRow.class);
+        when(row.canViewBeDismissed()).thenReturn(true);
+        when(mStackScroller.getChildCount()).thenReturn(1);
+        when(mStackScroller.getChildAt(anyInt())).thenReturn(row);
+        when(mRemoteInputController.isRemoteInputActive()).thenReturn(true);
+
+        mStackScroller.updateFooter();
+        verify(mStackScroller).updateFooterView(false, true);
+    }
+
+    @Test
+    public void testUpdateFooter_oneClearableNotification() {
+        setBarStateForTest(StatusBarState.SHADE);
+        ArrayList<Entry> entries = new ArrayList<>();
+        entries.add(mock(Entry.class));
+        when(mNotificationData.getActiveNotifications()).thenReturn(entries);
+
+        ExpandableNotificationRow row = mock(ExpandableNotificationRow.class);
+        when(row.canViewBeDismissed()).thenReturn(true);
+        when(mStackScroller.getChildCount()).thenReturn(1);
+        when(mStackScroller.getChildAt(anyInt())).thenReturn(row);
+
+        mStackScroller.updateFooter();
+        verify(mStackScroller).updateFooterView(true, true);
+    }
+
+    @Test
+    public void testUpdateFooter_oneNonClearableNotification() {
+        setBarStateForTest(StatusBarState.SHADE);
+        ArrayList<Entry> entries = new ArrayList<>();
+        entries.add(mock(Entry.class));
+        when(mNotificationData.getActiveNotifications()).thenReturn(entries);
+
+        mStackScroller.updateFooter();
+        verify(mStackScroller).updateFooterView(true, false);
+    }
+
+    @Test
+    public void testUpdateFooter_atEnd() {
+        // add footer
+        mStackScroller.inflateFooterView();
+
+        // add notification
+        ExpandableNotificationRow row = mock(ExpandableNotificationRow.class);
+        when(row.isClearable()).thenReturn(true);
+        mStackScroller.addContainerView(row);
+
+        mStackScroller.onUpdateRowStates();
+
+        // move footer to end
+        verify(mStackScroller).changeViewPosition(any(FooterView.class), eq(-1 /* end */));
+    }
+
+    @Test
+    public void testOnDensityOrFontScaleChanged_reInflatesFooterViews() {
+        clearInvocations(mStackScroller);
+        mStackScroller.onDensityOrFontScaleChanged();
+        verify(mStackScroller).setFooterView(any());
+        verify(mStackScroller).setEmptyShadeView(any());
+    }
+
+    private void setBarStateForTest(int state) {
+        ArgumentCaptor<StatusBarStateController.StateListener> captor =
+                ArgumentCaptor.forClass(StatusBarStateController.StateListener.class);
+        verify(mBarState).addListener(captor.capture());
+        captor.getValue().onStateChanged(state);
+    }
 }
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 7686233..cbba251 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
@@ -77,8 +77,6 @@
 import com.android.systemui.statusbar.notification.row.ActivatableNotificationView;
 import com.android.systemui.statusbar.notification.AppOpsListener;
 import com.android.systemui.statusbar.CommandQueue;
-import com.android.systemui.statusbar.notification.row.ExpandableNotificationRow;
-import com.android.systemui.statusbar.notification.row.FooterView;
 import com.android.systemui.statusbar.KeyguardIndicationController;
 import com.android.systemui.statusbar.notification.NotificationData;
 import com.android.systemui.statusbar.notification.NotificationData.Entry;
@@ -105,7 +103,6 @@
 import org.junit.Before;
 import org.junit.Test;
 import org.junit.runner.RunWith;
-import org.mockito.ArgumentCaptor;
 import org.mockito.Mock;
 import org.mockito.MockitoAnnotations;
 
@@ -593,85 +590,7 @@
     }
 
     @Test
-    public void testInflateFooterView() {
-        mStatusBar.inflateFooterView();
-        ArgumentCaptor<FooterView> captor = ArgumentCaptor.forClass(FooterView.class);
-        verify(mStackScroller).setFooterView(captor.capture());
 
-        assertNotNull(captor.getValue().findViewById(R.id.manage_text).hasOnClickListeners());
-        assertNotNull(captor.getValue().findViewById(R.id.dismiss_text).hasOnClickListeners());
-    }
-
-    @Test
-    public void testUpdateFooter_noNotifications() {
-        mStatusBar.setBarStateForTest(StatusBarState.SHADE);
-        assertEquals(0, mEntryManager.getNotificationData().getActiveNotifications().size());
-
-        mStatusBar.updateFooter();
-        verify(mStackScroller).updateFooterView(false, false);
-    }
-
-    @Test
-    public void testUpdateFooter_remoteInput() {
-        mStatusBar.setBarStateForTest(StatusBarState.SHADE);
-        ArrayList<Entry> entries = new ArrayList<>();
-        entries.add(mock(Entry.class));
-        when(mNotificationData.getActiveNotifications()).thenReturn(entries);
-
-        ExpandableNotificationRow row = mock(ExpandableNotificationRow.class);
-        when(row.canViewBeDismissed()).thenReturn(true);
-        when(mStackScroller.getChildCount()).thenReturn(1);
-        when(mStackScroller.getChildAt(anyInt())).thenReturn(row);
-        when(mRemoteInputController.isRemoteInputActive()).thenReturn(true);
-
-        mStatusBar.updateFooter();
-        verify(mStackScroller).updateFooterView(false, true);
-    }
-
-    @Test
-    public void testUpdateFooter_oneClearableNotification() {
-        mStatusBar.setBarStateForTest(StatusBarState.SHADE);
-        ArrayList<Entry> entries = new ArrayList<>();
-        entries.add(mock(Entry.class));
-        when(mNotificationData.getActiveNotifications()).thenReturn(entries);
-
-        ExpandableNotificationRow row = mock(ExpandableNotificationRow.class);
-        when(row.canViewBeDismissed()).thenReturn(true);
-        when(mStackScroller.getChildCount()).thenReturn(1);
-        when(mStackScroller.getChildAt(anyInt())).thenReturn(row);
-
-        mStatusBar.updateFooter();
-        verify(mStackScroller).updateFooterView(true, true);
-    }
-
-    @Test
-    public void testUpdateFooter_oneNonClearableNotification() {
-        mStatusBar.setBarStateForTest(StatusBarState.SHADE);
-        ArrayList<Entry> entries = new ArrayList<>();
-        entries.add(mock(Entry.class));
-        when(mNotificationData.getActiveNotifications()).thenReturn(entries);
-
-        mStatusBar.updateFooter();
-        verify(mStackScroller).updateFooterView(true, false);
-    }
-
-    @Test
-    public void testUpdateFooter_atEnd() {
-        // add footer
-        mStatusBar.inflateFooterView();
-
-        // add notification
-        ExpandableNotificationRow row = mock(ExpandableNotificationRow.class);
-        when(row.isClearable()).thenReturn(true);
-        mStackScroller.addContainerView(row);
-
-        mStatusBar.onUpdateRowStates();
-
-        // move footer to end
-        verify(mStackScroller).changeViewPosition(any(FooterView.class), eq(-1 /* end */));
-    }
-
-    @Test
     public void testSetState_changesIsFullScreenUserSwitcherState() {
         mStatusBar.setBarStateForTest(StatusBarState.KEYGUARD);
         assertFalse(mStatusBar.isFullScreenUserSwitcherState());
@@ -697,13 +616,6 @@
         verify(mStatusBarStateController).setState(eq(StatusBarState.FULLSCREEN_USER_SWITCHER));
     }
 
-    @Test
-    public void testOnDensityOrFontScaleChanged_reInflatesFooterViews() {
-        mStatusBar.onDensityOrFontScaleChanged();
-        verify(mStackScroller).setFooterView(any());
-        verify(mStackScroller).setEmptyShadeView(any());
-    }
-
     static class TestableStatusBar extends StatusBar {
         public TestableStatusBar(StatusBarKeyguardViewManager man,
                 UnlockMethodCache unlock, KeyguardIndicationController key,
@@ -743,7 +655,6 @@
             mBiometricUnlockController = biometricUnlockController;
             mActivityLaunchAnimator = launchAnimator;
             mKeyguardViewMediator = keyguardViewMediator;
-            mClearAllEnabled = true;
             mRemoteInputManager = notificationRemoteInputManager;
             mGroupManager = notificationGroupManager;
             mFalsingManager = falsingManager;
@@ -777,7 +688,7 @@
 
     }
 
-    private class TestableNotificationEntryManager extends NotificationEntryManager {
+    public static class TestableNotificationEntryManager extends NotificationEntryManager {
 
         public TestableNotificationEntryManager(SystemServicesProxy systemServicesProxy,
                 PowerManager powerManager, Context context) {
diff --git a/packages/SystemUI/tests/src/com/android/systemui/utils/leaks/FakeConfigurationController.java b/packages/SystemUI/tests/src/com/android/systemui/utils/leaks/FakeConfigurationController.java
index 9ef30c3..5ddf7a2 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/utils/leaks/FakeConfigurationController.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/utils/leaks/FakeConfigurationController.java
@@ -23,4 +23,8 @@
     public FakeConfigurationController(LeakCheckedTest.SysuiLeakCheck sysuiLeakCheck) {
         super(sysuiLeakCheck, "config");
     }
+
+    @Override
+    public void notifyThemeChanged() {
+    }
 }