Use the highlight padding when searching for target FocusArea

When searching for target FocusArea, subtract the highlight padding
from the bounds.
And revert the workaround introduced in ag/11729619 because we don't
need it any more.

Bug: 162114576
Bug: 157695058
Test: atest CarRotaryControllerRoboTests
Change-Id: I4095f8dc89848b0ed940e1d02d7a90f779c83ffd
diff --git a/src/com/android/car/rotary/FocusFinder.java b/src/com/android/car/rotary/FocusFinder.java
index fad9874..e99d388 100644
--- a/src/com/android/car/rotary/FocusFinder.java
+++ b/src/com/android/car/rotary/FocusFinder.java
@@ -68,8 +68,6 @@
      *  <li> and one of the following conditions must be true:
      *  <ul>
      *   <li> {@code destRect.right} is on the left of {@code srcRect.right}
-     *   <li> {@code destRect.right} equals {@code srcRect.right}, and {@code destRect} and {@code
-     *        srcRect} overlap in Y axis (an edge case)
      *   <li> {@code destRect.right} equals or is on the left of {@code srcRect.left} (an edge case
      *        for an empty {@code srcRect}, which is used in some cases when searching from a point
      *        on the screen)
@@ -84,27 +82,17 @@
     static boolean isCandidate(Rect srcRect, Rect destRect, int direction) {
         switch (direction) {
             case View.FOCUS_LEFT:
-                return srcRect.left > destRect.left
-                        && (srcRect.right > destRect.right
-                        || (srcRect.right == destRect.right && overlapOnYAxis(srcRect,
-                        destRect))
-                        || srcRect.left >= destRect.right);
+                return (srcRect.right > destRect.right || srcRect.left >= destRect.right)
+                        && srcRect.left > destRect.left;
             case View.FOCUS_RIGHT:
-                return srcRect.right < destRect.right
-                        && (srcRect.left < destRect.left
-                        || (srcRect.left == destRect.left && overlapOnYAxis(srcRect, destRect))
-                        || srcRect.right <= destRect.left);
+                return (srcRect.left < destRect.left || srcRect.right <= destRect.left)
+                        && srcRect.right < destRect.right;
             case View.FOCUS_UP:
-                return srcRect.top > destRect.top
-                        && (srcRect.bottom > destRect.bottom
-                        || (srcRect.bottom == destRect.bottom && overlapOnXAxis(srcRect,
-                        destRect))
-                        || srcRect.top >= destRect.bottom);
+                return (srcRect.bottom > destRect.bottom || srcRect.top >= destRect.bottom)
+                        && srcRect.top > destRect.top;
             case View.FOCUS_DOWN:
-                return srcRect.bottom < destRect.bottom
-                        && (srcRect.top < destRect.top
-                        || (srcRect.top == destRect.top && overlapOnXAxis(srcRect, destRect))
-                        || srcRect.bottom <= destRect.top);
+                return (srcRect.top < destRect.top || srcRect.bottom <= destRect.top)
+                        && srcRect.bottom < destRect.bottom;
         }
         throw new IllegalArgumentException("direction must be one of "
                 + "{FOCUS_UP, FOCUS_DOWN, FOCUS_LEFT, FOCUS_RIGHT}.");
@@ -296,20 +284,4 @@
         throw new IllegalArgumentException("direction must be one of "
                 + "{FOCUS_UP, FOCUS_DOWN, FOCUS_LEFT, FOCUS_RIGHT}.");
     }
-
-    /**
-     * Projects {@code rect1} and {@code rect2} onto Y axis, and returns whether the two projected
-     * intervals overlap. The overlap length must be > 0, otherwise it's not considered overlap.
-     */
-    private static boolean overlapOnYAxis(Rect rect1, Rect rect2) {
-        return rect1.bottom > rect2.top && rect1.top < rect2.bottom;
-    }
-
-    /**
-     * Projects {@code rect1} and {@code rect2} onto X axis, and returns whether the two projected
-     * intervals overlap. The overlap length must be > 0, otherwise it's not considered overlap.
-     */
-    private static boolean overlapOnXAxis(Rect rect1, Rect rect2) {
-        return rect1.left < rect2.right && rect1.right > rect2.left;
-    }
 }
