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
  • Loading branch information
mykola-mokhnach committed Jan 2, 2024
1 parent 9d2c2dd commit d9d4082
Show file tree
Hide file tree
Showing 12 changed files with 141 additions and 90 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
Original file line number Diff line number Diff line change
Expand Up @@ -23,6 +23,7 @@
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;
Expand All @@ -36,6 +37,7 @@

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;
Expand Down Expand Up @@ -128,12 +130,12 @@ private static Rect getBoundsForGestures(AccessibilityNodeInfo node) {

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 +144,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 +153,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 +162,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 (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,7 +185,7 @@ 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) {
Expand All @@ -178,7 +194,7 @@ public static void swipe(AccessibilityNodeInfo node, Direction direction, float

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) {
Expand All @@ -187,7 +203,7 @@ public static boolean scroll(AccessibilityNodeInfo node, Direction direction, fl

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) {
Expand All @@ -196,7 +212,7 @@ public static boolean fling(AccessibilityNodeInfo node, Direction direction) {

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

/**
Expand Down
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
Original file line number Diff line number Diff line change
Expand Up @@ -40,6 +40,7 @@
import io.appium.uiautomator2.common.exceptions.InvalidSelectorException;
import io.appium.uiautomator2.common.exceptions.UiAutomator2Exception;
import io.appium.uiautomator2.model.AccessibleUiObject;
import io.appium.uiautomator2.model.AndroidElement;
import io.appium.uiautomator2.model.ScreenRotation;
import io.appium.uiautomator2.utils.Device;
import io.appium.uiautomator2.utils.Logger;
Expand All @@ -65,15 +66,24 @@ public class CustomUiDevice {
private final Class<?> ByMatcherClass;
private final Constructor<?> uiObject2Constructor;
private final Instrumentation mInstrumentation;
private GestureController gestureController;

private final Object nativeGestureController;

private CustomUiDevice() {
this.mInstrumentation = (Instrumentation) getField(UiDevice.class, FIELD_M_INSTRUMENTATION, Device.getUiDevice());
this.ByMatcherClass = ReflectionUtils.getClass("androidx.test.uiautomator.ByMatcher");
this.METHOD_FIND_MATCH = getMethod(ByMatcherClass, "findMatch", UiDevice.class, BySelector.class, AccessibilityNodeInfo[].class);
this.METHOD_FIND_MATCHES = getMethod(ByMatcherClass, "findMatches", UiDevice.class, BySelector.class, AccessibilityNodeInfo[].class);
this.uiObject2Constructor = getConstructor(UiObject2.class, UiDevice.class, BySelector.class, AccessibilityNodeInfo.class);
this.METHOD_FIND_MATCH = getMethod(
ByMatcherClass, "findMatch",
UiDevice.class, BySelector.class, AccessibilityNodeInfo[].class
);
this.METHOD_FIND_MATCHES = getMethod(
ByMatcherClass, "findMatches",
UiDevice.class, BySelector.class, AccessibilityNodeInfo[].class
);
this.uiObject2Constructor = getConstructor(
UiObject2.class,
UiDevice.class, BySelector.class, AccessibilityNodeInfo.class
);
this.nativeGestureController = getNativeGestureControllerInstance();
}

public static synchronized CustomUiDevice getInstance() {
Expand All @@ -96,17 +106,6 @@ public UiAutomation getUiAutomation() {
}

private UiObject2 toUiObject2(@NonNull BySelector selector, @Nullable AccessibilityNodeInfo node) {
// TODO: remove this comment after upgrading to androidx.test.uiautomator:uiautomator:2.3.0
// UiObject2 with androidx.test.uiautomator:uiautomator:2.3.0 has below code to crate the instance,
// thus if the node was None, it should create an empty element for the AccessibilityNodeInfo.
// <pre>
// if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.R) {
// AccessibilityWindowInfo window = UiObject2.Api21Impl.getWindow(cachedNode);
// mDisplayId = window == null ? Display.DEFAULT_DISPLAY : UiObject2.Api30Impl.getDisplayId(window);
// } else {
// mDisplayId = Display.DEFAULT_DISPLAY;
// }
// </pre>
AccessibilityNodeInfo accessibilityNodeInfo =
(node == null && android.os.Build.VERSION.SDK_INT >= android.os.Build.VERSION_CODES.R)
? new AccessibilityNodeInfo()
Expand Down Expand Up @@ -151,36 +150,26 @@ public AccessibleUiObject findObject(Object selector) throws UiAutomator2Excepti
return node == null ? null : new AccessibleUiObject(toUiObject2(realSelector, node), node);
}

public synchronized GestureController getGestureController() {
if (gestureController == null) {
Class<?> gesturesClass = ReflectionUtils.getClass("androidx.test.uiautomator.Gestures");
// TODO: UIAutomator lib has changed this class significantly in v2.3.0,
// TODO: so this approach won't work anymore
Method gesturesFactory = ReflectionUtils.getMethod(
gesturesClass, "getInstance", UiDevice.class
);
Gestures gestures;
try {
gestures = new Gestures(gesturesFactory.invoke(gesturesClass, getUiDevice()));
} catch (InvocationTargetException | IllegalAccessException e) {
throw new UiAutomator2Exception("Cannot get an instance of the Gestures class", e);
}
Class<?> gestureControllerClass = ReflectionUtils.getClass(
"androidx.test.uiautomator.GestureController"
);
Method gestureControllerFactory = ReflectionUtils.getMethod(
gestureControllerClass, "getInstance", UiDevice.class
);
try {
gestureController = new GestureController(
gestureControllerFactory.invoke(gestureControllerClass, getUiDevice()),
gestures
);
} catch (InvocationTargetException | IllegalAccessException e) {
throw new UiAutomator2Exception("Cannot get an instance of the GestureController class", e);
}
private Object getNativeGestureControllerInstance() {
Class <?> gestureControllerClass = ReflectionUtils.getClass("androidx.test.uiautomator.GestureController");
Method gestureControllerFactory = ReflectionUtils.getMethod(gestureControllerClass, "getInstance", UiDevice.class);
try {
return gestureControllerFactory.invoke(gestureControllerClass, getUiDevice());
} catch (InvocationTargetException | IllegalAccessException e) {
throw new UiAutomator2Exception("Cannot get an instance of the GestureController class", e);
}
return gestureController;
}

public GestureController getGestureController(int displayId) {
return new GestureController(nativeGestureController, displayId);
}

public GestureController getGestureController() {
return new GestureController(nativeGestureController);
}

public GestureController getGestureController(AndroidElement element) {
return getGestureController(element.getDisplayId());
}

/**
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -21,6 +21,7 @@
import android.graphics.Point;
import android.graphics.Rect;
import android.os.SystemClock;
import android.view.Display;
import android.view.ViewConfiguration;

import androidx.annotation.Nullable;
Expand All @@ -35,15 +36,28 @@
import static io.appium.uiautomator2.utils.ReflectionUtils.invoke;

public class GestureController {

private final Object wrappedInstance;
private final Method performGestureMethod;
private final Gestures gestures;

GestureController(Object wrappedInstance, Gestures gestures) {
GestureController(Object wrappedInstance, int displayId) {
this.wrappedInstance = wrappedInstance;
this.performGestureMethod = extractPerformGestureMethod(wrappedInstance);
this.gestures = gestures;
this.gestures = new Gestures(displayId);
}

GestureController(Object wrappedInstance) {
this(wrappedInstance, getCurrentDisplayId());
}

private static int getCurrentDisplayId() {
if (android.os.Build.VERSION.SDK_INT >= android.os.Build.VERSION_CODES.R) {
Display display = getInstrumentation().getTargetContext().getDisplay();
if (display != null) {
return display.getDisplayId();
}
}
return Display.DEFAULT_DISPLAY;
}

private static Method extractPerformGestureMethod(Object wrappedInstance) {
Expand All @@ -70,7 +84,7 @@ private static UiDevice getDevice() {
}

private class GestureRunnable implements Runnable {
private PointerGesture[] mGestures;
private final PointerGesture[] mGestures;

public GestureRunnable(PointerGesture[] gestures) {
mGestures = gestures;
Expand Down
Loading

0 comments on commit d9d4082

Please sign in to comment.