Using material style overscroll effect for workspace and folders

Bug: 21335369
Change-Id: I53cc6edfa87334b9326f1dedd90c3e2222beade5
diff --git a/res/drawable-hdpi/overscroll_glow_left.9.png b/res/drawable-hdpi/overscroll_glow_left.9.png
deleted file mode 100644
index aaf43c7..0000000
--- a/res/drawable-hdpi/overscroll_glow_left.9.png
+++ /dev/null
Binary files differ
diff --git a/res/drawable-hdpi/overscroll_glow_right.9.png b/res/drawable-hdpi/overscroll_glow_right.9.png
deleted file mode 100644
index d034864..0000000
--- a/res/drawable-hdpi/overscroll_glow_right.9.png
+++ /dev/null
Binary files differ
diff --git a/res/drawable-mdpi/overscroll_glow_left.9.png b/res/drawable-mdpi/overscroll_glow_left.9.png
deleted file mode 100644
index b79cdcd..0000000
--- a/res/drawable-mdpi/overscroll_glow_left.9.png
+++ /dev/null
Binary files differ
diff --git a/res/drawable-mdpi/overscroll_glow_right.9.png b/res/drawable-mdpi/overscroll_glow_right.9.png
deleted file mode 100644
index 1321303..0000000
--- a/res/drawable-mdpi/overscroll_glow_right.9.png
+++ /dev/null
Binary files differ
diff --git a/res/drawable-xhdpi/overscroll_glow_left.9.png b/res/drawable-xhdpi/overscroll_glow_left.9.png
deleted file mode 100644
index 4f248f7..0000000
--- a/res/drawable-xhdpi/overscroll_glow_left.9.png
+++ /dev/null
Binary files differ
diff --git a/res/drawable-xhdpi/overscroll_glow_right.9.png b/res/drawable-xhdpi/overscroll_glow_right.9.png
deleted file mode 100644
index 818a70d..0000000
--- a/res/drawable-xhdpi/overscroll_glow_right.9.png
+++ /dev/null
Binary files differ
diff --git a/res/drawable-xxhdpi/overscroll_glow_left.9.png b/res/drawable-xxhdpi/overscroll_glow_left.9.png
deleted file mode 100644
index 1a895cd..0000000
--- a/res/drawable-xxhdpi/overscroll_glow_left.9.png
+++ /dev/null
Binary files differ
diff --git a/res/drawable-xxhdpi/overscroll_glow_right.9.png b/res/drawable-xxhdpi/overscroll_glow_right.9.png
deleted file mode 100644
index 5766761..0000000
--- a/res/drawable-xxhdpi/overscroll_glow_right.9.png
+++ /dev/null
Binary files differ
diff --git a/res/values/colors.xml b/res/values/colors.xml
index e2b8a2e..7d36101 100644
--- a/res/values/colors.xml
+++ b/res/values/colors.xml
@@ -29,6 +29,9 @@
 
     <color name="workspace_icon_text_color">#FFF</color>
 
+    <color name="workspace_edge_effect_color">#FFFFFFFF</color>
+    <color name="folder_edge_effect_color">#FF757575</color>
+
     <color name="quantum_panel_text_color">#FF666666</color>
     <color name="quantum_panel_bg_color">#FFF5F5F5</color>
     <color name="quantum_panel_bg_color_dark">#FF243036</color>
diff --git a/src/com/android/launcher3/CellLayout.java b/src/com/android/launcher3/CellLayout.java
index b5d0dca..2cde3d5 100644
--- a/src/com/android/launcher3/CellLayout.java
+++ b/src/com/android/launcher3/CellLayout.java
@@ -100,17 +100,11 @@
     private ArrayList<FolderRingAnimator> mFolderOuterRings = new ArrayList<FolderRingAnimator>();
     private int[] mFolderLeaveBehindCell = {-1, -1};
 
-    private static final float FOREGROUND_ALPHA_DAMPER = 0.65f;
-    private int mForegroundAlpha = 0;
     private float mBackgroundAlpha;
 
     private static final int BACKGROUND_ACTIVATE_DURATION = 120;
     private final TransitionDrawable mBackground;
 
-    private final Drawable mOverScrollLeft;
-    private final Drawable mOverScrollRight;
-    private Drawable mOverScrollForegroundDrawable;
-
     // These values allow a fixed measurement to be set on the CellLayout.
     private int mFixedWidth = -1;
     private int mFixedHeight = -1;
@@ -218,9 +212,6 @@
         mBackground = (TransitionDrawable) res.getDrawable(R.drawable.bg_screenpanel);
         mBackground.setCallback(this);
 
-        mOverScrollLeft = res.getDrawable(R.drawable.overscroll_glow_left);
-        mOverScrollRight = res.getDrawable(R.drawable.overscroll_glow_right);
-
         mReorderPreviewAnimationMagnitude = (REORDER_PREVIEW_MAGNITUDE *
                 grid.iconSizePx);
 
@@ -396,19 +387,6 @@
         return mDropPending;
     }
 
-    void setOverScrollAmount(float r, boolean left) {
-        if (left && mOverScrollForegroundDrawable != mOverScrollLeft) {
-            mOverScrollForegroundDrawable = mOverScrollLeft;
-        } else if (!left && mOverScrollForegroundDrawable != mOverScrollRight) {
-            mOverScrollForegroundDrawable = mOverScrollRight;
-        }
-
-        r *= FOREGROUND_ALPHA_DAMPER;
-        mForegroundAlpha = (int) Math.round((r * 255));
-        mOverScrollForegroundDrawable.setAlpha(mForegroundAlpha);
-        invalidate();
-    }
-
     @Override
     public void setPressedIcon(BubbleTextView icon, Bitmap background) {
         if (icon == null || background == null) {
@@ -553,14 +531,6 @@
         }
     }
 
