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 Original line Diff line number Diff line
@@ -35,8 +35,7 @@ import org.junit.runner.RunWith
@SmallTest
@SmallTest
@RunWith(AndroidTestingRunner::class)
@RunWith(AndroidTestingRunner::class)
class NotificationInterruptStateProviderWrapperTest : VisualInterruptionDecisionProviderTestBase() {
class NotificationInterruptStateProviderWrapperTest : VisualInterruptionDecisionProviderTestBase() {
    override val provider: VisualInterruptionDecisionProvider
    override val provider by lazy {
        get() =
        NotificationInterruptStateProviderWrapper(
        NotificationInterruptStateProviderWrapper(
            NotificationInterruptStateProviderImpl(
            NotificationInterruptStateProviderImpl(
                    powerManager,
                    powerManager,
@@ -57,6 +56,7 @@ class NotificationInterruptStateProviderWrapperTest : VisualInterruptionDecision
                )
                )
                .also { it.mUseHeadsUp = true }
                .also { it.mUseHeadsUp = true }
        )
        )
    }


    // Tests of internals of the wrapper:
    // Tests of internals of the wrapper:


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


    protected abstract val provider: VisualInterruptionDecisionProvider
    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
    @Before
    fun setUp() {
    fun setUp() {
        globalSettings.putInt(HEADS_UP_NOTIFICATIONS_ENABLED, HEADS_UP_ON)
        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)
        val user = UserInfo(ActivityManager.getCurrentUser(), "Current user", /* flags = */ 0)
        userTracker.set(listOf(user), /* currentUserIndex = */ 0)
        userTracker.set(listOf(user), /* currentUserIndex = */ 0)


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


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


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


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


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


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


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


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


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


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


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


    @Test
    @Test
    fun testShouldBubble() {
    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() {
    @Test
        whenever(powerManager.isScreenOn).thenReturn(true)
    fun testShouldBubble_legacySuppressAwakeHeadsUp() {
        statusBarStateController.dozing = false
        ensureBubbleState()
        statusBarStateController.dreaming = false
        provider.addLegacySuppressor(alwaysSuppressesAwakeHeadsUp)
        assertShouldBubble(buildBubbleEntry())
    }
    }


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


    private fun ensureStateForAwakeFsi() {
    @Test
        whenever(powerManager.isInteractive).thenReturn(false)
    fun testShouldFsi_dreaming() {
        statusBarStateController.dreaming = false
        ensureDreamingFsiState()
        statusBarStateController.state = SHADE
        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() {
            pulseOnNotificationsEnabled?.let {
        whenever(powerManager.isInteractive).thenReturn(true)
                ambientDisplayConfiguration.fakePulseOnNotificationEnabled = it
        statusBarStateController.dreaming = true
        statusBarStateController.state = SHADE
            }
            }


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


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

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


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


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

    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)
    private fun ensureKeyguardFsiState(block: State.() -> Unit = {}) = ensureState {
            .build()
        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 {
    private fun assertShouldNotFsi(entry: NotificationEntry) =
        val pendingIntent =
        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(
                    PendingIntent.getActivity(
                        context,
                        context,
                        /* requestCode = */ 0,
                        /* requestCode = */ 0,
                        Intent().setPackage(context.packageName),
                        Intent().setPackage(context.packageName),
                        FLAG_MUTABLE
                        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(
                    whenMs?.let { setWhen(it) }
        notif: Notification,

        importance: Int = IMPORTANCE_DEFAULT,
                    if (hasBubbleMetadata) {
        canBubble: Boolean? = null
                        setBubbleMetadata(buildBubbleMetadata())
    ): NotificationEntry {
                    }
        return NotificationEntryBuilder()
                }
                .build()
                .let { NotificationEntryBuilder().setNotification(it) }
                .apply {
                .apply {
                    setPkg(TEST_PACKAGE)
                    setPkg(TEST_PACKAGE)
                    setOpPkg(TEST_PACKAGE)
                    setOpPkg(TEST_PACKAGE)
                    setTag(TEST_TAG)
                    setTag(TEST_TAG)
                setChannel(NotificationChannel(TEST_CHANNEL_ID, TEST_CHANNEL_NAME, importance))

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


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


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


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


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


    private fun createBubbleEntry() =
    private fun buildFsiEntry(block: EntryBuilder.() -> Unit = {}) = buildEntry {
        createEntry(
        importance = IMPORTANCE_HIGH
            notif = createNotif(bubbleMetadata = createBubbleMetadata()),
        hasFsi = true
            importance = IMPORTANCE_HIGH,
        run(block)
    }

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

    private fun whenAgo(whenAgeMs: Long) = systemClock.currentTimeMillis() - whenAgeMs
}
}


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


    override fun isDreaming() = dreaming
    override fun isDreaming() = dreaming


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