Merge "Split the unseen logic out of the KeyguardCoordinator" into main
diff --git a/packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/notification/collection/coordinator/OriginalUnseenKeyguardCoordinatorTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/notification/collection/coordinator/OriginalUnseenKeyguardCoordinatorTest.kt
new file mode 100644
index 0000000..6ddc074
--- /dev/null
+++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/notification/collection/coordinator/OriginalUnseenKeyguardCoordinatorTest.kt
@@ -0,0 +1,683 @@
+/*
+ * Copyright (C) 2022 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.
+ */
+@file:OptIn(ExperimentalCoroutinesApi::class)
+
+package com.android.systemui.statusbar.notification.collection.coordinator
+
+import android.app.Notification
+import android.os.UserHandle
+import android.platform.test.flag.junit.FlagsParameterization
+import android.provider.Settings
+import androidx.test.filters.SmallTest
+import com.android.systemui.SysuiTestCase
+import com.android.systemui.dump.DumpManager
+import com.android.systemui.flags.andSceneContainer
+import com.android.systemui.keyguard.data.repository.FakeKeyguardRepository
+import com.android.systemui.keyguard.data.repository.fakeKeyguardTransitionRepository
+import com.android.systemui.keyguard.domain.interactor.keyguardTransitionInteractor
+import com.android.systemui.keyguard.shared.model.KeyguardState
+import com.android.systemui.keyguard.shared.model.TransitionStep
+import com.android.systemui.kosmos.Kosmos
+import com.android.systemui.log.logcatLogBuffer
+import com.android.systemui.plugins.statusbar.StatusBarStateController
+import com.android.systemui.scene.data.repository.Idle
+import com.android.systemui.scene.data.repository.setTransition
+import com.android.systemui.scene.shared.model.Scenes
+import com.android.systemui.statusbar.notification.collection.GroupEntryBuilder
+import com.android.systemui.statusbar.notification.collection.NotifPipeline
+import com.android.systemui.statusbar.notification.collection.NotificationEntryBuilder
+import com.android.systemui.statusbar.notification.collection.listbuilder.pluggable.NotifFilter
+import com.android.systemui.statusbar.notification.collection.listbuilder.pluggable.Pluggable
+import com.android.systemui.statusbar.notification.collection.notifcollection.NotifCollectionListener
+import com.android.systemui.statusbar.notification.data.repository.ActiveNotificationListRepository
+import com.android.systemui.statusbar.notification.domain.interactor.SeenNotificationsInteractor
+import com.android.systemui.statusbar.notification.row.ExpandableNotificationRow
+import com.android.systemui.statusbar.policy.HeadsUpManager
+import com.android.systemui.statusbar.policy.OnHeadsUpChangedListener
+import com.android.systemui.util.settings.FakeSettings
+import com.google.common.truth.Truth.assertThat
+import kotlin.time.Duration.Companion.seconds
+import kotlinx.coroutines.CoroutineScope
+import kotlinx.coroutines.ExperimentalCoroutinesApi
+import kotlinx.coroutines.test.TestCoroutineScheduler
+import kotlinx.coroutines.test.TestScope
+import kotlinx.coroutines.test.UnconfinedTestDispatcher
+import kotlinx.coroutines.test.runTest
+import org.junit.Test
+import org.junit.runner.RunWith
+import org.mockito.ArgumentMatchers.same
+import org.mockito.kotlin.any
+import org.mockito.kotlin.argumentCaptor
+import org.mockito.kotlin.mock
+import org.mockito.kotlin.never
+import org.mockito.kotlin.verify
+import org.mockito.kotlin.whenever
+import platform.test.runner.parameterized.ParameterizedAndroidJunit4
+import platform.test.runner.parameterized.Parameters
+
+@SmallTest
+@RunWith(ParameterizedAndroidJunit4::class)
+class OriginalUnseenKeyguardCoordinatorTest(flags: FlagsParameterization) : SysuiTestCase() {
+
+    private val kosmos = Kosmos()
+
+    private val headsUpManager: HeadsUpManager = mock()
+    private val keyguardRepository = FakeKeyguardRepository()
+    private val keyguardTransitionRepository = kosmos.fakeKeyguardTransitionRepository
+    private val notifPipeline: NotifPipeline = mock()
+    private val statusBarStateController: StatusBarStateController = mock()
+
+    init {
+        mSetFlagsRule.setFlagsParameterization(flags)
+    }
+
+    @Test
+    fun unseenFilterSuppressesSeenNotifWhileKeyguardShowing() {
+        // GIVEN: Keyguard is not showing, shade is expanded, and a notification is present
+        keyguardRepository.setKeyguardShowing(false)
+        whenever(statusBarStateController.isExpanded).thenReturn(true)
+        runKeyguardCoordinatorTest {
+            val fakeEntry = NotificationEntryBuilder().build()
+            collectionListener.onEntryAdded(fakeEntry)
+
+            // WHEN: The keyguard is now showing
+            keyguardRepository.setKeyguardShowing(true)
+            testScheduler.runCurrent()
+
+            // THEN: The notification is recognized as "seen" and is filtered out.
+            assertThat(unseenFilter.shouldFilterOut(fakeEntry, 0L)).isTrue()
+
+            // WHEN: The keyguard goes away
+            keyguardRepository.setKeyguardShowing(false)
+            testScheduler.runCurrent()
+
+            // THEN: The notification is shown regardless
+            assertThat(unseenFilter.shouldFilterOut(fakeEntry, 0L)).isFalse()
+        }
+    }
+
+    @Test
+    fun unseenFilterStopsMarkingSeenNotifWhenTransitionToAod() {
+        // GIVEN: Keyguard is not showing, shade is not expanded, and a notification is present
+        keyguardRepository.setKeyguardShowing(false)
+        whenever(statusBarStateController.isExpanded).thenReturn(false)
+        runKeyguardCoordinatorTest {
+            val fakeEntry = NotificationEntryBuilder().build()
+            collectionListener.onEntryAdded(fakeEntry)
+
+            // WHEN: The device transitions to AOD
+            keyguardTransitionRepository.sendTransitionSteps(
+                from = KeyguardState.GONE,
+                to = KeyguardState.AOD,
+                this.testScheduler,
+            )
+            testScheduler.runCurrent()
+
+            // THEN: We are no longer listening for shade expansions
+            verify(statusBarStateController, never()).addCallback(any())
+        }
+    }
+
+    @Test
+    fun unseenFilter_headsUpMarkedAsSeen() {
+        // GIVEN: Keyguard is not showing, shade is not expanded
+        keyguardRepository.setKeyguardShowing(false)
+        whenever(statusBarStateController.isExpanded).thenReturn(false)
+        runKeyguardCoordinatorTest {
+            kosmos.setTransition(
+                sceneTransition = Idle(Scenes.Gone),
+                stateTransition = TransitionStep(KeyguardState.LOCKSCREEN, KeyguardState.GONE)
+            )
+
+            // WHEN: A notification is posted
+            val fakeEntry = NotificationEntryBuilder().build()
+            collectionListener.onEntryAdded(fakeEntry)
+
+            // WHEN: That notification is heads up
+            onHeadsUpChangedListener.onHeadsUpStateChanged(fakeEntry, /* isHeadsUp= */ true)
+            testScheduler.runCurrent()
+
+            // WHEN: The keyguard is now showing
+            keyguardRepository.setKeyguardShowing(true)
+            kosmos.setTransition(
+                sceneTransition = Idle(Scenes.Lockscreen),
+                stateTransition = TransitionStep(KeyguardState.GONE, KeyguardState.AOD)
+            )
+
+            // THEN: The notification is recognized as "seen" and is filtered out.
+            assertThat(unseenFilter.shouldFilterOut(fakeEntry, 0L)).isTrue()
+
+            // WHEN: The keyguard goes away
+            keyguardRepository.setKeyguardShowing(false)
+            kosmos.setTransition(
+                sceneTransition = Idle(Scenes.Gone),
+                stateTransition = TransitionStep(KeyguardState.AOD, KeyguardState.GONE)
+            )
+
+            // THEN: The notification is shown regardless
+            assertThat(unseenFilter.shouldFilterOut(fakeEntry, 0L)).isFalse()
+        }
+    }
+
+    @Test
+    fun unseenFilterDoesNotSuppressSeenOngoingNotifWhileKeyguardShowing() {
+        // GIVEN: Keyguard is not showing, shade is expanded, and an ongoing notification is present
+        keyguardRepository.setKeyguardShowing(false)
+        whenever(statusBarStateController.isExpanded).thenReturn(true)
+        runKeyguardCoordinatorTest {
+            val fakeEntry =
+                NotificationEntryBuilder()
+                    .setNotification(Notification.Builder(mContext, "id").setOngoing(true).build())
+                    .build()
+            collectionListener.onEntryAdded(fakeEntry)
+
+            // WHEN: The keyguard is now showing
+            keyguardRepository.setKeyguardShowing(true)
+            testScheduler.runCurrent()
+
+            // THEN: The notification is recognized as "ongoing" and is not filtered out.
+            assertThat(unseenFilter.shouldFilterOut(fakeEntry, 0L)).isFalse()
+        }
+    }
+
+    @Test
+    fun unseenFilterDoesNotSuppressSeenMediaNotifWhileKeyguardShowing() {
+        // GIVEN: Keyguard is not showing, shade is expanded, and a media notification is present
+        keyguardRepository.setKeyguardShowing(false)
+        whenever(statusBarStateController.isExpanded).thenReturn(true)
+        runKeyguardCoordinatorTest {
+            val fakeEntry =
+                NotificationEntryBuilder().build().apply {
+                    row =
+                        mock<ExpandableNotificationRow>().apply {
+                            whenever(isMediaRow).thenReturn(true)
+                        }
+                }
+            collectionListener.onEntryAdded(fakeEntry)
+
+            // WHEN: The keyguard is now showing
+            keyguardRepository.setKeyguardShowing(true)
+            testScheduler.runCurrent()
+
+            // THEN: The notification is recognized as "media" and is not filtered out.
+            assertThat(unseenFilter.shouldFilterOut(fakeEntry, 0L)).isFalse()
+        }
+    }
+
+    @Test
+    fun unseenFilterUpdatesSeenProviderWhenSuppressing() {
+        // GIVEN: Keyguard is not showing, shade is expanded, and a notification is present
+        keyguardRepository.setKeyguardShowing(false)
+        whenever(statusBarStateController.isExpanded).thenReturn(true)
+        runKeyguardCoordinatorTest {
+            val fakeEntry = NotificationEntryBuilder().build()
+            collectionListener.onEntryAdded(fakeEntry)
+
+            // WHEN: The keyguard is now showing
+            keyguardRepository.setKeyguardShowing(true)
+            testScheduler.runCurrent()
+
+            // THEN: The notification is recognized as "seen" and is filtered out.
+            assertThat(unseenFilter.shouldFilterOut(fakeEntry, 0L)).isTrue()
+
+            // WHEN: The filter is cleaned up
+            unseenFilter.onCleanup()
+
+            // THEN: The SeenNotificationProvider has been updated to reflect the suppression
+            assertThat(seenNotificationsInteractor.hasFilteredOutSeenNotifications.value).isTrue()
+        }
+    }
+
+    @Test
+    fun unseenFilterInvalidatesWhenSettingChanges() {
+        // GIVEN: Keyguard is not showing, and shade is expanded
+        keyguardRepository.setKeyguardShowing(false)
+        whenever(statusBarStateController.isExpanded).thenReturn(true)
+        runKeyguardCoordinatorTest {
+            // GIVEN: A notification is present
+            val fakeEntry = NotificationEntryBuilder().build()
+            collectionListener.onEntryAdded(fakeEntry)
+
+            // GIVEN: The setting for filtering unseen notifications is disabled
+            showOnlyUnseenNotifsOnKeyguardSetting = false
+
+            // GIVEN: The pipeline has registered the unseen filter for invalidation
+            val invalidationListener: Pluggable.PluggableListener<NotifFilter> = mock()
+            unseenFilter.setInvalidationListener(invalidationListener)
+
+            // WHEN: The keyguard is now showing
+            keyguardRepository.setKeyguardShowing(true)
+            testScheduler.runCurrent()
+
+            // THEN: The notification is not filtered out
+            assertThat(unseenFilter.shouldFilterOut(fakeEntry, 0L)).isFalse()
+
+            // WHEN: The secure setting is changed
+            showOnlyUnseenNotifsOnKeyguardSetting = true
+
+            // THEN: The pipeline is invalidated
+            verify(invalidationListener).onPluggableInvalidated(same(unseenFilter), any())
+
+            // THEN: The notification is recognized as "seen" and is filtered out.
+            assertThat(unseenFilter.shouldFilterOut(fakeEntry, 0L)).isTrue()
+        }
+    }
+
+    @Test
+    fun unseenFilterAllowsNewNotif() {
+        // GIVEN: Keyguard is showing, no notifications present
+        keyguardRepository.setKeyguardShowing(true)
+        runKeyguardCoordinatorTest {
+            // WHEN: A new notification is posted
+            val fakeEntry = NotificationEntryBuilder().build()
+            collectionListener.onEntryAdded(fakeEntry)
+
+            // THEN: The notification is recognized as "unseen" and is not filtered out.
+            assertThat(unseenFilter.shouldFilterOut(fakeEntry, 0L)).isFalse()
+        }
+    }
+
+    @Test
+    fun unseenFilterSeenGroupSummaryWithUnseenChild() {
+        // GIVEN: Keyguard is not showing, shade is expanded, and a notification is present
+        keyguardRepository.setKeyguardShowing(false)
+        whenever(statusBarStateController.isExpanded).thenReturn(true)
+        runKeyguardCoordinatorTest {
+            // WHEN: A new notification is posted
+            val fakeSummary = NotificationEntryBuilder().build()
+            val fakeChild =
+                NotificationEntryBuilder()
+                    .setGroup(context, "group")
+                    .setGroupSummary(context, false)
+                    .build()
+            GroupEntryBuilder().setSummary(fakeSummary).addChild(fakeChild).build()
+
+            collectionListener.onEntryAdded(fakeSummary)
+            collectionListener.onEntryAdded(fakeChild)
+
+            // WHEN: Keyguard is now showing, both notifications are marked as seen
+            keyguardRepository.setKeyguardShowing(true)
+            testScheduler.runCurrent()
+
+            // WHEN: The child notification is now unseen
+            collectionListener.onEntryUpdated(fakeChild)
+
+            // THEN: The summary is not filtered out, because the child is unseen
+            assertThat(unseenFilter.shouldFilterOut(fakeSummary, 0L)).isFalse()
+        }
+    }
+
+    @Test
+    fun unseenNotificationIsMarkedAsSeenWhenKeyguardGoesAway() {
+        // GIVEN: Keyguard is showing, not dozing, unseen notification is present
+        keyguardRepository.setKeyguardShowing(true)
+        keyguardRepository.setIsDozing(false)
+        runKeyguardCoordinatorTest {
+            val fakeEntry = NotificationEntryBuilder().build()
+            collectionListener.onEntryAdded(fakeEntry)
+            keyguardTransitionRepository.sendTransitionSteps(
+                from = KeyguardState.AOD,
+                to = KeyguardState.LOCKSCREEN,
+                this.testScheduler,
+            )
+            testScheduler.runCurrent()
+
+            // WHEN: five seconds have passed
+            testScheduler.advanceTimeBy(5.seconds)
+            testScheduler.runCurrent()
+
+            // WHEN: Keyguard is no longer showing
+            keyguardRepository.setKeyguardShowing(false)
+            kosmos.setTransition(
+                sceneTransition = Idle(Scenes.Gone),
+                stateTransition = TransitionStep(KeyguardState.LOCKSCREEN, KeyguardState.GONE)
+            )
+
+            // WHEN: Keyguard is shown again
+            keyguardRepository.setKeyguardShowing(true)
+            kosmos.setTransition(
+                sceneTransition = Idle(Scenes.Lockscreen),
+                stateTransition = TransitionStep(KeyguardState.GONE, KeyguardState.AOD)
+            )
+
+            // THEN: The notification is now recognized as "seen" and is filtered out.
+            assertThat(unseenFilter.shouldFilterOut(fakeEntry, 0L)).isTrue()
+        }
+    }
+
+    @Test
+    fun unseenNotificationIsNotMarkedAsSeenIfShadeNotExpanded() {
+        // GIVEN: Keyguard is showing, unseen notification is present
+        keyguardRepository.setKeyguardShowing(true)
+        runKeyguardCoordinatorTest {
+            keyguardTransitionRepository.sendTransitionSteps(
+                from = KeyguardState.GONE,
+                to = KeyguardState.LOCKSCREEN,
+                this.testScheduler,
+            )
+            val fakeEntry = NotificationEntryBuilder().build()
+            collectionListener.onEntryAdded(fakeEntry)
+
+            // WHEN: Keyguard is no longer showing
+            keyguardRepository.setKeyguardShowing(false)
+            keyguardTransitionRepository.sendTransitionSteps(
+                from = KeyguardState.LOCKSCREEN,
+                to = KeyguardState.GONE,
+                this.testScheduler,
+            )
+
+            // WHEN: Keyguard is shown again
+            keyguardRepository.setKeyguardShowing(true)
+            testScheduler.runCurrent()
+
+            // THEN: The notification is not recognized as "seen" and is not filtered out.
+            assertThat(unseenFilter.shouldFilterOut(fakeEntry, 0L)).isFalse()
+        }
+    }
+
+    @Test
+    fun unseenNotificationIsNotMarkedAsSeenIfNotOnKeyguardLongEnough() {
+        // GIVEN: Keyguard is showing, not dozing, unseen notification is present
+        keyguardRepository.setKeyguardShowing(true)
+        keyguardRepository.setIsDozing(false)
+        runKeyguardCoordinatorTest {
+            kosmos.setTransition(
+                sceneTransition = Idle(Scenes.Lockscreen),
+                stateTransition = TransitionStep(KeyguardState.GONE, KeyguardState.LOCKSCREEN)
+            )
+            val firstEntry = NotificationEntryBuilder().setId(1).build()
+            collectionListener.onEntryAdded(firstEntry)
+            testScheduler.runCurrent()
+
+            // WHEN: one second has passed
+            testScheduler.advanceTimeBy(1.seconds)
+            testScheduler.runCurrent()
+
+            // WHEN: another unseen notification is posted
+            val secondEntry = NotificationEntryBuilder().setId(2).build()
+            collectionListener.onEntryAdded(secondEntry)
+            testScheduler.runCurrent()
+
+            // WHEN: four more seconds have passed
+            testScheduler.advanceTimeBy(4.seconds)
+            testScheduler.runCurrent()
+
+            // WHEN: the keyguard is no longer showing
+            keyguardRepository.setKeyguardShowing(false)
+            kosmos.setTransition(
+                sceneTransition = Idle(Scenes.Gone),
+                stateTransition = TransitionStep(KeyguardState.LOCKSCREEN, KeyguardState.GONE)
+            )
+
+            // WHEN: Keyguard is shown again
+            keyguardRepository.setKeyguardShowing(true)
+            kosmos.setTransition(
+                sceneTransition = Idle(Scenes.Lockscreen),
+                stateTransition = TransitionStep(KeyguardState.GONE, KeyguardState.LOCKSCREEN)
+            )
+
+            // THEN: The first notification is considered seen and is filtered out.
+            assertThat(unseenFilter.shouldFilterOut(firstEntry, 0L)).isTrue()
+
+            // THEN: The second notification is still considered unseen and is not filtered out
+            assertThat(unseenFilter.shouldFilterOut(secondEntry, 0L)).isFalse()
+        }
+    }
+
+    @Test
+    fun unseenNotificationOnKeyguardNotMarkedAsSeenIfRemovedAfterThreshold() {
+        // GIVEN: Keyguard is showing, not dozing
+        keyguardRepository.setKeyguardShowing(true)
+        keyguardRepository.setIsDozing(false)
+        runKeyguardCoordinatorTest {
+            keyguardTransitionRepository.sendTransitionSteps(
+                from = KeyguardState.GONE,
+                to = KeyguardState.LOCKSCREEN,
+                this.testScheduler,
+            )
+            testScheduler.runCurrent()
+
+            // WHEN: a new notification is posted
+            val entry = NotificationEntryBuilder().setId(1).build()
+            collectionListener.onEntryAdded(entry)
+            testScheduler.runCurrent()
+
+            // WHEN: five more seconds have passed
+            testScheduler.advanceTimeBy(5.seconds)
+            testScheduler.runCurrent()
+
+            // WHEN: the notification is removed
+            collectionListener.onEntryRemoved(entry, 0)
+            testScheduler.runCurrent()
+
+            // WHEN: the notification is re-posted
+            collectionListener.onEntryAdded(entry)
+            testScheduler.runCurrent()
+
+            // WHEN: one more second has passed
+            testScheduler.advanceTimeBy(1.seconds)
+            testScheduler.runCurrent()
+
+            // WHEN: the keyguard is no longer showing
+            keyguardRepository.setKeyguardShowing(false)
+            keyguardTransitionRepository.sendTransitionSteps(
+                from = KeyguardState.LOCKSCREEN,
+                to = KeyguardState.GONE,
+                this.testScheduler,
+            )
+            testScheduler.runCurrent()
+
+            // WHEN: Keyguard is shown again
+            keyguardRepository.setKeyguardShowing(true)
+            keyguardTransitionRepository.sendTransitionSteps(
+                from = KeyguardState.GONE,
+                to = KeyguardState.LOCKSCREEN,
+                this.testScheduler,
+            )
+            testScheduler.runCurrent()
+
+            // THEN: The notification is considered unseen and is not filtered out.
+            assertThat(unseenFilter.shouldFilterOut(entry, 0L)).isFalse()
+        }
+    }
+
+    @Test
+    fun unseenNotificationOnKeyguardNotMarkedAsSeenIfRemovedBeforeThreshold() {
+        // GIVEN: Keyguard is showing, not dozing
+        keyguardRepository.setKeyguardShowing(true)
+        keyguardRepository.setIsDozing(false)
+        runKeyguardCoordinatorTest {
+            keyguardTransitionRepository.sendTransitionSteps(
+                from = KeyguardState.GONE,
+                to = KeyguardState.LOCKSCREEN,
+                this.testScheduler,
+            )
+            testScheduler.runCurrent()
+
+            // WHEN: a new notification is posted
+            val entry = NotificationEntryBuilder().setId(1).build()
+            collectionListener.onEntryAdded(entry)
+            testScheduler.runCurrent()
+
+            // WHEN: one second has passed
+            testScheduler.advanceTimeBy(1.seconds)
+            testScheduler.runCurrent()
+
+            // WHEN: the notification is removed
+            collectionListener.onEntryRemoved(entry, 0)
+            testScheduler.runCurrent()
+
+            // WHEN: the notification is re-posted
+            collectionListener.onEntryAdded(entry)
+            testScheduler.runCurrent()
+
+            // WHEN: one more second has passed
+            testScheduler.advanceTimeBy(1.seconds)
+            testScheduler.runCurrent()
+
+            // WHEN: the keyguard is no longer showing
+            keyguardRepository.setKeyguardShowing(false)
+            keyguardTransitionRepository.sendTransitionSteps(
+                from = KeyguardState.LOCKSCREEN,
+                to = KeyguardState.GONE,
+                this.testScheduler,
+            )
+            testScheduler.runCurrent()
+
+            // WHEN: Keyguard is shown again
+            keyguardRepository.setKeyguardShowing(true)
+            keyguardTransitionRepository.sendTransitionSteps(
+                from = KeyguardState.GONE,
+                to = KeyguardState.LOCKSCREEN,
+                this.testScheduler,
+            )
+            testScheduler.runCurrent()
+
+            // THEN: The notification is considered unseen and is not filtered out.
+            assertThat(unseenFilter.shouldFilterOut(entry, 0L)).isFalse()
+        }
+    }
+
+    @Test
+    fun unseenNotificationOnKeyguardNotMarkedAsSeenIfUpdatedBeforeThreshold() {
+        // GIVEN: Keyguard is showing, not dozing
+        keyguardRepository.setKeyguardShowing(true)
+        keyguardRepository.setIsDozing(false)
+        runKeyguardCoordinatorTest {
+            keyguardTransitionRepository.sendTransitionSteps(
+                from = KeyguardState.GONE,
+                to = KeyguardState.LOCKSCREEN,
+                this.testScheduler,
+            )
+            testScheduler.runCurrent()
+
+            // WHEN: a new notification is posted
+            val entry = NotificationEntryBuilder().setId(1).build()
+            collectionListener.onEntryAdded(entry)
+            testScheduler.runCurrent()
+
+            // WHEN: one second has passed
+            testScheduler.advanceTimeBy(1.seconds)
+            testScheduler.runCurrent()
+
+            // WHEN: the notification is updated
+            collectionListener.onEntryUpdated(entry)
+            testScheduler.runCurrent()
+
+            // WHEN: four more seconds have passed
+            testScheduler.advanceTimeBy(4.seconds)
+            testScheduler.runCurrent()
+
+            // WHEN: the keyguard is no longer showing
+            keyguardRepository.setKeyguardShowing(false)
+            keyguardTransitionRepository.sendTransitionSteps(
+                from = KeyguardState.LOCKSCREEN,
+                to = KeyguardState.GONE,
+                this.testScheduler,
+            )
+            testScheduler.runCurrent()
+
+            // WHEN: Keyguard is shown again
+            keyguardRepository.setKeyguardShowing(true)
+            keyguardTransitionRepository.sendTransitionSteps(
+                from = KeyguardState.GONE,
+                to = KeyguardState.LOCKSCREEN,
+                this.testScheduler,
+            )
+            testScheduler.runCurrent()
+
+            // THEN: The notification is considered unseen and is not filtered out.
+            assertThat(unseenFilter.shouldFilterOut(entry, 0L)).isFalse()
+        }
+    }
+
+    private fun runKeyguardCoordinatorTest(
+        testBlock: suspend KeyguardCoordinatorTestScope.() -> Unit
+    ) {
+        val testDispatcher = UnconfinedTestDispatcher()
+        val testScope = TestScope(testDispatcher)
+        val fakeSettings =
+            FakeSettings().apply {
+                putInt(Settings.Secure.LOCK_SCREEN_SHOW_ONLY_UNSEEN_NOTIFICATIONS, 1)
+            }
+        val seenNotificationsInteractor =
+            SeenNotificationsInteractor(ActiveNotificationListRepository())
+        val keyguardCoordinator =
+            OriginalUnseenKeyguardCoordinator(
+                testDispatcher,
+                mock<DumpManager>(),
+                headsUpManager,
+                keyguardRepository,
+                kosmos.keyguardTransitionInteractor,
+                KeyguardCoordinatorLogger(logcatLogBuffer()),
+                testScope.backgroundScope,
+                fakeSettings,
+                seenNotificationsInteractor,
+                statusBarStateController,
+            )
+        keyguardCoordinator.attach(notifPipeline)
+        testScope.runTest {
+            KeyguardCoordinatorTestScope(
+                    keyguardCoordinator,
+                    testScope,
+                    seenNotificationsInteractor,
+                    fakeSettings,
+                )
+                .testBlock()
+        }
+    }
+
+    private inner class KeyguardCoordinatorTestScope(
+        private val keyguardCoordinator: OriginalUnseenKeyguardCoordinator,
+        private val scope: TestScope,
+        val seenNotificationsInteractor: SeenNotificationsInteractor,
+        private val fakeSettings: FakeSettings,
+    ) : CoroutineScope by scope {
+        val testScheduler: TestCoroutineScheduler
+            get() = scope.testScheduler
+
+        val unseenFilter: NotifFilter
+            get() = keyguardCoordinator.unseenNotifFilter
+
+        val collectionListener: NotifCollectionListener =
+            argumentCaptor { verify(notifPipeline).addCollectionListener(capture()) }.lastValue
+
+        val onHeadsUpChangedListener: OnHeadsUpChangedListener
+            get() = argumentCaptor { verify(headsUpManager).addListener(capture()) }.lastValue
+
+        var showOnlyUnseenNotifsOnKeyguardSetting: Boolean
+            get() =
+                fakeSettings.getIntForUser(
+                    Settings.Secure.LOCK_SCREEN_SHOW_ONLY_UNSEEN_NOTIFICATIONS,
+                    UserHandle.USER_CURRENT,
+                ) == 1
+            set(value) {
+                fakeSettings.putIntForUser(
+                    Settings.Secure.LOCK_SCREEN_SHOW_ONLY_UNSEEN_NOTIFICATIONS,
+                    if (value) 1 else 2,
+                    UserHandle.USER_CURRENT,
+                )
+            }
+    }
+
+    companion object {
+        @JvmStatic
+        @Parameters(name = "{0}")
+        fun getParams(): List<FlagsParameterization> {
+            return FlagsParameterization.allCombinationsOf().andSceneContainer()
+        }
+    }
+}
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/coordinator/KeyguardCoordinator.kt b/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/coordinator/KeyguardCoordinator.kt
index 55c6790..b1b2a65 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/coordinator/KeyguardCoordinator.kt
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/coordinator/KeyguardCoordinator.kt
@@ -16,62 +16,15 @@
 
 package com.android.systemui.statusbar.notification.collection.coordinator
 
