Improving widget transitions:

-> When a widget has no configuration activity, we bind
   and inflate it when the user picks it up. This allows
   us to smoothly transition between it's preview and
   some actual state of the widget when it is dropped.
-> When a widget has a configuration activity, we delay
   the above process until the configuration activity
   has been run at which time we transition between
   the preview and the actual widget.

Change-Id: I5265cd98400d70e5e75c3dcd21e322ed0b352d7b
diff --git a/src/com/android/launcher2/AppsCustomizePagedView.java b/src/com/android/launcher2/AppsCustomizePagedView.java
index 7cfe3be..de96eda 100644
--- a/src/com/android/launcher2/AppsCustomizePagedView.java
+++ b/src/com/android/launcher2/AppsCustomizePagedView.java
@@ -19,6 +19,7 @@
 import android.animation.AnimatorSet;
 import android.animation.ObjectAnimator;
 import android.animation.ValueAnimator;
+import android.appwidget.AppWidgetHostView;
 import android.appwidget.AppWidgetManager;
 import android.appwidget.AppWidgetProviderInfo;
 import android.content.ComponentName;
@@ -39,6 +40,8 @@
 import android.graphics.TableMaskFilter;
 import android.graphics.drawable.Drawable;
 import android.os.AsyncTask;
+import android.os.Handler;
+import android.os.HandlerThread;
 import android.os.Process;
 import android.util.AttributeSet;
 import android.util.Log;
@@ -167,7 +170,7 @@
  */
 public class AppsCustomizePagedView extends PagedViewWithDraggableItems implements
         AllAppsView, View.OnClickListener, View.OnKeyListener, DragSource,
