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