Merge "Convert ScrollCaptureViewHelper#requestScroll to async"

This commit is contained in:
Mark Renouf
2021-10-20 00:44:30 +00:00
committed by Android (Google) Code Review
8 changed files with 113 additions and 77 deletions

View File

@@ -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

View File

@@ -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);
}
/**

View File

@@ -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.
*
@@ -98,21 +101,22 @@ public interface ScrollCaptureViewHelper<V extends View> {
/**
* Map the request onto the screen.
* <p>
* 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<ScrollResult> resultConsumer);
/**
* Restore the target after capture.

View File

@@ -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:
/* 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

View File

@@ -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) {

View File

@@ -23,8 +23,11 @@ import static java.lang.Math.min;
import android.annotation.NonNull;
import android.graphics.Rect;
import android.os.CancellationSignal;
import android.webkit.WebView;
import java.util.function.Consumer;
/**
* ScrollCapture for WebView.
*/
@@ -51,8 +54,9 @@ public class WebViewCaptureHelper implements ScrollCaptureViewHelper<WebView> {
@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<ScrollResult> resultConsumer) {
int scrollDelta = view.getScrollY() - mOriginScrollY;
@@ -64,7 +68,7 @@ public class WebViewCaptureHelper implements ScrollCaptureViewHelper<WebView> {
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<WebView> {
result.availableArea = new Rect(mRequestWebViewLocal);
result.availableArea.offset(0, result.scrollDelta);
}
return result;
resultConsumer.accept(result);
}
@Override

View File

@@ -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<T extends View, H extends ScrollCaptu
private T mTarget;
private Rect mScrollBounds;
private H mHelper;
private CancellationSignal mCancellationSignal;
private Instrumentation mInstrumentation;
@@ -96,6 +101,7 @@ public abstract class AbsCaptureHelperTest<T extends View, H extends ScrollCaptu
mInstrumentation = InstrumentationRegistry.getInstrumentation();
Context context = mInstrumentation.getTargetContext();
mWm = context.getSystemService(WindowManager.class);
mCancellationSignal = new CancellationSignal();
// Instantiate parent view on the main thread
mInstrumentation.runOnMainSync(() -> mContentRoot = new FrameLayout(context));
@@ -157,7 +163,6 @@ public abstract class AbsCaptureHelperTest<T extends View, H extends ScrollCaptu
}
@Test
@UiThreadTest
public void onScrollRequested_up_fromTop() {
initHelper(ScrollPosition.TOP);
@@ -168,7 +173,6 @@ public abstract class AbsCaptureHelperTest<T extends View, H extends ScrollCaptu
}
@Test
@UiThreadTest
public void onScrollRequested_down_fromTop() {
initHelper(ScrollPosition.TOP);
Rect request = new Rect(0, WINDOW_HEIGHT, WINDOW_WIDTH, WINDOW_HEIGHT + CAPTURE_HEIGHT);
@@ -182,7 +186,6 @@ public abstract class AbsCaptureHelperTest<T extends View, H extends ScrollCaptu
}
@Test
@UiThreadTest
public void onScrollRequested_up_fromMiddle() {
initHelper(ScrollPosition.MIDDLE);
@@ -197,7 +200,6 @@ public abstract class AbsCaptureHelperTest<T extends View, H extends ScrollCaptu
}
@Test
@UiThreadTest
public void onScrollRequested_down_fromMiddle() {
initHelper(ScrollPosition.MIDDLE);
@@ -212,7 +214,6 @@ public abstract class AbsCaptureHelperTest<T extends View, H extends ScrollCaptu
}
@Test
@UiThreadTest
public void onScrollRequested_up_fromBottom() {
initHelper(ScrollPosition.BOTTOM);
@@ -227,7 +228,6 @@ public abstract class AbsCaptureHelperTest<T extends View, H extends ScrollCaptu
}
@Test
@UiThreadTest
public void onScrollRequested_down_fromBottom() {
initHelper(ScrollPosition.BOTTOM);
@@ -242,7 +242,6 @@ public abstract class AbsCaptureHelperTest<T extends View, H extends ScrollCaptu
}
@Test
@UiThreadTest
public void onScrollRequested_offTopEdge() {
initHelper(ScrollPosition.TOP);
@@ -262,7 +261,6 @@ public abstract class AbsCaptureHelperTest<T extends View, H extends ScrollCaptu
}
@Test
@UiThreadTest
public void onScrollRequested_offBottomEdge() {
initHelper(ScrollPosition.BOTTOM);
@@ -279,7 +277,7 @@ public abstract class AbsCaptureHelperTest<T extends View, H extends ScrollCaptu
}
@After
public final void removeWindow() throws InterruptedException {
public final void removeWindow() {
mInstrumentation.runOnMainSync(() -> {
if (mContentRoot != null && mContentRoot.isAttachedToWindow()) {
mWm.removeViewImmediate(mContentRoot);
@@ -288,17 +286,36 @@ public abstract class AbsCaptureHelperTest<T extends View, H extends ScrollCaptu
}
private void initHelper(ScrollPosition position) {
setInitialScrollPosition(mTarget, position);
mHelper = createHelper();
mScrollBounds = mHelper.onComputeScrollBounds(mTarget);
mHelper.onPrepareForStart(mTarget, mScrollBounds);
mInstrumentation.runOnMainSync(() -> {
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<ScrollResult> 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;
}

View File

@@ -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<View> mViewHelper = new ScrollCaptureViewHelper<View>() {
@@ -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<ScrollResult> resultConsumer) {
resultConsumer.accept(new ScrollResult());
}
@Override