Allow android multi touch actions without an element

This commit is contained in:
Isaac Murchie
2014-04-12 11:37:37 -04:00
committed by Jonathan Lipps
parent 799cb256a4
commit ec28b95468
4 changed files with 129 additions and 72 deletions
+31 -14
View File
@@ -853,18 +853,26 @@ androidController.tap = function (elementId, x, y, count, cb) {
this.proxy(opts, loop);
}.bind(this);
if (x !== 0 || y !== 0) {
this.proxy(["element:getLocation", {elementId: elementId}], function (err, res) {
if (err) return cb(err);
var value = res.value;
x += parseInt(value.x);
y += parseInt(value.y);
if (elementId) {
// we are either tapping on the default location of the element
// or an offset from the top left corner
if (x !== 0 || y !== 0) {
this.proxy(["element:getLocation", {elementId: elementId}], function (err, res) {
if (err) return cb(err);
var value = res.value;
x += parseInt(value.x);
y += parseInt(value.y);
opts = ["click", {x: x, y: y}];
opts = ["click", {x: x, y: y}];
loop();
}.bind(this));
} else {
opts = ["element:click", {elementId: elementId}];
loop();
}.bind(this));
}
} else {
opts = ["element:click", {elementId: elementId}];
// we have absolute coordinates
opts = ["click", {x: x, y: y}];
loop();
}
};
@@ -1005,6 +1013,7 @@ androidController.parseTouch = function (gestures, cb) {
x: (gesture.options.x || 0),
y: (gesture.options.y || 0)
};
touchStateObject = {
timeOffset: 0.005,
touch: tapPoint
@@ -1070,11 +1079,19 @@ androidController.performMultiAction = function (elementId, actions, cb) {
}.bind(this), function (err) {
if (err) return cb(err);
var opts = {
elementId: elementId,
actions: states
};
return this.proxy(["element:performMultiPointerGesture", opts], cb);
var opts;
if (elementId) {
opts = {
elementId: elementId,
actions: states
};
return this.proxy(["element:performMultiPointerGesture", opts], cb);
} else {
opts = {
actions: states
};
return this.proxy(["performMultiPointerGesture", opts], cb);
}
}.bind(this));
};
@@ -22,42 +22,58 @@ import io.appium.android.bootstrap.exceptions.ElementNotInHashException;
import java.lang.reflect.Method;
public class MultiPointerGesture extends CommandHandler {
public class MultiPointerGesture extends TouchableEvent {
@Override
public AndroidCommandResult execute(final AndroidCommand command)
throws JSONException {
if (!command.isElementCommand()) {
return getErrorResult("A gesturable view is required for this command.");
}
try {
JSONArray actions = (org.json.JSONArray)command.params().get("actions");
PointerCoords[][] pcs = parsePointerCoords(command);
double time = computeLongestTime(actions);
PointerCoords[][] pcs = new PointerCoords[actions.length()][];
for (int i = 0; i < actions.length(); i++) {
JSONArray gestures = actions.getJSONArray(i);
pcs[i] = gesturesToPointerCoords(time, gestures);
}
final AndroidElement el = command.getElement();
if (el.performMultiPointerGesture(pcs)) {
return getSuccessResult("OK");
if (command.isElementCommand()) {
final AndroidElement el = command.getElement();
if (el.performMultiPointerGesture(pcs)) {
return getSuccessResult("OK");
} else {
return getErrorResult("Unable to perform multi pointer gesture");
}
} else {
return getErrorResult("Unable to perform multi pointer gesture");
Object controller = getController();
final Method pmpg = getMethod("performMultiPointerGesture", controller);
Boolean rt = (Boolean)pmpg.invoke(controller, (Object)pcs);
if (rt.booleanValue()) {
return getSuccessResult("OK");
} else {
return getErrorResult("Unable to perform multi pointer gesture");
}
}
} catch (final ElementNotInHashException e) {
return new AndroidCommandResult(WDStatus.NO_SUCH_ELEMENT, e.getMessage());
} catch (final Exception e) {
Logger.debug("Exception: " + e);
e.printStackTrace();
return new AndroidCommandResult(WDStatus.UNKNOWN_ERROR, e.getMessage());
}
}
private PointerCoords[] gesturesToPointerCoords(double maxTime, JSONArray gestures) throws Exception {
private PointerCoords[][] parsePointerCoords(AndroidCommand command)
throws JSONException {
JSONArray actions = (org.json.JSONArray)command.params().get("actions");
double time = computeLongestTime(actions);
PointerCoords[][] pcs = new PointerCoords[actions.length()][];
for (int i = 0; i < actions.length(); i++) {
JSONArray gestures = actions.getJSONArray(i);
pcs[i] = gesturesToPointerCoords(time, gestures);
}
return pcs;
}
private PointerCoords[] gesturesToPointerCoords(double maxTime, JSONArray gestures)
throws JSONException {
// gestures, e.g.:
// [
// {"touch":{"y":529.5,"x":120},"time":0.2},
@@ -23,18 +23,9 @@ import com.android.uiautomator.core.UiObjectNotFoundException;
/**
* This handler is and abstract class that contains all the common code for
* touch event handlers.
*
*
*/
public abstract class TouchEvent extends CommandHandler {
private static Field enableField(final Class<?> clazz, final String field)
throws SecurityException, NoSuchFieldException {
Logger.debug("Updating class \"" + clazz + "\" to enable field \"" + field
+ "\"");
final Field fieldObject = clazz.getDeclaredField(field);
fieldObject.setAccessible(true);
return fieldObject;
}
public abstract class TouchEvent extends TouchableEvent {
protected AndroidElement el;
protected int clickX;
@@ -46,7 +37,7 @@ public abstract class TouchEvent extends CommandHandler {
protected boolean isElement;
/**
*
*
* @param command
* The {@link AndroidCommand}
* @return {@link AndroidCommandResult}
@@ -113,32 +104,6 @@ public abstract class TouchEvent extends CommandHandler {
protected abstract boolean executeTouchEvent()
throws UiObjectNotFoundException;
/*
* getAutomatorBridge is private so we access the bridge via reflection to use
* the touchDown / touchUp / touchMove methods.
*/
protected Object getController() throws IllegalArgumentException,
IllegalAccessException, SecurityException, NoSuchFieldException {
final UiDevice device = UiDevice.getInstance();
final Object bridge = enableField(device.getClass(), "mUiAutomationBridge")
.get(device);
final Object controller = enableField(bridge.getClass().getSuperclass(),
"mInteractionController").get(bridge);
return controller;
}
protected Method getMethod(final String name, final Object controller)
throws NoSuchMethodException, SecurityException {
final Class<?> controllerClass = controller.getClass();
Logger.debug("Finding methods on class: " + controllerClass);
final Method method = controllerClass.getDeclaredMethod(name, int.class,
int.class);
method.setAccessible(true);
return method;
}
/**
* Variables persist across executions. initialize must be called at the start
* of execute.
@@ -0,0 +1,59 @@
package io.appium.android.bootstrap.handler;
import io.appium.android.bootstrap.CommandHandler;
import io.appium.android.bootstrap.Logger;
import android.view.MotionEvent.PointerCoords;
import com.android.uiautomator.core.UiDevice;
import java.lang.reflect.Field;
import java.lang.reflect.Method;
/**
* This handler is and abstract class that contains all the common code for
* touch event handlers.
*
*/
public abstract class TouchableEvent extends CommandHandler {
private static Field enableField(final Class<?> clazz, final String field)
throws SecurityException, NoSuchFieldException {
Logger.debug("Updating class \"" + clazz + "\" to enable field \"" + field
+ "\"");
final Field fieldObject = clazz.getDeclaredField(field);
fieldObject.setAccessible(true);
return fieldObject;
}
/*
* getAutomatorBridge is private so we access the bridge via reflection to use
* the touchDown / touchUp / touchMove methods.
*/
protected Object getController() throws IllegalArgumentException,
IllegalAccessException, SecurityException, NoSuchFieldException {
final UiDevice device = UiDevice.getInstance();
final Object bridge = enableField(device.getClass(), "mUiAutomationBridge")
.get(device);
final Object controller = enableField(bridge.getClass().getSuperclass(),
"mInteractionController").get(bridge);
return controller;
}
protected Method getMethod(final String name, final Object controller)
throws NoSuchMethodException, SecurityException {
final Class<?> controllerClass = controller.getClass();
Logger.debug("Finding methods on class: " + controllerClass);
final Method method;
if (name.equals("performMultiPointerGesture")) {
// multi pointer gestures take a 2d array of coordinates
method = controllerClass.getDeclaredMethod(name, PointerCoords[][].class);
} else {
// all the other touch events send two ints
method = controllerClass.getDeclaredMethod(name, int.class, int.class);
}
method.setAccessible(true);
return method;
}
}