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

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

Merge "Improve visual interruption decision provider tests" into main

parents c5712240 87be0dff
Loading
Loading
Loading
Loading
+22 −22
Original line number Diff line number Diff line
@@ -35,8 +35,7 @@ import org.junit.runner.RunWith
@SmallTest
@RunWith(AndroidTestingRunner::class)
class NotificationInterruptStateProviderWrapperTest : VisualInterruptionDecisionProviderTestBase() {
    override val provider: VisualInterruptionDecisionProvider
        get() =
    override val provider by lazy {
        NotificationInterruptStateProviderWrapper(
            NotificationInterruptStateProviderImpl(
                    powerManager,
@@ -57,6 +56,7 @@ class NotificationInterruptStateProviderWrapperTest : VisualInterruptionDecision
                )
                .also { it.mUseHeadsUp = true }
        )
    }

    // Tests of internals of the wrapper:

+292 −94
Original line number Diff line number Diff line
@@ -25,6 +25,7 @@ import android.app.NotificationManager.IMPORTANCE_HIGH
import android.app.NotificationManager.VISIBILITY_NO_OVERRIDE
import android.app.PendingIntent
import android.app.PendingIntent.FLAG_MUTABLE
import android.content.Context
import android.content.Intent
import android.content.pm.UserInfo
import android.graphics.drawable.Icon
@@ -53,6 +54,7 @@ import com.android.systemui.util.settings.FakeGlobalSettings
import com.android.systemui.util.time.FakeSystemClock
import com.android.systemui.utils.leaks.FakeBatteryController
import com.android.systemui.utils.leaks.LeakCheckedTest
import junit.framework.Assert.assertFalse
import junit.framework.Assert.assertTrue
import org.junit.Before
import org.junit.Test
@@ -80,6 +82,23 @@ abstract class VisualInterruptionDecisionProviderTestBase : SysuiTestCase() {

    protected abstract val provider: VisualInterruptionDecisionProvider

    private val neverSuppresses = object : NotificationInterruptSuppressor {}

    private val alwaysSuppressesInterruptions =
        object : NotificationInterruptSuppressor {
            override fun suppressInterruptions(entry: NotificationEntry?) = true
        }

    private val alwaysSuppressesAwakeInterruptions =
        object : NotificationInterruptSuppressor {
            override fun suppressAwakeInterruptions(entry: NotificationEntry?) = true
        }

    private val alwaysSuppressesAwakeHeadsUp =
        object : NotificationInterruptSuppressor {
            override fun suppressAwakeHeadsUp(entry: NotificationEntry?) = true
        }

    @Before
    fun setUp() {
        globalSettings.putInt(HEADS_UP_NOTIFICATIONS_ENABLED, HEADS_UP_ON)
@@ -87,154 +106,333 @@ abstract class VisualInterruptionDecisionProviderTestBase : SysuiTestCase() {
        val user = UserInfo(ActivityManager.getCurrentUser(), "Current user", /* flags = */ 0)
        userTracker.set(listOf(user), /* currentUserIndex = */ 0)

        whenever(headsUpManager.isSnoozed(any())).thenReturn(false)
        whenever(keyguardNotificationVisibilityProvider.shouldHideNotification(any()))
            .thenReturn(false)
    }

    @Test
    fun testShouldPeek() {
        ensureStateForPeek()
        ensurePeekState()
        assertShouldHeadsUp(buildPeekEntry())
    }

        assertTrue(provider.makeUnloggedHeadsUpDecision(createPeekEntry()).shouldInterrupt)
    @Test
    fun testShouldPeek_defaultLegacySuppressor() {
        ensurePeekState()
        provider.addLegacySuppressor(neverSuppresses)
        assertShouldHeadsUp(buildPeekEntry())
    }

    @Test
    fun testShouldPulse() {
        ensureStateForPulse()
    fun testShouldNotPeek_legacySuppressInterruptions() {
        ensurePeekState()
        provider.addLegacySuppressor(alwaysSuppressesInterruptions)
        assertShouldNotHeadsUp(buildPeekEntry())
    }

        assertTrue(provider.makeUnloggedHeadsUpDecision(createPulseEntry()).shouldInterrupt)
    @Test
    fun testShouldNotPeek_legacySuppressAwakeInterruptions() {
        ensurePeekState()
        provider.addLegacySuppressor(alwaysSuppressesAwakeInterruptions)
        assertShouldNotHeadsUp(buildPeekEntry())
    }

    @Test
    fun testShouldFsi_awake() {
        ensureStateForAwakeFsi()
    fun testShouldNotPeek_legacySuppressAwakeHeadsUp() {
        ensurePeekState()
        provider.addLegacySuppressor(alwaysSuppressesAwakeHeadsUp)
        assertShouldNotHeadsUp(buildPeekEntry())
    }

        assertTrue(provider.makeUnloggedFullScreenIntentDecision(createFsiEntry()).shouldInterrupt)
    @Test
    fun testShouldPulse() {
        ensurePulseState()
        assertShouldHeadsUp(buildPulseEntry())
    }

    @Test
    fun testShouldFsi_dreaming() {
        ensureStateForDreamingFsi()
    fun testShouldPulse_defaultLegacySuppressor() {
        ensurePulseState()
        provider.addLegacySuppressor(neverSuppresses)
        assertShouldHeadsUp(buildPulseEntry())
    }

        assertTrue(provider.makeUnloggedFullScreenIntentDecision(createFsiEntry()).shouldInterrupt)
    @Test
    fun testShouldNotPulse_legacySuppressInterruptions() {
        ensurePulseState()
        provider.addLegacySuppressor(alwaysSuppressesInterruptions)
        assertShouldNotHeadsUp(buildPulseEntry())
    }

    @Test
    fun testShouldFsi_keyguard() {
        ensureStateForKeyguardFsi()
    fun testShouldPulse_legacySuppressAwakeInterruptions() {
        ensurePulseState()
        provider.addLegacySuppressor(alwaysSuppressesAwakeInterruptions)
        assertShouldHeadsUp(buildPulseEntry())
    }

        assertTrue(provider.makeUnloggedFullScreenIntentDecision(createFsiEntry()).shouldInterrupt)
    @Test
    fun testShouldPulse_legacySuppressAwakeHeadsUp() {
        ensurePulseState()
        provider.addLegacySuppressor(alwaysSuppressesAwakeHeadsUp)
        assertShouldHeadsUp(buildPulseEntry())
    }

    @Test
    fun testShouldBubble() {
        assertTrue(provider.makeAndLogBubbleDecision(createBubbleEntry()).shouldInterrupt)
        ensureBubbleState()
        assertShouldBubble(buildBubbleEntry())
    }

    @Test
    fun testShouldBubble_defaultLegacySuppressor() {
        ensureBubbleState()
        provider.addLegacySuppressor(neverSuppresses)
        assertShouldBubble(buildBubbleEntry())
    }

    @Test
    fun testShouldNotBubble_legacySuppressInterruptions() {
        ensureBubbleState()
        provider.addLegacySuppressor(alwaysSuppressesInterruptions)
        assertShouldNotBubble(buildBubbleEntry())
    }

    @Test
    fun testShouldNotBubble_legacySuppressAwakeInterruptions() {
        ensureBubbleState()
        provider.addLegacySuppressor(alwaysSuppressesAwakeInterruptions)
        assertShouldNotBubble(buildBubbleEntry())
    }

    private fun ensureStateForPeek() {
        whenever(powerManager.isScreenOn).thenReturn(true)
        statusBarStateController.dozing = false
        statusBarStateController.dreaming = false
    @Test
    fun testShouldBubble_legacySuppressAwakeHeadsUp() {
        ensureBubbleState()
        provider.addLegacySuppressor(alwaysSuppressesAwakeHeadsUp)
        assertShouldBubble(buildBubbleEntry())
    }

    private fun ensureStateForPulse() {
        ambientDisplayConfiguration.fakePulseOnNotificationEnabled = true
        batteryController.setIsAodPowerSave(false)
        statusBarStateController.dozing = true
    @Test
    fun testShouldFsi_notInteractive() {
        ensureNotInteractiveFsiState()
        assertShouldFsi(buildFsiEntry())
    }

    private fun ensureStateForAwakeFsi() {
        whenever(powerManager.isInteractive).thenReturn(false)
        statusBarStateController.dreaming = false
        statusBarStateController.state = SHADE
    @Test
    fun testShouldFsi_dreaming() {
        ensureDreamingFsiState()
        assertShouldFsi(buildFsiEntry())
    }

    @Test
    fun testShouldFsi_keyguard() {
        ensureKeyguardFsiState()
        assertShouldFsi(buildFsiEntry())
    }

    private data class State(
        var hunSnoozed: Boolean? = null,
        var isAodPowerSave: Boolean? = null,
        var isDozing: Boolean? = null,
        var isDreaming: Boolean? = null,
        var isInteractive: Boolean? = null,
        var isScreenOn: Boolean? = null,
        var keyguardShouldHideNotification: Boolean? = null,
        var pulseOnNotificationsEnabled: Boolean? = null,
        var statusBarState: Int? = null,
    )

    private fun setState(state: State): Unit =
        state.run {
            hunSnoozed?.let { whenever(headsUpManager.isSnoozed(TEST_PACKAGE)).thenReturn(it) }

            isAodPowerSave?.let { batteryController.setIsAodPowerSave(it) }

            isDozing?.let { statusBarStateController.dozing = it }

            isDreaming?.let { statusBarStateController.dreaming = it }

            isInteractive?.let { whenever(powerManager.isInteractive).thenReturn(it) }

            isScreenOn?.let { whenever(powerManager.isScreenOn).thenReturn(it) }

            keyguardShouldHideNotification?.let {
                whenever(keyguardNotificationVisibilityProvider.shouldHideNotification(any()))
                    .thenReturn(it)
            }

    private fun ensureStateForDreamingFsi() {
        whenever(powerManager.isInteractive).thenReturn(true)
        statusBarStateController.dreaming = true
        statusBarStateController.state = SHADE
            pulseOnNotificationsEnabled?.let {
                ambientDisplayConfiguration.fakePulseOnNotificationEnabled = it
            }

    private fun ensureStateForKeyguardFsi() {
        whenever(powerManager.isInteractive).thenReturn(true)
        statusBarStateController.dreaming = false
        statusBarStateController.state = KEYGUARD
            statusBarState?.let { statusBarStateController.state = it }
        }

    private fun createNotif(
        hasFsi: Boolean = false,
        bubbleMetadata: BubbleMetadata? = null
    ): Notification {
        return Notification.Builder(context, TEST_CHANNEL_ID)
    private fun ensureState(block: State.() -> Unit) =
        State()
            .apply {
                setContentTitle(TEST_CONTENT_TITLE)
                setContentText(TEST_CONTENT_TEXT)
                keyguardShouldHideNotification = false
                apply(block)
            }
            .run(this::setState)

    private fun ensurePeekState(block: State.() -> Unit = {}) = ensureState {
        hunSnoozed = false
        isDozing = false
        isDreaming = false
        isScreenOn = true
        run(block)
    }

                if (hasFsi) {
                    setFullScreenIntent(mock(), /* highPriority = */ true)
    private fun ensurePulseState(block: State.() -> Unit = {}) = ensureState {
        isAodPowerSave = false
        isDozing = true
        pulseOnNotificationsEnabled = true
        run(block)
    }

                if (bubbleMetadata != null) {
                    setBubbleMetadata(bubbleMetadata)
    private fun ensureBubbleState(block: State.() -> Unit = {}) = ensureState(block)

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

    private fun ensureDreamingFsiState(block: State.() -> Unit = {}) = ensureState {
        isDreaming = true
        isInteractive = true
        statusBarState = SHADE
        run(block)
    }
            .setContentTitle(TEST_CONTENT_TITLE)
            .setContentText(TEST_CONTENT_TEXT)
            .build()

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

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

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

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

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

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

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

    private class EntryBuilder(val context: Context) {
        var importance = IMPORTANCE_DEFAULT
        var suppressedVisualEffects: Int? = null
        var whenMs: Long? = null
        var visibilityOverride: Int? = null
        var hasFsi = false
        var canBubble: Boolean? = null
        var hasBubbleMetadata = false
        var bubbleSuppressNotification: Boolean? = null

        private fun buildBubbleMetadata() =
            BubbleMetadata.Builder(
                    PendingIntent.getActivity(
                        context,
                        /* requestCode = */ 0,
                        Intent().setPackage(context.packageName),
                        FLAG_MUTABLE
                    ),
                    Icon.createWithResource(context.resources, R.drawable.android)
                )
                .apply { bubbleSuppressNotification?.let { setSuppressNotification(it) } }
                .build()

        val icon = Icon.createWithResource(context.resources, R.drawable.android)
        fun build() =
            Notification.Builder(context, TEST_CHANNEL_ID)
                .apply {
                    setContentTitle(TEST_CONTENT_TITLE)
                    setContentText(TEST_CONTENT_TEXT)

        return BubbleMetadata.Builder(pendingIntent, icon).build()
                    if (hasFsi) {
                        setFullScreenIntent(mock(), /* highPriority = */ true)
                    }

    private fun createEntry(
        notif: Notification,
        importance: Int = IMPORTANCE_DEFAULT,
        canBubble: Boolean? = null
    ): NotificationEntry {
        return NotificationEntryBuilder()
                    whenMs?.let { setWhen(it) }

                    if (hasBubbleMetadata) {
                        setBubbleMetadata(buildBubbleMetadata())
                    }
                }
                .build()
                .let { NotificationEntryBuilder().setNotification(it) }
                .apply {
                    setPkg(TEST_PACKAGE)
                    setOpPkg(TEST_PACKAGE)
                    setTag(TEST_TAG)
                setChannel(NotificationChannel(TEST_CHANNEL_ID, TEST_CHANNEL_NAME, importance))
                setNotification(notif)

                    setImportance(importance)
                    setChannel(NotificationChannel(TEST_CHANNEL_ID, TEST_CHANNEL_NAME, importance))

                if (canBubble != null) {
                    setCanBubble(canBubble)
                    canBubble?.let { setCanBubble(it) }
                }
                .build()!!
                .also {
                    modifyRanking(it)
                        .apply {
                            suppressedVisualEffects?.let { setSuppressedVisualEffects(it) }
                            visibilityOverride?.let { setVisibilityOverride(it) }
                        }
                        .build()
                }
    }

    private fun createPeekEntry() = createEntry(notif = createNotif(), importance = IMPORTANCE_HIGH)
    private fun buildEntry(block: EntryBuilder.() -> Unit) =
        EntryBuilder(context).also(block).build()

    private fun createPulseEntry() =
        createEntry(notif = createNotif(), importance = IMPORTANCE_HIGH).also {
            modifyRanking(it).setVisibilityOverride(VISIBILITY_NO_OVERRIDE).build()
    private fun buildPeekEntry(block: EntryBuilder.() -> Unit = {}) = buildEntry {
        importance = IMPORTANCE_HIGH
        run(block)
    }

    private fun createFsiEntry() =
        createEntry(notif = createNotif(hasFsi = true), importance = IMPORTANCE_HIGH)
    private fun buildPulseEntry(block: EntryBuilder.() -> Unit = {}) = buildEntry {
        importance = IMPORTANCE_DEFAULT
        visibilityOverride = VISIBILITY_NO_OVERRIDE
        run(block)
    }

    private fun createBubbleEntry() =
        createEntry(
            notif = createNotif(bubbleMetadata = createBubbleMetadata()),
            importance = IMPORTANCE_HIGH,
    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 whenAgo(whenAgeMs: Long) = systemClock.currentTimeMillis() - whenAgeMs
}

private const val TEST_CONTENT_TITLE = "Test Content Title"
+1 −1
Original line number Diff line number Diff line
@@ -104,7 +104,7 @@ class FakeStatusBarStateController : SysuiStatusBarStateController {

    override fun isDreaming() = dreaming

    override fun setIsDreaming(drreaming: Boolean): Boolean {
    override fun setIsDreaming(dreaming: Boolean): Boolean {
        dreaming != this.dreaming || return false
        this.dreaming = dreaming
        callbacks.forEach { it.onDreamingChanged(dreaming) }