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

Commit 38d2ec41 authored by Mark Renouf's avatar Mark Renouf
Browse files

Convert ScrollCaptureViewHelper#requestScroll to async

This is a prerequisite for supporting longer scrolling intervals
to allow scrolling in steps, allowing the main thread to loop in
between.

This also provides an opportunity for a ScrollCaptureViewHelper
instance to delay until content stabilizes (layout, image loading,
etc).

Bug: 195109744
Test: atest ListViewCaptureHelperTest ScrollViewCaptureHelperTest \
      RecyclerViewCaptureHelperTest WebViewCaptureHelperTest
Change-Id: Ia7c146338472b958e85992f39941ec844115d66f
parent 2967ca7d
Loading
Loading
Loading
Loading
+8 −4
Original line number Diff line number Diff line
@@ -23,10 +23,13 @@ import static com.android.internal.view.ScrollCaptureViewSupport.transformFromRe

import android.annotation.NonNull;
import android.graphics.Rect;
import android.os.CancellationSignal;
import android.util.Log;
import android.view.View;
import android.widget.ListView;

import java.util.function.Consumer;

/**
 * Scroll capture support for ListView.
 *
@@ -56,8 +59,8 @@ public class ListViewCaptureHelper implements ScrollCaptureViewHelper<ListView>
    }

    @Override
    public ScrollResult onScrollRequested(@NonNull ListView listView, Rect scrollBounds,
            Rect requestRect) {
    public void onScrollRequested(@NonNull ListView listView, Rect scrollBounds,
            Rect requestRect, CancellationSignal signal, Consumer<ScrollResult> resultConsumer) {
        Log.d(TAG, "-----------------------------------------------------------");
        Log.d(TAG, "onScrollRequested(scrollBounds=" + scrollBounds + ", "
                + "requestRect=" + requestRect + ")");
@@ -69,7 +72,8 @@ public class ListViewCaptureHelper implements ScrollCaptureViewHelper<ListView>

        if (!listView.isVisibleToUser() || listView.getChildCount() == 0) {
            Log.w(TAG, "listView is empty or not visible, cannot continue");
            return result; // result.availableArea == empty Rect
            resultConsumer.accept(result);  // result.availableArea == empty Rect
            return;
        }

        // Make requestRect relative to RecyclerView (from scrollBounds)
@@ -117,7 +121,7 @@ public class ListViewCaptureHelper implements ScrollCaptureViewHelper<ListView>
                    mScrollDelta, scrollBounds, requestedContainerBounds);
        }
        Log.d(TAG, "-----------------------------------------------------------");
        return result;
        resultConsumer.accept(result);
    }

    @Override
+12 −6
Original line number Diff line number Diff line
@@ -18,11 +18,14 @@ package com.android.internal.view;

import android.annotation.NonNull;
import android.graphics.Rect;
import android.os.CancellationSignal;
import android.util.Log;
import android.view.View;
import android.view.ViewGroup;
import android.view.ViewParent;

import java.util.function.Consumer;

/**
 * ScrollCapture for RecyclerView and <i>RecyclerView-like</i> ViewGroups.
 * <p>
@@ -61,8 +64,8 @@ public class RecyclerViewCaptureHelper implements ScrollCaptureViewHelper<ViewGr
    }

    @Override
    public ScrollResult onScrollRequested(@NonNull ViewGroup recyclerView, Rect scrollBounds,
            Rect requestRect) {
    public void onScrollRequested(@NonNull ViewGroup recyclerView, Rect scrollBounds,
            Rect requestRect, CancellationSignal signal, Consumer<ScrollResult> resultConsumer) {
        ScrollResult result = new ScrollResult();
        result.requestedArea = new Rect(requestRect);
        result.scrollDelta = mScrollDelta;
@@ -70,7 +73,8 @@ public class RecyclerViewCaptureHelper implements ScrollCaptureViewHelper<ViewGr

        if (!recyclerView.isVisibleToUser() || recyclerView.getChildCount() == 0) {
            Log.w(TAG, "recyclerView is empty or not visible, cannot continue");
            return result; // result.availableArea == empty Rect
            resultConsumer.accept(result); // result.availableArea == empty Rect
            return;
        }

        // move from scrollBounds-relative to parent-local coordinates
@@ -83,7 +87,8 @@ public class RecyclerViewCaptureHelper implements ScrollCaptureViewHelper<ViewGr
        View anchor = findChildNearestTarget(recyclerView, requestedContainerBounds);
        if (anchor == null) {
            Log.w(TAG, "Failed to locate anchor view");
            return result; // result.availableArea == empty rect
            resultConsumer.accept(result); // result.availableArea == empty rect
            return;
        }

        Rect requestedContentBounds = new Rect(requestedContainerBounds);
@@ -113,13 +118,14 @@ public class RecyclerViewCaptureHelper implements ScrollCaptureViewHelper<ViewGr

        if (!requestedContainerBounds.intersect(recyclerLocalVisible)) {
            // Requested area is still not visible
            return result;
            resultConsumer.accept(result);
            return;
        }
        Rect available = new Rect(requestedContainerBounds);
        available.offset(-scrollBounds.left, -scrollBounds.top);
        available.offset(0, mScrollDelta);
        result.availableArea = available;
        return result;
        resultConsumer.accept(result);
    }

    /**
+11 −7
Original line number Diff line number Diff line
@@ -18,9 +18,12 @@ package com.android.internal.view;

import android.annotation.NonNull;
import android.graphics.Rect;
import android.os.CancellationSignal;
import android.view.View;
import android.view.ViewGroup;

import java.util.function.Consumer;

/**
 * Provides view-specific handling to ScrollCaptureViewSupport.
 *
@@ -102,17 +105,18 @@ public interface ScrollCaptureViewHelper<V extends View> {
     * necessary to bring the content within the rectangle into the visible area of the view if
     * 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 result of the request as a {@link ScrollResult}
     * @param cancellationSignal allows for the request to be cancelled by the caller
     * @param resultConsumer accepts the result of the request as a {@link ScrollResult}
     */
    @NonNull
    ScrollResult onScrollRequested(@NonNull V view, @NonNull Rect scrollBounds,
            @NonNull Rect requestRect);
    void onScrollRequested(@NonNull V view, @NonNull Rect scrollBounds,
            @NonNull Rect requestRect, CancellationSignal cancellationSignal,
            Consumer<ScrollResult> resultConsumer);

    /**
     * Restore the target after capture.
+23 −31
Original line number Diff line number Diff line
@@ -249,46 +249,38 @@ public class ScrollCaptureViewSupport<V extends View> implements ScrollCaptureCa
        }

        // Ask the view to scroll as needed to bring this area into view.
        ScrollResult scrollResult = mViewHelper.onScrollRequested(view, session.getScrollBounds(),
                requestRect);
        mViewHelper.onScrollRequested(view, session.getScrollBounds(), requestRect, signal,
                (result) -> onScrollResult(result, view, signal, onComplete));
    }

    private void onScrollResult(ScrollResult scrollResult, V view, CancellationSignal signal,
            Consumer<Rect> onComplete) {

        if (signal.isCanceled()) {
            Log.w(TAG, "onScrollCaptureImageRequest: cancelled! skipping render.");
            return;
        }

        if (scrollResult.availableArea.isEmpty()) {
            onComplete.accept(scrollResult.availableArea);
            return;
        }

        // For image capture, shift back by scrollDelta to arrive at the location within the view
        // where the requested content will be drawn
        // 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);

        Runnable captureAction = () -> {
            if (signal.isCanceled()) {
                Log.w(TAG, "onScrollCaptureImageRequest: cancelled! skipping render.");
            } else {
        int result = mRenderer.renderView(view, viewCaptureArea);
                switch (result) {
                    case HardwareRenderer.SYNC_OK:
                    case HardwareRenderer.SYNC_REDRAW_REQUESTED:
        if (result == HardwareRenderer.SYNC_OK
                || result == HardwareRenderer.SYNC_REDRAW_REQUESTED) {
            /* Frame synced, buffer will be produced... notify client. */
            onComplete.accept(new Rect(scrollResult.availableArea));
                        return;
                    case HardwareRenderer.SYNC_FRAME_DROPPED:
                        Log.e(TAG, "syncAndDraw(): SYNC_FRAME_DROPPED !");
                        break;
                    case HardwareRenderer.SYNC_LOST_SURFACE_REWARD_IF_FOUND:
                        Log.e(TAG, "syncAndDraw(): SYNC_LOST_SURFACE !");
                        break;
                    case HardwareRenderer.SYNC_CONTEXT_IS_STOPPED:
                        Log.e(TAG, "syncAndDraw(): SYNC_CONTEXT_IS_STOPPED !");
                        break;
                }
        } else {
            // No buffer will be produced.
            Log.e(TAG, "syncAndDraw(): SyncAndDrawResult = " + result);
            onComplete.accept(new Rect(/* empty */));
        }
        };

        view.postOnAnimationDelayed(captureAction, mPostScrollDelayMillis);
    }

    @Override
