1/* 2 * Copyright (C) 2022 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 */ 16import {assertDefined} from 'common/assert_utils'; 17import {FilterFlag} from 'common/filter_flag'; 18import {Timestamp} from 'common/time'; 19import {Item} from 'trace/item'; 20import {HierarchyTreeNode} from 'trace/tree_node/hierarchy_tree_node'; 21import {PropertyTreeNode} from 'trace/tree_node/property_tree_node'; 22import {WindowType} from 'trace/window_type'; 23import {TextFilter} from 'viewers/common/text_filter'; 24import {WmImeUtils} from 'viewers/common/wm_ime_utils'; 25import {TreeNodeFilter, UiTreeUtils} from './ui_tree_utils'; 26 27interface WmStateProperties { 28 timestamp: string | undefined; 29 focusedApp: string | undefined; 30 focusedWindow: string | undefined; 31 focusedActivity: string | undefined; 32 isInputMethodWindowVisible: boolean; 33 imeInputTarget: PropertyTreeNode | undefined; 34 imeLayeringTarget: PropertyTreeNode | undefined; 35 imeInsetsSourceProvider: PropertyTreeNode | undefined; 36 imeControlTarget: PropertyTreeNode | undefined; 37} 38 39export class ProcessedWindowManagerState implements Item { 40 constructor( 41 readonly id: string, 42 readonly name: string, 43 readonly wmStateProperties: WmStateProperties, 44 readonly hierarchyTree: HierarchyTreeNode, 45 ) {} 46} 47 48export interface ImeContainerProperties { 49 id: string; 50 zOrderRelativeOfId: number; 51 z: number; 52} 53 54export interface InputMethodSurfaceProperties { 55 id: string; 56 isVisible: boolean; 57 screenBounds?: PropertyTreeNode; 58 rect?: PropertyTreeNode; 59} 60 61interface RootImeProperties { 62 timestamp: string; 63} 64 65interface ImeLayerProperties { 66 imeContainer: ImeContainerProperties | undefined; 67 inputMethodSurface: InputMethodSurfaceProperties | undefined; 68 focusedWindowColor: PropertyTreeNode | undefined; 69 root: RootImeProperties | undefined; 70} 71 72export class ImeLayers implements Item { 73 constructor( 74 readonly id: string, 75 readonly name: string, 76 readonly properties: ImeLayerProperties, 77 readonly taskLayerOfImeContainer: HierarchyTreeNode | undefined, 78 readonly taskLayerOfImeSnapshot: HierarchyTreeNode | undefined, 79 ) {} 80} 81 82class ImeAdditionalPropertiesUtils { 83 private isInputMethodSurface = UiTreeUtils.makeNodeFilter( 84 new TextFilter('InputMethod').getFilterPredicate(), 85 ); 86 private isImeContainer = UiTreeUtils.makeNodeFilter( 87 new TextFilter('ImeContainer').getFilterPredicate(), 88 ); 89 90 processWindowManagerTraceEntry( 91 entry: HierarchyTreeNode, 92 wmEntryTimestamp: Timestamp | undefined, 93 ): ProcessedWindowManagerState { 94 const displayContent = entry.getAllChildren()[0]; 95 96 const props: WmStateProperties = { 97 timestamp: wmEntryTimestamp ? wmEntryTimestamp.format() : undefined, 98 focusedApp: entry.getEagerPropertyByName('focusedApp')?.getValue(), 99 focusedWindow: this.getFocusedWindowString(entry), 100 focusedActivity: this.getFocusedActivityString(entry), 101 isInputMethodWindowVisible: this.isInputMethodVisible(displayContent), 102 imeInputTarget: this.getImeInputTargetProperty(displayContent), 103 imeLayeringTarget: this.getImeLayeringTargetProperty(displayContent), 104 imeInsetsSourceProvider: displayContent.getEagerPropertyByName( 105 'imeInsetsSourceProvider', 106 ), 107 imeControlTarget: this.getImeControlTargetProperty(displayContent), 108 }; 109 110 return new ProcessedWindowManagerState(entry.id, entry.name, props, entry); 111 } 112 113 getImeLayers( 114 entryTree: HierarchyTreeNode, 115 processedWindowManagerState: ProcessedWindowManagerState, 116 sfEntryTimestamp: Timestamp | undefined, 117 ): ImeLayers | undefined { 118 const imeContainerLayer = entryTree.findDfs(this.isImeContainer); 119 120 if (!imeContainerLayer) { 121 return undefined; 122 } 123 124 const imeContainerProps: ImeContainerProperties = { 125 id: imeContainerLayer.id, 126 zOrderRelativeOfId: assertDefined( 127 imeContainerLayer.getEagerPropertyByName('zOrderRelativeOf'), 128 ).getValue(), 129 z: assertDefined( 130 imeContainerLayer.getEagerPropertyByName('z'), 131 ).getValue(), 132 }; 133 134 const inputMethodSurfaceLayer = imeContainerLayer.findDfs( 135 this.isInputMethodSurface, 136 ); 137 138 if (!inputMethodSurfaceLayer) { 139 return undefined; 140 } 141 142 const inputMethodSurfaceProps: InputMethodSurfaceProperties = { 143 id: inputMethodSurfaceLayer.id, 144 isVisible: assertDefined( 145 inputMethodSurfaceLayer.getEagerPropertyByName('isComputedVisible'), 146 ).getValue(), 147 screenBounds: 148 inputMethodSurfaceLayer.getEagerPropertyByName('screenBounds'), 149 rect: inputMethodSurfaceLayer.getEagerPropertyByName('bounds'), 150 }; 151 152 let focusedWindowLayer: HierarchyTreeNode | undefined; 153 const focusedWindowToken = 154 processedWindowManagerState.wmStateProperties.focusedWindow 155 ?.split(' ')[0] 156 .slice(1); 157 if (focusedWindowToken) { 158 const isFocusedWindow = UiTreeUtils.makeNodeFilter( 159 new TextFilter(focusedWindowToken).getFilterPredicate(), 160 ); 161 focusedWindowLayer = entryTree.findDfs(isFocusedWindow); 162 } 163 164 const focusedWindowColor = focusedWindowLayer 165 ? focusedWindowLayer.getEagerPropertyByName('color') 166 : undefined; 167 168 // we want to see both ImeContainer and IME-snapshot if there are 169 // cases where both exist 170 const taskLayerOfImeContainer = this.findAncestorTaskLayerOfImeLayer( 171 entryTree, 172 this.isImeContainer, 173 ); 174 175 const taskLayerOfImeSnapshot = this.findAncestorTaskLayerOfImeLayer( 176 entryTree, 177 UiTreeUtils.makeNodeFilter( 178 new TextFilter('IME-snapshot').getFilterPredicate(), 179 ), 180 ); 181 182 const rootProperties = sfEntryTimestamp 183 ? {timestamp: sfEntryTimestamp.format()} 184 : undefined; 185 186 return new ImeLayers( 187 entryTree.id, 188 entryTree.name, 189 { 190 imeContainer: imeContainerProps, 191 inputMethodSurface: inputMethodSurfaceProps, 192 focusedWindowColor, 193 root: rootProperties, 194 }, 195 taskLayerOfImeContainer, 196 taskLayerOfImeSnapshot, 197 ); 198 } 199 200 private getFocusedWindowString(entry: HierarchyTreeNode): string | undefined { 201 let focusedWindowString = undefined; 202 const focusedWindow = WmImeUtils.getFocusedWindow(entry); 203 if (focusedWindow) { 204 const token = assertDefined( 205 focusedWindow.getEagerPropertyByName('token'), 206 ).getValue(); 207 const windowTypeSuffix = this.getWindowTypeSuffix( 208 assertDefined( 209 focusedWindow.getEagerPropertyByName('windowType'), 210 ).getValue(), 211 ); 212 const type = assertDefined( 213 focusedWindow 214 .getEagerPropertyByName('attributes') 215 ?.getChildByName('type'), 216 ).formattedValue(); 217 const windowFrames = assertDefined( 218 focusedWindow.getEagerPropertyByName('windowFrames'), 219 ); 220 const containingFrame = assertDefined( 221 windowFrames.getChildByName('containingFrame')?.formattedValue(), 222 ); 223 const parentFrame = assertDefined( 224 windowFrames.getChildByName('parentFrame')?.formattedValue(), 225 ); 226 227 focusedWindowString = `{${token} ${focusedWindow.name}${windowTypeSuffix}} type=${type} cf=${containingFrame} pf=${parentFrame}`; 228 } 229 return focusedWindowString; 230 } 231 232 private getFocusedActivityString(entry: HierarchyTreeNode): string { 233 let focusedActivityString = 'null'; 234 const focusedActivity = WmImeUtils.getFocusedActivity(entry); 235 if (focusedActivity) { 236 const token = assertDefined( 237 focusedActivity.getEagerPropertyByName('token'), 238 ).getValue(); 239 const state = assertDefined( 240 focusedActivity.getEagerPropertyByName('state'), 241 ).getValue(); 242 const isVisible = 243 focusedActivity 244 .getEagerPropertyByName('isComputedVisible') 245 ?.getValue() ?? false; 246 247 focusedActivityString = `{${token} ${focusedActivity.name}} state=${state} visible=${isVisible}`; 248 } 249 return focusedActivityString; 250 } 251 252 private getWindowTypeSuffix(windowType: number): string { 253 switch (windowType) { 254 case WindowType.STARTING: 255 return ' STARTING'; 256 case WindowType.EXITING: 257 return ' EXITING'; 258 case WindowType.DEBUGGER: 259 return ' DEBUGGER'; 260 default: 261 return ''; 262 } 263 } 264 265 private findAncestorTaskLayerOfImeLayer( 266 entryTree: HierarchyTreeNode, 267 isTargetImeLayer: TreeNodeFilter, 268 ): HierarchyTreeNode | undefined { 269 const imeLayer = entryTree.findDfs(isTargetImeLayer); 270 271 if (!imeLayer) { 272 return undefined; 273 } 274 275 const isTaskLayer = UiTreeUtils.makeNodeFilter( 276 new TextFilter('Task|ImePlaceholder', [ 277 FilterFlag.USE_REGEX, 278 ]).getFilterPredicate(), 279 ); 280 const taskLayer = imeLayer.findAncestor(isTaskLayer); 281 if (!taskLayer) { 282 return undefined; 283 } 284 285 return taskLayer; 286 } 287 288 private getImeControlTargetProperty( 289 displayContent: HierarchyTreeNode, 290 ): PropertyTreeNode | undefined { 291 return displayContent.getEagerPropertyByName('inputMethodControlTarget'); 292 } 293 294 private getImeInputTargetProperty( 295 displayContent: HierarchyTreeNode, 296 ): PropertyTreeNode | undefined { 297 return displayContent.getEagerPropertyByName('inputMethodInputTarget'); 298 } 299 300 private getImeLayeringTargetProperty( 301 displayContent: HierarchyTreeNode, 302 ): PropertyTreeNode | undefined { 303 return displayContent.getEagerPropertyByName('inputMethodTarget'); 304 } 305 306 private isInputMethodVisible(displayContent: HierarchyTreeNode): boolean { 307 const inputMethodWindowOrLayer = displayContent.findDfs( 308 this.isInputMethodSurface, 309 ); 310 return inputMethodWindowOrLayer 311 ?.getEagerPropertyByName('isComputedVisible') 312 ?.getValue(); 313 } 314} 315 316export const ImeUtils = new ImeAdditionalPropertiesUtils(); 317