am 7b7d5e43: Merge "New, inkier EdgeEffect visual style"

* commit '7b7d5e4347ca9f7cddbec07cea752756f8c1ac47':
  New, inkier EdgeEffect visual style
diff --git a/api/current.txt b/api/current.txt
index 49f84d3..ce0744d 100644
--- a/api/current.txt
+++ b/api/current.txt
@@ -35175,9 +35175,11 @@
     ctor public EdgeEffect(android.content.Context);
     method public boolean draw(android.graphics.Canvas);
     method public void finish();
+    method public int getMaxHeight();
     method public boolean isFinished();
     method public void onAbsorb(int);
     method public void onPull(float);
+    method public void onPull(float, float);
     method public void onRelease();
     method public void setSize(int, int);
   }
diff --git a/core/java/android/widget/AbsListView.java b/core/java/android/widget/AbsListView.java
index f4cd5fc..565ea13 100644
--- a/core/java/android/widget/AbsListView.java
+++ b/core/java/android/widget/AbsListView.java
@@ -3267,7 +3267,7 @@
         }
     }
 
-    private boolean startScrollIfNeeded(int y, MotionEvent vtev) {
+    private boolean startScrollIfNeeded(int x, int y, MotionEvent vtev) {
         // Check if we have moved far enough that it looks more like a
         // scroll than a tap
         final int deltaY = y - mMotionY;
@@ -3296,14 +3296,14 @@
             if (parent != null) {
                 parent.requestDisallowInterceptTouchEvent(true);
             }
-            scrollIfNeeded(y, vtev);
+            scrollIfNeeded(x, y, vtev);
             return true;
         }
 
         return false;
     }
 
