Merge "Add logging for unseen notif filtering" into udc-dev
diff --git a/packages/SystemUI/src/com/android/systemui/log/dagger/LogModule.java b/packages/SystemUI/src/com/android/systemui/log/dagger/LogModule.java
index 7edb378..077ee02 100644
--- a/packages/SystemUI/src/com/android/systemui/log/dagger/LogModule.java
+++ b/packages/SystemUI/src/com/android/systemui/log/dagger/LogModule.java
@@ -136,6 +136,14 @@
         return factory.create("NotifRemoteInputLog", 50 /* maxSize */, false /* systrace */);
     }
 
+    /** Provides a logging buffer for all logs related to unseen notifications. */
+    @Provides
+    @SysUISingleton
+    @UnseenNotificationLog
+    public static LogBuffer provideUnseenNotificationLogBuffer(LogBufferFactory factory) {
+        return factory.create("UnseenNotifLog", 20 /* maxSize */, false /* systrace */);
+    }
+
     /** Provides a logging buffer for all logs related to the data layer of notifications. */
     @Provides
     @SysUISingleton
diff --git a/packages/SystemUI/src/com/android/systemui/log/dagger/UnseenNotificationLog.java b/packages/SystemUI/src/com/android/systemui/log/dagger/UnseenNotificationLog.java
new file mode 100644
index 0000000..5c2321b
--- /dev/null
+++ b/packages/SystemUI/src/com/android/systemui/log/dagger/UnseenNotificationLog.java
@@ -0,0 +1,33 @@
+/*
+ * Copyright (C) 2023 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.log.dagger;
+
+import static java.lang.annotation.RetentionPolicy.RUNTIME;
+
+import com.android.systemui.plugins.log.LogBuffer;
+
+import java.lang.annotation.Documented;
+import java.lang.annotation.Retention;
+
+import javax.inject.Qualifier;
+
+/** A {@link LogBuffer} for unseen notification related messages. */
+@Qualifier
+@Documented
+@Retention(RUNTIME)
+public @interface UnseenNotificationLog {
+}
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 4cbbefe..2fa070c 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
@@ -21,8 +21,10 @@
 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.data.repository.KeyguardTransitionRepository
 import com.android.systemui.keyguard.shared.model.KeyguardState
@@ -42,8 +44,14 @@
 import com.android.systemui.statusbar.notification.interruption.KeyguardNotificationVisibilityProvider
 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
+import kotlin.time.Duration.Companion.seconds
 import kotlinx.coroutines.CoroutineDispatcher
 import kotlinx.coroutines.CoroutineScope
 import kotlinx.coroutines.ExperimentalCoroutinesApi
@@ -57,13 +65,11 @@
 import kotlinx.coroutines.flow.first
 import kotlinx.coroutines.flow.flowOn
 import kotlinx.coroutines.flow.map
+import kotlinx.coroutines.flow.onEach
 import kotlinx.coroutines.flow.onStart
 import kotlinx.coroutines.flow.transformLatest
 import kotlinx.coroutines.launch
 import kotlinx.coroutines.yield
