BaseRecyclerViewInstrumentationTest.java revision c50c4cad31d73e574b27bb3d7581542975e37263
1d7d27e9ebe5c7325e67e1a8af265378bd2056cadChet Haase/*
2d7d27e9ebe5c7325e67e1a8af265378bd2056cadChet Haase * Copyright (C) 2014 The Android Open Source Project
3d7d27e9ebe5c7325e67e1a8af265378bd2056cadChet Haase *
4d7d27e9ebe5c7325e67e1a8af265378bd2056cadChet Haase * Licensed under the Apache License, Version 2.0 (the "License");
5d7d27e9ebe5c7325e67e1a8af265378bd2056cadChet Haase * you may not use this file except in compliance with the License.
6d7d27e9ebe5c7325e67e1a8af265378bd2056cadChet Haase * You may obtain a copy of the License at
7d7d27e9ebe5c7325e67e1a8af265378bd2056cadChet Haase *
8d7d27e9ebe5c7325e67e1a8af265378bd2056cadChet Haase *      http://www.apache.org/licenses/LICENSE-2.0
9d7d27e9ebe5c7325e67e1a8af265378bd2056cadChet Haase *
10d7d27e9ebe5c7325e67e1a8af265378bd2056cadChet Haase * Unless required by applicable law or agreed to in writing, software
11d7d27e9ebe5c7325e67e1a8af265378bd2056cadChet Haase * distributed under the License is distributed on an "AS IS" BASIS,
12d7d27e9ebe5c7325e67e1a8af265378bd2056cadChet Haase * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13d7d27e9ebe5c7325e67e1a8af265378bd2056cadChet Haase * See the License for the specific language governing permissions and
14d7d27e9ebe5c7325e67e1a8af265378bd2056cadChet Haase * limitations under the License.
15d7d27e9ebe5c7325e67e1a8af265378bd2056cadChet Haase */
16d7d27e9ebe5c7325e67e1a8af265378bd2056cadChet Haase
17d7d27e9ebe5c7325e67e1a8af265378bd2056cadChet Haasepackage android.support.v7.widget;
18d7d27e9ebe5c7325e67e1a8af265378bd2056cadChet Haase
19c50c4cad31d73e574b27bb3d7581542975e37263Yigit Boyarimport android.graphics.Rect;
208ae76f91527ce850f155ce960fb9068bcd5d49f9Yigit Boyarimport android.os.Looper;
21d7d27e9ebe5c7325e67e1a8af265378bd2056cadChet Haaseimport android.test.ActivityInstrumentationTestCase2;
22d7d27e9ebe5c7325e67e1a8af265378bd2056cadChet Haaseimport android.util.Log;
23d7d27e9ebe5c7325e67e1a8af265378bd2056cadChet Haaseimport android.view.View;
24d7d27e9ebe5c7325e67e1a8af265378bd2056cadChet Haaseimport android.view.ViewGroup;
25310e95e1c6dfe4f26ef594233e65e1ff83e0f1ffYigit Boyarimport android.widget.FrameLayout;
26d7d27e9ebe5c7325e67e1a8af265378bd2056cadChet Haaseimport android.widget.TextView;
27d7d27e9ebe5c7325e67e1a8af265378bd2056cadChet Haase
28d7d27e9ebe5c7325e67e1a8af265378bd2056cadChet Haaseimport java.util.ArrayList;
29d7d27e9ebe5c7325e67e1a8af265378bd2056cadChet Haaseimport java.util.List;
30d7d27e9ebe5c7325e67e1a8af265378bd2056cadChet Haaseimport java.util.concurrent.CountDownLatch;
31d7d27e9ebe5c7325e67e1a8af265378bd2056cadChet Haaseimport java.util.concurrent.TimeUnit;
3211b7cfea15306c0d6e4ed7da09f7698df01695dfYigit Boyarimport java.util.concurrent.atomic.AtomicInteger;
33d7d27e9ebe5c7325e67e1a8af265378bd2056cadChet Haase
34d7d27e9ebe5c7325e67e1a8af265378bd2056cadChet Haaseabstract public class BaseRecyclerViewInstrumentationTest extends
35d7d27e9ebe5c7325e67e1a8af265378bd2056cadChet Haase        ActivityInstrumentationTestCase2<TestActivity> {
36d7d27e9ebe5c7325e67e1a8af265378bd2056cadChet Haase
37d7d27e9ebe5c7325e67e1a8af265378bd2056cadChet Haase    private static final String TAG = "RecyclerViewTest";
38d7d27e9ebe5c7325e67e1a8af265378bd2056cadChet Haase
39b5f8b4fecb531aee6b359b4968409410aba90817Yigit Boyar    private boolean mDebug;
40b5f8b4fecb531aee6b359b4968409410aba90817Yigit Boyar
41b5f8b4fecb531aee6b359b4968409410aba90817Yigit Boyar    protected RecyclerView mRecyclerView;
42d7d27e9ebe5c7325e67e1a8af265378bd2056cadChet Haase
438ae76f91527ce850f155ce960fb9068bcd5d49f9Yigit Boyar    protected AdapterHelper mAdapterHelper;
448ae76f91527ce850f155ce960fb9068bcd5d49f9Yigit Boyar
457e9da1a2629b657d56e2f54abd18c6249f3be037Yigit Boyar    Throwable mainThreadException;
467e9da1a2629b657d56e2f54abd18c6249f3be037Yigit Boyar
47d7d27e9ebe5c7325e67e1a8af265378bd2056cadChet Haase    public BaseRecyclerViewInstrumentationTest() {
48b5f8b4fecb531aee6b359b4968409410aba90817Yigit Boyar        this(false);
49b5f8b4fecb531aee6b359b4968409410aba90817Yigit Boyar    }
50b5f8b4fecb531aee6b359b4968409410aba90817Yigit Boyar
51b5f8b4fecb531aee6b359b4968409410aba90817Yigit Boyar    public BaseRecyclerViewInstrumentationTest(boolean debug) {
528ae76f91527ce850f155ce960fb9068bcd5d49f9Yigit Boyar        super("android.support.v7.recyclerview", TestActivity.class);
53b5f8b4fecb531aee6b359b4968409410aba90817Yigit Boyar        mDebug = debug;
54b5f8b4fecb531aee6b359b4968409410aba90817Yigit Boyar    }
55b5f8b4fecb531aee6b359b4968409410aba90817Yigit Boyar
567e9da1a2629b657d56e2f54abd18c6249f3be037Yigit Boyar    void checkForMainThreadException() throws Throwable {
577e9da1a2629b657d56e2f54abd18c6249f3be037Yigit Boyar        if (mainThreadException != null) {
587e9da1a2629b657d56e2f54abd18c6249f3be037Yigit Boyar            throw mainThreadException;
597e9da1a2629b657d56e2f54abd18c6249f3be037Yigit Boyar        }
607e9da1a2629b657d56e2f54abd18c6249f3be037Yigit Boyar    }
617e9da1a2629b657d56e2f54abd18c6249f3be037Yigit Boyar
620b1059b711009601ff08354e2df6bfc45266e80aYigit Boyar    void setAdapter(final RecyclerView.Adapter adapter) throws Throwable {
630b1059b711009601ff08354e2df6bfc45266e80aYigit Boyar        runTestOnUiThread(new Runnable() {
640b1059b711009601ff08354e2df6bfc45266e80aYigit Boyar            @Override
650b1059b711009601ff08354e2df6bfc45266e80aYigit Boyar            public void run() {
660b1059b711009601ff08354e2df6bfc45266e80aYigit Boyar                mRecyclerView.setAdapter(adapter);
670b1059b711009601ff08354e2df6bfc45266e80aYigit Boyar            }
680b1059b711009601ff08354e2df6bfc45266e80aYigit Boyar        });
690b1059b711009601ff08354e2df6bfc45266e80aYigit Boyar    }
700b1059b711009601ff08354e2df6bfc45266e80aYigit Boyar
710b1059b711009601ff08354e2df6bfc45266e80aYigit Boyar    void swapAdapter(final RecyclerView.Adapter adapter,
720b1059b711009601ff08354e2df6bfc45266e80aYigit Boyar            final boolean removeAndRecycleExistingViews) throws Throwable {
730b1059b711009601ff08354e2df6bfc45266e80aYigit Boyar        runTestOnUiThread(new Runnable() {
740b1059b711009601ff08354e2df6bfc45266e80aYigit Boyar            @Override
750b1059b711009601ff08354e2df6bfc45266e80aYigit Boyar            public void run() {
760b1059b711009601ff08354e2df6bfc45266e80aYigit Boyar                mRecyclerView.swapAdapter(adapter, removeAndRecycleExistingViews);
770b1059b711009601ff08354e2df6bfc45266e80aYigit Boyar            }
780b1059b711009601ff08354e2df6bfc45266e80aYigit Boyar        });
790b1059b711009601ff08354e2df6bfc45266e80aYigit Boyar    }
800b1059b711009601ff08354e2df6bfc45266e80aYigit Boyar
817e9da1a2629b657d56e2f54abd18c6249f3be037Yigit Boyar    void postExceptionToInstrumentation(Throwable t) {
827e9da1a2629b657d56e2f54abd18c6249f3be037Yigit Boyar        if (mDebug) {
837e9da1a2629b657d56e2f54abd18c6249f3be037Yigit Boyar            Log.e(TAG, "captured exception on main thread", t);
847e9da1a2629b657d56e2f54abd18c6249f3be037Yigit Boyar        }
856e83751247c5be0211d7bffaf057431c03dfef38Yigit Boyar        if (mainThreadException != null) {
866e83751247c5be0211d7bffaf057431c03dfef38Yigit Boyar            Log.e(TAG, "receiving another main thread exception. dropping.", t);
876e83751247c5be0211d7bffaf057431c03dfef38Yigit Boyar        } else {
886e83751247c5be0211d7bffaf057431c03dfef38Yigit Boyar            mainThreadException = t;
896e83751247c5be0211d7bffaf057431c03dfef38Yigit Boyar        }
906e83751247c5be0211d7bffaf057431c03dfef38Yigit Boyar
917e9da1a2629b657d56e2f54abd18c6249f3be037Yigit Boyar        if (mRecyclerView != null && mRecyclerView
927e9da1a2629b657d56e2f54abd18c6249f3be037Yigit Boyar                .getLayoutManager() instanceof TestLayoutManager) {
937e9da1a2629b657d56e2f54abd18c6249f3be037Yigit Boyar            TestLayoutManager lm = (TestLayoutManager) mRecyclerView.getLayoutManager();
947e9da1a2629b657d56e2f54abd18c6249f3be037Yigit Boyar            // finish all layouts so that we get the correct exception
957e9da1a2629b657d56e2f54abd18c6249f3be037Yigit Boyar            while (lm.layoutLatch.getCount() > 0) {
967e9da1a2629b657d56e2f54abd18c6249f3be037Yigit Boyar                lm.layoutLatch.countDown();
977e9da1a2629b657d56e2f54abd18c6249f3be037Yigit Boyar            }
987e9da1a2629b657d56e2f54abd18c6249f3be037Yigit Boyar        }
997e9da1a2629b657d56e2f54abd18c6249f3be037Yigit Boyar    }
1007e9da1a2629b657d56e2f54abd18c6249f3be037Yigit Boyar
1018ae76f91527ce850f155ce960fb9068bcd5d49f9Yigit Boyar    @Override
1028ae76f91527ce850f155ce960fb9068bcd5d49f9Yigit Boyar    protected void tearDown() throws Exception {
1038ae76f91527ce850f155ce960fb9068bcd5d49f9Yigit Boyar        if (mRecyclerView != null) {
1048ae76f91527ce850f155ce960fb9068bcd5d49f9Yigit Boyar            try {
1058ae76f91527ce850f155ce960fb9068bcd5d49f9Yigit Boyar                removeRecyclerView();
1068ae76f91527ce850f155ce960fb9068bcd5d49f9Yigit Boyar            } catch (Throwable throwable) {
1078ae76f91527ce850f155ce960fb9068bcd5d49f9Yigit Boyar                throwable.printStackTrace();
1088ae76f91527ce850f155ce960fb9068bcd5d49f9Yigit Boyar            }
1098ae76f91527ce850f155ce960fb9068bcd5d49f9Yigit Boyar        }
1108ae76f91527ce850f155ce960fb9068bcd5d49f9Yigit Boyar        getInstrumentation().waitForIdleSync();
1116e83751247c5be0211d7bffaf057431c03dfef38Yigit Boyar        try {
1126e83751247c5be0211d7bffaf057431c03dfef38Yigit Boyar            checkForMainThreadException();
1136e83751247c5be0211d7bffaf057431c03dfef38Yigit Boyar        } catch (Exception e) {
1146e83751247c5be0211d7bffaf057431c03dfef38Yigit Boyar            throw e;
1156e83751247c5be0211d7bffaf057431c03dfef38Yigit Boyar        } catch (Throwable throwable) {
1166e83751247c5be0211d7bffaf057431c03dfef38Yigit Boyar            throwable.printStackTrace();
1176e83751247c5be0211d7bffaf057431c03dfef38Yigit Boyar        }
1188ae76f91527ce850f155ce960fb9068bcd5d49f9Yigit Boyar        super.tearDown();
1198ae76f91527ce850f155ce960fb9068bcd5d49f9Yigit Boyar    }
1208ae76f91527ce850f155ce960fb9068bcd5d49f9Yigit Boyar
121c50c4cad31d73e574b27bb3d7581542975e37263Yigit Boyar    public Rect getDecoratedRecyclerViewBounds() {
122c50c4cad31d73e574b27bb3d7581542975e37263Yigit Boyar        return new Rect(
123c50c4cad31d73e574b27bb3d7581542975e37263Yigit Boyar                mRecyclerView.getPaddingLeft(),
124c50c4cad31d73e574b27bb3d7581542975e37263Yigit Boyar                mRecyclerView.getPaddingTop(),
125c50c4cad31d73e574b27bb3d7581542975e37263Yigit Boyar                mRecyclerView.getPaddingLeft() + mRecyclerView.getWidth(),
126c50c4cad31d73e574b27bb3d7581542975e37263Yigit Boyar                mRecyclerView.getPaddingTop() + mRecyclerView.getHeight()
127c50c4cad31d73e574b27bb3d7581542975e37263Yigit Boyar        );
128c50c4cad31d73e574b27bb3d7581542975e37263Yigit Boyar    }
129c50c4cad31d73e574b27bb3d7581542975e37263Yigit Boyar
130b5f8b4fecb531aee6b359b4968409410aba90817Yigit Boyar    public void removeRecyclerView() throws Throwable {
131b5f8b4fecb531aee6b359b4968409410aba90817Yigit Boyar        mRecyclerView = null;
132b5f8b4fecb531aee6b359b4968409410aba90817Yigit Boyar        runTestOnUiThread(new Runnable() {
133b5f8b4fecb531aee6b359b4968409410aba90817Yigit Boyar            @Override
134b5f8b4fecb531aee6b359b4968409410aba90817Yigit Boyar            public void run() {
135b5f8b4fecb531aee6b359b4968409410aba90817Yigit Boyar                getActivity().mContainer.removeAllViews();
136b5f8b4fecb531aee6b359b4968409410aba90817Yigit Boyar            }
137b5f8b4fecb531aee6b359b4968409410aba90817Yigit Boyar        });
138b5f8b4fecb531aee6b359b4968409410aba90817Yigit Boyar    }
139b5f8b4fecb531aee6b359b4968409410aba90817Yigit Boyar
140504c54ea52c1b2aae6f8f4ae128f1dcaac7e3f6aYigit Boyar    void waitForAnimations(int seconds) throws InterruptedException {
141504c54ea52c1b2aae6f8f4ae128f1dcaac7e3f6aYigit Boyar        final CountDownLatch latch = new CountDownLatch(2);
142504c54ea52c1b2aae6f8f4ae128f1dcaac7e3f6aYigit Boyar        boolean running = mRecyclerView.mItemAnimator
143504c54ea52c1b2aae6f8f4ae128f1dcaac7e3f6aYigit Boyar                .isRunning(new RecyclerView.ItemAnimator.ItemAnimatorFinishedListener() {
144504c54ea52c1b2aae6f8f4ae128f1dcaac7e3f6aYigit Boyar                    @Override
145504c54ea52c1b2aae6f8f4ae128f1dcaac7e3f6aYigit Boyar                    public void onAnimationsFinished() {
146504c54ea52c1b2aae6f8f4ae128f1dcaac7e3f6aYigit Boyar                        latch.countDown();
147504c54ea52c1b2aae6f8f4ae128f1dcaac7e3f6aYigit Boyar                    }
148504c54ea52c1b2aae6f8f4ae128f1dcaac7e3f6aYigit Boyar                });
149504c54ea52c1b2aae6f8f4ae128f1dcaac7e3f6aYigit Boyar        if (running) {
150504c54ea52c1b2aae6f8f4ae128f1dcaac7e3f6aYigit Boyar            latch.await(seconds, TimeUnit.SECONDS);
151504c54ea52c1b2aae6f8f4ae128f1dcaac7e3f6aYigit Boyar        }
152504c54ea52c1b2aae6f8f4ae128f1dcaac7e3f6aYigit Boyar    }
153504c54ea52c1b2aae6f8f4ae128f1dcaac7e3f6aYigit Boyar
154b5f8b4fecb531aee6b359b4968409410aba90817Yigit Boyar    public void setRecyclerView(final RecyclerView recyclerView) throws Throwable {
1550b1059b711009601ff08354e2df6bfc45266e80aYigit Boyar        setRecyclerView(recyclerView, true);
1560b1059b711009601ff08354e2df6bfc45266e80aYigit Boyar    }
1570b1059b711009601ff08354e2df6bfc45266e80aYigit Boyar    public void setRecyclerView(final RecyclerView recyclerView, boolean assignDummyPool)
1580b1059b711009601ff08354e2df6bfc45266e80aYigit Boyar            throws Throwable {
159b5f8b4fecb531aee6b359b4968409410aba90817Yigit Boyar        mRecyclerView = recyclerView;
1600b1059b711009601ff08354e2df6bfc45266e80aYigit Boyar        if (assignDummyPool) {
1610b1059b711009601ff08354e2df6bfc45266e80aYigit Boyar            RecyclerView.RecycledViewPool pool = new RecyclerView.RecycledViewPool() {
1620b1059b711009601ff08354e2df6bfc45266e80aYigit Boyar                @Override
1630b1059b711009601ff08354e2df6bfc45266e80aYigit Boyar                public RecyclerView.ViewHolder getRecycledView(int viewType) {
1640b1059b711009601ff08354e2df6bfc45266e80aYigit Boyar                    RecyclerView.ViewHolder viewHolder = super.getRecycledView(viewType);
1650b1059b711009601ff08354e2df6bfc45266e80aYigit Boyar                    if (viewHolder == null) {
1660b1059b711009601ff08354e2df6bfc45266e80aYigit Boyar                        return null;
1670b1059b711009601ff08354e2df6bfc45266e80aYigit Boyar                    }
1680b1059b711009601ff08354e2df6bfc45266e80aYigit Boyar                    viewHolder.addFlags(RecyclerView.ViewHolder.FLAG_BOUND);
1690b1059b711009601ff08354e2df6bfc45266e80aYigit Boyar                    viewHolder.mPosition = 200;
1700b1059b711009601ff08354e2df6bfc45266e80aYigit Boyar                    viewHolder.mOldPosition = 300;
1710b1059b711009601ff08354e2df6bfc45266e80aYigit Boyar                    viewHolder.mPreLayoutPosition = 500;
1720b1059b711009601ff08354e2df6bfc45266e80aYigit Boyar                    return viewHolder;
1738da5bba529a2fe9bd2aafab28010e11ec4de93c7Yigit Boyar                }
1748da5bba529a2fe9bd2aafab28010e11ec4de93c7Yigit Boyar
1750b1059b711009601ff08354e2df6bfc45266e80aYigit Boyar                @Override
1760b1059b711009601ff08354e2df6bfc45266e80aYigit Boyar                public void putRecycledView(RecyclerView.ViewHolder scrap) {
1770b1059b711009601ff08354e2df6bfc45266e80aYigit Boyar                    super.putRecycledView(scrap);
1780b1059b711009601ff08354e2df6bfc45266e80aYigit Boyar                }
1790b1059b711009601ff08354e2df6bfc45266e80aYigit Boyar            };
1800b1059b711009601ff08354e2df6bfc45266e80aYigit Boyar            mRecyclerView.setRecycledViewPool(pool);
1810b1059b711009601ff08354e2df6bfc45266e80aYigit Boyar        }
1828ae76f91527ce850f155ce960fb9068bcd5d49f9Yigit Boyar        mAdapterHelper = recyclerView.mAdapterHelper;
183b5f8b4fecb531aee6b359b4968409410aba90817Yigit Boyar        runTestOnUiThread(new Runnable() {
184b5f8b4fecb531aee6b359b4968409410aba90817Yigit Boyar            @Override
185b5f8b4fecb531aee6b359b4968409410aba90817Yigit Boyar            public void run() {
186b5f8b4fecb531aee6b359b4968409410aba90817Yigit Boyar                getActivity().mContainer.addView(recyclerView);
187b5f8b4fecb531aee6b359b4968409410aba90817Yigit Boyar            }
188b5f8b4fecb531aee6b359b4968409410aba90817Yigit Boyar        });
189b5f8b4fecb531aee6b359b4968409410aba90817Yigit Boyar    }
190b5f8b4fecb531aee6b359b4968409410aba90817Yigit Boyar
191310e95e1c6dfe4f26ef594233e65e1ff83e0f1ffYigit Boyar    protected FrameLayout getRecyclerViewContainer() {
192310e95e1c6dfe4f26ef594233e65e1ff83e0f1ffYigit Boyar        return getActivity().mContainer;
193310e95e1c6dfe4f26ef594233e65e1ff83e0f1ffYigit Boyar    }
194310e95e1c6dfe4f26ef594233e65e1ff83e0f1ffYigit Boyar
195b5f8b4fecb531aee6b359b4968409410aba90817Yigit Boyar    public void requestLayoutOnUIThread(final View view) throws Throwable {
196b5f8b4fecb531aee6b359b4968409410aba90817Yigit Boyar        runTestOnUiThread(new Runnable() {
197b5f8b4fecb531aee6b359b4968409410aba90817Yigit Boyar            @Override
198b5f8b4fecb531aee6b359b4968409410aba90817Yigit Boyar            public void run() {
199b5f8b4fecb531aee6b359b4968409410aba90817Yigit Boyar                view.requestLayout();
200b5f8b4fecb531aee6b359b4968409410aba90817Yigit Boyar            }
201b5f8b4fecb531aee6b359b4968409410aba90817Yigit Boyar        });
202b5f8b4fecb531aee6b359b4968409410aba90817Yigit Boyar    }
203b5f8b4fecb531aee6b359b4968409410aba90817Yigit Boyar
204b5f8b4fecb531aee6b359b4968409410aba90817Yigit Boyar    public void scrollBy(final int dt) throws Throwable {
205b5f8b4fecb531aee6b359b4968409410aba90817Yigit Boyar        runTestOnUiThread(new Runnable() {
206b5f8b4fecb531aee6b359b4968409410aba90817Yigit Boyar            @Override
207b5f8b4fecb531aee6b359b4968409410aba90817Yigit Boyar            public void run() {
208b5f8b4fecb531aee6b359b4968409410aba90817Yigit Boyar                if (mRecyclerView.getLayoutManager().canScrollHorizontally()) {
209b5f8b4fecb531aee6b359b4968409410aba90817Yigit Boyar                    mRecyclerView.scrollBy(dt, 0);
210b5f8b4fecb531aee6b359b4968409410aba90817Yigit Boyar                } else {
211b5f8b4fecb531aee6b359b4968409410aba90817Yigit Boyar                    mRecyclerView.scrollBy(0, dt);
212b5f8b4fecb531aee6b359b4968409410aba90817Yigit Boyar                }
213b5f8b4fecb531aee6b359b4968409410aba90817Yigit Boyar
214b5f8b4fecb531aee6b359b4968409410aba90817Yigit Boyar            }
215b5f8b4fecb531aee6b359b4968409410aba90817Yigit Boyar        });
216d7d27e9ebe5c7325e67e1a8af265378bd2056cadChet Haase    }
217d7d27e9ebe5c7325e67e1a8af265378bd2056cadChet Haase
2188edcb0bdeaba6931f9d8154f0c81f57da7ddab2aYigit Boyar    void scrollToPosition(final int position) throws Throwable {
2198edcb0bdeaba6931f9d8154f0c81f57da7ddab2aYigit Boyar        runTestOnUiThread(new Runnable() {
2208edcb0bdeaba6931f9d8154f0c81f57da7ddab2aYigit Boyar            @Override
2218edcb0bdeaba6931f9d8154f0c81f57da7ddab2aYigit Boyar            public void run() {
2228edcb0bdeaba6931f9d8154f0c81f57da7ddab2aYigit Boyar                mRecyclerView.getLayoutManager().scrollToPosition(position);
2238edcb0bdeaba6931f9d8154f0c81f57da7ddab2aYigit Boyar            }
2248edcb0bdeaba6931f9d8154f0c81f57da7ddab2aYigit Boyar        });
2258edcb0bdeaba6931f9d8154f0c81f57da7ddab2aYigit Boyar    }
2268edcb0bdeaba6931f9d8154f0c81f57da7ddab2aYigit Boyar
2278edcb0bdeaba6931f9d8154f0c81f57da7ddab2aYigit Boyar    void smoothScrollToPosition(final int position)
2288edcb0bdeaba6931f9d8154f0c81f57da7ddab2aYigit Boyar            throws Throwable {
2298edcb0bdeaba6931f9d8154f0c81f57da7ddab2aYigit Boyar        Log.d(TAG, "SMOOTH scrolling to " + position);
2308edcb0bdeaba6931f9d8154f0c81f57da7ddab2aYigit Boyar        runTestOnUiThread(new Runnable() {
2318edcb0bdeaba6931f9d8154f0c81f57da7ddab2aYigit Boyar            @Override
2328edcb0bdeaba6931f9d8154f0c81f57da7ddab2aYigit Boyar            public void run() {
2338edcb0bdeaba6931f9d8154f0c81f57da7ddab2aYigit Boyar                mRecyclerView.smoothScrollToPosition(position);
2348edcb0bdeaba6931f9d8154f0c81f57da7ddab2aYigit Boyar            }
2358edcb0bdeaba6931f9d8154f0c81f57da7ddab2aYigit Boyar        });
2368edcb0bdeaba6931f9d8154f0c81f57da7ddab2aYigit Boyar        while (mRecyclerView.getLayoutManager().isSmoothScrolling() ||
2378edcb0bdeaba6931f9d8154f0c81f57da7ddab2aYigit Boyar                mRecyclerView.getScrollState() != RecyclerView.SCROLL_STATE_IDLE) {
2388edcb0bdeaba6931f9d8154f0c81f57da7ddab2aYigit Boyar            if (mDebug) {
2398edcb0bdeaba6931f9d8154f0c81f57da7ddab2aYigit Boyar                Log.d(TAG, "SMOOTH scrolling step");
2408edcb0bdeaba6931f9d8154f0c81f57da7ddab2aYigit Boyar            }
2418edcb0bdeaba6931f9d8154f0c81f57da7ddab2aYigit Boyar            Thread.sleep(200);
2428edcb0bdeaba6931f9d8154f0c81f57da7ddab2aYigit Boyar        }
2438edcb0bdeaba6931f9d8154f0c81f57da7ddab2aYigit Boyar        Log.d(TAG, "SMOOTH scrolling done");
244504c54ea52c1b2aae6f8f4ae128f1dcaac7e3f6aYigit Boyar        getInstrumentation().waitForIdleSync();
2458edcb0bdeaba6931f9d8154f0c81f57da7ddab2aYigit Boyar    }
2468edcb0bdeaba6931f9d8154f0c81f57da7ddab2aYigit Boyar
247d7d27e9ebe5c7325e67e1a8af265378bd2056cadChet Haase    class TestViewHolder extends RecyclerView.ViewHolder {
248d7d27e9ebe5c7325e67e1a8af265378bd2056cadChet Haase
249d7d27e9ebe5c7325e67e1a8af265378bd2056cadChet Haase        Item mBindedItem;
250d7d27e9ebe5c7325e67e1a8af265378bd2056cadChet Haase
251d7d27e9ebe5c7325e67e1a8af265378bd2056cadChet Haase        public TestViewHolder(View itemView) {
252d7d27e9ebe5c7325e67e1a8af265378bd2056cadChet Haase            super(itemView);
253310e95e1c6dfe4f26ef594233e65e1ff83e0f1ffYigit Boyar            itemView.setFocusable(true);
254d7d27e9ebe5c7325e67e1a8af265378bd2056cadChet Haase        }
255504c54ea52c1b2aae6f8f4ae128f1dcaac7e3f6aYigit Boyar
256504c54ea52c1b2aae6f8f4ae128f1dcaac7e3f6aYigit Boyar        @Override
257504c54ea52c1b2aae6f8f4ae128f1dcaac7e3f6aYigit Boyar        public String toString() {
258504c54ea52c1b2aae6f8f4ae128f1dcaac7e3f6aYigit Boyar            return super.toString() + " item:" + mBindedItem;
259504c54ea52c1b2aae6f8f4ae128f1dcaac7e3f6aYigit Boyar        }
260d7d27e9ebe5c7325e67e1a8af265378bd2056cadChet Haase    }
261d7d27e9ebe5c7325e67e1a8af265378bd2056cadChet Haase
262d7d27e9ebe5c7325e67e1a8af265378bd2056cadChet Haase    class TestLayoutManager extends RecyclerView.LayoutManager {
263d7d27e9ebe5c7325e67e1a8af265378bd2056cadChet Haase
264d7d27e9ebe5c7325e67e1a8af265378bd2056cadChet Haase        CountDownLatch layoutLatch;
265d7d27e9ebe5c7325e67e1a8af265378bd2056cadChet Haase
266d7d27e9ebe5c7325e67e1a8af265378bd2056cadChet Haase        public void expectLayouts(int count) {
267d7d27e9ebe5c7325e67e1a8af265378bd2056cadChet Haase            layoutLatch = new CountDownLatch(count);
268d7d27e9ebe5c7325e67e1a8af265378bd2056cadChet Haase        }
269d7d27e9ebe5c7325e67e1a8af265378bd2056cadChet Haase
270b5f8b4fecb531aee6b359b4968409410aba90817Yigit Boyar        public void waitForLayout(long timeout, TimeUnit timeUnit) throws Throwable {
271b5f8b4fecb531aee6b359b4968409410aba90817Yigit Boyar            layoutLatch.await(timeout * (mDebug ? 100 : 1), timeUnit);
272d7d27e9ebe5c7325e67e1a8af265378bd2056cadChet Haase            assertEquals("all expected layouts should be executed at the expected time",
273d7d27e9ebe5c7325e67e1a8af265378bd2056cadChet Haase                    0, layoutLatch.getCount());
274e0c347f627f8a78d3e5e3e5eaac9c3ae26208689Yigit Boyar            getInstrumentation().waitForIdleSync();
275d7d27e9ebe5c7325e67e1a8af265378bd2056cadChet Haase        }
276d7d27e9ebe5c7325e67e1a8af265378bd2056cadChet Haase
277b5f8b4fecb531aee6b359b4968409410aba90817Yigit Boyar        public void assertLayoutCount(int count, String msg, long timeout) throws Throwable {
278b5f8b4fecb531aee6b359b4968409410aba90817Yigit Boyar            layoutLatch.await(timeout, TimeUnit.SECONDS);
279b5f8b4fecb531aee6b359b4968409410aba90817Yigit Boyar            assertEquals(msg, count, layoutLatch.getCount());
280b5f8b4fecb531aee6b359b4968409410aba90817Yigit Boyar        }
281b5f8b4fecb531aee6b359b4968409410aba90817Yigit Boyar
282b5f8b4fecb531aee6b359b4968409410aba90817Yigit Boyar        public void assertNoLayout(String msg, long timeout) throws Throwable {
283b5f8b4fecb531aee6b359b4968409410aba90817Yigit Boyar            layoutLatch.await(timeout, TimeUnit.SECONDS);
284b5f8b4fecb531aee6b359b4968409410aba90817Yigit Boyar            assertFalse(msg, layoutLatch.getCount() == 0);
285b5f8b4fecb531aee6b359b4968409410aba90817Yigit Boyar        }
286b5f8b4fecb531aee6b359b4968409410aba90817Yigit Boyar
287b5f8b4fecb531aee6b359b4968409410aba90817Yigit Boyar        public void waitForLayout(long timeout) throws Throwable {
2888ae76f91527ce850f155ce960fb9068bcd5d49f9Yigit Boyar            waitForLayout(timeout * (mDebug ? 10000 : 1), TimeUnit.SECONDS);
289b5f8b4fecb531aee6b359b4968409410aba90817Yigit Boyar        }
290b5f8b4fecb531aee6b359b4968409410aba90817Yigit Boyar
291d7d27e9ebe5c7325e67e1a8af265378bd2056cadChet Haase        @Override
292d7d27e9ebe5c7325e67e1a8af265378bd2056cadChet Haase        public RecyclerView.LayoutParams generateDefaultLayoutParams() {
293d7d27e9ebe5c7325e67e1a8af265378bd2056cadChet Haase            return new RecyclerView.LayoutParams(ViewGroup.LayoutParams.WRAP_CONTENT,
294d7d27e9ebe5c7325e67e1a8af265378bd2056cadChet Haase                    ViewGroup.LayoutParams.WRAP_CONTENT);
295d7d27e9ebe5c7325e67e1a8af265378bd2056cadChet Haase        }
296d7d27e9ebe5c7325e67e1a8af265378bd2056cadChet Haase
297d7d27e9ebe5c7325e67e1a8af265378bd2056cadChet Haase        void assertVisibleItemPositions() {
298d7d27e9ebe5c7325e67e1a8af265378bd2056cadChet Haase            int i = getChildCount();
299d7d27e9ebe5c7325e67e1a8af265378bd2056cadChet Haase            TestAdapter testAdapter = (TestAdapter) mRecyclerView.getAdapter();
300d7d27e9ebe5c7325e67e1a8af265378bd2056cadChet Haase            while (i-- > 0) {
301d7d27e9ebe5c7325e67e1a8af265378bd2056cadChet Haase                View view = getChildAt(i);
302d7d27e9ebe5c7325e67e1a8af265378bd2056cadChet Haase                RecyclerView.LayoutParams lp = (RecyclerView.LayoutParams) view.getLayoutParams();
303d7d27e9ebe5c7325e67e1a8af265378bd2056cadChet Haase                Item item = ((TestViewHolder) lp.mViewHolder).mBindedItem;
304b5f8b4fecb531aee6b359b4968409410aba90817Yigit Boyar                if (mDebug) {
305d7d27e9ebe5c7325e67e1a8af265378bd2056cadChet Haase                    Log.d(TAG, "testing item " + i);
306d7d27e9ebe5c7325e67e1a8af265378bd2056cadChet Haase                }
3078ae76f91527ce850f155ce960fb9068bcd5d49f9Yigit Boyar                if (!lp.isItemRemoved()) {
3088ae76f91527ce850f155ce960fb9068bcd5d49f9Yigit Boyar                    RecyclerView.ViewHolder vh = mRecyclerView.getChildViewHolder(view);
3098ae76f91527ce850f155ce960fb9068bcd5d49f9Yigit Boyar                    assertSame("item position in LP should match adapter value :" + vh,
3108ae76f91527ce850f155ce960fb9068bcd5d49f9Yigit Boyar                            testAdapter.mItems.get(vh.mPosition), item);
3118ae76f91527ce850f155ce960fb9068bcd5d49f9Yigit Boyar                }
312d7d27e9ebe5c7325e67e1a8af265378bd2056cadChet Haase            }
313d7d27e9ebe5c7325e67e1a8af265378bd2056cadChet Haase        }
314d7d27e9ebe5c7325e67e1a8af265378bd2056cadChet Haase
315b5f8b4fecb531aee6b359b4968409410aba90817Yigit Boyar        RecyclerView.LayoutParams getLp(View v) {
316b5f8b4fecb531aee6b359b4968409410aba90817Yigit Boyar            return (RecyclerView.LayoutParams) v.getLayoutParams();
317b5f8b4fecb531aee6b359b4968409410aba90817Yigit Boyar        }
318b5f8b4fecb531aee6b359b4968409410aba90817Yigit Boyar
319f485be9def4c0f72cfdfa6b0c616c23f04652817Yigit Boyar        void layoutRange(RecyclerView.Recycler recycler, int start, int end) {
320fe3996cf5af65fe11cc3ed47b67123ec267bd336Yigit Boyar            assertScrap(recycler);
321b5f8b4fecb531aee6b359b4968409410aba90817Yigit Boyar            if (mDebug) {
322d7d27e9ebe5c7325e67e1a8af265378bd2056cadChet Haase                Log.d(TAG, "will layout items from " + start + " to " + end);
323d7d27e9ebe5c7325e67e1a8af265378bd2056cadChet Haase            }
3245ced882cabdcefbb469e332f6f73983999aad0e5Yigit Boyar            int diff = end > start ? 1 : -1;
325504c54ea52c1b2aae6f8f4ae128f1dcaac7e3f6aYigit Boyar            int top = 0;
3265ced882cabdcefbb469e332f6f73983999aad0e5Yigit Boyar            for (int i = start; i != end; i+=diff) {
327b5f8b4fecb531aee6b359b4968409410aba90817Yigit Boyar                if (mDebug) {
328d7d27e9ebe5c7325e67e1a8af265378bd2056cadChet Haase                    Log.d(TAG, "laying out item " + i);
329d7d27e9ebe5c7325e67e1a8af265378bd2056cadChet Haase                }
330d7d27e9ebe5c7325e67e1a8af265378bd2056cadChet Haase                View view = recycler.getViewForPosition(i);
331b5f8b4fecb531aee6b359b4968409410aba90817Yigit Boyar                assertNotNull("view should not be null for valid position. "
332b5f8b4fecb531aee6b359b4968409410aba90817Yigit Boyar                        + "got null view at position " + i, view);
333c8b0ff5869e17a656a3a1d7fb306efa5b99fa07fYigit Boyar                if (!mRecyclerView.mState.isPreLayout()) {
334c8b0ff5869e17a656a3a1d7fb306efa5b99fa07fYigit Boyar                    RecyclerView.LayoutParams layoutParams = (RecyclerView.LayoutParams) view
335c8b0ff5869e17a656a3a1d7fb306efa5b99fa07fYigit Boyar                            .getLayoutParams();
336c8b0ff5869e17a656a3a1d7fb306efa5b99fa07fYigit Boyar                    assertFalse("In post layout, getViewForPosition should never return a view "
337c8b0ff5869e17a656a3a1d7fb306efa5b99fa07fYigit Boyar                            + "that is removed", layoutParams != null
338c8b0ff5869e17a656a3a1d7fb306efa5b99fa07fYigit Boyar                            && layoutParams.isItemRemoved());
339c8b0ff5869e17a656a3a1d7fb306efa5b99fa07fYigit Boyar
340b5f8b4fecb531aee6b359b4968409410aba90817Yigit Boyar                }
3418da5bba529a2fe9bd2aafab28010e11ec4de93c7Yigit Boyar                assertEquals("getViewForPosition should return correct position",
3428da5bba529a2fe9bd2aafab28010e11ec4de93c7Yigit Boyar                        i, getPosition(view));
343c8b0ff5869e17a656a3a1d7fb306efa5b99fa07fYigit Boyar                addView(view);
344b5f8b4fecb531aee6b359b4968409410aba90817Yigit Boyar
345d7d27e9ebe5c7325e67e1a8af265378bd2056cadChet Haase                measureChildWithMargins(view, 0, 0);
346504c54ea52c1b2aae6f8f4ae128f1dcaac7e3f6aYigit Boyar                layoutDecorated(view, 0, top, getDecoratedMeasuredWidth(view)
347504c54ea52c1b2aae6f8f4ae128f1dcaac7e3f6aYigit Boyar                        , top + getDecoratedMeasuredHeight(view));
348504c54ea52c1b2aae6f8f4ae128f1dcaac7e3f6aYigit Boyar                top += view.getMeasuredHeight();
349d7d27e9ebe5c7325e67e1a8af265378bd2056cadChet Haase            }
350d7d27e9ebe5c7325e67e1a8af265378bd2056cadChet Haase        }
351fe3996cf5af65fe11cc3ed47b67123ec267bd336Yigit Boyar
352fe3996cf5af65fe11cc3ed47b67123ec267bd336Yigit Boyar        private void assertScrap(RecyclerView.Recycler recycler) {
353e0c347f627f8a78d3e5e3e5eaac9c3ae26208689Yigit Boyar            if (mRecyclerView.getAdapter() != null &&
354e0c347f627f8a78d3e5e3e5eaac9c3ae26208689Yigit Boyar                    !mRecyclerView.getAdapter().hasStableIds()) {
355e0c347f627f8a78d3e5e3e5eaac9c3ae26208689Yigit Boyar                for (RecyclerView.ViewHolder viewHolder : recycler.getScrapList()) {
356e0c347f627f8a78d3e5e3e5eaac9c3ae26208689Yigit Boyar                    assertFalse("Invalid scrap should be no kept", viewHolder.isInvalid());
357e0c347f627f8a78d3e5e3e5eaac9c3ae26208689Yigit Boyar                }
358fe3996cf5af65fe11cc3ed47b67123ec267bd336Yigit Boyar            }
359fe3996cf5af65fe11cc3ed47b67123ec267bd336Yigit Boyar        }
360d7d27e9ebe5c7325e67e1a8af265378bd2056cadChet Haase    }
361d7d27e9ebe5c7325e67e1a8af265378bd2056cadChet Haase
362d7d27e9ebe5c7325e67e1a8af265378bd2056cadChet Haase    static class Item {
36311b7cfea15306c0d6e4ed7da09f7698df01695dfYigit Boyar        final static AtomicInteger idCounter = new AtomicInteger(0);
36411b7cfea15306c0d6e4ed7da09f7698df01695dfYigit Boyar        final public int mId = idCounter.incrementAndGet();
365d7d27e9ebe5c7325e67e1a8af265378bd2056cadChet Haase
3662d2e8d88103866b631eb0f3805da137ebcfb0275Yigit Boyar        int mAdapterIndex;
367d7d27e9ebe5c7325e67e1a8af265378bd2056cadChet Haase
3682d2e8d88103866b631eb0f3805da137ebcfb0275Yigit Boyar        final String mText;
369d7d27e9ebe5c7325e67e1a8af265378bd2056cadChet Haase
3702d2e8d88103866b631eb0f3805da137ebcfb0275Yigit Boyar        Item(int adapterIndex, String text) {
3712d2e8d88103866b631eb0f3805da137ebcfb0275Yigit Boyar            mAdapterIndex = adapterIndex;
3722d2e8d88103866b631eb0f3805da137ebcfb0275Yigit Boyar            mText = text;
373d7d27e9ebe5c7325e67e1a8af265378bd2056cadChet Haase        }
3748ae76f91527ce850f155ce960fb9068bcd5d49f9Yigit Boyar
3758ae76f91527ce850f155ce960fb9068bcd5d49f9Yigit Boyar        @Override
3768ae76f91527ce850f155ce960fb9068bcd5d49f9Yigit Boyar        public String toString() {
3778ae76f91527ce850f155ce960fb9068bcd5d49f9Yigit Boyar            return "Item{" +
3788ae76f91527ce850f155ce960fb9068bcd5d49f9Yigit Boyar                    "mId=" + mId +
3792d2e8d88103866b631eb0f3805da137ebcfb0275Yigit Boyar                    ", originalIndex=" + mAdapterIndex +
3802d2e8d88103866b631eb0f3805da137ebcfb0275Yigit Boyar                    ", text='" + mText + '\'' +
3818ae76f91527ce850f155ce960fb9068bcd5d49f9Yigit Boyar                    '}';
3828ae76f91527ce850f155ce960fb9068bcd5d49f9Yigit Boyar        }
383d7d27e9ebe5c7325e67e1a8af265378bd2056cadChet Haase    }
384d7d27e9ebe5c7325e67e1a8af265378bd2056cadChet Haase
385d7d27e9ebe5c7325e67e1a8af265378bd2056cadChet Haase    class TestAdapter extends RecyclerView.Adapter<TestViewHolder> {
386d7d27e9ebe5c7325e67e1a8af265378bd2056cadChet Haase
387d7d27e9ebe5c7325e67e1a8af265378bd2056cadChet Haase        List<Item> mItems;
388d7d27e9ebe5c7325e67e1a8af265378bd2056cadChet Haase
389d7d27e9ebe5c7325e67e1a8af265378bd2056cadChet Haase        TestAdapter(int count) {
390d7d27e9ebe5c7325e67e1a8af265378bd2056cadChet Haase            mItems = new ArrayList<Item>(count);
391d7d27e9ebe5c7325e67e1a8af265378bd2056cadChet Haase            for (int i = 0; i < count; i++) {
392d7d27e9ebe5c7325e67e1a8af265378bd2056cadChet Haase                mItems.add(new Item(i, "Item " + i));
393d7d27e9ebe5c7325e67e1a8af265378bd2056cadChet Haase            }
394d7d27e9ebe5c7325e67e1a8af265378bd2056cadChet Haase        }
395d7d27e9ebe5c7325e67e1a8af265378bd2056cadChet Haase
396d7d27e9ebe5c7325e67e1a8af265378bd2056cadChet Haase        @Override
397d7d27e9ebe5c7325e67e1a8af265378bd2056cadChet Haase        public TestViewHolder onCreateViewHolder(ViewGroup parent,
398d7d27e9ebe5c7325e67e1a8af265378bd2056cadChet Haase                int viewType) {
399d7d27e9ebe5c7325e67e1a8af265378bd2056cadChet Haase            return new TestViewHolder(new TextView(parent.getContext()));
400d7d27e9ebe5c7325e67e1a8af265378bd2056cadChet Haase        }
401d7d27e9ebe5c7325e67e1a8af265378bd2056cadChet Haase
402d7d27e9ebe5c7325e67e1a8af265378bd2056cadChet Haase        @Override
403d7d27e9ebe5c7325e67e1a8af265378bd2056cadChet Haase        public void onBindViewHolder(TestViewHolder holder, int position) {
404d7d27e9ebe5c7325e67e1a8af265378bd2056cadChet Haase            final Item item = mItems.get(position);
4052d2e8d88103866b631eb0f3805da137ebcfb0275Yigit Boyar            ((TextView) (holder.itemView)).setText(item.mText + "(" + item.mAdapterIndex + ")");
406d7d27e9ebe5c7325e67e1a8af265378bd2056cadChet Haase            holder.mBindedItem = item;
407d7d27e9ebe5c7325e67e1a8af265378bd2056cadChet Haase        }
408d7d27e9ebe5c7325e67e1a8af265378bd2056cadChet Haase
409b5f8b4fecb531aee6b359b4968409410aba90817Yigit Boyar        public void deleteAndNotify(final int start, final int count) throws Throwable {
410b5f8b4fecb531aee6b359b4968409410aba90817Yigit Boyar            deleteAndNotify(new int[]{start, count});
411b5f8b4fecb531aee6b359b4968409410aba90817Yigit Boyar        }
412b5f8b4fecb531aee6b359b4968409410aba90817Yigit Boyar
413b5f8b4fecb531aee6b359b4968409410aba90817Yigit Boyar        /**
414b5f8b4fecb531aee6b359b4968409410aba90817Yigit Boyar         * Deletes items in the given ranges.
415b5f8b4fecb531aee6b359b4968409410aba90817Yigit Boyar         * <p>
416b5f8b4fecb531aee6b359b4968409410aba90817Yigit Boyar         * Note that each operation affects the one after so you should offset them properly.
417b5f8b4fecb531aee6b359b4968409410aba90817Yigit Boyar         * <p>
418b5f8b4fecb531aee6b359b4968409410aba90817Yigit Boyar         * For example, if adapter has 5 items (A,B,C,D,E), and then you call this method with
419b5f8b4fecb531aee6b359b4968409410aba90817Yigit Boyar         * <code>[1, 2],[2, 1]</code>, it will first delete items B,C and the new adapter will be
420b5f8b4fecb531aee6b359b4968409410aba90817Yigit Boyar         * A D E. Then it will delete 2,1 which means it will delete E.
421b5f8b4fecb531aee6b359b4968409410aba90817Yigit Boyar         */
422b5f8b4fecb531aee6b359b4968409410aba90817Yigit Boyar        public void deleteAndNotify(final int[]... startCountTuples) throws Throwable {
4238ae76f91527ce850f155ce960fb9068bcd5d49f9Yigit Boyar            for (int[] tuple : startCountTuples) {
4248ae76f91527ce850f155ce960fb9068bcd5d49f9Yigit Boyar                tuple[1] = -tuple[1];
4258ae76f91527ce850f155ce960fb9068bcd5d49f9Yigit Boyar            }
4268ae76f91527ce850f155ce960fb9068bcd5d49f9Yigit Boyar            new AddRemoveRunnable(startCountTuples).runOnMainThread();
427d7d27e9ebe5c7325e67e1a8af265378bd2056cadChet Haase        }
428d7d27e9ebe5c7325e67e1a8af265378bd2056cadChet Haase
429c8b0ff5869e17a656a3a1d7fb306efa5b99fa07fYigit Boyar        @Override
430c8b0ff5869e17a656a3a1d7fb306efa5b99fa07fYigit Boyar        public long getItemId(int position) {
431c8b0ff5869e17a656a3a1d7fb306efa5b99fa07fYigit Boyar            return hasStableIds() ? mItems.get(position).mId : super.getItemId(position);
432c8b0ff5869e17a656a3a1d7fb306efa5b99fa07fYigit Boyar        }
433c8b0ff5869e17a656a3a1d7fb306efa5b99fa07fYigit Boyar
4342d2e8d88103866b631eb0f3805da137ebcfb0275Yigit Boyar        public void offsetOriginalIndices(int start, int offset) {
4352d2e8d88103866b631eb0f3805da137ebcfb0275Yigit Boyar            for (int i = start; i < mItems.size(); i++) {
4362d2e8d88103866b631eb0f3805da137ebcfb0275Yigit Boyar                mItems.get(i).mAdapterIndex += offset;
4372d2e8d88103866b631eb0f3805da137ebcfb0275Yigit Boyar            }
4382d2e8d88103866b631eb0f3805da137ebcfb0275Yigit Boyar        }
4392d2e8d88103866b631eb0f3805da137ebcfb0275Yigit Boyar
440e0c347f627f8a78d3e5e3e5eaac9c3ae26208689Yigit Boyar        /**
441e0c347f627f8a78d3e5e3e5eaac9c3ae26208689Yigit Boyar         * @param start inclusive
442e0c347f627f8a78d3e5e3e5eaac9c3ae26208689Yigit Boyar         * @param end exclusive
443e0c347f627f8a78d3e5e3e5eaac9c3ae26208689Yigit Boyar         * @param offset
444e0c347f627f8a78d3e5e3e5eaac9c3ae26208689Yigit Boyar         */
445e0c347f627f8a78d3e5e3e5eaac9c3ae26208689Yigit Boyar        public void offsetOriginalIndicesBetween(int start, int end, int offset) {
446e0c347f627f8a78d3e5e3e5eaac9c3ae26208689Yigit Boyar            for (int i = start; i < end && i < mItems.size(); i++) {
447e0c347f627f8a78d3e5e3e5eaac9c3ae26208689Yigit Boyar                mItems.get(i).mAdapterIndex += offset;
448e0c347f627f8a78d3e5e3e5eaac9c3ae26208689Yigit Boyar            }
449e0c347f627f8a78d3e5e3e5eaac9c3ae26208689Yigit Boyar        }
450e0c347f627f8a78d3e5e3e5eaac9c3ae26208689Yigit Boyar
451b5f8b4fecb531aee6b359b4968409410aba90817Yigit Boyar        public void addAndNotify(final int start, final int count) throws Throwable {
452b5f8b4fecb531aee6b359b4968409410aba90817Yigit Boyar            addAndNotify(new int[]{start, count});
453b5f8b4fecb531aee6b359b4968409410aba90817Yigit Boyar        }
454b5f8b4fecb531aee6b359b4968409410aba90817Yigit Boyar
455b5f8b4fecb531aee6b359b4968409410aba90817Yigit Boyar        public void addAndNotify(final int[]... startCountTuples) throws Throwable {
4568ae76f91527ce850f155ce960fb9068bcd5d49f9Yigit Boyar            new AddRemoveRunnable(startCountTuples).runOnMainThread();
4578ae76f91527ce850f155ce960fb9068bcd5d49f9Yigit Boyar        }
458b5f8b4fecb531aee6b359b4968409410aba90817Yigit Boyar
459cadd54707a7aaa4cfa9c8b8051c6185e8717f22eChet Haase        public void dispatchDataSetChanged() throws Throwable {
46022b6b8163d2e15eb6d0a89906854dec801945f1dYigit Boyar            runTestOnUiThread(new Runnable() {
46122b6b8163d2e15eb6d0a89906854dec801945f1dYigit Boyar                @Override
46222b6b8163d2e15eb6d0a89906854dec801945f1dYigit Boyar                public void run() {
46322b6b8163d2e15eb6d0a89906854dec801945f1dYigit Boyar                    notifyDataSetChanged();
46422b6b8163d2e15eb6d0a89906854dec801945f1dYigit Boyar                }
46522b6b8163d2e15eb6d0a89906854dec801945f1dYigit Boyar            });
46622b6b8163d2e15eb6d0a89906854dec801945f1dYigit Boyar        }
46722b6b8163d2e15eb6d0a89906854dec801945f1dYigit Boyar
468cadd54707a7aaa4cfa9c8b8051c6185e8717f22eChet Haase        public void changeAndNotify(final int start, final int count) throws Throwable {
4697e9da1a2629b657d56e2f54abd18c6249f3be037Yigit Boyar            runTestOnUiThread(new Runnable() {
4707e9da1a2629b657d56e2f54abd18c6249f3be037Yigit Boyar                @Override
4717e9da1a2629b657d56e2f54abd18c6249f3be037Yigit Boyar                public void run() {
4727e9da1a2629b657d56e2f54abd18c6249f3be037Yigit Boyar                    notifyItemRangeChanged(start, count);
4737e9da1a2629b657d56e2f54abd18c6249f3be037Yigit Boyar                }
4747e9da1a2629b657d56e2f54abd18c6249f3be037Yigit Boyar            });
4757e9da1a2629b657d56e2f54abd18c6249f3be037Yigit Boyar        }
4767e9da1a2629b657d56e2f54abd18c6249f3be037Yigit Boyar
477cadd54707a7aaa4cfa9c8b8051c6185e8717f22eChet Haase        public void changePositionsAndNotify(final int... positions) throws Throwable {
478cadd54707a7aaa4cfa9c8b8051c6185e8717f22eChet Haase            runTestOnUiThread(new Runnable() {
479cadd54707a7aaa4cfa9c8b8051c6185e8717f22eChet Haase                @Override
480cadd54707a7aaa4cfa9c8b8051c6185e8717f22eChet Haase                public void run() {
481cadd54707a7aaa4cfa9c8b8051c6185e8717f22eChet Haase                    for (int i = 0; i < positions.length; i += 1) {
482cadd54707a7aaa4cfa9c8b8051c6185e8717f22eChet Haase                        TestAdapter.super.notifyItemRangeChanged(positions[i], 1);
483cadd54707a7aaa4cfa9c8b8051c6185e8717f22eChet Haase                    }
484cadd54707a7aaa4cfa9c8b8051c6185e8717f22eChet Haase                }
485cadd54707a7aaa4cfa9c8b8051c6185e8717f22eChet Haase            });
486cadd54707a7aaa4cfa9c8b8051c6185e8717f22eChet Haase        }
487cadd54707a7aaa4cfa9c8b8051c6185e8717f22eChet Haase
4888ae76f91527ce850f155ce960fb9068bcd5d49f9Yigit Boyar        /**
4898ae76f91527ce850f155ce960fb9068bcd5d49f9Yigit Boyar         * Similar to other methods but negative count means delete and position count means add.
4908ae76f91527ce850f155ce960fb9068bcd5d49f9Yigit Boyar         * <p>
4918ae76f91527ce850f155ce960fb9068bcd5d49f9Yigit Boyar         * For instance, calling this method with <code>[1,1], [2,-1]</code> it will first add an
4928ae76f91527ce850f155ce960fb9068bcd5d49f9Yigit Boyar         * item to index 1, then remove an item from index 2 (updated index 2)
4938ae76f91527ce850f155ce960fb9068bcd5d49f9Yigit Boyar         */
4948ae76f91527ce850f155ce960fb9068bcd5d49f9Yigit Boyar        public void addDeleteAndNotify(final int[]... startCountTuples) throws Throwable {
4958ae76f91527ce850f155ce960fb9068bcd5d49f9Yigit Boyar            new AddRemoveRunnable(startCountTuples).runOnMainThread();
496d7d27e9ebe5c7325e67e1a8af265378bd2056cadChet Haase        }
497d7d27e9ebe5c7325e67e1a8af265378bd2056cadChet Haase
498d7d27e9ebe5c7325e67e1a8af265378bd2056cadChet Haase        @Override
499d7d27e9ebe5c7325e67e1a8af265378bd2056cadChet Haase        public int getItemCount() {
500d7d27e9ebe5c7325e67e1a8af265378bd2056cadChet Haase            return mItems.size();
501d7d27e9ebe5c7325e67e1a8af265378bd2056cadChet Haase        }
5028ae76f91527ce850f155ce960fb9068bcd5d49f9Yigit Boyar
503e0c347f627f8a78d3e5e3e5eaac9c3ae26208689Yigit Boyar        public void moveItems(boolean notifyChange, int[]... fromToTuples) throws Throwable {
504e0c347f627f8a78d3e5e3e5eaac9c3ae26208689Yigit Boyar            for (int i = 0; i < fromToTuples.length; i += 1) {
505e0c347f627f8a78d3e5e3e5eaac9c3ae26208689Yigit Boyar                int[] tuple = fromToTuples[i];
506e0c347f627f8a78d3e5e3e5eaac9c3ae26208689Yigit Boyar                moveItem(tuple[0], tuple[1], false);
507e0c347f627f8a78d3e5e3e5eaac9c3ae26208689Yigit Boyar            }
508e0c347f627f8a78d3e5e3e5eaac9c3ae26208689Yigit Boyar            if (notifyChange) {
509cadd54707a7aaa4cfa9c8b8051c6185e8717f22eChet Haase                dispatchDataSetChanged();
510e0c347f627f8a78d3e5e3e5eaac9c3ae26208689Yigit Boyar            }
511e0c347f627f8a78d3e5e3e5eaac9c3ae26208689Yigit Boyar        }
512e0c347f627f8a78d3e5e3e5eaac9c3ae26208689Yigit Boyar
513e0c347f627f8a78d3e5e3e5eaac9c3ae26208689Yigit Boyar        public void moveItem(final int from, final int to, final boolean notifyChange)
514e0c347f627f8a78d3e5e3e5eaac9c3ae26208689Yigit Boyar                throws Throwable {
515e0c347f627f8a78d3e5e3e5eaac9c3ae26208689Yigit Boyar            runTestOnUiThread(new Runnable() {
516e0c347f627f8a78d3e5e3e5eaac9c3ae26208689Yigit Boyar                @Override
517e0c347f627f8a78d3e5e3e5eaac9c3ae26208689Yigit Boyar                public void run() {
518e0c347f627f8a78d3e5e3e5eaac9c3ae26208689Yigit Boyar                    Item item = mItems.remove(from);
519e0c347f627f8a78d3e5e3e5eaac9c3ae26208689Yigit Boyar                    mItems.add(to, item);
520e0c347f627f8a78d3e5e3e5eaac9c3ae26208689Yigit Boyar                    offsetOriginalIndices(from, to - 1);
521e0c347f627f8a78d3e5e3e5eaac9c3ae26208689Yigit Boyar                    item.mAdapterIndex = to;
522e0c347f627f8a78d3e5e3e5eaac9c3ae26208689Yigit Boyar                    if (notifyChange) {
523e0c347f627f8a78d3e5e3e5eaac9c3ae26208689Yigit Boyar                        notifyDataSetChanged();
524e0c347f627f8a78d3e5e3e5eaac9c3ae26208689Yigit Boyar                    }
525e0c347f627f8a78d3e5e3e5eaac9c3ae26208689Yigit Boyar                }
526e0c347f627f8a78d3e5e3e5eaac9c3ae26208689Yigit Boyar            });
527e0c347f627f8a78d3e5e3e5eaac9c3ae26208689Yigit Boyar        }
528e0c347f627f8a78d3e5e3e5eaac9c3ae26208689Yigit Boyar
5298ae76f91527ce850f155ce960fb9068bcd5d49f9Yigit Boyar
5308ae76f91527ce850f155ce960fb9068bcd5d49f9Yigit Boyar        private class AddRemoveRunnable implements Runnable {
5318ae76f91527ce850f155ce960fb9068bcd5d49f9Yigit Boyar            final int[][] mStartCountTuples;
5328ae76f91527ce850f155ce960fb9068bcd5d49f9Yigit Boyar
5338ae76f91527ce850f155ce960fb9068bcd5d49f9Yigit Boyar            public AddRemoveRunnable(int[][] startCountTuples) {
5348ae76f91527ce850f155ce960fb9068bcd5d49f9Yigit Boyar                mStartCountTuples = startCountTuples;
5358ae76f91527ce850f155ce960fb9068bcd5d49f9Yigit Boyar            }
5368ae76f91527ce850f155ce960fb9068bcd5d49f9Yigit Boyar
5378ae76f91527ce850f155ce960fb9068bcd5d49f9Yigit Boyar            public void runOnMainThread() throws Throwable {
5388ae76f91527ce850f155ce960fb9068bcd5d49f9Yigit Boyar                if (Looper.myLooper() == Looper.getMainLooper()) {
5398ae76f91527ce850f155ce960fb9068bcd5d49f9Yigit Boyar                    run();
5408ae76f91527ce850f155ce960fb9068bcd5d49f9Yigit Boyar                } else {
5418ae76f91527ce850f155ce960fb9068bcd5d49f9Yigit Boyar                    runTestOnUiThread(this);
5428ae76f91527ce850f155ce960fb9068bcd5d49f9Yigit Boyar                }
5438ae76f91527ce850f155ce960fb9068bcd5d49f9Yigit Boyar            }
5448ae76f91527ce850f155ce960fb9068bcd5d49f9Yigit Boyar
5458ae76f91527ce850f155ce960fb9068bcd5d49f9Yigit Boyar            @Override
5468ae76f91527ce850f155ce960fb9068bcd5d49f9Yigit Boyar            public void run() {
5478ae76f91527ce850f155ce960fb9068bcd5d49f9Yigit Boyar                for (int[] tuple : mStartCountTuples) {
5488ae76f91527ce850f155ce960fb9068bcd5d49f9Yigit Boyar                    if (tuple[1] < 0) {
5498ae76f91527ce850f155ce960fb9068bcd5d49f9Yigit Boyar                        delete(tuple);
5508ae76f91527ce850f155ce960fb9068bcd5d49f9Yigit Boyar                    } else {
5518ae76f91527ce850f155ce960fb9068bcd5d49f9Yigit Boyar                        add(tuple);
5528ae76f91527ce850f155ce960fb9068bcd5d49f9Yigit Boyar                    }
5538ae76f91527ce850f155ce960fb9068bcd5d49f9Yigit Boyar                }
5548ae76f91527ce850f155ce960fb9068bcd5d49f9Yigit Boyar            }
5558ae76f91527ce850f155ce960fb9068bcd5d49f9Yigit Boyar
5568ae76f91527ce850f155ce960fb9068bcd5d49f9Yigit Boyar            private void add(int[] tuple) {
5572d2e8d88103866b631eb0f3805da137ebcfb0275Yigit Boyar                // offset others
5582d2e8d88103866b631eb0f3805da137ebcfb0275Yigit Boyar                offsetOriginalIndices(tuple[0], tuple[1]);
5598ae76f91527ce850f155ce960fb9068bcd5d49f9Yigit Boyar                for (int i = 0; i < tuple[1]; i++) {
5608ae76f91527ce850f155ce960fb9068bcd5d49f9Yigit Boyar                    mItems.add(tuple[0], new Item(i, "new item " + i));
5618ae76f91527ce850f155ce960fb9068bcd5d49f9Yigit Boyar                }
5628ae76f91527ce850f155ce960fb9068bcd5d49f9Yigit Boyar                notifyItemRangeInserted(tuple[0], tuple[1]);
5638ae76f91527ce850f155ce960fb9068bcd5d49f9Yigit Boyar            }
5648ae76f91527ce850f155ce960fb9068bcd5d49f9Yigit Boyar
5658ae76f91527ce850f155ce960fb9068bcd5d49f9Yigit Boyar            private void delete(int[] tuple) {
5662d2e8d88103866b631eb0f3805da137ebcfb0275Yigit Boyar                final int count = -tuple[1];
5672d2e8d88103866b631eb0f3805da137ebcfb0275Yigit Boyar                offsetOriginalIndices(tuple[0] + count, tuple[1]);
5682d2e8d88103866b631eb0f3805da137ebcfb0275Yigit Boyar                for (int i = 0; i < count; i++) {
5698ae76f91527ce850f155ce960fb9068bcd5d49f9Yigit Boyar                    mItems.remove(tuple[0]);
5708ae76f91527ce850f155ce960fb9068bcd5d49f9Yigit Boyar                }
5712d2e8d88103866b631eb0f3805da137ebcfb0275Yigit Boyar                notifyItemRangeRemoved(tuple[0], count);
5728ae76f91527ce850f155ce960fb9068bcd5d49f9Yigit Boyar            }
5738ae76f91527ce850f155ce960fb9068bcd5d49f9Yigit Boyar        }
5748ae76f91527ce850f155ce960fb9068bcd5d49f9Yigit Boyar    }
5758ae76f91527ce850f155ce960fb9068bcd5d49f9Yigit Boyar
5768ae76f91527ce850f155ce960fb9068bcd5d49f9Yigit Boyar    @Override
5778ae76f91527ce850f155ce960fb9068bcd5d49f9Yigit Boyar    public void runTestOnUiThread(Runnable r) throws Throwable {
5788ae76f91527ce850f155ce960fb9068bcd5d49f9Yigit Boyar        if (Looper.myLooper() == Looper.getMainLooper()) {
5798ae76f91527ce850f155ce960fb9068bcd5d49f9Yigit Boyar            r.run();
5808ae76f91527ce850f155ce960fb9068bcd5d49f9Yigit Boyar        } else {
5818ae76f91527ce850f155ce960fb9068bcd5d49f9Yigit Boyar            super.runTestOnUiThread(r);
5828ae76f91527ce850f155ce960fb9068bcd5d49f9Yigit Boyar        }
583d7d27e9ebe5c7325e67e1a8af265378bd2056cadChet Haase    }
584c50c4cad31d73e574b27bb3d7581542975e37263Yigit Boyar
585c50c4cad31d73e574b27bb3d7581542975e37263Yigit Boyar    static class TargetTuple {
586c50c4cad31d73e574b27bb3d7581542975e37263Yigit Boyar
587c50c4cad31d73e574b27bb3d7581542975e37263Yigit Boyar        final int mPosition;
588c50c4cad31d73e574b27bb3d7581542975e37263Yigit Boyar
589c50c4cad31d73e574b27bb3d7581542975e37263Yigit Boyar        final int mLayoutDirection;
590c50c4cad31d73e574b27bb3d7581542975e37263Yigit Boyar
591c50c4cad31d73e574b27bb3d7581542975e37263Yigit Boyar        TargetTuple(int position, int layoutDirection) {
592c50c4cad31d73e574b27bb3d7581542975e37263Yigit Boyar            this.mPosition = position;
593c50c4cad31d73e574b27bb3d7581542975e37263Yigit Boyar            this.mLayoutDirection = layoutDirection;
594c50c4cad31d73e574b27bb3d7581542975e37263Yigit Boyar        }
595c50c4cad31d73e574b27bb3d7581542975e37263Yigit Boyar
596c50c4cad31d73e574b27bb3d7581542975e37263Yigit Boyar        @Override
597c50c4cad31d73e574b27bb3d7581542975e37263Yigit Boyar        public String toString() {
598c50c4cad31d73e574b27bb3d7581542975e37263Yigit Boyar            return "TargetTuple{" +
599c50c4cad31d73e574b27bb3d7581542975e37263Yigit Boyar                    "mPosition=" + mPosition +
600c50c4cad31d73e574b27bb3d7581542975e37263Yigit Boyar                    ", mLayoutDirection=" + mLayoutDirection +
601c50c4cad31d73e574b27bb3d7581542975e37263Yigit Boyar                    '}';
602c50c4cad31d73e574b27bb3d7581542975e37263Yigit Boyar        }
603c50c4cad31d73e574b27bb3d7581542975e37263Yigit Boyar    }
6047a59e4b36500bcfddd842376d6c3abe0d9f23d84Yigit Boyar}
605