• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
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