Merge "Individually track vis duration of unseen notifs" into udc-qpr-dev am: f58e1e47df
Original change: https://googleplex-android-review.googlesource.com/c/platform/frameworks/base/+/23904070 Change-Id: I7c3feaf7f1cf0c6d0bbca4c218d6770d8afce84e Signed-off-by: Automerger Merge Worker <android-build-automerger-merge-worker@system.gserviceaccount.com>
This commit is contained in:
@@ -28,12 +28,9 @@ 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
|
||||||
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.plugins.statusbar.StatusBarStateController
|
||||||
import com.android.systemui.statusbar.StatusBarState
|
import com.android.systemui.statusbar.StatusBarState
|
||||||
import com.android.systemui.statusbar.expansionChanges
|
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.NotifPipeline
|
||||||
import com.android.systemui.statusbar.notification.collection.NotificationEntry
|
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.coordinator.dagger.CoordinatorScope
|
||||||
@@ -50,30 +47,29 @@ 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 java.io.PrintWriter
|
||||||
import javax.inject.Inject
|
import javax.inject.Inject
|
||||||
import kotlin.time.Duration
|
|
||||||
import kotlin.time.Duration.Companion.seconds
|
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
|
||||||
|
import kotlinx.coroutines.Job
|
||||||
import kotlinx.coroutines.coroutineScope
|
import kotlinx.coroutines.coroutineScope
|
||||||
import kotlinx.coroutines.delay
|
import kotlinx.coroutines.delay
|
||||||
import kotlinx.coroutines.flow.Flow
|
import kotlinx.coroutines.flow.Flow
|
||||||
|
import kotlinx.coroutines.flow.MutableSharedFlow
|
||||||
import kotlinx.coroutines.flow.collectLatest
|
import kotlinx.coroutines.flow.collectLatest
|
||||||
import kotlinx.coroutines.flow.conflate
|
import kotlinx.coroutines.flow.conflate
|
||||||
import kotlinx.coroutines.flow.distinctUntilChanged
|
import kotlinx.coroutines.flow.distinctUntilChanged
|
||||||
import kotlinx.coroutines.flow.emitAll
|
|
||||||
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.onEach
|
||||||
import kotlinx.coroutines.flow.onStart
|
import kotlinx.coroutines.flow.onStart
|
||||||
import kotlinx.coroutines.flow.transformLatest
|
|
||||||
import kotlinx.coroutines.launch
|
import kotlinx.coroutines.launch
|
||||||
import kotlinx.coroutines.yield
|
import kotlinx.coroutines.yield
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* 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
|
||||||
* headers on the lockscreen.
|
* headers on the lockscreen. If enabled, it will also track and hide seen notifications on the
|
||||||
|
* lockscreen.
|
||||||
*/
|
*/
|
||||||
@CoordinatorScope
|
@CoordinatorScope
|
||||||
class KeyguardCoordinator
|
class KeyguardCoordinator
|
||||||
@@ -86,7 +82,6 @@ constructor(
|
|||||||
private val keyguardRepository: KeyguardRepository,
|
private val keyguardRepository: KeyguardRepository,
|
||||||
private val keyguardTransitionRepository: KeyguardTransitionRepository,
|
private val keyguardTransitionRepository: KeyguardTransitionRepository,
|
||||||
private val logger: KeyguardCoordinatorLogger,
|
private val logger: KeyguardCoordinatorLogger,
|
||||||
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,
|
||||||
@@ -95,6 +90,8 @@ constructor(
|
|||||||
) : Coordinator, Dumpable {
|
) : Coordinator, Dumpable {
|
||||||
|
|
||||||
private val unseenNotifications = mutableSetOf<NotificationEntry>()
|
private val unseenNotifications = mutableSetOf<NotificationEntry>()
|
||||||
|
private val unseenEntryAdded = MutableSharedFlow<NotificationEntry>(extraBufferCapacity = 1)
|
||||||
|
private val unseenEntryRemoved = MutableSharedFlow<NotificationEntry>(extraBufferCapacity = 1)
|
||||||
private var unseenFilterEnabled = false
|
private var unseenFilterEnabled = false
|
||||||
|
|
||||||
override fun attach(pipeline: NotifPipeline) {
|
override fun attach(pipeline: NotifPipeline) {
|
||||||
@@ -109,79 +106,130 @@ constructor(
|
|||||||
private fun attachUnseenFilter(pipeline: NotifPipeline) {
|
private fun attachUnseenFilter(pipeline: NotifPipeline) {
|
||||||
pipeline.addFinalizeFilter(unseenNotifFilter)
|
pipeline.addFinalizeFilter(unseenNotifFilter)
|
||||||
pipeline.addCollectionListener(collectionListener)
|
pipeline.addCollectionListener(collectionListener)
|
||||||
scope.launch { trackUnseenNotificationsWhileUnlocked() }
|
scope.launch { trackUnseenFilterSettingChanges() }
|
||||||
scope.launch { invalidateWhenUnseenSettingChanges() }
|
|
||||||
dumpManager.registerDumpable(this)
|
dumpManager.registerDumpable(this)
|
||||||
}
|
}
|
||||||
|
|
||||||
private suspend fun trackUnseenNotificationsWhileUnlocked() {
|
private suspend fun trackSeenNotifications() {
|
||||||
// Whether or not we're actively tracking unseen notifications to mark them as seen when
|
// Whether or not keyguard is visible (or occluded).
|
||||||
// appropriate.
|
val isKeyguardPresent: Flow<Boolean> =
|
||||||
val isTrackingUnseen: Flow<Boolean> =
|
keyguardTransitionRepository.transitions
|
||||||
keyguardRepository.isKeyguardShowing
|
.map { step -> step.to != KeyguardState.GONE }
|
||||||
// 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()
|
.distinctUntilChanged()
|
||||||
.onEach { trackingUnseen -> logger.logTrackingUnseen(trackingUnseen) }
|
.onEach { trackingUnseen -> logger.logTrackingUnseen(trackingUnseen) }
|
||||||
|
|
||||||
// Use collectLatest so that trackUnseenNotifications() is cancelled when the keyguard is
|
// Separately track seen notifications while the device is locked, applying once the device
|
||||||
// showing again
|
// is unlocked.
|
||||||
var clearUnseenOnBeginTracking = false
|
val notificationsSeenWhileLocked = mutableSetOf<NotificationEntry>()
|
||||||
isTrackingUnseen.collectLatest { trackingUnseen ->
|
|
||||||
if (!trackingUnseen) {
|
// Use [collectLatest] to cancel any running jobs when [trackingUnseen] changes.
|
||||||
// Wait for the user to spend enough time on the lock screen before clearing unseen
|
isKeyguardPresent.collectLatest { isKeyguardPresent: Boolean ->
|
||||||
// set when unlocked
|
if (isKeyguardPresent) {
|
||||||
awaitTimeSpentNotDozing(SEEN_TIMEOUT)
|
// Keyguard is not gone, notifications need to be visible for a certain threshold
|
||||||
clearUnseenOnBeginTracking = true
|
// before being marked as seen
|
||||||
logger.logSeenOnLockscreen()
|
trackSeenNotificationsWhileLocked(notificationsSeenWhileLocked)
|
||||||
} else {
|
} else {
|
||||||
if (clearUnseenOnBeginTracking) {
|
// Mark all seen-while-locked notifications as seen for real.
|
||||||
clearUnseenOnBeginTracking = false
|
if (notificationsSeenWhileLocked.isNotEmpty()) {
|
||||||
logger.logAllMarkedSeenOnUnlock()
|
unseenNotifications.removeAll(notificationsSeenWhileLocked)
|
||||||
unseenNotifications.clear()
|
logger.logAllMarkedSeenOnUnlock(
|
||||||
|
seenCount = notificationsSeenWhileLocked.size,
|
||||||
|
remainingUnseenCount = unseenNotifications.size
|
||||||
|
)
|
||||||
|
notificationsSeenWhileLocked.clear()
|
||||||
}
|
}
|
||||||
unseenNotifFilter.invalidateList("keyguard no longer showing")
|
unseenNotifFilter.invalidateList("keyguard no longer showing")
|
||||||
trackUnseenNotifications()
|
// Keyguard is gone, notifications can be immediately marked as seen when they
|
||||||
|
// become visible.
|
||||||
|
trackSeenNotificationsWhileUnlocked()
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
private suspend fun awaitTimeSpentNotDozing(duration: Duration) {
|
/**
|
||||||
keyguardRepository.isDozing
|
* Keep [notificationsSeenWhileLocked] updated to represent which notifications have actually
|
||||||
// Use transformLatest so that the timeout delay is cancelled if the device enters doze,
|
* been "seen" while the device is on the keyguard.
|
||||||
// and is restarted when doze ends.
|
*/
|
||||||
.transformLatest { isDozing ->
|
private suspend fun trackSeenNotificationsWhileLocked(
|
||||||
if (!isDozing) {
|
notificationsSeenWhileLocked: MutableSet<NotificationEntry>,
|
||||||
delay(duration)
|
) = coroutineScope {
|
||||||
// Signal timeout has completed
|
// Remove removed notifications from the set
|
||||||
emit(Unit)
|
launch {
|
||||||
|
unseenEntryRemoved.collect { entry ->
|
||||||
|
if (notificationsSeenWhileLocked.remove(entry)) {
|
||||||
|
logger.logRemoveSeenOnLockscreen(entry)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
// Suspend until the first emission
|
}
|
||||||
.first()
|
// 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)
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// Track "unseen" notifications, marking them as seen when either shade is expanded or the
|
/**
|
||||||
|
* 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.
|
// notification becomes heads up.
|
||||||
private suspend fun trackUnseenNotifications() {
|
private suspend fun trackSeenNotificationsWhileUnlocked() {
|
||||||
coroutineScope {
|
coroutineScope {
|
||||||
launch { clearUnseenNotificationsWhenShadeIsExpanded() }
|
launch { clearUnseenNotificationsWhenShadeIsExpanded() }
|
||||||
launch { markHeadsUpNotificationsAsSeen() }
|
launch { markHeadsUpNotificationsAsSeen() }
|
||||||
@@ -212,7 +260,7 @@ constructor(
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
private suspend fun invalidateWhenUnseenSettingChanges() {
|
private suspend fun trackUnseenFilterSettingChanges() {
|
||||||
secureSettings
|
secureSettings
|
||||||
// emit whenever the setting has changed
|
// emit whenever the setting has changed
|
||||||
.observerFlow(
|
.observerFlow(
|
||||||
@@ -228,17 +276,23 @@ constructor(
|
|||||||
UserHandle.USER_CURRENT,
|
UserHandle.USER_CURRENT,
|
||||||
) == 1
|
) == 1
|
||||||
}
|
}
|
||||||
|
// don't emit anything if nothing has changed
|
||||||
|
.distinctUntilChanged()
|
||||||
// perform lookups on the bg thread pool
|
// perform lookups on the bg thread pool
|
||||||
.flowOn(bgDispatcher)
|
.flowOn(bgDispatcher)
|
||||||
// only track the most recent emission, if events are happening faster than they can be
|
// only track the most recent emission, if events are happening faster than they can be
|
||||||
// consumed
|
// consumed
|
||||||
.conflate()
|
.conflate()
|
||||||
// update local field and invalidate if necessary
|
.collectLatest { setting ->
|
||||||
.collect { setting ->
|
// update local field and invalidate if necessary
|
||||||
if (setting != unseenFilterEnabled) {
|
if (setting != unseenFilterEnabled) {
|
||||||
unseenFilterEnabled = setting
|
unseenFilterEnabled = setting
|
||||||
unseenNotifFilter.invalidateList("unseen setting changed")
|
unseenNotifFilter.invalidateList("unseen setting changed")
|
||||||
}
|
}
|
||||||
|
// if the setting is enabled, then start tracking and filtering unseen notifications
|
||||||
|
if (setting) {
|
||||||
|
trackSeenNotifications()
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -250,6 +304,7 @@ constructor(
|
|||||||
) {
|
) {
|
||||||
logger.logUnseenAdded(entry.key)
|
logger.logUnseenAdded(entry.key)
|
||||||
unseenNotifications.add(entry)
|
unseenNotifications.add(entry)
|
||||||
|
unseenEntryAdded.tryEmit(entry)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -259,12 +314,14 @@ constructor(
|
|||||||
) {
|
) {
|
||||||
logger.logUnseenUpdated(entry.key)
|
logger.logUnseenUpdated(entry.key)
|
||||||
unseenNotifications.add(entry)
|
unseenNotifications.add(entry)
|
||||||
|
unseenEntryAdded.tryEmit(entry)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
override fun onEntryRemoved(entry: NotificationEntry, reason: Int) {
|
override fun onEntryRemoved(entry: NotificationEntry, reason: Int) {
|
||||||
if (unseenNotifications.remove(entry)) {
|
if (unseenNotifications.remove(entry)) {
|
||||||
logger.logUnseenRemoved(entry.key)
|
logger.logUnseenRemoved(entry.key)
|
||||||
|
unseenEntryRemoved.tryEmit(entry)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -347,6 +404,3 @@ constructor(
|
|||||||
private val SEEN_TIMEOUT = 5.seconds
|
private val SEEN_TIMEOUT = 5.seconds
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
private val TransitionStep.isScreenTurningOff: Boolean
|
|
||||||
get() = transitionState == TransitionState.STARTED && to != KeyguardState.GONE
|
|
||||||
|
|||||||
@@ -19,6 +19,7 @@ package com.android.systemui.statusbar.notification.collection.coordinator
|
|||||||
import com.android.systemui.log.LogBuffer
|
import com.android.systemui.log.LogBuffer
|
||||||
import com.android.systemui.log.core.LogLevel
|
import com.android.systemui.log.core.LogLevel
|
||||||
import com.android.systemui.log.dagger.UnseenNotificationLog
|
import com.android.systemui.log.dagger.UnseenNotificationLog
|
||||||
|
import com.android.systemui.statusbar.notification.collection.NotificationEntry
|
||||||
import javax.inject.Inject
|
import javax.inject.Inject
|
||||||
|
|
||||||
private const val TAG = "KeyguardCoordinator"
|
private const val TAG = "KeyguardCoordinator"
|
||||||
@@ -28,11 +29,14 @@ class KeyguardCoordinatorLogger
|
|||||||
constructor(
|
constructor(
|
||||||
@UnseenNotificationLog private val buffer: LogBuffer,
|
@UnseenNotificationLog private val buffer: LogBuffer,
|
||||||
) {
|
) {
|
||||||
fun logSeenOnLockscreen() =
|
fun logSeenOnLockscreen(entry: NotificationEntry) =
|
||||||
buffer.log(
|
buffer.log(
|
||||||
TAG,
|
TAG,
|
||||||
LogLevel.DEBUG,
|
LogLevel.DEBUG,
|
||||||
"Notifications on lockscreen will be marked as seen when unlocked."
|
messageInitializer = { str1 = entry.key },
|
||||||
|
messagePrinter = {
|
||||||
|
"Notification [$str1] on lockscreen will be marked as seen when unlocked."
|
||||||
|
},
|
||||||
)
|
)
|
||||||
|
|
||||||
fun logTrackingUnseen(trackingUnseen: Boolean) =
|
fun logTrackingUnseen(trackingUnseen: Boolean) =
|
||||||
@@ -43,11 +47,21 @@ constructor(
|
|||||||
messagePrinter = { "${if (bool1) "Start" else "Stop"} tracking unseen notifications." },
|
messagePrinter = { "${if (bool1) "Start" else "Stop"} tracking unseen notifications." },
|
||||||
)
|
)
|
||||||
|
|
||||||
fun logAllMarkedSeenOnUnlock() =
|
fun logAllMarkedSeenOnUnlock(
|
||||||
|
seenCount: Int,
|
||||||
|
remainingUnseenCount: Int,
|
||||||
|
) =
|
||||||
buffer.log(
|
buffer.log(
|
||||||
TAG,
|
TAG,
|
||||||
LogLevel.DEBUG,
|
LogLevel.DEBUG,
|
||||||
"Notifications have been marked as seen now that device is unlocked."
|
messageInitializer = {
|
||||||
|
int1 = seenCount
|
||||||
|
int2 = remainingUnseenCount
|
||||||
|
},
|
||||||
|
messagePrinter = {
|
||||||
|
"$int1 Notifications have been marked as seen now that device is unlocked. " +
|
||||||
|
"$int2 notifications remain unseen."
|
||||||
|
},
|
||||||
)
|
)
|
||||||
|
|
||||||
fun logShadeExpanded() =
|
fun logShadeExpanded() =
|
||||||
@@ -96,4 +110,60 @@ constructor(
|
|||||||
messageInitializer = { str1 = key },
|
messageInitializer = { str1 = key },
|
||||||
messagePrinter = { "Unseen notif has become heads up: $str1" },
|
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" },
|
||||||
|
)
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -32,7 +32,6 @@ import com.android.systemui.keyguard.shared.model.TransitionState
|
|||||||
import com.android.systemui.keyguard.shared.model.TransitionStep
|
import com.android.systemui.keyguard.shared.model.TransitionStep
|
||||||
import com.android.systemui.plugins.statusbar.StatusBarStateController
|
import com.android.systemui.plugins.statusbar.StatusBarStateController
|
||||||
import com.android.systemui.statusbar.StatusBarState
|
import com.android.systemui.statusbar.StatusBarState
|
||||||
import com.android.systemui.statusbar.notification.NotifPipelineFlags
|
|
||||||
import com.android.systemui.statusbar.notification.collection.GroupEntryBuilder
|
import com.android.systemui.statusbar.notification.collection.GroupEntryBuilder
|
||||||
import com.android.systemui.statusbar.notification.collection.NotifPipeline
|
import com.android.systemui.statusbar.notification.collection.NotifPipeline
|
||||||
import com.android.systemui.statusbar.notification.collection.NotificationEntryBuilder
|
import com.android.systemui.statusbar.notification.collection.NotificationEntryBuilder
|
||||||
@@ -46,11 +45,14 @@ import com.android.systemui.statusbar.notification.interruption.KeyguardNotifica
|
|||||||
import com.android.systemui.statusbar.notification.row.ExpandableNotificationRow
|
import com.android.systemui.statusbar.notification.row.ExpandableNotificationRow
|
||||||
import com.android.systemui.statusbar.policy.HeadsUpManager
|
import com.android.systemui.statusbar.policy.HeadsUpManager
|
||||||
import com.android.systemui.statusbar.policy.OnHeadsUpChangedListener
|
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.eq
|
||||||
import com.android.systemui.util.mockito.mock
|
import com.android.systemui.util.mockito.mock
|
||||||
import com.android.systemui.util.mockito.withArgCaptor
|
import com.android.systemui.util.mockito.withArgCaptor
|
||||||
import com.android.systemui.util.settings.FakeSettings
|
import com.android.systemui.util.settings.FakeSettings
|
||||||
import com.google.common.truth.Truth.assertThat
|
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.CoroutineScope
|
||||||
import kotlinx.coroutines.ExperimentalCoroutinesApi
|
import kotlinx.coroutines.ExperimentalCoroutinesApi
|
||||||
import kotlinx.coroutines.test.TestCoroutineScheduler
|
import kotlinx.coroutines.test.TestCoroutineScheduler
|
||||||
@@ -62,9 +64,8 @@ import org.junit.runner.RunWith
|
|||||||
import org.mockito.ArgumentMatchers.same
|
import org.mockito.ArgumentMatchers.same
|
||||||
import org.mockito.Mockito.anyString
|
import org.mockito.Mockito.anyString
|
||||||
import org.mockito.Mockito.clearInvocations
|
import org.mockito.Mockito.clearInvocations
|
||||||
|
import org.mockito.Mockito.never
|
||||||
import org.mockito.Mockito.verify
|
import org.mockito.Mockito.verify
|
||||||
import java.util.function.Consumer
|
|
||||||
import kotlin.time.Duration.Companion.seconds
|
|
||||||
import org.mockito.Mockito.`when` as whenever
|
import org.mockito.Mockito.`when` as whenever
|
||||||
|
|
||||||
@SmallTest
|
@SmallTest
|
||||||
@@ -75,7 +76,6 @@ class KeyguardCoordinatorTest : SysuiTestCase() {
|
|||||||
private val keyguardNotifVisibilityProvider: KeyguardNotificationVisibilityProvider = mock()
|
private val keyguardNotifVisibilityProvider: KeyguardNotificationVisibilityProvider = mock()
|
||||||
private val keyguardRepository = FakeKeyguardRepository()
|
private val keyguardRepository = FakeKeyguardRepository()
|
||||||
private val keyguardTransitionRepository = FakeKeyguardTransitionRepository()
|
private val keyguardTransitionRepository = FakeKeyguardTransitionRepository()
|
||||||
private val notifPipelineFlags: NotifPipelineFlags = mock()
|
|
||||||
private val notifPipeline: NotifPipeline = mock()
|
private val notifPipeline: NotifPipeline = mock()
|
||||||
private val sectionHeaderVisibilityProvider: SectionHeaderVisibilityProvider = mock()
|
private val sectionHeaderVisibilityProvider: SectionHeaderVisibilityProvider = mock()
|
||||||
private val statusBarStateController: StatusBarStateController = mock()
|
private val statusBarStateController: StatusBarStateController = mock()
|
||||||
@@ -136,13 +136,8 @@ class KeyguardCoordinatorTest : SysuiTestCase() {
|
|||||||
)
|
)
|
||||||
testScheduler.runCurrent()
|
testScheduler.runCurrent()
|
||||||
|
|
||||||
// WHEN: The shade is expanded
|
// THEN: We are no longer listening for shade expansions
|
||||||
whenever(statusBarStateController.isExpanded).thenReturn(true)
|
verify(statusBarStateController, never()).addCallback(any())
|
||||||
statusBarStateListener.onExpandedChanged(true)
|
|
||||||
testScheduler.runCurrent()
|
|
||||||
|
|
||||||
// THEN: The notification is still treated as "unseen" and is not filtered out.
|
|
||||||
assertThat(unseenFilter.shouldFilterOut(fakeEntry, 0L)).isFalse()
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -152,6 +147,10 @@ class KeyguardCoordinatorTest : SysuiTestCase() {
|
|||||||
keyguardRepository.setKeyguardShowing(false)
|
keyguardRepository.setKeyguardShowing(false)
|
||||||
whenever(statusBarStateController.isExpanded).thenReturn(false)
|
whenever(statusBarStateController.isExpanded).thenReturn(false)
|
||||||
runKeyguardCoordinatorTest {
|
runKeyguardCoordinatorTest {
|
||||||
|
keyguardTransitionRepository.sendTransitionStep(
|
||||||
|
TransitionStep(from = KeyguardState.LOCKSCREEN, to = KeyguardState.GONE)
|
||||||
|
)
|
||||||
|
|
||||||
// WHEN: A notification is posted
|
// WHEN: A notification is posted
|
||||||
val fakeEntry = NotificationEntryBuilder().build()
|
val fakeEntry = NotificationEntryBuilder().build()
|
||||||
collectionListener.onEntryAdded(fakeEntry)
|
collectionListener.onEntryAdded(fakeEntry)
|
||||||
@@ -162,6 +161,9 @@ class KeyguardCoordinatorTest : SysuiTestCase() {
|
|||||||
|
|
||||||
// WHEN: The keyguard is now showing
|
// WHEN: The keyguard is now showing
|
||||||
keyguardRepository.setKeyguardShowing(true)
|
keyguardRepository.setKeyguardShowing(true)
|
||||||
|
keyguardTransitionRepository.sendTransitionStep(
|
||||||
|
TransitionStep(from = KeyguardState.GONE, to = KeyguardState.AOD)
|
||||||
|
)
|
||||||
testScheduler.runCurrent()
|
testScheduler.runCurrent()
|
||||||
|
|
||||||
// THEN: The notification is recognized as "seen" and is filtered out.
|
// THEN: The notification is recognized as "seen" and is filtered out.
|
||||||
@@ -169,6 +171,9 @@ class KeyguardCoordinatorTest : SysuiTestCase() {
|
|||||||
|
|
||||||
// WHEN: The keyguard goes away
|
// WHEN: The keyguard goes away
|
||||||
keyguardRepository.setKeyguardShowing(false)
|
keyguardRepository.setKeyguardShowing(false)
|
||||||
|
keyguardTransitionRepository.sendTransitionStep(
|
||||||
|
TransitionStep(from = KeyguardState.AOD, to = KeyguardState.GONE)
|
||||||
|
)
|
||||||
testScheduler.runCurrent()
|
testScheduler.runCurrent()
|
||||||
|
|
||||||
// THEN: The notification is shown regardless
|
// THEN: The notification is shown regardless
|
||||||
@@ -182,9 +187,10 @@ class KeyguardCoordinatorTest : SysuiTestCase() {
|
|||||||
keyguardRepository.setKeyguardShowing(false)
|
keyguardRepository.setKeyguardShowing(false)
|
||||||
whenever(statusBarStateController.isExpanded).thenReturn(true)
|
whenever(statusBarStateController.isExpanded).thenReturn(true)
|
||||||
runKeyguardCoordinatorTest {
|
runKeyguardCoordinatorTest {
|
||||||
val fakeEntry = NotificationEntryBuilder()
|
val fakeEntry =
|
||||||
|
NotificationEntryBuilder()
|
||||||
.setNotification(Notification.Builder(mContext, "id").setOngoing(true).build())
|
.setNotification(Notification.Builder(mContext, "id").setOngoing(true).build())
|
||||||
.build()
|
.build()
|
||||||
collectionListener.onEntryAdded(fakeEntry)
|
collectionListener.onEntryAdded(fakeEntry)
|
||||||
|
|
||||||
// WHEN: The keyguard is now showing
|
// WHEN: The keyguard is now showing
|
||||||
@@ -202,11 +208,13 @@ class KeyguardCoordinatorTest : SysuiTestCase() {
|
|||||||
keyguardRepository.setKeyguardShowing(false)
|
keyguardRepository.setKeyguardShowing(false)
|
||||||
whenever(statusBarStateController.isExpanded).thenReturn(true)
|
whenever(statusBarStateController.isExpanded).thenReturn(true)
|
||||||
runKeyguardCoordinatorTest {
|
runKeyguardCoordinatorTest {
|
||||||
val fakeEntry = NotificationEntryBuilder().build().apply {
|
val fakeEntry =
|
||||||
row = mock<ExpandableNotificationRow>().apply {
|
NotificationEntryBuilder().build().apply {
|
||||||
whenever(isMediaRow).thenReturn(true)
|
row =
|
||||||
|
mock<ExpandableNotificationRow>().apply {
|
||||||
|
whenever(isMediaRow).thenReturn(true)
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
|
||||||
collectionListener.onEntryAdded(fakeEntry)
|
collectionListener.onEntryAdded(fakeEntry)
|
||||||
|
|
||||||
// WHEN: The keyguard is now showing
|
// WHEN: The keyguard is now showing
|
||||||
@@ -299,14 +307,12 @@ class KeyguardCoordinatorTest : SysuiTestCase() {
|
|||||||
runKeyguardCoordinatorTest {
|
runKeyguardCoordinatorTest {
|
||||||
// WHEN: A new notification is posted
|
// WHEN: A new notification is posted
|
||||||
val fakeSummary = NotificationEntryBuilder().build()
|
val fakeSummary = NotificationEntryBuilder().build()
|
||||||
val fakeChild = NotificationEntryBuilder()
|
val fakeChild =
|
||||||
|
NotificationEntryBuilder()
|
||||||
.setGroup(context, "group")
|
.setGroup(context, "group")
|
||||||
.setGroupSummary(context, false)
|
.setGroupSummary(context, false)
|
||||||
.build()
|
.build()
|
||||||
GroupEntryBuilder()
|
GroupEntryBuilder().setSummary(fakeSummary).addChild(fakeChild).build()
|
||||||
.setSummary(fakeSummary)
|
|
||||||
.addChild(fakeChild)
|
|
||||||
.build()
|
|
||||||
|
|
||||||
collectionListener.onEntryAdded(fakeSummary)
|
collectionListener.onEntryAdded(fakeSummary)
|
||||||
collectionListener.onEntryAdded(fakeChild)
|
collectionListener.onEntryAdded(fakeChild)
|
||||||
@@ -331,6 +337,10 @@ class KeyguardCoordinatorTest : SysuiTestCase() {
|
|||||||
runKeyguardCoordinatorTest {
|
runKeyguardCoordinatorTest {
|
||||||
val fakeEntry = NotificationEntryBuilder().build()
|
val fakeEntry = NotificationEntryBuilder().build()
|
||||||
collectionListener.onEntryAdded(fakeEntry)
|
collectionListener.onEntryAdded(fakeEntry)
|
||||||
|
keyguardTransitionRepository.sendTransitionStep(
|
||||||
|
TransitionStep(from = KeyguardState.AOD, to = KeyguardState.LOCKSCREEN)
|
||||||
|
)
|
||||||
|
testScheduler.runCurrent()
|
||||||
|
|
||||||
// WHEN: five seconds have passed
|
// WHEN: five seconds have passed
|
||||||
testScheduler.advanceTimeBy(5.seconds)
|
testScheduler.advanceTimeBy(5.seconds)
|
||||||
@@ -338,10 +348,16 @@ class KeyguardCoordinatorTest : SysuiTestCase() {
|
|||||||
|
|
||||||
// WHEN: Keyguard is no longer showing
|
// WHEN: Keyguard is no longer showing
|
||||||
keyguardRepository.setKeyguardShowing(false)
|
keyguardRepository.setKeyguardShowing(false)
|
||||||
|
keyguardTransitionRepository.sendTransitionStep(
|
||||||
|
TransitionStep(from = KeyguardState.LOCKSCREEN, to = KeyguardState.GONE)
|
||||||
|
)
|
||||||
testScheduler.runCurrent()
|
testScheduler.runCurrent()
|
||||||
|
|
||||||
// WHEN: Keyguard is shown again
|
// WHEN: Keyguard is shown again
|
||||||
keyguardRepository.setKeyguardShowing(true)
|
keyguardRepository.setKeyguardShowing(true)
|
||||||
|
keyguardTransitionRepository.sendTransitionStep(
|
||||||
|
TransitionStep(from = KeyguardState.GONE, to = KeyguardState.AOD)
|
||||||
|
)
|
||||||
testScheduler.runCurrent()
|
testScheduler.runCurrent()
|
||||||
|
|
||||||
// THEN: The notification is now recognized as "seen" and is filtered out.
|
// THEN: The notification is now recognized as "seen" and is filtered out.
|
||||||
@@ -354,11 +370,17 @@ class KeyguardCoordinatorTest : SysuiTestCase() {
|
|||||||
// GIVEN: Keyguard is showing, unseen notification is present
|
// GIVEN: Keyguard is showing, unseen notification is present
|
||||||
keyguardRepository.setKeyguardShowing(true)
|
keyguardRepository.setKeyguardShowing(true)
|
||||||
runKeyguardCoordinatorTest {
|
runKeyguardCoordinatorTest {
|
||||||
|
keyguardTransitionRepository.sendTransitionStep(
|
||||||
|
TransitionStep(from = KeyguardState.GONE, to = KeyguardState.LOCKSCREEN)
|
||||||
|
)
|
||||||
val fakeEntry = NotificationEntryBuilder().build()
|
val fakeEntry = NotificationEntryBuilder().build()
|
||||||
collectionListener.onEntryAdded(fakeEntry)
|
collectionListener.onEntryAdded(fakeEntry)
|
||||||
|
|
||||||
// WHEN: Keyguard is no longer showing
|
// WHEN: Keyguard is no longer showing
|
||||||
keyguardRepository.setKeyguardShowing(false)
|
keyguardRepository.setKeyguardShowing(false)
|
||||||
|
keyguardTransitionRepository.sendTransitionStep(
|
||||||
|
TransitionStep(from = KeyguardState.LOCKSCREEN, to = KeyguardState.GONE)
|
||||||
|
)
|
||||||
|
|
||||||
// WHEN: Keyguard is shown again
|
// WHEN: Keyguard is shown again
|
||||||
keyguardRepository.setKeyguardShowing(true)
|
keyguardRepository.setKeyguardShowing(true)
|
||||||
@@ -369,14 +391,212 @@ class KeyguardCoordinatorTest : SysuiTestCase() {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
fun unseenNotificationIsNotMarkedAsSeenIfNotOnKeyguardLongEnough() {
|
||||||
|
// GIVEN: Keyguard is showing, not dozing, unseen notification is present
|
||||||
|
keyguardRepository.setKeyguardShowing(true)
|
||||||
|
keyguardRepository.setIsDozing(false)
|
||||||
|
runKeyguardCoordinatorTest {
|
||||||
|
keyguardTransitionRepository.sendTransitionStep(
|
||||||
|
TransitionStep(from = KeyguardState.GONE, to = 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)
|
||||||
|
keyguardTransitionRepository.sendTransitionStep(
|
||||||
|
TransitionStep(from = KeyguardState.LOCKSCREEN, to = KeyguardState.GONE)
|
||||||
|
)
|
||||||
|
testScheduler.runCurrent()
|
||||||
|
|
||||||
|
// WHEN: Keyguard is shown again
|
||||||
|
keyguardRepository.setKeyguardShowing(true)
|
||||||
|
keyguardTransitionRepository.sendTransitionStep(
|
||||||
|
TransitionStep(from = KeyguardState.GONE, to = KeyguardState.LOCKSCREEN)
|
||||||
|
)
|
||||||
|
testScheduler.runCurrent()
|
||||||
|
|
||||||
|
// 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.sendTransitionStep(
|
||||||
|
TransitionStep(from = KeyguardState.GONE, to = KeyguardState.LOCKSCREEN)
|
||||||
|
)
|
||||||
|
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.sendTransitionStep(
|
||||||
|
TransitionStep(from = KeyguardState.LOCKSCREEN, to = KeyguardState.GONE)
|
||||||
|
)
|
||||||
|
testScheduler.runCurrent()
|
||||||
|
|
||||||
|
// WHEN: Keyguard is shown again
|
||||||
|
keyguardRepository.setKeyguardShowing(true)
|
||||||
|
keyguardTransitionRepository.sendTransitionStep(
|
||||||
|
TransitionStep(from = KeyguardState.GONE, to = KeyguardState.LOCKSCREEN)
|
||||||
|
)
|
||||||
|
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.sendTransitionStep(
|
||||||
|
TransitionStep(from = KeyguardState.GONE, to = KeyguardState.LOCKSCREEN)
|
||||||
|
)
|
||||||
|
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.sendTransitionStep(
|
||||||
|
TransitionStep(from = KeyguardState.LOCKSCREEN, to = KeyguardState.GONE)
|
||||||
|
)
|
||||||
|
testScheduler.runCurrent()
|
||||||
|
|
||||||
|
// WHEN: Keyguard is shown again
|
||||||
|
keyguardRepository.setKeyguardShowing(true)
|
||||||
|
keyguardTransitionRepository.sendTransitionStep(
|
||||||
|
TransitionStep(from = KeyguardState.GONE, to = KeyguardState.LOCKSCREEN)
|
||||||
|
)
|
||||||
|
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.sendTransitionStep(
|
||||||
|
TransitionStep(from = KeyguardState.GONE, to = KeyguardState.LOCKSCREEN)
|
||||||
|
)
|
||||||
|
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.sendTransitionStep(
|
||||||
|
TransitionStep(from = KeyguardState.LOCKSCREEN, to = KeyguardState.GONE)
|
||||||
|
)
|
||||||
|
testScheduler.runCurrent()
|
||||||
|
|
||||||
|
// WHEN: Keyguard is shown again
|
||||||
|
keyguardRepository.setKeyguardShowing(true)
|
||||||
|
keyguardTransitionRepository.sendTransitionStep(
|
||||||
|
TransitionStep(from = KeyguardState.GONE, to = KeyguardState.LOCKSCREEN)
|
||||||
|
)
|
||||||
|
testScheduler.runCurrent()
|
||||||
|
|
||||||
|
// THEN: The notification is considered unseen and is not filtered out.
|
||||||
|
assertThat(unseenFilter.shouldFilterOut(entry, 0L)).isFalse()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
private fun runKeyguardCoordinatorTest(
|
private fun runKeyguardCoordinatorTest(
|
||||||
testBlock: suspend KeyguardCoordinatorTestScope.() -> Unit
|
testBlock: suspend KeyguardCoordinatorTestScope.() -> Unit
|
||||||
) {
|
) {
|
||||||
val testDispatcher = UnconfinedTestDispatcher()
|
val testDispatcher = UnconfinedTestDispatcher()
|
||||||
val testScope = TestScope(testDispatcher)
|
val testScope = TestScope(testDispatcher)
|
||||||
val fakeSettings = FakeSettings().apply {
|
val fakeSettings =
|
||||||
putInt(Settings.Secure.LOCK_SCREEN_SHOW_ONLY_UNSEEN_NOTIFICATIONS, 1)
|
FakeSettings().apply {
|
||||||
}
|
putInt(Settings.Secure.LOCK_SCREEN_SHOW_ONLY_UNSEEN_NOTIFICATIONS, 1)
|
||||||
|
}
|
||||||
val seenNotificationsProvider = SeenNotificationsProviderImpl()
|
val seenNotificationsProvider = SeenNotificationsProviderImpl()
|
||||||
val keyguardCoordinator =
|
val keyguardCoordinator =
|
||||||
KeyguardCoordinator(
|
KeyguardCoordinator(
|
||||||
@@ -387,7 +607,6 @@ class KeyguardCoordinatorTest : SysuiTestCase() {
|
|||||||
keyguardRepository,
|
keyguardRepository,
|
||||||
keyguardTransitionRepository,
|
keyguardTransitionRepository,
|
||||||
mock<KeyguardCoordinatorLogger>(),
|
mock<KeyguardCoordinatorLogger>(),
|
||||||
notifPipelineFlags,
|
|
||||||
testScope.backgroundScope,
|
testScope.backgroundScope,
|
||||||
sectionHeaderVisibilityProvider,
|
sectionHeaderVisibilityProvider,
|
||||||
fakeSettings,
|
fakeSettings,
|
||||||
@@ -397,11 +616,12 @@ class KeyguardCoordinatorTest : SysuiTestCase() {
|
|||||||
keyguardCoordinator.attach(notifPipeline)
|
keyguardCoordinator.attach(notifPipeline)
|
||||||
testScope.runTest(dispatchTimeoutMs = 1.seconds.inWholeMilliseconds) {
|
testScope.runTest(dispatchTimeoutMs = 1.seconds.inWholeMilliseconds) {
|
||||||
KeyguardCoordinatorTestScope(
|
KeyguardCoordinatorTestScope(
|
||||||
keyguardCoordinator,
|
keyguardCoordinator,
|
||||||
testScope,
|
testScope,
|
||||||
seenNotificationsProvider,
|
seenNotificationsProvider,
|
||||||
fakeSettings,
|
fakeSettings,
|
||||||
).testBlock()
|
)
|
||||||
|
.testBlock()
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -414,10 +634,9 @@ class KeyguardCoordinatorTest : SysuiTestCase() {
|
|||||||
val testScheduler: TestCoroutineScheduler
|
val testScheduler: TestCoroutineScheduler
|
||||||
get() = scope.testScheduler
|
get() = scope.testScheduler
|
||||||
|
|
||||||
val onStateChangeListener: Consumer<String> =
|
val onStateChangeListener: Consumer<String> = withArgCaptor {
|
||||||
withArgCaptor {
|
verify(keyguardNotifVisibilityProvider).addOnStateChangedListener(capture())
|
||||||
verify(keyguardNotifVisibilityProvider).addOnStateChangedListener(capture())
|
}
|
||||||
}
|
|
||||||
|
|
||||||
val unseenFilter: NotifFilter
|
val unseenFilter: NotifFilter
|
||||||
get() = keyguardCoordinator.unseenNotifFilter
|
get() = keyguardCoordinator.unseenNotifFilter
|
||||||
@@ -426,11 +645,11 @@ class KeyguardCoordinatorTest : SysuiTestCase() {
|
|||||||
verify(notifPipeline).addCollectionListener(capture())
|
verify(notifPipeline).addCollectionListener(capture())
|
||||||
}
|
}
|
||||||
|
|
||||||
val onHeadsUpChangedListener: OnHeadsUpChangedListener get() =
|
val onHeadsUpChangedListener: OnHeadsUpChangedListener
|
||||||
withArgCaptor { verify(headsUpManager).addListener(capture()) }
|
get() = withArgCaptor { verify(headsUpManager).addListener(capture()) }
|
||||||
|
|
||||||
val statusBarStateListener: StatusBarStateController.StateListener get() =
|
val statusBarStateListener: StatusBarStateController.StateListener
|
||||||
withArgCaptor { verify(statusBarStateController).addCallback(capture()) }
|
get() = withArgCaptor { verify(statusBarStateController).addCallback(capture()) }
|
||||||
|
|
||||||
var showOnlyUnseenNotifsOnKeyguardSetting: Boolean
|
var showOnlyUnseenNotifsOnKeyguardSetting: Boolean
|
||||||
get() =
|
get() =
|
||||||
|
|||||||
Reference in New Issue
Block a user