New folder visualization, renaming, staggering reorder

Change-Id: I62963d225e6ea5d2ec9d8ebc8a6d73099f5d6c7f
diff --git a/src/com/android/launcher2/BubbleTextView.java b/src/com/android/launcher2/BubbleTextView.java
index 703b3a8..8c0c27c 100644
--- a/src/com/android/launcher2/BubbleTextView.java
+++ b/src/com/android/launcher2/BubbleTextView.java
@@ -262,10 +262,12 @@
     }
 
     void setCellLayoutPressedOrFocusedIcon() {
-        CellLayoutChildren parent = (CellLayoutChildren) getParent();
-        if (parent != null) {
-            CellLayout layout = (CellLayout) parent.getParent();
-            layout.setPressedOrFocusedIcon((mPressedOrFocusedBackground != null) ? this : null);
+        if (getParent() instanceof CellLayoutChildren) {
+            CellLayoutChildren parent = (CellLayoutChildren) getParent();
+            if (parent != null) {
+                CellLayout layout = (CellLayout) parent.getParent();
+                layout.setPressedOrFocusedIcon((mPressedOrFocusedBackground != null) ? this : null);
+            }
         }
     }
 
diff --git a/src/com/android/launcher2/CellLayout.java b/src/com/android/launcher2/CellLayout.java
index a9ba88d..c1aa2d5 100644
--- a/src/com/android/launcher2/CellLayout.java
+++ b/src/com/android/launcher2/CellLayout.java
@@ -298,6 +298,7 @@
         mCountX = x;
         mCountY = y;
         mOccupied = new boolean[mCountX][mCountY];
+        requestLayout();
     }
 
     private void invalidateBubbleTextView(BubbleTextView icon) {
@@ -971,7 +972,8 @@
         return mChildren.getChildAt(x, y);
     }
 
-    public boolean animateChildToPosition(final View child, int cellX, int cellY, int duration) {
+    public boolean animateChildToPosition(final View child, int cellX, int cellY, int duration,
+            int delay) {
         CellLayoutChildren clc = getChildrenLayout();
         if (clc.indexOfChild(child) != -1 && !mOccupied[cellX][cellY]) {
             final LayoutParams lp = (LayoutParams) child.getLayoutParams();
@@ -996,6 +998,10 @@
             int newX = lp.x;
             int newY = lp.y;
 
+            lp.x = oldX;
+            lp.y = oldY;
+            child.requestLayout();
+
             PropertyValuesHolder x = PropertyValuesHolder.ofInt("x", oldX, newX);
             PropertyValuesHolder y = PropertyValuesHolder.ofInt("y", oldY, newY);
             ObjectAnimator oa = ObjectAnimator.ofPropertyValuesHolder(lp, x, y);
@@ -1023,6 +1029,7 @@
                     cancelled = true;
                 }
             });
+            oa.setStartDelay(delay);
             oa.start();
             return true;
         }
diff --git a/src/com/android/launcher2/CellLayoutChildren.java b/src/com/android/launcher2/CellLayoutChildren.java
index 6e78885..1caecc0 100644
--- a/src/com/android/launcher2/CellLayoutChildren.java
+++ b/src/com/android/launcher2/CellLayoutChildren.java
@@ -83,6 +83,7 @@
         lp.setup(mCellWidth, mCellHeight, mWidthGap, mHeightGap);
     }
 
+
     public void measureChild(View child) {
         final int cellWidth = mCellWidth;
         final int cellHeight = mCellHeight;
@@ -92,7 +93,6 @@
         int childWidthMeasureSpec = MeasureSpec.makeMeasureSpec(lp.width, MeasureSpec.EXACTLY);
         int childheightMeasureSpec = MeasureSpec.makeMeasureSpec(lp.height,
                 MeasureSpec.EXACTLY);
-
         child.measure(childWidthMeasureSpec, childheightMeasureSpec);
     }
 
diff --git a/src/com/android/launcher2/DragLayer.java b/src/com/android/launcher2/DragLayer.java
index af47bea..c4d75d6 100644
--- a/src/com/android/launcher2/DragLayer.java
+++ b/src/com/android/launcher2/DragLayer.java
@@ -25,6 +25,7 @@
 import android.view.KeyEvent;
 import android.view.MotionEvent;
 import android.view.View;
+import android.view.ViewParent;
 import android.widget.FrameLayout;
 import android.widget.ImageView;
 
@@ -62,13 +63,13 @@
         mLauncher = launcher;
         mDragController = controller;
     }
-    
+
     @Override
     public boolean dispatchKeyEvent(KeyEvent event) {
         return mDragController.dispatchKeyEvent(event) || super.dispatchKeyEvent(event);
     }
 
