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