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