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

Commit b40df8ff authored by Julia Tuttle's avatar Julia Tuttle Committed by Android (Google) Code Review
Browse files

Merge "Implement remaining peek suppression logic" into main

parents 3af72a8c a03910b2
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
}