1 /*
<lambda>null2  * Copyright (C) 2021 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.render
18 
19 import com.android.systemui.statusbar.notification.NotificationSectionsFeatureManager
20 import com.android.systemui.statusbar.notification.collection.GroupEntry
21 import com.android.systemui.statusbar.notification.collection.ListEntry
22 import com.android.systemui.statusbar.notification.collection.NotificationEntry
23 import com.android.systemui.statusbar.notification.collection.listbuilder.NotifSection
24 import com.android.systemui.statusbar.notification.collection.provider.SectionHeaderVisibilityProvider
25 import com.android.systemui.util.Compile
26 import com.android.app.tracing.traceSection
27 
28 /**
29  * Converts a notif list (the output of the ShadeListBuilder) into a NodeSpec, an abstract
30  * representation of which views should be present in the shade. This spec will later be consumed
31  * by the ViewDiffer, which will add and remove views until the shade matches the spec. Up until
32  * this point, the pipeline has dealt with pure data representations of notifications (in the
33  * form of NotificationEntries). In this step, NotificationEntries finally become associated with
34  * the views that will represent them. In addition, we add in any non-notification views that also
35  * need to present in the shade, notably the section headers.
36  */
37 class NodeSpecBuilder(
38     private val mediaContainerController: MediaContainerController,
39     private val sectionsFeatureManager: NotificationSectionsFeatureManager,
40     private val sectionHeaderVisibilityProvider: SectionHeaderVisibilityProvider,
41     private val viewBarn: NotifViewBarn,
42     private val logger: NodeSpecBuilderLogger
43 ) {
44     private var lastSections = setOf<NotifSection?>()
45 
46     fun buildNodeSpec(
47         rootController: NodeController,
48         notifList: List<ListEntry>
49     ): NodeSpec = traceSection("NodeSpecBuilder.buildNodeSpec") {
50         val root = NodeSpecImpl(null, rootController)
51 
52         // The media container should be added as the first child of the root node
53         // TODO: Perhaps the node spec building process should be more of a pipeline of its own?
54         if (sectionsFeatureManager.isMediaControlsEnabled()) {
55             root.children.add(NodeSpecImpl(root, mediaContainerController))
56         }
57 
58         var currentSection: NotifSection? = null
59         val prevSections = mutableSetOf<NotifSection?>()
60         val showHeaders = sectionHeaderVisibilityProvider.sectionHeadersVisible
61         val sectionOrder = mutableListOf<NotifSection?>()
62         val sectionHeaders = mutableMapOf<NotifSection?, NodeController?>()
63         val sectionCounts = mutableMapOf<NotifSection?, Int>()
64 
65         for (entry in notifList) {
66             val section = entry.section!!
67             if (prevSections.contains(section)) {
68                 throw java.lang.RuntimeException("Section ${section.label} has been duplicated")
69             }
70 
71             // If this notif begins a new section, first add the section's header view
72             if (section != currentSection) {
73                 if (section.headerController != currentSection?.headerController && showHeaders) {
74                     section.headerController?.let { headerController ->
75                         root.children.add(NodeSpecImpl(root, headerController))
76                         if (Compile.IS_DEBUG) {
77                             sectionHeaders[section] = headerController
78                         }
79                     }
80                 }
81                 prevSections.add(currentSection)
82                 currentSection = section
83                 if (Compile.IS_DEBUG) {
84                     sectionOrder.add(section)
85                 }
86             }
87 
88             // Finally, add the actual notif node!
89             root.children.add(buildNotifNode(root, entry))
90             if (Compile.IS_DEBUG) {
91                 sectionCounts[section] = sectionCounts.getOrDefault(section, 0) + 1
92             }
93         }
94 
95         if (Compile.IS_DEBUG) {
96             logger.logBuildNodeSpec(lastSections, sectionHeaders, sectionCounts, sectionOrder)
97             lastSections = sectionCounts.keys
98         }
99 
100         return@traceSection root
101     }
102 
103     private fun buildNotifNode(parent: NodeSpec, entry: ListEntry): NodeSpec = when (entry) {
104         is NotificationEntry -> NodeSpecImpl(parent, viewBarn.requireNodeController(entry))
105         is GroupEntry ->
106             NodeSpecImpl(parent, viewBarn.requireNodeController(checkNotNull(entry.summary)))
107                 .apply { entry.children.forEach { children.add(buildNotifNode(this, it)) } }
108         else -> throw RuntimeException("Unexpected entry: $entry")
109     }
110 }
111