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