Implement automatic SV clipping

Clip SV to its ancestor clipping bounds. This enables
Z-above SurfaceView + scrolling containers to work more naturally

Replaces the hidden API of setEnableSurfaceClipping

Fixes: 298621623
Test: Sample app
Change-Id: Iaa862598e37065677f5ba163a5ac7a6fab2739ea
diff --git a/libs/hwui/Android.bp b/libs/hwui/Android.bp
index ff1eedb..da728f9 100644
--- a/libs/hwui/Android.bp
+++ b/libs/hwui/Android.bp
@@ -144,6 +144,7 @@
                 "libsync",
                 "libui",
                 "aconfig_text_flags_c_lib",
+                "server_configurable_flags",
             ],
             static_libs: [
                 "libEGL_blobCache",
diff --git a/libs/hwui/DamageAccumulator.cpp b/libs/hwui/DamageAccumulator.cpp
index a8d170d..fd27641 100644
--- a/libs/hwui/DamageAccumulator.cpp
+++ b/libs/hwui/DamageAccumulator.cpp
@@ -242,6 +242,47 @@
     }
 }
 
+SkRect DamageAccumulator::computeClipAndTransform(const SkRect& bounds, Matrix4* outMatrix) const {
+    const DirtyStack* frame = mHead;
+    Matrix4 transform;
+    SkRect pretransformResult = bounds;
+    while (true) {
+        SkRect currentBounds = pretransformResult;
+        pretransformResult.setEmpty();
+        switch (frame->type) {
+            case TransformRenderNode: {
+                const RenderProperties& props = frame->renderNode->properties();
+                // Perform clipping
+                if (props.getClipDamageToBounds() && !currentBounds.isEmpty()) {
+                    if (!currentBounds.intersect(
+                                SkRect::MakeIWH(props.getWidth(), props.getHeight()))) {
+                        currentBounds.setEmpty();
+                    }
+                }
+
+                // apply all transforms
+                mapRect(props, currentBounds, &pretransformResult);
+                frame->renderNode->applyViewPropertyTransforms(transform);
+            } break;
+            case TransformMatrix4:
+                mapRect(frame->matrix4, currentBounds, &pretransformResult);
+                transform.multiply(*frame->matrix4);
+                break;
+            default:
+                pretransformResult = currentBounds;
+                break;
+        }
+        if (frame->prev == frame) break;
+        frame = frame->prev;
+    }
+    SkRect result;
+    Matrix4 globalToLocal;
+    globalToLocal.loadInverse(transform);
+    mapRect(&globalToLocal, pretransformResult, &result);
+    *outMatrix = transform;
+    return result;
+}
+
 void DamageAccumulator::dirty(float left, float top, float right, float bottom) {
     mHead->pendingDirty.join({left, top, right, bottom});
 }
diff --git a/libs/hwui/DamageAccumulator.h b/libs/hwui/DamageAccumulator.h
index c4249af..30bf706 100644
--- a/libs/hwui/DamageAccumulator.h
+++ b/libs/hwui/DamageAccumulator.h
@@ -61,6 +61,8 @@
 
     void computeCurrentTransform(Matrix4* outMatrix) const;
 
