1/*
2 * Copyright (C) 2022 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 {PersistentStoreProxy} from 'common/persistent_store_proxy';
19import {Timestamp} from 'common/time';
20import {WinscopeEvent, WinscopeEventType} from 'messaging/winscope_event';
21import {Trace, TraceEntry} from 'trace/trace';
22import {Traces} from 'trace/traces';
23import {ImeTraceType, TraceType} from 'trace/trace_type';
24import {HierarchyTreeNode} from 'trace/tree_node/hierarchy_tree_node';
25import {PropertyTreeNode} from 'trace/tree_node/property_tree_node';
26import {TreeNode} from 'trace/tree_node/tree_node';
27import {ImeAdditionalProperties} from 'viewers/common/ime_additional_properties';
28import {ImeUiData} from 'viewers/common/ime_ui_data';
29import {
30  ImeLayers,
31  ImeUtils,
32  ProcessedWindowManagerState,
33} from 'viewers/common/ime_utils';
34import {TableProperties} from 'viewers/common/table_properties';
35import {UserOptions} from 'viewers/common/user_options';
36import {
37  AbstractHierarchyViewerPresenter,
38  NotifyHierarchyViewCallbackType,
39} from './abstract_hierarchy_viewer_presenter';
40import {VISIBLE_CHIP} from './chip';
41import {HierarchyPresenter} from './hierarchy_presenter';
42import {PropertiesPresenter} from './properties_presenter';
43import {UiHierarchyTreeNode} from './ui_hierarchy_tree_node';
44import {UiTreeUtils} from './ui_tree_utils';
45
46export abstract class AbstractPresenterInputMethod extends AbstractHierarchyViewerPresenter {
47  protected getHierarchyTreeNameStrategy = (
48    entry: TraceEntry<HierarchyTreeNode>,
49    tree: HierarchyTreeNode,
50  ) => {
51    const where = tree.getEagerPropertyByName('where')?.formattedValue();
52    return entry.getTimestamp().format() + ' - ' + where;
53  };
54  protected override hierarchyPresenter = new HierarchyPresenter(
55    PersistentStoreProxy.new<UserOptions>(
56      'ImeHierarchyOptions',
57      {
58        simplifyNames: {
59          name: 'Simplify names',
60          enabled: true,
61        },
62        showOnlyVisible: {
63          name: 'Show only',
64          chip: VISIBLE_CHIP,
65          enabled: false,
66        },
67        flat: {
68          name: 'Flat',
69          enabled: false,
70        },
71      },
72      this.storage,
73    ),
74    [],
75    true,
76    false,
77    this.getHierarchyTreeNameStrategy,
78  );
79  protected override propertiesPresenter = new PropertiesPresenter(
80    PersistentStoreProxy.new<UserOptions>(
81      'ImePropertiesOptions',
82      {
83        showDefaults: {
84          name: 'Show defaults',
85          enabled: false,
86          tooltip: `
87                If checked, shows the value of all properties.
88                Otherwise, hides all properties whose value is
89                the default for its data type.
90              `,
91        },
92      },
93      this.storage,
94    ),
95    [],
96  );
97  protected override multiTraceType = undefined;
98
99  protected readonly imeTrace: Trace<HierarchyTreeNode>;
100  private readonly wmTrace?: Trace<HierarchyTreeNode>;
101  private readonly sfTrace?: Trace<HierarchyTreeNode>;
102
103  private hierarchyTableProperties: TableProperties | undefined;
104  private additionalProperties: ImeAdditionalProperties | undefined;
105
106  constructor(
107    trace: Trace<HierarchyTreeNode>,
108    traces: Traces,
109    storage: Storage,
110    notifyViewCallback: NotifyHierarchyViewCallbackType,
111  ) {
112    super(
113      trace,
114      traces,
115      storage,
116      notifyViewCallback,
117      new ImeUiData(trace.type as ImeTraceType),
118    );
119    this.imeTrace = trace;
120    this.sfTrace = traces.getTrace(TraceType.SURFACE_FLINGER);
121    this.wmTrace = traces.getTrace(TraceType.WINDOW_MANAGER);
122  }
123
124  async onAppEvent(event: WinscopeEvent) {
125    await event.visit(
126      WinscopeEventType.TRACE_POSITION_UPDATE,
127      async (event) => {
128        this.clearOverridePropertiesTreeSelection();
129        await this.applyTracePositionUpdate(event);
130
131        const imeEntry = this.hierarchyPresenter.getCurrentEntryForTrace(
132          this.imeTrace,
133        );
134        const [sfEntry, wmEntry] = this.findSfWmTraceEntries(imeEntry);
135
136        if (imeEntry) {
137          this.additionalProperties = await this.getAdditionalProperties(
138            await wmEntry?.getValue(),
139            await sfEntry?.getValue(),
140            wmEntry?.getTimestamp(),
141            sfEntry?.getTimestamp(),
142          );
143          this.hierarchyTableProperties = this.getHierarchyTableProperties();
144
145          await this.updateOverridePropertiesTree(this.additionalProperties);
146
147          const highlightedItem = this.getHighlightedItem();
148          const selected = this.hierarchyPresenter.getSelectedTree();
149
150          if (!selected && highlightedItem !== undefined) {
151            const isHighlightedFilter = (node: HierarchyTreeNode) =>
152              UiTreeUtils.isHighlighted(node, highlightedItem);
153            let selectedTree =
154              this.additionalProperties?.sf?.taskLayerOfImeContainer?.findDfs(
155                isHighlightedFilter,
156              );
157            if (!selectedTree) {
158              selectedTree =
159                this.additionalProperties?.sf?.taskLayerOfImeSnapshot?.findDfs(
160                  isHighlightedFilter,
161                );
162            }
163
164            if (selectedTree) {
165              this.hierarchyPresenter.setSelectedTree([
166                assertDefined(this.sfTrace),
167                selectedTree,
168              ]);
169              await this.updatePropertiesTree();
170            }
171          }
172        }
173        this.refreshUIData();
174      },
175    );
176  }
177
178  async onHighlightedNodeChange(node: UiHierarchyTreeNode) {
179    this.clearOverridePropertiesTreeSelection();
180    await this.applyHighlightedNodeChange(node);
181    this.refreshUIData();
182  }
183
184  async onHighlightedIdChange(newId: string) {
185    const selectedHierarchyTree = this.hierarchyPresenter.getSelectedTree();
186    if (!selectedHierarchyTree || selectedHierarchyTree[1].id !== newId) {
187      this.clearOverridePropertiesTreeSelection();
188    }
189    await this.applyHighlightedIdChange(newId);
190    this.refreshUIData();
191  }
192
193  async onAdditionalPropertySelected(selectedItem: {
194    name: string;
195    treeNode: TreeNode;
196  }) {
197    this.updateHighlightedItem(selectedItem.treeNode.id);
198    if (selectedItem.treeNode instanceof HierarchyTreeNode) {
199      this.clearOverridePropertiesTreeSelection();
200      this.hierarchyPresenter.setSelectedTree([
201        assertDefined(this.wmTrace),
202        selectedItem.treeNode,
203      ]);
204    } else if (selectedItem.treeNode instanceof PropertyTreeNode) {
205      this.hierarchyPresenter.setSelectedTree(undefined);
206      this.overridePropertiesTree = selectedItem.treeNode;
207    }
208
209    this.overridePropertiesTreeName = selectedItem.name;
210    await this.updatePropertiesTree();
211    this.refreshUIData();
212  }
213
214  protected async getAdditionalProperties(
215    wmEntry: HierarchyTreeNode | undefined,
216    sfEntry: HierarchyTreeNode | undefined,
217    wmEntryTimestamp: Timestamp | undefined,
218    sfEntryTimestamp: Timestamp | undefined,
219  ): Promise<ImeAdditionalProperties> {
220    let wmProperties: ProcessedWindowManagerState | undefined;
221    let sfProperties: ImeLayers | undefined;
222
223    if (wmEntry) {
224      wmProperties = ImeUtils.processWindowManagerTraceEntry(
225        wmEntry,
226        wmEntryTimestamp,
227      );
228
229      if (sfEntry) {
230        sfProperties = ImeUtils.getImeLayers(
231          sfEntry,
232          wmProperties,
233          sfEntryTimestamp,
234        );
235
236        if (sfProperties) {
237          await this.makeSfSubtrees(sfProperties);
238        }
239      }
240    }
241
242    return new ImeAdditionalProperties(wmProperties, sfProperties);
243  }
244
245  protected override keepCalculated(tree: HierarchyTreeNode): boolean {
246    return false;
247  }
248
249  protected override getOverrideDisplayName(
250    selected: [Trace<HierarchyTreeNode>, HierarchyTreeNode],
251  ): string | undefined {
252    return this.overridePropertiesTreeName;
253  }
254
255  private async makeSfSubtrees(
256    sfProperties: ImeLayers,
257  ): Promise<UiHierarchyTreeNode[]> {
258    const sfHierarchyTrees = [];
259    const sfTrace = assertDefined(this.sfTrace);
260    if (sfProperties.taskLayerOfImeContainer) {
261      sfHierarchyTrees.push(sfProperties.taskLayerOfImeContainer);
262    }
263    if (sfProperties.taskLayerOfImeSnapshot) {
264      sfHierarchyTrees.push(sfProperties.taskLayerOfImeSnapshot);
265    }
266    if (sfHierarchyTrees.length > 0) {
267      await this.hierarchyPresenter.addCurrentHierarchyTrees(
268        [sfTrace, sfHierarchyTrees],
269        this.getHighlightedItem(),
270      );
271    }
272    const sfSubtrees = assertDefined(
273      this.hierarchyPresenter.getFormattedTreesByTrace(sfTrace),
274    );
275    sfSubtrees.forEach((subtree) =>
276      subtree.setDisplayName('SfSubtree - ' + subtree.name),
277    );
278    return sfSubtrees;
279  }
280
281  private clearOverridePropertiesTreeSelection() {
282    this.overridePropertiesTree = undefined;
283    this.overridePropertiesTreeName = undefined;
284  }
285
286  private findSfWmTraceEntries(
287    imeEntry: TraceEntry<HierarchyTreeNode> | undefined,
288  ): [
289    TraceEntry<HierarchyTreeNode> | undefined,
290    TraceEntry<HierarchyTreeNode> | undefined,
291  ] {
292    if (!imeEntry || !this.imeTrace.hasFrameInfo()) {
293      return [undefined, undefined];
294    }
295
296    const frames = imeEntry.getFramesRange();
297    if (!frames || frames.start === frames.end) {
298      return [undefined, undefined];
299    }
300
301    const frame = frames.start;
302    const sfEntry = this.sfTrace
303      ?.getFrame(frame)
304      ?.findClosestEntry(imeEntry.getTimestamp());
305    const wmEntry = this.wmTrace
306      ?.getFrame(frame)
307      ?.findClosestEntry(imeEntry.getTimestamp());
308
309    return [sfEntry, wmEntry];
310  }
311
312  private async updateOverridePropertiesTree(
313    additionalProperties: ImeAdditionalProperties,
314  ) {
315    const highlightedItem = this.getHighlightedItem();
316    if (!highlightedItem) {
317      this.clearOverridePropertiesTreeSelection();
318      return;
319    }
320    if (highlightedItem.includes('WindowManagerState')) {
321      this.overridePropertiesTree = undefined;
322      const wmHierarchyTree = additionalProperties.wm?.hierarchyTree;
323      this.hierarchyPresenter.setSelectedTree(
324        wmHierarchyTree
325          ? [assertDefined(this.wmTrace), wmHierarchyTree]
326          : undefined,
327      );
328      this.overridePropertiesTreeName = wmHierarchyTree
329        ? 'Window Manager State'
330        : undefined;
331    } else if (highlightedItem.includes('imeInsetsSourceProvider')) {
332      this.hierarchyPresenter.setSelectedTree(undefined);
333      const imeInsetsSourceProvider =
334        additionalProperties.wm?.wmStateProperties?.imeInsetsSourceProvider;
335      this.overridePropertiesTree = imeInsetsSourceProvider;
336      this.overridePropertiesTreeName = imeInsetsSourceProvider
337        ? 'Ime Insets Source Provider'
338        : undefined;
339    } else if (highlightedItem.includes('inputMethodControlTarget')) {
340      this.hierarchyPresenter.setSelectedTree(undefined);
341      const imeControlTarget =
342        additionalProperties.wm?.wmStateProperties?.imeControlTarget;
343      this.overridePropertiesTree = imeControlTarget;
344      this.overridePropertiesTreeName = imeControlTarget
345        ? 'Ime Control Target'
346        : undefined;
347    } else if (highlightedItem.includes('inputMethodInputTarget')) {
348      this.hierarchyPresenter.setSelectedTree(undefined);
349      const imeInputTarget =
350        additionalProperties.wm?.wmStateProperties?.imeInputTarget;
351      this.overridePropertiesTree = imeInputTarget;
352      this.overridePropertiesTreeName = imeInputTarget
353        ? 'Ime Input Target'
354        : undefined;
355    } else if (highlightedItem.includes('inputMethodTarget')) {
356      this.hierarchyPresenter.setSelectedTree(undefined);
357      const imeLayeringTarget =
358        additionalProperties.wm?.wmStateProperties?.imeLayeringTarget;
359      this.overridePropertiesTree = imeLayeringTarget;
360      this.overridePropertiesTreeName = imeLayeringTarget
361        ? 'Ime Layering Target'
362        : undefined;
363    }
364
365    await this.updatePropertiesTree();
366  }
367
368  private refreshUIData() {
369    this.refreshHierarchyViewerUiData(
370      new ImeUiData(
371        this.imeTrace.type as ImeTraceType,
372        this.hierarchyTableProperties,
373        this.additionalProperties,
374      ),
375    );
376  }
377
378  protected abstract getHierarchyTableProperties(): TableProperties;
379}
380