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

Commit 01bcfa46 authored by Steve Elliott's avatar Steve Elliott
Browse files

Preserve notif group stucture in repository

Flag: ACONFIG com.android.systemui.notifications_icon_container_refactor
DEVELOPMENT
Bug: 278765923
Test: atest SystemUITests

Change-Id: If9355f54d09e1cf38c8ee891851ebe91ef3bd7fe
parent 96cc5e10
Loading
Loading
Loading
Loading
+60 −6
Original line number Diff line number Diff line
@@ -16,6 +16,9 @@
package com.android.systemui.statusbar.notification.data.repository

import com.android.systemui.dagger.SysUISingleton
import com.android.systemui.statusbar.notification.data.repository.ActiveNotificationsStore.Key
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 javax.inject.Inject
import kotlinx.coroutines.flow.MutableStateFlow
@@ -29,13 +32,64 @@ import kotlinx.coroutines.flow.MutableStateFlow
 */
@SysUISingleton
class ActiveNotificationListRepository @Inject constructor() {
    /**
     * Notifications actively presented to the user in the notification stack.
     *
     * @see com.android.systemui.statusbar.notification.collection.listbuilder.OnAfterRenderListListener
     */
    val activeNotifications = MutableStateFlow(emptyMap<String, ActiveNotificationModel>())
    /** Notifications actively presented to the user in the notification list. */
    val activeNotifications = MutableStateFlow(ActiveNotificationsStore())

    /** Are any already-seen notifications currently filtered out of the active list? */
    val hasFilteredOutSeenNotifications = MutableStateFlow(false)
}

/** Represents the notification list, comprised of groups and individual notifications. */
data class ActiveNotificationsStore(
    /** Notification groups, stored by key. */
    val groups: Map<String, ActiveNotificationGroupModel> = emptyMap(),
    /** All individual notifications, including top-level and group children, stored by key. */
    val individuals: Map<String, ActiveNotificationModel> = emptyMap(),
    /**
     * Ordered top-level list of entries in the notification list (either groups or individual),
     * represented as [Key]s. The associated [ActiveNotificationEntryModel] can be retrieved by
     * invoking [get].
     */
    val renderList: List<Key> = emptyList(),
) {
    operator fun get(key: Key): ActiveNotificationEntryModel? {
        return when (key) {
            is Key.Group -> groups[key.key]
            is Key.Individual -> individuals[key.key]
        }
    }

    /** Unique key identifying an [ActiveNotificationEntryModel] in the store. */
    sealed class Key {
        data class Individual(val key: String) : Key()
        data class Group(val key: String) : Key()
    }

    /** Mutable builder for an [ActiveNotificationsStore]. */
    class Builder {
        private val groups = mutableMapOf<String, ActiveNotificationGroupModel>()
        private val individuals = mutableMapOf<String, ActiveNotificationModel>()
        private val renderList = mutableListOf<Key>()

        fun build() = ActiveNotificationsStore(groups, individuals, renderList)

        fun addEntry(entry: ActiveNotificationEntryModel) {
            when (entry) {
                is ActiveNotificationModel -> addIndividualNotif(entry)
                is ActiveNotificationGroupModel -> addNotifGroup(entry)
            }
        }

        fun addIndividualNotif(notif: ActiveNotificationModel) {
            renderList.add(Key.Individual(notif.key))
            individuals[notif.key] = notif
        }

        fun addNotifGroup(group: ActiveNotificationGroupModel) {
            renderList.add(Key.Group(group.key))
            groups[group.key] = group
            individuals[group.summary.key] = group.summary
            group.children.forEach { individuals[it.key] = it }
        }
    }
}
+13 −2
Original line number Diff line number Diff line
@@ -16,6 +16,7 @@
package com.android.systemui.statusbar.notification.domain.interactor

import com.android.systemui.statusbar.notification.data.repository.ActiveNotificationListRepository
import com.android.systemui.statusbar.notification.shared.ActiveNotificationGroupModel
import com.android.systemui.statusbar.notification.shared.ActiveNotificationModel
import javax.inject.Inject
import kotlinx.coroutines.flow.Flow
@@ -27,6 +28,16 @@ constructor(
    repository: ActiveNotificationListRepository,
) {
    /** Notifications actively presented to the user in the notification stack, in order. */
    val notifications: Flow<Collection<ActiveNotificationModel>> =
        repository.activeNotifications.map { it.values }
    val topLevelRepresentativeNotifications: Flow<List<ActiveNotificationModel>> =
        repository.activeNotifications.map { store ->
            store.renderList.map { key ->
                val entry =
                    store[key]
                        ?: error("Could not find notification with key $key in active notif store.")
                when (entry) {
                    is ActiveNotificationGroupModel -> entry.summary
                    is ActiveNotificationModel -> entry
                }
            }
        }
}
+143 −82
Original line number Diff line number Diff line
@@ -16,16 +16,18 @@
package com.android.systemui.statusbar.notification.domain.interactor

import android.graphics.drawable.Icon
import com.android.systemui.statusbar.notification.collection.GroupEntry
import com.android.systemui.statusbar.notification.collection.ListEntry
import com.android.systemui.statusbar.notification.collection.NotificationEntry
import com.android.systemui.statusbar.notification.collection.provider.SectionStyleProvider
import com.android.systemui.statusbar.notification.data.repository.ActiveNotificationListRepository
import com.android.systemui.statusbar.notification.data.repository.ActiveNotificationsStore
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 javax.inject.Inject
import kotlinx.coroutines.flow.update

