1 /*
<lambda>null2 * Copyright (C) 2023 The Android Open Source Project
3 *
4 * Licensed under the Apache License, Version 2.0 (the "License");
5 * you may not use this file except in compliance with the License.
6 * You may obtain a copy of the License at
7 *
8 * http://www.apache.org/licenses/LICENSE-2.0
9 *
10 * Unless required by applicable law or agreed to in writing, software
11 * distributed under the License is distributed on an "AS IS" BASIS,
12 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13 * See the License for the specific language governing permissions and
14 * limitations under the License.
15 */
16 package com.android.systemui.statusbar.notification.domain.interactor
17
18 import android.graphics.drawable.Icon
19 import android.util.ArrayMap
20 import com.android.app.tracing.traceSection
21 import com.android.systemui.statusbar.notification.collection.GroupEntry
22 import com.android.systemui.statusbar.notification.collection.ListEntry
23 import com.android.systemui.statusbar.notification.collection.NotificationEntry
24 import com.android.systemui.statusbar.notification.collection.provider.SectionStyleProvider
25 import com.android.systemui.statusbar.notification.data.repository.ActiveNotificationListRepository
26 import com.android.systemui.statusbar.notification.data.repository.ActiveNotificationsStore
27 import com.android.systemui.statusbar.notification.shared.ActiveNotificationEntryModel
28 import com.android.systemui.statusbar.notification.shared.ActiveNotificationGroupModel
29 import com.android.systemui.statusbar.notification.shared.ActiveNotificationModel
30 import javax.inject.Inject
31 import kotlinx.coroutines.flow.update
32
33 /**
34 * Logic for passing information from the
35 * [com.android.systemui.statusbar.notification.collection.NotifPipeline] to the presentation
36 * layers.
37 */
38 class RenderNotificationListInteractor
39 @Inject
40 constructor(
41 private val repository: ActiveNotificationListRepository,
42 private val sectionStyleProvider: SectionStyleProvider,
43 ) {
44 /**
45 * Sets the current list of rendered notification entries as displayed in the notification list.
46 */
47 fun setRenderedList(entries: List<ListEntry>) {
48 traceSection("RenderNotificationListInteractor.setRenderedList") {
49 repository.activeNotifications.update { existingModels ->
50 buildActiveNotificationsStore(existingModels, sectionStyleProvider) {
51 entries.forEach(::addListEntry)
52 setRankingsMap(entries)
53 }
54 }
55 }
56 }
57 }
58
buildActiveNotificationsStorenull59 private fun buildActiveNotificationsStore(
60 existingModels: ActiveNotificationsStore,
61 sectionStyleProvider: SectionStyleProvider,
62 block: ActiveNotificationsStoreBuilder.() -> Unit
63 ): ActiveNotificationsStore =
64 ActiveNotificationsStoreBuilder(existingModels, sectionStyleProvider).apply(block).build()
65
66 private class ActiveNotificationsStoreBuilder(
67 private val existingModels: ActiveNotificationsStore,
68 private val sectionStyleProvider: SectionStyleProvider,
69 ) {
70 private val builder = ActiveNotificationsStore.Builder()
71
72 fun build(): ActiveNotificationsStore = builder.build()
73
74 /**
75 * Convert a [ListEntry] into [ActiveNotificationEntryModel]s, and add them to the
76 * [ActiveNotificationsStore]. Special care is taken to avoid re-allocating models if the result
77 * would be identical to an existing model (at the expense of additional computations).
78 */
79 fun addListEntry(entry: ListEntry) {
80 when (entry) {
81 is GroupEntry -> {
82 entry.summary?.let { summary ->
83 val summaryModel = summary.toModel()
84 val childModels = entry.children.map { it.toModel() }
85 builder.addNotifGroup(
86 existingModels.createOrReuse(
87 key = entry.key,
88 summary = summaryModel,
89 children = childModels
90 )
91 )
92 }
93 }
94 else -> {
95 entry.representativeEntry?.let { notifEntry ->
96 builder.addIndividualNotif(notifEntry.toModel())
97 }
98 }
99 }
100 }
101
102 fun setRankingsMap(entries: List<ListEntry>) {
103 builder.setRankingsMap(flatMapToRankingsMap(entries))
104 }
105
106 fun flatMapToRankingsMap(entries: List<ListEntry>): Map<String, Int> {
107 val result = ArrayMap<String, Int>()
108 for (entry in entries) {
109 if (entry is NotificationEntry) {
110 entry.representativeEntry?.let { representativeEntry ->
111 result[representativeEntry.key] = representativeEntry.ranking.rank
112 }
113 } else if (entry is GroupEntry) {
114 entry.summary?.let { summary -> result[summary.key] = summary.ranking.rank }
115 for (child in entry.children) {
116 result[child.key] = child.ranking.rank
117 }
118 }
119 }
120 return result
121 }
122
123 private fun NotificationEntry.toModel(): ActiveNotificationModel =
124 existingModels.createOrReuse(
125 key = key,
126 groupKey = sbn.groupKey,
127 isAmbient = sectionStyleProvider.isMinimized(this),
128 isRowDismissed = isRowDismissed,
129 isSilent = sectionStyleProvider.isSilent(this),
130 isLastMessageFromReply = isLastMessageFromReply,
131 isSuppressedFromStatusBar = shouldSuppressStatusBar(),
132 isPulsing = showingPulsing(),
133 aodIcon = icons.aodIcon?.sourceIcon,
134 shelfIcon = icons.shelfIcon?.sourceIcon,
135 statusBarIcon = icons.statusBarIcon?.sourceIcon,
136 uid = sbn.uid,
137 packageName = sbn.packageName,
138 instanceId = sbn.instanceId?.id,
139 isGroupSummary = sbn.notification.isGroupSummary,
140 bucket = bucket,
141 )
142 }
143
createOrReusenull144 private fun ActiveNotificationsStore.createOrReuse(
145 key: String,
146 groupKey: String?,
147 isAmbient: Boolean,
148 isRowDismissed: Boolean,
149 isSilent: Boolean,
150 isLastMessageFromReply: Boolean,
151 isSuppressedFromStatusBar: Boolean,
152 isPulsing: Boolean,
153 aodIcon: Icon?,
154 shelfIcon: Icon?,
155 statusBarIcon: Icon?,
156 uid: Int,
157 packageName: String,
158 instanceId: Int?,
159 isGroupSummary: Boolean,
160 bucket: Int,
161 ): ActiveNotificationModel {
162 return individuals[key]?.takeIf {
163 it.isCurrent(
164 key = key,
165 groupKey = groupKey,
166 isAmbient = isAmbient,
167 isRowDismissed = isRowDismissed,
168 isSilent = isSilent,
169 isLastMessageFromReply = isLastMessageFromReply,
170 isSuppressedFromStatusBar = isSuppressedFromStatusBar,
171 isPulsing = isPulsing,
172 aodIcon = aodIcon,
173 shelfIcon = shelfIcon,
174 statusBarIcon = statusBarIcon,
175 uid = uid,
176 instanceId = instanceId,
177 isGroupSummary = isGroupSummary,
178 packageName = packageName,
179 bucket = bucket,
180 )
181 }
182 ?: ActiveNotificationModel(
183 key = key,
184 groupKey = groupKey,
185 isAmbient = isAmbient,
186 isRowDismissed = isRowDismissed,
187 isSilent = isSilent,
188 isLastMessageFromReply = isLastMessageFromReply,
189 isSuppressedFromStatusBar = isSuppressedFromStatusBar,
190 isPulsing = isPulsing,
191 aodIcon = aodIcon,
192 shelfIcon = shelfIcon,
193 statusBarIcon = statusBarIcon,
194 uid = uid,
195 instanceId = instanceId,
196 isGroupSummary = isGroupSummary,
197 packageName = packageName,
198 bucket = bucket,
199 )
200 }
201
ActiveNotificationModelnull202 private fun ActiveNotificationModel.isCurrent(
203 key: String,
204 groupKey: String?,
205 isAmbient: Boolean,
206 isRowDismissed: Boolean,
207 isSilent: Boolean,
208 isLastMessageFromReply: Boolean,
209 isSuppressedFromStatusBar: Boolean,
210 isPulsing: Boolean,
211 aodIcon: Icon?,
212 shelfIcon: Icon?,
213 statusBarIcon: Icon?,
214 uid: Int,
215 packageName: String,
216 instanceId: Int?,
217 isGroupSummary: Boolean,
218 bucket: Int,
219 ): Boolean {
220 return when {
221 key != this.key -> false
222 groupKey != this.groupKey -> false
223 isAmbient != this.isAmbient -> false
224 isRowDismissed != this.isRowDismissed -> false
225 isSilent != this.isSilent -> false
226 isLastMessageFromReply != this.isLastMessageFromReply -> false
227 isSuppressedFromStatusBar != this.isSuppressedFromStatusBar -> false
228 isPulsing != this.isPulsing -> false
229 aodIcon != this.aodIcon -> false
230 shelfIcon != this.shelfIcon -> false
231 statusBarIcon != this.statusBarIcon -> false
232 uid != this.uid -> false
233 instanceId != this.instanceId -> false
234 isGroupSummary != this.isGroupSummary -> false
235 packageName != this.packageName -> false
236 bucket != this.bucket -> false
237 else -> true
238 }
239 }
240
createOrReusenull241 private fun ActiveNotificationsStore.createOrReuse(
242 key: String,
243 summary: ActiveNotificationModel,
244 children: List<ActiveNotificationModel>,
245 ): ActiveNotificationGroupModel {
246 return groups[key]?.takeIf { it.isCurrent(key, summary, children) }
247 ?: ActiveNotificationGroupModel(key, summary, children)
248 }
249
ActiveNotificationGroupModelnull250 private fun ActiveNotificationGroupModel.isCurrent(
251 key: String,
252 summary: ActiveNotificationModel,
253 children: List<ActiveNotificationModel>,
254 ): Boolean {
255 return when {
256 key != this.key -> false
257 summary != this.summary -> false
258 children != this.children -> false
259 else -> true
260 }
261 }
262