Merge "Add Coordinators to the NewNotifPipeline"
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/NotificationLockscreenUserManager.java b/packages/SystemUI/src/com/android/systemui/statusbar/NotificationLockscreenUserManager.java
index 4cc5b21..ff4ce94 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/NotificationLockscreenUserManager.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/NotificationLockscreenUserManager.java
@@ -65,8 +65,18 @@
 
     boolean needsRedaction(NotificationEntry entry);
 
+    /**
+     * Has the given user chosen to allow their private (full) notifications to be shown even
+     * when the lockscreen is in "public" (secure & locked) mode?
+     */
     boolean userAllowsPrivateNotificationsInPublic(int currentUserId);
 
+    /**
+     * Has the given user chosen to allow notifications to be shown even when the lockscreen is in
+     * "public" (secure & locked) mode?
+     */
+    boolean userAllowsNotificationsInPublic(int userId);
+
     /** Notified when the current user changes. */
     interface UserChangedListener {
         void onUserChanged(int userId);
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/NotificationLockscreenUserManagerImpl.java b/packages/SystemUI/src/com/android/systemui/statusbar/NotificationLockscreenUserManagerImpl.java
index f5710a8..0f3f6b7 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/NotificationLockscreenUserManagerImpl.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/NotificationLockscreenUserManagerImpl.java
@@ -435,7 +435,7 @@
      * 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) {
+    public boolean userAllowsNotificationsInPublic(int userHandle) {
         if (isCurrentProfile(userHandle) && userHandle != mCurrentUserId) {
             return true;
         }
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/NotificationFilter.java b/packages/SystemUI/src/com/android/systemui/statusbar/notification/NotificationFilter.java
index e5f44bd..b61c1ef 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/NotificationFilter.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/NotificationFilter.java
@@ -36,7 +36,10 @@
 import javax.inject.Inject;
 import javax.inject.Singleton;
 
-/** Component which manages the various reasons a notification might be filtered out. */
+/** Component which manages the various reasons a notification might be filtered out.*/
+// TODO: delete NotificationFilter.java after migrating to new NotifPipeline b/145659174.
+//  Notification filtering is taken care of across the different Coordinators (mostly
+//  KeyguardCoordinator.java)
 @Singleton
 public class NotificationFilter {
 
@@ -109,7 +112,7 @@
             return true;
         }
 
-        if (entry.isSuspended()) {
+        if (entry.getRanking().isSuspended()) {
             return true;
         }
 
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/GroupEntry.java b/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/GroupEntry.java
index f9f3266..9ae3882 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/GroupEntry.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/GroupEntry.java
@@ -18,6 +18,8 @@
 
 import android.annotation.Nullable;
 
+import com.android.internal.annotations.VisibleForTesting;
+
 import java.util.ArrayList;
 import java.util.Collections;
 import java.util.Comparator;
@@ -34,7 +36,8 @@
     private final List<NotificationEntry> mUnmodifiableChildren =
             Collections.unmodifiableList(mChildren);
 
-    GroupEntry(String key) {
+    @VisibleForTesting
+    public GroupEntry(String key) {
         super(key);
     }
 
@@ -52,7 +55,8 @@
         return mUnmodifiableChildren;
     }
 
-    void setSummary(@Nullable NotificationEntry summary) {
+    @VisibleForTesting
+    public void setSummary(@Nullable NotificationEntry summary) {
         mSummary = summary;
     }
 
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/ListEntry.java b/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/ListEntry.java
index dc68c4b..6ce7fd9 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/ListEntry.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/ListEntry.java
@@ -18,6 +18,8 @@
 
 import android.annotation.Nullable;
 
+import com.android.internal.annotations.VisibleForTesting;
+
 /**
  * Abstract superclass for top-level entries, i.e. things that can appear in the final notification
  * list shown to users. In practice, this means either GroupEntries or NotificationEntries.
@@ -49,7 +51,8 @@
         return mParent;
     }
 
-    void setParent(@Nullable GroupEntry parent) {
+    @VisibleForTesting
+    public void setParent(@Nullable GroupEntry parent) {
         mParent = parent;
     }
 
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/coordinator/Coordinator.java b/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/coordinator/Coordinator.java
new file mode 100644
index 0000000..898918e
--- /dev/null
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/coordinator/Coordinator.java
@@ -0,0 +1,42 @@
+/*
+ * Copyright (C) 2019 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.systemui.statusbar.notification.collection.coordinator;
+
+import com.android.systemui.statusbar.notification.collection.NotifCollection;
+import com.android.systemui.statusbar.notification.collection.NotifCollectionListener;
+import com.android.systemui.statusbar.notification.collection.NotifLifetimeExtender;
+import com.android.systemui.statusbar.notification.collection.init.NewNotifPipeline;
+import com.android.systemui.statusbar.notification.collection.listbuilder.NotifListBuilder;
+import com.android.systemui.statusbar.notification.collection.listbuilder.pluggable.Pluggable;
+
+/**
+ * Interface for registering callbacks to the {@link NewNotifPipeline}.
+ *
+ * This includes registering:
+ *  {@link Pluggable}s to the {@link NotifListBuilder}
+ *  {@link NotifCollectionListener}s and {@link NotifLifetimeExtender}s to {@link NotifCollection}
+ */
+public interface Coordinator {
+
+    /**
+     * Called after the NewNotifPipeline is initialized.
+     * Coordinators should register their {@link Pluggable}s to the notifListBuilder
+     * and their {@link NotifCollectionListener}s and {@link NotifLifetimeExtender}s
+     * to the notifCollection in this method.
+     */
+    void attach(NotifCollection notifCollection, NotifListBuilder notifListBuilder);
+}
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/coordinator/KeyguardCoordinator.java b/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/coordinator/KeyguardCoordinator.java
new file mode 100644
index 0000000..f5ed089
--- /dev/null
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/coordinator/KeyguardCoordinator.java
@@ -0,0 +1,233 @@
+/*
+ * Copyright (C) 2019 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.systemui.statusbar.notification.collection.coordinator;
+
+import static android.app.Notification.VISIBILITY_SECRET;
+
+import android.content.BroadcastReceiver;
+import android.content.Context;
+import android.content.Intent;
+import android.content.IntentFilter;
+import android.database.ContentObserver;
+import android.net.Uri;
+import android.os.Handler;
+import android.os.UserHandle;
+import android.provider.Settings;
+import android.service.notification.StatusBarNotification;
+
+import androidx.annotation.MainThread;
+
+import com.android.keyguard.KeyguardUpdateMonitor;
+import com.android.systemui.broadcast.BroadcastDispatcher;
+import com.android.systemui.plugins.statusbar.StatusBarStateController;
+import com.android.systemui.statusbar.NotificationLockscreenUserManager;
+import com.android.systemui.statusbar.notification.NotificationUtils;
+import com.android.systemui.statusbar.notification.collection.NotifCollection;
+import com.android.systemui.statusbar.notification.collection.NotificationEntry;
+import com.android.systemui.statusbar.notification.collection.listbuilder.NotifListBuilder;
+import com.android.systemui.statusbar.notification.collection.listbuilder.pluggable.NotifFilter;
+import com.android.systemui.statusbar.policy.KeyguardStateController;
+
+import javax.inject.Inject;
+import javax.inject.Singleton;
+
+/**
+ * Filters low priority and privacy-sensitive notifications from the lockscreen.
+ */
+@Singleton
+public class KeyguardCoordinator implements Coordinator {
+    private static final String TAG = "KeyguardNotificationCoordinator";
+
+    private final Context mContext;
+    private final Handler mMainHandler;
+    private final KeyguardStateController mKeyguardStateController;
+    private final NotificationLockscreenUserManager mLockscreenUserManager;
+    private final BroadcastDispatcher mBroadcastDispatcher;
+    private final StatusBarStateController mStatusBarStateController;
+    private final KeyguardUpdateMonitor mKeyguardUpdateMonitor;
+
+    @Inject
+    public KeyguardCoordinator(
+            Context context,
+            @MainThread Handler mainThreadHandler,
+            KeyguardStateController keyguardStateController,
+            NotificationLockscreenUserManager lockscreenUserManager,
+            BroadcastDispatcher broadcastDispatcher,
+            StatusBarStateController statusBarStateController,
+            KeyguardUpdateMonitor keyguardUpdateMonitor) {
+        mContext = context;
+        mMainHandler = mainThreadHandler;
+        mKeyguardStateController = keyguardStateController;
+        mLockscreenUserManager = lockscreenUserManager;
+
+        mBroadcastDispatcher = broadcastDispatcher;
+        mStatusBarStateController = statusBarStateController;
+        mKeyguardUpdateMonitor = keyguardUpdateMonitor;
+    }
+
+    @Override
+    public void attach(NotifCollection notifCollection, NotifListBuilder notifListBuilder) {
+        setupInvalidateNotifListCallbacks();
+        notifListBuilder.addFilter(mNotifFilter);
+    }
+
+    protected final NotifFilter mNotifFilter = new NotifFilter(TAG) {
+        @Override
+        public boolean shouldFilterOut(NotificationEntry entry, long now) {
+            final StatusBarNotification sbn = entry.getSbn();
+
+            // FILTER OUT the notification when the notification isn't for the current profile
+            if (!mLockscreenUserManager.isCurrentProfile(sbn.getUserId())) {
+                return true;
+            }
+
+            // FILTER OUT the notification when the keyguard is showing and...
+            if (mKeyguardStateController.isShowing()) {
+                // ... user settings or the device policy manager doesn't allow lockscreen
+                // notifications;
+                if (!mLockscreenUserManager.shouldShowLockscreenNotifications()) {
+                    return true;
+                }
+
+                final int currUserId = mLockscreenUserManager.getCurrentUserId();
+                final int notifUserId = (sbn.getUser().getIdentifier() == UserHandle.USER_ALL)
+                        ? currUserId : sbn.getUser().getIdentifier();
+
+                // ... user is in lockdown
+                if (mKeyguardUpdateMonitor.isUserInLockdown(currUserId)
+                        || mKeyguardUpdateMonitor.isUserInLockdown(notifUserId)) {
+                    return true;
+                }
+
+                // ... device is in public mode and the user's settings doesn't allow
+                // notifications to show in public mode
+                if (mLockscreenUserManager.isLockscreenPublicMode(currUserId)
+                        || mLockscreenUserManager.isLockscreenPublicMode(notifUserId)) {
+                    if (entry.getRanking().getVisibilityOverride() == VISIBILITY_SECRET) {
+                        return true;
+                    }
+
+                    if (!mLockscreenUserManager.userAllowsNotificationsInPublic(currUserId)
+                            || !mLockscreenUserManager.userAllowsNotificationsInPublic(
+                            notifUserId)) {
+                        return true;
+                    }
+                }
+
+                // ... neither this notification nor its summary have high enough priority
+                // to be shown on the lockscreen
+                // TODO: grouping hasn't happened yet (b/145134683)
+                if (entry.getParent() != null) {
+                    final NotificationEntry summary = entry.getParent().getRepresentativeEntry();
+                    if (priorityExceedsLockscreenShowingThreshold(summary)) {
+                        return false;
+                    }
+                }
+                return !priorityExceedsLockscreenShowingThreshold(entry);
+            }
+            return false;
+        }
+    };
+
+    private boolean priorityExceedsLockscreenShowingThreshold(NotificationEntry entry) {
+        if (entry == null) {
+            return false;
+        }
+        if (NotificationUtils.useNewInterruptionModel(mContext)
+                && hideSilentNotificationsOnLockscreen()) {
+            // TODO: make sure in the NewNotifPipeline that entry.isHighPriority() has been
+            //  correctly updated before reaching this point (b/145134683)
+            return entry.isHighPriority();
+        } else {
+            return !entry.getRanking().isAmbient();
+        }
+    }
+
+    private boolean hideSilentNotificationsOnLockscreen() {
+        return Settings.Secure.getInt(mContext.getContentResolver(),
+                Settings.Secure.LOCK_SCREEN_SHOW_SILENT_NOTIFICATIONS, 1) == 0;
+    }
+
+    private void setupInvalidateNotifListCallbacks() {
+        // register onKeyguardShowing callback
+        mKeyguardStateController.addCallback(mKeyguardCallback);
+
+        // register lockscreen settings changed callbacks:
+        final ContentObserver settingsObserver = new ContentObserver(mMainHandler) {
+            @Override
+            public void onChange(boolean selfChange, Uri uri) {
+                if (mKeyguardStateController.isShowing()) {
+                    invalidateListFromFilter("Settings " + uri + " changed");
+                }
+            }
+        };
+
+        mContext.getContentResolver().registerContentObserver(
+                Settings.Secure.getUriFor(Settings.Secure.LOCK_SCREEN_SHOW_NOTIFICATIONS),
+                false,
+                settingsObserver,
+                UserHandle.USER_ALL);
+
+        mContext.getContentResolver().registerContentObserver(
+                Settings.Secure.getUriFor(Settings.Secure.LOCK_SCREEN_ALLOW_PRIVATE_NOTIFICATIONS),
+                true,
+                settingsObserver,
+                UserHandle.USER_ALL);
+
+        mContext.getContentResolver().registerContentObserver(
+                Settings.Global.getUriFor(Settings.Global.ZEN_MODE),
+                false,
+                settingsObserver);
+
+        // register (maybe) public mode changed callbacks:
+        mStatusBarStateController.addCallback(mStatusBarStateListener);
+        mBroadcastDispatcher.registerReceiver(new BroadcastReceiver() {
+            @Override
+            public void onReceive(Context context, Intent intent) {
+                if (mKeyguardStateController.isShowing()) {
+                    // maybe public mode changed
+                    invalidateListFromFilter(intent.getAction());
+                }
+            }}, new IntentFilter(Intent.ACTION_USER_SWITCHED));
+    }
+
+    private void invalidateListFromFilter(String reason) {
+        mNotifFilter.invalidateList();
+    }
+
+    private final KeyguardStateController.Callback mKeyguardCallback =
+            new KeyguardStateController.Callback() {
+        @Override
+        public void onUnlockedChanged() {
+            invalidateListFromFilter("onUnlockedChanged");
+        }
+
+        @Override
+        public void onKeyguardShowingChanged() {
+            invalidateListFromFilter("onKeyguardShowingChanged");
+        }
+    };
+
+    private final StatusBarStateController.StateListener mStatusBarStateListener =
+            new StatusBarStateController.StateListener() {
+                @Override
+                public void onStateChanged(int newState) {
+                    // maybe public mode changed
+                    invalidateListFromFilter("onStatusBarStateChanged");
+                }
+    };
+}
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/coordinator/NotifCoordinators.java b/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/coordinator/NotifCoordinators.java
new file mode 100644
index 0000000..0d53a76
--- /dev/null
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/coordinator/NotifCoordinators.java
@@ -0,0 +1,71 @@
+/*
+ * Copyright (C) 2019 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.systemui.statusbar.notification.collection.coordinator;
+
+import com.android.systemui.Dumpable;
+import com.android.systemui.statusbar.notification.collection.NotifCollection;
+import com.android.systemui.statusbar.notification.collection.NotifCollectionListener;
+import com.android.systemui.statusbar.notification.collection.NotifLifetimeExtender;
+import com.android.systemui.statusbar.notification.collection.listbuilder.NotifListBuilder;
+import com.android.systemui.statusbar.notification.collection.listbuilder.pluggable.Pluggable;
+
+import java.io.FileDescriptor;
+import java.io.PrintWriter;
+import java.util.ArrayList;
+import java.util.List;
+
+import javax.inject.Inject;
+import javax.inject.Singleton;
+
+/**
+ * Handles the attachment of the {@link NotifListBuilder} and {@link NotifCollection} to the
+ * {@link Coordinator}s, so that the Coordinators can register their respective callbacks.
+ */
+@Singleton
+public class NotifCoordinators implements Dumpable {
+    private static final String TAG = "NotifCoordinators";
+    private final List<Coordinator> mCoordinators = new ArrayList<>();
+
+    /**
+     * Creates all the coordinators.
+     */
+    @Inject
+    public NotifCoordinators(KeyguardCoordinator keyguardNotificationCoordinator) {
+        mCoordinators.add(keyguardNotificationCoordinator);
+        // TODO: add new Coordinators here! (b/145134683, b/112656837)
+    }
+
+    /**
+     * Sends the initialized notifListBuilder and notifCollection to each
+     * coordinator to indicate the notifListBuilder is ready to accept {@link Pluggable}s
+     * and the notifCollection is ready to accept {@link NotifCollectionListener}s and
+     * {@link NotifLifetimeExtender}s.
+     */
+    public void attach(NotifCollection notifCollection, NotifListBuilder notifListBuilder) {
+        for (Coordinator c : mCoordinators) {
+            c.attach(notifCollection, notifListBuilder);
+        }
+    }
+
+    @Override
+    public void dump(FileDescriptor fd, PrintWriter pw, String[] args) {
+        pw.println(TAG + ":");
+        for (Coordinator c : mCoordinators) {
+            pw.println("\t" + c.getClass());
+        }
+    }
+}
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/init/NewNotifPipeline.java b/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/init/NewNotifPipeline.java
index 3b3e7e2..5fc55da 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/init/NewNotifPipeline.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/init/NewNotifPipeline.java
@@ -23,6 +23,7 @@
 import com.android.systemui.statusbar.NotificationListener;
 import com.android.systemui.statusbar.notification.collection.NotifCollection;
 import com.android.systemui.statusbar.notification.collection.NotifListBuilderImpl;
+import com.android.systemui.statusbar.notification.collection.coordinator.NotifCoordinators;
 
 import java.io.FileDescriptor;
 import java.io.PrintWriter;
@@ -37,6 +38,7 @@
 public class NewNotifPipeline implements Dumpable {
     private final NotifCollection mNotifCollection;
     private final NotifListBuilderImpl mNotifPipeline;
+    private final NotifCoordinators mNotifPluggableCoordinators;
     private final DumpController mDumpController;
 
     private final FakePipelineConsumer mFakePipelineConsumer = new FakePipelineConsumer();
@@ -45,9 +47,11 @@
     public NewNotifPipeline(
             NotifCollection notifCollection,
             NotifListBuilderImpl notifPipeline,
+            NotifCoordinators notifCoordinators,
             DumpController dumpController) {
         mNotifCollection = notifCollection;
         mNotifPipeline = notifPipeline;
+        mNotifPluggableCoordinators = notifCoordinators;
         mDumpController = dumpController;
     }
 
@@ -57,6 +61,7 @@
         mFakePipelineConsumer.attach(mNotifPipeline);
         mNotifPipeline.attach(mNotifCollection);
         mNotifCollection.attach(notificationService);
+        mNotifPluggableCoordinators.attach(mNotifCollection, mNotifPipeline);
 
         Log.d(TAG, "Notif pipeline initialized");
 
@@ -66,6 +71,7 @@
     @Override
     public void dump(FileDescriptor fd, PrintWriter pw, String[] args) {
         mFakePipelineConsumer.dump(fd, pw, args);
+        mNotifPluggableCoordinators.dump(fd, pw, args);
     }
 
     private static final String TAG = "NewNotifPipeline";
diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/collection/coordinator/KeyguardCoordinatorTest.java b/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/collection/coordinator/KeyguardCoordinatorTest.java
new file mode 100644
index 0000000..87b3783d
--- /dev/null
+++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/collection/coordinator/KeyguardCoordinatorTest.java
@@ -0,0 +1,243 @@
+/*
+ * Copyright (C) 2019 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.systemui.statusbar.notification.collection.coordinator;
+
+import static android.app.Notification.VISIBILITY_PUBLIC;
+import static android.app.Notification.VISIBILITY_SECRET;
+import static android.app.NotificationManager.IMPORTANCE_HIGH;
+import static android.app.NotificationManager.IMPORTANCE_MIN;
+
+import static junit.framework.Assert.assertFalse;
+import static junit.framework.Assert.assertTrue;
+
+import static org.mockito.Mockito.when;
+
+import android.os.Handler;
+import android.os.UserHandle;
+import android.testing.AndroidTestingRunner;
+
+import androidx.test.filters.SmallTest;
+
+import com.android.keyguard.KeyguardUpdateMonitor;
+import com.android.systemui.SysuiTestCase;
+import com.android.systemui.broadcast.BroadcastDispatcher;
+import com.android.systemui.plugins.statusbar.StatusBarStateController;
+import com.android.systemui.statusbar.NotificationEntryBuilder;
+import com.android.systemui.statusbar.NotificationLockscreenUserManager;
+import com.android.systemui.statusbar.RankingBuilder;
+import com.android.systemui.statusbar.notification.collection.GroupEntry;
+import com.android.systemui.statusbar.notification.collection.NotificationEntry;
+import com.android.systemui.statusbar.policy.KeyguardStateController;
+
+import org.junit.Before;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+import org.mockito.Mock;
+import org.mockito.MockitoAnnotations;
+
+@SmallTest
+@RunWith(AndroidTestingRunner.class)
+public class KeyguardCoordinatorTest extends SysuiTestCase {
+    private static final int NOTIF_USER_ID = 0;
+    private static final int CURR_USER_ID = 1;
+
+    @Mock private Handler mMainHandler;
+    @Mock private KeyguardStateController mKeyguardStateController;
+    @Mock private NotificationLockscreenUserManager mLockscreenUserManager;
+    @Mock private BroadcastDispatcher mBroadcastDispatcher;
+    @Mock private StatusBarStateController mStatusBarStateController;
+    @Mock private KeyguardUpdateMonitor mKeyguardUpdateMonitor;
+
+    private NotificationEntry mEntry;
+    private KeyguardCoordinator mKeyguardNotificationCoordinator;
+
+    @Before
+    public void setup() {
+        MockitoAnnotations.initMocks(this);
+        mKeyguardNotificationCoordinator = new KeyguardCoordinator(
+                mContext, mMainHandler, mKeyguardStateController, mLockscreenUserManager,
+                mBroadcastDispatcher, mStatusBarStateController,
+                mKeyguardUpdateMonitor);
+
+        mEntry = new NotificationEntryBuilder()
+                .setUser(new UserHandle(NOTIF_USER_ID))
+                .build();
+    }
+
+    @Test
+    public void unfilteredState() {
+        // GIVEN an 'unfiltered-keyguard-showing' state
+        setupUnfilteredState();
+
+        // THEN don't filter out the entry
+        assertFalse(mKeyguardNotificationCoordinator.mNotifFilter.shouldFilterOut(mEntry, 0));
+    }
+
+    @Test
+    public void notificationNotForCurrentProfile() {
+        // GIVEN the notification isn't for the given user
+        setupUnfilteredState();
+        when(mLockscreenUserManager.isCurrentProfile(NOTIF_USER_ID)).thenReturn(false);
+
+        // THEN filter out the entry
+        assertTrue(mKeyguardNotificationCoordinator.mNotifFilter.shouldFilterOut(mEntry, 0));
+    }
+
+    @Test
+    public void keyguardNotShowing() {
+        // GIVEN the lockscreen isn't showing
+        setupUnfilteredState();
+        when(mKeyguardStateController.isShowing()).thenReturn(false);
+
+        // THEN don't filter out the entry
+        assertFalse(mKeyguardNotificationCoordinator.mNotifFilter.shouldFilterOut(mEntry, 0));
+    }
+
+    @Test
+    public void doNotShowLockscreenNotifications() {
+        // GIVEN an 'unfiltered-keyguard-showing' state
+        setupUnfilteredState();
+
+        // WHEN we shouldn't show any lockscreen notifications
+        when(mLockscreenUserManager.shouldShowLockscreenNotifications()).thenReturn(false);
+
+        // THEN filter out the entry
+        assertTrue(mKeyguardNotificationCoordinator.mNotifFilter.shouldFilterOut(mEntry, 0));
+    }
+
+    @Test
+    public void lockdown() {
+        // GIVEN an 'unfiltered-keyguard-showing' state
+        setupUnfilteredState();
+
+        // WHEN the notification's user is in lockdown:
+        when(mKeyguardUpdateMonitor.isUserInLockdown(NOTIF_USER_ID)).thenReturn(true);
+
+        // THEN filter out the entry
+        assertTrue(mKeyguardNotificationCoordinator.mNotifFilter.shouldFilterOut(mEntry, 0));
+    }
+
+    @Test
+    public void publicMode_settingsDisallow() {
+        // GIVEN an 'unfiltered-keyguard-showing' state
+        setupUnfilteredState();
+
+        // WHEN the notification's user is in public mode and settings are configured to disallow
+        // notifications in public mode
+        when(mLockscreenUserManager.isLockscreenPublicMode(NOTIF_USER_ID)).thenReturn(true);
+        when(mLockscreenUserManager.userAllowsNotificationsInPublic(NOTIF_USER_ID))
+                .thenReturn(false);
+
+        // THEN filter out the entry
+        assertTrue(mKeyguardNotificationCoordinator.mNotifFilter.shouldFilterOut(mEntry, 0));
+    }
+
+    @Test
+    public void publicMode_notifDisallowed() {
+        // GIVEN an 'unfiltered-keyguard-showing' state
+        setupUnfilteredState();
+
+        // WHEN the notification's user is in public mode and settings are configured to disallow
+        // notifications in public mode
+        when(mLockscreenUserManager.isLockscreenPublicMode(CURR_USER_ID)).thenReturn(true);
+        mEntry.setRanking(new RankingBuilder()
+                .setKey(mEntry.getKey())
+                .setVisibilityOverride(VISIBILITY_SECRET).build());
+
+        // THEN filter out the entry
+        assertTrue(mKeyguardNotificationCoordinator.mNotifFilter.shouldFilterOut(mEntry, 0));
+    }
+
+    @Test
+    public void doesNotExceedThresholdToShow() {
+        // GIVEN an 'unfiltered-keyguard-showing' state
+        setupUnfilteredState();
+
+        // WHEN the notification doesn't exceed the threshold to show on the lockscreen
+        mEntry.setRanking(new RankingBuilder()
+                .setKey(mEntry.getKey())
+                .setImportance(IMPORTANCE_MIN)
+                .build());
+
+        // THEN filter out the entry
+        assertTrue(mKeyguardNotificationCoordinator.mNotifFilter.shouldFilterOut(mEntry, 0));
+    }
+
+    @Test
+    public void summaryExceedsThresholdToShow() {
+        // GIVEN an 'unfiltered-keyguard-showing' state
+        setupUnfilteredState();
+
+        // WHEN the notification doesn't exceed the threshold to show on the lockscreen
+        // but its summary does
+        mEntry.setRanking(new RankingBuilder()
+                .setKey(mEntry.getKey())
+                .setImportance(IMPORTANCE_MIN)
+                .build());
+
+        final NotificationEntry summary = new NotificationEntryBuilder().build();
+        summary.setRanking(new RankingBuilder()
+                .setKey(summary.getKey())
+                .setImportance(IMPORTANCE_HIGH)
+                .build());
+        final GroupEntry group = new GroupEntry(mEntry.getSbn().getGroupKey());
+        group.setSummary(summary);
+        mEntry.setParent(group);
+
+        // THEN don't filter out the entry
+        assertFalse(mKeyguardNotificationCoordinator.mNotifFilter.shouldFilterOut(mEntry, 0));
+    }
+
+    /**
+     * setup a state where the notification will not be filtered by the
+     * KeyguardNotificationCoordinator when the keyguard is showing.
+     */
+    private void setupUnfilteredState() {
+        // notification is for current profile
+        when(mLockscreenUserManager.isCurrentProfile(NOTIF_USER_ID)).thenReturn(true);
+
+        // keyguard is showing
+        when(mKeyguardStateController.isShowing()).thenReturn(true);
+
+        // show notifications on the lockscreen
+        when(mLockscreenUserManager.shouldShowLockscreenNotifications()).thenReturn(true);
+
+        // neither the current user nor the notification's user is in lockdown
+        when(mLockscreenUserManager.getCurrentUserId()).thenReturn(CURR_USER_ID);
+        when(mKeyguardUpdateMonitor.isUserInLockdown(NOTIF_USER_ID)).thenReturn(false);
+        when(mKeyguardUpdateMonitor.isUserInLockdown(CURR_USER_ID)).thenReturn(false);
+
+        // not in public mode
+        when(mLockscreenUserManager.isLockscreenPublicMode(CURR_USER_ID)).thenReturn(false);
+        when(mLockscreenUserManager.isLockscreenPublicMode(NOTIF_USER_ID)).thenReturn(false);
+
+        // entry's ranking - should show on all lockscreens
+        // + priority of the notification exceeds the threshold to be shown on the lockscreen
+        mEntry.setRanking(new RankingBuilder()
+                .setKey(mEntry.getKey())
+                .setVisibilityOverride(VISIBILITY_PUBLIC)
+                .setImportance(IMPORTANCE_HIGH)
+                .build());
+
+        // settings allows notifications in public mode
+        when(mLockscreenUserManager.userAllowsNotificationsInPublic(CURR_USER_ID)).thenReturn(true);
+        when(mLockscreenUserManager.userAllowsNotificationsInPublic(NOTIF_USER_ID))
+                .thenReturn(true);
+
+        // notification doesn't have a summary
+    }
+}