Extracts heads-up/pulsing logic from NotificationEntryManager.

This change introduces the NotificationInterruptionStateProvider
component, which contains all the logic around whether a notification
should heads-up or pulse previously contained in
NotificationEntryManager.

We also introduce the NotificationFilter component which extracts logic
about when to filter notifications from NotificationData, in order to
break a circular dependency that would otherwise be introduced.  As part
of this, some additional fields from the notification ranking map are
denormalized on to the NotificationData.Entry object.

Test: atest SystemUITests, manually
Change-Id: Ic61edca966a3c3e0b01f1a6a9e7ce79c8701da4e
diff --git a/packages/SystemUI/src/com/android/systemui/Dependency.java b/packages/SystemUI/src/com/android/systemui/Dependency.java
index 445d156..6b4d07a 100644
--- a/packages/SystemUI/src/com/android/systemui/Dependency.java
+++ b/packages/SystemUI/src/com/android/systemui/Dependency.java
@@ -56,6 +56,8 @@
 import com.android.systemui.statusbar.VibratorHelper;
 import com.android.systemui.statusbar.notification.NotificationData.KeyguardEnvironment;
 import com.android.systemui.statusbar.notification.NotificationEntryManager;
+import com.android.systemui.statusbar.notification.NotificationFilter;
+import com.android.systemui.statusbar.notification.NotificationInterruptionStateProvider;
 import com.android.systemui.statusbar.notification.NotificationRowBinder;
 import com.android.systemui.statusbar.notification.VisualStabilityManager;
 import com.android.systemui.statusbar.notification.logging.NotificationLogger;
@@ -256,6 +258,8 @@
     @Inject Lazy<NotificationLogger> mNotificationLogger;
     @Inject Lazy<NotificationViewHierarchyManager> mNotificationViewHierarchyManager;
     @Inject Lazy<NotificationRowBinder> mNotificationRowBinder;
+    @Inject Lazy<NotificationFilter> mNotificationFilter;
+    @Inject Lazy<NotificationInterruptionStateProvider> mNotificationInterruptionStateProvider;
     @Inject Lazy<KeyguardDismissUtil> mKeyguardDismissUtil;
     @Inject Lazy<SmartReplyController> mSmartReplyController;
     @Inject Lazy<RemoteInputQuickSettingsDisabler> mRemoteInputQuickSettingsDisabler;
@@ -425,6 +429,9 @@
         mProviders.put(NotificationViewHierarchyManager.class,
                 mNotificationViewHierarchyManager::get);
         mProviders.put(NotificationRowBinder.class, mNotificationRowBinder::get);
+        mProviders.put(NotificationFilter.class, mNotificationFilter::get);
+        mProviders.put(NotificationInterruptionStateProvider.class,
+                mNotificationInterruptionStateProvider::get);
         mProviders.put(KeyguardDismissUtil.class, mKeyguardDismissUtil::get);
         mProviders.put(SmartReplyController.class, mSmartReplyController::get);
         mProviders.put(RemoteInputQuickSettingsDisabler.class,
diff --git a/packages/SystemUI/src/com/android/systemui/SystemUIFactory.java b/packages/SystemUI/src/com/android/systemui/SystemUIFactory.java
index 7689dfc..384a14c 100644
--- a/packages/SystemUI/src/com/android/systemui/SystemUIFactory.java
+++ b/packages/SystemUI/src/com/android/systemui/SystemUIFactory.java
@@ -42,6 +42,7 @@
 import com.android.systemui.statusbar.ScrimView;
 import com.android.systemui.statusbar.notification.NotificationData;
 import com.android.systemui.statusbar.notification.NotificationEntryManager;
+import com.android.systemui.statusbar.notification.NotificationInterruptionStateProvider;
 import com.android.systemui.statusbar.phone.DozeParameters;
 import com.android.systemui.statusbar.phone.KeyguardBouncer;
 import com.android.systemui.statusbar.phone.KeyguardEnvironmentImpl;
@@ -198,6 +199,13 @@
         return new NotificationListener(context);
     }
 
+    @Singleton
+    @Provides
+    public NotificationInterruptionStateProvider provideNotificationInterruptionStateProvider(
+            Context context) {
+        return new NotificationInterruptionStateProvider(context);
+    }
+
     @Module
     protected static class ContextHolder {
         private Context mContext;
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/NotificationViewHierarchyManager.java b/packages/SystemUI/src/com/android/systemui/statusbar/NotificationViewHierarchyManager.java
index 2524747..017cda7 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/NotificationViewHierarchyManager.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/NotificationViewHierarchyManager.java
@@ -133,7 +133,6 @@
         mAlwaysExpandNonGroupedNotification =
                 res.getBoolean(R.bool.config_alwaysExpandNonGroupedNotifications);
         mStatusBarStateListener = new StatusBarStateListener(mBubbleController);
-        mEntryManager.setStatusBarStateListener(mStatusBarStateListener);
         Dependency.get(StatusBarStateController.class).addCallback(mStatusBarStateListener);
     }
 
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 ef7e0fe..433a994 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/NotificationData.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/NotificationData.java
@@ -27,20 +27,15 @@
 import static android.app.NotificationManager.Policy.SUPPRESSED_EFFECT_PEEK;
 import static android.app.NotificationManager.Policy.SUPPRESSED_EFFECT_STATUS_BAR;
 
-import android.Manifest;
 import android.annotation.NonNull;
-import android.app.AppGlobals;
 import android.app.Notification;
 import android.app.NotificationChannel;
 import android.app.NotificationManager;
 import android.app.Person;
 import android.content.Context;
-import android.content.pm.IPackageManager;
-import android.content.pm.PackageManager;
 import android.graphics.drawable.Icon;
 import android.os.Bundle;
 import android.os.Parcelable;
-import android.os.RemoteException;
 import android.os.SystemClock;
 import android.service.notification.NotificationListenerService.Ranking;
 import android.service.notification.NotificationListenerService.RankingMap;
@@ -58,17 +53,13 @@
 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.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.notification.row.NotificationGuts;
 import com.android.systemui.statusbar.notification.row.NotificationInflater.InflationFlag;
 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 java.io.PrintWriter;
@@ -83,14 +74,13 @@
  */
 public class NotificationData {
 
+    private final NotificationFilter mNotificationFilter = Dependency.get(NotificationFilter.class);
+
     /**
      * These dependencies are late init-ed
      */
     private KeyguardEnvironment mEnvironment;
-    private ShadeController mShadeController;
     private NotificationMediaManager mMediaManager;
-    private ForegroundServiceController mFsc;
-    private NotificationLockscreenUserManager mUserManager;
 
     private HeadsUpManager mHeadsUpManager;
 
@@ -120,6 +110,9 @@
         @NonNull
         public List<Notification.Action> systemGeneratedSmartActions = Collections.emptyList();
         public CharSequence[] smartReplies = new CharSequence[0];
+        @VisibleForTesting
+        public int suppressedVisualEffects;
+        public boolean suspended;
 
         private Entry parent; // our parent (if we're in a group)
         private ArrayList<Entry> children = new ArrayList<Entry>();
@@ -183,6 +176,8 @@
             smartReplies = ranking.getSmartReplies() == null
                     ? new CharSequence[0]
                     : ranking.getSmartReplies().toArray(new CharSequence[0]);
+            suppressedVisualEffects = ranking.getSuppressedVisualEffects();
+            suspended = ranking.isSuspended();
         }
 
         public void setInterruption() {
@@ -625,6 +620,71 @@
             if (row == null) return true;
             return row.canViewBeDismissed();
         }
