• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
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.app.Notification.CallStyle.CALL_TYPE_INCOMING
19 import android.app.Notification.CallStyle.CALL_TYPE_ONGOING
20 import android.app.Notification.CallStyle.CALL_TYPE_SCREENING
21 import android.app.Notification.CallStyle.CALL_TYPE_UNKNOWN
22 import android.app.Notification.EXTRA_CALL_TYPE
23 import android.app.Notification.FLAG_ONGOING_EVENT
24 import android.app.PendingIntent
25 import android.content.Context
26 import android.graphics.drawable.Icon
27 import android.service.notification.StatusBarNotification
28 import android.util.ArrayMap
29 import com.android.app.tracing.traceSection
30 import com.android.internal.logging.InstanceId
31 import com.android.systemui.dagger.qualifiers.Main
32 import com.android.systemui.statusbar.StatusBarIconView
33 import com.android.systemui.statusbar.notification.collection.GroupEntry
34 import com.android.systemui.statusbar.notification.collection.NotificationEntry
35 import com.android.systemui.statusbar.notification.collection.PipelineEntry
36 import com.android.systemui.statusbar.notification.collection.provider.SectionStyleProvider
37 import com.android.systemui.statusbar.notification.data.repository.ActiveNotificationListRepository
38 import com.android.systemui.statusbar.notification.data.repository.ActiveNotificationsStore
39 import com.android.systemui.statusbar.notification.promoted.shared.model.PromotedNotificationContentModel
40 import com.android.systemui.statusbar.notification.promoted.shared.model.PromotedNotificationContentModels
41 import com.android.systemui.statusbar.notification.shared.ActiveNotificationEntryModel
42 import com.android.systemui.statusbar.notification.shared.ActiveNotificationGroupModel
43 import com.android.systemui.statusbar.notification.shared.ActiveNotificationModel
44 import com.android.systemui.statusbar.notification.shared.CallType
45 import javax.inject.Inject
46 import kotlinx.coroutines.flow.update
47 
48 /**
49  * Logic for passing information from the
50  * [com.android.systemui.statusbar.notification.collection.NotifPipeline] to the presentation
51  * layers.
52  */
53 class RenderNotificationListInteractor
54 @Inject
55 constructor(
56     private val repository: ActiveNotificationListRepository,
57     private val sectionStyleProvider: SectionStyleProvider,
58     @Main private val context: Context,
59 ) {
60     /**
61      * Sets the current list of rendered notification entries as displayed in the notification list.
62      */
63     fun setRenderedList(entries: List<PipelineEntry>) {
64         traceSection("RenderNotificationListInteractor.setRenderedList") {
65             repository.activeNotifications.update { existingModels ->
66                 buildActiveNotificationsStore(existingModels, sectionStyleProvider, context) {
67                     entries.forEach(::addPipelineEntry)
68                     setRankingsMap(entries)
69                 }
70             }
71         }
72     }
73 }
74 
buildActiveNotificationsStorenull75 private fun buildActiveNotificationsStore(
76     existingModels: ActiveNotificationsStore,
77     sectionStyleProvider: SectionStyleProvider,
78     context: Context,
79     block: ActiveNotificationsStoreBuilder.() -> Unit,
80 ): ActiveNotificationsStore =
81     ActiveNotificationsStoreBuilder(existingModels, sectionStyleProvider, context)
82         .apply(block)
83         .build()
84 
85 private class ActiveNotificationsStoreBuilder(
86     private val existingModels: ActiveNotificationsStore,
87     private val sectionStyleProvider: SectionStyleProvider,
88     private val context: Context,
89 ) {
90     private val builder = ActiveNotificationsStore.Builder()
91 
92     fun build(): ActiveNotificationsStore = builder.build()
93 
94     /**
95      * Convert a [PipelineEntry] into [ActiveNotificationEntryModel]s, and add them to the
96      * [ActiveNotificationsStore]. Special care is taken to avoid re-allocating models if the result
97      * would be identical to an existing model (at the expense of additional computations).
98      */
99     fun addPipelineEntry(entry: PipelineEntry) {
100         when (entry) {
101             is GroupEntry -> {
102                 entry.summary?.let { summary ->
103                     val summaryModel = summary.toModel()
104                     val childModels = entry.children.map { it.toModel() }
105                     builder.addNotifGroup(
106                         existingModels.createOrReuse(
107                             key = entry.key,
108                             summary = summaryModel,
109                             children = childModels,
110                         )
111                     )
112                 }
113             }
114             else -> {
115                 entry.representativeEntry?.let { notifEntry ->
116                     builder.addIndividualNotif(notifEntry.toModel())
117                 }
118             }
119         }
120     }
121 
122     fun setRankingsMap(entries: List<PipelineEntry>) {
123         builder.setRankingsMap(flatMapToRankingsMap(entries))
124     }
125 
126     fun flatMapToRankingsMap(entries: List<PipelineEntry>): Map<String, Int> {
127         val result = ArrayMap<String, Int>()
128         for (entry in entries) {
129             if (entry is NotificationEntry) {
130                 entry.representativeEntry?.let { representativeEntry ->
131                     result[representativeEntry.key] = representativeEntry.ranking.rank
132                 }
133             } else if (entry is GroupEntry) {
134                 entry.summary?.let { summary -> result[summary.key] = summary.ranking.rank }
135                 for (child in entry.children) {
136                     result[child.key] = child.ranking.rank
137                 }
138             }
139         }
140         return result
141     }
142 
143     private fun NotificationEntry.toModel(): ActiveNotificationModel {
144         val promotedContent =
145             if (PromotedNotificationContentModel.featureFlagEnabled()) {
146                 promotedNotificationContentModels
147             } else {
148                 null
149             }
150 
151         return existingModels.createOrReuse(
152             key = key,
153             groupKey = sbn.groupKey,
154             whenTime = sbn.notification.`when`,
155             isForegroundService = sbn.notification.isForegroundService,
156             isOngoingEvent = (sbn.notification.flags and FLAG_ONGOING_EVENT) != 0,
157             isAmbient = sectionStyleProvider.isMinimized(this),
158             isRowDismissed = isRowDismissed,
159             isSilent = sectionStyleProvider.isSilent(this),
160             isLastMessageFromReply = isLastMessageFromReply,
161             isSuppressedFromStatusBar = shouldSuppressStatusBar(),
162             isPulsing = showingPulsing(),
163             aodIcon = icons.aodIcon?.sourceIcon,
164             shelfIcon = icons.shelfIcon?.sourceIcon,
165             statusBarIcon = icons.statusBarIcon?.sourceIcon,
166             statusBarChipIconView = icons.statusBarChipIcon,
167             uid = sbn.uid,
168             packageName = sbn.packageName,
169             appName = sbn.notification.loadHeaderAppName(context) ?: "",
170             contentIntent = sbn.notification.contentIntent,
171             instanceId = sbn.instanceId,
172             isGroupSummary = sbn.notification.isGroupSummary,
173             bucket = bucket,
174             callType = sbn.toCallType(),
175             promotedContent = promotedContent,
176         )
177     }
178 }
179 
createOrReusenull180 private fun ActiveNotificationsStore.createOrReuse(
181     key: String,
182     groupKey: String?,
183     whenTime: Long,
184     isForegroundService: Boolean,
185     isOngoingEvent: Boolean,
186     isAmbient: Boolean,
187     isRowDismissed: Boolean,
188     isSilent: Boolean,
189     isLastMessageFromReply: Boolean,
190     isSuppressedFromStatusBar: Boolean,
191     isPulsing: Boolean,
192     aodIcon: Icon?,
193     shelfIcon: Icon?,
194     statusBarIcon: Icon?,
195     statusBarChipIconView: StatusBarIconView?,
196     uid: Int,
197     packageName: String,
198     appName: String,
199     contentIntent: PendingIntent?,
200     instanceId: InstanceId?,
201     isGroupSummary: Boolean,
202     bucket: Int,
203     callType: CallType,
204     promotedContent: PromotedNotificationContentModels?,
205 ): ActiveNotificationModel {
206     return individuals[key]?.takeIf {
207         it.isCurrent(
208             key = key,
209             groupKey = groupKey,
210             whenTime = whenTime,
211             isForegroundService = isForegroundService,
212             isOngoingEvent = isOngoingEvent,
213             isAmbient = isAmbient,
214             isRowDismissed = isRowDismissed,
215             isSilent = isSilent,
216             isLastMessageFromReply = isLastMessageFromReply,
217             isSuppressedFromStatusBar = isSuppressedFromStatusBar,
218             isPulsing = isPulsing,
219             aodIcon = aodIcon,
220             shelfIcon = shelfIcon,
221             statusBarIcon = statusBarIcon,
222             statusBarChipIconView = statusBarChipIconView,
223             uid = uid,
224             instanceId = instanceId,
225             isGroupSummary = isGroupSummary,
226             packageName = packageName,
227             appName = appName,
228             contentIntent = contentIntent,
229             bucket = bucket,
230             callType = callType,
231             promotedContent = promotedContent,
232         )
233     }
234         ?: ActiveNotificationModel(
235             key = key,
236             groupKey = groupKey,
237             whenTime = whenTime,
238             isForegroundService = isForegroundService,
239             isOngoingEvent = isOngoingEvent,
240             isAmbient = isAmbient,
241             isRowDismissed = isRowDismissed,
242             isSilent = isSilent,
243             isLastMessageFromReply = isLastMessageFromReply,
244             isSuppressedFromStatusBar = isSuppressedFromStatusBar,
245             isPulsing = isPulsing,
246             aodIcon = aodIcon,
247             shelfIcon = shelfIcon,
248             statusBarIcon = statusBarIcon,
249             statusBarChipIconView = statusBarChipIconView,
250             uid = uid,
251             instanceId = instanceId,
252             isGroupSummary = isGroupSummary,
253             packageName = packageName,
254             appName = appName,
255             contentIntent = contentIntent,
256             bucket = bucket,
257             callType = callType,
258             promotedContent = promotedContent,
259         )
260 }
261 
ActiveNotificationModelnull262 private fun ActiveNotificationModel.isCurrent(
263     key: String,
264     groupKey: String?,
265     whenTime: Long,
266     isForegroundService: Boolean,
267     isOngoingEvent: Boolean,
268     isAmbient: Boolean,
269     isRowDismissed: Boolean,
270     isSilent: Boolean,
271     isLastMessageFromReply: Boolean,
272     isSuppressedFromStatusBar: Boolean,
273     isPulsing: Boolean,
274     aodIcon: Icon?,
275     shelfIcon: Icon?,
276     statusBarIcon: Icon?,
277     statusBarChipIconView: StatusBarIconView?,
278     uid: Int,
279     packageName: String,
280     appName: String,
281     contentIntent: PendingIntent?,
282     instanceId: InstanceId?,
283     isGroupSummary: Boolean,
284     bucket: Int,
285     callType: CallType,
286     promotedContent: PromotedNotificationContentModels?,
287 ): Boolean {
288     return when {
289         key != this.key -> false
290         groupKey != this.groupKey -> false
291         whenTime != this.whenTime -> false
292         isForegroundService != this.isForegroundService -> false
293         isOngoingEvent != this.isOngoingEvent -> false
294         isAmbient != this.isAmbient -> false
295         isRowDismissed != this.isRowDismissed -> false
296         isSilent != this.isSilent -> false
297         isLastMessageFromReply != this.isLastMessageFromReply -> false
298         isSuppressedFromStatusBar != this.isSuppressedFromStatusBar -> false
299         isPulsing != this.isPulsing -> false
300         aodIcon != this.aodIcon -> false
301         shelfIcon != this.shelfIcon -> false
302         statusBarIcon != this.statusBarIcon -> false
303         statusBarChipIconView != this.statusBarChipIconView -> false
304         uid != this.uid -> false
305         instanceId != this.instanceId -> false
306         isGroupSummary != this.isGroupSummary -> false
307         packageName != this.packageName -> false
308         appName != this.appName -> false
309         contentIntent != this.contentIntent -> false
310         bucket != this.bucket -> false
311         callType != this.callType -> false
312         // QQQ: Do we need to do the same `isCurrent` thing within the content model to avoid
313         // recreating the active notification model constantly?
314         promotedContent != this.promotedContent -> false
315         else -> true
316     }
317 }
318 
createOrReusenull319 private fun ActiveNotificationsStore.createOrReuse(
320     key: String,
321     summary: ActiveNotificationModel,
322     children: List<ActiveNotificationModel>,
323 ): ActiveNotificationGroupModel {
324     return groups[key]?.takeIf { it.isCurrent(key, summary, children) }
325         ?: ActiveNotificationGroupModel(key, summary, children)
326 }
327 
ActiveNotificationGroupModelnull328 private fun ActiveNotificationGroupModel.isCurrent(
329     key: String,
330     summary: ActiveNotificationModel,
331     children: List<ActiveNotificationModel>,
332 ): Boolean {
333     return when {
334         key != this.key -> false
335         summary != this.summary -> false
336         children != this.children -> false
337         else -> true
338     }
339 }
340 
toCallTypenull341 private fun StatusBarNotification.toCallType(): CallType =
342     when (this.notification.extras.getInt(EXTRA_CALL_TYPE, -1)) {
343         -1 -> CallType.None
344         CALL_TYPE_INCOMING -> CallType.Incoming
345         CALL_TYPE_ONGOING -> CallType.Ongoing
346         CALL_TYPE_SCREENING -> CallType.Screening
347         CALL_TYPE_UNKNOWN -> CallType.Unknown
348         else -> CallType.Unknown
349     }
350