-        PagedViewIcon.PressedCallback {
+        PagedViewIcon.PressedCallback, PagedViewWidget.ShortPressListener {
     static final String LOG_TAG = "AppsCustomizePagedView";
 
     /**
@@ -229,6 +232,14 @@
     ArrayList<AppsCustomizeAsyncTask> mRunningTasks;
     private static final int sPageSleepDelay = 200;
 
+    private Runnable mInflateWidgetRunnable = null;
+    private Runnable mBindWidgetRunnable = null;
+    static final int WIDGET_NO_CLEANUP_REQUIRED = -1;
+    static final int WIDGET_BOUND = 0;
+    static final int WIDGET_INFLATED = 1;
+    int mWidgetCleanupState = WIDGET_NO_CLEANUP_REQUIRED;
+    int mWidgetLoadingId = -1;
+
     public AppsCustomizePagedView(Context context, AttributeSet attrs) {
         super(context, attrs);
         mLayoutInflater = LayoutInflater.from(context);
@@ -536,7 +547,64 @@
         mLauncher.getWorkspace().beginDragShared(v, this);
     }
 
+    private void loadWidgetInBackground(final PendingAddWidgetInfo info) {
+        final AppWidgetProviderInfo pInfo = info.info;
+        if (pInfo.configure != null) {
+            return;
+        }
+
+        mBindWidgetRunnable = new Runnable() {
+            @Override
+            public void run() {
+                mWidgetLoadingId = mLauncher.getAppWidgetHost().allocateAppWidgetId();
+                AppWidgetManager.getInstance(mLauncher).bindAppWidgetId(mWidgetLoadingId,
+                        info.componentName);
+                mWidgetCleanupState = WIDGET_BOUND;
+            }
+        };
+        post(mBindWidgetRunnable);
+
+        mInflateWidgetRunnable = new Runnable() {
+            @Override
+            public void run() {
+                AppWidgetHostView hostView =
+                        mLauncher.getAppWidgetHost().createView(mContext, mWidgetLoadingId, pInfo);
+                info.boundWidget = hostView;
+                mWidgetCleanupState = WIDGET_INFLATED;
+            }
+        };
+        post(mInflateWidgetRunnable);
+    }
+
+    @Override
+    public void onShortPress(View v) {
+        // We are anticipating a long press, and we use this time to load bind and instantiate
+        // the widget. This will need to be cleaned up if it turns out no long press occurs.
+        PendingAddWidgetInfo createWidgetInfo = (PendingAddWidgetInfo) v.getTag();
+        loadWidgetInBackground(createWidgetInfo);
+    }
+
+    @Override
+    public void cleanUpShortPress(View v) {
+        PendingAddWidgetInfo info = (PendingAddWidgetInfo) v.getTag();
+        if (mWidgetCleanupState >= 0 && mWidgetLoadingId != -1) {
+            mLauncher.getAppWidgetHost().deleteAppWidgetId(mWidgetLoadingId);
+        }
+        if (mWidgetCleanupState == WIDGET_BOUND) {
+            removeCallbacks(mInflateWidgetRunnable);
+        } else if (mWidgetCleanupState == WIDGET_INFLATED) {
+            AppWidgetHostView widget = info.boundWidget;
+            int widgetId = widget.getAppWidgetId();
+            mLauncher.getAppWidgetHost().deleteAppWidgetId(widgetId);
+        }
+        mWidgetCleanupState = WIDGET_NO_CLEANUP_REQUIRED;
+        mWidgetLoadingId = -1;
+    }
+
     private void beginDraggingWidget(View v) {
+        mWidgetCleanupState = WIDGET_NO_CLEANUP_REQUIRED;
+        mWidgetLoadingId = -1;
+
         // Get the widget preview as the drag representation
         ImageView image = (ImageView) v.findViewById(R.id.widget_preview);
         PendingAddItemInfo createItemInfo = (PendingAddItemInfo) v.getTag();
@@ -547,13 +615,13 @@
         if (createItemInfo instanceof PendingAddWidgetInfo) {
             PendingAddWidgetInfo createWidgetInfo = (PendingAddWidgetInfo) createItemInfo;
             int[] spanXY = mLauncher.getSpanForWidget(createWidgetInfo, null);
+            int[] size = mLauncher.getWorkspace().estimateItemSize(spanXY[0],
+                    spanXY[1], createWidgetInfo, true);
             createItemInfo.spanX = spanXY[0];
             createItemInfo.spanY = spanXY[1];
 
-            int[] maxSize = mLauncher.getWorkspace().estimateItemSize(spanXY[0], spanXY[1],
-                    createWidgetInfo, true);
             preview = getWidgetPreview(createWidgetInfo.componentName, createWidgetInfo.previewImage,
-                    createWidgetInfo.icon, spanXY[0], spanXY[1], maxSize[0], maxSize[1]);
+                    createWidgetInfo.icon, spanXY[0], spanXY[1], size[0], size[1]);
         } else {
             // Workaround for the fact that we don't keep the original ResolveInfo associated with
             // the shortcut around.  To get the icon, we just render the preview image (which has
@@ -593,24 +661,33 @@
         outline.recycle();
         preview.recycle();
     }
+
     @Override
-    protected boolean beginDragging(View v) {
-        // Dismiss the cling
-        mLauncher.dismissAllAppsCling(null);
-
+    protected boolean beginDragging(final View v) {
         if (!super.beginDragging(v)) return false;
 
-        // Reset the alpha on the dragged icon before we drag
-        resetDrawableState();
-
-        // Go into spring loaded mode (must happen before we startDrag())
-        mLauncher.enterSpringLoadedDragMode();
-
         if (v instanceof PagedViewIcon) {
             beginDraggingApplication(v);
         } else if (v instanceof PagedViewWidget) {
             beginDraggingWidget(v);
         }
+
+        // We delay entering spring-loaded mode slightly to make sure the UI
+        // thready is free of any work.
+        postDelayed(new Runnable() {
+            @Override
+            public void run() {
+                // Dismiss the cling
+                mLauncher.dismissAllAppsCling(null);
+
+                // Reset the alpha on the dragged icon before we drag
+                resetDrawableState();
+
+                // Go into spring loaded mode (must happen before we startDrag())
+                mLauncher.enterSpringLoadedDragMode();
+            }
+        },150);
+
         return true;
     }
     private void endDragging(View target, boolean success) {
@@ -1045,6 +1122,7 @@
                 int[] cellSpans = mLauncher.getSpanForWidget(info, null);
                 widget.applyFromAppWidgetProviderInfo(info, -1, cellSpans);
                 widget.setTag(createItemInfo);
+                widget.setShortPressListener(this);
             } else if (rawInfo instanceof ResolveInfo) {
                 // Fill in the shortcuts information
                 ResolveInfo info = (ResolveInfo) rawInfo;
diff --git a/src/com/android/launcher2/DeleteDropTarget.java b/src/com/android/launcher2/DeleteDropTarget.java
index 3b82f9e..a6b2b5c 100644
--- a/src/com/android/launcher2/DeleteDropTarget.java
+++ b/src/com/android/launcher2/DeleteDropTarget.java
@@ -165,23 +165,30 @@
         }
     }
 
-    private void animateToTrashAndCompleteDrop(final DragObject d) {
+    Rect getDeleteRect(int deleteItemWidth, int deleteItemHeight) {
         DragLayer dragLayer = mLauncher.getDragLayer();
-        Rect from = new Rect();
-        Rect to = new Rect();
-        dragLayer.getViewRectRelativeToSelf(d.dragView, from);
-        dragLayer.getViewRectRelativeToSelf(this, to);
 
+        Rect to = new Rect();
+        dragLayer.getViewRectRelativeToSelf(this, to);
         int width = mCurrentDrawable.getIntrinsicWidth();
         int height = mCurrentDrawable.getIntrinsicHeight();
         to.set(to.left + getPaddingLeft(), to.top + getPaddingTop(),
                 to.left + getPaddingLeft() + width, to.bottom);
 
         // Center the destination rect about the trash icon
-        int xOffset = (int) -(d.dragView.getMeasuredWidth() - width) / 2;
-        int yOffset = (int) -(d.dragView.getMeasuredHeight() - height) / 2;
+        int xOffset = (int) -(deleteItemWidth - width) / 2;
+        int yOffset = (int) -(deleteItemHeight - height) / 2;
         to.offset(xOffset, yOffset);
 
+        return to;
+    }
+
+    private void animateToTrashAndCompleteDrop(final DragObject d) {
+        DragLayer dragLayer = mLauncher.getDragLayer();
+        Rect from = new Rect();
+        dragLayer.getViewRectRelativeToSelf(d.dragView, from);
+        Rect to = getDeleteRect(d.dragView.getMeasuredWidth(), d.dragView.getMeasuredHeight());
+
         mSearchDropTargetBar.deferOnDragEnd();
         Runnable onAnimationEndRunnable = new Runnable() {
             @Override
@@ -191,9 +198,10 @@
                 completeDrop(d);
             }
         };
-        dragLayer.animateView(d.dragView, from, to, 0.1f, 0.1f,
+        dragLayer.animateView(d.dragView, from, to, 0.1f, 1, 1, 0.1f, 0.1f,
                 DELETE_ANIMATION_DURATION, new DecelerateInterpolator(2),
-                new DecelerateInterpolator(1.5f), onAnimationEndRunnable, false, null);
+                new DecelerateInterpolator(1.5f), onAnimationEndRunnable,
+                DragLayer.ANIMATION_END_DISAPPEAR, null);
     }
 
     private void completeDrop(DragObject d) {
diff --git a/src/com/android/launcher2/DragLayer.java b/src/com/android/launcher2/DragLayer.java
index c315b60..6f3bcd1 100644
--- a/src/com/android/launcher2/DragLayer.java
+++ b/src/com/android/launcher2/DragLayer.java
@@ -67,12 +67,16 @@
     private View mAnchorView = null;
 
     private int[] mDropViewPos = new int[2];
-    private float mDropViewScale;
+    private float mDropViewScaleX;
+    private float mDropViewScaleY;
     private float mDropViewAlpha;
     private boolean mHoverPointClosesFolder = false;
     private Rect mHitRect = new Rect();
     private int mWorkspaceIndex = -1;
     private int mQsbIndex = -1;
+    public static final int ANIMATION_END_DISAPPEAR = 0;
+    public static final int ANIMATION_END_FADE_OUT = 1;
+    public static final int ANIMATION_END_REMAIN_VISIBLE = 2;
 
     /**
      * Used to create a new DragLayer from XML.
@@ -414,15 +418,23 @@
         animateViewIntoPosition(dragView, child, null);
     }
 
-    public void animateViewIntoPosition(DragView dragView, final int[] pos, float scale,
-            Runnable onFinishRunnable) {
+    public void animateViewIntoPosition(DragView dragView, final int[] pos, float scaleX, float
+            scaleY, int animationEndStyle, Runnable onFinishRunnable, int duration) {
         Rect r = new Rect();
         getViewRectRelativeToSelf(dragView, r);
         final int fromX = r.left;
         final int fromY = r.top;
 
-        animateViewIntoPosition(dragView, fromX, fromY, pos[0], pos[1], scale,
-                onFinishRunnable, true, -1, null);
+        animateViewIntoPosition(dragView, fromX, fromY, pos[0], pos[1], 1, 1, 1, scaleX, scaleY,
+                onFinishRunnable, animationEndStyle, duration, null);
+    }
+
+    public void scaleViewIntoPosition(DragView dragView, final int[] pos, float finalAlpha,
+            float scaleX, float scaleY, int animationEndStyle, Runnable onFinishRunnable,
+            int duration) {
+        animateViewIntoPosition(dragView, pos[0], pos[1], pos[0], pos[1], finalAlpha,
+                mDropViewScaleX, mDropViewScaleY, scaleX, scaleY, onFinishRunnable,
+                animationEndStyle, duration, null);
     }
 
     public void animateViewIntoPosition(DragView dragView, final View child,
@@ -486,18 +498,19 @@
                 oa.start();
             }
         };
-        animateViewIntoPosition(dragView, fromX, fromY, toX, toY, scale,
-                onCompleteRunnable, true, duration, anchorView);
+        animateViewIntoPosition(dragView, fromX, fromY, toX, toY, 1, 1, 1, scale, scale,
+                onCompleteRunnable, ANIMATION_END_FADE_OUT, duration, anchorView);
     }
 
     private void animateViewIntoPosition(final View view, final int fromX, final int fromY,
-            final int toX, final int toY, float finalScale, Runnable onCompleteRunnable,
-            boolean fadeOut, int duration, View anchorView) {
+            final int toX, final int toY, float finalAlpha, float initScaleX, float initScaleY,
+            float finalScaleX, float finalScaleY, Runnable onCompleteRunnable,
+            int animationEndStyle, int duration, View anchorView) {
         Rect from = new Rect(fromX, fromY, fromX +
                 view.getMeasuredWidth(), fromY + view.getMeasuredHeight());
         Rect to = new Rect(toX, toY, toX + view.getMeasuredWidth(), toY + view.getMeasuredHeight());
-        animateView(view, from, to, 1f, finalScale, duration, null, null,
-                onCompleteRunnable, true, anchorView);
+        animateView(view, from, to, finalAlpha, initScaleX, initScaleY, finalScaleX, finalScaleY, duration,
+                null, null, onCompleteRunnable, animationEndStyle, anchorView);
     }
 
     /**
@@ -522,9 +535,10 @@
      *        only used for the X dimension for the case of the workspace.
      */
     public void animateView(final View view, final Rect from, final Rect to, final float finalAlpha,
-            final float finalScale, int duration, final Interpolator motionInterpolator,
+            final float initScaleX, final float initScaleY, final float finalScaleX,
+            final float finalScaleY, int duration, final Interpolator motionInterpolator,
             final Interpolator alphaInterpolator, final Runnable onCompleteRunnable,
-            final boolean fadeOut, View anchorView) {
+            final int animationEndStyle, View anchorView) {
         // Calculate the duration of the animation based on the object's distance
         final float dist = (float) Math.sqrt(Math.pow(to.left - from.left, 2) +
                 Math.pow(to.top - from.top, 2));
@@ -578,10 +592,10 @@
 
                 mDropViewPos[0] = from.left + (int) Math.round(((to.left - from.left) * motionPercent));
                 mDropViewPos[1] = from.top + (int) Math.round(((to.top - from.top) * motionPercent));
-                mDropViewScale = percent * finalScale + (1 - percent);
+                mDropViewScaleX = percent * finalScaleX + (1 - percent) * initScaleX;
+                mDropViewScaleY = percent * finalScaleY + (1 - percent) * initScaleY;
                 mDropViewAlpha = alphaPercent * finalAlpha + (1 - alphaPercent) * initialAlpha;
-                invalidate(mDropViewPos[0], mDropViewPos[1],
-                        mDropViewPos[0] + width, mDropViewPos[1] + height);
+                invalidate();
             }
         });
         mDropAnim.addListener(new AnimatorListenerAdapter() {
@@ -589,16 +603,32 @@
                 if (onCompleteRunnable != null) {
                     onCompleteRunnable.run();
                 }
-                if (fadeOut) {
+                switch (animationEndStyle) {
+                case ANIMATION_END_DISAPPEAR:
+                    clearAnimatedView();
+                    break;
+                case ANIMATION_END_FADE_OUT:
                     fadeOutDragView();
-                } else {
-                    mDropView = null;
+                    break;
+                case ANIMATION_END_REMAIN_VISIBLE:
+                    break;
                 }
             }
         });
         mDropAnim.start();
     }
 
