Merge "Import initial translations for 20 new locales."
diff --git a/proguard.flags b/proguard.flags
index 010a1db..bedc5e7 100644
--- a/proguard.flags
+++ b/proguard.flags
@@ -32,6 +32,8 @@
 -keep class com.android.launcher2.Workspace {
   public float getBackgroundAlpha();
   public void setBackgroundAlpha(float);
+  public float getChildrenOutlineAlpha();
+  public void setChildrenOutlineAlpha(float);
 }
 
 -keep class com.android.launcher2.AllApps3D$Defines {
diff --git a/res/drawable-xlarge-hdpi/home_press.9.png b/res/drawable-xlarge-hdpi/home_press.9.png
new file mode 100644
index 0000000..4beec62
--- /dev/null
+++ b/res/drawable-xlarge-hdpi/home_press.9.png
Binary files differ
diff --git a/res/drawable-xlarge-hdpi/ic_voice_search.png b/res/drawable-xlarge-hdpi/ic_voice_search.png
new file mode 100644
index 0000000..66d14ae
--- /dev/null
+++ b/res/drawable-xlarge-hdpi/ic_voice_search.png
Binary files differ
diff --git a/res/drawable-xlarge-hdpi/rotate_button_normal.png b/res/drawable-xlarge-hdpi/rotate_button_normal.png
deleted file mode 100644
index 3772018..0000000
--- a/res/drawable-xlarge-hdpi/rotate_button_normal.png
+++ /dev/null
Binary files differ
diff --git a/res/drawable-xlarge-hdpi/rotate_button_pressed.png b/res/drawable-xlarge-hdpi/rotate_button_pressed.png
deleted file mode 100644
index 7ceb996..0000000
--- a/res/drawable-xlarge-hdpi/rotate_button_pressed.png
+++ /dev/null
Binary files differ
diff --git a/res/drawable-xlarge-hdpi/textfield_end.9.png b/res/drawable-xlarge-hdpi/textfield_end.9.png
new file mode 100644
index 0000000..be1b2b6
--- /dev/null
+++ b/res/drawable-xlarge-hdpi/textfield_end.9.png
Binary files differ
diff --git a/res/drawable-xlarge-hdpi/textfield_start.9.png b/res/drawable-xlarge-hdpi/textfield_start.9.png
new file mode 100644
index 0000000..6b5ebe2
--- /dev/null
+++ b/res/drawable-xlarge-hdpi/textfield_start.9.png
Binary files differ
diff --git a/res/drawable-xlarge-mdpi/home_press.9.png b/res/drawable-xlarge-mdpi/home_press.9.png
new file mode 100644
index 0000000..ff841c3
--- /dev/null
+++ b/res/drawable-xlarge-mdpi/home_press.9.png
Binary files differ
diff --git a/res/drawable-xlarge-mdpi/ic_voice_search.png b/res/drawable-xlarge-mdpi/ic_voice_search.png
new file mode 100644
index 0000000..a2fe874
--- /dev/null
+++ b/res/drawable-xlarge-mdpi/ic_voice_search.png
Binary files differ
diff --git a/res/drawable-xlarge-mdpi/textfield_end.9.png b/res/drawable-xlarge-mdpi/textfield_end.9.png
new file mode 100644
index 0000000..94706b4
--- /dev/null
+++ b/res/drawable-xlarge-mdpi/textfield_end.9.png
Binary files differ
diff --git a/res/drawable-xlarge-mdpi/textfield_start.9.png b/res/drawable-xlarge-mdpi/textfield_start.9.png
new file mode 100644
index 0000000..8cddc34
--- /dev/null
+++ b/res/drawable-xlarge-mdpi/textfield_start.9.png
Binary files differ
diff --git a/res/drawable-xlarge-nodpi/glow_wallpaper_small.png b/res/drawable-xlarge-nodpi/glow_wallpaper_small.png
new file mode 100644
index 0000000..f83fc60
--- /dev/null
+++ b/res/drawable-xlarge-nodpi/glow_wallpaper_small.png
Binary files differ
diff --git a/res/drawable-xlarge/rotate_button.xml b/res/drawable-xlarge/button_bg.xml
similarity index 89%
rename from res/drawable-xlarge/rotate_button.xml
rename to res/drawable-xlarge/button_bg.xml
index c29efa4..9e6e1ff 100644
--- a/res/drawable-xlarge/rotate_button.xml
+++ b/res/drawable-xlarge/button_bg.xml
@@ -15,6 +15,6 @@
 -->
 
 <selector xmlns:android="http://schemas.android.com/apk/res/android">
-    <item android:state_pressed="true" android:drawable="@drawable/rotate_button_pressed" />
-    <item android:drawable="@drawable/rotate_button_normal" />
+    <item android:state_pressed="true" android:drawable="@drawable/home_press" />
+    <item android:drawable="@android:color/transparent" />
 </selector>
diff --git a/res/layout-xlarge-land/launcher.xml b/res/layout-xlarge-land/launcher.xml
index 8e6f1fe..7516796 100644
--- a/res/layout-xlarge-land/launcher.xml
+++ b/res/layout-xlarge-land/launcher.xml
@@ -54,16 +54,54 @@
         android:layout_height="?android:attr/actionBarSize"
         android:layout_gravity="top">
 
-        <ImageView
-            android:id="@+id/search_button"
+        <LinearLayout android:id="@+id/search_button_cluster"
             android:layout_width="wrap_content"
-            android:layout_height="match_parent"
-            android:layout_gravity="left"
-            android:layout_marginLeft="@dimen/toolbar_button_spacing"
+            android:layout_height="48dp"
+            android:gravity="bottom"
+            >
+            <LinearLayout
+                android:layout_width="wrap_content"
+                android:layout_height="match_parent"
+                android:layout_marginLeft="16dp"
+                android:background="@drawable/textfield_start"
+                android:orientation="horizontal">
+                <!-- Global search icon -->
+                <ImageView
+                    android:id="@+id/search_button"
+                    android:layout_width="wrap_content"
+                    android:layout_height="wrap_content"
+                    android:layout_gravity="bottom"
+                    android:paddingLeft="8dp"
+                    android:paddingRight="8dp"
+                    android:paddingTop="12dp"
+                    android:src="@drawable/search_button_generic"
+                    android:background="@drawable/button_bg"
+                    android:onClick="onClickSearchButton"
+                    android:focusable="true"
+                    android:clickable="true"/>
+            </LinearLayout>
 
-            android:onClick="onClickSearchButton"
-            android:focusable="true"
-            android:clickable="true"/>
+            <LinearLayout
+                android:layout_width="wrap_content"
+                android:layout_height="match_parent"
+                android:background="@drawable/textfield_end"
+                android:orientation="horizontal">
+                <!-- Voice search icon -->
+                <ImageView
+                    android:id="@+id/voice_button"
+                    android:layout_width="wrap_content"
+                    android:layout_height="wrap_content"
+                    android:layout_gravity="bottom"
+                    android:paddingLeft="8dp"
+                    android:paddingRight="8dp"
+                    android:paddingTop="12dp"
+                    android:src="@drawable/ic_voice_search"
+                    android:background="@drawable/button_bg"
+                    android:onClick="onClickVoiceButton"
+                    android:focusable="true"
+                    android:clickable="true"/>
+            </LinearLayout>
+        </LinearLayout>
 
         <ImageView
             android:id="@+id/configure_button"
diff --git a/res/layout-xlarge-port/launcher.xml b/res/layout-xlarge-port/launcher.xml
index a6fff4a..c2ca467 100644
--- a/res/layout-xlarge-port/launcher.xml
+++ b/res/layout-xlarge-port/launcher.xml
@@ -54,16 +54,54 @@
         android:layout_height="?android:attr/actionBarSize"
         android:layout_gravity="top">
 
-        <ImageView
-            android:id="@+id/search_button"
+        <LinearLayout android:id="@+id/search_button_cluster"
             android:layout_width="wrap_content"
-            android:layout_height="match_parent"
-            android:layout_gravity="left"
-            android:layout_marginLeft="@dimen/toolbar_button_spacing"
+            android:layout_height="48dp"
+            android:gravity="bottom"
+            >
+            <LinearLayout
+                android:layout_width="wrap_content"
+                android:layout_height="match_parent"
+                android:layout_marginLeft="16dp"
+                android:background="@drawable/textfield_start"
+                android:orientation="horizontal">
+                <!-- Global search icon -->
+                <ImageView
+                    android:id="@+id/search_button"
+                    android:layout_width="wrap_content"
+                    android:layout_height="wrap_content"
+                    android:layout_gravity="bottom"
+                    android:paddingLeft="8dp"
+                    android:paddingRight="8dp"
+                    android:paddingTop="12dp"
+                    android:src="@drawable/search_button_generic"
+                    android:background="@drawable/button_bg"
+                    android:onClick="onClickSearchButton"
+                    android:focusable="true"
+                    android:clickable="true"/>
+            </LinearLayout>
 
-            android:onClick="onClickSearchButton"
-            android:focusable="true"
-            android:clickable="true"/>
+            <LinearLayout
+                android:layout_width="wrap_content"
+                android:layout_height="match_parent"
+                android:background="@drawable/textfield_end"
+                android:orientation="horizontal">
+                <!-- Voice search icon -->
+                <ImageView
+                    android:id="@+id/voice_button"
+                    android:layout_width="wrap_content"
+                    android:layout_height="wrap_content"
+                    android:layout_gravity="bottom"
+                    android:paddingLeft="8dp"
+                    android:paddingRight="8dp"
+                    android:paddingTop="12dp"
+                    android:src="@drawable/ic_voice_search"
+                    android:background="@drawable/button_bg"
+                    android:onClick="onClickVoiceButton"
+                    android:focusable="true"
+                    android:clickable="true"/>
+            </LinearLayout>
+        </LinearLayout>
 
         <ImageView
             android:id="@+id/configure_button"
diff --git a/src/com/android/launcher2/AllAppsPagedView.java b/src/com/android/launcher2/AllAppsPagedView.java
index f472600..1f3df5f 100644
--- a/src/com/android/launcher2/AllAppsPagedView.java
+++ b/src/com/android/launcher2/AllAppsPagedView.java
@@ -533,8 +533,6 @@
 
         mDragController.removeDropTarget(this);
         endChoiceMode();
-
-        mLauncher.getWorkspace().shrinkToBottomHidden();
     }
 
     @Override
