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

Commit 6965f498 authored by Julia Reynolds's avatar Julia Reynolds
Browse files

Fix bundle icon logic

- If DND is active and all bundle children are suppressed, suppress the icon
- Show on AOD by default

Test: NotificationIconsInteractorTest
Fixes: 429809027
Flag: com.android.systemui.notification_bundle_ui
Change-Id: Id14586b921ca76b827e887f03d5198ac1d911784
parent d37a9688
Loading
Loading
Loading
Loading
+89 −60
Original line number Diff line number Diff line
@@ -26,16 +26,15 @@ import com.android.systemui.kosmos.testDispatcher
import com.android.systemui.kosmos.testScope
import com.android.systemui.statusbar.data.repository.notificationListenerSettingsRepository
import com.android.systemui.statusbar.headsup.shared.StatusBarNoHunBehavior
import com.android.systemui.statusbar.notification.data.model.activeNotificationModel
import com.android.systemui.statusbar.notification.data.repository.ActiveNotificationsStore
import com.android.systemui.statusbar.notification.data.repository.activeNotificationListRepository
import com.android.systemui.statusbar.notification.data.repository.getPipelineModels
import com.android.systemui.statusbar.notification.data.repository.getPopulatedActiveNotificationsStore
import com.android.systemui.statusbar.notification.data.repository.notificationsKeyguardViewStateRepository
import com.android.systemui.statusbar.notification.domain.interactor.headsUpNotificationIconInteractor
import com.android.systemui.statusbar.notification.promoted.domain.interactor.aodPromotedNotificationInteractor
import com.android.systemui.statusbar.notification.promoted.shared.model.PromotedNotificationContentBuilder
import com.android.systemui.statusbar.notification.promoted.shared.model.PromotedNotificationContentModel
import com.android.systemui.statusbar.notification.promoted.shared.model.PromotedNotificationContentModel.Style.Base
import com.android.systemui.statusbar.notification.promoted.shared.model.PromotedNotificationContentModels
import com.android.systemui.statusbar.notification.shared.ActiveBundleModel
import com.android.systemui.statusbar.notification.shared.ActiveNotificationModel
import com.android.systemui.statusbar.notification.shared.ActivePipelineEntryModel
import com.android.systemui.statusbar.notification.shared.byAssociatedNotifModel
import com.android.systemui.statusbar.notification.shared.byIconIsAmbient
import com.android.systemui.statusbar.notification.shared.byIconNotifKey
@@ -74,13 +73,15 @@ class NotificationIconsInteractorTest : SysuiTestCase() {
            kosmos.applicationContext,
        )

    private lateinit var testIcons: List<ActivePipelineEntryModel>

    @Before
    fun setup() {
        testScope.apply {
            activeNotificationListRepository.activeNotifications.value =
                ActiveNotificationsStore.Builder()
                    .apply { testIcons.forEach(::addIndividualNotif) }
                    .build()
                kosmos.getPopulatedActiveNotificationsStore()
            testIcons =
                activeNotificationListRepository.activeNotifications.value.getPipelineModels()
        }
    }

@@ -108,7 +109,12 @@ class NotificationIconsInteractorTest : SysuiTestCase() {
            assertThat(filteredSet).comparingElementsUsing(byIconIsAmbient).doesNotContain(true)
            assertThat(filteredSet)
                .comparingElementsUsing(byAssociatedNotifModel)
                .containsNoneIn(testIcons.filter { it.isSuppressedFromStatusBar })
                .containsNoneIn(
                    testIcons.filter {
                        (it is ActiveNotificationModel && it.isSuppressedFromStatusBar) ||
                            (it is ActiveBundleModel && it.key == "bundle2")
                    }
                )
        }

    @Test
