Update hole punch logic in HWUI

--Updated HWUI holepunch logic for SurfaceView to
also apply the stretch to the hole punch
--Updated RenderNode callbacks to also include
an offset from the ancestor RenderNode that also
has a stretch configured on it
--Added new test activity to verify hole punch
logic

Bug: 179047472
Test: manual
Change-Id: Ibbaf8248a31839ba9dc352ecb9fef54e1276918e
diff --git a/libs/hwui/Android.bp b/libs/hwui/Android.bp
index f2c48bb..0212309 100644
--- a/libs/hwui/Android.bp
+++ b/libs/hwui/Android.bp
@@ -467,6 +467,7 @@
         "pipeline/skia/HolePunch.cpp",
         "pipeline/skia/SkiaDisplayList.cpp",
         "pipeline/skia/SkiaRecordingCanvas.cpp",
+        "pipeline/skia/StretchMask.cpp",
         "pipeline/skia/RenderNodeDrawable.cpp",
         "pipeline/skia/ReorderBarrierDrawables.cpp",
         "pipeline/skia/TransformCanvas.cpp",
diff --git a/libs/hwui/DamageAccumulator.cpp b/libs/hwui/DamageAccumulator.cpp
index 0bf9480..94fe243 100644
--- a/libs/hwui/DamageAccumulator.cpp
+++ b/libs/hwui/DamageAccumulator.cpp
@@ -197,6 +197,27 @@
     }
 }
 
+static void computeTransformImpl(const DirtyStack* frame, const DirtyStack* end,
+                                 Matrix4* outMatrix) {
+  while (frame != end) {
+    switch (frame->type) {
+        case TransformRenderNode:
+            frame->renderNode->applyViewPropertyTransforms(*outMatrix);
+            break;
+        case TransformMatrix4:
+            outMatrix->multiply(*frame->matrix4);
+            break;
+        case TransformNone:
+            // nothing to be done
+            break;
+        default:
+            LOG_ALWAYS_FATAL("Tried to compute transform with an invalid type: %d",
+                             frame->type);
+    }
+    frame = frame->prev;
+  }
+}
+
 void DamageAccumulator::applyRenderNodeTransform(DirtyStack* frame) {
     if (frame->pendingDirty.isEmpty()) {
         return;
@@ -249,19 +270,38 @@
     mHead->pendingDirty.setEmpty();
 }
 
-const StretchEffect* DamageAccumulator::findNearestStretchEffect() const {
+DamageAccumulator::StretchResult DamageAccumulator::findNearestStretchEffect() const {
     DirtyStack* frame = mHead;
     while (frame->prev != frame) {
-        frame = frame->prev;
         if (frame->type == TransformRenderNode) {
+            const auto& renderNode = frame->renderNode;
+            const auto& frameRenderNodeProperties = renderNode->properties();
             const auto& effect =
-                    frame->renderNode->properties().layerProperties().getStretchEffect();
+                    frameRenderNodeProperties.layerProperties().getStretchEffect();
+            const float width = (float) renderNode->getWidth();
+            const float height = (float) renderNode->getHeight();
             if (!effect.isEmpty()) {
-                return &effect;
+                Matrix4 stretchMatrix;
+                computeTransformImpl(mHead, frame, &stretchMatrix);
+                Rect stretchRect = Rect(0.f, 0.f, width, height);
+                stretchMatrix.mapRect(stretchRect);
+
+                return StretchResult{
+                    .stretchEffect = &effect,
+                    .childRelativeBounds = SkRect::MakeLTRB(
+                        stretchRect.left,
+                        stretchRect.top,
+                        stretchRect.right,
+                        stretchRect.bottom
+                    ),
+                    .width = width,
+                    .height = height
+                };
             }
         }
+        frame = frame->prev;
     }
-    return nullptr;
+    return StretchResult{};
 }
 
 } /* namespace uirenderer */
diff --git a/libs/hwui/DamageAccumulator.h b/libs/hwui/DamageAccumulator.h
index 89ee0e3..90a3517 100644
--- a/libs/hwui/DamageAccumulator.h
+++ b/libs/hwui/DamageAccumulator.h
@@ -21,6 +21,7 @@
 
 #include <SkMatrix.h>
 #include <SkRect.h>
+#include <effects/StretchEffect.h>
 
 #include "utils/Macros.h"
 
@@ -35,7 +36,6 @@
 struct DirtyStack;
 class RenderNode;
 class Matrix4;
-class StretchEffect;
 
 class DamageAccumulator {
     PREVENT_COPY_AND_ASSIGN(DamageAccumulator);
@@ -63,7 +63,29 @@
 
     void finish(SkRect* totalDirty);
 
-    const StretchEffect* findNearestStretchEffect() const;
+    struct StretchResult {
+        /**
+         * Stretch parameters configured on the stretch container
+         */
+        const StretchEffect* stretchEffect;
+
+        /**
+         * Bounds of the child relative to the stretch container
+         */
+        const SkRect childRelativeBounds;
+
+        /**
+         * Width of the stretch container
+         */
+        const float width;
+
+        /**
+         * Height of the stretch container
+         */
+        const float height;
+    };
+
+    [[nodiscard]] StretchResult findNearestStretchEffect() const;
 
 private:
     void pushCommon();
diff --git a/libs/hwui/RenderNode.cpp b/libs/hwui/RenderNode.cpp
index e9eae3d..fce2e1f 100644
--- a/libs/hwui/RenderNode.cpp
+++ b/libs/hwui/RenderNode.cpp
@@ -194,6 +194,9 @@
     SkRect dirty;
     info.damageAccumulator->peekAtDirty(&dirty);
     info.layerUpdateQueue->enqueueLayerWithDamage(this, dirty);
+    if (!dirty.isEmpty()) {
+      mStretchMask.markDirty();
+    }
 
     // There might be prefetched layers that need to be accounted for.
     // That might be us, so tell CanvasContext that this layer is in the
@@ -302,6 +305,12 @@
         damageSelf(info);
         info.damageAccumulator->popTransform();
         syncProperties();
+
+        const StretchEffect& stagingStretch =
+            mProperties.layerProperties().getStretchEffect();
+        if (stagingStretch.isEmpty()) {
+            mStretchMask.clear();
+        }
         // We could try to be clever and only re-damage if the matrix changed.
         // However, we don't need to worry about that. The cost of over-damaging
         // here is only going to be a single additional map rect of this node
diff --git a/libs/hwui/RenderNode.h b/libs/hwui/RenderNode.h
index 988141f..6a0b1aa 100644
--- a/libs/hwui/RenderNode.h
+++ b/libs/hwui/RenderNode.h
@@ -40,6 +40,7 @@
 #include "pipeline/skia/SkiaLayer.h"
 
 #include <vector>
+#include <pipeline/skia/StretchMask.h>
 
 class SkBitmap;
 class SkPaint;
@@ -127,6 +128,8 @@
         }
     }
 
