BaseRecyclerViewInstrumentationTest.java revision ca9ce7ccdccf6b6a2a81da73dd1e6425d90e4b23
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();
111ca9ce7ccdccf6b6a2a81da73dd1e6425d90e4b23Yigit Boyar        super.tearDown();
112ca9ce7ccdccf6b6a2a81da73dd1e6425d90e4b23Yigit Boyar
1136e83751247c5be0211d7bffaf057431c03dfef38Yigit Boyar        try {
1146e83751247c5be0211d7bffaf057431c03dfef38Yigit Boyar            checkForMainThreadException();
1156e83751247c5be0211d7bffaf057431c03dfef38Yigit Boyar        } catch (Exception e) {
1166e83751247c5be0211d7bffaf057431c03dfef38Yigit Boyar            throw e;
1176e83751247c5be0211d7bffaf057431c03dfef38Yigit Boyar        } catch (Throwable throwable) {
118ca9ce7ccdccf6b6a2a81da73dd1e6425d90e4b23Yigit Boyar            throw new Exception(throwable);
1196e83751247c5be0211d7bffaf057431c03dfef38Yigit Boyar        }
1208ae76f91527ce850f155ce960fb9068bcd5d49f9Yigit Boyar    }
1218ae76f91527ce850f155ce960fb9068bcd5d49f9Yigit Boyar
122c50c4cad31d73e574b27bb3d7581542975e37263Yigit Boyar    public Rect getDecoratedRecyclerViewBounds() {
123c50c4cad31d73e574b27bb3d7581542975e37263Yigit Boyar        return new Rect(
124c50c4cad31d73e574b27bb3d7581542975e37263Yigit Boyar                mRecyclerView.getPaddingLeft(),
125c50c4cad31d73e574b27bb3d7581542975e37263Yigit Boyar                mRecyclerView.getPaddingTop(),
126c50c4cad31d73e574b27bb3d7581542975e37263Yigit Boyar                mRecyclerView.getPaddingLeft() + mRecyclerView.getWidth(),
127c50c4cad31d73e574b27bb3d7581542975e37263Yigit Boyar                mRecyclerView.getPaddingTop() + mRecyclerView.getHeight()
128c50c4cad31d73e574b27bb3d7581542975e37263Yigit Boyar        );
129c50c4cad31d73e574b27bb3d7581542975e37263Yigit Boyar    }
130c50c4cad31d73e574b27bb3d7581542975e37263Yigit Boyar
131b5f8b4fecb531aee6b359b4968409410aba90817Yigit Boyar    public void removeRecyclerView() throws Throwable {
132d7e2f2ab1d253133fa54f309e74a7ee384c33971Yigit Boyar        if (mRecyclerView == null) {
133d7e2f2ab1d253133fa54f309e74a7ee384c33971Yigit Boyar            return;
134d7e2f2ab1d253133fa54f309e74a7ee384c33971Yigit Boyar        }
135b5f8b4fecb531aee6b359b4968409410aba90817Yigit Boyar        mRecyclerView = null;
136b5f8b4fecb531aee6b359b4968409410aba90817Yigit Boyar        runTestOnUiThread(new Runnable() {
137b5f8b4fecb531aee6b359b4968409410aba90817Yigit Boyar            @Override
138b5f8b4fecb531aee6b359b4968409410aba90817Yigit Boyar            public void run() {
139b5f8b4fecb531aee6b359b4968409410aba90817Yigit Boyar                getActivity().mContainer.removeAllViews();
140b5f8b4fecb531aee6b359b4968409410aba90817Yigit Boyar            }
141b5f8b4fecb531aee6b359b4968409410aba90817Yigit Boyar        });
142b5f8b4fecb531aee6b359b4968409410aba90817Yigit Boyar    }
143b5f8b4fecb531aee6b359b4968409410aba90817Yigit Boyar
144504c54ea52c1b2aae6f8f4ae128f1dcaac7e3f6aYigit Boyar    void waitForAnimations(int seconds) throws InterruptedException {
145504c54ea52c1b2aae6f8f4ae128f1dcaac7e3f6aYigit Boyar        final CountDownLatch latch = new CountDownLatch(2);
146504c54ea52c1b2aae6f8f4ae128f1dcaac7e3f6aYigit Boyar        boolean running = mRecyclerView.mItemAnimator
147504c54ea52c1b2aae6f8f4ae128f1dcaac7e3f6aYigit Boyar                .isRunning(new RecyclerView.ItemAnimator.ItemAnimatorFinishedListener() {
148504c54ea52c1b2aae6f8f4ae128f1dcaac7e3f6aYigit Boyar                    @Override
149504c54ea52c1b2aae6f8f4ae128f1dcaac7e3f6aYigit Boyar                    public void onAnimationsFinished() {
150504c54ea52c1b2aae6f8f4ae128f1dcaac7e3f6aYigit Boyar                        latch.countDown();
151504c54ea52c1b2aae6f8f4ae128f1dcaac7e3f6aYigit Boyar                    }
152504c54ea52c1b2aae6f8f4ae128f1dcaac7e3f6aYigit Boyar                });
153504c54ea52c1b2aae6f8f4ae128f1dcaac7e3f6aYigit Boyar        if (running) {
154504c54ea52c1b2aae6f8f4ae128f1dcaac7e3f6aYigit Boyar            latch.await(seconds, TimeUnit.SECONDS);
155504c54ea52c1b2aae6f8f4ae128f1dcaac7e3f6aYigit Boyar        }
156504c54ea52c1b2aae6f8f4ae128f1dcaac7e3f6aYigit Boyar    }
157504c54ea52c1b2aae6f8f4ae128f1dcaac7e3f6aYigit Boyar
158b5f8b4fecb531aee6b359b4968409410aba90817Yigit Boyar    public void setRecyclerView(final RecyclerView recyclerView) throws Throwable {
1590b1059b711009601ff08354e2df6bfc45266e80aYigit Boyar        setRecyclerView(recyclerView, true);
1600b1059b711009601ff08354e2df6bfc45266e80aYigit Boyar    }
1610b1059b711009601ff08354e2df6bfc45266e80aYigit Boyar    public void setRecyclerView(final RecyclerView recyclerView, boolean assignDummyPool)
1620b1059b711009601ff08354e2df6bfc45266e80aYigit Boyar            throws Throwable {
163b5f8b4fecb531aee6b359b4968409410aba90817Yigit Boyar        mRecyclerView = recyclerView;
1640b1059b711009601ff08354e2df6bfc45266e80aYigit Boyar        if (assignDummyPool) {
1650b1059b711009601ff08354e2df6bfc45266e80aYigit Boyar            RecyclerView.RecycledViewPool pool = new RecyclerView.RecycledViewPool() {
1660b1059b711009601ff08354e2df6bfc45266e80aYigit Boyar                @Override
1670b1059b711009601ff08354e2df6bfc45266e80aYigit Boyar                public RecyclerView.ViewHolder getRecycledView(int viewType) {
1680b1059b711009601ff08354e2df6bfc45266e80aYigit Boyar                    RecyclerView.ViewHolder viewHolder = super.getRecycledView(viewType);
1690b1059b711009601ff08354e2df6bfc45266e80aYigit Boyar                    if (viewHolder == null) {
1700b1059b711009601ff08354e2df6bfc45266e80aYigit Boyar                        return null;
1710b1059b711009601ff08354e2df6bfc45266e80aYigit Boyar                    }
1720b1059b711009601ff08354e2df6bfc45266e80aYigit Boyar                    viewHolder.addFlags(RecyclerView.ViewHolder.FLAG_BOUND);
1730b1059b711009601ff08354e2df6bfc45266e80aYigit Boyar                    viewHolder.mPosition = 200;
1740b1059b711009601ff08354e2df6bfc45266e80aYigit Boyar                    viewHolder.mOldPosition = 300;
1750b1059b711009601ff08354e2df6bfc45266e80aYigit Boyar                    viewHolder.mPreLayoutPosition = 500;
1760b1059b711009601ff08354e2df6bfc45266e80aYigit Boyar                    return viewHolder;
1778da5bba529a2fe9bd2aafab28010e11ec4de93c7Yigit Boyar                }
1788da5bba529a2fe9bd2aafab28010e11ec4de93c7Yigit Boyar
1790b1059b711009601ff08354e2df6bfc45266e80aYigit Boyar                @Override
1800b1059b711009601ff08354e2df6bfc45266e80aYigit Boyar                public void putRecycledView(RecyclerView.ViewHolder scrap) {
1810b1059b711009601ff08354e2df6bfc45266e80aYigit Boyar                    super.putRecycledView(scrap);
1820b1059b711009601ff08354e2df6bfc45266e80aYigit Boyar                }
1830b1059b711009601ff08354e2df6bfc45266e80aYigit Boyar            };
1840b1059b711009601ff08354e2df6bfc45266e80aYigit Boyar            mRecyclerView.setRecycledViewPool(pool);
1850b1059b711009601ff08354e2df6bfc45266e80aYigit Boyar        }
1868ae76f91527ce850f155ce960fb9068bcd5d49f9Yigit Boyar        mAdapterHelper = recyclerView.mAdapterHelper;
187b5f8b4fecb531aee6b359b4968409410aba90817Yigit Boyar        runTestOnUiThread(new Runnable() {
188b5f8b4fecb531aee6b359b4968409410aba90817Yigit Boyar            @Override
189b5f8b4fecb531aee6b359b4968409410aba90817Yigit Boyar            public void run() {
190b5f8b4fecb531aee6b359b4968409410aba90817Yigit Boyar                getActivity().mContainer.addView(recyclerView);
191b5f8b4fecb531aee6b359b4968409410aba90817Yigit Boyar            }
192b5f8b4fecb531aee6b359b4968409410aba90817Yigit Boyar        });
193b5f8b4fecb531aee6b359b4968409410aba90817Yigit Boyar    }
194b5f8b4fecb531aee6b359b4968409410aba90817Yigit Boyar
195310e95e1c6dfe4f26ef594233e65e1ff83e0f1ffYigit Boyar    protected FrameLayout getRecyclerViewContainer() {
196310e95e1c6dfe4f26ef594233e65e1ff83e0f1ffYigit Boyar        return getActivity().mContainer;
197310e95e1c6dfe4f26ef594233e65e1ff83e0f1ffYigit Boyar    }
198310e95e1c6dfe4f26ef594233e65e1ff83e0f1ffYigit Boyar
199b5f8b4fecb531aee6b359b4968409410aba90817Yigit Boyar    public void requestLayoutOnUIThread(final View view) throws Throwable {
200b5f8b4fecb531aee6b359b4968409410aba90817Yigit Boyar        runTestOnUiThread(new Runnable() {
201b5f8b4fecb531aee6b359b4968409410aba90817Yigit Boyar            @Override
202b5f8b4fecb531aee6b359b4968409410aba90817Yigit Boyar            public void run() {
203b5f8b4fecb531aee6b359b4968409410aba90817Yigit Boyar                view.requestLayout();
204b5f8b4fecb531aee6b359b4968409410aba90817Yigit Boyar            }
205b5f8b4fecb531aee6b359b4968409410aba90817Yigit Boyar        });
206b5f8b4fecb531aee6b359b4968409410aba90817Yigit Boyar    }
207b5f8b4fecb531aee6b359b4968409410aba90817Yigit Boyar
208b5f8b4fecb531aee6b359b4968409410aba90817Yigit Boyar    public void scrollBy(final int dt) throws Throwable {
209b5f8b4fecb531aee6b359b4968409410aba90817Yigit Boyar        runTestOnUiThread(new Runnable() {
210b5f8b4fecb531aee6b359b4968409410aba90817Yigit Boyar            @Override
211b5f8b4fecb531aee6b359b4968409410aba90817Yigit Boyar            public void run() {
212b5f8b4fecb531aee6b359b4968409410aba90817Yigit Boyar                if (mRecyclerView.getLayoutManager().canScrollHorizontally()) {
213b5f8b4fecb531aee6b359b4968409410aba90817Yigit Boyar                    mRecyclerView.scrollBy(dt, 0);
214b5f8b4fecb531aee6b359b4968409410aba90817Yigit Boyar                } else {
215b5f8b4fecb531aee6b359b4968409410aba90817Yigit Boyar                    mRecyclerView.scrollBy(0, dt);
216b5f8b4fecb531aee6b359b4968409410aba90817Yigit Boyar                }
217b5f8b4fecb531aee6b359b4968409410aba90817Yigit Boyar
218b5f8b4fecb531aee6b359b4968409410aba90817Yigit Boyar            }
219b5f8b4fecb531aee6b359b4968409410aba90817Yigit Boyar        });
220d7d27e9ebe5c7325e67e1a8af265378bd2056cadChet Haase    }
221d7d27e9ebe5c7325e67e1a8af265378bd2056cadChet Haase
2228edcb0bdeaba6931f9d8154f0c81f57da7ddab2aYigit Boyar    void scrollToPosition(final int position) throws Throwable {
2238edcb0bdeaba6931f9d8154f0c81f57da7ddab2aYigit Boyar        runTestOnUiThread(new Runnable() {
2248edcb0bdeaba6931f9d8154f0c81f57da7ddab2aYigit Boyar            @Override
2258edcb0bdeaba6931f9d8154f0c81f57da7ddab2aYigit Boyar            public void run() {
2268edcb0bdeaba6931f9d8154f0c81f57da7ddab2aYigit Boyar                mRecyclerView.getLayoutManager().scrollToPosition(position);
2278edcb0bdeaba6931f9d8154f0c81f57da7ddab2aYigit Boyar            }
2288edcb0bdeaba6931f9d8154f0c81f57da7ddab2aYigit Boyar        });
2298edcb0bdeaba6931f9d8154f0c81f57da7ddab2aYigit Boyar    }
2308edcb0bdeaba6931f9d8154f0c81f57da7ddab2aYigit Boyar
2318edcb0bdeaba6931f9d8154f0c81f57da7ddab2aYigit Boyar    void smoothScrollToPosition(final int position)
2328edcb0bdeaba6931f9d8154f0c81f57da7ddab2aYigit Boyar            throws Throwable {
2338edcb0bdeaba6931f9d8154f0c81f57da7ddab2aYigit Boyar        Log.d(TAG, "SMOOTH scrolling to " + position);
2348edcb0bdeaba6931f9d8154f0c81f57da7ddab2aYigit Boyar        runTestOnUiThread(new Runnable() {
2358edcb0bdeaba6931f9d8154f0c81f57da7ddab2aYigit Boyar            @Override
2368edcb0bdeaba6931f9d8154f0c81f57da7ddab2aYigit Boyar            public void run() {
2378edcb0bdeaba6931f9d8154f0c81f57da7ddab2aYigit Boyar                mRecyclerView.smoothScrollToPosition(position);
2388edcb0bdeaba6931f9d8154f0c81f57da7ddab2aYigit Boyar            }
2398edcb0bdeaba6931f9d8154f0c81f57da7ddab2aYigit Boyar        });
2408edcb0bdeaba6931f9d8154f0c81f57da7ddab2aYigit Boyar        while (mRecyclerView.getLayoutManager().isSmoothScrolling() ||
2418edcb0bdeaba6931f9d8154f0c81f57da7ddab2aYigit Boyar                mRecyclerView.getScrollState() != RecyclerView.SCROLL_STATE_IDLE) {
2428edcb0bdeaba6931f9d8154f0c81f57da7ddab2aYigit Boyar            if (mDebug) {
2438edcb0bdeaba6931f9d8154f0c81f57da7ddab2aYigit Boyar                Log.d(TAG, "SMOOTH scrolling step");
2448edcb0bdeaba6931f9d8154f0c81f57da7ddab2aYigit Boyar            }
2458edcb0bdeaba6931f9d8154f0c81f57da7ddab2aYigit Boyar            Thread.sleep(200);
2468edcb0bdeaba6931f9d8154f0c81f57da7ddab2aYigit Boyar        }
2478edcb0bdeaba6931f9d8154f0c81f57da7ddab2aYigit Boyar        Log.d(TAG, "SMOOTH scrolling done");
248504c54ea52c1b2aae6f8f4ae128f1dcaac7e3f6aYigit Boyar        getInstrumentation().waitForIdleSync();
2498edcb0bdeaba6931f9d8154f0c81f57da7ddab2aYigit Boyar    }
2508edcb0bdeaba6931f9d8154f0c81f57da7ddab2aYigit Boyar
251d7d27e9ebe5c7325e67e1a8af265378bd2056cadChet Haase    class TestViewHolder extends RecyclerView.ViewHolder {
252d7d27e9ebe5c7325e67e1a8af265378bd2056cadChet Haase
253d7d27e9ebe5c7325e67e1a8af265378bd2056cadChet Haase        Item mBindedItem;
254d7d27e9ebe5c7325e67e1a8af265378bd2056cadChet Haase
255d7d27e9ebe5c7325e67e1a8af265378bd2056cadChet Haase        public TestViewHolder(View itemView) {
256d7d27e9ebe5c7325e67e1a8af265378bd2056cadChet Haase            super(itemView);
257310e95e1c6dfe4f26ef594233e65e1ff83e0f1ffYigit Boyar            itemView.setFocusable(true);
258d7d27e9ebe5c7325e67e1a8af265378bd2056cadChet Haase        }
259504c54ea52c1b2aae6f8f4ae128f1dcaac7e3f6aYigit Boyar
260504c54ea52c1b2aae6f8f4ae128f1dcaac7e3f6aYigit Boyar        @Override
261504c54ea52c1b2aae6f8f4ae128f1dcaac7e3f6aYigit Boyar        public String toString() {
262504c54ea52c1b2aae6f8f4ae128f1dcaac7e3f6aYigit Boyar            return super.toString() + " item:" + mBindedItem;
263504c54ea52c1b2aae6f8f4ae128f1dcaac7e3f6aYigit Boyar        }
264d7d27e9ebe5c7325e67e1a8af265378bd2056cadChet Haase    }
265d7d27e9ebe5c7325e67e1a8af265378bd2056cadChet Haase
266d7d27e9ebe5c7325e67e1a8af265378bd2056cadChet Haase    class TestLayoutManager extends RecyclerView.LayoutManager {
267d7d27e9ebe5c7325e67e1a8af265378bd2056cadChet Haase
268d7d27e9ebe5c7325e67e1a8af265378bd2056cadChet Haase        CountDownLatch layoutLatch;
269d7d27e9ebe5c7325e67e1a8af265378bd2056cadChet Haase
270d7d27e9ebe5c7325e67e1a8af265378bd2056cadChet Haase        public void expectLayouts(int count) {
271d7d27e9ebe5c7325e67e1a8af265378bd2056cadChet Haase            layoutLatch = new CountDownLatch(count);
272d7d27e9ebe5c7325e67e1a8af265378bd2056cadChet Haase        }
273d7d27e9ebe5c7325e67e1a8af265378bd2056cadChet Haase
274b5f8b4fecb531aee6b359b4968409410aba90817Yigit Boyar        public void waitForLayout(long timeout, TimeUnit timeUnit) throws Throwable {
275b5f8b4fecb531aee6b359b4968409410aba90817Yigit Boyar            layoutLatch.await(timeout * (mDebug ? 100 : 1), timeUnit);
276d7d27e9ebe5c7325e67e1a8af265378bd2056cadChet Haase            assertEquals("all expected layouts should be executed at the expected time",
277d7d27e9ebe5c7325e67e1a8af265378bd2056cadChet Haase                    0, layoutLatch.getCount());
278e0c347f627f8a78d3e5e3e5eaac9c3ae26208689Yigit Boyar            getInstrumentation().waitForIdleSync();
279d7d27e9ebe5c7325e67e1a8af265378bd2056cadChet Haase        }
280d7d27e9ebe5c7325e67e1a8af265378bd2056cadChet Haase
281b5f8b4fecb531aee6b359b4968409410aba90817Yigit Boyar        public void assertLayoutCount(int count, String msg, long timeout) throws Throwable {
282b5f8b4fecb531aee6b359b4968409410aba90817Yigit Boyar            layoutLatch.await(timeout, TimeUnit.SECONDS);
283b5f8b4fecb531aee6b359b4968409410aba90817Yigit Boyar            assertEquals(msg, count, layoutLatch.getCount());
284b5f8b4fecb531aee6b359b4968409410aba90817Yigit Boyar        }
285b5f8b4fecb531aee6b359b4968409410aba90817Yigit Boyar
286b5f8b4fecb531aee6b359b4968409410aba90817Yigit Boyar        public void assertNoLayout(String msg, long timeout) throws Throwable {
287b5f8b4fecb531aee6b359b4968409410aba90817Yigit Boyar            layoutLatch.await(timeout, TimeUnit.SECONDS);
288b5f8b4fecb531aee6b359b4968409410aba90817Yigit Boyar            assertFalse(msg, layoutLatch.getCount() == 0);
289b5f8b4fecb531aee6b359b4968409410aba90817Yigit Boyar        }
290b5f8b4fecb531aee6b359b4968409410aba90817Yigit Boyar
291b5f8b4fecb531aee6b359b4968409410aba90817Yigit Boyar        public void waitForLayout(long timeout) throws Throwable {
2928ae76f91527ce850f155ce960fb9068bcd5d49f9Yigit Boyar            waitForLayout(timeout * (mDebug ? 10000 : 1), TimeUnit.SECONDS);
293b5f8b4fecb531aee6b359b4968409410aba90817Yigit Boyar        }
294b5f8b4fecb531aee6b359b4968409410aba90817Yigit Boyar
295d7d27e9ebe5c7325e67e1a8af265378bd2056cadChet Haase        @Override
296d7d27e9ebe5c7325e67e1a8af265378bd2056cadChet Haase        public RecyclerView.LayoutParams generateDefaultLayoutParams() {
297d7d27e9ebe5c7325e67e1a8af265378bd2056cadChet Haase            return new RecyclerView.LayoutParams(ViewGroup.LayoutParams.WRAP_CONTENT,
298d7d27e9ebe5c7325e67e1a8af265378bd2056cadChet Haase                    ViewGroup.LayoutParams.WRAP_CONTENT);
299d7d27e9ebe5c7325e67e1a8af265378bd2056cadChet Haase        }
300d7d27e9ebe5c7325e67e1a8af265378bd2056cadChet Haase
301d7d27e9ebe5c7325e67e1a8af265378bd2056cadChet Haase        void assertVisibleItemPositions() {
302d7d27e9ebe5c7325e67e1a8af265378bd2056cadChet Haase            int i = getChildCount();
303d7d27e9ebe5c7325e67e1a8af265378bd2056cadChet Haase            TestAdapter testAdapter = (TestAdapter) mRecyclerView.getAdapter();
304d7d27e9ebe5c7325e67e1a8af265378bd2056cadChet Haase            while (i-- > 0) {
305d7d27e9ebe5c7325e67e1a8af265378bd2056cadChet Haase                View view = getChildAt(i);
306d7d27e9ebe5c7325e67e1a8af265378bd2056cadChet Haase                RecyclerView.LayoutParams lp = (RecyclerView.LayoutParams) view.getLayoutParams();
307d7d27e9ebe5c7325e67e1a8af265378bd2056cadChet Haase                Item item = ((TestViewHolder) lp.mViewHolder).mBindedItem;
308b5f8b4fecb531aee6b359b4968409410aba90817Yigit Boyar                if (mDebug) {
309d7d27e9ebe5c7325e67e1a8af265378bd2056cadChet Haase                    Log.d(TAG, "testing item " + i);
310d7d27e9ebe5c7325e67e1a8af265378bd2056cadChet Haase                }
3118ae76f91527ce850f155ce960fb9068bcd5d49f9Yigit Boyar                if (!lp.isItemRemoved()) {
3128ae76f91527ce850f155ce960fb9068bcd5d49f9Yigit Boyar                    RecyclerView.ViewHolder vh = mRecyclerView.getChildViewHolder(view);
3138ae76f91527ce850f155ce960fb9068bcd5d49f9Yigit Boyar                    assertSame("item position in LP should match adapter value :" + vh,
3148ae76f91527ce850f155ce960fb9068bcd5d49f9Yigit Boyar                            testAdapter.mItems.get(vh.mPosition), item);
3158ae76f91527ce850f155ce960fb9068bcd5d49f9Yigit Boyar                }
316d7d27e9ebe5c7325e67e1a8af265378bd2056cadChet Haase            }
317d7d27e9ebe5c7325e67e1a8af265378bd2056cadChet Haase        }
318d7d27e9ebe5c7325e67e1a8af265378bd2056cadChet Haase
319b5f8b4fecb531aee6b359b4968409410aba90817Yigit Boyar        RecyclerView.LayoutParams getLp(View v) {
320b5f8b4fecb531aee6b359b4968409410aba90817Yigit Boyar            return (RecyclerView.LayoutParams) v.getLayoutParams();
321b5f8b4fecb531aee6b359b4968409410aba90817Yigit Boyar        }
322b5f8b4fecb531aee6b359b4968409410aba90817Yigit Boyar
323f485be9def4c0f72cfdfa6b0c616c23f04652817Yigit Boyar        void layoutRange(RecyclerView.Recycler recycler, int start, int end) {
324fe3996cf5af65fe11cc3ed47b67123ec267bd336Yigit Boyar            assertScrap(recycler);
325b5f8b4fecb531aee6b359b4968409410aba90817Yigit Boyar            if (mDebug) {
326d7d27e9ebe5c7325e67e1a8af265378bd2056cadChet Haase                Log.d(TAG, "will layout items from " + start + " to " + end);
327d7d27e9ebe5c7325e67e1a8af265378bd2056cadChet Haase            }
3285ced882cabdcefbb469e332f6f73983999aad0e5Yigit Boyar            int diff = end > start ? 1 : -1;
329504c54ea52c1b2aae6f8f4ae128f1dcaac7e3f6aYigit Boyar            int top = 0;
3305ced882cabdcefbb469e332f6f73983999aad0e5Yigit Boyar            for (int i = start; i != end; i+=diff) {
331b5f8b4fecb531aee6b359b4968409410aba90817Yigit Boyar                if (mDebug) {
332d7d27e9ebe5c7325e67e1a8af265378bd2056cadChet Haase                    Log.d(TAG, "laying out item " + i);
333d7d27e9ebe5c7325e67e1a8af265378bd2056cadChet Haase                }
334d7d27e9ebe5c7325e67e1a8af265378bd2056cadChet Haase                View view = recycler.getViewForPosition(i);
335b5f8b4fecb531aee6b359b4968409410aba90817Yigit Boyar                assertNotNull("view should not be null for valid position. "
336b5f8b4fecb531aee6b359b4968409410aba90817Yigit Boyar                        + "got null view at position " + i, view);
337c8b0ff5869e17a656a3a1d7fb306efa5b99fa07fYigit Boyar                if (!mRecyclerView.mState.isPreLayout()) {
338c8b0ff5869e17a656a3a1d7fb306efa5b99fa07fYigit Boyar                    RecyclerView.LayoutParams layoutParams = (RecyclerView.LayoutParams) view
339c8b0ff5869e17a656a3a1d7fb306efa5b99fa07fYigit Boyar                            .getLayoutParams();
340c8b0ff5869e17a656a3a1d7fb306efa5b99fa07fYigit Boyar                    assertFalse("In post layout, getViewForPosition should never return a view "
341c8b0ff5869e17a656a3a1d7fb306efa5b99fa07fYigit Boyar                            + "that is removed", layoutParams != null
342c8b0ff5869e17a656a3a1d7fb306efa5b99fa07fYigit Boyar                            && layoutParams.isItemRemoved());
343c8b0ff5869e17a656a3a1d7fb306efa5b99fa07fYigit Boyar
344b5f8b4fecb531aee6b359b4968409410aba90817Yigit Boyar                }
3458da5bba529a2fe9bd2aafab28010e11ec4de93c7Yigit Boyar                assertEquals("getViewForPosition should return correct position",
3468da5bba529a2fe9bd2aafab28010e11ec4de93c7Yigit Boyar                        i, getPosition(view));
347c8b0ff5869e17a656a3a1d7fb306efa5b99fa07fYigit Boyar                addView(view);
348b5f8b4fecb531aee6b359b4968409410aba90817Yigit Boyar
349d7d27e9ebe5c7325e67e1a8af265378bd2056cadChet Haase                measureChildWithMargins(view, 0, 0);
350504c54ea52c1b2aae6f8f4ae128f1dcaac7e3f6aYigit Boyar                layoutDecorated(view, 0, top, getDecoratedMeasuredWidth(view)
351504c54ea52c1b2aae6f8f4ae128f1dcaac7e3f6aYigit Boyar                        , top + getDecoratedMeasuredHeight(view));
352504c54ea52c1b2aae6f8f4ae128f1dcaac7e3f6aYigit Boyar                top += view.getMeasuredHeight();
353d7d27e9ebe5c7325e67e1a8af265378bd2056cadChet Haase            }
354d7d27e9ebe5c7325e67e1a8af265378bd2056cadChet Haase        }
355fe3996cf5af65fe11cc3ed47b67123ec267bd336Yigit Boyar
356fe3996cf5af65fe11cc3ed47b67123ec267bd336Yigit Boyar        private void assertScrap(RecyclerView.Recycler recycler) {
357e0c347f627f8a78d3e5e3e5eaac9c3ae26208689Yigit Boyar            if (mRecyclerView.getAdapter() != null &&
358e0c347f627f8a78d3e5e3e5eaac9c3ae26208689Yigit Boyar                    !mRecyclerView.getAdapter().hasStableIds()) {
359e0c347f627f8a78d3e5e3e5eaac9c3ae26208689Yigit Boyar                for (RecyclerView.ViewHolder viewHolder : recycler.getScrapList()) {
360e0c347f627f8a78d3e5e3e5eaac9c3ae26208689Yigit Boyar                    assertFalse("Invalid scrap should be no kept", viewHolder.isInvalid());
361e0c347f627f8a78d3e5e3e5eaac9c3ae26208689Yigit Boyar                }
362fe3996cf5af65fe11cc3ed47b67123ec267bd336Yigit Boyar            }
363fe3996cf5af65fe11cc3ed47b67123ec267bd336Yigit Boyar        }
364bb6ebab3b3b85ec20b3bc262289a2460705322f5Yigit Boyar
365bb6ebab3b3b85ec20b3bc262289a2460705322f5Yigit Boyar        @Override
366bb6ebab3b3b85ec20b3bc262289a2460705322f5Yigit Boyar        public boolean canScrollHorizontally() {
367bb6ebab3b3b85ec20b3bc262289a2460705322f5Yigit Boyar            return true;
368bb6ebab3b3b85ec20b3bc262289a2460705322f5Yigit Boyar        }
369bb6ebab3b3b85ec20b3bc262289a2460705322f5Yigit Boyar
370bb6ebab3b3b85ec20b3bc262289a2460705322f5Yigit Boyar        @Override
371bb6ebab3b3b85ec20b3bc262289a2460705322f5Yigit Boyar        public boolean canScrollVertically() {
372bb6ebab3b3b85ec20b3bc262289a2460705322f5Yigit Boyar            return true;
373bb6ebab3b3b85ec20b3bc262289a2460705322f5Yigit Boyar        }
374bb6ebab3b3b85ec20b3bc262289a2460705322f5Yigit Boyar
375bb6ebab3b3b85ec20b3bc262289a2460705322f5Yigit Boyar        @Override
376bb6ebab3b3b85ec20b3bc262289a2460705322f5Yigit Boyar        public int scrollHorizontallyBy(int dx, RecyclerView.Recycler recycler,
377bb6ebab3b3b85ec20b3bc262289a2460705322f5Yigit Boyar                RecyclerView.State state) {
378bb6ebab3b3b85ec20b3bc262289a2460705322f5Yigit Boyar            return dx;
379bb6ebab3b3b85ec20b3bc262289a2460705322f5Yigit Boyar        }
380bb6ebab3b3b85ec20b3bc262289a2460705322f5Yigit Boyar
381bb6ebab3b3b85ec20b3bc262289a2460705322f5Yigit Boyar        @Override
382bb6ebab3b3b85ec20b3bc262289a2460705322f5Yigit Boyar        public int scrollVerticallyBy(int dy, RecyclerView.Recycler recycler,
383bb6ebab3b3b85ec20b3bc262289a2460705322f5Yigit Boyar                RecyclerView.State state) {
384bb6ebab3b3b85ec20b3bc262289a2460705322f5Yigit Boyar            return dy;
385bb6ebab3b3b85ec20b3bc262289a2460705322f5Yigit Boyar        }
386d7d27e9ebe5c7325e67e1a8af265378bd2056cadChet Haase    }
387d7d27e9ebe5c7325e67e1a8af265378bd2056cadChet Haase
388d7d27e9ebe5c7325e67e1a8af265378bd2056cadChet Haase    static class Item {
38911b7cfea15306c0d6e4ed7da09f7698df01695dfYigit Boyar        final static AtomicInteger idCounter = new AtomicInteger(0);
39011b7cfea15306c0d6e4ed7da09f7698df01695dfYigit Boyar        final public int mId = idCounter.incrementAndGet();
391d7d27e9ebe5c7325e67e1a8af265378bd2056cadChet Haase
3922d2e8d88103866b631eb0f3805da137ebcfb0275Yigit Boyar        int mAdapterIndex;
393d7d27e9ebe5c7325e67e1a8af265378bd2056cadChet Haase
3942d2e8d88103866b631eb0f3805da137ebcfb0275Yigit Boyar        final String mText;
395d7d27e9ebe5c7325e67e1a8af265378bd2056cadChet Haase
3962d2e8d88103866b631eb0f3805da137ebcfb0275Yigit Boyar        Item(int adapterIndex, String text) {
3972d2e8d88103866b631eb0f3805da137ebcfb0275Yigit Boyar            mAdapterIndex = adapterIndex;
3982d2e8d88103866b631eb0f3805da137ebcfb0275Yigit Boyar            mText = text;
399d7d27e9ebe5c7325e67e1a8af265378bd2056cadChet Haase        }
4008ae76f91527ce850f155ce960fb9068bcd5d49f9Yigit Boyar
4018ae76f91527ce850f155ce960fb9068bcd5d49f9Yigit Boyar        @Override
4028ae76f91527ce850f155ce960fb9068bcd5d49f9Yigit Boyar        public String toString() {
4038ae76f91527ce850f155ce960fb9068bcd5d49f9Yigit Boyar            return "Item{" +
4048ae76f91527ce850f155ce960fb9068bcd5d49f9Yigit Boyar                    "mId=" + mId +
4052d2e8d88103866b631eb0f3805da137ebcfb0275Yigit Boyar                    ", originalIndex=" + mAdapterIndex +
4062d2e8d88103866b631eb0f3805da137ebcfb0275Yigit Boyar                    ", text='" + mText + '\'' +
4078ae76f91527ce850f155ce960fb9068bcd5d49f9Yigit Boyar                    '}';
4088ae76f91527ce850f155ce960fb9068bcd5d49f9Yigit Boyar        }
409d7d27e9ebe5c7325e67e1a8af265378bd2056cadChet Haase    }
410d7d27e9ebe5c7325e67e1a8af265378bd2056cadChet Haase
411d7d27e9ebe5c7325e67e1a8af265378bd2056cadChet Haase    class TestAdapter extends RecyclerView.Adapter<TestViewHolder> {
412d7d27e9ebe5c7325e67e1a8af265378bd2056cadChet Haase
413d7d27e9ebe5c7325e67e1a8af265378bd2056cadChet Haase        List<Item> mItems;
414d7d27e9ebe5c7325e67e1a8af265378bd2056cadChet Haase
415d7d27e9ebe5c7325e67e1a8af265378bd2056cadChet Haase        TestAdapter(int count) {
416d7d27e9ebe5c7325e67e1a8af265378bd2056cadChet Haase            mItems = new ArrayList<Item>(count);
417d7d27e9ebe5c7325e67e1a8af265378bd2056cadChet Haase            for (int i = 0; i < count; i++) {
418d7d27e9ebe5c7325e67e1a8af265378bd2056cadChet Haase                mItems.add(new Item(i, "Item " + i));
419d7d27e9ebe5c7325e67e1a8af265378bd2056cadChet Haase            }
420d7d27e9ebe5c7325e67e1a8af265378bd2056cadChet Haase        }
421d7d27e9ebe5c7325e67e1a8af265378bd2056cadChet Haase
422d7d27e9ebe5c7325e67e1a8af265378bd2056cadChet Haase        @Override
423d7d27e9ebe5c7325e67e1a8af265378bd2056cadChet Haase        public TestViewHolder onCreateViewHolder(ViewGroup parent,
424d7d27e9ebe5c7325e67e1a8af265378bd2056cadChet Haase                int viewType) {
425d7d27e9ebe5c7325e67e1a8af265378bd2056cadChet Haase            return new TestViewHolder(new TextView(parent.getContext()));
426d7d27e9ebe5c7325e67e1a8af265378bd2056cadChet Haase        }
427d7d27e9ebe5c7325e67e1a8af265378bd2056cadChet Haase
428d7d27e9ebe5c7325e67e1a8af265378bd2056cadChet Haase        @Override
429d7d27e9ebe5c7325e67e1a8af265378bd2056cadChet Haase        public void onBindViewHolder(TestViewHolder holder, int position) {
430d7d27e9ebe5c7325e67e1a8af265378bd2056cadChet Haase            final Item item = mItems.get(position);
4312d2e8d88103866b631eb0f3805da137ebcfb0275Yigit Boyar            ((TextView) (holder.itemView)).setText(item.mText + "(" + item.mAdapterIndex + ")");
432d7d27e9ebe5c7325e67e1a8af265378bd2056cadChet Haase            holder.mBindedItem = item;
433d7d27e9ebe5c7325e67e1a8af265378bd2056cadChet Haase        }
434d7d27e9ebe5c7325e67e1a8af265378bd2056cadChet Haase
435b5f8b4fecb531aee6b359b4968409410aba90817Yigit Boyar        public void deleteAndNotify(final int start, final int count) throws Throwable {
436b5f8b4fecb531aee6b359b4968409410aba90817Yigit Boyar            deleteAndNotify(new int[]{start, count});
437b5f8b4fecb531aee6b359b4968409410aba90817Yigit Boyar        }
438b5f8b4fecb531aee6b359b4968409410aba90817Yigit Boyar
439b5f8b4fecb531aee6b359b4968409410aba90817Yigit Boyar        /**
440b5f8b4fecb531aee6b359b4968409410aba90817Yigit Boyar         * Deletes items in the given ranges.
441b5f8b4fecb531aee6b359b4968409410aba90817Yigit Boyar         * <p>
442b5f8b4fecb531aee6b359b4968409410aba90817Yigit Boyar         * Note that each operation affects the one after so you should offset them properly.
443b5f8b4fecb531aee6b359b4968409410aba90817Yigit Boyar         * <p>
444b5f8b4fecb531aee6b359b4968409410aba90817Yigit Boyar         * For example, if adapter has 5 items (A,B,C,D,E), and then you call this method with
445b5f8b4fecb531aee6b359b4968409410aba90817Yigit Boyar         * <code>[1, 2],[2, 1]</code>, it will first delete items B,C and the new adapter will be
446b5f8b4fecb531aee6b359b4968409410aba90817Yigit Boyar         * A D E. Then it will delete 2,1 which means it will delete E.
447b5f8b4fecb531aee6b359b4968409410aba90817Yigit Boyar         */
448b5f8b4fecb531aee6b359b4968409410aba90817Yigit Boyar        public void deleteAndNotify(final int[]... startCountTuples) throws Throwable {
4498ae76f91527ce850f155ce960fb9068bcd5d49f9Yigit Boyar            for (int[] tuple : startCountTuples) {
4508ae76f91527ce850f155ce960fb9068bcd5d49f9Yigit Boyar                tuple[1] = -tuple[1];
4518ae76f91527ce850f155ce960fb9068bcd5d49f9Yigit Boyar            }
4528ae76f91527ce850f155ce960fb9068bcd5d49f9Yigit Boyar            new AddRemoveRunnable(startCountTuples).runOnMainThread();
453d7d27e9ebe5c7325e67e1a8af265378bd2056cadChet Haase        }
454d7d27e9ebe5c7325e67e1a8af265378bd2056cadChet Haase
455c8b0ff5869e17a656a3a1d7fb306efa5b99fa07fYigit Boyar        @Override
456c8b0ff5869e17a656a3a1d7fb306efa5b99fa07fYigit Boyar        public long getItemId(int position) {
457c8b0ff5869e17a656a3a1d7fb306efa5b99fa07fYigit Boyar            return hasStableIds() ? mItems.get(position).mId : super.getItemId(position);
458c8b0ff5869e17a656a3a1d7fb306efa5b99fa07fYigit Boyar        }
459c8b0ff5869e17a656a3a1d7fb306efa5b99fa07fYigit Boyar
4602d2e8d88103866b631eb0f3805da137ebcfb0275Yigit Boyar        public void offsetOriginalIndices(int start, int offset) {
4612d2e8d88103866b631eb0f3805da137ebcfb0275Yigit Boyar            for (int i = start; i < mItems.size(); i++) {
4622d2e8d88103866b631eb0f3805da137ebcfb0275Yigit Boyar                mItems.get(i).mAdapterIndex += offset;
4632d2e8d88103866b631eb0f3805da137ebcfb0275Yigit Boyar            }
4642d2e8d88103866b631eb0f3805da137ebcfb0275Yigit Boyar        }
4652d2e8d88103866b631eb0f3805da137ebcfb0275Yigit Boyar
466e0c347f627f8a78d3e5e3e5eaac9c3ae26208689Yigit Boyar        /**
467e0c347f627f8a78d3e5e3e5eaac9c3ae26208689Yigit Boyar         * @param start inclusive
468e0c347f627f8a78d3e5e3e5eaac9c3ae26208689Yigit Boyar         * @param end exclusive
469e0c347f627f8a78d3e5e3e5eaac9c3ae26208689Yigit Boyar         * @param offset
470e0c347f627f8a78d3e5e3e5eaac9c3ae26208689Yigit Boyar         */
471e0c347f627f8a78d3e5e3e5eaac9c3ae26208689Yigit Boyar        public void offsetOriginalIndicesBetween(int start, int end, int offset) {
472e0c347f627f8a78d3e5e3e5eaac9c3ae26208689Yigit Boyar            for (int i = start; i < end && i < mItems.size(); i++) {
473e0c347f627f8a78d3e5e3e5eaac9c3ae26208689Yigit Boyar                mItems.get(i).mAdapterIndex += offset;
474e0c347f627f8a78d3e5e3e5eaac9c3ae26208689Yigit Boyar            }
475e0c347f627f8a78d3e5e3e5eaac9c3ae26208689Yigit Boyar        }
476e0c347f627f8a78d3e5e3e5eaac9c3ae26208689Yigit Boyar
477b5f8b4fecb531aee6b359b4968409410aba90817Yigit Boyar        public void addAndNotify(final int start, final int count) throws Throwable {
478b5f8b4fecb531aee6b359b4968409410aba90817Yigit Boyar            addAndNotify(new int[]{start, count});
479b5f8b4fecb531aee6b359b4968409410aba90817Yigit Boyar        }
480b5f8b4fecb531aee6b359b4968409410aba90817Yigit Boyar
481b5f8b4fecb531aee6b359b4968409410aba90817Yigit Boyar        public void addAndNotify(final int[]... startCountTuples) throws Throwable {
4828ae76f91527ce850f155ce960fb9068bcd5d49f9Yigit Boyar            new AddRemoveRunnable(startCountTuples).runOnMainThread();
4838ae76f91527ce850f155ce960fb9068bcd5d49f9Yigit Boyar        }
484b5f8b4fecb531aee6b359b4968409410aba90817Yigit Boyar
485cadd54707a7aaa4cfa9c8b8051c6185e8717f22eChet Haase        public void dispatchDataSetChanged() throws Throwable {
48622b6b8163d2e15eb6d0a89906854dec801945f1dYigit Boyar            runTestOnUiThread(new Runnable() {
48722b6b8163d2e15eb6d0a89906854dec801945f1dYigit Boyar                @Override
48822b6b8163d2e15eb6d0a89906854dec801945f1dYigit Boyar                public void run() {
48922b6b8163d2e15eb6d0a89906854dec801945f1dYigit Boyar                    notifyDataSetChanged();
49022b6b8163d2e15eb6d0a89906854dec801945f1dYigit Boyar                }
49122b6b8163d2e15eb6d0a89906854dec801945f1dYigit Boyar            });
49222b6b8163d2e15eb6d0a89906854dec801945f1dYigit Boyar        }
49322b6b8163d2e15eb6d0a89906854dec801945f1dYigit Boyar
494cadd54707a7aaa4cfa9c8b8051c6185e8717f22eChet Haase        public void changeAndNotify(final int start, final int count) throws Throwable {
4957e9da1a2629b657d56e2f54abd18c6249f3be037Yigit Boyar            runTestOnUiThread(new Runnable() {
4967e9da1a2629b657d56e2f54abd18c6249f3be037Yigit Boyar                @Override
4977e9da1a2629b657d56e2f54abd18c6249f3be037Yigit Boyar                public void run() {
4987e9da1a2629b657d56e2f54abd18c6249f3be037Yigit Boyar                    notifyItemRangeChanged(start, count);
4997e9da1a2629b657d56e2f54abd18c6249f3be037Yigit Boyar                }
5007e9da1a2629b657d56e2f54abd18c6249f3be037Yigit Boyar            });
5017e9da1a2629b657d56e2f54abd18c6249f3be037Yigit Boyar        }
5027e9da1a2629b657d56e2f54abd18c6249f3be037Yigit Boyar
503cadd54707a7aaa4cfa9c8b8051c6185e8717f22eChet Haase        public void changePositionsAndNotify(final int... positions) throws Throwable {
504cadd54707a7aaa4cfa9c8b8051c6185e8717f22eChet Haase            runTestOnUiThread(new Runnable() {
505cadd54707a7aaa4cfa9c8b8051c6185e8717f22eChet Haase                @Override
506cadd54707a7aaa4cfa9c8b8051c6185e8717f22eChet Haase                public void run() {
507cadd54707a7aaa4cfa9c8b8051c6185e8717f22eChet Haase                    for (int i = 0; i < positions.length; i += 1) {
508cadd54707a7aaa4cfa9c8b8051c6185e8717f22eChet Haase                        TestAdapter.super.notifyItemRangeChanged(positions[i], 1);
509cadd54707a7aaa4cfa9c8b8051c6185e8717f22eChet Haase                    }
510cadd54707a7aaa4cfa9c8b8051c6185e8717f22eChet Haase                }
511cadd54707a7aaa4cfa9c8b8051c6185e8717f22eChet Haase            });
512cadd54707a7aaa4cfa9c8b8051c6185e8717f22eChet Haase        }
513cadd54707a7aaa4cfa9c8b8051c6185e8717f22eChet Haase
5148ae76f91527ce850f155ce960fb9068bcd5d49f9Yigit Boyar        /**
5158ae76f91527ce850f155ce960fb9068bcd5d49f9Yigit Boyar         * Similar to other methods but negative count means delete and position count means add.
5168ae76f91527ce850f155ce960fb9068bcd5d49f9Yigit Boyar         * <p>
5178ae76f91527ce850f155ce960fb9068bcd5d49f9Yigit Boyar         * For instance, calling this method with <code>[1,1], [2,-1]</code> it will first add an
5188ae76f91527ce850f155ce960fb9068bcd5d49f9Yigit Boyar         * item to index 1, then remove an item from index 2 (updated index 2)
5198ae76f91527ce850f155ce960fb9068bcd5d49f9Yigit Boyar         */
5208ae76f91527ce850f155ce960fb9068bcd5d49f9Yigit Boyar        public void addDeleteAndNotify(final int[]... startCountTuples) throws Throwable {
5218ae76f91527ce850f155ce960fb9068bcd5d49f9Yigit Boyar            new AddRemoveRunnable(startCountTuples).runOnMainThread();
522d7d27e9ebe5c7325e67e1a8af265378bd2056cadChet Haase        }
523d7d27e9ebe5c7325e67e1a8af265378bd2056cadChet Haase
524d7d27e9ebe5c7325e67e1a8af265378bd2056cadChet Haase        @Override
525d7d27e9ebe5c7325e67e1a8af265378bd2056cadChet Haase        public int getItemCount() {
526d7d27e9ebe5c7325e67e1a8af265378bd2056cadChet Haase            return mItems.size();
527d7d27e9ebe5c7325e67e1a8af265378bd2056cadChet Haase        }
5288ae76f91527ce850f155ce960fb9068bcd5d49f9Yigit Boyar
529e0c347f627f8a78d3e5e3e5eaac9c3ae26208689Yigit Boyar        public void moveItems(boolean notifyChange, int[]... fromToTuples) throws Throwable {
530e0c347f627f8a78d3e5e3e5eaac9c3ae26208689Yigit Boyar            for (int i = 0; i < fromToTuples.length; i += 1) {
531e0c347f627f8a78d3e5e3e5eaac9c3ae26208689Yigit Boyar                int[] tuple = fromToTuples[i];
532e0c347f627f8a78d3e5e3e5eaac9c3ae26208689Yigit Boyar                moveItem(tuple[0], tuple[1], false);
533e0c347f627f8a78d3e5e3e5eaac9c3ae26208689Yigit Boyar            }
534e0c347f627f8a78d3e5e3e5eaac9c3ae26208689Yigit Boyar            if (notifyChange) {
535cadd54707a7aaa4cfa9c8b8051c6185e8717f22eChet Haase                dispatchDataSetChanged();
536e0c347f627f8a78d3e5e3e5eaac9c3ae26208689Yigit Boyar            }
537e0c347f627f8a78d3e5e3e5eaac9c3ae26208689Yigit Boyar        }
538e0c347f627f8a78d3e5e3e5eaac9c3ae26208689Yigit Boyar
539e0c347f627f8a78d3e5e3e5eaac9c3ae26208689Yigit Boyar        public void moveItem(final int from, final int to, final boolean notifyChange)
540e0c347f627f8a78d3e5e3e5eaac9c3ae26208689Yigit Boyar                throws Throwable {
541e0c347f627f8a78d3e5e3e5eaac9c3ae26208689Yigit Boyar            runTestOnUiThread(new Runnable() {
542e0c347f627f8a78d3e5e3e5eaac9c3ae26208689Yigit Boyar                @Override
543e0c347f627f8a78d3e5e3e5eaac9c3ae26208689Yigit Boyar                public void run() {
544e0c347f627f8a78d3e5e3e5eaac9c3ae26208689Yigit Boyar                    Item item = mItems.remove(from);
545e0c347f627f8a78d3e5e3e5eaac9c3ae26208689Yigit Boyar                    mItems.add(to, item);
546e0c347f627f8a78d3e5e3e5eaac9c3ae26208689Yigit Boyar                    offsetOriginalIndices(from, to - 1);
547e0c347f627f8a78d3e5e3e5eaac9c3ae26208689Yigit Boyar                    item.mAdapterIndex = to;
548e0c347f627f8a78d3e5e3e5eaac9c3ae26208689Yigit Boyar                    if (notifyChange) {
549e0c347f627f8a78d3e5e3e5eaac9c3ae26208689Yigit Boyar                        notifyDataSetChanged();
550e0c347f627f8a78d3e5e3e5eaac9c3ae26208689Yigit Boyar                    }
551e0c347f627f8a78d3e5e3e5eaac9c3ae26208689Yigit Boyar                }
552e0c347f627f8a78d3e5e3e5eaac9c3ae26208689Yigit Boyar            });
553e0c347f627f8a78d3e5e3e5eaac9c3ae26208689Yigit Boyar        }
554e0c347f627f8a78d3e5e3e5eaac9c3ae26208689Yigit Boyar
5558ae76f91527ce850f155ce960fb9068bcd5d49f9Yigit Boyar
5568ae76f91527ce850f155ce960fb9068bcd5d49f9Yigit Boyar        private class AddRemoveRunnable implements Runnable {
5578ae76f91527ce850f155ce960fb9068bcd5d49f9Yigit Boyar            final int[][] mStartCountTuples;
5588ae76f91527ce850f155ce960fb9068bcd5d49f9Yigit Boyar
5598ae76f91527ce850f155ce960fb9068bcd5d49f9Yigit Boyar            public AddRemoveRunnable(int[][] startCountTuples) {
5608ae76f91527ce850f155ce960fb9068bcd5d49f9Yigit Boyar                mStartCountTuples = startCountTuples;
5618ae76f91527ce850f155ce960fb9068bcd5d49f9Yigit Boyar            }
5628ae76f91527ce850f155ce960fb9068bcd5d49f9Yigit Boyar
5638ae76f91527ce850f155ce960fb9068bcd5d49f9Yigit Boyar            public void runOnMainThread() throws Throwable {
5648ae76f91527ce850f155ce960fb9068bcd5d49f9Yigit Boyar                if (Looper.myLooper() == Looper.getMainLooper()) {
5658ae76f91527ce850f155ce960fb9068bcd5d49f9Yigit Boyar                    run();
5668ae76f91527ce850f155ce960fb9068bcd5d49f9Yigit Boyar                } else {
5678ae76f91527ce850f155ce960fb9068bcd5d49f9Yigit Boyar                    runTestOnUiThread(this);
5688ae76f91527ce850f155ce960fb9068bcd5d49f9Yigit Boyar                }
5698ae76f91527ce850f155ce960fb9068bcd5d49f9Yigit Boyar            }
5708ae76f91527ce850f155ce960fb9068bcd5d49f9Yigit Boyar
5718ae76f91527ce850f155ce960fb9068bcd5d49f9Yigit Boyar            @Override
5728ae76f91527ce850f155ce960fb9068bcd5d49f9Yigit Boyar            public void run() {
5738ae76f91527ce850f155ce960fb9068bcd5d49f9Yigit Boyar                for (int[] tuple : mStartCountTuples) {
5748ae76f91527ce850f155ce960fb9068bcd5d49f9Yigit Boyar                    if (tuple[1] < 0) {
5758ae76f91527ce850f155ce960fb9068bcd5d49f9Yigit Boyar                        delete(tuple);
5768ae76f91527ce850f155ce960fb9068bcd5d49f9Yigit Boyar                    } else {
5778ae76f91527ce850f155ce960fb9068bcd5d49f9Yigit Boyar                        add(tuple);
5788ae76f91527ce850f155ce960fb9068bcd5d49f9Yigit Boyar                    }
5798ae76f91527ce850f155ce960fb9068bcd5d49f9Yigit Boyar                }
5808ae76f91527ce850f155ce960fb9068bcd5d49f9Yigit Boyar            }
5818ae76f91527ce850f155ce960fb9068bcd5d49f9Yigit Boyar
5828ae76f91527ce850f155ce960fb9068bcd5d49f9Yigit Boyar            private void add(int[] tuple) {
5832d2e8d88103866b631eb0f3805da137ebcfb0275Yigit Boyar                // offset others
5842d2e8d88103866b631eb0f3805da137ebcfb0275Yigit Boyar                offsetOriginalIndices(tuple[0], tuple[1]);
5858ae76f91527ce850f155ce960fb9068bcd5d49f9Yigit Boyar                for (int i = 0; i < tuple[1]; i++) {
5868ae76f91527ce850f155ce960fb9068bcd5d49f9Yigit Boyar                    mItems.add(tuple[0], new Item(i, "new item " + i));
5878ae76f91527ce850f155ce960fb9068bcd5d49f9Yigit Boyar                }
5888ae76f91527ce850f155ce960fb9068bcd5d49f9Yigit Boyar                notifyItemRangeInserted(tuple[0], tuple[1]);
5898ae76f91527ce850f155ce960fb9068bcd5d49f9Yigit Boyar            }
5908ae76f91527ce850f155ce960fb9068bcd5d49f9Yigit Boyar
5918ae76f91527ce850f155ce960fb9068bcd5d49f9Yigit Boyar            private void delete(int[] tuple) {
5922d2e8d88103866b631eb0f3805da137ebcfb0275Yigit Boyar                final int count = -tuple[1];
5932d2e8d88103866b631eb0f3805da137ebcfb0275Yigit Boyar                offsetOriginalIndices(tuple[0] + count, tuple[1]);
5942d2e8d88103866b631eb0f3805da137ebcfb0275Yigit Boyar                for (int i = 0; i < count; i++) {
5958ae76f91527ce850f155ce960fb9068bcd5d49f9Yigit Boyar                    mItems.remove(tuple[0]);
5968ae76f91527ce850f155ce960fb9068bcd5d49f9Yigit Boyar                }
5972d2e8d88103866b631eb0f3805da137ebcfb0275Yigit Boyar                notifyItemRangeRemoved(tuple[0], count);
5988ae76f91527ce850f155ce960fb9068bcd5d49f9Yigit Boyar            }
5998ae76f91527ce850f155ce960fb9068bcd5d49f9Yigit Boyar        }
6008ae76f91527ce850f155ce960fb9068bcd5d49f9Yigit Boyar    }
6018ae76f91527ce850f155ce960fb9068bcd5d49f9Yigit Boyar
6028ae76f91527ce850f155ce960fb9068bcd5d49f9Yigit Boyar    @Override
6038ae76f91527ce850f155ce960fb9068bcd5d49f9Yigit Boyar    public void runTestOnUiThread(Runnable r) throws Throwable {
6048ae76f91527ce850f155ce960fb9068bcd5d49f9Yigit Boyar        if (Looper.myLooper() == Looper.getMainLooper()) {
6058ae76f91527ce850f155ce960fb9068bcd5d49f9Yigit Boyar            r.run();
6068ae76f91527ce850f155ce960fb9068bcd5d49f9Yigit Boyar        } else {
6078ae76f91527ce850f155ce960fb9068bcd5d49f9Yigit Boyar            super.runTestOnUiThread(r);
6088ae76f91527ce850f155ce960fb9068bcd5d49f9Yigit Boyar        }
609d7d27e9ebe5c7325e67e1a8af265378bd2056cadChet Haase    }
610c50c4cad31d73e574b27bb3d7581542975e37263Yigit Boyar
611c50c4cad31d73e574b27bb3d7581542975e37263Yigit Boyar    static class TargetTuple {
612c50c4cad31d73e574b27bb3d7581542975e37263Yigit Boyar
613c50c4cad31d73e574b27bb3d7581542975e37263Yigit Boyar        final int mPosition;
614c50c4cad31d73e574b27bb3d7581542975e37263Yigit Boyar
615c50c4cad31d73e574b27bb3d7581542975e37263Yigit Boyar        final int mLayoutDirection;
616c50c4cad31d73e574b27bb3d7581542975e37263Yigit Boyar
617c50c4cad31d73e574b27bb3d7581542975e37263Yigit Boyar        TargetTuple(int position, int layoutDirection) {
618c50c4cad31d73e574b27bb3d7581542975e37263Yigit Boyar            this.mPosition = position;
619c50c4cad31d73e574b27bb3d7581542975e37263Yigit Boyar            this.mLayoutDirection = layoutDirection;
620c50c4cad31d73e574b27bb3d7581542975e37263Yigit Boyar        }
621c50c4cad31d73e574b27bb3d7581542975e37263Yigit Boyar
622c50c4cad31d73e574b27bb3d7581542975e37263Yigit Boyar        @Override
623c50c4cad31d73e574b27bb3d7581542975e37263Yigit Boyar        public String toString() {
624c50c4cad31d73e574b27bb3d7581542975e37263Yigit Boyar            return "TargetTuple{" +
625c50c4cad31d73e574b27bb3d7581542975e37263Yigit Boyar                    "mPosition=" + mPosition +
626c50c4cad31d73e574b27bb3d7581542975e37263Yigit Boyar                    ", mLayoutDirection=" + mLayoutDirection +
627c50c4cad31d73e574b27bb3d7581542975e37263Yigit Boyar                    '}';
628c50c4cad31d73e574b27bb3d7581542975e37263Yigit Boyar        }
629c50c4cad31d73e574b27bb3d7581542975e37263Yigit Boyar    }
6307a59e4b36500bcfddd842376d6c3abe0d9f23d84Yigit Boyar}
631