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