+    StretchMask& getStretchMask() { return mStretchMask; }
+
     VirtualLightRefBase* getUserContext() const { return mUserContext.get(); }
 
     void setUserContext(VirtualLightRefBase* context) { mUserContext = context; }
@@ -286,6 +289,7 @@
     UsageHint mUsageHint = UsageHint::Unknown;
 
     bool mHasHolePunches;
+    StretchMask mStretchMask;
 
     // METHODS & FIELDS ONLY USED BY THE SKIA RENDERER
 public:
diff --git a/libs/hwui/effects/StretchEffect.cpp b/libs/hwui/effects/StretchEffect.cpp
index 6eb6e1e..1519d69 100644
--- a/libs/hwui/effects/StretchEffect.cpp
+++ b/libs/hwui/effects/StretchEffect.cpp
@@ -189,17 +189,12 @@
 static const float CONTENT_DISTANCE_STRETCHED = 1.f;
 static const float INTERPOLATION_STRENGTH_VALUE = 0.7f;
 
-sk_sp<SkShader> StretchEffect::getShader(const sk_sp<SkImage>& snapshotImage) const {
+sk_sp<SkShader> StretchEffect::getShader(float width, float height,
+                                         const sk_sp<SkImage>& snapshotImage) const {
     if (isEmpty()) {
         return nullptr;
     }
 
-    if (mStretchShader != nullptr) {
-        return mStretchShader;
-    }
-
-    float viewportWidth = stretchArea.width();
-    float viewportHeight = stretchArea.height();
     float normOverScrollDistX = mStretchDirection.x();
     float normOverScrollDistY = mStretchDirection.y();
     float distanceStretchedX = CONTENT_DISTANCE_STRETCHED / (1 + abs(normOverScrollDistX));
@@ -228,12 +223,10 @@
     mBuilder->uniform("uOverscrollY").set(&normOverScrollDistY, 1);
     mBuilder->uniform("uScrollX").set(&ZERO, 1);
     mBuilder->uniform("uScrollY").set(&ZERO, 1);
-    mBuilder->uniform("viewportWidth").set(&viewportWidth, 1);
-    mBuilder->uniform("viewportHeight").set(&viewportHeight, 1);
+    mBuilder->uniform("viewportWidth").set(&width, 1);
+    mBuilder->uniform("viewportHeight").set(&height, 1);
 
-    mStretchShader = mBuilder->makeShader(nullptr, false);
-
-    return mStretchShader;
+    return mBuilder->makeShader(nullptr, false);
 }
 
 sk_sp<SkRuntimeEffect> StretchEffect::getStretchEffect() {
diff --git a/libs/hwui/effects/StretchEffect.h b/libs/hwui/effects/StretchEffect.h
index 546d53b..61537f0 100644
--- a/libs/hwui/effects/StretchEffect.h
+++ b/libs/hwui/effects/StretchEffect.h
@@ -26,19 +26,15 @@
 
 namespace android::uirenderer {
 
-// TODO: Inherit from base RenderEffect type?
 class StretchEffect {
 public:
-    enum class StretchInterpolator {
-        SmoothStep,
-    };
 
-    StretchEffect(const SkRect& area, const SkVector& direction, float maxStretchAmountX,
+    StretchEffect(const SkVector& direction,
+                  float maxStretchAmountX,
                   float maxStretchAmountY)
-            : stretchArea(area)
-            , maxStretchAmountX(maxStretchAmountX)
+            : maxStretchAmountX(maxStretchAmountX)
             , maxStretchAmountY(maxStretchAmountY)
-            , mStretchDirection(direction) {}
+            , mStretchDirection(direction) { }
 
     StretchEffect() {}
 
@@ -51,14 +47,18 @@
     }
 
     StretchEffect& operator=(const StretchEffect& other) {
-        this->stretchArea = other.stretchArea;
         this->mStretchDirection = other.mStretchDirection;
-        this->mStretchShader = other.mStretchShader;
         this->maxStretchAmountX = other.maxStretchAmountX;
         this->maxStretchAmountY = other.maxStretchAmountY;
         return *this;
     }
 
+    bool operator==(const StretchEffect& other) const {
+        return mStretchDirection == other.mStretchDirection &&
+                maxStretchAmountX == other.maxStretchAmountX &&
+                maxStretchAmountY == other.maxStretchAmountY;
+    }
+
     void mergeWith(const StretchEffect& other) {
         if (other.isEmpty()) {
             return;
@@ -67,33 +67,26 @@
             *this = other;
             return;
         }
-        setStretchDirection(mStretchDirection + other.mStretchDirection);
+        mStretchDirection += other.mStretchDirection;
         if (isEmpty()) {
             return setEmpty();
         }
-        stretchArea.join(other.stretchArea);
         maxStretchAmountX = std::max(maxStretchAmountX, other.maxStretchAmountX);
         maxStretchAmountY = std::max(maxStretchAmountY, other.maxStretchAmountY);
     }
 
-    sk_sp<SkShader> getShader(const sk_sp<SkImage>& snapshotImage) const;
+    sk_sp<SkShader> getShader(float width, float height,
+                              const sk_sp<SkImage>& snapshotImage) const;
 
-    SkRect stretchArea {0, 0, 0, 0};
     float maxStretchAmountX = 0;
     float maxStretchAmountY = 0;
 
-    void setStretchDirection(const SkVector& direction) {
-        mStretchShader = nullptr;
-        mStretchDirection = direction;
-    }
-
     const SkVector getStretchDirection() const { return mStretchDirection; }
 
 private:
     static sk_sp<SkRuntimeEffect> getStretchEffect();
     mutable SkVector mStretchDirection{0, 0};
     mutable std::unique_ptr<SkRuntimeShaderBuilder> mBuilder;
-    mutable sk_sp<SkShader> mStretchShader;
 };
 
 } // namespace android::uirenderer
diff --git a/libs/hwui/jni/android_graphics_RenderNode.cpp b/libs/hwui/jni/android_graphics_RenderNode.cpp
index fffa806..5131c64 100644
--- a/libs/hwui/jni/android_graphics_RenderNode.cpp
+++ b/libs/hwui/jni/android_graphics_RenderNode.cpp
@@ -180,12 +180,10 @@
 }
 
 static jboolean android_view_RenderNode_stretch(CRITICAL_JNI_PARAMS_COMMA jlong renderNodePtr,
-                                                jfloat left, jfloat top, jfloat right,
-                                                jfloat bottom, jfloat vX, jfloat vY, jfloat maxX,
+                                                jfloat vX, jfloat vY, jfloat maxX,
                                                 jfloat maxY) {
-    StretchEffect effect = StretchEffect(SkRect::MakeLTRB(left, top, right, bottom),
-                                         {.fX = vX, .fY = vY}, maxX, maxY);
-    RenderNode* renderNode = reinterpret_cast<RenderNode*>(renderNodePtr);
+    auto* renderNode = reinterpret_cast<RenderNode*>(renderNodePtr);
+    StretchEffect effect = StretchEffect({.fX = vX, .fY = vY}, maxX, maxY);
     renderNode->mutateStagingProperties().mutateLayerProperties().mutableStretchEffect().mergeWith(
             effect);
     renderNode->setPropertyFieldsDirty(RenderNode::GENERIC);
@@ -643,13 +641,15 @@
 
         void handleStretchEffect(const TreeInfo& info, const Matrix4& transform) {
             // Search up to find the nearest stretcheffect parent
-            const StretchEffect* effect = info.damageAccumulator->findNearestStretchEffect();
+            const DamageAccumulator::StretchResult result =
+                info.damageAccumulator->findNearestStretchEffect();
+            const StretchEffect* effect = result.stretchEffect;
             if (!effect) {
                 return;
             }
 
-            uirenderer::Rect area = effect->stretchArea;
-            transform.mapRect(area);
+            const auto& childRelativeBounds = result.childRelativeBounds;
+
             JNIEnv* env = jnienv();
 
             jobject localref = env->NewLocalRef(mWeakRef);
@@ -661,9 +661,17 @@
 #ifdef __ANDROID__  // Layoutlib does not support CanvasContext
             SkVector stretchDirection = effect->getStretchDirection();
             env->CallVoidMethod(localref, gPositionListener_ApplyStretchMethod,
-                                info.canvasContext.getFrameNumber(), area.left, area.top,
-                                area.right, area.bottom, stretchDirection.fX, stretchDirection.fY,
-                                effect->maxStretchAmountX, effect->maxStretchAmountY);
+                                info.canvasContext.getFrameNumber(),
+                                result.width,
+                                result.height,
+                                stretchDirection.fX,
+                                stretchDirection.fY,
+                                effect->maxStretchAmountX,
+                                effect->maxStretchAmountY,
+                                childRelativeBounds.left(),
+                                childRelativeBounds.top(),
+                                childRelativeBounds.right(),
+                                childRelativeBounds.bottom());
 #endif
             env->DeleteLocalRef(localref);
         }
@@ -739,7 +747,7 @@
         {"nSetOutlineEmpty", "(J)Z", (void*)android_view_RenderNode_setOutlineEmpty},
         {"nSetOutlineNone", "(J)Z", (void*)android_view_RenderNode_setOutlineNone},
         {"nClearStretch", "(J)Z", (void*)android_view_RenderNode_clearStretch},
-        {"nStretch", "(JFFFFFFFF)Z", (void*)android_view_RenderNode_stretch},
+        {"nStretch", "(JFFFF)Z", (void*)android_view_RenderNode_stretch},
         {"nHasShadow", "(J)Z", (void*)android_view_RenderNode_hasShadow},
         {"nSetSpotShadowColor", "(JI)Z", (void*)android_view_RenderNode_setSpotShadowColor},
         {"nGetSpotShadowColor", "(J)I", (void*)android_view_RenderNode_getSpotShadowColor},
@@ -814,7 +822,7 @@
     gPositionListener_PositionChangedMethod = GetMethodIDOrDie(env, clazz,
             "positionChanged", "(JIIII)V");
     gPositionListener_ApplyStretchMethod =
-            GetMethodIDOrDie(env, clazz, "applyStretch", "(JFFFFFFF)V");
+            GetMethodIDOrDie(env, clazz, "applyStretch", "(JFFFFFFFFFF)V");
     gPositionListener_PositionLostMethod = GetMethodIDOrDie(env, clazz,
             "positionLost", "(J)V");
     return RegisterMethodsOrDie(env, kClassPathName, gMethods, NELEM(gMethods));
diff --git a/libs/hwui/pipeline/skia/RenderNodeDrawable.cpp b/libs/hwui/pipeline/skia/RenderNodeDrawable.cpp
index 77d99a6..1ae06d0 100644
--- a/libs/hwui/pipeline/skia/RenderNodeDrawable.cpp
+++ b/libs/hwui/pipeline/skia/RenderNodeDrawable.cpp
@@ -16,6 +16,7 @@
 
 #include "RenderNodeDrawable.h"
 #include <SkPaintFilterCanvas.h>
+#include "StretchMask.h"
 #include "RenderNode.h"
 #include "SkiaDisplayList.h"
 #include "TransformCanvas.h"
@@ -245,17 +246,37 @@
                     "SurfaceID|%" PRId64, renderNode->uniqueId()).c_str(), nullptr);
             }
 
