• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
1 /*
2  * Copyright (C) 2018 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 com.android.server.wm.flicker;
18 
19 import android.annotation.Nullable;
20 import android.graphics.Rect;
21 import android.surfaceflinger.nano.Layers.LayerProto;
22 import android.surfaceflinger.nano.Layers.RectProto;
23 import android.surfaceflinger.nano.Layers.RegionProto;
24 import android.surfaceflinger.nano.Layerstrace.LayersTraceFileProto;
25 import android.surfaceflinger.nano.Layerstrace.LayersTraceProto;
26 import android.util.SparseArray;
27 
28 import com.android.server.wm.flicker.Assertions.Result;
29 
30 import java.nio.file.Path;
31 import java.util.ArrayList;
32 import java.util.Collection;
33 import java.util.List;
34 import java.util.Optional;
35 import java.util.stream.Collectors;
36 
37 /**
38  * Contains a collection of parsed Layers trace entries and assertions to apply over
39  * a single entry.
40  *
41  * Each entry is parsed into a list of {@link LayersTrace.Entry} objects.
42  */
43 public class LayersTrace {
44     final private List<Entry> mEntries;
45     @Nullable
46     final private Path mSource;
47 
LayersTrace(List<Entry> entries, Path source)48     private LayersTrace(List<Entry> entries, Path source) {
49         this.mEntries = entries;
50         this.mSource = source;
51     }
52 
53     /**
54      * Parses {@code LayersTraceFileProto} from {@code data} and uses the proto to generates a list
55      * of trace entries, storing the flattened layers into its hierarchical structure.
56      *
57      * @param data   binary proto data
58      * @param source Path to source of data for additional debug information
59      */
parseFrom(byte[] data, Path source)60     static LayersTrace parseFrom(byte[] data, Path source) {
61         List<Entry> entries = new ArrayList<>();
62         LayersTraceFileProto fileProto;
63         try {
64             fileProto = LayersTraceFileProto.parseFrom(data);
65         } catch (Exception e) {
66             throw new RuntimeException(e);
67         }
68         for (LayersTraceProto traceProto : fileProto.entry) {
69             Entry entry = Entry.fromFlattenedLayers(traceProto.elapsedRealtimeNanos,
70                     traceProto.layers.layers);
71             entries.add(entry);
72         }
73         return new LayersTrace(entries, source);
74     }
75 
76     /**
77      * Parses {@code LayersTraceFileProto} from {@code data} and uses the proto to generates a list
78      * of trace entries, storing the flattened layers into its hierarchical structure.
79      *
80      * @param data binary proto data
81      */
parseFrom(byte[] data)82     static LayersTrace parseFrom(byte[] data) {
83         return parseFrom(data, null);
84     }
85 
getEntries()86     List<Entry> getEntries() {
87         return mEntries;
88     }
89 
getEntry(long timestamp)90     Entry getEntry(long timestamp) {
91         Optional<Entry> entry = mEntries.stream()
92                 .filter(e -> e.getTimestamp() == timestamp)
93                 .findFirst();
94         if (!entry.isPresent()) {
95             throw new RuntimeException("Entry does not exist for timestamp " + timestamp);
96         }
97         return entry.get();
98     }
99 
getSource()100     Optional<Path> getSource() {
101         return Optional.ofNullable(mSource);
102     }
103 
104     /**
105      * Represents a single Layer trace entry.
106      */
107     static class Entry implements ITraceEntry {
108         private long mTimestamp;
109         private List<Layer> mRootLayers; // hierarchical representation of layers
110         private List<Layer> mFlattenedLayers = null;
111 
Entry(long timestamp, List<Layer> rootLayers)112         private Entry(long timestamp, List<Layer> rootLayers) {
113             this.mTimestamp = timestamp;
114             this.mRootLayers = rootLayers;
115         }
116 
117         /**
118          * Constructs the layer hierarchy from a flattened list of layers.
119          */
fromFlattenedLayers(long timestamp, LayerProto[] protos)120         static Entry fromFlattenedLayers(long timestamp, LayerProto[] protos) {
121             SparseArray<Layer> layerMap = new SparseArray<>();
122             ArrayList<Layer> orphans = new ArrayList<>();
123             for (LayerProto proto : protos) {
124                 int id = proto.id;
125                 int parentId = proto.parent;
126 
127                 Layer newLayer = layerMap.get(id);
128                 if (newLayer == null) {
129                     newLayer = new Layer(proto);
130                     layerMap.append(id, newLayer);
131                 } else if (newLayer.mProto != null) {
132                     throw new RuntimeException("Duplicate layer id found:" + id);
133                 } else {
134                     newLayer.mProto = proto;
135                     orphans.remove(newLayer);
136                 }
137 
138                 // add parent placeholder
139                 if (layerMap.get(parentId) == null) {
140                     Layer orphanLayer = new Layer(null);
141                     layerMap.append(parentId, orphanLayer);
142                     orphans.add(orphanLayer);
143                 }
144                 layerMap.get(parentId).addChild(newLayer);
145                 newLayer.addParent(layerMap.get(parentId));
146             }
147 
148             // Fail if we find orphan layers.
149             orphans.remove(layerMap.get(-1));
150             orphans.forEach(orphan -> {
151                 String childNodes = orphan.mChildren.stream().map(node ->
152                         Integer.toString(node.getId())).collect(Collectors.joining(", "));
153                 int orphanId = orphan.mChildren.get(0).mProto.parent;
154                 throw new RuntimeException(
155                         "Failed to parse layers trace. Found orphan layers with parent "
156                                 + "layer id:" + orphanId + " : " + childNodes);
157             });
158 
159             return new Entry(timestamp, layerMap.get(-1).mChildren);
160         }
161 
162         /**
163          * Extracts {@link Rect} from {@link RectProto}.
164          */
extract(RectProto proto)165         private static Rect extract(RectProto proto) {
166             return new Rect(proto.left, proto.top, proto.right, proto.bottom);
167         }
168 
169         /**
170          * Extracts {@link Rect} from {@link RegionProto} by returning a rect that encompasses all
171          * the rects making up the region.
172          */
extract(RegionProto regionProto)173         private static Rect extract(RegionProto regionProto) {
174             Rect region = new Rect();
175             for (RectProto proto : regionProto.rect) {
176                 region.union(proto.left, proto.top, proto.right, proto.bottom);
177             }
178             return region;
179         }
180 
181         /**
182          * Checks if a region specified by {@code testRect} is covered by all visible layers.
183          */
coversRegion(Rect testRect)184         Result coversRegion(Rect testRect) {
185             String assertionName = "coversRegion";
186             Collection<Layer> layers = asFlattenedLayers();
187 
188             for (int x = testRect.left; x < testRect.right; x++) {
189                 for (int y = testRect.top; y < testRect.bottom; y++) {
190                     boolean emptyRegionFound = true;
191                     for (Layer layer : layers) {
192                         if (layer.isInvisible() || layer.isHiddenByParent()) {
193                             continue;
194                         }
195                         for (RectProto rectProto : layer.mProto.visibleRegion.rect) {
196                             Rect r = extract(rectProto);
197                             if (r.contains(x, y)) {
198                                 y = r.bottom;
199                                 emptyRegionFound = false;
200                             }
201                         }
202                     }
203                     if (emptyRegionFound) {
204                         String reason = "Region to test: " + testRect
205                                 + "\nfirst empty point: " + x + ", " + y;
206                         reason += "\nvisible regions:";
207                         for (Layer layer : layers) {
208                             if (layer.isInvisible() || layer.isHiddenByParent()) {
209                                 continue;
210                             }
211                             Rect r = extract(layer.mProto.visibleRegion);
212                             reason += "\n" + layer.mProto.name + r.toString();
213                         }
214                         return new Result(false /* success */, this.mTimestamp, assertionName,
215                                 reason);
216                     }
217                 }
218             }
219             String info = "Region covered: " + testRect;
220             return new Result(true /* success */, this.mTimestamp, assertionName, info);
221         }
222 
223         /**
224          * Checks if a layer with name {@code layerName} has a visible region
225          * {@code expectedVisibleRegion}.
226          */
hasVisibleRegion(String layerName, Rect expectedVisibleRegion)227         Result hasVisibleRegion(String layerName, Rect expectedVisibleRegion) {
228             String assertionName = "hasVisibleRegion";
229             String reason = "Could not find " + layerName;
230             for (Layer layer : asFlattenedLayers()) {
231                 if (layer.mProto.name.contains(layerName)) {
232                     if (layer.isHiddenByParent()) {
233                         reason = layer.getHiddenByParentReason();
234                         continue;
235                     }
236                     if (layer.isInvisible()) {
237                         reason = layer.getVisibilityReason();
238                         continue;
239                     }
240                     Rect visibleRegion = extract(layer.mProto.visibleRegion);
241                     if (visibleRegion.equals(expectedVisibleRegion)) {
242                         return new Result(true /* success */, this.mTimestamp, assertionName,
243                                 layer.mProto.name + "has visible region " + expectedVisibleRegion);
244                     }
245                     reason = layer.mProto.name + " has visible region:" + visibleRegion + " "
246                             + "expected:" + expectedVisibleRegion;
247                 }
248             }
249             return new Result(false /* success */, this.mTimestamp, assertionName, reason);
250         }
251 
252         /**
253          * Checks if a layer with name {@code layerName} is visible.
254          */
isVisible(String layerName)255         Result isVisible(String layerName) {
256             String assertionName = "isVisible";
257             String reason = "Could not find " + layerName;
258             for (Layer layer : asFlattenedLayers()) {
259                 if (layer.mProto.name.contains(layerName)) {
260                     if (layer.isHiddenByParent()) {
261                         reason = layer.getHiddenByParentReason();
262                         continue;
263                     }
264                     if (layer.isInvisible()) {
265                         reason = layer.getVisibilityReason();
266                         continue;
267                     }
268                     return new Result(true /* success */, this.mTimestamp, assertionName,
269                             layer.mProto.name + " is visible");
270                 }
271             }
272             return new Result(false /* success */, this.mTimestamp, assertionName, reason);
273         }
274 
275         @Override
getTimestamp()276         public long getTimestamp() {
277             return mTimestamp;
278         }
279 
getRootLayers()280         List<Layer> getRootLayers() {
281             return mRootLayers;
282         }
283 
asFlattenedLayers()284         List<Layer> asFlattenedLayers() {
285             if (mFlattenedLayers == null) {
286                 mFlattenedLayers = new ArrayList<>();
287                 ArrayList<Layer> pendingLayers = new ArrayList<>(this.mRootLayers);
288                 while (!pendingLayers.isEmpty()) {
289                     Layer layer = pendingLayers.remove(0);
290                     mFlattenedLayers.add(layer);
291                     pendingLayers.addAll(layer.mChildren);
292                 }
293             }
294             return mFlattenedLayers;
295         }
296 
getVisibleBounds(String layerName)297         Rect getVisibleBounds(String layerName) {
298             List<Layer> layers = asFlattenedLayers();
299             for (Layer layer : layers) {
300                 if (layer.mProto.name.contains(layerName) && layer.isVisible()) {
301                     return extract(layer.mProto.visibleRegion);
302                 }
303             }
304             return new Rect(0, 0, 0, 0);
305         }
306     }
307 
308     /**
309      * Represents a single layer with links to its parent and child layers.
310      */
311     static class Layer {
312         @Nullable
313         LayerProto mProto;
314         List<Layer> mChildren;
315         @Nullable
316         Layer mParent = null;
317 
Layer(LayerProto proto)318         private Layer(LayerProto proto) {
319             this.mProto = proto;
320             this.mChildren = new ArrayList<>();
321         }
322 
addChild(Layer childLayer)323         private void addChild(Layer childLayer) {
324             this.mChildren.add(childLayer);
325         }
326 
addParent(Layer parentLayer)327         private void addParent(Layer parentLayer) {
328             this.mParent = parentLayer;
329         }
330 
getId()331         int getId() {
332             return mProto.id;
333         }
334 
isActiveBufferEmpty()335         boolean isActiveBufferEmpty() {
336             return this.mProto.activeBuffer == null || this.mProto.activeBuffer.height == 0
337                     || this.mProto.activeBuffer.width == 0;
338         }
339 
isVisibleRegionEmpty()340         boolean isVisibleRegionEmpty() {
341             if (this.mProto.visibleRegion == null) {
342                 return true;
343             }
344             Rect visibleRect = Entry.extract(this.mProto.visibleRegion);
345             return visibleRect.height() == 0 || visibleRect.width() == 0;
346         }
347 
isHidden()348         boolean isHidden() {
349             return (this.mProto.flags & /* FLAG_HIDDEN */ 0x1) != 0x0;
350         }
351 
isVisible()352         boolean isVisible() {
353             return (!isActiveBufferEmpty() || isColorLayer()) &&
354                     !isHidden() && this.mProto.color.a > 0 && !isVisibleRegionEmpty();
355         }
356 
isColorLayer()357         boolean isColorLayer() {
358             return this.mProto.type.equals("ColorLayer");
359         }
360 
isRootLayer()361         boolean isRootLayer() {
362             return mParent == null || mParent.mProto == null;
363         }
364 
isInvisible()365         boolean isInvisible() {
366             return !isVisible();
367         }
368 
isHiddenByParent()369         boolean isHiddenByParent() {
370             return !isRootLayer() && (mParent.isHidden() || mParent.isHiddenByParent());
371         }
372 
getHiddenByParentReason()373         String getHiddenByParentReason() {
374             String reason = "Layer " + mProto.name;
375             if (isHiddenByParent()) {
376                 reason += " is hidden by parent: " + mParent.mProto.name;
377             } else {
378                 reason += " is not hidden by parent: " + mParent.mProto.name;
379             }
380             return reason;
381         }
382 
getVisibilityReason()383         String getVisibilityReason() {
384             String reason = "Layer " + mProto.name;
385             if (isVisible()) {
386                 reason += " is visible:";
387             } else {
388                 reason += " is invisible:";
389                 if (this.mProto.activeBuffer == null) {
390                     reason += " activeBuffer=null";
391                 } else if (this.mProto.activeBuffer.height == 0) {
392                     reason += " activeBuffer.height=0";
393                 } else if (this.mProto.activeBuffer.width == 0) {
394                     reason += " activeBuffer.width=0";
395                 }
396                 if (!isColorLayer()) {
397                     reason += " type != ColorLayer";
398                 }
399                 if (isHidden()) {
400                     reason += " flags=" + this.mProto.flags + " (FLAG_HIDDEN set)";
401                 }
402                 if (this.mProto.color.a == 0) {
403                     reason += " color.a=0";
404                 }
405                 if (isVisibleRegionEmpty()) {
406                     reason += " visible region is empty";
407                 }
408             }
409             return reason;
410         }
411     }
412 }