+
+        boolean isExemptFromDndVisualSuppression() {
+            if (isNotificationBlockedByPolicy(notification.getNotification())) {
+                return false;
+            }
+
+            if ((notification.getNotification().flags
+                    & Notification.FLAG_FOREGROUND_SERVICE) != 0) {
+                return true;
+            }
+            if (notification.getNotification().isMediaNotification()) {
+                return true;
+            }
+            if (mIsSystemNotification != null && mIsSystemNotification) {
+                return true;
+            }
+            return false;
+        }
+
+        private boolean shouldSuppressVisualEffect(int effect) {
+            if (isExemptFromDndVisualSuppression()) {
+                return false;
+            }
+            return (suppressedVisualEffects & effect) != 0;
+        }
+
+        /**
+         * Returns whether {@link NotificationManager.Policy#SUPPRESSED_EFFECT_FULL_SCREEN_INTENT}
+         * is set for this entry.
+         */
+        public boolean shouldSuppressFullScreenIntent() {
+            return shouldSuppressVisualEffect(SUPPRESSED_EFFECT_FULL_SCREEN_INTENT);
+        }
+
+        /**
+         * Returns whether {@link NotificationManager.Policy#SUPPRESSED_EFFECT_PEEK}
+         * is set for this entry.
+         */
+        public boolean shouldSuppressPeek() {
+            return shouldSuppressVisualEffect(SUPPRESSED_EFFECT_PEEK);
+        }
+
+        /**
+         * Returns whether {@link NotificationManager.Policy#SUPPRESSED_EFFECT_STATUS_BAR}
+         * is set for this entry.
+         */
+        public boolean shouldSuppressStatusBar() {
+            return shouldSuppressVisualEffect(SUPPRESSED_EFFECT_STATUS_BAR);
+        }
+
+        /**
+         * Returns whether {@link NotificationManager.Policy#SUPPRESSED_EFFECT_AMBIENT}
+         * is set for this entry.
+         */
+        public boolean shouldSuppressAmbient() {
+            return shouldSuppressVisualEffect(SUPPRESSED_EFFECT_AMBIENT);
+        }
+
+        /**
+         * Returns whether {@link NotificationManager.Policy#SUPPRESSED_EFFECT_NOTIFICATION_LIST}
+         * is set for this entry.
+         */
+        public boolean shouldSuppressNotificationList() {
+            return shouldSuppressVisualEffect(SUPPRESSED_EFFECT_NOTIFICATION_LIST);
+        }
     }
 
     private final ArrayMap<String, Entry> mEntries = new ArrayMap<>();
@@ -706,13 +766,6 @@
         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);
@@ -720,20 +773,6 @@
         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 KeyguardEnvironment}
      *
@@ -778,7 +817,7 @@
     }
 
     public Entry remove(String key, RankingMap ranking) {
-        Entry removed = null;
+        Entry removed;
         synchronized (mEntries) {
             removed = mEntries.remove(key);
         }
@@ -849,62 +888,12 @@
         return Ranking.VISIBILITY_NO_OVERRIDE;
     }
 
-    public boolean shouldSuppressFullScreenIntent(Entry entry) {
-        return shouldSuppressVisualEffect(entry, SUPPRESSED_EFFECT_FULL_SCREEN_INTENT);
-    }
-
-    public boolean shouldSuppressPeek(Entry entry) {
-        return shouldSuppressVisualEffect(entry, SUPPRESSED_EFFECT_PEEK);
-    }
-
-    public boolean shouldSuppressStatusBar(Entry entry) {
-        return shouldSuppressVisualEffect(entry, SUPPRESSED_EFFECT_STATUS_BAR);
-    }
-
-    public boolean shouldSuppressAmbient(Entry entry) {
-        return shouldSuppressVisualEffect(entry, SUPPRESSED_EFFECT_AMBIENT);
-    }
-
-    public boolean shouldSuppressNotificationList(Entry entry) {
-        return shouldSuppressVisualEffect(entry, SUPPRESSED_EFFECT_NOTIFICATION_LIST);
-    }
-
-    private boolean shouldSuppressVisualEffect(Entry entry, int effect) {
-        if (isExemptFromDndVisualSuppression(entry)) {
-            return false;
-        }
-        String key = entry.key;
-        if (mRankingMap != null) {
-            getRanking(key, mTmpRanking);
-            return (mTmpRanking.getSuppressedVisualEffects() & effect) != 0;
-        }
-        return false;
-    }
-
-    protected boolean isExemptFromDndVisualSuppression(Entry entry) {
-        if (isNotificationBlockedByPolicy(entry.notification.getNotification())) {
-            return false;
-        }
-
-        if ((entry.notification.getNotification().flags
-                & Notification.FLAG_FOREGROUND_SERVICE) != 0) {
-            return true;
-        }
-        if (entry.notification.getNotification().isMediaNotification()) {
-            return true;
-        }
-        if (entry.mIsSystemNotification != null && entry.mIsSystemNotification) {
-            return true;
-        }
-        return false;
-    }
-
     /**
      * Categories that are explicitly called out on DND settings screens are always blocked, if
      * DND has flagged them, even if they are foreground or system notifications that might
      * otherwise visually bypass DND.
      */