-            if (renderNode->hasHolePunches()) {
-                TransformCanvas transformCanvas(canvas);
-                displayList->draw(&transformCanvas);
-            }
-
             const StretchEffect& stretch = properties.layerProperties().getStretchEffect();
             if (stretch.isEmpty()) {
+                // If we don't have any stretch effects, issue the filtered
+                // canvas draw calls to make sure we still punch a hole
+                // with the same canvas transformation + clip into the target
+                // canvas then draw the layer on top
+                if (renderNode->hasHolePunches()) {
+                    TransformCanvas transformCanvas(canvas, SkBlendMode::kClear);
+                    displayList->draw(&transformCanvas);
+                }
                 canvas->drawImageRect(snapshotImage, bounds, bounds, sampling, &paint,
                                       SkCanvas::kStrict_SrcRectConstraint);
             } else {
-                sk_sp<SkShader> stretchShader = stretch.getShader(snapshotImage);
+                // If we do have stretch effects and have hole punches,
+                // then create a mask and issue the filtered draw calls to
+                // get the corresponding hole punches.
+                // Then apply the stretch to the mask and draw the mask to
+                // the destination
+                if (renderNode->hasHolePunches()) {
+                    GrRecordingContext* context = canvas->recordingContext();
+                    StretchMask& stretchMask = renderNode->getStretchMask();
+                    stretchMask.draw(context,
+                                     stretch,
+                                     bounds,
+                                     displayList,
+                                     canvas);
+                }
+
+                sk_sp<SkShader> stretchShader = stretch.getShader(bounds.width(),
+                                                                  bounds.height(),
+                                                                  snapshotImage);
                 paint.setShader(stretchShader);
                 canvas->drawRect(bounds, paint);
             }