@@ -117,7 +123,7 @@ class NotificationIconsInteractorTest : SysuiTestCase() {
            val filteredSet by collectLastValue(underTest.filteredNotifSet(showLowPriority = false))
            assertThat(filteredSet)
                .comparingElementsUsing(byAssociatedNotifModel)
                .containsNoneIn(testIcons.filter { it.isSilent })
                .containsNoneIn(testIcons.filter { it is ActiveNotificationModel && it.isSilent })
        }

    @Test
@@ -126,7 +132,9 @@ class NotificationIconsInteractorTest : SysuiTestCase() {
            val filteredSet by collectLastValue(underTest.filteredNotifSet(showDismissed = false))
            assertThat(filteredSet)
                .comparingElementsUsing(byAssociatedNotifModel)
                .containsNoneIn(testIcons.filter { it.isRowDismissed })
                .containsNoneIn(
                    testIcons.filter { it is ActiveNotificationModel && it.isRowDismissed }
                )
        }

    @Test
@@ -136,7 +144,9 @@ class NotificationIconsInteractorTest : SysuiTestCase() {
                collectLastValue(underTest.filteredNotifSet(showRepliedMessages = false))
            assertThat(filteredSet)
                .comparingElementsUsing(byAssociatedNotifModel)
                .containsNoneIn(testIcons.filter { it.isLastMessageFromReply })
                .containsNoneIn(
                    testIcons.filter { it is ActiveNotificationModel && it.isLastMessageFromReply }
                )
        }

    @Test
@@ -146,7 +156,7 @@ class NotificationIconsInteractorTest : SysuiTestCase() {
            notificationsKeyguardInteractor.setNotificationsFullyHidden(false)
            assertThat(filteredSet)
                .comparingElementsUsing(byAssociatedNotifModel)
                .containsNoneIn(testIcons.filter { it.isPulsing })
                .containsNoneIn(testIcons.filter { it is ActiveNotificationModel && it.isPulsing })
        }

    @Test
@@ -156,7 +166,7 @@ class NotificationIconsInteractorTest : SysuiTestCase() {
            notificationsKeyguardInteractor.setNotificationsFullyHidden(true)
            assertThat(filteredSet)
                .comparingElementsUsing(byAssociatedNotifModel)
                .containsAnyIn(testIcons.filter { it.isPulsing })
                .containsAnyIn(testIcons.filter { it is ActiveNotificationModel && it.isPulsing })
        }

    @Test
@@ -165,7 +175,9 @@ class NotificationIconsInteractorTest : SysuiTestCase() {
            val filteredSet by collectLastValue(underTest.filteredNotifSet(showAodPromoted = true))
            assertThat(filteredSet)
                .comparingElementsUsing(byAssociatedNotifModel)
                .containsAnyIn(testIcons.filter { it.promotedContent != null })
                .containsAnyIn(
                    testIcons.filter { it is ActiveNotificationModel && it.promotedContent != null }
                )
        }
    }

