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