| /* |
| * Copyright (C) 2020 The Android Open Source Project |
| * |
| * Licensed under the Apache License, Version 2.0 (the "License"); |
| * you may not use this file except in compliance with the License. |
| * You may obtain a copy of the License at |
| * |
| * http://www.apache.org/licenses/LICENSE-2.0 |
| * |
| * Unless required by applicable law or agreed to in writing, software |
| * distributed under the License is distributed on an "AS IS" BASIS, |
| * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. |
| * See the License for the specific language governing permissions and |
| * limitations under the License. |
| */ |
| |
| package com.android.server.wm.traces.common.layers |
| |
| import com.android.server.wm.traces.common.ActiveBuffer |
| import com.android.server.wm.traces.common.Color |
| import com.android.server.wm.traces.common.Rect |
| import com.android.server.wm.traces.common.RectF |
| import com.android.server.wm.traces.common.region.Region |
| import kotlin.js.JsName |
| |
| /** |
| * Represents a single layer with links to its parent and child layers. |
| * |
| * This is a generic object that is reused by both Flicker and Winscope and cannot access internal |
| * Java/Android functionality |
| */ |
| class Layer |
| private constructor( |
| val name: String, |
| val id: Int, |
| val parentId: Int, |
| val z: Int, |
| val currFrame: Long, |
| properties: ILayerProperties, |
| ) : ILayerProperties by properties { |
| val stableId: String = "$type $id $name" |
| var parent: Layer? = null |
| var zOrderRelativeOf: Layer? = null |
| var zOrderRelativeParentOf: Int = 0 |
| |
| /** |
| * Checks if the [Layer] is a root layer in the hierarchy |
| * |
| * @return |
| */ |
| val isRootLayer: Boolean |
| get() = parent == null |
| |
| private val _children = mutableListOf<Layer>() |
| private val _occludedBy = mutableListOf<Layer>() |
| private val _partiallyOccludedBy = mutableListOf<Layer>() |
| private val _coveredBy = mutableListOf<Layer>() |
| val children: Array<Layer> |
| get() = _children.toTypedArray() |
| val occludedBy: Array<Layer> |
| get() = _occludedBy.toTypedArray() |
| val partiallyOccludedBy: Array<Layer> |
| get() = _partiallyOccludedBy.toTypedArray() |
| val coveredBy: Array<Layer> |
| get() = _coveredBy.toTypedArray() |
| var isMissing: Boolean = false |
| internal set |
| |
| /** |
| * Checks if the layer is hidden, that is, if its flags contain Flag.HIDDEN |
| * |
| * @return |
| */ |
| val isHiddenByPolicy: Boolean |
| get() { |
| return (flags and ILayerProperties.Flag.HIDDEN.value) != 0x0 || |
| // offscreen layer root has a unique layer id |
| id == 0x7FFFFFFD |
| } |
| |
| /** |
| * Checks if the layer is visible. |
| * |
| * A layer is visible if: |
| * - it has an active buffer or has effects |
| * - is not hidden |
| * - is not transparent |
| * - not occluded by other layers |
| * |
| * @return |
| */ |
| val isVisible: Boolean |
| get() { |
| val visibleRegion = |
| if (excludesCompositionState) { |
| // Doesn't include state sent during composition like visible region and |
| // composition type, so we fallback on the bounds as the visible region |
| Region.from(this.bounds) |
| } else { |
| this.visibleRegion ?: Region.EMPTY |
| } |
| return when { |
| isHiddenByParent -> false |
| isHiddenByPolicy -> false |
| isActiveBufferEmpty && !hasEffects -> false |
| occludedBy.isNotEmpty() -> false |
| else -> visibleRegion.isNotEmpty |
| } |
| } |
| |
| /** |
| * Checks if the [Layer] is hidden by its parent |
| * |
| * @return |
| */ |
| val isHiddenByParent: Boolean |
| get() = |
| !isRootLayer && (parent?.isHiddenByPolicy == true || parent?.isHiddenByParent == true) |
| |
| /** |
| * Gets a description of why the layer is (in)visible |
| * |
| * @return |
| */ |
| val visibilityReason: Array<String> |
| get() { |
| if (isVisible) { |
| return emptyArray() |
| } |
| val reasons = mutableListOf<String>() |
| if (isHiddenByPolicy) reasons.add("Flag is hidden") |
| if (isHiddenByParent) reasons.add("Hidden by parent ${parent?.name}") |
| if (isActiveBufferEmpty) reasons.add("Buffer is empty") |
| if (color.a == 0.0f) reasons.add("Alpha is 0") |
| if (bounds.isEmpty) reasons.add("Bounds is 0x0") |
| if (bounds.isEmpty && crop.isEmpty) reasons.add("Crop is 0x0") |
| if (!transform.isValid) reasons.add("Transform is invalid") |
| if (isRelativeOf && zOrderRelativeOf == null) { |
| reasons.add("RelativeOf layer has been removed") |
| } |
| if (isActiveBufferEmpty && !fillsColor && !drawsShadows && !hasBlur) { |
| reasons.add("does not have color fill, shadow or blur") |
| } |
| if (_occludedBy.isNotEmpty()) { |
| val occludedByLayers = _occludedBy.joinToString(", ") { "${it.name} (${it.id})" } |
| reasons.add("Layer is occluded by: $occludedByLayers") |
| } |
| if (visibleRegion?.isEmpty == true) { |
| reasons.add("Visible region calculated by Composition Engine is empty") |
| } |
| if (reasons.isEmpty()) reasons.add("Unknown") |
| return reasons.toTypedArray() |
| } |
| |
| val zOrderPath: Array<Int> |
| get() { |
| val zOrderRelativeOf = zOrderRelativeOf |
| val zOrderPath = |
| when { |
| zOrderRelativeOf != null -> zOrderRelativeOf.zOrderPath.toMutableList() |
| parent != null -> parent?.zOrderPath?.toMutableList() ?: mutableListOf() |
| else -> mutableListOf() |
| } |
| zOrderPath.add(z) |
| return zOrderPath.toTypedArray() |
| } |
| |
| /** |
| * Returns true iff the [innerLayer] screen bounds are inside or equal to this layer's |
| * [screenBounds] and neither layers are rotating. |
| */ |
| fun contains(innerLayer: Layer, crop: RectF = RectF.EMPTY): Boolean { |
| return if (!this.transform.isSimpleRotation || !innerLayer.transform.isSimpleRotation) { |
| false |
| } else { |
| val thisBounds: RectF |
| val innerLayerBounds: RectF |
| if (crop.isNotEmpty) { |
| thisBounds = this.screenBounds.crop(crop) |
| innerLayerBounds = innerLayer.screenBounds.crop(crop) |
| } else { |
| thisBounds = this.screenBounds |
| innerLayerBounds = innerLayer.screenBounds |
| } |
| thisBounds.contains(innerLayerBounds) |
| } |
| } |
| |
| fun addChild(childLayer: Layer) { |
| _children.add(childLayer) |
| } |
| |
| fun addOccludedBy(layers: Array<Layer>) { |
| _occludedBy.addAll(layers) |
| } |
| |
| fun addPartiallyOccludedBy(layers: Array<Layer>) { |
| _partiallyOccludedBy.addAll(layers) |
| } |
| |
| fun addCoveredBy(layers: Array<Layer>) { |
| _coveredBy.addAll(layers) |
| } |
| |
| fun overlaps(other: Layer, crop: RectF = RectF.EMPTY): Boolean { |
| val thisBounds: RectF |
| val otherBounds: RectF |
| if (crop.isNotEmpty) { |
| thisBounds = this.screenBounds.crop(crop) |
| otherBounds = other.screenBounds.crop(crop) |
| } else { |
| thisBounds = this.screenBounds |
| otherBounds = other.screenBounds |
| } |
| return !thisBounds.intersection(otherBounds).isEmpty |
| } |
| |
| override fun toString(): String { |
| return buildString { |
| append(name) |
| |
| if (activeBuffer.isNotEmpty) { |
| append(" buffer:$activeBuffer") |
| append(" frame#$currFrame") |
| } |
| |
| if (isVisible) { |
| append(" visible:$visibleRegion") |
| } |
| } |
| } |
| |
| override fun equals(other: Any?): Boolean { |
| if (this === other) return true |
| if (other !is Layer) return false |
| |
| if (name != other.name) return false |
| if (id != other.id) return false |
| if (parentId != other.parentId) return false |
| if (z != other.z) return false |
| if (currFrame != other.currFrame) return false |
| if (stableId != other.stableId) return false |
| if (zOrderRelativeOf != other.zOrderRelativeOf) return false |
| if (zOrderRelativeParentOf != other.zOrderRelativeParentOf) return false |
| if (_occludedBy != other._occludedBy) return false |
| if (_partiallyOccludedBy != other._partiallyOccludedBy) return false |
| if (_coveredBy != other._coveredBy) return false |
| if (isMissing != other.isMissing) return false |
| if (visibleRegion != other.visibleRegion) return false |
| if (activeBuffer != other.activeBuffer) return false |
| if (flags != other.flags) return false |
| if (bounds != other.bounds) return false |
| if (color != other.color) return false |
| if (shadowRadius != other.shadowRadius) return false |
| if (cornerRadius != other.cornerRadius) return false |
| if (type != other.type) return false |
| if (transform != other.transform) return false |
| if (sourceBounds != other.sourceBounds) return false |
| if (effectiveScalingMode != other.effectiveScalingMode) return false |
| if (bufferTransform != other.bufferTransform) return false |
| if (hwcCompositionType != other.hwcCompositionType) return false |
| if (hwcCrop != other.hwcCrop) return false |
| if (hwcFrame != other.hwcFrame) return false |
| if (backgroundBlurRadius != other.backgroundBlurRadius) return false |
| if (crop != other.crop) return false |
| if (isRelativeOf != other.isRelativeOf) return false |
| if (zOrderRelativeOfId != other.zOrderRelativeOfId) return false |
| if (stackId != other.stackId) return false |
| if (requestedTransform != other.requestedTransform) return false |
| if (requestedColor != other.requestedColor) return false |
| if (cornerRadiusCrop != other.cornerRadiusCrop) return false |
| if (inputTransform != other.inputTransform) return false |
| if (inputRegion != other.inputRegion) return false |
| if (screenBounds != other.screenBounds) return false |
| if (isOpaque != other.isOpaque) return false |
| if (excludesCompositionState != other.excludesCompositionState) return false |
| |
| return true |
| } |
| |
| override fun hashCode(): Int { |
| var result = visibleRegion?.hashCode() ?: 0 |
| result = 31 * result + activeBuffer.hashCode() |
| result = 31 * result + flags |
| result = 31 * result + bounds.hashCode() |
| result = 31 * result + color.hashCode() |
| result = 31 * result + shadowRadius.hashCode() |
| result = 31 * result + cornerRadius.hashCode() |
| result = 31 * result + type.hashCode() |
| result = 31 * result + transform.hashCode() |
| result = 31 * result + sourceBounds.hashCode() |
| result = 31 * result + effectiveScalingMode |
| result = 31 * result + bufferTransform.hashCode() |
| result = 31 * result + hwcCompositionType.hashCode() |
| result = 31 * result + hwcCrop.hashCode() |
| result = 31 * result + hwcFrame.hashCode() |
| result = 31 * result + backgroundBlurRadius |
| result = 31 * result + crop.hashCode() |
| result = 31 * result + isRelativeOf.hashCode() |
| result = 31 * result + zOrderRelativeOfId |
| result = 31 * result + stackId |
| result = 31 * result + requestedTransform.hashCode() |
| result = 31 * result + requestedColor.hashCode() |
| result = 31 * result + cornerRadiusCrop.hashCode() |
| result = 31 * result + inputTransform.hashCode() |
| result = 31 * result + (inputRegion?.hashCode() ?: 0) |
| result = 31 * result + screenBounds.hashCode() |
| result = 31 * result + isOpaque.hashCode() |
| result = 31 * result + name.hashCode() |
| result = 31 * result + id |
| result = 31 * result + parentId |
| result = 31 * result + z |
| result = 31 * result + currFrame.hashCode() |
| result = 31 * result + stableId.hashCode() |
| result = 31 * result + (zOrderRelativeOf?.hashCode() ?: 0) |
| result = 31 * result + zOrderRelativeParentOf |
| result = 31 * result + _children.hashCode() |
| result = 31 * result + _occludedBy.hashCode() |
| result = 31 * result + _partiallyOccludedBy.hashCode() |
| result = 31 * result + _coveredBy.hashCode() |
| result = 31 * result + isMissing.hashCode() |
| result = 31 * result + excludesCompositionState.hashCode() |
| return result |
| } |
| |
| companion object { |
| @JsName("from") |
| fun from( |
| name: String, |
| id: Int, |
| parentId: Int, |
| z: Int, |
| visibleRegion: Region, |
| activeBuffer: ActiveBuffer, |
| flags: Int, |
| bounds: RectF, |
| color: Color, |
| isOpaque: Boolean, |
| shadowRadius: Float, |
| cornerRadius: Float, |
| type: String, |
| screenBounds: RectF, |
| transform: Transform, |
| sourceBounds: RectF, |
| currFrame: Long, |
| effectiveScalingMode: Int, |
| bufferTransform: Transform, |
| hwcCompositionType: HwcCompositionType, |
| hwcCrop: RectF, |
| hwcFrame: Rect, |
| backgroundBlurRadius: Int, |
| crop: Rect?, |
| isRelativeOf: Boolean, |
| zOrderRelativeOfId: Int, |
| stackId: Int, |
| requestedTransform: Transform, |
| requestedColor: Color, |
| cornerRadiusCrop: RectF, |
| inputTransform: Transform, |
| inputRegion: Region?, |
| excludesCompositionState: Boolean |
| ): Layer { |
| val properties = |
| LayerProperties.from( |
| visibleRegion, |
| activeBuffer, |
| flags, |
| bounds, |
| color, |
| isOpaque, |
| shadowRadius, |
| cornerRadius, |
| type, |
| screenBounds, |
| transform, |
| sourceBounds, |
| effectiveScalingMode, |
| bufferTransform, |
| hwcCompositionType, |
| hwcCrop, |
| hwcFrame, |
| backgroundBlurRadius, |
| crop, |
| isRelativeOf, |
| zOrderRelativeOfId, |
| stackId, |
| requestedTransform, |
| requestedColor, |
| cornerRadiusCrop, |
| inputTransform, |
| inputRegion, |
| excludesCompositionState |
| ) |
| return Layer(name, id, parentId, z, currFrame, properties) |
| } |
| } |
| } |