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

Commit 4c162b14 authored by Steve Elliott's avatar Steve Elliott
Browse files

Revert "Individually track vis duration of unseen notifs"

This reverts commit c197cbec.

Reason for revert: b/285715497

Fixes: 285715497
Change-Id: Ibf90edc1902ccfa48237e36c21a815ec61e8145c
parent eec52524
Loading
Loading
Loading
Loading
+66 −115
Original line number Diff line number Diff line
@@ -28,9 +28,12 @@ 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
import com.android.systemui.keyguard.shared.model.TransitionState
import com.android.systemui.keyguard.shared.model.TransitionStep
import com.android.systemui.plugins.statusbar.StatusBarStateController
import com.android.systemui.statusbar.StatusBarState
import com.android.systemui.statusbar.expansionChanges
import com.android.systemui.statusbar.notification.NotifPipelineFlags
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
@@ -47,29 +50,30 @@ 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
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.emitAll
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

/**
 * 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.
 * headers on the lockscreen.
 */
@CoordinatorScope
class KeyguardCoordinator
@@ -82,6 +86,7 @@ constructor(
    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,
@@ -90,8 +95,6 @@ constructor(
) : 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) {
@@ -106,131 +109,79 @@ constructor(
    private fun attachUnseenFilter(pipeline: NotifPipeline) {
        pipeline.addFinalizeFilter(unseenNotifFilter)
        pipeline.addCollectionListener(collectionListener)
        scope.launch { trackSeenNotifications() }
        scope.launch { trackUnseenNotificationsWhileUnlocked() }
        scope.launch { invalidateWhenUnseenSettingChanges() }
        dumpManager.registerDumpable(this)
    }

    private suspend fun trackSeenNotifications() {
        // Whether or not keyguard is visible (or occluded).
        val isKeyguardPresent: Flow<Boolean> =
            keyguardTransitionRepository.transitions
                .map { step -> step.to != KeyguardState.GONE }
    private suspend fun trackUnseenNotificationsWhileUnlocked() {
        // Whether or not we're actively tracking unseen notifications to mark them as seen when
        // appropriate.
        val isTrackingUnseen: Flow<Boolean> =
            keyguardRepository.isKeyguardShowing
                // transformLatest so that we can cancel listening to keyguard transitions once
                // isKeyguardShowing changes (after a successful transition to the keyguard).
                .transformLatest { isShowing ->
                    if (isShowing) {
                        // If the keyguard is showing, we're not tracking unseen.
                        emit(false)
                    } else {
                        // If the keyguard stops showing, then start tracking unseen notifications.
                        emit(true)
                        // If the screen is turning off, stop tracking, but if that transition is
                        // cancelled, then start again.
                        emitAll(
                            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) }

        // 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)
        // Use collectLatest so that trackUnseenNotifications() is cancelled when the keyguard is
        // showing again
        var clearUnseenOnBeginTracking = false
        isTrackingUnseen.collectLatest { trackingUnseen ->
            if (!trackingUnseen) {
                // Wait for the user to spend enough time on the lock screen before clearing unseen
                // set when unlocked
                awaitTimeSpentNotDozing(SEEN_TIMEOUT)
                clearUnseenOnBeginTracking = true
                logger.logSeenOnLockscreen()
            } 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()
                if (clearUnseenOnBeginTracking) {
                    clearUnseenOnBeginTracking = false
                    logger.logAllMarkedSeenOnUnlock()
                    unseenNotifications.clear()
                }
                unseenNotifFilter.invalidateList("keyguard no longer showing")
                // Keyguard is gone, notifications can be immediately marked as seen when they
                // become visible.
                trackSeenNotificationsWhileUnlocked()
                trackUnseenNotifications()
            }
        }
    }

    /**
     * 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 ->
    private suspend fun awaitTimeSpentNotDozing(duration: Duration) {
        keyguardRepository.isDozing
            // Use transformLatest so that the timeout delay is cancelled if the device enters doze,
            // and is restarted when doze ends.
            .transformLatest { isDozing ->
                if (!isDozing) {
                trackSeenNotificationsWhileLockedAndNotDozing(notificationsSeenWhileLocked)
                    delay(duration)
                    // Signal timeout has completed
                    emit(Unit)
                }
            }
            // Suspend until the first emission
            .first()
    }

    /**
     * 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
    // Track "unseen" notifications, marking them as seen when either shade is expanded or the
    // notification becomes heads up.
    private suspend fun trackSeenNotificationsWhileUnlocked() {
    private suspend fun trackUnseenNotifications() {
        coroutineScope {
            launch { clearUnseenNotificationsWhenShadeIsExpanded() }
            launch { markHeadsUpNotificationsAsSeen() }
@@ -299,7 +250,6 @@ constructor(
                ) {
                    logger.logUnseenAdded(entry.key)
                    unseenNotifications.add(entry)
                    unseenEntryAdded.tryEmit(entry)
                }
            }

@@ -309,14 +259,12 @@ constructor(
                ) {
                    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)
                }
            }
        }
@@ -399,3 +347,6 @@ constructor(
        private val SEEN_TIMEOUT = 5.seconds
    }
}

private val TransitionStep.isScreenTurningOff: Boolean
    get() = transitionState == TransitionState.STARTED && to != KeyguardState.GONE
+4 −74
Original line number Diff line number Diff line
@@ -19,7 +19,6 @@ package com.android.systemui.statusbar.notification.collection.coordinator
import com.android.systemui.log.LogBuffer
import com.android.systemui.log.LogLevel
import com.android.systemui.log.dagger.UnseenNotificationLog
import com.android.systemui.statusbar.notification.collection.NotificationEntry
import javax.inject.Inject

private const val TAG = "KeyguardCoordinator"
@@ -29,14 +28,11 @@ class KeyguardCoordinatorLogger
constructor(
    @UnseenNotificationLog private val buffer: LogBuffer,
) {
    fun logSeenOnLockscreen(entry: NotificationEntry) =
    fun logSeenOnLockscreen() =
        buffer.log(
            TAG,
            LogLevel.DEBUG,
            messageInitializer = { str1 = entry.key },
            messagePrinter = {
                "Notification [$str1] on lockscreen will be marked as seen when unlocked."
            },
            "Notifications on lockscreen will be marked as seen when unlocked."
        )

    fun logTrackingUnseen(trackingUnseen: Boolean) =
@@ -47,21 +43,11 @@ constructor(
            messagePrinter = { "${if (bool1) "Start" else "Stop"} tracking unseen notifications." },
        )

    fun logAllMarkedSeenOnUnlock(
        seenCount: Int,
        remainingUnseenCount: Int,
    ) =
    fun logAllMarkedSeenOnUnlock() =
        buffer.log(
            TAG,
            LogLevel.DEBUG,
            messageInitializer = {
                int1 = seenCount
                int2 = remainingUnseenCount
            },
            messagePrinter = {
                "$int1 Notifications have been marked as seen now that device is unlocked. " +
                    "$int2 notifications remain unseen."
            },
            "Notifications have been marked as seen now that device is unlocked."
        )

    fun logShadeExpanded() =
@@ -110,60 +96,4 @@ constructor(
            messageInitializer = { str1 = key },
            messagePrinter = { "Unseen notif has become heads up: $str1" },
        )

    fun logTrackingLockscreenSeenDuration(unseenNotifications: Set<NotificationEntry>) {
        buffer.log(
            TAG,
            LogLevel.DEBUG,
            messageInitializer = {
                str1 = unseenNotifications.joinToString { it.key }
                int1 = unseenNotifications.size
            },
            messagePrinter = {
                "Tracking $int1 unseen notifications for lockscreen seen duration threshold: $str1"
            },
        )
    }

    fun logTrackingLockscreenSeenDuration(entry: NotificationEntry) {
        buffer.log(
            TAG,
            LogLevel.DEBUG,
            messageInitializer = { str1 = entry.key },
            messagePrinter = {
                "Tracking new notification for lockscreen seen duration threshold: $str1"
            },
        )
    }

    fun logStopTrackingLockscreenSeenDuration(entry: NotificationEntry) {
        buffer.log(
            TAG,
            LogLevel.DEBUG,
            messageInitializer = { str1 = entry.key },
            messagePrinter = {
                "Stop tracking removed notification for lockscreen seen duration threshold: $str1"
            },
        )
    }

    fun logResetSeenOnLockscreen(entry: NotificationEntry) {
        buffer.log(
            TAG,
            LogLevel.DEBUG,
            messageInitializer = { str1 = entry.key },
            messagePrinter = {
                "Reset tracking updated notification for lockscreen seen duration threshold: $str1"
            },
        )
    }

    fun logRemoveSeenOnLockscreen(entry: NotificationEntry) {
        buffer.log(
            TAG,
            LogLevel.DEBUG,
            messageInitializer = { str1 = entry.key },
            messagePrinter = { "Notification marked as seen on lockscreen removed: $str1" },
        )
    }
}
+39 −258

File changed.

Preview size limit exceeded, changes collapsed.