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