Merge "Setting statusbar and nav bar colors in theme instead of code" into ub-launcher3-burnaby-polish
diff --git a/WallpaperPicker/src/com/android/launcher3/WallpaperPickerActivity.java b/WallpaperPicker/src/com/android/launcher3/WallpaperPickerActivity.java
index b40e4bd..5d41238 100644
--- a/WallpaperPicker/src/com/android/launcher3/WallpaperPickerActivity.java
+++ b/WallpaperPicker/src/com/android/launcher3/WallpaperPickerActivity.java
@@ -309,7 +309,7 @@
 
                 @Override
                 public float getParallaxOffset() {
-                    return 0;
+                    return 0.5f;
                 }
             };
             req.result = new DrawableTileSource(a.getContext(),
diff --git a/WallpaperPicker/src/com/android/photos/BitmapRegionTileSource.java b/WallpaperPicker/src/com/android/photos/BitmapRegionTileSource.java
index 2d496a5..6baac6a 100644
--- a/WallpaperPicker/src/com/android/photos/BitmapRegionTileSource.java
+++ b/WallpaperPicker/src/com/android/photos/BitmapRegionTileSource.java
@@ -159,6 +159,7 @@
         public enum State { NOT_LOADED, LOADED, ERROR_LOADING };
         private State mState = State.NOT_LOADED;
 
+        /** Returns whether loading was successful. */
         public boolean loadInBackground(InBitmapProvider bitmapProvider) {
             ExifInterface ei = new ExifInterface();
             if (readExif(ei)) {
@@ -193,7 +194,7 @@
                         try {
                             mPreview = loadPreviewBitmap(opts);
                         } catch (IllegalArgumentException e) {
-                            Log.d(TAG, "Unable to reusage bitmap", e);
+                            Log.d(TAG, "Unable to reuse bitmap", e);
                             opts.inBitmap = null;
                             mPreview = null;
                         }
@@ -202,6 +203,10 @@
                 if (mPreview == null) {
                     mPreview = loadPreviewBitmap(opts);
                 }
+                if (mPreview == null) {
+                    mState = State.ERROR_LOADING;
+                    return false;
+                }
 
                 // Verify that the bitmap can be used on GL surface
                 try {
@@ -212,7 +217,7 @@
                     Log.d(TAG, "Image cannot be rendered on a GL surface", e);
                     mState = State.ERROR_LOADING;
                 }
-                return true;
+                return mState == State.LOADED;
             }
         }
 
@@ -310,7 +315,7 @@
                 Bitmap b = BitmapFactory.decodeStream(is, null, options);
                 Utils.closeSilently(is);
                 return b;
-            } catch (FileNotFoundException e) {
+            } catch (FileNotFoundException | OutOfMemoryError e) {
                 Log.e("BitmapRegionTileSource", "Failed to load URI " + mUri, e);
                 return null;
             }
@@ -412,7 +417,8 @@
                         "Failed to create preview of apropriate size! "
                         + " in: %dx%d, out: %dx%d",
                         mWidth, mHeight,
-                        preview.getWidth(), preview.getHeight()));
+                        preview == null ? -1 : preview.getWidth(),
+                        preview == null ? -1 : preview.getHeight()));
             }
         }
     }
diff --git a/res/values/dimens.xml b/res/values/dimens.xml
index 5d69012..1025aba 100644
--- a/res/values/dimens.xml
+++ b/res/values/dimens.xml
@@ -19,11 +19,15 @@
 
 <!-- Dynamic Grid -->
     <dimen name="dynamic_grid_edge_margin">6dp</dimen>
-    <dimen name="dynamic_grid_search_bar_max_width">500dp</dimen>
-    <dimen name="dynamic_grid_search_bar_height">56dp</dimen>
-    <!-- We want 32dp extra for the tall search bar, but 10dp comes from unwanted padding between
-         the search bar and workspace. -->
-    <dimen name="dynamic_grid_search_bar_height_tall">78dp</dimen>
+    <dimen name="dynamic_grid_search_bar_height">48dp</dimen>
+    <!-- We want 32dp extra for the tall search bar. -->
+    <dimen name="dynamic_grid_search_bar_height_tall">80dp</dimen>
+    <dimen name="qsb_internal_padding_top">8dp</dimen>
+    <dimen name="qsb_internal_padding_bottom">8dp</dimen>
+    <dimen name="dynamic_grid_search_bar_bottom_padding">4dp</dimen>
+    <!-- Reduce the padding between the search bar and workspace when the search bar is tall -->
+    <dimen name="dynamic_grid_search_bar_bottom_padding_short">-6dp</dimen>
+    <dimen name="dynamic_grid_search_bar_bottom_padding_tablet">16dp</dimen>
     <dimen name="dynamic_grid_page_indicator_height">20dp</dimen>
     <dimen name="dynamic_grid_icon_drawable_padding">4dp</dimen>
     <dimen name="dynamic_grid_workspace_page_spacing">8dp</dimen>
