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.traces.surfaceflinger
18 
19 import android.tools.Timestamp
20 
21 /** Builder for LayerTraceEntries */
22 class LayerTraceEntryBuilder {
23     private var elapsedTimestamp: Long = 0L
24     private var realTimestamp: Long? = null
25     private var orphanLayerCallback: ((Layer) -> Boolean)? = null
26     private val orphans = mutableListOf<Layer>()
27     private var layers: MutableMap<Int, Layer> = mutableMapOf()
28     private var ignoreVirtualDisplay = false
29     private var ignoreLayersStackMatchNoDisplay = false
30     private var timestamp: Timestamp? = null
31     private var displays: Collection<Display> = emptyList()
32     private var vSyncId: Long = 0L
33     private var hwcBlob: String = ""
34     private var where: String = ""
35     private var duplicateLayerCallback: ((Layer) -> Boolean) = {
36         error("Duplicate layer id found: ${it.id}")
37     }
38 
39     fun setVSyncId(vSyncId: Long): LayerTraceEntryBuilder = apply { this.vSyncId = vSyncId }
40 
41     fun setHwcBlob(hwcBlob: String): LayerTraceEntryBuilder = apply { this.hwcBlob = hwcBlob }
42 
43     fun setWhere(where: String): LayerTraceEntryBuilder = apply { this.where = where }
44 
45     fun setDisplays(displays: Collection<Display>): LayerTraceEntryBuilder = apply {
46         this.displays = displays
47     }
48 
49     fun setElapsedTimestamp(timestamp: Long): LayerTraceEntryBuilder = apply {
50         this.elapsedTimestamp = timestamp
51     }
52 
53     fun setRealToElapsedTimeOffsetNs(realToElapsedTimeOffsetNs: Long?): LayerTraceEntryBuilder =
54         apply {
55             this.realTimestamp =
56                 if (realToElapsedTimeOffsetNs != null && realToElapsedTimeOffsetNs != 0L) {
57                     realToElapsedTimeOffsetNs + elapsedTimestamp
58                 } else {
59                     null
60                 }
61         }
62 
63     fun setLayers(layers: Collection<Layer>): LayerTraceEntryBuilder = apply {
64         val result = mutableMapOf<Int, Layer>()
65         layers.forEach { layer ->
66             val id = layer.id
67             if (result.containsKey(id)) {
68                 duplicateLayerCallback(layer)
69             }
70             result[id] = layer
71         }
72 
73         this.layers = result
74     }
75 
76     fun setOrphanLayerCallback(value: ((Layer) -> Boolean)?): LayerTraceEntryBuilder = apply {
77         this.orphanLayerCallback = value
78     }
79 
80     fun setDuplicateLayerCallback(value: ((Layer) -> Boolean)): LayerTraceEntryBuilder = apply {
81         this.duplicateLayerCallback = value
82     }
83 
84     private fun notifyOrphansLayers() {
85         val callback = this.orphanLayerCallback ?: return
86 
87         // Fail if we find orphan layers.
88         orphans.forEach { orphan ->
89             // Workaround for b/141326137, ignore the existence of an orphan layer
90             if (callback.invoke(orphan)) {
91                 return@forEach
92             }
93             throw RuntimeException(
94                 ("Failed to parse layers trace. Found orphan layer with id = ${orphan.id}" +
95                     " with parentId = ${orphan.parentId}")
96             )
97         }
98     }
99 
100     /**
101      * Update the parent layers or each trace
102      *
103      * @return root layer
104      */
105     private fun updateParents() {
106         for (layer in layers.values) {
107             val parentId = layer.parentId
108 
109             val parentLayer = layers[parentId]
110             if (parentLayer == null) {
111                 orphans.add(layer)
112                 continue
113             }
114             parentLayer.addChild(layer)
115             layer.parent = parentLayer
116         }
117     }
118 
119     /**
120      * Update the parent layers or each trace
121      *
122      * @return root layer
123      */
124     private fun updateRelZParents() {
125         for (layer in layers.values) {
126             val parentId = layer.zOrderRelativeOfId
127 
128             val parentLayer = layers[parentId]
129             if (parentLayer == null) {
130                 layer.zOrderRelativeParentOf = parentId
131                 continue
132             }
133             layer.zOrderRelativeOf = parentLayer
134         }
135     }
136 
137     private fun computeRootLayers(): Collection<Layer> {
138         updateParents()
139         updateRelZParents()
140 
141         // Find all root layers (any sibling of the root layer is considered a root layer in the
142         // trace)
143         val rootLayers = mutableListOf<Layer>()
144 
145         // Getting the first orphan works because when dumping the layers, the root layer comes
146         // first, and given that orphans are added in the same order as the layers are provided
147         // in the first orphan layer should be the root layer.
148         if (orphans.isNotEmpty()) {
149             val firstRoot = orphans.first()
150             orphans.remove(firstRoot)
151             rootLayers.add(firstRoot)
152 
153             val remainingRoots = orphans.filter { it.parentId == firstRoot.parentId }
154             rootLayers.addAll(remainingRoots)
155 
156             // Remove RootLayers from orphans
157             orphans.removeAll(rootLayers)
158         }
159 
160         return rootLayers
161     }
162 
163     private fun filterOutLayersInVirtualDisplays(roots: Collection<Layer>): Collection<Layer> {
164         val physicalDisplays = displays.filterNot { it.isVirtual }.map { it.layerStackId }
165 
166         return roots.filter { physicalDisplays.contains(it.stackId) }
167     }
168 
169     private fun filterOutVirtualDisplays(displays: Collection<Display>): Collection<Display> {
170         return displays.filterNot { it.isVirtual }
171     }
172 
173     private fun filterOutLayersInOffDisplays(roots: Collection<Layer>): Collection<Layer> {
174         val offDisplays = displays.filter { it.isOff }.map { it.layerStackId }
175 
176         // Negated filtering because legacy traces do not contain any displays, so we don't want to
177         // remove all traces since displays will be empty, so we won't have any on or off displays
178         return roots.filterNot { offDisplays.contains(it.stackId) }
179     }
180 
181     private fun filterOutLayersStackMatchNoDisplay(roots: Collection<Layer>): Collection<Layer> {
182         val displayStacks = displays.map { it.layerStackId }
183         return roots.filter { displayStacks.contains(it.stackId) }
184     }
185 
186     /**
187      * Defines if virtual displays and the layers belonging to virtual displays (e.g., Screen
188      * Recording) should be ignored while parsing the entry
189      *
190      * @param ignore If the layers from virtual displays should be ignored or not
191      */
192     fun ignoreVirtualDisplay(ignore: Boolean): LayerTraceEntryBuilder = apply {
193         this.ignoreVirtualDisplay = ignore
194     }
195 
196     /**
197      * Ignore layers whose stack ID doesn't match any display. This is the case, for example, when
198      * the device screen is off, or for layers that have not yet been removed after a display change
199      * (e.g., virtual screen recording display removed)
200      *
201      * @param ignore If the layers not matching any stack id should be removed or not
202      */
203     fun ignoreLayersStackMatchNoDisplay(ignore: Boolean): LayerTraceEntryBuilder = apply {
204         this.ignoreLayersStackMatchNoDisplay = ignore
205     }
206 
207     /** Constructs the layer hierarchy from a flattened list of layers. */
208     fun build(): LayerTraceEntry {
209         val allRoots = computeRootLayers()
210         var filteredRoots = allRoots
211         var filteredDisplays = displays
212 
213         if (ignoreLayersStackMatchNoDisplay) {
214             filteredRoots = filterOutLayersStackMatchNoDisplay(filteredRoots)
215         }
216 
217         if (ignoreVirtualDisplay) {
218             filteredRoots = filterOutLayersInVirtualDisplays(filteredRoots)
219             filteredDisplays = filterOutVirtualDisplays(filteredDisplays)
220         }
221 
222         filteredRoots = filterOutLayersInOffDisplays(filteredRoots)
223 
224         // Fail if we find orphan layers.
225         notifyOrphansLayers()
226 
227         return LayerTraceEntry(
228             elapsedTimestamp,
229             realTimestamp,
230             hwcBlob,
231             where,
232             filteredDisplays,
233             vSyncId,
234             filteredRoots,
235         )
236     }
237 }
238