Merge "Clearing DB instead of deleting the DB file." into ub-launcher3-burnaby-polish
diff --git a/res/values-sw600dp/dimens.xml b/res/values-sw600dp/dimens.xml
index fb54f12..feb1a00 100644
--- a/res/values-sw600dp/dimens.xml
+++ b/res/values-sw600dp/dimens.xml
@@ -21,6 +21,7 @@
     <dimen name="all_apps_icon_top_bottom_padding">12dp</dimen>
     <dimen name="all_apps_background_canvas_width">850dp</dimen>
     <dimen name="all_apps_background_canvas_height">525dp</dimen>
+    <dimen name="all_apps_icon_width_gap">36dp</dimen>
 
 <!-- Cling -->
     <dimen name="cling_migration_logo_height">400dp</dimen>
diff --git a/src/com/android/launcher3/CellLayout.java b/src/com/android/launcher3/CellLayout.java
index 0a2a017..94e3e41 100644
--- a/src/com/android/launcher3/CellLayout.java
+++ b/src/com/android/launcher3/CellLayout.java
@@ -902,9 +902,7 @@
                 ((LayoutParams) mShortcutsAndWidgets.getChildAt(0).getLayoutParams()).isFullscreen;
         int left = getPaddingLeft();
         if (!isFullscreen) {
-            int offset = getMeasuredWidth() - getPaddingLeft() - getPaddingRight() -
-                    (mCountX * mCellWidth);
-            left += (int) Math.ceil(offset / 2f);
+            left += (int) Math.ceil(getUnusedHorizontalSpace() / 2f);
         }
         int top = getPaddingTop();
 
@@ -916,6 +914,15 @@
                 top + b - t);
     }
 
+    /**
+     * Returns the amount of space left over after subtracting padding and cells. This space will be
+     * very small, a few pixels at most, and is a result of rounding down when calculating the cell
+     * width in {@link DeviceProfile#calculateCellWidth(int, int)}.
+     */
+    public int getUnusedHorizontalSpace() {
+        return getMeasuredWidth() - getPaddingLeft() - getPaddingRight() - (mCountX * mCellWidth);
+    }
+
     @Override
     protected void onSizeChanged(int w, int h, int oldw, int oldh) {
         super.onSizeChanged(w, h, oldw, oldh);
@@ -1048,8 +1055,8 @@
         return false;
     }
 