-    private boolean handleTouchDown(MotionEvent ev) {
+    private boolean handleTouchDown(MotionEvent ev, boolean intercept) {
         Rect hitRect = new Rect();
         int x = (int) ev.getX();
         int y = (int) ev.getY();
@@ -85,17 +86,21 @@
                 }
             }
         }
-        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 (mCurrentFolder != null && intercept) {
+            if (mCurrentFolder.isEditingName()) {
+                getDescendantRectRelativeToSelf(mCurrentFolder.getEditTextRegion(), hitRect);
                 if (!hitRect.contains(x, y)) {
-                    mLauncher.closeFolder();
+                    mCurrentFolder.dismissEditingName();
+                    return true;
                 }
             }
+
+            getDescendantRectRelativeToSelf(mCurrentFolder, hitRect);
+            if (!hitRect.contains(x, y)) {
+                mLauncher.closeFolder();
+                return true;
+            }
         }
         return false;
     }
@@ -103,7 +108,7 @@
     @Override
     public boolean onInterceptTouchEvent(MotionEvent ev) {
         if (ev.getAction() == MotionEvent.ACTION_DOWN) {
-            if (handleTouchDown(ev)) {
+            if (handleTouchDown(ev, true)) {
                 return true;
             }
         }
@@ -121,7 +126,7 @@
 
         if (ev.getAction() == MotionEvent.ACTION_DOWN) {
             if (ev.getAction() == MotionEvent.ACTION_DOWN) {
-                if (handleTouchDown(ev)) {
+                if (handleTouchDown(ev, false)) {
                     return true;
                 }
             }
@@ -143,6 +148,18 @@
         return mDragController.onTouchEvent(ev);
     }
 
+    private void getDescendantRectRelativeToSelf(View descendant, Rect r) {
+        descendant.getHitRect(r);
+
+        ViewParent viewParent = descendant.getParent();
+        while (viewParent instanceof View && viewParent != this) {
+            final View view = (View)viewParent;
+            r.offset(view.getLeft() + (int) (view.getTranslationX() + 0.5f) - view.getScrollX(),
+                    view.getTop() + (int) (view.getTranslationY() + 0.5f) - view.getScrollY());
+            viewParent = view.getParent();
+        }
+    }
+
     @Override
     public boolean dispatchUnhandledMove(View focused, int direction) {
         return mDragController.dispatchUnhandledMove(focused, direction);
diff --git a/src/com/android/launcher2/FastBitmapDrawable.java b/src/com/android/launcher2/FastBitmapDrawable.java
index 3e75fb6..9fa62da 100644
--- a/src/com/android/launcher2/FastBitmapDrawable.java
+++ b/src/com/android/launcher2/FastBitmapDrawable.java
@@ -64,6 +64,10 @@
         mPaint.setAlpha(alpha);
     }
 
+    public void setFilterBitmap(boolean filterBitmap) {
+        mPaint.setFilterBitmap(filterBitmap);
+    }
+
     public int getAlpha() {
         return mAlpha;
     }
diff --git a/src/com/android/launcher2/Folder.java b/src/com/android/launcher2/Folder.java
index d81183c..960fa55 100644
--- a/src/com/android/launcher2/Folder.java
+++ b/src/com/android/launcher2/Folder.java
@@ -23,16 +23,22 @@
 import android.animation.ValueAnimator;
 import android.animation.ValueAnimator.AnimatorUpdateListener;
 import android.content.Context;
-import android.graphics.Color;
+import android.content.res.Resources;
 import android.graphics.Rect;
 import android.graphics.drawable.Drawable;
 import android.util.AttributeSet;
+import android.view.ActionMode;
+import android.view.KeyEvent;
 import android.view.LayoutInflater;
+import android.view.Menu;
+import android.view.MenuItem;
 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.view.inputmethod.EditorInfo;
+import android.view.inputmethod.InputMethodManager;
 import android.widget.AdapterView;
 import android.widget.LinearLayout;
 import android.widget.TextView;
@@ -48,7 +54,8 @@
  * Represents a set of icons chosen by the user or generated by the system.
  */
 public class Folder extends LinearLayout implements DragSource, OnItemLongClickListener,
-        OnItemClickListener, OnClickListener, View.OnLongClickListener, DropTarget, FolderListener {
+        OnItemClickListener, OnClickListener, View.OnLongClickListener, DropTarget, FolderListener,
+        TextView.OnEditorActionListener {
 
     protected DragController mDragController;
 
@@ -90,6 +97,11 @@
     private int[] mEmptyCell = new int[2];
     private Alarm mReorderAlarm = new Alarm();
     private Alarm mOnExitAlarm = new Alarm();
+    private TextView mFolderName;
+    private int mFolderNameHeight;
+
+    private boolean mIsEditingName = false;
+    private InputMethodManager mInputMethodManager;
 
     /**
      * Used to inflate the Workspace from XML.
@@ -102,10 +114,14 @@
         setAlwaysDrawnWithCacheEnabled(false);
         mInflater = LayoutInflater.from(context);
         mIconCache = ((LauncherApplication)context.getApplicationContext()).getIconCache();
-        mExpandDuration = getResources().getInteger(R.integer.config_folderAnimDuration);
-
         mMaxCountX = LauncherModel.getCellCountX() - 1;
         mMaxCountY = LauncherModel.getCellCountY() - 1;
+
+        mInputMethodManager = (InputMethodManager)
+                mContext.getSystemService(Context.INPUT_METHOD_SERVICE);
+
+        Resources res = getResources();
+        mExpandDuration = res.getInteger(R.integer.config_folderAnimDuration);
     }
 
     @Override
@@ -114,8 +130,37 @@
         mContent = (CellLayout) findViewById(R.id.folder_content);
         mContent.setGridSize(0, 0);
         mContent.enableHardwareLayers();
+        mFolderName = (TextView) findViewById(R.id.folder_name);
+
+        // We find out how tall the text view wants to be (it is set to wrap_content), so that
+        // we can allocate the appropriate amount of space for it.
+        int measureSpec = MeasureSpec.UNSPECIFIED;
+        mFolderName.measure(measureSpec, measureSpec);
+        mFolderNameHeight = mFolderName.getMeasuredHeight();
+
+        // We disable action mode for now since it messes up the view on phones
+        mFolderName.setCustomSelectionActionModeCallback(mActionModeCallback);
+        mFolderName.setCursorVisible(false);
+        mFolderName.setOnEditorActionListener(this);
     }
 
+    private ActionMode.Callback mActionModeCallback = new ActionMode.Callback() {
+        public boolean onActionItemClicked(ActionMode mode, MenuItem item) {
+            return false;
+        }
+
+        public boolean onCreateActionMode(ActionMode mode, Menu menu) {
+            return false;
+        }
+
+        public void onDestroyActionMode(ActionMode mode) {
+        }
+
+        public boolean onPrepareActionMode(ActionMode mode, Menu menu) {
+            return false;
+        }
+    };
+
     public void onItemClick(AdapterView parent, View v, int position, long id) {
         ShortcutInfo app = (ShortcutInfo) parent.getItemAtPosition(position);
         int[] pos = new int[2];
@@ -138,6 +183,17 @@
         }
     }
 
+    private Rect mHitRect = new Rect();
+    public boolean onInterceptTouchEvent(MotionEvent ev) {
+        if (ev.getAction() == MotionEvent.ACTION_DOWN) {
+            mFolderName.getHitRect(mHitRect);
+            if (mHitRect.contains((int) ev.getX(), (int) ev.getY()) && !mIsEditingName) {
+                startEditingFolderName();
+            }
+        }
+        return false;
+    }
+
     public boolean onLongClick(View v) {
         Object tag = v.getTag();
         if (tag instanceof ShortcutInfo) {
@@ -158,13 +214,44 @@
             mCurrentDragView = v;
             mContent.removeView(mCurrentDragView);
             mInfo.remove(item);
-        } else {
-            mLauncher.closeFolder(this);
-            mLauncher.showRenameDialog(mInfo);
         }
         return true;
     }
 
+    public boolean isEditingName() {
+        return mIsEditingName;
+    }
+
+    public void startEditingFolderName() {
+        mFolderName.setCursorVisible(true);
+        mIsEditingName = true;
+    }
+
+    public void dismissEditingName() {
+        mInputMethodManager.hideSoftInputFromWindow(getWindowToken(), 0);
+        doneEditingFolderName(true);
+    }
+
+    public void doneEditingFolderName(boolean commit) {
+        mInfo.setTitle(mFolderName.getText());
+        LauncherModel.updateItemInDatabase(mLauncher, mInfo);
+        mFolderName.setCursorVisible(false);
+        mFolderName.clearFocus();
+        mIsEditingName = false;
+    }
+
+    public boolean onEditorAction(TextView v, int actionId, KeyEvent event) {
+        if (actionId == EditorInfo.IME_ACTION_DONE) {
+            dismissEditingName();
+            return true;
+        }
+        return false;
+    }
+
+    public View getEditTextRegion() {
+        return mFolderName;
+    }
+
     public Drawable getDragDrawable() {
         return mIconDrawable;
     }
@@ -216,12 +303,13 @@
         // forcing a layout
         // TODO: find out if this is still necessary
         mContent.requestLayout();
-        requestFocus();
     }
 
     void onClose() {
-        final Workspace workspace = mLauncher.getWorkspace();
-        workspace.getChildAt(workspace.getCurrentPage()).requestFocus();
+        CellLayoutChildren clc = (CellLayoutChildren) getParent();
+        final CellLayout cellLayout = (CellLayout) clc.getParent();
+        cellLayout.removeViewWithoutMarkingCells(Folder.this);
+        clearFocus();
     }
 
     void bind(FolderInfo info) {
@@ -234,6 +322,7 @@
         }
         mItemsInvalidated = true;
         mInfo.addListener(this);
+        mFolderName.setText(mInfo.title);
     }
 
     /**
@@ -322,10 +411,7 @@
     public void animateClosed() {
         if (!(getParent() instanceof CellLayoutChildren)) return;
 
-        CellLayoutChildren clc = (CellLayoutChildren) getParent();
-        final CellLayout cellLayout = (CellLayout) clc.getParent();
         ObjectAnimator oa;
-
         if (mMode == PARTIAL_GROW) {
             PropertyValuesHolder alpha = PropertyValuesHolder.ofFloat("alpha", 0);
             PropertyValuesHolder scaleX = PropertyValuesHolder.ofFloat("scaleX", 0.9f);
@@ -356,8 +442,8 @@
         oa.addListener(new AnimatorListenerAdapter() {
             @Override
             public void onAnimationEnd(Animator animation) {
+                onClose();
                 onCloseComplete();
-                cellLayout.removeViewWithoutMarkingCells(Folder.this);
                 mState = STATE_SMALL;
             }
             @Override
@@ -438,6 +524,8 @@
         int startX;
         int endX;
         int startY;
+        int delay = 0;
+        float delayAmount = 30;
         if (readingOrderGreaterThan(target, empty)) {
             wrap = empty[0] >= mContent.getCountX() - 1;
             startY = wrap ? empty[1] + 1 : empty[1];
@@ -447,9 +535,11 @@
                 for (int x = startX; x <= endX; x++) {
                     View v = mContent.getChildAt(x,y);
                     if (mContent.animateChildToPosition(v, empty[0], empty[1],
-                            REORDER_ANIMATION_DURATION)) {
+                            REORDER_ANIMATION_DURATION, delay)) {
                         empty[0] = x;
                         empty[1] = y;
+                        delay += delayAmount;
+                        delayAmount *= 0.9;
                     }
                 }
             }
@@ -462,9 +552,11 @@
                 for (int x = startX; x >= endX; x--) {
                     View v = mContent.getChildAt(x,y);
                     if (mContent.animateChildToPosition(v, empty[0], empty[1],
-                            REORDER_ANIMATION_DURATION)) {
+                            REORDER_ANIMATION_DURATION, delay)) {
                         empty[0] = x;
                         empty[1] = y;
+                        delay += delayAmount;
+                        delayAmount *= 0.9;
                     }
                 }
             }
@@ -591,7 +683,9 @@
         CellLayout.LayoutParams lp = (CellLayout.LayoutParams) getLayoutParams();
 
         int width = getPaddingLeft() + getPaddingRight() + mContent.getDesiredWidth();
-        int height = getPaddingTop() + getPaddingBottom() + mContent.getDesiredHeight();
+        // Technically there is no padding at the bottom, but we add space equal to the padding
+        // and have to account for that here.
+        int height = getPaddingTop() + mContent.getDesiredHeight() + mFolderNameHeight;
 
         int centerX = iconLp.x + iconLp.width / 2;
         int centerY = iconLp.y + iconLp.height / 2;
@@ -732,6 +826,8 @@
 
     public void onItemsChanged() {
     }
+    public void onTitleChanged(CharSequence title) {
+    }
 
     public ArrayList<View> getItemsInReadingOrder() {
         return getItemsInReadingOrder(true);
diff --git a/src/com/android/launcher2/FolderIcon.java b/src/com/android/launcher2/FolderIcon.java
index 18b242b..db3dfe8 100644
--- a/src/com/android/launcher2/FolderIcon.java
+++ b/src/com/android/launcher2/FolderIcon.java
@@ -30,7 +30,8 @@
 import android.view.LayoutInflater;
 import android.view.View;
 import android.view.ViewGroup;
-import android.widget.FrameLayout;
+import android.widget.ImageView;
+import android.widget.LinearLayout;
 import android.widget.TextView;
 
 import com.android.launcher.R;
@@ -42,39 +43,32 @@
 /**
  * An icon that can appear on in the workspace representing an {@link UserFolder}.
  */
-public class FolderIcon extends FrameLayout implements FolderListener {
+public class FolderIcon extends LinearLayout implements FolderListener {
     private Launcher mLauncher;
     Folder mFolder;
     FolderInfo mInfo;
 
     // The number of icons to display in the
-    private static final int NUM_ITEMS_IN_PREVIEW = 4;
+    private static final int NUM_ITEMS_IN_PREVIEW = 3;
     private static final int CONSUMPTION_ANIMATION_DURATION = 100;
 
     // The degree to which the inner ring grows when accepting drop
     private static final float INNER_RING_GROWTH_FACTOR = 0.1f;
 
-    // The degree to which the inner ring is scaled in its natural state
-    private static final float INNER_RING_BASELINE_SCALE = 1.0f;
-
-    // The degree to which the outer ring grows when accepting drop
-    private static final float OUTER_RING_BASELINE_SCALE = 0.7f;
-
     // The degree to which the outer ring is scaled in its natural state
-    private static final float OUTER_RING_GROWTH_FACTOR = 0.3f;
+    private static final float OUTER_RING_GROWTH_FACTOR = 0.4f;
 
     // The amount of vertical spread between items in the stack [0...1]
-    private static final float PERSPECTIVE_SHIFT_FACTOR = 0.3f;
+    private static final float PERSPECTIVE_SHIFT_FACTOR = 0.24f;
 
     // The degree to which the item in the back of the stack is scaled [0...1]
     // (0 means it's not scaled at all, 1 means it's scaled to nothing)
-    private static final float PERSPECTIVE_SCALE_FACTOR = 0.3f;
-
-    // The percentage of the FolderIcons view that will be dedicated to the items preview
-    private static final float SPACE_PERCENTAGE_FOR_ICONS = 0.8f;
+    private static final float PERSPECTIVE_SCALE_FACTOR = 0.35f;
 
     private int mOriginalWidth = -1;
     private int mOriginalHeight = -1;
+    private ImageView mPreviewBackground;
+    private BubbleTextView mFolderName;
 
     FolderRingAnimator mFolderRingAnimator = null;
 
@@ -98,9 +92,10 @@
 
         FolderIcon icon = (FolderIcon) LayoutInflater.from(launcher).inflate(resId, group, false);
 
-        final Resources resources = launcher.getResources();
-        Drawable d = iconCache.getFullResIcon(resources, R.drawable.portal_ring_inner_holo);
-        icon.setBackgroundDrawable(d);
+        icon.mFolderName = (BubbleTextView) icon.findViewById(R.id.folder_name);
+        icon.mFolderName.setText(folderInfo.title);
+        icon.mPreviewBackground = (ImageView) icon.findViewById(R.id.preview_background);
+
         icon.setTag(folderInfo);
         icon.setOnClickListener(launcher);
         icon.mInfo = folderInfo;
@@ -121,31 +116,36 @@
     public static class FolderRingAnimator {
         public int mFolderLocX;
         public int mFolderLocY;
-        public float mOuterRingScale;
-        public float mInnerRingScale;
+        public float mOuterRingSize;
+        public float mInnerRingSize;
         public FolderIcon mFolderIcon = null;
         private Launcher mLauncher;
         public Drawable mOuterRingDrawable = null;
         public Drawable mInnerRingDrawable = null;
         public static Drawable sSharedOuterRingDrawable = null;
         public static Drawable sSharedInnerRingDrawable = null;
+        public static int sPreviewSize = -1;
+        public static int sPreviewPadding = -1;
+
         private ValueAnimator mAcceptAnimator;
         private ValueAnimator mNeutralAnimator;
 
         public FolderRingAnimator(Launcher launcher, FolderIcon folderIcon) {
             mLauncher = launcher;
             mFolderIcon = folderIcon;
-            mOuterRingDrawable =
-                    launcher.getResources().getDrawable(R.drawable.portal_ring_outer_holo);
-            mInnerRingDrawable =
-                    launcher.getResources().getDrawable(R.drawable.portal_ring_inner_holo);
+            Resources res = launcher.getResources();
+            mOuterRingDrawable = res.getDrawable(R.drawable.portal_ring_outer_holo);
+            mInnerRingDrawable = res.getDrawable(R.drawable.portal_ring_inner_holo);
+
+            if (sPreviewSize < 0 || sPreviewPadding < 0) {
+                sPreviewSize = res.getDimensionPixelSize(R.dimen.folder_preview_size);
+                sPreviewPadding = res.getDimensionPixelSize(R.dimen.folder_preview_padding);
+            }
             if (sSharedOuterRingDrawable == null) {
-                sSharedOuterRingDrawable =
-                        launcher.getResources().getDrawable(R.drawable.portal_ring_outer_holo);
+                sSharedOuterRingDrawable = res.getDrawable(R.drawable.portal_ring_outer_holo);
             }
             if (sSharedInnerRingDrawable == null) {
-                sSharedInnerRingDrawable =
-                        launcher.getResources().getDrawable(R.drawable.portal_ring_inner_holo);
+                sSharedInnerRingDrawable = res.getDrawable(R.drawable.portal_ring_inner_holo);
             }
         }
 
@@ -164,8 +164,8 @@
             mAcceptAnimator.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;
+                    mOuterRingSize = (1 + percent * OUTER_RING_GROWTH_FACTOR) * sPreviewSize;
+                    mInnerRingSize = (1 + percent * INNER_RING_GROWTH_FACTOR) * sPreviewSize;
                     mLauncher.getWorkspace().invalidate();
                     if (mFolderIcon != null) {
                         mFolderIcon.invalidate();
@@ -176,7 +176,7 @@
                 @Override
                 public void onAnimationStart(Animator animation) {
                     if (mFolderIcon != null) {
-                        mFolderIcon.setBackgroundDrawable(null);
+                        mFolderIcon.mPreviewBackground.setVisibility(INVISIBLE);
                     }
                 }
             });
@@ -192,10 +192,8 @@
             mNeutralAnimator.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;
+                    mOuterRingSize = (1 + (1 - percent) * OUTER_RING_GROWTH_FACTOR) * sPreviewSize;
+                    mInnerRingSize = (1 + (1 - percent) * INNER_RING_GROWTH_FACTOR) * sPreviewSize;
                     mLauncher.getWorkspace().invalidate();
                     if (mFolderIcon != null) {
                         mFolderIcon.invalidate();
@@ -206,7 +204,7 @@
                 @Override
                 public void onAnimationEnd(Animator animation) {
                     if (mFolderIcon != null) {
-                        mFolderIcon.setBackgroundDrawable(mInnerRingDrawable);
+                        mFolderIcon.mPreviewBackground.setVisibility(VISIBLE);
                     }
                     mLauncher.getWorkspace().hideFolderAccept(FolderRingAnimator.this);
                 }
@@ -220,12 +218,12 @@
             loc[1] = mFolderLocY;
         }
 
-        public float getOuterRingScale() {
-            return mOuterRingScale;
+        public float getOuterRingSize() {
+            return mOuterRingSize;
         }
 
-        public float getInnerRingScale() {
-            return mInnerRingScale;
+        public float getInnerRingSize() {
+            return mInnerRingSize;
         }
     }
 
@@ -258,7 +256,7 @@
         mLauncher.getWorkspace().getLocationInWindow(wsLocation);
 
         int x = tvLocation[0] - wsLocation[0] + getMeasuredWidth() / 2;
-        int y = tvLocation[1] - wsLocation[1] + getMeasuredHeight() / 2;
+        int y = tvLocation[1] - wsLocation[1] + FolderRingAnimator.sPreviewSize / 2;
         mFolderRingAnimator.setLocation(x, y);
     }
 
@@ -296,31 +294,38 @@
 
     @Override
     protected void dispatchDraw(Canvas canvas) {
+        super.dispatchDraw(canvas);
+
         if (mFolder == null) return;
         if (mFolder.getItemCount() == 0) return;
 
         canvas.save();
         TextView v = (TextView) mFolder.getItemAt(0);
         Drawable d = v.getCompoundDrawables()[1];
+        int intrinsicIconSize = d.getIntrinsicHeight();
 
         if (mOriginalWidth < 0 || mOriginalHeight < 0) {
             mOriginalWidth = getMeasuredWidth();
             mOriginalHeight = getMeasuredHeight();
         }
+        final int previewSize = FolderRingAnimator.sPreviewSize;
+        final int previewPadding = FolderRingAnimator.sPreviewPadding;
 
-        int unscaledHeight = (int) (d.getIntrinsicHeight() * (1 + PERSPECTIVE_SHIFT_FACTOR));
-        float baselineIconScale = SPACE_PERCENTAGE_FOR_ICONS / (unscaledHeight / (mOriginalHeight * 1.0f));
+        int halfAvailableSpace = (previewSize - 2 * previewPadding) / 2;
+        // cos(45) = 0.707  + ~= 0.1)
+        int availableSpace = (int) (halfAvailableSpace * (1 + 0.8f));
 
-        int baselineHeight = (int) (d.getIntrinsicHeight() * baselineIconScale);
-        int totalStackHeight = (int) (baselineHeight * (1 + PERSPECTIVE_SHIFT_FACTOR));
-        int baselineWidth = (int) (d.getIntrinsicWidth() * baselineIconScale);
-        float maxPerpectiveShift = baselineHeight * PERSPECTIVE_SHIFT_FACTOR;
+        int unscaledHeight = (int) (intrinsicIconSize * (1 + PERSPECTIVE_SHIFT_FACTOR));
+        float baselineIconScale = (1.0f * availableSpace / unscaledHeight);
+
+        int baselineSize = (int) (intrinsicIconSize * baselineIconScale);
+        float maxPerspectiveShift = baselineSize * PERSPECTIVE_SHIFT_FACTOR;
 
         ArrayList<View> items = mFolder.getItemsInReadingOrder(false);
         int firstItemIndex = Math.max(0, items.size() - NUM_ITEMS_IN_PREVIEW);
 
-        int xShift = (int) (mOriginalWidth - baselineWidth) / 2;
-        int yShift = (int) (mOriginalHeight - totalStackHeight) / 2;
+        int xShift = (mOriginalWidth - 2 * halfAvailableSpace) / 2;
+        int yShift = previewPadding;
         canvas.translate(xShift, yShift);
         for (int i = firstItemIndex; i < items.size(); i++) {
             int index = i - firstItemIndex;
@@ -328,10 +333,17 @@
 
             float r = (index * 1.0f) / (NUM_ITEMS_IN_PREVIEW - 1);
             float scale = (1 - PERSPECTIVE_SCALE_FACTOR * (1 - r));
-            r = (float) Math.pow(r, 2);
 
-            float transY = r * maxPerpectiveShift;
-            float transX = (1 - scale) * baselineWidth / 2.0f;
+            //r = (float) Math.pow(r, 2);
+
+            float offset = (1 - r) * maxPerspectiveShift;
+            float scaledSize = scale * baselineSize;
+            float scaleOffsetCorrection = (1 - scale) * baselineSize;
+
+            // We want to imagine our coordinates from the bottom left, growing up and to the
+            // right. This is natural for the x-axis, but for the y-axis, we have to invert things.
+            float transY = 2 * halfAvailableSpace - (offset + scaledSize + scaleOffsetCorrection);
+            float transX = offset + scaleOffsetCorrection;
 
             v = (TextView) items.get(i);
             d = v.getCompoundDrawables()[1];
@@ -342,10 +354,12 @@
 
             int overlayAlpha = (int) (80 * (1 - r));
             if (d != null) {
-                d.setBounds(0, 0, d.getIntrinsicWidth(), d.getIntrinsicHeight());
+                d.setBounds(0, 0, intrinsicIconSize, intrinsicIconSize);
+                d.setFilterBitmap(true);
                 d.setColorFilter(Color.argb(overlayAlpha, 0, 0, 0), PorterDuff.Mode.SRC_ATOP);
                 d.draw(canvas);
                 d.clearColorFilter();
+                d.setFilterBitmap(false);
             }
             canvas.restore();
         }
@@ -366,4 +380,10 @@
         invalidate();
         requestLayout();
     }
+
+    public void onTitleChanged(CharSequence title) {
+        mFolderName.setText(title);
+        mFolderName.invalidate();
+        mFolderName.requestLayout();
+    }
 }
diff --git a/src/com/android/launcher2/FolderInfo.java b/src/com/android/launcher2/FolderInfo.java
index 805a51f..b5b5b29 100644
--- a/src/com/android/launcher2/FolderInfo.java
+++ b/src/com/android/launcher2/FolderInfo.java
@@ -70,6 +70,13 @@
         }
     }
 
+    public void setTitle(CharSequence title) {
+        this.title = title;
+        for (int i = 0; i < listeners.size(); i++) {
+            listeners.get(i).onTitleChanged(title);
+        }
+    }
+
     @Override
     void onAddToDatabase(ContentValues values) {
         super.onAddToDatabase(values);
@@ -95,6 +102,7 @@
     interface FolderListener {
         public void onAdd(ShortcutInfo item);
         public void onRemove(ShortcutInfo item);
+        public void onTitleChanged(CharSequence title);
         public void onItemsChanged();
     }
 }
diff --git a/src/com/android/launcher2/Launcher.java b/src/com/android/launcher2/Launcher.java
index bda5591..323c527 100644
--- a/src/com/android/launcher2/Launcher.java
+++ b/src/com/android/launcher2/Launcher.java
@@ -1728,7 +1728,12 @@
         if (mState == State.APPS_CUSTOMIZE) {
             showWorkspace(true);
         } else if (mWorkspace.getOpenFolder() != null) {
-            closeFolder();
+            Folder openFolder = mWorkspace.getOpenFolder();
+            if (openFolder.isEditingName()) {
+                openFolder.dismissEditingName();
+            } else {
+                closeFolder();
+            }
         } else if (isPreviewVisible()) {
             dismissPreview(mPreviousView);
             dismissPreview(mNextView);
@@ -1760,7 +1765,6 @@
         }
 
         folder.animateClosed();
-        folder.onClose();
     }
 
     /**
diff --git a/src/com/android/launcher2/PagedView.java b/src/com/android/launcher2/PagedView.java
index 1fa23cf..ca0847e 100644
--- a/src/com/android/launcher2/PagedView.java
+++ b/src/com/android/launcher2/PagedView.java
@@ -692,7 +692,7 @@
         }
         View v = getPageAt(focusablePage);
         if (v != null) {
-            v.requestFocus(direction, previouslyFocusedRect);
+            return v.requestFocus(direction, previouslyFocusedRect);
         }
         return false;
     }
diff --git a/src/com/android/launcher2/Workspace.java b/src/com/android/launcher2/Workspace.java
index 1048fd5..b07e22b 100644
--- a/src/com/android/launcher2/Workspace.java
+++ b/src/com/android/launcher2/Workspace.java
@@ -1199,11 +1199,12 @@
             View currentPage = getChildAt(getCurrentPage());
             Matrix m = currentPage.getMatrix();
 
-            // Draw outer ring
             FolderRingAnimator fra = mFolderOuterRings.get(i);
+
+            // Draw outer ring
             Drawable d = FolderRingAnimator.sSharedOuterRingDrawable;
-            int width = (int) (d.getIntrinsicWidth() * fra.getOuterRingScale());
-            int height = (int) (d.getIntrinsicHeight() * fra.getOuterRingScale());
+            int width = (int) fra.getOuterRingSize();
+            int height = width;
             fra.getLocation(mTempLocation);
 
             // First we map the folder's location from window coordinates to its containing
@@ -1226,34 +1227,28 @@
             canvas.restore();
 
             // Draw inner ring
-            if (fra.mFolderIcon != null) {
-                int folderWidth = fra.mFolderIcon != null ?
-                        fra.mFolderIcon.getMeasuredWidth() : mCellWidth;
-                int folderHeight = fra.mFolderIcon != null ?
-                        fra.mFolderIcon.getMeasuredWidth() : mCellHeight;
-                d = FolderRingAnimator.sSharedInnerRingDrawable;
-                width = (int) (folderWidth * fra.getInnerRingScale());
-                height = (int) (folderHeight * fra.getInnerRingScale());
+            d = FolderRingAnimator.sSharedInnerRingDrawable;
+            width = (int) fra.getInnerRingSize();
+            height = width;
 
-                // First we map the folder's location from window coordinates to its containing
-                // CellLayout's coordinates. Then we transform the coordinates according to the
-                // CellLayout's transform. Finally, we map this back into the coordinates of the
-                // the window (ie. Workspace).
-                x = mTempLocation[0] + mScrollX - width / 2 - currentPage.getLeft();
-                y = mTempLocation[1] + mScrollY - height / 2 - currentPage.getTop();
-                mTempFloatTuple[0] = x;
-                mTempFloatTuple[1] = y;
-                m.mapPoints(mTempFloatTuple);
-                x = (int) (mTempFloatTuple[0]) + currentPage.getLeft();
-                y = (int) (mTempFloatTuple[1]) + currentPage.getTop();
+            // First we map the folder's location from window coordinates to its containing
+            // CellLayout's coordinates. Then we transform the coordinates according to the
+            // CellLayout's transform. Finally, we map this back into the coordinates of the
+            // the window (ie. Workspace).
+            x = mTempLocation[0] + mScrollX - width / 2 - currentPage.getLeft();
+            y = mTempLocation[1] + mScrollY - height / 2 - currentPage.getTop();
+            mTempFloatTuple[0] = x;
+            mTempFloatTuple[1] = y;
+            m.mapPoints(mTempFloatTuple);
+            x = (int) (mTempFloatTuple[0]) + currentPage.getLeft();
+            y = (int) (mTempFloatTuple[1]) + currentPage.getTop();
 
-                canvas.save();
-                canvas.translate(x, y);
-                d.setBounds(0, 0, (int) (width * currentPage.getScaleX()),
-                        (int) (height * currentPage.getScaleY()));
-                d.draw(canvas);
-                canvas.restore();
-            }
+            canvas.save();
+            canvas.translate(x, y);
+            d.setBounds(0, 0, (int) (width * currentPage.getScaleX()),
+                    (int) (height * currentPage.getScaleY()));
+            d.draw(canvas);
+            canvas.restore();
         }
         super.onDraw(canvas);
     }
@@ -3025,8 +3020,9 @@
                 mCellWidth = mDragTargetLayout.getCellWidth();
                 mCellHeight = mDragTargetLayout.getCellHeight();
             }
+
             int x = tvLocation[0] - wsLocation[0] + v.getMeasuredWidth() / 2;
-            int y = tvLocation[1] - wsLocation[1] + v.getMeasuredHeight() / 2;
+            int y = tvLocation[1] - wsLocation[1] + FolderRingAnimator.sPreviewSize / 2;
 
             if (mDragFolderRingAnimator == null) {
                 mDragFolderRingAnimator = new FolderRingAnimator(mLauncher, null);