// Copyright 2019 The Chromium Authors
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.
package org.chromium.components.paintpreview.player.frame;
import static org.mockito.ArgumentMatchers.argThat;
import static org.mockito.ArgumentMatchers.eq;
import static org.mockito.Mockito.inOrder;
import android.app.Activity;
import android.content.Context;
import android.graphics.Bitmap;
import android.graphics.Matrix;
import android.graphics.Point;
import android.graphics.Rect;
import android.os.Parcel;
import android.util.Pair;
import android.util.Size;
import android.view.View;
import android.widget.OverScroller;
import androidx.annotation.NonNull;
import org.junit.Assert;
import org.junit.Before;
import org.junit.Test;
import org.junit.runner.RunWith;
import org.mockito.ArgumentMatcher;
import org.mockito.InOrder;
import org.mockito.Mockito;
import org.robolectric.Robolectric;
import org.robolectric.annotation.Config;
import org.robolectric.shadows.ShadowLooper;
import org.robolectric.shadows.ShadowView;
import org.chromium.base.Callback;
import org.chromium.base.ContextUtils;
import org.chromium.base.UnguessableToken;
import org.chromium.base.test.BaseRobolectricTestRunner;
import org.chromium.components.paintpreview.player.PlayerCompositorDelegate;
import org.chromium.components.paintpreview.player.PlayerGestureListener;
import org.chromium.ui.modelutil.PropertyModel;
import org.chromium.url.GURL;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.List;
/**
* Tests for the {@link PlayerFrameMediator} class. This also serves as a sort of integration test
* for the {@link PlayerFrameScrollController}, {@link PlayerFrameScaleController}, {@link
* PlayerFrameViewport}, and {@link PlayerFrameBitmapState}.
*/
@RunWith(BaseRobolectricTestRunner.class)
@Config(shadows = {PaintPreviewCustomFlingingShadowScroller.class, ShadowView.class})
public class PlayerFrameMediatorTest {
private static final int CONTENT_WIDTH = 560;
private static final int CONTENT_HEIGHT = 1150;
private UnguessableToken mFrameGuid;
private PropertyModel mModel;
private TestPlayerCompositorDelegate mCompositorDelegate;
private OverScroller mScroller;
private boolean mHasUserInteraction;
private PlayerGestureListener mGestureListener;
private PlayerFrameMediator mMediator;
private PlayerFrameBitmapStateController mBitmapStateController;
private PlayerFrameScrollController mScrollController;
private PlayerFrameScaleController mScaleController;
/** Generate an UnguessableToken with a static value. */
private UnguessableToken frameGuid() {
// Use a parcel for testing to avoid calling the normal native constructor.
Parcel parcel = Parcel.obtain();
parcel.writeLong(123321L);
parcel.writeLong(987654L);
parcel.setDataPosition(0);
return UnguessableToken.CREATOR.createFromParcel(parcel);
}
/** Gets the visible bitmap state from the bitmap state controller. */
private PlayerFrameBitmapState getVisibleBitmapState() {
return mBitmapStateController.getBitmapState(false);
}
/** Used for keeping track of all bitmap requests that {@link PlayerFrameMediator} makes. */
private class RequestedBitmap {
UnguessableToken mFrameGuid;
Rect mClipRect;
float mScaleFactor;
Callback<Bitmap> mBitmapCallback;
Runnable mErrorCallback;
public RequestedBitmap(
UnguessableToken frameGuid,
Rect clipRect,
float scaleFactor,
Callback<Bitmap> bitmapCallback,
Runnable errorCallback) {
this.mFrameGuid = frameGuid;
this.mClipRect = clipRect;
this.mScaleFactor = scaleFactor;
this.mBitmapCallback = bitmapCallback;
this.mErrorCallback = errorCallback;
}
public RequestedBitmap(UnguessableToken frameGuid, Rect clipRect, float scaleFactor) {
this.mFrameGuid = frameGuid;
this.mClipRect = clipRect;
this.mScaleFactor = scaleFactor;
}
@Override
public boolean equals(Object o) {
if (o == null) return false;
if (o == this) return true;
if (o.getClass() != this.getClass()) return false;
RequestedBitmap rb = (RequestedBitmap) o;
return rb.mClipRect.equals(mClipRect)
&& rb.mFrameGuid.equals(mFrameGuid)
&& rb.mScaleFactor == mScaleFactor;
}
@NonNull
@Override
public String toString() {
return mFrameGuid + ", " + mClipRect + ", " + mScaleFactor;
}
}
/**
* Used for keeping track of all click events that {@link PlayerFrameMediator} sends to {@link
* PlayerCompositorDelegate}.
*/
private class ClickedPoint {
UnguessableToken mFrameGuid;
int mX;
int mY;
public ClickedPoint(UnguessableToken frameGuid, int x, int y) {
mFrameGuid = frameGuid;
this.mX = x;
this.mY = y;
}
@Override
public boolean equals(Object o) {
if (o == null) return false;
if (o == this) return true;
if (o.getClass() != this.getClass()) return false;
ClickedPoint cp = (ClickedPoint) o;
return cp.mFrameGuid.equals(mFrameGuid) && cp.mX == mX && cp.mY == mY;
}
@NonNull
@Override
public String toString() {
return "Click event for frame "
+ mFrameGuid.toString()
+ " on ("
+ mX
+ ", "
+ mY
+ ")";
}
}
/**
* Mocks {@link PlayerCompositorDelegate}. Stores all bitmap requests as {@link
* RequestedBitmap}s.
*/
private class TestPlayerCompositorDelegate implements PlayerCompositorDelegate {
List<RequestedBitmap> mRequestedBitmap = new ArrayList<>();
List<ClickedPoint> mClickedPoints = new ArrayList<>();
Runnable mOnMemoryPressureRunnable;
private int mNextRequestId;
@Override
public void addMemoryPressureListener(Runnable runnable) {
mOnMemoryPressureRunnable = runnable;
}
@Override
public int requestBitmap(
UnguessableToken frameGuid,
Rect clipRect,
float scaleFactor,
Callback<Bitmap> bitmapCallback,
Runnable errorCallback) {
mRequestedBitmap.add(
new RequestedBitmap(
frameGuid,
new Rect(clipRect),
scaleFactor,
bitmapCallback,
errorCallback));
int requestId = mNextRequestId;
mNextRequestId++;
return requestId;
}
@Override
public int requestBitmap(
Rect clipRect,
float scaleFactor,
Callback<Bitmap> bitmapCallback,
Runnable errorCallback) {
Assert.fail(
"The GUIDless version of TestPlayerCompositorDelegate#requestBitmap() "
+ "shouldn't be called.");
return 0;
}
@Override
public boolean cancelBitmapRequest(int requestId) {
return false;
}
@Override
public void cancelAllBitmapRequests() {}
@Override
public GURL onClick(UnguessableToken frameGuid, int x, int y) {
mClickedPoints.add(new ClickedPoint(frameGuid, x, y));
return null;
}
@Override
public Point getRootFrameOffsets() {
return new Point();
}
}
private class MatrixMatcher implements ArgumentMatcher<Matrix> {
private Matrix mLeft;
MatrixMatcher(Matrix left) {
mLeft = left;
}
@Override
public boolean matches(Matrix right) {
return mLeft.equals(right);
}
}
@Before
public void setUp() {
mFrameGuid = frameGuid();
mModel = new PropertyModel.Builder(PlayerFrameProperties.ALL_KEYS).build();
mCompositorDelegate = new TestPlayerCompositorDelegate();
mScroller = new OverScroller(ContextUtils.getApplicationContext());
mGestureListener = new PlayerGestureListener(null, () -> mHasUserInteraction = true, null);
Size contentSize = new Size(CONTENT_WIDTH, CONTENT_HEIGHT);
mMediator =
new PlayerFrameMediator(
mModel,
mCompositorDelegate,
mGestureListener,
mFrameGuid,
contentSize,
0,
0,
0f,
null);
mScaleController =
new PlayerFrameScaleController(
mModel.get(PlayerFrameProperties.SCALE_MATRIX),
mMediator,
null,
mGestureListener::onScale);
mScrollController =
new PlayerFrameScrollController(
mScroller,
mMediator,
mGestureListener::onScroll,
mGestureListener::onFling);
mBitmapStateController = mMediator.getBitmapStateControllerForTest();
}
private static Rect getRectForTile(int tileWidth, int tileHeight, int row, int col) {
int left = col * tileWidth;
int top = row * tileHeight;
return new Rect(left, top, left + tileWidth, top + tileHeight);
}
private static List<Boolean> getVisibilities(List<View> views) {
List<Boolean> visibilities = new ArrayList<>();
for (View view : views) {
visibilities.add(view.getVisibility() == View.VISIBLE);
}
return visibilities;
}
private static void assertViewportStateIs(Matrix matrix, PlayerFrameViewport viewport) {
float matrixValues[] = new float[9];
matrix.getValues(matrixValues);
assert matrixValues[Matrix.MSCALE_X] == matrixValues[Matrix.MSCALE_Y];
assertViewportStateIs(
matrixValues[Matrix.MSCALE_X],
matrixValues[Matrix.MTRANS_X],
matrixValues[Matrix.MTRANS_Y],
viewport);
}
/** Asserts that the viewport's transformation state matches. */
private static void assertViewportStateIs(
float expectedScaleFactor,
float expectedX,
float expectedY,
PlayerFrameViewport viewport) {
final float tolerance = 0.01f;
Assert.assertEquals(expectedScaleFactor, viewport.getScale(), tolerance);
Assert.assertEquals(expectedX, viewport.getTransX(), tolerance);
Assert.assertEquals(expectedY, viewport.getTransY(), tolerance);
}
/**
* Tests that {@link PlayerFrameMediator} is initialized correctly on the first call to {@link
* PlayerFrameMediator#setLayoutDimensions}.
*/
@Test
public void testInitialLayoutDimensions() {
// Initial view port setup.
mMediator.setLayoutDimensions(150, 200);
// View port should be as big as size set in the first setLayoutDimensions call, showing
// the top left corner.
Rect expectedViewPort = new Rect(0, 0, 150, 200);
Assert.assertEquals(expectedViewPort, mModel.get(PlayerFrameProperties.VIEWPORT));
// The bitmap matrix should be empty, but initialized with the correct number of rows and
// columns. Because we set the initial scale factor to view port width over content width,
// we should have only one column.
Bitmap[][] bitmapMatrix = mModel.get(PlayerFrameProperties.BITMAP_MATRIX);
Assert.assertTrue(Arrays.deepEquals(bitmapMatrix, new Bitmap[4][1]));
Assert.assertEquals(
new ArrayList<Pair<View, Rect>>(),
mModel.get(PlayerFrameProperties.SUBFRAME_VIEWS));
}
/**
* Tests that {@link PlayerFrameMediator} requests for the right bitmap tiles as the view port
* moves.
*/
@Test
public void testBitmapRequest() {
// Initial view port setup.
mMediator.updateViewportSize(100, 200, 1f);
// Requests for bitmaps in all tiles that are visible in the view port as well as their
// adjacent tiles should've been made.
// The current view port fully matches the top left bitmap tiles, so we expect requests for
// the top left bitmaps, plus bitmaps tothe right, and below.
// Below is a schematic of the entire bitmap matrix. Those marked with number should have
// been requested, in the order of numbers.
// -------------------------
// | 1 | 3 | | | | |
// -------------------------
// | 2 | 4 | | | | |
// -------------------------
// | 5 | | | | | |
// -------------------------
// | | | | | | |
// -------------------------
// | | | | | | |
// -------------------------
// | | | | | | |
List<RequestedBitmap> expectedRequestedBitmaps = new ArrayList<>();
expectedRequestedBitmaps.add(
new RequestedBitmap(mFrameGuid, getRectForTile(100, 100, 0, 0), 1f));
expectedRequestedBitmaps.add(
new RequestedBitmap(mFrameGuid, getRectForTile(100, 100, 1, 0), 1f));
expectedRequestedBitmaps.add(
new RequestedBitmap(mFrameGuid, getRectForTile(100, 100, 0, 1), 1f));
expectedRequestedBitmaps.add(
new RequestedBitmap(mFrameGuid, getRectForTile(100, 100, 2, 0), 1f));
expectedRequestedBitmaps.add(
new RequestedBitmap(mFrameGuid, getRectForTile(100, 100, 1, 1), 1f));
Assert.assertEquals(expectedRequestedBitmaps, mCompositorDelegate.mRequestedBitmap);
mScrollController.scrollBy(10, 20);
// The view port was moved with the #updateViewport call. It should've been updated in the
// model.
Rect expectedViewPort = new Rect(10, 20, 110, 220);
Assert.assertEquals(expectedViewPort, mModel.get(PlayerFrameProperties.VIEWPORT));
// The current viewport covers portions of the top left bitmap tiles. We have requested
// bitmaps for 8 of them before. Make sure requests for the 4th bitmap, as well adjacent
// bitmaps are made.
// Below is a schematic of the entire bitmap matrix. Those marked with number should have
// been requested, in the order of numbers.
// -------------------------
// | x | x | 3 | | | |
// -------------------------
// | x | x | 5 | | | |
// -------------------------
// | x | 1 | 6 | | | |
// -------------------------
// | 2 | 4 | | | | |
// -------------------------
// | | | | | | |
// -------------------------
// | | | | | | |
expectedRequestedBitmaps.add(
new RequestedBitmap(mFrameGuid, getRectForTile(100, 100, 2, 1), 1f));
expectedRequestedBitmaps.add(
new RequestedBitmap(mFrameGuid, getRectForTile(100, 100, 3, 0), 1f));
expectedRequestedBitmaps.add(
new RequestedBitmap(mFrameGuid, getRectForTile(100, 100, 0, 2), 1f));
expectedRequestedBitmaps.add(
new RequestedBitmap(mFrameGuid, getRectForTile(100, 100, 1, 2), 1f));
expectedRequestedBitmaps.add(
new RequestedBitmap(mFrameGuid, getRectForTile(100, 100, 3, 1), 1f));
expectedRequestedBitmaps.add(
new RequestedBitmap(mFrameGuid, getRectForTile(100, 100, 2, 2), 1f));
Assert.assertEquals(expectedRequestedBitmaps, mCompositorDelegate.mRequestedBitmap);
// Move the view port slightly. It is still covered by the same tiles. Since there were
// already bitmap requests out for those tiles and their adjacent tiles, we shouldn't have
// made new requests.
mScrollController.scrollBy(10, 20);
Assert.assertEquals(expectedRequestedBitmaps, mCompositorDelegate.mRequestedBitmap);
// Move the view port to the bottom right so it covers portions of the 4 bottom right bitmap
// tiles. New bitmap requests should be made.
mScrollController.scrollBy(430, 900);
expectedViewPort.set(450, 940, 550, 1140);
Assert.assertEquals(expectedViewPort, mModel.get(PlayerFrameProperties.VIEWPORT));
expectedRequestedBitmaps.add(
new RequestedBitmap(mFrameGuid, getRectForTile(100, 100, 9, 4), 1f));
expectedRequestedBitmaps.add(
new RequestedBitmap(mFrameGuid, getRectForTile(100, 100, 10, 4), 1f));
expectedRequestedBitmaps.add(
new RequestedBitmap(mFrameGuid, getRectForTile(100, 100, 11, 4), 1f));
expectedRequestedBitmaps.add(
new RequestedBitmap(mFrameGuid, getRectForTile(100, 100, 9, 5), 1f));
expectedRequestedBitmaps.add(
new RequestedBitmap(mFrameGuid, getRectForTile(100, 100, 10, 5), 1f));
expectedRequestedBitmaps.add(
new RequestedBitmap(mFrameGuid, getRectForTile(100, 100, 11, 5), 1f));
expectedRequestedBitmaps.add(
new RequestedBitmap(mFrameGuid, getRectForTile(100, 100, 8, 4), 1f));
expectedRequestedBitmaps.add(
new RequestedBitmap(mFrameGuid, getRectForTile(100, 100, 9, 3), 1f));
expectedRequestedBitmaps.add(
new RequestedBitmap(mFrameGuid, getRectForTile(100, 100, 10, 3), 1f));
expectedRequestedBitmaps.add(
new RequestedBitmap(mFrameGuid, getRectForTile(100, 100, 11, 3), 1f));
expectedRequestedBitmaps.add(
new RequestedBitmap(mFrameGuid, getRectForTile(100, 100, 8, 5), 1f));
Assert.assertEquals(expectedRequestedBitmaps, mCompositorDelegate.mRequestedBitmap);
}
/**
* Tests that the mediator keeps around the required bitmaps and removes the unrequired bitmaps
* when the view port changes. Required bitmaps are those in the viewport and its adjacent
* tiles.
*/
@Test
public void testRequiredBitmapMatrix() {
// Initial view port setup.
mMediator.updateViewportSize(100, 200, 1f);
boolean[][] expectedRequiredBitmaps = new boolean[12][6];
// The current view port fully matches the top left bitmap tile.
// Below is a schematic of the entire bitmap matrix. Tiles marked with x are required for
// the current view port.
// -------------------------
// | x | x | x | | | |
// -------------------------
// | x | x | x | | | |
// -------------------------
// | x | x | | | | |
// -------------------------
// | | | | | | |
// -------------------------
// | | | | | | |
// -------------------------
// | | | | | | |
expectedRequiredBitmaps[0][0] = true;
expectedRequiredBitmaps[0][1] = true;
expectedRequiredBitmaps[1][0] = true;
expectedRequiredBitmaps[1][1] = true;
expectedRequiredBitmaps[2][0] = true;
Assert.assertTrue(
Arrays.deepEquals(
expectedRequiredBitmaps,
getVisibleBitmapState().getRequiredBitmapsForTest()));
mScrollController.scrollBy(10, 15);
// The current viewport covers portions of the 4 top left bitmap tiles.
// -------------------------
// | x | x | x | x | | |
// -------------------------
// | x | x | x | x | | |
// -------------------------
// | x | x | x | x | | |
// -------------------------
// | x | x | x | | | |
// -------------------------
// | | | | | | |
// -------------------------
// | | | | | | |
expectedRequiredBitmaps[0][2] = true;
expectedRequiredBitmaps[1][2] = true;
expectedRequiredBitmaps[2][1] = true;
expectedRequiredBitmaps[2][2] = true;
expectedRequiredBitmaps[3][0] = true;
expectedRequiredBitmaps[3][1] = true;
Assert.assertTrue(
Arrays.deepEquals(
expectedRequiredBitmaps,
getVisibleBitmapState().getRequiredBitmapsForTest()));
mScrollController.scrollBy(200, 400);
// The current view port contains portions of the middle 9 tiles.
// ---------------------
// | | x | x | | |
// ---------------------
// | x | x | x | x | |
// ---------------------
// | x | x | x | x | |
// ---------------------
// | x | x | x | x | |
// ---------------------
// | | x | x | | |
// ---------------------
expectedRequiredBitmaps = new boolean[12][6];
expectedRequiredBitmaps[3][2] = true;
expectedRequiredBitmaps[3][3] = true;
expectedRequiredBitmaps[4][1] = true;
expectedRequiredBitmaps[4][2] = true;
expectedRequiredBitmaps[4][3] = true;
expectedRequiredBitmaps[4][4] = true;
expectedRequiredBitmaps[5][1] = true;
expectedRequiredBitmaps[5][2] = true;
expectedRequiredBitmaps[5][3] = true;
expectedRequiredBitmaps[5][4] = true;
expectedRequiredBitmaps[6][1] = true;
expectedRequiredBitmaps[6][2] = true;
expectedRequiredBitmaps[6][3] = true;
expectedRequiredBitmaps[6][4] = true;
expectedRequiredBitmaps[7][2] = true;
expectedRequiredBitmaps[7][3] = true;
Assert.assertTrue(
Arrays.deepEquals(
expectedRequiredBitmaps,
getVisibleBitmapState().getRequiredBitmapsForTest()));
mScrollController.scrollBy(200, 400);
// The current view port contains portions of the 9 bottom right tiles.
// Tiles marked with x are required for the current view port.
expectedRequiredBitmaps = new boolean[12][6];
expectedRequiredBitmaps[7][4] = true;
expectedRequiredBitmaps[7][5] = true;
expectedRequiredBitmaps[8][3] = true;
expectedRequiredBitmaps[8][4] = true;
expectedRequiredBitmaps[8][5] = true;
expectedRequiredBitmaps[9][3] = true;
expectedRequiredBitmaps[9][4] = true;
expectedRequiredBitmaps[9][5] = true;
expectedRequiredBitmaps[10][3] = true;
expectedRequiredBitmaps[10][4] = true;
expectedRequiredBitmaps[10][5] = true;
expectedRequiredBitmaps[11][4] = true;
expectedRequiredBitmaps[11][5] = true;
Assert.assertTrue(
Arrays.deepEquals(
expectedRequiredBitmaps,
getVisibleBitmapState().getRequiredBitmapsForTest()));
}
/**
* Mocks responses on bitmap requests from {@link PlayerFrameMediator} and tests those responses
* are correctly handled.
*/
@Test
public void testBitmapRequestResponse() {
// Sets the bitmap tile size to 150x200 and triggers bitmap request for the upper left tiles
// and their adjacent tiles.
mMediator.updateViewportSize(150, 200, 1f);
// Create mock bitmaps for response.
Bitmap bitmap00 = Mockito.mock(Bitmap.class);
Bitmap bitmap10 = Mockito.mock(Bitmap.class);
Bitmap bitmap20 = Mockito.mock(Bitmap.class);
Bitmap bitmap30 = Mockito.mock(Bitmap.class);
Bitmap bitmap01 = Mockito.mock(Bitmap.class);
Bitmap bitmap11 = Mockito.mock(Bitmap.class);
Bitmap bitmap21 = Mockito.mock(Bitmap.class);
Bitmap bitmap31 = Mockito.mock(Bitmap.class);
Bitmap bitmap02 = Mockito.mock(Bitmap.class);
Bitmap bitmap12 = Mockito.mock(Bitmap.class);
Bitmap bitmap22 = Mockito.mock(Bitmap.class);
Bitmap[][] expectedBitmapMatrix = new Bitmap[12][4];
expectedBitmapMatrix[0][0] = bitmap00;
expectedBitmapMatrix[0][1] = bitmap01;
expectedBitmapMatrix[1][0] = bitmap10;
expectedBitmapMatrix[1][1] = bitmap11;
expectedBitmapMatrix[2][0] = bitmap20;
// Call the request callback with mock bitmaps and assert they're added to the model.
mCompositorDelegate.mRequestedBitmap.get(0).mBitmapCallback.onResult(bitmap00);
mCompositorDelegate.mRequestedBitmap.get(1).mBitmapCallback.onResult(bitmap10);
mCompositorDelegate.mRequestedBitmap.get(2).mBitmapCallback.onResult(bitmap01);
mCompositorDelegate.mRequestedBitmap.get(3).mBitmapCallback.onResult(bitmap20);
mCompositorDelegate.mRequestedBitmap.get(4).mBitmapCallback.onResult(bitmap11);
Assert.assertTrue(
Arrays.deepEquals(
expectedBitmapMatrix, mModel.get(PlayerFrameProperties.BITMAP_MATRIX)));
// Move the viewport slightly..
mScrollController.scrollBy(10, 10);
// Scroll should've triggered bitmap requests for an the new tiles as well as adjacent
// tiles. See comments on {@link #testBitmapRequest} for details on which tiles will be
// requested.
// Call the request callback with mock bitmaps and assert they're added to the model.
expectedBitmapMatrix[2][1] = bitmap21;
// expectedBitmapMatrix[3][0] = bitmap30;
expectedBitmapMatrix[0][2] = bitmap02;
expectedBitmapMatrix[1][2] = bitmap12;
expectedBitmapMatrix[3][1] = bitmap31;
expectedBitmapMatrix[2][2] = bitmap22;
mCompositorDelegate.mRequestedBitmap.get(5).mBitmapCallback.onResult(bitmap21);
// Mock a compositing failure for this tile. No bitmaps should be added.
mCompositorDelegate.mRequestedBitmap.get(6).mErrorCallback.run();
mCompositorDelegate.mRequestedBitmap.get(7).mBitmapCallback.onResult(bitmap02);
mCompositorDelegate.mRequestedBitmap.get(8).mBitmapCallback.onResult(bitmap12);
mCompositorDelegate.mRequestedBitmap.get(9).mBitmapCallback.onResult(bitmap31);
mCompositorDelegate.mRequestedBitmap.get(10).mBitmapCallback.onResult(bitmap22);
Assert.assertTrue(
Arrays.deepEquals(
expectedBitmapMatrix, mModel.get(PlayerFrameProperties.BITMAP_MATRIX)));
// Assert 15 bitmap requests have been made in total.
Assert.assertEquals(11, mCompositorDelegate.mRequestedBitmap.size());
// Move the view port while staying within the current tiles in order to trigger the
// request logic again. Make sure only one new request is added, for the tile with a
// compositing failure.
mScrollController.scrollBy(10, 10);
Assert.assertEquals(12, mCompositorDelegate.mRequestedBitmap.size());
Assert.assertEquals(
new RequestedBitmap(mFrameGuid, getRectForTile(150, 100, 3, 0), 1f),
mCompositorDelegate.mRequestedBitmap.get(
mCompositorDelegate.mRequestedBitmap.size() - 1));
}
/**
* View port should be updated on scroll events. Bounds checks are verified in {@link
* PlayerFrameScrollControllerTest}.
*/
@Test
public void testViewPortOnScrollBy() {
// Initial view port setup.
mMediator.updateViewportSize(100, 200, 1f);
Rect expectedViewPort = new Rect(0, 0, 100, 200);
// Scroll right and down by a within bounds amount. Both scroll directions should be
// effective.
Assert.assertTrue(mScrollController.scrollBy(250f, 80f));
expectedViewPort.offset(250, 80);
Assert.assertEquals(expectedViewPort, mModel.get(PlayerFrameProperties.VIEWPORT));
}
/**
* Tests sub-frames' visibility when view port changes. sub-frames that are out of the view
* port's bounds should not be added to the model.
*/
@Test
public void testSubFramesPosition() {
Context context = Robolectric.buildActivity(Activity.class).get();
View subFrame1View = new View(context);
View subFrame2View = new View(context);
View subFrame3View = new View(context);
Pair<View, Rect> subFrame1 = new Pair<>(subFrame1View, new Rect(10, 20, 60, 120));
Pair<View, Rect> subFrame2 = new Pair<>(subFrame2View, new Rect(30, 130, 70, 160));
Pair<View, Rect> subFrame3 = new Pair<>(subFrame3View, new Rect(120, 35, 150, 65));
PlayerFrameMediator subFrame1Mediator = Mockito.mock(PlayerFrameMediator.class);
PlayerFrameMediator subFrame2Mediator = Mockito.mock(PlayerFrameMediator.class);
PlayerFrameMediator subFrame3Mediator = Mockito.mock(PlayerFrameMediator.class);
InOrder inOrder = inOrder(subFrame1Mediator, subFrame2Mediator, subFrame3Mediator);
mMediator.addSubFrame(subFrame1.first, subFrame1.second, subFrame1Mediator);
mMediator.addSubFrame(subFrame2.first, subFrame2.second, subFrame2Mediator);
mMediator.addSubFrame(subFrame3.first, subFrame3.second, subFrame3Mediator);
// Initial view port setup.
mMediator.updateViewportSize(100, 200, 1f);
inOrder.verify(subFrame1Mediator).setVisibleRegion(0, 0, 50, 100);
inOrder.verify(subFrame2Mediator).setVisibleRegion(0, 0, 40, 30);
inOrder.verify(subFrame3Mediator).setVisibleRegion(0, 0, 0, 0);
List<View> expectedViews = new ArrayList<>();
List<Rect> expectedRects = new ArrayList<>();
List<Boolean> expectedVisibility = new ArrayList<>();
expectedViews.add(subFrame1.first);
expectedViews.add(subFrame2.first);
expectedViews.add(subFrame3.first);
expectedRects.add(subFrame1.second);
expectedRects.add(subFrame2.second);
expectedRects.add(new Rect(0, 0, 0, 0));
expectedVisibility.add(true);
expectedVisibility.add(true);
expectedVisibility.add(false);
Assert.assertEquals(expectedViews, mModel.get(PlayerFrameProperties.SUBFRAME_VIEWS));
Assert.assertEquals(expectedRects, mModel.get(PlayerFrameProperties.SUBFRAME_RECTS));
Assert.assertEquals(
expectedVisibility,
getVisibilities(mModel.get(PlayerFrameProperties.SUBFRAME_VIEWS)));
mScrollController.scrollBy(100, 0);
inOrder.verify(subFrame1Mediator).setVisibleRegion(0, 0, 0, 0);
inOrder.verify(subFrame2Mediator).setVisibleRegion(0, 0, 0, 0);
inOrder.verify(subFrame3Mediator).setVisibleRegion(0, 0, 30, 30);
expectedRects.set(0, new Rect(0, 0, 0, 0));
expectedRects.set(1, new Rect(0, 0, 0, 0));
expectedRects.set(2, new Rect(20, 35, 50, 65));
expectedVisibility.clear();
expectedVisibility.add(false);
expectedVisibility.add(false);
expectedVisibility.add(true);
Assert.assertEquals(expectedViews, mModel.get(PlayerFrameProperties.SUBFRAME_VIEWS));
Assert.assertEquals(expectedRects, mModel.get(PlayerFrameProperties.SUBFRAME_RECTS));
Assert.assertEquals(
expectedVisibility,
getVisibilities(mModel.get(PlayerFrameProperties.SUBFRAME_VIEWS)));
mScrollController.scrollBy(-50, 0);
inOrder.verify(subFrame1Mediator).setVisibleRegion(40, 0, 50, 100);
inOrder.verify(subFrame2Mediator).setVisibleRegion(20, 0, 40, 30);
inOrder.verify(subFrame3Mediator).setVisibleRegion(0, 0, 30, 30);
expectedRects.clear();
expectedRects.add(new Rect(-40, 20, 10, 120));
expectedRects.add(new Rect(-20, 130, 20, 160));
expectedRects.add(new Rect(70, 35, 100, 65));
expectedVisibility.clear();
expectedVisibility.add(true);
expectedVisibility.add(true);
expectedVisibility.add(true);
Assert.assertEquals(expectedViews, mModel.get(PlayerFrameProperties.SUBFRAME_VIEWS));
Assert.assertEquals(expectedRects, mModel.get(PlayerFrameProperties.SUBFRAME_RECTS));
Assert.assertEquals(
expectedVisibility,
getVisibilities(mModel.get(PlayerFrameProperties.SUBFRAME_VIEWS)));
mScrollController.scrollBy(0, 200);
inOrder.verify(subFrame1Mediator).setVisibleRegion(0, 0, 0, 0);
inOrder.verify(subFrame2Mediator).setVisibleRegion(0, 0, 0, 0);
inOrder.verify(subFrame3Mediator).setVisibleRegion(0, 0, 0, 0);
expectedRects.clear();
expectedRects.add(new Rect(0, 0, 0, 0));
expectedRects.add(new Rect(0, 0, 0, 0));
expectedRects.add(new Rect(0, 0, 0, 0));
expectedVisibility.clear();
expectedVisibility.add(false);
expectedVisibility.add(false);
expectedVisibility.add(false);
Assert.assertEquals(expectedViews, mModel.get(PlayerFrameProperties.SUBFRAME_VIEWS));
Assert.assertEquals(expectedRects, mModel.get(PlayerFrameProperties.SUBFRAME_RECTS));
Assert.assertEquals(
expectedVisibility,
getVisibilities(mModel.get(PlayerFrameProperties.SUBFRAME_VIEWS)));
}
/**
* View port should be updated on fling events. There are more extensive tests for this in
* {@link PlayerFrameScrollControllerTest}.
*/
@Test
public void testViewPortOnFling() {
// Initial view port setup.
mMediator.updateViewportSize(100, 200, 1f);
Rect expectedViewPort = new Rect(0, 0, 100, 200);
mScrollController.onFling(100, 0);
expectedViewPort.offsetTo(mScroller.getFinalX(), mScroller.getFinalY());
ShadowLooper.runUiThreadTasks();
Assert.assertTrue(mScroller.isFinished());
Assert.assertEquals(expectedViewPort, mModel.get(PlayerFrameProperties.VIEWPORT));
}
/**
* Tests that {@link PlayerFrameMediator} correctly relays the click events to {@link
* PlayerCompositorDelegate} and accounts for scroll offsets.
*/
@Test
public void testOnClick() {
// Initial view port setup.
mMediator.updateViewportSize(100, 200, 1f);
List<ClickedPoint> expectedClickedPoints = new ArrayList<>();
// No scrolling has happened yet.
mMediator.onTap(15, 26, false);
expectedClickedPoints.add(new ClickedPoint(mFrameGuid, 15, 26));
Assert.assertEquals(expectedClickedPoints, mCompositorDelegate.mClickedPoints);
// Scroll, and then click. The call to {@link PlayerFrameMediator} must account for the
// scroll offset.
mScrollController.scrollBy(90, 100);
mMediator.onTap(70, 50, false);
expectedClickedPoints.add(new ClickedPoint(mFrameGuid, 160, 150));
Assert.assertEquals(expectedClickedPoints, mCompositorDelegate.mClickedPoints);
mScrollController.scrollBy(-40, -60);
mMediator.onTap(30, 80, false);
expectedClickedPoints.add(new ClickedPoint(mFrameGuid, 80, 120));
Assert.assertEquals(expectedClickedPoints, mCompositorDelegate.mClickedPoints);
}
/**
* Tests that {@link PlayerFrameMediator} correctly consumes scale events. There are more
* extensive tests for keeping the viewport in bounds and ensuring limits on scaling in {@link
* PlayerFrameScaleControllerTest}.
*/
@Test
public void testViewPortOnScaleBy() {
// Initial view port setup.
mMediator.setLayoutDimensions(100, 200);
mMediator.updateViewportSize(100, 200, 1f);
// The current view port fully matches the top left bitmap tile.
// Below is a schematic of the entire bitmap matrix. Tiles marked with x are required for
// the current view port.
// -------------------------
// | x | x | x | | | |
// -------------------------
// | x | x | x | | | |
// -------------------------
// | x | x | | | | |
// -------------------------
// | | | | | | |
// -------------------------
// | | | | | | |
// -------------------------
// | | | | | | |
boolean[][] expectedRequiredBitmaps = new boolean[12][6];
expectedRequiredBitmaps[0][0] = true;
expectedRequiredBitmaps[0][1] = true;
expectedRequiredBitmaps[1][0] = true;
expectedRequiredBitmaps[1][1] = true;
expectedRequiredBitmaps[2][0] = true;
mBitmapStateController.swapForTest();
Assert.assertTrue(
Arrays.deepEquals(
expectedRequiredBitmaps,
getVisibleBitmapState().getRequiredBitmapsForTest()));
// Now a scale factor of 2 will be applied. This will happen at a focal point of 0, 0.
// The same bitmaps will be required but the grid will be double the size.
Assert.assertTrue(mScaleController.scaleBy(2f, 0, 0));
Assert.assertTrue(mScaleController.scaleFinished(1f, 0, 0));
mBitmapStateController.swapForTest();
expectedRequiredBitmaps = new boolean[23][12];
expectedRequiredBitmaps[0][0] = true;
expectedRequiredBitmaps[0][1] = true;
expectedRequiredBitmaps[1][0] = true;
expectedRequiredBitmaps[1][1] = true;
expectedRequiredBitmaps[2][0] = true;
Assert.assertTrue(
Arrays.deepEquals(
expectedRequiredBitmaps,
getVisibleBitmapState().getRequiredBitmapsForTest()));
// Reduce the scale factor by 0.5 returning to a scale of 1.
Assert.assertTrue(mScaleController.scaleBy(0.5f, 0, 0));
Assert.assertTrue(mScaleController.scaleFinished(1f, 0, 0));
mBitmapStateController.swapForTest();
expectedRequiredBitmaps = new boolean[12][6];
expectedRequiredBitmaps[0][0] = true;
expectedRequiredBitmaps[0][1] = true;
expectedRequiredBitmaps[1][0] = true;
expectedRequiredBitmaps[1][1] = true;
expectedRequiredBitmaps[2][0] = true;
Assert.assertTrue(
Arrays.deepEquals(
expectedRequiredBitmaps,
getVisibleBitmapState().getRequiredBitmapsForTest()));
}
/** Tests that {@link PlayerFrameMediator} works correctly when scrolling. */
@Test
public void testViewPortOnScaleByWithScroll() {
// Initial view port setup.
mMediator.updateViewportSize(100, 200, 1f);
boolean[][] expectedRequiredBitmaps = new boolean[12][6];
// STEP 1: Original request.
// The current view port fully matches the top left bitmap tile.
// Below is a schematic of the entire bitmap matrix. Tiles marked with x are required for
// the current view port.
// -------------------------
// | x | x | | | | |
// -------------------------
// | x | x | | | | |
// -------------------------
// | x | | | | | |
// -------------------------
// | | | | | | |
// -------------------------
// | | | | | | |
// -------------------------
// | | | | | | |
// -------------------------
expectedRequiredBitmaps[0][0] = true;
expectedRequiredBitmaps[0][1] = true;
expectedRequiredBitmaps[1][0] = true;
expectedRequiredBitmaps[1][1] = true;
expectedRequiredBitmaps[2][0] = true;
List<RequestedBitmap> expectedRequestedBitmaps = new ArrayList<>();
expectedRequestedBitmaps.add(
new RequestedBitmap(mFrameGuid, getRectForTile(100, 100, 0, 0), 1f));
expectedRequestedBitmaps.add(
new RequestedBitmap(mFrameGuid, getRectForTile(100, 100, 1, 0), 1f));
expectedRequestedBitmaps.add(
new RequestedBitmap(mFrameGuid, getRectForTile(100, 100, 0, 1), 1f));
expectedRequestedBitmaps.add(
new RequestedBitmap(mFrameGuid, getRectForTile(100, 100, 2, 0), 1f));
expectedRequestedBitmaps.add(
new RequestedBitmap(mFrameGuid, getRectForTile(100, 100, 1, 1), 1f));
// Both matricies should be identity to start.
assertViewportStateIs(1f, 0f, 0f, mMediator.getViewport());
Assert.assertTrue(mModel.get(PlayerFrameProperties.SCALE_MATRIX).isIdentity());
// Ensure the correct bitmaps are required and requested.
Assert.assertTrue(
Arrays.deepEquals(
expectedRequiredBitmaps,
getVisibleBitmapState().getRequiredBitmapsForTest()));
Assert.assertEquals(expectedRequestedBitmaps, mCompositorDelegate.mRequestedBitmap);
// STEP 2: Scroll slightly.
mScrollController.scrollBy(10, 15);
// -------------------------
// | x | x | x | | | |
// -------------------------
// | x | x | x | | | |
// -------------------------
// | x | x | x | | | |
// -------------------------
// | x | x | | | | |
// -------------------------
// | | | | | | |
// -------------------------
// | | | | | | |
// -------------------------
expectedRequiredBitmaps[3][0] = true;
expectedRequiredBitmaps[3][1] = true;
expectedRequiredBitmaps[2][1] = true;
expectedRequiredBitmaps[2][2] = true;
expectedRequiredBitmaps[0][2] = true;
expectedRequiredBitmaps[1][2] = true;
Assert.assertTrue(
Arrays.deepEquals(
expectedRequiredBitmaps,
getVisibleBitmapState().getRequiredBitmapsForTest()));
expectedRequestedBitmaps.add(
new RequestedBitmap(mFrameGuid, getRectForTile(100, 100, 2, 1), 1f));
expectedRequestedBitmaps.add(
new RequestedBitmap(mFrameGuid, getRectForTile(100, 100, 3, 0), 1f));
expectedRequestedBitmaps.add(
new RequestedBitmap(mFrameGuid, getRectForTile(100, 100, 0, 2), 1f));
expectedRequestedBitmaps.add(
new RequestedBitmap(mFrameGuid, getRectForTile(100, 100, 1, 2), 1f));
expectedRequestedBitmaps.add(
new RequestedBitmap(mFrameGuid, getRectForTile(100, 100, 3, 1), 1f));
expectedRequestedBitmaps.add(
new RequestedBitmap(mFrameGuid, getRectForTile(100, 100, 2, 2), 1f));
Assert.assertEquals(expectedRequestedBitmaps, mCompositorDelegate.mRequestedBitmap);
// The viewport matrix should track scroll and zoom.
Matrix expectedViewportMatrix = new Matrix();
float[] expectedViewportMatrixValues = new float[9];
expectedViewportMatrix.getValues(expectedViewportMatrixValues);
expectedViewportMatrixValues[Matrix.MTRANS_X] = 10;
expectedViewportMatrixValues[Matrix.MTRANS_Y] = 15;
expectedViewportMatrix.setValues(expectedViewportMatrixValues);
assertViewportStateIs(expectedViewportMatrix, mMediator.getViewport());
Assert.assertTrue(mModel.get(PlayerFrameProperties.SCALE_MATRIX).isIdentity());
// STEP 3: Now a scale factor of 2 will be applied. This will happen at a focal point of 50,
// 100.
Assert.assertTrue(mScaleController.scaleBy(2f, 50f, 100f));
// Before the scaling commits both matricies should update.
expectedViewportMatrix.postScale(2f, 2f, -50f, -100f);
Matrix expectedBitmapMatrix = new Matrix();
expectedBitmapMatrix.postScale(2f, 2f, 50f, 100f);
assertViewportStateIs(expectedViewportMatrix, mMediator.getViewport());
Assert.assertEquals(expectedBitmapMatrix, mModel.get(PlayerFrameProperties.SCALE_MATRIX));
// Bitmaps should be the same as before scaling until scaling is finished.
Assert.assertEquals(expectedRequestedBitmaps, mCompositorDelegate.mRequestedBitmap);
mCompositorDelegate.mRequestedBitmap.clear();
expectedRequestedBitmaps.clear();
Assert.assertTrue(mScaleController.scaleFinished(1f, 0, 0));
mBitmapStateController.swapForTest();
expectedRequiredBitmaps = new boolean[23][12];
expectedRequiredBitmaps[0][0] = true;
expectedRequiredBitmaps[0][1] = true;
expectedRequiredBitmaps[1][0] = true;
expectedRequiredBitmaps[1][1] = true;
expectedRequiredBitmaps[1][2] = true;
expectedRequiredBitmaps[2][0] = true;
expectedRequiredBitmaps[2][1] = true;
expectedRequiredBitmaps[2][2] = true;
expectedRequiredBitmaps[3][0] = true;
expectedRequiredBitmaps[3][1] = true;
expectedRequiredBitmaps[3][2] = true;
expectedRequiredBitmaps[4][0] = true;
expectedRequiredBitmaps[4][1] = true;
Assert.assertTrue(
Arrays.deepEquals(
expectedRequiredBitmaps,
getVisibleBitmapState().getRequiredBitmapsForTest()));
expectedRequestedBitmaps.add(
new RequestedBitmap(mFrameGuid, getRectForTile(100, 100, 1, 0), 2f));
expectedRequestedBitmaps.add(
new RequestedBitmap(mFrameGuid, getRectForTile(100, 100, 2, 0), 2f));
expectedRequestedBitmaps.add(
new RequestedBitmap(mFrameGuid, getRectForTile(100, 100, 3, 0), 2f));
expectedRequestedBitmaps.add(
new RequestedBitmap(mFrameGuid, getRectForTile(100, 100, 1, 1), 2f));
expectedRequestedBitmaps.add(
new RequestedBitmap(mFrameGuid, getRectForTile(100, 100, 2, 1), 2f));
expectedRequestedBitmaps.add(
new RequestedBitmap(mFrameGuid, getRectForTile(100, 100, 3, 1), 2f));
expectedRequestedBitmaps.add(
new RequestedBitmap(mFrameGuid, getRectForTile(100, 100, 0, 0), 2f));
expectedRequestedBitmaps.add(
new RequestedBitmap(mFrameGuid, getRectForTile(100, 100, 4, 0), 2f));
expectedRequestedBitmaps.add(
new RequestedBitmap(mFrameGuid, getRectForTile(100, 100, 0, 1), 2f));
expectedRequestedBitmaps.add(
new RequestedBitmap(mFrameGuid, getRectForTile(100, 100, 1, 2), 2f));
expectedRequestedBitmaps.add(
new RequestedBitmap(mFrameGuid, getRectForTile(100, 100, 2, 2), 2f));
expectedRequestedBitmaps.add(
new RequestedBitmap(mFrameGuid, getRectForTile(100, 100, 4, 1), 2f));
expectedRequestedBitmaps.add(
new RequestedBitmap(mFrameGuid, getRectForTile(100, 100, 3, 2), 2f));
Assert.assertEquals(expectedRequestedBitmaps, mCompositorDelegate.mRequestedBitmap);
// The bitmap matrix should be cleared.
expectedBitmapMatrix.reset();
Assert.assertTrue(mModel.get(PlayerFrameProperties.SCALE_MATRIX).isIdentity());
// STEP4: Now a scale factor of 0.5 will be applied. This will happen at a focal point of
// 50, 100.
Assert.assertTrue(mScaleController.scaleBy(0.5f, 50f, 100f));
// Ensure the matricies are correct mid-scale.
expectedViewportMatrix.postScale(0.5f, 0.5f, -50f, -100f);
expectedBitmapMatrix.postScale(0.5f, 0.5f, 50f, 100f);
assertViewportStateIs(expectedViewportMatrix, mMediator.getViewport());
Assert.assertEquals(expectedBitmapMatrix, mModel.get(PlayerFrameProperties.SCALE_MATRIX));
// Bitmaps should be the same as before scaling until scaling is finished.
Assert.assertEquals(expectedRequestedBitmaps, mCompositorDelegate.mRequestedBitmap);
mCompositorDelegate.mRequestedBitmap.clear();
expectedRequestedBitmaps.clear();
Assert.assertTrue(mScaleController.scaleFinished(1f, 0, 0));
mBitmapStateController.swapForTest();
expectedRequiredBitmaps = new boolean[12][6];
expectedRequiredBitmaps[0][0] = true;
expectedRequiredBitmaps[0][1] = true;
expectedRequiredBitmaps[0][2] = true;
expectedRequiredBitmaps[1][0] = true;
expectedRequiredBitmaps[1][1] = true;
expectedRequiredBitmaps[1][2] = true;
expectedRequiredBitmaps[2][0] = true;
expectedRequiredBitmaps[2][1] = true;
expectedRequiredBitmaps[2][2] = true;
expectedRequiredBitmaps[3][0] = true;
expectedRequiredBitmaps[3][1] = true;
Assert.assertTrue(
Arrays.deepEquals(
expectedRequiredBitmaps,
getVisibleBitmapState().getRequiredBitmapsForTest()));
expectedRequestedBitmaps.add(
new RequestedBitmap(mFrameGuid, getRectForTile(100, 100, 0, 0), 1f));
expectedRequestedBitmaps.add(
new RequestedBitmap(mFrameGuid, getRectForTile(100, 100, 1, 0), 1f));
expectedRequestedBitmaps.add(
new RequestedBitmap(mFrameGuid, getRectForTile(100, 100, 2, 0), 1f));
expectedRequestedBitmaps.add(
new RequestedBitmap(mFrameGuid, getRectForTile(100, 100, 0, 1), 1f));
expectedRequestedBitmaps.add(
new RequestedBitmap(mFrameGuid, getRectForTile(100, 100, 1, 1), 1f));
expectedRequestedBitmaps.add(
new RequestedBitmap(mFrameGuid, getRectForTile(100, 100, 2, 1), 1f));
expectedRequestedBitmaps.add(
new RequestedBitmap(mFrameGuid, getRectForTile(100, 100, 3, 0), 1f));
expectedRequestedBitmaps.add(
new RequestedBitmap(mFrameGuid, getRectForTile(100, 100, 0, 2), 1f));
expectedRequestedBitmaps.add(
new RequestedBitmap(mFrameGuid, getRectForTile(100, 100, 1, 2), 1f));
expectedRequestedBitmaps.add(
new RequestedBitmap(mFrameGuid, getRectForTile(100, 100, 3, 1), 1f));
expectedRequestedBitmaps.add(
new RequestedBitmap(mFrameGuid, getRectForTile(100, 100, 2, 2), 1f));
Assert.assertEquals(expectedRequestedBitmaps, mCompositorDelegate.mRequestedBitmap);
expectedBitmapMatrix.reset();
Assert.assertTrue(mModel.get(PlayerFrameProperties.SCALE_MATRIX).isIdentity());
// Now a scale factor of 2 will be applied. This will happen at a focal point of 100, 200.
// Due to the position of the focal point the required bitmaps will move.
// -------------------------
// | | x | x | | | |
// -------------------------
// | x | x | x | x | | |
// -------------------------
// | x | x | x | x | | |
// -------------------------
// | x | x | x | x | | |
// -------------------------
// | | x | x | | | |
// -------------------------
// | | | | | | |
Assert.assertTrue(mScaleController.scaleBy(2f, 100f, 200f));
expectedViewportMatrix.postScale(2f, 2f, -100f, -200f);
expectedBitmapMatrix.postScale(2f, 2f, 100f, 200f);
assertViewportStateIs(expectedViewportMatrix, mMediator.getViewport());
Assert.assertEquals(expectedBitmapMatrix, mModel.get(PlayerFrameProperties.SCALE_MATRIX));
Assert.assertEquals(expectedRequestedBitmaps, mCompositorDelegate.mRequestedBitmap);
mCompositorDelegate.mRequestedBitmap.clear();
expectedRequestedBitmaps.clear();
Assert.assertTrue(mScaleController.scaleFinished(1f, 0, 0));
mBitmapStateController.swapForTest();
expectedRequiredBitmaps = new boolean[23][12];
expectedRequiredBitmaps[1][1] = true;
expectedRequiredBitmaps[1][2] = true;
expectedRequiredBitmaps[2][0] = true;
expectedRequiredBitmaps[2][1] = true;
expectedRequiredBitmaps[2][2] = true;
expectedRequiredBitmaps[2][3] = true;
expectedRequiredBitmaps[3][0] = true;
expectedRequiredBitmaps[3][1] = true;
expectedRequiredBitmaps[3][2] = true;
expectedRequiredBitmaps[3][3] = true;
expectedRequiredBitmaps[4][0] = true;
expectedRequiredBitmaps[4][1] = true;
expectedRequiredBitmaps[4][2] = true;
expectedRequiredBitmaps[4][3] = true;
expectedRequiredBitmaps[5][1] = true;
expectedRequiredBitmaps[5][2] = true;
Assert.assertTrue(
Arrays.deepEquals(
expectedRequiredBitmaps,
getVisibleBitmapState().getRequiredBitmapsForTest()));
expectedRequestedBitmaps.add(
new RequestedBitmap(mFrameGuid, getRectForTile(100, 100, 2, 1), 2f));
expectedRequestedBitmaps.add(
new RequestedBitmap(mFrameGuid, getRectForTile(100, 100, 3, 1), 2f));
expectedRequestedBitmaps.add(
new RequestedBitmap(mFrameGuid, getRectForTile(100, 100, 4, 1), 2f));
expectedRequestedBitmaps.add(
new RequestedBitmap(mFrameGuid, getRectForTile(100, 100, 2, 2), 2f));
expectedRequestedBitmaps.add(
new RequestedBitmap(mFrameGuid, getRectForTile(100, 100, 3, 2), 2f));
expectedRequestedBitmaps.add(
new RequestedBitmap(mFrameGuid, getRectForTile(100, 100, 4, 2), 2f));
expectedRequestedBitmaps.add(
new RequestedBitmap(mFrameGuid, getRectForTile(100, 100, 1, 1), 2f));
expectedRequestedBitmaps.add(
new RequestedBitmap(mFrameGuid, getRectForTile(100, 100, 2, 0), 2f));
expectedRequestedBitmaps.add(
new RequestedBitmap(mFrameGuid, getRectForTile(100, 100, 3, 0), 2f));
expectedRequestedBitmaps.add(
new RequestedBitmap(mFrameGuid, getRectForTile(100, 100, 5, 1), 2f));
expectedRequestedBitmaps.add(
new RequestedBitmap(mFrameGuid, getRectForTile(100, 100, 4, 0), 2f));
expectedRequestedBitmaps.add(
new RequestedBitmap(mFrameGuid, getRectForTile(100, 100, 1, 2), 2f));
expectedRequestedBitmaps.add(
new RequestedBitmap(mFrameGuid, getRectForTile(100, 100, 2, 3), 2f));
expectedRequestedBitmaps.add(
new RequestedBitmap(mFrameGuid, getRectForTile(100, 100, 3, 3), 2f));
expectedRequestedBitmaps.add(
new RequestedBitmap(mFrameGuid, getRectForTile(100, 100, 5, 2), 2f));
expectedRequestedBitmaps.add(
new RequestedBitmap(mFrameGuid, getRectForTile(100, 100, 4, 3), 2f));
Assert.assertEquals(expectedRequestedBitmaps, mCompositorDelegate.mRequestedBitmap);
expectedBitmapMatrix.reset();
Assert.assertTrue(mModel.get(PlayerFrameProperties.SCALE_MATRIX).isIdentity());
}
/** Tests that {@link PlayerFrameMediator} works correctly when subframes are present. */
@Test
public void testViewPortOnScaleByWithSubFrames() {
Context context = Robolectric.buildActivity(Activity.class).get();
View subFrame1View = new View(context);
View subFrame2View = new View(context);
PlayerFrameMediator subFrame1Mediator = Mockito.mock(PlayerFrameMediator.class);
Pair<View, Rect> subFrame1 = new Pair<>(subFrame1View, new Rect(10, 20, 60, 40));
PlayerFrameMediator subFrame2Mediator = Mockito.mock(PlayerFrameMediator.class);
Pair<View, Rect> subFrame2 = new Pair<>(subFrame2View, new Rect(30, 50, 70, 160));
InOrder inOrderMediator1 = inOrder(subFrame1Mediator);
InOrder inOrderMediator2 = inOrder(subFrame2Mediator);
mMediator.addSubFrame(subFrame1.first, subFrame1.second, subFrame1Mediator);
mMediator.addSubFrame(subFrame2.first, subFrame2.second, subFrame2Mediator);
// Both subframes should be visible.
mMediator.updateViewportSize(50, 100, 1f);
List<View> expectedViews = new ArrayList<>();
List<Rect> expectedRects = new ArrayList<>();
List<Boolean> expectedVisibility = new ArrayList<>();
expectedViews.add(subFrame1.first);
expectedViews.add(subFrame2.first);
expectedRects.add(subFrame1.second);
expectedRects.add(subFrame2.second);
expectedVisibility.add(true);
expectedVisibility.add(true);
Assert.assertEquals(expectedViews, mModel.get(PlayerFrameProperties.SUBFRAME_VIEWS));
Assert.assertEquals(expectedRects, mModel.get(PlayerFrameProperties.SUBFRAME_RECTS));
Assert.assertEquals(
expectedVisibility,
getVisibilities(mModel.get(PlayerFrameProperties.SUBFRAME_VIEWS)));
expectedRects.clear();
expectedRects.add(new Rect(20, 40, 120, 80));
expectedRects.add(new Rect(0, 0, 0, 0));
expectedVisibility.set(1, false);
// During scaling the second subframe should disappear from the viewport.
Assert.assertTrue(mScaleController.scaleBy(2f, 0f, 0f));
Assert.assertEquals(expectedViews, mModel.get(PlayerFrameProperties.SUBFRAME_VIEWS));
Assert.assertEquals(expectedRects, mModel.get(PlayerFrameProperties.SUBFRAME_RECTS));
Assert.assertEquals(
expectedVisibility,
getVisibilities(mModel.get(PlayerFrameProperties.SUBFRAME_VIEWS)));
Matrix expectedMatrix = new Matrix();
expectedMatrix.setScale(2f, 2f);
inOrderMediator1
.verify(subFrame1Mediator)
.setBitmapScaleMatrixOfSubframe(argThat(new MatrixMatcher(expectedMatrix)), eq(2f));
Assert.assertTrue(mScaleController.scaleFinished(1f, 0f, 0f));
mBitmapStateController.swapForTest();
inOrderMediator1.verify(subFrame1Mediator).updateScaleFactor(eq(2f));
inOrderMediator1.verify(subFrame1Mediator).forceRedraw();
Assert.assertEquals(expectedViews, mModel.get(PlayerFrameProperties.SUBFRAME_VIEWS));
Assert.assertEquals(expectedRects, mModel.get(PlayerFrameProperties.SUBFRAME_RECTS));
Assert.assertEquals(
expectedVisibility,
getVisibilities(mModel.get(PlayerFrameProperties.SUBFRAME_VIEWS)));
expectedMatrix.reset();
inOrderMediator1
.verify(subFrame1Mediator)
.setBitmapScaleMatrixOfSubframe(argThat(new MatrixMatcher(expectedMatrix)), eq(1f));
// Scroll so the second subframe is back in the viewport..
mScrollController.scrollBy(20, 40);
expectedRects.clear();
expectedRects.add(new Rect(0, 0, 100, 40));
expectedRects.add(new Rect(40, 60, 120, 280));
expectedVisibility.clear();
expectedVisibility.add(true);
expectedVisibility.add(true);
Assert.assertEquals(expectedViews, mModel.get(PlayerFrameProperties.SUBFRAME_VIEWS));
Assert.assertEquals(expectedRects, mModel.get(PlayerFrameProperties.SUBFRAME_RECTS));
Assert.assertEquals(
expectedVisibility,
getVisibilities(mModel.get(PlayerFrameProperties.SUBFRAME_VIEWS)));
// Scale out keeping the subframes in the viewport..
Assert.assertTrue(mScaleController.scaleBy(0.75f, 25f, 50f));
expectedRects.clear();
expectedRects.add(new Rect(6, 12, 81, 42));
expectedRects.add(new Rect(36, 57, 96, 222));
Assert.assertEquals(expectedViews, mModel.get(PlayerFrameProperties.SUBFRAME_VIEWS));
Assert.assertEquals(expectedRects, mModel.get(PlayerFrameProperties.SUBFRAME_RECTS));
Assert.assertEquals(
expectedVisibility,
getVisibilities(mModel.get(PlayerFrameProperties.SUBFRAME_VIEWS)));
expectedMatrix.setScale(0.75f, 0.75f);
inOrderMediator1
.verify(subFrame1Mediator)
.setBitmapScaleMatrixOfSubframe(
argThat(new MatrixMatcher(expectedMatrix)), eq(1.5f));
inOrderMediator2
.verify(subFrame2Mediator)
.setBitmapScaleMatrixOfSubframe(
argThat(new MatrixMatcher(expectedMatrix)), eq(1.5f));
}
/**
* Tests that {@link PlayerFrameMediator} works correctly with nested subframes. This test
* pretends that mMediator is for a subframe. The calls made to this mediator are verified to
* occur in {@link testViewPortOnScaleByWithSubFrames}.
*/
@Test
public void testViewPortOnScaleByWithNestedSubFrames() {
Context context = Robolectric.buildActivity(Activity.class).get();
View subframeView = new View(context);
PlayerFrameMediator subFrameMediator = Mockito.mock(PlayerFrameMediator.class);
Pair<View, Rect> subFrame = new Pair<>(subframeView, new Rect(10, 20, 60, 40));
mMediator.addSubFrame(subFrame.first, subFrame.second, subFrameMediator);
InOrder inOrder = inOrder(subFrameMediator);
// The subframe should be visible.
mMediator.updateViewportSize(50, 100, 1f);
List<View> expectedVisibleViews = new ArrayList<>();
List<Rect> expectedVisibleRects = new ArrayList<>();
expectedVisibleViews.add(subFrame.first);
expectedVisibleRects.add(subFrame.second);
Assert.assertEquals(expectedVisibleViews, mModel.get(PlayerFrameProperties.SUBFRAME_VIEWS));
Assert.assertEquals(expectedVisibleRects, mModel.get(PlayerFrameProperties.SUBFRAME_RECTS));
expectedVisibleViews.clear();
expectedVisibleRects.clear();
expectedVisibleViews.add(subFrame.first);
expectedVisibleRects.add(new Rect(20, 40, 120, 80));
// Scale by a factor of two via the parent.
Matrix scaleMatrix = new Matrix();
scaleMatrix.setScale(2f, 2f);
mMediator.setBitmapScaleMatrixOfSubframe(scaleMatrix, 2f);
Assert.assertEquals(expectedVisibleViews, mModel.get(PlayerFrameProperties.SUBFRAME_VIEWS));
Assert.assertEquals(expectedVisibleRects, mModel.get(PlayerFrameProperties.SUBFRAME_RECTS));
inOrder.verify(subFrameMediator)
.setBitmapScaleMatrixOfSubframe(argThat(new MatrixMatcher(scaleMatrix)), eq(2f));
expectedVisibleViews.clear();
expectedVisibleRects.clear();
expectedVisibleViews.add(subFrame.first);
expectedVisibleRects.add(new Rect(15, 30, 90, 60));
// Zoom out by a factor of 0.75, note the scale factor argument is compounded.
scaleMatrix.setScale(0.75f, 0.75f);
mMediator.setBitmapScaleMatrixOfSubframe(scaleMatrix, 1.5f);
Assert.assertEquals(expectedVisibleViews, mModel.get(PlayerFrameProperties.SUBFRAME_VIEWS));
Assert.assertEquals(expectedVisibleRects, mModel.get(PlayerFrameProperties.SUBFRAME_RECTS));
inOrder.verify(subFrameMediator)
.setBitmapScaleMatrixOfSubframe(argThat(new MatrixMatcher(scaleMatrix)), eq(1.5f));
// Simulate scaleFinished() by force a scale factor clear and redraw.
mMediator.updateScaleFactor(1.5f);
inOrder.verify(subFrameMediator).updateScaleFactor(eq(1.5f));
mMediator.forceRedraw();
inOrder.verify(subFrameMediator).forceRedraw();
}
/** Tests that {@link PlayerFrameMediator} calls the user interaction callback. */
@Test
public void testUserInteractionCallback() {
mMediator.updateViewportSize(100, 200, 1f);
Assert.assertFalse(
"User interaction callback shouldn't have been called", mHasUserInteraction);
mScrollController.scrollBy(0, 10);
Assert.assertTrue("User interaction callback should have been called", mHasUserInteraction);
mHasUserInteraction = false;
mScrollController.onFling(0, 10);
Assert.assertTrue("User interaction callback should have been called", mHasUserInteraction);
mHasUserInteraction = false;
mScaleController.scaleBy(1.5f, 20, 30);
Assert.assertTrue("User interaction callback should have been called", mHasUserInteraction);
}
/** Tests that bitmap matrix is offset correctly. */
@Test
public void testOffsetBitmapScaleMatrix() {
Matrix bitmapScaleMatrix = mModel.get(PlayerFrameProperties.SCALE_MATRIX);
Matrix expectedBitmapScaleMatrix = new Matrix();
mMediator.offsetBitmapScaleMatrix(5f, 10f); // Should no-op.
Assert.assertEquals(expectedBitmapScaleMatrix, bitmapScaleMatrix);
bitmapScaleMatrix.postScale(2f, 2f);
mMediator.offsetBitmapScaleMatrix(5f, 10f);
expectedBitmapScaleMatrix.postScale(2f, 2f);
expectedBitmapScaleMatrix.postTranslate(-5f, -10f);
Assert.assertEquals(expectedBitmapScaleMatrix, bitmapScaleMatrix);
}
/** Tests purging on bitmap responses. */
@Test
public void testOnMemoryPressure() {
// Sets the bitmap tile size to 150x200 and triggers bitmap request for the upper left tiles
// and their adjacent tiles.
mMediator.updateViewportSize(150, 200, 1f);
// Create mock bitmaps for response.
Bitmap bitmap00 = Mockito.mock(Bitmap.class);
Bitmap bitmap10 = Mockito.mock(Bitmap.class);
Bitmap bitmap20 = Mockito.mock(Bitmap.class);
Bitmap bitmap01 = Mockito.mock(Bitmap.class);
Bitmap bitmap11 = Mockito.mock(Bitmap.class);
Bitmap[][] expectedBitmapMatrix = new Bitmap[12][4];
expectedBitmapMatrix[0][0] = bitmap00;
expectedBitmapMatrix[0][1] = bitmap01;
expectedBitmapMatrix[1][0] = bitmap10;
expectedBitmapMatrix[1][1] = bitmap11;
expectedBitmapMatrix[2][0] = bitmap20;
// Call the request callback with mock bitmaps and assert they're added to the model.
mCompositorDelegate.mRequestedBitmap.get(0).mBitmapCallback.onResult(bitmap00);
mCompositorDelegate.mRequestedBitmap.get(1).mBitmapCallback.onResult(bitmap10);
mCompositorDelegate.mRequestedBitmap.get(2).mBitmapCallback.onResult(bitmap01);
mCompositorDelegate.mRequestedBitmap.get(3).mBitmapCallback.onResult(bitmap20);
mCompositorDelegate.mRequestedBitmap.get(4).mBitmapCallback.onResult(bitmap11);
Assert.assertTrue(
Arrays.deepEquals(
expectedBitmapMatrix, mModel.get(PlayerFrameProperties.BITMAP_MATRIX)));
expectedBitmapMatrix = new Bitmap[12][4];
expectedBitmapMatrix[0][0] = bitmap00;
expectedBitmapMatrix[1][0] = bitmap10;
Assert.assertNotNull(mCompositorDelegate.mOnMemoryPressureRunnable);
mCompositorDelegate.mOnMemoryPressureRunnable.run();
Assert.assertTrue(
Arrays.deepEquals(
expectedBitmapMatrix, mModel.get(PlayerFrameProperties.BITMAP_MATRIX)));
}
}