Merge "Fix disappearing ripple background, treat active ripple separately" into lmp-dev
diff --git a/graphics/java/android/graphics/drawable/Ripple.java b/graphics/java/android/graphics/drawable/Ripple.java
index 063ac09..cd919a6 100644
--- a/graphics/java/android/graphics/drawable/Ripple.java
+++ b/graphics/java/android/graphics/drawable/Ripple.java
@@ -105,6 +105,9 @@
     /** Whether we have an explicit maximum radius. */
     private boolean mHasMaxRadius;
 
+    /** Whether we were canceled externally and should avoid self-removal. */
+    private boolean mCanceled;
+
     /**
      * Creates a new ripple.
      */
@@ -295,6 +298,8 @@
      * Starts the enter animation.
      */
     public void enter() {
+        cancel();
+
         final int radiusDuration = (int)
                 (1000 * Math.sqrt(mOuterRadius / WAVE_TOUCH_DOWN_ACCELERATION * mDensity) + 0.5);
 
@@ -332,7 +337,8 @@
      * Starts the exit animation.
      */
     public void exit() {
-        cancelSoftwareAnimations();
+        cancel();
+
         final float radius = MathUtils.lerp(0, mOuterRadius, mTweenRadius);
         final float remaining;
         if (mAnimRadius != null && mAnimRadius.isRunning()) {
@@ -399,9 +405,15 @@
         invalidateSelf();
     }
 
+    /**
+     * Jump all animations to their end state. The caller is responsible for
+     * removing the ripple from the list of animating ripples.
+     */
     public void jump() {
+        mCanceled = true;
         endSoftwareAnimations();
         endHardwareAnimations();
+        mCanceled = false;
     }
 
     private void endSoftwareAnimations() {
@@ -436,6 +448,8 @@
             mPendingAnimations.clear();
             removeSelf();
         }
+
+        mHardwareAnimating = false;
     }
 
     private Paint getTempPaint() {
@@ -479,11 +493,14 @@
     }
 
     /**
-     * Cancel all animations.
+     * Cancels all animations. The caller is responsible for removing
+     * the ripple from the list of animating ripples.
      */
     public void cancel() {
+        mCanceled = true;
         cancelSoftwareAnimations();
         cancelHardwareAnimations(true);
+        mCanceled = false;
     }
 
     private void cancelSoftwareAnimations() {
@@ -517,13 +534,16 @@
 
         if (cancelPending && !mPendingAnimations.isEmpty()) {
             mPendingAnimations.clear();
-            removeSelf();
         }
+
+        mHardwareAnimating = false;
     }
 
     private void removeSelf() {
         // The owner will invalidate itself.
-        mOwner.removeRipple(this);
+        if (!mCanceled) {
+            mOwner.removeRipple(this);
+        }
     }
 
     private void invalidateSelf() {
diff --git a/graphics/java/android/graphics/drawable/RippleBackground.java b/graphics/java/android/graphics/drawable/RippleBackground.java
index 34e6a20..40a5e18 100644
--- a/graphics/java/android/graphics/drawable/RippleBackground.java
+++ b/graphics/java/android/graphics/drawable/RippleBackground.java
@@ -107,6 +107,9 @@
     /** Whether we have an explicit maximum radius. */
     private boolean mHasMaxRadius;
 
+    /** Whether we were canceled externally and should avoid self-removal. */
+    private boolean mCanceled;
+
     /**
      * Creates a new ripple.
      */
@@ -284,6 +287,8 @@
      * Starts the enter animation.
      */
     public void enter() {
+        cancel();
+
         final int radiusDuration = (int)
                 (1000 * Math.sqrt(mOuterRadius / WAVE_TOUCH_DOWN_ACCELERATION * mDensity) + 0.5);
         final int outerDuration = (int) (1000 * 1.0f / WAVE_OUTER_OPACITY_ENTER_VELOCITY);
@@ -321,7 +326,7 @@
      * Starts the exit animation.
      */
     public void exit() {
-        cancelSoftwareAnimations();
+        cancel();
 
         // Scale the outer max opacity and opacity velocity based
         // on the size of the outer radius.
@@ -404,9 +409,15 @@
         invalidateSelf();
     }
 
+    /**
+     * Jump all animations to their end state. The caller is responsible for
+     * removing the ripple from the list of animating ripples.
+     */
     public void jump() {
+        mCanceled = true;
         endSoftwareAnimations();
         endHardwareAnimations();
+        mCanceled = false;
     }
 
     private void endSoftwareAnimations() {
@@ -437,6 +448,8 @@
             mPendingAnimations.clear();
             removeSelf();
         }
+
+        mHardwareAnimating = false;
     }
 
     private Paint getTempPaint() {
@@ -509,11 +522,14 @@
     }
 
     /**
-     * Cancel all animations.
+     * Cancel all animations. The caller is responsible for removing
+     * the ripple from the list of animating ripples.
      */
     public void cancel() {
+        mCanceled = true;
         cancelSoftwareAnimations();
         cancelHardwareAnimations(true);
+        mCanceled = false;
     }
 
     private void cancelSoftwareAnimations() {
@@ -544,13 +560,16 @@
 
         if (cancelPending && !mPendingAnimations.isEmpty()) {
             mPendingAnimations.clear();
-            removeSelf();
         }
+
+        mHardwareAnimating = false;
     }
 
     private void removeSelf() {
         // The owner will invalidate itself.
-        mOwner.removeBackground(this);
+        if (!mCanceled) {
+            mOwner.removeBackground(this);
+        }
     }
 
     private void invalidateSelf() {
diff --git a/graphics/java/android/graphics/drawable/RippleDrawable.java b/graphics/java/android/graphics/drawable/RippleDrawable.java
index 7402bdb..b90fd81 100644
--- a/graphics/java/android/graphics/drawable/RippleDrawable.java
+++ b/graphics/java/android/graphics/drawable/RippleDrawable.java
@@ -140,8 +140,8 @@
      * Lazily-created array of actively animating ripples. Inactive ripples are
      * pruned during draw(). The locations of these will not change.
      */
-    private Ripple[] mAnimatingRipples;
-    private int mAnimatingRipplesCount = 0;
+    private Ripple[] mExitingRipples;
+    private int mExitingRipplesCount = 0;
 
     /** Paint used to control appearance of ripples. */
     private Paint mRipplePaint;
@@ -156,12 +156,6 @@
     private boolean mOverrideBounds;
 
     /**
-     * Whether hotspots are being cleared. Used to prevent re-entry by
-     * animation finish listeners.
-     */
-    private boolean mClearingHotspots;
-
-    /**
      * Constructor used for drawable inflation.
      */
     RippleDrawable() {
@@ -209,19 +203,21 @@
             mBackground.jump();
         }
 
-        mClearingHotspots = true;
-        final int count = mAnimatingRipplesCount;
-        final Ripple[] ripples = mAnimatingRipples;
+        cancelExitingRipples();
+        invalidateSelf();
+    }
+
+    private void cancelExitingRipples() {
+        final int count = mExitingRipplesCount;
+        final Ripple[] ripples = mExitingRipples;
         for (int i = 0; i < count; i++) {
-            ripples[i].jump();
+            ripples[i].cancel();
         }
+
         if (ripples != null) {
             Arrays.fill(ripples, 0, count, null);
         }
-        mAnimatingRipplesCount = 0;
-        mClearingHotspots = false;
-
-        invalidateSelf();
+        mExitingRipplesCount = 0;
     }
 
     @Override
@@ -287,9 +283,9 @@
         if (mRippleActive != active) {
             mRippleActive = active;
             if (active) {
-                activateRipple();
+                tryRippleEnter();
             } else {
-                removeRipple();
+                tryRippleExit();
             }
         }
     }
@@ -298,9 +294,9 @@
         if (mBackgroundActive != active) {
             mBackgroundActive = active;
             if (active) {
-                activateBackground();
+                tryBackgroundEnter();
             } else {
-                removeBackground();
+                tryBackgroundExit();
             }
         }
     }
