Merge "Pixel perfect drag animations in workspace and folders, including springloaded"
diff --git a/src/com/android/launcher2/AppsCustomizePagedView.java b/src/com/android/launcher2/AppsCustomizePagedView.java
index 12fe971..8a4bdc7 100644
--- a/src/com/android/launcher2/AppsCustomizePagedView.java
+++ b/src/com/android/launcher2/AppsCustomizePagedView.java
@@ -385,37 +385,12 @@
     protected void determineDraggingStart(android.view.MotionEvent ev) {
         // Disable dragging by pulling an app down for now.
     }
+
     private void beginDraggingApplication(View v) {
-        // Make a copy of the ApplicationInfo
-        ApplicationInfo appInfo = new ApplicationInfo((ApplicationInfo) v.getTag());
-
-        // Compose the drag image (top compound drawable, index is 1)
-        final TextView tv = (TextView) v;
-        final Drawable icon = tv.getCompoundDrawables()[1];
-        Bitmap b = Bitmap.createBitmap(v.getWidth(), v.getHeight(),
-                Bitmap.Config.ARGB_8888);
-        mCanvas.setBitmap(b);
-        mCanvas.save();
-        mCanvas.translate((v.getWidth() - icon.getIntrinsicWidth()) / 2, v.getPaddingTop());
-        icon.draw(mCanvas);
-        mCanvas.restore();
-
-        // Compose the visible rect of the drag image
-        Rect dragRect = null;
-        if (v instanceof TextView) {
-            int top = v.getPaddingTop();
-            int left = (b.getWidth() - mAppIconSize) / 2;
-            int right = left + mAppIconSize;
-            int bottom = top + mAppIconSize;
-            dragRect = new Rect(left, top, right, bottom);
-        }
-
-        // Start the drag
-        mLauncher.lockScreenOrientation();
-        mLauncher.getWorkspace().onDragStartedWithItemSpans(1, 1, b);
-        mDragController.startDrag(v, b, this, appInfo, DragController.DRAG_ACTION_COPY, dragRect);
-        b.recycle();
+        mLauncher.getWorkspace().onDragStartedWithItem(v);
+        mLauncher.getWorkspace().beginDragShared(v, this);
     }
+
     private void beginDraggingWidget(View v) {
         // Get the widget preview as the drag representation
         ImageView image = (ImageView) v.findViewById(R.id.widget_preview);
diff --git a/src/com/android/launcher2/DragLayer.java b/src/com/android/launcher2/DragLayer.java
index 0bdfec1..37abe5a 100644
--- a/src/com/android/launcher2/DragLayer.java
+++ b/src/com/android/launcher2/DragLayer.java
@@ -26,6 +26,7 @@
 import android.content.res.Resources;
 import android.graphics.Canvas;
 import android.graphics.Rect;
+import android.graphics.drawable.Drawable;
 import android.util.AttributeSet;
 import android.view.KeyEvent;
 import android.view.MotionEvent;
@@ -34,6 +35,7 @@
 import android.view.animation.DecelerateInterpolator;
 import android.view.animation.Interpolator;
 import android.widget.FrameLayout;
+import android.widget.TextView;
 
 import com.android.launcher.R;
 
@@ -167,12 +169,13 @@
         return mDragController.onTouchEvent(ev);
     }
 
