Base interface and class definitions, utils classes.
TreeNode, HierarchyTreeNode, PropertyTreeNode, TraceRect
PropertiesProvider
Operation, OperationChain
TraceRect
TestNodeUtils, GeometryUtils, RawDataUtils, TransformUtils
Bug: b/311643292
Test: npm run test:unit:ci
Change-Id: I8508dff559eb8041f92982f7b74e960a5f5910ab
diff --git a/tools/winscope/src/app/components/timeline/expanded-timeline/abstract_timeline_row_component.ts b/tools/winscope/src/app/components/timeline/expanded-timeline/abstract_timeline_row_component.ts
index dd9ab56..f41a9ec 100644
--- a/tools/winscope/src/app/components/timeline/expanded-timeline/abstract_timeline_row_component.ts
+++ b/tools/winscope/src/app/components/timeline/expanded-timeline/abstract_timeline_row_component.ts
@@ -15,7 +15,7 @@
*/
import {ElementRef, EventEmitter, SimpleChanges} from '@angular/core';
-import {Point} from 'common/geometry_utils';
+import {Point} from 'common/geometry_types';
import {TraceEntry} from 'trace/trace';
import {TracePosition} from 'trace/trace_position';
import {CanvasDrawer} from './canvas_drawer';
diff --git a/tools/winscope/src/app/components/timeline/expanded-timeline/canvas_drawer.ts b/tools/winscope/src/app/components/timeline/expanded-timeline/canvas_drawer.ts
index 2190fca..d1f2efd 100644
--- a/tools/winscope/src/app/components/timeline/expanded-timeline/canvas_drawer.ts
+++ b/tools/winscope/src/app/components/timeline/expanded-timeline/canvas_drawer.ts
@@ -14,7 +14,7 @@
* limitations under the License.
*/
-import {Rect} from 'common/geometry_utils';
+import {Rect} from 'common/rect';
export class CanvasDrawer {
private canvas!: HTMLCanvasElement;
diff --git a/tools/winscope/src/app/components/timeline/expanded-timeline/canvas_drawer_test.ts b/tools/winscope/src/app/components/timeline/expanded-timeline/canvas_drawer_test.ts
index b759cec..626225f 100644
--- a/tools/winscope/src/app/components/timeline/expanded-timeline/canvas_drawer_test.ts
+++ b/tools/winscope/src/app/components/timeline/expanded-timeline/canvas_drawer_test.ts
@@ -15,6 +15,7 @@
*/
import {assertDefined} from 'common/assert_utils';
+import {Rect} from 'common/rect';
import {CanvasDrawer} from './canvas_drawer';
describe('CanvasDrawer', () => {
@@ -24,7 +25,7 @@
const canvasDrawer = new CanvasDrawer();
canvasDrawer.setCanvas(actualCanvas);
- canvasDrawer.drawRect({x: 10, y: 10, w: 10, h: 10}, '#333333', 1.0);
+ canvasDrawer.drawRect(new Rect(10, 10, 10, 10), '#333333', 1.0);
expect(pixelsAllMatch(actualCanvas, expectedCanvas)).toBeFalse();
@@ -39,7 +40,7 @@
const canvasDrawer = new CanvasDrawer();
canvasDrawer.setCanvas(actualCanvas);
- canvasDrawer.drawRect({x: 10, y: 10, w: 10, h: 10}, '#333333', 1.0);
+ canvasDrawer.drawRect(new Rect(10, 10, 10, 10), '#333333', 1.0);
const expectedCtx = assertDefined(expectedCanvas.getContext('2d'));
expectedCtx.fillStyle = '#333333';
@@ -55,7 +56,7 @@
const canvasDrawer = new CanvasDrawer();
canvasDrawer.setCanvas(actualCanvas);
- canvasDrawer.drawRect({x: 10, y: 10, w: 10, h: 10}, '#333333', 0.5);
+ canvasDrawer.drawRect(new Rect(10, 10, 10, 10), '#333333', 0.5);
const expectedCtx = assertDefined(expectedCanvas.getContext('2d'));
expectedCtx.fillStyle = 'rgba(51,51,51,0.5)';
@@ -71,7 +72,7 @@
const canvasDrawer = new CanvasDrawer();
canvasDrawer.setCanvas(actualCanvas);
- canvasDrawer.drawRectBorder({x: 10, y: 10, w: 10, h: 10});
+ canvasDrawer.drawRectBorder(new Rect(10, 10, 10, 10));
const expectedCtx = assertDefined(expectedCanvas.getContext('2d'));
@@ -93,8 +94,8 @@
const canvasDrawer = new CanvasDrawer();
canvasDrawer.setCanvas(actualCanvas);
- canvasDrawer.drawRect({x: 200, y: 200, w: 10, h: 10}, '#333333', 1.0);
- canvasDrawer.drawRect({x: 95, y: 95, w: 50, h: 50}, '#333333', 1.0);
+ canvasDrawer.drawRect(new Rect(200, 200, 10, 10), '#333333', 1.0);
+ canvasDrawer.drawRect(new Rect(95, 95, 50, 50), '#333333', 1.0);
const expectedCtx = assertDefined(expectedCanvas.getContext('2d'));
expectedCtx.fillStyle = '#333333';
diff --git a/tools/winscope/src/app/components/timeline/expanded-timeline/default_timeline_row_component.ts b/tools/winscope/src/app/components/timeline/expanded-timeline/default_timeline_row_component.ts
index 1168081..9b126bf 100644
--- a/tools/winscope/src/app/components/timeline/expanded-timeline/default_timeline_row_component.ts
+++ b/tools/winscope/src/app/components/timeline/expanded-timeline/default_timeline_row_component.ts
@@ -15,7 +15,8 @@
*/
import {Component, ElementRef, EventEmitter, Input, Output, ViewChild} from '@angular/core';
-import {GeometryUtils, Point, Rect} from 'common/geometry_utils';
+import {Point} from 'common/geometry_types';
+import {Rect} from 'common/rect';
import {TimeRange, Timestamp} from 'common/time';
import {Trace, TraceEntry} from 'trace/trace';
import {TracePosition} from 'trace/trace_position';
@@ -102,7 +103,7 @@
if (candidateEntry !== undefined) {
const timestamp = candidateEntry.getTimestamp();
const rect = this.entryRect(timestamp);
- if (GeometryUtils.isPointInRect(mousePoint, rect)) {
+ if (rect.containsPoint(mousePoint)) {
return candidateEntry;
}
}
@@ -121,12 +122,12 @@
private entryRect(entry: Timestamp, padding = 0): Rect {
const xPos = this.getXPosOf(entry);
- return {
- x: xPos + padding,
- y: padding,
- w: this.entryWidth - 2 * padding,
- h: this.entryWidth - 2 * padding,
- };
+ return new Rect(
+ xPos + padding,
+ padding,
+ this.entryWidth - 2 * padding,
+ this.entryWidth - 2 * padding
+ );
}
private getXPosOf(entry: Timestamp): number {
diff --git a/tools/winscope/src/app/components/timeline/expanded-timeline/default_timeline_row_component_test.ts b/tools/winscope/src/app/components/timeline/expanded-timeline/default_timeline_row_component_test.ts
index 6b3fd40..74332d5 100644
--- a/tools/winscope/src/app/components/timeline/expanded-timeline/default_timeline_row_component_test.ts
+++ b/tools/winscope/src/app/components/timeline/expanded-timeline/default_timeline_row_component_test.ts
@@ -26,6 +26,7 @@
import {MatTooltipModule} from '@angular/material/tooltip';
import {BrowserAnimationsModule} from '@angular/platform-browser/animations';
import {assertDefined} from 'common/assert_utils';
+import {Rect} from 'common/rect';
import {RealTimestamp} from 'common/time';
import {TraceBuilder} from 'test/unit/trace_builder';
import {waitToBeCalled} from 'test/utils';
@@ -81,43 +82,19 @@
const canvasWidth = component.canvasDrawer.getScaledCanvasWidth() - width;
expect(drawRectSpy).toHaveBeenCalledTimes(4);
+ expect(drawRectSpy).toHaveBeenCalledWith(new Rect(0, 0, width, height), component.color, alpha);
expect(drawRectSpy).toHaveBeenCalledWith(
- {
- x: 0,
- y: 0,
- w: width,
- h: height,
- },
+ new Rect(Math.floor((canvasWidth * 2) / 100), 0, width, height),
component.color,
alpha
);
expect(drawRectSpy).toHaveBeenCalledWith(
- {
- x: Math.floor((canvasWidth * 2) / 100),
- y: 0,
- w: width,
- h: height,
- },
+ new Rect(Math.floor((canvasWidth * 5) / 100), 0, width, height),
component.color,
alpha
);
expect(drawRectSpy).toHaveBeenCalledWith(
- {
- x: Math.floor((canvasWidth * 5) / 100),
- y: 0,
- w: width,
- h: height,
- },
- component.color,
- alpha
- );
- expect(drawRectSpy).toHaveBeenCalledWith(
- {
- x: Math.floor((canvasWidth * 60) / 100),
- y: 0,
- w: width,
- h: height,
- },
+ new Rect(Math.floor((canvasWidth * 60) / 100), 0, width, height),
component.color,
alpha
);
@@ -141,12 +118,7 @@
expect(drawRectSpy).toHaveBeenCalledTimes(1);
expect(drawRectSpy).toHaveBeenCalledWith(
- {
- x: Math.floor((canvasWidth * 10) / 25),
- y: 0,
- w: width,
- h: height,
- },
+ new Rect(Math.floor((canvasWidth * 10) / 25), 0, width, height),
component.color,
alpha
);
@@ -177,24 +149,10 @@
expect(assertDefined(component.hoveringEntry).getValueNs()).toBe(10n);
expect(drawRectSpy).toHaveBeenCalledTimes(1);
- expect(drawRectSpy).toHaveBeenCalledWith(
- {
- x: 0,
- y: 0,
- w: 32,
- h: 32,
- },
- component.color,
- 1.0
- );
+ expect(drawRectSpy).toHaveBeenCalledWith(new Rect(0, 0, 32, 32), component.color, 1.0);
expect(drawRectBorderSpy).toHaveBeenCalledTimes(1);
- expect(drawRectBorderSpy).toHaveBeenCalledWith({
- x: 0,
- y: 0,
- w: 32,
- h: 32,
- });
+ expect(drawRectBorderSpy).toHaveBeenCalledWith(new Rect(0, 0, 32, 32));
});
it('can draw correct entry on click of first entry', async () => {
@@ -289,12 +247,7 @@
expectedTimestampNs
);
- const expectedRect = {
- x: xPos + 1,
- y: 1,
- w: 30,
- h: 30,
- };
+ const expectedRect = new Rect(xPos + 1, 1, 30, 30);
expect(drawRectSpy).toHaveBeenCalledTimes(rectSpyCalls);
expect(drawRectSpy).toHaveBeenCalledWith(expectedRect, component.color, 1.0);
diff --git a/tools/winscope/src/app/components/timeline/expanded-timeline/transition_timeline_component.ts b/tools/winscope/src/app/components/timeline/expanded-timeline/transition_timeline_component.ts
index 836ab44..4f7909c 100644
--- a/tools/winscope/src/app/components/timeline/expanded-timeline/transition_timeline_component.ts
+++ b/tools/winscope/src/app/components/timeline/expanded-timeline/transition_timeline_component.ts
@@ -15,7 +15,8 @@
*/
import {Component, ElementRef, EventEmitter, Input, Output, ViewChild} from '@angular/core';
-import {GeometryUtils, Point, Rect} from 'common/geometry_utils';
+import {Point} from 'common/geometry_types';
+import {Rect} from 'common/rect';
import {ElapsedTimestamp, RealTimestamp, TimeRange, Timestamp, TimestampType} from 'common/time';
import {Transition} from 'flickerlib/common';
import {Trace, TraceEntry} from 'trace/trace';
@@ -150,7 +151,7 @@
const transitionSegment = await this.getSegmentForTransition(entry);
const rowToUse = this.getRowToUseFor(entry);
const rect = this.getSegmentRect(transitionSegment.from, transitionSegment.to, rowToUse);
- if (GeometryUtils.isPointInRect(mousePoint, rect)) {
+ if (rect.containsPoint(mousePoint)) {
return entry;
}
return undefined;
@@ -206,7 +207,7 @@
const padding = 5;
const rowHeight = totalRowHeight - padding;
- return {x: xPosStart, y: borderPadding + rowToUse * totalRowHeight, w: width, h: rowHeight};
+ return new Rect(xPosStart, borderPadding + rowToUse * totalRowHeight, width, rowHeight);
}
override async drawTimeline() {
diff --git a/tools/winscope/src/app/components/timeline/expanded-timeline/transition_timeline_component_test.ts b/tools/winscope/src/app/components/timeline/expanded-timeline/transition_timeline_component_test.ts
index a5bbd3e..841d4c7 100644
--- a/tools/winscope/src/app/components/timeline/expanded-timeline/transition_timeline_component_test.ts
+++ b/tools/winscope/src/app/components/timeline/expanded-timeline/transition_timeline_component_test.ts
@@ -25,6 +25,7 @@
import {MatSelectModule} from '@angular/material/select';
import {MatTooltipModule} from '@angular/material/tooltip';
import {BrowserAnimationsModule} from '@angular/platform-browser/animations';
+import {Rect} from 'common/rect';
import {RealTimestamp} from 'common/time';
import {Transition} from 'flickerlib/common';
import {TraceBuilder} from 'test/unit/trace_builder';
@@ -96,22 +97,12 @@
expect(drawRectSpy).toHaveBeenCalledTimes(2);
expect(drawRectSpy).toHaveBeenCalledWith(
- {
- x: 0,
- y: padding,
- w: Math.floor(width / 5),
- h: oneRowHeight,
- },
+ new Rect(0, padding, Math.floor(width / 5), oneRowHeight),
component.color,
1
);
expect(drawRectSpy).toHaveBeenCalledWith(
- {
- x: Math.floor(width / 2),
- y: padding,
- w: Math.floor(width / 2),
- h: oneRowHeight,
- },
+ new Rect(Math.floor(width / 2), padding, Math.floor(width / 2), oneRowHeight),
component.color,
1
);
@@ -147,22 +138,12 @@
expect(drawRectSpy).toHaveBeenCalledTimes(2);
expect(drawRectSpy).toHaveBeenCalledWith(
- {
- x: -Math.floor(width / 10),
- y: padding,
- w: Math.floor(width / 5),
- h: oneRowHeight,
- },
+ new Rect(-Math.floor(width / 10), padding, Math.floor(width / 5), oneRowHeight),
component.color,
1
);
expect(drawRectSpy).toHaveBeenCalledWith(
- {
- x: Math.floor(width / 2),
- y: padding,
- w: Math.floor(width),
- h: oneRowHeight,
- },
+ new Rect(Math.floor(width / 2), padding, Math.floor(width), oneRowHeight),
component.color,
1
);
@@ -197,12 +178,12 @@
const oneRowHeight = oneRowTotalHeight - padding;
const width = component.canvasDrawer.getScaledCanvasWidth();
- const expectedRect = {
- x: Math.floor((width * 1) / 4),
- y: padding,
- w: Math.floor(width / 2),
- h: oneRowHeight,
- };
+ const expectedRect = new Rect(
+ Math.floor((width * 1) / 4),
+ padding,
+ Math.floor(width / 2),
+ oneRowHeight
+ );
expect(drawRectSpy).toHaveBeenCalledTimes(2); // once drawn as a normal entry another time with rect border
expect(drawRectSpy).toHaveBeenCalledWith(expectedRect, component.color, 0.25);
@@ -244,12 +225,12 @@
await waitToBeCalled(drawRectSpy, 1);
await waitToBeCalled(drawRectBorderSpy, 1);
- const expectedRect = {
- x: Math.floor((width * 1) / 4),
- y: padding,
- w: Math.floor(width / 2),
- h: oneRowHeight,
- };
+ const expectedRect = new Rect(
+ Math.floor((width * 1) / 4),
+ padding,
+ Math.floor(width / 2),
+ oneRowHeight
+ );
expect(drawRectSpy).toHaveBeenCalledTimes(1);
expect(drawRectSpy).toHaveBeenCalledWith(expectedRect, component.color, 0.25);
@@ -288,22 +269,17 @@
expect(drawRectSpy).toHaveBeenCalledTimes(2);
expect(drawRectSpy).toHaveBeenCalledWith(
- {
- x: 0,
- y: padding,
- w: Math.floor((width * 3) / 4),
- h: oneRowHeight,
- },
+ new Rect(0, padding, Math.floor((width * 3) / 4), oneRowHeight),
component.color,
1
);
expect(drawRectSpy).toHaveBeenCalledWith(
- {
- x: Math.floor(width / 2),
- y: padding + oneRowTotalHeight,
- w: Math.floor(width / 2),
- h: oneRowHeight,
- },
+ new Rect(
+ Math.floor(width / 2),
+ padding + oneRowTotalHeight,
+ Math.floor(width / 2),
+ oneRowHeight
+ ),
component.color,
1
);
@@ -340,22 +316,17 @@
expect(drawRectSpy).toHaveBeenCalledTimes(2);
expect(drawRectSpy).toHaveBeenCalledWith(
- {
- x: 0,
- y: padding,
- w: Math.floor((width * 3) / 4),
- h: oneRowHeight,
- },
+ new Rect(0, padding, Math.floor((width * 3) / 4), oneRowHeight),
component.color,
1
);
expect(drawRectSpy).toHaveBeenCalledWith(
- {
- x: Math.floor(width / 4),
- y: padding + oneRowTotalHeight,
- w: Math.floor(width / 4),
- h: oneRowHeight,
- },
+ new Rect(
+ Math.floor(width / 4),
+ padding + oneRowTotalHeight,
+ Math.floor(width / 4),
+ oneRowHeight
+ ),
component.color,
1
);
@@ -388,12 +359,7 @@
expect(drawRectSpy).toHaveBeenCalledTimes(1);
expect(drawRectSpy).toHaveBeenCalledWith(
- {
- x: Math.floor((width * 1) / 4),
- y: padding,
- w: Math.floor(width / 2),
- h: oneRowHeight,
- },
+ new Rect(Math.floor((width * 1) / 4), padding, Math.floor(width / 2), oneRowHeight),
component.color,
0.25
);
diff --git a/tools/winscope/src/app/components/timeline/mini-timeline/drawer/canvas_mouse_handler_impl.ts b/tools/winscope/src/app/components/timeline/mini-timeline/drawer/canvas_mouse_handler_impl.ts
index dccdd8d..28faa91 100644
--- a/tools/winscope/src/app/components/timeline/mini-timeline/drawer/canvas_mouse_handler_impl.ts
+++ b/tools/winscope/src/app/components/timeline/mini-timeline/drawer/canvas_mouse_handler_impl.ts
@@ -15,7 +15,7 @@
*/
import {assertDefined} from 'common/assert_utils';
-import {Point} from 'common/geometry_utils';
+import {Point} from 'common/geometry_types';
import {CanvasMouseHandler, DragListener, DropListener} from './canvas_mouse_handler';
import {DraggableCanvasObject} from './draggable_canvas_object';
import {MiniTimelineDrawer} from './mini_timeline_drawer';
diff --git a/tools/winscope/src/app/components/timeline/mini-timeline/drawer/mini_timeline_drawer_impl.ts b/tools/winscope/src/app/components/timeline/mini-timeline/drawer/mini_timeline_drawer_impl.ts
index e739128..b0d4995 100644
--- a/tools/winscope/src/app/components/timeline/mini-timeline/drawer/mini_timeline_drawer_impl.ts
+++ b/tools/winscope/src/app/components/timeline/mini-timeline/drawer/mini_timeline_drawer_impl.ts
@@ -16,7 +16,7 @@
import {Color} from 'app/colors';
import {TRACE_INFO} from 'app/trace_info';
-import {Point} from 'common/geometry_utils';
+import {Point} from 'common/geometry_types';
import {Padding} from 'common/padding';
import {Timestamp} from 'common/time';
import {CanvasMouseHandler} from './canvas_mouse_handler';
diff --git a/tools/winscope/src/app/components/timeline/mini-timeline/slider_component.ts b/tools/winscope/src/app/components/timeline/mini-timeline/slider_component.ts
index 5badc62..1c46105 100644
--- a/tools/winscope/src/app/components/timeline/mini-timeline/slider_component.ts
+++ b/tools/winscope/src/app/components/timeline/mini-timeline/slider_component.ts
@@ -28,7 +28,7 @@
} from '@angular/core';
import {Color} from 'app/colors';
import {assertDefined} from 'common/assert_utils';
-import {Point} from 'common/geometry_utils';
+import {Point} from 'common/geometry_types';
import {TimeRange, Timestamp} from 'common/time';
import {TracePosition} from 'trace/trace_position';
import {Transformer} from './transformer';
diff --git a/tools/winscope/src/common/geometry_utils.ts b/tools/winscope/src/common/geometry_types.ts
similarity index 73%
rename from tools/winscope/src/common/geometry_utils.ts
rename to tools/winscope/src/common/geometry_types.ts
index d426d18..c485ce9 100644
--- a/tools/winscope/src/common/geometry_utils.ts
+++ b/tools/winscope/src/common/geometry_types.ts
@@ -19,13 +19,6 @@
y: number;
}
-export interface Rect {
- x: number;
- y: number;
- w: number;
- h: number;
-}
-
export interface TransformMatrix {
dsdx: number;
dtdx: number;
@@ -36,14 +29,3 @@
}
export const IDENTITY_MATRIX = {dsdx: 1, dtdx: 0, tx: 0, dsdy: 0, dtdy: 1, ty: 0};
-
-export class GeometryUtils {
- static isPointInRect(point: Point, rect: Rect): boolean {
- return (
- rect.x <= point.x &&
- point.x <= rect.x + rect.w &&
- rect.y <= point.y &&
- point.y <= rect.y + rect.h
- );
- }
-}
diff --git a/tools/winscope/src/common/rect.ts b/tools/winscope/src/common/rect.ts
new file mode 100644
index 0000000..e703c16
--- /dev/null
+++ b/tools/winscope/src/common/rect.ts
@@ -0,0 +1,93 @@
+/*
+ * Copyright (C) 2024 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+import {Point} from 'common/geometry_types';
+import {PropertyTreeNode} from 'trace/tree_node/property_tree_node';
+
+export class Rect {
+ constructor(public x: number, public y: number, public w: number, public h: number) {}
+
+ static from(node: PropertyTreeNode): Rect {
+ const left = node.getChildByName('left')?.getValue() ?? 0;
+ const top = node.getChildByName('top')?.getValue() ?? 0;
+ const right = node.getChildByName('right')?.getValue() ?? 0;
+ const bottom = node.getChildByName('bottom')?.getValue() ?? 0;
+ return new Rect(left, top, right - left, bottom - top);
+ }
+
+ containsPoint(point: Point): boolean {
+ return (
+ this.x <= point.x &&
+ point.x <= this.x + this.w &&
+ this.y <= point.y &&
+ point.y <= this.y + this.h
+ );
+ }
+
+ cropRect(other: Rect): Rect {
+ const maxLeft = Math.max(this.x, other.x);
+ const minRight = Math.min(this.x + this.w, other.x + other.w);
+ const maxTop = Math.max(this.y, other.y);
+ const minBottom = Math.min(this.y + this.h, other.y + other.h);
+ return new Rect(maxLeft, maxTop, minRight - maxLeft, minBottom - maxTop);
+ }
+
+ containsRect(other: Rect): boolean {
+ return (
+ this.w > 0 &&
+ this.h > 0 &&
+ this.x <= other.x &&
+ this.y <= other.y &&
+ this.x + this.w >= other.x + other.w &&
+ this.y + this.h >= other.y + other.h
+ );
+ }
+
+ intersectsRect(other: Rect): boolean {
+ if (
+ this.x < other.x + other.w &&
+ other.x < this.x + this.w &&
+ this.y <= other.y + other.h &&
+ other.y <= this.y + this.h
+ ) {
+ const intersectionRect = new Rect(this.x, this.y, this.w, this.h);
+
+ if (this.x < other.x) {
+ intersectionRect.x = other.x;
+ }
+ if (this.y < other.y) {
+ intersectionRect.y = other.y;
+ }
+ if (this.x + this.w > other.x + other.w) {
+ intersectionRect.w = other.w;
+ }
+ if (this.y + this.h > other.y + other.h) {
+ intersectionRect.h = other.h;
+ }
+
+ return !intersectionRect.isEmpty();
+ }
+
+ return false;
+ }
+
+ isEmpty(): boolean {
+ const [x, y, w, h] = [this.x, this.y, this.w, this.h];
+ const nullValuePresent = x === -1 || y === -1 || x + w === -1 || y + h === -1;
+ const nullHeightOrWidth = w <= 0 || h <= 0;
+ return nullValuePresent || nullHeightOrWidth;
+ }
+}
diff --git a/tools/winscope/src/parsers/parser_factory.ts b/tools/winscope/src/parsers/parser_factory.ts
index f262239..4bd566b 100644
--- a/tools/winscope/src/parsers/parser_factory.ts
+++ b/tools/winscope/src/parsers/parser_factory.ts
@@ -27,13 +27,13 @@
import {ParserProtoLog} from './parser_protolog';
import {ParserScreenRecording} from './parser_screen_recording';
import {ParserScreenRecordingLegacy} from './parser_screen_recording_legacy';
-import {ParserSurfaceFlinger} from './parser_surface_flinger';
import {ParserTransactions} from './parser_transactions';
import {ParserTransitionsShell} from './parser_transitions_shell';
import {ParserTransitionsWm} from './parser_transitions_wm';
import {ParserViewCapture} from './parser_view_capture';
import {ParserWindowManager} from './parser_window_manager';
import {ParserWindowManagerDump} from './parser_window_manager_dump';
+import {ParserSurfaceFlinger} from './surface_flinger/parser_surface_flinger';
export class ParserFactory {
static readonly PARSERS = [
diff --git a/tools/winscope/src/parsers/raw_data_utils.ts b/tools/winscope/src/parsers/raw_data_utils.ts
new file mode 100644
index 0000000..819a887
--- /dev/null
+++ b/tools/winscope/src/parsers/raw_data_utils.ts
@@ -0,0 +1,80 @@
+/*
+ * Copyright (C) 2024 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+import {Rect} from 'common/rect';
+import {PropertyTreeNode} from 'trace/tree_node/property_tree_node';
+
+export class RawDataUtils {
+ static isEmptyObj(obj: PropertyTreeNode): boolean {
+ if (RawDataUtils.isColor(obj)) {
+ return RawDataUtils.isEmptyColor(obj);
+ }
+
+ if (RawDataUtils.isRect(obj)) {
+ return Rect.from(obj).isEmpty();
+ }
+
+ return false;
+ }
+
+ static isColor(obj: PropertyTreeNode): boolean {
+ return obj.getChildByName('a') !== undefined;
+ }
+
+ static isRect(obj: PropertyTreeNode): boolean {
+ return (
+ (obj.getChildByName('right') !== undefined && obj.getChildByName('bottom') !== undefined) ||
+ (obj.getChildByName('left') !== undefined && obj.getChildByName('top') !== undefined)
+ );
+ }
+
+ static isBuffer(obj: PropertyTreeNode): boolean {
+ return obj.getChildByName('stride') !== undefined && obj.getChildByName('format') !== undefined;
+ }
+
+ static isSize(obj: PropertyTreeNode): boolean {
+ return (
+ obj.getAllChildren().length <= 2 &&
+ (obj.getChildByName('w') !== undefined || obj.getChildByName('h') !== undefined)
+ );
+ }
+
+ static isPosition(obj: PropertyTreeNode): boolean {
+ return (
+ obj.getAllChildren().length <= 2 &&
+ (obj.getChildByName('x') !== undefined || obj.getChildByName('y') !== undefined)
+ );
+ }
+
+ static isRegion(obj: PropertyTreeNode): boolean {
+ const rect = obj.getChildByName('rect');
+ return (
+ rect !== undefined &&
+ rect.getAllChildren().every((innerRect: PropertyTreeNode) => RawDataUtils.isRect(innerRect))
+ );
+ }
+
+ private static isEmptyColor(color: PropertyTreeNode): boolean {
+ const [r, g, b, a] = [
+ color.getChildByName('r')?.getValue() ?? 0,
+ color.getChildByName('g')?.getValue() ?? 0,
+ color.getChildByName('b')?.getValue() ?? 0,
+ color.getChildByName('a')?.getValue() ?? 0,
+ ];
+ if (a === 0) return true;
+ return r < 0 || g < 0 || b < 0 || (r === 0 && g === 0 && b === 0);
+ }
+}
diff --git a/tools/winscope/src/parsers/raw_data_utils_test.ts b/tools/winscope/src/parsers/raw_data_utils_test.ts
new file mode 100644
index 0000000..b64951a
--- /dev/null
+++ b/tools/winscope/src/parsers/raw_data_utils_test.ts
@@ -0,0 +1,113 @@
+/*
+ * Copyright (C) 2024 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+import {TreeNodeUtils} from 'test/unit/tree_node_utils';
+import {PropertySource, PropertyTreeNode} from 'trace/tree_node/property_tree_node';
+import {RawDataUtils} from './raw_data_utils';
+
+describe('RawDataUtils', () => {
+ it('identifies color', () => {
+ const color = TreeNodeUtils.makeColorNode(0, 0, 0, 1);
+ expect(RawDataUtils.isColor(color)).toBeTrue();
+
+ const colorOnlyA = TreeNodeUtils.makeColorNode(undefined, undefined, undefined, 1);
+ expect(RawDataUtils.isColor(colorOnlyA)).toBeTrue();
+ });
+
+ it('identifies rect', () => {
+ const rect = TreeNodeUtils.makeRectNode(0, 0, 1, 1);
+ expect(RawDataUtils.isRect(rect)).toBeTrue();
+
+ const rectLeftTop = TreeNodeUtils.makeRectNode(0, 0, undefined, undefined);
+ expect(RawDataUtils.isRect(rectLeftTop)).toBeTrue();
+
+ const rectRightBottom = TreeNodeUtils.makeRectNode(undefined, undefined, 1, 1);
+ expect(RawDataUtils.isRect(rectRightBottom)).toBeTrue();
+ });
+
+ it('identifies buffer', () => {
+ const buffer = TreeNodeUtils.makeBufferNode();
+ expect(RawDataUtils.isBuffer(buffer)).toBeTrue();
+ });
+
+ it('identifies size', () => {
+ const size = TreeNodeUtils.makeSizeNode(0, 0);
+ expect(RawDataUtils.isSize(size)).toBeTrue();
+ expect(RawDataUtils.isBuffer(size)).toBeFalse();
+
+ const sizeOnlyW = TreeNodeUtils.makeSizeNode(0, undefined);
+ expect(RawDataUtils.isSize(sizeOnlyW)).toBeTrue();
+
+ const sizeOnlyH = TreeNodeUtils.makeSizeNode(undefined, 0);
+ expect(RawDataUtils.isSize(sizeOnlyH)).toBeTrue();
+
+ const notSize = TreeNodeUtils.makeSizeNode(0, 0);
+ notSize.addChild(new PropertyTreeNode('size.x', 'x', PropertySource.PROTO, 0));
+ notSize.addChild(new PropertyTreeNode('size.y', 'y', PropertySource.PROTO, 0));
+ expect(RawDataUtils.isSize(notSize)).toBeFalse();
+ });
+
+ it('identifies position', () => {
+ const pos = TreeNodeUtils.makePositionNode(0, 0);
+ expect(RawDataUtils.isPosition(pos)).toBeTrue();
+ expect(RawDataUtils.isRect(pos)).toBeFalse();
+
+ const posOnlyX = TreeNodeUtils.makePositionNode(0, undefined);
+ expect(RawDataUtils.isPosition(posOnlyX)).toBeTrue();
+
+ const posOnlyY = TreeNodeUtils.makePositionNode(undefined, 0);
+ expect(RawDataUtils.isPosition(posOnlyY)).toBeTrue();
+
+ const notPos = TreeNodeUtils.makePositionNode(0, 0);
+ notPos.addChild(new PropertyTreeNode('pos.w', 'w', PropertySource.PROTO, 0));
+ notPos.addChild(new PropertyTreeNode('pos.h', 'h', PropertySource.PROTO, 0));
+ expect(RawDataUtils.isPosition(notPos)).toBeFalse();
+ });
+
+ it('identifies region', () => {
+ const region = new PropertyTreeNode('region', 'region', PropertySource.PROTO, undefined);
+ const rect = new PropertyTreeNode('region.rect', 'rect', PropertySource.PROTO, []);
+ region.addChild(rect);
+ expect(RawDataUtils.isRegion(region)).toBeTrue();
+
+ rect.addChild(TreeNodeUtils.makeRectNode(0, 0, 1, 1));
+ rect.addChild(TreeNodeUtils.makeRectNode(0, 0, undefined, undefined));
+ rect.addChild(TreeNodeUtils.makeRectNode(undefined, undefined, 1, 1));
+ expect(RawDataUtils.isRegion(region)).toBeTrue();
+ });
+
+ it('identifies non-empty color and rect', () => {
+ const color = TreeNodeUtils.makeColorNode(0, 8, 0, 1);
+ const rect = TreeNodeUtils.makeRectNode(0, 0, 1, 1);
+
+ const isEmptyColor = RawDataUtils.isEmptyObj(color);
+ const isEmptyRect = RawDataUtils.isEmptyObj(rect);
+ expect(isEmptyColor).toBeFalse();
+ expect(isEmptyRect).toBeFalse();
+ });
+
+ it('identifies empty color and rect', () => {
+ const color = TreeNodeUtils.makeColorNode(-1, -1, undefined, 1);
+ const rect = TreeNodeUtils.makeRectNode(0, 0, undefined, undefined);
+ const otherColor = TreeNodeUtils.makeColorNode(1, 1, 1, 0);
+ const otherRect = TreeNodeUtils.makeRectNode(0, 0, 0, 0);
+
+ expect(RawDataUtils.isEmptyObj(color)).toBeTrue();
+ expect(RawDataUtils.isEmptyObj(rect)).toBeTrue();
+ expect(RawDataUtils.isEmptyObj(otherColor)).toBeTrue();
+ expect(RawDataUtils.isEmptyObj(otherRect)).toBeTrue();
+ });
+});
diff --git a/tools/winscope/src/parsers/parser_surface_flinger.ts b/tools/winscope/src/parsers/surface_flinger/parser_surface_flinger.ts
similarity index 98%
rename from tools/winscope/src/parsers/parser_surface_flinger.ts
rename to tools/winscope/src/parsers/surface_flinger/parser_surface_flinger.ts
index 6d73ef8..be22699 100644
--- a/tools/winscope/src/parsers/parser_surface_flinger.ts
+++ b/tools/winscope/src/parsers/surface_flinger/parser_surface_flinger.ts
@@ -17,6 +17,7 @@
import {assertDefined} from 'common/assert_utils';
import {Timestamp, TimestampType} from 'common/time';
import {LayerTraceEntry} from 'flickerlib/layers/LayerTraceEntry';
+import {AbstractParser} from 'parsers/abstract_parser';
import root from 'protos/surfaceflinger/udc/json';
import {android} from 'protos/surfaceflinger/udc/static';
import {
@@ -27,7 +28,6 @@
import {EntriesRange} from 'trace/trace';
import {TraceFile} from 'trace/trace_file';
import {TraceType} from 'trace/trace_type';
-import {AbstractParser} from './abstract_parser';
class ParserSurfaceFlinger extends AbstractParser {
private static readonly LayersTraceFileProto = root.lookupType(
diff --git a/tools/winscope/src/parsers/parser_surface_flinger_dump_test.ts b/tools/winscope/src/parsers/surface_flinger/parser_surface_flinger_dump_test.ts
similarity index 100%
rename from tools/winscope/src/parsers/parser_surface_flinger_dump_test.ts
rename to tools/winscope/src/parsers/surface_flinger/parser_surface_flinger_dump_test.ts
diff --git a/tools/winscope/src/parsers/parser_surface_flinger_test.ts b/tools/winscope/src/parsers/surface_flinger/parser_surface_flinger_test.ts
similarity index 100%
rename from tools/winscope/src/parsers/parser_surface_flinger_test.ts
rename to tools/winscope/src/parsers/surface_flinger/parser_surface_flinger_test.ts
diff --git a/tools/winscope/src/parsers/surface_flinger/transform_utils.ts b/tools/winscope/src/parsers/surface_flinger/transform_utils.ts
new file mode 100644
index 0000000..d32220b
--- /dev/null
+++ b/tools/winscope/src/parsers/surface_flinger/transform_utils.ts
@@ -0,0 +1,184 @@
+/*
+ * Copyright (C) 2024 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+import {assertDefined} from 'common/assert_utils';
+import {IDENTITY_MATRIX, TransformMatrix} from 'common/geometry_types';
+import {PropertyTreeNode} from 'trace/tree_node/property_tree_node';
+
+export enum TransformType {
+ EMPTY = 0x0,
+ TRANSLATE_VAL = 0x0001,
+ ROTATE_VAL = 0x0002,
+ SCALE_VAL = 0x0004,
+ FLIP_H_VAL = 0x0100,
+ FLIP_V_VAL = 0x0200,
+ ROT_90_VAL = 0x0400,
+ ROT_INVALID_VAL = 0x8000,
+}
+
+export class Transform {
+ static EMPTY = new Transform(TransformType.EMPTY, IDENTITY_MATRIX);
+
+ constructor(public type: TransformType, public matrix: TransformMatrix) {}
+
+ static from(transformNode: PropertyTreeNode, position?: PropertyTreeNode): Transform {
+ if (transformNode.getAllChildren().length === 0) return Transform.EMPTY;
+
+ const transformType = transformNode.getChildByName('type')?.getValue() ?? 0;
+ const matrixNode = transformNode.getChildByName('matrix');
+
+ if (matrixNode) {
+ return new Transform(transformType, {
+ dsdx: assertDefined(matrixNode.getChildByName('dsdx')).getValue(),
+ dtdx: assertDefined(matrixNode.getChildByName('dtdx')).getValue(),
+ tx: assertDefined(matrixNode.getChildByName('tx')).getValue(),
+ dsdy: assertDefined(matrixNode.getChildByName('dsdy')).getValue(),
+ dtdy: assertDefined(matrixNode.getChildByName('dtdy')).getValue(),
+ ty: assertDefined(matrixNode.getChildByName('ty')).getValue(),
+ });
+ }
+
+ const x = position?.getChildByName('x')?.getValue() ?? 0;
+ const y = position?.getChildByName('y')?.getValue() ?? 0;
+
+ if (TransformUtils.isSimpleTransform(transformType)) {
+ return TransformUtils.getDefaultTransform(transformType, x, y);
+ }
+
+ return new Transform(transformType, {
+ dsdx: transformNode.getChildByName('dsdx')?.getValue() ?? 0,
+ dtdx: transformNode.getChildByName('dtdx')?.getValue() ?? 0,
+ tx: x,
+ dsdy: transformNode.getChildByName('dsdy')?.getValue() ?? 0,
+ dtdy: transformNode.getChildByName('dtdy')?.getValue() ?? 0,
+ ty: y,
+ });
+ }
+}
+
+export class TransformUtils {
+ static isValidTransform(transform: Transform): boolean {
+ return (
+ transform.matrix.dsdx * transform.matrix.dtdy !==
+ transform.matrix.dtdx * transform.matrix.dsdy
+ );
+ }
+
+ static isSimpleRotation(type: TransformType | undefined): boolean {
+ return !(type ? TransformUtils.isFlagSet(type, TransformType.ROT_INVALID_VAL) : false);
+ }
+
+ static getTypeFlags(type: TransformType): string {
+ const typeFlags: string[] = [];
+
+ if (
+ TransformUtils.isFlagClear(
+ type,
+ TransformType.SCALE_VAL | TransformType.ROTATE_VAL | TransformType.TRANSLATE_VAL
+ )
+ ) {
+ typeFlags.push('IDENTITY');
+ }
+
+ if (TransformUtils.isFlagSet(type, TransformType.SCALE_VAL)) {
+ typeFlags.push('SCALE');
+ }
+
+ if (TransformUtils.isFlagSet(type, TransformType.TRANSLATE_VAL)) {
+ typeFlags.push('TRANSLATE');
+ }
+
+ if (TransformUtils.isFlagSet(type, TransformType.ROT_INVALID_VAL)) {
+ typeFlags.push('ROT_INVALID');
+ } else if (
+ TransformUtils.isFlagSet(
+ type,
+ TransformType.ROT_90_VAL | TransformType.FLIP_V_VAL | TransformType.FLIP_H_VAL
+ )
+ ) {
+ typeFlags.push('ROT_270');
+ } else if (
+ TransformUtils.isFlagSet(type, TransformType.FLIP_V_VAL | TransformType.FLIP_H_VAL)
+ ) {
+ typeFlags.push('ROT_180');
+ } else {
+ if (TransformUtils.isFlagSet(type, TransformType.ROT_90_VAL)) {
+ typeFlags.push('ROT_90');
+ }
+ if (TransformUtils.isFlagSet(type, TransformType.FLIP_V_VAL)) {
+ typeFlags.push('FLIP_V');
+ }
+ if (TransformUtils.isFlagSet(type, TransformType.FLIP_H_VAL)) {
+ typeFlags.push('FLIP_H');
+ }
+ }
+
+ if (typeFlags.length === 0) {
+ throw Error(`Unknown transform type ${type}`);
+ }
+ return typeFlags.join('|');
+ }
+
+ static getDefaultTransform(type: TransformType, x: number, y: number): Transform {
+ // IDENTITY
+ if (!type) {
+ return new Transform(type, {dsdx: 1, dtdx: 0, tx: x, dsdy: 0, dtdy: 1, ty: y});
+ }
+
+ // ROT_270 = ROT_90|FLIP_H|FLIP_V
+ if (
+ TransformUtils.isFlagSet(
+ type,
+ TransformType.ROT_90_VAL | TransformType.FLIP_V_VAL | TransformType.FLIP_H_VAL
+ )
+ ) {
+ return new Transform(type, {dsdx: 0, dtdx: -1, tx: x, dsdy: 1, dtdy: 0, ty: y});
+ }
+
+ // ROT_180 = FLIP_H|FLIP_V
+ if (TransformUtils.isFlagSet(type, TransformType.FLIP_V_VAL | TransformType.FLIP_H_VAL)) {
+ return new Transform(type, {dsdx: -1, dtdx: 0, tx: x, dsdy: 0, dtdy: -1, ty: y});
+ }
+
+ // ROT_90
+ if (TransformUtils.isFlagSet(type, TransformType.ROT_90_VAL)) {
+ return new Transform(type, {dsdx: 0, dtdx: 1, tx: x, dsdy: -1, dtdy: 0, ty: y});
+ }
+
+ // IDENTITY
+ if (TransformUtils.isFlagClear(type, TransformType.SCALE_VAL | TransformType.ROTATE_VAL)) {
+ return new Transform(type, {dsdx: 1, dtdx: 0, tx: x, dsdy: 0, dtdy: 1, ty: y});
+ }
+
+ throw new Error(`Unknown transform type ${type}`);
+ }
+
+ static isSimpleTransform(type: TransformType): boolean {
+ return TransformUtils.isFlagClear(
+ type,
+ TransformType.ROT_INVALID_VAL | TransformType.SCALE_VAL
+ );
+ }
+
+ private static isFlagSet(type: TransformType, bits: number): boolean {
+ type = type || 0;
+ return (type & bits) === bits;
+ }
+
+ private static isFlagClear(type: TransformType, bits: number): boolean {
+ return (type & bits) === 0;
+ }
+}
diff --git a/tools/winscope/src/parsers/transform_utils.ts b/tools/winscope/src/parsers/transform_utils.ts
deleted file mode 100644
index 6a98c7e..0000000
--- a/tools/winscope/src/parsers/transform_utils.ts
+++ /dev/null
@@ -1,116 +0,0 @@
-/*
- * Copyright (C) 2023 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- * http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-import {IDENTITY_MATRIX, TransformMatrix} from 'common/geometry_utils';
-
-export class Transform {
- constructor(public type: TransformType, public matrix: TransformMatrix) {}
-}
-
-export enum TransformType {
- EMPTY = 0x0,
- TRANSLATE_VAL = 0x0001,
- ROTATE_VAL = 0x0002,
- SCALE_VAL = 0x0004,
- FLIP_H_VAL = 0x0100,
- FLIP_V_VAL = 0x0200,
- ROT_90_VAL = 0x0400,
- ROT_INVALID_VAL = 0x8000,
-}
-
-export const EMPTY_TRANSFORM = new Transform(TransformType.EMPTY, IDENTITY_MATRIX);
-
-export class TransformUtils {
- static isValidTransform(transform: any): boolean {
- if (!transform) return false;
- return transform.dsdx * transform.dtdy !== transform.dtdx * transform.dsdy;
- }
-
- static getTransform(transform: any, position: any): Transform {
- const transformType = transform?.type ?? 0;
- const x = position?.x ?? 0;
- const y = position?.y ?? 0;
-
- if (!transform || TransformUtils.isSimpleTransform(transformType)) {
- return TransformUtils.getDefaultTransform(transformType, x, y);
- }
-
- return new Transform(transformType, {
- dsdx: transform?.matrix.dsdx ?? 0,
- dtdx: transform?.matrix.dtdx ?? 0,
- tx: x,
- dsdy: transform?.matrix.dsdy ?? 0,
- dtdy: transform?.matrix.dtdy ?? 0,
- ty: y,
- });
- }
-
- static isSimpleRotation(transform: any): boolean {
- return !(transform?.type
- ? TransformUtils.isFlagSet(transform.type, TransformType.ROT_INVALID_VAL)
- : false);
- }
-
- private static getDefaultTransform(type: TransformType, x: number, y: number): Transform {
- // IDENTITY
- if (!type) {
- return new Transform(type, {dsdx: 1, dtdx: 0, tx: x, dsdy: 0, dtdy: 1, ty: y});
- }
-
- // ROT_270 = ROT_90|FLIP_H|FLIP_V
- if (
- TransformUtils.isFlagSet(
- type,
- TransformType.ROT_90_VAL | TransformType.FLIP_V_VAL | TransformType.FLIP_H_VAL
- )
- ) {
- return new Transform(type, {dsdx: 0, dtdx: -1, tx: x, dsdy: 1, dtdy: 0, ty: y});
- }
-
- // ROT_180 = FLIP_H|FLIP_V
- if (TransformUtils.isFlagSet(type, TransformType.FLIP_V_VAL | TransformType.FLIP_H_VAL)) {
- return new Transform(type, {dsdx: -1, dtdx: 0, tx: x, dsdy: 0, dtdy: -1, ty: y});
- }
-
- // ROT_90
- if (TransformUtils.isFlagSet(type, TransformType.ROT_90_VAL)) {
- return new Transform(type, {dsdx: 0, dtdx: 1, tx: x, dsdy: -1, dtdy: 0, ty: y});
- }
-
- // IDENTITY
- if (TransformUtils.isFlagClear(type, TransformType.SCALE_VAL | TransformType.ROTATE_VAL)) {
- return new Transform(type, {dsdx: 1, dtdx: 0, tx: x, dsdy: 0, dtdy: 1, ty: y});
- }
-
- throw new Error(`Unknown transform type ${type}`);
- }
-
- private static isFlagSet(type: number, bits: number): boolean {
- type = type || 0;
- return (type & bits) === bits;
- }
-
- private static isFlagClear(type: number, bits: number): boolean {
- return (type & bits) === 0;
- }
-
- private static isSimpleTransform(type: number): boolean {
- return TransformUtils.isFlagClear(
- type,
- TransformType.ROT_INVALID_VAL | TransformType.SCALE_VAL
- );
- }
-}
diff --git a/tools/winscope/src/test/unit/tree_node_utils.ts b/tools/winscope/src/test/unit/tree_node_utils.ts
new file mode 100644
index 0000000..acab913
--- /dev/null
+++ b/tools/winscope/src/test/unit/tree_node_utils.ts
@@ -0,0 +1,124 @@
+/*
+ * Copyright (C) 2024 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+import {TransformType} from 'parsers/surface_flinger/transform_utils';
+import {PropertySource, PropertyTreeNode} from 'trace/tree_node/property_tree_node';
+
+export class TreeNodeUtils {
+ static makeRectNode(
+ left: number | undefined,
+ top: number | undefined,
+ right: number | undefined,
+ bottom: number | undefined,
+ id = 'test node'
+ ): PropertyTreeNode {
+ const rect = new PropertyTreeNode(`${id}.rect`, 'rect', PropertySource.PROTO, undefined);
+ if (left !== undefined) {
+ rect.addChild(new PropertyTreeNode(`${id}.rect.left`, 'left', PropertySource.PROTO, left));
+ }
+ if (top !== undefined) {
+ rect.addChild(new PropertyTreeNode(`${id}.rect.top`, 'top', PropertySource.PROTO, top));
+ }
+ if (right !== undefined) {
+ rect.addChild(new PropertyTreeNode(`${id}.rect.right`, 'right', PropertySource.PROTO, right));
+ }
+ if (bottom !== undefined) {
+ rect.addChild(
+ new PropertyTreeNode(`${id}.rect.bottom`, 'bottom', PropertySource.PROTO, bottom)
+ );
+ }
+ return rect;
+ }
+
+ static makeColorNode(
+ r: number | undefined,
+ g: number | undefined,
+ b: number | undefined,
+ a: number | undefined
+ ): PropertyTreeNode {
+ const color = new PropertyTreeNode('test node.color', 'color', PropertySource.PROTO, undefined);
+ if (r !== undefined) {
+ color.addChild(new PropertyTreeNode('test node.color.r', 'r', PropertySource.PROTO, r));
+ }
+ if (g !== undefined) {
+ color.addChild(new PropertyTreeNode('test node.color.g', 'g', PropertySource.PROTO, g));
+ }
+ if (b !== undefined) {
+ color.addChild(new PropertyTreeNode('test node.color.b', 'b', PropertySource.PROTO, b));
+ }
+ if (a !== undefined) {
+ color.addChild(new PropertyTreeNode('test node.color.a', 'a', PropertySource.PROTO, a));
+ }
+ return color;
+ }
+
+ static makeBufferNode(): PropertyTreeNode {
+ const buffer = new PropertyTreeNode(
+ 'test node.buffer',
+ 'buffer',
+ PropertySource.PROTO,
+ undefined
+ );
+ buffer.addChild(
+ new PropertyTreeNode('test node.buffer.height', 'height', PropertySource.PROTO, 0)
+ );
+ buffer.addChild(
+ new PropertyTreeNode('test node.buffer.width', 'width', PropertySource.PROTO, 1)
+ );
+ buffer.addChild(
+ new PropertyTreeNode('test node.buffer.stride', 'stride', PropertySource.PROTO, 0)
+ );
+ buffer.addChild(
+ new PropertyTreeNode('test node.buffer.format', 'format', PropertySource.PROTO, 1)
+ );
+ return buffer;
+ }
+
+ static makeTransformNode(type: TransformType): PropertyTreeNode {
+ const transform = new PropertyTreeNode(
+ 'test node.transform',
+ 'transform',
+ PropertySource.PROTO,
+ undefined
+ );
+ transform.addChild(
+ new PropertyTreeNode('test node.transform.type', 'type', PropertySource.PROTO, type)
+ );
+ return transform;
+ }
+
+ static makeSizeNode(w: number | undefined, h: number | undefined): PropertyTreeNode {
+ const size = new PropertyTreeNode('test node.size', 'size', PropertySource.PROTO, undefined);
+ if (w !== undefined) {
+ size.addChild(new PropertyTreeNode('test node.size.w', 'w', PropertySource.PROTO, w));
+ }
+ if (h !== undefined) {
+ size.addChild(new PropertyTreeNode('test node.size.h', 'h', PropertySource.PROTO, h));
+ }
+ return size;
+ }
+
+ static makePositionNode(x: number | undefined, y: number | undefined): PropertyTreeNode {
+ const pos = new PropertyTreeNode('test node.pos', 'pos', PropertySource.PROTO, undefined);
+ if (x !== undefined) {
+ pos.addChild(new PropertyTreeNode('test node.pos.x', 'x', PropertySource.PROTO, x));
+ }
+ if (y !== undefined) {
+ pos.addChild(new PropertyTreeNode('test node.pos.y', 'y', PropertySource.PROTO, y));
+ }
+ return pos;
+ }
+}
diff --git a/tools/winscope/src/trace/item.ts b/tools/winscope/src/trace/item.ts
new file mode 100644
index 0000000..b57867f
--- /dev/null
+++ b/tools/winscope/src/trace/item.ts
@@ -0,0 +1,20 @@
+/*
+ * Copyright (C) 2024 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+export interface Item {
+ id: string;
+ name: string;
+}
diff --git a/tools/winscope/src/trace/trace_data_utils.ts b/tools/winscope/src/trace/trace_data_utils.ts
deleted file mode 100644
index a6aaff5..0000000
--- a/tools/winscope/src/trace/trace_data_utils.ts
+++ /dev/null
@@ -1,103 +0,0 @@
-/*
- * Copyright (C) 2023 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- * http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-import {Rect} from 'common/geometry_utils';
-import {Transform} from 'parsers/transform_utils';
-
-/* DATA INTERFACES */
-class Item {
- constructor(public id: string, public label: string) {}
-}
-
-class TreeNode<T> extends Item {
- constructor(id: string, label: string, public children: Array<TreeNode<T> & T> = []) {
- super(id, label);
- }
-}
-
-class TraceRect extends Item implements Rect {
- constructor(
- id: string,
- label: string,
- public x: number,
- public y: number,
- public w: number,
- public h: number,
- public cornerRadius: number,
- public transform: Transform,
- public zAbs: number,
- public groupId: number,
- public isVisible: boolean,
- public isDisplay: boolean,
- public isVirtual: boolean
- ) {
- super(id, label);
- }
-}
-
-type Proto = any;
-
-/* GET PROPERTIES TYPES */
-type GetPropertiesFromProtoType = (proto: Proto) => PropertyTreeNode;
-type GetPropertiesType = () => PropertyTreeNode;
-
-/* MIXINS */
-interface PropertiesGetter {
- getProperties: GetPropertiesType;
-}
-
-enum PropertySource {
- PROTO,
- DEFAULT,
- CALCULATED,
-}
-
-interface PropertyDetails {
- value: string;
- source: PropertySource;
-}
-
-interface AssociatedProperty {
- property: null | PropertyDetails;
-}
-
-interface Constructor<T = {}> {
- new (...args: any[]): T;
-}
-
-type PropertyTreeNode = TreeNode<AssociatedProperty> & AssociatedProperty;
-type HierarchyTreeNode = TreeNode<PropertiesGetter> & PropertiesGetter;
-
-const EMPTY_OBJ_STRING = '{empty}';
-const EMPTY_ARRAY_STRING = '[empty]';
-
-export {
- Item,
- TreeNode,
- TraceRect,
- Proto,
- GetPropertiesType,
- GetPropertiesFromProtoType,
- PropertiesGetter,
- PropertySource,
- PropertyDetails,
- AssociatedProperty,
- Constructor,
- PropertyTreeNode,
- HierarchyTreeNode,
- EMPTY_OBJ_STRING,
- EMPTY_ARRAY_STRING,
-};
diff --git a/tools/winscope/src/trace/trace_rect.ts b/tools/winscope/src/trace/trace_rect.ts
new file mode 100644
index 0000000..6928f00
--- /dev/null
+++ b/tools/winscope/src/trace/trace_rect.ts
@@ -0,0 +1,39 @@
+/*
+ * Copyright (C) 2024 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+import {TransformMatrix} from 'common/geometry_types';
+import {Rect} from 'common/rect';
+import {Item} from 'trace/item';
+
+export class TraceRect extends Rect implements Item {
+ constructor(
+ x: number,
+ y: number,
+ w: number,
+ h: number,
+ readonly id: string,
+ readonly name: string,
+ readonly cornerRadius: number,
+ readonly transform: TransformMatrix,
+ readonly zOrderPath: number[],
+ readonly groupId: number,
+ readonly isVisible: boolean,
+ readonly isDisplay: boolean,
+ readonly isVirtual: boolean
+ ) {
+ super(x, y, w, h);
+ }
+}
diff --git a/tools/winscope/src/trace/trace_rect_builder.ts b/tools/winscope/src/trace/trace_rect_builder.ts
new file mode 100644
index 0000000..becf14a
--- /dev/null
+++ b/tools/winscope/src/trace/trace_rect_builder.ts
@@ -0,0 +1,169 @@
+/*
+ * Copyright (C) 2024 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+import {TransformMatrix} from 'common/geometry_types';
+import {TraceRect} from './trace_rect';
+
+export class TraceRectBuilder {
+ x: number | undefined;
+ y: number | undefined;
+ w: number | undefined;
+ h: number | undefined;
+ id: string | undefined;
+ name: string | undefined;
+ cornerRadius: number | undefined;
+ transform: TransformMatrix | undefined;
+ zOrderPath: number[] | undefined;
+ groupId: number | undefined;
+ isVisible: boolean | undefined;
+ isDisplay: boolean | undefined;
+ isVirtual: boolean | undefined;
+
+ setX(value: number) {
+ this.x = value;
+ return this;
+ }
+
+ setY(value: number) {
+ this.y = value;
+ return this;
+ }
+
+ setWidth(value: number) {
+ this.w = value;
+ return this;
+ }
+
+ setHeight(value: number) {
+ this.h = value;
+ return this;
+ }
+
+ setId(value: string) {
+ this.id = value;
+ return this;
+ }
+
+ setName(value: string) {
+ this.name = value;
+ return this;
+ }
+
+ setCornerRadius(value: number) {
+ this.cornerRadius = value;
+ return this;
+ }
+
+ setTransform(value: TransformMatrix) {
+ this.transform = value;
+ return this;
+ }
+
+ setZOrderPath(value: number[]) {
+ this.zOrderPath = value;
+ return this;
+ }
+
+ setGroupId(value: number) {
+ this.groupId = value;
+ return this;
+ }
+
+ setIsVisible(value: boolean) {
+ this.isVisible = value;
+ return this;
+ }
+
+ setIsDisplay(value: boolean) {
+ this.isDisplay = value;
+ return this;
+ }
+
+ setIsVirtual(value: boolean) {
+ this.isVirtual = value;
+ return this;
+ }
+
+ build(): TraceRect {
+ if (this.x === undefined) {
+ throw Error('x not set');
+ }
+
+ if (this.y === undefined) {
+ throw Error('y not set');
+ }
+
+ if (this.w === undefined) {
+ throw Error('width not set');
+ }
+
+ if (this.h === undefined) {
+ throw Error('height not set');
+ }
+
+ if (this.id === undefined) {
+ throw Error('id not set');
+ }
+
+ if (this.name === undefined) {
+ throw Error('name not set');
+ }
+
+ if (this.cornerRadius === undefined) {
+ throw Error('cornerRadius not set');
+ }
+
+ if (this.transform === undefined) {
+ throw Error('transform not set');
+ }
+
+ if (this.zOrderPath === undefined) {
+ throw Error('zOrderPath not set');
+ }
+
+ if (this.groupId === undefined) {
+ throw Error('groupId not set');
+ }
+
+ if (this.isVisible === undefined) {
+ throw Error('isVisible not set');
+ }
+
+ if (this.isDisplay === undefined) {
+ throw Error('isDisplay not set');
+ }
+
+ if (this.isVirtual === undefined) {
+ throw Error('isVirtual not set');
+ }
+
+ return new TraceRect(
+ this.x,
+ this.y,
+ this.w,
+ this.h,
+ this.id,
+ this.name,
+ this.cornerRadius,
+ this.transform,
+ this.zOrderPath,
+ this.groupId,
+ this.isVisible,
+ this.isDisplay,
+ this.isVirtual
+ );
+ }
+}
diff --git a/tools/winscope/src/trace/trace_type.ts b/tools/winscope/src/trace/trace_type.ts
index 2a857a5..7b2bf85 100644
--- a/tools/winscope/src/trace/trace_type.ts
+++ b/tools/winscope/src/trace/trace_type.ts
@@ -13,10 +13,10 @@
* See the License for the specific language governing permissions and
* limitations under the License.
*/
+
import {Cuj, Event, Transition} from 'flickerlib/common';
import {LayerTraceEntry} from 'flickerlib/layers/LayerTraceEntry';
import {WindowManagerState} from 'flickerlib/windows/WindowManagerState';
-import {HierarchyTreeNode, TraceRect, TreeNode} from 'trace/trace_data_utils';
import {LogMessage} from './protolog';
import {ScreenRecordingTraceEntry} from './screen_recording';
@@ -52,46 +52,32 @@
export type ViewNode = any;
export type FrameData = any;
-export interface TreeAndRects {
- tree: HierarchyTreeNode;
- rects: TraceRect[];
-}
-
export interface TraceEntryTypeMap {
- [TraceType.PROTO_LOG]: {new: LogMessage; legacy: LogMessage};
- [TraceType.SURFACE_FLINGER]: {new: TreeAndRects; legacy: LayerTraceEntry};
- [TraceType.SCREEN_RECORDING]: {new: ScreenRecordingTraceEntry; legacy: ScreenRecordingTraceEntry};
- [TraceType.SYSTEM_UI]: {new: object; legacy: object};
- [TraceType.TRANSACTIONS]: {new: TreeNode<any>; legacy: object};
- [TraceType.TRANSACTIONS_LEGACY]: {new: TreeNode<any>; legacy: object};
- [TraceType.WAYLAND]: {new: object; legacy: object};
- [TraceType.WAYLAND_DUMP]: {new: object; legacy: object};
- [TraceType.WINDOW_MANAGER]: {new: TreeAndRects; legacy: WindowManagerState};
- [TraceType.INPUT_METHOD_CLIENTS]: {new: TreeNode<any>; legacy: object};
- [TraceType.INPUT_METHOD_MANAGER_SERVICE]: {new: TreeNode<any>; legacy: object};
- [TraceType.INPUT_METHOD_SERVICE]: {new: TreeNode<any>; legacy: object};
- [TraceType.EVENT_LOG]: {new: TreeNode<any>; legacy: Event};
- [TraceType.WM_TRANSITION]: {new: TreeNode<any>; legacy: object};
- [TraceType.SHELL_TRANSITION]: {new: TreeNode<any>; legacy: object};
- [TraceType.TRANSITION]: {new: TreeNode<any>; legacy: Transition};
- [TraceType.CUJS]: {new: TreeNode<any>; legacy: Cuj};
- [TraceType.TAG]: {new: object; legacy: object};
- [TraceType.ERROR]: {new: object; legacy: object};
- [TraceType.TEST_TRACE_STRING]: {new: string; legacy: string};
- [TraceType.TEST_TRACE_NUMBER]: {new: number; legacy: number};
- [TraceType.VIEW_CAPTURE]: {new: TreeNode<any>; legacy: object};
- [TraceType.VIEW_CAPTURE_LAUNCHER_ACTIVITY]: {
- new: TreeAndRects;
- legacy: FrameData;
- };
- [TraceType.VIEW_CAPTURE_TASKBAR_DRAG_LAYER]: {
- new: TreeAndRects;
- legacy: FrameData;
- };
- [TraceType.VIEW_CAPTURE_TASKBAR_OVERLAY_DRAG_LAYER]: {
- new: TreeAndRects;
- legacy: FrameData;
- };
+ [TraceType.PROTO_LOG]: LogMessage;
+ [TraceType.SURFACE_FLINGER]: LayerTraceEntry;
+ [TraceType.SCREEN_RECORDING]: ScreenRecordingTraceEntry;
+ [TraceType.SYSTEM_UI]: object;
+ [TraceType.TRANSACTIONS]: object;
+ [TraceType.TRANSACTIONS_LEGACY]: object;
+ [TraceType.WAYLAND]: object;
+ [TraceType.WAYLAND_DUMP]: object;
+ [TraceType.WINDOW_MANAGER]: WindowManagerState;
+ [TraceType.INPUT_METHOD_CLIENTS]: object;
+ [TraceType.INPUT_METHOD_MANAGER_SERVICE]: object;
+ [TraceType.INPUT_METHOD_SERVICE]: object;
+ [TraceType.EVENT_LOG]: Event;
+ [TraceType.WM_TRANSITION]: object;
+ [TraceType.SHELL_TRANSITION]: object;
+ [TraceType.TRANSITION]: Transition;
+ [TraceType.CUJS]: Cuj;
+ [TraceType.TAG]: object;
+ [TraceType.ERROR]: object;
+ [TraceType.TEST_TRACE_STRING]: string;
+ [TraceType.TEST_TRACE_NUMBER]: number;
+ [TraceType.VIEW_CAPTURE]: object;
+ [TraceType.VIEW_CAPTURE_LAUNCHER_ACTIVITY]: FrameData;
+ [TraceType.VIEW_CAPTURE_TASKBAR_DRAG_LAYER]: FrameData;
+ [TraceType.VIEW_CAPTURE_TASKBAR_OVERLAY_DRAG_LAYER]: FrameData;
}
export class TraceTypeUtils {
diff --git a/tools/winscope/src/trace/traces.ts b/tools/winscope/src/trace/traces.ts
index 20f2cdd..6d4a8b0 100644
--- a/tools/winscope/src/trace/traces.ts
+++ b/tools/winscope/src/trace/traces.ts
@@ -14,7 +14,7 @@
* limitations under the License.
*/
-import {Timestamp} from '../common/time';
+import {Timestamp} from 'common/time';
import {AbsoluteFrameIndex} from './index_types';
import {Trace} from './trace';
import {TraceEntryTypeMap, TraceType} from './trace_type';
@@ -22,12 +22,12 @@
export class Traces {
private traces = new Map<TraceType, Trace<{}>>();
- setTrace<T extends TraceType>(type: T, trace: Trace<TraceEntryTypeMap[T]['legacy']>) {
+ setTrace<T extends TraceType>(type: T, trace: Trace<TraceEntryTypeMap[T]>) {
this.traces.set(type, trace);
}
- getTrace<T extends TraceType>(type: T): Trace<TraceEntryTypeMap[T]['legacy']> | undefined {
- return this.traces.get(type) as Trace<TraceEntryTypeMap[T]['legacy']> | undefined;
+ getTrace<T extends TraceType>(type: T): Trace<TraceEntryTypeMap[T]> | undefined {
+ return this.traces.get(type) as Trace<TraceEntryTypeMap[T]> | undefined;
}
deleteTrace<T extends TraceType>(type: T) {
diff --git a/tools/winscope/src/trace/tree_node/formatters.ts b/tools/winscope/src/trace/tree_node/formatters.ts
new file mode 100644
index 0000000..190e0e4
--- /dev/null
+++ b/tools/winscope/src/trace/tree_node/formatters.ts
@@ -0,0 +1,166 @@
+/*
+ * Copyright (C) 2024 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+import {RawDataUtils} from 'parsers/raw_data_utils';
+import {TransformUtils} from 'parsers/surface_flinger/transform_utils';
+import {PropertyTreeNode} from './property_tree_node';
+
+const EMPTY_OBJ_STRING = '{empty}';
+const EMPTY_ARRAY_STRING = '[empty]';
+
+interface PropertyFormatter {
+ format(node: PropertyTreeNode): string;
+}
+
+class DefaultPropertyFormatter implements PropertyFormatter {
+ format(node: PropertyTreeNode): string {
+ const value = node.getValue();
+ if (Array.isArray(value) && value.length === 0) {
+ return EMPTY_ARRAY_STRING;
+ }
+
+ if (value?.toString) return value.toString();
+
+ return `${value}`;
+ }
+}
+const DEFAULT_PROPERTY_FORMATTER = new DefaultPropertyFormatter();
+
+class ColorFormatter implements PropertyFormatter {
+ format(node: PropertyTreeNode): string {
+ if (RawDataUtils.isEmptyObj(node)) {
+ return `${EMPTY_OBJ_STRING}, alpha: ${node.getChildByName('a')?.getValue() ?? 'unknown'}`;
+ }
+ return `(${node.getChildByName('r')?.getValue() ?? 0}, ${
+ node.getChildByName('g')?.getValue() ?? 0
+ }, ${node.getChildByName('b')?.getValue() ?? 0}, ${node.getChildByName('a')?.getValue() ?? 0})`;
+ }
+}
+const COLOR_FORMATTER = new ColorFormatter();
+
+class RectFormatter implements PropertyFormatter {
+ format(node: PropertyTreeNode): string {
+ if (RawDataUtils.isEmptyObj(node)) {
+ return EMPTY_OBJ_STRING;
+ }
+ return `(${node.getChildByName('left')?.getValue() ?? 0}, ${
+ node.getChildByName('top')?.getValue() ?? 0
+ }) - (${node.getChildByName('right')?.getValue() ?? 0}, ${
+ node.getChildByName('bottom')?.getValue() ?? 0
+ })`;
+ }
+}
+const RECT_FORMATTER = new RectFormatter();
+
+class BufferFormatter implements PropertyFormatter {
+ format(node: PropertyTreeNode): string {
+ return `w: ${node.getChildByName('width')?.getValue() ?? 0}, h: ${
+ node.getChildByName('height')?.getValue() ?? 0
+ }, stride: ${node.getChildByName('stride')?.getValue()}, format: ${node
+ .getChildByName('format')
+ ?.getValue()}`;
+ }
+}
+const BUFFER_FORMATTER = new BufferFormatter();
+
+class LayerIdFormatter implements PropertyFormatter {
+ format(node: PropertyTreeNode): string {
+ const value = node.getValue();
+ return value === -1 || value === 0 ? 'none' : `${value}`;
+ }
+}
+const LAYER_ID_FORMATTER = new LayerIdFormatter();
+
+class TransformFormatter implements PropertyFormatter {
+ format(node: PropertyTreeNode): string {
+ const type = node.getChildByName('type');
+ return type !== undefined ? TransformUtils.getTypeFlags(type.getValue() ?? 0) : 'null';
+ }
+}
+const TRANSFORM_FORMATTER = new TransformFormatter();
+
+class SizeFormatter implements PropertyFormatter {
+ format(node: PropertyTreeNode): string {
+ return `${node.getChildByName('w')?.getValue() ?? 0} x ${
+ node.getChildByName('h')?.getValue() ?? 0
+ }`;
+ }
+}
+const SIZE_FORMATTER = new SizeFormatter();
+
+class PositionFormatter implements PropertyFormatter {
+ format(node: PropertyTreeNode): string {
+ return `x: ${node.getChildByName('x')?.getValue() ?? 0}, y: ${
+ node.getChildByName('y')?.getValue() ?? 0
+ }`;
+ }
+}
+const POSITION_FORMATTER = new PositionFormatter();
+
+class RegionFormatter implements PropertyFormatter {
+ format(node: PropertyTreeNode): string {
+ let res = 'SkRegion(';
+ node
+ .getChildByName('rect')
+ ?.getAllChildren()
+ .forEach((rectNode: PropertyTreeNode) => {
+ res += `(${rectNode.getChildByName('left')?.getValue() ?? 0}, ${
+ rectNode.getChildByName('top')?.getValue() ?? 0
+ }, ${rectNode.getChildByName('right')?.getValue() ?? 0}, ${
+ rectNode.getChildByName('bottom')?.getValue() ?? 0
+ })`;
+ });
+ return res + ')';
+ }
+}
+const REGION_FORMATTER = new RegionFormatter();
+
+class EnumFormatter implements PropertyFormatter {
+ constructor(private readonly valuesById: {[key: number]: string}) {}
+
+ format(node: PropertyTreeNode): string {
+ const value = node.getValue();
+ if (typeof value === 'number' && this.valuesById[value]) {
+ return this.valuesById[value];
+ }
+ return `${value}`;
+ }
+}
+
+class FixedStringFormatter implements PropertyFormatter {
+ constructor(private readonly fixedStringValue: string) {}
+
+ format(node: PropertyTreeNode): string {
+ return this.fixedStringValue;
+ }
+}
+
+export {
+ EMPTY_OBJ_STRING,
+ EMPTY_ARRAY_STRING,
+ PropertyFormatter,
+ DEFAULT_PROPERTY_FORMATTER,
+ COLOR_FORMATTER,
+ RECT_FORMATTER,
+ BUFFER_FORMATTER,
+ LAYER_ID_FORMATTER,
+ TRANSFORM_FORMATTER,
+ SIZE_FORMATTER,
+ POSITION_FORMATTER,
+ REGION_FORMATTER,
+ EnumFormatter,
+ FixedStringFormatter,
+};
diff --git a/tools/winscope/src/trace/tree_node/formatters_test.ts b/tools/winscope/src/trace/tree_node/formatters_test.ts
new file mode 100644
index 0000000..d6b9d78
--- /dev/null
+++ b/tools/winscope/src/trace/tree_node/formatters_test.ts
@@ -0,0 +1,182 @@
+/*
+ * Copyright (C) 2024 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+import {TransformType} from 'parsers/surface_flinger/transform_utils';
+import {TreeNodeUtils} from 'test/unit/tree_node_utils';
+import {
+ BUFFER_FORMATTER,
+ COLOR_FORMATTER,
+ DEFAULT_PROPERTY_FORMATTER,
+ EMPTY_ARRAY_STRING,
+ EMPTY_OBJ_STRING,
+ LAYER_ID_FORMATTER,
+ POSITION_FORMATTER,
+ RECT_FORMATTER,
+ REGION_FORMATTER,
+ SIZE_FORMATTER,
+ TRANSFORM_FORMATTER,
+} from './formatters';
+import {PropertySource, PropertyTreeNode} from './property_tree_node';
+
+describe('Formatters', () => {
+ describe('PropertyFormatter', () => {
+ it('translates simple values correctly', () => {
+ expect(
+ DEFAULT_PROPERTY_FORMATTER.format(new PropertyTreeNode('', '', PropertySource.PROTO, 12345))
+ ).toEqual('12345');
+ expect(
+ DEFAULT_PROPERTY_FORMATTER.format(
+ new PropertyTreeNode('', '', PropertySource.PROTO, 'test_string')
+ )
+ ).toEqual('test_string');
+ expect(
+ DEFAULT_PROPERTY_FORMATTER.format(new PropertyTreeNode('', '', PropertySource.PROTO, 0.4))
+ ).toEqual('0.4');
+ });
+
+ it('translates values with toString method correctly', () => {
+ expect(
+ DEFAULT_PROPERTY_FORMATTER.format(
+ new PropertyTreeNode('', '', PropertySource.PROTO, BigInt(123))
+ )
+ ).toEqual('123');
+ });
+
+ it('translates default values correctly', () => {
+ expect(
+ DEFAULT_PROPERTY_FORMATTER.format(new PropertyTreeNode('', '', PropertySource.PROTO, []))
+ ).toEqual(EMPTY_ARRAY_STRING);
+ expect(
+ DEFAULT_PROPERTY_FORMATTER.format(new PropertyTreeNode('', '', PropertySource.PROTO, false))
+ ).toEqual('false');
+ expect(
+ DEFAULT_PROPERTY_FORMATTER.format(new PropertyTreeNode('', '', PropertySource.PROTO, null))
+ ).toEqual('null');
+ });
+ });
+
+ describe('ColorFormatter', () => {
+ it('translates empty color to string correctly', () => {
+ expect(COLOR_FORMATTER.format(TreeNodeUtils.makeColorNode(-1, -1, -1, 1))).toEqual(
+ `${EMPTY_OBJ_STRING}, alpha: 1`
+ );
+ expect(COLOR_FORMATTER.format(TreeNodeUtils.makeColorNode(1, 1, 1, 0))).toEqual(
+ `${EMPTY_OBJ_STRING}, alpha: 0`
+ );
+ });
+
+ it('translates non-empty color to string correctly', () => {
+ expect(COLOR_FORMATTER.format(TreeNodeUtils.makeColorNode(1, 2, 3, 1))).toEqual(
+ '(1, 2, 3, 1)'
+ );
+ expect(COLOR_FORMATTER.format(TreeNodeUtils.makeColorNode(1, 2, 3, 0.5))).toEqual(
+ '(1, 2, 3, 0.5)'
+ );
+ });
+ });
+
+ describe('RectFormatter', () => {
+ it('translates empty rect to string correctly', () => {
+ expect(RECT_FORMATTER.format(TreeNodeUtils.makeRectNode(0, 0, -1, -1))).toEqual(
+ EMPTY_OBJ_STRING
+ );
+ expect(RECT_FORMATTER.format(TreeNodeUtils.makeRectNode(0, 0, 0, 0))).toEqual(
+ EMPTY_OBJ_STRING
+ );
+ });
+
+ it('translates non-empty rect to string correctly', () => {
+ expect(RECT_FORMATTER.format(TreeNodeUtils.makeRectNode(0, 0, 1, 1))).toEqual(
+ '(0, 0) - (1, 1)'
+ );
+ expect(RECT_FORMATTER.format(TreeNodeUtils.makeRectNode(0, 0, 10, 10))).toEqual(
+ '(0, 0) - (10, 10)'
+ );
+ });
+ });
+
+ describe('BufferFormatter', () => {
+ it('translates buffer to string correctly', () => {
+ const buffer = TreeNodeUtils.makeBufferNode();
+ expect(BUFFER_FORMATTER.format(buffer)).toEqual('w: 1, h: 0, stride: 0, format: 1');
+ });
+ });
+
+ describe('LayerIdFormatter', () => {
+ it('translates -1 id correctly', () => {
+ expect(
+ LAYER_ID_FORMATTER.format(new PropertyTreeNode('', '', PropertySource.PROTO, -1))
+ ).toEqual('none');
+ });
+
+ it('translates valid id correctly', () => {
+ expect(
+ LAYER_ID_FORMATTER.format(new PropertyTreeNode('', '', PropertySource.PROTO, 1))
+ ).toEqual('1');
+ expect(
+ LAYER_ID_FORMATTER.format(new PropertyTreeNode('', '', PropertySource.PROTO, -10))
+ ).toEqual('-10');
+ });
+ });
+
+ describe('TransformFormatter', () => {
+ it('translates type correctly', () => {
+ expect(
+ TRANSFORM_FORMATTER.format(TreeNodeUtils.makeTransformNode(TransformType.EMPTY))
+ ).toEqual('IDENTITY');
+ expect(
+ TRANSFORM_FORMATTER.format(TreeNodeUtils.makeTransformNode(TransformType.TRANSLATE_VAL))
+ ).toEqual('TRANSLATE');
+ expect(
+ TRANSFORM_FORMATTER.format(TreeNodeUtils.makeTransformNode(TransformType.SCALE_VAL))
+ ).toEqual('SCALE');
+ expect(
+ TRANSFORM_FORMATTER.format(TreeNodeUtils.makeTransformNode(TransformType.FLIP_H_VAL))
+ ).toEqual('IDENTITY|FLIP_H');
+ expect(
+ TRANSFORM_FORMATTER.format(TreeNodeUtils.makeTransformNode(TransformType.FLIP_V_VAL))
+ ).toEqual('IDENTITY|FLIP_V');
+ expect(
+ TRANSFORM_FORMATTER.format(TreeNodeUtils.makeTransformNode(TransformType.ROT_90_VAL))
+ ).toEqual('IDENTITY|ROT_90');
+ expect(
+ TRANSFORM_FORMATTER.format(TreeNodeUtils.makeTransformNode(TransformType.ROT_INVALID_VAL))
+ ).toEqual('IDENTITY|ROT_INVALID');
+ });
+ });
+
+ describe('SizeFormatter', () => {
+ it('translates size correctly', () => {
+ expect(SIZE_FORMATTER.format(TreeNodeUtils.makeSizeNode(1, 2))).toEqual('1 x 2');
+ });
+ });
+
+ describe('PositionFormatter', () => {
+ it('translates position correctly', () => {
+ expect(POSITION_FORMATTER.format(TreeNodeUtils.makePositionNode(1, 2))).toEqual('x: 1, y: 2');
+ });
+ });
+
+ describe('RegionFormatter', () => {
+ it('translates region correctly', () => {
+ const region = new PropertyTreeNode('region', 'region', PropertySource.PROTO, undefined);
+ const rect = new PropertyTreeNode('region.rect', 'rect', PropertySource.PROTO, []);
+ rect.addChild(TreeNodeUtils.makeRectNode(0, 0, 1080, 2340));
+ region.addChild(rect);
+ expect(REGION_FORMATTER.format(region)).toEqual('SkRegion((0, 0, 1080, 2340))');
+ });
+ });
+});
diff --git a/tools/winscope/src/trace/tree_node/hierarchy_tree_node.ts b/tools/winscope/src/trace/tree_node/hierarchy_tree_node.ts
new file mode 100644
index 0000000..4acf708
--- /dev/null
+++ b/tools/winscope/src/trace/tree_node/hierarchy_tree_node.ts
@@ -0,0 +1,71 @@
+/*
+ * Copyright (C) 2024 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+import {TraceRect} from 'trace/trace_rect';
+import {PropertiesProvider} from 'trace/tree_node/properties_provider';
+import {PropertyTreeNode} from './property_tree_node';
+import {TreeNode} from './tree_node';
+
+export class HierarchyTreeNode extends TreeNode {
+ private rects: TraceRect[] | undefined;
+ private zParent: this | undefined;
+
+ constructor(id: string, name: string, protected readonly propertiesProvider: PropertiesProvider) {
+ super(id, name);
+ }
+
+ async getAllProperties(): Promise<PropertyTreeNode> {
+ return await this.propertiesProvider.getAll();
+ }
+
+ getEagerPropertyByName(name: string): PropertyTreeNode | undefined {
+ return this.propertiesProvider.getEagerProperties().getChildById(`${this.id}.${name}`);
+ }
+
+ addEagerProperty(property: PropertyTreeNode): void {
+ this.propertiesProvider.addEagerProperty(property);
+ }
+
+ setRects(value: TraceRect[]) {
+ this.rects = value;
+ }
+
+ getRects(): TraceRect[] | undefined {
+ return this.rects;
+ }
+
+ setZParent(parent: this): void {
+ this.zParent = parent;
+ }
+
+ getZParent(): this | undefined {
+ return this.zParent;
+ }
+
+ override isRoot(): boolean {
+ return !this.zParent;
+ }
+
+ findAncestor(targetNodeFilter: (node: this) => boolean): this | undefined {
+ let ancestor = this.getZParent();
+
+ while (ancestor && !targetNodeFilter(ancestor)) {
+ ancestor = ancestor.getZParent();
+ }
+
+ return ancestor;
+ }
+}
diff --git a/tools/winscope/src/trace/tree_node/operations/operation.ts b/tools/winscope/src/trace/tree_node/operations/operation.ts
new file mode 100644
index 0000000..8d50873
--- /dev/null
+++ b/tools/winscope/src/trace/tree_node/operations/operation.ts
@@ -0,0 +1,21 @@
+/*
+ * Copyright (C) 2024 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+import {TreeNode} from 'trace/tree_node/tree_node';
+
+export interface Operation<T extends TreeNode> {
+ apply(value: T): T;
+}
diff --git a/tools/winscope/src/trace/tree_node/operations/operation_chain.ts b/tools/winscope/src/trace/tree_node/operations/operation_chain.ts
new file mode 100644
index 0000000..b6d5852
--- /dev/null
+++ b/tools/winscope/src/trace/tree_node/operations/operation_chain.ts
@@ -0,0 +1,38 @@
+/*
+ * Copyright (C) 2024 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+import {TreeNode} from 'trace/tree_node/tree_node';
+import {Operation} from './operation';
+
+export class OperationChain<T extends TreeNode> {
+ constructor(private readonly enclosedOperations: Array<Operation<T>>) {}
+
+ apply(value: T): T {
+ this.enclosedOperations.forEach((operation: Operation<T>) => {
+ value = operation.apply(value);
+ });
+
+ return value;
+ }
+
+ push(enclosedOperation: Operation<T>) {
+ this.enclosedOperations.push(enclosedOperation);
+ }
+
+ static emptyChain<T extends TreeNode>(): OperationChain<T> {
+ return new OperationChain([]);
+ }
+}
diff --git a/tools/winscope/src/trace/tree_node/properties_provider.ts b/tools/winscope/src/trace/tree_node/properties_provider.ts
new file mode 100644
index 0000000..d83e64c
--- /dev/null
+++ b/tools/winscope/src/trace/tree_node/properties_provider.ts
@@ -0,0 +1,77 @@
+/*
+ * Copyright (C) 2024 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+import {OperationChain} from 'trace/tree_node/operations/operation_chain';
+import {PropertySource, PropertyTreeNode} from 'trace/tree_node/property_tree_node';
+
+export class PropertiesProvider {
+ private eagerPropertiesRoot: PropertyTreeNode;
+ private lazyPropertiesRoot: PropertyTreeNode | undefined;
+ private allPropertiesRoot: PropertyTreeNode | undefined;
+
+ constructor(
+ eagerPropertiesRoot: PropertyTreeNode,
+ private readonly lazyPropertiesStrategy: () => Promise<PropertyTreeNode>,
+ private readonly commonOperations: OperationChain<PropertyTreeNode>,
+ private readonly eagerOperations: OperationChain<PropertyTreeNode>,
+ private readonly lazyOperations: OperationChain<PropertyTreeNode>
+ ) {
+ this.eagerPropertiesRoot = this.commonOperations.apply(
+ this.eagerOperations.apply(eagerPropertiesRoot)
+ );
+ }
+
+ getEagerProperties(): PropertyTreeNode {
+ return this.eagerPropertiesRoot;
+ }
+
+ addEagerProperty(property: PropertyTreeNode) {
+ this.eagerPropertiesRoot.addChild(
+ this.commonOperations.apply(this.eagerOperations.apply(property))
+ );
+ }
+
+ async getAll(): Promise<PropertyTreeNode> {
+ if (this.allPropertiesRoot) return this.allPropertiesRoot;
+
+ const root = new PropertyTreeNode(
+ this.eagerPropertiesRoot.id,
+ this.eagerPropertiesRoot.name,
+ PropertySource.PROTO,
+ undefined
+ );
+ const children = [...this.eagerPropertiesRoot.getAllChildren()];
+
+ // all eager properties have already had operations applied so no need to reapply
+ if (!this.lazyPropertiesRoot) {
+ this.lazyPropertiesRoot = this.commonOperations.apply(
+ this.lazyOperations.apply(await this.lazyPropertiesStrategy())
+ );
+ }
+
+ children.push(...this.lazyPropertiesRoot.getAllChildren());
+ children.sort(this.sortChildren).forEach((child) => root.addChild(child));
+
+ root.setIsRoot(true);
+
+ this.allPropertiesRoot = root;
+ return this.allPropertiesRoot;
+ }
+
+ private sortChildren(a: PropertyTreeNode, b: PropertyTreeNode): number {
+ return a.name < b.name ? -1 : 1;
+ }
+}
diff --git a/tools/winscope/src/trace/tree_node/property_tree_node.ts b/tools/winscope/src/trace/tree_node/property_tree_node.ts
new file mode 100644
index 0000000..faee8f4
--- /dev/null
+++ b/tools/winscope/src/trace/tree_node/property_tree_node.ts
@@ -0,0 +1,67 @@
+/*
+ * Copyright (C) 2024 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+import {PropertyFormatter} from './formatters';
+import {TreeNode} from './tree_node';
+
+export class PropertyTreeNode extends TreeNode {
+ protected formatter: PropertyFormatter | undefined = undefined;
+ protected internalIsRoot = false;
+
+ constructor(
+ id: string,
+ name: string,
+ readonly source: PropertySource,
+ protected readonly value: any
+ ) {
+ super(id, name);
+ }
+
+ getChildByName(name: string): this | undefined {
+ return this.children.get(`${this.id}.${name}`);
+ }
+
+ getValue(): any {
+ return this.value;
+ }
+
+ setFormatter(formatter: PropertyFormatter): this {
+ this.formatter = formatter;
+ return this;
+ }
+
+ setIsRoot(value: boolean) {
+ this.internalIsRoot = value;
+ }
+
+ override isRoot(): boolean {
+ return this.internalIsRoot;
+ }
+
+ formattedValue(): string {
+ if (this.formatter) {
+ return this.formatter.format(this);
+ }
+
+ return '';
+ }
+}
+
+export enum PropertySource {
+ PROTO,
+ DEFAULT,
+ CALCULATED,
+}
diff --git a/tools/winscope/src/trace/tree_node/tree_node.ts b/tools/winscope/src/trace/tree_node/tree_node.ts
new file mode 100644
index 0000000..459a010
--- /dev/null
+++ b/tools/winscope/src/trace/tree_node/tree_node.ts
@@ -0,0 +1,65 @@
+/*
+ * Copyright (C) 2024 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+import {Item} from 'trace/item';
+
+export abstract class TreeNode implements Item {
+ protected children = new Map<string, this>();
+
+ constructor(public id: string, public name: string) {}
+
+ addChild(child: this): void {
+ this.children.set(child.id, child);
+ }
+
+ removeChild(childId: string) {
+ this.children.delete(childId);
+ }
+
+ removeAllChildren() {
+ this.children.clear();
+ }
+
+ getChildById(id: string): this | undefined {
+ return this.children.get(id);
+ }
+
+ getAllChildren(): this[] {
+ return [...this.children.values()];
+ }
+
+ forEachNodeDfs(callback: (node: this) => void) {
+ callback(this);
+ this.children.forEach((child) => {
+ child.forEachNodeDfs(callback);
+ });
+ }
+
+ findDfs(targetNodeFilter: (node: this) => boolean): this | undefined {
+ if (targetNodeFilter(this)) {
+ return this;
+ }
+
+ for (const child of this.children.values()) {
+ const nodeFound = child.findDfs(targetNodeFilter);
+ if (nodeFound) return nodeFound;
+ }
+
+ return undefined;
+ }
+
+ abstract isRoot(): boolean;
+}
diff --git a/tools/winscope/src/viewers/common/surface_flinger_utils.ts b/tools/winscope/src/viewers/common/surface_flinger_utils.ts
index 1bca894..0b7ee1d 100644
--- a/tools/winscope/src/viewers/common/surface_flinger_utils.ts
+++ b/tools/winscope/src/viewers/common/surface_flinger_utils.ts
@@ -14,9 +14,10 @@
* limitations under the License.
*/
-import {TransformMatrix} from 'common/geometry_utils';
+import {TransformMatrix} from 'common/geometry_types';
import {Layer, LayerTraceEntry} from 'flickerlib/common';
import {UiRect} from 'viewers/components/rects/types2d';
+import {UiRectBuilder} from 'viewers/components/rects/ui_rect_builder';
import {UserOptions} from './user_options';
export class SurfaceFlingerUtils {
@@ -46,25 +47,24 @@
.sort(SurfaceFlingerUtils.compareLayerZ)
.map((it: Layer) => {
const transform: TransformMatrix = it.rect.transform?.matrix ?? it.rect.transform;
- const rect: UiRect = {
- x: it.rect.left,
- y: it.rect.top,
- w: it.rect.right - it.rect.left,
- h: it.rect.bottom - it.rect.top,
- label: it.rect.label,
- transform,
- isVisible: it.isVisible,
- isDisplay: false,
- id: it.stableId,
- displayId: it.stackId,
- isVirtual: false,
- isClickable: true,
- cornerRadius: it.cornerRadius,
- hasContent: viewCapturePackageNames.includes(
- it.rect.label.substring(0, it.rect.label.indexOf('/'))
- ),
- };
- return rect;
+ return new UiRectBuilder()
+ .setX(it.rect.left)
+ .setY(it.rect.top)
+ .setWidth(it.rect.right - it.rect.left)
+ .setHeight(it.rect.bottom - it.rect.top)
+ .setLabel(it.rect.label)
+ .setTransform(transform)
+ .setIsVisible(it.isVisible)
+ .setIsDisplay(false)
+ .setId(it.stableId)
+ .setDisplayId(it.stackId)
+ .setIsVirtual(false)
+ .setIsClickable(true)
+ .setCornerRadius(it.cornerRadius)
+ .setHasContent(
+ viewCapturePackageNames.includes(it.rect.label.substring(0, it.rect.label.indexOf('/')))
+ )
+ .build();
});
}
@@ -75,23 +75,22 @@
return entry.displays?.map((display: any) => {
const transform: TransformMatrix = display.transform?.matrix ?? display.transform;
- const rect: UiRect = {
- x: 0,
- y: 0,
- w: display.size.width,
- h: display.size.height,
- label: 'Display',
- transform,
- isVisible: false,
- isDisplay: true,
- id: `Display - ${display.id}`,
- displayId: display.layerStackId,
- isVirtual: display.isVirtual ?? false,
- isClickable: false,
- cornerRadius: 0,
- hasContent: false,
- };
- return rect;
+ return new UiRectBuilder()
+ .setX(0)
+ .setY(0)
+ .setWidth(display.size.width)
+ .setHeight(display.size.height)
+ .setLabel('Display')
+ .setTransform(transform)
+ .setIsVisible(false)
+ .setIsDisplay(true)
+ .setId(`Display - ${display.id}`)
+ .setDisplayId(display.layerStackId)
+ .setIsVirtual(display.isVirtual ?? false)
+ .setIsClickable(false)
+ .setCornerRadius(0)
+ .setHasContent(false)
+ .build();
});
}
diff --git a/tools/winscope/src/viewers/components/rects/canvas.ts b/tools/winscope/src/viewers/components/rects/canvas.ts
index e0436f8..0c89665 100644
--- a/tools/winscope/src/viewers/components/rects/canvas.ts
+++ b/tools/winscope/src/viewers/components/rects/canvas.ts
@@ -13,7 +13,7 @@
* See the License for the specific language governing permissions and
* limitations under the License.
*/
-import {TransformMatrix} from 'common/geometry_utils';
+import {TransformMatrix} from 'common/geometry_types';
import * as THREE from 'three';
import {CSS2DObject, CSS2DRenderer} from 'three/examples/jsm/renderers/CSS2DRenderer';
import {ViewerEvents} from 'viewers/common/viewer_events';
diff --git a/tools/winscope/src/viewers/components/rects/mapper3d.ts b/tools/winscope/src/viewers/components/rects/mapper3d.ts
index 0643669..bbad7ab 100644
--- a/tools/winscope/src/viewers/components/rects/mapper3d.ts
+++ b/tools/winscope/src/viewers/components/rects/mapper3d.ts
@@ -14,7 +14,7 @@
* limitations under the License.
*/
-import {IDENTITY_MATRIX, TransformMatrix} from 'common/geometry_utils';
+import {IDENTITY_MATRIX, TransformMatrix} from 'common/geometry_types';
import {Size, UiRect} from 'viewers/components/rects/types2d';
import {Box3D, ColorType, Distance2D, Label3D, Point3D, Rect3D, Scene3D} from './types3d';
diff --git a/tools/winscope/src/viewers/components/rects/rects_component_test.ts b/tools/winscope/src/viewers/components/rects/rects_component_test.ts
index 55bd335..1a3f736 100644
--- a/tools/winscope/src/viewers/components/rects/rects_component_test.ts
+++ b/tools/winscope/src/viewers/components/rects/rects_component_test.ts
@@ -25,6 +25,7 @@
import {RectsComponent} from 'viewers/components/rects/rects_component';
import {UiRect} from 'viewers/components/rects/types2d';
import {Canvas} from './canvas';
+import {UiRectBuilder} from './ui_rect_builder';
describe('RectsComponent', () => {
let component: TestHostComponent;
@@ -68,28 +69,28 @@
it('draws scene when input data changes', async () => {
spyOn(Canvas.prototype, 'draw').and.callThrough();
- const inputRect: UiRect = {
- x: 0,
- y: 0,
- w: 1,
- h: 1,
- label: 'rectangle1',
- transform: {
+ const inputRect = new UiRectBuilder()
+ .setX(0)
+ .setY(0)
+ .setWidth(1)
+ .setHeight(1)
+ .setLabel('rectangle1')
+ .setTransform({
dsdx: 1,
dsdy: 0,
dtdx: 0,
dtdy: 1,
tx: 0,
ty: 0,
- },
- isVisible: true,
- isDisplay: false,
- id: 'test-id-1234',
- displayId: 0,
- isVirtual: false,
- isClickable: false,
- cornerRadius: 0,
- };
+ })
+ .setIsVisible(true)
+ .setIsDisplay(false)
+ .setId('test-id-1234')
+ .setDisplayId(0)
+ .setIsVirtual(false)
+ .setIsClickable(false)
+ .setCornerRadius(0)
+ .build();
expect(Canvas.prototype.draw).toHaveBeenCalledTimes(0);
component.rectsComponent.rects = [inputRect];
diff --git a/tools/winscope/src/viewers/components/rects/types2d.ts b/tools/winscope/src/viewers/components/rects/types2d.ts
index fe34955..a150a36 100644
--- a/tools/winscope/src/viewers/components/rects/types2d.ts
+++ b/tools/winscope/src/viewers/components/rects/types2d.ts
@@ -14,20 +14,29 @@
* limitations under the License.
*/
-import {Rect, TransformMatrix} from 'common/geometry_utils';
+import {TransformMatrix} from 'common/geometry_types';
+import {Rect} from 'common/rect';
-export interface UiRect extends Rect {
- label: string;
- transform?: TransformMatrix;
- isVisible: boolean;
- isDisplay: boolean;
- id: string;
- displayId: number;
- isVirtual: boolean;
- isClickable: boolean;
- cornerRadius: number;
- depth?: number;
- hasContent?: boolean;
+export class UiRect extends Rect {
+ constructor(
+ x: number,
+ y: number,
+ w: number,
+ h: number,
+ readonly label: string,
+ readonly isVisible: boolean,
+ readonly isDisplay: boolean,
+ readonly id: string,
+ readonly displayId: number,
+ readonly isVirtual: boolean,
+ readonly isClickable: boolean,
+ readonly cornerRadius: number,
+ readonly transform: TransformMatrix | undefined,
+ readonly depth: number | undefined,
+ readonly hasContent: boolean | undefined
+ ) {
+ super(x, y, w, h);
+ }
}
export interface Size {
diff --git a/tools/winscope/src/viewers/components/rects/types3d.ts b/tools/winscope/src/viewers/components/rects/types3d.ts
index fa06e00..1c4f541 100644
--- a/tools/winscope/src/viewers/components/rects/types3d.ts
+++ b/tools/winscope/src/viewers/components/rects/types3d.ts
@@ -14,7 +14,7 @@
* limitations under the License.
*/
-import {TransformMatrix} from 'common/geometry_utils';
+import {TransformMatrix} from 'common/geometry_types';
export enum ColorType {
VISIBLE,
diff --git a/tools/winscope/src/viewers/components/rects/ui_rect_builder.ts b/tools/winscope/src/viewers/components/rects/ui_rect_builder.ts
new file mode 100644
index 0000000..263294f
--- /dev/null
+++ b/tools/winscope/src/viewers/components/rects/ui_rect_builder.ts
@@ -0,0 +1,179 @@
+/*
+ * Copyright (C) 2024 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+import {TransformMatrix} from 'common/geometry_types';
+import {UiRect} from './types2d';
+
+export class UiRectBuilder {
+ x: number | undefined;
+ y: number | undefined;
+ w: number | undefined;
+ h: number | undefined;
+ label: string | undefined;
+ transform: TransformMatrix | undefined;
+ isVisible: boolean | undefined;
+ isDisplay: boolean | undefined;
+ id: string | undefined;
+ displayId: number | undefined;
+ isVirtual: boolean | undefined;
+ isClickable: boolean | undefined;
+ cornerRadius: number | undefined;
+ depth: number | undefined;
+ hasContent: boolean | undefined;
+
+ setX(value: number) {
+ this.x = value;
+ return this;
+ }
+
+ setY(value: number) {
+ this.y = value;
+ return this;
+ }
+
+ setWidth(value: number) {
+ this.w = value;
+ return this;
+ }
+
+ setHeight(value: number) {
+ this.h = value;
+ return this;
+ }
+
+ setLabel(value: string) {
+ this.label = value;
+ return this;
+ }
+
+ setTransform(value: TransformMatrix) {
+ this.transform = value;
+ return this;
+ }
+
+ setIsVisible(value: boolean) {
+ this.isVisible = value;
+ return this;
+ }
+
+ setIsDisplay(value: boolean) {
+ this.isDisplay = value;
+ return this;
+ }
+
+ setId(value: string) {
+ this.id = value;
+ return this;
+ }
+
+ setDisplayId(value: number) {
+ this.displayId = value;
+ return this;
+ }
+
+ setIsVirtual(value: boolean) {
+ this.isVirtual = value;
+ return this;
+ }
+
+ setIsClickable(value: boolean) {
+ this.isClickable = value;
+ return this;
+ }
+
+ setCornerRadius(value: number) {
+ this.cornerRadius = value;
+ return this;
+ }
+
+ setDepth(value: number) {
+ this.depth = value;
+ return this;
+ }
+
+ setHasContent(value: boolean) {
+ this.hasContent = value;
+ return this;
+ }
+
+ build(): UiRect {
+ if (this.x === undefined) {
+ throw Error('x not set');
+ }
+
+ if (this.y === undefined) {
+ throw Error('y not set');
+ }
+
+ if (this.w === undefined) {
+ throw Error('width not set');
+ }
+
+ if (this.h === undefined) {
+ throw Error('height not set');
+ }
+
+ if (this.label === undefined) {
+ throw Error('label not set');
+ }
+
+ if (this.isVisible === undefined) {
+ throw Error('isVisible not set');
+ }
+
+ if (this.isDisplay === undefined) {
+ throw Error('isDisplay not set');
+ }
+
+ if (this.id === undefined) {
+ throw Error('id not set');
+ }
+
+ if (this.displayId === undefined) {
+ throw Error('displayId not set');
+ }
+
+ if (this.isVirtual === undefined) {
+ throw Error('isVirtual not set');
+ }
+
+ if (this.isClickable === undefined) {
+ throw Error('isClickable not set');
+ }
+
+ if (this.cornerRadius === undefined) {
+ throw Error('cornerRadius not set');
+ }
+
+ return new UiRect(
+ this.x,
+ this.y,
+ this.w,
+ this.h,
+ this.label,
+ this.isVisible,
+ this.isDisplay,
+ this.id,
+ this.displayId,
+ this.isVirtual,
+ this.isClickable,
+ this.cornerRadius,
+ this.transform,
+ this.depth,
+ this.hasContent
+ );
+ }
+}
diff --git a/tools/winscope/src/viewers/viewer_view_capture/presenter.ts b/tools/winscope/src/viewers/viewer_view_capture/presenter.ts
index 97e446b..34141b5 100644
--- a/tools/winscope/src/viewers/viewer_view_capture/presenter.ts
+++ b/tools/winscope/src/viewers/viewer_view_capture/presenter.ts
@@ -29,6 +29,7 @@
import {UserOptions} from 'viewers/common/user_options';
import {ViewCaptureUtils} from 'viewers/common/view_capture_utils';
import {UiRect} from 'viewers/components/rects/types2d';
+import {UiRectBuilder} from 'viewers/components/rects/ui_rect_builder';
import {UiData} from './ui_data';
export class Presenter {
@@ -171,23 +172,23 @@
const rectangles: UiRect[] = [];
function inner(node: any /* ViewNode */) {
- const aUiRect: UiRect = {
- x: node.boxPos.left,
- y: node.boxPos.top,
- w: node.boxPos.width,
- h: node.boxPos.height,
- label: '',
- transform: undefined,
- isVisible: node.isVisible,
- isDisplay: false,
- id: node.id,
- displayId: 0,
- isVirtual: false,
- isClickable: true,
- cornerRadius: 0,
- depth: node.depth,
- hasContent: node.isVisible,
- };
+ const aUiRect = new UiRectBuilder()
+ .setX(node.boxPos.left)
+ .setY(node.boxPos.top)
+ .setWidth(node.boxPos.width)
+ .setHeight(node.boxPos.height)
+ .setLabel('')
+ .setIsVisible(node.isVisible)
+ .setIsDisplay(false)
+ .setId(node.id)
+ .setDisplayId(0)
+ .setIsVirtual(false)
+ .setIsClickable(true)
+ .setCornerRadius(0)
+ .setHasContent(node.isVisible)
+ .setDepth(node.depth)
+ .build();
+
rectangles.push(aUiRect);
node.children.forEach((it: any) /* ViewNode */ => inner(it));
}
diff --git a/tools/winscope/src/viewers/viewer_window_manager/presenter.ts b/tools/winscope/src/viewers/viewer_window_manager/presenter.ts
index f5875a8..e3ee90f 100644
--- a/tools/winscope/src/viewers/viewer_window_manager/presenter.ts
+++ b/tools/winscope/src/viewers/viewer_window_manager/presenter.ts
@@ -15,7 +15,7 @@
*/
import {assertDefined} from 'common/assert_utils';
-import {TransformMatrix} from 'common/geometry_utils';
+import {TransformMatrix} from 'common/geometry_types';
import {PersistentStoreProxy} from 'common/persistent_store_proxy';
import {FilterType, TreeUtils} from 'common/tree_utils';
import {DisplayContent} from 'flickerlib/windows/DisplayContent';
@@ -31,6 +31,7 @@
import {HierarchyTreeNode, PropertiesTreeNode} from 'viewers/common/ui_tree_utils';
import {UserOptions} from 'viewers/common/user_options';
import {UiRect} from 'viewers/components/rects/types2d';
+import {UiRectBuilder} from 'viewers/components/rects/ui_rect_builder';
import {UiData} from './ui_data';
type NotifyViewCallbackType = (uiData: UiData) => void;
@@ -206,21 +207,20 @@
};
const displayRects: UiRect[] =
entry.displays?.map((display: DisplayContent) => {
- const rect: UiRect = {
- x: display.displayRect.left,
- y: display.displayRect.top,
- w: display.displayRect.right - display.displayRect.left,
- h: display.displayRect.bottom - display.displayRect.top,
- label: `Display - ${display.title}`,
- transform: identityMatrix,
- isVisible: false, //TODO: check if displayRect.ref.isVisible exists
- isDisplay: true,
- id: display.stableId,
- displayId: display.id,
- isVirtual: false,
- isClickable: false,
- cornerRadius: 0,
- };
+ const rect = new UiRectBuilder()
+ .setX(display.displayRect.left)
+ .setY(display.displayRect.top)
+ .setWidth(display.displayRect.right - display.displayRect.left)
+ .setHeight(display.displayRect.bottom - display.displayRect.top)
+ .setLabel(`Display - ${display.title}`)
+ .setIsVisible(false)
+ .setIsDisplay(true)
+ .setId(display.stableId)
+ .setDisplayId(display.id)
+ .setIsVirtual(false)
+ .setIsClickable(false)
+ .setCornerRadius(0)
+ .build();
return rect;
}) ?? [];
@@ -228,21 +228,20 @@
entry.windowStates
?.sort((a: any, b: any) => b.computedZ - a.computedZ)
.map((it: any) => {
- const rect: UiRect = {
- x: it.rect.left,
- y: it.rect.top,
- w: it.rect.right - it.rect.left,
- h: it.rect.bottom - it.rect.top,
- label: it.rect.label,
- transform: identityMatrix,
- isVisible: it.isVisible,
- isDisplay: false,
- id: it.stableId,
- displayId: it.displayId,
- isVirtual: false, //TODO: is this correct?
- isClickable: true,
- cornerRadius: 0,
- };
+ const rect = new UiRectBuilder()
+ .setX(it.rect.left)
+ .setY(it.rect.top)
+ .setWidth(it.rect.right - it.rect.left)
+ .setHeight(it.rect.bottom - it.rect.top)
+ .setLabel(it.rect.label)
+ .setIsVisible(it.isVisible)
+ .setIsDisplay(false)
+ .setId(it.stableId)
+ .setDisplayId(it.displayId)
+ .setIsVirtual(false)
+ .setIsClickable(true)
+ .setCornerRadius(0)
+ .build();
return rect;
}) ?? [];