mirror of
https://gitlab.winehq.org/wine/wine-gecko.git
synced 2024-09-13 09:24:08 -07:00
218 lines
10 KiB
Java
218 lines
10 KiB
Java
#filter substitution
|
|
package @ANDROID_PACKAGE_NAME@.tests;
|
|
|
|
import @ANDROID_PACKAGE_NAME@.*;
|
|
|
|
import java.io.BufferedReader;
|
|
import java.io.InputStream;
|
|
import java.io.InputStreamReader;
|
|
import java.io.IOException;
|
|
import java.lang.reflect.InvocationTargetException;
|
|
import java.lang.reflect.Method;
|
|
import java.util.Map;
|
|
import java.util.HashMap;
|
|
import java.util.StringTokenizer;
|
|
import java.util.regex.Matcher;
|
|
import java.util.regex.Pattern;
|
|
import android.app.Instrumentation;
|
|
import android.os.Build;
|
|
import android.os.SystemClock;
|
|
import android.util.Log;
|
|
import android.view.MotionEvent;
|
|
|
|
class MotionEventReplayer {
|
|
private static final String LOGTAG = "RobocopMotionEventReplayer";
|
|
|
|
// the inner dimensions of the window on which the motion event capture was taken from
|
|
private static final int CAPTURE_WINDOW_WIDTH = 720;
|
|
private static final int CAPTURE_WINDOW_HEIGHT = 1038;
|
|
|
|
private final Instrumentation mInstrumentation;
|
|
private final int mSurfaceOffsetX;
|
|
private final int mSurfaceOffsetY;
|
|
private final int mSurfaceWidth;
|
|
private final int mSurfaceHeight;
|
|
private final Map<String, Integer> mActionTypes;
|
|
private Method mObtainNanoMethod;
|
|
|
|
public MotionEventReplayer(Instrumentation inst, int surfaceOffsetX, int surfaceOffsetY, int surfaceWidth, int surfaceHeight) {
|
|
mInstrumentation = inst;
|
|
mSurfaceOffsetX = surfaceOffsetX;
|
|
mSurfaceOffsetY = surfaceOffsetY;
|
|
mSurfaceWidth = surfaceWidth;
|
|
mSurfaceHeight = surfaceHeight;
|
|
Log.i(LOGTAG, "Initialized using offset (" + mSurfaceOffsetX + "," + mSurfaceOffsetY + ")");
|
|
|
|
mActionTypes = new HashMap<String, Integer>();
|
|
mActionTypes.put("ACTION_CANCEL", MotionEvent.ACTION_CANCEL);
|
|
mActionTypes.put("ACTION_DOWN", MotionEvent.ACTION_DOWN);
|
|
mActionTypes.put("ACTION_MOVE", MotionEvent.ACTION_MOVE);
|
|
mActionTypes.put("ACTION_POINTER_DOWN", MotionEvent.ACTION_POINTER_DOWN);
|
|
mActionTypes.put("ACTION_POINTER_UP", MotionEvent.ACTION_POINTER_UP);
|
|
mActionTypes.put("ACTION_UP", MotionEvent.ACTION_UP);
|
|
}
|
|
|
|
private int parseAction(String action) {
|
|
int index = 0;
|
|
|
|
// ACTION_POINTER_DOWN and ACTION_POINTER_UP might be followed by
|
|
// pointer index in parentheses, like ACTION_POINTER_UP(1)
|
|
int beginParen = action.indexOf("(");
|
|
if (beginParen >= 0) {
|
|
int endParen = action.indexOf(")", beginParen + 1);
|
|
index = Integer.parseInt(action.substring(beginParen + 1, endParen));
|
|
action = action.substring(0, beginParen);
|
|
}
|
|
|
|
return mActionTypes.get(action) | (index << MotionEvent.ACTION_POINTER_INDEX_SHIFT);
|
|
}
|
|
|
|
private int parseInt(String value) {
|
|
if (value == null) {
|
|
return 0;
|
|
}
|
|
if (value.startsWith("0x")) {
|
|
return Integer.parseInt(value.substring(2), 16);
|
|
}
|
|
return Integer.parseInt(value);
|
|
}
|
|
|
|
private float scaleX(float value) {
|
|
return value * (float)mSurfaceWidth / (float)CAPTURE_WINDOW_WIDTH;
|
|
}
|
|
|
|
private float scaleY(float value) {
|
|
return value * (float)mSurfaceHeight / (float)CAPTURE_WINDOW_HEIGHT;
|
|
}
|
|
|
|
public void replayEvents(InputStream eventDescriptions)
|
|
throws IOException, IllegalAccessException, InvocationTargetException, NoSuchMethodException
|
|
{
|
|
// As an example, a line in the input stream might look like:
|
|
//
|
|
// MotionEvent { action=ACTION_DOWN, id[0]=0, x[0]=424.41055, y[0]=825.2412,
|
|
// toolType[0]=TOOL_TYPE_FINGER, buttonState=0, metaState=0, flags=0x0,
|
|
// edgeFlags=0x0, pointerCount=1, historySize=0, eventTime=21972329,
|
|
// downTime=21972329, deviceId=6, source=0x1002 }
|
|
//
|
|
// These can be generated by printing out event.toString() in LayerView's
|
|
// onTouchEvent function on a phone running Ice Cream Sandwich. Different
|
|
// Android versions have different serializations of the motion event, and this
|
|
// code could probably be modified to parse other serializations if needed.
|
|
Pattern p = Pattern.compile("MotionEvent \\{ (.*?) \\}");
|
|
Map<String, String> eventProperties = new HashMap<String, String>();
|
|
|
|
boolean firstEvent = true;
|
|
long timeDelta = 0L;
|
|
long lastEventTime = 0L;
|
|
|
|
BufferedReader br = new BufferedReader(new InputStreamReader(eventDescriptions));
|
|
try {
|
|
for (String eventStr = br.readLine(); eventStr != null; eventStr = br.readLine()) {
|
|
Matcher m = p.matcher(eventStr);
|
|
if (! m.find()) {
|
|
// this line doesn't have any MotionEvent data, skip it
|
|
continue;
|
|
}
|
|
|
|
// extract the key-value pairs from the description and store them
|
|
// in the eventProperties table
|
|
StringTokenizer keyValues = new StringTokenizer(m.group(1), ",");
|
|
while (keyValues.hasMoreTokens()) {
|
|
String keyValue = keyValues.nextToken();
|
|
String key = keyValue.substring(0, keyValue.indexOf('=')).trim();
|
|
String value = keyValue.substring(keyValue.indexOf('=') + 1).trim();
|
|
eventProperties.put(key, value);
|
|
}
|
|
|
|
// set up the values we need to build the MotionEvent
|
|
long downTime = Long.parseLong(eventProperties.get("downTime"));
|
|
long eventTime = Long.parseLong(eventProperties.get("eventTime"));
|
|
int action = parseAction(eventProperties.get("action"));
|
|
float pressure = 1.0f;
|
|
float size = 1.0f;
|
|
int metaState = parseInt(eventProperties.get("metaState"));
|
|
float xPrecision = 1.0f;
|
|
float yPrecision = 1.0f;
|
|
int deviceId = 0;
|
|
int edgeFlags = parseInt(eventProperties.get("edgeFlags"));
|
|
int source = parseInt(eventProperties.get("source"));
|
|
int flags = parseInt(eventProperties.get("flags"));
|
|
|
|
int pointerCount = parseInt(eventProperties.get("pointerCount"));
|
|
int[] pointerIds = new int[pointerCount];
|
|
Object pointerData;
|
|
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.GINGERBREAD) {
|
|
MotionEvent.PointerCoords[] pointerCoords = new MotionEvent.PointerCoords[pointerCount];
|
|
for (int i = 0; i < pointerCount; i++) {
|
|
pointerIds[i] = Integer.parseInt(eventProperties.get("id[" + i + "]"));
|
|
pointerCoords[i] = new MotionEvent.PointerCoords();
|
|
pointerCoords[i].x = mSurfaceOffsetX + scaleX(Float.parseFloat(eventProperties.get("x[" + i + "]")));
|
|
pointerCoords[i].y = mSurfaceOffsetY + scaleY(Float.parseFloat(eventProperties.get("y[" + i + "]")));
|
|
}
|
|
pointerData = pointerCoords;
|
|
} else {
|
|
// pre-gingerbread we have to use a hidden API to create the motion event, and we have
|
|
// to create a flattened list of floats rather than an array of PointerCoords
|
|
final int NUM_SAMPLE_DATA = 4; // MotionEvent.NUM_SAMPLE_DATA
|
|
final int SAMPLE_X = 0; // MotionEvent.SAMPLE_X
|
|
final int SAMPLE_Y = 1; // MotionEvent.SAMPLE_Y
|
|
float[] sampleData = new float[pointerCount * NUM_SAMPLE_DATA];
|
|
for (int i = 0; i < pointerCount; i++) {
|
|
pointerIds[i] = Integer.parseInt(eventProperties.get("id[" + i + "]"));
|
|
sampleData[(i * NUM_SAMPLE_DATA) + SAMPLE_X] =
|
|
mSurfaceOffsetX + scaleX(Float.parseFloat(eventProperties.get("x[" + i + "]")));
|
|
sampleData[(i * NUM_SAMPLE_DATA) + SAMPLE_Y] =
|
|
mSurfaceOffsetY + scaleY(Float.parseFloat(eventProperties.get("y[" + i + "]")));
|
|
}
|
|
pointerData = sampleData;
|
|
}
|
|
|
|
// we want to adjust the timestamps on all the generated events so that they line up with
|
|
// the time that this function is executing on-device.
|
|
long now = SystemClock.uptimeMillis();
|
|
if (firstEvent) {
|
|
timeDelta = now - eventTime;
|
|
firstEvent = false;
|
|
}
|
|
downTime += timeDelta;
|
|
eventTime += timeDelta;
|
|
|
|
// we also generate the events in "real-time" (i.e. have delays between events that
|
|
// correspond to the delays in the event timestamps).
|
|
if (now < eventTime) {
|
|
try {
|
|
Thread.sleep(eventTime - now);
|
|
} catch (InterruptedException ie) {
|
|
}
|
|
}
|
|
|
|
// and finally we dispatch the event
|
|
MotionEvent event;
|
|
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.GINGERBREAD) {
|
|
event = MotionEvent.obtain(downTime, eventTime, action, pointerCount,
|
|
pointerIds, (MotionEvent.PointerCoords[])pointerData, metaState,
|
|
xPrecision, yPrecision, deviceId, edgeFlags, source, flags);
|
|
} else {
|
|
// pre-gingerbread we have to use a hidden API to accomplish this
|
|
if (mObtainNanoMethod == null) {
|
|
mObtainNanoMethod = MotionEvent.class.getMethod("obtainNano", long.class,
|
|
long.class, long.class, int.class, int.class, pointerIds.getClass(),
|
|
pointerData.getClass(), int.class, float.class, float.class,
|
|
int.class, int.class);
|
|
}
|
|
event = (MotionEvent)mObtainNanoMethod.invoke(null, downTime, eventTime,
|
|
eventTime * 1000000, action, pointerCount, pointerIds, (float[])pointerData,
|
|
metaState, xPrecision, yPrecision, deviceId, edgeFlags);
|
|
}
|
|
Log.v(LOGTAG, "Injecting " + event.toString());
|
|
mInstrumentation.sendPointerSync(event);
|
|
|
|
eventProperties.clear();
|
|
}
|
|
} finally {
|
|
br.close();
|
|
}
|
|
}
|
|
}
|