Add initial support for TAB navigation.

Bug: 3286652
Change-Id: I813a0318b3b8d9c9bc791ea6a2427be11c08de00
diff --git a/core/java/android/view/View.java b/core/java/android/view/View.java
index 75aebc9..71d07c6 100644
--- a/core/java/android/view/View.java
+++ b/core/java/android/view/View.java
@@ -64,6 +64,7 @@
 import android.view.inputmethod.InputMethodManager;
 import android.widget.ScrollBarDrawable;
 import com.android.internal.R;
+import com.android.internal.util.Predicate;
 import com.android.internal.view.menu.MenuBuilder;
 
 import java.lang.ref.WeakReference;
@@ -2066,6 +2067,12 @@
      */
     private int mNextFocusDownId = View.NO_ID;
 
+    /**
+     * When this view has focus and the next focus is {@link #FOCUS_FORWARD},
+     * the user may specify which view to go to next.
+     */
+    int mNextFocusForwardId = View.NO_ID;
+
     private CheckForLongPress mPendingCheckForLongPress;
     private CheckForTap mPendingCheckForTap = null;
     private PerformClick mPerformClick;
@@ -2424,6 +2431,9 @@
                 case R.styleable.View_nextFocusDown:
                     mNextFocusDownId = a.getResourceId(attr, View.NO_ID);
                     break;
+                case R.styleable.View_nextFocusForward:
+                    mNextFocusForwardId = a.getResourceId(attr, View.NO_ID);
+                    break;
                 case R.styleable.View_minWidth:
                     mMinWidth = a.getDimensionPixelSize(attr, 0);
                     break;
@@ -3112,9 +3122,9 @@
      * @param gainFocus True if the View has focus; false otherwise.
      * @param direction The direction focus has moved when requestFocus()
      *                  is called to give this view focus. Values are
-     *                  {@link #FOCUS_UP}, {@link #FOCUS_DOWN}, {@link #FOCUS_LEFT} or
-     *                  {@link #FOCUS_RIGHT}. It may not always apply, in which
-     *                  case use the default.
+     *                  {@link #FOCUS_UP}, {@link #FOCUS_DOWN}, {@link #FOCUS_LEFT},
+     *                  {@link #FOCUS_RIGHT}, {@link #FOCUS_FORWARD}, or {@link #FOCUS_BACKWARD}.
+     *                  It may not always apply, in which case use the default.
      * @param previouslyFocusedRect The rectangle, in this view's coordinate
      *        system, of the previously focused view.  If applicable, this will be
      *        passed in as finer grained information about where the focus is coming
@@ -3359,7 +3369,8 @@
     }
 
     /**
-     * @return The user specified next focus ID.
+     * Gets the id of the view to use when the next focus is {@link #FOCUS_LEFT}.
+     * @return The next focus ID, or {@link #NO_ID} if the framework should decide automatically.
      *
      * @attr ref android.R.styleable#View_nextFocusLeft
      */
@@ -3368,9 +3379,9 @@
     }
 
     /**
-     * Set the id of the view to use for the next focus
-     *
-     * @param nextFocusLeftId
+     * Sets the id of the view to use when the next focus is {@link #FOCUS_LEFT}.
+     * @param nextFocusLeftId The next focus ID, or {@link #NO_ID} if the framework should
+     * decide automatically.
      *
      * @attr ref android.R.styleable#View_nextFocusLeft
      */
@@ -3379,7 +3390,8 @@
     }
 
     /**
-     * @return The user specified next focus ID.
+     * Gets the id of the view to use when the next focus is {@link #FOCUS_RIGHT}.
+     * @return The next focus ID, or {@link #NO_ID} if the framework should decide automatically.
      *
      * @attr ref android.R.styleable#View_nextFocusRight
      */
@@ -3388,9 +3400,9 @@
     }
 
     /**
-     * Set the id of the view to use for the next focus
-     *
-     * @param nextFocusRightId
+     * Sets the id of the view to use when the next focus is {@link #FOCUS_RIGHT}.
+     * @param nextFocusRightId The next focus ID, or {@link #NO_ID} if the framework should
+     * decide automatically.
      *
      * @attr ref android.R.styleable#View_nextFocusRight
      */
@@ -3399,7 +3411,8 @@
     }
 
     /**
-     * @return The user specified next focus ID.
+     * Gets the id of the view to use when the next focus is {@link #FOCUS_UP}.
+     * @return The next focus ID, or {@link #NO_ID} if the framework should decide automatically.
      *
      * @attr ref android.R.styleable#View_nextFocusUp
      */