diff --git a/libs/hwui/pipeline/skia/StretchMask.cpp b/libs/hwui/pipeline/skia/StretchMask.cpp
new file mode 100644
index 0000000..2bbd8a4
--- /dev/null
+++ b/libs/hwui/pipeline/skia/StretchMask.cpp
@@ -0,0 +1,64 @@
+/*
+ * Copyright (C) 2021 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+#include "StretchMask.h"
+#include "SkSurface.h"
+#include "SkCanvas.h"
+#include "TransformCanvas.h"
+#include "SkiaDisplayList.h"
+
+using android::uirenderer::StretchMask;
+
+void StretchMask::draw(GrRecordingContext* context,
+                       const StretchEffect& stretch,
+                       const SkRect& bounds,
+                       skiapipeline::SkiaDisplayList* displayList,
+                       SkCanvas* canvas) {
+    float width = bounds.width();
+    float height = bounds.height();
+    if (mMaskSurface == nullptr || mMaskSurface->width() != width ||
+        mMaskSurface->height() != height) {
+        // Create a new surface if we don't have one or our existing size does
+        // not match.
+        mMaskSurface = SkSurface::MakeRenderTarget(
+            context,
+            SkBudgeted::kYes,
+            SkImageInfo::Make(
+                width,
+                height,
+                SkColorType::kAlpha_8_SkColorType,
+                SkAlphaType::kPremul_SkAlphaType)
+        );
+        mIsDirty = true;
+    }
+
+    if (mIsDirty) {
+        SkCanvas* maskCanvas = mMaskSurface->getCanvas();
+        maskCanvas->drawColor(0, SkBlendMode::kClear);
+        TransformCanvas transformCanvas(maskCanvas, SkBlendMode::kSrcOver);
+        displayList->draw(&transformCanvas);
+    }
+
+    sk_sp<SkImage> maskImage = mMaskSurface->makeImageSnapshot();
+    sk_sp<SkShader> maskStretchShader = stretch.getShader(
+        width, height, maskImage);
+
+    SkPaint maskPaint;
+    maskPaint.setShader(maskStretchShader);
+    maskPaint.setBlendMode(SkBlendMode::kDstOut);
+    canvas->drawRect(bounds, maskPaint);
+
+    mIsDirty = false;
+}
\ No newline at end of file
diff --git a/libs/hwui/pipeline/skia/StretchMask.h b/libs/hwui/pipeline/skia/StretchMask.h
new file mode 100644
index 0000000..dc698b8
--- /dev/null
+++ b/libs/hwui/pipeline/skia/StretchMask.h
@@ -0,0 +1,62 @@
+/*
+ * Copyright (C) 2021 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+#pragma once
+
+#include "GrRecordingContext.h"
+#include <effects/StretchEffect.h>
+#include <SkSurface.h>
+#include "SkiaDisplayList.h"
+
+namespace android::uirenderer {
+
+/**
+ * Helper class used to create/cache an SkSurface instance
+ * to create a mask that is used to draw a stretched hole punch
+ */
+class StretchMask {
+ public:
+  /**
+   * Release the current surface used for the stretch mask
+   */
+  void clear() {
+      mMaskSurface = nullptr;
+  }
+
+  /**
+   * Reset the dirty flag to re-create the stretch mask on the next draw
+   * pass
+   */
+  void markDirty() {
+      mIsDirty = true;
+  }
+
+  /**
+   * Draws the stretch mask into the given target canvas
+   * @param context GrRecordingContext used to create the surface if necessary
+   * @param stretch StretchEffect to apply to the mask
+   * @param bounds Target bounds to draw into the given canvas
+   * @param displayList List of drawing commands to render into the stretch mask
+   * @param canvas Target canvas to draw the mask into
+   */
+  void draw(GrRecordingContext* context,
+            const StretchEffect& stretch, const SkRect& bounds,
+            skiapipeline::SkiaDisplayList* displayList, SkCanvas* canvas);
+private:
+  sk_sp<SkSurface> mMaskSurface;
+  bool mIsDirty = true;
+};
+
+}
diff --git a/libs/hwui/pipeline/skia/TransformCanvas.cpp b/libs/hwui/pipeline/skia/TransformCanvas.cpp
index a6e4c4c..6777c00 100644
--- a/libs/hwui/pipeline/skia/TransformCanvas.cpp
+++ b/libs/hwui/pipeline/skia/TransformCanvas.cpp
@@ -28,8 +28,8 @@
         SkRRect roundRect = SkRRect::MakeRectXY(rect, radiusX, radiusY);
 
         SkPaint paint;
