Merge "Update ExploreByTouchHelper to calculate bounds on screen properly" into nyc-support-25.1-dev
diff --git a/compat/java/android/support/v4/view/accessibility/AccessibilityNodeInfoCompat.java b/compat/java/android/support/v4/view/accessibility/AccessibilityNodeInfoCompat.java
index 0f0df96..0b00094 100644
--- a/compat/java/android/support/v4/view/accessibility/AccessibilityNodeInfoCompat.java
+++ b/compat/java/android/support/v4/view/accessibility/AccessibilityNodeInfoCompat.java
@@ -16,10 +16,13 @@
 
 package android.support.v4.view.accessibility;
 
+import static android.support.annotation.RestrictTo.Scope.LIBRARY_GROUP;
+
 import android.graphics.Rect;
 import android.os.Build;
 import android.os.Bundle;
 import android.support.annotation.Nullable;
+import android.support.annotation.RestrictTo;
 import android.support.v4.accessibilityservice.AccessibilityServiceInfoCompat;
 import android.support.v4.view.ViewCompat;
 import android.text.InputType;
@@ -2365,6 +2368,14 @@
 
     private final Object mInfo;
 
+    /**
+     *  android.support.v4.widget.ExploreByTouchHelper.HOST_ID = -1;
+     *
+     *  @hide
+     */
+    @RestrictTo(LIBRARY_GROUP)
+    public int mParentVirtualDescendantId = -1;
+
     // Actions introduced in IceCreamSandwich
 
     /**
@@ -3171,6 +3182,7 @@
      * @param virtualDescendantId The id of the virtual descendant.
      */
     public void setParent(View root, int virtualDescendantId) {
+        mParentVirtualDescendantId = virtualDescendantId;
         IMPL.setParent(mInfo, root, virtualDescendantId);
     }
 
diff --git a/core-ui/java/android/support/v4/widget/ExploreByTouchHelper.java b/core-ui/java/android/support/v4/widget/ExploreByTouchHelper.java
index 1b21bc2..526575c 100644
--- a/core-ui/java/android/support/v4/widget/ExploreByTouchHelper.java
+++ b/core-ui/java/android/support/v4/widget/ExploreByTouchHelper.java
@@ -847,21 +847,45 @@
         }
         node.setFocused(isFocused);
 
-        // Set the visibility based on the parent bound.
-        if (intersectVisibleToUser(mTempParentRect)) {
-            node.setVisibleToUser(true);
-            node.setBoundsInParent(mTempParentRect);
-        }
+        mHost.getLocationOnScreen(mTempGlobalRect);
 
         // If not explicitly specified, calculate screen-relative bounds and
         // offset for scroll position based on bounds in parent.
         node.getBoundsInScreen(mTempScreenRect);
         if (mTempScreenRect.equals(INVALID_PARENT_BOUNDS)) {
-            mHost.getLocationOnScreen(mTempGlobalRect);
             node.getBoundsInParent(mTempScreenRect);
+
+            // If there is a parent node, adjust bounds based on the parent node.
+            if (node.mParentVirtualDescendantId != HOST_ID) {
+                AccessibilityNodeInfoCompat parentNode = AccessibilityNodeInfoCompat.obtain();
+                // Walk up the node tree to adjust the screen rect.
+                for (int virtualDescendantId = node.mParentVirtualDescendantId;
+                        virtualDescendantId != HOST_ID;
+                        virtualDescendantId = parentNode.mParentVirtualDescendantId) {
+                    // Reset the values in the parent node we'll be using.
+                    parentNode.setParent(mHost, HOST_ID);
+                    parentNode.setBoundsInParent(INVALID_PARENT_BOUNDS);
+                    // Adjust the bounds for the parent node.
+                    onPopulateNodeForVirtualView(virtualDescendantId, parentNode);
+                    parentNode.getBoundsInParent(mTempParentRect);
+                    mTempScreenRect.offset(mTempParentRect.left, mTempParentRect.top);
+                }
+                parentNode.recycle();
+            }
+            // Adjust the rect for the host view's location.
             mTempScreenRect.offset(mTempGlobalRect[0] - mHost.getScrollX(),
                     mTempGlobalRect[1] - mHost.getScrollY());
+        }
+
+        if (mHost.getLocalVisibleRect(mTempVisibleRect)) {
+            mTempVisibleRect.offset(mTempGlobalRect[0] - mHost.getScrollX(),
+                    mTempGlobalRect[1] - mHost.getScrollY());
+            mTempScreenRect.intersect(mTempVisibleRect);
             node.setBoundsInScreen(mTempScreenRect);
+
+            if (isVisibleToUser(mTempScreenRect)) {
+                node.setVisibleToUser(true);
+            }
         }
 
         return node;
@@ -903,7 +927,7 @@
      * @param localRect a rectangle in local (parent) coordinates
      * @return whether the specified {@link Rect} is visible on the screen
      */
