blob: 9ac42298e539a8daa20af070d04968bdab6f29ed [file] [log] [blame]
Evan Laird181de622019-10-24 09:53:02 -04001/*
2 * 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
17package com.android.systemui.statusbar.notification.collection
18
Evan Laird181de622019-10-24 09:53:02 -040019import android.app.NotificationManager.IMPORTANCE_HIGH
Evan Laird181de622019-10-24 09:53:02 -040020import android.app.NotificationManager.IMPORTANCE_MIN
Evan Laird181de622019-10-24 09:53:02 -040021import android.service.notification.NotificationListenerService.Ranking
22import android.service.notification.NotificationListenerService.RankingMap
23import android.service.notification.StatusBarNotification
Evan Laird181de622019-10-24 09:53:02 -040024import com.android.systemui.statusbar.NotificationMediaManager
Ned Burnsafe77bc2020-01-30 20:45:07 -050025import com.android.systemui.statusbar.notification.NotificationEntryManagerLogger
Evan Laird181de622019-10-24 09:53:02 -040026import com.android.systemui.statusbar.notification.NotificationFilter
27import com.android.systemui.statusbar.notification.NotificationSectionsFeatureManager
Beverlye558f1d2020-01-07 16:28:58 -050028import com.android.systemui.statusbar.notification.collection.provider.HighPriorityProvider
Steve Elliott960f4ce2019-12-04 15:13:52 -050029import com.android.systemui.statusbar.notification.people.PeopleNotificationIdentifier
Steve Elliott307d3852020-03-05 11:00:22 -050030import com.android.systemui.statusbar.notification.people.PeopleNotificationIdentifier.Companion.TYPE_NON_PERSON
Evan Laird181de622019-10-24 09:53:02 -040031import com.android.systemui.statusbar.notification.stack.NotificationSectionsManager.BUCKET_ALERTING
Steve Elliott49671e02020-05-12 13:51:28 -040032import com.android.systemui.statusbar.notification.stack.NotificationSectionsManager.BUCKET_FOREGROUND_SERVICE
Steve Elliottb0940382020-02-20 14:24:02 -050033import com.android.systemui.statusbar.notification.stack.NotificationSectionsManager.BUCKET_HEADS_UP
Evan Laird181de622019-10-24 09:53:02 -040034import com.android.systemui.statusbar.notification.stack.NotificationSectionsManager.BUCKET_PEOPLE
35import com.android.systemui.statusbar.notification.stack.NotificationSectionsManager.BUCKET_SILENT
Steve Elliott19b2c862020-05-05 14:15:14 -040036import com.android.systemui.statusbar.notification.stack.NotificationSectionsManager.PriorityBucket
Evan Laird181de622019-10-24 09:53:02 -040037import com.android.systemui.statusbar.phone.NotificationGroupManager
38import com.android.systemui.statusbar.policy.HeadsUpManager
Evan Laird181de622019-10-24 09:53:02 -040039import dagger.Lazy
Steve Elliott19b2c862020-05-05 14:15:14 -040040import java.util.Objects
Steve Elliott960f4ce2019-12-04 15:13:52 -050041import javax.inject.Inject
Evan Laird181de622019-10-24 09:53:02 -040042
43private const val TAG = "NotifRankingManager"
44
45/**
46 * NotificationRankingManager is responsible for holding on to the most recent [RankingMap], and
47 * updating SystemUI's set of [NotificationEntry]s with their own ranking. It also sorts and filters
48 * a set of entries (but retains none of them). We also set buckets on the entries here since
49 * bucketing is tied closely to sorting.
50 *
51 * For the curious: this class is one iteration closer to null of what used to be called
52 * NotificationData.java.
53 */
54open class NotificationRankingManager @Inject constructor(
55 private val mediaManagerLazy: Lazy<NotificationMediaManager>,
56 private val groupManager: NotificationGroupManager,
57 private val headsUpManager: HeadsUpManager,
58 private val notifFilter: NotificationFilter,
Ned Burnsafe77bc2020-01-30 20:45:07 -050059 private val logger: NotificationEntryManagerLogger,
Steve Elliott5c42f4a2020-03-20 11:19:48 -040060 private val sectionsFeatureManager: NotificationSectionsFeatureManager,
Beverlye558f1d2020-01-07 16:28:58 -050061 private val peopleNotificationIdentifier: PeopleNotificationIdentifier,
62 private val highPriorityProvider: HighPriorityProvider
Evan Laird181de622019-10-24 09:53:02 -040063) {
64
65 var rankingMap: RankingMap? = null
66 protected set
67 private val mediaManager by lazy {
68 mediaManagerLazy.get()
69 }
Steve Elliott5c42f4a2020-03-20 11:19:48 -040070 private val usePeopleFiltering: Boolean
71 get() = sectionsFeatureManager.isFilteringEnabled()
Evan Laird181de622019-10-24 09:53:02 -040072 private val rankingComparator: Comparator<NotificationEntry> = Comparator { a, b ->
73 val na = a.sbn
74 val nb = b.sbn
75 val aRank = a.ranking.rank
76 val bRank = b.ranking.rank
77
Steve Elliott307d3852020-03-05 11:00:22 -050078 val aPersonType = a.getPeopleNotificationType()
79 val bPersonType = b.getPeopleNotificationType()
Steve Elliott960f4ce2019-12-04 15:13:52 -050080
Evan Laird181de622019-10-24 09:53:02 -040081 val aMedia = isImportantMedia(a)
82 val bMedia = isImportantMedia(b)
83
84 val aSystemMax = a.isSystemMax()
85 val bSystemMax = b.isSystemMax()
86
87 val aHeadsUp = a.isRowHeadsUp
88 val bHeadsUp = b.isRowHeadsUp
89
Beverlye558f1d2020-01-07 16:28:58 -050090 val aIsHighPriority = a.isHighPriority()
91 val bIsHighPriority = b.isHighPriority()
Steve Elliott960f4ce2019-12-04 15:13:52 -050092 when {
Steve Elliott960f4ce2019-12-04 15:13:52 -050093 aHeadsUp != bHeadsUp -> if (aHeadsUp) -1 else 1
Evan Laird181de622019-10-24 09:53:02 -040094 // Provide consistent ranking with headsUpManager
Steve Elliott960f4ce2019-12-04 15:13:52 -050095 aHeadsUp -> headsUpManager.compare(a, b)
Julia Reynolds8cdfdb62020-04-24 12:03:33 -040096
97 usePeopleFiltering && aPersonType != bPersonType ->
98 peopleNotificationIdentifier.compareTo(aPersonType, bPersonType)
Evan Laird181de622019-10-24 09:53:02 -040099 // Upsort current media notification.
Steve Elliott960f4ce2019-12-04 15:13:52 -0500100 aMedia != bMedia -> if (aMedia) -1 else 1
Evan Laird181de622019-10-24 09:53:02 -0400101 // Upsort PRIORITY_MAX system notifications
Steve Elliott960f4ce2019-12-04 15:13:52 -0500102 aSystemMax != bSystemMax -> if (aSystemMax) -1 else 1
Beverlye558f1d2020-01-07 16:28:58 -0500103 aIsHighPriority != bIsHighPriority ->
104 -1 * aIsHighPriority.compareTo(bIsHighPriority)
Steve Elliott960f4ce2019-12-04 15:13:52 -0500105 aRank != bRank -> aRank - bRank
106 else -> nb.notification.`when`.compareTo(na.notification.`when`)
Evan Laird181de622019-10-24 09:53:02 -0400107 }
108 }
109
110 private fun isImportantMedia(entry: NotificationEntry): Boolean {
111 val importance = entry.ranking.importance
112 return entry.key == mediaManager.mediaNotificationKey && importance > IMPORTANCE_MIN
113 }
114
Evan Laird181de622019-10-24 09:53:02 -0400115 fun updateRanking(
116 newRankingMap: RankingMap?,
117 entries: Collection<NotificationEntry>,
118 reason: String
119 ): List<NotificationEntry> {
Evan Laird181de622019-10-24 09:53:02 -0400120 // TODO: may not be ideal to guard on null here, but this code is implementing exactly what
121 // NotificationData used to do
122 if (newRankingMap != null) {
123 rankingMap = newRankingMap
Steve Elliott5c42f4a2020-03-20 11:19:48 -0400124 updateRankingForEntries(entries)
Evan Laird181de622019-10-24 09:53:02 -0400125 }
Steve Elliott5c42f4a2020-03-20 11:19:48 -0400126 return synchronized(this) {
127 filterAndSortLocked(entries, reason)
Evan Laird181de622019-10-24 09:53:02 -0400128 }
Evan Laird181de622019-10-24 09:53:02 -0400129 }
130
131 /** Uses the [rankingComparator] to sort notifications which aren't filtered */
132 private fun filterAndSortLocked(
Steve Elliott5c42f4a2020-03-20 11:19:48 -0400133 entries: Collection<NotificationEntry>,
Evan Laird181de622019-10-24 09:53:02 -0400134 reason: String
Steve Elliott5c42f4a2020-03-20 11:19:48 -0400135 ): List<NotificationEntry> {
Ned Burnsafe77bc2020-01-30 20:45:07 -0500136 logger.logFilterAndSort(reason)
Steve Elliott5c42f4a2020-03-20 11:19:48 -0400137 val filtered = entries.asSequence()
138 .filterNot(notifFilter::shouldFilterOut)
Evan Laird181de622019-10-24 09:53:02 -0400139 .sortedWith(rankingComparator)
Steve Elliott5c42f4a2020-03-20 11:19:48 -0400140 .toList()
Steve Elliott19b2c862020-05-05 14:15:14 -0400141 assignBuckets(filtered)
Steve Elliott5c42f4a2020-03-20 11:19:48 -0400142 return filtered
Evan Laird181de622019-10-24 09:53:02 -0400143 }
144
Steve Elliott19b2c862020-05-05 14:15:14 -0400145 private fun assignBuckets(entries: List<NotificationEntry>) {
146 entries.forEach { it.bucket = getBucketForEntry(it) }
147 if (!usePeopleFiltering) {
148 // If we don't have a Conversation section, just assign buckets normally based on the
149 // content.
150 return
151 }
152 // If HUNs are not continuous with the top section, break out into a new Incoming section.
153 entries.asReversed().asSequence().zipWithNext().forEach { (next, entry) ->
154 if (entry.isRowHeadsUp && entry.bucket > next.bucket) {
155 entry.bucket = BUCKET_HEADS_UP
156 }
157 }
158 }
159
160 @PriorityBucket
161 private fun getBucketForEntry(entry: NotificationEntry): Int {
Evan Laird181de622019-10-24 09:53:02 -0400162 val isHeadsUp = entry.isRowHeadsUp
163 val isMedia = isImportantMedia(entry)
164 val isSystemMax = entry.isSystemMax()
Steve Elliott19b2c862020-05-05 14:15:14 -0400165 return when {
Steve Elliott49671e02020-05-12 13:51:28 -0400166 entry.sbn.notification.isForegroundService && entry.sbn.notification.isColorized ->
167 BUCKET_FOREGROUND_SERVICE
Steve Elliott19b2c862020-05-05 14:15:14 -0400168 usePeopleFiltering && entry.getPeopleNotificationType() != TYPE_NON_PERSON ->
169 BUCKET_PEOPLE
170 isHeadsUp || isMedia || isSystemMax || entry.isHighPriority() ->
171 BUCKET_ALERTING
172 else -> BUCKET_SILENT
Evan Laird181de622019-10-24 09:53:02 -0400173 }
174 }
175
Steve Elliott5c42f4a2020-03-20 11:19:48 -0400176 private fun updateRankingForEntries(entries: Iterable<NotificationEntry>) {
Evan Laird181de622019-10-24 09:53:02 -0400177 rankingMap?.let { rankingMap ->
178 synchronized(entries) {
Steve Elliott5c42f4a2020-03-20 11:19:48 -0400179 for (entry in entries) {
Evan Laird181de622019-10-24 09:53:02 -0400180 val newRanking = Ranking()
181 if (!rankingMap.getRanking(entry.key, newRanking)) {
Steve Elliott5c42f4a2020-03-20 11:19:48 -0400182 continue
Evan Laird181de622019-10-24 09:53:02 -0400183 }
184 entry.ranking = newRanking
185
Evan Laird181de622019-10-24 09:53:02 -0400186 val newOverrideGroupKey = newRanking.overrideGroupKey
Beverlyc5094122020-01-09 17:05:15 -0500187 if (!Objects.equals(entry.sbn.overrideGroupKey, newOverrideGroupKey)) {
188 val oldGroupKey = entry.sbn.groupKey
189 val oldIsGroup = entry.sbn.isGroup
190 val oldIsGroupSummary = entry.sbn.notification.isGroupSummary
Evan Laird181de622019-10-24 09:53:02 -0400191 entry.sbn.overrideGroupKey = newOverrideGroupKey
Beverlyc5094122020-01-09 17:05:15 -0500192 groupManager.onEntryUpdated(entry, oldGroupKey, oldIsGroup,
193 oldIsGroupSummary)
Evan Laird181de622019-10-24 09:53:02 -0400194 }
Evan Laird181de622019-10-24 09:53:02 -0400195 }
196 }
197 }
198 }
Steve Elliott960f4ce2019-12-04 15:13:52 -0500199
Steve Elliott307d3852020-03-05 11:00:22 -0500200 private fun NotificationEntry.getPeopleNotificationType() =
201 peopleNotificationIdentifier.getPeopleNotificationType(sbn, ranking)
Julia Reynoldsdc07d042020-02-18 13:30:03 -0500202
Beverlye558f1d2020-01-07 16:28:58 -0500203 private fun NotificationEntry.isHighPriority() =
204 highPriorityProvider.isHighPriority(this)
Evan Laird181de622019-10-24 09:53:02 -0400205}
206
207// Convenience functions
208private fun NotificationEntry.isSystemMax(): Boolean {
209 return importance >= IMPORTANCE_HIGH && sbn.isSystemNotification()
210}
211
212private fun StatusBarNotification.isSystemNotification(): Boolean {
213 return "android" == packageName || "com.android.systemui" == packageName
214}