Shrink-wrapped folders

Change-Id: Ida1b5d0bd4d39eabfde9f8a5bee0d4b6e9b33627
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());
+        }
     }
 }