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);