1 /*
<lambda>null2 * Copyright (C) 2019 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
17 package com.android.systemui.statusbar.notification.collection
18
19 import android.app.NotificationManager.IMPORTANCE_HIGH
20 import android.app.NotificationManager.IMPORTANCE_MIN
21 import android.service.notification.NotificationListenerService.Ranking
22 import android.service.notification.NotificationListenerService.RankingMap
23 import android.service.notification.StatusBarNotification
24 import com.android.systemui.statusbar.NotificationMediaManager
25 import com.android.systemui.statusbar.notification.NotificationEntryManagerLogger
26 import com.android.systemui.statusbar.notification.NotificationFilter
27 import com.android.systemui.statusbar.notification.NotificationSectionsFeatureManager
28 import com.android.systemui.statusbar.notification.collection.provider.HighPriorityProvider
29 import com.android.systemui.statusbar.notification.people.PeopleNotificationIdentifier
30 import com.android.systemui.statusbar.notification.people.PeopleNotificationIdentifier.Companion.TYPE_NON_PERSON
31 import com.android.systemui.statusbar.notification.stack.BUCKET_ALERTING
32 import com.android.systemui.statusbar.notification.stack.BUCKET_FOREGROUND_SERVICE
33 import com.android.systemui.statusbar.notification.stack.BUCKET_PEOPLE
34 import com.android.systemui.statusbar.notification.stack.BUCKET_SILENT
35 import com.android.systemui.statusbar.notification.stack.PriorityBucket
36 import com.android.systemui.statusbar.phone.NotificationGroupManager
37 import com.android.systemui.statusbar.policy.HeadsUpManager
38 import dagger.Lazy
39 import java.util.Objects
40 import javax.inject.Inject
41
42 private const val TAG = "NotifRankingManager"
43
44 /**
45 * NotificationRankingManager is responsible for holding on to the most recent [RankingMap], and
46 * updating SystemUI's set of [NotificationEntry]s with their own ranking. It also sorts and filters
47 * a set of entries (but retains none of them). We also set buckets on the entries here since
48 * bucketing is tied closely to sorting.
49 *
50 * For the curious: this class is one iteration closer to null of what used to be called
51 * NotificationData.java.
52 */
53 open class NotificationRankingManager @Inject constructor(
54 private val mediaManagerLazy: Lazy<NotificationMediaManager>,
55 private val groupManager: NotificationGroupManager,
56 private val headsUpManager: HeadsUpManager,
57 private val notifFilter: NotificationFilter,
58 private val logger: NotificationEntryManagerLogger,
59 private val sectionsFeatureManager: NotificationSectionsFeatureManager,
60 private val peopleNotificationIdentifier: PeopleNotificationIdentifier,
61 private val highPriorityProvider: HighPriorityProvider
62 ) {
63
64 var rankingMap: RankingMap? = null
65 protected set
66 private val mediaManager by lazy {
67 mediaManagerLazy.get()
68 }
69 private val usePeopleFiltering: Boolean
70 get() = sectionsFeatureManager.isFilteringEnabled()
71 private val rankingComparator: Comparator<NotificationEntry> = Comparator { a, b ->
72 val na = a.sbn
73 val nb = b.sbn
74 val aRank = a.ranking.rank
75 val bRank = b.ranking.rank
76
77 val aIsFsn = a.isColorizedForegroundService()
78 val bIsFsn = b.isColorizedForegroundService()
79
80 val aPersonType = a.getPeopleNotificationType()
81 val bPersonType = b.getPeopleNotificationType()
82
83 val aMedia = a.isImportantMedia()
84 val bMedia = b.isImportantMedia()
85
86 val aSystemMax = a.isSystemMax()
87 val bSystemMax = b.isSystemMax()
88
89 val aHeadsUp = a.isRowHeadsUp
90 val bHeadsUp = b.isRowHeadsUp
91
92 val aIsHighPriority = a.isHighPriority()
93 val bIsHighPriority = b.isHighPriority()
94 when {
95 aHeadsUp != bHeadsUp -> if (aHeadsUp) -1 else 1
96 // Provide consistent ranking with headsUpManager
97 aHeadsUp -> headsUpManager.compare(a, b)
98 aIsFsn != bIsFsn -> if (aIsFsn) -1 else 1
99 usePeopleFiltering && aPersonType != bPersonType ->
100 peopleNotificationIdentifier.compareTo(aPersonType, bPersonType)
101 // Upsort current media notification.
102 aMedia != bMedia -> if (aMedia) -1 else 1
103 // Upsort PRIORITY_MAX system notifications
104 aSystemMax != bSystemMax -> if (aSystemMax) -1 else 1
105 aIsHighPriority != bIsHighPriority ->
106 -1 * aIsHighPriority.compareTo(bIsHighPriority)
107 aRank != bRank -> aRank - bRank
108 else -> nb.notification.`when`.compareTo(na.notification.`when`)
109 }
110 }
111
112 fun updateRanking(
113 newRankingMap: RankingMap?,
114 entries: Collection<NotificationEntry>,
115 reason: String
116 ): List<NotificationEntry> {
117 // TODO: may not be ideal to guard on null here, but this code is implementing exactly what
118 // NotificationData used to do
119 if (newRankingMap != null) {
120 rankingMap = newRankingMap
121 updateRankingForEntries(entries)
122 }
123 return synchronized(this) {
124 filterAndSortLocked(entries, reason)
125 }
126 }
127
128 /** Uses the [rankingComparator] to sort notifications which aren't filtered */
129 private fun filterAndSortLocked(
130 entries: Collection<NotificationEntry>,
131 reason: String
132 ): List<NotificationEntry> {
133 logger.logFilterAndSort(reason)
134 val filtered = entries.asSequence()
135 .filterNot(this::filter)
136 .sortedWith(rankingComparator)
137 .toList()
138 entries.forEach { it.bucket = getBucketForEntry(it) }
139 return filtered
140 }
141
142 private fun filter(entry: NotificationEntry): Boolean {
143 val filtered = notifFilter.shouldFilterOut(entry)
144 if (filtered) {
145 // notification is removed from the list, so we reset its initialization time
146 entry.resetInitializationTime()
147 }
148 return filtered
149 }
150
151 @PriorityBucket
152 private fun getBucketForEntry(entry: NotificationEntry): Int {
153 val isHeadsUp = entry.isRowHeadsUp
154 val isMedia = entry.isImportantMedia()
155 val isSystemMax = entry.isSystemMax()
156 return when {
157 entry.isColorizedForegroundService() -> BUCKET_FOREGROUND_SERVICE
158 usePeopleFiltering && entry.isConversation() -> BUCKET_PEOPLE
159 isHeadsUp || isMedia || isSystemMax || entry.isHighPriority() -> BUCKET_ALERTING
160 else -> BUCKET_SILENT
161 }
162 }
163
164 private fun updateRankingForEntries(entries: Iterable<NotificationEntry>) {
165 rankingMap?.let { rankingMap ->
166 synchronized(entries) {
167 for (entry in entries) {
168 val newRanking = Ranking()
169 if (!rankingMap.getRanking(entry.key, newRanking)) {
170 continue
171 }
172 entry.ranking = newRanking
173
174 val newOverrideGroupKey = newRanking.overrideGroupKey
175 if (!Objects.equals(entry.sbn.overrideGroupKey, newOverrideGroupKey)) {
176 val oldGroupKey = entry.sbn.groupKey
177 val oldIsGroup = entry.sbn.isGroup
178 val oldIsGroupSummary = entry.sbn.notification.isGroupSummary
179 entry.sbn.overrideGroupKey = newOverrideGroupKey
180 groupManager.onEntryUpdated(entry, oldGroupKey, oldIsGroup,
181 oldIsGroupSummary)
182 }
183 }
184 }
185 }
186 }
187
188 private fun NotificationEntry.isImportantMedia() =
189 key == mediaManager.mediaNotificationKey && ranking.importance > IMPORTANCE_MIN
190
191 private fun NotificationEntry.isConversation() = getPeopleNotificationType() != TYPE_NON_PERSON
192
193 private fun NotificationEntry.getPeopleNotificationType() =
194 peopleNotificationIdentifier.getPeopleNotificationType(sbn, ranking)
195
196 private fun NotificationEntry.isHighPriority() =
197 highPriorityProvider.isHighPriority(this)
198 }
199
200 // Convenience functions
isSystemMaxnull201 private fun NotificationEntry.isSystemMax() =
202 importance >= IMPORTANCE_HIGH && sbn.isSystemNotification()
203
204 private fun StatusBarNotification.isSystemNotification() =
205 "android" == packageName || "com.android.systemui" == packageName
206
207 private fun NotificationEntry.isColorizedForegroundService() = sbn.notification.run {
208 isForegroundService && isColorized && importance > IMPORTANCE_MIN
209 }
210