Make StatusBar not be a NotificationPresenter

Break the NotificationPresenter out of the StatusBar and most of
the logic with it.

 - Break RemoteInput Callbacks out of NotificationPresenter
 - Break Environment Callbacks out of NotificationPresenter
 - Add ShadeController interface for StatusBar (abstraction
   layer from StatusBar)
 - Add InitController to allow for post-init tasks (dependency
 resolution should not go here)
 - Make some dependencies (ShadeController, NotificationEntryManager,
 NotificationLockscreenUserManager usually) lazily-resolved to break
 some dependency cycles

 - Lots of other cleanup

Test: Existing tests do not pass
Change-Id: Ic043c6b15a4ffe551fc27f99b25d5c3caa1be582
diff --git a/packages/SystemUI/src/com/android/systemui/ActivityStarterDelegate.java b/packages/SystemUI/src/com/android/systemui/ActivityStarterDelegate.java
index e58538d..e1b8dc8 100644
--- a/packages/SystemUI/src/com/android/systemui/ActivityStarterDelegate.java
+++ b/packages/SystemUI/src/com/android/systemui/ActivityStarterDelegate.java
@@ -29,46 +29,69 @@
 
     @Override
     public void startPendingIntentDismissingKeyguard(PendingIntent intent) {
-        if (mActualStarter == null) return;
+        if (mActualStarter == null) {
+            return;
+        }
         mActualStarter.startPendingIntentDismissingKeyguard(intent);
     }
 
     @Override
     public void startActivity(Intent intent, boolean dismissShade) {
-        if (mActualStarter == null) return;
+        if (mActualStarter == null) {
+            return;
+        }
         mActualStarter.startActivity(intent, dismissShade);
     }
 
     @Override
     public void startActivity(Intent intent, boolean onlyProvisioned, boolean dismissShade) {
-        if (mActualStarter == null) return;
+        if (mActualStarter == null) {
+            return;
+        }
         mActualStarter.startActivity(intent, onlyProvisioned, dismissShade);
     }
 
     @Override
     public void startActivity(Intent intent, boolean dismissShade, Callback callback) {
-        if (mActualStarter == null) return;
+        if (mActualStarter == null) {
+            return;
+        }
         mActualStarter.startActivity(intent, dismissShade, callback);
     }
 
     @Override
     public void postStartActivityDismissingKeyguard(Intent intent, int delay) {
-        if (mActualStarter == null) return;
+        if (mActualStarter == null) {
+            return;
+        }
         mActualStarter.postStartActivityDismissingKeyguard(intent, delay);
     }
 
     @Override
     public void postStartActivityDismissingKeyguard(PendingIntent intent) {
-        if (mActualStarter == null) return;
+        if (mActualStarter == null) {
+            return;
+        }
         mActualStarter.postStartActivityDismissingKeyguard(intent);
     }
 
     @Override
     public void postQSRunnableDismissingKeyguard(Runnable runnable) {
-        if (mActualStarter == null) return;
+        if (mActualStarter == null) {
+            return;
+        }
         mActualStarter.postQSRunnableDismissingKeyguard(runnable);
     }
 
+    @Override
+    public void dismissKeyguardThenExecute(OnDismissAction action, Runnable cancel,
+            boolean afterKeyguardGone) {
+        if (mActualStarter == null) {
+            return;
+        }
+        mActualStarter.dismissKeyguardThenExecute(action, cancel, afterKeyguardGone);
+    }
+
     public void setActivityStarterImpl(ActivityStarter starter) {
         mActualStarter = starter;
     }
diff --git a/packages/SystemUI/src/com/android/systemui/Dependency.java b/packages/SystemUI/src/com/android/systemui/Dependency.java
index 2c821b2..494880e 100644
--- a/packages/SystemUI/src/com/android/systemui/Dependency.java
+++ b/packages/SystemUI/src/com/android/systemui/Dependency.java
@@ -48,16 +48,22 @@
 import com.android.systemui.power.EnhancedEstimatesImpl;
 import com.android.systemui.power.PowerNotificationWarnings;
 import com.android.systemui.power.PowerUI;
+import com.android.systemui.statusbar.NotificationRemoteInputManager;
 import com.android.systemui.statusbar.notification.AppOpsListener;
 import com.android.systemui.statusbar.VibratorHelper;
+import com.android.systemui.statusbar.notification.NotificationData.KeyguardEnvironment;
 import com.android.systemui.statusbar.phone.ConfigurationControllerImpl;
 import com.android.systemui.statusbar.phone.DarkIconDispatcherImpl;
 import com.android.systemui.statusbar.phone.LightBarController;
 import com.android.systemui.statusbar.phone.LockscreenGestureLogger;
 import com.android.systemui.statusbar.phone.ManagedProfileController;
 import com.android.systemui.statusbar.phone.ManagedProfileControllerImpl;
+import com.android.systemui.statusbar.phone.ShadeController;
+import com.android.systemui.statusbar.phone.StatusBar;
+import com.android.systemui.statusbar.phone.KeyguardEnvironmentImpl;
 import com.android.systemui.statusbar.phone.StatusBarIconController;
 import com.android.systemui.statusbar.phone.StatusBarIconControllerImpl;
+import com.android.systemui.statusbar.phone.StatusBarRemoteInputCallback;
 import com.android.systemui.statusbar.phone.StatusBarWindowController;
 import com.android.systemui.statusbar.policy.AccessibilityController;
 import com.android.systemui.statusbar.policy.AccessibilityManagerWrapper;
@@ -343,6 +349,14 @@
 
         mProviders.put(LockscreenGestureLogger.class, () -> new LockscreenGestureLogger());
 
+        mProviders.put(KeyguardEnvironment.class, () -> new KeyguardEnvironmentImpl());
+        mProviders.put(ShadeController.class, () ->
+                SysUiServiceProvider.getComponent(mContext, StatusBar.class));
+        mProviders.put(NotificationRemoteInputManager.Callback.class,
+                () -> new StatusBarRemoteInputCallback(mContext));
+
+        mProviders.put(InitController.class, InitController::new);
+
         // Put all dependencies above here so the factory can override them if it wants.
         SystemUIFactory.getInstance().injectDependencies(mProviders, mContext);
 
diff --git a/packages/SystemUI/src/com/android/systemui/InitController.java b/packages/SystemUI/src/com/android/systemui/InitController.java
new file mode 100644
index 0000000..52ba66a
--- /dev/null
+++ b/packages/SystemUI/src/com/android/systemui/InitController.java
@@ -0,0 +1,43 @@
+/*
+ * Copyright (C) 2018 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;
+
+import java.util.ArrayList;
+
+/**
+ * Created by {@link Dependency} on SystemUI startup. Add tasks which need to be executed only
+ * after all other dependencies have been created.
+ */
+public class InitController {
+
+    private final ArrayList<Runnable> mTasks = new ArrayList<>();
+
+    /**
+     * Add a task to be executed after {@link Dependency#start()}
+     * @param runnable the task to be executed
+     */
+    public void addPostInitTask(Runnable runnable) {
+        mTasks.add(runnable);
+    }
+
+    /**
+     * Run post-init tasks and remove them from the tasks list
+     */
+    public void executePostInitTasks() {
+        while (!mTasks.isEmpty()) {
+            mTasks.remove(0).run();
+        }
+    }
+}
diff --git a/packages/SystemUI/src/com/android/systemui/SystemUIApplication.java b/packages/SystemUI/src/com/android/systemui/SystemUIApplication.java
index 78053b2..92aa652 100644
--- a/packages/SystemUI/src/com/android/systemui/SystemUIApplication.java
+++ b/packages/SystemUI/src/com/android/systemui/SystemUIApplication.java
@@ -194,6 +194,7 @@
                 mServices[i].onBootCompleted();
             }
         }
+        Dependency.get(InitController.class).executePostInitTasks();
         log.traceEnd();
         Dependency.get(PluginManager.class).addPluginListener(
                 new PluginListener<OverlayPlugin>() {
diff --git a/packages/SystemUI/src/com/android/systemui/SystemUIFactory.java b/packages/SystemUI/src/com/android/systemui/SystemUIFactory.java
index 258b6f6..c4bf27b 100644
--- a/packages/SystemUI/src/com/android/systemui/SystemUIFactory.java
+++ b/packages/SystemUI/src/com/android/systemui/SystemUIFactory.java
@@ -32,6 +32,7 @@
 import com.android.systemui.qs.QSTileHost;
 import com.android.systemui.statusbar.AmbientPulseManager;
 import com.android.systemui.statusbar.KeyguardIndicationController;
+import com.android.systemui.statusbar.NotificationLockscreenUserManagerImpl;
 import com.android.systemui.statusbar.StatusBarStateController;
 import com.android.systemui.statusbar.notification.row.NotificationBlockingHelperManager;
 import com.android.systemui.statusbar.notification.NotificationEntryManager;
@@ -133,7 +134,7 @@
             Context context) {
         providers.put(StatusBarStateController.class, StatusBarStateController::new);
         providers.put(NotificationLockscreenUserManager.class,
-                () -> new NotificationLockscreenUserManager(context));
+                () -> new NotificationLockscreenUserManagerImpl(context));
         providers.put(VisualStabilityManager.class, VisualStabilityManager::new);
         providers.put(NotificationGroupManager.class, NotificationGroupManager::new);
         providers.put(NotificationMediaManager.class, () -> new NotificationMediaManager(context));
diff --git a/packages/SystemUI/src/com/android/systemui/assist/AssistManager.java b/packages/SystemUI/src/com/android/systemui/assist/AssistManager.java
index 38a90cf..53cdee5 100644
--- a/packages/SystemUI/src/com/android/systemui/assist/AssistManager.java
+++ b/packages/SystemUI/src/com/android/systemui/assist/AssistManager.java
@@ -202,7 +202,8 @@
 
         // Close Recent Apps if needed
         SysUiServiceProvider.getComponent(mContext, CommandQueue.class).animateCollapsePanels(
-                CommandQueue.FLAG_EXCLUDE_SEARCH_PANEL | CommandQueue.FLAG_EXCLUDE_RECENTS_PANEL);
+                CommandQueue.FLAG_EXCLUDE_SEARCH_PANEL | CommandQueue.FLAG_EXCLUDE_RECENTS_PANEL,
+                false /* force */);
 
         boolean structureEnabled = Settings.Secure.getIntForUser(mContext.getContentResolver(),
                 Settings.Secure.ASSIST_STRUCTURE_ENABLED, 1, UserHandle.USER_CURRENT) != 0;
diff --git a/packages/SystemUI/src/com/android/systemui/car/CarNotificationEntryManager.java b/packages/SystemUI/src/com/android/systemui/car/CarNotificationEntryManager.java
index 5739c99..96af08b 100644
--- a/packages/SystemUI/src/com/android/systemui/car/CarNotificationEntryManager.java
+++ b/packages/SystemUI/src/com/android/systemui/car/CarNotificationEntryManager.java
@@ -44,7 +44,7 @@
         // 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 (!mPresenter.isPresenterFullyCollapsed()) {
+        if (!getPresenter().isPresenterFullyCollapsed()) {
             return false;
         }
 
diff --git a/packages/SystemUI/src/com/android/systemui/car/CarNotificationMediaManager.java b/packages/SystemUI/src/com/android/systemui/car/CarNotificationMediaManager.java
new file mode 100644
index 0000000..f34d6b3
--- /dev/null
+++ b/packages/SystemUI/src/com/android/systemui/car/CarNotificationMediaManager.java
@@ -0,0 +1,30 @@
+/*
+ * Copyright (C) 2018 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.car;
+
+import android.content.Context;
+
+import com.android.systemui.statusbar.NotificationMediaManager;
+
+public class CarNotificationMediaManager extends NotificationMediaManager {
+    public CarNotificationMediaManager(Context context) {
+        super(context);
+    }
+
+    @Override
+    public void updateMediaMetaData(boolean metaDataChanged, boolean allowEnterAnimation) {
+        // Do nothing, we don't want to display media art in the lock screen for a car.
+    }
+}
diff --git a/packages/SystemUI/src/com/android/systemui/car/CarSystemUIFactory.java b/packages/SystemUI/src/com/android/systemui/car/CarSystemUIFactory.java
index a015a18..e4b2e07 100644
--- a/packages/SystemUI/src/com/android/systemui/car/CarSystemUIFactory.java
+++ b/packages/SystemUI/src/com/android/systemui/car/CarSystemUIFactory.java
@@ -22,6 +22,7 @@
 import com.android.keyguard.ViewMediatorCallback;
 import com.android.systemui.Dependency.DependencyProvider;
 import com.android.systemui.SystemUIFactory;
+import com.android.systemui.statusbar.NotificationMediaManager;
 import com.android.systemui.statusbar.notification.NotificationEntryManager;
 import com.android.systemui.statusbar.car.CarFacetButtonController;
 import com.android.systemui.statusbar.car.CarStatusBarKeyguardViewManager;
@@ -46,5 +47,7 @@
                 () -> new CarNotificationEntryManager(context));
         providers.put(CarFacetButtonController.class, () -> new CarFacetButtonController(context));
         providers.put(HvacController.class, () -> new HvacController(context));
+        providers.put(NotificationMediaManager.class,
+                () -> new CarNotificationMediaManager(context));
     }
 }
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/CommandQueue.java b/packages/SystemUI/src/com/android/systemui/statusbar/CommandQueue.java
index 5c0b328..daaefb9 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/CommandQueue.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/CommandQueue.java
@@ -16,6 +16,9 @@
 
 package com.android.systemui.statusbar;
 
+import static com.android.systemui.statusbar.phone.StatusBar.ONLY_CORE_APPS;
+
+import android.app.StatusBarManager;
 import android.content.ComponentName;
 import android.graphics.Rect;
 import android.hardware.biometrics.IBiometricPromptReceiver;
@@ -24,7 +27,7 @@
 import android.os.IBinder;
 import android.os.Looper;
 import android.os.Message;
-import android.os.RemoteException;
+
 import androidx.annotation.VisibleForTesting;
 import android.util.Pair;
 
@@ -117,7 +120,7 @@
         default void removeIcon(String slot) { }
         default void disable(int state1, int state2, boolean animate) { }
         default void animateExpandNotificationsPanel() { }
-        default void animateCollapsePanels(int flags) { }
+        default void animateCollapsePanels(int flags, boolean force) { }
         default void togglePanel() { }
         default void animateExpandSettingsPanel(String obj) { }
         default void setSystemUiVisibility(int vis, int fullscreenStackVis,
@@ -169,7 +172,13 @@
     }
 
     @VisibleForTesting
-    protected CommandQueue() {
+    public CommandQueue() {
+    }
+
+    public boolean panelsEnabled() {
+        return (mDisable1 & StatusBarManager.DISABLE_EXPAND) == 0
+                && (mDisable2 & StatusBarManager.DISABLE2_NOTIFICATION_SHADE) == 0
+                && !ONLY_CORE_APPS;
     }
 
     public void addCallbacks(Callbacks callbacks) {
@@ -234,10 +243,10 @@
         }
     }
 
-    public void animateCollapsePanels(int flags) {
+    public void animateCollapsePanels(int flags, boolean force) {
         synchronized (mLock) {
             mHandler.removeMessages(MSG_COLLAPSE_PANELS);
-            mHandler.obtainMessage(MSG_COLLAPSE_PANELS, flags, 0).sendToTarget();
+            mHandler.obtainMessage(MSG_COLLAPSE_PANELS, flags, force ? 1 : 0).sendToTarget();
         }
     }
 
@@ -592,7 +601,7 @@
                     break;
                 case MSG_COLLAPSE_PANELS:
                     for (int i = 0; i < mCallbacks.size(); i++) {
-                        mCallbacks.get(i).animateCollapsePanels(msg.arg1);
+                        mCallbacks.get(i).animateCollapsePanels(msg.arg1, msg.arg2 != 0);
                     }
                     break;
                 case MSG_TOGGLE_PANEL:
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/NotificationListener.java b/packages/SystemUI/src/com/android/systemui/statusbar/NotificationListener.java
index cfa09bc..f3a46ce 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/NotificationListener.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/NotificationListener.java
@@ -29,6 +29,7 @@
 
 import com.android.systemui.Dependency;
 import com.android.systemui.statusbar.notification.NotificationEntryManager;
+import com.android.systemui.statusbar.phone.NotificationGroupManager;
 import com.android.systemui.statusbar.phone.NotificationListenerWithPlugins;
 
 /**
@@ -41,11 +42,14 @@
     // Dependencies:
     private final NotificationRemoteInputManager mRemoteInputManager =
             Dependency.get(NotificationRemoteInputManager.class);
+    private final NotificationEntryManager mEntryManager =
+            Dependency.get(NotificationEntryManager.class);
+    private final NotificationGroupManager mGroupManager =
+            Dependency.get(NotificationGroupManager.class);
 
     private final Context mContext;
 
     protected NotificationPresenter mPresenter;
-    protected NotificationEntryManager mEntryManager;
 
     public NotificationListener(Context context) {
         mContext = context;
@@ -61,7 +65,7 @@
             return;
         }
         final RankingMap currentRanking = getCurrentRanking();
-        mPresenter.getHandler().post(() -> {
+        Dependency.get(Dependency.MAIN_HANDLER).post(() -> {
             for (StatusBarNotification sbn : notifications) {
                 mEntryManager.addNotification(sbn, currentRanking);
             }
@@ -73,7 +77,7 @@
             final RankingMap rankingMap) {
         if (DEBUG) Log.d(TAG, "onNotificationPosted: " + sbn);
         if (sbn != null && !onPluginNotificationPosted(sbn, rankingMap)) {
-            mPresenter.getHandler().post(() -> {
+            Dependency.get(Dependency.MAIN_HANDLER).post(() -> {
                 processForRemoteInput(sbn.getNotification(), mContext);
                 String key = sbn.getKey();
                 boolean isUpdate =
@@ -83,7 +87,7 @@
                 // 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)) {
+                        && mGroupManager.isChildInGroupWithSummary(sbn)) {
                     if (DEBUG) {
                         Log.d(TAG, "Ignoring group child due to existing summary: " + sbn);
                     }
@@ -112,7 +116,7 @@
         if (DEBUG) Log.d(TAG, "onNotificationRemoved: " + sbn);
         if (sbn != null && !onPluginNotificationRemoved(sbn, rankingMap)) {
             final String key = sbn.getKey();
-            mPresenter.getHandler().post(() -> {
+            Dependency.get(Dependency.MAIN_HANDLER).post(() -> {
                 mEntryManager.removeNotification(key, rankingMap);
             });
         }
@@ -123,16 +127,14 @@
         if (DEBUG) Log.d(TAG, "onRankingUpdate");
         if (rankingMap != null) {
             RankingMap r = onPluginRankingUpdate(rankingMap);
-            mPresenter.getHandler().post(() -> {
+            Dependency.get(Dependency.MAIN_HANDLER).post(() -> {
                 mEntryManager.updateNotificationRanking(r);
             });
         }
     }
 
-    public void setUpWithPresenter(NotificationPresenter presenter,
-            NotificationEntryManager entryManager) {
+    public void setUpWithPresenter(NotificationPresenter presenter) {
         mPresenter = presenter;
-        mEntryManager = entryManager;
 
         try {
             registerAsSystemService(mContext,
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/NotificationLockscreenUserManager.java b/packages/SystemUI/src/com/android/systemui/statusbar/NotificationLockscreenUserManager.java
index 89a842e..bc662e3 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/NotificationLockscreenUserManager.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/NotificationLockscreenUserManager.java
@@ -1,516 +1,61 @@
 /*
  * 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
+ * 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
+ * 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.KeyguardManager;
-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 com.android.internal.statusbar.IStatusBarService;
-import com.android.internal.statusbar.NotificationVisibility;
-import com.android.internal.widget.LockPatternUtils;
-import com.android.keyguard.KeyguardUpdateMonitor;
-import com.android.systemui.Dependency;
-import com.android.systemui.Dumpable;
-import com.android.systemui.OverviewProxyService;
-import com.android.systemui.statusbar.StatusBarStateController.StateListener;
-import com.android.systemui.statusbar.notification.NotificationData;
-import com.android.systemui.statusbar.notification.NotificationEntryManager;
-import com.android.systemui.statusbar.phone.StatusBarKeyguardViewManager;
-import com.android.systemui.statusbar.policy.DeviceProvisionedController;
+import com.android.systemui.statusbar.notification.NotificationData.Entry;
 
-import java.io.FileDescriptor;
-import java.io.PrintWriter;
-
-/**
- * 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, StateListener {
-    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
+public interface NotificationLockscreenUserManager {
+    String PERMISSION_SELF = "com.android.systemui.permission.SELF";
+    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 final LockPatternUtils mLockPatternUtils;
-    private final KeyguardManager mKeyguardManager;
-    private StatusBarKeyguardViewManager mKeyguardViewManager;
-
-    private boolean mShowLockscreenNotifications;
-    private boolean mAllowLockscreenRemoteInput;
-    private int mState = StatusBarState.SHADE;
-
-    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)) {
-                    updatePublicMode();
-                    mPresenter.onWorkChallengeChanged();
-                    mEntryManager.updateNotifications();
-                }
-            }
-        }
-    };
-
-    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();
-                updatePublicMode();
-                mPresenter.onUserSwitched(mCurrentUserId);
-                mEntryManager.getNotificationData().filterAndSort();
-            } 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 (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) {
-                    final int count =
-                            mEntryManager.getNotificationData().getActiveNotifications().size();
-                    final int rank = mEntryManager.getNotificationData().getRank(notificationKey);
-                    final NotificationVisibility nv = NotificationVisibility.obtain(notificationKey,
-                            rank, count, true);
-                    try {
-                        mBarService.onNotificationClick(notificationKey, nv);
-                    } 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));
-        mLockPatternUtils = new LockPatternUtils(mContext);
-        mKeyguardManager = (KeyguardManager) mContext.getSystemService(Context.KEYGUARD_SERVICE);
-        Dependency.get(StatusBarStateController.class).addListener(this);
-    }
-
-    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_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
-    }
-
-    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;
-        }
-    }
-
-    public void setKeyguardViewManager(StatusBarKeyguardViewManager sbkvm) {
-        mKeyguardViewManager = sbkvm;
-    }
-
-    @Override
-    public void onStateChanged(int newState) {
-        mState = newState;
-        updatePublicMode();
-    }
-
-    public void updatePublicMode() {
-        //TODO: I think there may be a race condition where mKeyguardViewManager.isShowing() returns
-        // false when it should be true. Therefore, if we are not on the SHADE, don't even bother
-        // asking if the keyguard is showing. We still need to check it though because showing the
-        // camera on the keyguard has a state of SHADE but the keyguard is still showing.
-        boolean showingKeyguard = mState != StatusBarState.SHADE
-                || mKeyguardViewManager.isShowing();
-        boolean devicePublic = showingKeyguard && mKeyguardViewManager.isSecure(getCurrentUserId());
-
-        SparseArray<UserInfo> currentProfiles = getCurrentProfiles();
-        for (int i = currentProfiles.size() - 1; i >= 0; i--) {
-            final int userId = currentProfiles.valueAt(i).id;
-            boolean isProfilePublic = devicePublic;
-            if (!devicePublic && userId != mCurrentUserId) {
-                // 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
-                // state being cached in mLockscreenPublicMode. (b/35951989)
-                if (mLockPatternUtils.isSeparateProfileChallengeEnabled(userId)
-                        && mKeyguardViewManager.isSecure(userId)) {
-                    isProfilePublic = mKeyguardManager.isDeviceLocked(userId);
-                }
-            }
-            setLockscreenPublicMode(isProfilePublic, userId);
-        }
-    }
+    boolean shouldAllowLockscreenRemoteInput();
 
     /**
-     * Returns true if notifications are temporarily disabled for this user for security reasons,
-     * regardless of the normal settings for that user.
+     * @param userId user Id
+     * @return true if we re on a secure lock screen
      */
-    private boolean shouldTemporarilyHideNotifications(int userId) {
-        if (userId == UserHandle.USER_ALL) {
-            userId = mCurrentUserId;
-        }
-        return KeyguardUpdateMonitor.getInstance(mContext).isUserInLockdown(userId);
-    }
+    boolean isLockscreenPublicMode(int userId);
 
