Merge "[key event focus] DPAD navigates to the nearest item on next/previous page b/19381790 b/16351792" into ub-launcher3-burnaby
diff --git a/src/com/android/launcher3/FocusHelper.java b/src/com/android/launcher3/FocusHelper.java
index bdfd7b2..ebb319b 100644
--- a/src/com/android/launcher3/FocusHelper.java
+++ b/src/com/android/launcher3/FocusHelper.java
@@ -89,6 +89,7 @@
             countX = ((CellLayout) parentLayout).getCountX();
             countY = ((CellLayout) parentLayout).getCountY();
         } else if (v.getParent() instanceof ViewGroup) {
+            //TODO(hyunyoungs): figure out when this needs to be called.
             itemContainer = parentLayout = (ViewGroup) v.getParent();
             countX = ((PagedViewGridLayout) parentLayout).getCellCountX();
             countY = ((PagedViewGridLayout) parentLayout).getCellCountY();
@@ -102,6 +103,7 @@
         final int pageCount = container.getChildCount();
         ViewGroup newParent = null;
         View child = null;
+        // TODO(hyunyoungs): this matrix is not applicable on the last page.
         int[][] matrix = FocusLogic.createFullMatrix(countX, countY, true);
 
         // Process focus.
@@ -111,12 +113,22 @@
             return consume;
         }
         switch (newIconIndex) {
+            case FocusLogic.PREVIOUS_PAGE_RIGHT_COLUMN:
+                newParent = getAppsCustomizePage(container, pageIndex -1);
+                if (newParent != null) {
+                    int row = FocusLogic.findRow(matrix, iconIndex);
+                    container.snapToPage(pageIndex - 1);
+                    // no need to create a new matrix.
+                    child = newParent.getChildAt(matrix[countX-1][row]);
+                }
+                break;
             case FocusLogic.PREVIOUS_PAGE_FIRST_ITEM:
                 newParent = getAppsCustomizePage(container, pageIndex - 1);
                 if (newParent != null) {
                     container.snapToPage(pageIndex - 1);
                     child = newParent.getChildAt(0);
                 }
+                break;
             case FocusLogic.PREVIOUS_PAGE_LAST_ITEM:
                 newParent = getAppsCustomizePage(container, pageIndex - 1);
                 if (newParent != null) {
@@ -131,6 +143,14 @@
                     child = newParent.getChildAt(0);
                 }
                 break;
+            case FocusLogic.NEXT_PAGE_LEFT_COLUMN:
+                newParent = getAppsCustomizePage(container, pageIndex + 1);
+                if (newParent != null) {
+                    container.snapToPage(pageIndex + 1);
+                    int row = FocusLogic.findRow(matrix, iconIndex);
+                    child = newParent.getChildAt(matrix[0][row]);
+                }
+                break;
             case FocusLogic.CURRENT_PAGE_FIRST_ITEM:
                 child = container.getChildAt(0);
                 break;
@@ -256,7 +276,7 @@
 
         // Initialize the variables.
         ShortcutAndWidgetContainer parent = (ShortcutAndWidgetContainer) v.getParent();
-        final CellLayout iconLayout = (CellLayout) parent.getParent();
+        CellLayout iconLayout = (CellLayout) parent.getParent();
         final Workspace workspace = (Workspace) iconLayout.getParent();
         final ViewGroup launcher = (ViewGroup) workspace.getParent();
         final ViewGroup tabs = (ViewGroup) launcher.findViewById(R.id.search_drop_target_bar);
@@ -301,10 +321,23 @@
                     newIcon = tabs;
                 }
                 break;
+            case FocusLogic.PREVIOUS_PAGE_RIGHT_COLUMN:
+                int row = FocusLogic.findRow(matrix, iconIndex);
+                parent = getCellLayoutChildrenForIndex(workspace, pageIndex - 1);
+                if (parent != null) {
+                    iconLayout = (CellLayout) parent.getParent();
+                    matrix = FocusLogic.createSparseMatrix(iconLayout, orientation,
+                        iconLayout.getCountX(), row);
+                    newIconIndex = FocusLogic.handleKeyEvent(keyCode, countX + 1, countY, matrix,
+                        FocusLogic.PIVOT, pageIndex - 1, pageCount);
+                    newIcon = parent.getChildAt(newIconIndex);
+                }
+                break;
             case FocusLogic.PREVIOUS_PAGE_FIRST_ITEM:
                 parent = getCellLayoutChildrenForIndex(workspace, pageIndex - 1);
                 newIcon = parent.getChildAt(0);
                 workspace.snapToPage(pageIndex - 1);
+                break;
             case FocusLogic.PREVIOUS_PAGE_LAST_ITEM:
                 parent = getCellLayoutChildrenForIndex(workspace, pageIndex - 1);
                 newIcon = parent.getChildAt(parent.getChildCount() - 1);
@@ -315,6 +348,17 @@
                 newIcon = parent.getChildAt(0);
                 workspace.snapToPage(pageIndex + 1);
                 break;
