Merge "Skip empty nodes when finding next focusable" into rvc-qpr-dev am: 720cf3c088
Original change: https://googleplex-android-review.googlesource.com/c/platform/packages/apps/Car/RotaryController/+/12551163
Change-Id: I5ffd7576651828dde63e87e65276f1f08b7dc319
diff --git a/src/com/android/car/rotary/Navigator.java b/src/com/android/car/rotary/Navigator.java
index 3219ddc..4c6b4e1 100644
--- a/src/com/android/car/rotary/Navigator.java
+++ b/src/com/android/car/rotary/Navigator.java
@@ -676,10 +676,11 @@
}
/**
- * Returns the previous node before {@code referenceNode} in Tab order or the next node after
- * {@code referenceNode} in Tab order, depending on {@code direction}. The search is limited to
- * descendants of {@code containerNode}. Returns null if there are no focusable descendants in
- * the given direction. The caller is responsible for recycling the result.
+ * Returns the previous node before {@code referenceNode} in Tab order that can take focus or
+ * the next node after {@code referenceNode} in Tab order that can take focus, depending on
+ * {@code direction}. The search is limited to descendants of {@code containerNode}. Returns
+ * null if there are no descendants that can take focus in the given direction. The caller is
+ * responsible for recycling the result.
*
* @param containerNode the node with descendants
* @param referenceNode a descendant of {@code containerNode} to start from
@@ -687,24 +688,30 @@
* @return the node before or after {@code referenceNode} or null if none
*/
@Nullable
- static AccessibilityNodeInfo findFocusableDescendantInDirection(
+ AccessibilityNodeInfo findFocusableDescendantInDirection(
@NonNull AccessibilityNodeInfo containerNode,
@NonNull AccessibilityNodeInfo referenceNode,
int direction) {
- AccessibilityNodeInfo targetNode = referenceNode.focusSearch(direction);
- if (targetNode == null
- || targetNode.equals(containerNode)
- || !Utils.canTakeFocus(targetNode)
- || !Utils.isDescendant(containerNode, targetNode)) {
- Utils.recycleNode(targetNode);
- return null;
- }
- if (targetNode.equals(referenceNode)) {
- L.w((direction == View.FOCUS_FORWARD ? "Next" : "Previous")
- + " node is the same node: " + referenceNode);
- Utils.recycleNode(targetNode);
- return null;
- }
+ AccessibilityNodeInfo targetNode = copyNode(referenceNode);
+ do {
+ AccessibilityNodeInfo nextTargetNode = targetNode.focusSearch(direction);
+ if (nextTargetNode == null
+ || nextTargetNode.equals(containerNode)
+ || !Utils.isDescendant(containerNode, nextTargetNode)) {
+ Utils.recycleNode(nextTargetNode);
+ Utils.recycleNode(targetNode);
+ return null;
+ }
+ if (nextTargetNode.equals(referenceNode) || nextTargetNode.equals(targetNode)) {
+ L.w((direction == View.FOCUS_FORWARD ? "Next" : "Previous")
+ + " node is the same node: " + referenceNode);
+ Utils.recycleNode(nextTargetNode);
+ Utils.recycleNode(targetNode);
+ return null;
+ }
+ targetNode.recycle();
+ targetNode = nextTargetNode;
+ } while (!Utils.canTakeFocus(targetNode));
return targetNode;
}
diff --git a/src/com/android/car/rotary/RotaryService.java b/src/com/android/car/rotary/RotaryService.java
index 89cb097..9fd5d14 100644
--- a/src/com/android/car/rotary/RotaryService.java
+++ b/src/com/android/car/rotary/RotaryService.java
@@ -907,7 +907,7 @@
if (mFocusedNode.equals(sourceNode)) {
break;
}
- AccessibilityNodeInfo target = Navigator.findFocusableDescendantInDirection(
+ AccessibilityNodeInfo target = mNavigator.findFocusableDescendantInDirection(
sourceNode, mFocusedNode,
mAfterScrollAction == AfterScrollAction.FOCUS_PREVIOUS
? View.FOCUS_BACKWARD
diff --git a/tests/robotests/src/com/android/car/rotary/NavigatorTest.java b/tests/robotests/src/com/android/car/rotary/NavigatorTest.java
index 4d5d76b..75bc6c3 100644
--- a/tests/robotests/src/com/android/car/rotary/NavigatorTest.java
+++ b/tests/robotests/src/com/android/car/rotary/NavigatorTest.java
@@ -2047,6 +2047,50 @@
}
/**
+ * Tests {@link Navigator#findNextFocusableDescendant} in the following node tree:
+ * <pre>
+ * root
+ * |
+ * |
+ * container
+ * / / \ \
+ * / / \ \
+ * button1 button2 button3 button4
+ * </pre>
+ * where {@code button3} and {@code button4} have empty bounds.
+ */
+ @Test
+ public void testFindNextFocusableDescendantWithEmptyBounds() {
+ AccessibilityNodeInfo root = mNodeBuilder.build();
+ AccessibilityNodeInfo container = mNodeBuilder.setParent(root).build();
+ AccessibilityNodeInfo button1 = mNodeBuilder.setParent(container).build();
+ AccessibilityNodeInfo button2 = mNodeBuilder.setParent(container).build();
+ AccessibilityNodeInfo button3 = mNodeBuilder.setParent(container)
+ .setBoundsInScreen(new Rect(5, 10, 5, 10)).build();
+ AccessibilityNodeInfo button4 = mNodeBuilder.setParent(container)
+ .setBoundsInScreen(new Rect(20, 40, 20, 40)).build();
+
+ int direction = View.FOCUS_FORWARD;
+ when(button1.focusSearch(direction)).thenReturn(button2);
+ when(button2.focusSearch(direction)).thenReturn(button3);
+ when(button3.focusSearch(direction)).thenReturn(button4);
+ when(button4.focusSearch(direction)).thenReturn(button1);
+
+ AccessibilityNodeInfo target = mNavigator.findFocusableDescendantInDirection(container,
+ button1, View.FOCUS_FORWARD);
+ assertThat(target).isSameAs(button2);
+ target = mNavigator.findFocusableDescendantInDirection(container, button2,
+ View.FOCUS_FORWARD);
+ assertThat(target).isSameAs(button1);
+ target = mNavigator.findFocusableDescendantInDirection(container, button3,
+ View.FOCUS_FORWARD);
+ assertThat(target).isSameAs(button1);
+ target = mNavigator.findFocusableDescendantInDirection(container, button4,
+ View.FOCUS_FORWARD);
+ assertThat(target).isSameAs(button1);
+ }
+
+ /**
* Tests {@link Navigator#findFirstFocusableDescendant} in the following node tree:
* <pre>
* root