private typealias ModelStore = Map<String, ActiveNotificationModel>

/**
 * Logic for passing information from the
 * [com.android.systemui.statusbar.notification.collection.NotifPipeline] to the presentation
@@ -38,24 +40,61 @@ constructor(
    private val sectionStyleProvider: SectionStyleProvider,
) {
    /**
     * Sets the current list of rendered notification entries as displayed in the notification
     * stack.
     *
     * @see com.android.systemui.statusbar.notification.data.repository.ActiveNotificationListRepository.activeNotifications
     * Sets the current list of rendered notification entries as displayed in the notification list.
     */
    fun setRenderedList(entries: List<ListEntry>) {
        repository.activeNotifications.update { existingModels ->
            entries
                .asSequence()
                .mapNotNull { it.representativeEntry }
                .associateBy(
                    keySelector = { it.key },
                    valueTransform = { it.toModel(existingModels) },
            buildActiveNotificationsStore(existingModels, sectionStyleProvider) {
                entries.forEach(::addListEntry)
            }
        }
    }
}

private fun buildActiveNotificationsStore(
    existingModels: ActiveNotificationsStore,
    sectionStyleProvider: SectionStyleProvider,
    block: ActiveNotificationsStoreBuilder.() -> Unit
): ActiveNotificationsStore =
    ActiveNotificationsStoreBuilder(existingModels, sectionStyleProvider).apply(block).build()

private class ActiveNotificationsStoreBuilder(
    private val existingModels: ActiveNotificationsStore,
    private val sectionStyleProvider: SectionStyleProvider,
) {
    private val builder = ActiveNotificationsStore.Builder()

    fun build(): ActiveNotificationsStore = builder.build()

    /**
     * Convert a [ListEntry] into [ActiveNotificationEntryModel]s, and add them to the
     * [ActiveNotificationsStore]. Special care is taken to avoid re-allocating models if the result
     * would be identical to an existing model (at the expense of additional computations).
     */
    fun addListEntry(entry: ListEntry) {
        when (entry) {
            is GroupEntry -> {
                entry.summary?.let { summary ->
                    val summaryModel = summary.toModel()
                    val childModels = entry.children.map { it.toModel() }
                    builder.addNotifGroup(
                        existingModels.createOrReuse(
                            key = entry.key,
                            summary = summaryModel,
                            children = childModels
                        )
                    )
                }
            }
            else -> {
                entry.representativeEntry?.let { notifEntry ->
                    builder.addIndividualNotif(notifEntry.toModel())
                }
            }
        }
    }

    private fun NotificationEntry.toModel(existingModels: ModelStore): ActiveNotificationModel =
    private fun NotificationEntry.toModel(): ActiveNotificationModel =
        existingModels.createOrReuse(
            key = key,
            groupKey = sbn.groupKey,
@@ -69,8 +108,9 @@ constructor(
            shelfIcon = icons.shelfIcon?.sourceIcon,
            statusBarIcon = icons.statusBarIcon?.sourceIcon,
        )
}

    private fun ModelStore.createOrReuse(
private fun ActiveNotificationsStore.createOrReuse(
    key: String,
    groupKey: String?,
    isAmbient: Boolean,
@@ -83,7 +123,7 @@ constructor(
    shelfIcon: Icon?,
    statusBarIcon: Icon?
): ActiveNotificationModel {
        return this[key]?.takeIf {
    return individuals[key]?.takeIf {
        it.isCurrent(
            key = key,
            groupKey = groupKey,
@@ -141,4 +181,25 @@ constructor(
        else -> true
    }
}

private fun ActiveNotificationsStore.createOrReuse(
    key: String,
    summary: ActiveNotificationModel,
    children: List<ActiveNotificationModel>,
): ActiveNotificationGroupModel {
    return groups[key]?.takeIf { it.isCurrent(key, summary, children) }
        ?: ActiveNotificationGroupModel(key, summary, children)
}

private fun ActiveNotificationGroupModel.isCurrent(
    key: String,
    summary: ActiveNotificationModel,
    children: List<ActiveNotificationModel>,
): Boolean {
    return when {
        key != this.key -> false
        summary != this.summary -> false
        children != this.children -> false
        else -> true
    }
}
+1 −1
Original line number Diff line number Diff line
@@ -48,7 +48,7 @@ constructor(
        showPulsing: Boolean = true,
    ): Flow<Set<ActiveNotificationModel>> {
        return combine(
            activeNotificationsInteractor.notifications,
            activeNotificationsInteractor.topLevelRepresentativeNotifications,
            keyguardViewStateRepository.areNotificationsFullyHidden,
        ) { notifications, notifsFullyHidden ->
            notifications
+1 −1
Original line number Diff line number Diff line
@@ -61,7 +61,7 @@ constructor(
            darkIconInteractor.tintAreas,
            darkIconInteractor.tintColor,
            // Included so that tints are re-applied after entries are changed.
            notificationsInteractor.notifications,
            notificationsInteractor.topLevelRepresentativeNotifications,
        ) { areas, tint, _ ->
            NotificationIconColorLookup { viewBounds: Rect ->
                if (DarkIconDispatcher.isInAreas(areas, viewBounds)) {
Loading