• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
1 /*
2  * Copyright (C) 2017 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 package com.android.launcher3.testcomponent;
17 
18 import android.graphics.Point;
19 import android.util.Pair;
20 import android.view.InputDevice;
21 import android.view.MotionEvent;
22 import android.view.MotionEvent.PointerCoords;
23 import android.view.MotionEvent.PointerProperties;
24 
25 import java.util.HashMap;
26 import java.util.Map;
27 
28 /**
29  * Utility class to generate MotionEvent event sequences for testing touch gesture detectors.
30  */
31 public class TouchEventGenerator {
32 
33     /**
34      * Amount of time between two generated events.
35      */
36     private static final long TIME_INCREMENT_MS = 20L;
37 
38     /**
39      * Id of the fake device generating the events.
40      */
41     private static final int DEVICE_ID = 2104;
42 
43     /**
44      * The fingers currently present on the emulated touch screen.
45      */
46     private Map<Integer, Point> mFingers;
47 
48     /**
49      * Initial event time for the current sequence.
50      */
51     private long mInitialTime;
52 
53     /**
54      * Time of the last generated event.
55      */
56     private long mLastEventTime;
57 
58     /**
59      * Time of the next event.
60      */
61     private long mTime;
62 
63     /**
64      * Receives the generated events.
65      */
66     public interface Listener {
67 
68         /**
69          * Called when an event was generated.
70          */
onTouchEvent(MotionEvent event)71         void onTouchEvent(MotionEvent event);
72     }
73     private final Listener mListener;
74 
TouchEventGenerator(Listener listener)75     public TouchEventGenerator(Listener listener) {
76         mListener = listener;
77         mFingers = new HashMap<Integer, Point>();
78     }
79 
80     /**
81      * Adds a finger on the touchscreen.
82      */
put(int id, int x, int y, long ms)83     public TouchEventGenerator put(int id, int x, int y, long ms) {
84         checkFingerExistence(id, false);
85         boolean isInitialDown = mFingers.isEmpty();
86         mFingers.put(id, new Point(x, y));
87         int action;
88         if (isInitialDown) {
89             action = MotionEvent.ACTION_DOWN;
90         } else {
91             action = MotionEvent.ACTION_POINTER_DOWN;
92             // Set the id of the changed pointer.
93             action |= id << MotionEvent.ACTION_POINTER_INDEX_SHIFT;
94         }
95         generateEvent(action, ms);
96         return this;
97     }
98 
99     /**
100      * Adds a finger on the touchscreen after advancing default time interval.
101      */
put(int id, int x, int y)102     public TouchEventGenerator put(int id, int x, int y) {
103         return put(id, x, y, TIME_INCREMENT_MS);
104     }
105 
106     /**
107      * Adjusts the position of a finger for an upcoming move event.
108      *
109      * @see #move(long ms)
110      */
position(int id, int x, int y)111     public TouchEventGenerator position(int id, int x, int y) {
112         checkFingerExistence(id, true);
113         mFingers.get(id).set(x, y);
114         return this;
115     }
116 
117     /**
118      * Commits the finger position changes of {@link #position(int, int, int)} by generating a move
119      * event.
120      *
121      * @see #position(int, int, int)
122      */
move(long ms)123     public TouchEventGenerator move(long ms) {
124         generateEvent(MotionEvent.ACTION_MOVE, ms);
125         return this;
126     }
127 
128     /**
129      * Commits the finger position changes of {@link #position(int, int, int)} by generating a move
130      * event after advancing the default time interval.
131      *
132      * @see #position(int, int, int)
133      */
move()134     public TouchEventGenerator move() {
135         return move(TIME_INCREMENT_MS);
136     }
137 
138     /**
139      * Moves a single finger on the touchscreen.
140      */
move(int id, int x, int y, long ms)141     public TouchEventGenerator move(int id, int x, int y, long ms) {
142         return position(id, x, y).move(ms);
143     }
144 
145     /**
146      * Moves a single finger on the touchscreen after advancing default time interval.
147      */
move(int id, int x, int y)148     public TouchEventGenerator move(int id, int x, int y) {
149         return move(id, x, y, TIME_INCREMENT_MS);
150     }
151 
152     /**
153      * Removes an existing finger from the touchscreen.
154      */
lift(int id, long ms)155     public TouchEventGenerator lift(int id, long ms) {
156         checkFingerExistence(id, true);
157         boolean isFinalUp = mFingers.size() == 1;
158         int action;
159         if (isFinalUp) {
160             action = MotionEvent.ACTION_UP;
161         } else {
162             action = MotionEvent.ACTION_POINTER_UP;
163             // Set the id of the changed pointer.
164             action |= id << MotionEvent.ACTION_POINTER_INDEX_SHIFT;
165         }
166         generateEvent(action, ms);
167         mFingers.remove(id);
168         return this;
169     }
170 
171     /**
172      * Removes a finger from the touchscreen.
173      */
lift(int id, int x, int y, long ms)174     public TouchEventGenerator lift(int id, int x, int y, long ms) {
175         checkFingerExistence(id, true);
176         mFingers.get(id).set(x, y);
177         return lift(id, ms);
178     }
179 
180     /**
181      * Removes an existing finger from the touchscreen after advancing default time interval.
182      */
lift(int id)183     public TouchEventGenerator lift(int id) {
184         return lift(id, TIME_INCREMENT_MS);
185     }
186 
187     /**
188      * Cancels an ongoing sequence.
189      */
cancel(long ms)190     public TouchEventGenerator cancel(long ms) {
191         generateEvent(MotionEvent.ACTION_CANCEL, ms);
192         mFingers.clear();
193         return this;
194     }
195 
196     /**
197      * Cancels an ongoing sequence.
198      */
cancel()199     public TouchEventGenerator cancel() {
200         return cancel(TIME_INCREMENT_MS);
201     }
202 
checkFingerExistence(int id, boolean shouldExist)203     private void checkFingerExistence(int id, boolean shouldExist) {
204         if (shouldExist != mFingers.containsKey(id)) {
205             throw new IllegalArgumentException(
206                     shouldExist ? "Finger does not exist" : "Finger already exists");
207         }
208     }
209 
generateEvent(int action, long ms)210     private void generateEvent(int action, long ms) {
211         mTime = mLastEventTime + ms;
212         Pair<PointerProperties[], PointerCoords[]> state = getFingerState();
213         MotionEvent event = MotionEvent.obtain(
214                 mInitialTime,
215                 mTime,
216                 action,
217                 state.first.length,
218                 state.first,
219                 state.second,
220                 0 /* metaState */,
221                 0 /* buttonState */,
222                 1.0f /* xPrecision */,
223                 1.0f /* yPrecision */,
224                 DEVICE_ID,
225                 0 /* edgeFlags */,
226                 InputDevice.SOURCE_TOUCHSCREEN,
227                 0 /* flags */);
228         mListener.onTouchEvent(event);
229         if (action == MotionEvent.ACTION_UP) {
230             resetTime();
231         }
232         event.recycle();
233         mLastEventTime = mTime;
234     }
235 
236     /**
237      * Returns the description of the fingers' state expected by MotionEvent.
238      */
getFingerState()239     private Pair<PointerProperties[], PointerCoords[]> getFingerState() {
240         int nFingers = mFingers.size();
241         PointerProperties[] properties = new PointerProperties[nFingers];
242         PointerCoords[] coordinates = new PointerCoords[nFingers];
243 
244         int index = 0;
245         for (Map.Entry<Integer, Point> entry : mFingers.entrySet()) {
246             int id = entry.getKey();
247             Point location = entry.getValue();
248 
249             PointerProperties property = new PointerProperties();
250             property.id = id;
251             property.toolType = MotionEvent.TOOL_TYPE_FINGER;
252             properties[index] = property;
253 
254             PointerCoords coordinate = new PointerCoords();
255             coordinate.x = location.x;
256             coordinate.y = location.y;
257             coordinate.pressure = 1.0f;
258             coordinates[index] = coordinate;
259 
260             index++;
261         }
262 
263         return new Pair<MotionEvent.PointerProperties[], MotionEvent.PointerCoords[]>(
264                 properties, coordinates);
265     }
266 
267     /**
268      * Resets the time references for a new sequence.
269      */
resetTime()270     private void resetTime() {
271         mInitialTime = 0L;
272         mLastEventTime = -1L;
273         mTime = 0L;
274     }
275 }
276