-    void visualizeDropLocation(View v, Bitmap dragOutline, int originX, int originY, int cellX,
-            int cellY, int spanX, int spanY, boolean resize, DropTarget.DragObject dragObject) {
+    void visualizeDropLocation(View v, Bitmap dragOutline, int cellX, int cellY, int spanX,
+            int spanY, boolean resize, DropTarget.DragObject dragObject) {
         final int oldDragCellX = mDragCell[0];
         final int oldDragCellY = mDragCell[1];
 
diff --git a/src/com/android/launcher3/DragLayer.java b/src/com/android/launcher3/DragLayer.java
index 1c18747..ad9063c 100644
--- a/src/com/android/launcher3/DragLayer.java
+++ b/src/com/android/launcher3/DragLayer.java
@@ -565,10 +565,6 @@
         resizeFrame.snapToWidget(false);
     }
 
-    public void animateViewIntoPosition(DragView dragView, final View child) {
-        animateViewIntoPosition(dragView, child, null, null);
-    }
-
     public void animateViewIntoPosition(DragView dragView, final int[] pos, float alpha,
             float scaleX, float scaleY, int animationEndStyle, Runnable onFinishRunnable,
             int duration) {
diff --git a/src/com/android/launcher3/DragView.java b/src/com/android/launcher3/DragView.java
index 2acfc61..a584667 100644
--- a/src/com/android/launcher3/DragView.java
+++ b/src/com/android/launcher3/DragView.java
@@ -321,10 +321,10 @@
         setTranslationY(touchY - mRegistrationY);
         // Post the animation to skip other expensive work happening on the first frame
         post(new Runnable() {
-                public void run() {
-                    mAnim.start();
-                }
-            });
+            public void run() {
+                mAnim.start();
+            }
+        });
     }
 
     public void cancelAnimation() {
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/IconCache.java b/src/com/android/launcher3/IconCache.java
index 59ab839..2675d27 100644
--- a/src/com/android/launcher3/IconCache.java
+++ b/src/com/android/launcher3/IconCache.java
@@ -179,15 +179,7 @@
 
     private Bitmap makeDefaultIcon(UserHandleCompat user) {
         Drawable unbadged = getFullResDefaultActivityIcon();
-        Drawable d = mUserManager.getBadgedDrawableForUser(unbadged, user);
-        Bitmap b = Bitmap.createBitmap(Math.max(d.getIntrinsicWidth(), 1),
-                Math.max(d.getIntrinsicHeight(), 1),
-                Bitmap.Config.ARGB_8888);
-        Canvas c = new Canvas(b);
-        d.setBounds(0, 0, b.getWidth(), b.getHeight());
-        d.draw(c);
-        c.setBitmap(null);
-        return b;
+        return Utilities.createBadgedIconBitmap(unbadged, user, mContext);
     }
 
     /**
@@ -380,7 +372,8 @@
         }
         if (entry == null) {
             entry = new CacheEntry();
-            entry.icon = Utilities.createIconBitmap(app.getBadgedIcon(mIconDpi), mContext);
+            entry.icon = Utilities.createBadgedIconBitmap(
+                    app.getIcon(mIconDpi), app.getUser(), mContext);
         }
         entry.title = app.getLabel();
         entry.contentDescription = mUserManager.getBadgedLabelForUser(entry.title, app.getUser());
@@ -542,7 +535,8 @@
             // Check the DB first.
             if (!getEntryFromDB(cacheKey, entry, useLowResIcon)) {
                 if (info != null) {
-                    entry.icon = Utilities.createIconBitmap(info.getBadgedIcon(mIconDpi), mContext);
+                    entry.icon = Utilities.createBadgedIconBitmap(
+                            info.getIcon(mIconDpi), info.getUser(), mContext);
                 } else {
                     if (usePackageIcon) {
                         CacheEntry packageEntry = getEntryForPackageLocked(
@@ -623,9 +617,8 @@
                     if (appInfo == null) {
                         throw new NameNotFoundException("ApplicationInfo is null");
                     }
-                    Drawable drawable = mUserManager.getBadgedDrawableForUser(
-                            appInfo.loadIcon(mPackageManager), user);
-                    entry.icon = Utilities.createIconBitmap(drawable, mContext);
+                    entry.icon = Utilities.createBadgedIconBitmap(
+                            appInfo.loadIcon(mPackageManager), user, mContext);
                     entry.title = appInfo.loadLabel(mPackageManager);
                     entry.contentDescription = mUserManager.getBadgedLabelForUser(entry.title, user);
                     entry.isLowResIcon = false;
@@ -789,7 +782,7 @@
     }
 
     private static final class IconDB extends SQLiteOpenHelper {
-        private final static int DB_VERSION = 7;
+        private final static int DB_VERSION = 8;
 
         private final static String TABLE_NAME = "icons";
         private final static String COLUMN_ROWID = "rowid";
diff --git a/src/com/android/launcher3/InstallShortcutReceiver.java b/src/com/android/launcher3/InstallShortcutReceiver.java
index 571d99a..7f15160 100644
--- a/src/com/android/launcher3/InstallShortcutReceiver.java
+++ b/src/com/android/launcher3/InstallShortcutReceiver.java
@@ -144,29 +144,45 @@
         if (!ACTION_INSTALL_SHORTCUT.equals(data.getAction())) {
             return;
         }
-
-        PendingInstallShortcutInfo info = new PendingInstallShortcutInfo(data, context);
-        if (info.launchIntent == null || info.label == null) {
-            if (DBG) Log.e(TAG, "Invalid install shortcut intent");
-            return;
+        PendingInstallShortcutInfo info = createPendingInfo(context, data);
+        if (info != null) {
+            queuePendingShortcutInfo(info, context);
         }
-
-        info = convertToLauncherActivityIfPossible(info);
-        queuePendingShortcutInfo(info, context);
     }
 
-    public static ShortcutInfo fromShortcutIntent(Context context, Intent data) {
+    /**
+     * @return true is the extra is either null or is of type {@param type}
+     */
+    private static boolean isValidExtraType(Intent intent, String key, Class type) {
+        Object extra = intent.getParcelableExtra(key);
+        return extra == null || type.isInstance(extra);
+    }
+
+    /**
+     * Verifies the intent and creates a {@link PendingInstallShortcutInfo}
+     */
+    private static PendingInstallShortcutInfo createPendingInfo(Context context, Intent data) {
+        if (!isValidExtraType(data, Intent.EXTRA_SHORTCUT_INTENT, Intent.class) ||
+                !(isValidExtraType(data, Intent.EXTRA_SHORTCUT_ICON_RESOURCE,
+                        Intent.ShortcutIconResource.class)) ||
+                !(isValidExtraType(data, Intent.EXTRA_SHORTCUT_ICON, Bitmap.class))) {
+
+            if (DBG) Log.e(TAG, "Invalid install shortcut intent");
+            return null;
+        }
+
         PendingInstallShortcutInfo info = new PendingInstallShortcutInfo(data, context);
         if (info.launchIntent == null || info.label == null) {
             if (DBG) Log.e(TAG, "Invalid install shortcut intent");
             return null;
         }
-        info = convertToLauncherActivityIfPossible(info);
-        return info.getShortcutInfo();
+
+        return convertToLauncherActivityIfPossible(info);
     }
 
-    static void queueInstallShortcut(LauncherActivityInfoCompat info, Context context) {
-        queuePendingShortcutInfo(new PendingInstallShortcutInfo(info, context), context);
+    public static ShortcutInfo fromShortcutIntent(Context context, Intent data) {
+        PendingInstallShortcutInfo info = createPendingInfo(context, data);
+        return info == null ? null : info.getShortcutInfo();
     }
 
     private static void queuePendingShortcutInfo(PendingInstallShortcutInfo info, Context context) {
diff --git a/src/com/android/launcher3/InvariantDeviceProfile.java b/src/com/android/launcher3/InvariantDeviceProfile.java
index e983eb1..a91181d 100644
--- a/src/com/android/launcher3/InvariantDeviceProfile.java
+++ b/src/com/android/launcher3/InvariantDeviceProfile.java
@@ -17,7 +17,6 @@
 package com.android.launcher3;
 
 import android.annotation.TargetApi;
-import android.app.ActivityManager;
 import android.content.Context;
 import android.graphics.Point;
 import android.util.DisplayMetrics;
@@ -66,10 +65,10 @@
      */
     public int numFolderRows;
     public int numFolderColumns;
-    float iconSize;
-    int iconBitmapSize;
-    int fillResIconDpi;
-    float iconTextSize;
+    public float iconSize;
+    public int iconBitmapSize;
+    public int fillResIconDpi;
+    public float iconTextSize;
 
     /**
      * Number of icons inside the hotseat area.
@@ -84,9 +83,6 @@
     DeviceProfile landscapeProfile;
     DeviceProfile portraitProfile;
 
-    // On Marshmallow the status bar is no longer opaque, when drawn on the right.
-    public boolean isRightInsetOpaque;
-
     InvariantDeviceProfile() {
     }
 
@@ -170,9 +166,6 @@
                 largeSide, smallSide, true /* isLandscape */);
         portraitProfile = new DeviceProfile(context, this, smallestSize, largestSize,
                 smallSide, largeSide, false /* isLandscape */);
-
-        isRightInsetOpaque = !Utilities.ATLEAST_MARSHMALLOW ||
-                context.getSystemService(ActivityManager.class).isLowRamDevice();
     }
 
     ArrayList<InvariantDeviceProfile> getPredefinedDeviceProfiles() {
diff --git a/src/com/android/launcher3/Launcher.java b/src/com/android/launcher3/Launcher.java
index a379acc..0323e23 100644
--- a/src/com/android/launcher3/Launcher.java
+++ b/src/com/android/launcher3/Launcher.java
@@ -900,8 +900,7 @@
     }
 
     @Thunk void completeTwoStageWidgetDrop(final int resultCode, final int appWidgetId) {
-        CellLayout cellLayout =
-                (CellLayout) mWorkspace.getScreenWithId(mPendingAddInfo.screenId);
+        CellLayout cellLayout = mWorkspace.getScreenWithId(mPendingAddInfo.screenId);
         Runnable onCompleteRunnable = null;
         int animationType = 0;
 
@@ -2530,6 +2529,9 @@
         if (mAppWidgetHost != null) {
             mAppWidgetHost.startListening();
         }
+
+        // Recreate the QSB, as the widget has been reset.
+        bindSearchProviderChanged();
     }
 
     /**
diff --git a/src/com/android/launcher3/LauncherAppWidgetHostView.java b/src/com/android/launcher3/LauncherAppWidgetHostView.java
index 34c2943..c49d43f 100644
--- a/src/com/android/launcher3/LauncherAppWidgetHostView.java
+++ b/src/com/android/launcher3/LauncherAppWidgetHostView.java
@@ -244,4 +244,27 @@
         }
         super.onFocusChanged(gainFocus, direction, previouslyFocusedRect);
     }
+
+    @Override
+    public void requestChildFocus(View child, View focused) {
+        super.requestChildFocus(child, focused);
+        dispatchChildFocus(focused != null);
+    }
+
+    @Override
+    public void clearChildFocus(View child) {
+        super.clearChildFocus(child);
+        dispatchChildFocus(false);
+    }
+
+    @Override
+    public boolean dispatchUnhandledMove(View focused, int direction) {
+        return mChildrenFocused;
+    }
+
+    private void dispatchChildFocus(boolean focused) {
+        if (getOnFocusChangeListener() != null) {
+            getOnFocusChangeListener().onFocusChange(this, focused || isFocused());
+        }
+    }
 }
diff --git a/src/com/android/launcher3/LauncherRootView.java b/src/com/android/launcher3/LauncherRootView.java
index 1c6ca87..71ccd85 100644
--- a/src/com/android/launcher3/LauncherRootView.java
+++ b/src/com/android/launcher3/LauncherRootView.java
@@ -1,17 +1,22 @@
 package com.android.launcher3;
 
+import android.annotation.TargetApi;
+import android.app.ActivityManager;
 import android.content.Context;
 import android.graphics.Canvas;
 import android.graphics.Color;
 import android.graphics.Paint;
 import android.graphics.Rect;
 import android.util.AttributeSet;
+import android.view.View;
 
 public class LauncherRootView extends InsettableFrameLayout {
 
     private final Paint mOpaquePaint;
     private boolean mDrawRightInsetBar;
 
+    private View mAlignedView;
+
     public LauncherRootView(Context context, AttributeSet attrs) {
         super(context, attrs);
 
@@ -21,10 +26,32 @@
     }
 
     @Override
+    protected void onFinishInflate() {
+        if (getChildCount() > 0) {
+            // LauncherRootView contains only one child, which should be aligned
+            // based on the horizontal insets.
+            mAlignedView = getChildAt(0);
+        }
+        super.onFinishInflate();
+    }
+
+    @TargetApi(23)
+    @Override
     protected boolean fitSystemWindows(Rect insets) {
-        setInsets(insets);
-        mDrawRightInsetBar = mInsets.right > 0 && LauncherAppState
-                .getInstance().getInvariantDeviceProfile().isRightInsetOpaque;
+        mDrawRightInsetBar = insets.right > 0 &&
+                (!Utilities.ATLEAST_MARSHMALLOW ||
+                getContext().getSystemService(ActivityManager.class).isLowRamDevice());
+        setInsets(mDrawRightInsetBar ? new Rect(0, insets.top, 0, insets.bottom) : insets);
+
+        if (mAlignedView != null) {
+            // Apply margins on aligned view to handle left/right insets.
+            MarginLayoutParams lp = (MarginLayoutParams) mAlignedView.getLayoutParams();
+            if (lp.leftMargin != insets.left || lp.rightMargin != insets.right) {
+                lp.leftMargin = insets.left;
+                lp.rightMargin = insets.right;
+                mAlignedView.setLayoutParams(lp);
+            }
+        }
 
         return true; // I'll take it from here
     }
diff --git a/src/com/android/launcher3/LauncherStateTransitionAnimation.java b/src/com/android/launcher3/LauncherStateTransitionAnimation.java
index 3391d06..83b12a9 100644
--- a/src/com/android/launcher3/LauncherStateTransitionAnimation.java
+++ b/src/com/android/launcher3/LauncherStateTransitionAnimation.java
@@ -641,11 +641,11 @@
                     }
                     animation.play(reveal);
                 }
-
-                dispatchOnLauncherTransitionPrepare(fromView, animated, true);
-                dispatchOnLauncherTransitionPrepare(toView, animated, true);
             }
 
+            dispatchOnLauncherTransitionPrepare(fromView, animated, true);
+            dispatchOnLauncherTransitionPrepare(toView, animated, true);
+
             animation.addListener(new AnimatorListenerAdapter() {
                 @Override
                 public void onAnimationEnd(Animator animation) {
diff --git a/src/com/android/launcher3/PagedView.java b/src/com/android/launcher3/PagedView.java
index 9258360..b6d3cc4 100644
--- a/src/com/android/launcher3/PagedView.java
+++ b/src/com/android/launcher3/PagedView.java
@@ -202,9 +202,6 @@
     protected final Rect mInsets = new Rect();
     protected final boolean mIsRtl;
 
-    // When set to true, full screen content and overscroll effect is shited inside by right inset.
-    protected boolean mIgnoreRightInset;
-
     // Edge effect
     private final LauncherEdgeEffect mEdgeGlowLeft = new LauncherEdgeEffect();
     private final LauncherEdgeEffect mEdgeGlowRight = new LauncherEdgeEffect();
@@ -822,8 +819,7 @@
                     childWidthMode = MeasureSpec.EXACTLY;
                     childHeightMode = MeasureSpec.EXACTLY;
 
-                    childWidth = getViewportWidth() - mInsets.left
-                            - (mIgnoreRightInset ? mInsets.right : 0);
+                    childWidth = getViewportWidth() - mInsets.left - mInsets.right;
                     childHeight = getViewportHeight();
                 }
                 if (referenceChildWidth == 0) {
@@ -1182,9 +1178,8 @@
 
                 getEdgeVerticalPostion(sTmpIntPoint);
 
-                int width = mIgnoreRightInset ? (display.width() - mInsets.right) : display.width();
-                canvas.translate(sTmpIntPoint[0] - display.top, -width);
-                mEdgeGlowRight.setSize(sTmpIntPoint[1] - sTmpIntPoint[0], width);
+                canvas.translate(sTmpIntPoint[0] - display.top, -display.width());
+                mEdgeGlowRight.setSize(sTmpIntPoint[1] - sTmpIntPoint[0], display.width());
                 if (mEdgeGlowRight.draw(canvas)) {
                     postInvalidateOnAnimation();
                 }
@@ -1225,7 +1220,17 @@
 
     @Override
     public boolean dispatchUnhandledMove(View focused, int direction) {
-        // XXX-RTL: This will be fixed in a future CL
+        if (super.dispatchUnhandledMove(focused, direction)) {
+            return true;
+        }
+
+        if (mIsRtl) {
+            if (direction == View.FOCUS_LEFT) {
+                direction = View.FOCUS_RIGHT;
+            } else if (direction == View.FOCUS_RIGHT) {
+                direction = View.FOCUS_LEFT;
+            }
+        }
         if (direction == View.FOCUS_LEFT) {
             if (getCurrentPage() > 0) {
                 snapToPage(getCurrentPage() - 1);
@@ -1237,7 +1242,7 @@
                 return true;
             }
         }
-        return super.dispatchUnhandledMove(focused, direction);
+        return false;
     }
 
     @Override
diff --git a/src/com/android/launcher3/ShortcutAndWidgetContainer.java b/src/com/android/launcher3/ShortcutAndWidgetContainer.java
index 56282fe..21e72e9 100644
--- a/src/com/android/launcher3/ShortcutAndWidgetContainer.java
+++ b/src/com/android/launcher3/ShortcutAndWidgetContainer.java
@@ -163,8 +163,7 @@
             lp.height = getMeasuredHeight();
         }
         int childWidthMeasureSpec = MeasureSpec.makeMeasureSpec(lp.width, MeasureSpec.EXACTLY);
-        int childheightMeasureSpec = MeasureSpec.makeMeasureSpec(lp.height,
-                MeasureSpec.EXACTLY);
+        int childheightMeasureSpec = MeasureSpec.makeMeasureSpec(lp.height, MeasureSpec.EXACTLY);
         child.measure(childWidthMeasureSpec, childheightMeasureSpec);
     }
 
diff --git a/src/com/android/launcher3/Utilities.java b/src/com/android/launcher3/Utilities.java
index 735cbeb..0a70286 100644
--- a/src/com/android/launcher3/Utilities.java
+++ b/src/com/android/launcher3/Utilities.java
@@ -60,6 +60,9 @@
 import android.view.View;
 import android.widget.Toast;
 
+import com.android.launcher3.compat.UserHandleCompat;
+import com.android.launcher3.util.IconNormalizer;
+
 import java.io.ByteArrayOutputStream;
 import java.io.IOException;
 import java.util.ArrayList;
@@ -195,9 +198,41 @@
     }
 
     /**
+     * Returns a bitmap suitable for the all apps view. The icon is badged for {@param user}.
+     * The bitmap is also visually normalized with other icons.
+     */
+    @TargetApi(Build.VERSION_CODES.LOLLIPOP)
+    public static Bitmap createBadgedIconBitmap(
+            Drawable icon, UserHandleCompat user, Context context) {
+        float scale = LauncherAppState.isDogfoodBuild() ?
+                IconNormalizer.getInstance().getScale(icon) : 1;
+        Bitmap bitmap = createIconBitmap(icon, context, scale);
+        if (Utilities.ATLEAST_LOLLIPOP && user != null
+                && !UserHandleCompat.myUserHandle().equals(user)) {
+            BitmapDrawable drawable = new BitmapDrawable(context.getResources(), bitmap);
+            Drawable badged = context.getPackageManager().getUserBadgedIcon(
+                    drawable, user.getUser());
+            if (badged instanceof BitmapDrawable) {
+                return ((BitmapDrawable) badged).getBitmap();
+            } else {
+                return createIconBitmap(badged, context);
+            }
+        } else {
+            return bitmap;
+        }
+    }
+
+    /**
      * Returns a bitmap suitable for the all apps view.
      */
     public static Bitmap createIconBitmap(Drawable icon, Context context) {
+        return createIconBitmap(icon, context, 1.0f /* scale */);
+    }
+
+    /**
+     * @param scale the scale to apply before drawing {@param icon} on the canvas
+     */
+    public static Bitmap createIconBitmap(Drawable icon, Context context, float scale) {
         synchronized (sCanvas) {
             final int iconBitmapSize = getIconBitmapSize();
 
@@ -253,7 +288,10 @@
 
             sOldBounds.set(icon.getBounds());
             icon.setBounds(left, top, left+width, top+height);
+            canvas.save(Canvas.MATRIX_SAVE_FLAG);
+            canvas.scale(scale, scale, textureWidth / 2, textureHeight / 2);
             icon.draw(canvas);
+            canvas.restore();
             icon.setBounds(sOldBounds);
             canvas.setBitmap(null);
 
@@ -310,7 +348,7 @@
     }
 
     /**
-     * Inverse of {@link #getDescendantCoordRelativeToSelf(View, int[])}.
+     * Inverse of {@link #getDescendantCoordRelativeToParent(View, View, int[], boolean)}.
      */
     public static float mapCoordInSelfToDescendent(View descendant, View root,
                                                    int[] coord) {
diff --git a/src/com/android/launcher3/Workspace.java b/src/com/android/launcher3/Workspace.java
index f046fbd..a0ecafb 100644
--- a/src/com/android/launcher3/Workspace.java
+++ b/src/com/android/launcher3/Workspace.java
@@ -359,7 +359,7 @@
         if (getChildCount() > 0) {
             // Use the first non-custom page to estimate the child position
             CellLayout cl = (CellLayout) getChildAt(numCustomPages());
-            Rect r = estimateItemPosition(cl, itemInfo, 0, 0, itemInfo.spanX, itemInfo.spanY);
+            Rect r = estimateItemPosition(cl, 0, 0, itemInfo.spanX, itemInfo.spanY);
             size[0] = r.width();
             size[1] = r.height();
             if (springLoaded) {
@@ -374,8 +374,7 @@
         }
     }
 
-    public Rect estimateItemPosition(CellLayout cl, ItemInfo pendingInfo,
-            int hCell, int vCell, int hSpan, int vSpan) {
+    public Rect estimateItemPosition(CellLayout cl, int hCell, int vCell, int hSpan, int vSpan) {
         Rect r = new Rect();
         cl.cellToRect(hCell, vCell, hSpan, vSpan, r);
         return r;
@@ -455,7 +454,6 @@
         setWallpaperDimension();
 
         setEdgeGlowColor(getResources().getColor(R.color.workspace_edge_effect_color));
-        mIgnoreRightInset = app.getInvariantDeviceProfile().isRightInsetOpaque;
     }
 
     private void setupLayoutTransition() {
@@ -652,6 +650,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;
 
@@ -1544,6 +1546,13 @@
     }
 
     @Override
+    protected void determineScrollingStart(MotionEvent ev, float touchSlopScale) {
+        if (!isSwitchingState()) {
+            super.determineScrollingStart(ev, touchSlopScale);
+        }
+    }
+
+    @Override
     public void announceForAccessibility(CharSequence text) {
         // Don't announce if apps is on top of us.
         if (!mLauncher.isAppsViewVisible()) {
@@ -3245,7 +3254,6 @@
 
             if (!nearestDropOccupied) {
                 mDragTargetLayout.visualizeDropLocation(child, mDragOutline,
-                        (int) mDragViewVisualCenter[0], (int) mDragViewVisualCenter[1],
                         mTargetCell[0], mTargetCell[1], item.spanX, item.spanY, false, d);
             } else if ((mDragMode == DRAG_MODE_NONE || mDragMode == DRAG_MODE_REORDER)
                     && !mReorderAlarm.alarmPending() && (mLastReorderX != reorderX ||
@@ -3387,7 +3395,6 @@
 
             boolean resize = resultSpan[0] != spanX || resultSpan[1] != spanY;
             mDragTargetLayout.visualizeDropLocation(child, mDragOutline,
-                (int) mDragViewVisualCenter[0], (int) mDragViewVisualCenter[1],
                 mTargetCell[0], mTargetCell[1], resultSpan[0], resultSpan[1], resize, dragObject);
         }
     }
@@ -3611,14 +3618,13 @@
     }
 
     private void getFinalPositionForDropAnimation(int[] loc, float[] scaleXY,
-            DragView dragView, CellLayout layout, ItemInfo info, int[] targetCell,
-            boolean external, boolean scale) {
+            DragView dragView, CellLayout layout, ItemInfo info, int[] targetCell, boolean scale) {
         // Now we animate the dragView, (ie. the widget or shortcut preview) into its final
         // location and size on the home screen.
         int spanX = info.spanX;
         int spanY = info.spanY;
 
-        Rect r = estimateItemPosition(layout, info, targetCell[0], targetCell[1], spanX, spanY);
+        Rect r = estimateItemPosition(layout, targetCell[0], targetCell[1], spanX, spanY);
         loc[0] = r.left;
         loc[1] = r.top;
 
@@ -3639,14 +3645,15 @@
 
         // The animation will scale the dragView about its center, so we need to center about
         // the final location.
-        loc[0] -= (dragView.getMeasuredWidth() - cellLayoutScale * r.width()) / 2;
+        loc[0] -= (dragView.getMeasuredWidth() - cellLayoutScale * r.width()) / 2
+                - Math.ceil(layout.getUnusedHorizontalSpace() / 2f);
         loc[1] -= (dragView.getMeasuredHeight() - cellLayoutScale * r.height()) / 2;
 
         scaleXY[0] = dragViewScaleX * cellLayoutScale;
         scaleXY[1] = dragViewScaleY * cellLayoutScale;
     }
 
-    public void animateWidgetDrop(ItemInfo info, CellLayout cellLayout, DragView dragView,
+    public void animateWidgetDrop(ItemInfo info, CellLayout cellLayout, final DragView dragView,
             final Runnable onCompleteRunnable, int animationType, final View finalView,
             boolean external) {
         Rect from = new Rect();
@@ -3656,7 +3663,7 @@
         float scaleXY[] = new float[2];
         boolean scalePreview = !(info instanceof PendingAddShortcutInfo);
         getFinalPositionForDropAnimation(finalPos, scaleXY, dragView, cellLayout, info, mTargetCell,
-                external, scalePreview);
+                scalePreview);
 
         Resources res = mLauncher.getResources();
         final int duration = res.getInteger(R.integer.config_dropAnimMaxDuration) - 200;
@@ -3680,7 +3687,7 @@
             if (animationType == ANIMATE_INTO_POSITION_AND_REMAIN) {
                 endStyle = DragLayer.ANIMATION_END_REMAIN_VISIBLE;
             } else {
-                endStyle = DragLayer.ANIMATION_END_DISAPPEAR;;
+                endStyle = DragLayer.ANIMATION_END_DISAPPEAR;
             }
 
             Runnable onComplete = new Runnable() {
diff --git a/src/com/android/launcher3/compat/LauncherActivityInfoCompat.java b/src/com/android/launcher3/compat/LauncherActivityInfoCompat.java
index 07ef0ef..0bc9588 100644
--- a/src/com/android/launcher3/compat/LauncherActivityInfoCompat.java
+++ b/src/com/android/launcher3/compat/LauncherActivityInfoCompat.java
@@ -33,7 +33,6 @@
     public abstract Drawable getIcon(int density);
     public abstract ApplicationInfo getApplicationInfo();
     public abstract long getFirstInstallTime();
-    public abstract Drawable getBadgedIcon(int density);
 
     /**
      * Creates a LauncherActivityInfoCompat for the primary user.
diff --git a/src/com/android/launcher3/compat/LauncherActivityInfoCompatV16.java b/src/com/android/launcher3/compat/LauncherActivityInfoCompatV16.java
index ea51aac..fee0376 100644
--- a/src/com/android/launcher3/compat/LauncherActivityInfoCompatV16.java
+++ b/src/com/android/launcher3/compat/LauncherActivityInfoCompatV16.java
@@ -93,8 +93,4 @@
     public String getName() {
         return mActivityInfo.name;
     }
-
-    public Drawable getBadgedIcon(int density) {
-        return getIcon(density);
-    }
 }
diff --git a/src/com/android/launcher3/compat/LauncherActivityInfoCompatVL.java b/src/com/android/launcher3/compat/LauncherActivityInfoCompatVL.java
index 4448758..67c5c27 100644
--- a/src/com/android/launcher3/compat/LauncherActivityInfoCompatVL.java
+++ b/src/com/android/launcher3/compat/LauncherActivityInfoCompatVL.java
@@ -55,8 +55,4 @@
     public long getFirstInstallTime() {
         return mLauncherActivityInfo.getFirstInstallTime();
     }
-
-    public Drawable getBadgedIcon(int density) {
-        return mLauncherActivityInfo.getBadgedIcon(density);
-    }
 }
diff --git a/src/com/android/launcher3/compat/UserHandleCompat.java b/src/com/android/launcher3/compat/UserHandleCompat.java
index 567022b..9479908 100644
--- a/src/com/android/launcher3/compat/UserHandleCompat.java
+++ b/src/com/android/launcher3/compat/UserHandleCompat.java
@@ -49,7 +49,7 @@
         }
     }
 
-    UserHandle getUser() {
+    public UserHandle getUser() {
         return mUser;
     }
 
diff --git a/src/com/android/launcher3/compat/UserManagerCompat.java b/src/com/android/launcher3/compat/UserManagerCompat.java
index f708004..6b7cba8 100644
--- a/src/com/android/launcher3/compat/UserManagerCompat.java
+++ b/src/com/android/launcher3/compat/UserManagerCompat.java
@@ -17,8 +17,6 @@
 package com.android.launcher3.compat;
 
 import android.content.Context;
-import android.graphics.drawable.Drawable;
-import android.os.Build;
 
 import com.android.launcher3.Utilities;
 
@@ -54,7 +52,6 @@
     public abstract List<UserHandleCompat> getUserProfiles();
     public abstract long getSerialNumberForUser(UserHandleCompat user);
     public abstract UserHandleCompat getUserForSerialNumber(long serialNumber);
-    public abstract Drawable getBadgedDrawableForUser(Drawable unbadged, UserHandleCompat user);
     public abstract CharSequence getBadgedLabelForUser(CharSequence label, UserHandleCompat user);
     public abstract long getUserCreationTime(UserHandleCompat user);
 }
diff --git a/src/com/android/launcher3/compat/UserManagerCompatV16.java b/src/com/android/launcher3/compat/UserManagerCompatV16.java
index 85aee57..fcd7555 100644
--- a/src/com/android/launcher3/compat/UserManagerCompatV16.java
+++ b/src/com/android/launcher3/compat/UserManagerCompatV16.java
@@ -16,8 +16,6 @@
 
 package com.android.launcher3.compat;
 
-import android.graphics.drawable.Drawable;
-
 import java.util.ArrayList;
 import java.util.List;
 
@@ -36,11 +34,6 @@
         return UserHandleCompat.myUserHandle();
     }
 
-    public Drawable getBadgedDrawableForUser(Drawable unbadged,
-            UserHandleCompat user) {
-        return unbadged;
-    }
-
     public long getSerialNumberForUser(UserHandleCompat user) {
         return 0;
     }
diff --git a/src/com/android/launcher3/compat/UserManagerCompatVL.java b/src/com/android/launcher3/compat/UserManagerCompatVL.java
index 98d5eca..c53d702 100644
--- a/src/com/android/launcher3/compat/UserManagerCompatVL.java
+++ b/src/com/android/launcher3/compat/UserManagerCompatVL.java
@@ -21,7 +21,6 @@
 import android.content.Context;
 import android.content.SharedPreferences;
 import android.content.pm.PackageManager;
-import android.graphics.drawable.Drawable;
 import android.os.Build;
 import android.os.UserHandle;
 
@@ -86,11 +85,6 @@
     }
 
     @Override
-    public Drawable getBadgedDrawableForUser(Drawable unbadged, UserHandleCompat user) {
-        return mPm.getUserBadgedIcon(unbadged, user.getUser());
-    }
-
-    @Override
     public CharSequence getBadgedLabelForUser(CharSequence label, UserHandleCompat user) {
         if (user == null) {
             return label;
diff --git a/src/com/android/launcher3/config/FeatureFlags.java b/src/com/android/launcher3/config/FeatureFlags.java
new file mode 100644
index 0000000..0098669
--- /dev/null
+++ b/src/com/android/launcher3/config/FeatureFlags.java
@@ -0,0 +1,36 @@
+/*
+ * Copyright (C) 2015 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.launcher3.config;
+
+/**
+ * Defines a set of flags used to control various launcher behaviors
+ * All the flags must be defined as
+ *   public static final boolean LAUNCHER3_FLAG_NAME = true/false;
+ *
+ * Use LAUNCHER3_ prefix for prevent namespace conflicts.
+ */
+public final class FeatureFlags {
+
+    private FeatureFlags() {}
+
+    public static final boolean IS_DEV_BUILD = false;
+    public static final boolean IS_ALPHA_BUILD = false;
+    public static final boolean IS_RELEASE_BUILD = true;
+
+    // Custom flags go below this
+
+}
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/src/com/android/launcher3/util/IconNormalizer.java b/src/com/android/launcher3/util/IconNormalizer.java
new file mode 100644
index 0000000..001cac0
--- /dev/null
+++ b/src/com/android/launcher3/util/IconNormalizer.java
@@ -0,0 +1,239 @@
+/*
+ * Copyright (C) 2015 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.launcher3.util;
+
+import android.graphics.Bitmap;
+import android.graphics.Canvas;
+import android.graphics.Color;
+import android.graphics.drawable.Drawable;
+
+import com.android.launcher3.LauncherAppState;
+
+import java.nio.ByteBuffer;
+
+public class IconNormalizer {
+
+    // Ratio of icon visible area to full icon size for a square shaped icon
+    private static final float MAX_SQUARE_AREA_FACTOR = 359.0f / 576;
+    // Ratio of icon visible area to full icon size for a circular shaped icon
+    private static final float MAX_CIRCLE_AREA_FACTOR = 380.0f / 576;
+
+    private static final float CIRCLE_AREA_BY_RECT = (float) Math.PI / 4;
+
+    // Slope used to calculate icon visible area to full icon size for any generic shaped icon.
+    private static final float LINEAR_SCALE_SLOPE =
+            (MAX_CIRCLE_AREA_FACTOR - MAX_SQUARE_AREA_FACTOR) / (1 - CIRCLE_AREA_BY_RECT);
+
+    private static final int MIN_VISIBLE_ALPHA = 40;
+
+    private static final Object LOCK = new Object();
+    private static IconNormalizer sIconNormalizer;
+
+    private final int mMaxSize;
+    private final Bitmap mBitmap;
+    private final Canvas mCanvas;
+    private final byte[] mPixels;
+
+    // for each y, stores the position of the leftmost x and the rightmost x
+    private final float[] mLeftBorder;
+    private final float[] mRightBorder;
+
+    private IconNormalizer() {
+        // Use twice the icon size as maximum size to avoid scaling down twice.
+        mMaxSize = LauncherAppState.getInstance().getInvariantDeviceProfile().iconBitmapSize * 2;
+        mBitmap = Bitmap.createBitmap(mMaxSize, mMaxSize, Bitmap.Config.ALPHA_8);
+        mCanvas = new Canvas(mBitmap);
+        mPixels = new byte[mMaxSize * mMaxSize];
+
+        mLeftBorder = new float[mMaxSize];
+        mRightBorder = new float[mMaxSize];
+    }
+
+    /**
+     * Returns the amount by which the {@param d} should be scaled (in both dimensions) so that it
+     * matches the design guidelines for a launcher icon.
+     *
+     * We first calculate the convex hull of the visible portion of the icon.
+     * This hull then compared with the bounding rectangle of the hull to find how closely it
+     * resembles a circle and a square, by comparing the ratio of the areas. Note that this is not an
+     * ideal solution but it gives satisfactory result without affecting the performance.
+     *
+     * This closeness is used to determine the ratio of hull area to the full icon size.
+     * Refer {@link #MAX_CIRCLE_AREA_FACTOR} and {@link #MAX_SQUARE_AREA_FACTOR}
+     */
+    public synchronized float getScale(Drawable d) {
+        int width = d.getIntrinsicWidth();
+        int height = d.getIntrinsicHeight();
+        if (width <= 0 || height <= 0) {
+            width = width <= 0 || width > mMaxSize ? mMaxSize : width;
+            height = height <= 0 || height > mMaxSize ? mMaxSize : height;
+        } else if (width > mMaxSize || height > mMaxSize) {
+            int max = Math.max(width, height);
+            width = mMaxSize * width / max;
+            height = mMaxSize * height / max;
+        }
+
+        mBitmap.eraseColor(Color.TRANSPARENT);
+        d.setBounds(0, 0, width, height);
+        d.draw(mCanvas);
+
+        ByteBuffer buffer = ByteBuffer.wrap(mPixels);
+        buffer.rewind();
+        mBitmap.copyPixelsToBuffer(buffer);
+
+        // Overall bounds of the visible icon.
+        int topY = -1;
+        int bottomY = -1;
+        int leftX = mMaxSize + 1;
+        int rightX = -1;
+
+        // Create border by going through all pixels one row at a time and for each row find
+        // the first and the last non-transparent pixel. Set those values to mLeftBorder and
+        // mRightBorder and use -1 if there are no visible pixel in the row.
+
+        // buffer position
+        int index = 0;
+        // buffer shift after every row, width of buffer = mMaxSize
+        int rowSizeDiff = mMaxSize - width;
+        // first and last position for any row.
+        int firstX, lastX;
+
+        for (int y = 0; y < height; y++) {
+            firstX = lastX = -1;
+            for (int x = 0; x < width; x++) {
+                if ((mPixels[index] & 0xFF) > MIN_VISIBLE_ALPHA) {
+                    if (firstX == -1) {
+                        firstX = x;
+                    }
+                    lastX = x;
+                }
+                index++;
+            }
+            index += rowSizeDiff;
+
+            mLeftBorder[y] = firstX;
+            mRightBorder[y] = lastX;
+
+            // If there is at least one visible pixel, update the overall bounds.
+            if (firstX != -1) {
+                bottomY = y;
+                if (topY == -1) {
+                    topY = y;
+                }
+
+                leftX = Math.min(leftX, firstX);
+                rightX = Math.max(rightX, lastX);
+            }
+        }
+
+        if (topY == -1 || rightX == -1) {
+            // No valid pixels found. Do not scale.
+            return 1;
+        }
+
+        convertToConvexArray(mLeftBorder, 1, topY, bottomY);
+        convertToConvexArray(mRightBorder, -1, topY, bottomY);
+
+        // Area of the convex hull
+        float area = 0;
+        for (int y = 0; y < height; y++) {
+            if (mLeftBorder[y] <= -1) {
+                continue;
+            }
+            area += mRightBorder[y] - mLeftBorder[y] + 1;
+        }
+
+        // Area of the rectangle required to fit the convex hull
+        float rectArea = (bottomY + 1 - topY) * (rightX + 1 - leftX);
+        float hullByRect = area / rectArea;
+
+        float scaleRequired;
+        if (hullByRect < CIRCLE_AREA_BY_RECT) {
+            scaleRequired = MAX_CIRCLE_AREA_FACTOR;
+        } else {
+            scaleRequired = MAX_SQUARE_AREA_FACTOR + LINEAR_SCALE_SLOPE * (1  - hullByRect);
+        }
+
+        float areaScale = area / (width * height);
+        // Use sqrt of the final ratio as the images is scaled across both width and height.
+        float scale = areaScale > scaleRequired ? (float) Math.sqrt(scaleRequired / areaScale) : 1;
+        return scale;
+    }
+
+    /**
+     * Modifies {@param xCordinates} to represent a convex border. Fills in all missing values
+     * (except on either ends) with appropriate values.
+     * @param xCordinates map of x coordinate per y.
+     * @param direction 1 for left border and -1 for right border.
+     * @param topY the first Y position (inclusive) with a valid value.
+     * @param bottomY the last Y position (inclusive) with a valid value.
+     */
+    private static void convertToConvexArray(
+            float[] xCordinates, int direction, int topY, int bottomY) {
+        int total = xCordinates.length;
+        // The tangent at each pixel.
+        float[] angles = new float[total - 1];
+
+        int first = topY; // First valid y coordinate
+        int last = -1;    // Last valid y coordinate which didn't have a missing value
+
+        float lastAngle = Float.MAX_VALUE;
+
+        for (int i = topY + 1; i <= bottomY; i++) {
+            if (xCordinates[i] <= -1) {
+                continue;
+            }
+            int start;
+
+            if (lastAngle == Float.MAX_VALUE) {
+                start = first;
+            } else {
+                float currentAngle = (xCordinates[i] - xCordinates[last]) / (i - last);
+                start = last;
+                // If this position creates a concave angle, keep moving up until we find a
+                // position which creates a convex angle.
+                if ((currentAngle - lastAngle) * direction < 0) {
+                    while (start > first) {
+                        start --;
+                        currentAngle = (xCordinates[i] - xCordinates[start]) / (i - start);
+                        if ((currentAngle - angles[start]) * direction >= 0) {
+                            break;
+                        }
+                    }
+                }
+            }
+
+            // Reset from last check
+            lastAngle = (xCordinates[i] - xCordinates[start]) / (i - start);
+            // Update all the points from start.
+            for (int j = start; j < i; j++) {
+                angles[j] = lastAngle;
+                xCordinates[j] = xCordinates[start] + lastAngle * (j - start);
+            }
+            last = i;
+        }
+    }
+
+    public static IconNormalizer getInstance() {
+        synchronized (LOCK) {
+            if (sIconNormalizer == null) {
+                sIconNormalizer = new IconNormalizer();
+            }
+        }
+        return sIconNormalizer;
+    }
+}
diff --git a/tests/src/com/android/launcher3/QuickAddWidgetTest.java b/tests/src/com/android/launcher3/QuickAddWidgetTest.java
new file mode 100644
index 0000000..8c563f3
--- /dev/null
+++ b/tests/src/com/android/launcher3/QuickAddWidgetTest.java
@@ -0,0 +1,81 @@
+package com.android.launcher3;
+
+import android.content.Intent;
+import android.graphics.Point;
+import android.os.SystemClock;
+import android.support.test.uiautomator.By;
+import android.support.test.uiautomator.Direction;
+import android.support.test.uiautomator.UiDevice;
+import android.support.test.uiautomator.UiObject2;
+import android.support.test.uiautomator.Until;
+import android.test.InstrumentationTestCase;
+import android.view.MotionEvent;
+import android.view.ViewConfiguration;
+
+import java.util.List;
+
+/**
+ * Add an arbitrary widget from the widget picker very quickly to test potential race conditions.
+ */
+public class QuickAddWidgetTest extends InstrumentationTestCase {
+    private UiDevice mDevice;
+    private String mTargetPackage;
+
+    @Override
+    protected void setUp() throws Exception {
+        super.setUp();
+
+        mDevice = UiDevice.getInstance(getInstrumentation());
+
+        // Set Launcher3 as home.
+        mTargetPackage = getInstrumentation().getTargetContext().getPackageName();
+        Intent homeIntent = new Intent(Intent.ACTION_MAIN)
+                .addCategory(Intent.CATEGORY_HOME)
+                .setPackage(mTargetPackage)
+                .addFlags(Intent.FLAG_ACTIVITY_NEW_TASK);
+        getInstrumentation().getContext().startActivity(homeIntent);
+        mDevice.wait(Until.hasObject(By.pkg(mTargetPackage).depth(0)), 3000);
+    }
+
+    public void testAddWidgetQuickly() throws Exception {
+        mDevice.pressMenu(); // Enter overview mode.
+        mDevice.wait(Until.findObject(By.text("Widgets")), 3000).click();
+        UiObject2 calendarWidget = getWidgetByName("Calendar");
+        Point center = calendarWidget.getVisibleCenter();
+        // Touch widget just long enough to pick it up (longPressTimeout), then let go immediately.
+        getInstrumentation().sendPointerSync(MotionEvent.obtain(SystemClock.uptimeMillis(),
+                SystemClock.uptimeMillis(), MotionEvent.ACTION_DOWN, center.x, center.y, 0));
+        Thread.sleep(ViewConfiguration.getLongPressTimeout());
+        getInstrumentation().sendPointerSync(MotionEvent.obtain(SystemClock.uptimeMillis(),
+                SystemClock.uptimeMillis(), MotionEvent.ACTION_UP, center.x, center.y, 0));
+
+        assertTrue("Drag was never started", isOnHomescreen());
+    }
+
+    private UiObject2 getWidgetByName(String name) {
+        UiObject2 widgetsList = mDevice.wait(Until.findObject(By.res(mTargetPackage,
+                "widgets_list_view")), 3000);
+        do {
+            UiObject2 widget = getVisibleWidgetByName(name);
+            if (widget != null) {
+                return widget;
+            }
+        } while (widgetsList.scroll(Direction.DOWN, 1f));
+        return getVisibleWidgetByName(name);
+    }
+
+    private UiObject2 getVisibleWidgetByName(String name) {
+        List<UiObject2> visibleWidgets = mDevice.wait(Until.findObjects(By.clazz(
+                "android.widget.LinearLayout")), 3000);
+        for (UiObject2 widget : visibleWidgets) {
+            if (widget.hasObject(By.text(name))) {
+                return widget;
+            }
+        }
+        return null;
+    }
+
+    private boolean isOnHomescreen() {
+        return mDevice.wait(Until.hasObject(By.res(mTargetPackage, "hotseat")), 3000);
+    }
+}
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. */