-    private boolean intersectVisibleToUser(Rect localRect) {
+    private boolean isVisibleToUser(Rect localRect) {
         // Missing or empty bounds mean this view is not visible.
         if ((localRect == null) || localRect.isEmpty()) {
             return false;
@@ -925,17 +949,7 @@
         }
 
         // A null parent implies the view is not visible.
-        if (viewParent == null) {
-            return false;
-        }
-
-        // If no portion of the parent is visible, this view is not visible.
-        if (!mHost.getLocalVisibleRect(mTempVisibleRect)) {
-            return false;
-        }
-
-        // Check if the view intersects the visible portion of the parent.
-        return localRect.intersect(mTempVisibleRect);
+        return viewParent != null;
     }
 
     /**
diff --git a/samples/Support4Demos/res/values/strings.xml b/samples/Support4Demos/res/values/strings.xml
index 2d4f0db..24d88d6 100644
--- a/samples/Support4Demos/res/values/strings.xml
+++ b/samples/Support4Demos/res/values/strings.xml
@@ -215,6 +215,7 @@
     <string name="sample_item_a">Sample item A</string>
     <string name="sample_item_b">Sample item B</string>
     <string name="sample_item_c">Sample item C</string>
+    <string name="sample_item_d">Sample item D</string>
 
     <!-- ContentLoadingProgressBar -->
     <string name="content_loading_progress_bar">Widget/Content Loading Progress Bar</string>
diff --git a/samples/Support4Demos/src/com/example/android/supportv4/widget/ExploreByTouchHelperActivity.java b/samples/Support4Demos/src/com/example/android/supportv4/widget/ExploreByTouchHelperActivity.java
index 3f7675c..10db8f3 100644
--- a/samples/Support4Demos/src/com/example/android/supportv4/widget/ExploreByTouchHelperActivity.java
+++ b/samples/Support4Demos/src/com/example/android/supportv4/widget/ExploreByTouchHelperActivity.java
@@ -86,6 +86,13 @@
         CustomView.CustomItem itemC =
                 customView.addItem(getString(R.string.sample_item_c), 0, 0.75f, 1, 1);
         customView.setParentItem(itemC, itemB);
+
+        // Add an item at the left quarter of Item C.
+        CustomView.CustomItem itemD =
+                customView.addItem(getString(R.string.sample_item_d), 0, 0f, 0.25f, 1);
+        customView.setParentItem(itemD, itemC);
+
+        customView.layoutItems();
     }
 
     /**
@@ -169,12 +176,28 @@
         public void setParentItem(CustomItem item, CustomItem parent) {
             item.mParent = parent;
             parent.mChildren.add(item.mId);
-            RectF bounds = item.mBounds;
-            item.mBounds = new RectF(parent.mBounds.left + bounds.left * parent.mBounds.width(),
-                    parent.mBounds.top + bounds.top * parent.mBounds.height(),
-                    parent.mBounds.left + bounds.right * parent.mBounds.width(),
-                    parent.mBounds.top + bounds.bottom * parent.mBounds.height());
+        }
 
+        /**
+         * Walk the view hierarchy of each item and calculate mBoundsInRoot.
+         */
+        public void layoutItems() {
+            for (CustomItem item : mItems) {
+                layoutItem(item);
+            }
+        }
+
+        void layoutItem(CustomItem item) {
+            item.mBoundsInRoot = new RectF(item.mBounds);
+            CustomItem parent = item.mParent;
+            while (parent != null) {
+                RectF bounds = item.mBoundsInRoot;
+                item.mBoundsInRoot.set(parent.mBounds.left + bounds.left * parent.mBounds.width(),
+                        parent.mBounds.top + bounds.top * parent.mBounds.height(),
+                        parent.mBounds.left + bounds.right * parent.mBounds.width(),
+                        parent.mBounds.top + bounds.bottom * parent.mBounds.height());
+                parent = parent.mParent;
+            }
         }
 
         @Override
@@ -193,7 +216,7 @@
                     paint.setColor(item.mChecked ? Color.MAGENTA : Color.GREEN);
                 }
                 paint.setStyle(Style.FILL);
-                scaleRectF(item.mBounds, bounds, width, height);
+                scaleRectF(item.mBoundsInRoot, bounds, width, height);
                 canvas.drawRect(bounds, paint);
                 paint.setColor(Color.WHITE);
                 paint.setTextAlign(Align.CENTER);
@@ -232,7 +255,7 @@
             // Search in reverse order, so that topmost items are selected first.
             for (int i = n - 1; i >= 0; i--) {
                 final CustomItem item = mItems.get(i);
-                if (item.mBounds.contains(scaledX, scaledY)) {
+                if (item.mBoundsInRoot.contains(scaledX, scaledY)) {
                     return i;
                 }
             }
@@ -321,8 +344,12 @@
                 // hit detection performed in getVirtualViewAt() and
                 // onTouchEvent().
                 final Rect bounds = mTempRect;
-                final int height = getHeight();
-                final int width = getWidth();
+                int height = getHeight();
+                int width = getWidth();
+                if (item.mParent != null) {
+                    width = (int) (width * item.mParent.mBoundsInRoot.width());
+                    height = (int) (height * item.mParent.mBoundsInRoot.height());
+                }
                 scaleRectF(item.mBounds, bounds, width, height);
                 node.setBoundsInParent(bounds);
 
@@ -365,6 +392,7 @@
             private List<Integer> mChildren = new ArrayList<>();
             private String mDescription;
             private RectF mBounds;
+            private RectF mBoundsInRoot;
             private boolean mChecked;
         }
     }