-    protected boolean isNotificationBlockedByPolicy(Notification n) {
+    private static boolean isNotificationBlockedByPolicy(Notification n) {
         if (isCategory(CATEGORY_CALL, n)
                 || isCategory(CATEGORY_MESSAGE, n)
                 || isCategory(CATEGORY_ALARM, n)
@@ -915,7 +904,7 @@
         return false;
     }
 
-    private boolean isCategory(String category, Notification n) {
+    private static boolean isCategory(String category, Notification n) {
         return Objects.equals(n.category, category);
     }
 
@@ -1013,7 +1002,7 @@
             for (int i = 0; i < N; i++) {
                 Entry entry = mEntries.valueAt(i);
 
-                if (shouldFilterOut(entry)) {
+                if (mNotificationFilter.shouldFilterOut(entry)) {
                     continue;
                 }
 
@@ -1024,87 +1013,6 @@
         Collections.sort(mSortedAndFiltered, mRankingComparator);
     }
 
-    /**
-     * @return true if this notification should NOT be shown right now
-     */
-    public boolean shouldFilterOut(Entry entry) {
-        final StatusBarNotification sbn = entry.notification;
-        if (!(getEnvironment().isDeviceProvisioned() ||
-                showNotificationEvenIfUnprovisioned(sbn))) {
-            return true;
-        }
-
-        if (!getEnvironment().isNotificationForCurrentProfiles(sbn)) {
-            return true;
-        }
-
-        if (getUserManager().isLockscreenPublicMode(sbn.getUserId()) &&
-                (sbn.getNotification().visibility == Notification.VISIBILITY_SECRET
-                        || getUserManager().shouldHideNotifications(sbn.getUserId())
-                        || getUserManager().shouldHideNotifications(sbn.getKey()))) {
-            return true;
-        }
-
-        if (getShadeController().isDozing() && shouldSuppressAmbient(entry)) {
-            return true;
-        }
-
-        if (!getShadeController().isDozing() && shouldSuppressNotificationList(entry)) {
-            return true;
-        }
-
-        if (shouldHide(sbn.getKey())) {
-            return true;
-        }
-
-        if (!StatusBar.ENABLE_CHILD_NOTIFICATIONS
-                && mGroupManager.isChildInGroupWithSummary(sbn)) {
-            return true;
-        }
-
-        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 (getFsc().isSystemAlertNotification(sbn)) {
-            final String[] apps = sbn.getNotification().extras.getStringArray(
-                    Notification.EXTRA_FOREGROUND_APPS);
-            if (apps != null && apps.length >= 1) {
-                if (!getFsc().isSystemAlertWarningNeeded(sbn.getUserId(), apps[0])) {
-                    return true;
-                }
-            }
-        }
-
-        return false;
-    }
-
-    // Q: What kinds of notifications should show during setup?
-    // A: Almost none! Only things coming from packages with permission
-    // android.permission.NOTIFICATION_DURING_SETUP that also have special "kind" tags marking them
-    // as relevant for setup (see below).
-    public static boolean showNotificationEvenIfUnprovisioned(StatusBarNotification sbn) {
-        return showNotificationEvenIfUnprovisioned(AppGlobals.getPackageManager(), sbn);
-    }
-
-    @VisibleForTesting
-    static boolean showNotificationEvenIfUnprovisioned(IPackageManager packageManager,
-            StatusBarNotification sbn) {
-        return checkUidPermission(packageManager, Manifest.permission.NOTIFICATION_DURING_SETUP,
-                sbn.getUid()) == PackageManager.PERMISSION_GRANTED
-                && sbn.getNotification().extras.getBoolean(Notification.EXTRA_ALLOW_DURING_SETUP);
-    }
-
-    private static int checkUidPermission(IPackageManager packageManager, String permission,
-            int uid) {
-        try {
-            return packageManager.checkUidPermission(permission, uid);
-        } catch (RemoteException e) {
-            throw e.rethrowFromSystemServer();
-        }
-    }
-
     public void dump(PrintWriter pw, String indent) {
         int N = mSortedAndFiltered.size();
         pw.print(indent);
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 535ea62..bcbe860 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/NotificationEntryManager.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/NotificationEntryManager.java
@@ -17,7 +17,6 @@
 
 import static com.android.systemui.bubbles.BubbleController.DEBUG_DEMOTE_TO_NOTIF;
 import static com.android.systemui.statusbar.NotificationRemoteInputManager.FORCE_REMOTE_INPUT_HISTORY;
-import static com.android.systemui.statusbar.StatusBarState.SHADE;
 import static com.android.systemui.statusbar.notification.row.NotificationInflater.FLAG_CONTENT_VIEW_AMBIENT;
 import static com.android.systemui.statusbar.notification.row.NotificationInflater.FLAG_CONTENT_VIEW_HEADS_UP;
 
@@ -26,20 +25,16 @@
 import android.app.NotificationManager;
 import android.app.PendingIntent;
 import android.content.Context;
-import android.database.ContentObserver;
-import android.os.Bundle;
 import android.os.Handler;
 import android.os.PowerManager;
 import android.os.RemoteException;
 import android.os.ServiceManager;
 import android.os.UserHandle;
-import android.provider.Settings;
 import android.service.dreams.DreamService;
 import android.service.dreams.IDreamManager;
 import android.service.notification.NotificationListenerService;
 import android.service.notification.NotificationStats;
 import android.service.notification.StatusBarNotification;
-import android.text.TextUtils;
 import android.util.ArrayMap;
 import android.util.ArraySet;
 import android.util.EventLog;
@@ -64,7 +59,6 @@
 import com.android.systemui.statusbar.NotificationRemoteInputManager;
 import com.android.systemui.statusbar.NotificationUiAdjustment;
 import com.android.systemui.statusbar.NotificationUpdateHandler;
-import com.android.systemui.statusbar.NotificationViewHierarchyManager;
 import com.android.systemui.statusbar.notification.NotificationData.KeyguardEnvironment;
 import com.android.systemui.statusbar.notification.row.ExpandableNotificationRow;
 import com.android.systemui.statusbar.notification.row.NotificationGutsManager;
@@ -97,8 +91,6 @@
         BubbleController.BubbleDismissListener {
     private static final String TAG = "NotificationEntryMgr";
     protected static final boolean DEBUG = Log.isLoggable(TAG, Log.DEBUG);
-    private static final boolean ENABLE_HEADS_UP = true;
-    private static final String SETTING_HEADS_UP_TICKER = "ticker_gets_heads_up";
 
     public static final long RECENTLY_ALERTED_THRESHOLD_MS = TimeUnit.SECONDS.toMillis(30);
 
@@ -119,6 +111,8 @@
     private final AmbientPulseManager mAmbientPulseManager =
             Dependency.get(AmbientPulseManager.class);
     private final BubbleController mBubbleController = Dependency.get(BubbleController.class);
+    private final NotificationInterruptionStateProvider mNotificationInterruptionStateProvider =
+            Dependency.get(NotificationInterruptionStateProvider.class);
 
     // Lazily retrieved dependencies
     private NotificationRemoteInputManager mRemoteInputManager;
@@ -138,14 +132,10 @@
     private NotificationListenerService.RankingMap mLatestRankingMap;
     protected HeadsUpManager mHeadsUpManager;
     protected NotificationData mNotificationData;
-    private ContentObserver mHeadsUpObserver;
-    protected boolean mUseHeadsUp = false;
-    private boolean mDisableNotificationAlerts;
     protected NotificationListContainer mListContainer;
     @VisibleForTesting
     final ArrayList<NotificationLifetimeExtender> mNotificationLifetimeExtenders
             = new ArrayList<>();
-    private NotificationViewHierarchyManager.StatusBarStateListener mStatusBarStateListener;
     @Nullable private AlertTransferListener mAlertTransferListener;
 
     private final DeviceProvisionedController.DeviceProvisionedListener
@@ -157,11 +147,6 @@
                 }
             };
 
-    public void setDisableNotificationAlerts(boolean disableNotificationAlerts) {
-        mDisableNotificationAlerts = disableNotificationAlerts;
-        mHeadsUpObserver.onChange(true);
-    }
-
     public void destroy() {
         mDeviceProvisionedController.removeCallback(mDeviceProvisionedListener);
     }
@@ -177,8 +162,6 @@
                 pw.println(entry.notification);
             }
         }
-        pw.print("  mUseHeadsUp=");
-        pw.println(mUseHeadsUp);
     }
 
     public NotificationEntryManager(Context context) {
@@ -245,36 +228,6 @@
         mNotificationData.setHeadsUpManager(mHeadsUpManager);
         mListContainer = listContainer;
 
-        mHeadsUpObserver = new ContentObserver(Dependency.get(Dependency.MAIN_HANDLER)) {
-            @Override
-            public void onChange(boolean selfChange) {
-                boolean wasUsing = mUseHeadsUp;
-                mUseHeadsUp = ENABLE_HEADS_UP && !mDisableNotificationAlerts
-                        && Settings.Global.HEADS_UP_OFF != Settings.Global.getInt(
-                        mContext.getContentResolver(),
-                        Settings.Global.HEADS_UP_NOTIFICATIONS_ENABLED,
-                        Settings.Global.HEADS_UP_OFF);
-                Log.d(TAG, "heads up is " + (mUseHeadsUp ? "enabled" : "disabled"));
-                if (wasUsing != mUseHeadsUp) {
-                    if (!mUseHeadsUp) {
-                        Log.d(TAG,
-                                "dismissing any existing heads up notification on disable event");
-                        mHeadsUpManager.releaseAllImmediately();
-                    }
-                }
-            }
-        };
-
-        if (ENABLE_HEADS_UP) {
-            mContext.getContentResolver().registerContentObserver(
-                    Settings.Global.getUriFor(Settings.Global.HEADS_UP_NOTIFICATIONS_ENABLED),
-                    true,
-                    mHeadsUpObserver);
-            mContext.getContentResolver().registerContentObserver(
-                    Settings.Global.getUriFor(SETTING_HEADS_UP_TICKER), true,
-                    mHeadsUpObserver);
-        }
-
         mNotificationLifetimeExtenders.add(mHeadsUpManager);
         mNotificationLifetimeExtenders.add(mAmbientPulseManager);
         mNotificationLifetimeExtenders.add(mGutsManager);
@@ -285,20 +238,6 @@
         }
 
         mDeviceProvisionedController.addCallback(mDeviceProvisionedListener);
-
-        mHeadsUpObserver.onChange(true); // set up
-
-        getRowBinder().setInterruptionStateProvider(new InterruptionStateProvider() {
-            @Override
-            public boolean shouldHeadsUp(NotificationData.Entry entry) {
-                return NotificationEntryManager.this.shouldHeadsUp(entry);
-            }
-
-            @Override
-            public boolean shouldPulse(NotificationData.Entry entry) {
-                return NotificationEntryManager.this.shouldPulse(entry);
-            }
-        });
     }
 
     public NotificationData getNotificationData() {
@@ -327,7 +266,7 @@
             return true;
         }
 
