Revert "De-duplicate bounds calculations and fix warnings"
This reverts commit 5f9f592feb929a8f10767f889974c29a28721bc8.
Reason for revert: This causes some test failures. It seems that the window bounds are not always valid (e.g. 0 height) which means that the node and its window do not overlap. Will need to investigate further.
Change-Id: I2df84169e641dae10cbe054b15acea26deba56d7
diff --git a/test/uiautomator/uiautomator/src/main/java/androidx/test/uiautomator/AccessibilityNodeInfoHelper.java b/test/uiautomator/uiautomator/src/main/java/androidx/test/uiautomator/AccessibilityNodeInfoHelper.java
index 5c82419..7f8f775 100644
--- a/test/uiautomator/uiautomator/src/main/java/androidx/test/uiautomator/AccessibilityNodeInfoHelper.java
+++ b/test/uiautomator/uiautomator/src/main/java/androidx/test/uiautomator/AccessibilityNodeInfoHelper.java
@@ -22,58 +22,53 @@
import android.view.accessibility.AccessibilityWindowInfo;
import androidx.annotation.DoNotInline;
-import androidx.annotation.NonNull;
import androidx.annotation.RequiresApi;
-/** Static helper methods for working with {@link AccessibilityNodeInfo}s. */
+/**
+ * This class contains static helper methods to work with
+ * {@link AccessibilityNodeInfo}
+ */
class AccessibilityNodeInfoHelper {
private AccessibilityNodeInfoHelper() {}
/**
- * Returns the visible bounds of an {@link AccessibilityNodeInfo}.
+ * Returns the node's bounds clipped to the size of the display
*
- * @param node node to analyze
- * @param width display width in pixels
- * @param height display height in pixels
- * @return {@link Rect} containing the visible bounds
+ * @param node
+ * @param width pixel width of the display
+ * @param height pixel height of the display
+ * @return null if node is null, else a Rect containing visible bounds
*/
- @NonNull
- static Rect getVisibleBoundsInScreen(@NonNull AccessibilityNodeInfo node, int width,
- int height) {
- Rect nodeBounds = new Rect();
- node.getBoundsInScreen(nodeBounds);
+ @SuppressWarnings("RectIntersectReturnValueIgnored")
+ static Rect getVisibleBoundsInScreen(AccessibilityNodeInfo node, int width, int height) {
+ if (node == null) {
+ return null;
+ }
+ // targeted node's bounds
+ Rect nodeRect = new Rect();
+ node.getBoundsInScreen(nodeRect);
- // Trim portions that are outside the specified display bounds.
- Rect displayBounds = new Rect(0, 0, width, height);
- nodeBounds = intersect(nodeBounds, displayBounds);
+ Rect displayRect = new Rect();
+ displayRect.top = 0;
+ displayRect.left = 0;
+ displayRect.right = width;
+ displayRect.bottom = height;
- // Trim portions that are outside the window bounds on API 21+.
+ nodeRect.intersect(displayRect);
+
+ // On platforms that give us access to the node's window
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.LOLLIPOP) {
- Rect windowBounds = new Rect();
+ // Trim any portion of the bounds that are outside the window
+ Rect bounds = new Rect();
AccessibilityWindowInfo window = Api21Impl.getWindow(node);
if (window != null) {
- Api21Impl.getBoundsInScreen(window, windowBounds);
- nodeBounds = intersect(nodeBounds, windowBounds);
+ Api21Impl.getBoundsInScreen(window, bounds);
+ nodeRect.intersect(bounds);
}
}
- // Trim portions that are outside the first scrollable ancestor.
- for (AccessibilityNodeInfo ancestor = node.getParent(); ancestor != null;
- ancestor = ancestor.getParent()) {
- if (ancestor.isScrollable()) {
- Rect ancestorBounds = getVisibleBoundsInScreen(ancestor, width, height);
- nodeBounds = intersect(nodeBounds, ancestorBounds);
- break;
- }
- }
-
- return nodeBounds;
- }
-
- /** Returns the intersection of two rectangles, or an empty rectangle if they do not overlap. */
- private static Rect intersect(Rect first, Rect second) {
- return first.intersect(second) ? first : new Rect();
+ return nodeRect;
}
@RequiresApi(21)
diff --git a/test/uiautomator/uiautomator/src/main/java/androidx/test/uiautomator/UiObject.java b/test/uiautomator/uiautomator/src/main/java/androidx/test/uiautomator/UiObject.java
index 0db19ba..22c1a0f 100644
--- a/test/uiautomator/uiautomator/src/main/java/androidx/test/uiautomator/UiObject.java
+++ b/test/uiautomator/uiautomator/src/main/java/androidx/test/uiautomator/UiObject.java
@@ -26,8 +26,6 @@
import android.view.MotionEvent.PointerCoords;
import android.view.accessibility.AccessibilityNodeInfo;
-import androidx.annotation.NonNull;
-
/**
* A UiObject is a representation of a view. It is not in any way directly bound to a
* view as an object reference. A UiObject contains information to help it
@@ -352,12 +350,56 @@
rect.centerY(), rect.right - SWIPE_MARGIN_LIMIT, rect.centerY(), steps);
}
- /** Returns the visible bounds of an {@link AccessibilityNodeInfo}. */
- @NonNull
- private Rect getVisibleBounds(@NonNull AccessibilityNodeInfo node) {
+ /**
+ * Finds the visible bounds of a partially visible UI element
+ *
+ * @param node
+ * @return null if node is null, else a Rect containing visible bounds
+ */
+ @SuppressWarnings("RectIntersectReturnValueIgnored")
+ private Rect getVisibleBounds(AccessibilityNodeInfo node) {
+ if (node == null) {
+ return null;
+ }
+
+ // targeted node's bounds
int w = getDevice().getDisplayWidth();
int h = getDevice().getDisplayHeight();
- return AccessibilityNodeInfoHelper.getVisibleBoundsInScreen(node, w, h);
+ Rect nodeRect = AccessibilityNodeInfoHelper.getVisibleBoundsInScreen(node, w, h);
+
+ // is the targeted node within a scrollable container?
+ AccessibilityNodeInfo scrollableParentNode = getScrollableParent(node);
+ if(scrollableParentNode == null) {
+ // nothing to adjust for so return the node's Rect as is
+ return nodeRect;
+ }
+
+ // Scrollable parent's visible bounds
+ Rect parentRect = AccessibilityNodeInfoHelper
+ .getVisibleBoundsInScreen(scrollableParentNode, w, h);
+ // adjust for partial clipping of targeted by parent node if required
+ nodeRect.intersect(parentRect);
+ return nodeRect;
+ }
+
+ /**
+ * Walks up the layout hierarchy to find a scrollable parent. A scrollable parent
+ * indicates that this node might be in a container where it is partially
+ * visible due to scrolling. In this case, its clickable center might not be visible and
+ * the click coordinates should be adjusted.
+ *
+ * @param node
+ * @return The accessibility node info.
+ */
+ private AccessibilityNodeInfo getScrollableParent(AccessibilityNodeInfo node) {
+ AccessibilityNodeInfo parent = node;
+ while(parent != null) {
+ parent = parent.getParent();
+ if (parent != null && parent.isScrollable()) {
+ return parent;
+ }
+ }
+ return null;
}
/**
diff --git a/test/uiautomator/uiautomator/src/main/java/androidx/test/uiautomator/UiObject2.java b/test/uiautomator/uiautomator/src/main/java/androidx/test/uiautomator/UiObject2.java
index 75445bf..19ede49 100644
--- a/test/uiautomator/uiautomator/src/main/java/androidx/test/uiautomator/UiObject2.java
+++ b/test/uiautomator/uiautomator/src/main/java/androidx/test/uiautomator/UiObject2.java
@@ -32,7 +32,6 @@
import android.view.accessibility.AccessibilityWindowInfo;
import androidx.annotation.DoNotInline;
-import androidx.annotation.NonNull;
import androidx.annotation.RequiresApi;
import java.util.ArrayList;
@@ -270,21 +269,57 @@
return false;
}
- /** Returns the visible bounds of an {@link AccessibilityNodeInfo}. */
- @NonNull
- private Rect getVisibleBounds(@NonNull AccessibilityNodeInfo node) {
- DisplayManager displayManager =
- (DisplayManager) mDevice.getInstrumentation().getContext().getSystemService(
- Service.DISPLAY_SERVICE);
- Display display = displayManager.getDisplay(getDisplayId());
- if (display != null) {
- Point displaySize = new Point();
- display.getRealSize(displaySize);
- return AccessibilityNodeInfoHelper.getVisibleBoundsInScreen(
- node, displaySize.x, displaySize.y);
+ /** Returns the visible bounds of {@code node} in screen coordinates. */
+ @SuppressWarnings("RectIntersectReturnValueIgnored")
+ private Rect getVisibleBounds(AccessibilityNodeInfo node) {
+ // Get the object bounds in screen coordinates
+ Rect ret = new Rect();
+ node.getBoundsInScreen(ret);
+
+ // Trim any portion of the bounds that are not on the screen
+ final int displayId = getDisplayId();
+ if (displayId == Display.DEFAULT_DISPLAY) {
+ final Rect screen =
+ new Rect(0, 0, getDevice().getDisplayWidth(), getDevice().getDisplayHeight());
+ ret.intersect(screen);
+ } else {
+ final DisplayManager dm =
+ (DisplayManager) mDevice.getInstrumentation().getContext().getSystemService(
+ Service.DISPLAY_SERVICE);
+ final Display display = dm.getDisplay(getDisplayId());
+ if (display != null) {
+ final Point size = new Point();
+ display.getRealSize(size);
+ final Rect screen = new Rect(0, 0, size.x, size.y);
+ ret.intersect(screen);
+ }
}
- return AccessibilityNodeInfoHelper.getVisibleBoundsInScreen(
- node, Integer.MAX_VALUE, Integer.MAX_VALUE);
+
+ // On platforms that give us access to the node's window
+ if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.LOLLIPOP) {
+ // Trim any portion of the bounds that are outside the window
+ Rect bounds = new Rect();
+ AccessibilityWindowInfo window = Api21Impl.getWindow(node);
+ if (window != null) {
+ Api21Impl.getBoundsInScreen(window, bounds);
+ ret.intersect(bounds);
+ }
+ }
+
+ // Find the visible bounds of our first scrollable ancestor
+ AccessibilityNodeInfo ancestor = null;
+ for (ancestor = node.getParent(); ancestor != null; ancestor = ancestor.getParent()) {
+ // 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 = getVisibleBounds(ancestor);
+ ret.intersect(ancestorRect);
+ break;
+ }
+ }
+
+ return ret;
}
/** Returns a point in the center of the visible bounds of this object. */
@@ -761,6 +796,12 @@
static AccessibilityWindowInfo getWindow(AccessibilityNodeInfo accessibilityNodeInfo) {
return accessibilityNodeInfo.getWindow();
}
+
+ @DoNotInline
+ static void getBoundsInScreen(AccessibilityWindowInfo accessibilityWindowInfo,
+ Rect outBounds) {
+ accessibilityWindowInfo.getBoundsInScreen(outBounds);
+ }
}
@RequiresApi(30)