Home | History | Annotate | Download | only in testcomponent
      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