-import android.app.NotificationManager
-import android.os.UserHandle
-import android.provider.Settings
-import androidx.annotation.VisibleForTesting
-import com.android.systemui.Dumpable
-import com.android.systemui.dagger.qualifiers.Application
-import com.android.systemui.dagger.qualifiers.Background
-import com.android.systemui.dump.DumpManager
-import com.android.systemui.keyguard.data.repository.KeyguardRepository
-import com.android.systemui.keyguard.domain.interactor.KeyguardTransitionInteractor
-import com.android.systemui.keyguard.shared.model.KeyguardState
 import com.android.systemui.plugins.statusbar.StatusBarStateController
-import com.android.systemui.scene.shared.model.Scenes
 import com.android.systemui.statusbar.StatusBarState
-import com.android.systemui.statusbar.expansionChanges
-import com.android.systemui.statusbar.notification.collection.GroupEntry
-import com.android.systemui.statusbar.notification.collection.ListEntry
 import com.android.systemui.statusbar.notification.collection.NotifPipeline
 import com.android.systemui.statusbar.notification.collection.NotificationEntry
 import com.android.systemui.statusbar.notification.collection.coordinator.dagger.CoordinatorScope
 import com.android.systemui.statusbar.notification.collection.listbuilder.pluggable.NotifFilter
