Home | History | Annotate | Download | only in widget
      1 package android.support.v17.leanback.widget;
      2 
      3 import static org.junit.Assert.assertEquals;
      4 import static org.junit.Assert.assertNotNull;
      5 import static org.mockito.Matchers.anyInt;
      6 import static org.mockito.Mockito.mock;
      7 import static org.mockito.Mockito.times;
      8 import static org.mockito.Mockito.verify;
      9 import static org.mockito.Mockito.when;
     10 
     11 import android.content.Context;
     12 import android.os.Parcelable;
     13 import android.support.test.InstrumentationRegistry;
     14 import android.support.test.filters.SmallTest;
     15 import android.support.test.runner.AndroidJUnit4;
     16 import android.support.v7.widget.RecyclerView;
     17 import android.view.View;
     18 import android.view.ViewGroup;
     19 
     20 import org.junit.Test;
     21 import org.junit.runner.RunWith;
     22 
     23 import java.util.ArrayList;
     24 
     25 @SmallTest
     26 @RunWith(AndroidJUnit4.class)
     27 public class GridWidgetPrefetchTest {
     28 
     29     private Context getContext() {
     30         return InstrumentationRegistry.getContext();
     31     }
     32 
     33     private void layout(View view, int width, int height) {
     34         view.measure(
     35                 View.MeasureSpec.makeMeasureSpec(width, View.MeasureSpec.EXACTLY),
     36                 View.MeasureSpec.makeMeasureSpec(height, View.MeasureSpec.EXACTLY));
     37         view.layout(0, 0, width, height);
     38     }
     39 
     40     public void validatePrefetch(BaseGridView gridView, int scrollX, int scrollY,
     41             Integer[]... positionData) {
     42         // duplicates logic in support.v7.widget.CacheUtils#verifyPositionsPrefetched
     43         RecyclerView.State state = mock(RecyclerView.State.class);
     44         when(state.getItemCount()).thenReturn(gridView.getAdapter().getItemCount());
     45         RecyclerView.LayoutManager.LayoutPrefetchRegistry registry
     46                 = mock(RecyclerView.LayoutManager.LayoutPrefetchRegistry.class);
     47 
     48         gridView.getLayoutManager().collectAdjacentPrefetchPositions(scrollX, scrollY,
     49                 state, registry);
     50 
     51         verify(registry, times(positionData.length)).addPosition(anyInt(), anyInt());
     52         for (Integer[] aPositionData : positionData) {
     53             verify(registry).addPosition(aPositionData[0], aPositionData[1]);
     54         }
     55     }
     56 
     57     private RecyclerView.Adapter createBoxAdapter() {
     58         return new RecyclerView.Adapter() {
     59             @Override
     60             public RecyclerView.ViewHolder onCreateViewHolder(ViewGroup parent, int viewType) {
     61                 View view = new View(getContext());
     62                 view.setMinimumWidth(100);
     63                 view.setMinimumHeight(100);
     64                 return new RecyclerView.ViewHolder(view) {};
     65             }
     66 
     67             @Override
     68             public void onBindViewHolder(RecyclerView.ViewHolder holder, int position) {
     69                 // noop
     70             }
     71 
     72             @Override
     73             public int getItemCount() {
     74                 return 100;
     75             }
     76         };
     77     }
     78 
     79     @Test
     80     public void prefetch() {
     81         HorizontalGridView gridView = new HorizontalGridView(getContext());
     82         gridView.setNumRows(1);
     83         gridView.setRowHeight(100);
     84         gridView.setAdapter(createBoxAdapter());
     85 
     86         layout(gridView, 150, 100);
     87 
     88         // validate 2 children in viewport
     89         assertEquals(2, gridView.getChildCount());
     90         assertEquals(0, gridView.getLayoutManager().findViewByPosition(0).getLeft());
     91         assertEquals(100, gridView.getLayoutManager().findViewByPosition(1).getLeft());
     92 
     93         validatePrefetch(gridView, -50, 0); // no view to left
     94         validatePrefetch(gridView, 50, 0, new Integer[] {2, 50}); // next view 50 pixels to right
     95 
     96         // scroll to position 5, and layout
     97         gridView.scrollToPosition(5);
     98         layout(gridView, 150, 100);
     99 
    100         /* Visual representation, each number column represents 25 pixels:
    101          *              |           |
    102          * ... 3 3 4 4 4|4 5 5 5 5 6|6 6 6 7 7 ...
    103          *              |           |
    104          */
    105 
    106         // validate the 3 children in the viewport, and their positions
    107         assertEquals(3, gridView.getChildCount());
    108         assertNotNull(gridView.getLayoutManager().findViewByPosition(4));
    109         assertNotNull(gridView.getLayoutManager().findViewByPosition(5));
    110         assertNotNull(gridView.getLayoutManager().findViewByPosition(6));
    111         assertEquals(-75, gridView.getLayoutManager().findViewByPosition(4).getLeft());
    112         assertEquals(25, gridView.getLayoutManager().findViewByPosition(5).getLeft());
    113         assertEquals(125, gridView.getLayoutManager().findViewByPosition(6).getLeft());
    114 
    115         // next views are 75 pixels to right and left:
    116         validatePrefetch(gridView, -50, 0, new Integer[] {3, 75});
    117         validatePrefetch(gridView, 50, 0, new Integer[] {7, 75});
    118 
    119         // no views returned for vertical prefetch:
    120         validatePrefetch(gridView, 0, 10);
    121         validatePrefetch(gridView, 0, -10);
    122 
    123         // test minor offset
    124         gridView.scrollBy(5, 0);
    125         validatePrefetch(gridView, -50, 0, new Integer[] {3, 80});
    126         validatePrefetch(gridView, 50, 0, new Integer[] {7, 70});
    127     }
    128 
    129     @Test
    130     public void prefetchRtl() {
    131         HorizontalGridView gridView = new HorizontalGridView(getContext());
    132         gridView.setNumRows(1);
    133         gridView.setRowHeight(100);
    134         gridView.setAdapter(createBoxAdapter());
    135         gridView.setLayoutDirection(View.LAYOUT_DIRECTION_RTL);
    136 
    137         layout(gridView, 150, 100);
    138 
    139         // validate 2 children in viewport
    140         assertEquals(2, gridView.getChildCount());
    141         assertEquals(50, gridView.getLayoutManager().findViewByPosition(0).getLeft());
    142         assertEquals(-50, gridView.getLayoutManager().findViewByPosition(1).getLeft());
    143 
    144         validatePrefetch(gridView, 50, 0); // no view to right
    145         validatePrefetch(gridView, -10, 0, new Integer[] {2, 50}); // next view 50 pixels to right
    146 
    147 
    148         // scroll to position 5, and layout
    149         gridView.scrollToPosition(5);
    150         layout(gridView, 150, 100);
    151 
    152 
    153         /* Visual representation, each number column represents 25 pixels:
    154          *              |           |
    155          * ... 7 7 6 6 6|6 5 5 5 5 4|4 4 4 3 3 ...
    156          *              |           |
    157          */
    158         // validate 3 children in the viewport
    159         assertEquals(3, gridView.getChildCount());
    160         assertNotNull(gridView.getLayoutManager().findViewByPosition(6));
    161         assertNotNull(gridView.getLayoutManager().findViewByPosition(5));
    162         assertNotNull(gridView.getLayoutManager().findViewByPosition(4));
    163         assertEquals(-75, gridView.getLayoutManager().findViewByPosition(6).getLeft());
    164         assertEquals(25, gridView.getLayoutManager().findViewByPosition(5).getLeft());
    165         assertEquals(125, gridView.getLayoutManager().findViewByPosition(4).getLeft());
    166 
    167         // next views are 75 pixels to right and left:
    168         validatePrefetch(gridView, 50, 0, new Integer[] {3, 75});
    169         validatePrefetch(gridView, -50, 0, new Integer[] {7, 75});
    170 
    171         // no views returned for vertical prefetch:
    172         validatePrefetch(gridView, 0, 10);
    173         validatePrefetch(gridView, 0, -10);
    174 
    175         // test minor offset
    176         gridView.scrollBy(-5, 0);
    177         validatePrefetch(gridView, 50, 0, new Integer[] {3, 80});
    178         validatePrefetch(gridView, -50, 0, new Integer[] {7, 70});
    179     }
    180 
    181 
    182     class OuterAdapter extends RecyclerView.Adapter<OuterAdapter.ViewHolder> {
    183         OuterAdapter() {
    184             for (int i = 0; i < getItemCount(); i++) {
    185                 mAdapters.add(createBoxAdapter());
    186                 mSavedStates.add(null);
    187             }
    188         }
    189 
    190         class ViewHolder extends RecyclerView.ViewHolder {
    191             private final RecyclerView mRecyclerView;
    192             ViewHolder(RecyclerView itemView) {
    193                 super(itemView);
    194                 mRecyclerView = itemView;
    195             }
    196         }
    197 
    198         ArrayList<RecyclerView.Adapter> mAdapters = new ArrayList<>();
    199         ArrayList<Parcelable> mSavedStates = new ArrayList<>();
    200         RecyclerView.RecycledViewPool mSharedPool = new RecyclerView.RecycledViewPool();
    201 
    202         @Override
    203         public ViewHolder onCreateViewHolder(ViewGroup parent, int viewType) {
    204             HorizontalGridView gridView = new HorizontalGridView(getContext());
    205             gridView.setNumRows(1);
    206             gridView.setRowHeight(100);
    207             gridView.setLayoutDirection(View.LAYOUT_DIRECTION_LTR);
    208             gridView.setLayoutParams(new GridLayoutManager.LayoutParams(350, 100));
    209             gridView.setRecycledViewPool(mSharedPool);
    210             return new ViewHolder(gridView);
    211         }
    212 
    213         @Override
    214         public void onBindViewHolder(ViewHolder holder, int position) {
    215             holder.mRecyclerView.swapAdapter(mAdapters.get(position), true);
    216 
    217             Parcelable savedState = mSavedStates.get(position);
    218             if (savedState != null) {
    219                 holder.mRecyclerView.getLayoutManager().onRestoreInstanceState(savedState);
    220                 mSavedStates.set(position, null);
    221             }
    222         }
    223 
    224         @Override
    225         public int getItemCount() {
    226             return 100;
    227         }
    228     };
    229 
    230     public void validateInitialPrefetch(BaseGridView gridView,
    231             int... positionData) {
    232         RecyclerView.LayoutManager.LayoutPrefetchRegistry registry
    233                 = mock(RecyclerView.LayoutManager.LayoutPrefetchRegistry.class);
    234         gridView.getLayoutManager().collectInitialPrefetchPositions(
    235                 gridView.getAdapter().getItemCount(), registry);
    236 
    237         verify(registry, times(positionData.length)).addPosition(anyInt(), anyInt());
    238         for (int position : positionData) {
    239             verify(registry).addPosition(position, 0);
    240         }
    241     }
    242 
    243     @Test
    244     public void prefetchInitialFocusTest() {
    245         VerticalGridView view = new VerticalGridView(getContext());
    246         view.setNumColumns(1);
    247         view.setColumnWidth(350);
    248         view.setAdapter(createBoxAdapter());
    249 
    250         // check default
    251         assertEquals(4, view.getInitialPrefetchItemCount());
    252 
    253         // check setter behavior
    254         view.setInitialPrefetchItemCount(0);
    255         assertEquals(0, view.getInitialPrefetchItemCount());
    256 
    257         // check positions fetched, relative to focus
    258         view.scrollToPosition(2);
    259         view.setInitialPrefetchItemCount(5);
    260         validateInitialPrefetch(view, 0, 1, 2, 3, 4);
    261 
    262         view.setInitialPrefetchItemCount(3);
    263         validateInitialPrefetch(view, 1, 2, 3);
    264 
    265         view.scrollToPosition(0);
    266         view.setInitialPrefetchItemCount(4);
    267         validateInitialPrefetch(view, 0, 1, 2, 3);
    268 
    269         view.scrollToPosition(98);
    270         view.setInitialPrefetchItemCount(5);
    271         validateInitialPrefetch(view, 95, 96, 97, 98, 99);
    272 
    273         view.setInitialPrefetchItemCount(7);
    274         validateInitialPrefetch(view, 93, 94, 95, 96, 97, 98, 99);
    275 
    276         // implementation detail - rounds up
    277         view.scrollToPosition(50);
    278         view.setInitialPrefetchItemCount(4);
    279         validateInitialPrefetch(view, 49, 50, 51, 52);
    280     }
    281 
    282     @Test
    283     public void prefetchNested() {
    284         VerticalGridView gridView = new VerticalGridView(getContext());
    285         gridView.setNumColumns(1);
    286         gridView.setColumnWidth(350);
    287         OuterAdapter outerAdapter = new OuterAdapter();
    288         gridView.setAdapter(outerAdapter);
    289         gridView.setItemViewCacheSize(1); // enough to cache child 0 while offscreen
    290 
    291         layout(gridView, 350, 150);
    292 
    293         // validate 2 top level children in viewport
    294         assertEquals(2, gridView.getChildCount());
    295         for (int y = 0; y < 2; y++) {
    296             View child = gridView.getLayoutManager().findViewByPosition(y);
    297             assertEquals(y * 100, child.getTop());
    298             // each has 4 children
    299 
    300             HorizontalGridView inner = (HorizontalGridView) child;
    301             for (int x = 0; x < 4; x++) {
    302                 assertEquals(x * 100, inner.getLayoutManager().findViewByPosition(x).getLeft());
    303             }
    304         }
    305 
    306         // center child 0 at position 10
    307         HorizontalGridView offsetChild =
    308                 (HorizontalGridView) gridView.getLayoutManager().findViewByPosition(0);
    309         offsetChild.scrollToPosition(10);
    310 
    311         // scroll to position 2, and layout
    312         gridView.scrollToPosition(2);
    313         layout(gridView, 350, 150);
    314 
    315         // now, offset by 175, centered around row 2. Validate 3 top level children in viewport
    316         assertEquals(3, gridView.getChildCount());
    317         for (int y = 1; y < 4; y++) {
    318             assertEquals(y * 100 - 175, gridView.getLayoutManager().findViewByPosition(y).getTop());
    319         }
    320 
    321         validatePrefetch(gridView, 0, -5, new Integer[] {0, 75});
    322         validatePrefetch(gridView, 0, 5, new Integer[] {4, 75});
    323 
    324         // assume offsetChild still bound, in cache, just not attached...
    325         validateInitialPrefetch(offsetChild, 9, 10, 11, 12);
    326     }
    327 }
    328