am fbbe92e8: am c0b09353: Merge "Simplify ripple background drawing, fix ripple alphas" into lmp-dev

* commit 'fbbe92e8bb5aa1f36c5258324fa07357542b1672':
  Simplify ripple background drawing, fix ripple alphas
diff --git a/core/res/res/values/colors_material.xml b/core/res/res/values/colors_material.xml
index d8e14a0..939cbf1 100644
--- a/core/res/res/values/colors_material.xml
+++ b/core/res/res/values/colors_material.xml
@@ -19,8 +19,8 @@
     <color name="background_material_dark">#ff212121</color>
     <color name="background_material_light">#fffafafa</color>
 
-    <color name="ripple_material_light">#20444444</color>
-    <color name="ripple_material_dark">#20ffffff</color>
+    <color name="ripple_material_light">#40000000</color>
+    <color name="ripple_material_dark">#40ffffff</color>
 
     <color name="button_material_dark">#ff5a595b</color>
     <color name="button_material_light">#ffd6d7d7</color>
diff --git a/graphics/java/android/graphics/drawable/Ripple.java b/graphics/java/android/graphics/drawable/Ripple.java
index 43922b8..6f95f91 100644
--- a/graphics/java/android/graphics/drawable/Ripple.java
+++ b/graphics/java/android/graphics/drawable/Ripple.java
@@ -22,6 +22,7 @@
 import android.animation.TimeInterpolator;
 import android.graphics.Canvas;
 import android.graphics.CanvasProperty;
+import android.graphics.Color;
 import android.graphics.Paint;
 import android.graphics.Paint.Style;
 import android.graphics.Rect;
@@ -58,7 +59,7 @@
     private final Rect mBounds;
 
     /** Full-opacity color for drawing this ripple. */
-    private int mColor;
+    private int mColorOpaque;
 
     /** Maximum ripple radius. */
     private float mOuterRadius;
