Adding keyboard focus support for phones.

Change-Id: I2daab961d0727bc5d892db6b50ad0f51fe23873c
diff --git a/src/com/android/launcher2/FocusHelper.java b/src/com/android/launcher2/FocusHelper.java
index c9bd58c..f14ebae 100644
--- a/src/com/android/launcher2/FocusHelper.java
+++ b/src/com/android/launcher2/FocusHelper.java
@@ -20,6 +20,7 @@
 import java.util.Collections;
 import java.util.Comparator;
 
+import android.content.res.Configuration;
 import android.view.KeyEvent;
 import android.view.View;
 import android.view.ViewGroup;
@@ -40,6 +41,27 @@
 }
 
 /**
+ * A keyboard listener we set on the indicator buttons.
+ */
+class IndicatorKeyEventListener implements View.OnKeyListener {
+    @Override
+    public boolean onKey(View v, int keyCode, KeyEvent event) {
+        return FocusHelper.handleIndicatorButtonKeyEvent(v, keyCode, event);
+    }
+}
+
+/**
+ * A keyboard listener we set on all the dock buttons.
+ */
+class DockKeyEventListener implements View.OnKeyListener {
+    @Override
+    public boolean onKey(View v, int keyCode, KeyEvent event) {
+        final Configuration configuration = v.getResources().getConfiguration();
+        return FocusHelper.handleDockButtonKeyEvent(v, keyCode, event, configuration.orientation);
+    }
+}
+
+/**
  * A keyboard listener we set on the last tab button in AllApps to jump to then
  * market icon and vice versa.
  */
@@ -107,6 +129,7 @@
 
     /**
      * Handles key events in a PageViewExtendedLayout containing PagedViewWidgets.
+     * To be deprecated.
      */
     static boolean handlePagedViewWidgetKeyEvent(PagedViewWidget w, int keyCode, KeyEvent e) {
         if (!LauncherApplication.isScreenXLarge()) return false;
@@ -223,6 +246,136 @@
     }
 
     /**
+     * Handles key events in a PageViewExtendedLayout containing PagedViewWidgets.
+     */
+    static boolean handlePagedViewGridLayoutWidgetKeyEvent(PagedViewWidget w, int keyCode,
+            KeyEvent e) {
+
+        final PagedViewGridLayout parent = (PagedViewGridLayout) w.getParent();
+        final ViewGroup container = (ViewGroup) parent.getParent();
+        final TabHost tabHost = findTabHostParent(container);
+        final TabWidget tabs = (TabWidget) tabHost.findViewById(com.android.internal.R.id.tabs);
+        final int widgetIndex = parent.indexOfChild(w);
+        final int widgetCount = parent.getChildCount();
+        final int pageIndex = container.indexOfChild(parent);
+        final int pageCount = container.getChildCount();
+        final int cellCountX = parent.getCellCountX();
+        final int cellCountY = parent.getCellCountY();
+        final int x = widgetIndex % cellCountX;
+        final int y = widgetIndex / cellCountX;
+
+        final int action = e.getAction();
+        final boolean handleKeyEvent = (action != KeyEvent.ACTION_UP);
+        PagedViewGridLayout newParent = null;
+        boolean wasHandled = false;
+        switch (keyCode) {
+            case KeyEvent.KEYCODE_DPAD_LEFT:
+                if (handleKeyEvent) {
+                    // Select the previous widget or the last widget on the previous page
+                    if (widgetIndex > 0) {
+                        parent.getChildAt(widgetIndex - 1).requestFocus();
+                    } else {
+                        if (pageIndex > 0) {
+                            newParent = (PagedViewGridLayout)
+                                    container.getChildAt(pageIndex - 1);
+                            newParent.getChildAt(newParent.getChildCount() - 1).requestFocus();
+                        }
+                    }
+                }
+                wasHandled = true;
+                break;
+            case KeyEvent.KEYCODE_DPAD_RIGHT:
+                if (handleKeyEvent) {
+                    // Select the next widget or the first widget on the next page
+                    if (widgetIndex < (widgetCount - 1)) {
+                        parent.getChildAt(widgetIndex + 1).requestFocus();
+                    } else {
+                        if (pageIndex < (pageCount - 1)) {
+                            newParent = (PagedViewGridLayout)
+                                    container.getChildAt(pageIndex + 1);
+                            newParent.getChildAt(0).requestFocus();
+                        }
+                    }
+                }
+                wasHandled = true;
+                break;
+            case KeyEvent.KEYCODE_DPAD_UP:
+                if (handleKeyEvent) {
+                    // Select the closest icon in the previous row, otherwise select the tab bar
+                    if (y > 0) {
+                        int newWidgetIndex = ((y - 1) * cellCountX) + x;
+                        parent.getChildAt(newWidgetIndex).requestFocus();
+                    } else {
+                        tabs.requestFocus();
+                    }
+                }
+                wasHandled = true;
+                break;
+            case KeyEvent.KEYCODE_DPAD_DOWN:
+                if (handleKeyEvent) {
+                    // Select the closest icon in the previous row, otherwise do nothing
+                    if (y < (cellCountY - 1)) {
+                        int newWidgetIndex = Math.min(widgetCount - 1, ((y + 1) * cellCountX) + x);
+                        parent.getChildAt(newWidgetIndex).requestFocus();
+                    }
+                }
+                wasHandled = true;
+                break;
+            case KeyEvent.KEYCODE_ENTER:
+            case KeyEvent.KEYCODE_DPAD_CENTER:
+                if (handleKeyEvent) {
+                    // Simulate a click on the widget
+                    View.OnClickListener clickListener = (View.OnClickListener) container;
+                    clickListener.onClick(w);
+                }
+                wasHandled = true;
+                break;
+            case KeyEvent.KEYCODE_PAGE_UP:
+                if (handleKeyEvent) {
+                    // Select the first item on the previous page, or the first item on this page
+                    // if there is no previous page
+                    if (pageIndex > 0) {
+                        newParent = (PagedViewGridLayout) container.getChildAt(pageIndex - 1);
+                        newParent.getChildAt(0).requestFocus();
+                    } else {
+                        parent.getChildAt(0).requestFocus();
+                    }
+                }
+                wasHandled = true;
+                break;
+            case KeyEvent.KEYCODE_PAGE_DOWN:
+                if (handleKeyEvent) {
+                    // Select the first item on the next page, or the last item on this page
+                    // if there is no next page
+                    if (pageIndex < (pageCount - 1)) {
+                        newParent = (PagedViewGridLayout) container.getChildAt(pageIndex + 1);
+                        newParent.getChildAt(0).requestFocus();
+                    } else {
+                        parent.getChildAt(widgetCount - 1).requestFocus();
+                    }
+                }
+                wasHandled = true;
+                break;
+            case KeyEvent.KEYCODE_MOVE_HOME:
+                if (handleKeyEvent) {
+                    // Select the first item on this page
+                    parent.getChildAt(0).requestFocus();
+                }
+                wasHandled = true;
+                break;
+            case KeyEvent.KEYCODE_MOVE_END:
+                if (handleKeyEvent) {
+                    // Select the last item on this page
+                    parent.getChildAt(widgetCount - 1).requestFocus();
+                }
+                wasHandled = true;
+                break;
+            default: break;
+        }
+        return wasHandled;
+    }
+
+    /**
      * Private helper method to get the PagedViewCellLayoutChildren given a PagedViewCellLayout
      * index.
      */
