Merge "New folder visualization, renaming, staggering reorder"
diff --git a/AndroidManifest.xml b/AndroidManifest.xml
index 63e3c66..d9404a7 100644
--- a/AndroidManifest.xml
+++ b/AndroidManifest.xml
@@ -61,7 +61,7 @@
     <uses-permission android:name="android.permission.BIND_APPWIDGET" />
     <uses-permission android:name="com.android.launcher.permission.READ_SETTINGS" />
     <uses-permission android:name="com.android.launcher.permission.WRITE_SETTINGS" />
-    
+
     <application
         android:name="com.android.launcher2.LauncherApplication"
         android:process="@string/process"
@@ -69,14 +69,12 @@
         android:icon="@drawable/ic_launcher_home"
         android:hardwareAccelerated="@bool/config_hardwareAccelerated"
         android:largeHeap="true">
-
         <activity
             android:name="com.android.launcher2.Launcher"
             android:launchMode="singleTask"
             android:clearTaskOnLaunch="true"
             android:stateNotNeeded="true"
-            android:theme="@style/Theme"
-            android:windowSoftInputMode="stateUnspecified|adjustNothing">
+            android:theme="@style/Theme">
             <intent-filter>
                 <action android:name="android.intent.action.MAIN" />
                 <category android:name="android.intent.category.HOME" />
diff --git a/res/drawable-hdpi/portal_container_holo.9.png b/res/drawable-hdpi/portal_container_holo.9.png
index af2fa98..a2fbcb6 100644
--- a/res/drawable-hdpi/portal_container_holo.9.png
+++ b/res/drawable-hdpi/portal_container_holo.9.png
Binary files differ
diff --git a/res/drawable-hdpi/portal_ring_inner_holo.png b/res/drawable-hdpi/portal_ring_inner_holo.png
index 8a9e85b..e671d1b 100644
--- a/res/drawable-hdpi/portal_ring_inner_holo.png
+++ b/res/drawable-hdpi/portal_ring_inner_holo.png
Binary files differ
diff --git a/res/drawable-hdpi/portal_ring_outer_holo.png b/res/drawable-hdpi/portal_ring_outer_holo.png
index 5b46419..7aad607 100644
--- a/res/drawable-hdpi/portal_ring_outer_holo.png
+++ b/res/drawable-hdpi/portal_ring_outer_holo.png
Binary files differ
diff --git a/res/drawable-mdpi/portal_container_holo.9.png b/res/drawable-mdpi/portal_container_holo.9.png
index 42aca5f..d2f9b58 100644
--- a/res/drawable-mdpi/portal_container_holo.9.png
+++ b/res/drawable-mdpi/portal_container_holo.9.png
Binary files differ
diff --git a/res/drawable-mdpi/portal_ring_inner_holo.png b/res/drawable-mdpi/portal_ring_inner_holo.png
index 4a64694..dc0c041 100644
--- a/res/drawable-mdpi/portal_ring_inner_holo.png
+++ b/res/drawable-mdpi/portal_ring_inner_holo.png
Binary files differ
diff --git a/res/drawable-mdpi/portal_ring_outer_holo.png b/res/drawable-mdpi/portal_ring_outer_holo.png
index 90cf77f..5a7e740 100644
--- a/res/drawable-mdpi/portal_ring_outer_holo.png
+++ b/res/drawable-mdpi/portal_ring_outer_holo.png
Binary files differ
diff --git a/res/layout-land/folder_icon.xml b/res/layout-land/folder_icon.xml
index c76a756..60569c5 100644
--- a/res/layout-land/folder_icon.xml
+++ b/res/layout-land/folder_icon.xml
@@ -4,9 +4,9 @@
      Licensed under the Apache License, Version 2.0 (the "License");
      you may not use this file except in compliance with the License.
      You may obtain a copy of the License at
-  
+
           http://www.apache.org/licenses/LICENSE-2.0
-  
+
      Unless required by applicable law or agreed to in writing, software
      distributed under the License is distributed on an "AS IS" BASIS,
      WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
@@ -14,5 +14,19 @@
      limitations under the License.
 -->
 