+10 −5
Original line number Diff line number Diff line
@@ -19,10 +19,13 @@ package com.android.internal.view;
import android.annotation.NonNull;
import android.graphics.Point;
import android.graphics.Rect;
import android.os.CancellationSignal;
import android.view.View;
import android.view.ViewGroup;
import android.view.ViewParent;

import java.util.function.Consumer;

/**
 * ScrollCapture for ScrollView and <i>ScrollView-like</i> ViewGroups.
 * <p>
@@ -60,8 +63,8 @@ public class ScrollViewCaptureHelper implements ScrollCaptureViewHelper<ViewGrou
        }
    }

    public ScrollResult onScrollRequested(@NonNull ViewGroup view, Rect scrollBounds,
            Rect requestRect) {
    public void onScrollRequested(@NonNull ViewGroup view, Rect scrollBounds,
            Rect requestRect, CancellationSignal signal, Consumer<ScrollResult> resultConsumer) {
        /*
               +---------+ <----+ Content [25,25 - 275,1025] (w=250,h=1000)
               |         |
@@ -105,7 +108,8 @@ public class ScrollViewCaptureHelper implements ScrollCaptureViewHelper<ViewGrou
        final View contentView = view.getChildAt(0); // returns null, does not throw IOOBE
        if (contentView == null) {
            // No child view? Cannot continue.
            return result;
            resultConsumer.accept(result);
            return;
        }

        //  1) Translate request rect to make it relative to container view
@@ -155,7 +159,8 @@ public class ScrollViewCaptureHelper implements ScrollCaptureViewHelper<ViewGrou
        if (!view.getChildVisibleRect(contentView, available, offset)) {
            available.setEmpty();
            result.availableArea = available;
            return result;
            resultConsumer.accept(result);
            return;
        }
        // Transform back from global to content-view local
        available.offset(-offset.x, -offset.y);
@@ -174,7 +179,7 @@ public class ScrollViewCaptureHelper implements ScrollCaptureViewHelper<ViewGrou
        available.offset(0, scrollDelta);

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

    public void onPrepareForEnd(@NonNull ViewGroup view) {
Loading