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