Home | History | Annotate | Download | only in widget
      1 /*
      2  * Copyright (C) 2014 The Android Open Source Project
      3  *
      4  * Licensed under the Apache License, Version 2.0 (the "License");
      5  * you may not use this file except in compliance with the License.
      6  * You may obtain a copy of the License at
      7  *
      8  *      http://www.apache.org/licenses/LICENSE-2.0
      9  *
     10  * Unless required by applicable law or agreed to in writing, software
     11  * distributed under the License is distributed on an "AS IS" BASIS,
     12  * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
     13  * See the License for the specific language governing permissions and
     14  * limitations under the License.
     15  */
     16 
     17 
     18 package android.support.v7.widget;
     19 
     20 import org.junit.After;
     21 import org.junit.Before;
     22 import org.junit.Test;
     23 import org.junit.runner.RunWith;
     24 
     25 import android.support.test.InstrumentationRegistry;
     26 import android.graphics.Color;
     27 import android.graphics.PointF;
     28 import android.graphics.Rect;
     29 import android.os.SystemClock;
     30 import android.support.v4.view.ViewCompat;
     31 import android.support.v7.widget.RecyclerView;
     32 import android.test.TouchUtils;
     33 import android.util.Log;
     34 import android.view.Gravity;
     35 import android.view.KeyEvent;
     36 import android.view.MotionEvent;
     37 import android.view.View;
     38 import android.view.ViewConfiguration;
     39 import android.view.ViewGroup;
     40 import android.view.ViewTreeObserver;
     41 import android.widget.TextView;
     42 
     43 import java.util.ArrayList;
     44 import java.util.HashMap;
     45 import java.util.List;
     46 import java.util.Map;
     47 import java.util.concurrent.CountDownLatch;
     48 import java.util.concurrent.TimeUnit;
     49 import java.util.concurrent.atomic.AtomicBoolean;
     50 import java.util.concurrent.atomic.AtomicInteger;
     51 
     52 import static android.support.v7.widget.RecyclerView.NO_POSITION;
     53 import static android.support.v7.widget.RecyclerView.SCROLL_STATE_IDLE;
     54 import static android.support.v7.widget.RecyclerView.SCROLL_STATE_DRAGGING;
     55 import static android.support.v7.widget.RecyclerView.SCROLL_STATE_SETTLING;
     56 import static android.support.v7.widget.RecyclerView.getChildViewHolderInt;
     57 import android.support.test.runner.AndroidJUnit4;
     58 
     59 @RunWith(AndroidJUnit4.class)
     60 public class RecyclerViewLayoutTest extends BaseRecyclerViewInstrumentationTest {
     61     private static final int FLAG_HORIZONTAL = 1;
     62     private static final int FLAG_VERTICAL = 1 << 1;
     63     private static final int FLAG_FLING = 1 << 2;
     64 
     65     private static final boolean DEBUG = true;
     66 
     67     private static final String TAG = "RecyclerViewLayoutTest";
     68 
     69     public RecyclerViewLayoutTest() {
     70         super(DEBUG);
     71     }
     72 
     73     @Before
     74     @Override
     75     public void setUp() throws Exception {
     76         super.setUp();
     77         injectInstrumentation(InstrumentationRegistry.getInstrumentation());
     78     }
     79 
     80     @After
     81     @Override
     82     public void tearDown() throws Exception {
     83         super.tearDown();
     84     }
     85 
     86     @Test
     87     public void testFlingFrozen() throws Throwable {
     88         testScrollFrozen(true);
     89     }
     90 
     91     @Test
     92     public void testDragFrozen() throws Throwable {
     93         testScrollFrozen(false);
     94     }
     95 
     96     private void testScrollFrozen(boolean fling) throws Throwable {
     97         RecyclerView recyclerView = new RecyclerView(getActivity());
     98 
     99         final int horizontalScrollCount = 3;
    100         final int verticalScrollCount = 3;
    101         final int horizontalVelocity = 1000;
    102         final int verticalVelocity = 1000;
    103         final AtomicInteger horizontalCounter = new AtomicInteger(horizontalScrollCount);
    104         final AtomicInteger verticalCounter = new AtomicInteger(verticalScrollCount);
    105         TestLayoutManager tlm = new TestLayoutManager() {
    106             @Override
    107             public boolean canScrollHorizontally() {
    108                 return true;
    109             }
    110 
    111             @Override
    112             public boolean canScrollVertically() {
    113                 return true;
    114             }
    115 
    116             @Override
    117             public void onLayoutChildren(RecyclerView.Recycler recycler, RecyclerView.State state) {
    118                 layoutRange(recycler, 0, 10);
    119                 layoutLatch.countDown();
    120             }
    121 
    122             @Override
    123             public int scrollVerticallyBy(int dy, RecyclerView.Recycler recycler,
    124                     RecyclerView.State state) {
    125                 if (verticalCounter.get() > 0) {
    126                     verticalCounter.decrementAndGet();
    127                     return dy;
    128                 }
    129                 return 0;
    130             }
    131 
    132             @Override
    133             public int scrollHorizontallyBy(int dx, RecyclerView.Recycler recycler,
    134                     RecyclerView.State state) {
    135                 if (horizontalCounter.get() > 0) {
    136                     horizontalCounter.decrementAndGet();
    137                     return dx;
    138                 }
    139                 return 0;
    140             }
    141         };
    142         TestAdapter adapter = new TestAdapter(100);
    143         recyclerView.setAdapter(adapter);
    144         recyclerView.setLayoutManager(tlm);
    145         tlm.expectLayouts(1);
    146         setRecyclerView(recyclerView);
    147         tlm.waitForLayout(2);
    148 
    149         freezeLayout(true);
    150 
    151         if (fling) {
    152             assertFalse("fling should be blocked", fling(horizontalVelocity, verticalVelocity));
    153         } else { // drag
    154             TouchUtils.dragViewTo(this, recyclerView,
    155                     Gravity.LEFT | Gravity.TOP,
    156                     mRecyclerView.getWidth() / 2, mRecyclerView.getHeight() / 2);
    157         }
    158         assertEquals("rv's horizontal scroll cb must not run", horizontalScrollCount,
    159                 horizontalCounter.get());
    160         assertEquals("rv's vertical scroll cb must not run", verticalScrollCount,
    161                 verticalCounter.get());
    162 
    163         freezeLayout(false);
    164 
    165         if (fling) {
    166             assertTrue("fling should be started", fling(horizontalVelocity, verticalVelocity));
    167         } else { // drag
    168             TouchUtils.dragViewTo(this, recyclerView,
    169                     Gravity.LEFT | Gravity.TOP,
    170                     mRecyclerView.getWidth() / 2, mRecyclerView.getHeight() / 2);
    171         }
    172         assertEquals("rv's horizontal scroll cb must finishes", 0, horizontalCounter.get());
    173         assertEquals("rv's vertical scroll cb must finishes", 0, verticalCounter.get());
    174     }
    175 
    176     @Test
    177     public void testFocusSearchFailFrozen() throws Throwable {
    178         RecyclerView recyclerView = new RecyclerView(getActivity());
    179 
    180         final AtomicInteger focusSearchCalled = new AtomicInteger(0);
    181         TestLayoutManager tlm = new TestLayoutManager() {
    182             @Override
    183             public boolean canScrollHorizontally() {
    184                 return true;
    185             }
    186 
    187             @Override
    188             public boolean canScrollVertically() {
    189                 return true;
    190             }
    191 
    192             @Override
    193             public void onLayoutChildren(RecyclerView.Recycler recycler, RecyclerView.State state) {
    194                 layoutRange(recycler, 0, 10);
    195                 layoutLatch.countDown();
    196             }
    197 
    198             @Override
    199             public View onFocusSearchFailed(View focused, int direction, RecyclerView.Recycler recycler,
    200                     RecyclerView.State state) {
    201                 focusSearchCalled.addAndGet(1);
    202                 return null;
    203             }
    204         };
    205         TestAdapter adapter = new TestAdapter(100);
    206         recyclerView.setAdapter(adapter);
    207         recyclerView.setLayoutManager(tlm);
    208         tlm.expectLayouts(1);
    209         setRecyclerView(recyclerView);
    210         tlm.waitForLayout(2);
    211 
    212         final View c = recyclerView.getChildAt(recyclerView.getChildCount() - 1);
    213         runTestOnUiThread(new Runnable() {
    214             @Override
    215             public void run() {
    216                 c.requestFocus();
    217             }
    218         });
    219         assertTrue(c.hasFocus());
    220 
    221         freezeLayout(true);
    222         sendKeys(KeyEvent.KEYCODE_DPAD_DOWN);
    223         assertEquals("onFocusSearchFailed should not be called when layout is frozen",
    224                 0, focusSearchCalled.get());
    225 
    226         freezeLayout(false);
    227         sendKeys(KeyEvent.KEYCODE_DPAD_DOWN);
    228         assertEquals(1, focusSearchCalled.get());
    229     }
    230 
    231     @Test
    232     public void testFrozenAndChangeAdapter() throws Throwable {
    233         RecyclerView recyclerView = new RecyclerView(getActivity());
    234 
    235         final AtomicInteger focusSearchCalled = new AtomicInteger(0);
    236         TestLayoutManager tlm = new TestLayoutManager() {
    237             @Override
    238             public boolean canScrollHorizontally() {
    239                 return true;
    240             }
    241 
    242             @Override
    243             public boolean canScrollVertically() {
    244                 return true;
    245             }
    246 
    247             @Override
    248             public void onLayoutChildren(RecyclerView.Recycler recycler, RecyclerView.State state) {
    249                 layoutRange(recycler, 0, 10);
    250                 layoutLatch.countDown();
    251             }
    252 
    253             @Override
    254             public View onFocusSearchFailed(View focused, int direction, RecyclerView.Recycler recycler,
    255                     RecyclerView.State state) {
    256                 focusSearchCalled.addAndGet(1);
    257                 return null;
    258             }
    259         };
    260         TestAdapter adapter = new TestAdapter(100);
    261         recyclerView.setAdapter(adapter);
    262         recyclerView.setLayoutManager(tlm);
    263         tlm.expectLayouts(1);
    264         setRecyclerView(recyclerView);
    265         tlm.waitForLayout(2);
    266 
    267         freezeLayout(true);
    268         TestAdapter adapter2 = new TestAdapter(1000);
    269         setAdapter(adapter2);
    270         assertFalse(recyclerView.isLayoutFrozen());
    271         assertSame(adapter2, recyclerView.getAdapter());
    272 
    273         freezeLayout(true);
    274         TestAdapter adapter3 = new TestAdapter(1000);
    275         swapAdapter(adapter3, true);
    276         assertFalse(recyclerView.isLayoutFrozen());
    277         assertSame(adapter3, recyclerView.getAdapter());
    278     }
    279 
    280     @Test
    281     public void testScrollToPositionCallback() throws Throwable {
    282         RecyclerView recyclerView = new RecyclerView(getActivity());
    283         TestLayoutManager tlm = new TestLayoutManager() {
    284             int scrollPos = RecyclerView.NO_POSITION;
    285 
    286             @Override
    287             public void onLayoutChildren(RecyclerView.Recycler recycler, RecyclerView.State state) {
    288                 layoutLatch.countDown();
    289                 if (scrollPos == RecyclerView.NO_POSITION) {
    290                     layoutRange(recycler, 0, 10);
    291                 } else {
    292                     layoutRange(recycler, scrollPos, scrollPos + 10);
    293                 }
    294             }
    295 
    296             @Override
    297             public void scrollToPosition(int position) {
    298                 scrollPos = position;
    299                 requestLayout();
    300             }
    301         };
    302         recyclerView.setLayoutManager(tlm);
    303         TestAdapter adapter = new TestAdapter(100);
    304         recyclerView.setAdapter(adapter);
    305         final AtomicInteger rvCounter = new AtomicInteger(0);
    306         final AtomicInteger viewGroupCounter = new AtomicInteger(0);
    307         recyclerView.getViewTreeObserver().addOnScrollChangedListener(
    308                 new ViewTreeObserver.OnScrollChangedListener() {
    309                     @Override
    310                     public void onScrollChanged() {
    311                         viewGroupCounter.incrementAndGet();
    312                     }
    313                 });
    314         recyclerView.setOnScrollListener(new RecyclerView.OnScrollListener() {
    315             @Override
    316             public void onScrolled(RecyclerView recyclerView, int dx, int dy) {
    317                 rvCounter.incrementAndGet();
    318                 super.onScrolled(recyclerView, dx, dy);
    319             }
    320         });
    321         tlm.expectLayouts(1);
    322 
    323         setRecyclerView(recyclerView);
    324         tlm.waitForLayout(2);
    325         // wait for draw :/
    326         Thread.sleep(1000);
    327 
    328         assertEquals("RV on scroll should be called for initialization", 1, rvCounter.get());
    329         assertEquals("VTO on scroll should be called for initialization", 1,
    330                 viewGroupCounter.get());
    331         tlm.expectLayouts(1);
    332         freezeLayout(true);
    333         scrollToPosition(3);
    334         tlm.assertNoLayout("scrollToPosition should be ignored", 2);
    335         freezeLayout(false);
    336         scrollToPosition(3);
    337         tlm.waitForLayout(2);
    338         assertEquals("RV on scroll should be called", 2, rvCounter.get());
    339         assertEquals("VTO on scroll should be called", 2, viewGroupCounter.get());
    340         tlm.expectLayouts(1);
    341         requestLayoutOnUIThread(recyclerView);
    342         tlm.waitForLayout(2);
    343         // wait for draw :/
    344         Thread.sleep(1000);
    345         assertEquals("on scroll should NOT be called", 2, rvCounter.get());
    346         assertEquals("on scroll should NOT be called", 2, viewGroupCounter.get());
    347 
    348     }
    349 
    350     @Test
    351     public void testScrollInBothDirectionEqual() throws Throwable {
    352         scrollInBothDirection(3, 3, 1000, 1000);
    353     }
    354 
    355     @Test
    356     public void testScrollInBothDirectionMoreVertical() throws Throwable {
    357         scrollInBothDirection(2, 3, 1000, 1000);
    358     }
    359 
    360     @Test
    361     public void testScrollInBothDirectionMoreHorizontal() throws Throwable {
    362         scrollInBothDirection(3, 2, 1000, 1000);
    363     }
    364 
    365     @Test
    366     public void testScrollHorizontalOnly() throws Throwable {
    367         scrollInBothDirection(3, 0, 1000, 0);
    368     }
    369 
    370     @Test
    371     public void testScrollVerticalOnly() throws Throwable {
    372         scrollInBothDirection(0, 3, 0, 1000);
    373     }
    374 
    375     @Test
    376     public void testScrollInBothDirectionEqualReverse() throws Throwable {
    377         scrollInBothDirection(3, 3, -1000, -1000);
    378     }
    379 
    380     @Test
    381     public void testScrollInBothDirectionMoreVerticalReverse() throws Throwable {
    382         scrollInBothDirection(2, 3, -1000, -1000);
    383     }
    384 
    385     @Test
    386     public void testScrollInBothDirectionMoreHorizontalReverse() throws Throwable {
    387         scrollInBothDirection(3, 2, -1000, -1000);
    388     }
    389 
    390     @Test
    391     public void testScrollHorizontalOnlyReverse() throws Throwable {
    392         scrollInBothDirection(3, 0, -1000, 0);
    393     }
    394 
    395     @Test
    396     public void testScrollVerticalOnlyReverse() throws Throwable {
    397         scrollInBothDirection(0, 3, 0, -1000);
    398     }
    399 
    400     public void scrollInBothDirection(int horizontalScrollCount, int verticalScrollCount,
    401             int horizontalVelocity, int verticalVelocity)
    402             throws Throwable {
    403         RecyclerView recyclerView = new RecyclerView(getActivity());
    404         final AtomicInteger horizontalCounter = new AtomicInteger(horizontalScrollCount);
    405         final AtomicInteger verticalCounter = new AtomicInteger(verticalScrollCount);
    406         TestLayoutManager tlm = new TestLayoutManager() {
    407             @Override
    408             public boolean canScrollHorizontally() {
    409                 return true;
    410             }
    411 
    412             @Override
    413             public boolean canScrollVertically() {
    414                 return true;
    415             }
    416 
    417             @Override
    418             public void onLayoutChildren(RecyclerView.Recycler recycler, RecyclerView.State state) {
    419                 layoutRange(recycler, 0, 10);
    420                 layoutLatch.countDown();
    421             }
    422 
    423             @Override
    424             public int scrollVerticallyBy(int dy, RecyclerView.Recycler recycler,
    425                     RecyclerView.State state) {
    426                 if (verticalCounter.get() > 0) {
    427                     verticalCounter.decrementAndGet();
    428                     return dy;
    429                 }
    430                 return 0;
    431             }
    432 
    433             @Override
    434             public int scrollHorizontallyBy(int dx, RecyclerView.Recycler recycler,
    435                     RecyclerView.State state) {
    436                 if (horizontalCounter.get() > 0) {
    437                     horizontalCounter.decrementAndGet();
    438                     return dx;
    439                 }
    440                 return 0;
    441             }
    442         };
    443         TestAdapter adapter = new TestAdapter(100);
    444         recyclerView.setAdapter(adapter);
    445         recyclerView.setLayoutManager(tlm);
    446         tlm.expectLayouts(1);
    447         setRecyclerView(recyclerView);
    448         tlm.waitForLayout(2);
    449         assertTrue("test sanity, fling must run", fling(horizontalVelocity, verticalVelocity));
    450         assertEquals("rv's horizontal scroll cb must run " + horizontalScrollCount + " times'", 0,
    451                 horizontalCounter.get());
    452         assertEquals("rv's vertical scroll cb must run " + verticalScrollCount + " times'", 0,
    453                 verticalCounter.get());
    454     }
    455 
    456     @Test
    457     public void testDragHorizontal() throws Throwable {
    458         scrollInOtherOrientationTest(FLAG_HORIZONTAL);
    459     }
    460 
    461     @Test
    462     public void testDragVertical() throws Throwable {
    463         scrollInOtherOrientationTest(FLAG_VERTICAL);
    464     }
    465 
    466     @Test
    467     public void testFlingHorizontal() throws Throwable {
    468         scrollInOtherOrientationTest(FLAG_HORIZONTAL | FLAG_FLING);
    469     }
    470 
    471     @Test
    472     public void testFlingVertical() throws Throwable {
    473         scrollInOtherOrientationTest(FLAG_VERTICAL | FLAG_FLING);
    474     }
    475 
    476     @Test
    477     public void testNestedDragVertical() throws Throwable {
    478         TestedFrameLayout tfl = getActivity().mContainer;
    479         tfl.setNestedScrollMode(TestedFrameLayout.TEST_NESTED_SCROLL_MODE_CONSUME);
    480         tfl.setNestedFlingMode(TestedFrameLayout.TEST_NESTED_SCROLL_MODE_CONSUME);
    481         scrollInOtherOrientationTest(FLAG_VERTICAL, 0);
    482     }
    483 
    484     @Test
    485     public void testNestedDragHorizontal() throws Throwable {
    486         TestedFrameLayout tfl = getActivity().mContainer;
    487         tfl.setNestedScrollMode(TestedFrameLayout.TEST_NESTED_SCROLL_MODE_CONSUME);
    488         tfl.setNestedFlingMode(TestedFrameLayout.TEST_NESTED_SCROLL_MODE_CONSUME);
    489         scrollInOtherOrientationTest(FLAG_HORIZONTAL, 0);
    490     }
    491 
    492     @Test
    493     public void testNestedDragHorizontalCallsStopNestedScroll() throws Throwable {
    494         TestedFrameLayout tfl = getActivity().mContainer;
    495         tfl.setNestedScrollMode(TestedFrameLayout.TEST_NESTED_SCROLL_MODE_CONSUME);
    496         tfl.setNestedFlingMode(TestedFrameLayout.TEST_NESTED_SCROLL_MODE_CONSUME);
    497         scrollInOtherOrientationTest(FLAG_HORIZONTAL, 0);
    498         assertTrue("onStopNestedScroll called", tfl.stopNestedScrollCalled());
    499     }
    500 
    501     @Test
    502     public void testNestedDragVerticalCallsStopNestedScroll() throws Throwable {
    503         TestedFrameLayout tfl = getActivity().mContainer;
    504         tfl.setNestedScrollMode(TestedFrameLayout.TEST_NESTED_SCROLL_MODE_CONSUME);
    505         tfl.setNestedFlingMode(TestedFrameLayout.TEST_NESTED_SCROLL_MODE_CONSUME);
    506         scrollInOtherOrientationTest(FLAG_VERTICAL, 0);
    507         assertTrue("onStopNestedScroll called", tfl.stopNestedScrollCalled());
    508     }
    509 
    510     private void scrollInOtherOrientationTest(int flags)
    511             throws Throwable {
    512         scrollInOtherOrientationTest(flags, flags);
    513     }
    514 
    515     private void scrollInOtherOrientationTest(final int flags, int expectedFlags) throws Throwable {
    516         RecyclerView recyclerView = new RecyclerView(getActivity());
    517         final AtomicBoolean scrolledHorizontal = new AtomicBoolean(false);
    518         final AtomicBoolean scrolledVertical = new AtomicBoolean(false);
    519 
    520         final TestLayoutManager tlm = new TestLayoutManager() {
    521             @Override
    522             public boolean canScrollHorizontally() {
    523                 return (flags & FLAG_HORIZONTAL) != 0;
    524             }
    525 
    526             @Override
    527             public boolean canScrollVertically() {
    528                 return (flags & FLAG_VERTICAL) != 0;
    529             }
    530 
    531             @Override
    532             public void onLayoutChildren(RecyclerView.Recycler recycler, RecyclerView.State state) {
    533                 layoutRange(recycler, 0, 10);
    534                 layoutLatch.countDown();
    535             }
    536 
    537             @Override
    538             public int scrollVerticallyBy(int dy, RecyclerView.Recycler recycler,
    539                     RecyclerView.State state) {
    540                 scrolledVertical.set(true);
    541                 return super.scrollVerticallyBy(dy, recycler, state);
    542             }
    543 
    544             @Override
    545             public int scrollHorizontallyBy(int dx, RecyclerView.Recycler recycler,
    546                     RecyclerView.State state) {
    547                 scrolledHorizontal.set(true);
    548                 return super.scrollHorizontallyBy(dx, recycler, state);
    549             }
    550         };
    551         TestAdapter adapter = new TestAdapter(100);
    552         recyclerView.setAdapter(adapter);
    553         recyclerView.setLayoutManager(tlm);
    554         tlm.expectLayouts(1);
    555         setRecyclerView(recyclerView);
    556         tlm.waitForLayout(2);
    557         if ( (flags & FLAG_FLING) != 0 ) {
    558             int flingVelocity = (mRecyclerView.getMaxFlingVelocity() +
    559                     mRecyclerView.getMinFlingVelocity()) / 2;
    560             assertEquals("fling started", (expectedFlags & FLAG_FLING) != 0,
    561                     fling(flingVelocity, flingVelocity));
    562         } else { // drag
    563             TouchUtils.dragViewTo(this, recyclerView, Gravity.LEFT | Gravity.TOP,
    564                     mRecyclerView.getWidth() / 2, mRecyclerView.getHeight() / 2);
    565         }
    566         assertEquals("horizontally scrolled: " + tlm.mScrollHorizontallyAmount,
    567                 (expectedFlags & FLAG_HORIZONTAL) != 0, scrolledHorizontal.get());
    568         assertEquals("vertically scrolled: " + tlm.mScrollVerticallyAmount,
    569                 (expectedFlags & FLAG_VERTICAL) != 0, scrolledVertical.get());
    570     }
    571 
    572     private boolean fling(final int velocityX, final int velocityY) throws Throwable {
    573         final AtomicBoolean didStart = new AtomicBoolean(false);
    574         runTestOnUiThread(new Runnable() {
    575             @Override
    576             public void run() {
    577                 boolean result = mRecyclerView.fling(velocityX, velocityY);
    578                 didStart.set(result);
    579             }
    580         });
    581         if (!didStart.get()) {
    582             return false;
    583         }
    584         // cannot set scroll listener in case it is subject to some test so instead doing a busy
    585         // loop until state goes idle
    586         while (mRecyclerView.getScrollState() != SCROLL_STATE_IDLE) {
    587             getInstrumentation().waitForIdleSync();
    588         }
    589         return true;
    590     }
    591 
    592     private void assertPendingUpdatesAndLayout(TestLayoutManager testLayoutManager,
    593             final Runnable runnable) throws Throwable {
    594         testLayoutManager.expectLayouts(1);
    595         runTestOnUiThread(new Runnable() {
    596             @Override
    597             public void run() {
    598                 runnable.run();
    599                 assertTrue(mRecyclerView.hasPendingAdapterUpdates());
    600             }
    601         });
    602         testLayoutManager.waitForLayout(1);
    603         assertFalse(mRecyclerView.hasPendingAdapterUpdates());
    604     }
    605 
    606     private void setupBasic(RecyclerView recyclerView, TestLayoutManager tlm,
    607             TestAdapter adapter, boolean waitForFirstLayout) throws Throwable {
    608         recyclerView.setLayoutManager(tlm);
    609         recyclerView.setAdapter(adapter);
    610         if (waitForFirstLayout) {
    611             tlm.expectLayouts(1);
    612             setRecyclerView(recyclerView);
    613             tlm.waitForLayout(1);
    614         } else {
    615             setRecyclerView(recyclerView);
    616         }
    617     }
    618 
    619     @Test
    620     public void testHasPendingUpdatesBeforeFirstLayout() throws Throwable {
    621         RecyclerView recyclerView = new RecyclerView(getActivity());
    622         TestLayoutManager layoutManager = new DumbLayoutManager();
    623         TestAdapter testAdapter = new TestAdapter(10);
    624         setupBasic(recyclerView, layoutManager, testAdapter, false);
    625         assertTrue(mRecyclerView.hasPendingAdapterUpdates());
    626     }
    627 
    628     @Test
    629     public void testNoPendingUpdatesAfterLayout() throws Throwable {
    630         RecyclerView recyclerView = new RecyclerView(getActivity());
    631         TestLayoutManager layoutManager = new DumbLayoutManager();
    632         TestAdapter testAdapter = new TestAdapter(10);
    633         setupBasic(recyclerView, layoutManager, testAdapter, true);
    634         assertFalse(mRecyclerView.hasPendingAdapterUpdates());
    635     }
    636 
    637     @Test
    638     public void testHasPendingUpdatesWhenAdapterIsChanged() throws Throwable {
    639         RecyclerView recyclerView = new RecyclerView(getActivity());
    640         TestLayoutManager layoutManager = new DumbLayoutManager();
    641         final TestAdapter testAdapter = new TestAdapter(10);
    642         setupBasic(recyclerView, layoutManager, testAdapter, false);
    643         assertPendingUpdatesAndLayout(layoutManager, new Runnable() {
    644             @Override
    645             public void run() {
    646                 testAdapter.notifyItemRemoved(1);
    647             }
    648         });
    649         assertPendingUpdatesAndLayout(layoutManager, new Runnable() {
    650             @Override
    651             public void run() {
    652                 testAdapter.notifyItemInserted(2);
    653             }
    654         });
    655 
    656         assertPendingUpdatesAndLayout(layoutManager, new Runnable() {
    657             @Override
    658             public void run() {
    659                 testAdapter.notifyItemMoved(2, 3);
    660             }
    661         });
    662 
    663         assertPendingUpdatesAndLayout(layoutManager, new Runnable() {
    664             @Override
    665             public void run() {
    666                 testAdapter.notifyItemChanged(2);
    667             }
    668         });
    669 
    670         assertPendingUpdatesAndLayout(layoutManager, new Runnable() {
    671             @Override
    672             public void run() {
    673                 testAdapter.notifyDataSetChanged();
    674             }
    675         });
    676     }
    677 
    678     @Test
    679     public void testTransientStateRecycleViaAdapter() throws Throwable {
    680         transientStateRecycleTest(true, false);
    681     }
    682 
    683     @Test
    684     public void testTransientStateRecycleViaTransientStateCleanup() throws Throwable {
    685         transientStateRecycleTest(false, true);
    686     }
    687 
    688     @Test
    689     public void testTransientStateDontRecycle() throws Throwable {
    690         transientStateRecycleTest(false, false);
    691     }
    692 
    693     public void transientStateRecycleTest(final boolean succeed, final boolean unsetTransientState)
    694             throws Throwable {
    695         final List<View> failedToRecycle = new ArrayList<View>();
    696         final List<View> recycled = new ArrayList<View>();
    697         TestAdapter testAdapter = new TestAdapter(10) {
    698             @Override
    699             public boolean onFailedToRecycleView(
    700                     TestViewHolder holder) {
    701                 failedToRecycle.add(holder.itemView);
    702                 if (unsetTransientState) {
    703                     setHasTransientState(holder.itemView, false);
    704                 }
    705                 return succeed;
    706             }
    707 
    708             @Override
    709             public void onViewRecycled(TestViewHolder holder) {
    710                 recycled.add(holder.itemView);
    711                 super.onViewRecycled(holder);
    712             }
    713         };
    714         TestLayoutManager tlm = new TestLayoutManager() {
    715             @Override
    716             public void onLayoutChildren(RecyclerView.Recycler recycler, RecyclerView.State state) {
    717                 if (getChildCount() == 0) {
    718                     detachAndScrapAttachedViews(recycler);
    719                     layoutRange(recycler, 0, 5);
    720                 } else {
    721                     removeAndRecycleAllViews(recycler);
    722                 }
    723                 if (layoutLatch != null) {
    724                     layoutLatch.countDown();
    725                 }
    726             }
    727         };
    728         RecyclerView recyclerView = new RecyclerView(getActivity());
    729         recyclerView.setAdapter(testAdapter);
    730         recyclerView.setLayoutManager(tlm);
    731         recyclerView.setItemAnimator(null);
    732         setRecyclerView(recyclerView);
    733         getInstrumentation().waitForIdleSync();
    734         // make sure we have enough views after this position so that we'll receive the on recycled
    735         // callback
    736         View view = recyclerView.getChildAt(3);//this has to be greater than def cache size.
    737         setHasTransientState(view, true);
    738         tlm.expectLayouts(1);
    739         requestLayoutOnUIThread(recyclerView);
    740         tlm.waitForLayout(2);
    741 
    742         assertTrue(failedToRecycle.contains(view));
    743         assertEquals(succeed || unsetTransientState, recycled.contains(view));
    744     }
    745 
    746     @Test
    747     public void testAdapterPositionInvalidation() throws Throwable {
    748         final RecyclerView recyclerView = new RecyclerView(getActivity());
    749         final TestAdapter adapter = new TestAdapter(10);
    750         final TestLayoutManager tlm = new TestLayoutManager() {
    751             @Override
    752             public void onLayoutChildren(RecyclerView.Recycler recycler, RecyclerView.State state) {
    753                 layoutRange(recycler, 0, state.getItemCount());
    754                 layoutLatch.countDown();
    755             }
    756         };
    757         recyclerView.setAdapter(adapter);
    758         recyclerView.setLayoutManager(tlm);
    759         tlm.expectLayouts(1);
    760         setRecyclerView(recyclerView);
    761         tlm.waitForLayout(1);
    762         runTestOnUiThread(new Runnable() {
    763             @Override
    764             public void run() {
    765                 for (int i = 0; i < tlm.getChildCount(); i++) {
    766                     assertNotSame("adapter positions should not be undefined",
    767                             recyclerView.getChildAdapterPosition(tlm.getChildAt(i)),
    768                             RecyclerView.NO_POSITION);
    769                 }
    770                 adapter.notifyDataSetChanged();
    771                 for (int i = 0; i < tlm.getChildCount(); i++) {
    772                     assertSame("adapter positions should be undefined",
    773                             recyclerView.getChildAdapterPosition(tlm.getChildAt(i)),
    774                             RecyclerView.NO_POSITION);
    775                 }
    776             }
    777         });
    778     }
    779 
    780     @Test
    781     public void testAdapterPositionsBasic() throws Throwable {
    782         adapterPositionsTest(null);
    783     }
    784 
    785     @Test
    786     public void testAdapterPositionsRemoveItems() throws Throwable {
    787         adapterPositionsTest(new AdapterRunnable() {
    788             @Override
    789             public void run(TestAdapter adapter) throws Throwable {
    790                 adapter.deleteAndNotify(3, 4);
    791             }
    792         });
    793     }
    794 
    795     @Test
    796     public void testAdapterPositionsRemoveItemsBefore() throws Throwable {
    797         adapterPositionsTest(new AdapterRunnable() {
    798             @Override
    799             public void run(TestAdapter adapter) throws Throwable {
    800                 adapter.deleteAndNotify(0, 1);
    801             }
    802         });
    803     }
    804 
    805     @Test
    806     public void testAdapterPositionsAddItemsBefore() throws Throwable {
    807         adapterPositionsTest(new AdapterRunnable() {
    808             @Override
    809             public void run(TestAdapter adapter) throws Throwable {
    810                 adapter.addAndNotify(0, 5);
    811             }
    812         });
    813     }
    814 
    815     @Test
    816     public void testAdapterPositionsAddItemsInside() throws Throwable {
    817         adapterPositionsTest(new AdapterRunnable() {
    818             @Override
    819             public void run(TestAdapter adapter) throws Throwable {
    820                 adapter.addAndNotify(3, 2);
    821             }
    822         });
    823     }
    824 
    825     @Test
    826     public void testAdapterPositionsMoveItems() throws Throwable {
    827         adapterPositionsTest(new AdapterRunnable() {
    828             @Override
    829             public void run(TestAdapter adapter) throws Throwable {
    830                 adapter.moveAndNotify(3, 5);
    831             }
    832         });
    833     }
    834 
    835     @Test
    836     public void testAdapterPositionsNotifyDataSetChanged() throws Throwable {
    837         adapterPositionsTest(new AdapterRunnable() {
    838             @Override
    839             public void run(TestAdapter adapter) throws Throwable {
    840                 adapter.mItems.clear();
    841                 for (int i = 0; i < 20; i++) {
    842                     adapter.mItems.add(new Item(i, "added item"));
    843                 }
    844                 adapter.notifyDataSetChanged();
    845             }
    846         });
    847     }
    848 
    849     @Test
    850     public void testAvoidLeakingRecyclerViewIfViewIsNotRecycled() throws Throwable {
    851         final AtomicBoolean failedToRecycle = new AtomicBoolean(false);
    852         RecyclerView rv = new RecyclerView(getActivity());
    853         TestLayoutManager tlm = new TestLayoutManager() {
    854             @Override
    855             public void onLayoutChildren(RecyclerView.Recycler recycler, RecyclerView.State state) {
    856                 detachAndScrapAttachedViews(recycler);
    857                 layoutRange(recycler, 0, state.getItemCount());
    858                 layoutLatch.countDown();
    859             }
    860         };
    861         TestAdapter adapter = new TestAdapter(10) {
    862             @Override
    863             public boolean onFailedToRecycleView(
    864                     TestViewHolder holder) {
    865                 failedToRecycle.set(true);
    866                 return false;
    867             }
    868         };
    869         rv.setAdapter(adapter);
    870         rv.setLayoutManager(tlm);
    871         tlm.expectLayouts(1);
    872         setRecyclerView(rv);
    873         tlm.waitForLayout(1);
    874         final RecyclerView.ViewHolder vh = rv.getChildViewHolder(rv.getChildAt(0));
    875         runTestOnUiThread(new Runnable() {
    876             @Override
    877             public void run() {
    878                 ViewCompat.setHasTransientState(vh.itemView, true);
    879             }
    880         });
    881         tlm.expectLayouts(1);
    882         adapter.deleteAndNotify(0, 10);
    883         tlm.waitForLayout(2);
    884         final CountDownLatch animationsLatch = new CountDownLatch(1);
    885         rv.getItemAnimator().isRunning(
    886                 new RecyclerView.ItemAnimator.ItemAnimatorFinishedListener() {
    887                     @Override
    888                     public void onAnimationsFinished() {
    889                         animationsLatch.countDown();
    890                     }
    891                 });
    892         assertTrue(animationsLatch.await(2, TimeUnit.SECONDS));
    893         assertTrue(failedToRecycle.get());
    894         assertNull(vh.mOwnerRecyclerView);
    895         checkForMainThreadException();
    896     }
    897 
    898     @Test
    899     public void testAvoidLeakingRecyclerViewViaViewHolder() throws Throwable {
    900         RecyclerView rv = new RecyclerView(getActivity());
    901         TestLayoutManager tlm = new TestLayoutManager() {
    902             @Override
    903             public void onLayoutChildren(RecyclerView.Recycler recycler, RecyclerView.State state) {
    904                 detachAndScrapAttachedViews(recycler);
    905                 layoutRange(recycler, 0, state.getItemCount());
    906                 layoutLatch.countDown();
    907             }
    908         };
    909         TestAdapter adapter = new TestAdapter(10);
    910         rv.setAdapter(adapter);
    911         rv.setLayoutManager(tlm);
    912         tlm.expectLayouts(1);
    913         setRecyclerView(rv);
    914         tlm.waitForLayout(1);
    915         final RecyclerView.ViewHolder vh = rv.getChildViewHolder(rv.getChildAt(0));
    916         tlm.expectLayouts(1);
    917         adapter.deleteAndNotify(0, 10);
    918         tlm.waitForLayout(2);
    919         final CountDownLatch animationsLatch = new CountDownLatch(1);
    920         rv.getItemAnimator().isRunning(
    921                 new RecyclerView.ItemAnimator.ItemAnimatorFinishedListener() {
    922                     @Override
    923                     public void onAnimationsFinished() {
    924                         animationsLatch.countDown();
    925                     }
    926                 });
    927         assertTrue(animationsLatch.await(2, TimeUnit.SECONDS));
    928         assertNull(vh.mOwnerRecyclerView);
    929         checkForMainThreadException();
    930     }
    931 
    932     public void adapterPositionsTest(final AdapterRunnable adapterChanges) throws Throwable {
    933         final TestAdapter testAdapter = new TestAdapter(10);
    934         TestLayoutManager tlm = new TestLayoutManager() {
    935             @Override
    936             public void onLayoutChildren(RecyclerView.Recycler recycler, RecyclerView.State state) {
    937                 try {
    938                     layoutRange(recycler, Math.min(state.getItemCount(), 2)
    939                             , Math.min(state.getItemCount(), 7));
    940                     layoutLatch.countDown();
    941                 } catch (Throwable t) {
    942                     postExceptionToInstrumentation(t);
    943                 }
    944             }
    945         };
    946         final RecyclerView recyclerView = new RecyclerView(getActivity());
    947         recyclerView.setLayoutManager(tlm);
    948         recyclerView.setAdapter(testAdapter);
    949         tlm.expectLayouts(1);
    950         setRecyclerView(recyclerView);
    951         tlm.waitForLayout(1);
    952         runTestOnUiThread(new Runnable() {
    953             @Override
    954             public void run() {
    955                 try {
    956                     final int count = recyclerView.getChildCount();
    957                     Map<View, Integer> layoutPositions = new HashMap<View, Integer>();
    958                     assertTrue("test sanity", count > 0);
    959                     for (int i = 0; i < count; i++) {
    960                         View view = recyclerView.getChildAt(i);
    961                         TestViewHolder vh = (TestViewHolder) recyclerView.getChildViewHolder(view);
    962                         int index = testAdapter.mItems.indexOf(vh.mBoundItem);
    963                         assertEquals("should be able to find VH with adapter position " + index, vh,
    964                                 recyclerView.findViewHolderForAdapterPosition(index));
    965                         assertEquals("get adapter position should return correct index", index,
    966                                 vh.getAdapterPosition());
    967                         layoutPositions.put(view, vh.mPosition);
    968                     }
    969                     if (adapterChanges != null) {
    970                         adapterChanges.run(testAdapter);
    971                         for (int i = 0; i < count; i++) {
    972                             View view = recyclerView.getChildAt(i);
    973                             TestViewHolder vh = (TestViewHolder) recyclerView
    974                                     .getChildViewHolder(view);
    975                             int index = testAdapter.mItems.indexOf(vh.mBoundItem);
    976                             if (index >= 0) {
    977                                 assertEquals("should be able to find VH with adapter position "
    978                                                 + index, vh,
    979                                         recyclerView.findViewHolderForAdapterPosition(index));
    980                             }
    981                             assertSame("get adapter position should return correct index", index,
    982                                     vh.getAdapterPosition());
    983                             assertSame("should be able to find view with layout position",
    984                                     vh, mRecyclerView.findViewHolderForLayoutPosition(
    985                                             layoutPositions.get(view)));
    986                         }
    987 
    988                     }
    989 
    990                 } catch (Throwable t) {
    991                     postExceptionToInstrumentation(t);
    992                 }
    993             }
    994         });
    995         checkForMainThreadException();
    996     }
    997 
    998     @Test
    999     public void testScrollStateForSmoothScroll() throws Throwable {
   1000         TestAdapter testAdapter = new TestAdapter(10);
   1001         TestLayoutManager tlm = new TestLayoutManager();
   1002         RecyclerView recyclerView = new RecyclerView(getActivity());
   1003         recyclerView.setAdapter(testAdapter);
   1004         recyclerView.setLayoutManager(tlm);
   1005         setRecyclerView(recyclerView);
   1006         getInstrumentation().waitForIdleSync();
   1007         assertEquals(SCROLL_STATE_IDLE, recyclerView.getScrollState());
   1008         final int[] stateCnts = new int[10];
   1009         final CountDownLatch latch = new CountDownLatch(2);
   1010         recyclerView.setOnScrollListener(new RecyclerView.OnScrollListener() {
   1011             @Override
   1012             public void onScrollStateChanged(RecyclerView recyclerView, int newState) {
   1013                 stateCnts[newState] = stateCnts[newState] + 1;
   1014                 latch.countDown();
   1015             }
   1016         });
   1017         runTestOnUiThread(new Runnable() {
   1018             @Override
   1019             public void run() {
   1020                 mRecyclerView.smoothScrollBy(0, 500);
   1021             }
   1022         });
   1023         latch.await(5, TimeUnit.SECONDS);
   1024         assertEquals(1, stateCnts[SCROLL_STATE_SETTLING]);
   1025         assertEquals(1, stateCnts[SCROLL_STATE_IDLE]);
   1026         assertEquals(0, stateCnts[SCROLL_STATE_DRAGGING]);
   1027     }
   1028 
   1029     @Test
   1030     public void testScrollStateForSmoothScrollWithStop() throws Throwable {
   1031         TestAdapter testAdapter = new TestAdapter(10);
   1032         TestLayoutManager tlm = new TestLayoutManager();
   1033         RecyclerView recyclerView = new RecyclerView(getActivity());
   1034         recyclerView.setAdapter(testAdapter);
   1035         recyclerView.setLayoutManager(tlm);
   1036         setRecyclerView(recyclerView);
   1037         getInstrumentation().waitForIdleSync();
   1038         assertEquals(SCROLL_STATE_IDLE, recyclerView.getScrollState());
   1039         final int[] stateCnts = new int[10];
   1040         final CountDownLatch latch = new CountDownLatch(1);
   1041         recyclerView.setOnScrollListener(new RecyclerView.OnScrollListener() {
   1042             @Override
   1043             public void onScrollStateChanged(RecyclerView recyclerView, int newState) {
   1044                 stateCnts[newState] = stateCnts[newState] + 1;
   1045                 latch.countDown();
   1046             }
   1047         });
   1048         runTestOnUiThread(new Runnable() {
   1049             @Override
   1050             public void run() {
   1051                 mRecyclerView.smoothScrollBy(0, 500);
   1052             }
   1053         });
   1054         latch.await(5, TimeUnit.SECONDS);
   1055         runTestOnUiThread(new Runnable() {
   1056             @Override
   1057             public void run() {
   1058                 mRecyclerView.stopScroll();
   1059             }
   1060         });
   1061         assertEquals(SCROLL_STATE_IDLE, recyclerView.getScrollState());
   1062         assertEquals(1, stateCnts[SCROLL_STATE_SETTLING]);
   1063         assertEquals(1, stateCnts[SCROLL_STATE_IDLE]);
   1064         assertEquals(0, stateCnts[SCROLL_STATE_DRAGGING]);
   1065     }
   1066 
   1067     @Test
   1068     public void testScrollStateForFling() throws Throwable {
   1069         TestAdapter testAdapter = new TestAdapter(10);
   1070         TestLayoutManager tlm = new TestLayoutManager();
   1071         RecyclerView recyclerView = new RecyclerView(getActivity());
   1072         recyclerView.setAdapter(testAdapter);
   1073         recyclerView.setLayoutManager(tlm);
   1074         setRecyclerView(recyclerView);
   1075         getInstrumentation().waitForIdleSync();
   1076         assertEquals(SCROLL_STATE_IDLE, recyclerView.getScrollState());
   1077         final int[] stateCnts = new int[10];
   1078         final CountDownLatch latch = new CountDownLatch(2);
   1079         recyclerView.setOnScrollListener(new RecyclerView.OnScrollListener() {
   1080             @Override
   1081             public void onScrollStateChanged(RecyclerView recyclerView, int newState) {
   1082                 stateCnts[newState] = stateCnts[newState] + 1;
   1083                 latch.countDown();
   1084             }
   1085         });
   1086         final ViewConfiguration vc = ViewConfiguration.get(getActivity());
   1087         final float fling = vc.getScaledMinimumFlingVelocity()
   1088                 + (vc.getScaledMaximumFlingVelocity() - vc.getScaledMinimumFlingVelocity()) * .1f;
   1089         runTestOnUiThread(new Runnable() {
   1090             @Override
   1091             public void run() {
   1092                 mRecyclerView.fling(0, Math.round(fling));
   1093             }
   1094         });
   1095         latch.await(5, TimeUnit.SECONDS);
   1096         assertEquals(1, stateCnts[SCROLL_STATE_SETTLING]);
   1097         assertEquals(1, stateCnts[SCROLL_STATE_IDLE]);
   1098         assertEquals(0, stateCnts[SCROLL_STATE_DRAGGING]);
   1099     }
   1100 
   1101     @Test
   1102     public void testScrollStateForFlingWithStop() throws Throwable {
   1103         TestAdapter testAdapter = new TestAdapter(10);
   1104         TestLayoutManager tlm = new TestLayoutManager();
   1105         RecyclerView recyclerView = new RecyclerView(getActivity());
   1106         recyclerView.setAdapter(testAdapter);
   1107         recyclerView.setLayoutManager(tlm);
   1108         setRecyclerView(recyclerView);
   1109         getInstrumentation().waitForIdleSync();
   1110         assertEquals(SCROLL_STATE_IDLE, recyclerView.getScrollState());
   1111         final int[] stateCnts = new int[10];
   1112         final CountDownLatch latch = new CountDownLatch(1);
   1113         recyclerView.setOnScrollListener(new RecyclerView.OnScrollListener() {
   1114             @Override
   1115             public void onScrollStateChanged(RecyclerView recyclerView, int newState) {
   1116                 stateCnts[newState] = stateCnts[newState] + 1;
   1117                 latch.countDown();
   1118             }
   1119         });
   1120         final ViewConfiguration vc = ViewConfiguration.get(getActivity());
   1121         final float fling = vc.getScaledMinimumFlingVelocity()
   1122                 + (vc.getScaledMaximumFlingVelocity() - vc.getScaledMinimumFlingVelocity()) * .8f;
   1123         runTestOnUiThread(new Runnable() {
   1124             @Override
   1125             public void run() {
   1126                 mRecyclerView.fling(0, Math.round(fling));
   1127             }
   1128         });
   1129         latch.await(5, TimeUnit.SECONDS);
   1130         runTestOnUiThread(new Runnable() {
   1131             @Override
   1132             public void run() {
   1133                 mRecyclerView.stopScroll();
   1134             }
   1135         });
   1136         assertEquals(SCROLL_STATE_IDLE, recyclerView.getScrollState());
   1137         assertEquals(1, stateCnts[SCROLL_STATE_SETTLING]);
   1138         assertEquals(1, stateCnts[SCROLL_STATE_IDLE]);
   1139         assertEquals(0, stateCnts[SCROLL_STATE_DRAGGING]);
   1140     }
   1141 
   1142     @Test
   1143     public void testScrollStateDrag() throws Throwable {
   1144         TestAdapter testAdapter = new TestAdapter(10);
   1145         TestLayoutManager tlm = new TestLayoutManager();
   1146         RecyclerView recyclerView = new RecyclerView(getActivity());
   1147         recyclerView.setAdapter(testAdapter);
   1148         recyclerView.setLayoutManager(tlm);
   1149         setRecyclerView(recyclerView);
   1150         getInstrumentation().waitForIdleSync();
   1151         assertEquals(SCROLL_STATE_IDLE, recyclerView.getScrollState());
   1152         final int[] stateCnts = new int[10];
   1153         recyclerView.setOnScrollListener(new RecyclerView.OnScrollListener() {
   1154             @Override
   1155             public void onScrollStateChanged(RecyclerView recyclerView, int newState) {
   1156                 stateCnts[newState] = stateCnts[newState] + 1;
   1157             }
   1158         });
   1159         drag(mRecyclerView, 0, 0, 0, 500, 5);
   1160         assertEquals(0, stateCnts[SCROLL_STATE_SETTLING]);
   1161         assertEquals(1, stateCnts[SCROLL_STATE_IDLE]);
   1162         assertEquals(1, stateCnts[SCROLL_STATE_DRAGGING]);
   1163     }
   1164 
   1165     public void drag(ViewGroup view, float fromX, float toX, float fromY, float toY,
   1166             int stepCount) throws Throwable {
   1167         long downTime = SystemClock.uptimeMillis();
   1168         long eventTime = SystemClock.uptimeMillis();
   1169 
   1170         float y = fromY;
   1171         float x = fromX;
   1172 
   1173         float yStep = (toY - fromY) / stepCount;
   1174         float xStep = (toX - fromX) / stepCount;
   1175 
   1176         MotionEvent event = MotionEvent.obtain(downTime, eventTime,
   1177                 MotionEvent.ACTION_DOWN, x, y, 0);
   1178         sendTouch(view, event);
   1179         for (int i = 0; i < stepCount; ++i) {
   1180             y += yStep;
   1181             x += xStep;
   1182             eventTime = SystemClock.uptimeMillis();
   1183             event = MotionEvent.obtain(downTime, eventTime, MotionEvent.ACTION_MOVE, x, y, 0);
   1184             sendTouch(view, event);
   1185         }
   1186 
   1187         eventTime = SystemClock.uptimeMillis();
   1188         event = MotionEvent.obtain(downTime, eventTime, MotionEvent.ACTION_UP, x, y, 0);
   1189         sendTouch(view, event);
   1190         getInstrumentation().waitForIdleSync();
   1191     }
   1192 
   1193     private void sendTouch(final ViewGroup view, final MotionEvent event) throws Throwable {
   1194         runTestOnUiThread(new Runnable() {
   1195             @Override
   1196             public void run() {
   1197                 if (view.onInterceptTouchEvent(event)) {
   1198                     view.onTouchEvent(event);
   1199                 }
   1200             }
   1201         });
   1202     }
   1203 
   1204     @Test
   1205     public void testRecycleScrap() throws Throwable {
   1206         recycleScrapTest(false);
   1207         removeRecyclerView();
   1208         recycleScrapTest(true);
   1209     }
   1210 
   1211     public void recycleScrapTest(final boolean useRecycler) throws Throwable {
   1212         TestAdapter testAdapter = new TestAdapter(10);
   1213         final AtomicBoolean test = new AtomicBoolean(false);
   1214         TestLayoutManager lm = new TestLayoutManager() {
   1215             @Override
   1216             public void onLayoutChildren(RecyclerView.Recycler recycler, RecyclerView.State state) {
   1217                 if (test.get()) {
   1218                     try {
   1219                         detachAndScrapAttachedViews(recycler);
   1220                         for (int i = recycler.getScrapList().size() - 1; i >= 0; i--) {
   1221                             if (useRecycler) {
   1222                                 recycler.recycleView(recycler.getScrapList().get(i).itemView);
   1223                             } else {
   1224                                 removeAndRecycleView(recycler.getScrapList().get(i).itemView,
   1225                                         recycler);
   1226                             }
   1227                         }
   1228                         if (state.mOldChangedHolders != null) {
   1229                             for (int i = state.mOldChangedHolders.size() - 1; i >= 0; i--) {
   1230                                 if (useRecycler) {
   1231                                     recycler.recycleView(
   1232                                             state.mOldChangedHolders.valueAt(i).itemView);
   1233                                 } else {
   1234                                     removeAndRecycleView(
   1235                                             state.mOldChangedHolders.valueAt(i).itemView, recycler);
   1236                                 }
   1237                             }
   1238                         }
   1239                         assertEquals("no scrap should be left over", 0, recycler.getScrapCount());
   1240                         assertEquals("pre layout map should be empty", 0,
   1241                                 state.mPreLayoutHolderMap.size());
   1242                         assertEquals("post layout map should be empty", 0,
   1243                                 state.mPostLayoutHolderMap.size());
   1244                         if (state.mOldChangedHolders != null) {
   1245                             assertEquals("post old change map should be empty", 0,
   1246                                     state.mOldChangedHolders.size());
   1247                         }
   1248                     } catch (Throwable t) {
   1249                         postExceptionToInstrumentation(t);
   1250                     }
   1251 
   1252                 }
   1253                 layoutRange(recycler, 0, 5);
   1254                 layoutLatch.countDown();
   1255                 super.onLayoutChildren(recycler, state);
   1256             }
   1257         };
   1258         RecyclerView recyclerView = new RecyclerView(getActivity());
   1259         recyclerView.setAdapter(testAdapter);
   1260         recyclerView.setLayoutManager(lm);
   1261         recyclerView.getItemAnimator().setSupportsChangeAnimations(true);
   1262         lm.expectLayouts(1);
   1263         setRecyclerView(recyclerView);
   1264         lm.waitForLayout(2);
   1265         test.set(true);
   1266         lm.expectLayouts(1);
   1267         testAdapter.changeAndNotify(3, 1);
   1268         lm.waitForLayout(2);
   1269         checkForMainThreadException();
   1270     }
   1271 
   1272     @Test
   1273     public void testAccessRecyclerOnOnMeasure() throws Throwable {
   1274         accessRecyclerOnOnMeasureTest(false);
   1275         removeRecyclerView();
   1276         accessRecyclerOnOnMeasureTest(true);
   1277     }
   1278 
   1279     @Test
   1280     public void testSmoothScrollWithRemovedItemsAndRemoveItem() throws Throwable {
   1281         smoothScrollTest(true);
   1282     }
   1283 
   1284     @Test
   1285     public void testSmoothScrollWithRemovedItems() throws Throwable {
   1286         smoothScrollTest(false);
   1287     }
   1288 
   1289     public void smoothScrollTest(final boolean removeItem) throws Throwable {
   1290         final LinearSmoothScroller[] lss = new LinearSmoothScroller[1];
   1291         final CountDownLatch calledOnStart = new CountDownLatch(1);
   1292         final CountDownLatch calledOnStop = new CountDownLatch(1);
   1293         final int visibleChildCount = 10;
   1294         TestLayoutManager lm = new TestLayoutManager() {
   1295             int start = 0;
   1296 
   1297             @Override
   1298             public void onLayoutChildren(RecyclerView.Recycler recycler, RecyclerView.State state) {
   1299                 super.onLayoutChildren(recycler, state);
   1300                 layoutRange(recycler, start, visibleChildCount);
   1301                 layoutLatch.countDown();
   1302             }
   1303 
   1304             @Override
   1305             public int scrollVerticallyBy(int dy, RecyclerView.Recycler recycler,
   1306                     RecyclerView.State state) {
   1307                 start++;
   1308                 if (DEBUG) {
   1309                     Log.d(TAG, "on scroll, remove and recycling. start:" + start + ", cnt:"
   1310                             + visibleChildCount);
   1311                 }
   1312                 removeAndRecycleAllViews(recycler);
   1313                 layoutRange(recycler, start,
   1314                         Math.max(state.getItemCount(), start + visibleChildCount));
   1315                 return dy;
   1316             }
   1317 
   1318             @Override
   1319             public boolean canScrollVertically() {
   1320                 return true;
   1321             }
   1322 
   1323             @Override
   1324             public void smoothScrollToPosition(RecyclerView recyclerView, RecyclerView.State state,
   1325                     int position) {
   1326                 LinearSmoothScroller linearSmoothScroller =
   1327                         new LinearSmoothScroller(recyclerView.getContext()) {
   1328                             @Override
   1329                             public PointF computeScrollVectorForPosition(int targetPosition) {
   1330                                 return new PointF(0, 1);
   1331                             }
   1332 
   1333                             @Override
   1334                             protected void onStart() {
   1335                                 super.onStart();
   1336                                 calledOnStart.countDown();
   1337                             }
   1338 
   1339                             @Override
   1340                             protected void onStop() {
   1341                                 super.onStop();
   1342                                 calledOnStop.countDown();
   1343                             }
   1344                         };
   1345                 linearSmoothScroller.setTargetPosition(position);
   1346                 lss[0] = linearSmoothScroller;
   1347                 startSmoothScroll(linearSmoothScroller);
   1348             }
   1349         };
   1350         final RecyclerView rv = new RecyclerView(getActivity());
   1351         TestAdapter testAdapter = new TestAdapter(500);
   1352         rv.setLayoutManager(lm);
   1353         rv.setAdapter(testAdapter);
   1354         lm.expectLayouts(1);
   1355         setRecyclerView(rv);
   1356         lm.waitForLayout(1);
   1357         // regular scroll
   1358         final int targetPosition = visibleChildCount * (removeItem ? 30 : 4);
   1359         runTestOnUiThread(new Runnable() {
   1360             @Override
   1361             public void run() {
   1362                 rv.smoothScrollToPosition(targetPosition);
   1363             }
   1364         });
   1365         if (DEBUG) {
   1366             Log.d(TAG, "scrolling to target position " + targetPosition);
   1367         }
   1368         assertTrue("on start should be called very soon", calledOnStart.await(2, TimeUnit.SECONDS));
   1369         if (removeItem) {
   1370             final int newTarget = targetPosition - 10;
   1371             testAdapter.deleteAndNotify(newTarget + 1, testAdapter.getItemCount() - newTarget - 1);
   1372             final CountDownLatch targetCheck = new CountDownLatch(1);
   1373             runTestOnUiThread(new Runnable() {
   1374                 @Override
   1375                 public void run() {
   1376                     ViewCompat.postOnAnimationDelayed(rv, new Runnable() {
   1377                         @Override
   1378                         public void run() {
   1379                             try {
   1380                                 assertEquals("scroll position should be updated to next available",
   1381                                         newTarget, lss[0].getTargetPosition());
   1382                             } catch (Throwable t) {
   1383                                 postExceptionToInstrumentation(t);
   1384                             }
   1385                             targetCheck.countDown();
   1386                         }
   1387                     }, 50);
   1388                 }
   1389             });
   1390             assertTrue("target position should be checked on time ",
   1391                     targetCheck.await(10, TimeUnit.SECONDS));
   1392             checkForMainThreadException();
   1393             assertTrue("on stop should be called", calledOnStop.await(30, TimeUnit.SECONDS));
   1394             checkForMainThreadException();
   1395             assertNotNull("should scroll to new target " + newTarget
   1396                     , rv.findViewHolderForLayoutPosition(newTarget));
   1397             if (DEBUG) {
   1398                 Log.d(TAG, "on stop has been called on time");
   1399             }
   1400         } else {
   1401             assertTrue("on stop should be called eventually",
   1402                     calledOnStop.await(30, TimeUnit.SECONDS));
   1403             assertNotNull("scroll to position should succeed",
   1404                     rv.findViewHolderForLayoutPosition(targetPosition));
   1405         }
   1406         checkForMainThreadException();
   1407     }
   1408 
   1409     @Test
   1410     public void testConsecutiveSmoothScroll() throws Throwable {
   1411         final AtomicInteger visibleChildCount = new AtomicInteger(10);
   1412         final AtomicInteger totalScrolled = new AtomicInteger(0);
   1413         final TestLayoutManager lm = new TestLayoutManager() {
   1414             int start = 0;
   1415 
   1416             @Override
   1417             public void onLayoutChildren(RecyclerView.Recycler recycler, RecyclerView.State state) {
   1418                 super.onLayoutChildren(recycler, state);
   1419                 layoutRange(recycler, start, visibleChildCount.get());
   1420                 layoutLatch.countDown();
   1421             }
   1422 
   1423             @Override
   1424             public int scrollVerticallyBy(int dy, RecyclerView.Recycler recycler,
   1425                     RecyclerView.State state) {
   1426                 totalScrolled.set(totalScrolled.get() + dy);
   1427                 return dy;
   1428             }
   1429 
   1430             @Override
   1431             public boolean canScrollVertically() {
   1432                 return true;
   1433             }
   1434         };
   1435         final RecyclerView rv = new RecyclerView(getActivity());
   1436         TestAdapter testAdapter = new TestAdapter(500);
   1437         rv.setLayoutManager(lm);
   1438         rv.setAdapter(testAdapter);
   1439         lm.expectLayouts(1);
   1440         setRecyclerView(rv);
   1441         lm.waitForLayout(1);
   1442         runTestOnUiThread(new Runnable() {
   1443             @Override
   1444             public void run() {
   1445                 rv.smoothScrollBy(0, 2000);
   1446             }
   1447         });
   1448         Thread.sleep(250);
   1449         final AtomicInteger scrollAmt = new AtomicInteger();
   1450         runTestOnUiThread(new Runnable() {
   1451             @Override
   1452             public void run() {
   1453                 final int soFar = totalScrolled.get();
   1454                 scrollAmt.set(soFar);
   1455                 rv.smoothScrollBy(0, 5000 - soFar);
   1456             }
   1457         });
   1458         while (rv.getScrollState() != SCROLL_STATE_IDLE) {
   1459             Thread.sleep(100);
   1460         }
   1461         final int soFar = totalScrolled.get();
   1462         assertEquals("second scroll should be competed properly", 5000, soFar);
   1463     }
   1464 
   1465     public void accessRecyclerOnOnMeasureTest(final boolean enablePredictiveAnimations)
   1466             throws Throwable {
   1467         TestAdapter testAdapter = new TestAdapter(10);
   1468         final AtomicInteger expectedOnMeasureStateCount = new AtomicInteger(10);
   1469         TestLayoutManager lm = new TestLayoutManager() {
   1470             @Override
   1471             public void onLayoutChildren(RecyclerView.Recycler recycler, RecyclerView.State state) {
   1472                 super.onLayoutChildren(recycler, state);
   1473                 try {
   1474                     layoutRange(recycler, 0, state.getItemCount());
   1475                     layoutLatch.countDown();
   1476                 } catch (Throwable t) {
   1477                     postExceptionToInstrumentation(t);
   1478                 } finally {
   1479                     layoutLatch.countDown();
   1480                 }
   1481             }
   1482 
   1483             @Override
   1484             public void onMeasure(RecyclerView.Recycler recycler, RecyclerView.State state,
   1485                     int widthSpec, int heightSpec) {
   1486                 try {
   1487                     // make sure we access all views
   1488                     for (int i = 0; i < state.getItemCount(); i++) {
   1489                         View view = recycler.getViewForPosition(i);
   1490                         assertNotNull(view);
   1491                         assertEquals(i, getPosition(view));
   1492                     }
   1493                     assertEquals(state.toString(),
   1494                             expectedOnMeasureStateCount.get(), state.getItemCount());
   1495                 } catch (Throwable t) {
   1496                     postExceptionToInstrumentation(t);
   1497                 }
   1498                 super.onMeasure(recycler, state, widthSpec, heightSpec);
   1499             }
   1500 
   1501             @Override
   1502             public boolean supportsPredictiveItemAnimations() {
   1503                 return enablePredictiveAnimations;
   1504             }
   1505         };
   1506         RecyclerView recyclerView = new RecyclerView(getActivity());
   1507         recyclerView.setLayoutManager(lm);
   1508         recyclerView.setAdapter(testAdapter);
   1509         recyclerView.setLayoutManager(lm);
   1510         lm.expectLayouts(1);
   1511         setRecyclerView(recyclerView);
   1512         lm.waitForLayout(2);
   1513         checkForMainThreadException();
   1514         lm.expectLayouts(1);
   1515         if (!enablePredictiveAnimations) {
   1516             expectedOnMeasureStateCount.set(15);
   1517         }
   1518         testAdapter.addAndNotify(4, 5);
   1519         lm.waitForLayout(2);
   1520         checkForMainThreadException();
   1521     }
   1522 
   1523     @Test
   1524     public void testSetCompatibleAdapter() throws Throwable {
   1525         compatibleAdapterTest(true, true);
   1526         removeRecyclerView();
   1527         compatibleAdapterTest(false, true);
   1528         removeRecyclerView();
   1529         compatibleAdapterTest(true, false);
   1530         removeRecyclerView();
   1531         compatibleAdapterTest(false, false);
   1532         removeRecyclerView();
   1533     }
   1534 
   1535     private void compatibleAdapterTest(boolean useCustomPool, boolean removeAndRecycleExistingViews)
   1536             throws Throwable {
   1537         TestAdapter testAdapter = new TestAdapter(10);
   1538         final AtomicInteger recycledViewCount = new AtomicInteger();
   1539         TestLayoutManager lm = new TestLayoutManager() {
   1540             @Override
   1541             public void onLayoutChildren(RecyclerView.Recycler recycler, RecyclerView.State state) {
   1542                 try {
   1543                     layoutRange(recycler, 0, state.getItemCount());
   1544                     layoutLatch.countDown();
   1545                 } catch (Throwable t) {
   1546                     postExceptionToInstrumentation(t);
   1547                 } finally {
   1548                     layoutLatch.countDown();
   1549                 }
   1550             }
   1551         };
   1552         RecyclerView recyclerView = new RecyclerView(getActivity());
   1553         recyclerView.setLayoutManager(lm);
   1554         recyclerView.setAdapter(testAdapter);
   1555         recyclerView.setRecyclerListener(new RecyclerView.RecyclerListener() {
   1556             @Override
   1557             public void onViewRecycled(RecyclerView.ViewHolder holder) {
   1558                 recycledViewCount.incrementAndGet();
   1559             }
   1560         });
   1561         lm.expectLayouts(1);
   1562         setRecyclerView(recyclerView, !useCustomPool);
   1563         lm.waitForLayout(2);
   1564         checkForMainThreadException();
   1565         lm.expectLayouts(1);
   1566         swapAdapter(new TestAdapter(10), removeAndRecycleExistingViews);
   1567         lm.waitForLayout(2);
   1568         checkForMainThreadException();
   1569         if (removeAndRecycleExistingViews) {
   1570             assertTrue("Previous views should be recycled", recycledViewCount.get() > 0);
   1571         } else {
   1572             assertEquals("No views should be recycled if adapters are compatible and developer "
   1573                     + "did not request a recycle", 0, recycledViewCount.get());
   1574         }
   1575     }
   1576 
   1577     @Test
   1578     public void testSetIncompatibleAdapter() throws Throwable {
   1579         incompatibleAdapterTest(true);
   1580         incompatibleAdapterTest(false);
   1581     }
   1582 
   1583     public void incompatibleAdapterTest(boolean useCustomPool) throws Throwable {
   1584         TestAdapter testAdapter = new TestAdapter(10);
   1585         TestLayoutManager lm = new TestLayoutManager() {
   1586             @Override
   1587             public void onLayoutChildren(RecyclerView.Recycler recycler, RecyclerView.State state) {
   1588                 super.onLayoutChildren(recycler, state);
   1589                 try {
   1590                     layoutRange(recycler, 0, state.getItemCount());
   1591                     layoutLatch.countDown();
   1592                 } catch (Throwable t) {
   1593                     postExceptionToInstrumentation(t);
   1594                 } finally {
   1595                     layoutLatch.countDown();
   1596                 }
   1597             }
   1598         };
   1599         RecyclerView recyclerView = new RecyclerView(getActivity());
   1600         recyclerView.setLayoutManager(lm);
   1601         recyclerView.setAdapter(testAdapter);
   1602         recyclerView.setLayoutManager(lm);
   1603         lm.expectLayouts(1);
   1604         setRecyclerView(recyclerView, !useCustomPool);
   1605         lm.waitForLayout(2);
   1606         checkForMainThreadException();
   1607         lm.expectLayouts(1);
   1608         setAdapter(new TestAdapter2(10));
   1609         lm.waitForLayout(2);
   1610         checkForMainThreadException();
   1611     }
   1612 
   1613     @Test
   1614     public void testRecycleIgnored() throws Throwable {
   1615         final TestAdapter adapter = new TestAdapter(10);
   1616         final TestLayoutManager lm = new TestLayoutManager() {
   1617             @Override
   1618             public void onLayoutChildren(RecyclerView.Recycler recycler, RecyclerView.State state) {
   1619                 layoutRange(recycler, 0, 5);
   1620                 layoutLatch.countDown();
   1621             }
   1622         };
   1623         final RecyclerView recyclerView = new RecyclerView(getActivity());
   1624         recyclerView.setAdapter(adapter);
   1625         recyclerView.setLayoutManager(lm);
   1626         lm.expectLayouts(1);
   1627         setRecyclerView(recyclerView);
   1628         lm.waitForLayout(2);
   1629         runTestOnUiThread(new Runnable() {
   1630             @Override
   1631             public void run() {
   1632                 View child1 = lm.findViewByPosition(0);
   1633                 View child2 = lm.findViewByPosition(1);
   1634                 lm.ignoreView(child1);
   1635                 lm.ignoreView(child2);
   1636 
   1637                 lm.removeAndRecycleAllViews(recyclerView.mRecycler);
   1638                 assertEquals("ignored child should not be recycled or removed", 2,
   1639                         lm.getChildCount());
   1640 
   1641                 Throwable[] throwables = new Throwable[1];
   1642                 try {
   1643                     lm.removeAndRecycleView(child1, mRecyclerView.mRecycler);
   1644                 } catch (Throwable t) {
   1645                     throwables[0] = t;
   1646                 }
   1647                 assertTrue("Trying to recycle an ignored view should throw IllegalArgException "
   1648                         , throwables[0] instanceof IllegalArgumentException);
   1649                 lm.removeAllViews();
   1650                 assertEquals("ignored child should be removed as well ", 0, lm.getChildCount());
   1651             }
   1652         });
   1653     }
   1654 
   1655     @Test
   1656     public void testFindIgnoredByPosition() throws Throwable {
   1657         final TestAdapter adapter = new TestAdapter(10);
   1658         final TestLayoutManager lm = new TestLayoutManager() {
   1659             @Override
   1660             public void onLayoutChildren(RecyclerView.Recycler recycler, RecyclerView.State state) {
   1661                 detachAndScrapAttachedViews(recycler);
   1662                 layoutRange(recycler, 0, 5);
   1663                 layoutLatch.countDown();
   1664             }
   1665         };
   1666         final RecyclerView recyclerView = new RecyclerView(getActivity());
   1667         recyclerView.setAdapter(adapter);
   1668         recyclerView.setLayoutManager(lm);
   1669         lm.expectLayouts(1);
   1670         setRecyclerView(recyclerView);
   1671         lm.waitForLayout(2);
   1672         Thread.sleep(5000);
   1673         final int pos = 1;
   1674         final View[] ignored = new View[1];
   1675         runTestOnUiThread(new Runnable() {
   1676             @Override
   1677             public void run() {
   1678                 View child = lm.findViewByPosition(pos);
   1679                 lm.ignoreView(child);
   1680                 ignored[0] = child;
   1681             }
   1682         });
   1683         assertNotNull("ignored child should not be null", ignored[0]);
   1684         assertNull("find view by position should not return ignored child",
   1685                 lm.findViewByPosition(pos));
   1686         lm.expectLayouts(1);
   1687         requestLayoutOnUIThread(mRecyclerView);
   1688         lm.waitForLayout(1);
   1689         assertEquals("child count should be ", 6, lm.getChildCount());
   1690         View replacement = lm.findViewByPosition(pos);
   1691         assertNotNull("re-layout should replace ignored child w/ another one", replacement);
   1692         assertNotSame("replacement should be a different view", replacement, ignored[0]);
   1693     }
   1694 
   1695     @Test
   1696     public void testInvalidateAllDecorOffsets() throws Throwable {
   1697         final TestAdapter adapter = new TestAdapter(10);
   1698         final RecyclerView recyclerView = new RecyclerView(getActivity());
   1699         final AtomicBoolean invalidatedOffsets = new AtomicBoolean(true);
   1700         recyclerView.setAdapter(adapter);
   1701         final AtomicInteger layoutCount = new AtomicInteger(4);
   1702         final RecyclerView.ItemDecoration dummyItemDecoration = new RecyclerView.ItemDecoration() {
   1703         };
   1704         TestLayoutManager testLayoutManager = new TestLayoutManager() {
   1705             @Override
   1706             public void onLayoutChildren(RecyclerView.Recycler recycler, RecyclerView.State state) {
   1707                 try {
   1708                     // test
   1709                     for (int i = 0; i < getChildCount(); i++) {
   1710                         View child = getChildAt(i);
   1711                         RecyclerView.LayoutParams lp = (RecyclerView.LayoutParams)
   1712                                 child.getLayoutParams();
   1713                         assertEquals(
   1714                                 "Decor insets validation for VH should have expected value.",
   1715                                 invalidatedOffsets.get(), lp.mInsetsDirty);
   1716                     }
   1717                     for (RecyclerView.ViewHolder vh : mRecyclerView.mRecycler.mCachedViews) {
   1718                         RecyclerView.LayoutParams lp = (RecyclerView.LayoutParams)
   1719                                 vh.itemView.getLayoutParams();
   1720                         assertEquals(
   1721                                 "Decor insets invalidation in cache for VH should have expected "
   1722                                         + "value.",
   1723                                 invalidatedOffsets.get(), lp.mInsetsDirty);
   1724                     }
   1725                     detachAndScrapAttachedViews(recycler);
   1726                     layoutRange(recycler, 0, layoutCount.get());
   1727                 } catch (Throwable t) {
   1728                     postExceptionToInstrumentation(t);
   1729                 } finally {
   1730                     layoutLatch.countDown();
   1731                 }
   1732             }
   1733 
   1734             @Override
   1735             public boolean supportsPredictiveItemAnimations() {
   1736                 return false;
   1737             }
   1738         };
   1739         // first layout
   1740         recyclerView.setItemViewCacheSize(5);
   1741         recyclerView.setLayoutManager(testLayoutManager);
   1742         testLayoutManager.expectLayouts(1);
   1743         setRecyclerView(recyclerView, true, false);
   1744         testLayoutManager.waitForLayout(2);
   1745         checkForMainThreadException();
   1746 
   1747         // re-layout w/o any change
   1748         invalidatedOffsets.set(false);
   1749         testLayoutManager.expectLayouts(1);
   1750         requestLayoutOnUIThread(recyclerView);
   1751         testLayoutManager.waitForLayout(1);
   1752         checkForMainThreadException();
   1753 
   1754         // invalidate w/o an item decorator
   1755 
   1756         invalidateDecorOffsets(recyclerView);
   1757         testLayoutManager.expectLayouts(1);
   1758         invalidateDecorOffsets(recyclerView);
   1759         testLayoutManager.assertNoLayout("layout should not happen", 2);
   1760         checkForMainThreadException();
   1761 
   1762         // set item decorator, should invalidate
   1763         invalidatedOffsets.set(true);
   1764         testLayoutManager.expectLayouts(1);
   1765         addItemDecoration(mRecyclerView, dummyItemDecoration);
   1766         testLayoutManager.waitForLayout(1);
   1767         checkForMainThreadException();
   1768 
   1769         // re-layout w/o any change
   1770         invalidatedOffsets.set(false);
   1771         testLayoutManager.expectLayouts(1);
   1772         requestLayoutOnUIThread(recyclerView);
   1773         testLayoutManager.waitForLayout(1);
   1774         checkForMainThreadException();
   1775 
   1776         // invalidate w/ item decorator
   1777         invalidatedOffsets.set(true);
   1778         invalidateDecorOffsets(recyclerView);
   1779         testLayoutManager.expectLayouts(1);
   1780         invalidateDecorOffsets(recyclerView);
   1781         testLayoutManager.waitForLayout(2);
   1782         checkForMainThreadException();
   1783 
   1784         // trigger cache.
   1785         layoutCount.set(3);
   1786         invalidatedOffsets.set(false);
   1787         testLayoutManager.expectLayouts(1);
   1788         requestLayoutOnUIThread(mRecyclerView);
   1789         testLayoutManager.waitForLayout(1);
   1790         checkForMainThreadException();
   1791         assertEquals("a view should be cached", 1, mRecyclerView.mRecycler.mCachedViews.size());
   1792 
   1793         layoutCount.set(5);
   1794         invalidatedOffsets.set(true);
   1795         testLayoutManager.expectLayouts(1);
   1796         invalidateDecorOffsets(recyclerView);
   1797         testLayoutManager.waitForLayout(1);
   1798         checkForMainThreadException();
   1799 
   1800         // remove item decorator
   1801         invalidatedOffsets.set(true);
   1802         testLayoutManager.expectLayouts(1);
   1803         removeItemDecoration(mRecyclerView, dummyItemDecoration);
   1804         testLayoutManager.waitForLayout(1);
   1805         checkForMainThreadException();
   1806     }
   1807 
   1808     public void addItemDecoration(final RecyclerView recyclerView, final
   1809     RecyclerView.ItemDecoration itemDecoration) throws Throwable {
   1810         runTestOnUiThread(new Runnable() {
   1811             @Override
   1812             public void run() {
   1813                 recyclerView.addItemDecoration(itemDecoration);
   1814             }
   1815         });
   1816     }
   1817 
   1818     public void removeItemDecoration(final RecyclerView recyclerView, final
   1819     RecyclerView.ItemDecoration itemDecoration) throws Throwable {
   1820         runTestOnUiThread(new Runnable() {
   1821             @Override
   1822             public void run() {
   1823                 recyclerView.removeItemDecoration(itemDecoration);
   1824             }
   1825         });
   1826     }
   1827 
   1828     public void invalidateDecorOffsets(final RecyclerView recyclerView) throws Throwable {
   1829         runTestOnUiThread(new Runnable() {
   1830             @Override
   1831             public void run() {
   1832                 recyclerView.invalidateItemDecorations();
   1833             }
   1834         });
   1835     }
   1836 
   1837     @Test
   1838     public void testInvalidateDecorOffsets() throws Throwable {
   1839         final TestAdapter adapter = new TestAdapter(10);
   1840         adapter.setHasStableIds(true);
   1841         final RecyclerView recyclerView = new RecyclerView(getActivity());
   1842         recyclerView.setAdapter(adapter);
   1843 
   1844         final Map<Long, Boolean> changes = new HashMap<Long, Boolean>();
   1845 
   1846         TestLayoutManager testLayoutManager = new TestLayoutManager() {
   1847             @Override
   1848             public void onLayoutChildren(RecyclerView.Recycler recycler, RecyclerView.State state) {
   1849                 try {
   1850                     if (changes.size() > 0) {
   1851                         // test
   1852                         for (int i = 0; i < getChildCount(); i++) {
   1853                             View child = getChildAt(i);
   1854                             RecyclerView.LayoutParams lp = (RecyclerView.LayoutParams)
   1855                                     child.getLayoutParams();
   1856                             RecyclerView.ViewHolder vh = lp.mViewHolder;
   1857                             if (!changes.containsKey(vh.getItemId())) {
   1858                                 continue; //nothing to test
   1859                             }
   1860                             assertEquals(
   1861                                     "Decord insets validation for VH should have expected value.",
   1862                                     changes.get(vh.getItemId()).booleanValue(),
   1863                                     lp.mInsetsDirty);
   1864                         }
   1865                     }
   1866                     detachAndScrapAttachedViews(recycler);
   1867                     layoutRange(recycler, 0, state.getItemCount());
   1868                 } catch (Throwable t) {
   1869                     postExceptionToInstrumentation(t);
   1870                 } finally {
   1871                     layoutLatch.countDown();
   1872                 }
   1873             }
   1874 
   1875             @Override
   1876             public boolean supportsPredictiveItemAnimations() {
   1877                 return false;
   1878             }
   1879         };
   1880         recyclerView.setLayoutManager(testLayoutManager);
   1881         testLayoutManager.expectLayouts(1);
   1882         setRecyclerView(recyclerView);
   1883         testLayoutManager.waitForLayout(2);
   1884         int itemAddedTo = 5;
   1885         for (int i = 0; i < itemAddedTo; i++) {
   1886             changes.put(mRecyclerView.findViewHolderForLayoutPosition(i).getItemId(), false);
   1887         }
   1888         for (int i = itemAddedTo; i < mRecyclerView.getChildCount(); i++) {
   1889             changes.put(mRecyclerView.findViewHolderForLayoutPosition(i).getItemId(), true);
   1890         }
   1891         testLayoutManager.expectLayouts(1);
   1892         adapter.addAndNotify(5, 1);
   1893         testLayoutManager.waitForLayout(2);
   1894         checkForMainThreadException();
   1895 
   1896         changes.clear();
   1897         int[] changedItems = new int[]{3, 5, 6};
   1898         for (int i = 0; i < adapter.getItemCount(); i++) {
   1899             changes.put(mRecyclerView.findViewHolderForLayoutPosition(i).getItemId(), false);
   1900         }
   1901         for (int i = 0; i < changedItems.length; i++) {
   1902             changes.put(mRecyclerView.findViewHolderForLayoutPosition(changedItems[i]).getItemId(),
   1903                     true);
   1904         }
   1905         testLayoutManager.expectLayouts(1);
   1906         adapter.changePositionsAndNotify(changedItems);
   1907         testLayoutManager.waitForLayout(2);
   1908         checkForMainThreadException();
   1909 
   1910         for (int i = 0; i < adapter.getItemCount(); i++) {
   1911             changes.put(mRecyclerView.findViewHolderForLayoutPosition(i).getItemId(), true);
   1912         }
   1913         testLayoutManager.expectLayouts(1);
   1914         adapter.dispatchDataSetChanged();
   1915         testLayoutManager.waitForLayout(2);
   1916         checkForMainThreadException();
   1917     }
   1918 
   1919     @Test
   1920     public void testMovingViaStableIds() throws Throwable {
   1921         stableIdsMoveTest(true);
   1922         removeRecyclerView();
   1923         stableIdsMoveTest(false);
   1924         removeRecyclerView();
   1925     }
   1926 
   1927     public void stableIdsMoveTest(final boolean supportsPredictive) throws Throwable {
   1928         final TestAdapter testAdapter = new TestAdapter(10);
   1929         testAdapter.setHasStableIds(true);
   1930         final AtomicBoolean test = new AtomicBoolean(false);
   1931         final int movedViewFromIndex = 3;
   1932         final int movedViewToIndex = 6;
   1933         final View[] movedView = new View[1];
   1934         TestLayoutManager lm = new TestLayoutManager() {
   1935             @Override
   1936             public void onLayoutChildren(RecyclerView.Recycler recycler, RecyclerView.State state) {
   1937                 detachAndScrapAttachedViews(recycler);
   1938                 try {
   1939                     if (test.get()) {
   1940                         if (state.isPreLayout()) {
   1941                             View view = recycler.getViewForPosition(movedViewFromIndex, true);
   1942                             assertSame("In pre layout, should be able to get moved view w/ old "
   1943                                     + "position", movedView[0], view);
   1944                             RecyclerView.ViewHolder holder = mRecyclerView.getChildViewHolder(view);
   1945                             assertTrue("it should come from scrap", holder.wasReturnedFromScrap());
   1946                             // clear scrap flag
   1947                             holder.clearReturnedFromScrapFlag();
   1948                         } else {
   1949                             View view = recycler.getViewForPosition(movedViewToIndex, true);
   1950                             assertSame("In post layout, should be able to get moved view w/ new "
   1951                                     + "position", movedView[0], view);
   1952                             RecyclerView.ViewHolder holder = mRecyclerView.getChildViewHolder(view);
   1953                             assertTrue("it should come from scrap", holder.wasReturnedFromScrap());
   1954                             // clear scrap flag
   1955                             holder.clearReturnedFromScrapFlag();
   1956                         }
   1957                     }
   1958                     layoutRange(recycler, 0, state.getItemCount());
   1959                 } catch (Throwable t) {
   1960                     postExceptionToInstrumentation(t);
   1961                 } finally {
   1962                     layoutLatch.countDown();
   1963                 }
   1964 
   1965 
   1966             }
   1967 
   1968             @Override
   1969             public boolean supportsPredictiveItemAnimations() {
   1970                 return supportsPredictive;
   1971             }
   1972         };
   1973         RecyclerView recyclerView = new RecyclerView(this.getActivity());
   1974         recyclerView.setAdapter(testAdapter);
   1975         recyclerView.setLayoutManager(lm);
   1976         lm.expectLayouts(1);
   1977         setRecyclerView(recyclerView);
   1978         lm.waitForLayout(1);
   1979 
   1980         movedView[0] = recyclerView.getChildAt(movedViewFromIndex);
   1981         test.set(true);
   1982         lm.expectLayouts(supportsPredictive ? 2 : 1);
   1983         runTestOnUiThread(new Runnable() {
   1984             @Override
   1985             public void run() {
   1986                 Item item = testAdapter.mItems.remove(movedViewFromIndex);
   1987                 testAdapter.mItems.add(movedViewToIndex, item);
   1988                 testAdapter.notifyItemRemoved(movedViewFromIndex);
   1989                 testAdapter.notifyItemInserted(movedViewToIndex);
   1990             }
   1991         });
   1992         lm.waitForLayout(2);
   1993         checkForMainThreadException();
   1994     }
   1995 
   1996     @Test
   1997     public void testAdapterChangeDuringLayout() throws Throwable {
   1998         adapterChangeInMainThreadTest("notifyDataSetChanged", new Runnable() {
   1999             @Override
   2000             public void run() {
   2001                 mRecyclerView.getAdapter().notifyDataSetChanged();
   2002             }
   2003         });
   2004 
   2005         adapterChangeInMainThreadTest("notifyItemChanged", new Runnable() {
   2006             @Override
   2007             public void run() {
   2008                 mRecyclerView.getAdapter().notifyItemChanged(2);
   2009             }
   2010         });
   2011 
   2012         adapterChangeInMainThreadTest("notifyItemInserted", new Runnable() {
   2013             @Override
   2014             public void run() {
   2015                 mRecyclerView.getAdapter().notifyItemInserted(2);
   2016             }
   2017         });
   2018         adapterChangeInMainThreadTest("notifyItemRemoved", new Runnable() {
   2019             @Override
   2020             public void run() {
   2021                 mRecyclerView.getAdapter().notifyItemRemoved(2);
   2022             }
   2023         });
   2024     }
   2025 
   2026     public void adapterChangeInMainThreadTest(String msg,
   2027             final Runnable onLayoutRunnable) throws Throwable {
   2028         final AtomicBoolean doneFirstLayout = new AtomicBoolean(false);
   2029         TestAdapter testAdapter = new TestAdapter(10);
   2030         TestLayoutManager lm = new TestLayoutManager() {
   2031             @Override
   2032             public void onLayoutChildren(RecyclerView.Recycler recycler, RecyclerView.State state) {
   2033                 super.onLayoutChildren(recycler, state);
   2034                 try {
   2035                     layoutRange(recycler, 0, state.getItemCount());
   2036                     if (doneFirstLayout.get()) {
   2037                         onLayoutRunnable.run();
   2038                     }
   2039                 } catch (Throwable t) {
   2040                     postExceptionToInstrumentation(t);
   2041                 } finally {
   2042                     layoutLatch.countDown();
   2043                 }
   2044 
   2045             }
   2046         };
   2047         RecyclerView recyclerView = new RecyclerView(getActivity());
   2048         recyclerView.setLayoutManager(lm);
   2049         recyclerView.setAdapter(testAdapter);
   2050         lm.expectLayouts(1);
   2051         setRecyclerView(recyclerView);
   2052         lm.waitForLayout(2);
   2053         doneFirstLayout.set(true);
   2054         lm.expectLayouts(1);
   2055         requestLayoutOnUIThread(recyclerView);
   2056         lm.waitForLayout(2);
   2057         removeRecyclerView();
   2058         assertTrue("Invalid data updates should be caught:" + msg,
   2059                 mainThreadException instanceof IllegalStateException);
   2060         mainThreadException = null;
   2061     }
   2062 
   2063     @Test
   2064     public void testAdapterChangeDuringScroll() throws Throwable {
   2065         for (int orientation : new int[]{OrientationHelper.HORIZONTAL,
   2066                 OrientationHelper.VERTICAL}) {
   2067             adapterChangeDuringScrollTest("notifyDataSetChanged", orientation,
   2068                     new Runnable() {
   2069                         @Override
   2070                         public void run() {
   2071                             mRecyclerView.getAdapter().notifyDataSetChanged();
   2072                         }
   2073                     });
   2074             adapterChangeDuringScrollTest("notifyItemChanged", orientation, new Runnable() {
   2075                 @Override
   2076                 public void run() {
   2077                     mRecyclerView.getAdapter().notifyItemChanged(2);
   2078                 }
   2079             });
   2080 
   2081             adapterChangeDuringScrollTest("notifyItemInserted", orientation, new Runnable() {
   2082                 @Override
   2083                 public void run() {
   2084                     mRecyclerView.getAdapter().notifyItemInserted(2);
   2085                 }
   2086             });
   2087             adapterChangeDuringScrollTest("notifyItemRemoved", orientation, new Runnable() {
   2088                 @Override
   2089                 public void run() {
   2090                     mRecyclerView.getAdapter().notifyItemRemoved(2);
   2091                 }
   2092             });
   2093         }
   2094     }
   2095 
   2096     public void adapterChangeDuringScrollTest(String msg, final int orientation,
   2097             final Runnable onScrollRunnable) throws Throwable {
   2098         TestAdapter testAdapter = new TestAdapter(100);
   2099         TestLayoutManager lm = new TestLayoutManager() {
   2100             @Override
   2101             public void onLayoutChildren(RecyclerView.Recycler recycler, RecyclerView.State state) {
   2102                 super.onLayoutChildren(recycler, state);
   2103                 try {
   2104                     layoutRange(recycler, 0, 10);
   2105                 } catch (Throwable t) {
   2106                     postExceptionToInstrumentation(t);
   2107                 } finally {
   2108                     layoutLatch.countDown();
   2109                 }
   2110             }
   2111 
   2112             @Override
   2113             public boolean canScrollVertically() {
   2114                 return orientation == OrientationHelper.VERTICAL;
   2115             }
   2116 
   2117             @Override
   2118             public boolean canScrollHorizontally() {
   2119                 return orientation == OrientationHelper.HORIZONTAL;
   2120             }
   2121 
   2122             public int mockScroll() {
   2123                 try {
   2124                     onScrollRunnable.run();
   2125                 } catch (Throwable t) {
   2126                     postExceptionToInstrumentation(t);
   2127                 } finally {
   2128                     layoutLatch.countDown();
   2129                 }
   2130                 return 0;
   2131             }
   2132 
   2133             @Override
   2134             public int scrollHorizontallyBy(int dx, RecyclerView.Recycler recycler,
   2135                     RecyclerView.State state) {
   2136                 return mockScroll();
   2137             }
   2138 
   2139             @Override
   2140             public int scrollVerticallyBy(int dy, RecyclerView.Recycler recycler,
   2141                     RecyclerView.State state) {
   2142                 return mockScroll();
   2143             }
   2144         };
   2145         RecyclerView recyclerView = new RecyclerView(getActivity());
   2146         recyclerView.setLayoutManager(lm);
   2147         recyclerView.setAdapter(testAdapter);
   2148         lm.expectLayouts(1);
   2149         setRecyclerView(recyclerView);
   2150         lm.waitForLayout(2);
   2151         lm.expectLayouts(1);
   2152         scrollBy(200);
   2153         lm.waitForLayout(2);
   2154         removeRecyclerView();
   2155         assertTrue("Invalid data updates should be caught:" + msg,
   2156                 mainThreadException instanceof IllegalStateException);
   2157         mainThreadException = null;
   2158     }
   2159 
   2160     @Test
   2161     public void testRecycleOnDetach() throws Throwable {
   2162         final RecyclerView recyclerView = new RecyclerView(getActivity());
   2163         final TestAdapter testAdapter = new TestAdapter(10);
   2164         final AtomicBoolean didRunOnDetach = new AtomicBoolean(false);
   2165         final TestLayoutManager lm = new TestLayoutManager() {
   2166             @Override
   2167             public void onLayoutChildren(RecyclerView.Recycler recycler, RecyclerView.State state) {
   2168                 super.onLayoutChildren(recycler, state);
   2169                 layoutRange(recycler, 0, state.getItemCount() - 1);
   2170                 layoutLatch.countDown();
   2171             }
   2172 
   2173             @Override
   2174             public void onDetachedFromWindow(RecyclerView view, RecyclerView.Recycler recycler) {
   2175                 super.onDetachedFromWindow(view, recycler);
   2176                 didRunOnDetach.set(true);
   2177                 removeAndRecycleAllViews(recycler);
   2178             }
   2179         };
   2180         recyclerView.setAdapter(testAdapter);
   2181         recyclerView.setLayoutManager(lm);
   2182         lm.expectLayouts(1);
   2183         setRecyclerView(recyclerView);
   2184         lm.waitForLayout(2);
   2185         removeRecyclerView();
   2186         assertTrue("When recycler view is removed, detach should run", didRunOnDetach.get());
   2187         assertEquals("All children should be recycled", recyclerView.getChildCount(), 0);
   2188     }
   2189 
   2190     @Test
   2191     public void testUpdatesWhileDetached() throws Throwable {
   2192         final RecyclerView recyclerView = new RecyclerView(getActivity());
   2193         final int initialAdapterSize = 20;
   2194         final TestAdapter adapter = new TestAdapter(initialAdapterSize);
   2195         final AtomicInteger layoutCount = new AtomicInteger(0);
   2196         TestLayoutManager lm = new TestLayoutManager() {
   2197             @Override
   2198             public void onLayoutChildren(RecyclerView.Recycler recycler, RecyclerView.State state) {
   2199                 super.onLayoutChildren(recycler, state);
   2200                 layoutRange(recycler, 0, 5);
   2201                 layoutCount.incrementAndGet();
   2202                 layoutLatch.countDown();
   2203             }
   2204         };
   2205         recyclerView.setAdapter(adapter);
   2206         recyclerView.setLayoutManager(lm);
   2207         recyclerView.setHasFixedSize(true);
   2208         lm.expectLayouts(1);
   2209         adapter.addAndNotify(4, 5);
   2210         lm.assertNoLayout("When RV is not attached, layout should not happen", 1);
   2211     }
   2212 
   2213     @Test
   2214     public void testUpdatesAfterDetach() throws Throwable {
   2215         final RecyclerView recyclerView = new RecyclerView(getActivity());
   2216         final int initialAdapterSize = 20;
   2217         final TestAdapter adapter = new TestAdapter(initialAdapterSize);
   2218         final AtomicInteger layoutCount = new AtomicInteger(0);
   2219         TestLayoutManager lm = new TestLayoutManager() {
   2220             @Override
   2221             public void onLayoutChildren(RecyclerView.Recycler recycler, RecyclerView.State state) {
   2222                 super.onLayoutChildren(recycler, state);
   2223                 layoutRange(recycler, 0, 5);
   2224                 layoutCount.incrementAndGet();
   2225                 layoutLatch.countDown();
   2226             }
   2227         };
   2228         recyclerView.setAdapter(adapter);
   2229         recyclerView.setLayoutManager(lm);
   2230         lm.expectLayouts(1);
   2231         recyclerView.setHasFixedSize(true);
   2232         setRecyclerView(recyclerView);
   2233         lm.waitForLayout(2);
   2234         lm.expectLayouts(1);
   2235         final int prevLayoutCount = layoutCount.get();
   2236         runTestOnUiThread(new Runnable() {
   2237             @Override
   2238             public void run() {
   2239                 try {
   2240                     adapter.addAndNotify(4, 5);
   2241                     removeRecyclerView();
   2242                 } catch (Throwable throwable) {
   2243                     postExceptionToInstrumentation(throwable);
   2244                 }
   2245             }
   2246         });
   2247         checkForMainThreadException();
   2248 
   2249         lm.assertNoLayout("When RV is not attached, layout should not happen", 1);
   2250         assertEquals("No extra layout should happen when detached", prevLayoutCount,
   2251                 layoutCount.get());
   2252     }
   2253 
   2254     @Test
   2255     public void testNotifyDataSetChangedWithStableIds() throws Throwable {
   2256         final int defaultViewType = 1;
   2257         final Map<Item, Integer> viewTypeMap = new HashMap<Item, Integer>();
   2258         final Map<Integer, Integer> oldPositionToNewPositionMapping =
   2259                 new HashMap<Integer, Integer>();
   2260         final TestAdapter adapter = new TestAdapter(100) {
   2261             @Override
   2262             public int getItemViewType(int position) {
   2263                 Integer type = viewTypeMap.get(mItems.get(position));
   2264                 return type == null ? defaultViewType : type;
   2265             }
   2266 
   2267             @Override
   2268             public long getItemId(int position) {
   2269                 return mItems.get(position).mId;
   2270             }
   2271         };
   2272         adapter.setHasStableIds(true);
   2273         final ArrayList<Item> previousItems = new ArrayList<Item>();
   2274         previousItems.addAll(adapter.mItems);
   2275 
   2276         final AtomicInteger layoutStart = new AtomicInteger(50);
   2277         final AtomicBoolean validate = new AtomicBoolean(false);
   2278         final int childCount = 10;
   2279         final TestLayoutManager lm = new TestLayoutManager() {
   2280             @Override
   2281             public void onLayoutChildren(RecyclerView.Recycler recycler, RecyclerView.State state) {
   2282                 try {
   2283                     super.onLayoutChildren(recycler, state);
   2284                     if (validate.get()) {
   2285                         assertEquals("Cached views should be kept", 5, recycler
   2286                                 .mCachedViews.size());
   2287                         for (RecyclerView.ViewHolder vh : recycler.mCachedViews) {
   2288                             TestViewHolder tvh = (TestViewHolder) vh;
   2289                             assertTrue("view holder should be marked for update",
   2290                                     tvh.needsUpdate());
   2291                             assertTrue("view holder should be marked as invalid", tvh.isInvalid());
   2292                         }
   2293                     }
   2294                     detachAndScrapAttachedViews(recycler);
   2295                     if (validate.get()) {
   2296                         assertEquals("cache size should stay the same", 5,
   2297                                 recycler.mCachedViews.size());
   2298                         assertEquals("all views should be scrapped", childCount,
   2299                                 recycler.getScrapList().size());
   2300                         for (RecyclerView.ViewHolder vh : recycler.getScrapList()) {
   2301                             // TODO create test case for type change
   2302                             TestViewHolder tvh = (TestViewHolder) vh;
   2303                             assertTrue("view holder should be marked for update",
   2304                                     tvh.needsUpdate());
   2305                             assertTrue("view holder should be marked as invalid", tvh.isInvalid());
   2306                         }
   2307                     }
   2308                     layoutRange(recycler, layoutStart.get(), layoutStart.get() + childCount);
   2309                     if (validate.get()) {
   2310                         for (int i = 0; i < getChildCount(); i++) {
   2311                             View view = getChildAt(i);
   2312                             TestViewHolder tvh = (TestViewHolder) mRecyclerView
   2313                                     .getChildViewHolder(view);
   2314                             final int oldPos = previousItems.indexOf(tvh.mBoundItem);
   2315                             assertEquals("view holder's position should be correct",
   2316                                     oldPositionToNewPositionMapping.get(oldPos).intValue(),
   2317                                     tvh.getLayoutPosition());
   2318                             ;
   2319                         }
   2320                     }
   2321                 } catch (Throwable t) {
   2322                     postExceptionToInstrumentation(t);
   2323                 } finally {
   2324                     layoutLatch.countDown();
   2325                 }
   2326             }
   2327         };
   2328         final RecyclerView recyclerView = new RecyclerView(getActivity());
   2329         recyclerView.setItemAnimator(null);
   2330         recyclerView.setAdapter(adapter);
   2331         recyclerView.setLayoutManager(lm);
   2332         recyclerView.setItemViewCacheSize(10);
   2333         lm.expectLayouts(1);
   2334         setRecyclerView(recyclerView);
   2335         lm.waitForLayout(2);
   2336         checkForMainThreadException();
   2337         getInstrumentation().waitForIdleSync();
   2338         layoutStart.set(layoutStart.get() + 5);//55
   2339         lm.expectLayouts(1);
   2340         requestLayoutOnUIThread(recyclerView);
   2341         lm.waitForLayout(2);
   2342         validate.set(true);
   2343         lm.expectLayouts(1);
   2344         runTestOnUiThread(new Runnable() {
   2345             @Override
   2346             public void run() {
   2347                 try {
   2348                     adapter.moveItems(false,
   2349                             new int[]{50, 56}, new int[]{51, 1}, new int[]{52, 2},
   2350                             new int[]{53, 54}, new int[]{60, 61}, new int[]{62, 64},
   2351                             new int[]{75, 58});
   2352                     for (int i = 0; i < previousItems.size(); i++) {
   2353                         Item item = previousItems.get(i);
   2354                         oldPositionToNewPositionMapping.put(i, adapter.mItems.indexOf(item));
   2355                     }
   2356                     adapter.dispatchDataSetChanged();
   2357                 } catch (Throwable throwable) {
   2358                     postExceptionToInstrumentation(throwable);
   2359                 }
   2360             }
   2361         });
   2362         lm.waitForLayout(2);
   2363         checkForMainThreadException();
   2364     }
   2365 
   2366     @Test
   2367     public void testCallbacksDuringAdapterSwap() throws Throwable {
   2368         callbacksDuringAdapterChange(true);
   2369     }
   2370 
   2371     @Test
   2372     public void testCallbacksDuringAdapterSet() throws Throwable {
   2373         callbacksDuringAdapterChange(false);
   2374     }
   2375 
   2376     public void callbacksDuringAdapterChange(boolean swap) throws Throwable {
   2377         final TestAdapter2 adapter1 = swap ? createBinderCheckingAdapter()
   2378                 : createOwnerCheckingAdapter();
   2379         final TestAdapter2 adapter2 = swap ? createBinderCheckingAdapter()
   2380                 : createOwnerCheckingAdapter();
   2381 
   2382         TestLayoutManager tlm = new TestLayoutManager() {
   2383             @Override
   2384             public void onLayoutChildren(RecyclerView.Recycler recycler, RecyclerView.State state) {
   2385                 try {
   2386                     layoutRange(recycler, 0, state.getItemCount());
   2387                 } catch (Throwable t) {
   2388                     postExceptionToInstrumentation(t);
   2389                 }
   2390                 layoutLatch.countDown();
   2391             }
   2392         };
   2393         RecyclerView rv = new RecyclerView(getActivity());
   2394         rv.setAdapter(adapter1);
   2395         rv.setLayoutManager(tlm);
   2396         tlm.expectLayouts(1);
   2397         setRecyclerView(rv);
   2398         tlm.waitForLayout(1);
   2399         checkForMainThreadException();
   2400         tlm.expectLayouts(1);
   2401         if (swap) {
   2402             swapAdapter(adapter2, true);
   2403         } else {
   2404             setAdapter(adapter2);
   2405         }
   2406         checkForMainThreadException();
   2407         tlm.waitForLayout(1);
   2408         checkForMainThreadException();
   2409     }
   2410 
   2411     private TestAdapter2 createOwnerCheckingAdapter() {
   2412         return new TestAdapter2(10) {
   2413             @Override
   2414             public void onViewRecycled(TestViewHolder2 holder) {
   2415                 assertSame("on recycled should be called w/ the creator adapter", this,
   2416                         holder.mData);
   2417                 super.onViewRecycled(holder);
   2418             }
   2419 
   2420             @Override
   2421             public void onBindViewHolder(TestViewHolder2 holder, int position) {
   2422                 super.onBindViewHolder(holder, position);
   2423                 assertSame("on bind should be called w/ the creator adapter", this, holder.mData);
   2424             }
   2425 
   2426             @Override
   2427             public TestViewHolder2 onCreateViewHolder(ViewGroup parent,
   2428                     int viewType) {
   2429                 final TestViewHolder2 vh = super.onCreateViewHolder(parent, viewType);
   2430                 vh.mData = this;
   2431                 return vh;
   2432             }
   2433         };
   2434     }
   2435 
   2436     private TestAdapter2 createBinderCheckingAdapter() {
   2437         return new TestAdapter2(10) {
   2438             @Override
   2439             public void onViewRecycled(TestViewHolder2 holder) {
   2440                 assertSame("on recycled should be called w/ the creator adapter", this,
   2441                         holder.mData);
   2442                 holder.mData = null;
   2443                 super.onViewRecycled(holder);
   2444             }
   2445 
   2446             @Override
   2447             public void onBindViewHolder(TestViewHolder2 holder, int position) {
   2448                 super.onBindViewHolder(holder, position);
   2449                 holder.mData = this;
   2450             }
   2451         };
   2452     }
   2453 
   2454     @Test
   2455     public void testFindViewById() throws Throwable {
   2456         findViewByIdTest(false);
   2457         removeRecyclerView();
   2458         findViewByIdTest(true);
   2459     }
   2460 
   2461     public void findViewByIdTest(final boolean supportPredictive) throws Throwable {
   2462         final RecyclerView recyclerView = new RecyclerView(getActivity());
   2463         final int initialAdapterSize = 20;
   2464         final TestAdapter adapter = new TestAdapter(initialAdapterSize);
   2465         final int deleteStart = 6;
   2466         final int deleteCount = 5;
   2467         recyclerView.setAdapter(adapter);
   2468         final AtomicBoolean assertPositions = new AtomicBoolean(false);
   2469         TestLayoutManager lm = new TestLayoutManager() {
   2470             @Override
   2471             public void onLayoutChildren(RecyclerView.Recycler recycler, RecyclerView.State state) {
   2472                 super.onLayoutChildren(recycler, state);
   2473                 if (assertPositions.get()) {
   2474                     if (state.isPreLayout()) {
   2475                         for (int i = 0; i < deleteStart; i++) {
   2476                             View view = findViewByPosition(i);
   2477                             assertNotNull("find view by position for existing items should work "
   2478                                     + "fine", view);
   2479                             assertFalse("view should not be marked as removed",
   2480                                     ((RecyclerView.LayoutParams) view.getLayoutParams())
   2481                                             .isItemRemoved());
   2482                         }
   2483                         for (int i = 0; i < deleteCount; i++) {
   2484                             View view = findViewByPosition(i + deleteStart);
   2485                             assertNotNull("find view by position should work fine for removed "
   2486                                     + "views in pre-layout", view);
   2487                             assertTrue("view should be marked as removed",
   2488                                     ((RecyclerView.LayoutParams) view.getLayoutParams())
   2489                                             .isItemRemoved());
   2490                         }
   2491                         for (int i = deleteStart + deleteCount; i < 20; i++) {
   2492                             View view = findViewByPosition(i);
   2493                             assertNotNull(view);
   2494                             assertFalse("view should not be marked as removed",
   2495                                     ((RecyclerView.LayoutParams) view.getLayoutParams())
   2496                                             .isItemRemoved());
   2497                         }
   2498                     } else {
   2499                         for (int i = 0; i < initialAdapterSize - deleteCount; i++) {
   2500                             View view = findViewByPosition(i);
   2501                             assertNotNull("find view by position for existing item " + i +
   2502                                     " should work fine. child count:" + getChildCount(), view);
   2503                             TestViewHolder viewHolder =
   2504                                     (TestViewHolder) mRecyclerView.getChildViewHolder(view);
   2505                             assertSame("should be the correct item " + viewHolder
   2506                                     , viewHolder.mBoundItem,
   2507                                     adapter.mItems.get(viewHolder.mPosition));
   2508                             assertFalse("view should not be marked as removed",
   2509                                     ((RecyclerView.LayoutParams) view.getLayoutParams())
   2510                                             .isItemRemoved());
   2511                         }
   2512                     }
   2513                 }
   2514                 detachAndScrapAttachedViews(recycler);
   2515                 layoutRange(recycler, state.getItemCount() - 1, -1);
   2516                 layoutLatch.countDown();
   2517             }
   2518 
   2519             @Override
   2520             public boolean supportsPredictiveItemAnimations() {
   2521                 return supportPredictive;
   2522             }
   2523         };
   2524         recyclerView.setLayoutManager(lm);
   2525         lm.expectLayouts(1);
   2526         setRecyclerView(recyclerView);
   2527         lm.waitForLayout(2);
   2528         getInstrumentation().waitForIdleSync();
   2529 
   2530         assertPositions.set(true);
   2531         lm.expectLayouts(supportPredictive ? 2 : 1);
   2532         adapter.deleteAndNotify(new int[]{deleteStart, deleteCount - 1}, new int[]{deleteStart, 1});
   2533         lm.waitForLayout(2);
   2534     }
   2535 
   2536     @Test
   2537     public void testTypeForCache() throws Throwable {
   2538         final AtomicInteger viewType = new AtomicInteger(1);
   2539         final TestAdapter adapter = new TestAdapter(100) {
   2540             @Override
   2541             public int getItemViewType(int position) {
   2542                 return viewType.get();
   2543             }
   2544 
   2545             @Override
   2546             public long getItemId(int position) {
   2547                 return mItems.get(position).mId;
   2548             }
   2549         };
   2550         adapter.setHasStableIds(true);
   2551         final AtomicInteger layoutStart = new AtomicInteger(2);
   2552         final int childCount = 10;
   2553         final TestLayoutManager lm = new TestLayoutManager() {
   2554             @Override
   2555             public void onLayoutChildren(RecyclerView.Recycler recycler, RecyclerView.State state) {
   2556                 super.onLayoutChildren(recycler, state);
   2557                 detachAndScrapAttachedViews(recycler);
   2558                 layoutRange(recycler, layoutStart.get(), layoutStart.get() + childCount);
   2559                 layoutLatch.countDown();
   2560             }
   2561         };
   2562         final RecyclerView recyclerView = new RecyclerView(getActivity());
   2563         recyclerView.setItemAnimator(null);
   2564         recyclerView.setAdapter(adapter);
   2565         recyclerView.setLayoutManager(lm);
   2566         recyclerView.setItemViewCacheSize(10);
   2567         lm.expectLayouts(1);
   2568         setRecyclerView(recyclerView);
   2569         lm.waitForLayout(2);
   2570         getInstrumentation().waitForIdleSync();
   2571         layoutStart.set(4); // trigger a cache for 3,4
   2572         lm.expectLayouts(1);
   2573         requestLayoutOnUIThread(recyclerView);
   2574         lm.waitForLayout(2);
   2575         //
   2576         viewType.incrementAndGet();
   2577         layoutStart.set(2); // go back to bring views from cache
   2578         lm.expectLayouts(1);
   2579         adapter.mItems.remove(1);
   2580         adapter.dispatchDataSetChanged();
   2581         lm.waitForLayout(2);
   2582         runTestOnUiThread(new Runnable() {
   2583             @Override
   2584             public void run() {
   2585                 for (int i = 2; i < 4; i++) {
   2586                     RecyclerView.ViewHolder vh = recyclerView.findViewHolderForLayoutPosition(i);
   2587                     assertEquals("View holder's type should match latest type", viewType.get(),
   2588                             vh.getItemViewType());
   2589                 }
   2590             }
   2591         });
   2592     }
   2593 
   2594     @Test
   2595     public void testTypeForExistingViews() throws Throwable {
   2596         final AtomicInteger viewType = new AtomicInteger(1);
   2597         final int invalidatedCount = 2;
   2598         final int layoutStart = 2;
   2599         final TestAdapter adapter = new TestAdapter(100) {
   2600             @Override
   2601             public int getItemViewType(int position) {
   2602                 return viewType.get();
   2603             }
   2604 
   2605             @Override
   2606             public void onBindViewHolder(TestViewHolder holder,
   2607                     int position) {
   2608                 super.onBindViewHolder(holder, position);
   2609                 if (position >= layoutStart && position < invalidatedCount + layoutStart) {
   2610                     try {
   2611                         assertEquals("holder type should match current view type at position " +
   2612                                 position, viewType.get(), holder.getItemViewType());
   2613                     } catch (Throwable t) {
   2614                         postExceptionToInstrumentation(t);
   2615                     }
   2616                 }
   2617             }
   2618 
   2619             @Override
   2620             public long getItemId(int position) {
   2621                 return mItems.get(position).mId;
   2622             }
   2623         };
   2624         adapter.setHasStableIds(true);
   2625 
   2626         final int childCount = 10;
   2627         final TestLayoutManager lm = new TestLayoutManager() {
   2628             @Override
   2629             public void onLayoutChildren(RecyclerView.Recycler recycler, RecyclerView.State state) {
   2630                 super.onLayoutChildren(recycler, state);
   2631                 detachAndScrapAttachedViews(recycler);
   2632                 layoutRange(recycler, layoutStart, layoutStart + childCount);
   2633                 layoutLatch.countDown();
   2634             }
   2635         };
   2636         final RecyclerView recyclerView = new RecyclerView(getActivity());
   2637         recyclerView.setAdapter(adapter);
   2638         recyclerView.setLayoutManager(lm);
   2639         lm.expectLayouts(1);
   2640         setRecyclerView(recyclerView);
   2641         lm.waitForLayout(2);
   2642         getInstrumentation().waitForIdleSync();
   2643         viewType.incrementAndGet();
   2644         lm.expectLayouts(1);
   2645         adapter.changeAndNotify(layoutStart, invalidatedCount);
   2646         lm.waitForLayout(2);
   2647         checkForMainThreadException();
   2648     }
   2649 
   2650 
   2651     @Test
   2652     public void testState() throws Throwable {
   2653         final TestAdapter adapter = new TestAdapter(10);
   2654         final RecyclerView recyclerView = new RecyclerView(getActivity());
   2655         recyclerView.setAdapter(adapter);
   2656         recyclerView.setItemAnimator(null);
   2657         final AtomicInteger itemCount = new AtomicInteger();
   2658         final AtomicBoolean structureChanged = new AtomicBoolean();
   2659         TestLayoutManager testLayoutManager = new TestLayoutManager() {
   2660             @Override
   2661             public void onLayoutChildren(RecyclerView.Recycler recycler, RecyclerView.State state) {
   2662                 detachAndScrapAttachedViews(recycler);
   2663                 layoutRange(recycler, 0, state.getItemCount());
   2664                 itemCount.set(state.getItemCount());
   2665                 structureChanged.set(state.didStructureChange());
   2666                 layoutLatch.countDown();
   2667             }
   2668         };
   2669         recyclerView.setLayoutManager(testLayoutManager);
   2670         testLayoutManager.expectLayouts(1);
   2671         runTestOnUiThread(new Runnable() {
   2672             @Override
   2673             public void run() {
   2674                 getActivity().mContainer.addView(recyclerView);
   2675             }
   2676         });
   2677         testLayoutManager.waitForLayout(2, TimeUnit.SECONDS);
   2678 
   2679         assertEquals("item count in state should be correct", adapter.getItemCount()
   2680                 , itemCount.get());
   2681         assertEquals("structure changed should be true for first layout", true,
   2682                 structureChanged.get());
   2683         Thread.sleep(1000); //wait for other layouts.
   2684         testLayoutManager.expectLayouts(1);
   2685         runTestOnUiThread(new Runnable() {
   2686             @Override
   2687             public void run() {
   2688                 recyclerView.requestLayout();
   2689             }
   2690         });
   2691         testLayoutManager.waitForLayout(2);
   2692         assertEquals("in second layout,structure changed should be false", false,
   2693                 structureChanged.get());
   2694         testLayoutManager.expectLayouts(1); //
   2695         adapter.deleteAndNotify(3, 2);
   2696         testLayoutManager.waitForLayout(2);
   2697         assertEquals("when items are removed, item count in state should be updated",
   2698                 adapter.getItemCount(),
   2699                 itemCount.get());
   2700         assertEquals("structure changed should be true when items are removed", true,
   2701                 structureChanged.get());
   2702         testLayoutManager.expectLayouts(1);
   2703         adapter.addAndNotify(2, 5);
   2704         testLayoutManager.waitForLayout(2);
   2705 
   2706         assertEquals("when items are added, item count in state should be updated",
   2707                 adapter.getItemCount(),
   2708                 itemCount.get());
   2709         assertEquals("structure changed should be true when items are removed", true,
   2710                 structureChanged.get());
   2711     }
   2712 
   2713     @Test
   2714     public void testDetachWithoutLayoutManager() throws Throwable {
   2715         final RecyclerView recyclerView = new RecyclerView(getActivity());
   2716         runTestOnUiThread(new Runnable() {
   2717             @Override
   2718             public void run() {
   2719                 try {
   2720                     setRecyclerView(recyclerView);
   2721                     removeRecyclerView();
   2722                 } catch (Throwable t) {
   2723                     postExceptionToInstrumentation(t);
   2724                 }
   2725             }
   2726         });
   2727         checkForMainThreadException();
   2728     }
   2729 
   2730     @Test
   2731     public void testUpdateHiddenView() throws Throwable {
   2732         final RecyclerView.ViewHolder[] mTargetVH = new RecyclerView.ViewHolder[1];
   2733         final RecyclerView recyclerView = new RecyclerView(getActivity());
   2734         final int[] preLayoutRange = new int[]{0, 10};
   2735         final int[] postLayoutRange = new int[]{0, 10};
   2736         final AtomicBoolean enableGetViewTest = new AtomicBoolean(false);
   2737         final List<Integer> disappearingPositions = new ArrayList<Integer>();
   2738         final TestLayoutManager tlm = new TestLayoutManager() {
   2739             @Override
   2740             public boolean supportsPredictiveItemAnimations() {
   2741                 return true;
   2742             }
   2743 
   2744             @Override
   2745             public void onLayoutChildren(RecyclerView.Recycler recycler, RecyclerView.State state) {
   2746                 try {
   2747                     final int[] layoutRange = state.isPreLayout() ? preLayoutRange
   2748                             : postLayoutRange;
   2749                     detachAndScrapAttachedViews(recycler);
   2750                     layoutRange(recycler, layoutRange[0], layoutRange[1]);
   2751                     if (!state.isPreLayout()) {
   2752                         for (Integer position : disappearingPositions) {
   2753                             // test sanity.
   2754                             assertNull(findViewByPosition(position));
   2755                             final View view = recycler.getViewForPosition(position);
   2756                             addDisappearingView(view);
   2757                             measureChildWithMargins(view, 0, 0);
   2758                             // position item out of bounds.
   2759                             view.layout(0, -500, view.getMeasuredWidth(),
   2760                                     -500 + view.getMeasuredHeight());
   2761                         }
   2762                     }
   2763                 } catch (Throwable t) {
   2764                     postExceptionToInstrumentation(t);
   2765                 }
   2766                 layoutLatch.countDown();
   2767             }
   2768         };
   2769 
   2770         recyclerView.getItemAnimator().setMoveDuration(2000);
   2771         recyclerView.getItemAnimator().setRemoveDuration(2000);
   2772         final TestAdapter adapter = new TestAdapter(100);
   2773         recyclerView.setAdapter(adapter);
   2774         recyclerView.setLayoutManager(tlm);
   2775         tlm.expectLayouts(1);
   2776         setRecyclerView(recyclerView);
   2777 
   2778         tlm.waitForLayout(1);
   2779         checkForMainThreadException();
   2780         mTargetVH[0] = recyclerView.findViewHolderForAdapterPosition(0);
   2781         // now, a child disappears
   2782         disappearingPositions.add(0);
   2783         // layout one shifted
   2784         postLayoutRange[0] = 1;
   2785         postLayoutRange[1] = 11;
   2786         tlm.expectLayouts(2);
   2787         adapter.addAndNotify(8, 1);
   2788         tlm.waitForLayout(2);
   2789         checkForMainThreadException();
   2790 
   2791         tlm.expectLayouts(2);
   2792         disappearingPositions.clear();
   2793         // now that item should be moving, invalidate it and delete it.
   2794         enableGetViewTest.set(true);
   2795         runTestOnUiThread(new Runnable() {
   2796             @Override
   2797             public void run() {
   2798                 try {
   2799                     adapter.changeAndNotify(0, 1);
   2800                     adapter.deleteAndNotify(0, 1);
   2801                 } catch (Throwable throwable) {
   2802                     throwable.printStackTrace();
   2803                 }
   2804             }
   2805         });
   2806         tlm.waitForLayout(2);
   2807         checkForMainThreadException();
   2808     }
   2809 
   2810     @Test
   2811     public void testFocusBigViewOnTop() throws Throwable {
   2812         focusTooBigViewTest(Gravity.TOP);
   2813     }
   2814 
   2815     @Test
   2816     public void testFocusBigViewOnLeft() throws Throwable {
   2817         focusTooBigViewTest(Gravity.LEFT);
   2818     }
   2819 
   2820     @Test
   2821     public void testFocusBigViewOnRight() throws Throwable {
   2822         focusTooBigViewTest(Gravity.RIGHT);
   2823     }
   2824 
   2825     @Test
   2826     public void testFocusBigViewOnBottom() throws Throwable {
   2827         focusTooBigViewTest(Gravity.BOTTOM);
   2828     }
   2829 
   2830     @Test
   2831     public void testFocusBigViewOnLeftRTL() throws Throwable {
   2832         focusTooBigViewTest(Gravity.LEFT, true);
   2833         assertEquals("test sanity", ViewCompat.LAYOUT_DIRECTION_RTL,
   2834                 mRecyclerView.getLayoutManager().getLayoutDirection());
   2835     }
   2836 
   2837     @Test
   2838     public void testFocusBigViewOnRightRTL() throws Throwable {
   2839         focusTooBigViewTest(Gravity.RIGHT, true);
   2840         assertEquals("test sanity", ViewCompat.LAYOUT_DIRECTION_RTL,
   2841                 mRecyclerView.getLayoutManager().getLayoutDirection());
   2842     }
   2843 
   2844     public void focusTooBigViewTest(final int gravity) throws Throwable {
   2845         focusTooBigViewTest(gravity, false);
   2846     }
   2847 
   2848     public void focusTooBigViewTest(final int gravity, final boolean rtl) throws Throwable {
   2849         RecyclerView rv = new RecyclerView(getActivity());
   2850         if (rtl) {
   2851             ViewCompat.setLayoutDirection(rv, ViewCompat.LAYOUT_DIRECTION_RTL);
   2852         }
   2853         final AtomicInteger vScrollDist = new AtomicInteger(0);
   2854         final AtomicInteger hScrollDist = new AtomicInteger(0);
   2855         final AtomicInteger vDesiredDist = new AtomicInteger(0);
   2856         final AtomicInteger hDesiredDist = new AtomicInteger(0);
   2857         TestLayoutManager tlm = new TestLayoutManager() {
   2858 
   2859             @Override
   2860             public int getLayoutDirection() {
   2861                 return rtl ? ViewCompat.LAYOUT_DIRECTION_RTL : ViewCompat.LAYOUT_DIRECTION_LTR;
   2862             }
   2863 
   2864             @Override
   2865             public void onLayoutChildren(RecyclerView.Recycler recycler, RecyclerView.State state) {
   2866                 detachAndScrapAttachedViews(recycler);
   2867                 final View view = recycler.getViewForPosition(0);
   2868                 addView(view);
   2869                 int left = 0, top = 0;
   2870                 view.setBackgroundColor(Color.rgb(0, 0, 255));
   2871                 switch (gravity) {
   2872                     case Gravity.LEFT:
   2873                     case Gravity.RIGHT:
   2874                         view.measure(
   2875                                 View.MeasureSpec.makeMeasureSpec((int) (getWidth() * 1.5),
   2876                                         View.MeasureSpec.EXACTLY),
   2877                                 View.MeasureSpec.makeMeasureSpec((int) (getHeight() * .9),
   2878                                         View.MeasureSpec.AT_MOST));
   2879                         left = gravity == Gravity.LEFT ? getWidth() - view.getMeasuredWidth() - 80
   2880                                 : 90;
   2881                         top = 0;
   2882                         if (ViewCompat.LAYOUT_DIRECTION_RTL == getLayoutDirection()) {
   2883                             hDesiredDist.set((left + view.getMeasuredWidth()) - getWidth());
   2884                         } else {
   2885                             hDesiredDist.set(left);
   2886                         }
   2887                         break;
   2888                     case Gravity.TOP:
   2889                     case Gravity.BOTTOM:
   2890                         view.measure(
   2891                                 View.MeasureSpec.makeMeasureSpec((int) (getWidth() * .9),
   2892                                         View.MeasureSpec.AT_MOST),
   2893                                 View.MeasureSpec.makeMeasureSpec((int) (getHeight() * 1.5),
   2894                                         View.MeasureSpec.EXACTLY));
   2895                         top = gravity == Gravity.TOP ? getHeight() - view.getMeasuredHeight() -
   2896                                 80 : 90;
   2897                         left = 0;
   2898                         vDesiredDist.set(top);
   2899                         break;
   2900                 }
   2901 
   2902                 view.layout(left, top, left + view.getMeasuredWidth(),
   2903                         top + view.getMeasuredHeight());
   2904                 layoutLatch.countDown();
   2905             }
   2906 
   2907             @Override
   2908             public boolean canScrollVertically() {
   2909                 return true;
   2910             }
   2911 
   2912             @Override
   2913             public boolean canScrollHorizontally() {
   2914                 return super.canScrollHorizontally();
   2915             }
   2916 
   2917             @Override
   2918             public int scrollVerticallyBy(int dy, RecyclerView.Recycler recycler,
   2919                     RecyclerView.State state) {
   2920                 vScrollDist.addAndGet(dy);
   2921                 getChildAt(0).offsetTopAndBottom(-dy);
   2922                 return dy;
   2923             }
   2924 
   2925             @Override
   2926             public int scrollHorizontallyBy(int dx, RecyclerView.Recycler recycler,
   2927                     RecyclerView.State state) {
   2928                 hScrollDist.addAndGet(dx);
   2929                 getChildAt(0).offsetLeftAndRight(-dx);
   2930                 return dx;
   2931             }
   2932         };
   2933         TestAdapter adapter = new TestAdapter(10);
   2934         rv.setAdapter(adapter);
   2935         rv.setLayoutManager(tlm);
   2936         tlm.expectLayouts(1);
   2937         setRecyclerView(rv);
   2938         tlm.waitForLayout(2);
   2939         View view = rv.getChildAt(0);
   2940         requestFocus(view);
   2941         Thread.sleep(1000);
   2942         assertEquals(vDesiredDist.get(), vScrollDist.get());
   2943         assertEquals(hDesiredDist.get(), hScrollDist.get());
   2944         assertEquals(mRecyclerView.getPaddingTop(), view.getTop());
   2945         if (rtl) {
   2946             assertEquals(mRecyclerView.getWidth() - mRecyclerView.getPaddingRight(),
   2947                     view.getRight());
   2948         } else {
   2949             assertEquals(mRecyclerView.getPaddingLeft(), view.getLeft());
   2950         }
   2951     }
   2952 
   2953     @Test
   2954     public void testFocusRectOnScreenWithDecorOffsets() throws Throwable {
   2955         focusRectOnScreenTest(true);
   2956     }
   2957 
   2958     @Test
   2959     public void testFocusRectOnScreenWithout() throws Throwable {
   2960         focusRectOnScreenTest(false);
   2961     }
   2962 
   2963 
   2964     public void focusRectOnScreenTest(boolean addItemDecors) throws Throwable {
   2965         RecyclerView rv = new RecyclerView(getActivity());
   2966         final AtomicInteger scrollDist = new AtomicInteger(0);
   2967         TestLayoutManager tlm = new TestLayoutManager() {
   2968             @Override
   2969             public void onLayoutChildren(RecyclerView.Recycler recycler, RecyclerView.State state) {
   2970                 detachAndScrapAttachedViews(recycler);
   2971                 final View view = recycler.getViewForPosition(0);
   2972                 addView(view);
   2973                 measureChildWithMargins(view, 0, 0);
   2974                 view.layout(0, -20, view.getWidth(),
   2975                         -20 + view.getHeight());// ignore decors on purpose
   2976                 layoutLatch.countDown();
   2977             }
   2978 
   2979             @Override
   2980             public boolean canScrollVertically() {
   2981                 return true;
   2982             }
   2983 
   2984             @Override
   2985             public int scrollVerticallyBy(int dy, RecyclerView.Recycler recycler,
   2986                     RecyclerView.State state) {
   2987                 scrollDist.addAndGet(dy);
   2988                 return dy;
   2989             }
   2990         };
   2991         TestAdapter adapter = new TestAdapter(10);
   2992         if (addItemDecors) {
   2993             rv.addItemDecoration(new RecyclerView.ItemDecoration() {
   2994                 @Override
   2995                 public void getItemOffsets(Rect outRect, View view, RecyclerView parent,
   2996                         RecyclerView.State state) {
   2997                     outRect.set(0, 10, 0, 10);
   2998                 }
   2999             });
   3000         }
   3001         rv.setAdapter(adapter);
   3002         rv.setLayoutManager(tlm);
   3003         tlm.expectLayouts(1);
   3004         setRecyclerView(rv);
   3005         tlm.waitForLayout(2);
   3006 
   3007         View view = rv.getChildAt(0);
   3008         requestFocus(view);
   3009         Thread.sleep(1000);
   3010         assertEquals(addItemDecors ? -30 : -20, scrollDist.get());
   3011     }
   3012 
   3013     @Test
   3014     public void testUnimplementedSmoothScroll() throws Throwable {
   3015         final AtomicInteger receivedScrollToPosition = new AtomicInteger(-1);
   3016         final AtomicInteger receivedSmoothScrollToPosition = new AtomicInteger(-1);
   3017         final CountDownLatch cbLatch = new CountDownLatch(2);
   3018         TestLayoutManager tlm = new TestLayoutManager() {
   3019             @Override
   3020             public void onLayoutChildren(RecyclerView.Recycler recycler, RecyclerView.State state) {
   3021                 detachAndScrapAttachedViews(recycler);
   3022                 layoutRange(recycler, 0, 10);
   3023                 layoutLatch.countDown();
   3024             }
   3025 
   3026             @Override
   3027             public void smoothScrollToPosition(RecyclerView recyclerView, RecyclerView.State state,
   3028                     int position) {
   3029                 assertEquals(-1, receivedSmoothScrollToPosition.get());
   3030                 receivedSmoothScrollToPosition.set(position);
   3031                 RecyclerView.SmoothScroller ss =
   3032                         new LinearSmoothScroller(recyclerView.getContext()) {
   3033                             @Override
   3034                             public PointF computeScrollVectorForPosition(int targetPosition) {
   3035                                 return null;
   3036                             }
   3037                         };
   3038                 ss.setTargetPosition(position);
   3039                 startSmoothScroll(ss);
   3040                 cbLatch.countDown();
   3041             }
   3042 
   3043             @Override
   3044             public void scrollToPosition(int position) {
   3045                 assertEquals(-1, receivedScrollToPosition.get());
   3046                 receivedScrollToPosition.set(position);
   3047                 cbLatch.countDown();
   3048             }
   3049         };
   3050         RecyclerView rv = new RecyclerView(getActivity());
   3051         rv.setAdapter(new TestAdapter(100));
   3052         rv.setLayoutManager(tlm);
   3053         tlm.expectLayouts(1);
   3054         setRecyclerView(rv);
   3055         tlm.waitForLayout(2);
   3056         freezeLayout(true);
   3057         smoothScrollToPosition(35);
   3058         assertEquals("smoothScrollToPosition should be ignored when frozen",
   3059                 -1, receivedSmoothScrollToPosition.get());
   3060         freezeLayout(false);
   3061         smoothScrollToPosition(35);
   3062         assertTrue("both scrolls should be called", cbLatch.await(3, TimeUnit.SECONDS));
   3063         checkForMainThreadException();
   3064         assertEquals(35, receivedSmoothScrollToPosition.get());
   3065         assertEquals(35, receivedScrollToPosition.get());
   3066     }
   3067 
   3068     @Test
   3069     public void testJumpingJackSmoothScroller() throws Throwable {
   3070         jumpingJackSmoothScrollerTest(true);
   3071     }
   3072 
   3073     @Test
   3074     public void testJumpingJackSmoothScrollerGoesIdle() throws Throwable {
   3075         jumpingJackSmoothScrollerTest(false);
   3076     }
   3077 
   3078     private void jumpingJackSmoothScrollerTest(final boolean succeed) throws Throwable {
   3079         final List<Integer> receivedScrollToPositions = new ArrayList<>();
   3080         final TestAdapter testAdapter = new TestAdapter(200);
   3081         final AtomicBoolean mTargetFound = new AtomicBoolean(false);
   3082         TestLayoutManager tlm = new TestLayoutManager() {
   3083             int pendingScrollPosition = -1;
   3084             @Override
   3085             public void onLayoutChildren(RecyclerView.Recycler recycler, RecyclerView.State state) {
   3086                 detachAndScrapAttachedViews(recycler);
   3087                 final int pos = pendingScrollPosition < 0 ? 0: pendingScrollPosition;
   3088                 layoutRange(recycler, pos, pos + 10);
   3089                 if (layoutLatch != null) {
   3090                     layoutLatch.countDown();
   3091                 }
   3092             }
   3093 
   3094             @Override
   3095             public void smoothScrollToPosition(RecyclerView recyclerView, RecyclerView.State state,
   3096                     final int position) {
   3097                 RecyclerView.SmoothScroller ss =
   3098                         new LinearSmoothScroller(recyclerView.getContext()) {
   3099                             @Override
   3100                             public PointF computeScrollVectorForPosition(int targetPosition) {
   3101                                 return new PointF(0, 1);
   3102                             }
   3103 
   3104                             @Override
   3105                             protected void onTargetFound(View targetView, RecyclerView.State state,
   3106                                                          Action action) {
   3107                                 super.onTargetFound(targetView, state, action);
   3108                                 mTargetFound.set(true);
   3109                             }
   3110 
   3111                             @Override
   3112                             protected void updateActionForInterimTarget(Action action) {
   3113                                 int limit = succeed ? getTargetPosition() : 100;
   3114                                 if (pendingScrollPosition + 2 < limit) {
   3115                                     if (pendingScrollPosition != NO_POSITION) {
   3116                                         assertEquals(pendingScrollPosition,
   3117                                                 getChildViewHolderInt(getChildAt(0))
   3118                                                         .getAdapterPosition());
   3119                                     }
   3120                                     action.jumpTo(pendingScrollPosition + 2);
   3121                                 }
   3122                             }
   3123                         };
   3124                 ss.setTargetPosition(position);
   3125                 startSmoothScroll(ss);
   3126             }
   3127 
   3128             @Override
   3129             public void scrollToPosition(int position) {
   3130                 receivedScrollToPositions.add(position);
   3131                 pendingScrollPosition = position;
   3132                 requestLayout();
   3133             }
   3134         };
   3135         final RecyclerView rv = new RecyclerView(getActivity());
   3136         rv.setAdapter(testAdapter);
   3137         rv.setLayoutManager(tlm);
   3138 
   3139         tlm.expectLayouts(1);
   3140         setRecyclerView(rv);
   3141         tlm.waitForLayout(2);
   3142 
   3143         runTestOnUiThread(new Runnable() {
   3144             @Override
   3145             public void run() {
   3146                 rv.smoothScrollToPosition(150);
   3147             }
   3148         });
   3149         int limit = 100;
   3150         while (rv.getLayoutManager().isSmoothScrolling() && --limit > 0) {
   3151             Thread.sleep(200);
   3152             checkForMainThreadException();
   3153         }
   3154         checkForMainThreadException();
   3155         assertTrue(limit > 0);
   3156         for (int i = 1; i < 100; i+=2) {
   3157             assertTrue("scroll positions must include " + i, receivedScrollToPositions.contains(i));
   3158         }
   3159 
   3160         assertEquals(succeed, mTargetFound.get());
   3161 
   3162     }
   3163 
   3164     private static class TestViewHolder2 extends RecyclerView.ViewHolder {
   3165 
   3166         Object mData;
   3167 
   3168         public TestViewHolder2(View itemView) {
   3169             super(itemView);
   3170         }
   3171     }
   3172 
   3173     private static class TestAdapter2 extends RecyclerView.Adapter<TestViewHolder2> {
   3174 
   3175         List<Item> mItems;
   3176 
   3177         private TestAdapter2(int count) {
   3178             mItems = new ArrayList<Item>(count);
   3179             for (int i = 0; i < count; i++) {
   3180                 mItems.add(new Item(i, "Item " + i));
   3181             }
   3182         }
   3183 
   3184         @Override
   3185         public TestViewHolder2 onCreateViewHolder(ViewGroup parent,
   3186                 int viewType) {
   3187             return new TestViewHolder2(new TextView(parent.getContext()));
   3188         }
   3189 
   3190         @Override
   3191         public void onBindViewHolder(TestViewHolder2 holder, int position) {
   3192             final Item item = mItems.get(position);
   3193             ((TextView) (holder.itemView)).setText(item.mText + "(" + item.mAdapterIndex + ")");
   3194         }
   3195 
   3196         @Override
   3197         public int getItemCount() {
   3198             return mItems.size();
   3199         }
   3200     }
   3201 
   3202     private static interface AdapterRunnable {
   3203 
   3204         public void run(TestAdapter adapter) throws Throwable;
   3205     }
   3206 
   3207 }
   3208