[go: nahoru, domu]

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)