@@ -175,7 +187,9 @@ class NotificationIconsInteractorTest : SysuiTestCase() {
            val filteredSet by collectLastValue(underTest.filteredNotifSet(showAodPromoted = false))
            assertThat(filteredSet)
                .comparingElementsUsing(byAssociatedNotifModel)
                .containsNoneIn(testIcons.filter { it.promotedContent != null })
                .containsNoneIn(
                    testIcons.filter { it is ActiveNotificationModel && it.promotedContent != null }
                )
        }
    }
}
@@ -190,12 +204,19 @@ class AlwaysOnDisplayNotificationIconsInteractorTest : SysuiTestCase() {
    private val underTest
        get() = kosmos.alwaysOnDisplayNotificationIconsInteractor

    private val activeNotificationListRepository
        get() = kosmos.activeNotificationListRepository

    private lateinit var testIcons: List<ActivePipelineEntryModel>

    @Before
    fun setup() {
        kosmos.activeNotificationListRepository.activeNotifications.value =
            ActiveNotificationsStore.Builder()
                .apply { testIcons.forEach(::addIndividualNotif) }
                .build()
        testScope.apply {
            activeNotificationListRepository.activeNotifications.value =
                kosmos.getPopulatedActiveNotificationsStore()
            testIcons =
                activeNotificationListRepository.activeNotifications.value.getPipelineModels()
        }
    }

    @Test
@@ -213,7 +234,12 @@ class AlwaysOnDisplayNotificationIconsInteractorTest : SysuiTestCase() {
            assertThat(filteredSet).comparingElementsUsing(byIconIsAmbient).doesNotContain(true)
            assertThat(filteredSet)
                .comparingElementsUsing(byAssociatedNotifModel)
                .containsNoneIn(testIcons.filter { it.isSuppressedFromStatusBar })
                .containsNoneIn(
                    testIcons.filter {
                        (it is ActiveNotificationModel && it.isSuppressedFromStatusBar) ||
                            (it is ActiveBundleModel && it.key == "bundle2")
                    }
                )
        }

    @Test
@@ -222,7 +248,9 @@ class AlwaysOnDisplayNotificationIconsInteractorTest : SysuiTestCase() {
            val filteredSet by collectLastValue(underTest.aodNotifs)
            assertThat(filteredSet)
                .comparingElementsUsing(byAssociatedNotifModel)
                .containsNoneIn(testIcons.filter { it.isRowDismissed })
                .containsNoneIn(
                    testIcons.filter { it is ActiveNotificationModel && it.isRowDismissed }
                )
        }

    @Test
@@ -231,7 +259,9 @@ class AlwaysOnDisplayNotificationIconsInteractorTest : SysuiTestCase() {
            val filteredSet by collectLastValue(underTest.aodNotifs)
            assertThat(filteredSet)
                .comparingElementsUsing(byAssociatedNotifModel)
                .containsNoneIn(testIcons.filter { it.isLastMessageFromReply })
                .containsNoneIn(
                    testIcons.filter { it is ActiveNotificationModel && it.isLastMessageFromReply }
                )
        }

    @Test
@@ -242,7 +272,7 @@ class AlwaysOnDisplayNotificationIconsInteractorTest : SysuiTestCase() {
            kosmos.notificationsKeyguardInteractor.setNotificationsFullyHidden(false)
            assertThat(filteredSet)
                .comparingElementsUsing(byAssociatedNotifModel)
                .containsAnyIn(testIcons.filter { it.isPulsing })
                .containsAnyIn(testIcons.filter { it is ActiveNotificationModel && it.isPulsing })
        }

    @Test
@@ -253,7 +283,7 @@ class AlwaysOnDisplayNotificationIconsInteractorTest : SysuiTestCase() {
            kosmos.notificationsKeyguardInteractor.setNotificationsFullyHidden(true)
            assertThat(filteredSet)
                .comparingElementsUsing(byAssociatedNotifModel)
                .containsAnyIn(testIcons.filter { it.isPulsing })
                .containsAnyIn(testIcons.filter { it is ActiveNotificationModel && it.isPulsing })
        }

    @Test
@@ -264,7 +294,7 @@ class AlwaysOnDisplayNotificationIconsInteractorTest : SysuiTestCase() {
            kosmos.notificationsKeyguardInteractor.setNotificationsFullyHidden(false)
            assertThat(filteredSet)
                .comparingElementsUsing(byAssociatedNotifModel)
                .containsNoneIn(testIcons.filter { it.isPulsing })
                .containsNoneIn(testIcons.filter { it is ActiveNotificationModel && it.isPulsing })
        }

    @Test
@@ -275,7 +305,7 @@ class AlwaysOnDisplayNotificationIconsInteractorTest : SysuiTestCase() {
            kosmos.notificationsKeyguardInteractor.setNotificationsFullyHidden(true)
            assertThat(filteredSet)
                .comparingElementsUsing(byAssociatedNotifModel)
                .containsAnyIn(testIcons.filter { it.isPulsing })
                .containsAnyIn(testIcons.filter { it is ActiveNotificationModel && it.isPulsing })
        }
}

