1/* 2 * Copyright (C) 2025 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 {Operation} from 'trace/tree_node/operations/operation'; 19import {PropertyTreeNode} from 'trace/tree_node/property_tree_node'; 20import {PropertyTreeNodeFactory} from 'trace/tree_node/property_tree_node_factory'; 21 22/** 23 * A single input event can be dispatched to multiple windows, where each dispatch 24 * is represented by a separate AndroidWindowInputDispatchEvent. The dispatch event 25 * can contain axis values in the window and raw coordinate spaces, but to save space, 26 * these values are only traced if they are different from the events'. In this 27 * operation, we propagate the coordinate values in the cases that they were not 28 * logged to save space. 29 */ 30export class InputCoordinatePropagator implements Operation<PropertyTreeNode> { 31 static readonly AXIS_X = 0; 32 static readonly AXIS_Y = 1; 33 34 private propertyTreeNodeFactory = new PropertyTreeNodeFactory(); 35 36 apply(root: PropertyTreeNode): void { 37 const motionEventTree = root.getChildByName('motionEvent'); 38 if ( 39 motionEventTree === undefined || 40 motionEventTree.getAllChildren().length === 0 41 ) { 42 return; 43 } 44 45 const pointersById = this.getPointerCoordsById(motionEventTree); 46 if (pointersById.size === 0) return; 47 48 const dispatchTree = assertDefined( 49 root.getChildByName('windowDispatchEvents'), 50 ); 51 dispatchTree.getAllChildren().forEach((dispatchEntry) => { 52 dispatchEntry 53 .getChildByName('dispatchedPointer') 54 ?.getAllChildren() 55 ?.forEach((pointer) => { 56 const pointerId = pointer.getChildByName('pointerId')?.getValue(); 57 if (pointerId === undefined) return; 58 59 const eventXY = pointersById.get(Number(pointerId)); 60 if (eventXY === undefined) return; 61 62 let axisValues = pointer.getChildByName('axisValueInWindow'); 63 if (axisValues === undefined) { 64 axisValues = this.addPropertyTo(pointer, 'axisValueInWindow'); 65 } 66 67 const populatedAxes = new Set<number>(); 68 axisValues.getAllChildren().forEach((axisValue) => { 69 const axis = Number(axisValue.getChildByName('axis')?.getValue()); 70 if ( 71 axis !== InputCoordinatePropagator.AXIS_X && 72 axis !== InputCoordinatePropagator.AXIS_Y 73 ) { 74 return; 75 } 76 if (axisValue.getChildByName('value')?.getValue() === undefined) { 77 // This value is not populated, so remove it so that it will be replaced with the 78 // values propagated from the event. 79 axisValues?.removeChild(axisValue.id); 80 return; 81 } 82 populatedAxes.add(axis); 83 }); 84 85 // Populate the X and Y axis values 86 if (!populatedAxes.has(InputCoordinatePropagator.AXIS_X)) { 87 const xAxisValue = this.addPropertyTo(axisValues, 'x'); 88 this.addPropertyTo( 89 xAxisValue, 90 'axis', 91 InputCoordinatePropagator.AXIS_X, 92 ); 93 this.addPropertyTo(xAxisValue, 'value', eventXY[0]); 94 } 95 if (!populatedAxes.has(InputCoordinatePropagator.AXIS_Y)) { 96 const yAxisValue = this.addPropertyTo(axisValues, 'y'); 97 this.addPropertyTo( 98 yAxisValue, 99 'axis', 100 InputCoordinatePropagator.AXIS_Y, 101 ); 102 this.addPropertyTo(yAxisValue, 'value', eventXY[1]); 103 } 104 105 // Populate Raw X and Raw Y values (x/y in display) 106 if (pointer.getChildByName('xInDisplay')?.getValue() === undefined) { 107 this.addPropertyTo(pointer, 'xInDisplay', eventXY[0]); 108 } 109 if (pointer.getChildByName('yInDisplay')?.getValue() === undefined) { 110 this.addPropertyTo(pointer, 'yInDisplay', eventXY[1]); 111 } 112 }); 113 }); 114 } 115 116 private addPropertyTo( 117 parent: PropertyTreeNode, 118 name: string, 119 value: any = undefined, 120 ): PropertyTreeNode { 121 const node = this.propertyTreeNodeFactory.makeCalculatedProperty( 122 parent.id, 123 name, 124 value, 125 ); 126 parent.addOrReplaceChild(node); 127 return node; 128 } 129 130 private getPointerCoordsById( 131 motionEventTree: PropertyTreeNode, 132 ): Map<number, [number, number]> { 133 const pointersById = new Map<number, [number, number]>(); 134 135 motionEventTree 136 .getChildByName('pointer') 137 ?.getAllChildren() 138 ?.forEach((pointer) => { 139 const pointerId = pointer.getChildByName('pointerId')?.getValue(); 140 if (pointerId === undefined) return; 141 142 const axisValues = pointer.getChildByName('axisValue'); 143 let x: number | undefined; 144 let y: number | undefined; 145 axisValues?.getAllChildren()?.forEach((axisValue) => { 146 const axis = Number(axisValue.getChildByName('axis')?.getValue()); 147 if (axis === InputCoordinatePropagator.AXIS_X) { 148 x = axisValue.getChildByName('value')?.getValue(); 149 return; 150 } 151 if (axis === InputCoordinatePropagator.AXIS_Y) { 152 y = axisValue.getChildByName('value')?.getValue(); 153 return; 154 } 155 }); 156 if (x === undefined || y === undefined) return; 157 158 pointersById.set(pointerId, [x, y]); 159 }); 160 161 return pointersById; 162 } 163} 164