Adding force-dark support to RippleDrawable

Add force-dark support to RippleDrawable by modifying the ink color.

Test: manual
Test: atest CanvasOp
Fixes: 186130682
Change-Id: I89eaaf3afa5ec53a74d2d08de8cff6484e55a912
diff --git a/graphics/java/android/graphics/RecordingCanvas.java b/graphics/java/android/graphics/RecordingCanvas.java
index e9e58c6..a5184f2 100644
--- a/graphics/java/android/graphics/RecordingCanvas.java
+++ b/graphics/java/android/graphics/RecordingCanvas.java
@@ -16,6 +16,7 @@
 
 package android.graphics;
 
+import android.annotation.ColorInt;
 import android.annotation.NonNull;
 import android.os.SystemProperties;
 import android.util.Pools.SynchronizedPool;
@@ -227,11 +228,11 @@
     public void drawRipple(CanvasProperty<Float> cx, CanvasProperty<Float> cy,
             CanvasProperty<Float> radius, CanvasProperty<Paint> paint,
             CanvasProperty<Float> progress, CanvasProperty<Float> turbulencePhase,
-            RuntimeShader shader) {
+            @ColorInt int color, RuntimeShader shader) {
         nDrawRipple(mNativeCanvasWrapper, cx.getNativeContainer(), cy.getNativeContainer(),
                 radius.getNativeContainer(), paint.getNativeContainer(),
                 progress.getNativeContainer(), turbulencePhase.getNativeContainer(),
-                shader.getNativeShaderBuilder());
+                color, shader.getNativeShaderBuilder());
     }
 
     /**
@@ -292,7 +293,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 turbulencePhase, long runtimeEffect);
+            long propPaint, long propProgress, long turbulencePhase, int color, 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 60f73b5..ee867dd 100644
--- a/graphics/java/android/graphics/drawable/RippleAnimationSession.java
+++ b/graphics/java/android/graphics/drawable/RippleAnimationSession.java
@@ -19,6 +19,7 @@
 import android.animation.Animator;
 import android.animation.TimeInterpolator;
 import android.animation.ValueAnimator;
+import android.annotation.ColorInt;
 import android.annotation.NonNull;
 import android.annotation.Nullable;
 import android.graphics.Canvas;
@@ -211,6 +212,7 @@
                     CanvasProperty.createFloat(mProperties.getNoisePhase()),
                     CanvasProperty.createPaint(mProperties.getPaint()),
                     CanvasProperty.createFloat(mProperties.getProgress()),
+                    mProperties.getColor(),
                     mProperties.getShader());
         }
         return mCanvasProperties;
@@ -250,11 +252,12 @@
         private final FloatType mNoisePhase;
         private final PaintType mPaint;
         private final RippleShader mShader;
+        private final @ColorInt int mColor;
         private FloatType mX;
         private FloatType mY;
 
         AnimationProperties(FloatType x, FloatType y, FloatType maxRadius, FloatType noisePhase,
-                PaintType paint, FloatType progress, RippleShader shader) {
+                PaintType paint, FloatType progress, @ColorInt int color, RippleShader shader) {
             mY = y;
             mX = x;
             mMaxRadius = maxRadius;
@@ -262,6 +265,7 @@
             mPaint = paint;
             mShader = shader;
             mProgress = progress;
+            mColor = color;
         }
 
         FloatType getProgress() {
@@ -296,5 +300,9 @@
         FloatType getNoisePhase() {
             return mNoisePhase;
         }
+
+        @ColorInt int getColor() {
+            return mColor;
+        }
     }
 }
diff --git a/graphics/java/android/graphics/drawable/RippleDrawable.java b/graphics/java/android/graphics/drawable/RippleDrawable.java
index 895cd3e..d0fb67a 100644
--- a/graphics/java/android/graphics/drawable/RippleDrawable.java
+++ b/graphics/java/android/graphics/drawable/RippleDrawable.java
@@ -902,7 +902,7 @@
                     yProp = p.getY();
                 }
                 can.drawRipple(xProp, yProp, p.getMaxRadius(), p.getPaint(),
-                        p.getProgress(), p.getNoisePhase(), p.getShader());
+                        p.getProgress(), p.getNoisePhase(), p.getColor(), p.getShader());
             } else {
                 RippleAnimationSession.AnimationProperties<Float, Paint> p =
                         s.getProperties();
@@ -975,7 +975,7 @@
         shader.setRadius(radius);
         shader.setProgress(.0f);
         properties = new RippleAnimationSession.AnimationProperties<>(
-                cx, cy, radius, 0f, p, 0f, shader);
+                cx, cy, radius, 0f, p, 0f, color, shader);
         if (mMaskShader == null) {
             shader.setShader(null);
         } else {
diff --git a/libs/hwui/CanvasTransform.cpp b/libs/hwui/CanvasTransform.cpp
index 9d03ce5..d0d24a8 100644
--- a/libs/hwui/CanvasTransform.cpp
+++ b/libs/hwui/CanvasTransform.cpp
@@ -31,7 +31,7 @@
 
 namespace android::uirenderer {
 
-static SkColor makeLight(SkColor color) {
+SkColor makeLight(SkColor color) {
     Lab lab = sRGBToLab(color);
     float invertedL = std::min(110 - lab.L, 100.0f);
     if (invertedL > lab.L) {
@@ -42,7 +42,7 @@
     }
 }
 
-static SkColor makeDark(SkColor color) {
+SkColor makeDark(SkColor color) {
     Lab lab = sRGBToLab(color);
     float invertedL = std::min(110 - lab.L, 100.0f);
     if (invertedL < lab.L) {
@@ -53,7 +53,7 @@
     }
 }
 
-static SkColor transformColor(ColorTransform transform, SkColor color) {
+SkColor transformColor(ColorTransform transform, SkColor color) {
     switch (transform) {
         case ColorTransform::Light:
             return makeLight(color);
@@ -64,6 +64,17 @@
     }
 }
 
+SkColor transformColorInverse(ColorTransform transform, SkColor color) {
+    switch (transform) {
+        case ColorTransform::Dark:
+            return makeLight(color);
+        case ColorTransform::Light:
+            return makeDark(color);
+        default:
+            return color;
+    }
+}
+
 static void applyColorTransform(ColorTransform transform, SkPaint& paint) {
     if (transform == ColorTransform::None) return;
 
diff --git a/libs/hwui/CanvasTransform.h b/libs/hwui/CanvasTransform.h
index e723d64..c46a2d3 100644
--- a/libs/hwui/CanvasTransform.h
+++ b/libs/hwui/CanvasTransform.h
@@ -42,4 +42,7 @@
 
 bool transformPaint(ColorTransform transform, SkPaint* paint, BitmapPalette palette);
 
+SkColor transformColor(ColorTransform transform, SkColor color);
+SkColor transformColorInverse(ColorTransform transform, SkColor color);
+
 }  // namespace android::uirenderer;
\ No newline at end of file
diff --git a/libs/hwui/DisplayListOps.in b/libs/hwui/DisplayListOps.in
index 1b1be43..fb3e21f 100644
--- a/libs/hwui/DisplayListOps.in
+++ b/libs/hwui/DisplayListOps.in
@@ -49,4 +49,5 @@
 X(DrawAtlas)
 X(DrawShadowRec)
 X(DrawVectorDrawable)
+X(DrawRippleDrawable)
 X(DrawWebView)
diff --git a/libs/hwui/RecordingCanvas.cpp b/libs/hwui/RecordingCanvas.cpp
index 170f731..442ae0f 100644
--- a/libs/hwui/RecordingCanvas.cpp
+++ b/libs/hwui/RecordingCanvas.cpp
@@ -36,6 +36,7 @@
 #include "SkTextBlob.h"
 #include "SkVertices.h"
 #include "VectorDrawable.h"
+#include "pipeline/skia/AnimatedDrawables.h"
 #include "pipeline/skia/FunctorDrawable.h"
 
 namespace android {
@@ -497,6 +498,18 @@
     SkPaint paint;
     BitmapPalette palette;
 };
+
+struct DrawRippleDrawable final : Op {
+    static const auto kType = Type::DrawRippleDrawable;
+    DrawRippleDrawable(const skiapipeline::RippleDrawableParams& params) : mParams(params) {}
+
+    void draw(SkCanvas* canvas, const SkMatrix&) const {
+        skiapipeline::AnimatedRippleDrawable::draw(canvas, mParams);
+    }
+
+    skiapipeline::RippleDrawableParams mParams;
+};
+
 struct DrawWebView final : Op {
     static const auto kType = Type::DrawWebView;
     DrawWebView(skiapipeline::FunctorDrawable* drawable) : drawable(sk_ref_sp(drawable)) {}
@@ -721,6 +734,10 @@
     mHasText = true;
 }
 
+void DisplayListData::drawRippleDrawable(const skiapipeline::RippleDrawableParams& params) {
+    this->push<DrawRippleDrawable>(0, params);
+}
+
 void DisplayListData::drawPatch(const SkPoint points[12], const SkColor colors[4],
                                 const SkPoint texs[4], SkBlendMode bmode, const SkPaint& paint) {
     this->push<DrawPatch>(0, points, colors, texs, bmode, paint);
@@ -851,6 +868,16 @@
     };
 }
 
+template <>
+constexpr color_transform_fn colorTransformForOp<DrawRippleDrawable>() {
+    return [](const void* opRaw, ColorTransform transform) {
+        const DrawRippleDrawable* op = reinterpret_cast<const DrawRippleDrawable*>(opRaw);
+        // Ripple drawable needs to contrast against the background, so we need the inverse color.
+        SkColor color = transformColorInverse(transform, op->mParams.color);
+        const_cast<DrawRippleDrawable*>(op)->mParams.color = color;
+    };
+}
+
 #define X(T) colorTransformForOp<T>(),
 static const color_transform_fn color_transform_fns[] = {
 #include "DisplayListOps.in"
@@ -985,6 +1012,10 @@
     fDL->drawTextBlob(blob, x, y, paint);
 }
 
+void RecordingCanvas::drawRippleDrawable(const skiapipeline::RippleDrawableParams& params) {
+    fDL->drawRippleDrawable(params);
+}
+
 void RecordingCanvas::drawImage(const sk_sp<SkImage>& image, SkScalar x, SkScalar y,
                                 const SkSamplingOptions& sampling, const SkPaint* paint,
                                 BitmapPalette palette) {
diff --git a/libs/hwui/RecordingCanvas.h b/libs/hwui/RecordingCanvas.h
index a6a7b12..4fae6a1 100644
--- a/libs/hwui/RecordingCanvas.h
+++ b/libs/hwui/RecordingCanvas.h
@@ -29,6 +29,9 @@
 #include "SkPath.h"
 #include "SkRect.h"
 
+#include "pipeline/skia/AnimatedDrawables.h"
+
+#include <SkRuntimeEffect.h>
 #include <vector>
 
 namespace android {
@@ -125,6 +128,7 @@
     void drawVertices(const SkVertices*, SkBlendMode, const SkPaint&);
     void drawAtlas(const SkImage*, const SkRSXform[], const SkRect[], const SkColor[], int,
                    SkBlendMode, const SkSamplingOptions&, const SkRect*, const SkPaint*);
+    void drawRippleDrawable(const skiapipeline::RippleDrawableParams& params);
     void drawShadowRec(const SkPath&, const SkDrawShadowRec&);
     void drawVectorDrawable(VectorDrawableRoot* tree);
     void drawWebView(skiapipeline::FunctorDrawable*);
@@ -184,6 +188,7 @@
 
     void drawImage(const sk_sp<SkImage>&, SkScalar left, SkScalar top, const SkSamplingOptions&,
                    const SkPaint* paint, BitmapPalette pallete);
+    void drawRippleDrawable(const skiapipeline::RippleDrawableParams& params);
 
     void drawImageRect(const sk_sp<SkImage>& image, const SkRect& src, const SkRect& dst,
                        const SkSamplingOptions&, const SkPaint*, SrcRectConstraint, BitmapPalette);
diff --git a/libs/hwui/SkiaCanvas.cpp b/libs/hwui/SkiaCanvas.cpp
index 3056e97..d032e2b 100644
--- a/libs/hwui/SkiaCanvas.cpp
+++ b/libs/hwui/SkiaCanvas.cpp
@@ -815,17 +815,8 @@
     mCanvas->drawDrawable(drawable.get());
 }
 
-void SkiaCanvas::drawRipple(uirenderer::CanvasPropertyPrimitive* x,
-                            uirenderer::CanvasPropertyPrimitive* y,
-                            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,
-                                                         turbulencePhase, effectBuilder));
-    mCanvas->drawDrawable(drawable.get());
+void SkiaCanvas::drawRipple(const uirenderer::skiapipeline::RippleDrawableParams& params) {
+    uirenderer::skiapipeline::AnimatedRippleDrawable::draw(mCanvas, params);
 }
 
 void SkiaCanvas::drawPicture(const SkPicture& picture) {
diff --git a/libs/hwui/SkiaCanvas.h b/libs/hwui/SkiaCanvas.h
index 995f00c..438a40c 100644
--- a/libs/hwui/SkiaCanvas.h
+++ b/libs/hwui/SkiaCanvas.h
@@ -25,6 +25,7 @@
 #include "hwui/Paint.h"
 
 #include <SkCanvas.h>
+#include "pipeline/skia/AnimatedDrawables.h"
 #include "src/core/SkArenaAlloc.h"
 
 #include <cassert>
@@ -148,13 +149,7 @@
                             uirenderer::CanvasPropertyPrimitive* y,
                             uirenderer::CanvasPropertyPrimitive* radius,
                             uirenderer::CanvasPropertyPaint* paint) override;
-    virtual void drawRipple(uirenderer::CanvasPropertyPrimitive* x,
-                            uirenderer::CanvasPropertyPrimitive* y,
-                            uirenderer::CanvasPropertyPrimitive* radius,
-                            uirenderer::CanvasPropertyPaint* paint,
-                            uirenderer::CanvasPropertyPrimitive* progress,
-                            uirenderer::CanvasPropertyPrimitive* turbulencePhase,
-                            const SkRuntimeShaderBuilder& effectBuilder) override;
+    virtual void drawRipple(const uirenderer::skiapipeline::RippleDrawableParams& params) override;
 
     virtual void drawLayer(uirenderer::DeferredLayerUpdater* layerHandle) override;
     virtual void drawRenderNode(uirenderer::RenderNode* renderNode) override;
diff --git a/libs/hwui/canvas/CanvasOpTypes.h b/libs/hwui/canvas/CanvasOpTypes.h
index b55ef9d..6e18e49 100644
--- a/libs/hwui/canvas/CanvasOpTypes.h
+++ b/libs/hwui/canvas/CanvasOpTypes.h
@@ -43,7 +43,7 @@
     DrawRoundRectProperty,
     DrawDoubleRoundRect,
     DrawCircleProperty,
-    DrawRippleProperty,
+    DrawRippleDrawable,
     DrawCircle,
     DrawOval,
     DrawArc,
diff --git a/libs/hwui/canvas/CanvasOps.h b/libs/hwui/canvas/CanvasOps.h
index 173f394..fdc97a4 100644
--- a/libs/hwui/canvas/CanvasOps.h
+++ b/libs/hwui/canvas/CanvasOps.h
@@ -145,73 +145,13 @@
     ASSERT_DRAWABLE()
 };
 
-template<>
-struct CanvasOp<CanvasOpType::DrawRippleProperty> {
-    sp<uirenderer::CanvasPropertyPrimitive> x;
-    sp<uirenderer::CanvasPropertyPrimitive> y;
-    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);
+template <>
+struct CanvasOp<CanvasOpType::DrawRippleDrawable> {
+    skiapipeline::RippleDrawableParams params;
 
     void draw(SkCanvas* canvas) const {
-        SkRuntimeShaderBuilder runtimeEffectBuilder(effect);
-
-        setUniform2f(runtimeEffectBuilder, "in_origin", x->value, y->value);
-        setUniform(runtimeEffectBuilder, "in_radius", radius);
-        setUniform(runtimeEffectBuilder, "in_progress", progress);
-        setUniform(runtimeEffectBuilder, "in_turbulencePhase", turbulencePhase);
-
-        //
-        // 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);
+        skiapipeline::AnimatedRippleDrawable::draw(canvas, params);
     }
-
-    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 837b055..9023613 100644
--- a/libs/hwui/hwui/Canvas.h
+++ b/libs/hwui/hwui/Canvas.h
@@ -22,6 +22,7 @@
 
 #include <androidfw/ResourceTypes.h>
 #include "Properties.h"
+#include "pipeline/skia/AnimatedDrawables.h"
 #include "utils/Macros.h"
 
 #include <SkBitmap.h>
@@ -141,13 +142,7 @@
                             uirenderer::CanvasPropertyPrimitive* y,
                             uirenderer::CanvasPropertyPrimitive* radius,
                             uirenderer::CanvasPropertyPaint* paint) = 0;
-    virtual void drawRipple(uirenderer::CanvasPropertyPrimitive* x,
-                            uirenderer::CanvasPropertyPrimitive* y,
-                            uirenderer::CanvasPropertyPrimitive* radius,
-                            uirenderer::CanvasPropertyPaint* paint,
-                            uirenderer::CanvasPropertyPrimitive* progress,
-                            uirenderer::CanvasPropertyPrimitive* turbulencePhase,
-                            const SkRuntimeShaderBuilder& effectBuilder) = 0;
+    virtual void drawRipple(const uirenderer::skiapipeline::RippleDrawableParams& params) = 0;
 
     virtual void drawLayer(uirenderer::DeferredLayerUpdater* layerHandle) = 0;
     virtual void drawRenderNode(uirenderer::RenderNode* renderNode) = 0;
diff --git a/libs/hwui/jni/android_graphics_DisplayListCanvas.cpp b/libs/hwui/jni/android_graphics_DisplayListCanvas.cpp
index eb5a88a..f060bb3 100644
--- a/libs/hwui/jni/android_graphics_DisplayListCanvas.cpp
+++ b/libs/hwui/jni/android_graphics_DisplayListCanvas.cpp
@@ -144,7 +144,7 @@
 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) {
+        jint color, jlong builderPtr) {
     Canvas* canvas = reinterpret_cast<Canvas*>(canvasPtr);
     CanvasPropertyPrimitive* xProp = reinterpret_cast<CanvasPropertyPrimitive*>(xPropPtr);
     CanvasPropertyPrimitive* yProp = reinterpret_cast<CanvasPropertyPrimitive*>(yPropPtr);
@@ -155,8 +155,12 @@
     CanvasPropertyPrimitive* progressProp =
             reinterpret_cast<CanvasPropertyPrimitive*>(progressPropPtr);
     SkRuntimeShaderBuilder* builder = reinterpret_cast<SkRuntimeShaderBuilder*>(builderPtr);
-    canvas->drawRipple(xProp, yProp, radiusProp, paintProp, progressProp, turbulencePhaseProp,
-                       *builder);
+
+    const uirenderer::skiapipeline::RippleDrawableParams params =
+            uirenderer::skiapipeline::RippleDrawableParams{
+                    xProp,          yProp,     radiusProp, progressProp, turbulencePhaseProp,
+                    (SkColor)color, paintProp, *builder};
+    canvas->drawRipple(params);
 }
 
 static void android_view_DisplayListCanvas_drawWebViewFunctor(CRITICAL_JNI_PARAMS_COMMA jlong canvasPtr, jint functor) {
@@ -186,7 +190,7 @@
         {"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},
+        {"nDrawRipple", "(JJJJJJJIJ)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 7d65be1..10889e7 100644
--- a/libs/hwui/pipeline/skia/AnimatedDrawables.h
+++ b/libs/hwui/pipeline/skia/AnimatedDrawables.h
@@ -22,6 +22,7 @@
 #include <math.h>
 #include <utils/RefBase.h>
 #include "CanvasProperty.h"
+#include "CanvasTransform.h"
 
 namespace android {
 namespace uirenderer {
@@ -56,89 +57,80 @@
     sp<uirenderer::CanvasPropertyPaint> mPaint;
 };
 
-class AnimatedRipple : public SkDrawable {
-public:
-    AnimatedRipple(uirenderer::CanvasPropertyPrimitive* x, uirenderer::CanvasPropertyPrimitive* y,
-                   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) {}
+struct RippleDrawableParams {
+    sp<uirenderer::CanvasPropertyPrimitive> x;
+    sp<uirenderer::CanvasPropertyPrimitive> y;
+    sp<uirenderer::CanvasPropertyPrimitive> radius;
+    sp<uirenderer::CanvasPropertyPrimitive> progress;
+    sp<uirenderer::CanvasPropertyPrimitive> turbulencePhase;
+    SkColor color;
+    sp<uirenderer::CanvasPropertyPaint> paint;
+    SkRuntimeShaderBuilder effectBuilder;
+};
 
-protected:
-    virtual SkRect onGetBounds() override {
-        const float x = mX->value;
-        const float y = mY->value;
-        const float radius = mRadius->value;
-        return SkRect::MakeLTRB(x - radius, y - radius, x + radius, y + radius);
-    }
-    virtual void onDraw(SkCanvas* canvas) override {
-        setUniform2f("in_origin", mX->value, mY->value);
-        setUniform("in_radius", mRadius);
-        setUniform("in_progress", mProgress);
-        setUniform("in_turbulencePhase", mTurbulencePhase);
+class AnimatedRippleDrawable {
+public:
+    static void draw(SkCanvas* canvas, const RippleDrawableParams& params) {
+        auto& effectBuilder = const_cast<SkRuntimeShaderBuilder&>(params.effectBuilder);
+
+        setUniform2f(effectBuilder, "in_origin", params.x->value, params.y->value);
+        setUniform(effectBuilder, "in_radius", params.radius);
+        setUniform(effectBuilder, "in_progress", params.progress);
+        setUniform(effectBuilder, "in_turbulencePhase", params.turbulencePhase);
+
+        SkRuntimeShaderBuilder::BuilderUniform uniform = effectBuilder.uniform("in_color");
+        if (uniform.fVar != nullptr) {
+            uniform = SkV4{SkColorGetR(params.color) / 255.0f, SkColorGetG(params.color) / 255.0f,
+                           SkColorGetB(params.color) / 255.0f, SkColorGetA(params.color) / 255.0f};
+        }
+
+        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);
 
         //
         // 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),
+        const float turbulencePhase = params.turbulencePhase->value;
+        setUniform2f(effectBuilder, "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),
+        setUniform2f(effectBuilder, "in_tCircle2", SCALE * 0.2 + (turbulencePhase * CIRCLE_X_2),
                      SCALE * 0.2 + (turbulencePhase * CIRCLE_Y_2));
-        setUniform2f("in_tCircle3", SCALE + (turbulencePhase * CIRCLE_X_3),
+        setUniform2f(effectBuilder, "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));
+        setUniform2f(effectBuilder, "in_tRotation1", cos(rotation1), sin(rotation1));
         const float rotation2 = turbulencePhase * PI_ROTATE_LEFT + 2 * PI;
-        setUniform2f("in_tRotation2", cos(rotation2), sin(rotation2));
+        setUniform2f(effectBuilder, "in_tRotation2", cos(rotation2), sin(rotation2));
         const float rotation3 = turbulencePhase * PI_ROTATE_RIGHT + 2.75 * PI;
-        setUniform2f("in_tRotation3", cos(rotation3), sin(rotation3));
+        setUniform2f(effectBuilder, "in_tRotation3", cos(rotation3), sin(rotation3));
 
-        SkPaint paint = mPaint->value;
-        paint.setShader(mRuntimeEffectBuilder.makeShader(nullptr, false));
-        canvas->drawCircle(mX->value, mY->value, mRadius->value, paint);
+        params.paint->value.setShader(effectBuilder.makeShader(nullptr, false));
+        canvas->drawCircle(params.x->value, params.y->value, params.radius->value,
+                           params.paint->value);
     }
 
 private:
-    sp<uirenderer::CanvasPropertyPrimitive> mX;
-    sp<uirenderer::CanvasPropertyPrimitive> mY;
-    sp<uirenderer::CanvasPropertyPrimitive> mRadius;
-    sp<uirenderer::CanvasPropertyPaint> mPaint;
-    sp<uirenderer::CanvasPropertyPrimitive> mProgress;
-    sp<uirenderer::CanvasPropertyPrimitive> mTurbulencePhase;
-    SkRuntimeShaderBuilder mRuntimeEffectBuilder;
+    static constexpr float PI = 3.1415926535897932384626;
+    static constexpr float PI_ROTATE_RIGHT = PI * 0.0078125;
+    static constexpr float PI_ROTATE_LEFT = PI * -0.0078125;
+    static constexpr float SCALE = 1.5;
 
-    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());
+    static void setUniform(SkRuntimeShaderBuilder& effectBuilder, std::string name,
+                           sp<uirenderer::CanvasPropertyPrimitive> property) {
+        SkRuntimeShaderBuilder::BuilderUniform uniform = effectBuilder.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());
+    static void setUniform2f(SkRuntimeShaderBuilder& effectBuilder, std::string name, float a,
+                             float b) {
+        SkRuntimeShaderBuilder::BuilderUniform uniform = effectBuilder.uniform(name.c_str());
         if (uniform.fVar != nullptr) {
             uniform = SkV2{a, b};
         }
diff --git a/libs/hwui/pipeline/skia/SkiaRecordingCanvas.cpp b/libs/hwui/pipeline/skia/SkiaRecordingCanvas.cpp
index 9e73f04..76c4a03 100644
--- a/libs/hwui/pipeline/skia/SkiaRecordingCanvas.cpp
+++ b/libs/hwui/pipeline/skia/SkiaRecordingCanvas.cpp
@@ -109,15 +109,8 @@
     drawDrawable(mDisplayList->allocateDrawable<AnimatedCircle>(x, y, radius, paint));
 }
 
-void SkiaRecordingCanvas::drawRipple(uirenderer::CanvasPropertyPrimitive* x,
-                                     uirenderer::CanvasPropertyPrimitive* y,
-                                     uirenderer::CanvasPropertyPrimitive* radius,
-                                     uirenderer::CanvasPropertyPaint* paint,
-                                     uirenderer::CanvasPropertyPrimitive* progress,
-                                     uirenderer::CanvasPropertyPrimitive* turbulencePhase,
-                                     const SkRuntimeShaderBuilder& effectBuilder) {
-    drawDrawable(mDisplayList->allocateDrawable<AnimatedRipple>(x, y, radius, paint, progress,
-                                                                turbulencePhase, effectBuilder));
+void SkiaRecordingCanvas::drawRipple(const skiapipeline::RippleDrawableParams& params) {
+    mRecorder.drawRippleDrawable(params);
 }
 
 void SkiaRecordingCanvas::enableZ(bool enableZ) {
diff --git a/libs/hwui/pipeline/skia/SkiaRecordingCanvas.h b/libs/hwui/pipeline/skia/SkiaRecordingCanvas.h
index 4deb3b9..1445a27 100644
--- a/libs/hwui/pipeline/skia/SkiaRecordingCanvas.h
+++ b/libs/hwui/pipeline/skia/SkiaRecordingCanvas.h
@@ -20,6 +20,7 @@
 #include "ReorderBarrierDrawables.h"
 #include "SkiaCanvas.h"
 #include "SkiaDisplayList.h"
+#include "pipeline/skia/AnimatedDrawables.h"
 
 namespace android {
 namespace uirenderer {
@@ -70,13 +71,7 @@
                             uirenderer::CanvasPropertyPrimitive* y,
                             uirenderer::CanvasPropertyPrimitive* radius,
                             uirenderer::CanvasPropertyPaint* paint) override;
-    virtual void drawRipple(uirenderer::CanvasPropertyPrimitive* x,
-                            uirenderer::CanvasPropertyPrimitive* y,
-                            uirenderer::CanvasPropertyPrimitive* radius,
-                            uirenderer::CanvasPropertyPaint* paint,
-                            uirenderer::CanvasPropertyPrimitive* progress,
-                            uirenderer::CanvasPropertyPrimitive* turbulencePhase,
-                            const SkRuntimeShaderBuilder& effectBuilder) override;
+    virtual void drawRipple(const RippleDrawableParams& params) override;
 
     virtual void drawVectorDrawable(VectorDrawableRoot* vectorDrawable) override;
 
diff --git a/libs/hwui/tests/unit/CanvasOpTests.cpp b/libs/hwui/tests/unit/CanvasOpTests.cpp
index a718d46..2cf3456 100644
--- a/libs/hwui/tests/unit/CanvasOpTests.cpp
+++ b/libs/hwui/tests/unit/CanvasOpTests.cpp
@@ -31,6 +31,7 @@
 
 using namespace android;
 using namespace android::uirenderer;
+using namespace android::uirenderer::skiapipeline;
 using namespace android::uirenderer::test;
 
 // We lazy
@@ -569,6 +570,33 @@
     EXPECT_EQ(2, canvas.sumTotalDrawCalls());
 }
 
+TEST(CanvasOp, simpleDrawRipple) {
+    CanvasOpBuffer buffer;
+    EXPECT_EQ(buffer.size(), 0);
+
+    const char* sksl =
+            "half4 main(float2 coord) {"
+            "  return half4(1.);"
+            "}";
+    auto [effect, error] = SkRuntimeEffect::MakeForShader(SkString(sksl));
+    auto params = RippleDrawableParams{
+            .x = sp<CanvasPropertyPrimitive>(new CanvasPropertyPrimitive(100)),
+            .y = sp<CanvasPropertyPrimitive>(new CanvasPropertyPrimitive(200)),
+            .radius = sp<CanvasPropertyPrimitive>(new CanvasPropertyPrimitive(50)),
+            .progress = sp<CanvasPropertyPrimitive>(new CanvasPropertyPrimitive(0.5)),
+            .turbulencePhase = sp<CanvasPropertyPrimitive>(new CanvasPropertyPrimitive(1)),
+            .color = 0xff00ff,
+            .paint = sp<CanvasPropertyPaint>(new CanvasPropertyPaint(SkPaint{})),
+            .effectBuilder = SkRuntimeShaderBuilder(effect)};
+    buffer.push<Op::DrawRippleDrawable>({.params = params});
+
+    CallCountingCanvas canvas;
+    EXPECT_EQ(0, canvas.sumTotalDrawCalls());
+    rasterizeCanvasBuffer(buffer, &canvas);
+    EXPECT_EQ(1, canvas.drawOvalCount);
+    EXPECT_EQ(1, canvas.sumTotalDrawCalls());
+}
+
 TEST(CanvasOp, immediateRendering) {
     auto canvas = std::make_shared<CallCountingCanvas>();
 
diff --git a/tests/HwAccelerationTest/src/com/android/test/hwui/RippleActivity.java b/tests/HwAccelerationTest/src/com/android/test/hwui/RippleActivity.java
index 79410cf..d925541 100644
--- a/tests/HwAccelerationTest/src/com/android/test/hwui/RippleActivity.java
+++ b/tests/HwAccelerationTest/src/com/android/test/hwui/RippleActivity.java
@@ -20,6 +20,7 @@
 import android.content.Context;
 import android.graphics.Canvas;
 import android.graphics.CanvasProperty;
+import android.graphics.Color;
 import android.graphics.Paint;
 import android.graphics.RecordingCanvas;
 import android.graphics.RuntimeShader;
@@ -48,6 +49,7 @@
     static class RippleView extends View {
         static final int DURATION = 1000;
         static final int MAX_RADIUS = 250;
+        private final int mColor = Color.RED;
 
         private boolean mToggle = false;
         ArrayList<RenderNodeAnimator> mRunningAnimations = new ArrayList<RenderNodeAnimator>();
@@ -104,7 +106,7 @@
 
             Paint p = new Paint();
             p.setAntiAlias(true);
-            p.setColor(0xFFFF0000);
+            p.setColor(mColor);
             mPaint = CanvasProperty.createPaint(p);
 
             mRuntimeShader = new RuntimeShader(sSkSL, false);
@@ -118,7 +120,7 @@
             if (canvas.isHardwareAccelerated()) {
                 RecordingCanvas recordingCanvas = (RecordingCanvas) canvas;
                 recordingCanvas.drawRipple(mX, mY, mRadius, mPaint, mProgress, mNoisePhase,
-                        mRuntimeShader);
+                        mColor, mRuntimeShader);
             }
         }