-import com.android.systemui.statusbar.notification.collection.listbuilder.pluggable.NotifPromoter
-import com.android.systemui.statusbar.notification.collection.listbuilder.pluggable.NotifSectioner
-import com.android.systemui.statusbar.notification.collection.notifcollection.NotifCollectionListener
 import com.android.systemui.statusbar.notification.collection.provider.SectionHeaderVisibilityProvider
-import com.android.systemui.statusbar.notification.domain.interactor.SeenNotificationsInteractor
 import com.android.systemui.statusbar.notification.interruption.KeyguardNotificationVisibilityProvider
-import com.android.systemui.statusbar.notification.shared.NotificationMinimalismPrototype
-import com.android.systemui.statusbar.notification.stack.BUCKET_TOP_ONGOING
-import com.android.systemui.statusbar.notification.stack.BUCKET_TOP_UNSEEN
-import com.android.systemui.statusbar.policy.HeadsUpManager
-import com.android.systemui.statusbar.policy.headsUpEvents
-import com.android.systemui.util.asIndenting
-import com.android.systemui.util.indentIfPossible
-import com.android.systemui.util.settings.SecureSettings
-import com.android.systemui.util.settings.SettingsProxyExt.observerFlow
-import java.io.PrintWriter
 import javax.inject.Inject
