blob: 0437877d8330e51cdd8ef4788d5a215e3e8e2e7b [file] [log] [blame]
/*
* Copyright (C) 2019 The Android Open Source Project
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package com.android.systemui.statusbar.notification.collection
import android.annotation.MainThread
import android.view.ViewGroup
import com.android.systemui.statusbar.FeatureFlags
import com.android.systemui.statusbar.notification.VisualStabilityManager
import com.android.systemui.statusbar.notification.collection.GroupEntry.ROOT_ENTRY
import com.android.systemui.statusbar.notification.stack.NotificationListItem
import com.android.systemui.util.Assert
import java.io.FileDescriptor
import java.io.PrintWriter
import java.lang.IllegalStateException
import javax.inject.Inject
import javax.inject.Singleton
/**
* A consumer of a Notification tree built by [ShadeListBuilder] which will update the notification
* presenter with the minimum operations required to make the old tree match the new one
*/
@MainThread
@Singleton
class NotifViewManager @Inject constructor(
private val rowRegistry: NotifViewBarn,
private val stabilityManager: VisualStabilityManager,
private val featureFlags: FeatureFlags
) {
var currentNotifs = listOf<ListEntry>()
private lateinit var listContainer: SimpleNotificationListContainer
fun attach(listBuilder: ShadeListBuilder) {
if (featureFlags.isNewNotifPipelineRenderingEnabled) {
listBuilder.setOnRenderListListener { entries: List<ListEntry> ->
this.onNotifTreeBuilt(entries)
}
}
}
fun setViewConsumer(consumer: SimpleNotificationListContainer) {
listContainer = consumer
}
/**
* Callback for when the tree is rebuilt
*/
fun onNotifTreeBuilt(notifList: List<ListEntry>) {
Assert.isMainThread()
/*
* The assumption here is that anything from the old NotificationViewHierarchyManager that
* is responsible for filtering is done via the NotifFilter logic. This tree we get should
* be *the stuff to display* +/- redacted stuff
*/
detachRows(notifList)
attachRows(notifList)
currentNotifs = notifList
}
private fun detachRows(entries: List<ListEntry>) {
// To properly detach rows, we are looking to remove any view in the consumer that is not
// present in the incoming list.
//
// Every listItem was top-level, so it's entry's parent was ROOT_ENTRY, but now
// there are two possibilities:
//
// 1. It is not present in the entry list
// 1a. It has moved to be a child in the entry list - transfer it
// 1b. It is gone completely - remove it
// 2. It is present in the entry list - diff the children
getListItems(listContainer)
.filter {
// Ignore things that are showing the blocking helper
!it.isBlockingHelperShowing
}
.forEach { listItem ->
val noLongerTopLevel = listItem.entry.parent != ROOT_ENTRY
val becameChild = noLongerTopLevel && listItem.entry.parent != null
val idx = entries.indexOf(listItem.entry)
if (noLongerTopLevel) {
// Summaries won't become children; remove the whole group
if (listItem.isSummaryWithChildren) {
listItem.removeAllChildren()
}
if (becameChild) {
// Top-level element is becoming a child, don't generate an animation
listContainer.setChildTransferInProgress(true)
}
listContainer.removeListItem(listItem)
listContainer.setChildTransferInProgress(false)
} else if (entries[idx] is GroupEntry) {
// A top-level entry exists. If it's a group, diff the children
val groupChildren = (entries[idx] as GroupEntry).children
listItem.notificationChildren?.forEach { listChild ->
if (!groupChildren.contains(listChild.entry)) {
listItem.removeChildNotification(listChild)
// TODO: the old code only calls this if the notif is gone from
// NEM.getActiveNotificationUnfiltered(). Do we care?
listContainer.notifyGroupChildRemoved(
listChild.view, listChild.view.parent as ViewGroup)
}
}
}
}
}
/** Convenience method for getting a sequence of [NotificationListItem]s */
private fun getListItems(container: SimpleNotificationListContainer):
Sequence<NotificationListItem> {
return (0 until container.getContainerChildCount()).asSequence()
.map { container.getContainerChildAt(it) }
.filterIsInstance<NotificationListItem>()
}
private fun attachRows(entries: List<ListEntry>) {
var orderChanged = false
// To attach rows we can use _this one weird trick_: if the intended view to add does not
// have a parent, then simply add it (and its children).
entries.forEach { entry ->
val listItem = rowRegistry.requireView(entry)
if (listItem.view.parent != null) {
listContainer.addListItem(listItem)
stabilityManager.notifyViewAddition(listItem.view)
}
if (entry is GroupEntry) {
for ((idx, childEntry) in entry.children.withIndex()) {
val childListItem = rowRegistry.requireView(childEntry)
// Child hasn't been added yet. add it!
if (!listItem.notificationChildren.contains(childListItem)) {
// TODO: old code here just Log.wtf()'d here. This might wreak havoc
if (childListItem.view.parent != null) {
throw IllegalStateException("trying to add a notification child that " +
"already has a parent. class: " +
"${childListItem.view.parent?.javaClass} " +
"\n child: ${childListItem.view}"
)
}
listItem.addChildNotification(childListItem, idx)
stabilityManager.notifyViewAddition(childListItem.view)
listContainer.notifyGroupChildAdded(childListItem.view)
}
}
// finally after removing and adding has been performed we can apply the order
orderChanged = orderChanged ||
listItem.applyChildOrder(
getChildListFromParent(entry),
stabilityManager,
null /*TODO: stability callback */
)
}
}
if (orderChanged) {
listContainer.generateChildOrderChangedEvent()
}
}
private fun getChildListFromParent(parent: ListEntry): List<NotificationListItem> {
if (parent is GroupEntry) {
return parent.children.map { child -> rowRegistry.requireView(child) }
.toList()
}
return emptyList()
}
fun dump(fd: FileDescriptor, pw: PrintWriter, args: Array<String>) {
}
}
private const val TAG = "NotifViewDataSource"