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

Commit a09e15cc authored by Steve Elliott's avatar Steve Elliott
Browse files

model bundles in ActiveNotificationListRepository

Flag: com.android.systemui.notification_bundle_ui
Fixes: 410815667
Test: atest SystemUITests
Change-Id: I9fa0a74eeb77fa6ad3191986a85485509504a970
parent 35d4c4df
Loading
Loading
Loading
Loading
+49 −8
Original line number Diff line number Diff line
@@ -18,9 +18,11 @@ package com.android.systemui.statusbar.notification.data.repository
import com.android.systemui.dagger.SysUISingleton
import com.android.systemui.statusbar.notification.data.model.NotifStats
import com.android.systemui.statusbar.notification.data.repository.ActiveNotificationsStore.Key
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.systemui.statusbar.notification.shared.ActivePipelineEntryModel
import javax.inject.Inject
import kotlinx.coroutines.flow.MutableStateFlow

@@ -51,14 +53,16 @@ class ActiveNotificationListRepository @Inject constructor() {

/** Represents the notification list, comprised of groups and individual notifications. */
data class ActiveNotificationsStore(
    /** Notification bundles, stored by key. */
    val bundles: Map<String, ActiveBundleModel> = emptyMap(),
    /** 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].
     * represented as [Key]s. The associated [ActivePipelineEntryModel] can be retrieved by invoking
     * [get].
     */
    val renderList: List<Key> = emptyList(),

@@ -68,30 +72,42 @@ data class ActiveNotificationsStore(
     */
    val rankingsMap: Map<String, Int> = emptyMap(),
) {
    operator fun get(key: Key): ActiveNotificationEntryModel? {
    operator fun get(key: Key): ActivePipelineEntryModel? {
        return when (key) {
            is Key.Bundle -> bundles[key.key]
            is Key.Group -> groups[key.key]
            is Key.Individual -> individuals[key.key]
        }
    }

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

        data class Group(val key: String) : Key()

        data class Bundle(val key: String) : Key()
    }

    /** Mutable builder for an [ActiveNotificationsStore]. */
    class Builder {
        private val bundles = mutableMapOf<String, ActiveBundleModel>()
        private val groups = mutableMapOf<String, ActiveNotificationGroupModel>()
        private val individuals = mutableMapOf<String, ActiveNotificationModel>()
        private val renderList = mutableListOf<Key>()
        private var rankingsMap: Map<String, Int> = emptyMap()

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

        fun addNotifEntry(entry: ActivePipelineEntryModel) {
            when (entry) {
                is ActiveBundleModel -> addBundle(entry)
                is ActiveNotificationEntryModel -> addNotifEntry(entry)
            }
        }

        fun addEntry(entry: ActiveNotificationEntryModel) {
        fun addNotifEntry(entry: ActiveNotificationEntryModel) {
            when (entry) {
                is ActiveNotificationModel -> addIndividualNotif(entry)
                is ActiveNotificationGroupModel -> addNotifGroup(entry)
@@ -100,14 +116,39 @@ data class ActiveNotificationsStore(

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

        fun addNotifGroup(group: ActiveNotificationGroupModel) {
            renderList.add(Key.Group(group.key))
            trackNotifGroup(group)
        }

        fun addBundle(bundle: ActiveBundleModel) {
            renderList.add(Key.Bundle(bundle.key))
            trackBundle(bundle)
        }

        private fun trackNotifEntry(entry: ActiveNotificationEntryModel) {
            when (entry) {
                is ActiveNotificationGroupModel -> trackNotifGroup(entry)
                is ActiveNotificationModel -> trackIndividualNotif(entry)
            }
        }

        private fun trackIndividualNotif(notif: ActiveNotificationModel) {
            individuals[notif.key] = notif
        }

        private fun trackNotifGroup(group: ActiveNotificationGroupModel) {
            groups[group.key] = group
            individuals[group.summary.key] = group.summary
            group.children.forEach { individuals[it.key] = it }
            group.children.forEach { trackIndividualNotif(it) }
        }

        private fun trackBundle(bundle: ActiveBundleModel) {
            bundles[bundle.key] = bundle
            bundle.children.forEach { child -> trackNotifEntry(child) }
        }

        fun setRankingsMap(map: Map<String, Int>) {
+30 −15
Original line number Diff line number Diff line
@@ -19,9 +19,12 @@ import com.android.systemui.dagger.SysUISingleton
import com.android.systemui.dagger.qualifiers.Background
import com.android.systemui.statusbar.notification.data.model.NotifStats
import com.android.systemui.statusbar.notification.data.repository.ActiveNotificationListRepository
import com.android.systemui.statusbar.notification.data.repository.ActiveNotificationsStore
import com.android.systemui.statusbar.notification.promoted.PromotedNotificationUi
import com.android.systemui.statusbar.notification.shared.ActiveBundleModel
import com.android.systemui.statusbar.notification.shared.ActiveNotificationGroupModel
import com.android.systemui.statusbar.notification.shared.ActiveNotificationModel
import com.android.systemui.statusbar.notification.shared.ActivePipelineEntryModel
import com.android.systemui.statusbar.notification.shared.CallType
import javax.inject.Inject
import kotlinx.coroutines.CoroutineDispatcher
@@ -39,24 +42,14 @@ constructor(
    @Background private val backgroundDispatcher: CoroutineDispatcher,
) {
    /**
     * Top level list of Notifications actively presented to the user in the notification stack, in
     * order.
     * List of top-level entries in the notification stack that are backed by a notification.
     *
     * This omits bundles and bundled notifications; the bundle is top-level, and it is not backed
     * by a notification.
     */
    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
                    }
                }
            }
            .map { store -> topLevelRepresentativeModels(store) }
            .flowOn(backgroundDispatcher)

    /**
@@ -172,6 +165,28 @@ constructor(
        repository.notifStats.value = notifStats
    }

    /**
     * Returns the representative model for each top-level entry in the [store]. By definition, this
     * will omit bundles, which are always top-level and do not have a representative entry.
     */
    private fun topLevelRepresentativeModels(
        store: ActiveNotificationsStore
    ): List<ActiveNotificationModel> =
        store.renderList.mapNotNull { key -> representativeModelForKey(store, key) }

    private fun representativeModelForKey(
        store: ActiveNotificationsStore,
        key: ActiveNotificationsStore.Key,
    ): ActiveNotificationModel? {
        val entry: ActivePipelineEntryModel =
            store[key] ?: error("Could not find entry with key=$key in active notif store.")
        return when (entry) {
            is ActiveNotificationGroupModel -> entry.summary
            is ActiveNotificationModel -> entry
            is ActiveBundleModel -> null
        }
    }

    companion object {
        fun ActiveNotificationModel.isOngoingCallNotification() = this.callType == CallType.Ongoing
    }
+71 −26
Original line number Diff line number Diff line
@@ -40,6 +40,7 @@ import com.android.systemui.statusbar.notification.data.repository.ActiveNotific
import com.android.systemui.statusbar.notification.data.repository.ActiveNotificationsStore
import com.android.systemui.statusbar.notification.promoted.shared.model.PromotedNotificationContentModel
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.ActiveNotificationEntryModel
import com.android.systemui.statusbar.notification.shared.ActiveNotificationGroupModel
import com.android.systemui.statusbar.notification.shared.ActiveNotificationModel
@@ -100,35 +101,31 @@ private class ActiveNotificationsStoreBuilder(
     */
    fun addPipelineEntry(entry: PipelineEntry) {
        when (entry) {
            is BundleEntry -> {
                // TODO(b/410815667): Handle BundleEntry
            }
            is ListEntry -> {
                addListEntry(entry)
            }
            is BundleEntry -> addBundleEntry(entry)
            is ListEntry -> addListEntry(entry)
        }
    }

    private 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,
                        )
                    )
            is GroupEntry -> addGroupEntry(entry)
            is NotificationEntry -> addNotificationEntry(entry)
        }
    }

            is NotificationEntry -> {
                builder.addIndividualNotif(entry.toModel())
    private fun addBundleEntry(entry: BundleEntry) {
        val childModels = entry.children.mapNotNull { it.toModel() }
        builder.addBundle(
            existingModels.createOrReuseBundle(key = entry.key, children = childModels)
        )
    }

    private fun addGroupEntry(entry: GroupEntry) {
        entry.toModel()?.let { builder.addNotifGroup(it) }
    }

    private fun addNotificationEntry(entry: NotificationEntry) {
        builder.addIndividualNotif(entry.toModel())
    }

    fun setRankingsMap(entries: List<PipelineEntry>) {
@@ -165,6 +162,24 @@ private class ActiveNotificationsStoreBuilder(
        }
    }

    private fun ListEntry.toModel(): ActiveNotificationEntryModel? =
        when (this) {
            is GroupEntry -> toModel()
            is NotificationEntry -> toModel()
            else -> null
        }

    private fun GroupEntry.toModel(): ActiveNotificationGroupModel? =
        summary?.let { summary ->
            val summaryModel = summary.toModel()
            val childModels = children.map { it.toModel() }
            existingModels.createOrReuseGroup(
                key = key,
                summary = summaryModel,
                children = childModels,
            )
        }

    private fun NotificationEntry.toModel(): ActiveNotificationModel {
        val promotedContent =
            if (PromotedNotificationContentModel.featureFlagEnabled()) {
@@ -173,7 +188,7 @@ private class ActiveNotificationsStoreBuilder(
                null
            }

        return existingModels.createOrReuse(
        return existingModels.createOrReuseNotif(
            key = key,
            groupKey = sbn.groupKey,
            whenTime = sbn.notification.`when`,
@@ -202,7 +217,7 @@ private class ActiveNotificationsStoreBuilder(
    }
}

private fun ActiveNotificationsStore.createOrReuse(
private fun ActiveNotificationsStore.createOrReuseNotif(
    key: String,
    groupKey: String?,
    whenTime: Long,
@@ -341,7 +356,7 @@ private fun ActiveNotificationModel.isCurrent(
    }
}

private fun ActiveNotificationsStore.createOrReuse(
private fun ActiveNotificationsStore.createOrReuseGroup(
    key: String,
    summary: ActiveNotificationModel,
    children: List<ActiveNotificationModel>,
@@ -372,3 +387,33 @@ private fun StatusBarNotification.toCallType(): CallType =
        CALL_TYPE_UNKNOWN -> CallType.Unknown
        else -> CallType.Unknown
    }

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

private fun ActiveBundleModel.isCurrent(
    key: String,
    children: List<ActiveNotificationEntryModel>,
): Boolean {
    return when {
        key != this.key -> false
        !hasSameInstances(children, this.children) -> false
        else -> true
    }
}

private fun hasSameInstances(list1: List<*>, list2: List<*>): Boolean {
    if (list1.size != list2.size) {
        return false
    }
    for (i in list1.indices) {
        if (list1[i] !== list2[i]) {
            return false
        }
    }
    return true
}
+12 −1
Original line number Diff line number Diff line
@@ -26,9 +26,20 @@ import com.android.systemui.statusbar.notification.stack.PriorityBucket

/**
 * Model for a top-level "entry" in the notification list, either an
 * [individual notification][ActiveNotificationModel], a [group][ActiveNotificationGroupModel], or a
 * [bundle][ActiveBundleModel].
 */
sealed class ActivePipelineEntryModel

/** Model for a bundle of notifications. */
data class ActiveBundleModel(val key: String, val children: List<ActiveNotificationEntryModel>) :
    ActivePipelineEntryModel()

/**
 * Model for a notification-backed "entry" in the notification list, either an
 * [individual notification][ActiveNotificationModel], or a [group][ActiveNotificationGroupModel].
 */
sealed class ActiveNotificationEntryModel
sealed class ActiveNotificationEntryModel : ActivePipelineEntryModel()

/**
 * Model for an individual notification in the notification list. These can appear as either an
+1 −1
Original line number Diff line number Diff line
@@ -30,7 +30,7 @@ fun ActiveNotificationListRepository.setActiveNotifs(count: Int) {
                val rankingsMap = mutableMapOf<String, Int>()
                repeat(count) { i ->
                    val key = "$i"
                    addEntry(activeNotificationModel(key = key))
                    addNotifEntry(activeNotificationModel(key = key))
                    rankingsMap[key] = i
                }