diff --git a/src/com/android/car/rotary/Navigator.java b/src/com/android/car/rotary/Navigator.java
index 708d2aa..1761605 100644
--- a/src/com/android/car/rotary/Navigator.java
+++ b/src/com/android/car/rotary/Navigator.java
@@ -18,7 +18,13 @@
 import static android.view.accessibility.AccessibilityNodeInfo.AccessibilityAction.ACTION_SCROLL_BACKWARD;
 import static android.view.accessibility.AccessibilityNodeInfo.AccessibilityAction.ACTION_SCROLL_FORWARD;
 
+import static com.android.car.ui.utils.RotaryConstants.FOCUS_AREA_HIGHLIGHT_BOTTOM_PADDING;
+import static com.android.car.ui.utils.RotaryConstants.FOCUS_AREA_HIGHLIGHT_LEFT_PADDING;
+import static com.android.car.ui.utils.RotaryConstants.FOCUS_AREA_HIGHLIGHT_RIGHT_PADDING;
+import static com.android.car.ui.utils.RotaryConstants.FOCUS_AREA_HIGHLIGHT_TOP_PADDING;
+
 import android.graphics.Rect;
+import android.os.Bundle;
 import android.os.SystemClock;
 import android.view.View;
 import android.view.accessibility.AccessibilityNodeInfo;
@@ -353,8 +359,7 @@
                 // If we're navigating through a scrolling view that can scroll in the specified
                 // direction and the next view is off-screen, don't advance to it. (We'll scroll
                 // the remaining count instead.)
-                Rect nextTargetBounds = new Rect();
-                nextCandidate.getBoundsInScreen(nextTargetBounds);
+                Rect nextTargetBounds = getBoundsInScreen(nextCandidate);
                 AccessibilityNodeInfo scrollableContainer = findScrollableContainer(candidate);
                 AccessibilityNodeInfo.AccessibilityAction scrollAction =
                         direction == View.FOCUS_FORWARD