+            case FocusLogic.NEXT_PAGE_LEFT_COLUMN:
+                row = FocusLogic.findRow(matrix, iconIndex);
+                parent = getCellLayoutChildrenForIndex(workspace, pageIndex + 1);
+                if (parent != null) {
+                    iconLayout = (CellLayout) parent.getParent();
+                    matrix = FocusLogic.createSparseMatrix(iconLayout, orientation, -1, row);
+                    newIconIndex = FocusLogic.handleKeyEvent(keyCode, countX + 1, countY, matrix,
+                        FocusLogic.PIVOT, pageIndex, pageCount);
+                    newIcon = parent.getChildAt(newIconIndex);
+                }
+                break;
             case FocusLogic.CURRENT_PAGE_FIRST_ITEM:
                 newIcon = parent.getChildAt(0);
                 break;
diff --git a/src/com/android/launcher3/util/FocusLogic.java b/src/com/android/launcher3/util/FocusLogic.java
index c0730d9..0c6bfbf 100644
--- a/src/com/android/launcher3/util/FocusLogic.java
+++ b/src/com/android/launcher3/util/FocusLogic.java
@@ -48,16 +48,19 @@
     // Item and page index related constant used by {@link #handleKeyEvent}.
     public static final int NOOP = -1;
 
-    public static final int PREVIOUS_PAGE_FIRST_ITEM    = -2;
-    public static final int PREVIOUS_PAGE_LAST_ITEM     = -3;
+    public static final int PREVIOUS_PAGE_RIGHT_COLUMN  = -2;
+    public static final int PREVIOUS_PAGE_FIRST_ITEM    = -3;
+    public static final int PREVIOUS_PAGE_LAST_ITEM     = -4;
 
-    public static final int CURRENT_PAGE_FIRST_ITEM     = -4;
-    public static final int CURRENT_PAGE_LAST_ITEM      = -5;
+    public static final int CURRENT_PAGE_FIRST_ITEM     = -5;
+    public static final int CURRENT_PAGE_LAST_ITEM      = -6;
 
-    public static final int NEXT_PAGE_FIRST_ITEM        = -6;
+    public static final int NEXT_PAGE_FIRST_ITEM        = -7;
+    public static final int NEXT_PAGE_LEFT_COLUMN       = -8;
 
     // Matrix related constant.
     public static final int EMPTY = -1;
+    public static final int PIVOT = 100;
 
     /**
      * Returns true only if this utility class handles the key code.
@@ -87,13 +90,13 @@
             case KeyEvent.KEYCODE_DPAD_LEFT:
                 newIndex = handleDpadHorizontal(iconIdx, cntX, cntY, map, -1 /*increment*/);
                 if (newIndex == NOOP && pageIndex > 0) {
-                    return PREVIOUS_PAGE_LAST_ITEM;
+                    newIndex = PREVIOUS_PAGE_RIGHT_COLUMN;
                 }
                 break;
             case KeyEvent.KEYCODE_DPAD_RIGHT:
                 newIndex = handleDpadHorizontal(iconIdx, cntX, cntY, map, 1 /*increment*/);
                 if (newIndex == NOOP && pageIndex < pageCount - 1) {
-                    return NEXT_PAGE_FIRST_ITEM;
+                    newIndex = NEXT_PAGE_LEFT_COLUMN;
                 }
                 break;
             case KeyEvent.KEYCODE_DPAD_DOWN:
@@ -169,7 +172,7 @@
             matrix[cx][cy] = i;
         }
         if (DEBUG) {
-            printMatrix(matrix, m, n);
+            printMatrix(matrix);
         }
         return matrix;
     }
@@ -231,7 +234,47 @@
             }
         }
         if (DEBUG) {
-            printMatrix(matrix, m, n);
+            printMatrix(matrix);
+        }
+        return matrix;
+    }
+
+    /**
+     * Creates a sparse matrix that merges the icon of previous/next page and last column of
+     * current page. When left key is triggered on the leftmost column, sparse matrix is created
+     * that combines previous page matrix and an extra column on the right. Likewise, when right
+     * key is triggered on the rightmost column, sparse matrix is created that combines this column
+     * on the 0th column and the next page matrix.
+     *
+     * @param pivotX    x coordinate of the focused item in the current page
+     * @param pivotY    y coordinate of the focused item in the current page
+     */
+    // TODO: get rid of the dynamic matrix creation
+    public static int[][] createSparseMatrix(CellLayout iconLayout, int pivotX, int pivotY) {
+
+        ViewGroup iconParent = iconLayout.getShortcutsAndWidgets();
+
+        int[][] matrix = createFullMatrix(iconLayout.getCountX() + 1, iconLayout.getCountY(),
+                false /* set all cell to empty */);
+
+        // Iterate thru the children of the top parent.
+        for (int i = 0; i < iconParent.getChildCount(); i++) {
+            int cx = ((CellLayout.LayoutParams) iconParent.getChildAt(i).getLayoutParams()).cellX;
+            int cy = ((CellLayout.LayoutParams) iconParent.getChildAt(i).getLayoutParams()).cellY;
+            if (pivotX < 0) {
+                matrix[cx - pivotX][cy] = i;
+            } else {
+                matrix[cx][cy] = i;
+            }
+        }
+
+        if (pivotX < 0) {
+            matrix[0][pivotY] = PIVOT;
+        } else {
+            matrix[pivotX][pivotY] = PIVOT;
+        }
+        if (DEBUG) {
+            printMatrix(matrix);
         }
         return matrix;
     }
