Merge "Adding logs to track down missing workspace screen. (Bug 11683562)" into jb-ub-now-jolly-elf
diff --git a/src/com/android/launcher3/AppsCustomizePagedView.java b/src/com/android/launcher3/AppsCustomizePagedView.java
index d9ca157..688ff82 100644
--- a/src/com/android/launcher3/AppsCustomizePagedView.java
+++ b/src/com/android/launcher3/AppsCustomizePagedView.java
@@ -916,6 +916,13 @@
     }
 
     @Override
+    public float getIntrinsicIconScaleFactor() {
+        LauncherAppState app = LauncherAppState.getInstance();
+        DeviceProfile grid = app.getDynamicGrid().getDeviceProfile();
+        return (float) grid.allAppsIconSizePx / grid.iconSizePx;
+    }
+
+    @Override
     protected void onDetachedFromWindow() {
         super.onDetachedFromWindow();
         cancelAllTasks();
diff --git a/src/com/android/launcher3/DeviceProfile.java b/src/com/android/launcher3/DeviceProfile.java
new file mode 100644
index 0000000..626ec42
--- /dev/null
+++ b/src/com/android/launcher3/DeviceProfile.java
@@ -0,0 +1,717 @@
+/*
+ * Copyright (C) 2008 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;
+
+import android.appwidget.AppWidgetHostView;
+import android.content.ComponentName;
+import android.content.Context;
+import android.content.res.Configuration;
+import android.content.res.Resources;
+import android.graphics.Paint;
+import android.graphics.Paint.FontMetrics;
+import android.graphics.Point;
+import android.graphics.PointF;
+import android.graphics.Rect;
+import android.util.DisplayMetrics;
+import android.view.Display;
+import android.view.Gravity;
+import android.view.Surface;
+import android.view.View;
+import android.view.ViewGroup.LayoutParams;
+import android.view.WindowManager;
+import android.widget.FrameLayout;
+
+import java.util.ArrayList;
+import java.util.Collections;
+import java.util.Comparator;
+
+
+class DeviceProfileQuery {
+    float widthDps;
+    float heightDps;
+    float value;
+    PointF dimens;
+
+    DeviceProfileQuery(float w, float h, float v) {
+        widthDps = w;
+        heightDps = h;
+        value = v;
+        dimens = new PointF(w, h);
+    }
+}
+
+public class DeviceProfile {
+    public static interface DeviceProfileCallbacks {
+        public void onAvailableSizeChanged(DeviceProfile grid);
+    }
+
+    String name;
+    float minWidthDps;
+    float minHeightDps;
+    float numRows;
+    float numColumns;
+    float numHotseatIcons;
+    private float iconSize;
+    private float iconTextSize;
+    private int iconDrawablePaddingOriginalPx;
+    private float hotseatIconSize;
+
+    boolean isLandscape;
+    boolean isTablet;
+    boolean isLargeTablet;
+    boolean transposeLayoutWithOrientation;
+
+    int desiredWorkspaceLeftRightMarginPx;
+    int edgeMarginPx;
+    Rect defaultWidgetPadding;
+
+    int widthPx;
+    int heightPx;
+    int availableWidthPx;
+    int availableHeightPx;
+    int defaultPageSpacingPx;
+
+    int overviewModeMinIconZoneHeightPx;
+    int overviewModeMaxIconZoneHeightPx;
+    int overviewModeMaxBarWidthPx;
+    float overviewModeIconZoneRatio;
+    float overviewModeScaleFactor;
+
+    int iconSizePx;
+    int iconTextSizePx;
+    int iconDrawablePaddingPx;
+    int cellWidthPx;
+    int cellHeightPx;
+    int allAppsIconSizePx;
+    int allAppsIconTextSizePx;
+    int allAppsCellWidthPx;
+    int allAppsCellHeightPx;
+    int allAppsCellPaddingPx;
+    int folderBackgroundOffset;
+    int folderIconSizePx;
+    int folderCellWidthPx;
+    int folderCellHeightPx;
+    int hotseatCellWidthPx;
+    int hotseatCellHeightPx;
+    int hotseatIconSizePx;
+    int hotseatBarHeightPx;
+    int hotseatAllAppsRank;
+    int allAppsNumRows;
+    int allAppsNumCols;
+    int searchBarSpaceWidthPx;
+    int searchBarSpaceMaxWidthPx;
+    int searchBarSpaceHeightPx;
+    int searchBarHeightPx;
+    int pageIndicatorHeightPx;
+
+    private ArrayList<DeviceProfileCallbacks> mCallbacks = new ArrayList<DeviceProfileCallbacks>();
+
+    DeviceProfile(String n, float w, float h, float r, float c,
+                  float is, float its, float hs, float his) {
+        // Ensure that we have an odd number of hotseat items (since we need to place all apps)
+        if (!AppsCustomizePagedView.DISABLE_ALL_APPS && hs % 2 == 0) {
+            throw new RuntimeException("All Device Profiles must have an odd number of hotseat spaces");
+        }
+
+        name = n;
+        minWidthDps = w;
+        minHeightDps = h;
+        numRows = r;
+        numColumns = c;
+        iconSize = is;
+        iconTextSize = its;
+        numHotseatIcons = hs;
+        hotseatIconSize = his;
+    }
+
+    DeviceProfile(Context context,
+                  ArrayList<DeviceProfile> profiles,
+                  float minWidth, float minHeight,
+                  int wPx, int hPx,
+                  int awPx, int ahPx,
+                  Resources res) {
+        DisplayMetrics dm = res.getDisplayMetrics();
+        ArrayList<DeviceProfileQuery> points =
+                new ArrayList<DeviceProfileQuery>();
+        transposeLayoutWithOrientation =
+                res.getBoolean(R.bool.hotseat_transpose_layout_with_orientation);
+        minWidthDps = minWidth;
+        minHeightDps = minHeight;
+
+        ComponentName cn = new ComponentName(context.getPackageName(),
+                this.getClass().getName());
+        defaultWidgetPadding = AppWidgetHostView.getDefaultPaddingForWidget(context, cn, null);
+        edgeMarginPx = res.getDimensionPixelSize(R.dimen.dynamic_grid_edge_margin);
+        desiredWorkspaceLeftRightMarginPx = 2 * edgeMarginPx;
+        pageIndicatorHeightPx =
+                res.getDimensionPixelSize(R.dimen.dynamic_grid_page_indicator_height);
+        defaultPageSpacingPx =
+                res.getDimensionPixelSize(R.dimen.dynamic_grid_workspace_page_spacing);
+        allAppsCellPaddingPx =
+                res.getDimensionPixelSize(R.dimen.dynamic_grid_all_apps_cell_padding);
+        overviewModeMinIconZoneHeightPx =
+                res.getDimensionPixelSize(R.dimen.dynamic_grid_overview_min_icon_zone_height);
+        overviewModeMaxIconZoneHeightPx =
+                res.getDimensionPixelSize(R.dimen.dynamic_grid_overview_max_icon_zone_height);
+        overviewModeMaxBarWidthPx =
+                res.getDimensionPixelSize(R.dimen.dynamic_grid_overview_bar_max_width);
+        overviewModeIconZoneRatio =
+                res.getInteger(R.integer.config_dynamic_grid_overview_icon_zone_percentage) / 100f;
+        overviewModeScaleFactor =
+                res.getInteger(R.integer.config_dynamic_grid_overview_scale_percentage) / 100f;
+
+        // Interpolate the rows
+        for (DeviceProfile p : profiles) {
+            points.add(new DeviceProfileQuery(p.minWidthDps, p.minHeightDps, p.numRows));
+        }
+        numRows = Math.round(invDistWeightedInterpolate(minWidth, minHeight, points));
+        // Interpolate the columns
+        points.clear();
+        for (DeviceProfile p : profiles) {
+            points.add(new DeviceProfileQuery(p.minWidthDps, p.minHeightDps, p.numColumns));
+        }
+        numColumns = Math.round(invDistWeightedInterpolate(minWidth, minHeight, points));
+        // Interpolate the hotseat length
+        points.clear();
+        for (DeviceProfile p : profiles) {
+            points.add(new DeviceProfileQuery(p.minWidthDps, p.minHeightDps, p.numHotseatIcons));
+        }
+        numHotseatIcons = Math.round(invDistWeightedInterpolate(minWidth, minHeight, points));
+        hotseatAllAppsRank = (int) (numHotseatIcons / 2);
+
+        // Interpolate the icon size
+        points.clear();
+        for (DeviceProfile p : profiles) {
+            points.add(new DeviceProfileQuery(p.minWidthDps, p.minHeightDps, p.iconSize));
+        }
+        iconSize = invDistWeightedInterpolate(minWidth, minHeight, points);
+        // AllApps uses the original non-scaled icon size
+        allAppsIconSizePx = DynamicGrid.pxFromDp(iconSize, dm);
+
+        // Interpolate the icon text size
+        points.clear();
+        for (DeviceProfile p : profiles) {
+            points.add(new DeviceProfileQuery(p.minWidthDps, p.minHeightDps, p.iconTextSize));
+        }
+        iconTextSize = invDistWeightedInterpolate(minWidth, minHeight, points);
+        iconDrawablePaddingOriginalPx =
+                res.getDimensionPixelSize(R.dimen.dynamic_grid_icon_drawable_padding);
+        // AllApps uses the original non-scaled icon text size
+        allAppsIconTextSizePx = DynamicGrid.pxFromDp(iconTextSize, dm);
+
+        // Interpolate the hotseat icon size
+        points.clear();
+        for (DeviceProfile p : profiles) {
+            points.add(new DeviceProfileQuery(p.minWidthDps, p.minHeightDps, p.hotseatIconSize));
+        }
+        // Hotseat
+        hotseatIconSize = invDistWeightedInterpolate(minWidth, minHeight, points);
+
+        // Calculate the remaining vars
+        updateFromConfiguration(context, res, wPx, hPx, awPx, ahPx);
+        updateAvailableDimensions(context);
+    }
+
+    void addCallback(DeviceProfileCallbacks cb) {
+        mCallbacks.add(cb);
+        cb.onAvailableSizeChanged(this);
+    }
+    void removeCallback(DeviceProfileCallbacks cb) {
+        mCallbacks.remove(cb);
+    }
+
+    private int getDeviceOrientation(Context context) {
+        WindowManager windowManager =  (WindowManager)
+                context.getSystemService(Context.WINDOW_SERVICE);
+        Resources resources = context.getResources();
+        DisplayMetrics dm = resources.getDisplayMetrics();
+        Configuration config = resources.getConfiguration();
+        int rotation = windowManager.getDefaultDisplay().getRotation();
+
+        boolean isLandscape = (config.orientation == Configuration.ORIENTATION_LANDSCAPE) &&
+                (rotation == Surface.ROTATION_0 || rotation == Surface.ROTATION_180);
+        boolean isRotatedPortrait = (config.orientation == Configuration.ORIENTATION_PORTRAIT) &&
+                (rotation == Surface.ROTATION_90 || rotation == Surface.ROTATION_270);
+        if (isLandscape || isRotatedPortrait) {
+            return CellLayout.LANDSCAPE;
+        } else {
+            return CellLayout.PORTRAIT;
+        }
+    }
+
+    private void updateAvailableDimensions(Context context) {
+        WindowManager windowManager =  (WindowManager)
+                context.getSystemService(Context.WINDOW_SERVICE);
+        Display display = windowManager.getDefaultDisplay();
+        Resources resources = context.getResources();
+        DisplayMetrics dm = resources.getDisplayMetrics();
+        Configuration config = resources.getConfiguration();
+
+        // There are three possible configurations that the dynamic grid accounts for, portrait,
+        // landscape with the nav bar at the bottom, and landscape with the nav bar at the side.
+        // To prevent waiting for fitSystemWindows(), we make the observation that in landscape,
+        // the height is the smallest height (either with the nav bar at the bottom or to the
+        // side) and otherwise, the height is simply the largest possible height for a portrait
+        // device.
+        Point size = new Point();
+        Point smallestSize = new Point();
+        Point largestSize = new Point();
+        display.getSize(size);
+        display.getCurrentSizeRange(smallestSize, largestSize);
+        availableWidthPx = size.x;
+        if (config.orientation == Configuration.ORIENTATION_LANDSCAPE) {
+            availableHeightPx = smallestSize.y;
+        } else {
+            availableHeightPx = largestSize.y;
+        }
+
+        // Check to see if the icons fit in the new available height.  If not, then we need to
+        // shrink the icon size.
+        Rect workspacePadding = getWorkspacePadding();
+        float scale = 1f;
+        int drawablePadding = iconDrawablePaddingOriginalPx;
+        updateIconSize(1f, drawablePadding, resources, dm);
+        float usedHeight = (cellHeightPx * numRows);
+        int maxHeight = (availableHeightPx - workspacePadding.top - workspacePadding.bottom);
+        if (usedHeight > maxHeight) {
+            scale = maxHeight / usedHeight;
+            drawablePadding = 0;
+        }
+        updateIconSize(scale, drawablePadding, resources, dm);
+
+        // Make the callbacks
+        for (DeviceProfileCallbacks cb : mCallbacks) {
+            cb.onAvailableSizeChanged(this);
+        }
+    }
+
+    private void updateIconSize(float scale, int drawablePadding, Resources resources,
+                                DisplayMetrics dm) {
+        iconSizePx = (int) (DynamicGrid.pxFromDp(iconSize, dm) * scale);
+        iconTextSizePx = (int) (DynamicGrid.pxFromSp(iconTextSize, dm) * scale);
+        iconDrawablePaddingPx = drawablePadding;
+        hotseatIconSizePx = (int) (DynamicGrid.pxFromDp(hotseatIconSize, dm) * scale);
+
+        // Search Bar
+        searchBarSpaceMaxWidthPx = resources.getDimensionPixelSize(R.dimen.dynamic_grid_search_bar_max_width);
+        searchBarHeightPx = resources.getDimensionPixelSize(R.dimen.dynamic_grid_search_bar_height);
+        searchBarSpaceWidthPx = Math.min(searchBarSpaceMaxWidthPx, widthPx);
+        searchBarSpaceHeightPx = searchBarHeightPx + 2 * edgeMarginPx;
+
+        // Calculate the actual text height
+        Paint textPaint = new Paint();
+        textPaint.setTextSize(iconTextSizePx);
+        FontMetrics fm = textPaint.getFontMetrics();
+        cellWidthPx = iconSizePx;
+        cellHeightPx = iconSizePx + iconDrawablePaddingPx + (int) Math.ceil(fm.bottom - fm.top);
+
+        // Hotseat
+        hotseatBarHeightPx = iconSizePx + 4 * edgeMarginPx;
+        hotseatCellWidthPx = iconSizePx;
+        hotseatCellHeightPx = iconSizePx;
+
+        // Folder
+        folderCellWidthPx = cellWidthPx + 3 * edgeMarginPx;
+        folderCellHeightPx = cellHeightPx + edgeMarginPx;
+        folderBackgroundOffset = -edgeMarginPx;
+        folderIconSizePx = iconSizePx + 2 * -folderBackgroundOffset;
+
+        // All Apps
+        Rect padding = getWorkspacePadding(isLandscape ?
+                CellLayout.LANDSCAPE : CellLayout.PORTRAIT);
+        int pageIndicatorOffset =
+                resources.getDimensionPixelSize(R.dimen.apps_customize_page_indicator_offset);
+        allAppsCellWidthPx = allAppsIconSizePx;
+        allAppsCellHeightPx = allAppsIconSizePx + drawablePadding + iconTextSizePx;
+        int maxLongEdgeCellCount =
+                resources.getInteger(R.integer.config_dynamic_grid_max_long_edge_cell_count);
+        int maxShortEdgeCellCount =
+                resources.getInteger(R.integer.config_dynamic_grid_max_short_edge_cell_count);
+        int minEdgeCellCount =
+                resources.getInteger(R.integer.config_dynamic_grid_min_edge_cell_count);
+        int maxRows = (isLandscape ? maxShortEdgeCellCount : maxLongEdgeCellCount);
+        int maxCols = (isLandscape ? maxLongEdgeCellCount : maxShortEdgeCellCount);
+
+        allAppsNumRows = (availableHeightPx - pageIndicatorHeightPx) /
+                (allAppsCellHeightPx + allAppsCellPaddingPx);
+        allAppsNumRows = Math.max(minEdgeCellCount, Math.min(maxRows, allAppsNumRows));
+        allAppsNumCols = (availableWidthPx) /
+                (allAppsCellWidthPx + allAppsCellPaddingPx);
+        allAppsNumCols = Math.max(minEdgeCellCount, Math.min(maxCols, allAppsNumCols));
+    }
+
+    void updateFromConfiguration(Context context, Resources resources, int wPx, int hPx,
+                                 int awPx, int ahPx) {
+        isLandscape = (resources.getConfiguration().orientation ==
+                Configuration.ORIENTATION_LANDSCAPE);
+        isTablet = resources.getBoolean(R.bool.is_tablet);
+        isLargeTablet = resources.getBoolean(R.bool.is_large_tablet);
+        widthPx = wPx;
+        heightPx = hPx;
+        availableWidthPx = awPx;
+        availableHeightPx = ahPx;
+
+        updateAvailableDimensions(context);
+    }
+
+    private float dist(PointF p0, PointF p1) {
+        return (float) Math.sqrt((p1.x - p0.x)*(p1.x-p0.x) +
+                (p1.y-p0.y)*(p1.y-p0.y));
+    }
+
+    private float weight(PointF a, PointF b,
+                        float pow) {
+        float d = dist(a, b);
+        if (d == 0f) {
+            return Float.POSITIVE_INFINITY;
+        }
+        return (float) (1f / Math.pow(d, pow));
+    }
+
+    private float invDistWeightedInterpolate(float width, float height,
+                ArrayList<DeviceProfileQuery> points) {
+        float sum = 0;
+        float weights = 0;
+        float pow = 5;
+        float kNearestNeighbors = 3;
+        final PointF xy = new PointF(width, height);
+
+        ArrayList<DeviceProfileQuery> pointsByNearness = points;
+        Collections.sort(pointsByNearness, new Comparator<DeviceProfileQuery>() {
+            public int compare(DeviceProfileQuery a, DeviceProfileQuery b) {
+                return (int) (dist(xy, a.dimens) - dist(xy, b.dimens));
+            }
+        });
+
+        for (int i = 0; i < pointsByNearness.size(); ++i) {
+            DeviceProfileQuery p = pointsByNearness.get(i);
+            if (i < kNearestNeighbors) {
+                float w = weight(xy, p.dimens, pow);
+                if (w == Float.POSITIVE_INFINITY) {
+                    return p.value;
+                }
+                weights += w;
+            }
+        }
+
+        for (int i = 0; i < pointsByNearness.size(); ++i) {
+            DeviceProfileQuery p = pointsByNearness.get(i);
+            if (i < kNearestNeighbors) {
+                float w = weight(xy, p.dimens, pow);
+                sum += w * p.value / weights;
+            }
+        }
+
+        return sum;
+    }
+
+    /** Returns the search bar bounds in the current orientation */
+    Rect getSearchBarBounds() {
+        return getSearchBarBounds(isLandscape ? CellLayout.LANDSCAPE : CellLayout.PORTRAIT);
+    }
+    /** Returns the search bar bounds in the specified orientation */
+    Rect getSearchBarBounds(int orientation) {
+        Rect bounds = new Rect();
+        if (orientation == CellLayout.LANDSCAPE &&
+                transposeLayoutWithOrientation) {
+            bounds.set(0, edgeMarginPx, searchBarSpaceHeightPx, availableHeightPx - edgeMarginPx);
+        } else {
+            if (isTablet()) {
+                // Pad the left and right of the workspace to ensure consistent spacing
+                // between all icons
+                int width = (orientation == CellLayout.LANDSCAPE)
+                        ? Math.max(widthPx, heightPx)
+                        : Math.min(widthPx, heightPx);
+                // XXX: If the icon size changes across orientations, we will have to take
+                //      that into account here too.
+                int gap = (int) ((width - 2 * edgeMarginPx -
+                        (numColumns * cellWidthPx)) / (2 * (numColumns + 1)));
+                bounds.set(edgeMarginPx + gap, 0, availableWidthPx - (edgeMarginPx + gap),
+                        searchBarSpaceHeightPx);
+            } else {
+                bounds.set(desiredWorkspaceLeftRightMarginPx - defaultWidgetPadding.left, 0,
+                        availableWidthPx - (desiredWorkspaceLeftRightMarginPx -
+                        defaultWidgetPadding.right), searchBarSpaceHeightPx);
+            }
+        }
+        return bounds;
+    }
+
+    /** Returns the workspace padding in the specified orientation */
+    Rect getWorkspacePadding() {
+        return getWorkspacePadding(isLandscape ? CellLayout.LANDSCAPE : CellLayout.PORTRAIT);
+    }
+    Rect getWorkspacePadding(int orientation) {
+        Rect searchBarBounds = getSearchBarBounds(orientation);
+        Rect padding = new Rect();
+        if (orientation == CellLayout.LANDSCAPE &&
+                transposeLayoutWithOrientation) {
+            // Pad the left and right of the workspace with search/hotseat bar sizes
+            padding.set(searchBarBounds.right, edgeMarginPx,
+                    hotseatBarHeightPx, edgeMarginPx);
+        } else {
+            if (isTablet()) {
+                // Pad the left and right of the workspace to ensure consistent spacing
+                // between all icons
+                int width = (orientation == CellLayout.LANDSCAPE)
+                        ? Math.max(widthPx, heightPx)
+                        : Math.min(widthPx, heightPx);
+                // XXX: If the icon size changes across orientations, we will have to take
+                //      that into account here too.
+                int gap = (int) ((width - 2 * edgeMarginPx -
+                        (numColumns * cellWidthPx)) / (2 * (numColumns + 1)));
+                padding.set(edgeMarginPx + gap,
+                        searchBarBounds.bottom,
+                        edgeMarginPx + gap,
+                        hotseatBarHeightPx + pageIndicatorHeightPx);
+            } else {
+                // Pad the top and bottom of the workspace with search/hotseat bar sizes
+                padding.set(desiredWorkspaceLeftRightMarginPx - defaultWidgetPadding.left,
+                        searchBarBounds.bottom,
+                        desiredWorkspaceLeftRightMarginPx - defaultWidgetPadding.right,
+                        hotseatBarHeightPx + pageIndicatorHeightPx);
+            }
+        }
+        return padding;
+    }
+
+    int getWorkspacePageSpacing(int orientation) {
+        if (orientation == CellLayout.LANDSCAPE &&
+                transposeLayoutWithOrientation) {
+            // In landscape mode the page spacing is set to the default.
+            return defaultPageSpacingPx;
+        } else {
+            // In portrait, we want the pages spaced such that there is no
+            // overhang of the previous / next page into the current page viewport.
+            // We assume symmetrical padding in portrait mode.
+            return 2 * getWorkspacePadding().left;
+        }
+    }
+
+    Rect getOverviewModeButtonBarRect() {
+        int zoneHeight = (int) (overviewModeIconZoneRatio * availableHeightPx);
+        zoneHeight = Math.min(overviewModeMaxIconZoneHeightPx,
+                Math.max(overviewModeMinIconZoneHeightPx, zoneHeight));
+        return new Rect(0, availableHeightPx - zoneHeight, 0, availableHeightPx);
+    }
+
+    float getOverviewModeScale() {
+        Rect workspacePadding = getWorkspacePadding();
+        Rect overviewBar = getOverviewModeButtonBarRect();
+        int pageSpace = availableHeightPx - workspacePadding.top - workspacePadding.bottom;
+        return (overviewModeScaleFactor * (pageSpace - overviewBar.height())) / pageSpace;
+    }
+
+    // The rect returned will be extended to below the system ui that covers the workspace
+    Rect getHotseatRect() {
+        if (isVerticalBarLayout()) {
+            return new Rect(availableWidthPx - hotseatBarHeightPx, 0,
+                    Integer.MAX_VALUE, availableHeightPx);
+        } else {
+            return new Rect(0, availableHeightPx - hotseatBarHeightPx,
+                    availableWidthPx, Integer.MAX_VALUE);
+        }
+    }
+
+    int calculateCellWidth(int width, int countX) {
+        return width / countX;
+    }
+    int calculateCellHeight(int height, int countY) {
+        return height / countY;
+    }
+
+    boolean isPhone() {
+        return !isTablet && !isLargeTablet;
+    }
+    boolean isTablet() {
+        return isTablet;
+    }
+    boolean isLargeTablet() {
+        return isLargeTablet;
+    }
+
+    boolean isVerticalBarLayout() {
+        return isLandscape && transposeLayoutWithOrientation;
+    }
+
+    boolean shouldFadeAdjacentWorkspaceScreens() {
+        return isVerticalBarLayout() || isLargeTablet();
+    }
+
+    public void layout(Launcher launcher) {
+        FrameLayout.LayoutParams lp;
+        Resources res = launcher.getResources();
+        boolean hasVerticalBarLayout = isVerticalBarLayout();
+
+        // Layout the search bar space
+        View searchBar = launcher.getSearchBar();
+        lp = (FrameLayout.LayoutParams) searchBar.getLayoutParams();
+        if (hasVerticalBarLayout) {
+            // Vertical search bar
+            lp.gravity = Gravity.TOP | Gravity.LEFT;
+            lp.width = searchBarSpaceHeightPx;
+            lp.height = LayoutParams.MATCH_PARENT;
+            searchBar.setPadding(
+                    0, 2 * edgeMarginPx, 0,
+                    2 * edgeMarginPx);
+        } else {
+            // Horizontal search bar
+            lp.gravity = Gravity.TOP | Gravity.CENTER_HORIZONTAL;
+            lp.width = searchBarSpaceWidthPx;
+            lp.height = searchBarSpaceHeightPx;
+            searchBar.setPadding(
+                    2 * edgeMarginPx,
+                    2 * edgeMarginPx,
+                    2 * edgeMarginPx, 0);
+        }
+        searchBar.setLayoutParams(lp);
+
+        // Layout the search bar
+        View qsbBar = launcher.getQsbBar();
+        LayoutParams vglp = qsbBar.getLayoutParams();
+        vglp.width = LayoutParams.MATCH_PARENT;
+        vglp.height = LayoutParams.MATCH_PARENT;
+        qsbBar.setLayoutParams(vglp);
+
+        // Layout the voice proxy
+        View voiceButtonProxy = launcher.findViewById(R.id.voice_button_proxy);
+        if (voiceButtonProxy != null) {
+            if (hasVerticalBarLayout) {
+                // TODO: MOVE THIS INTO SEARCH BAR MEASURE
+            } else {
+                lp = (FrameLayout.LayoutParams) voiceButtonProxy.getLayoutParams();
+                lp.gravity = Gravity.TOP | Gravity.END;
+                lp.width = (widthPx - searchBarSpaceWidthPx) / 2 +
+                        2 * iconSizePx;
+                lp.height = searchBarSpaceHeightPx;
+            }
+        }
+
+        // Layout the workspace
+        PagedView workspace = (PagedView) launcher.findViewById(R.id.workspace);
+        lp = (FrameLayout.LayoutParams) workspace.getLayoutParams();
+        lp.gravity = Gravity.CENTER;
+        int orientation = isLandscape ? CellLayout.LANDSCAPE : CellLayout.PORTRAIT;
+        Rect padding = getWorkspacePadding(orientation);
+        workspace.setLayoutParams(lp);
+        workspace.setPadding(padding.left, padding.top, padding.right, padding.bottom);
+        workspace.setPageSpacing(getWorkspacePageSpacing(orientation));
+
+        // Layout the hotseat
+        View hotseat = launcher.findViewById(R.id.hotseat);
+        lp = (FrameLayout.LayoutParams) hotseat.getLayoutParams();
+        if (hasVerticalBarLayout) {
+            // Vertical hotseat
+            lp.gravity = Gravity.RIGHT;
+            lp.width = hotseatBarHeightPx;
+            lp.height = LayoutParams.MATCH_PARENT;
+            hotseat.findViewById(R.id.layout).setPadding(0, 2 * edgeMarginPx, 0, 2 * edgeMarginPx);
+        } else if (isTablet()) {
+            // Pad the hotseat with the grid gap calculated above
+            int gridGap = (int) ((widthPx - 2 * edgeMarginPx -
+                    (numColumns * cellWidthPx)) / (2 * (numColumns + 1)));
+            int gridWidth = (int) ((numColumns * cellWidthPx) +
+                    ((numColumns - 1) * gridGap));
+            int hotseatGap = (int) Math.max(0,
+                    (gridWidth - (numHotseatIcons * hotseatCellWidthPx))
+                            / (numHotseatIcons - 1));
+            lp.gravity = Gravity.BOTTOM;
+            lp.width = LayoutParams.MATCH_PARENT;
+            lp.height = hotseatBarHeightPx;
+            hotseat.setPadding(2 * edgeMarginPx + gridGap + hotseatGap, 0,
+                    2 * edgeMarginPx + gridGap + hotseatGap,
+                    2 * edgeMarginPx);
+        } else {
+            // For phones, layout the hotseat without any bottom margin
+            // to ensure that we have space for the folders
+            lp.gravity = Gravity.BOTTOM;
+            lp.width = LayoutParams.MATCH_PARENT;
+            lp.height = hotseatBarHeightPx;
+            hotseat.findViewById(R.id.layout).setPadding(2 * edgeMarginPx, 0,
+                    2 * edgeMarginPx, 0);
+        }
+        hotseat.setLayoutParams(lp);
+
+        // Layout the page indicators
+        View pageIndicator = launcher.findViewById(R.id.page_indicator);
+        if (pageIndicator != null) {
+            if (hasVerticalBarLayout) {
+                // Hide the page indicators when we have vertical search/hotseat
+                pageIndicator.setVisibility(View.GONE);
+            } else {
+                // Put the page indicators above the hotseat
+                lp = (FrameLayout.LayoutParams) pageIndicator.getLayoutParams();
+                lp.gravity = Gravity.CENTER_HORIZONTAL | Gravity.BOTTOM;
+                lp.width = LayoutParams.WRAP_CONTENT;
+                lp.height = LayoutParams.WRAP_CONTENT;
+                lp.bottomMargin = hotseatBarHeightPx;
+                pageIndicator.setLayoutParams(lp);
+            }
+        }
+
+        // Layout AllApps
+        AppsCustomizeTabHost host = (AppsCustomizeTabHost)
+                launcher.findViewById(R.id.apps_customize_pane);
+        if (host != null) {
+            // Center the all apps page indicator
+            int pageIndicatorHeight = (int) (pageIndicatorHeightPx * Math.min(1f,
+                    (allAppsIconSizePx / DynamicGrid.DEFAULT_ICON_SIZE_PX)));
+            pageIndicator = host.findViewById(R.id.apps_customize_page_indicator);
+            if (pageIndicator != null) {
+                lp = (FrameLayout.LayoutParams) pageIndicator.getLayoutParams();
+                lp.gravity = Gravity.CENTER_HORIZONTAL | Gravity.BOTTOM;
+                lp.width = LayoutParams.WRAP_CONTENT;
+                lp.height = pageIndicatorHeight;
+                pageIndicator.setLayoutParams(lp);
+            }
+
+            AppsCustomizePagedView pagedView = (AppsCustomizePagedView)
+                    host.findViewById(R.id.apps_customize_pane_content);
+            padding = new Rect();
+            if (pagedView != null) {
+                // Constrain the dimensions of all apps so that it does not span the full width
+                int paddingLR = (availableWidthPx - (allAppsCellWidthPx * allAppsNumCols)) /
+                        (2 * (allAppsNumCols + 1));
+                int paddingTB = (availableHeightPx - (allAppsCellHeightPx * allAppsNumRows)) /
+                        (2 * (allAppsNumRows + 1));
+                paddingLR = Math.min(paddingLR, (int)((paddingLR + paddingTB) * 0.75f));
+                paddingTB = Math.min(paddingTB, (int)((paddingLR + paddingTB) * 0.75f));
+                int maxAllAppsWidth = (allAppsNumCols * (allAppsCellWidthPx + 2 * paddingLR));
+                int gridPaddingLR = (availableWidthPx - maxAllAppsWidth) / 2;
+                if (gridPaddingLR > (allAppsCellWidthPx / 4)) {
+                    padding.left = padding.right = gridPaddingLR;
+                }
+                // The icons are centered, so we can't just offset by the page indicator height
+                // because the empty space will actually be pageIndicatorHeight + paddingTB
+                padding.bottom = Math.max(0, pageIndicatorHeight - paddingTB);
+                pagedView.setAllAppsPadding(padding);
+                pagedView.setWidgetsPageIndicatorPadding(pageIndicatorHeight);
+            }
+        }
+
+        // Layout the Overview Mode
+        View overviewMode = launcher.getOverviewPanel();
+        if (overviewMode != null) {
+            Rect r = getOverviewModeButtonBarRect();
+            lp = (FrameLayout.LayoutParams) overviewMode.getLayoutParams();
+            lp.gravity = Gravity.CENTER_HORIZONTAL | Gravity.BOTTOM;
+            lp.width = Math.min(availableWidthPx, overviewModeMaxBarWidthPx);
+            lp.height = r.height();
+            overviewMode.setLayoutParams(lp);
+        }
+    }
+}
diff --git a/src/com/android/launcher3/DragController.java b/src/com/android/launcher3/DragController.java
index ab48233..1bfaa23 100644
--- a/src/com/android/launcher3/DragController.java
+++ b/src/com/android/launcher3/DragController.java
@@ -198,7 +198,7 @@
      * @param dragRegion Coordinates within the bitmap b for the position of item being dragged.
      *          Makes dragging feel more precise, e.g. you can clip out a transparent border
      */