-    @Override
-    protected void dispatchDraw(Canvas canvas) {
-        super.dispatchDraw(canvas);
-        if (mForegroundAlpha > 0) {
-            mOverScrollForegroundDrawable.draw(canvas);
-        }
-    }
-
     public void showFolderAccept(FolderRingAnimator fra) {
         mFolderOuterRings.add(fra);
     }
@@ -920,9 +890,6 @@
         mBackground.getPadding(mTempRect);
         mBackground.setBounds(-mTempRect.left, -mTempRect.top,
                 w + mTempRect.right, h + mTempRect.bottom);
-
-        mOverScrollLeft.setBounds(0, 0, w, h);
-        mOverScrollRight.setBounds(0, 0, w, h);
     }
 
     @Override
diff --git a/src/com/android/launcher3/FolderPagedView.java b/src/com/android/launcher3/FolderPagedView.java
index b7a5aa8..f2ec1b6 100644
--- a/src/com/android/launcher3/FolderPagedView.java
+++ b/src/com/android/launcher3/FolderPagedView.java
@@ -97,6 +97,8 @@
 
         mIsRtl = Utilities.isRtl(getResources());
         setImportantForAccessibility(IMPORTANT_FOR_ACCESSIBILITY_YES);
+
+        setEdgeGlowColor(getResources().getColor(R.color.folder_edge_effect_color));
     }
 
     public void setFolder(Folder folder) {
@@ -459,16 +461,16 @@
                 ? -SCROLL_HINT_FRACTION : SCROLL_HINT_FRACTION;
         int hint = (int) (fraction * getWidth());
         int scroll = getScrollForPage(getNextPage()) + hint;
-        int delta = scroll - mUnboundedScrollX;
+        int delta = scroll - getScrollX();
         if (delta != 0) {
             mScroller.setInterpolator(new DecelerateInterpolator());
-            mScroller.startScroll(mUnboundedScrollX, 0, delta, 0, Folder.SCROLL_HINT_DURATION);
+            mScroller.startScroll(getScrollX(), 0, delta, 0, Folder.SCROLL_HINT_DURATION);
             invalidate();
         }
     }
 
     public void clearScrollHint() {
-        if (mUnboundedScrollX != getScrollForPage(getNextPage())) {
+        if (getScrollX() != getScrollForPage(getNextPage())) {
             snapToPage(getNextPage());
         }
     }
@@ -667,4 +669,10 @@
     public int itemsPerPage() {
         return mMaxItemsPerPage;
     }
+
+    @Override
+    protected void getEdgeVerticalPostion(int[] pos) {
+        pos[0] = 0;
+        pos[1] = getViewportHeight();
+    }
 }
diff --git a/src/com/android/launcher3/PagedView.java b/src/com/android/launcher3/PagedView.java
index 3d00034..e3cdc29 100644
--- a/src/com/android/launcher3/PagedView.java
+++ b/src/com/android/launcher3/PagedView.java
@@ -47,7 +47,10 @@
 import android.view.accessibility.AccessibilityManager;
 import android.view.accessibility.AccessibilityNodeInfo;
 import android.view.animation.Interpolator;
+
+import com.android.launcher3.util.LauncherEdgeEffect;
 import com.android.launcher3.util.Thunk;
