added spring loaded mode for adding items to workspace

Change-Id: Ie92294fe2b1d6697d84756a2fcea91a09f72825b
diff --git a/src/com/android/launcher2/Alarm.java b/src/com/android/launcher2/Alarm.java
new file mode 100644
index 0000000..38ff367
--- /dev/null
+++ b/src/com/android/launcher2/Alarm.java
@@ -0,0 +1,76 @@
+/*
+ * Copyright (C) 2010 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.launcher2;
+
+import android.os.Handler;
+
+public class Alarm implements Runnable{
+    // if we reach this time and the alarm hasn't been cancelled, call the listener
+    private long mAlarmTriggerTime;
+
+    // if we've scheduled a call to run() (ie called mHandler.postDelayed), this variable is true.
+    // We use this to avoid having multiple pending callbacks
+    private boolean mWaitingForCallback;
+
+    private Handler mHandler;
+    private OnAlarmListener mAlarmListener;
+
+    public Alarm() {
+        mHandler = new Handler();
+    }
+
+    public void setOnAlarmListener(OnAlarmListener alarmListener) {
+        mAlarmListener = alarmListener;
+    }
+
+    // Sets the alarm to go off in a certain number of milliseconds. If the alarm is already set,
+    // it's overwritten and only the new alarm setting is used
+    public void setAlarm(long millisecondsInFuture) {
+        long currentTime = System.currentTimeMillis();
+        mAlarmTriggerTime = currentTime + millisecondsInFuture;
+        if (!mWaitingForCallback) {
+            mHandler.postDelayed(this, mAlarmTriggerTime - currentTime);
+            mWaitingForCallback = true;
+        }
+    }
+
+    public void cancelAlarm() {
+        mAlarmTriggerTime = 0;
+    }
+
+    // this is called when our timer runs out
+    public void run() {
+        mWaitingForCallback = false;
+        if (mAlarmTriggerTime != 0) {
+            long currentTime = System.currentTimeMillis();
+            if (mAlarmTriggerTime > currentTime) {
+                // We still need to wait some time to trigger spring loaded mode--
+                // post a new callback
+                mHandler.postDelayed(this, Math.max(0, mAlarmTriggerTime - currentTime));
+                mWaitingForCallback = true;
+            } else {
+                if (mAlarmListener != null) {
+                    mAlarmListener.onAlarm(this);
+                }
+            }
+        }
+    }
+}
+
+interface OnAlarmListener {
+    public void onAlarm(Alarm alarm);
+}
diff --git a/src/com/android/launcher2/AllAppsPagedView.java b/src/com/android/launcher2/AllAppsPagedView.java
index 8d4ddba..2fd0b65 100644
--- a/src/com/android/launcher2/AllAppsPagedView.java
+++ b/src/com/android/launcher2/AllAppsPagedView.java
@@ -21,7 +21,11 @@
 import android.content.ComponentName;
 import android.content.Context;
 import android.content.res.TypedArray;
+import android.graphics.Bitmap;
+import android.graphics.Canvas;
+import android.graphics.drawable.Drawable;
 import android.util.AttributeSet;
+import android.util.Log;
 import android.view.LayoutInflater;
 import android.view.View;
 import android.view.animation.AnimationUtils;
@@ -258,8 +262,14 @@
         ApplicationInfo app = (ApplicationInfo) v.getTag();
         app = new ApplicationInfo(app);
 
-        mLauncher.getWorkspace().onDragStartedWithItemSpans(1, 1);
-        mDragController.startDrag(v, this, app, DragController.DRAG_ACTION_COPY);
+        // get icon (top compound drawable, index is 1)
+        final Drawable icon = ((TextView) v).getCompoundDrawables()[1];
+        Bitmap b = Bitmap.createBitmap(icon.getIntrinsicWidth(), icon.getIntrinsicHeight(),
+                Bitmap.Config.ARGB_8888);
+        Canvas c = new Canvas(b);
+        icon.draw(c);
+        mLauncher.getWorkspace().onDragStartedWithItemSpans(1, 1, b);
+        mDragController.startDrag(v, b, this, app, DragController.DRAG_ACTION_COPY, null);
         return true;
     }
 
diff --git a/src/com/android/launcher2/CellLayout.java b/src/com/android/launcher2/CellLayout.java
index 8fe489d..a104c55 100644
--- a/src/com/android/launcher2/CellLayout.java
+++ b/src/com/android/launcher2/CellLayout.java
@@ -801,7 +801,10 @@
                             cellXY[0] + childLeft + lp.width / 2,
                             cellXY[1] + childTop + lp.height / 2, 0, null);
 
-                    ((Workspace) mParent).animateViewIntoPosition(child);
+                    if (lp.animateDrop) {
+                        lp.animateDrop = false;
+                        ((Workspace) mParent).animateViewIntoPosition(child);
+                    }
                 }
             }
         }
@@ -1224,11 +1227,12 @@
      *
      * @param child The child that is being dropped
      */
