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

Commit 7a99085f authored by Julia Reynolds's avatar Julia Reynolds Committed by Android (Google) Code Review
Browse files

Merge "Fix bundle icon logic" into main

parents 4efe50f4 6965f498
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