Tests to check focus avoiding middle of chains.
Bug 24873983
Focus follows top->bottom left->right by default (LTR),
but that could put next focus on a View in the middle
of a focus chain (something else points to the View with
nextFocusForwardId). That means that the View pointing
to it may get skipped or it may end in a focus loop
in the chain.
Instead, we now prefer to have the next view be one that
is at the head of a chain (forward) or tail of a chain
(backward).
Change-Id: Ieefc2cc2238b9368fc75644f0b4b5f4c3afe1ed4
diff --git a/tests/tests/view/src/android/view/cts/FocusFinderTest.java b/tests/tests/view/src/android/view/cts/FocusFinderTest.java
index 7f47037..6b3b784 100644
--- a/tests/tests/view/src/android/view/cts/FocusFinderTest.java
+++ b/tests/tests/view/src/android/view/cts/FocusFinderTest.java
@@ -46,6 +46,10 @@
mTopRight = getActivity().topRightButton;
mBottomLeft = getActivity().bottomLeftButton;
mBottomRight = getActivity().bottomRightButton;
+ mTopLeft.setNextFocusLeftId(View.NO_ID);
+ mTopRight.setNextFocusLeftId(View.NO_ID);
+ mBottomLeft.setNextFocusLeftId(View.NO_ID);
+ mBottomRight.setNextFocusLeftId(View.NO_ID);
}
public void testGetInstance() {
@@ -169,4 +173,50 @@
assertEquals(0, deltas[0]);
assertEquals(-1, deltas[1]);
}
+
+ public void testFindNextAndPrevFocusAvoidingChain() {
+ mBottomRight.setNextFocusForwardId(mBottomLeft.getId());
+ mBottomLeft.setNextFocusForwardId(mTopRight.getId());
+ // Follow the chain
+ assertNextFocus(mBottomRight, View.FOCUS_FORWARD, mBottomLeft);
+ assertNextFocus(mBottomLeft, View.FOCUS_FORWARD, mTopRight);
+ assertNextFocus(mTopRight, View.FOCUS_BACKWARD, mBottomLeft);
+ assertNextFocus(mBottomLeft, View.FOCUS_BACKWARD, mBottomRight);
+
+ // Now go to the one not in the chain
+ assertNextFocus(mTopRight, View.FOCUS_FORWARD, mTopLeft);
+ assertNextFocus(mBottomRight, View.FOCUS_BACKWARD, mTopLeft);
+
+ // Now go back to the top of the chain
+ assertNextFocus(mTopLeft, View.FOCUS_FORWARD, mBottomRight);
+ assertNextFocus(mTopLeft, View.FOCUS_BACKWARD, mTopRight);
+
+ // Now make the chain a circle -- this is the pathological case
+ mTopRight.setNextFocusForwardId(mBottomRight.getId());
+ // Fall back to the next one in a chain.
+ assertNextFocus(mTopLeft, View.FOCUS_FORWARD, mTopRight);
+ assertNextFocus(mTopLeft, View.FOCUS_BACKWARD, mBottomRight);
+
+ //Now do branching focus changes
+ mTopRight.setNextFocusForwardId(View.NO_ID);
+ mBottomRight.setNextFocusForwardId(mTopRight.getId());
+ assertNextFocus(mBottomRight, View.FOCUS_FORWARD, mTopRight);
+ assertNextFocus(mBottomLeft, View.FOCUS_FORWARD, mTopRight);
+ // From the tail, it jumps out of the chain
+ assertNextFocus(mTopRight, View.FOCUS_FORWARD, mTopLeft);
+
+ // Back from the head of a tree goes out of the tree
+ // We don't know which is the head of the focus chain since it is branching.
+ View prevFocus1 = mFocusFinder.findNextFocus(mLayout, mBottomLeft, View.FOCUS_BACKWARD);
+ View prevFocus2 = mFocusFinder.findNextFocus(mLayout, mBottomRight, View.FOCUS_BACKWARD);
+ assertTrue(prevFocus1 == mTopLeft || prevFocus2 == mTopLeft);
+
+ // From outside, it chooses an arbitrary head of the chain
+ View nextFocus = mFocusFinder.findNextFocus(mLayout, mTopLeft, View.FOCUS_FORWARD);
+ assertTrue(nextFocus == mBottomRight || nextFocus == mBottomLeft);
+
+ // Going back from the tail of the split chain, it chooses an arbitrary head
+ nextFocus = mFocusFinder.findNextFocus(mLayout, mTopRight, View.FOCUS_BACKWARD);
+ assertTrue(nextFocus == mBottomRight || nextFocus == mBottomLeft);
+ }
}