-<com.android.launcher2.FolderIcon xmlns:android="http://schemas.android.com/apk/res/android"
- 	style="@style/WorkspaceIcon.Landscape" />
+<com.android.launcher2.FolderIcon
+    xmlns:android="http://schemas.android.com/apk/res/android"
+    xmlns:launcher="http://schemas.android.com/apk/res/com.android.launcher"
+    android:layout_width="match_parent"
+    android:layout_height="match_parent"
+    android:orientation="vertical">
+    <ImageView
+        android:id="@+id/preview_background"
+        android:layout_gravity="center_horizontal"
+        android:layout_width="@dimen/folder_preview_size"
+        android:layout_height="@dimen/folder_preview_size"
+        android:src="@drawable/portal_ring_inner_holo"/>
+    <com.android.launcher2.BubbleTextView
+        android:id="@+id/folder_name"
+        style="@style/WorkspaceIcon.Landscape"/>
+</com.android.launcher2.FolderIcon>
diff --git a/res/layout-port/folder_icon.xml b/res/layout-port/folder_icon.xml
index 49049cf..3f776de 100644
--- a/res/layout-port/folder_icon.xml
+++ b/res/layout-port/folder_icon.xml
@@ -4,9 +4,9 @@
      Licensed under the Apache License, Version 2.0 (the "License");
      you may not use this file except in compliance with the License.
      You may obtain a copy of the License at
-  
+
           http://www.apache.org/licenses/LICENSE-2.0
-  
+
      Unless required by applicable law or agreed to in writing, software
      distributed under the License is distributed on an "AS IS" BASIS,
      WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
@@ -14,5 +14,19 @@
      limitations under the License.
 -->
 
-<com.android.launcher2.FolderIcon xmlns:android="http://schemas.android.com/apk/res/android"
- 	style="@style/WorkspaceIcon.Portrait" />
+<com.android.launcher2.FolderIcon
+    xmlns:android="http://schemas.android.com/apk/res/android"
+    xmlns:launcher="http://schemas.android.com/apk/res/com.android.launcher"
+    android:layout_width="match_parent"
+    android:layout_height="match_parent"
+    android:orientation="vertical">
+    <ImageView
+        android:id="@+id/preview_background"
+        android:layout_gravity="center_horizontal"
+        android:layout_width="@dimen/folder_preview_size"
+        android:layout_height="@dimen/folder_preview_size"
+        android:src="@drawable/portal_ring_inner_holo"/>
+    <com.android.launcher2.BubbleTextView
+        android:id="@+id/folder_name"
+        style="@style/WorkspaceIcon.Portrait"/>
+</com.android.launcher2.FolderIcon>
diff --git a/res/layout/user_folder.xml b/res/layout/user_folder.xml
index 5ef959d..7e25f35 100644
--- a/res/layout/user_folder.xml
+++ b/res/layout/user_folder.xml
@@ -17,16 +17,20 @@
 <com.android.launcher2.Folder
     xmlns:android="http://schemas.android.com/apk/res/android"
     xmlns:launcher="http://schemas.android.com/apk/res/com.android.launcher"
+    android:layout_width="wrap_content"
+    android:layout_height="wrap_content"
+    android:paddingLeft="@dimen/folder_padding"
+    android:paddingRight="@dimen/folder_padding"
+    android:paddingTop="@dimen/folder_padding"
+    android:paddingBottom="0dp"
     android:orientation="vertical"
     android:background="@drawable/portal_container_holo">
 
     <com.android.launcher2.CellLayout
-        android:id="@id/folder_content"
-        android:layout_width="match_parent"
-        android:layout_height="match_parent"
-
+        android:id="@+id/folder_content"
+        android:layout_width="wrap_content"
+        android:layout_height="wrap_content"
         android:cacheColorHint="#ff333333"
-
         android:hapticFeedbackEnabled="false"
         launcher:widthGap="@dimen/workspace_width_gap"
         launcher:heightGap="@dimen/workspace_height_gap"
