• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
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