-        return mNotificationData.shouldSuppressFullScreenIntent(entry);
+        return entry.shouldSuppressFullScreenIntent();
     }
 
     public void performRemoveNotification(StatusBarNotification n) {
@@ -441,7 +380,7 @@
         if ((inflatedFlags & FLAG_CONTENT_VIEW_HEADS_UP) != 0) {
             // Possible for shouldHeadsUp to change between the inflation starting and ending.
             // If it does and we no longer need to heads up, we should free the view.
-            if (shouldHeadsUp(entry)) {
+            if (mNotificationInterruptionStateProvider.shouldHeadsUp(entry)) {
                 mHeadsUpManager.showNotification(entry);
                 // Mark as seen immediately
                 setNotificationShown(entry.notification);
@@ -450,7 +389,7 @@
             }
         }
         if ((inflatedFlags & FLAG_CONTENT_VIEW_AMBIENT) != 0) {
-            if (shouldPulse(entry)) {
+            if (mNotificationInterruptionStateProvider.shouldPulse(entry)) {
                 mAmbientPulseManager.showNotification(entry);
             } else {
                 entry.freeContentViewWhenSafe(FLAG_CONTENT_VIEW_AMBIENT);
@@ -651,7 +590,7 @@
         NotificationListenerService.Ranking ranking = new NotificationListenerService.Ranking();
         rankingMap.getRanking(key, ranking);
         NotificationData.Entry shadeEntry = createNotificationEntry(notification, ranking);
-        boolean isHeadsUped = shouldHeadsUp(shadeEntry);
+        boolean isHeadsUped = mNotificationInterruptionStateProvider.shouldHeadsUp(shadeEntry);
         if (!isHeadsUped && notification.getNotification().fullScreenIntent != null) {
             if (shouldSuppressFullScreenIntent(shadeEntry)) {
                 if (DEBUG) {
@@ -767,9 +706,11 @@
 
         boolean alertAgain = alertAgain(entry, entry.notification.getNotification());
         if (getShadeController().isDozing()) {
-            updateAlertState(entry, shouldPulse(entry), alertAgain, mAmbientPulseManager);
+            updateAlertState(entry, mNotificationInterruptionStateProvider.shouldPulse(entry),
+                    alertAgain, mAmbientPulseManager);
         } else {
-            updateAlertState(entry, shouldHeadsUp(entry), alertAgain, mHeadsUpManager);
+            updateAlertState(entry, mNotificationInterruptionStateProvider.shouldHeadsUp(entry),
+                    alertAgain, mHeadsUpManager);
         }
         updateNotifications();
 
@@ -828,7 +769,7 @@
 
         // By comparing the old and new UI adjustments, reinflate the view accordingly.
         for (NotificationData.Entry entry : entries) {
-            mNotificationRowBinder.onNotificationRankingUpdated(
+            getRowBinder().onNotificationRankingUpdated(
                     entry,
                     oldImportances.get(entry.key),
                     oldAdjustments.get(entry.key),
@@ -851,182 +792,6 @@
         }
     }
 
-    public void setStatusBarStateListener(
-            NotificationViewHierarchyManager.StatusBarStateListener listener) {
-        mStatusBarStateListener  = listener;
-    }
-
-    /**
-     * Whether the notification should peek in from the top and alert the user.
-     *
-     * @param entry the entry to check
-     * @return true if the entry should heads up, false otherwise
-     */
-    @VisibleForTesting(visibility = VisibleForTesting.Visibility.PROTECTED)
-    public boolean shouldHeadsUp(NotificationData.Entry entry) {
-        StatusBarNotification sbn = entry.notification;
-
-        if (getShadeController().isDozing()) {
-            if (DEBUG) {
-                Log.d(TAG, "No heads up: device is dozing: " + sbn.getKey());
-            }
-            return false;
-        }
-
-        // TODO: need to changes this, e.g. should still heads up in expanded shade, might want
-        // message bubble from the bubble to go through heads up path
-        boolean inShade = mStatusBarStateListener != null
-                && mStatusBarStateListener.getCurrentState() == SHADE;
-        if (entry.isBubble() && !entry.isBubbleDismissed() && inShade) {
-            return false;
-        }
-
-        if (!canAlertCommon(entry)) {
-            if (DEBUG) {
-                Log.d(TAG, "No heads up: notification shouldn't alert: " + sbn.getKey());
-            }
-            return false;
-        }
-
-        if (!mUseHeadsUp || mPresenter.isDeviceInVrMode()) {
-            if (DEBUG) {
-                Log.d(TAG, "No heads up: no huns or vr mode");
-            }
-            return false;
-        }
-
-        boolean isDreaming = false;
-        try {
-            isDreaming = mDreamManager.isDreaming();
-        } catch (RemoteException e) {
-            Log.e(TAG, "Failed to query dream manager.", e);
-        }
-        boolean inUse = mPowerManager.isScreenOn() && !isDreaming;
-
-        if (!inUse) {
-            if (DEBUG) {
-                Log.d(TAG, "No heads up: not in use: " + sbn.getKey());
-            }
-            return false;
-        }
-
-        if (mNotificationData.shouldSuppressPeek(entry)) {
-            if (DEBUG) {
-                Log.d(TAG, "No heads up: suppressed by DND: " + sbn.getKey());
-            }
-            return false;
-        }
-
-        if (isSnoozedPackage(sbn)) {
-            if (DEBUG) {
-                Log.d(TAG, "No heads up: snoozed package: " + sbn.getKey());
-            }
-            return false;
-        }
-
-        if (entry.hasJustLaunchedFullScreenIntent()) {
-            if (DEBUG) {
-                Log.d(TAG, "No heads up: recent fullscreen: " + sbn.getKey());
-            }
-            return false;
-        }
-
-        if (mNotificationData.getImportance(sbn.getKey()) < NotificationManager.IMPORTANCE_HIGH) {
-            if (DEBUG) {
-                Log.d(TAG, "No heads up: unimportant notification: " + sbn.getKey());
-            }
-            return false;
-        }
-
-        if (!mCallback.canHeadsUp(entry, sbn)) {
-            return false;
-        }
-
-        return true;
-    }
-
-    /**
-     * Whether or not the notification should "pulse" on the user's display when the phone is
-     * dozing.  This displays the ambient view of the notification.
-     *
-     * @param entry the entry to check
-     * @return true if the entry should ambient pulse, false otherwise
-     */
-    private boolean shouldPulse(NotificationData.Entry entry) {
-        StatusBarNotification sbn = entry.notification;
-
-        if (!getShadeController().isDozing()) {
-            if (DEBUG) {
-                Log.d(TAG, "No pulsing: not dozing: " + sbn.getKey());
-            }
-            return false;
-        }
-
-        if (!canAlertCommon(entry)) {
-            if (DEBUG) {
-                Log.d(TAG, "No pulsing: notification shouldn't alert: " + sbn.getKey());
-            }
-            return false;
-        }
-
-        if (mNotificationData.shouldSuppressAmbient(entry)) {
-            if (DEBUG) {
-                Log.d(TAG, "No pulsing: ambient effect suppressed: " + sbn.getKey());
-            }
-            return false;
-        }
-
-        if (mNotificationData.getImportance(sbn.getKey())
-                < NotificationManager.IMPORTANCE_DEFAULT) {
-            if (DEBUG) {
-                Log.d(TAG, "No pulsing: not important enough: " + sbn.getKey());
-            }
-            return false;
-        }
-
-        Bundle extras = sbn.getNotification().extras;
-        CharSequence title = extras.getCharSequence(Notification.EXTRA_TITLE);
-        CharSequence text = extras.getCharSequence(Notification.EXTRA_TEXT);
-        if (TextUtils.isEmpty(title) && TextUtils.isEmpty(text)) {
-            if (DEBUG) {
-                Log.d(TAG, "No pulsing: title and text are empty: " + sbn.getKey());
-            }
-            return false;
-        }
-
-        return true;
-    }
-
-    /**
-     * Common checks between heads up alerting and ambient pulse alerting.  See
-     * {@link NotificationEntryManager#shouldHeadsUp(NotificationData.Entry)} and
-     * {@link NotificationEntryManager#shouldPulse(NotificationData.Entry)}.  Notifications that
-     * fail any of these checks should not alert at all.
-     *
-     * @param entry the entry to check
-     * @return true if these checks pass, false if the notification should not alert
-     */
-    protected boolean canAlertCommon(NotificationData.Entry entry) {
-        StatusBarNotification sbn = entry.notification;
-
-        if (mNotificationData.shouldFilterOut(entry)) {
-            if (DEBUG) {
-                Log.d(TAG, "No alerting: filtered notification: " + sbn.getKey());
-            }
-            return false;
-        }
-
-        // Don't alert notifications that are suppressed due to group alert behavior
-        if (sbn.isGroup() && sbn.getNotification().suppressAlertingDueToGrouping()) {
-            if (DEBUG) {
-                Log.d(TAG, "No alerting: suppressed due to group alert behavior");
-            }
-            return false;
-        }
-
-        return true;
-    }
-
     private void setNotificationShown(StatusBarNotification n) {
         setNotificationsShown(new String[]{n.getKey()});
     }
@@ -1039,10 +804,6 @@
         }
     }
 
-    private boolean isSnoozedPackage(StatusBarNotification sbn) {
-        return mHeadsUpManager.isSnoozed(sbn.getPackageName());
-    }
-
     /**
      * Update the entry's alert state and call the appropriate {@link AlertingNotificationManager}
      * method.
@@ -1078,22 +839,6 @@
     }
 
     /**
-     * Interface for retrieving heads-up and pulsing state for an entry.
-     */
-    public interface InterruptionStateProvider {
-        /**
-         * Whether the provided entry should be marked as heads-up when inflated.
-         */
-        boolean shouldHeadsUp(NotificationData.Entry entry);
-
-        /**
-         * Whether the provided entry should be marked as pulsing (displayed in ambient) when
-         * inflated.
-         */
-        boolean shouldPulse(NotificationData.Entry entry);
-    }
-
-    /**
      * Callback for NotificationEntryManager.
      */
     public interface Callback {
@@ -1126,14 +871,5 @@
          * @param statusBarNotification notification that is being removed
          */
         void onPerformRemoveNotification(StatusBarNotification statusBarNotification);
-
-        /**
-         * Returns true if NotificationEntryManager can heads up this notification.
-         *
-         * @param entry entry of the notification that might be heads upped
-         * @param sbn notification that might be heads upped
-         * @return true if the notification can be heads upped
-         */
-        boolean canHeadsUp(NotificationData.Entry entry, StatusBarNotification sbn);
     }
 }
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/NotificationFilter.java b/packages/SystemUI/src/com/android/systemui/statusbar/notification/NotificationFilter.java
new file mode 100644
index 0000000..5e99c38
--- /dev/null
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/NotificationFilter.java
@@ -0,0 +1,162 @@
+/*
+ * 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.notification;
+
+import android.Manifest;
+import android.app.AppGlobals;
+import android.app.Notification;
+import android.content.pm.IPackageManager;
+import android.content.pm.PackageManager;
+import android.os.RemoteException;
+import android.service.notification.StatusBarNotification;
+
+import com.android.internal.annotations.VisibleForTesting;
+import com.android.systemui.Dependency;
+import com.android.systemui.ForegroundServiceController;
+import com.android.systemui.statusbar.NotificationLockscreenUserManager;
+import com.android.systemui.statusbar.phone.NotificationGroupManager;
+import com.android.systemui.statusbar.phone.ShadeController;
+import com.android.systemui.statusbar.phone.StatusBar;
+
+import javax.inject.Inject;
+import javax.inject.Singleton;
+
+/** Component which manages the various reasons a notification might be filtered out. */
+@Singleton
+public class NotificationFilter {
+
+    private final NotificationGroupManager mGroupManager = Dependency.get(
+            NotificationGroupManager.class);
+
+    private NotificationData.KeyguardEnvironment mEnvironment;
+    private ShadeController mShadeController;
+    private ForegroundServiceController mFsc;
+    private NotificationLockscreenUserManager mUserManager;
+
+    @Inject
+    public NotificationFilter() {}
+
+    private NotificationData.KeyguardEnvironment getEnvironment() {
+        if (mEnvironment == null) {
+            mEnvironment = Dependency.get(NotificationData.KeyguardEnvironment.class);
+        }
+        return mEnvironment;
+    }
+
+    private ShadeController getShadeController() {
+        if (mShadeController == null) {
+            mShadeController = Dependency.get(ShadeController.class);
+        }
+        return mShadeController;
+    }
+
+    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;
+    }
+
+
+    /**
+     * @return true if the provided notification should NOT be shown right now.
+     */
+    public boolean shouldFilterOut(NotificationData.Entry entry) {
+        final StatusBarNotification sbn = entry.notification;
+        if (!(getEnvironment().isDeviceProvisioned()
+                || showNotificationEvenIfUnprovisioned(sbn))) {
+            return true;
+        }
+
+        if (!getEnvironment().isNotificationForCurrentProfiles(sbn)) {
+            return true;
+        }
+
+        if (getUserManager().isLockscreenPublicMode(sbn.getUserId())
+                && (sbn.getNotification().visibility == Notification.VISIBILITY_SECRET
+                        || getUserManager().shouldHideNotifications(sbn.getUserId())
+                        || getUserManager().shouldHideNotifications(sbn.getKey()))) {
+            return true;
+        }
+
+        if (getShadeController().isDozing() && entry.shouldSuppressAmbient()) {
+            return true;
+        }
+
+        if (!getShadeController().isDozing() && entry.shouldSuppressNotificationList()) {
+            return true;
+        }
+
+        if (entry.suspended) {
+            return true;
+        }
+
+        if (!StatusBar.ENABLE_CHILD_NOTIFICATIONS
+                && mGroupManager.isChildInGroupWithSummary(sbn)) {
+            return true;
+        }
+
+        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 (getFsc().isSystemAlertNotification(sbn)) {
+            final String[] apps = sbn.getNotification().extras.getStringArray(
+                    Notification.EXTRA_FOREGROUND_APPS);
+            if (apps != null && apps.length >= 1) {
+                if (!getFsc().isSystemAlertWarningNeeded(sbn.getUserId(), apps[0])) {
+                    return true;
+                }
+            }
+        }
+
+        return false;
+    }
+
+    // Q: What kinds of notifications should show during setup?
+    // A: Almost none! Only things coming from packages with permission
+    // android.permission.NOTIFICATION_DURING_SETUP that also have special "kind" tags marking them
+    // as relevant for setup (see below).
+    private static boolean showNotificationEvenIfUnprovisioned(StatusBarNotification sbn) {
+        return showNotificationEvenIfUnprovisioned(AppGlobals.getPackageManager(), sbn);
+    }
+
+    @VisibleForTesting
+    static boolean showNotificationEvenIfUnprovisioned(IPackageManager packageManager,
+            StatusBarNotification sbn) {
+        return checkUidPermission(packageManager, Manifest.permission.NOTIFICATION_DURING_SETUP,
+                sbn.getUid()) == PackageManager.PERMISSION_GRANTED
+                && sbn.getNotification().extras.getBoolean(Notification.EXTRA_ALLOW_DURING_SETUP);
+    }
+
+    private static int checkUidPermission(IPackageManager packageManager, String permission,
+            int uid) {
+        try {
+            return packageManager.checkUidPermission(permission, uid);
+        } catch (RemoteException e) {
+            throw e.rethrowFromSystemServer();
+        }
+    }
+}
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/NotificationInterruptionStateProvider.java b/packages/SystemUI/src/com/android/systemui/statusbar/notification/NotificationInterruptionStateProvider.java
new file mode 100644
index 0000000..8bd0e9a
--- /dev/null
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/NotificationInterruptionStateProvider.java
@@ -0,0 +1,332 @@
+/*
+ * 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.notification;
+
+import static com.android.systemui.statusbar.StatusBarState.SHADE;
+
+import android.app.Notification;
+import android.app.NotificationManager;
+import android.content.Context;
+import android.database.ContentObserver;
+import android.os.Bundle;
+import android.os.PowerManager;
+import android.os.RemoteException;
+import android.os.ServiceManager;
+import android.provider.Settings;
+import android.service.dreams.DreamService;
+import android.service.dreams.IDreamManager;
+import android.service.notification.StatusBarNotification;
+import android.text.TextUtils;
+import android.util.Log;
+
+import com.android.internal.annotations.VisibleForTesting;
+import com.android.systemui.Dependency;
+import com.android.systemui.statusbar.NotificationPresenter;
+import com.android.systemui.statusbar.StatusBarStateController;
+import com.android.systemui.statusbar.phone.ShadeController;
+import com.android.systemui.statusbar.policy.HeadsUpManager;
+
+/**
+ * Provides heads-up and pulsing state for notification entries.
+ */
+public class NotificationInterruptionStateProvider {
+
+    private static final String TAG = "InterruptionStateProvider";
+    private static final boolean DEBUG = false;
+    private static final boolean ENABLE_HEADS_UP = true;
+    private static final String SETTING_HEADS_UP_TICKER = "ticker_gets_heads_up";
+
+    private final StatusBarStateController mStatusBarStateController =
+            Dependency.get(StatusBarStateController.class);
+    private final NotificationFilter mNotificationFilter = Dependency.get(NotificationFilter.class);
+
+    private final Context mContext;
+    private final PowerManager mPowerManager;
+    private final IDreamManager mDreamManager;
+
+    private NotificationPresenter mPresenter;
+    private ShadeController mShadeController;
+    private HeadsUpManager mHeadsUpManager;
+    private HeadsUpSuppressor mHeadsUpSuppressor;
+
+    private ContentObserver mHeadsUpObserver;
+    @VisibleForTesting
+    protected boolean mUseHeadsUp = false;
+    private boolean mDisableNotificationAlerts;
+
+    public NotificationInterruptionStateProvider(Context context) {
+        this(context,
+                (PowerManager) context.getSystemService(Context.POWER_SERVICE),
+                IDreamManager.Stub.asInterface(
+                        ServiceManager.checkService(DreamService.DREAM_SERVICE)));
+    }
+
+    @VisibleForTesting
+    protected NotificationInterruptionStateProvider(
+            Context context,
+            PowerManager powerManager,
+            IDreamManager dreamManager) {
+        mContext = context;
+        mPowerManager = powerManager;
+        mDreamManager = dreamManager;
+    }
+
+    /** Sets up late-binding dependencies for this component. */
+    public void setUpWithPresenter(
+            NotificationPresenter notificationPresenter,
+            HeadsUpManager headsUpManager,
+            HeadsUpSuppressor headsUpSuppressor) {
+        mPresenter = notificationPresenter;
+        mHeadsUpManager = headsUpManager;
+        mHeadsUpSuppressor = headsUpSuppressor;
+
+        mHeadsUpObserver = new ContentObserver(Dependency.get(Dependency.MAIN_HANDLER)) {
+            @Override
+            public void onChange(boolean selfChange) {
+                boolean wasUsing = mUseHeadsUp;
+                mUseHeadsUp = ENABLE_HEADS_UP && !mDisableNotificationAlerts
+                        && Settings.Global.HEADS_UP_OFF != Settings.Global.getInt(
+                        mContext.getContentResolver(),
+                        Settings.Global.HEADS_UP_NOTIFICATIONS_ENABLED,
+                        Settings.Global.HEADS_UP_OFF);
+                Log.d(TAG, "heads up is " + (mUseHeadsUp ? "enabled" : "disabled"));
+                if (wasUsing != mUseHeadsUp) {
+                    if (!mUseHeadsUp) {
+                        Log.d(TAG,
+                                "dismissing any existing heads up notification on disable event");
+                        mHeadsUpManager.releaseAllImmediately();
+                    }
+                }
+            }
+        };
+
+        if (ENABLE_HEADS_UP) {
+            mContext.getContentResolver().registerContentObserver(
+                    Settings.Global.getUriFor(Settings.Global.HEADS_UP_NOTIFICATIONS_ENABLED),
+                    true,
+                    mHeadsUpObserver);
+            mContext.getContentResolver().registerContentObserver(
+                    Settings.Global.getUriFor(SETTING_HEADS_UP_TICKER), true,
+                    mHeadsUpObserver);
+        }
+        mHeadsUpObserver.onChange(true); // set up
+    }
+
+    private ShadeController getShadeController() {
+        if (mShadeController == null) {
+            mShadeController = Dependency.get(ShadeController.class);
+        }
+        return mShadeController;
+    }
+
+    /**
+     * Whether the notification should peek in from the top and alert the user.
+     *
+     * @param entry the entry to check
+     * @return true if the entry should heads up, false otherwise
+     */
+    public boolean shouldHeadsUp(NotificationData.Entry entry) {
+        StatusBarNotification sbn = entry.notification;
+
+        if (getShadeController().isDozing()) {
+            if (DEBUG) {
+                Log.d(TAG, "No heads up: device is dozing: " + sbn.getKey());
+            }
+            return false;
+        }
+
+        // TODO: need to changes this, e.g. should still heads up in expanded shade, might want
+        // message bubble from the bubble to go through heads up path
+        boolean inShade = mStatusBarStateController.getState() == SHADE;
+        if (entry.isBubble() && !entry.isBubbleDismissed() && inShade) {
+            return false;
+        }
+
+        if (!canAlertCommon(entry)) {
+            if (DEBUG) {
+                Log.d(TAG, "No heads up: notification shouldn't alert: " + sbn.getKey());
+            }
+            return false;
+        }
+
+        if (!mUseHeadsUp || mPresenter.isDeviceInVrMode()) {
+            if (DEBUG) {
+                Log.d(TAG, "No heads up: no huns or vr mode");
+            }
+            return false;
+        }
+
+        boolean isDreaming = false;
+        try {
+            isDreaming = mDreamManager.isDreaming();
+        } catch (RemoteException e) {
+            Log.e(TAG, "Failed to query dream manager.", e);
+        }
+        boolean inUse = mPowerManager.isScreenOn() && !isDreaming;
+
+        if (!inUse) {
+            if (DEBUG) {
+                Log.d(TAG, "No heads up: not in use: " + sbn.getKey());
+            }
+            return false;
+        }
+
+        if (entry.shouldSuppressPeek()) {
+            if (DEBUG) {
+                Log.d(TAG, "No heads up: suppressed by DND: " + sbn.getKey());
+            }
+            return false;
+        }
+
+        if (isSnoozedPackage(sbn)) {
+            if (DEBUG) {
+                Log.d(TAG, "No heads up: snoozed package: " + sbn.getKey());
+            }
+            return false;
+        }
+
+        if (entry.hasJustLaunchedFullScreenIntent()) {
+            if (DEBUG) {
+                Log.d(TAG, "No heads up: recent fullscreen: " + sbn.getKey());
+            }
+            return false;
+        }
+
+        if (entry.importance < NotificationManager.IMPORTANCE_HIGH) {
+            if (DEBUG) {
+                Log.d(TAG, "No heads up: unimportant notification: " + sbn.getKey());
+            }
+            return false;
+        }
+
+        if (!mHeadsUpSuppressor.canHeadsUp(entry, sbn)) {
+            return false;
+        }
+
+        return true;
+    }
+
+    /**
+     * Whether or not the notification should "pulse" on the user's display when the phone is
+     * dozing.  This displays the ambient view of the notification.
+     *
+     * @param entry the entry to check
+     * @return true if the entry should ambient pulse, false otherwise
+     */
+    public boolean shouldPulse(NotificationData.Entry entry) {
+        StatusBarNotification sbn = entry.notification;
+
+        if (!getShadeController().isDozing()) {
+            if (DEBUG) {
+                Log.d(TAG, "No pulsing: not dozing: " + sbn.getKey());
+            }
+            return false;
+        }
+
+        if (!canAlertCommon(entry)) {
+            if (DEBUG) {
+                Log.d(TAG, "No pulsing: notification shouldn't alert: " + sbn.getKey());
+            }
+            return false;
+        }
+
+        if (entry.shouldSuppressAmbient()) {
+            if (DEBUG) {
+                Log.d(TAG, "No pulsing: ambient effect suppressed: " + sbn.getKey());
+            }
+            return false;
+        }
+
+        if (entry.importance < NotificationManager.IMPORTANCE_DEFAULT) {
+            if (DEBUG) {
+                Log.d(TAG, "No pulsing: not important enough: " + sbn.getKey());
+            }
+            return false;
+        }
+
+        Bundle extras = sbn.getNotification().extras;
+        CharSequence title = extras.getCharSequence(Notification.EXTRA_TITLE);
+        CharSequence text = extras.getCharSequence(Notification.EXTRA_TEXT);
+        if (TextUtils.isEmpty(title) && TextUtils.isEmpty(text)) {
+            if (DEBUG) {
+                Log.d(TAG, "No pulsing: title and text are empty: " + sbn.getKey());
+            }
+            return false;
+        }
+
+        return true;
+    }
+
+    /**
+     * Common checks between heads up alerting and ambient pulse alerting.  See
+     * {@link #shouldHeadsUp(NotificationData.Entry)} and
+     * {@link #shouldPulse(NotificationData.Entry)}.  Notifications that fail any of these checks
+     * should not alert at all.
+     *
+     * @param entry the entry to check
+     * @return true if these checks pass, false if the notification should not alert
+     */
+    protected boolean canAlertCommon(NotificationData.Entry entry) {
+        StatusBarNotification sbn = entry.notification;
+
+        if (mNotificationFilter.shouldFilterOut(entry)) {
+            if (DEBUG) {
+                Log.d(TAG, "No alerting: filtered notification: " + sbn.getKey());
+            }
+            return false;
+        }
+
+        // Don't alert notifications that are suppressed due to group alert behavior
+        if (sbn.isGroup() && sbn.getNotification().suppressAlertingDueToGrouping()) {
+            if (DEBUG) {
+                Log.d(TAG, "No alerting: suppressed due to group alert behavior");
+            }
+            return false;
+        }
+
+        return true;
+    }
+
+    private boolean isSnoozedPackage(StatusBarNotification sbn) {
+        return mHeadsUpManager.isSnoozed(sbn.getPackageName());
+    }
+
+    /** Sets whether to disable all alerts. */
+    public void setDisableNotificationAlerts(boolean disableNotificationAlerts) {
+        mDisableNotificationAlerts = disableNotificationAlerts;
+        mHeadsUpObserver.onChange(true);
+    }
+
+    protected NotificationPresenter getPresenter() {
+        return mPresenter;
+    }
+
+    /** A component which can suppress heads-up notifications due to the overall state of the UI. */
+    public interface HeadsUpSuppressor {
+        /**
+         * Returns false if the provided notification is ineligible for heads-up according to this
+         * component.
+         *
+         * @param entry entry of the notification that might be heads upped
+         * @param sbn   notification that might be heads upped
+         * @return false if the notification can not be heads upped
+         */
+        boolean canHeadsUp(NotificationData.Entry entry, StatusBarNotification sbn);
+
+    }
+
+}
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/NotificationRowBinder.java b/packages/SystemUI/src/com/android/systemui/statusbar/notification/NotificationRowBinder.java
index 824bd81..b241b8a 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/NotificationRowBinder.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/NotificationRowBinder.java
@@ -64,6 +64,8 @@
     private final NotificationGutsManager mGutsManager =
             Dependency.get(NotificationGutsManager.class);
     private final UiOffloadThread mUiOffloadThread = Dependency.get(UiOffloadThread.class);
+    private final NotificationInterruptionStateProvider mNotificationInterruptionStateProvider =
+            Dependency.get(NotificationInterruptionStateProvider.class);
 
     private final Context mContext;
     private final IStatusBarService mBarService;
@@ -79,7 +81,6 @@
     private ExpandableNotificationRow.OnAppOpsClickListener mOnAppOpsClickListener;
     private BindRowCallback mBindRowCallback;
     private NotificationClicker mNotificationClicker;
-    private NotificationEntryManager.InterruptionStateProvider mInterruptionStateProvider;
 
     @Inject
     public NotificationRowBinder(Context context) {
@@ -116,11 +117,6 @@
         mNotificationClicker = clicker;
     }
 
-    public void setInterruptionStateProvider(
-            NotificationEntryManager.InterruptionStateProvider interruptionStateProvider) {
-        mInterruptionStateProvider = interruptionStateProvider;
-    }
-
     /**
      * Inflates the views for the given entry (possibly asynchronously).
      */
@@ -253,10 +249,10 @@
         row.setUseIncreasedHeadsUpHeight(useIncreasedHeadsUp);
         row.setEntry(entry);
 
-        if (mInterruptionStateProvider.shouldHeadsUp(entry)) {
+        if (mNotificationInterruptionStateProvider.shouldHeadsUp(entry)) {
             row.updateInflationFlag(FLAG_CONTENT_VIEW_HEADS_UP, true /* shouldInflate */);
         }
-        if (mInterruptionStateProvider.shouldPulse(entry)) {
+        if (mNotificationInterruptionStateProvider.shouldPulse(entry)) {
             row.updateInflationFlag(FLAG_CONTENT_VIEW_AMBIENT, true /* shouldInflate */);
         }
         row.setNeedsRedaction(
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/phone/NotificationIconAreaController.java b/packages/SystemUI/src/com/android/systemui/statusbar/phone/NotificationIconAreaController.java
index 2d5d562..e40835f 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/NotificationIconAreaController.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/NotificationIconAreaController.java
@@ -207,7 +207,7 @@
         }
 
         // showAmbient == show in shade but not shelf
-        if (!showAmbient && mEntryManager.getNotificationData().shouldSuppressStatusBar(entry)) {
+        if (!showAmbient && entry.shouldSuppressStatusBar()) {
             return false;
         }
 
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 1b43f8f..008c21d 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/StatusBar.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/StatusBar.java
@@ -191,6 +191,7 @@
 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.NotificationInterruptionStateProvider;
 import com.android.systemui.statusbar.notification.NotificationRowBinder;
 import com.android.systemui.statusbar.notification.VisualStabilityManager;
 import com.android.systemui.statusbar.notification.logging.NotificationLogger;
@@ -377,6 +378,7 @@
     private NotificationGutsManager mGutsManager;
     protected NotificationLogger mNotificationLogger;
     protected NotificationEntryManager mEntryManager;
+    private NotificationInterruptionStateProvider mNotificationInterruptionStateProvider;
     private NotificationRowBinder mNotificationRowBinder;
     protected NotificationViewHierarchyManager mViewHierarchyManager;
     protected ForegroundServiceController mForegroundServiceController;
@@ -622,6 +624,8 @@
         mGutsManager = Dependency.get(NotificationGutsManager.class);
         mMediaManager = Dependency.get(NotificationMediaManager.class);
         mEntryManager = Dependency.get(NotificationEntryManager.class);
+        mNotificationInterruptionStateProvider =
+                Dependency.get(NotificationInterruptionStateProvider.class);
         mNotificationRowBinder = Dependency.get(NotificationRowBinder.class);
         mViewHierarchyManager = Dependency.get(NotificationViewHierarchyManager.class);
         mForegroundServiceController = Dependency.get(ForegroundServiceController.class);
@@ -1413,7 +1417,7 @@
         }
 
         if ((diff1 & StatusBarManager.DISABLE_NOTIFICATION_ALERTS) != 0) {
-            mEntryManager.setDisableNotificationAlerts(
+            mNotificationInterruptionStateProvider.setDisableNotificationAlerts(
                     (state1 & StatusBarManager.DISABLE_NOTIFICATION_ALERTS) != 0);
         }
 
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/phone/StatusBarNotificationPresenter.java b/packages/SystemUI/src/com/android/systemui/statusbar/phone/StatusBarNotificationPresenter.java
index c8c9ebe5..e69dea5 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/StatusBarNotificationPresenter.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/StatusBarNotificationPresenter.java
@@ -58,6 +58,7 @@
 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.NotificationInterruptionStateProvider;
 import com.android.systemui.statusbar.notification.NotificationRowBinder;
 import com.android.systemui.statusbar.notification.row.ActivatableNotificationView;
 import com.android.systemui.statusbar.notification.row.ExpandableNotificationRow;
@@ -91,6 +92,8 @@
             Dependency.get(NotificationEntryManager.class);
     private final NotificationRowBinder mNotificationRowBinder =
             Dependency.get(NotificationRowBinder.class);
+    private final NotificationInterruptionStateProvider mNotificationInterruptionStateProvider =
+            Dependency.get(NotificationInterruptionStateProvider.class);
     private final NotificationMediaManager mMediaManager =
             Dependency.get(NotificationMediaManager.class);
     protected AmbientPulseManager mAmbientPulseManager = Dependency.get(AmbientPulseManager.class);
@@ -173,6 +176,8 @@
             mEntryManager.setUpWithPresenter(this, notifListContainer, this, mHeadsUpManager);
             mNotificationRowBinder.setUpWithPresenter(this, notifListContainer, mHeadsUpManager,
                     mEntryManager, this);
+            mNotificationInterruptionStateProvider.setUpWithPresenter(
+                    this, mHeadsUpManager, this::canHeadsUp);
             mLockscreenUserManager.setUpWithPresenter(this);
             mMediaManager.setUpWithPresenter(this);
             Dependency.get(NotificationGutsManager.class).setUpWithPresenter(this,
@@ -225,7 +230,7 @@
     @Override
     public void onPerformRemoveNotification(StatusBarNotification n) {
         if (mNotificationPanel.hasPulsingNotifications() &&
-                    !mAmbientPulseManager.hasNotifications()) {
+                !mAmbientPulseManager.hasNotifications()) {
             // We were showing a pulse for a notification, but no notifications are pulsing anymore.
             // Finish the pulse.
             mDozeScrimController.pulseOutNow();
@@ -282,7 +287,6 @@
         return !mEntryManager.getNotificationData().getActiveNotifications().isEmpty();
     }
 
-    @Override
     public boolean canHeadsUp(Entry entry, StatusBarNotification sbn) {
         if (mShadeController.isDozing()) {
             return false;