xref: /aosp_15_r20/development/tools/winscope/src/viewers/viewer_view_capture/presenter.ts (revision 90c8c64db3049935a07c6143d7fd006e26f8ecca)
1/*
2 * Copyright (C) 2023 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, assertTrue} from 'common/assert_utils';
18import {PersistentStoreProxy} from 'common/persistent_store_proxy';
19import {Store} from 'common/store';
20import {
21  TabbedViewSwitchRequest,
22  TracePositionUpdate,
23} from 'messaging/winscope_event';
24import {CustomQueryType} from 'trace/custom_query';
25import {Trace} from 'trace/trace';
26import {Traces} from 'trace/traces';
27import {TraceEntryFinder} from 'trace/trace_entry_finder';
28import {TraceType} from 'trace/trace_type';
29import {HierarchyTreeNode} from 'trace/tree_node/hierarchy_tree_node';
30import {PropertyTreeNode} from 'trace/tree_node/property_tree_node';
31import {
32  AbstractHierarchyViewerPresenter,
33  NotifyHierarchyViewCallbackType,
34} from 'viewers/common/abstract_hierarchy_viewer_presenter';
35import {VISIBLE_CHIP} from 'viewers/common/chip';
36import {VcCuratedProperties} from 'viewers/common/curated_properties';
37import {DisplayIdentifier} from 'viewers/common/display_identifier';
38import {HierarchyPresenter} from 'viewers/common/hierarchy_presenter';
39import {PropertiesPresenter} from 'viewers/common/properties_presenter';
40import {RectsPresenter} from 'viewers/common/rects_presenter';
41import {TextFilter} from 'viewers/common/text_filter';
42import {UiHierarchyTreeNode} from 'viewers/common/ui_hierarchy_tree_node';
43import {UI_RECT_FACTORY} from 'viewers/common/ui_rect_factory';
44import {UserOptions} from 'viewers/common/user_options';
45import {UiRect} from 'viewers/components/rects/ui_rect';
46import {UiData} from './ui_data';
47
48export class Presenter extends AbstractHierarchyViewerPresenter<UiData> {
49  static readonly DENYLIST_PROPERTY_NAMES = ['children', 'isComputedVisible'];
50
51  private windowNames: string[] = [];
52  protected override hierarchyPresenter = new HierarchyPresenter(
53    PersistentStoreProxy.new<UserOptions>(
54      'VcHierarchyOptions',
55      {
56        showDiff: {
57          name: 'Show diff',
58          enabled: false,
59          isUnavailable: false,
60        },
61        showOnlyVisible: {
62          name: 'Show only',
63          chip: VISIBLE_CHIP,
64          enabled: false,
65        },
66        simplifyNames: {
67          name: 'Simplify names',
68          enabled: true,
69        },
70      },
71      this.storage,
72    ),
73    new TextFilter(),
74    Presenter.DENYLIST_PROPERTY_NAMES,
75    false,
76    true,
77  );
78  protected override rectsPresenter = new RectsPresenter(
79    PersistentStoreProxy.new<UserOptions>(
80      'VcRectsOptions',
81      {
82        ignoreRectShowState: {
83          name: 'Ignore',
84          icon: 'visibility',
85          enabled: false,
86        },
87        showOnlyVisible: {
88          name: 'Show only',
89          chip: VISIBLE_CHIP,
90          enabled: false,
91        },
92      },
93      this.storage,
94    ),
95    (tree: HierarchyTreeNode, trace: Trace<HierarchyTreeNode>) =>
96      UI_RECT_FACTORY.makeVcUiRects(
97        tree,
98        this.getIdFromViewCaptureTrace(trace),
99      ),
100    undefined,
101  );
102  protected override propertiesPresenter = new PropertiesPresenter(
103    PersistentStoreProxy.new<UserOptions>(
104      'VcPropertyOptions',
105      {
106        showDiff: {
107          name: 'Show diff',
108          enabled: false,
109          isUnavailable: false,
110        },
111        showDefaults: {
112          name: 'Show defaults',
113          enabled: false,
114          tooltip: `If checked, shows the value of all properties.
115Otherwise, hides all properties whose value is
116the default for its data type.`,
117        },
118      },
119      this.storage,
120    ),
121    new TextFilter(),
122    Presenter.DENYLIST_PROPERTY_NAMES,
123  );
124  protected override readonly multiTraceType = TraceType.VIEW_CAPTURE;
125
126  private readonly surfaceFlingerTrace: Trace<HierarchyTreeNode> | undefined;
127  private readonly viewCaptureTraces: Array<Trace<HierarchyTreeNode>>;
128
129  private viewCapturePackageNames: string[] = [];
130  private sfRects: UiRect[] | undefined;
131  private curatedProperties: VcCuratedProperties | undefined;
132
133  constructor(
134    traces: Traces,
135    storage: Readonly<Store>,
136    notifyViewCallback: NotifyHierarchyViewCallbackType<UiData>,
137  ) {
138    super(undefined, traces, storage, notifyViewCallback, new UiData());
139    this.viewCaptureTraces = traces.getTraces(TraceType.VIEW_CAPTURE);
140    this.surfaceFlingerTrace = traces.getTrace(TraceType.SURFACE_FLINGER);
141  }
142
143  async onMiniRectsDoubleClick() {
144    if (!this.surfaceFlingerTrace) {
145      return;
146    }
147    await this.emitWinscopeEvent(
148      new TabbedViewSwitchRequest(this.surfaceFlingerTrace),
149    );
150  }
151
152  getTraces(): Array<Trace<HierarchyTreeNode>> {
153    return this.viewCaptureTraces;
154  }
155
156  override async onHighlightedNodeChange(node: UiHierarchyTreeNode) {
157    await this.applyHighlightedNodeChange(node);
158    this.updateCuratedProperties();
159    this.refreshUIData();
160  }
161
162  override async onHighlightedIdChange(newId: string) {
163    await this.applyHighlightedIdChange(newId);
164    this.updateCuratedProperties();
165    this.refreshUIData();
166  }
167
168  protected override getOverrideDisplayName(): undefined {
169    return undefined;
170  }
171
172  protected override keepCalculated(node: UiHierarchyTreeNode): boolean {
173    return node.isRoot();
174  }
175
176  protected override async initializeIfNeeded() {
177    await this.initializePackageNamesIfNeeded();
178    await this.initializeWindowsIfNeeded();
179  }
180
181  protected override async processDataAfterPositionUpdate(
182    event: TracePositionUpdate,
183  ): Promise<void> {
184    if (this.uiData && this.surfaceFlingerTrace) {
185      const surfaceFlingerEntry =
186        (await TraceEntryFinder.findCorrespondingEntry(
187          this.surfaceFlingerTrace,
188          event.position,
189        )?.getValue()) as HierarchyTreeNode;
190      if (surfaceFlingerEntry) {
191        this.sfRects = UI_RECT_FACTORY.makeUiRects(
192          surfaceFlingerEntry,
193          this.viewCapturePackageNames,
194        );
195      }
196    }
197    this.updateCuratedProperties();
198  }
199
200  protected override refreshUIData() {
201    this.uiData.sfRects = this.sfRects;
202    this.uiData.curatedProperties = this.curatedProperties;
203    this.refreshHierarchyViewerUiData();
204  }
205
206  private async initializePackageNamesIfNeeded() {
207    if (this.viewCapturePackageNames.length > 0) {
208      return;
209    }
210
211    const promisesPackageName = this.viewCaptureTraces.map(async (trace) => {
212      const packageAndWindow = await trace.customQuery(
213        CustomQueryType.VIEW_CAPTURE_METADATA,
214      );
215      return packageAndWindow.packageName;
216    });
217
218    this.viewCapturePackageNames = await Promise.all(promisesPackageName);
219  }
220
221  private async initializeWindowsIfNeeded() {
222    if (this.rectsPresenter.getDisplays().length > 0) {
223      return;
224    }
225
226    const shortenAndCapitalizeWindowName = (name: string) => {
227      const lastDot = name.lastIndexOf('.');
228      if (lastDot !== -1) {
229        name = name.substring(lastDot + 1);
230      }
231      if (name.length > 0) {
232        name = name[0].toUpperCase() + name.slice(1);
233      }
234      return name;
235    };
236
237    const promisesWindowName = this.viewCaptureTraces.map(async (trace) => {
238      const packageAndWindow = await trace.customQuery(
239        CustomQueryType.VIEW_CAPTURE_METADATA,
240      );
241      return shortenAndCapitalizeWindowName(packageAndWindow.windowName);
242    });
243    this.windowNames = await Promise.all(promisesWindowName);
244    this.rectsPresenter.setDisplays(this.getWindows(this.windowNames));
245  }
246
247  private getWindows(windowNames: string[]): DisplayIdentifier[] {
248    return this.viewCaptureTraces.map((trace, i) => {
249      const traceId = this.getIdFromViewCaptureTrace(trace);
250      return {
251        displayId: traceId,
252        groupId: traceId,
253        name: windowNames[i],
254        isActive: true,
255      };
256    });
257  }
258
259  private updateCuratedProperties() {
260    const propertiesTree = this.propertiesPresenter.getPropertiesTree();
261    if (propertiesTree) {
262      this.curatedProperties = this.getCuratedProperties(propertiesTree);
263    } else {
264      this.curatedProperties = undefined;
265    }
266  }
267
268  private getCuratedProperties(tree: PropertyTreeNode): VcCuratedProperties {
269    const curated: VcCuratedProperties = {
270      className: tree.name,
271      hashcode: assertDefined(tree.getChildByName('hashcode')).formattedValue(),
272      left: assertDefined(tree.getChildByName('left')).formattedValue(),
273      top: assertDefined(tree.getChildByName('top')).formattedValue(),
274      elevation: assertDefined(
275        tree.getChildByName('elevation'),
276      ).formattedValue(),
277      height: assertDefined(tree.getChildByName('height')).formattedValue(),
278      width: assertDefined(tree.getChildByName('width')).formattedValue(),
279      translationX: assertDefined(
280        tree.getChildByName('translationX'),
281      ).formattedValue(),
282      translationY: assertDefined(
283        tree.getChildByName('translationY'),
284      ).formattedValue(),
285      scrollX: assertDefined(tree.getChildByName('scrollX')).formattedValue(),
286      scrollY: assertDefined(tree.getChildByName('scrollY')).formattedValue(),
287      scaleX: assertDefined(tree.getChildByName('scaleX')).formattedValue(),
288      scaleY: assertDefined(tree.getChildByName('scaleY')).formattedValue(),
289      visibility: assertDefined(
290        tree.getChildByName('visibility'),
291      ).formattedValue(),
292      alpha: assertDefined(tree.getChildByName('alpha')).formattedValue(),
293      willNotDraw: assertDefined(
294        tree.getChildByName('willNotDraw'),
295      ).formattedValue(),
296      clipChildren: assertDefined(
297        tree.getChildByName('clipChildren'),
298      ).formattedValue(),
299    };
300    return curated;
301  }
302
303  private getIdFromViewCaptureTrace(trace: Trace<HierarchyTreeNode>): number {
304    const index = this.viewCaptureTraces.indexOf(trace);
305    assertTrue(index !== -1);
306    return index;
307  }
308}
309