diff --git a/src/com/android/launcher2/CustomizePagedView.java b/src/com/android/launcher2/CustomizePagedView.java
index a7293c8..92140b1 100644
--- a/src/com/android/launcher2/CustomizePagedView.java
+++ b/src/com/android/launcher2/CustomizePagedView.java
@@ -302,6 +302,8 @@
 
         // End the current choice mode so that we don't carry selections across tabs
         endChoiceMode();
+        // Reset the touch item (if we are mid-dragging)
+        mLastTouchedItem = null;
     }
 
     @Override
@@ -417,7 +419,7 @@
         boolean yMoved = yDiff > touchSlop;
         boolean isUpwardMotion = (yDiff / (float) xDiff) > mDragSlopeThreshold;
 
-        if (isUpwardMotion && yMoved) {
+        if (isUpwardMotion && yMoved && mLastTouchedItem != null) {
             // Drag if the user moved far enough along the Y axis
             beginDragging(mLastTouchedItem);
 
diff --git a/src/com/android/launcher2/Launcher.java b/src/com/android/launcher2/Launcher.java
index 9954f39..55a6176 100644
--- a/src/com/android/launcher2/Launcher.java
+++ b/src/com/android/launcher2/Launcher.java
@@ -73,6 +73,7 @@
 import android.os.SystemProperties;
 import android.provider.LiveFolders;
 import android.provider.Settings;
+import android.speech.RecognizerIntent;
 import android.text.Selection;
 import android.text.SpannableStringBuilder;
 import android.text.TextUtils;
@@ -1925,6 +1926,21 @@
     }
 
     /**
+     * Event handler for the voice button
+     *
+     * @param v The view that was clicked.
+     */
+    public void onClickVoiceButton(View v) {
+        startVoiceSearch();
+    }
+
+    private void startVoiceSearch() {
+        Intent intent = new Intent(RecognizerIntent.ACTION_WEB_SEARCH);
+        intent.setFlags(Intent.FLAG_ACTIVITY_NEW_TASK);
+        startActivity(intent);
+    }
+
+    /**
      * Event handler for the "gear" button that appears on the home screen, which
      * enters home screen customization mode.
      *
@@ -2477,7 +2493,7 @@
      * @param hideSeq AnimatorSet in which to put "hide" animations, or null.
      */
     private void hideAndShowToolbarButtons(State newState, AnimatorSet showSeq, AnimatorSet hideSeq) {
-        final View searchButton = findViewById(R.id.search_button);
+        final View searchButton = findViewById(R.id.search_button_cluster);
         final View allAppsButton = findViewById(R.id.all_apps_button);
         final View configureButton = findViewById(R.id.configure_button);
 
@@ -2898,6 +2914,21 @@
             if (activityName != null) {
                 updateButtonWithIconFromExternalActivity(
                         R.id.search_button, activityName, R.drawable.search_button_generic);
+            } else {
+                findViewById(R.id.search_button).setVisibility(View.GONE);
+            }
+        }
+    }
+
+    private void updateVoiceSearchIcon() {
+        if (LauncherApplication.isScreenXLarge()) {
+            Intent intent = new Intent(RecognizerIntent.ACTION_WEB_SEARCH);
+            ComponentName activityName = intent.resolveActivity(getPackageManager());
+            if (activityName != null) {
+                updateButtonWithIconFromExternalActivity(
+                        R.id.voice_button, activityName, R.drawable.ic_voice_search);
+            } else {
+                findViewById(R.id.voice_button).setVisibility(View.GONE);
             }
         }
     }
