• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
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 {PersistentStoreProxy} from 'common/persistent_store_proxy';
18import {FilterType, TreeUtils} from 'common/tree_utils';
19import {LayerTraceEntry} from 'trace/flickerlib/layers/LayerTraceEntry';
20import {WindowManagerState} from 'trace/flickerlib/windows/WindowManagerState';
21import {Trace, TraceEntry} from 'trace/trace';
22import {Traces} from 'trace/traces';
23import {TraceEntryFinder} from 'trace/trace_entry_finder';
24import {TracePosition} from 'trace/trace_position';
25import {TraceTreeNode} from 'trace/trace_tree_node';
26import {TraceType} from 'trace/trace_type';
27import {ImeAdditionalProperties} from 'viewers/common/ime_additional_properties';
28import {ImeUiData} from 'viewers/common/ime_ui_data';
29import {ImeLayers, ImeUtils, ProcessedWindowManagerState} from 'viewers/common/ime_utils';
30import {TableProperties} from 'viewers/common/table_properties';
31import {TreeGenerator} from 'viewers/common/tree_generator';
32import {TreeTransformer} from 'viewers/common/tree_transformer';
33import {HierarchyTreeNode, PropertiesTreeNode} from 'viewers/common/ui_tree_utils';
34import {UserOptions} from 'viewers/common/user_options';
35
36type NotifyImeViewCallbackType = (uiData: ImeUiData) => void;
37
38export abstract class PresenterInputMethod {
39  private readonly imeTrace: Trace<object>;
40  private readonly wmTrace?: Trace<WindowManagerState>;
41  private readonly sfTrace?: Trace<LayerTraceEntry>;
42  private hierarchyFilter: FilterType = TreeUtils.makeNodeFilter('');
43  private propertiesFilter: FilterType = TreeUtils.makeNodeFilter('');
44  private pinnedItems: HierarchyTreeNode[] = [];
45  private pinnedIds: string[] = [];
46  private selectedHierarchyTree: HierarchyTreeNode | null = null;
47
48  readonly notifyViewCallback: NotifyImeViewCallbackType;
49  protected readonly dependencies: TraceType[];
50  protected uiData: ImeUiData;
51  protected highlightedItems: string[] = [];
52  protected entry: TraceTreeNode | null = null;
53  protected additionalPropertyEntry: TraceTreeNode | null = null;
54  protected hierarchyUserOptions: UserOptions = PersistentStoreProxy.new<UserOptions>(
55    'ImeHierarchyOptions',
56    {
57      simplifyNames: {
58        name: 'Simplify names',
59        enabled: true,
60      },
61      onlyVisible: {
62        name: 'Only visible',
63        enabled: false,
64      },
65      flat: {
66        name: 'Flat',
67        enabled: false,
68      },
69    },
70    this.storage
71  );
72  protected propertiesUserOptions: UserOptions = PersistentStoreProxy.new<UserOptions>(
73    'ImePropertiesOptions',
74    {
75      showDefaults: {
76        name: 'Show defaults',
77        enabled: false,
78        tooltip: `
79                If checked, shows the value of all properties.
80                Otherwise, hides all properties whose value is
81                the default for its data type.
82              `,
83      },
84    },
85    this.storage
86  );
87
88  constructor(
89    traces: Traces,
90    private storage: Storage,
91    dependencies: TraceType[],
92    notifyViewCallback: NotifyImeViewCallbackType
93  ) {
94    this.imeTrace = traces.getTrace(dependencies[0]) as Trace<TraceTreeNode>;
95    this.sfTrace = traces.getTrace(TraceType.SURFACE_FLINGER);
96    this.wmTrace = traces.getTrace(TraceType.WINDOW_MANAGER);
97
98    this.dependencies = dependencies;
99    this.notifyViewCallback = notifyViewCallback;
100    this.uiData = new ImeUiData(dependencies);
101    this.notifyViewCallback(this.uiData);
102  }
103
104  async onTracePositionUpdate(position: TracePosition) {
105    this.uiData = new ImeUiData(this.dependencies);
106    this.uiData.hierarchyUserOptions = this.hierarchyUserOptions;
107    this.uiData.propertiesUserOptions = this.propertiesUserOptions;
108
109    const [imeEntry, sfEntry, wmEntry] = this.findTraceEntries(position);
110
111    if (imeEntry) {
112      this.entry = (await imeEntry.getValue()) as TraceTreeNode;
113      this.uiData.highlightedItems = this.highlightedItems;
114      this.uiData.additionalProperties = this.getAdditionalProperties(
115        await wmEntry?.getValue(),
116        await sfEntry?.getValue()
117      );
118      this.uiData.tree = this.generateTree();
119      this.uiData.hierarchyTableProperties = this.updateHierarchyTableProperties();
120    }
121    this.notifyViewCallback(this.uiData);
122  }
123
124  updatePinnedItems(pinnedItem: HierarchyTreeNode) {
125    const pinnedId = `${pinnedItem.id}`;
126    if (this.pinnedItems.map((item) => `${item.id}`).includes(pinnedId)) {
127      this.pinnedItems = this.pinnedItems.filter((pinned) => `${pinned.id}` !== pinnedId);
128    } else {
129      this.pinnedItems.push(pinnedItem);
130    }
131    this.updatePinnedIds(pinnedId);
132    this.uiData.pinnedItems = this.pinnedItems;
133    this.notifyViewCallback(this.uiData);
134  }
135
136  updateHighlightedItems(id: string) {
137    if (this.highlightedItems.includes(id)) {
138      this.highlightedItems = this.highlightedItems.filter((hl) => hl !== id);
139    } else {
140      this.highlightedItems = []; //if multi-select surfaces implemented, remove this line
141      this.highlightedItems.push(id);
142    }
143    this.uiData.highlightedItems = this.highlightedItems;
144    this.notifyViewCallback(this.uiData);
145  }
146
147  updateHierarchyTree(userOptions: UserOptions) {
148    this.hierarchyUserOptions = userOptions;
149    this.uiData.hierarchyUserOptions = this.hierarchyUserOptions;
150    this.uiData.tree = this.generateTree();
151    this.notifyViewCallback(this.uiData);
152  }
153
154  filterHierarchyTree(filterString: string) {
155    this.hierarchyFilter = TreeUtils.makeNodeFilter(filterString);
156    this.uiData.tree = this.generateTree();
157    this.notifyViewCallback(this.uiData);
158  }
159
160  updatePropertiesTree(userOptions: UserOptions) {
161    this.propertiesUserOptions = userOptions;
162    this.uiData.propertiesUserOptions = this.propertiesUserOptions;
163    this.updateSelectedTreeUiData();
164  }
165
166  filterPropertiesTree(filterString: string) {
167    this.propertiesFilter = TreeUtils.makeNodeFilter(filterString);
168    this.updateSelectedTreeUiData();
169  }
170
171  newPropertiesTree(selectedItem: HierarchyTreeNode) {
172    this.additionalPropertyEntry = null;
173    this.selectedHierarchyTree = selectedItem;
174    this.updateSelectedTreeUiData();
175  }
176
177  newAdditionalPropertiesTree(selectedItem: any) {
178    this.selectedHierarchyTree = new HierarchyTreeNode(
179      selectedItem.name,
180      'AdditionalProperty',
181      'AdditionalProperty'
182    );
183    this.additionalPropertyEntry = {
184      name: selectedItem.name,
185      kind: 'AdditionalProperty',
186      children: [],
187      stableId: 'AdditionalProperty',
188      proto: selectedItem.proto,
189    };
190    this.updateSelectedTreeUiData();
191  }
192
193  protected getAdditionalProperties(
194    wmEntry: TraceTreeNode | undefined,
195    sfEntry: TraceTreeNode | undefined
196  ) {
197    let wmProperties: ProcessedWindowManagerState | undefined;
198    let sfProperties: ImeLayers | undefined;
199    let sfSubtrees: any[];
200
201    if (wmEntry) {
202      wmProperties = ImeUtils.processWindowManagerTraceEntry(wmEntry);
203
204      if (sfEntry) {
205        sfProperties = ImeUtils.getImeLayers(sfEntry, wmProperties);
206        sfSubtrees = [sfProperties?.taskOfImeContainer, sfProperties?.taskOfImeSnapshot]
207          .filter((node) => node) // filter away null values
208          .map((node) => {
209            node.kind = 'SF subtree - ' + node.id;
210            return node;
211          });
212        this.entry?.children.push(...sfSubtrees);
213      }
214    }
215
216    return new ImeAdditionalProperties(wmProperties, sfProperties);
217  }
218
219  protected generateTree() {
220    if (!this.entry) {
221      return null;
222    }
223
224    const generator = new TreeGenerator(this.entry, this.hierarchyFilter, this.pinnedIds)
225      .setIsOnlyVisibleView(this.hierarchyUserOptions['onlyVisible']?.enabled)
226      .setIsSimplifyNames(this.hierarchyUserOptions['simplifyNames']?.enabled)
227      .setIsFlatView(this.hierarchyUserOptions['flat']?.enabled)
228      .withUniqueNodeId();
229    const tree: HierarchyTreeNode | null = generator.generateTree();
230    this.pinnedItems = generator.getPinnedItems();
231    this.uiData.pinnedItems = this.pinnedItems;
232    return tree;
233  }
234
235  private updateSelectedTreeUiData() {
236    if (this.selectedHierarchyTree) {
237      this.uiData.propertiesTree = this.getTreeWithTransformedProperties(
238        this.selectedHierarchyTree
239      );
240    }
241    this.notifyViewCallback(this.uiData);
242  }
243  private updatePinnedIds(newId: string) {
244    if (this.pinnedIds.includes(newId)) {
245      this.pinnedIds = this.pinnedIds.filter((pinned) => pinned !== newId);
246    } else {
247      this.pinnedIds.push(newId);
248    }
249  }
250
251  private getTreeWithTransformedProperties(selectedTree: HierarchyTreeNode): PropertiesTreeNode {
252    const transformer = new TreeTransformer(selectedTree, this.propertiesFilter)
253      .setOnlyProtoDump(this.additionalPropertyEntry != null)
254      .setIsShowDefaults(this.propertiesUserOptions['showDefaults']?.enabled)
255      .setTransformerOptions({skip: selectedTree.skip})
256      .setProperties(this.additionalPropertyEntry ?? this.entry);
257    const transformedTree = transformer.transform();
258    return transformedTree;
259  }
260
261  private findTraceEntries(
262    position: TracePosition
263  ): [
264    TraceEntry<object> | undefined,
265    TraceEntry<LayerTraceEntry> | undefined,
266    TraceEntry<WindowManagerState> | undefined
267  ] {
268    const imeEntry = TraceEntryFinder.findCorrespondingEntry(this.imeTrace, position);
269    if (!imeEntry) {
270      return [undefined, undefined, undefined];
271    }
272
273    if (!this.imeTrace.hasFrameInfo()) {
274      return [imeEntry, undefined, undefined];
275    }
276
277    const frames = imeEntry.getFramesRange();
278    if (!frames || frames.start === frames.end) {
279      return [imeEntry, undefined, undefined];
280    }
281
282    const frame = frames.start;
283    const sfEntry = this.sfTrace?.getFrame(frame)?.findClosestEntry(imeEntry.getTimestamp());
284    const wmEntry = this.wmTrace?.getFrame(frame)?.findClosestEntry(imeEntry.getTimestamp());
285
286    return [imeEntry, sfEntry, wmEntry];
287  }
288
289  protected abstract updateHierarchyTableProperties(): TableProperties;
290}
291