Add initial support for TAB navigation.

Bug: 3286652
Change-Id: I813a0318b3b8d9c9bc791ea6a2427be11c08de00
diff --git a/core/java/android/view/ViewRoot.java b/core/java/android/view/ViewRoot.java
index 95949b9..281dd27 100644
--- a/core/java/android/view/ViewRoot.java
+++ b/core/java/android/view/ViewRoot.java
@@ -2415,81 +2415,78 @@
     }
 
     /**
-     * @param keyCode The key code
-     * @return True if the key is directional.
+     * Returns true if the key is used for keyboard navigation.
+     * @param keyEvent The key event.
+     * @return True if the key is used for keyboard navigation.
      */
-    static boolean isDirectional(int keyCode) {
-        switch (keyCode) {
+    private static boolean isNavigationKey(KeyEvent keyEvent) {
+        switch (keyEvent.getKeyCode()) {
         case KeyEvent.KEYCODE_DPAD_LEFT:
         case KeyEvent.KEYCODE_DPAD_RIGHT:
         case KeyEvent.KEYCODE_DPAD_UP:
         case KeyEvent.KEYCODE_DPAD_DOWN:
+        case KeyEvent.KEYCODE_DPAD_CENTER:
+        case KeyEvent.KEYCODE_PAGE_UP:
+        case KeyEvent.KEYCODE_PAGE_DOWN:
+        case KeyEvent.KEYCODE_MOVE_HOME:
+        case KeyEvent.KEYCODE_MOVE_END:
+        case KeyEvent.KEYCODE_TAB:
+        case KeyEvent.KEYCODE_SPACE:
+        case KeyEvent.KEYCODE_ENTER:
             return true;
         }
         return false;
     }
 
     /**
-     * Returns true if this key is a keyboard key.
+     * Returns true if the key is used for typing.
      * @param keyEvent The key event.
-     * @return whether this key is a keyboard key.
+     * @return True if the key is used for typing.
      */
-    private static boolean isKeyboardKey(KeyEvent keyEvent) {
-      final int convertedKey = keyEvent.getUnicodeChar();
-        return convertedKey > 0;
+    private static boolean isTypingKey(KeyEvent keyEvent) {
+        return keyEvent.getUnicodeChar() > 0;
     }
 
-
-
     /**
-     * See if the key event means we should leave touch mode (and leave touch
-     * mode if so).
+     * See if the key event means we should leave touch mode (and leave touch mode if so).
      * @param event The key event.
      * @return Whether this key event should be consumed (meaning the act of
      *   leaving touch mode alone is considered the event).
      */
     private boolean checkForLeavingTouchModeAndConsume(KeyEvent event) {
-        final int action = event.getAction();
-        if (action != KeyEvent.ACTION_DOWN && action != KeyEvent.ACTION_MULTIPLE) {
-            return false;
-        }
-        if ((event.getFlags()&KeyEvent.FLAG_KEEP_TOUCH_MODE) != 0) {
-            return false;
-        }
-
-        // only relevant if we are in touch mode
+        // Only relevant in touch mode.
         if (!mAttachInfo.mInTouchMode) {
             return false;
         }
 
-        // if something like an edit text has focus and the user is typing,
-        // leave touch mode
-        //
-        // note: the condition of not being a keyboard key is kind of a hacky
-        // approximation of whether we think the focused view will want the
-        // key; if we knew for sure whether the focused view would consume
-        // the event, that would be better.
-        if (isKeyboardKey(event) && mView != null && mView.hasFocus()) {
-            mFocusedView = mView.findFocus();
-            if ((mFocusedView instanceof ViewGroup)
-                    && ((ViewGroup) mFocusedView).getDescendantFocusability() ==
-                    ViewGroup.FOCUS_AFTER_DESCENDANTS) {
-                // something has focus, but is holding it weakly as a container
-                return false;
-            }
-            if (ensureTouchMode(false)) {
-                throw new IllegalStateException("should not have changed focus "
-                        + "when leaving touch mode while a view has focus.");
-            }
+        // Only consider leaving touch mode on DOWN or MULTIPLE actions, never on UP.
+        final int action = event.getAction();
+        if (action != KeyEvent.ACTION_DOWN && action != KeyEvent.ACTION_MULTIPLE) {
             return false;
         }
 
-        if (isDirectional(event.getKeyCode())) {
-            // no view has focus, so we leave touch mode (and find something
-            // to give focus to).  the event is consumed if we were able to
-            // find something to give focus to.
+        // Don't leave touch mode if the IME told us not to.
+        if ((event.getFlags() & KeyEvent.FLAG_KEEP_TOUCH_MODE) != 0) {
+            return false;
+        }
+
+        // If the key can be used for keyboard navigation then leave touch mode
+        // and select a focused view if needed (in ensureTouchMode).
+        // When a new focused view is selected, we consume the navigation key because
+        // navigation doesn't make much sense unless a view already has focus so
+        // the key's purpose is to set focus.
+        if (isNavigationKey(event)) {
             return ensureTouchMode(false);
         }
+
+        // If the key can be used for typing then leave touch mode
+        // and select a focused view if needed (in ensureTouchMode).
+        // Always allow the view to process the typing key.
+        if (isTypingKey(event)) {
+            ensureTouchMode(false);
+            return false;
+        }
+
         return false;
     }
 
@@ -2640,16 +2637,31 @@
             int direction = 0;
             switch (event.getKeyCode()) {
             case KeyEvent.KEYCODE_DPAD_LEFT:
-                direction = View.FOCUS_LEFT;
+                if (event.hasNoModifiers()) {
+                    direction = View.FOCUS_LEFT;
+                }
                 break;
             case KeyEvent.KEYCODE_DPAD_RIGHT:
-                direction = View.FOCUS_RIGHT;
+                if (event.hasNoModifiers()) {
+                    direction = View.FOCUS_RIGHT;
+                }
                 break;
             case KeyEvent.KEYCODE_DPAD_UP:
-                direction = View.FOCUS_UP;
+                if (event.hasNoModifiers()) {
+                    direction = View.FOCUS_UP;
+                }
                 break;
             case KeyEvent.KEYCODE_DPAD_DOWN:
-                direction = View.FOCUS_DOWN;
+                if (event.hasNoModifiers()) {
+                    direction = View.FOCUS_DOWN;
+                }
+                break;
+            case KeyEvent.KEYCODE_TAB:
+                if (event.hasNoModifiers()) {
+                    direction = View.FOCUS_FORWARD;
+                } else if (event.hasModifiers(KeyEvent.META_SHIFT_ON)) {
+                    direction = View.FOCUS_BACKWARD;
+                }
                 break;
             }