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