-    public void startDrag(Bitmap b, int dragLayerX, int dragLayerY,
+    public DragView startDrag(Bitmap b, int dragLayerX, int dragLayerY,
             DragSource source, Object dragInfo, int dragAction, Point dragOffset, Rect dragRegion,
             float initialDragViewScale) {
         if (PROFILE_DRAWING_DURING_DRAG) {
@@ -245,6 +245,7 @@
         mLauncher.getDragLayer().performHapticFeedback(HapticFeedbackConstants.LONG_PRESS);
         dragView.show(mMotionDownX, mMotionDownY);
         handleMoveEvent(mMotionDownX, mMotionDownY);
+        return dragView;
     }
 
     /**
diff --git a/src/com/android/launcher3/DragLayer.java b/src/com/android/launcher3/DragLayer.java
index 159d7d9..dc0ba90 100644
--- a/src/com/android/launcher3/DragLayer.java
+++ b/src/com/android/launcher3/DragLayer.java
@@ -522,14 +522,18 @@
         scale *= childScale;
         int toX = coord[0];
         int toY = coord[1];
+        float toScale = scale;
         if (child instanceof TextView) {
             TextView tv = (TextView) child;
+            // Account for the source scale of the icon (ie. from AllApps to Workspace, in which
+            // the workspace may have smaller icon bounds).
+            toScale = scale / dragView.getIntrinsicIconScaleFactor();
 
             // The child may be scaled (always about the center of the view) so to account for it,
             // we have to offset the position by the scaled size.  Once we do that, we can center
             // the drag view about the scaled child view.
-            toY += Math.round(scale * tv.getPaddingTop());
-            toY -= dragView.getMeasuredHeight() * (1 - scale) / 2;
+            toY += Math.round(toScale * tv.getPaddingTop());
+            toY -= dragView.getMeasuredHeight() * (1 - toScale) / 2;
             toX -= (dragView.getMeasuredWidth() - Math.round(scale * child.getMeasuredWidth())) / 2;
         } else if (child instanceof FolderIcon) {
             // Account for holographic blur padding on the drag view
@@ -555,7 +559,7 @@
                 }
             }
         };
-        animateViewIntoPosition(dragView, fromX, fromY, toX, toY, 1, 1, 1, scale, scale,
+        animateViewIntoPosition(dragView, fromX, fromY, toX, toY, 1, 1, 1, toScale, toScale,
                 onCompleteRunnable, ANIMATION_END_DISAPPEAR, duration, anchorView);
     }
 
diff --git a/src/com/android/launcher3/DragSource.java b/src/com/android/launcher3/DragSource.java
index 2ef99ae..cca9ab1 100644
--- a/src/com/android/launcher3/DragSource.java
+++ b/src/com/android/launcher3/DragSource.java
@@ -30,6 +30,11 @@
      */
     boolean supportsFlingToDelete();
 
+    /*
+     * @return the scale of the icons over the workspace icon size
+     */
+    float getIntrinsicIconScaleFactor();
+
     /**
      * A callback specifically made back to the source after an item from this source has been flung
      * to be deleted on a DropTarget.  In such a situation, this method will be called after
diff --git a/src/com/android/launcher3/DragView.java b/src/com/android/launcher3/DragView.java
index 686cf62..b66b55c 100644
--- a/src/com/android/launcher3/DragView.java
+++ b/src/com/android/launcher3/DragView.java
@@ -51,6 +51,9 @@
     private float mOffsetX = 0.0f;
     private float mOffsetY = 0.0f;
     private float mInitialScale = 1f;
+    // The intrinsic icon scale factor is the scale factor for a drag icon over the workspace
+    // size.  This is ignored for non-icons.
+    private float mIntrinsicIconScale = 1f;
 
     /**
      * Construct the drag view.
@@ -120,6 +123,15 @@
         mPaint = new Paint(Paint.FILTER_BITMAP_FLAG);
     }
 
+    /** Sets the scale of the view over the normal workspace icon size. */
+    public void setIntrinsicIconScaleFactor(float scale) {
+        mIntrinsicIconScale = scale;
+    }
+
+    public float getIntrinsicIconScaleFactor() {
+        return mIntrinsicIconScale;
+    }
+
     public float getOffsetY() {
         return mOffsetY;
     }
diff --git a/src/com/android/launcher3/DynamicGrid.java b/src/com/android/launcher3/DynamicGrid.java
index 5f8c011..42aa20d 100644
--- a/src/com/android/launcher3/DynamicGrid.java
+++ b/src/com/android/launcher3/DynamicGrid.java
@@ -16,674 +16,14 @@
 
 package com.android.launcher3;
 
-import android.appwidget.AppWidgetHostView;
-import android.content.ComponentName;
 import android.content.Context;
-import android.content.res.Configuration;
 import android.content.res.Resources;
-import android.graphics.Paint;
-import android.graphics.Paint.FontMetrics;
-import android.graphics.Point;
-import android.graphics.PointF;
-import android.graphics.Rect;
 import android.util.DisplayMetrics;
 import android.util.TypedValue;
-import android.view.Display;
-import android.view.Gravity;
-import android.view.Surface;
-import android.view.View;
-import android.view.ViewGroup.LayoutParams;
-import android.view.WindowManager;
-import android.widget.FrameLayout;
 
 import java.util.ArrayList;
-import java.util.Collections;
-import java.util.Comparator;
 
 
-class DeviceProfileQuery {
-    float widthDps;
-    float heightDps;
-    float value;
-    PointF dimens;
-
-    DeviceProfileQuery(float w, float h, float v) {
-        widthDps = w;
-        heightDps = h;
-        value = v;
-        dimens = new PointF(w, h);
-    }
-}
-
-class DeviceProfile {
-    public static interface DeviceProfileCallbacks {
-        public void onAvailableSizeChanged(DeviceProfile grid);
-    }
-
-    String name;
-    float minWidthDps;
-    float minHeightDps;
-    float numRows;
-    float numColumns;
-    float numHotseatIcons;
-    private float iconSize;
-    private float iconTextSize;
-    private int iconDrawablePaddingOriginalPx;
-    private float hotseatIconSize;
-
-    boolean isLandscape;
-    boolean isTablet;
-    boolean isLargeTablet;
-    boolean transposeLayoutWithOrientation;
-
-    int desiredWorkspaceLeftRightMarginPx;
-    int edgeMarginPx;
-    Rect defaultWidgetPadding;
-
-    int widthPx;
-    int heightPx;
-    int availableWidthPx;
-    int availableHeightPx;
-    int defaultPageSpacingPx;
-
-    int overviewModeMinIconZoneHeightPx;
-    int overviewModeMaxIconZoneHeightPx;
-    int overviewModeMaxBarWidthPx;
-    float overviewModeIconZoneRatio;
-    float overviewModeScaleFactor;
-
-    int iconSizePx;
-    int iconTextSizePx;
-    int iconDrawablePaddingPx;
-    int cellWidthPx;
-    int cellHeightPx;
-    int allAppsIconSizePx;
-    int allAppsIconTextSizePx;
-    int allAppsCellWidthPx;
-    int allAppsCellHeightPx;
-    int allAppsCellPaddingPx;
-    int folderBackgroundOffset;
-    int folderIconSizePx;
-    int folderCellWidthPx;
-    int folderCellHeightPx;
-    int hotseatCellWidthPx;
-    int hotseatCellHeightPx;
-    int hotseatIconSizePx;
-    int hotseatBarHeightPx;
-    int hotseatAllAppsRank;
-    int allAppsNumRows;
-    int allAppsNumCols;
-    int searchBarSpaceWidthPx;
-    int searchBarSpaceMaxWidthPx;
-    int searchBarSpaceHeightPx;
-    int searchBarHeightPx;
-    int pageIndicatorHeightPx;
-
-    private ArrayList<DeviceProfileCallbacks> mCallbacks = new ArrayList<DeviceProfileCallbacks>();
-
-    DeviceProfile(String n, float w, float h, float r, float c,
-                  float is, float its, float hs, float his) {
-        // Ensure that we have an odd number of hotseat items (since we need to place all apps)
-        if (!AppsCustomizePagedView.DISABLE_ALL_APPS && hs % 2 == 0) {
-            throw new RuntimeException("All Device Profiles must have an odd number of hotseat spaces");
-        }
-
-        name = n;
-        minWidthDps = w;
-        minHeightDps = h;
-        numRows = r;
-        numColumns = c;
-        iconSize = is;
-        iconTextSize = its;
-        numHotseatIcons = hs;
-        hotseatIconSize = his;
-    }
-
-    DeviceProfile(Context context,
-                  ArrayList<DeviceProfile> profiles,
-                  float minWidth, float minHeight,
-                  int wPx, int hPx,
-                  int awPx, int ahPx,
-                  Resources res) {
-        DisplayMetrics dm = res.getDisplayMetrics();
-        ArrayList<DeviceProfileQuery> points =
-                new ArrayList<DeviceProfileQuery>();
-        transposeLayoutWithOrientation =
-                res.getBoolean(R.bool.hotseat_transpose_layout_with_orientation);
-        minWidthDps = minWidth;
-        minHeightDps = minHeight;
-
-        ComponentName cn = new ComponentName(context.getPackageName(),
-                this.getClass().getName());
-        defaultWidgetPadding = AppWidgetHostView.getDefaultPaddingForWidget(context, cn, null);
-        edgeMarginPx = res.getDimensionPixelSize(R.dimen.dynamic_grid_edge_margin);
-        desiredWorkspaceLeftRightMarginPx = 2 * edgeMarginPx;
-        pageIndicatorHeightPx =
-                res.getDimensionPixelSize(R.dimen.dynamic_grid_page_indicator_height);
-        defaultPageSpacingPx =
-                res.getDimensionPixelSize(R.dimen.dynamic_grid_workspace_page_spacing);
-        allAppsCellPaddingPx =
-                res.getDimensionPixelSize(R.dimen.dynamic_grid_all_apps_cell_padding);
-        overviewModeMinIconZoneHeightPx =
-                res.getDimensionPixelSize(R.dimen.dynamic_grid_overview_min_icon_zone_height);
-        overviewModeMaxIconZoneHeightPx =
-                res.getDimensionPixelSize(R.dimen.dynamic_grid_overview_max_icon_zone_height);
-        overviewModeMaxBarWidthPx =
-                res.getDimensionPixelSize(R.dimen.dynamic_grid_overview_bar_max_width);
-        overviewModeIconZoneRatio =
-                res.getInteger(R.integer.config_dynamic_grid_overview_icon_zone_percentage) / 100f;
-        overviewModeScaleFactor =
-                res.getInteger(R.integer.config_dynamic_grid_overview_scale_percentage) / 100f;
-
-        // Interpolate the rows
-        for (DeviceProfile p : profiles) {
-            points.add(new DeviceProfileQuery(p.minWidthDps, p.minHeightDps, p.numRows));
-        }
-        numRows = Math.round(invDistWeightedInterpolate(minWidth, minHeight, points));
-        // Interpolate the columns
-        points.clear();
-        for (DeviceProfile p : profiles) {
-            points.add(new DeviceProfileQuery(p.minWidthDps, p.minHeightDps, p.numColumns));
-        }
-        numColumns = Math.round(invDistWeightedInterpolate(minWidth, minHeight, points));
-        // Interpolate the hotseat length
-        points.clear();
-        for (DeviceProfile p : profiles) {
-            points.add(new DeviceProfileQuery(p.minWidthDps, p.minHeightDps, p.numHotseatIcons));
-        }
-        numHotseatIcons = Math.round(invDistWeightedInterpolate(minWidth, minHeight, points));
-        hotseatAllAppsRank = (int) (numHotseatIcons / 2);
-
-        // Interpolate the icon size
-        points.clear();
-        for (DeviceProfile p : profiles) {
-            points.add(new DeviceProfileQuery(p.minWidthDps, p.minHeightDps, p.iconSize));
-        }
-        iconSize = invDistWeightedInterpolate(minWidth, minHeight, points);
-        // AllApps uses the original non-scaled icon size
-        allAppsIconSizePx = DynamicGrid.pxFromDp(iconSize, dm);
-
-        // Interpolate the icon text size
-        points.clear();
-        for (DeviceProfile p : profiles) {
-            points.add(new DeviceProfileQuery(p.minWidthDps, p.minHeightDps, p.iconTextSize));
-        }
-        iconTextSize = invDistWeightedInterpolate(minWidth, minHeight, points);
-        iconDrawablePaddingOriginalPx =
-                res.getDimensionPixelSize(R.dimen.dynamic_grid_icon_drawable_padding);
-        // AllApps uses the original non-scaled icon text size
-        allAppsIconTextSizePx = DynamicGrid.pxFromDp(iconTextSize, dm);
-
-        // Interpolate the hotseat icon size
-        points.clear();
-        for (DeviceProfile p : profiles) {
-            points.add(new DeviceProfileQuery(p.minWidthDps, p.minHeightDps, p.hotseatIconSize));
-        }
-        // Hotseat
-        hotseatIconSize = invDistWeightedInterpolate(minWidth, minHeight, points);
-
-        // Calculate the remaining vars
-        updateFromConfiguration(context, res, wPx, hPx, awPx, ahPx);
-        updateAvailableDimensions(context);
-    }
-
-    void addCallback(DeviceProfileCallbacks cb) {
-        mCallbacks.add(cb);
-        cb.onAvailableSizeChanged(this);
-    }
-    void removeCallback(DeviceProfileCallbacks cb) {
-        mCallbacks.remove(cb);
-    }
-
-    private int getDeviceOrientation(Context context) {
-        WindowManager windowManager =  (WindowManager)
-                context.getSystemService(Context.WINDOW_SERVICE);
-        Resources resources = context.getResources();
-        DisplayMetrics dm = resources.getDisplayMetrics();
-        Configuration config = resources.getConfiguration();
-        int rotation = windowManager.getDefaultDisplay().getRotation();
-
-        boolean isLandscape = (config.orientation == Configuration.ORIENTATION_LANDSCAPE) &&
-                (rotation == Surface.ROTATION_0 || rotation == Surface.ROTATION_180);
-        boolean isRotatedPortrait = (config.orientation == Configuration.ORIENTATION_PORTRAIT) &&
-                (rotation == Surface.ROTATION_90 || rotation == Surface.ROTATION_270);
-        if (isLandscape || isRotatedPortrait) {
-            return CellLayout.LANDSCAPE;
-        } else {
-            return CellLayout.PORTRAIT;
-        }
-    }
-
-    private void updateAvailableDimensions(Context context) {
-        WindowManager windowManager =  (WindowManager)
-                context.getSystemService(Context.WINDOW_SERVICE);
-        Display display = windowManager.getDefaultDisplay();
-        Resources resources = context.getResources();
-        DisplayMetrics dm = resources.getDisplayMetrics();
-        Configuration config = resources.getConfiguration();
-
-        // There are three possible configurations that the dynamic grid accounts for, portrait,
-        // landscape with the nav bar at the bottom, and landscape with the nav bar at the side.
-        // To prevent waiting for fitSystemWindows(), we make the observation that in landscape,
-        // the height is the smallest height (either with the nav bar at the bottom or to the
-        // side) and otherwise, the height is simply the largest possible height for a portrait
-        // device.
-        Point size = new Point();
-        Point smallestSize = new Point();
-        Point largestSize = new Point();
-        display.getSize(size);
-        display.getCurrentSizeRange(smallestSize, largestSize);
-        availableWidthPx = size.x;
-        if (config.orientation == Configuration.ORIENTATION_LANDSCAPE) {
-            availableHeightPx = smallestSize.y;
-        } else {
-            availableHeightPx = largestSize.y;
-        }
-
-        // Check to see if the icons fit in the new available height.  If not, then we need to
-        // shrink the icon size.
-        Rect workspacePadding = getWorkspacePadding();
-        float scale = 1f;
-        int drawablePadding = iconDrawablePaddingOriginalPx;
-        updateIconSize(1f, drawablePadding, resources, dm);
-        float usedHeight = (cellHeightPx * numRows);
-        int maxHeight = (availableHeightPx - workspacePadding.top - workspacePadding.bottom);
-        if (usedHeight > maxHeight) {
-            scale = maxHeight / usedHeight;
-            drawablePadding = 0;
-        }
-        updateIconSize(scale, drawablePadding, resources, dm);
-
-        // Make the callbacks
-        for (DeviceProfileCallbacks cb : mCallbacks) {
-            cb.onAvailableSizeChanged(this);
-        }
-    }
-
-    private void updateIconSize(float scale, int drawablePadding, Resources resources,
-                                DisplayMetrics dm) {
-        iconSizePx = (int) (DynamicGrid.pxFromDp(iconSize, dm) * scale);
-        iconTextSizePx = (int) (DynamicGrid.pxFromSp(iconTextSize, dm) * scale);
-        iconDrawablePaddingPx = drawablePadding;
-        hotseatIconSizePx = (int) (DynamicGrid.pxFromDp(hotseatIconSize, dm) * scale);
-
-        // Search Bar
-        searchBarSpaceMaxWidthPx = resources.getDimensionPixelSize(R.dimen.dynamic_grid_search_bar_max_width);
-        searchBarHeightPx = resources.getDimensionPixelSize(R.dimen.dynamic_grid_search_bar_height);
-        searchBarSpaceWidthPx = Math.min(searchBarSpaceMaxWidthPx, widthPx);
-        searchBarSpaceHeightPx = searchBarHeightPx + 2 * edgeMarginPx;
-
-        // Calculate the actual text height
-        Paint textPaint = new Paint();
-        textPaint.setTextSize(iconTextSizePx);
-        FontMetrics fm = textPaint.getFontMetrics();
-        cellWidthPx = iconSizePx;
-        cellHeightPx = iconSizePx + iconDrawablePaddingPx + (int) Math.ceil(fm.bottom - fm.top);
-
-        // Hotseat
-        hotseatBarHeightPx = iconSizePx + 4 * edgeMarginPx;
-        hotseatCellWidthPx = iconSizePx;
-        hotseatCellHeightPx = iconSizePx;
-
-        // Folder
-        folderCellWidthPx = cellWidthPx + 3 * edgeMarginPx;
-        folderCellHeightPx = cellHeightPx + edgeMarginPx;
-        folderBackgroundOffset = -edgeMarginPx;
-        folderIconSizePx = iconSizePx + 2 * -folderBackgroundOffset;
-
-        // All Apps
-        Rect padding = getWorkspacePadding(isLandscape ?
-                CellLayout.LANDSCAPE : CellLayout.PORTRAIT);
-        int pageIndicatorOffset =
-                resources.getDimensionPixelSize(R.dimen.apps_customize_page_indicator_offset);
-        allAppsCellWidthPx = allAppsIconSizePx;
-        allAppsCellHeightPx = allAppsIconSizePx + drawablePadding + iconTextSizePx;
-        int maxLongEdgeCellCount =
-                resources.getInteger(R.integer.config_dynamic_grid_max_long_edge_cell_count);
-        int maxShortEdgeCellCount =
-                resources.getInteger(R.integer.config_dynamic_grid_max_short_edge_cell_count);
-        int minEdgeCellCount =
-                resources.getInteger(R.integer.config_dynamic_grid_min_edge_cell_count);
-        int maxRows = (isLandscape ? maxShortEdgeCellCount : maxLongEdgeCellCount);
-        int maxCols = (isLandscape ? maxLongEdgeCellCount : maxShortEdgeCellCount);
-
-        allAppsNumRows = (availableHeightPx - pageIndicatorHeightPx) /
-                (allAppsCellHeightPx + allAppsCellPaddingPx);
-        allAppsNumRows = Math.max(minEdgeCellCount, Math.min(maxRows, allAppsNumRows));
-        allAppsNumCols = (availableWidthPx) /
-                (allAppsCellWidthPx + allAppsCellPaddingPx);
-        allAppsNumCols = Math.max(minEdgeCellCount, Math.min(maxCols, allAppsNumCols));
-    }
-
-    void updateFromConfiguration(Context context, Resources resources, int wPx, int hPx,
-                                 int awPx, int ahPx) {
-        isLandscape = (resources.getConfiguration().orientation ==
-                Configuration.ORIENTATION_LANDSCAPE);
-        isTablet = resources.getBoolean(R.bool.is_tablet);
-        isLargeTablet = resources.getBoolean(R.bool.is_large_tablet);
-        widthPx = wPx;
-        heightPx = hPx;
-        availableWidthPx = awPx;
-        availableHeightPx = ahPx;
-
-        updateAvailableDimensions(context);
-    }
-
-    private float dist(PointF p0, PointF p1) {
-        return (float) Math.sqrt((p1.x - p0.x)*(p1.x-p0.x) +
-                (p1.y-p0.y)*(p1.y-p0.y));
-    }
-
-    private float weight(PointF a, PointF b,
-                        float pow) {
-        float d = dist(a, b);
-        if (d == 0f) {
-            return Float.POSITIVE_INFINITY;
-        }
-        return (float) (1f / Math.pow(d, pow));
-    }
-
-    private float invDistWeightedInterpolate(float width, float height,
-                ArrayList<DeviceProfileQuery> points) {
-        float sum = 0;
-        float weights = 0;
-        float pow = 5;
-        float kNearestNeighbors = 3;
-        final PointF xy = new PointF(width, height);
-
-        ArrayList<DeviceProfileQuery> pointsByNearness = points;
-        Collections.sort(pointsByNearness, new Comparator<DeviceProfileQuery>() {
-            public int compare(DeviceProfileQuery a, DeviceProfileQuery b) {
-                return (int) (dist(xy, a.dimens) - dist(xy, b.dimens));
-            }
-        });
-
-        for (int i = 0; i < pointsByNearness.size(); ++i) {
-            DeviceProfileQuery p = pointsByNearness.get(i);
-            if (i < kNearestNeighbors) {
-                float w = weight(xy, p.dimens, pow);
-                if (w == Float.POSITIVE_INFINITY) {
-                    return p.value;
-                }
-                weights += w;
-            }
-        }
-
-        for (int i = 0; i < pointsByNearness.size(); ++i) {
-            DeviceProfileQuery p = pointsByNearness.get(i);
-            if (i < kNearestNeighbors) {
-                float w = weight(xy, p.dimens, pow);
-                sum += w * p.value / weights;
-            }
-        }
-
-        return sum;
-    }
-
-    Rect getWorkspacePadding() {
-        return getWorkspacePadding(isLandscape ? CellLayout.LANDSCAPE : CellLayout.PORTRAIT);
-    }
-
-    Rect getWorkspacePadding(int orientation) {
-        Rect padding = new Rect();
-        if (orientation == CellLayout.LANDSCAPE &&
-                transposeLayoutWithOrientation) {
-            // Pad the left and right of the workspace with search/hotseat bar sizes
-            padding.set(searchBarSpaceHeightPx, edgeMarginPx,
-                    hotseatBarHeightPx, edgeMarginPx);
-        } else {
-            if (isTablet()) {
-                // Pad the left and right of the workspace to ensure consistent spacing
-                // between all icons
-                int width = (orientation == CellLayout.LANDSCAPE)
-                        ? Math.max(widthPx, heightPx)
-                        : Math.min(widthPx, heightPx);
-                // XXX: If the icon size changes across orientations, we will have to take
-                //      that into account here too.
-                int gap = (int) ((width - 2 * edgeMarginPx -
-                        (numColumns * cellWidthPx)) / (2 * (numColumns + 1)));
-                padding.set(edgeMarginPx + gap,
-                        searchBarSpaceHeightPx,
-                        edgeMarginPx + gap,
-                        hotseatBarHeightPx + pageIndicatorHeightPx);
-            } else {
-                // Pad the top and bottom of the workspace with search/hotseat bar sizes
-                padding.set(desiredWorkspaceLeftRightMarginPx - defaultWidgetPadding.left,
-                        searchBarSpaceHeightPx,
-                        desiredWorkspaceLeftRightMarginPx - defaultWidgetPadding.right,
-                        hotseatBarHeightPx + pageIndicatorHeightPx);
-            }
-        }
-        return padding;
-    }
-
-    int getWorkspacePageSpacing(int orientation) {
-        if (orientation == CellLayout.LANDSCAPE &&
-                transposeLayoutWithOrientation) {
-            // In landscape mode the page spacing is set to the default.
-            return defaultPageSpacingPx;
-        } else {
-            // In portrait, we want the pages spaced such that there is no
-            // overhang of the previous / next page into the current page viewport.
-            // We assume symmetrical padding in portrait mode.
-            return 2 * getWorkspacePadding().left;
-        }
-    }
-
-    Rect getOverviewModeButtonBarRect() {
-        int zoneHeight = (int) (overviewModeIconZoneRatio * availableHeightPx);
-        zoneHeight = Math.min(overviewModeMaxIconZoneHeightPx,
-                Math.max(overviewModeMinIconZoneHeightPx, zoneHeight));
-        return new Rect(0, availableHeightPx - zoneHeight, 0, availableHeightPx);
-    }
-
-    float getOverviewModeScale() {
-        Rect workspacePadding = getWorkspacePadding();
-        Rect overviewBar = getOverviewModeButtonBarRect();
-        int pageSpace = availableHeightPx - workspacePadding.top - workspacePadding.bottom;
-        return (overviewModeScaleFactor * (pageSpace - overviewBar.height())) / pageSpace;
-    }
-
-    // The rect returned will be extended to below the system ui that covers the workspace
-    Rect getHotseatRect() {
-        if (isVerticalBarLayout()) {
-            return new Rect(availableWidthPx - hotseatBarHeightPx, 0,
-                    Integer.MAX_VALUE, availableHeightPx);
-        } else {
-            return new Rect(0, availableHeightPx - hotseatBarHeightPx,
-                    availableWidthPx, Integer.MAX_VALUE);
-        }
-    }
-
-    int calculateCellWidth(int width, int countX) {
-        return width / countX;
-    }
-    int calculateCellHeight(int height, int countY) {
-        return height / countY;
-    }
-
-    boolean isPhone() {
-        return !isTablet && !isLargeTablet;
-    }
-    boolean isTablet() {
-        return isTablet;
-    }
-    boolean isLargeTablet() {
-        return isLargeTablet;
-    }
-
-    boolean isVerticalBarLayout() {
-        return isLandscape && transposeLayoutWithOrientation;
-    }
-
-    boolean shouldFadeAdjacentWorkspaceScreens() {
-        return isVerticalBarLayout() || isLargeTablet();
-    }
-
-    public void layout(Launcher launcher) {
-        FrameLayout.LayoutParams lp;
-        Resources res = launcher.getResources();
-        boolean hasVerticalBarLayout = isVerticalBarLayout();
-
-        // Layout the search bar space
-        View searchBar = launcher.getSearchBar();
-        lp = (FrameLayout.LayoutParams) searchBar.getLayoutParams();
-        if (hasVerticalBarLayout) {
-            // Vertical search bar
-            lp.gravity = Gravity.TOP | Gravity.LEFT;
-            lp.width = searchBarSpaceHeightPx;
-            lp.height = LayoutParams.MATCH_PARENT;
-            searchBar.setPadding(
-                    0, 2 * edgeMarginPx, 0,
-                    2 * edgeMarginPx);
-        } else {
-            // Horizontal search bar
-            lp.gravity = Gravity.TOP | Gravity.CENTER_HORIZONTAL;
-            lp.width = searchBarSpaceWidthPx;
-            lp.height = searchBarSpaceHeightPx;
-            searchBar.setPadding(
-                    2 * edgeMarginPx,
-                    2 * edgeMarginPx,
-                    2 * edgeMarginPx, 0);
-        }
-        searchBar.setLayoutParams(lp);
-
-        // Layout the search bar
-        View qsbBar = launcher.getQsbBar();
-        LayoutParams vglp = qsbBar.getLayoutParams();
-        vglp.width = LayoutParams.MATCH_PARENT;
-        vglp.height = LayoutParams.MATCH_PARENT;
-        qsbBar.setLayoutParams(vglp);
-
-        // Layout the voice proxy
-        View voiceButtonProxy = launcher.findViewById(R.id.voice_button_proxy);
-        if (voiceButtonProxy != null) {
-            if (hasVerticalBarLayout) {
-                // TODO: MOVE THIS INTO SEARCH BAR MEASURE
-            } else {
-                lp = (FrameLayout.LayoutParams) voiceButtonProxy.getLayoutParams();
-                lp.gravity = Gravity.TOP | Gravity.END;
-                lp.width = (widthPx - searchBarSpaceWidthPx) / 2 +
-                        2 * iconSizePx;
-                lp.height = searchBarSpaceHeightPx;
-            }
-        }
-
-        // Layout the workspace
-        PagedView workspace = (PagedView) launcher.findViewById(R.id.workspace);
-        lp = (FrameLayout.LayoutParams) workspace.getLayoutParams();
-        lp.gravity = Gravity.CENTER;
-        int orientation = isLandscape ? CellLayout.LANDSCAPE : CellLayout.PORTRAIT;
-        Rect padding = getWorkspacePadding(orientation);
-        workspace.setLayoutParams(lp);
-        workspace.setPadding(padding.left, padding.top, padding.right, padding.bottom);
-        workspace.setPageSpacing(getWorkspacePageSpacing(orientation));
-
-        // Layout the hotseat
-        View hotseat = launcher.findViewById(R.id.hotseat);
-        lp = (FrameLayout.LayoutParams) hotseat.getLayoutParams();
-        if (hasVerticalBarLayout) {
-            // Vertical hotseat
-            lp.gravity = Gravity.RIGHT;
-            lp.width = hotseatBarHeightPx;
-            lp.height = LayoutParams.MATCH_PARENT;
-            hotseat.findViewById(R.id.layout).setPadding(0, 2 * edgeMarginPx, 0, 2 * edgeMarginPx);
-        } else if (isTablet()) {
-            // Pad the hotseat with the grid gap calculated above
-            int gridGap = (int) ((widthPx - 2 * edgeMarginPx -
-                    (numColumns * cellWidthPx)) / (2 * (numColumns + 1)));
-            int gridWidth = (int) ((numColumns * cellWidthPx) +
-                    ((numColumns - 1) * gridGap));
-            int hotseatGap = (int) Math.max(0,
-                    (gridWidth - (numHotseatIcons * hotseatCellWidthPx))
-                            / (numHotseatIcons - 1));
-            lp.gravity = Gravity.BOTTOM;
-            lp.width = LayoutParams.MATCH_PARENT;
-            lp.height = hotseatBarHeightPx;
-            hotseat.setPadding(2 * edgeMarginPx + gridGap + hotseatGap, 0,
-                    2 * edgeMarginPx + gridGap + hotseatGap,
-                    2 * edgeMarginPx);
-        } else {
-            // For phones, layout the hotseat without any bottom margin
-            // to ensure that we have space for the folders
-            lp.gravity = Gravity.BOTTOM;
-            lp.width = LayoutParams.MATCH_PARENT;
-            lp.height = hotseatBarHeightPx;
-            hotseat.findViewById(R.id.layout).setPadding(2 * edgeMarginPx, 0,
-                    2 * edgeMarginPx, 0);
-        }
-        hotseat.setLayoutParams(lp);
-
-        // Layout the page indicators
-        View pageIndicator = launcher.findViewById(R.id.page_indicator);
-        if (pageIndicator != null) {
-            if (hasVerticalBarLayout) {
-                // Hide the page indicators when we have vertical search/hotseat
-                pageIndicator.setVisibility(View.GONE);
-            } else {
-                // Put the page indicators above the hotseat
-                lp = (FrameLayout.LayoutParams) pageIndicator.getLayoutParams();
-                lp.gravity = Gravity.CENTER_HORIZONTAL | Gravity.BOTTOM;
-                lp.width = LayoutParams.WRAP_CONTENT;
-                lp.height = LayoutParams.WRAP_CONTENT;
-                lp.bottomMargin = hotseatBarHeightPx;
-                pageIndicator.setLayoutParams(lp);
-            }
-        }
-
-        // Layout AllApps
-        AppsCustomizeTabHost host = (AppsCustomizeTabHost)
-                launcher.findViewById(R.id.apps_customize_pane);
-        if (host != null) {
-            // Center the all apps page indicator
-            int pageIndicatorHeight = (int) (pageIndicatorHeightPx * Math.min(1f,
-                    (allAppsIconSizePx / DynamicGrid.DEFAULT_ICON_SIZE_PX)));
-            pageIndicator = host.findViewById(R.id.apps_customize_page_indicator);
-            if (pageIndicator != null) {
-                lp = (FrameLayout.LayoutParams) pageIndicator.getLayoutParams();
-                lp.gravity = Gravity.CENTER_HORIZONTAL | Gravity.BOTTOM;
-                lp.width = LayoutParams.WRAP_CONTENT;
-                lp.height = pageIndicatorHeight;
-                pageIndicator.setLayoutParams(lp);
-            }
-
-            AppsCustomizePagedView pagedView = (AppsCustomizePagedView)
-                    host.findViewById(R.id.apps_customize_pane_content);
-            padding = new Rect();
-            if (pagedView != null) {
-                // Constrain the dimensions of all apps so that it does not span the full width
-                int paddingLR = (availableWidthPx - (allAppsCellWidthPx * allAppsNumCols)) /
-                        (2 * (allAppsNumCols + 1));
-                int paddingTB = (availableHeightPx - (allAppsCellHeightPx * allAppsNumRows)) /
-                        (2 * (allAppsNumRows + 1));
-                paddingLR = Math.min(paddingLR, (int)((paddingLR + paddingTB) * 0.75f));
-                paddingTB = Math.min(paddingTB, (int)((paddingLR + paddingTB) * 0.75f));
-                int maxAllAppsWidth = (allAppsNumCols * (allAppsCellWidthPx + 2 * paddingLR));
-                int gridPaddingLR = (availableWidthPx - maxAllAppsWidth) / 2;
-                if (gridPaddingLR > (allAppsCellWidthPx / 4)) {
-                    padding.left = padding.right = gridPaddingLR;
-                }
-                // The icons are centered, so we can't just offset by the page indicator height
-                // because the empty space will actually be pageIndicatorHeight + paddingTB
-                padding.bottom = Math.max(0, pageIndicatorHeight - paddingTB);
-                pagedView.setAllAppsPadding(padding);
-                pagedView.setWidgetsPageIndicatorPadding(pageIndicatorHeight);
-            }
-        }
-
-        // Layout the Overview Mode
-        View overviewMode = launcher.getOverviewPanel();
-        if (overviewMode != null) {
-            Rect r = getOverviewModeButtonBarRect();
-            lp = (FrameLayout.LayoutParams) overviewMode.getLayoutParams();
-            lp.gravity = Gravity.CENTER_HORIZONTAL | Gravity.BOTTOM;
-            lp.width = Math.min(availableWidthPx, overviewModeMaxBarWidthPx);
-            lp.height = r.height();
-            overviewMode.setLayoutParams(lp);
-        }
-    }
-}
-
 public class DynamicGrid {
     @SuppressWarnings("unused")
     private static final String TAG = "DynamicGrid";
@@ -755,7 +95,7 @@
                 resources);
     }
 
-    DeviceProfile getDeviceProfile() {
+    public DeviceProfile getDeviceProfile() {
         return mProfile;
     }
 
diff --git a/src/com/android/launcher3/FastBitmapDrawable.java b/src/com/android/launcher3/FastBitmapDrawable.java
index bce6707..83be143 100644
--- a/src/com/android/launcher3/FastBitmapDrawable.java
+++ b/src/com/android/launcher3/FastBitmapDrawable.java
@@ -27,19 +27,11 @@
 class FastBitmapDrawable extends Drawable {
     private Bitmap mBitmap;
     private int mAlpha;
-    private int mWidth;
-    private int mHeight;
     private final Paint mPaint = new Paint(Paint.FILTER_BITMAP_FLAG);
 
     FastBitmapDrawable(Bitmap b) {
-	mAlpha = 255;
+        mAlpha = 255;
         mBitmap = b;
-        if (b != null) {
-            mWidth = mBitmap.getWidth();
-            mHeight = mBitmap.getHeight();
-        } else {
-            mWidth = mHeight = 0;
-        }
     }
 
     @Override
@@ -76,32 +68,22 @@
 
     @Override
     public int getIntrinsicWidth() {
-        return mWidth;
+        return getBounds().width();
     }
 
     @Override
     public int getIntrinsicHeight() {
-        return mHeight;
+        return getBounds().height();
     }
 
     @Override
     public int getMinimumWidth() {
-        return mWidth;
+        return getBounds().width();
     }
 
     @Override
     public int getMinimumHeight() {
-        return mHeight;
-    }
-
-    public void setBitmap(Bitmap b) {
-        mBitmap = b;
-        if (b != null) {
-            mWidth = mBitmap.getWidth();
-            mHeight = mBitmap.getHeight();
-        } else {
-            mWidth = mHeight = 0;
-        }
+        return getBounds().height();
     }
 
     public Bitmap getBitmap() {
diff --git a/src/com/android/launcher3/Folder.java b/src/com/android/launcher3/Folder.java
index f26d01f..1d234ff 100644
--- a/src/com/android/launcher3/Folder.java
+++ b/src/com/android/launcher3/Folder.java
@@ -119,6 +119,11 @@
     private int DRAG_MODE_REORDER = 1;
     private int mDragMode = DRAG_MODE_NONE;
 
+    // We avoid measuring the scroll view with a 0 width or height, as this
+    // results in CellLayout being measured as UNSPECIFIED, which it does
+    // not support.
+    private static final int MIN_CONTENT_DIMEN = 5;
+
     private boolean mDestroyed;
 
     private AutoScrollHelper mAutoScrollHelper;
@@ -787,6 +792,11 @@
     }
 
     @Override
+    public float getIntrinsicIconScaleFactor() {
+        return 1f;
+    }
+
+    @Override
     public boolean supportsFlingToDelete() {
         return true;
     }
@@ -961,8 +971,13 @@
         int maxContentAreaHeight = grid.availableHeightPx -
                 workspacePadding.top - workspacePadding.bottom -
                 mFolderNameHeight;
-        return Math.min(maxContentAreaHeight,
+        int height = Math.min(maxContentAreaHeight,
                 mContent.getDesiredHeight());
+        return Math.max(height, MIN_CONTENT_DIMEN);
+    }
+
+    private int getContentAreaWidth() {
+        return Math.max(mContent.getDesiredWidth(), MIN_CONTENT_DIMEN);
     }
 
     private int getFolderHeight() {
@@ -974,11 +989,12 @@
     protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
         int width = getPaddingLeft() + getPaddingRight() + mContent.getDesiredWidth();
         int height = getFolderHeight();
-        int contentAreaWidthSpec = MeasureSpec.makeMeasureSpec(mContent.getDesiredWidth(),
+        int contentAreaWidthSpec = MeasureSpec.makeMeasureSpec(getContentAreaWidth(),
                 MeasureSpec.EXACTLY);
         int contentAreaHeightSpec = MeasureSpec.makeMeasureSpec(getContentAreaHeight(),
                 MeasureSpec.EXACTLY);
-        mContent.setFixedSize(mContent.getDesiredWidth(), mContent.getDesiredHeight());
+
+        mContent.setFixedSize(getContentAreaWidth(), getContentAreaHeight());
         mScrollView.measure(contentAreaWidthSpec, contentAreaHeightSpec);
         mFolderName.measure(contentAreaWidthSpec,
                 MeasureSpec.makeMeasureSpec(mFolderNameHeight, MeasureSpec.EXACTLY));
diff --git a/src/com/android/launcher3/InstallShortcutReceiver.java b/src/com/android/launcher3/InstallShortcutReceiver.java
index 835c472..fb75161 100644
--- a/src/com/android/launcher3/InstallShortcutReceiver.java
+++ b/src/com/android/launcher3/InstallShortcutReceiver.java
@@ -294,7 +294,7 @@
             // Add the new apps to the model and bind them
             if (!addShortcuts.isEmpty()) {
                 LauncherAppState app = LauncherAppState.getInstance();
-                app.getModel().addAndBindAddedApps(context, addShortcuts, null);
+                app.getModel().addAndBindAddedApps(context, addShortcuts, new ArrayList<AppInfo>());
             }
         }
     }
diff --git a/src/com/android/launcher3/Launcher.java b/src/com/android/launcher3/Launcher.java
index 3100241..89abd9f 100644
--- a/src/com/android/launcher3/Launcher.java
+++ b/src/com/android/launcher3/Launcher.java
@@ -45,7 +45,6 @@
 import android.content.pm.ActivityInfo;
 import android.content.pm.PackageManager;
 import android.content.pm.PackageManager.NameNotFoundException;
-import android.content.pm.ResolveInfo;
 import android.content.res.Configuration;
 import android.content.res.Resources;
 import android.database.ContentObserver;
@@ -113,6 +112,7 @@
 import java.util.Date;
 import java.util.HashMap;
 import java.util.List;
+import java.util.concurrent.atomic.AtomicInteger;
 
 /**
  * Default launcher application.
@@ -181,8 +181,10 @@
     private static final String RUNTIME_STATE_PENDING_ADD_SPAN_Y = "launcher.add_span_y";
     // Type: parcelable
     private static final String RUNTIME_STATE_PENDING_ADD_WIDGET_INFO = "launcher.add_widget_info";
- // Type: parcelable
+    // Type: parcelable
     private static final String RUNTIME_STATE_PENDING_ADD_WIDGET_ID = "launcher.add_widget_id";
+    // Type: int[]
+    private static final String RUNTIME_STATE_VIEW_IDS = "launcher.view_ids";
 
     private static final String TOOLBAR_ICON_METADATA_NAME = "com.android.launcher.toolbar_icon";
     private static final String TOOLBAR_SEARCH_ICON_METADATA_NAME =
@@ -207,6 +209,9 @@
     private static final Object sLock = new Object();
     private static int sScreen = DEFAULT_SCREEN;
 
+    private HashMap<Integer, Integer> mItemIdToViewId = new HashMap<Integer, Integer>();
+    private static final AtomicInteger sNextGeneratedId = new AtomicInteger(1);
+
     // How long to wait before the new-shortcut animation automatically pans the workspace
     private static int NEW_APPS_PAGE_MOVE_DELAY = 500;
     private static int NEW_APPS_ANIMATION_INACTIVE_TIMEOUT_SECONDS = 5;
@@ -671,6 +676,34 @@
     }
 
     /**
+     * Copied from View -- the View version of the method isn't called
+     * anywhere else in our process and only exists for API level 17+,
+     * so it's ok to keep our own version with no API requirement.
+     */
+    public static int generateViewId() {
+        for (;;) {
+            final int result = sNextGeneratedId.get();
+            // aapt-generated IDs have the high byte nonzero; clamp to the range under that.
+            int newValue = result + 1;
+            if (newValue > 0x00FFFFFF) newValue = 1; // Roll over to 1, not 0.
+            if (sNextGeneratedId.compareAndSet(result, newValue)) {
+                return result;
+            }
+        }
+    }
+
+    public int getViewIdForItem(ItemInfo info) {
+        // This cast is safe given the > 2B range for int.
+        int itemId = (int) info.id;
+        if (mItemIdToViewId.containsKey(itemId)) {
+            return mItemIdToViewId.get(itemId);
+        }
+        int viewId = generateViewId();
+        mItemIdToViewId.put(itemId, viewId);
+        return viewId;
+    }
+
+    /**
      * Returns whether we should delay spring loaded mode -- for shortcuts and widgets that have
      * a configuration step, this allows the proper animations to run after other transitions.
      */
@@ -1108,6 +1141,7 @@
      *
      * @param savedState The previous state.
      */
+    @SuppressWarnings("unchecked")
     private void restoreState(Bundle savedState) {
         if (savedState == null) {
             return;
@@ -1160,6 +1194,8 @@
             int currentIndex = savedState.getInt("apps_customize_currentIndex");
             mAppsCustomizeContent.restorePageForIndex(currentIndex);
         }
+        mItemIdToViewId = (HashMap<Integer, Integer>)
+                savedState.getSerializable(RUNTIME_STATE_VIEW_IDS);
     }
 
     /**
@@ -1704,7 +1740,7 @@
             // In all these cases, only animate if we're already on home
             mWorkspace.exitWidgetResizeMode();
             if (alreadyOnHome && mState == State.WORKSPACE && !mWorkspace.isTouchActive() &&
-                    openFolder == null) {
+                    openFolder == null && shouldMoveToDefaultScreenOnHomeIntent()) {
                 mWorkspace.moveToDefaultScreen(true);
             }
 
@@ -1730,6 +1766,8 @@
             if (mAppsCustomizeTabHost != null) {
                 mAppsCustomizeTabHost.reset();
             }
+
+            onHomeIntent();
         }
 
         if (DEBUG_RESUME_TIME) {
@@ -1737,6 +1775,21 @@
         }
     }
 
+    /**
+     * Override point for subclasses to prevent movement to the default screen when the home
+     * button is pressed. Used (for example) in GEL, to prevent movement during a search.
+     */
+    protected boolean shouldMoveToDefaultScreenOnHomeIntent() {
+        return true;
+    }
+
+    /**
+     * Override point for subclasses to provide custom behaviour for when a home intent is fired.
+     */
+    protected void onHomeIntent() {
+        // Do nothing
+    }
+
     @Override
     public void onRestoreInstanceState(Bundle state) {
         super.onRestoreInstanceState(state);
@@ -1784,6 +1837,7 @@
             int currentIndex = mAppsCustomizeContent.getSaveInstanceStateIndex();
             outState.putInt("apps_customize_currentIndex", currentIndex);
         }
+        outState.putSerializable(RUNTIME_STATE_VIEW_IDS, mItemIdToViewId);
     }
 
     @Override
diff --git a/src/com/android/launcher3/LauncherAppState.java b/src/com/android/launcher3/LauncherAppState.java
index fe2b43f..9a47eaa 100644
--- a/src/com/android/launcher3/LauncherAppState.java
+++ b/src/com/android/launcher3/LauncherAppState.java
@@ -191,7 +191,7 @@
                 availableWidth, availableHeight);
         return grid;
     }
