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 */ 71 void onTouchEvent(MotionEvent event); 72 } 73 private final Listener mListener; 74 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 */ 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 */ 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 */ 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 */ 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 */ 134 public TouchEventGenerator move() { 135 return move(TIME_INCREMENT_MS); 136 } 137 138 /** 139 * Moves a single finger on the touchscreen. 140 */ 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 */ 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 */ 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 */ 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 */ 183 public TouchEventGenerator lift(int id) { 184 return lift(id, TIME_INCREMENT_MS); 185 } 186 187 /** 188 * Cancels an ongoing sequence. 189 */ 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 */ 199 public TouchEventGenerator cancel() { 200 return cancel(TIME_INCREMENT_MS); 201 } 202 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 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 */ 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 */ 270 private void resetTime() { 271 mInitialTime = 0L; 272 mLastEventTime = -1L; 273 mTime = 0L; 274 } 275 } 276