diff --git a/core/java/com/android/internal/view/ListViewCaptureHelper.java b/core/java/com/android/internal/view/ListViewCaptureHelper.java index f4a5b7181224a..11ed8207b7db5 100644 --- a/core/java/com/android/internal/view/ListViewCaptureHelper.java +++ b/core/java/com/android/internal/view/ListViewCaptureHelper.java @@ -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 } @Override - public ScrollResult onScrollRequested(@NonNull ListView listView, Rect scrollBounds, - Rect requestRect) { + public void onScrollRequested(@NonNull ListView listView, Rect scrollBounds, + Rect requestRect, CancellationSignal signal, Consumer resultConsumer) { Log.d(TAG, "-----------------------------------------------------------"); Log.d(TAG, "onScrollRequested(scrollBounds=" + scrollBounds + ", " + "requestRect=" + requestRect + ")"); @@ -69,7 +72,8 @@ public class ListViewCaptureHelper implements ScrollCaptureViewHelper 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 mScrollDelta, scrollBounds, requestedContainerBounds); } Log.d(TAG, "-----------------------------------------------------------"); - return result; + resultConsumer.accept(result); } @Override diff --git a/core/java/com/android/internal/view/RecyclerViewCaptureHelper.java b/core/java/com/android/internal/view/RecyclerViewCaptureHelper.java index 64622f098599f..8192ffd102308 100644 --- a/core/java/com/android/internal/view/RecyclerViewCaptureHelper.java +++ b/core/java/com/android/internal/view/RecyclerViewCaptureHelper.java @@ -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 RecyclerView-like ViewGroups. *