diff --git a/src/com/android/launcher3/CellLayout.java b/src/com/android/launcher3/CellLayout.java
index 71a1df3..5f64a82 100644
--- a/src/com/android/launcher3/CellLayout.java
+++ b/src/com/android/launcher3/CellLayout.java
@@ -899,9 +899,14 @@
 
     @Override
     protected void onLayout(boolean changed, int l, int t, int r, int b) {
-        int offset = getMeasuredWidth() - getPaddingLeft() - getPaddingRight() -
-                (mCountX * mCellWidth);
-        int left = getPaddingLeft() + (int) Math.ceil(offset / 2f);
+        boolean isFullscreen = mShortcutsAndWidgets.getChildCount() > 0 &&
+                ((LayoutParams) mShortcutsAndWidgets.getChildAt(0).getLayoutParams()).isFullscreen;
+        int left = getPaddingLeft();
+        if (!isFullscreen) {
+            int offset = getMeasuredWidth() - getPaddingLeft() - getPaddingRight() -
+                    (mCountX * mCellWidth);
+            left += (int) Math.ceil(offset / 2f);
+        }
         int top = getPaddingTop();
 
         mTouchFeedbackView.layout(left, top,
diff --git a/src/com/android/launcher3/DeviceProfile.java b/src/com/android/launcher3/DeviceProfile.java
index ed6b7d7..ccbfba1 100644
--- a/src/com/android/launcher3/DeviceProfile.java
+++ b/src/com/android/launcher3/DeviceProfile.java
@@ -90,7 +90,7 @@
     public int hotseatCellWidthPx;
     public int hotseatCellHeightPx;
     public int hotseatIconSizePx;
-    private int hotseatBarHeightNormalPx, hotseatBarHeightShortPx;
+    private int normalHotseatBarHeightPx, shortHotseatBarHeightPx;
     private int hotseatBarHeightPx; // One of the above.
 
     // All apps
@@ -101,8 +101,11 @@
     public final int allAppsIconTextSizePx;
 
     // QSB
-    private int searchBarSpaceWidthPx;
-    private int searchBarSpaceHeightNormalPx, searchBarSpaceHeightTallPx;
+    private int searchBarWidgetInternalPaddingTop, searchBarWidgetInternalPaddingBottom;
+    private int searchBarTopPaddingPx;
+    private int normalSearchBarBottomPaddingPx, tallSearchBarBottomPaddingPx;
+    private int searchBarBottomPaddingPx; // One of the above.
+    private int normalSearchBarSpaceHeightPx, tallSearchBarSpaceHeightPx;
     private int searchBarSpaceHeightPx; // One of the above.
 
     public DeviceProfile(Context context, InvariantDeviceProfile inv,
@@ -205,12 +208,26 @@
         hotseatIconSizePx = (int) (Utilities.pxFromDp(inv.hotseatIconSize, dm) * scale);
 
         // Search Bar
-        searchBarSpaceWidthPx = Math.min(widthPx,
-                res.getDimensionPixelSize(R.dimen.dynamic_grid_search_bar_max_width));
-        searchBarSpaceHeightNormalPx = getSearchBarTopOffset()
-                + res.getDimensionPixelSize(R.dimen.dynamic_grid_search_bar_height);
-        searchBarSpaceHeightTallPx = getSearchBarTopOffset()
-                + res.getDimensionPixelSize(R.dimen.dynamic_grid_search_bar_height_tall);
+        normalSearchBarSpaceHeightPx = res.getDimensionPixelSize(
+                R.dimen.dynamic_grid_search_bar_height);
+        tallSearchBarSpaceHeightPx = res.getDimensionPixelSize(
+                R.dimen.dynamic_grid_search_bar_height_tall);
+        searchBarWidgetInternalPaddingTop = res.getDimensionPixelSize(
+                R.dimen.qsb_internal_padding_top);
+        searchBarWidgetInternalPaddingBottom = res.getDimensionPixelSize(
+                R.dimen.qsb_internal_padding_bottom);
+        if (isTablet && !isVerticalBarLayout()) {
+            searchBarTopPaddingPx = searchBarWidgetInternalPaddingTop;
+            normalSearchBarBottomPaddingPx = searchBarWidgetInternalPaddingBottom +
+                    res.getDimensionPixelSize(R.dimen.dynamic_grid_search_bar_bottom_padding_tablet);
+            tallSearchBarBottomPaddingPx = normalSearchBarBottomPaddingPx;
+        } else {
+            searchBarTopPaddingPx = searchBarWidgetInternalPaddingTop;
+            normalSearchBarBottomPaddingPx = searchBarWidgetInternalPaddingBottom +
+                    res.getDimensionPixelSize(R.dimen.dynamic_grid_search_bar_bottom_padding);
+            tallSearchBarBottomPaddingPx = searchBarWidgetInternalPaddingBottom +
+                    res.getDimensionPixelSize(R.dimen.dynamic_grid_search_bar_bottom_padding_short);
+        }
 
         // Calculate the actual text height
         Paint textPaint = new Paint();
@@ -222,13 +239,15 @@
         dragViewScale = (iconSizePx + scaleDps) / iconSizePx;
 
         // Hotseat
-        hotseatBarHeightNormalPx = iconSizePx + 4 * edgeMarginPx;
-        hotseatBarHeightShortPx = iconSizePx + 2 * edgeMarginPx;
+        normalHotseatBarHeightPx = iconSizePx + 4 * edgeMarginPx;
+        shortHotseatBarHeightPx = iconSizePx + 2 * edgeMarginPx;
         hotseatCellWidthPx = iconSizePx;
         hotseatCellHeightPx = iconSizePx;
 
         // Folder
-        folderCellWidthPx = Math.min(cellWidthPx + 6 * edgeMarginPx,
+        int folderCellPadding = isTablet || isLandscape ? 6 * edgeMarginPx : 3 * edgeMarginPx;
+        // Don't let the folder get too close to the edges of the screen.
+        folderCellWidthPx = Math.min(cellWidthPx + folderCellPadding,
                 (availableWidthPx - 4 * edgeMarginPx) / inv.numFolderColumns);
         folderCellHeightPx = cellHeightPx + edgeMarginPx;
         folderBackgroundOffset = -edgeMarginPx;
@@ -251,13 +270,20 @@
         allAppsNumPredictiveCols = numPredictiveAppCols;
     }
 
-    /** Returns the search bar top offset */
-    private int getSearchBarTopOffset() {
-        if (isTablet && !isVerticalBarLayout()) {
-            return 4 * edgeMarginPx;
-        } else {
-            return 2 * edgeMarginPx;
+    /** Returns the amount of extra space to allocate to the search bar for vertical padding. */
+    private int getSearchBarTotalVerticalPadding() {
+        return searchBarTopPaddingPx + searchBarBottomPaddingPx;
+    }
+
+    /** Returns the width and height of the search bar, ignoring any padding. */
+    public Point getSearchBarDimensForWidgetOpts(Resources res) {
+        Rect searchBarBounds = getSearchBarBounds(Utilities.isRtl(res));
+        if (isVerticalBarLayout()) {
+            return new Point(searchBarBounds.width(), searchBarBounds.height());
         }
+        int widgetInternalPadding = searchBarWidgetInternalPaddingTop +
+                searchBarWidgetInternalPaddingBottom;
+        return new Point(searchBarBounds.width(), searchBarSpaceHeightPx + widgetInternalPadding);
     }
 
     /** Returns the search bar bounds in the current orientation */
@@ -265,13 +291,14 @@
         Rect bounds = new Rect();
         if (isVerticalBarLayout()) {
             if (isLayoutRtl) {
-                bounds.set(availableWidthPx - searchBarSpaceHeightNormalPx, edgeMarginPx,
+                bounds.set(availableWidthPx - normalSearchBarSpaceHeightPx, edgeMarginPx,
                         availableWidthPx, availableHeightPx - edgeMarginPx);
             } else {
-                bounds.set(0, edgeMarginPx, searchBarSpaceHeightNormalPx,
+                bounds.set(0, edgeMarginPx, normalSearchBarSpaceHeightPx,
                         availableHeightPx - edgeMarginPx);
             }
         } else {
+            int boundsBottom = searchBarSpaceHeightPx + getSearchBarTotalVerticalPadding();
             if (isTablet) {
                 // Pad the left and right of the workspace to ensure consistent spacing
                 // between all icons
@@ -280,14 +307,13 @@
                 //      that into account here too.
                 int gap = (int) ((width - 2 * edgeMarginPx -
                         (inv.numColumns * cellWidthPx)) / (2 * (inv.numColumns + 1)));
-                bounds.set(edgeMarginPx + gap, getSearchBarTopOffset(),
-                        availableWidthPx - (edgeMarginPx + gap),
-                        searchBarSpaceHeightPx);
+                bounds.set(edgeMarginPx + gap, 0,
+                        availableWidthPx - (edgeMarginPx + gap), boundsBottom);
             } else {
                 bounds.set(desiredWorkspaceLeftRightMarginPx - defaultWidgetPadding.left,
-                        getSearchBarTopOffset(),
+                        0,
                         availableWidthPx - (desiredWorkspaceLeftRightMarginPx -
-                        defaultWidgetPadding.right), searchBarSpaceHeightPx);
+                        defaultWidgetPadding.right), boundsBottom);
             }
         }
         return bounds;
@@ -300,21 +326,21 @@
         if (isVerticalBarLayout()) {
             // Pad the left and right of the workspace with search/hotseat bar sizes
             if (isLayoutRtl) {
-                padding.set(hotseatBarHeightNormalPx, edgeMarginPx,
+                padding.set(normalHotseatBarHeightPx, edgeMarginPx,
                         searchBarBounds.width(), edgeMarginPx);
             } else {
                 padding.set(searchBarBounds.width(), edgeMarginPx,
-                        hotseatBarHeightNormalPx, edgeMarginPx);
+                        normalHotseatBarHeightPx, edgeMarginPx);
             }
         } else {
+            int paddingTop = searchBarBounds.bottom;
+            int paddingBottom = hotseatBarHeightPx + pageIndicatorHeightPx;
             if (isTablet) {
                 // Pad the left and right of the workspace to ensure consistent spacing
                 // between all icons
                 float gapScale = 1f + (dragViewScale - 1f) / 2f;
                 int width = getCurrentWidth();
                 int height = getCurrentHeight();
-                int paddingTop = searchBarBounds.bottom;
-                int paddingBottom = hotseatBarHeightPx + pageIndicatorHeightPx;
                 // The amount of screen space available for left/right padding.
                 int availablePaddingX = Math.max(0, width - (int) ((inv.numColumns * cellWidthPx) +
                         ((inv.numColumns - 1) * gapScale * cellWidthPx)));
@@ -327,9 +353,9 @@
             } else {
                 // Pad the top and bottom of the workspace with search/hotseat bar sizes
                 padding.set(desiredWorkspaceLeftRightMarginPx - defaultWidgetPadding.left,
-                        searchBarBounds.bottom,
+                        paddingTop,
                         desiredWorkspaceLeftRightMarginPx - defaultWidgetPadding.right,
-                        hotseatBarHeightPx + pageIndicatorHeightPx);
+                        paddingBottom);
             }
         }
         return padding;
@@ -357,7 +383,7 @@
     // The rect returned will be extended to below the system ui that covers the workspace
     Rect getHotseatRect() {
         if (isVerticalBarLayout()) {
-            return new Rect(availableWidthPx - hotseatBarHeightNormalPx, 0,
+            return new Rect(availableWidthPx - normalHotseatBarHeightPx, 0,
                     Integer.MAX_VALUE, availableHeightPx);
         } else {
             return new Rect(0, availableHeightPx - hotseatBarHeightPx,
@@ -398,11 +424,13 @@
     // TODO(twickham): b/25154513
     public void setSearchBarHeight(int searchBarHeight) {
         if (searchBarHeight == LauncherCallbacks.SEARCH_BAR_HEIGHT_TALL) {
-            hotseatBarHeightPx = hotseatBarHeightShortPx;
-            searchBarSpaceHeightPx = searchBarSpaceHeightTallPx;
+            hotseatBarHeightPx = shortHotseatBarHeightPx;
+            searchBarSpaceHeightPx = tallSearchBarSpaceHeightPx;
+            searchBarBottomPaddingPx = tallSearchBarBottomPaddingPx;
         } else {
-            hotseatBarHeightPx = hotseatBarHeightNormalPx;
-            searchBarSpaceHeightPx = searchBarSpaceHeightNormalPx;
+            hotseatBarHeightPx = normalHotseatBarHeightPx;
+            searchBarSpaceHeightPx = normalSearchBarSpaceHeightPx;
+            searchBarBottomPaddingPx = normalSearchBarBottomPaddingPx;
         }
     }
 
@@ -412,13 +440,15 @@
         final boolean isLayoutRtl = Utilities.isRtl(launcher.getResources());
 
         // Layout the search bar space
+        Rect searchBarBounds = getSearchBarBounds(isLayoutRtl);
         View searchBar = launcher.getSearchDropTargetBar();
         lp = (FrameLayout.LayoutParams) searchBar.getLayoutParams();
+        lp.width = searchBarBounds.width();
+        lp.height = searchBarBounds.height();
         if (hasVerticalBarLayout) {
             // Vertical search bar space -- The search bar is fixed in the layout to be on the left
             //                              of the screen regardless of RTL
             lp.gravity = Gravity.LEFT;
-            lp.width = searchBarSpaceHeightNormalPx;
 
             LinearLayout targets = (LinearLayout) searchBar.findViewById(R.id.drag_target_bar);
             targets.setOrientation(LinearLayout.VERTICAL);
@@ -428,11 +458,7 @@
 
         } else {
             // Horizontal search bar space
-            lp.gravity = Gravity.TOP;
-            lp.height = searchBarSpaceHeightPx;
-
-            LinearLayout targets = (LinearLayout) searchBar.findViewById(R.id.drag_target_bar);
-            targets.getLayoutParams().width = searchBarSpaceWidthPx;
+            lp.gravity = Gravity.TOP|Gravity.CENTER_HORIZONTAL;
         }
         searchBar.setLayoutParams(lp);
 
@@ -459,7 +485,7 @@
             // Vertical hotseat -- The hotseat is fixed in the layout to be on the right of the
             //                     screen regardless of RTL
             lp.gravity = Gravity.RIGHT;
-            lp.width = hotseatBarHeightNormalPx;
+            lp.width = normalHotseatBarHeightPx;
             lp.height = LayoutParams.MATCH_PARENT;
             hotseat.findViewById(R.id.layout).setPadding(0, 2 * edgeMarginPx, 0, 2 * edgeMarginPx);
         } else if (isTablet) {
diff --git a/src/com/android/launcher3/FastBitmapDrawable.java b/src/com/android/launcher3/FastBitmapDrawable.java
index 30bc7ea..d7f1d86 100644
--- a/src/com/android/launcher3/FastBitmapDrawable.java
+++ b/src/com/android/launcher3/FastBitmapDrawable.java
@@ -42,8 +42,8 @@
 
         NORMAL                      (0f, 0f, 1f, new DecelerateInterpolator()),
         PRESSED                     (0f, 100f / 255f, 1f, CLICK_FEEDBACK_INTERPOLATOR),
-        FAST_SCROLL_HIGHLIGHTED     (0f, 0f, 1.1f, new DecelerateInterpolator()),
-        FAST_SCROLL_UNHIGHLIGHTED   (0.8f, 0.35f, 1f, new DecelerateInterpolator()),
+        FAST_SCROLL_HIGHLIGHTED     (0f, 0f, 1.15f, new DecelerateInterpolator()),
+        FAST_SCROLL_UNHIGHLIGHTED   (0f, 0f, 1f, new DecelerateInterpolator()),
         DISABLED                    (1f, 0.5f, 1f, new DecelerateInterpolator());
 
         public final float desaturation;
diff --git a/src/com/android/launcher3/FocusHelper.java b/src/com/android/launcher3/FocusHelper.java
index 6872d5b..44403e2 100644
--- a/src/com/android/launcher3/FocusHelper.java
+++ b/src/com/android/launcher3/FocusHelper.java
@@ -142,7 +142,8 @@
                     newParent = getCellLayoutChildrenForIndex(pagedView, pageIndex + 1);
                     if (newParent != null) {
                         pagedView.snapToPage(pageIndex + 1);
-                        child = FocusLogic.getAdjacentChildInNextPage(newParent, v, newIconIndex);
+                        child = FocusLogic.getAdjacentChildInNextFolderPage(
+                                newParent, v, newIconIndex);
                     }
                     break;
                 case FocusLogic.CURRENT_PAGE_FIRST_ITEM:
@@ -173,7 +174,7 @@
     }
 
     /**
-     * Handles key events in the workspace hot seat (bottom of the screen).
+     * Handles key events in the workspace hotseat (bottom of the screen).
      * <p>Currently we don't special case for the phone UI in different orientations, even though
      * the hotseat is on the side in landscape mode. This is to ensure that accessibility
      * consistency is maintained across rotations.
@@ -262,12 +263,38 @@
                 countY, matrix, iconIndex, pageIndex, pageCount, Utilities.isRtl(v.getResources()));
 
         View newIcon = null;
-        if (newIconIndex == FocusLogic.NEXT_PAGE_FIRST_ITEM) {
-            parent = getCellLayoutChildrenForIndex(workspace, pageIndex + 1);
-            newIcon = parent.getChildAt(0);
-            // TODO(hyunyoungs): handle cases where the child is not an icon but
-            // a folder or a widget.
-            workspace.snapToPage(pageIndex + 1);
+        switch (newIconIndex) {
+            case FocusLogic.NEXT_PAGE_FIRST_ITEM:
+                parent = getCellLayoutChildrenForIndex(workspace, pageIndex + 1);
+                newIcon = parent.getChildAt(0);
+                // TODO(hyunyoungs): handle cases where the child is not an icon but
+                // a folder or a widget.
+                workspace.snapToPage(pageIndex + 1);
+                break;
+            case FocusLogic.PREVIOUS_PAGE_FIRST_ITEM:
+                parent = getCellLayoutChildrenForIndex(workspace, pageIndex - 1);
+                newIcon = parent.getChildAt(0);
+                // TODO(hyunyoungs): handle cases where the child is not an icon but
+                // a folder or a widget.
+                workspace.snapToPage(pageIndex - 1);
+                break;
+            case FocusLogic.PREVIOUS_PAGE_LAST_ITEM:
+                parent = getCellLayoutChildrenForIndex(workspace, pageIndex - 1);
+                newIcon = parent.getChildAt(parent.getChildCount() - 1);
+                // TODO(hyunyoungs): handle cases where the child is not an icon but
+                // a folder or a widget.
+                workspace.snapToPage(pageIndex - 1);
+                break;
+            case FocusLogic.PREVIOUS_PAGE_LEFT_COLUMN:
+            case FocusLogic.PREVIOUS_PAGE_RIGHT_COLUMN:
+                // Go to the previous page but keep the focus on the same hotseat icon.
+                workspace.snapToPage(pageIndex - 1);
+                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);
+                break;
         }
         if (parent == iconParent && newIconIndex >= iconParent.getChildCount()) {
             newIconIndex -= iconParent.getChildCount();
@@ -364,12 +391,10 @@
                 }
                 int row = ((CellLayout.LayoutParams) v.getLayoutParams()).cellY;
                 parent = getCellLayoutChildrenForIndex(workspace, newPageIndex);
-                workspace.snapToPage(newPageIndex);
                 if (parent != null) {
-                    workspace.snapToPage(newPageIndex);
                     iconLayout = (CellLayout) parent.getParent();
                     matrix = FocusLogic.createSparseMatrix(iconLayout,
-                        iconLayout.getCountX(), row);
+                            iconLayout.getCountX(), row);
                     newIconIndex = FocusLogic.handleKeyEvent(keyCode, countX + 1, countY,
                             matrix, FocusLogic.PIVOT, newPageIndex, pageCount,
                             Utilities.isRtl(v.getResources()));
@@ -379,17 +404,14 @@
             case FocusLogic.PREVIOUS_PAGE_FIRST_ITEM:
                 parent = getCellLayoutChildrenForIndex(workspace, pageIndex - 1);
                 newIcon = parent.getChildAt(0);
-                workspace.snapToPage(pageIndex - 1);
                 break;
             case FocusLogic.PREVIOUS_PAGE_LAST_ITEM:
                 parent = getCellLayoutChildrenForIndex(workspace, pageIndex - 1);
                 newIcon = parent.getChildAt(parent.getChildCount() - 1);
-                workspace.snapToPage(pageIndex - 1);
                 break;
             case FocusLogic.NEXT_PAGE_FIRST_ITEM:
                 parent = getCellLayoutChildrenForIndex(workspace, pageIndex + 1);
                 newIcon = parent.getChildAt(0);
-                workspace.snapToPage(pageIndex + 1);
                 break;
             case FocusLogic.NEXT_PAGE_LEFT_COLUMN:
             case FocusLogic.PREVIOUS_PAGE_LEFT_COLUMN:
@@ -397,11 +419,9 @@
                 if (newIconIndex == FocusLogic.PREVIOUS_PAGE_LEFT_COLUMN) {
                     newPageIndex = pageIndex - 1;
                 }
-                workspace.snapToPage(newPageIndex);
                 row = ((CellLayout.LayoutParams) v.getLayoutParams()).cellY;
                 parent = getCellLayoutChildrenForIndex(workspace, newPageIndex);
                 if (parent != null) {
-                    workspace.snapToPage(newPageIndex);
                     iconLayout = (CellLayout) parent.getParent();
                     matrix = FocusLogic.createSparseMatrix(iconLayout, -1, row);
                     newIconIndex = FocusLogic.handleKeyEvent(keyCode, countX + 1, countY,
diff --git a/src/com/android/launcher3/Folder.java b/src/com/android/launcher3/Folder.java
index 9377bad..8c831b9 100644
--- a/src/com/android/launcher3/Folder.java
+++ b/src/com/android/launcher3/Folder.java
@@ -318,9 +318,10 @@
             sendCustomAccessibilityEvent(AccessibilityEvent.TYPE_WINDOW_STATE_CHANGED,
                     String.format(getContext().getString(R.string.folder_renamed), newTitle));
         }
-        // In order to clear the focus from the text field, we set the focus on ourself. This
-        // ensures that every time the field is clicked, focus is gained, giving reliable behavior.
-        requestFocus();
+
+        // This ensures that focus is gained every time the field is clicked, which selects all
+        // the text and brings up the soft keyboard if necessary.
+        mFolderName.clearFocus();
 
         Selection.setSelection((Spannable) mFolderName.getText(), 0, 0);
         mIsEditingName = false;
@@ -640,7 +641,7 @@
             @Override
             public void onAnimationEnd(Animator animation) {
                 setLayerType(LAYER_TYPE_NONE, null);
-                close();
+                close(true);
             }
             @Override
             public void onAnimationStart(Animator animation) {
@@ -654,7 +655,7 @@
         oa.start();
     }
 
-    public void close() {
+    public void close(boolean wasAnimated) {
         // TODO: Clear all active animations.
         DragLayer parent = (DragLayer) getParent();
         if (parent != null) {
@@ -662,7 +663,9 @@
         }
         mDragController.removeDropTarget(this);
         clearFocus();
-        mFolderIcon.requestFocus();
+        if (wasAnimated) {
+            mFolderIcon.requestFocus();
+        }
 
         if (mRearrangeOnClose) {
             rearrangeChildren();
@@ -1144,10 +1147,10 @@
                         // addInScreenFromBind() to ensure that hotseat items are placed correctly.
                         mLauncher.getWorkspace().addInScreenFromBind(newIcon, mInfo.container,
                                 mInfo.screenId, mInfo.cellX, mInfo.cellY, mInfo.spanX, mInfo.spanY);
-                    }
 
-                    // Focus the newly created child
-                    newIcon.requestFocus();
+                        // Focus the newly created child
+                        newIcon.requestFocus();
+                    }
                 }
             }
         };
@@ -1164,15 +1167,37 @@
         return mDestroyed;
     }
 
-    // This method keeps track of the last item in the folder for the purposes
+    // This method keeps track of the first and last item in the folder for the purposes
     // of keyboard focus
     public void updateTextViewFocus() {
-        View lastChild = mContent.getLastItem();
-        if (lastChild != null) {
+        final View firstChild = mContent.getFirstItem();
+        final View lastChild = mContent.getLastItem();
+        if (firstChild != null && lastChild != null) {
             mFolderName.setNextFocusDownId(lastChild.getId());
             mFolderName.setNextFocusRightId(lastChild.getId());
             mFolderName.setNextFocusLeftId(lastChild.getId());
             mFolderName.setNextFocusUpId(lastChild.getId());
+            // Hitting TAB from the folder name wraps around to the first item on the current
+            // folder page, and hitting SHIFT+TAB from that item wraps back to the folder name.
+            mFolderName.setNextFocusForwardId(firstChild.getId());
+            // When clicking off the folder when editing the name, this Folder gains focus. When
+            // pressing an arrow key from that state, give the focus to the first item.
+            this.setNextFocusDownId(firstChild.getId());
+            this.setNextFocusRightId(firstChild.getId());
+            this.setNextFocusLeftId(firstChild.getId());
+            this.setNextFocusUpId(firstChild.getId());
+            // When pressing shift+tab in the above state, give the focus to the last item.
+            setOnKeyListener(new OnKeyListener() {
+                @Override
+                public boolean onKey(View v, int keyCode, KeyEvent event) {
+                    boolean isShiftPlusTab = keyCode == KeyEvent.KEYCODE_TAB &&
+                            event.hasModifiers(KeyEvent.META_SHIFT_ON);
+                    if (isShiftPlusTab && Folder.this.isFocused()) {
+                        return lastChild.requestFocus();
+                    }
+                    return false;
+                }
+            });
         }
     }
 
@@ -1293,7 +1318,11 @@
             rearrangeChildren();
         }
         if (getItemCount() <= 1) {
-            replaceFolderWithFinalItem();
+            if (mInfo.opened) {
+                mLauncher.closeFolder(this, true);
+            } else {
+                replaceFolderWithFinalItem();
+            }
         }
     }
 
@@ -1337,6 +1366,8 @@
     public void onFocusChange(View v, boolean hasFocus) {
         if (v == mFolderName && hasFocus) {
             startEditingFolderName();
+        } else if (v == mFolderName && !hasFocus) {
+            dismissEditingName();
         }
     }
 
diff --git a/src/com/android/launcher3/FolderPagedView.java b/src/com/android/launcher3/FolderPagedView.java
index cc9c573..d503d2c 100644
--- a/src/com/android/launcher3/FolderPagedView.java
+++ b/src/com/android/launcher3/FolderPagedView.java
@@ -402,16 +402,28 @@
         return !ALLOW_FOLDER_SCROLL && getItemCount() >= mMaxItemsPerPage;
     }
 
+    public View getFirstItem() {
+        if (getChildCount() < 1) {
+            return null;
+        }
+        ShortcutAndWidgetContainer currContainer = getCurrentCellLayout().getShortcutsAndWidgets();
+        if (mGridCountX > 0) {
+            return currContainer.getChildAt(0, 0);
+        } else {
+            return currContainer.getChildAt(0);
+        }
+    }
+
     public View getLastItem() {
         if (getChildCount() < 1) {
             return null;
         }
-        ShortcutAndWidgetContainer lastContainer = getCurrentCellLayout().getShortcutsAndWidgets();
-        int lastRank = lastContainer.getChildCount() - 1;
+        ShortcutAndWidgetContainer currContainer = getCurrentCellLayout().getShortcutsAndWidgets();
+        int lastRank = currContainer.getChildCount() - 1;
         if (mGridCountX > 0) {
-            return lastContainer.getChildAt(lastRank % mGridCountX, lastRank / mGridCountX);
+            return currContainer.getChildAt(lastRank % mGridCountX, lastRank / mGridCountX);
         } else {
-            return lastContainer.getChildAt(lastRank);
+            return currContainer.getChildAt(lastRank);
         }
     }
 
diff --git a/src/com/android/launcher3/Launcher.java b/src/com/android/launcher3/Launcher.java
index d3934e6..97a4eed 100644
--- a/src/com/android/launcher3/Launcher.java
+++ b/src/com/android/launcher3/Launcher.java
@@ -52,6 +52,7 @@
 import android.graphics.Bitmap;
 import android.graphics.Canvas;
 import android.graphics.Color;
+import android.graphics.Point;
 import android.graphics.PorterDuff;
 import android.graphics.Rect;
 import android.graphics.drawable.ColorDrawable;
@@ -3203,7 +3204,7 @@
         if (animate) {
             folder.animateClosed();
         } else {
-            folder.close();
+            folder.close(false);
         }
 
         // Notify the accessibility manager that this folder "window" has disappeared and no
@@ -3607,17 +3608,17 @@
             DeviceProfile portraitProfile = app.getInvariantDeviceProfile().portraitProfile;
             DeviceProfile landscapeProfile = app.getInvariantDeviceProfile().landscapeProfile;
             float density = getResources().getDisplayMetrics().density;
-            Rect searchBounds = portraitProfile.getSearchBarBounds(Utilities.isRtl(getResources()));
-            int maxHeight = (int) (searchBounds.height() / density);
+            Point searchDimens = portraitProfile.getSearchBarDimensForWidgetOpts(getResources());
+            int maxHeight = (int) (searchDimens.y / density);
             int minHeight = maxHeight;
-            int maxWidth = (int) (searchBounds.width() / density);
+            int maxWidth = (int) (searchDimens.x / density);
             int minWidth = maxWidth;
             if (!landscapeProfile.isVerticalBarLayout()) {
-                searchBounds = landscapeProfile.getSearchBarBounds(Utilities.isRtl(getResources()));
-                maxHeight = (int) Math.max(maxHeight, searchBounds.height() / density);
-                minHeight = (int) Math.min(minHeight, searchBounds.height() / density);
-                maxWidth = (int) Math.max(maxWidth, searchBounds.width() / density);
-                minWidth = (int) Math.min(minWidth, searchBounds.width() / density);
+                searchDimens = landscapeProfile.getSearchBarDimensForWidgetOpts(getResources());
+                maxHeight = (int) Math.max(maxHeight, searchDimens.y / density);
+                minHeight = (int) Math.min(minHeight, searchDimens.y / density);
+                maxWidth = (int) Math.max(maxWidth, searchDimens.x / density);
+                minWidth = (int) Math.min(minWidth, searchDimens.x / density);
             }
             opts.putInt(AppWidgetManager.OPTION_APPWIDGET_MAX_HEIGHT, maxHeight);
             opts.putInt(AppWidgetManager.OPTION_APPWIDGET_MIN_HEIGHT, minHeight);
diff --git a/src/com/android/launcher3/LauncherAppWidgetHost.java b/src/com/android/launcher3/LauncherAppWidgetHost.java
index 28c5eac..b07ccc3 100644
--- a/src/com/android/launcher3/LauncherAppWidgetHost.java
+++ b/src/com/android/launcher3/LauncherAppWidgetHost.java
@@ -20,6 +20,7 @@
 import android.appwidget.AppWidgetHostView;
 import android.appwidget.AppWidgetProviderInfo;
 import android.content.Context;
+import android.os.DeadObjectException;
 import android.os.TransactionTooLargeException;
 import android.view.LayoutInflater;
 import android.view.View;
@@ -69,7 +70,8 @@
         try {
             super.startListening();
         } catch (Exception e) {
-            if (e.getCause() instanceof TransactionTooLargeException) {
+            if (e.getCause() instanceof TransactionTooLargeException ||
+                    e.getCause() instanceof DeadObjectException) {
                 // We're willing to let this slide. The exception is being caused by the list of
                 // RemoteViews which is being passed back. The startListening relationship will
                 // have been established by this point, and we will end up populating the
diff --git a/src/com/android/launcher3/PagedView.java b/src/com/android/launcher3/PagedView.java
index 2579ea3..9258360 100644
--- a/src/com/android/launcher3/PagedView.java
+++ b/src/com/android/launcher3/PagedView.java
@@ -2078,11 +2078,6 @@
         whichPage = validateNewPage(whichPage);
 
         mNextPage = whichPage;
-        View focusedChild = getFocusedChild();
-        if (focusedChild != null && whichPage != mCurrentPage &&
-                focusedChild == getPageAt(mCurrentPage)) {
-            focusedChild.clearFocus();
-        }
 
         pageBeginMoving();
         awakenScrollBars(duration);
diff --git a/src/com/android/launcher3/ShortcutAndWidgetContainer.java b/src/com/android/launcher3/ShortcutAndWidgetContainer.java
index 157b48a..56282fe 100644
--- a/src/com/android/launcher3/ShortcutAndWidgetContainer.java
+++ b/src/com/android/launcher3/ShortcutAndWidgetContainer.java
@@ -23,7 +23,6 @@
 import android.graphics.Rect;
 import android.view.View;
 import android.view.ViewGroup;
-import android.view.ViewGroup.LayoutParams;
 
 public class ShortcutAndWidgetContainer extends ViewGroup {
     static final String TAG = "CellLayoutChildren";
diff --git a/src/com/android/launcher3/allapps/AllAppsFastScrollHelper.java b/src/com/android/launcher3/allapps/AllAppsFastScrollHelper.java
index 1045342..73de45e 100644
--- a/src/com/android/launcher3/allapps/AllAppsFastScrollHelper.java
+++ b/src/com/android/launcher3/allapps/AllAppsFastScrollHelper.java
@@ -27,7 +27,7 @@
 
 public class AllAppsFastScrollHelper implements AllAppsGridAdapter.BindViewCallback {
 
-    private static final int INITIAL_TOUCH_SETTLING_DURATION = 300;
+    private static final int INITIAL_TOUCH_SETTLING_DURATION = 100;
     private static final int REPEAT_TOUCH_SETTLING_DURATION = 200;
     private static final float FAST_SCROLL_TOUCH_VELOCITY_BARRIER = 1900f;
 
@@ -219,7 +219,9 @@
         FastBitmapDrawable.State newState = FastBitmapDrawable.State.NORMAL;
         if (mCurrentFastScrollSection != null && pos > -1) {
             AlphabeticalAppsList.AdapterItem item = mApps.getAdapterItems().get(pos);
-            newState = item.sectionName.equals(mCurrentFastScrollSection) ?
+            boolean highlight = item.sectionName.equals(mCurrentFastScrollSection) &&
+                    item.position == mTargetFastScrollPosition;
+            newState = highlight ?
                     FastBitmapDrawable.State.FAST_SCROLL_HIGHLIGHTED :
                     FastBitmapDrawable.State.FAST_SCROLL_UNHIGHLIGHTED;
         }
diff --git a/src/com/android/launcher3/util/FocusLogic.java b/src/com/android/launcher3/util/FocusLogic.java
index 7f0da77..2aae3c0 100644
--- a/src/com/android/launcher3/util/FocusLogic.java
+++ b/src/com/android/launcher3/util/FocusLogic.java
@@ -346,6 +346,18 @@
                 }
             }
         }
+
+        // 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)
+        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;
+                    }
+                }
+            }
+        }
         return newIconIndex;
     }
 
@@ -494,9 +506,9 @@
     /**
      * @param edgeColumn the column of the new icon. either {@link #NEXT_PAGE_LEFT_COLUMN} or
      * {@link #NEXT_PAGE_RIGHT_COLUMN}
-     * @return the view adjacent to {@param oldView} in the {@param nextPage}.
+     * @return the view adjacent to {@param oldView} in the {@param nextPage} of the folder.
      */
-    public static View getAdjacentChildInNextPage(
+    public static View getAdjacentChildInNextFolderPage(
             ShortcutAndWidgetContainer nextPage, View oldView, int edgeColumn) {
         final int newRow = ((CellLayout.LayoutParams) oldView.getLayoutParams()).cellY;
 
diff --git a/tests/src/com/android/launcher3/InvariantDeviceProfileTest.java b/tests/src/com/android/launcher3/InvariantDeviceProfileTest.java
index a99ae4f..db3f72f 100644
--- a/tests/src/com/android/launcher3/InvariantDeviceProfileTest.java
+++ b/tests/src/com/android/launcher3/InvariantDeviceProfileTest.java
@@ -15,15 +15,14 @@
  */
 package com.android.launcher3;
 
+import android.content.res.Resources;
+import android.graphics.Point;
 import android.graphics.PointF;
+import android.graphics.Rect;
 import android.test.AndroidTestCase;
 import android.test.suitebuilder.annotation.SmallTest;
 import android.util.Log;
 
-import com.android.launcher3.DeviceProfile;
-import com.android.launcher3.InvariantDeviceProfile;
-import com.android.launcher3.util.FocusLogic;
-
 import java.util.ArrayList;
 
 /**
@@ -123,4 +122,72 @@
     // Add more tests for other devices, however, running them once on a single device is enough
     // for verifying that for a platform version, the WindowManager and DisplayMetrics is
     // working as intended.
+
+    /**
+     * Make sure that the height for the QSB is what we expect in normal mode.
+     */
+    public void testQsbNormalHeight() {
+        Resources resources = getContext().getResources();
+        DeviceProfile landscapeProfile = mInvariantProfile.landscapeProfile;
+        DeviceProfile portraitProfile = mInvariantProfile.portraitProfile;
+        landscapeProfile.setSearchBarHeight(LauncherCallbacks.SEARCH_BAR_HEIGHT_NORMAL);
+        portraitProfile.setSearchBarHeight(LauncherCallbacks.SEARCH_BAR_HEIGHT_NORMAL);
+        Rect portraitBounds = portraitProfile.getSearchBarBounds(true); // RTL shouldn't matter.
+        int portraitHeight = (int) Utilities.dpiFromPx(portraitBounds.height(),
+                resources.getDisplayMetrics());
+        Rect landscapeBounds = landscapeProfile.getSearchBarBounds(true); // RTL shouldn't matter.
+        int landscapeHeight = (int) Utilities.dpiFromPx(landscapeBounds.height(),
+                resources.getDisplayMetrics());
+        if (portraitProfile.isTablet) {
+            assertEquals(8 + 48 + 24, portraitHeight);
+        } else {
+            assertEquals(8 + 48 + 12, portraitHeight);
+        }
+        // Make sure the height that we pass in the widget options bundle is the height of the
+        // search bar + 8dps padding top and bottom.
+        Point portraitDimens = portraitProfile.getSearchBarDimensForWidgetOpts(resources);
+        int portraitWidgetOptsHeight = portraitDimens.y;
+        Point landscapeDimens = landscapeProfile.getSearchBarDimensForWidgetOpts(resources);
+        int landscapeWidgetOptsHeight = landscapeDimens.y;
+        assertEquals(8 + 48 + 8, (int) Utilities.dpiFromPx(portraitWidgetOptsHeight,
+                resources.getDisplayMetrics()));
+        if (!landscapeProfile.isVerticalBarLayout()) {
+            assertEquals(portraitHeight, landscapeHeight);
+            assertEquals(portraitWidgetOptsHeight, landscapeWidgetOptsHeight);
+        }
+    }
+
+    /**
+     * Make sure that the height for the QSB is what we expect in tall mode.
+     */
+    public void testQsbTallHeight() {
+        Resources resources = getContext().getResources();
+        DeviceProfile landscapeProfile = mInvariantProfile.landscapeProfile;
+        DeviceProfile portraitProfile = mInvariantProfile.portraitProfile;
+        landscapeProfile.setSearchBarHeight(LauncherCallbacks.SEARCH_BAR_HEIGHT_TALL);
+        portraitProfile.setSearchBarHeight(LauncherCallbacks.SEARCH_BAR_HEIGHT_TALL);
+        Rect portraitBounds = portraitProfile.getSearchBarBounds(true); // RTL shouldn't matter.
+        int portraitHeight = (int) Utilities.dpiFromPx(portraitBounds.height(),
+                resources.getDisplayMetrics());
+        Rect landscapeBounds = landscapeProfile.getSearchBarBounds(true); // RTL shouldn't matter.
+        int landscapeHeight = (int) Utilities.dpiFromPx(landscapeBounds.height(),
+                resources.getDisplayMetrics());
+        if (portraitProfile.isTablet) {
+            assertEquals(8 + 80 + 24, portraitHeight);
+        } else {
+            assertEquals(8 + 80 + 2, portraitHeight);
+        }
+        // Make sure the height that we pass in the widget options bundle is the height of the
+        // search bar + 8dps padding top and bottom.
+        Point portraitDimens = portraitProfile.getSearchBarDimensForWidgetOpts(resources);
+        int portraitWidgetOptsHeight = portraitDimens.y;
+        Point landscapeDimens = landscapeProfile.getSearchBarDimensForWidgetOpts(resources);
+        int landscapeWidgetOptsHeight = landscapeDimens.y;
+        assertEquals(8 + 80 + 8, (int) Utilities.dpiFromPx(portraitWidgetOptsHeight,
+                resources.getDisplayMetrics()));
+        if (!landscapeProfile.isVerticalBarLayout()) {
+            assertEquals(portraitHeight, landscapeHeight);
+            assertEquals(portraitWidgetOptsHeight, landscapeWidgetOptsHeight);
+        }
+    }
 }
diff --git a/tests/src/com/android/launcher3/util/FocusLogicTest.java b/tests/src/com/android/launcher3/util/FocusLogicTest.java
index ea87014..2c2f0d3 100644
--- a/tests/src/com/android/launcher3/util/FocusLogicTest.java
+++ b/tests/src/com/android/launcher3/util/FocusLogicTest.java
@@ -57,4 +57,39 @@
          // may get created in real world to test this method. OR 2) Move all the matrix
          // management routine to celllayout and write tests for them.
     }
+
+    public void testMoveFromBottomRightToBottomLeft() {
+        int[][] map = transpose(new int[][] {
+                {-1, 0, -1, -1, -1, -1},
+                {-1, -1, -1, -1, -1, -1},
+                {-1, -1, -1, -1, -1, -1},
+                {-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);
+        assertEquals(1, i);
+    }
+
+    public void testMoveFromBottomRightToTopLeft() {
+        int[][] map = transpose(new int[][] {
+                {-1, 0, -1, -1, -1, -1},
+                {-1, -1, -1, -1, -1, -1},
+                {-1, -1, -1, -1, -1, -1},
+                {-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);
+        assertEquals(0, i);
+    }
+
+    /** Transposes the matrix so that we can write it in human-readable format in the tests. */
+    private int[][] transpose(int[][] m) {
+        int[][] t = new int[m[0].length][m.length];
+        for (int i = 0; i < m.length; i++) {
+            for (int j = 0; j < m[0].length; j++) {
+                t[j][i] = m[i][j];
+            }
+        }
+        return t;
+    }
 }