-    DynamicGrid getDynamicGrid() {
+    public DynamicGrid getDynamicGrid() {
         return mDynamicGrid;
     }
 
diff --git a/src/com/android/launcher3/LauncherModel.java b/src/com/android/launcher3/LauncherModel.java
index b89579e..0a3ff3e 100644
--- a/src/com/android/launcher3/LauncherModel.java
+++ b/src/com/android/launcher3/LauncherModel.java
@@ -283,7 +283,10 @@
         addAndBindAddedApps(context, workspaceApps, cb, allAppsApps);
     }
     public void addAndBindAddedApps(final Context context, final ArrayList<ItemInfo> workspaceApps,
-                                    final Callbacks callbacks, final ArrayList<AppInfo> allAppsApps) {
+                                final Callbacks callbacks, final ArrayList<AppInfo> allAppsApps) {
+        if (workspaceApps == null || allAppsApps == null) {
+            throw new RuntimeException("workspaceApps and allAppsApps must not be null");
+        }
         if (workspaceApps.isEmpty() && allAppsApps.isEmpty()) {
             return;
         }
@@ -1522,7 +1525,7 @@
             }
             if (!added.isEmpty()) {
                 Callbacks cb = mCallbacks != null ? mCallbacks.get() : null;
-                addAndBindAddedApps(context, added, cb, null);
+                addAndBindAddedApps(context, added, cb, new ArrayList<AppInfo>());
             }
         }
 
