Merge "Normalizing app icons based on the standard icon guidelines" into ub-launcher3-burnaby-polish
diff --git a/src/com/android/launcher3/FocusHelper.java b/src/com/android/launcher3/FocusHelper.java
index 44403e2..d59c644 100644
--- a/src/com/android/launcher3/FocusHelper.java
+++ b/src/com/android/launcher3/FocusHelper.java
@@ -45,6 +45,23 @@
     }
 }
 
+/**
+ * A keyboard listener we set on full screen pages (e.g. custom content).
+ */
+class FullscreenKeyEventListener implements View.OnKeyListener {
+    @Override
+    public boolean onKey(View v, int keyCode, KeyEvent event) {
+        if (keyCode == KeyEvent.KEYCODE_DPAD_LEFT || keyCode == KeyEvent.KEYCODE_DPAD_RIGHT
+                || keyCode == KeyEvent.KEYCODE_PAGE_DOWN || keyCode == KeyEvent.KEYCODE_PAGE_UP) {
+            // Handle the key event just like a workspace icon would in these cases. In this case,
+            // it will basically act as if there is a single icon in the top left (so you could
+            // think of the fullscreen page as a focusable fullscreen widget).
+            return FocusHelper.handleIconKeyEvent(v, keyCode, event);
+        }
+        return false;
+    }
+}
+
 public class FocusHelper {
 
     private static final String TAG = "FocusHelper";
@@ -83,8 +100,6 @@
             // Initialize variables.
             final ShortcutAndWidgetContainer itemContainer = (ShortcutAndWidgetContainer) v.getParent();
             final CellLayout cellLayout = (CellLayout) itemContainer.getParent();
-            final int countX = cellLayout.getCountX();
-            final int countY = cellLayout.getCountY();
 
             final int iconIndex = itemContainer.indexOfChild(v);
             final FolderPagedView pagedView = (FolderPagedView) cellLayout.getParent();
@@ -95,8 +110,8 @@
 
             int[][] matrix = FocusLogic.createSparseMatrix(cellLayout);
             // Process focus.
-            int newIconIndex = FocusLogic.handleKeyEvent(keyCode, countX,
-                    countY, matrix, iconIndex, pageIndex, pageCount, isLayoutRtl);
+            int newIconIndex = FocusLogic.handleKeyEvent(keyCode, matrix, iconIndex, pageIndex,
+                    pageCount, isLayoutRtl);
             if (newIconIndex == FocusLogic.NOOP) {
                 handleNoopKey(keyCode, v);
                 return consume;
@@ -113,7 +128,8 @@
                         pagedView.snapToPage(pageIndex - 1);
                         child = newParent.getChildAt(
                                 ((newIconIndex == FocusLogic.PREVIOUS_PAGE_LEFT_COLUMN)
-                                    ^ newParent.invertLayoutHorizontally()) ? 0 : countX - 1, row);
+                                    ^ newParent.invertLayoutHorizontally()) ? 0 : matrix.length - 1,
+                                row);
                     }
                     break;
                 case FocusLogic.PREVIOUS_PAGE_FIRST_ITEM:
@@ -127,7 +143,7 @@
                     newParent = getCellLayoutChildrenForIndex(pagedView, pageIndex - 1);
                     if (newParent != null) {
                         pagedView.snapToPage(pageIndex - 1);
-                        child = newParent.getChildAt(countX - 1, countY - 1);
+                        child = newParent.getChildAt(matrix.length - 1, matrix[0].length - 1);
                     }
                     break;
                 case FocusLogic.NEXT_PAGE_FIRST_ITEM:
@@ -202,8 +218,6 @@
         final ItemInfo itemInfo = (ItemInfo) v.getTag();
         int pageIndex = workspace.getNextPage();
         int pageCount = workspace.getChildCount();
-        int countX = -1;
-        int countY = -1;
         int iconIndex = hotseatParent.indexOfChild(v);
         int iconRank = ((CellLayout.LayoutParams) hotseatLayout.getShortcutsAndWidgets()
                 .getChildAt(iconIndex).getLayoutParams()).cellX;
@@ -222,21 +236,15 @@
 
         if (keyCode == KeyEvent.KEYCODE_DPAD_UP &&
                 !profile.isVerticalBarLayout()) {
-            matrix = FocusLogic.createSparseMatrix(iconLayout, hotseatLayout,
-                    true /* hotseat horizontal */, profile.inv.hotseatAllAppsRank,
-                    iconRank == profile.inv.hotseatAllAppsRank /* include all apps icon */);
+            matrix = FocusLogic.createSparseMatrixWithHotseat(iconLayout, hotseatLayout,
+                    true /* hotseat horizontal */, profile.inv.hotseatAllAppsRank);
             iconIndex += iconParent.getChildCount();
-            countX = iconLayout.getCountX();
-            countY = iconLayout.getCountY() + hotseatLayout.getCountY();
             parent = iconParent;
         } else if (keyCode == KeyEvent.KEYCODE_DPAD_LEFT &&
                 profile.isVerticalBarLayout()) {
-            matrix = FocusLogic.createSparseMatrix(iconLayout, hotseatLayout,
-                    false /* hotseat horizontal */, profile.inv.hotseatAllAppsRank,
-                    iconRank == profile.inv.hotseatAllAppsRank /* include all apps icon */);
+            matrix = FocusLogic.createSparseMatrixWithHotseat(iconLayout, hotseatLayout,
+                    false /* hotseat horizontal */, profile.inv.hotseatAllAppsRank);
             iconIndex += iconParent.getChildCount();
-            countX = iconLayout.getCountX() + hotseatLayout.getCountX();
-            countY = iconLayout.getCountY();
             parent = iconParent;
         } else if (keyCode == KeyEvent.KEYCODE_DPAD_RIGHT &&
                 profile.isVerticalBarLayout()) {
@@ -253,14 +261,12 @@
             // For other KEYCODE_DPAD_LEFT and KEYCODE_DPAD_RIGHT navigation, do not use the
             // matrix extended with hotseat.
             matrix = FocusLogic.createSparseMatrix(hotseatLayout);
-            countX = hotseatLayout.getCountX();
-            countY = hotseatLayout.getCountY();
             parent = hotseatParent;
         }
 
         // Process the focus.
