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

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

Merge changes I563a12bb,I3cda6c62,I316150ef into main

* changes:
  Add VisualInterruptionSuppressor logic tests
  Implement common suppression logic
  Implement remaining bubble suppression logic
parents 4ce39bf5 5f9a3dd3
Loading
Loading
Loading
Loading
+37 −0
Original line number Diff line number Diff line
@@ -16,6 +16,7 @@

package com.android.systemui.statusbar.notification.interruption

import android.app.Notification.BubbleMetadata
import android.app.Notification.VISIBILITY_PRIVATE
import android.app.NotificationManager.IMPORTANCE_DEFAULT
import android.app.NotificationManager.IMPORTANCE_HIGH
@@ -31,6 +32,7 @@ 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.BUBBLE
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
@@ -180,3 +182,38 @@ class PulseLowImportanceSuppressor() :
    VisualInterruptionFilter(types = setOf(PULSE), reason = "importance less than DEFAULT") {
    override fun shouldSuppress(entry: NotificationEntry) = entry.importance < IMPORTANCE_DEFAULT
}

class HunGroupAlertBehaviorSuppressor() :
    VisualInterruptionFilter(
        types = setOf(PEEK, PULSE),
        reason = "suppressive group alert behavior"
    ) {
    override fun shouldSuppress(entry: NotificationEntry) =
        entry.sbn.let { it.isGroup && it.notification.suppressAlertingDueToGrouping() }
}

class HunJustLaunchedFsiSuppressor() :
    VisualInterruptionFilter(types = setOf(PEEK, PULSE), reason = "just launched FSI") {
    override fun shouldSuppress(entry: NotificationEntry) = entry.hasJustLaunchedFullScreenIntent()
}

class BubbleNotAllowedSuppressor() :
    VisualInterruptionFilter(types = setOf(BUBBLE), reason = "not allowed") {
    override fun shouldSuppress(entry: NotificationEntry) = !entry.canBubble()
}

class BubbleNoMetadataSuppressor() :
    VisualInterruptionFilter(types = setOf(BUBBLE), reason = "no bubble metadata") {

    private fun isValidMetadata(metadata: BubbleMetadata?) =
        metadata != null && (metadata.intent != null || metadata.shortcutId != null)

    override fun shouldSuppress(entry: NotificationEntry) = !isValidMetadata(entry.bubbleMetadata)
}

