1 /* 2 * Copyright (C) 2023 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 android.tools.common.traces.surfaceflinger 18 19 import android.tools.common.datatypes.ActiveBuffer 20 import android.tools.common.datatypes.Color 21 import android.tools.common.datatypes.Rect 22 import android.tools.common.datatypes.RectF 23 import android.tools.common.datatypes.Region 24 import android.tools.common.traces.component.ComponentName 25 import kotlin.js.JsExport 26 import kotlin.js.JsName 27 28 /** 29 * Represents a single layer with links to its parent and child layers. 30 * 31 * This is a generic object that is reused by both Flicker and Winscope and cannot access internal 32 * Java/Android functionality 33 */ 34 @JsExport 35 class Layer 36 private constructor( 37 val name: String, 38 val id: Int, 39 val parentId: Int, 40 val z: Int, 41 val currFrame: Long, 42 properties: ILayerProperties, 43 ) : ILayerProperties by properties { 44 val stableId: String = "$type $id $name" 45 var parent: Layer? = null 46 var zOrderRelativeOf: Layer? = null 47 var zOrderRelativeParentOf: Int = 0 48 val packageName = ComponentName.fromLayerName(name).packageName 49 50 /** 51 * Checks if the [Layer] is a root layer in the hierarchy 52 * 53 * @return 54 */ 55 val isRootLayer: Boolean 56 get() = parent == null 57 58 private val _children = mutableListOf<Layer>() 59 private val _occludedBy = mutableListOf<Layer>() 60 private val _partiallyOccludedBy = mutableListOf<Layer>() 61 private val _coveredBy = mutableListOf<Layer>() 62 val children: Array<Layer> 63 get() = _children.toTypedArray() 64 val occludedBy: Array<Layer> 65 get() = _occludedBy.toTypedArray() 66 val partiallyOccludedBy: Array<Layer> 67 get() = _partiallyOccludedBy.toTypedArray() 68 val coveredBy: Array<Layer> 69 get() = _coveredBy.toTypedArray() 70 var isMissing: Boolean = false 71 internal set 72 73 /** 74 * Checks if the layer is hidden, that is, if its flags contain Flag.HIDDEN 75 * 76 * @return 77 */ 78 val isHiddenByPolicy: Boolean 79 get() { 80 return (flags and Flag.HIDDEN.value) != 0x0 || 81 // offscreen layer root has a unique layer id 82 id == 0x7FFFFFFD 83 } 84 85 /** 86 * Checks if the layer is visible. 87 * 88 * A layer is visible if: 89 * - it has an active buffer or has effects 90 * - is not hidden 91 * - is not transparent 92 * - not occluded by other layers 93 * 94 * @return 95 */ 96 val isVisible: Boolean 97 get() { 98 val visibleRegion = 99 if (excludesCompositionState) { 100 // Doesn't include state sent during composition like visible region and 101 // composition type, so we fallback on the bounds as the visible region 102 Region.from(this.bounds) 103 } else { 104 this.visibleRegion ?: Region.EMPTY 105 } 106 return when { 107 isHiddenByParent -> false 108 isHiddenByPolicy -> false 109 isActiveBufferEmpty && !hasEffects -> false 110 occludedBy.isNotEmpty() -> false 111 else -> visibleRegion.isNotEmpty 112 } 113 } 114 115 /** 116 * Checks if the [Layer] is hidden by its parent 117 * 118 * @return 119 */ 120 val isHiddenByParent: Boolean 121 get() = 122 !isRootLayer && (parent?.isHiddenByPolicy == true || parent?.isHiddenByParent == true) 123 124 /** 125 * Gets a description of why the layer is (in)visible 126 * 127 * @return 128 */ 129 val visibilityReason: Array<String> 130 get() { 131 if (isVisible) { 132 return emptyArray() 133 } 134 val reasons = mutableListOf<String>() 135 if (isHiddenByPolicy) reasons.add("Flag is hidden") 136 if (isHiddenByParent) reasons.add("Hidden by parent ${parent?.name}") 137 if (isActiveBufferEmpty) reasons.add("Buffer is empty") 138 if (color.a == 0.0f) reasons.add("Alpha is 0") 139 if (bounds.isEmpty) reasons.add("Bounds is 0x0") 140 if (bounds.isEmpty && crop.isEmpty) reasons.add("Crop is 0x0") 141 if (!transform.isValid) reasons.add("Transform is invalid") 142 if (isRelativeOf && zOrderRelativeOf == null) { 143 reasons.add("RelativeOf layer has been removed") 144 } 145 if (isActiveBufferEmpty && !fillsColor && !drawsShadows && !hasBlur) { 146 reasons.add("does not have color fill, shadow or blur") 147 } 148 if (_occludedBy.isNotEmpty()) { <lambda>null149 val occludedByLayers = _occludedBy.joinToString(", ") { "${it.name} (${it.id})" } 150 reasons.add("Layer is occluded by: $occludedByLayers") 151 } 152 if (visibleRegion?.isEmpty == true) { 153 reasons.add("Visible region calculated by Composition Engine is empty") 154 } 155 if (reasons.isEmpty()) reasons.add("Unknown") 156 return reasons.toTypedArray() 157 } 158 159 val zOrderPath: Array<Int> 160 get() { 161 val zOrderRelativeOf = zOrderRelativeOf 162 val zOrderPath = 163 when { 164 zOrderRelativeOf != null -> zOrderRelativeOf.zOrderPath.toMutableList() 165 parent != null -> parent?.zOrderPath?.toMutableList() ?: mutableListOf() 166 else -> mutableListOf() 167 } 168 zOrderPath.add(z) 169 return zOrderPath.toTypedArray() 170 } 171 172 val isTask: Boolean 173 get() = name.startsWith("Task=") 174 175 /** 176 * Returns true iff the [innerLayer] screen bounds are inside or equal to this layer's 177 * [screenBounds] and neither layers are rotating. 178 */ containsnull179 fun contains(innerLayer: Layer, crop: RectF = RectF.EMPTY): Boolean { 180 return if (!this.transform.isSimpleRotation || !innerLayer.transform.isSimpleRotation) { 181 false 182 } else { 183 val thisBounds: RectF 184 val innerLayerBounds: RectF 185 if (crop.isNotEmpty) { 186 thisBounds = this.screenBounds.crop(crop) 187 innerLayerBounds = innerLayer.screenBounds.crop(crop) 188 } else { 189 thisBounds = this.screenBounds 190 innerLayerBounds = innerLayer.screenBounds 191 } 192 thisBounds.contains(innerLayerBounds) 193 } 194 } 195 addChildnull196 fun addChild(childLayer: Layer) { 197 _children.add(childLayer) 198 } 199 addOccludedBynull200 fun addOccludedBy(layers: Array<Layer>) { 201 _occludedBy.addAll(layers) 202 } 203 addPartiallyOccludedBynull204 fun addPartiallyOccludedBy(layers: Array<Layer>) { 205 _partiallyOccludedBy.addAll(layers) 206 } 207 addCoveredBynull208 fun addCoveredBy(layers: Array<Layer>) { 209 _coveredBy.addAll(layers) 210 } 211 overlapsnull212 fun overlaps(other: Layer, crop: RectF = RectF.EMPTY): Boolean { 213 val thisBounds: RectF 214 val otherBounds: RectF 215 if (crop.isNotEmpty) { 216 thisBounds = this.screenBounds.crop(crop) 217 otherBounds = other.screenBounds.crop(crop) 218 } else { 219 thisBounds = this.screenBounds 220 otherBounds = other.screenBounds 221 } 222 return !thisBounds.intersection(otherBounds).isEmpty 223 } 224 toStringnull225 override fun toString(): String { 226 return buildString { 227 append(name) 228 229 if (!activeBuffer.isEmpty) { 230 append(" buffer:$activeBuffer") 231 append(" frame#$currFrame") 232 } 233 234 if (isVisible) { 235 append(" visible:$visibleRegion") 236 } 237 } 238 } 239 equalsnull240 override fun equals(other: Any?): Boolean { 241 if (this === other) return true 242 if (other !is Layer) return false 243 244 if (name != other.name) return false 245 if (id != other.id) return false 246 if (parentId != other.parentId) return false 247 if (z != other.z) return false 248 if (currFrame != other.currFrame) return false 249 if (stableId != other.stableId) return false 250 if (zOrderRelativeOf != other.zOrderRelativeOf) return false 251 if (zOrderRelativeParentOf != other.zOrderRelativeParentOf) return false 252 if (_occludedBy != other._occludedBy) return false 253 if (_partiallyOccludedBy != other._partiallyOccludedBy) return false 254 if (_coveredBy != other._coveredBy) return false 255 if (isMissing != other.isMissing) return false 256 if (visibleRegion != other.visibleRegion) return false 257 if (activeBuffer != other.activeBuffer) return false 258 if (flags != other.flags) return false 259 if (bounds != other.bounds) return false 260 if (color != other.color) return false 261 if (shadowRadius != other.shadowRadius) return false 262 if (cornerRadius != other.cornerRadius) return false 263 if (type != other.type) return false 264 if (transform != other.transform) return false 265 if (sourceBounds != other.sourceBounds) return false 266 if (effectiveScalingMode != other.effectiveScalingMode) return false 267 if (bufferTransform != other.bufferTransform) return false 268 if (hwcCompositionType != other.hwcCompositionType) return false 269 if (hwcCrop != other.hwcCrop) return false 270 if (hwcFrame != other.hwcFrame) return false 271 if (backgroundBlurRadius != other.backgroundBlurRadius) return false 272 if (crop != other.crop) return false 273 if (isRelativeOf != other.isRelativeOf) return false 274 if (zOrderRelativeOfId != other.zOrderRelativeOfId) return false 275 if (stackId != other.stackId) return false 276 if (requestedTransform != other.requestedTransform) return false 277 if (requestedColor != other.requestedColor) return false 278 if (cornerRadiusCrop != other.cornerRadiusCrop) return false 279 if (inputTransform != other.inputTransform) return false 280 if (inputRegion != other.inputRegion) return false 281 if (screenBounds != other.screenBounds) return false 282 if (isOpaque != other.isOpaque) return false 283 if (excludesCompositionState != other.excludesCompositionState) return false 284 285 return true 286 } 287 hashCodenull288 override fun hashCode(): Int { 289 var result = visibleRegion?.hashCode() ?: 0 290 result = 31 * result + activeBuffer.hashCode() 291 result = 31 * result + flags 292 result = 31 * result + bounds.hashCode() 293 result = 31 * result + color.hashCode() 294 result = 31 * result + shadowRadius.hashCode() 295 result = 31 * result + cornerRadius.hashCode() 296 result = 31 * result + type.hashCode() 297 result = 31 * result + transform.hashCode() 298 result = 31 * result + sourceBounds.hashCode() 299 result = 31 * result + effectiveScalingMode 300 result = 31 * result + bufferTransform.hashCode() 301 result = 31 * result + hwcCompositionType.hashCode() 302 result = 31 * result + hwcCrop.hashCode() 303 result = 31 * result + hwcFrame.hashCode() 304 result = 31 * result + backgroundBlurRadius 305 result = 31 * result + crop.hashCode() 306 result = 31 * result + isRelativeOf.hashCode() 307 result = 31 * result + zOrderRelativeOfId 308 result = 31 * result + stackId 309 result = 31 * result + requestedTransform.hashCode() 310 result = 31 * result + requestedColor.hashCode() 311 result = 31 * result + cornerRadiusCrop.hashCode() 312 result = 31 * result + inputTransform.hashCode() 313 result = 31 * result + (inputRegion?.hashCode() ?: 0) 314 result = 31 * result + screenBounds.hashCode() 315 result = 31 * result + isOpaque.hashCode() 316 result = 31 * result + name.hashCode() 317 result = 31 * result + id 318 result = 31 * result + parentId 319 result = 31 * result + z 320 result = 31 * result + currFrame.hashCode() 321 result = 31 * result + stableId.hashCode() 322 result = 31 * result + (zOrderRelativeOf?.hashCode() ?: 0) 323 result = 31 * result + zOrderRelativeParentOf 324 result = 31 * result + _children.hashCode() 325 result = 31 * result + _occludedBy.hashCode() 326 result = 31 * result + _partiallyOccludedBy.hashCode() 327 result = 31 * result + _coveredBy.hashCode() 328 result = 31 * result + isMissing.hashCode() 329 result = 31 * result + excludesCompositionState.hashCode() 330 return result 331 } 332 333 companion object { 334 @JsName("from") fromnull335 fun from( 336 name: String, 337 id: Int, 338 parentId: Int, 339 z: Int, 340 visibleRegion: Region, 341 activeBuffer: ActiveBuffer, 342 flags: Int, 343 bounds: RectF, 344 color: Color, 345 isOpaque: Boolean, 346 shadowRadius: Float, 347 cornerRadius: Float, 348 type: String, 349 screenBounds: RectF, 350 transform: Transform, 351 sourceBounds: RectF, 352 currFrame: Long, 353 effectiveScalingMode: Int, 354 bufferTransform: Transform, 355 hwcCompositionType: HwcCompositionType, 356 hwcCrop: RectF, 357 hwcFrame: Rect, 358 backgroundBlurRadius: Int, 359 crop: Rect?, 360 isRelativeOf: Boolean, 361 zOrderRelativeOfId: Int, 362 stackId: Int, 363 requestedTransform: Transform, 364 requestedColor: Color, 365 cornerRadiusCrop: RectF, 366 inputTransform: Transform, 367 inputRegion: Region?, 368 excludesCompositionState: Boolean 369 ): Layer { 370 val properties = 371 LayerProperties.from( 372 visibleRegion, 373 activeBuffer, 374 flags, 375 bounds, 376 color, 377 isOpaque, 378 shadowRadius, 379 cornerRadius, 380 type, 381 screenBounds, 382 transform, 383 sourceBounds, 384 effectiveScalingMode, 385 bufferTransform, 386 hwcCompositionType, 387 hwcCrop, 388 hwcFrame, 389 backgroundBlurRadius, 390 crop, 391 isRelativeOf, 392 zOrderRelativeOfId, 393 stackId, 394 requestedTransform, 395 requestedColor, 396 cornerRadiusCrop, 397 inputTransform, 398 inputRegion, 399 excludesCompositionState 400 ) 401 return Layer(name, id, parentId, z, currFrame, properties) 402 } 403 } 404 } 405