-        int newIconIndex = FocusLogic.handleKeyEvent(keyCode, countX,
-                countY, matrix, iconIndex, pageIndex, pageCount, Utilities.isRtl(v.getResources()));
+        int newIconIndex = FocusLogic.handleKeyEvent(keyCode, matrix, iconIndex, pageIndex,
+                pageCount, Utilities.isRtl(v.getResources()));
 
         View newIcon = null;
         switch (newIconIndex) {
@@ -289,18 +295,32 @@
             case FocusLogic.PREVIOUS_PAGE_RIGHT_COLUMN:
                 // Go to the previous page but keep the focus on the same hotseat icon.
                 workspace.snapToPage(pageIndex - 1);
+                // If the page we are going to is fullscreen, have it take the focus from hotseat.
+                CellLayout prevPage = (CellLayout) workspace.getPageAt(pageIndex - 1);
+                boolean isPrevPageFullscreen = ((CellLayout.LayoutParams) prevPage
+                        .getShortcutsAndWidgets().getChildAt(0).getLayoutParams()).isFullscreen;
+                if (isPrevPageFullscreen) {
+                    workspace.getPageAt(pageIndex - 1).requestFocus();
+                }
                 break;
             case FocusLogic.NEXT_PAGE_LEFT_COLUMN:
             case FocusLogic.NEXT_PAGE_RIGHT_COLUMN:
                 // Go to the next page but keep the focus on the same hotseat icon.
                 workspace.snapToPage(pageIndex + 1);
+                // If the page we are going to is fullscreen, have it take the focus from hotseat.
+                CellLayout nextPage = (CellLayout) workspace.getPageAt(pageIndex + 1);
+                boolean isNextPageFullscreen = ((CellLayout.LayoutParams) nextPage
+                        .getShortcutsAndWidgets().getChildAt(0).getLayoutParams()).isFullscreen;
+                if (isNextPageFullscreen) {
+                    workspace.getPageAt(pageIndex + 1).requestFocus();
+                }
                 break;
         }
         if (parent == iconParent && newIconIndex >= iconParent.getChildCount()) {
             newIconIndex -= iconParent.getChildCount();
         }
         if (parent != null) {
-            if (newIcon == null && newIconIndex >=0) {
+            if (newIcon == null && newIconIndex >= 0) {
                 newIcon = parent.getChildAt(newIconIndex);
             }
             if (newIcon != null) {
@@ -340,8 +360,6 @@
         final int iconIndex = parent.indexOfChild(v);
         final int pageIndex = workspace.indexOfChild(iconLayout);
         final int pageCount = workspace.getChildCount();
-        int countX = iconLayout.getCountX();
-        int countY = iconLayout.getCountY();
 
         CellLayout hotseatLayout = (CellLayout) hotseat.getChildAt(0);
         ShortcutAndWidgetContainer hotseatParent = hotseatLayout.getShortcutsAndWidgets();
@@ -351,16 +369,12 @@
         // to take a user to the hotseat. For other dpad navigation, do not use the matrix extended
         // with the hotseat.
         if (keyCode == KeyEvent.KEYCODE_DPAD_DOWN && !profile.isVerticalBarLayout()) {
-            matrix = FocusLogic.createSparseMatrix(iconLayout, hotseatLayout, true /* horizontal */,
-                    profile.inv.hotseatAllAppsRank,
-                    !hotseat.hasIcons() /* ignore all apps icon, unless there are no other icons */);
-            countY = countY + 1;
+            matrix = FocusLogic.createSparseMatrixWithHotseat(iconLayout, hotseatLayout,
+                    true /* horizontal */, profile.inv.hotseatAllAppsRank);
         } else if (keyCode == KeyEvent.KEYCODE_DPAD_RIGHT &&
                 profile.isVerticalBarLayout()) {
-            matrix = FocusLogic.createSparseMatrix(iconLayout, hotseatLayout, false /* horizontal */,
-                    profile.inv.hotseatAllAppsRank,
-                    !hotseat.hasIcons() /* ignore all apps icon, unless there are no other icons */);
-            countX = countX + 1;
+            matrix = FocusLogic.createSparseMatrixWithHotseat(iconLayout, hotseatLayout,
+                    false /* horizontal */, profile.inv.hotseatAllAppsRank);
         } else if (isUninstallKeyChord(e)) {
             matrix = FocusLogic.createSparseMatrix(iconLayout);
             if (UninstallDropTarget.supportsDrop(launcher, itemInfo)) {
@@ -374,9 +388,11 @@
         }
 
         // Process the focus.
-        int newIconIndex = FocusLogic.handleKeyEvent(keyCode, countX,
-                countY, matrix, iconIndex, pageIndex, pageCount, Utilities.isRtl(v.getResources()));
+        int newIconIndex = FocusLogic.handleKeyEvent(keyCode, matrix, iconIndex, pageIndex,
+                pageCount, Utilities.isRtl(v.getResources()));
+        boolean isRtl = Utilities.isRtl(v.getResources());
         View newIcon = null;
+        CellLayout workspaceLayout = (CellLayout) workspace.getChildAt(pageIndex);
         switch (newIconIndex) {
             case FocusLogic.NOOP:
                 if (keyCode == KeyEvent.KEYCODE_DPAD_UP) {
@@ -393,25 +409,35 @@
                 parent = getCellLayoutChildrenForIndex(workspace, newPageIndex);
                 if (parent != null) {
                     iconLayout = (CellLayout) parent.getParent();
-                    matrix = FocusLogic.createSparseMatrix(iconLayout,
+                    matrix = FocusLogic.createSparseMatrixWithPivotColumn(iconLayout,
                             iconLayout.getCountX(), row);
-                    newIconIndex = FocusLogic.handleKeyEvent(keyCode, countX + 1, countY,
-                            matrix, FocusLogic.PIVOT, newPageIndex, pageCount,
-                            Utilities.isRtl(v.getResources()));
-                    newIcon = parent.getChildAt(newIconIndex);
+                    newIconIndex = FocusLogic.handleKeyEvent(keyCode, matrix, FocusLogic.PIVOT,
+                            newPageIndex, pageCount, Utilities.isRtl(v.getResources()));
+                    if (newIconIndex == FocusLogic.NEXT_PAGE_FIRST_ITEM) {
+                        newIcon = handleNextPageFirstItem(workspace, hotseatLayout, pageIndex,
+                                isRtl);
+                    } else if (newIconIndex == FocusLogic.PREVIOUS_PAGE_LAST_ITEM) {
+                        newIcon = handlePreviousPageLastItem(workspace, hotseatLayout, pageIndex,
+                                isRtl);
+                    } else {
+                        newIcon = parent.getChildAt(newIconIndex);
+                    }
                 }
                 break;
             case FocusLogic.PREVIOUS_PAGE_FIRST_ITEM:
-                parent = getCellLayoutChildrenForIndex(workspace, pageIndex - 1);
-                newIcon = parent.getChildAt(0);
+                workspaceLayout = (CellLayout) workspace.getChildAt(pageIndex - 1);
+                newIcon = getFirstFocusableIconInReadingOrder(workspaceLayout, isRtl);
+                if (newIcon == null) {
+                    // Check the hotseat if no focusable item was found on the workspace.
+                    newIcon = getFirstFocusableIconInReadingOrder(hotseatLayout, isRtl);
+                    workspace.snapToPage(pageIndex - 1);
+                }
                 break;
             case FocusLogic.PREVIOUS_PAGE_LAST_ITEM:
-                parent = getCellLayoutChildrenForIndex(workspace, pageIndex - 1);
-                newIcon = parent.getChildAt(parent.getChildCount() - 1);
+                newIcon = handlePreviousPageLastItem(workspace, hotseatLayout, pageIndex, isRtl);
                 break;
             case FocusLogic.NEXT_PAGE_FIRST_ITEM:
-                parent = getCellLayoutChildrenForIndex(workspace, pageIndex + 1);
-                newIcon = parent.getChildAt(0);
+                newIcon = handleNextPageFirstItem(workspace, hotseatLayout, pageIndex, isRtl);
                 break;
             case FocusLogic.NEXT_PAGE_LEFT_COLUMN:
             case FocusLogic.PREVIOUS_PAGE_LEFT_COLUMN:
@@ -423,18 +449,33 @@
                 parent = getCellLayoutChildrenForIndex(workspace, newPageIndex);
                 if (parent != null) {
                     iconLayout = (CellLayout) parent.getParent();
-                    matrix = FocusLogic.createSparseMatrix(iconLayout, -1, row);
-                    newIconIndex = FocusLogic.handleKeyEvent(keyCode, countX + 1, countY,
-                            matrix, FocusLogic.PIVOT, newPageIndex, pageCount,
-                            Utilities.isRtl(v.getResources()));
-                    newIcon = parent.getChildAt(newIconIndex);
+                    matrix = FocusLogic.createSparseMatrixWithPivotColumn(iconLayout, -1, row);
+                    newIconIndex = FocusLogic.handleKeyEvent(keyCode, matrix, FocusLogic.PIVOT,
+                            newPageIndex, pageCount, Utilities.isRtl(v.getResources()));
+                    if (newIconIndex == FocusLogic.NEXT_PAGE_FIRST_ITEM) {
+                        newIcon = handleNextPageFirstItem(workspace, hotseatLayout, pageIndex,
+                                isRtl);
+                    } else if (newIconIndex == FocusLogic.PREVIOUS_PAGE_LAST_ITEM) {
+                        newIcon = handlePreviousPageLastItem(workspace, hotseatLayout, pageIndex,
+                                isRtl);
+                    } else {
+                        newIcon = parent.getChildAt(newIconIndex);
+                    }
                 }
                 break;
             case FocusLogic.CURRENT_PAGE_FIRST_ITEM:
-                newIcon = parent.getChildAt(0);
+                newIcon = getFirstFocusableIconInReadingOrder(workspaceLayout, isRtl);
+                if (newIcon == null) {
+                    // Check the hotseat if no focusable item was found on the workspace.
+                    newIcon = getFirstFocusableIconInReadingOrder(hotseatLayout, isRtl);
+                }
                 break;
             case FocusLogic.CURRENT_PAGE_LAST_ITEM:
-                newIcon = parent.getChildAt(parent.getChildCount() - 1);
+                newIcon = getFirstFocusableIconInReverseReadingOrder(workspaceLayout, isRtl);
+                if (newIcon == null) {
+                    // Check the hotseat if no focusable item was found on the workspace.
+                    newIcon = getFirstFocusableIconInReverseReadingOrder(hotseatLayout, isRtl);
+                }
                 break;
             default:
                 // current page, some item.
@@ -509,4 +550,63 @@
         return (keyCode == KeyEvent.KEYCODE_DEL || keyCode == KeyEvent.KEYCODE_FORWARD_DEL) &&
                 event.hasModifiers(KeyEvent.META_CTRL_ON);
     }
+
+    private static View handlePreviousPageLastItem(Workspace workspace, CellLayout hotseatLayout,
+            int pageIndex, boolean isRtl) {
+        if (pageIndex - 1 < 0) {
+            return null;
+        }
+        CellLayout workspaceLayout = (CellLayout) workspace.getChildAt(pageIndex - 1);
+        View newIcon = getFirstFocusableIconInReverseReadingOrder(workspaceLayout, isRtl);
+        if (newIcon == null) {
+            // Check the hotseat if no focusable item was found on the workspace.
+            newIcon = getFirstFocusableIconInReverseReadingOrder(hotseatLayout,isRtl);
+            workspace.snapToPage(pageIndex - 1);
+        }
+        return newIcon;
+    }
+
+    private static View handleNextPageFirstItem(Workspace workspace, CellLayout hotseatLayout,
+            int pageIndex, boolean isRtl) {
+        if (pageIndex + 1 >= workspace.getPageCount()) {
+            return null;
+        }
+        CellLayout workspaceLayout = (CellLayout) workspace.getChildAt(pageIndex + 1);
+        View newIcon = getFirstFocusableIconInReadingOrder(workspaceLayout, isRtl);
+        if (newIcon == null) {
+            // Check the hotseat if no focusable item was found on the workspace.
+            newIcon = getFirstFocusableIconInReadingOrder(hotseatLayout, isRtl);
+            workspace.snapToPage(pageIndex + 1);
+        }
+        return newIcon;
+    }
+
+    private static View getFirstFocusableIconInReadingOrder(CellLayout cellLayout, boolean isRtl) {
+        View icon;
+        int countX = cellLayout.getCountX();
+        for (int y = 0; y < cellLayout.getCountY(); y++) {
+            int increment = isRtl ? -1 : 1;
+            for (int x = isRtl ? countX - 1 : 0; 0 <= x && x < countX; x += increment) {
+                if ((icon = cellLayout.getChildAt(x, y)) != null && icon.isFocusable()) {
+                    return icon;
+                }
+            }
+        }
+        return null;
+    }
+
+    private static View getFirstFocusableIconInReverseReadingOrder(CellLayout cellLayout,
+            boolean isRtl) {
+        View icon;
+        int countX = cellLayout.getCountX();
+        for (int y = cellLayout.getCountY() - 1; y >= 0; y--) {
+            int increment = isRtl ? 1 : -1;
+            for (int x = isRtl ? 0 : countX - 1; 0 <= x && x < countX; x += increment) {
+                if ((icon = cellLayout.getChildAt(x, y)) != null && icon.isFocusable()) {
+                    return icon;
+                }
+            }
+        }
+        return null;
+    }
 }
diff --git a/src/com/android/launcher3/FocusIndicatorView.java b/src/com/android/launcher3/FocusIndicatorView.java
index ecf93e4..58b38eb 100644
--- a/src/com/android/launcher3/FocusIndicatorView.java
+++ b/src/com/android/launcher3/FocusIndicatorView.java
@@ -30,6 +30,7 @@
 
     // It can be any number >0. The view is resized using scaleX and scaleY.
     static final int DEFAULT_LAYOUT_SIZE = 100;
+
     private static final float MIN_VISIBLE_ALPHA = 0.2f;
     private static final long ANIM_DURATION = 150;
 
@@ -41,6 +42,7 @@
 
     private View mLastFocusedView;
     private boolean mInitiated;
+    private final OnFocusChangeListener mHideIndicatorOnFocusListener;
 
     private Pair<View, Boolean> mPendingCall;
 
@@ -52,6 +54,16 @@
         super(context, attrs);
         setAlpha(0);
         setBackgroundColor(getResources().getColor(R.color.focused_background));
+
+        mHideIndicatorOnFocusListener = new OnFocusChangeListener() {
+            @Override
+            public void onFocusChange(View v, boolean hasFocus) {
+                if (hasFocus) {
+                    endCurrentAnimation();
+                    setAlpha(0);
+                }
+            }
+        };
     }
 
     @Override
@@ -66,6 +78,13 @@
         }
     }
 
+    /**
+     * Sets the alpha of this FocusIndicatorView to 0 when a view with this listener receives focus.
+     */
+    public View.OnFocusChangeListener getHideIndicatorOnFocusListener() {
+        return mHideIndicatorOnFocusListener;
+    }
+
     @Override
     public void onFocusChange(View v, boolean hasFocus) {
         mPendingCall = null;
diff --git a/src/com/android/launcher3/Hotseat.java b/src/com/android/launcher3/Hotseat.java
index 902b6ec..3e83876 100644
--- a/src/com/android/launcher3/Hotseat.java
+++ b/src/com/android/launcher3/Hotseat.java
@@ -70,7 +70,7 @@
     public void setOnLongClickListener(OnLongClickListener l) {
         mContent.setOnLongClickListener(l);
     }
-  
+
     /* Get the orientation invariant order of the item in the hotseat for persistence. */
     int getOrderInHotseat(int x, int y) {
         return mHasVerticalHotseat ? (mContent.getCountY() - y - 1) : x;
diff --git a/src/com/android/launcher3/Workspace.java b/src/com/android/launcher3/Workspace.java
index 5073902..94cbc7a 100644
--- a/src/com/android/launcher3/Workspace.java
+++ b/src/com/android/launcher3/Workspace.java
@@ -651,6 +651,10 @@
             parent.removeView(customContent);
         }
         customScreen.removeAllViews();
+        customContent.setFocusable(true);
+        customContent.setOnKeyListener(new FullscreenKeyEventListener());
+        customContent.setOnFocusChangeListener(mLauncher.mFocusHandler
+                .getHideIndicatorOnFocusListener());
         customScreen.addViewToCellLayout(customContent, 0, 0, lp, true);
         mCustomContentDescription = description;
 
diff --git a/src/com/android/launcher3/util/FocusLogic.java b/src/com/android/launcher3/util/FocusLogic.java
index 2aae3c0..a5498f7 100644
--- a/src/com/android/launcher3/util/FocusLogic.java
+++ b/src/com/android/launcher3/util/FocusLogic.java
@@ -63,6 +63,8 @@
     public static final int NEXT_PAGE_LEFT_COLUMN       = -9;
     public static final int NEXT_PAGE_RIGHT_COLUMN      = -10;
 
+    public static final int ALL_APPS_COLUMN = -11;
+
     // Matrix related constant.
     public static final int EMPTY = -1;
     public static final int PIVOT = 100;
@@ -78,8 +80,11 @@
                 keyCode == KeyEvent.KEYCODE_DEL || keyCode == KeyEvent.KEYCODE_FORWARD_DEL);
     }
 
-    public static int handleKeyEvent(int keyCode, int cntX, int cntY,
-            int [][] map, int iconIdx, int pageIndex, int pageCount, boolean isRtl) {
+    public static int handleKeyEvent(int keyCode, int [][] map, int iconIdx, int pageIndex,
+            int pageCount, boolean isRtl) {
+
+        int cntX = map == null ? -1 : map.length;
+        int cntY = map == null ? -1 : map[0].length;
 
         if (DEBUG) {
             Log.v(TAG, String.format(
@@ -90,7 +95,7 @@
         int newIndex = NOOP;
         switch (keyCode) {
             case KeyEvent.KEYCODE_DPAD_LEFT:
-                newIndex = handleDpadHorizontal(iconIdx, cntX, cntY, map, -1 /*increment*/);
+                newIndex = handleDpadHorizontal(iconIdx, cntX, cntY, map, -1 /*increment*/, isRtl);
                 if (!isRtl && newIndex == NOOP && pageIndex > 0) {
                     newIndex = PREVIOUS_PAGE_RIGHT_COLUMN;
                 } else if (isRtl && newIndex == NOOP && pageIndex < pageCount - 1) {
@@ -98,7 +103,7 @@
                 }
                 break;
             case KeyEvent.KEYCODE_DPAD_RIGHT:
-                newIndex = handleDpadHorizontal(iconIdx, cntX, cntY, map, 1 /*increment*/);
+                newIndex = handleDpadHorizontal(iconIdx, cntX, cntY, map, 1 /*increment*/, isRtl);
                 if (!isRtl && newIndex == NOOP && pageIndex < pageCount - 1) {
                     newIndex = NEXT_PAGE_LEFT_COLUMN;
                 } else if (isRtl && newIndex == NOOP && pageIndex > 0) {
@@ -185,23 +190,37 @@
      * in portrait orientation. In landscape, [(icon + hotseat) column count x (icon row count)]
      */
     // TODO: get rid of the dynamic matrix creation
-    public static int[][] createSparseMatrix(CellLayout iconLayout, CellLayout hotseatLayout,
-            boolean isHorizontal, int allappsiconRank, boolean includeAllappsicon) {
+    public static int[][] createSparseMatrixWithHotseat(CellLayout iconLayout,
+            CellLayout hotseatLayout, boolean isHotseatHorizontal, int allappsiconRank) {
 
         ViewGroup iconParent = iconLayout.getShortcutsAndWidgets();
         ViewGroup hotseatParent = hotseatLayout.getShortcutsAndWidgets();
 
+        boolean moreIconsInHotseatThanWorkspace = isHotseatHorizontal ?
+                hotseatLayout.getCountX() > iconLayout.getCountX() :
+                hotseatLayout.getCountY() > iconLayout.getCountY();
+
         int m, n;
-        if (isHorizontal) {
-            m = iconLayout.getCountX();
+        if (isHotseatHorizontal) {
+            m = hotseatLayout.getCountX();
             n = iconLayout.getCountY() + hotseatLayout.getCountY();
         } else {
             m = iconLayout.getCountX() + hotseatLayout.getCountX();
-            n = iconLayout.getCountY();
+            n = hotseatLayout.getCountY();
         }
         int[][] matrix = createFullMatrix(m, n);
-
-        // Iterate thru the children of the top parent.
+        if (moreIconsInHotseatThanWorkspace) {
+            if (isHotseatHorizontal) {
+                for (int j = 0; j < n; j++) {
+                    matrix[allappsiconRank][j] = ALL_APPS_COLUMN;
+                }
+            } else {
+                for (int j = 0; j < m; j++) {
+                    matrix[j][allappsiconRank] = ALL_APPS_COLUMN;
+                }
+            }
+        }
+        // Iterate thru the children of the workspace.
         for (int i = 0; i < iconParent.getChildCount(); i++) {
             View cell = iconParent.getChildAt(i);
             if (!cell.isFocusable()) {
@@ -209,31 +228,29 @@
             }
             int cx = ((CellLayout.LayoutParams) cell.getLayoutParams()).cellX;
             int cy = ((CellLayout.LayoutParams) cell.getLayoutParams()).cellY;
+            if (moreIconsInHotseatThanWorkspace) {
+                if (isHotseatHorizontal && cx >= allappsiconRank) {
+                    // Add 1 to account for the All Apps button.
+                    cx++;
+                }
+                if (!isHotseatHorizontal && cy >= allappsiconRank) {
+                    // Add 1 to account for the All Apps button.
+                    cy++;
+                }
+            }
             matrix[cx][cy] = i;
         }
 
-        // Iterate thru the children of the bottom parent
-        // The hotseat view group contains one more item than iconLayout column count.
-        // If {@param allappsiconRank} not negative, then the last icon in the hotseat
-        // is truncated. If it is negative, then all apps icon index is not inserted.
-        for(int i = hotseatParent.getChildCount() - 1; i >= (includeAllappsicon ? 0 : 1); i--) {
-            int delta = 0;
-            if (isHorizontal) {
+        // Iterate thru the children of the hotseat.
+        for (int i = hotseatParent.getChildCount() - 1; i >= 0; i--) {
+            if (isHotseatHorizontal) {
                 int cx = ((CellLayout.LayoutParams)
                         hotseatParent.getChildAt(i).getLayoutParams()).cellX;
-                if ((includeAllappsicon && cx >= allappsiconRank) ||
-                        (!includeAllappsicon && cx > allappsiconRank)) {
-                        delta = -1;
-                }
-                matrix[cx + delta][iconLayout.getCountY()] = iconParent.getChildCount() + i;
+                matrix[cx][iconLayout.getCountY()] = iconParent.getChildCount() + i;
             } else {
                 int cy = ((CellLayout.LayoutParams)
                         hotseatParent.getChildAt(i).getLayoutParams()).cellY;
-                if ((includeAllappsicon && cy >= allappsiconRank) ||
-                        (!includeAllappsicon && cy > allappsiconRank)) {
-                        delta = -1;
-                }
-                matrix[iconLayout.getCountX()][cy + delta] = iconParent.getChildCount() + i;
+                matrix[iconLayout.getCountX()][cy] = iconParent.getChildCount() + i;
             }
         }
         if (DEBUG) {
@@ -253,7 +270,8 @@
      * @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) {
+    public static int[][] createSparseMatrixWithPivotColumn(CellLayout iconLayout,
+            int pivotX, int pivotY) {
 
         ViewGroup iconParent = iconLayout.getShortcutsAndWidgets();
 
@@ -300,7 +318,7 @@
      */
     // TODO: add unit tests to verify all permutation.
     private static int handleDpadHorizontal(int iconIdx, int cntX, int cntY,
-            int[][] matrix, int increment) {
+            int[][] matrix, int increment, boolean isRtl) {
         if(matrix == null) {
             throw new IllegalStateException("Dpad navigation requires a matrix.");
         }
@@ -323,8 +341,9 @@
         }
 
         // Rule1: check first in the horizontal direction
-        for (int i = xPos + increment; 0 <= i && i < cntX; i = i + increment) {
-            if ((newIconIndex = inspectMatrix(i, yPos, cntX, cntY, matrix)) != NOOP) {
+        for (int x = xPos + increment; 0 <= x && x < cntX; x += increment) {
+            if ((newIconIndex = inspectMatrix(x, yPos, cntX, cntY, matrix)) != NOOP
+                    && newIconIndex != ALL_APPS_COLUMN) {
                 return newIconIndex;
             }
         }
@@ -333,30 +352,40 @@
         //              (x2-n, yPos + 2*increment), (x2-n, yPos - 2*increment)
         int nextYPos1;
         int nextYPos2;
-        int i = -1;
+        boolean haveCrossedAllAppsColumn1 = false;
+        boolean haveCrossedAllAppsColumn2 = false;
+        int x = -1;
         for (int coeff = 1; coeff < cntY; coeff++) {
             nextYPos1 = yPos + coeff * increment;
             nextYPos2 = yPos - coeff * increment;
-            for (i = xPos + increment * coeff; 0 <= i && i < cntX; i = i + increment) {
-                if ((newIconIndex = inspectMatrix(i, nextYPos1, cntX, cntY, matrix)) != NOOP) {
+            x = xPos + increment * coeff;
+            if (inspectMatrix(x, nextYPos1, cntX, cntY, matrix) == ALL_APPS_COLUMN) {
+                haveCrossedAllAppsColumn1 = true;
+            }
+            if (inspectMatrix(x, nextYPos2, cntX, cntY, matrix) == ALL_APPS_COLUMN) {
+                haveCrossedAllAppsColumn2 = true;
+            }
+            for (; 0 <= x && x < cntX; x += increment) {
+                int offset1 = haveCrossedAllAppsColumn1 && x < cntX - 1 ? increment : 0;
+                newIconIndex = inspectMatrix(x, nextYPos1 + offset1, cntX, cntY, matrix);
+                if (newIconIndex != NOOP) {
                     return newIconIndex;
                 }
-                if ((newIconIndex = inspectMatrix(i, nextYPos2, cntX, cntY, matrix)) != NOOP) {
+                int offset2 = haveCrossedAllAppsColumn2 && x < cntX - 1 ? -increment : 0;
+                newIconIndex = inspectMatrix(x, nextYPos2 + offset2, cntX, cntY, matrix);
+                if (newIconIndex != NOOP) {
                     return newIconIndex;
                 }
             }
         }
 
-        // Rule 3: if switching between pages, do a brute-force search to find an item that was
-        //         missed by rules 1 and 2 (such as when going from a bottom right icon to top left)
+        // Rule3: if switching between pages, do a brute-force search to find an item that was
+        //        missed by rules 1 and 2 (such as when going from a bottom right icon to top left)
         if (iconIdx == PIVOT) {
-            for (int x = xPos + increment; 0 <= x && x < cntX; x = x + increment) {
-                for (int y = 0; y < cntY; y++) {
-                    if ((newIconIndex = inspectMatrix(x, y, cntX, cntY, matrix)) != NOOP) {
-                        return newIconIndex;
-                    }
-                }
+            if (isRtl) {
+                return increment < 0 ? NEXT_PAGE_FIRST_ITEM : PREVIOUS_PAGE_LAST_ITEM;
             }
+            return increment < 0 ? PREVIOUS_PAGE_LAST_ITEM : NEXT_PAGE_FIRST_ITEM;
         }
         return newIconIndex;
     }
@@ -396,8 +425,9 @@
         }
 
         // Rule1: check first in the dpad direction
-        for (int j = yPos + increment; 0 <= j && j <cntY && 0 <= j; j = j + increment) {
-            if ((newIconIndex = inspectMatrix(xPos, j, cntX, cntY, matrix)) != NOOP) {
+        for (int y = yPos + increment; 0 <= y && y <cntY && 0 <= y; y += increment) {
+            if ((newIconIndex = inspectMatrix(xPos, y, cntX, cntY, matrix)) != NOOP
+                    && newIconIndex != ALL_APPS_COLUMN) {
                 return newIconIndex;
             }
         }
@@ -406,15 +436,28 @@
         //              (xPos + 2*increment, y_(2-n))), (xPos - 2*increment, y_(2-n))
         int nextXPos1;
         int nextXPos2;
-        int j = -1;
+        boolean haveCrossedAllAppsColumn1 = false;
+        boolean haveCrossedAllAppsColumn2 = false;
+        int y = -1;
         for (int coeff = 1; coeff < cntX; coeff++) {
             nextXPos1 = xPos + coeff * increment;
             nextXPos2 = xPos - coeff * increment;
-            for (j = yPos + increment * coeff; 0 <= j && j < cntY; j = j + increment) {
-                if ((newIconIndex = inspectMatrix(nextXPos1, j, cntX, cntY, matrix)) != NOOP) {
+            y = yPos + increment * coeff;
+            if (inspectMatrix(nextXPos1, y, cntX, cntY, matrix) == ALL_APPS_COLUMN) {
+                haveCrossedAllAppsColumn1 = true;
+            }
+            if (inspectMatrix(nextXPos2, y, cntX, cntY, matrix) == ALL_APPS_COLUMN) {
+                haveCrossedAllAppsColumn2 = true;
+            }
+            for (; 0 <= y && y < cntY; y = y + increment) {
+                int offset1 = haveCrossedAllAppsColumn1 && y < cntY - 1 ? increment : 0;
+                newIconIndex = inspectMatrix(nextXPos1 + offset1, y, cntX, cntY, matrix);
+                if (newIconIndex != NOOP) {
                     return newIconIndex;
                 }
-                if ((newIconIndex = inspectMatrix(nextXPos2, j, cntX, cntY, matrix)) != NOOP) {
+                int offset2 = haveCrossedAllAppsColumn2 && y < cntY - 1 ? -increment : 0;
+                newIconIndex = inspectMatrix(nextXPos2 + offset2, y, cntX, cntY, matrix);
+                if (newIconIndex != NOOP) {
                     return newIconIndex;
                 }
             }
@@ -481,6 +524,7 @@
             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";
+            case ALL_APPS_COLUMN:           return "ALL_APPS_COLUMN";
             default:
                 return Integer.toString(index);
         }
diff --git a/tests/src/com/android/launcher3/util/FocusLogicTest.java b/tests/src/com/android/launcher3/util/FocusLogicTest.java
index 2c2f0d3..a0d17c8 100644
--- a/tests/src/com/android/launcher3/util/FocusLogicTest.java
+++ b/tests/src/com/android/launcher3/util/FocusLogicTest.java
@@ -19,6 +19,7 @@
 import android.test.AndroidTestCase;
 import android.test.suitebuilder.annotation.SmallTest;
 import android.view.KeyEvent;
+import android.view.View;
 
 import com.android.launcher3.util.FocusLogic;
 
@@ -66,7 +67,7 @@
                 {-1, -1, -1, -1, -1, -1},
                 {100, 1, -1, -1, -1, -1},
         });
-        int i = FocusLogic.handleKeyEvent(KeyEvent.KEYCODE_DPAD_RIGHT, 6, 5, map, 100, 1, 2, false);
+        int i = FocusLogic.handleKeyEvent(KeyEvent.KEYCODE_DPAD_RIGHT, map, 100, 1, 2, false);
         assertEquals(1, i);
     }
 
@@ -78,8 +79,169 @@
                 {-1, -1, -1, -1, -1, -1},
                 {100, -1, -1, -1, -1, -1},
         });
-        int i = FocusLogic.handleKeyEvent(KeyEvent.KEYCODE_DPAD_RIGHT, 6, 5, map, 100, 1, 2, false);
+        int i = FocusLogic.handleKeyEvent(KeyEvent.KEYCODE_DPAD_RIGHT, map, 100, 1, 2, false);
+        assertEquals(FocusLogic.NEXT_PAGE_FIRST_ITEM, i);
+    }
+
+    public void testMoveIntoHotseatWithEqualHotseatAndWorkspaceColumns() {
+        // Test going from an icon right above the All Apps button to the All Apps button.
+        int[][] map = transpose(new int[][] {
+                {-1, -1, -1, -1, -1},
+                {-1, -1, -1, -1, -1},
+                {-1, -1, -1, -1, -1},
+                {-1, -1,  0, -1, -1},
+                { 2,  3,  1,  4,  5},
+        });
+        int i = FocusLogic.handleKeyEvent(KeyEvent.KEYCODE_DPAD_DOWN, map, 0, 1, 1, true);
+        assertEquals(1, i);
+        // Test going from an icon above and to the right of the All Apps
+        // button to an icon to the right of the All Apps button.
+        map = transpose(new int[][] {
+                {-1, -1, -1, -1, -1},
+                {-1, -1, -1, -1, -1},
+                {-1, -1, -1, -1, -1},
+                {-1, -1, -1,  0, -1},
+                { 2,  3,  1,  4,  5},
+        });
+        i = FocusLogic.handleKeyEvent(KeyEvent.KEYCODE_DPAD_DOWN, map, 0, 1, 1, true);
+        assertEquals(4, i);
+    }
+
+    public void testMoveIntoHotseatWithExtraColumnForAllApps() {
+        // Test going from an icon above and to the left
+        // of the All Apps button to the All Apps button.
+        int[][] map = transpose(new int[][] {
+                {-1, -1, -1,-11, -1, -1, -1},
+                {-1, -1, -1,-11, -1, -1, -1},
+                {-1, -1, -1,-11, -1, -1, -1},
+                {-1, -1, -1,-11, -1, -1, -1},
+                {-1, -1,  0,-11, -1, -1, -1},
+                {-1, -1, -1,  1,  1, -1, -1},
+        });
+        int i = FocusLogic.handleKeyEvent(KeyEvent.KEYCODE_DPAD_DOWN, map, 0, 1, 1, true);
+        assertEquals(1, i);
+        // Test going from an icon above and to the right
+        // of the All Apps button to the All Apps button.
+        map = transpose(new int[][] {
+                {-1, -1, -1,-11, -1, -1, -1},
+                {-1, -1, -1,-11, -1, -1, -1},
+                {-1, -1, -1,-11, -1, -1, -1},
+                {-1, -1, -1,-11, -1, -1, -1},
+                {-1, -1, -1,-11,  0, -1, -1},
+                {-1, -1, -1,  1, -1, -1, -1},
+        });
+        i = FocusLogic.handleKeyEvent(KeyEvent.KEYCODE_DPAD_DOWN, map, 0, 1, 1, true);
+        assertEquals(1, i);
+        // Test going from the All Apps button to an icon
+        // above and to the right of the All Apps button.
+        map = transpose(new int[][] {
+                {-1, -1, -1,-11, -1, -1, -1},
+                {-1, -1, -1,-11, -1, -1, -1},
+                {-1, -1, -1,-11, -1, -1, -1},
+                {-1, -1, -1,-11, -1, -1, -1},
+                {-1, -1, -1,-11,  0, -1, -1},
+                {-1, -1, -1,  1, -1, -1, -1},
+        });
+        i = FocusLogic.handleKeyEvent(KeyEvent.KEYCODE_DPAD_UP, map, 1, 1, 1, true);
         assertEquals(0, i);
+        // Test going from an icon above and to the left of the
+        // All Apps button in landscape to the All Apps button.
+        map = transpose(new int[][] {
+                { -1, -1, -1, -1, -1},
+                { -1, -1, -1,  0, -1},
+                {-11,-11,-11,-11,  1},
+                { -1, -1, -1, -1, -1},
+                { -1, -1, -1, -1, -1},
+        });
+        i = FocusLogic.handleKeyEvent(KeyEvent.KEYCODE_DPAD_RIGHT, map, 0, 1, 1, true);
+        assertEquals(1, i);
+        // Test going from the All Apps button in landscape to
+        // an icon above and to the left of the All Apps button.
+        map = transpose(new int[][] {
+                { -1, -1, -1, -1, -1},
+                { -1, -1, -1,  0, -1},
+                {-11,-11,-11,-11,  1},
+                { -1, -1, -1, -1, -1},
+                { -1, -1, -1, -1, -1},
+        });
+        i = FocusLogic.handleKeyEvent(KeyEvent.KEYCODE_DPAD_LEFT, map, 1, 1, 1, true);
+        assertEquals(0, i);
+        // Test that going to the hotseat always goes to the same row as the original icon.
+        map = transpose(new int[][]{
+                { 0,  1,  2,-11,  3,  4,  5},
+                {-1, -1, -1,-11, -1, -1, -1},
+                {-1, -1, -1,-11, -1, -1, -1},
+                {-1, -1, -1,-11, -1, -1, -1},
+                {-1, -1, -1,-11, -1, -1, -1},
+                { 7,  8,  9,  6, 10, 11, 12},
+        });
+        i = FocusLogic.handleKeyEvent(KeyEvent.KEYCODE_DPAD_DOWN, map, 0, 1, 1, true);
+        assertEquals(7, i);
+        i = FocusLogic.handleKeyEvent(KeyEvent.KEYCODE_DPAD_DOWN, map, 1, 1, 1, true);
+        assertEquals(8, i);
+        i = FocusLogic.handleKeyEvent(KeyEvent.KEYCODE_DPAD_DOWN, map, 2, 1, 1, true);
+        assertEquals(9, i);
+        i = FocusLogic.handleKeyEvent(KeyEvent.KEYCODE_DPAD_DOWN, map, 3, 1, 1, true);
+        assertEquals(10, i);
+        i = FocusLogic.handleKeyEvent(KeyEvent.KEYCODE_DPAD_DOWN, map, 4, 1, 1, true);
+        assertEquals(11, i);
+        i = FocusLogic.handleKeyEvent(KeyEvent.KEYCODE_DPAD_DOWN, map, 5, 1, 1, true);
+        assertEquals(12, i);
+    }
+
+    public void testCrossingAllAppsColumn() {
+        // Test crossing from left to right in portrait.
+        int[][] map = transpose(new int[][] {
+                {-1, -1,-11, -1, -1},
+                {-1,  0,-11, -1, -1},
+                {-1, -1,-11,  1, -1},
+                {-1, -1,-11, -1, -1},
+                {-1, -1,  2, -1, -1},
+        });
+        int i = FocusLogic.handleKeyEvent(KeyEvent.KEYCODE_DPAD_DOWN, map, 0, 1, 1, true);
+        assertEquals(1, i);
+        // Test crossing from right to left in portrait.
+        map = transpose(new int[][] {
+                {-1, -1,-11, -1, -1},
+                {-1, -1,-11,  0, -1},
+                {-1,  1,-11, -1, -1},
+                {-1, -1,-11, -1, -1},
+                {-1, -1,  2, -1, -1},
+        });
+        i = FocusLogic.handleKeyEvent(KeyEvent.KEYCODE_DPAD_DOWN, map, 0, 1, 1, true);
+        assertEquals(1, i);
+        // Test crossing from left to right in landscape.
+        map = transpose(new int[][] {
+                { -1, -1, -1, -1, -1},
+                { -1, -1, -1,  0, -1},
+                {-11,-11,-11,-11,  2},
+                { -1,  1, -1, -1, -1},
+                { -1, -1  -1, -1, -1},
+        });
+        i = FocusLogic.handleKeyEvent(KeyEvent.KEYCODE_DPAD_LEFT, map, 0, 1, 1, true);
+        assertEquals(1, i);
+        // Test crossing from right to left in landscape.
+        map = transpose(new int[][] {
+                { -1, -1, -1, -1, -1},
+                { -1,  0, -1, -1, -1},
+                {-11,-11,-11,-11,  2},
+                { -1, -1,  1, -1, -1},
+                { -1, -1, -1, -1, -1},
+        });
+        i = FocusLogic.handleKeyEvent(KeyEvent.KEYCODE_DPAD_RIGHT, map, 0, 1, 1, true);
+        assertEquals(1, i);
+        // Test NOT crossing it, if the All Apps button is the only suitable candidate.
+        map = transpose(new int[][]{
+                {-1, 0, -1, -1, -1},
+                {-1, 1, -1, -1, -1},
+                {-11, -11, -11, -11, 4},
+                {-1, 2, -1, -1, -1},
+                {-1, 3, -1, -1, -1},
+        });
+        i = FocusLogic.handleKeyEvent(KeyEvent.KEYCODE_DPAD_RIGHT, map, 1, 1, 1, true);
+        assertEquals(4, i);
+        i = FocusLogic.handleKeyEvent(KeyEvent.KEYCODE_DPAD_RIGHT, map, 2, 1, 1, true);
+        assertEquals(4, i);
     }
 
     /** Transposes the matrix so that we can write it in human-readable format in the tests. */