Shrink-wrapped folders

Change-Id: Ida1b5d0bd4d39eabfde9f8a5bee0d4b6e9b33627
diff --git a/src/com/android/launcher2/CellLayout.java b/src/com/android/launcher2/CellLayout.java
index fdef18d..5f848a8 100644
--- a/src/com/android/launcher2/CellLayout.java
+++ b/src/com/android/launcher2/CellLayout.java
@@ -321,6 +321,16 @@
         return heightGap * (numCells - 1) + cellHeight * numCells + (crosshairsSize + 1) / 2;
     }
 
+    public void enableHardwareLayers() {
+        mChildren.enableHardwareLayers();
+    }
+
+    public void setGridSize(int x, int y) {
+        mCountX = x;
+        mCountY = y;
+        mOccupied = new boolean[mCountX][mCountY];
+    }
+
     private void invalidateBubbleTextView(BubbleTextView icon) {
         final int padding = icon.getPressedOrFocusedBackgroundPadding();
         invalidate(icon.getLeft() + getLeftPadding() - padding,
@@ -870,10 +880,10 @@
 
         if (mWidthGap < 0 || mHeightGap < 0) {
             int vSpaceLeft = heightSpecSize - mTopPadding - mBottomPadding - (cellHeight * mCountY);
-            mHeightGap = vSpaceLeft / numHeightGaps;
+            mHeightGap = numHeightGaps > 0 ? vSpaceLeft / numHeightGaps : 0;
 
             int hSpaceLeft = widthSpecSize - mLeftPadding - mRightPadding - (cellWidth * mCountX);
-            mWidthGap = hSpaceLeft / numWidthGaps;
+            mWidthGap = numWidthGaps > 0 ? hSpaceLeft / numWidthGaps : 0;
 
             // center it around the min gaps
             int minGap = Math.min(mWidthGap, mHeightGap);
@@ -894,9 +904,10 @@
         int count = getChildCount();
         for (int i = 0; i < count; i++) {
             View child = getChildAt(i);
-            int childWidthMeasureSpec = MeasureSpec.makeMeasureSpec(newWidth, MeasureSpec.EXACTLY);
-            int childheightMeasureSpec = MeasureSpec.makeMeasureSpec(newHeight,
-                    MeasureSpec.EXACTLY);
+            int childWidthMeasureSpec = MeasureSpec.makeMeasureSpec(newWidth - mLeftPadding -
+                    mRightPadding, MeasureSpec.EXACTLY);
+            int childheightMeasureSpec = MeasureSpec.makeMeasureSpec(newHeight - mTopPadding -
+                    mBottomPadding, MeasureSpec.EXACTLY);
             child.measure(childWidthMeasureSpec, childheightMeasureSpec);
         }
         setMeasuredDimension(newWidth, newHeight);
@@ -1027,6 +1038,13 @@
             mDragCenter.set(originX, originY);
         }
 
+        if (dragOutline == null && v == null) {
+            if (mCrosshairsDrawable != null) {
+                invalidate();
+            }
+            return;
+        }
+
         if (nearest != null && (nearest[0] != oldDragCellX || nearest[1] != oldDragCellY)) {
             // Find the top left corner of the rect the object will occupy
             final int[] topLeft = mTmpPoint;
@@ -1492,8 +1510,8 @@
     static boolean findVacantCell(int[] vacant, int spanX, int spanY,
             int xCount, int yCount, boolean[][] occupied) {
 
-        for (int x = 0; x < xCount; x++) {
-            for (int y = 0; y < yCount; y++) {
+        for (int y = 0; y < yCount; y++) {
+            for (int x = 0; x < xCount; x++) {
                 boolean available = !occupied[x][y];
 out:            for (int i = x; i < x + spanX - 1 && x < xCount; i++) {
                     for (int j = y; j < y + spanY - 1 && y < yCount; j++) {
@@ -1597,6 +1615,16 @@
         }
     }
 
+    public int getDesiredWidth() {
+        return mLeftPadding + mRightPadding + (mCountX * mCellWidth) +
+                (Math.max((mCountX - 1), 0) * mWidthGap);
+    }
+
+    public int getDesiredHeight()  {
+        return mTopPadding + mBottomPadding + (mCountY * mCellHeight) +
+                (Math.max((mCountY - 1), 0) * mHeightGap);
+    }
+
     public boolean isOccupied(int x, int y) {
         if (x < mCountX && y < mCountY) {
             return mOccupied[x][y];
diff --git a/src/com/android/launcher2/CellLayoutChildren.java b/src/com/android/launcher2/CellLayoutChildren.java
index f9adce0..59db9c9 100644
--- a/src/com/android/launcher2/CellLayoutChildren.java
+++ b/src/com/android/launcher2/CellLayoutChildren.java
@@ -40,6 +40,9 @@
     public CellLayoutChildren(Context context) {
         super(context);
         mWallpaperManager = WallpaperManager.getInstance(context);
+    }
+
+    public void enableHardwareLayers() {
         setLayerType(LAYER_TYPE_HARDWARE, null);
     }
 
diff --git a/src/com/android/launcher2/DragLayer.java b/src/com/android/launcher2/DragLayer.java
index c2b710e..7503dda 100644
--- a/src/com/android/launcher2/DragLayer.java
+++ b/src/com/android/launcher2/DragLayer.java
@@ -42,6 +42,8 @@
             new ArrayList<AppWidgetResizeFrame>();
     private AppWidgetResizeFrame mCurrentResizeFrame;
     private int mXDown, mYDown;
+    private Folder mCurrentFolder = null;
+    private Launcher mLauncher;
 
     /**
      * Used to create a new DragLayer from XML.
@@ -82,6 +84,18 @@
                 }
             }
         }
+        if (mCurrentFolder != null) {
+            mCurrentFolder.getHitRect(hitRect);
+            int[] screenPos = new int[2];
+            View parent = (View) mCurrentFolder.getParent();
+            if (parent != null) {
+                parent.getLocationOnScreen(screenPos);
+                hitRect.offset(screenPos[0], screenPos[1]);
+                if (!hitRect.contains(x, y)) {
+                    mLauncher.closeFolder();
+                }
+            }
+        }
         return false;
     }
 
@@ -237,4 +251,12 @@
 
         resizeFrame.snapToWidget(false);
     }
+
+    public void setLauncher(Launcher l) {
+        mLauncher = l;
+    }
+
+    public void setCurrentFolder(Folder f) {
+        mCurrentFolder = f;
+    }
 }
diff --git a/src/com/android/launcher2/Folder.java b/src/com/android/launcher2/Folder.java
index 3a8a68d..a4aeec4 100644
--- a/src/com/android/launcher2/Folder.java
+++ b/src/com/android/launcher2/Folder.java
@@ -20,20 +20,21 @@
 
 import android.animation.Animator;
 import android.animation.AnimatorListenerAdapter;
-import android.animation.AnimatorSet;
 import android.animation.ObjectAnimator;
 import android.animation.PropertyValuesHolder;
 import android.animation.ValueAnimator;
 import android.animation.ValueAnimator.AnimatorUpdateListener;
 import android.content.Context;
+import android.graphics.Matrix;
 import android.graphics.Rect;
 import android.util.AttributeSet;
 import android.view.LayoutInflater;
 import android.view.MotionEvent;
 import android.view.View;
 import android.view.View.OnClickListener;
+import android.view.animation.AccelerateInterpolator;
+import android.view.animation.DecelerateInterpolator;
 import android.widget.AdapterView;
-import android.widget.Button;
 import android.widget.LinearLayout;
 import android.widget.TextView;
 import android.widget.AdapterView.OnItemClickListener;
@@ -41,7 +42,6 @@
 
 import com.android.launcher.R;
 import com.android.launcher2.FolderInfo.FolderListener;
-import com.android.launcher2.Workspace.ShrinkState;
 
 /**
  * Represents a set of icons chosen by the user or generated by the system.
@@ -53,8 +53,6 @@
 
     protected Launcher mLauncher;
 
-    protected Button mCloseButton;
-
     protected FolderInfo mInfo;
     
     /**
@@ -75,6 +73,14 @@
     private final IconCache mIconCache;
     private int mState = STATE_NONE;
     private int[] mDragItemPosition = new int[2];
+    private static final int FULL_GROW = 0;
+    private static final int PARTIAL_GROW = 1;
+    private int mMode = PARTIAL_GROW;
+    private boolean mRearrangeOnClose = false;
+    private FolderIcon mFolderIcon;
+    private int mMaxCountX;
+    private int mMaxCountY;
+    private Rect mNewSize = new Rect();
 
     /**
      * Used to inflate the Workspace from XML.
@@ -88,18 +94,19 @@
         mInflater = LayoutInflater.from(context);
         mIconCache = ((LauncherApplication)context.getApplicationContext()).getIconCache();
         mExpandDuration = getResources().getInteger(R.integer.config_folderAnimDuration);
+
+        mMaxCountX = LauncherModel.getCellCountX() - 1;
+        mMaxCountY = LauncherModel.getCellCountY() - 1;
     }
 
     @Override
     protected void onFinishInflate() {
         super.onFinishInflate();
-
-        mCloseButton = (Button) findViewById(R.id.folder_close);
-        mCloseButton.setOnClickListener(this);
-        mCloseButton.setOnLongClickListener(this);
         mContent = (CellLayout) findViewById(R.id.folder_content);
+        mContent.setGridSize(0, 0);
+        mContent.enableHardwareLayers();
     }
-    
+
     public void onItemClick(AdapterView parent, View v, int position, long id) {
         ShortcutInfo app = (ShortcutInfo) parent.getItemAtPosition(position);
         int[] pos = new int[2];
@@ -119,15 +126,14 @@
             item.intent.setSourceBounds(new Rect(pos[0], pos[1],
                     pos[0] + v.getWidth(), pos[1] + v.getHeight()));
             mLauncher.startActivitySafely(item.intent, item);
-        } else {
-            mLauncher.closeFolder(this);
         }
     }
 
     public boolean onLongClick(View v) {
         Object tag = v.getTag();
         if (tag instanceof ShortcutInfo) {
-         // refactor this code from Folder
+            mLauncher.closeFolder(this);
+
             ShortcutInfo item = (ShortcutInfo) tag;
             if (!v.isInTouchMode()) {
                 return false;
@@ -137,7 +143,8 @@
             mDragController.startDrag(v, this, item, DragController.DRAG_ACTION_COPY);
             mDragItemPosition[0] = item.cellX;
             mDragItemPosition[1] = item.cellY;
-            mLauncher.closeFolder(this);
+            mInfo.remove(item);
+
             mDragItem = item;
         } else {
             mLauncher.closeFolder(this);
@@ -178,7 +185,11 @@
     void setLauncher(Launcher launcher) {
         mLauncher = launcher;
     }
-    
+
+    void setFolderIcon(FolderIcon icon) {
+        mFolderIcon = icon;
+    }
+
     /**
      * @return the FolderInfo object associated with this folder
      */
@@ -201,15 +212,10 @@
 
     void bind(FolderInfo info) {
         mInfo = info;
-        mCloseButton.setText(info.title);
         ArrayList<ShortcutInfo> children = info.contents;
         for (int i = 0; i < children.size(); i++) {
             ShortcutInfo child = (ShortcutInfo) children.get(i);
-            if ((child.cellX == -1 && child.cellY == -1) ||
-                    mContent.isOccupied(child.cellX, child.cellY)) {
-                findAndSetEmptyCells(child);
-            }
-            createAndAddShortcut((ShortcutInfo) children.get(i));
+            onAdd(child);
         }
         mInfo.addListener(this);
     }
@@ -232,19 +238,20 @@
     private void positionAndSizeAsIcon() {
         if (!(getParent() instanceof CellLayoutChildren)) return;
 
-        CellLayoutChildren clc = (CellLayoutChildren) getParent();
-        CellLayout cellLayout = (CellLayout) clc.getParent();
-
-        FolderIcon fi = (FolderIcon) cellLayout.getChildAt(mInfo.cellX, mInfo.cellY);
-        CellLayout.LayoutParams iconLp = (CellLayout.LayoutParams) fi.getLayoutParams();
+        CellLayout.LayoutParams iconLp = (CellLayout.LayoutParams) mFolderIcon.getLayoutParams();
         CellLayout.LayoutParams lp = (CellLayout.LayoutParams) getLayoutParams();
 
-        lp.width = iconLp.width;
-        lp.height = iconLp.height;
-        lp.x = iconLp.x;
-        lp.y = iconLp.y;
-
-        mContent.setAlpha(0f);
+        if (mMode == PARTIAL_GROW) {
+            setScaleX(0.8f);
+            setScaleY(0.8f);
+            setAlpha(0f);
+        } else {
+            lp.width = iconLp.width;
+            lp.height = iconLp.height;
+            lp.x = iconLp.x;
+            lp.y = iconLp.y;
+            mContent.setAlpha(0);
+        }
         mState = STATE_SMALL;
     }
 
@@ -254,41 +261,46 @@
         }
         if (!(getParent() instanceof CellLayoutChildren)) return;
 
+        ObjectAnimator oa;
         CellLayout.LayoutParams lp = (CellLayout.LayoutParams) getLayoutParams();
 
-        CellLayoutChildren clc = (CellLayoutChildren) getParent();
-        CellLayout cellLayout = (CellLayout) clc.getParent();
-        Rect r = cellLayout.getContentRect(null);
+        centerAboutIcon();
+        if (mMode == PARTIAL_GROW) {
+            PropertyValuesHolder alpha = PropertyValuesHolder.ofFloat("alpha", 1);
+            PropertyValuesHolder scaleX = PropertyValuesHolder.ofFloat("scaleX", 1.0f);
+            PropertyValuesHolder scaleY = PropertyValuesHolder.ofFloat("scaleY", 1.0f);
+            oa = ObjectAnimator.ofPropertyValuesHolder(this, alpha, scaleX, scaleY);
+        } else {
+            PropertyValuesHolder width = PropertyValuesHolder.ofInt("width", mNewSize.width());
+            PropertyValuesHolder height = PropertyValuesHolder.ofInt("height", mNewSize.height());
+            PropertyValuesHolder x = PropertyValuesHolder.ofInt("x", mNewSize.left);
+            PropertyValuesHolder y = PropertyValuesHolder.ofInt("y", mNewSize.top);
+            oa = ObjectAnimator.ofPropertyValuesHolder(lp, width, height, x, y);
+            oa.addUpdateListener(new AnimatorUpdateListener() {
+                public void onAnimationUpdate(ValueAnimator animation) {
+                    requestLayout();
+                }
+            });
 
-        PropertyValuesHolder width = PropertyValuesHolder.ofInt("width", r.width());
-        PropertyValuesHolder height = PropertyValuesHolder.ofInt("height", r.height());
-        PropertyValuesHolder x = PropertyValuesHolder.ofInt("x", 0);
-        PropertyValuesHolder y = PropertyValuesHolder.ofInt("y", 0);
+            PropertyValuesHolder alpha = PropertyValuesHolder.ofFloat("alpha", 1.0f);
+            ObjectAnimator alphaOa = ObjectAnimator.ofPropertyValuesHolder(mContent, alpha);
+            alphaOa.setDuration(mExpandDuration);
+            alphaOa.setInterpolator(new AccelerateInterpolator(2.0f));
+            alphaOa.start();
+        }
 
-        ObjectAnimator oa = ObjectAnimator.ofPropertyValuesHolder(lp, width, height, x, y);
-        oa.addUpdateListener(new AnimatorUpdateListener() {
-            public void onAnimationUpdate(ValueAnimator animation) {
-                requestLayout();
-            }
-        });
-
-        PropertyValuesHolder alpha = PropertyValuesHolder.ofFloat("alpha", 1.0f);
-        ObjectAnimator oaContentAlpha = ObjectAnimator.ofPropertyValuesHolder(mContent, alpha);
-
-        AnimatorSet set = new AnimatorSet();
-        set.playTogether(oa, oaContentAlpha);
-        set.setDuration(mExpandDuration);
-        set.addListener(new AnimatorListenerAdapter() {
+        oa.addListener(new AnimatorListenerAdapter() {
             @Override
             public void onAnimationStart(Animator animation) {
                 mState = STATE_ANIMATING;
             }
             @Override
             public void onAnimationEnd(Animator animation) {
-                mState = STATE_SMALL;
+                mState = STATE_OPEN;
             }
         });
-        set.start();
+        oa.setDuration(mExpandDuration);
+        oa.start();
     }
 
     public void animateClosed() {
@@ -296,42 +308,49 @@
 
         CellLayoutChildren clc = (CellLayoutChildren) getParent();
         final CellLayout cellLayout = (CellLayout) clc.getParent();
+        ObjectAnimator oa;
 
-        FolderIcon fi = (FolderIcon) cellLayout.getChildAt(mInfo.cellX, mInfo.cellY);
-        CellLayout.LayoutParams iconLp = (CellLayout.LayoutParams) fi.getLayoutParams();
-        CellLayout.LayoutParams lp = (CellLayout.LayoutParams) getLayoutParams();
+        if (mMode == PARTIAL_GROW) {
+            PropertyValuesHolder alpha = PropertyValuesHolder.ofFloat("alpha", 0);
+            PropertyValuesHolder scaleX = PropertyValuesHolder.ofFloat("scaleX", 0.9f);
+            PropertyValuesHolder scaleY = PropertyValuesHolder.ofFloat("scaleY", 0.9f);
+            oa = ObjectAnimator.ofPropertyValuesHolder(this, alpha, scaleX, scaleY);
+        } else {
+            CellLayout.LayoutParams iconLp = (CellLayout.LayoutParams) mFolderIcon.getLayoutParams();
+            CellLayout.LayoutParams lp = (CellLayout.LayoutParams) getLayoutParams();
 
-        PropertyValuesHolder width = PropertyValuesHolder.ofInt("width", iconLp.width);
-        PropertyValuesHolder height = PropertyValuesHolder.ofInt("height", iconLp.height);
-        PropertyValuesHolder x = PropertyValuesHolder.ofInt("x",iconLp.x);
-        PropertyValuesHolder y = PropertyValuesHolder.ofInt("y", iconLp.y);
+            PropertyValuesHolder width = PropertyValuesHolder.ofInt("width", iconLp.width);
+            PropertyValuesHolder height = PropertyValuesHolder.ofInt("height", iconLp.height);
+            PropertyValuesHolder x = PropertyValuesHolder.ofInt("x",iconLp.x);
+            PropertyValuesHolder y = PropertyValuesHolder.ofInt("y", iconLp.y);
+            oa = ObjectAnimator.ofPropertyValuesHolder(lp, width, height, x, y);
+            oa.addUpdateListener(new AnimatorUpdateListener() {
+                public void onAnimationUpdate(ValueAnimator animation) {
+                    requestLayout();
+                }
+            });
 
-        ObjectAnimator oa = ObjectAnimator.ofPropertyValuesHolder(lp, width, height, x, y);
-        oa.addUpdateListener(new AnimatorUpdateListener() {
-            public void onAnimationUpdate(ValueAnimator animation) {
-                requestLayout();
-            }
-        });
+            PropertyValuesHolder alpha = PropertyValuesHolder.ofFloat("alpha", 0f);
+            ObjectAnimator alphaOa = ObjectAnimator.ofPropertyValuesHolder(mContent, alpha);
+            alphaOa.setDuration(mExpandDuration);
+            alphaOa.setInterpolator(new DecelerateInterpolator(2.0f));
+            alphaOa.start();
+        }
 
-        PropertyValuesHolder alpha = PropertyValuesHolder.ofFloat("alpha", 0f);
-        ObjectAnimator oaContentAlpha = ObjectAnimator.ofPropertyValuesHolder(mContent, alpha);
-
-        AnimatorSet set = new AnimatorSet();
-        set.playTogether(oa, oaContentAlpha);
-        set.setDuration(mExpandDuration);
-
-        set.addListener(new AnimatorListenerAdapter() {
+        oa.addListener(new AnimatorListenerAdapter() {
             @Override
             public void onAnimationEnd(Animator animation) {
+                onCloseComplete();
                 cellLayout.removeViewWithoutMarkingCells(Folder.this);
-                mState = STATE_OPEN;
+                mState = STATE_SMALL;
             }
             @Override
             public void onAnimationStart(Animator animation) {
                 mState = STATE_ANIMATING;
             }
         });
-        set.start();
+        oa.setDuration(mExpandDuration);
+        oa.start();
     }
 
     void notifyDataSetChanged() {
@@ -345,9 +364,9 @@
             DragView dragView, Object dragInfo) {
         final ItemInfo item = (ItemInfo) dragInfo;
         final int itemType = item.itemType;
-        return (itemType == LauncherSettings.Favorites.ITEM_TYPE_APPLICATION ||
-                    itemType == LauncherSettings.Favorites.ITEM_TYPE_SHORTCUT)
-                && item.container != mInfo.id;
+        return ((itemType == LauncherSettings.Favorites.ITEM_TYPE_APPLICATION ||
+                    itemType == LauncherSettings.Favorites.ITEM_TYPE_SHORTCUT) &&
+                    !isFull());
     }
 
     public void onDrop(DragSource source, int x, int y, int xOffset, int yOffset,
@@ -361,7 +380,6 @@
         } else {
             item = (ShortcutInfo)dragInfo;
         }
-        findAndSetEmptyCells(item);
         mInfo.add(item);
         LauncherModel.addOrMoveItemInDatabase(mLauncher, item, mInfo.id, 0, item.cellX, item.cellY);
     }
@@ -398,20 +416,34 @@
 
     public void onDragEnter(DragSource source, int x, int y, int xOffset, int yOffset,
             DragView dragView, Object dragInfo) {
+        mContent.onDragEnter();
     }
 
     public void onDragOver(DragSource source, int x, int y, int xOffset, int yOffset,
             DragView dragView, Object dragInfo) {
+        float[] r = mapPointFromScreenToContent(x, y, null);
+        mContent.visualizeDropLocation(null, null, (int) r[0], (int) r[1], 1, 1);
     }
 
     public void onDragExit(DragSource source, int x, int y, int xOffset, int yOffset,
             DragView dragView, Object dragInfo) {
+        mContent.onDragExit();
+    }
+
+    public float[] mapPointFromScreenToContent(int x, int y, float[] r) {
+        if (r == null) {
+            r = new float[2];
+        }
+
+        int[] screenLocation = new int[2];
+        mContent.getLocationOnScreen(screenLocation);
+
+        r[0] = x - screenLocation[0];
+        r[1] = y - screenLocation[1];
+        return r;
     }
 
     public void onDropCompleted(View target, Object dragInfo, boolean success) {
-        if (success) {
-            mInfo.remove(mDragItem);
-        }
     }
 
     public boolean isDropEnabled() {
@@ -423,9 +455,119 @@
         return null;
     }
 
+    private void setupContentDimension(int count) {
+        ArrayList<View> list = getItemsInReadingOrder();
+
+        int countX = mContent.getCountX();
+        int countY = mContent.getCountY();
+        if (countX * countY < count) {
+            // Current grid is too small, expand it
+            if (countX <= countY && countX < mMaxCountX) {
+                countX++;
+            } else if (countY < mMaxCountY) {
+                countY++;
+            }
+            if (countY == 0) countY++;
+
+            mContent.setGridSize(countX, countY);
+        } else if ((countX - 1) * countY >= count || (countY - 1) * countX >= count) {
+            // Current grid is too big, shrink it
+            if (countX <= countY) {
+                countY--;
+            } else {
+                countX--;
+            }
+            mContent.setGridSize(countX, countY);
+        }
+        arrangeChildren(list);
+    }
+
+    public boolean isFull() {
+        return getItemCount() >= mMaxCountX * mMaxCountY;
+    }
+
+    private void centerAboutIcon() {
+        CellLayout.LayoutParams iconLp = (CellLayout.LayoutParams) mFolderIcon.getLayoutParams();
+        CellLayout.LayoutParams lp = (CellLayout.LayoutParams) getLayoutParams();
+
+        int width = getPaddingLeft() + getPaddingRight() + mContent.getDesiredWidth();
+        int height = getPaddingTop() + getPaddingBottom() + mContent.getDesiredHeight();
+
+        int centerX = iconLp.x + iconLp.width / 2;
+        int centerY = iconLp.y + iconLp.height / 2;
+        int centeredLeft = centerX - width / 2;
+        int centeredTop = centerY - height / 2;
+
+        CellLayoutChildren clc = (CellLayoutChildren) getParent();
+        int parentWidth = 0;
+        int parentHeight = 0;
+        if (clc != null) {
+            parentWidth = clc.getMeasuredWidth();
+            parentHeight = clc.getMeasuredHeight();
+        }
+
+        int left = Math.min(Math.max(0, centeredLeft), parentWidth - width);
+        int top = Math.min(Math.max(0, centeredTop), parentHeight - height);
+
+        int folderPivotX = width / 2 + (centeredLeft - left);
+        int folderPivotY = height / 2 + (centeredTop - top);
+        setPivotX(folderPivotX);
+        setPivotY(folderPivotY);
+        int folderIconPivotX = (int) (mFolderIcon.getMeasuredWidth() *
+                (1.0f * folderPivotX / width));
+        int folderIconPivotY = (int) (mFolderIcon.getMeasuredHeight() *
+                (1.0f * folderPivotY / height));
+        mFolderIcon.setPivotX(folderIconPivotX);
+        mFolderIcon.setPivotY(folderIconPivotY);
+
+        if (mMode == PARTIAL_GROW) {
+            lp.width = width;
+            lp.height = height;
+            lp.x = left;
+            lp.y = top;
+        } else {
+            mNewSize.set(left, top, left + width, top + height);
+        }
+    }
+
+    private void setupContentForNumItems(int count) {
+
+        setupContentDimension(count);
+
+        CellLayout.LayoutParams lp = (CellLayout.LayoutParams) getLayoutParams();
+        if (lp == null) {
+            lp = new CellLayout.LayoutParams(0, 0, -1, -1);
+            lp.isLockedToGrid = false;
+            setLayoutParams(lp);
+        }
+        centerAboutIcon();
+    }
+
+    private void arrangeChildren(ArrayList<View> list) {
+        int[] vacant = new int[2];
+        if (list == null) {
+            list = getItemsInReadingOrder();
+        }
+        mContent.removeAllViews();
+
+        for (int i = 0; i < list.size(); i++) {
+            View v = list.get(i);
+            mContent.getVacantCell(vacant, 1, 1);
+            CellLayout.LayoutParams lp = (CellLayout.LayoutParams) v.getLayoutParams();
+            lp.cellX = vacant[0];
+            lp.cellY = vacant[1];
+            ItemInfo info = (ItemInfo) v.getTag();
+            info.cellX = vacant[0];
+            info.cellY = vacant[1];
+            boolean insert = false;
+            mContent.addViewToCellLayout(v, insert ? 0 : -1, (int)info.id, lp, true);
+        }
+    }
+
     public void onAdd(ShortcutInfo item) {
-        if ((item.cellX == -1 && item.cellY == -1) ||
-                mContent.isOccupied(item.cellX, item.cellY)) {
+        if (!findAndSetEmptyCells(item)) {
+            // The current layout is full, can we expand it?
+            setupContentForNumItems(getItemCount() + 1);
             findAndSetEmptyCells(item);
         }
         createAndAddShortcut(item);
@@ -439,8 +581,33 @@
         return mContent.getChildrenLayout().getChildAt(index);
     }
 
+    private ArrayList<View> getItemsInReadingOrder() {
+        ArrayList<View> list = new ArrayList<View>();
+        for (int j = 0; j < mContent.getCountY(); j++) {
+            for (int i = 0; i < mContent.getCountX(); i++) {
+                View v = mContent.getChildAt(i, j);
+                if (v != null) {
+                    list.add(v);
+                }
+            }
+        }
+        return list;
+    }
+
+    private void onCloseComplete() {
+        if (mRearrangeOnClose) {
+            setupContentForNumItems(getItemCount());
+            mRearrangeOnClose = false;
+        }
+    }
+
     public void onRemove(ShortcutInfo item) {
         View v = mContent.getChildAt(mDragItemPosition[0], mDragItemPosition[1]);
         mContent.removeView(v);
+        if (mState == STATE_ANIMATING) {
+            mRearrangeOnClose = true;
+        } else {
+            setupContentForNumItems(getItemCount());
+        }
     }
 }
diff --git a/src/com/android/launcher2/FolderIcon.java b/src/com/android/launcher2/FolderIcon.java
index b770cd6..aa7d079 100644
--- a/src/com/android/launcher2/FolderIcon.java
+++ b/src/com/android/launcher2/FolderIcon.java
@@ -16,6 +16,12 @@
 
 package com.android.launcher2;
 
+import android.animation.Animator;
+import android.animation.AnimatorListenerAdapter;
+import android.animation.ObjectAnimator;
+import android.animation.PropertyValuesHolder;
+import android.animation.ValueAnimator;
+import android.animation.ValueAnimator.AnimatorUpdateListener;
 import android.content.Context;
 import android.content.res.Resources;
 import android.graphics.Canvas;
@@ -40,6 +46,11 @@
     private static final int NUM_ITEMS_IN_PREVIEW = 4;
     private static final float ICON_ANGLE = 15f;
 
+    int mOriginalWidth;
+    int mOriginalHeight;
+    int mOriginalX;
+    int mOriginalY;
+
     public FolderIcon(Context context, AttributeSet attrs) {
         super(context, attrs);
     }
@@ -61,7 +72,7 @@
         FolderIcon icon = (FolderIcon) LayoutInflater.from(launcher).inflate(resId, group, false);
 
         final Resources resources = launcher.getResources();
-        Drawable d = iconCache.getFullResIcon(resources, R.drawable.folder_bg);
+        Drawable d = iconCache.getFullResIcon(resources, R.drawable.portal_ring_inner_holo);
         icon.setBackgroundDrawable(d);
         icon.setTag(folderInfo);
         icon.setOnClickListener(launcher);
@@ -71,6 +82,7 @@
         Folder folder = Folder.fromXml(launcher);
         folder.setDragController(launcher.getDragController());
         folder.setLauncher(launcher);
+        folder.setFolderIcon(icon);
         folder.bind(folderInfo);
         icon.mFolder = folder;
 
@@ -83,9 +95,9 @@
             DragView dragView, Object dragInfo) {
         final ItemInfo item = (ItemInfo) dragInfo;
         final int itemType = item.itemType;
-        return (itemType == LauncherSettings.Favorites.ITEM_TYPE_APPLICATION ||
-                itemType == LauncherSettings.Favorites.ITEM_TYPE_SHORTCUT)
-                && item.container != mInfo.id;
+        return ((itemType == LauncherSettings.Favorites.ITEM_TYPE_APPLICATION ||
+                itemType == LauncherSettings.Favorites.ITEM_TYPE_SHORTCUT) &&
+                !mFolder.isFull());
     }
 
     public void addItem(ShortcutInfo item) {
@@ -107,8 +119,32 @@
         addItem(item);
     }
 
+    void saveState(CellLayout.LayoutParams lp) {
+        mOriginalWidth = lp.width;
+        mOriginalHeight = lp.height;
+        mOriginalX = lp.x;
+        mOriginalY = lp.y;
+    }
+
     public void onDragEnter(DragSource source, int x, int y, int xOffset, int yOffset,
             DragView dragView, Object dragInfo) {
+        CellLayout.LayoutParams lp = (CellLayout.LayoutParams) getLayoutParams();
+        lp.isLockedToGrid = false;
+        saveState(lp);
+
+        PropertyValuesHolder width = PropertyValuesHolder.ofInt("width", (int) (1.1 * lp.width));
+        PropertyValuesHolder height = PropertyValuesHolder.ofInt("height", (int) (1.1 * lp.height));
+        PropertyValuesHolder newX = PropertyValuesHolder.ofInt("x", lp.x - (int) (0.05 * lp.width));
+        PropertyValuesHolder newY = PropertyValuesHolder.ofInt("y", lp.y - (int) (0.05 * lp.height));
+        ObjectAnimator oa = ObjectAnimator.ofPropertyValuesHolder(lp, width, height, newX, newY);
+        oa.addUpdateListener(new AnimatorUpdateListener() {
+            public void onAnimationUpdate(ValueAnimator animation) {
+                invalidate();
+                requestLayout();
+            }
+        });
+        oa.setDuration(50);
+        oa.start();
     }
 
     public void onDragOver(DragSource source, int x, int y, int xOffset, int yOffset,
@@ -117,6 +153,28 @@
 
     public void onDragExit(DragSource source, int x, int y, int xOffset, int yOffset,
             DragView dragView, Object dragInfo) {
+        final CellLayout.LayoutParams lp = (CellLayout.LayoutParams) getLayoutParams();
+        lp.isLockedToGrid = false;
+
+        PropertyValuesHolder width = PropertyValuesHolder.ofInt("width", mOriginalWidth);
+        PropertyValuesHolder height = PropertyValuesHolder.ofInt("height", mOriginalHeight);
+        PropertyValuesHolder newX = PropertyValuesHolder.ofInt("x", mOriginalX);
+        PropertyValuesHolder newY = PropertyValuesHolder.ofInt("y", mOriginalY);
+        ObjectAnimator oa = ObjectAnimator.ofPropertyValuesHolder(lp, width, height, newX, newY);
+        oa.addUpdateListener(new AnimatorUpdateListener() {
+            public void onAnimationUpdate(ValueAnimator animation) {
+                invalidate();
+                requestLayout();
+            }
+        });
+        oa.addListener(new AnimatorListenerAdapter() {
+            @Override
+            public void onAnimationEnd(Animator animation) {
+                lp.isLockedToGrid = true;
+            }
+        });
+        oa.setDuration(50);
+        oa.start();
     }
 
     @Override
diff --git a/src/com/android/launcher2/Launcher.java b/src/com/android/launcher2/Launcher.java
index 2fe1c7d..9dfed69 100644
--- a/src/com/android/launcher2/Launcher.java
+++ b/src/com/android/launcher2/Launcher.java
@@ -28,6 +28,8 @@
 import android.animation.Animator;
 import android.animation.AnimatorListenerAdapter;
 import android.animation.AnimatorSet;
+import android.animation.ObjectAnimator;
+import android.animation.PropertyValuesHolder;
 import android.animation.ValueAnimator;
 import android.animation.ValueAnimator.AnimatorUpdateListener;
 import android.app.Activity;
@@ -266,6 +268,8 @@
     private static Drawable.ConstantState sVoiceSearchIcon;
     private static Drawable.ConstantState sAppMarketIcon;
 
+    private DragLayer mDragLayer;
+
     private BubbleTextView mWaitingForResume;
 
     private static ArrayList<PendingAddArguments> sPendingAddList
@@ -894,6 +898,8 @@
 
         DragLayer dragLayer = (DragLayer) findViewById(R.id.drag_layer);
         dragLayer.setDragController(dragController);
+        dragLayer.setLauncher(this);
+        mDragLayer = dragLayer;
 
         if (LauncherApplication.isScreenLarge()) {
             mAllAppsGrid = (AllAppsView) dragLayer.findViewById(R.id.all_apps_view);
@@ -1917,10 +1923,11 @@
         }
     }
 
-    private void closeFolder() {
+    public void closeFolder() {
         Folder folder = mWorkspace.getOpenFolder();
         if (folder != null) {
             closeFolder(folder);
+            mDragLayer.setCurrentFolder(null);
         }
     }
 
@@ -1930,6 +1937,8 @@
         ViewGroup parent = (ViewGroup) folder.getParent().getParent();
         if (parent != null) {
             CellLayout cl = (CellLayout) parent;
+            FolderIcon fi = (FolderIcon) cl.getChildAt(folder.mInfo.cellX, folder.mInfo.cellY);
+            shrinkAndFadeInFolderIcon(fi);
             mDragController.removeDropTarget((DropTarget)folder);
         }
 
@@ -2128,6 +2137,26 @@
         }
     }
 
+    private void growAndFadeOutFolderIcon(FolderIcon fi) {
+        PropertyValuesHolder alpha = PropertyValuesHolder.ofFloat("alpha", 0);
+        PropertyValuesHolder scaleX = PropertyValuesHolder.ofFloat("scaleX", 1.5f);
+        PropertyValuesHolder scaleY = PropertyValuesHolder.ofFloat("scaleY", 1.5f);
+
+        ObjectAnimator oa = ObjectAnimator.ofPropertyValuesHolder(fi, alpha, scaleX, scaleY);
+        oa.setDuration(getResources().getInteger(R.integer.config_folderAnimDuration));
+        oa.start();
+    }
+
+    private void shrinkAndFadeInFolderIcon(FolderIcon fi) {
+        PropertyValuesHolder alpha = PropertyValuesHolder.ofFloat("alpha", 1.0f);
+        PropertyValuesHolder scaleX = PropertyValuesHolder.ofFloat("scaleX", 1.0f);
+        PropertyValuesHolder scaleY = PropertyValuesHolder.ofFloat("scaleY", 1.0f);
+
+        ObjectAnimator oa = ObjectAnimator.ofPropertyValuesHolder(fi, alpha, scaleX, scaleY);
+        oa.setDuration(getResources().getInteger(R.integer.config_folderAnimDuration));
+        oa.start();
+    }
+
     /**
      * Opens the user folder described by the specified tag. The opening of the folder
      * is animated relative to the specified View. If the View is null, no animation
@@ -2139,9 +2168,11 @@
         Folder folder = folderIcon.mFolder;
         FolderInfo info = folder.mInfo;
 
+        growAndFadeOutFolderIcon(folderIcon);
         info.opened = true;
 
         mWorkspace.addInFullScreen(folder, info.screen);
+        mDragLayer.setCurrentFolder(folder);
         folder.animateOpen();
         folder.onOpen();
     }
diff --git a/src/com/android/launcher2/Workspace.java b/src/com/android/launcher2/Workspace.java
index 70de0a4..24160f0 100644
--- a/src/com/android/launcher2/Workspace.java
+++ b/src/com/android/launcher2/Workspace.java
@@ -389,10 +389,13 @@
         if (!(child instanceof CellLayout)) {
             throw new IllegalArgumentException("A Workspace can only have CellLayout children.");
         }
-        ((CellLayout) child).setOnInterceptTouchListener(this);
-        child.setOnClickListener(this);
-        child.setClickable(true);
+        CellLayout cl = ((CellLayout) child);
+        cl.setOnInterceptTouchListener(this);
+        cl.setOnClickListener(this);
+        cl.setClickable(true);
+        cl.enableHardwareLayers();
     }
+
     @Override
     public void addView(View child, int index, LayoutParams params) {
         onAddView(child);