@@ -291,13 +321,18 @@ class StatusBarNotificationIconsInteractorTest : SysuiTestCase() {
            kosmos.notificationListenerSettingsRepository,
        )

    private val activeNotificationListRepository
        get() = kosmos.activeNotificationListRepository

    private lateinit var testIcons: List<ActivePipelineEntryModel>

    @Before
    fun setup() {
        testScope.apply {
            kosmos.activeNotificationListRepository.activeNotifications.value =
                ActiveNotificationsStore.Builder()
                    .apply { testIcons.forEach(::addIndividualNotif) }
                    .build()
            activeNotificationListRepository.activeNotifications.value =
                kosmos.getPopulatedActiveNotificationsStore()
            testIcons =
                activeNotificationListRepository.activeNotifications.value.getPipelineModels()
        }
    }

@@ -316,7 +351,12 @@ class StatusBarNotificationIconsInteractorTest : SysuiTestCase() {
            assertThat(filteredSet).comparingElementsUsing(byIconIsAmbient).doesNotContain(true)
            assertThat(filteredSet)
                .comparingElementsUsing(byAssociatedNotifModel)
                .containsNoneIn(testIcons.filter { it.isSuppressedFromStatusBar })
                .containsNoneIn(
                    testIcons.filter {
                        (it is ActiveNotificationModel && it.isSuppressedFromStatusBar) ||
                            (it is ActiveBundleModel && it.key == "bundle2")
                    }
                )
        }

    @Test
@@ -326,7 +366,11 @@ class StatusBarNotificationIconsInteractorTest : SysuiTestCase() {
            kosmos.notificationListenerSettingsRepository.showSilentStatusIcons.value = false
            assertThat(filteredSet)
                .comparingElementsUsing(byAssociatedNotifModel)
                .containsNoneIn(testIcons.filter { it.isSilent })
                .containsNoneIn(
                    testIcons.filter {
                        (it is ActiveNotificationModel && it.isSilent) || (it is ActiveBundleModel)
                    }
                )
        }

    @Test
@@ -336,7 +380,11 @@ class StatusBarNotificationIconsInteractorTest : SysuiTestCase() {
            kosmos.notificationListenerSettingsRepository.showSilentStatusIcons.value = true
            assertThat(filteredSet)
                .comparingElementsUsing(byAssociatedNotifModel)
                .containsAnyIn(testIcons.filter { it.isSilent })
                .containsAnyIn(
                    testIcons.filter {
                        (it is ActiveNotificationModel && it.isSilent) || (it is ActiveBundleModel)
                    }
                )
        }

    @Test
@@ -345,7 +393,9 @@ class StatusBarNotificationIconsInteractorTest : SysuiTestCase() {
            val filteredSet by collectLastValue(underTest.statusBarNotifs)
            assertThat(filteredSet)
                .comparingElementsUsing(byAssociatedNotifModel)
                .containsNoneIn(testIcons.filter { it.isRowDismissed })
                .containsNoneIn(
                    testIcons.filter { it is ActiveNotificationModel && it.isRowDismissed }
                )
        }

    @Test
@@ -354,7 +404,9 @@ class StatusBarNotificationIconsInteractorTest : SysuiTestCase() {
            val filteredSet by collectLastValue(underTest.statusBarNotifs)
            assertThat(filteredSet)
                .comparingElementsUsing(byAssociatedNotifModel)
                .containsNoneIn(testIcons.filter { it.isLastMessageFromReply })
                .containsNoneIn(
                    testIcons.filter { it is ActiveNotificationModel && it.isLastMessageFromReply }
                )
        }

    @Test
@@ -366,26 +418,3 @@ class StatusBarNotificationIconsInteractorTest : SysuiTestCase() {
            assertThat(filteredSet).comparingElementsUsing(byIconNotifKey).contains("notif5")
        }
}

