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 androidx.annotation.Nullable;
20 
21 import android.graphics.Rect;
22 import android.surfaceflinger.nano.Layers.LayerProto;
23 import android.surfaceflinger.nano.Layers.RectProto;
24 import android.surfaceflinger.nano.Layers.RegionProto;
25 import android.surfaceflinger.nano.Layerstrace.LayersTraceFileProto;
26 import android.surfaceflinger.nano.Layerstrace.LayersTraceProto;
27 import android.util.SparseArray;
28 
29 import com.android.server.wm.flicker.Assertions.Result;
30 
31 import java.nio.file.Path;
32 import java.util.ArrayList;
33 import java.util.Collection;
34 import java.util.List;
35 import java.util.Optional;
36 import java.util.function.Consumer;
37 import java.util.stream.Collectors;
38 
39 /**
40  * Contains a collection of parsed Layers trace entries and assertions to apply over a single entry.
41  *
42  * <p>Each entry is parsed into a list of {@link LayersTrace.Entry} objects.
43  */
44 public class LayersTrace {
45     private final List<Entry> mEntries;
46     @Nullable private final Path mSource;
47     @Nullable private final String mSourceChecksum;
48 
LayersTrace(List<Entry> entries, Path source, String sourceChecksum)49     private LayersTrace(List<Entry> entries, Path source, String sourceChecksum) {
50         this.mEntries = entries;
51         this.mSource = source;
52         this.mSourceChecksum = sourceChecksum;
53     }
54 
55     /**
56      * Parses {@code LayersTraceFileProto} from {@code data} and uses the proto to generates a list
57      * of trace entries, storing the flattened layers into its hierarchical structure.
58      *
59      * @param data binary proto data
60      * @param source Path to source of data for additional debug information
61      * @param orphanLayerCallback a callback to handle any unexpected orphan layers
62      */
parseFrom( byte[] data, Path source, Consumer<Layer> orphanLayerCallback)63     public static LayersTrace parseFrom(
64             byte[] data, Path source, Consumer<Layer> orphanLayerCallback) {
65         return parseFrom(data, source, null /* sourceChecksum */, orphanLayerCallback);
66     }
67 
68     /**
69      * Parses {@code LayersTraceFileProto} from {@code data} and uses the proto to generates a list
70      * of trace entries, storing the flattened layers into its hierarchical structure.
71      *
72      * @param data binary proto data
73      * @param source Path to source of data for additional debug information
74      * @param orphanLayerCallback a callback to handle any unexpected orphan layers
75      */
parseFrom( byte[] data, Path source, String sourceChecksum, Consumer<Layer> orphanLayerCallback)76     public static LayersTrace parseFrom(
77             byte[] data, Path source, String sourceChecksum, Consumer<Layer> orphanLayerCallback) {
78         List<Entry> entries = new ArrayList<>();
79         LayersTraceFileProto fileProto;
80         try {
81             fileProto = LayersTraceFileProto.parseFrom(data);
82         } catch (Exception e) {
83             throw new RuntimeException(e);
84         }
85         for (LayersTraceProto traceProto : fileProto.entry) {
86             Entry entry =
87                     Entry.fromFlattenedLayers(
88                             traceProto.elapsedRealtimeNanos, traceProto.layers.layers,
89                             orphanLayerCallback);
90             entries.add(entry);
91         }
92         return new LayersTrace(entries, source, sourceChecksum);
93     }
94 
95     /**
96      * Parses {@code LayersTraceFileProto} from {@code data} and uses the proto to generates a list
97      * of trace entries, storing the flattened layers into its hierarchical structure.
98      *
99      * @param data binary proto data
100      * @param source Path to source of data for additional debug information
101      */
parseFrom(byte[] data, Path source, String sourceChecksum)102     public static LayersTrace parseFrom(byte[] data, Path source, String sourceChecksum) {
103         return parseFrom(data, source, sourceChecksum, null /* orphanLayerCallback */);
104     }
105 
106     /**
107      * Parses {@code LayersTraceFileProto} from {@code data} and uses the proto to generates a list
108      * of trace entries, storing the flattened layers into its hierarchical structure.
109      *
110      * @param data binary proto data
111      * @param source Path to source of data for additional debug information
112      */
parseFrom(byte[] data, Path source)113     public static LayersTrace parseFrom(byte[] data, Path source) {
114         return parseFrom(data, source, null /* sourceChecksum */, null /* orphanLayerCallback */);
115     }
116 
117     /**
118      * Parses {@code LayersTraceFileProto} from {@code data} and uses the proto to generates a list
119      * of trace entries, storing the flattened layers into its hierarchical structure.
120      *
121      * @param data binary proto data
122      */
parseFrom(byte[] data)123     public static LayersTrace parseFrom(byte[] data) {
124         return parseFrom(data, null /* source */);
125     }
126 
getEntries()127     public List<Entry> getEntries() {
128         return mEntries;
129     }
130 
getEntry(long timestamp)131     public Entry getEntry(long timestamp) {
132         Optional<Entry> entry =
133                 mEntries.stream().filter(e -> e.getTimestamp() == timestamp).findFirst();
134         if (!entry.isPresent()) {
135             throw new RuntimeException("Entry does not exist for timestamp " + timestamp);
136         }
137         return entry.get();
138     }
139 
getSource()140     public Optional<Path> getSource() {
141         return Optional.ofNullable(mSource);
142     }
143 
getSourceChecksum()144     public String getSourceChecksum() {
145         return mSourceChecksum;
146     }
147 
148     /** Represents a single Layer trace entry. */
149     public static class Entry implements ITraceEntry {
150         private long mTimestamp;
151         private List<Layer> mRootLayers; // hierarchical representation of layers
152         private List<Layer> mFlattenedLayers = null;
153 
Entry(long timestamp, List<Layer> rootLayers)154         private Entry(long timestamp, List<Layer> rootLayers) {
155             this.mTimestamp = timestamp;
156             this.mRootLayers = rootLayers;
157         }
158 
159         /**
160          * Determines the id of the root element.
161          *
162          * <p>On some files, such as the ones used in the FlickerLib testdata, the root nodes are
163          * those that have parent=0, on newer traces, the root nodes are those that have parent=-1
164          *
165          * <p>This function keeps compatibility with both new and older traces by searching for a
166          * known root layer (Display Root) and considering its parent Id as overall root.
167          */
getRootLayer(SparseArray<Layer> layerMap)168         private static Layer getRootLayer(SparseArray<Layer> layerMap) {
169             Layer knownRoot = null;
170             int numKeys = layerMap.size();
171             for (int i = 0; i < numKeys; ++i) {
172                 Layer currentLayer = layerMap.valueAt(i);
173                 if (currentLayer.isRootLayer()) {
174                     knownRoot = currentLayer;
175                     break;
176                 }
177             }
178 
179             if (knownRoot == null) {
180                 throw new IllegalStateException("Display root layer not found.");
181             }
182 
183             return layerMap.get(knownRoot.getParentId());
184         }
185 
186         /** Constructs the layer hierarchy from a flattened list of layers. */
fromFlattenedLayers(long timestamp, LayerProto[] protos, Consumer<Layer> orphanLayerCallback)187         public static Entry fromFlattenedLayers(long timestamp, LayerProto[] protos,
188                 Consumer<Layer> orphanLayerCallback) {
189             SparseArray<Layer> layerMap = new SparseArray<>();
190             ArrayList<Layer> orphans = new ArrayList<>();
191             for (LayerProto proto : protos) {
192                 int id = proto.id;
193                 int parentId = proto.parent;
194 
195                 Layer newLayer = layerMap.get(id);
196                 if (newLayer == null) {
197                     newLayer = new Layer(proto);
198                     layerMap.append(id, newLayer);
199                 } else if (newLayer.mProto != null) {
200                     throw new RuntimeException("Duplicate layer id found:" + id);
201                 } else {
202                     newLayer.mProto = proto;
203                     orphans.remove(newLayer);
204                 }
205 
206                 // add parent placeholder
207                 if (layerMap.get(parentId) == null) {
208                     Layer orphanLayer = new Layer(null);
209                     layerMap.append(parentId, orphanLayer);
210                     orphans.add(orphanLayer);
211                 }
212                 layerMap.get(parentId).addChild(newLayer);
213                 newLayer.addParent(layerMap.get(parentId));
214             }
215 
216             // Remove root node
217             Layer rootLayer = getRootLayer(layerMap);
218             orphans.remove(rootLayer);
219             // Fail if we find orphan layers.
220             orphans.forEach(
221                     orphan -> {
222                         if (orphanLayerCallback != null) {
223                             // Workaround for b/141326137, ignore the existence of an orphan layer
224                             orphanLayerCallback.accept(orphan);
225                             return;
226                         }
227                         String childNodes =
228                                 orphan.mChildren
229                                         .stream()
230                                         .map(node -> Integer.toString(node.getId()))
231                                         .collect(Collectors.joining(", "));
232                         int orphanId = orphan.mChildren.get(0).getParentId();
233                         throw new RuntimeException(
234                                 "Failed to parse layers trace. Found orphan layers with parent "
235                                         + "layer id:"
236                                         + orphanId
237                                         + " : "
238                                         + childNodes);
239                     });
240 
241             return new Entry(timestamp, rootLayer.mChildren);
242         }
243 
244         /** Extracts {@link Rect} from {@link RectProto}. */
extract(RectProto proto)245         private static Rect extract(RectProto proto) {
246             return new Rect(proto.left, proto.top, proto.right, proto.bottom);
247         }
248 
249         /**
250          * Extracts {@link Rect} from {@link RegionProto} by returning a rect that encompasses all
251          * the rects making up the region.
252          */
extract(RegionProto regionProto)253         private static Rect extract(RegionProto regionProto) {
254             Rect region = new Rect();
255             for (RectProto proto : regionProto.rect) {
256                 region.union(proto.left, proto.top, proto.right, proto.bottom);
257             }
258             return region;
259         }
260 
261         /** Checks if a region specified by {@code testRect} is covered by all visible layers. */
coversRegion(Rect testRect)262         public Result coversRegion(Rect testRect) {
263             String assertionName = "coversRegion";
264             Collection<Layer> layers = asFlattenedLayers();
265 
266             for (int x = testRect.left; x < testRect.right; x++) {
267                 for (int y = testRect.top; y < testRect.bottom; y++) {
268                     boolean emptyRegionFound = true;
269                     for (Layer layer : layers) {
270                         if (layer.isInvisible() || layer.isHiddenByParent()) {
271                             continue;
272                         }
273                         for (RectProto rectProto : layer.mProto.visibleRegion.rect) {
274                             Rect r = extract(rectProto);
275                             if (r.contains(x, y)) {
276                                 y = r.bottom;
277                                 emptyRegionFound = false;
278                             }
279                         }
280                     }
281                     if (emptyRegionFound) {
282                         String reason =
283                                 "Region to test: "
284                                         + testRect
285                                         + "\nfirst empty point: "
286                                         + x
287                                         + ", "
288                                         + y;
289                         reason += "\nvisible regions:";
290                         for (Layer layer : layers) {
291                             if (layer.isInvisible() || layer.isHiddenByParent()) {
292                                 continue;
293                             }
294                             Rect r = extract(layer.mProto.visibleRegion);
295                             reason += "\n" + layer.mProto.name + r.toString();
296                         }
297                         return new Result(
298                                 false /* success */, this.mTimestamp, assertionName, reason);
299                     }
300                 }
301             }
302             String info = "Region covered: " + testRect;
303             return new Result(true /* success */, this.mTimestamp, assertionName, info);
304         }
305 
306         /**
307          * Checks if a layer with name {@code layerName} has a visible region {@code
308          * expectedVisibleRegion}.
309          */
hasVisibleRegion(String layerName, Rect expectedVisibleRegion)310         public Result hasVisibleRegion(String layerName, Rect expectedVisibleRegion) {
311             String assertionName = "hasVisibleRegion";
312             String reason = "Could not find " + layerName;
313             for (Layer layer : asFlattenedLayers()) {
314                 if (layer.mProto.name.contains(layerName)) {
315                     if (layer.isHiddenByParent()) {
316                         reason = layer.getHiddenByParentReason();
317                         continue;
318                     }
319                     if (layer.isInvisible()) {
320                         reason = layer.getVisibilityReason();
321                         continue;
322                     }
323                     Rect visibleRegion = extract(layer.mProto.visibleRegion);
324                     if (visibleRegion.equals(expectedVisibleRegion)) {
325                         return new Result(
326                                 true /* success */,
327                                 this.mTimestamp,
328                                 assertionName,
329                                 layer.mProto.name + "has visible region " + expectedVisibleRegion);
330                     }
331                     reason =
332                             layer.mProto.name
333                                     + " has visible region:"
334                                     + visibleRegion
335                                     + " "
336                                     + "expected:"
337                                     + expectedVisibleRegion;
338                 }
339             }
340             return new Result(false /* success */, this.mTimestamp, assertionName, reason);
341         }
342 
343         /** Checks if a layer with name {@code layerName} exists in the hierarchy. */
exists(String layerName)344         public Result exists(String layerName) {
345             String assertionName = "exists";
346             String reason = "Could not find " + layerName;
347             for (Layer layer : asFlattenedLayers()) {
348                 if (layer.mProto.name.contains(layerName)) {
349                     return new Result(
350                             true /* success */,
351                             this.mTimestamp,
352                             assertionName,
353                             layer.mProto.name + " exists");
354                 }
355             }
356             return new Result(false /* success */, this.mTimestamp, assertionName, reason);
357         }
358 
359         /** Checks if a layer with name {@code layerName} is visible. */
isVisible(String layerName)360         public Result isVisible(String layerName) {
361             String assertionName = "isVisible";
362             String reason = "Could not find " + layerName;
363             for (Layer layer : asFlattenedLayers()) {
364                 if (layer.mProto.name.contains(layerName)) {
365                     if (layer.isHiddenByParent()) {
366                         reason = layer.getHiddenByParentReason();
367                         continue;
368                     }
369                     if (layer.isInvisible()) {
370                         reason = layer.getVisibilityReason();
371                         continue;
372                     }
373                     return new Result(
374                             true /* success */,
375                             this.mTimestamp,
376                             assertionName,
377                             layer.mProto.name + " is visible");
378                 }
379             }
380             return new Result(false /* success */, this.mTimestamp, assertionName, reason);
381         }
382 
383         @Override
getTimestamp()384         public long getTimestamp() {
385             return mTimestamp;
386         }
387 
getRootLayers()388         public List<Layer> getRootLayers() {
389             return mRootLayers;
390         }
391 
392         /** Returns all layers as a flattened list using a depth first traversal. */
asFlattenedLayers()393         public List<Layer> asFlattenedLayers() {
394             if (mFlattenedLayers == null) {
395                 mFlattenedLayers = new ArrayList<>();
396                 ArrayList<Layer> pendingLayers = new ArrayList<>(this.mRootLayers);
397                 while (!pendingLayers.isEmpty()) {
398                     Layer layer = pendingLayers.remove(0);
399                     mFlattenedLayers.add(layer);
400                     pendingLayers.addAll(layer.mChildren);
401                 }
402             }
403             return mFlattenedLayers;
404         }
405 
getVisibleBounds(String layerName)406         public Rect getVisibleBounds(String layerName) {
407             List<Layer> layers = asFlattenedLayers();
408             for (Layer layer : layers) {
409                 if (layer.mProto.name.contains(layerName) && layer.isVisible()) {
410                     return extract(layer.mProto.visibleRegion);
411                 }
412             }
413             return new Rect(0, 0, 0, 0);
414         }
415     }
416 
417     /** Represents a single layer with links to its parent and child layers. */
418     public static class Layer {
419         @Nullable public LayerProto mProto;
420         public List<Layer> mChildren;
421         @Nullable public Layer mParent = null;
422 
Layer(LayerProto proto)423         private Layer(LayerProto proto) {
424             this.mProto = proto;
425             this.mChildren = new ArrayList<>();
426         }
427 
addChild(Layer childLayer)428         private void addChild(Layer childLayer) {
429             this.mChildren.add(childLayer);
430         }
431 
addParent(Layer parentLayer)432         private void addParent(Layer parentLayer) {
433             this.mParent = parentLayer;
434         }
435 
getId()436         public int getId() {
437             return mProto.id;
438         }
439 
getParentId()440         public int getParentId() {
441             return mProto.parent;
442         }
443 
getName()444         public String getName() {
445             if (mProto != null) {
446                 return mProto.name;
447             }
448 
449             return "";
450         }
451 
isActiveBufferEmpty()452         public boolean isActiveBufferEmpty() {
453             return this.mProto.activeBuffer == null
454                     || this.mProto.activeBuffer.height == 0
455                     || this.mProto.activeBuffer.width == 0;
456         }
457 
isVisibleRegionEmpty()458         public boolean isVisibleRegionEmpty() {
459             if (this.mProto.visibleRegion == null) {
460                 return true;
461             }
462             Rect visibleRect = Entry.extract(this.mProto.visibleRegion);
463             return visibleRect.height() == 0 || visibleRect.width() == 0;
464         }
465 
isHidden()466         public boolean isHidden() {
467             return (this.mProto.flags & /* FLAG_HIDDEN */ 0x1) != 0x0;
468         }
469 
isVisible()470         public boolean isVisible() {
471             return (!isActiveBufferEmpty() || isColorLayer())
472                     && !isHidden()
473                     && this.mProto.color != null
474                     && this.mProto.color.a > 0
475                     && !isVisibleRegionEmpty();
476         }
477 
isColorLayer()478         public boolean isColorLayer() {
479             return this.mProto.type.equals("ColorLayer");
480         }
481 
isRootLayer()482         public boolean isRootLayer() {
483             return mParent != null && mParent.mProto == null;
484         }
485 
isInvisible()486         public boolean isInvisible() {
487             return !isVisible();
488         }
489 
isHiddenByParent()490         public boolean isHiddenByParent() {
491             return !isRootLayer() && (mParent.isHidden() || mParent.isHiddenByParent());
492         }
493 
getHiddenByParentReason()494         public String getHiddenByParentReason() {
495             String reason = "Layer " + mProto.name;
496             if (isHiddenByParent()) {
497                 reason += " is hidden by parent: " + mParent.mProto.name;
498             } else {
499                 reason += " is not hidden by parent: " + mParent.mProto.name;
500             }
501             return reason;
502         }
503 
getVisibilityReason()504         public String getVisibilityReason() {
505             String reason = "Layer " + mProto.name;
506             if (isVisible()) {
507                 reason += " is visible:";
508             } else {
509                 reason += " is invisible:";
510                 if (this.mProto.activeBuffer == null) {
511                     reason += " activeBuffer=null";
512                 } else if (this.mProto.activeBuffer.height == 0) {
513                     reason += " activeBuffer.height=0";
514                 } else if (this.mProto.activeBuffer.width == 0) {
515                     reason += " activeBuffer.width=0";
516                 }
517                 if (!isColorLayer()) {
518                     reason += " type != ColorLayer";
519                 }
520                 if (isHidden()) {
521                     reason += " flags=" + this.mProto.flags + " (FLAG_HIDDEN set)";
522                 }
523                 if (this.mProto.color == null || this.mProto.color.a == 0) {
524                     reason += " color.a=0";
525                 }
526                 if (isVisibleRegionEmpty()) {
527                     reason += " visible region is empty";
528                 }
529             }
530             return reason;
531         }
532     }
533 }
534