Home | History | Annotate | Download | only in widget
      1 /*
      2  * Copyright (C) 2014 The Android Open Source Project
      3  *
      4  * Licensed under the Apache License, Version 2.0 (the "License");
      5  * you may not use this file except in compliance with the License.
      6  * You may obtain a copy of the License at
      7  *
      8  *      http://www.apache.org/licenses/LICENSE-2.0
      9  *
     10  * Unless required by applicable law or agreed to in writing, software
     11  * distributed under the License is distributed on an "AS IS" BASIS,
     12  * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
     13  * See the License for the specific language governing permissions and
     14  * limitations under the License.
     15  */
     16 
     17 
     18 package android.support.v7.widget;
     19 
     20 import org.hamcrest.CoreMatchers;
     21 import org.hamcrest.MatcherAssert;
     22 import org.junit.After;
     23 import org.junit.Before;
     24 import org.junit.Test;
     25 import org.junit.runner.RunWith;
     26 import org.mockito.Mockito;
     27 
     28 import android.content.Context;
     29 import android.support.annotation.Nullable;
     30 import android.support.test.InstrumentationRegistry;
     31 import android.graphics.Color;
     32 import android.graphics.PointF;
     33 import android.graphics.Rect;
     34 import android.os.SystemClock;
     35 import android.support.test.InstrumentationRegistry;
     36 import android.support.test.runner.AndroidJUnit4;
     37 import android.support.v4.view.ViewCompat;
     38 import android.support.v7.util.TouchUtils;
     39 import android.test.suitebuilder.annotation.MediumTest;
     40 import android.util.AttributeSet;
     41 import android.util.Log;
     42 import android.view.Gravity;
     43 import android.view.MotionEvent;
     44 import android.view.View;
     45 import android.view.ViewConfiguration;
     46 import android.view.ViewGroup;
     47 import android.view.ViewTreeObserver;
     48 import android.widget.LinearLayout;
     49 import android.widget.TextView;
     50 
     51 import java.util.ArrayList;
     52 import java.util.HashMap;
     53 import java.util.List;
     54 import java.util.Map;
     55 import java.util.concurrent.CountDownLatch;
     56 import java.util.concurrent.TimeUnit;
     57 import java.util.concurrent.atomic.AtomicBoolean;
     58 import java.util.concurrent.atomic.AtomicInteger;
     59 
     60 import static android.support.v7.widget.RecyclerView.NO_POSITION;
     61 import static android.support.v7.widget.RecyclerView.SCROLL_STATE_DRAGGING;
     62 import static android.support.v7.widget.RecyclerView.SCROLL_STATE_IDLE;
     63 import static android.support.v7.widget.RecyclerView.SCROLL_STATE_SETTLING;
     64 import static android.support.v7.widget.RecyclerView.getChildViewHolderInt;
     65 
     66 import static org.hamcrest.CoreMatchers.sameInstance;
     67 import static org.junit.Assert.*;
     68 import static org.mockito.Mockito.*;
     69 import static org.hamcrest.CoreMatchers.is;
     70 
     71 @RunWith(AndroidJUnit4.class)
     72 @MediumTest
     73 public class RecyclerViewLayoutTest extends BaseRecyclerViewInstrumentationTest {
     74     private static final int FLAG_HORIZONTAL = 1;
     75     private static final int FLAG_VERTICAL = 1 << 1;
     76     private static final int FLAG_FLING = 1 << 2;
     77 
     78     private static final boolean DEBUG = false;
     79 
     80     private static final String TAG = "RecyclerViewLayoutTest";
     81 
     82     public RecyclerViewLayoutTest() {
     83         super(DEBUG);
     84     }
     85 
     86     @Test
     87     public void detachAttachGetReadyWithoutChanges() throws Throwable {
     88         detachAttachGetReady(false, false, false);
     89     }
     90 
     91     @Test
     92     public void detachAttachGetReadyRequireLayout() throws Throwable {
     93         detachAttachGetReady(true, false, false);
     94     }
     95 
     96     @Test
     97     public void detachAttachGetReadyRemoveAdapter() throws Throwable {
     98         detachAttachGetReady(false, true, false);
     99     }
    100 
    101     @Test
    102     public void detachAttachGetReadyRemoveLayoutManager() throws Throwable {
    103         detachAttachGetReady(false, false, true);
    104     }
    105 
    106     private void detachAttachGetReady(final boolean requestLayoutOnDetach,
    107             final boolean removeAdapter, final boolean removeLayoutManager) throws Throwable {
    108         final LinearLayout ll1 = new LinearLayout(getActivity());
    109         final LinearLayout ll2 = new LinearLayout(getActivity());
    110         final LinearLayout ll3 = new LinearLayout(getActivity());
    111 
    112         final RecyclerView rv = new RecyclerView(getActivity());
    113         ll1.addView(ll2);
    114         ll2.addView(ll3);
    115         ll3.addView(rv);
    116         TestLayoutManager layoutManager = new TestLayoutManager() {
    117             @Override
    118             public void onLayoutCompleted(RecyclerView.State state) {
    119                 super.onLayoutCompleted(state);
    120                 layoutLatch.countDown();
    121             }
    122 
    123             @Override
    124             public void onDetachedFromWindow(RecyclerView view, RecyclerView.Recycler recycler) {
    125                 super.onDetachedFromWindow(view, recycler);
    126                 if (requestLayoutOnDetach) {
    127                     view.requestLayout();
    128                 }
    129             }
    130         };
    131         rv.setLayoutManager(layoutManager);
    132         rv.setAdapter(new TestAdapter(10));
    133         layoutManager.expectLayouts(1);
    134         runTestOnUiThread(new Runnable() {
    135             @Override
    136             public void run() {
    137                 getActivity().getContainer().addView(ll1);
    138             }
    139         });
    140         layoutManager.waitForLayout(2);
    141         runTestOnUiThread(new Runnable() {
    142             @Override
    143             public void run() {
    144                 ll1.removeView(ll2);
    145             }
    146         });
    147         getInstrumentation().waitForIdleSync();
    148         if (removeLayoutManager) {
    149             rv.setLayoutManager(null);
    150             rv.setLayoutManager(layoutManager);
    151         }
    152         if (removeAdapter) {
    153             rv.setAdapter(null);
    154             rv.setAdapter(new TestAdapter(10));
    155         }
    156         final boolean requireLayout = requestLayoutOnDetach || removeAdapter || removeLayoutManager;
    157         layoutManager.expectLayouts(1);
    158         runTestOnUiThread(new Runnable() {
    159             @Override
    160             public void run() {
    161                 ll1.addView(ll2);
    162                 if (requireLayout) {
    163                     assertTrue(rv.hasPendingAdapterUpdates());
    164                     assertFalse(rv.mFirstLayoutComplete);
    165                 } else {
    166                     assertFalse(rv.hasPendingAdapterUpdates());
    167                     assertTrue(rv.mFirstLayoutComplete);
    168                 }
    169             }
    170         });
    171         if (requireLayout) {
    172             layoutManager.waitForLayout(2);
    173         } else {
    174             layoutManager.assertNoLayout("nothing is invalid, layout should not happen", 2);
    175         }
    176     }
    177 
    178     @Test
    179     public void focusSearchWithOtherFocusables() throws Throwable {
    180         final LinearLayout container = new LinearLayout(getActivity());
    181         container.setOrientation(LinearLayout.VERTICAL);
    182         RecyclerView rv = new RecyclerView(getActivity());
    183         mRecyclerView = rv;
    184         rv.setAdapter(new TestAdapter(10) {
    185             @Override
    186             public void onBindViewHolder(TestViewHolder holder,
    187                     int position) {
    188                 super.onBindViewHolder(holder, position);
    189                 holder.itemView.setFocusableInTouchMode(true);
    190                 holder.itemView.setLayoutParams(
    191                         new ViewGroup.LayoutParams(ViewGroup.LayoutParams.MATCH_PARENT,
    192                         ViewGroup.LayoutParams.WRAP_CONTENT));
    193             }
    194         });
    195         TestLayoutManager tlm = new TestLayoutManager() {
    196             @Override
    197             public void onLayoutChildren(RecyclerView.Recycler recycler, RecyclerView.State state) {
    198                 detachAndScrapAttachedViews(recycler);
    199                 layoutRange(recycler, 0, 1);
    200                 layoutLatch.countDown();
    201             }
    202 
    203             @Nullable
    204             @Override
    205             public View onFocusSearchFailed(View focused, int direction,
    206                     RecyclerView.Recycler recycler,
    207                     RecyclerView.State state) {
    208                 assertEquals(View.FOCUS_FORWARD, direction);
    209                 assertEquals(1, getChildCount());
    210                 View child0 = getChildAt(0);
    211                 View view = recycler.getViewForPosition(1);
    212                 addView(view);
    213                 measureChild(view, 0, 0);
    214                 layoutDecorated(view, 0, child0.getBottom(), getDecoratedMeasuredWidth(view),
    215                         child0.getBottom() + getDecoratedMeasuredHeight(view));
    216                 return view;
    217             }
    218         };
    219         tlm.setAutoMeasureEnabled(true);
    220         rv.setLayoutManager(tlm);
    221         TextView viewAbove = new TextView(getActivity());
    222         viewAbove.setText("view above");
    223         viewAbove.setFocusableInTouchMode(true);
    224         container.addView(viewAbove);
    225         container.addView(rv);
    226         TextView viewBelow = new TextView(getActivity());
    227         viewBelow.setText("view below");
    228         viewBelow.setFocusableInTouchMode(true);
    229         container.addView(viewBelow);
    230         tlm.expectLayouts(1);
    231         runTestOnUiThread(new Runnable() {
    232             @Override
    233             public void run() {
    234                 getActivity().getContainer().addView(container);
    235             }
    236         });
    237 
    238         tlm.waitForLayout(2);
    239         requestFocus(viewAbove, true);
    240         assertTrue(viewAbove.hasFocus());
    241         View newFocused = focusSearch(viewAbove, View.FOCUS_FORWARD);
    242         assertThat(newFocused, sameInstance(rv.getChildAt(0)));
    243         newFocused = focusSearch(rv.getChildAt(0), View.FOCUS_FORWARD);
    244         assertThat(newFocused, sameInstance(rv.getChildAt(1)));
    245     }
    246 
    247     @Test
    248     public void boundingBoxNoTranslation() throws Throwable {
    249         transformedBoundingBoxTest(new ViewRunnable() {
    250             @Override
    251             public void run(View view) throws RuntimeException {
    252                 view.layout(10, 10, 30, 50);
    253                 assertThat(getTransformedBoundingBox(view), is(new Rect(10, 10, 30, 50)));
    254             }
    255         });
    256     }
    257 
    258     @Test
    259     public void boundingBoxTranslateX() throws Throwable {
    260         transformedBoundingBoxTest(new ViewRunnable() {
    261             @Override
    262             public void run(View view) throws RuntimeException {
    263                 view.layout(10, 10, 30, 50);
    264                 ViewCompat.setTranslationX(view, 10);
    265                 assertThat(getTransformedBoundingBox(view), is(new Rect(20, 10, 40, 50)));
    266             }
    267         });
    268     }
    269 
    270     @Test
    271     public void boundingBoxTranslateY() throws Throwable {
    272         transformedBoundingBoxTest(new ViewRunnable() {
    273             @Override
    274             public void run(View view) throws RuntimeException {
    275                 view.layout(10, 10, 30, 50);
    276                 ViewCompat.setTranslationY(view, 10);
    277                 assertThat(getTransformedBoundingBox(view), is(new Rect(10, 20, 30, 60)));
    278             }
    279         });
    280     }
    281 
    282     @Test
    283     public void boundingBoxScaleX() throws Throwable {
    284         transformedBoundingBoxTest(new ViewRunnable() {
    285             @Override
    286             public void run(View view) throws RuntimeException {
    287                 view.layout(10, 10, 30, 50);
    288                 ViewCompat.setScaleX(view, 2);
    289                 assertThat(getTransformedBoundingBox(view), is(new Rect(0, 10, 40, 50)));
    290             }
    291         });
    292     }
    293 
    294     @Test
    295     public void boundingBoxScaleY() throws Throwable {
    296         transformedBoundingBoxTest(new ViewRunnable() {
    297             @Override
    298             public void run(View view) throws RuntimeException {
    299                 view.layout(10, 10, 30, 50);
    300                 ViewCompat.setScaleY(view, 2);
    301                 assertThat(getTransformedBoundingBox(view), is(new Rect(10, -10, 30, 70)));
    302             }
    303         });
    304     }
    305 
    306     @Test
    307     public void boundingBoxRotated() throws Throwable {
    308         transformedBoundingBoxTest(new ViewRunnable() {
    309             @Override
    310             public void run(View view) throws RuntimeException {
    311                 view.layout(10, 10, 30, 50);
    312                 ViewCompat.setRotation(view, 90);
    313                 assertThat(getTransformedBoundingBox(view), is(new Rect(0, 20, 40, 40)));
    314             }
    315         });
    316     }
    317 
    318     @Test
    319     public void boundingBoxRotatedWithDecorOffsets() throws Throwable {
    320         final RecyclerView recyclerView = new RecyclerView(getActivity());
    321         final TestAdapter adapter = new TestAdapter(1);
    322         recyclerView.setAdapter(adapter);
    323         recyclerView.addItemDecoration(new RecyclerView.ItemDecoration() {
    324             @Override
    325             public void getItemOffsets(Rect outRect, View view, RecyclerView parent,
    326                     RecyclerView.State state) {
    327                 outRect.set(1, 2, 3, 4);
    328             }
    329         });
    330         TestLayoutManager layoutManager = new TestLayoutManager() {
    331             @Override
    332             public void onLayoutChildren(RecyclerView.Recycler recycler, RecyclerView.State state) {
    333                 detachAndScrapAttachedViews(recycler);
    334                 View view = recycler.getViewForPosition(0);
    335                 addView(view);
    336                 view.measure(
    337                         View.MeasureSpec.makeMeasureSpec(20, View.MeasureSpec.EXACTLY),
    338                         View.MeasureSpec.makeMeasureSpec(40, View.MeasureSpec.EXACTLY)
    339                 );
    340                 // trigger decor offsets calculation
    341                 calculateItemDecorationsForChild(view, new Rect());
    342                 view.layout(10, 10, 30, 50);
    343                 ViewCompat.setRotation(view, 90);
    344                 assertThat(RecyclerViewLayoutTest.this.getTransformedBoundingBox(view),
    345                         is(new Rect(-4, 19, 42, 43)));
    346 
    347                 layoutLatch.countDown();
    348             }
    349         };
    350         recyclerView.setLayoutManager(layoutManager);
    351         layoutManager.expectLayouts(1);
    352         setRecyclerView(recyclerView);
    353         layoutManager.waitForLayout(2);
    354         checkForMainThreadException();
    355     }
    356 
    357     private Rect getTransformedBoundingBox(View child) {
    358         Rect rect = new Rect();
    359         mRecyclerView.getLayoutManager().getTransformedBoundingBox(child, true, rect);
    360         return rect;
    361     }
    362 
    363     public void transformedBoundingBoxTest(final ViewRunnable layout) throws Throwable {
    364         final RecyclerView recyclerView = new RecyclerView(getActivity());
    365         final TestAdapter adapter = new TestAdapter(1);
    366         recyclerView.setAdapter(adapter);
    367         TestLayoutManager layoutManager = new TestLayoutManager() {
    368             @Override
    369             public void onLayoutChildren(RecyclerView.Recycler recycler, RecyclerView.State state) {
    370                 detachAndScrapAttachedViews(recycler);
    371                 View view = recycler.getViewForPosition(0);
    372                 addView(view);
    373                 view.measure(
    374                         View.MeasureSpec.makeMeasureSpec(20, View.MeasureSpec.EXACTLY),
    375                         View.MeasureSpec.makeMeasureSpec(40, View.MeasureSpec.EXACTLY)
    376                 );
    377                 layout.run(view);
    378                 layoutLatch.countDown();
    379             }
    380         };
    381         recyclerView.setLayoutManager(layoutManager);
    382         layoutManager.expectLayouts(1);
    383         setRecyclerView(recyclerView);
    384         layoutManager.waitForLayout(2);
    385         checkForMainThreadException();
    386     }
    387 
    388     @Test
    389     public void flingFrozen() throws Throwable {
    390         testScrollFrozen(true);
    391     }
    392 
    393     @Test
    394     public void dragFrozen() throws Throwable {
    395         testScrollFrozen(false);
    396     }
    397 
    398     @Test
    399     public void requestRectOnScreenWithScrollOffset() throws Throwable {
    400         final RecyclerView recyclerView = new RecyclerView(getActivity());
    401         final LayoutAllLayoutManager tlm = spy(new LayoutAllLayoutManager());
    402         final int scrollY = 50;
    403         RecyclerView.Adapter adapter = new RecyclerView.Adapter() {
    404             @Override
    405             public RecyclerView.ViewHolder onCreateViewHolder(ViewGroup parent, int viewType) {
    406                 View view = new View(parent.getContext());
    407                 view.setScrollY(scrollY);
    408                 return new RecyclerView.ViewHolder(view) {
    409                 };
    410             }
    411             @Override
    412             public void onBindViewHolder(RecyclerView.ViewHolder holder, int position) {}
    413             @Override
    414             public int getItemCount() {
    415                 return 1;
    416             }
    417         };
    418         recyclerView.setAdapter(adapter);
    419         recyclerView.setLayoutManager(tlm);
    420         tlm.expectLayouts(1);
    421         setRecyclerView(recyclerView);
    422         tlm.waitForLayout(1);
    423         final View child = recyclerView.getChildAt(0);
    424         assertThat(child.getScrollY(), CoreMatchers.is(scrollY));
    425         runTestOnUiThread(new Runnable() {
    426             @Override
    427             public void run() {
    428                 recyclerView.requestChildRectangleOnScreen(child, new Rect(3, 4, 5, 6), true);
    429                 verify(tlm, times(1)).scrollVerticallyBy(eq(-46), any(RecyclerView.Recycler.class),
    430                         any(RecyclerView.State.class));
    431             }
    432         });
    433     }
    434 
    435     @Test
    436     public void reattachAndScrollCrash() throws Throwable {
    437         final RecyclerView recyclerView = new RecyclerView(getActivity());
    438         final TestLayoutManager tlm = new TestLayoutManager() {
    439 
    440             @Override
    441             public boolean canScrollVertically() {
    442                 return true;
    443             }
    444 
    445             @Override
    446             public void onLayoutChildren(RecyclerView.Recycler recycler, RecyclerView.State state) {
    447                 layoutRange(recycler, 0, Math.min(state.getItemCount(), 10));
    448             }
    449 
    450             @Override
    451             public int scrollVerticallyBy(int dy, RecyclerView.Recycler recycler,
    452                                           RecyclerView.State state) {
    453                 // Access views in the state (that might have been deleted).
    454                 for (int  i = 10; i < state.getItemCount(); i++) {
    455                     recycler.getViewForPosition(i);
    456                 }
    457                 return dy;
    458             }
    459         };
    460 
    461         final TestAdapter adapter = new TestAdapter(12);
    462 
    463         recyclerView.setAdapter(adapter);
    464         recyclerView.setLayoutManager(tlm);
    465 
    466         setRecyclerView(recyclerView);
    467 
    468         runTestOnUiThread(new Runnable() {
    469             @Override
    470             public void run() {
    471                 getActivity().getContainer().removeView(recyclerView);
    472                 getActivity().getContainer().addView(recyclerView);
    473                 try {
    474                     adapter.deleteAndNotify(1, adapter.getItemCount() - 1);
    475                 } catch (Throwable throwable) {
    476                     postExceptionToInstrumentation(throwable);
    477                 }
    478                 recyclerView.scrollBy(0, 10);
    479             }
    480         });
    481     }
    482 
    483     private void testScrollFrozen(boolean fling) throws Throwable {
    484         RecyclerView recyclerView = new RecyclerView(getActivity());
    485 
    486         final int horizontalScrollCount = 3;
    487         final int verticalScrollCount = 3;
    488         final int horizontalVelocity = 1000;
    489         final int verticalVelocity = 1000;
    490         final AtomicInteger horizontalCounter = new AtomicInteger(horizontalScrollCount);
    491         final AtomicInteger verticalCounter = new AtomicInteger(verticalScrollCount);
    492         TestLayoutManager tlm = new TestLayoutManager() {
    493             @Override
    494             public boolean canScrollHorizontally() {
    495                 return true;
    496             }
    497 
    498             @Override
    499             public boolean canScrollVertically() {
    500                 return true;
    501             }
    502 
    503             @Override
    504             public void onLayoutChildren(RecyclerView.Recycler recycler, RecyclerView.State state) {
    505                 layoutRange(recycler, 0, 10);
    506                 layoutLatch.countDown();
    507             }
    508 
    509             @Override
    510             public int scrollVerticallyBy(int dy, RecyclerView.Recycler recycler,
    511                     RecyclerView.State state) {
    512                 if (verticalCounter.get() > 0) {
    513                     verticalCounter.decrementAndGet();
    514                     return dy;
    515                 }
    516                 return 0;
    517             }
    518 
    519             @Override
    520             public int scrollHorizontallyBy(int dx, RecyclerView.Recycler recycler,
    521                     RecyclerView.State state) {
    522                 if (horizontalCounter.get() > 0) {
    523                     horizontalCounter.decrementAndGet();
    524                     return dx;
    525                 }
    526                 return 0;
    527             }
    528         };
    529         TestAdapter adapter = new TestAdapter(100);
    530         recyclerView.setAdapter(adapter);
    531         recyclerView.setLayoutManager(tlm);
    532         tlm.expectLayouts(1);
    533         setRecyclerView(recyclerView);
    534         tlm.waitForLayout(2);
    535 
    536         freezeLayout(true);
    537 
    538         if (fling) {
    539             assertFalse("fling should be blocked", fling(horizontalVelocity, verticalVelocity));
    540         } else { // drag
    541             TouchUtils.dragViewTo(getInstrumentation(), recyclerView,
    542                     Gravity.LEFT | Gravity.TOP,
    543                     mRecyclerView.getWidth() / 2, mRecyclerView.getHeight() / 2);
    544         }
    545         assertEquals("rv's horizontal scroll cb must not run", horizontalScrollCount,
    546                 horizontalCounter.get());
    547         assertEquals("rv's vertical scroll cb must not run", verticalScrollCount,
    548                 verticalCounter.get());
    549 
    550         freezeLayout(false);
    551 
    552         if (fling) {
    553             assertTrue("fling should be started", fling(horizontalVelocity, verticalVelocity));
    554         } else { // drag
    555             TouchUtils.dragViewTo(getInstrumentation(), recyclerView,
    556                     Gravity.LEFT | Gravity.TOP,
    557                     mRecyclerView.getWidth() / 2, mRecyclerView.getHeight() / 2);
    558         }
    559         assertEquals("rv's horizontal scroll cb must finishes", 0, horizontalCounter.get());
    560         assertEquals("rv's vertical scroll cb must finishes", 0, verticalCounter.get());
    561     }
    562 
    563     @Test
    564     public void testFocusSearchAfterChangedData() throws Throwable {
    565         final RecyclerView recyclerView = new RecyclerView(getActivity());
    566         TestLayoutManager tlm = new TestLayoutManager() {
    567             @Override
    568             public void onLayoutChildren(RecyclerView.Recycler recycler, RecyclerView.State state) {
    569                 layoutRange(recycler, 0, 2);
    570                 layoutLatch.countDown();
    571             }
    572 
    573             @Nullable
    574             @Override
    575             public View onFocusSearchFailed(View focused, int direction,
    576                                             RecyclerView.Recycler recycler,
    577                                             RecyclerView.State state) {
    578                 try {
    579                     View view = recycler.getViewForPosition(state.getItemCount() - 1);
    580                 } catch (Throwable t) {
    581                     postExceptionToInstrumentation(t);
    582                 }
    583                 return null;
    584             }
    585         };
    586         recyclerView.setLayoutManager(tlm);
    587         final TestAdapter adapter = new TestAdapter(10) {
    588             @Override
    589             public void onBindViewHolder(TestViewHolder holder, int position) {
    590                 super.onBindViewHolder(holder, position);
    591                 holder.itemView.setFocusable(false);
    592                 holder.itemView.setFocusableInTouchMode(false);
    593             }
    594         };
    595         recyclerView.setAdapter(adapter);
    596         tlm.expectLayouts(1);
    597         setRecyclerView(recyclerView);
    598         tlm.waitForLayout(1);
    599         runTestOnUiThread(new Runnable() {
    600             @Override
    601             public void run() {
    602                 adapter.mItems.remove(9);
    603                 adapter.notifyItemRemoved(9);
    604                 recyclerView.focusSearch(recyclerView.getChildAt(1), View.FOCUS_DOWN);
    605             }
    606         });
    607         checkForMainThreadException();
    608     }
    609 
    610     @Test
    611     public void testFocusSearchWithRemovedFocusedItem() throws Throwable {
    612         final RecyclerView recyclerView = new RecyclerView(getActivity());
    613         recyclerView.setItemAnimator(null);
    614         TestLayoutManager tlm = new LayoutAllLayoutManager();
    615         recyclerView.setLayoutManager(tlm);
    616         final TestAdapter adapter = new TestAdapter(10) {
    617             @Override
    618             public void onBindViewHolder(TestViewHolder holder, int position) {
    619                 super.onBindViewHolder(holder, position);
    620                 holder.itemView.setFocusable(true);
    621                 holder.itemView.setFocusableInTouchMode(true);
    622             }
    623         };
    624         recyclerView.setAdapter(adapter);
    625         tlm.expectLayouts(1);
    626         setRecyclerView(recyclerView);
    627         tlm.waitForLayout(1);
    628         final RecyclerView.ViewHolder toFocus = recyclerView.findViewHolderForAdapterPosition(9);
    629         requestFocus(toFocus.itemView, true);
    630         assertThat("test sanity", toFocus.itemView.hasFocus(), is(true));
    631         runTestOnUiThread(new Runnable() {
    632             @Override
    633             public void run() {
    634                 adapter.mItems.remove(9);
    635                 adapter.notifyItemRemoved(9);
    636                 recyclerView.focusSearch(toFocus.itemView, View.FOCUS_DOWN);
    637             }
    638         });
    639         checkForMainThreadException();
    640     }
    641 
    642 
    643     @Test
    644     public void  testFocusSearchFailFrozen() throws Throwable {
    645         RecyclerView recyclerView = new RecyclerView(getActivity());
    646         final CountDownLatch focusLatch = new CountDownLatch(1);
    647         final AtomicInteger focusSearchCalled = new AtomicInteger(0);
    648         TestLayoutManager tlm = new TestLayoutManager() {
    649             @Override
    650             public boolean canScrollHorizontally() {
    651                 return true;
    652             }
    653 
    654             @Override
    655             public boolean canScrollVertically() {
    656                 return true;
    657             }
    658 
    659             @Override
    660             public void onLayoutChildren(RecyclerView.Recycler recycler, RecyclerView.State state) {
    661                 layoutRange(recycler, 0, 10);
    662                 layoutLatch.countDown();
    663             }
    664 
    665             @Override
    666             public View onFocusSearchFailed(View focused, int direction,
    667                     RecyclerView.Recycler recycler, RecyclerView.State state) {
    668                 focusSearchCalled.addAndGet(1);
    669                 focusLatch.countDown();
    670                 return null;
    671             }
    672         };
    673         TestAdapter adapter = new TestAdapter(100);
    674         recyclerView.setAdapter(adapter);
    675         recyclerView.setLayoutManager(tlm);
    676         tlm.expectLayouts(1);
    677         setRecyclerView(recyclerView);
    678         tlm.waitForLayout(2);
    679         final View c = recyclerView.getChildAt(recyclerView.getChildCount() - 1);
    680         assertTrue("test sanity", requestFocus(c, true));
    681         assertTrue("test sanity", c.hasFocus());
    682         freezeLayout(true);
    683         focusSearch(recyclerView, c, View.FOCUS_DOWN);
    684         assertEquals("onFocusSearchFailed should not be called when layout is frozen",
    685                 0, focusSearchCalled.get());
    686         freezeLayout(false);
    687         focusSearch(c, View.FOCUS_DOWN);
    688         assertTrue(focusLatch.await(2, TimeUnit.SECONDS));
    689         assertEquals(1, focusSearchCalled.get());
    690     }
    691 
    692     public View focusSearch(final ViewGroup parent, final View focused, final int direction)
    693             throws Throwable {
    694         final View[] result = new View[1];
    695         runTestOnUiThread(new Runnable() {
    696             @Override
    697             public void run() {
    698                 result[0] = parent.focusSearch(focused, direction);
    699             }
    700         });
    701         return result[0];
    702     }
    703 
    704     @Test
    705     public void frozenAndChangeAdapter() throws Throwable {
    706         RecyclerView recyclerView = new RecyclerView(getActivity());
    707 
    708         final AtomicInteger focusSearchCalled = new AtomicInteger(0);
    709         TestLayoutManager tlm = new TestLayoutManager() {
    710             @Override
    711             public boolean canScrollHorizontally() {
    712                 return true;
    713             }
    714 
    715             @Override
    716             public boolean canScrollVertically() {
    717                 return true;
    718             }
    719 
    720             @Override
    721             public void onLayoutChildren(RecyclerView.Recycler recycler, RecyclerView.State state) {
    722                 layoutRange(recycler, 0, 10);
    723                 layoutLatch.countDown();
    724             }
    725 
    726             @Override
    727             public View onFocusSearchFailed(View focused, int direction, RecyclerView.Recycler recycler,
    728                     RecyclerView.State state) {
    729                 focusSearchCalled.addAndGet(1);
    730                 return null;
    731             }
    732         };
    733         TestAdapter adapter = new TestAdapter(100);
    734         recyclerView.setAdapter(adapter);
    735         recyclerView.setLayoutManager(tlm);
    736         tlm.expectLayouts(1);
    737         setRecyclerView(recyclerView);
    738         tlm.waitForLayout(2);
    739 
    740         freezeLayout(true);
    741         TestAdapter adapter2 = new TestAdapter(1000);
    742         setAdapter(adapter2);
    743         assertFalse(recyclerView.isLayoutFrozen());
    744         assertSame(adapter2, recyclerView.getAdapter());
    745 
    746         freezeLayout(true);
    747         TestAdapter adapter3 = new TestAdapter(1000);
    748         swapAdapter(adapter3, true);
    749         assertFalse(recyclerView.isLayoutFrozen());
    750         assertSame(adapter3, recyclerView.getAdapter());
    751     }
    752 
    753     @Test
    754     public void noLayoutIf0ItemsAreChanged() throws Throwable {
    755         unnecessaryNotifyEvents(new AdapterRunnable() {
    756             @Override
    757             public void run(TestAdapter adapter) throws Throwable {
    758                 adapter.notifyItemRangeChanged(3, 0);
    759             }
    760         });
    761     }
    762 
    763     @Test
    764     public void noLayoutIf0ItemsAreChangedWithPayload() throws Throwable {
    765         unnecessaryNotifyEvents(new AdapterRunnable() {
    766             @Override
    767             public void run(TestAdapter adapter) throws Throwable {
    768                 adapter.notifyItemRangeChanged(0, 0, new Object());
    769             }
    770         });
    771     }
    772 
    773     @Test
    774     public void noLayoutIf0ItemsAreAdded() throws Throwable {
    775         unnecessaryNotifyEvents(new AdapterRunnable() {
    776             @Override
    777             public void run(TestAdapter adapter) throws Throwable {
    778                 adapter.notifyItemRangeInserted(3, 0);
    779             }
    780         });
    781     }
    782 
    783     @Test
    784     public void noLayoutIf0ItemsAreRemoved() throws Throwable {
    785         unnecessaryNotifyEvents(new AdapterRunnable() {
    786             @Override
    787             public void run(TestAdapter adapter) throws Throwable {
    788                 adapter.notifyItemRangeRemoved(3, 0);
    789             }
    790         });
    791     }
    792 
    793     @Test
    794     public void noLayoutIfItemMovedIntoItsOwnPlace() throws Throwable {
    795         unnecessaryNotifyEvents(new AdapterRunnable() {
    796             @Override
    797             public void run(TestAdapter adapter) throws Throwable {
    798                 adapter.notifyItemMoved(3, 3);
    799             }
    800         });
    801     }
    802 
    803     public void unnecessaryNotifyEvents(final AdapterRunnable action) throws Throwable {
    804         final RecyclerView recyclerView = new RecyclerView(getActivity());
    805         final TestAdapter adapter = new TestAdapter(5);
    806         TestLayoutManager tlm = new TestLayoutManager() {
    807             @Override
    808             public void onLayoutChildren(RecyclerView.Recycler recycler, RecyclerView.State state) {
    809                 super.onLayoutChildren(recycler, state);
    810                 layoutLatch.countDown();
    811             }
    812         };
    813         recyclerView.setLayoutManager(tlm);
    814         recyclerView.setAdapter(adapter);
    815         tlm.expectLayouts(1);
    816         setRecyclerView(recyclerView);
    817         tlm.waitForLayout(1);
    818         // ready
    819         tlm.expectLayouts(1);
    820         runTestOnUiThread(new Runnable() {
    821             @Override
    822             public void run() {
    823                 try {
    824                     action.run(adapter);
    825                 } catch (Throwable throwable) {
    826                     postExceptionToInstrumentation(throwable);
    827                 }
    828             }
    829         });
    830         tlm.assertNoLayout("dummy event should not trigger a layout", 1);
    831         checkForMainThreadException();
    832     }
    833 
    834     @Test
    835     public void scrollToPositionCallback() throws Throwable {
    836         RecyclerView recyclerView = new RecyclerView(getActivity());
    837         TestLayoutManager tlm = new TestLayoutManager() {
    838             int scrollPos = RecyclerView.NO_POSITION;
    839 
    840             @Override
    841             public void onLayoutChildren(RecyclerView.Recycler recycler, RecyclerView.State state) {
    842                 layoutLatch.countDown();
    843                 if (scrollPos == RecyclerView.NO_POSITION) {
    844                     layoutRange(recycler, 0, 10);
    845                 } else {
    846                     layoutRange(recycler, scrollPos, scrollPos + 10);
    847                 }
    848             }
    849 
    850             @Override
    851             public void scrollToPosition(int position) {
    852                 scrollPos = position;
    853                 requestLayout();
    854             }
    855         };
    856         recyclerView.setLayoutManager(tlm);
    857         TestAdapter adapter = new TestAdapter(100);
    858         recyclerView.setAdapter(adapter);
    859         final AtomicInteger rvCounter = new AtomicInteger(0);
    860         final AtomicInteger viewGroupCounter = new AtomicInteger(0);
    861         recyclerView.getViewTreeObserver().addOnScrollChangedListener(
    862                 new ViewTreeObserver.OnScrollChangedListener() {
    863                     @Override
    864                     public void onScrollChanged() {
    865                         viewGroupCounter.incrementAndGet();
    866                     }
    867                 });
    868         recyclerView.setOnScrollListener(new RecyclerView.OnScrollListener() {
    869             @Override
    870             public void onScrolled(RecyclerView recyclerView, int dx, int dy) {
    871                 rvCounter.incrementAndGet();
    872                 super.onScrolled(recyclerView, dx, dy);
    873             }
    874         });
    875         tlm.expectLayouts(1);
    876 
    877         setRecyclerView(recyclerView);
    878         tlm.waitForLayout(2);
    879         // wait for draw :/
    880         Thread.sleep(1000);
    881 
    882         assertEquals("RV on scroll should be called for initialization", 1, rvCounter.get());
    883         assertEquals("VTO on scroll should be called for initialization", 1,
    884                 viewGroupCounter.get());
    885         tlm.expectLayouts(1);
    886         freezeLayout(true);
    887         scrollToPosition(3);
    888         tlm.assertNoLayout("scrollToPosition should be ignored", 2);
    889         freezeLayout(false);
    890         scrollToPosition(3);
    891         tlm.waitForLayout(2);
    892         assertEquals("RV on scroll should be called", 2, rvCounter.get());
    893         assertEquals("VTO on scroll should be called", 2, viewGroupCounter.get());
    894         tlm.expectLayouts(1);
    895         requestLayoutOnUIThread(recyclerView);
    896         tlm.waitForLayout(2);
    897         // wait for draw :/
    898         Thread.sleep(1000);
    899         assertEquals("on scroll should NOT be called", 2, rvCounter.get());
    900         assertEquals("on scroll should NOT be called", 2, viewGroupCounter.get());
    901     }
    902 
    903     @Test
    904     public void scrollCalllbackOnVisibleRangeExpand() throws Throwable {
    905         scrollCallbackOnVisibleRangeChange(10, new int[]{3, 5}, new int[]{3, 6});
    906     }
    907 
    908     @Test
    909     public void scrollCalllbackOnVisibleRangeShrink() throws Throwable {
    910         scrollCallbackOnVisibleRangeChange(10, new int[]{3, 6}, new int[]{3, 5});
    911     }
    912 
    913     @Test
    914     public void scrollCalllbackOnVisibleRangeExpand2() throws Throwable {
    915         scrollCallbackOnVisibleRangeChange(10, new int[]{3, 5}, new int[]{2, 5});
    916     }
    917 
    918     @Test
    919     public void scrollCalllbackOnVisibleRangeShrink2() throws Throwable {
    920         scrollCallbackOnVisibleRangeChange(10, new int[]{3, 6}, new int[]{2, 6});
    921     }
    922 
    923     private void scrollCallbackOnVisibleRangeChange(int itemCount, final int[] beforeRange,
    924             final int[] afterRange) throws Throwable {
    925         RecyclerView recyclerView = new RecyclerView(getActivity());
    926         final AtomicBoolean beforeState = new AtomicBoolean(true);
    927         TestLayoutManager tlm = new TestLayoutManager() {
    928             @Override
    929             public void onLayoutChildren(RecyclerView.Recycler recycler, RecyclerView.State state) {
    930                 detachAndScrapAttachedViews(recycler);
    931                 int[] range = beforeState.get() ? beforeRange : afterRange;
    932                 layoutRange(recycler, range[0], range[1]);
    933                 layoutLatch.countDown();
    934             }
    935         };
    936         recyclerView.setLayoutManager(tlm);
    937         final TestAdapter adapter = new TestAdapter(itemCount);
    938         recyclerView.setAdapter(adapter);
    939         tlm.expectLayouts(1);
    940         setRecyclerView(recyclerView);
    941         tlm.waitForLayout(1);
    942 
    943         RecyclerView.OnScrollListener mockListener = mock(RecyclerView.OnScrollListener.class);
    944         recyclerView.addOnScrollListener(mockListener);
    945         verify(mockListener, never()).onScrolled(any(RecyclerView.class), anyInt(), anyInt());
    946 
    947         tlm.expectLayouts(1);
    948         beforeState.set(false);
    949         requestLayoutOnUIThread(recyclerView);
    950         tlm.waitForLayout(2);
    951         checkForMainThreadException();
    952         verify(mockListener).onScrolled(recyclerView, 0, 0);
    953     }
    954 
    955     @Test
    956     public void addItemOnScroll() throws Throwable {
    957         RecyclerView recyclerView = new RecyclerView(getActivity());
    958         final AtomicInteger start = new AtomicInteger(0);
    959         TestLayoutManager tlm = new TestLayoutManager() {
    960             @Override
    961             public void onLayoutChildren(RecyclerView.Recycler recycler, RecyclerView.State state) {
    962                 layoutRange(recycler, start.get(), start.get() + 10);
    963                 layoutLatch.countDown();
    964             }
    965         };
    966         recyclerView.setLayoutManager(tlm);
    967         final TestAdapter adapter = new TestAdapter(100);
    968         recyclerView.setAdapter(adapter);
    969         tlm.expectLayouts(1);
    970         setRecyclerView(recyclerView);
    971         tlm.waitForLayout(1);
    972         final Throwable[] error = new Throwable[1];
    973         final AtomicBoolean calledOnScroll = new AtomicBoolean(false);
    974         recyclerView.addOnScrollListener(new RecyclerView.OnScrollListener() {
    975             @Override
    976             public void onScrolled(RecyclerView recyclerView, int dx, int dy) {
    977                 super.onScrolled(recyclerView, dx, dy);
    978                 calledOnScroll.set(true);
    979                 try {
    980                     adapter.addAndNotify(5, 20);
    981                 } catch (Throwable throwable) {
    982                     error[0] = throwable;
    983                 }
    984             }
    985         });
    986         start.set(4);
    987         MatcherAssert.assertThat("test sanity", calledOnScroll.get(), CoreMatchers.is(false));
    988         tlm.expectLayouts(1);
    989         requestLayoutOnUIThread(recyclerView);
    990         tlm.waitForLayout(2);
    991         checkForMainThreadException();
    992         MatcherAssert.assertThat("test sanity", calledOnScroll.get(), CoreMatchers.is(true));
    993         MatcherAssert.assertThat(error[0], CoreMatchers.nullValue());
    994     }
    995 
    996     @Test
    997     public void scrollInBothDirectionEqual() throws Throwable {
    998         scrollInBothDirection(3, 3, 1000, 1000);
    999     }
   1000 
   1001     @Test
   1002     public void scrollInBothDirectionMoreVertical() throws Throwable {
   1003         scrollInBothDirection(2, 3, 1000, 1000);
   1004     }
   1005 
   1006     @Test
   1007     public void scrollInBothDirectionMoreHorizontal() throws Throwable {
   1008         scrollInBothDirection(3, 2, 1000, 1000);
   1009     }
   1010 
   1011     @Test
   1012     public void scrollHorizontalOnly() throws Throwable {
   1013         scrollInBothDirection(3, 0, 1000, 0);
   1014     }
   1015 
   1016     @Test
   1017     public void scrollVerticalOnly() throws Throwable {
   1018         scrollInBothDirection(0, 3, 0, 1000);
   1019     }
   1020 
   1021     @Test
   1022     public void scrollInBothDirectionEqualReverse() throws Throwable {
   1023         scrollInBothDirection(3, 3, -1000, -1000);
   1024     }
   1025 
   1026     @Test
   1027     public void scrollInBothDirectionMoreVerticalReverse() throws Throwable {
   1028         scrollInBothDirection(2, 3, -1000, -1000);
   1029     }
   1030 
   1031     @Test
   1032     public void scrollInBothDirectionMoreHorizontalReverse() throws Throwable {
   1033         scrollInBothDirection(3, 2, -1000, -1000);
   1034     }
   1035 
   1036     @Test
   1037     public void scrollHorizontalOnlyReverse() throws Throwable {
   1038         scrollInBothDirection(3, 0, -1000, 0);
   1039     }
   1040 
   1041     @Test
   1042     public void scrollVerticalOnlyReverse() throws Throwable {
   1043         scrollInBothDirection(0, 3, 0, -1000);
   1044     }
   1045 
   1046     public void scrollInBothDirection(int horizontalScrollCount, int verticalScrollCount,
   1047             int horizontalVelocity, int verticalVelocity)
   1048             throws Throwable {
   1049         RecyclerView recyclerView = new RecyclerView(getActivity());
   1050         final AtomicInteger horizontalCounter = new AtomicInteger(horizontalScrollCount);
   1051         final AtomicInteger verticalCounter = new AtomicInteger(verticalScrollCount);
   1052         TestLayoutManager tlm = new TestLayoutManager() {
   1053             @Override
   1054             public boolean canScrollHorizontally() {
   1055                 return true;
   1056             }
   1057 
   1058             @Override
   1059             public boolean canScrollVertically() {
   1060                 return true;
   1061             }
   1062 
   1063             @Override
   1064             public void onLayoutChildren(RecyclerView.Recycler recycler, RecyclerView.State state) {
   1065                 layoutRange(recycler, 0, 10);
   1066                 layoutLatch.countDown();
   1067             }
   1068 
   1069             @Override
   1070             public int scrollVerticallyBy(int dy, RecyclerView.Recycler recycler,
   1071                     RecyclerView.State state) {
   1072                 if (verticalCounter.get() > 0) {
   1073                     verticalCounter.decrementAndGet();
   1074                     return dy;
   1075                 }
   1076                 return 0;
   1077             }
   1078 
   1079             @Override
   1080             public int scrollHorizontallyBy(int dx, RecyclerView.Recycler recycler,
   1081                     RecyclerView.State state) {
   1082                 if (horizontalCounter.get() > 0) {
   1083                     horizontalCounter.decrementAndGet();
   1084                     return dx;
   1085                 }
   1086                 return 0;
   1087             }
   1088         };
   1089         TestAdapter adapter = new TestAdapter(100);
   1090         recyclerView.setAdapter(adapter);
   1091         recyclerView.setLayoutManager(tlm);
   1092         tlm.expectLayouts(1);
   1093         setRecyclerView(recyclerView);
   1094         tlm.waitForLayout(2);
   1095         assertTrue("test sanity, fling must run", fling(horizontalVelocity, verticalVelocity));
   1096         assertEquals("rv's horizontal scroll cb must run " + horizontalScrollCount + " times'", 0,
   1097                 horizontalCounter.get());
   1098         assertEquals("rv's vertical scroll cb must run " + verticalScrollCount + " times'", 0,
   1099                 verticalCounter.get());
   1100     }
   1101 
   1102     @Test
   1103     public void dragHorizontal() throws Throwable {
   1104         scrollInOtherOrientationTest(FLAG_HORIZONTAL);
   1105     }
   1106 
   1107     @Test
   1108     public void dragVertical() throws Throwable {
   1109         scrollInOtherOrientationTest(FLAG_VERTICAL);
   1110     }
   1111 
   1112     @Test
   1113     public void flingHorizontal() throws Throwable {
   1114         scrollInOtherOrientationTest(FLAG_HORIZONTAL | FLAG_FLING);
   1115     }
   1116 
   1117     @Test
   1118     public void flingVertical() throws Throwable {
   1119         scrollInOtherOrientationTest(FLAG_VERTICAL | FLAG_FLING);
   1120     }
   1121 
   1122     @Test
   1123     public void nestedDragVertical() throws Throwable {
   1124         TestedFrameLayout tfl = getActivity().getContainer();
   1125         tfl.setNestedScrollMode(TestedFrameLayout.TEST_NESTED_SCROLL_MODE_CONSUME);
   1126         tfl.setNestedFlingMode(TestedFrameLayout.TEST_NESTED_SCROLL_MODE_CONSUME);
   1127         scrollInOtherOrientationTest(FLAG_VERTICAL, 0);
   1128     }
   1129 
   1130     @Test
   1131     public void nestedDragHorizontal() throws Throwable {
   1132         TestedFrameLayout tfl = getActivity().getContainer();
   1133         tfl.setNestedScrollMode(TestedFrameLayout.TEST_NESTED_SCROLL_MODE_CONSUME);
   1134         tfl.setNestedFlingMode(TestedFrameLayout.TEST_NESTED_SCROLL_MODE_CONSUME);
   1135         scrollInOtherOrientationTest(FLAG_HORIZONTAL, 0);
   1136     }
   1137 
   1138     @Test
   1139     public void nestedDragHorizontalCallsStopNestedScroll() throws Throwable {
   1140         TestedFrameLayout tfl = getActivity().getContainer();
   1141         tfl.setNestedScrollMode(TestedFrameLayout.TEST_NESTED_SCROLL_MODE_CONSUME);
   1142         tfl.setNestedFlingMode(TestedFrameLayout.TEST_NESTED_SCROLL_MODE_CONSUME);
   1143         scrollInOtherOrientationTest(FLAG_HORIZONTAL, 0);
   1144         assertTrue("onStopNestedScroll called", tfl.stopNestedScrollCalled());
   1145     }
   1146 
   1147     @Test
   1148     public void nestedDragVerticalCallsStopNestedScroll() throws Throwable {
   1149         TestedFrameLayout tfl = getActivity().getContainer();
   1150         tfl.setNestedScrollMode(TestedFrameLayout.TEST_NESTED_SCROLL_MODE_CONSUME);
   1151         tfl.setNestedFlingMode(TestedFrameLayout.TEST_NESTED_SCROLL_MODE_CONSUME);
   1152         scrollInOtherOrientationTest(FLAG_VERTICAL, 0);
   1153         assertTrue("onStopNestedScroll called", tfl.stopNestedScrollCalled());
   1154     }
   1155 
   1156     private void scrollInOtherOrientationTest(int flags)
   1157             throws Throwable {
   1158         scrollInOtherOrientationTest(flags, flags);
   1159     }
   1160 
   1161     private void scrollInOtherOrientationTest(final int flags, int expectedFlags) throws Throwable {
   1162         RecyclerView recyclerView = new RecyclerView(getActivity());
   1163         final AtomicBoolean scrolledHorizontal = new AtomicBoolean(false);
   1164         final AtomicBoolean scrolledVertical = new AtomicBoolean(false);
   1165 
   1166         final TestLayoutManager tlm = new TestLayoutManager() {
   1167             @Override
   1168             public boolean canScrollHorizontally() {
   1169                 return (flags & FLAG_HORIZONTAL) != 0;
   1170             }
   1171 
   1172             @Override
   1173             public boolean canScrollVertically() {
   1174                 return (flags & FLAG_VERTICAL) != 0;
   1175             }
   1176 
   1177             @Override
   1178             public void onLayoutChildren(RecyclerView.Recycler recycler, RecyclerView.State state) {
   1179                 layoutRange(recycler, 0, 10);
   1180                 layoutLatch.countDown();
   1181             }
   1182 
   1183             @Override
   1184             public int scrollVerticallyBy(int dy, RecyclerView.Recycler recycler,
   1185                     RecyclerView.State state) {
   1186                 scrolledVertical.set(true);
   1187                 return super.scrollVerticallyBy(dy, recycler, state);
   1188             }
   1189 
   1190             @Override
   1191             public int scrollHorizontallyBy(int dx, RecyclerView.Recycler recycler,
   1192                     RecyclerView.State state) {
   1193                 scrolledHorizontal.set(true);
   1194                 return super.scrollHorizontallyBy(dx, recycler, state);
   1195             }
   1196         };
   1197         TestAdapter adapter = new TestAdapter(100);
   1198         recyclerView.setAdapter(adapter);
   1199         recyclerView.setLayoutManager(tlm);
   1200         tlm.expectLayouts(1);
   1201         setRecyclerView(recyclerView);
   1202         tlm.waitForLayout(2);
   1203         if ( (flags & FLAG_FLING) != 0 ) {
   1204             int flingVelocity = (mRecyclerView.getMaxFlingVelocity() +
   1205                     mRecyclerView.getMinFlingVelocity()) / 2;
   1206             assertEquals("fling started", (expectedFlags & FLAG_FLING) != 0,
   1207                     fling(flingVelocity, flingVelocity));
   1208         } else { // drag
   1209             TouchUtils.dragViewTo(getInstrumentation(), recyclerView, Gravity.LEFT | Gravity.TOP,
   1210                     mRecyclerView.getWidth() / 2, mRecyclerView.getHeight() / 2);
   1211         }
   1212         assertEquals("horizontally scrolled: " + tlm.mScrollHorizontallyAmount,
   1213                 (expectedFlags & FLAG_HORIZONTAL) != 0, scrolledHorizontal.get());
   1214         assertEquals("vertically scrolled: " + tlm.mScrollVerticallyAmount,
   1215                 (expectedFlags & FLAG_VERTICAL) != 0, scrolledVertical.get());
   1216     }
   1217 
   1218     private boolean fling(final int velocityX, final int velocityY) throws Throwable {
   1219         final AtomicBoolean didStart = new AtomicBoolean(false);
   1220         runTestOnUiThread(new Runnable() {
   1221             @Override
   1222             public void run() {
   1223                 boolean result = mRecyclerView.fling(velocityX, velocityY);
   1224                 didStart.set(result);
   1225             }
   1226         });
   1227         if (!didStart.get()) {
   1228             return false;
   1229         }
   1230         waitForIdleScroll(mRecyclerView);
   1231         return true;
   1232     }
   1233 
   1234     private void assertPendingUpdatesAndLayoutTest(final AdapterRunnable runnable) throws Throwable {
   1235         RecyclerView recyclerView = new RecyclerView(getActivity());
   1236         TestLayoutManager layoutManager = new DumbLayoutManager();
   1237         final TestAdapter testAdapter = new TestAdapter(10);
   1238         setupBasic(recyclerView, layoutManager, testAdapter, false);
   1239         layoutManager.expectLayouts(1);
   1240         runTestOnUiThread(new Runnable() {
   1241             @Override
   1242             public void run() {
   1243                 try {
   1244                     runnable.run(testAdapter);
   1245                 } catch (Throwable throwable) {
   1246                     fail("runnable has thrown an exception");
   1247                 }
   1248                 assertTrue(mRecyclerView.hasPendingAdapterUpdates());
   1249             }
   1250         });
   1251         layoutManager.waitForLayout(1);
   1252         assertFalse(mRecyclerView.hasPendingAdapterUpdates());
   1253         checkForMainThreadException();
   1254     }
   1255 
   1256     private void setupBasic(RecyclerView recyclerView, TestLayoutManager tlm,
   1257             TestAdapter adapter, boolean waitForFirstLayout) throws Throwable {
   1258         recyclerView.setLayoutManager(tlm);
   1259         recyclerView.setAdapter(adapter);
   1260         if (waitForFirstLayout) {
   1261             tlm.expectLayouts(1);
   1262             setRecyclerView(recyclerView);
   1263             tlm.waitForLayout(1);
   1264         } else {
   1265             setRecyclerView(recyclerView);
   1266         }
   1267     }
   1268 
   1269     @Test
   1270     public void hasPendingUpdatesBeforeFirstLayout() throws Throwable {
   1271         RecyclerView recyclerView = new RecyclerView(getActivity());
   1272         TestLayoutManager layoutManager = new DumbLayoutManager();
   1273         TestAdapter testAdapter = new TestAdapter(10);
   1274         setupBasic(recyclerView, layoutManager, testAdapter, false);
   1275         assertTrue(mRecyclerView.hasPendingAdapterUpdates());
   1276     }
   1277 
   1278     @Test
   1279     public void noPendingUpdatesAfterLayout() throws Throwable {
   1280         RecyclerView recyclerView = new RecyclerView(getActivity());
   1281         TestLayoutManager layoutManager = new DumbLayoutManager();
   1282         TestAdapter testAdapter = new TestAdapter(10);
   1283         setupBasic(recyclerView, layoutManager, testAdapter, true);
   1284         assertFalse(mRecyclerView.hasPendingAdapterUpdates());
   1285     }
   1286 
   1287     @Test
   1288     public void hasPendingUpdatesAfterItemIsRemoved() throws Throwable {
   1289         assertPendingUpdatesAndLayoutTest(new AdapterRunnable() {
   1290             @Override
   1291             public void run(TestAdapter testAdapter) throws Throwable {
   1292                 testAdapter.deleteAndNotify(1, 1);
   1293             }
   1294         });
   1295     }
   1296     @Test
   1297     public void hasPendingUpdatesAfterItemIsInserted() throws Throwable {
   1298         assertPendingUpdatesAndLayoutTest(new AdapterRunnable() {
   1299             @Override
   1300             public void run(TestAdapter testAdapter) throws Throwable {
   1301                 testAdapter.addAndNotify(2, 1);
   1302             }
   1303         });
   1304     }
   1305     @Test
   1306     public void hasPendingUpdatesAfterItemIsMoved() throws Throwable {
   1307         assertPendingUpdatesAndLayoutTest(new AdapterRunnable() {
   1308             @Override
   1309             public void run(TestAdapter testAdapter) throws Throwable {
   1310                 testAdapter.moveItem(2, 3, true);
   1311             }
   1312         });
   1313     }
   1314     @Test
   1315     public void hasPendingUpdatesAfterItemIsChanged() throws Throwable {
   1316         assertPendingUpdatesAndLayoutTest(new AdapterRunnable() {
   1317             @Override
   1318             public void run(TestAdapter testAdapter) throws Throwable {
   1319                 testAdapter.changeAndNotify(2, 1);
   1320             }
   1321         });
   1322     }
   1323     @Test
   1324     public void hasPendingUpdatesAfterDataSetIsChanged() throws Throwable {
   1325         assertPendingUpdatesAndLayoutTest(new AdapterRunnable() {
   1326             @Override
   1327             public void run(TestAdapter testAdapter) {
   1328                 mRecyclerView.getAdapter().notifyDataSetChanged();
   1329             }
   1330         });
   1331     }
   1332 
   1333     @Test
   1334     public void transientStateRecycleViaAdapter() throws Throwable {
   1335         transientStateRecycleTest(true, false);
   1336     }
   1337 
   1338     @Test
   1339     public void transientStateRecycleViaTransientStateCleanup() throws Throwable {
   1340         transientStateRecycleTest(false, true);
   1341     }
   1342 
   1343     @Test
   1344     public void transientStateDontRecycle() throws Throwable {
   1345         transientStateRecycleTest(false, false);
   1346     }
   1347 
   1348     public void transientStateRecycleTest(final boolean succeed, final boolean unsetTransientState)
   1349             throws Throwable {
   1350         final List<View> failedToRecycle = new ArrayList<View>();
   1351         final List<View> recycled = new ArrayList<View>();
   1352         TestAdapter testAdapter = new TestAdapter(10) {
   1353             @Override
   1354             public boolean onFailedToRecycleView(
   1355                     TestViewHolder holder) {
   1356                 failedToRecycle.add(holder.itemView);
   1357                 if (unsetTransientState) {
   1358                     setHasTransientState(holder.itemView, false);
   1359                 }
   1360                 return succeed;
   1361             }
   1362 
   1363             @Override
   1364             public void onViewRecycled(TestViewHolder holder) {
   1365                 recycled.add(holder.itemView);
   1366                 super.onViewRecycled(holder);
   1367             }
   1368         };
   1369         TestLayoutManager tlm = new TestLayoutManager() {
   1370             @Override
   1371             public void onLayoutChildren(RecyclerView.Recycler recycler, RecyclerView.State state) {
   1372                 if (getChildCount() == 0) {
   1373                     detachAndScrapAttachedViews(recycler);
   1374                     layoutRange(recycler, 0, 5);
   1375                 } else {
   1376                     removeAndRecycleAllViews(recycler);
   1377                 }
   1378                 if (layoutLatch != null) {
   1379                     layoutLatch.countDown();
   1380                 }
   1381             }
   1382         };
   1383         RecyclerView recyclerView = new RecyclerView(getActivity());
   1384         recyclerView.setAdapter(testAdapter);
   1385         recyclerView.setLayoutManager(tlm);
   1386         recyclerView.setItemAnimator(null);
   1387         setRecyclerView(recyclerView);
   1388         getInstrumentation().waitForIdleSync();
   1389         // make sure we have enough views after this position so that we'll receive the on recycled
   1390         // callback
   1391         View view = recyclerView.getChildAt(3);//this has to be greater than def cache size.
   1392         setHasTransientState(view, true);
   1393         tlm.expectLayouts(1);
   1394         requestLayoutOnUIThread(recyclerView);
   1395         tlm.waitForLayout(2);
   1396 
   1397         assertTrue(failedToRecycle.contains(view));
   1398         assertEquals(succeed || unsetTransientState, recycled.contains(view));
   1399     }
   1400 
   1401     @Test
   1402     public void adapterPositionInvalidation() throws Throwable {
   1403         final RecyclerView recyclerView = new RecyclerView(getActivity());
   1404         final TestAdapter adapter = new TestAdapter(10);
   1405         final TestLayoutManager tlm = new TestLayoutManager() {
   1406             @Override
   1407             public void onLayoutChildren(RecyclerView.Recycler recycler, RecyclerView.State state) {
   1408                 layoutRange(recycler, 0, state.getItemCount());
   1409                 layoutLatch.countDown();
   1410             }
   1411         };
   1412         recyclerView.setAdapter(adapter);
   1413         recyclerView.setLayoutManager(tlm);
   1414         tlm.expectLayouts(1);
   1415         setRecyclerView(recyclerView);
   1416         tlm.waitForLayout(1);
   1417         runTestOnUiThread(new Runnable() {
   1418             @Override
   1419             public void run() {
   1420                 for (int i = 0; i < tlm.getChildCount(); i++) {
   1421                     assertNotSame("adapter positions should not be undefined",
   1422                             recyclerView.getChildAdapterPosition(tlm.getChildAt(i)),
   1423                             RecyclerView.NO_POSITION);
   1424                 }
   1425                 adapter.notifyDataSetChanged();
   1426                 for (int i = 0; i < tlm.getChildCount(); i++) {
   1427                     assertSame("adapter positions should be undefined",
   1428                             recyclerView.getChildAdapterPosition(tlm.getChildAt(i)),
   1429                             RecyclerView.NO_POSITION);
   1430                 }
   1431             }
   1432         });
   1433     }
   1434 
   1435     @Test
   1436     public void adapterPositionsBasic() throws Throwable {
   1437         adapterPositionsTest(null);
   1438     }
   1439 
   1440     @Test
   1441     public void adapterPositionsRemoveItems() throws Throwable {
   1442         adapterPositionsTest(new AdapterRunnable() {
   1443             @Override
   1444             public void run(TestAdapter adapter) throws Throwable {
   1445                 adapter.deleteAndNotify(3, 4);
   1446             }
   1447         });
   1448     }
   1449 
   1450     @Test
   1451     public void adapterPositionsRemoveItemsBefore() throws Throwable {
   1452         adapterPositionsTest(new AdapterRunnable() {
   1453             @Override
   1454             public void run(TestAdapter adapter) throws Throwable {
   1455                 adapter.deleteAndNotify(0, 1);
   1456             }
   1457         });
   1458     }
   1459 
   1460     @Test
   1461     public void adapterPositionsAddItemsBefore() throws Throwable {
   1462         adapterPositionsTest(new AdapterRunnable() {
   1463             @Override
   1464             public void run(TestAdapter adapter) throws Throwable {
   1465                 adapter.addAndNotify(0, 5);
   1466             }
   1467         });
   1468     }
   1469 
   1470     @Test
   1471     public void adapterPositionsAddItemsInside() throws Throwable {
   1472         adapterPositionsTest(new AdapterRunnable() {
   1473             @Override
   1474             public void run(TestAdapter adapter) throws Throwable {
   1475                 adapter.addAndNotify(3, 2);
   1476             }
   1477         });
   1478     }
   1479 
   1480     @Test
   1481     public void adapterPositionsMoveItems() throws Throwable {
   1482         adapterPositionsTest(new AdapterRunnable() {
   1483             @Override
   1484             public void run(TestAdapter adapter) throws Throwable {
   1485                 adapter.moveAndNotify(3, 5);
   1486             }
   1487         });
   1488     }
   1489 
   1490     @Test
   1491     public void adapterPositionsNotifyDataSetChanged() throws Throwable {
   1492         adapterPositionsTest(new AdapterRunnable() {
   1493             @Override
   1494             public void run(TestAdapter adapter) throws Throwable {
   1495                 adapter.mItems.clear();
   1496                 for (int i = 0; i < 20; i++) {
   1497                     adapter.mItems.add(new Item(i, "added item"));
   1498                 }
   1499                 adapter.notifyDataSetChanged();
   1500             }
   1501         });
   1502     }
   1503 
   1504     @Test
   1505     public void avoidLeakingRecyclerViewIfViewIsNotRecycled() throws Throwable {
   1506         final AtomicBoolean failedToRecycle = new AtomicBoolean(false);
   1507         RecyclerView rv = new RecyclerView(getActivity());
   1508         TestLayoutManager tlm = new TestLayoutManager() {
   1509             @Override
   1510             public void onLayoutChildren(RecyclerView.Recycler recycler, RecyclerView.State state) {
   1511                 detachAndScrapAttachedViews(recycler);
   1512                 layoutRange(recycler, 0, state.getItemCount());
   1513                 layoutLatch.countDown();
   1514             }
   1515         };
   1516         TestAdapter adapter = new TestAdapter(10) {
   1517             @Override
   1518             public boolean onFailedToRecycleView(
   1519                     TestViewHolder holder) {
   1520                 failedToRecycle.set(true);
   1521                 return false;
   1522             }
   1523         };
   1524         rv.setAdapter(adapter);
   1525         rv.setLayoutManager(tlm);
   1526         tlm.expectLayouts(1);
   1527         setRecyclerView(rv);
   1528         tlm.waitForLayout(1);
   1529         final RecyclerView.ViewHolder vh = rv.getChildViewHolder(rv.getChildAt(0));
   1530         runTestOnUiThread(new Runnable() {
   1531             @Override
   1532             public void run() {
   1533                 ViewCompat.setHasTransientState(vh.itemView, true);
   1534             }
   1535         });
   1536         tlm.expectLayouts(1);
   1537         adapter.deleteAndNotify(0, 10);
   1538         tlm.waitForLayout(2);
   1539         final CountDownLatch animationsLatch = new CountDownLatch(1);
   1540         rv.getItemAnimator().isRunning(
   1541                 new RecyclerView.ItemAnimator.ItemAnimatorFinishedListener() {
   1542                     @Override
   1543                     public void onAnimationsFinished() {
   1544                         animationsLatch.countDown();
   1545                     }
   1546                 });
   1547         assertTrue(animationsLatch.await(2, TimeUnit.SECONDS));
   1548         assertTrue(failedToRecycle.get());
   1549         assertNull(vh.mOwnerRecyclerView);
   1550         checkForMainThreadException();
   1551     }
   1552 
   1553     @Test
   1554     public void avoidLeakingRecyclerViewViaViewHolder() throws Throwable {
   1555         RecyclerView rv = new RecyclerView(getActivity());
   1556         TestLayoutManager tlm = new TestLayoutManager() {
   1557             @Override
   1558             public void onLayoutChildren(RecyclerView.Recycler recycler, RecyclerView.State state) {
   1559                 detachAndScrapAttachedViews(recycler);
   1560                 layoutRange(recycler, 0, state.getItemCount());
   1561                 layoutLatch.countDown();
   1562             }
   1563         };
   1564         TestAdapter adapter = new TestAdapter(10);
   1565         rv.setAdapter(adapter);
   1566         rv.setLayoutManager(tlm);
   1567         tlm.expectLayouts(1);
   1568         setRecyclerView(rv);
   1569         tlm.waitForLayout(1);
   1570         final RecyclerView.ViewHolder vh = rv.getChildViewHolder(rv.getChildAt(0));
   1571         tlm.expectLayouts(1);
   1572         adapter.deleteAndNotify(0, 10);
   1573         tlm.waitForLayout(2);
   1574         final CountDownLatch animationsLatch = new CountDownLatch(1);
   1575         rv.getItemAnimator().isRunning(
   1576                 new RecyclerView.ItemAnimator.ItemAnimatorFinishedListener() {
   1577                     @Override
   1578                     public void onAnimationsFinished() {
   1579                         animationsLatch.countDown();
   1580                     }
   1581                 });
   1582         assertTrue(animationsLatch.await(2, TimeUnit.SECONDS));
   1583         assertNull(vh.mOwnerRecyclerView);
   1584         checkForMainThreadException();
   1585     }
   1586 
   1587     @Test
   1588     public void duplicateAdapterPositionTest() throws Throwable {
   1589         final TestAdapter testAdapter = new TestAdapter(10);
   1590         final TestLayoutManager tlm = new TestLayoutManager() {
   1591             @Override
   1592             public void onLayoutChildren(RecyclerView.Recycler recycler, RecyclerView.State state) {
   1593                 detachAndScrapAttachedViews(recycler);
   1594                 layoutRange(recycler, 0, state.getItemCount());
   1595                 if (!state.isPreLayout()) {
   1596                     while (!recycler.getScrapList().isEmpty()) {
   1597                         RecyclerView.ViewHolder viewHolder = recycler.getScrapList().get(0);
   1598                         addDisappearingView(viewHolder.itemView, 0);
   1599                     }
   1600                 }
   1601                 layoutLatch.countDown();
   1602             }
   1603 
   1604             @Override
   1605             public boolean supportsPredictiveItemAnimations() {
   1606                 return true;
   1607             }
   1608         };
   1609         final DefaultItemAnimator animator = new DefaultItemAnimator();
   1610         animator.setSupportsChangeAnimations(true);
   1611         animator.setChangeDuration(10000);
   1612         testAdapter.setHasStableIds(true);
   1613         final TestRecyclerView recyclerView = new TestRecyclerView(getActivity());
   1614         recyclerView.setLayoutManager(tlm);
   1615         recyclerView.setAdapter(testAdapter);
   1616         recyclerView.setItemAnimator(animator);
   1617 
   1618         tlm.expectLayouts(1);
   1619         setRecyclerView(recyclerView);
   1620         tlm.waitForLayout(2);
   1621 
   1622         tlm.expectLayouts(2);
   1623         testAdapter.mItems.get(2).mType += 2;
   1624         final int itemId = testAdapter.mItems.get(2).mId;
   1625         testAdapter.changeAndNotify(2, 1);
   1626         tlm.waitForLayout(2);
   1627 
   1628         runTestOnUiThread(new Runnable() {
   1629             @Override
   1630             public void run() {
   1631                 assertThat("test sanity", recyclerView.getChildCount(), CoreMatchers.is(11));
   1632                 // now mangle the order and run the test
   1633                 RecyclerView.ViewHolder hidden = null;
   1634                 RecyclerView.ViewHolder updated = null;
   1635                 for (int i = 0; i < recyclerView.getChildCount(); i ++) {
   1636                     View view = recyclerView.getChildAt(i);
   1637                     RecyclerView.ViewHolder vh = recyclerView.getChildViewHolder(view);
   1638                     if (vh.getAdapterPosition() == 2) {
   1639                         if (mRecyclerView.mChildHelper.isHidden(view)) {
   1640                             assertThat(hidden, CoreMatchers.nullValue());
   1641                             hidden = vh;
   1642                         } else {
   1643                             assertThat(updated, CoreMatchers.nullValue());
   1644                             updated = vh;
   1645                         }
   1646                     }
   1647                 }
   1648                 assertThat(hidden, CoreMatchers.notNullValue());
   1649                 assertThat(updated, CoreMatchers.notNullValue());
   1650 
   1651                 mRecyclerView.eatRequestLayout();
   1652 
   1653                 // first put the hidden child back
   1654                 int index1 = mRecyclerView.indexOfChild(hidden.itemView);
   1655                 int index2 = mRecyclerView.indexOfChild(updated.itemView);
   1656                 if (index1 < index2) {
   1657                     // swap views
   1658                     swapViewsAtIndices(recyclerView, index1, index2);
   1659                 }
   1660                 assertThat(tlm.findViewByPosition(2), CoreMatchers.sameInstance(updated.itemView));
   1661 
   1662                 assertThat(recyclerView.findViewHolderForAdapterPosition(2),
   1663                         CoreMatchers.sameInstance(updated));
   1664                 assertThat(recyclerView.findViewHolderForLayoutPosition(2),
   1665                         CoreMatchers.sameInstance(updated));
   1666                 assertThat(recyclerView.findViewHolderForItemId(itemId),
   1667                         CoreMatchers.sameInstance(updated));
   1668 
   1669                 // now swap back
   1670                 swapViewsAtIndices(recyclerView, index1, index2);
   1671 
   1672                 assertThat(tlm.findViewByPosition(2), CoreMatchers.sameInstance(updated.itemView));
   1673                 assertThat(recyclerView.findViewHolderForAdapterPosition(2),
   1674                         CoreMatchers.sameInstance(updated));
   1675                 assertThat(recyclerView.findViewHolderForLayoutPosition(2),
   1676                         CoreMatchers.sameInstance(updated));
   1677                 assertThat(recyclerView.findViewHolderForItemId(itemId),
   1678                         CoreMatchers.sameInstance(updated));
   1679 
   1680                 // now remove updated. re-assert fallback to the hidden one
   1681                 tlm.removeView(updated.itemView);
   1682 
   1683                 assertThat(tlm.findViewByPosition(2), CoreMatchers.nullValue());
   1684                 assertThat(recyclerView.findViewHolderForAdapterPosition(2),
   1685                         CoreMatchers.sameInstance(hidden));
   1686                 assertThat(recyclerView.findViewHolderForLayoutPosition(2),
   1687                         CoreMatchers.sameInstance(hidden));
   1688                 assertThat(recyclerView.findViewHolderForItemId(itemId),
   1689                         CoreMatchers.sameInstance(hidden));
   1690             }
   1691         });
   1692 
   1693     }
   1694 
   1695     private void swapViewsAtIndices(TestRecyclerView recyclerView, int index1, int index2) {
   1696         if (index1 == index2) {
   1697             return;
   1698         }
   1699         if (index2 < index1) {
   1700             int tmp = index1;
   1701             index1 = index2;
   1702             index2 = tmp;
   1703         }
   1704         final View v1 = recyclerView.getChildAt(index1);
   1705         final View v2 = recyclerView.getChildAt(index2);
   1706         boolean v1Hidden = recyclerView.mChildHelper.isHidden(v1);
   1707         boolean v2Hidden = recyclerView.mChildHelper.isHidden(v2);
   1708         // must unhide before swap otherwise bucket indices will become invalid.
   1709         if (v1Hidden) {
   1710             mRecyclerView.mChildHelper.unhide(v1);
   1711         }
   1712         if (v2Hidden) {
   1713             mRecyclerView.mChildHelper.unhide(v2);
   1714         }
   1715         recyclerView.detachViewFromParent(index2);
   1716         recyclerView.attachViewToParent(v2, index1, v2.getLayoutParams());
   1717         recyclerView.detachViewFromParent(index1 + 1);
   1718         recyclerView.attachViewToParent(v1, index2, v1.getLayoutParams());
   1719 
   1720         if (v1Hidden) {
   1721             mRecyclerView.mChildHelper.hide(v1);
   1722         }
   1723         if (v2Hidden) {
   1724             mRecyclerView.mChildHelper.hide(v2);
   1725         }
   1726     }
   1727 
   1728     public void adapterPositionsTest(final AdapterRunnable adapterChanges) throws Throwable {
   1729         final TestAdapter testAdapter = new TestAdapter(10);
   1730         TestLayoutManager tlm = new TestLayoutManager() {
   1731             @Override
   1732             public void onLayoutChildren(RecyclerView.Recycler recycler, RecyclerView.State state) {
   1733                 try {
   1734                     layoutRange(recycler, Math.min(state.getItemCount(), 2)
   1735                             , Math.min(state.getItemCount(), 7));
   1736                     layoutLatch.countDown();
   1737                 } catch (Throwable t) {
   1738                     postExceptionToInstrumentation(t);
   1739                 }
   1740             }
   1741         };
   1742         final RecyclerView recyclerView = new RecyclerView(getActivity());
   1743         recyclerView.setLayoutManager(tlm);
   1744         recyclerView.setAdapter(testAdapter);
   1745         tlm.expectLayouts(1);
   1746         setRecyclerView(recyclerView);
   1747         tlm.waitForLayout(1);
   1748         runTestOnUiThread(new Runnable() {
   1749             @Override
   1750             public void run() {
   1751                 try {
   1752                     final int count = recyclerView.getChildCount();
   1753                     Map<View, Integer> layoutPositions = new HashMap<View, Integer>();
   1754                     assertTrue("test sanity", count > 0);
   1755                     for (int i = 0; i < count; i++) {
   1756                         View view = recyclerView.getChildAt(i);
   1757                         TestViewHolder vh = (TestViewHolder) recyclerView.getChildViewHolder(view);
   1758                         int index = testAdapter.mItems.indexOf(vh.mBoundItem);
   1759                         assertEquals("should be able to find VH with adapter position " + index, vh,
   1760                                 recyclerView.findViewHolderForAdapterPosition(index));
   1761                         assertEquals("get adapter position should return correct index", index,
   1762                                 vh.getAdapterPosition());
   1763                         layoutPositions.put(view, vh.mPosition);
   1764                     }
   1765                     if (adapterChanges != null) {
   1766                         adapterChanges.run(testAdapter);
   1767                         for (int i = 0; i < count; i++) {
   1768                             View view = recyclerView.getChildAt(i);
   1769                             TestViewHolder vh = (TestViewHolder) recyclerView
   1770                                     .getChildViewHolder(view);
   1771                             int index = testAdapter.mItems.indexOf(vh.mBoundItem);
   1772                             if (index >= 0) {
   1773                                 assertEquals("should be able to find VH with adapter position "
   1774                                                 + index, vh,
   1775                                         recyclerView.findViewHolderForAdapterPosition(index));
   1776                             }
   1777                             assertSame("get adapter position should return correct index", index,
   1778                                     vh.getAdapterPosition());
   1779                             assertSame("should be able to find view with layout position",
   1780                                     vh, mRecyclerView.findViewHolderForLayoutPosition(
   1781                                             layoutPositions.get(view)));
   1782                         }
   1783 
   1784                     }
   1785 
   1786                 } catch (Throwable t) {
   1787                     postExceptionToInstrumentation(t);
   1788                 }
   1789             }
   1790         });
   1791         checkForMainThreadException();
   1792     }
   1793 
   1794     @Test
   1795     public void scrollStateForSmoothScroll() throws Throwable {
   1796         TestAdapter testAdapter = new TestAdapter(10);
   1797         TestLayoutManager tlm = new TestLayoutManager();
   1798         RecyclerView recyclerView = new RecyclerView(getActivity());
   1799         recyclerView.setAdapter(testAdapter);
   1800         recyclerView.setLayoutManager(tlm);
   1801         setRecyclerView(recyclerView);
   1802         getInstrumentation().waitForIdleSync();
   1803         assertEquals(SCROLL_STATE_IDLE, recyclerView.getScrollState());
   1804         final int[] stateCnts = new int[10];
   1805         final CountDownLatch latch = new CountDownLatch(2);
   1806         recyclerView.setOnScrollListener(new RecyclerView.OnScrollListener() {
   1807             @Override
   1808             public void onScrollStateChanged(RecyclerView recyclerView, int newState) {
   1809                 stateCnts[newState] = stateCnts[newState] + 1;
   1810                 latch.countDown();
   1811             }
   1812         });
   1813         runTestOnUiThread(new Runnable() {
   1814             @Override
   1815             public void run() {
   1816                 mRecyclerView.smoothScrollBy(0, 500);
   1817             }
   1818         });
   1819         latch.await(5, TimeUnit.SECONDS);
   1820         assertEquals(1, stateCnts[SCROLL_STATE_SETTLING]);
   1821         assertEquals(1, stateCnts[SCROLL_STATE_IDLE]);
   1822         assertEquals(0, stateCnts[SCROLL_STATE_DRAGGING]);
   1823     }
   1824 
   1825     @Test
   1826     public void scrollStateForSmoothScrollWithStop() throws Throwable {
   1827         TestAdapter testAdapter = new TestAdapter(10);
   1828         TestLayoutManager tlm = new TestLayoutManager();
   1829         RecyclerView recyclerView = new RecyclerView(getActivity());
   1830         recyclerView.setAdapter(testAdapter);
   1831         recyclerView.setLayoutManager(tlm);
   1832         setRecyclerView(recyclerView);
   1833         getInstrumentation().waitForIdleSync();
   1834         assertEquals(SCROLL_STATE_IDLE, recyclerView.getScrollState());
   1835         final int[] stateCnts = new int[10];
   1836         final CountDownLatch latch = new CountDownLatch(1);
   1837         recyclerView.setOnScrollListener(new RecyclerView.OnScrollListener() {
   1838             @Override
   1839             public void onScrollStateChanged(RecyclerView recyclerView, int newState) {
   1840                 stateCnts[newState] = stateCnts[newState] + 1;
   1841                 latch.countDown();
   1842             }
   1843         });
   1844         runTestOnUiThread(new Runnable() {
   1845             @Override
   1846             public void run() {
   1847                 mRecyclerView.smoothScrollBy(0, 500);
   1848             }
   1849         });
   1850         latch.await(5, TimeUnit.SECONDS);
   1851         runTestOnUiThread(new Runnable() {
   1852             @Override
   1853             public void run() {
   1854                 mRecyclerView.stopScroll();
   1855             }
   1856         });
   1857         assertEquals(SCROLL_STATE_IDLE, recyclerView.getScrollState());
   1858         assertEquals(1, stateCnts[SCROLL_STATE_SETTLING]);
   1859         assertEquals(1, stateCnts[SCROLL_STATE_IDLE]);
   1860         assertEquals(0, stateCnts[SCROLL_STATE_DRAGGING]);
   1861     }
   1862 
   1863     @Test
   1864     public void scrollStateForFling() throws Throwable {
   1865         TestAdapter testAdapter = new TestAdapter(10);
   1866         TestLayoutManager tlm = new TestLayoutManager();
   1867         RecyclerView recyclerView = new RecyclerView(getActivity());
   1868         recyclerView.setAdapter(testAdapter);
   1869         recyclerView.setLayoutManager(tlm);
   1870         setRecyclerView(recyclerView);
   1871         getInstrumentation().waitForIdleSync();
   1872         assertEquals(SCROLL_STATE_IDLE, recyclerView.getScrollState());
   1873         final int[] stateCnts = new int[10];
   1874         final CountDownLatch latch = new CountDownLatch(2);
   1875         recyclerView.setOnScrollListener(new RecyclerView.OnScrollListener() {
   1876             @Override
   1877             public void onScrollStateChanged(RecyclerView recyclerView, int newState) {
   1878                 stateCnts[newState] = stateCnts[newState] + 1;
   1879                 latch.countDown();
   1880             }
   1881         });
   1882         final ViewConfiguration vc = ViewConfiguration.get(getActivity());
   1883         final float fling = vc.getScaledMinimumFlingVelocity()
   1884                 + (vc.getScaledMaximumFlingVelocity() - vc.getScaledMinimumFlingVelocity()) * .1f;
   1885         runTestOnUiThread(new Runnable() {
   1886             @Override
   1887             public void run() {
   1888                 mRecyclerView.fling(0, Math.round(fling));
   1889             }
   1890         });
   1891         latch.await(5, TimeUnit.SECONDS);
   1892         assertEquals(1, stateCnts[SCROLL_STATE_SETTLING]);
   1893         assertEquals(1, stateCnts[SCROLL_STATE_IDLE]);
   1894         assertEquals(0, stateCnts[SCROLL_STATE_DRAGGING]);
   1895     }
   1896 
   1897     @Test
   1898     public void scrollStateForFlingWithStop() throws Throwable {
   1899         TestAdapter testAdapter = new TestAdapter(10);
   1900         TestLayoutManager tlm = new TestLayoutManager();
   1901         RecyclerView recyclerView = new RecyclerView(getActivity());
   1902         recyclerView.setAdapter(testAdapter);
   1903         recyclerView.setLayoutManager(tlm);
   1904         setRecyclerView(recyclerView);
   1905         getInstrumentation().waitForIdleSync();
   1906         assertEquals(SCROLL_STATE_IDLE, recyclerView.getScrollState());
   1907         final int[] stateCnts = new int[10];
   1908         final CountDownLatch latch = new CountDownLatch(1);
   1909         recyclerView.setOnScrollListener(new RecyclerView.OnScrollListener() {
   1910             @Override
   1911             public void onScrollStateChanged(RecyclerView recyclerView, int newState) {
   1912                 stateCnts[newState] = stateCnts[newState] + 1;
   1913                 latch.countDown();
   1914             }
   1915         });
   1916         final ViewConfiguration vc = ViewConfiguration.get(getActivity());
   1917         final float fling = vc.getScaledMinimumFlingVelocity()
   1918                 + (vc.getScaledMaximumFlingVelocity() - vc.getScaledMinimumFlingVelocity()) * .8f;
   1919         runTestOnUiThread(new Runnable() {
   1920             @Override
   1921             public void run() {
   1922                 mRecyclerView.fling(0, Math.round(fling));
   1923             }
   1924         });
   1925         latch.await(5, TimeUnit.SECONDS);
   1926         runTestOnUiThread(new Runnable() {
   1927             @Override
   1928             public void run() {
   1929                 mRecyclerView.stopScroll();
   1930             }
   1931         });
   1932         assertEquals(SCROLL_STATE_IDLE, recyclerView.getScrollState());
   1933         assertEquals(1, stateCnts[SCROLL_STATE_SETTLING]);
   1934         assertEquals(1, stateCnts[SCROLL_STATE_IDLE]);
   1935         assertEquals(0, stateCnts[SCROLL_STATE_DRAGGING]);
   1936     }
   1937 
   1938     @Test
   1939     public void scrollStateDrag() throws Throwable {
   1940         TestAdapter testAdapter = new TestAdapter(10);
   1941         TestLayoutManager tlm = new TestLayoutManager();
   1942         RecyclerView recyclerView = new RecyclerView(getActivity());
   1943         recyclerView.setAdapter(testAdapter);
   1944         recyclerView.setLayoutManager(tlm);
   1945         setRecyclerView(recyclerView);
   1946         getInstrumentation().waitForIdleSync();
   1947         assertEquals(SCROLL_STATE_IDLE, recyclerView.getScrollState());
   1948         final int[] stateCnts = new int[10];
   1949         recyclerView.setOnScrollListener(new RecyclerView.OnScrollListener() {
   1950             @Override
   1951             public void onScrollStateChanged(RecyclerView recyclerView, int newState) {
   1952                 stateCnts[newState] = stateCnts[newState] + 1;
   1953             }
   1954         });
   1955         drag(mRecyclerView, 0, 0, 0, 500, 5);
   1956         assertEquals(0, stateCnts[SCROLL_STATE_SETTLING]);
   1957         assertEquals(1, stateCnts[SCROLL_STATE_IDLE]);
   1958         assertEquals(1, stateCnts[SCROLL_STATE_DRAGGING]);
   1959     }
   1960 
   1961     public void drag(ViewGroup view, float fromX, float toX, float fromY, float toY,
   1962             int stepCount) throws Throwable {
   1963         long downTime = SystemClock.uptimeMillis();
   1964         long eventTime = SystemClock.uptimeMillis();
   1965 
   1966         float y = fromY;
   1967         float x = fromX;
   1968 
   1969         float yStep = (toY - fromY) / stepCount;
   1970         float xStep = (toX - fromX) / stepCount;
   1971 
   1972         MotionEvent event = MotionEvent.obtain(downTime, eventTime,
   1973                 MotionEvent.ACTION_DOWN, x, y, 0);
   1974         sendTouch(view, event);
   1975         for (int i = 0; i < stepCount; ++i) {
   1976             y += yStep;
   1977             x += xStep;
   1978             eventTime = SystemClock.uptimeMillis();
   1979             event = MotionEvent.obtain(downTime, eventTime, MotionEvent.ACTION_MOVE, x, y, 0);
   1980             sendTouch(view, event);
   1981         }
   1982 
   1983         eventTime = SystemClock.uptimeMillis();
   1984         event = MotionEvent.obtain(downTime, eventTime, MotionEvent.ACTION_UP, x, y, 0);
   1985         sendTouch(view, event);
   1986         getInstrumentation().waitForIdleSync();
   1987     }
   1988 
   1989     private void sendTouch(final ViewGroup view, final MotionEvent event) throws Throwable {
   1990         runTestOnUiThread(new Runnable() {
   1991             @Override
   1992             public void run() {
   1993                 if (view.onInterceptTouchEvent(event)) {
   1994                     view.onTouchEvent(event);
   1995                 }
   1996             }
   1997         });
   1998     }
   1999 
   2000     @Test
   2001     public void recycleScrap() throws Throwable {
   2002         recycleScrapTest(false);
   2003         removeRecyclerView();
   2004         recycleScrapTest(true);
   2005     }
   2006 
   2007     public void recycleScrapTest(final boolean useRecycler) throws Throwable {
   2008         TestAdapter testAdapter = new TestAdapter(10);
   2009         final AtomicBoolean test = new AtomicBoolean(false);
   2010         TestLayoutManager lm = new TestLayoutManager() {
   2011             @Override
   2012             public void onLayoutChildren(RecyclerView.Recycler recycler, RecyclerView.State state) {
   2013                 ViewInfoStore infoStore = mRecyclerView.mViewInfoStore;
   2014                 if (test.get()) {
   2015                     try {
   2016                         detachAndScrapAttachedViews(recycler);
   2017                         for (int i = recycler.getScrapList().size() - 1; i >= 0; i--) {
   2018                             if (useRecycler) {
   2019                                 recycler.recycleView(recycler.getScrapList().get(i).itemView);
   2020                             } else {
   2021                                 removeAndRecycleView(recycler.getScrapList().get(i).itemView,
   2022                                         recycler);
   2023                             }
   2024                         }
   2025                         if (infoStore.mOldChangedHolders != null) {
   2026                             for (int i = infoStore.mOldChangedHolders.size() - 1; i >= 0; i--) {
   2027                                 if (useRecycler) {
   2028                                     recycler.recycleView(
   2029                                             infoStore.mOldChangedHolders.valueAt(i).itemView);
   2030                                 } else {
   2031                                     removeAndRecycleView(
   2032                                             infoStore.mOldChangedHolders.valueAt(i).itemView,
   2033                                             recycler);
   2034                                 }
   2035                             }
   2036                         }
   2037                         assertEquals("no scrap should be left over", 0, recycler.getScrapCount());
   2038                         assertEquals("pre layout map should be empty", 0,
   2039                                 InfoStoreTrojan.sizeOfPreLayout(infoStore));
   2040                         assertEquals("post layout map should be empty", 0,
   2041                                 InfoStoreTrojan.sizeOfPostLayout(infoStore));
   2042                         if (infoStore.mOldChangedHolders != null) {
   2043                             assertEquals("post old change map should be empty", 0,
   2044                                     infoStore.mOldChangedHolders.size());
   2045                         }
   2046                     } catch (Throwable t) {
   2047                         postExceptionToInstrumentation(t);
   2048                     }
   2049 
   2050                 }
   2051                 layoutRange(recycler, 0, 5);
   2052                 layoutLatch.countDown();
   2053                 super.onLayoutChildren(recycler, state);
   2054             }
   2055         };
   2056         RecyclerView recyclerView = new RecyclerView(getActivity());
   2057         recyclerView.setAdapter(testAdapter);
   2058         recyclerView.setLayoutManager(lm);
   2059         ((SimpleItemAnimator)recyclerView.getItemAnimator()).setSupportsChangeAnimations(true);
   2060         lm.expectLayouts(1);
   2061         setRecyclerView(recyclerView);
   2062         lm.waitForLayout(2);
   2063         test.set(true);
   2064         lm.expectLayouts(1);
   2065         testAdapter.changeAndNotify(3, 1);
   2066         lm.waitForLayout(2);
   2067         checkForMainThreadException();
   2068     }
   2069 
   2070     @Test
   2071     public void aAccessRecyclerOnOnMeasureWithPredictive() throws Throwable {
   2072         accessRecyclerOnOnMeasureTest(true);
   2073     }
   2074 
   2075     @Test
   2076     public void accessRecyclerOnOnMeasureWithoutPredictive() throws Throwable {
   2077         accessRecyclerOnOnMeasureTest(false);
   2078     }
   2079 
   2080     @Test
   2081     public void smoothScrollWithRemovedItemsAndRemoveItem() throws Throwable {
   2082         smoothScrollTest(true);
   2083     }
   2084 
   2085     @Test
   2086     public void smoothScrollWithRemovedItems() throws Throwable {
   2087         smoothScrollTest(false);
   2088     }
   2089 
   2090     public void smoothScrollTest(final boolean removeItem) throws Throwable {
   2091         final LinearSmoothScroller[] lss = new LinearSmoothScroller[1];
   2092         final CountDownLatch calledOnStart = new CountDownLatch(1);
   2093         final CountDownLatch calledOnStop = new CountDownLatch(1);
   2094         final int visibleChildCount = 10;
   2095         TestLayoutManager lm = new TestLayoutManager() {
   2096             int start = 0;
   2097 
   2098             @Override
   2099             public void onLayoutChildren(RecyclerView.Recycler recycler, RecyclerView.State state) {
   2100                 super.onLayoutChildren(recycler, state);
   2101                 layoutRange(recycler, start, visibleChildCount);
   2102                 layoutLatch.countDown();
   2103             }
   2104 
   2105             @Override
   2106             public int scrollVerticallyBy(int dy, RecyclerView.Recycler recycler,
   2107                     RecyclerView.State state) {
   2108                 start++;
   2109                 if (DEBUG) {
   2110                     Log.d(TAG, "on scroll, remove and recycling. start:" + start + ", cnt:"
   2111                             + visibleChildCount);
   2112                 }
   2113                 removeAndRecycleAllViews(recycler);
   2114                 layoutRange(recycler, start,
   2115                         Math.max(state.getItemCount(), start + visibleChildCount));
   2116                 return dy;
   2117             }
   2118 
   2119             @Override
   2120             public boolean canScrollVertically() {
   2121                 return true;
   2122             }
   2123 
   2124             @Override
   2125             public void smoothScrollToPosition(RecyclerView recyclerView, RecyclerView.State state,
   2126                     int position) {
   2127                 LinearSmoothScroller linearSmoothScroller =
   2128                         new LinearSmoothScroller(recyclerView.getContext()) {
   2129                             @Override
   2130                             public PointF computeScrollVectorForPosition(int targetPosition) {
   2131                                 return new PointF(0, 1);
   2132                             }
   2133 
   2134                             @Override
   2135                             protected void onStart() {
   2136                                 super.onStart();
   2137                                 calledOnStart.countDown();
   2138                             }
   2139 
   2140                             @Override
   2141                             protected void onStop() {
   2142                                 super.onStop();
   2143                                 calledOnStop.countDown();
   2144                             }
   2145                         };
   2146                 linearSmoothScroller.setTargetPosition(position);
   2147                 lss[0] = linearSmoothScroller;
   2148                 startSmoothScroll(linearSmoothScroller);
   2149             }
   2150         };
   2151         final RecyclerView rv = new RecyclerView(getActivity());
   2152         TestAdapter testAdapter = new TestAdapter(500);
   2153         rv.setLayoutManager(lm);
   2154         rv.setAdapter(testAdapter);
   2155         lm.expectLayouts(1);
   2156         setRecyclerView(rv);
   2157         lm.waitForLayout(1);
   2158         // regular scroll
   2159         final int targetPosition = visibleChildCount * (removeItem ? 30 : 4);
   2160         runTestOnUiThread(new Runnable() {
   2161             @Override
   2162             public void run() {
   2163                 rv.smoothScrollToPosition(targetPosition);
   2164             }
   2165         });
   2166         if (DEBUG) {
   2167             Log.d(TAG, "scrolling to target position " + targetPosition);
   2168         }
   2169         assertTrue("on start should be called very soon", calledOnStart.await(2, TimeUnit.SECONDS));
   2170         if (removeItem) {
   2171             final int newTarget = targetPosition - 10;
   2172             testAdapter.deleteAndNotify(newTarget + 1, testAdapter.getItemCount() - newTarget - 1);
   2173             final CountDownLatch targetCheck = new CountDownLatch(1);
   2174             runTestOnUiThread(new Runnable() {
   2175                 @Override
   2176                 public void run() {
   2177                     ViewCompat.postOnAnimationDelayed(rv, new Runnable() {
   2178                         @Override
   2179                         public void run() {
   2180                             try {
   2181                                 assertEquals("scroll position should be updated to next available",
   2182                                         newTarget, lss[0].getTargetPosition());
   2183                             } catch (Throwable t) {
   2184                                 postExceptionToInstrumentation(t);
   2185                             }
   2186                             targetCheck.countDown();
   2187                         }
   2188                     }, 50);
   2189                 }
   2190             });
   2191             assertTrue("target position should be checked on time ",
   2192                     targetCheck.await(10, TimeUnit.SECONDS));
   2193             checkForMainThreadException();
   2194             assertTrue("on stop should be called", calledOnStop.await(30, TimeUnit.SECONDS));
   2195             checkForMainThreadException();
   2196             assertNotNull("should scroll to new target " + newTarget
   2197                     , rv.findViewHolderForLayoutPosition(newTarget));
   2198             if (DEBUG) {
   2199                 Log.d(TAG, "on stop has been called on time");
   2200             }
   2201         } else {
   2202             assertTrue("on stop should be called eventually",
   2203                     calledOnStop.await(30, TimeUnit.SECONDS));
   2204             assertNotNull("scroll to position should succeed",
   2205                     rv.findViewHolderForLayoutPosition(targetPosition));
   2206         }
   2207         checkForMainThreadException();
   2208     }
   2209 
   2210     @Test
   2211     public void consecutiveSmoothScroll() throws Throwable {
   2212         final AtomicInteger visibleChildCount = new AtomicInteger(10);
   2213         final AtomicInteger totalScrolled = new AtomicInteger(0);
   2214         final TestLayoutManager lm = new TestLayoutManager() {
   2215             int start = 0;
   2216 
   2217             @Override
   2218             public void onLayoutChildren(RecyclerView.Recycler recycler, RecyclerView.State state) {
   2219                 super.onLayoutChildren(recycler, state);
   2220                 layoutRange(recycler, start, visibleChildCount.get());
   2221                 layoutLatch.countDown();
   2222             }
   2223 
   2224             @Override
   2225             public int scrollVerticallyBy(int dy, RecyclerView.Recycler recycler,
   2226                     RecyclerView.State state) {
   2227                 totalScrolled.set(totalScrolled.get() + dy);
   2228                 return dy;
   2229             }
   2230 
   2231             @Override
   2232             public boolean canScrollVertically() {
   2233                 return true;
   2234             }
   2235         };
   2236         final RecyclerView rv = new RecyclerView(getActivity());
   2237         TestAdapter testAdapter = new TestAdapter(500);
   2238         rv.setLayoutManager(lm);
   2239         rv.setAdapter(testAdapter);
   2240         lm.expectLayouts(1);
   2241         setRecyclerView(rv);
   2242         lm.waitForLayout(1);
   2243         runTestOnUiThread(new Runnable() {
   2244             @Override
   2245             public void run() {
   2246                 rv.smoothScrollBy(0, 2000);
   2247             }
   2248         });
   2249         Thread.sleep(250);
   2250         final AtomicInteger scrollAmt = new AtomicInteger();
   2251         runTestOnUiThread(new Runnable() {
   2252             @Override
   2253             public void run() {
   2254                 final int soFar = totalScrolled.get();
   2255                 scrollAmt.set(soFar);
   2256                 rv.smoothScrollBy(0, 5000 - soFar);
   2257             }
   2258         });
   2259         while (rv.getScrollState() != SCROLL_STATE_IDLE) {
   2260             Thread.sleep(100);
   2261         }
   2262         final int soFar = totalScrolled.get();
   2263         assertEquals("second scroll should be competed properly", 5000, soFar);
   2264     }
   2265 
   2266     public void accessRecyclerOnOnMeasureTest(final boolean enablePredictiveAnimations)
   2267             throws Throwable {
   2268         TestAdapter testAdapter = new TestAdapter(10);
   2269         final AtomicInteger expectedOnMeasureStateCount = new AtomicInteger(10);
   2270         TestLayoutManager lm = new TestLayoutManager() {
   2271             @Override
   2272             public void onLayoutChildren(RecyclerView.Recycler recycler, RecyclerView.State state) {
   2273                 super.onLayoutChildren(recycler, state);
   2274                 try {
   2275                     layoutRange(recycler, 0, state.getItemCount());
   2276                     layoutLatch.countDown();
   2277                 } catch (Throwable t) {
   2278                     postExceptionToInstrumentation(t);
   2279                 } finally {
   2280                     layoutLatch.countDown();
   2281                 }
   2282             }
   2283 
   2284             @Override
   2285             public void onMeasure(RecyclerView.Recycler recycler, RecyclerView.State state,
   2286                     int widthSpec, int heightSpec) {
   2287                 try {
   2288                     // make sure we access all views
   2289                     for (int i = 0; i < state.getItemCount(); i++) {
   2290                         View view = recycler.getViewForPosition(i);
   2291                         assertNotNull(view);
   2292                         assertEquals(i, getPosition(view));
   2293                     }
   2294                     if (!state.isPreLayout()) {
   2295                         assertEquals(state.toString(),
   2296                                 expectedOnMeasureStateCount.get(), state.getItemCount());
   2297                     }
   2298                 } catch (Throwable t) {
   2299                     postExceptionToInstrumentation(t);
   2300                 }
   2301                 super.onMeasure(recycler, state, widthSpec, heightSpec);
   2302             }
   2303 
   2304             @Override
   2305             public boolean supportsPredictiveItemAnimations() {
   2306                 return enablePredictiveAnimations;
   2307             }
   2308         };
   2309         RecyclerView recyclerView = new RecyclerView(getActivity());
   2310         recyclerView.setLayoutManager(lm);
   2311         recyclerView.setAdapter(testAdapter);
   2312         recyclerView.setLayoutManager(lm);
   2313         lm.expectLayouts(1);
   2314         setRecyclerView(recyclerView);
   2315         lm.waitForLayout(2);
   2316         checkForMainThreadException();
   2317         lm.expectLayouts(1);
   2318         if (!enablePredictiveAnimations) {
   2319             expectedOnMeasureStateCount.set(15);
   2320         }
   2321         testAdapter.addAndNotify(4, 5);
   2322         lm.waitForLayout(2);
   2323         checkForMainThreadException();
   2324     }
   2325 
   2326     @Test
   2327     public void setCompatibleAdapter() throws Throwable {
   2328         compatibleAdapterTest(true, true);
   2329         removeRecyclerView();
   2330         compatibleAdapterTest(false, true);
   2331         removeRecyclerView();
   2332         compatibleAdapterTest(true, false);
   2333         removeRecyclerView();
   2334         compatibleAdapterTest(false, false);
   2335         removeRecyclerView();
   2336     }
   2337 
   2338     private void compatibleAdapterTest(boolean useCustomPool, boolean removeAndRecycleExistingViews)
   2339             throws Throwable {
   2340         TestAdapter testAdapter = new TestAdapter(10);
   2341         final AtomicInteger recycledViewCount = new AtomicInteger();
   2342         TestLayoutManager lm = new TestLayoutManager() {
   2343             @Override
   2344             public void onLayoutChildren(RecyclerView.Recycler recycler, RecyclerView.State state) {
   2345                 try {
   2346                     layoutRange(recycler, 0, state.getItemCount());
   2347                     layoutLatch.countDown();
   2348                 } catch (Throwable t) {
   2349                     postExceptionToInstrumentation(t);
   2350                 } finally {
   2351                     layoutLatch.countDown();
   2352                 }
   2353             }
   2354         };
   2355         RecyclerView recyclerView = new RecyclerView(getActivity());
   2356         recyclerView.setLayoutManager(lm);
   2357         recyclerView.setAdapter(testAdapter);
   2358         recyclerView.setRecyclerListener(new RecyclerView.RecyclerListener() {
   2359             @Override
   2360             public void onViewRecycled(RecyclerView.ViewHolder holder) {
   2361                 recycledViewCount.incrementAndGet();
   2362             }
   2363         });
   2364         lm.expectLayouts(1);
   2365         setRecyclerView(recyclerView, !useCustomPool);
   2366         lm.waitForLayout(2);
   2367         checkForMainThreadException();
   2368         lm.expectLayouts(1);
   2369         swapAdapter(new TestAdapter(10), removeAndRecycleExistingViews);
   2370         lm.waitForLayout(2);
   2371         checkForMainThreadException();
   2372         if (removeAndRecycleExistingViews) {
   2373             assertTrue("Previous views should be recycled", recycledViewCount.get() > 0);
   2374         } else {
   2375             assertEquals("No views should be recycled if adapters are compatible and developer "
   2376                     + "did not request a recycle", 0, recycledViewCount.get());
   2377         }
   2378     }
   2379 
   2380     @Test
   2381     public void setIncompatibleAdapter() throws Throwable {
   2382         incompatibleAdapterTest(true);
   2383         incompatibleAdapterTest(false);
   2384     }
   2385 
   2386     public void incompatibleAdapterTest(boolean useCustomPool) throws Throwable {
   2387         TestAdapter testAdapter = new TestAdapter(10);
   2388         TestLayoutManager lm = new TestLayoutManager() {
   2389             @Override
   2390             public void onLayoutChildren(RecyclerView.Recycler recycler, RecyclerView.State state) {
   2391                 super.onLayoutChildren(recycler, state);
   2392                 try {
   2393                     layoutRange(recycler, 0, state.getItemCount());
   2394                     layoutLatch.countDown();
   2395                 } catch (Throwable t) {
   2396                     postExceptionToInstrumentation(t);
   2397                 } finally {
   2398                     layoutLatch.countDown();
   2399                 }
   2400             }
   2401         };
   2402         RecyclerView recyclerView = new RecyclerView(getActivity());
   2403         recyclerView.setLayoutManager(lm);
   2404         recyclerView.setAdapter(testAdapter);
   2405         recyclerView.setLayoutManager(lm);
   2406         lm.expectLayouts(1);
   2407         setRecyclerView(recyclerView, !useCustomPool);
   2408         lm.waitForLayout(2);
   2409         checkForMainThreadException();
   2410         lm.expectLayouts(1);
   2411         setAdapter(new TestAdapter2(10));
   2412         lm.waitForLayout(2);
   2413         checkForMainThreadException();
   2414     }
   2415 
   2416     @Test
   2417     public void recycleIgnored() throws Throwable {
   2418         final TestAdapter adapter = new TestAdapter(10);
   2419         final TestLayoutManager lm = new TestLayoutManager() {
   2420             @Override
   2421             public void onLayoutChildren(RecyclerView.Recycler recycler, RecyclerView.State state) {
   2422                 layoutRange(recycler, 0, 5);
   2423                 layoutLatch.countDown();
   2424             }
   2425         };
   2426         final RecyclerView recyclerView = new RecyclerView(getActivity());
   2427         recyclerView.setAdapter(adapter);
   2428         recyclerView.setLayoutManager(lm);
   2429         lm.expectLayouts(1);
   2430         setRecyclerView(recyclerView);
   2431         lm.waitForLayout(2);
   2432         runTestOnUiThread(new Runnable() {
   2433             @Override
   2434             public void run() {
   2435                 View child1 = lm.findViewByPosition(0);
   2436                 View child2 = lm.findViewByPosition(1);
   2437                 lm.ignoreView(child1);
   2438                 lm.ignoreView(child2);
   2439 
   2440                 lm.removeAndRecycleAllViews(recyclerView.mRecycler);
   2441                 assertEquals("ignored child should not be recycled or removed", 2,
   2442                         lm.getChildCount());
   2443 
   2444                 Throwable[] throwables = new Throwable[1];
   2445                 try {
   2446                     lm.removeAndRecycleView(child1, mRecyclerView.mRecycler);
   2447                 } catch (Throwable t) {
   2448                     throwables[0] = t;
   2449                 }
   2450                 assertTrue("Trying to recycle an ignored view should throw IllegalArgException "
   2451                         , throwables[0] instanceof IllegalArgumentException);
   2452                 lm.removeAllViews();
   2453                 assertEquals("ignored child should be removed as well ", 0, lm.getChildCount());
   2454             }
   2455         });
   2456     }
   2457 
   2458     @Test
   2459     public void findIgnoredByPosition() throws Throwable {
   2460         final TestAdapter adapter = new TestAdapter(10);
   2461         final TestLayoutManager lm = new TestLayoutManager() {
   2462             @Override
   2463             public void onLayoutChildren(RecyclerView.Recycler recycler, RecyclerView.State state) {
   2464                 detachAndScrapAttachedViews(recycler);
   2465                 layoutRange(recycler, 0, 5);
   2466                 layoutLatch.countDown();
   2467             }
   2468         };
   2469         final RecyclerView recyclerView = new RecyclerView(getActivity());
   2470         recyclerView.setAdapter(adapter);
   2471         recyclerView.setLayoutManager(lm);
   2472         lm.expectLayouts(1);
   2473         setRecyclerView(recyclerView);
   2474         lm.waitForLayout(2);
   2475         Thread.sleep(5000);
   2476         final int pos = 1;
   2477         final View[] ignored = new View[1];
   2478         runTestOnUiThread(new Runnable() {
   2479             @Override
   2480             public void run() {
   2481                 View child = lm.findViewByPosition(pos);
   2482                 lm.ignoreView(child);
   2483                 ignored[0] = child;
   2484             }
   2485         });
   2486         assertNotNull("ignored child should not be null", ignored[0]);
   2487         assertNull("find view by position should not return ignored child",
   2488                 lm.findViewByPosition(pos));
   2489         lm.expectLayouts(1);
   2490         requestLayoutOnUIThread(mRecyclerView);
   2491         lm.waitForLayout(1);
   2492         assertEquals("child count should be ", 6, lm.getChildCount());
   2493         View replacement = lm.findViewByPosition(pos);
   2494         assertNotNull("re-layout should replace ignored child w/ another one", replacement);
   2495         assertNotSame("replacement should be a different view", replacement, ignored[0]);
   2496     }
   2497 
   2498     @Test
   2499     public void invalidateAllDecorOffsets() throws Throwable {
   2500         final TestAdapter adapter = new TestAdapter(10);
   2501         final RecyclerView recyclerView = new RecyclerView(getActivity());
   2502         final AtomicBoolean invalidatedOffsets = new AtomicBoolean(true);
   2503         recyclerView.setAdapter(adapter);
   2504         final AtomicInteger layoutCount = new AtomicInteger(4);
   2505         final RecyclerView.ItemDecoration dummyItemDecoration = new RecyclerView.ItemDecoration() {
   2506         };
   2507         TestLayoutManager testLayoutManager = new TestLayoutManager() {
   2508             @Override
   2509             public void onLayoutChildren(RecyclerView.Recycler recycler, RecyclerView.State state) {
   2510                 try {
   2511                     // test
   2512                     for (int i = 0; i < getChildCount(); i++) {
   2513                         View child = getChildAt(i);
   2514                         RecyclerView.LayoutParams lp = (RecyclerView.LayoutParams)
   2515                                 child.getLayoutParams();
   2516                         assertEquals(
   2517                                 "Decor insets validation for VH should have expected value.",
   2518                                 invalidatedOffsets.get(), lp.mInsetsDirty);
   2519                     }
   2520                     for (RecyclerView.ViewHolder vh : mRecyclerView.mRecycler.mCachedViews) {
   2521                         RecyclerView.LayoutParams lp = (RecyclerView.LayoutParams)
   2522                                 vh.itemView.getLayoutParams();
   2523                         assertEquals(
   2524                                 "Decor insets invalidation in cache for VH should have expected "
   2525                                         + "value.",
   2526                                 invalidatedOffsets.get(), lp.mInsetsDirty);
   2527                     }
   2528                     detachAndScrapAttachedViews(recycler);
   2529                     layoutRange(recycler, 0, layoutCount.get());
   2530                 } catch (Throwable t) {
   2531                     postExceptionToInstrumentation(t);
   2532                 } finally {
   2533                     layoutLatch.countDown();
   2534                 }
   2535             }
   2536 
   2537             @Override
   2538             public boolean supportsPredictiveItemAnimations() {
   2539                 return false;
   2540             }
   2541         };
   2542         // first layout
   2543         recyclerView.setItemViewCacheSize(5);
   2544         recyclerView.setLayoutManager(testLayoutManager);
   2545         testLayoutManager.expectLayouts(1);
   2546         setRecyclerView(recyclerView, true, false);
   2547         testLayoutManager.waitForLayout(2);
   2548         checkForMainThreadException();
   2549 
   2550         // re-layout w/o any change
   2551         invalidatedOffsets.set(false);
   2552         testLayoutManager.expectLayouts(1);
   2553         requestLayoutOnUIThread(recyclerView);
   2554         testLayoutManager.waitForLayout(1);
   2555         checkForMainThreadException();
   2556 
   2557         // invalidate w/o an item decorator
   2558 
   2559         invalidateDecorOffsets(recyclerView);
   2560         testLayoutManager.expectLayouts(1);
   2561         invalidateDecorOffsets(recyclerView);
   2562         testLayoutManager.assertNoLayout("layout should not happen", 2);
   2563         checkForMainThreadException();
   2564 
   2565         // set item decorator, should invalidate
   2566         invalidatedOffsets.set(true);
   2567         testLayoutManager.expectLayouts(1);
   2568         addItemDecoration(mRecyclerView, dummyItemDecoration);
   2569         testLayoutManager.waitForLayout(1);
   2570         checkForMainThreadException();
   2571 
   2572         // re-layout w/o any change
   2573         invalidatedOffsets.set(false);
   2574         testLayoutManager.expectLayouts(1);
   2575         requestLayoutOnUIThread(recyclerView);
   2576         testLayoutManager.waitForLayout(1);
   2577         checkForMainThreadException();
   2578 
   2579         // invalidate w/ item decorator
   2580         invalidatedOffsets.set(true);
   2581         invalidateDecorOffsets(recyclerView);
   2582         testLayoutManager.expectLayouts(1);
   2583         invalidateDecorOffsets(recyclerView);
   2584         testLayoutManager.waitForLayout(2);
   2585         checkForMainThreadException();
   2586 
   2587         // trigger cache.
   2588         layoutCount.set(3);
   2589         invalidatedOffsets.set(false);
   2590         testLayoutManager.expectLayouts(1);
   2591         requestLayoutOnUIThread(mRecyclerView);
   2592         testLayoutManager.waitForLayout(1);
   2593         checkForMainThreadException();
   2594         assertEquals("a view should be cached", 1, mRecyclerView.mRecycler.mCachedViews.size());
   2595 
   2596         layoutCount.set(5);
   2597         invalidatedOffsets.set(true);
   2598         testLayoutManager.expectLayouts(1);
   2599         invalidateDecorOffsets(recyclerView);
   2600         testLayoutManager.waitForLayout(1);
   2601         checkForMainThreadException();
   2602 
   2603         // remove item decorator
   2604         invalidatedOffsets.set(true);
   2605         testLayoutManager.expectLayouts(1);
   2606         removeItemDecoration(mRecyclerView, dummyItemDecoration);
   2607         testLayoutManager.waitForLayout(1);
   2608         checkForMainThreadException();
   2609     }
   2610 
   2611     public void addItemDecoration(final RecyclerView recyclerView, final
   2612     RecyclerView.ItemDecoration itemDecoration) throws Throwable {
   2613         runTestOnUiThread(new Runnable() {
   2614             @Override
   2615             public void run() {
   2616                 recyclerView.addItemDecoration(itemDecoration);
   2617             }
   2618         });
   2619     }
   2620 
   2621     public void removeItemDecoration(final RecyclerView recyclerView, final
   2622     RecyclerView.ItemDecoration itemDecoration) throws Throwable {
   2623         runTestOnUiThread(new Runnable() {
   2624             @Override
   2625             public void run() {
   2626                 recyclerView.removeItemDecoration(itemDecoration);
   2627             }
   2628         });
   2629     }
   2630 
   2631     public void invalidateDecorOffsets(final RecyclerView recyclerView) throws Throwable {
   2632         runTestOnUiThread(new Runnable() {
   2633             @Override
   2634             public void run() {
   2635                 recyclerView.invalidateItemDecorations();
   2636             }
   2637         });
   2638     }
   2639 
   2640     @Test
   2641     public void invalidateDecorOffsets() throws Throwable {
   2642         final TestAdapter adapter = new TestAdapter(10);
   2643         adapter.setHasStableIds(true);
   2644         final RecyclerView recyclerView = new RecyclerView(getActivity());
   2645         recyclerView.setAdapter(adapter);
   2646 
   2647         final Map<Long, Boolean> changes = new HashMap<Long, Boolean>();
   2648 
   2649         TestLayoutManager testLayoutManager = new TestLayoutManager() {
   2650             @Override
   2651             public void onLayoutChildren(RecyclerView.Recycler recycler, RecyclerView.State state) {
   2652                 try {
   2653                     if (changes.size() > 0) {
   2654                         // test
   2655                         for (int i = 0; i < getChildCount(); i++) {
   2656                             View child = getChildAt(i);
   2657                             RecyclerView.LayoutParams lp = (RecyclerView.LayoutParams)
   2658                                     child.getLayoutParams();
   2659                             RecyclerView.ViewHolder vh = lp.mViewHolder;
   2660                             if (!changes.containsKey(vh.getItemId())) {
   2661                                 continue; //nothing to test
   2662                             }
   2663                             assertEquals(
   2664                                     "Decord insets validation for VH should have expected value.",
   2665                                     changes.get(vh.getItemId()).booleanValue(),
   2666                                     lp.mInsetsDirty);
   2667                         }
   2668                     }
   2669                     detachAndScrapAttachedViews(recycler);
   2670                     layoutRange(recycler, 0, state.getItemCount());
   2671                 } catch (Throwable t) {
   2672                     postExceptionToInstrumentation(t);
   2673                 } finally {
   2674                     layoutLatch.countDown();
   2675                 }
   2676             }
   2677 
   2678             @Override
   2679             public boolean supportsPredictiveItemAnimations() {
   2680                 return false;
   2681             }
   2682         };
   2683         recyclerView.setLayoutManager(testLayoutManager);
   2684         testLayoutManager.expectLayouts(1);
   2685         setRecyclerView(recyclerView);
   2686         testLayoutManager.waitForLayout(2);
   2687         int itemAddedTo = 5;
   2688         for (int i = 0; i < itemAddedTo; i++) {
   2689             changes.put(mRecyclerView.findViewHolderForLayoutPosition(i).getItemId(), false);
   2690         }
   2691         for (int i = itemAddedTo; i < mRecyclerView.getChildCount(); i++) {
   2692             changes.put(mRecyclerView.findViewHolderForLayoutPosition(i).getItemId(), true);
   2693         }
   2694         testLayoutManager.expectLayouts(1);
   2695         adapter.addAndNotify(5, 1);
   2696         testLayoutManager.waitForLayout(2);
   2697         checkForMainThreadException();
   2698 
   2699         changes.clear();
   2700         int[] changedItems = new int[]{3, 5, 6};
   2701         for (int i = 0; i < adapter.getItemCount(); i++) {
   2702             changes.put(mRecyclerView.findViewHolderForLayoutPosition(i).getItemId(), false);
   2703         }
   2704         for (int i = 0; i < changedItems.length; i++) {
   2705             changes.put(mRecyclerView.findViewHolderForLayoutPosition(changedItems[i]).getItemId(),
   2706                     true);
   2707         }
   2708         testLayoutManager.expectLayouts(1);
   2709         adapter.changePositionsAndNotify(changedItems);
   2710         testLayoutManager.waitForLayout(2);
   2711         checkForMainThreadException();
   2712 
   2713         for (int i = 0; i < adapter.getItemCount(); i++) {
   2714             changes.put(mRecyclerView.findViewHolderForLayoutPosition(i).getItemId(), true);
   2715         }
   2716         testLayoutManager.expectLayouts(1);
   2717         adapter.dispatchDataSetChanged();
   2718         testLayoutManager.waitForLayout(2);
   2719         checkForMainThreadException();
   2720     }
   2721 
   2722     @Test
   2723     public void movingViaStableIds() throws Throwable {
   2724         stableIdsMoveTest(true);
   2725         removeRecyclerView();
   2726         stableIdsMoveTest(false);
   2727         removeRecyclerView();
   2728     }
   2729 
   2730     public void stableIdsMoveTest(final boolean supportsPredictive) throws Throwable {
   2731         final TestAdapter testAdapter = new TestAdapter(10);
   2732         testAdapter.setHasStableIds(true);
   2733         final AtomicBoolean test = new AtomicBoolean(false);
   2734         final int movedViewFromIndex = 3;
   2735         final int movedViewToIndex = 6;
   2736         final View[] movedView = new View[1];
   2737         TestLayoutManager lm = new TestLayoutManager() {
   2738             @Override
   2739             public void onLayoutChildren(RecyclerView.Recycler recycler, RecyclerView.State state) {
   2740                 detachAndScrapAttachedViews(recycler);
   2741                 try {
   2742                     if (test.get()) {
   2743                         if (state.isPreLayout()) {
   2744                             View view = recycler.getViewForPosition(movedViewFromIndex, true);
   2745                             assertSame("In pre layout, should be able to get moved view w/ old "
   2746                                     + "position", movedView[0], view);
   2747                             RecyclerView.ViewHolder holder = mRecyclerView.getChildViewHolder(view);
   2748                             assertTrue("it should come from scrap", holder.wasReturnedFromScrap());
   2749                             // clear scrap flag
   2750                             holder.clearReturnedFromScrapFlag();
   2751                         } else {
   2752                             View view = recycler.getViewForPosition(movedViewToIndex, true);
   2753                             assertSame("In post layout, should be able to get moved view w/ new "
   2754                                     + "position", movedView[0], view);
   2755                             RecyclerView.ViewHolder holder = mRecyclerView.getChildViewHolder(view);
   2756                             assertTrue("it should come from scrap", holder.wasReturnedFromScrap());
   2757                             // clear scrap flag
   2758                             holder.clearReturnedFromScrapFlag();
   2759                         }
   2760                     }
   2761                     layoutRange(recycler, 0, state.getItemCount());
   2762                 } catch (Throwable t) {
   2763                     postExceptionToInstrumentation(t);
   2764                 } finally {
   2765                     layoutLatch.countDown();
   2766                 }
   2767 
   2768 
   2769             }
   2770 
   2771             @Override
   2772             public boolean supportsPredictiveItemAnimations() {
   2773                 return supportsPredictive;
   2774             }
   2775         };
   2776         RecyclerView recyclerView = new RecyclerView(this.getActivity());
   2777         recyclerView.setAdapter(testAdapter);
   2778         recyclerView.setLayoutManager(lm);
   2779         lm.expectLayouts(1);
   2780         setRecyclerView(recyclerView);
   2781         lm.waitForLayout(1);
   2782 
   2783         movedView[0] = recyclerView.getChildAt(movedViewFromIndex);
   2784         test.set(true);
   2785         lm.expectLayouts(supportsPredictive ? 2 : 1);
   2786         runTestOnUiThread(new Runnable() {
   2787             @Override
   2788             public void run() {
   2789                 Item item = testAdapter.mItems.remove(movedViewFromIndex);
   2790                 testAdapter.mItems.add(movedViewToIndex, item);
   2791                 testAdapter.notifyItemRemoved(movedViewFromIndex);
   2792                 testAdapter.notifyItemInserted(movedViewToIndex);
   2793             }
   2794         });
   2795         lm.waitForLayout(2);
   2796         checkForMainThreadException();
   2797     }
   2798 
   2799     @Test
   2800     public void adapterChangeDuringLayout() throws Throwable {
   2801         adapterChangeInMainThreadTest("notifyDataSetChanged", new Runnable() {
   2802             @Override
   2803             public void run() {
   2804                 mRecyclerView.getAdapter().notifyDataSetChanged();
   2805             }
   2806         });
   2807 
   2808         adapterChangeInMainThreadTest("notifyItemChanged", new Runnable() {
   2809             @Override
   2810             public void run() {
   2811                 mRecyclerView.getAdapter().notifyItemChanged(2);
   2812             }
   2813         });
   2814 
   2815         adapterChangeInMainThreadTest("notifyItemInserted", new Runnable() {
   2816             @Override
   2817             public void run() {
   2818                 mRecyclerView.getAdapter().notifyItemInserted(2);
   2819             }
   2820         });
   2821         adapterChangeInMainThreadTest("notifyItemRemoved", new Runnable() {
   2822             @Override
   2823             public void run() {
   2824                 mRecyclerView.getAdapter().notifyItemRemoved(2);
   2825             }
   2826         });
   2827     }
   2828 
   2829     public void adapterChangeInMainThreadTest(String msg,
   2830             final Runnable onLayoutRunnable) throws Throwable {
   2831         setIgnoreMainThreadException(true);
   2832         final AtomicBoolean doneFirstLayout = new AtomicBoolean(false);
   2833         TestAdapter testAdapter = new TestAdapter(10);
   2834         TestLayoutManager lm = new TestLayoutManager() {
   2835             @Override
   2836             public void onLayoutChildren(RecyclerView.Recycler recycler, RecyclerView.State state) {
   2837                 super.onLayoutChildren(recycler, state);
   2838                 try {
   2839                     layoutRange(recycler, 0, state.getItemCount());
   2840                     if (doneFirstLayout.get()) {
   2841                         onLayoutRunnable.run();
   2842                     }
   2843                 } catch (Throwable t) {
   2844                     postExceptionToInstrumentation(t);
   2845                 } finally {
   2846                     layoutLatch.countDown();
   2847                 }
   2848 
   2849             }
   2850         };
   2851         RecyclerView recyclerView = new RecyclerView(getActivity());
   2852         recyclerView.setLayoutManager(lm);
   2853         recyclerView.setAdapter(testAdapter);
   2854         lm.expectLayouts(1);
   2855         setRecyclerView(recyclerView);
   2856         lm.waitForLayout(2);
   2857         doneFirstLayout.set(true);
   2858         lm.expectLayouts(1);
   2859         requestLayoutOnUIThread(recyclerView);
   2860         lm.waitForLayout(2);
   2861         removeRecyclerView();
   2862         assertTrue("Invalid data updates should be caught:" + msg,
   2863                 getMainThreadException() instanceof IllegalStateException);
   2864     }
   2865 
   2866     @Test
   2867     public void adapterChangeDuringScroll() throws Throwable {
   2868         for (int orientation : new int[]{OrientationHelper.HORIZONTAL,
   2869                 OrientationHelper.VERTICAL}) {
   2870             adapterChangeDuringScrollTest("notifyDataSetChanged", orientation,
   2871                     new Runnable() {
   2872                         @Override
   2873                         public void run() {
   2874                             mRecyclerView.getAdapter().notifyDataSetChanged();
   2875                         }
   2876                     });
   2877             adapterChangeDuringScrollTest("notifyItemChanged", orientation, new Runnable() {
   2878                 @Override
   2879                 public void run() {
   2880                     mRecyclerView.getAdapter().notifyItemChanged(2);
   2881                 }
   2882             });
   2883 
   2884             adapterChangeDuringScrollTest("notifyItemInserted", orientation, new Runnable() {
   2885                 @Override
   2886                 public void run() {
   2887                     mRecyclerView.getAdapter().notifyItemInserted(2);
   2888                 }
   2889             });
   2890             adapterChangeDuringScrollTest("notifyItemRemoved", orientation, new Runnable() {
   2891                 @Override
   2892                 public void run() {
   2893                     mRecyclerView.getAdapter().notifyItemRemoved(2);
   2894                 }
   2895             });
   2896         }
   2897     }
   2898 
   2899     public void adapterChangeDuringScrollTest(String msg, final int orientation,
   2900             final Runnable onScrollRunnable) throws Throwable {
   2901         setIgnoreMainThreadException(true);
   2902         TestAdapter testAdapter = new TestAdapter(100);
   2903         TestLayoutManager lm = new TestLayoutManager() {
   2904             @Override
   2905             public void onLayoutChildren(RecyclerView.Recycler recycler, RecyclerView.State state) {
   2906                 super.onLayoutChildren(recycler, state);
   2907                 try {
   2908                     layoutRange(recycler, 0, 10);
   2909                 } catch (Throwable t) {
   2910                     postExceptionToInstrumentation(t);
   2911                 } finally {
   2912                     layoutLatch.countDown();
   2913                 }
   2914             }
   2915 
   2916             @Override
   2917             public boolean canScrollVertically() {
   2918                 return orientation == OrientationHelper.VERTICAL;
   2919             }
   2920 
   2921             @Override
   2922             public boolean canScrollHorizontally() {
   2923                 return orientation == OrientationHelper.HORIZONTAL;
   2924             }
   2925 
   2926             public int mockScroll() {
   2927                 try {
   2928                     onScrollRunnable.run();
   2929                 } catch (Throwable t) {
   2930                     postExceptionToInstrumentation(t);
   2931                 } finally {
   2932                     layoutLatch.countDown();
   2933                 }
   2934                 return 0;
   2935             }
   2936 
   2937             @Override
   2938             public int scrollHorizontallyBy(int dx, RecyclerView.Recycler recycler,
   2939                     RecyclerView.State state) {
   2940                 return mockScroll();
   2941             }
   2942 
   2943             @Override
   2944             public int scrollVerticallyBy(int dy, RecyclerView.Recycler recycler,
   2945                     RecyclerView.State state) {
   2946                 return mockScroll();
   2947             }
   2948         };
   2949         RecyclerView recyclerView = new RecyclerView(getActivity());
   2950         recyclerView.setLayoutManager(lm);
   2951         recyclerView.setAdapter(testAdapter);
   2952         lm.expectLayouts(1);
   2953         setRecyclerView(recyclerView);
   2954         lm.waitForLayout(2);
   2955         lm.expectLayouts(1);
   2956         scrollBy(200);
   2957         lm.waitForLayout(2);
   2958         removeRecyclerView();
   2959         assertTrue("Invalid data updates should be caught:" + msg,
   2960                 getMainThreadException() instanceof IllegalStateException);
   2961     }
   2962 
   2963     @Test
   2964     public void recycleOnDetach() throws Throwable {
   2965         final RecyclerView recyclerView = new RecyclerView(getActivity());
   2966         final TestAdapter testAdapter = new TestAdapter(10);
   2967         final AtomicBoolean didRunOnDetach = new AtomicBoolean(false);
   2968         final TestLayoutManager lm = new TestLayoutManager() {
   2969             @Override
   2970             public void onLayoutChildren(RecyclerView.Recycler recycler, RecyclerView.State state) {
   2971                 super.onLayoutChildren(recycler, state);
   2972                 layoutRange(recycler, 0, state.getItemCount() - 1);
   2973                 layoutLatch.countDown();
   2974             }
   2975 
   2976             @Override
   2977             public void onDetachedFromWindow(RecyclerView view, RecyclerView.Recycler recycler) {
   2978                 super.onDetachedFromWindow(view, recycler);
   2979                 didRunOnDetach.set(true);
   2980                 removeAndRecycleAllViews(recycler);
   2981             }
   2982         };
   2983         recyclerView.setAdapter(testAdapter);
   2984         recyclerView.setLayoutManager(lm);
   2985         lm.expectLayouts(1);
   2986         setRecyclerView(recyclerView);
   2987         lm.waitForLayout(2);
   2988         removeRecyclerView();
   2989         assertTrue("When recycler view is removed, detach should run", didRunOnDetach.get());
   2990         assertEquals("All children should be recycled", recyclerView.getChildCount(), 0);
   2991     }
   2992 
   2993     @Test
   2994     public void updatesWhileDetached() throws Throwable {
   2995         final RecyclerView recyclerView = new RecyclerView(getActivity());
   2996         final int initialAdapterSize = 20;
   2997         final TestAdapter adapter = new TestAdapter(initialAdapterSize);
   2998         final AtomicInteger layoutCount = new AtomicInteger(0);
   2999         TestLayoutManager lm = new TestLayoutManager() {
   3000             @Override
   3001             public void onLayoutChildren(RecyclerView.Recycler recycler, RecyclerView.State state) {
   3002                 super.onLayoutChildren(recycler, state);
   3003                 layoutRange(recycler, 0, 5);
   3004                 layoutCount.incrementAndGet();
   3005                 layoutLatch.countDown();
   3006             }
   3007         };
   3008         recyclerView.setAdapter(adapter);
   3009         recyclerView.setLayoutManager(lm);
   3010         recyclerView.setHasFixedSize(true);
   3011         lm.expectLayouts(1);
   3012         adapter.addAndNotify(4, 5);
   3013         lm.assertNoLayout("When RV is not attached, layout should not happen", 1);
   3014     }
   3015 
   3016     @Test
   3017     public void updatesAfterDetach() throws Throwable {
   3018         final RecyclerView recyclerView = new RecyclerView(getActivity());
   3019         final int initialAdapterSize = 20;
   3020         final TestAdapter adapter = new TestAdapter(initialAdapterSize);
   3021         final AtomicInteger layoutCount = new AtomicInteger(0);
   3022         TestLayoutManager lm = new TestLayoutManager() {
   3023             @Override
   3024             public void onLayoutChildren(RecyclerView.Recycler recycler, RecyclerView.State state) {
   3025                 super.onLayoutChildren(recycler, state);
   3026                 layoutRange(recycler, 0, 5);
   3027                 layoutCount.incrementAndGet();
   3028                 layoutLatch.countDown();
   3029             }
   3030         };
   3031         recyclerView.setAdapter(adapter);
   3032         recyclerView.setLayoutManager(lm);
   3033         lm.expectLayouts(1);
   3034         recyclerView.setHasFixedSize(true);
   3035         setRecyclerView(recyclerView);
   3036         lm.waitForLayout(2);
   3037         lm.expectLayouts(1);
   3038         final int prevLayoutCount = layoutCount.get();
   3039         runTestOnUiThread(new Runnable() {
   3040             @Override
   3041             public void run() {
   3042                 try {
   3043                     adapter.addAndNotify(4, 5);
   3044                     removeRecyclerView();
   3045                 } catch (Throwable throwable) {
   3046                     postExceptionToInstrumentation(throwable);
   3047                 }
   3048             }
   3049         });
   3050         checkForMainThreadException();
   3051 
   3052         lm.assertNoLayout("When RV is not attached, layout should not happen", 1);
   3053         assertEquals("No extra layout should happen when detached", prevLayoutCount,
   3054                 layoutCount.get());
   3055     }
   3056 
   3057     @Test
   3058     public void notifyDataSetChangedWithStableIds() throws Throwable {
   3059         final int defaultViewType = 1;
   3060         final Map<Item, Integer> viewTypeMap = new HashMap<Item, Integer>();
   3061         final Map<Integer, Integer> oldPositionToNewPositionMapping =
   3062                 new HashMap<Integer, Integer>();
   3063         final TestAdapter adapter = new TestAdapter(100) {
   3064             @Override
   3065             public int getItemViewType(int position) {
   3066                 Integer type = viewTypeMap.get(mItems.get(position));
   3067                 return type == null ? defaultViewType : type;
   3068             }
   3069 
   3070             @Override
   3071             public long getItemId(int position) {
   3072                 return mItems.get(position).mId;
   3073             }
   3074         };
   3075         adapter.setHasStableIds(true);
   3076         final ArrayList<Item> previousItems = new ArrayList<Item>();
   3077         previousItems.addAll(adapter.mItems);
   3078 
   3079         final AtomicInteger layoutStart = new AtomicInteger(50);
   3080         final AtomicBoolean validate = new AtomicBoolean(false);
   3081         final int childCount = 10;
   3082         final TestLayoutManager lm = new TestLayoutManager() {
   3083             @Override
   3084             public void onLayoutChildren(RecyclerView.Recycler recycler, RecyclerView.State state) {
   3085                 try {
   3086                     super.onLayoutChildren(recycler, state);
   3087                     if (validate.get()) {
   3088                         assertEquals("Cached views should be kept", 5, recycler
   3089                                 .mCachedViews.size());
   3090                         for (RecyclerView.ViewHolder vh : recycler.mCachedViews) {
   3091                             TestViewHolder tvh = (TestViewHolder) vh;
   3092                             assertTrue("view holder should be marked for update",
   3093                                     tvh.needsUpdate());
   3094                             assertTrue("view holder should be marked as invalid", tvh.isInvalid());
   3095                         }
   3096                     }
   3097                     detachAndScrapAttachedViews(recycler);
   3098                     if (validate.get()) {
   3099                         assertEquals("cache size should stay the same", 5,
   3100                                 recycler.mCachedViews.size());
   3101                         assertEquals("all views should be scrapped", childCount,
   3102                                 recycler.getScrapList().size());
   3103                         for (RecyclerView.ViewHolder vh : recycler.getScrapList()) {
   3104                             // TODO create test case for type change
   3105                             TestViewHolder tvh = (TestViewHolder) vh;
   3106                             assertTrue("view holder should be marked for update",
   3107                                     tvh.needsUpdate());
   3108                             assertTrue("view holder should be marked as invalid", tvh.isInvalid());
   3109                         }
   3110                     }
   3111                     layoutRange(recycler, layoutStart.get(), layoutStart.get() + childCount);
   3112                     if (validate.get()) {
   3113                         for (int i = 0; i < getChildCount(); i++) {
   3114                             View view = getChildAt(i);
   3115                             TestViewHolder tvh = (TestViewHolder) mRecyclerView
   3116                                     .getChildViewHolder(view);
   3117                             final int oldPos = previousItems.indexOf(tvh.mBoundItem);
   3118                             assertEquals("view holder's position should be correct",
   3119                                     oldPositionToNewPositionMapping.get(oldPos).intValue(),
   3120                                     tvh.getLayoutPosition());
   3121                             ;
   3122                         }
   3123                     }
   3124                 } catch (Throwable t) {
   3125                     postExceptionToInstrumentation(t);
   3126                 } finally {
   3127                     layoutLatch.countDown();
   3128                 }
   3129             }
   3130         };
   3131         final RecyclerView recyclerView = new RecyclerView(getActivity());
   3132         recyclerView.setItemAnimator(null);
   3133         recyclerView.setAdapter(adapter);
   3134         recyclerView.setLayoutManager(lm);
   3135         recyclerView.setItemViewCacheSize(10);
   3136         lm.expectLayouts(1);
   3137         setRecyclerView(recyclerView);
   3138         lm.waitForLayout(2);
   3139         checkForMainThreadException();
   3140         getInstrumentation().waitForIdleSync();
   3141         layoutStart.set(layoutStart.get() + 5);//55
   3142         lm.expectLayouts(1);
   3143         requestLayoutOnUIThread(recyclerView);
   3144         lm.waitForLayout(2);
   3145         validate.set(true);
   3146         lm.expectLayouts(1);
   3147         runTestOnUiThread(new Runnable() {
   3148             @Override
   3149             public void run() {
   3150                 try {
   3151                     adapter.moveItems(false,
   3152                             new int[]{50, 56}, new int[]{51, 1}, new int[]{52, 2},
   3153                             new int[]{53, 54}, new int[]{60, 61}, new int[]{62, 64},
   3154                             new int[]{75, 58});
   3155                     for (int i = 0; i < previousItems.size(); i++) {
   3156                         Item item = previousItems.get(i);
   3157                         oldPositionToNewPositionMapping.put(i, adapter.mItems.indexOf(item));
   3158                     }
   3159                     adapter.dispatchDataSetChanged();
   3160                 } catch (Throwable throwable) {
   3161                     postExceptionToInstrumentation(throwable);
   3162                 }
   3163             }
   3164         });
   3165         lm.waitForLayout(2);
   3166         checkForMainThreadException();
   3167     }
   3168 
   3169     @Test
   3170     public void callbacksDuringAdapterSwap() throws Throwable {
   3171         callbacksDuringAdapterChange(true);
   3172     }
   3173 
   3174     @Test
   3175     public void callbacksDuringAdapterSet() throws Throwable {
   3176         callbacksDuringAdapterChange(false);
   3177     }
   3178 
   3179     public void callbacksDuringAdapterChange(boolean swap) throws Throwable {
   3180         final TestAdapter2 adapter1 = swap ? createBinderCheckingAdapter()
   3181                 : createOwnerCheckingAdapter();
   3182         final TestAdapter2 adapter2 = swap ? createBinderCheckingAdapter()
   3183                 : createOwnerCheckingAdapter();
   3184 
   3185         TestLayoutManager tlm = new TestLayoutManager() {
   3186             @Override
   3187             public void onLayoutChildren(RecyclerView.Recycler recycler, RecyclerView.State state) {
   3188                 try {
   3189                     layoutRange(recycler, 0, state.getItemCount());
   3190                 } catch (Throwable t) {
   3191                     postExceptionToInstrumentation(t);
   3192                 }
   3193                 layoutLatch.countDown();
   3194             }
   3195         };
   3196         RecyclerView rv = new RecyclerView(getActivity());
   3197         rv.setAdapter(adapter1);
   3198         rv.setLayoutManager(tlm);
   3199         tlm.expectLayouts(1);
   3200         setRecyclerView(rv);
   3201         tlm.waitForLayout(1);
   3202         checkForMainThreadException();
   3203         tlm.expectLayouts(1);
   3204         if (swap) {
   3205             swapAdapter(adapter2, true);
   3206         } else {
   3207             setAdapter(adapter2);
   3208         }
   3209         checkForMainThreadException();
   3210         tlm.waitForLayout(1);
   3211         checkForMainThreadException();
   3212     }
   3213 
   3214     private TestAdapter2 createOwnerCheckingAdapter() {
   3215         return new TestAdapter2(10) {
   3216             @Override
   3217             public void onViewRecycled(TestViewHolder2 holder) {
   3218                 assertSame("on recycled should be called w/ the creator adapter", this,
   3219                         holder.mData);
   3220                 super.onViewRecycled(holder);
   3221             }
   3222 
   3223             @Override
   3224             public void onBindViewHolder(TestViewHolder2 holder, int position) {
   3225                 super.onBindViewHolder(holder, position);
   3226                 assertSame("on bind should be called w/ the creator adapter", this, holder.mData);
   3227             }
   3228 
   3229             @Override
   3230             public TestViewHolder2 onCreateViewHolder(ViewGroup parent,
   3231                     int viewType) {
   3232                 final TestViewHolder2 vh = super.onCreateViewHolder(parent, viewType);
   3233                 vh.mData = this;
   3234                 return vh;
   3235             }
   3236         };
   3237     }
   3238 
   3239     private TestAdapter2 createBinderCheckingAdapter() {
   3240         return new TestAdapter2(10) {
   3241             @Override
   3242             public void onViewRecycled(TestViewHolder2 holder) {
   3243                 assertSame("on recycled should be called w/ the creator adapter", this,
   3244                         holder.mData);
   3245                 holder.mData = null;
   3246                 super.onViewRecycled(holder);
   3247             }
   3248 
   3249             @Override
   3250             public void onBindViewHolder(TestViewHolder2 holder, int position) {
   3251                 super.onBindViewHolder(holder, position);
   3252                 holder.mData = this;
   3253             }
   3254         };
   3255     }
   3256 
   3257     @Test
   3258     public void findViewById() throws Throwable {
   3259         findViewByIdTest(false);
   3260         removeRecyclerView();
   3261         findViewByIdTest(true);
   3262     }
   3263 
   3264     public void findViewByIdTest(final boolean supportPredictive) throws Throwable {
   3265         final RecyclerView recyclerView = new RecyclerView(getActivity());
   3266         final int initialAdapterSize = 20;
   3267         final TestAdapter adapter = new TestAdapter(initialAdapterSize);
   3268         final int deleteStart = 6;
   3269         final int deleteCount = 5;
   3270         recyclerView.setAdapter(adapter);
   3271         final AtomicBoolean assertPositions = new AtomicBoolean(false);
   3272         TestLayoutManager lm = new TestLayoutManager() {
   3273             @Override
   3274             public void onLayoutChildren(RecyclerView.Recycler recycler, RecyclerView.State state) {
   3275                 super.onLayoutChildren(recycler, state);
   3276                 if (assertPositions.get()) {
   3277                     if (state.isPreLayout()) {
   3278                         for (int i = 0; i < deleteStart; i++) {
   3279                             View view = findViewByPosition(i);
   3280                             assertNotNull("find view by position for existing items should work "
   3281                                     + "fine", view);
   3282                             assertFalse("view should not be marked as removed",
   3283                                     ((RecyclerView.LayoutParams) view.getLayoutParams())
   3284                                             .isItemRemoved());
   3285                         }
   3286                         for (int i = 0; i < deleteCount; i++) {
   3287                             View view = findViewByPosition(i + deleteStart);
   3288                             assertNotNull("find view by position should work fine for removed "
   3289                                     + "views in pre-layout", view);
   3290                             assertTrue("view should be marked as removed",
   3291                                     ((RecyclerView.LayoutParams) view.getLayoutParams())
   3292                                             .isItemRemoved());
   3293                         }
   3294                         for (int i = deleteStart + deleteCount; i < 20; i++) {
   3295                             View view = findViewByPosition(i);
   3296                             assertNotNull(view);
   3297                             assertFalse("view should not be marked as removed",
   3298                                     ((RecyclerView.LayoutParams) view.getLayoutParams())
   3299                                             .isItemRemoved());
   3300                         }
   3301                     } else {
   3302                         for (int i = 0; i < initialAdapterSize - deleteCount; i++) {
   3303                             View view = findViewByPosition(i);
   3304                             assertNotNull("find view by position for existing item " + i +
   3305                                     " should work fine. child count:" + getChildCount(), view);
   3306                             TestViewHolder viewHolder =
   3307                                     (TestViewHolder) mRecyclerView.getChildViewHolder(view);
   3308                             assertSame("should be the correct item " + viewHolder
   3309                                     , viewHolder.mBoundItem,
   3310                                     adapter.mItems.get(viewHolder.mPosition));
   3311                             assertFalse("view should not be marked as removed",
   3312                                     ((RecyclerView.LayoutParams) view.getLayoutParams())
   3313                                             .isItemRemoved());
   3314                         }
   3315                     }
   3316                 }
   3317                 detachAndScrapAttachedViews(recycler);
   3318                 layoutRange(recycler, state.getItemCount() - 1, -1);
   3319                 layoutLatch.countDown();
   3320             }
   3321 
   3322             @Override
   3323             public boolean supportsPredictiveItemAnimations() {
   3324                 return supportPredictive;
   3325             }
   3326         };
   3327         recyclerView.setLayoutManager(lm);
   3328         lm.expectLayouts(1);
   3329         setRecyclerView(recyclerView);
   3330         lm.waitForLayout(2);
   3331         getInstrumentation().waitForIdleSync();
   3332 
   3333         assertPositions.set(true);
   3334         lm.expectLayouts(supportPredictive ? 2 : 1);
   3335         adapter.deleteAndNotify(new int[]{deleteStart, deleteCount - 1}, new int[]{deleteStart, 1});
   3336         lm.waitForLayout(2);
   3337     }
   3338 
   3339     @Test
   3340     public void typeForCache() throws Throwable {
   3341         final AtomicInteger viewType = new AtomicInteger(1);
   3342         final TestAdapter adapter = new TestAdapter(100) {
   3343             @Override
   3344             public int getItemViewType(int position) {
   3345                 return viewType.get();
   3346             }
   3347 
   3348             @Override
   3349             public long getItemId(int position) {
   3350                 return mItems.get(position).mId;
   3351             }
   3352         };
   3353         adapter.setHasStableIds(true);
   3354         final AtomicInteger layoutStart = new AtomicInteger(2);
   3355         final int childCount = 10;
   3356         final TestLayoutManager lm = new TestLayoutManager() {
   3357             @Override
   3358             public void onLayoutChildren(RecyclerView.Recycler recycler, RecyclerView.State state) {
   3359                 super.onLayoutChildren(recycler, state);
   3360                 detachAndScrapAttachedViews(recycler);
   3361                 layoutRange(recycler, layoutStart.get(), layoutStart.get() + childCount);
   3362                 layoutLatch.countDown();
   3363             }
   3364         };
   3365         final RecyclerView recyclerView = new RecyclerView(getActivity());
   3366         recyclerView.setItemAnimator(null);
   3367         recyclerView.setAdapter(adapter);
   3368         recyclerView.setLayoutManager(lm);
   3369         recyclerView.setItemViewCacheSize(10);
   3370         lm.expectLayouts(1);
   3371         setRecyclerView(recyclerView);
   3372         lm.waitForLayout(2);
   3373         getInstrumentation().waitForIdleSync();
   3374         layoutStart.set(4); // trigger a cache for 3,4
   3375         lm.expectLayouts(1);
   3376         requestLayoutOnUIThread(recyclerView);
   3377         lm.waitForLayout(2);
   3378         //
   3379         viewType.incrementAndGet();
   3380         layoutStart.set(2); // go back to bring views from cache
   3381         lm.expectLayouts(1);
   3382         adapter.mItems.remove(1);
   3383         adapter.dispatchDataSetChanged();
   3384         lm.waitForLayout(2);
   3385         runTestOnUiThread(new Runnable() {
   3386             @Override
   3387             public void run() {
   3388                 for (int i = 2; i < 4; i++) {
   3389                     RecyclerView.ViewHolder vh = recyclerView.findViewHolderForLayoutPosition(i);
   3390                     assertEquals("View holder's type should match latest type", viewType.get(),
   3391                             vh.getItemViewType());
   3392                 }
   3393             }
   3394         });
   3395     }
   3396 
   3397     @Test
   3398     public void typeForExistingViews() throws Throwable {
   3399         final AtomicInteger viewType = new AtomicInteger(1);
   3400         final int invalidatedCount = 2;
   3401         final int layoutStart = 2;
   3402         final TestAdapter adapter = new TestAdapter(100) {
   3403             @Override
   3404             public int getItemViewType(int position) {
   3405                 return viewType.get();
   3406             }
   3407 
   3408             @Override
   3409             public void onBindViewHolder(TestViewHolder holder,
   3410                     int position) {
   3411                 super.onBindViewHolder(holder, position);
   3412                 if (position >= layoutStart && position < invalidatedCount + layoutStart) {
   3413                     try {
   3414                         assertEquals("holder type should match current view type at position " +
   3415                                 position, viewType.get(), holder.getItemViewType());
   3416                     } catch (Throwable t) {
   3417                         postExceptionToInstrumentation(t);
   3418                     }
   3419                 }
   3420             }
   3421 
   3422             @Override
   3423             public long getItemId(int position) {
   3424                 return mItems.get(position).mId;
   3425             }
   3426         };
   3427         adapter.setHasStableIds(true);
   3428 
   3429         final int childCount = 10;
   3430         final TestLayoutManager lm = new TestLayoutManager() {
   3431             @Override
   3432             public void onLayoutChildren(RecyclerView.Recycler recycler, RecyclerView.State state) {
   3433                 super.onLayoutChildren(recycler, state);
   3434                 detachAndScrapAttachedViews(recycler);
   3435                 layoutRange(recycler, layoutStart, layoutStart + childCount);
   3436                 layoutLatch.countDown();
   3437             }
   3438         };
   3439         final RecyclerView recyclerView = new RecyclerView(getActivity());
   3440         recyclerView.setAdapter(adapter);
   3441         recyclerView.setLayoutManager(lm);
   3442         lm.expectLayouts(1);
   3443         setRecyclerView(recyclerView);
   3444         lm.waitForLayout(2);
   3445         getInstrumentation().waitForIdleSync();
   3446         viewType.incrementAndGet();
   3447         lm.expectLayouts(1);
   3448         adapter.changeAndNotify(layoutStart, invalidatedCount);
   3449         lm.waitForLayout(2);
   3450         checkForMainThreadException();
   3451     }
   3452 
   3453 
   3454     @Test
   3455     public void state() throws Throwable {
   3456         final TestAdapter adapter = new TestAdapter(10);
   3457         final RecyclerView recyclerView = new RecyclerView(getActivity());
   3458         recyclerView.setAdapter(adapter);
   3459         recyclerView.setItemAnimator(null);
   3460         final AtomicInteger itemCount = new AtomicInteger();
   3461         final AtomicBoolean structureChanged = new AtomicBoolean();
   3462         TestLayoutManager testLayoutManager = new TestLayoutManager() {
   3463             @Override
   3464             public void onLayoutChildren(RecyclerView.Recycler recycler, RecyclerView.State state) {
   3465                 detachAndScrapAttachedViews(recycler);
   3466                 layoutRange(recycler, 0, state.getItemCount());
   3467                 itemCount.set(state.getItemCount());
   3468                 structureChanged.set(state.didStructureChange());
   3469                 layoutLatch.countDown();
   3470             }
   3471         };
   3472         recyclerView.setLayoutManager(testLayoutManager);
   3473         testLayoutManager.expectLayouts(1);
   3474         runTestOnUiThread(new Runnable() {
   3475             @Override
   3476             public void run() {
   3477                 getActivity().getContainer().addView(recyclerView);
   3478             }
   3479         });
   3480         testLayoutManager.waitForLayout(2);
   3481 
   3482         assertEquals("item count in state should be correct", adapter.getItemCount()
   3483                 , itemCount.get());
   3484         assertEquals("structure changed should be true for first layout", true,
   3485                 structureChanged.get());
   3486         Thread.sleep(1000); //wait for other layouts.
   3487         testLayoutManager.expectLayouts(1);
   3488         runTestOnUiThread(new Runnable() {
   3489             @Override
   3490             public void run() {
   3491                 recyclerView.requestLayout();
   3492             }
   3493         });
   3494         testLayoutManager.waitForLayout(2);
   3495         assertEquals("in second layout,structure changed should be false", false,
   3496                 structureChanged.get());
   3497         testLayoutManager.expectLayouts(1); //
   3498         adapter.deleteAndNotify(3, 2);
   3499         testLayoutManager.waitForLayout(2);
   3500         assertEquals("when items are removed, item count in state should be updated",
   3501                 adapter.getItemCount(),
   3502                 itemCount.get());
   3503         assertEquals("structure changed should be true when items are removed", true,
   3504                 structureChanged.get());
   3505         testLayoutManager.expectLayouts(1);
   3506         adapter.addAndNotify(2, 5);
   3507         testLayoutManager.waitForLayout(2);
   3508 
   3509         assertEquals("when items are added, item count in state should be updated",
   3510                 adapter.getItemCount(),
   3511                 itemCount.get());
   3512         assertEquals("structure changed should be true when items are removed", true,
   3513                 structureChanged.get());
   3514     }
   3515 
   3516     @Test
   3517     public void detachWithoutLayoutManager() throws Throwable {
   3518         final RecyclerView recyclerView = new RecyclerView(getActivity());
   3519         runTestOnUiThread(new Runnable() {
   3520             @Override
   3521             public void run() {
   3522                 try {
   3523                     setRecyclerView(recyclerView);
   3524                     removeRecyclerView();
   3525                 } catch (Throwable t) {
   3526                     postExceptionToInstrumentation(t);
   3527                 }
   3528             }
   3529         });
   3530         checkForMainThreadException();
   3531     }
   3532 
   3533     @Test
   3534     public void updateHiddenView() throws Throwable {
   3535         final RecyclerView recyclerView = new RecyclerView(getActivity());
   3536         final int[] preLayoutRange = new int[]{0, 10};
   3537         final int[] postLayoutRange = new int[]{0, 10};
   3538         final AtomicBoolean enableGetViewTest = new AtomicBoolean(false);
   3539         final List<Integer> disappearingPositions = new ArrayList<>();
   3540         final TestLayoutManager tlm = new TestLayoutManager() {
   3541             @Override
   3542             public boolean supportsPredictiveItemAnimations() {
   3543                 return true;
   3544             }
   3545 
   3546             @Override
   3547             public void onLayoutChildren(RecyclerView.Recycler recycler, RecyclerView.State state) {
   3548                 try {
   3549                     final int[] layoutRange = state.isPreLayout() ? preLayoutRange
   3550                             : postLayoutRange;
   3551                     detachAndScrapAttachedViews(recycler);
   3552                     layoutRange(recycler, layoutRange[0], layoutRange[1]);
   3553                     if (!state.isPreLayout()) {
   3554                         for (Integer position : disappearingPositions) {
   3555                             // test sanity.
   3556                             assertNull(findViewByPosition(position));
   3557                             final View view = recycler.getViewForPosition(position);
   3558                             assertNotNull(view);
   3559                             addDisappearingView(view);
   3560                             measureChildWithMargins(view, 0, 0);
   3561                             // position item out of bounds.
   3562                             view.layout(0, -500, view.getMeasuredWidth(),
   3563                                     -500 + view.getMeasuredHeight());
   3564                         }
   3565                     }
   3566                 } catch (Throwable t) {
   3567                     postExceptionToInstrumentation(t);
   3568                 }
   3569                 layoutLatch.countDown();
   3570             }
   3571         };
   3572         recyclerView.getItemAnimator().setMoveDuration(4000);
   3573         recyclerView.getItemAnimator().setRemoveDuration(4000);
   3574         final TestAdapter adapter = new TestAdapter(100);
   3575         recyclerView.setAdapter(adapter);
   3576         recyclerView.setLayoutManager(tlm);
   3577         tlm.expectLayouts(1);
   3578         setRecyclerView(recyclerView);
   3579         tlm.waitForLayout(1);
   3580         checkForMainThreadException();
   3581         // now, a child disappears
   3582         disappearingPositions.add(0);
   3583         // layout one shifted
   3584         postLayoutRange[0] = 1;
   3585         postLayoutRange[1] = 11;
   3586         tlm.expectLayouts(2);
   3587         adapter.addAndNotify(8, 1);
   3588         tlm.waitForLayout(2);
   3589         checkForMainThreadException();
   3590 
   3591         tlm.expectLayouts(2);
   3592         disappearingPositions.clear();
   3593         // now that item should be moving, invalidate it and delete it.
   3594         enableGetViewTest.set(true);
   3595         runTestOnUiThread(new Runnable() {
   3596             @Override
   3597             public void run() {
   3598                 try {
   3599                     assertThat("test sanity, should still be animating",
   3600                             mRecyclerView.isAnimating(), CoreMatchers.is(true));
   3601                     adapter.changeAndNotify(0, 1);
   3602                     adapter.deleteAndNotify(0, 1);
   3603                 } catch (Throwable throwable) {
   3604                     fail(throwable.getMessage());
   3605                 }
   3606             }
   3607         });
   3608         tlm.waitForLayout(2);
   3609         checkForMainThreadException();
   3610     }
   3611 
   3612     @Test
   3613     public void focusBigViewOnTop() throws Throwable {
   3614         focusTooBigViewTest(Gravity.TOP);
   3615     }
   3616 
   3617     @Test
   3618     public void focusBigViewOnLeft() throws Throwable {
   3619         focusTooBigViewTest(Gravity.LEFT);
   3620     }
   3621 
   3622     @Test
   3623     public void focusBigViewOnRight() throws Throwable {
   3624         focusTooBigViewTest(Gravity.RIGHT);
   3625     }
   3626 
   3627     @Test
   3628     public void focusBigViewOnBottom() throws Throwable {
   3629         focusTooBigViewTest(Gravity.BOTTOM);
   3630     }
   3631 
   3632     @Test
   3633     public void focusBigViewOnLeftRTL() throws Throwable {
   3634         focusTooBigViewTest(Gravity.LEFT, true);
   3635         assertEquals("test sanity", ViewCompat.LAYOUT_DIRECTION_RTL,
   3636                 mRecyclerView.getLayoutManager().getLayoutDirection());
   3637     }
   3638 
   3639     @Test
   3640     public void focusBigViewOnRightRTL() throws Throwable {
   3641         focusTooBigViewTest(Gravity.RIGHT, true);
   3642         assertEquals("test sanity", ViewCompat.LAYOUT_DIRECTION_RTL,
   3643                 mRecyclerView.getLayoutManager().getLayoutDirection());
   3644     }
   3645 
   3646     public void focusTooBigViewTest(final int gravity) throws Throwable {
   3647         focusTooBigViewTest(gravity, false);
   3648     }
   3649 
   3650     public void focusTooBigViewTest(final int gravity, final boolean rtl) throws Throwable {
   3651         RecyclerView rv = new RecyclerView(getActivity());
   3652         if (rtl) {
   3653             ViewCompat.setLayoutDirection(rv, ViewCompat.LAYOUT_DIRECTION_RTL);
   3654         }
   3655         final AtomicInteger vScrollDist = new AtomicInteger(0);
   3656         final AtomicInteger hScrollDist = new AtomicInteger(0);
   3657         final AtomicInteger vDesiredDist = new AtomicInteger(0);
   3658         final AtomicInteger hDesiredDist = new AtomicInteger(0);
   3659         TestLayoutManager tlm = new TestLayoutManager() {
   3660 
   3661             @Override
   3662             public int getLayoutDirection() {
   3663                 return rtl ? ViewCompat.LAYOUT_DIRECTION_RTL : ViewCompat.LAYOUT_DIRECTION_LTR;
   3664             }
   3665 
   3666             @Override
   3667             public void onLayoutChildren(RecyclerView.Recycler recycler, RecyclerView.State state) {
   3668                 detachAndScrapAttachedViews(recycler);
   3669                 final View view = recycler.getViewForPosition(0);
   3670                 addView(view);
   3671                 int left = 0, top = 0;
   3672                 view.setBackgroundColor(Color.rgb(0, 0, 255));
   3673                 switch (gravity) {
   3674                     case Gravity.LEFT:
   3675                     case Gravity.RIGHT:
   3676                         view.measure(
   3677                                 View.MeasureSpec.makeMeasureSpec((int) (getWidth() * 1.5),
   3678                                         View.MeasureSpec.EXACTLY),
   3679                                 View.MeasureSpec.makeMeasureSpec((int) (getHeight() * .9),
   3680                                         View.MeasureSpec.AT_MOST));
   3681                         left = gravity == Gravity.LEFT ? getWidth() - view.getMeasuredWidth() - 80
   3682                                 : 90;
   3683                         top = 0;
   3684                         if (ViewCompat.LAYOUT_DIRECTION_RTL == getLayoutDirection()) {
   3685                             hDesiredDist.set((left + view.getMeasuredWidth()) - getWidth());
   3686                         } else {
   3687                             hDesiredDist.set(left);
   3688                         }
   3689                         break;
   3690                     case Gravity.TOP:
   3691                     case Gravity.BOTTOM:
   3692                         view.measure(
   3693                                 View.MeasureSpec.makeMeasureSpec((int) (getWidth() * .9),
   3694                                         View.MeasureSpec.AT_MOST),
   3695                                 View.MeasureSpec.makeMeasureSpec((int) (getHeight() * 1.5),
   3696                                         View.MeasureSpec.EXACTLY));
   3697                         top = gravity == Gravity.TOP ? getHeight() - view.getMeasuredHeight() -
   3698                                 80 : 90;
   3699                         left = 0;
   3700                         vDesiredDist.set(top);
   3701                         break;
   3702                 }
   3703 
   3704                 view.layout(left, top, left + view.getMeasuredWidth(),
   3705                         top + view.getMeasuredHeight());
   3706                 layoutLatch.countDown();
   3707             }
   3708 
   3709             @Override
   3710             public boolean canScrollVertically() {
   3711                 return true;
   3712             }
   3713 
   3714             @Override
   3715             public boolean canScrollHorizontally() {
   3716                 return super.canScrollHorizontally();
   3717             }
   3718 
   3719             @Override
   3720             public int scrollVerticallyBy(int dy, RecyclerView.Recycler recycler,
   3721                     RecyclerView.State state) {
   3722                 vScrollDist.addAndGet(dy);
   3723                 getChildAt(0).offsetTopAndBottom(-dy);
   3724                 return dy;
   3725             }
   3726 
   3727             @Override
   3728             public int scrollHorizontallyBy(int dx, RecyclerView.Recycler recycler,
   3729                     RecyclerView.State state) {
   3730                 hScrollDist.addAndGet(dx);
   3731                 getChildAt(0).offsetLeftAndRight(-dx);
   3732                 return dx;
   3733             }
   3734         };
   3735         TestAdapter adapter = new TestAdapter(10);
   3736         rv.setAdapter(adapter);
   3737         rv.setLayoutManager(tlm);
   3738         tlm.expectLayouts(1);
   3739         setRecyclerView(rv);
   3740         tlm.waitForLayout(2);
   3741         View view = rv.getChildAt(0);
   3742         assertTrue("test sanity", requestFocus(view, true));
   3743         assertTrue("test sanity", view.hasFocus());
   3744         assertEquals(vDesiredDist.get(), vScrollDist.get());
   3745         assertEquals(hDesiredDist.get(), hScrollDist.get());
   3746         assertEquals(mRecyclerView.getPaddingTop(), view.getTop());
   3747         if (rtl) {
   3748             assertEquals(mRecyclerView.getWidth() - mRecyclerView.getPaddingRight(),
   3749                     view.getRight());
   3750         } else {
   3751             assertEquals(mRecyclerView.getPaddingLeft(), view.getLeft());
   3752         }
   3753     }
   3754 
   3755     @Test
   3756     public void firstLayoutWithAdapterChanges() throws Throwable {
   3757         final TestAdapter adapter = new TestAdapter(0);
   3758         final RecyclerView rv = new RecyclerView(getActivity());
   3759         setVisibility(rv, View.GONE);
   3760         TestLayoutManager tlm = new TestLayoutManager() {
   3761             @Override
   3762             public void onLayoutChildren(RecyclerView.Recycler recycler, RecyclerView.State state) {
   3763                 try {
   3764                     super.onLayoutChildren(recycler, state);
   3765                     layoutRange(recycler, 0, state.getItemCount());
   3766                 } catch (Throwable t) {
   3767                     postExceptionToInstrumentation(t);
   3768                 } finally {
   3769                     layoutLatch.countDown();
   3770                 }
   3771             }
   3772 
   3773             @Override
   3774             public boolean supportsPredictiveItemAnimations() {
   3775                 return true;
   3776             }
   3777         };
   3778         rv.setLayoutManager(tlm);
   3779         rv.setAdapter(adapter);
   3780         rv.setHasFixedSize(true);
   3781         setRecyclerView(rv);
   3782         tlm.expectLayouts(1);
   3783         tlm.assertNoLayout("test sanity, layout should not run", 1);
   3784         getInstrumentation().waitForIdleSync();
   3785         runTestOnUiThread(new Runnable() {
   3786             @Override
   3787             public void run() {
   3788                 try {
   3789                     adapter.addAndNotify(2);
   3790                 } catch (Throwable throwable) {
   3791                     throwable.printStackTrace();
   3792                 }
   3793                 rv.setVisibility(View.VISIBLE);
   3794             }
   3795         });
   3796         checkForMainThreadException();
   3797         tlm.waitForLayout(2);
   3798         assertEquals(2, rv.getChildCount());
   3799         checkForMainThreadException();
   3800     }
   3801 
   3802     @Test
   3803     public void computeScrollOfsetWithoutLayoutManager() throws Throwable {
   3804         RecyclerView rv = new RecyclerView(getActivity());
   3805         rv.setAdapter(new TestAdapter(10));
   3806         setRecyclerView(rv);
   3807         assertEquals(0, rv.computeHorizontalScrollExtent());
   3808         assertEquals(0, rv.computeHorizontalScrollOffset());
   3809         assertEquals(0, rv.computeHorizontalScrollRange());
   3810 
   3811         assertEquals(0, rv.computeVerticalScrollExtent());
   3812         assertEquals(0, rv.computeVerticalScrollOffset());
   3813         assertEquals(0, rv.computeVerticalScrollRange());
   3814     }
   3815 
   3816     @Test
   3817     public void computeScrollOfsetWithoutAdapter() throws Throwable {
   3818         RecyclerView rv = new RecyclerView(getActivity());
   3819         rv.setLayoutManager(new TestLayoutManager());
   3820         setRecyclerView(rv);
   3821         assertEquals(0, rv.computeHorizontalScrollExtent());
   3822         assertEquals(0, rv.computeHorizontalScrollOffset());
   3823         assertEquals(0, rv.computeHorizontalScrollRange());
   3824 
   3825         assertEquals(0, rv.computeVerticalScrollExtent());
   3826         assertEquals(0, rv.computeVerticalScrollOffset());
   3827         assertEquals(0, rv.computeVerticalScrollRange());
   3828     }
   3829 
   3830     @Test
   3831     public void focusRectOnScreenWithDecorOffsets() throws Throwable {
   3832         focusRectOnScreenTest(true);
   3833     }
   3834 
   3835     @Test
   3836     public void focusRectOnScreenWithout() throws Throwable {
   3837         focusRectOnScreenTest(false);
   3838     }
   3839 
   3840     public void focusRectOnScreenTest(boolean addItemDecors) throws Throwable {
   3841         RecyclerView rv = new RecyclerView(getActivity());
   3842         final AtomicInteger scrollDist = new AtomicInteger(0);
   3843         TestLayoutManager tlm = new TestLayoutManager() {
   3844             @Override
   3845             public void onLayoutChildren(RecyclerView.Recycler recycler, RecyclerView.State state) {
   3846                 detachAndScrapAttachedViews(recycler);
   3847                 final View view = recycler.getViewForPosition(0);
   3848                 addView(view);
   3849                 measureChildWithMargins(view, 0, 0);
   3850                 view.layout(0, -20, view.getWidth(),
   3851                         -20 + view.getHeight());// ignore decors on purpose
   3852                 layoutLatch.countDown();
   3853             }
   3854 
   3855             @Override
   3856             public boolean canScrollVertically() {
   3857                 return true;
   3858             }
   3859 
   3860             @Override
   3861             public int scrollVerticallyBy(int dy, RecyclerView.Recycler recycler,
   3862                     RecyclerView.State state) {
   3863                 scrollDist.addAndGet(dy);
   3864                 return dy;
   3865             }
   3866         };
   3867         TestAdapter adapter = new TestAdapter(10);
   3868         if (addItemDecors) {
   3869             rv.addItemDecoration(new RecyclerView.ItemDecoration() {
   3870                 @Override
   3871                 public void getItemOffsets(Rect outRect, View view, RecyclerView parent,
   3872                         RecyclerView.State state) {
   3873                     outRect.set(0, 10, 0, 10);
   3874                 }
   3875             });
   3876         }
   3877         rv.setAdapter(adapter);
   3878         rv.setLayoutManager(tlm);
   3879         tlm.expectLayouts(1);
   3880         setRecyclerView(rv);
   3881         tlm.waitForLayout(2);
   3882 
   3883         View view = rv.getChildAt(0);
   3884         requestFocus(view, true);
   3885         assertEquals(addItemDecors ? -30 : -20, scrollDist.get());
   3886     }
   3887 
   3888     @Test
   3889     public void unimplementedSmoothScroll() throws Throwable {
   3890         final AtomicInteger receivedScrollToPosition = new AtomicInteger(-1);
   3891         final AtomicInteger receivedSmoothScrollToPosition = new AtomicInteger(-1);
   3892         final CountDownLatch cbLatch = new CountDownLatch(2);
   3893         TestLayoutManager tlm = new TestLayoutManager() {
   3894             @Override
   3895             public void onLayoutChildren(RecyclerView.Recycler recycler, RecyclerView.State state) {
   3896                 detachAndScrapAttachedViews(recycler);
   3897                 layoutRange(recycler, 0, 10);
   3898                 layoutLatch.countDown();
   3899             }
   3900 
   3901             @Override
   3902             public void smoothScrollToPosition(RecyclerView recyclerView, RecyclerView.State state,
   3903                     int position) {
   3904                 assertEquals(-1, receivedSmoothScrollToPosition.get());
   3905                 receivedSmoothScrollToPosition.set(position);
   3906                 RecyclerView.SmoothScroller ss =
   3907                         new LinearSmoothScroller(recyclerView.getContext()) {
   3908                             @Override
   3909                             public PointF computeScrollVectorForPosition(int targetPosition) {
   3910                                 return null;
   3911                             }
   3912                         };
   3913                 ss.setTargetPosition(position);
   3914                 startSmoothScroll(ss);
   3915                 cbLatch.countDown();
   3916             }
   3917 
   3918             @Override
   3919             public void scrollToPosition(int position) {
   3920                 assertEquals(-1, receivedScrollToPosition.get());
   3921                 receivedScrollToPosition.set(position);
   3922                 cbLatch.countDown();
   3923             }
   3924         };
   3925         RecyclerView rv = new RecyclerView(getActivity());
   3926         rv.setAdapter(new TestAdapter(100));
   3927         rv.setLayoutManager(tlm);
   3928         tlm.expectLayouts(1);
   3929         setRecyclerView(rv);
   3930         tlm.waitForLayout(2);
   3931         freezeLayout(true);
   3932         smoothScrollToPosition(35, false);
   3933         assertEquals("smoothScrollToPosition should be ignored when frozen",
   3934                 -1, receivedSmoothScrollToPosition.get());
   3935         freezeLayout(false);
   3936         smoothScrollToPosition(35, false);
   3937         assertTrue("both scrolls should be called", cbLatch.await(3, TimeUnit.SECONDS));
   3938         checkForMainThreadException();
   3939         assertEquals(35, receivedSmoothScrollToPosition.get());
   3940         assertEquals(35, receivedScrollToPosition.get());
   3941     }
   3942 
   3943     @Test
   3944     public void jumpingJackSmoothScroller() throws Throwable {
   3945         jumpingJackSmoothScrollerTest(true);
   3946     }
   3947 
   3948     @Test
   3949     public void jumpingJackSmoothScrollerGoesIdle() throws Throwable {
   3950         jumpingJackSmoothScrollerTest(false);
   3951     }
   3952 
   3953     @Test
   3954     public void testScrollByBeforeFirstLayout() throws Throwable {
   3955         final RecyclerView recyclerView = new RecyclerView(getActivity());
   3956         TestAdapter adapter = new TestAdapter(10);
   3957         recyclerView.setLayoutManager(new TestLayoutManager() {
   3958             AtomicBoolean didLayout = new AtomicBoolean(false);
   3959             @Override
   3960             public void onLayoutChildren(RecyclerView.Recycler recycler, RecyclerView.State state) {
   3961                 super.onLayoutChildren(recycler, state);
   3962                 didLayout.set(true);
   3963             }
   3964 
   3965             @Override
   3966             public int scrollVerticallyBy(int dy, RecyclerView.Recycler recycler,
   3967                     RecyclerView.State state) {
   3968                 assertThat("should run layout before scroll",
   3969                         didLayout.get(), CoreMatchers.is(true));
   3970                 return super.scrollVerticallyBy(dy, recycler, state);
   3971             }
   3972 
   3973             @Override
   3974             public boolean canScrollVertically() {
   3975                 return true;
   3976             }
   3977         });
   3978         recyclerView.setAdapter(adapter);
   3979 
   3980         runTestOnUiThread(new Runnable() {
   3981             @Override
   3982             public void run() {
   3983                 try {
   3984                     setRecyclerView(recyclerView);
   3985                     recyclerView.scrollBy(10, 19);
   3986                 } catch (Throwable throwable) {
   3987                     postExceptionToInstrumentation(throwable);
   3988                 }
   3989             }
   3990         });
   3991 
   3992         checkForMainThreadException();
   3993     }
   3994 
   3995     private void jumpingJackSmoothScrollerTest(final boolean succeed) throws Throwable {
   3996         final List<Integer> receivedScrollToPositions = new ArrayList<>();
   3997         final TestAdapter testAdapter = new TestAdapter(200);
   3998         final AtomicBoolean mTargetFound = new AtomicBoolean(false);
   3999         TestLayoutManager tlm = new TestLayoutManager() {
   4000             int pendingScrollPosition = -1;
   4001             @Override
   4002             public void onLayoutChildren(RecyclerView.Recycler recycler, RecyclerView.State state) {
   4003                 detachAndScrapAttachedViews(recycler);
   4004                 final int pos = pendingScrollPosition < 0 ? 0: pendingScrollPosition;
   4005                 layoutRange(recycler, pos, pos + 10);
   4006                 if (layoutLatch != null) {
   4007                     layoutLatch.countDown();
   4008                 }
   4009             }
   4010 
   4011             @Override
   4012             public void smoothScrollToPosition(RecyclerView recyclerView, RecyclerView.State state,
   4013                     final int position) {
   4014                 RecyclerView.SmoothScroller ss =
   4015                         new LinearSmoothScroller(recyclerView.getContext()) {
   4016                             @Override
   4017                             public PointF computeScrollVectorForPosition(int targetPosition) {
   4018                                 return new PointF(0, 1);
   4019                             }
   4020 
   4021                             @Override
   4022                             protected void onTargetFound(View targetView, RecyclerView.State state,
   4023                                                          Action action) {
   4024                                 super.onTargetFound(targetView, state, action);
   4025                                 mTargetFound.set(true);
   4026                             }
   4027 
   4028                             @Override
   4029                             protected void updateActionForInterimTarget(Action action) {
   4030                                 int limit = succeed ? getTargetPosition() : 100;
   4031                                 if (pendingScrollPosition + 2 < limit) {
   4032                                     if (pendingScrollPosition != NO_POSITION) {
   4033                                         assertEquals(pendingScrollPosition,
   4034                                                 getChildViewHolderInt(getChildAt(0))
   4035                                                         .getAdapterPosition());
   4036                                     }
   4037                                     action.jumpTo(pendingScrollPosition + 2);
   4038                                 }
   4039                             }
   4040                         };
   4041                 ss.setTargetPosition(position);
   4042                 startSmoothScroll(ss);
   4043             }
   4044 
   4045             @Override
   4046             public void scrollToPosition(int position) {
   4047                 receivedScrollToPositions.add(position);
   4048                 pendingScrollPosition = position;
   4049                 requestLayout();
   4050             }
   4051         };
   4052         final RecyclerView rv = new RecyclerView(getActivity());
   4053         rv.setAdapter(testAdapter);
   4054         rv.setLayoutManager(tlm);
   4055 
   4056         tlm.expectLayouts(1);
   4057         setRecyclerView(rv);
   4058         tlm.waitForLayout(2);
   4059 
   4060         runTestOnUiThread(new Runnable() {
   4061             @Override
   4062             public void run() {
   4063                 rv.smoothScrollToPosition(150);
   4064             }
   4065         });
   4066         int limit = 100;
   4067         while (rv.getLayoutManager().isSmoothScrolling() && --limit > 0) {
   4068             Thread.sleep(200);
   4069             checkForMainThreadException();
   4070         }
   4071         checkForMainThreadException();
   4072         assertTrue(limit > 0);
   4073         for (int i = 1; i < 100; i+=2) {
   4074             assertTrue("scroll positions must include " + i, receivedScrollToPositions.contains(i));
   4075         }
   4076 
   4077         assertEquals(succeed, mTargetFound.get());
   4078 
   4079     }
   4080 
   4081     private static class TestViewHolder2 extends RecyclerView.ViewHolder {
   4082 
   4083         Object mData;
   4084 
   4085         public TestViewHolder2(View itemView) {
   4086             super(itemView);
   4087         }
   4088     }
   4089 
   4090     private static class TestAdapter2 extends RecyclerView.Adapter<TestViewHolder2> {
   4091 
   4092         List<Item> mItems;
   4093 
   4094         private TestAdapter2(int count) {
   4095             mItems = new ArrayList<Item>(count);
   4096             for (int i = 0; i < count; i++) {
   4097                 mItems.add(new Item(i, "Item " + i));
   4098             }
   4099         }
   4100 
   4101         @Override
   4102         public TestViewHolder2 onCreateViewHolder(ViewGroup parent,
   4103                 int viewType) {
   4104             return new TestViewHolder2(new TextView(parent.getContext()));
   4105         }
   4106 
   4107         @Override
   4108         public void onBindViewHolder(TestViewHolder2 holder, int position) {
   4109             final Item item = mItems.get(position);
   4110             ((TextView) (holder.itemView)).setText(item.mText + "(" + item.mAdapterIndex + ")");
   4111         }
   4112 
   4113         @Override
   4114         public int getItemCount() {
   4115             return mItems.size();
   4116         }
   4117     }
   4118 
   4119     public interface AdapterRunnable {
   4120 
   4121         void run(TestAdapter adapter) throws Throwable;
   4122     }
   4123 
   4124     public class LayoutAllLayoutManager extends TestLayoutManager {
   4125         @Override
   4126         public void onLayoutChildren(RecyclerView.Recycler recycler, RecyclerView.State state) {
   4127             detachAndScrapAttachedViews(recycler);
   4128             layoutRange(recycler, 0, state.getItemCount());
   4129             layoutLatch.countDown();
   4130         }
   4131     }
   4132 
   4133     /**
   4134      * Proxy class to make protected methods public
   4135      */
   4136     public static class TestRecyclerView extends RecyclerView {
   4137 
   4138         public TestRecyclerView(Context context) {
   4139             super(context);
   4140         }
   4141 
   4142         public TestRecyclerView(Context context, @Nullable AttributeSet attrs) {
   4143             super(context, attrs);
   4144         }
   4145 
   4146         public TestRecyclerView(Context context, @Nullable AttributeSet attrs, int defStyle) {
   4147             super(context, attrs, defStyle);
   4148         }
   4149 
   4150         @Override
   4151         public void detachViewFromParent(int index) {
   4152             super.detachViewFromParent(index);
   4153         }
   4154 
   4155         @Override
   4156         public void attachViewToParent(View child, int index, ViewGroup.LayoutParams params) {
   4157             super.attachViewToParent(child, index, params);
   4158         }
   4159     }
   4160 
   4161     private static interface ViewRunnable {
   4162         void run(View view) throws RuntimeException;
   4163     }
   4164 }
   4165