1 /* <lambda>null2 * 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.ITraceEntry 20 import android.tools.common.Timestamps 21 import android.tools.common.datatypes.Rect 22 import android.tools.common.datatypes.RectF 23 import android.tools.common.traces.component.ComponentNameMatcher 24 import android.tools.common.traces.component.IComponentMatcher 25 import kotlin.js.JsExport 26 import kotlin.js.JsName 27 28 /** 29 * Represents a single Layer trace entry. 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 LayerTraceEntry( 36 @JsName("elapsedTimestamp") val elapsedTimestamp: Long, 37 @JsName("clockTimestamp") val clockTimestamp: Long?, 38 val hwcBlob: String, 39 @JsName("where") val where: String, 40 @JsName("displays") val displays: Array<Display>, 41 @JsName("vSyncId") val vSyncId: Long, 42 _rootLayers: Array<Layer> 43 ) : ITraceEntry { 44 override val timestamp = 45 Timestamps.from(systemUptimeNanos = elapsedTimestamp, unixNanos = clockTimestamp) 46 47 @JsName("stableId") 48 val stableId: String = this::class.simpleName ?: error("Unable to determine class") 49 50 @JsName("flattenedLayers") val flattenedLayers: Array<Layer> = fillFlattenedLayers(_rootLayers) 51 52 // for winscope 53 @JsName("isVisible") val isVisible: Boolean = true 54 55 @JsName("visibleLayers") 56 val visibleLayers: Array<Layer> 57 get() = flattenedLayers.filter { it.isVisible }.toTypedArray() 58 59 @JsName("children") 60 val children: Array<Layer> 61 get() = flattenedLayers.filter { it.isRootLayer }.toTypedArray() 62 63 @JsName("physicalDisplay") 64 val physicalDisplay: Display? 65 get() = displays.firstOrNull { !it.isVirtual } 66 67 @JsName("physicalDisplayBounds") 68 val physicalDisplayBounds: Rect? 69 get() = physicalDisplay?.layerStackSpace 70 71 /** 72 * @param componentMatcher Components to search 73 * @return A [Layer] matching [componentMatcher] with a non-empty active buffer, or null if no 74 * layer matches [componentMatcher] or if the matching layer's buffer is empty 75 */ 76 fun getLayerWithBuffer(componentMatcher: IComponentMatcher): Layer? { 77 return flattenedLayers.firstOrNull { 78 componentMatcher.layerMatchesAnyOf(it) && !it.activeBuffer.isEmpty 79 } 80 } 81 82 /** @return The [Layer] with [layerId], or null if the layer is not found */ 83 fun getLayerById(layerId: Int): Layer? = this.flattenedLayers.firstOrNull { it.id == layerId } 84 85 /** 86 * Checks if any layer matching [componentMatcher] in the screen is animating. 87 * 88 * The screen is animating when a layer is not simple rotation, of when the pip overlay layer is 89 * visible 90 * 91 * @param componentMatcher Components to search 92 */ 93 fun isAnimating( 94 prevState: LayerTraceEntry?, 95 componentMatcher: IComponentMatcher? = null 96 ): Boolean { 97 val curLayers = 98 visibleLayers.filter { 99 componentMatcher == null || componentMatcher.layerMatchesAnyOf(it) 100 } 101 val currIds = visibleLayers.map { it.id } 102 val prevStateLayers = 103 prevState?.visibleLayers?.filter { currIds.contains(it.id) } ?: emptyList() 104 val layersAnimating = 105 curLayers.any { currLayer -> 106 val prevLayer = prevStateLayers.firstOrNull { it.id == currLayer.id } 107 currLayer.isAnimating(prevLayer) 108 } 109 val pipAnimating = isVisible(ComponentNameMatcher.PIP_CONTENT_OVERLAY) 110 return layersAnimating || pipAnimating 111 } 112 113 /** 114 * Check if at least one window matching [componentMatcher] is visible. 115 * 116 * @param componentMatcher Components to search 117 */ 118 @JsName("isVisibleComponent") 119 fun isVisible(componentMatcher: IComponentMatcher): Boolean = 120 componentMatcher.layerMatchesAnyOf(visibleLayers) 121 122 /** @return A [LayersTrace] object containing this state as its only entry */ 123 fun asTrace(): LayersTrace = LayersTrace(arrayOf(this)) 124 125 override fun toString(): String = timestamp.toString() 126 127 override fun equals(other: Any?): Boolean { 128 return other is LayerTraceEntry && other.timestamp == this.timestamp 129 } 130 131 override fun hashCode(): Int { 132 var result = timestamp.hashCode() 133 result = 31 * result + hwcBlob.hashCode() 134 result = 31 * result + where.hashCode() 135 result = 31 * result + displays.contentHashCode() 136 result = 31 * result + isVisible.hashCode() 137 result = 31 * result + flattenedLayers.contentHashCode() 138 return result 139 } 140 141 private fun fillFlattenedLayers(rootLayers: Array<Layer>): Array<Layer> { 142 val layers = mutableListOf<Layer>() 143 val roots = rootLayers.fillOcclusionState().toMutableList() 144 while (roots.isNotEmpty()) { 145 val layer = roots.removeAt(0) 146 layers.add(layer) 147 roots.addAll(layer.children) 148 } 149 return layers.toTypedArray() 150 } 151 152 private fun Array<Layer>.topDownTraversal(): List<Layer> { 153 return this.sortedBy { it.z }.flatMap { it.topDownTraversal() } 154 } 155 156 private fun Layer.topDownTraversal(): List<Layer> { 157 val traverseList = mutableListOf(this) 158 159 this.children 160 .sortedBy { it.z } 161 .forEach { childLayer -> traverseList.addAll(childLayer.topDownTraversal()) } 162 163 return traverseList 164 } 165 166 private fun Array<Layer>.fillOcclusionState(): Array<Layer> { 167 val traversalList = topDownTraversal().reversed() 168 169 val opaqueLayers = mutableListOf<Layer>() 170 val transparentLayers = mutableListOf<Layer>() 171 172 traversalList.forEach { layer -> 173 val visible = layer.isVisible 174 val displaySize = 175 displays 176 .firstOrNull { it.layerStackId == layer.stackId } 177 ?.layerStackSpace 178 ?.toRectF() 179 ?: RectF.EMPTY 180 181 if (visible) { 182 val occludedBy = 183 opaqueLayers 184 .filter { 185 it.stackId == layer.stackId && 186 it.contains(layer, displaySize) && 187 (!it.hasRoundedCorners || (layer.cornerRadius == it.cornerRadius)) 188 } 189 .toTypedArray() 190 layer.addOccludedBy(occludedBy) 191 val partiallyOccludedBy = 192 opaqueLayers 193 .filter { 194 it.stackId == layer.stackId && 195 it.overlaps(layer, displaySize) && 196 it !in layer.occludedBy 197 } 198 .toTypedArray() 199 layer.addPartiallyOccludedBy(partiallyOccludedBy) 200 val coveredBy = 201 transparentLayers 202 .filter { it.stackId == layer.stackId && it.overlaps(layer, displaySize) } 203 .toTypedArray() 204 layer.addCoveredBy(coveredBy) 205 206 if (layer.isOpaque) { 207 opaqueLayers.add(layer) 208 } else { 209 transparentLayers.add(layer) 210 } 211 } 212 } 213 214 return this 215 } 216 } 217