mobile/android/base/tests/MotionEventReplayer.java

Wed, 31 Dec 2014 07:22:50 +0100

author
Michael Schloh von Bennewitz <michael@schloh.com>
date
Wed, 31 Dec 2014 07:22:50 +0100
branch
TOR_BUG_3246
changeset 4
fc2d59ddac77
permissions
-rw-r--r--

Correct previous dual key logic pending first delivery installment.

     1 package org.mozilla.gecko.tests;
     3 import java.io.BufferedReader;
     4 import java.io.IOException;
     5 import java.io.InputStream;
     6 import java.io.InputStreamReader;
     7 import java.lang.reflect.InvocationTargetException;
     8 import java.lang.reflect.Method;
     9 import java.util.HashMap;
    10 import java.util.Map;
    11 import java.util.StringTokenizer;
    12 import java.util.regex.Matcher;
    13 import java.util.regex.Pattern;
    15 import android.app.Instrumentation;
    16 import android.os.Build;
    17 import android.os.SystemClock;
    18 import android.util.Log;
    19 import android.view.MotionEvent;
    21 class MotionEventReplayer {
    22     private static final String LOGTAG = "RobocopMotionEventReplayer";
    24     // the inner dimensions of the window on which the motion event capture was taken from
    25     private static final int CAPTURE_WINDOW_WIDTH = 720;
    26     private static final int CAPTURE_WINDOW_HEIGHT = 1038;
    28     private final Instrumentation mInstrumentation;
    29     private final int mSurfaceOffsetX;
    30     private final int mSurfaceOffsetY;
    31     private final int mSurfaceWidth;
    32     private final int mSurfaceHeight;
    33     private final Map<String, Integer> mActionTypes;
    34     private Method mObtainNanoMethod;
    36     public MotionEventReplayer(Instrumentation inst, int surfaceOffsetX, int surfaceOffsetY, int surfaceWidth, int surfaceHeight) {
    37         mInstrumentation = inst;
    38         mSurfaceOffsetX = surfaceOffsetX;
    39         mSurfaceOffsetY = surfaceOffsetY;
    40         mSurfaceWidth = surfaceWidth;
    41         mSurfaceHeight = surfaceHeight;
    42         Log.i(LOGTAG, "Initialized using offset (" + mSurfaceOffsetX + "," + mSurfaceOffsetY + ")");
    44         mActionTypes = new HashMap<String, Integer>();
    45         mActionTypes.put("ACTION_CANCEL", MotionEvent.ACTION_CANCEL);
    46         mActionTypes.put("ACTION_DOWN", MotionEvent.ACTION_DOWN);
    47         mActionTypes.put("ACTION_MOVE", MotionEvent.ACTION_MOVE);
    48         mActionTypes.put("ACTION_POINTER_DOWN", MotionEvent.ACTION_POINTER_DOWN);
    49         mActionTypes.put("ACTION_POINTER_UP", MotionEvent.ACTION_POINTER_UP);
    50         mActionTypes.put("ACTION_UP", MotionEvent.ACTION_UP);
    51     }
    53     private int parseAction(String action) {
    54         int index = 0;
    56         // ACTION_POINTER_DOWN and ACTION_POINTER_UP might be followed by
    57         // pointer index in parentheses, like ACTION_POINTER_UP(1)
    58         int beginParen = action.indexOf("(");
    59         if (beginParen >= 0) {
    60             int endParen = action.indexOf(")", beginParen + 1);
    61             index = Integer.parseInt(action.substring(beginParen + 1, endParen));
    62             action = action.substring(0, beginParen);
    63         }
    65         return mActionTypes.get(action) | (index << MotionEvent.ACTION_POINTER_INDEX_SHIFT);
    66     }
    68     private int parseInt(String value) {
    69         if (value == null) {
    70             return 0;
    71         }
    72         if (value.startsWith("0x")) {
    73             return Integer.parseInt(value.substring(2), 16);
    74         }
    75         return Integer.parseInt(value);
    76     }
    78     private float scaleX(float value) {
    79         return value * (float)mSurfaceWidth / (float)CAPTURE_WINDOW_WIDTH;
    80     }
    82     private float scaleY(float value) {
    83         return value * (float)mSurfaceHeight / (float)CAPTURE_WINDOW_HEIGHT;
    84     }
    86     public void replayEvents(InputStream eventDescriptions)
    87             throws IOException, IllegalAccessException, InvocationTargetException, NoSuchMethodException
    88     {
    89         // As an example, a line in the input stream might look like:
    90         //
    91         // MotionEvent { action=ACTION_DOWN, id[0]=0, x[0]=424.41055, y[0]=825.2412,
    92         //      toolType[0]=TOOL_TYPE_FINGER, buttonState=0, metaState=0, flags=0x0,
    93         //      edgeFlags=0x0, pointerCount=1, historySize=0, eventTime=21972329,
    94         //      downTime=21972329, deviceId=6, source=0x1002 }
    95         //
    96         // These can be generated by printing out event.toString() in LayerView's
    97         // onTouchEvent function on a phone running Ice Cream Sandwich. Different
    98         // Android versions have different serializations of the motion event, and this
    99         // code could probably be modified to parse other serializations if needed.
   100         Pattern p = Pattern.compile("MotionEvent \\{ (.*?) \\}");
   101         Map<String, String> eventProperties = new HashMap<String, String>();
   103         boolean firstEvent = true;
   104         long timeDelta = 0L;
   105         long lastEventTime = 0L;
   107         BufferedReader br = new BufferedReader(new InputStreamReader(eventDescriptions));
   108         try {
   109             for (String eventStr = br.readLine(); eventStr != null; eventStr = br.readLine()) {
   110                 Matcher m = p.matcher(eventStr);
   111                 if (! m.find()) {
   112                     // this line doesn't have any MotionEvent data, skip it
   113                     continue;
   114                 }
   116                 // extract the key-value pairs from the description and store them
   117                 // in the eventProperties table
   118                 StringTokenizer keyValues = new StringTokenizer(m.group(1), ",");
   119                 while (keyValues.hasMoreTokens()) {
   120                     String keyValue = keyValues.nextToken();
   121                     String key = keyValue.substring(0, keyValue.indexOf('=')).trim();
   122                     String value = keyValue.substring(keyValue.indexOf('=') + 1).trim();
   123                     eventProperties.put(key, value);
   124                 }
   126                 // set up the values we need to build the MotionEvent
   127                 long downTime = Long.parseLong(eventProperties.get("downTime"));
   128                 long eventTime = Long.parseLong(eventProperties.get("eventTime"));
   129                 int action = parseAction(eventProperties.get("action"));
   130                 float pressure = 1.0f;
   131                 float size = 1.0f;
   132                 int metaState = parseInt(eventProperties.get("metaState"));
   133                 float xPrecision = 1.0f;
   134                 float yPrecision = 1.0f;
   135                 int deviceId = 0;
   136                 int edgeFlags = parseInt(eventProperties.get("edgeFlags"));
   137                 int source = parseInt(eventProperties.get("source"));
   138                 int flags = parseInt(eventProperties.get("flags"));
   140                 int pointerCount = parseInt(eventProperties.get("pointerCount"));
   141                 int[] pointerIds = new int[pointerCount];
   142                 Object pointerData;
   143                 if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.GINGERBREAD) {
   144                     MotionEvent.PointerCoords[] pointerCoords = new MotionEvent.PointerCoords[pointerCount];
   145                     for (int i = 0; i < pointerCount; i++) {
   146                         pointerIds[i] = Integer.parseInt(eventProperties.get("id[" + i + "]"));
   147                         pointerCoords[i] = new MotionEvent.PointerCoords();
   148                         pointerCoords[i].x = mSurfaceOffsetX + scaleX(Float.parseFloat(eventProperties.get("x[" + i + "]")));
   149                         pointerCoords[i].y = mSurfaceOffsetY + scaleY(Float.parseFloat(eventProperties.get("y[" + i + "]")));
   150                     }
   151                     pointerData = pointerCoords;
   152                 } else {
   153                     // pre-gingerbread we have to use a hidden API to create the motion event, and we have
   154                     // to create a flattened list of floats rather than an array of PointerCoords
   155                     final int NUM_SAMPLE_DATA = 4; // MotionEvent.NUM_SAMPLE_DATA
   156                     final int SAMPLE_X = 0; // MotionEvent.SAMPLE_X
   157                     final int SAMPLE_Y = 1; // MotionEvent.SAMPLE_Y
   158                     float[] sampleData = new float[pointerCount * NUM_SAMPLE_DATA];
   159                     for (int i = 0; i < pointerCount; i++) {
   160                         pointerIds[i] = Integer.parseInt(eventProperties.get("id[" + i + "]"));
   161                         sampleData[(i * NUM_SAMPLE_DATA) + SAMPLE_X] =
   162                                 mSurfaceOffsetX + scaleX(Float.parseFloat(eventProperties.get("x[" + i + "]")));
   163                         sampleData[(i * NUM_SAMPLE_DATA) + SAMPLE_Y] =
   164                                 mSurfaceOffsetY + scaleY(Float.parseFloat(eventProperties.get("y[" + i + "]")));
   165                     }
   166                     pointerData = sampleData;
   167                 }
   169                 // we want to adjust the timestamps on all the generated events so that they line up with
   170                 // the time that this function is executing on-device.
   171                 long now = SystemClock.uptimeMillis();
   172                 if (firstEvent) {
   173                     timeDelta = now - eventTime;
   174                     firstEvent = false;
   175                 }
   176                 downTime += timeDelta;
   177                 eventTime += timeDelta;
   179                 // we also generate the events in "real-time" (i.e. have delays between events that
   180                 // correspond to the delays in the event timestamps).
   181                 if (now < eventTime) {
   182                     try {
   183                         Thread.sleep(eventTime - now);
   184                     } catch (InterruptedException ie) {
   185                     }
   186                 }
   188                 // and finally we dispatch the event
   189                 MotionEvent event;
   190                 if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.GINGERBREAD) {
   191                     event = MotionEvent.obtain(downTime, eventTime, action, pointerCount,
   192                             pointerIds, (MotionEvent.PointerCoords[])pointerData, metaState,
   193                             xPrecision, yPrecision, deviceId, edgeFlags, source, flags);
   194                 } else {
   195                     // pre-gingerbread we have to use a hidden API to accomplish this
   196                     if (mObtainNanoMethod == null) {
   197                         mObtainNanoMethod = MotionEvent.class.getMethod("obtainNano", long.class,
   198                             long.class, long.class, int.class, int.class, pointerIds.getClass(),
   199                             pointerData.getClass(), int.class, float.class, float.class,
   200                             int.class, int.class);
   201                     }
   202                     event = (MotionEvent)mObtainNanoMethod.invoke(null, downTime, eventTime,
   203                             eventTime * 1000000, action, pointerCount, pointerIds, (float[])pointerData,
   204                             metaState, xPrecision, yPrecision, deviceId, edgeFlags);
   205                 }
   206                 try {
   207                     Log.v(LOGTAG, "Injecting " + event.toString());
   208                     mInstrumentation.sendPointerSync(event);
   209                 } finally {
   210                     event.recycle();
   211                     event = null;
   212                 }
   214                 eventProperties.clear();
   215             }
   216         } finally {
   217             br.close();
   218         }
   219     }
   220 }

mercurial