@@ -3408,9 +3421,9 @@
     }
 
     /**
-     * Set the id of the view to use for the next focus
-     *
-     * @param nextFocusUpId
+     * Sets the id of the view to use when the next focus is {@link #FOCUS_UP}.
+     * @param nextFocusUpId The next focus ID, or {@link #NO_ID} if the framework should
+     * decide automatically.
      *
      * @attr ref android.R.styleable#View_nextFocusUp
      */
@@ -3419,7 +3432,8 @@
     }
 
     /**
-     * @return The user specified next focus ID.
+     * Gets the id of the view to use when the next focus is {@link #FOCUS_DOWN}.
+     * @return The next focus ID, or {@link #NO_ID} if the framework should decide automatically.
      *
      * @attr ref android.R.styleable#View_nextFocusDown
      */
@@ -3428,9 +3442,9 @@
     }
 
     /**
-     * Set the id of the view to use for the next focus
-     *
-     * @param nextFocusDownId
+     * Sets the id of the view to use when the next focus is {@link #FOCUS_DOWN}.
+     * @param nextFocusDownId The next focus ID, or {@link #NO_ID} if the framework should
+     * decide automatically.
      *
      * @attr ref android.R.styleable#View_nextFocusDown
      */
@@ -3439,6 +3453,27 @@
     }
 
     /**
+     * Gets the id of the view to use when the next focus is {@link #FOCUS_FORWARD}.
+     * @return The next focus ID, or {@link #NO_ID} if the framework should decide automatically.
+     *
+     * @attr ref android.R.styleable#View_nextFocusForward
+     */
+    public int getNextFocusForwardId() {
+        return mNextFocusForwardId;
+    }
+
+    /**
+     * Sets the id of the view to use when the next focus is {@link #FOCUS_FORWARD}.
+     * @param nextFocusForwardId The next focus ID, or {@link #NO_ID} if the framework should
+     * decide automatically.
+     *
+     * @attr ref android.R.styleable#View_nextFocusForward
+     */
+    public void setNextFocusForwardId(int nextFocusForwardId) {
+        mNextFocusForwardId = nextFocusForwardId;
+    }
+
+    /**
      * Returns the visibility of this view and all of its ancestors
      *
      * @return True if this view and all of its ancestors are {@link #VISIBLE}
@@ -3949,10 +3984,10 @@
 
     /**
      * If a user manually specified the next view id for a particular direction,
-     * use the root to look up the view.  Once a view is found, it is cached
-     * for future lookups.
+     * use the root to look up the view.
      * @param root The root view of the hierarchy containing this view.
-     * @param direction One of FOCUS_UP, FOCUS_DOWN, FOCUS_LEFT, and FOCUS_RIGHT
+     * @param direction One of FOCUS_UP, FOCUS_DOWN, FOCUS_LEFT, FOCUS_RIGHT, FOCUS_FORWARD,
+     * or FOCUS_BACKWARD.
      * @return The user specified next view, or null if there is none.
      */
     View findUserSetNextFocus(View root, int direction) {
@@ -3969,6 +4004,18 @@
             case FOCUS_DOWN:
                 if (mNextFocusDownId == View.NO_ID) return null;
                 return findViewShouldExist(root, mNextFocusDownId);
+            case FOCUS_FORWARD:
+                if (mNextFocusForwardId == View.NO_ID) return null;
+                return findViewShouldExist(root, mNextFocusForwardId);
+            case FOCUS_BACKWARD: {
+                final int id = mID;
+                return root.findViewByPredicate(new Predicate<View>() {
+                    @Override
+                    public boolean apply(View t) {
+                        return t.mNextFocusForwardId == id;
+                    }
+                });
+            }
         }
         return null;
     }
@@ -9359,6 +9406,18 @@
     }
 
     /**
+     * {@hide}
+     * @param predicate The predicate to evaluate.
+     * @return The first view that matches the predicate or null.
+     */
+    protected View findViewByPredicateTraversal(Predicate<View> predicate) {
+        if (predicate.apply(this)) {
+            return this;
+        }
+        return null;
+    }
+
+    /**
      * Look for a child view with the given id.  If this view has the given
      * id, return this view.
      *
@@ -9387,6 +9446,18 @@
     }
 
     /**
+     * {@hide}
+     * Look for a child view that matches the specified predicate.
+     * If this view matches the predicate, return this view.
+     *
+     * @param predicate The predicate to evaluate.
+     * @return The first view that matches the predicate or null.
+     */
+    public final View findViewByPredicate(Predicate<View> predicate) {
+        return findViewByPredicateTraversal(predicate);
+    }
+
+    /**
      * Sets the identifier for this view. The identifier does not have to be
      * unique in this view's hierarchy. The identifier should be a positive
      * number.