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 {Rect} from 'common/geometry/rect'; 19import {Region} from 'common/geometry/region'; 20import {Size} from 'common/geometry/size'; 21import {TransformMatrix} from 'common/geometry/transform_matrix'; 22import { 23 Transform, 24 TransformType, 25} from 'parsers/surface_flinger/transform_utils'; 26import {GeometryFactory} from 'trace/geometry_factory'; 27import {TraceRect} from 'trace/trace_rect'; 28import {TraceRectBuilder} from 'trace/trace_rect_builder'; 29import {Computation} from 'trace/tree_node/computation'; 30import {HierarchyTreeNode} from 'trace/tree_node/hierarchy_tree_node'; 31import {PropertyTreeNode} from 'trace/tree_node/property_tree_node'; 32import {LayerExtractor} from './layer_extractor'; 33 34function getDisplaySize(display: PropertyTreeNode): Size { 35 const displaySize = assertDefined(display.getChildByName('size')); 36 const w = assertDefined(displaySize.getChildByName('w')?.getValue()); 37 const h = assertDefined(displaySize.getChildByName('h')?.getValue()); 38 const transformType = 39 display.getChildByName('transform')?.getChildByName('type')?.getValue() ?? 40 0; 41 const typeFlags = TransformType.getTypeFlags(transformType); 42 const isRotated = 43 typeFlags.includes('ROT_90') || typeFlags.includes('ROT_270'); 44 return { 45 width: isRotated ? h : w, 46 height: isRotated ? w : h, 47 }; 48} 49 50// InputConfig constants defined in the platform: 51// frameworks/native/libs/input/android/os/InputConfig.aidl 52export enum InputConfig { 53 NOT_TOUCHABLE = 1 << 3, 54 IS_WALLPAPER = 1 << 6, 55 SPY = 1 << 14, 56} 57 58class RectSfFactory { 59 static makeDisplayRects(displays: readonly PropertyTreeNode[]): TraceRect[] { 60 const nameCounts = new Map<string, number>(); 61 return displays.map((display, index) => { 62 const layerStackSpaceRect = assertDefined( 63 display.getChildByName('layerStackSpaceRect'), 64 ); 65 66 let displayRect = GeometryFactory.makeRect(layerStackSpaceRect); 67 const isEmptyLayerStackRect = displayRect.isEmpty(); 68 69 if (isEmptyLayerStackRect) { 70 const size = getDisplaySize(display); 71 displayRect = new Rect(0, 0, size.width, size.height); 72 } 73 74 const layerStack = assertDefined( 75 display.getChildByName('layerStack'), 76 ).getValue(); 77 let displayName = display.getChildByName('name')?.getValue(); 78 if (!displayName) { 79 displayName = 'Unknown Display'; 80 } 81 const id = assertDefined(display.getChildByName('id')).getValue(); 82 83 const existingNameCount = nameCounts.get(displayName); 84 if (existingNameCount !== undefined) { 85 nameCounts.set(displayName, existingNameCount + 1); 86 displayName += ` (${existingNameCount + 1})`; 87 } else { 88 nameCounts.set(displayName, 1); 89 } 90 91 const isOn = display.getChildByName('isOn')?.getValue() ?? false; 92 const isVirtual = 93 display.getChildByName('isVirtual')?.getValue() ?? false; 94 95 return new TraceRectBuilder() 96 .setX(displayRect.x) 97 .setY(displayRect.y) 98 .setWidth(displayRect.w) 99 .setHeight(displayRect.h) 100 .setId(`Display - ${id}`) 101 .setName(displayName) 102 .setCornerRadius(0) 103 .setTransform(Transform.EMPTY.matrix) 104 .setGroupId(layerStack) 105 .setIsVisible(false) 106 .setIsDisplay(true) 107 .setIsActiveDisplay(isOn && !isVirtual) 108 .setDepth(index) 109 .setIsSpy(false) 110 .build(); 111 }); 112 } 113 114 static makeLayerRect( 115 layer: HierarchyTreeNode, 116 layerStack: number, 117 absoluteZ: number, 118 ): TraceRect { 119 const isVisible = assertDefined( 120 layer.getEagerPropertyByName('isComputedVisible'), 121 ).getValue(); 122 123 const name = assertDefined(layer.getEagerPropertyByName('name')).getValue(); 124 const bounds = assertDefined(layer.getEagerPropertyByName('bounds')); 125 const boundsRect = GeometryFactory.makeRect(bounds); 126 127 let opacity = layer 128 .getEagerPropertyByName('color') 129 ?.getChildByName('a') 130 ?.getValue(); 131 if (isVisible && opacity === undefined) opacity = 0; 132 133 return new TraceRectBuilder() 134 .setX(boundsRect.x) 135 .setY(boundsRect.y) 136 .setWidth(boundsRect.w) 137 .setHeight(boundsRect.h) 138 .setId( 139 `${assertDefined( 140 layer.getEagerPropertyByName('id'), 141 ).getValue()} ${name}`, 142 ) 143 .setName(name) 144 .setCornerRadius( 145 assertDefined(layer.getEagerPropertyByName('cornerRadius')).getValue(), 146 ) 147 .setTransform( 148 Transform.from(assertDefined(layer.getEagerPropertyByName('transform'))) 149 .matrix, 150 ) 151 .setGroupId(layerStack) 152 .setIsVisible(isVisible) 153 .setIsDisplay(false) 154 .setDepth(absoluteZ) 155 .setOpacity(opacity) 156 .setIsSpy(false) 157 .build(); 158 } 159 160 static makeInputWindowRect( 161 layer: HierarchyTreeNode, 162 layerStack: number, 163 absoluteZ: number, 164 invalidBoundsFromDisplays: Rect[], 165 display?: TraceRect, 166 displayTransform?: TransformMatrix, 167 ): TraceRect { 168 const name = assertDefined(layer.getEagerPropertyByName('name')).getValue(); 169 const inputWindowInfo = assertDefined( 170 layer.getEagerPropertyByName('inputWindowInfo'), 171 ); 172 173 const layerTransform = Transform.from( 174 assertDefined(layer.getEagerPropertyByName('transform')), 175 ).matrix; 176 const inverseLayerTransform = layerTransform.inverse(); 177 178 // The input frame is given in the display space. 179 let inputWindowRect = GeometryFactory.makeRect( 180 assertDefined(inputWindowInfo.getChildByName('frame')), 181 ); 182 // Transform it to layer space. 183 inputWindowRect = inverseLayerTransform.transformRect( 184 displayTransform?.transformRect(inputWindowRect) ?? inputWindowRect, 185 ); 186 187 const inputConfig = assertDefined( 188 inputWindowInfo.getChildByName('inputConfig'), 189 ).getValue(); 190 191 const shouldCropToDisplay = 192 inputWindowRect.isEmpty() || 193 (inputConfig & InputConfig.IS_WALLPAPER) !== 0 || 194 (invalidBoundsFromDisplays !== undefined && 195 invalidBoundsFromDisplays.some((invalid) => 196 inputWindowRect.isAlmostEqual(invalid, 0.01), 197 )); 198 if (shouldCropToDisplay && display !== undefined) { 199 inputWindowRect = inputWindowRect.cropRect(display); 200 } 201 202 const isVisible = 203 inputWindowInfo.getChildByName('visible')?.getValue() ?? 204 assertDefined( 205 layer.getEagerPropertyByName('isComputedVisible'), 206 ).getValue(); 207 208 let touchableRegion: Region | undefined; 209 const isTouchable = (inputConfig & InputConfig.NOT_TOUCHABLE) === 0; 210 const touchableRegionNode = 211 inputWindowInfo.getChildByName('touchableRegion'); 212 213 if (!isTouchable) { 214 touchableRegion = Region.createEmpty(); 215 } else if (touchableRegionNode !== undefined) { 216 // The touchable region is given in the display space. 217 touchableRegion = GeometryFactory.makeRegion(touchableRegionNode); 218 // Transform it to layer space. 219 touchableRegion = inverseLayerTransform.transformRegion( 220 displayTransform?.transformRegion(touchableRegion) ?? touchableRegion, 221 ); 222 if (shouldCropToDisplay && display !== undefined) { 223 touchableRegion = new Region( 224 touchableRegion.rects.map((rect) => { 225 return rect.cropRect(display); 226 }), 227 ); 228 } 229 } 230 231 return new TraceRectBuilder() 232 .setX(inputWindowRect.x) 233 .setY(inputWindowRect.y) 234 .setWidth(inputWindowRect.w) 235 .setHeight(inputWindowRect.h) 236 .setId(`${assertDefined(layer.getEagerPropertyByName('id')).getValue()}`) 237 .setName(name) 238 .setCornerRadius(0) 239 .setTransform(layerTransform) 240 .setGroupId(layerStack) 241 .setIsVisible(isVisible) 242 .setIsDisplay(false) 243 .setDepth(absoluteZ) 244 .setIsSpy((inputConfig & InputConfig.SPY) !== 0) 245 .setFillRegion(touchableRegion) 246 .build(); 247 } 248} 249 250export class RectsComputation implements Computation { 251 private static readonly DEFAULT_INVALID_BOUNDS = new Rect( 252 -50000, 253 -50000, 254 100000, 255 100000, 256 ); 257 258 private root?: HierarchyTreeNode; 259 private displaysByLayerStack?: Map<number, TraceRect>; 260 private displayTransformsByLayerStack?: Map<number, TransformMatrix>; 261 private invalidBoundsFromDisplays?: Rect[]; 262 263 setRoot(value: HierarchyTreeNode): this { 264 this.root = value; 265 return this; 266 } 267 268 // synced with getMaxDisplayBounds() in main/frameworks/native/services/surfaceflinger/SurfaceFlinger.cpp 269 private static getInvalidBoundsFromDisplays( 270 displays: readonly PropertyTreeNode[], 271 ): Rect[] { 272 if (displays.length === 0) return []; 273 274 // foldables expand rects to fill display space before all displays are available 275 // make invalid bounds for each individual display, and for the rect of max dimensions 276 277 const invalidBounds: Rect[] = []; 278 279 const maxSize = displays.reduce( 280 (size, display) => { 281 const displaySize = getDisplaySize(display); 282 invalidBounds.push( 283 ...RectsComputation.makeInvalidBoundsFromSize(displaySize), 284 ); 285 return { 286 width: Math.max(size.width, displaySize.width), 287 height: Math.max(size.height, displaySize.height), 288 }; 289 }, 290 {width: 0, height: 0}, 291 ); 292 invalidBounds.push(...RectsComputation.makeInvalidBoundsFromSize(maxSize)); 293 294 return invalidBounds; 295 } 296 297 private static makeInvalidBoundsFromSize(size: Size): Rect[] { 298 const [invalidX, invalidY] = [size.width * 10, size.height * 10]; 299 const invalidBounds = new Rect( 300 -invalidX, 301 -invalidY, 302 invalidX * 2, 303 invalidY * 2, 304 ); 305 const rotatedInvalidBounds = new Rect( 306 invalidBounds.y, 307 invalidBounds.x, 308 invalidBounds.h, 309 invalidBounds.w, 310 ); 311 return [invalidBounds, rotatedInvalidBounds]; 312 } 313 314 executeInPlace(): void { 315 this.processDisplays(); 316 this.processLayers( 317 RectsComputation.hasLayerRect, 318 RectSfFactory.makeLayerRect, 319 true, 320 ); 321 this.processLayers( 322 RectsComputation.hasInputWindowRect, 323 RectSfFactory.makeInputWindowRect, 324 false, 325 ); 326 } 327 328 private processDisplays() { 329 if (!this.root) { 330 throw new Error('root not set in SF rects computation'); 331 } 332 const displays = 333 this.root.getEagerPropertyByName('displays')?.getAllChildren() ?? []; 334 const displayRects = RectSfFactory.makeDisplayRects(displays); 335 this.root.setRects(displayRects); 336 337 this.displaysByLayerStack = new Map( 338 displayRects.map((rect) => [rect.groupId, rect]), 339 ); 340 341 this.invalidBoundsFromDisplays = 342 RectsComputation.getInvalidBoundsFromDisplays(displays); 343 344 this.displayTransformsByLayerStack = new Map(); 345 displays.forEach((display) => { 346 const layerStack = assertDefined( 347 display.getChildByName('layerStack'), 348 ).getValue(); 349 const matrix = RectsComputation.extractDisplayTransform(display); 350 if (matrix) { 351 assertDefined(this.displayTransformsByLayerStack).set( 352 layerStack, 353 matrix, 354 ); 355 } 356 }); 357 } 358 359 private static extractDisplayTransform( 360 display: PropertyTreeNode, 361 ): TransformMatrix | undefined { 362 const transformNode = display.getChildByName('transform'); 363 const layerStackSpaceRectNode = assertDefined( 364 display.getChildByName('layerStackSpaceRect'), 365 ); 366 if (!transformNode || !layerStackSpaceRectNode) { 367 return undefined; 368 } 369 const transform = Transform.from(transformNode); 370 let tx = transform.matrix.tx; 371 let ty = transform.matrix.ty; 372 const layerStackSpaceRect = GeometryFactory.makeRect( 373 layerStackSpaceRectNode, 374 ); 375 376 const typeFlags = TransformType.getTypeFlags(transform.type); 377 if (typeFlags.includes('ROT_180')) { 378 tx += layerStackSpaceRect.w; 379 ty += layerStackSpaceRect.h; 380 } else if (typeFlags.includes('ROT_270')) { 381 tx += layerStackSpaceRect.w; 382 } else if (typeFlags.includes('ROT_90')) { 383 ty += layerStackSpaceRect.h; 384 } 385 return TransformMatrix.from({tx, ty}, transform.matrix); 386 } 387 388 private processLayers( 389 shouldIncludeLayer: ( 390 node: HierarchyTreeNode, 391 invalidBoundsFromDisplays: Rect[], 392 ) => boolean, 393 makeTraceRect: ( 394 layer: HierarchyTreeNode, 395 layerStack: number, 396 absoluteZ: number, 397 invalidBoundsFromDisplays: Rect[], 398 display?: TraceRect, 399 displayTransform?: TransformMatrix, 400 ) => TraceRect, 401 isPrimaryRects: boolean, 402 ) { 403 const curAbsoluteZByLayerStack = new Map<number, number>(); 404 for (const layerStack of assertDefined(this.displaysByLayerStack).keys()) { 405 curAbsoluteZByLayerStack.set(layerStack, 1); 406 } 407 408 const layersWithRects = LayerExtractor.extractLayersTopToBottom( 409 assertDefined(this.root), 410 ).filter((node) => 411 shouldIncludeLayer(node, assertDefined(this.invalidBoundsFromDisplays)), 412 ); 413 414 for (let i = layersWithRects.length - 1; i > -1; i--) { 415 const layer = layersWithRects[i]; 416 const layerStack = assertDefined( 417 layer.getEagerPropertyByName('layerStack'), 418 ).getValue(); 419 const absoluteZ = curAbsoluteZByLayerStack.get(layerStack) ?? 0; 420 const rect = makeTraceRect( 421 layer, 422 layerStack, 423 absoluteZ, 424 assertDefined(this.invalidBoundsFromDisplays), 425 this.displaysByLayerStack?.get(layerStack), 426 this.displayTransformsByLayerStack?.get(layerStack), 427 ); 428 isPrimaryRects ? layer.setRects([rect]) : layer.setSecondaryRects([rect]); 429 curAbsoluteZByLayerStack.set(layerStack, absoluteZ + 1); 430 } 431 } 432 433 private static hasLayerRect( 434 node: HierarchyTreeNode, 435 invalidBoundsFromDisplays: Rect[], 436 ): boolean { 437 const isVisible = node 438 .getEagerPropertyByName('isComputedVisible') 439 ?.getValue(); 440 if (isVisible === undefined) { 441 throw new Error( 442 'SF rects computation attempted before visibility computation', 443 ); 444 } 445 446 const screenBounds = node.getEagerPropertyByName('screenBounds'); 447 if (!screenBounds) return false; 448 449 if (screenBounds && !isVisible) { 450 const screenBoundsRect = GeometryFactory.makeRect(screenBounds); 451 const isInvalidFromDisplays = 452 invalidBoundsFromDisplays.length > 0 && 453 invalidBoundsFromDisplays.some((invalid) => { 454 return screenBoundsRect.isAlmostEqual(invalid, 0.01); 455 }); 456 return ( 457 !isInvalidFromDisplays && 458 !screenBoundsRect.isAlmostEqual( 459 RectsComputation.DEFAULT_INVALID_BOUNDS, 460 0.01, 461 ) 462 ); 463 } 464 465 return true; 466 } 467 468 private static hasInputWindowRect(node: HierarchyTreeNode): boolean { 469 const inputWindowInfo = node.getEagerPropertyByName('inputWindowInfo'); 470 return ( 471 inputWindowInfo !== undefined && 472 inputWindowInfo.getChildByName('inputConfig') !== undefined 473 ); 474 } 475} 476