Fix long click on Android

Android's uiautomator has a broken long click implementation.
uiautomator will not pause long enough on long click which causes Android to detect the long click as a normal click.

This fix uses reflection to access uiautomator's private touchDown/touchUp methods to reimplement longClick correctly.

In theory I should be able to obtain a reference to android.app.UiAutomation without using reflection. However this doesn't seem possible when using uiautomator. getInstrumentation().getUiAutomation(); doesn't work because we're not using instrumentation.
This commit is contained in:
bootstraponline
2014-01-31 16:14:14 -05:00
parent a8b7d1d57a
commit afa2928ddf
2 changed files with 87 additions and 8 deletions

View File

@@ -195,6 +195,10 @@ public class AndroidElement {
return el;
}
public Rect getVisibleBounds() throws UiObjectNotFoundException {
return el.getVisibleBounds();
}
public boolean longClick() throws UiObjectNotFoundException {
return el.longClick();
}

View File

@@ -1,34 +1,109 @@
package io.appium.android.bootstrap.handler;
import com.android.uiautomator.core.UiObjectNotFoundException;
import io.appium.android.bootstrap.*;
import io.appium.android.bootstrap.AndroidCommand;
import io.appium.android.bootstrap.AndroidCommandResult;
import io.appium.android.bootstrap.AndroidElement;
import io.appium.android.bootstrap.CommandHandler;
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 org.json.JSONException;
import android.graphics.Rect;
import android.os.SystemClock;
import com.android.uiautomator.core.UiDevice;
import com.android.uiautomator.core.UiObjectNotFoundException;
/**
* This handler is used to long click elements in the Android UI.
*
*
*/
public class TouchLongClick 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;
}
/*
* UiAutomator has a broken longClick. getAutomatorBridge is private so we
* access the bridge via reflection to use the touchDown / touchUp methods.
*/
private boolean correctLongClick(final int x, final int y) {
try {
/*
* bridge.getClass() returns ShellUiAutomatorBridge on API 18/19 so use
* the super class.
*/
final UiDevice device = UiDevice.getInstance();
final Object bridge = enableField(device.getClass(),
"mUiAutomationBridge").get(device);
final Object controller = enableField(bridge.getClass().getSuperclass(),
"mInteractionController").get(bridge);
final Class<?> controllerClass = controller.getClass();
Logger.debug("Finding methods on class: " + controllerClass);
final Method touchDown = controllerClass.getDeclaredMethod("touchDown",
int.class, int.class);
touchDown.setAccessible(true);
final Method touchUp = controllerClass.getDeclaredMethod("touchUp",
int.class, int.class);
touchUp.setAccessible(true);
if ((Boolean) touchDown.invoke(controller, x, y)) {
SystemClock.sleep(2000);
if ((Boolean) touchUp.invoke(controller, x, y)) {
return true;
}
}
return false;
} catch (final Exception e) {
Logger.debug("Problem invoking correct long click: " + e);
return false;
}
}
/**
*
* @param command The {@link AndroidCommand}
*
* @param command
* The {@link AndroidCommand}
* @return {@link AndroidCommandResult}
* @throws JSONException
* @see io.appium.android.bootstrap.CommandHandler#execute(io.appium.android.bootstrap.AndroidCommand)
*/
@Override
public AndroidCommandResult execute(AndroidCommand command) throws JSONException {
public AndroidCommandResult execute(final AndroidCommand command)
throws JSONException {
if (!command.isElementCommand()) {
return getErrorResult("Unable to long click without an element.");
}
try {
final AndroidElement el = command.getElement();
final Rect bounds = el.getVisibleBounds();
final int x = bounds.centerX();
final int y = bounds.centerY();
if (correctLongClick(x, y)) {
return getSuccessResult(true);
}
Logger.debug("Falling back to broken longClick");
final boolean res = el.longClick();
return getSuccessResult(res);
} catch (final UiObjectNotFoundException e) {
return new AndroidCommandResult(WDStatus.NO_SUCH_ELEMENT, e.getMessage());
} catch (final ElementNotInHashException e) {
@@ -37,4 +112,4 @@ public class TouchLongClick extends CommandHandler {
return getErrorResult(e.getMessage());
}
}
}
}