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

Commit 462c652d authored by Caitlin Shkuratov's avatar Caitlin Shkuratov
Browse files

[SB][Notif] Include `isAppVisible` in notification chips model.

For both chip return animations (b/273205603) and accurate promoted
notification ranking (b/395154758), we need to model whether the
notification's app is visible instead of just immediately filtering out
those chips.

Bug: 395154758
Bug: 273205603
Bug: 364653005
Flag: com.android.systemui.status_bar_notification_chips
Test: Verify status bar notification chip still hides when its app is
open and re-shows when its app is closed
Test: atest SingleNotificationChipInteractorTest
StatusBarNotificationChipsInteractorTest

Change-Id: Iec8ef28e791b1b98f16c9b36a35079f9442dfedd
parent 436c93d6
Loading
Loading
Loading
Loading
+12 −10
Original line number Diff line number Diff line
@@ -306,7 +306,7 @@ class SingleNotificationChipInteractorTest : SysuiTestCase() {
        }

    @Test
    fun notificationChip_appIsVisibleOnCreation_emitsNull() =
    fun notificationChip_appIsVisibleOnCreation_emitsIsAppVisibleTrue() =
        kosmos.runTest {
            activityManagerRepository.fake.startingIsAppVisibleValue = true

@@ -323,11 +323,12 @@ class SingleNotificationChipInteractorTest : SysuiTestCase() {

            val latest by collectLastValue(underTest.notificationChip)

            assertThat(latest).isNull()
            assertThat(latest).isNotNull()
            assertThat(latest!!.isAppVisible).isTrue()
        }

    @Test
    fun notificationChip_appNotVisibleOnCreation_emitsValue() =
    fun notificationChip_appNotVisibleOnCreation_emitsIsAppVisibleFalse() =
        kosmos.runTest {
            activityManagerRepository.fake.startingIsAppVisibleValue = false

@@ -345,10 +346,11 @@ class SingleNotificationChipInteractorTest : SysuiTestCase() {
            val latest by collectLastValue(underTest.notificationChip)

            assertThat(latest).isNotNull()
            assertThat(latest!!.isAppVisible).isFalse()
        }

    @Test
    fun notificationChip_hidesWhenAppIsVisible() =
    fun notificationChip_updatesWhenAppIsVisible() =
        kosmos.runTest {
            val underTest =
                factory.create(
@@ -364,13 +366,13 @@ class SingleNotificationChipInteractorTest : SysuiTestCase() {
            val latest by collectLastValue(underTest.notificationChip)

            activityManagerRepository.fake.setIsAppVisible(UID, false)
            assertThat(latest).isNotNull()
            assertThat(latest!!.isAppVisible).isFalse()

            activityManagerRepository.fake.setIsAppVisible(UID, true)
            assertThat(latest).isNull()
            assertThat(latest!!.isAppVisible).isTrue()

            activityManagerRepository.fake.setIsAppVisible(UID, false)
            assertThat(latest).isNotNull()
            assertThat(latest!!.isAppVisible).isFalse()
        }

    // Note: This test is theoretically impossible because the notification key should contain the
@@ -396,6 +398,7 @@ class SingleNotificationChipInteractorTest : SysuiTestCase() {
                )
            val latest by collectLastValue(underTest.notificationChip)
            assertThat(latest).isNotNull()
            assertThat(latest!!.isAppVisible).isFalse()

            // WHEN the notif gets a new UID that starts as visible
            activityManagerRepository.fake.startingIsAppVisibleValue = true
@@ -408,9 +411,8 @@ class SingleNotificationChipInteractorTest : SysuiTestCase() {
                )
            )

            // THEN we re-fetch the app visibility state with the new UID, and since that UID is
            // visible, we hide the chip
            assertThat(latest).isNull()
            // THEN we re-fetch the app visibility state with the new UID
            assertThat(latest!!.isAppVisible).isTrue()
        }

    companion object {
+54 −25
Original line number Diff line number Diff line
@@ -21,8 +21,8 @@ import android.platform.test.annotations.EnableFlags
import androidx.test.ext.junit.runners.AndroidJUnit4
import androidx.test.filters.SmallTest
import com.android.systemui.SysuiTestCase
import com.android.systemui.coroutines.collectLastValue
import com.android.systemui.coroutines.collectValues
import com.android.systemui.activity.data.repository.activityManagerRepository
import com.android.systemui.activity.data.repository.fake
import com.android.systemui.kosmos.Kosmos
import com.android.systemui.kosmos.collectLastValue
import com.android.systemui.kosmos.collectValues
@@ -41,7 +41,6 @@ import com.android.systemui.testKosmos
import com.android.systemui.util.time.fakeSystemClock
import com.google.common.truth.Truth.assertThat
import kotlin.test.Test
import kotlinx.coroutines.test.runTest
import org.junit.runner.RunWith
import org.mockito.kotlin.mock

@@ -55,9 +54,9 @@ class StatusBarNotificationChipsInteractorTest : SysuiTestCase() {

    @Test
    @DisableFlags(StatusBarNotifChips.FLAG_NAME)
    fun notificationChips_flagOff_noNotifs() =
    fun shownNotificationChips_flagOff_noNotifs() =
        kosmos.runTest {
            val latest by collectLastValue(underTest.notificationChips)
            val latest by collectLastValue(underTest.shownNotificationChips)

            setNotifs(
                listOf(
@@ -74,9 +73,9 @@ class StatusBarNotificationChipsInteractorTest : SysuiTestCase() {

    @Test
    @EnableFlags(StatusBarNotifChips.FLAG_NAME)
    fun notificationChips_noNotifs_empty() =
    fun shownNotificationChips_noNotifs_empty() =
        kosmos.runTest {
            val latest by collectLastValue(underTest.notificationChips)
            val latest by collectLastValue(underTest.shownNotificationChips)

            setNotifs(emptyList())

@@ -86,9 +85,9 @@ class StatusBarNotificationChipsInteractorTest : SysuiTestCase() {
    @Test
    @EnableFlags(StatusBarNotifChips.FLAG_NAME)
    @DisableFlags(StatusBarConnectedDisplays.FLAG_NAME)
    fun notificationChips_notifMissingStatusBarChipIconView_cdFlagOff_empty() =
    fun shownNotificationChips_notifMissingStatusBarChipIconView_cdFlagOff_empty() =
        kosmos.runTest {
            val latest by collectLastValue(underTest.notificationChips)
            val latest by collectLastValue(underTest.shownNotificationChips)

            setNotifs(
                listOf(
@@ -105,9 +104,9 @@ class StatusBarNotificationChipsInteractorTest : SysuiTestCase() {

    @Test
    @EnableFlags(StatusBarNotifChips.FLAG_NAME, StatusBarConnectedDisplays.FLAG_NAME)
    fun notificationChips_notifMissingStatusBarChipIconView_cdFlagOn_notEmpty() =
    fun shownNotificationChips_notifMissingStatusBarChipIconView_cdFlagOn_notEmpty() =
        kosmos.runTest {
            val latest by collectLastValue(underTest.notificationChips)
            val latest by collectLastValue(underTest.shownNotificationChips)

            setNotifs(
                listOf(
@@ -124,9 +123,9 @@ class StatusBarNotificationChipsInteractorTest : SysuiTestCase() {

    @Test
    @EnableFlags(StatusBarNotifChips.FLAG_NAME)
    fun notificationChips_onePromotedNotif_statusBarIconViewMatches() =
    fun shownNotificationChips_onePromotedNotif_statusBarIconViewMatches() =
        kosmos.runTest {
            val latest by collectLastValue(underTest.notificationChips)
            val latest by collectLastValue(underTest.shownNotificationChips)

            val icon = mock<StatusBarIconView>()
            setNotifs(
@@ -146,9 +145,9 @@ class StatusBarNotificationChipsInteractorTest : SysuiTestCase() {

    @Test
    @EnableFlags(StatusBarNotifChips.FLAG_NAME)
    fun notificationChips_onlyForPromotedNotifs() =
    fun shownNotificationChips_onlyForPromotedNotifs() =
        kosmos.runTest {
            val latest by collectLastValue(underTest.notificationChips)
            val latest by collectLastValue(underTest.shownNotificationChips)

            val firstIcon = mock<StatusBarIconView>()
            val secondIcon = mock<StatusBarIconView>()
@@ -179,12 +178,42 @@ class StatusBarNotificationChipsInteractorTest : SysuiTestCase() {
            assertThat(latest!![1].statusBarChipIconView).isEqualTo(secondIcon)
        }

    @Test
    @EnableFlags(StatusBarNotifChips.FLAG_NAME)
    fun shownNotificationChips_onlyForNotVisibleApps() =
        kosmos.runTest {
            activityManagerRepository.fake.startingIsAppVisibleValue = false

            val latest by collectLastValue(underTest.shownNotificationChips)

            val uid = 433
            setNotifs(
                listOf(
                    activeNotificationModel(
                        key = "notif",
                        uid = uid,
                        statusBarChipIcon = mock<StatusBarIconView>(),
                        promotedContent = PromotedNotificationContentModel.Builder("notif1").build(),
                    )
                )
            )

            activityManagerRepository.fake.setIsAppVisible(uid, isAppVisible = false)
            assertThat(latest).hasSize(1)

            activityManagerRepository.fake.setIsAppVisible(uid, isAppVisible = true)
            assertThat(latest).isEmpty()

            activityManagerRepository.fake.setIsAppVisible(uid, isAppVisible = false)
            assertThat(latest).hasSize(1)
        }

    /** Regression test for b/388521980. */
    @Test
    @EnableFlags(StatusBarNotifChips.FLAG_NAME)
    fun notificationChips_callNotifIsAlsoPromoted_callNotifExcluded() =
    fun shownNotificationChips_callNotifIsAlsoPromoted_callNotifExcluded() =
        kosmos.runTest {
            val latest by collectLastValue(underTest.notificationChips)
            val latest by collectLastValue(underTest.shownNotificationChips)

            setNotifs(
                listOf(
@@ -212,9 +241,9 @@ class StatusBarNotificationChipsInteractorTest : SysuiTestCase() {

    @Test
    @EnableFlags(StatusBarNotifChips.FLAG_NAME)
    fun notificationChips_notifUpdatesGoThrough() =
    fun shownNotificationChips_notifUpdatesGoThrough() =
        kosmos.runTest {
            val latest by collectLastValue(underTest.notificationChips)
            val latest by collectLastValue(underTest.shownNotificationChips)

            val firstIcon = mock<StatusBarIconView>()
            val secondIcon = mock<StatusBarIconView>()
@@ -262,9 +291,9 @@ class StatusBarNotificationChipsInteractorTest : SysuiTestCase() {

    @Test
    @EnableFlags(StatusBarNotifChips.FLAG_NAME)
    fun notificationChips_promotedNotifDisappearsThenReappears() =
    fun shownNotificationChips_promotedNotifDisappearsThenReappears() =
        kosmos.runTest {
            val latest by collectLastValue(underTest.notificationChips)
            val latest by collectLastValue(underTest.shownNotificationChips)

            setNotifs(
                listOf(
@@ -304,9 +333,9 @@ class StatusBarNotificationChipsInteractorTest : SysuiTestCase() {

    @Test
    @EnableFlags(StatusBarNotifChips.FLAG_NAME)
    fun notificationChips_sortedBasedOnFirstAppearanceTime() =
    fun shownNotificationChips_sortedBasedOnFirstAppearanceTime() =
        kosmos.runTest {
            val latest by collectLastValue(underTest.notificationChips)
            val latest by collectLastValue(underTest.shownNotificationChips)

            val firstIcon = mock<StatusBarIconView>()
            val secondIcon = mock<StatusBarIconView>()
@@ -391,9 +420,9 @@ class StatusBarNotificationChipsInteractorTest : SysuiTestCase() {

    @Test
    @EnableFlags(StatusBarNotifChips.FLAG_NAME)
    fun notificationChips_notifChangesKey() =
    fun shownNotificationChips_notifChangesKey() =
        kosmos.runTest {
            val latest by collectLastValue(underTest.notificationChips)
            val latest by collectLastValue(underTest.shownNotificationChips)

            val firstIcon = mock<StatusBarIconView>()
            val secondIcon = mock<StatusBarIconView>()
+11 −10
Original line number Diff line number Diff line
@@ -105,17 +105,12 @@ constructor(
     */
    val notificationChip: Flow<NotificationChipModel?> =
        combine(_notificationModel, isAppVisible) { notif, isAppVisible ->
            if (isAppVisible) {
                // If the app that posted this notification is visible, we want to hide the chip
                // because information between the status bar chip and the app itself could be
                // out-of-sync (like a timer that's slightly off)
                null
            } else {
                notif.toNotificationChipModel()
            }
            notif.toNotificationChipModel(isAppVisible)
        }

    private fun ActiveNotificationModel.toNotificationChipModel(): NotificationChipModel? {
    private fun ActiveNotificationModel.toNotificationChipModel(
        isVisible: Boolean
    ): NotificationChipModel? {
        val promotedContent = this.promotedContent
        if (promotedContent == null) {
            logger.w({
@@ -138,7 +133,13 @@ constructor(
            }
        }

        return NotificationChipModel(key, appName, statusBarChipIconView, promotedContent)
        return NotificationChipModel(
            key,
            appName,
            statusBarChipIconView,
            promotedContent,
            isVisible,
        )
    }

    @AssistedFactory
+12 −3
Original line number Diff line number Diff line
@@ -142,10 +142,10 @@ constructor(
    }

    /**
     * A flow modeling the notifications that should be shown as chips in the status bar. Emits an
     * empty list if there are no notifications that should show a status bar chip.
     * Emits all notifications that are eligible to show as chips in the status bar. This is
     * different from which chips will *actually* show, see [shownNotificationChips] for that.
     */
    val notificationChips: Flow<List<NotificationChipModel>> =
    private val allNotificationChips: Flow<List<NotificationChipModel>> =
        if (StatusBarNotifChips.isEnabled) {
            // For all our current interactors...
            promotedNotificationInteractors.flatMapLatest { intrs ->
@@ -172,4 +172,13 @@ constructor(
        } else {
            flowOf(emptyList())
        }

    /** Emits the notifications that should actually be *shown* as chips in the status bar. */
    val shownNotificationChips: Flow<List<NotificationChipModel>> =
        allNotificationChips.map { chipsList ->
            // If the app that posted this notification is visible, we want to hide the chip
            // because information between the status bar chip and the app itself could be
            // out-of-sync (like a timer that's slightly off)
            chipsList.filter { !it.isAppVisible }
        }
}
+3 −1
Original line number Diff line number Diff line
@@ -22,8 +22,10 @@ import com.android.systemui.statusbar.notification.promoted.shared.model.Promote
/** Modeling all the data needed to render a status bar notification chip. */
data class NotificationChipModel(
    val key: String,
    /** The user-readable name of the app that posted the call notification. */
    /** The user-readable name of the app that posted this notification. */
    val appName: String,
    val statusBarChipIconView: StatusBarIconView?,
    val promotedContent: PromotedNotificationContentModel,
    /** True if the app managing this notification is currently visible to the user. */
    val isAppVisible: Boolean,
)
Loading