-import javax.inject.Inject
-import kotlin.time.Duration
-import kotlin.time.Duration.Companion.seconds
 
 /**
  * Filters low priority and privacy-sensitive notifications from the lockscreen, and hides section
@@ -74,17 +80,19 @@
 @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 keyguardTransitionRepository: KeyguardTransitionRepository,
+    private val logger: KeyguardCoordinatorLogger,
     private val notifPipelineFlags: NotifPipelineFlags,
     @Application private val scope: CoroutineScope,
     private val sectionHeaderVisibilityProvider: SectionHeaderVisibilityProvider,
     private val secureSettings: SecureSettings,
     private val seenNotifsProvider: SeenNotificationsProviderImpl,
     private val statusBarStateController: StatusBarStateController,
-) : Coordinator {
+) : Coordinator, Dumpable {
 
     private val unseenNotifications = mutableSetOf<NotificationEntry>()
     private var unseenFilterEnabled = false
@@ -103,6 +111,7 @@
         pipeline.addCollectionListener(collectionListener)
         scope.launch { trackUnseenNotificationsWhileUnlocked() }
         scope.launch { invalidateWhenUnseenSettingChanges() }
+        dumpManager.registerDumpable(this)
     }
 
     private suspend fun trackUnseenNotificationsWhileUnlocked() {
@@ -122,14 +131,16 @@
                         // If the screen is turning off, stop tracking, but if that transition is
                         // cancelled, then start again.
                         emitAll(
-                            keyguardTransitionRepository.transitions
-                                .map { step -> !step.isScreenTurningOff }
+                            keyguardTransitionRepository.transitions.map { step ->
+                                !step.isScreenTurningOff
+                            }
                         )
                     }
                 }
                 // Prevent double emit of `false` caused by transition to AOD, followed by keyguard
                 // showing
                 .distinctUntilChanged()
+                .onEach { trackingUnseen -> logger.logTrackingUnseen(trackingUnseen) }
 
         // Use collectLatest so that trackUnseenNotifications() is cancelled when the keyguard is
         // showing again
@@ -140,9 +151,11 @@
                 // set when unlocked
                 awaitTimeSpentNotDozing(SEEN_TIMEOUT)
                 clearUnseenOnBeginTracking = true
+                logger.logSeenOnLockscreen()
             } else {
                 if (clearUnseenOnBeginTracking) {
                     clearUnseenOnBeginTracking = false
+                    logger.logAllMarkedSeenOnUnlock()
                     unseenNotifications.clear()
                 }
                 unseenNotifFilter.invalidateList("keyguard no longer showing")
@@ -166,6 +179,8 @@
             .first()
     }
 
+    // Track "unseen" notifications, marking them as seen when either shade is expanded or the
+    // notification becomes heads up.
     private suspend fun trackUnseenNotifications() {
         coroutineScope {
             launch { clearUnseenNotificationsWhenShadeIsExpanded() }
@@ -179,6 +194,7 @@
             // keyguard transition and not the user expanding the shade
             yield()
             if (isExpanded) {
+                logger.logShadeExpanded()
                 unseenNotifications.clear()
             }
         }
@@ -190,6 +206,7 @@
             .forEach { unseenNotifications.remove(it) }
         headsUpManager.headsUpEvents.collect { (entry, isHun) ->
             if (isHun) {
+                logger.logUnseenHun(entry.key)
                 unseenNotifications.remove(entry)
             }
         }
@@ -231,6 +248,7 @@
                 if (
                     keyguardRepository.isKeyguardShowing() || !statusBarStateController.isExpanded
                 ) {
+                    logger.logUnseenAdded(entry.key)
                     unseenNotifications.add(entry)
                 }
             }
@@ -239,12 +257,15 @@
                 if (
                     keyguardRepository.isKeyguardShowing() || !statusBarStateController.isExpanded
                 ) {
+                    logger.logUnseenUpdated(entry.key)
                     unseenNotifications.add(entry)
                 }
             }
 
             override fun onEntryRemoved(entry: NotificationEntry, reason: Int) {
-                unseenNotifications.remove(entry)
+                if (unseenNotifications.remove(entry)) {
+                    logger.logUnseenRemoved(entry.key)
+                }
             }
         }
 
@@ -272,6 +293,7 @@
                 }.also { hasFiltered -> hasFilteredAnyNotifs = hasFilteredAnyNotifs || hasFiltered }
 
             override fun onCleanup() {
+                logger.logProviderHasFilteredOutSeenNotifs(hasFilteredAnyNotifs)
                 seenNotifsProvider.hasFilteredOutSeenNotifications = hasFilteredAnyNotifs
                 hasFilteredAnyNotifs = false
             }
@@ -306,11 +328,25 @@
         sectionHeaderVisibilityProvider.sectionHeadersVisible = showSections
     }
 
+    override fun dump(pw: PrintWriter, args: Array<out String>) =
+        with(pw.asIndenting()) {
+            println(
+                "seenNotifsProvider.hasFilteredOutSeenNotifications=" +
+                    seenNotifsProvider.hasFilteredOutSeenNotifications
+            )
+            println("unseen notifications:")
+            indentIfPossible {
+                for (notification in unseenNotifications) {
+                    println(notification.key)
+                }
+            }
+        }
+
     companion object {
         private const val TAG = "KeyguardCoordinator"
         private val SEEN_TIMEOUT = 5.seconds
     }
 }
 
-private val TransitionStep.isScreenTurningOff: Boolean get() =
-    transitionState == TransitionState.STARTED && to != KeyguardState.GONE
\ No newline at end of file
+private val TransitionStep.isScreenTurningOff: Boolean
+    get() = transitionState == TransitionState.STARTED && to != KeyguardState.GONE
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/coordinator/KeyguardCoordinatorLogger.kt b/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/coordinator/KeyguardCoordinatorLogger.kt
new file mode 100644
index 0000000..6503a64
--- /dev/null
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/coordinator/KeyguardCoordinatorLogger.kt
@@ -0,0 +1,99 @@
+/*
+ * Copyright (C) 2023 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.systemui.statusbar.notification.collection.coordinator
+
+import com.android.systemui.log.dagger.UnseenNotificationLog
+import com.android.systemui.plugins.log.LogBuffer
+import com.android.systemui.plugins.log.LogLevel
+import javax.inject.Inject
+
+private const val TAG = "KeyguardCoordinator"
+
+class KeyguardCoordinatorLogger
+@Inject
+constructor(
+    @UnseenNotificationLog private val buffer: LogBuffer,
+) {
+    fun logSeenOnLockscreen() =
+        buffer.log(
+            TAG,
+            LogLevel.DEBUG,
+            "Notifications on lockscreen will be marked as seen when unlocked."
+        )
+
+    fun logTrackingUnseen(trackingUnseen: Boolean) =
+        buffer.log(
+            TAG,
+            LogLevel.DEBUG,
+            messageInitializer = { bool1 = trackingUnseen },
+            messagePrinter = { "${if (bool1) "Start" else "Stop"} tracking unseen notifications." },
+        )
+
+    fun logAllMarkedSeenOnUnlock() =
+        buffer.log(
+            TAG,
+            LogLevel.DEBUG,
+            "Notifications have been marked as seen now that device is unlocked."
+        )
+
+    fun logShadeExpanded() =
+        buffer.log(
+            TAG,
+            LogLevel.DEBUG,
+            "Notifications have been marked as seen due to shade expansion."
+        )
+
+    fun logUnseenAdded(key: String) =
+        buffer.log(
+            TAG,
+            LogLevel.DEBUG,
+            messageInitializer = { str1 = key },
+            messagePrinter = { "Unseen notif added: $str1" },
+        )
+
+    fun logUnseenUpdated(key: String) =
+        buffer.log(
+            TAG,
+            LogLevel.DEBUG,
+            messageInitializer = { str1 = key },
+            messagePrinter = { "Unseen notif updated: $str1" },
+        )
+
+    fun logUnseenRemoved(key: String) =
+        buffer.log(
+            TAG,
+            LogLevel.DEBUG,
+            messageInitializer = { str1 = key },
+            messagePrinter = { "Unseen notif removed: $str1" },
+        )
+
+    fun logProviderHasFilteredOutSeenNotifs(hasFilteredAnyNotifs: Boolean) =
+        buffer.log(
+            TAG,
+            LogLevel.DEBUG,
+            messageInitializer = { bool1 = hasFilteredAnyNotifs },
+            messagePrinter = { "UI showing unseen filter treatment: $bool1" },
+        )
+
+    fun logUnseenHun(key: String) =
+        buffer.log(
+            TAG,
+            LogLevel.DEBUG,
+            messageInitializer = { str1 = key },
+            messagePrinter = { "Unseen notif has become heads up: $str1" },
+        )
+}
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 8f07f8d..c3f5123 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
@@ -24,6 +24,7 @@
 import androidx.test.filters.SmallTest
 import com.android.systemui.SysuiTestCase
 import com.android.systemui.coroutines.advanceTimeBy
+import com.android.systemui.dump.DumpManager
 import com.android.systemui.keyguard.data.repository.FakeKeyguardRepository
 import com.android.systemui.keyguard.data.repository.FakeKeyguardTransitionRepository
 import com.android.systemui.keyguard.shared.model.KeyguardState
@@ -380,10 +381,12 @@
         val keyguardCoordinator =
             KeyguardCoordinator(
                 testDispatcher,
+                mock<DumpManager>(),
                 headsUpManager,
                 keyguardNotifVisibilityProvider,
                 keyguardRepository,
                 keyguardTransitionRepository,
+                mock<KeyguardCoordinatorLogger>(),
                 notifPipelineFlags,
                 testScope.backgroundScope,
                 sectionHeaderVisibilityProvider,