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 {HierarchyTreeNode} from 'trace/tree_node/hierarchy_tree_node';
19
20class WindowManagerImeUtils {
21  getFocusedActivity(entry: HierarchyTreeNode): HierarchyTreeNode | undefined {
22    const focusedDisplay = this.getFocusedDisplay(entry);
23    const focusedWindow = this.getFocusedWindow(entry);
24    const resumedActivity =
25      focusedDisplay?.getEagerPropertyByName('resumedActivity');
26
27    let focusedActivity: HierarchyTreeNode | undefined;
28    if (focusedDisplay && resumedActivity) {
29      const rootTasks = this.getRootTasks(focusedDisplay);
30      focusedActivity = this.getActivityByName(
31        assertDefined(resumedActivity.getChildByName('title')).getValue(),
32        rootTasks,
33      );
34    } else if (focusedDisplay && focusedWindow) {
35      focusedActivity = this.getActivitiesForWindowState(
36        focusedWindow,
37        focusedDisplay,
38      )?.at(0);
39    }
40
41    return focusedActivity;
42  }
43
44  getFocusedWindow(entry: HierarchyTreeNode): HierarchyTreeNode | undefined {
45    const focusedWindowTitle = entry
46      .getEagerPropertyByName('focusedWindow')
47      ?.getChildByName('title')
48      ?.getValue();
49    return this.getVisibleWindows(entry).find(
50      (window) => window.name === focusedWindowTitle,
51    );
52  }
53
54  private getFocusedDisplay(
55    entry: HierarchyTreeNode,
56  ): HierarchyTreeNode | undefined {
57    const focusedDisplayId: number | undefined = entry
58      .getEagerPropertyByName('focusedDisplayId')
59      ?.getValue();
60    return entry
61      .getAllChildren()
62      .find(
63        (node) =>
64          node.getEagerPropertyByName('id')?.getValue() === focusedDisplayId,
65      );
66  }
67
68  private getVisibleWindows(entry: HierarchyTreeNode): HierarchyTreeNode[] {
69    const windowStates = entry.filterDfs((node) => {
70      return node.id.startsWith('WindowState ');
71    }, true);
72    const display = assertDefined(
73      entry
74        .getAllChildren()
75        .find(
76          (node) =>
77            node.getEagerPropertyByName('id')?.getValue() ===
78            this.defaultDisplayId,
79        ),
80    );
81
82    return windowStates.filter((state) => {
83      const activities = this.getActivitiesForWindowState(state, display);
84      const windowIsVisible =
85        state.getEagerPropertyByName('isComputedVisible')?.getValue() ?? false;
86      const activityIsVisible =
87        activities.find((activity) =>
88          activity.getEagerPropertyByName('isComputedVisible')?.getValue(),
89        ) ?? false;
90      return windowIsVisible && (activityIsVisible || activities.length === 0);
91    });
92  }
93
94  private getActivitiesForWindowState(
95    windowState: HierarchyTreeNode,
96    display: HierarchyTreeNode,
97  ): HierarchyTreeNode[] {
98    return this.getRootTasks(display).reduce((activities, stack) => {
99      const activity = this.getActivity(stack, (activity) =>
100        this.hasWindowState(activity, windowState),
101      );
102      if (activity) {
103        activities.push(activity);
104      }
105      return activities;
106    }, new Array<HierarchyTreeNode>());
107  }
108
109  private hasWindowState(
110    activity: HierarchyTreeNode,
111    windowState: HierarchyTreeNode,
112  ): boolean {
113    return (
114      activity.filterDfs((node) => {
115        return (
116          node.id.startsWith('WindowState ') && node.name === windowState.name
117        );
118      }, true).length > 0
119    );
120  }
121
122  private getRootTasks(display: HierarchyTreeNode): HierarchyTreeNode[] {
123    const tasks = display.filterDfs((node) => {
124      const isTask = node.id.startsWith('Task ');
125      if (!isTask) return false;
126
127      const taskId = node.getEagerPropertyByName('id')?.getValue();
128      const rootTaskId = node.getEagerPropertyByName('rootTaskId')?.getValue();
129      return rootTaskId !== undefined && taskId === rootTaskId;
130    }, true);
131
132    const rootOrganizedTasks: HierarchyTreeNode[] = [];
133
134    tasks.reverse().filter((task: HierarchyTreeNode) => {
135      if (task.getEagerPropertyByName('createdByOrganiser')?.getValue()) {
136        rootOrganizedTasks.push(task);
137        return false;
138      }
139      return true;
140    });
141    // Add root tasks controlled by an organizer
142    rootOrganizedTasks.reverse().forEach((rootOrganizedTask) => {
143      tasks.push(...rootOrganizedTask.getAllChildren().slice().reverse());
144    });
145
146    return tasks;
147  }
148
149  private getActivityByName(
150    activityName: string,
151    rootTasks: HierarchyTreeNode[],
152  ): HierarchyTreeNode | undefined {
153    for (const rootTask of rootTasks) {
154      const activity = this.getActivity(
155        rootTask,
156        (activity: HierarchyTreeNode) => activity.name.includes(activityName),
157      );
158      if (activity) {
159        return activity;
160      }
161    }
162    return undefined;
163  }
164
165  private getActivity(
166    task: HierarchyTreeNode,
167    predicate: (activity: HierarchyTreeNode) => boolean,
168  ): HierarchyTreeNode | undefined {
169    const children = task.getAllChildren().slice().reverse();
170    let activity = children
171      .filter((child) => child.id.startsWith('Activity '))
172      .find(predicate);
173
174    if (activity) {
175      return activity;
176    }
177
178    for (const task of children.filter((child) =>
179      child.id.startsWith('Task '),
180    )) {
181      activity = this.getActivity(task, predicate);
182      if (activity) {
183        return activity;
184      }
185    }
186    for (const taskFragment of children.filter((child) =>
187      child.id.startsWith('TaskFragment '),
188    )) {
189      activity = this.getActivity(taskFragment, predicate);
190      if (activity) {
191        return activity;
192      }
193    }
194    return;
195  }
196
197  private readonly defaultDisplayId = 0;
198}
199export const WmImeUtils = new WindowManagerImeUtils();
200