+    SkRect computeClipAndTransform(const SkRect& bounds, Matrix4* outMatrix) const;
+
     void finish(SkRect* totalDirty);
 
     struct StretchResult {
diff --git a/libs/hwui/Properties.cpp b/libs/hwui/Properties.cpp
index 5e5eb4a..ad600d0 100644
--- a/libs/hwui/Properties.cpp
+++ b/libs/hwui/Properties.cpp
@@ -20,15 +20,26 @@
 #ifdef __ANDROID__
 #include "HWUIProperties.sysprop.h"
 #endif
-#include "src/core/SkTraceEventCommon.h"
+#include <android-base/properties.h>
+#include <cutils/compiler.h>
+#include <log/log.h>
 
 #include <algorithm>
 #include <cstdlib>
 #include <optional>
 
-#include <android-base/properties.h>
-#include <cutils/compiler.h>
-#include <log/log.h>
+#include "src/core/SkTraceEventCommon.h"
+
+#ifdef __ANDROID__
+#include <com_android_graphics_hwui_flags.h>
+namespace hwui_flags = com::android::graphics::hwui::flags;
+#else
+namespace hwui_flags {
+constexpr bool clip_surfaceviews() {
+    return false;
+}
+}  // namespace hwui_flags
+#endif
 
 namespace android {
 namespace uirenderer {
@@ -92,6 +103,8 @@
 
 float Properties::maxHdrHeadroomOn8bit = 5.f;  // TODO: Refine this number
 
+bool Properties::clipSurfaceViews = false;
+
 StretchEffectBehavior Properties::stretchEffectBehavior = StretchEffectBehavior::ShaderHWUI;
 
 DrawingEnabled Properties::drawingEnabled = DrawingEnabled::NotInitialized;
@@ -159,6 +172,9 @@
     // call isDrawingEnabled to force loading of the property
     isDrawingEnabled();
 
+    clipSurfaceViews =
+            base::GetBoolProperty("debug.hwui.clip_surfaceviews", hwui_flags::clip_surfaceviews());
+
     return (prevDebugLayersUpdates != debugLayersUpdates) || (prevDebugOverdraw != debugOverdraw);
 }
 
diff --git a/libs/hwui/Properties.h b/libs/hwui/Properties.h
index bb47744..bca57e9 100644
--- a/libs/hwui/Properties.h
+++ b/libs/hwui/Properties.h
@@ -325,6 +325,8 @@
 
     static float maxHdrHeadroomOn8bit;
 
+    static bool clipSurfaceViews;
+
     static StretchEffectBehavior getStretchEffectBehavior() {
         return stretchEffectBehavior;
     }
diff --git a/libs/hwui/jni/android_graphics_RenderNode.cpp b/libs/hwui/jni/android_graphics_RenderNode.cpp
index 2a218a2..a1b05c1 100644
--- a/libs/hwui/jni/android_graphics_RenderNode.cpp
+++ b/libs/hwui/jni/android_graphics_RenderNode.cpp
@@ -568,6 +568,7 @@
 struct {
     jclass clazz;
     jmethodID callPositionChanged;
+    jmethodID callPositionChanged2;
     jmethodID callApplyStretch;
     jmethodID callPositionLost;
 } gPositionListener;
@@ -589,14 +590,25 @@
         virtual void onPositionUpdated(RenderNode& node, const TreeInfo& info) override {
             if (CC_UNLIKELY(!mListener || !info.updateWindowPositions)) return;
 
-            Matrix4 transform;
-            info.damageAccumulator->computeCurrentTransform(&transform);
             const RenderProperties& props = node.properties();
+            const bool enableClip = Properties::clipSurfaceViews;
 
-            uirenderer::Rect bounds(props.getWidth(), props.getHeight());
+            Matrix4 transform;
+            SkIRect clipBounds;
+            if (enableClip) {
+                uirenderer::Rect initialClipBounds;
+                props.getClippingRectForFlags(props.getClippingFlags(), &initialClipBounds);
+                clipBounds =
+                        info.damageAccumulator
+                                ->computeClipAndTransform(initialClipBounds.toSkRect(), &transform)
+                                .roundOut();
+            } else {
+                info.damageAccumulator->computeCurrentTransform(&transform);
+            }
             bool useStretchShader =
                     Properties::getStretchEffectBehavior() != StretchEffectBehavior::UniformScale;
             // Compute the transform bounds first before calculating the stretch
+            uirenderer::Rect bounds(props.getWidth(), props.getHeight());
             transform.mapRect(bounds);
 
             bool hasStretch = useStretchShader && info.stretchEffectCount;
@@ -614,10 +626,11 @@
                 bounds.roundOut();
             }
 
-            if (mPreviousPosition == bounds) {
+            if (mPreviousPosition == bounds && mPreviousClip == clipBounds) {
                 return;
             }
             mPreviousPosition = bounds;
+            mPreviousClip = clipBounds;
 
             ATRACE_NAME("Update SurfaceView position");
 
@@ -629,11 +642,23 @@
             // In particular if the app removes a view from the view tree before
             // this callback is dispatched, then we lose the position
             // information for this frame.
-            jboolean keepListening = env->CallStaticBooleanMethod(
-                    gPositionListener.clazz, gPositionListener.callPositionChanged, mListener,
-                    static_cast<jlong>(info.canvasContext.getFrameNumber()),
-                    static_cast<jint>(bounds.left), static_cast<jint>(bounds.top),
-                    static_cast<jint>(bounds.right), static_cast<jint>(bounds.bottom));
+            jboolean keepListening;
+            if (!enableClip) {
+                keepListening = env->CallStaticBooleanMethod(
+                        gPositionListener.clazz, gPositionListener.callPositionChanged, mListener,
+                        static_cast<jlong>(info.canvasContext.getFrameNumber()),
+                        static_cast<jint>(bounds.left), static_cast<jint>(bounds.top),
+                        static_cast<jint>(bounds.right), static_cast<jint>(bounds.bottom));
+            } else {
+                keepListening = env->CallStaticBooleanMethod(
+                        gPositionListener.clazz, gPositionListener.callPositionChanged2, mListener,
+                        static_cast<jlong>(info.canvasContext.getFrameNumber()),
+                        static_cast<jint>(bounds.left), static_cast<jint>(bounds.top),
+                        static_cast<jint>(bounds.right), static_cast<jint>(bounds.bottom),
+                        static_cast<jint>(clipBounds.fLeft), static_cast<jint>(clipBounds.fTop),
+                        static_cast<jint>(clipBounds.fRight),
+                        static_cast<jint>(clipBounds.fBottom));
+            }
             if (!keepListening) {
                 env->DeleteGlobalRef(mListener);
                 mListener = nullptr;
@@ -738,6 +763,7 @@
         JavaVM* mVm;
         jobject mListener;
         uirenderer::Rect mPreviousPosition;
+        uirenderer::Rect mPreviousClip;
     };
 
     RenderNode* renderNode = reinterpret_cast<RenderNode*>(renderNodePtr);
@@ -866,6 +892,8 @@
     gPositionListener.clazz = MakeGlobalRefOrDie(env, clazz);
     gPositionListener.callPositionChanged = GetStaticMethodIDOrDie(
             env, clazz, "callPositionChanged", "(Ljava/lang/ref/WeakReference;JIIII)Z");
+    gPositionListener.callPositionChanged2 = GetStaticMethodIDOrDie(
+            env, clazz, "callPositionChanged2", "(Ljava/lang/ref/WeakReference;JIIIIIIII)Z");
     gPositionListener.callApplyStretch = GetStaticMethodIDOrDie(
             env, clazz, "callApplyStretch", "(Ljava/lang/ref/WeakReference;JFFFFFFFFFF)Z");
     gPositionListener.callPositionLost = GetStaticMethodIDOrDie(