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

Commit e3143763 authored by Mark Renouf's avatar Mark Renouf Committed by Android (Google) Code Review
Browse files

Merge "update ScrollCaptureControllerTest" into sc-dev

parents fea22aab 1c21c64f
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.