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 {assertTrue} from 'common/assert_utils';
18import {ParserTimestampConverter} from 'common/timestamp_converter';
19import {TraceFile} from 'trace/trace_file';
20import {WasmEngineProxy} from 'trace_processor/wasm_engine_proxy';
21import {ParserViewCaptureWindow} from './parser_view_capture_window';
22
23interface WindowAndPackage {
24  window: string;
25  package: string;
26}
27
28export class ParserViewCapture {
29  private readonly traceFile: TraceFile;
30  private readonly traceProcessor: WasmEngineProxy;
31  private readonly timestampConverter: ParserTimestampConverter;
32
33  private windowParsers: ParserViewCaptureWindow[] = [];
34
35  private static readonly STDLIB_MODULE_NAME = 'android.winscope.viewcapture';
36
37  constructor(
38    traceFile: TraceFile,
39    traceProcessor: WasmEngineProxy,
40    timestampConverter: ParserTimestampConverter,
41  ) {
42    this.traceFile = traceFile;
43    this.traceProcessor = traceProcessor;
44    this.timestampConverter = timestampConverter;
45  }
46
47  async parse() {
48    await this.traceProcessor.query(
49      `INCLUDE PERFETTO MODULE ${ParserViewCapture.STDLIB_MODULE_NAME};`,
50    );
51
52    const windowAndPackageNames = await this.queryWindowAndPackageNames();
53    assertTrue(
54      windowAndPackageNames.length > 0,
55      () => 'Perfetto trace has no ViewCapture windows',
56    );
57
58    this.windowParsers = windowAndPackageNames.map(
59      (windowAndPackage) =>
60        new ParserViewCaptureWindow(
61          this.traceFile,
62          this.traceProcessor,
63          this.timestampConverter,
64          windowAndPackage.package,
65          windowAndPackage.window,
66        ),
67    );
68
69    const parsePromises = this.windowParsers.map((parser) => parser.parse());
70    await Promise.all(parsePromises);
71  }
72
73  getWindowParsers(): ParserViewCaptureWindow[] {
74    return this.windowParsers;
75  }
76
77  private async queryWindowAndPackageNames(): Promise<WindowAndPackage[]> {
78    const sql = `
79        SELECT DISTINCT GROUP_CONCAT(string_value ORDER BY args.key) AS package_and_window
80        FROM android_viewcapture AS vc
81        JOIN args ON vc.arg_set_id = args.arg_set_id
82        WHERE
83          args.key = 'package_name' OR
84          args.key = 'window_name'
85        GROUP BY vc.id
86        ORDER BY package_and_window;
87    `;
88
89    const result = await this.traceProcessor.query(sql).waitAllRows();
90
91    const names: WindowAndPackage[] = [];
92    for (const it = result.iter({}); it.valid(); it.next()) {
93      const packageAndWindow = it.get('package_and_window') as string;
94      const tokens = packageAndWindow.split(',');
95      assertTrue(tokens.length === 2);
96      names.push({package: tokens[0], window: tokens[1]});
97    }
98
99    return names;
100  }
101}
102