-    public void getDescendantRectRelativeToSelf(View descendant, Rect r) {
+    public float getDescendantRectRelativeToSelf(View descendant, Rect r) {
         mTmpXY[0] = 0;
         mTmpXY[1] = 0;
-        getDescendantCoordRelativeToSelf(descendant, mTmpXY);
+        float scale = getDescendantCoordRelativeToSelf(descendant, mTmpXY);
         r.set(mTmpXY[0], mTmpXY[1],
                 mTmpXY[0] + descendant.getWidth(), mTmpXY[1] + descendant.getHeight());
+        return scale;
     }
 
     private float getDescendantCoordRelativeToSelf(View descendant, int[] coord) {
@@ -315,6 +318,7 @@
     public void animateViewIntoPosition(DragView dragView, final View child) {
         animateViewIntoPosition(dragView, child, null);
     }
+
     public void animateViewIntoPosition(DragView dragView, final View child,
             final Runnable onFinishAnimationRunnable) {
         ((CellLayoutChildren) child.getParent()).measureChild(child);
@@ -324,18 +328,30 @@
         getViewRectRelativeToSelf(dragView, r);
 
         int coord[] = new int[2];
-        coord[0] = lp.x + (lp.width / 2);
-        coord[1] = lp.y + (lp.height / 2);
+        coord[0] = lp.x;
+        coord[1] = lp.y;
         // Since the child hasn't necessarily been laid out, we force the lp to be updated with
-        // the correct coordinates and use these to determine the final location
+        // the correct coordinates (above) and use these to determine the final location
         float scale = getDescendantCoordRelativeToSelf((View) child.getParent(), coord);
-        int toX = coord[0] - lp.width / 2;
-        int toY = coord[1] - lp.height / 2;
-        toX -= (dragView.getWidth() - child.getMeasuredWidth()) / 2;
-        toY -= (dragView.getHeight() - child.getMeasuredHeight()) / 2;
+        int toX = coord[0];
+        int toY = coord[1];
+        if (child instanceof TextView) {
+            TextView tv = (TextView) child;
+            Drawable d = tv.getCompoundDrawables()[1];
 
-        final int fromX = r.left + (dragView.getWidth() - child.getMeasuredWidth())  / 2;
-        final int fromY = r.top + (dragView.getHeight() - child.getMeasuredHeight())  / 2;
+            // Center in the y coordinate about the target's drawable
+            toY += Math.round(scale * tv.getPaddingTop());
+            toY -= (dragView.getHeight() - (int) Math.round(scale * d.getIntrinsicHeight())) / 2;
+            // Center in the x coordinate about the target's drawable
+            toX -= (dragView.getMeasuredWidth() - Math.round(scale * child.getMeasuredWidth())) / 2;
+        } else {
+            toY -= (Math.round(scale * (dragView.getHeight() - child.getMeasuredHeight()))) / 2;
+            toX -= (Math.round(scale * (dragView.getMeasuredWidth()
+                    - child.getMeasuredWidth()))) / 2;
+        }
+
+        final int fromX = r.left;
+        final int fromY = r.top;
         child.setVisibility(INVISIBLE);
         child.setAlpha(0);
         Runnable onCompleteRunnable = new Runnable() {
@@ -400,6 +416,10 @@
             mDropAnim.cancel();
         }
 
+        if (mFadeOutAnim != null) {
+            mFadeOutAnim.cancel();
+        }
+
         mDropView = view;
         final float initialAlpha = view.getAlpha();
         mDropAnim = new ValueAnimator();
@@ -424,8 +444,8 @@
                 float motionPercent = motionInterpolator == null ? percent :
                         motionInterpolator.getInterpolation(percent);
 
-                mDropViewPos[0] = from.left + (int) ((to.left - from.left) * motionPercent);
-                mDropViewPos[1] = from.top + (int) ((to.top - from.top) * motionPercent);
+                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);
                 mDropViewAlpha = alphaPercent * finalAlpha + (1 - alphaPercent) * initialAlpha;
                 invalidate(mDropViewPos[0], mDropViewPos[1],
diff --git a/src/com/android/launcher2/Folder.java b/src/com/android/launcher2/Folder.java
index 78df80f..dd0bffd 100644
--- a/src/com/android/launcher2/Folder.java
+++ b/src/com/android/launcher2/Folder.java
@@ -90,6 +90,7 @@
     private int[] mTargetCell = new int[2];
     private int[] mPreviousTargetCell = new int[2];
     private int[] mEmptyCell = new int[2];
+    private int[] mTempXY = new int[2];
     private Alarm mReorderAlarm = new Alarm();
     private Alarm mOnExitAlarm = new Alarm();
     private TextView mFolderName;
@@ -205,14 +206,13 @@
             }
 
             mLauncher.getWorkspace().onDragStartedWithItem(v);
-            mDragController.startDrag(v, this, item, DragController.DRAG_ACTION_COPY);
+            mLauncher.getWorkspace().beginDragShared(v, this);
             mIconDrawable = ((TextView) v).getCompoundDrawables()[1];
 
             mCurrentDragInfo = item;
             mEmptyCell[0] = item.cellX;
             mEmptyCell[1] = item.cellY;
             mCurrentDragView = v;
-
         }
         return true;
     }
@@ -270,18 +270,6 @@
         return true;
     }
 
-    public boolean onItemLongClick(AdapterView<?> parent, View view, int position, long id) {
-        if (!view.isInTouchMode()) {
-            return false;
-        }
-
-        ShortcutInfo app = (ShortcutInfo) parent.getItemAtPosition(position);
-
-        mDragController.startDrag(view, this, app, DragController.DRAG_ACTION_COPY);
-        mLauncher.closeFolder(this);
-        return true;
-    }
-
     public void setDragController(DragController dragController) {
         mDragController = dragController;
     }
