blob: fdd1c07c81518296fa62d83475d5c2efed7701d9 [file] [log] [blame]
package androidx.leanback.widget;
import static org.junit.Assert.assertEquals;
import static org.junit.Assert.assertNotNull;
import static org.mockito.Matchers.anyInt;
import static org.mockito.Mockito.mock;
import static org.mockito.Mockito.times;
import static org.mockito.Mockito.verify;
import static org.mockito.Mockito.when;
import android.content.Context;
import android.os.Parcelable;
import android.support.test.InstrumentationRegistry;
import android.support.test.filters.SmallTest;
import android.support.test.runner.AndroidJUnit4;
import android.view.View;
import android.view.ViewGroup;
import androidx.recyclerview.widget.RecyclerView;
import org.junit.Test;
import org.junit.runner.RunWith;
import java.util.ArrayList;
@SmallTest
@RunWith(AndroidJUnit4.class)
public class GridWidgetPrefetchTest {
private Context getContext() {
return InstrumentationRegistry.getContext();
}
private void layout(View view, int width, int height) {
view.measure(
View.MeasureSpec.makeMeasureSpec(width, View.MeasureSpec.EXACTLY),
View.MeasureSpec.makeMeasureSpec(height, View.MeasureSpec.EXACTLY));
view.layout(0, 0, width, height);
}
public void validatePrefetch(BaseGridView gridView, int scrollX, int scrollY,
Integer[]... positionData) {
// duplicates logic in support.v7.widget.CacheUtils#verifyPositionsPrefetched
RecyclerView.State state = mock(RecyclerView.State.class);
when(state.getItemCount()).thenReturn(gridView.getAdapter().getItemCount());
RecyclerView.LayoutManager.LayoutPrefetchRegistry registry
= mock(RecyclerView.LayoutManager.LayoutPrefetchRegistry.class);
gridView.getLayoutManager().collectAdjacentPrefetchPositions(scrollX, scrollY,
state, registry);
verify(registry, times(positionData.length)).addPosition(anyInt(), anyInt());
for (Integer[] aPositionData : positionData) {
verify(registry).addPosition(aPositionData[0], aPositionData[1]);
}
}
private RecyclerView.Adapter createBoxAdapter() {
return new RecyclerView.Adapter() {
@Override
public RecyclerView.ViewHolder onCreateViewHolder(ViewGroup parent, int viewType) {
View view = new View(getContext());
view.setMinimumWidth(100);
view.setMinimumHeight(100);
return new RecyclerView.ViewHolder(view) {};
}
@Override
public void onBindViewHolder(RecyclerView.ViewHolder holder, int position) {
// noop
}
@Override
public int getItemCount() {
return 100;
}
};
}
@Test
public void prefetch() {
HorizontalGridView gridView = new HorizontalGridView(getContext());
gridView.setNumRows(1);
gridView.setRowHeight(100);
gridView.setAdapter(createBoxAdapter());
layout(gridView, 150, 100);
// validate 2 children in viewport
assertEquals(2, gridView.getChildCount());
assertEquals(0, gridView.getLayoutManager().findViewByPosition(0).getLeft());
assertEquals(100, gridView.getLayoutManager().findViewByPosition(1).getLeft());
validatePrefetch(gridView, -50, 0); // no view to left
validatePrefetch(gridView, 50, 0, new Integer[] {2, 50}); // next view 50 pixels to right
// scroll to position 5, and layout
gridView.scrollToPosition(5);
layout(gridView, 150, 100);
/* Visual representation, each number column represents 25 pixels:
* | |
* ... 3 3 4 4 4|4 5 5 5 5 6|6 6 6 7 7 ...
* | |
*/
// validate the 3 children in the viewport, and their positions
assertEquals(3, gridView.getChildCount());
assertNotNull(gridView.getLayoutManager().findViewByPosition(4));
assertNotNull(gridView.getLayoutManager().findViewByPosition(5));
assertNotNull(gridView.getLayoutManager().findViewByPosition(6));
assertEquals(-75, gridView.getLayoutManager().findViewByPosition(4).getLeft());
assertEquals(25, gridView.getLayoutManager().findViewByPosition(5).getLeft());
assertEquals(125, gridView.getLayoutManager().findViewByPosition(6).getLeft());
// next views are 75 pixels to right and left:
validatePrefetch(gridView, -50, 0, new Integer[] {3, 75});
validatePrefetch(gridView, 50, 0, new Integer[] {7, 75});
// no views returned for vertical prefetch:
validatePrefetch(gridView, 0, 10);
validatePrefetch(gridView, 0, -10);
// test minor offset
gridView.scrollBy(5, 0);
validatePrefetch(gridView, -50, 0, new Integer[] {3, 80});
validatePrefetch(gridView, 50, 0, new Integer[] {7, 70});
}
@Test
public void prefetchRtl() {
HorizontalGridView gridView = new HorizontalGridView(getContext());
gridView.setNumRows(1);
gridView.setRowHeight(100);
gridView.setAdapter(createBoxAdapter());
gridView.setLayoutDirection(View.LAYOUT_DIRECTION_RTL);
layout(gridView, 150, 100);
// validate 2 children in viewport
assertEquals(2, gridView.getChildCount());
assertEquals(50, gridView.getLayoutManager().findViewByPosition(0).getLeft());
assertEquals(-50, gridView.getLayoutManager().findViewByPosition(1).getLeft());
validatePrefetch(gridView, 50, 0); // no view to right
validatePrefetch(gridView, -10, 0, new Integer[] {2, 50}); // next view 50 pixels to right
// scroll to position 5, and layout
gridView.scrollToPosition(5);
layout(gridView, 150, 100);
/* Visual representation, each number column represents 25 pixels:
* | |
* ... 7 7 6 6 6|6 5 5 5 5 4|4 4 4 3 3 ...
* | |
*/
// validate 3 children in the viewport
assertEquals(3, gridView.getChildCount());
assertNotNull(gridView.getLayoutManager().findViewByPosition(6));
assertNotNull(gridView.getLayoutManager().findViewByPosition(5));
assertNotNull(gridView.getLayoutManager().findViewByPosition(4));
assertEquals(-75, gridView.getLayoutManager().findViewByPosition(6).getLeft());
assertEquals(25, gridView.getLayoutManager().findViewByPosition(5).getLeft());
assertEquals(125, gridView.getLayoutManager().findViewByPosition(4).getLeft());
// next views are 75 pixels to right and left:
validatePrefetch(gridView, 50, 0, new Integer[] {3, 75});
validatePrefetch(gridView, -50, 0, new Integer[] {7, 75});
// no views returned for vertical prefetch:
validatePrefetch(gridView, 0, 10);
validatePrefetch(gridView, 0, -10);
// test minor offset
gridView.scrollBy(-5, 0);
validatePrefetch(gridView, 50, 0, new Integer[] {3, 80});
validatePrefetch(gridView, -50, 0, new Integer[] {7, 70});
}
class OuterAdapter extends RecyclerView.Adapter<OuterAdapter.ViewHolder> {
OuterAdapter() {
for (int i = 0; i < getItemCount(); i++) {
mAdapters.add(createBoxAdapter());
mSavedStates.add(null);
}
}
class ViewHolder extends RecyclerView.ViewHolder {
private final RecyclerView mRecyclerView;
ViewHolder(RecyclerView itemView) {
super(itemView);
mRecyclerView = itemView;
}
}
ArrayList<RecyclerView.Adapter> mAdapters = new ArrayList<>();
ArrayList<Parcelable> mSavedStates = new ArrayList<>();
RecyclerView.RecycledViewPool mSharedPool = new RecyclerView.RecycledViewPool();
@Override
public ViewHolder onCreateViewHolder(ViewGroup parent, int viewType) {
HorizontalGridView gridView = new HorizontalGridView(getContext());
gridView.setNumRows(1);
gridView.setRowHeight(100);
gridView.setLayoutDirection(View.LAYOUT_DIRECTION_LTR);
gridView.setLayoutParams(new GridLayoutManager.LayoutParams(350, 100));
gridView.setRecycledViewPool(mSharedPool);
return new ViewHolder(gridView);
}
@Override
public void onBindViewHolder(ViewHolder holder, int position) {
holder.mRecyclerView.swapAdapter(mAdapters.get(position), true);
Parcelable savedState = mSavedStates.get(position);
if (savedState != null) {
holder.mRecyclerView.getLayoutManager().onRestoreInstanceState(savedState);
mSavedStates.set(position, null);
}
}
@Override
public int getItemCount() {
return 100;
}
};
public void validateInitialPrefetch(BaseGridView gridView,
int... positionData) {
RecyclerView.LayoutManager.LayoutPrefetchRegistry registry
= mock(RecyclerView.LayoutManager.LayoutPrefetchRegistry.class);
gridView.getLayoutManager().collectInitialPrefetchPositions(
gridView.getAdapter().getItemCount(), registry);
verify(registry, times(positionData.length)).addPosition(anyInt(), anyInt());
for (int position : positionData) {
verify(registry).addPosition(position, 0);
}
}
@Test
public void prefetchInitialFocusTest() {
VerticalGridView view = new VerticalGridView(getContext());
view.setNumColumns(1);
view.setColumnWidth(350);
view.setAdapter(createBoxAdapter());
// check default
assertEquals(4, view.getInitialPrefetchItemCount());
// check setter behavior
view.setInitialPrefetchItemCount(0);
assertEquals(0, view.getInitialPrefetchItemCount());
// check positions fetched, relative to focus
view.scrollToPosition(2);
view.setInitialPrefetchItemCount(5);
validateInitialPrefetch(view, 0, 1, 2, 3, 4);
view.setInitialPrefetchItemCount(3);
validateInitialPrefetch(view, 1, 2, 3);
view.scrollToPosition(0);
view.setInitialPrefetchItemCount(4);
validateInitialPrefetch(view, 0, 1, 2, 3);
view.scrollToPosition(98);
view.setInitialPrefetchItemCount(5);
validateInitialPrefetch(view, 95, 96, 97, 98, 99);
view.setInitialPrefetchItemCount(7);
validateInitialPrefetch(view, 93, 94, 95, 96, 97, 98, 99);
// implementation detail - rounds up
view.scrollToPosition(50);
view.setInitialPrefetchItemCount(4);
validateInitialPrefetch(view, 49, 50, 51, 52);
}
@Test
public void prefetchNested() {
VerticalGridView gridView = new VerticalGridView(getContext());
gridView.setNumColumns(1);
gridView.setColumnWidth(350);
OuterAdapter outerAdapter = new OuterAdapter();
gridView.setAdapter(outerAdapter);
gridView.setItemViewCacheSize(1); // enough to cache child 0 while offscreen
layout(gridView, 350, 150);
// validate 2 top level children in viewport
assertEquals(2, gridView.getChildCount());
for (int y = 0; y < 2; y++) {
View child = gridView.getLayoutManager().findViewByPosition(y);
assertEquals(y * 100, child.getTop());
// each has 4 children
HorizontalGridView inner = (HorizontalGridView) child;
for (int x = 0; x < 4; x++) {
assertEquals(x * 100, inner.getLayoutManager().findViewByPosition(x).getLeft());
}
}
// center child 0 at position 10
HorizontalGridView offsetChild =
(HorizontalGridView) gridView.getLayoutManager().findViewByPosition(0);
offsetChild.scrollToPosition(10);
// scroll to position 2, and layout
gridView.scrollToPosition(2);
layout(gridView, 350, 150);
// now, offset by 175, centered around row 2. Validate 3 top level children in viewport
assertEquals(3, gridView.getChildCount());
for (int y = 1; y < 4; y++) {
assertEquals(y * 100 - 175, gridView.getLayoutManager().findViewByPosition(y).getTop());
}
validatePrefetch(gridView, 0, -5, new Integer[] {0, 75});
validatePrefetch(gridView, 0, 5, new Integer[] {4, 75});
// assume offsetChild still bound, in cache, just not attached...
validateInitialPrefetch(offsetChild, 9, 10, 11, 12);
}
}