• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
1/*
2 * Copyright (C) 2024 The Android Open Source Project
3 *
4 * Licensed under the Apache License, Version 2.0 (the "License");
5 * you may not use this file except in compliance with the License.
6 * You may obtain a copy of the License at
7 *
8 *      http://www.apache.org/licenses/LICENSE-2.0
9 *
10 * Unless required by applicable law or agreed to in writing, software
11 * distributed under the License is distributed on an "AS IS" BASIS,
12 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13 * See the License for the specific language governing permissions and
14 * limitations under the License.
15 */
16
17import {assertDefined} from 'common/assert_utils';
18import {ParserTimestampConverter} from 'common/time/timestamp_converter';
19import {AddDefaults} from 'parsers/operations/add_defaults';
20import {SetFormatters} from 'parsers/operations/set_formatters';
21import {AbstractParser} from 'parsers/perfetto/abstract_parser';
22import {FakeProto, FakeProtoBuilder} from 'parsers/perfetto/fake_proto_builder';
23import {FakeProtoTransformer} from 'parsers/perfetto/fake_proto_transformer';
24import {Utils} from 'parsers/perfetto/utils';
25import {TamperedMessageType} from 'parsers/tampered_message_type';
26import {RectsComputation} from 'parsers/view_capture/computations/rects_computation';
27import {VisibilityComputation} from 'parsers/view_capture/computations/visibility_computation';
28import root from 'protos/viewcapture/latest/json';
29import {perfetto} from 'protos/viewcapture/latest/static';
30import {
31  CustomQueryParserResultTypeMap,
32  CustomQueryType,
33  VisitableParserCustomQuery,
34} from 'trace/custom_query';
35import {EntriesRange} from 'trace/index_types';
36import {TraceFile} from 'trace/trace_file';
37import {TraceType} from 'trace/trace_type';
38import {HierarchyTreeNode} from 'trace/tree_node/hierarchy_tree_node';
39import {PropertiesProvider} from 'trace/tree_node/properties_provider';
40import {PropertiesProviderBuilder} from 'trace/tree_node/properties_provider_builder';
41import {PropertyTreeBuilderFromProto} from 'trace/tree_node/property_tree_builder_from_proto';
42import {PropertyTreeNode} from 'trace/tree_node/property_tree_node';
43import {TraceProcessor} from 'trace_processor/trace_processor';
44import {HierarchyTreeBuilderVc} from './hierarchy_tree_builder_vc';
45
46export class ParserViewCaptureWindow extends AbstractParser<HierarchyTreeNode> {
47  private static readonly PROTO_WRAPPER_MESSAGE = TamperedMessageType.tamper(
48    root.lookupType('perfetto.protos.Wrapper'),
49  );
50  private static readonly PROTO_VIEWCAPTURE_FIELD =
51    ParserViewCaptureWindow.PROTO_WRAPPER_MESSAGE.fields['viewcapture'];
52  private static readonly PROTO_VIEW_FIELD = assertDefined(
53    ParserViewCaptureWindow.PROTO_VIEWCAPTURE_FIELD.tamperedMessageType?.fields[
54      'views'
55    ],
56  );
57
58  private static readonly PROPERTY_TREE_OPERATIONS = [
59    new AddDefaults(ParserViewCaptureWindow.PROTO_VIEW_FIELD),
60    new SetFormatters(ParserViewCaptureWindow.PROTO_VIEW_FIELD),
61  ];
62
63  private readonly packageName: string;
64  private readonly windowName: string;
65  private readonly snapshotProtoTransformer: FakeProtoTransformer;
66  private readonly viewProtoTransformer: FakeProtoTransformer;
67
68  constructor(
69    traceFile: TraceFile,
70    traceProcessor: TraceProcessor,
71    timestampConverter: ParserTimestampConverter,
72    packageName: string,
73    windowName: string,
74  ) {
75    super(traceFile, traceProcessor, timestampConverter);
76    this.packageName = packageName;
77    this.windowName = windowName;
78    this.snapshotProtoTransformer = new FakeProtoTransformer(
79      assertDefined(
80        ParserViewCaptureWindow.PROTO_VIEWCAPTURE_FIELD.tamperedMessageType,
81      ),
82    );
83    this.viewProtoTransformer = new FakeProtoTransformer(
84      assertDefined(
85        ParserViewCaptureWindow.PROTO_VIEW_FIELD.tamperedMessageType,
86      ),
87    );
88  }
89
90  override getTraceType(): TraceType {
91    return TraceType.VIEW_CAPTURE;
92  }
93
94  override getDescriptors(): string[] {
95    return [this.windowName, ...super.getDescriptors()];
96  }
97
98  override async getEntry(index: number): Promise<HierarchyTreeNode> {
99    let snapshotProto = (await Utils.queryEntry(
100      this.traceProcessor,
101      this.getTableName(),
102      this.entryIndexToRowIdMap,
103      index,
104    )) as perfetto.protos.IViewCapture;
105    snapshotProto = this.snapshotProtoTransformer.transform(snapshotProto);
106
107    const viewProtos = (await this.queryViews(index)).map((viewProto) =>
108      this.viewProtoTransformer.transform(viewProto),
109    );
110    const views = this.makeViewPropertyProviders(viewProtos);
111
112    const rootView = assertDefined(
113      views.find((view) => {
114        const parentId = assertDefined(
115          view.getEagerProperties().getChildByName('parentId'),
116        ).getValue() as number;
117        return parentId === -1;
118      }),
119    );
120    const childrenViews = views.filter((view) => view !== rootView);
121
122    return new HierarchyTreeBuilderVc()
123      .setRoot(rootView)
124      .setChildren(childrenViews)
125      .setComputations([new VisibilityComputation(), new RectsComputation()])
126      .build();
127  }
128
129  override customQuery<Q extends CustomQueryType>(
130    type: Q,
131    entriesRange: EntriesRange,
132  ): Promise<CustomQueryParserResultTypeMap[Q]> {
133    return new VisitableParserCustomQuery(type)
134      .visit(CustomQueryType.VIEW_CAPTURE_METADATA, async () => {
135        const metadata = {
136          packageName: this.packageName,
137          windowName: this.windowName,
138        };
139        return Promise.resolve(metadata);
140      })
141      .getResult();
142  }
143
144  protected override getStdLibModuleName(): string | undefined {
145    return 'android.winscope.viewcapture';
146  }
147
148  protected override getTableName(): string {
149    return 'android_viewcapture';
150  }
151
152  override async buildEntryIndexToRowIdMap(): Promise<number[]> {
153    const sqlRowIdAndTimestamp = `
154        SELECT vc.id as id, vc.ts as ts
155        FROM ${this.getTableName()} AS vc
156        JOIN args ON vc.arg_set_id = args.arg_set_id
157        WHERE
158          args.key = 'window_name' AND
159          args.string_value = '${this.windowName}'
160        ORDER BY vc.ts;
161    `;
162    const result = await this.traceProcessor.queryAllRows(sqlRowIdAndTimestamp);
163    const entryIndexToRowId: number[] = [];
164    for (const it = result.iter({}); it.valid(); it.next()) {
165      const rowId = Number(it.get('id') as bigint);
166      entryIndexToRowId.push(rowId);
167    }
168    return entryIndexToRowId;
169  }
170
171  private async queryViews(
172    index: number,
173  ): Promise<perfetto.protos.ViewCapture.IView[]> {
174    const idToBuilder = new Map<number, FakeProtoBuilder>();
175    const getBuilder = (id: number) => {
176      if (!idToBuilder.has(id)) {
177        idToBuilder.set(id, new FakeProtoBuilder());
178      }
179      return assertDefined(idToBuilder.get(id));
180    };
181
182    const sql = `
183      SELECT
184          vcv.snapshot_id,
185          vcv.id as node_id,
186          args.key,
187          args.value_type,
188          args.int_value,
189          args.string_value,
190          args.real_value
191      FROM
192          __intrinsic_viewcapture_view as vcv
193          INNER JOIN args ON vcv.arg_set_id = args.arg_set_id
194      WHERE snapshot_id = ${this.entryIndexToRowIdMap[index]};
195    `;
196    const result = await this.traceProcessor.queryAllRows(sql);
197
198    for (const it = result.iter({}); it.valid(); it.next()) {
199      const builder = getBuilder(it.get('node_id') as number);
200      builder.addArg(
201        it.get('key') as string,
202        it.get('value_type') as string,
203        it.get('int_value') as bigint | undefined,
204        it.get('real_value') as number | undefined,
205        it.get('string_value') as string | undefined,
206      );
207    }
208
209    const viewProtos: perfetto.protos.ViewCapture.IView[] = [];
210    idToBuilder.forEach((builder) => {
211      viewProtos.push(builder.build());
212    });
213    return viewProtos;
214  }
215
216  private makeViewPropertyProviders(
217    views: perfetto.protos.ViewCapture.View[],
218  ): PropertiesProvider[] {
219    const providers = views.map((view) => {
220      const allProperties = this.makeViewPropertyTree(view);
221      const provider = new PropertiesProviderBuilder()
222        .setEagerProperties(allProperties)
223        .setCommonOperations(ParserViewCaptureWindow.PROPERTY_TREE_OPERATIONS)
224        .build();
225
226      return provider;
227    });
228
229    return providers;
230  }
231
232  private makeViewPropertyTree(
233    view: perfetto.protos.ViewCapture.IView,
234  ): PropertyTreeNode {
235    const rootName = `${(view as FakeProto).className}@${view.hashcode}`;
236
237    const nodeProperties = new PropertyTreeBuilderFromProto()
238      .setData(view)
239      .setRootId('root-view')
240      .setRootName(rootName)
241      .build();
242
243    return nodeProperties;
244  }
245}
246