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 {Timestamp} from 'common/time';
19import {ParserTimestampConverter} from 'common/timestamp_converter';
20import {AddDefaults} from 'parsers/operations/add_defaults';
21import {SetFormatters} from 'parsers/operations/set_formatters';
22import {
23  MakeTimestampStrategyType,
24  TransformToTimestamp,
25} from 'parsers/operations/transform_to_timestamp';
26import {TamperedMessageType} from 'parsers/tampered_message_type';
27import {perfetto} from 'protos/transitions/latest/static';
28import root from 'protos/transitions/udc/json';
29import {com} from 'protos/transitions/udc/static';
30import {
31  EnumFormatter,
32  PropertyFormatter,
33  TIMESTAMP_NODE_FORMATTER,
34} from 'trace/tree_node/formatters';
35import {PropertyTreeBuilderFromProto} from 'trace/tree_node/property_tree_builder_from_proto';
36import {PropertyTreeNode} from 'trace/tree_node/property_tree_node';
37import {AddDuration} from './operations/add_duration';
38import {AddRealToBootTimeOffsetTimestamp} from './operations/add_real_to_elapsed_time_offset_timestamp';
39import {AddRootProperties} from './operations/add_root_properties';
40import {AddStatus} from './operations/add_status';
41import {UpdateAbortTimeNodes} from './operations/update_abort_time_nodes';
42import {TransitionType} from './transition_type';
43
44interface TransitionInfo {
45  entry:
46    | com.android.server.wm.shell.ITransition
47    | com.android.wm.shell.ITransition
48    | perfetto.protos.IShellTransition;
49  realToBootTimeOffsetNs: bigint | undefined;
50  timestampConverter: ParserTimestampConverter;
51  handlerMapping?: {[key: number]: string};
52}
53
54export class ParserTransitionsUtils {
55  static readonly TRANSITION_OPERATIONS = [
56    new AddDuration(),
57    new AddStatus(),
58    new AddRootProperties(),
59  ];
60
61  private static readonly TransitionTraceProto = TamperedMessageType.tamper(
62    root.lookupType('com.android.server.wm.shell.TransitionTraceProto'),
63  );
64  private static readonly TransitionField =
65    ParserTransitionsUtils.TransitionTraceProto.fields['transitions'];
66  private static readonly WM_ADD_DEFAULTS_OPERATION = new AddDefaults(
67    ParserTransitionsUtils.TransitionField,
68    ['type', 'targets'],
69  );
70  private static readonly SET_FORMATTERS_OPERATION = new SetFormatters();
71  private static readonly PERFETTO_TRANSITION_OPERATIONS = [
72    new UpdateAbortTimeNodes(),
73  ];
74  private static readonly TRANSITION_TYPE_FORMATTER = new EnumFormatter(
75    TransitionType,
76  );
77
78  static makeTransitionPropertiesTree(
79    shellEntryTree: PropertyTreeNode,
80    wmEntryTree: PropertyTreeNode,
81  ): PropertyTreeNode {
82    const transitionTree = new PropertyTreeNode(
83      wmEntryTree.id,
84      wmEntryTree.name,
85      wmEntryTree.source,
86      undefined,
87    );
88
89    transitionTree.addOrReplaceChild(
90      assertDefined(shellEntryTree.getChildByName('shellData')),
91    );
92    transitionTree.addOrReplaceChild(
93      assertDefined(wmEntryTree.getChildByName('wmData')),
94    );
95    ParserTransitionsUtils.TRANSITION_OPERATIONS.forEach((operation) =>
96      operation.apply(transitionTree),
97    );
98    return transitionTree;
99  }
100
101  static makeWmPropertiesTree(
102    info?: TransitionInfo,
103    denylistProperties: string[] = [],
104  ): PropertyTreeNode {
105    const tree = new PropertyTreeBuilderFromProto()
106      .setData({wmData: info?.entry ?? null})
107      .setRootId('TransitionTraceEntry')
108      .setRootName('Selected Transition')
109      .setDenyList(denylistProperties)
110      .setVisitPrototype(false)
111      .build();
112
113    if (!info) {
114      ParserTransitionsUtils.SET_FORMATTERS_OPERATION.apply(tree);
115      return tree;
116    }
117
118    if (denylistProperties.length > 0) {
119      ParserTransitionsUtils.PERFETTO_TRANSITION_OPERATIONS.forEach(
120        (operation) => operation.apply(tree),
121      );
122    }
123
124    let realToBootTimeOffsetTimestamp: Timestamp | undefined;
125
126    if (info.realToBootTimeOffsetNs !== undefined) {
127      realToBootTimeOffsetTimestamp =
128        info.timestampConverter.makeTimestampFromRealNs(
129          info.realToBootTimeOffsetNs,
130        );
131    }
132
133    const wmDataNode = assertDefined(tree.getChildByName('wmData'));
134    new AddRealToBootTimeOffsetTimestamp(realToBootTimeOffsetTimestamp).apply(
135      wmDataNode,
136    );
137    ParserTransitionsUtils.WM_ADD_DEFAULTS_OPERATION.apply(wmDataNode);
138    new TransformToTimestamp(
139      [
140        'abortTimeNs',
141        'createTimeNs',
142        'sendTimeNs',
143        'finishTimeNs',
144        'startingWindowRemoveTimeNs',
145      ],
146      ParserTransitionsUtils.makeTimestampStrategy(info.timestampConverter),
147    ).apply(wmDataNode);
148
149    const customFormatters = new Map<string, PropertyFormatter>([
150      ['type', ParserTransitionsUtils.TRANSITION_TYPE_FORMATTER],
151      ['mode', ParserTransitionsUtils.TRANSITION_TYPE_FORMATTER],
152      ['abortTimeNs', TIMESTAMP_NODE_FORMATTER],
153      ['createTimeNs', TIMESTAMP_NODE_FORMATTER],
154      ['sendTimeNs', TIMESTAMP_NODE_FORMATTER],
155      ['finishTimeNs', TIMESTAMP_NODE_FORMATTER],
156      ['startingWindowRemoveTimeNs', TIMESTAMP_NODE_FORMATTER],
157    ]);
158
159    new SetFormatters(undefined, customFormatters).apply(tree);
160    return tree;
161  }
162
163  static makeShellPropertiesTree(
164    info?: TransitionInfo,
165    denylistProperties: string[] = [],
166  ): PropertyTreeNode {
167    const tree = new PropertyTreeBuilderFromProto()
168      .setData({shellData: info?.entry ?? null})
169      .setRootId('TransitionTraceEntry')
170      .setRootName('Selected Transition')
171      .setDenyList(denylistProperties)
172      .setVisitPrototype(false)
173      .build();
174
175    if (!info) {
176      ParserTransitionsUtils.SET_FORMATTERS_OPERATION.apply(tree);
177      return tree;
178    }
179
180    if (denylistProperties.length > 0) {
181      ParserTransitionsUtils.PERFETTO_TRANSITION_OPERATIONS.forEach(
182        (operation) => operation.apply(tree),
183      );
184    }
185
186    let realToBootTimeOffsetTimestamp: Timestamp | undefined;
187    if (info.realToBootTimeOffsetNs !== undefined) {
188      realToBootTimeOffsetTimestamp =
189        info.timestampConverter.makeTimestampFromRealNs(
190          info.realToBootTimeOffsetNs,
191        );
192    }
193
194    const shellDataNode = assertDefined(tree.getChildByName('shellData'));
195    new AddRealToBootTimeOffsetTimestamp(realToBootTimeOffsetTimestamp).apply(
196      shellDataNode,
197    );
198    new TransformToTimestamp(
199      ['dispatchTimeNs', 'mergeRequestTimeNs', 'mergeTimeNs', 'abortTimeNs'],
200      ParserTransitionsUtils.makeTimestampStrategy(info.timestampConverter),
201    ).apply(shellDataNode);
202
203    const customFormatters = new Map<string, PropertyFormatter>([
204      ['type', ParserTransitionsUtils.TRANSITION_TYPE_FORMATTER],
205      ['mode', ParserTransitionsUtils.TRANSITION_TYPE_FORMATTER],
206      ['dispatchTimeNs', TIMESTAMP_NODE_FORMATTER],
207      ['mergeRequestTimeNs', TIMESTAMP_NODE_FORMATTER],
208      ['mergeTimeNs', TIMESTAMP_NODE_FORMATTER],
209      ['abortTimeNs', TIMESTAMP_NODE_FORMATTER],
210    ]);
211
212    if (info.handlerMapping) {
213      customFormatters.set('handler', new EnumFormatter(info.handlerMapping));
214    }
215
216    new SetFormatters(undefined, customFormatters).apply(tree);
217
218    return tree;
219  }
220
221  private static makeTimestampStrategy(
222    timestampConverter: ParserTimestampConverter,
223  ): MakeTimestampStrategyType {
224    return (valueNs: bigint) => {
225      return timestampConverter.makeTimestampFromBootTimeNs(valueNs);
226    };
227  }
228}
229