diff --git a/src/com/android/launcher3/Workspace.java b/src/com/android/launcher3/Workspace.java
index d517d8b..9b17fcd 100644
--- a/src/com/android/launcher3/Workspace.java
+++ b/src/com/android/launcher3/Workspace.java
@@ -726,7 +726,12 @@
                         onComplete, stripEmptyScreens);
             }
             return;
+        } else if (stripEmptyScreens) {
+            // If we're not going to strip the empty screens after removing
+            // the extra empty screen, do it right away.
+            stripEmptyScreens();
         }
+
         if (onComplete != null) {
             onComplete.run();
         }
@@ -977,7 +982,9 @@
         }
 
         // Get the canonical child id to uniquely represent this view in this screen
-        int childId = LauncherModel.getCellLayoutChildId(container, screenId, x, y, spanX, spanY);
+        ItemInfo info = (ItemInfo) child.getTag();
+        int childId = mLauncher.getViewIdForItem(info);
+
         boolean markCellsAsOccupied = !(child instanceof Folder);
         if (!layout.addViewToCellLayout(child, insert ? 0 : -1, childId, lp, markCellsAsOccupied)) {
             // TODO: This branch occurs when the workspace is adding views
@@ -2136,7 +2143,8 @@
             if (stateIsSmall) {
                 finalAlpha = 0f;
             } else if (stateIsNormal && mWorkspaceFadeInAdjacentScreens) {
-                finalAlpha = i == getNextPage() ? 1f : 0f;
+
+                finalAlpha = (i == getNextPage() || i < numCustomPages()) ? 1f : 0f;
             } else {
                 finalAlpha = 1f;
             }
@@ -2361,7 +2369,9 @@
     void hideCustomContentIfNecessary() {
         boolean hide  = mState != Workspace.State.NORMAL;
         if (hide && hasCustomContent()) {
+            disableLayoutTransitions();
             mWorkspaceScreens.get(CUSTOM_CONTENT_SCREEN_ID).setVisibility(INVISIBLE);
+            enableLayoutTransitions();
         }
     }
 
@@ -2377,6 +2387,11 @@
                 final CellLayout cl = (CellLayout) getChildAt(i);
                 cl.setShortcutAndWidgetAlpha(1f);
             }