-        paint.setColor(0);
-        paint.setBlendMode(SkBlendMode::kClear);
+        paint.setColor(SkColors::kBlack);
+        paint.setBlendMode(mHolePunchBlendMode);
         mWrappedCanvas->drawRRect(roundRect, paint);
     }
 }
diff --git a/libs/hwui/pipeline/skia/TransformCanvas.h b/libs/hwui/pipeline/skia/TransformCanvas.h
index 47f77f1..685b71d 100644
--- a/libs/hwui/pipeline/skia/TransformCanvas.h
+++ b/libs/hwui/pipeline/skia/TransformCanvas.h
@@ -17,10 +17,12 @@
 
 #include <include/core/SkCanvas.h>
 #include "SkPaintFilterCanvas.h"
+#include <effects/StretchEffect.h>
 
 class TransformCanvas : public SkPaintFilterCanvas {
 public:
-    TransformCanvas(SkCanvas* target) : SkPaintFilterCanvas(target), mWrappedCanvas(target) {}
+    TransformCanvas(SkCanvas* target, SkBlendMode blendmode) :
+        SkPaintFilterCanvas(target), mWrappedCanvas(target), mHolePunchBlendMode(blendmode) {}
 
 protected:
     bool onFilter(SkPaint& paint) const override;
@@ -32,4 +34,5 @@
 private:
     // We don't own the canvas so just maintain a raw pointer to it
     SkCanvas* mWrappedCanvas;
+    const SkBlendMode mHolePunchBlendMode;
 };