BaseRecyclerViewInstrumentationTest.java revision da50fde358bd3ef2edb273b3bf0265cf5e84ec6b
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;
21e2437126f0225135891544315ff72e54c7de0f59Yigit Boyarimport android.support.v4.view.ViewCompat;
22d7d27e9ebe5c7325e67e1a8af265378bd2056cadChet Haaseimport android.test.ActivityInstrumentationTestCase2;
23d7d27e9ebe5c7325e67e1a8af265378bd2056cadChet Haaseimport android.util.Log;
24d7d27e9ebe5c7325e67e1a8af265378bd2056cadChet Haaseimport android.view.View;
25d7d27e9ebe5c7325e67e1a8af265378bd2056cadChet Haaseimport android.view.ViewGroup;
26310e95e1c6dfe4f26ef594233e65e1ff83e0f1ffYigit Boyarimport android.widget.FrameLayout;
27d7d27e9ebe5c7325e67e1a8af265378bd2056cadChet Haaseimport android.widget.TextView;
28d7d27e9ebe5c7325e67e1a8af265378bd2056cadChet Haase
29d7d27e9ebe5c7325e67e1a8af265378bd2056cadChet Haaseimport java.util.ArrayList;
3090ca3086dbf66ad6bb8840e46ec8524a705e1c18Yigit Boyarimport java.util.HashSet;
31d7d27e9ebe5c7325e67e1a8af265378bd2056cadChet Haaseimport java.util.List;
3290ca3086dbf66ad6bb8840e46ec8524a705e1c18Yigit Boyarimport java.util.Set;
33d7d27e9ebe5c7325e67e1a8af265378bd2056cadChet Haaseimport java.util.concurrent.CountDownLatch;
34d7d27e9ebe5c7325e67e1a8af265378bd2056cadChet Haaseimport java.util.concurrent.TimeUnit;
3511b7cfea15306c0d6e4ed7da09f7698df01695dfYigit Boyarimport java.util.concurrent.atomic.AtomicInteger;
36da50fde358bd3ef2edb273b3bf0265cf5e84ec6bYigit Boyarimport java.util.concurrent.locks.Lock;
37da50fde358bd3ef2edb273b3bf0265cf5e84ec6bYigit Boyarimport java.util.concurrent.locks.ReentrantLock;
38d7d27e9ebe5c7325e67e1a8af265378bd2056cadChet Haase
39d7d27e9ebe5c7325e67e1a8af265378bd2056cadChet Haaseabstract public class BaseRecyclerViewInstrumentationTest extends
40d7d27e9ebe5c7325e67e1a8af265378bd2056cadChet Haase        ActivityInstrumentationTestCase2<TestActivity> {
41d7d27e9ebe5c7325e67e1a8af265378bd2056cadChet Haase
42d7d27e9ebe5c7325e67e1a8af265378bd2056cadChet Haase    private static final String TAG = "RecyclerViewTest";
43d7d27e9ebe5c7325e67e1a8af265378bd2056cadChet Haase
44b5f8b4fecb531aee6b359b4968409410aba90817Yigit Boyar    private boolean mDebug;
45b5f8b4fecb531aee6b359b4968409410aba90817Yigit Boyar
46b5f8b4fecb531aee6b359b4968409410aba90817Yigit Boyar    protected RecyclerView mRecyclerView;
47d7d27e9ebe5c7325e67e1a8af265378bd2056cadChet Haase
488ae76f91527ce850f155ce960fb9068bcd5d49f9Yigit Boyar    protected AdapterHelper mAdapterHelper;
498ae76f91527ce850f155ce960fb9068bcd5d49f9Yigit Boyar
507e9da1a2629b657d56e2f54abd18c6249f3be037Yigit Boyar    Throwable mainThreadException;
517e9da1a2629b657d56e2f54abd18c6249f3be037Yigit Boyar
52d7d27e9ebe5c7325e67e1a8af265378bd2056cadChet Haase    public BaseRecyclerViewInstrumentationTest() {
53b5f8b4fecb531aee6b359b4968409410aba90817Yigit Boyar        this(false);
54b5f8b4fecb531aee6b359b4968409410aba90817Yigit Boyar    }
55b5f8b4fecb531aee6b359b4968409410aba90817Yigit Boyar
56b5f8b4fecb531aee6b359b4968409410aba90817Yigit Boyar    public BaseRecyclerViewInstrumentationTest(boolean debug) {
578ae76f91527ce850f155ce960fb9068bcd5d49f9Yigit Boyar        super("android.support.v7.recyclerview", TestActivity.class);
58b5f8b4fecb531aee6b359b4968409410aba90817Yigit Boyar        mDebug = debug;
59b5f8b4fecb531aee6b359b4968409410aba90817Yigit Boyar    }
60b5f8b4fecb531aee6b359b4968409410aba90817Yigit Boyar
617e9da1a2629b657d56e2f54abd18c6249f3be037Yigit Boyar    void checkForMainThreadException() throws Throwable {
627e9da1a2629b657d56e2f54abd18c6249f3be037Yigit Boyar        if (mainThreadException != null) {
637e9da1a2629b657d56e2f54abd18c6249f3be037Yigit Boyar            throw mainThreadException;
647e9da1a2629b657d56e2f54abd18c6249f3be037Yigit Boyar        }
657e9da1a2629b657d56e2f54abd18c6249f3be037Yigit Boyar    }
667e9da1a2629b657d56e2f54abd18c6249f3be037Yigit Boyar
67e2437126f0225135891544315ff72e54c7de0f59Yigit Boyar    void setHasTransientState(final View view, final boolean value) {
68e2437126f0225135891544315ff72e54c7de0f59Yigit Boyar        try {
69e2437126f0225135891544315ff72e54c7de0f59Yigit Boyar            runTestOnUiThread(new Runnable() {
70e2437126f0225135891544315ff72e54c7de0f59Yigit Boyar                @Override
71e2437126f0225135891544315ff72e54c7de0f59Yigit Boyar                public void run() {
72e2437126f0225135891544315ff72e54c7de0f59Yigit Boyar                    ViewCompat.setHasTransientState(view, value);
73e2437126f0225135891544315ff72e54c7de0f59Yigit Boyar                }
74e2437126f0225135891544315ff72e54c7de0f59Yigit Boyar            });
75e2437126f0225135891544315ff72e54c7de0f59Yigit Boyar        } catch (Throwable throwable) {
76e2437126f0225135891544315ff72e54c7de0f59Yigit Boyar            Log.e(TAG, "", throwable);
77e2437126f0225135891544315ff72e54c7de0f59Yigit Boyar        }
78e2437126f0225135891544315ff72e54c7de0f59Yigit Boyar    }
79e2437126f0225135891544315ff72e54c7de0f59Yigit Boyar
800b1059b711009601ff08354e2df6bfc45266e80aYigit Boyar    void setAdapter(final RecyclerView.Adapter adapter) throws Throwable {
810b1059b711009601ff08354e2df6bfc45266e80aYigit Boyar        runTestOnUiThread(new Runnable() {
820b1059b711009601ff08354e2df6bfc45266e80aYigit Boyar            @Override
830b1059b711009601ff08354e2df6bfc45266e80aYigit Boyar            public void run() {
840b1059b711009601ff08354e2df6bfc45266e80aYigit Boyar                mRecyclerView.setAdapter(adapter);
850b1059b711009601ff08354e2df6bfc45266e80aYigit Boyar            }
860b1059b711009601ff08354e2df6bfc45266e80aYigit Boyar        });
870b1059b711009601ff08354e2df6bfc45266e80aYigit Boyar    }
880b1059b711009601ff08354e2df6bfc45266e80aYigit Boyar
890b1059b711009601ff08354e2df6bfc45266e80aYigit Boyar    void swapAdapter(final RecyclerView.Adapter adapter,
900b1059b711009601ff08354e2df6bfc45266e80aYigit Boyar            final boolean removeAndRecycleExistingViews) throws Throwable {
910b1059b711009601ff08354e2df6bfc45266e80aYigit Boyar        runTestOnUiThread(new Runnable() {
920b1059b711009601ff08354e2df6bfc45266e80aYigit Boyar            @Override
930b1059b711009601ff08354e2df6bfc45266e80aYigit Boyar            public void run() {
94824a7db96feacf3b68f56993ed72712bc935153eYigit Boyar                try {
95824a7db96feacf3b68f56993ed72712bc935153eYigit Boyar                    mRecyclerView.swapAdapter(adapter, removeAndRecycleExistingViews);
96824a7db96feacf3b68f56993ed72712bc935153eYigit Boyar                } catch (Throwable t) {
97824a7db96feacf3b68f56993ed72712bc935153eYigit Boyar                    postExceptionToInstrumentation(t);
98824a7db96feacf3b68f56993ed72712bc935153eYigit Boyar                }
990b1059b711009601ff08354e2df6bfc45266e80aYigit Boyar            }
1000b1059b711009601ff08354e2df6bfc45266e80aYigit Boyar        });
101824a7db96feacf3b68f56993ed72712bc935153eYigit Boyar        checkForMainThreadException();
1020b1059b711009601ff08354e2df6bfc45266e80aYigit Boyar    }
1030b1059b711009601ff08354e2df6bfc45266e80aYigit Boyar
1047e9da1a2629b657d56e2f54abd18c6249f3be037Yigit Boyar    void postExceptionToInstrumentation(Throwable t) {
1056e83751247c5be0211d7bffaf057431c03dfef38Yigit Boyar        if (mainThreadException != null) {
1066e83751247c5be0211d7bffaf057431c03dfef38Yigit Boyar            Log.e(TAG, "receiving another main thread exception. dropping.", t);
1076e83751247c5be0211d7bffaf057431c03dfef38Yigit Boyar        } else {
10890ca3086dbf66ad6bb8840e46ec8524a705e1c18Yigit Boyar            Log.e(TAG, "captured exception on main thread", t);
1096e83751247c5be0211d7bffaf057431c03dfef38Yigit Boyar            mainThreadException = t;
1106e83751247c5be0211d7bffaf057431c03dfef38Yigit Boyar        }
1116e83751247c5be0211d7bffaf057431c03dfef38Yigit Boyar
1127e9da1a2629b657d56e2f54abd18c6249f3be037Yigit Boyar        if (mRecyclerView != null && mRecyclerView
1137e9da1a2629b657d56e2f54abd18c6249f3be037Yigit Boyar                .getLayoutManager() instanceof TestLayoutManager) {
1147e9da1a2629b657d56e2f54abd18c6249f3be037Yigit Boyar            TestLayoutManager lm = (TestLayoutManager) mRecyclerView.getLayoutManager();
1157e9da1a2629b657d56e2f54abd18c6249f3be037Yigit Boyar            // finish all layouts so that we get the correct exception
1167e9da1a2629b657d56e2f54abd18c6249f3be037Yigit Boyar            while (lm.layoutLatch.getCount() > 0) {
1177e9da1a2629b657d56e2f54abd18c6249f3be037Yigit Boyar                lm.layoutLatch.countDown();
1187e9da1a2629b657d56e2f54abd18c6249f3be037Yigit Boyar            }
1197e9da1a2629b657d56e2f54abd18c6249f3be037Yigit Boyar        }
1207e9da1a2629b657d56e2f54abd18c6249f3be037Yigit Boyar    }
1217e9da1a2629b657d56e2f54abd18c6249f3be037Yigit Boyar
1228ae76f91527ce850f155ce960fb9068bcd5d49f9Yigit Boyar    @Override
1238ae76f91527ce850f155ce960fb9068bcd5d49f9Yigit Boyar    protected void tearDown() throws Exception {
1248ae76f91527ce850f155ce960fb9068bcd5d49f9Yigit Boyar        if (mRecyclerView != null) {
1258ae76f91527ce850f155ce960fb9068bcd5d49f9Yigit Boyar            try {
1268ae76f91527ce850f155ce960fb9068bcd5d49f9Yigit Boyar                removeRecyclerView();
1278ae76f91527ce850f155ce960fb9068bcd5d49f9Yigit Boyar            } catch (Throwable throwable) {
1288ae76f91527ce850f155ce960fb9068bcd5d49f9Yigit Boyar                throwable.printStackTrace();
1298ae76f91527ce850f155ce960fb9068bcd5d49f9Yigit Boyar            }
1308ae76f91527ce850f155ce960fb9068bcd5d49f9Yigit Boyar        }
1318ae76f91527ce850f155ce960fb9068bcd5d49f9Yigit Boyar        getInstrumentation().waitForIdleSync();
132ca9ce7ccdccf6b6a2a81da73dd1e6425d90e4b23Yigit Boyar        super.tearDown();
133ca9ce7ccdccf6b6a2a81da73dd1e6425d90e4b23Yigit Boyar
1346e83751247c5be0211d7bffaf057431c03dfef38Yigit Boyar        try {
1356e83751247c5be0211d7bffaf057431c03dfef38Yigit Boyar            checkForMainThreadException();
1366e83751247c5be0211d7bffaf057431c03dfef38Yigit Boyar        } catch (Exception e) {
1376e83751247c5be0211d7bffaf057431c03dfef38Yigit Boyar            throw e;
1386e83751247c5be0211d7bffaf057431c03dfef38Yigit Boyar        } catch (Throwable throwable) {
139ca9ce7ccdccf6b6a2a81da73dd1e6425d90e4b23Yigit Boyar            throw new Exception(throwable);
1406e83751247c5be0211d7bffaf057431c03dfef38Yigit Boyar        }
1418ae76f91527ce850f155ce960fb9068bcd5d49f9Yigit Boyar    }
1428ae76f91527ce850f155ce960fb9068bcd5d49f9Yigit Boyar
143c50c4cad31d73e574b27bb3d7581542975e37263Yigit Boyar    public Rect getDecoratedRecyclerViewBounds() {
144c50c4cad31d73e574b27bb3d7581542975e37263Yigit Boyar        return new Rect(
145c50c4cad31d73e574b27bb3d7581542975e37263Yigit Boyar                mRecyclerView.getPaddingLeft(),
146c50c4cad31d73e574b27bb3d7581542975e37263Yigit Boyar                mRecyclerView.getPaddingTop(),
147c50c4cad31d73e574b27bb3d7581542975e37263Yigit Boyar                mRecyclerView.getPaddingLeft() + mRecyclerView.getWidth(),
148c50c4cad31d73e574b27bb3d7581542975e37263Yigit Boyar                mRecyclerView.getPaddingTop() + mRecyclerView.getHeight()
149c50c4cad31d73e574b27bb3d7581542975e37263Yigit Boyar        );
150c50c4cad31d73e574b27bb3d7581542975e37263Yigit Boyar    }
151c50c4cad31d73e574b27bb3d7581542975e37263Yigit Boyar
152b5f8b4fecb531aee6b359b4968409410aba90817Yigit Boyar    public void removeRecyclerView() throws Throwable {
153d7e2f2ab1d253133fa54f309e74a7ee384c33971Yigit Boyar        if (mRecyclerView == null) {
154d7e2f2ab1d253133fa54f309e74a7ee384c33971Yigit Boyar            return;
155d7e2f2ab1d253133fa54f309e74a7ee384c33971Yigit Boyar        }
15690ca3086dbf66ad6bb8840e46ec8524a705e1c18Yigit Boyar        if (!isMainThread()) {
15790ca3086dbf66ad6bb8840e46ec8524a705e1c18Yigit Boyar            getInstrumentation().waitForIdleSync();
15890ca3086dbf66ad6bb8840e46ec8524a705e1c18Yigit Boyar        }
159b5f8b4fecb531aee6b359b4968409410aba90817Yigit Boyar        runTestOnUiThread(new Runnable() {
160b5f8b4fecb531aee6b359b4968409410aba90817Yigit Boyar            @Override
161b5f8b4fecb531aee6b359b4968409410aba90817Yigit Boyar            public void run() {
162abd0fbfcdd5b438909f126392eca5978b5939b6dYigit Boyar                try {
163abd0fbfcdd5b438909f126392eca5978b5939b6dYigit Boyar                    final RecyclerView.Adapter adapter = mRecyclerView.getAdapter();
164abd0fbfcdd5b438909f126392eca5978b5939b6dYigit Boyar                    if (adapter instanceof AttachDetachCountingAdapter) {
165abd0fbfcdd5b438909f126392eca5978b5939b6dYigit Boyar                        ((AttachDetachCountingAdapter) adapter).getCounter()
166abd0fbfcdd5b438909f126392eca5978b5939b6dYigit Boyar                                .validateRemaining(mRecyclerView);
167abd0fbfcdd5b438909f126392eca5978b5939b6dYigit Boyar                    }
168abd0fbfcdd5b438909f126392eca5978b5939b6dYigit Boyar                    getActivity().mContainer.removeAllViews();
169abd0fbfcdd5b438909f126392eca5978b5939b6dYigit Boyar                } catch (Throwable t) {
170abd0fbfcdd5b438909f126392eca5978b5939b6dYigit Boyar                    postExceptionToInstrumentation(t);
17190ca3086dbf66ad6bb8840e46ec8524a705e1c18Yigit Boyar                }
172b5f8b4fecb531aee6b359b4968409410aba90817Yigit Boyar            }
173b5f8b4fecb531aee6b359b4968409410aba90817Yigit Boyar        });
17490ca3086dbf66ad6bb8840e46ec8524a705e1c18Yigit Boyar        mRecyclerView = null;
175b5f8b4fecb531aee6b359b4968409410aba90817Yigit Boyar    }
176b5f8b4fecb531aee6b359b4968409410aba90817Yigit Boyar
177504c54ea52c1b2aae6f8f4ae128f1dcaac7e3f6aYigit Boyar    void waitForAnimations(int seconds) throws InterruptedException {
178504c54ea52c1b2aae6f8f4ae128f1dcaac7e3f6aYigit Boyar        final CountDownLatch latch = new CountDownLatch(2);
179504c54ea52c1b2aae6f8f4ae128f1dcaac7e3f6aYigit Boyar        boolean running = mRecyclerView.mItemAnimator
180504c54ea52c1b2aae6f8f4ae128f1dcaac7e3f6aYigit Boyar                .isRunning(new RecyclerView.ItemAnimator.ItemAnimatorFinishedListener() {
181504c54ea52c1b2aae6f8f4ae128f1dcaac7e3f6aYigit Boyar                    @Override
182504c54ea52c1b2aae6f8f4ae128f1dcaac7e3f6aYigit Boyar                    public void onAnimationsFinished() {
183504c54ea52c1b2aae6f8f4ae128f1dcaac7e3f6aYigit Boyar                        latch.countDown();
184504c54ea52c1b2aae6f8f4ae128f1dcaac7e3f6aYigit Boyar                    }
185504c54ea52c1b2aae6f8f4ae128f1dcaac7e3f6aYigit Boyar                });
186504c54ea52c1b2aae6f8f4ae128f1dcaac7e3f6aYigit Boyar        if (running) {
187504c54ea52c1b2aae6f8f4ae128f1dcaac7e3f6aYigit Boyar            latch.await(seconds, TimeUnit.SECONDS);
188504c54ea52c1b2aae6f8f4ae128f1dcaac7e3f6aYigit Boyar        }
189504c54ea52c1b2aae6f8f4ae128f1dcaac7e3f6aYigit Boyar    }
190504c54ea52c1b2aae6f8f4ae128f1dcaac7e3f6aYigit Boyar
191542f1260934df280985294eaef1ec8469863281fYigit Boyar    public boolean requestFocus(final View view) {
192542f1260934df280985294eaef1ec8469863281fYigit Boyar        final boolean[] result = new boolean[1];
193542f1260934df280985294eaef1ec8469863281fYigit Boyar        getActivity().runOnUiThread(new Runnable() {
194542f1260934df280985294eaef1ec8469863281fYigit Boyar            @Override
195542f1260934df280985294eaef1ec8469863281fYigit Boyar            public void run() {
196542f1260934df280985294eaef1ec8469863281fYigit Boyar                result[0] = view.requestFocus();
197542f1260934df280985294eaef1ec8469863281fYigit Boyar            }
198542f1260934df280985294eaef1ec8469863281fYigit Boyar        });
199542f1260934df280985294eaef1ec8469863281fYigit Boyar        return result[0];
200542f1260934df280985294eaef1ec8469863281fYigit Boyar    }
201542f1260934df280985294eaef1ec8469863281fYigit Boyar
202b5f8b4fecb531aee6b359b4968409410aba90817Yigit Boyar    public void setRecyclerView(final RecyclerView recyclerView) throws Throwable {
2030b1059b711009601ff08354e2df6bfc45266e80aYigit Boyar        setRecyclerView(recyclerView, true);
2040b1059b711009601ff08354e2df6bfc45266e80aYigit Boyar    }
2050b1059b711009601ff08354e2df6bfc45266e80aYigit Boyar    public void setRecyclerView(final RecyclerView recyclerView, boolean assignDummyPool)
2060b1059b711009601ff08354e2df6bfc45266e80aYigit Boyar            throws Throwable {
207e37d2e63f16bc5c2938e372238ba54679a9e578eYigit Boyar        setRecyclerView(recyclerView, true, true);
208e37d2e63f16bc5c2938e372238ba54679a9e578eYigit Boyar    }
209e37d2e63f16bc5c2938e372238ba54679a9e578eYigit Boyar    public void setRecyclerView(final RecyclerView recyclerView, boolean assignDummyPool,
210e37d2e63f16bc5c2938e372238ba54679a9e578eYigit Boyar            boolean addPositionCheckItemAnimator)
211e37d2e63f16bc5c2938e372238ba54679a9e578eYigit Boyar            throws Throwable {
212b5f8b4fecb531aee6b359b4968409410aba90817Yigit Boyar        mRecyclerView = recyclerView;
2130b1059b711009601ff08354e2df6bfc45266e80aYigit Boyar        if (assignDummyPool) {
2140b1059b711009601ff08354e2df6bfc45266e80aYigit Boyar            RecyclerView.RecycledViewPool pool = new RecyclerView.RecycledViewPool() {
2150b1059b711009601ff08354e2df6bfc45266e80aYigit Boyar                @Override
2160b1059b711009601ff08354e2df6bfc45266e80aYigit Boyar                public RecyclerView.ViewHolder getRecycledView(int viewType) {
2170b1059b711009601ff08354e2df6bfc45266e80aYigit Boyar                    RecyclerView.ViewHolder viewHolder = super.getRecycledView(viewType);
2180b1059b711009601ff08354e2df6bfc45266e80aYigit Boyar                    if (viewHolder == null) {
2190b1059b711009601ff08354e2df6bfc45266e80aYigit Boyar                        return null;
2200b1059b711009601ff08354e2df6bfc45266e80aYigit Boyar                    }
2210b1059b711009601ff08354e2df6bfc45266e80aYigit Boyar                    viewHolder.addFlags(RecyclerView.ViewHolder.FLAG_BOUND);
2220b1059b711009601ff08354e2df6bfc45266e80aYigit Boyar                    viewHolder.mPosition = 200;
2230b1059b711009601ff08354e2df6bfc45266e80aYigit Boyar                    viewHolder.mOldPosition = 300;
2240b1059b711009601ff08354e2df6bfc45266e80aYigit Boyar                    viewHolder.mPreLayoutPosition = 500;
2250b1059b711009601ff08354e2df6bfc45266e80aYigit Boyar                    return viewHolder;
2268da5bba529a2fe9bd2aafab28010e11ec4de93c7Yigit Boyar                }
2278da5bba529a2fe9bd2aafab28010e11ec4de93c7Yigit Boyar
2280b1059b711009601ff08354e2df6bfc45266e80aYigit Boyar                @Override
2290b1059b711009601ff08354e2df6bfc45266e80aYigit Boyar                public void putRecycledView(RecyclerView.ViewHolder scrap) {
230619ac016831a5ad75c2e0921c9036b6f030a9e63Yigit Boyar                    assertNull(scrap.mOwnerRecyclerView);
2310b1059b711009601ff08354e2df6bfc45266e80aYigit Boyar                    super.putRecycledView(scrap);
2320b1059b711009601ff08354e2df6bfc45266e80aYigit Boyar                }
2330b1059b711009601ff08354e2df6bfc45266e80aYigit Boyar            };
2340b1059b711009601ff08354e2df6bfc45266e80aYigit Boyar            mRecyclerView.setRecycledViewPool(pool);
2350b1059b711009601ff08354e2df6bfc45266e80aYigit Boyar        }
236e37d2e63f16bc5c2938e372238ba54679a9e578eYigit Boyar        if (addPositionCheckItemAnimator) {
237e37d2e63f16bc5c2938e372238ba54679a9e578eYigit Boyar            mRecyclerView.addItemDecoration(new RecyclerView.ItemDecoration() {
238e37d2e63f16bc5c2938e372238ba54679a9e578eYigit Boyar                @Override
239e37d2e63f16bc5c2938e372238ba54679a9e578eYigit Boyar                public void getItemOffsets(Rect outRect, View view, RecyclerView parent,
240e37d2e63f16bc5c2938e372238ba54679a9e578eYigit Boyar                        RecyclerView.State state) {
241e37d2e63f16bc5c2938e372238ba54679a9e578eYigit Boyar                    RecyclerView.ViewHolder vh = parent.getChildViewHolder(view);
242e37d2e63f16bc5c2938e372238ba54679a9e578eYigit Boyar                    if (!vh.isRemoved()) {
243e37d2e63f16bc5c2938e372238ba54679a9e578eYigit Boyar                        assertNotSame("If getItemOffsets is called, child should have a valid"
244619ac016831a5ad75c2e0921c9036b6f030a9e63Yigit Boyar                                            + " adapter position unless it is removed : " + vh,
245e37d2e63f16bc5c2938e372238ba54679a9e578eYigit Boyar                                    vh.getAdapterPosition(), RecyclerView.NO_POSITION);
246e37d2e63f16bc5c2938e372238ba54679a9e578eYigit Boyar                    }
247e37d2e63f16bc5c2938e372238ba54679a9e578eYigit Boyar                }
248e37d2e63f16bc5c2938e372238ba54679a9e578eYigit Boyar            });
249e37d2e63f16bc5c2938e372238ba54679a9e578eYigit Boyar        }
2508ae76f91527ce850f155ce960fb9068bcd5d49f9Yigit Boyar        mAdapterHelper = recyclerView.mAdapterHelper;
251b5f8b4fecb531aee6b359b4968409410aba90817Yigit Boyar        runTestOnUiThread(new Runnable() {
252b5f8b4fecb531aee6b359b4968409410aba90817Yigit Boyar            @Override
253b5f8b4fecb531aee6b359b4968409410aba90817Yigit Boyar            public void run() {
254b5f8b4fecb531aee6b359b4968409410aba90817Yigit Boyar                getActivity().mContainer.addView(recyclerView);
255b5f8b4fecb531aee6b359b4968409410aba90817Yigit Boyar            }
256b5f8b4fecb531aee6b359b4968409410aba90817Yigit Boyar        });
257b5f8b4fecb531aee6b359b4968409410aba90817Yigit Boyar    }
258b5f8b4fecb531aee6b359b4968409410aba90817Yigit Boyar
259310e95e1c6dfe4f26ef594233e65e1ff83e0f1ffYigit Boyar    protected FrameLayout getRecyclerViewContainer() {
260310e95e1c6dfe4f26ef594233e65e1ff83e0f1ffYigit Boyar        return getActivity().mContainer;
261310e95e1c6dfe4f26ef594233e65e1ff83e0f1ffYigit Boyar    }
262310e95e1c6dfe4f26ef594233e65e1ff83e0f1ffYigit Boyar
263e2437126f0225135891544315ff72e54c7de0f59Yigit Boyar    public void requestLayoutOnUIThread(final View view) {
264e2437126f0225135891544315ff72e54c7de0f59Yigit Boyar        try {
265e2437126f0225135891544315ff72e54c7de0f59Yigit Boyar            runTestOnUiThread(new Runnable() {
266e2437126f0225135891544315ff72e54c7de0f59Yigit Boyar                @Override
267e2437126f0225135891544315ff72e54c7de0f59Yigit Boyar                public void run() {
268e2437126f0225135891544315ff72e54c7de0f59Yigit Boyar                    view.requestLayout();
269e2437126f0225135891544315ff72e54c7de0f59Yigit Boyar                }
270e2437126f0225135891544315ff72e54c7de0f59Yigit Boyar            });
271e2437126f0225135891544315ff72e54c7de0f59Yigit Boyar        } catch (Throwable throwable) {
272e2437126f0225135891544315ff72e54c7de0f59Yigit Boyar            Log.e(TAG, "", throwable);
273e2437126f0225135891544315ff72e54c7de0f59Yigit Boyar        }
274b5f8b4fecb531aee6b359b4968409410aba90817Yigit Boyar    }
275b5f8b4fecb531aee6b359b4968409410aba90817Yigit Boyar
276e2437126f0225135891544315ff72e54c7de0f59Yigit Boyar    public void scrollBy(final int dt) {
277e2437126f0225135891544315ff72e54c7de0f59Yigit Boyar        try {
278e2437126f0225135891544315ff72e54c7de0f59Yigit Boyar            runTestOnUiThread(new Runnable() {
279e2437126f0225135891544315ff72e54c7de0f59Yigit Boyar                @Override
280e2437126f0225135891544315ff72e54c7de0f59Yigit Boyar                public void run() {
281e2437126f0225135891544315ff72e54c7de0f59Yigit Boyar                    if (mRecyclerView.getLayoutManager().canScrollHorizontally()) {
282e2437126f0225135891544315ff72e54c7de0f59Yigit Boyar                        mRecyclerView.scrollBy(dt, 0);
283e2437126f0225135891544315ff72e54c7de0f59Yigit Boyar                    } else {
284e2437126f0225135891544315ff72e54c7de0f59Yigit Boyar                        mRecyclerView.scrollBy(0, dt);
285e2437126f0225135891544315ff72e54c7de0f59Yigit Boyar                    }
286b5f8b4fecb531aee6b359b4968409410aba90817Yigit Boyar
287e2437126f0225135891544315ff72e54c7de0f59Yigit Boyar                }
288e2437126f0225135891544315ff72e54c7de0f59Yigit Boyar            });
289e2437126f0225135891544315ff72e54c7de0f59Yigit Boyar        } catch (Throwable throwable) {
290e2437126f0225135891544315ff72e54c7de0f59Yigit Boyar            Log.e(TAG, "", throwable);
291e2437126f0225135891544315ff72e54c7de0f59Yigit Boyar        }
292d7d27e9ebe5c7325e67e1a8af265378bd2056cadChet Haase    }
293d7d27e9ebe5c7325e67e1a8af265378bd2056cadChet Haase
2948edcb0bdeaba6931f9d8154f0c81f57da7ddab2aYigit Boyar    void scrollToPosition(final int position) throws Throwable {
2958edcb0bdeaba6931f9d8154f0c81f57da7ddab2aYigit Boyar        runTestOnUiThread(new Runnable() {
2968edcb0bdeaba6931f9d8154f0c81f57da7ddab2aYigit Boyar            @Override
2978edcb0bdeaba6931f9d8154f0c81f57da7ddab2aYigit Boyar            public void run() {
2988edcb0bdeaba6931f9d8154f0c81f57da7ddab2aYigit Boyar                mRecyclerView.getLayoutManager().scrollToPosition(position);
2998edcb0bdeaba6931f9d8154f0c81f57da7ddab2aYigit Boyar            }
3008edcb0bdeaba6931f9d8154f0c81f57da7ddab2aYigit Boyar        });
3018edcb0bdeaba6931f9d8154f0c81f57da7ddab2aYigit Boyar    }
3028edcb0bdeaba6931f9d8154f0c81f57da7ddab2aYigit Boyar
3038edcb0bdeaba6931f9d8154f0c81f57da7ddab2aYigit Boyar    void smoothScrollToPosition(final int position)
3048edcb0bdeaba6931f9d8154f0c81f57da7ddab2aYigit Boyar            throws Throwable {
305619ac016831a5ad75c2e0921c9036b6f030a9e63Yigit Boyar        if (mDebug) {
306619ac016831a5ad75c2e0921c9036b6f030a9e63Yigit Boyar            Log.d(TAG, "SMOOTH scrolling to " + position);
307619ac016831a5ad75c2e0921c9036b6f030a9e63Yigit Boyar        }
3088edcb0bdeaba6931f9d8154f0c81f57da7ddab2aYigit Boyar        runTestOnUiThread(new Runnable() {
3098edcb0bdeaba6931f9d8154f0c81f57da7ddab2aYigit Boyar            @Override
3108edcb0bdeaba6931f9d8154f0c81f57da7ddab2aYigit Boyar            public void run() {
3118edcb0bdeaba6931f9d8154f0c81f57da7ddab2aYigit Boyar                mRecyclerView.smoothScrollToPosition(position);
3128edcb0bdeaba6931f9d8154f0c81f57da7ddab2aYigit Boyar            }
3138edcb0bdeaba6931f9d8154f0c81f57da7ddab2aYigit Boyar        });
314de5f54783555fa2e778c4ed6760472a002b2589bYigit Boyar        getInstrumentation().waitForIdleSync();
315de5f54783555fa2e778c4ed6760472a002b2589bYigit Boyar        Thread.sleep(200); //give scroller some time so start
3168edcb0bdeaba6931f9d8154f0c81f57da7ddab2aYigit Boyar        while (mRecyclerView.getLayoutManager().isSmoothScrolling() ||
3178edcb0bdeaba6931f9d8154f0c81f57da7ddab2aYigit Boyar                mRecyclerView.getScrollState() != RecyclerView.SCROLL_STATE_IDLE) {
3188edcb0bdeaba6931f9d8154f0c81f57da7ddab2aYigit Boyar            if (mDebug) {
3198edcb0bdeaba6931f9d8154f0c81f57da7ddab2aYigit Boyar                Log.d(TAG, "SMOOTH scrolling step");
3208edcb0bdeaba6931f9d8154f0c81f57da7ddab2aYigit Boyar            }
3218edcb0bdeaba6931f9d8154f0c81f57da7ddab2aYigit Boyar            Thread.sleep(200);
3228edcb0bdeaba6931f9d8154f0c81f57da7ddab2aYigit Boyar        }
323619ac016831a5ad75c2e0921c9036b6f030a9e63Yigit Boyar        if (mDebug) {
324619ac016831a5ad75c2e0921c9036b6f030a9e63Yigit Boyar            Log.d(TAG, "SMOOTH scrolling done");
325619ac016831a5ad75c2e0921c9036b6f030a9e63Yigit Boyar        }
326504c54ea52c1b2aae6f8f4ae128f1dcaac7e3f6aYigit Boyar        getInstrumentation().waitForIdleSync();
3278edcb0bdeaba6931f9d8154f0c81f57da7ddab2aYigit Boyar    }
3288edcb0bdeaba6931f9d8154f0c81f57da7ddab2aYigit Boyar
329d7d27e9ebe5c7325e67e1a8af265378bd2056cadChet Haase    class TestViewHolder extends RecyclerView.ViewHolder {
330d7d27e9ebe5c7325e67e1a8af265378bd2056cadChet Haase
331115ba0c7b2a14aa4cd0273952195e1d8f6468f87Yigit Boyar        Item mBoundItem;
332d7d27e9ebe5c7325e67e1a8af265378bd2056cadChet Haase
333d7d27e9ebe5c7325e67e1a8af265378bd2056cadChet Haase        public TestViewHolder(View itemView) {
334d7d27e9ebe5c7325e67e1a8af265378bd2056cadChet Haase            super(itemView);
335310e95e1c6dfe4f26ef594233e65e1ff83e0f1ffYigit Boyar            itemView.setFocusable(true);
336d7d27e9ebe5c7325e67e1a8af265378bd2056cadChet Haase        }
337504c54ea52c1b2aae6f8f4ae128f1dcaac7e3f6aYigit Boyar
338504c54ea52c1b2aae6f8f4ae128f1dcaac7e3f6aYigit Boyar        @Override
339504c54ea52c1b2aae6f8f4ae128f1dcaac7e3f6aYigit Boyar        public String toString() {
340115ba0c7b2a14aa4cd0273952195e1d8f6468f87Yigit Boyar            return super.toString() + " item:" + mBoundItem;
341504c54ea52c1b2aae6f8f4ae128f1dcaac7e3f6aYigit Boyar        }
342d7d27e9ebe5c7325e67e1a8af265378bd2056cadChet Haase    }
343da50fde358bd3ef2edb273b3bf0265cf5e84ec6bYigit Boyar    class DumbLayoutManager extends TestLayoutManager {
344da50fde358bd3ef2edb273b3bf0265cf5e84ec6bYigit Boyar        ReentrantLock mLayoutLock = new ReentrantLock();
345da50fde358bd3ef2edb273b3bf0265cf5e84ec6bYigit Boyar        public void blockLayout() {
346da50fde358bd3ef2edb273b3bf0265cf5e84ec6bYigit Boyar            mLayoutLock.lock();
347da50fde358bd3ef2edb273b3bf0265cf5e84ec6bYigit Boyar        }
348d7d27e9ebe5c7325e67e1a8af265378bd2056cadChet Haase
349da50fde358bd3ef2edb273b3bf0265cf5e84ec6bYigit Boyar        public void unblockLayout() {
350da50fde358bd3ef2edb273b3bf0265cf5e84ec6bYigit Boyar            mLayoutLock.unlock();
351da50fde358bd3ef2edb273b3bf0265cf5e84ec6bYigit Boyar        }
352da50fde358bd3ef2edb273b3bf0265cf5e84ec6bYigit Boyar        @Override
353da50fde358bd3ef2edb273b3bf0265cf5e84ec6bYigit Boyar        public void onLayoutChildren(RecyclerView.Recycler recycler, RecyclerView.State state) {
354da50fde358bd3ef2edb273b3bf0265cf5e84ec6bYigit Boyar            mLayoutLock.lock();
355da50fde358bd3ef2edb273b3bf0265cf5e84ec6bYigit Boyar            detachAndScrapAttachedViews(recycler);
356da50fde358bd3ef2edb273b3bf0265cf5e84ec6bYigit Boyar            layoutRange(recycler, 0, state.getItemCount());
357da50fde358bd3ef2edb273b3bf0265cf5e84ec6bYigit Boyar            if (layoutLatch != null) {
358da50fde358bd3ef2edb273b3bf0265cf5e84ec6bYigit Boyar                layoutLatch.countDown();
359da50fde358bd3ef2edb273b3bf0265cf5e84ec6bYigit Boyar            }
360da50fde358bd3ef2edb273b3bf0265cf5e84ec6bYigit Boyar            mLayoutLock.unlock();
361da50fde358bd3ef2edb273b3bf0265cf5e84ec6bYigit Boyar        }
362da50fde358bd3ef2edb273b3bf0265cf5e84ec6bYigit Boyar    }
363d7d27e9ebe5c7325e67e1a8af265378bd2056cadChet Haase    class TestLayoutManager extends RecyclerView.LayoutManager {
364d7d27e9ebe5c7325e67e1a8af265378bd2056cadChet Haase
365d7d27e9ebe5c7325e67e1a8af265378bd2056cadChet Haase        CountDownLatch layoutLatch;
366d7d27e9ebe5c7325e67e1a8af265378bd2056cadChet Haase
367d7d27e9ebe5c7325e67e1a8af265378bd2056cadChet Haase        public void expectLayouts(int count) {
368d7d27e9ebe5c7325e67e1a8af265378bd2056cadChet Haase            layoutLatch = new CountDownLatch(count);
369d7d27e9ebe5c7325e67e1a8af265378bd2056cadChet Haase        }
370d7d27e9ebe5c7325e67e1a8af265378bd2056cadChet Haase
371b5f8b4fecb531aee6b359b4968409410aba90817Yigit Boyar        public void waitForLayout(long timeout, TimeUnit timeUnit) throws Throwable {
372b5f8b4fecb531aee6b359b4968409410aba90817Yigit Boyar            layoutLatch.await(timeout * (mDebug ? 100 : 1), timeUnit);
373d7d27e9ebe5c7325e67e1a8af265378bd2056cadChet Haase            assertEquals("all expected layouts should be executed at the expected time",
374d7d27e9ebe5c7325e67e1a8af265378bd2056cadChet Haase                    0, layoutLatch.getCount());
375e0c347f627f8a78d3e5e3e5eaac9c3ae26208689Yigit Boyar            getInstrumentation().waitForIdleSync();
376d7d27e9ebe5c7325e67e1a8af265378bd2056cadChet Haase        }
377d7d27e9ebe5c7325e67e1a8af265378bd2056cadChet Haase
378b5f8b4fecb531aee6b359b4968409410aba90817Yigit Boyar        public void assertLayoutCount(int count, String msg, long timeout) throws Throwable {
379b5f8b4fecb531aee6b359b4968409410aba90817Yigit Boyar            layoutLatch.await(timeout, TimeUnit.SECONDS);
380b5f8b4fecb531aee6b359b4968409410aba90817Yigit Boyar            assertEquals(msg, count, layoutLatch.getCount());
381b5f8b4fecb531aee6b359b4968409410aba90817Yigit Boyar        }
382b5f8b4fecb531aee6b359b4968409410aba90817Yigit Boyar
383b5f8b4fecb531aee6b359b4968409410aba90817Yigit Boyar        public void assertNoLayout(String msg, long timeout) throws Throwable {
384b5f8b4fecb531aee6b359b4968409410aba90817Yigit Boyar            layoutLatch.await(timeout, TimeUnit.SECONDS);
385b5f8b4fecb531aee6b359b4968409410aba90817Yigit Boyar            assertFalse(msg, layoutLatch.getCount() == 0);
386b5f8b4fecb531aee6b359b4968409410aba90817Yigit Boyar        }
387b5f8b4fecb531aee6b359b4968409410aba90817Yigit Boyar
388b5f8b4fecb531aee6b359b4968409410aba90817Yigit Boyar        public void waitForLayout(long timeout) throws Throwable {
3898ae76f91527ce850f155ce960fb9068bcd5d49f9Yigit Boyar            waitForLayout(timeout * (mDebug ? 10000 : 1), TimeUnit.SECONDS);
390b5f8b4fecb531aee6b359b4968409410aba90817Yigit Boyar        }
391b5f8b4fecb531aee6b359b4968409410aba90817Yigit Boyar
392d7d27e9ebe5c7325e67e1a8af265378bd2056cadChet Haase        @Override
393d7d27e9ebe5c7325e67e1a8af265378bd2056cadChet Haase        public RecyclerView.LayoutParams generateDefaultLayoutParams() {
394d7d27e9ebe5c7325e67e1a8af265378bd2056cadChet Haase            return new RecyclerView.LayoutParams(ViewGroup.LayoutParams.WRAP_CONTENT,
395d7d27e9ebe5c7325e67e1a8af265378bd2056cadChet Haase                    ViewGroup.LayoutParams.WRAP_CONTENT);
396d7d27e9ebe5c7325e67e1a8af265378bd2056cadChet Haase        }
397d7d27e9ebe5c7325e67e1a8af265378bd2056cadChet Haase
398d7d27e9ebe5c7325e67e1a8af265378bd2056cadChet Haase        void assertVisibleItemPositions() {
399d7d27e9ebe5c7325e67e1a8af265378bd2056cadChet Haase            int i = getChildCount();
400d7d27e9ebe5c7325e67e1a8af265378bd2056cadChet Haase            TestAdapter testAdapter = (TestAdapter) mRecyclerView.getAdapter();
401d7d27e9ebe5c7325e67e1a8af265378bd2056cadChet Haase            while (i-- > 0) {
402d7d27e9ebe5c7325e67e1a8af265378bd2056cadChet Haase                View view = getChildAt(i);
403d7d27e9ebe5c7325e67e1a8af265378bd2056cadChet Haase                RecyclerView.LayoutParams lp = (RecyclerView.LayoutParams) view.getLayoutParams();
404115ba0c7b2a14aa4cd0273952195e1d8f6468f87Yigit Boyar                Item item = ((TestViewHolder) lp.mViewHolder).mBoundItem;
405b5f8b4fecb531aee6b359b4968409410aba90817Yigit Boyar                if (mDebug) {
406d7d27e9ebe5c7325e67e1a8af265378bd2056cadChet Haase                    Log.d(TAG, "testing item " + i);
407d7d27e9ebe5c7325e67e1a8af265378bd2056cadChet Haase                }
4088ae76f91527ce850f155ce960fb9068bcd5d49f9Yigit Boyar                if (!lp.isItemRemoved()) {
4098ae76f91527ce850f155ce960fb9068bcd5d49f9Yigit Boyar                    RecyclerView.ViewHolder vh = mRecyclerView.getChildViewHolder(view);
4108ae76f91527ce850f155ce960fb9068bcd5d49f9Yigit Boyar                    assertSame("item position in LP should match adapter value :" + vh,
4118ae76f91527ce850f155ce960fb9068bcd5d49f9Yigit Boyar                            testAdapter.mItems.get(vh.mPosition), item);
4128ae76f91527ce850f155ce960fb9068bcd5d49f9Yigit Boyar                }
413d7d27e9ebe5c7325e67e1a8af265378bd2056cadChet Haase            }
414d7d27e9ebe5c7325e67e1a8af265378bd2056cadChet Haase        }
415d7d27e9ebe5c7325e67e1a8af265378bd2056cadChet Haase
416b5f8b4fecb531aee6b359b4968409410aba90817Yigit Boyar        RecyclerView.LayoutParams getLp(View v) {
417b5f8b4fecb531aee6b359b4968409410aba90817Yigit Boyar            return (RecyclerView.LayoutParams) v.getLayoutParams();
418b5f8b4fecb531aee6b359b4968409410aba90817Yigit Boyar        }
419b5f8b4fecb531aee6b359b4968409410aba90817Yigit Boyar
420f485be9def4c0f72cfdfa6b0c616c23f04652817Yigit Boyar        void layoutRange(RecyclerView.Recycler recycler, int start, int end) {
421fe3996cf5af65fe11cc3ed47b67123ec267bd336Yigit Boyar            assertScrap(recycler);
422b5f8b4fecb531aee6b359b4968409410aba90817Yigit Boyar            if (mDebug) {
423d7d27e9ebe5c7325e67e1a8af265378bd2056cadChet Haase                Log.d(TAG, "will layout items from " + start + " to " + end);
424d7d27e9ebe5c7325e67e1a8af265378bd2056cadChet Haase            }
4255ced882cabdcefbb469e332f6f73983999aad0e5Yigit Boyar            int diff = end > start ? 1 : -1;
426504c54ea52c1b2aae6f8f4ae128f1dcaac7e3f6aYigit Boyar            int top = 0;
4275ced882cabdcefbb469e332f6f73983999aad0e5Yigit Boyar            for (int i = start; i != end; i+=diff) {
428b5f8b4fecb531aee6b359b4968409410aba90817Yigit Boyar                if (mDebug) {
429d7d27e9ebe5c7325e67e1a8af265378bd2056cadChet Haase                    Log.d(TAG, "laying out item " + i);
430d7d27e9ebe5c7325e67e1a8af265378bd2056cadChet Haase                }
431d7d27e9ebe5c7325e67e1a8af265378bd2056cadChet Haase                View view = recycler.getViewForPosition(i);
432b5f8b4fecb531aee6b359b4968409410aba90817Yigit Boyar                assertNotNull("view should not be null for valid position. "
433b5f8b4fecb531aee6b359b4968409410aba90817Yigit Boyar                        + "got null view at position " + i, view);
434c8b0ff5869e17a656a3a1d7fb306efa5b99fa07fYigit Boyar                if (!mRecyclerView.mState.isPreLayout()) {
435c8b0ff5869e17a656a3a1d7fb306efa5b99fa07fYigit Boyar                    RecyclerView.LayoutParams layoutParams = (RecyclerView.LayoutParams) view
436c8b0ff5869e17a656a3a1d7fb306efa5b99fa07fYigit Boyar                            .getLayoutParams();
437c8b0ff5869e17a656a3a1d7fb306efa5b99fa07fYigit Boyar                    assertFalse("In post layout, getViewForPosition should never return a view "
438c8b0ff5869e17a656a3a1d7fb306efa5b99fa07fYigit Boyar                            + "that is removed", layoutParams != null
439c8b0ff5869e17a656a3a1d7fb306efa5b99fa07fYigit Boyar                            && layoutParams.isItemRemoved());
440c8b0ff5869e17a656a3a1d7fb306efa5b99fa07fYigit Boyar
441b5f8b4fecb531aee6b359b4968409410aba90817Yigit Boyar                }
4428da5bba529a2fe9bd2aafab28010e11ec4de93c7Yigit Boyar                assertEquals("getViewForPosition should return correct position",
4438da5bba529a2fe9bd2aafab28010e11ec4de93c7Yigit Boyar                        i, getPosition(view));
444c8b0ff5869e17a656a3a1d7fb306efa5b99fa07fYigit Boyar                addView(view);
445b5f8b4fecb531aee6b359b4968409410aba90817Yigit Boyar
446d7d27e9ebe5c7325e67e1a8af265378bd2056cadChet Haase                measureChildWithMargins(view, 0, 0);
447504c54ea52c1b2aae6f8f4ae128f1dcaac7e3f6aYigit Boyar                layoutDecorated(view, 0, top, getDecoratedMeasuredWidth(view)
448504c54ea52c1b2aae6f8f4ae128f1dcaac7e3f6aYigit Boyar                        , top + getDecoratedMeasuredHeight(view));
449504c54ea52c1b2aae6f8f4ae128f1dcaac7e3f6aYigit Boyar                top += view.getMeasuredHeight();
450d7d27e9ebe5c7325e67e1a8af265378bd2056cadChet Haase            }
451d7d27e9ebe5c7325e67e1a8af265378bd2056cadChet Haase        }
452fe3996cf5af65fe11cc3ed47b67123ec267bd336Yigit Boyar
453fe3996cf5af65fe11cc3ed47b67123ec267bd336Yigit Boyar        private void assertScrap(RecyclerView.Recycler recycler) {
454e0c347f627f8a78d3e5e3e5eaac9c3ae26208689Yigit Boyar            if (mRecyclerView.getAdapter() != null &&
455e0c347f627f8a78d3e5e3e5eaac9c3ae26208689Yigit Boyar                    !mRecyclerView.getAdapter().hasStableIds()) {
456e0c347f627f8a78d3e5e3e5eaac9c3ae26208689Yigit Boyar                for (RecyclerView.ViewHolder viewHolder : recycler.getScrapList()) {
457e0c347f627f8a78d3e5e3e5eaac9c3ae26208689Yigit Boyar                    assertFalse("Invalid scrap should be no kept", viewHolder.isInvalid());
458e0c347f627f8a78d3e5e3e5eaac9c3ae26208689Yigit Boyar                }
459fe3996cf5af65fe11cc3ed47b67123ec267bd336Yigit Boyar            }
460fe3996cf5af65fe11cc3ed47b67123ec267bd336Yigit Boyar        }
461bb6ebab3b3b85ec20b3bc262289a2460705322f5Yigit Boyar
462bb6ebab3b3b85ec20b3bc262289a2460705322f5Yigit Boyar        @Override
463bb6ebab3b3b85ec20b3bc262289a2460705322f5Yigit Boyar        public boolean canScrollHorizontally() {
464bb6ebab3b3b85ec20b3bc262289a2460705322f5Yigit Boyar            return true;
465bb6ebab3b3b85ec20b3bc262289a2460705322f5Yigit Boyar        }
466bb6ebab3b3b85ec20b3bc262289a2460705322f5Yigit Boyar
467bb6ebab3b3b85ec20b3bc262289a2460705322f5Yigit Boyar        @Override
468bb6ebab3b3b85ec20b3bc262289a2460705322f5Yigit Boyar        public boolean canScrollVertically() {
469bb6ebab3b3b85ec20b3bc262289a2460705322f5Yigit Boyar            return true;
470bb6ebab3b3b85ec20b3bc262289a2460705322f5Yigit Boyar        }
471bb6ebab3b3b85ec20b3bc262289a2460705322f5Yigit Boyar
472bb6ebab3b3b85ec20b3bc262289a2460705322f5Yigit Boyar        @Override
473bb6ebab3b3b85ec20b3bc262289a2460705322f5Yigit Boyar        public int scrollHorizontallyBy(int dx, RecyclerView.Recycler recycler,
474bb6ebab3b3b85ec20b3bc262289a2460705322f5Yigit Boyar                RecyclerView.State state) {
475bb6ebab3b3b85ec20b3bc262289a2460705322f5Yigit Boyar            return dx;
476bb6ebab3b3b85ec20b3bc262289a2460705322f5Yigit Boyar        }
477bb6ebab3b3b85ec20b3bc262289a2460705322f5Yigit Boyar
478bb6ebab3b3b85ec20b3bc262289a2460705322f5Yigit Boyar        @Override
479bb6ebab3b3b85ec20b3bc262289a2460705322f5Yigit Boyar        public int scrollVerticallyBy(int dy, RecyclerView.Recycler recycler,
480bb6ebab3b3b85ec20b3bc262289a2460705322f5Yigit Boyar                RecyclerView.State state) {
481bb6ebab3b3b85ec20b3bc262289a2460705322f5Yigit Boyar            return dy;
482bb6ebab3b3b85ec20b3bc262289a2460705322f5Yigit Boyar        }
483d7d27e9ebe5c7325e67e1a8af265378bd2056cadChet Haase    }
484d7d27e9ebe5c7325e67e1a8af265378bd2056cadChet Haase
485d7d27e9ebe5c7325e67e1a8af265378bd2056cadChet Haase    static class Item {
48611b7cfea15306c0d6e4ed7da09f7698df01695dfYigit Boyar        final static AtomicInteger idCounter = new AtomicInteger(0);
48711b7cfea15306c0d6e4ed7da09f7698df01695dfYigit Boyar        final public int mId = idCounter.incrementAndGet();
488d7d27e9ebe5c7325e67e1a8af265378bd2056cadChet Haase
4892d2e8d88103866b631eb0f3805da137ebcfb0275Yigit Boyar        int mAdapterIndex;
490d7d27e9ebe5c7325e67e1a8af265378bd2056cadChet Haase
4912d2e8d88103866b631eb0f3805da137ebcfb0275Yigit Boyar        final String mText;
492d7d27e9ebe5c7325e67e1a8af265378bd2056cadChet Haase
4932d2e8d88103866b631eb0f3805da137ebcfb0275Yigit Boyar        Item(int adapterIndex, String text) {
4942d2e8d88103866b631eb0f3805da137ebcfb0275Yigit Boyar            mAdapterIndex = adapterIndex;
4952d2e8d88103866b631eb0f3805da137ebcfb0275Yigit Boyar            mText = text;
496d7d27e9ebe5c7325e67e1a8af265378bd2056cadChet Haase        }
4978ae76f91527ce850f155ce960fb9068bcd5d49f9Yigit Boyar
4988ae76f91527ce850f155ce960fb9068bcd5d49f9Yigit Boyar        @Override
4998ae76f91527ce850f155ce960fb9068bcd5d49f9Yigit Boyar        public String toString() {
5008ae76f91527ce850f155ce960fb9068bcd5d49f9Yigit Boyar            return "Item{" +
5018ae76f91527ce850f155ce960fb9068bcd5d49f9Yigit Boyar                    "mId=" + mId +
5022d2e8d88103866b631eb0f3805da137ebcfb0275Yigit Boyar                    ", originalIndex=" + mAdapterIndex +
5032d2e8d88103866b631eb0f3805da137ebcfb0275Yigit Boyar                    ", text='" + mText + '\'' +
5048ae76f91527ce850f155ce960fb9068bcd5d49f9Yigit Boyar                    '}';
5058ae76f91527ce850f155ce960fb9068bcd5d49f9Yigit Boyar        }
506d7d27e9ebe5c7325e67e1a8af265378bd2056cadChet Haase    }
507d7d27e9ebe5c7325e67e1a8af265378bd2056cadChet Haase
50890ca3086dbf66ad6bb8840e46ec8524a705e1c18Yigit Boyar    class TestAdapter extends RecyclerView.Adapter<TestViewHolder>
50990ca3086dbf66ad6bb8840e46ec8524a705e1c18Yigit Boyar            implements AttachDetachCountingAdapter {
510d7d27e9ebe5c7325e67e1a8af265378bd2056cadChet Haase
51190ca3086dbf66ad6bb8840e46ec8524a705e1c18Yigit Boyar        ViewAttachDetachCounter mAttachmentCounter = new ViewAttachDetachCounter();
512d7d27e9ebe5c7325e67e1a8af265378bd2056cadChet Haase        List<Item> mItems;
513d7d27e9ebe5c7325e67e1a8af265378bd2056cadChet Haase
514d7d27e9ebe5c7325e67e1a8af265378bd2056cadChet Haase        TestAdapter(int count) {
515d7d27e9ebe5c7325e67e1a8af265378bd2056cadChet Haase            mItems = new ArrayList<Item>(count);
516d7d27e9ebe5c7325e67e1a8af265378bd2056cadChet Haase            for (int i = 0; i < count; i++) {
517d7d27e9ebe5c7325e67e1a8af265378bd2056cadChet Haase                mItems.add(new Item(i, "Item " + i));
518d7d27e9ebe5c7325e67e1a8af265378bd2056cadChet Haase            }
519d7d27e9ebe5c7325e67e1a8af265378bd2056cadChet Haase        }
520d7d27e9ebe5c7325e67e1a8af265378bd2056cadChet Haase
521d7d27e9ebe5c7325e67e1a8af265378bd2056cadChet Haase        @Override
52290ca3086dbf66ad6bb8840e46ec8524a705e1c18Yigit Boyar        public void onViewAttachedToWindow(TestViewHolder holder) {
52390ca3086dbf66ad6bb8840e46ec8524a705e1c18Yigit Boyar            super.onViewAttachedToWindow(holder);
52490ca3086dbf66ad6bb8840e46ec8524a705e1c18Yigit Boyar            mAttachmentCounter.onViewAttached(holder);
52590ca3086dbf66ad6bb8840e46ec8524a705e1c18Yigit Boyar        }
52690ca3086dbf66ad6bb8840e46ec8524a705e1c18Yigit Boyar
52790ca3086dbf66ad6bb8840e46ec8524a705e1c18Yigit Boyar        @Override
52890ca3086dbf66ad6bb8840e46ec8524a705e1c18Yigit Boyar        public void onViewDetachedFromWindow(TestViewHolder holder) {
52990ca3086dbf66ad6bb8840e46ec8524a705e1c18Yigit Boyar            super.onViewDetachedFromWindow(holder);
53090ca3086dbf66ad6bb8840e46ec8524a705e1c18Yigit Boyar            mAttachmentCounter.onViewDetached(holder);
53190ca3086dbf66ad6bb8840e46ec8524a705e1c18Yigit Boyar        }
53290ca3086dbf66ad6bb8840e46ec8524a705e1c18Yigit Boyar
53390ca3086dbf66ad6bb8840e46ec8524a705e1c18Yigit Boyar        @Override
53490ca3086dbf66ad6bb8840e46ec8524a705e1c18Yigit Boyar        public void onAttachedToRecyclerView(RecyclerView recyclerView) {
53590ca3086dbf66ad6bb8840e46ec8524a705e1c18Yigit Boyar            super.onAttachedToRecyclerView(recyclerView);
53690ca3086dbf66ad6bb8840e46ec8524a705e1c18Yigit Boyar            mAttachmentCounter.onAttached(recyclerView);
53790ca3086dbf66ad6bb8840e46ec8524a705e1c18Yigit Boyar        }
53890ca3086dbf66ad6bb8840e46ec8524a705e1c18Yigit Boyar
53990ca3086dbf66ad6bb8840e46ec8524a705e1c18Yigit Boyar        @Override
54090ca3086dbf66ad6bb8840e46ec8524a705e1c18Yigit Boyar        public void onDetachedFromRecyclerView(RecyclerView recyclerView) {
54190ca3086dbf66ad6bb8840e46ec8524a705e1c18Yigit Boyar            super.onDetachedFromRecyclerView(recyclerView);
54290ca3086dbf66ad6bb8840e46ec8524a705e1c18Yigit Boyar            mAttachmentCounter.onDetached(recyclerView);
54390ca3086dbf66ad6bb8840e46ec8524a705e1c18Yigit Boyar        }
54490ca3086dbf66ad6bb8840e46ec8524a705e1c18Yigit Boyar
54590ca3086dbf66ad6bb8840e46ec8524a705e1c18Yigit Boyar        @Override
546d7d27e9ebe5c7325e67e1a8af265378bd2056cadChet Haase        public TestViewHolder onCreateViewHolder(ViewGroup parent,
547d7d27e9ebe5c7325e67e1a8af265378bd2056cadChet Haase                int viewType) {
548d7d27e9ebe5c7325e67e1a8af265378bd2056cadChet Haase            return new TestViewHolder(new TextView(parent.getContext()));
549d7d27e9ebe5c7325e67e1a8af265378bd2056cadChet Haase        }
550d7d27e9ebe5c7325e67e1a8af265378bd2056cadChet Haase
551d7d27e9ebe5c7325e67e1a8af265378bd2056cadChet Haase        @Override
552d7d27e9ebe5c7325e67e1a8af265378bd2056cadChet Haase        public void onBindViewHolder(TestViewHolder holder, int position) {
553619ac016831a5ad75c2e0921c9036b6f030a9e63Yigit Boyar            assertNotNull(holder.mOwnerRecyclerView);
554619ac016831a5ad75c2e0921c9036b6f030a9e63Yigit Boyar            assertEquals(position, holder.getAdapterPosition());
555d7d27e9ebe5c7325e67e1a8af265378bd2056cadChet Haase            final Item item = mItems.get(position);
5562d2e8d88103866b631eb0f3805da137ebcfb0275Yigit Boyar            ((TextView) (holder.itemView)).setText(item.mText + "(" + item.mAdapterIndex + ")");
557115ba0c7b2a14aa4cd0273952195e1d8f6468f87Yigit Boyar            holder.mBoundItem = item;
558d7d27e9ebe5c7325e67e1a8af265378bd2056cadChet Haase        }
559d7d27e9ebe5c7325e67e1a8af265378bd2056cadChet Haase
560619ac016831a5ad75c2e0921c9036b6f030a9e63Yigit Boyar        @Override
561619ac016831a5ad75c2e0921c9036b6f030a9e63Yigit Boyar        public void onViewRecycled(TestViewHolder holder) {
562619ac016831a5ad75c2e0921c9036b6f030a9e63Yigit Boyar            super.onViewRecycled(holder);
563619ac016831a5ad75c2e0921c9036b6f030a9e63Yigit Boyar            final int adapterPosition = holder.getAdapterPosition();
564619ac016831a5ad75c2e0921c9036b6f030a9e63Yigit Boyar            final boolean shouldHavePosition = !holder.isRemoved() && holder.isBound() &&
565619ac016831a5ad75c2e0921c9036b6f030a9e63Yigit Boyar                    !holder.isAdapterPositionUnknown() && !holder.isInvalid();
566619ac016831a5ad75c2e0921c9036b6f030a9e63Yigit Boyar            String log = "Position check for " + holder.toString();
567619ac016831a5ad75c2e0921c9036b6f030a9e63Yigit Boyar            assertEquals(log, shouldHavePosition, adapterPosition != RecyclerView.NO_POSITION);
568619ac016831a5ad75c2e0921c9036b6f030a9e63Yigit Boyar            if (shouldHavePosition) {
569619ac016831a5ad75c2e0921c9036b6f030a9e63Yigit Boyar                assertTrue(log, mItems.size() > adapterPosition);
570619ac016831a5ad75c2e0921c9036b6f030a9e63Yigit Boyar                assertSame(log, holder.mBoundItem, mItems.get(adapterPosition));
571619ac016831a5ad75c2e0921c9036b6f030a9e63Yigit Boyar            }
572619ac016831a5ad75c2e0921c9036b6f030a9e63Yigit Boyar        }
573619ac016831a5ad75c2e0921c9036b6f030a9e63Yigit Boyar
574b5f8b4fecb531aee6b359b4968409410aba90817Yigit Boyar        public void deleteAndNotify(final int start, final int count) throws Throwable {
575b5f8b4fecb531aee6b359b4968409410aba90817Yigit Boyar            deleteAndNotify(new int[]{start, count});
576b5f8b4fecb531aee6b359b4968409410aba90817Yigit Boyar        }
577b5f8b4fecb531aee6b359b4968409410aba90817Yigit Boyar
578b5f8b4fecb531aee6b359b4968409410aba90817Yigit Boyar        /**
579b5f8b4fecb531aee6b359b4968409410aba90817Yigit Boyar         * Deletes items in the given ranges.
580b5f8b4fecb531aee6b359b4968409410aba90817Yigit Boyar         * <p>
581b5f8b4fecb531aee6b359b4968409410aba90817Yigit Boyar         * Note that each operation affects the one after so you should offset them properly.
582b5f8b4fecb531aee6b359b4968409410aba90817Yigit Boyar         * <p>
583b5f8b4fecb531aee6b359b4968409410aba90817Yigit Boyar         * For example, if adapter has 5 items (A,B,C,D,E), and then you call this method with
584b5f8b4fecb531aee6b359b4968409410aba90817Yigit Boyar         * <code>[1, 2],[2, 1]</code>, it will first delete items B,C and the new adapter will be
585b5f8b4fecb531aee6b359b4968409410aba90817Yigit Boyar         * A D E. Then it will delete 2,1 which means it will delete E.
586b5f8b4fecb531aee6b359b4968409410aba90817Yigit Boyar         */
587b5f8b4fecb531aee6b359b4968409410aba90817Yigit Boyar        public void deleteAndNotify(final int[]... startCountTuples) throws Throwable {
5888ae76f91527ce850f155ce960fb9068bcd5d49f9Yigit Boyar            for (int[] tuple : startCountTuples) {
5898ae76f91527ce850f155ce960fb9068bcd5d49f9Yigit Boyar                tuple[1] = -tuple[1];
5908ae76f91527ce850f155ce960fb9068bcd5d49f9Yigit Boyar            }
5918ae76f91527ce850f155ce960fb9068bcd5d49f9Yigit Boyar            new AddRemoveRunnable(startCountTuples).runOnMainThread();
592d7d27e9ebe5c7325e67e1a8af265378bd2056cadChet Haase        }
593d7d27e9ebe5c7325e67e1a8af265378bd2056cadChet Haase
594c8b0ff5869e17a656a3a1d7fb306efa5b99fa07fYigit Boyar        @Override
595c8b0ff5869e17a656a3a1d7fb306efa5b99fa07fYigit Boyar        public long getItemId(int position) {
596c8b0ff5869e17a656a3a1d7fb306efa5b99fa07fYigit Boyar            return hasStableIds() ? mItems.get(position).mId : super.getItemId(position);
597c8b0ff5869e17a656a3a1d7fb306efa5b99fa07fYigit Boyar        }
598c8b0ff5869e17a656a3a1d7fb306efa5b99fa07fYigit Boyar
5992d2e8d88103866b631eb0f3805da137ebcfb0275Yigit Boyar        public void offsetOriginalIndices(int start, int offset) {
6002d2e8d88103866b631eb0f3805da137ebcfb0275Yigit Boyar            for (int i = start; i < mItems.size(); i++) {
6012d2e8d88103866b631eb0f3805da137ebcfb0275Yigit Boyar                mItems.get(i).mAdapterIndex += offset;
6022d2e8d88103866b631eb0f3805da137ebcfb0275Yigit Boyar            }
6032d2e8d88103866b631eb0f3805da137ebcfb0275Yigit Boyar        }
6042d2e8d88103866b631eb0f3805da137ebcfb0275Yigit Boyar
605e0c347f627f8a78d3e5e3e5eaac9c3ae26208689Yigit Boyar        /**
606e0c347f627f8a78d3e5e3e5eaac9c3ae26208689Yigit Boyar         * @param start inclusive
607e0c347f627f8a78d3e5e3e5eaac9c3ae26208689Yigit Boyar         * @param end exclusive
608e0c347f627f8a78d3e5e3e5eaac9c3ae26208689Yigit Boyar         * @param offset
609e0c347f627f8a78d3e5e3e5eaac9c3ae26208689Yigit Boyar         */
610e0c347f627f8a78d3e5e3e5eaac9c3ae26208689Yigit Boyar        public void offsetOriginalIndicesBetween(int start, int end, int offset) {
611e0c347f627f8a78d3e5e3e5eaac9c3ae26208689Yigit Boyar            for (int i = start; i < end && i < mItems.size(); i++) {
612e0c347f627f8a78d3e5e3e5eaac9c3ae26208689Yigit Boyar                mItems.get(i).mAdapterIndex += offset;
613e0c347f627f8a78d3e5e3e5eaac9c3ae26208689Yigit Boyar            }
614e0c347f627f8a78d3e5e3e5eaac9c3ae26208689Yigit Boyar        }
615e0c347f627f8a78d3e5e3e5eaac9c3ae26208689Yigit Boyar
616b5f8b4fecb531aee6b359b4968409410aba90817Yigit Boyar        public void addAndNotify(final int start, final int count) throws Throwable {
617b5f8b4fecb531aee6b359b4968409410aba90817Yigit Boyar            addAndNotify(new int[]{start, count});
618b5f8b4fecb531aee6b359b4968409410aba90817Yigit Boyar        }
619b5f8b4fecb531aee6b359b4968409410aba90817Yigit Boyar
620b5f8b4fecb531aee6b359b4968409410aba90817Yigit Boyar        public void addAndNotify(final int[]... startCountTuples) throws Throwable {
6218ae76f91527ce850f155ce960fb9068bcd5d49f9Yigit Boyar            new AddRemoveRunnable(startCountTuples).runOnMainThread();
6228ae76f91527ce850f155ce960fb9068bcd5d49f9Yigit Boyar        }
623b5f8b4fecb531aee6b359b4968409410aba90817Yigit Boyar
624cadd54707a7aaa4cfa9c8b8051c6185e8717f22eChet Haase        public void dispatchDataSetChanged() throws Throwable {
62522b6b8163d2e15eb6d0a89906854dec801945f1dYigit Boyar            runTestOnUiThread(new Runnable() {
62622b6b8163d2e15eb6d0a89906854dec801945f1dYigit Boyar                @Override
62722b6b8163d2e15eb6d0a89906854dec801945f1dYigit Boyar                public void run() {
62822b6b8163d2e15eb6d0a89906854dec801945f1dYigit Boyar                    notifyDataSetChanged();
62922b6b8163d2e15eb6d0a89906854dec801945f1dYigit Boyar                }
63022b6b8163d2e15eb6d0a89906854dec801945f1dYigit Boyar            });
63122b6b8163d2e15eb6d0a89906854dec801945f1dYigit Boyar        }
63222b6b8163d2e15eb6d0a89906854dec801945f1dYigit Boyar
633cadd54707a7aaa4cfa9c8b8051c6185e8717f22eChet Haase        public void changeAndNotify(final int start, final int count) throws Throwable {
6347e9da1a2629b657d56e2f54abd18c6249f3be037Yigit Boyar            runTestOnUiThread(new Runnable() {
6357e9da1a2629b657d56e2f54abd18c6249f3be037Yigit Boyar                @Override
6367e9da1a2629b657d56e2f54abd18c6249f3be037Yigit Boyar                public void run() {
6377e9da1a2629b657d56e2f54abd18c6249f3be037Yigit Boyar                    notifyItemRangeChanged(start, count);
6387e9da1a2629b657d56e2f54abd18c6249f3be037Yigit Boyar                }
6397e9da1a2629b657d56e2f54abd18c6249f3be037Yigit Boyar            });
6407e9da1a2629b657d56e2f54abd18c6249f3be037Yigit Boyar        }
6417e9da1a2629b657d56e2f54abd18c6249f3be037Yigit Boyar
642cadd54707a7aaa4cfa9c8b8051c6185e8717f22eChet Haase        public void changePositionsAndNotify(final int... positions) throws Throwable {
643cadd54707a7aaa4cfa9c8b8051c6185e8717f22eChet Haase            runTestOnUiThread(new Runnable() {
644cadd54707a7aaa4cfa9c8b8051c6185e8717f22eChet Haase                @Override
645cadd54707a7aaa4cfa9c8b8051c6185e8717f22eChet Haase                public void run() {
646cadd54707a7aaa4cfa9c8b8051c6185e8717f22eChet Haase                    for (int i = 0; i < positions.length; i += 1) {
647cadd54707a7aaa4cfa9c8b8051c6185e8717f22eChet Haase                        TestAdapter.super.notifyItemRangeChanged(positions[i], 1);
648cadd54707a7aaa4cfa9c8b8051c6185e8717f22eChet Haase                    }
649cadd54707a7aaa4cfa9c8b8051c6185e8717f22eChet Haase                }
650cadd54707a7aaa4cfa9c8b8051c6185e8717f22eChet Haase            });
651cadd54707a7aaa4cfa9c8b8051c6185e8717f22eChet Haase        }
652cadd54707a7aaa4cfa9c8b8051c6185e8717f22eChet Haase
6538ae76f91527ce850f155ce960fb9068bcd5d49f9Yigit Boyar        /**
6548ae76f91527ce850f155ce960fb9068bcd5d49f9Yigit Boyar         * Similar to other methods but negative count means delete and position count means add.
6558ae76f91527ce850f155ce960fb9068bcd5d49f9Yigit Boyar         * <p>
6568ae76f91527ce850f155ce960fb9068bcd5d49f9Yigit Boyar         * For instance, calling this method with <code>[1,1], [2,-1]</code> it will first add an
6578ae76f91527ce850f155ce960fb9068bcd5d49f9Yigit Boyar         * item to index 1, then remove an item from index 2 (updated index 2)
6588ae76f91527ce850f155ce960fb9068bcd5d49f9Yigit Boyar         */
6598ae76f91527ce850f155ce960fb9068bcd5d49f9Yigit Boyar        public void addDeleteAndNotify(final int[]... startCountTuples) throws Throwable {
6608ae76f91527ce850f155ce960fb9068bcd5d49f9Yigit Boyar            new AddRemoveRunnable(startCountTuples).runOnMainThread();
661d7d27e9ebe5c7325e67e1a8af265378bd2056cadChet Haase        }
662d7d27e9ebe5c7325e67e1a8af265378bd2056cadChet Haase
663d7d27e9ebe5c7325e67e1a8af265378bd2056cadChet Haase        @Override
664d7d27e9ebe5c7325e67e1a8af265378bd2056cadChet Haase        public int getItemCount() {
665d7d27e9ebe5c7325e67e1a8af265378bd2056cadChet Haase            return mItems.size();
666d7d27e9ebe5c7325e67e1a8af265378bd2056cadChet Haase        }
6678ae76f91527ce850f155ce960fb9068bcd5d49f9Yigit Boyar
668115ba0c7b2a14aa4cd0273952195e1d8f6468f87Yigit Boyar        /**
669115ba0c7b2a14aa4cd0273952195e1d8f6468f87Yigit Boyar         * Uses notifyDataSetChanged
670115ba0c7b2a14aa4cd0273952195e1d8f6468f87Yigit Boyar         */
671e0c347f627f8a78d3e5e3e5eaac9c3ae26208689Yigit Boyar        public void moveItems(boolean notifyChange, int[]... fromToTuples) throws Throwable {
672e0c347f627f8a78d3e5e3e5eaac9c3ae26208689Yigit Boyar            for (int i = 0; i < fromToTuples.length; i += 1) {
673e0c347f627f8a78d3e5e3e5eaac9c3ae26208689Yigit Boyar                int[] tuple = fromToTuples[i];
674e0c347f627f8a78d3e5e3e5eaac9c3ae26208689Yigit Boyar                moveItem(tuple[0], tuple[1], false);
675e0c347f627f8a78d3e5e3e5eaac9c3ae26208689Yigit Boyar            }
676e0c347f627f8a78d3e5e3e5eaac9c3ae26208689Yigit Boyar            if (notifyChange) {
677cadd54707a7aaa4cfa9c8b8051c6185e8717f22eChet Haase                dispatchDataSetChanged();
678e0c347f627f8a78d3e5e3e5eaac9c3ae26208689Yigit Boyar            }
679e0c347f627f8a78d3e5e3e5eaac9c3ae26208689Yigit Boyar        }
680e0c347f627f8a78d3e5e3e5eaac9c3ae26208689Yigit Boyar
681115ba0c7b2a14aa4cd0273952195e1d8f6468f87Yigit Boyar        /**
682115ba0c7b2a14aa4cd0273952195e1d8f6468f87Yigit Boyar         * Uses notifyDataSetChanged
683115ba0c7b2a14aa4cd0273952195e1d8f6468f87Yigit Boyar         */
684e0c347f627f8a78d3e5e3e5eaac9c3ae26208689Yigit Boyar        public void moveItem(final int from, final int to, final boolean notifyChange)
685e0c347f627f8a78d3e5e3e5eaac9c3ae26208689Yigit Boyar                throws Throwable {
686e0c347f627f8a78d3e5e3e5eaac9c3ae26208689Yigit Boyar            runTestOnUiThread(new Runnable() {
687e0c347f627f8a78d3e5e3e5eaac9c3ae26208689Yigit Boyar                @Override
688e0c347f627f8a78d3e5e3e5eaac9c3ae26208689Yigit Boyar                public void run() {
689e0c347f627f8a78d3e5e3e5eaac9c3ae26208689Yigit Boyar                    Item item = mItems.remove(from);
690e0c347f627f8a78d3e5e3e5eaac9c3ae26208689Yigit Boyar                    mItems.add(to, item);
691e0c347f627f8a78d3e5e3e5eaac9c3ae26208689Yigit Boyar                    offsetOriginalIndices(from, to - 1);
692e0c347f627f8a78d3e5e3e5eaac9c3ae26208689Yigit Boyar                    item.mAdapterIndex = to;
693e0c347f627f8a78d3e5e3e5eaac9c3ae26208689Yigit Boyar                    if (notifyChange) {
694e0c347f627f8a78d3e5e3e5eaac9c3ae26208689Yigit Boyar                        notifyDataSetChanged();
695e0c347f627f8a78d3e5e3e5eaac9c3ae26208689Yigit Boyar                    }
696e0c347f627f8a78d3e5e3e5eaac9c3ae26208689Yigit Boyar                }
697e0c347f627f8a78d3e5e3e5eaac9c3ae26208689Yigit Boyar            });
698e0c347f627f8a78d3e5e3e5eaac9c3ae26208689Yigit Boyar        }
699e0c347f627f8a78d3e5e3e5eaac9c3ae26208689Yigit Boyar
700115ba0c7b2a14aa4cd0273952195e1d8f6468f87Yigit Boyar        /**
701115ba0c7b2a14aa4cd0273952195e1d8f6468f87Yigit Boyar         * Uses notifyItemMoved
702115ba0c7b2a14aa4cd0273952195e1d8f6468f87Yigit Boyar         */
703115ba0c7b2a14aa4cd0273952195e1d8f6468f87Yigit Boyar        public void moveAndNotify(final int from, final int to) throws Throwable {
704115ba0c7b2a14aa4cd0273952195e1d8f6468f87Yigit Boyar            runTestOnUiThread(new Runnable() {
705115ba0c7b2a14aa4cd0273952195e1d8f6468f87Yigit Boyar                @Override
706115ba0c7b2a14aa4cd0273952195e1d8f6468f87Yigit Boyar                public void run() {
707115ba0c7b2a14aa4cd0273952195e1d8f6468f87Yigit Boyar                    Item item = mItems.remove(from);
708115ba0c7b2a14aa4cd0273952195e1d8f6468f87Yigit Boyar                    mItems.add(to, item);
709115ba0c7b2a14aa4cd0273952195e1d8f6468f87Yigit Boyar                    offsetOriginalIndices(from, to - 1);
710115ba0c7b2a14aa4cd0273952195e1d8f6468f87Yigit Boyar                    item.mAdapterIndex = to;
711115ba0c7b2a14aa4cd0273952195e1d8f6468f87Yigit Boyar                    notifyItemMoved(from, to);
712115ba0c7b2a14aa4cd0273952195e1d8f6468f87Yigit Boyar                }
713115ba0c7b2a14aa4cd0273952195e1d8f6468f87Yigit Boyar            });
714115ba0c7b2a14aa4cd0273952195e1d8f6468f87Yigit Boyar        }
715115ba0c7b2a14aa4cd0273952195e1d8f6468f87Yigit Boyar
716115ba0c7b2a14aa4cd0273952195e1d8f6468f87Yigit Boyar
717115ba0c7b2a14aa4cd0273952195e1d8f6468f87Yigit Boyar
71890ca3086dbf66ad6bb8840e46ec8524a705e1c18Yigit Boyar        @Override
71990ca3086dbf66ad6bb8840e46ec8524a705e1c18Yigit Boyar        public ViewAttachDetachCounter getCounter() {
72090ca3086dbf66ad6bb8840e46ec8524a705e1c18Yigit Boyar            return mAttachmentCounter;
72190ca3086dbf66ad6bb8840e46ec8524a705e1c18Yigit Boyar        }
72290ca3086dbf66ad6bb8840e46ec8524a705e1c18Yigit Boyar
7238ae76f91527ce850f155ce960fb9068bcd5d49f9Yigit Boyar
7248ae76f91527ce850f155ce960fb9068bcd5d49f9Yigit Boyar        private class AddRemoveRunnable implements Runnable {
7258ae76f91527ce850f155ce960fb9068bcd5d49f9Yigit Boyar            final int[][] mStartCountTuples;
7268ae76f91527ce850f155ce960fb9068bcd5d49f9Yigit Boyar
7278ae76f91527ce850f155ce960fb9068bcd5d49f9Yigit Boyar            public AddRemoveRunnable(int[][] startCountTuples) {
7288ae76f91527ce850f155ce960fb9068bcd5d49f9Yigit Boyar                mStartCountTuples = startCountTuples;
7298ae76f91527ce850f155ce960fb9068bcd5d49f9Yigit Boyar            }
7308ae76f91527ce850f155ce960fb9068bcd5d49f9Yigit Boyar
7318ae76f91527ce850f155ce960fb9068bcd5d49f9Yigit Boyar            public void runOnMainThread() throws Throwable {
7328ae76f91527ce850f155ce960fb9068bcd5d49f9Yigit Boyar                if (Looper.myLooper() == Looper.getMainLooper()) {
7338ae76f91527ce850f155ce960fb9068bcd5d49f9Yigit Boyar                    run();
7348ae76f91527ce850f155ce960fb9068bcd5d49f9Yigit Boyar                } else {
7358ae76f91527ce850f155ce960fb9068bcd5d49f9Yigit Boyar                    runTestOnUiThread(this);
7368ae76f91527ce850f155ce960fb9068bcd5d49f9Yigit Boyar                }
7378ae76f91527ce850f155ce960fb9068bcd5d49f9Yigit Boyar            }
7388ae76f91527ce850f155ce960fb9068bcd5d49f9Yigit Boyar
7398ae76f91527ce850f155ce960fb9068bcd5d49f9Yigit Boyar            @Override
7408ae76f91527ce850f155ce960fb9068bcd5d49f9Yigit Boyar            public void run() {
7418ae76f91527ce850f155ce960fb9068bcd5d49f9Yigit Boyar                for (int[] tuple : mStartCountTuples) {
7428ae76f91527ce850f155ce960fb9068bcd5d49f9Yigit Boyar                    if (tuple[1] < 0) {
7438ae76f91527ce850f155ce960fb9068bcd5d49f9Yigit Boyar                        delete(tuple);
7448ae76f91527ce850f155ce960fb9068bcd5d49f9Yigit Boyar                    } else {
7458ae76f91527ce850f155ce960fb9068bcd5d49f9Yigit Boyar                        add(tuple);
7468ae76f91527ce850f155ce960fb9068bcd5d49f9Yigit Boyar                    }
7478ae76f91527ce850f155ce960fb9068bcd5d49f9Yigit Boyar                }
7488ae76f91527ce850f155ce960fb9068bcd5d49f9Yigit Boyar            }
7498ae76f91527ce850f155ce960fb9068bcd5d49f9Yigit Boyar
7508ae76f91527ce850f155ce960fb9068bcd5d49f9Yigit Boyar            private void add(int[] tuple) {
7512d2e8d88103866b631eb0f3805da137ebcfb0275Yigit Boyar                // offset others
7522d2e8d88103866b631eb0f3805da137ebcfb0275Yigit Boyar                offsetOriginalIndices(tuple[0], tuple[1]);
7538ae76f91527ce850f155ce960fb9068bcd5d49f9Yigit Boyar                for (int i = 0; i < tuple[1]; i++) {
7548ae76f91527ce850f155ce960fb9068bcd5d49f9Yigit Boyar                    mItems.add(tuple[0], new Item(i, "new item " + i));
7558ae76f91527ce850f155ce960fb9068bcd5d49f9Yigit Boyar                }
7568ae76f91527ce850f155ce960fb9068bcd5d49f9Yigit Boyar                notifyItemRangeInserted(tuple[0], tuple[1]);
7578ae76f91527ce850f155ce960fb9068bcd5d49f9Yigit Boyar            }
7588ae76f91527ce850f155ce960fb9068bcd5d49f9Yigit Boyar
7598ae76f91527ce850f155ce960fb9068bcd5d49f9Yigit Boyar            private void delete(int[] tuple) {
7602d2e8d88103866b631eb0f3805da137ebcfb0275Yigit Boyar                final int count = -tuple[1];
7612d2e8d88103866b631eb0f3805da137ebcfb0275Yigit Boyar                offsetOriginalIndices(tuple[0] + count, tuple[1]);
7622d2e8d88103866b631eb0f3805da137ebcfb0275Yigit Boyar                for (int i = 0; i < count; i++) {
7638ae76f91527ce850f155ce960fb9068bcd5d49f9Yigit Boyar                    mItems.remove(tuple[0]);
7648ae76f91527ce850f155ce960fb9068bcd5d49f9Yigit Boyar                }
7652d2e8d88103866b631eb0f3805da137ebcfb0275Yigit Boyar                notifyItemRangeRemoved(tuple[0], count);
7668ae76f91527ce850f155ce960fb9068bcd5d49f9Yigit Boyar            }
7678ae76f91527ce850f155ce960fb9068bcd5d49f9Yigit Boyar        }
7688ae76f91527ce850f155ce960fb9068bcd5d49f9Yigit Boyar    }
7698ae76f91527ce850f155ce960fb9068bcd5d49f9Yigit Boyar
77090ca3086dbf66ad6bb8840e46ec8524a705e1c18Yigit Boyar    public boolean isMainThread() {
77190ca3086dbf66ad6bb8840e46ec8524a705e1c18Yigit Boyar        return Looper.myLooper() == Looper.getMainLooper();
77290ca3086dbf66ad6bb8840e46ec8524a705e1c18Yigit Boyar    }
77390ca3086dbf66ad6bb8840e46ec8524a705e1c18Yigit Boyar
7748ae76f91527ce850f155ce960fb9068bcd5d49f9Yigit Boyar    @Override
7758ae76f91527ce850f155ce960fb9068bcd5d49f9Yigit Boyar    public void runTestOnUiThread(Runnable r) throws Throwable {
7768ae76f91527ce850f155ce960fb9068bcd5d49f9Yigit Boyar        if (Looper.myLooper() == Looper.getMainLooper()) {
7778ae76f91527ce850f155ce960fb9068bcd5d49f9Yigit Boyar            r.run();
7788ae76f91527ce850f155ce960fb9068bcd5d49f9Yigit Boyar        } else {
7798ae76f91527ce850f155ce960fb9068bcd5d49f9Yigit Boyar            super.runTestOnUiThread(r);
7808ae76f91527ce850f155ce960fb9068bcd5d49f9Yigit Boyar        }
781d7d27e9ebe5c7325e67e1a8af265378bd2056cadChet Haase    }
782c50c4cad31d73e574b27bb3d7581542975e37263Yigit Boyar
783c50c4cad31d73e574b27bb3d7581542975e37263Yigit Boyar    static class TargetTuple {
784c50c4cad31d73e574b27bb3d7581542975e37263Yigit Boyar
785c50c4cad31d73e574b27bb3d7581542975e37263Yigit Boyar        final int mPosition;
786c50c4cad31d73e574b27bb3d7581542975e37263Yigit Boyar
787c50c4cad31d73e574b27bb3d7581542975e37263Yigit Boyar        final int mLayoutDirection;
788c50c4cad31d73e574b27bb3d7581542975e37263Yigit Boyar
789c50c4cad31d73e574b27bb3d7581542975e37263Yigit Boyar        TargetTuple(int position, int layoutDirection) {
790c50c4cad31d73e574b27bb3d7581542975e37263Yigit Boyar            this.mPosition = position;
791c50c4cad31d73e574b27bb3d7581542975e37263Yigit Boyar            this.mLayoutDirection = layoutDirection;
792c50c4cad31d73e574b27bb3d7581542975e37263Yigit Boyar        }
793c50c4cad31d73e574b27bb3d7581542975e37263Yigit Boyar
794c50c4cad31d73e574b27bb3d7581542975e37263Yigit Boyar        @Override
795c50c4cad31d73e574b27bb3d7581542975e37263Yigit Boyar        public String toString() {
796c50c4cad31d73e574b27bb3d7581542975e37263Yigit Boyar            return "TargetTuple{" +
797c50c4cad31d73e574b27bb3d7581542975e37263Yigit Boyar                    "mPosition=" + mPosition +
798c50c4cad31d73e574b27bb3d7581542975e37263Yigit Boyar                    ", mLayoutDirection=" + mLayoutDirection +
799c50c4cad31d73e574b27bb3d7581542975e37263Yigit Boyar                    '}';
800c50c4cad31d73e574b27bb3d7581542975e37263Yigit Boyar        }
801c50c4cad31d73e574b27bb3d7581542975e37263Yigit Boyar    }
80290ca3086dbf66ad6bb8840e46ec8524a705e1c18Yigit Boyar
80390ca3086dbf66ad6bb8840e46ec8524a705e1c18Yigit Boyar    public interface AttachDetachCountingAdapter {
80490ca3086dbf66ad6bb8840e46ec8524a705e1c18Yigit Boyar
80590ca3086dbf66ad6bb8840e46ec8524a705e1c18Yigit Boyar        ViewAttachDetachCounter getCounter();
80690ca3086dbf66ad6bb8840e46ec8524a705e1c18Yigit Boyar    }
80790ca3086dbf66ad6bb8840e46ec8524a705e1c18Yigit Boyar
80890ca3086dbf66ad6bb8840e46ec8524a705e1c18Yigit Boyar    public class ViewAttachDetachCounter {
80990ca3086dbf66ad6bb8840e46ec8524a705e1c18Yigit Boyar
81090ca3086dbf66ad6bb8840e46ec8524a705e1c18Yigit Boyar        Set<RecyclerView.ViewHolder> mAttachedSet = new HashSet<RecyclerView.ViewHolder>();
81190ca3086dbf66ad6bb8840e46ec8524a705e1c18Yigit Boyar
81290ca3086dbf66ad6bb8840e46ec8524a705e1c18Yigit Boyar        public void validateRemaining(RecyclerView recyclerView) {
81390ca3086dbf66ad6bb8840e46ec8524a705e1c18Yigit Boyar            final int childCount = recyclerView.getChildCount();
81490ca3086dbf66ad6bb8840e46ec8524a705e1c18Yigit Boyar            for (int i = 0; i < childCount; i++) {
81590ca3086dbf66ad6bb8840e46ec8524a705e1c18Yigit Boyar                View view = recyclerView.getChildAt(i);
81690ca3086dbf66ad6bb8840e46ec8524a705e1c18Yigit Boyar                RecyclerView.ViewHolder vh = recyclerView.getChildViewHolder(view);
81790ca3086dbf66ad6bb8840e46ec8524a705e1c18Yigit Boyar                assertTrue("remaining view should be in attached set " + vh,
81890ca3086dbf66ad6bb8840e46ec8524a705e1c18Yigit Boyar                        mAttachedSet.contains(vh));
81990ca3086dbf66ad6bb8840e46ec8524a705e1c18Yigit Boyar            }
82090ca3086dbf66ad6bb8840e46ec8524a705e1c18Yigit Boyar            assertEquals("there should not be any views left in attached set",
82190ca3086dbf66ad6bb8840e46ec8524a705e1c18Yigit Boyar                    childCount, mAttachedSet.size());
82290ca3086dbf66ad6bb8840e46ec8524a705e1c18Yigit Boyar        }
82390ca3086dbf66ad6bb8840e46ec8524a705e1c18Yigit Boyar
82490ca3086dbf66ad6bb8840e46ec8524a705e1c18Yigit Boyar        public void onViewDetached(RecyclerView.ViewHolder viewHolder) {
82590ca3086dbf66ad6bb8840e46ec8524a705e1c18Yigit Boyar            try {
82690ca3086dbf66ad6bb8840e46ec8524a705e1c18Yigit Boyar                assertTrue("view holder should be in attached set",
82790ca3086dbf66ad6bb8840e46ec8524a705e1c18Yigit Boyar                        mAttachedSet.remove(viewHolder));
82890ca3086dbf66ad6bb8840e46ec8524a705e1c18Yigit Boyar            } catch (Throwable t) {
82990ca3086dbf66ad6bb8840e46ec8524a705e1c18Yigit Boyar                postExceptionToInstrumentation(t);
83090ca3086dbf66ad6bb8840e46ec8524a705e1c18Yigit Boyar            }
83190ca3086dbf66ad6bb8840e46ec8524a705e1c18Yigit Boyar        }
83290ca3086dbf66ad6bb8840e46ec8524a705e1c18Yigit Boyar
83390ca3086dbf66ad6bb8840e46ec8524a705e1c18Yigit Boyar        public void onViewAttached(RecyclerView.ViewHolder viewHolder) {
83490ca3086dbf66ad6bb8840e46ec8524a705e1c18Yigit Boyar            try {
83590ca3086dbf66ad6bb8840e46ec8524a705e1c18Yigit Boyar                assertTrue("view holder should not be in attached set",
83690ca3086dbf66ad6bb8840e46ec8524a705e1c18Yigit Boyar                        mAttachedSet.add(viewHolder));
83790ca3086dbf66ad6bb8840e46ec8524a705e1c18Yigit Boyar            } catch (Throwable t) {
83890ca3086dbf66ad6bb8840e46ec8524a705e1c18Yigit Boyar                postExceptionToInstrumentation(t);
83990ca3086dbf66ad6bb8840e46ec8524a705e1c18Yigit Boyar            }
84090ca3086dbf66ad6bb8840e46ec8524a705e1c18Yigit Boyar        }
84190ca3086dbf66ad6bb8840e46ec8524a705e1c18Yigit Boyar
84290ca3086dbf66ad6bb8840e46ec8524a705e1c18Yigit Boyar        public void onAttached(RecyclerView recyclerView) {
84390ca3086dbf66ad6bb8840e46ec8524a705e1c18Yigit Boyar            // when a new RV is attached, clear the set and add all view holders
84490ca3086dbf66ad6bb8840e46ec8524a705e1c18Yigit Boyar            mAttachedSet.clear();
84590ca3086dbf66ad6bb8840e46ec8524a705e1c18Yigit Boyar            final int childCount = recyclerView.getChildCount();
84690ca3086dbf66ad6bb8840e46ec8524a705e1c18Yigit Boyar            for (int i = 0; i < childCount; i ++) {
84790ca3086dbf66ad6bb8840e46ec8524a705e1c18Yigit Boyar                View view = recyclerView.getChildAt(i);
84890ca3086dbf66ad6bb8840e46ec8524a705e1c18Yigit Boyar                mAttachedSet.add(recyclerView.getChildViewHolder(view));
84990ca3086dbf66ad6bb8840e46ec8524a705e1c18Yigit Boyar            }
85090ca3086dbf66ad6bb8840e46ec8524a705e1c18Yigit Boyar        }
85190ca3086dbf66ad6bb8840e46ec8524a705e1c18Yigit Boyar
85290ca3086dbf66ad6bb8840e46ec8524a705e1c18Yigit Boyar        public void onDetached(RecyclerView recyclerView) {
85390ca3086dbf66ad6bb8840e46ec8524a705e1c18Yigit Boyar            validateRemaining(recyclerView);
85490ca3086dbf66ad6bb8840e46ec8524a705e1c18Yigit Boyar        }
85590ca3086dbf66ad6bb8840e46ec8524a705e1c18Yigit Boyar    }
8567a59e4b36500bcfddd842376d6c3abe0d9f23d84Yigit Boyar}
857