Adding visual feedback for folder creation`

Change-Id: I47de931a07e3614e25f54d1ba5752a54993d6153
diff --git a/src/com/android/launcher2/FolderIcon.java b/src/com/android/launcher2/FolderIcon.java
index e97676d..952d704 100644
--- a/src/com/android/launcher2/FolderIcon.java
+++ b/src/com/android/launcher2/FolderIcon.java
@@ -78,10 +78,7 @@
     private int mOriginalWidth = -1;
     private int mOriginalHeight = -1;
 
-    private int mFolderLocX;
-    private int mFolderLocY;
-    private float mOuterRingScale;
-    private float mInnerRingScale;
+    FolderRingAnimator mFolderRingAnimator = null;
 
     public FolderIcon(Context context, AttributeSet attrs) {
         super(context, attrs);
@@ -117,18 +114,107 @@
         folder.setFolderIcon(icon);
         folder.bind(folderInfo);
         icon.mFolder = folder;
-
+        icon.mFolderRingAnimator = new FolderRingAnimator(launcher, icon);
         folderInfo.addListener(icon);
-        if (sFolderOuterRingDrawable == null) {
-            sFolderOuterRingDrawable =
-                    launcher.getResources().getDrawable(R.drawable.portal_ring_outer_holo);
+
+        return icon;
+    }
+
+    public static class FolderRingAnimator {
+        public int mFolderLocX;
+        public int mFolderLocY;
+        public float mOuterRingScale;
+        public float mInnerRingScale;
+        public FolderIcon mFolderIcon = null;
+        private Launcher mLauncher;
+
+        public FolderRingAnimator(Launcher launcher, FolderIcon folderIcon) {
+            mLauncher = launcher;
+            mFolderIcon = folderIcon;
+            if (sFolderOuterRingDrawable == null) {
+                sFolderOuterRingDrawable =
+                        launcher.getResources().getDrawable(R.drawable.portal_ring_outer_holo);
+            }
+            if (sFolderInnerRingDrawable == null) {
+                sFolderInnerRingDrawable =
+                        launcher.getResources().getDrawable(R.drawable.portal_ring_inner_holo);
+            }
         }
 
-        if (sFolderInnerRingDrawable == null) {
-            sFolderInnerRingDrawable =
-                    launcher.getResources().getDrawable(R.drawable.portal_ring_inner_holo);
+        public void setLocation(int x, int y) {
+            mFolderLocX = x;
+            mFolderLocY = y;
         }
-        return icon;
+
+        public void animateToAcceptState() {
+            ValueAnimator va = ValueAnimator.ofFloat(0f, 1f);
+            va.setDuration(CONSUMPTION_ANIMATION_DURATION);
+            va.addUpdateListener(new AnimatorUpdateListener() {
+                public void onAnimationUpdate(ValueAnimator animation) {
+                    final float percent = (Float) animation.getAnimatedValue();
+                    mOuterRingScale = OUTER_RING_BASELINE_SCALE + percent * OUTER_RING_GROWTH_FACTOR;
+                    mInnerRingScale = INNER_RING_BASELINE_SCALE + percent * INNER_RING_GROWTH_FACTOR;
+                    mLauncher.getWorkspace().invalidate();
+                    if (mFolderIcon != null) {
+                        mFolderIcon.invalidate();
+                    }
+                }
+            });
+            va.addListener(new AnimatorListenerAdapter() {
+                @Override
+                public void onAnimationEnd(Animator animation) {
+                    // Instead of setting the background drawable to null, we set the color to
+                    // transparent. Setting the background drawable to null results in onDraw
+                    // not getting called.
+                    if (mFolderIcon != null) {
+                        mFolderIcon.setBackgroundColor(Color.TRANSPARENT);
+                        mFolderIcon.requestLayout();
+                    }
+                }
+            });
+            va.start();
+        }
+
+        public void animateToNaturalState() {
+            ValueAnimator va = ValueAnimator.ofFloat(0f, 1f);
+            va.setDuration(CONSUMPTION_ANIMATION_DURATION);
+            va.addUpdateListener(new AnimatorUpdateListener() {
+                public void onAnimationUpdate(ValueAnimator animation) {
+                    final float percent = (Float) animation.getAnimatedValue();
+                    mOuterRingScale = OUTER_RING_BASELINE_SCALE + OUTER_RING_GROWTH_FACTOR
+                            - percent * OUTER_RING_GROWTH_FACTOR;
+                    mInnerRingScale = INNER_RING_BASELINE_SCALE + INNER_RING_GROWTH_FACTOR
+                            - percent * INNER_RING_GROWTH_FACTOR;
+                    mLauncher.getWorkspace().invalidate();
+                    if (mFolderIcon != null) {
+                        mFolderIcon.invalidate();
+                    }
+                }
+            });
+            va.addListener(new AnimatorListenerAdapter() {
+                @Override
+                public void onAnimationEnd(Animator animation) {
+                    if (mFolderIcon != null) {
+                        mFolderIcon.setBackgroundDrawable(sFolderInnerRingDrawable);
+                    }
+                    mLauncher.getWorkspace().hideFolderAccept(FolderRingAnimator.this);
+                }
+            });
+            va.start();
+        }
+
+        public void getLocation(int[] loc) {
+            loc[0] = mFolderLocX;
+            loc[1] = mFolderLocY;
+        }
+
+        public float getOuterRingScale() {
+            return mOuterRingScale;
+        }
+
+        public float getInnerRingScale() {
+            return mInnerRingScale;
+        }
     }
 
     private boolean willAcceptItem(ItemInfo item) {
@@ -166,69 +252,22 @@
         mOriginalHeight = lp.height;
     }
 
-    private void animateToAcceptState() {
-        ValueAnimator va = ValueAnimator.ofFloat(0f, 1f);
-        va.setDuration(CONSUMPTION_ANIMATION_DURATION);
-        va.addUpdateListener(new AnimatorUpdateListener() {
-            public void onAnimationUpdate(ValueAnimator animation) {
-                final float percent = (Float) animation.getAnimatedValue();
-                mOuterRingScale = OUTER_RING_BASELINE_SCALE + percent * OUTER_RING_GROWTH_FACTOR;
-                mInnerRingScale = INNER_RING_BASELINE_SCALE + percent * INNER_RING_GROWTH_FACTOR;
-                mLauncher.getWorkspace().invalidate();
-                invalidate();
-            }
-        });
-        va.addListener(new AnimatorListenerAdapter() {
-            @Override
-            public void onAnimationEnd(Animator animation) {
-                // Instead of setting the background drawable to null, we set the color to
-                // transparent. Setting the background drawable to null results in onDraw
-                // not getting called.
-                setBackgroundColor(Color.TRANSPARENT);
-                requestLayout();
-            }
-        });
-        va.start();
-    }
-
-    private void animateToNaturalState() {
-        ValueAnimator va = ValueAnimator.ofFloat(0f, 1f);
-        va.setDuration(CONSUMPTION_ANIMATION_DURATION);
-        va.addUpdateListener(new AnimatorUpdateListener() {
-            public void onAnimationUpdate(ValueAnimator animation) {
-                final float percent = (Float) animation.getAnimatedValue();
-                mOuterRingScale = OUTER_RING_BASELINE_SCALE + OUTER_RING_GROWTH_FACTOR
-                        - percent * OUTER_RING_GROWTH_FACTOR;
-                mInnerRingScale = INNER_RING_BASELINE_SCALE + INNER_RING_GROWTH_FACTOR
-                        - percent * INNER_RING_GROWTH_FACTOR;
-                mLauncher.getWorkspace().invalidate();
-                invalidate();
-            }
-        });
-        va.addListener(new AnimatorListenerAdapter() {
-            @Override
-            public void onAnimationEnd(Animator animation) {
-                setBackgroundDrawable(sFolderInnerRingDrawable);
-                mLauncher.getWorkspace().hideFolderAccept(FolderIcon.this);
-            }
-        });
-        va.start();
-    }
-
     private void determineFolderLocationInWorkspace() {
         int tvLocation[] = new int[2];
         int wsLocation[] = new int[2];
         getLocationOnScreen(tvLocation);
         mLauncher.getWorkspace().getLocationOnScreen(wsLocation);
-        mFolderLocX = tvLocation[0] - wsLocation[0] + getMeasuredWidth() / 2;
-        mFolderLocY = tvLocation[1] - wsLocation[1] + getMeasuredHeight() / 2;
+
+        int x = tvLocation[0] - wsLocation[0] + getMeasuredWidth() / 2;
+        int y = tvLocation[1] - wsLocation[1] + getMeasuredHeight() / 2;
+        mFolderRingAnimator.setLocation(x, y);
     }
 
     public void onDragEnter(DragObject d) {
         if (!willAcceptItem((ItemInfo) d.dragInfo)) return;
         determineFolderLocationInWorkspace();
-        mLauncher.getWorkspace().showFolderAccept(this);
-        animateToAcceptState();
+        mLauncher.getWorkspace().showFolderAccept(mFolderRingAnimator);
+        mFolderRingAnimator.animateToAcceptState();
     }
 
     public void onDragOver(DragObject d) {
@@ -236,26 +275,13 @@
 
     public void onDragExit(DragObject d) {
         if (!willAcceptItem((ItemInfo) d.dragInfo)) return;
-        animateToNaturalState();
+        mFolderRingAnimator.animateToNaturalState();
     }
 
     public DropTarget getDropTargetDelegate(DragObject d) {
         return null;
     }
 
-    public void getFolderLocation(int[] loc) {
-        loc[0] = mFolderLocX;
-        loc[1] = mFolderLocY;
-    }
-
-    public float getOuterRingScale() {
-        return mOuterRingScale;
-    }
-
-    public float getInnerRingScale() {
-        return mInnerRingScale;
-    }
-
     @Override
     protected void onDraw(Canvas canvas) {
         if (mFolder == null) return;
diff --git a/src/com/android/launcher2/LauncherAppWidgetHostView.java b/src/com/android/launcher2/LauncherAppWidgetHostView.java
index 099a676..670c8b6 100644
--- a/src/com/android/launcher2/LauncherAppWidgetHostView.java
+++ b/src/com/android/launcher2/LauncherAppWidgetHostView.java
@@ -60,7 +60,7 @@
             mHasPerformedLongPress = false;
             return true;
         }
-            
+
         // Watch for longpress events at this level to make sure
         // users can always pick up this widget
         switch (ev.getAction()) {
@@ -68,7 +68,7 @@
                 postCheckForLongClick();
                 break;
             }
-            
+
             case MotionEvent.ACTION_UP:
             case MotionEvent.ACTION_CANCEL:
                 mHasPerformedLongPress = false;
@@ -77,7 +77,7 @@
                 }
                 break;
         }
-        
+
         // Otherwise continue letting touch events fall through to children
         return false;
     }
diff --git a/src/com/android/launcher2/Workspace.java b/src/com/android/launcher2/Workspace.java
index 7d2626d..f3535f1 100644
--- a/src/com/android/launcher2/Workspace.java
+++ b/src/com/android/launcher2/Workspace.java
@@ -67,6 +67,7 @@
 import android.widget.Toast;
 
 import com.android.launcher.R;
+import com.android.launcher2.FolderIcon.FolderRingAnimator;
 import com.android.launcher2.InstallWidgetReceiver.WidgetMimeTypeHandlerData;
 
 /**
@@ -219,7 +220,16 @@
     private int mLastDragXOffset;
     private int mLastDragYOffset;
 
-    private ArrayList<FolderIcon> mFolderOuterRings = new ArrayList<FolderIcon>();
+    private ArrayList<FolderRingAnimator> mFolderOuterRings = new ArrayList<FolderRingAnimator>();
+
+    // Variables relating to the creation of user folders by hovering shortcuts over shortcuts
+    private static final int FOLDER_CREATION_TIMEOUT = 400;
+    private final Alarm mFolderCreationAlarm = new Alarm();
+    private FolderRingAnimator mDragFolderRingAnimator = null;
+    private View mLastDragOverView = null;
+    private boolean mCreateUserFolderOnDrop = false;
+    private int mCellWidth = -1;
+    private int mCellHeight = -1;
 
     // Variables relating to touch disambiguation (scrolling workspace vs. scrolling a widget)
     private float mXDown;
@@ -896,7 +906,6 @@
             }
             float fractionToCatchUpIn1MsVertical = mVerticalCatchupConstant;
 
-
             fractionToCatchUpIn1MsHorizontal /= 33f;
             fractionToCatchUpIn1MsVertical /= 33f;
 
@@ -1184,14 +1193,15 @@
         }
     }
 
-    public void showFolderAccept(FolderIcon fi) {
-        mFolderOuterRings.add(fi);
+    public void showFolderAccept(FolderRingAnimator fra) {
+        mFolderOuterRings.add(fra);
     }
 
-    public void hideFolderAccept(FolderIcon fi) {
-        if (mFolderOuterRings.contains(fi)) {
-            mFolderOuterRings.remove(fi);
+    public void hideFolderAccept(FolderRingAnimator fra) {
+        if (mFolderOuterRings.contains(fra)) {
+            mFolderOuterRings.remove(fra);
         }
+        invalidate();
     }
 
     @Override
@@ -1234,24 +1244,28 @@
         for (int i = 0; i < mFolderOuterRings.size(); i++) {
 
             // Draw outer ring
-            FolderIcon fi = mFolderOuterRings.get(i);
+            FolderRingAnimator fra = mFolderOuterRings.get(i);
             Drawable d = FolderIcon.sFolderOuterRingDrawable;
-            int width = (int) (d.getIntrinsicWidth() * fi.getOuterRingScale());
-            int height = (int) (d.getIntrinsicHeight() * fi.getOuterRingScale());
-            fi.getFolderLocation(mTempLocation);
+            int width = (int) (d.getIntrinsicWidth() * fra.getOuterRingScale());
+            int height = (int) (d.getIntrinsicHeight() * fra.getOuterRingScale());
+            fra.getLocation(mTempLocation);
             int x = mTempLocation[0] + mScrollX - width / 2;
             int y = mTempLocation[1] + mScrollY - height / 2;
             d.setBounds(x, y, x + width, y + height);
             d.draw(canvas);
 
             // Draw inner ring
-            d = FolderIcon.sFolderInnerRingDrawable;
-            width = (int) (fi.getMeasuredWidth() * fi.getInnerRingScale());
-            height = (int) (fi.getMeasuredHeight() * fi.getInnerRingScale());
-            x = mTempLocation[0] + mScrollX - width / 2;
-            y = mTempLocation[1] + mScrollY - height / 2;
-            d.setBounds(x, y, x + width, y + height);
-            d.draw(canvas);
+            if (fra.mFolderIcon != null) {
+                int folderWidth = fra.mFolderIcon != null ? fra.mFolderIcon.getMeasuredWidth() : mCellWidth;
+                int folderHeight = fra.mFolderIcon != null ? fra.mFolderIcon.getMeasuredWidth() : mCellHeight;
+                d = FolderIcon.sFolderInnerRingDrawable;
+                width = (int) (folderWidth * fra.getInnerRingScale());
+                height = (int) (folderHeight * fra.getInnerRingScale());
+                x = mTempLocation[0] + mScrollX - width / 2;
+                y = mTempLocation[1] + mScrollY - height / 2;
+                d.setBounds(x, y, x + width, y + height);
+                d.draw(canvas);
+            }
         }
         super.onDraw(canvas);
     }
@@ -2404,9 +2418,7 @@
     }
 
     boolean willCreateUserFolder(ItemInfo info, CellLayout target, int originX, int originY) {
-        mTargetCell = findNearestArea(originX, originY,
-                1, 1, target,
-                mTargetCell);
+        mTargetCell = findNearestArea(originX, originY, 1, 1, target, mTargetCell);
 
         View v = target.getChildAt(mTargetCell[0], mTargetCell[1]);
         boolean hasntMoved = mDragInfo != null && (mDragInfo.cellX == mTargetCell[0] &&
@@ -2437,8 +2449,8 @@
         boolean hasntMoved = mDragInfo != null && (mDragInfo.cellX == mTargetCell[0] &&
                 mDragInfo.cellY == mTargetCell[1]);
 
-        if (v == null || hasntMoved) return false;
-
+        if (v == null || hasntMoved || !mCreateUserFolderOnDrop) return false;
+        mCreateUserFolderOnDrop = false;
         final int screen = (mTargetCell == null) ?
                 mDragInfo.screen : indexOfChild(target);
 
@@ -3047,24 +3059,83 @@
                     mapPointFromSelfToChild(mDragTargetLayout, mDragViewVisualCenter, null);
                     ItemInfo info = (ItemInfo) d.dragInfo;
 
-                    if (!willCreateUserFolder(info, mDragTargetLayout,
-                            (int) mDragViewVisualCenter[0], (int) mDragViewVisualCenter[1])) {
+                    boolean willCreateUserFolder = willCreateUserFolder(info, mDragTargetLayout,
+                            (int) mDragViewVisualCenter[0], (int) mDragViewVisualCenter[1]);
+
+                    View newDropOver = null;
+                    if (willCreateUserFolder) {
+                        newDropOver = mDragTargetLayout.getChildAt(mTargetCell[0], mTargetCell[1]);
+                    }
+
+                    if (newDropOver != mLastDragOverView || !willCreateUserFolder) {
+                        if (mDragFolderRingAnimator != null && mCreateUserFolderOnDrop) {
+                            mDragFolderRingAnimator.animateToNaturalState();
+                        }
+                        mCreateUserFolderOnDrop = false;
+                        mFolderCreationAlarm.cancelAlarm();
                         mIsDraggingOverIcon = false;
+                    }
+
+                    if (willCreateUserFolder && !mIsDraggingOverIcon) {
+                        mIsDraggingOverIcon = true;
+
+                        mLastDragOverView = mDragTargetLayout.getChildAt(mTargetCell[0], mTargetCell[1]);
+                        mFolderCreationAlarm.setOnAlarmListener(new FolderCreationAlarmListener(mLastDragOverView));
+                        mFolderCreationAlarm.setAlarm(FOLDER_CREATION_TIMEOUT);
+
+                        mDragTargetLayout.clearDragOutlines();
+                    }
+
+                    if (!mCreateUserFolderOnDrop) {
                         mDragTargetLayout.visualizeDropLocation(child, mDragOutline,
                                 (int) mDragViewVisualCenter[0],
                                 (int) mDragViewVisualCenter[1],
                                 item.spanX, item.spanY);
-                    } else if (!mIsDraggingOverIcon) {
-                        mIsDraggingOverIcon = true;
-                        mDragTargetLayout.clearDragOutlines();
                     }
                 }
             }
         }
     }
 
+    class FolderCreationAlarmListener implements OnAlarmListener {
+        View v;
+
+        public FolderCreationAlarmListener(View v) {
+            this.v = v;
+        }
+
+        public void onAlarm(Alarm alarm) {
+            int tvLocation[] = new int[2];
+            int wsLocation[] = new int[2];
+            v.getLocationOnScreen(tvLocation);
+            getLocationOnScreen(wsLocation);
+
+            if (mCellWidth < 0 || mCellHeight < 0) {
+                mCellWidth = mDragTargetLayout.getCellWidth();
+                mCellHeight = mDragTargetLayout.getCellHeight();
+            }
+            int x = tvLocation[0] - wsLocation[0] + mCellWidth / 2;
+            int y = tvLocation[1] - wsLocation[1] + mCellHeight / 2;
+
+            if (mDragFolderRingAnimator == null) {
+                mDragFolderRingAnimator = new FolderRingAnimator(mLauncher, null);
+            }
+            mDragFolderRingAnimator.setLocation(x, y);
+            mDragFolderRingAnimator.animateToAcceptState();
+            showFolderAccept(mDragFolderRingAnimator);
+            mCreateUserFolderOnDrop = true;
+            mDragTargetLayout.clearDragOutlines();
+        }
+    }
+
     private void doDragExit() {
         mWasSpringLoadedOnDragExit = mShrinkState == ShrinkState.SPRING_LOADED;
+
+        if (mDragFolderRingAnimator != null && mCreateUserFolderOnDrop) {
+            mDragFolderRingAnimator.animateToNaturalState();
+        }
+        mFolderCreationAlarm.cancelAlarm();
+
         if (mDragTargetLayout != null) {
             mDragTargetLayout.onDragExit();
         }