diff --git a/src/com/android/launcher2/FolderIcon.java b/src/com/android/launcher2/FolderIcon.java
index 952916d..93f56fb 100644
--- a/src/com/android/launcher2/FolderIcon.java
+++ b/src/com/android/launcher2/FolderIcon.java
@@ -24,6 +24,7 @@
 import android.content.res.Resources;
 import android.graphics.Canvas;
 import android.graphics.Color;
+import android.graphics.Paint;
 import android.graphics.PorterDuff;
 import android.graphics.Rect;
 import android.graphics.drawable.Drawable;
@@ -286,12 +287,13 @@
 
     public void performCreateAnimation(final ShortcutInfo destInfo, final View destView,
             final ShortcutInfo srcInfo, final View srcView, Rect dstRect,
-            Runnable postAnimationRunnable) {
+            float scaleRelativeToDragLayer, Runnable postAnimationRunnable) {
 
         Drawable animateDrawable = ((TextView) destView).getCompoundDrawables()[1];
         computePreviewDrawingParams(animateDrawable.getIntrinsicWidth(), destView.getMeasuredWidth());
+
         // This will animate the dragView (srcView) into the new folder
-        onDrop(srcInfo, srcView, dstRect, 1, postAnimationRunnable);
+        onDrop(srcInfo, srcView, dstRect, scaleRelativeToDragLayer, 1, postAnimationRunnable);
 
         // This will animate the first item from it's position as an icon into its
         // position as the first item in the preview
@@ -309,8 +311,8 @@
         mFolderRingAnimator.animateToNaturalState();
     }
 
