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