-    /**
-     * 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))
-                || shouldTemporarilyHideNotifications(userId);
-    }
+    void setUpWithPresenter(NotificationPresenter presenter);
 
-    /**
-     * 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) {
-        if (mEntryManager == null) {
-            Log.wtf(TAG, "mEntryManager was null!", new Throwable());
-            return true;
-        }
-        return isLockscreenPublicMode(mCurrentUserId)
-                && mEntryManager.getNotificationData().getVisibilityOverride(key) ==
-                        Notification.VISIBILITY_SECRET;
-    }
+    int getCurrentUserId();
 
-    public boolean shouldShowOnKeyguard(StatusBarNotification sbn) {
-        if (mEntryManager == null) {
-            Log.wtf(TAG, "mEntryManager was null!", new Throwable());
-            return false;
-        }
-        return mShowLockscreenNotifications
-                && !mEntryManager.getNotificationData().isAmbient(sbn.getKey());
-    }
+    boolean isCurrentProfile(int userId);
 
-    private void setShowLockscreenNotifications(boolean show) {
-        mShowLockscreenNotifications = show;
-    }
+    void destroy();
 
-    private void setLockscreenAllowRemoteInput(boolean allowLockscreenRemoteInput) {
-        mAllowLockscreenRemoteInput = allowLockscreenRemoteInput;
-    }
+    SparseArray<UserInfo> getCurrentProfiles();
 
-    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;
+    void setLockscreenPublicMode(boolean isProfilePublic, int userId);
 
-        setShowLockscreenNotifications(show && allowedByDpm);
+    boolean shouldShowLockscreenNotifications();
 
-        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;
+    boolean shouldHideNotifications(int userId);
+    boolean shouldHideNotifications(String key);
+    boolean shouldShowOnKeyguard(StatusBarNotification sbn);
 
-            setLockscreenAllowRemoteInput(remoteInput && remoteInputDpm);
-        } else {
-            setLockscreenAllowRemoteInput(false);
-        }
-    }
+    boolean isAnyProfilePublicMode();
 
-    /**
-     * 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;
-        }
+    void updatePublicMode();
 
-        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 = adminAllowsKeyguardFeature(userHandle,
-                    DevicePolicyManager.KEYGUARD_DISABLE_UNREDACTED_NOTIFICATIONS);
-            final boolean allowed = allowedByUser && allowedByDpm;
-            mUsersAllowingPrivateNotifications.append(userHandle, allowed);
-            return allowed;
-        }
+    boolean needsRedaction(Entry entry);
 
-        return mUsersAllowingPrivateNotifications.get(userHandle);
-    }
-
-    private boolean adminAllowsKeyguardFeature(int userHandle, int feature) {
-        if (userHandle == UserHandle.USER_ALL) {
-            return true;
-        }
-        final int dpmFlags =
-                mDevicePolicyManager.getKeyguardDisabledFeatures(null /* admin */, userHandle);
-        return (dpmFlags & feature) == 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 (isCurrentProfile(userHandle) && userHandle != mCurrentUserId) {
-            return true;
-        }
-
-        if (mUsersAllowingNotifications.indexOfKey(userHandle) < 0) {
-            final boolean allowedByUser = 0 != Settings.Secure.getIntForUser(
-                    mContext.getContentResolver(),
-                    Settings.Secure.LOCK_SCREEN_SHOW_NOTIFICATIONS, 0, userHandle);
-            final boolean allowedByDpm = adminAllowsKeyguardFeature(userHandle,
-                    DevicePolicyManager.KEYGUARD_DISABLE_SECURE_NOTIFICATIONS);
-            final boolean allowed = allowedByUser && allowedByDpm;
-            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) {
-        if (mEntryManager == null) {
-            Log.wtf(TAG, "mEntryManager was null!", new Throwable());
-            return true;
-        }
-        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();
-    }
+    boolean userAllowsPrivateNotificationsInPublic(int currentUserId);
 }
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/NotificationLockscreenUserManagerImpl.java b/packages/SystemUI/src/com/android/systemui/statusbar/NotificationLockscreenUserManagerImpl.java
new file mode 100644
index 0000000..178c5c5
--- /dev/null
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/NotificationLockscreenUserManagerImpl.java
@@ -0,0 +1,551 @@
+/*
+ * Copyright (C) 2018 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.admin.DevicePolicyManager.ACTION_DEVICE_POLICY_MANAGER_STATE_CHANGED;
+
+import android.app.ActivityManager;
+import android.app.KeyguardManager;
+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 com.android.internal.statusbar.IStatusBarService;
+import com.android.internal.statusbar.NotificationVisibility;
+import com.android.internal.widget.LockPatternUtils;
+import com.android.keyguard.KeyguardUpdateMonitor;
+import com.android.systemui.Dependency;
+import com.android.systemui.Dumpable;
+import com.android.systemui.OverviewProxyService;
+import com.android.systemui.statusbar.StatusBarStateController.StateListener;
+import com.android.systemui.statusbar.notification.NotificationData;
+import com.android.systemui.statusbar.notification.NotificationEntryManager;
+import com.android.systemui.statusbar.phone.StatusBarRemoteInputCallback;
+import com.android.systemui.statusbar.policy.DeviceProvisionedController;
+import com.android.systemui.statusbar.policy.KeyguardMonitor;
+
+import java.io.FileDescriptor;
+import java.io.PrintWriter;
+
+/**
+ * Handles keeping track of the current user, profiles, and various things related to hiding
+ * contents, redacting notifications, and the lockscreen.
+ */
+public class NotificationLockscreenUserManagerImpl implements
+        Dumpable, NotificationLockscreenUserManager, StateListener {
+    private static final String TAG = "LockscreenUserManager";
+    private static final boolean ENABLE_LOCK_SCREEN_ALLOW_REMOTE_INPUT = false;
+
+    private final DeviceProvisionedController mDeviceProvisionedController =
+            Dependency.get(DeviceProvisionedController.class);
+    private final KeyguardMonitor mKeyguardMonitor = Dependency.get(KeyguardMonitor.class);
+
+    // Lazy
+    private NotificationEntryManager mEntryManager;
+
+    private final DevicePolicyManager mDevicePolicyManager;
+    private final SparseBooleanArray mLockscreenPublicMode = new SparseBooleanArray();
+    private final SparseBooleanArray mUsersAllowingPrivateNotifications = new SparseBooleanArray();
+    private final SparseBooleanArray mUsersAllowingNotifications = new SparseBooleanArray();
+    private final UserManager mUserManager;
+    private final IStatusBarService mBarService;
+
+    private boolean mShowLockscreenNotifications;
+    private boolean mAllowLockscreenRemoteInput;
+    private LockPatternUtils mLockPatternUtils;
+    protected KeyguardManager mKeyguardManager;
+    private int mState = StatusBarState.SHADE;
+
+    protected final BroadcastReceiver mAllUsersReceiver = new BroadcastReceiver() {
+        @Override
+        public void onReceive(Context context, Intent intent) {
+            final String action = intent.getAction();
+
+            if (ACTION_DEVICE_POLICY_MANAGER_STATE_CHANGED.equals(action) &&
+                    isCurrentProfile(getSendingUserId())) {
+                mUsersAllowingPrivateNotifications.clear();
+                updateLockscreenNotificationSetting();
+                getEntryManager().updateNotifications();
+            }
+        }
+    };
+
+    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();
+                updatePublicMode();
+                mPresenter.onUserSwitched(mCurrentUserId);
+                getEntryManager().getNotificationData().filterAndSort();
+            } 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 (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) {
+                    final int count =
+                            getEntryManager().getNotificationData().getActiveNotifications().size();
+                    final int rank = getEntryManager().getNotificationData().getRank(notificationKey);
+                    final NotificationVisibility nv = NotificationVisibility.obtain(notificationKey,
+                            rank, count, true);
+                    try {
+                        mBarService.onNotificationClick(notificationKey, nv);
+                    } catch (RemoteException e) {
+                        /* ignore */
+                    }
+                }
+            }
+        }
+    };
+
+    protected final Context mContext;
+    protected final SparseArray<UserInfo> mCurrentProfiles = new SparseArray<>();
+
+    protected int mCurrentUserId = 0;
+    protected NotificationPresenter mPresenter;
+    protected ContentObserver mLockscreenSettingsObserver;
+    protected ContentObserver mSettingsObserver;
+
+    private NotificationEntryManager getEntryManager() {
+        if (mEntryManager == null) {
+            mEntryManager = Dependency.get(NotificationEntryManager.class);
+        }
+        return mEntryManager;
+    }
+
+    public NotificationLockscreenUserManagerImpl(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));
+        Dependency.get(StatusBarStateController.class).addListener(this);
+        mLockPatternUtils = new LockPatternUtils(context);
+        mKeyguardManager = context.getSystemService(KeyguardManager.class);
+    }
+
+    public void setUpWithPresenter(NotificationPresenter presenter) {
+        mPresenter = presenter;
+
+        mLockscreenSettingsObserver = new ContentObserver(Dependency.get(Dependency.MAIN_HANDLER)) {
+            @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();
+                getEntryManager().updateNotifications();
+            }
+        };
+
+        mSettingsObserver = new ContentObserver(Dependency.get(Dependency.MAIN_HANDLER)) {
+            @Override
+            public void onChange(boolean selfChange) {
+                updateLockscreenNotificationSetting();
+                if (mDeviceProvisionedController.isDeviceProvisioned()) {
+                    getEntryManager().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);
+        }
+
+        mContext.registerReceiverAsUser(mAllUsersReceiver, UserHandle.ALL,
+                new IntentFilter(ACTION_DEVICE_POLICY_MANAGER_STATE_CHANGED),
+                null, null);
+
+        IntentFilter filter = new IntentFilter();
+        filter.addAction(Intent.ACTION_USER_SWITCHED);
+        filter.addAction(Intent.ACTION_USER_ADDED);
+        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
+    }
+
+    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 notifications are temporarily disabled for this user for security reasons,
+     * regardless of the normal settings for that user.
+     */
+    private boolean shouldTemporarilyHideNotifications(int userId) {
+        if (userId == UserHandle.USER_ALL) {
+            userId = mCurrentUserId;
+        }
+        return KeyguardUpdateMonitor.getInstance(mContext).isUserInLockdown(userId);
+    }
+
+    /**
+     * 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))
+                || shouldTemporarilyHideNotifications(userId);
+    }
+
+    /**
+     * 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) {
+        if (getEntryManager() == null) {
+            Log.wtf(TAG, "mEntryManager was null!", new Throwable());
+            return true;
+        }
+        return isLockscreenPublicMode(mCurrentUserId)
+                && getEntryManager().getNotificationData().getVisibilityOverride(key) ==
+                        Notification.VISIBILITY_SECRET;
+    }
+
+    public boolean shouldShowOnKeyguard(StatusBarNotification sbn) {
+        if (getEntryManager() == null) {
+            Log.wtf(TAG, "mEntryManager was null!", new Throwable());
+            return false;
+        }
+        return mShowLockscreenNotifications
+                && !getEntryManager().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 = adminAllowsKeyguardFeature(userHandle,
+                    DevicePolicyManager.KEYGUARD_DISABLE_UNREDACTED_NOTIFICATIONS);
+            final boolean allowed = allowedByUser && allowedByDpm;
+            mUsersAllowingPrivateNotifications.append(userHandle, allowed);
+            return allowed;
+        }
+
+        return mUsersAllowingPrivateNotifications.get(userHandle);
+    }
+
+    private boolean adminAllowsKeyguardFeature(int userHandle, int feature) {
+        if (userHandle == UserHandle.USER_ALL) {
+            return true;
+        }
+        final int dpmFlags =
+                mDevicePolicyManager.getKeyguardDisabledFeatures(null /* admin */, userHandle);
+        return (dpmFlags & feature) == 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 (isCurrentProfile(userHandle) && userHandle != mCurrentUserId) {
+            return true;
+        }
+
+        if (mUsersAllowingNotifications.indexOfKey(userHandle) < 0) {
+            final boolean allowedByUser = 0 != Settings.Secure.getIntForUser(
+                    mContext.getContentResolver(),
+                    Settings.Secure.LOCK_SCREEN_SHOW_NOTIFICATIONS, 0, userHandle);
+            final boolean allowedByDpm = adminAllowsKeyguardFeature(userHandle,
+                    DevicePolicyManager.KEYGUARD_DISABLE_SECURE_NOTIFICATIONS);
+            final boolean allowed = allowedByUser && allowedByDpm;
+            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) {
+        if (getEntryManager() == null) {
+            Log.wtf(TAG, "mEntryManager was null!", new Throwable());
+            return true;
+        }
+        return getEntryManager().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;
+    }
+
+    @Override
+    public void onStateChanged(int newState) {
+        mState = newState;
+        updatePublicMode();
+    }
+
+    public void updatePublicMode() {
+        //TODO: I think there may be a race condition where mKeyguardViewManager.isShowing() returns
+        // false when it should be true. Therefore, if we are not on the SHADE, don't even bother
+        // asking if the keyguard is showing. We still need to check it though because showing the
+        // camera on the keyguard has a state of SHADE but the keyguard is still showing.
+        final boolean showingKeyguard = mState != StatusBarState.SHADE
+              || mKeyguardMonitor.isShowing();
+        final boolean devicePublic = showingKeyguard && isSecure(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.
+        SparseArray<UserInfo> currentProfiles = getCurrentProfiles();
+        for (int i = currentProfiles.size() - 1; i >= 0; i--) {
+            final int userId = currentProfiles.valueAt(i).id;
+            boolean isProfilePublic = devicePublic;
+            if (!devicePublic && userId != 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
+                // state being cached in mLockscreenPublicMode. (b/35951989)
+                if (mLockPatternUtils.isSeparateProfileChallengeEnabled(userId)
+                        && isSecure(userId)) {
+                    isProfilePublic = mKeyguardManager.isDeviceLocked(userId);
+                }
+            }
+            setLockscreenPublicMode(isProfilePublic, userId);
+        }
+    }
+
+
+//    public void updatePublicMode() {
+//        //TODO: I think there may be a race condition where mKeyguardViewManager.isShowing() returns
+//        // false when it should be true. Therefore, if we are not on the SHADE, don't even bother
+//        // asking if the keyguard is showing. We still need to check it though because showing the
+//        // camera on the keyguard has a state of SHADE but the keyguard is still showing.
+//        final boolean showingKeyguard = mState != StatusBarState.SHADE
+//              || mKeyguardMonitor.isShowing();
+//        final boolean devicePublic = showingKeyguard && isSecure(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.
+//        SparseArray<UserInfo> currentProfiles = getCurrentProfiles();
+//        for (int i = currentProfiles.size() - 1; i >= 0; i--) {
+//            final int userId = currentProfiles.valueAt(i).id;
+//            boolean isProfilePublic = devicePublic;
+//            if (!devicePublic && userId != 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
+//                // state being cached in mLockscreenPublicMode. (b/35951989)
+//                if (mLockPatternUtils.isSeparateProfileChallengeEnabled(userId)
+//                        && isSecure(userId)) {
+//                    isProfilePublic = mKeyguardManager.isDeviceLocked(userId);
+//                }
+//            }
+//            setLockscreenPublicMode(isProfilePublic, userId);
+//        }
+//    }
+
+    private boolean isSecure(int userId) {
+        return mKeyguardMonitor.isSecure() || mLockPatternUtils.isSecure(userId);
+    }
+
+    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/packages/SystemUI/src/com/android/systemui/statusbar/NotificationMediaManager.java b/packages/SystemUI/src/com/android/systemui/statusbar/NotificationMediaManager.java
index 2db9945..67b21e1 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/NotificationMediaManager.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/NotificationMediaManager.java
@@ -15,19 +15,46 @@
  */
 package com.android.systemui.statusbar;
 
+import static com.android.systemui.Dependency.MAIN_HANDLER;
+import static com.android.systemui.statusbar.StatusBarState.KEYGUARD;
+import static com.android.systemui.statusbar.phone.StatusBar.DEBUG_MEDIA_FAKE_ARTWORK;
+import static com.android.systemui.statusbar.phone.StatusBar.ENABLE_LOCKSCREEN_WALLPAPER;
+import static com.android.systemui.statusbar.phone.StatusBar.SHOW_LOCKSCREEN_MEDIA_ARTWORK;
+
+import android.annotation.Nullable;
 import android.app.Notification;
 import android.content.Context;
+import android.graphics.Bitmap;
+import android.graphics.PorterDuff;
+import android.graphics.PorterDuffXfermode;
+import android.graphics.drawable.BitmapDrawable;
+import android.graphics.drawable.ColorDrawable;
+import android.graphics.drawable.Drawable;
 import android.media.MediaMetadata;
 import android.media.session.MediaController;
 import android.media.session.MediaSession;
 import android.media.session.MediaSessionManager;
 import android.media.session.PlaybackState;
+import android.os.Handler;
+import android.os.Trace;
 import android.os.UserHandle;
 import android.util.Log;
+import android.view.View;
+import android.widget.ImageView;
 
+import com.android.systemui.Dependency;
 import com.android.systemui.Dumpable;
+import com.android.systemui.Interpolators;
+import com.android.systemui.colorextraction.SysuiColorExtractor;
 import com.android.systemui.statusbar.notification.NotificationData;
 import com.android.systemui.statusbar.notification.NotificationEntryManager;
+import com.android.systemui.statusbar.phone.BiometricUnlockController;
+import com.android.systemui.statusbar.phone.LockscreenWallpaper;
+import com.android.systemui.statusbar.phone.ScrimController;
+import com.android.systemui.statusbar.phone.ScrimState;
+import com.android.systemui.statusbar.phone.ShadeController;
+import com.android.systemui.statusbar.phone.StatusBarWindowController;
+import com.android.systemui.statusbar.policy.KeyguardMonitor;
 
 import java.io.FileDescriptor;
 import java.io.PrintWriter;
@@ -42,15 +69,45 @@
     private static final String TAG = "NotificationMediaManager";
     public static final boolean DEBUG_MEDIA = false;
 
+    private final StatusBarStateController mStatusBarStateController
+            = Dependency.get(StatusBarStateController.class);
+    private final SysuiColorExtractor mColorExtractor = Dependency.get(SysuiColorExtractor.class);
+    private final KeyguardMonitor mKeyguardMonitor = Dependency.get(KeyguardMonitor.class);
+
+    // Late binding
+    private NotificationEntryManager mEntryManager;
+
+    // Late binding, also @Nullable due to being in com.android.systemui.statusbar.phone package
+    @Nullable
+    private ShadeController mShadeController;
+    @Nullable
+    private StatusBarWindowController mStatusBarWindowController;
+
+    @Nullable
+    private BiometricUnlockController mBiometricUnlockController;
+    @Nullable
+    private ScrimController mScrimController;
+    @Nullable
+    private LockscreenWallpaper mLockscreenWallpaper;
+
+    protected final PorterDuffXfermode mSrcXferMode = new PorterDuffXfermode(PorterDuff.Mode.SRC);
+    protected final PorterDuffXfermode mSrcOverXferMode =
+            new PorterDuffXfermode(PorterDuff.Mode.SRC_OVER);
+
+    private final Handler mHandler = Dependency.get(MAIN_HANDLER);
+
     private final Context mContext;
     private final MediaSessionManager mMediaSessionManager;
 
     protected NotificationPresenter mPresenter;
-    protected NotificationEntryManager mEntryManager;
     private MediaController mMediaController;
     private String mMediaNotificationKey;
     private MediaMetadata mMediaMetadata;
 
+    private BackDropView mBackdrop;
+    private ImageView mBackdropFront;
+    private ImageView mBackdropBack;
+
     private final MediaController.Callback mMediaListener = new MediaController.Callback() {
         @Override
         public void onPlaybackStateChanged(PlaybackState state) {
@@ -77,6 +134,29 @@
         }
     };
 
+    @Nullable
+    private ShadeController getShadeController() {
+        if (mShadeController == null) {
+            mShadeController = Dependency.get(ShadeController.class);
+        }
+        return mShadeController;
+    }
+
+    @Nullable
+    private StatusBarWindowController getWindowController() {
+        if (mStatusBarWindowController == null) {
+            mStatusBarWindowController = Dependency.get(StatusBarWindowController.class);
+        }
+        return mStatusBarWindowController;
+    }
+
+    private NotificationEntryManager getEntryManager() {
+        if (mEntryManager == null) {
+            mEntryManager = Dependency.get(NotificationEntryManager.class);
+        }
+        return mEntryManager;
+    }
+
     public NotificationMediaManager(Context context) {
         mContext = context;
         mMediaSessionManager
@@ -85,10 +165,8 @@
         // in session state
     }
 
-    public void setUpWithPresenter(NotificationPresenter presenter,
-            NotificationEntryManager entryManager) {
+    public void setUpWithPresenter(NotificationPresenter presenter) {
         mPresenter = presenter;
-        mEntryManager = entryManager;
     }
 
     public void onNotificationRemoved(String key) {
@@ -109,8 +187,9 @@
     public void findAndUpdateMediaNotifications() {
         boolean metaDataChanged = false;
 
-        synchronized (mEntryManager.getNotificationData()) {
-            ArrayList<NotificationData.Entry> activeNotifications = mEntryManager
+        NotificationEntryManager manager = getEntryManager();
+        synchronized (manager.getNotificationData()) {
+            ArrayList<NotificationData.Entry> activeNotifications = manager
                     .getNotificationData().getActiveNotifications();
             final int N = activeNotifications.size();
 
@@ -199,7 +278,7 @@
         }
 
         if (metaDataChanged) {
-            mEntryManager.updateNotifications();
+            getEntryManager().updateNotifications();
         }
         mPresenter.updateMediaMetaData(metaDataChanged, true);
     }
@@ -272,4 +351,202 @@
         }
         mMediaController = null;
     }
+
+    /**
+     * Refresh or remove lockscreen artwork from media metadata or the lockscreen wallpaper.
+     */
+    public void updateMediaMetaData(boolean metaDataChanged, boolean allowEnterAnimation) {
+        Trace.beginSection("StatusBar#updateMediaMetaData");
+        if (!SHOW_LOCKSCREEN_MEDIA_ARTWORK) {
+            Trace.endSection();
+            return;
+        }
+
+        if (mBackdrop == null) {
+            Trace.endSection();
+            return; // called too early
+        }
+
+        boolean wakeAndUnlock = mBiometricUnlockController != null
+            && mBiometricUnlockController.isWakeAndUnlock();
+        if (mKeyguardMonitor.isLaunchTransitionFadingAway() || wakeAndUnlock) {
+            mBackdrop.setVisibility(View.INVISIBLE);
+            Trace.endSection();
+            return;
+        }
+
+        MediaMetadata mediaMetadata = getMediaMetadata();
+
+        if (DEBUG_MEDIA) {
+            Log.v(TAG, "DEBUG_MEDIA: updating album art for notification "
+                    + getMediaNotificationKey()
+                    + " metadata=" + mediaMetadata
+                    + " metaDataChanged=" + metaDataChanged
+                    + " state=" + mStatusBarStateController.getState());
+        }
+
+        Drawable artworkDrawable = null;
+        if (mediaMetadata != null) {
+            Bitmap artworkBitmap = mediaMetadata.getBitmap(MediaMetadata.METADATA_KEY_ART);
+            if (artworkBitmap == null) {
+                artworkBitmap = mediaMetadata.getBitmap(MediaMetadata.METADATA_KEY_ALBUM_ART);
+                // might still be null
+            }
+            if (artworkBitmap != null) {
+                artworkDrawable = new BitmapDrawable(mBackdropBack.getResources(), artworkBitmap);
+            }
+        }
+        boolean allowWhenShade = false;
+        if (ENABLE_LOCKSCREEN_WALLPAPER && artworkDrawable == null) {
+            Bitmap lockWallpaper =
+                    mLockscreenWallpaper != null ? mLockscreenWallpaper.getBitmap() : null;
+            if (lockWallpaper != null) {
+                artworkDrawable = new LockscreenWallpaper.WallpaperDrawable(
+                        mBackdropBack.getResources(), lockWallpaper);
+                // We're in the SHADE mode on the SIM screen - yet we still need to show
+                // the lockscreen wallpaper in that mode.
+                allowWhenShade = mStatusBarStateController.getState() == KEYGUARD;
+            }
+        }
+
+        boolean hideBecauseOccluded = getShadeController() != null
+                && getShadeController().isOccluded();
+
+        final boolean hasArtwork = artworkDrawable != null;
+        mColorExtractor.setHasBackdrop(hasArtwork);
+        if (mScrimController != null) {
+            mScrimController.setHasBackdrop(hasArtwork);
+        }
+
+        if ((hasArtwork || DEBUG_MEDIA_FAKE_ARTWORK)
+                && (mStatusBarStateController.getState() != StatusBarState.SHADE || allowWhenShade)
+                &&  mBiometricUnlockController != null && mBiometricUnlockController.getMode()
+                        != BiometricUnlockController.MODE_WAKE_AND_UNLOCK_PULSING
+                && !hideBecauseOccluded) {
+            // time to show some art!
+            if (mBackdrop.getVisibility() != View.VISIBLE) {
+                mBackdrop.setVisibility(View.VISIBLE);
+                if (allowEnterAnimation) {
+                    mBackdrop.setAlpha(0);
+                    mBackdrop.animate().alpha(1f);
+                } else {
+                    mBackdrop.animate().cancel();
+                    mBackdrop.setAlpha(1f);
+                }
+                if (getWindowController() != null) {
+                    getWindowController().setBackdropShowing(true);
+                }
+                metaDataChanged = true;
+                if (DEBUG_MEDIA) {
+                    Log.v(TAG, "DEBUG_MEDIA: Fading in album artwork");
+                }
+            }
+            if (metaDataChanged) {
+                if (mBackdropBack.getDrawable() != null) {
+                    Drawable drawable =
+                            mBackdropBack.getDrawable().getConstantState()
+                                    .newDrawable(mBackdropFront.getResources()).mutate();
+                    mBackdropFront.setImageDrawable(drawable);
+                    mBackdropFront.setAlpha(1f);
+                    mBackdropFront.setVisibility(View.VISIBLE);
+                } else {
+                    mBackdropFront.setVisibility(View.INVISIBLE);
+                }
+
+                if (DEBUG_MEDIA_FAKE_ARTWORK) {
+                    final int c = 0xFF000000 | (int)(Math.random() * 0xFFFFFF);
+                    Log.v(TAG, String.format("DEBUG_MEDIA: setting new color: 0x%08x", c));
+                    mBackdropBack.setBackgroundColor(0xFFFFFFFF);
+                    mBackdropBack.setImageDrawable(new ColorDrawable(c));
+                } else {
+                    mBackdropBack.setImageDrawable(artworkDrawable);
+                }
+
+                if (mBackdropFront.getVisibility() == View.VISIBLE) {
+                    if (DEBUG_MEDIA) {
+                        Log.v(TAG, "DEBUG_MEDIA: Crossfading album artwork from "
+                                + mBackdropFront.getDrawable()
+                                + " to "
+                                + mBackdropBack.getDrawable());
+                    }
+                    mBackdropFront.animate()
+                            .setDuration(250)
+                            .alpha(0f).withEndAction(mHideBackdropFront);
+                }
+            }
+        } else {
+            // need to hide the album art, either because we are unlocked, on AOD
+            // or because the metadata isn't there to support it
+            if (mBackdrop.getVisibility() != View.GONE) {
+                if (DEBUG_MEDIA) {
+                    Log.v(TAG, "DEBUG_MEDIA: Fading out album artwork");
+                }
+                boolean cannotAnimateDoze = getShadeController() != null
+                        && getShadeController().isDozing()
+                        && !ScrimState.AOD.getAnimateChange();
+                if (mBiometricUnlockController != null && mBiometricUnlockController.getMode()
+                        == BiometricUnlockController.MODE_WAKE_AND_UNLOCK_PULSING
+                        || hideBecauseOccluded || cannotAnimateDoze) {
+
+                    // We are unlocking directly - no animation!
+                    mBackdrop.setVisibility(View.GONE);
+                    mBackdropBack.setImageDrawable(null);
+                    if (getWindowController() != null) {
+                        getWindowController().setBackdropShowing(false);
+                    }
+                } else {
+                    if (getWindowController() != null) {
+                        getWindowController().setBackdropShowing(false);
+                    }
+                    mBackdrop.animate()
+                            .alpha(0)
+                            .setInterpolator(Interpolators.ACCELERATE_DECELERATE)
+                            .setDuration(300)
+                            .setStartDelay(0)
+                            .withEndAction(() -> {
+                                mBackdrop.setVisibility(View.GONE);
+                                mBackdropFront.animate().cancel();
+                                mBackdropBack.setImageDrawable(null);
+                                mHandler.post(mHideBackdropFront);
+                            });
+                    if (mKeyguardMonitor.isKeyguardFadingAway()) {
+                        mBackdrop.animate()
+                                // Make it disappear faster, as the focus should be on the activity
+                                // behind.
+                                .setDuration(mKeyguardMonitor.getKeyguardFadingAwayDuration() / 2)
+                                .setStartDelay(mKeyguardMonitor.getKeyguardFadingAwayDelay())
+                                .setInterpolator(Interpolators.LINEAR)
+                                .start();
+                    }
+                }
+            }
+        }
+        Trace.endSection();
+    }
+
+    public void setup(BackDropView backdrop, ImageView backdropFront, ImageView backdropBack,
+            BiometricUnlockController biometricUnlockController, ScrimController scrimController,
+            LockscreenWallpaper lockscreenWallpaper) {
+        mBackdrop = backdrop;
+        mBackdropFront = backdropFront;
+        mBackdropBack = backdropBack;
+        mBiometricUnlockController = biometricUnlockController;
+        mScrimController = scrimController;
+        mLockscreenWallpaper = lockscreenWallpaper;
+    }
+
+    /**
+     * Hide the album artwork that is fading out and release its bitmap.
+     */
+    protected final Runnable mHideBackdropFront = new Runnable() {
+        @Override
+        public void run() {
+            if (DEBUG_MEDIA) {
+                Log.v(TAG, "DEBUG_MEDIA: removing fade layer");
+            }
+            mBackdropFront.setVisibility(View.INVISIBLE);
+            mBackdropFront.animate().cancel();
+            mBackdropFront.setImageDrawable(null);
+        }
+    };
 }
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/NotificationPresenter.java b/packages/SystemUI/src/com/android/systemui/statusbar/NotificationPresenter.java
index c58eb80..5c8f4cb 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/NotificationPresenter.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/NotificationPresenter.java
@@ -19,6 +19,7 @@
 import android.os.Handler;
 import android.view.View;
 
+import com.android.systemui.statusbar.notification.ActivityLaunchAnimator;
 import com.android.systemui.statusbar.notification.NotificationData;
 import com.android.systemui.statusbar.notification.NotificationEntryManager;
 import com.android.systemui.statusbar.notification.row.ActivatableNotificationView;
@@ -31,9 +32,7 @@
  * 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 extends NotificationData.Environment,
-        NotificationRemoteInputManager.Callback,
-        ExpandableNotificationRow.OnExpandClickListener,
+public interface NotificationPresenter extends ExpandableNotificationRow.OnExpandClickListener,
         ActivatableNotificationView.OnActivatedListener,
         NotificationEntryManager.Callback {
     /**
@@ -43,59 +42,23 @@
     boolean isPresenterFullyCollapsed();
 
     /**
-     * Returns true if the presenter is locked. For example, if the keyguard is active.
-     */
-    boolean isPresenterLocked();
-
-    /**
      * Runs the given intent. The presenter may want to run some animations or close itself when
      * this happens.
      */
     void startNotificationGutsIntent(Intent intent, int appUid, ExpandableNotificationRow row);
 
     /**
-     * Returns the Handler for NotificationPresenter.
-     */
-    Handler getHandler();
-
-    /**
      * Refresh or remove lockscreen artwork from media metadata or the lockscreen wallpaper.
      */
     void updateMediaMetaData(boolean metaDataChanged, boolean allowEnterAnimation);
 
     /**
-     * Called when the locked status of the device is changed for a work profile.
-     */
-    void onWorkChallengeChanged();
-
-    /**
      * Called when the current user changes.
      * @param newUserId new user id
      */
     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();
@@ -114,7 +77,36 @@
     int getMaxNotificationsWhileLocked(boolean recompute);
 
     /**
-     * Called when the row states are updated by NotificationViewHierarchyManager.
+     * True if the presenter
+     * @return
+     */
+    default boolean isPresenterLocked() { return false; }
+
+    /**
+     * Called when the row states are updated by {@link NotificationViewHierarchyManager}.
      */
     void onUpdateRowStates();
+
+    /**
+     * @return true if the shade is collapsing.
+     */
+    boolean isCollapsing();
+
+    /**
+     * @return true if the shade is collapsing to show an activity over the lock screen
+     */
+    default public boolean isCollapsingToShowActivityOverLockscreen() {
+        return false;
+    }
+
+    /**
+     * Get the {@link ActivityLaunchAnimator} from the presenter so it can be queried by
+     * {@link com.android.systemui.statusbar.phone.StatusBar}
+     * @return the current animator
+     * @deprecated This is only here for now because StatusBar is still the ActivityLaunchAnimator
+     * callback but shouldn't be.
+     */
+    default public ActivityLaunchAnimator getActivityLaunchAnimator() {
+        return null;
+    }
 }
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/NotificationRemoteInputManager.java b/packages/SystemUI/src/com/android/systemui/statusbar/NotificationRemoteInputManager.java
index ea7e03e..f30377e 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/NotificationRemoteInputManager.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/NotificationRemoteInputManager.java
@@ -20,6 +20,7 @@
 import android.annotation.NonNull;
 import android.app.ActivityManager;
 import android.app.ActivityOptions;
+import android.app.KeyguardManager;
 import android.app.Notification;
 import android.app.PendingIntent;
 import android.app.RemoteInput;
@@ -46,9 +47,11 @@
 import com.android.internal.statusbar.NotificationVisibility;
 import com.android.systemui.Dependency;
 import com.android.systemui.Dumpable;
+import com.android.systemui.InitController;
 import com.android.systemui.statusbar.notification.NotificationData;
 import com.android.systemui.statusbar.notification.NotificationEntryManager;
 import com.android.systemui.statusbar.notification.row.ExpandableNotificationRow;
+import com.android.systemui.statusbar.phone.ShadeController;
 import com.android.systemui.statusbar.policy.RemoteInputView;
 
 import java.io.FileDescriptor;
@@ -97,13 +100,18 @@
             Dependency.get(NotificationLockscreenUserManager.class);
     protected final SmartReplyController mSmartReplyController =
             Dependency.get(SmartReplyController.class);
+    private final NotificationEntryManager mEntryManager
+            = Dependency.get(NotificationEntryManager.class);
+
+    // Lazy
+    private ShadeController mShadeController;
 
     protected final Context mContext;
     private final UserManager mUserManager;
+    private final KeyguardManager mKeyguardManager;
 
     protected RemoteInputController mRemoteInputController;
     protected NotificationPresenter mPresenter;
-    protected NotificationEntryManager mEntryManager;
     protected NotificationLifetimeExtender.NotificationSafeToRemoveCallback
             mNotificationLifetimeFinishedCallback;
     protected IStatusBarService mBarService;
@@ -115,7 +123,7 @@
         @Override
         public boolean onClickHandler(
                 final View view, final PendingIntent pendingIntent, final Intent fillInIntent) {
-            mPresenter.wakeUpIfDozing(SystemClock.uptimeMillis(), view);
+            getShadeController().wakeUpIfDozing(SystemClock.uptimeMillis(), view);
 
             if (handleRemoteInput(view, pendingIntent)) {
                 return true;
@@ -240,7 +248,7 @@
                     return true;
                 }
                 if (mUserManager.getUserInfo(userId).isManagedProfile()
-                        && mPresenter.isDeviceLocked(userId)) {
+                        && mKeyguardManager.isDeviceLocked(userId)) {
                     mCallback.onLockedWorkRemoteInput(userId, row, view);
                     return true;
                 }
@@ -291,20 +299,26 @@
         }
     };
 
