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/timestamp_converter';
19import {AddDefaults} from 'parsers/operations/add_defaults';
20import {SetFormatters} from 'parsers/operations/set_formatters';
21import {AbstractParser} from 'parsers/perfetto/abstract_parser';
22import {FakeProtoTransformer} from 'parsers/perfetto/fake_proto_transformer';
23import {Utils} from 'parsers/perfetto/utils';
24import {TamperedMessageType} from 'parsers/tampered_message_type';
25import {RectsComputation} from 'parsers/view_capture/computations/rects_computation';
26import {VisibilityComputation} from 'parsers/view_capture/computations/visibility_computation';
27import root from 'protos/viewcapture/latest/json';
28import {perfetto} from 'protos/viewcapture/latest/static';
29import {
30  CustomQueryParserResultTypeMap,
31  CustomQueryType,
32  VisitableParserCustomQuery,
33} from 'trace/custom_query';
34import {EntriesRange} from 'trace/index_types';
35import {TraceFile} from 'trace/trace_file';
36import {TraceType} from 'trace/trace_type';
37import {HierarchyTreeNode} from 'trace/tree_node/hierarchy_tree_node';
38import {PropertiesProvider} from 'trace/tree_node/properties_provider';
39import {PropertiesProviderBuilder} from 'trace/tree_node/properties_provider_builder';
40import {PropertyTreeBuilderFromProto} from 'trace/tree_node/property_tree_builder_from_proto';
41import {PropertyTreeNode} from 'trace/tree_node/property_tree_node';
42import {WasmEngineProxy} from 'trace_processor/wasm_engine_proxy';
43import {HierarchyTreeBuilderVc} from './hierarchy_tree_builder_vc';
44
45export class ParserViewCaptureWindow extends AbstractParser<HierarchyTreeNode> {
46  private static readonly PROTO_WRAPPER_MESSAGE = TamperedMessageType.tamper(
47    root.lookupType('perfetto.protos.Wrapper'),
48  );
49  private static readonly PROTO_VIEWCAPTURE_FIELD =
50    ParserViewCaptureWindow.PROTO_WRAPPER_MESSAGE.fields['viewcapture'];
51  private static readonly PROTO_VIEW_FIELD = assertDefined(
52    ParserViewCaptureWindow.PROTO_VIEWCAPTURE_FIELD.tamperedMessageType?.fields[
53      'views'
54    ],
55  );
56
57  private static readonly PROPERTY_TREE_OPERATIONS = [
58    new AddDefaults(ParserViewCaptureWindow.PROTO_VIEW_FIELD),
59    new SetFormatters(ParserViewCaptureWindow.PROTO_VIEW_FIELD),
60  ];
61
62  private readonly packageName: string;
63  private readonly windowName: string;
64  private readonly protoTransformer: FakeProtoTransformer;
65
66  constructor(
67    traceFile: TraceFile,
68    traceProcessor: WasmEngineProxy,
69    timestampConverter: ParserTimestampConverter,
70    packageName: string,
71    windowName: string,
72  ) {
73    super(traceFile, traceProcessor, timestampConverter);
74
75    this.packageName = packageName;
76    this.windowName = windowName;
77
78    this.protoTransformer = new FakeProtoTransformer(
79      assertDefined(
80        ParserViewCaptureWindow.PROTO_VIEWCAPTURE_FIELD.tamperedMessageType,
81      ),
82    );
83  }
84
85  override getTraceType(): TraceType {
86    return TraceType.VIEW_CAPTURE;
87  }
88
89  override async getEntry(index: number): Promise<HierarchyTreeNode> {
90    let entry = (await Utils.queryEntry(
91      this.traceProcessor,
92      this.getTableName(),
93      this.entryIndexToRowIdMap,
94      index,
95    )) as perfetto.protos.IViewCapture;
96    entry = this.protoTransformer.transform(entry);
97
98    const views = this.makeViewPropertyProviders(entry);
99
100    const rootView = assertDefined(
101      views.find((view) => {
102        const parentId = assertDefined(
103          view.getEagerProperties().getChildByName('parentId'),
104        ).getValue() as number;
105        return parentId === -1;
106      }),
107    );
108
109    const childrenViews = views.filter((view) => view !== rootView);
110
111    return new HierarchyTreeBuilderVc()
112      .setRoot(rootView)
113      .setChildren(childrenViews)
114      .setComputations([new VisibilityComputation(), new RectsComputation()])
115      .build();
116  }
117
118  override customQuery<Q extends CustomQueryType>(
119    type: Q,
120    entriesRange: EntriesRange,
121  ): Promise<CustomQueryParserResultTypeMap[Q]> {
122    return new VisitableParserCustomQuery(type)
123      .visit(CustomQueryType.VIEW_CAPTURE_METADATA, async () => {
124        const metadata = {
125          packageName: this.packageName,
126          windowName: this.windowName,
127        };
128        return Promise.resolve(metadata);
129      })
130      .getResult();
131  }
132
133  protected override getStdLibModuleName(): string | undefined {
134    return 'android.winscope.viewcapture';
135  }
136
137  protected override getTableName(): string {
138    return 'android_viewcapture';
139  }
140
141  override async buildEntryIndexToRowIdMap(): Promise<number[]> {
142    const sqlRowIdAndTimestamp = `
143        SELECT vc.id as id, vc.ts as ts
144        FROM ${this.getTableName()} AS vc
145        JOIN args ON vc.arg_set_id = args.arg_set_id
146        WHERE
147          args.key = 'window_name' AND
148          args.string_value = '${this.windowName}'
149        ORDER BY vc.ts;
150    `;
151    const result = await this.traceProcessor
152      .query(sqlRowIdAndTimestamp)
153      .waitAllRows();
154    const entryIndexToRowId: number[] = [];
155    for (const it = result.iter({}); it.valid(); it.next()) {
156      const rowId = Number(it.get('id') as bigint);
157      entryIndexToRowId.push(rowId);
158    }
159    return entryIndexToRowId;
160  }
161
162  private makeViewPropertyProviders(
163    entry: perfetto.protos.IViewCapture,
164  ): PropertiesProvider[] {
165    const providers = (entry.views ?? []).map((view) => {
166      const allProperties = this.makeViewPropertyTree(view);
167      const provider = new PropertiesProviderBuilder()
168        .setEagerProperties(allProperties)
169        .setCommonOperations(ParserViewCaptureWindow.PROPERTY_TREE_OPERATIONS)
170        .build();
171
172      return provider;
173    });
174
175    return providers;
176  }
177
178  private makeViewPropertyTree(
179    view: perfetto.protos.ViewCapture.IView,
180  ): PropertyTreeNode {
181    const rootName = `${(view as any).className}@${view.hashcode}`;
182
183    const nodeProperties = new PropertyTreeBuilderFromProto()
184      .setData(view)
185      .setRootId('root-view')
186      .setRootName(rootName)
187      .build();
188
189    return nodeProperties;
190  }
191}
192