Skip to content

Commit

Permalink
Merge pull request #2673 from paymand/long_press_keycode
Browse files Browse the repository at this point in the history
Renamed keyevent to press_keycode and added long_press_keycode.
  • Loading branch information
jlipps committed May 28, 2014
2 parents 3c8b240 + 8b1ec9d commit 399adf2
Show file tree
Hide file tree
Showing 15 changed files with 229 additions and 119 deletions.
11 changes: 10 additions & 1 deletion lib/devices/android/android-controller.js
Expand Up @@ -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;
};
Expand Down
@@ -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 java.lang.reflect.Field;
import java.lang.reflect.Method;

import android.view.MotionEvent.PointerCoords;
import android.os.Build;

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 {
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
Expand All @@ -27,40 +19,39 @@ private static Field enableField(final Class<?> clazz, final String field)
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;
}

/*
* 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;
}

protected Method getMethod(final String name, final Object 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;
}
Expand Down
Expand Up @@ -14,6 +14,7 @@
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;
Expand Down Expand Up @@ -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());
Expand Down
@@ -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;
Expand All @@ -23,14 +10,53 @@

import java.lang.reflect.Method;

import org.json.JSONArray;
import org.json.JSONException;
import org.json.JSONObject;

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;

public class MultiPointerGesture extends TouchableEvent {
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();
Expand All @@ -40,10 +66,12 @@ public AndroidCommandResult execute(final AndroidCommand command)
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 {
Expand All @@ -64,45 +92,28 @@ public AndroidCommandResult execute(final AndroidCommand command)
}
}

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++);
Expand All @@ -117,31 +128,20 @@ private PointerCoords[] gesturesToPointerCoords(double maxTime, JSONArray gestur
return pc;
}

private PointerCoords createPointerCoords(JSONObject obj) throws JSONException {
JSONObject o = obj.getJSONObject("touch");

int x = o.getInt("x");
int y = o.getInt("y");

PointerCoords p = new PointerCoords();
p.size = 1;
p.pressure = 1;
p.x = x;
p.y = y;
private PointerCoords[][] parsePointerCoords(final AndroidCommand command)
throws JSONException {
final JSONArray actions = (org.json.JSONArray) command.params().get(
"actions");

return p;
}
final double time = computeLongestTime(actions);

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;
}
}

0 comments on commit 399adf2

Please sign in to comment.