Import Android SDK Platform P [4524038]

/google/data/ro/projects/android/fetch_artifact \
    --bid 4524038 \
    --target sdk_phone_armv7-win_sdk \
    sdk-repo-linux-sources-4524038.zip

AndroidVersion.ApiLevel has been modified to appear as 28

Change-Id: Ic193bf1cf0cae78d4f2bfb4fbddfe42025c5c3c2
diff --git a/com/android/systemui/statusbar/ExpandableNotificationRow.java b/com/android/systemui/statusbar/ExpandableNotificationRow.java
index 23d9cae..f53eb48 100644
--- a/com/android/systemui/statusbar/ExpandableNotificationRow.java
+++ b/com/android/systemui/statusbar/ExpandableNotificationRow.java
@@ -112,6 +112,7 @@
     private int mNotificationMaxHeight;
     private int mNotificationAmbientHeight;
     private int mIncreasedPaddingBetweenElements;
+    private boolean mMustStayOnScreen;
 
     /** Does this row contain layouts that can adapt to row expansion */
     private boolean mExpandable;
@@ -205,7 +206,7 @@
     private OnClickListener mExpandClickListener = new OnClickListener() {
         @Override
         public void onClick(View v) {
-            if (!mShowingPublic && (!mIsLowPriority || isExpanded())
+            if (!shouldShowPublic() && (!mIsLowPriority || isExpanded())
                     && mGroupManager.isSummaryOfGroup(mStatusBarNotification)) {
                 mGroupExpansionChanging = true;
                 final boolean wasExpanded = mGroupManager.isGroupExpanded(mStatusBarNotification);
@@ -491,6 +492,7 @@
             notifyHeightChanged(false  /* needsAnimation */);
         }
         if (isHeadsUp) {
+            mMustStayOnScreen = true;
             setAboveShelf(true);
         } else if (isAboveShelf() != wasAboveShelf) {
             mAboveShelfChangedListener.onAboveShelfStateChanged(!wasAboveShelf);
@@ -517,6 +519,12 @@
         addChildNotification(row, -1);
     }
 
+    @Override
+    public void setHeadsUpIsVisible() {
+        super.setHeadsUpIsVisible();
+        mMustStayOnScreen = false;
+    }
+
     /**
      * Add a child notification to this view.
      *
@@ -782,7 +790,7 @@
      * {@link #getNotificationHeader()} in case it is a low-priority group.
      */
     public NotificationHeaderView getVisibleNotificationHeader() {
-        if (mIsSummaryWithChildren && !mShowingPublic) {
+        if (mIsSummaryWithChildren && !shouldShowPublic()) {
             return mChildrenContainer.getVisibleHeader();
         }
         return getShowingLayout().getVisibleNotificationHeader();
@@ -1504,10 +1512,10 @@
     }
 
     private void updateChildrenVisibility() {
-        mPrivateLayout.setVisibility(!mShowingPublic && !mIsSummaryWithChildren ? VISIBLE
+        mPrivateLayout.setVisibility(!shouldShowPublic() && !mIsSummaryWithChildren ? VISIBLE
                 : INVISIBLE);
         if (mChildrenContainer != null) {
-            mChildrenContainer.setVisibility(!mShowingPublic && mIsSummaryWithChildren ? VISIBLE
+            mChildrenContainer.setVisibility(!shouldShowPublic() && mIsSummaryWithChildren ? VISIBLE
                     : INVISIBLE);
         }
         // The limits might have changed if the view suddenly became a group or vice versa
@@ -1558,7 +1566,7 @@
     }
 
     public boolean isExpandable() {
-        if (mIsSummaryWithChildren && !mShowingPublic) {
+        if (mIsSummaryWithChildren && !shouldShowPublic()) {
             return !mChildrenExpanded;
         }
         return mEnableNonGroupedNotificationExpand && mExpandable;
@@ -1603,7 +1611,7 @@
      */
     public void setUserExpanded(boolean userExpanded, boolean allowChildExpansion) {
         mFalsingManager.setNotificationExpanded();
-        if (mIsSummaryWithChildren && !mShowingPublic && allowChildExpansion
+        if (mIsSummaryWithChildren && !shouldShowPublic() && allowChildExpansion
                 && !mChildrenContainer.showingAsLowPriority()) {
             final boolean wasExpanded = mGroupManager.isGroupExpanded(mStatusBarNotification);
             mGroupManager.setGroupExpanded(mStatusBarNotification, userExpanded);
@@ -1898,7 +1906,7 @@
             mPublicLayout.setVisibility(mShowingPublic ? View.VISIBLE : View.INVISIBLE);
             updateChildrenVisibility();
         } else {
-            animateShowingPublic(delay, duration);
+            animateShowingPublic(delay, duration, mShowingPublic);
         }
         NotificationContentView showingLayout = getShowingLayout();
         showingLayout.updateBackgroundColor(animated);
@@ -1908,13 +1916,13 @@
         mShowingPublicInitialized = true;
     }
 
-    private void animateShowingPublic(long delay, long duration) {
+    private void animateShowingPublic(long delay, long duration, boolean showingPublic) {
         View[] privateViews = mIsSummaryWithChildren
                 ? new View[] {mChildrenContainer}
                 : new View[] {mPrivateLayout};
         View[] publicViews = new View[] {mPublicLayout};
-        View[] hiddenChildren = mShowingPublic ? privateViews : publicViews;
-        View[] shownChildren = mShowingPublic ? publicViews : privateViews;
+        View[] hiddenChildren = showingPublic ? privateViews : publicViews;
+        View[] shownChildren = showingPublic ? publicViews : privateViews;
         for (final View hiddenView : hiddenChildren) {
             hiddenView.setVisibility(View.VISIBLE);
             hiddenView.animate().cancel();
@@ -1942,7 +1950,7 @@
 
     @Override
     public boolean mustStayOnScreen() {
-        return mIsHeadsUp;
+        return mIsHeadsUp && mMustStayOnScreen;
     }
 
     /**
@@ -1951,7 +1959,11 @@
      *         see {@link #isClearable()}.
      */
     public boolean canViewBeDismissed() {
-        return isClearable() && (!mShowingPublic || !mSensitiveHiddenInGeneral);
+        return isClearable() && (!shouldShowPublic() || !mSensitiveHiddenInGeneral);
+    }
+
+    private boolean shouldShowPublic() {
+        return mSensitive && mHideSensitiveForIntrinsicHeight;
     }
 
     public void makeActionsVisibile() {
@@ -1997,7 +2009,7 @@
 
     @Override
     public boolean isContentExpandable() {
-        if (mIsSummaryWithChildren && !mShowingPublic) {
+        if (mIsSummaryWithChildren && !shouldShowPublic()) {
             return true;
         }
         NotificationContentView showingLayout = getShowingLayout();
@@ -2006,7 +2018,7 @@
 
     @Override
     protected View getContentView() {
-        if (mIsSummaryWithChildren && !mShowingPublic) {
+        if (mIsSummaryWithChildren && !shouldShowPublic()) {
             return mChildrenContainer;
         }
         return getShowingLayout();
@@ -2071,7 +2083,7 @@
 
     @Override
     public int getMaxContentHeight() {
-        if (mIsSummaryWithChildren && !mShowingPublic) {
+        if (mIsSummaryWithChildren && !shouldShowPublic()) {
             return mChildrenContainer.getMaxContentHeight();
         }
         NotificationContentView showingLayout = getShowingLayout();
@@ -2085,7 +2097,7 @@
         } else if (!ignoreTemporaryStates && isHeadsUpAllowed() && mIsHeadsUp
                 && mHeadsUpManager.isTrackingHeadsUp()) {
                 return getPinnedHeadsUpHeight(false /* atLeastMinHeight */);
-        } else if (mIsSummaryWithChildren && !isGroupExpanded() && !mShowingPublic) {
+        } else if (mIsSummaryWithChildren && !isGroupExpanded() && !shouldShowPublic()) {
             return mChildrenContainer.getMinHeight();
         } else if (!ignoreTemporaryStates && isHeadsUpAllowed() && mIsHeadsUp) {
             return mHeadsUpHeight;
@@ -2096,7 +2108,7 @@
 
     @Override
     public int getCollapsedHeight() {
-        if (mIsSummaryWithChildren && !mShowingPublic) {
+        if (mIsSummaryWithChildren && !shouldShowPublic()) {
             return mChildrenContainer.getCollapsedHeight();
         }
         return getMinHeight();
@@ -2136,7 +2148,7 @@
     }
 
     public NotificationContentView getShowingLayout() {
-        return mShowingPublic ? mPublicLayout : mPrivateLayout;
+        return shouldShowPublic() ? mPublicLayout : mPrivateLayout;
     }
 
     public void setLegacy(boolean legacy) {
@@ -2242,7 +2254,7 @@
         if (header != null && header.isInTouchRect(x - getTranslation(), y)) {
             return true;
         }
-        if ((!mIsSummaryWithChildren || mShowingPublic)
+        if ((!mIsSummaryWithChildren || shouldShowPublic())
                 && getShowingLayout().disallowSingleClick(x, y)) {
             return true;
         }
@@ -2272,7 +2284,7 @@
         if (canViewBeDismissed()) {
             info.addAction(AccessibilityNodeInfo.AccessibilityAction.ACTION_DISMISS);
         }
-        boolean expandable = mShowingPublic;
+        boolean expandable = shouldShowPublic();
         boolean isExpanded = false;
         if (!expandable) {
             if (mIsSummaryWithChildren) {
diff --git a/com/android/systemui/statusbar/ExpandableOutlineView.java b/com/android/systemui/statusbar/ExpandableOutlineView.java
index b3d6e32..db19d2f 100644
--- a/com/android/systemui/statusbar/ExpandableOutlineView.java
+++ b/com/android/systemui/statusbar/ExpandableOutlineView.java
@@ -80,10 +80,21 @@
     private final ViewOutlineProvider mProvider = new ViewOutlineProvider() {
         @Override
         public void getOutline(View view, Outline outline) {
-            Path clipPath = getClipPath();
-            if (clipPath != null && clipPath.isConvex()) {
-                // The path might not be convex in border cases where the view is small and clipped
-                outline.setConvexPath(clipPath);
+            if (!mCustomOutline && mCurrentTopRoundness == 0.0f
+                    && mCurrentBottomRoundness == 0.0f && !mAlwaysRoundBothCorners) {
+                int translation = mShouldTranslateContents ? (int) getTranslation() : 0;
+                int left = Math.max(translation + mCurrentSidePaddings, mCurrentSidePaddings);
+                int top = mClipTopAmount + mBackgroundTop;
+                int right = getWidth() - mCurrentSidePaddings + Math.min(translation, 0);
+                int bottom = Math.max(getActualHeight() - mClipBottomAmount, top);
+                outline.setRect(left, top, right, bottom);
+            } else {
+                Path clipPath = getClipPath();
+                if (clipPath != null && clipPath.isConvex()) {
+                    // The path might not be convex in border cases where the view is small and
+                    // clipped
+                    outline.setConvexPath(clipPath);
+                }
             }
             outline.setAlpha(mOutlineAlpha);
         }
diff --git a/com/android/systemui/statusbar/ExpandableView.java b/com/android/systemui/statusbar/ExpandableView.java
index 18b9860..f762513 100644
--- a/com/android/systemui/statusbar/ExpandableView.java
+++ b/com/android/systemui/statusbar/ExpandableView.java
@@ -478,6 +478,9 @@
         return false;
     }
 
+    public void setHeadsUpIsVisible() {
+    }
+
     public boolean isChildInGroup() {
         return false;
     }
diff --git a/com/android/systemui/statusbar/NotificationEntryManager.java b/com/android/systemui/statusbar/NotificationEntryManager.java
new file mode 100644
index 0000000..6bbd09f
--- /dev/null
+++ b/com/android/systemui/statusbar/NotificationEntryManager.java
@@ -0,0 +1,960 @@
+/*
+ * Copyright (C) 2017 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License
+ */
+package com.android.systemui.statusbar;
+
+import static com.android.systemui.statusbar.NotificationRemoteInputManager.ENABLE_REMOTE_INPUT;
+import static com.android.systemui.statusbar.NotificationRemoteInputManager
+        .FORCE_REMOTE_INPUT_HISTORY;
+
+import android.app.Notification;
+import android.app.NotificationManager;
+import android.app.PendingIntent;
+import android.content.Context;
+import android.content.pm.ApplicationInfo;
+import android.content.pm.PackageManager;
+import android.database.ContentObserver;
+import android.os.Build;
+import android.os.PowerManager;
+import android.os.RemoteException;
+import android.os.ServiceManager;
+import android.os.SystemClock;
+import android.provider.Settings;
+import android.service.notification.NotificationListenerService;
+import android.service.notification.NotificationStats;
+import android.service.notification.StatusBarNotification;
+import android.util.ArraySet;
+import android.util.EventLog;
+import android.util.Log;
+import android.view.View;
+import android.view.ViewGroup;
+
+import com.android.internal.logging.MetricsLogger;
+import com.android.internal.statusbar.IStatusBarService;
+import com.android.internal.util.NotificationMessagingUtil;
+import com.android.systemui.DejankUtils;
+import com.android.systemui.Dependency;
+import com.android.systemui.Dumpable;
+import com.android.systemui.EventLogTags;
+import com.android.systemui.ForegroundServiceController;
+import com.android.systemui.R;
+import com.android.systemui.UiOffloadThread;
+import com.android.systemui.recents.misc.SystemServicesProxy;
+import com.android.systemui.statusbar.notification.InflationException;
+import com.android.systemui.statusbar.notification.NotificationInflater;
+import com.android.systemui.statusbar.notification.RowInflaterTask;
+import com.android.systemui.statusbar.notification.VisualStabilityManager;
+import com.android.systemui.statusbar.phone.NotificationGroupManager;
+import com.android.systemui.statusbar.phone.StatusBar;
+import com.android.systemui.statusbar.policy.DeviceProvisionedController;
+import com.android.systemui.statusbar.policy.HeadsUpManager;
+import com.android.systemui.util.leak.LeakDetector;
+
+import java.io.FileDescriptor;
+import java.io.PrintWriter;
+import java.util.ArrayList;
+import java.util.HashMap;
+import java.util.List;
+
+/**
+ * NotificationEntryManager is responsible for the adding, removing, and updating of notifications.
+ * It also handles tasks such as their inflation and their interaction with other
+ * Notification.*Manager objects.
+ */
+public class NotificationEntryManager implements Dumpable, NotificationInflater.InflationCallback,
+        ExpandableNotificationRow.ExpansionLogger, NotificationUpdateHandler,
+        VisualStabilityManager.Callback {
+    private static final String TAG = "NotificationEntryManager";
+    protected static final boolean DEBUG = false;
+    protected static final boolean ENABLE_HEADS_UP = true;
+    protected static final String SETTING_HEADS_UP_TICKER = "ticker_gets_heads_up";
+
+    protected final NotificationMessagingUtil mMessagingUtil;
+    protected final Context mContext;
+    protected final HashMap<String, NotificationData.Entry> mPendingNotifications = new HashMap<>();
+    protected final NotificationClicker mNotificationClicker = new NotificationClicker();
+    protected final ArraySet<NotificationData.Entry> mHeadsUpEntriesToRemoveOnSwitch =
+            new ArraySet<>();
+
+    // Dependencies:
+    protected final NotificationLockscreenUserManager mLockscreenUserManager =
+            Dependency.get(NotificationLockscreenUserManager.class);
+    protected final NotificationGroupManager mGroupManager =
+            Dependency.get(NotificationGroupManager.class);
+    protected final NotificationGutsManager mGutsManager =
+            Dependency.get(NotificationGutsManager.class);
+    protected final NotificationRemoteInputManager mRemoteInputManager =
+            Dependency.get(NotificationRemoteInputManager.class);
+    protected final NotificationMediaManager mMediaManager =
+            Dependency.get(NotificationMediaManager.class);
+    protected final MetricsLogger mMetricsLogger = Dependency.get(MetricsLogger.class);
+    protected final DeviceProvisionedController mDeviceProvisionedController =
+            Dependency.get(DeviceProvisionedController.class);
+    protected final VisualStabilityManager mVisualStabilityManager =
+            Dependency.get(VisualStabilityManager.class);
+    protected final UiOffloadThread mUiOffloadThread = Dependency.get(UiOffloadThread.class);
+    protected final ForegroundServiceController mForegroundServiceController =
+            Dependency.get(ForegroundServiceController.class);
+    protected final NotificationListener mNotificationListener =
+            Dependency.get(NotificationListener.class);
+
+    protected IStatusBarService mBarService;
+    protected NotificationPresenter mPresenter;
+    protected Callback mCallback;
+    protected PowerManager mPowerManager;
+    protected SystemServicesProxy mSystemServicesProxy;
+    protected NotificationListenerService.RankingMap mLatestRankingMap;
+    protected HeadsUpManager mHeadsUpManager;
+    protected NotificationData mNotificationData;
+    protected ContentObserver mHeadsUpObserver;
+    protected boolean mUseHeadsUp = false;
+    protected boolean mDisableNotificationAlerts;
+    protected NotificationListContainer mListContainer;
+
+    private final class NotificationClicker implements View.OnClickListener {
+
+        @Override
+        public void onClick(final View v) {
+            if (!(v instanceof ExpandableNotificationRow)) {
+                Log.e(TAG, "NotificationClicker called on a view that is not a notification row.");
+                return;
+            }
+
+            mPresenter.wakeUpIfDozing(SystemClock.uptimeMillis(), v);
+
+            final ExpandableNotificationRow row = (ExpandableNotificationRow) v;
+            final StatusBarNotification sbn = row.getStatusBarNotification();
+            if (sbn == null) {
+                Log.e(TAG, "NotificationClicker called on an unclickable notification,");
+                return;
+            }
+
+            // Check if the notification is displaying the menu, if so slide notification back
+            if (row.getProvider() != null && row.getProvider().isMenuVisible()) {
+                row.animateTranslateNotification(0);
+                return;
+            }
+
+            // Mark notification for one frame.
+            row.setJustClicked(true);
+            DejankUtils.postAfterTraversal(() -> row.setJustClicked(false));
+
+            mCallback.onNotificationClicked(sbn, row);
+        }
+
+        public void register(ExpandableNotificationRow row, StatusBarNotification sbn) {
+            Notification notification = sbn.getNotification();
+            if (notification.contentIntent != null || notification.fullScreenIntent != null) {
+                row.setOnClickListener(this);
+            } else {
+                row.setOnClickListener(null);
+            }
+        }
+    }
+
+    private final DeviceProvisionedController.DeviceProvisionedListener
+            mDeviceProvisionedListener =
+            new DeviceProvisionedController.DeviceProvisionedListener() {
+                @Override
+                public void onDeviceProvisionedChanged() {
+                    updateNotifications();
+                }
+            };
+
+    public NotificationListenerService.RankingMap getLatestRankingMap() {
+        return mLatestRankingMap;
+    }
+
+    public void setLatestRankingMap(NotificationListenerService.RankingMap latestRankingMap) {
+        mLatestRankingMap = latestRankingMap;
+    }
+
+    public void setDisableNotificationAlerts(boolean disableNotificationAlerts) {
+        mDisableNotificationAlerts = disableNotificationAlerts;
+        mHeadsUpObserver.onChange(true);
+    }
+
+    public void destroy() {
+        mDeviceProvisionedController.removeCallback(mDeviceProvisionedListener);
+    }
+
+    public void onHeadsUpStateChanged(NotificationData.Entry entry, boolean isHeadsUp) {
+        if (!isHeadsUp && mHeadsUpEntriesToRemoveOnSwitch.contains(entry)) {
+            removeNotification(entry.key, getLatestRankingMap());
+            mHeadsUpEntriesToRemoveOnSwitch.remove(entry);
+            if (mHeadsUpEntriesToRemoveOnSwitch.isEmpty()) {
+                setLatestRankingMap(null);
+            }
+        } else {
+            updateNotificationRanking(null);
+        }
+    }
+
+    @Override
+    public void dump(FileDescriptor fd, PrintWriter pw, String[] args) {
+        pw.println("NotificationEntryManager state:");
+        pw.print("  mPendingNotifications=");
+        if (mPendingNotifications.size() == 0) {
+            pw.println("null");
+        } else {
+            for (NotificationData.Entry entry : mPendingNotifications.values()) {
+                pw.println(entry.notification);
+            }
+        }
+        pw.print("  mUseHeadsUp=");
+        pw.println(mUseHeadsUp);
+    }
+
+    public NotificationEntryManager(Context context) {
+        mContext = context;
+        mPowerManager = (PowerManager) mContext.getSystemService(Context.POWER_SERVICE);
+        mBarService = IStatusBarService.Stub.asInterface(
+                ServiceManager.getService(Context.STATUS_BAR_SERVICE));
+        mMessagingUtil = new NotificationMessagingUtil(context);
+        mSystemServicesProxy = SystemServicesProxy.getInstance(mContext);
+    }
+
+    public void setUpWithPresenter(NotificationPresenter presenter,
+            NotificationListContainer listContainer, Callback callback,
+            HeadsUpManager headsUpManager) {
+        mPresenter = presenter;
+        mCallback = callback;
+        mNotificationData = new NotificationData(presenter);
+        mHeadsUpManager = headsUpManager;
+        mNotificationData.setHeadsUpManager(mHeadsUpManager);
+        mListContainer = listContainer;
+
+        mHeadsUpObserver = new ContentObserver(mPresenter.getHandler()) {
+            @Override
+            public void onChange(boolean selfChange) {
+                boolean wasUsing = mUseHeadsUp;
+                mUseHeadsUp = ENABLE_HEADS_UP && !mDisableNotificationAlerts
+                        && Settings.Global.HEADS_UP_OFF != Settings.Global.getInt(
+                        mContext.getContentResolver(),
+                        Settings.Global.HEADS_UP_NOTIFICATIONS_ENABLED,
+                        Settings.Global.HEADS_UP_OFF);
+                Log.d(TAG, "heads up is " + (mUseHeadsUp ? "enabled" : "disabled"));
+                if (wasUsing != mUseHeadsUp) {
+                    if (!mUseHeadsUp) {
+                        Log.d(TAG,
+                                "dismissing any existing heads up notification on disable event");
+                        mHeadsUpManager.releaseAllImmediately();
+                    }
+                }
+            }
+        };
+
+        if (ENABLE_HEADS_UP) {
+            mContext.getContentResolver().registerContentObserver(
+                    Settings.Global.getUriFor(Settings.Global.HEADS_UP_NOTIFICATIONS_ENABLED),
+                    true,
+                    mHeadsUpObserver);
+            mContext.getContentResolver().registerContentObserver(
+                    Settings.Global.getUriFor(SETTING_HEADS_UP_TICKER), true,
+                    mHeadsUpObserver);
+        }
+
+        mDeviceProvisionedController.addCallback(mDeviceProvisionedListener);
+
+        mHeadsUpObserver.onChange(true); // set up
+    }
+
+    public NotificationData getNotificationData() {
+        return mNotificationData;
+    }
+
+    public ExpandableNotificationRow.LongPressListener getNotificationLongClicker() {
+        return mGutsManager::openGuts;
+    }
+
+    @Override
+    public void logNotificationExpansion(String key, boolean userAction, boolean expanded) {
+        mUiOffloadThread.submit(() -> {
+            try {
+                mBarService.onNotificationExpansionChanged(key, userAction, expanded);
+            } catch (RemoteException e) {
+                // Ignore.
+            }
+        });
+    }
+
+    @Override
+    public void onReorderingAllowed() {
+        updateNotifications();
+    }
+
+    private boolean shouldSuppressFullScreenIntent(String key) {
+        if (mPresenter.isDeviceInVrMode()) {
+            return true;
+        }
+
+        if (mPowerManager.isInteractive()) {
+            return mNotificationData.shouldSuppressScreenOn(key);
+        } else {
+            return mNotificationData.shouldSuppressScreenOff(key);
+        }
+    }
+
+    private void inflateViews(NotificationData.Entry entry, ViewGroup parent) {
+        PackageManager pmUser = StatusBar.getPackageManagerForUser(mContext,
+                entry.notification.getUser().getIdentifier());
+
+        final StatusBarNotification sbn = entry.notification;
+        if (entry.row != null) {
+            entry.reset();
+            updateNotification(entry, pmUser, sbn, entry.row);
+        } else {
+            new RowInflaterTask().inflate(mContext, parent, entry,
+                    row -> {
+                        bindRow(entry, pmUser, sbn, row);
+                        updateNotification(entry, pmUser, sbn, row);
+                    });
+        }
+    }
+
+    private void bindRow(NotificationData.Entry entry, PackageManager pmUser,
+            StatusBarNotification sbn, ExpandableNotificationRow row) {
+        row.setExpansionLogger(this, entry.notification.getKey());
+        row.setGroupManager(mGroupManager);
+        row.setHeadsUpManager(mHeadsUpManager);
+        row.setOnExpandClickListener(mPresenter);
+        row.setInflationCallback(this);
+        row.setLongPressListener(getNotificationLongClicker());
+        mRemoteInputManager.bindRow(row);
+
+        // Get the app name.
+        // Note that Notification.Builder#bindHeaderAppName has similar logic
+        // but since this field is used in the guts, it must be accurate.
+        // Therefore we will only show the application label, or, failing that, the
+        // package name. No substitutions.
+        final String pkg = sbn.getPackageName();
+        String appname = pkg;
+        try {
+            final ApplicationInfo info = pmUser.getApplicationInfo(pkg,
+                    PackageManager.MATCH_UNINSTALLED_PACKAGES
+                            | PackageManager.MATCH_DISABLED_COMPONENTS);
+            if (info != null) {
+                appname = String.valueOf(pmUser.getApplicationLabel(info));
+            }
+        } catch (PackageManager.NameNotFoundException e) {
+            // Do nothing
+        }
+        row.setAppName(appname);
+        row.setOnDismissRunnable(() ->
+                performRemoveNotification(row.getStatusBarNotification()));
+        row.setDescendantFocusability(ViewGroup.FOCUS_BLOCK_DESCENDANTS);
+        if (ENABLE_REMOTE_INPUT) {
+            row.setDescendantFocusability(ViewGroup.FOCUS_BEFORE_DESCENDANTS);
+        }
+
+        mCallback.onBindRow(entry, pmUser, sbn, row);
+    }
+
+    public void performRemoveNotification(StatusBarNotification n) {
+        NotificationData.Entry entry = mNotificationData.get(n.getKey());
+        mRemoteInputManager.onPerformRemoveNotification(n, entry);
+        final String pkg = n.getPackageName();
+        final String tag = n.getTag();
+        final int id = n.getId();
+        final int userId = n.getUserId();
+        try {
+            int dismissalSurface = NotificationStats.DISMISSAL_SHADE;
+            if (isHeadsUp(n.getKey())) {
+                dismissalSurface = NotificationStats.DISMISSAL_PEEK;
+            } else if (mListContainer.hasPulsingNotifications()) {
+                dismissalSurface = NotificationStats.DISMISSAL_AOD;
+            }
+            mBarService.onNotificationClear(pkg, tag, id, userId, n.getKey(), dismissalSurface);
+            removeNotification(n.getKey(), null);
+
+        } catch (RemoteException ex) {
+            // system process is dead if we're here.
+        }
+
+        mCallback.onPerformRemoveNotification(n);
+    }
+
+    /**
+     * Cancel this notification and tell the StatusBarManagerService / NotificationManagerService
+     * about the failure.
+     *
+     * WARNING: this will call back into us.  Don't hold any locks.
+     */
+    void handleNotificationError(StatusBarNotification n, String message) {
+        removeNotification(n.getKey(), null);
+        try {
+            mBarService.onNotificationError(n.getPackageName(), n.getTag(), n.getId(), n.getUid(),
+                    n.getInitialPid(), message, n.getUserId());
+        } catch (RemoteException ex) {
+            // The end is nigh.
+        }
+    }
+
+    private void abortExistingInflation(String key) {
+        if (mPendingNotifications.containsKey(key)) {
+            NotificationData.Entry entry = mPendingNotifications.get(key);
+            entry.abortTask();
+            mPendingNotifications.remove(key);
+        }
+        NotificationData.Entry addedEntry = mNotificationData.get(key);
+        if (addedEntry != null) {
+            addedEntry.abortTask();
+        }
+    }
+
+    @Override
+    public void handleInflationException(StatusBarNotification notification, Exception e) {
+        handleNotificationError(notification, e.getMessage());
+    }
+
+    private void addEntry(NotificationData.Entry shadeEntry) {
+        boolean isHeadsUped = shouldPeek(shadeEntry);
+        if (isHeadsUped) {
+            mHeadsUpManager.showNotification(shadeEntry);
+            // Mark as seen immediately
+            setNotificationShown(shadeEntry.notification);
+        }
+        addNotificationViews(shadeEntry);
+        mCallback.onNotificationAdded(shadeEntry);
+    }
+
+    @Override
+    public void onAsyncInflationFinished(NotificationData.Entry entry) {
+        mPendingNotifications.remove(entry.key);
+        // If there was an async task started after the removal, we don't want to add it back to
+        // the list, otherwise we might get leaks.
+        boolean isNew = mNotificationData.get(entry.key) == null;
+        if (isNew && !entry.row.isRemoved()) {
+            addEntry(entry);
+        } else if (!isNew && entry.row.hasLowPriorityStateUpdated()) {
+            mVisualStabilityManager.onLowPriorityUpdated(entry);
+            mPresenter.updateNotificationViews();
+        }
+        entry.row.setLowPriorityStateUpdated(false);
+    }
+
+    @Override
+    public void removeNotification(String key, NotificationListenerService.RankingMap ranking) {
+        boolean deferRemoval = false;
+        abortExistingInflation(key);
+        if (mHeadsUpManager.isHeadsUp(key)) {
+            // A cancel() in response to a remote input shouldn't be delayed, as it makes the
+            // sending look longer than it takes.
+            // Also we should not defer the removal if reordering isn't allowed since otherwise
+            // some notifications can't disappear before the panel is closed.
+            boolean ignoreEarliestRemovalTime = mRemoteInputManager.getController().isSpinning(key)
+                    && !FORCE_REMOTE_INPUT_HISTORY
+                    || !mVisualStabilityManager.isReorderingAllowed();
+            deferRemoval = !mHeadsUpManager.removeNotification(key,  ignoreEarliestRemovalTime);
+        }
+        mMediaManager.onNotificationRemoved(key);
+
+        NotificationData.Entry entry = mNotificationData.get(key);
+        if (FORCE_REMOTE_INPUT_HISTORY && mRemoteInputManager.getController().isSpinning(key)
+                && entry.row != null && !entry.row.isDismissed()) {
+            StatusBarNotification sbn = entry.notification;
+
+            Notification.Builder b = Notification.Builder
+                    .recoverBuilder(mContext, sbn.getNotification().clone());
+            CharSequence[] oldHistory = sbn.getNotification().extras
+                    .getCharSequenceArray(Notification.EXTRA_REMOTE_INPUT_HISTORY);
+            CharSequence[] newHistory;
+            if (oldHistory == null) {
+                newHistory = new CharSequence[1];
+            } else {
+                newHistory = new CharSequence[oldHistory.length + 1];
+                System.arraycopy(oldHistory, 0, newHistory, 1, oldHistory.length);
+            }
+            newHistory[0] = String.valueOf(entry.remoteInputText);
+            b.setRemoteInputHistory(newHistory);
+
+            Notification newNotification = b.build();
+
+            // Undo any compatibility view inflation
+            newNotification.contentView = sbn.getNotification().contentView;
+            newNotification.bigContentView = sbn.getNotification().bigContentView;
+            newNotification.headsUpContentView = sbn.getNotification().headsUpContentView;
+
+            StatusBarNotification newSbn = new StatusBarNotification(sbn.getPackageName(),
+                    sbn.getOpPkg(),
+                    sbn.getId(), sbn.getTag(), sbn.getUid(), sbn.getInitialPid(),
+                    newNotification, sbn.getUser(), sbn.getOverrideGroupKey(), sbn.getPostTime());
+            boolean updated = false;
+            try {
+                updateNotificationInternal(newSbn, null);
+                updated = true;
+            } catch (InflationException e) {
+                deferRemoval = false;
+            }
+            if (updated) {
+                Log.w(TAG, "Keeping notification around after sending remote input "+ entry.key);
+                mRemoteInputManager.getKeysKeptForRemoteInput().add(entry.key);
+                return;
+            }
+        }
+        if (deferRemoval) {
+            mLatestRankingMap = ranking;
+            mHeadsUpEntriesToRemoveOnSwitch.add(mHeadsUpManager.getEntry(key));
+            return;
+        }
+
+        if (mRemoteInputManager.onRemoveNotification(entry)) {
+            mLatestRankingMap = ranking;
+            return;
+        }
+
+        if (entry != null && mGutsManager.getExposedGuts() != null
+                && mGutsManager.getExposedGuts() == entry.row.getGuts()
+                && entry.row.getGuts() != null && !entry.row.getGuts().isLeavebehind()) {
+            Log.w(TAG, "Keeping notification because it's showing guts. " + key);
+            mLatestRankingMap = ranking;
+            mGutsManager.setKeyToRemoveOnGutsClosed(key);
+            return;
+        }
+
+        if (entry != null) {
+            mForegroundServiceController.removeNotification(entry.notification);
+        }
+
+        if (entry != null && entry.row != null) {
+            entry.row.setRemoved();
+            mListContainer.cleanUpViewState(entry.row);
+        }
+        // Let's remove the children if this was a summary
+        handleGroupSummaryRemoved(key);
+        StatusBarNotification old = removeNotificationViews(key, ranking);
+
+        mCallback.onNotificationRemoved(key, old);
+    }
+
+    private StatusBarNotification removeNotificationViews(String key,
+            NotificationListenerService.RankingMap ranking) {
+        NotificationData.Entry entry = mNotificationData.remove(key, ranking);
+        if (entry == null) {
+            Log.w(TAG, "removeNotification for unknown key: " + key);
+            return null;
+        }
+        updateNotifications();
+        Dependency.get(LeakDetector.class).trackGarbage(entry);
+        return entry.notification;
+    }
+
+    /**
+     * Ensures that the group children are cancelled immediately when the group summary is cancelled
+     * instead of waiting for the notification manager to send all cancels. Otherwise this could
+     * lead to flickers.
+     *
+     * This also ensures that the animation looks nice and only consists of a single disappear
+     * animation instead of multiple.
+     *  @param key the key of the notification was removed
+     *
+     */
+    private void handleGroupSummaryRemoved(String key) {
+        NotificationData.Entry entry = mNotificationData.get(key);
+        if (entry != null && entry.row != null
+                && entry.row.isSummaryWithChildren()) {
+            if (entry.notification.getOverrideGroupKey() != null && !entry.row.isDismissed()) {
+                // We don't want to remove children for autobundled notifications as they are not
+                // always cancelled. We only remove them if they were dismissed by the user.
+                return;
+            }
+            List<ExpandableNotificationRow> notificationChildren =
+                    entry.row.getNotificationChildren();
+            for (int i = 0; i < notificationChildren.size(); i++) {
+                ExpandableNotificationRow row = notificationChildren.get(i);
+                if ((row.getStatusBarNotification().getNotification().flags
+                        & Notification.FLAG_FOREGROUND_SERVICE) != 0) {
+                    // the child is a foreground service notification which we can't remove!
+                    continue;
+                }
+                row.setKeepInParent(true);
+                // we need to set this state earlier as otherwise we might generate some weird
+                // animations
+                row.setRemoved();
+            }
+        }
+    }
+
+    public void updateNotificationsOnDensityOrFontScaleChanged() {
+        ArrayList<NotificationData.Entry> activeNotifications =
+                mNotificationData.getActiveNotifications();
+        for (int i = 0; i < activeNotifications.size(); i++) {
+            NotificationData.Entry entry = activeNotifications.get(i);
+            boolean exposedGuts = mGutsManager.getExposedGuts() != null
+                    && entry.row.getGuts() == mGutsManager.getExposedGuts();
+            entry.row.onDensityOrFontScaleChanged();
+            if (exposedGuts) {
+                mGutsManager.setExposedGuts(entry.row.getGuts());
+                mGutsManager.bindGuts(entry.row);
+            }
+        }
+    }
+
+    private void updateNotification(NotificationData.Entry entry, PackageManager pmUser,
+            StatusBarNotification sbn, ExpandableNotificationRow row) {
+        row.setNeedsRedaction(mLockscreenUserManager.needsRedaction(entry));
+        boolean isLowPriority = mNotificationData.isAmbient(sbn.getKey());
+        boolean isUpdate = mNotificationData.get(entry.key) != null;
+        boolean wasLowPriority = row.isLowPriority();
+        row.setIsLowPriority(isLowPriority);
+        row.setLowPriorityStateUpdated(isUpdate && (wasLowPriority != isLowPriority));
+        // bind the click event to the content area
+        mNotificationClicker.register(row, sbn);
+
+        // Extract target SDK version.
+        try {
+            ApplicationInfo info = pmUser.getApplicationInfo(sbn.getPackageName(), 0);
+            entry.targetSdk = info.targetSdkVersion;
+        } catch (PackageManager.NameNotFoundException ex) {
+            Log.e(TAG, "Failed looking up ApplicationInfo for " + sbn.getPackageName(), ex);
+        }
+        row.setLegacy(entry.targetSdk >= Build.VERSION_CODES.GINGERBREAD
+                && entry.targetSdk < Build.VERSION_CODES.LOLLIPOP);
+        entry.setIconTag(R.id.icon_is_pre_L, entry.targetSdk < Build.VERSION_CODES.LOLLIPOP);
+        entry.autoRedacted = entry.notification.getNotification().publicVersion == null;
+
+        entry.row = row;
+        entry.row.setOnActivatedListener(mPresenter);
+
+        boolean useIncreasedCollapsedHeight = mMessagingUtil.isImportantMessaging(sbn,
+                mNotificationData.getImportance(sbn.getKey()));
+        boolean useIncreasedHeadsUp = useIncreasedCollapsedHeight
+                && !mPresenter.isPresenterFullyCollapsed();
+        row.setUseIncreasedCollapsedHeight(useIncreasedCollapsedHeight);
+        row.setUseIncreasedHeadsUpHeight(useIncreasedHeadsUp);
+        row.updateNotification(entry);
+    }
+
+
+    protected void addNotificationViews(NotificationData.Entry entry) {
+        if (entry == null) {
+            return;
+        }
+        // Add the expanded view and icon.
+        mNotificationData.add(entry);
+        updateNotifications();
+    }
+
+    protected NotificationData.Entry createNotificationViews(StatusBarNotification sbn)
+            throws InflationException {
+        if (DEBUG) {
+            Log.d(TAG, "createNotificationViews(notification=" + sbn);
+        }
+        NotificationData.Entry entry = new NotificationData.Entry(sbn);
+        Dependency.get(LeakDetector.class).trackInstance(entry);
+        entry.createIcons(mContext, sbn);
+        // Construct the expanded view.
+        inflateViews(entry, mListContainer.getViewParentForNotification(entry));
+        return entry;
+    }
+
+    private void addNotificationInternal(StatusBarNotification notification,
+            NotificationListenerService.RankingMap ranking) throws InflationException {
+        String key = notification.getKey();
+        if (DEBUG) Log.d(TAG, "addNotification key=" + key);
+
+        mNotificationData.updateRanking(ranking);
+        NotificationData.Entry shadeEntry = createNotificationViews(notification);
+        boolean isHeadsUped = shouldPeek(shadeEntry);
+        if (!isHeadsUped && notification.getNotification().fullScreenIntent != null) {
+            if (shouldSuppressFullScreenIntent(key)) {
+                if (DEBUG) {
+                    Log.d(TAG, "No Fullscreen intent: suppressed by DND: " + key);
+                }
+            } else if (mNotificationData.getImportance(key)
+                    < NotificationManager.IMPORTANCE_HIGH) {
+                if (DEBUG) {
+                    Log.d(TAG, "No Fullscreen intent: not important enough: "
+                            + key);
+                }
+            } else {
+                // Stop screensaver if the notification has a fullscreen intent.
+                // (like an incoming phone call)
+                SystemServicesProxy.getInstance(mContext).awakenDreamsAsync();
+
+                // not immersive & a fullscreen alert should be shown
+                if (DEBUG)
+                    Log.d(TAG, "Notification has fullScreenIntent; sending fullScreenIntent");
+                try {
+                    EventLog.writeEvent(EventLogTags.SYSUI_FULLSCREEN_NOTIFICATION,
+                            key);
+                    notification.getNotification().fullScreenIntent.send();
+                    shadeEntry.notifyFullScreenIntentLaunched();
+                    mMetricsLogger.count("note_fullscreen", 1);
+                } catch (PendingIntent.CanceledException e) {
+                }
+            }
+        }
+        abortExistingInflation(key);
+
+        mForegroundServiceController.addNotification(notification,
+                mNotificationData.getImportance(key));
+
+        mPendingNotifications.put(key, shadeEntry);
+    }
+
+    @Override
+    public void addNotification(StatusBarNotification notification,
+            NotificationListenerService.RankingMap ranking) {
+        try {
+            addNotificationInternal(notification, ranking);
+        } catch (InflationException e) {
+            handleInflationException(notification, e);
+        }
+    }
+
+    private boolean alertAgain(NotificationData.Entry oldEntry, Notification newNotification) {
+        return oldEntry == null || !oldEntry.hasInterrupted()
+                || (newNotification.flags & Notification.FLAG_ONLY_ALERT_ONCE) == 0;
+    }
+
+    private void updateNotificationInternal(StatusBarNotification notification,
+            NotificationListenerService.RankingMap ranking) throws InflationException {
+        if (DEBUG) Log.d(TAG, "updateNotification(" + notification + ")");
+
+        final String key = notification.getKey();
+        abortExistingInflation(key);
+        NotificationData.Entry entry = mNotificationData.get(key);
+        if (entry == null) {
+            return;
+        }
+        mHeadsUpEntriesToRemoveOnSwitch.remove(entry);
+        mRemoteInputManager.onUpdateNotification(entry);
+
+        if (key.equals(mGutsManager.getKeyToRemoveOnGutsClosed())) {
+            mGutsManager.setKeyToRemoveOnGutsClosed(null);
+            Log.w(TAG, "Notification that was kept for guts was updated. " + key);
+        }
+
+        Notification n = notification.getNotification();
+        mNotificationData.updateRanking(ranking);
+
+        final StatusBarNotification oldNotification = entry.notification;
+        entry.notification = notification;
+        mGroupManager.onEntryUpdated(entry, oldNotification);
+
+        entry.updateIcons(mContext, notification);
+        inflateViews(entry, mListContainer.getViewParentForNotification(entry));
+
+        mForegroundServiceController.updateNotification(notification,
+                mNotificationData.getImportance(key));
+
+        boolean shouldPeek = shouldPeek(entry, notification);
+        boolean alertAgain = alertAgain(entry, n);
+
+        updateHeadsUp(key, entry, shouldPeek, alertAgain);
+        updateNotifications();
+
+        if (!notification.isClearable()) {
+            // The user may have performed a dismiss action on the notification, since it's
+            // not clearable we should snap it back.
+            mListContainer.snapViewIfNeeded(entry.row);
+        }
+
+        if (DEBUG) {
+            // Is this for you?
+            boolean isForCurrentUser = mPresenter.isNotificationForCurrentProfiles(notification);
+            Log.d(TAG, "notification is " + (isForCurrentUser ? "" : "not ") + "for you");
+        }
+
+        mCallback.onNotificationUpdated(notification);
+    }
+
+    @Override
+    public void updateNotification(StatusBarNotification notification,
+            NotificationListenerService.RankingMap ranking) {
+        try {
+            updateNotificationInternal(notification, ranking);
+        } catch (InflationException e) {
+            handleInflationException(notification, e);
+        }
+    }
+
+    public void updateNotifications() {
+        mNotificationData.filterAndSort();
+
+        mPresenter.updateNotificationViews();
+    }
+
+    public void updateNotificationRanking(NotificationListenerService.RankingMap ranking) {
+        mNotificationData.updateRanking(ranking);
+        updateNotifications();
+    }
+
+    protected boolean shouldPeek(NotificationData.Entry entry) {
+        return shouldPeek(entry, entry.notification);
+    }
+
+    public boolean shouldPeek(NotificationData.Entry entry, StatusBarNotification sbn) {
+        if (!mUseHeadsUp || mPresenter.isDeviceInVrMode()) {
+            if (DEBUG) Log.d(TAG, "No peeking: no huns or vr mode");
+            return false;
+        }
+
+        if (mNotificationData.shouldFilterOut(sbn)) {
+            if (DEBUG) Log.d(TAG, "No peeking: filtered notification: " + sbn.getKey());
+            return false;
+        }
+
+        boolean inUse = mPowerManager.isScreenOn() && !mSystemServicesProxy.isDreaming();
+
+        if (!inUse && !mPresenter.isDozing()) {
+            if (DEBUG) {
+                Log.d(TAG, "No peeking: not in use: " + sbn.getKey());
+            }
+            return false;
+        }
+
+        if (!mPresenter.isDozing() && mNotificationData.shouldSuppressScreenOn(sbn.getKey())) {
+            if (DEBUG) Log.d(TAG, "No peeking: suppressed by DND: " + sbn.getKey());
+            return false;
+        }
+
+        if (mPresenter.isDozing() && mNotificationData.shouldSuppressScreenOff(sbn.getKey())) {
+            if (DEBUG) Log.d(TAG, "No peeking: suppressed by DND: " + sbn.getKey());
+            return false;
+        }
+
+        if (entry.hasJustLaunchedFullScreenIntent()) {
+            if (DEBUG) Log.d(TAG, "No peeking: recent fullscreen: " + sbn.getKey());
+            return false;
+        }
+
+        if (isSnoozedPackage(sbn)) {
+            if (DEBUG) Log.d(TAG, "No peeking: snoozed package: " + sbn.getKey());
+            return false;
+        }
+
+        // Allow peeking for DEFAULT notifications only if we're on Ambient Display.
+        int importanceLevel = mPresenter.isDozing() ? NotificationManager.IMPORTANCE_DEFAULT
+                : NotificationManager.IMPORTANCE_HIGH;
+        if (mNotificationData.getImportance(sbn.getKey()) < importanceLevel) {
+            if (DEBUG) Log.d(TAG, "No peeking: unimportant notification: " + sbn.getKey());
+            return false;
+        }
+
+        // Don't peek notifications that are suppressed due to group alert behavior
+        if (sbn.isGroup() && sbn.getNotification().suppressAlertingDueToGrouping()) {
+            if (DEBUG) Log.d(TAG, "No peeking: suppressed due to group alert behavior");
+            return false;
+        }
+
+        if (!mCallback.shouldPeek(entry, sbn)) {
+            return false;
+        }
+
+        return true;
+    }
+
+    protected void setNotificationShown(StatusBarNotification n) {
+        setNotificationsShown(new String[]{n.getKey()});
+    }
+
+    protected void setNotificationsShown(String[] keys) {
+        try {
+            mNotificationListener.setNotificationsShown(keys);
+        } catch (RuntimeException e) {
+            Log.d(TAG, "failed setNotificationsShown: ", e);
+        }
+    }
+
+    protected boolean isSnoozedPackage(StatusBarNotification sbn) {
+        return mHeadsUpManager.isSnoozed(sbn.getPackageName());
+    }
+
+    protected void updateHeadsUp(String key, NotificationData.Entry entry, boolean shouldPeek,
+            boolean alertAgain) {
+        final boolean wasHeadsUp = isHeadsUp(key);
+        if (wasHeadsUp) {
+            if (!shouldPeek) {
+                // We don't want this to be interrupting anymore, lets remove it
+                mHeadsUpManager.removeNotification(key, false /* ignoreEarliestRemovalTime */);
+            } else {
+                mHeadsUpManager.updateNotification(entry, alertAgain);
+            }
+        } else if (shouldPeek && alertAgain) {
+            // This notification was updated to be a heads-up, show it!
+            mHeadsUpManager.showNotification(entry);
+        }
+    }
+
+    protected boolean isHeadsUp(String key) {
+        return mHeadsUpManager.isHeadsUp(key);
+    }
+
+    /**
+     * Callback for NotificationEntryManager.
+     */
+    public interface Callback {
+
+        /**
+         * Called when a new entry is created.
+         *
+         * @param shadeEntry entry that was created
+         */
+        void onNotificationAdded(NotificationData.Entry shadeEntry);
+
+        /**
+         * Called when a notification was updated.
+         *
+         * @param notification notification that was updated
+         */
+        void onNotificationUpdated(StatusBarNotification notification);
+
+        /**
+         * Called when a notification was removed.
+         *
+         * @param key key of notification that was removed
+         * @param old StatusBarNotification of the notification before it was removed
+         */
+        void onNotificationRemoved(String key, StatusBarNotification old);
+
+
+        /**
+         * Called when a notification is clicked.
+         *
+         * @param sbn notification that was clicked
+         * @param row row for that notification
+         */
+        void onNotificationClicked(StatusBarNotification sbn, ExpandableNotificationRow row);
+
+        /**
+         * Called when a new notification and row is created.
+         *
+         * @param entry entry for the notification
+         * @param pmUser package manager for user
+         * @param sbn notification
+         * @param row row for the notification
+         */
+        void onBindRow(NotificationData.Entry entry, PackageManager pmUser,
+                StatusBarNotification sbn, ExpandableNotificationRow row);
+
+        /**
+         * Removes a notification immediately.
+         *
+         * @param statusBarNotification notification that is being removed
+         */
+        void onPerformRemoveNotification(StatusBarNotification statusBarNotification);
+
+        /**
+         * Returns true if NotificationEntryManager should peek this notification.
+         *
+         * @param entry entry of the notification that might be peeked
+         * @param sbn notification that might be peeked
+         * @return true if the notification should be peeked
+         */
+        boolean shouldPeek(NotificationData.Entry entry, StatusBarNotification sbn);
+    }
+}
diff --git a/com/android/systemui/statusbar/NotificationGutsManager.java b/com/android/systemui/statusbar/NotificationGutsManager.java
index f451fda..87ad6f6 100644
--- a/com/android/systemui/statusbar/NotificationGutsManager.java
+++ b/com/android/systemui/statusbar/NotificationGutsManager.java
@@ -37,13 +37,11 @@
 
 import com.android.internal.logging.MetricsLogger;
 import com.android.internal.logging.nano.MetricsProto;
-import com.android.internal.statusbar.IStatusBarService;
 import com.android.systemui.Dependency;
 import com.android.systemui.Dumpable;
 import com.android.systemui.Interpolators;
 import com.android.systemui.plugins.statusbar.NotificationMenuRowPlugin;
 import com.android.systemui.statusbar.phone.StatusBar;
-import com.android.systemui.statusbar.stack.NotificationStackScrollLayout;
 import com.android.systemui.statusbar.stack.StackStateAnimator;
 
 import java.io.FileDescriptor;
@@ -66,30 +64,25 @@
 
     private final MetricsLogger mMetricsLogger = Dependency.get(MetricsLogger.class);
     private final Set<String> mNonBlockablePkgs;
-    private final NotificationPresenter mPresenter;
-    // TODO: Create NotificationListContainer interface and use it instead of
-    // NotificationStackScrollLayout here
-    private final NotificationStackScrollLayout mStackScroller;
     private final Context mContext;
     private final AccessibilityManager mAccessibilityManager;
+
+    // Dependencies:
+    private final NotificationLockscreenUserManager mLockscreenUserManager =
+            Dependency.get(NotificationLockscreenUserManager.class);
+
     // which notification is currently being longpress-examined by the user
     private NotificationGuts mNotificationGutsExposed;
     private NotificationMenuRowPlugin.MenuItem mGutsMenuItem;
-    private final NotificationInfo.CheckSaveListener mCheckSaveListener;
-    private final OnSettingsClickListener mOnSettingsClickListener;
+    protected NotificationPresenter mPresenter;
+    protected NotificationEntryManager mEntryManager;
+    private NotificationListContainer mListContainer;
+    private NotificationInfo.CheckSaveListener mCheckSaveListener;
+    private OnSettingsClickListener mOnSettingsClickListener;
     private String mKeyToRemoveOnGutsClosed;
 
-    public NotificationGutsManager(
-            NotificationPresenter presenter,
-            NotificationStackScrollLayout stackScroller,
-            NotificationInfo.CheckSaveListener checkSaveListener,
-            Context context,
-            OnSettingsClickListener onSettingsClickListener) {
-        mPresenter = presenter;
-        mStackScroller = stackScroller;
-        mCheckSaveListener = checkSaveListener;
+    public NotificationGutsManager(Context context) {
         mContext = context;
-        mOnSettingsClickListener = onSettingsClickListener;
         Resources res = context.getResources();
 
         mNonBlockablePkgs = new HashSet<>();
@@ -100,6 +93,17 @@
                 mContext.getSystemService(Context.ACCESSIBILITY_SERVICE);
     }
 
+    public void setUpWithPresenter(NotificationPresenter presenter,
+            NotificationEntryManager entryManager, NotificationListContainer listContainer,
+            NotificationInfo.CheckSaveListener checkSaveListener,
+            OnSettingsClickListener onSettingsClickListener) {
+        mPresenter = presenter;
+        mEntryManager = entryManager;
+        mListContainer = listContainer;
+        mCheckSaveListener = checkSaveListener;
+        mOnSettingsClickListener = onSettingsClickListener;
+    }
+
     public String getKeyToRemoveOnGutsClosed() {
         return mKeyToRemoveOnGutsClosed;
     }
@@ -152,7 +156,7 @@
         final NotificationGuts guts = row.getGuts();
         guts.setClosedListener((NotificationGuts g) -> {
             if (!g.willBeRemoved() && !row.isRemoved()) {
-                mStackScroller.onHeightChanged(
+                mListContainer.onHeightChanged(
                         row, !mPresenter.isPresenterFullyCollapsed() /* needsAnimation */);
             }
             if (mNotificationGutsExposed == g) {
@@ -162,18 +166,18 @@
             String key = sbn.getKey();
             if (key.equals(mKeyToRemoveOnGutsClosed)) {
                 mKeyToRemoveOnGutsClosed = null;
-                mPresenter.removeNotification(key, mPresenter.getLatestRankingMap());
+                mEntryManager.removeNotification(key, mEntryManager.getLatestRankingMap());
             }
         });
 
         View gutsView = item.getGutsView();
         if (gutsView instanceof NotificationSnooze) {
             NotificationSnooze snoozeGuts = (NotificationSnooze) gutsView;
-            snoozeGuts.setSnoozeListener(mStackScroller.getSwipeActionHelper());
+            snoozeGuts.setSnoozeListener(mListContainer.getSwipeActionHelper());
             snoozeGuts.setStatusBarNotification(sbn);
             snoozeGuts.setSnoozeOptions(row.getEntry().snoozeCriteria);
             guts.setHeightChangedListener((NotificationGuts g) -> {
-                mStackScroller.onHeightChanged(row, row.isShown() /* needsAnimation */);
+                mListContainer.onHeightChanged(row, row.isShown() /* needsAnimation */);
             });
         }
 
@@ -189,7 +193,7 @@
             // system user.
             NotificationInfo.OnSettingsClickListener onSettingsClick = null;
             if (!userHandle.equals(UserHandle.ALL)
-                    || mPresenter.getCurrentUserId() == UserHandle.USER_SYSTEM) {
+                    || mLockscreenUserManager.getCurrentUserId() == UserHandle.USER_SYSTEM) {
                 onSettingsClick = (View v, NotificationChannel channel, int appUid) -> {
                     mMetricsLogger.action(MetricsProto.MetricsEvent.ACTION_NOTE_INFO);
                     guts.resetFalsingCheck();
@@ -251,7 +255,7 @@
             mNotificationGutsExposed.closeControls(removeLeavebehinds, removeControls, x, y, force);
         }
         if (resetMenu) {
-            mStackScroller.resetExposedMenuView(false /* animate */, true /* force */);
+            mListContainer.resetExposedMenuView(false /* animate */, true /* force */);
         }
     }
 
@@ -344,7 +348,7 @@
                                 !mAccessibilityManager.isTouchExplorationEnabled());
                 guts.setExposed(true /* exposed */, needsFalsingProtection);
                 row.closeRemoteInput();
-                mStackScroller.onHeightChanged(row, true /* needsAnimation */);
+                mListContainer.onHeightChanged(row, true /* needsAnimation */);
                 mNotificationGutsExposed = guts;
                 mGutsMenuItem = item;
             }
@@ -354,7 +358,8 @@
 
     @Override
     public void dump(FileDescriptor fd, PrintWriter pw, String[] args) {
-        pw.print("mKeyToRemoveOnGutsClosed: ");
+        pw.println("NotificationGutsManager state:");
+        pw.print("  mKeyToRemoveOnGutsClosed: ");
         pw.println(mKeyToRemoveOnGutsClosed);
     }
 
diff --git a/com/android/systemui/statusbar/NotificationListContainer.java b/com/android/systemui/statusbar/NotificationListContainer.java
new file mode 100644
index 0000000..43be44d
--- /dev/null
+++ b/com/android/systemui/statusbar/NotificationListContainer.java
@@ -0,0 +1,182 @@
+/*
+ * Copyright (C) 2017 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License
+ */
+
+package com.android.systemui.statusbar;
+
+import android.view.View;
+import android.view.ViewGroup;
+
+import com.android.systemui.plugins.statusbar.NotificationSwipeActionHelper;
+
+/**
+ * Interface representing the entity that contains notifications. It can have
+ * notification views added and removed from it, and will manage displaying them to the user.
+ */
+public interface NotificationListContainer {
+
+    /**
+     * Called when a child is being transferred.
+     *
+     * @param childTransferInProgress whether child transfer is in progress
+     */
+    void setChildTransferInProgress(boolean childTransferInProgress);
+
+    /**
+     * Change the position of child to a new location
+     *
+     * @param child the view to change the position for
+     * @param newIndex the new index
+     */
+    void changeViewPosition(View child, int newIndex);
+
+    /**
+     * Called when a child was added to a group.
+     *
+     * @param row row of the group child that was added
+     */
+    void notifyGroupChildAdded(View row);
+
+    /**
+     * Called when a child was removed from a group.
+     *
+     * @param row row of the child that was removed
+     * @param childrenContainer ViewGroup of the group that the child was removed from
+     */
+    void notifyGroupChildRemoved(View row, ViewGroup childrenContainer);
+
+    /**
+     * Generate an animation for an added child view.
+     *
+     * @param child The view to be added.
+     * @param fromMoreCard Whether this add is coming from the "more" card on lockscreen.
+     */
+    void generateAddAnimation(View child, boolean fromMoreCard);
+
+    /**
+     * Generate a child order changed event.
+     */
+    void generateChildOrderChangedEvent();
+
+    /**
+     * Returns the number of children in the NotificationListContainer.
+     *
+     * @return the number of children in the NotificationListContainer
+     */
+    int getContainerChildCount();
+
+    /**
+     * Gets the ith child in the NotificationListContainer.
+     *
+     * @param i ith child to get
+     * @return the ith child in the list container
+     */
+    View getContainerChildAt(int i);
+
+    /**
+     * Remove a view from the container
+     *
+     * @param v view to remove
+     */
+    void removeContainerView(View v);
+
+    /**
+     * Add a view to the container
+     *
+     * @param v view to add
+     */
+    void addContainerView(View v);
+
+    /**
+     * Sets the maximum number of notifications to display.
+     *
+     * @param maxNotifications max number of notifications to display
+     */
+    void setMaxDisplayedNotifications(int maxNotifications);
+
+    /**
+     * Handle snapping a non-dismissable row back if the user tried to dismiss it.
+     *
+     * @param row row to snap back
+     */
+    void snapViewIfNeeded(ExpandableNotificationRow row);
+
+    /**
+     * Get the view parent for a notification entry. For example, NotificationStackScrollLayout.
+     *
+     * @param entry entry to get the view parent for
+     * @return the view parent for entry
+     */
+    ViewGroup getViewParentForNotification(NotificationData.Entry entry);
+
+    /**
+     * Called when the height of an expandable view changes.
+     *
+     * @param view view whose height changed
+     * @param animate whether this change should be animated
+     */
+    void onHeightChanged(ExpandableView view, boolean animate);
+
+    /**
+     * Resets the currently exposed menu view.
+     *
+     * @param animate whether to animate the closing/change of menu view
+     * @param force reset the menu view even if it looks like it is already reset
+     */
+    void resetExposedMenuView(boolean animate, boolean force);
+
+    /**
+     * Returns the NotificationSwipeActionHelper for the NotificationListContainer.
+     *
+     * @return swipe action helper for the list container
+     */
+    NotificationSwipeActionHelper getSwipeActionHelper();
+
+    /**
+     * Called when a notification is removed from the shade. This cleans up the state for a
+     * given view.
+     *
+     * @param view view to clean up view state for
+     */
+    void cleanUpViewState(View view);
+
+    /**
+     * Returns whether an ExpandableNotificationRow is in a visible location or not.
+     *
+     * @param row
+     * @return true if row is in a visible location
+     */
+    boolean isInVisibleLocation(ExpandableNotificationRow row);
+
+    /**
+     * Sets a listener to listen for changes in notification locations.
+     *
+     * @param listener listener to set
+     */
+    void setChildLocationsChangedListener(
+            NotificationLogger.OnChildLocationsChangedListener listener);
+
+    /**
+     * Called when an update to the notification view hierarchy is completed.
+     */
+    default void onNotificationViewUpdateFinished() {}
+
+    /**
+     * Returns true if there are pulsing notifications.
+     *
+     * @return true if has pulsing notifications
+     */
+    boolean hasPulsingNotifications();
+}
diff --git a/com/android/systemui/statusbar/NotificationListener.java b/com/android/systemui/statusbar/NotificationListener.java
new file mode 100644
index 0000000..0144f42
--- /dev/null
+++ b/com/android/systemui/statusbar/NotificationListener.java
@@ -0,0 +1,145 @@
+/*
+ * Copyright (C) 2017 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License
+ */
+
+package com.android.systemui.statusbar;
+
+import static com.android.systemui.statusbar.RemoteInputController.processForRemoteInput;
+import static com.android.systemui.statusbar.phone.StatusBar.DEBUG;
+import static com.android.systemui.statusbar.phone.StatusBar.ENABLE_CHILD_NOTIFICATIONS;
+
+import android.content.ComponentName;
+import android.content.Context;
+import android.os.RemoteException;
+import android.os.UserHandle;
+import android.service.notification.StatusBarNotification;
+import android.util.Log;
+
+import com.android.systemui.Dependency;
+import com.android.systemui.statusbar.phone.NotificationListenerWithPlugins;
+
+/**
+ * This class handles listening to notification updates and passing them along to
+ * NotificationPresenter to be displayed to the user.
+ */
+public class NotificationListener extends NotificationListenerWithPlugins {
+    private static final String TAG = "NotificationListener";
+
+    // Dependencies:
+    private final NotificationRemoteInputManager mRemoteInputManager =
+            Dependency.get(NotificationRemoteInputManager.class);
+
+    private final Context mContext;
+
+    protected NotificationPresenter mPresenter;
+    protected NotificationEntryManager mEntryManager;
+
+    public NotificationListener(Context context) {
+        mContext = context;
+    }
+
+    @Override
+    public void onListenerConnected() {
+        if (DEBUG) Log.d(TAG, "onListenerConnected");
+        onPluginConnected();
+        final StatusBarNotification[] notifications = getActiveNotifications();
+        if (notifications == null) {
+            Log.w(TAG, "onListenerConnected unable to get active notifications.");
+            return;
+        }
+        final RankingMap currentRanking = getCurrentRanking();
+        mPresenter.getHandler().post(() -> {
+            for (StatusBarNotification sbn : notifications) {
+                mEntryManager.addNotification(sbn, currentRanking);
+            }
+        });
+    }
+
+    @Override
+    public void onNotificationPosted(final StatusBarNotification sbn,
+            final RankingMap rankingMap) {
+        if (DEBUG) Log.d(TAG, "onNotificationPosted: " + sbn);
+        if (sbn != null && !onPluginNotificationPosted(sbn, rankingMap)) {
+            mPresenter.getHandler().post(() -> {
+                processForRemoteInput(sbn.getNotification(), mContext);
+                String key = sbn.getKey();
+                mRemoteInputManager.getKeysKeptForRemoteInput().remove(key);
+                boolean isUpdate =
+                        mEntryManager.getNotificationData().get(key) != null;
+                // In case we don't allow child notifications, we ignore children of
+                // notifications that have a summary, since` we're not going to show them
+                // anyway. This is true also when the summary is canceled,
+                // because children are automatically canceled by NoMan in that case.
+                if (!ENABLE_CHILD_NOTIFICATIONS
+                        && mPresenter.getGroupManager().isChildInGroupWithSummary(sbn)) {
+                    if (DEBUG) {
+                        Log.d(TAG, "Ignoring group child due to existing summary: " + sbn);
+                    }
+
+                    // Remove existing notification to avoid stale data.
+                    if (isUpdate) {
+                        mEntryManager.removeNotification(key, rankingMap);
+                    } else {
+                        mEntryManager.getNotificationData()
+                                .updateRanking(rankingMap);
+                    }
+                    return;
+                }
+                if (isUpdate) {
+                    mEntryManager.updateNotification(sbn, rankingMap);
+                } else {
+                    mEntryManager.addNotification(sbn, rankingMap);
+                }
+            });
+        }
+    }
+
+    @Override
+    public void onNotificationRemoved(StatusBarNotification sbn,
+            final RankingMap rankingMap) {
+        if (DEBUG) Log.d(TAG, "onNotificationRemoved: " + sbn);
+        if (sbn != null && !onPluginNotificationRemoved(sbn, rankingMap)) {
+            final String key = sbn.getKey();
+            mPresenter.getHandler().post(() -> {
+                mEntryManager.removeNotification(key, rankingMap);
+            });
+        }
+    }
+
+    @Override
+    public void onNotificationRankingUpdate(final RankingMap rankingMap) {
+        if (DEBUG) Log.d(TAG, "onRankingUpdate");
+        if (rankingMap != null) {
+            RankingMap r = onPluginRankingUpdate(rankingMap);
+            mPresenter.getHandler().post(() -> {
+                mEntryManager.updateNotificationRanking(r);
+            });
+        }
+    }
+
+    public void setUpWithPresenter(NotificationPresenter presenter,
+            NotificationEntryManager entryManager) {
+        mPresenter = presenter;
+        mEntryManager = entryManager;
+
+        try {
+            registerAsSystemService(mContext,
+                    new ComponentName(mContext.getPackageName(), getClass().getCanonicalName()),
+                    UserHandle.USER_ALL);
+        } catch (RemoteException e) {
+            Log.e(TAG, "Unable to register notification listener", e);
+        }
+    }
+}
diff --git a/com/android/systemui/statusbar/NotificationLockscreenUserManager.java b/com/android/systemui/statusbar/NotificationLockscreenUserManager.java
new file mode 100644
index 0000000..bcdc269
--- /dev/null
+++ b/com/android/systemui/statusbar/NotificationLockscreenUserManager.java
@@ -0,0 +1,457 @@
+/*
+ * Copyright (C) 2017 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License
+ */
+package com.android.systemui.statusbar;
+
+import android.app.ActivityManager;
+import android.app.Notification;
+import android.app.admin.DevicePolicyManager;
+import android.content.BroadcastReceiver;
+import android.content.Context;
+import android.content.Intent;
+import android.content.IntentFilter;
+import android.content.IntentSender;
+import android.content.pm.UserInfo;
+import android.database.ContentObserver;
+import android.os.RemoteException;
+import android.os.ServiceManager;
+import android.os.UserHandle;
+import android.os.UserManager;
+import android.provider.Settings;
+import android.service.notification.StatusBarNotification;
+import android.util.Log;
+import android.util.SparseArray;
+import android.util.SparseBooleanArray;
+import android.widget.TextView;
+import android.widget.Toast;
+
+import com.android.internal.statusbar.IStatusBarService;
+import com.android.systemui.Dependency;
+import com.android.systemui.Dumpable;
+import com.android.systemui.OverviewProxyService;
+import com.android.systemui.R;
+import com.android.systemui.statusbar.policy.DeviceProvisionedController;
+
+import java.io.FileDescriptor;
+import java.io.PrintWriter;
+import java.util.List;
+
+/**
+ * Handles keeping track of the current user, profiles, and various things related to hiding
+ * contents, redacting notifications, and the lockscreen.
+ */
+public class NotificationLockscreenUserManager implements Dumpable {
+    private static final String TAG = "LockscreenUserManager";
+    private static final boolean ENABLE_LOCK_SCREEN_ALLOW_REMOTE_INPUT = false;
+    public static final String PERMISSION_SELF = "com.android.systemui.permission.SELF";
+    public static final String NOTIFICATION_UNLOCKED_BY_WORK_CHALLENGE_ACTION
+            = "com.android.systemui.statusbar.work_challenge_unlocked_notification_action";
+
+    private final DevicePolicyManager mDevicePolicyManager;
+    private final SparseBooleanArray mLockscreenPublicMode = new SparseBooleanArray();
+    private final SparseBooleanArray mUsersAllowingPrivateNotifications = new SparseBooleanArray();
+    private final SparseBooleanArray mUsersAllowingNotifications = new SparseBooleanArray();
+    private final DeviceProvisionedController mDeviceProvisionedController =
+            Dependency.get(DeviceProvisionedController.class);
+    private final UserManager mUserManager;
+    private final IStatusBarService mBarService;
+
+    private boolean mShowLockscreenNotifications;
+    private boolean mAllowLockscreenRemoteInput;
+
+    protected final BroadcastReceiver mAllUsersReceiver = new BroadcastReceiver() {
+        @Override
+        public void onReceive(Context context, Intent intent) {
+            final String action = intent.getAction();
+            final int userId = intent.getIntExtra(Intent.EXTRA_USER_HANDLE, UserHandle.USER_NULL);
+
+            if (DevicePolicyManager.ACTION_DEVICE_POLICY_MANAGER_STATE_CHANGED.equals(action) &&
+                    isCurrentProfile(getSendingUserId())) {
+                mUsersAllowingPrivateNotifications.clear();
+                updateLockscreenNotificationSetting();
+                mEntryManager.updateNotifications();
+            } else if (Intent.ACTION_DEVICE_LOCKED_CHANGED.equals(action)) {
+                if (userId != mCurrentUserId && isCurrentProfile(userId)) {
+                    mPresenter.onWorkChallengeChanged();
+                }
+            }
+        }
+    };
+
+    protected final BroadcastReceiver mBaseBroadcastReceiver = new BroadcastReceiver() {
+        @Override
+        public void onReceive(Context context, Intent intent) {
+            String action = intent.getAction();
+            if (Intent.ACTION_USER_SWITCHED.equals(action)) {
+                mCurrentUserId = intent.getIntExtra(Intent.EXTRA_USER_HANDLE, -1);
+                updateCurrentProfilesCache();
+                Log.v(TAG, "userId " + mCurrentUserId + " is in the house");
+
+                updateLockscreenNotificationSetting();
+
+                mPresenter.onUserSwitched(mCurrentUserId);
+            } else if (Intent.ACTION_USER_ADDED.equals(action)) {
+                updateCurrentProfilesCache();
+            } else if (Intent.ACTION_USER_UNLOCKED.equals(action)) {
+                // Start the overview connection to the launcher service
+                Dependency.get(OverviewProxyService.class).startConnectionToCurrentUser();
+            } else if (Intent.ACTION_USER_PRESENT.equals(action)) {
+                try {
+                    final int lastResumedActivityUserId =
+                            ActivityManager.getService().getLastResumedActivityUserId();
+                    if (mUserManager.isManagedProfile(lastResumedActivityUserId)) {
+                        showForegroundManagedProfileActivityToast();
+                    }
+                } catch (RemoteException e) {
+                    // Abandon hope activity manager not running.
+                }
+            } else if (NOTIFICATION_UNLOCKED_BY_WORK_CHALLENGE_ACTION.equals(action)) {
+                final IntentSender intentSender = intent.getParcelableExtra(Intent.EXTRA_INTENT);
+                final String notificationKey = intent.getStringExtra(Intent.EXTRA_INDEX);
+                if (intentSender != null) {
+                    try {
+                        mContext.startIntentSender(intentSender, null, 0, 0, 0);
+                    } catch (IntentSender.SendIntentException e) {
+                        /* ignore */
+                    }
+                }
+                if (notificationKey != null) {
+                    try {
+                        mBarService.onNotificationClick(notificationKey);
+                    } catch (RemoteException e) {
+                        /* ignore */
+                    }
+                }
+            }
+        }
+    };
+
+    protected final Context mContext;
+    protected final SparseArray<UserInfo> mCurrentProfiles = new SparseArray<>();
+
+    protected int mCurrentUserId = 0;
+    protected NotificationPresenter mPresenter;
+    protected NotificationEntryManager mEntryManager;
+    protected ContentObserver mLockscreenSettingsObserver;
+    protected ContentObserver mSettingsObserver;
+
+    public NotificationLockscreenUserManager(Context context) {
+        mContext = context;
+        mDevicePolicyManager = (DevicePolicyManager) mContext.getSystemService(
+                Context.DEVICE_POLICY_SERVICE);
+        mUserManager = (UserManager) mContext.getSystemService(Context.USER_SERVICE);
+        mCurrentUserId = ActivityManager.getCurrentUser();
+        mBarService = IStatusBarService.Stub.asInterface(
+                ServiceManager.getService(Context.STATUS_BAR_SERVICE));
+    }
+
+    public void setUpWithPresenter(NotificationPresenter presenter,
+            NotificationEntryManager entryManager) {
+        mPresenter = presenter;
+        mEntryManager = entryManager;
+
+        mLockscreenSettingsObserver = new ContentObserver(mPresenter.getHandler()) {
+            @Override
+            public void onChange(boolean selfChange) {
+                // We don't know which user changed LOCK_SCREEN_ALLOW_PRIVATE_NOTIFICATIONS or
+                // LOCK_SCREEN_SHOW_NOTIFICATIONS, so we just dump our cache ...
+                mUsersAllowingPrivateNotifications.clear();
+                mUsersAllowingNotifications.clear();
+                // ... and refresh all the notifications
+                updateLockscreenNotificationSetting();
+                mEntryManager.updateNotifications();
+            }
+        };
+
+        mSettingsObserver = new ContentObserver(mPresenter.getHandler()) {
+            @Override
+            public void onChange(boolean selfChange) {
+                updateLockscreenNotificationSetting();
+                if (mDeviceProvisionedController.isDeviceProvisioned()) {
+                    mEntryManager.updateNotifications();
+                }
+            }
+        };
+
+        mContext.getContentResolver().registerContentObserver(
+                Settings.Secure.getUriFor(Settings.Secure.LOCK_SCREEN_SHOW_NOTIFICATIONS), false,
+                mLockscreenSettingsObserver,
+                UserHandle.USER_ALL);
+
+        mContext.getContentResolver().registerContentObserver(
+                Settings.Secure.getUriFor(Settings.Secure.LOCK_SCREEN_ALLOW_PRIVATE_NOTIFICATIONS),
+                true,
+                mLockscreenSettingsObserver,
+                UserHandle.USER_ALL);
+
+        mContext.getContentResolver().registerContentObserver(
+                Settings.Global.getUriFor(Settings.Global.ZEN_MODE), false,
+                mSettingsObserver);
+
+        if (ENABLE_LOCK_SCREEN_ALLOW_REMOTE_INPUT) {
+            mContext.getContentResolver().registerContentObserver(
+                    Settings.Secure.getUriFor(Settings.Secure.LOCK_SCREEN_ALLOW_REMOTE_INPUT),
+                    false,
+                    mSettingsObserver,
+                    UserHandle.USER_ALL);
+        }
+
+        IntentFilter allUsersFilter = new IntentFilter();
+        allUsersFilter.addAction(
+                DevicePolicyManager.ACTION_DEVICE_POLICY_MANAGER_STATE_CHANGED);
+        allUsersFilter.addAction(Intent.ACTION_DEVICE_LOCKED_CHANGED);
+        mContext.registerReceiverAsUser(mAllUsersReceiver, UserHandle.ALL, allUsersFilter,
+                null, null);
+
+        IntentFilter filter = new IntentFilter();
+        filter.addAction(Intent.ACTION_USER_SWITCHED);
+        filter.addAction(Intent.ACTION_USER_ADDED);
+        filter.addAction(Intent.ACTION_USER_PRESENT);
+        filter.addAction(Intent.ACTION_USER_UNLOCKED);
+        mContext.registerReceiver(mBaseBroadcastReceiver, filter);
+
+        IntentFilter internalFilter = new IntentFilter();
+        internalFilter.addAction(NOTIFICATION_UNLOCKED_BY_WORK_CHALLENGE_ACTION);
+        mContext.registerReceiver(mBaseBroadcastReceiver, internalFilter, PERMISSION_SELF, null);
+
+        updateCurrentProfilesCache();
+
+        mSettingsObserver.onChange(false);  // set up
+    }
+
+    private void showForegroundManagedProfileActivityToast() {
+        Toast toast = Toast.makeText(mContext,
+                R.string.managed_profile_foreground_toast,
+                Toast.LENGTH_SHORT);
+        TextView text = toast.getView().findViewById(android.R.id.message);
+        text.setCompoundDrawablesRelativeWithIntrinsicBounds(
+                R.drawable.stat_sys_managed_profile_status, 0, 0, 0);
+        int paddingPx = mContext.getResources().getDimensionPixelSize(
+                R.dimen.managed_profile_toast_padding);
+        text.setCompoundDrawablePadding(paddingPx);
+        toast.show();
+    }
+
+    public boolean shouldShowLockscreenNotifications() {
+        return mShowLockscreenNotifications;
+    }
+
+    public boolean shouldAllowLockscreenRemoteInput() {
+        return mAllowLockscreenRemoteInput;
+    }
+
+    public boolean isCurrentProfile(int userId) {
+        synchronized (mCurrentProfiles) {
+            return userId == UserHandle.USER_ALL || mCurrentProfiles.get(userId) != null;
+        }
+    }
+
+    /**
+     * Returns true if we're on a secure lockscreen and the user wants to hide notification data.
+     * If so, notifications should be hidden.
+     */
+    public boolean shouldHideNotifications(int userId) {
+        return isLockscreenPublicMode(userId) && !userAllowsNotificationsInPublic(userId)
+                || (userId != mCurrentUserId && shouldHideNotifications(mCurrentUserId));
+    }
+
+    /**
+     * Returns true if we're on a secure lockscreen and the user wants to hide notifications via
+     * package-specific override.
+     */
+    public boolean shouldHideNotifications(String key) {
+        return isLockscreenPublicMode(mCurrentUserId)
+                && mEntryManager.getNotificationData().getVisibilityOverride(key) ==
+                        Notification.VISIBILITY_SECRET;
+    }
+
+    public boolean shouldShowOnKeyguard(StatusBarNotification sbn) {
+        return mShowLockscreenNotifications
+                && !mEntryManager.getNotificationData().isAmbient(sbn.getKey());
+    }
+
+    private void setShowLockscreenNotifications(boolean show) {
+        mShowLockscreenNotifications = show;
+    }
+
+    private void setLockscreenAllowRemoteInput(boolean allowLockscreenRemoteInput) {
+        mAllowLockscreenRemoteInput = allowLockscreenRemoteInput;
+    }
+
+    protected void updateLockscreenNotificationSetting() {
+        final boolean show = Settings.Secure.getIntForUser(mContext.getContentResolver(),
+                Settings.Secure.LOCK_SCREEN_SHOW_NOTIFICATIONS,
+                1,
+                mCurrentUserId) != 0;
+        final int dpmFlags = mDevicePolicyManager.getKeyguardDisabledFeatures(
+                null /* admin */, mCurrentUserId);
+        final boolean allowedByDpm = (dpmFlags
+                & DevicePolicyManager.KEYGUARD_DISABLE_SECURE_NOTIFICATIONS) == 0;
+
+        setShowLockscreenNotifications(show && allowedByDpm);
+
+        if (ENABLE_LOCK_SCREEN_ALLOW_REMOTE_INPUT) {
+            final boolean remoteInput = Settings.Secure.getIntForUser(mContext.getContentResolver(),
+                    Settings.Secure.LOCK_SCREEN_ALLOW_REMOTE_INPUT,
+                    0,
+                    mCurrentUserId) != 0;
+            final boolean remoteInputDpm =
+                    (dpmFlags & DevicePolicyManager.KEYGUARD_DISABLE_REMOTE_INPUT) == 0;
+
+            setLockscreenAllowRemoteInput(remoteInput && remoteInputDpm);
+        } else {
+            setLockscreenAllowRemoteInput(false);
+        }
+    }
+
+    /**
+     * Has the given user chosen to allow their private (full) notifications to be shown even
+     * when the lockscreen is in "public" (secure & locked) mode?
+     */
+    public boolean userAllowsPrivateNotificationsInPublic(int userHandle) {
+        if (userHandle == UserHandle.USER_ALL) {
+            return true;
+        }
+
+        if (mUsersAllowingPrivateNotifications.indexOfKey(userHandle) < 0) {
+            final boolean allowedByUser = 0 != Settings.Secure.getIntForUser(
+                    mContext.getContentResolver(),
+                    Settings.Secure.LOCK_SCREEN_ALLOW_PRIVATE_NOTIFICATIONS, 0, userHandle);
+            final boolean allowedByDpm = adminAllowsUnredactedNotifications(userHandle);
+            final boolean allowed = allowedByUser && allowedByDpm;
+            mUsersAllowingPrivateNotifications.append(userHandle, allowed);
+            return allowed;
+        }
+
+        return mUsersAllowingPrivateNotifications.get(userHandle);
+    }
+
+    private boolean adminAllowsUnredactedNotifications(int userHandle) {
+        if (userHandle == UserHandle.USER_ALL) {
+            return true;
+        }
+        final int dpmFlags = mDevicePolicyManager.getKeyguardDisabledFeatures(null /* admin */,
+                userHandle);
+        return (dpmFlags & DevicePolicyManager.KEYGUARD_DISABLE_UNREDACTED_NOTIFICATIONS) == 0;
+    }
+
+    /**
+     * Save the current "public" (locked and secure) state of the lockscreen.
+     */
+    public void setLockscreenPublicMode(boolean publicMode, int userId) {
+        mLockscreenPublicMode.put(userId, publicMode);
+    }
+
+    public boolean isLockscreenPublicMode(int userId) {
+        if (userId == UserHandle.USER_ALL) {
+            return mLockscreenPublicMode.get(mCurrentUserId, false);
+        }
+        return mLockscreenPublicMode.get(userId, false);
+    }
+
+    /**
+     * Has the given user chosen to allow notifications to be shown even when the lockscreen is in
+     * "public" (secure & locked) mode?
+     */
+    private boolean userAllowsNotificationsInPublic(int userHandle) {
+        if (userHandle == UserHandle.USER_ALL) {
+            return true;
+        }
+
+        if (mUsersAllowingNotifications.indexOfKey(userHandle) < 0) {
+            final boolean allowed = 0 != Settings.Secure.getIntForUser(
+                    mContext.getContentResolver(),
+                    Settings.Secure.LOCK_SCREEN_SHOW_NOTIFICATIONS, 0, userHandle);
+            mUsersAllowingNotifications.append(userHandle, allowed);
+            return allowed;
+        }
+
+        return mUsersAllowingNotifications.get(userHandle);
+    }
+
+    /** @return true if the entry needs redaction when on the lockscreen. */
+    public boolean needsRedaction(NotificationData.Entry ent) {
+        int userId = ent.notification.getUserId();
+
+        boolean currentUserWantsRedaction = !userAllowsPrivateNotificationsInPublic(mCurrentUserId);
+        boolean notiUserWantsRedaction = !userAllowsPrivateNotificationsInPublic(userId);
+        boolean redactedLockscreen = currentUserWantsRedaction || notiUserWantsRedaction;
+
+        boolean notificationRequestsRedaction =
+                ent.notification.getNotification().visibility == Notification.VISIBILITY_PRIVATE;
+        boolean userForcesRedaction = packageHasVisibilityOverride(ent.notification.getKey());
+
+        return userForcesRedaction || notificationRequestsRedaction && redactedLockscreen;
+    }
+
+    private boolean packageHasVisibilityOverride(String key) {
+        return mEntryManager.getNotificationData().getVisibilityOverride(key) ==
+                Notification.VISIBILITY_PRIVATE;
+    }
+
+
+    private void updateCurrentProfilesCache() {
+        synchronized (mCurrentProfiles) {
+            mCurrentProfiles.clear();
+            if (mUserManager != null) {
+                for (UserInfo user : mUserManager.getProfiles(mCurrentUserId)) {
+                    mCurrentProfiles.put(user.id, user);
+                }
+            }
+        }
+    }
+
+    public boolean isAnyProfilePublicMode() {
+        for (int i = mCurrentProfiles.size() - 1; i >= 0; i--) {
+            if (isLockscreenPublicMode(mCurrentProfiles.valueAt(i).id)) {
+                return true;
+            }
+        }
+        return false;
+    }
+
+    /**
+     * Returns the current user id. This can change if the user is switched.
+     */
+    public int getCurrentUserId() {
+        return mCurrentUserId;
+    }
+
+    public SparseArray<UserInfo> getCurrentProfiles() {
+        return mCurrentProfiles;
+    }
+
+    public void destroy() {
+        mContext.unregisterReceiver(mBaseBroadcastReceiver);
+        mContext.unregisterReceiver(mAllUsersReceiver);
+    }
+
+    @Override
+    public void dump(FileDescriptor fd, PrintWriter pw, String[] args) {
+        pw.println("NotificationLockscreenUserManager state:");
+        pw.print("  mCurrentUserId=");
+        pw.println(mCurrentUserId);
+        pw.print("  mShowLockscreenNotifications=");
+        pw.println(mShowLockscreenNotifications);
+        pw.print("  mAllowLockscreenRemoteInput=");
+        pw.println(mAllowLockscreenRemoteInput);
+        pw.print("  mCurrentProfiles=");
+        for (int i = mCurrentProfiles.size() - 1; i >= 0; i--) {
+            final int userId = mCurrentProfiles.valueAt(i).id;
+            pw.print("" + userId + " ");
+        }
+        pw.println();
+    }
+}
diff --git a/com/android/systemui/statusbar/NotificationLogger.java b/com/android/systemui/statusbar/NotificationLogger.java
new file mode 100644
index 0000000..4225f83
--- /dev/null
+++ b/com/android/systemui/statusbar/NotificationLogger.java
@@ -0,0 +1,227 @@
+/*
+ * Copyright (C) 2017 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License
+ */
+package com.android.systemui.statusbar;
+
+import android.content.Context;
+import android.os.Handler;
+import android.os.RemoteException;
+import android.os.ServiceManager;
+import android.os.SystemClock;
+import android.service.notification.NotificationListenerService;
+import android.util.ArraySet;
+import android.util.Log;
+
+import com.android.internal.annotations.VisibleForTesting;
+import com.android.internal.statusbar.IStatusBarService;
+import com.android.internal.statusbar.NotificationVisibility;
+import com.android.systemui.Dependency;
+import com.android.systemui.UiOffloadThread;
+
+import java.util.ArrayList;
+import java.util.Collection;
+import java.util.Collections;
+
+/**
+ * Handles notification logging, in particular, logging which notifications are visible and which
+ * are not.
+ */
+public class NotificationLogger {
+    private static final String TAG = "NotificationLogger";
+
+    /** The minimum delay in ms between reports of notification visibility. */
+    private static final int VISIBILITY_REPORT_MIN_DELAY_MS = 500;
+
+    /** Keys of notifications currently visible to the user. */
+    private final ArraySet<NotificationVisibility> mCurrentlyVisibleNotifications =
+            new ArraySet<>();
+
+    // Dependencies:
+    private final NotificationListenerService mNotificationListener =
+            Dependency.get(NotificationListener.class);
+    private final UiOffloadThread mUiOffloadThread = Dependency.get(UiOffloadThread.class);
+
+    protected NotificationEntryManager mEntryManager;
+    protected Handler mHandler = new Handler();
+    protected IStatusBarService mBarService;
+    private long mLastVisibilityReportUptimeMs;
+    private NotificationListContainer mListContainer;
+
+    protected final OnChildLocationsChangedListener mNotificationLocationsChangedListener =
+            new OnChildLocationsChangedListener() {
+                @Override
+                public void onChildLocationsChanged() {
+                    if (mHandler.hasCallbacks(mVisibilityReporter)) {
+                        // Visibilities will be reported when the existing
+                        // callback is executed.
+                        return;
+                    }
+                    // Calculate when we're allowed to run the visibility
+                    // reporter. Note that this timestamp might already have
+                    // passed. That's OK, the callback will just be executed
+                    // ASAP.
+                    long nextReportUptimeMs =
+                            mLastVisibilityReportUptimeMs + VISIBILITY_REPORT_MIN_DELAY_MS;
+                    mHandler.postAtTime(mVisibilityReporter, nextReportUptimeMs);
+                }
+            };
+
+    // Tracks notifications currently visible in mNotificationStackScroller and
+    // emits visibility events via NoMan on changes.
+    protected final Runnable mVisibilityReporter = new Runnable() {
+        private final ArraySet<NotificationVisibility> mTmpNewlyVisibleNotifications =
+                new ArraySet<>();
+        private final ArraySet<NotificationVisibility> mTmpCurrentlyVisibleNotifications =
+                new ArraySet<>();
+        private final ArraySet<NotificationVisibility> mTmpNoLongerVisibleNotifications =
+                new ArraySet<>();
+
+        @Override
+        public void run() {
+            mLastVisibilityReportUptimeMs = SystemClock.uptimeMillis();
+
+            // 1. Loop over mNotificationData entries:
+            //   A. Keep list of visible notifications.
+            //   B. Keep list of previously hidden, now visible notifications.
+            // 2. Compute no-longer visible notifications by removing currently
+            //    visible notifications from the set of previously visible
+            //    notifications.
+            // 3. Report newly visible and no-longer visible notifications.
+            // 4. Keep currently visible notifications for next report.
+            ArrayList<NotificationData.Entry> activeNotifications = mEntryManager
+                    .getNotificationData().getActiveNotifications();
+            int N = activeNotifications.size();
+            for (int i = 0; i < N; i++) {
+                NotificationData.Entry entry = activeNotifications.get(i);
+                String key = entry.notification.getKey();
+                boolean isVisible = mListContainer.isInVisibleLocation(entry.row);
+                NotificationVisibility visObj = NotificationVisibility.obtain(key, i, isVisible);
+                boolean previouslyVisible = mCurrentlyVisibleNotifications.contains(visObj);
+                if (isVisible) {
+                    // Build new set of visible notifications.
+                    mTmpCurrentlyVisibleNotifications.add(visObj);
+                    if (!previouslyVisible) {
+                        mTmpNewlyVisibleNotifications.add(visObj);
+                    }
+                } else {
+                    // release object
+                    visObj.recycle();
+                }
+            }
+            mTmpNoLongerVisibleNotifications.addAll(mCurrentlyVisibleNotifications);
+            mTmpNoLongerVisibleNotifications.removeAll(mTmpCurrentlyVisibleNotifications);
+
+            logNotificationVisibilityChanges(
+                    mTmpNewlyVisibleNotifications, mTmpNoLongerVisibleNotifications);
+
+            recycleAllVisibilityObjects(mCurrentlyVisibleNotifications);
+            mCurrentlyVisibleNotifications.addAll(mTmpCurrentlyVisibleNotifications);
+
+            recycleAllVisibilityObjects(mTmpNoLongerVisibleNotifications);
+            mTmpCurrentlyVisibleNotifications.clear();
+            mTmpNewlyVisibleNotifications.clear();
+            mTmpNoLongerVisibleNotifications.clear();
+        }
+    };
+
+    public NotificationLogger() {
+        mBarService = IStatusBarService.Stub.asInterface(
+                ServiceManager.getService(Context.STATUS_BAR_SERVICE));
+    }
+
+    public void setUpWithEntryManager(NotificationEntryManager entryManager,
+            NotificationListContainer listContainer) {
+        mEntryManager = entryManager;
+        mListContainer = listContainer;
+    }
+
+    public void stopNotificationLogging() {
+        // Report all notifications as invisible and turn down the
+        // reporter.
+        if (!mCurrentlyVisibleNotifications.isEmpty()) {
+            logNotificationVisibilityChanges(
+                    Collections.emptyList(), mCurrentlyVisibleNotifications);
+            recycleAllVisibilityObjects(mCurrentlyVisibleNotifications);
+        }
+        mHandler.removeCallbacks(mVisibilityReporter);
+        mListContainer.setChildLocationsChangedListener(null);
+    }
+
+    public void startNotificationLogging() {
+        mListContainer.setChildLocationsChangedListener(mNotificationLocationsChangedListener);
+        // Some transitions like mVisibleToUser=false -> mVisibleToUser=true don't
+        // cause the scroller to emit child location events. Hence generate
+        // one ourselves to guarantee that we're reporting visible
+        // notifications.
+        // (Note that in cases where the scroller does emit events, this
+        // additional event doesn't break anything.)
+        mNotificationLocationsChangedListener.onChildLocationsChanged();
+    }
+
+    private void logNotificationVisibilityChanges(
+            Collection<NotificationVisibility> newlyVisible,
+            Collection<NotificationVisibility> noLongerVisible) {
+        if (newlyVisible.isEmpty() && noLongerVisible.isEmpty()) {
+            return;
+        }
+        NotificationVisibility[] newlyVisibleAr =
+                newlyVisible.toArray(new NotificationVisibility[newlyVisible.size()]);
+        NotificationVisibility[] noLongerVisibleAr =
+                noLongerVisible.toArray(new NotificationVisibility[noLongerVisible.size()]);
+        mUiOffloadThread.submit(() -> {
+            try {
+                mBarService.onNotificationVisibilityChanged(newlyVisibleAr, noLongerVisibleAr);
+            } catch (RemoteException e) {
+                // Ignore.
+            }
+
+            final int N = newlyVisible.size();
+            if (N > 0) {
+                String[] newlyVisibleKeyAr = new String[N];
+                for (int i = 0; i < N; i++) {
+                    newlyVisibleKeyAr[i] = newlyVisibleAr[i].key;
+                }
+
+                // TODO: Call NotificationEntryManager to do this, once it exists.
+                // TODO: Consider not catching all runtime exceptions here.
+                try {
+                    mNotificationListener.setNotificationsShown(newlyVisibleKeyAr);
+                } catch (RuntimeException e) {
+                    Log.d(TAG, "failed setNotificationsShown: ", e);
+                }
+            }
+        });
+    }
+
+    private void recycleAllVisibilityObjects(ArraySet<NotificationVisibility> array) {
+        final int N = array.size();
+        for (int i = 0 ; i < N; i++) {
+            array.valueAt(i).recycle();
+        }
+        array.clear();
+    }
+
+    @VisibleForTesting
+    public Runnable getVisibilityReporter() {
+        return mVisibilityReporter;
+    }
+
+    /**
+     * A listener that is notified when some child locations might have changed.
+     */
+    public interface OnChildLocationsChangedListener {
+        void onChildLocationsChanged();
+    }
+}
diff --git a/com/android/systemui/statusbar/NotificationMediaManager.java b/com/android/systemui/statusbar/NotificationMediaManager.java
index e65bab2..852239a 100644
--- a/com/android/systemui/statusbar/NotificationMediaManager.java
+++ b/com/android/systemui/statusbar/NotificationMediaManager.java
@@ -1,3 +1,18 @@
+/*
+ * Copyright (C) 2017 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License
+ */
 package com.android.systemui.statusbar;
 
 import android.app.Notification;
@@ -25,9 +40,11 @@
     private static final String TAG = "NotificationMediaManager";
     public static final boolean DEBUG_MEDIA = false;
 
-    private final NotificationPresenter mPresenter;
     private final Context mContext;
     private final MediaSessionManager mMediaSessionManager;
+
+    protected NotificationPresenter mPresenter;
+    protected NotificationEntryManager mEntryManager;
     private MediaController mMediaController;
     private String mMediaNotificationKey;
     private MediaMetadata mMediaMetadata;
@@ -58,8 +75,7 @@
         }
     };
 
-    public NotificationMediaManager(NotificationPresenter presenter, Context context) {
-        mPresenter = presenter;
+    public NotificationMediaManager(Context context) {
         mContext = context;
         mMediaSessionManager
                 = (MediaSessionManager) mContext.getSystemService(Context.MEDIA_SESSION_SERVICE);
@@ -67,6 +83,12 @@
         // in session state
     }
 
+    public void setUpWithPresenter(NotificationPresenter presenter,
+            NotificationEntryManager entryManager) {
+        mPresenter = presenter;
+        mEntryManager = entryManager;
+    }
+
     public void onNotificationRemoved(String key) {
         if (key.equals(mMediaNotificationKey)) {
             clearCurrentMediaNotification();
@@ -85,8 +107,8 @@
     public void findAndUpdateMediaNotifications() {
         boolean metaDataChanged = false;
 
-        synchronized (mPresenter.getNotificationData()) {
-            ArrayList<NotificationData.Entry> activeNotifications = mPresenter
+        synchronized (mEntryManager.getNotificationData()) {
+            ArrayList<NotificationData.Entry> activeNotifications = mEntryManager
                     .getNotificationData().getActiveNotifications();
             final int N = activeNotifications.size();
 
@@ -173,7 +195,7 @@
         }
 
         if (metaDataChanged) {
-            mPresenter.updateNotifications();
+            mEntryManager.updateNotifications();
         }
         mPresenter.updateMediaMetaData(metaDataChanged, true);
     }
diff --git a/com/android/systemui/statusbar/NotificationPresenter.java b/com/android/systemui/statusbar/NotificationPresenter.java
index 1aca60c..12641a0 100644
--- a/com/android/systemui/statusbar/NotificationPresenter.java
+++ b/com/android/systemui/statusbar/NotificationPresenter.java
@@ -16,7 +16,10 @@
 package com.android.systemui.statusbar;
 
 import android.content.Intent;
-import android.service.notification.NotificationListenerService;
+import android.content.pm.PackageManager;
+import android.os.Handler;
+import android.service.notification.StatusBarNotification;
+import android.view.View;
 
 /**
  * An abstraction of something that presents notifications, e.g. StatusBar. Contains methods
@@ -25,8 +28,11 @@
  * for affecting the state of the system (e.g. starting an intent, given that the presenter may
  * want to perform some action before doing so).
  */
-public interface NotificationPresenter {
-
+public interface NotificationPresenter extends NotificationData.Environment,
+        NotificationRemoteInputManager.Callback,
+        ExpandableNotificationRow.OnExpandClickListener,
+        ActivatableNotificationView.OnActivatedListener,
+        NotificationEntryManager.Callback {
     /**
      * Returns true if the presenter is not visible. For example, it may not be necessary to do
      * animations if this returns true.
@@ -39,41 +45,78 @@
     boolean isPresenterLocked();
 
     /**
-     * Returns the current user id. This can change if the user is switched.
-     */
-    int getCurrentUserId();
-
-    /**
      * Runs the given intent. The presenter may want to run some animations or close itself when
      * this happens.
      */
     void startNotificationGutsIntent(Intent intent, int appUid);
 
     /**
-     * Returns NotificationData.
+     * Returns the Handler for NotificationPresenter.
      */
-    NotificationData getNotificationData();
-
-    // TODO: Create NotificationEntryManager and move this method to there.
-    /**
-     * Signals that some notifications have changed, and NotificationPresenter should update itself.
-     */
-    void updateNotifications();
+    Handler getHandler();
 
     /**
      * Refresh or remove lockscreen artwork from media metadata or the lockscreen wallpaper.
      */
     void updateMediaMetaData(boolean metaDataChanged, boolean allowEnterAnimation);
 
-    // TODO: Create NotificationUpdateHandler and move this method to there.
     /**
-     * Removes a notification.
+     * Called when the locked status of the device is changed for a work profile.
      */
-    void removeNotification(String key, NotificationListenerService.RankingMap ranking);
+    void onWorkChallengeChanged();
 
-    // TODO: Create NotificationEntryManager and move this method to there.
     /**
-     * Gets the latest ranking map.
+     * Called when the current user changes.
+     * @param newUserId new user id
      */
-    NotificationListenerService.RankingMap getLatestRankingMap();
+    void onUserSwitched(int newUserId);
+
+    /**
+     * Gets the NotificationLockscreenUserManager for this Presenter.
+     */
+    NotificationLockscreenUserManager getNotificationLockscreenUserManager();
+
+    /**
+     * Wakes the device up if dozing.
+     *
+     * @param time the time when the request to wake up was issued
+     * @param where which view caused this wake up request
+     */
+    void wakeUpIfDozing(long time, View where);
+
+    /**
+     * True if the device currently requires a PIN, pattern, or password to unlock.
+     *
+     * @param userId user id to query about
+     * @return true iff the device is locked
+     */
+    boolean isDeviceLocked(int userId);
+
+    /**
+     * @return true iff the device is in vr mode
+     */
+    boolean isDeviceInVrMode();
+
+    /**
+     * Updates the visual representation of the notifications.
+     */
+    void updateNotificationViews();
+
+    /**
+     * @return true iff the device is dozing
+     */
+    boolean isDozing();
+
+    /**
+     * Returns the maximum number of notifications to show while locked.
+     *
+     * @param recompute whether something has changed that means we should recompute this value
+     * @return the maximum number of notifications to show while locked
+     */
+    int getMaxNotificationsWhileLocked(boolean recompute);
+
+    /**
+     * Called when the row states are updated by NotificationViewHierarchyManager.
+     */
+    void onUpdateRowStates();
 }
diff --git a/com/android/systemui/statusbar/NotificationRemoteInputManager.java b/com/android/systemui/statusbar/NotificationRemoteInputManager.java
new file mode 100644
index 0000000..f25379a
--- /dev/null
+++ b/com/android/systemui/statusbar/NotificationRemoteInputManager.java
@@ -0,0 +1,446 @@
+/*
+ * Copyright (C) 2017 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License
+ */
+package com.android.systemui.statusbar;
+
+import static android.app.WindowConfiguration.WINDOWING_MODE_FULLSCREEN_OR_SPLIT_SCREEN_SECONDARY;
+
+import android.app.ActivityManager;
+import android.app.PendingIntent;
+import android.app.RemoteInput;
+import android.content.Context;
+import android.content.Intent;
+import android.os.RemoteException;
+import android.os.ServiceManager;
+import android.os.SystemClock;
+import android.os.SystemProperties;
+import android.os.UserManager;
+import android.service.notification.StatusBarNotification;
+import android.util.ArraySet;
+import android.util.Log;
+import android.view.MotionEvent;
+import android.view.View;
+import android.view.ViewGroup;
+import android.view.ViewParent;
+import android.widget.RemoteViews;
+import android.widget.TextView;
+
+import com.android.internal.annotations.VisibleForTesting;
+import com.android.internal.statusbar.IStatusBarService;
+import com.android.systemui.Dependency;
+import com.android.systemui.Dumpable;
+import com.android.systemui.statusbar.policy.RemoteInputView;
+
+import java.io.FileDescriptor;
+import java.io.PrintWriter;
+import java.util.Set;
+
+/**
+ * Class for handling remote input state over a set of notifications. This class handles things
+ * like keeping notifications temporarily that were cancelled as a response to a remote input
+ * interaction, keeping track of notifications to remove when NotificationPresenter is collapsed,
+ * and handling clicks on remote views.
+ */
+public class NotificationRemoteInputManager implements Dumpable {
+    public static final boolean ENABLE_REMOTE_INPUT =
+            SystemProperties.getBoolean("debug.enable_remote_input", true);
+    public static final boolean FORCE_REMOTE_INPUT_HISTORY =
+            SystemProperties.getBoolean("debug.force_remoteinput_history", true);
+    private static final boolean DEBUG = false;
+    private static final String TAG = "NotificationRemoteInputManager";
+
+    /**
+     * How long to wait before auto-dismissing a notification that was kept for remote input, and
+     * has now sent a remote input. We auto-dismiss, because the app may not see a reason to cancel
+     * these given that they technically don't exist anymore. We wait a bit in case the app issues
+     * an update.
+     */
+    private static final int REMOTE_INPUT_KEPT_ENTRY_AUTO_CANCEL_DELAY = 200;
+
+    protected final ArraySet<NotificationData.Entry> mRemoteInputEntriesToRemoveOnCollapse =
+            new ArraySet<>();
+
+    // Dependencies:
+    protected final NotificationLockscreenUserManager mLockscreenUserManager =
+            Dependency.get(NotificationLockscreenUserManager.class);
+
+    /**
+     * Notifications with keys in this set are not actually around anymore. We kept them around
+     * when they were canceled in response to a remote input interaction. This allows us to show
+     * what you replied and allows you to continue typing into it.
+     */
+    protected final ArraySet<String> mKeysKeptForRemoteInput = new ArraySet<>();
+    protected final Context mContext;
+    private final UserManager mUserManager;
+
+    protected RemoteInputController mRemoteInputController;
+    protected NotificationPresenter mPresenter;
+    protected NotificationEntryManager mEntryManager;
+    protected IStatusBarService mBarService;
+    protected Callback mCallback;
+
+    private final RemoteViews.OnClickHandler mOnClickHandler = new RemoteViews.OnClickHandler() {
+
+        @Override
+        public boolean onClickHandler(
+                final View view, final PendingIntent pendingIntent, final Intent fillInIntent) {
+            mPresenter.wakeUpIfDozing(SystemClock.uptimeMillis(), view);
+
+            if (handleRemoteInput(view, pendingIntent)) {
+                return true;
+            }
+
+            if (DEBUG) {
+                Log.v(TAG, "Notification click handler invoked for intent: " + pendingIntent);
+            }
+            logActionClick(view);
+            // The intent we are sending is for the application, which
+            // won't have permission to immediately start an activity after
+            // the user switches to home.  We know it is safe to do at this
+            // point, so make sure new activity switches are now allowed.
+            try {
+                ActivityManager.getService().resumeAppSwitches();
+            } catch (RemoteException e) {
+            }
+            return mCallback.handleRemoteViewClick(view, pendingIntent, fillInIntent,
+                    () -> superOnClickHandler(view, pendingIntent, fillInIntent));
+        }
+
+        private void logActionClick(View view) {
+            ViewParent parent = view.getParent();
+            String key = getNotificationKeyForParent(parent);
+            if (key == null) {
+                Log.w(TAG, "Couldn't determine notification for click.");
+                return;
+            }
+            int index = -1;
+            // If this is a default template, determine the index of the button.
+            if (view.getId() == com.android.internal.R.id.action0 &&
+                    parent != null && parent instanceof ViewGroup) {
+                ViewGroup actionGroup = (ViewGroup) parent;
+                index = actionGroup.indexOfChild(view);
+            }
+            try {
+                mBarService.onNotificationActionClick(key, index);
+            } catch (RemoteException e) {
+                // Ignore
+            }
+        }
+
+        private String getNotificationKeyForParent(ViewParent parent) {
+            while (parent != null) {
+                if (parent instanceof ExpandableNotificationRow) {
+                    return ((ExpandableNotificationRow) parent)
+                            .getStatusBarNotification().getKey();
+                }
+                parent = parent.getParent();
+            }
+            return null;
+        }
+
+        private boolean superOnClickHandler(View view, PendingIntent pendingIntent,
+                Intent fillInIntent) {
+            return super.onClickHandler(view, pendingIntent, fillInIntent,
+                    WINDOWING_MODE_FULLSCREEN_OR_SPLIT_SCREEN_SECONDARY);
+        }
+
+        private boolean handleRemoteInput(View view, PendingIntent pendingIntent) {
+            if (mCallback.shouldHandleRemoteInput(view, pendingIntent)) {
+                return true;
+            }
+
+            Object tag = view.getTag(com.android.internal.R.id.remote_input_tag);
+            RemoteInput[] inputs = null;
+            if (tag instanceof RemoteInput[]) {
+                inputs = (RemoteInput[]) tag;
+            }
+
+            if (inputs == null) {
+                return false;
+            }
+
+            RemoteInput input = null;
+
+            for (RemoteInput i : inputs) {
+                if (i.getAllowFreeFormInput()) {
+                    input = i;
+                }
+            }
+
+            if (input == null) {
+                return false;
+            }
+
+            ViewParent p = view.getParent();
+            RemoteInputView riv = null;
+            while (p != null) {
+                if (p instanceof View) {
+                    View pv = (View) p;
+                    if (pv.isRootNamespace()) {
+                        riv = findRemoteInputView(pv);
+                        break;
+                    }
+                }
+                p = p.getParent();
+            }
+            ExpandableNotificationRow row = null;
+            while (p != null) {
+                if (p instanceof ExpandableNotificationRow) {
+                    row = (ExpandableNotificationRow) p;
+                    break;
+                }
+                p = p.getParent();
+            }
+
+            if (row == null) {
+                return false;
+            }
+
+            row.setUserExpanded(true);
+
+            if (!mLockscreenUserManager.shouldAllowLockscreenRemoteInput()) {
+                final int userId = pendingIntent.getCreatorUserHandle().getIdentifier();
+                if (mLockscreenUserManager.isLockscreenPublicMode(userId)) {
+                    mCallback.onLockedRemoteInput(row, view);
+                    return true;
+                }
+                if (mUserManager.getUserInfo(userId).isManagedProfile()
+                        && mPresenter.isDeviceLocked(userId)) {
+                    mCallback.onLockedWorkRemoteInput(userId, row, view);
+                    return true;
+                }
+            }
+
+            if (riv == null) {
+                riv = findRemoteInputView(row.getPrivateLayout().getExpandedChild());
+                if (riv == null) {
+                    return false;
+                }
+                if (!row.getPrivateLayout().getExpandedChild().isShown()) {
+                    mCallback.onMakeExpandedVisibleForRemoteInput(row, view);
+                    return true;
+                }
+            }
+
+            int width = view.getWidth();
+            if (view instanceof TextView) {
+                // Center the reveal on the text which might be off-center from the TextView
+                TextView tv = (TextView) view;
+                if (tv.getLayout() != null) {
+                    int innerWidth = (int) tv.getLayout().getLineWidth(0);
+                    innerWidth += tv.getCompoundPaddingLeft() + tv.getCompoundPaddingRight();
+                    width = Math.min(width, innerWidth);
+                }
+            }
+            int cx = view.getLeft() + width / 2;
+            int cy = view.getTop() + view.getHeight() / 2;
+            int w = riv.getWidth();
+            int h = riv.getHeight();
+            int r = Math.max(
+                    Math.max(cx + cy, cx + (h - cy)),
+                    Math.max((w - cx) + cy, (w - cx) + (h - cy)));
+
+            riv.setRevealParameters(cx, cy, r);
+            riv.setPendingIntent(pendingIntent);
+            riv.setRemoteInput(inputs, input);
+            riv.focusAnimated();
+
+            return true;
+        }
+
+        private RemoteInputView findRemoteInputView(View v) {
+            if (v == null) {
+                return null;
+            }
+            return (RemoteInputView) v.findViewWithTag(RemoteInputView.VIEW_TAG);
+        }
+    };
+
+    public NotificationRemoteInputManager(Context context) {
+        mContext = context;
+        mBarService = IStatusBarService.Stub.asInterface(
+                ServiceManager.getService(Context.STATUS_BAR_SERVICE));
+        mUserManager = (UserManager) mContext.getSystemService(Context.USER_SERVICE);
+    }
+
+    public void setUpWithPresenter(NotificationPresenter presenter,
+            NotificationEntryManager entryManager,
+            Callback callback,
+            RemoteInputController.Delegate delegate) {
+        mPresenter = presenter;
+        mEntryManager = entryManager;
+        mCallback = callback;
+        mRemoteInputController = new RemoteInputController(delegate);
+        mRemoteInputController.addCallback(new RemoteInputController.Callback() {
+            @Override
+            public void onRemoteInputSent(NotificationData.Entry entry) {
+                if (FORCE_REMOTE_INPUT_HISTORY && mKeysKeptForRemoteInput.contains(entry.key)) {
+                    mEntryManager.removeNotification(entry.key, null);
+                } else if (mRemoteInputEntriesToRemoveOnCollapse.contains(entry)) {
+                    // We're currently holding onto this notification, but from the apps point of
+                    // view it is already canceled, so we'll need to cancel it on the apps behalf
+                    // after sending - unless the app posts an update in the mean time, so wait a
+                    // bit.
+                    mPresenter.getHandler().postDelayed(() -> {
+                        if (mRemoteInputEntriesToRemoveOnCollapse.remove(entry)) {
+                            mEntryManager.removeNotification(entry.key, null);
+                        }
+                    }, REMOTE_INPUT_KEPT_ENTRY_AUTO_CANCEL_DELAY);
+                }
+            }
+        });
+
+    }
+
+    public RemoteInputController getController() {
+        return mRemoteInputController;
+    }
+
+    public void onUpdateNotification(NotificationData.Entry entry) {
+        mRemoteInputEntriesToRemoveOnCollapse.remove(entry);
+    }
+
+    /**
+     * Returns true if NotificationRemoteInputManager wants to keep this notification around.
+     *
+     * @param entry notification being removed
+     */
+    public boolean onRemoveNotification(NotificationData.Entry entry) {
+        if (entry != null && mRemoteInputController.isRemoteInputActive(entry)
+                && (entry.row != null && !entry.row.isDismissed())) {
+            mRemoteInputEntriesToRemoveOnCollapse.add(entry);
+            return true;
+        }
+        return false;
+    }
+
+    public void onPerformRemoveNotification(StatusBarNotification n,
+            NotificationData.Entry entry) {
+        if (mRemoteInputController.isRemoteInputActive(entry)) {
+            mRemoteInputController.removeRemoteInput(entry, null);
+        }
+        if (FORCE_REMOTE_INPUT_HISTORY
+                && mKeysKeptForRemoteInput.contains(n.getKey())) {
+            mKeysKeptForRemoteInput.remove(n.getKey());
+        }
+    }
+
+    public void removeRemoteInputEntriesKeptUntilCollapsed() {
+        for (int i = 0; i < mRemoteInputEntriesToRemoveOnCollapse.size(); i++) {
+            NotificationData.Entry entry = mRemoteInputEntriesToRemoveOnCollapse.valueAt(i);
+            mRemoteInputController.removeRemoteInput(entry, null);
+            mEntryManager.removeNotification(entry.key, mEntryManager.getLatestRankingMap());
+        }
+        mRemoteInputEntriesToRemoveOnCollapse.clear();
+    }
+
+    public void checkRemoteInputOutside(MotionEvent event) {
+        if (event.getAction() == MotionEvent.ACTION_OUTSIDE // touch outside the source bar
+                && event.getX() == 0 && event.getY() == 0  // a touch outside both bars
+                && mRemoteInputController.isRemoteInputActive()) {
+            mRemoteInputController.closeRemoteInputs();
+        }
+    }
+
+    @Override
+    public void dump(FileDescriptor fd, PrintWriter pw, String[] args) {
+        pw.println("NotificationRemoteInputManager state:");
+        pw.print("  mRemoteInputEntriesToRemoveOnCollapse: ");
+        pw.println(mRemoteInputEntriesToRemoveOnCollapse);
+        pw.print("  mKeysKeptForRemoteInput: ");
+        pw.println(mKeysKeptForRemoteInput);
+    }
+
+    public void bindRow(ExpandableNotificationRow row) {
+        row.setRemoteInputController(mRemoteInputController);
+        row.setRemoteViewClickHandler(mOnClickHandler);
+    }
+
+    public Set<String> getKeysKeptForRemoteInput() {
+        return mKeysKeptForRemoteInput;
+    }
+
+    @VisibleForTesting
+    public Set<NotificationData.Entry> getRemoteInputEntriesToRemoveOnCollapse() {
+        return mRemoteInputEntriesToRemoveOnCollapse;
+    }
+
+    /**
+     * Callback for various remote input related events, or for providing information that
+     * NotificationRemoteInputManager needs to know to decide what to do.
+     */
+    public interface Callback {
+
+        /**
+         * Called when remote input was activated but the device is locked.
+         *
+         * @param row
+         * @param clicked
+         */
+        void onLockedRemoteInput(ExpandableNotificationRow row, View clicked);
+
+        /**
+         * Called when remote input was activated but the device is locked and in a managed profile.
+         *
+         * @param userId
+         * @param row
+         * @param clicked
+         */
+        void onLockedWorkRemoteInput(int userId, ExpandableNotificationRow row, View clicked);
+
+        /**
+         * Called when a row should be made expanded for the purposes of remote input.
+         *
+         * @param row
+         * @param clickedView
+         */
+        void onMakeExpandedVisibleForRemoteInput(ExpandableNotificationRow row, View clickedView);
+
+        /**
+         * Return whether or not remote input should be handled for this view.
+         *
+         * @param view
+         * @param pendingIntent
+         * @return true iff the remote input should be handled
+         */
+        boolean shouldHandleRemoteInput(View view, PendingIntent pendingIntent);
+
+        /**
+         * Performs any special handling for a remote view click. The default behaviour can be
+         * called through the defaultHandler parameter.
+         *
+         * @param view
+         * @param pendingIntent
+         * @param fillInIntent
+         * @param defaultHandler
+         * @return  true iff the click was handled
+         */
+        boolean handleRemoteViewClick(View view, PendingIntent pendingIntent, Intent fillInIntent,
+                ClickHandler defaultHandler);
+    }
+
+    /**
+     * Helper interface meant for passing the default on click behaviour to NotificationPresenter,
+     * so it may do its own handling before invoking the default behaviour.
+     */
+    public interface ClickHandler {
+        /**
+         * Tries to handle a click on a remote view.
+         *
+         * @return true iff the click was handled
+         */
+        boolean handleClick();
+    }
+}
diff --git a/com/android/systemui/statusbar/NotificationShelf.java b/com/android/systemui/statusbar/NotificationShelf.java
index b7a00eb..8325df7 100644
--- a/com/android/systemui/statusbar/NotificationShelf.java
+++ b/com/android/systemui/statusbar/NotificationShelf.java
@@ -25,6 +25,7 @@
 import android.graphics.Rect;
 import android.os.SystemProperties;
 import android.util.AttributeSet;
+import android.util.Log;
 import android.view.View;
 import android.view.ViewGroup;
 import android.view.ViewTreeObserver;
@@ -56,6 +57,7 @@
     private static final boolean ICON_ANMATIONS_WHILE_SCROLLING
             = SystemProperties.getBoolean("debug.icon_scroll_animations", true);
     private static final int TAG_CONTINUOUS_CLIPPING = R.id.continuous_clipping_tag;
+    private static final String TAG = "NotificationShelf";
     private ViewInvertHelper mViewInvertHelper;
     private boolean mDark;
     private NotificationIconContainer mShelfIcons;
@@ -82,9 +84,6 @@
     private boolean mNoAnimationsInThisFrame;
     private boolean mAnimationsEnabled = true;
     private boolean mShowNotificationShelf;
-    private boolean mVibrationOnAnimation;
-    private boolean mUserTouchingScreen;
-    private boolean mTouchActive;
     private float mFirstElementRoundness;
 
     public NotificationShelf(Context context, AttributeSet attrs) {
@@ -101,10 +100,7 @@
         setClipToActualHeight(false);
         setClipChildren(false);
         setClipToPadding(false);
-        mShelfIcons.setShowAllIcons(false);
-        mVibrationOnAnimation = mContext.getResources().getBoolean(
-                R.bool.config_vibrateOnIconAnimation);
-        updateVibrationOnAnimation();
+        mShelfIcons.setIsStaticLayout(false);
         mViewInvertHelper = new ViewInvertHelper(mShelfIcons,
                 NotificationPanelView.DOZE_ANIMATION_DURATION);
         mShelfState = new ShelfState();
@@ -112,15 +108,6 @@
         initDimens();
     }
 
-    private void updateVibrationOnAnimation() {
-        mShelfIcons.setVibrateOnAnimation(mVibrationOnAnimation && mTouchActive);
-    }
-
-    public void setTouchActive(boolean touchActive) {
-        mTouchActive = touchActive;
-        updateVibrationOnAnimation();
-    }
-
     public void bind(AmbientState ambientState, NotificationStackScrollLayout hostLayout) {
         mAmbientState = ambientState;
         mHostLayout = hostLayout;
@@ -309,10 +296,15 @@
             if (notGoneIndex == 0) {
                 StatusBarIconView icon = row.getEntry().expandedIcon;
                 NotificationIconContainer.IconState iconState = getIconState(icon);
-                if (iconState.clampedAppearAmount == 1.0f) {
+                if (iconState != null && iconState.clampedAppearAmount == 1.0f) {
                     // only if the first icon is fully in the shelf we want to clip to it!
                     backgroundTop = (int) (row.getTranslationY() - getTranslationY());
                     firstElementRoundness = row.getCurrentTopRoundness();
+                } else if (iconState == null) {
+                    Log.wtf(TAG, "iconState is null. ExpandedIcon: " + row.getEntry().expandedIcon
+                            + (row.getEntry().expandedIcon != null
+                            ? "\n icon parent: " + row.getEntry().expandedIcon.getParent() : "")
+                            + " \n number of notifications: " + mHostLayout.getChildCount() );
                 }
             }
             notGoneIndex++;
@@ -696,7 +688,8 @@
         if (isLayoutRtl()) {
             start = getWidth() - start - mCollapsedIcons.getWidth();
         }
-        int width = (int) NotificationUtils.interpolate(start + mCollapsedIcons.getWidth(),
+        int width = (int) NotificationUtils.interpolate(
+                start + mCollapsedIcons.getFinalTranslationX(),
                 mShelfIcons.getWidth(),
                 openedAmount);
         mShelfIcons.setActualLayoutWidth(width);
@@ -706,6 +699,9 @@
             // we have to ensure that adding the low priority notification won't lead to an
             // overflow
             collapsedPadding -= (1.0f + OVERFLOW_EARLY_AMOUNT) * mCollapsedIcons.getIconSize();
+        } else {
+            // Partial overflow padding will fill enough space to add extra dots
+            collapsedPadding -= mCollapsedIcons.getPartialOverflowExtraPadding();
         }
         float padding = NotificationUtils.interpolate(collapsedPadding,
                 mShelfIcons.getPaddingEnd(),
@@ -715,7 +711,6 @@
                 mShelfIcons.getPaddingStart(), openedAmount);
         mShelfIcons.setActualPaddingStart(paddingStart);
         mShelfIcons.setOpenedAmount(openedAmount);
-        mShelfIcons.setVisualOverflowAdaption(mCollapsedIcons.getVisualOverflowAdaption());
     }
 
     public void setMaxLayoutHeight(int maxLayoutHeight) {
diff --git a/com/android/systemui/statusbar/NotificationSnooze.java b/com/android/systemui/statusbar/NotificationSnooze.java
index 492ab44..aea0127 100644
--- a/com/android/systemui/statusbar/NotificationSnooze.java
+++ b/com/android/systemui/statusbar/NotificationSnooze.java
@@ -16,7 +16,6 @@
  */
 
 import java.util.ArrayList;
-import java.util.Arrays;
 import java.util.List;
 import java.util.concurrent.TimeUnit;
 
@@ -234,7 +233,7 @@
 
         final int defaultSnooze = mParser.getInt(KEY_DEFAULT_SNOOZE,
                 resources.getInteger(R.integer.config_notification_snooze_time_default));
-        final int[] snoozeTimes = parseIntArray(KEY_OPTIONS,
+        final int[] snoozeTimes = mParser.getIntArray(KEY_OPTIONS,
                 resources.getIntArray(R.array.config_notification_snooze_times));
 
         for (int i = 0; i < snoozeTimes.length && i < sAccessibilityActions.length; i++) {
@@ -248,21 +247,6 @@
         return options;
     }
 
-    @VisibleForTesting
-    int[] parseIntArray(final String key, final int[] defaultArray) {
-        final String value = mParser.getString(key, null);
-        if (value != null) {
-            try {
-                return Arrays.stream(value.split(":")).map(String::trim).mapToInt(
-                        Integer::parseInt).toArray();
-            } catch (NumberFormatException e) {
-                return defaultArray;
-            }
-        } else {
-            return defaultArray;
-        }
-    }
-
     private SnoozeOption createOption(int minutes, int accessibilityActionId) {
         Resources res = getResources();
         boolean showInHours = minutes >= 60;
diff --git a/com/android/systemui/statusbar/NotificationUpdateHandler.java b/com/android/systemui/statusbar/NotificationUpdateHandler.java
new file mode 100644
index 0000000..0044194
--- /dev/null
+++ b/com/android/systemui/statusbar/NotificationUpdateHandler.java
@@ -0,0 +1,58 @@
+/*
+ * Copyright (C) 2017 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License
+ */
+
+package com.android.systemui.statusbar;
+
+import android.service.notification.NotificationListenerService;
+import android.service.notification.StatusBarNotification;
+
+/**
+ * Interface for accepting notification updates from {@link NotificationListener}.
+ */
+public interface NotificationUpdateHandler {
+    /**
+     * Add a new notification and update the current notification ranking map.
+     *
+     * @param notification Notification to add
+     * @param ranking RankingMap to update with
+     */
+    void addNotification(StatusBarNotification notification,
+            NotificationListenerService.RankingMap ranking);
+
+    /**
+     * Remove a notification and update the current notification ranking map.
+     *
+     * @param key Key identifying the notification to remove
+     * @param ranking RankingMap to update with
+     */
+    void removeNotification(String key, NotificationListenerService.RankingMap ranking);
+
+    /**
+     * Update a given notification and the current notification ranking map.
+     *
+     * @param notification Updated notification
+     * @param ranking RankingMap to update with
+     */
+    void updateNotification(StatusBarNotification notification,
+            NotificationListenerService.RankingMap ranking);
+
+    /**
+     * Update with a new notification ranking map.
+     *
+     * @param ranking RankingMap to update with
+     */
+    void updateNotificationRanking(NotificationListenerService.RankingMap ranking);
+}
diff --git a/com/android/systemui/statusbar/NotificationViewHierarchyManager.java b/com/android/systemui/statusbar/NotificationViewHierarchyManager.java
new file mode 100644
index 0000000..266c09b
--- /dev/null
+++ b/com/android/systemui/statusbar/NotificationViewHierarchyManager.java
@@ -0,0 +1,345 @@
+/*
+ * Copyright (C) 2017 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License
+ */
+
+package com.android.systemui.statusbar;
+
+import android.content.Context;
+import android.content.res.Resources;
+import android.util.Log;
+import android.view.View;
+import android.view.ViewGroup;
+
+import com.android.systemui.Dependency;
+import com.android.systemui.R;
+import com.android.systemui.statusbar.notification.VisualStabilityManager;
+import com.android.systemui.statusbar.phone.NotificationGroupManager;
+
+import java.util.ArrayList;
+import java.util.HashMap;
+import java.util.List;
+import java.util.Stack;
+
+/**
+ * NotificationViewHierarchyManager manages updating the view hierarchy of notification views based
+ * on their group structure. For example, if a notification becomes bundled with another,
+ * NotificationViewHierarchyManager will update the view hierarchy to reflect that. It also will
+ * tell NotificationListContainer which notifications to display, and inform it of changes to those
+ * notifications that might affect their display.
+ */
+public class NotificationViewHierarchyManager {
+    private static final String TAG = "NotificationViewHierarchyManager";
+
+    private final HashMap<ExpandableNotificationRow, List<ExpandableNotificationRow>>
+            mTmpChildOrderMap = new HashMap<>();
+
+    // Dependencies:
+    protected final NotificationLockscreenUserManager mLockscreenUserManager =
+            Dependency.get(NotificationLockscreenUserManager.class);
+    protected final NotificationGroupManager mGroupManager =
+            Dependency.get(NotificationGroupManager.class);
+    protected final VisualStabilityManager mVisualStabilityManager =
+            Dependency.get(VisualStabilityManager.class);
+
+    /**
+     * {@code true} if notifications not part of a group should by default be rendered in their
+     * expanded state. If {@code false}, then only the first notification will be expanded if
+     * possible.
+     */
+    private final boolean mAlwaysExpandNonGroupedNotification;
+
+    private NotificationPresenter mPresenter;
+    private NotificationEntryManager mEntryManager;
+    private NotificationListContainer mListContainer;
+
+    public NotificationViewHierarchyManager(Context context) {
+        Resources res = context.getResources();
+        mAlwaysExpandNonGroupedNotification =
+                res.getBoolean(R.bool.config_alwaysExpandNonGroupedNotifications);
+    }
+
+    public void setUpWithPresenter(NotificationPresenter presenter,
+            NotificationEntryManager entryManager, NotificationListContainer listContainer) {
+        mPresenter = presenter;
+        mEntryManager = entryManager;
+        mListContainer = listContainer;
+    }
+
+    /**
+     * Updates the visual representation of the notifications.
+     */
+    public void updateNotificationViews() {
+        ArrayList<NotificationData.Entry> activeNotifications = mEntryManager.getNotificationData()
+                .getActiveNotifications();
+        ArrayList<ExpandableNotificationRow> toShow = new ArrayList<>(activeNotifications.size());
+        final int N = activeNotifications.size();
+        for (int i = 0; i < N; i++) {
+            NotificationData.Entry ent = activeNotifications.get(i);
+            if (ent.row.isDismissed() || ent.row.isRemoved()) {
+                // we don't want to update removed notifications because they could
+                // temporarily become children if they were isolated before.
+                continue;
+            }
+            int userId = ent.notification.getUserId();
+
+            // Display public version of the notification if we need to redact.
+            // TODO: This area uses a lot of calls into NotificationLockscreenUserManager.
+            // We can probably move some of this code there.
+            boolean devicePublic = mLockscreenUserManager.isLockscreenPublicMode(
+                    mLockscreenUserManager.getCurrentUserId());
+            boolean userPublic = devicePublic
+                    || mLockscreenUserManager.isLockscreenPublicMode(userId);
+            boolean needsRedaction = mLockscreenUserManager.needsRedaction(ent);
+            boolean sensitive = userPublic && needsRedaction;
+            boolean deviceSensitive = devicePublic
+                    && !mLockscreenUserManager.userAllowsPrivateNotificationsInPublic(
+                    mLockscreenUserManager.getCurrentUserId());
+            ent.row.setSensitive(sensitive, deviceSensitive);
+            ent.row.setNeedsRedaction(needsRedaction);
+            if (mGroupManager.isChildInGroupWithSummary(ent.row.getStatusBarNotification())) {
+                ExpandableNotificationRow summary = mGroupManager.getGroupSummary(
+                        ent.row.getStatusBarNotification());
+                List<ExpandableNotificationRow> orderedChildren =
+                        mTmpChildOrderMap.get(summary);
+                if (orderedChildren == null) {
+                    orderedChildren = new ArrayList<>();
+                    mTmpChildOrderMap.put(summary, orderedChildren);
+                }
+                orderedChildren.add(ent.row);
+            } else {
+                toShow.add(ent.row);
+            }
+
+        }
+
+        ArrayList<ExpandableNotificationRow> toRemove = new ArrayList<>();
+        for (int i=0; i< mListContainer.getContainerChildCount(); i++) {
+            View child = mListContainer.getContainerChildAt(i);
+            if (!toShow.contains(child) && child instanceof ExpandableNotificationRow) {
+                toRemove.add((ExpandableNotificationRow) child);
+            }
+        }
+
+        for (ExpandableNotificationRow remove : toRemove) {
+            if (mGroupManager.isChildInGroupWithSummary(remove.getStatusBarNotification())) {
+                // we are only transferring this notification to its parent, don't generate an
+                // animation
+                mListContainer.setChildTransferInProgress(true);
+            }
+            if (remove.isSummaryWithChildren()) {
+                remove.removeAllChildren();
+            }
+            mListContainer.removeContainerView(remove);
+            mListContainer.setChildTransferInProgress(false);
+        }
+
+        removeNotificationChildren();
+
+        for (int i = 0; i < toShow.size(); i++) {
+            View v = toShow.get(i);
+            if (v.getParent() == null) {
+                mVisualStabilityManager.notifyViewAddition(v);
+                mListContainer.addContainerView(v);
+            }
+        }
+
+        addNotificationChildrenAndSort();
+
+        // So after all this work notifications still aren't sorted correctly.
+        // Let's do that now by advancing through toShow and mListContainer in
+        // lock-step, making sure mListContainer matches what we see in toShow.
+        int j = 0;
+        for (int i = 0; i < mListContainer.getContainerChildCount(); i++) {
+            View child = mListContainer.getContainerChildAt(i);
+            if (!(child instanceof ExpandableNotificationRow)) {
+                // We don't care about non-notification views.
+                continue;
+            }
+
+            ExpandableNotificationRow targetChild = toShow.get(j);
+            if (child != targetChild) {
+                // Oops, wrong notification at this position. Put the right one
+                // here and advance both lists.
+                if (mVisualStabilityManager.canReorderNotification(targetChild)) {
+                    mListContainer.changeViewPosition(targetChild, i);
+                } else {
+                    mVisualStabilityManager.addReorderingAllowedCallback(mEntryManager);
+                }
+            }
+            j++;
+
+        }
+
+        mVisualStabilityManager.onReorderingFinished();
+        // clear the map again for the next usage
+        mTmpChildOrderMap.clear();
+
+        updateRowStates();
+
+        mListContainer.onNotificationViewUpdateFinished();
+    }
+
+    private void addNotificationChildrenAndSort() {
+        // Let's now add all notification children which are missing
+        boolean orderChanged = false;
+        for (int i = 0; i < mListContainer.getContainerChildCount(); i++) {
+            View view = mListContainer.getContainerChildAt(i);
+            if (!(view instanceof ExpandableNotificationRow)) {
+                // We don't care about non-notification views.
+                continue;
+            }
+
+            ExpandableNotificationRow parent = (ExpandableNotificationRow) view;
+            List<ExpandableNotificationRow> children = parent.getNotificationChildren();
+            List<ExpandableNotificationRow> orderedChildren = mTmpChildOrderMap.get(parent);
+
+            for (int childIndex = 0; orderedChildren != null && childIndex < orderedChildren.size();
+                    childIndex++) {
+                ExpandableNotificationRow childView = orderedChildren.get(childIndex);
+                if (children == null || !children.contains(childView)) {
+                    if (childView.getParent() != null) {
+                        Log.wtf(TAG, "trying to add a notification child that already has " +
+                                "a parent. class:" + childView.getParent().getClass() +
+                                "\n child: " + childView);
+                        // This shouldn't happen. We can recover by removing it though.
+                        ((ViewGroup) childView.getParent()).removeView(childView);
+                    }
+                    mVisualStabilityManager.notifyViewAddition(childView);
+                    parent.addChildNotification(childView, childIndex);
+                    mListContainer.notifyGroupChildAdded(childView);
+                }
+            }
+
+            // Finally after removing and adding has been performed we can apply the order.
+            orderChanged |= parent.applyChildOrder(orderedChildren, mVisualStabilityManager,
+                    mEntryManager);
+        }
+        if (orderChanged) {
+            mListContainer.generateChildOrderChangedEvent();
+        }
+    }
+
+    private void removeNotificationChildren() {
+        // First let's remove all children which don't belong in the parents
+        ArrayList<ExpandableNotificationRow> toRemove = new ArrayList<>();
+        for (int i = 0; i < mListContainer.getContainerChildCount(); i++) {
+            View view = mListContainer.getContainerChildAt(i);
+            if (!(view instanceof ExpandableNotificationRow)) {
+                // We don't care about non-notification views.
+                continue;
+            }
+
+            ExpandableNotificationRow parent = (ExpandableNotificationRow) view;
+            List<ExpandableNotificationRow> children = parent.getNotificationChildren();
+            List<ExpandableNotificationRow> orderedChildren = mTmpChildOrderMap.get(parent);
+
+            if (children != null) {
+                toRemove.clear();
+                for (ExpandableNotificationRow childRow : children) {
+                    if ((orderedChildren == null
+                            || !orderedChildren.contains(childRow))
+                            && !childRow.keepInParent()) {
+                        toRemove.add(childRow);
+                    }
+                }
+                for (ExpandableNotificationRow remove : toRemove) {
+                    parent.removeChildNotification(remove);
+                    if (mEntryManager.getNotificationData().get(
+                            remove.getStatusBarNotification().getKey()) == null) {
+                        // We only want to add an animation if the view is completely removed
+                        // otherwise it's just a transfer
+                        mListContainer.notifyGroupChildRemoved(remove,
+                                parent.getChildrenContainer());
+                    }
+                }
+            }
+        }
+    }
+
+    /**
+     * Updates expanded, dimmed and locked states of notification rows.
+     */
+    public void updateRowStates() {
+        final int N = mListContainer.getContainerChildCount();
+
+        int visibleNotifications = 0;
+        boolean isLocked = mPresenter.isPresenterLocked();
+        int maxNotifications = -1;
+        if (isLocked) {
+            maxNotifications = mPresenter.getMaxNotificationsWhileLocked(true /* recompute */);
+        }
+        mListContainer.setMaxDisplayedNotifications(maxNotifications);
+        Stack<ExpandableNotificationRow> stack = new Stack<>();
+        for (int i = N - 1; i >= 0; i--) {
+            View child = mListContainer.getContainerChildAt(i);
+            if (!(child instanceof ExpandableNotificationRow)) {
+                continue;
+            }
+            stack.push((ExpandableNotificationRow) child);
+        }
+        while(!stack.isEmpty()) {
+            ExpandableNotificationRow row = stack.pop();
+            NotificationData.Entry entry = row.getEntry();
+            boolean isChildNotification =
+                    mGroupManager.isChildInGroupWithSummary(entry.notification);
+
+            row.setOnKeyguard(isLocked);
+
+            if (!isLocked) {
+                // If mAlwaysExpandNonGroupedNotification is false, then only expand the
+                // very first notification and if it's not a child of grouped notifications.
+                row.setSystemExpanded(mAlwaysExpandNonGroupedNotification
+                        || (visibleNotifications == 0 && !isChildNotification
+                        && !row.isLowPriority()));
+            }
+
+            entry.row.setShowAmbient(mPresenter.isDozing());
+            int userId = entry.notification.getUserId();
+            boolean suppressedSummary = mGroupManager.isSummaryOfSuppressedGroup(
+                    entry.notification) && !entry.row.isRemoved();
+            boolean showOnKeyguard = mLockscreenUserManager.shouldShowOnKeyguard(entry
+                    .notification);
+            if (suppressedSummary
+                    || (mLockscreenUserManager.isLockscreenPublicMode(userId)
+                    && !mLockscreenUserManager.shouldShowLockscreenNotifications())
+                    || (isLocked && !showOnKeyguard)) {
+                entry.row.setVisibility(View.GONE);
+            } else {
+                boolean wasGone = entry.row.getVisibility() == View.GONE;
+                if (wasGone) {
+                    entry.row.setVisibility(View.VISIBLE);
+                }
+                if (!isChildNotification && !entry.row.isRemoved()) {
+                    if (wasGone) {
+                        // notify the scroller of a child addition
+                        mListContainer.generateAddAnimation(entry.row,
+                                !showOnKeyguard /* fromMoreCard */);
+                    }
+                    visibleNotifications++;
+                }
+            }
+            if (row.isSummaryWithChildren()) {
+                List<ExpandableNotificationRow> notificationChildren =
+                        row.getNotificationChildren();
+                int size = notificationChildren.size();
+                for (int i = size - 1; i >= 0; i--) {
+                    stack.push(notificationChildren.get(i));
+                }
+            }
+        }
+
+        mPresenter.onUpdateRowStates();
+    }
+}
diff --git a/com/android/systemui/statusbar/RemoteInputController.java b/com/android/systemui/statusbar/RemoteInputController.java
index ff6c775..97e3d22 100644
--- a/com/android/systemui/statusbar/RemoteInputController.java
+++ b/com/android/systemui/statusbar/RemoteInputController.java
@@ -21,17 +21,24 @@
 import com.android.systemui.statusbar.phone.StatusBarWindowManager;
 import com.android.systemui.statusbar.policy.RemoteInputView;
 
+import android.app.Notification;
+import android.app.RemoteInput;
+import android.content.Context;
+import android.os.SystemProperties;
 import android.util.ArrayMap;
 import android.util.ArraySet;
 import android.util.Pair;
 
 import java.lang.ref.WeakReference;
 import java.util.ArrayList;
+import java.util.List;
 
 /**
  * Keeps track of the currently active {@link RemoteInputView}s.
  */
 public class RemoteInputController {
+    private static final boolean ENABLE_REMOTE_INPUT =
+            SystemProperties.getBoolean("debug.enable_remote_input", true);
 
     private final ArrayList<Pair<WeakReference<NotificationData.Entry>, Object>> mOpen
             = new ArrayList<>();
@@ -45,6 +52,53 @@
     }
 
     /**
+     * Adds RemoteInput actions from the WearableExtender; to be removed once more apps support this
+     * via first-class API.
+     *
+     * TODO: Remove once enough apps specify remote inputs on their own.
+     */
+    public static void processForRemoteInput(Notification n, Context context) {
+        if (!ENABLE_REMOTE_INPUT) {
+            return;
+        }
+
+        if (n.extras != null && n.extras.containsKey("android.wearable.EXTENSIONS") &&
+                (n.actions == null || n.actions.length == 0)) {
+            Notification.Action viableAction = null;
+            Notification.WearableExtender we = new Notification.WearableExtender(n);
+
+            List<Notification.Action> actions = we.getActions();
+            final int numActions = actions.size();
+
+            for (int i = 0; i < numActions; i++) {
+                Notification.Action action = actions.get(i);
+                if (action == null) {
+                    continue;
+                }
+                RemoteInput[] remoteInputs = action.getRemoteInputs();
+                if (remoteInputs == null) {
+                    continue;
+                }
+                for (RemoteInput ri : remoteInputs) {
+                    if (ri.getAllowFreeFormInput()) {
+                        viableAction = action;
+                        break;
+                    }
+                }
+                if (viableAction != null) {
+                    break;
+                }
+            }
+
+            if (viableAction != null) {
+                Notification.Builder rebuilder = Notification.Builder.recoverBuilder(context, n);
+                rebuilder.setActions(viableAction);
+                rebuilder.build(); // will rewrite n
+            }
+        }
+    }
+
+    /**
      * Adds a currently active remote input.
      *
      * @param entry the entry for which a remote input is now active.
diff --git a/com/android/systemui/statusbar/car/CarStatusBar.java b/com/android/systemui/statusbar/car/CarStatusBar.java
index 5941af2..3ebeb4d 100644
--- a/com/android/systemui/statusbar/car/CarStatusBar.java
+++ b/com/android/systemui/statusbar/car/CarStatusBar.java
@@ -247,19 +247,6 @@
         return null;
     }
 
-    /**
-     * Returns the
-     * {@link com.android.systemui.statusbar.ExpandableNotificationRow.LongPressListener} that will
-     * be triggered when a notification card is long-pressed.
-     */
-    @Override
-    protected ExpandableNotificationRow.LongPressListener getNotificationLongClicker() {
-        // For the automative use case, we do not want to the user to be able to interact with
-        // a notification other than a regular click. As a result, just return null for the
-        // long click listener.
-        return null;
-    }
-
     @Override
     public void showBatteryView() {
         if (Log.isLoggable(TAG, Log.DEBUG)) {
@@ -335,8 +322,8 @@
     }
 
     @Override
-    public void userSwitched(int newUserId) {
-        super.userSwitched(newUserId);
+    public void onUserSwitched(int newUserId) {
+        super.onUserSwitched(newUserId);
         if (mFullscreenUserSwitcher != null) {
             mFullscreenUserSwitcher.onUserSwitched(newUserId);
         }
@@ -388,18 +375,6 @@
     }
 
     @Override
-    protected boolean shouldPeek(NotificationData.Entry entry, StatusBarNotification sbn) {
-        // Because space is usually constrained in the auto use-case, there should not be a
-        // pinned notification when the shade has been expanded. Ensure this by not pinning any
-        // notification if the shade is already opened.
-        if (mPanelExpanded) {
-            return false;
-        }
-
-        return super.shouldPeek(entry, sbn);
-    }
-
-    @Override
     public void animateExpandNotificationsPanel() {
         // Because space is usually constrained in the auto use-case, there should not be a
         // pinned notification when the shade has been expanded. Ensure this by removing all heads-
diff --git a/com/android/systemui/statusbar/notification/NotificationUtils.java b/com/android/systemui/statusbar/notification/NotificationUtils.java
index af393c9..7e2336c 100644
--- a/com/android/systemui/statusbar/notification/NotificationUtils.java
+++ b/com/android/systemui/statusbar/notification/NotificationUtils.java
@@ -61,11 +61,6 @@
         return sLocationOffset[1] - sLocationBase[1];
     }
 
-    public static boolean isHapticFeedbackDisabled(Context context) {
-        return Settings.System.getIntForUser(context.getContentResolver(),
-                Settings.System.HAPTIC_FEEDBACK_ENABLED, 0, UserHandle.USER_CURRENT) == 0;
-    }
-
     /**
      * @param dimenId the dimen to look up
      * @return the font scaled dimen as if it were in sp but doesn't shrink sizes below dp
diff --git a/com/android/systemui/statusbar/phone/CollapsedStatusBarFragment.java b/com/android/systemui/statusbar/phone/CollapsedStatusBarFragment.java
index 61f3130..61cb61c 100644
--- a/com/android/systemui/statusbar/phone/CollapsedStatusBarFragment.java
+++ b/com/android/systemui/statusbar/phone/CollapsedStatusBarFragment.java
@@ -55,6 +55,7 @@
     private KeyguardMonitor mKeyguardMonitor;
     private NetworkController mNetworkController;
     private LinearLayout mSystemIconArea;
+    private View mClockView;
     private View mNotificationIconAreaInner;
     private int mDisabled1;
     private StatusBar mStatusBarComponent;
@@ -93,6 +94,7 @@
         mDarkIconManager = new DarkIconManager(view.findViewById(R.id.statusIcons));
         Dependency.get(StatusBarIconController.class).addIconGroup(mDarkIconManager);
         mSystemIconArea = mStatusBar.findViewById(R.id.system_icon_area);
+        mClockView = mStatusBar.findViewById(R.id.clock);
         mSignalClusterView = mStatusBar.findViewById(R.id.signal_cluster);
         Dependency.get(DarkIconDispatcher.class).addDarkReceiver(mSignalClusterView);
         // Default to showing until we know otherwise.
@@ -197,10 +199,12 @@
 
     public void hideSystemIconArea(boolean animate) {
         animateHide(mSystemIconArea, animate);
+        animateHide(mClockView, animate);
     }
 
     public void showSystemIconArea(boolean animate) {
         animateShow(mSystemIconArea, animate);
+        animateShow(mClockView, animate);
     }
 
     public void hideNotificationIconArea(boolean animate) {
diff --git a/com/android/systemui/statusbar/phone/DozeParameters.java b/com/android/systemui/statusbar/phone/DozeParameters.java
index 3f57c2f..6d85fb3 100644
--- a/com/android/systemui/statusbar/phone/DozeParameters.java
+++ b/com/android/systemui/statusbar/phone/DozeParameters.java
@@ -26,6 +26,7 @@
 
 import com.android.internal.hardware.AmbientDisplayConfiguration;
 import com.android.systemui.R;
+import com.android.systemui.doze.AlwaysOnDisplayPolicy;
 
 import java.io.PrintWriter;
 
@@ -37,10 +38,12 @@
     private final AmbientDisplayConfiguration mAmbientDisplayConfiguration;
 
     private static IntInOutMatcher sPickupSubtypePerformsProxMatcher;
+    private final AlwaysOnDisplayPolicy mAlwaysOnPolicy;
 
     public DozeParameters(Context context) {
         mContext = context;
         mAmbientDisplayConfiguration = new AmbientDisplayConfiguration(mContext);
+        mAlwaysOnPolicy = new AlwaysOnDisplayPolicy(context);
     }
 
     public void dump(PrintWriter pw) {
@@ -83,6 +86,11 @@
         return getPulseInDuration() + getPulseVisibleDuration() + getPulseOutDuration();
     }
 
+    public float getScreenBrightnessDoze() {
+        return mContext.getResources().getInteger(
+                com.android.internal.R.integer.config_screenBrightnessDoze) / 255f;
+    }
+
     public int getPulseInDuration() {
         return getInt("doze.pulse.duration.in", R.integer.doze_pulse_duration_in);
     }
@@ -115,10 +123,52 @@
         return getInt("doze.pickup.vibration.threshold", R.integer.doze_pickup_vibration_threshold);
     }
 
+    /**
+     * For how long a wallpaper can be visible in AoD before it fades aways.
+     * @return duration in millis.
+     */
+    public long getWallpaperAodDuration() {
+        return mAlwaysOnPolicy.wallpaperVisibilityDuration;
+    }
+
+    /**
+     * How long it takes for the wallpaper fade away (Animation duration.)
+     * @return duration in millis.
+     */
+    public long getWallpaperFadeOutDuration() {
+        return mAlwaysOnPolicy.wallpaperFadeOutDuration;
+    }
+
+    /**
+     * Checks if always on is available and enabled for the current user.
+     * @return {@code true} if enabled and available.
+     */
     public boolean getAlwaysOn() {
         return mAmbientDisplayConfiguration.alwaysOnEnabled(UserHandle.USER_CURRENT);
     }
 
+    /**
+     * Some screens need to be completely black before changing the display power mode,
+     * unexpected behavior might happen if this parameter isn't respected.
+     *
+     * @return {@code true} if screen needs to be completely black before a power transition.
+     */
+    public boolean getDisplayNeedsBlanking() {
+        return mContext.getResources().getBoolean(
+                com.android.internal.R.bool.config_displayBlanksAfterDoze);
+    }
+
+    /**
+     * Whether we can implement our own screen off animation or if we need
+     * to rely on DisplayPowerManager to dim the display.
+     *
+     * @return {@code true} if SystemUI can control the screen off animation.
+     */
+    public boolean getCanControlScreenOffAnimation() {
+        return !mContext.getResources().getBoolean(
+                com.android.internal.R.bool.config_dozeAfterScreenOff);
+    }
+
     private boolean getBoolean(String propName, int resId) {
         return SystemProperties.getBoolean(propName, mContext.getResources().getBoolean(resId));
     }
diff --git a/com/android/systemui/statusbar/phone/FingerprintUnlockController.java b/com/android/systemui/statusbar/phone/FingerprintUnlockController.java
index 80d4061..a2b1013 100644
--- a/com/android/systemui/statusbar/phone/FingerprintUnlockController.java
+++ b/com/android/systemui/statusbar/phone/FingerprintUnlockController.java
@@ -26,7 +26,7 @@
 import com.android.keyguard.KeyguardConstants;
 import com.android.keyguard.KeyguardUpdateMonitor;
 import com.android.keyguard.KeyguardUpdateMonitorCallback;
-import com.android.keyguard.LatencyTracker;
+import com.android.internal.util.LatencyTracker;
 import com.android.systemui.Dependency;
 import com.android.systemui.keyguard.KeyguardViewMediator;
 import com.android.systemui.keyguard.ScreenLifecycle;
@@ -266,7 +266,6 @@
     }
 
     private void showBouncer() {
-        mScrimController.transitionTo(ScrimState.BOUNCER);
         mStatusBarKeyguardViewManager.animateCollapsePanels(
                 FINGERPRINT_COLLAPSE_SPEEDUP_FACTOR);
         mPendingShowBouncer = false;
diff --git a/com/android/systemui/statusbar/phone/KeyguardBouncer.java b/com/android/systemui/statusbar/phone/KeyguardBouncer.java
index 99debee..b71ebfd 100644
--- a/com/android/systemui/statusbar/phone/KeyguardBouncer.java
+++ b/com/android/systemui/statusbar/phone/KeyguardBouncer.java
@@ -16,7 +16,6 @@
 
 package com.android.systemui.statusbar.phone;
 
-import android.app.ActivityManager;
 import android.content.Context;
 import android.os.Handler;
 import android.os.UserHandle;
@@ -102,7 +101,7 @@
             return;
         }
 
-        final int activeUserId = ActivityManager.getCurrentUser();
+        final int activeUserId = KeyguardUpdateMonitor.getCurrentUser();
         final boolean isSystemUser =
                 UserManager.isSplitSystemUser() && activeUserId == UserHandle.USER_SYSTEM;
         final boolean allowDismissKeyguard = !isSystemUser && activeUserId == keyguardUserId;
@@ -122,6 +121,8 @@
 
         // Split up the work over multiple frames.
         DejankUtils.postAfterTraversal(mShowRunnable);
+
+        mCallback.onBouncerVisiblityChanged(true /* shown */);
     }
 
     private final Runnable mShowRunnable = new Runnable() {
@@ -182,6 +183,7 @@
             mDismissCallbackRegistry.notifyDismissCancelled();
         }
         mFalsingManager.onBouncerHidden();
+        mCallback.onBouncerVisiblityChanged(false /* shown */);
         cancelShowRunnable();
         if (mKeyguardView != null) {
             mKeyguardView.cancelDismissAction();
diff --git a/com/android/systemui/statusbar/phone/ManagedProfileControllerImpl.java b/com/android/systemui/statusbar/phone/ManagedProfileControllerImpl.java
index 316bd5b..7f4deb0 100644
--- a/com/android/systemui/statusbar/phone/ManagedProfileControllerImpl.java
+++ b/com/android/systemui/statusbar/phone/ManagedProfileControllerImpl.java
@@ -61,14 +61,10 @@
     public void setWorkModeEnabled(boolean enableWorkMode) {
         synchronized (mProfiles) {
             for (UserInfo ui : mProfiles) {
-                if (enableWorkMode) {
-                    if (!mUserManager.trySetQuietModeDisabled(ui.id, null)) {
-                        StatusBarManager statusBarManager = (StatusBarManager) mContext
-                                .getSystemService(android.app.Service.STATUS_BAR_SERVICE);
-                        statusBarManager.collapsePanels();
-                    }
-                } else {
-                    mUserManager.setQuietModeEnabled(ui.id, true);
+                if (!mUserManager.trySetQuietModeEnabled(!enableWorkMode, UserHandle.of(ui.id))) {
+                    StatusBarManager statusBarManager = (StatusBarManager) mContext
+                            .getSystemService(android.app.Service.STATUS_BAR_SERVICE);
+                    statusBarManager.collapsePanels();
                 }
             }
         }
diff --git a/com/android/systemui/statusbar/phone/NavigationBarFragment.java b/com/android/systemui/statusbar/phone/NavigationBarFragment.java
index 6d3bc1d..695168e 100644
--- a/com/android/systemui/statusbar/phone/NavigationBarFragment.java
+++ b/com/android/systemui/statusbar/phone/NavigationBarFragment.java
@@ -67,8 +67,9 @@
 
 import com.android.internal.logging.MetricsLogger;
 import com.android.internal.logging.nano.MetricsProto.MetricsEvent;
-import com.android.keyguard.LatencyTracker;
+import com.android.internal.util.LatencyTracker;
 import com.android.systemui.Dependency;
+import com.android.systemui.OverviewProxyService;
 import com.android.systemui.R;
 import com.android.systemui.SysUiServiceProvider;
 import com.android.systemui.assist.AssistManager;
@@ -125,6 +126,8 @@
     private int mSystemUiVisibility;
     private LightBarController mLightBarController;
 
+    private OverviewProxyService mOverviewProxyService;
+
     public boolean mHomeBlockedThisTouch;
 
     // ----- Fragment Lifecycle Callbacks -----
@@ -152,6 +155,7 @@
             mDisabledFlags1 = savedInstanceState.getInt(EXTRA_DISABLE_STATE, 0);
         }
         mAssistManager = Dependency.get(AssistManager.class);
+        mOverviewProxyService = Dependency.get(OverviewProxyService.class);
 
         try {
             WindowManagerGlobal.getWindowManagerService()
@@ -364,7 +368,8 @@
 
     private boolean shouldDisableNavbarGestures() {
         return !mStatusBar.isDeviceProvisioned()
-                || (mDisabledFlags1 & StatusBarManager.DISABLE_SEARCH) != 0;
+                || (mDisabledFlags1 & StatusBarManager.DISABLE_SEARCH) != 0
+                || mOverviewProxyService.getProxy() != null;
     }
 
     private void repositionNavigationBar() {
@@ -449,6 +454,7 @@
         MetricsLogger.action(getContext(), MetricsEvent.ACTION_ASSIST_LONG_PRESS);
         mAssistManager.startAssist(new Bundle() /* args */);
         mStatusBar.awakenDreams();
+
         if (mNavigationBarView != null) {
             mNavigationBarView.abortCurrentGesture();
         }
diff --git a/com/android/systemui/statusbar/phone/NavigationBarView.java b/com/android/systemui/statusbar/phone/NavigationBarView.java
index 4e7f205..2796f0f 100644
--- a/com/android/systemui/statusbar/phone/NavigationBarView.java
+++ b/com/android/systemui/statusbar/phone/NavigationBarView.java
@@ -98,6 +98,7 @@
     private GestureHelper mGestureHelper;
     private DeadZone mDeadZone;
     private final NavigationBarTransitions mBarTransitions;
+    private final OverviewProxyService mOverviewProxyService;
 
     // workaround for LayoutTransitions leaving the nav buttons in a weird state (bug 5549288)
     final static boolean WORKAROUND_INVALID_LAYOUT = true;
@@ -226,6 +227,7 @@
         mButtonDispatchers.put(R.id.ime_switcher, new ButtonDispatcher(R.id.ime_switcher));
         mButtonDispatchers.put(R.id.accessibility_button,
                 new ButtonDispatcher(R.id.accessibility_button));
+        mOverviewProxyService = Dependency.get(OverviewProxyService.class);
     }
 
     public BarTransitions getBarTransitions() {
@@ -464,6 +466,10 @@
             disableBack = false;
             disableRecent = false;
         }
+        if (mOverviewProxyService.getProxy() != null) {
+            // When overview is connected to the launcher service, disable the recents button
+            disableRecent = true;
+        }
 
         ViewGroup navButtons = (ViewGroup) getCurrentView().findViewById(R.id.nav_buttons);
         if (navButtons != null) {
@@ -779,7 +785,7 @@
         onPluginDisconnected(null); // Create default gesture helper
         Dependency.get(PluginManager.class).addPluginListener(this,
                 NavGesture.class, false /* Only one */);
-        Dependency.get(OverviewProxyService.class).addCallback(mOverviewProxyListener);
+        mOverviewProxyService.addCallback(mOverviewProxyListener);
     }
 
     @Override
@@ -789,7 +795,7 @@
         if (mGestureHelper != null) {
             mGestureHelper.destroy();
         }
-        Dependency.get(OverviewProxyService.class).removeCallback(mOverviewProxyListener);
+        mOverviewProxyService.removeCallback(mOverviewProxyListener);
     }
 
     @Override
diff --git a/com/android/systemui/statusbar/phone/NotificationIconContainer.java b/com/android/systemui/statusbar/phone/NotificationIconContainer.java
index 836efff..91cae0a 100644
--- a/com/android/systemui/statusbar/phone/NotificationIconContainer.java
+++ b/com/android/systemui/statusbar/phone/NotificationIconContainer.java
@@ -16,8 +16,6 @@
 
 package com.android.systemui.statusbar.phone;
 
-import static com.android.systemui.statusbar.notification.NotificationUtils.isHapticFeedbackDisabled;
-
 import android.content.Context;
 import android.content.res.Configuration;
 import android.graphics.Canvas;
@@ -102,8 +100,10 @@
     }.setDuration(200).setDelay(50);
 
     public static final int MAX_VISIBLE_ICONS_WHEN_DARK = 5;
+    public static final int MAX_STATIC_ICONS = 4;
+    private static final int MAX_DOTS = 3;
 
-    private boolean mShowAllIcons = true;
+    private boolean mIsStaticLayout = true;
     private final HashMap<View, IconState> mIconStates = new HashMap<>();
     private int mDotPadding;
     private int mStaticDotRadius;
@@ -117,19 +117,18 @@
     private int mSpeedBumpIndex = -1;
     private int mIconSize;
     private float mOpenedAmount = 0.0f;
-    private float mVisualOverflowAdaption;
     private boolean mDisallowNextAnimation;
     private boolean mAnimationsEnabled = true;
-    private boolean mVibrateOnAnimation;
-    private Vibrator mVibrator;
     private ArrayMap<String, ArrayList<StatusBarIcon>> mReplacingIcons;
     private int mDarkOffsetX;
+    // Keep track of the last visible icon so collapsed container can report on its location
+    private IconState mLastVisibleIconState;
+
 
     public NotificationIconContainer(Context context, AttributeSet attrs) {
         super(context, attrs);
         initDimens();
         setWillNotDraw(!DEBUG);
-        mVibrator = mContext.getSystemService(Vibrator.class);
     }
 
     private void initDimens() {
@@ -168,7 +167,7 @@
                 mIconSize = child.getWidth();
             }
         }
-        if (mShowAllIcons) {
+        if (mIsStaticLayout) {
             resetViewStates();
             calculateIconTranslations();
             applyIconStates();
@@ -292,7 +291,8 @@
         float translationX = getActualPaddingStart();
         int firstOverflowIndex = -1;
         int childCount = getChildCount();
-        int maxVisibleIcons = mDark ? MAX_VISIBLE_ICONS_WHEN_DARK : childCount;
+        int maxVisibleIcons = mDark ? MAX_VISIBLE_ICONS_WHEN_DARK :
+                    mIsStaticLayout ? MAX_STATIC_ICONS : childCount;
         float layoutEnd = getLayoutEnd();
         float overflowStart = layoutEnd - mIconSize * (2 + OVERFLOW_EARLY_AMOUNT);
         boolean hasAmbient = mSpeedBumpIndex != -1 && mSpeedBumpIndex < getChildCount();
@@ -325,23 +325,6 @@
                     visualOverflowStart += (translationX - overflowStart) / mIconSize
                             * (mStaticDotRadius * 2 + mDotPadding);
                 }
-                if (mShowAllIcons) {
-                    // We want to perfectly position the overflow in the static state, such that
-                    // it's perfectly centered instead of measuring it from the end.
-                    mVisualOverflowAdaption = 0;
-                    if (firstOverflowIndex != -1) {
-                        View firstOverflowView = getChildAt(i);
-                        IconState overflowState = mIconStates.get(firstOverflowView);
-                        float totalAmount = layoutEnd - overflowState.xTranslation;
-                        float newPosition = overflowState.xTranslation + totalAmount / 2
-                                - totalDotLength / 2
-                                - mIconSize * 0.5f + mStaticDotRadius;
-                        mVisualOverflowAdaption = newPosition - visualOverflowStart;
-                        visualOverflowStart = newPosition;
-                    }
-                } else {
-                    visualOverflowStart += mVisualOverflowAdaption * (1f - mOpenedAmount);
-                }
             }
             translationX += iconState.iconAppearAmount * view.getWidth() * drawingScale;
         }
@@ -353,20 +336,24 @@
                 IconState iconState = mIconStates.get(view);
                 int dotWidth = mStaticDotRadius * 2 + mDotPadding;
                 iconState.xTranslation = translationX;
-                if (numDots <= 3) {
+                if (numDots <= MAX_DOTS) {
                     if (numDots == 1 && iconState.iconAppearAmount < 0.8f) {
                         iconState.visibleState = StatusBarIconView.STATE_ICON;
                         numDots--;
                     } else {
                         iconState.visibleState = StatusBarIconView.STATE_DOT;
                     }
-                    translationX += (numDots == 3 ? 3 * dotWidth : dotWidth)
+                    translationX += (numDots == MAX_DOTS ? MAX_DOTS * dotWidth : dotWidth)
                             * iconState.iconAppearAmount;
+                    mLastVisibleIconState = iconState;
                 } else {
                     iconState.visibleState = StatusBarIconView.STATE_HIDDEN;
                 }
                 numDots++;
             }
+        } else if (childCount > 0) {
+            View lastChild = getChildAt(childCount - 1);
+            mLastVisibleIconState = mIconStates.get(lastChild);
         }
         boolean center = mDark;
         if (center && translationX < getLayoutEnd()) {
@@ -420,13 +407,13 @@
     }
 
     /**
-     * Sets whether the layout should always show all icons.
+     * Sets whether the layout should always show the same number of icons.
      * If this is true, the icon positions will be updated on layout.
      * If this if false, the layout is managed from the outside and layouting won't trigger a
      * repositioning of the icons.
      */
-    public void setShowAllIcons(boolean showAllIcons) {
-        mShowAllIcons = showAllIcons;
+    public void setIsStaticLayout(boolean isStaticLayout) {
+        mIsStaticLayout = isStaticLayout;
     }
 
     public void setActualLayoutWidth(int actualLayoutWidth) {
@@ -457,6 +444,14 @@
         return mActualLayoutWidth;
     }
 
+    public int getFinalTranslationX() {
+        if (mLastVisibleIconState == null) {
+            return 0;
+        }
+
+        return (int) (mLastVisibleIconState.xTranslation + mIconSize * (1 + OVERFLOW_EARLY_AMOUNT));
+    }
+
     public void setChangingViewPositions(boolean changingViewPositions) {
         mChangingViewPositions = changingViewPositions;
     }
@@ -484,21 +479,41 @@
         mOpenedAmount = expandAmount;
     }
 
-    public float getVisualOverflowAdaption() {
-        return mVisualOverflowAdaption;
-    }
-
-    public void setVisualOverflowAdaption(float visualOverflowAdaption) {
-        mVisualOverflowAdaption = visualOverflowAdaption;
-    }
-
     public boolean hasOverflow() {
+        if (mIsStaticLayout) {
+            return getChildCount() > MAX_STATIC_ICONS;
+        }
+
         float width = (getChildCount() + OVERFLOW_EARLY_AMOUNT) * mIconSize;
         return width - (getWidth() - getActualPaddingStart() - getActualPaddingEnd()) > 0;
     }
 
-    public void setVibrateOnAnimation(boolean vibrateOnAnimation) {
-        mVibrateOnAnimation = vibrateOnAnimation;
+    /**
+     * If the overflow is in the range [1, max_dots - 1) (basically 1 or 2 dots), then
+     * extra padding will have to be accounted for
+     *
+     * This method has no meaning for non-static containers
+     */
+    public boolean hasPartialOverflow() {
+        if (mIsStaticLayout) {
+            int count = getChildCount();
+            return count > MAX_STATIC_ICONS && count <= MAX_STATIC_ICONS + MAX_DOTS;
+        }
+
+        return false;
+    }
+
+    /**
+     * Get padding that can account for extra dots up to the max. The only valid values for
+     * this method are for 1 or 2 dots.
+     * @return only extraDotPadding or extraDotPadding * 2
+     */
+    public int getPartialOverflowExtraPadding() {
+        if (!hasPartialOverflow()) {
+            return 0;
+        }
+
+        return (MAX_STATIC_ICONS + MAX_DOTS - getChildCount()) * (mStaticDotRadius + mDotPadding);
     }
 
     public int getIconSize() {
@@ -608,39 +623,14 @@
                 } else {
                     super.applyToView(view);
                 }
-                boolean wasInShelf = icon.isInShelf();
                 boolean inShelf = iconAppearAmount == 1.0f;
                 icon.setIsInShelf(inShelf);
-                if (shouldVibrateChange(wasInShelf != inShelf)) {
-                    AsyncTask.execute(
-                            () -> mVibrator.vibrate(VibrationEffect.get(
-                                    VibrationEffect.EFFECT_TICK)));
-                }
             }
             justAdded = false;
             justReplaced = false;
             needsCannedAnimation = false;
         }
 
-        private boolean shouldVibrateChange(boolean inShelfChanged) {
-            if (!mVibrateOnAnimation) {
-                return false;
-            }
-            if (justAdded) {
-                return false;
-            }
-            if (!mAnimationsEnabled) {
-                return false;
-            }
-            if (!inShelfChanged) {
-                return false;
-            }
-            if (isHapticFeedbackDisabled(mContext)) {
-                return false;
-            }
-            return true;
-        }
-
         public boolean hasCustomTransformHeight() {
             return isLastExpandIcon && customTransformHeight != NO_VALUE;
         }
diff --git a/com/android/systemui/statusbar/phone/NotificationPanelView.java b/com/android/systemui/statusbar/phone/NotificationPanelView.java
index 17e3599..f0bd1f9 100644
--- a/com/android/systemui/statusbar/phone/NotificationPanelView.java
+++ b/com/android/systemui/statusbar/phone/NotificationPanelView.java
@@ -455,7 +455,7 @@
             mTopPaddingAdjustment = 0;
         } else {
             mClockPositionAlgorithm.setup(
-                    mStatusBar.getMaxKeyguardNotifications(),
+                    mStatusBar.getMaxNotificationsWhileLocked(),
                     getMaxPanelHeight(),
                     getExpandedHeight(),
                     mNotificationStackScroller.getNotGoneChildCount(),
@@ -506,7 +506,8 @@
             if (suppressedSummary) {
                 continue;
             }
-            if (!mStatusBar.shouldShowOnKeyguard(row.getStatusBarNotification())) {
+            if (!mStatusBar.getNotificationLockscreenUserManager().shouldShowOnKeyguard(
+                    row.getStatusBarNotification())) {
                 continue;
             }
             if (row.isRemoved()) {
diff --git a/com/android/systemui/statusbar/phone/PanelView.java b/com/android/systemui/statusbar/phone/PanelView.java
index afe5c91..2fc22ca 100644
--- a/com/android/systemui/statusbar/phone/PanelView.java
+++ b/com/android/systemui/statusbar/phone/PanelView.java
@@ -16,8 +16,6 @@
 
 package com.android.systemui.statusbar.phone;
 
-import static com.android.systemui.statusbar.notification.NotificationUtils.isHapticFeedbackDisabled;
-
 import android.animation.Animator;
 import android.animation.AnimatorListenerAdapter;
 import android.animation.ObjectAnimator;
@@ -25,7 +23,9 @@
 import android.content.Context;
 import android.content.res.Configuration;
 import android.content.res.Resources;
+import android.database.ContentObserver;
 import android.os.AsyncTask;
+import android.os.Handler;
 import android.os.SystemClock;
 import android.os.UserHandle;
 import android.os.VibrationEffect;
@@ -42,7 +42,7 @@
 import android.widget.FrameLayout;
 
 import com.android.internal.logging.nano.MetricsProto.MetricsEvent;
-import com.android.keyguard.LatencyTracker;
+import com.android.internal.util.LatencyTracker;
 import com.android.systemui.DejankUtils;
 import com.android.systemui.Interpolators;
 import com.android.systemui.R;
@@ -50,7 +50,6 @@
 import com.android.systemui.doze.DozeLog;
 import com.android.systemui.statusbar.FlingAnimationUtils;
 import com.android.systemui.statusbar.StatusBarState;
-import com.android.systemui.statusbar.notification.NotificationUtils;
 import com.android.systemui.statusbar.policy.HeadsUpManager;
 
 import java.io.FileDescriptor;
@@ -66,6 +65,7 @@
     private LockscreenGestureLogger mLockscreenGestureLogger = new LockscreenGestureLogger();
     private boolean mPanelUpdateWhenAnimatorEnds;
     private boolean mVibrateOnOpening;
+    private boolean mVibrationEnabled;
 
     private final void logf(String fmt, Object... args) {
         Log.v(TAG, (mViewName != null ? (mViewName + ": ") : "") + String.format(fmt, args));
@@ -108,6 +108,12 @@
     private FlingAnimationUtils mFlingAnimationUtilsDismissing;
     private FalsingManager mFalsingManager;
     private final Vibrator mVibrator;
+    final private ContentObserver mVibrationObserver = new ContentObserver(Handler.getMain()) {
+        @Override
+        public void onChange(boolean selfChange) {
+            updateHapticFeedBackEnabled();
+        }
+    };
 
     /**
      * Whether an instant expand request is currently pending and we are just waiting for layout.
@@ -212,6 +218,15 @@
         mVibrator = mContext.getSystemService(Vibrator.class);
         mVibrateOnOpening = mContext.getResources().getBoolean(
                 R.bool.config_vibrateOnIconAnimation);
+        mContext.getContentResolver().registerContentObserver(
+                Settings.System.getUriFor(Settings.System.HAPTIC_FEEDBACK_ENABLED), true,
+                mVibrationObserver);
+        mVibrationObserver.onChange(false /* selfChange */);
+    }
+
+    public void updateHapticFeedBackEnabled() {
+        mVibrationEnabled = Settings.System.getIntForUser(mContext.getContentResolver(),
+                Settings.System.HAPTIC_FEEDBACK_ENABLED, 0, UserHandle.USER_CURRENT) != 0;
     }
 
     protected void loadDimens() {
@@ -403,7 +418,7 @@
         runPeekAnimation(INITIAL_OPENING_PEEK_DURATION, getOpeningHeight(),
                 false /* collapseWhenFinished */);
         notifyBarPanelExpansionChanged();
-        if (mVibrateOnOpening && !isHapticFeedbackDisabled(mContext)) {
+        if (mVibrateOnOpening && mVibrationEnabled) {
             AsyncTask.execute(() ->
                     mVibrator.vibrate(VibrationEffect.get(VibrationEffect.EFFECT_TICK, false)));
         }
diff --git a/com/android/systemui/statusbar/phone/PhoneStatusBarPolicy.java b/com/android/systemui/statusbar/phone/PhoneStatusBarPolicy.java
index 09fe579..f41cb29 100644
--- a/com/android/systemui/statusbar/phone/PhoneStatusBarPolicy.java
+++ b/com/android/systemui/statusbar/phone/PhoneStatusBarPolicy.java
@@ -615,6 +615,7 @@
                     .addCategory(Intent.CATEGORY_BROWSABLE)
                     .addCategory("unique:" + System.currentTimeMillis())
                     .putExtra(Intent.EXTRA_PACKAGE_NAME, appInfo.packageName)
+                    .putExtra(Intent.EXTRA_VERSION_CODE, (int) (appInfo.versionCode & 0x7fffffff))
                     .putExtra(Intent.EXTRA_VERSION_CODE, appInfo.versionCode)
                     .putExtra(Intent.EXTRA_EPHEMERAL_FAILURE, pendingIntent);
 
diff --git a/com/android/systemui/statusbar/phone/ScrimController.java b/com/android/systemui/statusbar/phone/ScrimController.java
index 3a36776..14329b5 100644
--- a/com/android/systemui/statusbar/phone/ScrimController.java
+++ b/com/android/systemui/statusbar/phone/ScrimController.java
@@ -20,6 +20,7 @@
 import android.animation.AnimatorListenerAdapter;
 import android.animation.PropertyValuesHolder;
 import android.animation.ValueAnimator;
+import android.app.AlarmManager;
 import android.app.WallpaperManager;
 import android.content.Context;
 import android.graphics.Color;
@@ -51,6 +52,7 @@
 import com.android.systemui.statusbar.ScrimView;
 import com.android.systemui.statusbar.policy.OnHeadsUpChangedListener;
 import com.android.systemui.statusbar.stack.ViewState;
+import com.android.systemui.util.AlarmTimeout;
 import com.android.systemui.util.wakelock.DelayedWakeLock;
 import com.android.systemui.util.wakelock.WakeLock;
 
@@ -73,6 +75,19 @@
             = new PathInterpolator(0f, 0, 0.7f, 1f);
     public static final Interpolator KEYGUARD_FADE_OUT_INTERPOLATOR_LOCKED
             = new PathInterpolator(0.3f, 0f, 0.8f, 1f);
+
+    /**
+     * When both scrims have 0 alpha.
+     */
+    public static final int VISIBILITY_FULLY_TRANSPARENT = 0;
+    /**
+     * When scrims aren't transparent (alpha 0) but also not opaque (alpha 1.)
+     */
+    public static final int VISIBILITY_SEMI_TRANSPARENT = 1;
+    /**
+     * When at least 1 scrim is fully opaque (alpha set to 1.)
+     */
+    public static final int VISIBILITY_FULLY_OPAQUE = 2;
     /**
      * Default alpha value for most scrims.
      */
@@ -111,6 +126,7 @@
     private final UnlockMethodCache mUnlockMethodCache;
     private final KeyguardUpdateMonitor mKeyguardUpdateMonitor;
     private final DozeParameters mDozeParameters;
+    private final AlarmTimeout mTimeTicker;
 
     private final SysuiColorExtractor mColorExtractor;
     private GradientColors mLockColors;
@@ -138,23 +154,25 @@
     private float mCurrentBehindAlpha = NOT_INITIALIZED;
     private int mCurrentInFrontTint;
     private int mCurrentBehindTint;
+    private boolean mWallpaperVisibilityTimedOut;
     private int mPinnedHeadsUpCount;
     private float mTopHeadsUpDragAmount;
     private View mDraggedHeadsUpView;
     private boolean mKeyguardFadingOutInProgress;
     private ValueAnimator mKeyguardFadeoutAnimation;
-    private boolean mScrimsVisible;
-    private final Consumer<Boolean> mScrimVisibleListener;
+    private int mScrimsVisibility;
+    private final Consumer<Integer> mScrimVisibleListener;
     private boolean mBlankScreen;
     private boolean mScreenBlankingCallbackCalled;
     private Callback mCallback;
+    private boolean mWallpaperSupportsAmbientMode;
 
     private final WakeLock mWakeLock;
     private boolean mWakeLockHeld;
 
     public ScrimController(LightBarController lightBarController, ScrimView scrimBehind,
-            ScrimView scrimInFront, View headsUpScrim, Consumer<Boolean> scrimVisibleListener,
-            DozeParameters dozeParameters) {
+            ScrimView scrimInFront, View headsUpScrim, Consumer<Integer> scrimVisibleListener,
+            DozeParameters dozeParameters, AlarmManager alarmManager) {
         mScrimBehind = scrimBehind;
         mScrimInFront = scrimInFront;
         mHeadsUpScrim = headsUpScrim;
@@ -164,6 +182,8 @@
         mKeyguardUpdateMonitor = KeyguardUpdateMonitor.getInstance(mContext);
         mLightBarController = lightBarController;
         mScrimBehindAlphaResValue = mContext.getResources().getFloat(R.dimen.scrim_behind_alpha);
+        mTimeTicker = new AlarmTimeout(alarmManager, this::onHideWallpaperTimeout,
+                "hide_aod_wallpaper", new Handler());
         mWakeLock = createWakeLock();
         // Scrim alpha is initially set to the value on the resource but might be changed
         // to make sure that text on top of it is legible.
@@ -195,6 +215,10 @@
 
     public void transitionTo(ScrimState state, Callback callback) {
         if (state == mState) {
+            // Call the callback anyway, unless it's already enqueued
+            if (callback != null && mCallback != callback) {
+                callback.onFinished();
+            }
             return;
         } else if (DEBUG) {
             Log.d(TAG, "State changed to: " + state);
@@ -204,12 +228,15 @@
             throw new IllegalArgumentException("Cannot change to UNINITIALIZED.");
         }
 
+        final ScrimState oldState = mState;
+        mState = state;
+
         if (mCallback != null) {
             mCallback.onCancelled();
         }
         mCallback = callback;
 
-        state.prepare(mState);
+        state.prepare(oldState);
         mScreenBlankingCallbackCalled = false;
         mAnimationDelay = 0;
         mBlankScreen = state.getBlanksScreen();
@@ -228,16 +255,24 @@
             mKeyguardFadeoutAnimation.cancel();
         }
 
-        mState = state;
+        // The device might sleep if it's entering AOD, we need to make sure that
+        // the animation plays properly until the last frame.
+        // It's important to avoid holding the wakelock unless necessary because
+        // WakeLock#aqcuire will trigger an IPC and will cause jank.
+        if (mState == ScrimState.AOD) {
+            holdWakeLock();
+        }
 
-        // Do not let the device sleep until we're done with all animations
-        if (!mWakeLockHeld) {
-            if (mWakeLock != null) {
-                mWakeLockHeld = true;
-                mWakeLock.acquire();
-            } else {
-                Log.w(TAG, "Cannot hold wake lock, it has not been set yet");
+        // AOD wallpapers should fade away after a while
+        if (mWallpaperSupportsAmbientMode && mDozeParameters.getAlwaysOn()
+                && (mState == ScrimState.AOD || mState == ScrimState.PULSING)) {
+            if (!mWallpaperVisibilityTimedOut) {
+                mTimeTicker.schedule(mDozeParameters.getWallpaperAodDuration(),
+                        AlarmTimeout.MODE_IGNORE_IF_SCHEDULED);
             }
+        } else {
+            mTimeTicker.cancel();
+            mWallpaperVisibilityTimedOut = false;
         }
 
         if (!mKeyguardUpdateMonitor.needsSlowUnlockTransition()) {
@@ -274,6 +309,30 @@
         mTracking = false;
     }
 
+    @VisibleForTesting
+    protected void onHideWallpaperTimeout() {
+        if (mState != ScrimState.AOD && mState != ScrimState.PULSING) {
+            return;
+        }
+
+        holdWakeLock();
+        mWallpaperVisibilityTimedOut = true;
+        mAnimateChange = true;
+        mAnimationDuration = mDozeParameters.getWallpaperFadeOutDuration();
+        scheduleUpdate();
+    }
+
+    private void holdWakeLock() {
+        if (!mWakeLockHeld) {
+            if (mWakeLock != null) {
+                mWakeLockHeld = true;
+                mWakeLock.acquire();
+            } else {
+                Log.w(TAG, "Cannot hold wake lock, it has not been set yet");
+            }
+        }
+    }
+
     /**
      * Current state of the shade expansion when pulling it from the top.
      * This value is 1 when on top of the keyguard and goes to 0 as the user drags up.
@@ -310,7 +369,6 @@
                     mCurrentInFrontAlpha = 0;
                 }
             } else {
-                Log.w(TAG, "Invalid state, cannot set panel expansion when: " + mState);
                 return;
             }
 
@@ -387,6 +445,14 @@
             mLightBarController.setScrimColor(mScrimInFront.getColors());
         }
 
+        // We want to override the back scrim opacity for AOD and PULSING
+        // when it's time to fade the wallpaper away.
+        boolean overrideBackScrimAlpha = (mState == ScrimState.PULSING || mState == ScrimState.AOD)
+                && mWallpaperVisibilityTimedOut;
+        if (overrideBackScrimAlpha) {
+            mCurrentBehindAlpha = 1;
+        }
+
         setScrimInFrontAlpha(mCurrentInFrontAlpha);
         setScrimBehindAlpha(mCurrentBehindAlpha);
 
@@ -394,12 +460,18 @@
     }
 
     private void dispatchScrimsVisible() {
-        boolean scrimsVisible = mScrimBehind.getViewAlpha() > 0 || mScrimInFront.getViewAlpha() > 0;
+        final int currentScrimVisibility;
+        if (mScrimInFront.getViewAlpha() == 1 || mScrimBehind.getViewAlpha() == 1) {
+            currentScrimVisibility = VISIBILITY_FULLY_OPAQUE;
+        } else if (mScrimInFront.getViewAlpha() == 0 && mScrimBehind.getViewAlpha() == 0) {
+            currentScrimVisibility = VISIBILITY_FULLY_TRANSPARENT;
+        } else {
+            currentScrimVisibility = VISIBILITY_SEMI_TRANSPARENT;
+        }
 
-        if (mScrimsVisible != scrimsVisible) {
-            mScrimsVisible = scrimsVisible;
-
-            mScrimVisibleListener.accept(scrimsVisible);
+        if (mScrimsVisibility != currentScrimVisibility) {
+            mScrimsVisibility = currentScrimVisibility;
+            mScrimVisibleListener.accept(currentScrimVisibility);
         }
     }
 
@@ -807,6 +879,14 @@
         pw.print("   mTracking="); pw.println(mTracking);
     }
 
+    public void setWallpaperSupportsAmbientMode(boolean wallpaperSupportsAmbientMode) {
+        mWallpaperSupportsAmbientMode = wallpaperSupportsAmbientMode;
+        ScrimState[] states = ScrimState.values();
+        for (int i = 0; i < states.length; i++) {
+            states[i].setWallpaperSupportsAmbientMode(wallpaperSupportsAmbientMode);
+        }
+    }
+
     public interface Callback {
         default void onStart() {
         }
diff --git a/com/android/systemui/statusbar/phone/ScrimState.java b/com/android/systemui/statusbar/phone/ScrimState.java
index 0db98f3..fa2c1b3 100644
--- a/com/android/systemui/statusbar/phone/ScrimState.java
+++ b/com/android/systemui/statusbar/phone/ScrimState.java
@@ -19,7 +19,9 @@
 import android.graphics.Color;
 import android.os.Trace;
 
+import com.android.keyguard.KeyguardUpdateMonitor;
 import com.android.systemui.statusbar.ScrimView;
+import com.android.systemui.statusbar.stack.StackStateAnimator;
 
 /**
  * Possible states of the ScrimController state machine.
@@ -38,12 +40,18 @@
 
         @Override
         public void prepare(ScrimState previousState) {
-            // DisplayPowerManager will blank the screen, we'll just
-            // set our scrim to black in this frame to avoid flickering and
-            // fade it out afterwards.
-            mBlankScreen = previousState == ScrimState.AOD;
+            mBlankScreen = false;
             if (previousState == ScrimState.AOD) {
-                updateScrimColor(mScrimInFront, 1, Color.BLACK);
+                mAnimationDuration = StackStateAnimator.ANIMATION_DURATION_WAKEUP;
+                if (mDisplayRequiresBlanking) {
+                    // DisplayPowerManager will blank the screen, we'll just
+                    // set our scrim to black in this frame to avoid flickering and
+                    // fade it out afterwards.
+                    mBlankScreen = true;
+                    updateScrimColor(mScrimInFront, 1, Color.BLACK);
+                }
+            } else {
+                mAnimationDuration = ScrimController.ANIMATION_DURATION;
             }
             mCurrentBehindAlpha = mScrimBehindAlphaKeyguard;
             mCurrentInFrontAlpha = 0;
@@ -78,18 +86,20 @@
     AOD {
         @Override
         public void prepare(ScrimState previousState) {
-            if (previousState == ScrimState.PULSING) {
+            if (previousState == ScrimState.PULSING && !mCanControlScreenOff) {
                 updateScrimColor(mScrimInFront, 1, Color.BLACK);
             }
             final boolean alwaysOnEnabled = mDozeParameters.getAlwaysOn();
-            mBlankScreen = previousState == ScrimState.PULSING;
-            mCurrentBehindAlpha = 1;
+            final boolean wasPulsing = previousState == ScrimState.PULSING;
+            mBlankScreen = wasPulsing && !mCanControlScreenOff;
+            mCurrentBehindAlpha = mWallpaperSupportsAmbientMode
+                    && !mKeyguardUpdateMonitor.hasLockscreenWallpaper() ? 0f : 1f;
             mCurrentInFrontAlpha = alwaysOnEnabled ? mAodFrontScrimAlpha : 1f;
             mCurrentInFrontTint = Color.BLACK;
             mCurrentBehindTint = Color.BLACK;
             // DisplayPowerManager will blank the screen for us, we just need
             // to set our state.
-            mAnimateChange = false;
+            mAnimateChange = mCanControlScreenOff;
         }
     },
 
@@ -99,12 +109,15 @@
     PULSING {
         @Override
         public void prepare(ScrimState previousState) {
-            mCurrentBehindAlpha = 1;
             mCurrentInFrontAlpha = 0;
             mCurrentInFrontTint = Color.BLACK;
+            mCurrentBehindAlpha = mWallpaperSupportsAmbientMode
+                    && !mKeyguardUpdateMonitor.hasLockscreenWallpaper() ? 0f : 1f;
             mCurrentBehindTint = Color.BLACK;
-            mBlankScreen = true;
-            updateScrimColor(mScrimInFront, 1, Color.BLACK);
+            mBlankScreen = mDisplayRequiresBlanking;
+            if (mDisplayRequiresBlanking) {
+                updateScrimColor(mScrimInFront, 1, Color.BLACK);
+            }
         }
     },
 
@@ -147,11 +160,18 @@
     ScrimView mScrimInFront;
     ScrimView mScrimBehind;
     DozeParameters mDozeParameters;
+    boolean mDisplayRequiresBlanking;
+    boolean mCanControlScreenOff;
+    boolean mWallpaperSupportsAmbientMode;
+    KeyguardUpdateMonitor mKeyguardUpdateMonitor;
 
     public void init(ScrimView scrimInFront, ScrimView scrimBehind, DozeParameters dozeParameters) {
         mScrimInFront = scrimInFront;
         mScrimBehind = scrimBehind;
         mDozeParameters = dozeParameters;
+        mDisplayRequiresBlanking = dozeParameters.getDisplayNeedsBlanking();
+        mCanControlScreenOff = dozeParameters.getCanControlScreenOffAnimation();
+        mKeyguardUpdateMonitor = KeyguardUpdateMonitor.getInstance(scrimInFront.getContext());
     }
 
     public void prepare(ScrimState previousState) {
@@ -205,4 +225,8 @@
     public void setScrimBehindAlphaKeyguard(float scrimBehindAlphaKeyguard) {
         mScrimBehindAlphaKeyguard = scrimBehindAlphaKeyguard;
     }
+
+    public void setWallpaperSupportsAmbientMode(boolean wallpaperSupportsAmbientMode) {
+        mWallpaperSupportsAmbientMode = wallpaperSupportsAmbientMode;
+    }
 }
\ No newline at end of file
diff --git a/com/android/systemui/statusbar/phone/StatusBar.java b/com/android/systemui/statusbar/phone/StatusBar.java
index dc8100f..2da1e4d 100644
--- a/com/android/systemui/statusbar/phone/StatusBar.java
+++ b/com/android/systemui/statusbar/phone/StatusBar.java
@@ -16,7 +16,6 @@
 
 package com.android.systemui.statusbar.phone;
 
-import static android.app.StatusBarManager.DISABLE2_NOTIFICATION_SHADE;
 import static android.app.StatusBarManager.WINDOW_STATE_HIDDEN;
 import static android.app.StatusBarManager.WINDOW_STATE_SHOWING;
 import static android.app.StatusBarManager.windowStateToString;
@@ -25,8 +24,10 @@
 import static com.android.systemui.keyguard.WakefulnessLifecycle.WAKEFULNESS_ASLEEP;
 import static com.android.systemui.keyguard.WakefulnessLifecycle.WAKEFULNESS_AWAKE;
 import static com.android.systemui.keyguard.WakefulnessLifecycle.WAKEFULNESS_WAKING;
+import static com.android.systemui.statusbar.NotificationLockscreenUserManager
+        .NOTIFICATION_UNLOCKED_BY_WORK_CHALLENGE_ACTION;
+import static com.android.systemui.statusbar.NotificationLockscreenUserManager.PERMISSION_SELF;
 import static com.android.systemui.statusbar.NotificationMediaManager.DEBUG_MEDIA;
-import static com.android.systemui.statusbar.notification.NotificationInflater.InflationCallback;
 import static com.android.systemui.statusbar.phone.BarTransitions.MODE_LIGHTS_OUT;
 import static com.android.systemui.statusbar.phone.BarTransitions.MODE_LIGHTS_OUT_TRANSPARENT;
 import static com.android.systemui.statusbar.phone.BarTransitions.MODE_OPAQUE;
@@ -41,14 +42,15 @@
 import android.annotation.Nullable;
 import android.app.ActivityManager;
 import android.app.ActivityOptions;
+import android.app.AlarmManager;
 import android.app.KeyguardManager;
 import android.app.Notification;
 import android.app.NotificationManager;
 import android.app.PendingIntent;
-import android.app.RemoteInput;
 import android.app.StatusBarManager;
 import android.app.TaskStackBuilder;
 import android.app.WallpaperColors;
+import android.app.WallpaperInfo;
 import android.app.WallpaperManager;
 import android.app.admin.DevicePolicyManager;
 import android.content.BroadcastReceiver;
@@ -60,14 +62,12 @@
 import android.content.IntentSender;
 import android.content.om.IOverlayManager;
 import android.content.om.OverlayInfo;
-import android.content.pm.ApplicationInfo;
 import android.content.pm.IPackageManager;
 import android.content.pm.PackageManager;
 import android.content.pm.PackageManager.NameNotFoundException;
 import android.content.pm.UserInfo;
 import android.content.res.Configuration;
 import android.content.res.Resources;
-import android.database.ContentObserver;
 import android.graphics.Bitmap;
 import android.graphics.Point;
 import android.graphics.PointF;
@@ -82,7 +82,6 @@
 import android.metrics.LogMaker;
 import android.net.Uri;
 import android.os.AsyncTask;
-import android.os.Build;
 import android.os.Bundle;
 import android.os.Handler;
 import android.os.IBinder;
@@ -97,18 +96,14 @@
 import android.os.UserManager;
 import android.os.Vibrator;
 import android.provider.Settings;
-import android.service.notification.NotificationListenerService.RankingMap;
-import android.service.notification.NotificationStats;
 import android.service.notification.StatusBarNotification;
 import android.service.vr.IVrManager;
 import android.service.vr.IVrStateCallbacks;
-import android.util.ArraySet;
 import android.util.DisplayMetrics;
 import android.util.EventLog;
 import android.util.Log;
 import android.util.Slog;
 import android.util.SparseArray;
-import android.util.SparseBooleanArray;
 import android.view.Display;
 import android.view.IWindowManager;
 import android.view.KeyEvent;
@@ -125,9 +120,7 @@
 import android.view.animation.AccelerateInterpolator;
 import android.widget.DateTimeView;
 import android.widget.ImageView;
-import android.widget.RemoteViews;
 import android.widget.TextView;
-import android.widget.Toast;
 
 import com.android.internal.annotations.VisibleForTesting;
 import com.android.internal.colorextraction.ColorExtractor;
@@ -135,9 +128,7 @@
 import com.android.internal.logging.nano.MetricsProto.MetricsEvent;
 import com.android.internal.messages.nano.SystemMessageProto.SystemMessage;
 import com.android.internal.statusbar.IStatusBarService;
-import com.android.internal.statusbar.NotificationVisibility;
 import com.android.internal.statusbar.StatusBarIcon;
-import com.android.internal.util.NotificationMessagingUtil;
 import com.android.internal.widget.LockPatternUtils;
 import com.android.internal.widget.MessagingGroup;
 import com.android.internal.widget.MessagingMessage;
@@ -147,13 +138,10 @@
 import com.android.keyguard.ViewMediatorCallback;
 import com.android.systemui.ActivityStarterDelegate;
 import com.android.systemui.AutoReinflateContainer;
-import com.android.systemui.DejankUtils;
 import com.android.systemui.DemoMode;
 import com.android.systemui.Dependency;
 import com.android.systemui.EventLogTags;
-import com.android.systemui.ForegroundServiceController;
 import com.android.systemui.Interpolators;
-import com.android.systemui.OverviewProxyService;
 import com.android.systemui.Prefs;
 import com.android.systemui.R;
 import com.android.systemui.RecentsComponent;
@@ -199,18 +187,22 @@
 import com.android.systemui.statusbar.KeyguardIndicationController;
 import com.android.systemui.statusbar.NotificationData;
 import com.android.systemui.statusbar.NotificationData.Entry;
+import com.android.systemui.statusbar.NotificationEntryManager;
 import com.android.systemui.statusbar.NotificationGutsManager;
 import com.android.systemui.statusbar.NotificationInfo;
+import com.android.systemui.statusbar.NotificationListener;
+import com.android.systemui.statusbar.NotificationLockscreenUserManager;
+import com.android.systemui.statusbar.NotificationLogger;
 import com.android.systemui.statusbar.NotificationMediaManager;
 import com.android.systemui.statusbar.NotificationPresenter;
+import com.android.systemui.statusbar.NotificationRemoteInputManager;
 import com.android.systemui.statusbar.NotificationShelf;
+import com.android.systemui.statusbar.NotificationViewHierarchyManager;
 import com.android.systemui.statusbar.RemoteInputController;
 import com.android.systemui.statusbar.ScrimView;
 import com.android.systemui.statusbar.SignalClusterView;
 import com.android.systemui.statusbar.StatusBarState;
 import com.android.systemui.statusbar.notification.AboveShelfObserver;
-import com.android.systemui.statusbar.notification.InflationException;
-import com.android.systemui.statusbar.notification.RowInflaterTask;
 import com.android.systemui.statusbar.notification.VisualStabilityManager;
 import com.android.systemui.statusbar.phone.UnlockMethodCache.OnUnlockMethodChangedListener;
 import com.android.systemui.statusbar.policy.BatteryController;
@@ -229,16 +221,11 @@
 import com.android.systemui.statusbar.policy.NetworkController;
 import com.android.systemui.statusbar.policy.OnHeadsUpChangedListener;
 import com.android.systemui.statusbar.policy.PreviewInflater;
-import com.android.systemui.statusbar.policy.RemoteInputView;
 import com.android.systemui.statusbar.policy.UserInfoController;
 import com.android.systemui.statusbar.policy.UserInfoControllerImpl;
 import com.android.systemui.statusbar.policy.UserSwitcherController;
 import com.android.systemui.statusbar.stack.NotificationStackScrollLayout;
-import com.android.systemui.statusbar.stack.NotificationStackScrollLayout
-        .OnChildLocationsChangedListener;
 import com.android.systemui.util.NotificationChannels;
-import com.android.systemui.util.leak.LeakDetector;
-import com.android.systemui.util.wakelock.WakeLock;
 import com.android.systemui.volume.VolumeComponent;
 
 import java.io.FileDescriptor;
@@ -246,28 +233,17 @@
 import java.io.StringWriter;
 import java.util.ArrayList;
 import java.util.Collection;
-import java.util.Collections;
-import java.util.HashMap;
 import java.util.List;
 import java.util.Map;
-import java.util.Stack;
 
 public class StatusBar extends SystemUI implements DemoMode,
         DragDownHelper.DragDownCallback, ActivityStarter, OnUnlockMethodChangedListener,
-        OnHeadsUpChangedListener, VisualStabilityManager.Callback, CommandQueue.Callbacks,
-        ActivatableNotificationView.OnActivatedListener,
-        ExpandableNotificationRow.ExpansionLogger, NotificationData.Environment,
-        ExpandableNotificationRow.OnExpandClickListener, InflationCallback,
+        OnHeadsUpChangedListener, CommandQueue.Callbacks,
         ColorExtractor.OnColorsChangedListener, ConfigurationListener, NotificationPresenter {
     public static final boolean MULTIUSER_DEBUG = false;
 
-    public static final boolean ENABLE_REMOTE_INPUT =
-            SystemProperties.getBoolean("debug.enable_remote_input", true);
     public static final boolean ENABLE_CHILD_NOTIFICATIONS
             = SystemProperties.getBoolean("debug.child_notifs", true);
-    public static final boolean FORCE_REMOTE_INPUT_HISTORY =
-            SystemProperties.getBoolean("debug.force_remoteinput_history", true);
-    private static final boolean ENABLE_LOCK_SCREEN_ALLOW_REMOTE_INPUT = false;
 
     protected static final int MSG_HIDE_RECENT_APPS = 1020;
     protected static final int MSG_PRELOAD_RECENT_APPS = 1022;
@@ -275,11 +251,6 @@
     protected static final int MSG_TOGGLE_KEYBOARD_SHORTCUTS_MENU = 1026;
     protected static final int MSG_DISMISS_KEYBOARD_SHORTCUTS_MENU = 1027;
 
-    protected static final boolean ENABLE_HEADS_UP = true;
-    protected static final String SETTING_HEADS_UP_TICKER = "ticker_gets_heads_up";
-
-    private static final String PERMISSION_SELF = "com.android.systemui.permission.SELF";
-
     // Should match the values in PhoneWindowManager
     public static final String SYSTEM_DIALOG_REASON_HOME_KEY = "homekey";
     public static final String SYSTEM_DIALOG_REASON_RECENT_APPS = "recentapps";
@@ -289,8 +260,6 @@
             "com.android.systemui.statusbar.banner_action_cancel";
     private static final String BANNER_ACTION_SETUP =
             "com.android.systemui.statusbar.banner_action_setup";
-    private static final String NOTIFICATION_UNLOCKED_BY_WORK_CHALLENGE_ACTION
-            = "com.android.systemui.statusbar.work_challenge_unlocked_notification_action";
     public static final String TAG = "StatusBar";
     public static final boolean DEBUG = false;
     public static final boolean SPEW = false;
@@ -317,15 +286,12 @@
     // Time after we abort the launch transition.
     private static final long LAUNCH_TRANSITION_TIMEOUT_MS = 5000;
 
-    private static final boolean CLOSE_PANEL_WHEN_EMPTIED = true;
+    protected static final boolean CLOSE_PANEL_WHEN_EMPTIED = true;
 
     private static final int STATUS_OR_NAV_TRANSIENT =
             View.STATUS_BAR_TRANSIENT | View.NAVIGATION_BAR_TRANSIENT;
     private static final long AUTOHIDE_TIMEOUT_MS = 2250;
 
-    /** The minimum delay in ms between reports of notification visibility. */
-    private static final int VISIBILITY_REPORT_MIN_DELAY_MS = 500;
-
     /**
      * The delay to reset the hint text when the hint animation is finished running.
      */
@@ -348,14 +314,6 @@
     private static final boolean ENABLE_LOCKSCREEN_WALLPAPER = true;
 
     /**
-     * How long to wait before auto-dismissing a notification that was kept for remote input, and
-     * has now sent a remote input. We auto-dismiss, because the app may not see a reason to cancel
-     * these given that they technically don't exist anymore. We wait a bit in case the app issues
-     * an update.
-     */
-    private static final int REMOTE_INPUT_KEPT_ENTRY_AUTO_CANCEL_DELAY = 200;
-
-    /**
      * Never let the alpha become zero for surfaces that draw with SRC - otherwise the RenderNode
      * won't draw anything and uninitialized memory will show through
      * if mScrimSrcModeEnabled. Note that 0.001 is rounded down to 0 in
@@ -380,8 +338,6 @@
      */
     protected int mState;
     protected boolean mBouncerShowing;
-    protected boolean mShowLockscreenNotifications;
-    protected boolean mAllowLockscreenRemoteInput;
 
     private PhoneStatusBarPolicy mIconPolicy;
 
@@ -413,13 +369,6 @@
     protected NotificationPanelView mNotificationPanel; // the sliding/resizing panel within the notification window
     private TextView mNotificationPanelDebugText;
 
-    /**
-     * {@code true} if notifications not part of a group should by default be rendered in their
-     * expanded state. If {@code false}, then only the first notification will be expanded if
-     * possible.
-     */
-    private boolean mAlwaysExpandNonGroupedNotification;
-
     // settings
     private QSPanel mQSPanel;
 
@@ -447,6 +396,9 @@
     private final ArrayList<Runnable> mPostCollapseRunnables = new ArrayList<>();
 
     private NotificationGutsManager mGutsManager;
+    protected NotificationLogger mNotificationLogger;
+    protected NotificationEntryManager mEntryManager;
+    protected NotificationViewHierarchyManager mViewHierarchyManager;
 
     // for disabling the status bar
     private int mDisabled1 = 0;
@@ -498,23 +450,6 @@
     };
 
     protected final H mHandler = createHandler();
-    final private ContentObserver mHeadsUpObserver = new ContentObserver(mHandler) {
-        @Override
-        public void onChange(boolean selfChange) {
-            boolean wasUsing = mUseHeadsUp;
-            mUseHeadsUp = ENABLE_HEADS_UP && !mDisableNotificationAlerts
-                    && Settings.Global.HEADS_UP_OFF != Settings.Global.getInt(
-                    mContext.getContentResolver(), Settings.Global.HEADS_UP_NOTIFICATIONS_ENABLED,
-                    Settings.Global.HEADS_UP_OFF);
-            Log.d(TAG, "heads up is " + (mUseHeadsUp ? "enabled" : "disabled"));
-            if (wasUsing != mUseHeadsUp) {
-                if (!mUseHeadsUp) {
-                    Log.d(TAG, "dismissing any existing heads up notification on disable event");
-                    mHeadsUpManager.releaseAllImmediately();
-                }
-            }
-        }
-    };
 
     private int mInteractingWindows;
     private boolean mAutohideSuspended;
@@ -544,11 +479,25 @@
             new PorterDuffXfermode(PorterDuff.Mode.SRC_OVER);
 
     private NotificationMediaManager mMediaManager;
+    protected NotificationLockscreenUserManager mLockscreenUserManager;
+    protected NotificationRemoteInputManager mRemoteInputManager;
 
-    /** Keys of notifications currently visible to the user. */
-    private final ArraySet<NotificationVisibility> mCurrentlyVisibleNotifications =
-            new ArraySet<>();
-    private long mLastVisibilityReportUptimeMs;
+    private BroadcastReceiver mWallpaperChangedReceiver = new BroadcastReceiver() {
+        @Override
+        public void onReceive(Context context, Intent intent) {
+            WallpaperManager wallpaperManager = context.getSystemService(WallpaperManager.class);
+            if (wallpaperManager == null) {
+                Log.w(TAG, "WallpaperManager not available");
+                return;
+            }
+            WallpaperInfo info = wallpaperManager.getWallpaperInfo();
+            final boolean supportsAmbientMode = info != null &&
+                    info.getSupportsAmbientMode();
+
+            mStatusBarWindowManager.setWallpaperSupportsAmbientMode(supportsAmbientMode);
+            mScrimController.setWallpaperSupportsAmbientMode(supportsAmbientMode);
+        }
+    };
 
     private Runnable mLaunchTransitionEndRunnable;
     protected boolean mLaunchTransitionFadingAway;
@@ -571,83 +520,6 @@
     private boolean mWereIconsJustHidden;
     private boolean mBouncerWasShowingWhenHidden;
 
-    private final OnChildLocationsChangedListener mNotificationLocationsChangedListener =
-            new OnChildLocationsChangedListener() {
-                @Override
-                public void onChildLocationsChanged(
-                        NotificationStackScrollLayout stackScrollLayout) {
-                    if (mHandler.hasCallbacks(mVisibilityReporter)) {
-                        // Visibilities will be reported when the existing
-                        // callback is executed.
-                        return;
-                    }
-                    // Calculate when we're allowed to run the visibility
-                    // reporter. Note that this timestamp might already have
-                    // passed. That's OK, the callback will just be executed
-                    // ASAP.
-                    long nextReportUptimeMs =
-                            mLastVisibilityReportUptimeMs + VISIBILITY_REPORT_MIN_DELAY_MS;
-                    mHandler.postAtTime(mVisibilityReporter, nextReportUptimeMs);
-                }
-            };
-
-    // Tracks notifications currently visible in mNotificationStackScroller and
-    // emits visibility events via NoMan on changes.
-    protected final Runnable mVisibilityReporter = new Runnable() {
-        private final ArraySet<NotificationVisibility> mTmpNewlyVisibleNotifications =
-                new ArraySet<>();
-        private final ArraySet<NotificationVisibility> mTmpCurrentlyVisibleNotifications =
-                new ArraySet<>();
-        private final ArraySet<NotificationVisibility> mTmpNoLongerVisibleNotifications =
-                new ArraySet<>();
-
-        @Override
-        public void run() {
-            mLastVisibilityReportUptimeMs = SystemClock.uptimeMillis();
-
-            // 1. Loop over mNotificationData entries:
-            //   A. Keep list of visible notifications.
-            //   B. Keep list of previously hidden, now visible notifications.
-            // 2. Compute no-longer visible notifications by removing currently
-            //    visible notifications from the set of previously visible
-            //    notifications.
-            // 3. Report newly visible and no-longer visible notifications.
-            // 4. Keep currently visible notifications for next report.
-            ArrayList<Entry> activeNotifications = mNotificationData.getActiveNotifications();
-            int N = activeNotifications.size();
-            for (int i = 0; i < N; i++) {
-                Entry entry = activeNotifications.get(i);
-                String key = entry.notification.getKey();
-                boolean isVisible = mStackScroller.isInVisibleLocation(entry.row);
-                NotificationVisibility visObj = NotificationVisibility.obtain(key, i, isVisible);
-                boolean previouslyVisible = mCurrentlyVisibleNotifications.contains(visObj);
-                if (isVisible) {
-                    // Build new set of visible notifications.
-                    mTmpCurrentlyVisibleNotifications.add(visObj);
-                    if (!previouslyVisible) {
-                        mTmpNewlyVisibleNotifications.add(visObj);
-                    }
-                } else {
-                    // release object
-                    visObj.recycle();
-                }
-            }
-            mTmpNoLongerVisibleNotifications.addAll(mCurrentlyVisibleNotifications);
-            mTmpNoLongerVisibleNotifications.removeAll(mTmpCurrentlyVisibleNotifications);
-
-            logNotificationVisibilityChanges(
-                    mTmpNewlyVisibleNotifications, mTmpNoLongerVisibleNotifications);
-
-            recycleAllVisibilityObjects(mCurrentlyVisibleNotifications);
-            mCurrentlyVisibleNotifications.addAll(mTmpCurrentlyVisibleNotifications);
-
-            recycleAllVisibilityObjects(mTmpNoLongerVisibleNotifications);
-            mTmpCurrentlyVisibleNotifications.clear();
-            mTmpNewlyVisibleNotifications.clear();
-            mTmpNoLongerVisibleNotifications.clear();
-        }
-    };
-
     // Notifies StatusBarKeyguardViewManager every time the keyguard transition is over,
     // this animation is tied to the scrim for historic reasons.
     // TODO: notify when keyguard has faded away instead of the scrim.
@@ -655,25 +527,22 @@
             .Callback() {
         @Override
         public void onFinished() {
-            notifyKeyguardState();
-        }
-
-        @Override
-        public void onCancelled() {
-            notifyKeyguardState();
-        }
-
-        private void notifyKeyguardState() {
             if (mStatusBarKeyguardViewManager == null) {
                 Log.w(TAG, "Tried to notify keyguard visibility when "
                         + "mStatusBarKeyguardViewManager was null");
                 return;
             }
-            mStatusBarKeyguardViewManager.onKeyguardFadedAway();
+            if (mKeyguardFadingAway) {
+                mStatusBarKeyguardViewManager.onKeyguardFadedAway();
+            }
+        }
+
+        @Override
+        public void onCancelled() {
+            onFinished();
         }
     };
 
-    private NotificationMessagingUtil mMessagingUtil;
     private KeyguardUserSwitcher mKeyguardUserSwitcher;
     private UserSwitcherController mUserSwitcherController;
     private NetworkController mNetworkController;
@@ -688,31 +557,18 @@
     private final LockscreenGestureLogger mLockscreenGestureLogger = new LockscreenGestureLogger();
     protected NotificationIconAreaController mNotificationIconAreaController;
     private boolean mReinflateNotificationsOnUserSwitched;
-    private final HashMap<String, Entry> mPendingNotifications = new HashMap<>();
     private boolean mClearAllEnabled;
     @Nullable private View mAmbientIndicationContainer;
     private SysuiColorExtractor mColorExtractor;
-    private ForegroundServiceController mForegroundServiceController;
     private ScreenLifecycle mScreenLifecycle;
     @VisibleForTesting WakefulnessLifecycle mWakefulnessLifecycle;
 
-    private void recycleAllVisibilityObjects(ArraySet<NotificationVisibility> array) {
-        final int N = array.size();
-        for (int i = 0 ; i < N; i++) {
-            array.valueAt(i).recycle();
-        }
-        array.clear();
-    }
-
     private final View.OnClickListener mGoToLockedShadeListener = v -> {
         if (mState == StatusBarState.KEYGUARD) {
             wakeUpIfDozing(SystemClock.uptimeMillis(), v);
             goToLockedShade(null);
         }
     };
-    private final HashMap<ExpandableNotificationRow, List<ExpandableNotificationRow>>
-            mTmpChildOrderMap = new HashMap<>();
-    private RankingMap mLatestRankingMap;
     private boolean mNoAnimationOnNextBarModeChange;
     private FalsingManager mFalsingManager;
 
@@ -731,6 +587,12 @@
 
     @Override
     public void start() {
+        mGroupManager = Dependency.get(NotificationGroupManager.class);
+        mVisualStabilityManager = Dependency.get(VisualStabilityManager.class);
+        mNotificationLogger = Dependency.get(NotificationLogger.class);
+        mRemoteInputManager = Dependency.get(NotificationRemoteInputManager.class);
+        mNotificationListener =  Dependency.get(NotificationListener.class);
+        mGroupManager = Dependency.get(NotificationGroupManager.class);
         mNetworkController = Dependency.get(NetworkController.class);
         mUserSwitcherController = Dependency.get(UserSwitcherController.class);
         mScreenLifecycle = Dependency.get(ScreenLifecycle.class);
@@ -739,25 +601,25 @@
         mWakefulnessLifecycle.addObserver(mWakefulnessObserver);
         mBatteryController = Dependency.get(BatteryController.class);
         mAssistManager = Dependency.get(AssistManager.class);
-        mSystemServicesProxy = SystemServicesProxy.getInstance(mContext);
         mOverlayManager = IOverlayManager.Stub.asInterface(
                 ServiceManager.getService(Context.OVERLAY_SERVICE));
+        mLockscreenUserManager = Dependency.get(NotificationLockscreenUserManager.class);
+        mGutsManager = Dependency.get(NotificationGutsManager.class);
+        mMediaManager = Dependency.get(NotificationMediaManager.class);
+        mEntryManager = Dependency.get(NotificationEntryManager.class);
+        mViewHierarchyManager = Dependency.get(NotificationViewHierarchyManager.class);
 
         mColorExtractor = Dependency.get(SysuiColorExtractor.class);
         mColorExtractor.addOnColorsChangedListener(this);
 
         mWindowManager = (WindowManager) mContext.getSystemService(Context.WINDOW_SERVICE);
 
-        mForegroundServiceController = Dependency.get(ForegroundServiceController.class);
-
         mDisplay = mWindowManager.getDefaultDisplay();
         updateDisplaySize();
 
         Resources res = mContext.getResources();
         mScrimSrcModeEnabled = res.getBoolean(R.bool.config_status_bar_scrim_behind_use_src);
         mClearAllEnabled = res.getBoolean(R.bool.config_enableNotificationsClearAll);
-        mAlwaysExpandNonGroupedNotification =
-                res.getBoolean(R.bool.config_alwaysExpandNonGroupedNotifications);
 
         DateTimeView.setReceiverHandler(Dependency.get(Dependency.TIME_TICK_HANDLER));
         putComponent(StatusBar.class, this);
@@ -767,47 +629,22 @@
         mDevicePolicyManager = (DevicePolicyManager) mContext.getSystemService(
                 Context.DEVICE_POLICY_SERVICE);
 
-        mNotificationData = new NotificationData(this);
-        mMessagingUtil = new NotificationMessagingUtil(mContext);
-
         mAccessibilityManager = (AccessibilityManager)
                 mContext.getSystemService(Context.ACCESSIBILITY_SERVICE);
 
         mPowerManager = (PowerManager) mContext.getSystemService(Context.POWER_SERVICE);
 
         mDeviceProvisionedController = Dependency.get(DeviceProvisionedController.class);
-        mDeviceProvisionedController.addCallback(mDeviceProvisionedListener);
-        mContext.getContentResolver().registerContentObserver(
-                Settings.Global.getUriFor(Settings.Global.ZEN_MODE), false,
-                mSettingsObserver);
-        mContext.getContentResolver().registerContentObserver(
-                Settings.Secure.getUriFor(Settings.Secure.LOCK_SCREEN_SHOW_NOTIFICATIONS), false,
-                mLockscreenSettingsObserver,
-                UserHandle.USER_ALL);
-        if (ENABLE_LOCK_SCREEN_ALLOW_REMOTE_INPUT) {
-            mContext.getContentResolver().registerContentObserver(
-                    Settings.Secure.getUriFor(Settings.Secure.LOCK_SCREEN_ALLOW_REMOTE_INPUT),
-                    false,
-                    mSettingsObserver,
-                    UserHandle.USER_ALL);
-        }
-
-        mContext.getContentResolver().registerContentObserver(
-                Settings.Secure.getUriFor(Settings.Secure.LOCK_SCREEN_ALLOW_PRIVATE_NOTIFICATIONS),
-                true,
-                mLockscreenSettingsObserver,
-                UserHandle.USER_ALL);
 
         mBarService = IStatusBarService.Stub.asInterface(
                 ServiceManager.getService(Context.STATUS_BAR_SERVICE));
 
         mRecents = getComponent(Recents.class);
 
-        mUserManager = (UserManager) mContext.getSystemService(Context.USER_SERVICE);
         mKeyguardManager = (KeyguardManager) mContext.getSystemService(Context.KEYGUARD_SERVICE);
         mLockPatternUtils = new LockPatternUtils(mContext);
 
-        mMediaManager = new NotificationMediaManager(this, mContext);
+        mMediaManager.setUpWithPresenter(this, mEntryManager);
 
         // Connect in to the status bar manager service
         mCommandQueue = getComponent(CommandQueue.class);
@@ -828,7 +665,12 @@
 
         createAndAddWindows();
 
-        mSettingsObserver.onChange(false); // set up
+        // Make sure we always have the most current wallpaper info.
+        IntentFilter wallpaperChangedFilter = new IntentFilter(Intent.ACTION_WALLPAPER_CHANGED);
+        mContext.registerReceiver(mWallpaperChangedReceiver, wallpaperChangedFilter);
+        mWallpaperChangedReceiver.onReceive(mContext, null);
+
+        mLockscreenUserManager.setUpWithPresenter(this, mEntryManager);
         mCommandQueue.disable(switches[0], switches[6], false /* animate */);
         setSystemUiVisibility(switches[1], switches[7], switches[8], 0xffffffff,
                 fullscreenStackBounds, dockedStackBounds);
@@ -843,14 +685,7 @@
         }
 
         // Set up the initial notification state.
-        try {
-            mNotificationListener.registerAsSystemService(mContext,
-                    new ComponentName(mContext.getPackageName(), getClass().getCanonicalName()),
-                    UserHandle.USER_ALL);
-        } catch (RemoteException e) {
-            Log.e(TAG, "Unable to register notification listener", e);
-        }
-
+        mNotificationListener.setUpWithPresenter(this, mEntryManager);
 
         if (DEBUG) {
             Log.d(TAG, String.format(
@@ -863,29 +698,13 @@
                    ));
         }
 
-        mCurrentUserId = ActivityManager.getCurrentUser();
-        setHeadsUpUser(mCurrentUserId);
-
-        IntentFilter filter = new IntentFilter();
-        filter.addAction(Intent.ACTION_USER_SWITCHED);
-        filter.addAction(Intent.ACTION_USER_ADDED);
-        filter.addAction(Intent.ACTION_USER_PRESENT);
-        filter.addAction(Intent.ACTION_USER_UNLOCKED);
-        mContext.registerReceiver(mBaseBroadcastReceiver, filter);
+        setHeadsUpUser(mLockscreenUserManager.getCurrentUserId());
 
         IntentFilter internalFilter = new IntentFilter();
-        internalFilter.addAction(NOTIFICATION_UNLOCKED_BY_WORK_CHALLENGE_ACTION);
         internalFilter.addAction(BANNER_ACTION_CANCEL);
         internalFilter.addAction(BANNER_ACTION_SETUP);
-        mContext.registerReceiver(mBaseBroadcastReceiver, internalFilter, PERMISSION_SELF, null);
-
-        IntentFilter allUsersFilter = new IntentFilter();
-        allUsersFilter.addAction(
-                DevicePolicyManager.ACTION_DEVICE_POLICY_MANAGER_STATE_CHANGED);
-        allUsersFilter.addAction(Intent.ACTION_DEVICE_LOCKED_CHANGED);
-        mContext.registerReceiverAsUser(mAllUsersReceiver, UserHandle.ALL, allUsersFilter,
-                null, null);
-        updateCurrentProfilesCache();
+        mContext.registerReceiver(mBannerActionBroadcastReceiver, internalFilter, PERMISSION_SELF,
+                null);
 
         IVrManager vrManager = IVrManager.Stub.asInterface(ServiceManager.getService(
                 Context.VR_SERVICE));
@@ -899,17 +718,7 @@
 
         // Lastly, call to the icon policy to install/update all the icons.
         mIconPolicy = new PhoneStatusBarPolicy(mContext, mIconController);
-        mSettingsObserver.onChange(false); // set up
 
-        mHeadsUpObserver.onChange(true); // set up
-        if (ENABLE_HEADS_UP) {
-            mContext.getContentResolver().registerContentObserver(
-                    Settings.Global.getUriFor(Settings.Global.HEADS_UP_NOTIFICATIONS_ENABLED), true,
-                    mHeadsUpObserver);
-            mContext.getContentResolver().registerContentObserver(
-                    Settings.Global.getUriFor(SETTING_HEADS_UP_TICKER), true,
-                    mHeadsUpObserver);
-        }
         mUnlockMethodCache = UnlockMethodCache.getInstance(mContext);
         mUnlockMethodCache.addListener(this);
         startKeyguard();
@@ -944,8 +753,7 @@
         // 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);
-        mGutsManager = new NotificationGutsManager(this, mStackScroller,
-                mCheckSaveListener, mContext,
+        mGutsManager.setUpWithPresenter(this, mEntryManager, mStackScroller, mCheckSaveListener,
                 key -> {
                     try {
                         mBarService.onNotificationSettingsViewed(key);
@@ -953,6 +761,7 @@
                         // if we're here we're dead
                     }
                 });
+        mNotificationLogger.setUpWithEntryManager(mEntryManager, mStackScroller);
         mNotificationPanel.setStatusBar(this);
         mNotificationPanel.setGroupManager(mGroupManager);
         mAboveShelfObserver = new AboveShelfObserver(mStackScroller);
@@ -991,11 +800,13 @@
         mHeadsUpManager.addListener(mGroupManager);
         mHeadsUpManager.addListener(mVisualStabilityManager);
         mNotificationPanel.setHeadsUpManager(mHeadsUpManager);
-        mNotificationData.setHeadsUpManager(mHeadsUpManager);
         mGroupManager.setHeadsUpManager(mHeadsUpManager);
         mHeadsUpManager.setVisualStabilityManager(mVisualStabilityManager);
         putComponent(HeadsUpManager.class, mHeadsUpManager);
 
+        mEntryManager.setUpWithPresenter(this, mStackScroller, this, mHeadsUpManager);
+        mViewHierarchyManager.setUpWithPresenter(this, mEntryManager, mStackScroller);
+
         if (MULTIUSER_DEBUG) {
             mNotificationPanelDebugText = mNotificationPanel.findViewById(R.id.header_debug_info);
             mNotificationPanelDebugText.setVisibility(View.VISIBLE);
@@ -1011,7 +822,7 @@
             // no window manager? good luck with that
         }
 
-        mStackScroller.setLongPressListener(getNotificationLongClicker());
+        mStackScroller.setLongPressListener(mEntryManager.getNotificationLongClicker());
         mStackScroller.setStatusBar(this);
         mStackScroller.setGroupManager(mGroupManager);
         mStackScroller.setHeadsUpManager(mHeadsUpManager);
@@ -1070,9 +881,9 @@
                 scrimBehind, scrimInFront, headsUpScrim, mLockscreenWallpaper,
                 scrimsVisible -> {
                     if (mStatusBarWindowManager != null) {
-                        mStatusBarWindowManager.setScrimsVisible(scrimsVisible);
+                        mStatusBarWindowManager.setScrimsVisibility(scrimsVisible);
                     }
-                }, new DozeParameters(mContext));
+                }, new DozeParameters(mContext), mContext.getSystemService(AlarmManager.class));
         if (mScrimSrcModeEnabled) {
             Runnable runnable = () -> {
                 boolean asSrc = mBackdrop.getVisibility() != View.VISIBLE;
@@ -1209,7 +1020,7 @@
     protected View.OnTouchListener getStatusBarWindowTouchListener() {
         return (v, event) -> {
             checkUserAutohide(event);
-            checkRemoteInputOutside(event);
+            mRemoteInputManager.checkRemoteInputOutside(event);
             if (event.getAction() == MotionEvent.ACTION_DOWN) {
                 if (mExpandedVisible) {
                     animateCollapsePanels();
@@ -1234,7 +1045,7 @@
         MessagingGroup.dropCache();
         // start old BaseStatusBar.onDensityOrFontScaleChanged().
         if (!KeyguardUpdateMonitor.getInstance(mContext).isSwitchingUser()) {
-            updateNotificationsOnDensityOrFontScaleChanged();
+            mEntryManager.updateNotificationsOnDensityOrFontScaleChanged();
         } else {
             mReinflateNotificationsOnUserSwitched = true;
         }
@@ -1298,20 +1109,6 @@
         }
     }
 
-    private void updateNotificationsOnDensityOrFontScaleChanged() {
-        ArrayList<Entry> activeNotifications = mNotificationData.getActiveNotifications();
-        for (int i = 0; i < activeNotifications.size(); i++) {
-            Entry entry = activeNotifications.get(i);
-            boolean exposedGuts = mGutsManager.getExposedGuts() != null
-                    && entry.row.getGuts() == mGutsManager.getExposedGuts();
-            entry.row.onDensityOrFontScaleChanged();
-            if (exposedGuts) {
-                mGutsManager.setExposedGuts(entry.row.getGuts());
-                mGutsManager.bindGuts(entry.row);
-            }
-        }
-    }
-
     private void inflateSignalClusters() {
         if (mKeyguardStatusBar != null) reinflateSignalCluster(mKeyguardStatusBar);
     }
@@ -1425,13 +1222,13 @@
             mStackScroller.setDismissAllInProgress(false);
             for (ExpandableNotificationRow rowToRemove : viewsToRemove) {
                 if (mStackScroller.canChildBeDismissed(rowToRemove)) {
-                    removeNotification(rowToRemove.getEntry().key, null);
+                    mEntryManager.removeNotification(rowToRemove.getEntry().key, null);
                 } else {
                     rowToRemove.resetTranslation();
                 }
             }
             try {
-                mBarService.onClearAllNotifications(mCurrentUserId);
+                mBarService.onClearAllNotifications(mLockscreenUserManager.getCurrentUserId());
             } catch (Exception ex) {
             }
         });
@@ -1471,15 +1268,6 @@
         }
     }
 
-    protected void setZenMode(int mode) {
-        // start old BaseStatusBar.setZenMode().
-        if (isDeviceProvisioned()) {
-            mZenMode = mode;
-            updateNotifications();
-        }
-        // end old BaseStatusBar.setZenMode().
-    }
-
     protected void startKeyguard() {
         Trace.beginSection("StatusBar#startKeyguard");
         KeyguardViewMediator keyguardViewMediator = getComponent(KeyguardViewMediator.class);
@@ -1491,31 +1279,7 @@
         mKeyguardIndicationController
                 .setStatusBarKeyguardViewManager(mStatusBarKeyguardViewManager);
         mFingerprintUnlockController.setStatusBarKeyguardViewManager(mStatusBarKeyguardViewManager);
-        mRemoteInputController.addCallback(mStatusBarKeyguardViewManager);
-
-        mRemoteInputController.addCallback(new RemoteInputController.Callback() {
-            @Override
-            public void onRemoteInputSent(Entry entry) {
-                if (FORCE_REMOTE_INPUT_HISTORY && mKeysKeptForRemoteInput.contains(entry.key)) {
-                    removeNotification(entry.key, null);
-                } else if (mRemoteInputEntriesToRemoveOnCollapse.contains(entry)) {
-                    // We're currently holding onto this notification, but from the apps point of
-                    // view it is already canceled, so we'll need to cancel it on the apps behalf
-                    // after sending - unless the app posts an update in the mean time, so wait a
-                    // bit.
-                    mHandler.postDelayed(() -> {
-                        if (mRemoteInputEntriesToRemoveOnCollapse.remove(entry)) {
-                            removeNotification(entry.key, null);
-                        }
-                    }, REMOTE_INPUT_KEPT_ENTRY_AUTO_CANCEL_DELAY);
-                }
-                try {
-                    mBarService.onNotificationDirectReplied(entry.key);
-                } catch (RemoteException e) {
-                    // system process is dead if we're here.
-                }
-            }
-        });
+        mRemoteInputManager.getController().addCallback(mStatusBarKeyguardViewManager);
 
         mKeyguardViewMediatorCallback = keyguardViewMediator.getViewMediatorCallback();
         mLightBarController.setFingerprintUnlockController(mFingerprintUnlockController);
@@ -1566,207 +1330,53 @@
         return true;
     }
 
-    void awakenDreams() {
-        SystemServicesProxy.getInstance(mContext).awakenDreamsAsync();
-    }
-
-    public void addNotification(StatusBarNotification notification, RankingMap ranking)
-            throws InflationException {
-        String key = notification.getKey();
-        if (DEBUG) Log.d(TAG, "addNotification key=" + key);
-
-        mNotificationData.updateRanking(ranking);
-        Entry shadeEntry = createNotificationViews(notification);
-        boolean isHeadsUped = shouldPeek(shadeEntry);
-        if (!isHeadsUped && notification.getNotification().fullScreenIntent != null) {
-            if (shouldSuppressFullScreenIntent(key)) {
-                if (DEBUG) {
-                    Log.d(TAG, "No Fullscreen intent: suppressed by DND: " + key);
-                }
-            } else if (mNotificationData.getImportance(key)
-                    < NotificationManager.IMPORTANCE_HIGH) {
-                if (DEBUG) {
-                    Log.d(TAG, "No Fullscreen intent: not important enough: "
-                            + key);
-                }
-            } else {
-                // Stop screensaver if the notification has a full-screen intent.
-                // (like an incoming phone call)
-                awakenDreams();
-
-                // not immersive & a full-screen alert should be shown
-                if (DEBUG)
-                    Log.d(TAG, "Notification has fullScreenIntent; sending fullScreenIntent");
-                try {
-                    EventLog.writeEvent(EventLogTags.SYSUI_FULLSCREEN_NOTIFICATION,
-                            key);
-                    notification.getNotification().fullScreenIntent.send();
-                    shadeEntry.notifyFullScreenIntentLaunched();
-                    mMetricsLogger.count("note_fullscreen", 1);
-                } catch (PendingIntent.CanceledException e) {
-                }
-            }
-        }
-        abortExistingInflation(key);
-
-        mForegroundServiceController.addNotification(notification,
-                mNotificationData.getImportance(key));
-
-        mPendingNotifications.put(key, shadeEntry);
-    }
-
-    private void abortExistingInflation(String key) {
-        if (mPendingNotifications.containsKey(key)) {
-            Entry entry = mPendingNotifications.get(key);
-            entry.abortTask();
-            mPendingNotifications.remove(key);
-        }
-        Entry addedEntry = mNotificationData.get(key);
-        if (addedEntry != null) {
-            addedEntry.abortTask();
+    @Override
+    public void onPerformRemoveNotification(StatusBarNotification n) {
+        if (mStackScroller.hasPulsingNotifications() && mHeadsUpManager.getAllEntries().isEmpty()) {
+            // We were showing a pulse for a notification, but no notifications are pulsing anymore.
+            // Finish the pulse.
+            mDozeScrimController.pulseOutNow();
         }
     }
 
-    private void addEntry(Entry shadeEntry) {
-        boolean isHeadsUped = shouldPeek(shadeEntry);
-        if (isHeadsUped) {
-            mHeadsUpManager.showNotification(shadeEntry);
-            // Mark as seen immediately
-            setNotificationShown(shadeEntry.notification);
+    @Override
+    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;
+
+        // Do not modify the notifications during collapse.
+        if (isCollapsing()) {
+            addPostCollapseAction(this::updateNotificationViews);
+            return;
         }
-        addNotificationViews(shadeEntry);
+
+        mViewHierarchyManager.updateNotificationViews();
+
+        updateSpeedBumpIndex();
+        updateClearAll();
+        updateEmptyShadeView();
+
+        updateQsExpansionEnabled();
+
+        // Let's also update the icons
+        mNotificationIconAreaController.updateNotificationIcons(
+                mEntryManager.getNotificationData());
+    }
+
+    @Override
+    public void onNotificationAdded(Entry shadeEntry) {
         // Recalculate the position of the sliding windows and the titles.
         setAreThereNotifications();
     }
 
     @Override
-    public void handleInflationException(StatusBarNotification notification, Exception e) {
-        handleNotificationError(notification, e.getMessage());
+    public void onNotificationUpdated(StatusBarNotification notification) {
+        setAreThereNotifications();
     }
 
     @Override
-    public void onAsyncInflationFinished(Entry entry) {
-        mPendingNotifications.remove(entry.key);
-        // If there was an async task started after the removal, we don't want to add it back to
-        // the list, otherwise we might get leaks.
-        boolean isNew = mNotificationData.get(entry.key) == null;
-        if (isNew && !entry.row.isRemoved()) {
-            addEntry(entry);
-        } else if (!isNew && entry.row.hasLowPriorityStateUpdated()) {
-            mVisualStabilityManager.onLowPriorityUpdated(entry);
-            updateNotificationShade();
-        }
-        entry.row.setLowPriorityStateUpdated(false);
-    }
-
-    private boolean shouldSuppressFullScreenIntent(String key) {
-        if (isDeviceInVrMode()) {
-            return true;
-        }
-
-        if (mPowerManager.isInteractive()) {
-            return mNotificationData.shouldSuppressScreenOn(key);
-        } else {
-            return mNotificationData.shouldSuppressScreenOff(key);
-        }
-    }
-
-    protected void updateNotificationRanking(RankingMap ranking) {
-        mNotificationData.updateRanking(ranking);
-        updateNotifications();
-    }
-
-    @Override
-    public void removeNotification(String key, RankingMap ranking) {
-        boolean deferRemoval = false;
-        abortExistingInflation(key);
-        if (mHeadsUpManager.isHeadsUp(key)) {
-            // A cancel() in response to a remote input shouldn't be delayed, as it makes the
-            // sending look longer than it takes.
-            // Also we should not defer the removal if reordering isn't allowed since otherwise
-            // some notifications can't disappear before the panel is closed.
-            boolean ignoreEarliestRemovalTime = mRemoteInputController.isSpinning(key)
-                    && !FORCE_REMOTE_INPUT_HISTORY
-                    || !mVisualStabilityManager.isReorderingAllowed();
-            deferRemoval = !mHeadsUpManager.removeNotification(key,  ignoreEarliestRemovalTime);
-        }
-        mMediaManager.onNotificationRemoved(key);
-
-        Entry entry = mNotificationData.get(key);
-        if (FORCE_REMOTE_INPUT_HISTORY && mRemoteInputController.isSpinning(key)
-                && entry.row != null && !entry.row.isDismissed()) {
-            StatusBarNotification sbn = entry.notification;
-
-            Notification.Builder b = Notification.Builder
-                    .recoverBuilder(mContext, sbn.getNotification().clone());
-            CharSequence[] oldHistory = sbn.getNotification().extras
-                    .getCharSequenceArray(Notification.EXTRA_REMOTE_INPUT_HISTORY);
-            CharSequence[] newHistory;
-            if (oldHistory == null) {
-                newHistory = new CharSequence[1];
-            } else {
-                newHistory = new CharSequence[oldHistory.length + 1];
-                System.arraycopy(oldHistory, 0, newHistory, 1, oldHistory.length);
-            }
-            newHistory[0] = String.valueOf(entry.remoteInputText);
-            b.setRemoteInputHistory(newHistory);
-
-            Notification newNotification = b.build();
-
-            // Undo any compatibility view inflation
-            newNotification.contentView = sbn.getNotification().contentView;
-            newNotification.bigContentView = sbn.getNotification().bigContentView;
-            newNotification.headsUpContentView = sbn.getNotification().headsUpContentView;
-
-            StatusBarNotification newSbn = new StatusBarNotification(sbn.getPackageName(),
-                    sbn.getOpPkg(),
-                    sbn.getId(), sbn.getTag(), sbn.getUid(), sbn.getInitialPid(),
-                    newNotification, sbn.getUser(), sbn.getOverrideGroupKey(), sbn.getPostTime());
-            boolean updated = false;
-            try {
-                updateNotification(newSbn, null);
-                updated = true;
-            } catch (InflationException e) {
-                deferRemoval = false;
-            }
-            if (updated) {
-                Log.w(TAG, "Keeping notification around after sending remote input "+ entry.key);
-                mKeysKeptForRemoteInput.add(entry.key);
-                return;
-            }
-        }
-        if (deferRemoval) {
-            mLatestRankingMap = ranking;
-            mHeadsUpEntriesToRemoveOnSwitch.add(mHeadsUpManager.getEntry(key));
-            return;
-        }
-
-        if (entry != null && mRemoteInputController.isRemoteInputActive(entry)
-                && (entry.row != null && !entry.row.isDismissed())) {
-            mLatestRankingMap = ranking;
-            mRemoteInputEntriesToRemoveOnCollapse.add(entry);
-            return;
-        }
-        if (entry != null && mGutsManager.getExposedGuts() != null
-                && mGutsManager.getExposedGuts() == entry.row.getGuts()
-                && entry.row.getGuts() != null && !entry.row.getGuts().isLeavebehind()) {
-            Log.w(TAG, "Keeping notification because it's showing guts. " + key);
-            mLatestRankingMap = ranking;
-            mGutsManager.setKeyToRemoveOnGutsClosed(key);
-            return;
-        }
-
-        if (entry != null) {
-            mForegroundServiceController.removeNotification(entry.notification);
-        }
-
-        if (entry != null && entry.row != null) {
-            entry.row.setRemoved();
-            mStackScroller.cleanUpViewState(entry.row);
-        }
-        // Let's remove the children if this was a summary
-        handleGroupSummaryRemoved(key);
-        StatusBarNotification old = removeNotificationViews(key, ranking);
+    public void onNotificationRemoved(String key, StatusBarNotification old) {
         if (SPEW) Log.d(TAG, "removeNotification key=" + key + " old=" + old);
 
         if (old != null) {
@@ -1783,212 +1393,6 @@
     }
 
     /**
-     * Ensures that the group children are cancelled immediately when the group summary is cancelled
-     * instead of waiting for the notification manager to send all cancels. Otherwise this could
-     * lead to flickers.
-     *
-     * This also ensures that the animation looks nice and only consists of a single disappear
-     * animation instead of multiple.
-     *  @param key the key of the notification was removed
-     *
-     */
-    private void handleGroupSummaryRemoved(String key) {
-        Entry entry = mNotificationData.get(key);
-        if (entry != null && entry.row != null
-                && entry.row.isSummaryWithChildren()) {
-            if (entry.notification.getOverrideGroupKey() != null && !entry.row.isDismissed()) {
-                // We don't want to remove children for autobundled notifications as they are not
-                // always cancelled. We only remove them if they were dismissed by the user.
-                return;
-            }
-            List<ExpandableNotificationRow> notificationChildren =
-                    entry.row.getNotificationChildren();
-            for (int i = 0; i < notificationChildren.size(); i++) {
-                ExpandableNotificationRow row = notificationChildren.get(i);
-                if ((row.getStatusBarNotification().getNotification().flags
-                        & Notification.FLAG_FOREGROUND_SERVICE) != 0) {
-                    // the child is a foreground service notification which we can't remove!
-                    continue;
-                }
-                row.setKeepInParent(true);
-                // we need to set this state earlier as otherwise we might generate some weird
-                // animations
-                row.setRemoved();
-            }
-        }
-    }
-
-    protected void performRemoveNotification(StatusBarNotification n) {
-        Entry entry = mNotificationData.get(n.getKey());
-        if (mRemoteInputController.isRemoteInputActive(entry)) {
-            mRemoteInputController.removeRemoteInput(entry, null);
-        }
-        // start old BaseStatusBar.performRemoveNotification.
-        final String pkg = n.getPackageName();
-        final String tag = n.getTag();
-        final int id = n.getId();
-        final int userId = n.getUserId();
-        try {
-            int dismissalSurface = NotificationStats.DISMISSAL_SHADE;
-            if (isHeadsUp(n.getKey())) {
-                dismissalSurface = NotificationStats.DISMISSAL_PEEK;
-            } else if (mStackScroller.hasPulsingNotifications()) {
-                dismissalSurface = NotificationStats.DISMISSAL_AOD;
-            }
-            mBarService.onNotificationClear(pkg, tag, id, userId, n.getKey(),
-                    dismissalSurface);
-            if (FORCE_REMOTE_INPUT_HISTORY
-                    && mKeysKeptForRemoteInput.contains(n.getKey())) {
-                mKeysKeptForRemoteInput.remove(n.getKey());
-            }
-            removeNotification(n.getKey(), null);
-
-        } catch (RemoteException ex) {
-            // system process is dead if we're here.
-        }
-        if (mStackScroller.hasPulsingNotifications() && mHeadsUpManager.getAllEntries().isEmpty()) {
-            // We were showing a pulse for a notification, but no notifications are pulsing anymore.
-            // Finish the pulse.
-            mDozeScrimController.pulseOutNow();
-        }
-        // end old BaseStatusBar.performRemoveNotification.
-    }
-
-    private void updateNotificationShade() {
-        if (mStackScroller == null) return;
-
-        // Do not modify the notifications during collapse.
-        if (isCollapsing()) {
-            addPostCollapseAction(this::updateNotificationShade);
-            return;
-        }
-
-        ArrayList<Entry> activeNotifications = mNotificationData.getActiveNotifications();
-        ArrayList<ExpandableNotificationRow> toShow = new ArrayList<>(activeNotifications.size());
-        final int N = activeNotifications.size();
-        for (int i = 0; i < N; i++) {
-            Entry ent = activeNotifications.get(i);
-            if (ent.row.isDismissed() || ent.row.isRemoved()) {
-                // we don't want to update removed notifications because they could
-                // temporarily become children if they were isolated before.
-                continue;
-            }
-            int userId = ent.notification.getUserId();
-
-            // Display public version of the notification if we need to redact.
-            boolean devicePublic = isLockscreenPublicMode(mCurrentUserId);
-            boolean userPublic = devicePublic || isLockscreenPublicMode(userId);
-            boolean needsRedaction = needsRedaction(ent);
-            boolean sensitive = userPublic && needsRedaction;
-            boolean deviceSensitive = devicePublic
-                    && !userAllowsPrivateNotificationsInPublic(mCurrentUserId);
-            ent.row.setSensitive(sensitive, deviceSensitive);
-            ent.row.setNeedsRedaction(needsRedaction);
-            if (mGroupManager.isChildInGroupWithSummary(ent.row.getStatusBarNotification())) {
-                ExpandableNotificationRow summary = mGroupManager.getGroupSummary(
-                        ent.row.getStatusBarNotification());
-                List<ExpandableNotificationRow> orderedChildren =
-                        mTmpChildOrderMap.get(summary);
-                if (orderedChildren == null) {
-                    orderedChildren = new ArrayList<>();
-                    mTmpChildOrderMap.put(summary, orderedChildren);
-                }
-                orderedChildren.add(ent.row);
-            } else {
-                toShow.add(ent.row);
-            }
-
-        }
-
-        ArrayList<ExpandableNotificationRow> toRemove = new ArrayList<>();
-        for (int i=0; i< mStackScroller.getChildCount(); i++) {
-            View child = mStackScroller.getChildAt(i);
-            if (!toShow.contains(child) && child instanceof ExpandableNotificationRow) {
-                toRemove.add((ExpandableNotificationRow) child);
-            }
-        }
-
-        for (ExpandableNotificationRow remove : toRemove) {
-            if (mGroupManager.isChildInGroupWithSummary(remove.getStatusBarNotification())) {
-                // we are only transferring this notification to its parent, don't generate an
-                // animation
-                mStackScroller.setChildTransferInProgress(true);
-            }
-            if (remove.isSummaryWithChildren()) {
-                remove.removeAllChildren();
-            }
-            mStackScroller.removeView(remove);
-            mStackScroller.setChildTransferInProgress(false);
-        }
-
-        removeNotificationChildren();
-
-        for (int i = 0; i < toShow.size(); i++) {
-            View v = toShow.get(i);
-            if (v.getParent() == null) {
-                mVisualStabilityManager.notifyViewAddition(v);
-                mStackScroller.addView(v);
-            }
-        }
-
-        addNotificationChildrenAndSort();
-
-        // So after all this work notifications still aren't sorted correctly.
-        // Let's do that now by advancing through toShow and mStackScroller in
-        // lock-step, making sure mStackScroller matches what we see in toShow.
-        int j = 0;
-        for (int i = 0; i < mStackScroller.getChildCount(); i++) {
-            View child = mStackScroller.getChildAt(i);
-            if (!(child instanceof ExpandableNotificationRow)) {
-                // We don't care about non-notification views.
-                continue;
-            }
-
-            ExpandableNotificationRow targetChild = toShow.get(j);
-            if (child != targetChild) {
-                // Oops, wrong notification at this position. Put the right one
-                // here and advance both lists.
-                if (mVisualStabilityManager.canReorderNotification(targetChild)) {
-                    mStackScroller.changeViewPosition(targetChild, i);
-                } else {
-                    mVisualStabilityManager.addReorderingAllowedCallback(this);
-                }
-            }
-            j++;
-
-        }
-
-        mVisualStabilityManager.onReorderingFinished();
-        // clear the map again for the next usage
-        mTmpChildOrderMap.clear();
-
-        updateRowStates();
-        updateSpeedBumpIndex();
-        updateClearAll();
-        updateEmptyShadeView();
-
-        updateQsExpansionEnabled();
-
-        // Let's also update the icons
-        mNotificationIconAreaController.updateNotificationIcons(mNotificationData);
-    }
-
-    /** @return true if the entry needs redaction when on the lockscreen. */
-    private boolean needsRedaction(Entry ent) {
-        int userId = ent.notification.getUserId();
-
-        boolean currentUserWantsRedaction = !userAllowsPrivateNotificationsInPublic(mCurrentUserId);
-        boolean notiUserWantsRedaction = !userAllowsPrivateNotificationsInPublic(userId);
-        boolean redactedLockscreen = currentUserWantsRedaction || notiUserWantsRedaction;
-
-        boolean notificationRequestsRedaction =
-                ent.notification.getNotification().visibility == Notification.VISIBILITY_PRIVATE;
-        boolean userForcesRedaction = packageHasVisibilityOverride(ent.notification.getKey());
-
-        return userForcesRedaction || notificationRequestsRedaction && redactedLockscreen;
-    }
-
-    /**
      * Disable QS if device not provisioned.
      * If the user switcher is simple then disable QS during setup because
      * the user intends to use the lock screen user switcher, QS in not needed.
@@ -2003,81 +1407,6 @@
                 && !ONLY_CORE_APPS);
     }
 
-    private void addNotificationChildrenAndSort() {
-        // Let's now add all notification children which are missing
-        boolean orderChanged = false;
-        for (int i = 0; i < mStackScroller.getChildCount(); i++) {
-            View view = mStackScroller.getChildAt(i);
-            if (!(view instanceof ExpandableNotificationRow)) {
-                // We don't care about non-notification views.
-                continue;
-            }
-
-            ExpandableNotificationRow parent = (ExpandableNotificationRow) view;
-            List<ExpandableNotificationRow> children = parent.getNotificationChildren();
-            List<ExpandableNotificationRow> orderedChildren = mTmpChildOrderMap.get(parent);
-
-            for (int childIndex = 0; orderedChildren != null && childIndex < orderedChildren.size();
-                    childIndex++) {
-                ExpandableNotificationRow childView = orderedChildren.get(childIndex);
-                if (children == null || !children.contains(childView)) {
-                    if (childView.getParent() != null) {
-                        Log.wtf(TAG, "trying to add a notification child that already has " +
-                                "a parent. class:" + childView.getParent().getClass() +
-                                "\n child: " + childView);
-                        // This shouldn't happen. We can recover by removing it though.
-                        ((ViewGroup) childView.getParent()).removeView(childView);
-                    }
-                    mVisualStabilityManager.notifyViewAddition(childView);
-                    parent.addChildNotification(childView, childIndex);
-                    mStackScroller.notifyGroupChildAdded(childView);
-                }
-            }
-
-            // Finally after removing and adding has been performed we can apply the order.
-            orderChanged |= parent.applyChildOrder(orderedChildren, mVisualStabilityManager, this);
-        }
-        if (orderChanged) {
-            mStackScroller.generateChildOrderChangedEvent();
-        }
-    }
-
-    private void removeNotificationChildren() {
-        // First let's remove all children which don't belong in the parents
-        ArrayList<ExpandableNotificationRow> toRemove = new ArrayList<>();
-        for (int i = 0; i < mStackScroller.getChildCount(); i++) {
-            View view = mStackScroller.getChildAt(i);
-            if (!(view instanceof ExpandableNotificationRow)) {
-                // We don't care about non-notification views.
-                continue;
-            }
-
-            ExpandableNotificationRow parent = (ExpandableNotificationRow) view;
-            List<ExpandableNotificationRow> children = parent.getNotificationChildren();
-            List<ExpandableNotificationRow> orderedChildren = mTmpChildOrderMap.get(parent);
-
-            if (children != null) {
-                toRemove.clear();
-                for (ExpandableNotificationRow childRow : children) {
-                    if ((orderedChildren == null
-                            || !orderedChildren.contains(childRow))
-                            && !childRow.keepInParent()) {
-                        toRemove.add(childRow);
-                    }
-                }
-                for (ExpandableNotificationRow remove : toRemove) {
-                    parent.removeChildNotification(remove);
-                    if (mNotificationData.get(remove.getStatusBarNotification().getKey()) == null) {
-                        // We only want to add an animation if the view is completely removed
-                        // otherwise it's just a transfer
-                        mStackScroller.notifyGroupChildRemoved(remove,
-                                parent.getChildrenContainer());
-                    }
-                }
-            }
-        }
-    }
-
     public void addQsTile(ComponentName tile) {
         mQSPanel.getHost().addTile(tile);
     }
@@ -2090,9 +1419,6 @@
         mQSPanel.clickTile(tile);
     }
 
-    private boolean packageHasVisibilityOverride(String key) {
-        return mNotificationData.getVisibilityOverride(key) == Notification.VISIBILITY_PRIVATE;
-    }
 
     private void updateClearAll() {
         if (!mClearAllEnabled) {
@@ -2123,7 +1449,7 @@
     private void updateEmptyShadeView() {
         boolean showEmptyShadeView =
                 mState != StatusBarState.KEYGUARD &&
-                        mNotificationData.getActiveNotifications().size() == 0;
+                        mEntryManager.getNotificationData().getActiveNotifications().size() == 0;
         mNotificationPanel.showEmptyShadeView(showEmptyShadeView);
     }
 
@@ -2138,7 +1464,8 @@
             }
             ExpandableNotificationRow row = (ExpandableNotificationRow) view;
             currentIndex++;
-            if (!mNotificationData.isAmbient(row.getStatusBarNotification().getKey())) {
+            if (!mEntryManager.getNotificationData().isAmbient(
+                    row.getStatusBarNotification().getKey())) {
                 speedBumpIndex = currentIndex;
             }
         }
@@ -2150,15 +1477,9 @@
         return entry.row.getParent() instanceof NotificationStackScrollLayout;
     }
 
-    @Override
-    public void updateNotifications() {
-        mNotificationData.filterAndSort();
-
-        updateNotificationShade();
-    }
 
     public void requestNotificationUpdate() {
-        updateNotifications();
+        mEntryManager.updateNotifications();
     }
 
     protected void setAreThereNotifications() {
@@ -2167,7 +1488,7 @@
             final boolean clearable = hasActiveNotifications() &&
                     hasActiveClearableNotifications();
             Log.d(TAG, "setAreThereNotifications: N=" +
-                    mNotificationData.getActiveNotifications().size() + " any=" +
+                    mEntryManager.getNotificationData().getActiveNotifications().size() + " any=" +
                     hasActiveNotifications() + " clearable=" + clearable);
         }
 
@@ -2452,9 +1773,8 @@
         }
 
         if ((diff1 & StatusBarManager.DISABLE_NOTIFICATION_ALERTS) != 0) {
-            mDisableNotificationAlerts =
-                    (state1 & StatusBarManager.DISABLE_NOTIFICATION_ALERTS) != 0;
-            mHeadsUpObserver.onChange(true);
+            mEntryManager.setDisableNotificationAlerts(
+                    (state1 & StatusBarManager.DISABLE_NOTIFICATION_ALERTS) != 0);
         }
 
         if ((diff2 & StatusBarManager.DISABLE2_QUICK_SETTINGS) != 0) {
@@ -2517,10 +1837,40 @@
         return getBarState() == StatusBarState.KEYGUARD;
     }
 
+    @Override
     public boolean isDozing() {
         return mDozing;
     }
 
+    @Override
+    public boolean shouldPeek(Entry entry, StatusBarNotification sbn) {
+        if (mIsOccluded && !isDozing()) {
+            boolean devicePublic = mLockscreenUserManager.
+                    isLockscreenPublicMode(mLockscreenUserManager.getCurrentUserId());
+            boolean userPublic = devicePublic
+                    || mLockscreenUserManager.isLockscreenPublicMode(sbn.getUserId());
+            boolean needsRedaction = mLockscreenUserManager.needsRedaction(entry);
+            if (userPublic && needsRedaction) {
+                return false;
+            }
+        }
+
+        if (sbn.getNotification().fullScreenIntent != null) {
+            if (mAccessibilityManager.isTouchExplorationEnabled()) {
+                if (DEBUG) Log.d(TAG, "No peeking: accessible fullscreen: " + sbn.getKey());
+                return false;
+            } else if (isDozing()) {
+                // We never want heads up when we are dozing.
+                return false;
+            } else {
+                // we only allow head-up on the lockscreen if it doesn't have a fullscreen intent
+                return !mStatusBarKeyguardViewManager.isShowing()
+                        || mStatusBarKeyguardViewManager.isOccluded();
+            }
+        }
+        return true;
+    }
+
     @Override  // NotificationData.Environment
     public String getCurrentMediaNotificationKey() {
         return mMediaManager.getMediaNotificationKey();
@@ -2572,7 +1922,7 @@
                         mStatusBarWindowManager.setHeadsUpShowing(false);
                         mHeadsUpManager.setHeadsUpGoingAway(false);
                     }
-                    removeRemoteInputEntriesKeptUntilCollapsed();
+                    mRemoteInputManager.removeRemoteInputEntriesKeptUntilCollapsed();
                 });
             }
         }
@@ -2589,34 +1939,10 @@
 
     @Override
     public void onHeadsUpStateChanged(Entry entry, boolean isHeadsUp) {
-        if (!isHeadsUp && mHeadsUpEntriesToRemoveOnSwitch.contains(entry)) {
-            removeNotification(entry.key, mLatestRankingMap);
-            mHeadsUpEntriesToRemoveOnSwitch.remove(entry);
-            if (mHeadsUpEntriesToRemoveOnSwitch.isEmpty()) {
-                mLatestRankingMap = null;
-            }
-        } else {
-            updateNotificationRanking(null);
-            if (isHeadsUp) {
-                mDozeServiceHost.fireNotificationHeadsUp();
-            }
-        }
+        mEntryManager.onHeadsUpStateChanged(entry, isHeadsUp);
 
-    }
-
-    protected void updateHeadsUp(String key, Entry entry, boolean shouldPeek,
-            boolean alertAgain) {
-        final boolean wasHeadsUp = isHeadsUp(key);
-        if (wasHeadsUp) {
-            if (!shouldPeek) {
-                // We don't want this to be interrupting anymore, lets remove it
-                mHeadsUpManager.removeNotification(key, false /* ignoreEarliestRemovalTime */);
-            } else {
-                mHeadsUpManager.updateNotification(entry, alertAgain);
-            }
-        } else if (shouldPeek && alertAgain) {
-            // This notification was updated to be a heads-up, show it!
-            mHeadsUpManager.showNotification(entry);
+        if (isHeadsUp) {
+            mDozeServiceHost.fireNotificationHeadsUp();
         }
     }
 
@@ -2626,14 +1952,6 @@
         }
     }
 
-    public boolean isHeadsUp(String key) {
-        return mHeadsUpManager.isHeadsUp(key);
-    }
-
-    protected boolean isSnoozedPackage(StatusBarNotification sbn) {
-        return mHeadsUpManager.isSnoozed(sbn.getPackageName());
-    }
-
     public boolean isKeyguardCurrentlySecure() {
         return !mUnlockMethodCache.canSkipBouncer();
     }
@@ -2651,19 +1969,10 @@
         }
 
         if (!isExpanded) {
-            removeRemoteInputEntriesKeptUntilCollapsed();
+            mRemoteInputManager.removeRemoteInputEntriesKeptUntilCollapsed();
         }
     }
 
-    private void removeRemoteInputEntriesKeptUntilCollapsed() {
-        for (int i = 0; i < mRemoteInputEntriesToRemoveOnCollapse.size(); i++) {
-            Entry entry = mRemoteInputEntriesToRemoveOnCollapse.valueAt(i);
-            mRemoteInputController.removeRemoteInput(entry, null);
-            removeNotification(entry.key, mLatestRankingMap);
-        }
-        mRemoteInputEntriesToRemoveOnCollapse.clear();
-    }
-
     public NotificationStackScrollLayout getNotificationScrollLayout() {
         return mStackScroller;
     }
@@ -2672,11 +1981,6 @@
         return mDozeScrimController != null && mDozeScrimController.isPulsing();
     }
 
-    @Override
-    public void onReorderingAllowed() {
-        updateNotifications();
-    }
-
     public boolean isLaunchTransitionFadingAway() {
         return mLaunchTransitionFadingAway;
     }
@@ -2694,7 +1998,7 @@
         OverlayInfo themeInfo = null;
         try {
             themeInfo = mOverlayManager.getOverlayInfo("com.android.systemui.theme.dark",
-                    mCurrentUserId);
+                    mLockscreenUserManager.getCurrentUserId());
         } catch (RemoteException e) {
             e.printStackTrace();
         }
@@ -3247,19 +2551,12 @@
         if ((mSystemUiVisibility & STATUS_OR_NAV_TRANSIENT) != 0  // a transient bar is revealed
                 && event.getAction() == MotionEvent.ACTION_OUTSIDE // touch outside the source bar
                 && event.getX() == 0 && event.getY() == 0  // a touch outside both bars
-                && !mRemoteInputController.isRemoteInputActive()) { // not due to typing in IME
+                && !mRemoteInputManager.getController()
+                        .isRemoteInputActive()) { // not due to typing in IME
             userAutohide();
         }
     }
 
-    private void checkRemoteInputOutside(MotionEvent event) {
-        if (event.getAction() == MotionEvent.ACTION_OUTSIDE // touch outside the source bar
-                && event.getX() == 0 && event.getY() == 0  // a touch outside both bars
-                && mRemoteInputController.isRemoteInputActive()) {
-            mRemoteInputController.closeRemoteInputs();
-        }
-    }
-
     private void userAutohide() {
         cancelAutohide();
         mHandler.postDelayed(mAutohide, 350); // longer than app gesture -> flag clear
@@ -3317,14 +2614,6 @@
                     + " scroll " + mStackScroller.getScrollX()
                     + "," + mStackScroller.getScrollY());
         }
-        pw.print("  mPendingNotifications=");
-        if (mPendingNotifications.size() == 0) {
-            pw.println("null");
-        } else {
-            for (Entry entry : mPendingNotifications.values()) {
-                pw.println(entry.notification);
-            }
-        }
 
         pw.print("  mInteractingWindows="); pw.println(mInteractingWindows);
         pw.print("  mStatusBarWindowState=");
@@ -3333,13 +2622,10 @@
         pw.println(BarTransitions.modeToString(mStatusBarMode));
         pw.print("  mDozing="); pw.println(mDozing);
         pw.print("  mZenMode=");
-        pw.println(Settings.Global.zenModeToString(mZenMode));
-        pw.print("  mUseHeadsUp=");
-        pw.println(mUseHeadsUp);
-        pw.print("  mGutsManager: ");
-        if (mGutsManager != null) {
-            mGutsManager.dump(fd, pw, args);
-        }
+        pw.println(Settings.Global.zenModeToString(Settings.Global.getInt(
+                mContext.getContentResolver(), Settings.Global.ZEN_MODE,
+                Settings.Global.ZEN_MODE_OFF)));
+
         if (mStatusBarView != null) {
             dumpBarTransitions(pw, "mStatusBarView", mStatusBarView.getBarTransitions());
         }
@@ -3381,8 +2667,8 @@
         }
 
         if (DUMPTRUCK) {
-            synchronized (mNotificationData) {
-                mNotificationData.dump(pw, "  ");
+            synchronized (mEntryManager.getNotificationData()) {
+                mEntryManager.getNotificationData().dump(pw, "  ");
             }
 
             if (false) {
@@ -3442,19 +2728,20 @@
     private void addStatusBarWindow() {
         makeStatusBarView();
         mStatusBarWindowManager = Dependency.get(StatusBarWindowManager.class);
-        mRemoteInputController = new RemoteInputController(new RemoteInputController.Delegate() {
-          public void setRemoteInputActive(NotificationData.Entry entry,
-                  boolean remoteInputActive) {
-              mHeadsUpManager.setRemoteInputActive(entry, remoteInputActive);
-          }
-          public void lockScrollTo(NotificationData.Entry entry) {
-              mStackScroller.lockScrollTo(entry.row);
-          }
-          public void requestDisallowLongPressAndDismiss() {
-              mStackScroller.requestDisallowLongPress();
-              mStackScroller.requestDisallowDismiss();
-          }
-        });
+        mRemoteInputManager.setUpWithPresenter(this, mEntryManager, this,
+                new RemoteInputController.Delegate() {
+                    public void setRemoteInputActive(NotificationData.Entry entry,
+                            boolean remoteInputActive) {
+                        mHeadsUpManager.setRemoteInputActive(entry, remoteInputActive);
+                    }
+                    public void lockScrollTo(NotificationData.Entry entry) {
+                        mStackScroller.lockScrollTo(entry.row);
+                    }
+                    public void requestDisallowLongPressAndDismiss() {
+                        mStackScroller.requestDisallowLongPress();
+                        mStackScroller.requestDisallowDismiss();
+                    }
+                });
         mStatusBarWindowManager.add(mStatusBarWindow, getStatusBarHeight());
     }
 
@@ -3484,7 +2771,7 @@
         if (onlyProvisioned && !isDeviceProvisioned()) return;
 
         final boolean afterKeyguardGone = PreviewInflater.wouldLaunchResolverActivity(
-                mContext, intent, mCurrentUserId);
+                mContext, intent, mLockscreenUserManager.getCurrentUserId());
         Runnable runnable = () -> {
             mAssistManager.hideAssist();
             intent.setFlags(
@@ -3573,10 +2860,10 @@
             String action = intent.getAction();
             if (Intent.ACTION_CLOSE_SYSTEM_DIALOGS.equals(action)) {
                 KeyboardShortcuts.dismiss();
-                if (mRemoteInputController != null) {
-                    mRemoteInputController.closeRemoteInputs();
+                if (mRemoteInputManager.getController() != null) {
+                    mRemoteInputManager.getController().closeRemoteInputs();
                 }
-                if (isCurrentProfile(getSendingUserId())) {
+                if (mLockscreenUserManager.isCurrentProfile(getSendingUserId())) {
                     int flags = CommandQueue.FLAG_EXCLUDE_NONE;
                     String reason = intent.getStringExtra("reason");
                     if (reason != null && reason.equals(SYSTEM_DIALOG_REASON_RECENT_APPS)) {
@@ -3621,7 +2908,8 @@
     };
 
     public void resetUserExpandedStates() {
-        ArrayList<Entry> activeNotifications = mNotificationData.getActiveNotifications();
+        ArrayList<Entry> activeNotifications = mEntryManager.getNotificationData()
+                .getActiveNotifications();
         final int notificationCount = activeNotifications.size();
         for (int i = 0; i < notificationCount; i++) {
             NotificationData.Entry entry = activeNotifications.get(i);
@@ -3664,27 +2952,40 @@
             Log.v(TAG, "configuration changed: " + mContext.getResources().getConfiguration());
         }
 
-        updateRowStates();
+        mViewHierarchyManager.updateRowStates();
         mScreenPinningRequest.onConfigurationChanged();
     }
 
-    public void userSwitched(int newUserId) {
+    @Override
+    public void onUserSwitched(int newUserId) {
         // Begin old BaseStatusBar.userSwitched
         setHeadsUpUser(newUserId);
         // End old BaseStatusBar.userSwitched
         if (MULTIUSER_DEBUG) mNotificationPanelDebugText.setText("USER " + newUserId);
         animateCollapsePanels();
         updatePublicMode();
-        mNotificationData.filterAndSort();
+        mEntryManager.getNotificationData().filterAndSort();
         if (mReinflateNotificationsOnUserSwitched) {
-            updateNotificationsOnDensityOrFontScaleChanged();
+            mEntryManager.updateNotificationsOnDensityOrFontScaleChanged();
             mReinflateNotificationsOnUserSwitched = false;
         }
-        updateNotificationShade();
+        updateNotificationViews();
         mMediaManager.clearCurrentMediaNotification();
         setLockscreenUser(newUserId);
     }
 
+    @Override
+    public NotificationLockscreenUserManager getNotificationLockscreenUserManager() {
+        return mLockscreenUserManager;
+    }
+
+    @Override
+    public void onBindRow(Entry entry, PackageManager pmUser,
+            StatusBarNotification sbn, ExpandableNotificationRow row) {
+        row.setAboveShelfChangedListener(mAboveShelfObserver);
+        row.setSecureStateProvider(this::isKeyguardCurrentlySecure);
+    }
+
     protected void setLockscreenUser(int newUserId) {
         mLockscreenWallpaper.setCurrentUser(newUserId);
         mScrimController.setCurrentUser(newUserId);
@@ -3734,9 +3035,9 @@
     protected void handleVisibleToUserChanged(boolean visibleToUser) {
         if (visibleToUser) {
             handleVisibleToUserChangedImpl(visibleToUser);
-            startNotificationLogging();
+            mNotificationLogger.startNotificationLogging();
         } else {
-            stopNotificationLogging();
+            mNotificationLogger.stopNotificationLogging();
             handleVisibleToUserChangedImpl(visibleToUser);
         }
     }
@@ -3745,7 +3046,8 @@
         try {
             // consider the transition from peek to expanded to be a panel open,
             // but not one that clears notification effects.
-            int notificationLoad = mNotificationData.getActiveNotifications().size();
+            int notificationLoad = mEntryManager.getNotificationData()
+                    .getActiveNotifications().size();
             mBarService.onPanelRevealed(false, notificationLoad);
         } catch (RemoteException ex) {
             // Won't fail unless the world has ended.
@@ -3757,77 +3059,38 @@
      * See also StatusBar.setPanelExpanded for another place where we attempt to do this.
      */
     private void handleVisibleToUserChangedImpl(boolean visibleToUser) {
-        try {
-            if (visibleToUser) {
-                boolean pinnedHeadsUp = mHeadsUpManager.hasPinnedHeadsUp();
-                boolean clearNotificationEffects =
-                        !isPresenterFullyCollapsed() &&
-                        (mState == StatusBarState.SHADE || mState == StatusBarState.SHADE_LOCKED);
-                int notificationLoad = mNotificationData.getActiveNotifications().size();
-                if (pinnedHeadsUp && isPresenterFullyCollapsed())  {
-                    notificationLoad = 1;
+        if (visibleToUser) {
+            boolean pinnedHeadsUp = mHeadsUpManager.hasPinnedHeadsUp();
+            boolean clearNotificationEffects =
+                    !isPresenterFullyCollapsed() &&
+                            (mState == StatusBarState.SHADE
+                                    || mState == StatusBarState.SHADE_LOCKED);
+            int notificationLoad = mEntryManager.getNotificationData().getActiveNotifications()
+                    .size();
+            if (pinnedHeadsUp && isPresenterFullyCollapsed()) {
+                notificationLoad = 1;
+            }
+            final int finalNotificationLoad = notificationLoad;
+            mUiOffloadThread.submit(() -> {
+                try {
+                    mBarService.onPanelRevealed(clearNotificationEffects,
+                            finalNotificationLoad);
+                } catch (RemoteException ex) {
+                    // Won't fail unless the world has ended.
                 }
-                mBarService.onPanelRevealed(clearNotificationEffects, notificationLoad);
-            } else {
-                mBarService.onPanelHidden();
-            }
-        } catch (RemoteException ex) {
-            // Won't fail unless the world has ended.
+            });
+        } else {
+            mUiOffloadThread.submit(() -> {
+                try {
+                    mBarService.onPanelHidden();
+                } catch (RemoteException ex) {
+                    // Won't fail unless the world has ended.
+                }
+            });
         }
+
     }
 
-    private void stopNotificationLogging() {
-        // Report all notifications as invisible and turn down the
-        // reporter.
-        if (!mCurrentlyVisibleNotifications.isEmpty()) {
-            logNotificationVisibilityChanges(
-                    Collections.emptyList(), mCurrentlyVisibleNotifications);
-            recycleAllVisibilityObjects(mCurrentlyVisibleNotifications);
-        }
-        mHandler.removeCallbacks(mVisibilityReporter);
-        mStackScroller.setChildLocationsChangedListener(null);
-    }
-
-    private void startNotificationLogging() {
-        mStackScroller.setChildLocationsChangedListener(mNotificationLocationsChangedListener);
-        // Some transitions like mVisibleToUser=false -> mVisibleToUser=true don't
-        // cause the scroller to emit child location events. Hence generate
-        // one ourselves to guarantee that we're reporting visible
-        // notifications.
-        // (Note that in cases where the scroller does emit events, this
-        // additional event doesn't break anything.)
-        mNotificationLocationsChangedListener.onChildLocationsChanged(mStackScroller);
-    }
-
-    private void logNotificationVisibilityChanges(
-            Collection<NotificationVisibility> newlyVisible,
-            Collection<NotificationVisibility> noLongerVisible) {
-        if (newlyVisible.isEmpty() && noLongerVisible.isEmpty()) {
-            return;
-        }
-        NotificationVisibility[] newlyVisibleAr =
-                newlyVisible.toArray(new NotificationVisibility[newlyVisible.size()]);
-        NotificationVisibility[] noLongerVisibleAr =
-                noLongerVisible.toArray(new NotificationVisibility[noLongerVisible.size()]);
-        try {
-            mBarService.onNotificationVisibilityChanged(newlyVisibleAr, noLongerVisibleAr);
-        } catch (RemoteException e) {
-            // Ignore.
-        }
-
-        final int N = newlyVisible.size();
-        if (N > 0) {
-            String[] newlyVisibleKeyAr = new String[N];
-            for (int i = 0; i < N; i++) {
-                newlyVisibleKeyAr[i] = newlyVisibleAr[i].key;
-            }
-
-            setNotificationsShown(newlyVisibleKeyAr);
-        }
-    }
-
-    // State logging
-
     private void logStateToEventlog() {
         boolean isShowing = mStatusBarKeyguardViewManager.isShowing();
         boolean isOccluded = mStatusBarKeyguardViewManager.isOccluded();
@@ -3931,13 +3194,14 @@
 
     public void destroy() {
         // Begin old BaseStatusBar.destroy().
-        mContext.unregisterReceiver(mBaseBroadcastReceiver);
+        mContext.unregisterReceiver(mBannerActionBroadcastReceiver);
+        mLockscreenUserManager.destroy();
         try {
             mNotificationListener.unregisterAsSystemService();
         } catch (RemoteException e) {
             // Ignore.
         }
-        mDeviceProvisionedController.removeCallback(mDeviceProvisionedListener);
+        mEntryManager.destroy();
         // End old BaseStatusBar.destroy().
         if (mStatusBarWindow != null) {
             mWindowManager.removeViewImmediate(mStatusBarWindow);
@@ -4185,14 +3449,10 @@
                 .setStartDelay(0)
                 .setDuration(FADE_KEYGUARD_DURATION_PULSING)
                 .setInterpolator(ScrimController.KEYGUARD_FADE_OUT_INTERPOLATOR)
-                .setListener(new AnimatorListenerAdapter() {
-                    @Override
-                    public void onAnimationEnd(Animator animation) {
-                        hideKeyguard();
-                        mStatusBarKeyguardViewManager.onKeyguardFadedAway();
-                    }
-                })
-                .start();
+                .withEndAction(()-> {
+                    hideKeyguard();
+                    mStatusBarKeyguardViewManager.onKeyguardFadedAway();
+                }).start();
     }
 
     /**
@@ -4337,18 +3597,21 @@
         mKeyguardMonitor.notifyKeyguardDoneFading();
     }
 
+    // TODO: Move this to NotificationLockscreenUserManager.
     private void updatePublicMode() {
         final boolean showingKeyguard = mStatusBarKeyguardViewManager.isShowing();
         final boolean devicePublic = showingKeyguard
-                && mStatusBarKeyguardViewManager.isSecure(mCurrentUserId);
+                && mStatusBarKeyguardViewManager.isSecure(
+                        mLockscreenUserManager.getCurrentUserId());
 
         // Look for public mode users. Users are considered public in either case of:
         //   - device keyguard is shown in secure mode;
         //   - profile is locked with a work challenge.
-        for (int i = mCurrentProfiles.size() - 1; i >= 0; i--) {
-            final int userId = mCurrentProfiles.valueAt(i).id;
+        SparseArray<UserInfo> currentProfiles = mLockscreenUserManager.getCurrentProfiles();
+        for (int i = currentProfiles.size() - 1; i >= 0; i--) {
+            final int userId = currentProfiles.valueAt(i).id;
             boolean isProfilePublic = devicePublic;
-            if (!devicePublic && userId != mCurrentUserId) {
+            if (!devicePublic && userId != mLockscreenUserManager.getCurrentUserId()) {
                 // We can't rely on KeyguardManager#isDeviceLocked() for unified profile challenge
                 // due to a race condition where this code could be called before
                 // TrustManagerService updates its internal records, resulting in an incorrect
@@ -4358,7 +3621,7 @@
                     isProfilePublic = mKeyguardManager.isDeviceLocked(userId);
                 }
             }
-            setLockscreenPublicMode(isProfilePublic, userId);
+            mLockscreenUserManager.setLockscreenPublicMode(isProfilePublic, userId);
         }
     }
 
@@ -4391,7 +3654,7 @@
         updateDozingState();
         updatePublicMode();
         updateStackScrollerState(goingToFullShade, fromShadeLocked);
-        updateNotifications();
+        mEntryManager.updateNotifications();
         checkBarModes();
         updateScrimController();
         updateMediaMetaData(false, mState != StatusBarState.KEYGUARD);
@@ -4416,7 +3679,7 @@
             mUiOffloadThread.submit(() -> {
                 try {
                     mOverlayManager.setEnabled("com.android.systemui.theme.dark",
-                            useDarkTheme, mCurrentUserId);
+                            useDarkTheme, mLockscreenUserManager.getCurrentUserId());
                 } catch (RemoteException e) {
                     Log.w(TAG, "Can't change theme", e);
                 }
@@ -4454,21 +3717,22 @@
     private void updateDozingState() {
         Trace.traceCounter(Trace.TRACE_TAG_APP, "dozing", mDozing ? 1 : 0);
         Trace.beginSection("StatusBar#updateDozingState");
-        boolean animate = !mDozing && mDozeServiceHost.shouldAnimateWakeup();
+        boolean animate = (!mDozing && mDozeServiceHost.shouldAnimateWakeup())
+                || (mDozing && mDozeServiceHost.shouldAnimateScreenOff());
         mNotificationPanel.setDozing(mDozing, animate);
         mStackScroller.setDark(mDozing, animate, mWakeUpTouchLocation);
         mDozeScrimController.setDozing(mDozing);
         mKeyguardIndicationController.setDozing(mDozing);
         mNotificationPanel.setDark(mDozing, animate);
         updateQsExpansionEnabled();
-        updateRowStates();
+        mViewHierarchyManager.updateRowStates();
         Trace.endSection();
     }
 
     public void updateStackScrollerState(boolean goingToFullShade, boolean fromShadeLocked) {
         if (mStackScroller == null) return;
         boolean onKeyguard = mState == StatusBarState.KEYGUARD;
-        boolean publicMode = isAnyProfilePublicMode();
+        boolean publicMode = mLockscreenUserManager.isAnyProfilePublicMode();
         mStackScroller.setHideSensitive(publicMode, goingToFullShade);
         mStackScroller.setDimmed(onKeyguard, fromShadeLocked /* animate */);
         mStackScroller.setExpandingEnabled(!onKeyguard);
@@ -4591,7 +3855,7 @@
             clearNotificationEffects();
         }
         if (state == StatusBarState.KEYGUARD) {
-            removeRemoteInputEntriesKeptUntilCollapsed();
+            mRemoteInputManager.removeRemoteInputEntriesKeptUntilCollapsed();
             maybeEscalateHeadsUp();
         }
         mState = state;
@@ -4665,7 +3929,8 @@
         }
     }
 
-    protected int getMaxKeyguardNotifications(boolean recompute) {
+    @Override
+    public int getMaxNotificationsWhileLocked(boolean recompute) {
         if (recompute) {
             mMaxKeyguardNotifications = Math.max(1,
                     mNotificationPanel.computeMaxKeyguardNotifications(
@@ -4675,8 +3940,8 @@
         return mMaxKeyguardNotifications;
     }
 
-    public int getMaxKeyguardNotifications() {
-        return getMaxKeyguardNotifications(false /* recompute */);
+    public int getMaxNotificationsWhileLocked() {
+        return getMaxNotificationsWhileLocked(false /* recompute */);
     }
 
     // TODO: Figure out way to remove these.
@@ -4762,7 +4027,7 @@
             return;
         }
 
-        int userId = mCurrentUserId;
+        int userId = mLockscreenUserManager.getCurrentUserId();
         ExpandableNotificationRow row = null;
         if (expandView instanceof ExpandableNotificationRow) {
             row = (ExpandableNotificationRow) expandView;
@@ -4774,9 +4039,11 @@
                 userId = row.getStatusBarNotification().getUserId();
             }
         }
-        boolean fullShadeNeedsBouncer = !userAllowsPrivateNotificationsInPublic(mCurrentUserId)
-                || !mShowLockscreenNotifications || mFalsingManager.shouldEnforceBouncer();
-        if (isLockscreenPublicMode(userId) && fullShadeNeedsBouncer) {
+        boolean fullShadeNeedsBouncer = !mLockscreenUserManager.
+                userAllowsPrivateNotificationsInPublic(mLockscreenUserManager.getCurrentUserId())
+                || !mLockscreenUserManager.shouldShowLockscreenNotifications()
+                || mFalsingManager.shouldEnforceBouncer();
+        if (mLockscreenUserManager.isLockscreenPublicMode(userId) && fullShadeNeedsBouncer) {
             mLeaveOpenOnKeyguardHide = true;
             showBouncerIfKeyguard();
             mDraggedDownRow = row;
@@ -4793,13 +4060,15 @@
         dismissKeyguardThenExecute(dismissAction, true /* afterKeyguardGone */);
     }
 
-    protected void onLockedRemoteInput(ExpandableNotificationRow row, View clicked) {
+    @Override
+    public void onLockedRemoteInput(ExpandableNotificationRow row, View clicked) {
         mLeaveOpenOnKeyguardHide = true;
         showBouncer();
         mPendingRemoteInputView = clicked;
     }
 
-    protected void onMakeExpandedVisibleForRemoteInput(ExpandableNotificationRow row,
+    @Override
+    public void onMakeExpandedVisibleForRemoteInput(ExpandableNotificationRow row,
             View clickedView) {
         if (isKeyguardShowing()) {
             onLockedRemoteInput(row, clickedView);
@@ -4809,6 +4078,47 @@
         }
     }
 
+    @Override
+    public boolean shouldHandleRemoteInput(View view, PendingIntent pendingIntent) {
+        // Skip remote input as doing so will expand the notification shade.
+        return (mDisabled2 & StatusBarManager.DISABLE2_NOTIFICATION_SHADE) != 0;
+    }
+
+    @Override
+    public boolean handleRemoteViewClick(View view, PendingIntent pendingIntent,
+            Intent fillInIntent, NotificationRemoteInputManager.ClickHandler defaultHandler) {
+        final boolean isActivity = pendingIntent.isActivity();
+        if (isActivity) {
+            final boolean afterKeyguardGone = PreviewInflater.wouldLaunchResolverActivity(
+                    mContext, pendingIntent.getIntent(), mLockscreenUserManager.getCurrentUserId());
+            dismissKeyguardThenExecute(() -> {
+                try {
+                    ActivityManager.getService().resumeAppSwitches();
+                } catch (RemoteException e) {
+                }
+
+                boolean handled = defaultHandler.handleClick();
+
+                // close the shade if it was open
+                if (handled && !mNotificationPanel.isFullyCollapsed()) {
+                    animateCollapsePanels(
+                            CommandQueue.FLAG_EXCLUDE_RECENTS_PANEL, true /* force */);
+                    visibilityChanged(false);
+                    mAssistManager.hideAssist();
+
+                    // Wait for activity start.
+                    return true;
+                } else {
+                    return false;
+                }
+
+            }, afterKeyguardGone);
+            return true;
+        } else {
+            return defaultHandler.handleClick();
+        }
+    }
+
     protected boolean startWorkChallengeIfNecessary(int userId, IntentSender intendSender,
             String notificationKey) {
         // Clear pending remote view, as we do not want to trigger pending remote input view when
@@ -4845,7 +4155,8 @@
         // End old BaseStatusBar.startWorkChallengeIfNecessary.
     }
 
-    protected void onLockedWorkRemoteInput(int userId, ExpandableNotificationRow row,
+    @Override
+    public void onLockedWorkRemoteInput(int userId, ExpandableNotificationRow row,
             View clicked) {
         // Collapse notification and show work challenge
         animateCollapsePanels();
@@ -4855,19 +4166,12 @@
         mPendingWorkRemoteInputView = clicked;
     }
 
-    private boolean isAnyProfilePublicMode() {
-        for (int i = mCurrentProfiles.size() - 1; i >= 0; i--) {
-            if (isLockscreenPublicMode(mCurrentProfiles.valueAt(i).id)) {
-                return true;
-            }
-        }
-        return false;
-    }
-
-    protected void onWorkChallengeChanged() {
+    @Override
+    public void onWorkChallengeChanged() {
         updatePublicMode();
-        updateNotifications();
-        if (mPendingWorkRemoteInputView != null && !isAnyProfilePublicMode()) {
+        mEntryManager.updateNotifications();
+        if (mPendingWorkRemoteInputView != null
+                && !mLockscreenUserManager.isAnyProfilePublicMode()) {
             // Expand notification panel and the notification row, then click on remote input view
             final Runnable clickPendingViewRunnable = () -> {
                 final View pendingWorkRemoteInputView = mPendingWorkRemoteInputView;
@@ -5075,9 +4379,10 @@
     }
 
     public boolean hasActiveNotifications() {
-        return !mNotificationData.getActiveNotifications().isEmpty();
+        return !mEntryManager.getNotificationData().getActiveNotifications().isEmpty();
     }
 
+    @Override
     public void wakeUpIfDozing(long time, View where) {
         if (mDozing) {
             PowerManager pm = (PowerManager) mContext.getSystemService(Context.POWER_SERVICE);
@@ -5092,6 +4397,11 @@
     }
 
     @Override
+    public boolean isDeviceLocked(int userId) {
+        return mKeyguardManager.isDeviceLocked(userId);
+    }
+
+    @Override
     public void appTransitionCancelled() {
         EventBus.getDefault().send(new AppTransitionFinishedEvent());
     }
@@ -5147,12 +4457,14 @@
     }
 
     boolean isCameraAllowedByAdmin() {
-        if (mDevicePolicyManager.getCameraDisabled(null, mCurrentUserId)) {
+        if (mDevicePolicyManager.getCameraDisabled(null,
+                mLockscreenUserManager.getCurrentUserId())) {
             return false;
         } else if (mStatusBarKeyguardViewManager == null ||
                 (isKeyguardShowing() && isKeyguardSecure())) {
             // Check if the admin has disabled the camera specifically for the keyguard
-            return (mDevicePolicyManager.getKeyguardDisabledFeatures(null, mCurrentUserId)
+            return (mDevicePolicyManager.
+                    getKeyguardDisabledFeatures(null, mLockscreenUserManager.getCurrentUserId())
                     & DevicePolicyManager.KEYGUARD_DISABLE_SECURE_CAMERA) == 0;
         }
 
@@ -5171,6 +4483,7 @@
 
     public void notifyFpAuthModeChanged() {
         updateDozing();
+        updateScrimController();
     }
 
     private void updateDozing() {
@@ -5195,7 +4508,15 @@
         Trace.endSection();
     }
 
-    public void updateScrimController() {
+    @VisibleForTesting
+    void updateScrimController() {
+        Trace.beginSection("StatusBar#updateScrimController");
+
+        // We don't want to end up in KEYGUARD state when we're unlocking with
+        // fingerprint from doze. We should cross fade directly from black.
+        final boolean wakeAndUnlocking = mFingerprintUnlockController.getMode()
+                == FingerprintUnlockController.MODE_WAKE_AND_UNLOCK;
+
         if (mBouncerShowing) {
             mScrimController.transitionTo(ScrimState.BOUNCER);
         } else if (mLaunchCameraOnScreenTurningOn || isInLaunchTransition()) {
@@ -5206,11 +4527,12 @@
             // Handled in DozeScrimController#setPulsing
         } else if (mDozing) {
             mScrimController.transitionTo(ScrimState.AOD);
-        } else if (mIsKeyguard) {
+        } else if (mIsKeyguard && !wakeAndUnlocking) {
             mScrimController.transitionTo(ScrimState.KEYGUARD);
         } else {
             mScrimController.transitionTo(ScrimState.UNLOCKED, mUnlockScrimCallback);
         }
+        Trace.endSection();
     }
 
     public boolean isKeyguardShowing() {
@@ -5224,6 +4546,7 @@
     private final class DozeServiceHost implements DozeHost {
         private final ArrayList<Callback> mCallbacks = new ArrayList<>();
         private boolean mAnimateWakeup;
+        private boolean mAnimateScreenOff;
         private boolean mIgnoreTouchWhilePulsing;
 
         @Override
@@ -5371,6 +4694,11 @@
         }
 
         @Override
+        public void setAnimateScreenOff(boolean animateScreenOff) {
+            mAnimateScreenOff = animateScreenOff;
+        }
+
+        @Override
         public void onDoubleTap(float screenX, float screenY) {
             if (screenX > 0 && screenY > 0 && mAmbientIndicationContainer != null
                 && mAmbientIndicationContainer.getVisibility() == View.VISIBLE) {
@@ -5414,6 +4742,10 @@
         private boolean shouldAnimateWakeup() {
             return mAnimateWakeup;
         }
+
+        public boolean shouldAnimateScreenOff() {
+            return mAnimateScreenOff;
+        }
     }
 
     public boolean shouldIgnoreTouch() {
@@ -5426,12 +4758,10 @@
     protected IStatusBarService mBarService;
 
     // all notifications
-    protected NotificationData mNotificationData;
     protected NotificationStackScrollLayout mStackScroller;
 
-    protected final NotificationGroupManager mGroupManager = new NotificationGroupManager();
+    protected NotificationGroupManager mGroupManager;
 
-    protected RemoteInputController mRemoteInputController;
 
     // for heads up notifications
     protected HeadsUpManager mHeadsUpManager;
@@ -5439,48 +4769,25 @@
     private AboveShelfObserver mAboveShelfObserver;
 
     // handling reordering
-    protected final VisualStabilityManager mVisualStabilityManager = new VisualStabilityManager();
-
-    protected int mCurrentUserId = 0;
-    final protected SparseArray<UserInfo> mCurrentProfiles = new SparseArray<>();
+    protected VisualStabilityManager mVisualStabilityManager;
 
     protected AccessibilityManager mAccessibilityManager;
 
     protected boolean mDeviceInteractive;
 
     protected boolean mVisible;
-    protected final ArraySet<Entry> mHeadsUpEntriesToRemoveOnSwitch = new ArraySet<>();
-    protected final ArraySet<Entry> mRemoteInputEntriesToRemoveOnCollapse = new ArraySet<>();
-
-    /**
-     * Notifications with keys in this set are not actually around anymore. We kept them around
-     * when they were canceled in response to a remote input interaction. This allows us to show
-     * what you replied and allows you to continue typing into it.
-     */
-    protected final ArraySet<String> mKeysKeptForRemoteInput = new ArraySet<>();
 
     // mScreenOnFromKeyguard && mVisible.
     private boolean mVisibleToUser;
 
-    protected boolean mUseHeadsUp = false;
-    protected boolean mDisableNotificationAlerts = false;
-
     protected DevicePolicyManager mDevicePolicyManager;
     protected PowerManager mPowerManager;
     protected StatusBarKeyguardViewManager mStatusBarKeyguardViewManager;
 
-    // public mode, private notifications, etc
-    private final SparseBooleanArray mLockscreenPublicMode = new SparseBooleanArray();
-    private final SparseBooleanArray mUsersAllowingPrivateNotifications = new SparseBooleanArray();
-    private final SparseBooleanArray mUsersAllowingNotifications = new SparseBooleanArray();
-
-    private UserManager mUserManager;
-
     protected KeyguardManager mKeyguardManager;
     private LockPatternUtils mLockPatternUtils;
     private DeviceProvisionedController mDeviceProvisionedController
             = Dependency.get(DeviceProvisionedController.class);
-    protected SystemServicesProxy mSystemServicesProxy;
 
     // UI-specific methods
 
@@ -5491,14 +4798,10 @@
 
     protected RecentsComponent mRecents;
 
-    protected int mZenMode;
-
     protected NotificationShelf mNotificationShelf;
     protected DismissView mDismissView;
     protected EmptyShadeView mEmptyShadeView;
 
-    private final NotificationClicker mNotificationClicker = new NotificationClicker();
-
     protected AssistManager mAssistManager;
 
     protected boolean mVrMode;
@@ -5523,288 +4826,15 @@
         return mVrMode;
     }
 
-    private final DeviceProvisionedListener mDeviceProvisionedListener =
-            new DeviceProvisionedListener() {
-        @Override
-        public void onDeviceProvisionedChanged() {
-            updateNotifications();
-        }
-    };
-
-    protected final ContentObserver mSettingsObserver = new ContentObserver(mHandler) {
-        @Override
-        public void onChange(boolean selfChange) {
-            final int mode = Settings.Global.getInt(mContext.getContentResolver(),
-                    Settings.Global.ZEN_MODE, Settings.Global.ZEN_MODE_OFF);
-            setZenMode(mode);
-
-            updateLockscreenNotificationSetting();
-        }
-    };
-
-    private final ContentObserver mLockscreenSettingsObserver = new ContentObserver(mHandler) {
-        @Override
-        public void onChange(boolean selfChange) {
-            // We don't know which user changed LOCK_SCREEN_ALLOW_PRIVATE_NOTIFICATIONS or
-            // LOCK_SCREEN_SHOW_NOTIFICATIONS, so we just dump our cache ...
-            mUsersAllowingPrivateNotifications.clear();
-            mUsersAllowingNotifications.clear();
-            // ... and refresh all the notifications
-            updateLockscreenNotificationSetting();
-            updateNotifications();
-        }
-    };
-
-    private final RemoteViews.OnClickHandler mOnClickHandler = new RemoteViews.OnClickHandler() {
-
-        @Override
-        public boolean onClickHandler(
-                final View view, final PendingIntent pendingIntent, final Intent fillInIntent) {
-            wakeUpIfDozing(SystemClock.uptimeMillis(), view);
-
-            if (handleRemoteInput(view, pendingIntent)) {
-                return true;
-            }
-
-            if (DEBUG) {
-                Log.v(TAG, "Notification click handler invoked for intent: " + pendingIntent);
-            }
-            logActionClick(view);
-            // The intent we are sending is for the application, which
-            // won't have permission to immediately start an activity after
-            // the user switches to home.  We know it is safe to do at this
-            // point, so make sure new activity switches are now allowed.
-            try {
-                ActivityManager.getService().resumeAppSwitches();
-            } catch (RemoteException e) {
-            }
-            final boolean isActivity = pendingIntent.isActivity();
-            if (isActivity) {
-                final boolean afterKeyguardGone = PreviewInflater.wouldLaunchResolverActivity(
-                        mContext, pendingIntent.getIntent(), mCurrentUserId);
-                dismissKeyguardThenExecute(() -> {
-                    try {
-                        ActivityManager.getService().resumeAppSwitches();
-                    } catch (RemoteException e) {
-                    }
-
-                    boolean handled = superOnClickHandler(view, pendingIntent, fillInIntent);
-
-                    // close the shade if it was open
-                    if (handled && !mNotificationPanel.isFullyCollapsed()) {
-                        animateCollapsePanels(
-                                CommandQueue.FLAG_EXCLUDE_RECENTS_PANEL, true /* force */);
-                        visibilityChanged(false);
-                        mAssistManager.hideAssist();
-
-                        // Wait for activity start.
-                        return true;
-                    } else {
-                        return false;
-                    }
-
-                }, afterKeyguardGone);
-                return true;
-            } else {
-                return superOnClickHandler(view, pendingIntent, fillInIntent);
-            }
-        }
-
-        private void logActionClick(View view) {
-            ViewParent parent = view.getParent();
-            String key = getNotificationKeyForParent(parent);
-            if (key == null) {
-                Log.w(TAG, "Couldn't determine notification for click.");
-                return;
-            }
-            int index = -1;
-            // If this is a default template, determine the index of the button.
-            if (view.getId() == com.android.internal.R.id.action0 &&
-                    parent != null && parent instanceof ViewGroup) {
-                ViewGroup actionGroup = (ViewGroup) parent;
-                index = actionGroup.indexOfChild(view);
-            }
-            try {
-                mBarService.onNotificationActionClick(key, index);
-            } catch (RemoteException e) {
-                // Ignore
-            }
-        }
-
-        private String getNotificationKeyForParent(ViewParent parent) {
-            while (parent != null) {
-                if (parent instanceof ExpandableNotificationRow) {
-                    return ((ExpandableNotificationRow) parent).getStatusBarNotification().getKey();
-                }
-                parent = parent.getParent();
-            }
-            return null;
-        }
-
-        private boolean superOnClickHandler(View view, PendingIntent pendingIntent,
-                Intent fillInIntent) {
-            return super.onClickHandler(view, pendingIntent, fillInIntent,
-                    WINDOWING_MODE_FULLSCREEN_OR_SPLIT_SCREEN_SECONDARY);
-        }
-
-        private boolean handleRemoteInput(View view, PendingIntent pendingIntent) {
-            if ((mDisabled2 & StatusBarManager.DISABLE2_NOTIFICATION_SHADE) != 0) {
-                // Skip remote input as doing so will expand the notification shade.
-                return true;
-            }
-
-            Object tag = view.getTag(com.android.internal.R.id.remote_input_tag);
-            RemoteInput[] inputs = null;
-            if (tag instanceof RemoteInput[]) {
-                inputs = (RemoteInput[]) tag;
-            }
-
-            if (inputs == null) {
-                return false;
-            }
-
-            RemoteInput input = null;
-
-            for (RemoteInput i : inputs) {
-                if (i.getAllowFreeFormInput()) {
-                    input = i;
-                }
-            }
-
-            if (input == null) {
-                return false;
-            }
-
-            ViewParent p = view.getParent();
-            RemoteInputView riv = null;
-            while (p != null) {
-                if (p instanceof View) {
-                    View pv = (View) p;
-                    if (pv.isRootNamespace()) {
-                        riv = findRemoteInputView(pv);
-                        break;
-                    }
-                }
-                p = p.getParent();
-            }
-            ExpandableNotificationRow row = null;
-            while (p != null) {
-                if (p instanceof ExpandableNotificationRow) {
-                    row = (ExpandableNotificationRow) p;
-                    break;
-                }
-                p = p.getParent();
-            }
-
-            if (row == null) {
-                return false;
-            }
-
-            row.setUserExpanded(true);
-
-            if (!mAllowLockscreenRemoteInput) {
-                final int userId = pendingIntent.getCreatorUserHandle().getIdentifier();
-                if (isLockscreenPublicMode(userId)) {
-                    onLockedRemoteInput(row, view);
-                    return true;
-                }
-                if (mUserManager.getUserInfo(userId).isManagedProfile()
-                        && mKeyguardManager.isDeviceLocked(userId)) {
-                    onLockedWorkRemoteInput(userId, row, view);
-                    return true;
-                }
-            }
-
-            if (riv == null) {
-                riv = findRemoteInputView(row.getPrivateLayout().getExpandedChild());
-                if (riv == null) {
-                    return false;
-                }
-                if (!row.getPrivateLayout().getExpandedChild().isShown()) {
-                    onMakeExpandedVisibleForRemoteInput(row, view);
-                    return true;
-                }
-            }
-
-            int width = view.getWidth();
-            if (view instanceof TextView) {
-                // Center the reveal on the text which might be off-center from the TextView
-                TextView tv = (TextView) view;
-                if (tv.getLayout() != null) {
-                    int innerWidth = (int) tv.getLayout().getLineWidth(0);
-                    innerWidth += tv.getCompoundPaddingLeft() + tv.getCompoundPaddingRight();
-                    width = Math.min(width, innerWidth);
-                }
-            }
-            int cx = view.getLeft() + width / 2;
-            int cy = view.getTop() + view.getHeight() / 2;
-            int w = riv.getWidth();
-            int h = riv.getHeight();
-            int r = Math.max(
-                    Math.max(cx + cy, cx + (h - cy)),
-                    Math.max((w - cx) + cy, (w - cx) + (h - cy)));
-
-            riv.setRevealParameters(cx, cy, r);
-            riv.setPendingIntent(pendingIntent);
-            riv.setRemoteInput(inputs, input);
-            riv.focusAnimated();
-
-            return true;
-        }
-
-        private RemoteInputView findRemoteInputView(View v) {
-            if (v == null) {
-                return null;
-            }
-            return (RemoteInputView) v.findViewWithTag(RemoteInputView.VIEW_TAG);
-        }
-    };
-
-    private final BroadcastReceiver mBaseBroadcastReceiver = new BroadcastReceiver() {
+    private final BroadcastReceiver mBannerActionBroadcastReceiver = new BroadcastReceiver() {
         @Override
         public void onReceive(Context context, Intent intent) {
             String action = intent.getAction();
-            if (Intent.ACTION_USER_SWITCHED.equals(action)) {
-                mCurrentUserId = intent.getIntExtra(Intent.EXTRA_USER_HANDLE, -1);
-                updateCurrentProfilesCache();
-                Log.v(TAG, "userId " + mCurrentUserId + " is in the house");
-
-                updateLockscreenNotificationSetting();
-
-                userSwitched(mCurrentUserId);
-            } else if (Intent.ACTION_USER_ADDED.equals(action)) {
-                updateCurrentProfilesCache();
-            } else if (Intent.ACTION_USER_UNLOCKED.equals(action)) {
-                // Start the overview connection to the launcher service
-                Dependency.get(OverviewProxyService.class).startConnectionToCurrentUser();
-            } else if (Intent.ACTION_USER_PRESENT.equals(action)) {
-                List<ActivityManager.RecentTaskInfo> recentTask = null;
-                try {
-                    recentTask = ActivityManager.getService().getRecentTasks(1,
-                            ActivityManager.RECENT_WITH_EXCLUDED,
-                            mCurrentUserId).getList();
-                } catch (RemoteException e) {
-                    // Abandon hope activity manager not running.
-                }
-                if (recentTask != null && recentTask.size() > 0) {
-                    UserInfo user = mUserManager.getUserInfo(recentTask.get(0).userId);
-                    if (user != null && user.isManagedProfile()) {
-                        Toast toast = Toast.makeText(mContext,
-                                R.string.managed_profile_foreground_toast,
-                                Toast.LENGTH_SHORT);
-                        TextView text = toast.getView().findViewById(android.R.id.message);
-                        text.setCompoundDrawablesRelativeWithIntrinsicBounds(
-                                R.drawable.stat_sys_managed_profile_status, 0, 0, 0);
-                        int paddingPx = mContext.getResources().getDimensionPixelSize(
-                                R.dimen.managed_profile_toast_padding);
-                        text.setCompoundDrawablePadding(paddingPx);
-                        toast.show();
-                    }
-                }
-            } else if (BANNER_ACTION_CANCEL.equals(action) || BANNER_ACTION_SETUP.equals(action)) {
+            if (BANNER_ACTION_CANCEL.equals(action) || BANNER_ACTION_SETUP.equals(action)) {
                 NotificationManager noMan = (NotificationManager)
                         mContext.getSystemService(Context.NOTIFICATION_SERVICE);
-                noMan.cancel(SystemMessage.NOTE_HIDDEN_NOTIFICATIONS);
+                noMan.cancel(com.android.internal.messages.nano.SystemMessageProto.SystemMessage.
+                        NOTE_HIDDEN_NOTIFICATIONS);
 
                 Settings.Secure.putInt(mContext.getContentResolver(),
                         Settings.Secure.SHOW_NOTE_ABOUT_NOTIFICATION_HIDING, 0);
@@ -5816,142 +4846,127 @@
 
                     );
                 }
-            } else if (NOTIFICATION_UNLOCKED_BY_WORK_CHALLENGE_ACTION.equals(action)) {
-                final IntentSender intentSender = intent.getParcelableExtra(Intent.EXTRA_INTENT);
-                final String notificationKey = intent.getStringExtra(Intent.EXTRA_INDEX);
-                if (intentSender != null) {
-                    try {
-                        mContext.startIntentSender(intentSender, null, 0, 0, 0);
-                    } catch (IntentSender.SendIntentException e) {
-                        /* ignore */
-                    }
-                }
-                if (notificationKey != null) {
-                    try {
-                        mBarService.onNotificationClick(notificationKey);
-                    } catch (RemoteException e) {
-                        /* ignore */
-                    }
-                }
             }
         }
     };
 
-    private final BroadcastReceiver mAllUsersReceiver = new BroadcastReceiver() {
-        @Override
-        public void onReceive(Context context, Intent intent) {
-            final String action = intent.getAction();
-            final int userId = intent.getIntExtra(Intent.EXTRA_USER_HANDLE, UserHandle.USER_NULL);
+    @Override
+    public void onNotificationClicked(StatusBarNotification sbn, ExpandableNotificationRow row) {
+        Notification notification = sbn.getNotification();
+        final PendingIntent intent = notification.contentIntent != null
+                ? notification.contentIntent
+                : notification.fullScreenIntent;
+        final String notificationKey = sbn.getKey();
 
-            if (DevicePolicyManager.ACTION_DEVICE_POLICY_MANAGER_STATE_CHANGED.equals(action) &&
-                    isCurrentProfile(getSendingUserId())) {
-                mUsersAllowingPrivateNotifications.clear();
-                updateLockscreenNotificationSetting();
-                updateNotifications();
-            } else if (Intent.ACTION_DEVICE_LOCKED_CHANGED.equals(action)) {
-                if (userId != mCurrentUserId && isCurrentProfile(userId)) {
-                    onWorkChallengeChanged();
+        final boolean afterKeyguardGone = intent.isActivity()
+                && PreviewInflater.wouldLaunchResolverActivity(mContext, intent.getIntent(),
+                mLockscreenUserManager.getCurrentUserId());
+        dismissKeyguardThenExecute(() -> {
+            // TODO: Some of this code may be able to move to NotificationEntryManager.
+            if (mHeadsUpManager != null && mHeadsUpManager.isHeadsUp(notificationKey)) {
+                // Release the HUN notification to the shade.
+
+                if (isPresenterFullyCollapsed()) {
+                    HeadsUpManager.setIsClickedNotification(row, true);
+                }
+                //
+                // In most cases, when FLAG_AUTO_CANCEL is set, the notification will
+                // become canceled shortly by NoMan, but we can't assume that.
+                mHeadsUpManager.releaseImmediately(notificationKey);
+            }
+            StatusBarNotification parentToCancel = null;
+            if (shouldAutoCancel(sbn) && mGroupManager.isOnlyChildInGroup(sbn)) {
+                StatusBarNotification summarySbn =
+                        mGroupManager.getLogicalGroupSummary(sbn).getStatusBarNotification();
+                if (shouldAutoCancel(summarySbn)) {
+                    parentToCancel = summarySbn;
                 }
             }
-        }
-    };
-
-    private final NotificationListenerWithPlugins mNotificationListener =
-            new NotificationListenerWithPlugins() {
-        @Override
-        public void onListenerConnected() {
-            if (DEBUG) Log.d(TAG, "onListenerConnected");
-            onPluginConnected();
-            final StatusBarNotification[] notifications = getActiveNotifications();
-            if (notifications == null) {
-                Log.w(TAG, "onListenerConnected unable to get active notifications.");
-                return;
-            }
-            final RankingMap currentRanking = getCurrentRanking();
-            mHandler.post(() -> {
-                for (StatusBarNotification sbn : notifications) {
-                    try {
-                        addNotification(sbn, currentRanking);
-                    } catch (InflationException e) {
-                        handleInflationException(sbn, e);
-                    }
+            final StatusBarNotification parentToCancelFinal = parentToCancel;
+            final Runnable runnable = () -> {
+                try {
+                    // The intent we are sending is for the application, which
+                    // won't have permission to immediately start an activity after
+                    // the user switches to home.  We know it is safe to do at this
+                    // point, so make sure new activity switches are now allowed.
+                    ActivityManager.getService().resumeAppSwitches();
+                } catch (RemoteException e) {
                 }
-            });
-        }
-
-        @Override
-        public void onNotificationPosted(final StatusBarNotification sbn,
-                final RankingMap rankingMap) {
-            if (DEBUG) Log.d(TAG, "onNotificationPosted: " + sbn);
-            if (sbn != null && !onPluginNotificationPosted(sbn, rankingMap)) {
-                mHandler.post(() -> {
-                    processForRemoteInput(sbn.getNotification());
-                    String key = sbn.getKey();
-                    mKeysKeptForRemoteInput.remove(key);
-                    boolean isUpdate = mNotificationData.get(key) != null;
-                    // In case we don't allow child notifications, we ignore children of
-                    // notifications that have a summary, since we're not going to show them
-                    // anyway. This is true also when the summary is canceled,
-                    // because children are automatically canceled by NoMan in that case.
-                    if (!ENABLE_CHILD_NOTIFICATIONS
-                            && mGroupManager.isChildInGroupWithSummary(sbn)) {
-                        if (DEBUG) {
-                            Log.d(TAG, "Ignoring group child due to existing summary: " + sbn);
+                if (intent != null) {
+                    // If we are launching a work activity and require to launch
+                    // separate work challenge, we defer the activity action and cancel
+                    // notification until work challenge is unlocked.
+                    if (intent.isActivity()) {
+                        final int userId = intent.getCreatorUserHandle().getIdentifier();
+                        if (mLockPatternUtils.isSeparateProfileChallengeEnabled(userId)
+                                && mKeyguardManager.isDeviceLocked(userId)) {
+                            // TODO(b/28935539): should allow certain activities to
+                            // bypass work challenge
+                            if (startWorkChallengeIfNecessary(userId, intent.getIntentSender(),
+                                    notificationKey)) {
+                                // Show work challenge, do not run PendingIntent and
+                                // remove notification
+                                return;
+                            }
                         }
+                    }
+                    try {
+                        intent.send(null, 0, null, null, null, null, getActivityOptions());
+                    } catch (PendingIntent.CanceledException e) {
+                        // the stack trace isn't very helpful here.
+                        // Just log the exception message.
+                        Log.w(TAG, "Sending contentIntent failed: " + e);
 
-                        // Remove existing notification to avoid stale data.
-                        if (isUpdate) {
-                            removeNotification(key, rankingMap);
+                        // TODO: Dismiss Keyguard.
+                    }
+                    if (intent.isActivity()) {
+                        mAssistManager.hideAssist();
+                    }
+                }
+
+                try {
+                    mBarService.onNotificationClick(notificationKey);
+                } catch (RemoteException ex) {
+                    // system process is dead if we're here.
+                }
+                if (parentToCancelFinal != null) {
+                    // We have to post it to the UI thread for synchronization
+                    mHandler.post(() -> {
+                        Runnable removeRunnable =
+                                () -> mEntryManager.performRemoveNotification(parentToCancelFinal);
+                        if (isCollapsing()) {
+                            // To avoid lags we're only performing the remove
+                            // after the shade was collapsed
+                            addPostCollapseAction(removeRunnable);
                         } else {
-                            mNotificationData.updateRanking(rankingMap);
+                            removeRunnable.run();
                         }
-                        return;
-                    }
-                    try {
-                        if (isUpdate) {
-                            updateNotification(sbn, rankingMap);
-                        } else {
-                            addNotification(sbn, rankingMap);
-                        }
-                    } catch (InflationException e) {
-                        handleInflationException(sbn, e);
-                    }
-                });
-            }
-        }
-
-        @Override
-        public void onNotificationRemoved(StatusBarNotification sbn,
-                final RankingMap rankingMap) {
-            if (DEBUG) Log.d(TAG, "onNotificationRemoved: " + sbn);
-            if (sbn != null && !onPluginNotificationRemoved(sbn, rankingMap)) {
-                final String key = sbn.getKey();
-                mHandler.post(() -> removeNotification(key, rankingMap));
-            }
-        }
-
-        @Override
-        public void onNotificationRankingUpdate(final RankingMap rankingMap) {
-            if (DEBUG) Log.d(TAG, "onRankingUpdate");
-            if (rankingMap != null) {
-                RankingMap r = onPluginRankingUpdate(rankingMap);
-                mHandler.post(() -> updateNotificationRanking(r));
-            }
-        }
-
-    };
-
-    private void updateCurrentProfilesCache() {
-        synchronized (mCurrentProfiles) {
-            mCurrentProfiles.clear();
-            if (mUserManager != null) {
-                for (UserInfo user : mUserManager.getProfiles(mCurrentUserId)) {
-                    mCurrentProfiles.put(user.id, user);
+                    });
                 }
+            };
+
+            if (mStatusBarKeyguardViewManager.isShowing()
+                    && mStatusBarKeyguardViewManager.isOccluded()) {
+                mStatusBarKeyguardViewManager.addAfterKeyguardGoneRunnable(runnable);
+            } else {
+                new Thread(runnable).start();
             }
-        }
+
+            if (!mNotificationPanel.isFullyCollapsed()) {
+                // close the shade if it was open
+                animateCollapsePanels(CommandQueue.FLAG_EXCLUDE_RECENTS_PANEL, true /* force */,
+                        true /* delayed */);
+                visibilityChanged(false);
+
+                return true;
+            } else {
+                return false;
+            }
+        }, afterKeyguardGone);
     }
 
+    protected NotificationListener mNotificationListener;
+
     protected void notifyUserAboutHiddenNotifications() {
         if (0 != Settings.Secure.getInt(mContext.getContentResolver(),
                 Settings.Secure.SHOW_NOTE_ABOUT_NOTIFICATION_HIDING, 1)) {
@@ -6006,27 +5021,9 @@
         final int notificationUserId = n.getUserId();
         if (DEBUG && MULTIUSER_DEBUG) {
             Log.v(TAG, String.format("%s: current userid: %d, notification userid: %d", n,
-                    mCurrentUserId, notificationUserId));
+                    mLockscreenUserManager.getCurrentUserId(), notificationUserId));
         }
-        return isCurrentProfile(notificationUserId);
-    }
-
-    protected void setNotificationShown(StatusBarNotification n) {
-        setNotificationsShown(new String[]{n.getKey()});
-    }
-
-    protected void setNotificationsShown(String[] keys) {
-        try {
-            mNotificationListener.setNotificationsShown(keys);
-        } catch (RuntimeException e) {
-            Log.d(TAG, "failed setNotificationsShown: ", e);
-        }
-    }
-
-    protected boolean isCurrentProfile(int userId) {
-        synchronized (mCurrentProfiles) {
-            return userId == UserHandle.USER_ALL || mCurrentProfiles.get(userId) != null;
-        }
+        return mLockscreenUserManager.isCurrentProfile(notificationUserId);
     }
 
     @Override
@@ -6058,15 +5055,15 @@
         }
     }
 
-    protected ExpandableNotificationRow.LongPressListener getNotificationLongClicker() {
-        return (v, x, y, item) -> mGutsManager.openGuts(v, x, y, item);
-    }
-
     @Override
     public void toggleSplitScreen() {
         toggleSplitScreenMode(-1 /* metricsDockAction */, -1 /* metricsUndockAction */);
     }
 
+    void awakenDreams() {
+        SystemServicesProxy.getInstance(mContext).awakenDreamsAsync();
+    }
+
     @Override
     public void preloadRecentApps() {
         int msg = MSG_PRELOAD_RECENT_APPS;
@@ -6115,89 +5112,14 @@
         KeyboardShortcuts.dismiss();
     }
 
-    /**
-     * Save the current "public" (locked and secure) state of the lockscreen.
-     */
-    public void setLockscreenPublicMode(boolean publicMode, int userId) {
-        mLockscreenPublicMode.put(userId, publicMode);
-    }
-
-    public boolean isLockscreenPublicMode(int userId) {
-        if (userId == UserHandle.USER_ALL) {
-            return mLockscreenPublicMode.get(mCurrentUserId, false);
-        }
-        return mLockscreenPublicMode.get(userId, false);
-    }
-
-    /**
-     * Has the given user chosen to allow notifications to be shown even when the lockscreen is in
-     * "public" (secure & locked) mode?
-     */
-    public boolean userAllowsNotificationsInPublic(int userHandle) {
-        if (userHandle == UserHandle.USER_ALL) {
-            return true;
-        }
-
-        if (mUsersAllowingNotifications.indexOfKey(userHandle) < 0) {
-            final boolean allowed = 0 != Settings.Secure.getIntForUser(
-                    mContext.getContentResolver(),
-                    Settings.Secure.LOCK_SCREEN_SHOW_NOTIFICATIONS, 0, userHandle);
-            mUsersAllowingNotifications.append(userHandle, allowed);
-            return allowed;
-        }
-
-        return mUsersAllowingNotifications.get(userHandle);
-    }
-
-    /**
-     * Has the given user chosen to allow their private (full) notifications to be shown even
-     * when the lockscreen is in "public" (secure & locked) mode?
-     */
-    public boolean userAllowsPrivateNotificationsInPublic(int userHandle) {
-        if (userHandle == UserHandle.USER_ALL) {
-            return true;
-        }
-
-        if (mUsersAllowingPrivateNotifications.indexOfKey(userHandle) < 0) {
-            final boolean allowedByUser = 0 != Settings.Secure.getIntForUser(
-                    mContext.getContentResolver(),
-                    Settings.Secure.LOCK_SCREEN_ALLOW_PRIVATE_NOTIFICATIONS, 0, userHandle);
-            final boolean allowedByDpm = adminAllowsUnredactedNotifications(userHandle);
-            final boolean allowed = allowedByUser && allowedByDpm;
-            mUsersAllowingPrivateNotifications.append(userHandle, allowed);
-            return allowed;
-        }
-
-        return mUsersAllowingPrivateNotifications.get(userHandle);
-    }
-
-    private boolean adminAllowsUnredactedNotifications(int userHandle) {
-        if (userHandle == UserHandle.USER_ALL) {
-            return true;
-        }
-        final int dpmFlags = mDevicePolicyManager.getKeyguardDisabledFeatures(null /* admin */,
-                    userHandle);
-        return (dpmFlags & DevicePolicyManager.KEYGUARD_DISABLE_UNREDACTED_NOTIFICATIONS) == 0;
-    }
-
-    /**
-     * Returns true if we're on a secure lockscreen and the user wants to hide notification data.
-     * If so, notifications should be hidden.
-     */
     @Override  // NotificationData.Environment
     public boolean shouldHideNotifications(int userId) {
-        return isLockscreenPublicMode(userId) && !userAllowsNotificationsInPublic(userId)
-                || (userId != mCurrentUserId && shouldHideNotifications(mCurrentUserId));
+        return mLockscreenUserManager.shouldHideNotifications(userId);
     }
 
-    /**
-     * Returns true if we're on a secure lockscreen and the user wants to hide notifications via
-     * package-specific override.
-     */
     @Override // NotificationDate.Environment
     public boolean shouldHideNotifications(String key) {
-        return isLockscreenPublicMode(mCurrentUserId)
-                && mNotificationData.getVisibilityOverride(key) == Notification.VISIBILITY_SECRET;
+        return mLockscreenUserManager.shouldHideNotifications(key);
     }
 
     /**
@@ -6205,7 +5127,7 @@
      */
     @Override  // NotificationData.Environment
     public boolean isSecurelyLocked(int userId) {
-        return isLockscreenPublicMode(userId);
+        return mLockscreenUserManager.isLockscreenPublicMode(userId);
     }
 
     /**
@@ -6219,146 +5141,10 @@
         if (mState == StatusBarState.KEYGUARD) {
             // Since the number of notifications is determined based on the height of the view, we
             // need to update them.
-            int maxBefore = getMaxKeyguardNotifications(false /* recompute */);
-            int maxNotifications = getMaxKeyguardNotifications(true /* recompute */);
+            int maxBefore = getMaxNotificationsWhileLocked(false /* recompute */);
+            int maxNotifications = getMaxNotificationsWhileLocked(true /* recompute */);
             if (maxBefore != maxNotifications) {
-                updateRowStates();
-            }
-        }
-    }
-
-    protected void inflateViews(Entry entry, ViewGroup parent) {
-        PackageManager pmUser = getPackageManagerForUser(mContext,
-                entry.notification.getUser().getIdentifier());
-
-        final StatusBarNotification sbn = entry.notification;
-        if (entry.row != null) {
-            entry.reset();
-            updateNotification(entry, pmUser, sbn, entry.row);
-        } else {
-            new RowInflaterTask().inflate(mContext, parent, entry,
-                    row -> {
-                        bindRow(entry, pmUser, sbn, row);
-                        updateNotification(entry, pmUser, sbn, row);
-                    });
-        }
-
-    }
-
-    private void bindRow(Entry entry, PackageManager pmUser,
-            StatusBarNotification sbn, ExpandableNotificationRow row) {
-        row.setExpansionLogger(this, entry.notification.getKey());
-        row.setGroupManager(mGroupManager);
-        row.setHeadsUpManager(mHeadsUpManager);
-        row.setAboveShelfChangedListener(mAboveShelfObserver);
-        row.setRemoteInputController(mRemoteInputController);
-        row.setOnExpandClickListener(this);
-        row.setRemoteViewClickHandler(mOnClickHandler);
-        row.setInflationCallback(this);
-        row.setSecureStateProvider(this::isKeyguardCurrentlySecure);
-        row.setLongPressListener(getNotificationLongClicker());
-
-        // Get the app name.
-        // Note that Notification.Builder#bindHeaderAppName has similar logic
-        // but since this field is used in the guts, it must be accurate.
-        // Therefore we will only show the application label, or, failing that, the
-        // package name. No substitutions.
-        final String pkg = sbn.getPackageName();
-        String appname = pkg;
-        try {
-            final ApplicationInfo info = pmUser.getApplicationInfo(pkg,
-                    PackageManager.MATCH_UNINSTALLED_PACKAGES
-                            | PackageManager.MATCH_DISABLED_COMPONENTS);
-            if (info != null) {
-                appname = String.valueOf(pmUser.getApplicationLabel(info));
-            }
-        } catch (NameNotFoundException e) {
-            // Do nothing
-        }
-        row.setAppName(appname);
-        row.setOnDismissRunnable(() ->
-                performRemoveNotification(row.getStatusBarNotification()));
-        row.setDescendantFocusability(ViewGroup.FOCUS_BLOCK_DESCENDANTS);
-        if (ENABLE_REMOTE_INPUT) {
-            row.setDescendantFocusability(ViewGroup.FOCUS_BEFORE_DESCENDANTS);
-        }
-    }
-
-    private void updateNotification(Entry entry, PackageManager pmUser,
-            StatusBarNotification sbn, ExpandableNotificationRow row) {
-        row.setNeedsRedaction(needsRedaction(entry));
-        boolean isLowPriority = mNotificationData.isAmbient(sbn.getKey());
-        boolean isUpdate = mNotificationData.get(entry.key) != null;
-        boolean wasLowPriority = row.isLowPriority();
-        row.setIsLowPriority(isLowPriority);
-        row.setLowPriorityStateUpdated(isUpdate && (wasLowPriority != isLowPriority));
-        // bind the click event to the content area
-        mNotificationClicker.register(row, sbn);
-
-        // Extract target SDK version.
-        try {
-            ApplicationInfo info = pmUser.getApplicationInfo(sbn.getPackageName(), 0);
-            entry.targetSdk = info.targetSdkVersion;
-        } catch (NameNotFoundException ex) {
-            Log.e(TAG, "Failed looking up ApplicationInfo for " + sbn.getPackageName(), ex);
-        }
-        row.setLegacy(entry.targetSdk >= Build.VERSION_CODES.GINGERBREAD
-                && entry.targetSdk < Build.VERSION_CODES.LOLLIPOP);
-        entry.setIconTag(R.id.icon_is_pre_L, entry.targetSdk < Build.VERSION_CODES.LOLLIPOP);
-        entry.autoRedacted = entry.notification.getNotification().publicVersion == null;
-
-        entry.row = row;
-        entry.row.setOnActivatedListener(this);
-
-        boolean useIncreasedCollapsedHeight = mMessagingUtil.isImportantMessaging(sbn,
-                mNotificationData.getImportance(sbn.getKey()));
-        boolean useIncreasedHeadsUp = useIncreasedCollapsedHeight && mPanelExpanded;
-        row.setUseIncreasedCollapsedHeight(useIncreasedCollapsedHeight);
-        row.setUseIncreasedHeadsUpHeight(useIncreasedHeadsUp);
-        row.updateNotification(entry);
-    }
-
-    /**
-     * Adds RemoteInput actions from the WearableExtender; to be removed once more apps support this
-     * via first-class API.
-     *
-     * TODO: Remove once enough apps specify remote inputs on their own.
-     */
-    private void processForRemoteInput(Notification n) {
-        if (!ENABLE_REMOTE_INPUT) return;
-
-        if (n.extras != null && n.extras.containsKey("android.wearable.EXTENSIONS") &&
-                (n.actions == null || n.actions.length == 0)) {
-            Notification.Action viableAction = null;
-            Notification.WearableExtender we = new Notification.WearableExtender(n);
-
-            List<Notification.Action> actions = we.getActions();
-            final int numActions = actions.size();
-
-            for (int i = 0; i < numActions; i++) {
-                Notification.Action action = actions.get(i);
-                if (action == null) {
-                    continue;
-                }
-                RemoteInput[] remoteInputs = action.getRemoteInputs();
-                if (remoteInputs == null) {
-                    continue;
-                }
-                for (RemoteInput ri : remoteInputs) {
-                    if (ri.getAllowFreeFormInput()) {
-                        viableAction = action;
-                        break;
-                    }
-                }
-                if (viableAction != null) {
-                    break;
-                }
-            }
-
-            if (viableAction != null) {
-                Notification.Builder rebuilder = Notification.Builder.recoverBuilder(mContext, n);
-                rebuilder.setActions(viableAction);
-                rebuilder.build(); // will rewrite n
+                mViewHierarchyManager.updateRowStates();
             }
         }
     }
@@ -6368,7 +5154,7 @@
 
         final boolean afterKeyguardGone = intent.isActivity()
                 && PreviewInflater.wouldLaunchResolverActivity(mContext, intent.getIntent(),
-                mCurrentUserId);
+                mLockscreenUserManager.getCurrentUserId());
         dismissKeyguardThenExecute(() -> {
             new Thread(() -> {
                 try {
@@ -6406,166 +5192,15 @@
         }, afterKeyguardGone);
     }
 
-
-    private final class NotificationClicker implements View.OnClickListener {
-
-        @Override
-        public void onClick(final View v) {
-            if (!(v instanceof ExpandableNotificationRow)) {
-                Log.e(TAG, "NotificationClicker called on a view that is not a notification row.");
-                return;
-            }
-
-            wakeUpIfDozing(SystemClock.uptimeMillis(), v);
-
-            final ExpandableNotificationRow row = (ExpandableNotificationRow) v;
-            final StatusBarNotification sbn = row.getStatusBarNotification();
-            if (sbn == null) {
-                Log.e(TAG, "NotificationClicker called on an unclickable notification,");
-                return;
-            }
-
-            // Check if the notification is displaying the menu, if so slide notification back
-            if (row.getProvider() != null && row.getProvider().isMenuVisible()) {
-                row.animateTranslateNotification(0);
-                return;
-            }
-
-            Notification notification = sbn.getNotification();
-            final PendingIntent intent = notification.contentIntent != null
-                    ? notification.contentIntent
-                    : notification.fullScreenIntent;
-            final String notificationKey = sbn.getKey();
-
-            // Mark notification for one frame.
-            row.setJustClicked(true);
-            DejankUtils.postAfterTraversal(() -> row.setJustClicked(false));
-
-            final boolean afterKeyguardGone = intent.isActivity()
-                    && PreviewInflater.wouldLaunchResolverActivity(mContext, intent.getIntent(),
-                            mCurrentUserId);
-            dismissKeyguardThenExecute(() -> {
-                if (mHeadsUpManager != null && mHeadsUpManager.isHeadsUp(notificationKey)) {
-                    // Release the HUN notification to the shade.
-
-                    if (isPresenterFullyCollapsed()) {
-                        HeadsUpManager.setIsClickedNotification(row, true);
-                    }
-                    //
-                    // In most cases, when FLAG_AUTO_CANCEL is set, the notification will
-                    // become canceled shortly by NoMan, but we can't assume that.
-                    mHeadsUpManager.releaseImmediately(notificationKey);
-                }
-                StatusBarNotification parentToCancel = null;
-                if (shouldAutoCancel(sbn) && mGroupManager.isOnlyChildInGroup(sbn)) {
-                    StatusBarNotification summarySbn =
-                            mGroupManager.getLogicalGroupSummary(sbn).getStatusBarNotification();
-                    if (shouldAutoCancel(summarySbn)) {
-                        parentToCancel = summarySbn;
-                    }
-                }
-                final StatusBarNotification parentToCancelFinal = parentToCancel;
-                final Runnable runnable = () -> {
-                    try {
-                        // The intent we are sending is for the application, which
-                        // won't have permission to immediately start an activity after
-                        // the user switches to home.  We know it is safe to do at this
-                        // point, so make sure new activity switches are now allowed.
-                        ActivityManager.getService().resumeAppSwitches();
-                    } catch (RemoteException e) {
-                    }
-                    if (intent != null) {
-                        // If we are launching a work activity and require to launch
-                        // separate work challenge, we defer the activity action and cancel
-                        // notification until work challenge is unlocked.
-                        if (intent.isActivity()) {
-                            final int userId = intent.getCreatorUserHandle().getIdentifier();
-                            if (mLockPatternUtils.isSeparateProfileChallengeEnabled(userId)
-                                    && mKeyguardManager.isDeviceLocked(userId)) {
-                                // TODO(b/28935539): should allow certain activities to
-                                // bypass work challenge
-                                if (startWorkChallengeIfNecessary(userId, intent.getIntentSender(),
-                                        notificationKey)) {
-                                    // Show work challenge, do not run PendingIntent and
-                                    // remove notification
-                                    return;
-                                }
-                            }
-                        }
-                        try {
-                            intent.send(null, 0, null, null, null, null, getActivityOptions());
-                        } catch (PendingIntent.CanceledException e) {
-                            // the stack trace isn't very helpful here.
-                            // Just log the exception message.
-                            Log.w(TAG, "Sending contentIntent failed: " + e);
-
-                            // TODO: Dismiss Keyguard.
-                        }
-                        if (intent.isActivity()) {
-                            mAssistManager.hideAssist();
-                        }
-                    }
-
-                    try {
-                        mBarService.onNotificationClick(notificationKey);
-                    } catch (RemoteException ex) {
-                        // system process is dead if we're here.
-                    }
-                    if (parentToCancelFinal != null) {
-                        // We have to post it to the UI thread for synchronization
-                        mHandler.post(() -> {
-                            Runnable removeRunnable =
-                                    () -> performRemoveNotification(parentToCancelFinal);
-                            if (isCollapsing()) {
-                                // To avoid lags we're only performing the remove
-                                // after the shade was collapsed
-                                addPostCollapseAction(removeRunnable);
-                            } else {
-                                removeRunnable.run();
-                            }
-                        });
-                    }
-                };
-
-                if (mStatusBarKeyguardViewManager.isShowing()
-                        && mStatusBarKeyguardViewManager.isOccluded()) {
-                    mStatusBarKeyguardViewManager.addAfterKeyguardGoneRunnable(runnable);
-                } else {
-                    new Thread(runnable).start();
-                }
-
-                if (!mNotificationPanel.isFullyCollapsed()) {
-                    // close the shade if it was open
-                    animateCollapsePanels(CommandQueue.FLAG_EXCLUDE_RECENTS_PANEL, true /* force */,
-                            true /* delayed */);
-                    visibilityChanged(false);
-
-                    return true;
-                } else {
-                    return false;
-                }
-            }, afterKeyguardGone);
+    private boolean shouldAutoCancel(StatusBarNotification sbn) {
+        int flags = sbn.getNotification().flags;
+        if ((flags & Notification.FLAG_AUTO_CANCEL) != Notification.FLAG_AUTO_CANCEL) {
+            return false;
         }
-
-        private boolean shouldAutoCancel(StatusBarNotification sbn) {
-            int flags = sbn.getNotification().flags;
-            if ((flags & Notification.FLAG_AUTO_CANCEL) != Notification.FLAG_AUTO_CANCEL) {
-                return false;
-            }
-            if ((flags & Notification.FLAG_FOREGROUND_SERVICE) != 0) {
-                return false;
-            }
-            return true;
+        if ((flags & Notification.FLAG_FOREGROUND_SERVICE) != 0) {
+            return false;
         }
-
-        public void register(ExpandableNotificationRow row, StatusBarNotification sbn) {
-            Notification notification = sbn.getNotification();
-            if (notification.contentIntent != null || notification.fullScreenIntent != null) {
-                row.setOnClickListener(this);
-            } else {
-                row.setOnClickListener(null);
-            }
-        }
+        return true;
     }
 
     protected Bundle getActivityOptions() {
@@ -6608,125 +5243,10 @@
     }
 
     /**
-     * Cancel this notification and tell the StatusBarManagerService / NotificationManagerService
-     * about the failure.
-     *
-     * WARNING: this will call back into us.  Don't hold any locks.
-     */
-    void handleNotificationError(StatusBarNotification n, String message) {
-        removeNotification(n.getKey(), null);
-        try {
-            mBarService.onNotificationError(n.getPackageName(), n.getTag(), n.getId(), n.getUid(),
-                    n.getInitialPid(), message, n.getUserId());
-        } catch (RemoteException ex) {
-            // The end is nigh.
-        }
-    }
-
-    protected StatusBarNotification removeNotificationViews(String key, RankingMap ranking) {
-        NotificationData.Entry entry = mNotificationData.remove(key, ranking);
-        if (entry == null) {
-            Log.w(TAG, "removeNotification for unknown key: " + key);
-            return null;
-        }
-        updateNotifications();
-        Dependency.get(LeakDetector.class).trackGarbage(entry);
-        return entry.notification;
-    }
-
-    protected NotificationData.Entry createNotificationViews(StatusBarNotification sbn)
-            throws InflationException {
-        if (DEBUG) {
-            Log.d(TAG, "createNotificationViews(notification=" + sbn);
-        }
-        NotificationData.Entry entry = new NotificationData.Entry(sbn);
-        Dependency.get(LeakDetector.class).trackInstance(entry);
-        entry.createIcons(mContext, sbn);
-        // Construct the expanded view.
-        inflateViews(entry, mStackScroller);
-        return entry;
-    }
-
-    protected void addNotificationViews(Entry entry) {
-        if (entry == null) {
-            return;
-        }
-        // Add the expanded view and icon.
-        mNotificationData.add(entry);
-        updateNotifications();
-    }
-
-    /**
      * Updates expanded, dimmed and locked states of notification rows.
      */
-    protected void updateRowStates() {
-        final int N = mStackScroller.getChildCount();
-
-        int visibleNotifications = 0;
-        boolean onKeyguard = mState == StatusBarState.KEYGUARD;
-        int maxNotifications = -1;
-        if (onKeyguard) {
-            maxNotifications = getMaxKeyguardNotifications(true /* recompute */);
-        }
-        mStackScroller.setMaxDisplayedNotifications(maxNotifications);
-        Stack<ExpandableNotificationRow> stack = new Stack<>();
-        for (int i = N - 1; i >= 0; i--) {
-            View child = mStackScroller.getChildAt(i);
-            if (!(child instanceof ExpandableNotificationRow)) {
-                continue;
-            }
-            stack.push((ExpandableNotificationRow) child);
-        }
-        while(!stack.isEmpty()) {
-            ExpandableNotificationRow row = stack.pop();
-            NotificationData.Entry entry = row.getEntry();
-            boolean isChildNotification =
-                    mGroupManager.isChildInGroupWithSummary(entry.notification);
-
-            row.setOnKeyguard(onKeyguard);
-
-            if (!onKeyguard) {
-                // If mAlwaysExpandNonGroupedNotification is false, then only expand the
-                // very first notification and if it's not a child of grouped notifications.
-                row.setSystemExpanded(mAlwaysExpandNonGroupedNotification
-                        || (visibleNotifications == 0 && !isChildNotification
-                        && !row.isLowPriority()));
-            }
-
-            entry.row.setShowAmbient(isDozing());
-            int userId = entry.notification.getUserId();
-            boolean suppressedSummary = mGroupManager.isSummaryOfSuppressedGroup(
-                    entry.notification) && !entry.row.isRemoved();
-            boolean showOnKeyguard = shouldShowOnKeyguard(entry.notification);
-            if (suppressedSummary
-                    || (isLockscreenPublicMode(userId) && !mShowLockscreenNotifications)
-                    || (onKeyguard && !showOnKeyguard)) {
-                entry.row.setVisibility(View.GONE);
-            } else {
-                boolean wasGone = entry.row.getVisibility() == View.GONE;
-                if (wasGone) {
-                    entry.row.setVisibility(View.VISIBLE);
-                }
-                if (!isChildNotification && !entry.row.isRemoved()) {
-                    if (wasGone) {
-                        // notify the scroller of a child addition
-                        mStackScroller.generateAddAnimation(entry.row,
-                                !showOnKeyguard /* fromMoreCard */);
-                    }
-                    visibleNotifications++;
-                }
-            }
-            if (row.isSummaryWithChildren()) {
-                List<ExpandableNotificationRow> notificationChildren =
-                        row.getNotificationChildren();
-                int size = notificationChildren.size();
-                for (int i = size - 1; i >= 0; i--) {
-                    stack.push(notificationChildren.get(i));
-                }
-            }
-        }
-        mNotificationPanel.setNoVisibleNotifications(visibleNotifications == 0);
-
+    @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
@@ -6749,191 +5269,10 @@
         mScrimController.setNotificationCount(mStackScroller.getNotGoneChildCount());
     }
 
-    public boolean shouldShowOnKeyguard(StatusBarNotification sbn) {
-        return mShowLockscreenNotifications
-                && ((mDisabled2 & DISABLE2_NOTIFICATION_SHADE) == 0)
-                && !mNotificationData.isAmbient(sbn.getKey());
-    }
-
-    // extended in StatusBar
-    protected void setShowLockscreenNotifications(boolean show) {
-        mShowLockscreenNotifications = show;
-    }
-
-    protected void setLockScreenAllowRemoteInput(boolean allowLockscreenRemoteInput) {
-        mAllowLockscreenRemoteInput = allowLockscreenRemoteInput;
-    }
-
-    protected void updateLockscreenNotificationSetting() {
-        final boolean show = Settings.Secure.getIntForUser(mContext.getContentResolver(),
-                Settings.Secure.LOCK_SCREEN_SHOW_NOTIFICATIONS,
-                1,
-                mCurrentUserId) != 0;
-        final int dpmFlags = mDevicePolicyManager.getKeyguardDisabledFeatures(
-                null /* admin */, mCurrentUserId);
-        final boolean allowedByDpm = (dpmFlags
-                & DevicePolicyManager.KEYGUARD_DISABLE_SECURE_NOTIFICATIONS) == 0;
-
-        setShowLockscreenNotifications(show && allowedByDpm);
-
-        if (ENABLE_LOCK_SCREEN_ALLOW_REMOTE_INPUT) {
-            final boolean remoteInput = Settings.Secure.getIntForUser(mContext.getContentResolver(),
-                    Settings.Secure.LOCK_SCREEN_ALLOW_REMOTE_INPUT,
-                    0,
-                    mCurrentUserId) != 0;
-            final boolean remoteInputDpm =
-                    (dpmFlags & DevicePolicyManager.KEYGUARD_DISABLE_REMOTE_INPUT) == 0;
-
-            setLockScreenAllowRemoteInput(remoteInput && remoteInputDpm);
-        } else {
-            setLockScreenAllowRemoteInput(false);
-        }
-    }
-
-    public void updateNotification(StatusBarNotification notification, RankingMap ranking)
-            throws InflationException {
-        if (DEBUG) Log.d(TAG, "updateNotification(" + notification + ")");
-
-        final String key = notification.getKey();
-        abortExistingInflation(key);
-        Entry entry = mNotificationData.get(key);
-        if (entry == null) {
-            return;
-        }
-        mHeadsUpEntriesToRemoveOnSwitch.remove(entry);
-        mRemoteInputEntriesToRemoveOnCollapse.remove(entry);
-        if (key.equals(mGutsManager.getKeyToRemoveOnGutsClosed())) {
-            mGutsManager.setKeyToRemoveOnGutsClosed(null);
-            Log.w(TAG, "Notification that was kept for guts was updated. " + key);
-        }
-
-        Notification n = notification.getNotification();
-        mNotificationData.updateRanking(ranking);
-
-        final StatusBarNotification oldNotification = entry.notification;
-        entry.notification = notification;
-        mGroupManager.onEntryUpdated(entry, oldNotification);
-
-        entry.updateIcons(mContext, notification);
-        inflateViews(entry, mStackScroller);
-
-        mForegroundServiceController.updateNotification(notification,
-                mNotificationData.getImportance(key));
-
-        boolean shouldPeek = shouldPeek(entry, notification);
-        boolean alertAgain = alertAgain(entry, n);
-
-        updateHeadsUp(key, entry, shouldPeek, alertAgain);
-        updateNotifications();
-
-        if (!notification.isClearable()) {
-            // The user may have performed a dismiss action on the notification, since it's
-            // not clearable we should snap it back.
-            mStackScroller.snapViewIfNeeded(entry.row);
-        }
-
-        if (DEBUG) {
-            // Is this for you?
-            boolean isForCurrentUser = isNotificationForCurrentProfiles(notification);
-            Log.d(TAG, "notification is " + (isForCurrentUser ? "" : "not ") + "for you");
-        }
-
-        setAreThereNotifications();
-    }
-
     protected void notifyHeadsUpGoingToSleep() {
         maybeEscalateHeadsUp();
     }
 
-    private boolean alertAgain(Entry oldEntry, Notification newNotification) {
-        return oldEntry == null || !oldEntry.hasInterrupted()
-                || (newNotification.flags & Notification.FLAG_ONLY_ALERT_ONCE) == 0;
-    }
-
-    protected boolean shouldPeek(Entry entry) {
-        return shouldPeek(entry, entry.notification);
-    }
-
-    protected boolean shouldPeek(Entry entry, StatusBarNotification sbn) {
-        if (!mUseHeadsUp || isDeviceInVrMode()) {
-            if (DEBUG) Log.d(TAG, "No peeking: no huns or vr mode");
-            return false;
-        }
-
-        if (mNotificationData.shouldFilterOut(sbn)) {
-            if (DEBUG) Log.d(TAG, "No peeking: filtered notification: " + sbn.getKey());
-            return false;
-        }
-
-        boolean inUse = mPowerManager.isScreenOn() && !mSystemServicesProxy.isDreaming();
-
-        if (!inUse && !isDozing()) {
-            if (DEBUG) {
-                Log.d(TAG, "No peeking: not in use: " + sbn.getKey());
-            }
-            return false;
-        }
-
-        if (!isDozing() && mNotificationData.shouldSuppressScreenOn(sbn.getKey())) {
-            if (DEBUG) Log.d(TAG, "No peeking: suppressed by DND: " + sbn.getKey());
-            return false;
-        }
-
-        if (isDozing() && mNotificationData.shouldSuppressScreenOff(sbn.getKey())) {
-            if (DEBUG) Log.d(TAG, "No peeking: suppressed by DND: " + sbn.getKey());
-            return false;
-        }
-
-        if (entry.hasJustLaunchedFullScreenIntent()) {
-            if (DEBUG) Log.d(TAG, "No peeking: recent fullscreen: " + sbn.getKey());
-            return false;
-        }
-
-        if (isSnoozedPackage(sbn)) {
-            if (DEBUG) Log.d(TAG, "No peeking: snoozed package: " + sbn.getKey());
-            return false;
-        }
-
-        // Allow peeking for DEFAULT notifications only if we're on Ambient Display.
-        int importanceLevel = isDozing() ? NotificationManager.IMPORTANCE_DEFAULT
-                : NotificationManager.IMPORTANCE_HIGH;
-        if (mNotificationData.getImportance(sbn.getKey()) < importanceLevel) {
-            if (DEBUG) Log.d(TAG, "No peeking: unimportant notification: " + sbn.getKey());
-            return false;
-        }
-
-        if (mIsOccluded && !isDozing()) {
-            boolean devicePublic = isLockscreenPublicMode(mCurrentUserId);
-            boolean userPublic = devicePublic || isLockscreenPublicMode(sbn.getUserId());
-            boolean needsRedaction = needsRedaction(entry);
-            if (userPublic && needsRedaction) {
-                return false;
-            }
-        }
-
-        if (sbn.getNotification().fullScreenIntent != null) {
-            if (mAccessibilityManager.isTouchExplorationEnabled()) {
-                if (DEBUG) Log.d(TAG, "No peeking: accessible fullscreen: " + sbn.getKey());
-                return false;
-            } else if (mDozing) {
-                // We never want heads up when we are dozing.
-                return false;
-            } else {
-                // we only allow head-up on the lockscreen if it doesn't have a fullscreen intent
-                return !mStatusBarKeyguardViewManager.isShowing()
-                        || mStatusBarKeyguardViewManager.isOccluded();
-            }
-        }
-
-        // Don't peek notifications that are suppressed due to group alert behavior
-        if (sbn.isGroup() && sbn.getNotification().suppressAlertingDueToGrouping()) {
-            if (DEBUG) Log.d(TAG, "No peeking: suppressed due to group alert behavior");
-            return false;
-        }
-
-        return true;
-    }
-
     /**
      * @return Whether the security bouncer from Keyguard is showing.
      */
@@ -6963,17 +5302,6 @@
         return contextForUser.getPackageManager();
     }
 
-    @Override
-    public void logNotificationExpansion(String key, boolean userAction, boolean expanded) {
-        mUiOffloadThread.submit(() -> {
-            try {
-                mBarService.onNotificationExpansionChanged(key, userAction, expanded);
-            } catch (RemoteException e) {
-                // Ignore.
-            }
-        });
-    }
-
     public boolean isKeyguardSecure() {
         if (mStatusBarKeyguardViewManager == null) {
             // startKeyguard() hasn't been called yet, so we don't know.
@@ -7021,26 +5349,16 @@
     }
 
     @Override
-    public int getCurrentUserId() {
-        return mCurrentUserId;
+    public Handler getHandler() {
+        return mHandler;
     }
 
-    @Override
-    public NotificationData getNotificationData() {
-        return mNotificationData;
-    }
-
-    @Override
-    public RankingMap getLatestRankingMap() {
-        return mLatestRankingMap;
-    }
-
-    final NotificationInfo.CheckSaveListener mCheckSaveListener =
+    private final NotificationInfo.CheckSaveListener mCheckSaveListener =
             (Runnable saveImportance, StatusBarNotification sbn) -> {
                 // If the user has security enabled, show challenge if the setting is changed.
-                if (isLockscreenPublicMode(sbn.getUser().getIdentifier()) && (
-                        mState == StatusBarState.KEYGUARD
-                                || mState == StatusBarState.SHADE_LOCKED)) {
+                if (mLockscreenUserManager.isLockscreenPublicMode(sbn.getUser().getIdentifier())
+                        && (mState == StatusBarState.KEYGUARD ||
+                                mState == StatusBarState.SHADE_LOCKED)) {
                     onLockedNotificationImportanceChange(() -> {
                         saveImportance.run();
                         return true;
diff --git a/com/android/systemui/statusbar/phone/StatusBarKeyguardViewManager.java b/com/android/systemui/statusbar/phone/StatusBarKeyguardViewManager.java
index ef05bbb..8504d8e 100644
--- a/com/android/systemui/statusbar/phone/StatusBarKeyguardViewManager.java
+++ b/com/android/systemui/statusbar/phone/StatusBarKeyguardViewManager.java
@@ -33,7 +33,7 @@
 import com.android.internal.widget.LockPatternUtils;
 import com.android.keyguard.KeyguardUpdateMonitor;
 import com.android.keyguard.KeyguardUpdateMonitorCallback;
-import com.android.keyguard.LatencyTracker;
+import com.android.internal.util.LatencyTracker;
 import com.android.keyguard.ViewMediatorCallback;
 import com.android.systemui.DejankUtils;
 import com.android.systemui.Dependency;
@@ -380,8 +380,6 @@
                 mStatusBar.fadeKeyguardWhilePulsing();
                 wakeAndUnlockDejank();
             } else {
-                mFingerprintUnlockController.startKeyguardFadingAway();
-                mStatusBar.setKeyguardFadingAway(startTime, delay, fadeoutDuration);
                 boolean staying = mStatusBar.hideKeyguard();
                 if (!staying) {
                     mStatusBarWindowManager.setKeyguardFadingAway(true);
diff --git a/com/android/systemui/statusbar/phone/StatusBarWindowManager.java b/com/android/systemui/statusbar/phone/StatusBarWindowManager.java
index ed96b41..c30f633 100644
--- a/com/android/systemui/statusbar/phone/StatusBarWindowManager.java
+++ b/com/android/systemui/statusbar/phone/StatusBarWindowManager.java
@@ -16,6 +16,8 @@
 
 package com.android.systemui.statusbar.phone;
 
+import static com.android.systemui.statusbar.NotificationRemoteInputManager.ENABLE_REMOTE_INPUT;
+
 import android.app.ActivityManager;
 import android.app.IActivityManager;
 import android.content.Context;
@@ -52,6 +54,7 @@
     private final Context mContext;
     private final WindowManager mWindowManager;
     private final IActivityManager mActivityManager;
+    private final DozeParameters mDozeParameters;
     private View mStatusBarView;
     private WindowManager.LayoutParams mLp;
     private WindowManager.LayoutParams mLpChanged;
@@ -68,8 +71,8 @@
         mWindowManager = (WindowManager) context.getSystemService(Context.WINDOW_SERVICE);
         mActivityManager = ActivityManager.getService();
         mKeyguardScreenRotation = shouldEnableKeyguardScreenRotation();
-        mScreenBrightnessDoze = mContext.getResources().getInteger(
-                com.android.internal.R.integer.config_screenBrightnessDoze) / 255f;
+        mDozeParameters = new DozeParameters(mContext);
+        mScreenBrightnessDoze = mDozeParameters.getScreenBrightnessDoze();
     }
 
     private boolean shouldEnableKeyguardScreenRotation() {
@@ -134,7 +137,11 @@
             mLpChanged.privateFlags &= ~WindowManager.LayoutParams.PRIVATE_FLAG_KEYGUARD;
         }
 
-        if (state.keyguardShowing && !state.backdropShowing && !state.dozing) {
+        final boolean showWallpaperOnAod = mDozeParameters.getAlwaysOn() &&
+                state.wallpaperSupportsAmbientMode &&
+                state.scrimsVisibility != ScrimController.VISIBILITY_FULLY_OPAQUE;
+        if (state.keyguardShowing && !state.backdropShowing &&
+                (!state.dozing || showWallpaperOnAod)) {
             mLpChanged.flags |= WindowManager.LayoutParams.FLAG_SHOW_WALLPAPER;
         } else {
             mLpChanged.flags &= ~WindowManager.LayoutParams.FLAG_SHOW_WALLPAPER;
@@ -156,7 +163,7 @@
     private void applyFocusableFlag(State state) {
         boolean panelFocusable = state.statusBarFocusable && state.panelExpanded;
         if (state.bouncerShowing && (state.keyguardOccluded || state.keyguardNeedsInput)
-                || StatusBar.ENABLE_REMOTE_INPUT && state.remoteInputActive) {
+                || ENABLE_REMOTE_INPUT && state.remoteInputActive) {
             mLpChanged.flags &= ~WindowManager.LayoutParams.FLAG_NOT_FOCUSABLE;
             mLpChanged.flags &= ~WindowManager.LayoutParams.FLAG_ALT_FOCUSABLE_IM;
         } else if (state.isKeyguardShowingAndNotOccluded() || panelFocusable) {
@@ -186,7 +193,8 @@
     private boolean isExpanded(State state) {
         return !state.forceCollapsed && (state.isKeyguardShowingAndNotOccluded()
                 || state.panelVisible || state.keyguardFadingAway || state.bouncerShowing
-                || state.headsUpShowing || state.scrimsVisible);
+                || state.headsUpShowing
+                || state.scrimsVisibility != ScrimController.VISIBILITY_FULLY_TRANSPARENT);
     }
 
     private void applyFitsSystemWindows(State state) {
@@ -334,8 +342,8 @@
         apply(mCurrentState);
     }
 
-    public void setScrimsVisible(boolean scrimsVisible) {
-        mCurrentState.scrimsVisible = scrimsVisible;
+    public void setScrimsVisibility(int scrimsVisibility) {
+        mCurrentState.scrimsVisibility = scrimsVisibility;
         apply(mCurrentState);
     }
 
@@ -344,6 +352,11 @@
         apply(mCurrentState);
     }
 
+    public void setWallpaperSupportsAmbientMode(boolean supportsAmbientMode) {
+        mCurrentState.wallpaperSupportsAmbientMode = supportsAmbientMode;
+        apply(mCurrentState);
+    }
+
     /**
      * @param state The {@link StatusBarState} of the status bar.
      */
@@ -431,6 +444,7 @@
         boolean forceDozeBrightness;
         boolean forceUserActivity;
         boolean backdropShowing;
+        boolean wallpaperSupportsAmbientMode;
 
         /**
          * The {@link StatusBar} state from the status bar.
@@ -440,7 +454,7 @@
         boolean remoteInputActive;
         boolean forcePluginOpen;
         boolean dozing;
-        boolean scrimsVisible;
+        int scrimsVisibility;
 
         private boolean isKeyguardShowingAndNotOccluded() {
             return keyguardShowing && !keyguardOccluded;
diff --git a/com/android/systemui/statusbar/phone/StatusBarWindowView.java b/com/android/systemui/statusbar/phone/StatusBarWindowView.java
index d7f11f7..4accd86 100644
--- a/com/android/systemui/statusbar/phone/StatusBarWindowView.java
+++ b/com/android/systemui/statusbar/phone/StatusBarWindowView.java
@@ -248,7 +248,6 @@
 
     public void setTouchActive(boolean touchActive) {
         mTouchActive = touchActive;
-        mStackScrollLayout.setTouchActive(touchActive);
     }
 
     @Override
diff --git a/com/android/systemui/statusbar/policy/LocationControllerImpl.java b/com/android/systemui/statusbar/policy/LocationControllerImpl.java
index 874f0d9..4ee4ef4 100644
--- a/com/android/systemui/statusbar/policy/LocationControllerImpl.java
+++ b/com/android/systemui/statusbar/policy/LocationControllerImpl.java
@@ -39,6 +39,8 @@
 import java.util.ArrayList;
 import java.util.List;
 
+import static com.android.settingslib.Utils.updateLocationMode;
+
 /**
  * A controller to manage changes of location related states and update the views accordingly.
  */
@@ -106,12 +108,13 @@
         final ContentResolver cr = mContext.getContentResolver();
         // When enabling location, a user consent dialog will pop up, and the
         // setting won't be fully enabled until the user accepts the agreement.
+        int currentMode = Settings.Secure.getIntForUser(cr, Settings.Secure.LOCATION_MODE, 
+                Settings.Secure.LOCATION_MODE_OFF, currentUserId);
         int mode = enabled
                 ? Settings.Secure.LOCATION_MODE_PREVIOUS : Settings.Secure.LOCATION_MODE_OFF;
         // QuickSettings always runs as the owner, so specifically set the settings
         // for the current foreground user.
-        return Settings.Secure
-                .putIntForUser(cr, Settings.Secure.LOCATION_MODE, mode, currentUserId);
+        return updateLocationMode(mContext, currentMode, mode, currentUserId);
     }
 
     /**
diff --git a/com/android/systemui/statusbar/policy/MobileSignalController.java b/com/android/systemui/statusbar/policy/MobileSignalController.java
index 652f8bb..8516278 100644
--- a/com/android/systemui/statusbar/policy/MobileSignalController.java
+++ b/com/android/systemui/statusbar/policy/MobileSignalController.java
@@ -288,7 +288,7 @@
         String description = null;
         // Only send data sim callbacks to QS.
         if (mCurrentState.dataSim) {
-            qsTypeIcon = showDataIcon ? icons.mQsDataType : 0;
+            qsTypeIcon = (showDataIcon || mConfig.alwaysShowDataRatIcon) ? icons.mQsDataType : 0;
             qsIcon = new IconState(mCurrentState.enabled
                     && !mCurrentState.isEmergency, getQsCurrentIconId(), contentDescription);
             description = mCurrentState.isEmergency ? null : mCurrentState.networkName;
@@ -300,7 +300,7 @@
                 && !mCurrentState.carrierNetworkChangeMode
                 && mCurrentState.activityOut;
         showDataIcon &= mCurrentState.isDefault || dataDisabled;
-        int typeIcon = showDataIcon ? icons.mDataType : 0;
+        int typeIcon = (showDataIcon || mConfig.alwaysShowDataRatIcon) ? icons.mDataType : 0;
         callback.setMobileDataIndicators(statusIcon, qsIcon, typeIcon, qsTypeIcon,
                 activityIn, activityOut, dataContentDescription, description, icons.mIsWide,
                 mSubscriptionInfo.getSubscriptionId(), mCurrentState.roaming);
@@ -460,7 +460,7 @@
         mCurrentState.roaming = isRoaming();
         if (isCarrierNetworkChangeActive()) {
             mCurrentState.iconGroup = TelephonyIcons.CARRIER_NETWORK_CHANGE;
-        } else if (isDataDisabled()) {
+        } else if (isDataDisabled() && !mConfig.alwaysShowDataRatIcon) {
             mCurrentState.iconGroup = TelephonyIcons.DATA_DISABLED;
         }
         if (isEmergencyOnly() != mCurrentState.isEmergency) {
diff --git a/com/android/systemui/statusbar/policy/NetworkControllerImpl.java b/com/android/systemui/statusbar/policy/NetworkControllerImpl.java
index 40ee838..baf0ebf 100644
--- a/com/android/systemui/statusbar/policy/NetworkControllerImpl.java
+++ b/com/android/systemui/statusbar/policy/NetworkControllerImpl.java
@@ -29,7 +29,9 @@
 import android.os.Bundle;
 import android.os.Handler;
 import android.os.Looper;
+import android.os.PersistableBundle;
 import android.provider.Settings;
+import android.telephony.CarrierConfigManager;
 import android.telephony.ServiceState;
 import android.telephony.SignalStrength;
 import android.telephony.SubscriptionInfo;
@@ -245,6 +247,7 @@
         filter.addAction(ConnectivityManager.CONNECTIVITY_ACTION);
         filter.addAction(ConnectivityManager.INET_CONDITION_ACTION);
         filter.addAction(Intent.ACTION_AIRPLANE_MODE_CHANGED);
+        filter.addAction(CarrierConfigManager.ACTION_CARRIER_CONFIG_CHANGED);
         mContext.registerReceiver(this, filter, null, mReceiverHandler);
         mListening = true;
 
@@ -426,6 +429,14 @@
                 // emergency state.
                 recalculateEmergency();
             }
+        } else if (action.equals(CarrierConfigManager.ACTION_CARRIER_CONFIG_CHANGED)) {
+            mConfig = Config.readConfig(mContext);
+            mReceiverHandler.post(new Runnable() {
+                @Override
+                public void run() {
+                    handleConfigurationChanged();
+                }
+            });
         } else {
             int subId = intent.getIntExtra(PhoneConstants.SUBSCRIPTION_KEY,
                     SubscriptionManager.INVALID_SUBSCRIPTION_ID);
@@ -969,6 +980,7 @@
         boolean hideLtePlus = false;
         boolean hspaDataDistinguishable;
         boolean inflateSignalStrengths = false;
+        boolean alwaysShowDataRatIcon = false;
 
         static Config readConfig(Context context) {
             Config config = new Config();
@@ -982,6 +994,14 @@
                     res.getBoolean(R.bool.config_hspa_data_distinguishable);
             config.hideLtePlus = res.getBoolean(R.bool.config_hideLtePlus);
             config.inflateSignalStrengths = res.getBoolean(R.bool.config_inflateSignalStrength);
+
+            CarrierConfigManager configMgr = (CarrierConfigManager)
+                    context.getSystemService(Context.CARRIER_CONFIG_SERVICE);
+            PersistableBundle b = configMgr.getConfig();
+            if (b != null) {
+                config.alwaysShowDataRatIcon = b.getBoolean(
+                        CarrierConfigManager.KEY_ALWAYS_SHOW_DATA_RAT_ICON_BOOL);
+            }
             return config;
         }
     }
diff --git a/com/android/systemui/statusbar/policy/RotationLockController.java b/com/android/systemui/statusbar/policy/RotationLockController.java
index 722874b..f258fb1 100644
--- a/com/android/systemui/statusbar/policy/RotationLockController.java
+++ b/com/android/systemui/statusbar/policy/RotationLockController.java
@@ -24,6 +24,7 @@
     boolean isRotationLockAffordanceVisible();
     boolean isRotationLocked();
     void setRotationLocked(boolean locked);
+    void setRotationLockedAtAngle(boolean locked, int rotation);
 
     public interface RotationLockControllerCallback {
         void onRotationLockStateChanged(boolean rotationLocked, boolean affordanceVisible);
diff --git a/com/android/systemui/statusbar/policy/RotationLockControllerImpl.java b/com/android/systemui/statusbar/policy/RotationLockControllerImpl.java
index 4f96496..5418dc1 100644
--- a/com/android/systemui/statusbar/policy/RotationLockControllerImpl.java
+++ b/com/android/systemui/statusbar/policy/RotationLockControllerImpl.java
@@ -63,6 +63,10 @@
         RotationPolicy.setRotationLock(mContext, locked);
     }
 
+    public void setRotationLockedAtAngle(boolean locked, int rotation){
+        RotationPolicy.setRotationLockAtAngle(mContext, locked, rotation);
+    }
+
     public boolean isRotationLockAffordanceVisible() {
         return RotationPolicy.isRotationLockToggleVisible(mContext);
     }
diff --git a/com/android/systemui/statusbar/stack/ExpandableViewState.java b/com/android/systemui/statusbar/stack/ExpandableViewState.java
index e0fd481..0650e23 100644
--- a/com/android/systemui/statusbar/stack/ExpandableViewState.java
+++ b/com/android/systemui/statusbar/stack/ExpandableViewState.java
@@ -95,6 +95,12 @@
     public boolean inShelf;
 
     /**
+     * A state indicating whether a headsup is currently fully visible, even when not scrolled.
+     * Only valid if the view is heads upped.
+     */
+    public boolean headsUpIsVisible;
+
+    /**
      * How much the child overlaps with the previous child on top. This is used to
      * show the background properly when the child on top is translating away.
      */
@@ -126,6 +132,7 @@
             clipTopAmount = svs.clipTopAmount;
             notGoneIndex = svs.notGoneIndex;
             location = svs.location;
+            headsUpIsVisible = svs.headsUpIsVisible;
         }
     }
 
@@ -175,6 +182,10 @@
 
             expandableView.setTransformingInShelf(false);
             expandableView.setInShelf(inShelf);
+
+            if (headsUpIsVisible) {
+                expandableView.setHeadsUpIsVisible();
+            }
         }
     }
 
@@ -229,6 +240,10 @@
             expandableView.setTransformingInShelf(true);
         }
         expandableView.setInShelf(this.inShelf);
+
+        if (headsUpIsVisible) {
+            expandableView.setHeadsUpIsVisible();
+        }
     }
 
     private void startHeightAnimation(final ExpandableView child, AnimationProperties properties) {
diff --git a/com/android/systemui/statusbar/stack/NotificationStackScrollLayout.java b/com/android/systemui/statusbar/stack/NotificationStackScrollLayout.java
index ebebfac..369e7ff 100644
--- a/com/android/systemui/statusbar/stack/NotificationStackScrollLayout.java
+++ b/com/android/systemui/statusbar/stack/NotificationStackScrollLayout.java
@@ -80,6 +80,8 @@
 import com.android.systemui.statusbar.ExpandableView;
 import com.android.systemui.statusbar.NotificationData;
 import com.android.systemui.statusbar.NotificationGuts;
+import com.android.systemui.statusbar.NotificationListContainer;
+import com.android.systemui.statusbar.NotificationLogger;
 import com.android.systemui.statusbar.NotificationShelf;
 import com.android.systemui.statusbar.NotificationSnooze;
 import com.android.systemui.statusbar.StackScrollerDecorView;
@@ -112,7 +114,8 @@
 public class NotificationStackScrollLayout extends ViewGroup
         implements SwipeHelper.Callback, ExpandHelper.Callback, ScrollAdapter,
         ExpandableView.OnHeightChangedListener, NotificationGroupManager.OnGroupChangeListener,
-        NotificationMenuRowPlugin.OnMenuEventListener, VisibilityLocationProvider {
+        NotificationMenuRowPlugin.OnMenuEventListener, VisibilityLocationProvider,
+        NotificationListContainer {
 
     public static final float BACKGROUND_ALPHA_DIMMED = 0.7f;
     private static final String TAG = "StackScroller";
@@ -207,7 +210,7 @@
      * The raw amount of the overScroll on the bottom, which is not rubber-banded.
      */
     private float mOverScrolledBottomPixels;
-    private OnChildLocationsChangedListener mListener;
+    private NotificationLogger.OnChildLocationsChangedListener mListener;
     private OnOverscrollTopChangedListener mOverscrollTopChangedListener;
     private ExpandableView.OnHeightChangedListener mOnHeightChangedListener;
     private OnEmptySpaceClickListener mOnEmptySpaceClickListener;
@@ -447,6 +450,7 @@
         }
     }
 
+    @Override
     public NotificationSwipeActionHelper getSwipeActionHelper() {
         return mSwipeHelper;
     }
@@ -614,7 +618,9 @@
         mNoAmbient = noAmbient;
     }
 
-    public void setChildLocationsChangedListener(OnChildLocationsChangedListener listener) {
+    @Override
+    public void setChildLocationsChangedListener(
+            NotificationLogger.OnChildLocationsChangedListener listener) {
         mListener = listener;
     }
 
@@ -624,7 +630,7 @@
         if (childViewState == null) {
             return false;
         }
-        if ((childViewState.location &= ExpandableViewState.VISIBLE_LOCATIONS) == 0) {
+        if ((childViewState.location & ExpandableViewState.VISIBLE_LOCATIONS) == 0) {
             return false;
         }
         if (row.getVisibility() != View.VISIBLE) {
@@ -1325,6 +1331,7 @@
                 true /* isDismissAll */);
     }
 
+    @Override
     public void snapViewIfNeeded(ExpandableNotificationRow child) {
         boolean animate = mIsExpanded || isPinnedHeadsUp(child);
         // If the child is showing the notification menu snap to that
@@ -1333,6 +1340,11 @@
     }
 
     @Override
+    public ViewGroup getViewParentForNotification(NotificationData.Entry entry) {
+        return this;
+    }
+
+    @Override
     public boolean onTouchEvent(MotionEvent ev) {
         boolean isCancelOrUp = ev.getActionMasked() == MotionEvent.ACTION_CANCEL
                 || ev.getActionMasked()== MotionEvent.ACTION_UP;
@@ -2053,6 +2065,7 @@
         return mAmbientState.isPulsing(entry);
     }
 
+    @Override
     public boolean hasPulsingNotifications() {
         return mPulsing != null;
     }
@@ -2610,10 +2623,7 @@
         }
     }
 
-    /**
-     * Called when a notification is removed from the shade. This cleans up the state for a given
-     * view.
-     */
+    @Override
     public void cleanUpViewState(View child) {
         if (child == mTranslatingParentView) {
             mTranslatingParentView = null;
@@ -2922,10 +2932,12 @@
         }
     }
 
+    @Override
     public void notifyGroupChildRemoved(View row, ViewGroup childrenContainer) {
         onViewRemovedInternal(row, childrenContainer);
     }
 
+    @Override
     public void notifyGroupChildAdded(View row) {
         onViewAddedInternal(row);
     }
@@ -2963,12 +2975,8 @@
         return mNeedsAnimation
                 && (!mChildrenToAddAnimated.isEmpty() || !mChildrenToRemoveAnimated.isEmpty());
     }
-    /**
-     * Generate an animation for an added child view.
-     *
-     * @param child The view to be added.
-     * @param fromMoreCard Whether this add is coming from the "more" card on lockscreen.
-     */
+
+    @Override
     public void generateAddAnimation(View child, boolean fromMoreCard) {
         if (mIsExpanded && mAnimationsEnabled && !mChangePositionInProgress) {
             // Generate Animations
@@ -2984,12 +2992,7 @@
         }
     }
 
-    /**
-     * Change the position of child to a new location
-     *
-     * @param child the view to change the position for
-     * @param newIndex the new index
-     */
+    @Override
     public void changeViewPosition(View child, int newIndex) {
         int currentIndex = indexOfChild(child);
         if (child != null && child.getParent() == this && currentIndex != newIndex) {
@@ -3681,6 +3684,7 @@
                 mHideSensitiveNeedsAnimation = true;
                 mNeedsAnimation =  true;
             }
+            updateContentHeight();
             requestChildrenUpdate();
         }
     }
@@ -3704,7 +3708,7 @@
     private void applyCurrentState() {
         mCurrentStackScrollState.apply();
         if (mListener != null) {
-            mListener.onChildLocationsChanged(this);
+            mListener.onChildLocationsChanged();
         }
         runAnimationFinishedRunnables();
         setAnimationRunning(false);
@@ -4188,6 +4192,26 @@
         }
     }
 
+    @Override
+    public int getContainerChildCount() {
+        return getChildCount();
+    }
+
+    @Override
+    public View getContainerChildAt(int i) {
+        return getChildAt(i);
+    }
+
+    @Override
+    public void removeContainerView(View v) {
+        removeView(v);
+    }
+
+    @Override
+    public void addContainerView(View v) {
+        addView(v);
+    }
+
     public void runAfterAnimationFinished(Runnable runnable) {
         mAnimationFinishedRunnables.add(runnable);
     }
@@ -4443,17 +4467,6 @@
                 mAmbientState.getScrollY()));
     }
 
-    public void setTouchActive(boolean touchActive) {
-        mShelf.setTouchActive(touchActive);
-    }
-
-    /**
-     * A listener that is notified when some child locations might have changed.
-     */
-    public interface OnChildLocationsChangedListener {
-        void onChildLocationsChanged(NotificationStackScrollLayout stackScrollLayout);
-    }
-
     /**
      * A listener that is notified when the empty space below the notifications is clicked on
      */
@@ -4709,6 +4722,7 @@
         }
     }
 
+    @Override
     public void resetExposedMenuView(boolean animate, boolean force) {
         mSwipeHelper.resetExposedMenuView(animate, force);
     }
diff --git a/com/android/systemui/statusbar/stack/StackScrollAlgorithm.java b/com/android/systemui/statusbar/stack/StackScrollAlgorithm.java
index c060b08..7374f11 100644
--- a/com/android/systemui/statusbar/stack/StackScrollAlgorithm.java
+++ b/com/android/systemui/statusbar/stack/StackScrollAlgorithm.java
@@ -135,7 +135,7 @@
         for (int i = 0; i < childCount; i++) {
             ExpandableView child = algorithmState.visibleChildren.get(i);
             ExpandableViewState state = resultState.getViewStateForView(child);
-            if (!child.mustStayOnScreen()) {
+            if (!child.mustStayOnScreen() || state.headsUpIsVisible) {
                 previousNotificationEnd = Math.max(drawStart, previousNotificationEnd);
                 previousNotificationStart = Math.max(drawStart, previousNotificationStart);
             }
@@ -378,6 +378,13 @@
         boolean isEmptyShadeView = child instanceof EmptyShadeView;
 
         childViewState.location = ExpandableViewState.LOCATION_MAIN_AREA;
+        float inset = ambientState.getTopPadding() + ambientState.getStackTranslation();
+        if (child.mustStayOnScreen() && childViewState.yTranslation >= 0) {
+            // Even if we're not scrolled away we're in view and we're also not in the
+            // shelf. We can relax the constraints and let us scroll off the top!
+            float end = childViewState.yTranslation + childViewState.height + inset;
+            childViewState.headsUpIsVisible = end < ambientState.getMaxHeadsUpTranslation();
+        }
         if (isDismissView) {
             childViewState.yTranslation = Math.min(childViewState.yTranslation,
                     ambientState.getInnerHeight() - childHeight);
@@ -396,8 +403,7 @@
             Log.wtf(LOG_TAG, "Failed to assign location for child " + i);
         }
 
-        childViewState.yTranslation += ambientState.getTopPadding()
-                + ambientState.getStackTranslation();
+        childViewState.yTranslation += inset;
         return currentYPosition;
     }
 
@@ -420,19 +426,21 @@
                 break;
             }
             ExpandableViewState childState = resultState.getViewStateForView(row);
-            if (topHeadsUpEntry == null) {
+            if (topHeadsUpEntry == null && row.mustStayOnScreen() && !childState.headsUpIsVisible) {
                 topHeadsUpEntry = row;
                 childState.location = ExpandableViewState.LOCATION_FIRST_HUN;
             }
             boolean isTopEntry = topHeadsUpEntry == row;
             float unmodifiedEndLocation = childState.yTranslation + childState.height;
             if (mIsExpanded) {
-                // Ensure that the heads up is always visible even when scrolled off
-                clampHunToTop(ambientState, row, childState);
-                if (i == 0 && ambientState.isAboveShelf(row)) {
-                    // the first hun can't get off screen.
-                    clampHunToMaxTranslation(ambientState, row, childState);
-                    childState.hidden = false;
+                if (row.mustStayOnScreen() && !childState.headsUpIsVisible) {
+                    // Ensure that the heads up is always visible even when scrolled off
+                    clampHunToTop(ambientState, row, childState);
+                    if (i == 0 && ambientState.isAboveShelf(row)) {
+                        // the first hun can't get off screen.
+                        clampHunToMaxTranslation(ambientState, row, childState);
+                        childState.hidden = false;
+                    }
                 }
             }
             if (row.isPinned()) {
@@ -440,7 +448,7 @@
                 childState.height = Math.max(row.getIntrinsicHeight(), childState.height);
                 childState.hidden = false;
                 ExpandableViewState topState = resultState.getViewStateForView(topHeadsUpEntry);
-                if (!isTopEntry && (!mIsExpanded
+                if (topState != null && !isTopEntry && (!mIsExpanded
                         || unmodifiedEndLocation < topState.yTranslation + topState.height)) {
                     // Ensure that a headsUp doesn't vertically extend further than the heads-up at
                     // the top most z-position
@@ -493,6 +501,7 @@
         if (childViewState.yTranslation >= shelfStart) {
             childViewState.hidden = true;
             childViewState.inShelf = true;
+            childViewState.headsUpIsVisible = false;
         }
         if (!ambientState.isShadeExpanded()) {
             childViewState.height = (int) (mStatusBarHeight - childViewState.yTranslation);
@@ -531,7 +540,8 @@
         ExpandableViewState childViewState = resultState.getViewStateForView(child);
         int zDistanceBetweenElements = ambientState.getZDistanceBetweenElements();
         float baseZ = ambientState.getBaseZHeight();
-        if (child.mustStayOnScreen() && !ambientState.isDozingAndNotPulsing(child)
+        if (child.mustStayOnScreen() && !childViewState.headsUpIsVisible
+                && !ambientState.isDozingAndNotPulsing(child)
                 && childViewState.yTranslation < ambientState.getTopPadding()
                 + ambientState.getStackTranslation()) {
             if (childrenOnTop != 0.0f) {
diff --git a/com/android/systemui/statusbar/stack/StackScrollState.java b/com/android/systemui/statusbar/stack/StackScrollState.java
index e3c746b..588b758 100644
--- a/com/android/systemui/statusbar/stack/StackScrollState.java
+++ b/com/android/systemui/statusbar/stack/StackScrollState.java
@@ -84,6 +84,7 @@
         viewState.scaleX = view.getScaleX();
         viewState.scaleY = view.getScaleY();
         viewState.inShelf = false;
+        viewState.headsUpIsVisible = false;
     }
 
     public ExpandableViewState getViewStateForView(View requestedView) {
diff --git a/com/android/systemui/statusbar/stack/StackStateAnimator.java b/com/android/systemui/statusbar/stack/StackStateAnimator.java
index f78a718..236c348 100644
--- a/com/android/systemui/statusbar/stack/StackStateAnimator.java
+++ b/com/android/systemui/statusbar/stack/StackStateAnimator.java
@@ -40,7 +40,7 @@
 public class StackStateAnimator {
 
     public static final int ANIMATION_DURATION_STANDARD = 360;
-    public static final int ANIMATION_DURATION_WAKEUP = 200;
+    public static final int ANIMATION_DURATION_WAKEUP = 500;
     public static final int ANIMATION_DURATION_GO_TO_FULL_SHADE = 448;
     public static final int ANIMATION_DURATION_APPEAR_DISAPPEAR = 464;
     public static final int ANIMATION_DURATION_DIMMED_ACTIVATED = 220;