add clipShader API to android.graphics.Canvas

Test: atest CtsUiRenderingTestCases:android.uirendering.cts.testclasses.ShaderClippingTests
Bug: 280116960

Change-Id: Ic78c4b2ca01e6af9e5af757aa64e15e248cadcf8
diff --git a/libs/hwui/SkiaCanvas.cpp b/libs/hwui/SkiaCanvas.cpp
index 14b8d8d..0b739c3 100644
--- a/libs/hwui/SkiaCanvas.cpp
+++ b/libs/hwui/SkiaCanvas.cpp
@@ -70,6 +70,8 @@
             : mType(Type::RRect), mOp(op), mMatrix(m), mRRect(rrect) {}
     Clip(const SkPath& path, SkClipOp op, const SkMatrix& m)
             : mType(Type::Path), mOp(op), mMatrix(m), mPath(std::in_place, path) {}
+    Clip(const sk_sp<SkShader> shader, SkClipOp op, const SkMatrix& m)
+            : mType(Type::Shader), mOp(op), mMatrix(m), mShader(shader) {}
 
     void apply(SkCanvas* canvas) const {
         canvas->setMatrix(mMatrix);
@@ -86,6 +88,8 @@
                 // Ensure path clips are anti-aliased
                 canvas->clipPath(mPath.value(), mOp, true);
                 break;
+            case Type::Shader:
+                canvas->clipShader(mShader, mOp);
         }
     }
 
@@ -94,6 +98,7 @@
         Rect,
         RRect,
         Path,
+        Shader,
     };
 
     Type mType;
@@ -103,6 +108,7 @@
     // These are logically a union (tracked separately due to non-POD path).
     std::optional<SkPath> mPath;
     SkRRect mRRect;
+    sk_sp<SkShader> mShader;
 };
 
 Canvas* Canvas::create_canvas(const SkBitmap& bitmap) {
@@ -413,6 +419,11 @@
     return !mCanvas->isClipEmpty();
 }
 
+void SkiaCanvas::clipShader(sk_sp<SkShader> shader, SkClipOp op) {
+    this->recordClip(shader, op);
+    mCanvas->clipShader(shader, op);
+}
+
 bool SkiaCanvas::replaceClipRect_deprecated(float left, float top, float right, float bottom) {
     SkRect rect = SkRect::MakeLTRB(left, top, right, bottom);
 
diff --git a/libs/hwui/SkiaCanvas.h b/libs/hwui/SkiaCanvas.h
index 5e3553b..4a012bc 100644
--- a/libs/hwui/SkiaCanvas.h
+++ b/libs/hwui/SkiaCanvas.h
@@ -97,6 +97,7 @@
     virtual bool quickRejectPath(const SkPath& path) const override;
     virtual bool clipRect(float left, float top, float right, float bottom, SkClipOp op) override;
     virtual bool clipPath(const SkPath* path, SkClipOp op) override;
+    virtual void clipShader(sk_sp<SkShader> shader, SkClipOp op) override;
     virtual bool replaceClipRect_deprecated(float left, float top, float right,
                                             float bottom) override;
     virtual bool replaceClipPath_deprecated(const SkPath* path) override;
diff --git a/libs/hwui/hwui/Canvas.h b/libs/hwui/hwui/Canvas.h
index 20e3ad2..14b4f58 100644
--- a/libs/hwui/hwui/Canvas.h
+++ b/libs/hwui/hwui/Canvas.h
@@ -188,6 +188,7 @@
 
     virtual bool clipRect(float left, float top, float right, float bottom, SkClipOp op) = 0;
     virtual bool clipPath(const SkPath* path, SkClipOp op) = 0;
+    virtual void clipShader(sk_sp<SkShader> shader, SkClipOp op) = 0;
     // Resets clip to wide open, used to emulate the now-removed SkClipOp::kReplace on
     // apps with compatibility < P. Canvases for version P and later are restricted to
     // intersect and difference at the Java level, matching SkClipOp's definition.
diff --git a/libs/hwui/jni/android_graphics_Canvas.cpp b/libs/hwui/jni/android_graphics_Canvas.cpp
index e5bdeee..1fc34d6 100644
--- a/libs/hwui/jni/android_graphics_Canvas.cpp
+++ b/libs/hwui/jni/android_graphics_Canvas.cpp
@@ -261,6 +261,23 @@
     return nonEmptyClip ? JNI_TRUE : JNI_FALSE;
 }
 
+static void clipShader(CRITICAL_JNI_PARAMS_COMMA jlong canvasHandle, jlong shaderHandle,
+                       jint opHandle) {
+    SkRegion::Op rgnOp = static_cast<SkRegion::Op>(opHandle);
+    sk_sp<SkShader> shader = sk_ref_sp(reinterpret_cast<SkShader*>(shaderHandle));
+    switch (rgnOp) {
+        case SkRegion::Op::kIntersect_Op:
+        case SkRegion::Op::kDifference_Op:
+            get_canvas(canvasHandle)->clipShader(shader, static_cast<SkClipOp>(rgnOp));
+            break;
+        default:
+            ALOGW("Ignoring unsupported clip operation %d", opHandle);
+            SkRect clipBounds;  // ignored
+            get_canvas(canvasHandle)->getClipBounds(&clipBounds);
+            break;
+    }
+}
+
 static void drawColor(JNIEnv* env, jobject, jlong canvasHandle, jint color, jint modeHandle) {
     SkBlendMode mode = static_cast<SkBlendMode>(modeHandle);
     get_canvas(canvasHandle)->drawColor(color, mode);
@@ -797,6 +814,7 @@
         {"nQuickReject", "(JFFFF)Z", (void*)CanvasJNI::quickRejectRect},
         {"nClipRect", "(JFFFFI)Z", (void*)CanvasJNI::clipRect},
         {"nClipPath", "(JJI)Z", (void*)CanvasJNI::clipPath},
+        {"nClipShader", "(JJI)V", (void*)CanvasJNI::clipShader},
         {"nSetDrawFilter", "(JJ)V", (void*)CanvasJNI::setPaintFilter},
 };