Don't focus on the recyclerview via rotate
Fixes: 160924453
Test: maual test and atest CarRotaryControllerRoboTests
Change-Id: I61ae6ad4158e85972991effc94ed98af92d41257
diff --git a/src/com/android/car/rotary/Navigator.java b/src/com/android/car/rotary/Navigator.java
index 56c5dd7..81a53d0 100644
--- a/src/com/android/car/rotary/Navigator.java
+++ b/src/com/android/car/rotary/Navigator.java
@@ -318,17 +318,18 @@
@NonNull AccessibilityNodeInfo sourceNode, int direction, int rotationCount) {
int advancedCount = 0;
AccessibilityNodeInfo currentFocusArea = getAncestorFocusArea(sourceNode);
- AccessibilityNodeInfo targetNode = copyNode(sourceNode);
- for (int i = 0; i < rotationCount; i++) {
- AccessibilityNodeInfo nextTargetNode = targetNode.focusSearch(direction);
- AccessibilityNodeInfo targetFocusArea =
- nextTargetNode == null ? null : getAncestorFocusArea(nextTargetNode);
+ AccessibilityNodeInfo candidate = copyNode(sourceNode);
+ AccessibilityNodeInfo target = null;
+ while (advancedCount < rotationCount) {
+ AccessibilityNodeInfo nextCandidate = candidate.focusSearch(direction);
+ AccessibilityNodeInfo candidateFocusArea =
+ nextCandidate == null ? null : getAncestorFocusArea(nextCandidate);
- // Only advance to nextTargetNode if it's in the same focus area and it isn't a
+ // Only advance to nextCandidate if it's in the same focus area and it isn't a
// FocusParkingView. The second condition prevents wrap-around when there is only one
// focus area in the window, including when the root node is treated as a focus area.
- if (nextTargetNode != null && currentFocusArea.equals(targetFocusArea)
- && !Utils.isFocusParkingView(nextTargetNode)) {
+ if (nextCandidate != null && currentFocusArea.equals(candidateFocusArea)
+ && !Utils.isFocusParkingView(nextCandidate)) {
// We need to skip nextTargetNode if:
// 1. it can't perform focus action (focusSearch() may return a node with zero
// width and height),
@@ -336,21 +337,20 @@
// the container because we want to focus on its element directly. We don't skip
// a scrollable container without descendants that can take focus because we want
// to focus on it, thus we can scroll it when the rotary controller is rotated.
- if (!Utils.canPerformFocus(nextTargetNode)
- || (Utils.isScrollableContainer(nextTargetNode)
- && Utils.descendantCanTakeFocus(nextTargetNode))) {
- Utils.recycleNode(targetNode);
- Utils.recycleNode(targetFocusArea);
- targetNode = nextTargetNode;
- --i;
+ if (!Utils.canPerformFocus(nextCandidate)
+ || (Utils.isScrollableContainer(nextCandidate)
+ && Utils.descendantCanTakeFocus(nextCandidate))) {
+ Utils.recycleNode(candidate);
+ Utils.recycleNode(candidateFocusArea);
+ candidate = nextCandidate;
continue;
}
// 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
- // instead.)
+ // the remaining count instead.)
Rect nextTargetBounds = new Rect();
- nextTargetNode.getBoundsInScreen(nextTargetBounds);
- AccessibilityNodeInfo scrollableContainer = findScrollableContainer(targetNode);
+ nextCandidate.getBoundsInScreen(nextTargetBounds);
+ AccessibilityNodeInfo scrollableContainer = findScrollableContainer(candidate);
AccessibilityNodeInfo.AccessibilityAction scrollAction =
direction == View.FOCUS_FORWARD
? ACTION_SCROLL_FORWARD
@@ -361,29 +361,33 @@
scrollableContainer.getBoundsInScreen(scrollBounds);
boolean intersects = nextTargetBounds.intersect(scrollBounds);
if (!intersects) {
- Utils.recycleNode(nextTargetNode);
- Utils.recycleNode(targetFocusArea);
+ Utils.recycleNode(nextCandidate);
+ Utils.recycleNode(candidateFocusArea);
break;
}
}
Utils.recycleNode(scrollableContainer);
- Utils.recycleNode(targetNode);
- Utils.recycleNode(targetFocusArea);
- targetNode = nextTargetNode;
+ Utils.recycleNode(candidate);
+ Utils.recycleNode(candidateFocusArea);
+ candidate = nextCandidate;
+ Utils.recycleNode(target);
+ target = copyNode(candidate);
advancedCount++;
} else {
- Utils.recycleNode(nextTargetNode);
- Utils.recycleNode(targetFocusArea);
+ Utils.recycleNode(nextCandidate);
+ Utils.recycleNode(candidateFocusArea);
break;
}
}
- Utils.recycleNode(currentFocusArea);
- if (sourceNode.equals(targetNode)) {
- targetNode.recycle();
+ currentFocusArea.recycle();
+ candidate.recycle();
+ if (sourceNode.equals(target)) {
+ L.e("Wrap-around on the same node");
+ target.recycle();
return null;
}
- return new FindRotateTargetResult(targetNode, advancedCount);
+ return target == null ? null : new FindRotateTargetResult(target, advancedCount);
}
/**
diff --git a/tests/robotests/src/com/android/car/rotary/NavigatorTest.java b/tests/robotests/src/com/android/car/rotary/NavigatorTest.java
index 650bba7..c89482f 100644
--- a/tests/robotests/src/com/android/car/rotary/NavigatorTest.java
+++ b/tests/robotests/src/com/android/car/rotary/NavigatorTest.java
@@ -270,6 +270,39 @@
}
/**
+ * Tests {@link Navigator#findRotateTarget} in the following node tree:
+ * <pre>
+ * root
+ * / \
+ * / \
+ * focusParkingView scrollableContainer
+ * / \
+ * / \
+ * focusable1 focusable2
+ * </pre>
+ */
+ @Test
+ public void testFindRotateTargetSkipScrollableContainer2() {
+ AccessibilityNodeInfo root = mNodeBuilder.build();
+ AccessibilityNodeInfo focusParkingView = mNodeBuilder.setParent(root).setFpv().build();
+ AccessibilityNodeInfo scrollableContainer = mNodeBuilder
+ .setParent(root)
+ .setScrollableContainer()
+ .build();
+ AccessibilityNodeInfo focusable1 = mNodeBuilder.setParent(scrollableContainer).build();
+ AccessibilityNodeInfo focusable2 = mNodeBuilder.setParent(scrollableContainer).build();
+
+ int direction = View.FOCUS_BACKWARD;
+ when(focusable2.focusSearch(direction)).thenReturn(focusable1);
+ when(focusable1.focusSearch(direction)).thenReturn(scrollableContainer);
+ when(scrollableContainer.focusSearch(direction)).thenReturn(focusParkingView);
+
+ FindRotateTargetResult target = mNavigator.findRotateTarget(focusable2, direction, 2);
+ assertThat(target.node).isSameAs(focusable1);
+ assertThat(target.advancedCount).isEqualTo(1);
+ }
+
+ /**
* Tests {@link Navigator#findRotateTarget} in the following layout:
* <pre>
* ============ focus area ============