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/core/java/android/view/SurfaceView.java b/core/java/android/view/SurfaceView.java
index 5b69d7f..0ae14a2 100644
--- a/core/java/android/view/SurfaceView.java
+++ b/core/java/android/view/SurfaceView.java
@@ -43,6 +43,7 @@
 import android.os.Looper;
 import android.os.RemoteException;
 import android.os.SystemClock;
+import android.text.TextUtils;
 import android.util.ArraySet;
 import android.util.AttributeSet;
 import android.util.Log;
@@ -51,6 +52,7 @@
 import android.view.accessibility.IAccessibilityEmbeddedConnection;
 import android.window.SurfaceSyncGroup;
 
+import com.android.graphics.hwui.flags.Flags;
 import com.android.internal.view.SurfaceCallbackHelper;
 
 import java.lang.annotation.Retention;
@@ -326,6 +328,8 @@
         }
     };
 
+    private final boolean mRtDrivenClipping = Flags.clipSurfaceviews();
+
     public SurfaceView(Context context) {
         this(context, null);
     }
@@ -572,6 +576,10 @@
     public void setClipBounds(Rect clipBounds) {
         super.setClipBounds(clipBounds);
 
+        if (mRtDrivenClipping && isHardwareAccelerated()) {
+            return;
+        }
+
         if (!mClipSurfaceToBounds || mSurfaceControl == null) {
             return;
         }
@@ -915,15 +923,17 @@
             }
             if (sizeChanged || creating || !isHardwareAccelerated()) {
 
-                // Set a window crop when creating the surface or changing its size to
-                // crop the buffer to the surface size since the buffer producer may
-                // use SCALING_MODE_SCALE and submit a larger size than the surface
-                // size.
-                if (mClipSurfaceToBounds && mClipBounds != null) {
-                    surfaceUpdateTransaction.setWindowCrop(mSurfaceControl, mClipBounds);
-                } else {
-                    surfaceUpdateTransaction.setWindowCrop(mSurfaceControl, mSurfaceWidth,
-                            mSurfaceHeight);
+                if (!mRtDrivenClipping || !isHardwareAccelerated()) {
+                    // Set a window crop when creating the surface or changing its size to
+                    // crop the buffer to the surface size since the buffer producer may
+                    // use SCALING_MODE_SCALE and submit a larger size than the surface
+                    // size.
+                    if (mClipSurfaceToBounds && mClipBounds != null) {
+                        surfaceUpdateTransaction.setWindowCrop(mSurfaceControl, mClipBounds);
+                    } else {
+                        surfaceUpdateTransaction.setWindowCrop(mSurfaceControl, mSurfaceWidth,
+                                mSurfaceHeight);
+                    }
                 }
 
                 surfaceUpdateTransaction.setDesintationFrame(mBlastSurfaceControl, mSurfaceWidth,
@@ -941,7 +951,7 @@
                             mScreenRect.height() / (float) mSurfaceHeight /*postScaleY*/);
                 }
                 if (DEBUG_POSITION) {
-                    Log.d(TAG, String.format(
+                    Log.d(TAG, TextUtils.formatSimple(
                             "%d performSurfaceTransaction %s "
                                 + "position = [%d, %d, %d, %d] surfaceSize = %dx%d",
                             System.identityHashCode(this),
@@ -1453,6 +1463,7 @@
     }
 
     private final Rect mRTLastReportedPosition = new Rect();
+    private final Rect mRTLastSetCrop = new Rect();
 
     private class SurfaceViewPositionUpdateListener implements RenderNode.PositionUpdateListener {
         private final int mRtSurfaceWidth;
@@ -1496,6 +1507,45 @@
         }
 
         @Override
+        public void positionChanged(long frameNumber, int left, int top, int right, int bottom,
+                int clipLeft, int clipTop, int clipRight, int clipBottom) {
+            try {
+                if (DEBUG_POSITION) {
+                    Log.d(TAG, String.format(
+                            "%d updateSurfacePosition RenderWorker, frameNr = %d, "
+                                    + "position = [%d, %d, %d, %d] clip = [%d, %d, %d, %d] "
+                                    + "surfaceSize = %dx%d",
+                            System.identityHashCode(SurfaceView.this), frameNumber,
+                            left, top, right, bottom, clipLeft, clipTop, clipRight, clipBottom,
+                            mRtSurfaceWidth, mRtSurfaceHeight));
+                }
+                synchronized (mSurfaceControlLock) {
+                    if (mSurfaceControl == null) return;
+
+                    mRTLastReportedPosition.set(left, top, right, bottom);
+                    onSetSurfacePositionAndScale(mPositionChangedTransaction, mSurfaceControl,
+                            mRTLastReportedPosition.left /*positionLeft*/,
+                            mRTLastReportedPosition.top /*positionTop*/,
+                            mRTLastReportedPosition.width()
+                                    / (float) mRtSurfaceWidth /*postScaleX*/,
+                            mRTLastReportedPosition.height()
+                                    / (float) mRtSurfaceHeight /*postScaleY*/);
+
+                    mRTLastSetCrop.set(clipLeft, clipTop, clipRight, clipBottom);
+                    mPositionChangedTransaction.setCrop(mSurfaceControl, mRTLastSetCrop);
+                    if (mRTLastSetCrop.isEmpty()) {
+                        mPositionChangedTransaction.hide(mSurfaceControl);
+                    } else {
+                        mPositionChangedTransaction.show(mSurfaceControl);
+                    }
+                }
+                applyOrMergeTransaction(mPositionChangedTransaction, frameNumber);
+            } catch (Exception ex) {
+                Log.e(TAG, "Exception from repositionChild", ex);
+            }
+        }
+
+        @Override
         public void applyStretch(long frameNumber, float width, float height,
                 float vecX, float vecY, float maxStretchX, float maxStretchY,
                 float childRelativeLeft, float childRelativeTop, float childRelativeRight,
diff --git a/graphics/java/android/graphics/RenderNode.java b/graphics/java/android/graphics/RenderNode.java
index 15d26eb..2732569 100644
--- a/graphics/java/android/graphics/RenderNode.java
+++ b/graphics/java/android/graphics/RenderNode.java
@@ -272,6 +272,17 @@
         void positionChanged(long frameNumber, int left, int top, int right, int bottom);
 
         /**
+         * Called by native by a Rendering Worker thread to update window position; includes
+         * the local rect that represents the clipped area of the RenderNode's bounds.
+         *
+         * @hide
+         */
+        default void positionChanged(long frameNumber, int left, int top, int right, int bottom,
+                int clipLeft, int clipTop, int clipRight, int clipBottom) {
+            positionChanged(frameNumber, left, top, right, bottom);
+        }
+
+        /**
          * Called by JNI
          *
          * @hide */
@@ -287,6 +298,23 @@
         }
 
         /**
+         * Called by JNI
+         *
+         * @hide */
+        static boolean callPositionChanged2(WeakReference<PositionUpdateListener> weakListener,
+                long frameNumber, int left, int top, int right, int bottom,
+                int clipLeft, int clipTop, int clipRight, int clipBottom) {
+            final PositionUpdateListener listener = weakListener.get();
+            if (listener != null) {
+                listener.positionChanged(frameNumber, left, top, right, bottom, clipLeft,
+                        clipTop, clipRight, clipBottom);
+                return true;
+            } else {
+                return false;
+            }
+        }
+
+        /**
          * Call to apply a stretch effect to any child SurfaceControl layers
          *
          * TODO: Fold this into positionChanged & have HWUI do the ASurfaceControl calls?
@@ -371,6 +399,15 @@
         }
 
         @Override
+        public void positionChanged(long frameNumber, int left, int top, int right, int bottom,
+                int clipLeft, int clipTop, int clipRight, int clipBottom) {
+            for (PositionUpdateListener pul : mListeners) {
+                pul.positionChanged(frameNumber, left, top, right, bottom, clipLeft, clipTop,
+                        clipRight, clipBottom);
+            }
+        }
+
+        @Override
         public void positionLost(long frameNumber) {
             for (PositionUpdateListener pul : mListeners) {
                 pul.positionLost(frameNumber);
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(