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

Commit 1c21c64f authored by Mark Renouf's avatar Mark Renouf
Browse files

update ScrollCaptureControllerTest

Extracted test setup, made all inputs explicit
Rewrote FakeSession operate similarly to live capture

Bug: 180390131
Test: atest ScrollCaptureControllerTest
Change-Id: Ib9ffee00b5d5f9b9bdfa40de1cee4ff7c7ab3203
parent bb12e225
Loading
Loading
Loading
Loading
+7 −0
Original line number Diff line number Diff line
@@ -82,6 +82,13 @@ public class ScrollCaptureClient {
         */
        int getMaxTiles();

        /**
         * @return the maximum combined capture height for this session, in pixels.
         */
        default int getMaxHeight() {
            return getMaxTiles() * getTileHeight();
        }

        /**
         * @return the height of each image tile
         */
+6 −0
Original line number Diff line number Diff line
@@ -27,6 +27,7 @@ import android.view.ScrollCaptureResponse;
import androidx.concurrent.futures.CallbackToFutureAdapter;
import androidx.concurrent.futures.CallbackToFutureAdapter.Completer;

import com.android.internal.annotations.VisibleForTesting;
import com.android.systemui.dagger.qualifiers.Background;
import com.android.systemui.screenshot.ScrollCaptureClient.CaptureResult;
import com.android.systemui.screenshot.ScrollCaptureClient.Session;
@@ -139,6 +140,11 @@ public class ScrollCaptureController {
        mImageTileSet = imageTileSet;
    }

    @VisibleForTesting
    float getTargetTopSizeRatio() {
        return IDEAL_PORTION_ABOVE;
    }

    /**
     * Run scroll capture. Performs a batch capture, collecting image tiles.
     *
+188 −0
Original line number Diff line number Diff line
/*
 * Copyright (C) 2021 The Android Open Source Project
 *
 * Licensed under the Apache License, Version 2.0 (the "License");
 * you may not use this file except in compliance with the License.
 * You may obtain a copy of the License at
 *
 *      http://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 */

package com.android.systemui.screenshot;

import static android.util.MathUtils.constrain;

import static com.google.common.util.concurrent.Futures.immediateFuture;

import static org.mockito.Mockito.mock;
import static org.mockito.Mockito.when;

import static java.lang.Math.abs;
import static java.lang.Math.max;
import static java.lang.Math.min;

import android.graphics.Rect;
import android.hardware.HardwareBuffer;
import android.media.Image;
import android.util.Log;

import com.google.common.util.concurrent.Futures;
import com.google.common.util.concurrent.ListenableFuture;

/**
 * A flexible test double for {@link ScrollCaptureClient.Session}.
 * <p>
 * FakeSession provides the ability to emulate both the available scrollable content range as well
 * as the current visible bounds. Visible bounds may vary because the target view itself may be
 * slid vertically during capture, with portions may become clipped by parent views. This scenario
 * frequently occurs with UIs constructed from nested scrolling views or collapsing headers.
 */
class FakeSession implements ScrollCaptureClient.Session {
    private static final String TAG = "FakeSession";
    // Available range of content
    private final Rect mAvailable;

    /** bounds for scrollDelta (y), range with bottom adjusted to account for page height. */
    private final Rect mAvailableTop;

    private final Rect mVisiblePage;
    private final int mTileHeight;
    private final int mMaxTiles;

    private int mScrollDelta;
    private int mPageHeight;

    FakeSession(int pageHeight, float maxPages, int tileHeight, int visiblePageTop,
            int visiblePageBottom, int availableTop, int availableBottom) {
        mPageHeight = pageHeight;
        mTileHeight = tileHeight;
        mAvailable = new Rect(0, availableTop, getPageWidth(), availableBottom);
        mAvailableTop = new Rect(mAvailable);
        mAvailableTop.inset(0, 0, 0, pageHeight);
        mVisiblePage = new Rect(0, visiblePageTop, getPageWidth(), visiblePageBottom);
        mMaxTiles = (int) Math.ceil((pageHeight * maxPages) / mTileHeight);
    }

    private static Image mockImage() {
        Image image = mock(Image.class);
        when(image.getHardwareBuffer()).thenReturn(mock(HardwareBuffer.class));
        return image;
    }

    public int getScrollDelta() {
        return mScrollDelta;
    }

    @Override
    public ListenableFuture<ScrollCaptureClient.CaptureResult> requestTile(int requestedTop) {
        Rect requested = new Rect(0, requestedTop, getPageWidth(), requestedTop + getTileHeight());
        Log.d(TAG, "requested: " + requested);
        Rect page = new Rect(0, 0, getPageWidth(), mPageHeight);
        page.offset(0, mScrollDelta);
        Log.d(TAG, "page: " + page);
        // Simulate behavior from lower levels by replicating 'requestChildRectangleOnScreen'
        if (!page.contains(requested)) {
            Log.d(TAG, "requested not within page, scrolling");
            // distance+direction needed to scroll to align each edge of request with
            // corresponding edge of the page
            int distTop = requested.top - page.top; // positive means already visible
            int distBottom = requested.bottom - page.bottom; // negative means already visible
            Log.d(TAG, "distTop = " + distTop);
            Log.d(TAG, "distBottom = " + distBottom);

            boolean scrollUp = false;
            if (distTop < 0  && distBottom > 0) {
                scrollUp = abs(distTop) < distBottom;
            } else if (distTop < 0) {
                scrollUp = true;
            }

            // determine which edges are currently clipped
            if (scrollUp) {
                Log.d(TAG, "trying to scroll up by " + -distTop + " px");
                // need to scroll up to align top edge to visible-top
                mScrollDelta += distTop;
                Log.d(TAG, "new scrollDelta = " + mScrollDelta);
            } else {
                Log.d(TAG, "trying to scroll down by " + distBottom + " px");
                // scroll down to align bottom edge with visible bottom, but keep top visible
                int topEdgeDistance = max(0, requestedTop - page.top);
                mScrollDelta += min(distBottom, topEdgeDistance);
                Log.d(TAG, "new scrollDelta = " + mScrollDelta);
            }

            // Clamp to available content
            mScrollDelta = constrain(mScrollDelta, mAvailableTop.top, mAvailableTop.bottom);
            Log.d(TAG, "scrollDelta, adjusted to available range = " + mScrollDelta);

            // Reset to apply a changed scroll delta possibly.
            page.offsetTo(0, 0);
            page.offset(0, mScrollDelta);

            Log.d(TAG, "page (after scroll): " + page);
            Log.d(TAG, "requested (after scroll): " + requested);
        }
        Log.d(TAG, "mVisiblePage = " + mVisiblePage);
        Log.d(TAG, "scrollDelta = " + mScrollDelta);

        Rect target = new Rect(requested);
        Rect visible = new Rect(mVisiblePage);
        visible.offset(0, mScrollDelta);

        Log.d(TAG, "target:  " + target);
        Log.d(TAG, "visible:  " + visible);

        // if any of the requested rect is available to scroll into the view:
        if (target.intersect(page) && target.intersect(visible)) {
            Log.d(TAG, "returning captured = " + target);
            ScrollCaptureClient.CaptureResult result =
                    new ScrollCaptureClient.CaptureResult(mockImage(), requested, target);
            return immediateFuture(result);
        }
        Log.d(TAG, "no part of requested rect is within page, returning empty");
        ScrollCaptureClient.CaptureResult result =
                new ScrollCaptureClient.CaptureResult(null, requested, new Rect());
        return immediateFuture(result);
    }


    @Override
    public int getMaxTiles() {
        return mMaxTiles;
    }

    @Override
    public int getTileHeight() {
        return mTileHeight;
    }

    @Override
    public int getPageWidth() {
        return 100;
    }

    @Override
    public int getPageHeight() {
        return mPageHeight;
    }

    @Override
    public Rect getWindowBounds() {
        throw new IllegalStateException("Not implemented");
    }

    @Override
    public ListenableFuture<Void> end() {
        return Futures.immediateVoidFuture();
    }

    @Override
    public void release() {
    }
}
+293 −0
Original line number Diff line number Diff line
/*
 * Copyright (C) 2021 The Android Open Source Project
 *
 * Licensed under the Apache License, Version 2.0 (the "License");
 * you may not use this file except in compliance with the License.
 * You may obtain a copy of the License at
 *
 *      http://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 */

package com.android.systemui.screenshot;

import static com.google.common.util.concurrent.Futures.getUnchecked;

import static org.junit.Assert.assertEquals;
import static org.junit.Assert.assertNotNull;
import static org.junit.Assert.assertNull;
import static org.junit.Assert.assertTrue;

import android.testing.AndroidTestingRunner;

import androidx.test.filters.SmallTest;

import com.android.systemui.SysuiTestCase;

import org.junit.Test;
import org.junit.runner.RunWith;

/**
 * Tests for {@link FakeSession}, a a test double for a ScrollCaptureClient.Session.
 * <p>
 * These tests verify a single tile request behaves similarly to a live scroll capture
 * client/connection.
 */
@SmallTest
@RunWith(AndroidTestingRunner.class)
public class FakeSessionTest extends SysuiTestCase {
    @Test
    public void testMaxTiles() {
        FakeSession session = new FakeSession(
                /* pageHeight */ 100,
                /* maxPages */ 2.25f,
                /* tileHeight */ 10,
                /* visiblePageTop */ 0,
                /* visiblePageBottom */ 100,
                /* availableTop */ -250,
                /* availableBottom */ 250);

        // (pageHeight * maxPages) / tileHeight
        assertEquals("getMaxTiles()", 23, session.getMaxTiles());
    }

    @Test
    public void testNonEmptyResult_hasImage() {
        FakeSession session = new FakeSession(
                /* pageHeight */ 100,
                /* maxPages */ 1.0f,
                /* tileHeight */ 10,
                /* visiblePageTop */ 0,
                /* visiblePageBottom */ 100,
                /* availableTop */ 0,
                /* availableBottom */ 100);
        ScrollCaptureClient.CaptureResult result = getUnchecked(session.requestTile(0));
        assertNotNull("result.image", result.image);
        assertNotNull("result.image.getHardwareBuffer()", result.image.getHardwareBuffer());
    }

    @Test
    public void testEmptyResult_hasNullImage() {
        FakeSession session = new FakeSession(
                /* pageHeight */ 100,
                /* maxPages */ 1.0f,
                /* tileHeight */ 10,
                /* visiblePageTop */ 0,
                /* visiblePageBottom */ 100,
                /* availableTop */ 0,
                /* availableBottom */ 100);
        ScrollCaptureClient.CaptureResult result = getUnchecked(session.requestTile(-100));
        assertNull("result.image", result.image);
    }

    @Test
    public void testCaptureAtZero() {
        FakeSession session = new FakeSession(
                /* pageHeight */ 100,
                /* maxPages */ 2.5f,
                /* tileHeight */ 10,
                /* visiblePageTop */ 0,
                /* visiblePageBottom */ 100,
                /* availableTop */ -250,
                /* availableBottom */ 250);

        ScrollCaptureClient.CaptureResult result = getUnchecked(session.requestTile(0));
        assertEquals("requested top", 0, result.requested.top);
        assertEquals("requested bottom", 10, result.requested.bottom);
        assertEquals("captured top", 0, result.captured.top);
        assertEquals("captured bottom", 10, result.captured.bottom);
        assertEquals("scroll delta", 0, session.getScrollDelta());
    }

    @Test
    public void testCaptureAtPageBottom() {
        FakeSession session = new FakeSession(
                /* pageHeight */ 100,
                /* maxPages */ 2.5f,
                /* tileHeight */ 10,
                /* visiblePageTop */ 0,
                /* visiblePageBottom */ 100,
                /* availableTop */ -250,
                /* availableBottom */ 250);

        ScrollCaptureClient.CaptureResult result = getUnchecked(session.requestTile(90));
        assertEquals("requested top", 90, result.requested.top);
        assertEquals("requested bottom", 100, result.requested.bottom);
        assertEquals("captured top", 90, result.captured.top);
        assertEquals("captured bottom", 100, result.captured.bottom);
        assertEquals("scroll delta", 0, session.getScrollDelta());
    }

    @Test
    public void testCaptureFromPreviousPage() {
        FakeSession session = new FakeSession(
                /* pageHeight */ 100,
                /* maxPages */ 2.5f,
                /* tileHeight */ 10,
                /* visiblePageTop */ 0,
                /* visiblePageBottom */ 100,
                /* availableTop */ -250,
                /* availableBottom */ 250);

        ScrollCaptureClient.CaptureResult result = getUnchecked(session.requestTile(-100));
        assertEquals("requested top", -100, result.requested.top);
        assertEquals("requested bottom", -90, result.requested.bottom);
        assertEquals("captured top", -100, result.captured.top);
        assertEquals("captured bottom", -90, result.captured.bottom);
        assertEquals("scroll delta", -100, session.getScrollDelta());
    }

    @Test
    public void testCaptureFromNextPage() {
        FakeSession session = new FakeSession(
                /* pageHeight */ 100,
                /* maxPages */ 2.5f,
                /* tileHeight */ 10,
                /* visiblePageTop */ 0,
                /* visiblePageBottom */ 100,
                /* availableTop */ -250,
                /* availableBottom */ 250);

        ScrollCaptureClient.CaptureResult result = getUnchecked(session.requestTile(150));
        assertEquals("requested top", 150, result.requested.top);
        assertEquals("requested bottom", 160, result.requested.bottom);
        assertEquals("captured top", 150, result.captured.top);
        assertEquals("captured bottom", 160, result.captured.bottom);
        assertEquals("scroll delta", 60, session.getScrollDelta());
    }

    @Test
    public void testCaptureTopPartiallyUnavailable() {
        FakeSession session = new FakeSession(
                /* pageHeight */ 100,
                /* maxPages */ 3f,
                /* tileHeight */ 50,
                /* visiblePageTop */ 0,
                /* visiblePageBottom */ 100,
                /* availableTop */ -100,
                /* availableBottom */ 100);

        ScrollCaptureClient.CaptureResult result = getUnchecked(session.requestTile(-125));
        assertEquals("requested top", -125, result.requested.top);
        assertEquals("requested bottom", -75, result.requested.bottom);
        assertEquals("captured top", -100, result.captured.top);
        assertEquals("captured bottom", -75, result.captured.bottom);
        assertEquals("scroll delta", -100, session.getScrollDelta());
    }

    @Test
    public void testCaptureBottomPartiallyUnavailable() {
        FakeSession session = new FakeSession(
                /* pageHeight */ 100,
                /* maxPages */ 3f,
                /* tileHeight */ 50,
                /* visiblePageTop */ 0,
                /* visiblePageBottom */ 100,
                /* availableTop */ -100,
                /* availableBottom */ 100);

        ScrollCaptureClient.CaptureResult result = getUnchecked(session.requestTile(75));
        assertEquals("requested top", 75, result.requested.top);
        assertEquals("requested bottom", 125, result.requested.bottom);
        assertEquals("captured top", 75, result.captured.top);
        assertEquals("captured bottom", 100, result.captured.bottom);
        assertEquals("scroll delta", 0, session.getScrollDelta());
    }

    /**
     * Set visiblePageTop > 0  to cause the returned request's top edge to be cropped.
     */
    @Test
    public void testCaptureTopPartiallyInvisible() {
        FakeSession session = new FakeSession(
                /* pageHeight */ 100,
                /* maxPages */ 3f,
                /* tileHeight */ 50,
                /* visiblePageTop */ 25,  // <<--
                /* visiblePageBottom */ 100,
                /* availableTop */ -150,
                /* availableBottom */ 150);

        ScrollCaptureClient.CaptureResult result = getUnchecked(session.requestTile(-150));
        assertEquals("requested top", -150, result.requested.top);
        assertEquals("requested bottom", -100, result.requested.bottom);
        assertEquals("captured top", -125, result.captured.top);
        assertEquals("captured bottom", -100, result.captured.bottom);
        assertEquals("scroll delta", -150, session.getScrollDelta());
    }

    /**
     * Set visiblePageBottom < pageHeight to cause the returned request's bottom edge to be cropped.
     */
    @Test
    public void testCaptureBottomPartiallyInvisible() {
        FakeSession session = new FakeSession(
                /* pageHeight */ 100,
                /* maxPages */ 3f,
                /* tileHeight */ 50,
                /* visiblePageTop */ 0,
                /* visiblePageBottom */ 75,
                /* availableTop */ -150,
                /* availableBottom */ 150);

        ScrollCaptureClient.CaptureResult result = getUnchecked(session.requestTile(50));
        assertEquals("requested top", 50, result.requested.top);
        assertEquals("requested bottom", 100, result.requested.bottom);
        assertEquals("captured top", 50, result.captured.top);
        assertEquals("captured bottom", 75, result.captured.bottom);
        assertEquals("scroll delta", 0, session.getScrollDelta());
    }

    @Test
    public void testEmptyResult_aboveAvailableTop() {
        FakeSession session = new FakeSession(
                /* pageHeight */ 100,
                /* maxPages */ 3.0f,
                /* tileHeight */ 50,
                /* visiblePageTop */ 0,
                /* visiblePageBottom */ 100,
                /* availableTop */ -100,
                /* availableBottom */ 200);
        ScrollCaptureClient.CaptureResult result = getUnchecked(session.requestTile(-150));
        assertTrue("captured rect is empty", result.captured.isEmpty());
    }

    @Test
    public void testEmptyResult_belowAvailableBottom() {
        FakeSession session = new FakeSession(
                /* pageHeight */ 100,
                /* maxPages */ 3.0f,
                /* tileHeight */ 50,
                /* visiblePageTop */ 0,
                /* visiblePageBottom */ 100,
                /* availableTop */ -100,
                /* availableBottom */ 200);
        ScrollCaptureClient.CaptureResult result = getUnchecked(session.requestTile(200));
        assertTrue("captured rect is empty", result.captured.isEmpty());
    }

    @Test
    public void testEmptyResult_notVisible() {
        FakeSession session = new FakeSession(
                /* pageHeight */ 100,
                /* maxPages */ 3f,
                /* tileHeight */ 50,
                /* visiblePageTop */ 60,  // <<---
                /* visiblePageBottom */ 0,
                /* availableTop */ -150,
                /* availableBottom */ 150);

        ScrollCaptureClient.CaptureResult result = getUnchecked(session.requestTile(0));
        assertEquals("requested top", 0, result.requested.top);
        assertEquals("requested bottom", 50, result.requested.bottom);
        assertEquals("captured top", 0, result.captured.top);
        assertEquals("captured bottom", 0, result.captured.bottom);
        assertEquals("scroll delta", 0, session.getScrollDelta());
    }

}
+156 −153

File changed.

Preview size limit exceeded, changes collapsed.