@@ -327,11 +323,11 @@
             // If we just became visible, ensure the background and ripple
             // visibilities are consistent with their internal states.
             if (mRippleActive) {
-                activateRipple();
+                tryRippleEnter();
             }
 
             if (mBackgroundActive) {
-                activateBackground();
+                tryBackgroundEnter();
             }
         }
 
@@ -491,7 +487,7 @@
     /**
      * Creates an active hotspot at the specified location.
      */
-    private void activateBackground() {
+    private void tryBackgroundEnter() {
         if (mBackground == null) {
             final float x;
             final float y;
@@ -511,7 +507,7 @@
         mBackground.enter();
     }
 
-    private void removeBackground() {
+    private void tryBackgroundExit() {
         if (mBackground != null) {
             // Don't null out the background, we need it to draw!
             mBackground.exit();
@@ -519,10 +515,11 @@
     }
 
     /**
-     * Creates an active hotspot at the specified location.
+     * Attempts to start an enter animation for the active hotspot. Fails if
+     * there are too many animating ripples.
      */
-    private void activateRipple() {
-        if (mAnimatingRipplesCount >= MAX_RIPPLES) {
+    private void tryRippleEnter() {
+        if (mExitingRipplesCount >= MAX_RIPPLES) {
             // This should never happen unless the user is tapping like a maniac
             // or there is a bug that's preventing ripples from being removed.
             return;
@@ -545,29 +542,27 @@
         final int color = mState.mColor.getColorForState(getState(), Color.TRANSPARENT);
         mRipple.setup(mState.mMaxRadius, color, mDensity);
         mRipple.enter();
-
-        if (mAnimatingRipples == null) {
-            mAnimatingRipples = new Ripple[MAX_RIPPLES];
-        }
-        mAnimatingRipples[mAnimatingRipplesCount++] = mRipple;
     }
 
-    @Override
-    public void invalidateSelf() {
-        // Don't invalidate when we're clearing hotspots. We'll handle that
-        // manually when we're done.
-        if (!mClearingHotspots) {
-            super.invalidateSelf();
-        }
-    }
-
-    private void removeRipple() {
+    /**
+     * Attempts to start an exit animation for the active hotspot. Fails if
+     * there is no active hotspot.
+     */
+    private void tryRippleExit() {
         if (mRipple != null) {
+            if (mExitingRipples == null) {
+                mExitingRipples = new Ripple[MAX_RIPPLES];
+            }
+            mExitingRipples[mExitingRipplesCount++] = mRipple;
             mRipple.exit();
             mRipple = null;
         }
     }
 
+    /**
+     * Cancels and removes the active ripple, all exiting ripples, and the
+     * background. Nothing will be drawn after this method is called.
+     */
     private void clearHotspots() {
         if (mRipple != null) {
             mRipple.cancel();
@@ -579,18 +574,7 @@
             mBackground = null;
         }
 
-        mClearingHotspots = true;
-        final int count = mAnimatingRipplesCount;
-        final Ripple[] ripples = mAnimatingRipples;
-        for (int i = 0; i < count; i++) {
-            ripples[i].cancel();
-        }
-        if (ripples != null) {
-            Arrays.fill(ripples, 0, count, null);
-        }
-        mAnimatingRipplesCount = 0;
-        mClearingHotspots = false;
-
+        cancelExitingRipples();
         invalidateSelf();
     }
 
@@ -612,8 +596,8 @@
      * Notifies all the animating ripples that the hotspot bounds have changed.
      */
     private void onHotspotBoundsChanged() {
-        final int count = mAnimatingRipplesCount;
-        final Ripple[] ripples = mAnimatingRipples;
+        final int count = mExitingRipplesCount;
+        final Ripple[] ripples = mExitingRipples;
         for (int i = 0; i < count; i++) {
             ripples[i].onHotspotBoundsChanged();
         }
@@ -683,22 +667,21 @@
     }
 
     /**
-     * Removes a ripple from the animating ripple list.
+     * Removes a ripple from the exiting ripple list.
      *
      * @param ripple the ripple to remove
      */
     void removeRipple(Ripple ripple) {
-        if (!mClearingHotspots) {
-            // Ripple ripple ripple ripple. Ripple ripple.
-            final Ripple[] ripples = mAnimatingRipples;
-            final int count = mAnimatingRipplesCount;
-            final int index = getRippleIndex(ripple);
-            if (index >= 0) {
-                System.arraycopy(ripples, index + 1, ripples, index, count - (index + 1));
-                ripples[count - 1] = null;
-                mAnimatingRipplesCount--;
-                invalidateSelf();
-            }
+        // Ripple ripple ripple ripple. Ripple ripple.
+        final Ripple[] ripples = mExitingRipples;
+        final int count = mExitingRipplesCount;
+        final int index = getRippleIndex(ripple);
+        if (index >= 0) {
+            System.arraycopy(ripples, index + 1, ripples, index, count - (index + 1));
+            ripples[count - 1] = null;
+            mExitingRipplesCount--;
+
+            invalidateSelf();
         }
     }
 
@@ -710,8 +693,8 @@
     }
 
     private int getRippleIndex(Ripple ripple) {
-        final Ripple[] ripples = mAnimatingRipples;
-        final int count = mAnimatingRipplesCount;
+        final Ripple[] ripples = mExitingRipples;
+        final int count = mExitingRipplesCount;
         for (int i = 0; i < count; i++) {
             if (ripples[i] == ripple) {
                 return i;
@@ -727,7 +710,7 @@
         // We don't need a layer if we don't expect to draw any ripples, we have
         // an explicit mask, or if the non-mask content is all opaque.
         boolean needsLayer = false;
-        if ((mAnimatingRipplesCount > 0 || mBackground != null) && mMask == null) {
+        if ((mExitingRipplesCount > 0 || mBackground != null) && mMask == null) {
             for (int i = 0; i < count; i++) {
                 if (array[i].mId != R.id.mask
                         && array[i].mDrawable.getOpacity() != PixelFormat.OPAQUE) {
@@ -831,10 +814,17 @@
         int restoreTranslate = -1;
 
         // Draw ripples and update the animating ripples array.
-        final int count = mAnimatingRipplesCount;
-        final Ripple[] ripples = mAnimatingRipples;
-        for (int i = 0; i < count; i++) {
-            final Ripple ripple = ripples[i];
+        final int count = mExitingRipplesCount;
+        final Ripple[] ripples = mExitingRipples;
+        for (int i = 0; i <= count; i++) {
+            final Ripple ripple;
+            if (i < count) {
+                ripple = ripples[i];
+            } else if (mRipple != null) {
+                ripple = mRipple;
+            } else {
+                continue;
+            }
 
             // If we're masking the ripple layer, make sure we have a layer
             // first. This will merge SRC_OVER (directly) onto the canvas.
@@ -898,8 +888,9 @@
             final int cX = (int) mHotspotBounds.exactCenterX();
             final int cY = (int) mHotspotBounds.exactCenterY();
             final Rect rippleBounds = mTempRect;
-            final Ripple[] activeRipples = mAnimatingRipples;
-            final int N = mAnimatingRipplesCount;
+
+            final Ripple[] activeRipples = mExitingRipples;
+            final int N = mExitingRipplesCount;
             for (int i = 0; i < N; i++) {
                 activeRipples[i].getBounds(rippleBounds);
                 rippleBounds.offset(cX, cY);