-import kotlin.time.Duration.Companion.seconds
-import kotlinx.coroutines.CoroutineDispatcher
-import kotlinx.coroutines.CoroutineScope
-import kotlinx.coroutines.Job
-import kotlinx.coroutines.coroutineScope
-import kotlinx.coroutines.delay
-import kotlinx.coroutines.flow.Flow
-import kotlinx.coroutines.flow.MutableSharedFlow
-import kotlinx.coroutines.flow.collectLatest
-import kotlinx.coroutines.flow.conflate
-import kotlinx.coroutines.flow.distinctUntilChanged
-import kotlinx.coroutines.flow.flowOf
-import kotlinx.coroutines.flow.flowOn
-import kotlinx.coroutines.flow.map
-import kotlinx.coroutines.flow.onEach
-import kotlinx.coroutines.flow.onStart
-import kotlinx.coroutines.launch
-import kotlinx.coroutines.yield
 
 /**
  * Filters low priority and privacy-sensitive notifications from the lockscreen, and hides section
@@ -82,24 +35,10 @@
 class KeyguardCoordinator
 @Inject
 constructor(
-    @Background private val bgDispatcher: CoroutineDispatcher,
-    private val dumpManager: DumpManager,
-    private val headsUpManager: HeadsUpManager,
     private val keyguardNotificationVisibilityProvider: KeyguardNotificationVisibilityProvider,
-    private val keyguardRepository: KeyguardRepository,
-    private val keyguardTransitionInteractor: KeyguardTransitionInteractor,
-    private val logger: KeyguardCoordinatorLogger,
-    @Application private val scope: CoroutineScope,
     private val sectionHeaderVisibilityProvider: SectionHeaderVisibilityProvider,
-    private val secureSettings: SecureSettings,
-    private val seenNotificationsInteractor: SeenNotificationsInteractor,
     private val statusBarStateController: StatusBarStateController,
-) : Coordinator, Dumpable {
-
-    private val unseenNotifications = mutableSetOf<NotificationEntry>()
-    private val unseenEntryAdded = MutableSharedFlow<NotificationEntry>(extraBufferCapacity = 1)
-    private val unseenEntryRemoved = MutableSharedFlow<NotificationEntry>(extraBufferCapacity = 1)
-    private var unseenFilterEnabled = false
+) : Coordinator {
 
     override fun attach(pipeline: NotifPipeline) {
         setupInvalidateNotifListCallbacks()
@@ -107,385 +46,14 @@
         pipeline.addFinalizeFilter(notifFilter)
         keyguardNotificationVisibilityProvider.addOnStateChangedListener(::invalidateListFromFilter)
         updateSectionHeadersVisibility()
-        attachUnseenFilter(pipeline)
     }
 
-    private fun attachUnseenFilter(pipeline: NotifPipeline) {
-        if (NotificationMinimalismPrototype.V2.isEnabled) {
-            pipeline.addPromoter(unseenNotifPromoter)
-            pipeline.addOnBeforeTransformGroupsListener(::pickOutTopUnseenNotifs)
-        }
-        pipeline.addFinalizeFilter(unseenNotifFilter)
-        pipeline.addCollectionListener(collectionListener)
-        scope.launch { trackUnseenFilterSettingChanges() }
-        dumpManager.registerDumpable(this)
-    }
-
-    private suspend fun trackSeenNotifications() {
-        // Whether or not keyguard is visible (or occluded).
-        val isKeyguardPresent: Flow<Boolean> =
-            keyguardTransitionInteractor
-                .transitionValue(
-                    scene = Scenes.Gone,
-                    stateWithoutSceneContainer = KeyguardState.GONE,
-                )
-                .map { it == 0f }
-                .distinctUntilChanged()
-                .onEach { trackingUnseen -> logger.logTrackingUnseen(trackingUnseen) }
-
-        // Separately track seen notifications while the device is locked, applying once the device
-        // is unlocked.
-        val notificationsSeenWhileLocked = mutableSetOf<NotificationEntry>()
-
-        // Use [collectLatest] to cancel any running jobs when [trackingUnseen] changes.
-        isKeyguardPresent.collectLatest { isKeyguardPresent: Boolean ->
-            if (isKeyguardPresent) {
-                // Keyguard is not gone, notifications need to be visible for a certain threshold
-                // before being marked as seen
-                trackSeenNotificationsWhileLocked(notificationsSeenWhileLocked)
-            } else {
-                // Mark all seen-while-locked notifications as seen for real.
-                if (notificationsSeenWhileLocked.isNotEmpty()) {
-                    unseenNotifications.removeAll(notificationsSeenWhileLocked)
-                    logger.logAllMarkedSeenOnUnlock(
-                        seenCount = notificationsSeenWhileLocked.size,
-                        remainingUnseenCount = unseenNotifications.size
-                    )
-                    notificationsSeenWhileLocked.clear()
-                }
-                unseenNotifFilter.invalidateList("keyguard no longer showing")
-                // Keyguard is gone, notifications can be immediately marked as seen when they
-                // become visible.
-                trackSeenNotificationsWhileUnlocked()
-            }
-        }
-    }
-
-    /**
-     * Keep [notificationsSeenWhileLocked] updated to represent which notifications have actually
-     * been "seen" while the device is on the keyguard.
-     */
-    private suspend fun trackSeenNotificationsWhileLocked(
-        notificationsSeenWhileLocked: MutableSet<NotificationEntry>,
-    ) = coroutineScope {
-        // Remove removed notifications from the set
-        launch {
-            unseenEntryRemoved.collect { entry ->
-                if (notificationsSeenWhileLocked.remove(entry)) {
-                    logger.logRemoveSeenOnLockscreen(entry)
-                }
-            }
-        }
-        // Use collectLatest so that the timeout delay is cancelled if the device enters doze, and
-        // is restarted when doze ends.
-        keyguardRepository.isDozing.collectLatest { isDozing ->
-            if (!isDozing) {
-                trackSeenNotificationsWhileLockedAndNotDozing(notificationsSeenWhileLocked)
-            }
-        }
-    }
-
-    /**
-     * Keep [notificationsSeenWhileLocked] updated to represent which notifications have actually
-     * been "seen" while the device is on the keyguard and not dozing. Any new and existing unseen
-     * notifications are not marked as seen until they are visible for the [SEEN_TIMEOUT] duration.
-     */
-    private suspend fun trackSeenNotificationsWhileLockedAndNotDozing(
-        notificationsSeenWhileLocked: MutableSet<NotificationEntry>
-    ) = coroutineScope {
-        // All child tracking jobs will be cancelled automatically when this is cancelled.
-        val trackingJobsByEntry = mutableMapOf<NotificationEntry, Job>()
-
-        /**
-         * Wait for the user to spend enough time on the lock screen before removing notification
-         * from unseen set upon unlock.
-         */
-        suspend fun trackSeenDurationThreshold(entry: NotificationEntry) {
-            if (notificationsSeenWhileLocked.remove(entry)) {
-                logger.logResetSeenOnLockscreen(entry)
-            }
-            delay(SEEN_TIMEOUT)
-            notificationsSeenWhileLocked.add(entry)
-            trackingJobsByEntry.remove(entry)
-            logger.logSeenOnLockscreen(entry)
-        }
-
-        /** Stop any unseen tracking when a notification is removed. */
-        suspend fun stopTrackingRemovedNotifs(): Nothing =
-            unseenEntryRemoved.collect { entry ->
-                trackingJobsByEntry.remove(entry)?.let {
-                    it.cancel()
-                    logger.logStopTrackingLockscreenSeenDuration(entry)
-                }
-            }
-
-        /** Start tracking new notifications when they are posted. */
-        suspend fun trackNewUnseenNotifs(): Nothing = coroutineScope {
-            unseenEntryAdded.collect { entry ->
-                logger.logTrackingLockscreenSeenDuration(entry)
-                // If this is an update, reset the tracking.
-                trackingJobsByEntry[entry]?.let {
-                    it.cancel()
-                    logger.logResetSeenOnLockscreen(entry)
-                }
-                trackingJobsByEntry[entry] = launch { trackSeenDurationThreshold(entry) }
-            }
-        }
-
-        // Start tracking for all notifications that are currently unseen.
-        logger.logTrackingLockscreenSeenDuration(unseenNotifications)
-        unseenNotifications.forEach { entry ->
-            trackingJobsByEntry[entry] = launch { trackSeenDurationThreshold(entry) }
-        }
-
-        launch { trackNewUnseenNotifs() }
-        launch { stopTrackingRemovedNotifs() }
-    }
-
-    // Track "seen" notifications, marking them as such when either shade is expanded or the
-    // notification becomes heads up.
-    private suspend fun trackSeenNotificationsWhileUnlocked() {
-        coroutineScope {
-            launch { clearUnseenNotificationsWhenShadeIsExpanded() }
-            launch { markHeadsUpNotificationsAsSeen() }
-        }
-    }
-
-    private suspend fun clearUnseenNotificationsWhenShadeIsExpanded() {
-        statusBarStateController.expansionChanges.collectLatest { isExpanded ->
-            // Give keyguard events time to propagate, in case this expansion is part of the
-            // keyguard transition and not the user expanding the shade
-            yield()
-            if (isExpanded) {
-                logger.logShadeExpanded()
-                unseenNotifications.clear()
-            }
-        }
-    }
-
-    private suspend fun markHeadsUpNotificationsAsSeen() {
-        headsUpManager.allEntries
-            .filter { it.isRowPinned }
-            .forEach { unseenNotifications.remove(it) }
-        headsUpManager.headsUpEvents.collect { (entry, isHun) ->
-            if (isHun) {
-                logger.logUnseenHun(entry.key)
-                unseenNotifications.remove(entry)
-            }
-        }
-    }
-
-    private fun unseenFeatureEnabled(): Flow<Boolean> {
-        if (
-            NotificationMinimalismPrototype.V1.isEnabled ||
-                NotificationMinimalismPrototype.V2.isEnabled
-        ) {
-            return flowOf(true)
-        }
-        return secureSettings
-            // emit whenever the setting has changed
-            .observerFlow(
-                UserHandle.USER_ALL,
-                Settings.Secure.LOCK_SCREEN_SHOW_ONLY_UNSEEN_NOTIFICATIONS,
-            )
-            // perform a query immediately
-            .onStart { emit(Unit) }
-            // for each change, lookup the new value
-            .map {
-                secureSettings.getIntForUser(
-                    name = Settings.Secure.LOCK_SCREEN_SHOW_ONLY_UNSEEN_NOTIFICATIONS,
-                    def = 0,
-                    userHandle = UserHandle.USER_CURRENT,
-                ) == 1
-            }
-            // don't emit anything if nothing has changed
-            .distinctUntilChanged()
-            // perform lookups on the bg thread pool
-            .flowOn(bgDispatcher)
-            // only track the most recent emission, if events are happening faster than they can be
-            // consumed
-            .conflate()
-    }
-
-    private suspend fun trackUnseenFilterSettingChanges() {
-        unseenFeatureEnabled().collectLatest { setting ->
-            // update local field and invalidate if necessary
-            if (setting != unseenFilterEnabled) {
-                unseenFilterEnabled = setting
-                unseenNotifFilter.invalidateList("unseen setting changed")
-            }
-            // if the setting is enabled, then start tracking and filtering unseen notifications
-            if (setting) {
-                trackSeenNotifications()
-            }
-        }
-    }
-
-    private val collectionListener =
-        object : NotifCollectionListener {
-            override fun onEntryAdded(entry: NotificationEntry) {
-                if (
-                    keyguardRepository.isKeyguardShowing() || !statusBarStateController.isExpanded
-                ) {
-                    logger.logUnseenAdded(entry.key)
-                    unseenNotifications.add(entry)
-                    unseenEntryAdded.tryEmit(entry)
-                }
-            }
-
-            override fun onEntryUpdated(entry: NotificationEntry) {
-                if (
-                    keyguardRepository.isKeyguardShowing() || !statusBarStateController.isExpanded
-                ) {
-                    logger.logUnseenUpdated(entry.key)
-                    unseenNotifications.add(entry)
-                    unseenEntryAdded.tryEmit(entry)
-                }
-            }
-
-            override fun onEntryRemoved(entry: NotificationEntry, reason: Int) {
-                if (unseenNotifications.remove(entry)) {
-                    logger.logUnseenRemoved(entry.key)
-                    unseenEntryRemoved.tryEmit(entry)
-                }
-            }
-        }
-
-    private fun pickOutTopUnseenNotifs(list: List<ListEntry>) {
-        if (NotificationMinimalismPrototype.V2.isUnexpectedlyInLegacyMode()) return
-        // Only ever elevate a top unseen notification on keyguard, not even locked shade
-        if (statusBarStateController.state != StatusBarState.KEYGUARD) {
-            seenNotificationsInteractor.setTopOngoingNotification(null)
-            seenNotificationsInteractor.setTopUnseenNotification(null)
-            return
-        }
-        // On keyguard pick the top-ranked unseen or ongoing notification to elevate
-        val nonSummaryEntries: Sequence<NotificationEntry> =
-            list
-                .asSequence()
-                .flatMap {
-                    when (it) {
-                        is NotificationEntry -> listOfNotNull(it)
-                        is GroupEntry -> it.children
-                        else -> error("unhandled type of $it")
-                    }
-                }
-                .filter { it.importance >= NotificationManager.IMPORTANCE_DEFAULT }
-        seenNotificationsInteractor.setTopOngoingNotification(
-            nonSummaryEntries
-                .filter { ColorizedFgsCoordinator.isRichOngoing(it) }
-                .minByOrNull { it.ranking.rank }
-        )
-        seenNotificationsInteractor.setTopUnseenNotification(
-            nonSummaryEntries
-                .filter { !ColorizedFgsCoordinator.isRichOngoing(it) && it in unseenNotifications }
-                .minByOrNull { it.ranking.rank }
-        )
-    }
-
-    @VisibleForTesting
-    internal val unseenNotifPromoter =
-        object : NotifPromoter("$TAG-unseen") {
-            override fun shouldPromoteToTopLevel(child: NotificationEntry): Boolean =
-                if (NotificationMinimalismPrototype.V2.isUnexpectedlyInLegacyMode()) false
-                else if (!NotificationMinimalismPrototype.V2.ungroupTopUnseen) false
-                else
-                    seenNotificationsInteractor.isTopOngoingNotification(child) ||
-                        seenNotificationsInteractor.isTopUnseenNotification(child)
-        }
-
-    val topOngoingSectioner =
-        object : NotifSectioner("TopOngoing", BUCKET_TOP_ONGOING) {
-            override fun isInSection(entry: ListEntry): Boolean {
-                if (NotificationMinimalismPrototype.V2.isUnexpectedlyInLegacyMode()) return false
-                return entry.anyEntry { notificationEntry ->
-                    seenNotificationsInteractor.isTopOngoingNotification(notificationEntry)
-                }
-            }
-        }
-
-    val topUnseenSectioner =
-        object : NotifSectioner("TopUnseen", BUCKET_TOP_UNSEEN) {
-            override fun isInSection(entry: ListEntry): Boolean {
-                if (NotificationMinimalismPrototype.V2.isUnexpectedlyInLegacyMode()) return false
-                return entry.anyEntry { notificationEntry ->
-                    seenNotificationsInteractor.isTopUnseenNotification(notificationEntry)
-                }
-            }
-        }
-
-    private fun ListEntry.anyEntry(predicate: (NotificationEntry?) -> Boolean) =
-        when {
-            predicate(representativeEntry) -> true
-            this !is GroupEntry -> false
-            else -> children.any(predicate)
-        }
-
-    @VisibleForTesting
-    internal val unseenNotifFilter =
-        object : NotifFilter("$TAG-unseen") {
-
-            var hasFilteredAnyNotifs = false
-
-            /**
-             * Encapsulates a definition of "being on the keyguard". Note that these two definitions
-             * are wildly different: [StatusBarState.KEYGUARD] is when on the lock screen and does
-             * not include shade or occluded states, whereas [KeyguardRepository.isKeyguardShowing]
-             * is any state where the keyguard has not been dismissed, including locked shade and
-             * occluded lock screen.
-             *
-             * Returning false for locked shade and occluded states means that this filter will
-             * allow seen notifications to appear in the locked shade.
-             */
-            private fun isOnKeyguard(): Boolean =
-                if (NotificationMinimalismPrototype.V2.isEnabled) {
-                    false // disable this feature under this prototype
-                } else if (
-                    NotificationMinimalismPrototype.V1.isEnabled &&
-                        NotificationMinimalismPrototype.V1.showOnLockedShade
-                ) {
-                    statusBarStateController.state == StatusBarState.KEYGUARD
-                } else {
-                    keyguardRepository.isKeyguardShowing()
-                }
-
-            override fun shouldFilterOut(entry: NotificationEntry, now: Long): Boolean =
-                when {
-                    // Don't apply filter if the setting is disabled
-                    !unseenFilterEnabled -> false
-                    // Don't apply filter if the keyguard isn't currently showing
-                    !isOnKeyguard() -> false
-                    // Don't apply the filter if the notification is unseen
-                    unseenNotifications.contains(entry) -> false
-                    // Don't apply the filter to (non-promoted) group summaries
-                    //  - summary will be pruned if necessary, depending on if children are filtered
-                    entry.parent?.summary == entry -> false
-                    // Check that the entry satisfies certain characteristics that would bypass the
-                    // filter
-                    shouldIgnoreUnseenCheck(entry) -> false
-                    else -> true
-                }.also { hasFiltered -> hasFilteredAnyNotifs = hasFilteredAnyNotifs || hasFiltered }
-
-            override fun onCleanup() {
-                logger.logProviderHasFilteredOutSeenNotifs(hasFilteredAnyNotifs)
-                seenNotificationsInteractor.setHasFilteredOutSeenNotifications(hasFilteredAnyNotifs)
-                hasFilteredAnyNotifs = false
-            }
-        }
-
     private val notifFilter: NotifFilter =
         object : NotifFilter(TAG) {
             override fun shouldFilterOut(entry: NotificationEntry, now: Long): Boolean =
                 keyguardNotificationVisibilityProvider.shouldHideNotification(entry)
         }
 
-    private fun shouldIgnoreUnseenCheck(entry: NotificationEntry): Boolean =
-        when {
-            entry.isMediaNotification -> true
-            entry.sbn.isOngoing -> true
-            else -> false
-        }
-
     // TODO(b/206118999): merge this class with SensitiveContentCoordinator which also depends on
     //  these same updates
     private fun setupInvalidateNotifListCallbacks() {}
@@ -502,22 +70,7 @@
         sectionHeaderVisibilityProvider.sectionHeadersVisible = showSections
     }
 
-    override fun dump(pw: PrintWriter, args: Array<out String>) =
-        with(pw.asIndenting()) {
-            println(
-                "notificationListInteractor.hasFilteredOutSeenNotifications.value=" +
-                    seenNotificationsInteractor.hasFilteredOutSeenNotifications.value
-            )
-            println("unseen notifications:")
-            indentIfPossible {
-                for (notification in unseenNotifications) {
-                    println(notification.key)
-                }
-            }
-        }
-
     companion object {
         private const val TAG = "KeyguardCoordinator"
-        private val SEEN_TIMEOUT = 5.seconds
     }
 }
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/coordinator/NotifCoordinators.kt b/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/coordinator/NotifCoordinators.kt
index e038982..99327d1 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/coordinator/NotifCoordinators.kt
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/coordinator/NotifCoordinators.kt
@@ -17,7 +17,11 @@
 
 import com.android.systemui.flags.FeatureFlags
 import com.android.systemui.flags.Flags.LOCKSCREEN_WALLPAPER_DREAM_ENABLED
-import com.android.systemui.statusbar.notification.collection.*
+import com.android.systemui.statusbar.notification.collection.NotifPipeline
+import com.android.systemui.statusbar.notification.collection.NotificationClassificationFlag
+import com.android.systemui.statusbar.notification.collection.PipelineDumpable
+import com.android.systemui.statusbar.notification.collection.PipelineDumper
+import com.android.systemui.statusbar.notification.collection.SortBySectionTimeFlag
 import com.android.systemui.statusbar.notification.collection.coordinator.dagger.CoordinatorScope
 import com.android.systemui.statusbar.notification.collection.listbuilder.pluggable.NotifSectioner
 import com.android.systemui.statusbar.notification.collection.provider.SectionStyleProvider
@@ -42,6 +46,7 @@
     hideLocallyDismissedNotifsCoordinator: HideLocallyDismissedNotifsCoordinator,
     hideNotifsForOtherUsersCoordinator: HideNotifsForOtherUsersCoordinator,
     keyguardCoordinator: KeyguardCoordinator,
+    unseenKeyguardCoordinator: OriginalUnseenKeyguardCoordinator,
     rankingCoordinator: RankingCoordinator,
     colorizedFgsCoordinator: ColorizedFgsCoordinator,
     deviceProvisionedCoordinator: DeviceProvisionedCoordinator,
@@ -82,6 +87,7 @@
         mCoordinators.add(hideLocallyDismissedNotifsCoordinator)
         mCoordinators.add(hideNotifsForOtherUsersCoordinator)
         mCoordinators.add(keyguardCoordinator)
+        mCoordinators.add(unseenKeyguardCoordinator)
         mCoordinators.add(rankingCoordinator)
         mCoordinators.add(colorizedFgsCoordinator)
         mCoordinators.add(deviceProvisionedCoordinator)
