Move sparkle animation to RenderThread

The sparkle loop animation was happening on the UI thread and is poses
some interesting challengers:
- Animations freezes when UI thread is busy, for example when
  startActivity is called.
- onDraw calls add unnecessary work to the UI thread, leading to jank
  in some cases, like PIP

Test: manual
Fixes: 184760248
Change-Id: Ie2840c767da61476678839eaac215dc3aff95b5c
diff --git a/graphics/java/android/graphics/RecordingCanvas.java b/graphics/java/android/graphics/RecordingCanvas.java
index 6c03ddc..e9e58c6 100644
--- a/graphics/java/android/graphics/RecordingCanvas.java
+++ b/graphics/java/android/graphics/RecordingCanvas.java
@@ -226,10 +226,12 @@
      */
     public void drawRipple(CanvasProperty<Float> cx, CanvasProperty<Float> cy,
             CanvasProperty<Float> radius, CanvasProperty<Paint> paint,
-            CanvasProperty<Float> progress, RuntimeShader shader) {
+            CanvasProperty<Float> progress, CanvasProperty<Float> turbulencePhase,
+            RuntimeShader shader) {
         nDrawRipple(mNativeCanvasWrapper, cx.getNativeContainer(), cy.getNativeContainer(),
                 radius.getNativeContainer(), paint.getNativeContainer(),
-                progress.getNativeContainer(), shader.getNativeShaderBuilder());
+                progress.getNativeContainer(), turbulencePhase.getNativeContainer(),
+                shader.getNativeShaderBuilder());
     }
 
     /**
@@ -290,7 +292,7 @@
             long propCy, long propRadius, long propPaint);
     @CriticalNative
     private static native void nDrawRipple(long renderer, long propCx, long propCy, long propRadius,
-            long propPaint, long propProgress, long runtimeEffect);
+            long propPaint, long propProgress, long turbulencePhase, long runtimeEffect);
     @CriticalNative
     private static native void nDrawRoundRect(long renderer, long propLeft, long propTop,
             long propRight, long propBottom, long propRx, long propRy, long propPaint);
diff --git a/graphics/java/android/graphics/drawable/RippleAnimationSession.java b/graphics/java/android/graphics/drawable/RippleAnimationSession.java
index c317831..1cd4cf1 100644
--- a/graphics/java/android/graphics/drawable/RippleAnimationSession.java
+++ b/graphics/java/android/graphics/drawable/RippleAnimationSession.java
@@ -40,6 +40,8 @@
     private static final String TAG = "RippleAnimationSession";
     private static final int ENTER_ANIM_DURATION = 450;
     private static final int EXIT_ANIM_DURATION = 300;
+    private static final long NOISE_ANIMATION_DURATION = 7000;
+    private static final long MAX_NOISE_PHASE = NOISE_ANIMATION_DURATION / 120;
     private static final TimeInterpolator LINEAR_INTERPOLATOR = new LinearInterpolator();
     private static final Interpolator FAST_OUT_SLOW_IN =
             new PathInterpolator(0.4f, 0f, 0.2f, 1f);
@@ -49,7 +51,7 @@
     private Runnable mOnUpdate;
     private long mStartTime;
     private boolean mForceSoftware;
-    private boolean mAnimateSparkle;
+    private Animator mLoopAnimation;
 
     RippleAnimationSession(@NonNull AnimationProperties<Float, Paint> properties,
             boolean forceSoftware) {
@@ -88,16 +90,6 @@
         return this;
     }
 
-    public boolean shouldAnimateSparkle() {
-        return mAnimateSparkle && ValueAnimator.getDurationScale() > 0;
-    }
-
-    public float getSparklePhase() {
-        final long now = AnimationUtils.currentAnimationTimeMillis();
-        final long elapsed = now - mStartTime;
-        return  (float) elapsed / 800;
-    }
-
     private boolean isHwAccelerated(Canvas canvas) {
         return canvas.isHardwareAccelerated() && !mForceSoftware;
     }
@@ -114,7 +106,7 @@
             @Override
             public void onAnimationEnd(Animator animation) {
                 super.onAnimationEnd(animation);
-                mAnimateSparkle = false;
+                if (mLoopAnimation != null) mLoopAnimation.cancel();
                 Consumer<RippleAnimationSession> onEnd = mOnSessionEnd;
                 if (onEnd != null) onEnd.accept(RippleAnimationSession.this);
             }
@@ -148,7 +140,7 @@
             @Override
             public void onAnimationEnd(Animator animation) {
                 super.onAnimationEnd(animation);
-                mAnimateSparkle = false;
+                if (mLoopAnimation != null) mLoopAnimation.cancel();
                 Consumer<RippleAnimationSession> onEnd = mOnSessionEnd;
                 if (onEnd != null) onEnd.accept(RippleAnimationSession.this);
             }
@@ -167,24 +159,42 @@
         RenderNodeAnimator expand =
                 new RenderNodeAnimator(props.getProgress(), .5f);
         expand.setTarget(canvas);
-        startAnimation(expand);
+        RenderNodeAnimator loop = new RenderNodeAnimator(props.getNoisePhase(), MAX_NOISE_PHASE);
+        loop.setTarget(canvas);
+        startAnimation(expand, loop);
     }
 
-    private void startAnimation(Animator expand) {
+    private void startAnimation(Animator expand, Animator loop) {
         expand.setDuration(ENTER_ANIM_DURATION);
         expand.addListener(new AnimatorListener(this));
         expand.setInterpolator(FAST_OUT_SLOW_IN);
         expand.start();
-        mAnimateSparkle = true;
+        loop.setDuration(NOISE_ANIMATION_DURATION);
+        loop.addListener(new AnimatorListener(this) {
+            @Override
+            public void onAnimationEnd(Animator animation) {
+                super.onAnimationEnd(animation);
+                mLoopAnimation = null;
+            }
+        });
+        loop.setInterpolator(LINEAR_INTERPOLATOR);
+        loop.start();
+        if (mLoopAnimation != null) mLoopAnimation.cancel();
+        mLoopAnimation = loop;
     }
 
     private void enterSoftware() {
         ValueAnimator expand = ValueAnimator.ofFloat(0f, 0.5f);
         expand.addUpdateListener(updatedAnimation -> {
             notifyUpdate();
-            mProperties.getShader().setProgress((Float) expand.getAnimatedValue());
+            mProperties.getShader().setProgress((float) expand.getAnimatedValue());
         });
-        startAnimation(expand);
+        ValueAnimator loop = ValueAnimator.ofFloat(0f, MAX_NOISE_PHASE);
+        loop.addUpdateListener(updatedAnimation -> {
+            notifyUpdate();
+            mProperties.getShader().setNoisePhase((float) loop.getAnimatedValue());
+        });
+        startAnimation(expand, loop);
     }
 
     @NonNull AnimationProperties<Float, Paint> getProperties() {
@@ -198,6 +208,7 @@
                     CanvasProperty.createFloat(mProperties.getX()),
                     CanvasProperty.createFloat(mProperties.getY()),
                     CanvasProperty.createFloat(mProperties.getMaxRadius()),
+                    CanvasProperty.createFloat(mProperties.getNoisePhase()),
                     CanvasProperty.createPaint(mProperties.getPaint()),
                     CanvasProperty.createFloat(mProperties.getProgress()),
                     mProperties.getShader());
@@ -236,16 +247,18 @@
     static class AnimationProperties<FloatType, PaintType> {
         private final FloatType mProgress;
         private final FloatType mMaxRadius;
+        private final FloatType mNoisePhase;
         private final PaintType mPaint;
         private final RippleShader mShader;
         private FloatType mX;
         private FloatType mY;
 
-        AnimationProperties(FloatType x, FloatType y, FloatType maxRadius,
+        AnimationProperties(FloatType x, FloatType y, FloatType maxRadius, FloatType noisePhase,
                 PaintType paint, FloatType progress, RippleShader shader) {
             mY = y;
             mX = x;
             mMaxRadius = maxRadius;
+            mNoisePhase = noisePhase;
             mPaint = paint;
             mShader = shader;
             mProgress = progress;
@@ -279,5 +292,9 @@
         RippleShader getShader() {
             return mShader;
         }
+
+        FloatType getNoisePhase() {
+            return mNoisePhase;
+        }
     }
 }
diff --git a/graphics/java/android/graphics/drawable/RippleDrawable.java b/graphics/java/android/graphics/drawable/RippleDrawable.java
index 0865332..c972a24 100644
--- a/graphics/java/android/graphics/drawable/RippleDrawable.java
+++ b/graphics/java/android/graphics/drawable/RippleDrawable.java
@@ -858,15 +858,6 @@
         }
         for (int i = 0; i < mRunningAnimations.size(); i++) {
             RippleAnimationSession s = mRunningAnimations.get(i);
-            if (s.shouldAnimateSparkle()) {
-                final float phase = s.getSparklePhase();
-                if (useCanvasProps) {
-                    s.getCanvasProperties().getShader().setNoisePhase(phase);
-                } else {
-                    s.getProperties().getShader().setNoisePhase(phase);
-                }
-                invalidateSelf();
-            }
             if (useCanvasProps) {
                 RippleAnimationSession.AnimationProperties<CanvasProperty<Float>,
                         CanvasProperty<Paint>>
@@ -883,7 +874,7 @@
                     yProp = p.getY();
                 }
                 can.drawRipple(xProp, yProp, p.getMaxRadius(), p.getPaint(),
-                        p.getProgress(), p.getShader());
+                        p.getProgress(), p.getNoisePhase(), p.getShader());
             } else {
                 RippleAnimationSession.AnimationProperties<Float, Paint> p =
                         s.getProperties();
@@ -953,7 +944,7 @@
         shader.setRadius(radius);
         shader.setProgress(.0f);
         properties = new RippleAnimationSession.AnimationProperties<>(
-                cx, cy, radius, p, 0f, shader);
+                cx, cy, radius, 0f, p, 0f, shader);
         if (mMaskShader == null) {
             shader.setShader(null);
         } else {
diff --git a/graphics/java/android/graphics/drawable/RippleShader.java b/graphics/java/android/graphics/drawable/RippleShader.java
index 4608d02..c1c6afc 100644
--- a/graphics/java/android/graphics/drawable/RippleShader.java
+++ b/graphics/java/android/graphics/drawable/RippleShader.java
@@ -167,6 +167,9 @@
         final float turbulencePhase = (float) ((mProgress + mNoisePhase * 0.333f) * 5f * Math.PI);
         setUniform("in_turbulencePhase", turbulencePhase);
 
+        //
+        // Keep in sync with: frameworks/base/libs/hwui/pipeline/skia/AnimatedDrawables.h
+        //
         final float scale = 1.5f;
         setUniform("in_tCircle1", new float[]{
                 (float) (scale * 0.5 + (turbulencePhase * 0.01 * Math.cos(scale * 0.55))),
diff --git a/libs/hwui/SkiaCanvas.cpp b/libs/hwui/SkiaCanvas.cpp
index 4c4a152..3056e97 100644
--- a/libs/hwui/SkiaCanvas.cpp
+++ b/libs/hwui/SkiaCanvas.cpp
@@ -820,10 +820,11 @@
                             uirenderer::CanvasPropertyPrimitive* radius,
                             uirenderer::CanvasPropertyPaint* paint,
                             uirenderer::CanvasPropertyPrimitive* progress,
+                            uirenderer::CanvasPropertyPrimitive* turbulencePhase,
                             const SkRuntimeShaderBuilder& effectBuilder) {
     sk_sp<uirenderer::skiapipeline::AnimatedRipple> drawable(
             new uirenderer::skiapipeline::AnimatedRipple(x, y, radius, paint, progress,
-                                                         effectBuilder));
+                                                         turbulencePhase, effectBuilder));
     mCanvas->drawDrawable(drawable.get());
 }
 
diff --git a/libs/hwui/SkiaCanvas.h b/libs/hwui/SkiaCanvas.h
index e0a0be5..995f00c 100644
--- a/libs/hwui/SkiaCanvas.h
+++ b/libs/hwui/SkiaCanvas.h
@@ -153,6 +153,7 @@
                             uirenderer::CanvasPropertyPrimitive* radius,
                             uirenderer::CanvasPropertyPaint* paint,
                             uirenderer::CanvasPropertyPrimitive* progress,
+                            uirenderer::CanvasPropertyPrimitive* turbulencePhase,
                             const SkRuntimeShaderBuilder& effectBuilder) override;
 
     virtual void drawLayer(uirenderer::DeferredLayerUpdater* layerHandle) override;
diff --git a/libs/hwui/canvas/CanvasOps.h b/libs/hwui/canvas/CanvasOps.h
index 855cd0d..173f394 100644
--- a/libs/hwui/canvas/CanvasOps.h
+++ b/libs/hwui/canvas/CanvasOps.h
@@ -152,32 +152,66 @@
     sp<uirenderer::CanvasPropertyPrimitive> radius;
     sp<uirenderer::CanvasPropertyPaint> paint;
     sp<uirenderer::CanvasPropertyPrimitive> progress;
+    sp<uirenderer::CanvasPropertyPrimitive> turbulencePhase;
     sk_sp<SkRuntimeEffect> effect;
 
+    const float PI = 3.1415926535897932384626;
+    const float PI_ROTATE_RIGHT = PI * 0.0078125;
+    const float PI_ROTATE_LEFT = PI * -0.0078125;
+    const float SCALE = 1.5;
+    const float CIRCLE_X_1 = 0.01 * cos(SCALE * 0.55);
+    const float CIRCLE_Y_1 = 0.01 * sin(SCALE * 0.55);
+    const float CIRCLE_X_2 = -0.0066 * cos(SCALE * 0.45);
+    const float CIRCLE_Y_2 = -0.0066 * sin(SCALE * 0.45);
+    const float CIRCLE_X_3 = -0.0066 * cos(SCALE * 0.35);
+    const float CIRCLE_Y_3 = -0.0066 * sin(SCALE * 0.35);
+
     void draw(SkCanvas* canvas) const {
         SkRuntimeShaderBuilder runtimeEffectBuilder(effect);
 
-        SkRuntimeShaderBuilder::BuilderUniform center = runtimeEffectBuilder.uniform("in_origin");
-        if (center.fVar != nullptr) {
-            center = SkV2{x->value, y->value};
-        }
+        setUniform2f(runtimeEffectBuilder, "in_origin", x->value, y->value);
+        setUniform(runtimeEffectBuilder, "in_radius", radius);
+        setUniform(runtimeEffectBuilder, "in_progress", progress);
+        setUniform(runtimeEffectBuilder, "in_turbulencePhase", turbulencePhase);
 
-        SkRuntimeShaderBuilder::BuilderUniform radiusU =
-                runtimeEffectBuilder.uniform("in_radius");
-        if (radiusU.fVar != nullptr) {
-            radiusU = radius->value;
-        }
-
-        SkRuntimeShaderBuilder::BuilderUniform progressU =
-                runtimeEffectBuilder.uniform("in_progress");
-        if (progressU.fVar != nullptr) {
-            progressU = progress->value;
-        }
+        //
+        // Keep in sync with:
+        // frameworks/base/graphics/java/android/graphics/drawable/RippleShader.java
+        //
+        const float turbulence = turbulencePhase->value;
+        setUniform2f(runtimeEffectBuilder, "in_tCircle1", SCALE * 0.5 + (turbulence * CIRCLE_X_1),
+                     SCALE * 0.5 + (turbulence * CIRCLE_Y_1));
+        setUniform2f(runtimeEffectBuilder, "in_tCircle2", SCALE * 0.2 + (turbulence * CIRCLE_X_2),
+                     SCALE * 0.2 + (turbulence * CIRCLE_Y_2));
+        setUniform2f(runtimeEffectBuilder, "in_tCircle3", SCALE + (turbulence * CIRCLE_X_3),
+                     SCALE + (turbulence * CIRCLE_Y_3));
+        const float rotation1 = turbulence * PI_ROTATE_RIGHT + 1.7 * PI;
+        setUniform2f(runtimeEffectBuilder, "in_tRotation1", cos(rotation1), sin(rotation1));
+        const float rotation2 = turbulence * PI_ROTATE_LEFT + 2 * PI;
+        setUniform2f(runtimeEffectBuilder, "in_tRotation2", cos(rotation2), sin(rotation2));
+        const float rotation3 = turbulence * PI_ROTATE_RIGHT + 2.75 * PI;
+        setUniform2f(runtimeEffectBuilder, "in_tRotation3", cos(rotation3), sin(rotation3));
 
         SkPaint paintMod = paint->value;
         paintMod.setShader(runtimeEffectBuilder.makeShader(nullptr, false));
         canvas->drawCircle(x->value, y->value, radius->value, paintMod);
     }
+
+    void setUniform(SkRuntimeShaderBuilder& effect, std::string name,
+                    sp<uirenderer::CanvasPropertyPrimitive> property) const {
+        SkRuntimeShaderBuilder::BuilderUniform uniform = effect.uniform(name.c_str());
+        if (uniform.fVar != nullptr) {
+            uniform = property->value;
+        }
+    }
+
+    void setUniform2f(SkRuntimeShaderBuilder effect, std::string name, float a, float b) const {
+        SkRuntimeShaderBuilder::BuilderUniform uniform = effect.uniform(name.c_str());
+        if (uniform.fVar != nullptr) {
+            uniform = SkV2{a, b};
+        }
+    }
+
     ASSERT_DRAWABLE()
 };
 
diff --git a/libs/hwui/hwui/Canvas.h b/libs/hwui/hwui/Canvas.h
index c1feb76..837b055 100644
--- a/libs/hwui/hwui/Canvas.h
+++ b/libs/hwui/hwui/Canvas.h
@@ -146,6 +146,7 @@
                             uirenderer::CanvasPropertyPrimitive* radius,
                             uirenderer::CanvasPropertyPaint* paint,
                             uirenderer::CanvasPropertyPrimitive* progress,
+                            uirenderer::CanvasPropertyPrimitive* turbulencePhase,
                             const SkRuntimeShaderBuilder& effectBuilder) = 0;
 
     virtual void drawLayer(uirenderer::DeferredLayerUpdater* layerHandle) = 0;
diff --git a/libs/hwui/jni/android_graphics_DisplayListCanvas.cpp b/libs/hwui/jni/android_graphics_DisplayListCanvas.cpp
index 855d56e..eb5a88a 100644
--- a/libs/hwui/jni/android_graphics_DisplayListCanvas.cpp
+++ b/libs/hwui/jni/android_graphics_DisplayListCanvas.cpp
@@ -141,20 +141,22 @@
     canvas->drawCircle(xProp, yProp, radiusProp, paintProp);
 }
 
-static void android_view_DisplayListCanvas_drawRippleProps(CRITICAL_JNI_PARAMS_COMMA jlong canvasPtr,
-                                                           jlong xPropPtr, jlong yPropPtr,
-                                                           jlong radiusPropPtr, jlong paintPropPtr,
-                                                           jlong progressPropPtr,
-                                                           jlong builderPtr) {
+static void android_view_DisplayListCanvas_drawRippleProps(
+        CRITICAL_JNI_PARAMS_COMMA jlong canvasPtr, jlong xPropPtr, jlong yPropPtr,
+        jlong radiusPropPtr, jlong paintPropPtr, jlong progressPropPtr, jlong turbulencePhasePtr,
+        jlong builderPtr) {
     Canvas* canvas = reinterpret_cast<Canvas*>(canvasPtr);
     CanvasPropertyPrimitive* xProp = reinterpret_cast<CanvasPropertyPrimitive*>(xPropPtr);
     CanvasPropertyPrimitive* yProp = reinterpret_cast<CanvasPropertyPrimitive*>(yPropPtr);
     CanvasPropertyPrimitive* radiusProp = reinterpret_cast<CanvasPropertyPrimitive*>(radiusPropPtr);
+    CanvasPropertyPrimitive* turbulencePhaseProp =
+            reinterpret_cast<CanvasPropertyPrimitive*>(turbulencePhasePtr);
     CanvasPropertyPaint* paintProp = reinterpret_cast<CanvasPropertyPaint*>(paintPropPtr);
     CanvasPropertyPrimitive* progressProp =
             reinterpret_cast<CanvasPropertyPrimitive*>(progressPropPtr);
     SkRuntimeShaderBuilder* builder = reinterpret_cast<SkRuntimeShaderBuilder*>(builderPtr);
-    canvas->drawRipple(xProp, yProp, radiusProp, paintProp, progressProp, *builder);
+    canvas->drawRipple(xProp, yProp, radiusProp, paintProp, progressProp, turbulencePhaseProp,
+                       *builder);
 }
 
 static void android_view_DisplayListCanvas_drawWebViewFunctor(CRITICAL_JNI_PARAMS_COMMA jlong canvasPtr, jint functor) {
@@ -169,19 +171,22 @@
 const char* const kClassPathName = "android/graphics/RecordingCanvas";
 
 static JNINativeMethod gMethods[] = {
-    // ------------ @CriticalNative --------------
-    { "nCreateDisplayListCanvas", "(JII)J",     (void*) android_view_DisplayListCanvas_createDisplayListCanvas },
-    { "nResetDisplayListCanvas",  "(JJII)V",    (void*) android_view_DisplayListCanvas_resetDisplayListCanvas },
-    { "nGetMaximumTextureWidth",  "()I",        (void*) android_view_DisplayListCanvas_getMaxTextureSize },
-    { "nGetMaximumTextureHeight", "()I",        (void*) android_view_DisplayListCanvas_getMaxTextureSize },
-    { "nEnableZ",                 "(JZ)V",      (void*) android_view_DisplayListCanvas_enableZ },
-    { "nFinishRecording",         "(JJ)V",      (void*) android_view_DisplayListCanvas_finishRecording },
-    { "nDrawRenderNode",          "(JJ)V",      (void*) android_view_DisplayListCanvas_drawRenderNode },
-    { "nDrawTextureLayer",        "(JJ)V",      (void*) android_view_DisplayListCanvas_drawTextureLayer },
-    { "nDrawCircle",              "(JJJJJ)V",   (void*) android_view_DisplayListCanvas_drawCircleProps },
-    { "nDrawRoundRect",           "(JJJJJJJJ)V",(void*) android_view_DisplayListCanvas_drawRoundRectProps },
-    { "nDrawWebViewFunctor",      "(JI)V",      (void*) android_view_DisplayListCanvas_drawWebViewFunctor },
-    { "nDrawRipple",              "(JJJJJJJ)V", (void*) android_view_DisplayListCanvas_drawRippleProps },
+        // ------------ @CriticalNative --------------
+        {"nCreateDisplayListCanvas", "(JII)J",
+         (void*)android_view_DisplayListCanvas_createDisplayListCanvas},
+        {"nResetDisplayListCanvas", "(JJII)V",
+         (void*)android_view_DisplayListCanvas_resetDisplayListCanvas},
+        {"nGetMaximumTextureWidth", "()I", (void*)android_view_DisplayListCanvas_getMaxTextureSize},
+        {"nGetMaximumTextureHeight", "()I",
+         (void*)android_view_DisplayListCanvas_getMaxTextureSize},
+        {"nEnableZ", "(JZ)V", (void*)android_view_DisplayListCanvas_enableZ},
+        {"nFinishRecording", "(JJ)V", (void*)android_view_DisplayListCanvas_finishRecording},
+        {"nDrawRenderNode", "(JJ)V", (void*)android_view_DisplayListCanvas_drawRenderNode},
+        {"nDrawTextureLayer", "(JJ)V", (void*)android_view_DisplayListCanvas_drawTextureLayer},
+        {"nDrawCircle", "(JJJJJ)V", (void*)android_view_DisplayListCanvas_drawCircleProps},
+        {"nDrawRoundRect", "(JJJJJJJJ)V", (void*)android_view_DisplayListCanvas_drawRoundRectProps},
+        {"nDrawWebViewFunctor", "(JI)V", (void*)android_view_DisplayListCanvas_drawWebViewFunctor},
+        {"nDrawRipple", "(JJJJJJJJ)V", (void*)android_view_DisplayListCanvas_drawRippleProps},
 };
 
 int register_android_view_DisplayListCanvas(JNIEnv* env) {
diff --git a/libs/hwui/pipeline/skia/AnimatedDrawables.h b/libs/hwui/pipeline/skia/AnimatedDrawables.h
index 7859145..7d65be1 100644
--- a/libs/hwui/pipeline/skia/AnimatedDrawables.h
+++ b/libs/hwui/pipeline/skia/AnimatedDrawables.h
@@ -19,6 +19,7 @@
 #include <SkCanvas.h>
 #include <SkDrawable.h>
 #include <SkRuntimeEffect.h>
+#include <math.h>
 #include <utils/RefBase.h>
 #include "CanvasProperty.h"
 
@@ -61,12 +62,14 @@
                    uirenderer::CanvasPropertyPrimitive* radius,
                    uirenderer::CanvasPropertyPaint* paint,
                    uirenderer::CanvasPropertyPrimitive* progress,
+                   uirenderer::CanvasPropertyPrimitive* turbulencePhase,
                    const SkRuntimeShaderBuilder& effectBuilder)
             : mX(x)
             , mY(y)
             , mRadius(radius)
             , mPaint(paint)
             , mProgress(progress)
+            , mTurbulencePhase(turbulencePhase)
             , mRuntimeEffectBuilder(effectBuilder) {}
 
 protected:
@@ -77,22 +80,28 @@
         return SkRect::MakeLTRB(x - radius, y - radius, x + radius, y + radius);
     }
     virtual void onDraw(SkCanvas* canvas) override {
-        SkRuntimeShaderBuilder::BuilderUniform center = mRuntimeEffectBuilder.uniform("in_origin");
-        if (center.fVar != nullptr) {
-            center = SkV2{mX->value, mY->value};
-        }
+        setUniform2f("in_origin", mX->value, mY->value);
+        setUniform("in_radius", mRadius);
+        setUniform("in_progress", mProgress);
+        setUniform("in_turbulencePhase", mTurbulencePhase);
 
-        SkRuntimeShaderBuilder::BuilderUniform radiusU =
-                mRuntimeEffectBuilder.uniform("in_radius");
-        if (radiusU.fVar != nullptr) {
-            radiusU = mRadius->value;
-        }
-
-        SkRuntimeShaderBuilder::BuilderUniform progressU =
-                mRuntimeEffectBuilder.uniform("in_progress");
-        if (progressU.fVar != nullptr) {
-            progressU = mProgress->value;
-        }
+        //
+        // Keep in sync with:
+        // frameworks/base/graphics/java/android/graphics/drawable/RippleShader.java
+        //
+        const float turbulencePhase = mTurbulencePhase->value;
+        setUniform2f("in_tCircle1", SCALE * 0.5 + (turbulencePhase * CIRCLE_X_1),
+                     SCALE * 0.5 + (turbulencePhase * CIRCLE_Y_1));
+        setUniform2f("in_tCircle2", SCALE * 0.2 + (turbulencePhase * CIRCLE_X_2),
+                     SCALE * 0.2 + (turbulencePhase * CIRCLE_Y_2));
+        setUniform2f("in_tCircle3", SCALE + (turbulencePhase * CIRCLE_X_3),
+                     SCALE + (turbulencePhase * CIRCLE_Y_3));
+        const float rotation1 = turbulencePhase * PI_ROTATE_RIGHT + 1.7 * PI;
+        setUniform2f("in_tRotation1", cos(rotation1), sin(rotation1));
+        const float rotation2 = turbulencePhase * PI_ROTATE_LEFT + 2 * PI;
+        setUniform2f("in_tRotation2", cos(rotation2), sin(rotation2));
+        const float rotation3 = turbulencePhase * PI_ROTATE_RIGHT + 2.75 * PI;
+        setUniform2f("in_tRotation3", cos(rotation3), sin(rotation3));
 
         SkPaint paint = mPaint->value;
         paint.setShader(mRuntimeEffectBuilder.makeShader(nullptr, false));
@@ -105,7 +114,35 @@
     sp<uirenderer::CanvasPropertyPrimitive> mRadius;
     sp<uirenderer::CanvasPropertyPaint> mPaint;
     sp<uirenderer::CanvasPropertyPrimitive> mProgress;
+    sp<uirenderer::CanvasPropertyPrimitive> mTurbulencePhase;
     SkRuntimeShaderBuilder mRuntimeEffectBuilder;
+
+    const float PI = 3.1415926535897932384626;
+    const float PI_ROTATE_RIGHT = PI * 0.0078125;
+    const float PI_ROTATE_LEFT = PI * -0.0078125;
+    const float SCALE = 1.5;
+    const float CIRCLE_X_1 = 0.01 * cos(SCALE * 0.55);
+    const float CIRCLE_Y_1 = 0.01 * sin(SCALE * 0.55);
+    const float CIRCLE_X_2 = -0.0066 * cos(SCALE * 0.45);
+    const float CIRCLE_Y_2 = -0.0066 * sin(SCALE * 0.45);
+    const float CIRCLE_X_3 = -0.0066 * cos(SCALE * 0.35);
+    const float CIRCLE_Y_3 = -0.0066 * sin(SCALE * 0.35);
+
+    virtual void setUniform(std::string name, sp<uirenderer::CanvasPropertyPrimitive> property) {
+        SkRuntimeShaderBuilder::BuilderUniform uniform =
+                mRuntimeEffectBuilder.uniform(name.c_str());
+        if (uniform.fVar != nullptr) {
+            uniform = property->value;
+        }
+    }
+
+    virtual void setUniform2f(std::string name, float a, float b) {
+        SkRuntimeShaderBuilder::BuilderUniform uniform =
+                mRuntimeEffectBuilder.uniform(name.c_str());
+        if (uniform.fVar != nullptr) {
+            uniform = SkV2{a, b};
+        }
+    }
 };
 
 class AnimatedCircle : public SkDrawable {
diff --git a/libs/hwui/pipeline/skia/SkiaRecordingCanvas.cpp b/libs/hwui/pipeline/skia/SkiaRecordingCanvas.cpp
index 82814de..a8e427d 100644
--- a/libs/hwui/pipeline/skia/SkiaRecordingCanvas.cpp
+++ b/libs/hwui/pipeline/skia/SkiaRecordingCanvas.cpp
@@ -115,9 +115,10 @@
                                      uirenderer::CanvasPropertyPrimitive* radius,
                                      uirenderer::CanvasPropertyPaint* paint,
                                      uirenderer::CanvasPropertyPrimitive* progress,
+                                     uirenderer::CanvasPropertyPrimitive* turbulencePhase,
                                      const SkRuntimeShaderBuilder& effectBuilder) {
     drawDrawable(mDisplayList->allocateDrawable<AnimatedRipple>(x, y, radius, paint, progress,
-                                                                effectBuilder));
+                                                                turbulencePhase, effectBuilder));
 }
 
 void SkiaRecordingCanvas::enableZ(bool enableZ) {
diff --git a/libs/hwui/pipeline/skia/SkiaRecordingCanvas.h b/libs/hwui/pipeline/skia/SkiaRecordingCanvas.h
index 06f2a27..4deb3b9 100644
--- a/libs/hwui/pipeline/skia/SkiaRecordingCanvas.h
+++ b/libs/hwui/pipeline/skia/SkiaRecordingCanvas.h
@@ -75,6 +75,7 @@
                             uirenderer::CanvasPropertyPrimitive* radius,
                             uirenderer::CanvasPropertyPaint* paint,
                             uirenderer::CanvasPropertyPrimitive* progress,
+                            uirenderer::CanvasPropertyPrimitive* turbulencePhase,
                             const SkRuntimeShaderBuilder& effectBuilder) override;
 
     virtual void drawVectorDrawable(VectorDrawableRoot* vectorDrawable) override;
diff --git a/tests/HwAccelerationTest/src/com/android/test/hwui/RippleActivity.java b/tests/HwAccelerationTest/src/com/android/test/hwui/RippleActivity.java
index f6d9a73..487c856 100644
--- a/tests/HwAccelerationTest/src/com/android/test/hwui/RippleActivity.java
+++ b/tests/HwAccelerationTest/src/com/android/test/hwui/RippleActivity.java
@@ -56,6 +56,7 @@
         CanvasProperty<Float> mY;
         CanvasProperty<Float> mRadius;
         CanvasProperty<Float> mProgress;
+        CanvasProperty<Float> mNoisePhase;
         CanvasProperty<Paint> mPaint;
         RuntimeShader mRuntimeShader;
 
@@ -99,6 +100,7 @@
             mY = CanvasProperty.createFloat(200.0f);
             mRadius = CanvasProperty.createFloat(150.0f);
             mProgress = CanvasProperty.createFloat(0.0f);
+            mNoisePhase = CanvasProperty.createFloat(0.0f);
 
             Paint p = new Paint();
             p.setAntiAlias(true);
@@ -115,7 +117,8 @@
 
             if (canvas.isHardwareAccelerated()) {
                 RecordingCanvas recordingCanvas = (RecordingCanvas) canvas;
-                recordingCanvas.drawRipple(mX, mY, mRadius, mPaint, mProgress, mRuntimeShader);
+                recordingCanvas.drawRipple(mX, mY, mRadius, mPaint, mProgress, mNoisePhase,
+                        mRuntimeShader);
             }
         }
 
@@ -141,6 +144,9 @@
                     mProgress, mToggle ? 1.0f : 0.0f));
 
             mRunningAnimations.add(new RenderNodeAnimator(
+                    mNoisePhase, DURATION));
+
+            mRunningAnimations.add(new RenderNodeAnimator(
                     mPaint, RenderNodeAnimator.PAINT_ALPHA, 64.0f));
 
             // Will be "chained" to run after the above