1 /*
<lambda>null2  * Copyright (C) 2020 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.coordinator
18 
19 import com.android.systemui.statusbar.notification.collection.ListEntry
20 import com.android.systemui.statusbar.notification.collection.NotifPipeline
21 import com.android.systemui.statusbar.notification.collection.NotificationEntry
22 import com.android.systemui.statusbar.notification.collection.SortBySectionTimeFlag
23 import com.android.systemui.statusbar.notification.collection.coordinator.dagger.CoordinatorScope
24 import com.android.systemui.statusbar.notification.collection.listbuilder.OnBeforeRenderListListener
25 import com.android.systemui.statusbar.notification.collection.listbuilder.pluggable.NotifComparator
26 import com.android.systemui.statusbar.notification.collection.listbuilder.pluggable.NotifPromoter
27 import com.android.systemui.statusbar.notification.collection.listbuilder.pluggable.NotifSectioner
28 import com.android.systemui.statusbar.notification.collection.provider.HighPriorityProvider
29 import com.android.systemui.statusbar.notification.collection.render.NodeController
30 import com.android.systemui.statusbar.notification.dagger.PeopleHeader
31 import com.android.systemui.statusbar.notification.icon.ConversationIconManager
32 import com.android.systemui.statusbar.notification.people.PeopleNotificationIdentifier
33 import com.android.systemui.statusbar.notification.people.PeopleNotificationIdentifier.Companion.PeopleNotificationType
34 import com.android.systemui.statusbar.notification.people.PeopleNotificationIdentifier.Companion.TYPE_IMPORTANT_PERSON
35 import com.android.systemui.statusbar.notification.people.PeopleNotificationIdentifier.Companion.TYPE_NON_PERSON
36 import com.android.systemui.statusbar.notification.stack.BUCKET_PEOPLE
37 import com.android.systemui.statusbar.notification.stack.BUCKET_PRIORITY_PEOPLE
38 import javax.inject.Inject
39 
40 /**
41  * A Conversation/People Coordinator that:
42  * - Elevates important conversation notifications
43  * - Puts conversations into its own people section. @see [NotifCoordinators] for section ordering.
44  */
45 @CoordinatorScope
46 class ConversationCoordinator @Inject constructor(
47         private val peopleNotificationIdentifier: PeopleNotificationIdentifier,
48         private val conversationIconManager: ConversationIconManager,
49         private val highPriorityProvider: HighPriorityProvider,
50         @PeopleHeader private val peopleHeaderController: NodeController,
51 ) : Coordinator {
52 
53     private val promotedEntriesToSummaryOfSameChannel =
54             mutableMapOf<NotificationEntry, NotificationEntry>()
55 
56     private val onBeforeRenderListListener = OnBeforeRenderListListener { _ ->
57         val unimportantSummaries = promotedEntriesToSummaryOfSameChannel
58                 .mapNotNull { (promoted, summary) ->
59                     val originalGroup = summary.parent
60                     when {
61                         originalGroup == null -> null
62                         originalGroup == promoted.parent -> null
63                         originalGroup.parent == null -> null
64                         originalGroup.summary != summary -> null
65                         originalGroup.children.any { it.channel == summary.channel } -> null
66                         else -> summary.key
67                     }
68                 }
69         conversationIconManager.setUnimportantConversations(unimportantSummaries)
70         promotedEntriesToSummaryOfSameChannel.clear()
71     }
72 
73     private val notificationPromoter = object : NotifPromoter(TAG) {
74         override fun shouldPromoteToTopLevel(entry: NotificationEntry): Boolean {
75             val shouldPromote = entry.channel?.isImportantConversation == true
76             if (shouldPromote) {
77                 val summary = entry.parent?.summary
78                 if (summary != null && entry.channel == summary.channel) {
79                     promotedEntriesToSummaryOfSameChannel[entry] = summary
80                 }
81             }
82             return shouldPromote
83         }
84     }
85 
86     val priorityPeopleSectioner =
87             object : NotifSectioner("Priority People", BUCKET_PRIORITY_PEOPLE) {
88                 override fun isInSection(entry: ListEntry): Boolean {
89                     return getPeopleType(entry) == TYPE_IMPORTANT_PERSON
90                 }
91             }
92 
93     // TODO(b/330193582): Rename to just "People"
94     val peopleAlertingSectioner = object : NotifSectioner("People(alerting)", BUCKET_PEOPLE) {
95         override fun isInSection(entry: ListEntry): Boolean  {
96             if (SortBySectionTimeFlag.isEnabled) {
97                 return highPriorityProvider.isHighPriorityConversation(entry)
98                         || isConversation(entry)
99             } else {
100                 return highPriorityProvider.isHighPriorityConversation(entry)
101             }
102         }
103 
104         override fun getComparator(): NotifComparator? {
105             return if (SortBySectionTimeFlag.isEnabled) null else notifComparator
106         }
107 
108         override fun getHeaderNodeController(): NodeController? = conversationHeaderNodeController
109     }
110 
111     val peopleSilentSectioner = object : NotifSectioner("People(silent)", BUCKET_PEOPLE) {
112         // Because the peopleAlertingSectioner is above this one, it will claim all conversations that are alerting.
113         // All remaining conversations must be silent.
114         override fun isInSection(entry: ListEntry): Boolean {
115             SortBySectionTimeFlag.assertInLegacyMode()
116             return isConversation(entry)
117         }
118 
119         override fun getComparator(): NotifComparator {
120             SortBySectionTimeFlag.assertInLegacyMode()
121             return notifComparator
122         }
123 
124         override fun getHeaderNodeController(): NodeController? {
125             SortBySectionTimeFlag.assertInLegacyMode()
126             return conversationHeaderNodeController
127         }
128     }
129 
130     override fun attach(pipeline: NotifPipeline) {
131         pipeline.addPromoter(notificationPromoter)
132         pipeline.addOnBeforeRenderListListener(onBeforeRenderListListener)
133     }
134 
135     private fun isConversation(entry: ListEntry): Boolean =
136             getPeopleType(entry) != TYPE_NON_PERSON
137 
138     @PeopleNotificationType
139     private fun getPeopleType(entry: ListEntry): Int =
140             entry.representativeEntry?.let {
141                 peopleNotificationIdentifier.getPeopleNotificationType(it)
142             } ?: TYPE_NON_PERSON
143 
144     private val notifComparator: NotifComparator = object : NotifComparator("People") {
145         override fun compare(entry1: ListEntry, entry2: ListEntry): Int {
146             val type1 = getPeopleType(entry1)
147             val type2 = getPeopleType(entry2)
148             return type2.compareTo(type1)
149         }
150     }
151 
152     // TODO: remove SHOW_ALL_SECTIONS, this redundant method, and peopleHeaderController
153     private val conversationHeaderNodeController: NodeController? =
154             if (RankingCoordinator.SHOW_ALL_SECTIONS) peopleHeaderController else null
155 
156     private companion object {
157         private const val TAG = "ConversationCoordinator"
158     }
159 }
160