• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
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 
RecyclerViewLayoutTest()82     public RecyclerViewLayoutTest() {
83         super(DEBUG);
84     }
85 
86     @Test
detachAttachGetReadyWithoutChanges()87     public void detachAttachGetReadyWithoutChanges() throws Throwable {
88         detachAttachGetReady(false, false, false);
89     }
90 
91     @Test
detachAttachGetReadyRequireLayout()92     public void detachAttachGetReadyRequireLayout() throws Throwable {
93         detachAttachGetReady(true, false, false);
94     }
95 
96     @Test
detachAttachGetReadyRemoveAdapter()97     public void detachAttachGetReadyRemoveAdapter() throws Throwable {
98         detachAttachGetReady(false, true, false);
99     }
100 
101     @Test
detachAttachGetReadyRemoveLayoutManager()102     public void detachAttachGetReadyRemoveLayoutManager() throws Throwable {
103         detachAttachGetReady(false, false, true);
104     }
105 
detachAttachGetReady(final boolean requestLayoutOnDetach, final boolean removeAdapter, final boolean removeLayoutManager)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
focusSearchWithOtherFocusables()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
boundingBoxNoTranslation()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
boundingBoxTranslateX()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
boundingBoxTranslateY()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
boundingBoxScaleX()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
boundingBoxScaleY()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
boundingBoxRotated()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
boundingBoxRotatedWithDecorOffsets()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 
getTransformedBoundingBox(View child)357     private Rect getTransformedBoundingBox(View child) {
358         Rect rect = new Rect();
359         mRecyclerView.getLayoutManager().getTransformedBoundingBox(child, true, rect);
360         return rect;
361     }
362 
transformedBoundingBoxTest(final ViewRunnable layout)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
flingFrozen()389     public void flingFrozen() throws Throwable {
390         testScrollFrozen(true);
391     }
392 
393     @Test
dragFrozen()394     public void dragFrozen() throws Throwable {
395         testScrollFrozen(false);
396     }
397 
398     @Test
requestRectOnScreenWithScrollOffset()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
reattachAndScrollCrash()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 
testScrollFrozen(boolean fling)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
testFocusSearchAfterChangedData()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
testFocusSearchWithRemovedFocusedItem()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
testFocusSearchFailFrozen()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 
focusSearch(final ViewGroup parent, final View focused, final int direction)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
frozenAndChangeAdapter()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
noLayoutIf0ItemsAreChanged()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
noLayoutIf0ItemsAreChangedWithPayload()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
noLayoutIf0ItemsAreAdded()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
noLayoutIf0ItemsAreRemoved()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
noLayoutIfItemMovedIntoItsOwnPlace()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 
unnecessaryNotifyEvents(final AdapterRunnable action)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
scrollToPositionCallback()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
scrollCalllbackOnVisibleRangeExpand()904     public void scrollCalllbackOnVisibleRangeExpand() throws Throwable {
905         scrollCallbackOnVisibleRangeChange(10, new int[]{3, 5}, new int[]{3, 6});
906     }
907 
908     @Test
scrollCalllbackOnVisibleRangeShrink()909     public void scrollCalllbackOnVisibleRangeShrink() throws Throwable {
910         scrollCallbackOnVisibleRangeChange(10, new int[]{3, 6}, new int[]{3, 5});
911     }
912 
913     @Test
scrollCalllbackOnVisibleRangeExpand2()914     public void scrollCalllbackOnVisibleRangeExpand2() throws Throwable {
915         scrollCallbackOnVisibleRangeChange(10, new int[]{3, 5}, new int[]{2, 5});
916     }
917 
918     @Test
scrollCalllbackOnVisibleRangeShrink2()919     public void scrollCalllbackOnVisibleRangeShrink2() throws Throwable {
920         scrollCallbackOnVisibleRangeChange(10, new int[]{3, 6}, new int[]{2, 6});
921     }
922 
scrollCallbackOnVisibleRangeChange(int itemCount, final int[] beforeRange, final int[] afterRange)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
addItemOnScroll()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
scrollInBothDirectionEqual()997     public void scrollInBothDirectionEqual() throws Throwable {
998         scrollInBothDirection(3, 3, 1000, 1000);
999     }
1000 
1001     @Test
scrollInBothDirectionMoreVertical()1002     public void scrollInBothDirectionMoreVertical() throws Throwable {
1003         scrollInBothDirection(2, 3, 1000, 1000);
1004     }
1005 
1006     @Test
scrollInBothDirectionMoreHorizontal()1007     public void scrollInBothDirectionMoreHorizontal() throws Throwable {
1008         scrollInBothDirection(3, 2, 1000, 1000);
1009     }
1010 
1011     @Test
scrollHorizontalOnly()1012     public void scrollHorizontalOnly() throws Throwable {
1013         scrollInBothDirection(3, 0, 1000, 0);
1014     }
1015 
1016     @Test
scrollVerticalOnly()1017     public void scrollVerticalOnly() throws Throwable {
1018         scrollInBothDirection(0, 3, 0, 1000);
1019     }
1020 
1021     @Test
scrollInBothDirectionEqualReverse()1022     public void scrollInBothDirectionEqualReverse() throws Throwable {
1023         scrollInBothDirection(3, 3, -1000, -1000);
1024     }
1025 
1026     @Test
scrollInBothDirectionMoreVerticalReverse()1027     public void scrollInBothDirectionMoreVerticalReverse() throws Throwable {
1028         scrollInBothDirection(2, 3, -1000, -1000);
1029     }
1030 
1031     @Test
scrollInBothDirectionMoreHorizontalReverse()1032     public void scrollInBothDirectionMoreHorizontalReverse() throws Throwable {
1033         scrollInBothDirection(3, 2, -1000, -1000);
1034     }
1035 
1036     @Test
scrollHorizontalOnlyReverse()1037     public void scrollHorizontalOnlyReverse() throws Throwable {
1038         scrollInBothDirection(3, 0, -1000, 0);
1039     }
1040 
1041     @Test
scrollVerticalOnlyReverse()1042     public void scrollVerticalOnlyReverse() throws Throwable {
1043         scrollInBothDirection(0, 3, 0, -1000);
1044     }
1045 
scrollInBothDirection(int horizontalScrollCount, int verticalScrollCount, int horizontalVelocity, int verticalVelocity)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
dragHorizontal()1103     public void dragHorizontal() throws Throwable {
1104         scrollInOtherOrientationTest(FLAG_HORIZONTAL);
1105     }
1106 
1107     @Test
dragVertical()1108     public void dragVertical() throws Throwable {
1109         scrollInOtherOrientationTest(FLAG_VERTICAL);
1110     }
1111 
1112     @Test
flingHorizontal()1113     public void flingHorizontal() throws Throwable {
1114         scrollInOtherOrientationTest(FLAG_HORIZONTAL | FLAG_FLING);
1115     }
1116 
1117     @Test
flingVertical()1118     public void flingVertical() throws Throwable {
1119         scrollInOtherOrientationTest(FLAG_VERTICAL | FLAG_FLING);
1120     }
1121 
1122     @Test
nestedDragVertical()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
nestedDragHorizontal()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
nestedDragHorizontalCallsStopNestedScroll()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
nestedDragVerticalCallsStopNestedScroll()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 
scrollInOtherOrientationTest(int flags)1156     private void scrollInOtherOrientationTest(int flags)
1157             throws Throwable {
1158         scrollInOtherOrientationTest(flags, flags);
1159     }
1160 
scrollInOtherOrientationTest(final int flags, int expectedFlags)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 
fling(final int velocityX, final int velocityY)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 
assertPendingUpdatesAndLayoutTest(final AdapterRunnable runnable)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 
setupBasic(RecyclerView recyclerView, TestLayoutManager tlm, TestAdapter adapter, boolean waitForFirstLayout)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
hasPendingUpdatesBeforeFirstLayout()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
noPendingUpdatesAfterLayout()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
hasPendingUpdatesAfterItemIsRemoved()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
hasPendingUpdatesAfterItemIsInserted()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
hasPendingUpdatesAfterItemIsMoved()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
hasPendingUpdatesAfterItemIsChanged()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
hasPendingUpdatesAfterDataSetIsChanged()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
transientStateRecycleViaAdapter()1334     public void transientStateRecycleViaAdapter() throws Throwable {
1335         transientStateRecycleTest(true, false);
1336     }
1337 
1338     @Test
transientStateRecycleViaTransientStateCleanup()1339     public void transientStateRecycleViaTransientStateCleanup() throws Throwable {
1340         transientStateRecycleTest(false, true);
1341     }
1342 
1343     @Test
transientStateDontRecycle()1344     public void transientStateDontRecycle() throws Throwable {
1345         transientStateRecycleTest(false, false);
1346     }
1347 
transientStateRecycleTest(final boolean succeed, final boolean unsetTransientState)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
adapterPositionInvalidation()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
adapterPositionsBasic()1436     public void adapterPositionsBasic() throws Throwable {
1437         adapterPositionsTest(null);
1438     }
1439 
1440     @Test
adapterPositionsRemoveItems()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
adapterPositionsRemoveItemsBefore()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
adapterPositionsAddItemsBefore()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
adapterPositionsAddItemsInside()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
adapterPositionsMoveItems()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
adapterPositionsNotifyDataSetChanged()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
avoidLeakingRecyclerViewIfViewIsNotRecycled()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
avoidLeakingRecyclerViewViaViewHolder()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
duplicateAdapterPositionTest()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 
swapViewsAtIndices(TestRecyclerView recyclerView, int index1, int index2)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 
adapterPositionsTest(final AdapterRunnable adapterChanges)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
scrollStateForSmoothScroll()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
scrollStateForSmoothScrollWithStop()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
scrollStateForFling()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
scrollStateForFlingWithStop()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
scrollStateDrag()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 
drag(ViewGroup view, float fromX, float toX, float fromY, float toY, int stepCount)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 
sendTouch(final ViewGroup view, final MotionEvent event)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
recycleScrap()2001     public void recycleScrap() throws Throwable {
2002         recycleScrapTest(false);
2003         removeRecyclerView();
2004         recycleScrapTest(true);
2005     }
2006 
recycleScrapTest(final boolean useRecycler)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
aAccessRecyclerOnOnMeasureWithPredictive()2071     public void aAccessRecyclerOnOnMeasureWithPredictive() throws Throwable {
2072         accessRecyclerOnOnMeasureTest(true);
2073     }
2074 
2075     @Test
accessRecyclerOnOnMeasureWithoutPredictive()2076     public void accessRecyclerOnOnMeasureWithoutPredictive() throws Throwable {
2077         accessRecyclerOnOnMeasureTest(false);
2078     }
2079 
2080     @Test
smoothScrollWithRemovedItemsAndRemoveItem()2081     public void smoothScrollWithRemovedItemsAndRemoveItem() throws Throwable {
2082         smoothScrollTest(true);
2083     }
2084 
2085     @Test
smoothScrollWithRemovedItems()2086     public void smoothScrollWithRemovedItems() throws Throwable {
2087         smoothScrollTest(false);
2088     }
2089 
smoothScrollTest(final boolean removeItem)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
consecutiveSmoothScroll()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 
accessRecyclerOnOnMeasureTest(final boolean enablePredictiveAnimations)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
setCompatibleAdapter()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 
compatibleAdapterTest(boolean useCustomPool, boolean removeAndRecycleExistingViews)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
setIncompatibleAdapter()2381     public void setIncompatibleAdapter() throws Throwable {
2382         incompatibleAdapterTest(true);
2383         incompatibleAdapterTest(false);
2384     }
2385 
incompatibleAdapterTest(boolean useCustomPool)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
recycleIgnored()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
findIgnoredByPosition()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
invalidateAllDecorOffsets()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 
addItemDecoration(final RecyclerView recyclerView, final RecyclerView.ItemDecoration itemDecoration)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 
removeItemDecoration(final RecyclerView recyclerView, final RecyclerView.ItemDecoration itemDecoration)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 
invalidateDecorOffsets(final RecyclerView recyclerView)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
invalidateDecorOffsets()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
movingViaStableIds()2723     public void movingViaStableIds() throws Throwable {
2724         stableIdsMoveTest(true);
2725         removeRecyclerView();
2726         stableIdsMoveTest(false);
2727         removeRecyclerView();
2728     }
2729 
stableIdsMoveTest(final boolean supportsPredictive)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
adapterChangeDuringLayout()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 
adapterChangeInMainThreadTest(String msg, final Runnable onLayoutRunnable)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
adapterChangeDuringScroll()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 
adapterChangeDuringScrollTest(String msg, final int orientation, final Runnable onScrollRunnable)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
recycleOnDetach()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
updatesWhileDetached()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
updatesAfterDetach()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
notifyDataSetChangedWithStableIds()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
callbacksDuringAdapterSwap()3170     public void callbacksDuringAdapterSwap() throws Throwable {
3171         callbacksDuringAdapterChange(true);
3172     }
3173 
3174     @Test
callbacksDuringAdapterSet()3175     public void callbacksDuringAdapterSet() throws Throwable {
3176         callbacksDuringAdapterChange(false);
3177     }
3178 
callbacksDuringAdapterChange(boolean swap)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 
createOwnerCheckingAdapter()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