Merge pull request #2335 from imurchie/isaac-actions-fix

Allow android multi touch actions without an element
This commit is contained in:
Jonathan Lipps
2014-04-14 10:51:43 -07:00
5 changed files with 144 additions and 72 deletions
+31 -14
View File
@@ -1004,18 +1004,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();
}
};
@@ -1156,6 +1164,7 @@ androidController.parseTouch = function (gestures, cb) {
x: (gesture.options.x || 0),
y: (gesture.options.y || 0)
};
touchStateObject = {
timeOffset: 0.005,
touch: tapPoint
@@ -1221,11 +1230,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;
}
}
+15
View File
@@ -266,6 +266,21 @@ exports.performTouch = function (req, res) {
gestures = gestures.actions;
}
// press-wait-moveTo-release is `swipe`, so use native method
if (gestures.length === 4) {
if ((gestures[0].action === 'press') && (gestures[1].action === 'wait') && (gestures[2].action === 'moveTo') && (gestures[3].action === 'release')) {
var body = {
startX: gestures[0].options.x,
startY: gestures[0].options.y,
endX: gestures[2].options.x,
endY: gestures[2].options.y,
duration: gestures[1].options.ms
};
req.body = body;
return exports.mobileSwipe(req, res);
}
}
req.device.performTouch(gestures, getResponseHandler(req, res));
};