xref: /aosp_15_r20/development/tools/winscope/src/parsers/window_manager/properties_provider_factory.ts (revision 90c8c64db3049935a07c6143d7fd006e26f8ecca)
1/*
2 * Copyright (C) 2024 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 {perfetto} from 'protos/windowmanager/latest/static';
19import {com} from 'protos/windowmanager/udc/static';
20import {
21  LazyPropertiesStrategyType,
22  PropertiesProvider,
23} from 'trace/tree_node/properties_provider';
24import {PropertiesProviderBuilder} from 'trace/tree_node/properties_provider_builder';
25import {PropertyTreeBuilderFromProto} from 'trace/tree_node/property_tree_builder_from_proto';
26import {PropertyTreeNode} from 'trace/tree_node/property_tree_node';
27import {DEFAULT_PROPERTY_TREE_NODE_FACTORY} from 'trace/tree_node/property_tree_node_factory';
28import {WindowTypePrefix} from 'trace/window_type';
29import {DENYLIST_PROPERTIES} from './denylist_properties';
30import {EAGER_PROPERTIES} from './eager_properties';
31import {OperationLists, WmOperationLists} from './operations/operation_lists';
32import {ProtoType} from './proto_type';
33import {TamperedProtos} from './tampered_protos';
34
35type WindowContainerChildTypeUdc =
36  | com.android.server.wm.IWindowContainerProto
37  | com.android.server.wm.IDisplayContentProto
38  | com.android.server.wm.IDisplayAreaProto
39  | com.android.server.wm.ITaskProto
40  | com.android.server.wm.IActivityRecordProto
41  | com.android.server.wm.IWindowTokenProto
42  | com.android.server.wm.IWindowStateProto
43  | com.android.server.wm.ITaskFragmentProto;
44type WindowContainerChildTypeLatest =
45  | perfetto.protos.IWindowContainerProto
46  | perfetto.protos.IDisplayContentProto
47  | perfetto.protos.IDisplayAreaProto
48  | perfetto.protos.ITaskProto
49  | perfetto.protos.IActivityRecordProto
50  | perfetto.protos.IWindowTokenProto
51  | perfetto.protos.IWindowStateProto
52  | perfetto.protos.ITaskFragmentProto;
53type WindowContainerChildType =
54  | WindowContainerChildTypeUdc
55  | WindowContainerChildTypeLatest;
56
57type IdentifierProto =
58  | com.android.server.wm.IIdentifierProto
59  | perfetto.protos.IIdentifierProto;
60type WindowManagerServiceDumpProto =
61  | com.android.server.wm.IWindowManagerServiceDumpProto
62  | perfetto.protos.WindowManagerServiceDumpProto;
63type WindowContainerChildProto =
64  | com.android.server.wm.IWindowContainerChildProto
65  | perfetto.protos.IWindowContainerChildProto;
66
67export class PropertiesProviderFactory {
68  private readonly operationLists: WmOperationLists;
69
70  constructor(tamperedProtos: TamperedProtos) {
71    this.operationLists = new WmOperationLists(tamperedProtos);
72  }
73
74  makeEntryProperties(
75    entryProto: WindowManagerServiceDumpProto,
76  ): PropertiesProvider {
77    const operations = assertDefined(
78      this.operationLists.get(ProtoType.WindowManagerService),
79    );
80    return new PropertiesProviderBuilder()
81      .setEagerProperties(
82        this.makeEntryEagerPropertiesTree(assertDefined(entryProto)),
83      )
84      .setLazyPropertiesStrategy(
85        this.makeEntryLazyPropertiesStrategy(assertDefined(entryProto)),
86      )
87      .setCommonOperations(operations.common)
88      .setEagerOperations(operations.eager)
89      .setLazyOperations(operations.lazy)
90      .build();
91  }
92
93  makeContainerProperties(
94    entryProto: WindowManagerServiceDumpProto,
95  ): PropertiesProvider[] {
96    let currChildren: WindowContainerChildProto[] = assertDefined(
97      entryProto.rootWindowContainer?.windowContainer?.children,
98    );
99
100    const rootContainer = assertDefined(entryProto.rootWindowContainer);
101    const rootContainerProperties = this.getContainerChildProperties(
102      rootContainer,
103      currChildren,
104      this.operationLists.get(ProtoType.RootWindowContainer),
105    );
106
107    const containers = [rootContainerProperties];
108
109    while (currChildren && currChildren.length > 0) {
110      const nextChildren: WindowContainerChildProto[] = [];
111      containers.push(
112        ...currChildren.map((containerChild: WindowContainerChildProto) => {
113          const children = this.getChildren(containerChild);
114          nextChildren.push(...children);
115          const containerProperties = this.getContainerChildProperties(
116            containerChild,
117            children,
118          );
119          return containerProperties;
120        }),
121      );
122      currChildren = nextChildren;
123    }
124
125    return containers;
126  }
127
128  private makeEntryEagerPropertiesTree(
129    entry: WindowManagerServiceDumpProto,
130  ): PropertyTreeNode {
131    const denyList: string[] = [];
132    const eagerProperties = assertDefined(
133      EAGER_PROPERTIES.get(ProtoType.WindowManagerService),
134    );
135    let obj = entry;
136    do {
137      Object.getOwnPropertyNames(obj).forEach((it) => {
138        if (!eagerProperties.includes(it)) {
139          denyList.push(it);
140        }
141      });
142      obj = Object.getPrototypeOf(obj);
143    } while (obj);
144
145    return new PropertyTreeBuilderFromProto()
146      .setData(entry)
147      .setRootId('WindowManagerState')
148      .setRootName('root')
149      .setDenyList(denyList)
150      .build();
151  }
152
153  private makeEntryLazyPropertiesStrategy(
154    entry: WindowManagerServiceDumpProto,
155  ): LazyPropertiesStrategyType {
156    return async () => {
157      return new PropertyTreeBuilderFromProto()
158        .setData(entry)
159        .setRootId('WindowManagerState')
160        .setRootName('root')
161        .setDenyList(assertDefined(DENYLIST_PROPERTIES))
162        .build();
163    };
164  }
165
166  private getChildren(
167    child: WindowContainerChildProto,
168  ): WindowContainerChildProto[] {
169    let children: WindowContainerChildProto[] = [];
170    if (child.displayContent) {
171      children =
172        child.displayContent.rootDisplayArea?.windowContainer?.children ?? [];
173    } else if (child.displayArea) {
174      children = child.displayArea.windowContainer?.children ?? [];
175    } else if (child.task) {
176      const taskContainer =
177        child.task.taskFragment?.windowContainer ?? child.task.windowContainer;
178      children = taskContainer?.children ?? [];
179    } else if (child.taskFragment) {
180      children = child.taskFragment.windowContainer?.children ?? [];
181    } else if (child.activity) {
182      children = child.activity.windowToken?.windowContainer?.children ?? [];
183    } else if (child.windowToken) {
184      children = child.windowToken.windowContainer?.children ?? [];
185    } else if (child.window) {
186      children = child.window.windowContainer?.children ?? [];
187    } else if (child.windowContainer) {
188      children = child.windowContainer?.children ?? [];
189    }
190
191    return children.filter((c) => Object.keys(c).length > 0);
192  }
193
194  private getContainerChildProperties(
195    containerChild: WindowContainerChildProto,
196    children: WindowContainerChildProto[],
197    operations?: OperationLists,
198  ): PropertiesProvider {
199    const containerChildType = this.getContainerChildType(containerChild);
200
201    const eagerProperties = this.makeContainerChildEagerPropertiesTree(
202      containerChild,
203      children,
204      containerChildType,
205    );
206    const lazyPropertiesStrategy =
207      this.makeContainerChildLazyPropertiesStrategy(
208        containerChild,
209        containerChildType,
210      );
211
212    if (!operations) {
213      operations = assertDefined(this.operationLists.get(containerChildType));
214    }
215
216    const containerProperties = new PropertiesProviderBuilder()
217      .setEagerProperties(eagerProperties)
218      .setLazyPropertiesStrategy(lazyPropertiesStrategy)
219      .setCommonOperations(operations.common)
220      .setEagerOperations(operations.eager)
221      .setLazyOperations(operations.lazy)
222      .build();
223    return containerProperties;
224  }
225
226  private getContainerChildType(child: WindowContainerChildProto): ProtoType {
227    if (child.displayContent) {
228      return ProtoType.DisplayContent;
229    } else if (child.displayArea) {
230      return ProtoType.DisplayArea;
231    } else if (child.task) {
232      return ProtoType.Task;
233    } else if (child.taskFragment) {
234      return ProtoType.TaskFragment;
235    } else if (child.activity) {
236      return ProtoType.Activity;
237    } else if (child.windowToken) {
238      return ProtoType.WindowToken;
239    } else if (child.window) {
240      return ProtoType.WindowState;
241    }
242
243    return ProtoType.WindowContainer;
244  }
245
246  private makeContainerChildEagerPropertiesTree(
247    containerChild: WindowContainerChildProto,
248    children: WindowContainerChildProto[],
249    containerChildType: ProtoType,
250  ): PropertyTreeNode {
251    const identifier = this.getIdentifier(containerChild);
252    const name = this.getName(containerChild, identifier);
253    const token = this.makeToken(identifier);
254
255    const eagerProperties = assertDefined(
256      EAGER_PROPERTIES.get(containerChildType),
257    );
258
259    const denyList: string[] = [];
260
261    const container = this.getContainer(containerChild);
262    let obj = container;
263    do {
264      Object.getOwnPropertyNames(obj).forEach((it) => {
265        if (!eagerProperties.includes(it)) denyList.push(it);
266      });
267      obj = Object.getPrototypeOf(obj);
268    } while (obj);
269
270    const containerProperties = new PropertyTreeBuilderFromProto()
271      .setData(container)
272      .setRootId(`${containerChildType} ${token}`)
273      .setRootName(name)
274      .setDenyList(denyList)
275      .build();
276
277    if (children.length > 0) {
278      containerProperties.addOrReplaceChild(
279        DEFAULT_PROPERTY_TREE_NODE_FACTORY.makeCalculatedProperty(
280          containerProperties.id,
281          'children',
282          this.mapChildrenToTokens(children),
283        ),
284      );
285    }
286
287    containerProperties.addOrReplaceChild(
288      DEFAULT_PROPERTY_TREE_NODE_FACTORY.makeCalculatedProperty(
289        containerProperties.id,
290        'token',
291        token,
292      ),
293    );
294
295    return containerProperties;
296  }
297
298  private makeContainerChildLazyPropertiesStrategy(
299    containerChild: WindowContainerChildProto,
300    containerChildType: ProtoType,
301  ): LazyPropertiesStrategyType {
302    return async () => {
303      const identifier = this.getIdentifier(containerChild);
304      const name = this.getName(containerChild, identifier);
305      const token = this.makeToken(identifier);
306      const container = this.getContainer(containerChild);
307
308      return new PropertyTreeBuilderFromProto()
309        .setData(container)
310        .setRootId(`${containerChildType} ${token}`)
311        .setRootName(name)
312        .setDenyList(DENYLIST_PROPERTIES)
313        .build();
314    };
315  }
316
317  private getIdentifier(
318    child: WindowContainerChildProto,
319  ): IdentifierProto | undefined {
320    if (child.displayContent) {
321      return (
322        child.displayContent.rootDisplayArea?.windowContainer?.identifier ??
323        undefined
324      );
325    }
326    if (child.displayArea) {
327      return child.displayArea.windowContainer?.identifier ?? undefined;
328    }
329    if (child.task) {
330      return (
331        child.task.taskFragment?.windowContainer?.identifier ??
332        child.task.windowContainer?.identifier ??
333        undefined
334      );
335    }
336    if (child.taskFragment) {
337      return child.taskFragment.windowContainer?.identifier ?? undefined;
338    }
339    if (child.activity) {
340      return (
341        child.activity.identifier ??
342        child.activity.windowToken?.windowContainer?.identifier ??
343        undefined
344      );
345    }
346    if (child.windowToken) {
347      return child.windowToken ?? undefined;
348    }
349    if (child.window) {
350      return (
351        child.window.windowContainer?.identifier ??
352        child.window.identifier ??
353        undefined
354      );
355    }
356    if (child.windowContainer) {
357      return child.windowContainer?.identifier ?? undefined;
358    }
359    return undefined;
360  }
361
362  private getName(
363    child: WindowContainerChildProto,
364    identifier: IdentifierProto | undefined,
365  ): string {
366    let nameOverride: string | undefined;
367    if (child.displayContent) {
368      nameOverride = child.displayContent.displayInfo?.name;
369    } else if (child.displayArea) {
370      nameOverride = child.displayArea.name ?? undefined;
371    } else if (child.activity) {
372      nameOverride = child.activity.name ?? undefined;
373    } else if (child.windowToken) {
374      nameOverride = child.windowToken.hashCode?.toString(16);
375    } else if (child.window) {
376      nameOverride =
377        child.window.windowContainer?.identifier?.title ??
378        child.window.identifier?.title ??
379        '';
380
381      if (nameOverride.startsWith(WindowTypePrefix.STARTING)) {
382        nameOverride = nameOverride.substring(WindowTypePrefix.STARTING.length);
383      } else if (nameOverride.startsWith(WindowTypePrefix.DEBUGGER)) {
384        nameOverride = nameOverride.substring(WindowTypePrefix.DEBUGGER.length);
385      }
386    }
387
388    return nameOverride ?? identifier?.title ?? '';
389  }
390
391  private makeToken(identifier: IdentifierProto | undefined): string {
392    return identifier?.hashCode?.toString(16) ?? '';
393  }
394
395  private getContainer(
396    containerChild: WindowContainerChildProto,
397  ): WindowContainerChildType {
398    if (containerChild.displayContent) {
399      return containerChild.displayContent;
400    }
401    if (containerChild.displayArea) {
402      return containerChild.displayArea;
403    }
404    if (containerChild.task) {
405      return containerChild.task;
406    }
407    if (containerChild.activity) {
408      return containerChild.activity;
409    }
410    if (containerChild.windowToken) {
411      return containerChild.windowToken;
412    }
413    if (containerChild.window) {
414      return containerChild.window;
415    }
416    if (containerChild.taskFragment) {
417      return containerChild.taskFragment;
418    }
419    return assertDefined(containerChild.windowContainer);
420  }
421
422  private mapChildrenToTokens(children: WindowContainerChildProto[]): string[] {
423    return children
424      .map((child) => {
425        const identifier = this.getIdentifier(child);
426        return this.makeToken(identifier);
427      })
428      .filter((token) => token.length > 0);
429  }
430}
431