@@ -362,8 +367,7 @@
                                 : ACTION_SCROLL_BACKWARD;
                 if (scrollableContainer != null
                         && scrollableContainer.getActionList().contains(scrollAction)) {
-                    Rect scrollBounds = new Rect();
-                    scrollableContainer.getBoundsInScreen(scrollBounds);
+                    Rect scrollBounds = getBoundsInScreen(scrollableContainer);
                     boolean intersects = nextTargetBounds.intersect(scrollBounds);
                     if (!intersects) {
                         Utils.recycleNode(nextCandidate);
@@ -678,7 +682,7 @@
      *
      * @param containerNode the node with descendants
      * @param referenceNode a descendant of {@code containerNode} to start from
-     * @param direction {@link View#FOCUS_FORWARD} or {@link View#FOCUS_BACKWARD}
+     * @param direction     {@link View#FOCUS_FORWARD} or {@link View#FOCUS_BACKWARD}
      * @return the node before or after {@code referenceNode} or null if none
      */
     @Nullable
@@ -763,16 +767,13 @@
         if (candidates.isEmpty()) {
             return null;
         }
-        Rect sourceBounds = new Rect();
-        sourceNode.getBoundsInScreen(sourceBounds);
-
+        Rect sourceBounds = getBoundsInScreen(sourceNode);
         AccessibilityNodeInfo bestNode = null;
         Rect bestBounds = new Rect();
 
-        Rect candidateBounds = new Rect();
         for (AccessibilityNodeInfo candidate : candidates) {
             if (isCandidate(sourceBounds, candidate, direction)) {
-                candidate.getBoundsInScreen(candidateBounds);
+                Rect candidateBounds = getBoundsInScreen(candidate);
                 if (bestNode == null || FocusFinder.isBetterCandidate(
                         direction, sourceBounds, candidateBounds, bestBounds)) {
                     bestNode = candidate;
@@ -855,4 +856,19 @@
             this.advancedCount = advancedCount;
         }
     }
+
+    @NonNull
+    private static Rect getBoundsInScreen(@NonNull AccessibilityNodeInfo node) {
+        Rect bounds = new Rect();
+        node.getBoundsInScreen(bounds);
+        if (Utils.isFocusArea(node)) {
+            // The bounds of a FocusArea is its bounds minus its highlight paddings.
+            Bundle bundle = node.getExtras();
+            bounds.left += bundle.getInt(FOCUS_AREA_HIGHLIGHT_LEFT_PADDING);
+            bounds.right -= bundle.getInt(FOCUS_AREA_HIGHLIGHT_RIGHT_PADDING);
+            bounds.top += bundle.getInt(FOCUS_AREA_HIGHLIGHT_TOP_PADDING);
+            bounds.bottom -= bundle.getInt(FOCUS_AREA_HIGHLIGHT_BOTTOM_PADDING);
+        }
+        return bounds;
+    }
 }
diff --git a/tests/robotests/src/com/android/car/rotary/FocusFinderTest.java b/tests/robotests/src/com/android/car/rotary/FocusFinderTest.java
index c95df14..9d154c9 100755
--- a/tests/robotests/src/com/android/car/rotary/FocusFinderTest.java
+++ b/tests/robotests/src/com/android/car/rotary/FocusFinderTest.java
@@ -79,24 +79,6 @@
     }
 
     @Test
-    public void testLeftOverlapNotCandidateForRightIfTheyDoNotOverlap() {
-        assertIsNotCandidate(
-                View.FOCUS_RIGHT,
-                //       L  T   R   B
-                new Rect(0, 50, 50, 100),
-                new Rect(0, 0, 100, 20));
-    }
-
-    @Test
-    public void testLeftOverlapIsCandidateForRightIfTheyOverlapVertically() {
-        assertDirectionIsCandidate(
-                View.FOCUS_RIGHT,
-                //       L  T   R   B
-                new Rect(0, 50, 50, 100),
-                new Rect(0, 50, 100, 100));
-    }
-
-    @Test
     public void testOverlappingIsCandidateWhenBothEdgesAreInDirection() {
         assertDirectionIsCandidate(
                 View.FOCUS_DOWN,
@@ -106,16 +88,13 @@
     }
 
     @Test
-    public void testTopEdgeOfDestAtTopOfSrcIsCandidateForDown() {
-        assertDirectionIsCandidate(
+    public void testTopEdgeOfDestAtOrAboveTopOfSrcNotCandidateForDown() {
+        assertIsNotCandidate(
                 View.FOCUS_DOWN,
                 //       L  T   R   B
                 new Rect(0, 0, 50, 50),
                 new Rect(0, 0, 50, 51));
-    }
 
-    @Test
-    public void testTopEdgeOfDestAboveTopOfSrcNotCandidateForDown() {
         assertIsNotCandidate(
                 View.FOCUS_DOWN,
                 //       L  T   R   B
diff --git a/tests/robotests/src/com/android/car/rotary/NavigatorTest.java b/tests/robotests/src/com/android/car/rotary/NavigatorTest.java
index 8e58b95..0808a67 100644
--- a/tests/robotests/src/com/android/car/rotary/NavigatorTest.java
+++ b/tests/robotests/src/com/android/car/rotary/NavigatorTest.java
@@ -1285,6 +1285,88 @@
     }
 
     /**
+     * Tests {@link Navigator#findNudgeTarget} in the following layout:
+     * <pre>
+     * In the same window
+     *
+     *          ==========focus area 1============
+     *          =  .......highlight............  =
+     *          =  .        *view1*           .  =
+     *          =  .......paddings.............  =
+     *          =                                =
+     *          =  ========focus area 2========  =
+     *          =  =         *view2*          =  =
+     *          =  ============================  =
+     *          =                                =
+     *          =                                =
+     *          =                                =
+     *          =                                =
+     *          ==================================
+     *
+     *          ===========focus area 3===========
+     *          =            *view3*             =
+     *          ==================================
+     * </pre>
+     */
+    @Test
+    public void testFindNudgeTargetWithFocusAreaHighlightPadding() {
+        Rect windowBounds = new Rect(0, 0, 100, 100);
+        AccessibilityWindowInfo window = new WindowBuilder()
+                .setBoundsInScreen(windowBounds)
+                .build();
+        AccessibilityNodeInfo root = mNodeBuilder
+                .setWindow(window)
+                .setBoundsInScreen(windowBounds)
+                .build();
+        setRootNodeForWindow(root, window);
+
+        AccessibilityNodeInfo focusArea1 = mNodeBuilder
+                .setWindow(window)
+                .setParent(root)
+                .setFocusArea()
+                .setFocusAreaHighlightPadding(0, 0, 0, 70)
+                .setBoundsInScreen(new Rect(0, 0, 100, 80))
+                .build();
+        AccessibilityNodeInfo view1 = mNodeBuilder
+                .setWindow(window)
+                .setParent(focusArea1)
+                .setBoundsInScreen(new Rect(0, 0, 100, 10))
+                .build();
+
+        AccessibilityNodeInfo focusArea2 = mNodeBuilder
+                .setWindow(window)
+                .setParent(root)
+                .setFocusArea()
+                .setBoundsInScreen(new Rect(0, 10, 100, 20))
+                .build();
+        AccessibilityNodeInfo view2 = mNodeBuilder
+                .setWindow(window)
+                .setParent(focusArea2)
+                .setBoundsInScreen(new Rect(0, 10, 100, 20))
+                .build();
+
+        AccessibilityNodeInfo focusArea3 = mNodeBuilder
+                .setWindow(window)
+                .setParent(root)
+                .setFocusArea()
+                .setBoundsInScreen(new Rect(0, 90, 100, 100))
+                .build();
+        AccessibilityNodeInfo view3 = mNodeBuilder
+                .setWindow(window)
+                .setParent(focusArea3)
+                .setBoundsInScreen(new Rect(0, 90, 100, 100))
+                .build();
+
+        List<AccessibilityWindowInfo> windows = new ArrayList<>();
+        windows.add(window);
+
+        // Nudge up from view3, it should go to view2.
+        AccessibilityNodeInfo target
+                = mNavigator.findNudgeTarget(windows, view3, View.FOCUS_UP);
+        assertThat(target).isSameAs(view2);
+    }
+
+    /**
      * Tests {@link Navigator#findFirstFocusDescendant} in the following node tree:
      * <pre>
      *                   root
diff --git a/tests/robotests/src/com/android/car/rotary/NodeBuilder.java b/tests/robotests/src/com/android/car/rotary/NodeBuilder.java
index 61230d9..72a7549 100644
--- a/tests/robotests/src/com/android/car/rotary/NodeBuilder.java
+++ b/tests/robotests/src/com/android/car/rotary/NodeBuilder.java
@@ -19,6 +19,10 @@
 
 import static com.android.car.rotary.Utils.FOCUS_AREA_CLASS_NAME;
 import static com.android.car.rotary.Utils.FOCUS_PARKING_VIEW_CLASS_NAME;
+import static com.android.car.ui.utils.RotaryConstants.FOCUS_AREA_HIGHLIGHT_BOTTOM_PADDING;
+import static com.android.car.ui.utils.RotaryConstants.FOCUS_AREA_HIGHLIGHT_LEFT_PADDING;
+import static com.android.car.ui.utils.RotaryConstants.FOCUS_AREA_HIGHLIGHT_RIGHT_PADDING;
+import static com.android.car.ui.utils.RotaryConstants.FOCUS_AREA_HIGHLIGHT_TOP_PADDING;
 import static com.android.car.ui.utils.RotaryConstants.ROTARY_VERTICALLY_SCROLLABLE;
 
 import static org.mockito.Mockito.any;
@@ -27,6 +31,7 @@
 import static org.mockito.Mockito.when;
 
 import android.graphics.Rect;
+import android.os.Bundle;
 import android.view.accessibility.AccessibilityNodeInfo;
 import android.view.accessibility.AccessibilityWindowInfo;
 
@@ -78,6 +83,9 @@
     /** The action list for this node. */
     @NonNull
     private List<AccessibilityNodeInfo.AccessibilityAction> mActionList = new ArrayList<>();
+    /** The extras of this node. */
+    @NonNull
+    private Bundle mExtras = new Bundle();
 
     NodeBuilder(@NonNull List<AccessibilityNodeInfo> nodeList) {
         mNodeList = nodeList;
@@ -85,9 +93,7 @@
 
     AccessibilityNodeInfo build() {
         // Make a copy of the current NodeBuilder.
-        NodeBuilder builder = copy();
-        // Clear the state of the current NodeBuilder so that it can be reused.
-        clear();
+        NodeBuilder builder = cut();
 
         AccessibilityNodeInfo node = mock(AccessibilityNodeInfo.class);
 
@@ -141,6 +147,7 @@
         when(node.refresh()).thenReturn(builder.mInViewTree);
         when(node.getContentDescription()).thenReturn(builder.mContentDescription);
         when(node.getActionList()).thenReturn(builder.mActionList);
+        when(node.getExtras()).thenReturn(builder.mExtras);
 
         builder.mNodeList.add(node);
         return node;
@@ -205,6 +212,14 @@
         return setClassName(FOCUS_AREA_CLASS_NAME).setFocusable(false);
     }
 
+    NodeBuilder setFocusAreaHighlightPadding(int left, int top, int right, int bottom) {
+        mExtras.putInt(FOCUS_AREA_HIGHLIGHT_LEFT_PADDING, left);
+        mExtras.putInt(FOCUS_AREA_HIGHLIGHT_TOP_PADDING, top);
+        mExtras.putInt(FOCUS_AREA_HIGHLIGHT_RIGHT_PADDING, right);
+        mExtras.putInt(FOCUS_AREA_HIGHLIGHT_BOTTOM_PADDING, bottom);
+        return this;
+    }
+
     NodeBuilder setFpv() {
         return setClassName(FOCUS_PARKING_VIEW_CLASS_NAME);
     }
@@ -213,8 +228,12 @@
         return setContentDescription(ROTARY_VERTICALLY_SCROLLABLE);
     }
 
-    /** Creates a NodeBuilder with the same states. */
-    private NodeBuilder copy() {
+    /**
+     * Creates a copy of the current NodeBuilder, and clears the states of the current NodeBuilder
+     * except for {@link #mNodeList}.
+     */
+    private NodeBuilder cut() {
+        // Create a copy.
         NodeBuilder copy = new NodeBuilder(this.mNodeList);
         copy.mWindow = mWindow;
         copy.mWindowId = mWindowId;
@@ -227,11 +246,9 @@
         copy.mInViewTree = mInViewTree;
         copy.mContentDescription = mContentDescription;
         copy.mActionList = mActionList;
-        return copy;
-    }
+        copy.mExtras = mExtras;
 
-    /** Clears all the states but {@link #mNodeList}. */
-    private void clear() {
+        // Clear the states so that it doesn't infect the next NodeBuilder we create.
         mWindow = null;
         mWindowId = UNDEFINED_WINDOW_ID;
         mParent = null;
@@ -243,5 +260,8 @@
         mInViewTree = true;
         mContentDescription = null;
         mActionList = new ArrayList<>();
+        mExtras = new Bundle();
+
+        return copy;
     }
 }
diff --git a/tests/robotests/src/com/android/car/rotary/NodeBuilderTest.java b/tests/robotests/src/com/android/car/rotary/NodeBuilderTest.java
index da9ce7c..7f5669f 100644
--- a/tests/robotests/src/com/android/car/rotary/NodeBuilderTest.java
+++ b/tests/robotests/src/com/android/car/rotary/NodeBuilderTest.java
@@ -19,11 +19,16 @@
 
 import static com.android.car.rotary.Utils.FOCUS_AREA_CLASS_NAME;
 import static com.android.car.rotary.Utils.FOCUS_PARKING_VIEW_CLASS_NAME;
+import static com.android.car.ui.utils.RotaryConstants.FOCUS_AREA_HIGHLIGHT_BOTTOM_PADDING;
+import static com.android.car.ui.utils.RotaryConstants.FOCUS_AREA_HIGHLIGHT_LEFT_PADDING;
+import static com.android.car.ui.utils.RotaryConstants.FOCUS_AREA_HIGHLIGHT_RIGHT_PADDING;
+import static com.android.car.ui.utils.RotaryConstants.FOCUS_AREA_HIGHLIGHT_TOP_PADDING;
 import static com.android.car.ui.utils.RotaryConstants.ROTARY_VERTICALLY_SCROLLABLE;
 
 import static com.google.common.truth.Truth.assertThat;
 
 import android.graphics.Rect;
+import android.os.Bundle;
 import android.view.accessibility.AccessibilityNodeInfo;
 import android.view.accessibility.AccessibilityWindowInfo;
 
@@ -136,6 +141,22 @@
     }
 
     @Test
+    public void testSetFocusAreaHighlightPadding() {
+        int left = 10;
+        int top = 20;
+        int right = 30;
+        int bottom = 40;
+        AccessibilityNodeInfo node = mNodeBuilder
+                .setFocusAreaHighlightPadding(left, top, right, bottom)
+                .build();
+        Bundle extras = node.getExtras();
+        assertThat(extras.getInt(FOCUS_AREA_HIGHLIGHT_LEFT_PADDING)).isEqualTo(left);
+        assertThat(extras.getInt(FOCUS_AREA_HIGHLIGHT_TOP_PADDING)).isEqualTo(top);
+        assertThat(extras.getInt(FOCUS_AREA_HIGHLIGHT_RIGHT_PADDING)).isEqualTo(right);
+        assertThat(extras.getInt(FOCUS_AREA_HIGHLIGHT_BOTTOM_PADDING)).isEqualTo(bottom);
+    }
+
+    @Test
     public void testSetFpv() {
         AccessibilityNodeInfo node = mNodeBuilder.setFpv().build();
         assertThat(node.getClassName().toString()).isEqualTo(FOCUS_PARKING_VIEW_CLASS_NAME);