@@ -115,11 +121,11 @@
 
         // Manually add Ordered Sections
         if (NotificationMinimalismPrototype.V2.isEnabled) {
-            mOrderedSections.add(keyguardCoordinator.topOngoingSectioner) // Top Ongoing
+            mOrderedSections.add(unseenKeyguardCoordinator.topOngoingSectioner) // Top Ongoing
         }
         mOrderedSections.add(headsUpCoordinator.sectioner) // HeadsUp
         if (NotificationMinimalismPrototype.V2.isEnabled) {
-            mOrderedSections.add(keyguardCoordinator.topUnseenSectioner) // Top Unseen
+            mOrderedSections.add(unseenKeyguardCoordinator.topUnseenSectioner) // Top Unseen
         }
         mOrderedSections.add(colorizedFgsCoordinator.sectioner) // ForegroundService
         if (PriorityPeopleSection.isEnabled) {
@@ -131,10 +137,10 @@
         }
         mOrderedSections.add(rankingCoordinator.alertingSectioner) // Alerting
         if (NotificationClassificationFlag.isEnabled) {
-            mOrderedSections.add(bundleCoordinator.newsSectioner);
-            mOrderedSections.add(bundleCoordinator.socialSectioner);
-            mOrderedSections.add(bundleCoordinator.recsSectioner);
-            mOrderedSections.add(bundleCoordinator.promoSectioner);
+            mOrderedSections.add(bundleCoordinator.newsSectioner)
+            mOrderedSections.add(bundleCoordinator.socialSectioner)
+            mOrderedSections.add(bundleCoordinator.recsSectioner)
+            mOrderedSections.add(bundleCoordinator.promoSectioner)
         }
         mOrderedSections.add(rankingCoordinator.silentSectioner) // Silent
         mOrderedSections.add(rankingCoordinator.minimizedSectioner) // Minimized
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/coordinator/OriginalUnseenKeyguardCoordinator.kt b/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/coordinator/OriginalUnseenKeyguardCoordinator.kt
new file mode 100644
index 0000000..5dd1663
--- /dev/null
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/coordinator/OriginalUnseenKeyguardCoordinator.kt
@@ -0,0 +1,490 @@
+/*
+ * Copyright (C) 2022 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 android.annotation.SuppressLint
+import android.app.NotificationManager
+import android.os.UserHandle
+import android.provider.Settings
+import androidx.annotation.VisibleForTesting
+import com.android.systemui.Dumpable
+import com.android.systemui.dagger.qualifiers.Application
+import com.android.systemui.dagger.qualifiers.Background
+import com.android.systemui.dump.DumpManager
+import com.android.systemui.keyguard.data.repository.KeyguardRepository
+import com.android.systemui.keyguard.domain.interactor.KeyguardTransitionInteractor
+import com.android.systemui.keyguard.shared.model.KeyguardState
+import com.android.systemui.plugins.statusbar.StatusBarStateController
+import com.android.systemui.scene.shared.model.Scenes
+import com.android.systemui.statusbar.StatusBarState
+import com.android.systemui.statusbar.expansionChanges
+import com.android.systemui.statusbar.notification.collection.GroupEntry
+import com.android.systemui.statusbar.notification.collection.ListEntry
+import com.android.systemui.statusbar.notification.collection.NotifPipeline
+import com.android.systemui.statusbar.notification.collection.NotificationEntry
+import com.android.systemui.statusbar.notification.collection.coordinator.dagger.CoordinatorScope
+import com.android.systemui.statusbar.notification.collection.listbuilder.pluggable.NotifFilter
+import com.android.systemui.statusbar.notification.collection.listbuilder.pluggable.NotifPromoter
+import com.android.systemui.statusbar.notification.collection.listbuilder.pluggable.NotifSectioner
+import com.android.systemui.statusbar.notification.collection.notifcollection.NotifCollectionListener
+import com.android.systemui.statusbar.notification.domain.interactor.SeenNotificationsInteractor
+import com.android.systemui.statusbar.notification.shared.NotificationMinimalismPrototype
+import com.android.systemui.statusbar.notification.stack.BUCKET_TOP_ONGOING
+import com.android.systemui.statusbar.notification.stack.BUCKET_TOP_UNSEEN
+import com.android.systemui.statusbar.policy.HeadsUpManager
+import com.android.systemui.statusbar.policy.headsUpEvents
+import com.android.systemui.util.asIndenting
+import com.android.systemui.util.indentIfPossible
+import com.android.systemui.util.settings.SecureSettings
+import com.android.systemui.util.settings.SettingsProxyExt.observerFlow
+import java.io.PrintWriter
+import javax.inject.Inject
+import kotlin.time.Duration.Companion.seconds
+import kotlinx.coroutines.CoroutineDispatcher
+import kotlinx.coroutines.CoroutineScope
+import kotlinx.coroutines.Job
+import kotlinx.coroutines.coroutineScope
+import kotlinx.coroutines.delay
+import kotlinx.coroutines.flow.Flow
+import kotlinx.coroutines.flow.MutableSharedFlow
+import kotlinx.coroutines.flow.collectLatest
+import kotlinx.coroutines.flow.conflate
+import kotlinx.coroutines.flow.distinctUntilChanged
+import kotlinx.coroutines.flow.flowOf
+import kotlinx.coroutines.flow.flowOn
+import kotlinx.coroutines.flow.map
+import kotlinx.coroutines.flow.onEach
+import kotlinx.coroutines.flow.onStart
+import kotlinx.coroutines.launch
+import kotlinx.coroutines.yield
+
+/**
+ * Filters low priority and privacy-sensitive notifications from the lockscreen, and hides section
+ * headers on the lockscreen. If enabled, it will also track and hide seen notifications on the
+ * lockscreen.
+ */
+@CoordinatorScope
+@SuppressLint("SharedFlowCreation")
+class OriginalUnseenKeyguardCoordinator
+@Inject
+constructor(
+    @Background private val bgDispatcher: CoroutineDispatcher,
+    private val dumpManager: DumpManager,
+    private val headsUpManager: HeadsUpManager,
+    private val keyguardRepository: KeyguardRepository,
+    private val keyguardTransitionInteractor: KeyguardTransitionInteractor,
+    private val logger: KeyguardCoordinatorLogger,
+    @Application private val scope: CoroutineScope,
+    private val secureSettings: SecureSettings,
+    private val seenNotificationsInteractor: SeenNotificationsInteractor,
+    private val statusBarStateController: StatusBarStateController,
+) : Coordinator, Dumpable {
+
+    private val unseenNotifications = mutableSetOf<NotificationEntry>()
+    private val unseenEntryAdded = MutableSharedFlow<NotificationEntry>(extraBufferCapacity = 1)
+    private val unseenEntryRemoved = MutableSharedFlow<NotificationEntry>(extraBufferCapacity = 1)
+    private var unseenFilterEnabled = false
+
+    override fun attach(pipeline: NotifPipeline) {
+        if (NotificationMinimalismPrototype.V2.isEnabled) {
+            pipeline.addPromoter(unseenNotifPromoter)
+            pipeline.addOnBeforeTransformGroupsListener(::pickOutTopUnseenNotifs)
+        }
+        pipeline.addFinalizeFilter(unseenNotifFilter)
+        pipeline.addCollectionListener(collectionListener)
+        scope.launch { trackUnseenFilterSettingChanges() }
+        dumpManager.registerDumpable(this)
+    }
+
+    private suspend fun trackSeenNotifications() {
+        // Whether or not keyguard is visible (or occluded).
+        val isKeyguardPresentFlow: Flow<Boolean> =
+            keyguardTransitionInteractor
+                .transitionValue(
+                    scene = Scenes.Gone,
+                    stateWithoutSceneContainer = KeyguardState.GONE,
+                )
+                .map { it == 0f }
+                .distinctUntilChanged()
+                .onEach { trackingUnseen -> logger.logTrackingUnseen(trackingUnseen) }
+
+        // Separately track seen notifications while the device is locked, applying once the device
+        // is unlocked.
+        val notificationsSeenWhileLocked = mutableSetOf<NotificationEntry>()
+
+        // Use [collectLatest] to cancel any running jobs when [trackingUnseen] changes.
+        isKeyguardPresentFlow.collectLatest { isKeyguardPresent: Boolean ->
+            if (isKeyguardPresent) {
+                // Keyguard is not gone, notifications need to be visible for a certain threshold
+                // before being marked as seen
+                trackSeenNotificationsWhileLocked(notificationsSeenWhileLocked)
+            } else {
+                // Mark all seen-while-locked notifications as seen for real.
+                if (notificationsSeenWhileLocked.isNotEmpty()) {
+                    unseenNotifications.removeAll(notificationsSeenWhileLocked)
+                    logger.logAllMarkedSeenOnUnlock(
+                        seenCount = notificationsSeenWhileLocked.size,
+                        remainingUnseenCount = unseenNotifications.size
+                    )
+                    notificationsSeenWhileLocked.clear()
+                }
+                unseenNotifFilter.invalidateList("keyguard no longer showing")
+                // Keyguard is gone, notifications can be immediately marked as seen when they
+                // become visible.
+                trackSeenNotificationsWhileUnlocked()
+            }
+        }
+    }
+
+    /**
+     * Keep [notificationsSeenWhileLocked] updated to represent which notifications have actually
+     * been "seen" while the device is on the keyguard.
+     */
+    private suspend fun trackSeenNotificationsWhileLocked(
+        notificationsSeenWhileLocked: MutableSet<NotificationEntry>,
+    ) = coroutineScope {
+        // Remove removed notifications from the set
+        launch {
+            unseenEntryRemoved.collect { entry ->
+                if (notificationsSeenWhileLocked.remove(entry)) {
+                    logger.logRemoveSeenOnLockscreen(entry)
+                }
+            }
+        }
+        // Use collectLatest so that the timeout delay is cancelled if the device enters doze, and
+        // is restarted when doze ends.
+        keyguardRepository.isDozing.collectLatest { isDozing ->
+            if (!isDozing) {
+                trackSeenNotificationsWhileLockedAndNotDozing(notificationsSeenWhileLocked)
+            }
+        }
+    }
+
+    /**
+     * Keep [notificationsSeenWhileLocked] updated to represent which notifications have actually
+     * been "seen" while the device is on the keyguard and not dozing. Any new and existing unseen
+     * notifications are not marked as seen until they are visible for the [SEEN_TIMEOUT] duration.
+     */
+    private suspend fun trackSeenNotificationsWhileLockedAndNotDozing(
+        notificationsSeenWhileLocked: MutableSet<NotificationEntry>
+    ) = coroutineScope {
+        // All child tracking jobs will be cancelled automatically when this is cancelled.
+        val trackingJobsByEntry = mutableMapOf<NotificationEntry, Job>()
+
+        /**
+         * Wait for the user to spend enough time on the lock screen before removing notification
+         * from unseen set upon unlock.
+         */
+        suspend fun trackSeenDurationThreshold(entry: NotificationEntry) {
+            if (notificationsSeenWhileLocked.remove(entry)) {
+                logger.logResetSeenOnLockscreen(entry)
+            }
+            delay(SEEN_TIMEOUT)
+            notificationsSeenWhileLocked.add(entry)
+            trackingJobsByEntry.remove(entry)
+            logger.logSeenOnLockscreen(entry)
+        }
+
+        /** Stop any unseen tracking when a notification is removed. */
+        suspend fun stopTrackingRemovedNotifs(): Nothing =
+            unseenEntryRemoved.collect { entry ->
+                trackingJobsByEntry.remove(entry)?.let {
+                    it.cancel()
+                    logger.logStopTrackingLockscreenSeenDuration(entry)
+                }
+            }
+
+        /** Start tracking new notifications when they are posted. */
+        suspend fun trackNewUnseenNotifs(): Nothing = coroutineScope {
+            unseenEntryAdded.collect { entry ->
+                logger.logTrackingLockscreenSeenDuration(entry)
+                // If this is an update, reset the tracking.
+                trackingJobsByEntry[entry]?.let {
+                    it.cancel()
+                    logger.logResetSeenOnLockscreen(entry)
+                }
+                trackingJobsByEntry[entry] = launch { trackSeenDurationThreshold(entry) }
+            }
+        }
+
+        // Start tracking for all notifications that are currently unseen.
+        logger.logTrackingLockscreenSeenDuration(unseenNotifications)
+        unseenNotifications.forEach { entry ->
+            trackingJobsByEntry[entry] = launch { trackSeenDurationThreshold(entry) }
+        }
+
+        launch { trackNewUnseenNotifs() }
+        launch { stopTrackingRemovedNotifs() }
+    }
+
+    // Track "seen" notifications, marking them as such when either shade is expanded or the
+    // notification becomes heads up.
+    private suspend fun trackSeenNotificationsWhileUnlocked() {
+        coroutineScope {
+            launch { clearUnseenNotificationsWhenShadeIsExpanded() }
+            launch { markHeadsUpNotificationsAsSeen() }
+        }
+    }
+
+    private suspend fun clearUnseenNotificationsWhenShadeIsExpanded() {
+        statusBarStateController.expansionChanges.collectLatest { isExpanded ->
+            // Give keyguard events time to propagate, in case this expansion is part of the
+            // keyguard transition and not the user expanding the shade
+            yield()
+            if (isExpanded) {
+                logger.logShadeExpanded()
+                unseenNotifications.clear()
+            }
+        }
+    }
+
+    private suspend fun markHeadsUpNotificationsAsSeen() {
+        headsUpManager.allEntries
+            .filter { it.isRowPinned }
+            .forEach { unseenNotifications.remove(it) }
+        headsUpManager.headsUpEvents.collect { (entry, isHun) ->
+            if (isHun) {
+                logger.logUnseenHun(entry.key)
+                unseenNotifications.remove(entry)
+            }
+        }
+    }
+
+    private fun unseenFeatureEnabled(): Flow<Boolean> {
+        if (
+            NotificationMinimalismPrototype.V1.isEnabled ||
+                NotificationMinimalismPrototype.V2.isEnabled
+        ) {
+            return flowOf(true)
+        }
+        return secureSettings
+            // emit whenever the setting has changed
+            .observerFlow(
+                UserHandle.USER_ALL,
+                Settings.Secure.LOCK_SCREEN_SHOW_ONLY_UNSEEN_NOTIFICATIONS,
+            )
+            // perform a query immediately
+            .onStart { emit(Unit) }
+            // for each change, lookup the new value
+            .map {
+                secureSettings.getIntForUser(
+                    name = Settings.Secure.LOCK_SCREEN_SHOW_ONLY_UNSEEN_NOTIFICATIONS,
+                    def = 0,
+                    userHandle = UserHandle.USER_CURRENT,
+                ) == 1
+            }
+            // don't emit anything if nothing has changed
+            .distinctUntilChanged()
+            // perform lookups on the bg thread pool
+            .flowOn(bgDispatcher)
+            // only track the most recent emission, if events are happening faster than they can be
+            // consumed
+            .conflate()
+    }
+
+    private suspend fun trackUnseenFilterSettingChanges() {
+        unseenFeatureEnabled().collectLatest { setting ->
+            // update local field and invalidate if necessary
+            if (setting != unseenFilterEnabled) {
+                unseenFilterEnabled = setting
+                unseenNotifFilter.invalidateList("unseen setting changed")
+            }
+            // if the setting is enabled, then start tracking and filtering unseen notifications
+            if (setting) {
+                trackSeenNotifications()
+            }
+        }
+    }
+
+    private val collectionListener =
+        object : NotifCollectionListener {
+            override fun onEntryAdded(entry: NotificationEntry) {
+                if (
+                    keyguardRepository.isKeyguardShowing() || !statusBarStateController.isExpanded
+                ) {
+                    logger.logUnseenAdded(entry.key)
+                    unseenNotifications.add(entry)
+                    unseenEntryAdded.tryEmit(entry)
+                }
+            }
+
+            override fun onEntryUpdated(entry: NotificationEntry) {
+                if (
+                    keyguardRepository.isKeyguardShowing() || !statusBarStateController.isExpanded
+                ) {
+                    logger.logUnseenUpdated(entry.key)
+                    unseenNotifications.add(entry)
+                    unseenEntryAdded.tryEmit(entry)
+                }
+            }
+
+            override fun onEntryRemoved(entry: NotificationEntry, reason: Int) {
+                if (unseenNotifications.remove(entry)) {
+                    logger.logUnseenRemoved(entry.key)
+                    unseenEntryRemoved.tryEmit(entry)
+                }
+            }
+        }
+
+    private fun pickOutTopUnseenNotifs(list: List<ListEntry>) {
+        if (NotificationMinimalismPrototype.V2.isUnexpectedlyInLegacyMode()) return
+        // Only ever elevate a top unseen notification on keyguard, not even locked shade
+        if (statusBarStateController.state != StatusBarState.KEYGUARD) {
+            seenNotificationsInteractor.setTopOngoingNotification(null)
+            seenNotificationsInteractor.setTopUnseenNotification(null)
+            return
+        }
+        // On keyguard pick the top-ranked unseen or ongoing notification to elevate
+        val nonSummaryEntries: Sequence<NotificationEntry> =
+            list
+                .asSequence()
+                .flatMap {
+                    when (it) {
+                        is NotificationEntry -> listOfNotNull(it)
+                        is GroupEntry -> it.children
+                        else -> error("unhandled type of $it")
+                    }
+                }
+                .filter { it.importance >= NotificationManager.IMPORTANCE_DEFAULT }
+        seenNotificationsInteractor.setTopOngoingNotification(
+            nonSummaryEntries
+                .filter { ColorizedFgsCoordinator.isRichOngoing(it) }
+                .minByOrNull { it.ranking.rank }
+        )
+        seenNotificationsInteractor.setTopUnseenNotification(
+            nonSummaryEntries
+                .filter { !ColorizedFgsCoordinator.isRichOngoing(it) && it in unseenNotifications }
+                .minByOrNull { it.ranking.rank }
+        )
+    }
+
+    @VisibleForTesting
+    val unseenNotifPromoter =
+        object : NotifPromoter("$TAG-unseen") {
+            override fun shouldPromoteToTopLevel(child: NotificationEntry): Boolean =
+                if (NotificationMinimalismPrototype.V2.isUnexpectedlyInLegacyMode()) false
+                else if (!NotificationMinimalismPrototype.V2.ungroupTopUnseen) false
+                else
+                    seenNotificationsInteractor.isTopOngoingNotification(child) ||
+                        seenNotificationsInteractor.isTopUnseenNotification(child)
+        }
+
+    val topOngoingSectioner =
+        object : NotifSectioner("TopOngoing", BUCKET_TOP_ONGOING) {
+            override fun isInSection(entry: ListEntry): Boolean {
+                if (NotificationMinimalismPrototype.V2.isUnexpectedlyInLegacyMode()) return false
+                return entry.anyEntry { notificationEntry ->
+                    seenNotificationsInteractor.isTopOngoingNotification(notificationEntry)
+                }
+            }
+        }
+
+    val topUnseenSectioner =
+        object : NotifSectioner("TopUnseen", BUCKET_TOP_UNSEEN) {
+            override fun isInSection(entry: ListEntry): Boolean {
+                if (NotificationMinimalismPrototype.V2.isUnexpectedlyInLegacyMode()) return false
+                return entry.anyEntry { notificationEntry ->
+                    seenNotificationsInteractor.isTopUnseenNotification(notificationEntry)
+                }
+            }
+        }
+
+    private fun ListEntry.anyEntry(predicate: (NotificationEntry?) -> Boolean) =
+        when {
+            predicate(representativeEntry) -> true
+            this !is GroupEntry -> false
+            else -> children.any(predicate)
+        }
+
+    @VisibleForTesting
+    val unseenNotifFilter =
+        object : NotifFilter("$TAG-unseen") {
+
+            var hasFilteredAnyNotifs = false
+
+            /**
+             * Encapsulates a definition of "being on the keyguard". Note that these two definitions
+             * are wildly different: [StatusBarState.KEYGUARD] is when on the lock screen and does
+             * not include shade or occluded states, whereas [KeyguardRepository.isKeyguardShowing]
+             * is any state where the keyguard has not been dismissed, including locked shade and
+             * occluded lock screen.
+             *
+             * Returning false for locked shade and occluded states means that this filter will
+             * allow seen notifications to appear in the locked shade.
+             */
+            private fun isOnKeyguard(): Boolean =
+                if (NotificationMinimalismPrototype.V2.isEnabled) {
+                    false // disable this feature under this prototype
+                } else if (
+                    NotificationMinimalismPrototype.V1.isEnabled &&
+                        NotificationMinimalismPrototype.V1.showOnLockedShade
+                ) {
+                    statusBarStateController.state == StatusBarState.KEYGUARD
+                } else {
+                    keyguardRepository.isKeyguardShowing()
+                }
+
+            override fun shouldFilterOut(entry: NotificationEntry, now: Long): Boolean =
+                when {
+                    // Don't apply filter if the setting is disabled
+                    !unseenFilterEnabled -> false
+                    // Don't apply filter if the keyguard isn't currently showing
+                    !isOnKeyguard() -> false
+                    // Don't apply the filter if the notification is unseen
+                    unseenNotifications.contains(entry) -> false
+                    // Don't apply the filter to (non-promoted) group summaries
+                    //  - summary will be pruned if necessary, depending on if children are filtered
+                    entry.parent?.summary == entry -> false
+                    // Check that the entry satisfies certain characteristics that would bypass the
+                    // filter
+                    shouldIgnoreUnseenCheck(entry) -> false
+                    else -> true
+                }.also { hasFiltered -> hasFilteredAnyNotifs = hasFilteredAnyNotifs || hasFiltered }
+
+            override fun onCleanup() {
+                logger.logProviderHasFilteredOutSeenNotifs(hasFilteredAnyNotifs)
+                seenNotificationsInteractor.setHasFilteredOutSeenNotifications(hasFilteredAnyNotifs)
+                hasFilteredAnyNotifs = false
+            }
+        }
+
+    private fun shouldIgnoreUnseenCheck(entry: NotificationEntry): Boolean =
+        when {
+            entry.isMediaNotification -> true
+            entry.sbn.isOngoing -> true
+            else -> false
+        }
+
+    override fun dump(pw: PrintWriter, args: Array<out String>) =
+        with(pw.asIndenting()) {
+            println(
+                "notificationListInteractor.hasFilteredOutSeenNotifications.value=" +
+                    seenNotificationsInteractor.hasFilteredOutSeenNotifications.value
+            )
+            println("unseen notifications:")
+            indentIfPossible {
+                for (notification in unseenNotifications) {
+                    println(notification.key)
+                }
+            }
+        }
+
+    companion object {
+        private const val TAG = "OriginalUnseenKeyguardCoordinator"
+        private val SEEN_TIMEOUT = 5.seconds
+    }
+}
diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/collection/coordinator/KeyguardCoordinatorTest.kt b/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/collection/coordinator/KeyguardCoordinatorTest.kt
index d87b3e2..4218be2 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/collection/coordinator/KeyguardCoordinatorTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/collection/coordinator/KeyguardCoordinatorTest.kt
@@ -13,88 +13,57 @@
  * See the License for the specific language governing permissions and
  * limitations under the License.
  */
