Merge branch 'master' into honeycomb-release
diff --git a/Android.mk b/Android.mk
index dfe6788..89e626b 100644
--- a/Android.mk
+++ b/Android.mk
@@ -30,7 +30,7 @@
 
 LOCAL_OVERRIDES_PACKAGES := Home
 
-LOCAL_PROGUARD_FLAGS := -include $(LOCAL_PATH)/proguard.flags
+LOCAL_PROGUARD_FLAG_FILES := proguard.flags
 
 include $(BUILD_PACKAGE)
 
diff --git a/print_db.py b/print_db.py
new file mode 100755
index 0000000..ebcba6c
--- /dev/null
+++ b/print_db.py
@@ -0,0 +1,220 @@
+#!/usr/bin/env python2.5
+
+import cgi
+import os
+import shutil
+import sys
+import sqlite3
+
+SCREENS = 5
+COLUMNS = 4
+ROWS = 4
+CELL_SIZE = 110
+
+DIR = "db_files"
+AUTO_FILE = DIR + "/launcher.db"
+INDEX_FILE = DIR + "/index.html"
+
+def usage():
+  print "usage: print_db.py launcher.db -- prints a launcher.db"
+  print "usage: print_db.py -- adb pulls a launcher.db from a device"
+  print "       and prints it"
+  print
+  print "The dump will be created in a directory called db_files in cwd."
+  print "This script will delete any db_files directory you have now"
+
+
+def make_dir():
+  shutil.rmtree(DIR, True)
+  os.makedirs(DIR)
+
+def pull_file(fn):
+  print "pull_file: " + fn
+  rv = os.system("adb pull"
+    + " /data/data/com.android.launcher/databases/launcher.db"
+    + " " + fn);
+  if rv != 0:
+    print "adb pull failed"
+    sys.exit(1)
+
+def get_favorites(conn):
+  c = conn.cursor()
+  c.execute("SELECT * FROM favorites")
+  columns = [d[0] for d in c.description]
+  rows = []
+  for row in c:
+    rows.append(row)
+  return columns,rows
+
+def print_intent(out, id, i, cell):
+  if cell:
+    out.write("""<span class="intent" title="%s">shortcut</span>""" % (
+        cgi.escape(cell, True)
+      ))
+
+
+def print_icon(out, id, i, cell):
+  if cell:
+    icon_fn = "icon_%d.png" % id
+    out.write("""<img src="%s">""" % ( icon_fn ))
+    f = file(DIR + "/" + icon_fn, "w")
+    f.write(cell)
+    f.close()
+
+def print_cell(out, id, i, cell):
+  if not cell is None:
+    out.write(cgi.escape(str(cell)))
+
+FUNCTIONS = {
+  "intent": print_intent,
+  "icon": print_icon
+}
+
+def process_file(fn):
+  print "process_file: " + fn
+  conn = sqlite3.connect(fn)
+  columns,rows = get_favorites(conn)
+  data = [dict(zip(columns,row)) for row in rows]
+
+  out = file(INDEX_FILE, "w")
+  out.write("""<html>
+<head>
+<style type="text/css">
+.intent {
+  font-style: italic;
+}
+</style>
+</head>
+<body>
+""")
+
+  # Data table
+  out.write("<b>Favorites table</b><br/>\n")
+  out.write("""<html>
+<table border=1 cellspacing=0 cellpadding=4>
+<tr>
+""")
+  print_functions = []
+  for col in columns:
+    print_functions.append(FUNCTIONS.get(col, print_cell))
+  for i in range(0,len(columns)):
+    col = columns[i]
+    out.write("""  <th>%s</th>
+""" % ( col ))
+  out.write("""
+</tr>
+""")
+  for row in rows:
+    out.write("""<tr>
+""")
+    for i in range(0,len(row)):
+      cell = row[i]
+      # row[0] is always _id
+      out.write("""  <td>""")
+      print_functions[i](out, row[0], row, cell)
+      out.write("""</td>
+""")
+    out.write("""</tr>
+""")
+  out.write("""</table>
+""")
+
+  # Pages
+  screens = []
+  for i in range(0,SCREENS):
+    screen = []
+    for j in range(0,ROWS):
+      m = []
+      for k in range(0,COLUMNS):
+        m.append(None)
+      screen.append(m)
+    screens.append(screen)
+  occupied = "occupied"
+  for row in data:
+    screen = screens[row["screen"]]
+    # desktop
+    if row["container"] != -100:
+      continue
+    cellX = row["cellX"]
+    cellY = row["cellY"]
+    spanX = row["spanX"]
+    spanY = row["spanY"]
+    for j in range(cellY, cellY+spanY):
+      for k in range(cellX, cellX+spanX):
+        screen[j][k] = occupied
+    screen[cellY][cellX] = row
+  i=0
+  for screen in screens:
+    out.write("<br/><b>Screen %d</b><br/>\n" % i)
+    out.write("<table class=layout border=1 cellspacing=0 cellpadding=4>\n")
+    for m in screen:
+      out.write("  <tr>\n")
+      for cell in m:
+        if cell is None:
+          out.write("    <td width=%d height=%d></td>\n" %
+              (CELL_SIZE, CELL_SIZE))
+        elif cell == occupied:
+          pass
+        else:
+          cellX = cell["cellX"]
+          cellY = cell["cellY"]
+          spanX = cell["spanX"]
+          spanY = cell["spanY"]
+          intent = cell["intent"]
+          if intent:
+            title = "title=\"%s\"" % cgi.escape(cell["intent"], True)
+          else:
+            title = ""
+          out.write(("    <td colspan=%d rowspan=%d width=%d height=%d"
+              + " bgcolor=#dddddd align=center valign=middle %s>") % (
+                spanX, spanY,
+                (CELL_SIZE*spanX), (CELL_SIZE*spanY),
+                title))
+          itemType = cell["itemType"]
+          if itemType == 0:
+            out.write("""<img src="icon_%d.png">\n""" % ( cell["_id"] ))
+            out.write("<br/>\n")
+            out.write(cgi.escape(cell["title"]) + " <br/><i>(app)</i>")
+          elif itemType == 1:
+            out.write("""<img src="icon_%d.png">\n""" % ( cell["_id"] ))
+            out.write("<br/>\n")
+            out.write(cgi.escape(cell["title"]) + " <br/><i>(shortcut)</i>")
+          elif itemType == 2:
+            out.write("""<i>folder</i>""")
+          elif itemType == 3:
+            out.write("""<i>live folder</i>""")
+          elif itemType == 4:
+            out.write("<i>widget %d</i><br/>\n" % cell["appWidgetId"])
+          elif itemType == 1000:
+            out.write("""<i>clock</i>""")
+          elif itemType == 1001:
+            out.write("""<i>search</i>""")
+          elif itemType == 1002:
+            out.write("""<i>photo frame</i>""")
+          else:
+            out.write("<b>unknown type: %d</b>" % itemType)
+          out.write("</td>\n")
+      out.write("</tr>\n")
+    out.write("</table>\n")
+    i=i+1
+
+  out.write("""
+</body>
+</html>
+""")
+
+  out.close()
+
+def main(argv):
+  if len(argv) == 1:
+    make_dir()
+    pull_file(AUTO_FILE)
+    process_file(AUTO_FILE)
+  elif len(argv) == 2:
+    make_dir()
+    process_file(argv[1])
+  else:
+    usage()
+
+if __name__=="__main__":
+  main(sys.argv)
diff --git a/res/drawable-hdpi/all_apps_button_focused.png b/res/drawable-hdpi/all_apps_button_focused.png
index 55574ed..ace493b 100644
--- a/res/drawable-hdpi/all_apps_button_focused.png
+++ b/res/drawable-hdpi/all_apps_button_focused.png
Binary files differ
diff --git a/res/drawable-hdpi/all_apps_button_normal.png b/res/drawable-hdpi/all_apps_button_normal.png
index 7e7ec86..cde455d 100644
--- a/res/drawable-hdpi/all_apps_button_normal.png
+++ b/res/drawable-hdpi/all_apps_button_normal.png
Binary files differ
diff --git a/res/drawable-hdpi/all_apps_button_pressed.png b/res/drawable-hdpi/all_apps_button_pressed.png
index 3ccb5af..d5f9f54 100644
--- a/res/drawable-hdpi/all_apps_button_pressed.png
+++ b/res/drawable-hdpi/all_apps_button_pressed.png
Binary files differ
diff --git a/res/drawable-hdpi/hotseat_bg_center.9.png b/res/drawable-hdpi/hotseat_bg_center.9.png
index 468e766..c69bc87 100644
--- a/res/drawable-hdpi/hotseat_bg_center.9.png
+++ b/res/drawable-hdpi/hotseat_bg_center.9.png
Binary files differ
diff --git a/res/drawable-hdpi/hotseat_bg_left.9.png b/res/drawable-hdpi/hotseat_bg_left.9.png
index 433a10e..ca901da 100644
--- a/res/drawable-hdpi/hotseat_bg_left.9.png
+++ b/res/drawable-hdpi/hotseat_bg_left.9.png
Binary files differ
diff --git a/res/drawable-hdpi/hotseat_bg_right.9.png b/res/drawable-hdpi/hotseat_bg_right.9.png
index 4ea2a73..810c4d4 100644
--- a/res/drawable-hdpi/hotseat_bg_right.9.png
+++ b/res/drawable-hdpi/hotseat_bg_right.9.png
Binary files differ
diff --git a/res/drawable-hdpi/hotseat_browser_focused.png b/res/drawable-hdpi/hotseat_browser_focused.png
index 6717ad2..4020a89 100644
--- a/res/drawable-hdpi/hotseat_browser_focused.png
+++ b/res/drawable-hdpi/hotseat_browser_focused.png
Binary files differ
diff --git a/res/drawable-hdpi/hotseat_browser_normal.png b/res/drawable-hdpi/hotseat_browser_normal.png
index d02fdd9..ebdab62 100644
--- a/res/drawable-hdpi/hotseat_browser_normal.png
+++ b/res/drawable-hdpi/hotseat_browser_normal.png
Binary files differ
diff --git a/res/drawable-hdpi/hotseat_browser_pressed.png b/res/drawable-hdpi/hotseat_browser_pressed.png
index 71df2d1..bfa23b3 100644
--- a/res/drawable-hdpi/hotseat_browser_pressed.png
+++ b/res/drawable-hdpi/hotseat_browser_pressed.png
Binary files differ
diff --git a/res/drawable-hdpi/hotseat_left.png b/res/drawable-hdpi/hotseat_left.png
deleted file mode 100644
index 5dabf57..0000000
--- a/res/drawable-hdpi/hotseat_left.png
+++ /dev/null
Binary files differ
diff --git a/res/drawable-hdpi/hotseat_phone_focused.png b/res/drawable-hdpi/hotseat_phone_focused.png
index 3e84a58..f81f0a8 100644
--- a/res/drawable-hdpi/hotseat_phone_focused.png
+++ b/res/drawable-hdpi/hotseat_phone_focused.png
Binary files differ
diff --git a/res/drawable-hdpi/hotseat_phone_normal.png b/res/drawable-hdpi/hotseat_phone_normal.png
index e8a869c..2a16f9c 100644
--- a/res/drawable-hdpi/hotseat_phone_normal.png
+++ b/res/drawable-hdpi/hotseat_phone_normal.png
Binary files differ
diff --git a/res/drawable-hdpi/hotseat_phone_pressed.png b/res/drawable-hdpi/hotseat_phone_pressed.png
index dc4ad6e..a6c2baf 100644
--- a/res/drawable-hdpi/hotseat_phone_pressed.png
+++ b/res/drawable-hdpi/hotseat_phone_pressed.png
Binary files differ
diff --git a/res/drawable-hdpi/hotseat_right.png b/res/drawable-hdpi/hotseat_right.png
deleted file mode 100644
index 114bcb5..0000000
--- a/res/drawable-hdpi/hotseat_right.png
+++ /dev/null
Binary files differ
diff --git a/res/drawable-xlarge/home_screen_bg.9.png b/res/drawable-xlarge/home_screen_bg.9.png
new file mode 100644
index 0000000..d939d5c
--- /dev/null
+++ b/res/drawable-xlarge/home_screen_bg.9.png
Binary files differ
diff --git a/res/values-xlarge/config.xml b/res/values-xlarge/config.xml
index f6f6646..c7c68e2 100644
--- a/res/values-xlarge/config.xml
+++ b/res/values-xlarge/config.xml
@@ -38,4 +38,11 @@
          the drag view should be offset from the position of the original view. -->
     <integer name="config_dragViewOffsetX">0</integer>
     <integer name="config_dragViewOffsetY">12</integer>