+
 import java.util.ArrayList;
 
 /**
@@ -63,13 +66,9 @@
     private static final int MIN_LENGTH_FOR_FLING = 25;
 
     protected static final int PAGE_SNAP_ANIMATION_DURATION = 750;
-    protected static final int OVER_SCROLL_PAGE_SNAP_ANIMATION_DURATION = 350;
     protected static final int SLOW_PAGE_SNAP_ANIMATION_DURATION = 950;
     protected static final float NANOTIME_DIV = 1000000000.0f;
 
-    private static final float OVERSCROLL_ACCELERATE_FACTOR = 2;
-    private static final float OVERSCROLL_DAMP_FACTOR = 0.07f;
-
     private static final float RETURN_TO_ORIGINAL_PAGE_THRESHOLD = 0.33f;
     // The page is moved more than halfway, automatically move to the next page on touch up.
     private static final float SIGNIFICANT_MOVE_THRESHOLD = 0.4f;
@@ -82,10 +81,6 @@
     private static final int MIN_SNAP_VELOCITY = 1500;
     private static final int MIN_FLING_VELOCITY = 250;
 
-    // We are disabling touch interaction of the widget region for factory ROM.
-    private static final boolean DISABLE_TOUCH_INTERACTION = false;
-    private static final boolean DISABLE_TOUCH_SIDE_PAGES = true;
-
     public static final int INVALID_RESTORE_PAGE = -1001;
 
     private boolean mFreeScroll = false;
@@ -153,15 +148,9 @@
     protected int mCellCountY = 0;
     protected boolean mCenterPagesVertically;
     protected boolean mAllowOverScroll = true;
-    protected int mUnboundedScrollX;
     protected int[] mTempVisiblePagesRange = new int[2];
     protected boolean mForceDrawAllChildrenNextFrame;
 
-    // mOverScrollX is equal to getScrollX() when we're within the normal scroll range. Otherwise
-    // it is equal to the scaled overscroll position. We use a separate value so as to prevent
-    // the screens from continuing to translate beyond the normal bounds.
-    protected int mOverScrollX;
-
     protected static final int INVALID_POINTER = -1;
 
     protected int mActivePointerId = INVALID_POINTER;
@@ -192,7 +181,6 @@
     private float mMinScale = 1f;
     private boolean mUseMinScale = false;
     protected View mDragView;
-    protected AnimatorSet mZoomInOutAnim;
     private Runnable mSidePageHoverRunnable;
     @Thunk int mSidePageHoverIndex = -1;
     // This variable's scope is only for the duration of startReordering() and endReordering()
@@ -214,6 +202,10 @@
     protected final Rect mInsets = new Rect();
     protected final boolean mIsRtl;
 
+    // Edge effect
+    private final LauncherEdgeEffect mEdgeGlowLeft = new LauncherEdgeEffect();
+    private final LauncherEdgeEffect mEdgeGlowRight = new LauncherEdgeEffect();
+
     public interface PageSwitchListener {
         void onPageSwitch(View newPage, int newPageIndex);
     }
@@ -262,6 +254,12 @@
         mMinFlingVelocity = (int) (MIN_FLING_VELOCITY * mDensity);
         mMinSnapVelocity = (int) (MIN_SNAP_VELOCITY * mDensity);
         setOnHierarchyChangeListener(this);
+        setWillNotDraw(false);
+    }
+
+    protected void setEdgeGlowColor(int color) {
+        mEdgeGlowLeft.setColor(color);
+        mEdgeGlowRight.setColor(color);
     }
 
     protected void setDefaultInterpolator(Interpolator interpolator) {
@@ -560,7 +558,7 @@
 
     @Override
     public void scrollBy(int x, int y) {
-        scrollTo(mUnboundedScrollX + x, getScrollY() + y);
+        scrollTo(getScrollX() + x, getScrollY() + y);
     }
 
     @Override
@@ -578,12 +576,10 @@
             x = Math.max(x, mFreeScrollMinScrollX);
         }
 
-        mUnboundedScrollX = x;
-
         boolean isXBeforeFirstPage = mIsRtl ? (x > mMaxScrollX) : (x < 0);
         boolean isXAfterLastPage = mIsRtl ? (x < 0) : (x > mMaxScrollX);
         if (isXBeforeFirstPage) {
-            super.scrollTo(0, y);
+            super.scrollTo(mIsRtl ? mMaxScrollX : 0, y);
             if (mAllowOverScroll) {
                 mWasInOverscroll = true;
                 if (mIsRtl) {
@@ -593,7 +589,7 @@
                 }
             }
         } else if (isXAfterLastPage) {
-            super.scrollTo(mMaxScrollX, y);
+            super.scrollTo(mIsRtl ? 0 : mMaxScrollX, y);
             if (mAllowOverScroll) {
                 mWasInOverscroll = true;
                 if (mIsRtl) {
@@ -607,7 +603,6 @@
                 overScroll(0);
                 mWasInOverscroll = false;
             }
-            mOverScrollX = x;
             super.scrollTo(x, y);
         }
 
@@ -646,8 +641,7 @@
         if (mScroller.computeScrollOffset()) {
             // Don't bother scrolling if the page does not need to be moved
             if (getScrollX() != mScroller.getCurrX()
-                || getScrollY() != mScroller.getCurrY()
-                || mOverScrollX != mScroller.getCurrX()) {
+                || getScrollY() != mScroller.getCurrY()) {
                 float scaleX = mFreeScroll ? getScaleX() : 1f;
                 int scrollX = (int) (mScroller.getCurrX() * (1 / scaleX));
                 scrollTo(scrollX, mScroller.getCurrY());
@@ -978,21 +972,10 @@
         requestLayout();
     }
 
-    protected void screenScrolled(int screenCenter) {
-        boolean isInOverscroll = mOverScrollX < 0 || mOverScrollX > mMaxScrollX;
-
-        if (mFadeInAdjacentScreens && !isInOverscroll) {
-            for (int i = 0; i < getChildCount(); i++) {
-                View child = getChildAt(i);
-                if (child != null) {
-                    float scrollProgress = getScrollProgress(screenCenter, child, i);
-                    float alpha = 1 - Math.abs(scrollProgress);
-                    child.setAlpha(alpha);
-                }
-            }
-            invalidate();
-        }
-    }
+    /**
+     * Called when the center screen changes during scrolling.
+     */
+    protected void screenScrolled(int screenCenter) { }
 
     @Override
     public void onChildViewAdded(View parent, View child) {
@@ -1129,9 +1112,7 @@
         final int pageCount = getChildCount();
         if (pageCount > 0) {
             int halfScreenSize = getViewportWidth() / 2;
-            // mOverScrollX is equal to getScrollX() when we're within the normal scroll range.
-            // Otherwise it is equal to the scaled overscroll position.
-            int screenCenter = mOverScrollX + halfScreenSize;
+            int screenCenter = getScrollX() + halfScreenSize;
 
             if (screenCenter != mLastScreenCenter || mForceScreenScrolled) {
                 // set mForceScreenScrolled before calling screenScrolled so that screenScrolled can
@@ -1172,6 +1153,47 @@
     }
 
     @Override
+    public void draw(Canvas canvas) {
+        super.draw(canvas);
+        if (getPageCount() > 0) {
+            if (!mEdgeGlowLeft.isFinished()) {
+                final int restoreCount = canvas.save();
+                Rect display = mViewport;
+                canvas.translate(display.left, display.top);
+                canvas.rotate(270);
+
+                getEdgeVerticalPostion(sTmpIntPoint);
+                canvas.translate(display.top - sTmpIntPoint[1], 0);
+                mEdgeGlowLeft.setSize(sTmpIntPoint[1] - sTmpIntPoint[0], display.width());
+                if (mEdgeGlowLeft.draw(canvas)) {
+                    postInvalidateOnAnimation();
+                }
+                canvas.restoreToCount(restoreCount);
+            }
+            if (!mEdgeGlowRight.isFinished()) {
+                final int restoreCount = canvas.save();
+                Rect display = mViewport;
+                canvas.translate(display.left +
+                        display.width() * (getChildCount() - 1), display.top);
+                canvas.rotate(90);
+
+                getEdgeVerticalPostion(sTmpIntPoint);
+                canvas.translate(sTmpIntPoint[0] - display.top, -display.width());
+                mEdgeGlowRight.setSize(sTmpIntPoint[1] - sTmpIntPoint[0], display.width());
+                if (mEdgeGlowRight.draw(canvas)) {
+                    postInvalidateOnAnimation();
+                }
+                canvas.restoreToCount(restoreCount);
+            }
+        }
+    }
+
+    /**
+     * Returns the top and bottom position for the edge effect.
+     */
+    protected abstract void getEdgeVerticalPostion(int[] pos);
+
+    @Override
     public boolean requestChildRectangleOnScreen(View child, Rect rectangle, boolean immediate) {
         int page = indexToPage(indexOfChild(child));
         if (page != mCurrentPage || !mScroller.isFinished()) {
@@ -1303,10 +1325,6 @@
 
     @Override
     public boolean onInterceptTouchEvent(MotionEvent ev) {
-        if (DISABLE_TOUCH_INTERACTION) {
-            return false;
-        }
-
         /*
          * This method JUST determines whether we want to intercept the motion.
          * If we return true, onTouchEvent will be called and we do the actual
@@ -1383,19 +1401,6 @@
                     }
                 }
 
-                // check if this can be the beginning of a tap on the side of the pages
-                // to scroll the current page
-                if (!DISABLE_TOUCH_SIDE_PAGES) {
-                    if (mTouchState != TOUCH_STATE_PREV_PAGE && mTouchState != TOUCH_STATE_NEXT_PAGE) {
-                        if (getChildCount() > 0) {
-                            if (hitsPreviousPage(x, y)) {
-                                mTouchState = TOUCH_STATE_PREV_PAGE;
-                            } else if (hitsNextPage(x, y)) {
-                                mTouchState = TOUCH_STATE_NEXT_PAGE;
-                            }
-                        }
-                    }
-                }
                 break;
             }
 
@@ -1515,49 +1520,15 @@
         }
     }
 
-    // This curve determines how the effect of scrolling over the limits of the page dimishes
-    // as the user pulls further and further from the bounds
-    private float overScrollInfluenceCurve(float f) {
-        f -= 1.0f;
-        return f * f * f + 1.0f;
-    }
-
-    protected float acceleratedOverFactor(float amount) {
-        int screenSize = getViewportWidth();
-
-        // We want to reach the max over scroll effect when the user has
-        // over scrolled half the size of the screen
-        float f = OVERSCROLL_ACCELERATE_FACTOR * (amount / screenSize);
-
-        if (f == 0) return 0;
-
-        // Clamp this factor, f, to -1 < f < 1
-        if (Math.abs(f) >= 1) {
-            f /= Math.abs(f);
-        }
-        return f;
-    }
-
     protected void dampedOverScroll(float amount) {
         int screenSize = getViewportWidth();
-
         float f = (amount / screenSize);
-
-        if (f == 0) return;
-        f = f / (Math.abs(f)) * (overScrollInfluenceCurve(Math.abs(f)));
-
-        // Clamp this factor, f, to -1 < f < 1
-        if (Math.abs(f) >= 1) {
-            f /= Math.abs(f);
-        }
-
-        int overScrollAmount = (int) Math.round(OVERSCROLL_DAMP_FACTOR * f * screenSize);
-        if (amount < 0) {
-            mOverScrollX = overScrollAmount;
-            super.scrollTo(mOverScrollX, getScrollY());
+        if (f < 0) {
+            mEdgeGlowLeft.onPull(-f);
+        } else if (f > 0) {
+            mEdgeGlowRight.onPull(f);
         } else {
-            mOverScrollX = mMaxScrollX + overScrollAmount;
-            super.scrollTo(mOverScrollX, getScrollY());
+            return;
         }
         invalidate();
     }
@@ -1566,14 +1537,6 @@
         dampedOverScroll(amount);
     }
 
-    protected float maxOverScroll() {
-        // Using the formula in overScroll, assuming that f = 1.0 (which it should generally not
-        // exceed). Used to find out how much extra wallpaper we need for the over scroll effect
-        float f = 1.0f;
-        f = f / (Math.abs(f)) * (overScrollInfluenceCurve(Math.abs(f)));
-        return OVERSCROLL_DAMP_FACTOR * f;
-    }
-
     public void enableFreeScroll() {
         setEnableFreeScroll(true);
     }
@@ -1636,10 +1599,6 @@
 
     @Override
     public boolean onTouchEvent(MotionEvent ev) {
-        if (DISABLE_TOUCH_INTERACTION) {
-            return false;
-        }
-
         super.onTouchEvent(ev);
 
         // Skip touch handling if there are no pages to swipe
@@ -1912,6 +1871,8 @@
         mCancelTap = false;
         mTouchState = TOUCH_STATE_REST;
         mActivePointerId = INVALID_POINTER;
+        mEdgeGlowLeft.onRelease();
+        mEdgeGlowRight.onRelease();
     }
 
     /**
@@ -2020,20 +1981,8 @@
         return minDistanceFromScreenCenterIndex;
     }
 
-    protected boolean isInOverScroll() {
-        return (mOverScrollX > mMaxScrollX || mOverScrollX < 0);
-    }
-
-    protected int getPageSnapDuration() {
-        if (isInOverScroll()) {
-            return OVER_SCROLL_PAGE_SNAP_ANIMATION_DURATION;
-        }
-        return PAGE_SNAP_ANIMATION_DURATION;
-
-    }
-
     protected void snapToDestination() {
-        snapToPage(getPageNearestToCenterOfScreen(), getPageSnapDuration());
+        snapToPage(getPageNearestToCenterOfScreen(), PAGE_SNAP_ANIMATION_DURATION);
     }
 
     private static class ScrollInterpolator implements Interpolator {
@@ -2050,7 +1999,7 @@
     // the screen has to travel, however, we don't want this duration to be effected in a
     // purely linear fashion. Instead, we use this method to moderate the effect that the distance
     // of travel has on the overall snap duration.
-    float distanceInfluenceForSnapDuration(float f) {
+    private float distanceInfluenceForSnapDuration(float f) {
         f -= 0.5f; // center the values about 0.
         f *= 0.3f * Math.PI / 2.0f;
         return (float) Math.sin(f);
@@ -2061,13 +2010,13 @@
         int halfScreenSize = getViewportWidth() / 2;
 
         final int newX = getScrollForPage(whichPage);
-        int delta = newX - mUnboundedScrollX;
+        int delta = newX - getScrollX();
         int duration = 0;
 
-        if (Math.abs(velocity) < mMinFlingVelocity || isInOverScroll()) {
+        if (Math.abs(velocity) < mMinFlingVelocity) {
             // If the velocity is low enough, then treat this more as an automatic page advance
             // as opposed to an apparent physical response to flinging
-            snapToPage(whichPage, getPageSnapDuration());
+            snapToPage(whichPage, PAGE_SNAP_ANIMATION_DURATION);
             return;
         }
 
@@ -2091,11 +2040,11 @@
     }
 
     public void snapToPage(int whichPage) {
-        snapToPage(whichPage, getPageSnapDuration());
+        snapToPage(whichPage, PAGE_SNAP_ANIMATION_DURATION);
     }
 
     protected void snapToPageImmediately(int whichPage) {
-        snapToPage(whichPage, getPageSnapDuration(), true, null);
+        snapToPage(whichPage, PAGE_SNAP_ANIMATION_DURATION, true, null);
     }
 
     protected void snapToPage(int whichPage, int duration) {
@@ -2111,8 +2060,7 @@
         whichPage = validateNewPage(whichPage);
 
         int newX = getScrollForPage(whichPage);
-        final int sX = mUnboundedScrollX;
-        final int delta = newX - sX;
+        final int delta = newX - getScrollX();
         snapToPage(whichPage, delta, duration, immediate, interpolator);
     }
 
@@ -2149,7 +2097,7 @@
             mScroller.setInterpolator(mDefaultInterpolator);
         }
 
-        mScroller.startScroll(mUnboundedScrollX, 0, delta, 0, duration);
+        mScroller.startScroll(getScrollX(), 0, delta, 0, duration);
 
         updatePageIndicator();
 
@@ -2221,7 +2169,7 @@
     }
 
     // Animate the drag view back to the original position
-    void animateDragViewToOriginalPosition() {
+    private void animateDragViewToOriginalPosition() {
         if (mDragView != null) {
             AnimatorSet anim = new AnimatorSet();
             anim.setDuration(REORDERING_DROP_REPOSITION_DURATION);
diff --git a/src/com/android/launcher3/Workspace.java b/src/com/android/launcher3/Workspace.java
index 76f872b..fb0a54d 100644
--- a/src/com/android/launcher3/Workspace.java
+++ b/src/com/android/launcher3/Workspace.java
@@ -197,7 +197,6 @@
     private static final Rect sTempRect = new Rect();
     private final int[] mTempXY = new int[2];
     private int[] mTempVisiblePagesRange = new int[2];
-    private boolean mOverscrollEffectSet;
     public static final int DRAG_BITMAP_PADDING = 2;
     private boolean mWorkspaceFadeInAdjacentScreens;
 
@@ -257,8 +256,6 @@
     private float mCurrentScale;
     private float mTransitionProgress;
 
-    float mOverScrollEffect = 0f;
-
     @Thunk Runnable mDeferredAction;
     private boolean mDeferDropAfterUninstall;
     private boolean mUninstallSuccessful;
@@ -443,6 +440,8 @@
 
         // Set the wallpaper dimensions when Launcher starts up
         setWallpaperDimension();
+
+        setEdgeGlowColor(getResources().getColor(R.color.workspace_edge_effect_color));
     }
 
     private void setupLayoutTransition() {
@@ -1260,9 +1259,6 @@
             mLauncherOverlay.onScrollChange(progress, mIsRtl);
         } else if (shouldOverScroll) {
             dampedOverScroll(amount);
-            mOverScrollEffect = acceleratedOverFactor(amount);
-        } else {
-            mOverScrollEffect = 0;
         }
 
         if (shouldZeroOverlay) {
@@ -1271,6 +1267,13 @@
     }
 
     @Override
+    protected void getEdgeVerticalPostion(int[] pos) {
+        View child = getChildAt(getPageCount() - 1);
+        pos[0] = child.getTop();
+        pos[1] = child.getBottom();
+    }
+
+    @Override
     protected void notifyPageSwitchListener() {
         super.notifyPageSwitchListener();
 
@@ -1540,11 +1543,9 @@
     }
 
     private void updatePageAlphaValues(int screenCenter) {
-        boolean isInOverscroll = mOverScrollX < 0 || mOverScrollX > mMaxScrollX;
         if (mWorkspaceFadeInAdjacentScreens &&
                 !workspaceInModalState() &&
-                !mIsSwitchingState &&
-                !isInOverscroll) {
+                !mIsSwitchingState) {
             for (int i = numCustomPages(); i < getChildCount(); i++) {
                 CellLayout child = (CellLayout) getChildAt(i);
                 if (child != null) {
@@ -1654,34 +1655,9 @@
 
     @Override
     protected void screenScrolled(int screenCenter) {
-        super.screenScrolled(screenCenter);
-
         updatePageAlphaValues(screenCenter);
         updateStateForCustomContent(screenCenter);
         enableHwLayersOnVisiblePages();
-
-        boolean shouldOverScroll = mOverScrollX < 0 || mOverScrollX > mMaxScrollX;
-
-        if (shouldOverScroll) {
-            int index = 0;
-            final int lowerIndex = 0;
-            final int upperIndex = getChildCount() - 1;
-
-            final boolean isLeftPage = mOverScrollX < 0;
-            index = (!mIsRtl && isLeftPage) || (mIsRtl && !isLeftPage) ? lowerIndex : upperIndex;
-
-            CellLayout cl = (CellLayout) getChildAt(index);
-            float effect = Math.abs(mOverScrollEffect);
-            cl.setOverScrollAmount(Math.abs(effect), isLeftPage);
-
-            mOverscrollEffectSet = true;
-        } else {
-            if (mOverscrollEffectSet && getChildCount() > 0) {
-                mOverscrollEffectSet = false;
-                ((CellLayout) getChildAt(0)).setOverScrollAmount(0, false);
-                ((CellLayout) getChildAt(getChildCount() - 1)).setOverScrollAmount(0, false);
-            }
-        }
     }
 
     protected void onAttachedToWindow() {
diff --git a/src/com/android/launcher3/util/LauncherEdgeEffect.java b/src/com/android/launcher3/util/LauncherEdgeEffect.java
new file mode 100644
index 0000000..3e3b255
--- /dev/null
+++ b/src/com/android/launcher3/util/LauncherEdgeEffect.java
@@ -0,0 +1,365 @@
+/*
+ * Copyright (C) 2015 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.launcher3.util;
+
+import android.graphics.Canvas;
+import android.graphics.Paint;
+import android.graphics.Rect;
+import android.view.animation.AnimationUtils;
+import android.view.animation.DecelerateInterpolator;
+import android.view.animation.Interpolator;
+
+/**
+ * This class differs from the framework {@link android.widget.EdgeEffect}:
+ *   1) It does not use PorterDuffXfermode
+ *   2) The width to radius factor is smaller (0.5 instead of 0.75)
+ */
+public class LauncherEdgeEffect {
+
+    // Time it will take the effect to fully recede in ms
+    private static final int RECEDE_TIME = 600;
+
+    // Time it will take before a pulled glow begins receding in ms
+    private static final int PULL_TIME = 167;
+
+    // Time it will take in ms for a pulled glow to decay to partial strength before release
+    private static final int PULL_DECAY_TIME = 2000;
+
+    private static final float MAX_ALPHA = 0.5f;
+
+    private static final float MAX_GLOW_SCALE = 2.f;
+
+    private static final float PULL_GLOW_BEGIN = 0.f;
+
+    // Minimum velocity that will be absorbed
+    private static final int MIN_VELOCITY = 100;
+    // Maximum velocity, clamps at this value
+    private static final int MAX_VELOCITY = 10000;
+
+    private static final float EPSILON = 0.001f;
+
+    private static final double ANGLE = Math.PI / 6;
+    private static final float SIN = (float) Math.sin(ANGLE);
+    private static final float COS = (float) Math.cos(ANGLE);
+
+    private float mGlowAlpha;
+    private float mGlowScaleY;
+
+    private float mGlowAlphaStart;
+    private float mGlowAlphaFinish;
+    private float mGlowScaleYStart;
+    private float mGlowScaleYFinish;
+
+    private long mStartTime;
+    private float mDuration;
+
+    private final Interpolator mInterpolator;
+
+    private static final int STATE_IDLE = 0;
+    private static final int STATE_PULL = 1;
+    private static final int STATE_ABSORB = 2;
+    private static final int STATE_RECEDE = 3;
+    private static final int STATE_PULL_DECAY = 4;
+
+    private static final float PULL_DISTANCE_ALPHA_GLOW_FACTOR = 0.8f;
+
+    private static final int VELOCITY_GLOW_FACTOR = 6;
+
+    private int mState = STATE_IDLE;
+
+    private float mPullDistance;
+
+    private final Rect mBounds = new Rect();
+    private final Paint mPaint = new Paint();
+    private float mRadius;
+    private float mBaseGlowScale;
+    private float mDisplacement = 0.5f;
+    private float mTargetDisplacement = 0.5f;
+
+    /**
+     * Construct a new EdgeEffect with a theme appropriate for the provided context.
+     */
+    public LauncherEdgeEffect() {
+        mPaint.setAntiAlias(true);
+        mPaint.setStyle(Paint.Style.FILL);
+        mInterpolator = new DecelerateInterpolator();
+    }
+
+    /**
+     * Set the size of this edge effect in pixels.
+     *
+     * @param width Effect width in pixels
+     * @param height Effect height in pixels
+     */
+    public void setSize(int width, int height) {
+        final float r = width * 0.5f / SIN;
+        final float y = COS * r;
+        final float h = r - y;
+        final float or = height * 0.75f / SIN;
+        final float oy = COS * or;
+        final float oh = or - oy;
+
+        mRadius = r;
+        mBaseGlowScale = h > 0 ? Math.min(oh / h, 1.f) : 1.f;
+
+        mBounds.set(mBounds.left, mBounds.top, width, (int) Math.min(height, h));
+    }
+
+    /**
+     * Reports if this EdgeEffect's animation is finished. If this method returns false
+     * after a call to {@link #draw(Canvas)} the host widget should schedule another
+     * drawing pass to continue the animation.
+     *
+     * @return true if animation is finished, false if drawing should continue on the next frame.
+     */
+    public boolean isFinished() {
+        return mState == STATE_IDLE;
+    }
+
+    /**
+     * Immediately finish the current animation.
+     * After this call {@link #isFinished()} will return true.
+     */
+    public void finish() {
+        mState = STATE_IDLE;
+    }
+
+    /**
+     * A view should call this when content is pulled away from an edge by the user.
+     * This will update the state of the current visual effect and its associated animation.
+     * The host view should always {@link android.view.View#invalidate()} after this
+     * and draw the results accordingly.
+     *
+     * <p>Views using EdgeEffect should favor {@link #onPull(float, float)} when the displacement
+     * of the pull point is known.</p>
+     *
+     * @param deltaDistance Change in distance since the last call. Values may be 0 (no change) to
+     *                      1.f (full length of the view) or negative values to express change
+     *                      back toward the edge reached to initiate the effect.
+     */
+    public void onPull(float deltaDistance) {
+        onPull(deltaDistance, 0.5f);
+    }
+
+    /**
+     * A view should call this when content is pulled away from an edge by the user.
+     * This will update the state of the current visual effect and its associated animation.
+     * The host view should always {@link android.view.View#invalidate()} after this
+     * and draw the results accordingly.
+     *
+     * @param deltaDistance Change in distance since the last call. Values may be 0 (no change) to
+     *                      1.f (full length of the view) or negative values to express change
+     *                      back toward the edge reached to initiate the effect.
+     * @param displacement The displacement from the starting side of the effect of the point
+     *                     initiating the pull. In the case of touch this is the finger position.
+     *                     Values may be from 0-1.
+     */
+    public void onPull(float deltaDistance, float displacement) {
+        final long now = AnimationUtils.currentAnimationTimeMillis();
+        mTargetDisplacement = displacement;
+        if (mState == STATE_PULL_DECAY && now - mStartTime < mDuration) {
+            return;
+        }
+        if (mState != STATE_PULL) {
+            mGlowScaleY = Math.max(PULL_GLOW_BEGIN, mGlowScaleY);
+        }
+        mState = STATE_PULL;
+
+        mStartTime = now;
+        mDuration = PULL_TIME;
+
+        mPullDistance += deltaDistance;
+
+        final float absdd = Math.abs(deltaDistance);
+        mGlowAlpha = mGlowAlphaStart = Math.min(MAX_ALPHA,
+                mGlowAlpha + (absdd * PULL_DISTANCE_ALPHA_GLOW_FACTOR));
+
+        if (mPullDistance == 0) {
+            mGlowScaleY = mGlowScaleYStart = 0;
+        } else {
+            final float scale = (float) (Math.max(0, 1 - 1 /
+                    Math.sqrt(Math.abs(mPullDistance) * mBounds.height()) - 0.3d) / 0.7d);
+
+            mGlowScaleY = mGlowScaleYStart = scale;
+        }
+
+        mGlowAlphaFinish = mGlowAlpha;
+        mGlowScaleYFinish = mGlowScaleY;
+    }
+
+    /**
+     * Call when the object is released after being pulled.
+     * This will begin the "decay" phase of the effect. After calling this method
+     * the host view should {@link android.view.View#invalidate()} and thereby
+     * draw the results accordingly.
+     */
+    public void onRelease() {
+        mPullDistance = 0;
+
+        if (mState != STATE_PULL && mState != STATE_PULL_DECAY) {
+            return;
+        }
+
+        mState = STATE_RECEDE;
+        mGlowAlphaStart = mGlowAlpha;
+        mGlowScaleYStart = mGlowScaleY;
+
+        mGlowAlphaFinish = 0.f;
+        mGlowScaleYFinish = 0.f;
+
+        mStartTime = AnimationUtils.currentAnimationTimeMillis();
+        mDuration = RECEDE_TIME;
+    }
+
+    /**
+     * Call when the effect absorbs an impact at the given velocity.
+     * Used when a fling reaches the scroll boundary.
+     *
+     * <p>When using a {@link android.widget.Scroller} or {@link android.widget.OverScroller},
+     * the method <code>getCurrVelocity</code> will provide a reasonable approximation
+     * to use here.</p>
+     *
+     * @param velocity Velocity at impact in pixels per second.
+     */
+    public void onAbsorb(int velocity) {
+        mState = STATE_ABSORB;
+        velocity = Math.min(Math.max(MIN_VELOCITY, Math.abs(velocity)), MAX_VELOCITY);
+
+        mStartTime = AnimationUtils.currentAnimationTimeMillis();
+        mDuration = 0.15f + (velocity * 0.02f);
+
+        // The glow depends more on the velocity, and therefore starts out
+        // nearly invisible.
+        mGlowAlphaStart = 0.3f;
+        mGlowScaleYStart = Math.max(mGlowScaleY, 0.f);
+
+
+        // Growth for the size of the glow should be quadratic to properly
+        // respond
+        // to a user's scrolling speed. The faster the scrolling speed, the more
+        // intense the effect should be for both the size and the saturation.
+        mGlowScaleYFinish = Math.min(0.025f + (velocity * (velocity / 100) * 0.00015f) / 2, 1.f);
+        // Alpha should change for the glow as well as size.
+        mGlowAlphaFinish = Math.max(
+                mGlowAlphaStart, Math.min(velocity * VELOCITY_GLOW_FACTOR * .00001f, MAX_ALPHA));
+        mTargetDisplacement = 0.5f;
+    }
+
+    /**
+     * Set the color of this edge effect in argb.
+     *
+     * @param color Color in argb
+     */
+    public void setColor(int color) {
+        mPaint.setColor(color);
+    }
+
+    /**
+     * Return the color of this edge effect in argb.
+     * @return The color of this edge effect in argb
+     */
+    public int getColor() {
+        return mPaint.getColor();
+    }
+
+    /**
+     * Draw into the provided canvas. Assumes that the canvas has been rotated
+     * accordingly and the size has been set. The effect will be drawn the full
+     * width of X=0 to X=width, beginning from Y=0 and extending to some factor <
+     * 1.f of height.
+     *
+     * @param canvas Canvas to draw into
+     * @return true if drawing should continue beyond this frame to continue the
+     *         animation
+     */
+    public boolean draw(Canvas canvas) {
+        update();
+
+        final float centerX = mBounds.centerX();
+        final float centerY = mBounds.height() - mRadius;
+
+        canvas.scale(1.f, Math.min(mGlowScaleY, 1.f) * mBaseGlowScale, centerX, 0);
+
+        final float displacement = Math.max(0, Math.min(mDisplacement, 1.f)) - 0.5f;
+        float translateX = mBounds.width() * displacement / 2;
+        mPaint.setAlpha((int) (0xff * mGlowAlpha));
+        canvas.drawCircle(centerX + translateX, centerY, mRadius, mPaint);
+
+        boolean oneLastFrame = false;
+        if (mState == STATE_RECEDE && mGlowScaleY == 0) {
+            mState = STATE_IDLE;
+            oneLastFrame = true;
+        }
+
+        return mState != STATE_IDLE || oneLastFrame;
+    }
+
+    /**
+     * Return the maximum height that the edge effect will be drawn at given the original
+     * {@link #setSize(int, int) input size}.
+     * @return The maximum height of the edge effect
+     */
+    public int getMaxHeight() {
+        return (int) (mBounds.height() * MAX_GLOW_SCALE + 0.5f);
+    }
+
+    private void update() {
+        final long time = AnimationUtils.currentAnimationTimeMillis();
+        final float t = Math.min((time - mStartTime) / mDuration, 1.f);
+
+        final float interp = mInterpolator.getInterpolation(t);
+
+        mGlowAlpha = mGlowAlphaStart + (mGlowAlphaFinish - mGlowAlphaStart) * interp;
+        mGlowScaleY = mGlowScaleYStart + (mGlowScaleYFinish - mGlowScaleYStart) * interp;
+        mDisplacement = (mDisplacement + mTargetDisplacement) / 2;
+
+        if (t >= 1.f - EPSILON) {
+            switch (mState) {
+                case STATE_ABSORB:
+                    mState = STATE_RECEDE;
+                    mStartTime = AnimationUtils.currentAnimationTimeMillis();
+                    mDuration = RECEDE_TIME;
+
+                    mGlowAlphaStart = mGlowAlpha;
+                    mGlowScaleYStart = mGlowScaleY;
+
+                    // After absorb, the glow should fade to nothing.
+                    mGlowAlphaFinish = 0.f;
+                    mGlowScaleYFinish = 0.f;
+                    break;
+                case STATE_PULL:
+                    mState = STATE_PULL_DECAY;
+                    mStartTime = AnimationUtils.currentAnimationTimeMillis();
+                    mDuration = PULL_DECAY_TIME;
+
+                    mGlowAlphaStart = mGlowAlpha;
+                    mGlowScaleYStart = mGlowScaleY;
+
+                    // After pull, the glow should fade to nothing.
+                    mGlowAlphaFinish = 0.f;
+                    mGlowScaleYFinish = 0.f;
+                    break;
+                case STATE_PULL_DECAY:
+                    mState = STATE_RECEDE;
+                    break;
+                case STATE_RECEDE:
+                    mState = STATE_IDLE;
+                    break;
+            }
+        }
+    }
+}
diff --git a/src/com/android/launcher3/util/ManagedProfileHeuristic.java b/src/com/android/launcher3/util/ManagedProfileHeuristic.java
index 94dc47f..b37f447 100644
--- a/src/com/android/launcher3/util/ManagedProfileHeuristic.java
+++ b/src/com/android/launcher3/util/ManagedProfileHeuristic.java
@@ -1,10 +1,28 @@
+/*
+ * Copyright (C) 2015 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.launcher3.util;
 
+import android.annotation.TargetApi;
 import android.content.Context;
 import android.content.SharedPreferences;
 import android.content.pm.PackageInfo;
 import android.content.pm.PackageManager;
 import android.content.pm.PackageManager.NameNotFoundException;
+import android.os.Build;
 import android.util.Log;
 
 import com.android.launcher3.FolderInfo;
@@ -32,6 +50,7 @@
  * Handles addition of app shortcuts for managed profiles.
  * Methods of class should only be called on {@link LauncherModel#sWorkerThread}.
  */
+@TargetApi(Build.VERSION_CODES.LOLLIPOP)
 public class ManagedProfileHeuristic {
 
     private static final String TAG = "ManagedProfileHeuristic";