@@ -3249,6 +3280,16 @@
     }
 
     /**
+     * Updates the icons on the launcher that are affected by changes to the package list
+     * on the device.
+     */
+    private void updateIconsAffectedByPackageManagerChanges() {
+        updateAppMarketIcon();
+        updateGlobalSearchIcon();
+        updateVoiceSearchIcon();
+    }
+
+    /**
      * Add the icons for all apps.
      *
      * Implementation of the method from LauncherModel.Callbacks.
@@ -3258,8 +3299,7 @@
         if (mCustomizePagedView != null) {
             mCustomizePagedView.setApps(apps);
         }
-        updateAppMarketIcon();
-        updateGlobalSearchIcon();
+        updateIconsAffectedByPackageManagerChanges();
     }
 
     /**
@@ -3274,8 +3314,7 @@
         if (mCustomizePagedView != null) {
             mCustomizePagedView.addApps(apps);
         }
-        updateAppMarketIcon();
-        updateGlobalSearchIcon();
+        updateIconsAffectedByPackageManagerChanges();
     }
 
     /**
@@ -3291,8 +3330,7 @@
         if (mCustomizePagedView != null) {
             mCustomizePagedView.updateApps(apps);
         }
-        updateAppMarketIcon();
-        updateGlobalSearchIcon();
+        updateIconsAffectedByPackageManagerChanges();
     }
 
     /**
@@ -3309,8 +3347,7 @@
         if (mCustomizePagedView != null) {
             mCustomizePagedView.removeApps(apps);
         }
-        updateAppMarketIcon();
-        updateGlobalSearchIcon();
+        updateIconsAffectedByPackageManagerChanges();
     }
 
     /**
diff --git a/src/com/android/launcher2/PagedView.java b/src/com/android/launcher2/PagedView.java
index fb8b7d6..a2ed985 100644
--- a/src/com/android/launcher2/PagedView.java
+++ b/src/com/android/launcher2/PagedView.java
@@ -27,7 +27,6 @@
 import android.os.Parcel;
 import android.os.Parcelable;
 import android.util.AttributeSet;
-import android.util.Log;
 import android.view.ActionMode;
 import android.view.MotionEvent;
 import android.view.VelocityTracker;
@@ -36,10 +35,9 @@
 import android.view.ViewGroup;
 import android.view.ViewParent;
 import android.view.animation.Animation;
-import android.view.animation.Animation.AnimationListener;
 import android.view.animation.AnimationUtils;
+import android.view.animation.Animation.AnimationListener;
 import android.widget.Checkable;
-import android.widget.LinearLayout;
 import android.widget.Scroller;
 
 import com.android.launcher.R;
@@ -60,6 +58,8 @@
     private static final int PAGE_SNAP_ANIMATION_DURATION = 750;
     protected static final float NANOTIME_DIV = 1000000000.0f;
 
+    private static final float OVERSCROLL_DAMP_FACTOR = 0.22f;
+
     // the velocity at which a fling gesture will cause us to snap to the next page
     protected int mSnapVelocity = 500;
 
@@ -70,6 +70,7 @@
 
     protected int mCurrentPage;
     protected int mNextPage = INVALID_PAGE;
+    protected int mMaxScrollX;
     protected Scroller mScroller;
     private VelocityTracker mVelocityTracker;
 
@@ -103,6 +104,8 @@
     protected int mCellCountX;
     protected int mCellCountY;
     protected boolean mCenterPagesVertically;
+    protected boolean mAllowOverScroll = true;
+    protected int mUnboundedScrollX;
 
     protected static final int INVALID_POINTER = -1;
 
@@ -312,8 +315,28 @@
     }
 
     @Override
+    public void scrollBy(int x, int y) {
+        scrollTo(mUnboundedScrollX + x, mScrollY + y);
+    }
+
+    @Override
     public void scrollTo(int x, int y) {
-        super.scrollTo(x, y);
+        mUnboundedScrollX = x;
+
+        if (x < 0) {
+            super.scrollTo(0, y);
+            if (mAllowOverScroll) {
+                overScroll(x);
+            }
+        } else if (x > mMaxScrollX) {
+            super.scrollTo(mMaxScrollX, y);
+            if (mAllowOverScroll) {
+                overScroll(x - mMaxScrollX);
+            }
+        } else {
+            super.scrollTo(x, y);
+        }
+
         mTouchX = x;
         mSmoothingTime = System.nanoTime() / NANOTIME_DIV;
     }
@@ -395,6 +418,7 @@
             heightSize = maxChildHeight + verticalPadding;
         }
 
+        mMaxScrollX = getChildOffset(childCount - 1) - getRelativeChildOffset(childCount - 1);
         setMeasuredDimension(widthSize, heightSize);
     }
 
@@ -799,6 +823,16 @@
         return false;
     }
 
+    protected void overScroll(float amount) {
+        int overScrollAmount = (int) Math.round(OVERSCROLL_DAMP_FACTOR * amount);
+        if (amount < 0) {
+            mScrollX = overScrollAmount;
+        } else {
+            mScrollX = mMaxScrollX + overScrollAmount;
+        }
+        invalidate();
+    }
+
     @Override
     public boolean onTouchEvent(MotionEvent ev) {
         // Skip touch handling if there are no pages to swipe
@@ -834,31 +868,13 @@
                 final int deltaX = (int) (mLastMotionX - x);
                 mLastMotionX = x;
 
-                int sx = getScrollX();
-                if (deltaX < 0) {
-                    if (sx > 0) {
-                        mTouchX += Math.max(-mTouchX, deltaX);
-                        mSmoothingTime = System.nanoTime() / NANOTIME_DIV;
-                        if (!mDeferScrollUpdate) {
-                            scrollBy(Math.max(-sx, deltaX), 0);
-                        } else {
-                            // This will trigger a call to computeScroll() on next drawChild() call
-                            invalidate();
-                        }
-                    }
-                } else if (deltaX > 0) {
-                    final int lastChildIndex = getChildCount() - 1;
-                    final int availableToScroll = getChildOffset(lastChildIndex) -
-                        getRelativeChildOffset(lastChildIndex) - sx;
-                    if (availableToScroll > 0) {
-                        mTouchX += Math.min(availableToScroll, deltaX);
-                        mSmoothingTime = System.nanoTime() / NANOTIME_DIV;
-                        if (!mDeferScrollUpdate) {
-                            scrollBy(Math.min(availableToScroll, deltaX), 0);
-                        } else {
-                            // This will trigger a call to computeScroll() on next drawChild() call
-                            invalidate();
-                        }
+                if (deltaX != 0) {
+                    mTouchX += deltaX;
+                    mSmoothingTime = System.nanoTime() / NANOTIME_DIV;
+                    if (!mDeferScrollUpdate) {
+                        scrollBy(deltaX, 0);
+                    } else {
+                        invalidate();
                     }
                 } else {
                     awakenScrollBars();
@@ -1042,7 +1058,7 @@
         whichPage = Math.max(0, Math.min(whichPage, getPageCount() - 1));
 
         int newX = getChildOffset(whichPage) - getRelativeChildOffset(whichPage);
-        final int sX = getScrollX();
+        final int sX = mUnboundedScrollX;
         final int delta = newX - sX;
         snapToPage(whichPage, delta, duration);
     }
@@ -1063,7 +1079,7 @@
         }
 
         if (!mScroller.isFinished()) mScroller.abortAnimation();
-        mScroller.startScroll(getScrollX(), 0, delta, 0, duration);
+        mScroller.startScroll(mUnboundedScrollX, 0, delta, 0, duration);
 
         // only load some associated pages
         loadAssociatedPages(mNextPage);
diff --git a/src/com/android/launcher2/SmoothPagedView.java b/src/com/android/launcher2/SmoothPagedView.java
index 8e729e4..ee8bca2 100644
--- a/src/com/android/launcher2/SmoothPagedView.java
+++ b/src/com/android/launcher2/SmoothPagedView.java
@@ -138,7 +138,7 @@
 
         final int screenDelta = Math.max(1, Math.abs(whichPage - mCurrentPage));
         final int newX = getChildOffset(whichPage) - getRelativeChildOffset(whichPage);
-        final int delta = newX - mScrollX;
+        final int delta = newX - mUnboundedScrollX;
         int duration;
         if (mScrollMode == OVERSHOOT_MODE) {
             duration = (screenDelta + 1) * 100;
@@ -180,8 +180,9 @@
         if (!scrollComputed && mTouchState == TOUCH_STATE_SCROLLING) {
             final float now = System.nanoTime() / NANOTIME_DIV;
             final float e = (float) Math.exp((now - mSmoothingTime) / SMOOTHING_CONSTANT);
-            final float dx = mTouchX - mScrollX;
-            mScrollX += dx * e;
+
+            final float dx = mTouchX - mUnboundedScrollX;
+            scrollTo(Math.round(mUnboundedScrollX + dx * e), mScrollY);
             mSmoothingTime = now;
 
             // Keep generating points as long as we're more than 1px away from the target
@@ -189,5 +190,6 @@
                 invalidate();
             }
         }
+
     }
 }
diff --git a/src/com/android/launcher2/Workspace.java b/src/com/android/launcher2/Workspace.java
index 169f53f..69be008 100644
--- a/src/com/android/launcher2/Workspace.java
+++ b/src/com/android/launcher2/Workspace.java
@@ -20,14 +20,13 @@
 import java.util.HashSet;
 import java.util.List;
 
-import android.R.integer;
 import android.animation.Animator;
-import android.animation.Animator.AnimatorListener;
 import android.animation.AnimatorSet;
 import android.animation.ObjectAnimator;
 import android.animation.PropertyValuesHolder;
 import android.animation.TimeInterpolator;
 import android.animation.ValueAnimator;
+import android.animation.Animator.AnimatorListener;
 import android.animation.ValueAnimator.AnimatorUpdateListener;
 import android.app.AlertDialog;
 import android.app.WallpaperManager;
@@ -92,14 +91,24 @@
     private static final float EXTRA_SCALE_FACTOR_1 = 1.0f;
     private static final float EXTRA_SCALE_FACTOR_2 = 1.10f;
 
-    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;
+    private static final int CHILDREN_OUTLINE_FADE_OUT_DELAY = 0;
+    private static final int CHILDREN_OUTLINE_FADE_OUT_DURATION = 375;
+    private static final int CHILDREN_OUTLINE_FADE_IN_DURATION = 100;
 
-    // These animators are used to fade the background
+    private static final int BACKGROUND_FADE_OUT_DURATION = 450;
+    private static final int BACKGROUND_FADE_IN_DURATION = 350;
+
+    // These animators are used to fade the children's outlines
+    private ObjectAnimator mChildrenOutlineFadeInAnimation;
+    private ObjectAnimator mChildrenOutlineFadeOutAnimation;
+    private float mChildrenOutlineAlpha = 0;
+
+    // These properties refer to the background protection gradient used for AllApps and Customize
     private ObjectAnimator mBackgroundFadeInAnimation;
     private ObjectAnimator mBackgroundFadeOutAnimation;
+    private Drawable mBackground;
     private float mBackgroundAlpha = 0;
+    private float mOverScrollMaxBackgroundAlpha = 0.0f;
 
     private final WallpaperManager mWallpaperManager;
 
@@ -236,6 +245,13 @@
         mExternalDragOutlinePaint.setAntiAlias(true);
         setWillNotDraw(false);
 
+        try {
+            final Resources res = getResources();
+            mBackground = res.getDrawable(R.drawable.all_apps_bg_gradient);
+        } catch (Resources.NotFoundException e) {
+            // In this case, we will skip drawing background protection
+        }
+
         mUnshrinkAnimationListener = new LauncherAnimatorListenerAdapter() {
             @Override
             public void onAnimationStart(Animator animation) {
@@ -499,7 +515,7 @@
             mAnimOnPageEndMoving.start();
             mAnimOnPageEndMoving = null;
         }
-
+        mOverScrollMaxBackgroundAlpha = 0.0f;
         mPageMoving = false;
     }
 
@@ -539,33 +555,60 @@
 
     public void showOutlines() {
         if (!mIsSmall && !mIsInUnshrinkAnimation) {
-            if (mBackgroundFadeOutAnimation != null) mBackgroundFadeOutAnimation.cancel();
-            if (mBackgroundFadeInAnimation != null) mBackgroundFadeInAnimation.cancel();
-            mBackgroundFadeInAnimation = ObjectAnimator.ofFloat(this, "backgroundAlpha", 1.0f);
-            mBackgroundFadeInAnimation.setDuration(BACKGROUND_FADE_IN_DURATION);
-            mBackgroundFadeInAnimation.start();
+            if (mChildrenOutlineFadeOutAnimation != null) mChildrenOutlineFadeOutAnimation.cancel();
+            if (mChildrenOutlineFadeInAnimation != null) mChildrenOutlineFadeInAnimation.cancel();
+            mChildrenOutlineFadeInAnimation = ObjectAnimator.ofFloat(this, "childrenOutlineAlpha", 1.0f);
+            mChildrenOutlineFadeInAnimation.setDuration(CHILDREN_OUTLINE_FADE_IN_DURATION);
+            mChildrenOutlineFadeInAnimation.start();
         }
     }
 
     public void hideOutlines() {
         if (!mIsSmall && !mIsInUnshrinkAnimation) {
-            if (mBackgroundFadeInAnimation != null) mBackgroundFadeInAnimation.cancel();
-            if (mBackgroundFadeOutAnimation != null) mBackgroundFadeOutAnimation.cancel();
-            mBackgroundFadeOutAnimation = ObjectAnimator.ofFloat(this, "backgroundAlpha", 0.0f);
-            mBackgroundFadeOutAnimation.setDuration(BACKGROUND_FADE_OUT_DURATION);
-            mBackgroundFadeOutAnimation.setStartDelay(BACKGROUND_FADE_OUT_DELAY);
-            mBackgroundFadeOutAnimation.start();
+            if (mChildrenOutlineFadeInAnimation != null) mChildrenOutlineFadeInAnimation.cancel();
+            if (mChildrenOutlineFadeOutAnimation != null) mChildrenOutlineFadeOutAnimation.cancel();
+            mChildrenOutlineFadeOutAnimation = ObjectAnimator.ofFloat(this, "childrenOutlineAlpha", 0.0f);
+            mChildrenOutlineFadeOutAnimation.setDuration(CHILDREN_OUTLINE_FADE_OUT_DURATION);
+            mChildrenOutlineFadeOutAnimation.setStartDelay(CHILDREN_OUTLINE_FADE_OUT_DELAY);
+            mChildrenOutlineFadeOutAnimation.start();
         }
     }
 
-    public void setBackgroundAlpha(float alpha) {
-        mBackgroundAlpha = alpha;
+    public void setChildrenOutlineAlpha(float alpha) {
+        mChildrenOutlineAlpha = alpha;
         for (int i = 0; i < getChildCount(); i++) {
             CellLayout cl = (CellLayout) getChildAt(i);
             cl.setBackgroundAlpha(alpha);
         }
     }
 
+    public float getChildrenOutlineAlpha() {
+        return mChildrenOutlineAlpha;
+    }
+
+    public void showBackgroundGradient() {
+        if (mBackground == null) return;
+        if (mBackgroundFadeOutAnimation != null) mBackgroundFadeOutAnimation.cancel();
+        if (mBackgroundFadeInAnimation != null) mBackgroundFadeInAnimation.cancel();
+        mBackgroundFadeInAnimation = ObjectAnimator.ofFloat(this, "backgroundAlpha", 1.0f);
+        mBackgroundFadeInAnimation.setDuration(BACKGROUND_FADE_IN_DURATION);
+        mBackgroundFadeInAnimation.start();
+    }
+
+    public void hideBackgroundGradient() {
+        if (mBackground == null) return;
+        if (mBackgroundFadeInAnimation != null) mBackgroundFadeInAnimation.cancel();
+        if (mBackgroundFadeOutAnimation != null) mBackgroundFadeOutAnimation.cancel();
+        mBackgroundFadeOutAnimation = ObjectAnimator.ofFloat(this, "backgroundAlpha", 0.0f);
+        mBackgroundFadeOutAnimation.setDuration(BACKGROUND_FADE_OUT_DURATION);
+        mBackgroundFadeOutAnimation.start();
+    }
+
+    public void setBackgroundAlpha(float alpha) {
+        mBackgroundAlpha = alpha;
+        invalidate();
+    }
+
     public float getBackgroundAlpha() {
         return mBackgroundAlpha;
     }
@@ -595,9 +638,51 @@
         return (width - mTempFloat2[0]) * (degrees > 0.0f ? 1.0f : -1.0f);
     }
 
+    float backgroundAlphaInterpolator(float r) {
+        float pivotA = 0.1f;
+        float pivotB = 0.4f;
+        if (r < pivotA) {
+            return 0;
+        } else if (r > pivotB) {
+            return 1.0f;
+        } else {
+            return (r - pivotA)/(pivotB - pivotA);
+        }
+    }
+
+    float overScrollBackgroundAlphaInterpolator(float r) {
+        float threshold = 0.1f;
+
+        if (r > mOverScrollMaxBackgroundAlpha) {
+            mOverScrollMaxBackgroundAlpha = r;
+        } else if (r < mOverScrollMaxBackgroundAlpha) {
+            r = mOverScrollMaxBackgroundAlpha;
+        }
+
+        return Math.min(r / threshold, 1.0f);
+    }
+
+    protected void overScroll(float amount) {
+        final int lastChildIndex = getChildCount() - 1;
+
+        CellLayout cl;
+        if (amount < 0) {
+            cl = (CellLayout) getChildAt(0);
+        } else {
+            cl = (CellLayout) getChildAt(lastChildIndex);
+        }
+
+        final int totalDistance = cl.getMeasuredWidth() + mPageSpacing;
+        float r = 1.0f * amount / totalDistance;
+        float rotation = -WORKSPACE_ROTATION * r;
+        cl.setBackgroundAlphaMultiplier(overScrollBackgroundAlphaInterpolator(Math.abs(r)));
+        cl.setRotationY(rotation);
+    }
+
     @Override
     protected void screenScrolled(int screenCenter) {
         final int halfScreenSize = getMeasuredWidth() / 2;
+
         for (int i = 0; i < getChildCount(); i++) {
             CellLayout cl = (CellLayout) getChildAt(i);
             if (cl != null) {
@@ -609,11 +694,12 @@
                 scrollProgress = Math.min(scrollProgress, 1.0f);
                 scrollProgress = Math.max(scrollProgress, -1.0f);
 
-                cl.setBackgroundAlphaMultiplier(Math.abs(scrollProgress));
+                cl.setBackgroundAlphaMultiplier(backgroundAlphaInterpolator(Math.abs(scrollProgress)));
 
                 float rotation = WORKSPACE_ROTATION * scrollProgress;
                 float translationX = getOffsetXForRotation(rotation, cl.getWidth(), cl.getHeight());
                 cl.setTranslationX(translationX);
+
                 cl.setRotationY(rotation);
             }
         }
@@ -644,6 +730,18 @@
     }
 
     @Override
+    protected void onDraw(Canvas canvas) {
+        // Draw the background gradient if necessary
+        if (mBackground != null && mBackgroundAlpha > 0.0f) {
+            mBackground.setAlpha((int) (mBackgroundAlpha * 255));
+            mBackground.setBounds(mScrollX, 0, mScrollX + getMeasuredWidth(), getMeasuredHeight());
+            mBackground.draw(canvas);
+        }
+
+        super.onDraw(canvas);
+    }
+
+    @Override
     protected void dispatchDraw(Canvas canvas) {
         if (mIsSmall || mIsInUnshrinkAnimation) {
             // Draw all the workspaces if we're small
@@ -822,6 +920,8 @@
 
     // we use this to shrink the workspace for the all apps view and the customize view
     private void shrink(ShrinkPosition shrinkPosition, boolean animated) {
+        showBackgroundGradient();
+
         if (mFirstLayout) {
             // (mFirstLayout == "first layout has not happened yet")
             // if we get a call to shrink() as part of our initialization (for example, if
@@ -1103,6 +1203,8 @@
     }
 
     void unshrink(boolean animated) {
+        hideBackgroundGradient();
+
         if (mIsSmall) {
             mIsSmall = false;
             if (mAnimator != null) {
@@ -1372,7 +1474,6 @@
             }
         });
 
-
         view.setVisibility(View.INVISIBLE);
 
         if (!mScroller.isFinished()) {