+
+    <!-- The duration (in ms) of the fade animation on the object outlines, used when
+         we are dragging objects around on the home screen. -->
+    <integer name="config_dragOutlineFadeTime">900</integer>
+
+    <!-- The alpha value at which to show the most recent drop visualization outline. -->
+    <integer name="config_dragOutlineMaxAlpha">180</integer>
 </resources>
diff --git a/res/values/dimens.xml b/res/values/dimens.xml
index 790f835..c83986b 100644
--- a/res/values/dimens.xml
+++ b/res/values/dimens.xml
@@ -37,4 +37,8 @@
     
     <!-- delete_zone_size_full - button_bar_height_portrait -->
     <dimen name="delete_zone_padding">14dip</dimen>
+
+    <!-- the area at the edge of the screen that makes the workspace go left
+         or right while you're dragging. -->
+    <dimen name="scroll_zone">20dp</dimen>
 </resources>
diff --git a/src/com/android/launcher2/CellLayout.java b/src/com/android/launcher2/CellLayout.java
index 84b26f2..0f1d469 100644
--- a/src/com/android/launcher2/CellLayout.java
+++ b/src/com/android/launcher2/CellLayout.java
@@ -31,18 +31,19 @@
 import android.graphics.RectF;
 import android.graphics.drawable.Drawable;
 import android.util.AttributeSet;
-import android.util.Log;
 import android.view.ContextMenu;
 import android.view.MotionEvent;
 import android.view.View;
 import android.view.ViewDebug;
 import android.view.ViewGroup;
 import android.view.animation.Animation;
+import android.view.animation.DecelerateInterpolator;
+import android.view.animation.Interpolator;
 import android.view.animation.LayoutAnimationController;
 
 import java.util.Arrays;
 
