Donate to e Foundation | Murena handsets with /e/OS | Own a part of Murena! Learn more

Commit 6a03dcd4 authored by Steve Elliott's avatar Steve Elliott
Browse files

Add logging for unseen notif filtering

Bug: 275032839
Test: manual - inspect bugreport output
Change-Id: Id9b715a758f7357d922078dc3daf5b2be161a4c5
parent 766ec400
Loading
Loading
Loading
Loading
+8 −0
Original line number Original line Diff line number Diff line
@@ -136,6 +136,14 @@ public class LogModule {
        return factory.create("NotifRemoteInputLog", 50 /* maxSize */, false /* systrace */);
        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 a logging buffer for all logs related to the data layer of notifications. */
    @Provides
    @Provides
    @SysUISingleton
    @SysUISingleton
+33 −0
Original line number Original line Diff line number Diff line
/*
 * 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 {
}
+45 −9
Original line number Original line Diff line number Diff line
@@ -21,8 +21,10 @@ package com.android.systemui.statusbar.notification.collection.coordinator
import android.os.UserHandle
import android.os.UserHandle
import android.provider.Settings
import android.provider.Settings
import androidx.annotation.VisibleForTesting
import androidx.annotation.VisibleForTesting
import com.android.systemui.Dumpable
import com.android.systemui.dagger.qualifiers.Application
import com.android.systemui.dagger.qualifiers.Application
import com.android.systemui.dagger.qualifiers.Background
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.KeyguardRepository
import com.android.systemui.keyguard.data.repository.KeyguardTransitionRepository
import com.android.systemui.keyguard.data.repository.KeyguardTransitionRepository
import com.android.systemui.keyguard.shared.model.KeyguardState
import com.android.systemui.keyguard.shared.model.KeyguardState
@@ -42,8 +44,14 @@ import com.android.systemui.statusbar.notification.collection.provider.SeenNotif
import com.android.systemui.statusbar.notification.interruption.KeyguardNotificationVisibilityProvider
import com.android.systemui.statusbar.notification.interruption.KeyguardNotificationVisibilityProvider
import com.android.systemui.statusbar.policy.HeadsUpManager
import com.android.systemui.statusbar.policy.HeadsUpManager
import com.android.systemui.statusbar.policy.headsUpEvents
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.SecureSettings
import com.android.systemui.util.settings.SettingsProxyExt.observerFlow
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.CoroutineDispatcher
import kotlinx.coroutines.CoroutineScope
import kotlinx.coroutines.CoroutineScope
import kotlinx.coroutines.ExperimentalCoroutinesApi
import kotlinx.coroutines.ExperimentalCoroutinesApi
@@ -57,13 +65,11 @@ import kotlinx.coroutines.flow.emitAll
import kotlinx.coroutines.flow.first
import kotlinx.coroutines.flow.first
import kotlinx.coroutines.flow.flowOn
import kotlinx.coroutines.flow.flowOn
import kotlinx.coroutines.flow.map
import kotlinx.coroutines.flow.map
import kotlinx.coroutines.flow.onEach
import kotlinx.coroutines.flow.onStart
import kotlinx.coroutines.flow.onStart
import kotlinx.coroutines.flow.transformLatest
import kotlinx.coroutines.flow.transformLatest
import kotlinx.coroutines.launch
import kotlinx.coroutines.launch
import kotlinx.coroutines.yield
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
 * Filters low priority and privacy-sensitive notifications from the lockscreen, and hides section
@@ -74,17 +80,19 @@ class KeyguardCoordinator
@Inject
@Inject
constructor(
constructor(
    @Background private val bgDispatcher: CoroutineDispatcher,
    @Background private val bgDispatcher: CoroutineDispatcher,
    private val dumpManager: DumpManager,
    private val headsUpManager: HeadsUpManager,
    private val headsUpManager: HeadsUpManager,
    private val keyguardNotificationVisibilityProvider: KeyguardNotificationVisibilityProvider,
    private val keyguardNotificationVisibilityProvider: KeyguardNotificationVisibilityProvider,
    private val keyguardRepository: KeyguardRepository,
    private val keyguardRepository: KeyguardRepository,
    private val keyguardTransitionRepository: KeyguardTransitionRepository,
    private val keyguardTransitionRepository: KeyguardTransitionRepository,
    private val logger: KeyguardCoordinatorLogger,
    private val notifPipelineFlags: NotifPipelineFlags,
    private val notifPipelineFlags: NotifPipelineFlags,
    @Application private val scope: CoroutineScope,
    @Application private val scope: CoroutineScope,
    private val sectionHeaderVisibilityProvider: SectionHeaderVisibilityProvider,
    private val sectionHeaderVisibilityProvider: SectionHeaderVisibilityProvider,
    private val secureSettings: SecureSettings,
    private val secureSettings: SecureSettings,
    private val seenNotifsProvider: SeenNotificationsProviderImpl,
    private val seenNotifsProvider: SeenNotificationsProviderImpl,
    private val statusBarStateController: StatusBarStateController,
    private val statusBarStateController: StatusBarStateController,
) : Coordinator {
) : Coordinator, Dumpable {


    private val unseenNotifications = mutableSetOf<NotificationEntry>()
    private val unseenNotifications = mutableSetOf<NotificationEntry>()
    private var unseenFilterEnabled = false
    private var unseenFilterEnabled = false
@@ -103,6 +111,7 @@ constructor(
        pipeline.addCollectionListener(collectionListener)
        pipeline.addCollectionListener(collectionListener)
        scope.launch { trackUnseenNotificationsWhileUnlocked() }
        scope.launch { trackUnseenNotificationsWhileUnlocked() }
        scope.launch { invalidateWhenUnseenSettingChanges() }
        scope.launch { invalidateWhenUnseenSettingChanges() }
        dumpManager.registerDumpable(this)
    }
    }


    private suspend fun trackUnseenNotificationsWhileUnlocked() {
    private suspend fun trackUnseenNotificationsWhileUnlocked() {
@@ -122,14 +131,16 @@ constructor(
                        // If the screen is turning off, stop tracking, but if that transition is
                        // If the screen is turning off, stop tracking, but if that transition is
                        // cancelled, then start again.
                        // cancelled, then start again.
                        emitAll(
                        emitAll(
                            keyguardTransitionRepository.transitions
                            keyguardTransitionRepository.transitions.map { step ->
                                .map { step -> !step.isScreenTurningOff }
                                !step.isScreenTurningOff
                            }
                        )
                        )
                    }
                    }
                }
                }
                // Prevent double emit of `false` caused by transition to AOD, followed by keyguard
                // Prevent double emit of `false` caused by transition to AOD, followed by keyguard
                // showing
                // showing
                .distinctUntilChanged()
                .distinctUntilChanged()
                .onEach { trackingUnseen -> logger.logTrackingUnseen(trackingUnseen) }


        // Use collectLatest so that trackUnseenNotifications() is cancelled when the keyguard is
        // Use collectLatest so that trackUnseenNotifications() is cancelled when the keyguard is
        // showing again
        // showing again
@@ -140,9 +151,11 @@ constructor(
                // set when unlocked
                // set when unlocked
                awaitTimeSpentNotDozing(SEEN_TIMEOUT)
                awaitTimeSpentNotDozing(SEEN_TIMEOUT)
                clearUnseenOnBeginTracking = true
                clearUnseenOnBeginTracking = true
                logger.logSeenOnLockscreen()
            } else {
            } else {
                if (clearUnseenOnBeginTracking) {
                if (clearUnseenOnBeginTracking) {
                    clearUnseenOnBeginTracking = false
                    clearUnseenOnBeginTracking = false
                    logger.logAllMarkedSeenOnUnlock()
                    unseenNotifications.clear()
                    unseenNotifications.clear()
                }
                }
                unseenNotifFilter.invalidateList("keyguard no longer showing")
                unseenNotifFilter.invalidateList("keyguard no longer showing")
@@ -166,6 +179,8 @@ constructor(
            .first()
            .first()
    }
    }


    // Track "unseen" notifications, marking them as seen when either shade is expanded or the
    // notification becomes heads up.
    private suspend fun trackUnseenNotifications() {
    private suspend fun trackUnseenNotifications() {
        coroutineScope {
        coroutineScope {
            launch { clearUnseenNotificationsWhenShadeIsExpanded() }
            launch { clearUnseenNotificationsWhenShadeIsExpanded() }
@@ -179,6 +194,7 @@ constructor(
            // keyguard transition and not the user expanding the shade
            // keyguard transition and not the user expanding the shade
            yield()
            yield()
            if (isExpanded) {
            if (isExpanded) {
                logger.logShadeExpanded()
                unseenNotifications.clear()
                unseenNotifications.clear()
            }
            }
        }
        }
@@ -190,6 +206,7 @@ constructor(
            .forEach { unseenNotifications.remove(it) }
            .forEach { unseenNotifications.remove(it) }
        headsUpManager.headsUpEvents.collect { (entry, isHun) ->
        headsUpManager.headsUpEvents.collect { (entry, isHun) ->
            if (isHun) {
            if (isHun) {
                logger.logUnseenHun(entry.key)
                unseenNotifications.remove(entry)
                unseenNotifications.remove(entry)
            }
            }
        }
        }
@@ -231,6 +248,7 @@ constructor(
                if (
                if (
                    keyguardRepository.isKeyguardShowing() || !statusBarStateController.isExpanded
                    keyguardRepository.isKeyguardShowing() || !statusBarStateController.isExpanded
                ) {
                ) {
                    logger.logUnseenAdded(entry.key)
                    unseenNotifications.add(entry)
                    unseenNotifications.add(entry)
                }
                }
            }
            }
@@ -239,12 +257,15 @@ constructor(
                if (
                if (
                    keyguardRepository.isKeyguardShowing() || !statusBarStateController.isExpanded
                    keyguardRepository.isKeyguardShowing() || !statusBarStateController.isExpanded
                ) {
                ) {
                    logger.logUnseenUpdated(entry.key)
                    unseenNotifications.add(entry)
                    unseenNotifications.add(entry)
                }
                }
            }
            }


            override fun onEntryRemoved(entry: NotificationEntry, reason: Int) {
            override fun onEntryRemoved(entry: NotificationEntry, reason: Int) {
                unseenNotifications.remove(entry)
                if (unseenNotifications.remove(entry)) {
                    logger.logUnseenRemoved(entry.key)
                }
            }
            }
        }
        }


@@ -272,6 +293,7 @@ constructor(
                }.also { hasFiltered -> hasFilteredAnyNotifs = hasFilteredAnyNotifs || hasFiltered }
                }.also { hasFiltered -> hasFilteredAnyNotifs = hasFilteredAnyNotifs || hasFiltered }


            override fun onCleanup() {
            override fun onCleanup() {
                logger.logProviderHasFilteredOutSeenNotifs(hasFilteredAnyNotifs)
                seenNotifsProvider.hasFilteredOutSeenNotifications = hasFilteredAnyNotifs
                seenNotifsProvider.hasFilteredOutSeenNotifications = hasFilteredAnyNotifs
                hasFilteredAnyNotifs = false
                hasFilteredAnyNotifs = false
            }
            }
@@ -306,11 +328,25 @@ constructor(
        sectionHeaderVisibilityProvider.sectionHeadersVisible = showSections
        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 {
    companion object {
        private const val TAG = "KeyguardCoordinator"
        private const val TAG = "KeyguardCoordinator"
        private val SEEN_TIMEOUT = 5.seconds
        private val SEEN_TIMEOUT = 5.seconds
    }
    }
}
}


private val TransitionStep.isScreenTurningOff: Boolean get() =
private val TransitionStep.isScreenTurningOff: Boolean
    transitionState == TransitionState.STARTED && to != KeyguardState.GONE
    get() = transitionState == TransitionState.STARTED && to != KeyguardState.GONE
 No newline at end of file
+99 −0
Original line number Original line Diff line number Diff line
/*
 * 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" },
        )
}
+3 −0
Original line number Original line Diff line number Diff line
@@ -24,6 +24,7 @@ import android.testing.AndroidTestingRunner
import androidx.test.filters.SmallTest
import androidx.test.filters.SmallTest
import com.android.systemui.SysuiTestCase
import com.android.systemui.SysuiTestCase
import com.android.systemui.coroutines.advanceTimeBy
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.FakeKeyguardRepository
import com.android.systemui.keyguard.data.repository.FakeKeyguardTransitionRepository
import com.android.systemui.keyguard.data.repository.FakeKeyguardTransitionRepository
import com.android.systemui.keyguard.shared.model.KeyguardState
import com.android.systemui.keyguard.shared.model.KeyguardState
@@ -380,10 +381,12 @@ class KeyguardCoordinatorTest : SysuiTestCase() {
        val keyguardCoordinator =
        val keyguardCoordinator =
            KeyguardCoordinator(
            KeyguardCoordinator(
                testDispatcher,
                testDispatcher,
                mock<DumpManager>(),
                headsUpManager,
                headsUpManager,
                keyguardNotifVisibilityProvider,
                keyguardNotifVisibilityProvider,
                keyguardRepository,
                keyguardRepository,
                keyguardTransitionRepository,
                keyguardTransitionRepository,
                mock<KeyguardCoordinatorLogger>(),
                notifPipelineFlags,
                notifPipelineFlags,
                testScope.backgroundScope,
                testScope.backgroundScope,
                sectionHeaderVisibilityProvider,
                sectionHeaderVisibilityProvider,