xref: /aosp_15_r20/development/tools/winscope/src/viewers/common/ime_utils.ts (revision 90c8c64db3049935a07c6143d7fd006e26f8ecca)
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 */
16import {assertDefined} from 'common/assert_utils';
17import {FilterFlag} from 'common/filter_flag';
18import {Timestamp} from 'common/time';
19import {Item} from 'trace/item';
20import {HierarchyTreeNode} from 'trace/tree_node/hierarchy_tree_node';
21import {PropertyTreeNode} from 'trace/tree_node/property_tree_node';
22import {WindowType} from 'trace/window_type';
23import {TextFilter} from 'viewers/common/text_filter';
24import {WmImeUtils} from 'viewers/common/wm_ime_utils';
25import {TreeNodeFilter, UiTreeUtils} from './ui_tree_utils';
26
27interface WmStateProperties {
28  timestamp: string | undefined;
29  focusedApp: string | undefined;
30  focusedWindow: string | undefined;
31  focusedActivity: string | undefined;
32  isInputMethodWindowVisible: boolean;
33  imeInputTarget: PropertyTreeNode | undefined;
34  imeLayeringTarget: PropertyTreeNode | undefined;
35  imeInsetsSourceProvider: PropertyTreeNode | undefined;
36  imeControlTarget: PropertyTreeNode | undefined;
37}
38
39export class ProcessedWindowManagerState implements Item {
40  constructor(
41    readonly id: string,
42    readonly name: string,
43    readonly wmStateProperties: WmStateProperties,
44    readonly hierarchyTree: HierarchyTreeNode,
45  ) {}
46}
47
48export interface ImeContainerProperties {
49  id: string;
50  zOrderRelativeOfId: number;
51  z: number;
52}
53
54export interface InputMethodSurfaceProperties {
55  id: string;
56  isVisible: boolean;
57  screenBounds?: PropertyTreeNode;
58  rect?: PropertyTreeNode;
59}
60
61interface RootImeProperties {
62  timestamp: string;
63}
64
65interface ImeLayerProperties {
66  imeContainer: ImeContainerProperties | undefined;
67  inputMethodSurface: InputMethodSurfaceProperties | undefined;
68  focusedWindowColor: PropertyTreeNode | undefined;
69  root: RootImeProperties | undefined;
70}
71
72export class ImeLayers implements Item {
73  constructor(
74    readonly id: string,
75    readonly name: string,
76    readonly properties: ImeLayerProperties,
77    readonly taskLayerOfImeContainer: HierarchyTreeNode | undefined,
78    readonly taskLayerOfImeSnapshot: HierarchyTreeNode | undefined,
79  ) {}
80}
81
82class ImeAdditionalPropertiesUtils {
83  private isInputMethodSurface = UiTreeUtils.makeNodeFilter(
84    new TextFilter('InputMethod').getFilterPredicate(),
85  );
86  private isImeContainer = UiTreeUtils.makeNodeFilter(
87    new TextFilter('ImeContainer').getFilterPredicate(),
88  );
89
90  processWindowManagerTraceEntry(
91    entry: HierarchyTreeNode,
92    wmEntryTimestamp: Timestamp | undefined,
93  ): ProcessedWindowManagerState {
94    const displayContent = entry.getAllChildren()[0];
95
96    const props: WmStateProperties = {
97      timestamp: wmEntryTimestamp ? wmEntryTimestamp.format() : undefined,
98      focusedApp: entry.getEagerPropertyByName('focusedApp')?.getValue(),
99      focusedWindow: this.getFocusedWindowString(entry),
100      focusedActivity: this.getFocusedActivityString(entry),
101      isInputMethodWindowVisible: this.isInputMethodVisible(displayContent),
102      imeInputTarget: this.getImeInputTargetProperty(displayContent),
103      imeLayeringTarget: this.getImeLayeringTargetProperty(displayContent),
104      imeInsetsSourceProvider: displayContent.getEagerPropertyByName(
105        'imeInsetsSourceProvider',
106      ),
107      imeControlTarget: this.getImeControlTargetProperty(displayContent),
108    };
109
110    return new ProcessedWindowManagerState(entry.id, entry.name, props, entry);
111  }
112
113  getImeLayers(
114    entryTree: HierarchyTreeNode,
115    processedWindowManagerState: ProcessedWindowManagerState,
116    sfEntryTimestamp: Timestamp | undefined,
117  ): ImeLayers | undefined {
118    const imeContainerLayer = entryTree.findDfs(this.isImeContainer);
119
120    if (!imeContainerLayer) {
121      return undefined;
122    }
123
124    const imeContainerProps: ImeContainerProperties = {
125      id: imeContainerLayer.id,
126      zOrderRelativeOfId: assertDefined(
127        imeContainerLayer.getEagerPropertyByName('zOrderRelativeOf'),
128      ).getValue(),
129      z: assertDefined(
130        imeContainerLayer.getEagerPropertyByName('z'),
131      ).getValue(),
132    };
133
134    const inputMethodSurfaceLayer = imeContainerLayer.findDfs(
135      this.isInputMethodSurface,
136    );
137
138    if (!inputMethodSurfaceLayer) {
139      return undefined;
140    }
141
142    const inputMethodSurfaceProps: InputMethodSurfaceProperties = {
143      id: inputMethodSurfaceLayer.id,
144      isVisible: assertDefined(
145        inputMethodSurfaceLayer.getEagerPropertyByName('isComputedVisible'),
146      ).getValue(),
147      screenBounds:
148        inputMethodSurfaceLayer.getEagerPropertyByName('screenBounds'),
149      rect: inputMethodSurfaceLayer.getEagerPropertyByName('bounds'),
150    };
151
152    let focusedWindowLayer: HierarchyTreeNode | undefined;
153    const focusedWindowToken =
154      processedWindowManagerState.wmStateProperties.focusedWindow
155        ?.split(' ')[0]
156        .slice(1);
157    if (focusedWindowToken) {
158      const isFocusedWindow = UiTreeUtils.makeNodeFilter(
159        new TextFilter(focusedWindowToken).getFilterPredicate(),
160      );
161      focusedWindowLayer = entryTree.findDfs(isFocusedWindow);
162    }
163
164    const focusedWindowColor = focusedWindowLayer
165      ? focusedWindowLayer.getEagerPropertyByName('color')
166      : undefined;
167
168    // we want to see both ImeContainer and IME-snapshot if there are
169    // cases where both exist
170    const taskLayerOfImeContainer = this.findAncestorTaskLayerOfImeLayer(
171      entryTree,
172      this.isImeContainer,
173    );
174
175    const taskLayerOfImeSnapshot = this.findAncestorTaskLayerOfImeLayer(
176      entryTree,
177      UiTreeUtils.makeNodeFilter(
178        new TextFilter('IME-snapshot').getFilterPredicate(),
179      ),
180    );
181
182    const rootProperties = sfEntryTimestamp
183      ? {timestamp: sfEntryTimestamp.format()}
184      : undefined;
185
186    return new ImeLayers(
187      entryTree.id,
188      entryTree.name,
189      {
190        imeContainer: imeContainerProps,
191        inputMethodSurface: inputMethodSurfaceProps,
192        focusedWindowColor,
193        root: rootProperties,
194      },
195      taskLayerOfImeContainer,
196      taskLayerOfImeSnapshot,
197    );
198  }
199
200  private getFocusedWindowString(entry: HierarchyTreeNode): string | undefined {
201    let focusedWindowString = undefined;
202    const focusedWindow = WmImeUtils.getFocusedWindow(entry);
203    if (focusedWindow) {
204      const token = assertDefined(
205        focusedWindow.getEagerPropertyByName('token'),
206      ).getValue();
207      const windowTypeSuffix = this.getWindowTypeSuffix(
208        assertDefined(
209          focusedWindow.getEagerPropertyByName('windowType'),
210        ).getValue(),
211      );
212      const type = assertDefined(
213        focusedWindow
214          .getEagerPropertyByName('attributes')
215          ?.getChildByName('type'),
216      ).formattedValue();
217      const windowFrames = assertDefined(
218        focusedWindow.getEagerPropertyByName('windowFrames'),
219      );
220      const containingFrame = assertDefined(
221        windowFrames.getChildByName('containingFrame')?.formattedValue(),
222      );
223      const parentFrame = assertDefined(
224        windowFrames.getChildByName('parentFrame')?.formattedValue(),
225      );
226
227      focusedWindowString = `{${token} ${focusedWindow.name}${windowTypeSuffix}} type=${type} cf=${containingFrame} pf=${parentFrame}`;
228    }
229    return focusedWindowString;
230  }
231
232  private getFocusedActivityString(entry: HierarchyTreeNode): string {
233    let focusedActivityString = 'null';
234    const focusedActivity = WmImeUtils.getFocusedActivity(entry);
235    if (focusedActivity) {
236      const token = assertDefined(
237        focusedActivity.getEagerPropertyByName('token'),
238      ).getValue();
239      const state = assertDefined(
240        focusedActivity.getEagerPropertyByName('state'),
241      ).getValue();
242      const isVisible =
243        focusedActivity
244          .getEagerPropertyByName('isComputedVisible')
245          ?.getValue() ?? false;
246
247      focusedActivityString = `{${token} ${focusedActivity.name}} state=${state} visible=${isVisible}`;
248    }
249    return focusedActivityString;
250  }
251
252  private getWindowTypeSuffix(windowType: number): string {
253    switch (windowType) {
254      case WindowType.STARTING:
255        return ' STARTING';
256      case WindowType.EXITING:
257        return ' EXITING';
258      case WindowType.DEBUGGER:
259        return ' DEBUGGER';
260      default:
261        return '';
262    }
263  }
264
265  private findAncestorTaskLayerOfImeLayer(
266    entryTree: HierarchyTreeNode,
267    isTargetImeLayer: TreeNodeFilter,
268  ): HierarchyTreeNode | undefined {
269    const imeLayer = entryTree.findDfs(isTargetImeLayer);
270
271    if (!imeLayer) {
272      return undefined;
273    }
274
275    const isTaskLayer = UiTreeUtils.makeNodeFilter(
276      new TextFilter('Task|ImePlaceholder', [
277        FilterFlag.USE_REGEX,
278      ]).getFilterPredicate(),
279    );
280    const taskLayer = imeLayer.findAncestor(isTaskLayer);
281    if (!taskLayer) {
282      return undefined;
283    }
284
285    return taskLayer;
286  }
287
288  private getImeControlTargetProperty(
289    displayContent: HierarchyTreeNode,
290  ): PropertyTreeNode | undefined {
291    return displayContent.getEagerPropertyByName('inputMethodControlTarget');
292  }
293
294  private getImeInputTargetProperty(
295    displayContent: HierarchyTreeNode,
296  ): PropertyTreeNode | undefined {
297    return displayContent.getEagerPropertyByName('inputMethodInputTarget');
298  }
299
300  private getImeLayeringTargetProperty(
301    displayContent: HierarchyTreeNode,
302  ): PropertyTreeNode | undefined {
303    return displayContent.getEagerPropertyByName('inputMethodTarget');
304  }
305
306  private isInputMethodVisible(displayContent: HierarchyTreeNode): boolean {
307    const inputMethodWindowOrLayer = displayContent.findDfs(
308      this.isInputMethodSurface,
309    );
310    return inputMethodWindowOrLayer
311      ?.getEagerPropertyByName('isComputedVisible')
312      ?.getValue();
313  }
314}
315
316export const ImeUtils = new ImeAdditionalPropertiesUtils();
317