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