+    private ShadeController getShadeController() {
+        if (mShadeController == null) {
+            mShadeController = Dependency.get(ShadeController.class);
+        }
+        return mShadeController;
+    }
+
     public NotificationRemoteInputManager(Context context) {
         mContext = context;
         mBarService = IStatusBarService.Stub.asInterface(
                 ServiceManager.getService(Context.STATUS_BAR_SERVICE));
         mUserManager = (UserManager) mContext.getSystemService(Context.USER_SERVICE);
         addLifetimeExtenders();
+        mKeyguardManager = context.getSystemService(KeyguardManager.class);
     }
 
     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() {
@@ -318,7 +332,7 @@
                     // 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(() -> {
+                    Dependency.get(Dependency.MAIN_HANDLER).postDelayed(() -> {
                         if (mEntriesKeptForRemoteInputActive.remove(entry)) {
                             mNotificationLifetimeFinishedCallback.onSafeToRemove(entry.key);
                         }
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/NotificationShelf.java b/packages/SystemUI/src/com/android/systemui/statusbar/NotificationShelf.java
index 1495abf..cd3da123 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/NotificationShelf.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/NotificationShelf.java
@@ -34,6 +34,7 @@
 import android.view.WindowInsets;
 import android.view.accessibility.AccessibilityNodeInfo;
 
+import com.android.internal.annotations.VisibleForTesting;
 import com.android.systemui.Dependency;
 import com.android.systemui.Interpolators;
 import com.android.systemui.R;
@@ -103,7 +104,8 @@
     }
 
     @Override
-    protected void onFinishInflate() {
+    @VisibleForTesting
+    public void onFinishInflate() {
         super.onFinishInflate();
         mShelfIcons = findViewById(R.id.content);
         mShelfIcons.setClipChildren(false);
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/NotificationViewHierarchyManager.java b/packages/SystemUI/src/com/android/systemui/statusbar/NotificationViewHierarchyManager.java
index 5b3082b..92765bb 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/NotificationViewHierarchyManager.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/NotificationViewHierarchyManager.java
@@ -24,6 +24,7 @@
 import android.view.ViewGroup;
 
 import com.android.systemui.Dependency;
+import com.android.systemui.InitController;
 import com.android.systemui.R;
 import com.android.systemui.statusbar.notification.NotificationData;
 import com.android.systemui.statusbar.notification.NotificationEntryManager;
@@ -31,6 +32,7 @@
 import com.android.systemui.statusbar.notification.row.ExpandableNotificationRow;
 import com.android.systemui.statusbar.notification.stack.NotificationListContainer;
 import com.android.systemui.statusbar.phone.NotificationGroupManager;
+import com.android.systemui.statusbar.phone.ShadeController;
 
 import java.util.ArrayList;
 import java.util.HashMap;
@@ -57,6 +59,13 @@
             Dependency.get(NotificationGroupManager.class);
     protected final VisualStabilityManager mVisualStabilityManager =
             Dependency.get(VisualStabilityManager.class);
+    private final StatusBarStateController mStatusBarStateController =
+            Dependency.get(StatusBarStateController.class);
+    private final NotificationEntryManager mEntryManager =
+            Dependency.get(NotificationEntryManager.class);
+
+    // Lazy
+    private ShadeController mShadeController;
 
     /**
      * {@code true} if notifications not part of a group should by default be rendered in their
@@ -66,9 +75,15 @@
     private final boolean mAlwaysExpandNonGroupedNotification;
 
     private NotificationPresenter mPresenter;
-    private NotificationEntryManager mEntryManager;
     private NotificationListContainer mListContainer;
 
+    private ShadeController getShadeController() {
+        if (mShadeController == null) {
+            mShadeController = Dependency.get(ShadeController.class);
+        }
+        return mShadeController;
+    }
+
     public NotificationViewHierarchyManager(Context context) {
         Resources res = context.getResources();
         mAlwaysExpandNonGroupedNotification =
@@ -76,9 +91,8 @@
     }
 
     public void setUpWithPresenter(NotificationPresenter presenter,
-            NotificationEntryManager entryManager, NotificationListContainer listContainer) {
+            NotificationListContainer listContainer) {
         mPresenter = presenter;
-        mEntryManager = entryManager;
         mListContainer = listContainer;
     }
 
@@ -291,9 +305,9 @@
         final int N = mListContainer.getContainerChildCount();
 
         int visibleNotifications = 0;
-        boolean isLocked = mPresenter.isPresenterLocked();
+        boolean onKeyguard = mStatusBarStateController.getState() == StatusBarState.KEYGUARD;
         int maxNotifications = -1;
-        if (isLocked) {
+        if (onKeyguard) {
             maxNotifications = mPresenter.getMaxNotificationsWhileLocked(true /* recompute */);
         }
         mListContainer.setMaxDisplayedNotifications(maxNotifications);
@@ -311,9 +325,9 @@
             boolean isChildNotification =
                     mGroupManager.isChildInGroupWithSummary(entry.notification);
 
-            row.setOnKeyguard(isLocked);
+            row.setOnKeyguard(onKeyguard);
 
-            if (!isLocked) {
+            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
@@ -321,7 +335,7 @@
                         && !row.isLowPriority()));
             }
 
-            entry.row.setOnAmbient(mPresenter.isDozing());
+            entry.row.setOnAmbient(getShadeController().isDozing());
             int userId = entry.notification.getUserId();
             boolean suppressedSummary = mGroupManager.isSummaryOfSuppressedGroup(
                     entry.notification) && !entry.row.isRemoved();
@@ -340,7 +354,7 @@
             }
             if (suppressedSummary
                     || mLockscreenUserManager.shouldHideNotifications(userId)
-                    || (isLocked && !showOnKeyguard)) {
+                    || (onKeyguard && !showOnKeyguard)) {
                 entry.row.setVisibility(View.GONE);
             } else {
                 boolean wasGone = entry.row.getVisibility() == View.GONE;
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/StatusBarStateController.java b/packages/SystemUI/src/com/android/systemui/statusbar/StatusBarStateController.java
index 78a5817..12c0fcb 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/StatusBarStateController.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/StatusBarStateController.java
@@ -44,6 +44,7 @@
     private int mState;
     private int mLastState;
     private boolean mLeaveOpenOnKeyguardHide;
+    private boolean mKeyguardRequested;
 
     // TODO: b/115739177 (remove this explicit ordering if we can)
     @Retention(SOURCE)
@@ -173,6 +174,14 @@
         }
     }
 
+    public void setKeyguardRequested(boolean keyguardRequested) {
+        mKeyguardRequested = keyguardRequested;
+    }
+
+    public boolean isKeyguardRequested() {
+        return mKeyguardRequested;
+    }
+
     public static String describe(int state) {
         return StatusBarState.toShortString(state);
     }
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/car/CarStatusBar.java b/packages/SystemUI/src/com/android/systemui/statusbar/car/CarStatusBar.java
index 24665ea..8799341 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/car/CarStatusBar.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/car/CarStatusBar.java
@@ -525,12 +525,6 @@
     }
 
     @Override
-    public void updateMediaMetaData(boolean metaDataChanged, boolean allowEnterAnimation) {
-        // Do nothing, we don't want to display media art in the lock screen for a car.
-    }
-
-
-    @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/packages/SystemUI/src/com/android/systemui/statusbar/notification/AppOpsListener.java b/packages/SystemUI/src/com/android/systemui/statusbar/notification/AppOpsListener.java
index 8cae806..9e99fbb 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/AppOpsListener.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/AppOpsListener.java
@@ -33,10 +33,11 @@
     // Dependencies:
     private final ForegroundServiceController mFsc =
             Dependency.get(ForegroundServiceController.class);
+    private final NotificationEntryManager mEntryManager =
+            Dependency.get(NotificationEntryManager.class);
 
     private final Context mContext;
     protected NotificationPresenter mPresenter;
-    protected NotificationEntryManager mEntryManager;
     protected final AppOpsManager mAppOps;
 
     protected static final int[] OPS = new int[] {AppOpsManager.OP_CAMERA,
@@ -48,10 +49,8 @@
         mAppOps = (AppOpsManager) context.getSystemService(Context.APP_OPS_SERVICE);
     }
 