-public class CellLayout extends ViewGroup {
+public class CellLayout extends ViewGroup implements Dimmable {
     static final String TAG = "CellLayout";
 
     private int mCellWidth;
@@ -75,17 +76,27 @@
 
     private float mBackgroundAlpha;
     private final Rect mBackgroundLayoutRect = new Rect();
+
     private Drawable mBackground;
-    private Drawable mBackgroundHover;
-    // If we're actively dragging something over this screen and it's small,
-    // mHover is true
+    private Drawable mBackgroundMini;
+    private Drawable mBackgroundMiniHover;
+    // If we're actively dragging something over this screen and it's small, mHover is true
     private boolean mHover = false;
 
-    private final RectF mDragRect = new RectF();
     private final Point mDragCenter = new Point();
 
     private Drawable mDragRectDrawable;
 
+    // These arrays are used to implement the drag visualization on x-large screens.
+    // They are used as circular arrays, indexed by mDragRectCurrent.
+    private Rect[] mDragRects = new Rect[8];
+    private int[] mDragRectAlphas = new int[mDragRects.length];
+    private InterruptibleInOutAnimator[] mDragRectAnims =
+        new InterruptibleInOutAnimator[mDragRects.length];
+
+    // Used as an index into the above 3 arrays; indicates which is the most current value.
+    private int mDragRectCurrent = 0;
+
     private Drawable mCrosshairsDrawable = null;
     private ValueAnimator mCrosshairsAnimator = null;
     private float mCrosshairsVisibility = 0.0f;
@@ -137,13 +148,18 @@
         if (LauncherApplication.isScreenXLarge()) {
             final Resources res = getResources();
 
-            mBackground = res.getDrawable(R.drawable.mini_home_screen_bg);
+            mBackgroundMini = res.getDrawable(R.drawable.mini_home_screen_bg);
+            mBackgroundMini.setFilterBitmap(true);
+            mBackground = res.getDrawable(R.drawable.home_screen_bg);
             mBackground.setFilterBitmap(true);
-            mBackgroundHover = res.getDrawable(R.drawable.mini_home_screen_bg_hover);
-            mBackgroundHover.setFilterBitmap(true);
+            mBackgroundMiniHover = res.getDrawable(R.drawable.mini_home_screen_bg_hover);
+            mBackgroundMiniHover.setFilterBitmap(true);
+
+            // Initialize the data structures used for the drag visualization.
 
             mDragRectDrawable = res.getDrawable(R.drawable.rounded_rect_green);
             mCrosshairsDrawable = res.getDrawable(R.drawable.gardening_crosshairs);
+            Interpolator interp = new DecelerateInterpolator(2.5f); // Quint ease out
 
             // Set up the animation for fading the crosshairs in and out
             int animDuration = res.getInteger(R.integer.config_crosshairsFadeInTime);
@@ -154,6 +170,32 @@
                     CellLayout.this.invalidate();
                 }
             });
+            mCrosshairsAnimator.setInterpolator(interp);
+
+            for (int i = 0; i < mDragRects.length; i++) {
+                mDragRects[i] = new Rect();
+            }
+
+            // When dragging things around the home screens, we show a green outline of
+            // where the item will land. The outlines gradually fade out, leaving a trail
+            // behind the drag path.
+            // Set up all the animations that are used to implement this fading.
+            final int duration = res.getInteger(R.integer.config_dragOutlineFadeTime);
+            final int fromAlphaValue = 0;
+            final int toAlphaValue = res.getInteger(R.integer.config_dragOutlineMaxAlpha);
+            for (int i = 0; i < mDragRectAnims.length; i++) {
+                final InterruptibleInOutAnimator anim =
+                    new InterruptibleInOutAnimator(duration, fromAlphaValue, toAlphaValue);
+                anim.setInterpolator(interp);
+                final int thisIndex = i;
+                anim.addUpdateListener(new AnimatorUpdateListener() {
+                    public void onAnimationUpdate(ValueAnimator animation) {
+                        mDragRectAlphas[thisIndex] = (Integer) animation.getAnimatedValue();
+                        CellLayout.this.invalidate(mDragRects[thisIndex]);
+                    }
+                });
+                mDragRectAnims[i] = anim;
+            }
         }
     }
 
@@ -176,7 +218,14 @@
     @Override
     public void dispatchDraw(Canvas canvas) {
         if (mBackgroundAlpha > 0.0f) {
-            final Drawable bg = mHover ? mBackgroundHover : mBackground;
+            Drawable bg;
+            if (mHover && getScaleX() < 0.5f) {
+                bg = mBackgroundMiniHover;
+            } else if (getScaleX() < 0.5f) {
+                bg = mBackgroundMini;
+            } else {
+                bg = mBackground;
+            }
             bg.setAlpha((int) (mBackgroundAlpha * 255));
             bg.draw(canvas);
         }
@@ -189,16 +238,6 @@
             final int countX = mCountX;
             final int countY = mCountY;
 
-            if (!mDragRect.isEmpty()) {
-                mDragRectDrawable.setBounds(
-                        (int)mDragRect.left,
-                        (int)mDragRect.top,
-                        (int)mDragRect.right,
-                        (int)mDragRect.bottom);
-                mDragRectDrawable.setAlpha((int) (mCrosshairsVisibility * 255));
-                mDragRectDrawable.draw(canvas);
-            }
-
             final float MAX_ALPHA = 0.4f;
             final int MAX_VISIBLE_DISTANCE = 600;
             final float DISTANCE_MULTIPLIER = 0.002f;
@@ -225,9 +264,32 @@
                 }
                 x += mCellWidth + mWidthGap;
             }
+
+            for (int i = 0; i < mDragRects.length; i++) {
+                int alpha = mDragRectAlphas[i];
+                if (alpha > 0) {
+                    mDragRectDrawable.setAlpha(alpha);
+                    mDragRectDrawable.setBounds(mDragRects[i]);
+                    mDragRectDrawable.draw(canvas);
+                }
+            }
         }
     }
 
+    public void setDimmableProgress(float progress) {
+        for (int i = 0; i < getChildCount(); i++) {
+            Dimmable d = (Dimmable) getChildAt(i);
+            d.setDimmableProgress(progress);
+        }
+    }
+
+    public float getDimmableProgress() {
+        if (getChildCount() > 0) {
+            return ((Dimmable) getChildAt(0)).getDimmableProgress();
+        }
+        return 0.0f;
+    }
+
     @Override
     public void cancelLongPress() {
         super.cancelLongPress();
@@ -581,8 +643,11 @@
         if (mBackground != null) {
             mBackground.setBounds(mBackgroundLayoutRect);
         }
-        if (mBackgroundHover != null) {
-            mBackgroundHover.setBounds(mBackgroundLayoutRect);
+        if (mBackgroundMiniHover != null) {
+            mBackgroundMiniHover.setBounds(mBackgroundLayoutRect);
+        }
+        if (mBackgroundMini != null) {
+            mBackgroundMini.setBounds(mBackgroundLayoutRect);
         }
     }
 
@@ -723,13 +788,23 @@
             final int left = topLeft[0];
             final int top = topLeft[1];
 
-            // Now find the bottom right
-            final int[] bottomRight = mTmpPoint;
-            cellToPoint(nearest[0] + spanX - 1, nearest[1] + spanY - 1, bottomRight);
-            bottomRight[0] += mCellWidth;
-            bottomRight[1] += mCellHeight;
-            mDragRect.set(left, top, bottomRight[0], bottomRight[1]);
-            invalidate();
+            final Rect dragRect = mDragRects[mDragRectCurrent];
+
+            if (dragRect.isEmpty() || left != dragRect.left || top != dragRect.top) {
+                // Now find the bottom right
+                final int[] bottomRight = mTmpPoint;
+                cellToPoint(nearest[0] + spanX - 1, nearest[1] + spanY - 1, bottomRight);
+                bottomRight[0] += mCellWidth;
+                bottomRight[1] += mCellHeight;
+
+                final int oldIndex = mDragRectCurrent;
+                mDragRectCurrent = (oldIndex + 1) % mDragRects.length;
+
+                mDragRects[mDragRectCurrent].set(left, top, bottomRight[0], bottomRight[1]);
+
+                mDragRectAnims[oldIndex].animateOut();
+                mDragRectAnims[mDragRectCurrent].animateIn();
+            }
         }
     }
 
@@ -766,9 +841,9 @@
      */
     int[] findNearestVacantArea(
             int pixelX, int pixelY, int spanX, int spanY, View ignoreView, int[] result) {
-        if (ignoreView != null) {
-            markCellsAsUnoccupiedForView(ignoreView);
-        }
+        // mark space take by ignoreView as available (method checks if ignoreView is null)
+        markCellsAsUnoccupiedForView(ignoreView);
+
         // Keep track of best-scoring drop area
         final int[] bestXY = result != null ? result : new int[2];
         double bestDistance = Double.MAX_VALUE;
@@ -802,9 +877,8 @@
                 }
             }
         }