+        } else {
+            for (int i = 0; i < numCustomPages(); i++) {
+                final CellLayout cl = (CellLayout) getChildAt(i);
+                cl.setShortcutAndWidgetAlpha(1f);
+            }
         }
         showCustomContentIfNecessary();
     }
@@ -2564,8 +2579,16 @@
             icon.clearPressedOrFocusedBackground();
         }
 
-        mDragController.startDrag(b, dragLayerX, dragLayerY, source, child.getTag(),
+        if (child.getTag() == null || !(child.getTag() instanceof ItemInfo)) {
+            String msg = "Drag started with a view that has no tag set. This "
+                    + "will cause a crash (issue 11627249) down the line. "
+                    + "View: " + child + "  tag: " + child.getTag();
+            throw new IllegalStateException(msg);
+        }
+
+        DragView dv = mDragController.startDrag(b, dragLayerX, dragLayerY, source, child.getTag(),
                 DragController.DRAG_ACTION_MOVE, dragVisualizeOffset, dragRect, scale);
+        dv.setIntrinsicIconScaleFactor(source.getIntrinsicIconScaleFactor());
 
         if (child.getParent() instanceof ShortcutAndWidgetContainer) {
             mDragSourceInternal = (ShortcutAndWidgetContainer) child.getParent();
@@ -2911,8 +2934,6 @@
                     lp.cellHSpan = item.spanX;
                     lp.cellVSpan = item.spanY;
                     lp.isLockedToGrid = true;
-                    cell.setId(LauncherModel.getCellLayoutChildId(container, mDragInfo.screenId,
-                            mTargetCell[0], mTargetCell[1], mDragInfo.spanX, mDragInfo.spanY));
 
                     if (container != LauncherSettings.Favorites.CONTAINER_HOTSEAT &&
                             cell instanceof LauncherAppWidgetHostView) {
@@ -4169,6 +4190,11 @@
     }
 
     @Override
+    public float getIntrinsicIconScaleFactor() {
+        return 1f;
+    }
+
+    @Override
     public boolean supportsFlingToDelete() {
         return true;
     }
@@ -4589,6 +4615,7 @@
                 child.requestFocus();
             }
          }
+        exitWidgetResizeMode();
     }
 
     @Override