@@ -407,21 +450,32 @@
         return newIconIndex;
     }
 
+    /**
+     * Only used for debugging.
+     */
     private static String getStringIndex(int index) {
         switch(index) {
             case NOOP: return "NOOP";
             case PREVIOUS_PAGE_FIRST_ITEM:  return "PREVIOUS_PAGE_FIRST";
             case PREVIOUS_PAGE_LAST_ITEM:   return "PREVIOUS_PAGE_LAST";
+            case PREVIOUS_PAGE_RIGHT_COLUMN:return "PREVIOUS_PAGE_RIGHT_COLUMN";
             case CURRENT_PAGE_FIRST_ITEM:   return "CURRENT_PAGE_FIRST";
             case CURRENT_PAGE_LAST_ITEM:    return "CURRENT_PAGE_LAST";
             case NEXT_PAGE_FIRST_ITEM:      return "NEXT_PAGE_FIRST";
+            case NEXT_PAGE_LEFT_COLUMN:     return "NEXT_PAGE_LEFT_COLUMN";
             default:
                 return Integer.toString(index);
         }
     }
 
-    private static void printMatrix(int[][] matrix, int m, int n) {
+    /**
+     * Only used for debugging.
+     */
+    private static void printMatrix(int[][] matrix) {
         Log.v(TAG, "\tprintMap:");
+        int m = matrix.length;
+        int n = matrix[0].length;
+
         for (int j=0; j < n; j++) {
             String colY = "\t\t";
             for (int i=0; i < m; i++) {
@@ -430,4 +484,24 @@
             Log.v(TAG, colY);
         }
     }
+
+    /**
+     * Figure out the location of the icon.
+     *
+     */
+    //TODO(hyunyoungs): this helper method should move to CellLayout class while removing the
+    // dynamic matrix creation all together.
+    public static int findRow(int[][] matrix, int iconIndex) {
+        int cntX = matrix.length;
+        int cntY = matrix[0].length;
+
+        for (int i = 0; i < cntX; i++) {
+            for (int j = 0; j < cntY; j++) {
+                if (matrix[i][j] == iconIndex) {
+                    return j;
+                }
+            }
+        }
+        return -1;
+    }
 }
diff --git a/tests/src/com/android/launcher3/util/FocusLogicTest.java b/tests/src/com/android/launcher3/util/FocusLogicTest.java
index fd2e2a8..ea87014 100644
--- a/tests/src/com/android/launcher3/util/FocusLogicTest.java
+++ b/tests/src/com/android/launcher3/util/FocusLogicTest.java
@@ -18,6 +18,9 @@
 
 import android.test.AndroidTestCase;
 import android.test.suitebuilder.annotation.SmallTest;
+import android.view.KeyEvent;
+
+import com.android.launcher3.util.FocusLogic;
 
 /**
  * Tests the {@link FocusLogic} class that handles key event based focus handling.
@@ -37,6 +40,21 @@
     }
 
     public void testShouldConsume() {
-        // write tests.
+         assertTrue(FocusLogic.shouldConsume(KeyEvent.KEYCODE_DPAD_LEFT));
+         assertTrue(FocusLogic.shouldConsume(KeyEvent.KEYCODE_DPAD_RIGHT));
+         assertTrue(FocusLogic.shouldConsume(KeyEvent.KEYCODE_DPAD_UP));
+         assertTrue(FocusLogic.shouldConsume(KeyEvent.KEYCODE_DPAD_DOWN));
+         assertTrue(FocusLogic.shouldConsume(KeyEvent.KEYCODE_MOVE_HOME));
+         assertTrue(FocusLogic.shouldConsume(KeyEvent.KEYCODE_MOVE_END));
+         assertTrue(FocusLogic.shouldConsume(KeyEvent.KEYCODE_PAGE_UP));
+         assertTrue(FocusLogic.shouldConsume(KeyEvent.KEYCODE_PAGE_DOWN));
+         assertTrue(FocusLogic.shouldConsume(KeyEvent.KEYCODE_DEL));
+         assertTrue(FocusLogic.shouldConsume(KeyEvent.KEYCODE_FORWARD_DEL));
+    }
+
+    public void testCreateSparseMatrix() {
+         // Either, 1) create a helper method to generate/instantiate all possible cell layout that
+         // may get created in real world to test this method. OR 2) Move all the matrix
+         // management routine to celllayout and write tests for them.
     }
 }