• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
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