Optimize invalidate calls in lists.

AbsListView was doing too many invalidates during scrolls/flings.
Some of them were also covering too large an area of the screen.

Change-Id: I68fe5dda3657bddd673996e7cf4f3c3672c66cfc
diff --git a/core/java/android/widget/AbsListView.java b/core/java/android/widget/AbsListView.java
index 5774440..9e07151 100644
--- a/core/java/android/widget/AbsListView.java
+++ b/core/java/android/widget/AbsListView.java
@@ -88,6 +88,7 @@
         ViewTreeObserver.OnTouchModeChangeListener,
         RemoteViewsAdapter.RemoteAdapterConnectionCallback {
 
+    @SuppressWarnings("UnusedDeclaration")
     private static final String TAG = "AbsListView";
 
     /**
@@ -2429,7 +2430,7 @@
         final ViewTreeObserver treeObserver = getViewTreeObserver();
         treeObserver.removeOnTouchModeChangeListener(this);
         if (mTextFilterEnabled && mPopup != null) {
-            treeObserver.removeGlobalOnLayoutListener(this);
+            treeObserver.removeOnGlobalLayoutListener(this);
             mGlobalLayoutListenerAddedFilter = false;
         }
 
@@ -2943,11 +2944,23 @@
                             mDirection = 0; // Reset when entering overscroll.
                             mTouchMode = TOUCH_MODE_OVERSCROLL;
                             if (rawDeltaY > 0) {
+                                if (!mEdgeGlowTop.isIdle()) {
+                                    invalidate(mEdgeGlowTop.getBounds());
+                                } else {
+                                    invalidate();
+                                }
+
                                 mEdgeGlowTop.onPull((float) overscroll / getHeight());
                                 if (!mEdgeGlowBottom.isFinished()) {
                                     mEdgeGlowBottom.onRelease();
                                 }
                             } else if (rawDeltaY < 0) {
+                                if (!mEdgeGlowBottom.isIdle()) {
+                                    invalidate(mEdgeGlowBottom.getBounds());
+                                } else {
+                                    invalidate();
+                                }
+
                                 mEdgeGlowBottom.onPull((float) overscroll / getHeight());
                                 if (!mEdgeGlowTop.isFinished()) {
                                     mEdgeGlowTop.onRelease();
@@ -2956,7 +2969,6 @@
                         }
                     }
                     mMotionY = y;
-                    invalidate();
                 }
                 mLastY = y;
             }
@@ -2990,26 +3002,26 @@
                             if (!mEdgeGlowBottom.isFinished()) {
                                 mEdgeGlowBottom.onRelease();
                             }
+                            invalidate(mEdgeGlowTop.getBounds());
                         } else if (rawDeltaY < 0) {
                             mEdgeGlowBottom.onPull((float) overScrollDistance / getHeight());
                             if (!mEdgeGlowTop.isFinished()) {
                                 mEdgeGlowTop.onRelease();
                             }
+                            invalidate(mEdgeGlowBottom.getBounds());
                         }
-                        invalidate();
                     }
                 }
 
                 if (incrementalDeltaY != 0) {
                     // Coming back to 'real' list scrolling
-                    mScrollY = 0;
-                    invalidateParentIfNeeded();
-
-                    // No need to do all this work if we're not going to move anyway
-                    if (incrementalDeltaY != 0) {
-                        trackMotionScroll(incrementalDeltaY, incrementalDeltaY);
+                    if (mScrollY != 0) {
+                        mScrollY = 0;
+                        invalidateParentIfNeeded();
                     }
 
+                    trackMotionScroll(incrementalDeltaY, incrementalDeltaY);
+
                     mTouchMode = TOUCH_MODE_SCROLL;
 
                     // We did not scroll the full amount. Treat this essentially like the
@@ -3468,11 +3480,12 @@
                 final int rightPadding = mListPadding.right + mGlowPaddingRight;
                 final int width = getWidth() - leftPadding - rightPadding;
 
-                canvas.translate(leftPadding,
-                        Math.min(0, scrollY + mFirstPositionDistanceGuess));
+                int edgeY = Math.min(0, scrollY + mFirstPositionDistanceGuess);
+                canvas.translate(leftPadding, edgeY);
                 mEdgeGlowTop.setSize(width, getHeight());
                 if (mEdgeGlowTop.draw(canvas)) {
-                    invalidate();
+                    mEdgeGlowTop.setPosition(leftPadding, edgeY);
+                    invalidate(mEdgeGlowTop.getBounds());
                 }
                 canvas.restoreToCount(restoreCount);
             }
@@ -3483,12 +3496,15 @@
                 final int width = getWidth() - leftPadding - rightPadding;
                 final int height = getHeight();
 
-                canvas.translate(-width + leftPadding,
-                        Math.max(height, scrollY + mLastPositionDistanceGuess));
+                int edgeX = -width + leftPadding;
+                int edgeY = Math.max(height, scrollY + mLastPositionDistanceGuess);
+                canvas.translate(edgeX, edgeY);
                 canvas.rotate(180, width, 0);
                 mEdgeGlowBottom.setSize(width, height);
                 if (mEdgeGlowBottom.draw(canvas)) {
-                    invalidate();
+                    // Account for the rotation
+                    mEdgeGlowBottom.setPosition(edgeX + width, edgeY - mEdgeGlowBottom.getHeight());
+                    invalidate(mEdgeGlowBottom.getBounds());
                 }
                 canvas.restoreToCount(restoreCount);
             }
@@ -3874,7 +3890,8 @@
                 }
 
                 // Don't stop just because delta is zero (it could have been rounded)
-                final boolean atEnd = trackMotionScroll(delta, delta) && (delta != 0);
+                final boolean atEdge = trackMotionScroll(delta, delta);
+                final boolean atEnd = atEdge && (delta != 0);
                 if (atEnd) {
                     if (motionView != null) {
                         // Tweak the scroll for how far we overshot
@@ -3889,7 +3906,7 @@
                 }
 
                 if (more && !atEnd) {
-                    invalidate();
+                    if (atEdge) invalidate();
                     mLastFlingY = y;
                     post(this);
                 } else {
@@ -4431,7 +4448,7 @@
     }
 
     private void createScrollingCache() {
-        if (mScrollingCacheEnabled && !mCachingStarted) {
+        if (mScrollingCacheEnabled && !mCachingStarted && !isHardwareAccelerated()) {
             setChildrenDrawnWithCacheEnabled(true);
             setChildrenDrawingCacheEnabled(true);
             mCachingStarted = mCachingActive = true;
@@ -4439,23 +4456,25 @@
     }
 
     private void clearScrollingCache() {
-        if (mClearScrollingCache == null) {
-            mClearScrollingCache = new Runnable() {
-                public void run() {
-                    if (mCachingStarted) {
-                        mCachingStarted = mCachingActive = false;
-                        setChildrenDrawnWithCacheEnabled(false);
-                        if ((mPersistentDrawingCache & PERSISTENT_SCROLLING_CACHE) == 0) {
-                            setChildrenDrawingCacheEnabled(false);
-                        }
-                        if (!isAlwaysDrawnWithCacheEnabled()) {
-                            invalidate();
+        if (!isHardwareAccelerated()) {
+            if (mClearScrollingCache == null) {
+                mClearScrollingCache = new Runnable() {
+                    public void run() {
+                        if (mCachingStarted) {
+                            mCachingStarted = mCachingActive = false;
+                            setChildrenDrawnWithCacheEnabled(false);
+                            if ((mPersistentDrawingCache & PERSISTENT_SCROLLING_CACHE) == 0) {
+                                setChildrenDrawingCacheEnabled(false);
+                            }
+                            if (!isAlwaysDrawnWithCacheEnabled()) {
+                                invalidate();
+                            }
                         }
                     }
-                }
-            };
+                };
+            }
+            post(mClearScrollingCache);
         }
-        post(mClearScrollingCache);
     }
 
     /**
@@ -4599,14 +4618,18 @@
             mRecycler.removeSkippedScrap();
         }
 
+        // invalidate before moving the children to avoid unnecessary invalidate
+        // calls to bubble up from the children all the way to the top
+        if (!awakenScrollBars()) {
+            invalidate();
+        }
+
         offsetChildrenTopAndBottom(incrementalDeltaY);
 
         if (down) {
             mFirstPosition += count;
         }
 
-        invalidate();
-
         final int absIncrementalDeltaY = Math.abs(incrementalDeltaY);
         if (spaceAbove < absIncrementalDeltaY || spaceBelow < absIncrementalDeltaY) {
             fillGap(down);
@@ -4629,7 +4652,6 @@
         mBlockLayoutRequests = false;
 
         invokeOnItemScrollListener();
-        awakenScrollBars();
 
         return false;
     }
diff --git a/core/java/android/widget/EdgeEffect.java b/core/java/android/widget/EdgeEffect.java
index 83aa8ba..c1f31bb 100644
--- a/core/java/android/widget/EdgeEffect.java
+++ b/core/java/android/widget/EdgeEffect.java
@@ -16,6 +16,7 @@
 
 package android.widget;
 
+import android.graphics.Rect;
 import com.android.internal.R;
 
 import android.content.Context;
@@ -45,6 +46,7 @@
  * {@link #draw(Canvas)} method.</p>
  */
 public class EdgeEffect {
+    @SuppressWarnings("UnusedDeclaration")
     private static final String TAG = "EdgeEffect";
 
     // Time it will take the effect to fully recede in ms
@@ -57,10 +59,7 @@
     private static final int PULL_DECAY_TIME = 1000;
 
     private static final float MAX_ALPHA = 1.f;
-    private static final float HELD_EDGE_ALPHA = 0.7f;
     private static final float HELD_EDGE_SCALE_Y = 0.5f;
-    private static final float HELD_GLOW_ALPHA = 0.5f;
-    private static final float HELD_GLOW_SCALE_Y = 0.5f;
 
     private static final float MAX_GLOW_HEIGHT = 4.f;
 
@@ -76,7 +75,9 @@
     private final Drawable mGlow;
     private int mWidth;
     private int mHeight;
-    private final int MIN_WIDTH = 300;
+    private int mX;
+    private int mY;
+    private static final int MIN_WIDTH = 300;
     private final int mMinWidth;
 
     private float mEdgeAlpha;
@@ -119,6 +120,8 @@
     private int mState = STATE_IDLE;
 
     private float mPullDistance;
+    
+    private final Rect mBounds = new Rect();
 
     /**
      * Construct a new EdgeEffect with a theme appropriate for the provided context.
@@ -145,6 +148,29 @@
     }
 
     /**
+     * Set the position of this edge effect in pixels. This position is
+     * only used by {@link #getBounds()}.
+     * 
+     * @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;
+    }
+
+    boolean isIdle() {
+        return mState == STATE_IDLE;
+    }
+
+    /**
+     * Returns the height of the effect itself.
+     */
+    int getHeight() {
+        return Math.max(mGlow.getBounds().height(), mEdge.getBounds().height());
+    }
+    
+    /**
      * 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.
@@ -301,7 +327,6 @@
         update();
 
         final int edgeHeight = mEdge.getIntrinsicHeight();
-        final int edgeWidth = mEdge.getIntrinsicWidth();
         final int glowHeight = mGlow.getIntrinsicHeight();
         final int glowWidth = mGlow.getIntrinsicWidth();
 
@@ -334,9 +359,23 @@
         }
         mEdge.draw(canvas);
 
+        if (mState == STATE_RECEDE && glowBottom == 0 && edgeBottom == 0) {
+            mState = STATE_IDLE;
+        }
+
         return mState != STATE_IDLE;
     }
 
+    /**
+     * Returns the bounds of the edge effect.
+     */
+    public Rect getBounds() {
+        mBounds.set(mGlow.getBounds());
+        mBounds.union(mEdge.getBounds());
+        mBounds.offset(mX, mY);
+        return mBounds;
+    }
+
     private void update() {
         final long time = AnimationUtils.currentAnimationTimeMillis();
         final float t = Math.min((time - mStartTime) / mDuration, 1.f);