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

Commit a03910b2 authored by Julia Tuttle's avatar Julia Tuttle
Browse files

Implement remaining peek suppression logic

Bug: 261728888
Test: atest NotificationInterruptStateProviderWrapperTest
Test: atest VisualInterruptionDecisionProviderImplTest
Flag: ACONFIG com.android.systemui.visual_interruptions_refactor DEVELOPMENT
Change-Id: I3f712a305b274a21565144c791b6eedb13340f99
parent f497595f
Loading
Loading
Loading
Loading
+71 −0
Original line number Diff line number Diff line
@@ -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
        }
}
+6 −0
Original line number Diff line number Diff line
@@ -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
    }
+94 −6
Original line number Diff line number Diff line
@@ -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
@@ -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()
@@ -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
}