@@ -36,4 +40,18 @@
         launcher:xAxisEndPadding="0dip"
         launcher:yAxisStartPadding="8dip"
         launcher:yAxisEndPadding="8dip"/>
-</com.android.launcher2.Folder>
+
+    <EditText
+        android:id="@+id/folder_name"
+        android:layout_width="match_parent"
+        android:layout_height="wrap_content"
+        android:layout_centerHorizontal="true"
+        android:paddingTop="@dimen/folder_content_name_gap"
+        android:paddingBottom="@dimen/folder_padding"
+        android:background="#00000000"
+        android:hint="@string/default_folder_name"
+        android:textSize="16sp"
+        android:textColor="#FFF"
+        android:gravity="center_horizontal"
+        android:singleLine="true"/>
+</com.android.launcher2.Folder>
\ No newline at end of file
diff --git a/res/values-large/dimens.xml b/res/values-large/dimens.xml
index 924832a..eb48859 100644
--- a/res/values-large/dimens.xml
+++ b/res/values-large/dimens.xml
@@ -106,9 +106,16 @@
     <!-- Max number of rows in all apps, because too many looks weird. -->
     <integer name="all_apps_view_maxCellCountY">6</integer>
 
+<!-- Workspace grid -->
     <!-- Padding applied to AppWidgets -->
     <dimen name="app_widget_padding_left">12dp</dimen>
     <dimen name="app_widget_padding_right">12dp</dimen>
     <dimen name="app_widget_padding_top">4dp</dimen>
     <dimen name="app_widget_padding_bottom">20dp</dimen>
+
+<!-- Folders -->
+    <!-- The size of the image which sits behind the preview of the folder contents -->
+    <dimen name="folder_preview_size">80dp</dimen>
+    <!-- The amount that the preview contents are inset from the preview background -->
+    <dimen name="folder_preview_padding">4dp</dimen>
 </resources>
diff --git a/res/values/dimens.xml b/res/values/dimens.xml
index 15dbc3a..141df06 100644
--- a/res/values/dimens.xml
+++ b/res/values/dimens.xml
@@ -83,9 +83,18 @@
     <dimen name="workspace_width_gap">-1dp</dimen>
     <dimen name="workspace_height_gap">-1dp</dimen>
 
+<!-- Workspace grid -->
     <!-- Padding applied to AppWidgets -->
     <dimen name="app_widget_padding_left">0dp</dimen>
     <dimen name="app_widget_padding_right">0dp</dimen>
     <dimen name="app_widget_padding_top">0dp</dimen>
     <dimen name="app_widget_padding_bottom">0dp</dimen>
+
+<!-- Folders -->
+    <!-- The size of the image which sits behind the preview of the folder contents -->
+    <dimen name="folder_preview_size">56dp</dimen>
+    <!-- The amount that the preview contents are inset from the preview background -->
+    <dimen name="folder_preview_padding">4dp</dimen>
+    <dimen name="folder_padding">18dp</dimen>
+    <dimen name="folder_content_name_gap">10dp</dimen>
 </resources>
diff --git a/res/values/strings.xml b/res/values/strings.xml
index 95f711c..bf03c3b 100644
--- a/res/values/strings.xml
+++ b/res/values/strings.xml
@@ -254,9 +254,12 @@
     <string name="default_browser_url" translatable="false">
         http://www.google.com/m?client=ms-{CID}&amp;source=android-home-hotseat</string>
 
-    <!--  Text to inform the user that they can't uninstall a system application -->
+    <!-- Text to inform the user that they can't uninstall a system application -->
     <string name="uninstall_system_app_text">This is a system application and cannot be uninstalled.</string>
 
     <!-- Title of the Android Dreams (screensaver) module -->
     <string name="dream_name">Rocket Launcher</string>
+
+    <!-- Default folder title -->
+    <string name="default_folder_name">Unnamed Folder</string>
 </resources>
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 7165865..7818ef4 100644
--- a/src/com/android/launcher2/Workspace.java
+++ b/src/com/android/launcher2/Workspace.java
@@ -1196,11 +1196,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
@@ -1223,34 +1224,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);
     }
@@ -3022,8 +3017,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);