/* * 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, final int duration) { 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(duration); 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; } }
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; }
protected void printEventDebugLine(final String methodName, final Integer... duration) { String extra = ""; if (duration.length > 0) { extra = ", duration: " + duration[0]; } Logger.debug( "Performing " + methodName + " using element? " + isElement + " x: " + clickX + ", y: " + clickY + extra); }
/* * @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 { final Hashtable<String, Object> params = command.params(); AndroidElement el; final String direction = params.get("direction").toString(); final Integer percent = (Integer) params.get("percent"); final Integer steps = (Integer) params.get("steps"); try { el = command.getElement(); if (el == null) { return getErrorResult( "Could not find an element with elementId: " + params.get("elementId")); } } catch (final Exception e) { // JSONException, NullPointerException, etc. return getErrorResult("Unknown error:" + e.getMessage()); } Logger.debug( "Pinching " + direction + " " + percent.toString() + "%" + " with steps: " + steps.toString()); boolean res; if (direction.equals("in")) { try { res = el.pinchIn(percent, steps); } catch (final UiObjectNotFoundException e) { return getErrorResult("Selector could not be matched to any UI element displayed"); } } else { try { res = el.pinchOut(percent, steps); } catch (final UiObjectNotFoundException e) { return getErrorResult("Selector could not be matched to any UI element displayed"); } } if (res) { return getSuccessResult(res); } else { return getErrorResult("Pinch did not complete successfully"); } }
public boolean check() { if (start + delta < System.currentTimeMillis()) { // Send only one alert message... if (isDialogPresent() && (!alerted)) { Logger.info("Emitting system alert message"); alerted = true; } // if the dialog went away, make sure we can send an alert again if (!isDialogPresent() && alerted) { alerted = false; } start = System.currentTimeMillis(); } return false; }
@Override protected boolean executeTouchEvent() throws UiObjectNotFoundException { final Object paramDuration = params.get("duration"); int duration = 2000; // two seconds if (paramDuration != null) { duration = Integer.parseInt(paramDuration.toString()); } printEventDebugLine("TouchLongClick", duration); if (correctLongClick(clickX, clickY, duration)) { return true; } // if correctLongClick failed and we have an element // then uiautomator's longClick is used as a fallback. if (isElement) { Logger.debug("Falling back to broken longClick"); return el.longClick(); } return false; }
/* * UiAutomator has a broken longClick, so we'll try to implement it using the * touchDown / touchUp events. */ protected static boolean correctLongClick(final int x, final int y, final int duration) { try { /* * bridge.getClass() returns ShellUiAutomatorBridge on API 18/19 so use * the super class. */ final ReflectionUtils utils = new ReflectionUtils(); final Method touchDown = utils.getControllerMethod("touchDown", int.class, int.class); final Method touchUp = utils.getControllerMethod("touchUp", int.class, int.class); if ((Boolean) touchDown.invoke(utils.getController(), x, y)) { SystemClock.sleep(duration); if ((Boolean) touchUp.invoke(utils.getController(), 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} * @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 Hashtable<String, Object> params = command.params(); AndroidElement el = null; int clickX = -1; int clickY = -1; boolean isElement = false; // isElementCommand doesn't check to see if we actually have an element // so getElement is used instead. try { if (command.getElement() != null) { isElement = true; } } catch (final Exception e) { } if (isElement) { // extract x and y from the element. el = command.getElement(); final Rect bounds = el.getVisibleBounds(); clickX = bounds.centerX(); clickY = bounds.centerY(); } else { // no element so extract x and y from params final Object paramX = params.get("x"); final Object paramY = params.get("y"); double targetX = 0.5; double targetY = 0.5; if (paramX != null) { targetX = Double.parseDouble(paramX.toString()); } if (paramY != null) { targetY = Double.parseDouble(paramY.toString()); } final ArrayList<Integer> posVals = absPosFromCoords(new Double[] {targetX, targetY}); clickX = posVals.get(0); clickY = posVals.get(1); } final Object paramDuration = params.get("duration"); int duration = 2000; // two seconds if (paramDuration != null) { duration = Integer.parseInt(paramDuration.toString()); } Logger.debug( "longClick using element? " + isElement + " x: " + clickX + ", y: " + clickY + ", duration: " + duration); if (correctLongClick(clickX, clickY, duration)) { return getSuccessResult(true); } // if correctLongClick failed and we have an element // then uiautomator's longClick is used as a fallback. if (isElement) { 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) { return new AndroidCommandResult(WDStatus.NO_SUCH_ELEMENT, e.getMessage()); } catch (final Exception e) { return getErrorResult(e.getMessage()); } return getErrorResult("Failed to long click"); }
/* * @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 { final Hashtable<String, Object> params = command.params(); // only makes sense on a device final Strategy strategy = Strategy.fromString((String) params.get("strategy")); final String text = (String) params.get("selector"); final String contextId = (String) params.get("context"); Logger.debug( "Finding " + text + " using " + strategy.toString() + " with the contextId: " + contextId); final Boolean multiple = (Boolean) params.get("multiple"); final boolean isXpath = strategy.equals("xpath"); if (isXpath) { final JSONArray xpathPath = (JSONArray) params.get("path"); final String xpathAttr = (String) params.get("attr"); final String xpathConstraint = (String) params.get("constraint"); final Boolean xpathSubstr = (Boolean) params.get("substr"); try { if (multiple) { final UiSelector sel = getSelectorForXpath(xpathPath, xpathAttr, xpathConstraint, xpathSubstr); return getSuccessResult(fetchElements(sel, contextId)); } else { final UiSelector sel = getSelectorForXpath(xpathPath, xpathAttr, xpathConstraint, xpathSubstr); return getSuccessResult(fetchElement(sel, contextId)); } } catch (final AndroidCommandException e) { return getErrorResult(e.getMessage()); } catch (final ElementNotFoundException e) { return getErrorResult(e.getMessage()); } catch (final ElementNotInHashException e) { return getErrorResult(e.getMessage()); } catch (final UiObjectNotFoundException e) { return getErrorResult(e.getMessage()); } } else { try { final UiSelector sel = getSelector(strategy, text, multiple); if (multiple) { return getSuccessResult(fetchElements(sel, contextId)); } else { return getSuccessResult(fetchElement(sel, contextId)); } } catch (final InvalidStrategyException e) { return getErrorResult(e.getMessage()); } catch (final ElementNotFoundException e) { return new AndroidCommandResult(WDStatus.NO_SUCH_ELEMENT, e.getMessage()); } catch (final AndroidCommandException e) { return getErrorResult(e.getMessage()); } catch (final ElementNotInHashException e) { return getErrorResult(e.getMessage()); } catch (final UiObjectNotFoundException e) { return getErrorResult(e.getMessage()); } } }
/** * Create and return a UiSelector based on Xpath attributes. * * @param path The Xpath path. * @param attr The attribute. * @param constraint Any constraint. * @param substr Any substr. * @return UiSelector * @throws AndroidCommandException */ private UiSelector getSelectorForXpath( final JSONArray path, final String attr, String constraint, final boolean substr) throws AndroidCommandException { UiSelector s = new UiSelector(); JSONObject pathObj; String nodeType; String searchType; final String substrStr = substr ? "true" : "false"; Logger.info( "Building xpath selector from attr " + attr + " and constraint " + constraint + " and substr " + substrStr); String selOut = "s"; // $driver.find_element :xpath, %(//*[contains(@text, 'agree')]) // info: [ANDROID] [info] Building xpath selector from attr text and // constraint agree and substr true // info: [ANDROID] [info] s.className('*').textContains('agree') try { nodeType = path.getJSONObject(0).getString("node"); } catch (final JSONException e) { throw new AndroidCommandException("Error parsing xpath path obj from JSON"); } if (attr.toLowerCase().contentEquals("text") && !constraint.isEmpty() && substr == true && nodeType.contentEquals("*") == true) { selOut += ".textContains('" + constraint + "')"; s = s.textContains(constraint); Logger.info(selOut); return s; } // //*[contains(@tag, "button")] if (attr.toLowerCase().contentEquals("tag") && !constraint.isEmpty() && substr == true && nodeType.contentEquals("*") == true) { // (?i) = case insensitive match. Esape everything that isn't an // alpha num. // use .* to match on contains. constraint = "(?i)^.*" + constraint.replaceAll("([^\\p{Alnum}])", "\\\\$1") + ".*$"; selOut += ".classNameMatches('" + constraint + "')"; s = s.classNameMatches(constraint); Logger.info(selOut); return s; } for (int i = 0; i < path.length(); i++) { try { pathObj = path.getJSONObject(i); nodeType = pathObj.getString("node"); searchType = pathObj.getString("search"); } catch (final JSONException e) { throw new AndroidCommandException("Error parsing xpath path obj from JSON"); } nodeType = AndroidElementClassMap.match(nodeType); if (searchType.equals("child")) { s = s.childSelector(s); selOut += ".childSelector(s)"; } else { s = s.className(nodeType); selOut += ".className('" + nodeType + "')"; } } if (attr.equals("desc") || attr.equals("name")) { selOut += ".description"; if (substr) { selOut += "Contains"; s = s.descriptionContains(constraint); } else { s = s.description(constraint); } selOut += "('" + constraint + "')"; } else if (attr.equals("text") || attr.equals("value")) { selOut += ".text"; if (substr) { selOut += "Contains"; s = s.textContains(constraint); } else { s = s.text(constraint); } selOut += "('" + constraint + "')"; } Logger.info(selOut); return s; }