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