/* * Copyright (C) 2017 The Android Open Source Project * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package com.android.launcher3.testcomponent; import android.graphics.Point; import android.util.Pair; import android.view.InputDevice; import android.view.MotionEvent; import android.view.MotionEvent.PointerCoords; import android.view.MotionEvent.PointerProperties; import java.util.HashMap; import java.util.Map; /** * Utility class to generate MotionEvent event sequences for testing touch gesture detectors. */ public class TouchEventGenerator { /** * Amount of time between two generated events. */ private static final long TIME_INCREMENT_MS = 20L; /** * Id of the fake device generating the events. */ private static final int DEVICE_ID = 2104; /** * The fingers currently present on the emulated touch screen. */ private Map mFingers; /** * Initial event time for the current sequence. */ private long mInitialTime; /** * Time of the last generated event. */ private long mLastEventTime; /** * Time of the next event. */ private long mTime; /** * Receives the generated events. */ public interface Listener { /** * Called when an event was generated. */ void onTouchEvent(MotionEvent event); } private final Listener mListener; public TouchEventGenerator(Listener listener) { mListener = listener; mFingers = new HashMap(); } /** * Adds a finger on the touchscreen. */ public TouchEventGenerator put(int id, int x, int y, long ms) { checkFingerExistence(id, false); boolean isInitialDown = mFingers.isEmpty(); mFingers.put(id, new Point(x, y)); int action; if (isInitialDown) { action = MotionEvent.ACTION_DOWN; } else { action = MotionEvent.ACTION_POINTER_DOWN; // Set the id of the changed pointer. action |= id << MotionEvent.ACTION_POINTER_INDEX_SHIFT; } generateEvent(action, ms); return this; } /** * Adds a finger on the touchscreen after advancing default time interval. */ public TouchEventGenerator put(int id, int x, int y) { return put(id, x, y, TIME_INCREMENT_MS); } /** * Adjusts the position of a finger for an upcoming move event. * * @see #move(long ms) */ public TouchEventGenerator position(int id, int x, int y) { checkFingerExistence(id, true); mFingers.get(id).set(x, y); return this; } /** * Commits the finger position changes of {@link #position(int, int, int)} by generating a move * event. * * @see #position(int, int, int) */ public TouchEventGenerator move(long ms) { generateEvent(MotionEvent.ACTION_MOVE, ms); return this; } /** * Commits the finger position changes of {@link #position(int, int, int)} by generating a move * event after advancing the default time interval. * * @see #position(int, int, int) */ public TouchEventGenerator move() { return move(TIME_INCREMENT_MS); } /** * Moves a single finger on the touchscreen. */ public TouchEventGenerator move(int id, int x, int y, long ms) { return position(id, x, y).move(ms); } /** * Moves a single finger on the touchscreen after advancing default time interval. */ public TouchEventGenerator move(int id, int x, int y) { return move(id, x, y, TIME_INCREMENT_MS); } /** * Removes an existing finger from the touchscreen. */ public TouchEventGenerator lift(int id, long ms) { checkFingerExistence(id, true); boolean isFinalUp = mFingers.size() == 1; int action; if (isFinalUp) { action = MotionEvent.ACTION_UP; } else { action = MotionEvent.ACTION_POINTER_UP; // Set the id of the changed pointer. action |= id << MotionEvent.ACTION_POINTER_INDEX_SHIFT; } generateEvent(action, ms); mFingers.remove(id); return this; } /** * Removes a finger from the touchscreen. */ public TouchEventGenerator lift(int id, int x, int y, long ms) { checkFingerExistence(id, true); mFingers.get(id).set(x, y); return lift(id, ms); } /** * Removes an existing finger from the touchscreen after advancing default time interval. */ public TouchEventGenerator lift(int id) { return lift(id, TIME_INCREMENT_MS); } /** * Cancels an ongoing sequence. */ public TouchEventGenerator cancel(long ms) { generateEvent(MotionEvent.ACTION_CANCEL, ms); mFingers.clear(); return this; } /** * Cancels an ongoing sequence. */ public TouchEventGenerator cancel() { return cancel(TIME_INCREMENT_MS); } private void checkFingerExistence(int id, boolean shouldExist) { if (shouldExist != mFingers.containsKey(id)) { throw new IllegalArgumentException( shouldExist ? "Finger does not exist" : "Finger already exists"); } } private void generateEvent(int action, long ms) { mTime = mLastEventTime + ms; Pair state = getFingerState(); MotionEvent event = MotionEvent.obtain( mInitialTime, mTime, action, state.first.length, state.first, state.second, 0 /* metaState */, 0 /* buttonState */, 1.0f /* xPrecision */, 1.0f /* yPrecision */, DEVICE_ID, 0 /* edgeFlags */, InputDevice.SOURCE_TOUCHSCREEN, 0 /* flags */); mListener.onTouchEvent(event); if (action == MotionEvent.ACTION_UP) { resetTime(); } event.recycle(); mLastEventTime = mTime; } /** * Returns the description of the fingers' state expected by MotionEvent. */ private Pair getFingerState() { int nFingers = mFingers.size(); PointerProperties[] properties = new PointerProperties[nFingers]; PointerCoords[] coordinates = new PointerCoords[nFingers]; int index = 0; for (Map.Entry entry : mFingers.entrySet()) { int id = entry.getKey(); Point location = entry.getValue(); PointerProperties property = new PointerProperties(); property.id = id; property.toolType = MotionEvent.TOOL_TYPE_FINGER; properties[index] = property; PointerCoords coordinate = new PointerCoords(); coordinate.x = location.x; coordinate.y = location.y; coordinate.pressure = 1.0f; coordinates[index] = coordinate; index++; } return new Pair( properties, coordinates); } /** * Resets the time references for a new sequence. */ private void resetTime() { mInitialTime = 0L; mLastEventTime = -1L; mTime = 0L; } }