-    private void scrollIfNeeded(int y, MotionEvent vtev) {
+    private void scrollIfNeeded(int x, int y, MotionEvent vtev) {
         int rawDeltaY = y - mMotionY;
         if (dispatchNestedPreScroll(0, rawDeltaY, mScrollConsumed, mScrollOffset)) {
             rawDeltaY -= mScrollConsumed[1];
@@ -3384,33 +3384,39 @@
                                 vtev.offsetLocation(0, mScrollOffset[1]);
                             }
                         } else {
-                            overScrollBy(0, overscroll, 0, mScrollY, 0, 0,
-                                    0, mOverscrollDistance, true);
-                            if (Math.abs(mOverscrollDistance) == Math.abs(mScrollY)) {
-                                // Don't allow overfling if we're at the edge.
-                                if (mVelocityTracker != null) {
-                                    mVelocityTracker.clear();
-                                }
+                            final boolean atOverscrollEdge = overScrollBy(0, overscroll,
+                                    0, mScrollY, 0, 0, 0, mOverscrollDistance, true);
+
+                            if (atOverscrollEdge && mVelocityTracker != null) {
+                                // Don't allow overfling if we're at the edge
+                                mVelocityTracker.clear();
                             }
 
                             final int overscrollMode = getOverScrollMode();
                             if (overscrollMode == OVER_SCROLL_ALWAYS ||
                                     (overscrollMode == OVER_SCROLL_IF_CONTENT_SCROLLS &&
                                             !contentFits())) {
-                                mDirection = 0; // Reset when entering overscroll.
-                                mTouchMode = TOUCH_MODE_OVERSCROLL;
-                                if (deltaY > 0) {
-                                    mEdgeGlowTop.onPull((float) overscroll / getHeight());
+                                if (!atOverscrollEdge) {
+                                    mDirection = 0; // Reset when entering overscroll.
+                                    mTouchMode = TOUCH_MODE_OVERSCROLL;
+                                }
+                                if (incrementalDeltaY > 0) {
+                                    mEdgeGlowTop.onPull((float) overscroll / getHeight(),
+                                            (float) x / getWidth());
                                     if (!mEdgeGlowBottom.isFinished()) {
                                         mEdgeGlowBottom.onRelease();
                                     }
-                                    invalidate(mEdgeGlowTop.getBounds(false));
-                                } else if (deltaY < 0) {
-                                    mEdgeGlowBottom.onPull((float) overscroll / getHeight());
+                                    invalidate(0, 0, getWidth(),
+                                            mEdgeGlowTop.getMaxHeight() + getPaddingTop());
+                                } else if (incrementalDeltaY < 0) {
+                                    mEdgeGlowBottom.onPull((float) overscroll / getHeight(),
+                                            1.f - (float) x / getWidth());
                                     if (!mEdgeGlowTop.isFinished()) {
                                         mEdgeGlowTop.onRelease();
                                     }
-                                    invalidate(mEdgeGlowBottom.getBounds(true));
+                                    invalidate(0, getHeight() - getPaddingBottom() -
+                                            mEdgeGlowBottom.getMaxHeight(), getWidth(),
+                                            getHeight());
                                 }
                             }
                         }
@@ -3445,17 +3451,22 @@
                             (overscrollMode == OVER_SCROLL_IF_CONTENT_SCROLLS &&
                                     !contentFits())) {
                         if (rawDeltaY > 0) {
-                            mEdgeGlowTop.onPull((float) overScrollDistance / getHeight());
+                            mEdgeGlowTop.onPull((float) overScrollDistance / getHeight(),
+                                    (float) x / getWidth());
                             if (!mEdgeGlowBottom.isFinished()) {
                                 mEdgeGlowBottom.onRelease();
                             }
-                            invalidate(mEdgeGlowTop.getBounds(false));
+                            invalidate(0, 0, getWidth(),
+                                    mEdgeGlowTop.getMaxHeight() + getPaddingTop());
                         } else if (rawDeltaY < 0) {
-                            mEdgeGlowBottom.onPull((float) overScrollDistance / getHeight());
+                            mEdgeGlowBottom.onPull((float) overScrollDistance / getHeight(),
+                                    1.f - (float) x / getWidth());
                             if (!mEdgeGlowTop.isFinished()) {
                                 mEdgeGlowTop.onRelease();
                             }
-                            invalidate(mEdgeGlowBottom.getBounds(true));
+                            invalidate(0, getHeight() - getPaddingBottom() -
+                                    mEdgeGlowBottom.getMaxHeight(), getWidth(),
+                                    getHeight());
                         }
                     }
                 }
@@ -3703,7 +3714,7 @@
             case TOUCH_MODE_DONE_WAITING:
                 // Check if we have moved far enough that it looks more like a
                 // scroll than a tap. If so, we'll enter scrolling mode.
-                if (startScrollIfNeeded(y, vtev)) {
+                if (startScrollIfNeeded((int) ev.getX(pointerIndex), y, vtev)) {
                     break;
                 }
                 // Otherwise, check containment within list bounds. If we're
@@ -3723,7 +3734,7 @@
                 break;
             case TOUCH_MODE_SCROLL:
             case TOUCH_MODE_OVERSCROLL:
-                scrollIfNeeded(y, vtev);
+                scrollIfNeeded((int) ev.getX(pointerIndex), y, vtev);
                 break;
         }
     }
@@ -4022,8 +4033,8 @@
                 canvas.translate(leftPadding, edgeY);
                 mEdgeGlowTop.setSize(width, getHeight());
                 if (mEdgeGlowTop.draw(canvas)) {
-                    mEdgeGlowTop.setPosition(leftPadding, edgeY);
-                    invalidate(mEdgeGlowTop.getBounds(false));
+                    invalidate(0, 0, getWidth(),
+                            mEdgeGlowTop.getMaxHeight() + getPaddingTop());
                 }
                 canvas.restoreToCount(restoreCount);
             }