-@file:OptIn(ExperimentalCoroutinesApi::class)
 
 package com.android.systemui.statusbar.notification.collection.coordinator
 
-import android.app.Notification
-import android.os.UserHandle
-import android.platform.test.flag.junit.FlagsParameterization
-import android.provider.Settings
+import androidx.test.ext.junit.runners.AndroidJUnit4
 import androidx.test.filters.SmallTest
 import com.android.systemui.SysuiTestCase
-import com.android.systemui.dump.DumpManager
-import com.android.systemui.flags.andSceneContainer
-import com.android.systemui.keyguard.data.repository.FakeKeyguardRepository
-import com.android.systemui.keyguard.data.repository.fakeKeyguardTransitionRepository
-import com.android.systemui.keyguard.domain.interactor.keyguardTransitionInteractor
-import com.android.systemui.keyguard.shared.model.KeyguardState
-import com.android.systemui.keyguard.shared.model.TransitionStep
-import com.android.systemui.kosmos.Kosmos
-import com.android.systemui.log.logcatLogBuffer
 import com.android.systemui.plugins.statusbar.StatusBarStateController
-import com.android.systemui.scene.data.repository.Idle
-import com.android.systemui.scene.data.repository.setTransition
-import com.android.systemui.scene.shared.model.Scenes
 import com.android.systemui.statusbar.StatusBarState
-import com.android.systemui.statusbar.notification.collection.GroupEntryBuilder
 import com.android.systemui.statusbar.notification.collection.NotifPipeline
-import com.android.systemui.statusbar.notification.collection.NotificationEntryBuilder
-import com.android.systemui.statusbar.notification.collection.listbuilder.pluggable.NotifFilter
-import com.android.systemui.statusbar.notification.collection.listbuilder.pluggable.Pluggable
-import com.android.systemui.statusbar.notification.collection.notifcollection.NotifCollectionListener
 import com.android.systemui.statusbar.notification.collection.provider.SectionHeaderVisibilityProvider
-import com.android.systemui.statusbar.notification.data.repository.ActiveNotificationListRepository
-import com.android.systemui.statusbar.notification.domain.interactor.SeenNotificationsInteractor
 import com.android.systemui.statusbar.notification.interruption.KeyguardNotificationVisibilityProvider
-import com.android.systemui.statusbar.notification.row.ExpandableNotificationRow
-import com.android.systemui.statusbar.policy.HeadsUpManager
-import com.android.systemui.statusbar.policy.OnHeadsUpChangedListener
-import com.android.systemui.util.mockito.any
-import com.android.systemui.util.mockito.eq
-import com.android.systemui.util.mockito.mock
-import com.android.systemui.util.mockito.withArgCaptor
-import com.android.systemui.util.settings.FakeSettings
-import com.google.common.truth.Truth.assertThat
 import java.util.function.Consumer
-import kotlin.time.Duration.Companion.seconds
-import kotlinx.coroutines.CoroutineScope
-import kotlinx.coroutines.ExperimentalCoroutinesApi
-import kotlinx.coroutines.test.TestCoroutineScheduler
-import kotlinx.coroutines.test.TestScope
-import kotlinx.coroutines.test.UnconfinedTestDispatcher
-import kotlinx.coroutines.test.runTest
+import org.junit.Before
 import org.junit.Test
 import org.junit.runner.RunWith
-import org.mockito.ArgumentMatchers.same
-import org.mockito.Mockito.anyString
 import org.mockito.Mockito.clearInvocations
-import org.mockito.Mockito.never
 import org.mockito.Mockito.verify
-import org.mockito.Mockito.`when` as whenever
-import platform.test.runner.parameterized.ParameterizedAndroidJunit4
-import platform.test.runner.parameterized.Parameters
+import org.mockito.kotlin.argumentCaptor
+import org.mockito.kotlin.eq
+import org.mockito.kotlin.mock
+import org.mockito.kotlin.whenever
 
 @SmallTest
