Skip to content

Commit

Permalink
feat!: Bump androidx.test.uiautomator:uiautomator version to 2.3.0-be…
Browse files Browse the repository at this point in the history
…ta01 (#591)
  • Loading branch information
mykola-mokhnach authored Jan 4, 2024
1 parent 9d2c2dd commit 4a68395
Show file tree
Hide file tree
Showing 13 changed files with 301 additions and 183 deletions.
6 changes: 3 additions & 3 deletions app/build.gradle
Original file line number Diff line number Diff line change
Expand Up @@ -14,11 +14,11 @@ apply plugin: 'com.android.application'
apply plugin: 'de.mobilej.unmock'

android {
compileSdk 33
compileSdk 34
defaultConfig {
applicationId 'io.appium.uiautomator2'
minSdkVersion 21
targetSdkVersion 33
targetSdkVersion 34
versionCode 151
archivesBaseName = 'appium-uiautomator2'
/**
Expand Down Expand Up @@ -89,7 +89,7 @@ unMock {
dependencies {
// https://download.eclipse.org/oomph/archive/reports/download.eclipse.org/releases/2021-09/index/org.eclipse.wst.xml.xpath2.processor_2.1.101.v201903222120.html
implementation fileTree(include: ['*.jar'], dir: 'libs')
implementation 'androidx.test.uiautomator:uiautomator:2.2.0'
implementation 'androidx.test.uiautomator:uiautomator:2.3.0-beta01'
implementation 'androidx.test:core:1.5.0'
implementation 'androidx.test:runner:1.5.2'
implementation 'com.google.code.gson:gson:2.10.1'
Expand Down
207 changes: 118 additions & 89 deletions app/src/main/java/io/appium/uiautomator2/core/AxNodeInfoHelper.java
Original file line number Diff line number Diff line change
Expand Up @@ -23,25 +23,22 @@
import android.os.Build;
import android.os.Bundle;
import android.util.Pair;
import android.view.Display;
import android.view.accessibility.AccessibilityNodeInfo;
import android.view.accessibility.AccessibilityNodeInfo.AccessibilityAction;
import android.view.accessibility.AccessibilityWindowInfo;

import androidx.annotation.Nullable;
import androidx.test.uiautomator.Direction;
import androidx.test.uiautomator.UiDevice;

import java.util.HashSet;
import java.util.Set;

import io.appium.uiautomator2.common.exceptions.InvalidElementStateException;
import io.appium.uiautomator2.model.internal.CustomUiDevice;
import io.appium.uiautomator2.model.internal.GestureController;
import io.appium.uiautomator2.model.settings.Settings;
import io.appium.uiautomator2.model.settings.SimpleBoundsCalculation;
import io.appium.uiautomator2.model.settings.SnapshotMaxDepth;
import io.appium.uiautomator2.utils.Logger;

import static io.appium.uiautomator2.utils.Device.getUiDevice;
import static io.appium.uiautomator2.utils.ReflectionUtils.getField;
import static io.appium.uiautomator2.utils.StringHelpers.charSequenceToNullableString;
import static io.appium.uiautomator2.utils.StringHelpers.charSequenceToString;
Expand All @@ -53,6 +50,13 @@ public class AxNodeInfoHelper {
private static final long UNDEFINED_NODE_ID =
(((long) Integer.MAX_VALUE) << 32) | Integer.MAX_VALUE;
private static final int UNDEFINED_WINDOW_ID = -1;
private static final float DEFAULT_GESTURE_MARGIN_PERCENT = 0.1f;
private static final Margins mMargins = new PercentMargins(
DEFAULT_GESTURE_MARGIN_PERCENT,
DEFAULT_GESTURE_MARGIN_PERCENT,
DEFAULT_GESTURE_MARGIN_PERCENT,
DEFAULT_GESTURE_MARGIN_PERCENT
);

@Nullable
public static String toUuid(AccessibilityNodeInfo info) {
Expand Down Expand Up @@ -114,26 +118,17 @@ private static Point getCenterPoint(Rect bounds) {

private static Rect getBoundsForGestures(AccessibilityNodeInfo node) {
Rect bounds = getBounds(node);
// The default margin values are copied from UiObject2 class:
// private int mMarginLeft = 5;
// private int mMarginTop = 5;
// private int mMarginRight = 5;
// private int mMarginBottom = 5;
bounds.left = bounds.left + 5;
bounds.top = bounds.top + 5;
bounds.right = bounds.right - 5;
bounds.bottom = bounds.bottom - 5;
return bounds;
return mMargins.apply(bounds);
}

public static void click(AccessibilityNodeInfo node) {
Rect bounds = getBounds(node);
CustomUiDevice.getInstance().getGestureController().click(getCenterPoint(bounds));
makeGestureController(node).click(getCenterPoint(bounds));
}

public static void doubleClick(AccessibilityNodeInfo node) {
Rect bounds = getBounds(node);
CustomUiDevice.getInstance().getGestureController().doubleClick(getCenterPoint(bounds));
makeGestureController(node).doubleClick(getCenterPoint(bounds));
}

public static void longClick(AccessibilityNodeInfo node) {
Expand All @@ -142,7 +137,7 @@ public static void longClick(AccessibilityNodeInfo node) {

public static void longClick(AccessibilityNodeInfo node, @Nullable Long durationMs) {
Rect bounds = getBounds(node);
CustomUiDevice.getInstance().getGestureController().longClick(getCenterPoint(bounds), durationMs);
makeGestureController(node).longClick(getCenterPoint(bounds), durationMs);
}

public static void drag(AccessibilityNodeInfo node, Point end) {
Expand All @@ -151,7 +146,7 @@ public static void drag(AccessibilityNodeInfo node, Point end) {

public static void drag(AccessibilityNodeInfo node, Point end, @Nullable Integer speed) {
Rect bounds = getBounds(node);
CustomUiDevice.getInstance().getGestureController().drag(getCenterPoint(bounds), end, speed);
makeGestureController(node).drag(getCenterPoint(bounds), end, speed);
}

public static void pinchClose(AccessibilityNodeInfo node, float percent) {
Expand All @@ -160,7 +155,21 @@ public static void pinchClose(AccessibilityNodeInfo node, float percent) {

public static void pinchClose(AccessibilityNodeInfo node, float percent, @Nullable Integer speed) {
Rect bounds = getBoundsForGestures(node);
CustomUiDevice.getInstance().getGestureController().pinchClose(bounds, percent, speed);
makeGestureController(node).pinchClose(bounds, percent, speed);
}

private static GestureController makeGestureController(AccessibilityNodeInfo node) {
return CustomUiDevice.getInstance().getGestureController(getAxNodeDisplayId(node));
}

public static int getAxNodeDisplayId(AccessibilityNodeInfo node) {
if (node != null && Build.VERSION.SDK_INT >= Build.VERSION_CODES.R) {
AccessibilityWindowInfo window = node.getWindow();
if (window != null) {
return window.getDisplayId();
}
}
return Display.DEFAULT_DISPLAY;
}

public static void pinchOpen(AccessibilityNodeInfo node, float percent) {
Expand All @@ -169,54 +178,101 @@ public static void pinchOpen(AccessibilityNodeInfo node, float percent) {

public static void pinchOpen(AccessibilityNodeInfo node, float percent, @Nullable Integer speed) {
Rect bounds = getBoundsForGestures(node);
CustomUiDevice.getInstance().getGestureController().pinchOpen(bounds, percent, speed);
makeGestureController(node).pinchOpen(bounds, percent, speed);
}

public static void swipe(AccessibilityNodeInfo node, Direction direction, float percent) {
swipe(node, direction, percent, null);
}

public static void swipe(AccessibilityNodeInfo node, Direction direction, float percent, @Nullable Integer speed) {
public static void swipe(
AccessibilityNodeInfo node, Direction direction, float percent, @Nullable Integer speed
) {
Rect bounds = getBoundsForGestures(node);
CustomUiDevice.getInstance().getGestureController().swipe(bounds, direction, percent, speed);
makeGestureController(node).swipe(bounds, direction, percent, speed);
}

public static boolean scroll(AccessibilityNodeInfo node, Direction direction, float percent) {
return scroll(node, direction, percent, null);
}

public static boolean scroll(AccessibilityNodeInfo node, Direction direction, float percent, @Nullable Integer speed) {
public static boolean scroll(
AccessibilityNodeInfo node, Direction direction, float percent, @Nullable Integer speed
) {
Rect bounds = getBoundsForGestures(node);
return CustomUiDevice.getInstance().getGestureController().scroll(bounds, direction, percent, speed);
return makeGestureController(node).scroll(bounds, direction, percent, speed);
}

public static boolean fling(AccessibilityNodeInfo node, Direction direction) {
return fling(node, direction, null);
}

public static boolean fling(AccessibilityNodeInfo node, Direction direction, @Nullable Integer speed) {
public static boolean fling(
AccessibilityNodeInfo node, Direction direction, @Nullable Integer speed
) {
Rect bounds = getBoundsForGestures(node);
return CustomUiDevice.getInstance().getGestureController().fling(bounds, direction, speed);
return makeGestureController(node).fling(bounds, direction, speed);
}

/**
* Returns the node's bounds clipped to the size of the display
*
* @return Empty Rect if node is null, else a Rect containing visible bounds
*/
public static Rect getBounds(@Nullable AccessibilityNodeInfo node) {
Rect rect = new Rect();
int displayId = AxNodeInfoHelper.getAxNodeDisplayId(node);
final boolean isDisplayAccessible = CustomUiDevice.getInstance().getDisplayById(displayId) != null;
Rect screen = null;
if (isDisplayAccessible) {
Point displaySize = CustomUiDevice.getInstance().getDisplaySize(displayId);
screen = new Rect(0, 0, displaySize.x, displaySize.y);
}
if (node == null) {
return rect;
return screen == null ? new Rect() : screen;
}
if (Settings.get(SimpleBoundsCalculation.class).getValue()) {
node.getBoundsInScreen(rect);
return rect;
return getVisibleBoundsInScreen(
node,
screen,
Boolean.FALSE.equals(Settings.get(SimpleBoundsCalculation.class).getValue()),
0
);
}

@SuppressLint("CheckResult")
private static Rect getVisibleBoundsInScreen(
AccessibilityNodeInfo node, Rect displayRect, boolean trimScrollableParent, int depth
) {
Rect nodeRect = new Rect();
node.getBoundsInScreen(nodeRect);

if (displayRect == null) {
displayRect = new Rect();
}
nodeRect.intersect(displayRect);

// Trim any portion of the bounds that are outside the window
Rect bounds = new Rect();
AccessibilityWindowInfo window = node.getWindow();
if (window != null) {
window.getBoundsInScreen(bounds);
nodeRect.intersect(bounds);
}

UiDevice uiDevice = getUiDevice();
Rect screenRect = new Rect(0, 0, uiDevice.getDisplayWidth(), uiDevice.getDisplayHeight());
return getBounds(node, screenRect, 0);
// Trim the bounds into any scrollable ancestor, if required.
if (trimScrollableParent) {
for (AccessibilityNodeInfo ancestor = node.getParent();
ancestor != null;
ancestor = ancestor.getParent()
) {
if (ancestor.isScrollable()) {
if (depth >= Settings.get(SnapshotMaxDepth.class).getValue()) {
break;
}
Rect ancestorRect = getVisibleBoundsInScreen(
ancestor, displayRect, true, depth + 1
);
nodeRect.intersect(ancestorRect);
break;
}
}
}

return nodeRect;
}

public static int calculateIndex(AccessibilityNodeInfo node) {
Expand All @@ -232,55 +288,6 @@ public static int calculateIndex(AccessibilityNodeInfo node) {
return 0;
}

/**
* Returns the node's bounds clipped to the size of the display, limited by the SnapshotMaxDepth
* The implementation is borrowed from `getVisibleBounds` method of `UiObject2` class
*
* @return Empty rect if node is null, else a Rect containing visible bounds
*/
@SuppressLint("CheckResult")
private static Rect getBounds(@Nullable AccessibilityNodeInfo node, Rect displayRect, int depth) {
Rect ret = new Rect();
if (node == null) {
return ret;
}

// Get the object bounds in screen coordinates
node.getBoundsInScreen(ret);

// Trim any portion of the bounds that are not on the screen
ret.intersect(displayRect);

// Trim any portion of the bounds that are outside the window
Rect window = new Rect();
AccessibilityWindowInfo nodeWindow = node.getWindow();
if (nodeWindow != null) {
nodeWindow.getBoundsInScreen(window);
ret.intersect(window);
}

// Find the visible bounds of our first scrollable ancestor
int currentDepth = depth;
Set<AccessibilityNodeInfo> ancestors = new HashSet<>();
AccessibilityNodeInfo ancestor = node.getParent();
// An erroneous situation is possible where node parent equals to the node itself
while (++currentDepth < Settings.get(SnapshotMaxDepth.class).getValue()
&& ancestor != null && !ancestors.contains(ancestor)) {
// If this ancestor is scrollable
if (ancestor.isScrollable()) {
// Trim any portion of the bounds that are hidden by the non-visible portion of our
// ancestor
Rect ancestorRect = getBounds(ancestor, displayRect, currentDepth);
ret.intersect(ancestorRect);
return ret;
}
ancestors.add(ancestor);
ancestor = ancestor.getParent();
}

return ret;
}

/**
* Perform accessibility action ACTION_SET_PROGRESS on the node
*
Expand Down Expand Up @@ -330,4 +337,26 @@ public static String truncateTextToMaxLength(final AccessibilityNodeInfo node, f
}
return text;
}

private interface Margins {
Rect apply(Rect bounds);
}

private static class PercentMargins implements Margins {
float mLeft, mTop, mRight, mBottom;
PercentMargins(float left, float top, float right, float bottom) {
mLeft = left;
mTop = top;
mRight = right;
mBottom = bottom;
}

@Override
public Rect apply(Rect bounds) {
return new Rect(bounds.left + (int) (bounds.width() * mLeft),
bounds.top + (int) (bounds.height() * mTop),
bounds.right - (int) (bounds.width() * mRight),
bounds.bottom - (int) (bounds.height() * mBottom));
}
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -56,8 +56,9 @@ protected AppiumResponse safeHandle(IHttpRequest request) {
Rect bounds = element.getBounds();
Point start = new Point(bounds.left + dragModel.start.x.intValue(),
bounds.top + dragModel.start.y.intValue());
CustomUiDevice.getInstance().getGestureController().drag(start, dragModel.end.toNativePoint(),
dragModel.speed);
CustomUiDevice.getInstance().getGestureController(element).drag(
start, dragModel.end.toNativePoint(), dragModel.speed
);
}
}

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -30,6 +30,8 @@ public interface AndroidElement {

@Nullable String getContextId();

int getDisplayId();

boolean isSingleMatch();

void clear() throws UiObjectNotFoundException;
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -161,6 +161,11 @@ public String getAttribute(String attr) throws UiObjectNotFoundException {
return (result instanceof String) ? (String) result : String.valueOf(result);
}

@Override
public int getDisplayId() {
return element.getDisplayId();
}

@Override
public void clear() {
element.clear();
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -39,6 +39,7 @@
import io.appium.uiautomator2.utils.PositionHelper;

import static io.appium.uiautomator2.core.AxNodeInfoExtractor.toAxNodeInfo;
import static io.appium.uiautomator2.core.AxNodeInfoHelper.getAxNodeDisplayId;
import static io.appium.uiautomator2.model.AccessibleUiObject.toAccessibleUiObject;
import static io.appium.uiautomator2.model.AccessibleUiObject.toAccessibleUiObjects;
import static io.appium.uiautomator2.utils.ElementHelpers.generateNoAttributeException;
Expand Down Expand Up @@ -152,6 +153,11 @@ public String getAttribute(String attr) throws UiObjectNotFoundException {
return (result instanceof String) ? (String) result : String.valueOf(result);
}

@Override
public int getDisplayId() {
return getAxNodeDisplayId(toAxNodeInfo(element));
}

@Override
public void clear() throws UiObjectNotFoundException {
element.setText("");
Expand Down
Loading

0 comments on commit 4a68395

Please sign in to comment.