Support frame-synchronized clipping on SurfaceView

Bug: 123306815
Test: See ag/9384030, manual test of bubbles expand collapse animation
Change-Id: I5350501667fe729a0c668e8f6edaf30070a2e1ec
diff --git a/core/java/android/view/SurfaceView.java b/core/java/android/view/SurfaceView.java
index 90e69f3..9acebe1 100644
--- a/core/java/android/view/SurfaceView.java
+++ b/core/java/android/view/SurfaceView.java
@@ -166,6 +166,7 @@
 
     boolean mUseAlpha = false;
     float mSurfaceAlpha = 1f;
+    boolean mClipSurfaceToBounds;
 
     @UnsupportedAppUsage
     boolean mHaveFrame = false;
@@ -554,9 +555,52 @@
         super.dispatchDraw(canvas);
     }
 
+    /**
+     * Control whether the surface is clipped to the same bounds as the View. If true, then
+     * the bounds set by {@link #setClipBounds(Rect)} are applied to the surface as window-crop.
+     *
+     * @param enabled whether to enable surface clipping
+     * @hide
+     */
+    public void setEnableSurfaceClipping(boolean enabled) {
+        mClipSurfaceToBounds = enabled;
+        invalidate();
+    }
+
+    @Override
+    public void setClipBounds(Rect clipBounds) {
+        super.setClipBounds(clipBounds);
+
+        if (!mClipSurfaceToBounds) {
+            return;
+        }
+
+        // When cornerRadius is non-zero, a draw() is required to update
+        // the viewport (rounding the corners of the clipBounds).
+        if (mCornerRadius > 0f && !isAboveParent()) {
+            invalidate();
+        }
+
+        if (mSurfaceControl != null) {
+            if (mClipBounds != null) {
+                mTmpRect.set(mClipBounds);
+            } else {
+                mTmpRect.set(0, 0, mSurfaceWidth, mSurfaceHeight);
+            }
+            SyncRtSurfaceTransactionApplier applier = new SyncRtSurfaceTransactionApplier(this);
+            applier.scheduleApply(
+                    new SyncRtSurfaceTransactionApplier.SurfaceParams.Builder(mSurfaceControl)
+                            .withWindowCrop(mTmpRect)
+                            .build());
+        }
+    }
+
     private void clearSurfaceViewPort(Canvas canvas) {
         if (mCornerRadius > 0f) {
             canvas.getClipBounds(mTmpRect);
+            if (mClipSurfaceToBounds && mClipBounds != null) {
+                mTmpRect.intersect(mClipBounds);
+            }
             canvas.drawRoundRect(mTmpRect.left, mTmpRect.top, mTmpRect.right, mTmpRect.bottom,
                     mCornerRadius, mCornerRadius, mRoundedViewportPaint);
         } else {
@@ -582,6 +626,16 @@
     }
 
     /**
+     * Returns the corner radius for the SurfaceView.
+
+     * @return the radius of the corners in pixels
+     * @hide
+     */
+    public float getCornerRadius() {
+        return mCornerRadius;
+    }
+
+    /**
      * Control whether the surface view's surface is placed on top of another
      * regular surface view in the window (but still behind the window itself).
      * This is typically used to place overlays on top of an underlying media
@@ -832,7 +886,11 @@
                             // 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.
-                            mSurfaceControl.setWindowCrop(mSurfaceWidth, mSurfaceHeight);
+                            if (mClipSurfaceToBounds && mClipBounds != null) {
+                                mSurfaceControl.setWindowCrop(mClipBounds);
+                            } else {
+                                mSurfaceControl.setWindowCrop(mSurfaceWidth, mSurfaceHeight);
+                            }
                         }
                         mSurfaceControl.setCornerRadius(mCornerRadius);
                         if (sizeChanged && !creating) {