-        if (ignoreView != null) {
-            markCellsAsOccupiedForView(ignoreView);
-        }
+        // re-mark space taken by ignoreView as occupied
+        markCellsAsOccupiedForView(ignoreView);
 
         // Return null if no suitable location found
         if (bestDistance < Double.MAX_VALUE) {
@@ -872,9 +946,8 @@
      */
     boolean findCellForSpanThatIntersectsIgnoring(int[] cellXY, int spanX, int spanY,
             int intersectX, int intersectY, View ignoreView) {
-        if (ignoreView != null) {
-            markCellsAsUnoccupiedForView(ignoreView);
-        }
+        // mark space take by ignoreView as available (method checks if ignoreView is null)
+        markCellsAsUnoccupiedForView(ignoreView);
 
         boolean foundCell = false;
         while (true) {
@@ -927,9 +1000,8 @@
             }
         }
 
-        if (ignoreView != null) {
-            markCellsAsOccupiedForView(ignoreView);
-        }
+        // re-mark space taken by ignoreView as occupied
+        markCellsAsOccupiedForView(ignoreView);
         return foundCell;
     }
 
@@ -948,6 +1020,10 @@
         if (mCrosshairsAnimator != null) {
             animateCrosshairsTo(0.0f);
         }
+
+        mDragRectAnims[mDragRectCurrent].animateOut();
+        mDragRectCurrent = (mDragRectCurrent + 1) % mDragRects.length;
+        mDragRects[mDragRectCurrent].setEmpty();
     }
 
     /**
@@ -990,7 +1066,6 @@
      * or it may have begun on another layout.
      */
     void onDragEnter(View dragView) {
-        mDragRect.setEmpty();
         // Fade in the drag indicators
         if (mCrosshairsAnimator != null) {
             animateCrosshairsTo(1.0f);
@@ -1123,11 +1198,13 @@
     }
 
     private void markCellsAsOccupiedForView(View view) {
+        if (view == null || view.getParent() != this) return;
         LayoutParams lp = (LayoutParams) view.getLayoutParams();
         markCellsForView(lp.cellX, lp.cellY, lp.cellHSpan, lp.cellVSpan, true);
     }
 
     private void markCellsAsUnoccupiedForView(View view) {
+        if (view == null || view.getParent() != this) return;
         LayoutParams lp = (LayoutParams) view.getLayoutParams();
         markCellsForView(lp.cellX, lp.cellY, lp.cellHSpan, lp.cellVSpan, false);
     }
diff --git a/src/com/android/launcher2/Dimmable.java b/src/com/android/launcher2/Dimmable.java
new file mode 100644
index 0000000..df43b3c
--- /dev/null
+++ b/src/com/android/launcher2/Dimmable.java
@@ -0,0 +1,6 @@
+package com.android.launcher2;
+
+public interface Dimmable {
+    public void setDimmableProgress(float progress);
+    public float getDimmableProgress();
+}
diff --git a/src/com/android/launcher2/DimmableAppWidgetHostView.java b/src/com/android/launcher2/DimmableAppWidgetHostView.java
index b5ec8cd..1f512a3 100644
--- a/src/com/android/launcher2/DimmableAppWidgetHostView.java
+++ b/src/com/android/launcher2/DimmableAppWidgetHostView.java
@@ -21,52 +21,21 @@
 import android.content.Context;
 import android.graphics.Bitmap;
 import android.graphics.Canvas;
-import android.graphics.Color;
 import android.graphics.Paint;
 import android.graphics.PorterDuff;
 import android.view.View;
 
-public class DimmableAppWidgetHostView extends LauncherAppWidgetHostView {
+public class DimmableAppWidgetHostView extends LauncherAppWidgetHostView implements Dimmable {
     public DimmableAppWidgetHostView(Context context) {
         super(context);
         mPaint.setFilterBitmap(true);
     }
 
     private final Paint mPaint = new Paint();
-    private int mAlpha;
-    private int mDimmedAlpha;
     private Bitmap mDimmedView;
     private Canvas mDimmedViewCanvas;
     private boolean isDimmedViewUpdatePass;
-
-    private static float cubic(float r) {
-        return (float) (Math.pow(r-1, 3) + 1);
-    }
-
-    /**
-     * Returns the interpolated holographic highlight alpha for the effect we want when scrolling
-     * pages.
-     */
-    public static float highlightAlphaInterpolator(float r) {
-        final float pivot = 0.3f;
-        if (r < pivot) {
-            return Math.max(0.5f, 0.65f*cubic(r/pivot));
-        } else {
-            return Math.min(1.0f, 0.65f*cubic(1 - (r-pivot)/(1-pivot)));
-        }
-    }
-
-    /**
-     * Returns the interpolated view alpha for the effect we want when scrolling pages.
-     */
-    public static float viewAlphaInterpolator(float r) {
-        final float pivot = 0.6f;
-        if (r < pivot) {
-            return r/pivot;
-        } else {
-            return 1.0f;
-        }
-    }
+    private float mDimmableProgress;
 
     private void setChildAlpha(float alpha) {
         if (getChildCount() > 0) {
@@ -83,20 +52,18 @@
         setChildAlpha(getAlpha());
     }
 
-    @Override
+    //@Override
     public boolean onSetAlpha(int alpha) {
         super.onSetAlpha(alpha);
         return true;
     }
 
-    @Override
-    public void setAlpha(float alpha) {
-        final float viewAlpha = viewAlphaInterpolator(alpha);
-        final float dimmedAlpha = highlightAlphaInterpolator(alpha);
-        mAlpha = (int) (viewAlpha * 255);
-        mDimmedAlpha = (int) (dimmedAlpha * 255);
-        super.setAlpha(viewAlpha);
-        setChildAlpha(viewAlpha);
+    public void setDimmableProgress(float progress) {
+        mDimmableProgress = progress;
+    }
+
+    public float getDimmableProgress() {
+        return mDimmableProgress;
     }
 
     private void updateDimmedView() {
@@ -113,13 +80,14 @@
         int dimmedColor = getContext().getResources().getColor(R.color.dimmed_view_color);
         mDimmedViewCanvas.drawColor(dimmedColor, PorterDuff.Mode.SRC_IN);
         isDimmedViewUpdatePass = false;
+        invalidate();
     }
 
     @Override
     protected void onLayout(boolean changed, int left, int top, int right, int bottom) {
         super.onLayout(changed, left, top, right, bottom);
 
-        if (mDimmedView == null && mDimmedAlpha > 0.0f) {
+        if (mDimmedView == null && mDimmableProgress > 0.0f) {
             updateDimmedView();
         }
     }
@@ -134,9 +102,9 @@
             canvas.restore();
             setAlpha(alpha);
         } else {
-            if (mDimmedView != null && mDimmedAlpha > 0) {
+            if (mDimmedView != null && mDimmableProgress > 0) {
                 // draw the dimmed version of this widget
-                mPaint.setAlpha(mDimmedAlpha);
+                mPaint.setAlpha((int) (mDimmableProgress * 255));
                 canvas.drawBitmap(mDimmedView, 0, 0, mPaint);
             }
 
diff --git a/src/com/android/launcher2/DimmableBubbleTextView.java b/src/com/android/launcher2/DimmableBubbleTextView.java
index 66cc97a..cb3b8ef 100644
--- a/src/com/android/launcher2/DimmableBubbleTextView.java
+++ b/src/com/android/launcher2/DimmableBubbleTextView.java
@@ -21,18 +21,17 @@
 import android.content.Context;
 import android.graphics.Bitmap;
 import android.graphics.Canvas;
-import android.graphics.Color;
 import android.graphics.Paint;
 import android.graphics.PorterDuff;
 import android.util.AttributeSet;
 
-public class DimmableBubbleTextView extends BubbleTextView {
+public class DimmableBubbleTextView extends BubbleTextView implements Dimmable {
     private  Paint mDimmedPaint = new Paint();
     private int mAlpha;
-    private int mDimmedAlpha;
     private Bitmap mDimmedView;
     private Canvas mDimmedViewCanvas;
     private boolean isDimmedViewUpdatePass;
+    private float mDimmableProgress;
 
     public DimmableBubbleTextView(Context context) {
         super(context);
@@ -49,48 +48,12 @@
         mDimmedPaint.setFilterBitmap(true);
     }
 
-    private static float cubic(float r) {
-        return (float) (Math.pow(r-1, 3) + 1);
+    public void setDimmableProgress(float progress) {
+        mDimmableProgress = progress;
     }
 
-    /**
-     * Returns the interpolated holographic highlight alpha for the effect we want when scrolling
-     * pages.
-     */
-    public static float highlightAlphaInterpolator(float r) {
-        final float pivot = 0.3f;
-        if (r < pivot) {
-            return Math.max(0.5f, 0.65f*cubic(r/pivot));
-        } else {
-            return Math.min(1.0f, 0.65f*cubic(1 - (r-pivot)/(1-pivot)));
-        }
-    }
-
-    /**
-     * Returns the interpolated view alpha for the effect we want when scrolling pages.
-     */
-    public static float viewAlphaInterpolator(float r) {
-        final float pivot = 0.6f;
-        if (r < pivot) {
-            return r/pivot;
-        } else {
-            return 1.0f;
-        }
-    }
-
-    @Override
-    public boolean onSetAlpha(int alpha) {
-        super.onSetAlpha(alpha);
-        return true;
-    }
-
-    @Override
-    public void setAlpha(float alpha) {
-        final float viewAlpha = viewAlphaInterpolator(alpha);
-        final float dimmedAlpha = highlightAlphaInterpolator(alpha);
-        mAlpha = (int) (viewAlpha * 255);
-        mDimmedAlpha = (int) (dimmedAlpha * 255);
-        super.setAlpha(viewAlpha);
+    public float getDimmableProgress() {
+        return mDimmableProgress;
     }
 
     @Override
@@ -124,13 +87,11 @@
             super.setAlpha(alpha);
             canvas.restore();
         } else {
-            if (mAlpha > 0) {
-                super.onDraw(canvas);
-            }
+            super.onDraw(canvas);
         }
 
-        if (mDimmedView != null && mDimmedAlpha > 0) {
-            mDimmedPaint.setAlpha(mDimmedAlpha);
+        if (mDimmedView != null && mDimmableProgress > 0) {
+            mDimmedPaint.setAlpha((int) (mDimmableProgress * 255));
             canvas.drawBitmap(mDimmedView, mScrollX, mScrollY, mDimmedPaint);
         }
     }
diff --git a/src/com/android/launcher2/DragController.java b/src/com/android/launcher2/DragController.java
index 185f704..d544340 100644
--- a/src/com/android/launcher2/DragController.java
+++ b/src/com/android/launcher2/DragController.java
@@ -33,6 +33,8 @@
 
 import java.util.ArrayList;
 
+import com.android.launcher.R;
+
 /**
  * Class for initiating a drag within a view or across multiple views.
  */
@@ -47,7 +49,6 @@
     public static int DRAG_ACTION_COPY = 1;
 
     private static final int SCROLL_DELAY = 600;
-    private static final int SCROLL_ZONE = 20;
     private static final int VIBRATE_DURATION = 35;
 
     private static final boolean PROFILE_DRAWING_DURING_DRAG = false;
@@ -87,6 +88,11 @@
     /** Y offset from the upper-left corner of the cell to where we touched.  */
     private float mTouchOffsetY;
 
+    /** the area at the edge of the screen that makes the workspace go left
+     *   or right while you're dragging.
+     */
+    private int mScrollZone;
+
     /** Where the drag originated */
     private DragSource mDragSource;
 
@@ -147,6 +153,7 @@
     public DragController(Context context) {
         mContext = context;
         mHandler = new Handler();
+        mScrollZone = context.getResources().getDimensionPixelSize(R.dimen.scroll_zone);
     }
 
     /**
@@ -475,13 +482,13 @@
         if (mDeleteRegion != null) {
             inDeleteRegion = mDeleteRegion.contains(x, y);
         }
-        if (!inDeleteRegion && x < SCROLL_ZONE) {
+        if (!inDeleteRegion && x < mScrollZone) {
             if (mScrollState == SCROLL_OUTSIDE_ZONE) {
                 mScrollState = SCROLL_WAITING_IN_ZONE;
                 mScrollRunnable.setDirection(SCROLL_LEFT);
                 mHandler.postDelayed(mScrollRunnable, SCROLL_DELAY);
             }
-        } else if (!inDeleteRegion && x > mScrollView.getWidth() - SCROLL_ZONE) {
+        } else if (!inDeleteRegion && x > mScrollView.getWidth() - mScrollZone) {
             if (mScrollState == SCROLL_OUTSIDE_ZONE) {
                 mScrollState = SCROLL_WAITING_IN_ZONE;
                 mScrollRunnable.setDirection(SCROLL_RIGHT);
@@ -514,7 +521,7 @@
             mMotionDownX = screenX;
             mMotionDownY = screenY;
 
-            if ((screenX < SCROLL_ZONE) || (screenX > mScrollView.getWidth() - SCROLL_ZONE)) {
+            if ((screenX < mScrollZone) || (screenX > mScrollView.getWidth() - mScrollZone)) {
                 mScrollState = SCROLL_WAITING_IN_ZONE;
                 mHandler.postDelayed(mScrollRunnable, SCROLL_DELAY);
             } else {
diff --git a/src/com/android/launcher2/InterruptibleInOutAnimator.java b/src/com/android/launcher2/InterruptibleInOutAnimator.java
new file mode 100644
index 0000000..fb07284
--- /dev/null
+++ b/src/com/android/launcher2/InterruptibleInOutAnimator.java
@@ -0,0 +1,69 @@
+/*
+ * Copyright (C) 2010 The Android Open Source Project
+ *
+ * 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.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.launcher2;
+
+import android.animation.ValueAnimator;
+import android.util.Log;
+
+/**
+ * A convenience class for two-way animations, e.g. a fadeIn/fadeOut animation.
+ * With a regular ValueAnimator, if you call reverse to show the 'out' animation, you'll get
+ * a frame-by-frame mirror of the 'in' animation -- i.e., the interpolated values will
+ * be exactly reversed. Using this class, both the 'in' and the 'out' animation use the
+ * interpolator in the same direction.
+ */
+public class InterruptibleInOutAnimator extends ValueAnimator {
+    private long mOriginalDuration;
+    private Object mOriginalFromValue;
+    private Object mOriginalToValue;
+
+    public InterruptibleInOutAnimator(long duration, Object fromValue, Object toValue) {
+        super(duration, fromValue, toValue);
+        mOriginalDuration = duration;
+        mOriginalFromValue = fromValue;
+        mOriginalToValue = toValue;
+    }
+
+    private void animate(Object fromValue, Object toValue) {
+        // This only makes sense when it's running in the opposite direction, or stopped.
+        setDuration(mOriginalDuration - getCurrentPlayTime());
+
+        final Object startValue = isRunning() ? getAnimatedValue() : fromValue;
+        cancel();
+        setValues(startValue, toValue);
+        start();
+    }
+
+    /**
+     * This is the equivalent of calling Animator.start(), except that it can be called when
+     * the animation is running in the opposite direction, in which case we reverse
+     * direction and animate for a correspondingly shorter duration.
+     */
+    public void animateIn() {
+        animate(mOriginalFromValue, mOriginalToValue);
+    }
+
+    /**
+     * This is the roughly the equivalent of calling Animator.reverse(), except that it uses the
+     * same interpolation curve as animateIn(), rather than mirroring it. Also, like animateIn(),
+     * if the animation is currently running in the opposite direction, we reverse
+     * direction and animate for a correspondingly shorter duration.
+     */
+    public void animateOut() {
+        animate(mOriginalToValue, mOriginalFromValue);
+    }
+}
diff --git a/src/com/android/launcher2/LauncherModel.java b/src/com/android/launcher2/LauncherModel.java
index 7c1fa21..2800605 100644
--- a/src/com/android/launcher2/LauncherModel.java
+++ b/src/com/android/launcher2/LauncherModel.java
@@ -300,8 +300,10 @@
     /**
      * Creates a new unique child id, for a given cell span across all layouts.
      */
-    static int getCellLayoutChildId(int cellId, int screen, int localCellX, int localCellY, int spanX, int spanY) {
-        return ((cellId & 0xFF) << 16) | (localCellX & 0xFF) << 8 | (localCellY & 0xFF);
+    static int getCellLayoutChildId(
+            int cellId, int screen, int localCellX, int localCellY, int spanX, int spanY) {
+        return ((cellId & 0xFF) << 24)
+                | (screen & 0xFF) << 16 | (localCellX & 0xFF) << 8 | (localCellY & 0xFF);
     }
 
     static int getCellCountX() {
@@ -412,8 +414,9 @@
             }
 
         } else if (Intent.ACTION_EXTERNAL_APPLICATIONS_AVAILABLE.equals(action)) {
-            String[] packages = intent.getStringArrayExtra(Intent.EXTRA_CHANGED_PACKAGE_LIST);
-            enqueuePackageUpdated(new PackageUpdatedTask(PackageUpdatedTask.OP_ADD, packages));
+            // When everything comes back, just reload everything.  We might not
+            // have the right icons for apps on external storage.
+            startLoader(mApp, false);
 
         } else if (Intent.ACTION_EXTERNAL_APPLICATIONS_UNAVAILABLE.equals(action)) {
             String[] packages = intent.getStringArrayExtra(Intent.EXTRA_CHANGED_PACKAGE_LIST);
diff --git a/src/com/android/launcher2/PagedView.java b/src/com/android/launcher2/PagedView.java
index 5aec48e..a1b1e08 100644
--- a/src/com/android/launcher2/PagedView.java
+++ b/src/com/android/launcher2/PagedView.java
@@ -73,11 +73,13 @@
     private float mDownMotionX;
     private float mLastMotionX;
     private float mLastMotionY;
+    private int mLastScreenCenter = -1;
 
     protected final static int TOUCH_STATE_REST = 0;
     protected final static int TOUCH_STATE_SCROLLING = 1;
     protected final static int TOUCH_STATE_PREV_PAGE = 2;
     protected final static int TOUCH_STATE_NEXT_PAGE = 3;
+    protected final static float ALPHA_QUANTIZE_LEVEL = 0.01f;
 
     protected int mTouchState = TOUCH_STATE_REST;
 
@@ -367,7 +369,6 @@
             if (mDirtyPageAlpha || (mTouchState == TOUCH_STATE_SCROLLING) || !mScroller.isFinished()) {
                 int halfScreenSize = getMeasuredWidth() / 2;
                 int screenCenter = mScrollX + halfScreenSize;
-
                 final int childCount = getChildCount();
                 for (int i = 0; i < childCount; ++i) {
                     View layout = (View) getChildAt(i);
@@ -391,6 +392,12 @@
                     dimAlpha = Math.max(0.0f, Math.min(1.0f, (dimAlpha * dimAlpha)));
                     float alpha = 1.0f - dimAlpha;
 
+                    if (alpha < ALPHA_QUANTIZE_LEVEL) {
+                        alpha = 0.0f;
+                    } else if (alpha > 1.0f - ALPHA_QUANTIZE_LEVEL) {
+                        alpha = 1.0f;
+                    }
+
                     if (Float.compare(alpha, layout.getAlpha()) != 0) {
                         layout.setAlpha(alpha);
                     }
@@ -400,9 +407,19 @@
         }
     }
 
+    protected void screenScrolled(int screenCenter) {
+    }
+
     @Override
     protected void dispatchDraw(Canvas canvas) {
-        updateAdjacentPagesAlpha();
+        int halfScreenSize = getMeasuredWidth() / 2;
+        int screenCenter = mScrollX + halfScreenSize;
+
+        if (screenCenter != mLastScreenCenter) {
+            screenScrolled(screenCenter);
+            updateAdjacentPagesAlpha();
+            mLastScreenCenter = screenCenter;
+        }
 
         // Find out which screens are visible; as an optimization we only call draw on them
         // As an optimization, this code assumes that all pages have the same width as the 0th
diff --git a/src/com/android/launcher2/PagedViewIcon.java b/src/com/android/launcher2/PagedViewIcon.java
index 0714a93..ff5ea49 100644
--- a/src/com/android/launcher2/PagedViewIcon.java
+++ b/src/com/android/launcher2/PagedViewIcon.java
@@ -110,9 +110,9 @@
         mIconCacheKey = info;
         mHolographicOutline = mIconCache.getOutline(mIconCacheKey);
 
-        Drawable image = info.loadIcon(packageManager);
-        image.setBounds(0, 0, image.getIntrinsicWidth(), image.getIntrinsicHeight());
-        setCompoundDrawablesWithIntrinsicBounds(null, image, null, null);
+        Bitmap image = Utilities.createIconBitmap(info.loadIcon(packageManager), mContext);
+        setCompoundDrawablesWithIntrinsicBounds(null, 
+                new FastBitmapDrawable(image), null, null);
         setText(info.loadLabel(packageManager));
         setTag(info);
     }
diff --git a/src/com/android/launcher2/SmoothPagedView.java b/src/com/android/launcher2/SmoothPagedView.java
index 5f80f25..56037ff 100644
--- a/src/com/android/launcher2/SmoothPagedView.java
+++ b/src/com/android/launcher2/SmoothPagedView.java
@@ -26,11 +26,15 @@
     private static final float SMOOTHING_SPEED = 0.75f;
     private static final float SMOOTHING_CONSTANT = (float) (0.016 / Math.log(SMOOTHING_SPEED));
 
+    private float mBaseLineFlingVelocity;
+    private float mFlingVelocityInfluence;
 
-    private static final float BASELINE_FLING_VELOCITY = 2500.f;
-    private static final float FLING_VELOCITY_INFLUENCE = 0.4f;
+    static final int OVERSHOOT_MODE = 0;
+    static final int QUINTIC_MODE = 1;
 
-    private WorkspaceOvershootInterpolator mScrollInterpolator;
+    int mScrollMode;
+
+    private Interpolator mScrollInterpolator;
 
     private static class WorkspaceOvershootInterpolator implements Interpolator {
         private static final float DEFAULT_TENSION = 1.3f;
@@ -56,6 +60,16 @@
         }
     }
 
+    private static class QuinticInterpolator implements Interpolator {
+        public QuinticInterpolator() {
+        }
+
+        public float getInterpolation(float t) {
+            t -= 1.0f;
+            return t*t*t*t*t + 1;
+        }
+    }
+
     /**
      * Used to inflate the Workspace from XML.
      *
@@ -83,14 +97,27 @@
         mDeferScrollUpdate = true;
     }
 
+    protected int getScrollMode() {
+        return OVERSHOOT_MODE;
+    }
+
     /**
      * Initializes various states for this workspace.
      */
     @Override
     protected void init() {
         super.init();
-        mScrollInterpolator = new WorkspaceOvershootInterpolator();
-        // overwrite the previous mScroller
+
+        mScrollMode = getScrollMode();
+        if (mScrollMode == QUINTIC_MODE) {
+            mBaseLineFlingVelocity = 700.0f;
+            mFlingVelocityInfluence = 0.8f;
+            mScrollInterpolator = new QuinticInterpolator();
+        } else {  // QUINTIC_MODE
+            mBaseLineFlingVelocity = 2500.0f;
+            mFlingVelocityInfluence = 0.4f;
+            mScrollInterpolator = new WorkspaceOvershootInterpolator();
+        }
         mScroller = new Scroller(getContext(), mScrollInterpolator);
     }
 
@@ -112,25 +139,32 @@
         final int screenDelta = Math.max(1, Math.abs(whichPage - mCurrentPage));
         final int newX = getChildOffset(whichPage) - getRelativeChildOffset(whichPage);
         final int delta = newX - mScrollX;
-        int duration = (screenDelta + 1) * 100;
+        int duration;
+        if (mScrollMode == OVERSHOOT_MODE) {
+            duration = (screenDelta + 1) * 100;
+        } else { // QUINTIC_MODE
+            duration = Math.round(Math.abs(delta) * 0.6f);
+        }
 
         if (!mScroller.isFinished()) {
             mScroller.abortAnimation();
         }
 
-        if (settle) {
-            mScrollInterpolator.setDistance(screenDelta);
-        } else {
-            mScrollInterpolator.disableSettle();
+        if (mScrollMode == OVERSHOOT_MODE) {
+            if (settle) {
+                ((WorkspaceOvershootInterpolator) mScrollInterpolator).setDistance(screenDelta);
+            } else {
+                ((WorkspaceOvershootInterpolator) mScrollInterpolator).disableSettle();
+            }
         }
 
         velocity = Math.abs(velocity);
         if (velocity > 0) {
-            duration += (duration / (velocity / BASELINE_FLING_VELOCITY))
-                    * FLING_VELOCITY_INFLUENCE;
+            duration += (duration / (velocity / mBaseLineFlingVelocity)) * mFlingVelocityInfluence;
         } else {
             duration += 100;
         }
+
         snapToPage(whichPage, delta, duration);
     }
 
diff --git a/src/com/android/launcher2/Workspace.java b/src/com/android/launcher2/Workspace.java
index 4d820cf..d80edae 100644
--- a/src/com/android/launcher2/Workspace.java
+++ b/src/com/android/launcher2/Workspace.java
@@ -33,7 +33,9 @@
 import android.content.pm.ProviderInfo;
 import android.content.res.Resources;
 import android.content.res.TypedArray;
+import android.graphics.Camera;
 import android.graphics.Canvas;
+import android.graphics.Color;
 import android.graphics.Matrix;
 import android.graphics.Rect;
 import android.graphics.drawable.Drawable;
@@ -63,8 +65,8 @@
     // customization mode
     private static final float SHRINK_FACTOR = 0.16f;
 
-    // The maximum Y rotation to apply to the mini home screens
-    private static final float MINI_PAGE_MAX_ROTATION = 25.0f;
+    // Y rotation to apply to the workspace screens
+    private static final float WORKSPACE_ROTATION = 12.5f;
 
     // These are extra scale factors to apply to the mini home screens
     // so as to achieve the desired transform
@@ -72,6 +74,18 @@
     private static final float EXTRA_SCALE_FACTOR_1 = 1.0f;
     private static final float EXTRA_SCALE_FACTOR_2 = 1.08f;
 
+    private static final int BACKGROUND_FADE_OUT_DELAY = 300;
+    private static final int BACKGROUND_FADE_OUT_DURATION = 300;
+    private static final int BACKGROUND_FADE_IN_DURATION = 100;
+
+    static final int SCROLL_RIGHT = 0;
+    static final int SCROLL_LEFT = 1;
+
+    // These animators are used to fade the
+    private ObjectAnimator<Float> mBackgroundFadeIn;
+    private ObjectAnimator<Float> mBackgroundFadeOut;
+    private float mBackgroundAlpha = 0;
+
     private enum ShrinkPosition { SHRINK_TO_TOP, SHRINK_TO_MIDDLE, SHRINK_TO_BOTTOM };
 
     private final WallpaperManager mWallpaperManager;
@@ -180,6 +194,15 @@
     }
 
     @Override
+    protected int getScrollMode() {
+        if (LauncherApplication.isScreenXLarge()) {
+            return SmoothPagedView.QUINTIC_MODE;
+        } else {
+            return SmoothPagedView.OVERSHOOT_MODE;
+        }
+    }
+
+    @Override
     public void addView(View child, int index, LayoutParams params) {
         if (!(child instanceof CellLayout)) {
             throw new IllegalArgumentException("A Workspace can only have CellLayout children.");
@@ -322,7 +345,7 @@
         }
 
         // Get the canonical child id to uniquely represent this view in this screen
-        int childId = LauncherModel.getCellLayoutChildId(child.getId(), screen, x, y, spanX, spanY);
+        int childId = LauncherModel.getCellLayoutChildId(-1, screen, x, y, spanX, spanY);
         if (!group.addViewToCellLayout(child, insert ? 0 : -1, childId, lp)) {
             // TODO: This branch occurs when the workspace is adding views
             // outside of the defined grid
@@ -382,12 +405,14 @@
                 enableChildrenCache(mCurrentPage - 1, mCurrentPage + 1);
             }
         }
+        showOutlines();
     }
 
     protected void pageEndMoving() {
         if (!LauncherApplication.isScreenXLarge()) {
             clearChildrenCache();
         }
+        hideOutlines();
     }
 
     @Override
@@ -424,6 +449,99 @@
         }
     }
 
+    private float getScaleXForRotation(float degrees) {
+        return (float) (1.0f / Math.cos(Math.PI * degrees / 180.0f));
+    }
+
+    public void showOutlines() {
+        if (mBackgroundFadeOut != null) mBackgroundFadeOut.cancel();
+        if (mBackgroundFadeIn != null) mBackgroundFadeIn.cancel();
+        mBackgroundFadeIn = new ObjectAnimator<Float>(BACKGROUND_FADE_IN_DURATION, this,
+                        new PropertyValuesHolder<Float>("backgroundAlpha", 1.0f));
+        mBackgroundFadeIn.start();
+    }
+
+    public void hideOutlines() {
+        if (mBackgroundFadeIn != null) mBackgroundFadeIn.cancel();
+        if (mBackgroundFadeOut != null) mBackgroundFadeOut.cancel();
+        mBackgroundFadeOut = new ObjectAnimator<Float>(BACKGROUND_FADE_OUT_DURATION, this,
+                        new PropertyValuesHolder<Float>("backgroundAlpha", 0.0f));
+        mBackgroundFadeOut.setStartDelay(BACKGROUND_FADE_OUT_DELAY);
+        mBackgroundFadeOut.start();
+    }
+
+    public void setBackgroundAlpha(float alpha) {
+        mBackgroundAlpha = alpha;
+        for (int i = 0; i < getChildCount(); i++) {
+            CellLayout cl = (CellLayout) getChildAt(i);
+            cl.setBackgroundAlpha(alpha);
+        }
+    }
+
+    public float getBackgroundAlpha() {
+        return mBackgroundAlpha;
+    }
+
+    @Override
+    protected void screenScrolled(int screenCenter) {
+        View cur = getChildAt(mCurrentPage);
+        View toRight = getChildAt(mCurrentPage + 1);
+        View toLeft = getChildAt(mCurrentPage - 1);
+
+        for (int i = 0; i < mCurrentPage - 1; i++) {
+            View v = getChildAt(i);
+            if (v != null) {
+                v.setRotationY(WORKSPACE_ROTATION);
+                v.setScaleX(getScaleXForRotation(WORKSPACE_ROTATION));
+            }
+        }
+        for (int i = mCurrentPage + 1; i < getChildCount(); i++) {
+            View v = getChildAt(i);
+            if (v != null) {
+                v.setRotationY(-WORKSPACE_ROTATION);
+                v.setScaleX(getScaleXForRotation(-WORKSPACE_ROTATION));
+            }
+        }
+
+        int pageWidth = cur.getMeasuredWidth();
+        int delta = screenCenter - (mCurrentPage * pageWidth + pageWidth / 2 +
+                getRelativeChildOffset(0));
+
+        float scrollProgress = Math.abs(delta/(pageWidth*1.0f));
+        int scrollDirection = delta > 0 ? SCROLL_LEFT : SCROLL_RIGHT;
+
+        float rotation;
+
+        if (scrollDirection == SCROLL_RIGHT) {
+            rotation = -scrollProgress * WORKSPACE_ROTATION;
+            cur.setRotationY(rotation);
+            cur.setScaleX(getScaleXForRotation(rotation));
+            if (toLeft != null) {
+                rotation = WORKSPACE_ROTATION * (1 - scrollProgress);
+                toLeft.setRotationY(rotation);
+                toLeft.setScaleX(getScaleXForRotation(rotation));
+            }
+            if (toRight != null) {
+                toRight.setRotationY(-WORKSPACE_ROTATION);
+                toRight.setScaleX(getScaleXForRotation(WORKSPACE_ROTATION));
+            }
+        } else {
+            rotation = scrollProgress * WORKSPACE_ROTATION;
+            cur.setRotationY(rotation);
+            cur.setScaleX(getScaleXForRotation(rotation));
+
+            if (toRight != null) {
+                rotation = -WORKSPACE_ROTATION * (1 - scrollProgress);
+                toRight.setRotationY(rotation);
+                toRight.setScaleX(getScaleXForRotation(rotation));
+            }
+            if (toLeft != null) {
+                toLeft.setRotationY(WORKSPACE_ROTATION);
+                toLeft.setScaleX(getScaleXForRotation(WORKSPACE_ROTATION));
+            }
+        }
+    }
+
     protected void onAttachedToWindow() {
         super.onAttachedToWindow();
         computeScroll();
@@ -624,7 +742,7 @@
         for (int i = 0; i < screenCount; i++) {
             CellLayout cl = (CellLayout) getChildAt(i);
 
-            float rotation = (-i + 2) * MINI_PAGE_MAX_ROTATION / 2.0f;
+            float rotation = (-i + 2) * WORKSPACE_ROTATION;
             float rotationScaleX = (float) (1.0f / Math.cos(Math.PI * rotation / 180.0f));
             float rotationScaleY = getYScaleForScreen(i);
 
@@ -636,14 +754,16 @@
                         new PropertyValuesHolder<Float>("scaleX", SHRINK_FACTOR * rotationScaleX),
                         new PropertyValuesHolder<Float>("scaleY", SHRINK_FACTOR * rotationScaleY),
                         new PropertyValuesHolder<Float>("backgroundAlpha", 1.0f),
+                        new PropertyValuesHolder<Float>("dimmableProgress", 1.0f),
                         new PropertyValuesHolder<Float>("alpha", 0.0f),
                         new PropertyValuesHolder<Float>("rotationY", rotation)).start();
             } else {
                 cl.setX((int)newX);
                 cl.setY((int)newY);
-                cl.setScaleX(SHRINK_FACTOR);
-                cl.setScaleY(SHRINK_FACTOR);
+                cl.setScaleX(SHRINK_FACTOR * rotationScaleX);
+                cl.setScaleY(SHRINK_FACTOR * rotationScaleY);
                 cl.setBackgroundAlpha(1.0f);
+                cl.setDimmableProgress(1.0f);
                 cl.setAlpha(0.0f);
                 cl.setRotationY(rotation);
             }
@@ -696,6 +816,14 @@
             for (int i = 0; i < screenCount; i++) {
                 final CellLayout cl = (CellLayout)getChildAt(i);
                 float finalAlphaValue = (i == mCurrentPage) ? 1.0f : 0.0f;
+                float rotation = 0.0f;
+
+                if (i < mCurrentPage) {
+                    rotation = WORKSPACE_ROTATION;
+                } else if (i > mCurrentPage) {
+                    rotation = -WORKSPACE_ROTATION;
+                }
+
                 if (animated) {
                     s.playTogether(
                             new ObjectAnimator<Float>(duration, cl, "translationX", 0.0f),
@@ -704,15 +832,17 @@
                             new ObjectAnimator<Float>(duration, cl, "scaleY", 1.0f),
                             new ObjectAnimator<Float>(duration, cl, "backgroundAlpha", 0.0f),
                             new ObjectAnimator<Float>(duration, cl, "alpha", finalAlphaValue),
-                            new ObjectAnimator<Float>(duration, cl, "rotationY", 0.0f));
+                            new ObjectAnimator<Float>(duration, cl, "dimmableProgress", 0.0f),
+                            new ObjectAnimator<Float>(duration, cl, "rotationY", rotation));
                 } else {
                     cl.setTranslationX(0.0f);
                     cl.setTranslationY(0.0f);
                     cl.setScaleX(1.0f);
                     cl.setScaleY(1.0f);
                     cl.setBackgroundAlpha(0.0f);
-                    cl.setAlpha(1.0f);
-                    cl.setRotationY(0.0f);
+                    cl.setDimmableProgress(0.0f);
+                    cl.setAlpha(finalAlphaValue);
+                    cl.setRotationY(rotation);
                 }
             }
             s.addListener(mUnshrinkAnimationListener);
@@ -774,23 +904,25 @@
         } else {
             cellLayout = getCurrentDropLayout();
         }
+
         if (source != this) {
             onDropExternal(originX, originY, dragInfo, cellLayout);
         } else {
             // Move internally
             if (mDragInfo != null) {
                 final View cell = mDragInfo.cell;
-                int index = mScroller.isFinished() ? mCurrentPage : mNextPage;
-                if (index != mDragInfo.screen) {
-                    final CellLayout originalCellLayout = (CellLayout) getChildAt(mDragInfo.screen);
-                    originalCellLayout.removeView(cell);
-                    addInScreen(cell, index, mDragInfo.cellX, mDragInfo.cellY,
-                            mDragInfo.spanX, mDragInfo.spanY);
-                }
 
                 mTargetCell = findNearestVacantArea(originX, originY,
                         mDragInfo.spanX, mDragInfo.spanY, cell, cellLayout,
                         mTargetCell);
+
+                int screen = indexOfChild(cellLayout);
+                if (screen != mDragInfo.screen) {
+                    final CellLayout originalCellLayout = (CellLayout) getChildAt(mDragInfo.screen);
+                    originalCellLayout.removeView(cell);
+                    addInScreen(cell, screen, mTargetCell[0], mTargetCell[1],
+                            mDragInfo.spanX, mDragInfo.spanY);
+                }
                 cellLayout.onDropChild(cell);
 
                 // update the item's position after drop
@@ -799,9 +931,11 @@
                 cellLayout.onMove(cell, mTargetCell[0], mTargetCell[1]);
                 lp.cellX = mTargetCell[0];
                 lp.cellY = mTargetCell[1];
+                cell.setId(LauncherModel.getCellLayoutChildId(-1, mDragInfo.screen,
+                        mTargetCell[0], mTargetCell[1], mDragInfo.spanX, mDragInfo.spanY));
 
                 LauncherModel.moveItemInDatabase(mLauncher, info,
-                        LauncherSettings.Favorites.CONTAINER_DESKTOP, index,
+                        LauncherSettings.Favorites.CONTAINER_DESKTOP, screen,
                         lp.cellX, lp.cellY);
             }
         }
@@ -1152,15 +1286,12 @@
     private int[] findNearestVacantArea(int pixelX, int pixelY,
             int spanX, int spanY, View ignoreView, CellLayout layout, int[] recycle) {
 
-        final int[] cellXY = mTempCell;
         int localPixelX = pixelX - (layout.getLeft() - mScrollX);
         int localPixelY = pixelY - (layout.getTop() - mScrollY);
-        layout.estimateDropCell(localPixelX, localPixelY, spanX, spanY, cellXY);
-        layout.cellToPoint(cellXY[0], cellXY[1], mTempEstimate);
 
         // Find the best target drop location
         return layout.findNearestVacantArea(
-                mTempEstimate[0], mTempEstimate[1], spanX, spanY, ignoreView, recycle);
+                localPixelX, localPixelY, spanX, spanY, ignoreView, recycle);
     }
 
     /**