private val testIcons =
    listOf(
        activeNotificationModel(key = "notif1", groupKey = "g1"),
        activeNotificationModel(key = "notif2", groupKey = "g2", isAmbient = true),
        activeNotificationModel(key = "notif3", groupKey = "g3", isRowDismissed = true),
        activeNotificationModel(key = "notif4", groupKey = "g4", isSilent = true),
        activeNotificationModel(key = "notif5", groupKey = "g5", isLastMessageFromReply = true),
        activeNotificationModel(key = "notif6", groupKey = "g6", isSuppressedFromStatusBar = true),
        activeNotificationModel(key = "notif7", groupKey = "g7", isPulsing = true),
        activeNotificationModel(
            key = "notif8",
            groupKey = "g8",
            promotedContent = promotedContent("notif8", Base),
        ),
    )

private fun promotedContent(
    key: String,
    style: PromotedNotificationContentModel.Style,
): PromotedNotificationContentModels {
    return PromotedNotificationContentBuilder(key).applyToShared { this.style = style }.build()
}
+30 −9
Original line number Diff line number Diff line
@@ -22,11 +22,31 @@ val byKey: Correspondence<ActiveNotificationModel, String> =
    Correspondence.transforming({ it.key }, "has a key of")
val byIconIsAmbient: Correspondence<ActiveNotificationIconModel, Boolean> =
    Correspondence.transforming({ it.isAmbient }, "has an isAmbient value of")
val byAssociatedNotifModel: Correspondence<ActiveNotificationIconModel, ActiveNotificationModel> =
val byAssociatedNotifModel: Correspondence<ActiveNotificationIconModel, ActivePipelineEntryModel> =
    Correspondence.transforming(
        /* actualTransform = */ { it },
        /* expectedTransform = */ { expected ->
            checkNotNull(expected)
            when (expected) {
                is ActiveBundleModel ->
                    ActiveNotificationIconModel(
                        expected.key,
                        expected.key,
                        expected.icon,
                        expected.icon,
                        expected.icon,
                        false,
                    )
                is ActiveNotificationGroupModel ->
                    ActiveNotificationIconModel(
                        expected.key,
                        expected.summary.groupKey!!,
                        expected.summary.shelfIcon,
                        expected.summary.statusBarIcon,
                        expected.summary.aodIcon,
                        expected.summary.isAmbient,
                    )
                is ActiveNotificationModel ->
                    ActiveNotificationIconModel(
                        expected.key,
                        expected.groupKey!!,
@@ -35,6 +55,7 @@ val byAssociatedNotifModel: Correspondence<ActiveNotificationIconModel, ActiveNo
                        expected.aodIcon,
                        expected.isAmbient,
                    )
            }
        },
        /* description = */ "is icon model of",
    )