@@ -120,7 +121,7 @@
     }
 
     public void setup(int maxRadius, int color, float density) {
-        mColor = color | 0xFF000000;
+        mColorOpaque = color | 0xFF000000;
 
         if (maxRadius != RippleDrawable.RADIUS_AUTO) {
             mHasMaxRadius = true;
@@ -236,6 +237,9 @@
         if (N > 0) {
             cancelHardwareAnimations(false);
 
+            // We canceled old animations, but we're about to run new ones.
+            mHardwareAnimating = true;
+
             for (int i = 0; i < N; i++) {
                 pendingAnimations.get(i).setTarget(c);
                 pendingAnimations.get(i).start();
@@ -253,9 +257,8 @@
     private boolean drawSoftware(Canvas c, Paint p) {
         boolean hasContent = false;
 
-        // Cache the paint alpha so we can restore it later.
-        final int paintAlpha = p.getAlpha();
-        final int alpha = (int) (paintAlpha * mOpacity + 0.5f);
+        p.setColor(mColorOpaque);
+        final int alpha = (int) (255 * mOpacity + 0.5f);
         final float radius = MathUtils.lerp(0, mOuterRadius, mTweenRadius);
         if (alpha > 0 && radius > 0) {
             final float x = MathUtils.lerp(
@@ -268,8 +271,6 @@
             hasContent = true;
         }
 
-        p.setAlpha(paintAlpha);
-
         return hasContent;
     }
 
@@ -369,7 +370,7 @@
         final float startRadius = MathUtils.lerp(0, mOuterRadius, mTweenRadius);
         final Paint paint = getTempPaint();
         paint.setAntiAlias(true);
-        paint.setColor(mColor);
+        paint.setColor(mColorOpaque);
         paint.setAlpha((int) (255 * mOpacity + 0.5f));
         paint.setStyle(Style.FILL);
         mPropPaint = CanvasProperty.createPaint(paint);
@@ -402,6 +403,12 @@
 
         mHardwareAnimating = true;
 
+        // Set up the software values to match the hardware end values.
+        mOpacity = 0;
+        mTweenX = 1;
+        mTweenY = 1;
+        mTweenRadius = 1;
+
         invalidateSelf();
     }
 
@@ -412,7 +419,7 @@
     public void jump() {
         mCanceled = true;
         endSoftwareAnimations();
-        endHardwareAnimations();
+        cancelHardwareAnimations(true);
         mCanceled = false;
     }
 
@@ -438,24 +445,6 @@
         }
     }
 
-    private void endHardwareAnimations() {
-        final ArrayList<RenderNodeAnimator> runningAnimations = mRunningAnimations;
-        final int N = runningAnimations.size();
-        for (int i = 0; i < N; i++) {
-            runningAnimations.get(i).end();
-        }
-        runningAnimations.clear();
-
-        // Abort any pending animations. Since we always have a completion
-        // listener on a pending animation, we also need to remove ourselves.
-        if (!mPendingAnimations.isEmpty()) {
-            mPendingAnimations.clear();
-            removeSelf();
-        }
-
-        mHardwareAnimating = false;
-    }
-
     private Paint getTempPaint() {
         if (mTempPaint == null) {
             mTempPaint = new Paint();
diff --git a/graphics/java/android/graphics/drawable/RippleBackground.java b/graphics/java/android/graphics/drawable/RippleBackground.java
index 80ecea3..bc6f5fb 100644
--- a/graphics/java/android/graphics/drawable/RippleBackground.java
+++ b/graphics/java/android/graphics/drawable/RippleBackground.java
@@ -22,6 +22,7 @@
 import android.animation.TimeInterpolator;
 import android.graphics.Canvas;
 import android.graphics.CanvasProperty;
+import android.graphics.Color;
 import android.graphics.Paint;
 import android.graphics.Paint.Style;
 import android.graphics.Rect;
@@ -46,8 +47,6 @@
     private static final float WAVE_OUTER_SIZE_INFLUENCE_MAX = 200f;
     private static final float WAVE_OUTER_SIZE_INFLUENCE_MIN = 40f;
 
-    private static final long RIPPLE_ENTER_DELAY = 80;
-
     // Hardware animators.
     private final ArrayList<RenderNodeAnimator> mRunningAnimations =
             new ArrayList<RenderNodeAnimator>();
@@ -60,7 +59,10 @@
     private final Rect mBounds;
 
     /** Full-opacity color for drawing this ripple. */
-    private int mColor;
+    private int mColorOpaque;
+
+    /** Maximum alpha value for drawing this ripple. */
+    private int mColorAlpha;
 
     /** Maximum ripple radius. */
     private float mOuterRadius;
@@ -103,7 +105,8 @@
     }
 
     public void setup(int maxRadius, int color, float density) {
-        mColor = color | 0xFF000000;
+        mColorOpaque = color | 0xFF000000;
+        mColorAlpha = Color.alpha(color);
 
         if (maxRadius != RippleDrawable.RADIUS_AUTO) {
             mHasMaxRadius = true;
@@ -159,6 +162,11 @@
         return hasContent;
     }
 
+    public boolean shouldDraw() {
+        final int outerAlpha = (int) (mColorAlpha * mOuterOpacity + 0.5f);
+        return mCanUseHardware && mHardwareAnimating || outerAlpha > 0 && mOuterRadius > 0;
+    }
+
     private boolean drawHardware(HardwareCanvas c) {
         // If we have any pending hardware animations, cancel any running
         // animations and start those now.
@@ -167,6 +175,9 @@
         if (N > 0) {
             cancelHardwareAnimations(false);
 
+            // We canceled old animations, but we're about to run new ones.
+            mHardwareAnimating = true;
+
             for (int i = 0; i < N; i++) {
                 pendingAnimations.get(i).setTarget(c);
                 pendingAnimations.get(i).start();
@@ -184,10 +195,8 @@
     private boolean drawSoftware(Canvas c, Paint p) {
         boolean hasContent = false;
 
-        // Cache the paint alpha so we can restore it later.
-        final int paintAlpha = p.getAlpha();
-
-        final int outerAlpha = (int) (paintAlpha * mOuterOpacity + 0.5f);
+        p.setColor(mColorOpaque);
+        final int outerAlpha = (int) (mColorAlpha * mOuterOpacity + 0.5f);
         if (outerAlpha > 0 && mOuterRadius > 0) {
             p.setAlpha(outerAlpha);
             p.setStyle(Style.FILL);
@@ -195,8 +204,6 @@
             hasContent = true;
         }
 
-        p.setAlpha(paintAlpha);
-
         return hasContent;
     }
 
@@ -248,25 +255,25 @@
         // Determine at what time the inner and outer opacity intersect.
         // inner(t) = mOpacity - t * WAVE_OPACITY_DECAY_VELOCITY / 1000
         // outer(t) = mOuterOpacity + t * WAVE_OUTER_OPACITY_VELOCITY / 1000
-        final int outerInflection = Math.max(0, (int) (1000 * (1 - mOuterOpacity)
+        final int inflectionDuration = Math.max(0, (int) (1000 * (1 - mOuterOpacity)
                 / (WAVE_OPACITY_DECAY_VELOCITY + outerOpacityVelocity) + 0.5f));
-        final int inflectionOpacity = (int) (255 * (mOuterOpacity + outerInflection
-                * outerOpacityVelocity * outerSizeInfluence / 1000) + 0.5f);
+        final int inflectionOpacity = (int) (mColorAlpha * (mOuterOpacity
+                + inflectionDuration * outerOpacityVelocity * outerSizeInfluence / 1000) + 0.5f);
 
         if (mCanUseHardware) {
-            exitHardware(opacityDuration, outerInflection, inflectionOpacity);
+            exitHardware(opacityDuration, inflectionDuration, inflectionOpacity);
         } else {
-            exitSoftware(opacityDuration, outerInflection, inflectionOpacity);
+            exitSoftware(opacityDuration, inflectionDuration, inflectionOpacity);
         }
     }
 
-    private void exitHardware(int opacityDuration, int outerInflection, int inflectionOpacity) {
+    private void exitHardware(int opacityDuration, int inflectionDuration, int inflectionOpacity) {
         mPendingAnimations.clear();
 
         final Paint outerPaint = getTempPaint();
         outerPaint.setAntiAlias(true);
-        outerPaint.setColor(mColor);
-        outerPaint.setAlpha((int) (255 * mOuterOpacity + 0.5f));
+        outerPaint.setColor(mColorOpaque);
+        outerPaint.setAlpha((int) (mColorAlpha * mOuterOpacity + 0.5f));
         outerPaint.setStyle(Style.FILL);
         mPropOuterPaint = CanvasProperty.createPaint(outerPaint);
         mPropOuterRadius = CanvasProperty.createFloat(mOuterRadius);
@@ -274,21 +281,21 @@
         mPropOuterY = CanvasProperty.createFloat(mOuterY);
 
         final RenderNodeAnimator outerOpacityAnim;
-        if (outerInflection > 0) {
+        if (inflectionDuration > 0) {
             // Outer opacity continues to increase for a bit.
-            outerOpacityAnim = new RenderNodeAnimator(
-                    mPropOuterPaint, RenderNodeAnimator.PAINT_ALPHA, inflectionOpacity);
-            outerOpacityAnim.setDuration(outerInflection);
+            outerOpacityAnim = new RenderNodeAnimator(mPropOuterPaint,
+                    RenderNodeAnimator.PAINT_ALPHA, inflectionOpacity);
+            outerOpacityAnim.setDuration(inflectionDuration);
             outerOpacityAnim.setInterpolator(LINEAR_INTERPOLATOR);
 
             // Chain the outer opacity exit animation.
-            final int outerDuration = opacityDuration - outerInflection;
+            final int outerDuration = opacityDuration - inflectionDuration;
             if (outerDuration > 0) {
                 final RenderNodeAnimator outerFadeOutAnim = new RenderNodeAnimator(
                         mPropOuterPaint, RenderNodeAnimator.PAINT_ALPHA, 0);
                 outerFadeOutAnim.setDuration(outerDuration);
                 outerFadeOutAnim.setInterpolator(LINEAR_INTERPOLATOR);
-                outerFadeOutAnim.setStartDelay(outerInflection);
+                outerFadeOutAnim.setStartDelay(inflectionDuration);
                 outerFadeOutAnim.setStartValue(inflectionOpacity);
                 outerFadeOutAnim.addListener(mAnimationListener);
 
@@ -320,7 +327,7 @@
      */
     public void jump() {
         endSoftwareAnimations();
-        endHardwareAnimations();
+        cancelHardwareAnimations(true);
     }
 
     private void endSoftwareAnimations() {
@@ -330,23 +337,6 @@
         }
     }
 
-    private void endHardwareAnimations() {
-        final ArrayList<RenderNodeAnimator> runningAnimations = mRunningAnimations;
-        final int N = runningAnimations.size();
-        for (int i = 0; i < N; i++) {
-            runningAnimations.get(i).end();
-        }
-        runningAnimations.clear();
-
-        // Abort any pending animations. Since we always have a completion
-        // listener on a pending animation, we also need to remove ourselves.
-        if (!mPendingAnimations.isEmpty()) {
-            mPendingAnimations.clear();
-        }
-
-        mHardwareAnimating = false;
-    }
-
     private Paint getTempPaint() {
         if (mTempPaint == null) {
             mTempPaint = new Paint();
@@ -354,18 +344,18 @@
         return mTempPaint;
     }
 
-    private void exitSoftware(int opacityDuration, int outerInflection, int inflectionOpacity) {
+    private void exitSoftware(int opacityDuration, int inflectionDuration, int inflectionOpacity) {
         final ObjectAnimator outerOpacityAnim;
-        if (outerInflection > 0) {
+        if (inflectionDuration > 0) {
             // Outer opacity continues to increase for a bit.
             outerOpacityAnim = ObjectAnimator.ofFloat(this,
                     "outerOpacity", inflectionOpacity / 255.0f);
             outerOpacityAnim.setAutoCancel(true);
-            outerOpacityAnim.setDuration(outerInflection);
+            outerOpacityAnim.setDuration(inflectionDuration);
             outerOpacityAnim.setInterpolator(LINEAR_INTERPOLATOR);
 
             // Chain the outer opacity exit animation.
-            final int outerDuration = opacityDuration - outerInflection;
+            final int outerDuration = opacityDuration - inflectionDuration;
             if (outerDuration > 0) {
                 outerOpacityAnim.addListener(new AnimatorListenerAdapter() {
                     @Override
@@ -446,14 +436,4 @@
             mHardwareAnimating = false;
         }
     };
-
-    /**
-    * Interpolator with a smooth log deceleration
-    */
-    private static final class LogInterpolator implements TimeInterpolator {
-        @Override
-        public float getInterpolation(float input) {
-            return 1 - (float) Math.pow(400, -input * 1.4);
-        }
-    }
 }
diff --git a/graphics/java/android/graphics/drawable/RippleDrawable.java b/graphics/java/android/graphics/drawable/RippleDrawable.java
index fa762b7..b05fb61 100644
--- a/graphics/java/android/graphics/drawable/RippleDrawable.java
+++ b/graphics/java/android/graphics/drawable/RippleDrawable.java
@@ -242,7 +242,7 @@
 
     @Override
     protected boolean onStateChange(int[] stateSet) {
-        super.onStateChange(stateSet);
+        final boolean changed = super.onStateChange(stateSet);
 
         boolean enabled = false;
         boolean pressed = false;
@@ -263,19 +263,7 @@
         setRippleActive(enabled && pressed);
         setBackgroundActive(focused || (enabled && pressed));
 
-        // Update the paint color. Only applicable when animated in software.
-        if (mRipplePaint != null && mState.mColor != null) {
-            final ColorStateList stateList = mState.mColor;
-            final int newColor = stateList.getColorForState(stateSet, 0);
-            final int oldColor = mRipplePaint.getColor();
-            if (oldColor != newColor) {
-                mRipplePaint.setColor(newColor);
-                invalidateSelf();
-                return true;
-            }
-        }
-
-        return false;
+        return changed;
     }
 
     private void setRippleActive(boolean active) {
@@ -587,6 +575,10 @@
             ripples[i].onHotspotBoundsChanged();
         }
 
+        if (mRipple != null) {
+            mRipple.onHotspotBoundsChanged();
+        }
+
         if (mBackground != null) {
             mBackground.onHotspotBoundsChanged();
         }
@@ -617,17 +609,23 @@
         final boolean drawNonMaskContent = mLayerState.mNum > (hasMask ? 1 : 0);
         final boolean drawMask = hasMask && mMask.getOpacity() != PixelFormat.OPAQUE;
         final Rect bounds = getDirtyBounds();
+        final int saveCount = canvas.save(Canvas.CLIP_SAVE_FLAG);
+        canvas.clipRect(bounds);
 
         // If we have content, draw it into a layer first.
-        final int contentLayer = drawNonMaskContent ?
-                drawContentLayer(canvas, bounds, SRC_OVER) : -1;
+        final int contentLayer;
+        if (drawNonMaskContent) {
+            contentLayer = drawContentLayer(canvas, bounds, SRC_OVER);
+        } else {
+            contentLayer = -1;
+        }
 
         // Next, try to draw the ripples (into a layer if necessary). If we need
         // to mask against the underlying content, set the xfermode to SRC_ATOP.
         final PorterDuffXfermode xfermode = (hasMask || !drawNonMaskContent) ? SRC_OVER : SRC_ATOP;
 
         // If we have a background and a non-opaque mask, draw the masking layer.
-        final int backgroundLayer = drawBackgroundLayer(canvas, bounds, xfermode);
+        final int backgroundLayer = drawBackgroundLayer(canvas, bounds, xfermode, drawMask);
         if (backgroundLayer >= 0) {
             if (drawMask) {
                 drawMaskingLayer(canvas, bounds, DST_IN);
@@ -644,10 +642,13 @@
             canvas.restoreToCount(rippleLayer);
         }
 
-        // Composite the layers if needed.
-        if (contentLayer >= 0) {
-            canvas.restoreToCount(contentLayer);
+        // If we failed to draw anything, at least draw a color so that
+        // invalidation works correctly.
+        if (contentLayer < 0 && backgroundLayer < 0 && rippleLayer < 0) {
+            canvas.drawColor(Color.TRANSPARENT);
         }
+
+        canvas.restoreToCount(saveCount);
     }
 
     /**
@@ -711,81 +712,29 @@
         return restoreToCount;
     }
 
-    private int drawBackgroundLayer(Canvas canvas, Rect bounds, PorterDuffXfermode mode) {
-        // Separate the ripple color and alpha channel. The alpha will be
-        // applied when we merge the ripples down to the canvas.
-        final int rippleARGB;
-        if (mState.mColor != null) {
-            rippleARGB = mState.mColor.getColorForState(getState(), Color.TRANSPARENT);
-        } else {
-            rippleARGB = Color.TRANSPARENT;
+    private int drawBackgroundLayer(
+            Canvas canvas, Rect bounds, PorterDuffXfermode mode, boolean drawMask) {
+        int saveCount = -1;
+
+        if (mBackground != null && mBackground.shouldDraw()) {
+            // TODO: We can avoid saveLayer here if we push the xfermode into
+            // the background's render thread animator at exit() time.
+            if (drawMask || mode != SRC_OVER) {
+                saveCount = canvas.saveLayer(bounds.left, bounds.top, bounds.right,
+                        bounds.bottom, getMaskingPaint(mode));
+            }
+
+            final float x = mHotspotBounds.exactCenterX();
+            final float y = mHotspotBounds.exactCenterY();
+            canvas.translate(x, y);
+            mBackground.draw(canvas, getRipplePaint());
+            canvas.translate(-x, -y);
         }
 
-        if (mRipplePaint == null) {
-            mRipplePaint = new Paint();
-            mRipplePaint.setAntiAlias(true);
-        }
-
-        final int rippleAlpha = Color.alpha(rippleARGB);
-        final Paint ripplePaint = mRipplePaint;
-        ripplePaint.setColor(rippleARGB);
-        ripplePaint.setAlpha(0xFF);
-
-        boolean drewRipples = false;
-        int restoreToCount = -1;
-        int restoreTranslate = -1;
-
-        // Draw background.
-        final RippleBackground background = mBackground;
-        if (background != null) {
-            // If we're masking the ripple layer, make sure we have a layer
-            // first. This will merge SRC_OVER (directly) onto the canvas.
-            final Paint maskingPaint = getMaskingPaint(mode);
-            maskingPaint.setAlpha(rippleAlpha);
-            restoreToCount = canvas.saveLayer(bounds.left, bounds.top,
-                    bounds.right, bounds.bottom, maskingPaint);
-
-            restoreTranslate = canvas.save();
-            // Translate the canvas to the current hotspot bounds.
-            canvas.translate(mHotspotBounds.exactCenterX(), mHotspotBounds.exactCenterY());
-
-            drewRipples = background.draw(canvas, ripplePaint);
-        }
-
-        // Always restore the translation.
-        if (restoreTranslate >= 0) {
-            canvas.restoreToCount(restoreTranslate);
-        }
-
-        // If we created a layer with no content, merge it immediately.
-        if (restoreToCount >= 0 && !drewRipples) {
-            canvas.restoreToCount(restoreToCount);
-            restoreToCount = -1;
-        }
-
-        return restoreToCount;
+        return saveCount;
     }
 
     private int drawRippleLayer(Canvas canvas, Rect bounds, PorterDuffXfermode mode) {
-        // Separate the ripple color and alpha channel. The alpha will be
-        // applied when we merge the ripples down to the canvas.
-        final int rippleARGB;
-        if (mState.mColor != null) {
-            rippleARGB = mState.mColor.getColorForState(getState(), Color.TRANSPARENT);
-        } else {
-            rippleARGB = Color.TRANSPARENT;
-        }
-
-        if (mRipplePaint == null) {
-            mRipplePaint = new Paint();
-            mRipplePaint.setAntiAlias(true);
-        }
-
-        final int rippleAlpha = Color.alpha(rippleARGB);
-        final Paint ripplePaint = mRipplePaint;
-        ripplePaint.setColor(rippleARGB);
-        ripplePaint.setAlpha(0xFF);
-
         boolean drewRipples = false;
         int restoreToCount = -1;
         int restoreTranslate = -1;
@@ -807,16 +756,21 @@
             // first. This will merge SRC_OVER (directly) onto the canvas.
             if (restoreToCount < 0) {
                 final Paint maskingPaint = getMaskingPaint(mode);
-                maskingPaint.setAlpha(rippleAlpha);
+                final int color = mState.mColor.getColorForState(getState(), Color.TRANSPARENT);
+                final int alpha = Color.alpha(color);
+                maskingPaint.setAlpha(alpha / 2);
+
+                // TODO: We can avoid saveLayer here if we're only drawing one
+                // ripple and we don't have content or a translucent mask.
                 restoreToCount = canvas.saveLayer(bounds.left, bounds.top,
                         bounds.right, bounds.bottom, maskingPaint);
 
-                restoreTranslate = canvas.save();
                 // Translate the canvas to the current hotspot bounds.
+                restoreTranslate = canvas.save();
                 canvas.translate(mHotspotBounds.exactCenterX(), mHotspotBounds.exactCenterY());
             }
 
-            drewRipples |= ripple.draw(canvas, ripplePaint);
+            drewRipples |= ripple.draw(canvas, getRipplePaint());
         }
 
         // Always restore the translation.
@@ -845,6 +799,14 @@
         return restoreToCount;
     }
 
+    private Paint getRipplePaint() {
+        if (mRipplePaint == null) {
+            mRipplePaint = new Paint();
+            mRipplePaint.setAntiAlias(true);
+        }
+        return mRipplePaint;
+    }
+
     private Paint getMaskingPaint(PorterDuffXfermode xfermode) {
         if (mMaskingPaint == null) {
             mMaskingPaint = new Paint();