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(layer.id) 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