• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
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.render
18 
19 import android.annotation.MainThread
20 import android.view.View
21 import com.android.systemui.util.kotlin.transform
22 
23 /**
24  * Given a "spec" that describes a "tree" of views, adds and removes views from the
25  * [rootController] and its children until the actual tree matches the spec.
26  *
27  * Every node in the spec tree must specify both a view and its associated [NodeController].
28  * Commands to add/remove/reorder children are sent to the controller. How the controller
29  * interprets these commands is left to its own discretion -- it might add them directly to its
30  * associated view or to some subview container.
31  *
32  * It's possible for nodes to mix "unmanaged" views in alongside managed ones within the same
33  * container. In this case, whenever the differ runs it will move all unmanaged views to the end
34  * of the node's child list.
35  */
36 @MainThread
37 class ShadeViewDiffer(
38     rootController: NodeController,
39     private val logger: ShadeViewDifferLogger
40 ) {
41     private val rootNode = ShadeNode(rootController)
42     private val nodes = mutableMapOf(rootController to rootNode)
43     private val views = mutableMapOf<View, ShadeNode>()
44 
45     /**
46      * Adds and removes views from the root (and its children) until their structure matches the
47      * provided [spec]. The root node of the spec must match the root controller passed to the
48      * differ's constructor.
49      */
50     fun applySpec(spec: NodeSpec) {
51         val specMap = treeToMap(spec)
52 
53         if (spec.controller != rootNode.controller) {
54             throw IllegalArgumentException("Tree root ${spec.controller.nodeLabel} does not " +
55                     "match own root at ${rootNode.label}")
56         }
57 
58         detachChildren(rootNode, specMap)
59         attachChildren(rootNode, specMap)
60     }
61 
62     /**
63      * If [view] is managed by this differ, then returns the label of the view's controller.
64      * Otherwise returns View.toString().
65      *
66      * For debugging purposes.
67      */
68     fun getViewLabel(view: View): String = views[view]?.label ?: view.toString()
69 
70     private fun detachChildren(
71         parentNode: ShadeNode,
72         specMap: Map<NodeController, NodeSpec>
73     ) {
74         val parentSpec = specMap[parentNode.controller]
75 
76         for (i in parentNode.getChildCount() - 1 downTo 0) {
77             val childView = parentNode.getChildAt(i)
78             views[childView]?.let { childNode ->
79                 val childSpec = specMap[childNode.controller]
80 
81                 maybeDetachChild(parentNode, parentSpec, childNode, childSpec)
82 
83                 if (childNode.controller.getChildCount() > 0) {
84                     detachChildren(childNode, specMap)
85                 }
86             }
87         }
88     }
89 
90     private fun maybeDetachChild(
91         parentNode: ShadeNode,
92         parentSpec: NodeSpec?,
93         childNode: ShadeNode,
94         childSpec: NodeSpec?
95     ) {
96         val newParentNode = transform(childSpec?.parent) { getNode(it) }
97 
98         if (newParentNode != parentNode) {
99             val childCompletelyRemoved = newParentNode == null
100 
101             if (childCompletelyRemoved) {
102                 nodes.remove(childNode.controller)
103                 views.remove(childNode.controller.view)
104             }
105 
106             if (childCompletelyRemoved && parentSpec == null) {
107                 // If both the child and the parent are being removed at the same time, then
108                 // keep the child attached to the parent for animation purposes
109                 logger.logSkippingDetach(childNode.label, parentNode.label)
110             } else {
111                 logger.logDetachingChild(
112                         childNode.label,
113                         !childCompletelyRemoved,
114                         parentNode.label,
115                         newParentNode?.label)
116                 parentNode.removeChild(childNode, !childCompletelyRemoved)
117                 childNode.parent = null
118             }
119         }
120     }
121 
122     private fun attachChildren(
123         parentNode: ShadeNode,
124         specMap: Map<NodeController, NodeSpec>
125     ) {
126         val parentSpec = checkNotNull(specMap[parentNode.controller])
127 
128         for ((index, childSpec) in parentSpec.children.withIndex()) {
129             val currView = parentNode.getChildAt(index)
130             val childNode = getNode(childSpec)
131 
132             if (childNode.view != currView) {
133 
134                 when (childNode.parent) {
135                     null -> {
136                         // A new child (either newly created or coming from some other parent)
137                         logger.logAttachingChild(childNode.label, parentNode.label)
138                         parentNode.addChildAt(childNode, index)
139                         childNode.parent = parentNode
140                     }
141                     parentNode -> {
142                         // A pre-existing child, just in the wrong position. Move it into place
143                         logger.logMovingChild(childNode.label, parentNode.label, index)
144                         parentNode.moveChildTo(childNode, index)
145                     }
146                     else -> {
147                         // Error: child still has a parent. We should have detached it in the
148                         // previous step.
149                         throw IllegalStateException("Child ${childNode.label} should have " +
150                                 "parent ${parentNode.label} but is actually " +
151                                 "${childNode.parent?.label}")
152                     }
153                 }
154             }
155 
156             if (childSpec.children.isNotEmpty()) {
157                 attachChildren(childNode, specMap)
158             }
159         }
160     }
161 
162     private fun getNode(spec: NodeSpec): ShadeNode {
163         var node = nodes[spec.controller]
164         if (node == null) {
165             node = ShadeNode(spec.controller)
166             nodes[node.controller] = node
167             views[node.view] = node
168         }
169         return node
170     }
171 
172     private fun treeToMap(tree: NodeSpec): Map<NodeController, NodeSpec> {
173         val map = mutableMapOf<NodeController, NodeSpec>()
174 
175         try {
176             registerNodes(tree, map)
177         } catch (ex: DuplicateNodeException) {
178             logger.logDuplicateNodeInTree(tree, ex)
179             throw ex
180         }
181 
182         return map
183     }
184 
185     private fun registerNodes(node: NodeSpec, map: MutableMap<NodeController, NodeSpec>) {
186         if (map.containsKey(node.controller)) {
187             throw DuplicateNodeException("Node ${node.controller.nodeLabel} appears more than once")
188         }
189         map[node.controller] = node
190 
191         if (node.children.isNotEmpty()) {
192             for (child in node.children) {
193                 registerNodes(child, map)
194             }
195         }
196     }
197 }
198 
199 private class DuplicateNodeException(message: String) : RuntimeException(message)
200 
201 private class ShadeNode(
202     val controller: NodeController
203 ) {
204     val view = controller.view
205 
206     var parent: ShadeNode? = null
207 
208     val label: String
209         get() = controller.nodeLabel
210 
getChildAtnull211     fun getChildAt(index: Int): View? = controller.getChildAt(index)
212 
213     fun getChildCount(): Int = controller.getChildCount()
214 
215     fun addChildAt(child: ShadeNode, index: Int) {
216         controller.addChildAt(child.controller, index)
217     }
218 
moveChildTonull219     fun moveChildTo(child: ShadeNode, index: Int) {
220         controller.moveChildTo(child.controller, index)
221     }
222 
removeChildnull223     fun removeChild(child: ShadeNode, isTransfer: Boolean) {
224         controller.removeChild(child.controller, isTransfer)
225     }
226 }
227