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 }