From bd5e0a094578a2ee580c18429ddec7021960ec33 Mon Sep 17 00:00:00 2001 From: Mykola Mokhnach Date: Mon, 4 Mar 2024 07:42:15 +0100 Subject: [PATCH] chore: Remove unused helpers (#617) --- .../core/InteractionController.java | 112 +------- .../uiautomator2/core/UiAutomatorBridge.java | 1 - .../uiautomator2/model/UiObject2Element.java | 3 +- .../uiautomator2/model/UiObjectElement.java | 3 +- .../utils/ContentSizeHelpers.java | 255 ++++++++++++++++++ .../uiautomator2/utils/ElementHelpers.java | 236 ---------------- .../utils/w3c/ActionsExecutor.java | 6 +- 7 files changed, 263 insertions(+), 353 deletions(-) create mode 100644 app/src/main/java/io/appium/uiautomator2/utils/ContentSizeHelpers.java diff --git a/app/src/main/java/io/appium/uiautomator2/core/InteractionController.java b/app/src/main/java/io/appium/uiautomator2/core/InteractionController.java index 776d4fa12..ba526992b 100644 --- a/app/src/main/java/io/appium/uiautomator2/core/InteractionController.java +++ b/app/src/main/java/io/appium/uiautomator2/core/InteractionController.java @@ -16,134 +16,24 @@ package io.appium.uiautomator2.core; import android.view.InputEvent; -import android.view.MotionEvent.PointerCoords; import io.appium.uiautomator2.common.exceptions.UiAutomator2Exception; -import io.appium.uiautomator2.model.settings.Settings; -import io.appium.uiautomator2.model.settings.TrackScrollEvents; -import io.appium.uiautomator2.utils.Logger; import static io.appium.uiautomator2.utils.ReflectionUtils.getMethod; import static io.appium.uiautomator2.utils.ReflectionUtils.invoke; public class InteractionController { - public static final String METHOD_PERFORM_MULTI_POINTER_GESTURE = "performMultiPointerGesture"; private static final String CLASS_INTERACTION_CONTROLLER = "androidx.test.uiautomator.InteractionController"; - private static final String METHOD_SEND_KEY = "sendKey"; private static final String METHOD_INJECT_EVENT_SYNC = "injectEventSync"; - private static final String METHOD_TOUCH_DOWN = "touchDown"; - private static final String METHOD_TOUCH_UP = "touchUp"; - private static final String METHOD_TOUCH_MOVE = "touchMove"; - private static final String METHOD_CLICK_NO_SYNC = "clickNoSync"; private final Object interactionController; public InteractionController(Object interactionController) { this.interactionController = interactionController; } - public boolean sendKey(int keyCode, int metaState) throws UiAutomator2Exception { - return (Boolean) invoke(getMethod(CLASS_INTERACTION_CONTROLLER, METHOD_SEND_KEY, int.class, int.class), - interactionController, keyCode, metaState); - } - - public boolean injectEventSync(final InputEvent event, boolean shouldRegister) throws UiAutomator2Exception { - if (!shouldRegister) { - return (Boolean) invoke(getMethod(CLASS_INTERACTION_CONTROLLER, - METHOD_INJECT_EVENT_SYNC, InputEvent.class), interactionController, event); - } - return EventRegister.runAndRegisterScrollEvents(new ReturningRunnable() { - @Override - public void run() { - Boolean result = (Boolean) invoke(getMethod(CLASS_INTERACTION_CONTROLLER, - METHOD_INJECT_EVENT_SYNC, InputEvent.class), interactionController, event); - setResult(result); - } - }); - } - public boolean injectEventSync(final InputEvent event) throws UiAutomator2Exception { - return injectEventSync(event, true); - } - - public boolean shouldTrackScrollEvents() { - final TrackScrollEvents trackScrollEventsSetting = Settings.get(TrackScrollEvents.class); - Boolean trackScrollEvents = trackScrollEventsSetting.getValue(); - Logger.error(String.format("Setting '%s' is set to %b", - trackScrollEventsSetting.getName(), trackScrollEvents)); - - return trackScrollEvents; - } - - private boolean doTouchDown(final int x, final int y) { - return (Boolean) invoke(getMethod(CLASS_INTERACTION_CONTROLLER, - METHOD_TOUCH_DOWN, int.class, int.class), interactionController, x, y); - } - - public boolean touchDown(final int x, final int y) throws UiAutomator2Exception { - return shouldTrackScrollEvents() - ? EventRegister.runAndRegisterScrollEvents(new ReturningRunnable() { - @Override - public void run() { - setResult(doTouchDown(x, y)); - } - }) - : doTouchDown(x, y); - } - - private boolean doTouchUp(final int x, final int y) { - return (Boolean) invoke(getMethod(CLASS_INTERACTION_CONTROLLER, METHOD_TOUCH_UP, - int.class, int.class), interactionController, x, y); - } - - public boolean touchUp(final int x, final int y) throws UiAutomator2Exception { - return shouldTrackScrollEvents() - ? EventRegister.runAndRegisterScrollEvents(new ReturningRunnable() { - @Override - public void run() { - setResult(doTouchUp(x, y)); - } - }) - : doTouchUp(x, y); - } - - private boolean doTouchMove(final int x, final int y) { - return (Boolean) invoke(getMethod(CLASS_INTERACTION_CONTROLLER, - METHOD_TOUCH_MOVE, int.class, int.class), interactionController, x, y); - } - - public boolean touchMove(final int x, final int y) throws UiAutomator2Exception { - return shouldTrackScrollEvents() - ? EventRegister.runAndRegisterScrollEvents(new ReturningRunnable() { - @Override - public void run() { - setResult(doTouchMove(x, y)); - } - }) - : doTouchMove(x, y); - } - - private boolean doPerformMultiPointerGesture(final PointerCoords[][] pcs) { return (Boolean) invoke(getMethod(CLASS_INTERACTION_CONTROLLER, - METHOD_PERFORM_MULTI_POINTER_GESTURE, PointerCoords[][].class), - interactionController, (Object) pcs); - } - - public boolean clickNoSync(int x, int y) { - return (Boolean) invoke(getMethod(CLASS_INTERACTION_CONTROLLER, - METHOD_CLICK_NO_SYNC, int.class, int.class), interactionController, x, y); - } - - public Boolean performMultiPointerGesture(final PointerCoords[][] pcs) throws UiAutomator2Exception { - if (shouldTrackScrollEvents()) { - return EventRegister.runAndRegisterScrollEvents(new ReturningRunnable() { - @Override - public void run() { - setResult(doPerformMultiPointerGesture(pcs)); - } - }); - } else { - return doPerformMultiPointerGesture(pcs); - } + METHOD_INJECT_EVENT_SYNC, InputEvent.class), interactionController, event); } } diff --git a/app/src/main/java/io/appium/uiautomator2/core/UiAutomatorBridge.java b/app/src/main/java/io/appium/uiautomator2/core/UiAutomatorBridge.java index 4ceee7fff..ecca3a162 100644 --- a/app/src/main/java/io/appium/uiautomator2/core/UiAutomatorBridge.java +++ b/app/src/main/java/io/appium/uiautomator2/core/UiAutomatorBridge.java @@ -18,7 +18,6 @@ import android.app.Service; import android.app.UiAutomation; import android.hardware.display.DisplayManager; -import android.os.Build; import android.view.Display; import android.view.accessibility.AccessibilityNodeInfo; diff --git a/app/src/main/java/io/appium/uiautomator2/model/UiObject2Element.java b/app/src/main/java/io/appium/uiautomator2/model/UiObject2Element.java index e7bc15716..b5d59d625 100644 --- a/app/src/main/java/io/appium/uiautomator2/model/UiObject2Element.java +++ b/app/src/main/java/io/appium/uiautomator2/model/UiObject2Element.java @@ -33,6 +33,7 @@ import io.appium.uiautomator2.core.AxNodeInfoHelper; import io.appium.uiautomator2.model.internal.CustomUiDevice; import io.appium.uiautomator2.utils.Attribute; +import io.appium.uiautomator2.utils.ContentSizeHelpers; import io.appium.uiautomator2.utils.ElementHelpers; import io.appium.uiautomator2.utils.Logger; import io.appium.uiautomator2.utils.PositionHelper; @@ -93,7 +94,7 @@ public String getAttribute(String attr) throws UiObjectNotFoundException { result = element.getResourceName(); break; case CONTENT_SIZE: - result = ElementHelpers.getContentSize(this); + result = ContentSizeHelpers.getContentSize(this); break; case ENABLED: result = element.isEnabled(); diff --git a/app/src/main/java/io/appium/uiautomator2/model/UiObjectElement.java b/app/src/main/java/io/appium/uiautomator2/model/UiObjectElement.java index 2da9e5504..614ea2995 100644 --- a/app/src/main/java/io/appium/uiautomator2/model/UiObjectElement.java +++ b/app/src/main/java/io/appium/uiautomator2/model/UiObjectElement.java @@ -34,6 +34,7 @@ import io.appium.uiautomator2.model.internal.CustomUiDevice; import io.appium.uiautomator2.utils.Attribute; import io.appium.uiautomator2.utils.ByUiAutomatorFinder; +import io.appium.uiautomator2.utils.ContentSizeHelpers; import io.appium.uiautomator2.utils.ElementHelpers; import io.appium.uiautomator2.utils.Logger; import io.appium.uiautomator2.utils.PositionHelper; @@ -85,7 +86,7 @@ public String getAttribute(String attr) throws UiObjectNotFoundException { result = toAxNodeInfo(element).getViewIdResourceName(); break; case CONTENT_SIZE: - result = ElementHelpers.getContentSize(this); + result = ContentSizeHelpers.getContentSize(this); break; case ENABLED: result = element.isEnabled(); diff --git a/app/src/main/java/io/appium/uiautomator2/utils/ContentSizeHelpers.java b/app/src/main/java/io/appium/uiautomator2/utils/ContentSizeHelpers.java new file mode 100644 index 000000000..1fad1a139 --- /dev/null +++ b/app/src/main/java/io/appium/uiautomator2/utils/ContentSizeHelpers.java @@ -0,0 +1,255 @@ +package io.appium.uiautomator2.utils; + +import static io.appium.uiautomator2.utils.Device.getUiDevice; + +import android.graphics.Rect; +import android.view.accessibility.AccessibilityNodeInfo; + +import androidx.test.uiautomator.By; +import androidx.test.uiautomator.UiObject; +import androidx.test.uiautomator.UiObject2; +import androidx.test.uiautomator.UiObjectNotFoundException; + +import org.json.JSONException; +import org.json.JSONObject; + +import java.io.InvalidClassException; +import java.lang.reflect.Field; +import java.lang.reflect.Method; +import java.util.Objects; + +import io.appium.uiautomator2.common.exceptions.UiAutomator2Exception; +import io.appium.uiautomator2.core.AxNodeInfoExtractor; +import io.appium.uiautomator2.core.EventRegister; +import io.appium.uiautomator2.core.ReturningRunnable; +import io.appium.uiautomator2.core.UiObjectChildGenerator; +import io.appium.uiautomator2.model.AccessibilityScrollData; +import io.appium.uiautomator2.model.AndroidElement; +import io.appium.uiautomator2.model.AppiumUIA2Driver; +import io.appium.uiautomator2.model.Session; +import io.appium.uiautomator2.model.UiObject2Element; + +public class ContentSizeHelpers { + // these constants are magic numbers experimentally determined to minimize flakiness in generating + // last scroll data used in getting the 'contentSize' attribute. + // TODO see whether anchoring these to time and screen size is more reliable across devices + private static final int MINI_SWIPE_STEPS = 10; + private static final int MINI_SWIPE_PIXELS = 200; + // https://android.googlesource.com/platform/frameworks/testing/+/master/uiautomator/library/core-src/com/android/uiautomator/core/UiScrollable.java#635 + private static final double SWIPE_DEAD_ZONE_PCT = 0.1; + + public static String getContentSize(AndroidElement element) throws UiObjectNotFoundException { + Rect boundsRect = element.getBounds(); + ContentSize contentSize = new ContentSize(boundsRect); + try { + contentSize.touchPadding = getTouchPadding(element); + } catch (ReflectiveOperationException e) { + throw new UiAutomator2Exception(e); + } + contentSize.scrollableOffset = getScrollableOffset(element); + return contentSize.toString(); + } + + private static Rect getElementBoundsInScreen(AndroidElement element) { + return getElementBoundsInScreen(element.getUiObject()); + } + + private static Rect getElementBoundsInScreen(Object uiObject) { + Logger.debug("Getting bounds in screen for an AndroidElement"); + AccessibilityNodeInfo nodeInfo = AxNodeInfoExtractor.toAxNodeInfo(uiObject); + + Rect rect = new Rect(); + nodeInfo.getBoundsInScreen(rect); + Logger.debug("Bounds were: " + rect); + return rect; + } + + private static int getScrollableOffset(AndroidElement uiScrollable) { + // get the bounds of the scrollable view + Rect bounds = getElementBoundsInScreen(uiScrollable); + + // now scroll a bit back and forth in the view to populate the lastScrollData we need + int x1 = bounds.centerX(); + int y1 = bounds.centerY() + MINI_SWIPE_PIXELS; + //noinspection UnnecessaryLocalVariable + int x2 = x1; + int y2 = y1 - (MINI_SWIPE_PIXELS * 2); + int yMargin = (int) Math.floor(bounds.height() * SWIPE_DEAD_ZONE_PCT); + + // ensure that our xs and ys are within the bounds of the element + if (y1 > bounds.height()) { + y1 = bounds.height() - yMargin; + } + if (y2 < 0) { + y2 = yMargin; + } + + Session session = AppiumUIA2Driver.getInstance().getSessionOrThrow(); + AccessibilityScrollData lastScrollData; + Logger.debug("Doing a mini swipe-and-back in the scrollable view to generate scroll data"); + swipe(x1, y1, x2, y2); + lastScrollData = session.getLastScrollData(); + if (lastScrollData == null) { + // if we didn't get scroll data from the down swipe, try to get it from the up swipe + swipe(x2, y2, x1, y1); + lastScrollData = session.getLastScrollData(); + } else { + // otherwise just do a reset swipe without worrying about scroll data, to avoid it + // failing because of flakiness + getUiDevice().swipe(x2, y2, x1, y1, MINI_SWIPE_STEPS); + } + + + if (lastScrollData == null) { + throw new UiAutomator2Exception("Could not retrieve accessibility scroll data; unable to determine scrollable offset"); + } + + // if we're in some views, like ScrollViews, we get x/y values directly, and we can simply return + if (lastScrollData.getMaxScrollY() != -1) { + return lastScrollData.getMaxScrollY(); + } + + // in other views, like List or Grid, we get item counts and indexes, and we have to turn + // that into pixels by doing some math + if (lastScrollData.getItemCount() == -1) { + throw new UiAutomator2Exception("Did not get either scrollY or itemCount from accessibility scroll data"); + } + + return getScrollableOffsetByItemCount(uiScrollable, lastScrollData.getItemCount()); + } + + private static int getScrollableOffsetByItemCount(AndroidElement uiScrollable, int itemCount) { + Logger.debug("Figuring out scrollableOffset via item count of " + itemCount); + Object scrollObject = uiScrollable.getUiObject(); + Rect scrollBounds = getElementBoundsInScreen(uiScrollable); + + // here we loop through the children and get their bounds until the height differs, then + // regardless of whether we have a list or a grid, we'll know the height of an item/row + try { + int itemsPerRow = 0; + int rowHeight = 0; + int lastExaminedItemY = Integer.MIN_VALUE; // initialize to something impossibly negative + int numRowsExamined = 0; + int numRowsToExamine = 3; // examine a few rows since the top ones often have bad offsets + Object lastExaminedItem = null; + + UiObjectChildGenerator gen = new UiObjectChildGenerator(scrollObject); + for (Object item : gen) { + if (item == null) { + throw new UiObjectNotFoundException("Could not get child of scrollview"); + } + + Rect itemBounds = getElementBoundsInScreen(item); + + ++itemsPerRow; + lastExaminedItem = item; + + if (lastExaminedItemY != Integer.MIN_VALUE && itemBounds.top > lastExaminedItemY) { + ++numRowsExamined; + rowHeight = itemBounds.top - lastExaminedItemY; + if (numRowsExamined >= numRowsToExamine) { + break; + } + // reset itemsPerRow as we examine another row; don't want it to overaccumulate + itemsPerRow = 0; + } + + lastExaminedItemY = itemBounds.top; + } + + if (lastExaminedItem == null) { + throw new UiObjectNotFoundException("Could not find any children of the scrollview to get offset from"); + } + Logger.debug("Determined there were " + itemsPerRow + " items per row"); + + if (itemsPerRow == 0) { + // Need to exit. Other case we will get an ArithmeticException + return 0; + } + + int numRows = itemCount / itemsPerRow; + if (itemCount % itemsPerRow > 0) { + // we might have an additional part-row + ++numRows; + } + int totalHeight = numRows * rowHeight; + int scrollableOffset = totalHeight - scrollBounds.height(); + Logger.debug("Determined there were " + numRows + " rows of height " + + rowHeight + ", for a total height of " + totalHeight + " and scroll offset " + + "of " + scrollableOffset); + return scrollableOffset; + } catch (UiObjectNotFoundException ignore) { + } catch (InvalidClassException e) { + Logger.error("Programming error, tried to build a UiObjectChildGenerator with wrong type"); + } + + // there were no child items we could find, so assume no offset + return 0; + } + + private static int getTouchPadding( + AndroidElement element + ) throws UiObjectNotFoundException, ReflectiveOperationException { + final UiObject2 uiObject2 = element instanceof UiObject2Element + ? getUiDevice().findObject(By.clazz(((UiObject2) element.getUiObject()).getClassName())) + : getUiDevice().findObject(By.clazz(((UiObject) element.getUiObject()).getClassName())); + Field gestureField = uiObject2.getClass().getDeclaredField("mGestures"); + gestureField.setAccessible(true); + Object gestureObject = Objects.requireNonNull(gestureField.get(uiObject2)); + + Field viewConfigField = gestureObject.getClass().getDeclaredField("mViewConfig"); + viewConfigField.setAccessible(true); + Object viewConfigObject = Objects.requireNonNull(viewConfigField.get(gestureObject)); + + Method getScaledPagingTouchSlopMethod = Objects.requireNonNull( + viewConfigObject.getClass().getDeclaredMethod("getScaledPagingTouchSlop") + ); + getScaledPagingTouchSlopMethod.setAccessible(true); + int touchPadding = (int) getScaledPagingTouchSlopMethod.invoke(viewConfigObject); + + return touchPadding / 2; + } + + @SuppressWarnings("UnusedReturnValue") + private static boolean swipe(final int startX, final int startY, final int endX, final int endY) { + Logger.debug(String.format("Swiping from [%s, %s] to [%s, %s]", startX, startY, endX, endY)); + return EventRegister.runAndRegisterScrollEvents(new ReturningRunnable() { + @Override + public void run() { + setResult(getUiDevice().swipe(startX, startY, endX, endY, MINI_SWIPE_STEPS)); + } + }); + } + + private static class ContentSize { + private final int width; + private final int height; + private final int top; + private final int left; + private int scrollableOffset; + private int touchPadding; + + ContentSize(Rect rect) { + width = rect.width(); + height = rect.height(); + top = rect.top; + left = rect.left; + } + + @Override + public String toString() { + JSONObject jsonObject = new JSONObject(); + try { + jsonObject.put("width", width); + jsonObject.put("height", height); + jsonObject.put("top", top); + jsonObject.put("left", left); + jsonObject.put("scrollableOffset", scrollableOffset); + jsonObject.put("touchPadding", touchPadding); + } catch (JSONException e) { + e.printStackTrace(); + } + return jsonObject.toString(); + } + } +} diff --git a/app/src/main/java/io/appium/uiautomator2/utils/ElementHelpers.java b/app/src/main/java/io/appium/uiautomator2/utils/ElementHelpers.java index 399d02194..789fe02f6 100644 --- a/app/src/main/java/io/appium/uiautomator2/utils/ElementHelpers.java +++ b/app/src/main/java/io/appium/uiautomator2/utils/ElementHelpers.java @@ -16,7 +16,6 @@ package io.appium.uiautomator2.utils; -import android.graphics.Rect; import android.os.Build; import android.os.Bundle; import android.view.accessibility.AccessibilityNodeInfo; @@ -24,52 +23,28 @@ import androidx.annotation.NonNull; import androidx.annotation.Nullable; -import androidx.test.uiautomator.By; import androidx.test.uiautomator.BySelector; -import androidx.test.uiautomator.UiObject; import androidx.test.uiautomator.UiObject2; -import androidx.test.uiautomator.UiObjectNotFoundException; -import org.json.JSONException; -import org.json.JSONObject; - -import java.io.InvalidClassException; -import java.lang.reflect.Field; -import java.lang.reflect.Method; import java.util.Arrays; import java.util.Objects; import io.appium.uiautomator2.common.exceptions.ElementNotFoundException; import io.appium.uiautomator2.common.exceptions.NoSuchAttributeException; -import io.appium.uiautomator2.common.exceptions.UiAutomator2Exception; import io.appium.uiautomator2.core.AxNodeInfoExtractor; import io.appium.uiautomator2.core.AxNodeInfoHelper; -import io.appium.uiautomator2.core.EventRegister; -import io.appium.uiautomator2.core.ReturningRunnable; -import io.appium.uiautomator2.core.UiObjectChildGenerator; -import io.appium.uiautomator2.model.AccessibilityScrollData; import io.appium.uiautomator2.model.AccessibleUiObject; import io.appium.uiautomator2.model.AndroidElement; import io.appium.uiautomator2.model.AppiumUIA2Driver; import io.appium.uiautomator2.model.Session; -import io.appium.uiautomator2.model.UiObject2Element; import static android.view.accessibility.AccessibilityNodeInfo.AccessibilityAction.ACTION_SET_PROGRESS; import static io.appium.uiautomator2.model.internal.CustomUiDevice.getInstance; -import static io.appium.uiautomator2.utils.Device.getUiDevice; import static io.appium.uiautomator2.utils.ReflectionUtils.getField; import static io.appium.uiautomator2.utils.StringHelpers.charSequenceToString; import static io.appium.uiautomator2.utils.StringHelpers.toNonNullString; public abstract class ElementHelpers { - // these constants are magic numbers experimentally determined to minimize flakiness in generating - // last scroll data used in getting the 'contentSize' attribute. - // TODO see whether anchoring these to time and screen size is more reliable across devices - private static final int MINI_SWIPE_STEPS = 10; - private static final int MINI_SWIPE_PIXELS = 200; - // https://android.googlesource.com/platform/frameworks/testing/+/master/uiautomator/library/core-src/com/android/uiautomator/core/UiScrollable.java#635 - private static final double SWIPE_DEAD_ZONE_PCT = 0.1; - public static boolean canSetProgress(Object element) { if (Build.VERSION.SDK_INT < Build.VERSION_CODES.N) { return false; @@ -153,215 +128,4 @@ public static String getText(Object element, boolean replaceNull) { return AxNodeInfoHelper.getText(AxNodeInfoExtractor.toAxNodeInfo(element), replaceNull); } - public static String getContentSize(AndroidElement element) throws UiObjectNotFoundException { - Rect boundsRect = element.getBounds(); - ContentSize contentSize = new ContentSize(boundsRect); - try { - contentSize.touchPadding = getTouchPadding(element); - } catch (ReflectiveOperationException e) { - throw new UiAutomator2Exception(e); - } - contentSize.scrollableOffset = getScrollableOffset(element); - return contentSize.toString(); - } - - private static Rect getElementBoundsInScreen(AndroidElement element) { - return getElementBoundsInScreen(element.getUiObject()); - } - - private static Rect getElementBoundsInScreen(Object uiObject) { - Logger.debug("Getting bounds in screen for an AndroidElement"); - AccessibilityNodeInfo nodeInfo = AxNodeInfoExtractor.toAxNodeInfo(uiObject); - - Rect rect = new Rect(); - nodeInfo.getBoundsInScreen(rect); - Logger.debug("Bounds were: " + rect); - return rect; - } - - private static int getScrollableOffset(AndroidElement uiScrollable) { - // get the bounds of the scrollable view - Rect bounds = getElementBoundsInScreen(uiScrollable); - - // now scroll a bit back and forth in the view to populate the lastScrollData we need - int x1 = bounds.centerX(); - int y1 = bounds.centerY() + MINI_SWIPE_PIXELS; - //noinspection UnnecessaryLocalVariable - int x2 = x1; - int y2 = y1 - (MINI_SWIPE_PIXELS * 2); - int yMargin = (int) Math.floor(bounds.height() * SWIPE_DEAD_ZONE_PCT); - - // ensure that our xs and ys are within the bounds of the element - if (y1 > bounds.height()) { - y1 = bounds.height() - yMargin; - } - if (y2 < 0) { - y2 = yMargin; - } - - Session session = AppiumUIA2Driver.getInstance().getSessionOrThrow(); - AccessibilityScrollData lastScrollData; - Logger.debug("Doing a mini swipe-and-back in the scrollable view to generate scroll data"); - swipe(x1, y1, x2, y2); - lastScrollData = session.getLastScrollData(); - if (lastScrollData == null) { - // if we didn't get scroll data from the down swipe, try to get it from the up swipe - swipe(x2, y2, x1, y1); - lastScrollData = session.getLastScrollData(); - } else { - // otherwise just do a reset swipe without worrying about scroll data, to avoid it - // failing because of flakiness - getUiDevice().swipe(x2, y2, x1, y1, MINI_SWIPE_STEPS); - } - - - if (lastScrollData == null) { - throw new UiAutomator2Exception("Could not retrieve accessibility scroll data; unable to determine scrollable offset"); - } - - // if we're in some views, like ScrollViews, we get x/y values directly, and we can simply return - if (lastScrollData.getMaxScrollY() != -1) { - return lastScrollData.getMaxScrollY(); - } - - // in other views, like List or Grid, we get item counts and indexes, and we have to turn - // that into pixels by doing some math - if (lastScrollData.getItemCount() == -1) { - throw new UiAutomator2Exception("Did not get either scrollY or itemCount from accessibility scroll data"); - } - - return getScrollableOffsetByItemCount(uiScrollable, lastScrollData.getItemCount()); - } - - private static int getScrollableOffsetByItemCount(AndroidElement uiScrollable, int itemCount) { - Logger.debug("Figuring out scrollableOffset via item count of " + itemCount); - Object scrollObject = uiScrollable.getUiObject(); - Rect scrollBounds = getElementBoundsInScreen(uiScrollable); - - // here we loop through the children and get their bounds until the height differs, then - // regardless of whether we have a list or a grid, we'll know the height of an item/row - try { - int itemsPerRow = 0; - int rowHeight = 0; - int lastExaminedItemY = Integer.MIN_VALUE; // initialize to something impossibly negative - int numRowsExamined = 0; - int numRowsToExamine = 3; // examine a few rows since the top ones often have bad offsets - Object lastExaminedItem = null; - - UiObjectChildGenerator gen = new UiObjectChildGenerator(scrollObject); - for (Object item : gen) { - if (item == null) { - throw new UiObjectNotFoundException("Could not get child of scrollview"); - } - - Rect itemBounds = getElementBoundsInScreen(item); - - ++itemsPerRow; - lastExaminedItem = item; - - if (lastExaminedItemY != Integer.MIN_VALUE && itemBounds.top > lastExaminedItemY) { - ++numRowsExamined; - rowHeight = itemBounds.top - lastExaminedItemY; - if (numRowsExamined >= numRowsToExamine) { - break; - } - // reset itemsPerRow as we examine another row; don't want it to overaccumulate - itemsPerRow = 0; - } - - lastExaminedItemY = itemBounds.top; - } - - if (lastExaminedItem == null) { - throw new UiObjectNotFoundException("Could not find any children of the scrollview to get offset from"); - } - Logger.debug("Determined there were " + itemsPerRow + " items per row"); - - if (itemsPerRow == 0) { - // Need to exit. Other case we will get an ArithmeticException - return 0; - } - - @SuppressWarnings("IntegerDivisionInFloatingPointContext") - int numRows = (int) Math.floor(itemCount / itemsPerRow); - if (itemCount % itemsPerRow > 0) { - // we might have an additional part-row - ++numRows; - } - int totalHeight = numRows * rowHeight; - int scrollableOffset = totalHeight - scrollBounds.height(); - Logger.debug("Determined there were " + numRows + " rows of height " + - rowHeight + ", for a total height of " + totalHeight + " and scroll offset " + - "of " + scrollableOffset); - return scrollableOffset; - } catch (UiObjectNotFoundException ignore) { - } catch (InvalidClassException e) { - Logger.error("Programming error, tried to build a UiObjectChildGenerator with wrong type"); - } - - // there were no child items we could find, so assume no offset - return 0; - } - - private static int getTouchPadding(AndroidElement element) throws UiObjectNotFoundException, ReflectiveOperationException { - final UiObject2 uiObject2 = element instanceof UiObject2Element - ? getUiDevice().findObject(By.clazz(((UiObject2) element.getUiObject()).getClassName())) - : getUiDevice().findObject(By.clazz(((UiObject) element.getUiObject()).getClassName())); - Field gestureField = uiObject2.getClass().getDeclaredField("mGestures"); - gestureField.setAccessible(true); - Object gestureObject = gestureField.get(uiObject2); - - Field viewConfigField = gestureObject.getClass().getDeclaredField("mViewConfig"); - viewConfigField.setAccessible(true); - Object viewConfigObject = viewConfigField.get(gestureObject); - - Method getScaledPagingTouchSlopMethod = viewConfigObject.getClass().getDeclaredMethod("getScaledPagingTouchSlop"); - getScaledPagingTouchSlopMethod.setAccessible(true); - int touchPadding = (int) getScaledPagingTouchSlopMethod.invoke(viewConfigObject); - - return touchPadding / 2; - } - - @SuppressWarnings("UnusedReturnValue") - private static boolean swipe(final int startX, final int startY, final int endX, final int endY) { - Logger.debug(String.format("Swiping from [%s, %s] to [%s, %s]", startX, startY, endX, endY)); - return EventRegister.runAndRegisterScrollEvents(new ReturningRunnable() { - @Override - public void run() { - setResult(getUiDevice().swipe(startX, startY, endX, endY, MINI_SWIPE_STEPS)); - } - }); - } - - private static class ContentSize { - private final int width; - private final int height; - private final int top; - private final int left; - private int scrollableOffset; - private int touchPadding; - - ContentSize(Rect rect) { - width = rect.width(); - height = rect.height(); - top = rect.top; - left = rect.left; - } - - @Override - public String toString() { - JSONObject jsonObject = new JSONObject(); - try { - jsonObject.put("width", width); - jsonObject.put("height", height); - jsonObject.put("top", top); - jsonObject.put("left", left); - jsonObject.put("scrollableOffset", scrollableOffset); - jsonObject.put("touchPadding", touchPadding); - } catch (JSONException e) { - e.printStackTrace(); - } - return jsonObject.toString(); - } - } } diff --git a/app/src/main/java/io/appium/uiautomator2/utils/w3c/ActionsExecutor.java b/app/src/main/java/io/appium/uiautomator2/utils/w3c/ActionsExecutor.java index 4934bf66a..d98ef86cd 100644 --- a/app/src/main/java/io/appium/uiautomator2/utils/w3c/ActionsExecutor.java +++ b/app/src/main/java/io/appium/uiautomator2/utils/w3c/ActionsExecutor.java @@ -120,7 +120,7 @@ private boolean injectKeyEvent(KeyInputEventParams eventParam, long startTimesta eventTime, keyAction, event.getKeyCode(), 0, event.getMetaState() | metaKeysToState(depressedMetaKeys), KeyCharacterMap.VIRTUAL_KEYBOARD, 0, 0); - result &= interactionController.injectEventSync(keyEvent, false); + result &= interactionController.injectEventSync(keyEvent); logEvent(keyEvent, eventTime, result); } } @@ -141,7 +141,7 @@ private boolean injectKeyEvent(KeyInputEventParams eventParam, long startTimesta final KeyEvent keyEvent = new KeyEvent(startTimestamp + eventParam.startDelta, eventTime, keyAction, w3CKeyCode.getAndroidCodePoint(), 0, metaKeysToState(depressedMetaKeys), KeyCharacterMap.VIRTUAL_KEYBOARD, 0, 0); - result = interactionController.injectEventSync(keyEvent, false); + result = interactionController.injectEventSync(keyEvent); logEvent(keyEvent, eventTime, result); return result; } @@ -250,7 +250,7 @@ private boolean executeMotionEvents(List events, long st break; } // switch if (synthesizedEvent != null) { - result &= interactionController.injectEventSync(synthesizedEvent, false); + result &= interactionController.injectEventSync(synthesizedEvent); logEvent(synthesizedEvent, eventTime, result); synthesizedEvent.recycle(); }