• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
1/*
2 * Copyright (C) 2023 The Android Open Source Project
3 *
4 * Licensed under the Apache License, Version 2.0 (the "License");
5 * you may not use this file except in compliance with the License.
6 * You may obtain a copy of the License at
7 *
8 *      http://www.apache.org/licenses/LICENSE-2.0
9 *
10 * Unless required by applicable law or agreed to in writing, software
11 * distributed under the License is distributed on an "AS IS" BASIS,
12 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13 * See the License for the specific language governing permissions and
14 * limitations under the License.
15 */
16
17import {Timestamp, TimestampType} from 'trace/timestamp';
18import {TraceFile} from 'trace/trace_file';
19import {TraceType} from 'trace/trace_type';
20import {AbstractParser} from './abstract_parser';
21import {ExportedData} from './proto_types';
22
23/* TODO: Support multiple Windows in one file upload.  */
24export class ParserViewCapture extends AbstractParser {
25  private classNames: string[] = [];
26  private realToElapsedTimeOffsetNanos: bigint | undefined = undefined;
27  packageName: string = '';
28  windowTitle: string = '';
29
30  constructor(trace: TraceFile) {
31    super(trace);
32  }
33
34  override getTraceType(): TraceType {
35    return TraceType.VIEW_CAPTURE;
36  }
37
38  override getMagicNumber(): number[] {
39    return ParserViewCapture.MAGIC_NUMBER;
40  }
41
42  override decodeTrace(buffer: Uint8Array): any[] {
43    const exportedData = ExportedData.decode(buffer) as any;
44    this.classNames = exportedData.classname;
45    this.realToElapsedTimeOffsetNanos = BigInt(exportedData.realToElapsedTimeOffsetNanos);
46    this.packageName = this.shortenAndCapitalize(exportedData.package);
47
48    const firstWindowData = exportedData.windowData[0];
49    this.windowTitle = this.shortenAndCapitalize(firstWindowData.title);
50
51    return firstWindowData.frameData;
52  }
53
54  override processDecodedEntry(index: number, timestampType: TimestampType, decodedEntry: any) {
55    this.formatProperties(decodedEntry.node, this.classNames);
56    return decodedEntry;
57  }
58
59  private shortenAndCapitalize(name: string): string {
60    const shortName = name.substring(name.lastIndexOf('.') + 1);
61    return shortName.charAt(0).toUpperCase() + shortName.slice(1);
62  }
63
64  private formatProperties(root: any /* ViewNode */, classNames: string[]): any /* ViewNode */ {
65    const DEPTH_MAGNIFICATION = 4;
66    const VISIBLE = 0;
67
68    function inner(
69      node: any /* ViewNode */,
70      leftShift: number,
71      topShift: number,
72      scaleX: number,
73      scaleY: number,
74      depth: number,
75      isParentVisible: boolean
76    ) {
77      const newScaleX = scaleX * node.scaleX;
78      const newScaleY = scaleY * node.scaleY;
79
80      const l =
81        leftShift +
82        (node.left + node.translationX) * scaleX +
83        (node.width * (scaleX - newScaleX)) / 2;
84      const t =
85        topShift +
86        (node.top + node.translationY) * scaleY +
87        (node.height * (scaleY - newScaleY)) / 2;
88      node.boxPos = {
89        left: l,
90        top: t,
91        width: node.width * newScaleX,
92        height: node.height * newScaleY,
93      };
94
95      node.name = `${classNames[node.classnameIndex]}@${node.hashcode}`;
96
97      node.shortName = node.name.split('.');
98      node.shortName = node.shortName[node.shortName.length - 1];
99
100      node.isVisible = isParentVisible && VISIBLE === node.visibility;
101
102      for (let i = 0; i < node.children.length; i++) {
103        inner(
104          node.children[i],
105          l - node.scrollX,
106          t - node.scrollY,
107          newScaleX,
108          newScaleY,
109          depth + 1,
110          node.isVisible
111        );
112        node.children[i].parent = node;
113      }
114
115      // TODO: Audit these properties
116      node.depth = depth * DEPTH_MAGNIFICATION;
117      node.type = 'ViewNode';
118      node.layerId = 0;
119      node.isMissing = false;
120      node.hwcCompositionType = 0;
121      node.zOrderRelativeOfId = -1;
122      node.isRootLayer = false;
123      node.skip = null;
124      node.id = node.name;
125      node.stableId = node.id;
126      node.equals = (other: any /* ViewNode */) => ParserViewCapture.equals(node, other);
127    }
128
129    root.scaleX = root.scaleY = 1;
130    root.translationX = root.translationY = 0;
131    inner(root, 0, 0, 1, 1, 0, true);
132
133    root.isRootLayer = true;
134    return root;
135  }
136
137  override getTimestamp(timestampType: TimestampType, frameData: any): undefined | Timestamp {
138    return Timestamp.from(
139      timestampType,
140      BigInt(frameData.timestamp),
141      this.realToElapsedTimeOffsetNanos
142    );
143  }
144
145  private static readonly MAGIC_NUMBER = [0x9, 0x78, 0x65, 0x90, 0x65, 0x73, 0x82, 0x65, 0x68];
146
147  /** This method is used by the tree_generator to determine if 2 nodes have equivalent properties. */
148  private static equals(node: any /* ViewNode */, other: any /* ViewNode */): boolean {
149    if (!node && !other) {
150      return true;
151    }
152    if (!node || !other) {
153      return false;
154    }
155    return (
156      node.id === other.id &&
157      node.name === other.name &&
158      node.hashcode === other.hashcode &&
159      node.left === other.left &&
160      node.top === other.top &&
161      node.height === other.height &&
162      node.width === other.width &&
163      node.elevation === other.elevation &&
164      node.scaleX === other.scaleX &&
165      node.scaleY === other.scaleY &&
166      node.scrollX === other.scrollX &&
167      node.scrollY === other.scrollY &&
168      node.translationX === other.translationX &&
169      node.translationY === other.translationY &&
170      node.alpha === other.alpha &&
171      node.visibility === other.visibility &&
172      node.willNotDraw === other.willNotDraw &&
173      node.clipChildren === other.clipChildren &&
174      node.depth === other.depth
175    );
176  }
177}
178