-    private void onDrop(final ShortcutInfo item, View animateView, Rect finalRect, int index,
-            Runnable postAnimationRunnable) {
+    private void onDrop(final ShortcutInfo item, View animateView, Rect finalRect,
+            float scaleRelativeToDragLayer, int index, Runnable postAnimationRunnable) {
         item.cellX = -1;
         item.cellY = -1;
         DragLayer dragLayer = mLauncher.getDragLayer();
@@ -319,22 +321,22 @@
         Rect to = finalRect;
         if (to == null) {
             to = new Rect();
-            dragLayer.getDescendantRectRelativeToSelf(this, to);
+            scaleRelativeToDragLayer = dragLayer.getDescendantRectRelativeToSelf(this, to);
         }
 
-        if (animateView.getMeasuredWidth() != to.width() ||
-                animateView.getMeasuredHeight() != to.height()) {
-            int offsetX = (animateView.getMeasuredWidth() - to.width()) / 2;
-            int offsetY = (animateView.getMeasuredHeight() - to.height()) / 2;
-            to.offset(-offsetX, -offsetY);
-        }
-        float scale = adjustFinalScreenRectForIndex(to, index);
+        int[] center = new int[2];
+        float scale = getLocalCenterForIndex(index, center);
+        center[0] = (int) Math.round(scaleRelativeToDragLayer * center[0]);
+        center[1] = (int) Math.round(scaleRelativeToDragLayer * center[1]);
+
+        to.offset(center[0] - animateView.getMeasuredWidth() / 2,
+                center[1] - animateView.getMeasuredHeight() / 2);
 
         float finalAlpha = index < NUM_ITEMS_IN_PREVIEW ? 0.5f : 0f;
 
-        dragLayer.animateView(animateView, from, to, finalAlpha, scale, DROP_IN_ANIMATION_DURATION,
-                new DecelerateInterpolator(2), new AccelerateInterpolator(2), postAnimationRunnable,
-                false);
+        dragLayer.animateView(animateView, from, to, finalAlpha, scale * scaleRelativeToDragLayer,
+                DROP_IN_ANIMATION_DURATION, new DecelerateInterpolator(2),
+                new AccelerateInterpolator(2), postAnimationRunnable, false);
         postDelayed(new Runnable() {
             public void run() {
                 addItem(item);
@@ -350,7 +352,7 @@
         } else {
             item = (ShortcutInfo) d.dragInfo;
         }
-        onDrop(item, d.dragView, null, mInfo.contents.size(), d.postAnimationRunnable);
+        onDrop(item, d.dragView, null, 1.0f, mInfo.contents.size(), d.postAnimationRunnable);
     }
 
     public DropTarget getDropTargetDelegate(DragObject d) {
@@ -398,15 +400,16 @@
         Drawable drawable;
     }
 
-    private float adjustFinalScreenRectForIndex(Rect r, int index) {
+    private float getLocalCenterForIndex(int index, int[] center) {
         mParams = computePreviewItemDrawingParams(Math.min(NUM_ITEMS_IN_PREVIEW, index), mParams);
 
         mParams.transX += mPreviewOffsetX;
         mParams.transY += mPreviewOffsetY;
-        float offsetX = mParams.transX + (mParams.scale * mIntrinsicIconSize) / 2 - mTotalWidth / 2;
-        float offsetY = mParams.transY + (mParams.scale * mIntrinsicIconSize) / 2 - mTotalWidth / 2;
+        float offsetX = mParams.transX + (mParams.scale * mIntrinsicIconSize) / 2;
+        float offsetY = mParams.transY + (mParams.scale * mIntrinsicIconSize) / 2;
 
-        r.offset((int) offsetX, (int) offsetY);
+        center[0] = (int) Math.round(offsetX);
+        center[1] = (int) Math.round(offsetY);
         return mParams.scale;
     }
 
diff --git a/src/com/android/launcher2/Workspace.java b/src/com/android/launcher2/Workspace.java
index 4b38a43..701b33b 100644
--- a/src/com/android/launcher2/Workspace.java
+++ b/src/com/android/launcher2/Workspace.java
@@ -39,6 +39,7 @@
 import android.graphics.Bitmap;
 import android.graphics.Camera;
 import android.graphics.Canvas;
+import android.graphics.Color;
 import android.graphics.Matrix;
 import android.graphics.Paint;
 import android.graphics.Rect;
@@ -1828,32 +1829,57 @@
      * @param destCanvas the canvas to draw on
      * @param padding the horizontal and vertical padding to use when drawing
      */
-    private void drawDragView(View v, Canvas destCanvas, int padding) {
+    private void drawDragView(View v, Canvas destCanvas, int padding, boolean pruneToDrawable) {
         final Rect clipRect = mTempRect;
         v.getDrawingRect(clipRect);
 
-        // For a TextView, adjust the clip rect so that we don't include the text label
-        if (v instanceof BubbleTextView) {
-            final BubbleTextView tv = (BubbleTextView) v;
-            clipRect.bottom = tv.getExtendedPaddingTop() - (int) BubbleTextView.PADDING_V +
-                    tv.getLayout().getLineTop(0);
-        } else if (v instanceof TextView) {
-            final TextView tv = (TextView) v;
-            clipRect.bottom = tv.getExtendedPaddingTop() - tv.getCompoundDrawablePadding() +
-                    tv.getLayout().getLineTop(0);
-        } else if (v instanceof FolderIcon) {
-            clipRect.bottom = getResources().getDimensionPixelSize(R.dimen.folder_preview_size);
+        destCanvas.save();
+        if (v instanceof TextView && pruneToDrawable) {
+            Drawable d = ((TextView) v).getCompoundDrawables()[1];
+            clipRect.set(0, 0, d.getIntrinsicWidth() + padding, d.getIntrinsicHeight() + padding);
+            destCanvas.translate(padding / 2, padding / 2);
+            d.draw(destCanvas);
+        } else {
+            if (v instanceof FolderIcon) {
+                clipRect.bottom = getResources().getDimensionPixelSize(R.dimen.folder_preview_size);
+            } else if (v instanceof BubbleTextView) {
+                final BubbleTextView tv = (BubbleTextView) v;
+                clipRect.bottom = tv.getExtendedPaddingTop() - (int) BubbleTextView.PADDING_V +
+                        tv.getLayout().getLineTop(0);
+            } else if (v instanceof TextView) {
+                final TextView tv = (TextView) v;
+                clipRect.bottom = tv.getExtendedPaddingTop() - tv.getCompoundDrawablePadding() +
+                        tv.getLayout().getLineTop(0);
+            }
+            destCanvas.translate(-v.getScrollX() + padding / 2, -v.getScrollY() + padding / 2);
+            destCanvas.clipRect(clipRect, Op.REPLACE);
+            v.draw(destCanvas);
+        }
+        destCanvas.restore();
+    }
+
+    /**
+     * Returns a new bitmap to show when the given View is being dragged around.
+     * Responsibility for the bitmap is transferred to the caller.
+     */
+    public Bitmap createDragBitmap(View v, Canvas canvas, int padding) {
+        final int outlineColor = getResources().getColor(R.color.drag_outline_color);
+        Bitmap b;
+
+        if (v instanceof TextView) {
+            Drawable d = ((TextView) v).getCompoundDrawables()[1];
+            b = Bitmap.createBitmap(d.getIntrinsicWidth() + padding,
+                    d.getIntrinsicHeight() + padding, Bitmap.Config.ARGB_8888);
+        } else {
+            b = Bitmap.createBitmap(
+                    v.getWidth() + padding, v.getHeight() + padding, Bitmap.Config.ARGB_8888);
         }
 
-        // Draw the View into the bitmap.
-        // The translate of scrollX and scrollY is necessary when drawing TextViews, because
-        // they set scrollX and scrollY to large values to achieve centered text
+        canvas.setBitmap(b);
+        drawDragView(v, canvas, padding, true);
+        mOutlineHelper.applyOuterBlur(b, canvas, outlineColor);
 
-        destCanvas.save();
-        destCanvas.translate(-v.getScrollX() + padding / 2, -v.getScrollY() + padding / 2);
-        destCanvas.clipRect(clipRect, Op.REPLACE);
-        v.draw(destCanvas);
-        destCanvas.restore();
+        return b;
     }
 
     /**
@@ -1866,7 +1892,7 @@
                 v.getWidth() + padding, v.getHeight() + padding, Bitmap.Config.ARGB_8888);
 
         canvas.setBitmap(b);
-        drawDragView(v, canvas, padding);
+        drawDragView(v, canvas, padding, false);
         mOutlineHelper.applyMediumExpensiveOutlineWithBlur(b, canvas, outlineColor, outlineColor);
         return b;
     }
@@ -1920,23 +1946,6 @@
         return b;
     }
 
-    /**
-     * Returns a new bitmap to show when the given View is being dragged around.
-     * Responsibility for the bitmap is transferred to the caller.
-     */
-    private Bitmap createDragBitmap(View v, Canvas canvas, int padding) {
-        final int outlineColor = getResources().getColor(R.color.drag_outline_color);
-        final Bitmap b = Bitmap.createBitmap(
-                mDragOutline.getWidth(), mDragOutline.getHeight(), Bitmap.Config.ARGB_8888);
-
-        canvas.setBitmap(b);
-        canvas.drawBitmap(mDragOutline, 0, 0, null);
-        drawDragView(v, canvas, padding);
-        mOutlineHelper.applyOuterBlur(b, canvas, outlineColor);
-
-        return b;
-    }
-
     void startDrag(CellLayout.CellInfo cellInfo) {
         View child = cellInfo.cell;
 
@@ -1960,9 +1969,15 @@
 
         // The outline is used to visualize where the item will land if dropped
         mDragOutline = createDragOutline(child, canvas, bitmapPadding);
+        beginDragShared(child, this);
+    }
+
+    public void beginDragShared(View child, DragSource source) {
+        // We need to add extra padding to the bitmap to make room for the glow effect
+        final int bitmapPadding = HolographicOutlineHelper.MAX_OUTER_BLUR_RADIUS;
 
         // The drag bitmap follows the touch point around on the screen
-        final Bitmap b = createDragBitmap(child, canvas, bitmapPadding);
+        final Bitmap b = createDragBitmap(child, new Canvas(), bitmapPadding);
 
         final int bmpWidth = b.getWidth();
         final int bmpHeight = b.getHeight();
@@ -1984,10 +1999,10 @@
             dragRect = new Rect(0, 0, child.getWidth(), previewSize);
         }
 
-        mLauncher.lockScreenOrientation();
-        mDragController.startDrag(b, dragLayerX, dragLayerY, this, child.getTag(),
+        mDragController.startDrag(b, dragLayerX, dragLayerY, source, child.getTag(),
                 DragController.DRAG_ACTION_MOVE, dragRect);
         b.recycle();
+        mLauncher.lockScreenOrientation();
     }
 
     void addApplicationShortcut(ShortcutInfo info, int screen, int cellX, int cellY,
@@ -2106,7 +2121,7 @@
             }
 
             Rect folderLocation = new Rect();
-            mLauncher.getDragLayer().getDescendantRectRelativeToSelf(v, folderLocation);
+            float scale = mLauncher.getDragLayer().getDescendantRectRelativeToSelf(v, folderLocation);
             target.removeView(v);
 
             FolderIcon fi = mLauncher.addFolder(screen, targetCell[0], targetCell[1]);
@@ -2115,7 +2130,7 @@
             sourceInfo.cellX = -1;
             sourceInfo.cellY = -1;
 
-            fi.performCreateAnimation(destInfo, v, sourceInfo, dragView, folderLocation,
+            fi.performCreateAnimation(destInfo, v, sourceInfo, dragView, folderLocation, scale,
                     postAnimationRunnable);
             return true;
         }