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