+    public void clearAnimatedView() {
+        mDropView = null;
+        mDropViewScaleX = 1;
+        mDropViewScaleY = 1;
+        invalidate();
+    }
+
+    public View getAnimatedView() {
+        return mDropView;
+    }
+
     private void fadeOutDragView() {
         mFadeOutAnim = new ValueAnimator();
         mFadeOutAnim.setDuration(150);
@@ -617,6 +647,7 @@
         mFadeOutAnim.addListener(new AnimatorListenerAdapter() {
             public void onAnimationEnd(Animator animation) {
                 mDropView = null;
+                invalidate();
             }
         });
         mFadeOutAnim.start();
@@ -679,8 +710,8 @@
             int width = mDropView.getMeasuredWidth();
             int height = mDropView.getMeasuredHeight();
             canvas.translate(xPos, yPos);
-            canvas.translate((1 - mDropViewScale) * width / 2, (1 - mDropViewScale) * height / 2);
-            canvas.scale(mDropViewScale, mDropViewScale);
+            canvas.translate((1 - mDropViewScaleX) * width / 2, (1 - mDropViewScaleY) * height / 2);
+            canvas.scale(mDropViewScaleX, mDropViewScaleY);
             mDropView.setAlpha(mDropViewAlpha);
             mDropView.draw(canvas);
             canvas.restore();
diff --git a/src/com/android/launcher2/DragView.java b/src/com/android/launcher2/DragView.java
index a3063b6..15d9c54 100644
--- a/src/com/android/launcher2/DragView.java
+++ b/src/com/android/launcher2/DragView.java
@@ -33,6 +33,7 @@
 
 public class DragView extends View {
     private Bitmap mBitmap;
+    private Bitmap mCrossFadeBitmap;
     private Paint mPaint;
     private int mRegistrationX;
     private int mRegistrationY;
@@ -41,6 +42,7 @@
     private Rect mDragRegion = null;
     private DragLayer mDragLayer = null;
     private boolean mHasDrawn = false;
+    private float mCrossFadeProgress = 0f;
 
     ValueAnimator mAnim;
     private float mOffsetX = 0.0f;
@@ -164,9 +166,43 @@
             p.setColor(0xaaffffff);
             canvas.drawRect(0, 0, getWidth(), getHeight(), p);
         }
+        if (mPaint == null) {
+            mPaint = new Paint();
+        }
 
         mHasDrawn = true;
+        boolean crossFade = mCrossFadeProgress > 0 && mCrossFadeBitmap != null;
+        if (crossFade) {
+            int alpha = crossFade ? (int) (255 * (1 - mCrossFadeProgress)) : 255;
+            mPaint.setAlpha(alpha);
+        }
         canvas.drawBitmap(mBitmap, 0.0f, 0.0f, mPaint);
+        if (crossFade) {
+            mPaint.setAlpha((int) (255 * mCrossFadeProgress));
+            canvas.save();
+            float sX = (mBitmap.getWidth() * 1.0f) / mCrossFadeBitmap.getWidth();
+            float sY = (mBitmap.getHeight() * 1.0f) / mCrossFadeBitmap.getHeight();
+            canvas.scale(sX, sY);
+            canvas.drawBitmap(mCrossFadeBitmap, 0.0f, 0.0f, mPaint);
+            canvas.restore();
+        }
+    }
+
+    public void setCrossFadeBitmap(Bitmap crossFadeBitmap) {
+        mCrossFadeBitmap = crossFadeBitmap;
+    }
+
+    public void crossFade(int duration) {
+        ValueAnimator va = ValueAnimator.ofFloat(0f, 1f);
+        va.setDuration(duration);
+        va.setInterpolator(new DecelerateInterpolator(1.5f));
+        va.addUpdateListener(new AnimatorUpdateListener() {
+            @Override
+            public void onAnimationUpdate(ValueAnimator animation) {
+                mCrossFadeProgress = animation.getAnimatedFraction();
+            }
+        });
+        va.start();
     }
 
     public void setPaint(Paint paint) {
diff --git a/src/com/android/launcher2/FolderIcon.java b/src/com/android/launcher2/FolderIcon.java
index 2a711f8..ca537d8 100644
--- a/src/com/android/launcher2/FolderIcon.java
+++ b/src/com/android/launcher2/FolderIcon.java
@@ -359,10 +359,11 @@
 
             float finalAlpha = index < NUM_ITEMS_IN_PREVIEW ? 0.5f : 0f;
 
+            float finalScale = scale * scaleRelativeToDragLayer;
             dragLayer.animateView(animateView, from, to, finalAlpha,
-                    scale * scaleRelativeToDragLayer, DROP_IN_ANIMATION_DURATION,
+                    1, 1, finalScale, finalScale, DROP_IN_ANIMATION_DURATION,
                     new DecelerateInterpolator(2), new AccelerateInterpolator(2),
-                    postAnimationRunnable, false, null);
+                    postAnimationRunnable, DragLayer.ANIMATION_END_DISAPPEAR, null);
             postDelayed(new Runnable() {
                 public void run() {
                     addItem(item);
diff --git a/src/com/android/launcher2/Launcher.java b/src/com/android/launcher2/Launcher.java
index 7d974a5..708d5d6 100644
--- a/src/com/android/launcher2/Launcher.java
+++ b/src/com/android/launcher2/Launcher.java
@@ -49,6 +49,7 @@
 import android.content.res.Configuration;
 import android.content.res.Resources;
 import android.database.ContentObserver;
+import android.graphics.Bitmap;
 import android.graphics.Rect;
 import android.graphics.drawable.Drawable;
 import android.net.Uri;
@@ -246,6 +247,7 @@
     private static Drawable.ConstantState[] sAppMarketIcon = new Drawable.ConstantState[2];
 
     static final ArrayList<String> sDumpLogs = new ArrayList<String>();
+    PendingAddWidgetInfo mWidgetBeingConfigured = null;
 
 
     private BubbleTextView mWaitingForResume;
@@ -498,7 +500,7 @@
                 break;
             case REQUEST_CREATE_APPWIDGET:
                 int appWidgetId = args.intent.getIntExtra(AppWidgetManager.EXTRA_APPWIDGET_ID, -1);
-                completeAddAppWidget(appWidgetId, args.container, args.screen);
+                completeAddAppWidget(appWidgetId, args.container, args.screen, null, null);
                 result = true;
                 break;
             case REQUEST_PICK_WALLPAPER:
@@ -509,10 +511,20 @@
     }
 
     @Override
-    protected void onActivityResult(final int requestCode, int resultCode, final Intent data) {
+    protected void onActivityResult(final int requestCode, final int resultCode, final Intent data) {
         boolean delayExitSpringLoadedMode = false;
+        boolean isWidgetDrop = (requestCode == REQUEST_PICK_APPWIDGET ||
+                requestCode == REQUEST_CREATE_APPWIDGET);
         mWaitingForResult = false;
 
+        // We have special handling for widgets
+        if (isWidgetDrop) {
+            int appWidgetId = data != null ?
+                    data.getIntExtra(AppWidgetManager.EXTRA_APPWIDGET_ID, -1) : -1;
+            completeTwoStageWidgetDrop(resultCode, appWidgetId);
+            return;
+        }
+
         // The pattern used here is that a user PICKs a specific application,
         // which, depending on the target, might need to CREATE the actual target.
 
@@ -526,26 +538,50 @@
             args.screen = mPendingAddInfo.screen;
             args.cellX = mPendingAddInfo.cellX;
             args.cellY = mPendingAddInfo.cellY;
-
-            // If the loader is still running, defer the add until it is done.
             if (isWorkspaceLocked()) {
                 sPendingAddList.add(args);
             } else {
                 delayExitSpringLoadedMode = completeAdd(args);
             }
-        } else if ((requestCode == REQUEST_PICK_APPWIDGET ||
-                requestCode == REQUEST_CREATE_APPWIDGET) && resultCode == RESULT_CANCELED) {
-            if (data != null) {
-                // Clean up the appWidgetId if we canceled
-                int appWidgetId = data.getIntExtra(AppWidgetManager.EXTRA_APPWIDGET_ID, -1);
-                if (appWidgetId != -1) {
-                    mAppWidgetHost.deleteAppWidgetId(appWidgetId);
-                }
-            }
         }
-
+        mDragLayer.clearAnimatedView();
         // Exit spring loaded mode if necessary after cancelling the configuration of a widget
-        exitSpringLoadedDragModeDelayed((resultCode != RESULT_CANCELED), delayExitSpringLoadedMode);
+        exitSpringLoadedDragModeDelayed((resultCode != RESULT_CANCELED), delayExitSpringLoadedMode,
+                null);
+    }
+
+    private void completeTwoStageWidgetDrop(final int resultCode, final int appWidgetId) {
+        CellLayout cellLayout = (CellLayout) mWorkspace.getChildAt(mWidgetBeingConfigured.screen);
+        Runnable onCompleteRunnable = null;
+        int animationType = 0;
+
+        if (resultCode == RESULT_OK) {
+            animationType = Workspace.COMPLETE_TWO_STAGE_WIDGET_DROP_ANIMATION;
+            final AppWidgetHostView layout = mAppWidgetHost.createView(this, appWidgetId,
+                    mWidgetBeingConfigured.info);
+            mWidgetBeingConfigured.boundWidget = layout;
+            onCompleteRunnable = new Runnable() {
+                @Override
+                public void run() {
+                    completeAddAppWidget(appWidgetId, mPendingAddInfo.container,
+                            mPendingAddInfo.screen, layout, null);
+                    exitSpringLoadedDragModeDelayed((resultCode != RESULT_CANCELED), false,
+                            null);
+                }
+            };
+        } else if (resultCode == RESULT_CANCELED) {
+            animationType = Workspace.CANCEL_TWO_STAGE_WIDGET_DROP_ANIMATION;
+            onCompleteRunnable = new Runnable() {
+                @Override
+                public void run() {
+                    exitSpringLoadedDragModeDelayed((resultCode != RESULT_CANCELED), false,
+                            null);
+                }
+            };
+        }
+        mWorkspace.animateExternalDrop(mWidgetBeingConfigured, cellLayout,
+                (DragView) mDragLayer.getAnimatedView(), onCompleteRunnable,
+                animationType);
     }
 
     @Override
@@ -934,8 +970,11 @@
      * @param appWidgetId The app widget id
      * @param cellInfo The position on screen where to create the widget.
      */
-    private void completeAddAppWidget(final int appWidgetId, long container, int screen) {
-        AppWidgetProviderInfo appWidgetInfo = mAppWidgetManager.getAppWidgetInfo(appWidgetId);
+    private void completeAddAppWidget(final int appWidgetId, long container, int screen,
+            AppWidgetHostView hostView, AppWidgetProviderInfo appWidgetInfo) {
+        if (appWidgetInfo == null) {
+            appWidgetInfo = mAppWidgetManager.getAppWidgetInfo(appWidgetId);
+        }
 
         // Calculate the grid spans needed to fit this widget
         CellLayout layout = getCellLayout(container, screen);
@@ -984,12 +1023,16 @@
                 container, screen, cellXY[0], cellXY[1], false);
 
         if (!mRestoring) {
-            // Perform actual inflation because we're live
-            launcherInfo.hostView = mAppWidgetHost.createView(this, appWidgetId, appWidgetInfo);
+            if (hostView == null) {
+                // Perform actual inflation because we're live
+                launcherInfo.hostView = mAppWidgetHost.createView(this, appWidgetId, appWidgetInfo);
+                launcherInfo.hostView.setAppWidget(appWidgetId, appWidgetInfo);
+            } else {
+                // The AppWidgetHostView has already been inflated and instantiated
+                launcherInfo.hostView = hostView;
+            }
 
-            launcherInfo.hostView.setAppWidget(appWidgetId, appWidgetInfo);
             launcherInfo.hostView.setTag(launcherInfo);
-
             mWorkspace.addInScreen(launcherInfo.hostView, container, screen, cellXY[0], cellXY[1],
                     launcherInfo.spanX, launcherInfo.spanY, isWorkspaceLocked());
 
@@ -1427,9 +1470,9 @@
         addAppWidgetImpl(appWidgetId, null);
     }
 
-    void addAppWidgetImpl(int appWidgetId, PendingAddWidgetInfo info) {
-        AppWidgetProviderInfo appWidget = mAppWidgetManager.getAppWidgetInfo(appWidgetId);
-
+    void addAppWidgetImpl(final int appWidgetId, final PendingAddWidgetInfo info) {
+        final AppWidgetProviderInfo appWidget = info.info;
+        Runnable configurationActivity = null;
         if (appWidget.configure != null) {
             // Launch over to configure widget, if needed
             Intent intent = new Intent(AppWidgetManager.ACTION_APPWIDGET_CONFIGURE);
@@ -1437,9 +1480,8 @@
             intent.putExtra(AppWidgetManager.EXTRA_APPWIDGET_ID, appWidgetId);
             if (info != null) {
                 if (info.mimeType != null && !info.mimeType.isEmpty()) {
-                    intent.putExtra(
-                            InstallWidgetReceiver.EXTRA_APPWIDGET_CONFIGURATION_DATA_MIME_TYPE,
-                            info.mimeType);
+                    intent.putExtra(InstallWidgetReceiver.
+                            EXTRA_APPWIDGET_CONFIGURATION_DATA_MIME_TYPE, info.mimeType);
 
                     final String mimeType = info.mimeType;
                     final ClipData clipData = (ClipData) info.configurationData;
@@ -1450,8 +1492,8 @@
                             final CharSequence stringData = item.getText();
                             final Uri uriData = item.getUri();
                             final Intent intentData = item.getIntent();
-                            final String key =
-                                InstallWidgetReceiver.EXTRA_APPWIDGET_CONFIGURATION_DATA;
+                            final String key = InstallWidgetReceiver.
+                                    EXTRA_APPWIDGET_CONFIGURATION_DATA;
                             if (uriData != null) {
                                 intent.putExtra(key, uriData);
                             } else if (intentData != null) {
@@ -1464,14 +1506,13 @@
                     }
                 }
             }
-
             startActivityForResultSafely(intent, REQUEST_CREATE_APPWIDGET);
+            mWidgetBeingConfigured = info;
         } else {
             // Otherwise just add it
-            completeAddAppWidget(appWidgetId, info.container, info.screen);
-
+            completeAddAppWidget(appWidgetId, info.container, info.screen, info.boundWidget, appWidget);
             // Exit spring loaded mode if necessary after adding the widget
-            exitSpringLoadedDragModeDelayed(true, false);
+            exitSpringLoadedDragModeDelayed(true, false, null);
         }
     }
 
@@ -1519,9 +1560,16 @@
             mPendingAddInfo.cellY = cell[1];
         }
 
-        int appWidgetId = getAppWidgetHost().allocateAppWidgetId();
-        AppWidgetManager.getInstance(this).bindAppWidgetId(appWidgetId, info.componentName);
+        AppWidgetHostView hostView = info.boundWidget;
+        int appWidgetId;
+        if (hostView != null) {
+            appWidgetId = hostView.getAppWidgetId();
+        } else {
+            appWidgetId = getAppWidgetHost().allocateAppWidgetId();
+            AppWidgetManager.getInstance(this).bindAppWidgetId(appWidgetId, info.componentName);
+        }
         addAppWidgetImpl(appWidgetId, info);
+
     }
 
     void processShortcut(Intent intent) {
@@ -2368,8 +2416,9 @@
      * This is the opposite of showAppsCustomizeHelper.
      * @param animated If true, the transition will be animated.
      */
-    private void hideAppsCustomizeHelper(
-            State toState, final boolean animated, final boolean springLoaded) {
+    private void hideAppsCustomizeHelper(State toState, final boolean animated,
+            final boolean springLoaded, final Runnable onCompleteRunnable) {
+
         if (mStateAnimation != null) {
             mStateAnimation.cancel();
             mStateAnimation = null;
@@ -2426,6 +2475,9 @@
                     dispatchOnLauncherTransitionEnd(fromView, animated, true);
                     dispatchOnLauncherTransitionEnd(toView, animated, true);
                     mWorkspace.hideScrollingIndicator(false);
+                    if (onCompleteRunnable != null) {
+                        onCompleteRunnable.run();
+                    }
                 }
             });
 
@@ -2453,9 +2505,13 @@
     }
 
     void showWorkspace(boolean animated) {
+        showWorkspace(animated, null);
+    }
+
+    void showWorkspace(boolean animated, Runnable onCompleteRunnable) {
         if (mState != State.WORKSPACE) {
             mWorkspace.setVisibility(View.VISIBLE);
-            hideAppsCustomizeHelper(State.WORKSPACE, animated, false);
+            hideAppsCustomizeHelper(State.WORKSPACE, animated, false, onCompleteRunnable);
 
             // Show the search bar and hotseat
             mSearchDropTargetBar.showSearchBar(animated);
@@ -2504,13 +2560,14 @@
 
     void enterSpringLoadedDragMode() {
         if (mState == State.APPS_CUSTOMIZE) {
-            hideAppsCustomizeHelper(State.APPS_CUSTOMIZE_SPRING_LOADED, true, true);
+            hideAppsCustomizeHelper(State.APPS_CUSTOMIZE_SPRING_LOADED, true, true, null);
             hideDockDivider();
             mState = State.APPS_CUSTOMIZE_SPRING_LOADED;
         }
     }
 
-    void exitSpringLoadedDragModeDelayed(final boolean successfulDrop, boolean extendedDelay) {
+    void exitSpringLoadedDragModeDelayed(final boolean successfulDrop, boolean extendedDelay,
+            final Runnable onCompleteRunnable) {
         if (mState != State.APPS_CUSTOMIZE_SPRING_LOADED) return;
 
         mHandler.postDelayed(new Runnable() {
@@ -2522,7 +2579,7 @@
                     // clean up our state transition functions
                     mAppsCustomizeTabHost.setVisibility(View.GONE);
                     mSearchDropTargetBar.showSearchBar(true);
-                    showWorkspace(true);
+                    showWorkspace(true, onCompleteRunnable);
                 } else {
                     exitSpringLoadedDragMode();
                 }
diff --git a/src/com/android/launcher2/PagedViewWidget.java b/src/com/android/launcher2/PagedViewWidget.java
index 12e9c46..5ba8691 100644
--- a/src/com/android/launcher2/PagedViewWidget.java
+++ b/src/com/android/launcher2/PagedViewWidget.java
@@ -23,6 +23,7 @@
 import android.content.res.Resources;
 import android.util.AttributeSet;
 import android.view.MotionEvent;
+import android.view.View;
 import android.widget.ImageView;
 import android.widget.LinearLayout;
 import android.widget.TextView;
@@ -40,6 +41,9 @@
     private ImageView mPreviewImageView;
 
     private String mDimensionsFormatString;
+    CheckForShortPress mPendingCheckForShortPress = null;
+    ShortPressListener mShortPressListener = null;
+    boolean mShortPressTriggered = false;
 
     public PagedViewWidget(Context context) {
         this(context, null);
@@ -127,8 +131,67 @@
         }
     }
 
+    void setShortPressListener(ShortPressListener listener) {
+        mShortPressListener = listener;
+    }
+
+    interface ShortPressListener {
+        void onShortPress(View v);
+        void cleanUpShortPress(View v);
+    }
+
+    class CheckForShortPress implements Runnable {
+        public void run() {
+            if (mShortPressListener != null) {
+                mShortPressListener.onShortPress(PagedViewWidget.this);
+            }
+            mShortPressTriggered = true;
+        }
+    }
+
+    private void checkForShortPress() {
+        if (mPendingCheckForShortPress == null) {
+            mPendingCheckForShortPress = new CheckForShortPress();
+        }
+        postDelayed(mPendingCheckForShortPress, 120);
+    }
+
+    /**
+     * Remove the longpress detection timer.
+     */
+    private void removeShortPressCallback() {
+        if (mPendingCheckForShortPress != null) {
+          removeCallbacks(mPendingCheckForShortPress);
+        }
+    }
+
+    private void cleanUpShortPress() {
+        removeShortPressCallback();
+        if (mShortPressTriggered) {
+            if (mShortPressListener != null) {
+                mShortPressListener.cleanUpShortPress(PagedViewWidget.this);
+            }
+            mShortPressTriggered = false;
+        }
+    }
+
     @Override
     public boolean onTouchEvent(MotionEvent event) {
+        super.onTouchEvent(event);
+
+        switch (event.getAction()) {
+            case MotionEvent.ACTION_UP:
+                cleanUpShortPress();
+                break;
+            case MotionEvent.ACTION_DOWN:
+                checkForShortPress();
+                break;
+            case MotionEvent.ACTION_CANCEL:
+                cleanUpShortPress();
+                break;
+            case MotionEvent.ACTION_MOVE:
+                break;
+        }
         // We eat up the touch events here, since the PagedView (which uses the same swiping
         // touch code as Workspace previously) uses onInterceptTouchEvent() to determine when
         // the user is scrolling between pages.  This means that if the pages themselves don't
@@ -136,6 +199,6 @@
         // onTouchEvent() handling will prevent further intercept touch events from being called
         // (it's the same view in that case).  This is not ideal, but to prevent more changes,
         // we just always mark the touch event as handled.
-        return super.onTouchEvent(event) || true;
+        return true;
     }
 }
diff --git a/src/com/android/launcher2/PendingAddItemInfo.java b/src/com/android/launcher2/PendingAddItemInfo.java
index 9c52ecf..851dddb 100644
--- a/src/com/android/launcher2/PendingAddItemInfo.java
+++ b/src/com/android/launcher2/PendingAddItemInfo.java
@@ -16,6 +16,7 @@
 
 package com.android.launcher2;
 
+import android.appwidget.AppWidgetHostView;
 import android.appwidget.AppWidgetProviderInfo;
 import android.content.ComponentName;
 import android.os.Parcelable;
@@ -35,6 +36,8 @@
     int minHeight;
     int previewImage;
     int icon;
+    AppWidgetProviderInfo info;
+    AppWidgetHostView boundWidget;
 
     // Any configuration data that we want to pass to a configuration activity when
     // starting up a widget
@@ -43,6 +46,7 @@
 
     public PendingAddWidgetInfo(AppWidgetProviderInfo i, String dataMimeType, Parcelable data) {
         itemType = LauncherSettings.Favorites.ITEM_TYPE_APPWIDGET;
+        this.info = i;
         componentName = i.provider;
         minWidth = i.minWidth;
         minHeight = i.minHeight;
diff --git a/src/com/android/launcher2/Workspace.java b/src/com/android/launcher2/Workspace.java
index 746c682..c8cab16 100644
--- a/src/com/android/launcher2/Workspace.java
+++ b/src/com/android/launcher2/Workspace.java
@@ -56,6 +56,7 @@
 import android.view.MotionEvent;
 import android.view.View;
 import android.view.ViewGroup;
+import android.view.View.MeasureSpec;
 import android.view.animation.DecelerateInterpolator;
 import android.widget.ImageView;
 import android.widget.TextView;
@@ -204,6 +205,11 @@
     final static float MAX_SWIPE_ANGLE = (float) Math.PI / 3;
     final static float TOUCH_SLOP_DAMPING_FACTOR = 4;
 
+    // Relating to the animation of items being dropped externally
+    public static final int ANIMATE_INTO_POSITION = 0;
+    public static final int COMPLETE_TWO_STAGE_WIDGET_DROP_ANIMATION = 1;
+    public static final int CANCEL_TWO_STAGE_WIDGET_DROP_ANIMATION = 2;
+
     // These variables are used for storing the initial and final values during workspace animations
     private int mSavedScrollX;
     private float mSavedRotationY;
@@ -334,15 +340,6 @@
             int hCell, int vCell, int hSpan, int vSpan) {
         RectF r = new RectF();
         cl.cellToRect(hCell, vCell, hSpan, vSpan, r);
-        if (pendingInfo instanceof PendingAddWidgetInfo) {
-            PendingAddWidgetInfo widgetInfo = (PendingAddWidgetInfo) pendingInfo;
-            Rect p = AppWidgetHostView.getDefaultPaddingForWidget(mContext,
-                    widgetInfo.componentName, null);
-            r.top += p.top;
-            r.left += p.left;
-            r.right -= p.right;
-            r.bottom -= p.bottom;
-        }
         return r;
     }
 
@@ -502,10 +499,12 @@
             child.setOnKeyListener(new IconKeyEventListener());
         }
 
-        CellLayout.LayoutParams lp = (CellLayout.LayoutParams) child.getLayoutParams();
-        if (lp == null) {
+        LayoutParams genericLp = child.getLayoutParams();
+        CellLayout.LayoutParams lp;
+        if (genericLp == null || !(genericLp instanceof CellLayout.LayoutParams)) {
             lp = new CellLayout.LayoutParams(x, y, spanX, spanY);
         } else {
+            lp = (CellLayout.LayoutParams) genericLp;
             lp.cellX = x;
             lp.cellY = y;
             lp.cellHSpan = spanX;
@@ -2869,7 +2868,7 @@
         final Runnable exitSpringLoadedRunnable = new Runnable() {
             @Override
             public void run() {
-                mLauncher.exitSpringLoadedDragModeDelayed(true, false);
+                mLauncher.exitSpringLoadedDragModeDelayed(true, false, null);
             }
         };
 
@@ -2930,27 +2929,8 @@
                 }
             };
 
-            // Now we animate the dragView, (ie. the widget or shortcut preview) into its final
-            // location and size on the home screen.
-            RectF r = estimateItemPosition(cellLayout, pendingInfo,
-                    mTargetCell[0], mTargetCell[1], spanX, spanY);
-            int loc[] = new int[2];
-            loc[0] = (int) r.left;
-            loc[1] = (int) r.top;
-            setFinalTransitionTransform(cellLayout);
-            float cellLayoutScale =
-                    mLauncher.getDragLayer().getDescendantCoordRelativeToSelf(cellLayout, loc);
-            resetTransitionTransform(cellLayout);
-
-            float dragViewScale =  Math.min(r.width() / d.dragView.getMeasuredWidth(),
-                    r.height() / d.dragView.getMeasuredHeight());
-            // The animation will scale the dragView about its center, so we need to center about
-            // the final location.
-            loc[0] -= (d.dragView.getMeasuredWidth() - cellLayoutScale * r.width()) / 2;
-            loc[1] -= (d.dragView.getMeasuredHeight() - cellLayoutScale * r.height()) / 2;
-
-            mLauncher.getDragLayer().animateViewIntoPosition(d.dragView, loc,
-                    dragViewScale * cellLayoutScale, onAnimationCompleteRunnable);
+            animateExternalDrop((PendingAddItemInfo) info, cellLayout, d.dragView,
+                    onAnimationCompleteRunnable, ANIMATE_INTO_POSITION);
         } else {
             // This is for other drag/drop cases, like dragging from All Apps
             View view = null;
@@ -3016,6 +2996,92 @@
         }
     }
 
+    // The following methods deal with animating an item from external drop
+    void onPreDraw(View v) {
+        if (v instanceof ViewGroup) {
+            ViewGroup vg = (ViewGroup) v;
+            for (int i = 0; i < vg.getChildCount(); i++) {
+                View child = vg.getChildAt(i);
+                onPreDraw(child);
+            }
+        } else if (v instanceof TextView) {
+            ((TextView) v).onPreDraw();
+        }
+    }
+
+    public Bitmap createWidgetBitmap(PendingAddWidgetInfo widgetInfo) {
+        int[] unScaledSize = mLauncher.getWorkspace().estimateItemSize(widgetInfo.spanX,
+                widgetInfo.spanY, widgetInfo, false);
+        View layout = widgetInfo.boundWidget;
+        layout.setVisibility(VISIBLE);
+
+        int width = MeasureSpec.makeMeasureSpec(unScaledSize[0], MeasureSpec.EXACTLY);
+        int height = MeasureSpec.makeMeasureSpec(unScaledSize[1], MeasureSpec.EXACTLY);
+        Bitmap b = Bitmap.createBitmap(unScaledSize[0], unScaledSize[1],
+                Bitmap.Config.ARGB_8888);
+        Canvas c = new Canvas(b);
+
+        layout.measure(width, height);
+        layout.layout(0, 0, unScaledSize[0], unScaledSize[1]);
+        onPreDraw(layout);
+        layout.draw(c);
+        c.setBitmap(null);
+        return b;
+    }
+
+    public void animateExternalDrop(PendingAddItemInfo pendingInfo, CellLayout cellLayout,
+            DragView dragView, Runnable onCompleteRunnable, int animationType) {
+        // Now we animate the dragView, (ie. the widget or shortcut preview) into its final
+        // location and size on the home screen.
+        int spanX = pendingInfo.spanX;
+        int spanY = pendingInfo.spanY;
+        RectF r = estimateItemPosition(cellLayout, pendingInfo,
+                mTargetCell[0], mTargetCell[1], spanX, spanY);
+        int loc[] = new int[2];
+        loc[0] = (int) r.left;
+        loc[1] = (int) r.top;
+        setFinalTransitionTransform(cellLayout);
+        float cellLayoutScale =
+                mLauncher.getDragLayer().getDescendantCoordRelativeToSelf(cellLayout, loc);
+        resetTransitionTransform(cellLayout);
+
+        float dragViewScaleX = r.width() / dragView.getMeasuredWidth();
+        float dragViewScaleY = r.height() / dragView.getMeasuredHeight();
+        // The animation will scale the dragView about its center, so we need to center about
+        // the final location.
+        loc[0] -= (dragView.getMeasuredWidth() - cellLayoutScale * r.width()) / 2;
+        loc[1] -= (dragView.getMeasuredHeight() - cellLayoutScale * r.height()) / 2;
+
+        float scaleX = dragViewScaleX * cellLayoutScale;
+        float scaleY = dragViewScaleY * cellLayoutScale;
+
+        Resources res = mLauncher.getResources();
+        int duration = res.getInteger(R.integer.config_dropAnimMaxDuration) - 200;
+
+        int animationEnd = DragLayer.ANIMATION_END_REMAIN_VISIBLE;
+        if (pendingInfo.itemType == LauncherSettings.Favorites.ITEM_TYPE_APPWIDGET &&
+                (((PendingAddWidgetInfo) pendingInfo).info.configure == null ||
+                animationType == COMPLETE_TWO_STAGE_WIDGET_DROP_ANIMATION)) {
+            Bitmap crossFadeBitmap = createWidgetBitmap((PendingAddWidgetInfo) pendingInfo);
+            dragView.setCrossFadeBitmap(crossFadeBitmap);
+            dragView.crossFade((int) (duration * 0.8f));
+            animationEnd = DragLayer.ANIMATION_END_DISAPPEAR;
+        } else {
+            scaleX = scaleY = Math.min(scaleX,  scaleY);
+        }
+
+        if (animationType == COMPLETE_TWO_STAGE_WIDGET_DROP_ANIMATION) {
+            mLauncher.getDragLayer().scaleViewIntoPosition(dragView, loc, 1, scaleX, scaleY,
+                    animationEnd, onCompleteRunnable, duration);
+        } else if (animationType == CANCEL_TWO_STAGE_WIDGET_DROP_ANIMATION) {
+            mLauncher.getDragLayer().scaleViewIntoPosition(dragView, loc, 0, 0.1f, 0.1f,
+                    DragLayer.ANIMATION_END_DISAPPEAR, onCompleteRunnable, duration);
+        } else {
+            mLauncher.getDragLayer().animateViewIntoPosition(dragView, loc, scaleX, scaleY,
+                animationEnd, onCompleteRunnable, duration);
+        }
+    }
+
     public void setFinalTransitionTransform(CellLayout layout) {
         if (isSwitchingState()) {
             int index = indexOfChild(layout);