+6 −7
Original line number Diff line number Diff line
@@ -28,7 +28,6 @@ import android.content.Context
import android.graphics.drawable.Icon
import android.service.notification.StatusBarNotification
import android.util.ArrayMap
import androidx.annotation.DrawableRes
import com.android.app.tracing.traceSection
import com.android.internal.logging.InstanceId
import com.android.systemui.dagger.qualifiers.Main
@@ -122,7 +121,7 @@ private class ActiveNotificationsStoreBuilder(
        builder.addBundle(
            existingModels.createOrReuseBundle(
                key = entry.key,
                iconResId = entry.bundleRepository.bundleIcon,
                icon = Icon.createWithResource(context, entry.bundleRepository.bundleIcon),
                children = childModels,
            )
        )
@@ -412,21 +411,21 @@ private fun StatusBarNotification.toCallType(): CallType =

private fun ActiveNotificationsStore.createOrReuseBundle(
    key: String,
    @DrawableRes iconResId: Int,
    icon: Icon,
    children: List<ActiveNotificationEntryModel>,
): ActiveBundleModel {
    return bundles[key]?.takeIf { it.isCurrent(key, iconResId, children) }
        ?: ActiveBundleModel(key, iconResId, children)
    return bundles[key]?.takeIf { it.isCurrent(key, icon, children) }
        ?: ActiveBundleModel(key, icon, children)
}

private fun ActiveBundleModel.isCurrent(
    key: String,
    @DrawableRes iconResId: Int,
    icon: Icon,
    children: List<ActiveNotificationEntryModel>,
): Boolean {
    return when {
        key != this.key -> false
        iconResId != this.iconResId -> false
        icon.resId != this.icon.resId -> false
        !hasSameInstances(children, this.children) -> false
        else -> true
    }
+28 −4
Original line number Diff line number Diff line
@@ -29,6 +29,7 @@ import com.android.systemui.statusbar.notification.data.repository.Notifications
import com.android.systemui.statusbar.notification.domain.interactor.HeadsUpNotificationIconInteractor
import com.android.systemui.statusbar.notification.promoted.domain.interactor.AODPromotedNotificationInteractor
import com.android.systemui.statusbar.notification.shared.ActiveBundleModel
import com.android.systemui.statusbar.notification.shared.ActiveNotificationEntryModel
import com.android.systemui.statusbar.notification.shared.ActiveNotificationGroupModel
import com.android.systemui.statusbar.notification.shared.ActiveNotificationModel
import com.android.wm.shell.bubbles.Bubbles
@@ -90,7 +91,11 @@ constructor(
                                // bundles are located in the silent section, so only include them
                                // if we're showing low priority icons
                                is ActiveBundleModel ->
                                    if (showLowPriority) Either.first(it.toIconModel()) else null
                                    if (shouldShowBundleIcon(it, showAmbient, showLowPriority)) {
                                        Either.first(it.toIconModel())
                                    } else {
                                        null
                                    }
                                is ActiveNotificationGroupModel -> Either.second(it.summary)
                                is ActiveNotificationModel -> Either.second(it)
                            }
@@ -120,6 +125,25 @@ constructor(
        }
    }

    private fun shouldShowBundleIcon(
        model: ActiveBundleModel,
        showAmbient: Boolean,
        showLowPriority: Boolean,
    ): Boolean {
        return when {
            !showLowPriority -> false
            !showAmbient && areAllChildrenSuppressed(model.children) -> false
            else -> true
        }
    }

    private fun areAllChildrenSuppressed(children: List<ActiveNotificationEntryModel>): Boolean {
        return children.none {
            (it is ActiveNotificationModel && !it.isSuppressedFromStatusBar) ||
                (it is ActiveNotificationGroupModel && !areAllChildrenSuppressed(it.children))
        }
    }

    private fun shouldShowNotificationIcon(
        model: ActiveNotificationModel,
        forceShowHeadsUp: Boolean,
@@ -150,9 +174,9 @@ constructor(
        ActiveNotificationIconModel(
            notifKey = key,
            groupKey = key,
            shelfIcon = Icon.createWithResource(appContext, iconResId),
            statusBarIcon = Icon.createWithResource(appContext, iconResId),
            aodIcon = null,
            shelfIcon = icon,
            statusBarIcon = icon,
            aodIcon = icon,
            isAmbient = false,
        )
}
+1 −2
Original line number Diff line number Diff line
@@ -18,7 +18,6 @@ package com.android.systemui.statusbar.notification.shared
import android.app.PendingIntent
import android.graphics.drawable.Icon
import android.util.Log
import androidx.annotation.DrawableRes
import com.android.internal.logging.InstanceId
import com.android.systemui.statusbar.StatusBarIconView
import com.android.systemui.statusbar.notification.promoted.shared.model.PromotedNotificationContentModel
@@ -35,7 +34,7 @@ sealed class ActivePipelineEntryModel
/** Model for a bundle of notifications. */
data class ActiveBundleModel(
    val key: String,
    @DrawableRes val iconResId: Int,
    val icon: Icon,
    val children: List<ActiveNotificationEntryModel>,
) : ActivePipelineEntryModel()

Loading