-    public void setUpWithPresenter(NotificationPresenter presenter,
-            NotificationEntryManager entryManager) {
+    public void setUpWithPresenter(NotificationPresenter presenter) {
         mPresenter = presenter;
-        mEntryManager = entryManager;
         mAppOps.startWatchingActive(OPS, this);
     }
 
@@ -62,7 +61,7 @@
     @Override
     public void onOpActiveChanged(int code, int uid, String packageName, boolean active) {
         mFsc.onAppOpChanged(code, uid, packageName, active);
-        mPresenter.getHandler().post(() -> {
+        Dependency.get(Dependency.MAIN_HANDLER).post(() -> {
           mEntryManager.updateNotificationsForAppOp(code, uid, packageName, active);
         });
     }
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/NotificationData.java b/packages/SystemUI/src/com/android/systemui/statusbar/notification/NotificationData.java
index fbf12ed..3539fff 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/NotificationData.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/NotificationData.java
@@ -51,21 +51,22 @@
 import android.view.View;
 import android.widget.ImageView;
 
-import androidx.annotation.Nullable;
-
 import com.android.internal.annotations.VisibleForTesting;
 import com.android.internal.statusbar.StatusBarIcon;
 import com.android.internal.util.ArrayUtils;
 import com.android.internal.util.ContrastColorUtil;
 import com.android.systemui.Dependency;
 import com.android.systemui.ForegroundServiceController;
+import com.android.systemui.InitController;
 import com.android.systemui.statusbar.InflationTask;
+import com.android.systemui.statusbar.NotificationLockscreenUserManager;
+import com.android.systemui.statusbar.NotificationMediaManager;
 import com.android.systemui.statusbar.StatusBarIconView;
 import com.android.systemui.statusbar.notification.row.ExpandableNotificationRow;
 import com.android.systemui.statusbar.phone.NotificationGroupManager;
+import com.android.systemui.statusbar.phone.ShadeController;
 import com.android.systemui.statusbar.phone.StatusBar;
 import com.android.systemui.statusbar.policy.HeadsUpManager;
-import com.android.systemui.statusbar.policy.ZenModeController;
 
 import java.io.PrintWriter;
 import java.util.ArrayList;
@@ -74,16 +75,23 @@
 import java.util.List;
 import java.util.Objects;
 
+import androidx.annotation.Nullable;
+
 /**
  * The list of currently displaying notifications.
  */
 public class NotificationData {
 
-    private final Environment mEnvironment;
-    private HeadsUpManager mHeadsUpManager;
+    /**
+     * These dependencies are late init-ed
+     */
+    private KeyguardEnvironment mEnvironment;
+    private ShadeController mShadeController;
+    private NotificationMediaManager mMediaManager;
+    private ForegroundServiceController mFsc;
+    private NotificationLockscreenUserManager mUserManager;
 
-    final ZenModeController mZen = Dependency.get(ZenModeController.class);
-    final ForegroundServiceController mFsc = Dependency.get(ForegroundServiceController.class);
+    private HeadsUpManager mHeadsUpManager;
 
     public static final class Entry {
         private static final long LAUNCH_COOLDOWN = 2000;
@@ -375,7 +383,8 @@
     private final ArrayList<Entry> mSortedAndFiltered = new ArrayList<>();
     private final ArrayList<Entry> mFilteredForUser = new ArrayList<>();
 
-    private NotificationGroupManager mGroupManager;
+    private final NotificationGroupManager mGroupManager
+            = Dependency.get(NotificationGroupManager.class);
 
     private RankingMap mRankingMap;
     private final Ranking mTmpRanking = new Ranking();
@@ -407,7 +416,7 @@
                 bRank = mRankingB.getRank();
             }
 
-            String mediaNotification = mEnvironment.getCurrentMediaNotificationKey();
+            String mediaNotification = getMediaManager().getMediaNotificationKey();
 
             // IMPORTANCE_MIN media streams are allowed to drift to the bottom
             final boolean aMedia = a.key.equals(mediaNotification)
@@ -442,13 +451,43 @@
         }
     };
 
-    public NotificationData(Environment environment) {
-        mEnvironment = environment;
-        mGroupManager = environment.getGroupManager();
+    private KeyguardEnvironment getEnvironment() {
+        if (mEnvironment == null) {
+            mEnvironment = Dependency.get(KeyguardEnvironment.class);
+        }
+        return mEnvironment;
+    }
+
+    private ShadeController getShadeController() {
+        if (mShadeController == null) {
+            mShadeController = Dependency.get(ShadeController.class);
+        }
+        return mShadeController;
+    }
+
+    private NotificationMediaManager getMediaManager() {
+        if (mMediaManager == null) {
+            mMediaManager = Dependency.get(NotificationMediaManager.class);
+        }
+        return mMediaManager;
+    }
+
+    private ForegroundServiceController getFsc() {
+        if (mFsc == null) {
+            mFsc = Dependency.get(ForegroundServiceController.class);
+        }
+        return mFsc;
+    }
+
+    private NotificationLockscreenUserManager getUserManager() {
+        if (mUserManager == null) {
+            mUserManager = Dependency.get(NotificationLockscreenUserManager.class);
+        }
+        return mUserManager;
     }
 
     /**
-     * Returns the sorted list of active notifications (depending on {@link Environment}
+     * Returns the sorted list of active notifications (depending on {@link KeyguardEnvironment}
      *
      * <p>
      * This call doesn't update the list of active notifications. Call {@link #filterAndSort()}
@@ -468,7 +507,7 @@
             for (int i = 0; i < N; i++) {
                 Entry entry = mEntries.valueAt(i);
                 final StatusBarNotification sbn = entry.notification;
-                if (!mEnvironment.isNotificationForCurrentProfiles(sbn)) {
+                if (!getEnvironment().isNotificationForCurrentProfiles(sbn)) {
                     continue;
                 }
                 mFilteredForUser.add(entry);
@@ -719,27 +758,27 @@
      */
     public boolean shouldFilterOut(Entry entry) {
         final StatusBarNotification sbn = entry.notification;
-        if (!(mEnvironment.isDeviceProvisioned() ||
+        if (!(getEnvironment().isDeviceProvisioned() ||
                 showNotificationEvenIfUnprovisioned(sbn))) {
             return true;
         }
 
-        if (!mEnvironment.isNotificationForCurrentProfiles(sbn)) {
+        if (!getEnvironment().isNotificationForCurrentProfiles(sbn)) {
             return true;
         }
 
-        if (mEnvironment.isSecurelyLocked(sbn.getUserId()) &&
+        if (getUserManager().isLockscreenPublicMode(sbn.getUserId()) &&
                 (sbn.getNotification().visibility == Notification.VISIBILITY_SECRET
-                        || mEnvironment.shouldHideNotifications(sbn.getUserId())
-                        || mEnvironment.shouldHideNotifications(sbn.getKey()))) {
+                        || getUserManager().shouldHideNotifications(sbn.getUserId())
+                        || getUserManager().shouldHideNotifications(sbn.getKey()))) {
             return true;
         }
 
-        if (mEnvironment.isDozing() && shouldSuppressAmbient(entry)) {
+        if (getShadeController().isDozing() && shouldSuppressAmbient(entry)) {
             return true;
         }
 
-        if (!mEnvironment.isDozing() && shouldSuppressNotificationList(entry)) {
+        if (!getShadeController().isDozing() && shouldSuppressNotificationList(entry)) {
             return true;
         }
 
@@ -752,15 +791,16 @@
             return true;
         }
 
-        if (mFsc.isDungeonNotification(sbn) && !mFsc.isDungeonNeededForUser(sbn.getUserId())) {
+        if (getFsc().isDungeonNotification(sbn)
+                && !getFsc().isDungeonNeededForUser(sbn.getUserId())) {
             // this is a foreground-service disclosure for a user that does not need to show one
             return true;
         }
-        if (mFsc.isSystemAlertNotification(sbn)) {
+        if (getFsc().isSystemAlertNotification(sbn)) {
             final String[] apps = sbn.getNotification().extras.getStringArray(
                     Notification.EXTRA_FOREGROUND_APPS);
             if (apps != null && apps.length >= 1) {
-                if (!mFsc.isSystemAlertWarningNeeded(sbn.getUserId(), apps[0])) {
+                if (!getFsc().isSystemAlertWarningNeeded(sbn.getUserId(), apps[0])) {
                     return true;
                 }
             }
@@ -838,18 +878,8 @@
     /**
      * Provides access to keyguard state and user settings dependent data.
      */
-    public interface Environment {
-        public boolean isSecurelyLocked(int userId);
-        public boolean shouldHideNotifications(int userid);
-        public boolean shouldHideNotifications(String key);
-        public boolean isDeviceProvisioned();
-        public boolean isNotificationForCurrentProfiles(StatusBarNotification sbn);
-        public String getCurrentMediaNotificationKey();
-        public NotificationGroupManager getGroupManager();
-
-        /**
-         * @return true iff the device is dozing
-         */
-        boolean isDozing();
+    public interface KeyguardEnvironment {
+        boolean isDeviceProvisioned();
+        boolean isNotificationForCurrentProfiles(StatusBarNotification sbn);
     }
 }
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/NotificationEntryManager.java b/packages/SystemUI/src/com/android/systemui/statusbar/notification/NotificationEntryManager.java
index 28d339a..d136c4a 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/NotificationEntryManager.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/NotificationEntryManager.java
@@ -62,6 +62,7 @@
 import com.android.systemui.Dumpable;
 import com.android.systemui.EventLogTags;
 import com.android.systemui.ForegroundServiceController;
+import com.android.systemui.InitController;
 import com.android.systemui.R;
 import com.android.systemui.UiOffloadThread;
 import com.android.systemui.statusbar.NotificationLifetimeExtender;
@@ -74,13 +75,15 @@
 import com.android.systemui.statusbar.NotificationRemoteInputManager;
 import com.android.systemui.statusbar.NotificationUiAdjustment;
 import com.android.systemui.statusbar.NotificationUpdateHandler;
+import com.android.systemui.statusbar.notification.NotificationData.KeyguardEnvironment;
+import com.android.systemui.statusbar.notification.row.ExpandableNotificationRow;
+import com.android.systemui.statusbar.notification.row.NotificationGutsManager;
 import com.android.systemui.statusbar.notification.row.NotificationInflater;
 import com.android.systemui.statusbar.notification.row.NotificationInflater.InflationFlag;
 import com.android.systemui.statusbar.notification.row.RowInflaterTask;
-import com.android.systemui.statusbar.notification.row.ExpandableNotificationRow;
-import com.android.systemui.statusbar.notification.row.NotificationGutsManager;
 import com.android.systemui.statusbar.notification.stack.NotificationListContainer;
 import com.android.systemui.statusbar.phone.NotificationGroupManager;
+import com.android.systemui.statusbar.phone.ShadeController;
 import com.android.systemui.statusbar.phone.StatusBar;
 import com.android.systemui.statusbar.policy.DeviceProvisionedController;
 import com.android.systemui.statusbar.policy.HeadsUpManager;
@@ -110,33 +113,31 @@
     protected final HashMap<String, NotificationData.Entry> mPendingNotifications = new HashMap<>();
     protected final NotificationClicker mNotificationClicker = new NotificationClicker();
 
-    // Dependencies:
-    protected final NotificationLockscreenUserManager mLockscreenUserManager =
-            Dependency.get(NotificationLockscreenUserManager.class);
-    protected final NotificationGroupManager mGroupManager =
+    private final NotificationGroupManager mGroupManager =
             Dependency.get(NotificationGroupManager.class);
-    protected final NotificationGutsManager mGutsManager =
+    private 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 =
+    private final MetricsLogger mMetricsLogger = Dependency.get(MetricsLogger.class);
+    private final DeviceProvisionedController mDeviceProvisionedController =
             Dependency.get(DeviceProvisionedController.class);
-    protected final VisualStabilityManager mVisualStabilityManager =
+    private final VisualStabilityManager mVisualStabilityManager =
             Dependency.get(VisualStabilityManager.class);
-    protected final UiOffloadThread mUiOffloadThread = Dependency.get(UiOffloadThread.class);
-    protected final ForegroundServiceController mForegroundServiceController =
+    private final UiOffloadThread mUiOffloadThread = Dependency.get(UiOffloadThread.class);
+    private final ForegroundServiceController mForegroundServiceController =
             Dependency.get(ForegroundServiceController.class);
-    protected final NotificationListener mNotificationListener =
-            Dependency.get(NotificationListener.class);
-    protected AmbientPulseManager mAmbientPulseManager = Dependency.get(AmbientPulseManager.class);
+    private final AmbientPulseManager mAmbientPulseManager =
+            Dependency.get(AmbientPulseManager.class);
+
+    // Lazily retrieved dependencies
+    private NotificationRemoteInputManager mRemoteInputManager;
+    private NotificationMediaManager mMediaManager;
+    private NotificationListener mNotificationListener;
+    private ShadeController mShadeController;
 
     protected IDreamManager mDreamManager;
     protected IStatusBarService mBarService;
-    protected NotificationPresenter mPresenter;
-    protected Callback mCallback;
+    private NotificationPresenter mPresenter;
+    private Callback mCallback;
     protected PowerManager mPowerManager;
     protected NotificationListenerService.RankingMap mLatestRankingMap;
     protected HeadsUpManager mHeadsUpManager;
@@ -149,7 +150,6 @@
             = new ArrayList<>();
     private ExpandableNotificationRow.OnAppOpsClickListener mOnAppOpsClickListener;
 
-
     private final class NotificationClicker implements View.OnClickListener {
 
         @Override
@@ -159,7 +159,7 @@
                 return;
             }
 
-            mPresenter.wakeUpIfDozing(SystemClock.uptimeMillis(), v);
+            getShadeController().wakeUpIfDozing(SystemClock.uptimeMillis(), v);
 
             final ExpandableNotificationRow row = (ExpandableNotificationRow) v;
             final StatusBarNotification sbn = row.getStatusBarNotification();
@@ -232,20 +232,55 @@
         mDreamManager = IDreamManager.Stub.asInterface(
                 ServiceManager.checkService(DreamService.DREAM_SERVICE));
         mMessagingUtil = new NotificationMessagingUtil(context);
+        mNotificationData = new NotificationData();
+        Dependency.get(InitController.class).addPostInitTask(this::onPostInit);
+    }
+
+    private void onPostInit() {
         mGroupManager.setPendingEntries(mPendingNotifications);
     }
 
+    /**
+     * Our dependencies can have cyclic references, so some need to be lazy
+     */
+    private NotificationRemoteInputManager getRemoteInputManager() {
+        if (mRemoteInputManager == null) {
+            mRemoteInputManager = Dependency.get(NotificationRemoteInputManager.class);
+        }
+        return mRemoteInputManager;
+    }
+
+    private NotificationMediaManager getMediaManager() {
+        if (mMediaManager == null) {
+            mMediaManager = Dependency.get(NotificationMediaManager.class);
+        }
+        return mMediaManager;
+    }
+
+    private NotificationListener getNotificationListener() {
+        if (mNotificationListener == null) {
+            mNotificationListener = Dependency.get(NotificationListener.class);
+        }
+        return mNotificationListener;
+    }
+
+    private ShadeController getShadeController() {
+        if (mShadeController == null) {
+            mShadeController = Dependency.get(ShadeController.class);
+        }
+        return mShadeController;
+    }
+
     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()) {
+        mHeadsUpObserver = new ContentObserver(Dependency.get(Dependency.MAIN_HANDLER)) {
             @Override
             public void onChange(boolean selfChange) {
                 boolean wasUsing = mUseHeadsUp;
@@ -278,7 +313,7 @@
         mNotificationLifetimeExtenders.add(mHeadsUpManager);
         mNotificationLifetimeExtenders.add(mAmbientPulseManager);
         mNotificationLifetimeExtenders.add(mGutsManager);
-        mNotificationLifetimeExtenders.addAll(mRemoteInputManager.getLifetimeExtenders());
+        mNotificationLifetimeExtenders.addAll(getRemoteInputManager().getLifetimeExtenders());
 
         for (NotificationLifetimeExtender extender : mNotificationLifetimeExtenders) {
             extender.setCallback(key -> removeNotification(key, mLatestRankingMap));
@@ -294,6 +329,14 @@
         return mNotificationData;
     }
 
+    protected Context getContext() {
+        return mContext;
+    }
+
+    protected NotificationPresenter getPresenter() {
+        return mPresenter;
+    }
+
     public ExpandableNotificationRow.LongPressListener getNotificationLongClicker() {
         return mGutsManager::openGuts;
     }
@@ -348,7 +391,7 @@
         row.setInflationCallback(this);
         row.setLongPressListener(getNotificationLongClicker());
         mListContainer.bindRow(row);
-        mRemoteInputManager.bindRow(row);
+        getRemoteInputManager().bindRow(row);
 
         // Get the app name.
         // Note that Notification.Builder#bindHeaderAppName has similar logic
@@ -387,7 +430,7 @@
                 true);
         NotificationData.Entry entry = mNotificationData.get(n.getKey());
 
-        mRemoteInputManager.onPerformRemoveNotification(n, entry);
+        getRemoteInputManager().onPerformRemoveNotification(n, entry);
         final String pkg = n.getPackageName();
         final String tag = n.getTag();
         final int id = n.getId();
@@ -512,7 +555,7 @@
             // 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)
+            boolean ignoreEarliestRemovalTime = getRemoteInputManager().getController().isSpinning(key)
                     && !FORCE_REMOTE_INPUT_HISTORY
                     || !mVisualStabilityManager.isReorderingAllowed();
             mHeadsUpManager.removeNotification(key, ignoreEarliestRemovalTime);
@@ -547,7 +590,7 @@
             extender.setShouldManageLifetime(entry, false /* shouldManage */);
         }
 
-        mMediaManager.onNotificationRemoved(key);
+        getMediaManager().onNotificationRemoved(key);
         mForegroundServiceController.removeNotification(entry.notification);
 
         if (entry.row != null) {
@@ -602,8 +645,8 @@
                 boolean isForeground = (row.getStatusBarNotification().getNotification().flags
                         & Notification.FLAG_FOREGROUND_SERVICE) != 0;
                 boolean keepForReply =
-                        mRemoteInputManager.shouldKeepForRemoteInputHistory(childEntry)
-                        || mRemoteInputManager.shouldKeepForSmartReplyHistory(childEntry);
+                        getRemoteInputManager().shouldKeepForRemoteInputHistory(childEntry)
+                        || getRemoteInputManager().shouldKeepForSmartReplyHistory(childEntry);
                 if (isForeground || keepForReply) {
                     // the child is a foreground service notification which we can't remove or it's
                     // a child we're keeping around for reply!
@@ -633,7 +676,8 @@
 
     protected void updateNotification(NotificationData.Entry entry, PackageManager pmUser,
             StatusBarNotification sbn, ExpandableNotificationRow row) {
-        row.setNeedsRedaction(mLockscreenUserManager.needsRedaction(entry));
+        row.setNeedsRedaction(
+                Dependency.get(NotificationLockscreenUserManager.class).needsRedaction(entry));
         boolean isLowPriority = mNotificationData.isAmbient(sbn.getKey());
         boolean isUpdate = mNotificationData.get(entry.key) != null;
         boolean wasLowPriority = row.isLowPriority();
@@ -818,7 +862,7 @@
                 mNotificationData.getImportance(key));
 
         boolean alertAgain = alertAgain(entry, entry.notification.getNotification());
-        if (mPresenter.isDozing()) {
+        if (getShadeController().isDozing()) {
             updateAlertState(entry, shouldPulse(entry), alertAgain, mAmbientPulseManager);
         } else {
             updateAlertState(entry, shouldHeadsUp(entry), alertAgain, mHeadsUpManager);
@@ -833,7 +877,8 @@
 
         if (DEBUG) {
             // Is this for you?
-            boolean isForCurrentUser = mPresenter.isNotificationForCurrentProfiles(notification);
+            boolean isForCurrentUser = Dependency.get(KeyguardEnvironment.class)
+                    .isNotificationForCurrentProfiles(notification);
             Log.d(TAG, "notification is " + (isForCurrentUser ? "" : "not ") + "for you");
         }
 
@@ -917,7 +962,7 @@
     public boolean shouldHeadsUp(NotificationData.Entry entry) {
         StatusBarNotification sbn = entry.notification;
 
-        if (mPresenter.isDozing()) {
+        if (getShadeController().isDozing()) {
             if (DEBUG) {
                 Log.d(TAG, "No heads up: device is dozing: " + sbn.getKey());
             }
@@ -998,7 +1043,7 @@
     protected boolean shouldPulse(NotificationData.Entry entry) {
         StatusBarNotification sbn = entry.notification;
 
-        if (!mPresenter.isDozing()) {
+        if (!getShadeController().isDozing()) {
             if (DEBUG) {
                 Log.d(TAG, "No pulsing: not dozing: " + sbn.getKey());
             }
@@ -1076,7 +1121,7 @@
 
     protected void setNotificationsShown(String[] keys) {
         try {
-            mNotificationListener.setNotificationsShown(keys);
+            getNotificationListener().setNotificationsShown(keys);
         } catch (RuntimeException e) {
             Log.d(TAG, "failed setNotificationsShown: ", e);
         }
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/logging/NotificationLogger.java b/packages/SystemUI/src/com/android/systemui/statusbar/notification/logging/NotificationLogger.java
index e96e176..b5fbde1 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/logging/NotificationLogger.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/logging/NotificationLogger.java
@@ -56,8 +56,9 @@
     private final NotificationListenerService mNotificationListener =
             Dependency.get(NotificationListener.class);
     private final UiOffloadThread mUiOffloadThread = Dependency.get(UiOffloadThread.class);
+    protected NotificationEntryManager mEntryManager
+            = Dependency.get(NotificationEntryManager.class);
 
-    protected NotificationEntryManager mEntryManager;
     protected Handler mHandler = new Handler();
     protected IStatusBarService mBarService;
     private long mLastVisibilityReportUptimeMs;
@@ -147,9 +148,7 @@
                 ServiceManager.getService(Context.STATUS_BAR_SERVICE));
     }
 
-    public void setUpWithEntryManager(NotificationEntryManager entryManager,
-            NotificationListContainer listContainer) {
-        mEntryManager = entryManager;
+    public void setUpWithContainer(NotificationListContainer listContainer) {
         mListContainer = listContainer;
     }
 
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/NotificationGutsManager.java b/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/NotificationGutsManager.java
index f4ef0f8..2499952 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/NotificationGutsManager.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/NotificationGutsManager.java
@@ -28,6 +28,7 @@
 import android.content.pm.PackageManager;
 import android.content.res.Resources;
 import android.net.Uri;
+import android.os.RemoteException;
 import android.os.ServiceManager;
 import android.os.UserHandle;
 import android.provider.Settings;
@@ -41,15 +42,23 @@
 import com.android.internal.annotations.VisibleForTesting;
 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.plugins.ActivityStarter;
+import com.android.systemui.plugins.ActivityStarter.OnDismissAction;
 import com.android.systemui.plugins.statusbar.NotificationMenuRowPlugin;
+import com.android.systemui.plugins.statusbar.NotificationMenuRowPlugin.MenuItem;
 import com.android.systemui.statusbar.NotificationLifetimeExtender;
-import com.android.systemui.statusbar.notification.NotificationData;
 import com.android.systemui.statusbar.NotificationLockscreenUserManager;
+import com.android.systemui.statusbar.notification.NotificationData;
 import com.android.systemui.statusbar.NotificationPresenter;
+import com.android.systemui.statusbar.StatusBarState;
+import com.android.systemui.statusbar.StatusBarStateController;
+import com.android.systemui.statusbar.notification.row.NotificationInfo.CheckSaveListener;
 import com.android.systemui.statusbar.notification.stack.NotificationListContainer;
 import com.android.systemui.statusbar.phone.StatusBar;
+import com.android.systemui.statusbar.policy.DeviceProvisionedController;
 
 import java.io.FileDescriptor;
 import java.io.PrintWriter;
@@ -71,14 +80,20 @@
     // Dependencies:
     private final NotificationLockscreenUserManager mLockscreenUserManager =
             Dependency.get(NotificationLockscreenUserManager.class);
+    private final StatusBarStateController mStatusBarStateController =
+            Dependency.get(StatusBarStateController.class);
+    private final DeviceProvisionedController mDeviceProvisionedController =
+            Dependency.get(DeviceProvisionedController.class);
+    private final ActivityStarter mActivityStarter = Dependency.get(ActivityStarter.class);
 
     // which notification is currently being longpress-examined by the user
+    private final IStatusBarService mBarService;
     private NotificationGuts mNotificationGutsExposed;
     private NotificationMenuRowPlugin.MenuItem mGutsMenuItem;
-    private NotificationPresenter mPresenter;
     private NotificationSafeToRemoveCallback mNotificationLifetimeFinishedCallback;
+    private NotificationPresenter mPresenter;
     private NotificationListContainer mListContainer;
-    private NotificationInfo.CheckSaveListener mCheckSaveListener;
+    private CheckSaveListener mCheckSaveListener;
     private OnSettingsClickListener mOnSettingsClickListener;
     @VisibleForTesting
     protected String mKeyToRemoveOnGutsClosed;
@@ -89,16 +104,17 @@
 
         mAccessibilityManager = (AccessibilityManager)
                 mContext.getSystemService(Context.ACCESSIBILITY_SERVICE);
+        mBarService = IStatusBarService.Stub.asInterface(
+                ServiceManager.getService(Context.STATUS_BAR_SERVICE));
     }
 
     public void setUpWithPresenter(NotificationPresenter presenter,
             NotificationListContainer listContainer,
-            NotificationInfo.CheckSaveListener checkSaveListener,
-            OnSettingsClickListener onSettingsClickListener) {
+            CheckSaveListener checkSave, OnSettingsClickListener onSettingsClick) {
         mPresenter = presenter;
         mListContainer = listContainer;
-        mCheckSaveListener = checkSaveListener;
-        mOnSettingsClickListener = onSettingsClickListener;
+        mCheckSaveListener = checkSave;
+        mOnSettingsClickListener = onSettingsClick;
     }
 
     public void onDensityOrFontScaleChanged(ExpandableNotificationRow row) {
@@ -264,7 +280,7 @@
             onSettingsClick = (View v, NotificationChannel channel, int appUid) -> {
                 mMetricsLogger.action(MetricsProto.MetricsEvent.ACTION_NOTE_INFO);
                 guts.resetFalsingCheck();
-                mOnSettingsClickListener.onClick(sbn.getKey());
+                mOnSettingsClickListener.onSettingsClick(sbn.getKey());
                 startAppNotificationSettingsActivity(packageName, appUid, channel, row);
             };
         }
@@ -279,7 +295,7 @@
                 mCheckSaveListener,
                 onSettingsClick,
                 onAppSettingsClick,
-                mPresenter.isDeviceProvisioned(),
+                mDeviceProvisionedController.isDeviceProvisioned(),
                 row.getIsNonblockable(),
                 isForBlockingHelper,
                 row.getEntry().userSentiment == USER_SENTIMENT_NEGATIVE);
@@ -317,6 +333,10 @@
         mNotificationGutsExposed = guts;
     }
 
+    public ExpandableNotificationRow.LongPressListener getNotificationLongClicker() {
+        return this::openGuts;
+    }
+
     /**
      * Opens guts on the given ExpandableNotificationRow {@code view}. This handles opening guts for
      * the normal half-swipe and long-press use cases via a circular reveal. When the blocking
@@ -385,7 +405,7 @@
                 guts.setVisibility(View.VISIBLE);
 
                 final boolean needsFalsingProtection =
-                        (mPresenter.isPresenterLocked() &&
+                        (mStatusBarStateController.getState() == StatusBarState.KEYGUARD &&
                                 !mAccessibilityManager.isTouchExplorationEnabled());
 
                 guts.openControls(
@@ -442,6 +462,6 @@
     }
 
     public interface OnSettingsClickListener {
-        void onClick(String key);
+        public void onSettingsClick(String key);
     }
 }
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/NotificationStackScrollLayout.java b/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/NotificationStackScrollLayout.java
index 659f6c7..c9cbb40 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/NotificationStackScrollLayout.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/NotificationStackScrollLayout.java
@@ -44,7 +44,6 @@
 import android.os.ServiceManager;
 import android.provider.Settings;
 import android.service.notification.StatusBarNotification;
-
 import android.util.AttributeSet;
 import android.util.DisplayMetrics;
 import android.util.Log;
@@ -88,33 +87,33 @@
 import com.android.systemui.plugins.statusbar.NotificationSwipeActionHelper;
 import com.android.systemui.statusbar.CommandQueue;
 import com.android.systemui.statusbar.DragDownHelper.DragDownCallback;
-import com.android.systemui.statusbar.NotificationLockscreenUserManager;
-import com.android.systemui.statusbar.StatusBarState;
-import com.android.systemui.statusbar.NotificationRemoteInputManager;
-import com.android.systemui.statusbar.RemoteInputController;
-import com.android.systemui.statusbar.StatusBarStateController.StateListener;
-import com.android.systemui.statusbar.notification.NotificationEntryManager;
-import com.android.systemui.statusbar.notification.VisualStabilityManager;
-import com.android.systemui.statusbar.notification.row.ActivatableNotificationView;
 import com.android.systemui.statusbar.EmptyShadeView;
+import com.android.systemui.statusbar.NotificationLockscreenUserManager;
+import com.android.systemui.statusbar.NotificationRemoteInputManager;
+import com.android.systemui.statusbar.NotificationShelf;
+import com.android.systemui.statusbar.RemoteInputController;
+import com.android.systemui.statusbar.StatusBarState;
+import com.android.systemui.statusbar.StatusBarStateController;
+import com.android.systemui.statusbar.StatusBarStateController.StateListener;
+import com.android.systemui.statusbar.notification.FakeShadowView;
+import com.android.systemui.statusbar.notification.NotificationData;
+import com.android.systemui.statusbar.notification.NotificationEntryManager;
+import com.android.systemui.statusbar.notification.NotificationUtils;
+import com.android.systemui.statusbar.notification.ShadeViewRefactor;
+import com.android.systemui.statusbar.notification.ShadeViewRefactor.RefactorComponent;
+import com.android.systemui.statusbar.notification.VisibilityLocationProvider;
+import com.android.systemui.statusbar.notification.VisualStabilityManager;
+import com.android.systemui.statusbar.notification.logging.NotificationLogger;
+import com.android.systemui.statusbar.notification.row.ActivatableNotificationView;
 import com.android.systemui.statusbar.notification.row.ExpandableNotificationRow;
 import com.android.systemui.statusbar.notification.row.ExpandableView;
 import com.android.systemui.statusbar.notification.row.ExpandableView.OnHeightChangedListener;
 import com.android.systemui.statusbar.notification.row.FooterView;
 import com.android.systemui.statusbar.notification.row.NotificationBlockingHelperManager;
-import com.android.systemui.statusbar.notification.NotificationData;
 import com.android.systemui.statusbar.notification.row.NotificationGuts;
-import com.android.systemui.statusbar.notification.logging.NotificationLogger;
-import com.android.systemui.statusbar.NotificationShelf;
 import com.android.systemui.statusbar.notification.row.NotificationGutsManager;
 import com.android.systemui.statusbar.notification.row.NotificationSnooze;
 import com.android.systemui.statusbar.notification.row.StackScrollerDecorView;
-import com.android.systemui.statusbar.StatusBarStateController;
-import com.android.systemui.statusbar.notification.FakeShadowView;
-import com.android.systemui.statusbar.notification.NotificationUtils;
-import com.android.systemui.statusbar.notification.ShadeViewRefactor;
-import com.android.systemui.statusbar.notification.ShadeViewRefactor.RefactorComponent;
-import com.android.systemui.statusbar.notification.VisibilityLocationProvider;
 import com.android.systemui.statusbar.phone.DozeParameters;
 import com.android.systemui.statusbar.phone.HeadsUpAppearanceController;
 import com.android.systemui.statusbar.phone.HeadsUpManagerPhone;
@@ -125,6 +124,7 @@
 import com.android.systemui.statusbar.phone.NotificationIconAreaController;
 import com.android.systemui.statusbar.phone.NotificationPanelView;
 import com.android.systemui.statusbar.phone.ScrimController;
+import com.android.systemui.statusbar.phone.ShadeController;
 import com.android.systemui.statusbar.phone.StatusBar;
 import com.android.systemui.statusbar.policy.ConfigurationController;
 import com.android.systemui.statusbar.policy.ConfigurationController.ConfigurationListener;
@@ -435,6 +435,8 @@
     private float mVerticalPanelTranslation;
     private final NotificationLockscreenUserManager mLockscreenUserManager =
             Dependency.get(NotificationLockscreenUserManager.class);
+    protected final NotificationGutsManager mGutsManager =
+            Dependency.get(NotificationGutsManager.class);
     private final Rect mTmpRect = new Rect();
     private final NotificationEntryManager mEntryManager =
             Dependency.get(NotificationEntryManager.class);
@@ -454,6 +456,7 @@
 
     private Interpolator mDarkXInterpolator = Interpolators.FAST_OUT_SLOW_IN;
     private NotificationPanelView mNotificationPanel;
+    private final ShadeController mShadeController = Dependency.get(ShadeController.class);
 
     private final NotificationGutsManager
             mNotificationGutsManager = Dependency.get(NotificationGutsManager.class);
@@ -4740,7 +4743,8 @@
     }
 
     @ShadeViewRefactor(RefactorComponent.SHADE_VIEW)
-    private void setStatusBarState(int statusBarState) {
+    @VisibleForTesting
+    protected void setStatusBarState(int statusBarState) {
         mStatusBarState = statusBarState;
         mAmbientState.setStatusBarState(statusBarState);
     }
@@ -4942,7 +4946,7 @@
             return;
         }
 
-        mStatusBar.addPostCollapseAction(() -> {
+        mShadeController.addPostCollapseAction(() -> {
             setDismissAllInProgress(false);
             for (ExpandableNotificationRow rowToRemove : viewsToRemove) {
                 if (canChildBeDismissed(rowToRemove)) {
@@ -5042,6 +5046,10 @@
         mNotificationPanel = notificationPanelView;
     }
 
+    public void updateIconAreaViews() {
+        mIconAreaController.updateNotificationIcons();
+    }
+
     /**
      * A listener that is notified when the empty space below the notifications is clicked on
      */
@@ -5662,7 +5670,7 @@
 
                 if (mNotificationPanel.onDraggedDown() || startingChild != null) {
                     // We have notifications, go to locked shade.
-                    mStatusBar.goToLockedShade(startingChild);
+                    mShadeController.goToLockedShade(startingChild);
                     if (startingChild instanceof ExpandableNotificationRow) {
                         ExpandableNotificationRow row = (ExpandableNotificationRow) startingChild;
                         row.onExpandedByGesture(true /* drag down is always an open */);
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/phone/BiometricUnlockController.java b/packages/SystemUI/src/com/android/systemui/statusbar/phone/BiometricUnlockController.java
index c094669..8325bf8 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/BiometricUnlockController.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/BiometricUnlockController.java
@@ -32,6 +32,7 @@
 import com.android.systemui.keyguard.KeyguardViewMediator;
 import com.android.systemui.keyguard.ScreenLifecycle;
 import com.android.systemui.keyguard.WakefulnessLifecycle;
+import com.android.systemui.statusbar.NotificationMediaManager;
 
 import java.io.PrintWriter;
 
@@ -95,6 +96,8 @@
      */
     private static final float BIOMETRIC_COLLAPSE_SPEEDUP_FACTOR = 1.1f;
 
+    private final NotificationMediaManager mMediaManager =
+            Dependency.get(NotificationMediaManager.class);
     private PowerManager mPowerManager;
     private Handler mHandler = new Handler();
     private PowerManager.WakeLock mWakeLock;
@@ -264,7 +267,7 @@
             case MODE_WAKE_AND_UNLOCK:
                 if (mMode == MODE_WAKE_AND_UNLOCK_PULSING) {
                     Trace.beginSection("MODE_WAKE_AND_UNLOCK_PULSING");
-                    mStatusBar.updateMediaMetaData(false /* metaDataChanged */,
+                    mMediaManager.updateMediaMetaData(false /* metaDataChanged */,
                             true /* allowEnterAnimation */);
                 } else if (mMode == MODE_WAKE_AND_UNLOCK){
                     Trace.beginSection("MODE_WAKE_AND_UNLOCK");
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/phone/CollapsedStatusBarFragment.java b/packages/SystemUI/src/com/android/systemui/statusbar/phone/CollapsedStatusBarFragment.java
index a781be6..fa63831 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/CollapsedStatusBarFragment.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/CollapsedStatusBarFragment.java
@@ -64,11 +64,12 @@
     private StatusBar mStatusBarComponent;
     private DarkIconManager mDarkIconManager;
     private View mOperatorNameFrame;
+    private CommandQueue mCommandQueue;
 
     private SignalCallback mSignalCallback = new SignalCallback() {
         @Override
         public void setIsAirplaneMode(NetworkController.IconState icon) {
-            mStatusBarComponent.recomputeDisableFlags(true /* animate */);
+            mCommandQueue.recomputeDisableFlags(true /* animate */);
         }
     };
 
@@ -78,6 +79,7 @@
         mKeyguardMonitor = Dependency.get(KeyguardMonitor.class);
         mNetworkController = Dependency.get(NetworkController.class);
         mStatusBarComponent = SysUiServiceProvider.getComponent(getContext(), StatusBar.class);
+        mCommandQueue = SysUiServiceProvider.getComponent(getContext(), CommandQueue.class);
     }
 
     @Override
@@ -116,13 +118,13 @@
     @Override
     public void onResume() {
         super.onResume();
-        SysUiServiceProvider.getComponent(getContext(), CommandQueue.class).addCallbacks(this);
+        mCommandQueue.addCallbacks(this);
     }
 
     @Override
     public void onPause() {
         super.onPause();
-        SysUiServiceProvider.getComponent(getContext(), CommandQueue.class).removeCallbacks(this);
+        mCommandQueue.removeCallbacks(this);
     }
 
     @Override
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/phone/KeyguardBottomAreaView.java b/packages/SystemUI/src/com/android/systemui/statusbar/phone/KeyguardBottomAreaView.java
index 072343a..32c9301 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/KeyguardBottomAreaView.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/KeyguardBottomAreaView.java
@@ -787,6 +787,10 @@
         mIndicationController = keyguardIndicationController;
     }
 
+    public void showTransientIndication(int id) {
+        mIndicationController.showTransientIndication(id);
+    }
+
     public void updateLeftAffordance() {
         updateLeftAffordanceIcon();
         updateLeftPreview();
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/phone/KeyguardBouncer.java b/packages/SystemUI/src/com/android/systemui/statusbar/phone/KeyguardBouncer.java
index 8ac8677..235629b 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/KeyguardBouncer.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/KeyguardBouncer.java
@@ -16,7 +16,7 @@
 
 package com.android.systemui.statusbar.phone;
 
-import static com.android.keyguard.KeyguardHostView.OnDismissAction;
+import static com.android.systemui.plugins.ActivityStarter.OnDismissAction;
 import static com.android.keyguard.KeyguardSecurityModel.SecurityMode;
 
 import android.content.Context;
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/phone/KeyguardDismissHandler.java b/packages/SystemUI/src/com/android/systemui/statusbar/phone/KeyguardDismissHandler.java
index 76ddca4..6111178 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/KeyguardDismissHandler.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/KeyguardDismissHandler.java
@@ -16,9 +16,7 @@
 
 package com.android.systemui.statusbar.phone;
 
-import android.annotation.Nullable;
-
-import com.android.keyguard.KeyguardHostView.OnDismissAction;
+import com.android.systemui.plugins.ActivityStarter.OnDismissAction;
 
 
 /** Executes actions that require the screen to be unlocked. */
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/phone/KeyguardDismissUtil.java b/packages/SystemUI/src/com/android/systemui/statusbar/phone/KeyguardDismissUtil.java
index d676692..462201c 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/KeyguardDismissUtil.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/KeyguardDismissUtil.java
@@ -18,7 +18,7 @@
 
 import android.util.Log;
 
-import com.android.keyguard.KeyguardHostView.OnDismissAction;
+import com.android.systemui.plugins.ActivityStarter.OnDismissAction;
 
 /**
  * Executes actions that require the screen to be unlocked. Delegates the actual handling to an
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/phone/KeyguardEnvironmentImpl.java b/packages/SystemUI/src/com/android/systemui/statusbar/phone/KeyguardEnvironmentImpl.java
new file mode 100644
index 0000000..b3423a8
--- /dev/null
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/KeyguardEnvironmentImpl.java
@@ -0,0 +1,57 @@
+/*
+ * Copyright (C) 2018 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file
+ * except in compliance with the License. You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software distributed under the
+ * License is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied. See the License for the specific language governing
+ * permissions and limitations under the License.
+ */
+
+package com.android.systemui.statusbar.phone;
+
+import static com.android.systemui.statusbar.phone.StatusBar.DEBUG;
+import static com.android.systemui.statusbar.phone.StatusBar.MULTIUSER_DEBUG;
+
+import android.service.notification.StatusBarNotification;
+import android.util.Log;
+
+import com.android.systemui.Dependency;
+import com.android.systemui.statusbar.NotificationLockscreenUserManager;
+import com.android.systemui.statusbar.NotificationMediaManager;
+import com.android.systemui.statusbar.notification.NotificationData.KeyguardEnvironment;
+import com.android.systemui.statusbar.policy.DeviceProvisionedController;
+
+public class KeyguardEnvironmentImpl implements KeyguardEnvironment {
+
+    private static final String TAG = "KeyguardEnvironmentImpl";
+
+    private final NotificationLockscreenUserManager mLockscreenUserManager =
+            Dependency.get(NotificationLockscreenUserManager.class);
+    private final DeviceProvisionedController mDeviceProvisionedController =
+            Dependency.get(DeviceProvisionedController.class);
+    private final NotificationMediaManager mMediaManager =
+            Dependency.get(NotificationMediaManager.class);
+
+    public KeyguardEnvironmentImpl() {
+    }
+
+    @Override  // NotificationData.KeyguardEnvironment
+    public boolean isDeviceProvisioned() {
+        return mDeviceProvisionedController.isDeviceProvisioned();
+    }
+
+    @Override  // NotificationData.KeyguardEnvironment
+    public boolean isNotificationForCurrentProfiles(StatusBarNotification n) {
+        final int notificationUserId = n.getUserId();
+        if (DEBUG && MULTIUSER_DEBUG) {
+            Log.v(TAG, String.format("%s: current userid: %d, notification userid: %d", n,
+                    mLockscreenUserManager.getCurrentUserId(), notificationUserId));
+        }
+        return mLockscreenUserManager.isCurrentProfile(notificationUserId);
+    }
+}
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/phone/LockscreenWallpaper.java b/packages/SystemUI/src/com/android/systemui/statusbar/phone/LockscreenWallpaper.java
index 40ddf5b..673cdb7 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/LockscreenWallpaper.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/LockscreenWallpaper.java
@@ -40,6 +40,8 @@
 import android.util.Log;
 
 import com.android.keyguard.KeyguardUpdateMonitor;
+import com.android.systemui.Dependency;
+import com.android.systemui.statusbar.NotificationMediaManager;
 
 import libcore.io.IoUtils;
 
@@ -52,6 +54,9 @@
 
     private static final String TAG = "LockscreenWallpaper";
 
+    private final NotificationMediaManager mMediaManager =
+            Dependency.get(NotificationMediaManager.class);
+
     private final StatusBar mBar;
     private final WallpaperManager mWallpaperManager;
     private final Handler mH;
@@ -193,7 +198,7 @@
                     mCached = true;
                     mCache = result.bitmap;
                     mUpdateMonitor.setHasLockscreenWallpaper(result.bitmap != null);
-                    mBar.updateMediaMetaData(
+                    mMediaManager.updateMediaMetaData(
                             true /* metaDataChanged */, true /* allowEnterAnimation */);
                 }
                 mLoader = null;
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/phone/NavigationBarFragment.java b/packages/SystemUI/src/com/android/systemui/statusbar/phone/NavigationBarFragment.java
index 80f3506..f105f612 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/NavigationBarFragment.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/NavigationBarFragment.java
@@ -95,6 +95,7 @@
 import com.android.systemui.statusbar.CommandQueue;
 import com.android.systemui.statusbar.CommandQueue.Callbacks;
 import com.android.systemui.statusbar.policy.AccessibilityManagerWrapper;
+import com.android.systemui.statusbar.policy.DeviceProvisionedController;
 import com.android.systemui.statusbar.policy.KeyButtonDrawable;
 import com.android.systemui.statusbar.policy.KeyButtonView;
 import com.android.systemui.statusbar.policy.RotationLockController;
@@ -126,6 +127,9 @@
     /** Allow some time inbetween the long press for back and recents. */
     private static final int LOCK_TO_APP_GESTURE_TOLERENCE = 200;
 
+    private final DeviceProvisionedController mDeviceProvisionedController =
+            Dependency.get(DeviceProvisionedController.class);
+
     protected NavigationBarView mNavigationBarView = null;
     protected AssistManager mAssistManager;
 
@@ -725,7 +729,7 @@
     }
 
     private boolean shouldDisableNavbarGestures() {
-        return !mStatusBar.isDeviceProvisioned()
+        return !mDeviceProvisionedController.isDeviceProvisioned()
                 || (mDisabledFlags1 & StatusBarManager.DISABLE_SEARCH) != 0;
     }
 
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/phone/NavigationBarView.java b/packages/SystemUI/src/com/android/systemui/statusbar/phone/NavigationBarView.java
index e92656ae..a2bd00e 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/NavigationBarView.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/NavigationBarView.java
@@ -350,7 +350,7 @@
     @Override
     public boolean onTouchEvent(MotionEvent event) {
         shouldDeadZoneConsumeTouchEvents(event);
-        if (mGestureHelper.onTouchEvent(event)) {
+        if (mGestureHelper != null && mGestureHelper.onTouchEvent(event)) {
             return true;
         }
         return super.onTouchEvent(event);
@@ -680,7 +680,9 @@
     }
 
     public void onNavigationButtonLongPress(View v) {
-        mGestureHelper.onNavigationButtonLongPress(v);
+        if (mGestureHelper != null) {
+            mGestureHelper.onNavigationButtonLongPress(v);
+        }
     }
 
     public void onPanelExpandedChange(boolean expanded) {
@@ -807,7 +809,9 @@
 
     @Override
     protected void onDraw(Canvas canvas) {
-        mGestureHelper.onDraw(canvas);
+        if (mGestureHelper != null) {
+            mGestureHelper.onDraw(canvas);
+        }
         mDeadZone.onDraw(canvas);
         super.onDraw(canvas);
     }
@@ -819,7 +823,9 @@
         updateButtonLocationOnScreen(getHomeButton(), mHomeButtonBounds);
         updateButtonLocationOnScreen(getRecentsButton(), mRecentsButtonBounds);
         updateButtonLocationOnScreen(getRotateSuggestionButton(), mRotationButtonBounds);
-        mGestureHelper.onLayout(changed, left, top, right, bottom);
+        if (mGestureHelper != null) {
+            mGestureHelper.onLayout(changed, left, top, right, bottom);
+        }
         mRecentsOnboarding.setNavBarHeight(getMeasuredHeight());
     }
 
@@ -1117,7 +1123,9 @@
         pw.println("    }");
 
         mContextualButtonGroup.dump(pw);
-        mGestureHelper.dump(pw);
+        if (mGestureHelper != null) {
+            mGestureHelper.dump(pw);
+        }
         mRecentsOnboarding.dump(pw);
     }
 
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/phone/NotificationPanelView.java b/packages/SystemUI/src/com/android/systemui/statusbar/phone/NotificationPanelView.java
index e31bad65..5ee0823 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/NotificationPanelView.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/NotificationPanelView.java
@@ -16,6 +16,7 @@
 
 package com.android.systemui.statusbar.phone;
 
+import static com.android.systemui.SysUiServiceProvider.getComponent;
 import static com.android.systemui.statusbar.notification.ActivityLaunchAnimator
         .ExpandAnimationParameters;
 
@@ -65,10 +66,12 @@
 import com.android.systemui.fragments.FragmentHostManager.FragmentListener;
 import com.android.systemui.plugins.qs.QS;
 import com.android.systemui.qs.QSFragment;
+import com.android.systemui.statusbar.CommandQueue;
 import com.android.systemui.statusbar.FlingAnimationUtils;
 import com.android.systemui.statusbar.GestureRecorder;
 import com.android.systemui.statusbar.KeyguardAffordanceView;
 import com.android.systemui.statusbar.KeyguardIndicationController;
+import com.android.systemui.statusbar.NotificationLockscreenUserManager;
 import com.android.systemui.statusbar.NotificationShelf;
 import com.android.systemui.statusbar.RemoteInputController;
 import com.android.systemui.statusbar.StatusBarState;
@@ -337,6 +340,11 @@
             Dependency.get(NotificationEntryManager.class);
 
     private final StateListener mListener = this::setBarState;
+    private final CommandQueue mCommandQueue;
+    private final NotificationLockscreenUserManager mLockscreenUserManager =
+            Dependency.get(NotificationLockscreenUserManager.class);
+    private final ShadeController mShadeController =
+            Dependency.get(ShadeController.class);
 
     public NotificationPanelView(Context context, AttributeSet attrs) {
         super(context, attrs);
@@ -347,6 +355,7 @@
         setAccessibilityPaneTitle(determineAccessibilityPaneTitle());
         mAlphaPaint.setXfermode(new PorterDuffXfermode(PorterDuff.Mode.MULTIPLY));
         setPanelAlpha(255, false /* animate */);
+        mCommandQueue = getComponent(context, CommandQueue.class);
     }
 
     private void setStatusBar(StatusBar bar) {
@@ -630,7 +639,7 @@
             if (suppressedSummary) {
                 continue;
             }
-            if (!mStatusBar.getNotificationLockscreenUserManager().shouldShowOnKeyguard(
+            if (!mLockscreenUserManager.shouldShowOnKeyguard(
                     row.getStatusBarNotification())) {
                 continue;
             }
@@ -2414,7 +2423,7 @@
                 return true;
             case StatusBarState.SHADE_LOCKED:
                 if (!mQsExpanded) {
-                    mStatusBar.goToKeyguard();
+                    mShadeController.goToKeyguard();
                 }
                 return true;
             case StatusBarState.SHADE:
@@ -2617,7 +2626,7 @@
         }
         if (showIconsWhenExpanded != mShowIconsWhenExpanded) {
             mShowIconsWhenExpanded = showIconsWhenExpanded;
-            mStatusBar.recomputeDisableFlags(false);
+            mCommandQueue.recomputeDisableFlags(false);
         }
     }
 
@@ -2904,7 +2913,7 @@
             if (hideIcons != mHideIconsDuringNotificationLaunch) {
                 mHideIconsDuringNotificationLaunch = hideIcons;
                 if (!hideIcons) {
-                    mStatusBar.recomputeDisableFlags(true /* animate */);
+                    mCommandQueue.recomputeDisableFlags(true /* animate */);
                 }
             }
         }
@@ -2972,6 +2981,7 @@
         mNotificationStackScroller.updateSpeedBumpIndex();
         mNotificationStackScroller.updateFooter();
         updateShowEmptyShadeView();
+        mNotificationStackScroller.updateIconAreaViews();
     }
 
     public void onUpdateRowStates() {
@@ -3019,6 +3029,10 @@
         updateShowEmptyShadeView();
     }
 
+    public void showTransientIndication(int id) {
+        mKeyguardBottomArea.showTransientIndication(id);
+    }
+
     /**
      * Whenever a user drags down on the empty area (pulling down the shade and clock) and lets go.
      *
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/phone/PhoneStatusBarView.java b/packages/SystemUI/src/com/android/systemui/statusbar/phone/PhoneStatusBarView.java
index 59863ec..2129835 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/PhoneStatusBarView.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/PhoneStatusBarView.java
@@ -19,6 +19,7 @@
 import static android.content.res.Configuration.ORIENTATION_PORTRAIT;
 
 import static com.android.systemui.ScreenDecorations.DisplayCutoutView.boundsFromDirection;
+import static com.android.systemui.SysUiServiceProvider.getComponent;
 
 import android.annotation.Nullable;
 import android.content.Context;
@@ -42,6 +43,7 @@
 import com.android.systemui.Dependency;
 import com.android.systemui.EventLogTags;
 import com.android.systemui.R;
+import com.android.systemui.statusbar.CommandQueue;
 import com.android.systemui.statusbar.policy.DarkIconDispatcher;
 import com.android.systemui.statusbar.policy.DarkIconDispatcher.DarkReceiver;
 
@@ -52,6 +54,7 @@
     private static final boolean DEBUG = StatusBar.DEBUG;
     private static final boolean DEBUG_GESTURES = false;
     private static final int NO_VALUE = Integer.MIN_VALUE;
+    private final CommandQueue mCommandQueue;
 
     StatusBar mBar;
 
@@ -82,6 +85,7 @@
         super(context, attrs);
 
         mBarTransitions = new PhoneStatusBarTransitions(this);
+        mCommandQueue = getComponent(context, CommandQueue.class);
     }
 
     public BarTransitions getBarTransitions() {
@@ -166,7 +170,7 @@
 
     @Override
     public boolean panelEnabled() {
-        return mBar.panelsEnabled();
+        return mCommandQueue.panelsEnabled();
     }
 
     @Override
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/phone/ShadeController.java b/packages/SystemUI/src/com/android/systemui/statusbar/phone/ShadeController.java
new file mode 100644
index 0000000..e546119
--- /dev/null
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/ShadeController.java
@@ -0,0 +1,147 @@
+/*
+ * Copyright (C) 2018 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file
+ * except in compliance with the License. You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software distributed under the
+ * License is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied. See the License for the specific language governing
+ * permissions and limitations under the License.
+ */
+
+package com.android.systemui.statusbar.phone;
+
+import android.view.View;
+import com.android.systemui.statusbar.StatusBarState;
+import com.android.systemui.statusbar.notification.row.ActivatableNotificationView;
+
+/**
+ * {@link ShadeController} is an abstraction of the work that used to be hard-coded in
+ * {@link StatusBar}. The shade itself represents the concept of the status bar window state, and
+ * can be in multiple states: dozing, locked, showing the bouncer, occluded, etc. All/some of these
+ * are coordinated with {@link StatusBarKeyguardViewManager} via
+ * {@link com.android.systemui.keyguard.KeyguardViewMediator} and others.
+ */
+public interface ShadeController {
+
+    /**
+     * Shows the keyguard bouncer - the password challenge on the lock screen
+     *
+     * @param scrimmed true when the bouncer should show scrimmed, false when the user will be
+     * dragging it and translation should be deferred {@see KeyguardBouncer#show(boolean, boolean)}
+     */
+    void showBouncer(boolean scrimmed);
+
+    /**
+     * Make our window larger and the panel expanded
+     */
+    void instantExpandNotificationsPanel();
+
+    /**
+     * If the notifications panel is not fully expanded, collapse it animated.
+     *
+     * @return Seems to always return false
+     */
+    boolean closeShadeIfOpen();
+
+    /**
+     * Add a runnable for NotificationPanelView to post when the panel is expanded.
+     *
+     * @param action the action to post
+     */
+    void postOnShadeExpanded(Runnable action);
+
+    /**
+     * Add a runnable to be executed after the shade collapses. Post-collapse runnables are
+     * aggregated and run serially.
+     *
+     * @param action the action to execute
+     */
+    void addPostCollapseAction(Runnable action);
+
+    /**
+     * Ask shade controller to set the state to {@link StatusBarState#KEYGUARD}, but only from
+     * {@link StatusBarState#SHADE_LOCKED}
+     */
+    void goToKeyguard();
+
+    /**
+     * When the keyguard is showing and covered by something (bouncer, keyguard activity, etc.) it
+     * is occluded. This is controlled by {@link com.android.server.policy.PhoneWindowManager}
+     *
+     * @return whether the keyguard is currently occluded
+     */
+    boolean isOccluded();
+
+    /**
+     * Notify the shade controller that the current user changed
+     *
+     * @param newUserId userId of the new user
+     */
+    void setLockscreenUser(int newUserId);
+
+    /**
+     * Dozing is when the screen is in AOD or asleep
+     *
+     * @return true if we are dozing
+     */
+    boolean isDozing();
+
+    /**
+     * Ask the display to wake up if currently dozing, else do nothing
+     *
+     * @param time when to wake up
+     * @param view the view requesting the wakeup
+     */
+    void wakeUpIfDozing(long time, View view);
+
+    /**
+     * If secure with redaction: Show bouncer, go to unlocked shade.
+     *
+     * <p>If secure without redaction or no security: Go to {@link StatusBarState#SHADE_LOCKED}.</p>
+     *
+     * @param startingChild The view to expand after going to the shade.
+     */
+    void goToLockedShade(View startingChild);
+
+    /**
+     * Adds a {@param runnable} to be executed after Keyguard is gone.
+     */
+    void addAfterKeyguardGoneRunnable(Runnable runnable);
+
+    /**
+     * Close the shade if it was open
+     *
+     * @return true if the shade was open, else false
+     */
+    boolean collapsePanel();
+
+    /**
+     * If {@param animate}, does the same as {@link #collapsePanel()}. Otherwise, instantly collapse
+     * the panel. Post collapse runnables will be executed
+     *
+     * @param animate
+     */
+    void collapsePanel(boolean animate);
+
+    /**
+     * Callback to tell the shade controller that an activity launch animation was canceled
+     */
+    void onLaunchAnimationCancelled();
+
+    /**
+     * When notifications update, give the shade controller a chance to do thing in response to
+     * the new data set
+     */
+    void updateAreThereNotifications();
+
+    /**
+     * Callback to notify the shade controller that a {@link ActivatableNotificationView} has become
+     * inactive
+     */
+    void onActivationReset();
+
+}
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/phone/StatusBar.java b/packages/SystemUI/src/com/android/systemui/statusbar/phone/StatusBar.java
index 56e5a1e..f56e219 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/StatusBar.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/StatusBar.java
@@ -26,10 +26,8 @@
 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.shared.system.WindowManagerWrapper.NAV_BAR_POS_LEFT;
 import static com.android.systemui.shared.system.WindowManagerWrapper.NAV_BAR_POS_INVALID;
-import static com.android.systemui.statusbar.NotificationLockscreenUserManager
-        .NOTIFICATION_UNLOCKED_BY_WORK_CHALLENGE_ACTION;
+import static com.android.systemui.shared.system.WindowManagerWrapper.NAV_BAR_POS_LEFT;
 import static com.android.systemui.statusbar.NotificationLockscreenUserManager.PERMISSION_SELF;
 import static com.android.systemui.statusbar.NotificationMediaManager.DEBUG_MEDIA;
 import static com.android.systemui.statusbar.phone.BarTransitions.MODE_LIGHTS_OUT;
@@ -54,7 +52,6 @@
 import android.app.NotificationManager;
 import android.app.PendingIntent;
 import android.app.StatusBarManager;
-import android.app.TaskStackBuilder;
 import android.app.UiModeManager;
 import android.app.WallpaperInfo;
 import android.app.WallpaperManager;
@@ -65,31 +62,21 @@
 import android.content.Context;
 import android.content.Intent;
 import android.content.IntentFilter;
-import android.content.IntentSender;
 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.graphics.Bitmap;
 import android.graphics.Point;
 import android.graphics.PointF;
-import android.graphics.PorterDuff;
-import android.graphics.PorterDuffXfermode;
 import android.graphics.Rect;
-import android.graphics.drawable.BitmapDrawable;
-import android.graphics.drawable.ColorDrawable;
-import android.graphics.drawable.Drawable;
 import android.media.AudioAttributes;
-import android.media.MediaMetadata;
 import android.metrics.LogMaker;
 import android.net.Uri;
 import android.os.AsyncTask;
 import android.os.Bundle;
 import android.os.Handler;
 import android.os.IBinder;
-import android.os.Looper;
 import android.os.Message;
 import android.os.PowerManager;
 import android.os.RemoteException;
@@ -105,14 +92,10 @@
 import android.service.dreams.DreamService;
 import android.service.dreams.IDreamManager;
 import android.service.notification.StatusBarNotification;
-import android.service.vr.IVrManager;
-import android.service.vr.IVrStateCallbacks;
-import android.text.TextUtils;
 import android.util.DisplayMetrics;
 import android.util.EventLog;
 import android.util.Log;
 import android.util.Slog;
-import android.util.SparseArray;
 import android.view.Display;
 import android.view.IWindowManager;
 import android.view.KeyEvent;
@@ -122,7 +105,6 @@
 import android.view.ThreadedRenderer;
 import android.view.View;
 import android.view.ViewGroup;
-import android.view.ViewParent;
 import android.view.ViewTreeObserver;
 import android.view.WindowManager;
 import android.view.WindowManagerGlobal;
@@ -130,19 +112,15 @@
 import android.view.animation.AccelerateInterpolator;
 import android.widget.DateTimeView;
 import android.widget.ImageView;
-import android.widget.TextView;
 
 import com.android.internal.annotations.VisibleForTesting;
 import com.android.internal.colorextraction.ColorExtractor;
 import com.android.internal.logging.MetricsLogger;
 import com.android.internal.logging.nano.MetricsProto.MetricsEvent;
 import com.android.internal.statusbar.IStatusBarService;
-import com.android.internal.statusbar.NotificationVisibility;
 import com.android.internal.statusbar.StatusBarIcon;
-import com.android.internal.widget.LockPatternUtils;
 import com.android.internal.widget.MessagingGroup;
 import com.android.internal.widget.MessagingMessage;
-import com.android.keyguard.KeyguardHostView.OnDismissAction;
 import com.android.keyguard.KeyguardUpdateMonitor;
 import com.android.keyguard.KeyguardUpdateMonitorCallback;
 import com.android.keyguard.ViewMediatorCallback;
@@ -152,11 +130,11 @@
 import com.android.systemui.Dependency;
 import com.android.systemui.Dumpable;
 import com.android.systemui.EventLogTags;
+import com.android.systemui.InitController;
 import com.android.systemui.Interpolators;
 import com.android.systemui.Prefs;
 import com.android.systemui.R;
 import com.android.systemui.RecentsComponent;
-import com.android.systemui.SysUiServiceProvider;
 import com.android.systemui.SystemUI;
 import com.android.systemui.SystemUIFactory;
 import com.android.systemui.UiOffloadThread;
@@ -185,38 +163,34 @@
 import com.android.systemui.shared.system.WindowManagerWrapper;
 import com.android.systemui.stackdivider.Divider;
 import com.android.systemui.stackdivider.WindowManagerProxy;
-import com.android.systemui.statusbar.StatusBarState;
 import com.android.systemui.statusbar.AmbientPulseManager;
-import com.android.systemui.statusbar.notification.row.ActivatableNotificationView;
-import com.android.systemui.statusbar.notification.AppOpsListener;
 import com.android.systemui.statusbar.BackDropView;
 import com.android.systemui.statusbar.CommandQueue;
 import com.android.systemui.statusbar.CrossFadeHelper;
 import com.android.systemui.statusbar.EmptyShadeView;
-import com.android.systemui.statusbar.notification.row.ExpandableNotificationRow;
 import com.android.systemui.statusbar.GestureRecorder;
 import com.android.systemui.statusbar.KeyboardShortcuts;
 import com.android.systemui.statusbar.KeyguardIndicationController;
-import com.android.systemui.statusbar.notification.NotificationData;
-import com.android.systemui.statusbar.notification.NotificationData.Entry;
-import com.android.systemui.statusbar.notification.NotificationEntryManager;
-import com.android.systemui.statusbar.notification.row.NotificationGutsManager;
-import com.android.systemui.statusbar.notification.row.NotificationInfo;
 import com.android.systemui.statusbar.NotificationListener;
 import com.android.systemui.statusbar.NotificationLockscreenUserManager;
-import com.android.systemui.statusbar.notification.logging.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.StatusBarState;
 import com.android.systemui.statusbar.StatusBarStateController;
 import com.android.systemui.statusbar.VibratorHelper;
-import com.android.systemui.statusbar.notification.AboveShelfObserver;
 import com.android.systemui.statusbar.notification.ActivityLaunchAnimator;
+import com.android.systemui.statusbar.notification.AppOpsListener;
+import com.android.systemui.statusbar.notification.NotificationData;
+import com.android.systemui.statusbar.notification.NotificationData.Entry;
+import com.android.systemui.statusbar.notification.NotificationEntryManager;
 import com.android.systemui.statusbar.notification.VisualStabilityManager;
+import com.android.systemui.statusbar.notification.logging.NotificationLogger;
+import com.android.systemui.statusbar.notification.row.ExpandableNotificationRow;
+import com.android.systemui.statusbar.notification.row.NotificationGutsManager;
 import com.android.systemui.statusbar.notification.stack.NotificationListContainer;
 import com.android.systemui.statusbar.notification.stack.NotificationStackScrollLayout;
 import com.android.systemui.statusbar.phone.UnlockMethodCache.OnUnlockMethodChangedListener;
@@ -230,7 +204,6 @@
 import com.android.systemui.statusbar.policy.DeviceProvisionedController.DeviceProvisionedListener;
 import com.android.systemui.statusbar.policy.ExtensionController;
 import com.android.systemui.statusbar.policy.HeadsUpManager;
-import com.android.systemui.statusbar.policy.HeadsUpUtil;
 import com.android.systemui.statusbar.policy.KeyguardMonitor;
 import com.android.systemui.statusbar.policy.KeyguardMonitorImpl;
 import com.android.systemui.statusbar.policy.KeyguardUserSwitcher;
@@ -253,9 +226,9 @@
 public class StatusBar extends SystemUI implements DemoMode,
         ActivityStarter, OnUnlockMethodChangedListener,
         OnHeadsUpChangedListener, CommandQueue.Callbacks, ZenModeController.Callback,
-        ColorExtractor.OnColorsChangedListener, ConfigurationListener, NotificationPresenter,
-        StatusBarStateController.StateListener,  AmbientPulseManager.OnAmbientChangedListener,
-        ActivityLaunchAnimator.Callback {
+        ColorExtractor.OnColorsChangedListener, ConfigurationListener,
+        StatusBarStateController.StateListener, ShadeController,
+        ActivityLaunchAnimator.Callback, AmbientPulseManager.OnAmbientChangedListener {
     public static final boolean MULTIUSER_DEBUG = false;
 
     public static final boolean ENABLE_CHILD_NOTIFICATIONS
@@ -324,10 +297,10 @@
 
     /** If true, the system is in the half-boot-to-decryption-screen state.
      * Prudently disable QS and notifications.  */
-    private static final boolean ONLY_CORE_APPS;
+    public static final boolean ONLY_CORE_APPS;
 
     /** If true, the lockscreen will show a distinct wallpaper */
-    private static final boolean ENABLE_LOCKSCREEN_WALLPAPER = true;
+    public static final boolean ENABLE_LOCKSCREEN_WALLPAPER = true;
 
     static {
         boolean onlyCoreApps;
@@ -376,7 +349,6 @@
 
     // expanded notifications
     protected NotificationPanelView mNotificationPanel; // the sliding/resizing panel within the notification window
-    private TextView mNotificationPanelDebugText;
 
     // settings
     private QSPanel mQSPanel;
@@ -385,15 +357,12 @@
 
     // RemoteInputView to be activated after unlock
     private View mPendingRemoteInputView;
-    private View mPendingWorkRemoteInputView;
 
     private RemoteInputQuickSettingsDisabler mRemoteInputQuickSettingsDisabler =
             Dependency.get(RemoteInputQuickSettingsDisabler.class);
 
     private View mReportRejectedTouch;
 
-    private int mMaxAllowedKeyguardNotifications;
-
     private boolean mExpandedVisible;
 
     private final int[] mAbsPos = new int[2];
@@ -460,7 +429,6 @@
     private int mInteractingWindows;
     private boolean mAutohideSuspended;
     private int mStatusBarMode;
-    private int mMaxKeyguardNotifications;
 
     private ViewMediatorCallback mKeyguardViewMediatorCallback;
     protected ScrimController mScrimController;
@@ -479,9 +447,6 @@
 
     protected BackDropView mBackdrop;
     protected ImageView mBackdropFront, mBackdropBack;
-    protected final PorterDuffXfermode mSrcXferMode = new PorterDuffXfermode(PorterDuff.Mode.SRC);
-    protected final PorterDuffXfermode mSrcOverXferMode =
-            new PorterDuffXfermode(PorterDuff.Mode.SRC_OVER);
 
     private NotificationMediaManager mMediaManager;
     protected NotificationLockscreenUserManager mLockscreenUserManager;
@@ -524,7 +489,6 @@
     private boolean mIsOccluded;
     private boolean mWereIconsJustHidden;
     private boolean mBouncerWasShowingWhenHidden;
-    private boolean mIsCollapsingToShowActivityOverLockscreen;
 
     // Notifies StatusBarKeyguardViewManager every time the keyguard transition is over,
     // this animation is tied to the scrim for historic reasons.
@@ -557,13 +521,9 @@
     private BatteryController mBatteryController;
     protected boolean mPanelExpanded;
     private UiModeManager mUiModeManager;
-    private boolean mKeyguardRequested;
     private boolean mIsKeyguard;
     private LogMaker mStatusBarStateLog;
-    private final LockscreenGestureLogger mLockscreenGestureLogger =
-            Dependency.get(LockscreenGestureLogger.class);
     protected NotificationIconAreaController mNotificationIconAreaController;
-    private boolean mReinflateNotificationsOnUserSwitched;
     @Nullable private View mAmbientIndicationContainer;
     private SysuiColorExtractor mColorExtractor;
     private ScreenLifecycle mScreenLifecycle;
@@ -598,10 +558,10 @@
 
     private NavigationBarFragment mNavigationBar;
     private View mNavigationBarView;
-    protected ActivityLaunchAnimator mActivityLaunchAnimator;
     private HeadsUpAppearanceController mHeadsUpAppearanceController;
     private boolean mVibrateOnOpening;
     private VibratorHelper mVibratorHelper;
+    protected NotificationPresenter mPresenter;
 
     @Override
     public void start() {
@@ -626,11 +586,11 @@
         mEntryManager = Dependency.get(NotificationEntryManager.class);
         mViewHierarchyManager = Dependency.get(NotificationViewHierarchyManager.class);
         mAppOpsListener = Dependency.get(AppOpsListener.class);
-        mAppOpsListener.setUpWithPresenter(this, mEntryManager);
         mZenController = Dependency.get(ZenModeController.class);
         mKeyguardViewMediator = getComponent(KeyguardViewMediator.class);
-
         mColorExtractor = Dependency.get(SysuiColorExtractor.class);
+        mDeviceProvisionedController = Dependency.get(DeviceProvisionedController.class);
+
         mColorExtractor.addOnColorsChangedListener(this);
         mStatusBarStateController.addListener(this, StatusBarStateController.RANK_STATUS_BAR);
 
@@ -659,17 +619,12 @@
 
         mPowerManager = (PowerManager) mContext.getSystemService(Context.POWER_SERVICE);
 
-        mDeviceProvisionedController = Dependency.get(DeviceProvisionedController.class);
-
         mBarService = IStatusBarService.Stub.asInterface(
                 ServiceManager.getService(Context.STATUS_BAR_SERVICE));
 
         mRecents = getComponent(Recents.class);
 
         mKeyguardManager = (KeyguardManager) mContext.getSystemService(Context.KEYGUARD_SERVICE);
-        mLockPatternUtils = new LockPatternUtils(mContext);
-
-        mMediaManager.setUpWithPresenter(this, mEntryManager);
 
         // Connect in to the status bar manager service
         mCommandQueue = getComponent(CommandQueue.class);
@@ -696,7 +651,6 @@
                 wallpaperChangedFilter, null /* broadcastPermission */, null /* scheduler */);
         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);
@@ -711,7 +665,16 @@
         }
 
         // Set up the initial notification state.
-        mNotificationListener.setUpWithPresenter(this, mEntryManager);
+        mPresenter = new StatusBarNotificationPresenter(mContext, mNotificationPanel,
+                mHeadsUpManager, mStatusBarWindow, mStackScroller, mDozeScrimController,
+                mScrimController, this);
+        mAppOpsListener.setUpWithPresenter(mPresenter);
+        mNotificationListener.setUpWithPresenter(mPresenter);
+        mNotificationShelf.setOnActivatedListener(mPresenter);
+        mRemoteInputManager.getController().addCallback(mStatusBarWindowController);
+
+        // set the initial view visibility
+        Dependency.get(InitController.class).addPostInitTask(this::updateAreThereNotifications);
 
         if (DEBUG) {
             Log.d(TAG, String.format(
@@ -724,24 +687,12 @@
                    ));
         }
 
-        setHeadsUpUser(mLockscreenUserManager.getCurrentUserId());
-
         IntentFilter internalFilter = new IntentFilter();
         internalFilter.addAction(BANNER_ACTION_CANCEL);
         internalFilter.addAction(BANNER_ACTION_SETUP);
         mContext.registerReceiver(mBannerActionBroadcastReceiver, internalFilter, PERMISSION_SELF,
                 null);
 
-        IVrManager vrManager = IVrManager.Stub.asInterface(ServiceManager.getService(
-                Context.VR_SERVICE));
-        if (vrManager != null) {
-            try {
-                vrManager.registerListener(mVrStateCallbacks);
-            } catch (RemoteException e) {
-                Slog.e(TAG, "Failed to register VR mode state listener: " + e);
-            }
-        }
-
         IWallpaperManager wallpaperManager = IWallpaperManager.Stub.asInterface(
                 ServiceManager.getService(Context.WALLPAPER_SERVICE));
         try {
@@ -788,24 +739,9 @@
         // into fragments, but the rest here, it leaves some awkward lifecycle and whatnot.
         mNotificationPanel = mStatusBarWindow.findViewById(R.id.notification_panel);
         mStackScroller = mStatusBarWindow.findViewById(R.id.notification_stack_scroller);
-        NotificationListContainer notifListContainer = (NotificationListContainer) mStackScroller;
         mZenController.addCallback(this);
-        mActivityLaunchAnimator = new ActivityLaunchAnimator(mStatusBarWindow,
-                this,
-                mNotificationPanel,
-                notifListContainer);
-        mGutsManager.setUpWithPresenter(this, notifListContainer, mCheckSaveListener,
-                key -> {
-                    try {
-                        mBarService.onNotificationSettingsViewed(key);
-                    } catch (RemoteException e) {
-                        // if we're here we're dead
-                    }
-                });
-        mNotificationLogger.setUpWithEntryManager(mEntryManager, notifListContainer);
-        mAboveShelfObserver = new AboveShelfObserver(mStackScroller);
-        mAboveShelfObserver.setListener(mStatusBarWindow.findViewById(
-                R.id.notification_container_parent));
+        NotificationListContainer notifListContainer = (NotificationListContainer) mStackScroller;
+        mNotificationLogger.setUpWithContainer(notifListContainer);
 
         mNotificationIconAreaController = SystemUIFactory.getInstance()
                 .createNotificationIconAreaController(context, this);
@@ -850,7 +786,7 @@
                             mNotificationIconAreaController, mHeadsUpManager, mStatusBarWindow);
                     mHeadsUpAppearanceController.readFrom(oldController);
                     mStatusBarWindow.setStatusBarView(mStatusBarView);
-                    setAreThereNotifications();
+                    updateAreThereNotifications();
                     checkBarModes();
                 }).getFragmentManager()
                 .beginTransaction()
@@ -872,13 +808,6 @@
         mGroupManager.setHeadsUpManager(mHeadsUpManager);
         putComponent(HeadsUpManager.class, mHeadsUpManager);
 
-        mEntryManager.setUpWithPresenter(this, notifListContainer, this, mHeadsUpManager);
-        mViewHierarchyManager.setUpWithPresenter(this, mEntryManager, notifListContainer);
-
-        if (MULTIUSER_DEBUG) {
-            mNotificationPanelDebugText = mNotificationPanel.findViewById(R.id.header_debug_info);
-            mNotificationPanelDebugText.setVisibility(View.VISIBLE);
-        }
 
         try {
             boolean showNav = mWindowManagerService.hasNavigationBar();
@@ -890,14 +819,16 @@
             // no window manager? good luck with that
         }
 
-        mBackdrop = mStatusBarWindow.findViewById(R.id.backdrop);
-        mBackdropFront = mBackdrop.findViewById(R.id.backdrop_front);
-        mBackdropBack = mBackdrop.findViewById(R.id.backdrop_back);
-
         if (ENABLE_LOCKSCREEN_WALLPAPER) {
             mLockscreenWallpaper = new LockscreenWallpaper(mContext, this, mHandler);
         }
 
+        mBackdrop = mStatusBarWindow.findViewById(R.id.backdrop);
+        mBackdropFront = mBackdrop.findViewById(R.id.backdrop_front);
+        mBackdropBack = mBackdrop.findViewById(R.id.backdrop_back);
+        mMediaManager.setup(mBackdrop, mBackdropFront, mBackdropBack, mBiometricUnlockController,
+                mScrimController, mLockscreenWallpaper);
+
         mKeyguardIndicationController =
                 SystemUIFactory.getInstance().createKeyguardIndicationController(mContext,
                         mStatusBarWindow.findViewById(R.id.keyguard_indication_area),
@@ -908,9 +839,6 @@
         mAmbientIndicationContainer = mStatusBarWindow.findViewById(
                 R.id.ambient_indication_container);
 
-        // set the initial view visibility
-        setAreThereNotifications();
-
         // TODO: Find better place for this callback.
         mBatteryController.addCallback(new BatteryStateChangeCallback() {
             @Override
@@ -1053,6 +981,30 @@
         ThreadedRenderer.overrideProperty("ambientRatio", String.valueOf(1.5f));
     }
 
+    @Override
+    public void addAfterKeyguardGoneRunnable(Runnable runnable) {
+        mStatusBarKeyguardViewManager.addAfterKeyguardGoneRunnable(runnable);
+    }
+
+    @Override
+    public boolean isDozing() {
+        return mDozing && mNotificationPanel.isFullyDark();
+    }
+
+    @Override
+    public void wakeUpIfDozing(long time, View where) {
+        if (mDozing) {
+            PowerManager pm = (PowerManager) mContext.getSystemService(Context.POWER_SERVICE);
+            pm.wakeUp(time, "com.android.systemui:NODOZE");
+            mWakeUpComingFromTouch = true;
+            where.getLocationInWindow(mTmpInt2);
+            mWakeUpTouchLocation = new PointF(mTmpInt2[0] + where.getWidth() / 2,
+                    mTmpInt2[1] + where.getHeight() / 2);
+            mStatusBarKeyguardViewManager.notifyDeviceWakeUpRequested();
+            mFalsingManager.onScreenOnFromTouch();
+        }
+    }
+
     protected void createNavigationBar() {
         mNavigationBarView = NavigationBarFragment.create(mContext, (tag, fragment) -> {
             mNavigationBar = (NavigationBarFragment) fragment;
@@ -1084,7 +1036,6 @@
         mNotificationShelf =
                 (NotificationShelf) LayoutInflater.from(mContext).inflate(
                         R.layout.status_bar_notification_shelf, mStackScroller, false);
-        mNotificationShelf.setOnActivatedListener(this);
         mNotificationShelf.setOnClickListener(mGoToLockedShadeListener);
     }
 
@@ -1092,13 +1043,6 @@
     public void onDensityOrFontScaleChanged() {
         MessagingMessage.dropCache();
         MessagingGroup.dropCache();
-        // start old BaseStatusBar.onDensityOrFontScaleChanged().
-        if (!KeyguardUpdateMonitor.getInstance(mContext).isSwitchingUser()) {
-            mEntryManager.updateNotificationsOnDensityOrFontScaleChanged();
-        } else {
-            mReinflateNotificationsOnUserSwitched = true;
-        }
-        // end old BaseStatusBar.onDensityOrFontScaleChanged().
         // TODO: Remove this.
         if (mBrightnessMirrorController != null) {
             mBrightnessMirrorController.onDensityOrFontScaleChanged();
@@ -1169,8 +1113,6 @@
                 mScrimController, this, UnlockMethodCache.getInstance(mContext));
         mStatusBarKeyguardViewManager = keyguardViewMediator.registerStatusBar(this,
                 getBouncerContainer(), mNotificationPanel, mBiometricUnlockController);
-        //TODO: Can we put the keyguard view manager in Dependency?
-        mLockscreenUserManager.setKeyguardViewManager(mStatusBarKeyguardViewManager);
         mKeyguardIndicationController
                 .setStatusBarKeyguardViewManager(mStatusBarKeyguardViewManager);
         mBiometricUnlockController.setStatusBarKeyguardViewManager(mStatusBarKeyguardViewManager);
@@ -1234,73 +1176,13 @@
         return true;
     }
 
-    @Override
-    public void onPerformRemoveNotification(StatusBarNotification n) {
-        if (mNotificationPanel.hasPulsingNotifications() &&
-                    !mAmbientPulseManager.hasNotifications()) {
-            // We were showing a pulse for a notification, but no notifications are pulsing anymore.
-            // Finish the pulse.
-            mDozeScrimController.pulseOutNow();
-        }
-    }
-
-    @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 (mScrimController == null) return;
-
-        // Do not modify the notifications during collapse.
-        if (isCollapsing()) {
-            addPostCollapseAction(this::updateNotificationViews);
-            return;
-        }
-
-        mViewHierarchyManager.updateNotificationViews();
-
-        mNotificationPanel.updateNotificationViews();
-
-        updateQsExpansionEnabled();
-
-        // Let's also update the icons
-        mNotificationIconAreaController.updateNotificationIcons();
-    }
-
-    @Override
-    public void onNotificationAdded(Entry shadeEntry) {
-        // Recalculate the position of the sliding windows and the titles.
-        setAreThereNotifications();
-    }
-
-    @Override
-    public void onNotificationUpdated(StatusBarNotification notification) {
-        setAreThereNotifications();
-    }
-
-    @Override
-    public void onNotificationRemoved(String key, StatusBarNotification old) {
-        if (SPEW) Log.d(TAG, "removeNotification key=" + key + " old=" + old);
-
-        if (old != null) {
-            if (CLOSE_PANEL_WHEN_EMPTIED && !hasActiveNotifications()
-                    && !mNotificationPanel.isTracking() && !mNotificationPanel.isQsExpanded()) {
-                if (mState == StatusBarState.SHADE) {
-                    animateCollapsePanels();
-                } else if (mState == StatusBarState.SHADE_LOCKED && !isCollapsing()) {
-                    goToKeyguard();
-                }
-            }
-        }
-        setAreThereNotifications();
-    }
-
     /**
      * 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.
      */
     private void updateQsExpansionEnabled() {
-        mNotificationPanel.setQsExpansionEnabled(isDeviceProvisioned()
+        mNotificationPanel.setQsExpansionEnabled(mDeviceProvisionedController.isDeviceProvisioned()
                 && (mUserSetup || mUserSwitcherController == null
                         || !mUserSwitcherController.isSimpleUserSwitcher())
                 && ((mDisabled2 & StatusBarManager.DISABLE2_NOTIFICATION_SHADE) == 0)
@@ -1337,12 +1219,11 @@
         mEntryManager.updateNotifications();
     }
 
-    protected void setAreThereNotifications() {
-
+    public void updateAreThereNotifications() {
         if (SPEW) {
             final boolean clearable = hasActiveNotifications() &&
                     mNotificationPanel.hasActiveClearableNotifications();
-            Log.d(TAG, "setAreThereNotifications: N=" +
+            Log.d(TAG, "updateAreThereNotifications: N=" +
                     mEntryManager.getNotificationData().getActiveNotifications().size() + " any=" +
                     hasActiveNotifications() + " clearable=" + clearable);
         }
@@ -1368,192 +1249,9 @@
                         .start();
             }
         }
-
         mMediaManager.findAndUpdateMediaNotifications();
     }
 
-
-    /**
-     * Hide the album artwork that is fading out and release its bitmap.
-     */
-    protected final Runnable mHideBackdropFront = new Runnable() {
-        @Override
-        public void run() {
-            if (DEBUG_MEDIA) {
-                Log.v(TAG, "DEBUG_MEDIA: removing fade layer");
-            }
-            mBackdropFront.setVisibility(View.INVISIBLE);
-            mBackdropFront.animate().cancel();
-            mBackdropFront.setImageDrawable(null);
-        }
-    };
-
-    // TODO: Move this to NotificationMediaManager.
-    /**
-     * Refresh or remove lockscreen artwork from media metadata or the lockscreen wallpaper.
-     */
-    @Override
-    public void updateMediaMetaData(boolean metaDataChanged, boolean allowEnterAnimation) {
-        Trace.beginSection("StatusBar#updateMediaMetaData");
-        if (!SHOW_LOCKSCREEN_MEDIA_ARTWORK) {
-            Trace.endSection();
-            return;
-        }
-
-        if (mBackdrop == null) {
-            Trace.endSection();
-            return; // called too early
-        }
-
-        boolean wakeAndUnlock = mBiometricUnlockController != null
-            && mBiometricUnlockController.isWakeAndUnlock();
-        if (mLaunchTransitionFadingAway || wakeAndUnlock) {
-            mBackdrop.setVisibility(View.INVISIBLE);
-            Trace.endSection();
-            return;
-        }
-
-        MediaMetadata mediaMetadata = mMediaManager.getMediaMetadata();
-
-        if (DEBUG_MEDIA) {
-            Log.v(TAG, "DEBUG_MEDIA: updating album art for notification "
-                    + mMediaManager.getMediaNotificationKey()
-                    + " metadata=" + mediaMetadata
-                    + " metaDataChanged=" + metaDataChanged
-                    + " state=" + mState);
-        }
-
-        Drawable artworkDrawable = null;
-        if (mediaMetadata != null) {
-            Bitmap artworkBitmap = mediaMetadata.getBitmap(MediaMetadata.METADATA_KEY_ART);
-            if (artworkBitmap == null) {
-                artworkBitmap = mediaMetadata.getBitmap(MediaMetadata.METADATA_KEY_ALBUM_ART);
-                // might still be null
-            }
-            if (artworkBitmap != null) {
-                artworkDrawable = new BitmapDrawable(mBackdropBack.getResources(), artworkBitmap);
-            }
-        }
-        boolean allowWhenShade = false;
-        if (ENABLE_LOCKSCREEN_WALLPAPER && artworkDrawable == null) {
-            Bitmap lockWallpaper = mLockscreenWallpaper.getBitmap();
-            if (lockWallpaper != null) {
-                artworkDrawable = new LockscreenWallpaper.WallpaperDrawable(
-                        mBackdropBack.getResources(), lockWallpaper);
-                // We're in the SHADE mode on the SIM screen - yet we still need to show
-                // the lockscreen wallpaper in that mode.
-                allowWhenShade = mStatusBarKeyguardViewManager != null
-                        && mStatusBarKeyguardViewManager.isShowing();
-            }
-        }
-
-        boolean hideBecauseOccluded = mStatusBarKeyguardViewManager != null
-                && mStatusBarKeyguardViewManager.isOccluded();
-
-        final boolean hasArtwork = artworkDrawable != null;
-        mColorExtractor.setHasBackdrop(hasArtwork);
-        if (mScrimController != null) {
-            mScrimController.setHasBackdrop(hasArtwork);
-        }
-
-        if ((hasArtwork || DEBUG_MEDIA_FAKE_ARTWORK)
-                && (mState != StatusBarState.SHADE || allowWhenShade)
-                && mBiometricUnlockController.getMode()
-                        != BiometricUnlockController.MODE_WAKE_AND_UNLOCK_PULSING
-                && !hideBecauseOccluded) {
-            // time to show some art!
-            if (mBackdrop.getVisibility() != View.VISIBLE) {
-                mBackdrop.setVisibility(View.VISIBLE);
-                if (allowEnterAnimation) {
-                    mBackdrop.setAlpha(0);
-                    mBackdrop.animate().alpha(1f);
-                } else {
-                    mBackdrop.animate().cancel();
-                    mBackdrop.setAlpha(1f);
-                }
-                mStatusBarWindowController.setBackdropShowing(true);
-                metaDataChanged = true;
-                if (DEBUG_MEDIA) {
-                    Log.v(TAG, "DEBUG_MEDIA: Fading in album artwork");
-                }
-            }
-            if (metaDataChanged) {
-                if (mBackdropBack.getDrawable() != null) {
-                    Drawable drawable =
-                            mBackdropBack.getDrawable().getConstantState()
-                                    .newDrawable(mBackdropFront.getResources()).mutate();
-                    mBackdropFront.setImageDrawable(drawable);
-                    mBackdropFront.setAlpha(1f);
-                    mBackdropFront.setVisibility(View.VISIBLE);
-                } else {
-                    mBackdropFront.setVisibility(View.INVISIBLE);
-                }
-
-                if (DEBUG_MEDIA_FAKE_ARTWORK) {
-                    final int c = 0xFF000000 | (int)(Math.random() * 0xFFFFFF);
-                    Log.v(TAG, String.format("DEBUG_MEDIA: setting new color: 0x%08x", c));
-                    mBackdropBack.setBackgroundColor(0xFFFFFFFF);
-                    mBackdropBack.setImageDrawable(new ColorDrawable(c));
-                } else {
-                    mBackdropBack.setImageDrawable(artworkDrawable);
-                }
-
-                if (mBackdropFront.getVisibility() == View.VISIBLE) {
-                    if (DEBUG_MEDIA) {
-                        Log.v(TAG, "DEBUG_MEDIA: Crossfading album artwork from "
-                                + mBackdropFront.getDrawable()
-                                + " to "
-                                + mBackdropBack.getDrawable());
-                    }
-                    mBackdropFront.animate()
-                            .setDuration(250)
-                            .alpha(0f).withEndAction(mHideBackdropFront);
-                }
-            }
-        } else {
-            // need to hide the album art, either because we are unlocked, on AOD
-            // or because the metadata isn't there to support it
-            if (mBackdrop.getVisibility() != View.GONE) {
-                if (DEBUG_MEDIA) {
-                    Log.v(TAG, "DEBUG_MEDIA: Fading out album artwork");
-                }
-                boolean cannotAnimateDoze = mDozing && !ScrimState.AOD.getAnimateChange();
-                if (mBiometricUnlockController.getMode()
-                        == BiometricUnlockController.MODE_WAKE_AND_UNLOCK_PULSING
-                        || hideBecauseOccluded || cannotAnimateDoze) {
-
-                    // We are unlocking directly - no animation!
-                    mBackdrop.setVisibility(View.GONE);
-                    mBackdropBack.setImageDrawable(null);
-                    mStatusBarWindowController.setBackdropShowing(false);
-                } else {
-                    mStatusBarWindowController.setBackdropShowing(false);
-                    mBackdrop.animate()
-                            .alpha(0)
-                            .setInterpolator(Interpolators.ACCELERATE_DECELERATE)
-                            .setDuration(300)
-                            .setStartDelay(0)
-                            .withEndAction(() -> {
-                                mBackdrop.setVisibility(View.GONE);
-                                mBackdropFront.animate().cancel();
-                                mBackdropBack.setImageDrawable(null);
-                                mHandler.post(mHideBackdropFront);
-                            });
-                    if (mKeyguardMonitor.isKeyguardFadingAway()) {
-                        mBackdrop.animate()
-                                // Make it disappear faster, as the focus should be on the activity
-                                // behind.
-                                .setDuration(mKeyguardMonitor.getKeyguardFadingAwayDuration() / 2)
-                                .setStartDelay(mKeyguardMonitor.getKeyguardFadingAwayDelay())
-                                .setInterpolator(Interpolators.LINEAR)
-                                .start();
-                    }
-                }
-            }
-        }
-        Trace.endSection();
-    }
-
     private void updateReportRejectedTouchVisibility() {
         if (mReportRejectedTouch == null) {
             return;
@@ -1646,15 +1344,6 @@
         }
     }
 
-    /**
-     * Reapplies the disable flags as last requested by StatusBarManager.
-     *
-     * This needs to be called if state used by adjustDisableFlags changes.
-     */
-    public void recomputeDisableFlags(boolean animate) {
-        mCommandQueue.recomputeDisableFlags(animate);
-    }
-
     protected H createHandler() {
         return new StatusBar.H();
     }
@@ -1695,53 +1384,6 @@
         return mStatusBarStateController.getState() == StatusBarState.KEYGUARD;
     }
 
-    @Override
-    public boolean isDozing() {
-        return mDozing && mNotificationPanel.isFullyDark();
-    }
-
-    @Override
-    public boolean canHeadsUp(Entry entry, StatusBarNotification sbn) {
-        if (isDozing()) {
-            return false;
-        }
-
-        if (mIsOccluded) {
-            boolean devicePublic = mLockscreenUserManager.
-                    isLockscreenPublicMode(mLockscreenUserManager.getCurrentUserId());
-            boolean userPublic = devicePublic
-                    || mLockscreenUserManager.isLockscreenPublicMode(sbn.getUserId());
-            boolean needsRedaction = mLockscreenUserManager.needsRedaction(entry);
-            if (userPublic && needsRedaction) {
-                return false;
-            }
-        }
-
-        if (!panelsEnabled()) {
-            if (DEBUG) {
-                Log.d(TAG, "No heads up: disabled panel : " + sbn.getKey());
-            }
-            return false;
-        }
-
-        if (sbn.getNotification().fullScreenIntent != null) {
-            if (mAccessibilityManager.isTouchExplorationEnabled()) {
-                if (DEBUG) Log.d(TAG, "No heads up: accessible fullscreen: " + sbn.getKey());
-                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();
-    }
-
     /**
      * To be called when there's a state change in StatusBarKeyguardViewManager.
      */
@@ -1815,12 +1457,6 @@
         }
     }
 
-    protected void setHeadsUpUser(int newUserId) {
-        if (mHeadsUpManager != null) {
-            mHeadsUpManager.setUser(newUserId);
-        }
-    }
-
     public boolean isKeyguardCurrentlySecure() {
         return !mUnlockMethodCache.canSkipBouncer();
     }
@@ -1868,6 +1504,11 @@
         return mAmbientIndicationContainer;
     }
 
+    @Override
+    public boolean isOccluded() {
+        return mIsOccluded;
+    }
+
     public void setOccluded(boolean occluded) {
         mIsOccluded = occluded;
         mScrimController.setKeyguardOccluded(occluded);
@@ -1901,10 +1542,10 @@
                 mWereIconsJustHidden = true;
                 mHandler.postDelayed(() -> {
                     mWereIconsJustHidden = false;
-                    recomputeDisableFlags(true);
+                    mCommandQueue.recomputeDisableFlags(true);
                 }, 500);
             } else {
-                recomputeDisableFlags(animate);
+                mCommandQueue.recomputeDisableFlags(animate);
             }
         }
         if (shouldHideIconsForBouncer) {
@@ -1912,20 +1553,21 @@
         }
     }
 
-    @Override
-    public void onLaunchAnimationCancelled() {
-        if (!isCollapsing()) {
-            onClosingFinished();
-        }
-    }
-
     public boolean isHeadsUpShouldBeVisible() {
         return mHeadsUpAppearanceController.shouldBeVisible();
     }
 
+    //TODO: These can / should probably be moved to NotificationPresenter or ShadeController
+    @Override
+    public void onLaunchAnimationCancelled() {
+        if (!mPresenter.isCollapsing()) {
+            onClosingFinished();
+        }
+    }
+
     @Override
     public void onExpandAnimationFinished(boolean launchIsFullScreen) {
-        if (!isCollapsing()) {
+        if (!mPresenter.isCollapsing()) {
             onClosingFinished();
         }
         if (launchIsFullScreen) {
@@ -1935,8 +1577,9 @@
 
     @Override
     public void onExpandAnimationTimedOut() {
-        if (isPresenterFullyCollapsed() && !isCollapsing()
-                && !mActivityLaunchAnimator.isLaunchForActivity()) {
+        ActivityLaunchAnimator animator = mPresenter.getActivityLaunchAnimator();
+        if (mPresenter.isPresenterFullyCollapsed() && !mPresenter.isCollapsing()
+                && animator != null && !animator.isLaunchForActivity()) {
             onClosingFinished();
         } else {
             collapsePanel(true /* animate */);
@@ -1948,6 +1591,14 @@
         return mState == StatusBarState.SHADE;
     }
 
+    public boolean isDeviceInVrMode() {
+        return mPresenter.isDeviceInVrMode();
+    }
+
+    public NotificationPresenter getPresenter() {
+        return mPresenter;
+    }
+
     /**
      * All changes to the status bar and notifications funnel through here and are batched.
      */
@@ -2005,7 +1656,7 @@
     @Override
     public void handleSystemKey(int key) {
         if (SPEW) Log.d(TAG, "handleNavigationKey: " + key);
-        if (!panelsEnabled() || !mKeyguardMonitor.isDeviceInteractive()
+        if (!mCommandQueue.panelsEnabled() || !mKeyguardMonitor.isDeviceInteractive()
                 || mKeyguardMonitor.isShowing() && !mKeyguardMonitor.isOccluded()) {
             return;
         }
@@ -2047,15 +1698,9 @@
         }
     }
 
-    boolean panelsEnabled() {
-        return (mDisabled1 & StatusBarManager.DISABLE_EXPAND) == 0
-                && (mDisabled2 & StatusBarManager.DISABLE2_NOTIFICATION_SHADE) == 0
-                && !ONLY_CORE_APPS;
-    }
-
     void makeExpandedVisible(boolean force) {
         if (SPEW) Log.d(TAG, "Make expanded visible: expanded visible=" + mExpandedVisible);
-        if (!force && (mExpandedVisible || !panelsEnabled())) {
+        if (!force && (mExpandedVisible || !mCommandQueue.panelsEnabled())) {
             return;
         }
 
@@ -2066,7 +1711,7 @@
         mStatusBarWindowController.setPanelVisible(true);
 
         visibilityChanged(true);
-        recomputeDisableFlags(!force /* animate */);
+        mCommandQueue.recomputeDisableFlags(!force /* animate */);
         setInteracting(StatusBarManager.WINDOW_STATUS_BAR, true);
     }
 
@@ -2099,12 +1744,12 @@
         }
     }
 
-    @Override
     public void animateCollapsePanels(int flags) {
         animateCollapsePanels(flags, false /* force */, false /* delayed */,
                 1.0f /* speedUpFactor */);
     }
 
+    @Override
     public void animateCollapsePanels(int flags, boolean force) {
         animateCollapsePanels(flags, force, false /* delayed */, 1.0f /* speedUpFactor */);
     }
@@ -2155,7 +1800,7 @@
     }
 
     public void dispatchNotificationsPanelTouchEvent(MotionEvent ev) {
-        if (!panelsEnabled()) {
+        if (!mCommandQueue.panelsEnabled()) {
             return;
         }
         mNotificationPanel.dispatchTouchEvent(ev);
@@ -2173,7 +1818,7 @@
     @Override
     public void animateExpandNotificationsPanel() {
         if (SPEW) Log.d(TAG, "animateExpand: mExpandedVisible=" + mExpandedVisible);
-        if (!panelsEnabled()) {
+        if (!mCommandQueue.panelsEnabled()) {
             return ;
         }
 
@@ -2185,7 +1830,7 @@
     @Override
     public void animateExpandSettingsPanel(@Nullable String subPanel) {
         if (SPEW) Log.d(TAG, "animateExpand: mExpandedVisible=" + mExpandedVisible);
-        if (!panelsEnabled()) {
+        if (!mCommandQueue.panelsEnabled()) {
             return;
         }
 
@@ -2233,12 +1878,13 @@
 
         runPostCollapseRunnables();
         setInteracting(StatusBarManager.WINDOW_STATUS_BAR, false);
-        if (!mIsCollapsingToShowActivityOverLockscreen) {
+        if (!mPresenter.isCollapsingToShowActivityOverLockscreen()) {
             showBouncerIfKeyguard();
         } else if (DEBUG) {
             Log.d(TAG, "Not showing bouncer due to activity showing over lockscreen");
         }
-        recomputeDisableFlags(mNotificationPanel.hideStatusBarIconsWhenExpanded() /* animate */);
+        mCommandQueue.recomputeDisableFlags(
+                mNotificationPanel.hideStatusBarIconsWhenExpanded() /* animate */);
 
         // Trimming will happen later if Keyguard is showing - doing it here might cause a jank in
         // the bouncer appear animation.
@@ -2330,7 +1976,7 @@
 
             // update low profile
             if ((diff & View.SYSTEM_UI_FLAG_LOW_PROFILE) != 0) {
-                setAreThereNotifications();
+                updateAreThereNotifications();
             }
 
             // ready to unhide
@@ -2703,9 +2349,6 @@
     private void addStatusBarWindow() {
         makeStatusBarView();
         mStatusBarWindowController = Dependency.get(StatusBarWindowController.class);
-        mRemoteInputManager.setUpWithPresenter(this, mEntryManager, this,
-                mNotificationPanel.createRemoteInputDelegate());
-        mRemoteInputManager.getController().addCallback(mStatusBarWindowController);
         mStatusBarWindowController.add(mStatusBarWindow, getStatusBarHeight());
     }
 
@@ -2750,7 +2393,7 @@
     public void startActivityDismissingKeyguard(final Intent intent, boolean onlyProvisioned,
             final boolean dismissShade, final boolean disallowEnterPictureInPictureWhileLaunching,
             final Callback callback, int flags) {
-        if (onlyProvisioned && !isDeviceProvisioned()) return;
+        if (onlyProvisioned && !mDeviceProvisionedController.isDeviceProvisioned()) return;
 
         final boolean afterKeyguardGone = PreviewInflater.wouldLaunchResolverActivity(
                 mContext, intent, mLockscreenUserManager.getCurrentUserId());
@@ -2888,7 +2531,7 @@
                 }
             } else if (ACTION_FAKE_ARTWORK.equals(action)) {
                 if (DEBUG_MEDIA_FAKE_ARTWORK) {
-                    updateMediaMetaData(true, true);
+                    mPresenter.updateMediaMetaData(true, true);
                 }
             }
         }
@@ -2917,7 +2560,8 @@
         dismissKeyguardThenExecute(action, null /* cancelRunnable */, afterKeyguardGone);
     }
 
-    private void dismissKeyguardThenExecute(OnDismissAction action, Runnable cancelAction,
+    @Override
+    public void dismissKeyguardThenExecute(OnDismissAction action, Runnable cancelAction,
             boolean afterKeyguardGone) {
         if (mWakefulnessLifecycle.getWakefulness() == WAKEFULNESS_ASLEEP
                 && mUnlockMethodCache.canSkipBouncer()
@@ -2951,38 +2595,10 @@
     }
 
     @Override
-    public void onUserSwitched(int newUserId) {
-        // Begin old BaseStatusBar.userSwitched
-        setHeadsUpUser(newUserId);
-        // End old BaseStatusBar.userSwitched
-        if (MULTIUSER_DEBUG) mNotificationPanelDebugText.setText("USER " + newUserId);
-        animateCollapsePanels();
-        if (mReinflateNotificationsOnUserSwitched) {
-            mEntryManager.updateNotificationsOnDensityOrFontScaleChanged();
-            mReinflateNotificationsOnUserSwitched = false;
-        }
-        updateNotificationViews();
-        mMediaManager.clearCurrentMediaNotification();
-        setLockscreenUser(newUserId);
-        mWallpaperChangedReceiver.onReceive(mContext, null);
-    }
-
-    @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) {
+    public void setLockscreenUser(int newUserId) {
         mLockscreenWallpaper.setCurrentUser(newUserId);
         mScrimController.setCurrentUser(newUserId);
-        updateMediaMetaData(true, false);
+        mWallpaperChangedReceiver.onReceive(mContext, null);
     }
 
     /**
@@ -3020,8 +2636,6 @@
         if (mStatusBarWindowController != null && mNaturalBarHeight != oldBarHeight) {
             mStatusBarWindowController.setBarHeight(mNaturalBarHeight);
         }
-        mMaxAllowedKeyguardNotifications = res.getInteger(
-                R.integer.keyguard_max_notification_count);
 
         if (DEBUG) Log.v(TAG, "defineSlots");
     }
@@ -3058,12 +2672,12 @@
         if (visibleToUser) {
             boolean pinnedHeadsUp = mHeadsUpManager.hasPinnedHeadsUp();
             boolean clearNotificationEffects =
-                    !isPresenterFullyCollapsed() &&
+                    !mPresenter.isPresenterFullyCollapsed() &&
                             (mState == StatusBarState.SHADE
                                     || mState == StatusBarState.SHADE_LOCKED);
             int notificationLoad = mEntryManager.getNotificationData().getActiveNotifications()
                     .size();
-            if (pinnedHeadsUp && isPresenterFullyCollapsed()) {
+            if (pinnedHeadsUp && mPresenter.isPresenterFullyCollapsed()) {
                 notificationLoad = 1;
             }
             final int finalNotificationLoad = notificationLoad;
@@ -3298,13 +2912,8 @@
         }
     }
 
-    @Override
-    public boolean isPresenterFullyCollapsed() {
-        return mNotificationPanel.isFullyCollapsed();
-    }
-
     public void showKeyguard() {
-        mKeyguardRequested = true;
+        mStatusBarStateController.setKeyguardRequested(true);
         mStatusBarStateController.setLeaveOpenOnKeyguardHide(false);
         mPendingRemoteInputView = null;
         updateIsKeyguard();
@@ -3312,11 +2921,12 @@
     }
 
     public boolean hideKeyguard() {
-        mKeyguardRequested = false;
+        mStatusBarStateController.setKeyguardRequested(false);
         return updateIsKeyguard();
     }
 
     /**
+     * stop(tag)
      * @return True if StatusBar state is FULLSCREEN_USER_SWITCHER.
      */
     public boolean isFullScreenUserSwitcherState() {
@@ -3333,7 +2943,8 @@
         // turned off fully.
         boolean keyguardForDozing = mDozingRequested &&
                 (!mDeviceInteractive || isGoingToSleep() && (isScreenFullyOff() || mIsKeyguard));
-        boolean shouldBeKeyguard = (mKeyguardRequested || keyguardForDozing) && !wakeAndUnlocking;
+        boolean shouldBeKeyguard = (mStatusBarStateController.isKeyguardRequested()
+                || keyguardForDozing) && !wakeAndUnlocking;
         if (keyguardForDozing) {
             updatePanelExpansionForKeyguard();
         }
@@ -3385,13 +2996,7 @@
         releaseGestureWakeLock();
         runLaunchTransitionEndRunnable();
         mLaunchTransitionFadingAway = false;
-        updateMediaMetaData(true /* metaDataChanged */, true);
-    }
-
-    public boolean isCollapsing() {
-        return mNotificationPanel.isCollapsing()
-                || mActivityLaunchAnimator.isAnimationPending()
-                || mActivityLaunchAnimator.isAnimationRunning();
+        mPresenter.updateMediaMetaData(true /* metaDataChanged */, true);
     }
 
     public void addPostCollapseAction(Runnable r) {
@@ -3415,12 +3020,13 @@
         mHandler.removeMessages(MSG_LAUNCH_TRANSITION_TIMEOUT);
         mLaunchTransitionEndRunnable = endRunnable;
         Runnable hideRunnable = () -> {
+            mKeyguardMonitor.setLaunchTransitionFadingAway(true);
             mLaunchTransitionFadingAway = true;
             if (beforeFading != null) {
                 beforeFading.run();
             }
             updateScrimController();
-            updateMediaMetaData(false, true);
+            mPresenter.updateMediaMetaData(false, true);
             mNotificationPanel.setAlpha(1);
             mNotificationPanel.animate()
                     .alpha(0)
@@ -3505,9 +3111,8 @@
             mLockscreenUserManager.updatePublicMode();
             mEntryManager.updateNotifications();
         }
-        View viewToClick = null;
         if (mStatusBarStateController.leaveOpenOnKeyguardHide()) {
-            if (!mKeyguardRequested) {
+            if (!mStatusBarStateController.isKeyguardRequested()) {
                 mStatusBarStateController.setLeaveOpenOnKeyguardHide(false);
             }
             long delay = mKeyguardMonitor.calculateGoingToFullShadeDelay();
@@ -3516,10 +3121,6 @@
                 mDraggedDownRow.setUserLocked(false);
                 mDraggedDownRow = null;
             }
-            if (!mKeyguardRequested) {
-                viewToClick = mPendingRemoteInputView;
-                mPendingRemoteInputView = null;
-            }
 
             // Disable layout transitions in navbar for this transition because the load is just
             // too heavy for the CPU and GPU on any device.
@@ -3530,10 +3131,6 @@
             instantCollapseNotificationPanel();
         }
 
-        if (viewToClick != null && viewToClick.isAttachedToWindow()) {
-            viewToClick.callOnClick();
-        }
-
         // Keyguard state has changed, but QS is not listening anymore. Make sure to update the tile
         // visibilities so next time we open the panel we know the correct height already.
         if (mQSPanel != null) {
@@ -3575,7 +3172,7 @@
         mCommandQueue.appTransitionStarting(startTime + fadeoutDuration
                         - LightBarTransitionsController.DEFAULT_TINT_ANIMATION_DURATION,
                 LightBarTransitionsController.DEFAULT_TINT_ANIMATION_DURATION, true);
-        recomputeDisableFlags(fadeoutDuration > 0 /* animate */);
+        mCommandQueue.recomputeDisableFlags(fadeoutDuration > 0 /* animate */);
         mCommandQueue.appTransitionStarting(
                     startTime - LightBarTransitionsController.DEFAULT_TINT_ANIMATION_DURATION,
                     LightBarTransitionsController.DEFAULT_TINT_ANIMATION_DURATION, true);
@@ -3697,15 +3294,43 @@
         }
     }
 
-    protected void showBouncer(boolean scrimmed) {
+    @Override
+    public void showBouncer(boolean scrimmed) {
         mStatusBarKeyguardViewManager.showBouncer(scrimmed);
     }
 
-    private void instantExpandNotificationsPanel() {
+    @Override
+    public void instantExpandNotificationsPanel() {
         // Make our window larger and the panel expanded.
         makeExpandedVisible(true);
         mNotificationPanel.expand(false /* animate */);
-        recomputeDisableFlags(false /* animate */);
+        mCommandQueue.recomputeDisableFlags(false /* animate */);
+    }
+
+    @Override
+    public boolean closeShadeIfOpen() {
+        if (!mNotificationPanel.isFullyCollapsed()) {
+            mCommandQueue.animateCollapsePanels(
+                    CommandQueue.FLAG_EXCLUDE_RECENTS_PANEL, true /* force */);
+            visibilityChanged(false);
+            mAssistManager.hideAssist();
+        }
+        return false;
+    }
+
+    @Override
+    public void postOnShadeExpanded(Runnable executable) {
+        mNotificationPanel.getViewTreeObserver().addOnGlobalLayoutListener(
+                new ViewTreeObserver.OnGlobalLayoutListener() {
+                    @Override
+                    public void onGlobalLayout() {
+                        if (getStatusBarWindow().getHeight() != getStatusBarHeight()) {
+                            mNotificationPanel.getViewTreeObserver()
+                                    .removeOnGlobalLayoutListener(this);
+                            mNotificationPanel.post(executable);
+                        }
+                    }
+                });
     }
 
     private void instantCollapseNotificationPanel() {
@@ -3714,25 +3339,7 @@
     }
 
     @Override
-    public void onActivated(ActivatableNotificationView view) {
-        onActivated((View) view);
-        mNotificationPanel.setActivatedChild(view);
-    }
-
-    public void onActivated(View view) {
-        mLockscreenGestureLogger.write(
-                MetricsEvent.ACTION_LS_NOTE,
-                0 /* lengthDp - N/A */, 0 /* velocityDp - N/A */);
-        mKeyguardIndicationController.showTransientIndication(R.string.notification_tap_again);
-        ActivatableNotificationView previousView = mNotificationPanel.getActivatedChild();
-        if (previousView != null) {
-            previousView.makeInactive(true /* animate */);
-        }
-    }
-
-    @Override
     public void onStatePreChange(int oldState, int newState) {
-
         // If we're visible and switched to SHADE_LOCKED (the user dragged
         // down on the lockscreen), clear notification LED, vibration,
         // ringing.
@@ -3780,7 +3387,7 @@
         updateDozingState();
         checkBarModes();
         updateScrimController();
-        updateMediaMetaData(false, mState != StatusBarState.KEYGUARD);
+        mPresenter.updateMediaMetaData(false, mState != StatusBarState.KEYGUARD);
         mKeyguardMonitor.notifyKeyguardState(mStatusBarKeyguardViewManager.isShowing(),
                 mUnlockMethodCache.isMethodSecure(),
                 mStatusBarKeyguardViewManager.isOccluded());
@@ -3797,6 +3404,7 @@
                 && DozeParameters.getInstance(mContext).shouldControlScreenOff();
         mNotificationPanel.resetViews(dozingAnimated);
 
+        updateQsExpansionEnabled();
         mKeyguardViewMediator.setAodShowing(mDozing);
 
         //TODO: make these folks listeners of StatusBarStateController.onDozingChanged
@@ -3828,15 +3436,7 @@
         mStatusBarStateController.setIsDozing(dozing);
     }
 
-    @Override
-    public void onActivationReset(ActivatableNotificationView view) {
-        if (view == mNotificationPanel.getActivatedChild()) {
-            mNotificationPanel.setActivatedChild(null);
-            onActivationReset((View)view);
-        }
-    }
-
-    public void onActivationReset(View view) {
+    public void onActivationReset() {
         mKeyguardIndicationController.hideTransientIndication();
     }
 
@@ -3846,7 +3446,7 @@
 
     public void onClosingFinished() {
         runPostCollapseRunnables();
-        if (!isPresenterFullyCollapsed()) {
+        if (!mPresenter.isPresenterFullyCollapsed()) {
             // if we set it not to be focusable when collapsing, we have to undo it when we aborted
             // the closing
             mStatusBarWindowController.setStatusBarFocusable(true);
@@ -3886,22 +3486,6 @@
         }
     }
 
-    @Override
-    public int getMaxNotificationsWhileLocked(boolean recompute) {
-        if (recompute) {
-            mMaxKeyguardNotifications = Math.max(1,
-                    mNotificationPanel.computeMaxKeyguardNotifications(
-                            mMaxAllowedKeyguardNotifications));
-            return mMaxKeyguardNotifications;
-        }
-        return mMaxKeyguardNotifications;
-    }
-
-    @Override
-    public void onUpdateRowStates() {
-        mNotificationPanel.onUpdateRowStates();
-    }
-
     // TODO: Figure out way to remove these.
     public NavigationBarView getNavigationBarView() {
         return (mNavigationBar != null ? (NavigationBarView) mNavigationBar.getView() : null);
@@ -3958,184 +3542,6 @@
         }
     }
 
-    public void onLockedNotificationImportanceChange(OnDismissAction dismissAction) {
-        mStatusBarStateController.setLeaveOpenOnKeyguardHide(true);
-        dismissKeyguardThenExecute(dismissAction, true /* afterKeyguardGone */);
-    }
-
-    @Override
-    public void onLockedRemoteInput(ExpandableNotificationRow row, View clicked) {
-        mStatusBarStateController.setLeaveOpenOnKeyguardHide(true);
-        showBouncer(true /* scrimmed */);
-        mPendingRemoteInputView = clicked;
-    }
-
-    @Override
-    public void onMakeExpandedVisibleForRemoteInput(ExpandableNotificationRow row,
-            View clickedView) {
-        if (isKeyguardShowing()) {
-            onLockedRemoteInput(row, clickedView);
-        } else {
-            row.setUserExpanded(true);
-            row.getPrivateLayout().setOnExpandedVisibleListener(clickedView::performClick);
-        }
-    }
-
-    @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
-        // it's called by other code
-        mPendingWorkRemoteInputView = null;
-        // Begin old BaseStatusBar.startWorkChallengeIfNecessary.
-        final Intent newIntent = mKeyguardManager.createConfirmDeviceCredentialIntent(null,
-                null, userId);
-        if (newIntent == null) {
-            return false;
-        }
-        final Intent callBackIntent = new Intent(NOTIFICATION_UNLOCKED_BY_WORK_CHALLENGE_ACTION);
-        callBackIntent.putExtra(Intent.EXTRA_INTENT, intendSender);
-        callBackIntent.putExtra(Intent.EXTRA_INDEX, notificationKey);
-        callBackIntent.setPackage(mContext.getPackageName());
-
-        PendingIntent callBackPendingIntent = PendingIntent.getBroadcast(
-                mContext,
-                0,
-                callBackIntent,
-                PendingIntent.FLAG_CANCEL_CURRENT |
-                        PendingIntent.FLAG_ONE_SHOT |
-                        PendingIntent.FLAG_IMMUTABLE);
-        newIntent.putExtra(
-                Intent.EXTRA_INTENT,
-                callBackPendingIntent.getIntentSender());
-        try {
-            ActivityManager.getService().startConfirmDeviceCredentialIntent(newIntent,
-                    null /*options*/);
-        } catch (RemoteException ex) {
-            // ignore
-        }
-        return true;
-        // End old BaseStatusBar.startWorkChallengeIfNecessary.
-    }
-
-    @Override
-    public void onLockedWorkRemoteInput(int userId, ExpandableNotificationRow row,
-            View clicked) {
-        // Collapse notification and show work challenge
-        animateCollapsePanels();
-        startWorkChallengeIfNecessary(userId, null, null);
-        // Add pending remote input view after starting work challenge, as starting work challenge
-        // will clear all previous pending review view
-        mPendingWorkRemoteInputView = clicked;
-    }
-
-    @Override
-    public void onWorkChallengeChanged() {
-        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;
-                if (pendingWorkRemoteInputView == null) {
-                    return;
-                }
-
-                // Climb up the hierarchy until we get to the container for this row.
-                ViewParent p = pendingWorkRemoteInputView.getParent();
-                while (!(p instanceof ExpandableNotificationRow)) {
-                    if (p == null) {
-                        return;
-                    }
-                    p = p.getParent();
-                }
-
-                final ExpandableNotificationRow row = (ExpandableNotificationRow) p;
-                ViewParent viewParent = row.getParent();
-                if (viewParent instanceof NotificationStackScrollLayout) {
-                    final NotificationStackScrollLayout scrollLayout =
-                            (NotificationStackScrollLayout) viewParent;
-                    row.makeActionsVisibile();
-                    row.post(() -> {
-                        final Runnable finishScrollingCallback = () -> {
-                            mPendingWorkRemoteInputView.callOnClick();
-                            mPendingWorkRemoteInputView = null;
-                            scrollLayout.setFinishScrollingCallback(null);
-                        };
-                        if (scrollLayout.scrollTo(row)) {
-                            // It scrolls! So call it when it's finished.
-                            scrollLayout.setFinishScrollingCallback(finishScrollingCallback);
-                        } else {
-                            // It does not scroll, so call it now!
-                            finishScrollingCallback.run();
-                        }
-                    });
-                }
-            };
-            mNotificationPanel.getViewTreeObserver().addOnGlobalLayoutListener(
-                    new ViewTreeObserver.OnGlobalLayoutListener() {
-                        @Override
-                        public void onGlobalLayout() {
-                            if (mNotificationPanel.mStatusBar.getStatusBarWindow()
-                                    .getHeight() != mNotificationPanel.mStatusBar
-                                            .getStatusBarHeight()) {
-                                mNotificationPanel.getViewTreeObserver()
-                                        .removeOnGlobalLayoutListener(this);
-                                mNotificationPanel.post(clickPendingViewRunnable);
-                            }
-                        }
-                    });
-            instantExpandNotificationsPanel();
-        }
-    }
-
-    @Override
-    public void onExpandClicked(Entry clickedEntry, boolean nowExpanded) {
-        mHeadsUpManager.setExpanded(clickedEntry, nowExpanded);
-        if (mState == StatusBarState.KEYGUARD && nowExpanded) {
-            goToLockedShade(clickedEntry.row);
-        }
-    }
-
     /**
      * Goes back to the keyguard after hanging around in {@link StatusBarState#SHADE_LOCKED}.
      */
@@ -4149,7 +3555,7 @@
         mBouncerShowing = bouncerShowing;
         if (mStatusBarView != null) mStatusBarView.setBouncerShowing(bouncerShowing);
         updateHideIconsForBouncer(true /* animate */);
-        recomputeDisableFlags(true /* animate */);
+        mCommandQueue.recomputeDisableFlags(true /* animate */);
         updateScrimController();
     }
 
@@ -4273,25 +3679,6 @@
     }
 
     @Override
-    public void wakeUpIfDozing(long time, View where) {
-        if (mDozing) {
-            PowerManager pm = (PowerManager) mContext.getSystemService(Context.POWER_SERVICE);
-            pm.wakeUp(time, "com.android.systemui:NODOZE");
-            mWakeUpComingFromTouch = true;
-            where.getLocationInWindow(mTmpInt2);
-            mWakeUpTouchLocation = new PointF(mTmpInt2[0] + where.getWidth() / 2,
-                    mTmpInt2[1] + where.getHeight() / 2);
-            mStatusBarKeyguardViewManager.notifyDeviceWakeUpRequested();
-            mFalsingManager.onScreenOnFromTouch();
-        }
-    }
-
-    @Override
-    public boolean isDeviceLocked(int userId) {
-        return mKeyguardManager.isDeviceLocked(userId);
-    }
-
-    @Override
     public void appTransitionCancelled() {
         getComponent(Divider.class).onAppTransitionFinished();
     }
@@ -4669,8 +4056,6 @@
 
     protected AmbientPulseManager mAmbientPulseManager = Dependency.get(AmbientPulseManager.class);
 
-    private AboveShelfObserver mAboveShelfObserver;
-
     // handling reordering
     protected VisualStabilityManager mVisualStabilityManager;
 
@@ -4688,7 +4073,6 @@
     protected StatusBarKeyguardViewManager mStatusBarKeyguardViewManager;
 
     protected KeyguardManager mKeyguardManager;
-    private LockPatternUtils mLockPatternUtils;
     private DeviceProvisionedController mDeviceProvisionedController
             = Dependency.get(DeviceProvisionedController.class);
 
@@ -4707,28 +4091,10 @@
 
     protected AssistManager mAssistManager;
 
-    protected boolean mVrMode;
-
     public boolean isDeviceInteractive() {
         return mDeviceInteractive;
     }
 
-    @Override  // NotificationData.Environment
-    public boolean isDeviceProvisioned() {
-        return mDeviceProvisionedController.isDeviceProvisioned();
-    }
-
-    private final IVrStateCallbacks mVrStateCallbacks = new IVrStateCallbacks.Stub() {
-        @Override
-        public void onVrStateChanged(boolean enabled) {
-            mVrMode = enabled;
-        }
-    };
-
-    public boolean isDeviceInVrMode() {
-        return mVrMode;
-    }
-
     private final BroadcastReceiver mBannerActionBroadcastReceiver = new BroadcastReceiver() {
         @Override
         public void onReceive(Context context, Intent intent) {
@@ -4754,182 +4120,13 @@
     };
 
     @Override
-    public void onNotificationClicked(StatusBarNotification sbn, ExpandableNotificationRow row) {
-        RemoteInputController controller = mRemoteInputManager.getController();
-        if (controller.isRemoteInputActive(row.getEntry())
-                && !TextUtils.isEmpty(row.getActiveRemoteInputText())) {
-            // We have an active remote input typed and the user clicked on the notification.
-            // this was probably unintentional, so we're closing the edit text instead.
-            controller.closeRemoteInputs();
-            return;
-        }
-        Notification notification = sbn.getNotification();
-        final PendingIntent intent = notification.contentIntent != null
-                ? notification.contentIntent
-                : notification.fullScreenIntent;
-        final String notificationKey = sbn.getKey();
-
-        boolean isActivityIntent = intent.isActivity();
-        final boolean afterKeyguardGone = isActivityIntent
-                && PreviewInflater.wouldLaunchResolverActivity(mContext, intent.getIntent(),
-                mLockscreenUserManager.getCurrentUserId());
-        final boolean wasOccluded = mIsOccluded;
-        boolean showOverLockscreen = mStatusBarKeyguardViewManager.isShowing()
-                && PreviewInflater.wouldShowOverLockscreen(mContext,
-                intent.getIntent(),
-                mLockscreenUserManager.getCurrentUserId());
-        OnDismissAction postKeyguardAction = () -> {
-            // TODO: Some of this code may be able to move to NotificationEntryManager.
-            if (mHeadsUpManager != null && mHeadsUpManager.isAlerting(notificationKey)) {
-                // Release the HUN notification to the shade.
-
-                if (isPresenterFullyCollapsed()) {
-                    HeadsUpUtil.setIsClickedHeadsUpNotification(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.removeNotification(sbn.getKey(),
-                        true /* releaseImmediately */);
-            }
-            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) {
-                }
-                int launchResult = ActivityManager.START_CANCELED;
-                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 (isActivityIntent) {
-                        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
-                                collapseOnMainThread();
-                                return;
-                            }
-                        }
-                    }
-                    Intent fillInIntent = null;
-                    Entry entry = row.getEntry();
-                    CharSequence remoteInputText = null;
-                    if (!TextUtils.isEmpty(entry.remoteInputText)) {
-                        remoteInputText = entry.remoteInputText;
-                    }
-                    if (!TextUtils.isEmpty(remoteInputText)
-                            && !controller.isSpinning(entry.key)) {
-                        fillInIntent = new Intent().putExtra(Notification.EXTRA_REMOTE_INPUT_DRAFT,
-                                remoteInputText.toString());
-                    }
-                    RemoteAnimationAdapter adapter = mActivityLaunchAnimator.getLaunchAnimation(
-                            row, wasOccluded);
-                    try {
-                        if (adapter != null) {
-                            ActivityTaskManager.getService()
-                                    .registerRemoteAnimationForNextActivityStart(
-                                            intent.getCreatorPackage(), adapter);
-                        }
-                        launchResult = intent.sendAndReturnResult(mContext, 0, fillInIntent, null,
-                                null, null, getActivityOptions(adapter));
-                        mActivityLaunchAnimator.setLaunchResult(launchResult, isActivityIntent);
-                    } catch (RemoteException | 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 (isActivityIntent) {
-                        mAssistManager.hideAssist();
-                    }
-                }
-                if (shouldCollapse()) {
-                    collapseOnMainThread();
-                }
-
-                final int count =
-                        mEntryManager.getNotificationData().getActiveNotifications().size();
-                final int rank = mEntryManager.getNotificationData().getRank(notificationKey);
-                final NotificationVisibility nv = NotificationVisibility.obtain(notificationKey,
-                        rank, count, true);
-                try {
-                    mBarService.onNotificationClick(notificationKey, nv);
-                } catch (RemoteException ex) {
-                    // system process is dead if we're here.
-                }
-                if (parentToCancelFinal != null) {
-                    removeNotification(parentToCancelFinal);
-                }
-                if (shouldAutoCancel(sbn)
-                        || mRemoteInputManager.isNotificationKeptForRemoteInputHistory(
-                                notificationKey)) {
-                    // Automatically remove all notifications that we may have kept around longer
-                    removeNotification(sbn);
-                }
-
-                mIsCollapsingToShowActivityOverLockscreen = false;
-            };
-
-            if (showOverLockscreen) {
-                addPostCollapseAction(runnable);
-                collapsePanel(true /* animate */);
-            } else if (mStatusBarKeyguardViewManager.isShowing()
-                    && mStatusBarKeyguardViewManager.isOccluded()) {
-                mStatusBarKeyguardViewManager.addAfterKeyguardGoneRunnable(runnable);
-                collapsePanel(true /* animate */);
-            } else {
-                new Thread(runnable).start();
-            }
-
-            return !mNotificationPanel.isFullyCollapsed();
-        };
-        if (showOverLockscreen) {
-            mIsCollapsingToShowActivityOverLockscreen = true;
-            postKeyguardAction.onDismiss();
-        } else {
-            dismissKeyguardThenExecute(postKeyguardAction, afterKeyguardGone);
-        }
-    }
-
-    private void collapseOnMainThread() {
-        if (Looper.getMainLooper().isCurrentThread()) {
-            collapsePanel();
-        } else {
-            Dependency.get(Dependency.MAIN_HANDLER).post(this::collapsePanel);
-        }
-    }
-
-    private boolean shouldCollapse() {
-        return mState != StatusBarState.SHADE || !mActivityLaunchAnimator.isAnimationPending();
-    }
-
     public void collapsePanel(boolean animate) {
         if (animate) {
             boolean willCollapse = collapsePanel();
             if (!willCollapse) {
                 runPostCollapseRunnables();
             }
-        } else if (!isPresenterFullyCollapsed()) {
+        } else if (!mPresenter.isPresenterFullyCollapsed()) {
             instantCollapseNotificationPanel();
             visibilityChanged(false);
         } else {
@@ -4937,7 +4134,8 @@
         }
     }
 
-    private boolean collapsePanel() {
+    @Override
+    public boolean collapsePanel() {
         if (!mNotificationPanel.isFullyCollapsed()) {
             // close the shade if it was open
             animateCollapsePanels(CommandQueue.FLAG_EXCLUDE_RECENTS_PANEL, true /* force */,
@@ -4950,59 +4148,8 @@
         }
     }
 
-    private void removeNotification(StatusBarNotification notification) {
-        // We have to post it to the UI thread for synchronization
-        mHandler.post(() -> {
-            Runnable removeRunnable =
-                    () -> mEntryManager.performRemoveNotification(notification);
-            if (isCollapsing()) {
-                // To avoid lags we're only performing the remove
-                // after the shade was collapsed
-                addPostCollapseAction(removeRunnable);
-            } else {
-                removeRunnable.run();
-            }
-        });
-    }
-
     protected NotificationListener mNotificationListener;
 
-    @Override  // NotificationData.Environment
-    public boolean isNotificationForCurrentProfiles(StatusBarNotification n) {
-        final int notificationUserId = n.getUserId();
-        if (DEBUG && MULTIUSER_DEBUG) {
-            Log.v(TAG, String.format("%s: current userid: %d, notification userid: %d", n,
-                    mLockscreenUserManager.getCurrentUserId(), notificationUserId));
-        }
-        return mLockscreenUserManager.isCurrentProfile(notificationUserId);
-    }
-
-    @Override
-    public NotificationGroupManager getGroupManager() {
-        return mGroupManager;
-    }
-
-    @Override
-    public void startNotificationGutsIntent(final Intent intent, final int appUid,
-            ExpandableNotificationRow row) {
-        dismissKeyguardThenExecute(() -> {
-            AsyncTask.execute(() -> {
-                int launchResult = TaskStackBuilder.create(mContext)
-                        .addNextIntentWithParentStack(intent)
-                        .startActivities(getActivityOptions(
-                                mActivityLaunchAnimator.getLaunchAnimation(row, mIsOccluded)),
-                                new UserHandle(UserHandle.getUserId(appUid)));
-                mActivityLaunchAnimator.setLaunchResult(launchResult, true /* isActivityIntent */);
-                if (shouldCollapse()) {
-                    // Putting it back on the main thread, since we're touching views
-                    mStatusBarWindow.post(() -> animateCollapsePanels(
-                            CommandQueue.FLAG_EXCLUDE_RECENTS_PANEL, true /* force */));
-                }
-            });
-            return true;
-        }, false /* afterKeyguardGone */);
-    }
-
     public void setNotificationSnoozed(StatusBarNotification sbn, SnoozeOption snoozeOption) {
         if (snoozeOption.getSnoozeCriterion() != null) {
             mNotificationListener.snoozeNotification(sbn.getKey(),
@@ -5063,7 +4210,7 @@
             // Immediately update the icon hidden state, since that should only apply if we're
             // staying fullscreen.
             mWereIconsJustHidden = false;
-            recomputeDisableFlags(true);
+            mCommandQueue.recomputeDisableFlags(true);
         }
         updateHideIconsForBouncer(true /* animate */);
     }
@@ -5076,24 +4223,6 @@
         KeyboardShortcuts.dismiss();
     }
 
-    @Override  // NotificationData.Environment
-    public boolean shouldHideNotifications(int userId) {
-        return mLockscreenUserManager.shouldHideNotifications(userId);
-    }
-
-    @Override // NotificationDate.Environment
-    public boolean shouldHideNotifications(String key) {
-        return mLockscreenUserManager.shouldHideNotifications(key);
-    }
-
-    /**
-     * Returns true if we're on a secure lockscreen.
-     */
-    @Override  // NotificationData.Environment
-    public boolean isSecurelyLocked(int userId) {
-        return mLockscreenUserManager.isLockscreenPublicMode(userId);
-    }
-
     /**
      * Called when the notification panel layouts
      */
@@ -5105,8 +4234,8 @@
         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 = getMaxNotificationsWhileLocked(false /* recompute */);
-            int maxNotifications = getMaxNotificationsWhileLocked(true /* recompute */);
+            int maxBefore = mPresenter.getMaxNotificationsWhileLocked(false /* recompute */);
+            int maxNotifications = mPresenter.getMaxNotificationsWhileLocked(true /* recompute */);
             if (maxBefore != maxNotifications) {
                 mViewHierarchyManager.updateRowStates();
             }
@@ -5114,7 +4243,7 @@
     }
 
     public void startPendingIntentDismissingKeyguard(final PendingIntent intent) {
-        if (!isDeviceProvisioned()) return;
+        if (!mDeviceProvisionedController.isDeviceProvisioned()) return;
 
         final boolean afterKeyguardGone = intent.isActivity()
                 && PreviewInflater.wouldLaunchResolverActivity(mContext, intent.getIntent(),
@@ -5148,18 +4277,7 @@
         }, afterKeyguardGone);
     }
 
-    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;
-    }
-
-    protected Bundle getActivityOptions(@Nullable RemoteAnimationAdapter animationAdapter) {
+    public static Bundle getActivityOptions(@Nullable RemoteAnimationAdapter animationAdapter) {
         ActivityOptions options;
         if (animationAdapter != null) {
             options = ActivityOptions.makeRemoteAnimation(animationAdapter);
@@ -5276,28 +4394,4 @@
     public NotificationGutsManager getGutsManager() {
         return mGutsManager;
     }
-
-    @Override
-    public boolean isPresenterLocked() {
-        return mState == StatusBarState.KEYGUARD;
-    }
-
-    @Override
-    public Handler getHandler() {
-        return mHandler;
-    }
-
-    private final NotificationInfo.CheckSaveListener mCheckSaveListener =
-            (Runnable saveImportance, StatusBarNotification sbn) -> {
-                // If the user has security enabled, show challenge if the setting is changed.
-                if (mLockscreenUserManager.isLockscreenPublicMode(sbn.getUser().getIdentifier())
-                        && mKeyguardManager.isKeyguardLocked()) {
-                    onLockedNotificationImportanceChange(() -> {
-                        saveImportance.run();
-                        return true;
-                    });
-                } else {
-                    saveImportance.run();
-                }
-            };
 }
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/phone/StatusBarKeyguardViewManager.java b/packages/SystemUI/src/com/android/systemui/statusbar/phone/StatusBarKeyguardViewManager.java
index ac3608b..c560301 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/StatusBarKeyguardViewManager.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/StatusBarKeyguardViewManager.java
@@ -16,7 +16,7 @@
 
 package com.android.systemui.statusbar.phone;
 
-import static com.android.keyguard.KeyguardHostView.OnDismissAction;
+import static com.android.systemui.plugins.ActivityStarter.OnDismissAction;
 import static com.android.systemui.statusbar.phone.BiometricUnlockController.MODE_WAKE_AND_UNLOCK;
 import static com.android.systemui.statusbar.phone.BiometricUnlockController.MODE_WAKE_AND_UNLOCK_PULSING;
 
@@ -43,6 +43,7 @@
 import com.android.systemui.SystemUIFactory;
 import com.android.systemui.keyguard.DismissCallbackRegistry;
 import com.android.systemui.statusbar.CommandQueue;
+import com.android.systemui.statusbar.NotificationMediaManager;
 import com.android.systemui.statusbar.RemoteInputController;
 import com.android.systemui.statusbar.StatusBarStateController;
 import com.android.systemui.statusbar.phone.KeyguardBouncer.BouncerExpansionCallback;
@@ -124,6 +125,8 @@
     // Dismiss action to be launched when we stop dozing or the keyguard is gone.
     private DismissWithActionRequest mPendingWakeupAction;
     private final KeyguardMonitor mKeyguardMonitor = Dependency.get(KeyguardMonitor.class);
+    private final NotificationMediaManager mMediaManager =
+            Dependency.get(NotificationMediaManager.class);
 
     private final KeyguardUpdateMonitorCallback mUpdateMonitorCallback =
             new KeyguardUpdateMonitorCallback() {
@@ -381,7 +384,7 @@
         boolean isOccluding = !mOccluded && occluded;
         mOccluded = occluded;
         if (mShowing) {
-            mStatusBar.updateMediaMetaData(false, animate && !occluded);
+            mMediaManager.updateMediaMetaData(false, animate && !occluded);
         }
         mStatusBarWindowController.setKeyguardOccluded(occluded);
 
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/phone/StatusBarNotificationPresenter.java b/packages/SystemUI/src/com/android/systemui/statusbar/phone/StatusBarNotificationPresenter.java
new file mode 100644
index 0000000..635ffc7
--- /dev/null
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/StatusBarNotificationPresenter.java
@@ -0,0 +1,672 @@
+/*
+ * Copyright (C) 2018 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file
+ * except in compliance with the License. You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software distributed under the
+ * License is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied. See the License for the specific language governing
+ * permissions and limitations under the License.
+ */
+
+package com.android.systemui.statusbar.phone;
+
+import static com.android.systemui.Dependency.MAIN_HANDLER;
+import static com.android.systemui.SysUiServiceProvider.getComponent;
+import static com.android.systemui.statusbar.phone.StatusBar.CLOSE_PANEL_WHEN_EMPTIED;
+import static com.android.systemui.statusbar.phone.StatusBar.DEBUG;
+import static com.android.systemui.statusbar.phone.StatusBar.MULTIUSER_DEBUG;
+import static com.android.systemui.statusbar.phone.StatusBar.SPEW;
+import static com.android.systemui.statusbar.phone.StatusBar.getActivityOptions;
+
+import android.app.ActivityManager;
+import android.app.ActivityTaskManager;
+import android.app.KeyguardManager;
+import android.app.Notification;
+import android.app.PendingIntent;
+import android.app.TaskStackBuilder;
+import android.content.Context;
+import android.content.Intent;
+import android.content.pm.PackageManager;
+import android.os.AsyncTask;
+import android.os.Looper;
+import android.os.RemoteException;
+import android.os.ServiceManager;
+import android.os.UserHandle;
+import android.service.notification.StatusBarNotification;
+import android.service.vr.IVrManager;
+import android.service.vr.IVrStateCallbacks;
+import android.text.TextUtils;
+import android.util.Log;
+import android.util.Slog;
+import android.view.RemoteAnimationAdapter;
+import android.view.View;
+import android.view.ViewGroup;
+import android.view.accessibility.AccessibilityManager;
+import android.widget.TextView;
+
+import com.android.internal.logging.nano.MetricsProto.MetricsEvent;
+import com.android.internal.statusbar.IStatusBarService;
+import com.android.internal.statusbar.NotificationVisibility;
+import com.android.internal.widget.LockPatternUtils;
+import com.android.keyguard.KeyguardUpdateMonitor;
+import com.android.systemui.Dependency;
+import com.android.systemui.InitController;
+import com.android.systemui.R;
+import com.android.systemui.assist.AssistManager;
+import com.android.systemui.plugins.ActivityStarter;
+import com.android.systemui.plugins.ActivityStarter.OnDismissAction;
+import com.android.systemui.statusbar.AmbientPulseManager;
+import com.android.systemui.statusbar.CommandQueue;
+import com.android.systemui.statusbar.NotificationLockscreenUserManager;
+import com.android.systemui.statusbar.NotificationMediaManager;
+import com.android.systemui.statusbar.NotificationPresenter;
+import com.android.systemui.statusbar.NotificationRemoteInputManager;
+import com.android.systemui.statusbar.NotificationRemoteInputManager.Callback;
+import com.android.systemui.statusbar.NotificationViewHierarchyManager;
+import com.android.systemui.statusbar.RemoteInputController;
+import com.android.systemui.statusbar.StatusBarState;
+import com.android.systemui.statusbar.StatusBarStateController;
+import com.android.systemui.statusbar.notification.AboveShelfObserver;
+import com.android.systemui.statusbar.notification.ActivityLaunchAnimator;
+import com.android.systemui.statusbar.notification.NotificationData.Entry;
+import com.android.systemui.statusbar.notification.NotificationEntryManager;
+import com.android.systemui.statusbar.notification.row.ActivatableNotificationView;
+import com.android.systemui.statusbar.notification.row.ExpandableNotificationRow;
+import com.android.systemui.statusbar.notification.row.NotificationGutsManager;
+import com.android.systemui.statusbar.notification.row.NotificationGutsManager.OnSettingsClickListener;
+import com.android.systemui.statusbar.notification.row.NotificationInfo.CheckSaveListener;
+import com.android.systemui.statusbar.notification.stack.NotificationListContainer;
+import com.android.systemui.statusbar.policy.HeadsUpUtil;
+import com.android.systemui.statusbar.policy.KeyguardMonitor;
+import com.android.systemui.statusbar.policy.PreviewInflater;
+
+public class StatusBarNotificationPresenter implements NotificationPresenter {
+
+    private final LockscreenGestureLogger mLockscreenGestureLogger =
+            Dependency.get(LockscreenGestureLogger.class);
+
+    private static final String TAG = "StatusBarNotificationPresenter";
+
+    private final ShadeController mShadeController = Dependency.get(ShadeController.class);
+    private final ActivityStarter mActivityStarter = Dependency.get(ActivityStarter.class);
+    private final AssistManager mAssistManager = Dependency.get(AssistManager.class);
+    private final KeyguardMonitor mKeyguardMonitor = Dependency.get(KeyguardMonitor.class);
+    private final NotificationViewHierarchyManager mViewHierarchyManager =
+            Dependency.get(NotificationViewHierarchyManager.class);
+    private final NotificationLockscreenUserManager mLockscreenUserManager =
+            Dependency.get(NotificationLockscreenUserManager.class);
+    private final StatusBarStateController mStatusBarStateController =
+            Dependency.get(StatusBarStateController.class);
+    private final NotificationEntryManager mEntryManager =
+            Dependency.get(NotificationEntryManager.class);
+    private final NotificationMediaManager mMediaManager =
+            Dependency.get(NotificationMediaManager.class);
+    private final NotificationRemoteInputManager mRemoteInputManager =
+            Dependency.get(NotificationRemoteInputManager.class);
+    private final NotificationGroupManager mGroupManager =
+            Dependency.get(NotificationGroupManager.class);
+    private final StatusBarRemoteInputCallback mStatusBarRemoteInputCallback =
+            (StatusBarRemoteInputCallback) Dependency.get(Callback.class);
+    protected AmbientPulseManager mAmbientPulseManager = Dependency.get(AmbientPulseManager.class);
+
+    private final NotificationPanelView mNotificationPanel;
+    private final HeadsUpManagerPhone mHeadsUpManager;
+    private final AboveShelfObserver mAboveShelfObserver;
+    private final DozeScrimController mDozeScrimController;
+    private final ScrimController mScrimController;
+    private final Context mContext;
+    private final CommandQueue mCommandQueue;
+
+    private final AccessibilityManager mAccessibilityManager;
+    private final LockPatternUtils mLockPatternUtils;
+    private final KeyguardManager mKeyguardManager;
+    private final ActivityLaunchAnimator mActivityLaunchAnimator;
+    private final int mMaxAllowedKeyguardNotifications;
+    private final IStatusBarService mBarService;
+    private boolean mReinflateNotificationsOnUserSwitched;
+    private final UnlockMethodCache mUnlockMethodCache;
+    private TextView mNotificationPanelDebugText;
+
+    protected boolean mVrMode;
+    private int mMaxKeyguardNotifications;
+    private boolean mIsCollapsingToShowActivityOverLockscreen;
+
+    public StatusBarNotificationPresenter(Context context, NotificationPanelView panel,
+            HeadsUpManagerPhone headsUp, StatusBarWindowView statusBarWindow,
+            ViewGroup stackScroller, DozeScrimController dozeScrimController,
+            ScrimController scrimController,
+            ActivityLaunchAnimator.Callback launchAnimatorCallback) {
+        mContext = context;
+        mNotificationPanel = panel;
+        mHeadsUpManager = headsUp;
+        mCommandQueue = getComponent(context, CommandQueue.class);
+        mAboveShelfObserver = new AboveShelfObserver(stackScroller);
+        mAboveShelfObserver.setListener(statusBarWindow.findViewById(
+                R.id.notification_container_parent));
+        mAccessibilityManager = context.getSystemService(AccessibilityManager.class);
+        mDozeScrimController = dozeScrimController;
+        mScrimController = scrimController;
+        mUnlockMethodCache = UnlockMethodCache.getInstance(mContext);
+        mLockPatternUtils = new LockPatternUtils(context);
+        mKeyguardManager = context.getSystemService(KeyguardManager.class);
+        mMaxAllowedKeyguardNotifications = context.getResources().getInteger(
+                R.integer.keyguard_max_notification_count);
+        mBarService = IStatusBarService.Stub.asInterface(
+                ServiceManager.getService(Context.STATUS_BAR_SERVICE));
+        mActivityLaunchAnimator = new ActivityLaunchAnimator(statusBarWindow,
+                launchAnimatorCallback,
+                mNotificationPanel,
+                (NotificationListContainer) stackScroller);
+
+        if (MULTIUSER_DEBUG) {
+            mNotificationPanelDebugText = mNotificationPanel.findViewById(R.id.header_debug_info);
+            mNotificationPanelDebugText.setVisibility(View.VISIBLE);
+        }
+
+        IVrManager vrManager = IVrManager.Stub.asInterface(ServiceManager.getService(
+                Context.VR_SERVICE));
+        if (vrManager != null) {
+            try {
+                vrManager.registerListener(mVrStateCallbacks);
+            } catch (RemoteException e) {
+                Slog.e(TAG, "Failed to register VR mode state listener: " + e);
+            }
+        }
+        mRemoteInputManager.setUpWithPresenter(this,
+                Dependency.get(NotificationRemoteInputManager.Callback.class),
+                mNotificationPanel.createRemoteInputDelegate());
+        mRemoteInputManager.getController().addCallback(
+                Dependency.get(StatusBarWindowController.class));
+
+        NotificationListContainer notifListContainer = (NotificationListContainer) stackScroller;
+        Dependency.get(InitController.class).addPostInitTask(() -> {
+            mViewHierarchyManager.setUpWithPresenter(this, notifListContainer);
+            mEntryManager.setUpWithPresenter(this, notifListContainer, this, mHeadsUpManager);
+            mLockscreenUserManager.setUpWithPresenter(this);
+            mMediaManager.setUpWithPresenter(this);
+            Dependency.get(NotificationGutsManager.class).setUpWithPresenter(this,
+                    notifListContainer, mCheckSaveListener, mOnSettingsClickListener);
+
+            onUserSwitched(mLockscreenUserManager.getCurrentUserId());
+        });
+    }
+
+    public void onDensityOrFontScaleChanged() {
+        if (!KeyguardUpdateMonitor.getInstance(mContext).isSwitchingUser()) {
+            mEntryManager.updateNotificationsOnDensityOrFontScaleChanged();
+        } else {
+            mReinflateNotificationsOnUserSwitched = true;
+        }
+    }
+
+    @Override
+    public ActivityLaunchAnimator getActivityLaunchAnimator() {
+        return mActivityLaunchAnimator;
+    }
+
+    @Override
+    public boolean isCollapsing() {
+        return mNotificationPanel.isCollapsing()
+                || mActivityLaunchAnimator.isAnimationPending()
+                || mActivityLaunchAnimator.isAnimationRunning();
+    }
+
+    @Override
+    public boolean isCollapsingToShowActivityOverLockscreen() {
+        return mIsCollapsingToShowActivityOverLockscreen;
+    }
+
+    @Override
+    public void onPerformRemoveNotification(StatusBarNotification n) {
+        if (mNotificationPanel.hasPulsingNotifications() &&
+                    !mAmbientPulseManager.hasNotifications()) {
+            // We were showing a pulse for a notification, but no notifications are pulsing anymore.
+            // Finish the pulse.
+            mDozeScrimController.pulseOutNow();
+        }
+    }
+
+    @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 (mScrimController == null) return;
+
+        // Do not modify the notifications during collapse.
+        if (isCollapsing()) {
+            mShadeController.addPostCollapseAction(this::updateNotificationViews);
+            return;
+        }
+
+        mViewHierarchyManager.updateNotificationViews();
+
+        mNotificationPanel.updateNotificationViews();
+    }
+
+    @Override
+    public void onNotificationAdded(Entry shadeEntry) {
+        // Recalculate the position of the sliding windows and the titles.
+        mShadeController.updateAreThereNotifications();
+    }
+
+    @Override
+    public void onNotificationUpdated(StatusBarNotification notification) {
+        mShadeController.updateAreThereNotifications();
+    }
+
+    @Override
+    public void onNotificationRemoved(String key, StatusBarNotification old) {
+        if (SPEW) Log.d(TAG, "removeNotification key=" + key + " old=" + old);
+
+        if (old != null) {
+            if (CLOSE_PANEL_WHEN_EMPTIED && !hasActiveNotifications()
+                    && !mNotificationPanel.isTracking() && !mNotificationPanel.isQsExpanded()) {
+                if (mStatusBarStateController.getState() == StatusBarState.SHADE) {
+                    mCommandQueue.animateCollapsePanels();
+                } else if (mStatusBarStateController.getState() == StatusBarState.SHADE_LOCKED
+                        && !isCollapsing()) {
+                    mShadeController.goToKeyguard();
+                }
+            }
+        }
+        mShadeController.updateAreThereNotifications();
+    }
+
+    public boolean hasActiveNotifications() {
+        return !mEntryManager.getNotificationData().getActiveNotifications().isEmpty();
+    }
+
+    @Override
+    public boolean canHeadsUp(Entry entry, StatusBarNotification sbn) {
+        if (!mShadeController.isDozing()) {
+            return false;
+        }
+
+        if (mShadeController.isOccluded()) {
+            boolean devicePublic = mLockscreenUserManager.
+                    isLockscreenPublicMode(mLockscreenUserManager.getCurrentUserId());
+            boolean userPublic = devicePublic
+                    || mLockscreenUserManager.isLockscreenPublicMode(sbn.getUserId());
+            boolean needsRedaction = mLockscreenUserManager.needsRedaction(entry);
+            if (userPublic && needsRedaction) {
+                return false;
+            }
+        }
+
+        if (!mCommandQueue.panelsEnabled()) {
+            if (DEBUG) {
+                Log.d(TAG, "No heads up: disabled panel : " + sbn.getKey());
+            }
+            return false;
+        }
+
+        if (sbn.getNotification().fullScreenIntent != null) {
+            if (mAccessibilityManager.isTouchExplorationEnabled()) {
+                if (DEBUG) Log.d(TAG, "No heads up: accessible fullscreen: " + sbn.getKey());
+                return false;
+            } else {
+                // we only allow head-up on the lockscreen if it doesn't have a fullscreen intent
+                return !mKeyguardMonitor.isShowing()
+                        || mShadeController.isOccluded();
+            }
+        }
+        return true;
+    }
+
+    @Override
+    public void onUserSwitched(int newUserId) {
+        // Begin old BaseStatusBar.userSwitched
+        mHeadsUpManager.setUser(newUserId);
+        // End old BaseStatusBar.userSwitched
+        if (MULTIUSER_DEBUG) mNotificationPanelDebugText.setText("USER " + newUserId);
+        mCommandQueue.animateCollapsePanels();
+        if (mReinflateNotificationsOnUserSwitched) {
+            mEntryManager.updateNotificationsOnDensityOrFontScaleChanged();
+            mReinflateNotificationsOnUserSwitched = false;
+        }
+        updateNotificationViews();
+        mMediaManager.clearCurrentMediaNotification();
+        mShadeController.setLockscreenUser(newUserId);
+        updateMediaMetaData(true, false);
+    }
+
+    @Override
+    public void onBindRow(Entry entry, PackageManager pmUser,
+            StatusBarNotification sbn, ExpandableNotificationRow row) {
+        row.setAboveShelfChangedListener(mAboveShelfObserver);
+        row.setSecureStateProvider(mUnlockMethodCache::canSkipBouncer);
+    }
+
+    @Override
+    public boolean isPresenterFullyCollapsed() {
+        return mNotificationPanel.isFullyCollapsed();
+    }
+
+    @Override
+    public void onActivated(ActivatableNotificationView view) {
+        onActivated();
+        if (view != null) mNotificationPanel.setActivatedChild(view);
+    }
+
+    public void onActivated() {
+        mLockscreenGestureLogger.write(
+                MetricsEvent.ACTION_LS_NOTE,
+                0 /* lengthDp - N/A */, 0 /* velocityDp - N/A */);
+        mNotificationPanel.showTransientIndication(R.string.notification_tap_again);
+        ActivatableNotificationView previousView = mNotificationPanel.getActivatedChild();
+        if (previousView != null) {
+            previousView.makeInactive(true /* animate */);
+        }
+    }
+
+    @Override
+    public void onActivationReset(ActivatableNotificationView view) {
+        if (view == mNotificationPanel.getActivatedChild()) {
+            mNotificationPanel.setActivatedChild(null);
+            mShadeController.onActivationReset();
+        }
+    }
+
+    @Override
+    public void updateMediaMetaData(boolean metaDataChanged, boolean allowEnterAnimation) {
+        mMediaManager.updateMediaMetaData(metaDataChanged, allowEnterAnimation);
+    }
+
+    @Override
+    public void onNotificationClicked(StatusBarNotification sbn, ExpandableNotificationRow row) {
+        RemoteInputController controller = mRemoteInputManager.getController();
+        if (controller.isRemoteInputActive(row.getEntry())
+                && !TextUtils.isEmpty(row.getActiveRemoteInputText())) {
+            // We have an active remote input typed and the user clicked on the notification.
+            // this was probably unintentional, so we're closing the edit text instead.
+            controller.closeRemoteInputs();
+            return;
+        }
+        Notification notification = sbn.getNotification();
+        final PendingIntent intent = notification.contentIntent != null
+                ? notification.contentIntent
+                : notification.fullScreenIntent;
+        final String notificationKey = sbn.getKey();
+
+        boolean isActivityIntent = intent.isActivity();
+        final boolean afterKeyguardGone = isActivityIntent
+                && PreviewInflater.wouldLaunchResolverActivity(mContext, intent.getIntent(),
+                mLockscreenUserManager.getCurrentUserId());
+        final boolean wasOccluded = mShadeController.isOccluded();
+        boolean showOverLockscreen = mKeyguardMonitor.isShowing()
+                && PreviewInflater.wouldShowOverLockscreen(mContext,
+                intent.getIntent(),
+                mLockscreenUserManager.getCurrentUserId());
+        OnDismissAction postKeyguardAction = () -> {
+            // TODO: Some of this code may be able to move to NotificationEntryManager.
+            if (mHeadsUpManager != null && mHeadsUpManager.isAlerting(notificationKey)) {
+                // Release the HUN notification to the shade.
+
+                if (isPresenterFullyCollapsed()) {
+                    HeadsUpUtil.setIsClickedHeadsUpNotification(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.removeNotification(sbn.getKey(),
+                        true /* releaseImmediately */);
+            }
+            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) {
+                }
+                int launchResult = ActivityManager.START_CANCELED;
+                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 (isActivityIntent) {
+                        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 (mStatusBarRemoteInputCallback.startWorkChallengeIfNecessary(userId,
+                                    intent.getIntentSender(), notificationKey)) {
+                                // Show work challenge, do not run PendingIntent and
+                                // remove notification
+                                collapseOnMainThread();
+                                return;
+                            }
+                        }
+                    }
+                    Intent fillInIntent = null;
+                    Entry entry = row.getEntry();
+                    CharSequence remoteInputText = null;
+                    if (!TextUtils.isEmpty(entry.remoteInputText)) {
+                        remoteInputText = entry.remoteInputText;
+                    }
+                    if (!TextUtils.isEmpty(remoteInputText)
+                            && !controller.isSpinning(entry.key)) {
+                        fillInIntent = new Intent().putExtra(Notification.EXTRA_REMOTE_INPUT_DRAFT,
+                                remoteInputText.toString());
+                    }
+                    RemoteAnimationAdapter adapter = mActivityLaunchAnimator.getLaunchAnimation(
+                            row, wasOccluded);
+                    try {
+                        if (adapter != null) {
+                            ActivityTaskManager.getService()
+                                    .registerRemoteAnimationForNextActivityStart(
+                                            intent.getCreatorPackage(), adapter);
+                        }
+                        launchResult = intent.sendAndReturnResult(mContext, 0, fillInIntent, null,
+                                null, null, getActivityOptions(adapter));
+                        mActivityLaunchAnimator.setLaunchResult(launchResult, isActivityIntent);
+                    } catch (RemoteException | 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 (isActivityIntent) {
+                        mAssistManager.hideAssist();
+                    }
+                }
+                if (shouldCollapse()) {
+                    collapseOnMainThread();
+                }
+
+                final int count =
+                        mEntryManager.getNotificationData().getActiveNotifications().size();
+                final int rank = mEntryManager.getNotificationData().getRank(notificationKey);
+                final NotificationVisibility nv = NotificationVisibility.obtain(notificationKey,
+                        rank, count, true);
+                try {
+                    mBarService.onNotificationClick(notificationKey, nv);
+                } catch (RemoteException ex) {
+                    // system process is dead if we're here.
+                }
+                if (parentToCancelFinal != null) {
+                    removeNotification(parentToCancelFinal);
+                }
+                if (shouldAutoCancel(sbn)
+                        || mRemoteInputManager.isNotificationKeptForRemoteInputHistory(
+                                notificationKey)) {
+                    // Automatically remove all notifications that we may have kept around longer
+                    removeNotification(sbn);
+                }
+                mIsCollapsingToShowActivityOverLockscreen = false;
+            };
+
+            if (showOverLockscreen) {
+                mShadeController.addPostCollapseAction(runnable);
+                mShadeController.collapsePanel(true /* animate */);
+            } else if (mKeyguardMonitor.isShowing()
+                    && mShadeController.isOccluded()) {
+                mShadeController.addAfterKeyguardGoneRunnable(runnable);
+                mShadeController.collapsePanel();
+            } else {
+                new Thread(runnable).start();
+            }
+
+            return !mNotificationPanel.isFullyCollapsed();
+        };
+        if (showOverLockscreen) {
+            mIsCollapsingToShowActivityOverLockscreen = true;
+            postKeyguardAction.onDismiss();
+        } else {
+            mActivityStarter.dismissKeyguardThenExecute(
+                    postKeyguardAction, null /* cancel */, afterKeyguardGone);
+        }
+    }
+
+    private void removeNotification(StatusBarNotification notification) {
+        // We have to post it to the UI thread for synchronization
+        Dependency.get(MAIN_HANDLER).post(() -> {
+            Runnable removeRunnable =
+                    () -> mEntryManager.performRemoveNotification(notification);
+            if (isCollapsing()) {
+                // To avoid lags we're only performing the remove
+                // after the shade was collapsed
+                mShadeController.addPostCollapseAction(removeRunnable);
+            } else {
+                removeRunnable.run();
+            }
+        });
+    }
+
+    @Override
+    public void startNotificationGutsIntent(final Intent intent, final int appUid,
+            ExpandableNotificationRow row) {
+        mActivityStarter.dismissKeyguardThenExecute(() -> {
+            AsyncTask.execute(() -> {
+                int launchResult = TaskStackBuilder.create(mContext)
+                        .addNextIntentWithParentStack(intent)
+                        .startActivities(getActivityOptions(
+                                mActivityLaunchAnimator.getLaunchAnimation(
+                                        row, mShadeController.isOccluded())),
+                                new UserHandle(UserHandle.getUserId(appUid)));
+                mActivityLaunchAnimator.setLaunchResult(launchResult, true /* isActivityIntent */);
+                if (shouldCollapse()) {
+                    // Putting it back on the main thread, since we're touching views
+                    Dependency.get(MAIN_HANDLER).post(() -> mCommandQueue.animateCollapsePanels(
+                            CommandQueue.FLAG_EXCLUDE_RECENTS_PANEL, true /* force */));
+                }
+            });
+            return true;
+        }, null, false /* afterKeyguardGone */);
+    }
+
+    @Override
+    public int getMaxNotificationsWhileLocked(boolean recompute) {
+        if (recompute) {
+            mMaxKeyguardNotifications = Math.max(1,
+                    mNotificationPanel.computeMaxKeyguardNotifications(
+                            mMaxAllowedKeyguardNotifications));
+            return mMaxKeyguardNotifications;
+        }
+        return mMaxKeyguardNotifications;
+    }
+
+    @Override
+    public void onUpdateRowStates() {
+        mNotificationPanel.onUpdateRowStates();
+    }
+
+    @Override
+    public void onExpandClicked(Entry clickedEntry, boolean nowExpanded) {
+        mHeadsUpManager.setExpanded(clickedEntry, nowExpanded);
+        if (mStatusBarStateController.getState() == StatusBarState.KEYGUARD && nowExpanded) {
+            mShadeController.goToLockedShade(clickedEntry.row);
+        }
+    }
+
+    @Override
+    public boolean isDeviceInVrMode() {
+        return mVrMode;
+    }
+
+    @Override
+    public boolean isPresenterLocked() {
+        return mStatusBarStateController.getState() == StatusBarState.KEYGUARD;
+    }
+
+    private void collapseOnMainThread() {
+        if (Looper.getMainLooper().isCurrentThread()) {
+            mShadeController.collapsePanel();
+        } else {
+            Dependency.get(MAIN_HANDLER).post(mShadeController::collapsePanel);
+        }
+    }
+
+    private boolean shouldCollapse() {
+        return mStatusBarStateController.getState() != StatusBarState.SHADE
+                || !mActivityLaunchAnimator.isAnimationPending();
+    }
+
+    private void onLockedNotificationImportanceChange(OnDismissAction dismissAction) {
+        mStatusBarStateController.setLeaveOpenOnKeyguardHide(true);
+        mActivityStarter.dismissKeyguardThenExecute(dismissAction, null,
+                true /* afterKeyguardGone */);
+    }
+
+    private static 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;
+    }
+
+    private final IVrStateCallbacks mVrStateCallbacks = new IVrStateCallbacks.Stub() {
+        @Override
+        public void onVrStateChanged(boolean enabled) {
+            mVrMode = enabled;
+        }
+    };
+
+    private final CheckSaveListener mCheckSaveListener = new CheckSaveListener() {
+        @Override
+        public void checkSave(Runnable saveImportance, StatusBarNotification sbn) {
+            int state = mStatusBarStateController.getState();
+            // If the user has security enabled, show challenge if the setting is changed.
+            if (mLockscreenUserManager.isLockscreenPublicMode(sbn.getUser().getIdentifier())
+                    && mKeyguardManager.isKeyguardLocked()) {
+                onLockedNotificationImportanceChange(() -> {
+                    saveImportance.run();
+                    return true;
+                });
+            } else {
+                saveImportance.run();
+            }
+        }
+    };
+
+    private final OnSettingsClickListener mOnSettingsClickListener = new OnSettingsClickListener() {
+        @Override
+        public void onSettingsClick(String key) {
+            try {
+                mBarService.onNotificationSettingsViewed(key);
+            } catch (RemoteException e) {
+                // if we're here we're dead
+            }
+        }
+    };
+}
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/phone/StatusBarRemoteInputCallback.java b/packages/SystemUI/src/com/android/systemui/statusbar/phone/StatusBarRemoteInputCallback.java
new file mode 100644
index 0000000..06f9658
--- /dev/null
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/StatusBarRemoteInputCallback.java
@@ -0,0 +1,252 @@
+/*
+ * Copyright (C) 2018 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file
+ * except in compliance with the License. You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software distributed under the
+ * License is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied. See the License for the specific language governing
+ * permissions and limitations under the License.
+ */
+
+package com.android.systemui.statusbar.phone;
+
+import static android.content.Intent.ACTION_DEVICE_LOCKED_CHANGED;
+
+import static com.android.systemui.SysUiServiceProvider.getComponent;
+import static com.android.systemui.statusbar.NotificationLockscreenUserManager
+        .NOTIFICATION_UNLOCKED_BY_WORK_CHALLENGE_ACTION;
+
+import android.app.ActivityManager;
+import android.app.KeyguardManager;
+import android.app.PendingIntent;
+import android.app.StatusBarManager;
+import android.content.BroadcastReceiver;
+import android.content.Context;
+import android.content.Intent;
+import android.content.IntentFilter;
+import android.content.IntentSender;
+import android.os.RemoteException;
+import android.os.UserHandle;
+import android.view.View;
+import android.view.ViewParent;
+import android.view.ViewTreeObserver;
+
+import com.android.systemui.Dependency;
+import com.android.systemui.plugins.ActivityStarter;
+import com.android.systemui.statusbar.CommandQueue;
+import com.android.systemui.statusbar.CommandQueue.Callbacks;
+import com.android.systemui.statusbar.NotificationLockscreenUserManager;
+import com.android.systemui.statusbar.NotificationRemoteInputManager;
+import com.android.systemui.statusbar.NotificationRemoteInputManager.Callback;
+import com.android.systemui.statusbar.StatusBarState;
+import com.android.systemui.statusbar.StatusBarStateController;
+import com.android.systemui.statusbar.notification.NotificationEntryManager;
+import com.android.systemui.statusbar.notification.row.ExpandableNotificationRow;
+import com.android.systemui.statusbar.notification.stack.NotificationStackScrollLayout;
+import com.android.systemui.statusbar.policy.KeyguardMonitor;
+import com.android.systemui.statusbar.policy.PreviewInflater;
+
+public class StatusBarRemoteInputCallback implements Callback, Callbacks {
+
+    private final KeyguardMonitor mKeyguardMonitor = Dependency.get(KeyguardMonitor.class);
+    private final StatusBarStateController mStatusBarStateController
+            = Dependency.get(StatusBarStateController.class);
+    private final NotificationLockscreenUserManager mLockscreenUserManager
+            = Dependency.get(NotificationLockscreenUserManager.class);
+    private final ActivityStarter mActivityStarter = Dependency.get(ActivityStarter.class);
+    private final Context mContext;
+    private View mPendingWorkRemoteInputView;
+    private final StatusBarStateController.StateListener mStateListener = this::setStatusBarState;
+    private View mPendingRemoteInputView;
+    private final ShadeController mShadeController = Dependency.get(ShadeController.class);
+    private KeyguardManager mKeyguardManager;
+    private final CommandQueue mCommandQueue;
+    private int mDisabled2;
+    protected BroadcastReceiver mChallengeReceiver = new ChallengeReceiver();
+
+    public StatusBarRemoteInputCallback(Context context) {
+        mContext = context;
+        mContext.registerReceiverAsUser(mChallengeReceiver, UserHandle.ALL,
+                new IntentFilter(ACTION_DEVICE_LOCKED_CHANGED), null, null);
+        mStatusBarStateController.addListener(mStateListener);
+        mKeyguardManager = context.getSystemService(KeyguardManager.class);
+        mCommandQueue = getComponent(context, CommandQueue.class);
+        mCommandQueue.addCallbacks(this);
+    }
+
+    private void setStatusBarState(int state) {
+        if (state == StatusBarState.SHADE && mStatusBarStateController.leaveOpenOnKeyguardHide()) {
+            if (!mStatusBarStateController.isKeyguardRequested()) {
+                if (mPendingRemoteInputView != null
+                        && mPendingRemoteInputView.isAttachedToWindow()) {
+                    mPendingRemoteInputView.post(mPendingRemoteInputView::callOnClick);
+                }
+                mPendingRemoteInputView = null;
+            }
+        }
+    }
+
+    @Override
+    public void onLockedRemoteInput(ExpandableNotificationRow row, View clicked) {
+        mStatusBarStateController.setLeaveOpenOnKeyguardHide(true);
+        mShadeController.showBouncer(true /* scrimmed */);
+        mPendingRemoteInputView = clicked;
+    }
+
+    protected void onWorkChallengeChanged() {
+        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;
+                if (pendingWorkRemoteInputView == null) {
+                    return;
+                }
+
+                // Climb up the hierarchy until we get to the container for this row.
+                ViewParent p = pendingWorkRemoteInputView.getParent();
+                while (!(p instanceof ExpandableNotificationRow)) {
+                    if (p == null) {
+                        return;
+                    }
+                    p = p.getParent();
+                }
+
+                final ExpandableNotificationRow row = (ExpandableNotificationRow) p;
+                ViewParent viewParent = row.getParent();
+                if (viewParent instanceof NotificationStackScrollLayout) {
+                    final NotificationStackScrollLayout scrollLayout =
+                            (NotificationStackScrollLayout) viewParent;
+                    row.makeActionsVisibile();
+                    row.post(() -> {
+                        final Runnable finishScrollingCallback = () -> {
+                            mPendingWorkRemoteInputView.callOnClick();
+                            mPendingWorkRemoteInputView = null;
+                            scrollLayout.setFinishScrollingCallback(null);
+                        };
+                        if (scrollLayout.scrollTo(row)) {
+                            // It scrolls! So call it when it's finished.
+                            scrollLayout.setFinishScrollingCallback(finishScrollingCallback);
+                        } else {
+                            // It does not scroll, so call it now!
+                            finishScrollingCallback.run();
+                        }
+                    });
+                }
+            };
+            mShadeController.postOnShadeExpanded(clickPendingViewRunnable);
+            mShadeController.instantExpandNotificationsPanel();
+        }
+    }
+
+    @Override
+    public void onMakeExpandedVisibleForRemoteInput(ExpandableNotificationRow row,
+            View clickedView) {
+        if (mKeyguardMonitor.isShowing()) {
+            onLockedRemoteInput(row, clickedView);
+        } else {
+            row.setUserExpanded(true);
+            row.getPrivateLayout().setOnExpandedVisibleListener(clickedView::performClick);
+        }
+    }
+
+    @Override
+    public void onLockedWorkRemoteInput(int userId, ExpandableNotificationRow row,
+            View clicked) {
+        // Collapse notification and show work challenge
+        mCommandQueue.animateCollapsePanels();
+        startWorkChallengeIfNecessary(userId, null, null);
+        // Add pending remote input view after starting work challenge, as starting work challenge
+        // will clear all previous pending review view
+        mPendingWorkRemoteInputView = clicked;
+    }
+
+    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
+        // it's called by other code
+        mPendingWorkRemoteInputView = null;
+        // Begin old BaseStatusBar.startWorkChallengeIfNecessary.
+        final Intent newIntent = mKeyguardManager.createConfirmDeviceCredentialIntent(null,
+                null, userId);
+        if (newIntent == null) {
+            return false;
+        }
+        final Intent callBackIntent = new Intent(NOTIFICATION_UNLOCKED_BY_WORK_CHALLENGE_ACTION);
+        callBackIntent.putExtra(Intent.EXTRA_INTENT, intendSender);
+        callBackIntent.putExtra(Intent.EXTRA_INDEX, notificationKey);
+        callBackIntent.setPackage(mContext.getPackageName());
+
+        PendingIntent callBackPendingIntent = PendingIntent.getBroadcast(
+                mContext,
+                0,
+                callBackIntent,
+                PendingIntent.FLAG_CANCEL_CURRENT |
+                        PendingIntent.FLAG_ONE_SHOT |
+                        PendingIntent.FLAG_IMMUTABLE);
+        newIntent.putExtra(
+                Intent.EXTRA_INTENT,
+                callBackPendingIntent.getIntentSender());
+        try {
+            ActivityManager.getService().startConfirmDeviceCredentialIntent(newIntent,
+                    null /*options*/);
+        } catch (RemoteException ex) {
+            // ignore
+        }
+        return true;
+        // End old BaseStatusBar.startWorkChallengeIfNecessary.
+    }
+
+    @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());
+            mActivityStarter.dismissKeyguardThenExecute(() -> {
+                try {
+                    ActivityManager.getService().resumeAppSwitches();
+                } catch (RemoteException e) {
+                }
+
+                boolean handled = defaultHandler.handleClick();
+
+                // close the shade if it was open and maybe wait for activity start.
+                return handled && mShadeController.closeShadeIfOpen();
+            }, null, afterKeyguardGone);
+            return true;
+        } else {
+            return defaultHandler.handleClick();
+        }
+    }
+
+    @Override
+    public void disable(int state1, int state2, boolean animate) {
+        mDisabled2 = state2;
+    }
+
+    protected class ChallengeReceiver extends 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 (Intent.ACTION_DEVICE_LOCKED_CHANGED.equals(action)) {
+                if (userId != mLockscreenUserManager.getCurrentUserId()
+                        && mLockscreenUserManager.isCurrentProfile(userId)) {
+                    onWorkChallengeChanged();
+                }
+            }
+        }
+    };
+}
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/policy/KeyguardMonitor.java b/packages/SystemUI/src/com/android/systemui/statusbar/policy/KeyguardMonitor.java
index 7b42dd4..aba2377 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/policy/KeyguardMonitor.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/policy/KeyguardMonitor.java
@@ -24,6 +24,7 @@
     boolean isOccluded();
     boolean isKeyguardFadingAway();
     boolean isKeyguardGoingAway();
+    boolean isLaunchTransitionFadingAway();
     long getKeyguardFadingAwayDuration();
     long getKeyguardFadingAwayDelay();
     long calculateGoingToFullShadeDelay();
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/policy/KeyguardMonitorImpl.java b/packages/SystemUI/src/com/android/systemui/statusbar/policy/KeyguardMonitorImpl.java
index 10cb09b..5eb0fb7 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/policy/KeyguardMonitorImpl.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/policy/KeyguardMonitorImpl.java
@@ -28,7 +28,7 @@
 public class KeyguardMonitorImpl extends KeyguardUpdateMonitorCallback
         implements KeyguardMonitor {
 
-    private final ArrayList<Callback> mCallbacks = new ArrayList<Callback>();
+    private final ArrayList<Callback> mCallbacks = new ArrayList<>();
 
     private final Context mContext;
     private final CurrentUserTracker mUserTracker;
@@ -45,6 +45,7 @@
     private long mKeyguardFadingAwayDelay;
     private long mKeyguardFadingAwayDuration;
     private boolean mKeyguardGoingAway;
+    private boolean mLaunchTransitionFadingAway;
 
     public KeyguardMonitorImpl(Context context) {
         mContext = context;
@@ -123,7 +124,7 @@
 
     private void notifyKeyguardChanged() {
         // Copy the list to allow removal during callback.
-        new ArrayList<Callback>(mCallbacks).forEach(Callback::onKeyguardShowingChanged);
+        new ArrayList<>(mCallbacks).forEach(Callback::onKeyguardShowingChanged);
     }
 
     public void notifyKeyguardFadingAway(long delay, long fadeoutDuration) {
@@ -165,4 +166,13 @@
     public void notifyKeyguardGoingAway(boolean keyguardGoingAway) {
         mKeyguardGoingAway = keyguardGoingAway;
     }
+
+    public void setLaunchTransitionFadingAway(boolean fadingAway) {
+        mLaunchTransitionFadingAway = fadingAway;
+    }
+
+    @Override
+    public boolean isLaunchTransitionFadingAway() {
+        return mLaunchTransitionFadingAway;
+    }
 }
\ No newline at end of file
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/policy/SmartReplyView.java b/packages/SystemUI/src/com/android/systemui/statusbar/policy/SmartReplyView.java
index dd03162..aa4782f 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/policy/SmartReplyView.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/policy/SmartReplyView.java
@@ -28,7 +28,7 @@
 
 import com.android.internal.annotations.VisibleForTesting;
 import com.android.internal.util.ContrastColorUtil;
-import com.android.keyguard.KeyguardHostView.OnDismissAction;
+import com.android.systemui.plugins.ActivityStarter.OnDismissAction;
 import com.android.systemui.Dependency;
 import com.android.systemui.R;
 import com.android.systemui.statusbar.notification.NotificationData;