-@RunWith(ParameterizedAndroidJunit4::class)
-class KeyguardCoordinatorTest(flags: FlagsParameterization) : SysuiTestCase() {
+@RunWith(AndroidJUnit4::class)
+class KeyguardCoordinatorTest : SysuiTestCase() {
 
-    private val kosmos = Kosmos()
-
-    private val headsUpManager: HeadsUpManager = mock()
     private val keyguardNotifVisibilityProvider: KeyguardNotificationVisibilityProvider = mock()
-    private val keyguardRepository = FakeKeyguardRepository()
-    private val keyguardTransitionRepository = kosmos.fakeKeyguardTransitionRepository
     private val notifPipeline: NotifPipeline = mock()
     private val sectionHeaderVisibilityProvider: SectionHeaderVisibilityProvider = mock()
     private val statusBarStateController: StatusBarStateController = mock()
 
-    init {
-        mSetFlagsRule.setFlagsParameterization(flags)
+    private lateinit var onStateChangeListener: Consumer<String>
+
+    @Before
+    fun setup() {
+        val keyguardCoordinator =
+            KeyguardCoordinator(
+                keyguardNotifVisibilityProvider,
+                sectionHeaderVisibilityProvider,
+                statusBarStateController,
+            )
+        keyguardCoordinator.attach(notifPipeline)
+        onStateChangeListener =
+            argumentCaptor {
+                    verify(keyguardNotifVisibilityProvider).addOnStateChangedListener(capture())
+                }
+                .lastValue
     }
 
     @Test
-    fun testSetSectionHeadersVisibleInShade() = runKeyguardCoordinatorTest {
+    fun testSetSectionHeadersVisibleInShade() {
         clearInvocations(sectionHeaderVisibilityProvider)
         whenever(statusBarStateController.state).thenReturn(StatusBarState.SHADE)
         onStateChangeListener.accept("state change")
@@ -102,617 +71,10 @@
     }
 
     @Test
-    fun testSetSectionHeadersNotVisibleOnKeyguard() = runKeyguardCoordinatorTest {
+    fun testSetSectionHeadersNotVisibleOnKeyguard() {
         clearInvocations(sectionHeaderVisibilityProvider)
         whenever(statusBarStateController.state).thenReturn(StatusBarState.KEYGUARD)
         onStateChangeListener.accept("state change")
         verify(sectionHeaderVisibilityProvider).sectionHeadersVisible = eq(false)
     }
-
-    @Test
-    fun unseenFilterSuppressesSeenNotifWhileKeyguardShowing() {
-        // GIVEN: Keyguard is not showing, shade is expanded, and a notification is present
-        keyguardRepository.setKeyguardShowing(false)
-        whenever(statusBarStateController.isExpanded).thenReturn(true)
-        runKeyguardCoordinatorTest {
-            val fakeEntry = NotificationEntryBuilder().build()
-            collectionListener.onEntryAdded(fakeEntry)
-
-            // WHEN: The keyguard is now showing
-            keyguardRepository.setKeyguardShowing(true)
-            testScheduler.runCurrent()
-
-            // THEN: The notification is recognized as "seen" and is filtered out.
-            assertThat(unseenFilter.shouldFilterOut(fakeEntry, 0L)).isTrue()
-
-            // WHEN: The keyguard goes away
-            keyguardRepository.setKeyguardShowing(false)
-            testScheduler.runCurrent()
-
-            // THEN: The notification is shown regardless
-            assertThat(unseenFilter.shouldFilterOut(fakeEntry, 0L)).isFalse()
-        }
-    }
-
-    @Test
-    fun unseenFilterStopsMarkingSeenNotifWhenTransitionToAod() {
-        // GIVEN: Keyguard is not showing, shade is not expanded, and a notification is present
-        keyguardRepository.setKeyguardShowing(false)
-        whenever(statusBarStateController.isExpanded).thenReturn(false)
-        runKeyguardCoordinatorTest {
-            val fakeEntry = NotificationEntryBuilder().build()
-            collectionListener.onEntryAdded(fakeEntry)
-
-            // WHEN: The device transitions to AOD
-            keyguardTransitionRepository.sendTransitionSteps(
-                from = KeyguardState.GONE,
-                to = KeyguardState.AOD,
-                this.testScheduler,
-            )
-            testScheduler.runCurrent()
-
-            // THEN: We are no longer listening for shade expansions
-            verify(statusBarStateController, never()).addCallback(any())
-        }
-    }
-
-    @Test
-    fun unseenFilter_headsUpMarkedAsSeen() {
-        // GIVEN: Keyguard is not showing, shade is not expanded
-        keyguardRepository.setKeyguardShowing(false)
-        whenever(statusBarStateController.isExpanded).thenReturn(false)
-        runKeyguardCoordinatorTest {
-            kosmos.setTransition(
-                sceneTransition = Idle(Scenes.Gone),
-                stateTransition = TransitionStep(KeyguardState.LOCKSCREEN, KeyguardState.GONE)
-            )
-
-            // WHEN: A notification is posted
-            val fakeEntry = NotificationEntryBuilder().build()
-            collectionListener.onEntryAdded(fakeEntry)
-
-            // WHEN: That notification is heads up
-            onHeadsUpChangedListener.onHeadsUpStateChanged(fakeEntry, /* isHeadsUp= */ true)
-            testScheduler.runCurrent()
-
-            // WHEN: The keyguard is now showing
-            keyguardRepository.setKeyguardShowing(true)
-            kosmos.setTransition(
-                sceneTransition = Idle(Scenes.Lockscreen),
-                stateTransition = TransitionStep(KeyguardState.GONE, KeyguardState.AOD)
-            )
-
-            // THEN: The notification is recognized as "seen" and is filtered out.
-            assertThat(unseenFilter.shouldFilterOut(fakeEntry, 0L)).isTrue()
-
-            // WHEN: The keyguard goes away
-            keyguardRepository.setKeyguardShowing(false)
-            kosmos.setTransition(
-                sceneTransition = Idle(Scenes.Gone),
-                stateTransition = TransitionStep(KeyguardState.AOD, KeyguardState.GONE)
-            )
-
-            // THEN: The notification is shown regardless
-            assertThat(unseenFilter.shouldFilterOut(fakeEntry, 0L)).isFalse()
-        }
-    }
-
-    @Test
-    fun unseenFilterDoesNotSuppressSeenOngoingNotifWhileKeyguardShowing() {
-        // GIVEN: Keyguard is not showing, shade is expanded, and an ongoing notification is present
-        keyguardRepository.setKeyguardShowing(false)
-        whenever(statusBarStateController.isExpanded).thenReturn(true)
-        runKeyguardCoordinatorTest {
-            val fakeEntry =
-                NotificationEntryBuilder()
-                    .setNotification(Notification.Builder(mContext, "id").setOngoing(true).build())
-                    .build()
-            collectionListener.onEntryAdded(fakeEntry)
-
-            // WHEN: The keyguard is now showing
-            keyguardRepository.setKeyguardShowing(true)
-            testScheduler.runCurrent()
-
-            // THEN: The notification is recognized as "ongoing" and is not filtered out.
-            assertThat(unseenFilter.shouldFilterOut(fakeEntry, 0L)).isFalse()
-        }
-    }
-
-    @Test
-    fun unseenFilterDoesNotSuppressSeenMediaNotifWhileKeyguardShowing() {
-        // GIVEN: Keyguard is not showing, shade is expanded, and a media notification is present
-        keyguardRepository.setKeyguardShowing(false)
-        whenever(statusBarStateController.isExpanded).thenReturn(true)
-        runKeyguardCoordinatorTest {
-            val fakeEntry =
-                NotificationEntryBuilder().build().apply {
-                    row =
-                        mock<ExpandableNotificationRow>().apply {
-                            whenever(isMediaRow).thenReturn(true)
-                        }
-                }
-            collectionListener.onEntryAdded(fakeEntry)
-
-            // WHEN: The keyguard is now showing
-            keyguardRepository.setKeyguardShowing(true)
-            testScheduler.runCurrent()
-
-            // THEN: The notification is recognized as "media" and is not filtered out.
-            assertThat(unseenFilter.shouldFilterOut(fakeEntry, 0L)).isFalse()
-        }
-    }
-
-    @Test
-    fun unseenFilterUpdatesSeenProviderWhenSuppressing() {
-        // GIVEN: Keyguard is not showing, shade is expanded, and a notification is present
-        keyguardRepository.setKeyguardShowing(false)
-        whenever(statusBarStateController.isExpanded).thenReturn(true)
-        runKeyguardCoordinatorTest {
-            val fakeEntry = NotificationEntryBuilder().build()
-            collectionListener.onEntryAdded(fakeEntry)
-
-            // WHEN: The keyguard is now showing
-            keyguardRepository.setKeyguardShowing(true)
-            testScheduler.runCurrent()
-
-            // THEN: The notification is recognized as "seen" and is filtered out.
-            assertThat(unseenFilter.shouldFilterOut(fakeEntry, 0L)).isTrue()
-
-            // WHEN: The filter is cleaned up
-            unseenFilter.onCleanup()
-
-            // THEN: The SeenNotificationProvider has been updated to reflect the suppression
-            assertThat(seenNotificationsInteractor.hasFilteredOutSeenNotifications.value).isTrue()
-        }
-    }
-
-    @Test
-    fun unseenFilterInvalidatesWhenSettingChanges() {
-        // GIVEN: Keyguard is not showing, and shade is expanded
-        keyguardRepository.setKeyguardShowing(false)
-        whenever(statusBarStateController.isExpanded).thenReturn(true)
-        runKeyguardCoordinatorTest {
-            // GIVEN: A notification is present
-            val fakeEntry = NotificationEntryBuilder().build()
-            collectionListener.onEntryAdded(fakeEntry)
-
-            // GIVEN: The setting for filtering unseen notifications is disabled
-            showOnlyUnseenNotifsOnKeyguardSetting = false
-
-            // GIVEN: The pipeline has registered the unseen filter for invalidation
-            val invalidationListener: Pluggable.PluggableListener<NotifFilter> = mock()
-            unseenFilter.setInvalidationListener(invalidationListener)
-
-            // WHEN: The keyguard is now showing
-            keyguardRepository.setKeyguardShowing(true)
-            testScheduler.runCurrent()
-
-            // THEN: The notification is not filtered out
-            assertThat(unseenFilter.shouldFilterOut(fakeEntry, 0L)).isFalse()
-
-            // WHEN: The secure setting is changed
-            showOnlyUnseenNotifsOnKeyguardSetting = true
-
-            // THEN: The pipeline is invalidated
-            verify(invalidationListener).onPluggableInvalidated(same(unseenFilter), anyString())
-
-            // THEN: The notification is recognized as "seen" and is filtered out.
-            assertThat(unseenFilter.shouldFilterOut(fakeEntry, 0L)).isTrue()
-        }
-    }
-
-    @Test
-    fun unseenFilterAllowsNewNotif() {
-        // GIVEN: Keyguard is showing, no notifications present
-        keyguardRepository.setKeyguardShowing(true)
-        runKeyguardCoordinatorTest {
-            // WHEN: A new notification is posted
-            val fakeEntry = NotificationEntryBuilder().build()
-            collectionListener.onEntryAdded(fakeEntry)
-
-            // THEN: The notification is recognized as "unseen" and is not filtered out.
-            assertThat(unseenFilter.shouldFilterOut(fakeEntry, 0L)).isFalse()
-        }
-    }
-
-    @Test
-    fun unseenFilterSeenGroupSummaryWithUnseenChild() {
-        // GIVEN: Keyguard is not showing, shade is expanded, and a notification is present
-        keyguardRepository.setKeyguardShowing(false)
-        whenever(statusBarStateController.isExpanded).thenReturn(true)
-        runKeyguardCoordinatorTest {
-            // WHEN: A new notification is posted
-            val fakeSummary = NotificationEntryBuilder().build()
-            val fakeChild =
-                NotificationEntryBuilder()
-                    .setGroup(context, "group")
-                    .setGroupSummary(context, false)
-                    .build()
-            GroupEntryBuilder().setSummary(fakeSummary).addChild(fakeChild).build()
-
-            collectionListener.onEntryAdded(fakeSummary)
-            collectionListener.onEntryAdded(fakeChild)
-
-            // WHEN: Keyguard is now showing, both notifications are marked as seen
-            keyguardRepository.setKeyguardShowing(true)
-            testScheduler.runCurrent()
-
-            // WHEN: The child notification is now unseen
-            collectionListener.onEntryUpdated(fakeChild)
-
-            // THEN: The summary is not filtered out, because the child is unseen
-            assertThat(unseenFilter.shouldFilterOut(fakeSummary, 0L)).isFalse()
-        }
-    }
-
-    @Test
-    fun unseenNotificationIsMarkedAsSeenWhenKeyguardGoesAway() {
-        // GIVEN: Keyguard is showing, not dozing, unseen notification is present
-        keyguardRepository.setKeyguardShowing(true)
-        keyguardRepository.setIsDozing(false)
-        runKeyguardCoordinatorTest {
-            val fakeEntry = NotificationEntryBuilder().build()
-            collectionListener.onEntryAdded(fakeEntry)
-            keyguardTransitionRepository.sendTransitionSteps(
-                from = KeyguardState.AOD,
-                to = KeyguardState.LOCKSCREEN,
-                this.testScheduler,
-            )
-            testScheduler.runCurrent()
-
-            // WHEN: five seconds have passed
-            testScheduler.advanceTimeBy(5.seconds)
-            testScheduler.runCurrent()
-
-            // WHEN: Keyguard is no longer showing
-            keyguardRepository.setKeyguardShowing(false)
-            kosmos.setTransition(
-                sceneTransition = Idle(Scenes.Gone),
-                stateTransition = TransitionStep(KeyguardState.LOCKSCREEN, KeyguardState.GONE)
-            )
-
-            // WHEN: Keyguard is shown again
-            keyguardRepository.setKeyguardShowing(true)
-            kosmos.setTransition(
-                sceneTransition = Idle(Scenes.Lockscreen),
-                stateTransition = TransitionStep(KeyguardState.GONE, KeyguardState.AOD)
-            )
-
-            // THEN: The notification is now recognized as "seen" and is filtered out.
-            assertThat(unseenFilter.shouldFilterOut(fakeEntry, 0L)).isTrue()
-        }
-    }
-
-    @Test
-    fun unseenNotificationIsNotMarkedAsSeenIfShadeNotExpanded() {
-        // GIVEN: Keyguard is showing, unseen notification is present
-        keyguardRepository.setKeyguardShowing(true)
-        runKeyguardCoordinatorTest {
-            keyguardTransitionRepository.sendTransitionSteps(
-                from = KeyguardState.GONE,
-                to = KeyguardState.LOCKSCREEN,
-                this.testScheduler,
-            )
-            val fakeEntry = NotificationEntryBuilder().build()
-            collectionListener.onEntryAdded(fakeEntry)
-
-            // WHEN: Keyguard is no longer showing
-            keyguardRepository.setKeyguardShowing(false)
-            keyguardTransitionRepository.sendTransitionSteps(
-                from = KeyguardState.LOCKSCREEN,
-                to = KeyguardState.GONE,
-                this.testScheduler,
-            )
-
-            // WHEN: Keyguard is shown again
-            keyguardRepository.setKeyguardShowing(true)
-            testScheduler.runCurrent()
-
-            // THEN: The notification is not recognized as "seen" and is not filtered out.
-            assertThat(unseenFilter.shouldFilterOut(fakeEntry, 0L)).isFalse()
-        }
-    }
-
-    @Test
-    fun unseenNotificationIsNotMarkedAsSeenIfNotOnKeyguardLongEnough() {
-        // GIVEN: Keyguard is showing, not dozing, unseen notification is present
-        keyguardRepository.setKeyguardShowing(true)
-        keyguardRepository.setIsDozing(false)
-        runKeyguardCoordinatorTest {
-            kosmos.setTransition(
-                sceneTransition = Idle(Scenes.Lockscreen),
-                stateTransition = TransitionStep(KeyguardState.GONE, KeyguardState.LOCKSCREEN)
-            )
-            val firstEntry = NotificationEntryBuilder().setId(1).build()
-            collectionListener.onEntryAdded(firstEntry)
-            testScheduler.runCurrent()
-
-            // WHEN: one second has passed
-            testScheduler.advanceTimeBy(1.seconds)
-            testScheduler.runCurrent()
-
-            // WHEN: another unseen notification is posted
-            val secondEntry = NotificationEntryBuilder().setId(2).build()
-            collectionListener.onEntryAdded(secondEntry)
-            testScheduler.runCurrent()
-
-            // WHEN: four more seconds have passed
-            testScheduler.advanceTimeBy(4.seconds)
-            testScheduler.runCurrent()
-
-            // WHEN: the keyguard is no longer showing
-            keyguardRepository.setKeyguardShowing(false)
-            kosmos.setTransition(
-                sceneTransition = Idle(Scenes.Gone),
-                stateTransition = TransitionStep(KeyguardState.LOCKSCREEN, KeyguardState.GONE)
-            )
-
-            // WHEN: Keyguard is shown again
-            keyguardRepository.setKeyguardShowing(true)
-            kosmos.setTransition(
-                sceneTransition = Idle(Scenes.Lockscreen),
-                stateTransition = TransitionStep(KeyguardState.GONE, KeyguardState.LOCKSCREEN)
-            )
-
-            // THEN: The first notification is considered seen and is filtered out.
-            assertThat(unseenFilter.shouldFilterOut(firstEntry, 0L)).isTrue()
-
-            // THEN: The second notification is still considered unseen and is not filtered out
-            assertThat(unseenFilter.shouldFilterOut(secondEntry, 0L)).isFalse()
-        }
-    }
-
-    @Test
-    fun unseenNotificationOnKeyguardNotMarkedAsSeenIfRemovedAfterThreshold() {
-        // GIVEN: Keyguard is showing, not dozing
-        keyguardRepository.setKeyguardShowing(true)
-        keyguardRepository.setIsDozing(false)
-        runKeyguardCoordinatorTest {
-            keyguardTransitionRepository.sendTransitionSteps(
-                from = KeyguardState.GONE,
-                to = KeyguardState.LOCKSCREEN,
-                this.testScheduler,
-            )
-            testScheduler.runCurrent()
-
-            // WHEN: a new notification is posted
-            val entry = NotificationEntryBuilder().setId(1).build()
-            collectionListener.onEntryAdded(entry)
-            testScheduler.runCurrent()
-
-            // WHEN: five more seconds have passed
-            testScheduler.advanceTimeBy(5.seconds)
-            testScheduler.runCurrent()
-
-            // WHEN: the notification is removed
-            collectionListener.onEntryRemoved(entry, 0)
-            testScheduler.runCurrent()
-
-            // WHEN: the notification is re-posted
-            collectionListener.onEntryAdded(entry)
-            testScheduler.runCurrent()
-
-            // WHEN: one more second has passed
-            testScheduler.advanceTimeBy(1.seconds)
-            testScheduler.runCurrent()
-
-            // WHEN: the keyguard is no longer showing
-            keyguardRepository.setKeyguardShowing(false)
-            keyguardTransitionRepository.sendTransitionSteps(
-                from = KeyguardState.LOCKSCREEN,
-                to = KeyguardState.GONE,
-                this.testScheduler,
-            )
-            testScheduler.runCurrent()
-
-            // WHEN: Keyguard is shown again
-            keyguardRepository.setKeyguardShowing(true)
-            keyguardTransitionRepository.sendTransitionSteps(
-                from = KeyguardState.GONE,
-                to = KeyguardState.LOCKSCREEN,
-                this.testScheduler,
-            )
-            testScheduler.runCurrent()
-
-            // THEN: The notification is considered unseen and is not filtered out.
-            assertThat(unseenFilter.shouldFilterOut(entry, 0L)).isFalse()
-        }
-    }
-
-    @Test
-    fun unseenNotificationOnKeyguardNotMarkedAsSeenIfRemovedBeforeThreshold() {
-        // GIVEN: Keyguard is showing, not dozing
-        keyguardRepository.setKeyguardShowing(true)
-        keyguardRepository.setIsDozing(false)
-        runKeyguardCoordinatorTest {
-            keyguardTransitionRepository.sendTransitionSteps(
-                from = KeyguardState.GONE,
-                to = KeyguardState.LOCKSCREEN,
-                this.testScheduler,
-            )
-            testScheduler.runCurrent()
-
-            // WHEN: a new notification is posted
-            val entry = NotificationEntryBuilder().setId(1).build()
-            collectionListener.onEntryAdded(entry)
-            testScheduler.runCurrent()
-
-            // WHEN: one second has passed
-            testScheduler.advanceTimeBy(1.seconds)
-            testScheduler.runCurrent()
-
-            // WHEN: the notification is removed
-            collectionListener.onEntryRemoved(entry, 0)
-            testScheduler.runCurrent()
-
-            // WHEN: the notification is re-posted
-            collectionListener.onEntryAdded(entry)
-            testScheduler.runCurrent()
-
-            // WHEN: one more second has passed
-            testScheduler.advanceTimeBy(1.seconds)
-            testScheduler.runCurrent()
-
-            // WHEN: the keyguard is no longer showing
-            keyguardRepository.setKeyguardShowing(false)
-            keyguardTransitionRepository.sendTransitionSteps(
-                from = KeyguardState.LOCKSCREEN,
-                to = KeyguardState.GONE,
-                this.testScheduler,
-            )
-            testScheduler.runCurrent()
-
-            // WHEN: Keyguard is shown again
-            keyguardRepository.setKeyguardShowing(true)
-            keyguardTransitionRepository.sendTransitionSteps(
-                from = KeyguardState.GONE,
-                to = KeyguardState.LOCKSCREEN,
-                this.testScheduler,
-            )
-            testScheduler.runCurrent()
-
-            // THEN: The notification is considered unseen and is not filtered out.
-            assertThat(unseenFilter.shouldFilterOut(entry, 0L)).isFalse()
-        }
-    }
-
-    @Test
-    fun unseenNotificationOnKeyguardNotMarkedAsSeenIfUpdatedBeforeThreshold() {
-        // GIVEN: Keyguard is showing, not dozing
-        keyguardRepository.setKeyguardShowing(true)
-        keyguardRepository.setIsDozing(false)
-        runKeyguardCoordinatorTest {
-            keyguardTransitionRepository.sendTransitionSteps(
-                from = KeyguardState.GONE,
-                to = KeyguardState.LOCKSCREEN,
-                this.testScheduler,
-            )
-            testScheduler.runCurrent()
-
-            // WHEN: a new notification is posted
-            val entry = NotificationEntryBuilder().setId(1).build()
-            collectionListener.onEntryAdded(entry)
-            testScheduler.runCurrent()
-
-            // WHEN: one second has passed
-            testScheduler.advanceTimeBy(1.seconds)
-            testScheduler.runCurrent()
-
-            // WHEN: the notification is updated
-            collectionListener.onEntryUpdated(entry)
-            testScheduler.runCurrent()
-
-            // WHEN: four more seconds have passed
-            testScheduler.advanceTimeBy(4.seconds)
-            testScheduler.runCurrent()
-
-            // WHEN: the keyguard is no longer showing
-            keyguardRepository.setKeyguardShowing(false)
-            keyguardTransitionRepository.sendTransitionSteps(
-                from = KeyguardState.LOCKSCREEN,
-                to = KeyguardState.GONE,
-                this.testScheduler,
-            )
-            testScheduler.runCurrent()
-
-            // WHEN: Keyguard is shown again
-            keyguardRepository.setKeyguardShowing(true)
-            keyguardTransitionRepository.sendTransitionSteps(
-                from = KeyguardState.GONE,
-                to = KeyguardState.LOCKSCREEN,
-                this.testScheduler,
-            )
-            testScheduler.runCurrent()
-
-            // THEN: The notification is considered unseen and is not filtered out.
-            assertThat(unseenFilter.shouldFilterOut(entry, 0L)).isFalse()
-        }
-    }
-
-    private fun runKeyguardCoordinatorTest(
-        testBlock: suspend KeyguardCoordinatorTestScope.() -> Unit
-    ) {
-        val testDispatcher = UnconfinedTestDispatcher()
-        val testScope = TestScope(testDispatcher)
-        val fakeSettings =
-            FakeSettings().apply {
-                putInt(Settings.Secure.LOCK_SCREEN_SHOW_ONLY_UNSEEN_NOTIFICATIONS, 1)
-            }
-        val seenNotificationsInteractor =
-            SeenNotificationsInteractor(ActiveNotificationListRepository())
-        val keyguardCoordinator =
-            KeyguardCoordinator(
-                testDispatcher,
-                mock<DumpManager>(),
-                headsUpManager,
-                keyguardNotifVisibilityProvider,
-                keyguardRepository,
-                kosmos.keyguardTransitionInteractor,
-                KeyguardCoordinatorLogger(logcatLogBuffer()),
-                testScope.backgroundScope,
-                sectionHeaderVisibilityProvider,
-                fakeSettings,
-                seenNotificationsInteractor,
-                statusBarStateController,
-            )
-        keyguardCoordinator.attach(notifPipeline)
-        testScope.runTest(dispatchTimeoutMs = 1.seconds.inWholeMilliseconds) {
-            KeyguardCoordinatorTestScope(
-                    keyguardCoordinator,
-                    testScope,
-                    seenNotificationsInteractor,
-                    fakeSettings,
-                )
-                .testBlock()
-        }
-    }
-
-    private inner class KeyguardCoordinatorTestScope(
-        private val keyguardCoordinator: KeyguardCoordinator,
-        private val scope: TestScope,
-        val seenNotificationsInteractor: SeenNotificationsInteractor,
-        private val fakeSettings: FakeSettings,
-    ) : CoroutineScope by scope {
-        val testScheduler: TestCoroutineScheduler
-            get() = scope.testScheduler
-
-        val onStateChangeListener: Consumer<String> = withArgCaptor {
-            verify(keyguardNotifVisibilityProvider).addOnStateChangedListener(capture())
-        }
-
-        val unseenFilter: NotifFilter
-            get() = keyguardCoordinator.unseenNotifFilter
-
-        val collectionListener: NotifCollectionListener = withArgCaptor {
-            verify(notifPipeline).addCollectionListener(capture())
-        }
-
-        val onHeadsUpChangedListener: OnHeadsUpChangedListener
-            get() = withArgCaptor { verify(headsUpManager).addListener(capture()) }
-
-        val statusBarStateListener: StatusBarStateController.StateListener
-            get() = withArgCaptor { verify(statusBarStateController).addCallback(capture()) }
-
-        var showOnlyUnseenNotifsOnKeyguardSetting: Boolean
-            get() =
-                fakeSettings.getIntForUser(
-                    Settings.Secure.LOCK_SCREEN_SHOW_ONLY_UNSEEN_NOTIFICATIONS,
-                    UserHandle.USER_CURRENT,
-                ) == 1
-            set(value) {
-                fakeSettings.putIntForUser(
-                    Settings.Secure.LOCK_SCREEN_SHOW_ONLY_UNSEEN_NOTIFICATIONS,
-                    if (value) 1 else 2,
-                    UserHandle.USER_CURRENT,
-                )
-            }
-    }
-
-    companion object {
-        @JvmStatic
-        @Parameters(name = "{0}")
-        fun getParams(): List<FlagsParameterization> {
-            return FlagsParameterization.allCombinationsOf().andSceneContainer()
-        }
-    }
 }