@@ -61,8 +64,8 @@ public class RecyclerViewCaptureHelper implements ScrollCaptureViewHelper resultConsumer) { ScrollResult result = new ScrollResult(); result.requestedArea = new Rect(requestRect); result.scrollDelta = mScrollDelta; @@ -70,7 +73,8 @@ public class RecyclerViewCaptureHelper implements ScrollCaptureViewHelper { /** * Map the request onto the screen. *

- * Given a rect describing the area to capture, relative to scrollBounds, take actions + * Given a rect describing the area to capture, relative to scrollBounds, take actions * 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 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} + * content to capture for the request + * @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 resultConsumer); /** * Restore the target after capture. diff --git a/core/java/com/android/internal/view/ScrollCaptureViewSupport.java b/core/java/com/android/internal/view/ScrollCaptureViewSupport.java index 2f25d60bf3aec..94a8ae5a8a67c 100644 --- a/core/java/com/android/internal/view/ScrollCaptureViewSupport.java +++ b/core/java/com/android/internal/view/ScrollCaptureViewSupport.java @@ -249,46 +249,38 @@ public class ScrollCaptureViewSupport 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 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: - /* 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; - } - // No buffer will be produced. - onComplete.accept(new Rect(/* empty */)); - } - }; - - view.postOnAnimationDelayed(captureAction, mPostScrollDelayMillis); + int result = mRenderer.renderView(view, viewCaptureArea); + if (result == HardwareRenderer.SYNC_OK + || result == HardwareRenderer.SYNC_REDRAW_REQUESTED) { + /* Frame synced, buffer will be produced... notify client. */ + onComplete.accept(new Rect(scrollResult.availableArea)); + } else { + // No buffer will be produced. + Log.e(TAG, "syncAndDraw(): SyncAndDrawResult = " + result); + onComplete.accept(new Rect(/* empty */)); + } } @Override diff --git a/core/java/com/android/internal/view/ScrollViewCaptureHelper.java b/core/java/com/android/internal/view/ScrollViewCaptureHelper.java index db7881f59dd97..c8ff28334d451 100644 --- a/core/java/com/android/internal/view/ScrollViewCaptureHelper.java +++ b/core/java/com/android/internal/view/ScrollViewCaptureHelper.java @@ -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 ScrollView-like ViewGroups. *

@@ -60,8 +63,8 @@ public class ScrollViewCaptureHelper implements ScrollCaptureViewHelper resultConsumer) { /* +---------+ <----+ Content [25,25 - 275,1025] (w=250,h=1000) | | @@ -105,7 +108,8 @@ public class ScrollViewCaptureHelper implements ScrollCaptureViewHelper { @NonNull @Override - public ScrollResult onScrollRequested(@NonNull WebView view, @NonNull Rect scrollBounds, - @NonNull Rect requestRect) { + public void onScrollRequested(@NonNull WebView view, @NonNull Rect scrollBounds, + @NonNull Rect requestRect, CancellationSignal cancellationSignal, + Consumer resultConsumer) { int scrollDelta = view.getScrollY() - mOriginScrollY; @@ -64,7 +68,7 @@ public class WebViewCaptureHelper implements ScrollCaptureViewHelper { mWebViewBounds.set(0, 0, view.getWidth(), view.getHeight()); if (!view.isVisibleToUser()) { - return result; + resultConsumer.accept(result); } // Map the request into local coordinates @@ -88,7 +92,7 @@ public class WebViewCaptureHelper implements ScrollCaptureViewHelper { result.availableArea = new Rect(mRequestWebViewLocal); result.availableArea.offset(0, result.scrollDelta); } - return result; + resultConsumer.accept(result); } @Override diff --git a/core/tests/coretests/src/com/android/internal/view/AbsCaptureHelperTest.java b/core/tests/coretests/src/com/android/internal/view/AbsCaptureHelperTest.java index 42b2b16fd7a88..c76e24c043e26 100644 --- a/core/tests/coretests/src/com/android/internal/view/AbsCaptureHelperTest.java +++ b/core/tests/coretests/src/com/android/internal/view/AbsCaptureHelperTest.java @@ -31,6 +31,7 @@ import android.app.Instrumentation; import android.content.Context; import android.graphics.PixelFormat; import android.graphics.Rect; +import android.os.CancellationSignal; import android.util.Log; import android.view.Gravity; import android.view.View; @@ -38,7 +39,6 @@ import android.view.ViewGroup; import android.view.WindowManager; import android.widget.FrameLayout; -import androidx.test.annotation.UiThreadTest; import androidx.test.platform.app.InstrumentationRegistry; import com.android.internal.view.ScrollCaptureViewHelper.ScrollResult; @@ -47,6 +47,10 @@ import org.junit.After; import org.junit.Before; import org.junit.Test; +import java.util.concurrent.CountDownLatch; +import java.util.concurrent.TimeUnit; +import java.util.concurrent.atomic.AtomicReference; + /** * This test contains a set of operations designed to verify the behavior of a * ScrollCaptureViewHelper implementation. Subclasses define and initialize @@ -88,6 +92,7 @@ public abstract class AbsCaptureHelperTest mContentRoot = new FrameLayout(context)); @@ -157,7 +163,6 @@ public abstract class AbsCaptureHelperTest { if (mContentRoot != null && mContentRoot.isAttachedToWindow()) { mWm.removeViewImmediate(mContentRoot); @@ -288,17 +286,36 @@ public abstract class AbsCaptureHelperTest { + setInitialScrollPosition(mTarget, position); + mScrollBounds = mHelper.onComputeScrollBounds(mTarget); + mHelper.onPrepareForStart(mTarget, mScrollBounds); + }); } @NonNull - private ScrollResult requestScrollSync(H helper, Rect scrollBounds, Rect request) { - helper.onPrepareForStart(mTarget, scrollBounds); - ScrollResult result = helper.onScrollRequested(mTarget, scrollBounds, request); - + private ScrollResult requestScrollSync(H helper, Rect scrollBounds, Rect request) { + AtomicReference resultRef = new AtomicReference<>(); + CountDownLatch latch = new CountDownLatch(1); + mInstrumentation.runOnMainSync(() -> { + helper.onPrepareForStart(mTarget, scrollBounds); + helper.onScrollRequested(mTarget, scrollBounds, request, mCancellationSignal, + (result) -> { + resultRef.set(result); + latch.countDown(); + }); + }); + try { + if (!latch.await(5, TimeUnit.SECONDS)) { + mCancellationSignal.cancel(); + fail("Timeout waiting for ScrollResult"); + } + } catch (InterruptedException e) { + mCancellationSignal.cancel(); + fail("Interrupted!"); + } + ScrollResult result = resultRef.get(); assertNotNull(result); return result; } diff --git a/core/tests/coretests/src/com/android/internal/view/ScrollCaptureViewSupportTest.java b/core/tests/coretests/src/com/android/internal/view/ScrollCaptureViewSupportTest.java index 699008b75a663..8409958e29f91 100644 --- a/core/tests/coretests/src/com/android/internal/view/ScrollCaptureViewSupportTest.java +++ b/core/tests/coretests/src/com/android/internal/view/ScrollCaptureViewSupportTest.java @@ -20,6 +20,7 @@ import static org.junit.Assert.assertEquals; import android.content.Context; import android.graphics.Rect; +import android.os.CancellationSignal; import android.view.View; import android.view.ViewGroup; @@ -28,6 +29,8 @@ import androidx.test.platform.app.InstrumentationRegistry; import org.junit.Test; +import java.util.function.Consumer; + public class ScrollCaptureViewSupportTest { ScrollCaptureViewHelper mViewHelper = new ScrollCaptureViewHelper() { @@ -42,9 +45,10 @@ public class ScrollCaptureViewSupportTest { @NonNull @Override - public ScrollResult onScrollRequested(@NonNull View view, @NonNull Rect scrollBounds, - @NonNull Rect requestRect) { - return new ScrollResult(); + public void onScrollRequested(@NonNull View view, @NonNull Rect scrollBounds, + @NonNull Rect requestRect, CancellationSignal signal, + Consumer resultConsumer) { + resultConsumer.accept(new ScrollResult()); } @Override