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;
36d7d27e9ebe5c7325e67e1a8af265378bd2056cadChet Haase
37d7d27e9ebe5c7325e67e1a8af265378bd2056cadChet Haaseabstract public class BaseRecyclerViewInstrumentationTest extends
38d7d27e9ebe5c7325e67e1a8af265378bd2056cadChet Haase        ActivityInstrumentationTestCase2<TestActivity> {
39d7d27e9ebe5c7325e67e1a8af265378bd2056cadChet Haase
40d7d27e9ebe5c7325e67e1a8af265378bd2056cadChet Haase    private static final String TAG = "RecyclerViewTest";
41d7d27e9ebe5c7325e67e1a8af265378bd2056cadChet Haase
42b5f8b4fecb531aee6b359b4968409410aba90817Yigit Boyar    private boolean mDebug;
43b5f8b4fecb531aee6b359b4968409410aba90817Yigit Boyar
44b5f8b4fecb531aee6b359b4968409410aba90817Yigit Boyar    protected RecyclerView mRecyclerView;
45d7d27e9ebe5c7325e67e1a8af265378bd2056cadChet Haase
468ae76f91527ce850f155ce960fb9068bcd5d49f9Yigit Boyar    protected AdapterHelper mAdapterHelper;
478ae76f91527ce850f155ce960fb9068bcd5d49f9Yigit Boyar
487e9da1a2629b657d56e2f54abd18c6249f3be037Yigit Boyar    Throwable mainThreadException;
497e9da1a2629b657d56e2f54abd18c6249f3be037Yigit Boyar
50d7d27e9ebe5c7325e67e1a8af265378bd2056cadChet Haase    public BaseRecyclerViewInstrumentationTest() {
51b5f8b4fecb531aee6b359b4968409410aba90817Yigit Boyar        this(false);
52b5f8b4fecb531aee6b359b4968409410aba90817Yigit Boyar    }
53b5f8b4fecb531aee6b359b4968409410aba90817Yigit Boyar
54b5f8b4fecb531aee6b359b4968409410aba90817Yigit Boyar    public BaseRecyclerViewInstrumentationTest(boolean debug) {
558ae76f91527ce850f155ce960fb9068bcd5d49f9Yigit Boyar        super("android.support.v7.recyclerview", TestActivity.class);
56b5f8b4fecb531aee6b359b4968409410aba90817Yigit Boyar        mDebug = debug;
57b5f8b4fecb531aee6b359b4968409410aba90817Yigit Boyar    }
58b5f8b4fecb531aee6b359b4968409410aba90817Yigit Boyar
597e9da1a2629b657d56e2f54abd18c6249f3be037Yigit Boyar    void checkForMainThreadException() throws Throwable {
607e9da1a2629b657d56e2f54abd18c6249f3be037Yigit Boyar        if (mainThreadException != null) {
617e9da1a2629b657d56e2f54abd18c6249f3be037Yigit Boyar            throw mainThreadException;
627e9da1a2629b657d56e2f54abd18c6249f3be037Yigit Boyar        }
637e9da1a2629b657d56e2f54abd18c6249f3be037Yigit Boyar    }
647e9da1a2629b657d56e2f54abd18c6249f3be037Yigit Boyar
65e2437126f0225135891544315ff72e54c7de0f59Yigit Boyar    void setHasTransientState(final View view, final boolean value) {
66e2437126f0225135891544315ff72e54c7de0f59Yigit Boyar        try {
67e2437126f0225135891544315ff72e54c7de0f59Yigit Boyar            runTestOnUiThread(new Runnable() {
68e2437126f0225135891544315ff72e54c7de0f59Yigit Boyar                @Override
69e2437126f0225135891544315ff72e54c7de0f59Yigit Boyar                public void run() {
70e2437126f0225135891544315ff72e54c7de0f59Yigit Boyar                    ViewCompat.setHasTransientState(view, value);
71e2437126f0225135891544315ff72e54c7de0f59Yigit Boyar                }
72e2437126f0225135891544315ff72e54c7de0f59Yigit Boyar            });
73e2437126f0225135891544315ff72e54c7de0f59Yigit Boyar        } catch (Throwable throwable) {
74e2437126f0225135891544315ff72e54c7de0f59Yigit Boyar            Log.e(TAG, "", throwable);
75e2437126f0225135891544315ff72e54c7de0f59Yigit Boyar        }
76e2437126f0225135891544315ff72e54c7de0f59Yigit Boyar    }
77e2437126f0225135891544315ff72e54c7de0f59Yigit Boyar
780b1059b711009601ff08354e2df6bfc45266e80aYigit Boyar    void setAdapter(final RecyclerView.Adapter adapter) throws Throwable {
790b1059b711009601ff08354e2df6bfc45266e80aYigit Boyar        runTestOnUiThread(new Runnable() {
800b1059b711009601ff08354e2df6bfc45266e80aYigit Boyar            @Override
810b1059b711009601ff08354e2df6bfc45266e80aYigit Boyar            public void run() {
820b1059b711009601ff08354e2df6bfc45266e80aYigit Boyar                mRecyclerView.setAdapter(adapter);
830b1059b711009601ff08354e2df6bfc45266e80aYigit Boyar            }
840b1059b711009601ff08354e2df6bfc45266e80aYigit Boyar        });
850b1059b711009601ff08354e2df6bfc45266e80aYigit Boyar    }
860b1059b711009601ff08354e2df6bfc45266e80aYigit Boyar
870b1059b711009601ff08354e2df6bfc45266e80aYigit Boyar    void swapAdapter(final RecyclerView.Adapter adapter,
880b1059b711009601ff08354e2df6bfc45266e80aYigit Boyar            final boolean removeAndRecycleExistingViews) throws Throwable {
890b1059b711009601ff08354e2df6bfc45266e80aYigit Boyar        runTestOnUiThread(new Runnable() {
900b1059b711009601ff08354e2df6bfc45266e80aYigit Boyar            @Override
910b1059b711009601ff08354e2df6bfc45266e80aYigit Boyar            public void run() {
92824a7db96feacf3b68f56993ed72712bc935153eYigit Boyar                try {
93824a7db96feacf3b68f56993ed72712bc935153eYigit Boyar                    mRecyclerView.swapAdapter(adapter, removeAndRecycleExistingViews);
94824a7db96feacf3b68f56993ed72712bc935153eYigit Boyar                } catch (Throwable t) {
95824a7db96feacf3b68f56993ed72712bc935153eYigit Boyar                    postExceptionToInstrumentation(t);
96824a7db96feacf3b68f56993ed72712bc935153eYigit Boyar                }
970b1059b711009601ff08354e2df6bfc45266e80aYigit Boyar            }
980b1059b711009601ff08354e2df6bfc45266e80aYigit Boyar        });
99824a7db96feacf3b68f56993ed72712bc935153eYigit Boyar        checkForMainThreadException();
1000b1059b711009601ff08354e2df6bfc45266e80aYigit Boyar    }
1010b1059b711009601ff08354e2df6bfc45266e80aYigit Boyar
1027e9da1a2629b657d56e2f54abd18c6249f3be037Yigit Boyar    void postExceptionToInstrumentation(Throwable t) {
1036e83751247c5be0211d7bffaf057431c03dfef38Yigit Boyar        if (mainThreadException != null) {
1046e83751247c5be0211d7bffaf057431c03dfef38Yigit Boyar            Log.e(TAG, "receiving another main thread exception. dropping.", t);
1056e83751247c5be0211d7bffaf057431c03dfef38Yigit Boyar        } else {
10690ca3086dbf66ad6bb8840e46ec8524a705e1c18Yigit Boyar            Log.e(TAG, "captured exception on main thread", t);
1076e83751247c5be0211d7bffaf057431c03dfef38Yigit Boyar            mainThreadException = t;
1086e83751247c5be0211d7bffaf057431c03dfef38Yigit Boyar        }
1096e83751247c5be0211d7bffaf057431c03dfef38Yigit Boyar
1107e9da1a2629b657d56e2f54abd18c6249f3be037Yigit Boyar        if (mRecyclerView != null && mRecyclerView
1117e9da1a2629b657d56e2f54abd18c6249f3be037Yigit Boyar                .getLayoutManager() instanceof TestLayoutManager) {
1127e9da1a2629b657d56e2f54abd18c6249f3be037Yigit Boyar            TestLayoutManager lm = (TestLayoutManager) mRecyclerView.getLayoutManager();
1137e9da1a2629b657d56e2f54abd18c6249f3be037Yigit Boyar            // finish all layouts so that we get the correct exception
1147e9da1a2629b657d56e2f54abd18c6249f3be037Yigit Boyar            while (lm.layoutLatch.getCount() > 0) {
1157e9da1a2629b657d56e2f54abd18c6249f3be037Yigit Boyar                lm.layoutLatch.countDown();
1167e9da1a2629b657d56e2f54abd18c6249f3be037Yigit Boyar            }
1177e9da1a2629b657d56e2f54abd18c6249f3be037Yigit Boyar        }
1187e9da1a2629b657d56e2f54abd18c6249f3be037Yigit Boyar    }
1197e9da1a2629b657d56e2f54abd18c6249f3be037Yigit Boyar
1208ae76f91527ce850f155ce960fb9068bcd5d49f9Yigit Boyar    @Override
1218ae76f91527ce850f155ce960fb9068bcd5d49f9Yigit Boyar    protected void tearDown() throws Exception {
1228ae76f91527ce850f155ce960fb9068bcd5d49f9Yigit Boyar        if (mRecyclerView != null) {
1238ae76f91527ce850f155ce960fb9068bcd5d49f9Yigit Boyar            try {
1248ae76f91527ce850f155ce960fb9068bcd5d49f9Yigit Boyar                removeRecyclerView();
1258ae76f91527ce850f155ce960fb9068bcd5d49f9Yigit Boyar            } catch (Throwable throwable) {
1268ae76f91527ce850f155ce960fb9068bcd5d49f9Yigit Boyar                throwable.printStackTrace();
1278ae76f91527ce850f155ce960fb9068bcd5d49f9Yigit Boyar            }
1288ae76f91527ce850f155ce960fb9068bcd5d49f9Yigit Boyar        }
1298ae76f91527ce850f155ce960fb9068bcd5d49f9Yigit Boyar        getInstrumentation().waitForIdleSync();
130ca9ce7ccdccf6b6a2a81da73dd1e6425d90e4b23Yigit Boyar        super.tearDown();
131ca9ce7ccdccf6b6a2a81da73dd1e6425d90e4b23Yigit Boyar
1326e83751247c5be0211d7bffaf057431c03dfef38Yigit Boyar        try {
1336e83751247c5be0211d7bffaf057431c03dfef38Yigit Boyar            checkForMainThreadException();
1346e83751247c5be0211d7bffaf057431c03dfef38Yigit Boyar        } catch (Exception e) {
1356e83751247c5be0211d7bffaf057431c03dfef38Yigit Boyar            throw e;
1366e83751247c5be0211d7bffaf057431c03dfef38Yigit Boyar        } catch (Throwable throwable) {
137ca9ce7ccdccf6b6a2a81da73dd1e6425d90e4b23Yigit Boyar            throw new Exception(throwable);
1386e83751247c5be0211d7bffaf057431c03dfef38Yigit Boyar        }
1398ae76f91527ce850f155ce960fb9068bcd5d49f9Yigit Boyar    }
1408ae76f91527ce850f155ce960fb9068bcd5d49f9Yigit Boyar
141c50c4cad31d73e574b27bb3d7581542975e37263Yigit Boyar    public Rect getDecoratedRecyclerViewBounds() {
142c50c4cad31d73e574b27bb3d7581542975e37263Yigit Boyar        return new Rect(
143c50c4cad31d73e574b27bb3d7581542975e37263Yigit Boyar                mRecyclerView.getPaddingLeft(),
144c50c4cad31d73e574b27bb3d7581542975e37263Yigit Boyar                mRecyclerView.getPaddingTop(),
145c50c4cad31d73e574b27bb3d7581542975e37263Yigit Boyar                mRecyclerView.getPaddingLeft() + mRecyclerView.getWidth(),
146c50c4cad31d73e574b27bb3d7581542975e37263Yigit Boyar                mRecyclerView.getPaddingTop() + mRecyclerView.getHeight()
147c50c4cad31d73e574b27bb3d7581542975e37263Yigit Boyar        );
148c50c4cad31d73e574b27bb3d7581542975e37263Yigit Boyar    }
149c50c4cad31d73e574b27bb3d7581542975e37263Yigit Boyar
150b5f8b4fecb531aee6b359b4968409410aba90817Yigit Boyar    public void removeRecyclerView() throws Throwable {
151d7e2f2ab1d253133fa54f309e74a7ee384c33971Yigit Boyar        if (mRecyclerView == null) {
152d7e2f2ab1d253133fa54f309e74a7ee384c33971Yigit Boyar            return;
153d7e2f2ab1d253133fa54f309e74a7ee384c33971Yigit Boyar        }
15490ca3086dbf66ad6bb8840e46ec8524a705e1c18Yigit Boyar        if (!isMainThread()) {
15590ca3086dbf66ad6bb8840e46ec8524a705e1c18Yigit Boyar            getInstrumentation().waitForIdleSync();
15690ca3086dbf66ad6bb8840e46ec8524a705e1c18Yigit Boyar        }
157b5f8b4fecb531aee6b359b4968409410aba90817Yigit Boyar        runTestOnUiThread(new Runnable() {
158b5f8b4fecb531aee6b359b4968409410aba90817Yigit Boyar            @Override
159b5f8b4fecb531aee6b359b4968409410aba90817Yigit Boyar            public void run() {
160abd0fbfcdd5b438909f126392eca5978b5939b6dYigit Boyar                try {
161abd0fbfcdd5b438909f126392eca5978b5939b6dYigit Boyar                    final RecyclerView.Adapter adapter = mRecyclerView.getAdapter();
162abd0fbfcdd5b438909f126392eca5978b5939b6dYigit Boyar                    if (adapter instanceof AttachDetachCountingAdapter) {
163abd0fbfcdd5b438909f126392eca5978b5939b6dYigit Boyar                        ((AttachDetachCountingAdapter) adapter).getCounter()
164abd0fbfcdd5b438909f126392eca5978b5939b6dYigit Boyar                                .validateRemaining(mRecyclerView);
165abd0fbfcdd5b438909f126392eca5978b5939b6dYigit Boyar                    }
166abd0fbfcdd5b438909f126392eca5978b5939b6dYigit Boyar                    getActivity().mContainer.removeAllViews();
167abd0fbfcdd5b438909f126392eca5978b5939b6dYigit Boyar                } catch (Throwable t) {
168abd0fbfcdd5b438909f126392eca5978b5939b6dYigit Boyar                    postExceptionToInstrumentation(t);
16990ca3086dbf66ad6bb8840e46ec8524a705e1c18Yigit Boyar                }
170b5f8b4fecb531aee6b359b4968409410aba90817Yigit Boyar            }
171b5f8b4fecb531aee6b359b4968409410aba90817Yigit Boyar        });
17290ca3086dbf66ad6bb8840e46ec8524a705e1c18Yigit Boyar        mRecyclerView = null;
173b5f8b4fecb531aee6b359b4968409410aba90817Yigit Boyar    }
174b5f8b4fecb531aee6b359b4968409410aba90817Yigit Boyar
175504c54ea52c1b2aae6f8f4ae128f1dcaac7e3f6aYigit Boyar    void waitForAnimations(int seconds) throws InterruptedException {
176504c54ea52c1b2aae6f8f4ae128f1dcaac7e3f6aYigit Boyar        final CountDownLatch latch = new CountDownLatch(2);
177504c54ea52c1b2aae6f8f4ae128f1dcaac7e3f6aYigit Boyar        boolean running = mRecyclerView.mItemAnimator
178504c54ea52c1b2aae6f8f4ae128f1dcaac7e3f6aYigit Boyar                .isRunning(new RecyclerView.ItemAnimator.ItemAnimatorFinishedListener() {
179504c54ea52c1b2aae6f8f4ae128f1dcaac7e3f6aYigit Boyar                    @Override
180504c54ea52c1b2aae6f8f4ae128f1dcaac7e3f6aYigit Boyar                    public void onAnimationsFinished() {
181504c54ea52c1b2aae6f8f4ae128f1dcaac7e3f6aYigit Boyar                        latch.countDown();
182504c54ea52c1b2aae6f8f4ae128f1dcaac7e3f6aYigit Boyar                    }
183504c54ea52c1b2aae6f8f4ae128f1dcaac7e3f6aYigit Boyar                });
184504c54ea52c1b2aae6f8f4ae128f1dcaac7e3f6aYigit Boyar        if (running) {
185504c54ea52c1b2aae6f8f4ae128f1dcaac7e3f6aYigit Boyar            latch.await(seconds, TimeUnit.SECONDS);
186504c54ea52c1b2aae6f8f4ae128f1dcaac7e3f6aYigit Boyar        }
187504c54ea52c1b2aae6f8f4ae128f1dcaac7e3f6aYigit Boyar    }
188504c54ea52c1b2aae6f8f4ae128f1dcaac7e3f6aYigit Boyar
189b5f8b4fecb531aee6b359b4968409410aba90817Yigit Boyar    public void setRecyclerView(final RecyclerView recyclerView) throws Throwable {
1900b1059b711009601ff08354e2df6bfc45266e80aYigit Boyar        setRecyclerView(recyclerView, true);
1910b1059b711009601ff08354e2df6bfc45266e80aYigit Boyar    }
1920b1059b711009601ff08354e2df6bfc45266e80aYigit Boyar    public void setRecyclerView(final RecyclerView recyclerView, boolean assignDummyPool)
1930b1059b711009601ff08354e2df6bfc45266e80aYigit Boyar            throws Throwable {
194e37d2e63f16bc5c2938e372238ba54679a9e578eYigit Boyar        setRecyclerView(recyclerView, true, true);
195e37d2e63f16bc5c2938e372238ba54679a9e578eYigit Boyar    }
196e37d2e63f16bc5c2938e372238ba54679a9e578eYigit Boyar    public void setRecyclerView(final RecyclerView recyclerView, boolean assignDummyPool,
197e37d2e63f16bc5c2938e372238ba54679a9e578eYigit Boyar            boolean addPositionCheckItemAnimator)
198e37d2e63f16bc5c2938e372238ba54679a9e578eYigit Boyar            throws Throwable {
199b5f8b4fecb531aee6b359b4968409410aba90817Yigit Boyar        mRecyclerView = recyclerView;
2000b1059b711009601ff08354e2df6bfc45266e80aYigit Boyar        if (assignDummyPool) {
2010b1059b711009601ff08354e2df6bfc45266e80aYigit Boyar            RecyclerView.RecycledViewPool pool = new RecyclerView.RecycledViewPool() {
2020b1059b711009601ff08354e2df6bfc45266e80aYigit Boyar                @Override
2030b1059b711009601ff08354e2df6bfc45266e80aYigit Boyar                public RecyclerView.ViewHolder getRecycledView(int viewType) {
2040b1059b711009601ff08354e2df6bfc45266e80aYigit Boyar                    RecyclerView.ViewHolder viewHolder = super.getRecycledView(viewType);
2050b1059b711009601ff08354e2df6bfc45266e80aYigit Boyar                    if (viewHolder == null) {
2060b1059b711009601ff08354e2df6bfc45266e80aYigit Boyar                        return null;
2070b1059b711009601ff08354e2df6bfc45266e80aYigit Boyar                    }
2080b1059b711009601ff08354e2df6bfc45266e80aYigit Boyar                    viewHolder.addFlags(RecyclerView.ViewHolder.FLAG_BOUND);
2090b1059b711009601ff08354e2df6bfc45266e80aYigit Boyar                    viewHolder.mPosition = 200;
2100b1059b711009601ff08354e2df6bfc45266e80aYigit Boyar                    viewHolder.mOldPosition = 300;
2110b1059b711009601ff08354e2df6bfc45266e80aYigit Boyar                    viewHolder.mPreLayoutPosition = 500;
2120b1059b711009601ff08354e2df6bfc45266e80aYigit Boyar                    return viewHolder;
2138da5bba529a2fe9bd2aafab28010e11ec4de93c7Yigit Boyar                }
2148da5bba529a2fe9bd2aafab28010e11ec4de93c7Yigit Boyar
2150b1059b711009601ff08354e2df6bfc45266e80aYigit Boyar                @Override
2160b1059b711009601ff08354e2df6bfc45266e80aYigit Boyar                public void putRecycledView(RecyclerView.ViewHolder scrap) {
2170b1059b711009601ff08354e2df6bfc45266e80aYigit Boyar                    super.putRecycledView(scrap);
2180b1059b711009601ff08354e2df6bfc45266e80aYigit Boyar                }
2190b1059b711009601ff08354e2df6bfc45266e80aYigit Boyar            };
2200b1059b711009601ff08354e2df6bfc45266e80aYigit Boyar            mRecyclerView.setRecycledViewPool(pool);
2210b1059b711009601ff08354e2df6bfc45266e80aYigit Boyar        }
222e37d2e63f16bc5c2938e372238ba54679a9e578eYigit Boyar        if (addPositionCheckItemAnimator) {
223e37d2e63f16bc5c2938e372238ba54679a9e578eYigit Boyar            mRecyclerView.addItemDecoration(new RecyclerView.ItemDecoration() {
224e37d2e63f16bc5c2938e372238ba54679a9e578eYigit Boyar                @Override
225e37d2e63f16bc5c2938e372238ba54679a9e578eYigit Boyar                public void getItemOffsets(Rect outRect, View view, RecyclerView parent,
226e37d2e63f16bc5c2938e372238ba54679a9e578eYigit Boyar                        RecyclerView.State state) {
227e37d2e63f16bc5c2938e372238ba54679a9e578eYigit Boyar                    RecyclerView.ViewHolder vh = parent.getChildViewHolder(view);
228e37d2e63f16bc5c2938e372238ba54679a9e578eYigit Boyar                    if (!vh.isRemoved()) {
229e37d2e63f16bc5c2938e372238ba54679a9e578eYigit Boyar                        assertNotSame("If getItemOffsets is called, child should have a valid"
230e37d2e63f16bc5c2938e372238ba54679a9e578eYigit Boyar                                            + " adapter position unless it is removed",
231e37d2e63f16bc5c2938e372238ba54679a9e578eYigit Boyar                                    vh.getAdapterPosition(), RecyclerView.NO_POSITION);
232e37d2e63f16bc5c2938e372238ba54679a9e578eYigit Boyar                    }
233e37d2e63f16bc5c2938e372238ba54679a9e578eYigit Boyar                }
234e37d2e63f16bc5c2938e372238ba54679a9e578eYigit Boyar            });
235e37d2e63f16bc5c2938e372238ba54679a9e578eYigit Boyar        }
2368ae76f91527ce850f155ce960fb9068bcd5d49f9Yigit Boyar        mAdapterHelper = recyclerView.mAdapterHelper;
237b5f8b4fecb531aee6b359b4968409410aba90817Yigit Boyar        runTestOnUiThread(new Runnable() {
238b5f8b4fecb531aee6b359b4968409410aba90817Yigit Boyar            @Override
239b5f8b4fecb531aee6b359b4968409410aba90817Yigit Boyar            public void run() {
240b5f8b4fecb531aee6b359b4968409410aba90817Yigit Boyar                getActivity().mContainer.addView(recyclerView);
241b5f8b4fecb531aee6b359b4968409410aba90817Yigit Boyar            }
242b5f8b4fecb531aee6b359b4968409410aba90817Yigit Boyar        });
243b5f8b4fecb531aee6b359b4968409410aba90817Yigit Boyar    }
244b5f8b4fecb531aee6b359b4968409410aba90817Yigit Boyar
245310e95e1c6dfe4f26ef594233e65e1ff83e0f1ffYigit Boyar    protected FrameLayout getRecyclerViewContainer() {
246310e95e1c6dfe4f26ef594233e65e1ff83e0f1ffYigit Boyar        return getActivity().mContainer;
247310e95e1c6dfe4f26ef594233e65e1ff83e0f1ffYigit Boyar    }
248310e95e1c6dfe4f26ef594233e65e1ff83e0f1ffYigit Boyar
249e2437126f0225135891544315ff72e54c7de0f59Yigit Boyar    public void requestLayoutOnUIThread(final View view) {
250e2437126f0225135891544315ff72e54c7de0f59Yigit Boyar        try {
251e2437126f0225135891544315ff72e54c7de0f59Yigit Boyar            runTestOnUiThread(new Runnable() {
252e2437126f0225135891544315ff72e54c7de0f59Yigit Boyar                @Override
253e2437126f0225135891544315ff72e54c7de0f59Yigit Boyar                public void run() {
254e2437126f0225135891544315ff72e54c7de0f59Yigit Boyar                    view.requestLayout();
255e2437126f0225135891544315ff72e54c7de0f59Yigit Boyar                }
256e2437126f0225135891544315ff72e54c7de0f59Yigit Boyar            });
257e2437126f0225135891544315ff72e54c7de0f59Yigit Boyar        } catch (Throwable throwable) {
258e2437126f0225135891544315ff72e54c7de0f59Yigit Boyar            Log.e(TAG, "", throwable);
259e2437126f0225135891544315ff72e54c7de0f59Yigit Boyar        }
260b5f8b4fecb531aee6b359b4968409410aba90817Yigit Boyar    }
261b5f8b4fecb531aee6b359b4968409410aba90817Yigit Boyar
262e2437126f0225135891544315ff72e54c7de0f59Yigit Boyar    public void scrollBy(final int dt) {
263e2437126f0225135891544315ff72e54c7de0f59Yigit Boyar        try {
264e2437126f0225135891544315ff72e54c7de0f59Yigit Boyar            runTestOnUiThread(new Runnable() {
265e2437126f0225135891544315ff72e54c7de0f59Yigit Boyar                @Override
266e2437126f0225135891544315ff72e54c7de0f59Yigit Boyar                public void run() {
267e2437126f0225135891544315ff72e54c7de0f59Yigit Boyar                    if (mRecyclerView.getLayoutManager().canScrollHorizontally()) {
268e2437126f0225135891544315ff72e54c7de0f59Yigit Boyar                        mRecyclerView.scrollBy(dt, 0);
269e2437126f0225135891544315ff72e54c7de0f59Yigit Boyar                    } else {
270e2437126f0225135891544315ff72e54c7de0f59Yigit Boyar                        mRecyclerView.scrollBy(0, dt);
271e2437126f0225135891544315ff72e54c7de0f59Yigit Boyar                    }
272b5f8b4fecb531aee6b359b4968409410aba90817Yigit Boyar
273e2437126f0225135891544315ff72e54c7de0f59Yigit Boyar                }
274e2437126f0225135891544315ff72e54c7de0f59Yigit Boyar            });
275e2437126f0225135891544315ff72e54c7de0f59Yigit Boyar        } catch (Throwable throwable) {
276e2437126f0225135891544315ff72e54c7de0f59Yigit Boyar            Log.e(TAG, "", throwable);
277e2437126f0225135891544315ff72e54c7de0f59Yigit Boyar        }
278d7d27e9ebe5c7325e67e1a8af265378bd2056cadChet Haase    }
279d7d27e9ebe5c7325e67e1a8af265378bd2056cadChet Haase
2808edcb0bdeaba6931f9d8154f0c81f57da7ddab2aYigit Boyar    void scrollToPosition(final int position) throws Throwable {
2818edcb0bdeaba6931f9d8154f0c81f57da7ddab2aYigit Boyar        runTestOnUiThread(new Runnable() {
2828edcb0bdeaba6931f9d8154f0c81f57da7ddab2aYigit Boyar            @Override
2838edcb0bdeaba6931f9d8154f0c81f57da7ddab2aYigit Boyar            public void run() {
2848edcb0bdeaba6931f9d8154f0c81f57da7ddab2aYigit Boyar                mRecyclerView.getLayoutManager().scrollToPosition(position);
2858edcb0bdeaba6931f9d8154f0c81f57da7ddab2aYigit Boyar            }
2868edcb0bdeaba6931f9d8154f0c81f57da7ddab2aYigit Boyar        });
2878edcb0bdeaba6931f9d8154f0c81f57da7ddab2aYigit Boyar    }
2888edcb0bdeaba6931f9d8154f0c81f57da7ddab2aYigit Boyar
2898edcb0bdeaba6931f9d8154f0c81f57da7ddab2aYigit Boyar    void smoothScrollToPosition(final int position)
2908edcb0bdeaba6931f9d8154f0c81f57da7ddab2aYigit Boyar            throws Throwable {
2918edcb0bdeaba6931f9d8154f0c81f57da7ddab2aYigit Boyar        Log.d(TAG, "SMOOTH scrolling to " + position);
2928edcb0bdeaba6931f9d8154f0c81f57da7ddab2aYigit Boyar        runTestOnUiThread(new Runnable() {
2938edcb0bdeaba6931f9d8154f0c81f57da7ddab2aYigit Boyar            @Override
2948edcb0bdeaba6931f9d8154f0c81f57da7ddab2aYigit Boyar            public void run() {
2958edcb0bdeaba6931f9d8154f0c81f57da7ddab2aYigit Boyar                mRecyclerView.smoothScrollToPosition(position);
2968edcb0bdeaba6931f9d8154f0c81f57da7ddab2aYigit Boyar            }
2978edcb0bdeaba6931f9d8154f0c81f57da7ddab2aYigit Boyar        });
2988edcb0bdeaba6931f9d8154f0c81f57da7ddab2aYigit Boyar        while (mRecyclerView.getLayoutManager().isSmoothScrolling() ||
2998edcb0bdeaba6931f9d8154f0c81f57da7ddab2aYigit Boyar                mRecyclerView.getScrollState() != RecyclerView.SCROLL_STATE_IDLE) {
3008edcb0bdeaba6931f9d8154f0c81f57da7ddab2aYigit Boyar            if (mDebug) {
3018edcb0bdeaba6931f9d8154f0c81f57da7ddab2aYigit Boyar                Log.d(TAG, "SMOOTH scrolling step");
3028edcb0bdeaba6931f9d8154f0c81f57da7ddab2aYigit Boyar            }
3038edcb0bdeaba6931f9d8154f0c81f57da7ddab2aYigit Boyar            Thread.sleep(200);
3048edcb0bdeaba6931f9d8154f0c81f57da7ddab2aYigit Boyar        }
3058edcb0bdeaba6931f9d8154f0c81f57da7ddab2aYigit Boyar        Log.d(TAG, "SMOOTH scrolling done");
306504c54ea52c1b2aae6f8f4ae128f1dcaac7e3f6aYigit Boyar        getInstrumentation().waitForIdleSync();
3078edcb0bdeaba6931f9d8154f0c81f57da7ddab2aYigit Boyar    }
3088edcb0bdeaba6931f9d8154f0c81f57da7ddab2aYigit Boyar
309d7d27e9ebe5c7325e67e1a8af265378bd2056cadChet Haase    class TestViewHolder extends RecyclerView.ViewHolder {
310d7d27e9ebe5c7325e67e1a8af265378bd2056cadChet Haase
311115ba0c7b2a14aa4cd0273952195e1d8f6468f87Yigit Boyar        Item mBoundItem;
312d7d27e9ebe5c7325e67e1a8af265378bd2056cadChet Haase
313d7d27e9ebe5c7325e67e1a8af265378bd2056cadChet Haase        public TestViewHolder(View itemView) {
314d7d27e9ebe5c7325e67e1a8af265378bd2056cadChet Haase            super(itemView);
315310e95e1c6dfe4f26ef594233e65e1ff83e0f1ffYigit Boyar            itemView.setFocusable(true);
316d7d27e9ebe5c7325e67e1a8af265378bd2056cadChet Haase        }
317504c54ea52c1b2aae6f8f4ae128f1dcaac7e3f6aYigit Boyar
318504c54ea52c1b2aae6f8f4ae128f1dcaac7e3f6aYigit Boyar        @Override
319504c54ea52c1b2aae6f8f4ae128f1dcaac7e3f6aYigit Boyar        public String toString() {
320115ba0c7b2a14aa4cd0273952195e1d8f6468f87Yigit Boyar            return super.toString() + " item:" + mBoundItem;
321504c54ea52c1b2aae6f8f4ae128f1dcaac7e3f6aYigit Boyar        }
322d7d27e9ebe5c7325e67e1a8af265378bd2056cadChet Haase    }
323d7d27e9ebe5c7325e67e1a8af265378bd2056cadChet Haase
324d7d27e9ebe5c7325e67e1a8af265378bd2056cadChet Haase    class TestLayoutManager extends RecyclerView.LayoutManager {
325d7d27e9ebe5c7325e67e1a8af265378bd2056cadChet Haase
326d7d27e9ebe5c7325e67e1a8af265378bd2056cadChet Haase        CountDownLatch layoutLatch;
327d7d27e9ebe5c7325e67e1a8af265378bd2056cadChet Haase
328d7d27e9ebe5c7325e67e1a8af265378bd2056cadChet Haase        public void expectLayouts(int count) {
329d7d27e9ebe5c7325e67e1a8af265378bd2056cadChet Haase            layoutLatch = new CountDownLatch(count);
330d7d27e9ebe5c7325e67e1a8af265378bd2056cadChet Haase        }
331d7d27e9ebe5c7325e67e1a8af265378bd2056cadChet Haase
332b5f8b4fecb531aee6b359b4968409410aba90817Yigit Boyar        public void waitForLayout(long timeout, TimeUnit timeUnit) throws Throwable {
333b5f8b4fecb531aee6b359b4968409410aba90817Yigit Boyar            layoutLatch.await(timeout * (mDebug ? 100 : 1), timeUnit);
334d7d27e9ebe5c7325e67e1a8af265378bd2056cadChet Haase            assertEquals("all expected layouts should be executed at the expected time",
335d7d27e9ebe5c7325e67e1a8af265378bd2056cadChet Haase                    0, layoutLatch.getCount());
336e0c347f627f8a78d3e5e3e5eaac9c3ae26208689Yigit Boyar            getInstrumentation().waitForIdleSync();
337d7d27e9ebe5c7325e67e1a8af265378bd2056cadChet Haase        }
338d7d27e9ebe5c7325e67e1a8af265378bd2056cadChet Haase
339b5f8b4fecb531aee6b359b4968409410aba90817Yigit Boyar        public void assertLayoutCount(int count, String msg, long timeout) throws Throwable {
340b5f8b4fecb531aee6b359b4968409410aba90817Yigit Boyar            layoutLatch.await(timeout, TimeUnit.SECONDS);
341b5f8b4fecb531aee6b359b4968409410aba90817Yigit Boyar            assertEquals(msg, count, layoutLatch.getCount());
342b5f8b4fecb531aee6b359b4968409410aba90817Yigit Boyar        }
343b5f8b4fecb531aee6b359b4968409410aba90817Yigit Boyar
344b5f8b4fecb531aee6b359b4968409410aba90817Yigit Boyar        public void assertNoLayout(String msg, long timeout) throws Throwable {
345b5f8b4fecb531aee6b359b4968409410aba90817Yigit Boyar            layoutLatch.await(timeout, TimeUnit.SECONDS);
346b5f8b4fecb531aee6b359b4968409410aba90817Yigit Boyar            assertFalse(msg, layoutLatch.getCount() == 0);
347b5f8b4fecb531aee6b359b4968409410aba90817Yigit Boyar        }
348b5f8b4fecb531aee6b359b4968409410aba90817Yigit Boyar
349b5f8b4fecb531aee6b359b4968409410aba90817Yigit Boyar        public void waitForLayout(long timeout) throws Throwable {
3508ae76f91527ce850f155ce960fb9068bcd5d49f9Yigit Boyar            waitForLayout(timeout * (mDebug ? 10000 : 1), TimeUnit.SECONDS);
351b5f8b4fecb531aee6b359b4968409410aba90817Yigit Boyar        }
352b5f8b4fecb531aee6b359b4968409410aba90817Yigit Boyar
353d7d27e9ebe5c7325e67e1a8af265378bd2056cadChet Haase        @Override
354d7d27e9ebe5c7325e67e1a8af265378bd2056cadChet Haase        public RecyclerView.LayoutParams generateDefaultLayoutParams() {
355d7d27e9ebe5c7325e67e1a8af265378bd2056cadChet Haase            return new RecyclerView.LayoutParams(ViewGroup.LayoutParams.WRAP_CONTENT,
356d7d27e9ebe5c7325e67e1a8af265378bd2056cadChet Haase                    ViewGroup.LayoutParams.WRAP_CONTENT);
357d7d27e9ebe5c7325e67e1a8af265378bd2056cadChet Haase        }
358d7d27e9ebe5c7325e67e1a8af265378bd2056cadChet Haase
359d7d27e9ebe5c7325e67e1a8af265378bd2056cadChet Haase        void assertVisibleItemPositions() {
360d7d27e9ebe5c7325e67e1a8af265378bd2056cadChet Haase            int i = getChildCount();
361d7d27e9ebe5c7325e67e1a8af265378bd2056cadChet Haase            TestAdapter testAdapter = (TestAdapter) mRecyclerView.getAdapter();
362d7d27e9ebe5c7325e67e1a8af265378bd2056cadChet Haase            while (i-- > 0) {
363d7d27e9ebe5c7325e67e1a8af265378bd2056cadChet Haase                View view = getChildAt(i);
364d7d27e9ebe5c7325e67e1a8af265378bd2056cadChet Haase                RecyclerView.LayoutParams lp = (RecyclerView.LayoutParams) view.getLayoutParams();
365115ba0c7b2a14aa4cd0273952195e1d8f6468f87Yigit Boyar                Item item = ((TestViewHolder) lp.mViewHolder).mBoundItem;
366b5f8b4fecb531aee6b359b4968409410aba90817Yigit Boyar                if (mDebug) {
367d7d27e9ebe5c7325e67e1a8af265378bd2056cadChet Haase                    Log.d(TAG, "testing item " + i);
368d7d27e9ebe5c7325e67e1a8af265378bd2056cadChet Haase                }
3698ae76f91527ce850f155ce960fb9068bcd5d49f9Yigit Boyar                if (!lp.isItemRemoved()) {
3708ae76f91527ce850f155ce960fb9068bcd5d49f9Yigit Boyar                    RecyclerView.ViewHolder vh = mRecyclerView.getChildViewHolder(view);
3718ae76f91527ce850f155ce960fb9068bcd5d49f9Yigit Boyar                    assertSame("item position in LP should match adapter value :" + vh,
3728ae76f91527ce850f155ce960fb9068bcd5d49f9Yigit Boyar                            testAdapter.mItems.get(vh.mPosition), item);
3738ae76f91527ce850f155ce960fb9068bcd5d49f9Yigit Boyar                }
374d7d27e9ebe5c7325e67e1a8af265378bd2056cadChet Haase            }
375d7d27e9ebe5c7325e67e1a8af265378bd2056cadChet Haase        }
376d7d27e9ebe5c7325e67e1a8af265378bd2056cadChet Haase
377b5f8b4fecb531aee6b359b4968409410aba90817Yigit Boyar        RecyclerView.LayoutParams getLp(View v) {
378b5f8b4fecb531aee6b359b4968409410aba90817Yigit Boyar            return (RecyclerView.LayoutParams) v.getLayoutParams();
379b5f8b4fecb531aee6b359b4968409410aba90817Yigit Boyar        }
380b5f8b4fecb531aee6b359b4968409410aba90817Yigit Boyar
381f485be9def4c0f72cfdfa6b0c616c23f04652817Yigit Boyar        void layoutRange(RecyclerView.Recycler recycler, int start, int end) {
382fe3996cf5af65fe11cc3ed47b67123ec267bd336Yigit Boyar            assertScrap(recycler);
383b5f8b4fecb531aee6b359b4968409410aba90817Yigit Boyar            if (mDebug) {
384d7d27e9ebe5c7325e67e1a8af265378bd2056cadChet Haase                Log.d(TAG, "will layout items from " + start + " to " + end);
385d7d27e9ebe5c7325e67e1a8af265378bd2056cadChet Haase            }
3865ced882cabdcefbb469e332f6f73983999aad0e5Yigit Boyar            int diff = end > start ? 1 : -1;
387504c54ea52c1b2aae6f8f4ae128f1dcaac7e3f6aYigit Boyar            int top = 0;
3885ced882cabdcefbb469e332f6f73983999aad0e5Yigit Boyar            for (int i = start; i != end; i+=diff) {
389b5f8b4fecb531aee6b359b4968409410aba90817Yigit Boyar                if (mDebug) {
390d7d27e9ebe5c7325e67e1a8af265378bd2056cadChet Haase                    Log.d(TAG, "laying out item " + i);
391d7d27e9ebe5c7325e67e1a8af265378bd2056cadChet Haase                }
392d7d27e9ebe5c7325e67e1a8af265378bd2056cadChet Haase                View view = recycler.getViewForPosition(i);
393b5f8b4fecb531aee6b359b4968409410aba90817Yigit Boyar                assertNotNull("view should not be null for valid position. "
394b5f8b4fecb531aee6b359b4968409410aba90817Yigit Boyar                        + "got null view at position " + i, view);
395c8b0ff5869e17a656a3a1d7fb306efa5b99fa07fYigit Boyar                if (!mRecyclerView.mState.isPreLayout()) {
396c8b0ff5869e17a656a3a1d7fb306efa5b99fa07fYigit Boyar                    RecyclerView.LayoutParams layoutParams = (RecyclerView.LayoutParams) view
397c8b0ff5869e17a656a3a1d7fb306efa5b99fa07fYigit Boyar                            .getLayoutParams();
398c8b0ff5869e17a656a3a1d7fb306efa5b99fa07fYigit Boyar                    assertFalse("In post layout, getViewForPosition should never return a view "
399c8b0ff5869e17a656a3a1d7fb306efa5b99fa07fYigit Boyar                            + "that is removed", layoutParams != null
400c8b0ff5869e17a656a3a1d7fb306efa5b99fa07fYigit Boyar                            && layoutParams.isItemRemoved());
401c8b0ff5869e17a656a3a1d7fb306efa5b99fa07fYigit Boyar
402b5f8b4fecb531aee6b359b4968409410aba90817Yigit Boyar                }
4038da5bba529a2fe9bd2aafab28010e11ec4de93c7Yigit Boyar                assertEquals("getViewForPosition should return correct position",
4048da5bba529a2fe9bd2aafab28010e11ec4de93c7Yigit Boyar                        i, getPosition(view));
405c8b0ff5869e17a656a3a1d7fb306efa5b99fa07fYigit Boyar                addView(view);
406b5f8b4fecb531aee6b359b4968409410aba90817Yigit Boyar
407d7d27e9ebe5c7325e67e1a8af265378bd2056cadChet Haase                measureChildWithMargins(view, 0, 0);
408504c54ea52c1b2aae6f8f4ae128f1dcaac7e3f6aYigit Boyar                layoutDecorated(view, 0, top, getDecoratedMeasuredWidth(view)
409504c54ea52c1b2aae6f8f4ae128f1dcaac7e3f6aYigit Boyar                        , top + getDecoratedMeasuredHeight(view));
410504c54ea52c1b2aae6f8f4ae128f1dcaac7e3f6aYigit Boyar                top += view.getMeasuredHeight();
411d7d27e9ebe5c7325e67e1a8af265378bd2056cadChet Haase            }
412d7d27e9ebe5c7325e67e1a8af265378bd2056cadChet Haase        }
413fe3996cf5af65fe11cc3ed47b67123ec267bd336Yigit Boyar
414fe3996cf5af65fe11cc3ed47b67123ec267bd336Yigit Boyar        private void assertScrap(RecyclerView.Recycler recycler) {
415e0c347f627f8a78d3e5e3e5eaac9c3ae26208689Yigit Boyar            if (mRecyclerView.getAdapter() != null &&
416e0c347f627f8a78d3e5e3e5eaac9c3ae26208689Yigit Boyar                    !mRecyclerView.getAdapter().hasStableIds()) {
417e0c347f627f8a78d3e5e3e5eaac9c3ae26208689Yigit Boyar                for (RecyclerView.ViewHolder viewHolder : recycler.getScrapList()) {
418e0c347f627f8a78d3e5e3e5eaac9c3ae26208689Yigit Boyar                    assertFalse("Invalid scrap should be no kept", viewHolder.isInvalid());
419e0c347f627f8a78d3e5e3e5eaac9c3ae26208689Yigit Boyar                }
420fe3996cf5af65fe11cc3ed47b67123ec267bd336Yigit Boyar            }
421fe3996cf5af65fe11cc3ed47b67123ec267bd336Yigit Boyar        }
422bb6ebab3b3b85ec20b3bc262289a2460705322f5Yigit Boyar
423bb6ebab3b3b85ec20b3bc262289a2460705322f5Yigit Boyar        @Override
424bb6ebab3b3b85ec20b3bc262289a2460705322f5Yigit Boyar        public boolean canScrollHorizontally() {
425bb6ebab3b3b85ec20b3bc262289a2460705322f5Yigit Boyar            return true;
426bb6ebab3b3b85ec20b3bc262289a2460705322f5Yigit Boyar        }
427bb6ebab3b3b85ec20b3bc262289a2460705322f5Yigit Boyar
428bb6ebab3b3b85ec20b3bc262289a2460705322f5Yigit Boyar        @Override
429bb6ebab3b3b85ec20b3bc262289a2460705322f5Yigit Boyar        public boolean canScrollVertically() {
430bb6ebab3b3b85ec20b3bc262289a2460705322f5Yigit Boyar            return true;
431bb6ebab3b3b85ec20b3bc262289a2460705322f5Yigit Boyar        }
432bb6ebab3b3b85ec20b3bc262289a2460705322f5Yigit Boyar
433bb6ebab3b3b85ec20b3bc262289a2460705322f5Yigit Boyar        @Override
434bb6ebab3b3b85ec20b3bc262289a2460705322f5Yigit Boyar        public int scrollHorizontallyBy(int dx, RecyclerView.Recycler recycler,
435bb6ebab3b3b85ec20b3bc262289a2460705322f5Yigit Boyar                RecyclerView.State state) {
436bb6ebab3b3b85ec20b3bc262289a2460705322f5Yigit Boyar            return dx;
437bb6ebab3b3b85ec20b3bc262289a2460705322f5Yigit Boyar        }
438bb6ebab3b3b85ec20b3bc262289a2460705322f5Yigit Boyar
439bb6ebab3b3b85ec20b3bc262289a2460705322f5Yigit Boyar        @Override
440bb6ebab3b3b85ec20b3bc262289a2460705322f5Yigit Boyar        public int scrollVerticallyBy(int dy, RecyclerView.Recycler recycler,
441bb6ebab3b3b85ec20b3bc262289a2460705322f5Yigit Boyar                RecyclerView.State state) {
442bb6ebab3b3b85ec20b3bc262289a2460705322f5Yigit Boyar            return dy;
443bb6ebab3b3b85ec20b3bc262289a2460705322f5Yigit Boyar        }
444d7d27e9ebe5c7325e67e1a8af265378bd2056cadChet Haase    }
445d7d27e9ebe5c7325e67e1a8af265378bd2056cadChet Haase
446d7d27e9ebe5c7325e67e1a8af265378bd2056cadChet Haase    static class Item {
44711b7cfea15306c0d6e4ed7da09f7698df01695dfYigit Boyar        final static AtomicInteger idCounter = new AtomicInteger(0);
44811b7cfea15306c0d6e4ed7da09f7698df01695dfYigit Boyar        final public int mId = idCounter.incrementAndGet();
449d7d27e9ebe5c7325e67e1a8af265378bd2056cadChet Haase
4502d2e8d88103866b631eb0f3805da137ebcfb0275Yigit Boyar        int mAdapterIndex;
451d7d27e9ebe5c7325e67e1a8af265378bd2056cadChet Haase
4522d2e8d88103866b631eb0f3805da137ebcfb0275Yigit Boyar        final String mText;
453d7d27e9ebe5c7325e67e1a8af265378bd2056cadChet Haase
4542d2e8d88103866b631eb0f3805da137ebcfb0275Yigit Boyar        Item(int adapterIndex, String text) {
4552d2e8d88103866b631eb0f3805da137ebcfb0275Yigit Boyar            mAdapterIndex = adapterIndex;
4562d2e8d88103866b631eb0f3805da137ebcfb0275Yigit Boyar            mText = text;
457d7d27e9ebe5c7325e67e1a8af265378bd2056cadChet Haase        }
4588ae76f91527ce850f155ce960fb9068bcd5d49f9Yigit Boyar
4598ae76f91527ce850f155ce960fb9068bcd5d49f9Yigit Boyar        @Override
4608ae76f91527ce850f155ce960fb9068bcd5d49f9Yigit Boyar        public String toString() {
4618ae76f91527ce850f155ce960fb9068bcd5d49f9Yigit Boyar            return "Item{" +
4628ae76f91527ce850f155ce960fb9068bcd5d49f9Yigit Boyar                    "mId=" + mId +
4632d2e8d88103866b631eb0f3805da137ebcfb0275Yigit Boyar                    ", originalIndex=" + mAdapterIndex +
4642d2e8d88103866b631eb0f3805da137ebcfb0275Yigit Boyar                    ", text='" + mText + '\'' +
4658ae76f91527ce850f155ce960fb9068bcd5d49f9Yigit Boyar                    '}';
4668ae76f91527ce850f155ce960fb9068bcd5d49f9Yigit Boyar        }
467d7d27e9ebe5c7325e67e1a8af265378bd2056cadChet Haase    }
468d7d27e9ebe5c7325e67e1a8af265378bd2056cadChet Haase
46990ca3086dbf66ad6bb8840e46ec8524a705e1c18Yigit Boyar    class TestAdapter extends RecyclerView.Adapter<TestViewHolder>
47090ca3086dbf66ad6bb8840e46ec8524a705e1c18Yigit Boyar            implements AttachDetachCountingAdapter {
471d7d27e9ebe5c7325e67e1a8af265378bd2056cadChet Haase
47290ca3086dbf66ad6bb8840e46ec8524a705e1c18Yigit Boyar        ViewAttachDetachCounter mAttachmentCounter = new ViewAttachDetachCounter();
473d7d27e9ebe5c7325e67e1a8af265378bd2056cadChet Haase        List<Item> mItems;
474d7d27e9ebe5c7325e67e1a8af265378bd2056cadChet Haase
475d7d27e9ebe5c7325e67e1a8af265378bd2056cadChet Haase        TestAdapter(int count) {
476d7d27e9ebe5c7325e67e1a8af265378bd2056cadChet Haase            mItems = new ArrayList<Item>(count);
477d7d27e9ebe5c7325e67e1a8af265378bd2056cadChet Haase            for (int i = 0; i < count; i++) {
478d7d27e9ebe5c7325e67e1a8af265378bd2056cadChet Haase                mItems.add(new Item(i, "Item " + i));
479d7d27e9ebe5c7325e67e1a8af265378bd2056cadChet Haase            }
480d7d27e9ebe5c7325e67e1a8af265378bd2056cadChet Haase        }
481d7d27e9ebe5c7325e67e1a8af265378bd2056cadChet Haase
482d7d27e9ebe5c7325e67e1a8af265378bd2056cadChet Haase        @Override
48390ca3086dbf66ad6bb8840e46ec8524a705e1c18Yigit Boyar        public void onViewAttachedToWindow(TestViewHolder holder) {
48490ca3086dbf66ad6bb8840e46ec8524a705e1c18Yigit Boyar            super.onViewAttachedToWindow(holder);
48590ca3086dbf66ad6bb8840e46ec8524a705e1c18Yigit Boyar            mAttachmentCounter.onViewAttached(holder);
48690ca3086dbf66ad6bb8840e46ec8524a705e1c18Yigit Boyar        }
48790ca3086dbf66ad6bb8840e46ec8524a705e1c18Yigit Boyar
48890ca3086dbf66ad6bb8840e46ec8524a705e1c18Yigit Boyar        @Override
48990ca3086dbf66ad6bb8840e46ec8524a705e1c18Yigit Boyar        public void onViewDetachedFromWindow(TestViewHolder holder) {
49090ca3086dbf66ad6bb8840e46ec8524a705e1c18Yigit Boyar            super.onViewDetachedFromWindow(holder);
49190ca3086dbf66ad6bb8840e46ec8524a705e1c18Yigit Boyar            mAttachmentCounter.onViewDetached(holder);
49290ca3086dbf66ad6bb8840e46ec8524a705e1c18Yigit Boyar        }
49390ca3086dbf66ad6bb8840e46ec8524a705e1c18Yigit Boyar
49490ca3086dbf66ad6bb8840e46ec8524a705e1c18Yigit Boyar        @Override
49590ca3086dbf66ad6bb8840e46ec8524a705e1c18Yigit Boyar        public void onAttachedToRecyclerView(RecyclerView recyclerView) {
49690ca3086dbf66ad6bb8840e46ec8524a705e1c18Yigit Boyar            super.onAttachedToRecyclerView(recyclerView);
49790ca3086dbf66ad6bb8840e46ec8524a705e1c18Yigit Boyar            mAttachmentCounter.onAttached(recyclerView);
49890ca3086dbf66ad6bb8840e46ec8524a705e1c18Yigit Boyar        }
49990ca3086dbf66ad6bb8840e46ec8524a705e1c18Yigit Boyar
50090ca3086dbf66ad6bb8840e46ec8524a705e1c18Yigit Boyar        @Override
50190ca3086dbf66ad6bb8840e46ec8524a705e1c18Yigit Boyar        public void onDetachedFromRecyclerView(RecyclerView recyclerView) {
50290ca3086dbf66ad6bb8840e46ec8524a705e1c18Yigit Boyar            super.onDetachedFromRecyclerView(recyclerView);
50390ca3086dbf66ad6bb8840e46ec8524a705e1c18Yigit Boyar            mAttachmentCounter.onDetached(recyclerView);
50490ca3086dbf66ad6bb8840e46ec8524a705e1c18Yigit Boyar        }
50590ca3086dbf66ad6bb8840e46ec8524a705e1c18Yigit Boyar
50690ca3086dbf66ad6bb8840e46ec8524a705e1c18Yigit Boyar        @Override
507d7d27e9ebe5c7325e67e1a8af265378bd2056cadChet Haase        public TestViewHolder onCreateViewHolder(ViewGroup parent,
508d7d27e9ebe5c7325e67e1a8af265378bd2056cadChet Haase                int viewType) {
509d7d27e9ebe5c7325e67e1a8af265378bd2056cadChet Haase            return new TestViewHolder(new TextView(parent.getContext()));
510d7d27e9ebe5c7325e67e1a8af265378bd2056cadChet Haase        }
511d7d27e9ebe5c7325e67e1a8af265378bd2056cadChet Haase
512d7d27e9ebe5c7325e67e1a8af265378bd2056cadChet Haase        @Override
513d7d27e9ebe5c7325e67e1a8af265378bd2056cadChet Haase        public void onBindViewHolder(TestViewHolder holder, int position) {
514d7d27e9ebe5c7325e67e1a8af265378bd2056cadChet Haase            final Item item = mItems.get(position);
5152d2e8d88103866b631eb0f3805da137ebcfb0275Yigit Boyar            ((TextView) (holder.itemView)).setText(item.mText + "(" + item.mAdapterIndex + ")");
516115ba0c7b2a14aa4cd0273952195e1d8f6468f87Yigit Boyar            holder.mBoundItem = item;
517d7d27e9ebe5c7325e67e1a8af265378bd2056cadChet Haase        }
518d7d27e9ebe5c7325e67e1a8af265378bd2056cadChet Haase
519b5f8b4fecb531aee6b359b4968409410aba90817Yigit Boyar        public void deleteAndNotify(final int start, final int count) throws Throwable {
520b5f8b4fecb531aee6b359b4968409410aba90817Yigit Boyar            deleteAndNotify(new int[]{start, count});
521b5f8b4fecb531aee6b359b4968409410aba90817Yigit Boyar        }
522b5f8b4fecb531aee6b359b4968409410aba90817Yigit Boyar
523b5f8b4fecb531aee6b359b4968409410aba90817Yigit Boyar        /**
524b5f8b4fecb531aee6b359b4968409410aba90817Yigit Boyar         * Deletes items in the given ranges.
525b5f8b4fecb531aee6b359b4968409410aba90817Yigit Boyar         * <p>
526b5f8b4fecb531aee6b359b4968409410aba90817Yigit Boyar         * Note that each operation affects the one after so you should offset them properly.
527b5f8b4fecb531aee6b359b4968409410aba90817Yigit Boyar         * <p>
528b5f8b4fecb531aee6b359b4968409410aba90817Yigit Boyar         * For example, if adapter has 5 items (A,B,C,D,E), and then you call this method with
529b5f8b4fecb531aee6b359b4968409410aba90817Yigit Boyar         * <code>[1, 2],[2, 1]</code>, it will first delete items B,C and the new adapter will be
530b5f8b4fecb531aee6b359b4968409410aba90817Yigit Boyar         * A D E. Then it will delete 2,1 which means it will delete E.
531b5f8b4fecb531aee6b359b4968409410aba90817Yigit Boyar         */
532b5f8b4fecb531aee6b359b4968409410aba90817Yigit Boyar        public void deleteAndNotify(final int[]... startCountTuples) throws Throwable {
5338ae76f91527ce850f155ce960fb9068bcd5d49f9Yigit Boyar            for (int[] tuple : startCountTuples) {
5348ae76f91527ce850f155ce960fb9068bcd5d49f9Yigit Boyar                tuple[1] = -tuple[1];
5358ae76f91527ce850f155ce960fb9068bcd5d49f9Yigit Boyar            }
5368ae76f91527ce850f155ce960fb9068bcd5d49f9Yigit Boyar            new AddRemoveRunnable(startCountTuples).runOnMainThread();
537d7d27e9ebe5c7325e67e1a8af265378bd2056cadChet Haase        }
538d7d27e9ebe5c7325e67e1a8af265378bd2056cadChet Haase
539c8b0ff5869e17a656a3a1d7fb306efa5b99fa07fYigit Boyar        @Override
540c8b0ff5869e17a656a3a1d7fb306efa5b99fa07fYigit Boyar        public long getItemId(int position) {
541c8b0ff5869e17a656a3a1d7fb306efa5b99fa07fYigit Boyar            return hasStableIds() ? mItems.get(position).mId : super.getItemId(position);
542c8b0ff5869e17a656a3a1d7fb306efa5b99fa07fYigit Boyar        }
543c8b0ff5869e17a656a3a1d7fb306efa5b99fa07fYigit Boyar
5442d2e8d88103866b631eb0f3805da137ebcfb0275Yigit Boyar        public void offsetOriginalIndices(int start, int offset) {
5452d2e8d88103866b631eb0f3805da137ebcfb0275Yigit Boyar            for (int i = start; i < mItems.size(); i++) {
5462d2e8d88103866b631eb0f3805da137ebcfb0275Yigit Boyar                mItems.get(i).mAdapterIndex += offset;
5472d2e8d88103866b631eb0f3805da137ebcfb0275Yigit Boyar            }
5482d2e8d88103866b631eb0f3805da137ebcfb0275Yigit Boyar        }
5492d2e8d88103866b631eb0f3805da137ebcfb0275Yigit Boyar
550e0c347f627f8a78d3e5e3e5eaac9c3ae26208689Yigit Boyar        /**
551e0c347f627f8a78d3e5e3e5eaac9c3ae26208689Yigit Boyar         * @param start inclusive
552e0c347f627f8a78d3e5e3e5eaac9c3ae26208689Yigit Boyar         * @param end exclusive
553e0c347f627f8a78d3e5e3e5eaac9c3ae26208689Yigit Boyar         * @param offset
554e0c347f627f8a78d3e5e3e5eaac9c3ae26208689Yigit Boyar         */
555e0c347f627f8a78d3e5e3e5eaac9c3ae26208689Yigit Boyar        public void offsetOriginalIndicesBetween(int start, int end, int offset) {
556e0c347f627f8a78d3e5e3e5eaac9c3ae26208689Yigit Boyar            for (int i = start; i < end && i < mItems.size(); i++) {
557e0c347f627f8a78d3e5e3e5eaac9c3ae26208689Yigit Boyar                mItems.get(i).mAdapterIndex += offset;
558e0c347f627f8a78d3e5e3e5eaac9c3ae26208689Yigit Boyar            }
559e0c347f627f8a78d3e5e3e5eaac9c3ae26208689Yigit Boyar        }
560e0c347f627f8a78d3e5e3e5eaac9c3ae26208689Yigit Boyar
561b5f8b4fecb531aee6b359b4968409410aba90817Yigit Boyar        public void addAndNotify(final int start, final int count) throws Throwable {
562b5f8b4fecb531aee6b359b4968409410aba90817Yigit Boyar            addAndNotify(new int[]{start, count});
563b5f8b4fecb531aee6b359b4968409410aba90817Yigit Boyar        }
564b5f8b4fecb531aee6b359b4968409410aba90817Yigit Boyar
565b5f8b4fecb531aee6b359b4968409410aba90817Yigit Boyar        public void addAndNotify(final int[]... startCountTuples) throws Throwable {
5668ae76f91527ce850f155ce960fb9068bcd5d49f9Yigit Boyar            new AddRemoveRunnable(startCountTuples).runOnMainThread();
5678ae76f91527ce850f155ce960fb9068bcd5d49f9Yigit Boyar        }
568b5f8b4fecb531aee6b359b4968409410aba90817Yigit Boyar
569cadd54707a7aaa4cfa9c8b8051c6185e8717f22eChet Haase        public void dispatchDataSetChanged() throws Throwable {
57022b6b8163d2e15eb6d0a89906854dec801945f1dYigit Boyar            runTestOnUiThread(new Runnable() {
57122b6b8163d2e15eb6d0a89906854dec801945f1dYigit Boyar                @Override
57222b6b8163d2e15eb6d0a89906854dec801945f1dYigit Boyar                public void run() {
57322b6b8163d2e15eb6d0a89906854dec801945f1dYigit Boyar                    notifyDataSetChanged();
57422b6b8163d2e15eb6d0a89906854dec801945f1dYigit Boyar                }
57522b6b8163d2e15eb6d0a89906854dec801945f1dYigit Boyar            });
57622b6b8163d2e15eb6d0a89906854dec801945f1dYigit Boyar        }
57722b6b8163d2e15eb6d0a89906854dec801945f1dYigit Boyar
578cadd54707a7aaa4cfa9c8b8051c6185e8717f22eChet Haase        public void changeAndNotify(final int start, final int count) throws Throwable {
5797e9da1a2629b657d56e2f54abd18c6249f3be037Yigit Boyar            runTestOnUiThread(new Runnable() {
5807e9da1a2629b657d56e2f54abd18c6249f3be037Yigit Boyar                @Override
5817e9da1a2629b657d56e2f54abd18c6249f3be037Yigit Boyar                public void run() {
5827e9da1a2629b657d56e2f54abd18c6249f3be037Yigit Boyar                    notifyItemRangeChanged(start, count);
5837e9da1a2629b657d56e2f54abd18c6249f3be037Yigit Boyar                }
5847e9da1a2629b657d56e2f54abd18c6249f3be037Yigit Boyar            });
5857e9da1a2629b657d56e2f54abd18c6249f3be037Yigit Boyar        }
5867e9da1a2629b657d56e2f54abd18c6249f3be037Yigit Boyar
587cadd54707a7aaa4cfa9c8b8051c6185e8717f22eChet Haase        public void changePositionsAndNotify(final int... positions) throws Throwable {
588cadd54707a7aaa4cfa9c8b8051c6185e8717f22eChet Haase            runTestOnUiThread(new Runnable() {
589cadd54707a7aaa4cfa9c8b8051c6185e8717f22eChet Haase                @Override
590cadd54707a7aaa4cfa9c8b8051c6185e8717f22eChet Haase                public void run() {
591cadd54707a7aaa4cfa9c8b8051c6185e8717f22eChet Haase                    for (int i = 0; i < positions.length; i += 1) {
592cadd54707a7aaa4cfa9c8b8051c6185e8717f22eChet Haase                        TestAdapter.super.notifyItemRangeChanged(positions[i], 1);
593cadd54707a7aaa4cfa9c8b8051c6185e8717f22eChet Haase                    }
594cadd54707a7aaa4cfa9c8b8051c6185e8717f22eChet Haase                }
595cadd54707a7aaa4cfa9c8b8051c6185e8717f22eChet Haase            });
596cadd54707a7aaa4cfa9c8b8051c6185e8717f22eChet Haase        }
597cadd54707a7aaa4cfa9c8b8051c6185e8717f22eChet Haase
5988ae76f91527ce850f155ce960fb9068bcd5d49f9Yigit Boyar        /**
5998ae76f91527ce850f155ce960fb9068bcd5d49f9Yigit Boyar         * Similar to other methods but negative count means delete and position count means add.
6008ae76f91527ce850f155ce960fb9068bcd5d49f9Yigit Boyar         * <p>
6018ae76f91527ce850f155ce960fb9068bcd5d49f9Yigit Boyar         * For instance, calling this method with <code>[1,1], [2,-1]</code> it will first add an
6028ae76f91527ce850f155ce960fb9068bcd5d49f9Yigit Boyar         * item to index 1, then remove an item from index 2 (updated index 2)
6038ae76f91527ce850f155ce960fb9068bcd5d49f9Yigit Boyar         */
6048ae76f91527ce850f155ce960fb9068bcd5d49f9Yigit Boyar        public void addDeleteAndNotify(final int[]... startCountTuples) throws Throwable {
6058ae76f91527ce850f155ce960fb9068bcd5d49f9Yigit Boyar            new AddRemoveRunnable(startCountTuples).runOnMainThread();
606d7d27e9ebe5c7325e67e1a8af265378bd2056cadChet Haase        }
607d7d27e9ebe5c7325e67e1a8af265378bd2056cadChet Haase
608d7d27e9ebe5c7325e67e1a8af265378bd2056cadChet Haase        @Override
609d7d27e9ebe5c7325e67e1a8af265378bd2056cadChet Haase        public int getItemCount() {
610d7d27e9ebe5c7325e67e1a8af265378bd2056cadChet Haase            return mItems.size();
611d7d27e9ebe5c7325e67e1a8af265378bd2056cadChet Haase        }
6128ae76f91527ce850f155ce960fb9068bcd5d49f9Yigit Boyar
613115ba0c7b2a14aa4cd0273952195e1d8f6468f87Yigit Boyar        /**
614115ba0c7b2a14aa4cd0273952195e1d8f6468f87Yigit Boyar         * Uses notifyDataSetChanged
615115ba0c7b2a14aa4cd0273952195e1d8f6468f87Yigit Boyar         */
616e0c347f627f8a78d3e5e3e5eaac9c3ae26208689Yigit Boyar        public void moveItems(boolean notifyChange, int[]... fromToTuples) throws Throwable {
617e0c347f627f8a78d3e5e3e5eaac9c3ae26208689Yigit Boyar            for (int i = 0; i < fromToTuples.length; i += 1) {
618e0c347f627f8a78d3e5e3e5eaac9c3ae26208689Yigit Boyar                int[] tuple = fromToTuples[i];
619e0c347f627f8a78d3e5e3e5eaac9c3ae26208689Yigit Boyar                moveItem(tuple[0], tuple[1], false);
620e0c347f627f8a78d3e5e3e5eaac9c3ae26208689Yigit Boyar            }
621e0c347f627f8a78d3e5e3e5eaac9c3ae26208689Yigit Boyar            if (notifyChange) {
622cadd54707a7aaa4cfa9c8b8051c6185e8717f22eChet Haase                dispatchDataSetChanged();
623e0c347f627f8a78d3e5e3e5eaac9c3ae26208689Yigit Boyar            }
624e0c347f627f8a78d3e5e3e5eaac9c3ae26208689Yigit Boyar        }
625e0c347f627f8a78d3e5e3e5eaac9c3ae26208689Yigit Boyar
626115ba0c7b2a14aa4cd0273952195e1d8f6468f87Yigit Boyar        /**
627115ba0c7b2a14aa4cd0273952195e1d8f6468f87Yigit Boyar         * Uses notifyDataSetChanged
628115ba0c7b2a14aa4cd0273952195e1d8f6468f87Yigit Boyar         */
629e0c347f627f8a78d3e5e3e5eaac9c3ae26208689Yigit Boyar        public void moveItem(final int from, final int to, final boolean notifyChange)
630e0c347f627f8a78d3e5e3e5eaac9c3ae26208689Yigit Boyar                throws Throwable {
631e0c347f627f8a78d3e5e3e5eaac9c3ae26208689Yigit Boyar            runTestOnUiThread(new Runnable() {
632e0c347f627f8a78d3e5e3e5eaac9c3ae26208689Yigit Boyar                @Override
633e0c347f627f8a78d3e5e3e5eaac9c3ae26208689Yigit Boyar                public void run() {
634e0c347f627f8a78d3e5e3e5eaac9c3ae26208689Yigit Boyar                    Item item = mItems.remove(from);
635e0c347f627f8a78d3e5e3e5eaac9c3ae26208689Yigit Boyar                    mItems.add(to, item);
636e0c347f627f8a78d3e5e3e5eaac9c3ae26208689Yigit Boyar                    offsetOriginalIndices(from, to - 1);
637e0c347f627f8a78d3e5e3e5eaac9c3ae26208689Yigit Boyar                    item.mAdapterIndex = to;
638e0c347f627f8a78d3e5e3e5eaac9c3ae26208689Yigit Boyar                    if (notifyChange) {
639e0c347f627f8a78d3e5e3e5eaac9c3ae26208689Yigit Boyar                        notifyDataSetChanged();
640e0c347f627f8a78d3e5e3e5eaac9c3ae26208689Yigit Boyar                    }
641e0c347f627f8a78d3e5e3e5eaac9c3ae26208689Yigit Boyar                }
642e0c347f627f8a78d3e5e3e5eaac9c3ae26208689Yigit Boyar            });
643e0c347f627f8a78d3e5e3e5eaac9c3ae26208689Yigit Boyar        }
644e0c347f627f8a78d3e5e3e5eaac9c3ae26208689Yigit Boyar
645115ba0c7b2a14aa4cd0273952195e1d8f6468f87Yigit Boyar        /**
646115ba0c7b2a14aa4cd0273952195e1d8f6468f87Yigit Boyar         * Uses notifyItemMoved
647115ba0c7b2a14aa4cd0273952195e1d8f6468f87Yigit Boyar         */
648115ba0c7b2a14aa4cd0273952195e1d8f6468f87Yigit Boyar        public void moveAndNotify(final int from, final int to) throws Throwable {
649115ba0c7b2a14aa4cd0273952195e1d8f6468f87Yigit Boyar            runTestOnUiThread(new Runnable() {
650115ba0c7b2a14aa4cd0273952195e1d8f6468f87Yigit Boyar                @Override
651115ba0c7b2a14aa4cd0273952195e1d8f6468f87Yigit Boyar                public void run() {
652115ba0c7b2a14aa4cd0273952195e1d8f6468f87Yigit Boyar                    Item item = mItems.remove(from);
653115ba0c7b2a14aa4cd0273952195e1d8f6468f87Yigit Boyar                    mItems.add(to, item);
654115ba0c7b2a14aa4cd0273952195e1d8f6468f87Yigit Boyar                    offsetOriginalIndices(from, to - 1);
655115ba0c7b2a14aa4cd0273952195e1d8f6468f87Yigit Boyar                    item.mAdapterIndex = to;
656115ba0c7b2a14aa4cd0273952195e1d8f6468f87Yigit Boyar                    notifyItemMoved(from, to);
657115ba0c7b2a14aa4cd0273952195e1d8f6468f87Yigit Boyar                }
658115ba0c7b2a14aa4cd0273952195e1d8f6468f87Yigit Boyar            });
659115ba0c7b2a14aa4cd0273952195e1d8f6468f87Yigit Boyar        }
660115ba0c7b2a14aa4cd0273952195e1d8f6468f87Yigit Boyar
661115ba0c7b2a14aa4cd0273952195e1d8f6468f87Yigit Boyar
662115ba0c7b2a14aa4cd0273952195e1d8f6468f87Yigit Boyar
66390ca3086dbf66ad6bb8840e46ec8524a705e1c18Yigit Boyar        @Override
66490ca3086dbf66ad6bb8840e46ec8524a705e1c18Yigit Boyar        public ViewAttachDetachCounter getCounter() {
66590ca3086dbf66ad6bb8840e46ec8524a705e1c18Yigit Boyar            return mAttachmentCounter;
66690ca3086dbf66ad6bb8840e46ec8524a705e1c18Yigit Boyar        }
66790ca3086dbf66ad6bb8840e46ec8524a705e1c18Yigit Boyar
6688ae76f91527ce850f155ce960fb9068bcd5d49f9Yigit Boyar
6698ae76f91527ce850f155ce960fb9068bcd5d49f9Yigit Boyar        private class AddRemoveRunnable implements Runnable {
6708ae76f91527ce850f155ce960fb9068bcd5d49f9Yigit Boyar            final int[][] mStartCountTuples;
6718ae76f91527ce850f155ce960fb9068bcd5d49f9Yigit Boyar
6728ae76f91527ce850f155ce960fb9068bcd5d49f9Yigit Boyar            public AddRemoveRunnable(int[][] startCountTuples) {
6738ae76f91527ce850f155ce960fb9068bcd5d49f9Yigit Boyar                mStartCountTuples = startCountTuples;
6748ae76f91527ce850f155ce960fb9068bcd5d49f9Yigit Boyar            }
6758ae76f91527ce850f155ce960fb9068bcd5d49f9Yigit Boyar
6768ae76f91527ce850f155ce960fb9068bcd5d49f9Yigit Boyar            public void runOnMainThread() throws Throwable {
6778ae76f91527ce850f155ce960fb9068bcd5d49f9Yigit Boyar                if (Looper.myLooper() == Looper.getMainLooper()) {
6788ae76f91527ce850f155ce960fb9068bcd5d49f9Yigit Boyar                    run();
6798ae76f91527ce850f155ce960fb9068bcd5d49f9Yigit Boyar                } else {
6808ae76f91527ce850f155ce960fb9068bcd5d49f9Yigit Boyar                    runTestOnUiThread(this);
6818ae76f91527ce850f155ce960fb9068bcd5d49f9Yigit Boyar                }
6828ae76f91527ce850f155ce960fb9068bcd5d49f9Yigit Boyar            }
6838ae76f91527ce850f155ce960fb9068bcd5d49f9Yigit Boyar
6848ae76f91527ce850f155ce960fb9068bcd5d49f9Yigit Boyar            @Override
6858ae76f91527ce850f155ce960fb9068bcd5d49f9Yigit Boyar            public void run() {
6868ae76f91527ce850f155ce960fb9068bcd5d49f9Yigit Boyar                for (int[] tuple : mStartCountTuples) {
6878ae76f91527ce850f155ce960fb9068bcd5d49f9Yigit Boyar                    if (tuple[1] < 0) {
6888ae76f91527ce850f155ce960fb9068bcd5d49f9Yigit Boyar                        delete(tuple);
6898ae76f91527ce850f155ce960fb9068bcd5d49f9Yigit Boyar                    } else {
6908ae76f91527ce850f155ce960fb9068bcd5d49f9Yigit Boyar                        add(tuple);
6918ae76f91527ce850f155ce960fb9068bcd5d49f9Yigit Boyar                    }
6928ae76f91527ce850f155ce960fb9068bcd5d49f9Yigit Boyar                }
6938ae76f91527ce850f155ce960fb9068bcd5d49f9Yigit Boyar            }
6948ae76f91527ce850f155ce960fb9068bcd5d49f9Yigit Boyar
6958ae76f91527ce850f155ce960fb9068bcd5d49f9Yigit Boyar            private void add(int[] tuple) {
6962d2e8d88103866b631eb0f3805da137ebcfb0275Yigit Boyar                // offset others
6972d2e8d88103866b631eb0f3805da137ebcfb0275Yigit Boyar                offsetOriginalIndices(tuple[0], tuple[1]);
6988ae76f91527ce850f155ce960fb9068bcd5d49f9Yigit Boyar                for (int i = 0; i < tuple[1]; i++) {
6998ae76f91527ce850f155ce960fb9068bcd5d49f9Yigit Boyar                    mItems.add(tuple[0], new Item(i, "new item " + i));
7008ae76f91527ce850f155ce960fb9068bcd5d49f9Yigit Boyar                }
7018ae76f91527ce850f155ce960fb9068bcd5d49f9Yigit Boyar                notifyItemRangeInserted(tuple[0], tuple[1]);
7028ae76f91527ce850f155ce960fb9068bcd5d49f9Yigit Boyar            }
7038ae76f91527ce850f155ce960fb9068bcd5d49f9Yigit Boyar
7048ae76f91527ce850f155ce960fb9068bcd5d49f9Yigit Boyar            private void delete(int[] tuple) {
7052d2e8d88103866b631eb0f3805da137ebcfb0275Yigit Boyar                final int count = -tuple[1];
7062d2e8d88103866b631eb0f3805da137ebcfb0275Yigit Boyar                offsetOriginalIndices(tuple[0] + count, tuple[1]);
7072d2e8d88103866b631eb0f3805da137ebcfb0275Yigit Boyar                for (int i = 0; i < count; i++) {
7088ae76f91527ce850f155ce960fb9068bcd5d49f9Yigit Boyar                    mItems.remove(tuple[0]);
7098ae76f91527ce850f155ce960fb9068bcd5d49f9Yigit Boyar                }
7102d2e8d88103866b631eb0f3805da137ebcfb0275Yigit Boyar                notifyItemRangeRemoved(tuple[0], count);
7118ae76f91527ce850f155ce960fb9068bcd5d49f9Yigit Boyar            }
7128ae76f91527ce850f155ce960fb9068bcd5d49f9Yigit Boyar        }
7138ae76f91527ce850f155ce960fb9068bcd5d49f9Yigit Boyar    }
7148ae76f91527ce850f155ce960fb9068bcd5d49f9Yigit Boyar
71590ca3086dbf66ad6bb8840e46ec8524a705e1c18Yigit Boyar    public boolean isMainThread() {
71690ca3086dbf66ad6bb8840e46ec8524a705e1c18Yigit Boyar        return Looper.myLooper() == Looper.getMainLooper();
71790ca3086dbf66ad6bb8840e46ec8524a705e1c18Yigit Boyar    }
71890ca3086dbf66ad6bb8840e46ec8524a705e1c18Yigit Boyar
7198ae76f91527ce850f155ce960fb9068bcd5d49f9Yigit Boyar    @Override
7208ae76f91527ce850f155ce960fb9068bcd5d49f9Yigit Boyar    public void runTestOnUiThread(Runnable r) throws Throwable {
7218ae76f91527ce850f155ce960fb9068bcd5d49f9Yigit Boyar        if (Looper.myLooper() == Looper.getMainLooper()) {
7228ae76f91527ce850f155ce960fb9068bcd5d49f9Yigit Boyar            r.run();
7238ae76f91527ce850f155ce960fb9068bcd5d49f9Yigit Boyar        } else {
7248ae76f91527ce850f155ce960fb9068bcd5d49f9Yigit Boyar            super.runTestOnUiThread(r);
7258ae76f91527ce850f155ce960fb9068bcd5d49f9Yigit Boyar        }
726d7d27e9ebe5c7325e67e1a8af265378bd2056cadChet Haase    }
727c50c4cad31d73e574b27bb3d7581542975e37263Yigit Boyar
728c50c4cad31d73e574b27bb3d7581542975e37263Yigit Boyar    static class TargetTuple {
729c50c4cad31d73e574b27bb3d7581542975e37263Yigit Boyar
730c50c4cad31d73e574b27bb3d7581542975e37263Yigit Boyar        final int mPosition;
731c50c4cad31d73e574b27bb3d7581542975e37263Yigit Boyar
732c50c4cad31d73e574b27bb3d7581542975e37263Yigit Boyar        final int mLayoutDirection;
733c50c4cad31d73e574b27bb3d7581542975e37263Yigit Boyar
734c50c4cad31d73e574b27bb3d7581542975e37263Yigit Boyar        TargetTuple(int position, int layoutDirection) {
735c50c4cad31d73e574b27bb3d7581542975e37263Yigit Boyar            this.mPosition = position;
736c50c4cad31d73e574b27bb3d7581542975e37263Yigit Boyar            this.mLayoutDirection = layoutDirection;
737c50c4cad31d73e574b27bb3d7581542975e37263Yigit Boyar        }
738c50c4cad31d73e574b27bb3d7581542975e37263Yigit Boyar
739c50c4cad31d73e574b27bb3d7581542975e37263Yigit Boyar        @Override
740c50c4cad31d73e574b27bb3d7581542975e37263Yigit Boyar        public String toString() {
741c50c4cad31d73e574b27bb3d7581542975e37263Yigit Boyar            return "TargetTuple{" +
742c50c4cad31d73e574b27bb3d7581542975e37263Yigit Boyar                    "mPosition=" + mPosition +
743c50c4cad31d73e574b27bb3d7581542975e37263Yigit Boyar                    ", mLayoutDirection=" + mLayoutDirection +
744c50c4cad31d73e574b27bb3d7581542975e37263Yigit Boyar                    '}';
745c50c4cad31d73e574b27bb3d7581542975e37263Yigit Boyar        }
746c50c4cad31d73e574b27bb3d7581542975e37263Yigit Boyar    }
74790ca3086dbf66ad6bb8840e46ec8524a705e1c18Yigit Boyar
74890ca3086dbf66ad6bb8840e46ec8524a705e1c18Yigit Boyar    public interface AttachDetachCountingAdapter {
74990ca3086dbf66ad6bb8840e46ec8524a705e1c18Yigit Boyar
75090ca3086dbf66ad6bb8840e46ec8524a705e1c18Yigit Boyar        ViewAttachDetachCounter getCounter();
75190ca3086dbf66ad6bb8840e46ec8524a705e1c18Yigit Boyar    }
75290ca3086dbf66ad6bb8840e46ec8524a705e1c18Yigit Boyar
75390ca3086dbf66ad6bb8840e46ec8524a705e1c18Yigit Boyar    public class ViewAttachDetachCounter {
75490ca3086dbf66ad6bb8840e46ec8524a705e1c18Yigit Boyar
75590ca3086dbf66ad6bb8840e46ec8524a705e1c18Yigit Boyar        Set<RecyclerView.ViewHolder> mAttachedSet = new HashSet<RecyclerView.ViewHolder>();
75690ca3086dbf66ad6bb8840e46ec8524a705e1c18Yigit Boyar
75790ca3086dbf66ad6bb8840e46ec8524a705e1c18Yigit Boyar        public void validateRemaining(RecyclerView recyclerView) {
75890ca3086dbf66ad6bb8840e46ec8524a705e1c18Yigit Boyar            final int childCount = recyclerView.getChildCount();
75990ca3086dbf66ad6bb8840e46ec8524a705e1c18Yigit Boyar            for (int i = 0; i < childCount; i++) {
76090ca3086dbf66ad6bb8840e46ec8524a705e1c18Yigit Boyar                View view = recyclerView.getChildAt(i);
76190ca3086dbf66ad6bb8840e46ec8524a705e1c18Yigit Boyar                RecyclerView.ViewHolder vh = recyclerView.getChildViewHolder(view);
76290ca3086dbf66ad6bb8840e46ec8524a705e1c18Yigit Boyar                assertTrue("remaining view should be in attached set " + vh,
76390ca3086dbf66ad6bb8840e46ec8524a705e1c18Yigit Boyar                        mAttachedSet.contains(vh));
76490ca3086dbf66ad6bb8840e46ec8524a705e1c18Yigit Boyar            }
76590ca3086dbf66ad6bb8840e46ec8524a705e1c18Yigit Boyar            assertEquals("there should not be any views left in attached set",
76690ca3086dbf66ad6bb8840e46ec8524a705e1c18Yigit Boyar                    childCount, mAttachedSet.size());
76790ca3086dbf66ad6bb8840e46ec8524a705e1c18Yigit Boyar        }
76890ca3086dbf66ad6bb8840e46ec8524a705e1c18Yigit Boyar
76990ca3086dbf66ad6bb8840e46ec8524a705e1c18Yigit Boyar        public void onViewDetached(RecyclerView.ViewHolder viewHolder) {
77090ca3086dbf66ad6bb8840e46ec8524a705e1c18Yigit Boyar            try {
77190ca3086dbf66ad6bb8840e46ec8524a705e1c18Yigit Boyar                assertTrue("view holder should be in attached set",
77290ca3086dbf66ad6bb8840e46ec8524a705e1c18Yigit Boyar                        mAttachedSet.remove(viewHolder));
77390ca3086dbf66ad6bb8840e46ec8524a705e1c18Yigit Boyar            } catch (Throwable t) {
77490ca3086dbf66ad6bb8840e46ec8524a705e1c18Yigit Boyar                postExceptionToInstrumentation(t);
77590ca3086dbf66ad6bb8840e46ec8524a705e1c18Yigit Boyar            }
77690ca3086dbf66ad6bb8840e46ec8524a705e1c18Yigit Boyar        }
77790ca3086dbf66ad6bb8840e46ec8524a705e1c18Yigit Boyar
77890ca3086dbf66ad6bb8840e46ec8524a705e1c18Yigit Boyar        public void onViewAttached(RecyclerView.ViewHolder viewHolder) {
77990ca3086dbf66ad6bb8840e46ec8524a705e1c18Yigit Boyar            try {
78090ca3086dbf66ad6bb8840e46ec8524a705e1c18Yigit Boyar                assertTrue("view holder should not be in attached set",
78190ca3086dbf66ad6bb8840e46ec8524a705e1c18Yigit Boyar                        mAttachedSet.add(viewHolder));
78290ca3086dbf66ad6bb8840e46ec8524a705e1c18Yigit Boyar            } catch (Throwable t) {
78390ca3086dbf66ad6bb8840e46ec8524a705e1c18Yigit Boyar                postExceptionToInstrumentation(t);
78490ca3086dbf66ad6bb8840e46ec8524a705e1c18Yigit Boyar            }
78590ca3086dbf66ad6bb8840e46ec8524a705e1c18Yigit Boyar        }
78690ca3086dbf66ad6bb8840e46ec8524a705e1c18Yigit Boyar
78790ca3086dbf66ad6bb8840e46ec8524a705e1c18Yigit Boyar        public void onAttached(RecyclerView recyclerView) {
78890ca3086dbf66ad6bb8840e46ec8524a705e1c18Yigit Boyar            // when a new RV is attached, clear the set and add all view holders
78990ca3086dbf66ad6bb8840e46ec8524a705e1c18Yigit Boyar            mAttachedSet.clear();
79090ca3086dbf66ad6bb8840e46ec8524a705e1c18Yigit Boyar            final int childCount = recyclerView.getChildCount();
79190ca3086dbf66ad6bb8840e46ec8524a705e1c18Yigit Boyar            for (int i = 0; i < childCount; i ++) {
79290ca3086dbf66ad6bb8840e46ec8524a705e1c18Yigit Boyar                View view = recyclerView.getChildAt(i);
79390ca3086dbf66ad6bb8840e46ec8524a705e1c18Yigit Boyar                mAttachedSet.add(recyclerView.getChildViewHolder(view));
79490ca3086dbf66ad6bb8840e46ec8524a705e1c18Yigit Boyar            }
79590ca3086dbf66ad6bb8840e46ec8524a705e1c18Yigit Boyar        }
79690ca3086dbf66ad6bb8840e46ec8524a705e1c18Yigit Boyar
79790ca3086dbf66ad6bb8840e46ec8524a705e1c18Yigit Boyar        public void onDetached(RecyclerView recyclerView) {
79890ca3086dbf66ad6bb8840e46ec8524a705e1c18Yigit Boyar            validateRemaining(recyclerView);
79990ca3086dbf66ad6bb8840e46ec8524a705e1c18Yigit Boyar        }
80090ca3086dbf66ad6bb8840e46ec8524a705e1c18Yigit Boyar    }
8017a59e4b36500bcfddd842376d6c3abe0d9f23d84Yigit Boyar}
802