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/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;
 };