@@ -236,8 +389,6 @@
      * Handles key events in a PageViewCellLayout containing PagedViewIcons.
      */
     static boolean handlePagedViewIconKeyEvent(PagedViewIcon v, int keyCode, KeyEvent e) {
-        if (!LauncherApplication.isScreenXLarge()) return false;
-
         final PagedViewCellLayoutChildren parent = (PagedViewCellLayoutChildren) v.getParent();
         final PagedViewCellLayout parentLayout = (PagedViewCellLayout) parent.getParent();
         // Note we have an extra parent because of the
@@ -423,7 +574,7 @@
     }
 
     /**
-     * Handles key events in a the workspace button bar.
+     * Handles key events in the workspace button bar.
      */
     static boolean handleButtonBarButtonKeyEvent(View v, int keyCode, KeyEvent e) {
         if (!LauncherApplication.isScreenXLarge()) return false;
@@ -512,6 +663,141 @@
     }
 
     /**
+     * Handles key events in the prev/next indicators.
+     */
+    static boolean handleIndicatorButtonKeyEvent(View v, int keyCode, KeyEvent e) {
+        final ViewGroup launcher = (ViewGroup) v.getParent();
+        final Workspace workspace = (Workspace) launcher.findViewById(R.id.workspace);
+        final ViewGroup hotseat = (ViewGroup) launcher.findViewById(R.id.all_apps_button_cluster);
+        final View previousIndicator = launcher.findViewById(R.id.previous_screen);
+        final View nextIndicator = launcher.findViewById(R.id.next_screen);
+        final int pageIndex = workspace.getCurrentPage();
+        final int pageCount = workspace.getChildCount();
+
+        final int action = e.getAction();
+        final boolean handleKeyEvent = (action != KeyEvent.ACTION_UP);
+        boolean wasHandled = false;
+        switch (keyCode) {
+            case KeyEvent.KEYCODE_DPAD_LEFT:
+                if (handleKeyEvent) {
+                    if (v == previousIndicator) {
+                        if (pageIndex > 0) {
+                            // Snap to previous page and clear focus
+                            workspace.snapToPage(pageIndex - 1);
+                        }
+                    } else if (v == nextIndicator) {
+                        // Select the last button in the hot seat
+                        hotseat.getChildAt(hotseat.getChildCount() - 1).requestFocus();
+                    }
+                }
+                wasHandled = true;
+                break;
+            case KeyEvent.KEYCODE_DPAD_RIGHT:
+                if (handleKeyEvent) {
+                    if (v == previousIndicator) {
+                        // Select the first button in the hot seat
+                        hotseat.getChildAt(0).requestFocus();
+                    } else if (v == nextIndicator) {
+                        if (pageIndex < (pageCount - 1)) {
+                            // Snap to next page and clear focus
+                            workspace.snapToPage(pageIndex + 1);
+                        }
+                    }
+                }
+                wasHandled = true;
+                break;
+            case KeyEvent.KEYCODE_DPAD_UP:
+                if (handleKeyEvent) {
+                    // Select the first bubble text view in the current page of the workspace
+                    final CellLayout layout = (CellLayout) workspace.getChildAt(pageIndex);
+                    final CellLayoutChildren children = layout.getChildrenLayout();
+                    final View newIcon = getBubbleTextViewInDirection(layout, children, -1, 1);
+                    if (newIcon != null) {
+                        newIcon.requestFocus();
+                    } else {
+                        workspace.requestFocus();
+                    }
+                }
+                wasHandled = true;
+                break;
+            case KeyEvent.KEYCODE_DPAD_DOWN:
+                // Do nothing
+                wasHandled = true;
+                break;
+            default: break;
+        }
+        return wasHandled;
+    }
+
+    /**
+     * Handles key events in the workspace dock (bottom of the screen).
+     */
+    static boolean handleDockButtonKeyEvent(View v, int keyCode, KeyEvent e, int orientation) {
+        final ViewGroup parent = (ViewGroup) v.getParent();
+        final ViewGroup launcher = (ViewGroup) parent.getParent();
+        final Workspace workspace = (Workspace) launcher.findViewById(R.id.workspace);
+        final int buttonIndex = parent.indexOfChild(v);
+        final int buttonCount = parent.getChildCount();
+        final int pageIndex = workspace.getCurrentPage();
+        final int pageCount = workspace.getChildCount();
+        final View previousIndicator = launcher.findViewById(R.id.previous_screen);
+        final View nextIndicator = launcher.findViewById(R.id.next_screen);
+
+        // NOTE: currently we don't special case for the phone UI in different
+        // orientations, even though the dock is on the side in landscape mode.  This
+        // is to ensure that accessibility consistency is maintained across rotations.
+
+        final int action = e.getAction();
+        final boolean handleKeyEvent = (action != KeyEvent.ACTION_UP);
+        boolean wasHandled = false;
+        switch (keyCode) {
+            case KeyEvent.KEYCODE_DPAD_LEFT:
+                if (handleKeyEvent) {
+
+                    // Select the previous button, otherwise select the previous page indicator
+                    if (buttonIndex > 0) {
+                        parent.getChildAt(buttonIndex - 1).requestFocus();
+                    } else {
+                        previousIndicator.requestFocus();
+                    }
+                }
+                wasHandled = true;
+                break;
+            case KeyEvent.KEYCODE_DPAD_RIGHT:
+                if (handleKeyEvent) {
+                    // Select the next button, otherwise select the next page indicator
+                    if (buttonIndex < (buttonCount - 1)) {
+                        parent.getChildAt(buttonIndex + 1).requestFocus();
+                    } else {
+                        nextIndicator.requestFocus();
+                    }
+                }
+                wasHandled = true;
+                break;
+            case KeyEvent.KEYCODE_DPAD_UP:
+                if (handleKeyEvent) {
+                    // Select the first bubble text view in the current page of the workspace
+                    final CellLayout layout = (CellLayout) workspace.getChildAt(pageIndex);
+                    final CellLayoutChildren children = layout.getChildrenLayout();
+                    final View newIcon = getBubbleTextViewInDirection(layout, children, -1, 1);
+                    if (newIcon != null) {
+                        newIcon.requestFocus();
+                    } else {
+                        workspace.requestFocus();
+                    }
+                }
+                wasHandled = true;
+                break;
+            case KeyEvent.KEYCODE_DPAD_DOWN:
+                // Do nothing
+                wasHandled = true;
+                break;
+            default: break;
+        }
+        return wasHandled;
+    }
+
+    /**
      * Private helper method to get the CellLayoutChildren given a CellLayout index.
      */
     private static CellLayoutChildren getCellLayoutChildrenForIndex(ViewGroup container, int i) {
@@ -618,8 +904,6 @@
      * Handles key events in a Workspace containing BubbleTextView.
      */
     static boolean handleBubbleTextViewKeyEvent(BubbleTextView v, int keyCode, KeyEvent e) {
-        if (!LauncherApplication.isScreenXLarge()) return false;
-
         CellLayoutChildren parent = (CellLayoutChildren) v.getParent();
         final CellLayout layout = (CellLayout) parent.getParent();
         final Workspace workspace = (Workspace) layout.getParent();