class AlertKeyguardVisibilitySuppressor(
    private val keyguardNotificationVisibilityProvider: KeyguardNotificationVisibilityProvider
) : VisualInterruptionFilter(types = setOf(PEEK, PULSE, BUBBLE), reason = "hidden on keyguard") {
    override fun shouldSuppress(entry: NotificationEntry) =
        keyguardNotificationVisibilityProvider.shouldHideNotification(entry)
}
+17 −0
Original line number Diff line number Diff line
@@ -19,6 +19,7 @@ import android.hardware.display.AmbientDisplayConfiguration
import android.os.Handler
import android.os.PowerManager
import android.util.Log
import com.android.internal.annotations.VisibleForTesting
import com.android.systemui.dagger.qualifiers.Main
import com.android.systemui.plugins.statusbar.StatusBarStateController
import com.android.systemui.settings.UserTracker
@@ -41,6 +42,7 @@ constructor(
    private val batteryController: BatteryController,
    private val globalSettings: GlobalSettings,
    private val headsUpManager: HeadsUpManager,
    private val keyguardNotificationVisibilityProvider: KeyguardNotificationVisibilityProvider,
    private val logger: NotificationInterruptLogger,
    @Main private val mainHandler: Handler,
    private val powerManager: PowerManager,
@@ -65,6 +67,11 @@ constructor(
        addFilter(PulseEffectSuppressor())
        addFilter(PulseLockscreenVisibilityPrivateSuppressor())
        addFilter(PulseLowImportanceSuppressor())
        addFilter(BubbleNotAllowedSuppressor())
        addFilter(BubbleNoMetadataSuppressor())
        addFilter(HunGroupAlertBehaviorSuppressor())
        addFilter(HunJustLaunchedFsiSuppressor())
        addFilter(AlertKeyguardVisibilitySuppressor(keyguardNotificationVisibilityProvider))

        started = true
    }
@@ -100,11 +107,21 @@ constructor(
        condition.start()
    }

    @VisibleForTesting
    fun removeCondition(condition: VisualInterruptionCondition) {
        conditions.remove(condition)
    }

    fun addFilter(filter: VisualInterruptionFilter) {
        filters.add(filter)
        filter.start()
    }

    @VisibleForTesting
    fun removeFilter(filter: VisualInterruptionFilter) {
        filters.remove(filter)
    }

    override fun makeUnloggedHeadsUpDecision(entry: NotificationEntry): Decision {
        check(started)
        return makeHeadsUpDecision(entry)
+181 −0
Original line number Diff line number Diff line
@@ -18,6 +18,11 @@ package com.android.systemui.statusbar.notification.interruption

import android.testing.AndroidTestingRunner
import androidx.test.filters.SmallTest
import com.android.systemui.statusbar.notification.collection.NotificationEntry
import com.android.systemui.statusbar.notification.interruption.VisualInterruptionType.BUBBLE
import com.android.systemui.statusbar.notification.interruption.VisualInterruptionType.PEEK
import com.android.systemui.statusbar.notification.interruption.VisualInterruptionType.PULSE
import org.junit.Test
import org.junit.runner.RunWith

@SmallTest
@@ -29,6 +34,7 @@ class VisualInterruptionDecisionProviderImplTest : VisualInterruptionDecisionPro
            batteryController,
            globalSettings,
            headsUpManager,
            keyguardNotificationVisibilityProvider,
            logger,
            mainHandler,
            powerManager,
@@ -37,4 +43,179 @@ class VisualInterruptionDecisionProviderImplTest : VisualInterruptionDecisionPro
            userTracker,
        )
    }

    @Test
    fun testNothingCondition_suppressesNothing() {
        withCondition(TestCondition(types = emptySet()) { true }) {
            assertPeekNotSuppressed()
            assertPulseNotSuppressed()
            assertBubbleNotSuppressed()
        }
    }

    @Test
    fun testNothingFilter_suppressesNothing() {
        withFilter(TestFilter(types = emptySet()) { true }) {
            assertPeekNotSuppressed()
            assertPulseNotSuppressed()
            assertBubbleNotSuppressed()
        }
    }

    @Test
    fun testPeekCondition_suppressesOnlyPeek() {
        withCondition(TestCondition(types = setOf(PEEK)) { true }) {
            assertPeekSuppressed()
            assertPulseNotSuppressed()
            assertBubbleNotSuppressed()
        }
    }

    @Test
    fun testPeekFilter_suppressesOnlyPeek() {
        withFilter(TestFilter(types = setOf(PEEK)) { true }) {
            assertPeekSuppressed()
            assertPulseNotSuppressed()
            assertBubbleNotSuppressed()
        }
    }

    @Test
    fun testPulseCondition_suppressesOnlyPulse() {
        withCondition(TestCondition(types = setOf(PULSE)) { true }) {
            assertPeekNotSuppressed()
            assertPulseSuppressed()
            assertBubbleNotSuppressed()
        }
    }

    @Test
    fun testPulseFilter_suppressesOnlyPulse() {
        withFilter(TestFilter(types = setOf(PULSE)) { true }) {
            assertPeekNotSuppressed()
            assertPulseSuppressed()
            assertBubbleNotSuppressed()
        }
    }

    @Test
    fun testBubbleCondition_suppressesOnlyBubble() {
        withCondition(TestCondition(types = setOf(BUBBLE)) { true }) {
            assertPeekNotSuppressed()
            assertPulseNotSuppressed()
            assertBubbleSuppressed()
        }
    }

    @Test
    fun testBubbleFilter_suppressesOnlyBubble() {
        withFilter(TestFilter(types = setOf(BUBBLE)) { true }) {
            assertPeekNotSuppressed()
            assertPulseNotSuppressed()
            assertBubbleSuppressed()
        }
    }

    @Test
    fun testCondition_differentState() {
        ensurePeekState()
        val entry = buildPeekEntry()

        var stateShouldSuppress = false
        withCondition(TestCondition(types = setOf(PEEK)) { stateShouldSuppress }) {
            assertShouldHeadsUp(entry)

            stateShouldSuppress = true
            assertShouldNotHeadsUp(entry)

            stateShouldSuppress = false
            assertShouldHeadsUp(entry)
        }
    }

    @Test
    fun testFilter_differentState() {
        ensurePeekState()
        val entry = buildPeekEntry()

        var stateShouldSuppress = false
        withFilter(TestFilter(types = setOf(PEEK)) { stateShouldSuppress }) {
            assertShouldHeadsUp(entry)

            stateShouldSuppress = true
            assertShouldNotHeadsUp(entry)

            stateShouldSuppress = false
            assertShouldHeadsUp(entry)
        }
    }

    @Test
    fun testFilter_differentNotif() {
        ensurePeekState()

        val suppressedEntry = buildPeekEntry()
        val unsuppressedEntry = buildPeekEntry()

        withFilter(TestFilter(types = setOf(PEEK)) { it == suppressedEntry }) {
            assertShouldNotHeadsUp(suppressedEntry)
            assertShouldHeadsUp(unsuppressedEntry)
        }
    }

    private fun assertPeekSuppressed() {
        ensurePeekState()
        assertShouldNotHeadsUp(buildPeekEntry())
    }

    private fun assertPeekNotSuppressed() {
        ensurePeekState()
        assertShouldHeadsUp(buildPeekEntry())
    }

    private fun assertPulseSuppressed() {
        ensurePulseState()
        assertShouldNotHeadsUp(buildPulseEntry())
    }

    private fun assertPulseNotSuppressed() {
        ensurePulseState()
        assertShouldHeadsUp(buildPulseEntry())
    }

    private fun assertBubbleSuppressed() {
        ensureBubbleState()
        assertShouldNotBubble(buildBubbleEntry())
    }

    private fun assertBubbleNotSuppressed() {
        ensureBubbleState()
        assertShouldBubble(buildBubbleEntry())
    }

    private fun withCondition(condition: VisualInterruptionCondition, block: () -> Unit) {
        provider.addCondition(condition)
        block()
        provider.removeCondition(condition)
    }

    private fun withFilter(filter: VisualInterruptionFilter, block: () -> Unit) {
        provider.addFilter(filter)
        block()
        provider.removeFilter(filter)
    }

    private class TestCondition(
        types: Set<VisualInterruptionType>,
        val onShouldSuppress: () -> Boolean
    ) : VisualInterruptionCondition(types = types, reason = "") {
        override fun shouldSuppress(): Boolean = onShouldSuppress()
    }

    private class TestFilter(
        types: Set<VisualInterruptionType>,
        val onShouldSuppress: (NotificationEntry) -> Boolean = { true }
    ) : VisualInterruptionFilter(types = types, reason = "") {
        override fun shouldSuppress(entry: NotificationEntry) = onShouldSuppress(entry)
    }
}
+200 −37
Original line number Diff line number Diff line
@@ -20,6 +20,9 @@ import android.app.ActivityManager
import android.app.Notification
import android.app.Notification.BubbleMetadata
import android.app.Notification.FLAG_BUBBLE
import android.app.Notification.GROUP_ALERT_ALL
import android.app.Notification.GROUP_ALERT_CHILDREN
import android.app.Notification.GROUP_ALERT_SUMMARY
import android.app.Notification.VISIBILITY_PRIVATE
import android.app.NotificationChannel
import android.app.NotificationManager.IMPORTANCE_DEFAULT
@@ -305,10 +308,132 @@ abstract class VisualInterruptionDecisionProviderTestBase : SysuiTestCase() {
        assertShouldNotHeadsUp(buildPulseEntry { importance = IMPORTANCE_LOW })
    }

    private fun withPeekAndPulseEntry(
        extendEntry: EntryBuilder.() -> Unit,
        block: (NotificationEntry) -> Unit
    ) {
        ensurePeekState()
        block(buildPeekEntry(extendEntry))

        ensurePulseState()
        block(buildPulseEntry(extendEntry))
    }

    @Test
    fun testShouldHeadsUp_groupedSummaryNotif_groupAlertAll() {
        withPeekAndPulseEntry({
            isGrouped = true
            isGroupSummary = true
            groupAlertBehavior = GROUP_ALERT_ALL
        }) {
            assertShouldHeadsUp(it)
        }
    }

    @Test
    fun testShouldHeadsUp_groupedSummaryNotif_groupAlertSummary() {
        withPeekAndPulseEntry({
            isGrouped = true
            isGroupSummary = true
            groupAlertBehavior = GROUP_ALERT_SUMMARY
        }) {
            assertShouldHeadsUp(it)
        }
    }

    @Test
    fun testShouldNotHeadsUp_groupedSummaryNotif_groupAlertChildren() {
        withPeekAndPulseEntry({
            isGrouped = true
            isGroupSummary = true
            groupAlertBehavior = GROUP_ALERT_CHILDREN
        }) {
            assertShouldNotHeadsUp(it)
        }
    }

    @Test
    fun testShouldHeadsUp_ungroupedSummaryNotif_groupAlertChildren() {
        withPeekAndPulseEntry({
            isGrouped = false
            isGroupSummary = true
            groupAlertBehavior = GROUP_ALERT_CHILDREN
        }) {
            assertShouldHeadsUp(it)
        }
    }

    @Test
    fun testShouldHeadsUp_groupedChildNotif_groupAlertAll() {
        withPeekAndPulseEntry({
            isGrouped = true
            isGroupSummary = false
            groupAlertBehavior = GROUP_ALERT_ALL
        }) {
            assertShouldHeadsUp(it)
        }
    }

    @Test
    fun testShouldHeadsUp_groupedChildNotif_groupAlertChildren() {
        withPeekAndPulseEntry({
            isGrouped = true
            isGroupSummary = false
            groupAlertBehavior = GROUP_ALERT_CHILDREN
        }) {
            assertShouldHeadsUp(it)
        }
    }

    @Test
    fun testShouldNotHeadsUp_groupedChildNotif_groupAlertSummary() {
        withPeekAndPulseEntry({
            isGrouped = true
            isGroupSummary = false
            groupAlertBehavior = GROUP_ALERT_SUMMARY
        }) {
            assertShouldNotHeadsUp(it)
        }
    }

    @Test
    fun testShouldHeadsUp_ungroupedChildNotif_groupAlertSummary() {
        withPeekAndPulseEntry({
            isGrouped = false
            isGroupSummary = false
            groupAlertBehavior = GROUP_ALERT_SUMMARY
        }) {
            assertShouldHeadsUp(it)
        }
    }

    @Test
    fun testShouldBubble() {
    fun testShouldNotHeadsUp_justLaunchedFsi() {
        withPeekAndPulseEntry({ hasJustLaunchedFsi = true }) { assertShouldNotHeadsUp(it) }
    }

    @Test
    fun testShouldBubble_withIntentAndIcon() {
        ensureBubbleState()
        assertShouldBubble(buildBubbleEntry())
        assertShouldBubble(buildBubbleEntry { bubbleIsShortcut = false })
    }

    @Test
    fun testShouldBubble_withShortcut() {
        ensureBubbleState()
        assertShouldBubble(buildBubbleEntry { bubbleIsShortcut = true })
    }

    @Test
    fun testShouldNotBubble_notAllowed() {
        ensureBubbleState()
        assertShouldNotBubble(buildBubbleEntry { canBubble = false })
    }

    @Test
    fun testShouldNotBubble_noBubbleMetadata() {
        ensureBubbleState()
        assertShouldNotBubble(buildBubbleEntry { hasBubbleMetadata = false })
    }

    @Test
@@ -339,6 +464,18 @@ abstract class VisualInterruptionDecisionProviderTestBase : SysuiTestCase() {
        assertShouldBubble(buildBubbleEntry())
    }

    @Test
    fun testShouldNotAlert_hiddenOnKeyguard() {
        ensurePeekState({ keyguardShouldHideNotification = true })
        assertShouldNotHeadsUp(buildPeekEntry())

        ensurePulseState({ keyguardShouldHideNotification = true })
        assertShouldNotHeadsUp(buildPulseEntry())

        ensureBubbleState({ keyguardShouldHideNotification = true })
        assertShouldNotBubble(buildBubbleEntry())
    }

    @Test
    fun testShouldFsi_notInteractive() {
        ensureNotInteractiveFsiState()
@@ -357,7 +494,7 @@ abstract class VisualInterruptionDecisionProviderTestBase : SysuiTestCase() {
        assertShouldFsi(buildFsiEntry())
    }

    private data class State(
    protected data class State(
        var hunSettingEnabled: Boolean? = null,
        var hunSnoozed: Boolean? = null,
        var isAodPowerSave: Boolean? = null,
@@ -370,7 +507,7 @@ abstract class VisualInterruptionDecisionProviderTestBase : SysuiTestCase() {
        var statusBarState: Int? = null,
    )

    private fun setState(state: State): Unit =
    protected fun setState(state: State): Unit =
        state.run {
            hunSettingEnabled?.let {
                val newSetting = if (it) HEADS_UP_ON else HEADS_UP_OFF
@@ -401,7 +538,7 @@ abstract class VisualInterruptionDecisionProviderTestBase : SysuiTestCase() {
            statusBarState?.let { statusBarStateController.state = it }
        }

    private fun ensureState(block: State.() -> Unit) =
    protected fun ensureState(block: State.() -> Unit) =
        State()
            .apply {
                keyguardShouldHideNotification = false
@@ -409,7 +546,7 @@ abstract class VisualInterruptionDecisionProviderTestBase : SysuiTestCase() {
            }
            .run(this::setState)

    private fun ensurePeekState(block: State.() -> Unit = {}) = ensureState {
    protected fun ensurePeekState(block: State.() -> Unit = {}) = ensureState {
        hunSettingEnabled = true
        hunSnoozed = false
        isDozing = false
@@ -418,67 +555,67 @@ abstract class VisualInterruptionDecisionProviderTestBase : SysuiTestCase() {
        run(block)
    }

    private fun ensurePulseState(block: State.() -> Unit = {}) = ensureState {
    protected fun ensurePulseState(block: State.() -> Unit = {}) = ensureState {
        isAodPowerSave = false
        isDozing = true
        pulseOnNotificationsEnabled = true
        run(block)
    }

    private fun ensureBubbleState(block: State.() -> Unit = {}) = ensureState(block)
    protected fun ensureBubbleState(block: State.() -> Unit = {}) = ensureState(block)

    private fun ensureNotInteractiveFsiState(block: State.() -> Unit = {}) = ensureState {
    protected fun ensureNotInteractiveFsiState(block: State.() -> Unit = {}) = ensureState {
        isDreaming = false
        isInteractive = false
        statusBarState = SHADE
        run(block)
    }

    private fun ensureDreamingFsiState(block: State.() -> Unit = {}) = ensureState {
    protected fun ensureDreamingFsiState(block: State.() -> Unit = {}) = ensureState {
        isDreaming = true
        isInteractive = true
        statusBarState = SHADE
        run(block)
    }

    private fun ensureKeyguardFsiState(block: State.() -> Unit = {}) = ensureState {
    protected fun ensureKeyguardFsiState(block: State.() -> Unit = {}) = ensureState {
        isDreaming = false
        isInteractive = true
        statusBarState = KEYGUARD
        run(block)
    }

    private fun assertShouldHeadsUp(entry: NotificationEntry) =
    protected fun assertShouldHeadsUp(entry: NotificationEntry) =
        provider.makeUnloggedHeadsUpDecision(entry).let {
            assertTrue("unexpected suppressed HUN: ${it.logReason}", it.shouldInterrupt)
        }

    private fun assertShouldNotHeadsUp(entry: NotificationEntry) =
    protected fun assertShouldNotHeadsUp(entry: NotificationEntry) =
        provider.makeUnloggedHeadsUpDecision(entry).let {
            assertFalse("unexpected unsuppressed HUN: ${it.logReason}", it.shouldInterrupt)
        }

    private fun assertShouldBubble(entry: NotificationEntry) =
    protected fun assertShouldBubble(entry: NotificationEntry) =
        provider.makeAndLogBubbleDecision(entry).let {
            assertTrue("unexpected suppressed bubble: ${it.logReason}", it.shouldInterrupt)
        }

    private fun assertShouldNotBubble(entry: NotificationEntry) =
    protected fun assertShouldNotBubble(entry: NotificationEntry) =
        provider.makeAndLogBubbleDecision(entry).let {
            assertFalse("unexpected unsuppressed bubble: ${it.logReason}", it.shouldInterrupt)
        }

    private fun assertShouldFsi(entry: NotificationEntry) =
    protected fun assertShouldFsi(entry: NotificationEntry) =
        provider.makeUnloggedFullScreenIntentDecision(entry).let {
            assertTrue("unexpected suppressed FSI: ${it.logReason}", it.shouldInterrupt)
        }

    private fun assertShouldNotFsi(entry: NotificationEntry) =
    protected fun assertShouldNotFsi(entry: NotificationEntry) =
        provider.makeUnloggedFullScreenIntentDecision(entry).let {
            assertFalse("unexpected unsuppressed FSI: ${it.logReason}", it.shouldInterrupt)
        }

    private class EntryBuilder(val context: Context) {
    protected class EntryBuilder(val context: Context) {
        var importance = IMPORTANCE_DEFAULT
        var suppressedVisualEffects: Int? = null
        var whenMs: Long? = null
@@ -487,9 +624,18 @@ abstract class VisualInterruptionDecisionProviderTestBase : SysuiTestCase() {
        var canBubble: Boolean? = null
        var isBubble = false
        var hasBubbleMetadata = false
        var bubbleSuppressNotification: Boolean? = null

        private fun buildBubbleMetadata() =
        var bubbleIsShortcut = false
        var bubbleSuppressesNotification: Boolean? = null
        var isGrouped = false
        var isGroupSummary: Boolean? = null
        var groupAlertBehavior: Int? = null
        var hasJustLaunchedFsi = false

        private fun buildBubbleMetadata(): BubbleMetadata {
            val builder =
                if (bubbleIsShortcut) {
                    BubbleMetadata.Builder(context.packageName + ":test_shortcut_id")
                } else {
                    BubbleMetadata.Builder(
                        PendingIntent.getActivity(
                            context,
@@ -499,8 +645,12 @@ abstract class VisualInterruptionDecisionProviderTestBase : SysuiTestCase() {
                        ),
                        Icon.createWithResource(context.resources, R.drawable.android)
                    )
                .apply { bubbleSuppressNotification?.let { setSuppressNotification(it) } }
                .build()
                }

            bubbleSuppressesNotification?.let { builder.setSuppressNotification(it) }

            return builder.build()
        }

        fun build() =
            Notification.Builder(context, TEST_CHANNEL_ID)
@@ -517,6 +667,14 @@ abstract class VisualInterruptionDecisionProviderTestBase : SysuiTestCase() {
                    if (hasBubbleMetadata) {
                        setBubbleMetadata(buildBubbleMetadata())
                    }

                    if (isGrouped) {
                        setGroup(TEST_GROUP_KEY)
                    }

                    isGroupSummary?.let { setGroupSummary(it) }

                    groupAlertBehavior?.let { setGroupAlertBehavior(it) }
                }
                .build()
                .apply {
@@ -537,6 +695,10 @@ abstract class VisualInterruptionDecisionProviderTestBase : SysuiTestCase() {
                }
                .build()!!
                .also {
                    if (hasJustLaunchedFsi) {
                        it.notifyFullScreenIntentLaunched()
                    }

                    modifyRanking(it)
                        .apply {
                            suppressedVisualEffects?.let { setSuppressedVisualEffects(it) }
@@ -546,27 +708,27 @@ abstract class VisualInterruptionDecisionProviderTestBase : SysuiTestCase() {
                }
    }

    private fun buildEntry(block: EntryBuilder.() -> Unit) =
    protected fun buildEntry(block: EntryBuilder.() -> Unit) =
        EntryBuilder(context).also(block).build()

    private fun buildPeekEntry(block: EntryBuilder.() -> Unit = {}) = buildEntry {
    protected fun buildPeekEntry(block: EntryBuilder.() -> Unit = {}) = buildEntry {
        importance = IMPORTANCE_HIGH
        run(block)
    }

    private fun buildPulseEntry(block: EntryBuilder.() -> Unit = {}) = buildEntry {
    protected fun buildPulseEntry(block: EntryBuilder.() -> Unit = {}) = buildEntry {
        importance = IMPORTANCE_DEFAULT
        visibilityOverride = VISIBILITY_NO_OVERRIDE
        run(block)
    }

    private fun buildBubbleEntry(block: EntryBuilder.() -> Unit = {}) = buildEntry {
    protected fun buildBubbleEntry(block: EntryBuilder.() -> Unit = {}) = buildEntry {
        canBubble = true
        hasBubbleMetadata = true
        run(block)
    }

    private fun buildFsiEntry(block: EntryBuilder.() -> Unit = {}) = buildEntry {
    protected fun buildFsiEntry(block: EntryBuilder.() -> Unit = {}) = buildEntry {
        importance = IMPORTANCE_HIGH
        hasFsi = true
        run(block)
@@ -581,3 +743,4 @@ private const val TEST_CHANNEL_ID = "test_channel"
private const val TEST_CHANNEL_NAME = "Test Channel"
private const val TEST_PACKAGE = "test_package"
private const val TEST_TAG = "test_tag"
private const val TEST_GROUP_KEY = "test_group_key"