@@ -4040,9 +4051,9 @@
                 canvas.rotate(180, width, 0);
                 mEdgeGlowBottom.setSize(width, height);
                 if (mEdgeGlowBottom.draw(canvas)) {
-                    // Account for the rotation
-                    mEdgeGlowBottom.setPosition(edgeX + width, edgeY);
-                    invalidate(mEdgeGlowBottom.getBounds(true));
+                    invalidate(0, getHeight() - getPaddingBottom() -
+                            mEdgeGlowBottom.getMaxHeight(), getWidth(),
+                            getHeight());
                 }
                 canvas.restoreToCount(restoreCount);
             }
@@ -4161,7 +4172,7 @@
                 final int y = (int) ev.getY(pointerIndex);
                 initVelocityTrackerIfNotExists();
                 mVelocityTracker.addMovement(ev);
-                if (startScrollIfNeeded(y, null)) {
+                if (startScrollIfNeeded((int) ev.getX(pointerIndex), y, null)) {
                     return true;
                 }
                 break;
diff --git a/core/java/android/widget/EdgeEffect.java b/core/java/android/widget/EdgeEffect.java
index fa37443..83fbe8f 100644
--- a/core/java/android/widget/EdgeEffect.java
+++ b/core/java/android/widget/EdgeEffect.java
@@ -16,7 +16,14 @@
 
 package android.widget;
 
+import android.content.res.TypedArray;
+import android.graphics.Paint;
+import android.graphics.PorterDuff;
+import android.graphics.PorterDuffXfermode;
 import android.graphics.Rect;
+import android.graphics.RectF;
+import android.graphics.Xfermode;
+import android.util.Log;
 import com.android.internal.R;
 
 import android.content.Context;
@@ -59,12 +66,10 @@
     private static final int PULL_DECAY_TIME = 1000;
 
     private static final float MAX_ALPHA = 1.f;
-    private static final float HELD_EDGE_SCALE_Y = 0.5f;
 
-    private static final float MAX_GLOW_HEIGHT = 4.f;
+    private static final float MAX_GLOW_HEIGHT = 1.5f;
 
-    private static final float PULL_GLOW_BEGIN = 1.f;
-    private static final float PULL_EDGE_BEGIN = 0.6f;
+    private static final float PULL_GLOW_BEGIN = 0.f;
 
     // Minimum velocity that will be absorbed
     private static final int MIN_VELOCITY = 100;
@@ -73,24 +78,11 @@
 
     private static final float EPSILON = 0.001f;
 
-    private final Drawable mEdge;
-    private final Drawable mGlow;
-    private int mWidth;
-    private int mHeight;
-    private int mX;
-    private int mY;
-    private static final int MIN_WIDTH = 300;
-    private final int mMinWidth;
+    private static final float SIN_45 = (float) Math.sin(Math.PI / 4);
 
-    private float mEdgeAlpha;
-    private float mEdgeScaleY;
     private float mGlowAlpha;
     private float mGlowScaleY;
 
-    private float mEdgeAlphaStart;
-    private float mEdgeAlphaFinish;
-    private float mEdgeScaleYStart;
-    private float mEdgeScaleYFinish;
     private float mGlowAlphaStart;
     private float mGlowAlphaFinish;
     private float mGlowScaleYStart;
@@ -107,16 +99,11 @@
     private static final int STATE_RECEDE = 3;
     private static final int STATE_PULL_DECAY = 4;
 
-    // How much dragging should effect the height of the edge image.
-    // Number determined by user testing.
-    private static final int PULL_DISTANCE_EDGE_FACTOR = 7;
-
     // How much dragging should effect the height of the glow image.
     // Number determined by user testing.
     private static final int PULL_DISTANCE_GLOW_FACTOR = 7;
     private static final float PULL_DISTANCE_ALPHA_GLOW_FACTOR = 1.1f;
 
-    private static final int VELOCITY_EDGE_FACTOR = 8;
     private static final int VELOCITY_GLOW_FACTOR = 12;
 
     private int mState = STATE_IDLE;
@@ -124,30 +111,26 @@
     private float mPullDistance;
     
     private final Rect mBounds = new Rect();
-
-    private final int mEdgeHeight;
-    private final int mGlowHeight;
-    private final int mGlowWidth;
-    private final int mMaxEffectHeight;
+    private final RectF mArcRect = new RectF();
+    private final Paint mPaint = new Paint();
+    private float mRadius;
+    private float mDisplacement = 0.5f;
+    private float mTargetDisplacement = 0.5f;
 
     /**
      * Construct a new EdgeEffect with a theme appropriate for the provided context.
      * @param context Context used to provide theming and resource information for the EdgeEffect
      */
     public EdgeEffect(Context context) {
-        final Resources res = context.getResources();
-        mEdge = context.getDrawable(R.drawable.overscroll_edge);
-        mGlow = context.getDrawable(R.drawable.overscroll_glow);
-
-        mEdgeHeight = mEdge.getIntrinsicHeight();
-        mGlowHeight = mGlow.getIntrinsicHeight();
-        mGlowWidth = mGlow.getIntrinsicWidth();
-
-        mMaxEffectHeight = (int) (Math.min(
-                mGlowHeight * MAX_GLOW_HEIGHT * mGlowHeight / mGlowWidth * 0.6f,
-                mGlowHeight * MAX_GLOW_HEIGHT) + 0.5f);
-
-        mMinWidth = (int) (res.getDisplayMetrics().density * MIN_WIDTH + 0.5f);
+        mPaint.setAntiAlias(true);
+        final TypedArray a = context.obtainStyledAttributes(
+                com.android.internal.R.styleable.EdgeEffect);
+        final int themeColor = a.getColor(
+                com.android.internal.R.styleable.EdgeEffect_colorPrimaryLight, 0xff666666);
+        a.recycle();
+        mPaint.setColor((themeColor & 0xffffff) | 0x66000000);
+        mPaint.setStyle(Paint.Style.FILL);
+        mPaint.setXfermode(new PorterDuffXfermode(PorterDuff.Mode.SRC_ATOP));
         mInterpolator = new DecelerateInterpolator();
     }
 
@@ -158,20 +141,12 @@
      * @param height Effect height in pixels
      */
     public void setSize(int width, int height) {
-        mWidth = width;
-        mHeight = height;
-    }
+        final float r = width * 0.5f / SIN_45;
+        final float y = SIN_45 * r;
+        final float h = r - y;
+        mRadius = r;
 
-    /**
-     * Set the position of this edge effect in pixels. This position is
-     * only used by {@link #getBounds(boolean)}.
-     * 
-     * @param x The position of the edge effect on the X axis
-     * @param y The position of the edge effect on the Y axis
-     */
-    void setPosition(int x, int y) {
-        mX = x;
-        mY = y;
+        mBounds.set(mBounds.left, mBounds.top, width, (int) Math.min(height, h));
     }
 
     /**
@@ -199,17 +174,38 @@
      * 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 = PULL_GLOW_BEGIN;
+            mGlowScaleY = Math.max(PULL_GLOW_BEGIN, mGlowScaleY);
         }
         mState = STATE_PULL;
 
@@ -217,15 +213,10 @@
         mDuration = PULL_TIME;
 
         mPullDistance += deltaDistance;
-        float distance = Math.abs(mPullDistance);
-
-        mEdgeAlpha = mEdgeAlphaStart = Math.max(PULL_EDGE_BEGIN, Math.min(distance, MAX_ALPHA));
-        mEdgeScaleY = mEdgeScaleYStart = Math.max(
-                HELD_EDGE_SCALE_Y, Math.min(distance * PULL_DISTANCE_EDGE_FACTOR, 1.f));
 
         mGlowAlpha = mGlowAlphaStart = Math.min(MAX_ALPHA,
                 mGlowAlpha +
-                (Math.abs(deltaDistance) * PULL_DISTANCE_ALPHA_GLOW_FACTOR));
+                        (Math.abs(deltaDistance) * PULL_DISTANCE_ALPHA_GLOW_FACTOR));
 
         float glowChange = Math.abs(deltaDistance);
         if (deltaDistance > 0 && mPullDistance < 0) {
@@ -239,8 +230,6 @@
         mGlowScaleY = mGlowScaleYStart = Math.min(MAX_GLOW_HEIGHT, Math.max(
                 0, mGlowScaleY + glowChange * PULL_DISTANCE_GLOW_FACTOR));
 
-        mEdgeAlphaFinish = mEdgeAlpha;
-        mEdgeScaleYFinish = mEdgeScaleY;
         mGlowAlphaFinish = mGlowAlpha;
         mGlowScaleYFinish = mGlowScaleY;
     }
@@ -259,13 +248,9 @@
         }
 
         mState = STATE_RECEDE;
-        mEdgeAlphaStart = mEdgeAlpha;
-        mEdgeScaleYStart = mEdgeScaleY;
         mGlowAlphaStart = mGlowAlpha;
         mGlowScaleYStart = mGlowScaleY;
 
-        mEdgeAlphaFinish = 0.f;
-        mEdgeScaleYFinish = 0.f;
         mGlowAlphaFinish = 0.f;
         mGlowScaleYFinish = 0.f;
 
@@ -290,30 +275,21 @@
         mStartTime = AnimationUtils.currentAnimationTimeMillis();
         mDuration = 0.15f + (velocity * 0.02f);
 
-        // The edge should always be at least partially visible, regardless
-        // of velocity.
-        mEdgeAlphaStart = 0.f;
-        mEdgeScaleY = mEdgeScaleYStart = 0.f;
         // The glow depends more on the velocity, and therefore starts out
         // nearly invisible.
         mGlowAlphaStart = 0.3f;
-        mGlowScaleYStart = 0.f;
+        mGlowScaleYStart = Math.max(mGlowScaleY, 0.f);
 
-        // Factor the velocity by 8. Testing on device shows this works best to
-        // reflect the strength of the user's scrolling.
-        mEdgeAlphaFinish = Math.max(0, Math.min(velocity * VELOCITY_EDGE_FACTOR, 1));
-        // Edge should never get larger than the size of its asset.
-        mEdgeScaleYFinish = Math.max(
-                HELD_EDGE_SCALE_Y, Math.min(velocity * VELOCITY_EDGE_FACTOR, 1.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), 1.75f);
+        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;
     }
 
 
@@ -330,52 +306,42 @@
     public boolean draw(Canvas canvas) {
         update();
 
-        mGlow.setAlpha((int) (Math.max(0, Math.min(mGlowAlpha, 1)) * 255));
+        final int count = canvas.save();
 
-        int glowBottom = (int) Math.min(
-                mGlowHeight * mGlowScaleY * mGlowHeight / mGlowWidth * 0.6f,
-                mGlowHeight * MAX_GLOW_HEIGHT);
-        if (mWidth < mMinWidth) {
-            // Center the glow and clip it.
-            int glowLeft = (mWidth - mMinWidth)/2;
-            mGlow.setBounds(glowLeft, 0, mWidth - glowLeft, glowBottom);
-        } else {
-            // Stretch the glow to fit.
-            mGlow.setBounds(0, 0, mWidth, glowBottom);
+        final float y = mBounds.height();
+        final float centerY = y - mRadius;
+        final float centerX = mBounds.centerX();
+        mArcRect.set(centerX - mRadius, centerY - mRadius, centerX + mRadius, centerY + mRadius);
+        canvas.scale(1.f, Math.min(mGlowScaleY, 1.f), centerX, 0);
+
+        final float displacement = Math.max(0, Math.min(mDisplacement, 1.f)) - 0.5f;
+        float translateX = mBounds.width() * displacement;
+        float translateY = 0;
+        if (mGlowScaleY > 1.f) {
+            translateY = (mGlowScaleY - 1.f) * mBounds.height();
         }
+        canvas.clipRect(Float.MIN_VALUE, mBounds.top,
+                Float.MAX_VALUE, Float.MAX_VALUE);
+        canvas.translate(translateX, translateY);
+        canvas.drawArc(mArcRect, 0, 180, true, mPaint);
+        canvas.restoreToCount(count);
 
-        mGlow.draw(canvas);
-
-        mEdge.setAlpha((int) (Math.max(0, Math.min(mEdgeAlpha, 1)) * 255));
-
-        int edgeBottom = (int) (mEdgeHeight * mEdgeScaleY);
-        if (mWidth < mMinWidth) {
-            // Center the edge and clip it.
-            int edgeLeft = (mWidth - mMinWidth)/2;
-            mEdge.setBounds(edgeLeft, 0, mWidth - edgeLeft, edgeBottom);
-        } else {
-            // Stretch the edge to fit.
-            mEdge.setBounds(0, 0, mWidth, edgeBottom);
-        }
-        mEdge.draw(canvas);
-
-        if (mState == STATE_RECEDE && glowBottom == 0 && edgeBottom == 0) {
+        boolean oneLastFrame = false;
+        if (mState == STATE_RECEDE && mGlowScaleY == 0) {
             mState = STATE_IDLE;
+            oneLastFrame = true;
         }
 
-        return mState != STATE_IDLE;
+        return mState != STATE_IDLE || oneLastFrame;
     }
 
     /**
-     * Returns the bounds of the edge effect.
-     * 
-     * @hide
+     * 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 Rect getBounds(boolean reverse) {
-        mBounds.set(0, 0, mWidth, mMaxEffectHeight);
-        mBounds.offset(mX, mY - (reverse ? mMaxEffectHeight : 0));
-
-        return mBounds;
+    public int getMaxHeight() {
+        return (int) (mBounds.height() * MAX_GLOW_HEIGHT + 0.5f);
     }
 
     private void update() {
@@ -384,10 +350,9 @@
 
         final float interp = mInterpolator.getInterpolation(t);
 
-        mEdgeAlpha = mEdgeAlphaStart + (mEdgeAlphaFinish - mEdgeAlphaStart) * interp;
-        mEdgeScaleY = mEdgeScaleYStart + (mEdgeScaleYFinish - mEdgeScaleYStart) * interp;
         mGlowAlpha = mGlowAlphaStart + (mGlowAlphaFinish - mGlowAlphaStart) * interp;
         mGlowScaleY = mGlowScaleYStart + (mGlowScaleYFinish - mGlowScaleYStart) * interp;
+        mDisplacement = (mDisplacement + mTargetDisplacement) / 2;
 
         if (t >= 1.f - EPSILON) {
             switch (mState) {
@@ -396,14 +361,10 @@
                     mStartTime = AnimationUtils.currentAnimationTimeMillis();
                     mDuration = RECEDE_TIME;
 
-                    mEdgeAlphaStart = mEdgeAlpha;
-                    mEdgeScaleYStart = mEdgeScaleY;
                     mGlowAlphaStart = mGlowAlpha;
                     mGlowScaleYStart = mGlowScaleY;
 
-                    // After absorb, the glow and edge should fade to nothing.
-                    mEdgeAlphaFinish = 0.f;
-                    mEdgeScaleYFinish = 0.f;
+                    // After absorb, the glow should fade to nothing.
                     mGlowAlphaFinish = 0.f;
                     mGlowScaleYFinish = 0.f;
                     break;
@@ -412,26 +373,14 @@
                     mStartTime = AnimationUtils.currentAnimationTimeMillis();
                     mDuration = PULL_DECAY_TIME;
 
-                    mEdgeAlphaStart = mEdgeAlpha;
-                    mEdgeScaleYStart = mEdgeScaleY;
                     mGlowAlphaStart = mGlowAlpha;
                     mGlowScaleYStart = mGlowScaleY;
 
-                    // After pull, the glow and edge should fade to nothing.
-                    mEdgeAlphaFinish = 0.f;
-                    mEdgeScaleYFinish = 0.f;
+                    // After pull, the glow should fade to nothing.
                     mGlowAlphaFinish = 0.f;
                     mGlowScaleYFinish = 0.f;
                     break;
                 case STATE_PULL_DECAY:
-                    // When receding, we want edge to decrease more slowly
-                    // than the glow.
-                    float factor = mGlowScaleYFinish != 0 ? 1
-                            / (mGlowScaleYFinish * mGlowScaleYFinish)
-                            : Float.MAX_VALUE;
-                    mEdgeScaleY = mEdgeScaleYStart +
-                        (mEdgeScaleYFinish - mEdgeScaleYStart) *
-                            interp * factor;
                     mState = STATE_RECEDE;
                     break;
                 case STATE_RECEDE:
diff --git a/core/java/android/widget/HorizontalScrollView.java b/core/java/android/widget/HorizontalScrollView.java
index 25d4f42..0c65c50 100644
--- a/core/java/android/widget/HorizontalScrollView.java
+++ b/core/java/android/widget/HorizontalScrollView.java
@@ -616,12 +616,14 @@
                     if (canOverscroll) {
                         final int pulledToX = oldX + deltaX;
                         if (pulledToX < 0) {
-                            mEdgeGlowLeft.onPull((float) deltaX / getWidth());
+                            mEdgeGlowLeft.onPull((float) deltaX / getWidth(),
+                                    1.f - ev.getY(activePointerIndex) / getHeight());
                             if (!mEdgeGlowRight.isFinished()) {
                                 mEdgeGlowRight.onRelease();
                             }
                         } else if (pulledToX > range) {
-                            mEdgeGlowRight.onPull((float) deltaX / getWidth());
+                            mEdgeGlowRight.onPull((float) deltaX / getWidth(),
+                                    ev.getY(activePointerIndex) / getHeight());
                             if (!mEdgeGlowLeft.isFinished()) {
                                 mEdgeGlowLeft.onRelease();
                             }
diff --git a/core/java/android/widget/ScrollView.java b/core/java/android/widget/ScrollView.java
index 0fa75a6..fd04890 100644
--- a/core/java/android/widget/ScrollView.java
+++ b/core/java/android/widget/ScrollView.java
@@ -669,12 +669,14 @@
                     } else if (canOverscroll) {
                         final int pulledToY = oldY + deltaY;
                         if (pulledToY < 0) {
-                            mEdgeGlowTop.onPull((float) deltaY / getHeight());
+                            mEdgeGlowTop.onPull((float) deltaY / getHeight(),
+                                    ev.getX(activePointerIndex) / getWidth());
                             if (!mEdgeGlowBottom.isFinished()) {
                                 mEdgeGlowBottom.onRelease();
                             }
                         } else if (pulledToY > range) {
-                            mEdgeGlowBottom.onPull((float) deltaY / getHeight());
+                            mEdgeGlowBottom.onPull((float) deltaY / getHeight(),
+                                    1.f - ev.getX(activePointerIndex) / getWidth());
                             if (!mEdgeGlowTop.isFinished()) {
                                 mEdgeGlowTop.onRelease();
                             }
diff --git a/core/res/res/values/attrs.xml b/core/res/res/values/attrs.xml
index 28e75e6..7a6832e 100644
--- a/core/res/res/values/attrs.xml
+++ b/core/res/res/values/attrs.xml
@@ -6605,4 +6605,8 @@
         <attr name="layout_gravity" />
     </declare-styleable>
 
+    <!-- Used as a filter array on the theme to pull out only the EdgeEffect-relevant bits. -->
+    <declare-styleable name="EdgeEffect">
+        <attr name="colorPrimaryLight" />
+    </declare-styleable>
 </resources>