1/* 2 * Copyright (C) 2024 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 17import {Transform} from 'parsers/surface_flinger/transform_utils'; 18import {android} from 'protos/surfaceflinger/udc/static'; 19import {HierarchyTreeBuilder} from 'test/unit/hierarchy_tree_builder'; 20import {TraceRect} from 'trace/trace_rect'; 21import {TraceRectBuilder} from 'trace/trace_rect_builder'; 22import {RectsComputation} from './rects_computation'; 23 24describe('RectsComputation', () => { 25 let computation: RectsComputation; 26 27 beforeEach(() => { 28 computation = new RectsComputation(); 29 }); 30 31 it('makes layer rects', () => { 32 const hierarchyRoot = new HierarchyTreeBuilder() 33 .setId('LayerTraceEntry') 34 .setName('root') 35 .setChildren([ 36 { 37 id: 1, 38 name: 'layer1', 39 properties: { 40 id: 1, 41 name: 'layer1', 42 cornerRadius: 0, 43 layerStack: 0, 44 bounds: {left: 0, top: 0, right: 1, bottom: 1}, 45 zOrderPath: [0], 46 isComputedVisible: true, 47 transform: Transform.EMPTY, 48 } as android.surfaceflinger.ILayerProto, 49 children: [ 50 { 51 id: 2, 52 name: 'layer2', 53 properties: { 54 id: 2, 55 name: 'layer2', 56 cornerRadius: 2, 57 layerStack: 0, 58 bounds: {left: 0, top: 0, right: 2, bottom: 2}, 59 zOrderPath: [0, 1], 60 occludedBy: [1], 61 isComputedVisible: false, 62 transform: Transform.EMPTY, 63 } as android.surfaceflinger.ILayerProto, 64 }, 65 ], 66 }, 67 { 68 id: 4, 69 name: 'layerRelativeZ', 70 properties: { 71 id: 4, 72 name: 'layerRelativeZ', 73 cornerRadius: 0, 74 layerStack: 0, 75 bounds: {left: 0, top: 0, right: 5, bottom: 5}, 76 zOrderPath: [0, 2], 77 isComputedVisible: true, 78 color: {r: 0, g: 0, b: 0, a: 1}, 79 transform: Transform.EMPTY, 80 } as android.surfaceflinger.ILayerProto, 81 }, 82 ]) 83 .build(); 84 85 const expectedRects: TraceRect[] = [ 86 new TraceRectBuilder() 87 .setX(0) 88 .setY(0) 89 .setWidth(1) 90 .setHeight(1) 91 .setId('1 layer1') 92 .setName('layer1') 93 .setCornerRadius(0) 94 .setTransform(Transform.EMPTY.matrix) 95 .setDepth(0) 96 .setGroupId(0) 97 .setIsVisible(true) 98 .setOpacity(0) 99 .setIsDisplay(false) 100 .setIsVirtual(false) 101 .build(), 102 103 new TraceRectBuilder() 104 .setX(0) 105 .setY(0) 106 .setWidth(2) 107 .setHeight(2) 108 .setId('2 layer2') 109 .setName('layer2') 110 .setCornerRadius(2) 111 .setTransform(Transform.EMPTY.matrix) 112 .setDepth(1) 113 .setGroupId(0) 114 .setIsVisible(false) 115 .setIsDisplay(false) 116 .setIsVirtual(false) 117 .build(), 118 119 new TraceRectBuilder() 120 .setX(0) 121 .setY(0) 122 .setWidth(5) 123 .setHeight(5) 124 .setId('4 layerRelativeZ') 125 .setName('layerRelativeZ') 126 .setCornerRadius(0) 127 .setTransform(Transform.EMPTY.matrix) 128 .setDepth(2) 129 .setGroupId(0) 130 .setIsVisible(true) 131 .setOpacity(1) 132 .setIsDisplay(false) 133 .setIsVirtual(false) 134 .build(), 135 ]; 136 137 computation.setRoot(hierarchyRoot).executeInPlace(); 138 139 const rects: TraceRect[] = []; 140 hierarchyRoot.forEachNodeDfs((node) => { 141 if (node.id === 'LayerTraceEntry root') { 142 return; 143 } 144 const nodeRects = node.getRects(); 145 if (nodeRects) rects.push(...nodeRects); 146 }); 147 148 expect(rects).toEqual(expectedRects); 149 }); 150 151 it('handles layer rects with different group ids', () => { 152 const hierarchyRoot = new HierarchyTreeBuilder() 153 .setId('LayerTraceEntry') 154 .setName('root') 155 .setChildren([ 156 { 157 id: 1, 158 name: 'layer1', 159 properties: { 160 id: 1, 161 name: 'layer1', 162 cornerRadius: 0, 163 layerStack: 0, 164 bounds: {left: 0, top: 0, right: 1, bottom: 1}, 165 zOrderPath: [0], 166 isComputedVisible: true, 167 color: {r: 0, g: 0, b: 0, a: 1}, 168 transform: Transform.EMPTY, 169 } as android.surfaceflinger.ILayerProto, 170 }, 171 { 172 id: 2, 173 name: 'layer2', 174 properties: { 175 id: 2, 176 name: 'layer2', 177 cornerRadius: 0, 178 layerStack: 1, 179 bounds: {left: 0, top: 0, right: 1, bottom: 1}, 180 zOrderPath: [0], 181 isComputedVisible: true, 182 color: {r: 0, g: 0, b: 0, a: 1}, 183 transform: Transform.EMPTY, 184 } as android.surfaceflinger.ILayerProto, 185 }, 186 ]) 187 .build(); 188 189 const expectedRects: TraceRect[] = [ 190 new TraceRectBuilder() 191 .setX(0) 192 .setY(0) 193 .setWidth(1) 194 .setHeight(1) 195 .setId('1 layer1') 196 .setName('layer1') 197 .setCornerRadius(0) 198 .setTransform(Transform.EMPTY.matrix) 199 .setDepth(0) 200 .setGroupId(0) 201 .setIsVisible(true) 202 .setOpacity(1) 203 .setIsDisplay(false) 204 .setIsVirtual(false) 205 .build(), 206 207 new TraceRectBuilder() 208 .setX(0) 209 .setY(0) 210 .setWidth(1) 211 .setHeight(1) 212 .setId('2 layer2') 213 .setName('layer2') 214 .setCornerRadius(0) 215 .setTransform(Transform.EMPTY.matrix) 216 .setDepth(0) 217 .setGroupId(1) 218 .setIsVisible(true) 219 .setOpacity(1) 220 .setIsDisplay(false) 221 .setIsVirtual(false) 222 .build(), 223 ]; 224 225 computation.setRoot(hierarchyRoot).executeInPlace(); 226 227 const rects: TraceRect[] = []; 228 hierarchyRoot.forEachNodeDfs((node) => { 229 if (node.id === 'LayerTraceEntry root') { 230 return; 231 } 232 const nodeRects = node.getRects(); 233 if (nodeRects) rects.push(...nodeRects); 234 }); 235 236 expect(rects).toEqual(expectedRects); 237 }); 238 239 it('makes display rects', () => { 240 const hierarchyRoot = new HierarchyTreeBuilder() 241 .setId('LayerTraceEntry') 242 .setName('root') 243 .setProperties({ 244 displays: [ 245 { 246 id: 1, 247 layerStack: 0, 248 layerStackSpaceRect: {left: 0, top: 0, right: 5, bottom: 5}, 249 transform: Transform.EMPTY, 250 name: 'Test Display', 251 }, 252 ], 253 }) 254 .build(); 255 256 const expectedDisplayRects = [ 257 new TraceRectBuilder() 258 .setX(0) 259 .setY(0) 260 .setWidth(5) 261 .setHeight(5) 262 .setId('Display - 1') 263 .setName('Test Display') 264 .setCornerRadius(0) 265 .setTransform(Transform.EMPTY.matrix) 266 .setDepth(0) 267 .setGroupId(0) 268 .setIsVisible(false) 269 .setIsDisplay(true) 270 .setIsVirtual(false) 271 .build(), 272 ]; 273 274 computation.setRoot(hierarchyRoot).executeInPlace(); 275 expect(hierarchyRoot.getRects()).toEqual(expectedDisplayRects); 276 }); 277 278 it('makes display rects with unknown or empty name', () => { 279 const hierarchyRoot = new HierarchyTreeBuilder() 280 .setId('LayerTraceEntry') 281 .setName('root') 282 .setProperties({ 283 displays: [ 284 { 285 id: 1, 286 layerStack: 0, 287 layerStackSpaceRect: {left: 0, top: 0, right: 5, bottom: 5}, 288 transform: Transform.EMPTY, 289 }, 290 { 291 id: 1, 292 layerStack: 0, 293 layerStackSpaceRect: {left: 0, top: 0, right: 5, bottom: 5}, 294 transform: Transform.EMPTY, 295 name: '', 296 }, 297 ], 298 }) 299 .build(); 300 301 const expectedDisplayRects = [ 302 new TraceRectBuilder() 303 .setX(0) 304 .setY(0) 305 .setWidth(5) 306 .setHeight(5) 307 .setId('Display - 1') 308 .setName('Unknown Display') 309 .setCornerRadius(0) 310 .setTransform(Transform.EMPTY.matrix) 311 .setDepth(0) 312 .setGroupId(0) 313 .setIsVisible(false) 314 .setIsDisplay(true) 315 .setIsVirtual(false) 316 .build(), 317 new TraceRectBuilder() 318 .setX(0) 319 .setY(0) 320 .setWidth(5) 321 .setHeight(5) 322 .setId('Display - 1') 323 .setName('Unknown Display (2)') 324 .setCornerRadius(0) 325 .setTransform(Transform.EMPTY.matrix) 326 .setDepth(1) 327 .setGroupId(0) 328 .setIsVisible(false) 329 .setIsDisplay(true) 330 .setIsVirtual(false) 331 .build(), 332 ]; 333 334 computation.setRoot(hierarchyRoot).executeInPlace(); 335 expect(hierarchyRoot.getRects()).toEqual(expectedDisplayRects); 336 }); 337 338 it('handles z-order paths with different lengths', () => { 339 const hierarchyRoot = new HierarchyTreeBuilder() 340 .setId('LayerTraceEntry') 341 .setName('root') 342 .setChildren([ 343 { 344 id: 1, 345 name: 'layer1', 346 properties: { 347 id: 1, 348 name: 'layer1', 349 cornerRadius: 0, 350 layerStack: 0, 351 bounds: {left: 0, top: 0, right: 1, bottom: 1}, 352 zOrderPath: [0, 1], 353 isComputedVisible: true, 354 color: {r: 0, g: 0, b: 0, a: 1}, 355 transform: Transform.EMPTY, 356 } as android.surfaceflinger.ILayerProto, 357 }, 358 { 359 id: 2, 360 name: 'layer2', 361 properties: { 362 id: 2, 363 name: 'layer2', 364 cornerRadius: 0, 365 layerStack: 0, 366 bounds: {left: 0, top: 0, right: 2, bottom: 2}, 367 zOrderPath: [0, 0, 0], 368 isComputedVisible: true, 369 color: {r: 0, g: 0, b: 0, a: 1}, 370 transform: Transform.EMPTY, 371 } as android.surfaceflinger.ILayerProto, 372 }, 373 ]) 374 .build(); 375 376 const expectedRects: TraceRect[] = [ 377 new TraceRectBuilder() 378 .setX(0) 379 .setY(0) 380 .setWidth(1) 381 .setHeight(1) 382 .setId('1 layer1') 383 .setName('layer1') 384 .setCornerRadius(0) 385 .setTransform(Transform.EMPTY.matrix) 386 .setDepth(1) 387 .setGroupId(0) 388 .setIsVisible(true) 389 .setOpacity(1) 390 .setIsDisplay(false) 391 .setIsVirtual(false) 392 .build(), 393 394 new TraceRectBuilder() 395 .setX(0) 396 .setY(0) 397 .setWidth(2) 398 .setHeight(2) 399 .setId('2 layer2') 400 .setName('layer2') 401 .setCornerRadius(0) 402 .setTransform(Transform.EMPTY.matrix) 403 .setDepth(0) 404 .setGroupId(0) 405 .setIsVisible(true) 406 .setOpacity(1) 407 .setIsDisplay(false) 408 .setIsVirtual(false) 409 .build(), 410 ]; 411 412 computation.setRoot(hierarchyRoot).executeInPlace(); 413 414 const rects: TraceRect[] = []; 415 hierarchyRoot.forEachNodeDfs((node) => { 416 if (node.id === 'LayerTraceEntry root') { 417 return; 418 } 419 const nodeRects = node.getRects(); 420 if (nodeRects) rects.push(...nodeRects); 421 }); 422 423 expect(rects).toEqual(expectedRects); 424 }); 425 426 it('handles z-order paths with equal values (fall back to Layer ID comparison)', () => { 427 const hierarchyRoot = new HierarchyTreeBuilder() 428 .setId('LayerTraceEntry') 429 .setName('root') 430 .setChildren([ 431 { 432 id: 1, 433 name: 'layer1', 434 properties: { 435 id: 1, 436 name: 'layer1', 437 cornerRadius: 0, 438 layerStack: 0, 439 bounds: {left: 0, top: 0, right: 1, bottom: 1}, 440 zOrderPath: [0, 1], 441 isComputedVisible: true, 442 color: {r: 0, g: 0, b: 0, a: 1}, 443 transform: Transform.EMPTY, 444 } as android.surfaceflinger.ILayerProto, 445 }, 446 { 447 id: 2, 448 name: 'layer2', 449 properties: { 450 id: 2, 451 name: 'layer2', 452 cornerRadius: 0, 453 layerStack: 0, 454 bounds: {left: 0, top: 0, right: 2, bottom: 2}, 455 zOrderPath: [0, 1, 0], 456 isComputedVisible: true, 457 color: {r: 0, g: 0, b: 0, a: 1}, 458 transform: Transform.EMPTY, 459 } as android.surfaceflinger.ILayerProto, 460 }, 461 ]) 462 .build(); 463 464 const expectedRects: TraceRect[] = [ 465 new TraceRectBuilder() 466 .setX(0) 467 .setY(0) 468 .setWidth(1) 469 .setHeight(1) 470 .setId('1 layer1') 471 .setName('layer1') 472 .setCornerRadius(0) 473 .setTransform(Transform.EMPTY.matrix) 474 .setDepth(0) 475 .setGroupId(0) 476 .setIsVisible(true) 477 .setOpacity(1) 478 .setIsDisplay(false) 479 .setIsVirtual(false) 480 .build(), 481 482 new TraceRectBuilder() 483 .setX(0) 484 .setY(0) 485 .setWidth(2) 486 .setHeight(2) 487 .setId('2 layer2') 488 .setName('layer2') 489 .setCornerRadius(0) 490 .setTransform(Transform.EMPTY.matrix) 491 .setDepth(1) 492 .setGroupId(0) 493 .setIsVisible(true) 494 .setOpacity(1) 495 .setIsDisplay(false) 496 .setIsVirtual(false) 497 .build(), 498 ]; 499 500 computation.setRoot(hierarchyRoot).executeInPlace(); 501 502 const rects: TraceRect[] = []; 503 hierarchyRoot.forEachNodeDfs((node) => { 504 if (node.id === 'LayerTraceEntry root') { 505 return; 506 } 507 const nodeRects = node.getRects(); 508 if (nodeRects) rects.push(...nodeRects); 509 }); 510 511 expect(rects).toEqual(expectedRects); 512 }); 513}); 514