summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
-rw-r--r--packages/SystemUI/src/com/android/systemui/statusbar/notification/interruption/CommonVisualInterruptionSuppressors.kt71
-rw-r--r--packages/SystemUI/src/com/android/systemui/statusbar/notification/interruption/VisualInterruptionDecisionProviderImpl.kt6
-rw-r--r--packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/interruption/VisualInterruptionDecisionProviderTestBase.kt100
3 files changed, 171 insertions, 6 deletions
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/interruption/CommonVisualInterruptionSuppressors.kt b/packages/SystemUI/src/com/android/systemui/statusbar/notification/interruption/CommonVisualInterruptionSuppressors.kt
index 75e04841d4b0..e4a7813ed918 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/interruption/CommonVisualInterruptionSuppressors.kt
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/interruption/CommonVisualInterruptionSuppressors.kt
@@ -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,
@@ -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
+ }
+}
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/interruption/VisualInterruptionDecisionProviderImpl.kt b/packages/SystemUI/src/com/android/systemui/statusbar/notification/interruption/VisualInterruptionDecisionProviderImpl.kt
index bae713486be5..f0cbd214937b 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/interruption/VisualInterruptionDecisionProviderImpl.kt
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/interruption/VisualInterruptionDecisionProviderImpl.kt
@@ -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
}
diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/interruption/VisualInterruptionDecisionProviderTestBase.kt b/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/interruption/VisualInterruptionDecisionProviderTestBase.kt
index 551119436c7f..11be85c70428 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/interruption/VisualInterruptionDecisionProviderTestBase.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/interruption/VisualInterruptionDecisionProviderTestBase.kt
@@ -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
@@ -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
@@ -127,6 +131,84 @@ abstract class VisualInterruptionDecisionProviderTestBase : SysuiTestCase() {
}
@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()
provider.addLegacySuppressor(neverSuppresses)
@@ -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
@@ -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)
@@ -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
}