diff options
4 files changed, 217 insertions, 154 deletions
diff --git a/core/java/com/android/internal/view/ScrollCaptureViewHelper.java b/core/java/com/android/internal/view/ScrollCaptureViewHelper.java index 9f100bd6440f..a92e978b2fc1 100644 --- a/core/java/com/android/internal/view/ScrollCaptureViewHelper.java +++ b/core/java/com/android/internal/view/ScrollCaptureViewHelper.java @@ -26,6 +26,36 @@ interface ScrollCaptureViewHelper<V extends View> { 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. diff --git a/core/java/com/android/internal/view/ScrollCaptureViewSupport.java b/core/java/com/android/internal/view/ScrollCaptureViewSupport.java index 4087eda944e0..7b4f73ffde2b 100644 --- a/core/java/com/android/internal/view/ScrollCaptureViewSupport.java +++ b/core/java/com/android/internal/view/ScrollCaptureViewSupport.java @@ -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) { - mViewHelper.onPrepareForEnd(view); - /* empty */ + if (view != null) { + mViewHelper.onPrepareForEnd(view); + 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(); - } } } diff --git a/core/java/com/android/internal/view/ScrollViewCaptureHelper.java b/core/java/com/android/internal/view/ScrollViewCaptureHelper.java index 12bd461f810b..1514b9a285dd 100644 --- a/core/java/com/android/internal/view/ScrollViewCaptureHelper.java +++ b/core/java/com/android/internal/view/ScrollViewCaptureHelper.java @@ -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) { diff --git a/core/tests/coretests/src/com/android/internal/view/ScrollViewCaptureHelperTest.java b/core/tests/coretests/src/com/android/internal/view/ScrollViewCaptureHelperTest.java index 63a68e99b788..ab13fd7d81e0 100644 --- a/core/tests/coretests/src/com/android/internal/view/ScrollViewCaptureHelperTest.java +++ b/core/tests/coretests/src/com/android/internal/view/ScrollViewCaptureHelperTest.java @@ -21,12 +21,11 @@ import static android.view.ViewGroup.LayoutParams.WRAP_CONTENT; import static android.view.WindowManager.LayoutParams.FLAG_NOT_TOUCHABLE; import static android.view.WindowManager.LayoutParams.TYPE_APPLICATION_OVERLAY; -import static androidx.test.InstrumentationRegistry.getContext; - import static org.junit.Assert.assertEquals; import static org.junit.Assert.assertTrue; import static org.junit.Assert.fail; +import android.content.Context; import android.graphics.Color; import android.graphics.PixelFormat; import android.graphics.Rect; @@ -40,10 +39,12 @@ import android.widget.ScrollView; import android.widget.TextView; import androidx.test.annotation.UiThreadTest; +import androidx.test.platform.app.InstrumentationRegistry; + +import com.android.internal.view.ScrollCaptureViewHelper.ScrollResult; import org.junit.After; import org.junit.Before; -import org.junit.BeforeClass; import org.junit.Test; import java.util.Random; @@ -67,28 +68,27 @@ public class ScrollViewCaptureHelperTest { private Random mRandom; - private static float sDensity; - - @BeforeClass - public static void setUpClass() { - sDensity = getContext().getResources().getDisplayMetrics().density; - } + private Context mContext; + private float mDensity; @Before @UiThreadTest public void setUp() { + mContext = InstrumentationRegistry.getInstrumentation().getContext(); + mDensity = mContext.getResources().getDisplayMetrics().density; + mRandom = new Random(); - mParent = new FrameLayout(getContext()); + mParent = new FrameLayout(mContext); - mTarget = new ScrollView(getContext()); + mTarget = new ScrollView(mContext); mParent.addView(mTarget, new ViewGroup.LayoutParams(MATCH_PARENT, MATCH_PARENT)); - mContent = new LinearLayout(getContext()); + mContent = new LinearLayout(mContext); mContent.setOrientation(LinearLayout.VERTICAL); mTarget.addView(mContent, new ViewGroup.LayoutParams(MATCH_PARENT, WRAP_CONTENT)); for (int i = 0; i < CHILD_VIEWS; i++) { - TextView view = new TextView(getContext()); + TextView view = new TextView(mContext); view.setText("Child #" + i); view.setTextColor(Color.WHITE); view.setTextSize(30f); @@ -99,7 +99,7 @@ public class ScrollViewCaptureHelperTest { // Window -> Parent -> Target -> Content - mWm = getContext().getSystemService(WindowManager.class); + mWm = mContext.getSystemService(WindowManager.class); // Setup the window that we are going to use mWindowLayoutParams = new WindowManager.LayoutParams(WINDOW_WIDTH, WINDOW_HEIGHT, @@ -123,58 +123,6 @@ public class ScrollViewCaptureHelperTest { svc.onPrepareForStart(mTarget, scrollBounds); } - static void assertEmpty(Rect r) { - if (r != null && !r.isEmpty()) { - fail("Not true that " + r + " is empty"); - } - } - - static void assertContains(Rect parent, Rect child) { - if (!parent.contains(child)) { - fail("Not true that " + parent + " contains " + child); - } - } - - static void assertRectEquals(Rect parent, Rect child) { - if (!parent.equals(child)) { - fail("Not true that " + parent + " is equal to " + child); - } - } - - static Rect getVisibleRect(View v) { - Rect r = new Rect(0, 0, v.getWidth(), v.getHeight()); - v.getLocalVisibleRect(r); - return r; - } - - - static int assertScrollToY(View v, int scrollY) { - v.scrollTo(0, scrollY); - int dest = v.getScrollY(); - assertEquals(scrollY, dest); - return scrollY; - } - - - static void assertCapturedAreaCompletelyVisible(int startScrollY, Rect requestRect, - Rect localVisibleNow) { - Rect captured = new Rect(localVisibleNow); - captured.offset(0, -startScrollY); // make relative - - if (!captured.contains(requestRect)) { - fail("Not true that all of " + requestRect + " is contained by " + captured); - } - } - static void assertCapturedAreaPartiallyVisible(int startScrollY, Rect requestRect, - Rect localVisibleNow) { - Rect captured = new Rect(localVisibleNow); - captured.offset(0, -startScrollY); // make relative - - if (!Rect.intersects(captured, requestRect)) { - fail("Not true that any of " + requestRect + " intersects " + captured); - } - } - @Test @UiThreadTest public void onScrollRequested_up_fromTop() { @@ -188,12 +136,13 @@ public class ScrollViewCaptureHelperTest { Rect request = new Rect(0, -CAPTURE_HEIGHT, scrollBounds.width(), 0); - Rect result = svc.onScrollRequested(mTarget, scrollBounds, request); + ScrollResult scrollResult = svc.onScrollRequested(mTarget, + scrollBounds, request); // The result is an empty rectangle and no scrolling, since it // is not possible to physically scroll further up to make the // requested area visible at all (it doesn't exist). - assertEmpty(result); + assertEmpty(scrollResult.availableArea); } @Test @@ -201,7 +150,6 @@ public class ScrollViewCaptureHelperTest { public void onScrollRequested_down_fromTop() { final int startScrollY = assertScrollToY(mTarget, 0); - ScrollViewCaptureHelper svc = new ScrollViewCaptureHelper(); Rect scrollBounds = svc.onComputeScrollBounds(mTarget); svc.onPrepareForStart(mTarget, scrollBounds); @@ -212,13 +160,13 @@ public class ScrollViewCaptureHelperTest { Rect request = new Rect(0, WINDOW_HEIGHT, scrollBounds.width(), WINDOW_HEIGHT + CAPTURE_HEIGHT); - Rect result = svc.onScrollRequested(mTarget, scrollBounds, request); - assertRectEquals(request, result); - - assertCapturedAreaCompletelyVisible(startScrollY, request, getVisibleRect(mContent)); + ScrollResult scrollResult = svc.onScrollRequested(mTarget, scrollBounds, request); + assertRectEquals(request, scrollResult.requestedArea); + assertRectEquals(request, scrollResult.availableArea); + assertRequestedRectCompletelyVisible(startScrollY, request, getVisibleRect(mContent)); + assertEquals(CAPTURE_HEIGHT, scrollResult.scrollDelta); } - @Test @UiThreadTest public void onScrollRequested_up_fromMiddle() { @@ -230,12 +178,11 @@ public class ScrollViewCaptureHelperTest { Rect request = new Rect(0, -CAPTURE_HEIGHT, scrollBounds.width(), 0); - - Rect result = svc.onScrollRequested(mTarget, scrollBounds, request); - - assertRectEquals(request, result); - - assertCapturedAreaCompletelyVisible(startScrollY, request, getVisibleRect(mContent)); + ScrollResult scrollResult = svc.onScrollRequested(mTarget, scrollBounds, request); + assertRectEquals(request, scrollResult.requestedArea); + assertRectEquals(request, scrollResult.availableArea); + assertRequestedRectCompletelyVisible(startScrollY, request, getVisibleRect(mContent)); + assertEquals(-CAPTURE_HEIGHT, scrollResult.scrollDelta); } @Test @@ -250,10 +197,12 @@ public class ScrollViewCaptureHelperTest { Rect request = new Rect(0, WINDOW_HEIGHT, scrollBounds.width(), WINDOW_HEIGHT + CAPTURE_HEIGHT); - Rect result = svc.onScrollRequested(mTarget, scrollBounds, request); - assertRectEquals(request, result); + ScrollResult scrollResult = svc.onScrollRequested(mTarget, scrollBounds, request); + assertRectEquals(request, scrollResult.requestedArea); + assertRectEquals(request, scrollResult.availableArea); + assertRequestedRectCompletelyVisible(startScrollY, request, getVisibleRect(mContent)); + assertEquals(CAPTURE_HEIGHT, scrollResult.scrollDelta); - assertCapturedAreaCompletelyVisible(startScrollY, request, getVisibleRect(mContent)); } @Test @@ -267,10 +216,11 @@ public class ScrollViewCaptureHelperTest { Rect request = new Rect(0, -CAPTURE_HEIGHT, scrollBounds.width(), 0); - Rect result = svc.onScrollRequested(mTarget, scrollBounds, request); - assertRectEquals(request, result); - - assertCapturedAreaCompletelyVisible(startScrollY, request, getVisibleRect(mContent)); + ScrollResult scrollResult = svc.onScrollRequested(mTarget, scrollBounds, request); + assertRectEquals(request, scrollResult.requestedArea); + assertRectEquals(request, scrollResult.availableArea); + assertRequestedRectCompletelyVisible(startScrollY, request, getVisibleRect(mContent)); + assertEquals(-CAPTURE_HEIGHT, scrollResult.scrollDelta); } @Test @@ -285,12 +235,14 @@ public class ScrollViewCaptureHelperTest { Rect request = new Rect(0, WINDOW_HEIGHT, scrollBounds.width(), WINDOW_HEIGHT + CAPTURE_HEIGHT); - Rect result = svc.onScrollRequested(mTarget, scrollBounds, request); + ScrollResult scrollResult = svc.onScrollRequested(mTarget, scrollBounds, request); + assertRectEquals(request, scrollResult.requestedArea); // The result is an empty rectangle and no scrolling, since it // is not possible to physically scroll further down to make the // requested area visible at all (it doesn't exist). - assertEmpty(result); + assertEmpty(scrollResult.availableArea); + assertEquals(0, scrollResult.scrollDelta); } @Test @@ -309,12 +261,16 @@ public class ScrollViewCaptureHelperTest { 0, top - (CAPTURE_HEIGHT / 2), scrollBounds.width(), top + (CAPTURE_HEIGHT / 2)); - Rect result = svc.onScrollRequested(mTarget, scrollBounds, request); + ScrollResult scrollResult = svc.onScrollRequested(mTarget, scrollBounds, request); + assertRectEquals(request, scrollResult.requestedArea); + + ScrollResult result = svc.onScrollRequested(mTarget, scrollBounds, request); // The result is a partial result Rect expectedResult = new Rect(request); expectedResult.top += 300; // top half clipped - assertRectEquals(expectedResult, result); - assertCapturedAreaPartiallyVisible(startScrollY, request, getVisibleRect(mContent)); + assertRectEquals(expectedResult, result.availableArea); + assertRequestedRectPartiallyVisible(startScrollY, request, getVisibleRect(mContent)); + assertEquals(0, scrollResult.scrollDelta); } @Test @@ -334,13 +290,13 @@ public class ScrollViewCaptureHelperTest { 0, bottom - (CAPTURE_HEIGHT / 2), scrollBounds.width(), bottom + (CAPTURE_HEIGHT / 2)); - Rect result = svc.onScrollRequested(mTarget, scrollBounds, request); + ScrollResult result = svc.onScrollRequested(mTarget, scrollBounds, request); Rect expectedResult = new Rect(request); expectedResult.bottom -= 300; // bottom half clipped - assertRectEquals(expectedResult, result); - assertCapturedAreaPartiallyVisible(startScrollY, request, getVisibleRect(mContent)); - + assertRectEquals(expectedResult, result.availableArea); + assertRequestedRectPartiallyVisible(startScrollY, request, getVisibleRect(mContent)); + assertEquals(0, result.scrollDelta); } @Test @@ -349,4 +305,56 @@ public class ScrollViewCaptureHelperTest { ScrollViewCaptureHelper svc = new ScrollViewCaptureHelper(); svc.onPrepareForEnd(mTarget); } + + + static void assertEmpty(Rect r) { + if (r != null && !r.isEmpty()) { + fail("Not true that " + r + " is empty"); + } + } + + static void assertContains(Rect parent, Rect child) { + if (!parent.contains(child)) { + fail("Not true that " + parent + " contains " + child); + } + } + + static void assertRectEquals(Rect parent, Rect child) { + if (!parent.equals(child)) { + fail("Not true that " + parent + " is equal to " + child); + } + } + + static Rect getVisibleRect(View v) { + Rect r = new Rect(0, 0, v.getWidth(), v.getHeight()); + v.getLocalVisibleRect(r); + return r; + } + + + static int assertScrollToY(View v, int scrollY) { + v.scrollTo(0, scrollY); + int dest = v.getScrollY(); + assertEquals(scrollY, dest); + return scrollY; + } + + static void assertRequestedRectCompletelyVisible(int startScrollY, Rect requestRect, + Rect localVisibleNow) { + Rect captured = new Rect(localVisibleNow); + captured.offset(0, -startScrollY); // make relative + + if (!captured.contains(requestRect)) { + fail("Not true that all of " + requestRect + " is contained by " + captured); + } + } + static void assertRequestedRectPartiallyVisible(int startScrollY, Rect requestRect, + Rect localVisibleNow) { + Rect captured = new Rect(localVisibleNow); + captured.offset(0, -startScrollY); // make relative + + if (!Rect.intersects(captured, requestRect)) { + fail("Not true that any of " + requestRect + " intersects " + captured); + } + } } |