blob: cf670bd5a424ad4c8f2a40bd229f02e9673318db [file] [log] [blame]
Evan Laird9a9a6192019-12-19 10:44:21 -05001/*
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
19import android.annotation.MainThread
20import android.view.ViewGroup
21
22import com.android.systemui.statusbar.FeatureFlags
23import com.android.systemui.statusbar.notification.VisualStabilityManager
24import com.android.systemui.statusbar.notification.collection.GroupEntry.ROOT_ENTRY
25import com.android.systemui.statusbar.notification.stack.NotificationListItem
26import com.android.systemui.util.Assert
27
28import java.io.FileDescriptor
29import java.io.PrintWriter
30import java.lang.IllegalStateException
31import javax.inject.Inject
32import javax.inject.Singleton
33
34/**
35 * A consumer of a Notification tree built by [ShadeListBuilder] which will update the notification
36 * presenter with the minimum operations required to make the old tree match the new one
37 */
38@MainThread
39@Singleton
40class NotifViewManager @Inject constructor(
41 private val rowRegistry: NotifViewBarn,
42 private val stabilityManager: VisualStabilityManager,
43 private val featureFlags: FeatureFlags
44) {
45 var currentNotifs = listOf<ListEntry>()
46
47 private lateinit var listContainer: SimpleNotificationListContainer
48
49 fun attach(listBuilder: ShadeListBuilder) {
50 if (featureFlags.isNewNotifPipelineRenderingEnabled) {
51 listBuilder.setOnRenderListListener { entries: List<ListEntry> ->
52 this.onNotifTreeBuilt(entries)
53 }
54 }
55 }
56
57 fun setViewConsumer(consumer: SimpleNotificationListContainer) {
58 listContainer = consumer
59 }
60
61 /**
62 * Callback for when the tree is rebuilt
63 */
64 fun onNotifTreeBuilt(notifList: List<ListEntry>) {
65 Assert.isMainThread()
66
67 /*
68 * The assumption here is that anything from the old NotificationViewHierarchyManager that
69 * is responsible for filtering is done via the NotifFilter logic. This tree we get should
70 * be *the stuff to display* +/- redacted stuff
71 */
72
73 detachRows(notifList)
74 attachRows(notifList)
75
76 currentNotifs = notifList
77 }
78
79 private fun detachRows(entries: List<ListEntry>) {
80 // To properly detach rows, we are looking to remove any view in the consumer that is not
81 // present in the incoming list.
82 //
83 // Every listItem was top-level, so it's entry's parent was ROOT_ENTRY, but now
84 // there are two possibilities:
85 //
86 // 1. It is not present in the entry list
87 // 1a. It has moved to be a child in the entry list - transfer it
88 // 1b. It is gone completely - remove it
89 // 2. It is present in the entry list - diff the children
90 getListItems(listContainer)
91 .filter {
92 // Ignore things that are showing the blocking helper
93 !it.isBlockingHelperShowing
94 }
95 .forEach { listItem ->
96 val noLongerTopLevel = listItem.entry.parent != ROOT_ENTRY
97 val becameChild = noLongerTopLevel && listItem.entry.parent != null
98
99 val idx = entries.indexOf(listItem.entry)
100
101 if (noLongerTopLevel) {
102 // Summaries won't become children; remove the whole group
103 if (listItem.isSummaryWithChildren) {
104 listItem.removeAllChildren()
105 }
106
107 if (becameChild) {
108 // Top-level element is becoming a child, don't generate an animation
109 listContainer.setChildTransferInProgress(true)
110 }
111 listContainer.removeListItem(listItem)
112 listContainer.setChildTransferInProgress(false)
113 } else if (entries[idx] is GroupEntry) {
114 // A top-level entry exists. If it's a group, diff the children
115 val groupChildren = (entries[idx] as GroupEntry).children
116 listItem.notificationChildren?.forEach { listChild ->
117 if (!groupChildren.contains(listChild.entry)) {
118 listItem.removeChildNotification(listChild)
119
120 // TODO: the old code only calls this if the notif is gone from
121 // NEM.getActiveNotificationUnfiltered(). Do we care?
122 listContainer.notifyGroupChildRemoved(
123 listChild.view, listChild.view.parent as ViewGroup)
124 }
125 }
126 }
127 }
128 }
129
130 /** Convenience method for getting a sequence of [NotificationListItem]s */
131 private fun getListItems(container: SimpleNotificationListContainer):
132 Sequence<NotificationListItem> {
133 return (0 until container.getContainerChildCount()).asSequence()
134 .map { container.getContainerChildAt(it) }
135 .filterIsInstance<NotificationListItem>()
136 }
137
138 private fun attachRows(entries: List<ListEntry>) {
139
140 var orderChanged = false
141
142 // To attach rows we can use _this one weird trick_: if the intended view to add does not
143 // have a parent, then simply add it (and its children).
144 entries.forEach { entry ->
Kevin Han4943b862020-03-23 17:14:39 -0700145 // TODO: We should eventually map GroupEntry's themselves to views so that we don't
146 // depend on representativeEntry here which may actually be null in the future
147 val listItem = rowRegistry.requireView(entry.representativeEntry!!)
Evan Laird9a9a6192019-12-19 10:44:21 -0500148
Kevin Han4943b862020-03-23 17:14:39 -0700149 if (listItem.view.parent == null) {
Evan Laird9a9a6192019-12-19 10:44:21 -0500150 listContainer.addListItem(listItem)
151 stabilityManager.notifyViewAddition(listItem.view)
152 }
153
154 if (entry is GroupEntry) {
155 for ((idx, childEntry) in entry.children.withIndex()) {
156 val childListItem = rowRegistry.requireView(childEntry)
157 // Child hasn't been added yet. add it!
Kevin Han4943b862020-03-23 17:14:39 -0700158 if (listItem.notificationChildren == null ||
159 !listItem.notificationChildren.contains(childListItem)) {
Evan Laird9a9a6192019-12-19 10:44:21 -0500160 // TODO: old code here just Log.wtf()'d here. This might wreak havoc
161 if (childListItem.view.parent != null) {
162 throw IllegalStateException("trying to add a notification child that " +
163 "already has a parent. class: " +
164 "${childListItem.view.parent?.javaClass} " +
165 "\n child: ${childListItem.view}"
166 )
167 }
168
169 listItem.addChildNotification(childListItem, idx)
170 stabilityManager.notifyViewAddition(childListItem.view)
171 listContainer.notifyGroupChildAdded(childListItem.view)
172 }
173 }
174
175 // finally after removing and adding has been performed we can apply the order
176 orderChanged = orderChanged ||
177 listItem.applyChildOrder(
178 getChildListFromParent(entry),
179 stabilityManager,
180 null /*TODO: stability callback */
181 )
182 }
183 }
184
185 if (orderChanged) {
186 listContainer.generateChildOrderChangedEvent()
187 }
188 }
189
190 private fun getChildListFromParent(parent: ListEntry): List<NotificationListItem> {
191 if (parent is GroupEntry) {
192 return parent.children.map { child -> rowRegistry.requireView(child) }
193 .toList()
194 }
195
196 return emptyList()
197 }
198
199 fun dump(fd: FileDescriptor, pw: PrintWriter, args: Array<String>) {
200 }
201}
202
203private const val TAG = "NotifViewDataSource"