Merge pull request #2673 from paymand/long_press_keycode

Renamed keyevent to press_keycode and added long_press_keycode.
This commit is contained in:
Jonathan Lipps
2014-05-28 12:30:04 -07:00
15 changed files with 231 additions and 121 deletions
+10 -1
View File
@@ -24,10 +24,19 @@ var NATIVE_WIN = "NATIVE_APP";
var WEBVIEW_WIN = "WEBVIEW";
var WEBVIEW_BASE = WEBVIEW_WIN + "_";
androidController.keyevent = function (keycode, metastate, cb) {
androidController.pressKeyCode = function (keycode, metastate, cb) {
this.proxy(["pressKeyCode", {keycode: keycode, metastate: metastate}], cb);
};
androidController.longPressKeyCode = function (keycode, metastate, cb) {
this.proxy(["longPressKeyCode", {keycode: keycode, metastate: metastate}], cb);
};
androidController.keyevent = function (keycode, metastate, cb) {
warnDeprecated('function', 'keyevent', 'pressKeyCode');
this.pressKeyCode(keycode, metastate, cb);
};
androidController.defaultContext = function () {
return NATIVE_WIN;
};
@@ -1,23 +1,15 @@
package io.appium.android.bootstrap.handler;
package com.android.uiautomator.common;
import io.appium.android.bootstrap.CommandHandler;
import io.appium.android.bootstrap.Logger;
import android.os.Build;
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 {
import android.os.Build;
import com.android.uiautomator.core.UiDevice;
public class ReflectionUtils {
private static Field enableField(final Class<?> clazz, final String field)
throws SecurityException, NoSuchFieldException {
Logger.debug("Updating class \"" + clazz + "\" to enable field \"" + field
@@ -27,40 +19,39 @@ public abstract class TouchableEvent extends CommandHandler {
return fieldObject;
}
/*
* getAutomatorBridge is private so we access the bridge via reflection to use
* the touchDown / touchUp / touchMove methods.
*/
protected Object getController() throws IllegalArgumentException,
private Object controller = null;
public ReflectionUtils() throws IllegalArgumentException,
IllegalAccessException, SecurityException, NoSuchFieldException {
final UiDevice device = UiDevice.getInstance();
final Object bridge = enableField(device.getClass(), "mUiAutomationBridge")
.get(device);
Object controller = null;
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.JELLY_BEAN_MR2) {
controller = enableField(bridge.getClass().getSuperclass(),
"mInteractionController").get(bridge);
} else {
controller = enableField(bridge.getClass(),
"mInteractionController").get(bridge);
controller = enableField(bridge.getClass(), "mInteractionController")
.get(bridge);
}
return controller;
}
protected Method getMethod(final String name, final Object controller)
/*
* getAutomatorBridge is private so we access the bridge via reflection to use
* the touchDown / touchUp / touchMove methods.
*/
public Object getController() throws IllegalArgumentException,
IllegalAccessException, SecurityException, NoSuchFieldException {
return controller;
}
public Method getMethod(final String name, final Class<?>... parameterTypes)
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 = controllerClass.getDeclaredMethod(name, parameterTypes);
method.setAccessible(true);
return method;
}
@@ -14,6 +14,7 @@ import io.appium.android.bootstrap.handler.GetLocation;
import io.appium.android.bootstrap.handler.GetName;
import io.appium.android.bootstrap.handler.GetSize;
import io.appium.android.bootstrap.handler.GetText;
import io.appium.android.bootstrap.handler.LongPressKeyCode;
import io.appium.android.bootstrap.handler.MultiPointerGesture;
import io.appium.android.bootstrap.handler.Orientation;
import io.appium.android.bootstrap.handler.Pinch;
@@ -70,6 +71,7 @@ class AndroidCommandExecutor {
map.put("pressBack", new PressBack());
map.put("dumpWindowHierarchy", new DumpWindowHierarchy());
map.put("pressKeyCode", new PressKeyCode());
map.put("longPressKeyCode", new LongPressKeyCode());
map.put("takeScreenshot", new TakeScreenshot());
map.put("updateStrings", new UpdateStrings());
map.put("getDataDir", new GetDataDir());
@@ -0,0 +1,72 @@
package io.appium.android.bootstrap.handler;
import io.appium.android.bootstrap.AndroidCommand;
import io.appium.android.bootstrap.AndroidCommandResult;
import io.appium.android.bootstrap.CommandHandler;
import java.lang.reflect.Method;
import java.util.Hashtable;
import org.json.JSONException;
import org.json.JSONObject;
import android.os.SystemClock;
import android.view.InputDevice;
import android.view.InputEvent;
import android.view.KeyCharacterMap;
import android.view.KeyEvent;
import com.android.uiautomator.common.ReflectionUtils;
/**
* This handler is used to LongPressKeyCode.
*
*/
public class LongPressKeyCode extends CommandHandler {
public Integer keyCode;
public Integer metaState;
/*
* @param command The {@link AndroidCommand} used for this handler.
*
* @return {@link AndroidCommandResult}
*
* @throws JSONException
*
* @see io.appium.android.bootstrap.CommandHandler#execute(io.appium.android.
* bootstrap.AndroidCommand)
*/
@Override
public AndroidCommandResult execute(final AndroidCommand command)
throws JSONException {
try {
final ReflectionUtils utils = new ReflectionUtils();
final Method injectEventSync = utils.getMethod("injectEventSync",
InputEvent.class);
final Hashtable<String, Object> params = command.params();
keyCode = (Integer) params.get("keycode");
metaState = params.get("metastate") != JSONObject.NULL ? (Integer) params
.get("metastate") : 0;
final long eventTime = SystemClock.uptimeMillis();
// Send an initial down event
final KeyEvent downEvent = new KeyEvent(eventTime, eventTime,
KeyEvent.ACTION_DOWN, keyCode, 0, metaState,
KeyCharacterMap.VIRTUAL_KEYBOARD, 0, 0, InputDevice.SOURCE_KEYBOARD);
if ((Boolean) injectEventSync.invoke(utils.getController(), downEvent)) {
// Send a repeat event. This will cause the FLAG_LONG_PRESS to be set.
final KeyEvent repeatEvent = KeyEvent.changeTimeRepeat(downEvent,
eventTime, 1);
injectEventSync.invoke(utils.getController(), repeatEvent);
// Finally, send the up event
final KeyEvent upEvent = new KeyEvent(eventTime, eventTime,
KeyEvent.ACTION_UP, keyCode, 0, metaState,
KeyCharacterMap.VIRTUAL_KEYBOARD, 0, 0, InputDevice.SOURCE_KEYBOARD);
injectEventSync.invoke(utils.getController(), upEvent);
}
return getSuccessResult(true);
} catch (final Exception e) {
return getErrorResult(e.getMessage());
}
}
}
@@ -1,18 +1,5 @@
package io.appium.android.bootstrap.handler;
import android.os.Build;
import android.view.MotionEvent.PointerCoords;
import com.android.uiautomator.core.UiObject;
import com.android.uiautomator.core.UiObjectNotFoundException;
import com.android.uiautomator.core.UiScrollable;
import com.android.uiautomator.core.UiSelector;
import org.json.JSONException;
import org.json.JSONArray;
import org.json.JSONObject;
import io.appium.android.bootstrap.AndroidCommand;
import io.appium.android.bootstrap.AndroidCommandResult;
import io.appium.android.bootstrap.AndroidElement;
@@ -23,14 +10,53 @@ import io.appium.android.bootstrap.exceptions.ElementNotInHashException;
import java.lang.reflect.Method;
import org.json.JSONArray;
import org.json.JSONException;
import org.json.JSONObject;
public class MultiPointerGesture extends TouchableEvent {
import android.os.Build;
import android.view.MotionEvent.PointerCoords;
import com.android.uiautomator.common.ReflectionUtils;
public class MultiPointerGesture extends CommandHandler {
private double computeLongestTime(final JSONArray actions)
throws JSONException {
double max = 0.0;
for (int i = 0; i < actions.length(); i++) {
final JSONArray gestures = actions.getJSONArray(i);
final double endTime = gestures.getJSONObject(gestures.length() - 1)
.getDouble("time");
if (endTime > max) {
max = endTime;
}
}
return max;
}
private PointerCoords createPointerCoords(final JSONObject obj)
throws JSONException {
final JSONObject o = obj.getJSONObject("touch");
final int x = o.getInt("x");
final int y = o.getInt("y");
final PointerCoords p = new PointerCoords();
p.size = 1;
p.pressure = 1;
p.x = x;
p.y = y;
return p;
}
@Override
public AndroidCommandResult execute(final AndroidCommand command)
throws JSONException {
try {
PointerCoords[][] pcs = parsePointerCoords(command);
final PointerCoords[][] pcs = parsePointerCoords(command);
if (command.isElementCommand()) {
final AndroidElement el = command.getElement();
@@ -40,10 +66,12 @@ public class MultiPointerGesture extends TouchableEvent {
return getErrorResult("Unable to perform multi pointer gesture");
}
} else {
Object controller = getController();
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.JELLY_BEAN_MR2) {
final Method pmpg = getMethod("performMultiPointerGesture", controller);
Boolean rt = (Boolean)pmpg.invoke(controller, (Object)pcs);
final ReflectionUtils utils = new ReflectionUtils();
final Method pmpg = utils.getMethod("performMultiPointerGesture",
PointerCoords[][].class);
final Boolean rt = (Boolean) pmpg.invoke(utils.getController(),
(Object) pcs);
if (rt.booleanValue()) {
return getSuccessResult("OK");
} else {
@@ -64,45 +92,28 @@ public class MultiPointerGesture extends TouchableEvent {
}
}
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 {
private PointerCoords[] gesturesToPointerCoords(final double maxTime,
final JSONArray gestures) throws JSONException {
// gestures, e.g.:
// [
// {"touch":{"y":529.5,"x":120},"time":0.2},
// {"touch":{"y":529.5,"x":130},"time":0.4},
// {"touch":{"y":454.5,"x":140},"time":0.6},
// {"touch":{"y":304.5,"x":150},"time":0.8}
// ]
// [
// {"touch":{"y":529.5,"x":120},"time":0.2},
// {"touch":{"y":529.5,"x":130},"time":0.4},
// {"touch":{"y":454.5,"x":140},"time":0.6},
// {"touch":{"y":304.5,"x":150},"time":0.8}
// ]
// From the docs:
// "Steps are injected about 5 milliseconds apart, so 100 steps may take
// around 0.5 seconds to complete."
int steps = (int)(maxTime * 200) + 2;
final int steps = (int) (maxTime * 200) + 2;
PointerCoords[] pc = new PointerCoords[steps];
final PointerCoords[] pc = new PointerCoords[steps];
int i = 1;
JSONObject current = gestures.getJSONObject(0);
double currentTime = current.getDouble("time");
double runningTime = 0.0;
int gesturesLength = gestures.length();
final int gesturesLength = gestures.length();
for (int j = 0; j < steps; j++) {
if (runningTime > currentTime && i < gesturesLength) {
current = gestures.getJSONObject(i++);
@@ -117,31 +128,20 @@ public class MultiPointerGesture extends TouchableEvent {
return pc;
}
private PointerCoords createPointerCoords(JSONObject obj) throws JSONException {
JSONObject o = obj.getJSONObject("touch");
private PointerCoords[][] parsePointerCoords(final AndroidCommand command)
throws JSONException {
final JSONArray actions = (org.json.JSONArray) command.params().get(
"actions");
int x = o.getInt("x");
int y = o.getInt("y");
final double time = computeLongestTime(actions);
PointerCoords p = new PointerCoords();
p.size = 1;
p.pressure = 1;
p.x = x;
p.y = y;
return p;
}
private double computeLongestTime(JSONArray actions) throws JSONException {
double max = 0.0;
final PointerCoords[][] pcs = new PointerCoords[actions.length()][];
for (int i = 0; i < actions.length(); i++) {
JSONArray gestures = actions.getJSONArray(i);
double endTime = gestures.getJSONObject(gestures.length()-1).getDouble("time");
if (endTime > max) {
max = endTime;
}
final JSONArray gestures = actions.getJSONArray(i);
pcs[i] = gesturesToPointerCoords(time, gestures);
}
return max;
return pcs;
}
}
@@ -4,6 +4,7 @@ import io.appium.android.bootstrap.Logger;
import java.lang.reflect.Method;
import com.android.uiautomator.common.ReflectionUtils;
import com.android.uiautomator.core.UiObjectNotFoundException;
/**
@@ -17,9 +18,10 @@ public class TouchDown extends TouchEvent {
protected boolean executeTouchEvent() throws UiObjectNotFoundException {
printEventDebugLine("TouchDown");
try {
final Object controller = getController();
final Method touchDown = getMethod("touchDown", controller);
return (Boolean) touchDown.invoke(controller, clickX, clickY);
final ReflectionUtils utils = new ReflectionUtils();
final Method touchDown = utils.getMethod("touchDown", int.class,
int.class);
return (Boolean) touchDown.invoke(utils.getController(), clickX, clickY);
} catch (final Exception e) {
Logger.debug("Problem invoking touchDown: " + e);
return false;
@@ -8,8 +8,6 @@ import io.appium.android.bootstrap.Logger;
import io.appium.android.bootstrap.WDStatus;
import io.appium.android.bootstrap.exceptions.ElementNotInHashException;
import java.lang.reflect.Field;
import java.lang.reflect.Method;
import java.util.ArrayList;
import java.util.Hashtable;
@@ -17,15 +15,14 @@ import org.json.JSONException;
import android.graphics.Rect;
import com.android.uiautomator.core.UiDevice;
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 TouchableEvent {
public abstract class TouchEvent extends CommandHandler {
protected AndroidElement el;
protected int clickX;
@@ -37,7 +34,7 @@ public abstract class TouchEvent extends TouchableEvent {
protected boolean isElement;
/**
*
*
* @param command
* The {@link AndroidCommand}
* @return {@link AndroidCommandResult}
@@ -6,6 +6,7 @@ import java.lang.reflect.Method;
import android.os.SystemClock;
import com.android.uiautomator.common.ReflectionUtils;
import com.android.uiautomator.core.UiObjectNotFoundException;
/**
@@ -24,13 +25,14 @@ public class TouchLongClick extends TouchEvent {
* the super class.
*/
final Object controller = getController();
final Method touchDown = getMethod("touchDown", controller);
final Method touchUp = getMethod("touchUp", controller);
final ReflectionUtils utils = new ReflectionUtils();
final Method touchDown = utils.getMethod("touchDown", int.class,
int.class);
final Method touchUp = utils.getMethod("touchUp", int.class, int.class);
if ((Boolean) touchDown.invoke(controller, x, y)) {
if ((Boolean) touchDown.invoke(utils.getController(), x, y)) {
SystemClock.sleep(duration);
if ((Boolean) touchUp.invoke(controller, x, y)) {
if ((Boolean) touchUp.invoke(utils.getController(), x, y)) {
return true;
}
}
@@ -4,6 +4,7 @@ import io.appium.android.bootstrap.Logger;
import java.lang.reflect.Method;
import com.android.uiautomator.common.ReflectionUtils;
import com.android.uiautomator.core.UiObjectNotFoundException;
/**
@@ -17,9 +18,10 @@ public class TouchMove extends TouchEvent {
protected boolean executeTouchEvent() throws UiObjectNotFoundException {
printEventDebugLine("TouchMove");
try {
final Object controller = getController();
final Method touchMove = getMethod("touchMove", controller);
return (Boolean) touchMove.invoke(controller, clickX, clickY);
final ReflectionUtils utils = new ReflectionUtils();
final Method touchMove = utils.getMethod("touchMove", int.class,
int.class);
return (Boolean) touchMove.invoke(utils.getController(), clickX, clickY);
} catch (final Exception e) {
Logger.debug("Problem invoking touchMove: " + e);
return false;
@@ -4,6 +4,7 @@ import io.appium.android.bootstrap.Logger;
import java.lang.reflect.Method;
import com.android.uiautomator.common.ReflectionUtils;
import com.android.uiautomator.core.UiObjectNotFoundException;
/**
@@ -17,9 +18,9 @@ public class TouchUp extends TouchEvent {
protected boolean executeTouchEvent() throws UiObjectNotFoundException {
printEventDebugLine("TouchUp");
try {
final Object controller = getController();
final Method touchUp = getMethod("touchUp", controller);
return (Boolean) touchUp.invoke(controller, clickX, clickY);
final ReflectionUtils utils = new ReflectionUtils();
final Method touchUp = utils.getMethod("touchUp", int.class, int.class);
return (Boolean) touchUp.invoke(utils.getController(), clickX, clickY);
} catch (final Exception e) {
Logger.debug("Problem invoking touchUp: " + e);
return false;
+2
View File
@@ -260,6 +260,8 @@ Firefox.prototype.notImplementedCmds = function () {
, 'getSize'
, 'getWindowSize'
, 'getPageIndex'
, 'pressKeyCode'
, 'longPressKeyCode'
, 'keyevent'
, 'back'
, 'forward'
+8
View File
@@ -657,6 +657,14 @@ iOSController.submit = function (elementId, cb) {
}
};
iOSController.pressKeyCode = function (keycode, metastate, cb) {
cb(new NotImplementedError(), null);
};
iOSController.longPressKeyCode = function (keycode, metastate, cb) {
cb(new NotImplementedError(), null);
};
iOSController.keyevent = function (keycode, metastate, cb) {
cb(new NotImplementedError(), null);
};
+20
View File
@@ -614,6 +614,26 @@ exports.getPageIndex = function (req, res) {
req.device.getPageIndex(elementId, getResponseHandler(req, res));
};
exports.pressKeyCode = function (req, res) {
req.body = _.defaults(req.body, {
keycode: null
, metastate: null
});
var keycode = req.body.keycode
, metastate = req.body.metastate;
req.device.pressKeyCode(keycode, metastate, getResponseHandler(req, res));
};
exports.longPressKeyCode = function (req, res) {
req.body = _.defaults(req.body, {
keycode: null
, metastate: null
});
var keycode = req.body.keycode
, metastate = req.body.metastate;
req.device.longPressKeyCode(keycode, metastate, getResponseHandler(req, res));
};
exports.keyevent = function (req, res) {
req.body = _.defaults(req.body, {
keycode: null
+2
View File
@@ -94,6 +94,8 @@ module.exports = function (appium) {
// appium-specific extensions to JSONWP
rest.post('/wd/hub/session/:sessionId?/appium/device/shake', controller.mobileShake);
rest.post('/wd/hub/session/:sessionId?/appium/device/lock', controller.lock);
rest.post('/wd/hub/session/:sessionId?/appium/device/press_keycode', controller.pressKeyCode);
rest.post('/wd/hub/session/:sessionId?/appium/device/long_press_keycode', controller.longPressKeyCode);
rest.post('/wd/hub/session/:sessionId?/appium/device/keyevent', controller.keyevent);
rest.post('/wd/hub/session/:sessionId?/appium/device/rotate', controller.mobileRotation);
rest.get('/wd/hub/session/:sessionId?/appium/device/current_activity', controller.getCurrentActivity);
@@ -44,7 +44,7 @@ class ContactsAndroidTests(unittest.TestCase):
# no way to handle alerts in Android
self.driver.find_element_by_android_uiautomator('new UiSelector().clickable(true)').click()
self.driver.keyevent(3)
self.driver.press_keycode(3)