-    void onDropChild(View child) {
+    void onDropChild(View child, boolean animate) {
         if (child != null) {
             LayoutParams lp = (LayoutParams) child.getLayoutParams();
             lp.isDragging = false;
             lp.dropped = true;
+            lp.animateDrop = animate;
             child.setVisibility(View.VISIBLE);
             child.requestLayout();
         }
@@ -1466,6 +1470,8 @@
 
         boolean dropped;
 
+        boolean animateDrop;
+
         public LayoutParams(Context c, AttributeSet attrs) {
             super(c, attrs);
             cellHSpan = 1;
diff --git a/src/com/android/launcher2/CustomizePagedView.java b/src/com/android/launcher2/CustomizePagedView.java
index 62dcf4a..342974a 100644
--- a/src/com/android/launcher2/CustomizePagedView.java
+++ b/src/com/android/launcher2/CustomizePagedView.java
@@ -60,6 +60,7 @@
 import android.view.animation.Interpolator;
 import android.widget.ImageView;
 import android.widget.LinearLayout;
+import android.widget.TextView;
 
 import com.android.launcher.R;
 
@@ -587,30 +588,47 @@
         mIsDragging = true;
 
         switch (mCustomizationType) {
-        case WidgetCustomization:
+        case WidgetCustomization: {
             // Get the widget preview as the drag representation
+            final LinearLayout l = (LinearLayout) v;
+            final Drawable icon = ((ImageView) l.findViewById(R.id.widget_preview)).getDrawable();
+            Bitmap b = drawableToBitmap(icon);
             PendingAddWidgetInfo createWidgetInfo = (PendingAddWidgetInfo) v.getTag();
             final View dragView = v.findViewById(R.id.widget_preview);
 
-            mLauncher.getWorkspace().onDragStartedWithItemMinSize(
-                    createWidgetInfo.minWidth, createWidgetInfo.minHeight);
-            mDragController.startDrag(dragView, this, createWidgetInfo, DragController.DRAG_ACTION_COPY, null);
+            int[] spanXY = CellLayout.rectToCell(
+                    getResources(), createWidgetInfo.minWidth, createWidgetInfo.minHeight, null);
+            createWidgetInfo.spanX = spanXY[0];
+            createWidgetInfo.spanY = spanXY[1];
+            mLauncher.getWorkspace().onDragStartedWithItemSpans(spanXY[0], spanXY[1], b);
+            mDragController.startDrag(
+                    v, b, this, createWidgetInfo, DragController.DRAG_ACTION_COPY, null);
 
             return true;
-        case ShortcutCustomization:
+        }
+        case ShortcutCustomization: {
+            // get icon (top compound drawable, index is 1)
+            final Drawable icon = ((TextView) v).getCompoundDrawables()[1];
+            Bitmap b = drawableToBitmap(icon);
             PendingAddItemInfo createItemInfo = (PendingAddItemInfo) v.getTag();
-            mDragController.startDrag(v, this, createItemInfo, DragController.DRAG_ACTION_COPY);
-            mLauncher.getWorkspace().onDragStartedWithItemSpans(1, 1);
+            mDragController.startDrag(
+                    v, b, this, createItemInfo, DragController.DRAG_ACTION_COPY, null);
+            mLauncher.getWorkspace().onDragStartedWithItemSpans(1, 1, b);
             return true;
-        case ApplicationCustomization:
+        }
+        case ApplicationCustomization: {
             // Pick up the application for dropping
+            // get icon (top compound drawable, index is 1)
+            final Drawable icon = ((TextView) v).getCompoundDrawables()[1];
+            Bitmap b = drawableToBitmap(icon);
             ApplicationInfo app = (ApplicationInfo) v.getTag();
             app = new ApplicationInfo(app);
 
-            mDragController.startDrag(v, this, app, DragController.DRAG_ACTION_COPY);
-            mLauncher.getWorkspace().onDragStartedWithItemSpans(1, 1);
+            mDragController.startDrag(v, b, this, app, DragController.DRAG_ACTION_COPY, null);
+            mLauncher.getWorkspace().onDragStartedWithItemSpans(1, 1, b);
             return true;
         }
+        }
         return false;
     }
 
diff --git a/src/com/android/launcher2/Launcher.java b/src/com/android/launcher2/Launcher.java
index d5f20c5..0cb0e13 100644
--- a/src/com/android/launcher2/Launcher.java
+++ b/src/com/android/launcher2/Launcher.java
@@ -176,7 +176,8 @@
     private static final String TOOLBAR_ICON_METADATA_NAME = "com.android.launcher.toolbar_icon";
 
     /** The different states that Launcher can be in. */
-    private enum State { WORKSPACE, ALL_APPS, CUSTOMIZE, OVERVIEW };
+    private enum State { WORKSPACE, ALL_APPS, CUSTOMIZE, OVERVIEW,
+        CUSTOMIZE_SPRING_LOADED, ALL_APPS_SPRING_LOADED };
     private State mState = State.WORKSPACE;
     private AnimatorSet mStateAnimation;
 
@@ -1112,7 +1113,23 @@
         final int[] cellXY = mTmpAddItemCellCoordinates;
         final CellLayout layout = (CellLayout) mWorkspace.getChildAt(screen);
 
-        if (!layout.findCellForSpanThatIntersects(cellXY, 1, 1, intersectCellX, intersectCellY)) {
+        int[] touchXY = null;
+        if (mAddDropPosition != null && mAddDropPosition[0] > -1 && mAddDropPosition[1] > -1) {
+            touchXY = mAddDropPosition;
+        }
+        boolean foundCellSpan = false;
+        if (touchXY != null) {
+            // when dragging and dropping, just find the closest free spot
+            CellLayout screenLayout = (CellLayout) mWorkspace.getChildAt(screen);
+            int[] result = screenLayout.findNearestVacantArea(
+                    touchXY[0], touchXY[1], 1, 1, cellXY);
+            foundCellSpan = (result != null);
+        } else {
+            foundCellSpan = layout.findCellForSpanThatIntersects(
+                    cellXY, 1, 1, intersectCellX, intersectCellY);
+        }
+
+        if (!foundCellSpan) {
             showOutOfSpaceMessage();
             return;
         }
@@ -1151,15 +1168,13 @@
         if (mAddDropPosition != null && mAddDropPosition[0] > -1 && mAddDropPosition[1] > -1) {
             touchXY = mAddDropPosition;
         }
-        boolean findNearestVacantAreaFailed = false;
         boolean foundCellSpan = false;
         if (touchXY != null) {
             // when dragging and dropping, just find the closest free spot
             CellLayout screenLayout = (CellLayout) mWorkspace.getChildAt(screen);
             int[] result = screenLayout.findNearestVacantArea(
                     touchXY[0], touchXY[1], spanXY[0], spanXY[1], cellXY);
-            findNearestVacantAreaFailed = (result == null);
-            foundCellSpan = !findNearestVacantAreaFailed;
+            foundCellSpan = (result != null);
         } else {
             // if we long pressed on an empty cell to bring up a menu,
             // make sure we intersect the empty cell
@@ -1626,8 +1641,6 @@
     void addAppWidgetFromDrop(PendingAddWidgetInfo info, int screen, int[] position) {
         resetAddInfo();
         mAddScreen = screen;
-
-        // only set mAddDropPosition if we dropped on home screen in "spring-loaded" manner
         mAddDropPosition = position;
 
         int appWidgetId = getAppWidgetHost().allocateAppWidgetId();
@@ -2685,6 +2698,10 @@
      * @param animated If true, the transition will be animated.
      */
     private void cameraZoomIn(State fromState, boolean animated) {
+        cameraZoomIn(fromState, animated, false);
+    }
+
+    private void cameraZoomIn(State fromState, boolean animated, boolean springLoaded) {
         Resources res = getResources();
         int duration = res.getInteger(R.integer.config_allAppsZoomOutTime);
         float scaleFactor = (float) res.getInteger(R.integer.config_allAppsZoomScaleFactor);
@@ -2696,7 +2713,9 @@
 
         setPivotsForZoom(fromView, fromState, scaleFactor);
 
-        mWorkspace.unshrink(animated);
+        if (!springLoaded) {
+            mWorkspace.unshrink(animated);
+        }
 
         if (animated) {
             if (mStateAnimation != null) mStateAnimation.cancel();
@@ -2719,7 +2738,9 @@
 
             AnimatorSet toolbarHideAnim = new AnimatorSet();
             AnimatorSet toolbarShowAnim = new AnimatorSet();
-            hideAndShowToolbarButtons(State.WORKSPACE, toolbarShowAnim, toolbarHideAnim);
+            if (!springLoaded) {
+                hideAndShowToolbarButtons(State.WORKSPACE, toolbarShowAnim, toolbarHideAnim);
+            }
 
             mStateAnimation.playTogether(scaleAnim, toolbarHideAnim, alphaAnim);
 
@@ -2730,7 +2751,9 @@
             mStateAnimation.start();
         } else {
             fromView.setVisibility(View.GONE);
-            hideAndShowToolbarButtons(State.WORKSPACE, null, null);
+            if (!springLoaded) {
+                hideAndShowToolbarButtons(State.WORKSPACE, null, null);
+            }
         }
     }
 
@@ -2859,6 +2882,33 @@
         mState = State.WORKSPACE;
     }
 
+    void enterSpringLoadedDragMode(CellLayout layout) {
+        mWorkspace.enterSpringLoadedDragMode(layout);
+        if (mState == State.ALL_APPS) {
+            cameraZoomIn(State.ALL_APPS, true, true);
+            mState = State.ALL_APPS_SPRING_LOADED;
+        } else if (mState == State.CUSTOMIZE) {
+            cameraZoomIn(State.CUSTOMIZE, true, true);
+            mState = State.CUSTOMIZE_SPRING_LOADED;
+        }/* else {
+            // we're already in spring loaded mode; don't do anything
+        }*/
+    }
+
+    void exitSpringLoadedDragMode() {
+        if (mState == State.ALL_APPS_SPRING_LOADED) {
+            mWorkspace.exitSpringLoadedDragMode(Workspace.ShrinkState.BOTTOM_VISIBLE);
+            cameraZoomOut(State.ALL_APPS, true);
+            mState = State.ALL_APPS;
+        } else if (mState == State.CUSTOMIZE_SPRING_LOADED) {
+            mWorkspace.exitSpringLoadedDragMode(Workspace.ShrinkState.TOP);
+            cameraZoomOut(State.CUSTOMIZE, true);
+            mState = State.CUSTOMIZE;
+        }/* else {
+            // we're not in spring loaded mode; don't do anything
+        }*/
+    }
+
     /**
      * Things to test when changing this code.
      *   - Home from workspace
@@ -2899,7 +2949,7 @@
      *          - From another workspace
      */
     void closeAllApps(boolean animated) {
-        if (mState == State.ALL_APPS) {
+        if (mState == State.ALL_APPS || mState == State.ALL_APPS_SPRING_LOADED) {
             mWorkspace.setVisibility(View.VISIBLE);
             if (LauncherApplication.isScreenXLarge()) {
                 cameraZoomIn(State.ALL_APPS, animated);
@@ -2932,7 +2982,7 @@
 
     // Hide the customization drawer (only exists in x-large configuration)
     void hideCustomizationDrawer(boolean animated) {
-        if (mState == State.CUSTOMIZE) {
+        if (mState == State.CUSTOMIZE || mState == State.CUSTOMIZE_SPRING_LOADED) {
             cameraZoomIn(State.CUSTOMIZE, animated);
         }
     }
diff --git a/src/com/android/launcher2/PagedView.java b/src/com/android/launcher2/PagedView.java
index fed0884..856507d 100644
--- a/src/com/android/launcher2/PagedView.java
+++ b/src/com/android/launcher2/PagedView.java
@@ -110,6 +110,9 @@
     protected boolean mAllowOverScroll = true;
     protected int mUnboundedScrollX;
 
+    // parameter that adjusts the layout to be optimized for CellLayouts with that scale factor
+    protected float mLayoutScale = 1.0f;
+
     protected static final int INVALID_POINTER = -1;
 
     protected int mActivePointerId = INVALID_POINTER;
@@ -268,7 +271,9 @@
         if (!mScroller.isFinished()) {
             mScroller.abortAnimation();
         }
-        if (getChildCount() == 0 || currentPage == mCurrentPage) {
+        // don't introduce any checks like mCurrentPage == currentPage here-- if we change the
+        // the default
+        if (getChildCount() == 0) {
             return;
         }
 
@@ -445,6 +450,32 @@
         setCurrentPage(newCurrentPage);
     }
 
+    // A layout scale of 1.0f assumes that the CellLayouts, in their unshrunken state, have a
+    // scale of 1.0f. A layout scale of 0.8f assumes the CellLayouts have a scale of 0.8f, and
+    // tightens the layout accordingly
+    public void setLayoutScale(float childrenScale) {
+        mLayoutScale = childrenScale;
+
+        // Now we need to do a re-layout, but preserving absolute X and Y coordinates
+        int childCount = getChildCount();
+        float childrenX[] = new float[childCount];
+        float childrenY[] = new float[childCount];
+        for (int i = 0; i < childCount; i++) {
+            final View child = getChildAt(i);
+            childrenX[i] = child.getX();
+            childrenY[i] = child.getY();
+        }
+        onLayout(false, mLeft, mTop, mRight, mBottom);
+        for (int i = 0; i < childCount; i++) {
+            final View child = getChildAt(i);
+            child.setX(childrenX[i]);
+            child.setY(childrenY[i]);
+        }
+        // Also, the page offset has changed  (since the pages are now smaller);
+        // update the page offset, but again preserving absolute X and Y coordinates
+        moveToNewPageWithoutMovingCellLayouts(mCurrentPage);
+    }
+
     @Override
     protected void onLayout(boolean changed, int left, int top, int right, int bottom) {
         if (mFirstLayout && mCurrentPage >= 0 && mCurrentPage < getChildCount()) {
@@ -466,17 +497,21 @@
         for (int i = 0; i < childCount; i++) {
             final View child = getChildAt(i);
             if (child.getVisibility() != View.GONE) {
-                final int childWidth = child.getMeasuredWidth();
+                final int childWidth = getScaledMeasuredWidth(child);
                 final int childHeight = child.getMeasuredHeight();
                 int childTop = mPaddingTop;
                 if (mCenterPagesVertically) {
                     childTop += ((getMeasuredHeight() - verticalPadding) - childHeight) / 2;
                 }
+
                 child.layout(childLeft, childTop,
-                        childLeft + childWidth, childTop + childHeight);
+                        childLeft + child.getMeasuredWidth(), childTop + childHeight);
                 childLeft += childWidth + mPageSpacing;
             }
         }
+        if (mFirstLayout && mCurrentPage >= 0 && mCurrentPage < getChildCount()) {
+            mFirstLayout = false;
+        }
     }
 
     protected void updateAdjacentPagesAlpha() {
@@ -487,7 +522,7 @@
                 final int childCount = getChildCount();
                 for (int i = 0; i < childCount; ++i) {
                     View layout = (View) getChildAt(i);
-                    int childWidth = layout.getMeasuredWidth();
+                    int childWidth = getScaledMeasuredWidth(layout);
                     int halfChildWidth = (childWidth / 2);
                     int childCenter = getChildOffset(i) + halfChildWidth;
 
@@ -503,11 +538,11 @@
                     int distanceFromScreenCenter = childCenter - screenCenter;
                     if (distanceFromScreenCenter > 0) {
                         if (i > 0) {
-                            d += getChildAt(i - 1).getMeasuredWidth() / 2;
+                            d += getScaledMeasuredWidth(getChildAt(i - 1)) / 2;
                         }
                     } else {
                         if (i < childCount - 1) {
-                            d += getChildAt(i + 1).getMeasuredWidth() / 2;
+                            d += getScaledMeasuredWidth(getChildAt(i + 1)) / 2;
                         }
                     }
                     d += mPageSpacing;
@@ -553,7 +588,7 @@
         // page.
         final int pageCount = getChildCount();
         if (pageCount > 0) {
-            final int pageWidth = getChildAt(0).getMeasuredWidth();
+            final int pageWidth = getScaledMeasuredWidth(getChildAt(0));
             final int screenWidth = getMeasuredWidth();
             int x = getRelativeChildOffset(0) + pageWidth;
             int leftScreen = 0;
@@ -563,7 +598,7 @@
                 x += pageWidth + mPageSpacing;
                 // replace above line with this if you don't assume all pages have same width as 0th
                 // page:
-                // x += getChildAt(leftScreen).getMeasuredWidth();
+                // x += getScaledMeasuredWidth(getChildAt(leftScreen));
             }
             rightScreen = leftScreen;
             while (x < mScrollX + screenWidth) {
@@ -572,7 +607,7 @@
                 // replace above line with this if you don't assume all pages have same width as 0th
                 // page:
                 //if (rightScreen < pageCount) {
-                //    x += getChildAt(rightScreen).getMeasuredWidth();
+                //    x += getScaledMeasuredWidth(getChildAt(rightScreen));
                 //}
             }
             rightScreen = Math.min(getChildCount() - 1, rightScreen);
@@ -1049,7 +1084,7 @@
         int right;
         for (int i = 0; i < childCount; ++i) {
             left = getRelativeChildOffset(i);
-            right = (left + getChildAt(i).getMeasuredWidth());
+            right = (left + getScaledMeasuredWidth(getChildAt(i)));
             if (left <= relativeOffset && relativeOffset <= right) {
                 return i;
             }
@@ -1067,11 +1102,15 @@
 
         int offset = getRelativeChildOffset(0);
         for (int i = 0; i < index; ++i) {
-            offset += getChildAt(i).getMeasuredWidth() + mPageSpacing;
+            offset += getScaledMeasuredWidth(getChildAt(i)) + mPageSpacing;
         }
         return offset;
     }
 
+    protected int getScaledMeasuredWidth(View child) {
+        return (int) (child.getMeasuredWidth() * mLayoutScale + 0.5f);
+    }
+
     int getPageNearestToCenterOfScreen() {
         int minDistanceFromScreenCenter = getMeasuredWidth();
         int minDistanceFromScreenCenterIndex = -1;
@@ -1079,7 +1118,7 @@
         final int childCount = getChildCount();
         for (int i = 0; i < childCount; ++i) {
             View layout = (View) getChildAt(i);
-            int childWidth = layout.getMeasuredWidth();
+            int childWidth = getScaledMeasuredWidth(layout);
             int halfChildWidth = (childWidth / 2);
             int childCenter = getChildOffset(i) + halfChildWidth;
             int distanceFromScreenCenter = Math.abs(childCenter - screenCenter);
diff --git a/src/com/android/launcher2/SpringLoadedDragController.java b/src/com/android/launcher2/SpringLoadedDragController.java
new file mode 100644
index 0000000..a734258
--- /dev/null
+++ b/src/com/android/launcher2/SpringLoadedDragController.java
@@ -0,0 +1,58 @@
+/*
+ * Copyright (C) 2010 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.launcher2;
+
+public class SpringLoadedDragController implements OnAlarmListener {
+    // how long the user must hover over a mini-screen before it unshrinks
+    final long ENTER_SPRING_LOAD_HOVER_TIME = 1000;
+    final long EXIT_SPRING_LOAD_HOVER_TIME = 200;
+
+    Alarm mAlarm;
+
+    // the screen the user is currently hovering over, if any
+    private CellLayout mScreen;
+    private Launcher mLauncher;
+
+    public SpringLoadedDragController(Launcher launcher) {
+        mLauncher = launcher;
+        mAlarm = new Alarm();
+        mAlarm.setOnAlarmListener(this);
+    }
+
+    public void onDragEnter(CellLayout cl) {
+        mScreen = cl;
+        mAlarm.setAlarm(ENTER_SPRING_LOAD_HOVER_TIME);
+    }
+
+    public void onDragExit() {
+        if (mScreen != null) {
+            mScreen.onDragExit();
+        }
+        mScreen = null;
+        mAlarm.setAlarm(EXIT_SPRING_LOAD_HOVER_TIME);
+    }
+
+    // this is called when our timer runs out
+    public void onAlarm(Alarm alarm) {
+        if (mScreen != null) {
+            // we're currently hovering over a screen
+            mLauncher.enterSpringLoadedDragMode(mScreen);
+        } else {
+            mLauncher.exitSpringLoadedDragMode();
+        }
+    }
+}
diff --git a/src/com/android/launcher2/Workspace.java b/src/com/android/launcher2/Workspace.java
index 239f6f6..b58faed 100644
--- a/src/com/android/launcher2/Workspace.java
+++ b/src/com/android/launcher2/Workspace.java
@@ -81,6 +81,9 @@
     // customization mode
     private static final float SHRINK_FACTOR = 0.16f;
 
+    // How much the screens shrink when we enter spring loaded drag mode
+    private static final float SPRING_LOADED_DRAG_SHRINK_FACTOR = 0.7f;
+
     // Y rotation to apply to the workspace screens
     private static final float WORKSPACE_ROTATION = 12.5f;
     private static final float WORKSPACE_TRANSLATION = 50.0f;
@@ -118,6 +121,7 @@
     private int mDefaultPage;
 
     private boolean mPageMoving = false;
+    private boolean mIsDragInProcess = false;
 
     /**
      * CellInfo for the cell that is currently being dragged
@@ -149,6 +153,8 @@
     private float[] mTempDragBottomRightCoordinates = new float[2];
     private Matrix mTempInverseMatrix = new Matrix();
 
+    private SpringLoadedDragController mSpringLoadedDragControllger;
+
     private static final int DEFAULT_CELL_COUNT_X = 4;
     private static final int DEFAULT_CELL_COUNT_Y = 4;
 
@@ -159,9 +165,10 @@
     // in all apps or customize mode)
     private boolean mIsSmall = false;
     private boolean mIsInUnshrinkAnimation = false;
-    private AnimatorListener mUnshrinkAnimationListener;
+    private AnimatorListener mShrinkAnimationListener, mUnshrinkAnimationListener;
     enum ShrinkState { TOP, SPRING_LOADED, MIDDLE, BOTTOM_HIDDEN, BOTTOM_VISIBLE };
     private ShrinkState mShrinkState;
+    private boolean mWasSpringLoadedOnDragExit = false;
     private boolean mWaitingToShrink = false;
     private ShrinkState mWaitingToShrinkState;
     private AnimatorSet mAnimator;
@@ -677,7 +684,7 @@
         for (int i = 0; i < getChildCount(); i++) {
             CellLayout cl = (CellLayout) getChildAt(i);
             if (cl != null) {
-                int totalDistance = cl.getMeasuredWidth() + mPageSpacing;
+                int totalDistance = getScaledMeasuredWidth(cl) + mPageSpacing;
                 int delta = screenCenter - (getChildOffset(i) -
                         getRelativeChildOffset(i) + halfScreenSize);
 
@@ -938,7 +945,9 @@
 
         // Stop any scrolling, move to the current page right away
         setCurrentPage((mNextPage != INVALID_PAGE) ? mNextPage : mCurrentPage);
-        updateWhichPagesAcceptDrops(mShrinkState);
+        if (!mIsDragInProcess) {
+            updateWhichPagesAcceptDrops(mShrinkState);
+        }
 
         // we intercept and reject all touch events when we're small, so be sure to reset the state
         mTouchState = TOUCH_STATE_REST;
@@ -1039,6 +1048,7 @@
             // increment newX for the next screen
             newX += scaledPageWidth + extraScaledSpacing;
         }
+        setLayoutScale(1.0f);
         if (animated) {
             mAnimator.start();
         }
@@ -1159,18 +1169,24 @@
      * appearance).
      *
      */
-    public void onDragStartedWithItemSpans(int spanX, int spanY) {
-        updateWhichPagesAcceptDropsDuringDrag(mShrinkState, spanX, spanY);
-    }
+    public void onDragStartedWithItemSpans(int spanX, int spanY, Bitmap b) {
+        mIsDragInProcess = true;
 
-    public void onDragStartedWithItemMinSize(int minWidth, int minHeight) {
-        int[] spanXY = CellLayout.rectToCell(getResources(), minWidth, minHeight, null);
-        onDragStartedWithItemSpans(spanXY[0], spanXY[1]);
+        final Canvas canvas = new Canvas();
+
+        // We need to add extra padding to the bitmap to make room for the glow effect
+        final int bitmapPadding = HolographicOutlineHelper.OUTER_BLUR_RADIUS;
+
+        // The outline is used to visualize where the item will land if dropped
+        mDragOutline = createDragOutline(b, canvas, bitmapPadding);
+
+        updateWhichPagesAcceptDropsDuringDrag(mShrinkState, spanX, spanY);
     }
 
     // we call this method whenever a drag and drop in Launcher finishes, even if Workspace was
     // never dragged over
     public void onDragStopped() {
+        mIsDragInProcess = false;
         updateWhichPagesAcceptDrops(mShrinkState);
     }
 
@@ -1181,16 +1197,48 @@
 
     // We call this when we trigger an unshrink by clicking on the CellLayout cl
     public void unshrink(CellLayout clThatWasClicked) {
+        unshrink(clThatWasClicked, false);
+    }
+
+    public void unshrink(CellLayout clThatWasClicked, boolean springLoaded) {
         int newCurrentPage = indexOfChild(clThatWasClicked);
         if (mIsSmall) {
+            if (springLoaded) {
+                setLayoutScale(SPRING_LOADED_DRAG_SHRINK_FACTOR);
+            }
             moveToNewPageWithoutMovingCellLayouts(newCurrentPage);
-            unshrink(true);
+            unshrink(true, springLoaded);
+        }
+    }
+
+
+    public void enterSpringLoadedDragMode(CellLayout clThatWasClicked) {
+        mShrinkState = ShrinkState.SPRING_LOADED;
+        unshrink(clThatWasClicked, true);
+        mDragTargetLayout.onDragEnter();
+    }
+
+    public void exitSpringLoadedDragMode(ShrinkState shrinkState) {
+        shrink(shrinkState);
+        if (mDragTargetLayout != null) {
+            mDragTargetLayout.onDragExit();
         }
     }
 
     void unshrink(boolean animated) {
+        unshrink(animated, false);
+    }
+
+    void unshrink(boolean animated, boolean springLoaded) {
         if (mIsSmall) {
-            mIsSmall = false;
+            float finalScaleFactor = 1.0f;
+            float finalBackgroundAlpha = 0.0f;
+            if (springLoaded) {
+                finalScaleFactor = SPRING_LOADED_DRAG_SHRINK_FACTOR;
+                finalBackgroundAlpha = 1.0f;
+            } else {
+                mIsSmall = false;
+            }
             if (mAnimator != null) {
                 mAnimator.cancel();
             }
@@ -1216,9 +1264,9 @@
                     ObjectAnimator animWithInterpolator = ObjectAnimator.ofPropertyValuesHolder(cl,
                             PropertyValuesHolder.ofFloat("translationX", translation),
                             PropertyValuesHolder.ofFloat("translationY", 0.0f),
-                            PropertyValuesHolder.ofFloat("scaleX", 1.0f),
-                            PropertyValuesHolder.ofFloat("scaleY", 1.0f),
-                            PropertyValuesHolder.ofFloat("backgroundAlpha", 0.0f),
+                            PropertyValuesHolder.ofFloat("scaleX", finalScaleFactor),
+                            PropertyValuesHolder.ofFloat("scaleY", finalScaleFactor),
+                            PropertyValuesHolder.ofFloat("backgroundAlpha", finalBackgroundAlpha),
                             PropertyValuesHolder.ofFloat("alpha", finalAlphaValue),
                             PropertyValuesHolder.ofFloat("rotationY", rotation));
                     animWithInterpolator.setDuration(duration);
@@ -1227,8 +1275,8 @@
                 } else {
                     cl.setTranslationX(translation);
                     cl.setTranslationY(0.0f);
-                    cl.setScaleX(1.0f);
-                    cl.setScaleY(1.0f);
+                    cl.setScaleX(finalScaleFactor);
+                    cl.setScaleY(finalScaleFactor);
                     cl.setBackgroundAlpha(0.0f);
                     cl.setAlpha(finalAlphaValue);
                     cl.setRotationY(rotation);
@@ -1295,6 +1343,22 @@
     }
 
     /**
+     * Returns a new bitmap to be used as the object outline, e.g. to visualize the drop location.
+     * Responsibility for the bitmap is transferred to the caller.
+     */
+    private Bitmap createDragOutline(Bitmap orig, Canvas canvas, int padding) {
+        final int outlineColor = getResources().getColor(R.color.drag_outline_color);
+        final Bitmap b = Bitmap.createBitmap(
+                orig.getWidth() + padding, orig.getHeight() + padding, Bitmap.Config.ARGB_8888);
+
+        canvas.setBitmap(b);
+        canvas.drawBitmap(orig, 0, 0, new Paint());
+        mOutlineHelper.applyMediumExpensiveOutlineWithBlur(b, canvas, outlineColor, outlineColor);
+
+        return b;
+    }
+
+    /**
      * Creates a drag outline to represent a drop (that we don't have the actual information for
      * yet).  May be changed in the future to alter the drop outline slightly depending on the
      * clip description mime data.
@@ -1515,12 +1579,12 @@
         }
 
         if (source != this) {
-            if (mIsSmall) {
+            if (!mIsSmall || mWasSpringLoadedOnDragExit) {
+                onDropExternal(originX, originY, dragInfo, mDragTargetLayout, false);
+            } else {
                 // if we drag and drop to small screens, don't pass the touch x/y coords (when we
                 // enable spring-loaded adding, however, we do want to pass the touch x/y coords)
                 onDropExternal(-1, -1, dragInfo, mDragTargetLayout, false);
-            } else {
-                onDropExternal(originX, originY, dragInfo, mDragTargetLayout, false);
             }
         } else if (mDragInfo != null) {
             final View cell = mDragInfo.cell;
@@ -1577,7 +1641,8 @@
             // Prepare it to be animated into its new position
             // This must be called after the view has been re-parented
             setPositionForDropAnimation(dragView, originX, originY, parent, cell);
-            parent.onDropChild(cell);
+            boolean animateDrop = !mWasSpringLoadedOnDragExit;
+            parent.onDropChild(cell, animateDrop);
         }
     }
 
@@ -1919,21 +1984,34 @@
             CellLayout layout;
             int originX = x - xOffset;
             int originY = y - yOffset;
-            if (mIsSmall || mIsInUnshrinkAnimation) {
+            boolean shrunken = mIsSmall || mIsInUnshrinkAnimation;
+            if (shrunken) {
                 layout = findMatchingPageForDragOver(
                         dragView, originX, originY, xOffset, yOffset);
 
                 if (layout != mDragTargetLayout) {
                     if (mDragTargetLayout != null) {
                         mDragTargetLayout.setHover(false);
+                        mSpringLoadedDragControllger.onDragExit();
                     }
                     mDragTargetLayout = layout;
                     if (mDragTargetLayout != null && mDragTargetLayout.getAcceptsDrops()) {
                         mDragTargetLayout.setHover(true);
+                        mSpringLoadedDragControllger.onDragEnter(mDragTargetLayout);
                     }
                 }
             } else {
                 layout = getCurrentDropLayout();
+                if (layout != mDragTargetLayout) {
+                    if (mDragTargetLayout != null) {
+                        mDragTargetLayout.onDragExit();
+                    }
+                    layout.onDragEnter();
+                    mDragTargetLayout = layout;
+                }
+            }
+            if (!shrunken || mShrinkState == ShrinkState.SPRING_LOADED) {
+                layout = getCurrentDropLayout();
 
                 final ItemInfo item = (ItemInfo)dragInfo;
                 if (dragInfo instanceof LauncherAppWidgetInfo) {
@@ -1964,23 +2042,12 @@
                     }
                 }
 
-                if (layout != mDragTargetLayout) {
-                    if (mDragTargetLayout != null) {
-                        mDragTargetLayout.onDragExit();
-                    }
-                    layout.onDragEnter();
-                    mDragTargetLayout = layout;
-                }
-
-                // only visualize the drop locations for moving icons within the home screen on
-                // tablet on phone, we also visualize icons dragged in from All Apps
-                if ((!LauncherApplication.isScreenXLarge() || source == this)
-                        && mDragTargetLayout != null) {
+                if (mDragTargetLayout != null) {
                     final View child = (mDragInfo == null) ? null : mDragInfo.cell;
-                    int localOriginX = originX - (mDragTargetLayout.getLeft() - mScrollX);
-                    int localOriginY = originY - (mDragTargetLayout.getTop() - mScrollY);
+                    float[] localOrigin = { originX, originY };
+                    mapPointFromSelfToChild(mDragTargetLayout, localOrigin, null);
                     mDragTargetLayout.visualizeDropLocation(child, mDragOutline,
-                            localOriginX, localOriginY, item.spanX, item.spanY);
+                            (int) localOrigin[0], (int) localOrigin[1], item.spanX, item.spanY);
                 }
             }
         }
@@ -1988,12 +2055,16 @@
 
     public void onDragExit(DragSource source, int x, int y, int xOffset,
             int yOffset, DragView dragView, Object dragInfo) {
+        mWasSpringLoadedOnDragExit = mShrinkState == ShrinkState.SPRING_LOADED;
         if (mDragTargetLayout != null) {
             mDragTargetLayout.onDragExit();
         }
         if (!mIsPageMoving) {
             hideOutlines();
         }
+        if (mShrinkState == ShrinkState.SPRING_LOADED) {
+            mLauncher.exitSpringLoadedDragMode();
+        }
         clearAllHovers();
     }
 
@@ -2083,7 +2154,9 @@
             }
             addInScreen(view, indexOfChild(cellLayout), mTargetCell[0],
                     mTargetCell[1], info.spanX, info.spanY, insertAtFirst);
-            cellLayout.onDropChild(view);
+            boolean animateDrop = !mWasSpringLoadedOnDragExit;
+            cellLayout.onDropChild(view, animateDrop);
+            cellLayout.animateDrop();
             CellLayout.LayoutParams lp = (CellLayout.LayoutParams) view.getLayoutParams();
 
             LauncherModel.addOrMoveItemInDatabase(mLauncher, info,
@@ -2136,6 +2209,7 @@
 
     void setLauncher(Launcher launcher) {
         mLauncher = launcher;
+        mSpringLoadedDragControllger = new SpringLoadedDragController(mLauncher);
     }
 
     public void setDragController(DragController dragController) {
@@ -2153,7 +2227,8 @@
                 // final Object tag = mDragInfo.cell.getTag();
             }
         } else if (mDragInfo != null) {
-            ((CellLayout) getChildAt(mDragInfo.screen)).onDropChild(mDragInfo.cell);
+            boolean animateDrop = !mWasSpringLoadedOnDragExit;
+            ((CellLayout) getChildAt(mDragInfo.screen)).onDropChild(mDragInfo.cell, animateDrop);
         }
 
         mDragOutline = null;
@@ -2214,6 +2289,7 @@
         for (int i = 0; i < childCount; i++) {
             ((CellLayout) getChildAt(i)).setHover(false);
         }
+        mSpringLoadedDragControllger.onDragExit();
     }
 
     @Override