Loading packages/SystemUI/src/com/android/systemui/statusbar/notification/interruption/CommonVisualInterruptionSuppressors.kt +71 −0 Original line number Diff line number Diff line Loading @@ -16,18 +16,25 @@ package com.android.systemui.statusbar.notification.interruption import android.app.NotificationManager.IMPORTANCE_HIGH import android.database.ContentObserver import android.hardware.display.AmbientDisplayConfiguration import android.os.Handler import android.os.PowerManager import android.provider.Settings.Global.HEADS_UP_NOTIFICATIONS_ENABLED import android.provider.Settings.Global.HEADS_UP_OFF import com.android.systemui.dagger.qualifiers.Main import com.android.systemui.plugins.statusbar.StatusBarStateController import com.android.systemui.settings.UserTracker import com.android.systemui.statusbar.StatusBarState.SHADE import com.android.systemui.statusbar.notification.collection.NotificationEntry import com.android.systemui.statusbar.notification.interruption.NotificationInterruptStateProviderImpl.MAX_HUN_WHEN_AGE_MS import com.android.systemui.statusbar.notification.interruption.VisualInterruptionType.PEEK import com.android.systemui.statusbar.notification.interruption.VisualInterruptionType.PULSE import com.android.systemui.statusbar.policy.BatteryController import com.android.systemui.statusbar.policy.HeadsUpManager import com.android.systemui.util.settings.GlobalSettings import com.android.systemui.util.time.SystemClock class PeekDisabledSuppressor( private val globalSettings: GlobalSettings, Loading Loading @@ -88,3 +95,67 @@ class PulseBatterySaverSuppressor(private val batteryController: BatteryControll ) { override fun shouldSuppress() = batteryController.isAodPowerSave() } class PeekPackageSnoozedSuppressor(private val headsUpManager: HeadsUpManager) : VisualInterruptionFilter(types = setOf(PEEK), reason = "package snoozed") { override fun shouldSuppress(entry: NotificationEntry) = when { // Assume any notification with an FSI is time-sensitive (like an alarm or incoming // call) and ignore whether HUNs have been snoozed for the package. entry.sbn.notification.fullScreenIntent != null -> false // Otherwise, check if the package is snoozed. else -> headsUpManager.isSnoozed(entry.sbn.packageName) } } class PeekAlreadyBubbledSuppressor(private val statusBarStateController: StatusBarStateController) : VisualInterruptionFilter(types = setOf(PEEK), reason = "already bubbled") { override fun shouldSuppress(entry: NotificationEntry) = when { statusBarStateController.state != SHADE -> false else -> entry.isBubble } } class PeekDndSuppressor() : VisualInterruptionFilter(types = setOf(PEEK), reason = "suppressed by DND") { override fun shouldSuppress(entry: NotificationEntry) = entry.shouldSuppressPeek() } class PeekNotImportantSuppressor() : VisualInterruptionFilter(types = setOf(PEEK), reason = "not important") { override fun shouldSuppress(entry: NotificationEntry) = entry.importance < IMPORTANCE_HIGH } class PeekDeviceNotInUseSuppressor( private val powerManager: PowerManager, private val statusBarStateController: StatusBarStateController ) : VisualInterruptionCondition(types = setOf(PEEK), reason = "not in use") { override fun shouldSuppress() = when { !powerManager.isScreenOn || statusBarStateController.isDreaming -> true else -> false } } class PeekOldWhenSuppressor(private val systemClock: SystemClock) : VisualInterruptionFilter(types = setOf(PEEK), reason = "old when") { private fun whenAge(entry: NotificationEntry) = systemClock.currentTimeMillis() - entry.sbn.notification.`when` override fun shouldSuppress(entry: NotificationEntry): Boolean = when { // Ignore a "when" of 0, as it is unlikely to be a meaningful timestamp. entry.sbn.notification.`when` <= 0L -> false // Assume all HUNs with FSIs, foreground services, or user-initiated jobs are // time-sensitive, regardless of their "when". entry.sbn.notification.fullScreenIntent != null || entry.sbn.notification.isForegroundService || entry.sbn.notification.isUserInitiatedJob -> false // Otherwise, check if the HUN's "when" is too old. else -> whenAge(entry) >= MAX_HUN_WHEN_AGE_MS } } packages/SystemUI/src/com/android/systemui/statusbar/notification/interruption/VisualInterruptionDecisionProviderImpl.kt +6 −0 Original line number Diff line number Diff line Loading @@ -56,6 +56,12 @@ constructor( addCondition(PeekDisabledSuppressor(globalSettings, headsUpManager, logger, mainHandler)) addCondition(PulseDisabledSuppressor(ambientDisplayConfiguration, userTracker)) addCondition(PulseBatterySaverSuppressor(batteryController)) addFilter(PeekPackageSnoozedSuppressor(headsUpManager)) addFilter(PeekAlreadyBubbledSuppressor(statusBarStateController)) addFilter(PeekDndSuppressor()) addFilter(PeekNotImportantSuppressor()) addCondition(PeekDeviceNotInUseSuppressor(powerManager, statusBarStateController)) addFilter(PeekOldWhenSuppressor(systemClock)) started = true } Loading packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/interruption/VisualInterruptionDecisionProviderTestBase.kt +94 −6 Original line number Diff line number Diff line Loading @@ -19,9 +19,11 @@ package com.android.systemui.statusbar.notification.interruption import android.app.ActivityManager import android.app.Notification import android.app.Notification.BubbleMetadata import android.app.Notification.FLAG_BUBBLE import android.app.NotificationChannel import android.app.NotificationManager.IMPORTANCE_DEFAULT import android.app.NotificationManager.IMPORTANCE_HIGH import android.app.NotificationManager.Policy.SUPPRESSED_EFFECT_PEEK import android.app.NotificationManager.VISIBILITY_NO_OVERRIDE import android.app.PendingIntent import android.app.PendingIntent.FLAG_MUTABLE Loading @@ -43,9 +45,11 @@ import com.android.systemui.statusbar.FakeStatusBarStateController import com.android.systemui.statusbar.NotificationEntryHelper.modifyRanking import com.android.systemui.statusbar.StatusBarState.KEYGUARD import com.android.systemui.statusbar.StatusBarState.SHADE import com.android.systemui.statusbar.StatusBarState.SHADE_LOCKED import com.android.systemui.statusbar.notification.NotifPipelineFlags import com.android.systemui.statusbar.notification.collection.NotificationEntry import com.android.systemui.statusbar.notification.collection.NotificationEntryBuilder import com.android.systemui.statusbar.notification.interruption.NotificationInterruptStateProviderImpl.MAX_HUN_WHEN_AGE_MS import com.android.systemui.statusbar.policy.DeviceProvisionedController import com.android.systemui.statusbar.policy.HeadsUpManager import com.android.systemui.statusbar.policy.KeyguardStateController Loading Loading @@ -126,6 +130,84 @@ abstract class VisualInterruptionDecisionProviderTestBase : SysuiTestCase() { assertShouldNotHeadsUp(buildPeekEntry()) } @Test fun testShouldNotPeek_packageSnoozed() { ensurePeekState { hunSnoozed = true } assertShouldNotHeadsUp(buildPeekEntry()) } @Test fun testShouldPeek_packageSnoozedButFsi() { ensurePeekState { hunSnoozed = true } assertShouldHeadsUp(buildFsiEntry()) } @Test fun testShouldNotPeek_alreadyBubbled() { ensurePeekState { statusBarState = SHADE } assertShouldNotHeadsUp(buildPeekEntry { isBubble = true }) } @Test fun testShouldPeek_isBubble_shadeLocked() { ensurePeekState { statusBarState = SHADE_LOCKED } assertShouldHeadsUp(buildPeekEntry { isBubble = true }) } @Test fun testShouldPeek_isBubble_keyguard() { ensurePeekState { statusBarState = KEYGUARD } assertShouldHeadsUp(buildPeekEntry { isBubble = true }) } @Test fun testShouldNotPeek_dnd() { ensurePeekState() assertShouldNotHeadsUp(buildPeekEntry { suppressedVisualEffects = SUPPRESSED_EFFECT_PEEK }) } @Test fun testShouldNotPeek_notImportant() { ensurePeekState() assertShouldNotHeadsUp(buildPeekEntry { importance = IMPORTANCE_DEFAULT }) } @Test fun testShouldNotPeek_screenOff() { ensurePeekState { isScreenOn = false } assertShouldNotHeadsUp(buildPeekEntry()) } @Test fun testShouldNotPeek_dreaming() { ensurePeekState { isDreaming = true } assertShouldNotHeadsUp(buildPeekEntry()) } @Test fun testShouldNotPeek_oldWhen() { ensurePeekState() assertShouldNotHeadsUp(buildPeekEntry { whenMs = whenAgo(MAX_HUN_WHEN_AGE_MS) }) } @Test fun testShouldPeek_notQuiteOldEnoughWhen() { ensurePeekState() assertShouldHeadsUp(buildPeekEntry { whenMs = whenAgo(MAX_HUN_WHEN_AGE_MS - 1) }) } @Test fun testShouldPeek_zeroWhen() { ensurePeekState() assertShouldHeadsUp(buildPeekEntry { whenMs = 0L }) } @Test fun testShouldPeek_oldWhenButFsi() { ensurePeekState() assertShouldHeadsUp(buildFsiEntry { whenMs = whenAgo(MAX_HUN_WHEN_AGE_MS) }) } @Test fun testShouldPeek_defaultLegacySuppressor() { ensurePeekState() Loading Loading @@ -380,6 +462,7 @@ abstract class VisualInterruptionDecisionProviderTestBase : SysuiTestCase() { var visibilityOverride: Int? = null var hasFsi = false var canBubble: Boolean? = null var isBubble = false var hasBubbleMetadata = false var bubbleSuppressNotification: Boolean? = null Loading Loading @@ -413,6 +496,11 @@ abstract class VisualInterruptionDecisionProviderTestBase : SysuiTestCase() { } } .build() .apply { if (isBubble) { flags = flags or FLAG_BUBBLE } } .let { NotificationEntryBuilder().setNotification(it) } .apply { setPkg(TEST_PACKAGE) Loading Loading @@ -449,18 +537,18 @@ abstract class VisualInterruptionDecisionProviderTestBase : SysuiTestCase() { run(block) } private fun buildFsiEntry(block: EntryBuilder.() -> Unit = {}) = buildEntry { importance = IMPORTANCE_HIGH hasFsi = true run(block) } private fun buildBubbleEntry(block: EntryBuilder.() -> Unit = {}) = buildEntry { canBubble = true hasBubbleMetadata = true run(block) } private fun buildFsiEntry(block: EntryBuilder.() -> Unit = {}) = buildEntry { importance = IMPORTANCE_HIGH hasFsi = true run(block) } private fun whenAgo(whenAgeMs: Long) = systemClock.currentTimeMillis() - whenAgeMs } Loading Loading
packages/SystemUI/src/com/android/systemui/statusbar/notification/interruption/CommonVisualInterruptionSuppressors.kt +71 −0 Original line number Diff line number Diff line Loading @@ -16,18 +16,25 @@ package com.android.systemui.statusbar.notification.interruption import android.app.NotificationManager.IMPORTANCE_HIGH import android.database.ContentObserver import android.hardware.display.AmbientDisplayConfiguration import android.os.Handler import android.os.PowerManager import android.provider.Settings.Global.HEADS_UP_NOTIFICATIONS_ENABLED import android.provider.Settings.Global.HEADS_UP_OFF import com.android.systemui.dagger.qualifiers.Main import com.android.systemui.plugins.statusbar.StatusBarStateController import com.android.systemui.settings.UserTracker import com.android.systemui.statusbar.StatusBarState.SHADE import com.android.systemui.statusbar.notification.collection.NotificationEntry import com.android.systemui.statusbar.notification.interruption.NotificationInterruptStateProviderImpl.MAX_HUN_WHEN_AGE_MS import com.android.systemui.statusbar.notification.interruption.VisualInterruptionType.PEEK import com.android.systemui.statusbar.notification.interruption.VisualInterruptionType.PULSE import com.android.systemui.statusbar.policy.BatteryController import com.android.systemui.statusbar.policy.HeadsUpManager import com.android.systemui.util.settings.GlobalSettings import com.android.systemui.util.time.SystemClock class PeekDisabledSuppressor( private val globalSettings: GlobalSettings, Loading Loading @@ -88,3 +95,67 @@ class PulseBatterySaverSuppressor(private val batteryController: BatteryControll ) { override fun shouldSuppress() = batteryController.isAodPowerSave() } class PeekPackageSnoozedSuppressor(private val headsUpManager: HeadsUpManager) : VisualInterruptionFilter(types = setOf(PEEK), reason = "package snoozed") { override fun shouldSuppress(entry: NotificationEntry) = when { // Assume any notification with an FSI is time-sensitive (like an alarm or incoming // call) and ignore whether HUNs have been snoozed for the package. entry.sbn.notification.fullScreenIntent != null -> false // Otherwise, check if the package is snoozed. else -> headsUpManager.isSnoozed(entry.sbn.packageName) } } class PeekAlreadyBubbledSuppressor(private val statusBarStateController: StatusBarStateController) : VisualInterruptionFilter(types = setOf(PEEK), reason = "already bubbled") { override fun shouldSuppress(entry: NotificationEntry) = when { statusBarStateController.state != SHADE -> false else -> entry.isBubble } } class PeekDndSuppressor() : VisualInterruptionFilter(types = setOf(PEEK), reason = "suppressed by DND") { override fun shouldSuppress(entry: NotificationEntry) = entry.shouldSuppressPeek() } class PeekNotImportantSuppressor() : VisualInterruptionFilter(types = setOf(PEEK), reason = "not important") { override fun shouldSuppress(entry: NotificationEntry) = entry.importance < IMPORTANCE_HIGH } class PeekDeviceNotInUseSuppressor( private val powerManager: PowerManager, private val statusBarStateController: StatusBarStateController ) : VisualInterruptionCondition(types = setOf(PEEK), reason = "not in use") { override fun shouldSuppress() = when { !powerManager.isScreenOn || statusBarStateController.isDreaming -> true else -> false } } class PeekOldWhenSuppressor(private val systemClock: SystemClock) : VisualInterruptionFilter(types = setOf(PEEK), reason = "old when") { private fun whenAge(entry: NotificationEntry) = systemClock.currentTimeMillis() - entry.sbn.notification.`when` override fun shouldSuppress(entry: NotificationEntry): Boolean = when { // Ignore a "when" of 0, as it is unlikely to be a meaningful timestamp. entry.sbn.notification.`when` <= 0L -> false // Assume all HUNs with FSIs, foreground services, or user-initiated jobs are // time-sensitive, regardless of their "when". entry.sbn.notification.fullScreenIntent != null || entry.sbn.notification.isForegroundService || entry.sbn.notification.isUserInitiatedJob -> false // Otherwise, check if the HUN's "when" is too old. else -> whenAge(entry) >= MAX_HUN_WHEN_AGE_MS } }
packages/SystemUI/src/com/android/systemui/statusbar/notification/interruption/VisualInterruptionDecisionProviderImpl.kt +6 −0 Original line number Diff line number Diff line Loading @@ -56,6 +56,12 @@ constructor( addCondition(PeekDisabledSuppressor(globalSettings, headsUpManager, logger, mainHandler)) addCondition(PulseDisabledSuppressor(ambientDisplayConfiguration, userTracker)) addCondition(PulseBatterySaverSuppressor(batteryController)) addFilter(PeekPackageSnoozedSuppressor(headsUpManager)) addFilter(PeekAlreadyBubbledSuppressor(statusBarStateController)) addFilter(PeekDndSuppressor()) addFilter(PeekNotImportantSuppressor()) addCondition(PeekDeviceNotInUseSuppressor(powerManager, statusBarStateController)) addFilter(PeekOldWhenSuppressor(systemClock)) started = true } Loading
packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/interruption/VisualInterruptionDecisionProviderTestBase.kt +94 −6 Original line number Diff line number Diff line Loading @@ -19,9 +19,11 @@ package com.android.systemui.statusbar.notification.interruption import android.app.ActivityManager import android.app.Notification import android.app.Notification.BubbleMetadata import android.app.Notification.FLAG_BUBBLE import android.app.NotificationChannel import android.app.NotificationManager.IMPORTANCE_DEFAULT import android.app.NotificationManager.IMPORTANCE_HIGH import android.app.NotificationManager.Policy.SUPPRESSED_EFFECT_PEEK import android.app.NotificationManager.VISIBILITY_NO_OVERRIDE import android.app.PendingIntent import android.app.PendingIntent.FLAG_MUTABLE Loading @@ -43,9 +45,11 @@ import com.android.systemui.statusbar.FakeStatusBarStateController import com.android.systemui.statusbar.NotificationEntryHelper.modifyRanking import com.android.systemui.statusbar.StatusBarState.KEYGUARD import com.android.systemui.statusbar.StatusBarState.SHADE import com.android.systemui.statusbar.StatusBarState.SHADE_LOCKED import com.android.systemui.statusbar.notification.NotifPipelineFlags import com.android.systemui.statusbar.notification.collection.NotificationEntry import com.android.systemui.statusbar.notification.collection.NotificationEntryBuilder import com.android.systemui.statusbar.notification.interruption.NotificationInterruptStateProviderImpl.MAX_HUN_WHEN_AGE_MS import com.android.systemui.statusbar.policy.DeviceProvisionedController import com.android.systemui.statusbar.policy.HeadsUpManager import com.android.systemui.statusbar.policy.KeyguardStateController Loading Loading @@ -126,6 +130,84 @@ abstract class VisualInterruptionDecisionProviderTestBase : SysuiTestCase() { assertShouldNotHeadsUp(buildPeekEntry()) } @Test fun testShouldNotPeek_packageSnoozed() { ensurePeekState { hunSnoozed = true } assertShouldNotHeadsUp(buildPeekEntry()) } @Test fun testShouldPeek_packageSnoozedButFsi() { ensurePeekState { hunSnoozed = true } assertShouldHeadsUp(buildFsiEntry()) } @Test fun testShouldNotPeek_alreadyBubbled() { ensurePeekState { statusBarState = SHADE } assertShouldNotHeadsUp(buildPeekEntry { isBubble = true }) } @Test fun testShouldPeek_isBubble_shadeLocked() { ensurePeekState { statusBarState = SHADE_LOCKED } assertShouldHeadsUp(buildPeekEntry { isBubble = true }) } @Test fun testShouldPeek_isBubble_keyguard() { ensurePeekState { statusBarState = KEYGUARD } assertShouldHeadsUp(buildPeekEntry { isBubble = true }) } @Test fun testShouldNotPeek_dnd() { ensurePeekState() assertShouldNotHeadsUp(buildPeekEntry { suppressedVisualEffects = SUPPRESSED_EFFECT_PEEK }) } @Test fun testShouldNotPeek_notImportant() { ensurePeekState() assertShouldNotHeadsUp(buildPeekEntry { importance = IMPORTANCE_DEFAULT }) } @Test fun testShouldNotPeek_screenOff() { ensurePeekState { isScreenOn = false } assertShouldNotHeadsUp(buildPeekEntry()) } @Test fun testShouldNotPeek_dreaming() { ensurePeekState { isDreaming = true } assertShouldNotHeadsUp(buildPeekEntry()) } @Test fun testShouldNotPeek_oldWhen() { ensurePeekState() assertShouldNotHeadsUp(buildPeekEntry { whenMs = whenAgo(MAX_HUN_WHEN_AGE_MS) }) } @Test fun testShouldPeek_notQuiteOldEnoughWhen() { ensurePeekState() assertShouldHeadsUp(buildPeekEntry { whenMs = whenAgo(MAX_HUN_WHEN_AGE_MS - 1) }) } @Test fun testShouldPeek_zeroWhen() { ensurePeekState() assertShouldHeadsUp(buildPeekEntry { whenMs = 0L }) } @Test fun testShouldPeek_oldWhenButFsi() { ensurePeekState() assertShouldHeadsUp(buildFsiEntry { whenMs = whenAgo(MAX_HUN_WHEN_AGE_MS) }) } @Test fun testShouldPeek_defaultLegacySuppressor() { ensurePeekState() Loading Loading @@ -380,6 +462,7 @@ abstract class VisualInterruptionDecisionProviderTestBase : SysuiTestCase() { var visibilityOverride: Int? = null var hasFsi = false var canBubble: Boolean? = null var isBubble = false var hasBubbleMetadata = false var bubbleSuppressNotification: Boolean? = null Loading Loading @@ -413,6 +496,11 @@ abstract class VisualInterruptionDecisionProviderTestBase : SysuiTestCase() { } } .build() .apply { if (isBubble) { flags = flags or FLAG_BUBBLE } } .let { NotificationEntryBuilder().setNotification(it) } .apply { setPkg(TEST_PACKAGE) Loading Loading @@ -449,18 +537,18 @@ abstract class VisualInterruptionDecisionProviderTestBase : SysuiTestCase() { run(block) } private fun buildFsiEntry(block: EntryBuilder.() -> Unit = {}) = buildEntry { importance = IMPORTANCE_HIGH hasFsi = true run(block) } private fun buildBubbleEntry(block: EntryBuilder.() -> Unit = {}) = buildEntry { canBubble = true hasBubbleMetadata = true run(block) } private fun buildFsiEntry(block: EntryBuilder.() -> Unit = {}) = buildEntry { importance = IMPORTANCE_HIGH hasFsi = true run(block) } private fun whenAgo(whenAgeMs: Long) = systemClock.currentTimeMillis() - whenAgeMs } Loading