Donate to e Foundation | Murena handsets with /e/OS | Own a part of Murena! Learn more

Commit d6bcd0e1 authored by Mark Renouf's avatar Mark Renouf Committed by Android (Google) Code Review
Browse files

Merge "Fix area rendered by ScrollCaptureViewSupport"

parents 64138a94 af948d55
Loading
Loading
Loading
Loading
+34 −3
Original line number Diff line number Diff line
@@ -25,6 +25,36 @@ interface ScrollCaptureViewHelper<V extends View> {
    int UP = -1;
    int DOWN = 1;

    /**
     * Contains the result of a scroll request.
     */
    class ScrollResult {
        /**
         * The area requested in pixels, within {@link #onComputeScrollBounds scroll bounds}, with
         * top/bottom relative to the scroll position at the start of capture.
         */
        public Rect requestedArea;
        /**
         * The area, in pixels of the request which is visible and available for capture. In the
         * same coordinate space as {@link #requestedArea}.
         */
        public Rect availableArea;
        /**
         * The updated scroll delta (the relative distance, in pixels that the scroll position has
         * moved from the starting position since capture started).
         */
        public int scrollDelta; // visible top offset from start

        @Override
        public String toString() {
            return "ScrollResult{"
                    + "requestedArea=" + requestedArea
                    + ", availableArea=" + availableArea
                    + ", scrollDelta=" + scrollDelta
                    + '}';
        }
    }

    /**
     * Verifies that the view is still visible and scrollable. If true is returned here, expect a
     * call to {@link #onComputeScrollBounds(View)} to follow.
@@ -48,6 +78,7 @@ interface ScrollCaptureViewHelper<V extends View> {
                view.getWidth() - view.getPaddingRight(),
                view.getHeight() - view.getPaddingBottom());
    }

    /**
     * Adjust the target for capture.
     * <p>
@@ -67,14 +98,14 @@ interface ScrollCaptureViewHelper<V extends View> {
     * needed and return the resulting rectangle describing the position and bounds of the area
     * which is visible.
     *
     * @param view the view being captured
     * @param scrollBounds the area in which scrolling content moves, local to the {@code containing
     *                     view}
     * @param requestRect  the area relative to {@code scrollBounds} which describes the location of
     *                     content to capture for the request
     * @return the visible area within scrollBounds of the requested rectangle, return {@code null}
     * in the case of an unrecoverable error condition, to abort the capture process
     * @return the result of the request as a {@link ScrollResult}
     */
    Rect onScrollRequested(@NonNull V view, Rect scrollBounds, Rect requestRect);
    ScrollResult onScrollRequested(@NonNull V view, Rect scrollBounds, Rect requestRect);

    /**
     * Restore the target after capture.
+57 −37
Original line number Diff line number Diff line
@@ -30,21 +30,24 @@ import android.view.ScrollCaptureSession;
import android.view.Surface;
import android.view.View;

import com.android.internal.view.ScrollCaptureViewHelper.ScrollResult;

import java.lang.ref.WeakReference;
import java.util.function.Consumer;

/**
 * Provides a ScrollCaptureCallback implementation for to handle arbitrary View-based scrolling
 * containers.
 * <p>
 * To use this class, supply the target view and an implementation of {@ScrollCaptureViewHelper}
 * to the callback.
 * Provides a base ScrollCaptureCallback implementation to handle arbitrary View-based scrolling
 * containers. This class handles the bookkeeping aspects of {@link ScrollCaptureCallback}
 * including rendering output using HWUI. Adaptable to any {@link View} using
 * {@link ScrollCaptureViewHelper}.
 *
 * @param <V> the specific View subclass handled
 * @hide
 * @see ScrollCaptureViewHelper
 */
public class ScrollCaptureViewSupport<V extends View> implements ScrollCaptureCallback {

    private static final String TAG = "ScrollCaptureViewSupport";

    private final WeakReference<V> mWeakView;
    private final ScrollCaptureViewHelper<V> mViewHelper;
    private ViewRenderer mRenderer;
@@ -52,11 +55,6 @@ public class ScrollCaptureViewSupport<V extends View> implements ScrollCaptureCa
    private boolean mStarted;
    private boolean mEnded;

    static <V extends View> ScrollCaptureCallback createCallback(V view,
            ScrollCaptureViewHelper<V> impl) {
        return new ScrollCaptureViewSupport<>(view, impl);
    }

    ScrollCaptureViewSupport(V containingView, ScrollCaptureViewHelper<V> viewHelper) {
        mWeakView = new WeakReference<>(containingView);
        mRenderer = new ViewRenderer();
@@ -82,6 +80,7 @@ public class ScrollCaptureViewSupport<V extends View> implements ScrollCaptureCa
    @Override
    public final void onScrollCaptureStart(ScrollCaptureSession session, Runnable onReady) {
        V view = mWeakView.get();

        mEnded = false;
        mStarted = true;

@@ -103,21 +102,30 @@ public class ScrollCaptureViewSupport<V extends View> implements ScrollCaptureCa
            session.notifyBufferSent(0, null);
            return;
        }
        Rect captureArea = mViewHelper.onScrollRequested(view, session.getScrollBounds(),
        // Ask the view to scroll as needed to bring this area into view.
        ScrollResult scrollResult = mViewHelper.onScrollRequested(view, session.getScrollBounds(),
                requestRect);
        mRenderer.renderFrame(view, captureArea, mUiHandler,
                () -> session.notifyBufferSent(0, captureArea));
        view.invalidate(); // don't wait for vsync

        // For image capture, shift back by scrollDelta to arrive at the location within the view
        // where the requested content will be drawn
        Rect viewCaptureArea = new Rect(scrollResult.availableArea);
        viewCaptureArea.offset(0, -scrollResult.scrollDelta);

        mRenderer.renderView(view, viewCaptureArea, mUiHandler,
                (frameNumber) -> session.notifyBufferSent(frameNumber, scrollResult.availableArea));
    }

    @Override
    public final void onScrollCaptureEnd(Runnable onReady) {
        V view = mWeakView.get();
        if (mStarted && !mEnded) {
            if (view != null) {
                mViewHelper.onPrepareForEnd(view);
            /* empty */
                view.invalidate();
            }
            mEnded = true;
            mRenderer.trimMemory();
            mRenderer.setSurface(null);
            mRenderer.destroy();
        }
        onReady.run();
    }
@@ -142,7 +150,7 @@ public class ScrollCaptureViewSupport<V extends View> implements ScrollCaptureCa
        private static final String TAG = "ViewRenderer";

        private HardwareRenderer mRenderer;
        private RenderNode mRootRenderNode;
        private RenderNode mCaptureRenderNode;
        private final RectF mTempRectF = new RectF();
        private final Rect mSourceRect = new Rect();
        private final Rect mTempRect = new Rect();
@@ -151,10 +159,14 @@ public class ScrollCaptureViewSupport<V extends View> implements ScrollCaptureCa
        private long mLastRenderedSourceDrawingId = -1;


        public interface FrameCompleteListener {
            void onFrameComplete(long frameNumber);
        }

        ViewRenderer() {
            mRenderer = new HardwareRenderer();
            mRootRenderNode = new RenderNode("ScrollCaptureRoot");
            mRenderer.setContentRoot(mRootRenderNode);
            mCaptureRenderNode = new RenderNode("ScrollCaptureRoot");
            mRenderer.setContentRoot(mCaptureRenderNode);

            // TODO: Figure out a way to flip this on when we are sure the source window is opaque
            mRenderer.setOpaque(false);
@@ -193,18 +205,36 @@ public class ScrollCaptureViewSupport<V extends View> implements ScrollCaptureCa
            // Enable shadows for elevation/Z
            mRenderer.setLightSourceGeometry(lightX, lightY, lightZ, lightRadius);
            mRenderer.setLightSourceAlpha(AMBIENT_SHADOW_ALPHA, SPOT_SHADOW_ALPHA);
        }

        private void updateRootNode(View source, Rect localSourceRect) {
            final View rootView = source.getRootView();
            transformToRoot(source, localSourceRect, mTempRect);

            mCaptureRenderNode.setPosition(0, 0, mTempRect.width(), mTempRect.height());
            RecordingCanvas canvas = mCaptureRenderNode.beginRecording();
            canvas.enableZ();
            canvas.translate(-mTempRect.left, -mTempRect.top);

            RenderNode rootViewRenderNode = rootView.updateDisplayListIfDirty();
            if (rootViewRenderNode.hasDisplayList()) {
                canvas.drawRenderNode(rootViewRenderNode);
            }
            mCaptureRenderNode.endRecording();
        }

        public void renderFrame(View localReference, Rect sourceRect, Handler handler,
                Runnable onFrameCommitted) {
            if (updateForView(localReference)) {
                setupLighting(localReference);
        public void renderView(View view, Rect sourceRect, Handler handler,
                FrameCompleteListener frameListener) {
            if (updateForView(view)) {
                setupLighting(view);
            }
            buildRootDisplayList(localReference, sourceRect);
            view.invalidate();
            updateRootNode(view, sourceRect);
            HardwareRenderer.FrameRenderRequest request = mRenderer.createRenderRequest();
            request.setVsyncTime(SystemClock.elapsedRealtimeNanos());
            request.setFrameCommitCallback(handler::post, onFrameCommitted);
            // private API b/c request.setFrameCommitCallback does not provide access to frameNumber
            mRenderer.setFrameCompleteCallback(
                    frameNr -> handler.post(() -> frameListener.onFrameComplete(frameNr)));
            request.setWaitForPresent(true);
            request.syncAndDraw();
        }
@@ -225,15 +255,5 @@ public class ScrollCaptureViewSupport<V extends View> implements ScrollCaptureCa
            mTempRectF.round(outRect);
        }

        private void buildRootDisplayList(View source, Rect localSourceRect) {
            final View captureSource = source.getRootView();
            transformToRoot(source, localSourceRect, mTempRect);
            mRootRenderNode.setPosition(0, 0, mTempRect.width(), mTempRect.height());
            RecordingCanvas canvas = mRootRenderNode.beginRecording(mTempRect.width(),
                    mTempRect.height());
            canvas.translate(-mTempRect.left, -mTempRect.top);
            canvas.drawRenderNode(captureSource.updateDisplayListIfDirty());
            mRootRenderNode.endRecording();
        }
    }
}
+22 −18
Original line number Diff line number Diff line
@@ -35,13 +35,14 @@ import android.view.ViewParent;
 * <li>correctly implements {@link ViewParent#requestChildRectangleOnScreen(View,
 * Rect, boolean)}
 * </ul>
 *
 * @see ScrollCaptureViewSupport
 */
public class ScrollViewCaptureHelper implements ScrollCaptureViewHelper<ViewGroup> {
    private int mStartScrollY;
    private boolean mScrollBarEnabled;
    private int mOverScrollMode;

    /** @see ScrollCaptureViewHelper#onPrepareForStart(View, Rect) */
    public void onPrepareForStart(@NonNull ViewGroup view, Rect scrollBounds) {
        mStartScrollY = view.getScrollY();
        mOverScrollMode = view.getOverScrollMode();
@@ -54,8 +55,8 @@ public class ScrollViewCaptureHelper implements ScrollCaptureViewHelper<ViewGrou
        }
    }

    /** @see ScrollCaptureViewHelper#onScrollRequested(View, Rect, Rect) */
    public Rect onScrollRequested(@NonNull ViewGroup view, Rect scrollBounds, Rect requestRect) {
    public ScrollResult onScrollRequested(@NonNull ViewGroup view, Rect scrollBounds,
            Rect requestRect) {
        final View contentView = view.getChildAt(0); // returns null, does not throw IOOBE
        if (contentView == null) {
            return null;
@@ -87,6 +88,9 @@ public class ScrollViewCaptureHelper implements ScrollCaptureViewHelper<ViewGrou
            \__ Requested Bounds[0,300 - 200,400] (200x100)
       */

        ScrollResult result = new ScrollResult();
        result.requestedArea = new Rect(requestRect);

        // 0) adjust the requestRect to account for scroll change since start
        //
        //  Scroll Bounds[50,50 - 250,250]  (w=200,h=200)
@@ -117,8 +121,6 @@ public class ScrollViewCaptureHelper implements ScrollCaptureViewHelper<ViewGrou
                view.getScrollX() - contentView.getLeft(),
                view.getScrollY() - contentView.getTop());



        // requestRect is now local to contentView as requestedContentBounds
        // contentView (and each parent in turn if possible) will be scrolled
        // (if necessary) to make all of requestedContent visible, (if possible!)
@@ -126,35 +128,37 @@ public class ScrollViewCaptureHelper implements ScrollCaptureViewHelper<ViewGrou

        // update new offset between starting and current scroll position
        scrollDelta = view.getScrollY() - mStartScrollY;
        result.scrollDelta = scrollDelta;


        // TODO: adjust to avoid occlusions/minimize scroll changes
        // TODO: crop capture area to avoid occlusions/minimize scroll changes

        Point offset = new Point();
        final Rect capturedRect = new Rect(requestedContentBounds); // empty
        if (!view.getChildVisibleRect(contentView, capturedRect, offset)) {
            capturedRect.setEmpty();
            return capturedRect;
        final Rect available = new Rect(requestedContentBounds); // empty
        if (!view.getChildVisibleRect(contentView, available, offset)) {
            available.setEmpty();
            result.availableArea = available;
            return result;
        }
        // Transform back from global to content-view local
        capturedRect.offset(-offset.x, -offset.y);
        available.offset(-offset.x, -offset.y);

        // Then back to container view
        capturedRect.offset(
        available.offset(
                contentView.getLeft() - view.getScrollX(),
                contentView.getTop() - view.getScrollY());


        // And back to relative to scrollBounds
        capturedRect.offset(-scrollBounds.left, -scrollBounds.top);
        available.offset(-scrollBounds.left, -scrollBounds.top);

        // Apply scrollDelta again to return to make capturedRect relative to scrollBounds at
        // Apply scrollDelta again to return to make `available` relative to `scrollBounds` at
        // the scroll position at start of capture.
        capturedRect.offset(0, scrollDelta);
        return capturedRect;
        available.offset(0, scrollDelta);

        result.availableArea = new Rect(available);
        return result;
    }

    /** @see ScrollCaptureViewHelper#onPrepareForEnd(View)  */
    public void onPrepareForEnd(@NonNull ViewGroup view) {
        view.scrollTo(0, mStartScrollY);
        if (mOverScrollMode != View.OVER_SCROLL_NEVER) {
+104 −96

File changed.

Preview size limit exceeded, changes collapsed.