1 /*
2  * Copyright (C) 2020 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 
17 package com.android.server.accessibility.utils;
18 
19 import static android.view.MotionEvent.ACTION_DOWN;
20 import static android.view.MotionEvent.ACTION_MOVE;
21 import static android.view.MotionEvent.ACTION_POINTER_DOWN;
22 import static android.view.MotionEvent.ACTION_POINTER_UP;
23 import static android.view.MotionEvent.ACTION_UP;
24 
25 import android.graphics.PointF;
26 import android.os.SystemClock;
27 import android.view.InputDevice;
28 import android.view.MotionEvent;
29 
30 import java.util.ArrayList;
31 import java.util.List;
32 
33 /**
34  * generates {@link MotionEvent} with source {@link InputDevice#SOURCE_TOUCHSCREEN}
35  */
36 public class TouchEventGenerator {
37 
downEvent(int displayId, float x, float y)38     public static MotionEvent downEvent(int displayId, float x, float y) {
39         return generateSingleTouchEvent(displayId, ACTION_DOWN, x, y);
40     }
41 
moveEvent(int displayId, float x, float y)42     public static MotionEvent moveEvent(int displayId, float x, float y) {
43         return generateSingleTouchEvent(displayId, ACTION_MOVE, x, y);
44     }
45 
upEvent(int displayId, float x, float y)46     public static MotionEvent upEvent(int displayId, float x, float y) {
47         return generateSingleTouchEvent(displayId, ACTION_UP, x, y);
48     }
49 
generateSingleTouchEvent(int displayId, int action, float x, float y)50     private static MotionEvent generateSingleTouchEvent(int displayId, int action, float x,
51             float y) {
52         return generateMultiplePointersEvent(displayId, action, new PointF(x, y));
53     }
54 
55     /**
56      * Creates a list of {@link MotionEvent} with given pointers location.
57      *
58      * @param displayId the display id
59      * @param pointF1   location on the screen of the second pointer.
60      * @param pointF2   location on the screen of the second pointer.
61      * @return a list of {@link MotionEvent} with {@link MotionEvent#ACTION_DOWN} and {@link
62      * MotionEvent#ACTION_POINTER_DOWN}.
63      */
twoPointersDownEvents(int displayId, PointF pointF1, PointF pointF2)64     public static List<MotionEvent> twoPointersDownEvents(int displayId, PointF pointF1,
65             PointF pointF2) {
66         final List<MotionEvent> downEvents = new ArrayList<>();
67         final MotionEvent downEvent = generateMultiplePointersEvent(displayId,
68                 MotionEvent.ACTION_DOWN, pointF1);
69         downEvents.add(downEvent);
70 
71         final int actionIndex = 1 << MotionEvent.ACTION_POINTER_INDEX_SHIFT;
72         final int action = ACTION_POINTER_DOWN | actionIndex;
73 
74         final MotionEvent twoPointersDownEvent = generateMultiplePointersEvent(displayId, action,
75                 pointF1, pointF2);
76         downEvents.add(twoPointersDownEvent);
77         return downEvents;
78     }
79 
generateMultiplePointersEvent(int displayId, int action, PointF... pointFs)80     private static MotionEvent generateMultiplePointersEvent(int displayId, int action,
81             PointF... pointFs) {
82         final int length = pointFs.length;
83         final MotionEvent.PointerCoords[] pointerCoordsArray =
84                 new MotionEvent.PointerCoords[length];
85         final MotionEvent.PointerProperties[] pointerPropertiesArray =
86                 new MotionEvent.PointerProperties[length];
87         for (int i = 0; i < length; i++) {
88             MotionEvent.PointerCoords pointerCoords = new MotionEvent.PointerCoords();
89             pointerCoords.x = pointFs[i].x;
90             pointerCoords.y = pointFs[i].y;
91             pointerCoordsArray[i] = pointerCoords;
92 
93             MotionEvent.PointerProperties pointerProperties = new MotionEvent.PointerProperties();
94             pointerProperties.id = i;
95             pointerProperties.toolType = MotionEvent.TOOL_TYPE_FINGER;
96             pointerPropertiesArray[i] = pointerProperties;
97         }
98 
99         final long downTime = SystemClock.uptimeMillis();
100         final MotionEvent ev = MotionEvent.obtain(
101                 /* downTime */ downTime,
102                 /* eventTime */ downTime,
103                 /* action */ action,
104                 /* pointerCount */ length,
105                 /* pointerProperties */ pointerPropertiesArray,
106                 /* pointerCoords */ pointerCoordsArray,
107                 /* metaState */ 0,
108                 /* buttonState */ 0,
109                 /* xPrecision */ 1.0f,
110                 /* yPrecision */ 1.0f,
111                 /* deviceId */ 0,
112                 /* edgeFlags */ 0,
113                 /* source */ InputDevice.SOURCE_TOUCHSCREEN,
114                 /* flags */ 0);
115         ev.setDisplayId(displayId);
116         return ev;
117     }
118 
119     /**
120      *  Generates a move event that moves the pointer of the original event with given index.
121      *  The original event should not be up event and we don't support
122      *  {@link MotionEvent#ACTION_POINTER_UP} now.
123      *
124      * @param originalEvent the move or down event
125      * @param pointerIndex the index of the pointer we want to move.
126      * @param offsetX the offset in X coordinate.
127      * @param offsetY the offset in Y coordinate.
128      * @return a motion event with move action.
129      */
movePointer(MotionEvent originalEvent, int pointerIndex, float offsetX, float offsetY)130     public static MotionEvent movePointer(MotionEvent originalEvent, int pointerIndex,
131             float offsetX, float offsetY) {
132         if (originalEvent.getActionMasked() == ACTION_UP) {
133             throw new IllegalArgumentException("No pointer is on the screen");
134         }
135 
136         if (originalEvent.getActionMasked() == ACTION_POINTER_UP) {
137             throw new IllegalArgumentException("unsupported yet,please implement it first");
138         }
139 
140         final int pointerCount = originalEvent.getPointerCount();
141         if (pointerIndex >= pointerCount) {
142             throw new IllegalArgumentException(
143                     pointerIndex + "is not available with pointer count" + pointerCount);
144         }
145         final int action = MotionEvent.ACTION_MOVE;
146         final MotionEvent.PointerProperties[] pp = new MotionEvent.PointerProperties[pointerCount];
147         for (int i = 0; i < pointerCount; i++) {
148             MotionEvent.PointerProperties pointerProperty = new MotionEvent.PointerProperties();
149             originalEvent.getPointerProperties(i, pointerProperty);
150             pp[i] = pointerProperty;
151         }
152 
153         final MotionEvent.PointerCoords[] pc = new MotionEvent.PointerCoords[pointerCount];
154         for (int i = 0; i < pointerCount; i++) {
155             MotionEvent.PointerCoords pointerCoord = new MotionEvent.PointerCoords();
156             originalEvent.getPointerCoords(i, pointerCoord);
157             pc[i] = pointerCoord;
158         }
159         pc[pointerIndex].x += offsetX;
160         pc[pointerIndex].y += offsetY;
161         final MotionEvent ev = MotionEvent.obtain(
162                 /* downTime */ originalEvent.getDownTime(),
163                 /* eventTime */ SystemClock.uptimeMillis(),
164                 /* action */ action,
165                 /* pointerCount */ 2,
166                 /* pointerProperties */ pp,
167                 /* pointerCoords */ pc,
168                 /* metaState */ 0,
169                 /* buttonState */ 0,
170                 /* xPrecision */ 1.0f,
171                 /* yPrecision */ 1.0f,
172                 /* deviceId */ 0,
173                 /* edgeFlags */ 0,
174                 /* source */ originalEvent.getSource(),
175                 /* flags */ originalEvent.getFlags());
176         ev.setDisplayId(originalEvent.getDisplayId());
177         return ev;
178     }
179 }
180