BaseRecyclerViewInstrumentationTest.java revision 90ca3086dbf66ad6bb8840e46ec8524a705e1c18
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;
2090ca3086dbf66ad6bb8840e46ec8524a705e1c18Yigit Boyarimport android.os.Debug;
218ae76f91527ce850f155ce960fb9068bcd5d49f9Yigit Boyarimport android.os.Looper;
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.HashMap;
3190ca3086dbf66ad6bb8840e46ec8524a705e1c18Yigit Boyarimport java.util.HashSet;
32d7d27e9ebe5c7325e67e1a8af265378bd2056cadChet Haaseimport java.util.List;
3390ca3086dbf66ad6bb8840e46ec8524a705e1c18Yigit Boyarimport java.util.Set;
34d7d27e9ebe5c7325e67e1a8af265378bd2056cadChet Haaseimport java.util.concurrent.CountDownLatch;
35d7d27e9ebe5c7325e67e1a8af265378bd2056cadChet Haaseimport java.util.concurrent.TimeUnit;
3611b7cfea15306c0d6e4ed7da09f7698df01695dfYigit Boyarimport java.util.concurrent.atomic.AtomicInteger;
37d7d27e9ebe5c7325e67e1a8af265378bd2056cadChet Haase
38d7d27e9ebe5c7325e67e1a8af265378bd2056cadChet Haaseabstract public class BaseRecyclerViewInstrumentationTest extends
39d7d27e9ebe5c7325e67e1a8af265378bd2056cadChet Haase        ActivityInstrumentationTestCase2<TestActivity> {
40d7d27e9ebe5c7325e67e1a8af265378bd2056cadChet Haase
41d7d27e9ebe5c7325e67e1a8af265378bd2056cadChet Haase    private static final String TAG = "RecyclerViewTest";
42d7d27e9ebe5c7325e67e1a8af265378bd2056cadChet Haase
43b5f8b4fecb531aee6b359b4968409410aba90817Yigit Boyar    private boolean mDebug;
44b5f8b4fecb531aee6b359b4968409410aba90817Yigit Boyar
45b5f8b4fecb531aee6b359b4968409410aba90817Yigit Boyar    protected RecyclerView mRecyclerView;
46d7d27e9ebe5c7325e67e1a8af265378bd2056cadChet Haase
478ae76f91527ce850f155ce960fb9068bcd5d49f9Yigit Boyar    protected AdapterHelper mAdapterHelper;
488ae76f91527ce850f155ce960fb9068bcd5d49f9Yigit Boyar
497e9da1a2629b657d56e2f54abd18c6249f3be037Yigit Boyar    Throwable mainThreadException;
507e9da1a2629b657d56e2f54abd18c6249f3be037Yigit Boyar
51d7d27e9ebe5c7325e67e1a8af265378bd2056cadChet Haase    public BaseRecyclerViewInstrumentationTest() {
52b5f8b4fecb531aee6b359b4968409410aba90817Yigit Boyar        this(false);
53b5f8b4fecb531aee6b359b4968409410aba90817Yigit Boyar    }
54b5f8b4fecb531aee6b359b4968409410aba90817Yigit Boyar
55b5f8b4fecb531aee6b359b4968409410aba90817Yigit Boyar    public BaseRecyclerViewInstrumentationTest(boolean debug) {
568ae76f91527ce850f155ce960fb9068bcd5d49f9Yigit Boyar        super("android.support.v7.recyclerview", TestActivity.class);
57b5f8b4fecb531aee6b359b4968409410aba90817Yigit Boyar        mDebug = debug;
58b5f8b4fecb531aee6b359b4968409410aba90817Yigit Boyar    }
59b5f8b4fecb531aee6b359b4968409410aba90817Yigit Boyar
607e9da1a2629b657d56e2f54abd18c6249f3be037Yigit Boyar    void checkForMainThreadException() throws Throwable {
617e9da1a2629b657d56e2f54abd18c6249f3be037Yigit Boyar        if (mainThreadException != null) {
627e9da1a2629b657d56e2f54abd18c6249f3be037Yigit Boyar            throw mainThreadException;
637e9da1a2629b657d56e2f54abd18c6249f3be037Yigit Boyar        }
647e9da1a2629b657d56e2f54abd18c6249f3be037Yigit Boyar    }
657e9da1a2629b657d56e2f54abd18c6249f3be037Yigit Boyar
660b1059b711009601ff08354e2df6bfc45266e80aYigit Boyar    void setAdapter(final RecyclerView.Adapter adapter) throws Throwable {
670b1059b711009601ff08354e2df6bfc45266e80aYigit Boyar        runTestOnUiThread(new Runnable() {
680b1059b711009601ff08354e2df6bfc45266e80aYigit Boyar            @Override
690b1059b711009601ff08354e2df6bfc45266e80aYigit Boyar            public void run() {
700b1059b711009601ff08354e2df6bfc45266e80aYigit Boyar                mRecyclerView.setAdapter(adapter);
710b1059b711009601ff08354e2df6bfc45266e80aYigit Boyar            }
720b1059b711009601ff08354e2df6bfc45266e80aYigit Boyar        });
730b1059b711009601ff08354e2df6bfc45266e80aYigit Boyar    }
740b1059b711009601ff08354e2df6bfc45266e80aYigit Boyar
750b1059b711009601ff08354e2df6bfc45266e80aYigit Boyar    void swapAdapter(final RecyclerView.Adapter adapter,
760b1059b711009601ff08354e2df6bfc45266e80aYigit Boyar            final boolean removeAndRecycleExistingViews) throws Throwable {
770b1059b711009601ff08354e2df6bfc45266e80aYigit Boyar        runTestOnUiThread(new Runnable() {
780b1059b711009601ff08354e2df6bfc45266e80aYigit Boyar            @Override
790b1059b711009601ff08354e2df6bfc45266e80aYigit Boyar            public void run() {
80824a7db96feacf3b68f56993ed72712bc935153eYigit Boyar                try {
81824a7db96feacf3b68f56993ed72712bc935153eYigit Boyar                    mRecyclerView.swapAdapter(adapter, removeAndRecycleExistingViews);
82824a7db96feacf3b68f56993ed72712bc935153eYigit Boyar                } catch (Throwable t) {
83824a7db96feacf3b68f56993ed72712bc935153eYigit Boyar                    postExceptionToInstrumentation(t);
84824a7db96feacf3b68f56993ed72712bc935153eYigit Boyar                }
850b1059b711009601ff08354e2df6bfc45266e80aYigit Boyar            }
860b1059b711009601ff08354e2df6bfc45266e80aYigit Boyar        });
87824a7db96feacf3b68f56993ed72712bc935153eYigit Boyar        checkForMainThreadException();
880b1059b711009601ff08354e2df6bfc45266e80aYigit Boyar    }
890b1059b711009601ff08354e2df6bfc45266e80aYigit Boyar
907e9da1a2629b657d56e2f54abd18c6249f3be037Yigit Boyar    void postExceptionToInstrumentation(Throwable t) {
916e83751247c5be0211d7bffaf057431c03dfef38Yigit Boyar        if (mainThreadException != null) {
926e83751247c5be0211d7bffaf057431c03dfef38Yigit Boyar            Log.e(TAG, "receiving another main thread exception. dropping.", t);
936e83751247c5be0211d7bffaf057431c03dfef38Yigit Boyar        } else {
9490ca3086dbf66ad6bb8840e46ec8524a705e1c18Yigit Boyar            Log.e(TAG, "captured exception on main thread", t);
956e83751247c5be0211d7bffaf057431c03dfef38Yigit Boyar            mainThreadException = t;
966e83751247c5be0211d7bffaf057431c03dfef38Yigit Boyar        }
976e83751247c5be0211d7bffaf057431c03dfef38Yigit Boyar
987e9da1a2629b657d56e2f54abd18c6249f3be037Yigit Boyar        if (mRecyclerView != null && mRecyclerView
997e9da1a2629b657d56e2f54abd18c6249f3be037Yigit Boyar                .getLayoutManager() instanceof TestLayoutManager) {
1007e9da1a2629b657d56e2f54abd18c6249f3be037Yigit Boyar            TestLayoutManager lm = (TestLayoutManager) mRecyclerView.getLayoutManager();
1017e9da1a2629b657d56e2f54abd18c6249f3be037Yigit Boyar            // finish all layouts so that we get the correct exception
1027e9da1a2629b657d56e2f54abd18c6249f3be037Yigit Boyar            while (lm.layoutLatch.getCount() > 0) {
1037e9da1a2629b657d56e2f54abd18c6249f3be037Yigit Boyar                lm.layoutLatch.countDown();
1047e9da1a2629b657d56e2f54abd18c6249f3be037Yigit Boyar            }
1057e9da1a2629b657d56e2f54abd18c6249f3be037Yigit Boyar        }
1067e9da1a2629b657d56e2f54abd18c6249f3be037Yigit Boyar    }
1077e9da1a2629b657d56e2f54abd18c6249f3be037Yigit Boyar
1088ae76f91527ce850f155ce960fb9068bcd5d49f9Yigit Boyar    @Override
1098ae76f91527ce850f155ce960fb9068bcd5d49f9Yigit Boyar    protected void tearDown() throws Exception {
1108ae76f91527ce850f155ce960fb9068bcd5d49f9Yigit Boyar        if (mRecyclerView != null) {
1118ae76f91527ce850f155ce960fb9068bcd5d49f9Yigit Boyar            try {
1128ae76f91527ce850f155ce960fb9068bcd5d49f9Yigit Boyar                removeRecyclerView();
1138ae76f91527ce850f155ce960fb9068bcd5d49f9Yigit Boyar            } catch (Throwable throwable) {
1148ae76f91527ce850f155ce960fb9068bcd5d49f9Yigit Boyar                throwable.printStackTrace();
1158ae76f91527ce850f155ce960fb9068bcd5d49f9Yigit Boyar            }
1168ae76f91527ce850f155ce960fb9068bcd5d49f9Yigit Boyar        }
1178ae76f91527ce850f155ce960fb9068bcd5d49f9Yigit Boyar        getInstrumentation().waitForIdleSync();
118ca9ce7ccdccf6b6a2a81da73dd1e6425d90e4b23Yigit Boyar        super.tearDown();
119ca9ce7ccdccf6b6a2a81da73dd1e6425d90e4b23Yigit Boyar
1206e83751247c5be0211d7bffaf057431c03dfef38Yigit Boyar        try {
1216e83751247c5be0211d7bffaf057431c03dfef38Yigit Boyar            checkForMainThreadException();
1226e83751247c5be0211d7bffaf057431c03dfef38Yigit Boyar        } catch (Exception e) {
1236e83751247c5be0211d7bffaf057431c03dfef38Yigit Boyar            throw e;
1246e83751247c5be0211d7bffaf057431c03dfef38Yigit Boyar        } catch (Throwable throwable) {
125ca9ce7ccdccf6b6a2a81da73dd1e6425d90e4b23Yigit Boyar            throw new Exception(throwable);
1266e83751247c5be0211d7bffaf057431c03dfef38Yigit Boyar        }
1278ae76f91527ce850f155ce960fb9068bcd5d49f9Yigit Boyar    }
1288ae76f91527ce850f155ce960fb9068bcd5d49f9Yigit Boyar
129c50c4cad31d73e574b27bb3d7581542975e37263Yigit Boyar    public Rect getDecoratedRecyclerViewBounds() {
130c50c4cad31d73e574b27bb3d7581542975e37263Yigit Boyar        return new Rect(
131c50c4cad31d73e574b27bb3d7581542975e37263Yigit Boyar                mRecyclerView.getPaddingLeft(),
132c50c4cad31d73e574b27bb3d7581542975e37263Yigit Boyar                mRecyclerView.getPaddingTop(),
133c50c4cad31d73e574b27bb3d7581542975e37263Yigit Boyar                mRecyclerView.getPaddingLeft() + mRecyclerView.getWidth(),
134c50c4cad31d73e574b27bb3d7581542975e37263Yigit Boyar                mRecyclerView.getPaddingTop() + mRecyclerView.getHeight()
135c50c4cad31d73e574b27bb3d7581542975e37263Yigit Boyar        );
136c50c4cad31d73e574b27bb3d7581542975e37263Yigit Boyar    }
137c50c4cad31d73e574b27bb3d7581542975e37263Yigit Boyar
138b5f8b4fecb531aee6b359b4968409410aba90817Yigit Boyar    public void removeRecyclerView() throws Throwable {
139d7e2f2ab1d253133fa54f309e74a7ee384c33971Yigit Boyar        if (mRecyclerView == null) {
140d7e2f2ab1d253133fa54f309e74a7ee384c33971Yigit Boyar            return;
141d7e2f2ab1d253133fa54f309e74a7ee384c33971Yigit Boyar        }
14290ca3086dbf66ad6bb8840e46ec8524a705e1c18Yigit Boyar        if (!isMainThread()) {
14390ca3086dbf66ad6bb8840e46ec8524a705e1c18Yigit Boyar            getInstrumentation().waitForIdleSync();
14490ca3086dbf66ad6bb8840e46ec8524a705e1c18Yigit Boyar        }
145b5f8b4fecb531aee6b359b4968409410aba90817Yigit Boyar        runTestOnUiThread(new Runnable() {
146b5f8b4fecb531aee6b359b4968409410aba90817Yigit Boyar            @Override
147b5f8b4fecb531aee6b359b4968409410aba90817Yigit Boyar            public void run() {
14890ca3086dbf66ad6bb8840e46ec8524a705e1c18Yigit Boyar                final RecyclerView.Adapter adapter = mRecyclerView.getAdapter();
14990ca3086dbf66ad6bb8840e46ec8524a705e1c18Yigit Boyar                if (adapter instanceof AttachDetachCountingAdapter) {
15090ca3086dbf66ad6bb8840e46ec8524a705e1c18Yigit Boyar                    ((AttachDetachCountingAdapter) adapter).getCounter()
15190ca3086dbf66ad6bb8840e46ec8524a705e1c18Yigit Boyar                            .validateRemaining(mRecyclerView);
15290ca3086dbf66ad6bb8840e46ec8524a705e1c18Yigit Boyar                }
153b5f8b4fecb531aee6b359b4968409410aba90817Yigit Boyar                getActivity().mContainer.removeAllViews();
154b5f8b4fecb531aee6b359b4968409410aba90817Yigit Boyar            }
155b5f8b4fecb531aee6b359b4968409410aba90817Yigit Boyar        });
15690ca3086dbf66ad6bb8840e46ec8524a705e1c18Yigit Boyar        mRecyclerView = null;
157b5f8b4fecb531aee6b359b4968409410aba90817Yigit Boyar    }
158b5f8b4fecb531aee6b359b4968409410aba90817Yigit Boyar
159504c54ea52c1b2aae6f8f4ae128f1dcaac7e3f6aYigit Boyar    void waitForAnimations(int seconds) throws InterruptedException {
160504c54ea52c1b2aae6f8f4ae128f1dcaac7e3f6aYigit Boyar        final CountDownLatch latch = new CountDownLatch(2);
161504c54ea52c1b2aae6f8f4ae128f1dcaac7e3f6aYigit Boyar        boolean running = mRecyclerView.mItemAnimator
162504c54ea52c1b2aae6f8f4ae128f1dcaac7e3f6aYigit Boyar                .isRunning(new RecyclerView.ItemAnimator.ItemAnimatorFinishedListener() {
163504c54ea52c1b2aae6f8f4ae128f1dcaac7e3f6aYigit Boyar                    @Override
164504c54ea52c1b2aae6f8f4ae128f1dcaac7e3f6aYigit Boyar                    public void onAnimationsFinished() {
165504c54ea52c1b2aae6f8f4ae128f1dcaac7e3f6aYigit Boyar                        latch.countDown();
166504c54ea52c1b2aae6f8f4ae128f1dcaac7e3f6aYigit Boyar                    }
167504c54ea52c1b2aae6f8f4ae128f1dcaac7e3f6aYigit Boyar                });
168504c54ea52c1b2aae6f8f4ae128f1dcaac7e3f6aYigit Boyar        if (running) {
169504c54ea52c1b2aae6f8f4ae128f1dcaac7e3f6aYigit Boyar            latch.await(seconds, TimeUnit.SECONDS);
170504c54ea52c1b2aae6f8f4ae128f1dcaac7e3f6aYigit Boyar        }
171504c54ea52c1b2aae6f8f4ae128f1dcaac7e3f6aYigit Boyar    }
172504c54ea52c1b2aae6f8f4ae128f1dcaac7e3f6aYigit Boyar
173b5f8b4fecb531aee6b359b4968409410aba90817Yigit Boyar    public void setRecyclerView(final RecyclerView recyclerView) throws Throwable {
1740b1059b711009601ff08354e2df6bfc45266e80aYigit Boyar        setRecyclerView(recyclerView, true);
1750b1059b711009601ff08354e2df6bfc45266e80aYigit Boyar    }
1760b1059b711009601ff08354e2df6bfc45266e80aYigit Boyar    public void setRecyclerView(final RecyclerView recyclerView, boolean assignDummyPool)
1770b1059b711009601ff08354e2df6bfc45266e80aYigit Boyar            throws Throwable {
178b5f8b4fecb531aee6b359b4968409410aba90817Yigit Boyar        mRecyclerView = recyclerView;
1790b1059b711009601ff08354e2df6bfc45266e80aYigit Boyar        if (assignDummyPool) {
1800b1059b711009601ff08354e2df6bfc45266e80aYigit Boyar            RecyclerView.RecycledViewPool pool = new RecyclerView.RecycledViewPool() {
1810b1059b711009601ff08354e2df6bfc45266e80aYigit Boyar                @Override
1820b1059b711009601ff08354e2df6bfc45266e80aYigit Boyar                public RecyclerView.ViewHolder getRecycledView(int viewType) {
1830b1059b711009601ff08354e2df6bfc45266e80aYigit Boyar                    RecyclerView.ViewHolder viewHolder = super.getRecycledView(viewType);
1840b1059b711009601ff08354e2df6bfc45266e80aYigit Boyar                    if (viewHolder == null) {
1850b1059b711009601ff08354e2df6bfc45266e80aYigit Boyar                        return null;
1860b1059b711009601ff08354e2df6bfc45266e80aYigit Boyar                    }
1870b1059b711009601ff08354e2df6bfc45266e80aYigit Boyar                    viewHolder.addFlags(RecyclerView.ViewHolder.FLAG_BOUND);
1880b1059b711009601ff08354e2df6bfc45266e80aYigit Boyar                    viewHolder.mPosition = 200;
1890b1059b711009601ff08354e2df6bfc45266e80aYigit Boyar                    viewHolder.mOldPosition = 300;
1900b1059b711009601ff08354e2df6bfc45266e80aYigit Boyar                    viewHolder.mPreLayoutPosition = 500;
1910b1059b711009601ff08354e2df6bfc45266e80aYigit Boyar                    return viewHolder;
1928da5bba529a2fe9bd2aafab28010e11ec4de93c7Yigit Boyar                }
1938da5bba529a2fe9bd2aafab28010e11ec4de93c7Yigit Boyar
1940b1059b711009601ff08354e2df6bfc45266e80aYigit Boyar                @Override
1950b1059b711009601ff08354e2df6bfc45266e80aYigit Boyar                public void putRecycledView(RecyclerView.ViewHolder scrap) {
1960b1059b711009601ff08354e2df6bfc45266e80aYigit Boyar                    super.putRecycledView(scrap);
1970b1059b711009601ff08354e2df6bfc45266e80aYigit Boyar                }
1980b1059b711009601ff08354e2df6bfc45266e80aYigit Boyar            };
1990b1059b711009601ff08354e2df6bfc45266e80aYigit Boyar            mRecyclerView.setRecycledViewPool(pool);
2000b1059b711009601ff08354e2df6bfc45266e80aYigit Boyar        }
2018ae76f91527ce850f155ce960fb9068bcd5d49f9Yigit Boyar        mAdapterHelper = recyclerView.mAdapterHelper;
202b5f8b4fecb531aee6b359b4968409410aba90817Yigit Boyar        runTestOnUiThread(new Runnable() {
203b5f8b4fecb531aee6b359b4968409410aba90817Yigit Boyar            @Override
204b5f8b4fecb531aee6b359b4968409410aba90817Yigit Boyar            public void run() {
205b5f8b4fecb531aee6b359b4968409410aba90817Yigit Boyar                getActivity().mContainer.addView(recyclerView);
206b5f8b4fecb531aee6b359b4968409410aba90817Yigit Boyar            }
207b5f8b4fecb531aee6b359b4968409410aba90817Yigit Boyar        });
208b5f8b4fecb531aee6b359b4968409410aba90817Yigit Boyar    }
209b5f8b4fecb531aee6b359b4968409410aba90817Yigit Boyar
210310e95e1c6dfe4f26ef594233e65e1ff83e0f1ffYigit Boyar    protected FrameLayout getRecyclerViewContainer() {
211310e95e1c6dfe4f26ef594233e65e1ff83e0f1ffYigit Boyar        return getActivity().mContainer;
212310e95e1c6dfe4f26ef594233e65e1ff83e0f1ffYigit Boyar    }
213310e95e1c6dfe4f26ef594233e65e1ff83e0f1ffYigit Boyar
214b5f8b4fecb531aee6b359b4968409410aba90817Yigit Boyar    public void requestLayoutOnUIThread(final View view) throws Throwable {
215b5f8b4fecb531aee6b359b4968409410aba90817Yigit Boyar        runTestOnUiThread(new Runnable() {
216b5f8b4fecb531aee6b359b4968409410aba90817Yigit Boyar            @Override
217b5f8b4fecb531aee6b359b4968409410aba90817Yigit Boyar            public void run() {
218b5f8b4fecb531aee6b359b4968409410aba90817Yigit Boyar                view.requestLayout();
219b5f8b4fecb531aee6b359b4968409410aba90817Yigit Boyar            }
220b5f8b4fecb531aee6b359b4968409410aba90817Yigit Boyar        });
221b5f8b4fecb531aee6b359b4968409410aba90817Yigit Boyar    }
222b5f8b4fecb531aee6b359b4968409410aba90817Yigit Boyar
223b5f8b4fecb531aee6b359b4968409410aba90817Yigit Boyar    public void scrollBy(final int dt) throws Throwable {
224b5f8b4fecb531aee6b359b4968409410aba90817Yigit Boyar        runTestOnUiThread(new Runnable() {
225b5f8b4fecb531aee6b359b4968409410aba90817Yigit Boyar            @Override
226b5f8b4fecb531aee6b359b4968409410aba90817Yigit Boyar            public void run() {
227b5f8b4fecb531aee6b359b4968409410aba90817Yigit Boyar                if (mRecyclerView.getLayoutManager().canScrollHorizontally()) {
228b5f8b4fecb531aee6b359b4968409410aba90817Yigit Boyar                    mRecyclerView.scrollBy(dt, 0);
229b5f8b4fecb531aee6b359b4968409410aba90817Yigit Boyar                } else {
230b5f8b4fecb531aee6b359b4968409410aba90817Yigit Boyar                    mRecyclerView.scrollBy(0, dt);
231b5f8b4fecb531aee6b359b4968409410aba90817Yigit Boyar                }
232b5f8b4fecb531aee6b359b4968409410aba90817Yigit Boyar
233b5f8b4fecb531aee6b359b4968409410aba90817Yigit Boyar            }
234b5f8b4fecb531aee6b359b4968409410aba90817Yigit Boyar        });
235d7d27e9ebe5c7325e67e1a8af265378bd2056cadChet Haase    }
236d7d27e9ebe5c7325e67e1a8af265378bd2056cadChet Haase
2378edcb0bdeaba6931f9d8154f0c81f57da7ddab2aYigit Boyar    void scrollToPosition(final int position) throws Throwable {
2388edcb0bdeaba6931f9d8154f0c81f57da7ddab2aYigit Boyar        runTestOnUiThread(new Runnable() {
2398edcb0bdeaba6931f9d8154f0c81f57da7ddab2aYigit Boyar            @Override
2408edcb0bdeaba6931f9d8154f0c81f57da7ddab2aYigit Boyar            public void run() {
2418edcb0bdeaba6931f9d8154f0c81f57da7ddab2aYigit Boyar                mRecyclerView.getLayoutManager().scrollToPosition(position);
2428edcb0bdeaba6931f9d8154f0c81f57da7ddab2aYigit Boyar            }
2438edcb0bdeaba6931f9d8154f0c81f57da7ddab2aYigit Boyar        });
2448edcb0bdeaba6931f9d8154f0c81f57da7ddab2aYigit Boyar    }
2458edcb0bdeaba6931f9d8154f0c81f57da7ddab2aYigit Boyar
2468edcb0bdeaba6931f9d8154f0c81f57da7ddab2aYigit Boyar    void smoothScrollToPosition(final int position)
2478edcb0bdeaba6931f9d8154f0c81f57da7ddab2aYigit Boyar            throws Throwable {
2488edcb0bdeaba6931f9d8154f0c81f57da7ddab2aYigit Boyar        Log.d(TAG, "SMOOTH scrolling to " + position);
2498edcb0bdeaba6931f9d8154f0c81f57da7ddab2aYigit Boyar        runTestOnUiThread(new Runnable() {
2508edcb0bdeaba6931f9d8154f0c81f57da7ddab2aYigit Boyar            @Override
2518edcb0bdeaba6931f9d8154f0c81f57da7ddab2aYigit Boyar            public void run() {
2528edcb0bdeaba6931f9d8154f0c81f57da7ddab2aYigit Boyar                mRecyclerView.smoothScrollToPosition(position);
2538edcb0bdeaba6931f9d8154f0c81f57da7ddab2aYigit Boyar            }
2548edcb0bdeaba6931f9d8154f0c81f57da7ddab2aYigit Boyar        });
2558edcb0bdeaba6931f9d8154f0c81f57da7ddab2aYigit Boyar        while (mRecyclerView.getLayoutManager().isSmoothScrolling() ||
2568edcb0bdeaba6931f9d8154f0c81f57da7ddab2aYigit Boyar                mRecyclerView.getScrollState() != RecyclerView.SCROLL_STATE_IDLE) {
2578edcb0bdeaba6931f9d8154f0c81f57da7ddab2aYigit Boyar            if (mDebug) {
2588edcb0bdeaba6931f9d8154f0c81f57da7ddab2aYigit Boyar                Log.d(TAG, "SMOOTH scrolling step");
2598edcb0bdeaba6931f9d8154f0c81f57da7ddab2aYigit Boyar            }
2608edcb0bdeaba6931f9d8154f0c81f57da7ddab2aYigit Boyar            Thread.sleep(200);
2618edcb0bdeaba6931f9d8154f0c81f57da7ddab2aYigit Boyar        }
2628edcb0bdeaba6931f9d8154f0c81f57da7ddab2aYigit Boyar        Log.d(TAG, "SMOOTH scrolling done");
263504c54ea52c1b2aae6f8f4ae128f1dcaac7e3f6aYigit Boyar        getInstrumentation().waitForIdleSync();
2648edcb0bdeaba6931f9d8154f0c81f57da7ddab2aYigit Boyar    }
2658edcb0bdeaba6931f9d8154f0c81f57da7ddab2aYigit Boyar
266d7d27e9ebe5c7325e67e1a8af265378bd2056cadChet Haase    class TestViewHolder extends RecyclerView.ViewHolder {
267d7d27e9ebe5c7325e67e1a8af265378bd2056cadChet Haase
268d7d27e9ebe5c7325e67e1a8af265378bd2056cadChet Haase        Item mBindedItem;
269d7d27e9ebe5c7325e67e1a8af265378bd2056cadChet Haase
270d7d27e9ebe5c7325e67e1a8af265378bd2056cadChet Haase        public TestViewHolder(View itemView) {
271d7d27e9ebe5c7325e67e1a8af265378bd2056cadChet Haase            super(itemView);
272310e95e1c6dfe4f26ef594233e65e1ff83e0f1ffYigit Boyar            itemView.setFocusable(true);
273d7d27e9ebe5c7325e67e1a8af265378bd2056cadChet Haase        }
274504c54ea52c1b2aae6f8f4ae128f1dcaac7e3f6aYigit Boyar
275504c54ea52c1b2aae6f8f4ae128f1dcaac7e3f6aYigit Boyar        @Override
276504c54ea52c1b2aae6f8f4ae128f1dcaac7e3f6aYigit Boyar        public String toString() {
277504c54ea52c1b2aae6f8f4ae128f1dcaac7e3f6aYigit Boyar            return super.toString() + " item:" + mBindedItem;
278504c54ea52c1b2aae6f8f4ae128f1dcaac7e3f6aYigit Boyar        }
279d7d27e9ebe5c7325e67e1a8af265378bd2056cadChet Haase    }
280d7d27e9ebe5c7325e67e1a8af265378bd2056cadChet Haase
281d7d27e9ebe5c7325e67e1a8af265378bd2056cadChet Haase    class TestLayoutManager extends RecyclerView.LayoutManager {
282d7d27e9ebe5c7325e67e1a8af265378bd2056cadChet Haase
283d7d27e9ebe5c7325e67e1a8af265378bd2056cadChet Haase        CountDownLatch layoutLatch;
284d7d27e9ebe5c7325e67e1a8af265378bd2056cadChet Haase
285d7d27e9ebe5c7325e67e1a8af265378bd2056cadChet Haase        public void expectLayouts(int count) {
286d7d27e9ebe5c7325e67e1a8af265378bd2056cadChet Haase            layoutLatch = new CountDownLatch(count);
287d7d27e9ebe5c7325e67e1a8af265378bd2056cadChet Haase        }
288d7d27e9ebe5c7325e67e1a8af265378bd2056cadChet Haase
289b5f8b4fecb531aee6b359b4968409410aba90817Yigit Boyar        public void waitForLayout(long timeout, TimeUnit timeUnit) throws Throwable {
290b5f8b4fecb531aee6b359b4968409410aba90817Yigit Boyar            layoutLatch.await(timeout * (mDebug ? 100 : 1), timeUnit);
291d7d27e9ebe5c7325e67e1a8af265378bd2056cadChet Haase            assertEquals("all expected layouts should be executed at the expected time",
292d7d27e9ebe5c7325e67e1a8af265378bd2056cadChet Haase                    0, layoutLatch.getCount());
293e0c347f627f8a78d3e5e3e5eaac9c3ae26208689Yigit Boyar            getInstrumentation().waitForIdleSync();
294d7d27e9ebe5c7325e67e1a8af265378bd2056cadChet Haase        }
295d7d27e9ebe5c7325e67e1a8af265378bd2056cadChet Haase
296b5f8b4fecb531aee6b359b4968409410aba90817Yigit Boyar        public void assertLayoutCount(int count, String msg, long timeout) throws Throwable {
297b5f8b4fecb531aee6b359b4968409410aba90817Yigit Boyar            layoutLatch.await(timeout, TimeUnit.SECONDS);
298b5f8b4fecb531aee6b359b4968409410aba90817Yigit Boyar            assertEquals(msg, count, layoutLatch.getCount());
299b5f8b4fecb531aee6b359b4968409410aba90817Yigit Boyar        }
300b5f8b4fecb531aee6b359b4968409410aba90817Yigit Boyar
301b5f8b4fecb531aee6b359b4968409410aba90817Yigit Boyar        public void assertNoLayout(String msg, long timeout) throws Throwable {
302b5f8b4fecb531aee6b359b4968409410aba90817Yigit Boyar            layoutLatch.await(timeout, TimeUnit.SECONDS);
303b5f8b4fecb531aee6b359b4968409410aba90817Yigit Boyar            assertFalse(msg, layoutLatch.getCount() == 0);
304b5f8b4fecb531aee6b359b4968409410aba90817Yigit Boyar        }
305b5f8b4fecb531aee6b359b4968409410aba90817Yigit Boyar
306b5f8b4fecb531aee6b359b4968409410aba90817Yigit Boyar        public void waitForLayout(long timeout) throws Throwable {
3078ae76f91527ce850f155ce960fb9068bcd5d49f9Yigit Boyar            waitForLayout(timeout * (mDebug ? 10000 : 1), TimeUnit.SECONDS);
308b5f8b4fecb531aee6b359b4968409410aba90817Yigit Boyar        }
309b5f8b4fecb531aee6b359b4968409410aba90817Yigit Boyar
310d7d27e9ebe5c7325e67e1a8af265378bd2056cadChet Haase        @Override
311d7d27e9ebe5c7325e67e1a8af265378bd2056cadChet Haase        public RecyclerView.LayoutParams generateDefaultLayoutParams() {
312d7d27e9ebe5c7325e67e1a8af265378bd2056cadChet Haase            return new RecyclerView.LayoutParams(ViewGroup.LayoutParams.WRAP_CONTENT,
313d7d27e9ebe5c7325e67e1a8af265378bd2056cadChet Haase                    ViewGroup.LayoutParams.WRAP_CONTENT);
314d7d27e9ebe5c7325e67e1a8af265378bd2056cadChet Haase        }
315d7d27e9ebe5c7325e67e1a8af265378bd2056cadChet Haase
316d7d27e9ebe5c7325e67e1a8af265378bd2056cadChet Haase        void assertVisibleItemPositions() {
317d7d27e9ebe5c7325e67e1a8af265378bd2056cadChet Haase            int i = getChildCount();
318d7d27e9ebe5c7325e67e1a8af265378bd2056cadChet Haase            TestAdapter testAdapter = (TestAdapter) mRecyclerView.getAdapter();
319d7d27e9ebe5c7325e67e1a8af265378bd2056cadChet Haase            while (i-- > 0) {
320d7d27e9ebe5c7325e67e1a8af265378bd2056cadChet Haase                View view = getChildAt(i);
321d7d27e9ebe5c7325e67e1a8af265378bd2056cadChet Haase                RecyclerView.LayoutParams lp = (RecyclerView.LayoutParams) view.getLayoutParams();
322d7d27e9ebe5c7325e67e1a8af265378bd2056cadChet Haase                Item item = ((TestViewHolder) lp.mViewHolder).mBindedItem;
323b5f8b4fecb531aee6b359b4968409410aba90817Yigit Boyar                if (mDebug) {
324d7d27e9ebe5c7325e67e1a8af265378bd2056cadChet Haase                    Log.d(TAG, "testing item " + i);
325d7d27e9ebe5c7325e67e1a8af265378bd2056cadChet Haase                }
3268ae76f91527ce850f155ce960fb9068bcd5d49f9Yigit Boyar                if (!lp.isItemRemoved()) {
3278ae76f91527ce850f155ce960fb9068bcd5d49f9Yigit Boyar                    RecyclerView.ViewHolder vh = mRecyclerView.getChildViewHolder(view);
3288ae76f91527ce850f155ce960fb9068bcd5d49f9Yigit Boyar                    assertSame("item position in LP should match adapter value :" + vh,
3298ae76f91527ce850f155ce960fb9068bcd5d49f9Yigit Boyar                            testAdapter.mItems.get(vh.mPosition), item);
3308ae76f91527ce850f155ce960fb9068bcd5d49f9Yigit Boyar                }
331d7d27e9ebe5c7325e67e1a8af265378bd2056cadChet Haase            }
332d7d27e9ebe5c7325e67e1a8af265378bd2056cadChet Haase        }
333d7d27e9ebe5c7325e67e1a8af265378bd2056cadChet Haase
334b5f8b4fecb531aee6b359b4968409410aba90817Yigit Boyar        RecyclerView.LayoutParams getLp(View v) {
335b5f8b4fecb531aee6b359b4968409410aba90817Yigit Boyar            return (RecyclerView.LayoutParams) v.getLayoutParams();
336b5f8b4fecb531aee6b359b4968409410aba90817Yigit Boyar        }
337b5f8b4fecb531aee6b359b4968409410aba90817Yigit Boyar
338f485be9def4c0f72cfdfa6b0c616c23f04652817Yigit Boyar        void layoutRange(RecyclerView.Recycler recycler, int start, int end) {
339fe3996cf5af65fe11cc3ed47b67123ec267bd336Yigit Boyar            assertScrap(recycler);
340b5f8b4fecb531aee6b359b4968409410aba90817Yigit Boyar            if (mDebug) {
341d7d27e9ebe5c7325e67e1a8af265378bd2056cadChet Haase                Log.d(TAG, "will layout items from " + start + " to " + end);
342d7d27e9ebe5c7325e67e1a8af265378bd2056cadChet Haase            }
3435ced882cabdcefbb469e332f6f73983999aad0e5Yigit Boyar            int diff = end > start ? 1 : -1;
344504c54ea52c1b2aae6f8f4ae128f1dcaac7e3f6aYigit Boyar            int top = 0;
3455ced882cabdcefbb469e332f6f73983999aad0e5Yigit Boyar            for (int i = start; i != end; i+=diff) {
346b5f8b4fecb531aee6b359b4968409410aba90817Yigit Boyar                if (mDebug) {
347d7d27e9ebe5c7325e67e1a8af265378bd2056cadChet Haase                    Log.d(TAG, "laying out item " + i);
348d7d27e9ebe5c7325e67e1a8af265378bd2056cadChet Haase                }
349d7d27e9ebe5c7325e67e1a8af265378bd2056cadChet Haase                View view = recycler.getViewForPosition(i);
350b5f8b4fecb531aee6b359b4968409410aba90817Yigit Boyar                assertNotNull("view should not be null for valid position. "
351b5f8b4fecb531aee6b359b4968409410aba90817Yigit Boyar                        + "got null view at position " + i, view);
352c8b0ff5869e17a656a3a1d7fb306efa5b99fa07fYigit Boyar                if (!mRecyclerView.mState.isPreLayout()) {
353c8b0ff5869e17a656a3a1d7fb306efa5b99fa07fYigit Boyar                    RecyclerView.LayoutParams layoutParams = (RecyclerView.LayoutParams) view
354c8b0ff5869e17a656a3a1d7fb306efa5b99fa07fYigit Boyar                            .getLayoutParams();
355c8b0ff5869e17a656a3a1d7fb306efa5b99fa07fYigit Boyar                    assertFalse("In post layout, getViewForPosition should never return a view "
356c8b0ff5869e17a656a3a1d7fb306efa5b99fa07fYigit Boyar                            + "that is removed", layoutParams != null
357c8b0ff5869e17a656a3a1d7fb306efa5b99fa07fYigit Boyar                            && layoutParams.isItemRemoved());
358c8b0ff5869e17a656a3a1d7fb306efa5b99fa07fYigit Boyar
359b5f8b4fecb531aee6b359b4968409410aba90817Yigit Boyar                }
3608da5bba529a2fe9bd2aafab28010e11ec4de93c7Yigit Boyar                assertEquals("getViewForPosition should return correct position",
3618da5bba529a2fe9bd2aafab28010e11ec4de93c7Yigit Boyar                        i, getPosition(view));
362c8b0ff5869e17a656a3a1d7fb306efa5b99fa07fYigit Boyar                addView(view);
363b5f8b4fecb531aee6b359b4968409410aba90817Yigit Boyar
364d7d27e9ebe5c7325e67e1a8af265378bd2056cadChet Haase                measureChildWithMargins(view, 0, 0);
365504c54ea52c1b2aae6f8f4ae128f1dcaac7e3f6aYigit Boyar                layoutDecorated(view, 0, top, getDecoratedMeasuredWidth(view)
366504c54ea52c1b2aae6f8f4ae128f1dcaac7e3f6aYigit Boyar                        , top + getDecoratedMeasuredHeight(view));
367504c54ea52c1b2aae6f8f4ae128f1dcaac7e3f6aYigit Boyar                top += view.getMeasuredHeight();
368d7d27e9ebe5c7325e67e1a8af265378bd2056cadChet Haase            }
369d7d27e9ebe5c7325e67e1a8af265378bd2056cadChet Haase        }
370fe3996cf5af65fe11cc3ed47b67123ec267bd336Yigit Boyar
371fe3996cf5af65fe11cc3ed47b67123ec267bd336Yigit Boyar        private void assertScrap(RecyclerView.Recycler recycler) {
372e0c347f627f8a78d3e5e3e5eaac9c3ae26208689Yigit Boyar            if (mRecyclerView.getAdapter() != null &&
373e0c347f627f8a78d3e5e3e5eaac9c3ae26208689Yigit Boyar                    !mRecyclerView.getAdapter().hasStableIds()) {
374e0c347f627f8a78d3e5e3e5eaac9c3ae26208689Yigit Boyar                for (RecyclerView.ViewHolder viewHolder : recycler.getScrapList()) {
375e0c347f627f8a78d3e5e3e5eaac9c3ae26208689Yigit Boyar                    assertFalse("Invalid scrap should be no kept", viewHolder.isInvalid());
376e0c347f627f8a78d3e5e3e5eaac9c3ae26208689Yigit Boyar                }
377fe3996cf5af65fe11cc3ed47b67123ec267bd336Yigit Boyar            }
378fe3996cf5af65fe11cc3ed47b67123ec267bd336Yigit Boyar        }
379bb6ebab3b3b85ec20b3bc262289a2460705322f5Yigit Boyar
380bb6ebab3b3b85ec20b3bc262289a2460705322f5Yigit Boyar        @Override
381bb6ebab3b3b85ec20b3bc262289a2460705322f5Yigit Boyar        public boolean canScrollHorizontally() {
382bb6ebab3b3b85ec20b3bc262289a2460705322f5Yigit Boyar            return true;
383bb6ebab3b3b85ec20b3bc262289a2460705322f5Yigit Boyar        }
384bb6ebab3b3b85ec20b3bc262289a2460705322f5Yigit Boyar
385bb6ebab3b3b85ec20b3bc262289a2460705322f5Yigit Boyar        @Override
386bb6ebab3b3b85ec20b3bc262289a2460705322f5Yigit Boyar        public boolean canScrollVertically() {
387bb6ebab3b3b85ec20b3bc262289a2460705322f5Yigit Boyar            return true;
388bb6ebab3b3b85ec20b3bc262289a2460705322f5Yigit Boyar        }
389bb6ebab3b3b85ec20b3bc262289a2460705322f5Yigit Boyar
390bb6ebab3b3b85ec20b3bc262289a2460705322f5Yigit Boyar        @Override
391bb6ebab3b3b85ec20b3bc262289a2460705322f5Yigit Boyar        public int scrollHorizontallyBy(int dx, RecyclerView.Recycler recycler,
392bb6ebab3b3b85ec20b3bc262289a2460705322f5Yigit Boyar                RecyclerView.State state) {
393bb6ebab3b3b85ec20b3bc262289a2460705322f5Yigit Boyar            return dx;
394bb6ebab3b3b85ec20b3bc262289a2460705322f5Yigit Boyar        }
395bb6ebab3b3b85ec20b3bc262289a2460705322f5Yigit Boyar
396bb6ebab3b3b85ec20b3bc262289a2460705322f5Yigit Boyar        @Override
397bb6ebab3b3b85ec20b3bc262289a2460705322f5Yigit Boyar        public int scrollVerticallyBy(int dy, RecyclerView.Recycler recycler,
398bb6ebab3b3b85ec20b3bc262289a2460705322f5Yigit Boyar                RecyclerView.State state) {
399bb6ebab3b3b85ec20b3bc262289a2460705322f5Yigit Boyar            return dy;
400bb6ebab3b3b85ec20b3bc262289a2460705322f5Yigit Boyar        }
401d7d27e9ebe5c7325e67e1a8af265378bd2056cadChet Haase    }
402d7d27e9ebe5c7325e67e1a8af265378bd2056cadChet Haase
403d7d27e9ebe5c7325e67e1a8af265378bd2056cadChet Haase    static class Item {
40411b7cfea15306c0d6e4ed7da09f7698df01695dfYigit Boyar        final static AtomicInteger idCounter = new AtomicInteger(0);
40511b7cfea15306c0d6e4ed7da09f7698df01695dfYigit Boyar        final public int mId = idCounter.incrementAndGet();
406d7d27e9ebe5c7325e67e1a8af265378bd2056cadChet Haase
4072d2e8d88103866b631eb0f3805da137ebcfb0275Yigit Boyar        int mAdapterIndex;
408d7d27e9ebe5c7325e67e1a8af265378bd2056cadChet Haase
4092d2e8d88103866b631eb0f3805da137ebcfb0275Yigit Boyar        final String mText;
410d7d27e9ebe5c7325e67e1a8af265378bd2056cadChet Haase
4112d2e8d88103866b631eb0f3805da137ebcfb0275Yigit Boyar        Item(int adapterIndex, String text) {
4122d2e8d88103866b631eb0f3805da137ebcfb0275Yigit Boyar            mAdapterIndex = adapterIndex;
4132d2e8d88103866b631eb0f3805da137ebcfb0275Yigit Boyar            mText = text;
414d7d27e9ebe5c7325e67e1a8af265378bd2056cadChet Haase        }
4158ae76f91527ce850f155ce960fb9068bcd5d49f9Yigit Boyar
4168ae76f91527ce850f155ce960fb9068bcd5d49f9Yigit Boyar        @Override
4178ae76f91527ce850f155ce960fb9068bcd5d49f9Yigit Boyar        public String toString() {
4188ae76f91527ce850f155ce960fb9068bcd5d49f9Yigit Boyar            return "Item{" +
4198ae76f91527ce850f155ce960fb9068bcd5d49f9Yigit Boyar                    "mId=" + mId +
4202d2e8d88103866b631eb0f3805da137ebcfb0275Yigit Boyar                    ", originalIndex=" + mAdapterIndex +
4212d2e8d88103866b631eb0f3805da137ebcfb0275Yigit Boyar                    ", text='" + mText + '\'' +
4228ae76f91527ce850f155ce960fb9068bcd5d49f9Yigit Boyar                    '}';
4238ae76f91527ce850f155ce960fb9068bcd5d49f9Yigit Boyar        }
424d7d27e9ebe5c7325e67e1a8af265378bd2056cadChet Haase    }
425d7d27e9ebe5c7325e67e1a8af265378bd2056cadChet Haase
42690ca3086dbf66ad6bb8840e46ec8524a705e1c18Yigit Boyar    class TestAdapter extends RecyclerView.Adapter<TestViewHolder>
42790ca3086dbf66ad6bb8840e46ec8524a705e1c18Yigit Boyar            implements AttachDetachCountingAdapter {
428d7d27e9ebe5c7325e67e1a8af265378bd2056cadChet Haase
42990ca3086dbf66ad6bb8840e46ec8524a705e1c18Yigit Boyar        ViewAttachDetachCounter mAttachmentCounter = new ViewAttachDetachCounter();
430d7d27e9ebe5c7325e67e1a8af265378bd2056cadChet Haase        List<Item> mItems;
431d7d27e9ebe5c7325e67e1a8af265378bd2056cadChet Haase
432d7d27e9ebe5c7325e67e1a8af265378bd2056cadChet Haase        TestAdapter(int count) {
433d7d27e9ebe5c7325e67e1a8af265378bd2056cadChet Haase            mItems = new ArrayList<Item>(count);
434d7d27e9ebe5c7325e67e1a8af265378bd2056cadChet Haase            for (int i = 0; i < count; i++) {
435d7d27e9ebe5c7325e67e1a8af265378bd2056cadChet Haase                mItems.add(new Item(i, "Item " + i));
436d7d27e9ebe5c7325e67e1a8af265378bd2056cadChet Haase            }
437d7d27e9ebe5c7325e67e1a8af265378bd2056cadChet Haase        }
438d7d27e9ebe5c7325e67e1a8af265378bd2056cadChet Haase
439d7d27e9ebe5c7325e67e1a8af265378bd2056cadChet Haase        @Override
44090ca3086dbf66ad6bb8840e46ec8524a705e1c18Yigit Boyar        public void onViewAttachedToWindow(TestViewHolder holder) {
44190ca3086dbf66ad6bb8840e46ec8524a705e1c18Yigit Boyar            super.onViewAttachedToWindow(holder);
44290ca3086dbf66ad6bb8840e46ec8524a705e1c18Yigit Boyar            mAttachmentCounter.onViewAttached(holder);
44390ca3086dbf66ad6bb8840e46ec8524a705e1c18Yigit Boyar        }
44490ca3086dbf66ad6bb8840e46ec8524a705e1c18Yigit Boyar
44590ca3086dbf66ad6bb8840e46ec8524a705e1c18Yigit Boyar        @Override
44690ca3086dbf66ad6bb8840e46ec8524a705e1c18Yigit Boyar        public void onViewDetachedFromWindow(TestViewHolder holder) {
44790ca3086dbf66ad6bb8840e46ec8524a705e1c18Yigit Boyar            super.onViewDetachedFromWindow(holder);
44890ca3086dbf66ad6bb8840e46ec8524a705e1c18Yigit Boyar            mAttachmentCounter.onViewDetached(holder);
44990ca3086dbf66ad6bb8840e46ec8524a705e1c18Yigit Boyar        }
45090ca3086dbf66ad6bb8840e46ec8524a705e1c18Yigit Boyar
45190ca3086dbf66ad6bb8840e46ec8524a705e1c18Yigit Boyar        @Override
45290ca3086dbf66ad6bb8840e46ec8524a705e1c18Yigit Boyar        public void onAttachedToRecyclerView(RecyclerView recyclerView) {
45390ca3086dbf66ad6bb8840e46ec8524a705e1c18Yigit Boyar            super.onAttachedToRecyclerView(recyclerView);
45490ca3086dbf66ad6bb8840e46ec8524a705e1c18Yigit Boyar            mAttachmentCounter.onAttached(recyclerView);
45590ca3086dbf66ad6bb8840e46ec8524a705e1c18Yigit Boyar        }
45690ca3086dbf66ad6bb8840e46ec8524a705e1c18Yigit Boyar
45790ca3086dbf66ad6bb8840e46ec8524a705e1c18Yigit Boyar        @Override
45890ca3086dbf66ad6bb8840e46ec8524a705e1c18Yigit Boyar        public void onDetachedFromRecyclerView(RecyclerView recyclerView) {
45990ca3086dbf66ad6bb8840e46ec8524a705e1c18Yigit Boyar            super.onDetachedFromRecyclerView(recyclerView);
46090ca3086dbf66ad6bb8840e46ec8524a705e1c18Yigit Boyar            mAttachmentCounter.onDetached(recyclerView);
46190ca3086dbf66ad6bb8840e46ec8524a705e1c18Yigit Boyar        }
46290ca3086dbf66ad6bb8840e46ec8524a705e1c18Yigit Boyar
46390ca3086dbf66ad6bb8840e46ec8524a705e1c18Yigit Boyar        @Override
464d7d27e9ebe5c7325e67e1a8af265378bd2056cadChet Haase        public TestViewHolder onCreateViewHolder(ViewGroup parent,
465d7d27e9ebe5c7325e67e1a8af265378bd2056cadChet Haase                int viewType) {
466d7d27e9ebe5c7325e67e1a8af265378bd2056cadChet Haase            return new TestViewHolder(new TextView(parent.getContext()));
467d7d27e9ebe5c7325e67e1a8af265378bd2056cadChet Haase        }
468d7d27e9ebe5c7325e67e1a8af265378bd2056cadChet Haase
469d7d27e9ebe5c7325e67e1a8af265378bd2056cadChet Haase        @Override
470d7d27e9ebe5c7325e67e1a8af265378bd2056cadChet Haase        public void onBindViewHolder(TestViewHolder holder, int position) {
471d7d27e9ebe5c7325e67e1a8af265378bd2056cadChet Haase            final Item item = mItems.get(position);
4722d2e8d88103866b631eb0f3805da137ebcfb0275Yigit Boyar            ((TextView) (holder.itemView)).setText(item.mText + "(" + item.mAdapterIndex + ")");
473d7d27e9ebe5c7325e67e1a8af265378bd2056cadChet Haase            holder.mBindedItem = item;
474d7d27e9ebe5c7325e67e1a8af265378bd2056cadChet Haase        }
475d7d27e9ebe5c7325e67e1a8af265378bd2056cadChet Haase
476b5f8b4fecb531aee6b359b4968409410aba90817Yigit Boyar        public void deleteAndNotify(final int start, final int count) throws Throwable {
477b5f8b4fecb531aee6b359b4968409410aba90817Yigit Boyar            deleteAndNotify(new int[]{start, count});
478b5f8b4fecb531aee6b359b4968409410aba90817Yigit Boyar        }
479b5f8b4fecb531aee6b359b4968409410aba90817Yigit Boyar
480b5f8b4fecb531aee6b359b4968409410aba90817Yigit Boyar        /**
481b5f8b4fecb531aee6b359b4968409410aba90817Yigit Boyar         * Deletes items in the given ranges.
482b5f8b4fecb531aee6b359b4968409410aba90817Yigit Boyar         * <p>
483b5f8b4fecb531aee6b359b4968409410aba90817Yigit Boyar         * Note that each operation affects the one after so you should offset them properly.
484b5f8b4fecb531aee6b359b4968409410aba90817Yigit Boyar         * <p>
485b5f8b4fecb531aee6b359b4968409410aba90817Yigit Boyar         * For example, if adapter has 5 items (A,B,C,D,E), and then you call this method with
486b5f8b4fecb531aee6b359b4968409410aba90817Yigit Boyar         * <code>[1, 2],[2, 1]</code>, it will first delete items B,C and the new adapter will be
487b5f8b4fecb531aee6b359b4968409410aba90817Yigit Boyar         * A D E. Then it will delete 2,1 which means it will delete E.
488b5f8b4fecb531aee6b359b4968409410aba90817Yigit Boyar         */
489b5f8b4fecb531aee6b359b4968409410aba90817Yigit Boyar        public void deleteAndNotify(final int[]... startCountTuples) throws Throwable {
4908ae76f91527ce850f155ce960fb9068bcd5d49f9Yigit Boyar            for (int[] tuple : startCountTuples) {
4918ae76f91527ce850f155ce960fb9068bcd5d49f9Yigit Boyar                tuple[1] = -tuple[1];
4928ae76f91527ce850f155ce960fb9068bcd5d49f9Yigit Boyar            }
4938ae76f91527ce850f155ce960fb9068bcd5d49f9Yigit Boyar            new AddRemoveRunnable(startCountTuples).runOnMainThread();
494d7d27e9ebe5c7325e67e1a8af265378bd2056cadChet Haase        }
495d7d27e9ebe5c7325e67e1a8af265378bd2056cadChet Haase
496c8b0ff5869e17a656a3a1d7fb306efa5b99fa07fYigit Boyar        @Override
497c8b0ff5869e17a656a3a1d7fb306efa5b99fa07fYigit Boyar        public long getItemId(int position) {
498c8b0ff5869e17a656a3a1d7fb306efa5b99fa07fYigit Boyar            return hasStableIds() ? mItems.get(position).mId : super.getItemId(position);
499c8b0ff5869e17a656a3a1d7fb306efa5b99fa07fYigit Boyar        }
500c8b0ff5869e17a656a3a1d7fb306efa5b99fa07fYigit Boyar
5012d2e8d88103866b631eb0f3805da137ebcfb0275Yigit Boyar        public void offsetOriginalIndices(int start, int offset) {
5022d2e8d88103866b631eb0f3805da137ebcfb0275Yigit Boyar            for (int i = start; i < mItems.size(); i++) {
5032d2e8d88103866b631eb0f3805da137ebcfb0275Yigit Boyar                mItems.get(i).mAdapterIndex += offset;
5042d2e8d88103866b631eb0f3805da137ebcfb0275Yigit Boyar            }
5052d2e8d88103866b631eb0f3805da137ebcfb0275Yigit Boyar        }
5062d2e8d88103866b631eb0f3805da137ebcfb0275Yigit Boyar
507e0c347f627f8a78d3e5e3e5eaac9c3ae26208689Yigit Boyar        /**
508e0c347f627f8a78d3e5e3e5eaac9c3ae26208689Yigit Boyar         * @param start inclusive
509e0c347f627f8a78d3e5e3e5eaac9c3ae26208689Yigit Boyar         * @param end exclusive
510e0c347f627f8a78d3e5e3e5eaac9c3ae26208689Yigit Boyar         * @param offset
511e0c347f627f8a78d3e5e3e5eaac9c3ae26208689Yigit Boyar         */
512e0c347f627f8a78d3e5e3e5eaac9c3ae26208689Yigit Boyar        public void offsetOriginalIndicesBetween(int start, int end, int offset) {
513e0c347f627f8a78d3e5e3e5eaac9c3ae26208689Yigit Boyar            for (int i = start; i < end && i < mItems.size(); i++) {
514e0c347f627f8a78d3e5e3e5eaac9c3ae26208689Yigit Boyar                mItems.get(i).mAdapterIndex += offset;
515e0c347f627f8a78d3e5e3e5eaac9c3ae26208689Yigit Boyar            }
516e0c347f627f8a78d3e5e3e5eaac9c3ae26208689Yigit Boyar        }
517e0c347f627f8a78d3e5e3e5eaac9c3ae26208689Yigit Boyar
518b5f8b4fecb531aee6b359b4968409410aba90817Yigit Boyar        public void addAndNotify(final int start, final int count) throws Throwable {
519b5f8b4fecb531aee6b359b4968409410aba90817Yigit Boyar            addAndNotify(new int[]{start, count});
520b5f8b4fecb531aee6b359b4968409410aba90817Yigit Boyar        }
521b5f8b4fecb531aee6b359b4968409410aba90817Yigit Boyar
522b5f8b4fecb531aee6b359b4968409410aba90817Yigit Boyar        public void addAndNotify(final int[]... startCountTuples) throws Throwable {
5238ae76f91527ce850f155ce960fb9068bcd5d49f9Yigit Boyar            new AddRemoveRunnable(startCountTuples).runOnMainThread();
5248ae76f91527ce850f155ce960fb9068bcd5d49f9Yigit Boyar        }
525b5f8b4fecb531aee6b359b4968409410aba90817Yigit Boyar
526cadd54707a7aaa4cfa9c8b8051c6185e8717f22eChet Haase        public void dispatchDataSetChanged() throws Throwable {
52722b6b8163d2e15eb6d0a89906854dec801945f1dYigit Boyar            runTestOnUiThread(new Runnable() {
52822b6b8163d2e15eb6d0a89906854dec801945f1dYigit Boyar                @Override
52922b6b8163d2e15eb6d0a89906854dec801945f1dYigit Boyar                public void run() {
53022b6b8163d2e15eb6d0a89906854dec801945f1dYigit Boyar                    notifyDataSetChanged();
53122b6b8163d2e15eb6d0a89906854dec801945f1dYigit Boyar                }
53222b6b8163d2e15eb6d0a89906854dec801945f1dYigit Boyar            });
53322b6b8163d2e15eb6d0a89906854dec801945f1dYigit Boyar        }
53422b6b8163d2e15eb6d0a89906854dec801945f1dYigit Boyar
535cadd54707a7aaa4cfa9c8b8051c6185e8717f22eChet Haase        public void changeAndNotify(final int start, final int count) throws Throwable {
5367e9da1a2629b657d56e2f54abd18c6249f3be037Yigit Boyar            runTestOnUiThread(new Runnable() {
5377e9da1a2629b657d56e2f54abd18c6249f3be037Yigit Boyar                @Override
5387e9da1a2629b657d56e2f54abd18c6249f3be037Yigit Boyar                public void run() {
5397e9da1a2629b657d56e2f54abd18c6249f3be037Yigit Boyar                    notifyItemRangeChanged(start, count);
5407e9da1a2629b657d56e2f54abd18c6249f3be037Yigit Boyar                }
5417e9da1a2629b657d56e2f54abd18c6249f3be037Yigit Boyar            });
5427e9da1a2629b657d56e2f54abd18c6249f3be037Yigit Boyar        }
5437e9da1a2629b657d56e2f54abd18c6249f3be037Yigit Boyar
544cadd54707a7aaa4cfa9c8b8051c6185e8717f22eChet Haase        public void changePositionsAndNotify(final int... positions) throws Throwable {
545cadd54707a7aaa4cfa9c8b8051c6185e8717f22eChet Haase            runTestOnUiThread(new Runnable() {
546cadd54707a7aaa4cfa9c8b8051c6185e8717f22eChet Haase                @Override
547cadd54707a7aaa4cfa9c8b8051c6185e8717f22eChet Haase                public void run() {
548cadd54707a7aaa4cfa9c8b8051c6185e8717f22eChet Haase                    for (int i = 0; i < positions.length; i += 1) {
549cadd54707a7aaa4cfa9c8b8051c6185e8717f22eChet Haase                        TestAdapter.super.notifyItemRangeChanged(positions[i], 1);
550cadd54707a7aaa4cfa9c8b8051c6185e8717f22eChet Haase                    }
551cadd54707a7aaa4cfa9c8b8051c6185e8717f22eChet Haase                }
552cadd54707a7aaa4cfa9c8b8051c6185e8717f22eChet Haase            });
553cadd54707a7aaa4cfa9c8b8051c6185e8717f22eChet Haase        }
554cadd54707a7aaa4cfa9c8b8051c6185e8717f22eChet Haase
5558ae76f91527ce850f155ce960fb9068bcd5d49f9Yigit Boyar        /**
5568ae76f91527ce850f155ce960fb9068bcd5d49f9Yigit Boyar         * Similar to other methods but negative count means delete and position count means add.
5578ae76f91527ce850f155ce960fb9068bcd5d49f9Yigit Boyar         * <p>
5588ae76f91527ce850f155ce960fb9068bcd5d49f9Yigit Boyar         * For instance, calling this method with <code>[1,1], [2,-1]</code> it will first add an
5598ae76f91527ce850f155ce960fb9068bcd5d49f9Yigit Boyar         * item to index 1, then remove an item from index 2 (updated index 2)
5608ae76f91527ce850f155ce960fb9068bcd5d49f9Yigit Boyar         */
5618ae76f91527ce850f155ce960fb9068bcd5d49f9Yigit Boyar        public void addDeleteAndNotify(final int[]... startCountTuples) throws Throwable {
5628ae76f91527ce850f155ce960fb9068bcd5d49f9Yigit Boyar            new AddRemoveRunnable(startCountTuples).runOnMainThread();
563d7d27e9ebe5c7325e67e1a8af265378bd2056cadChet Haase        }
564d7d27e9ebe5c7325e67e1a8af265378bd2056cadChet Haase
565d7d27e9ebe5c7325e67e1a8af265378bd2056cadChet Haase        @Override
566d7d27e9ebe5c7325e67e1a8af265378bd2056cadChet Haase        public int getItemCount() {
567d7d27e9ebe5c7325e67e1a8af265378bd2056cadChet Haase            return mItems.size();
568d7d27e9ebe5c7325e67e1a8af265378bd2056cadChet Haase        }
5698ae76f91527ce850f155ce960fb9068bcd5d49f9Yigit Boyar
570e0c347f627f8a78d3e5e3e5eaac9c3ae26208689Yigit Boyar        public void moveItems(boolean notifyChange, int[]... fromToTuples) throws Throwable {
571e0c347f627f8a78d3e5e3e5eaac9c3ae26208689Yigit Boyar            for (int i = 0; i < fromToTuples.length; i += 1) {
572e0c347f627f8a78d3e5e3e5eaac9c3ae26208689Yigit Boyar                int[] tuple = fromToTuples[i];
573e0c347f627f8a78d3e5e3e5eaac9c3ae26208689Yigit Boyar                moveItem(tuple[0], tuple[1], false);
574e0c347f627f8a78d3e5e3e5eaac9c3ae26208689Yigit Boyar            }
575e0c347f627f8a78d3e5e3e5eaac9c3ae26208689Yigit Boyar            if (notifyChange) {
576cadd54707a7aaa4cfa9c8b8051c6185e8717f22eChet Haase                dispatchDataSetChanged();
577e0c347f627f8a78d3e5e3e5eaac9c3ae26208689Yigit Boyar            }
578e0c347f627f8a78d3e5e3e5eaac9c3ae26208689Yigit Boyar        }
579e0c347f627f8a78d3e5e3e5eaac9c3ae26208689Yigit Boyar
580e0c347f627f8a78d3e5e3e5eaac9c3ae26208689Yigit Boyar        public void moveItem(final int from, final int to, final boolean notifyChange)
581e0c347f627f8a78d3e5e3e5eaac9c3ae26208689Yigit Boyar                throws Throwable {
582e0c347f627f8a78d3e5e3e5eaac9c3ae26208689Yigit Boyar            runTestOnUiThread(new Runnable() {
583e0c347f627f8a78d3e5e3e5eaac9c3ae26208689Yigit Boyar                @Override
584e0c347f627f8a78d3e5e3e5eaac9c3ae26208689Yigit Boyar                public void run() {
585e0c347f627f8a78d3e5e3e5eaac9c3ae26208689Yigit Boyar                    Item item = mItems.remove(from);
586e0c347f627f8a78d3e5e3e5eaac9c3ae26208689Yigit Boyar                    mItems.add(to, item);
587e0c347f627f8a78d3e5e3e5eaac9c3ae26208689Yigit Boyar                    offsetOriginalIndices(from, to - 1);
588e0c347f627f8a78d3e5e3e5eaac9c3ae26208689Yigit Boyar                    item.mAdapterIndex = to;
589e0c347f627f8a78d3e5e3e5eaac9c3ae26208689Yigit Boyar                    if (notifyChange) {
590e0c347f627f8a78d3e5e3e5eaac9c3ae26208689Yigit Boyar                        notifyDataSetChanged();
591e0c347f627f8a78d3e5e3e5eaac9c3ae26208689Yigit Boyar                    }
592e0c347f627f8a78d3e5e3e5eaac9c3ae26208689Yigit Boyar                }
593e0c347f627f8a78d3e5e3e5eaac9c3ae26208689Yigit Boyar            });
594e0c347f627f8a78d3e5e3e5eaac9c3ae26208689Yigit Boyar        }
595e0c347f627f8a78d3e5e3e5eaac9c3ae26208689Yigit Boyar
59690ca3086dbf66ad6bb8840e46ec8524a705e1c18Yigit Boyar        @Override
59790ca3086dbf66ad6bb8840e46ec8524a705e1c18Yigit Boyar        public ViewAttachDetachCounter getCounter() {
59890ca3086dbf66ad6bb8840e46ec8524a705e1c18Yigit Boyar            return mAttachmentCounter;
59990ca3086dbf66ad6bb8840e46ec8524a705e1c18Yigit Boyar        }
60090ca3086dbf66ad6bb8840e46ec8524a705e1c18Yigit Boyar
6018ae76f91527ce850f155ce960fb9068bcd5d49f9Yigit Boyar
6028ae76f91527ce850f155ce960fb9068bcd5d49f9Yigit Boyar        private class AddRemoveRunnable implements Runnable {
6038ae76f91527ce850f155ce960fb9068bcd5d49f9Yigit Boyar            final int[][] mStartCountTuples;
6048ae76f91527ce850f155ce960fb9068bcd5d49f9Yigit Boyar
6058ae76f91527ce850f155ce960fb9068bcd5d49f9Yigit Boyar            public AddRemoveRunnable(int[][] startCountTuples) {
6068ae76f91527ce850f155ce960fb9068bcd5d49f9Yigit Boyar                mStartCountTuples = startCountTuples;
6078ae76f91527ce850f155ce960fb9068bcd5d49f9Yigit Boyar            }
6088ae76f91527ce850f155ce960fb9068bcd5d49f9Yigit Boyar
6098ae76f91527ce850f155ce960fb9068bcd5d49f9Yigit Boyar            public void runOnMainThread() throws Throwable {
6108ae76f91527ce850f155ce960fb9068bcd5d49f9Yigit Boyar                if (Looper.myLooper() == Looper.getMainLooper()) {
6118ae76f91527ce850f155ce960fb9068bcd5d49f9Yigit Boyar                    run();
6128ae76f91527ce850f155ce960fb9068bcd5d49f9Yigit Boyar                } else {
6138ae76f91527ce850f155ce960fb9068bcd5d49f9Yigit Boyar                    runTestOnUiThread(this);
6148ae76f91527ce850f155ce960fb9068bcd5d49f9Yigit Boyar                }
6158ae76f91527ce850f155ce960fb9068bcd5d49f9Yigit Boyar            }
6168ae76f91527ce850f155ce960fb9068bcd5d49f9Yigit Boyar
6178ae76f91527ce850f155ce960fb9068bcd5d49f9Yigit Boyar            @Override
6188ae76f91527ce850f155ce960fb9068bcd5d49f9Yigit Boyar            public void run() {
6198ae76f91527ce850f155ce960fb9068bcd5d49f9Yigit Boyar                for (int[] tuple : mStartCountTuples) {
6208ae76f91527ce850f155ce960fb9068bcd5d49f9Yigit Boyar                    if (tuple[1] < 0) {
6218ae76f91527ce850f155ce960fb9068bcd5d49f9Yigit Boyar                        delete(tuple);
6228ae76f91527ce850f155ce960fb9068bcd5d49f9Yigit Boyar                    } else {
6238ae76f91527ce850f155ce960fb9068bcd5d49f9Yigit Boyar                        add(tuple);
6248ae76f91527ce850f155ce960fb9068bcd5d49f9Yigit Boyar                    }
6258ae76f91527ce850f155ce960fb9068bcd5d49f9Yigit Boyar                }
6268ae76f91527ce850f155ce960fb9068bcd5d49f9Yigit Boyar            }
6278ae76f91527ce850f155ce960fb9068bcd5d49f9Yigit Boyar
6288ae76f91527ce850f155ce960fb9068bcd5d49f9Yigit Boyar            private void add(int[] tuple) {
6292d2e8d88103866b631eb0f3805da137ebcfb0275Yigit Boyar                // offset others
6302d2e8d88103866b631eb0f3805da137ebcfb0275Yigit Boyar                offsetOriginalIndices(tuple[0], tuple[1]);
6318ae76f91527ce850f155ce960fb9068bcd5d49f9Yigit Boyar                for (int i = 0; i < tuple[1]; i++) {
6328ae76f91527ce850f155ce960fb9068bcd5d49f9Yigit Boyar                    mItems.add(tuple[0], new Item(i, "new item " + i));
6338ae76f91527ce850f155ce960fb9068bcd5d49f9Yigit Boyar                }
6348ae76f91527ce850f155ce960fb9068bcd5d49f9Yigit Boyar                notifyItemRangeInserted(tuple[0], tuple[1]);
6358ae76f91527ce850f155ce960fb9068bcd5d49f9Yigit Boyar            }
6368ae76f91527ce850f155ce960fb9068bcd5d49f9Yigit Boyar
6378ae76f91527ce850f155ce960fb9068bcd5d49f9Yigit Boyar            private void delete(int[] tuple) {
6382d2e8d88103866b631eb0f3805da137ebcfb0275Yigit Boyar                final int count = -tuple[1];
6392d2e8d88103866b631eb0f3805da137ebcfb0275Yigit Boyar                offsetOriginalIndices(tuple[0] + count, tuple[1]);
6402d2e8d88103866b631eb0f3805da137ebcfb0275Yigit Boyar                for (int i = 0; i < count; i++) {
6418ae76f91527ce850f155ce960fb9068bcd5d49f9Yigit Boyar                    mItems.remove(tuple[0]);
6428ae76f91527ce850f155ce960fb9068bcd5d49f9Yigit Boyar                }
6432d2e8d88103866b631eb0f3805da137ebcfb0275Yigit Boyar                notifyItemRangeRemoved(tuple[0], count);
6448ae76f91527ce850f155ce960fb9068bcd5d49f9Yigit Boyar            }
6458ae76f91527ce850f155ce960fb9068bcd5d49f9Yigit Boyar        }
6468ae76f91527ce850f155ce960fb9068bcd5d49f9Yigit Boyar    }
6478ae76f91527ce850f155ce960fb9068bcd5d49f9Yigit Boyar
64890ca3086dbf66ad6bb8840e46ec8524a705e1c18Yigit Boyar    public boolean isMainThread() {
64990ca3086dbf66ad6bb8840e46ec8524a705e1c18Yigit Boyar        return Looper.myLooper() == Looper.getMainLooper();
65090ca3086dbf66ad6bb8840e46ec8524a705e1c18Yigit Boyar    }
65190ca3086dbf66ad6bb8840e46ec8524a705e1c18Yigit Boyar
6528ae76f91527ce850f155ce960fb9068bcd5d49f9Yigit Boyar    @Override
6538ae76f91527ce850f155ce960fb9068bcd5d49f9Yigit Boyar    public void runTestOnUiThread(Runnable r) throws Throwable {
6548ae76f91527ce850f155ce960fb9068bcd5d49f9Yigit Boyar        if (Looper.myLooper() == Looper.getMainLooper()) {
6558ae76f91527ce850f155ce960fb9068bcd5d49f9Yigit Boyar            r.run();
6568ae76f91527ce850f155ce960fb9068bcd5d49f9Yigit Boyar        } else {
6578ae76f91527ce850f155ce960fb9068bcd5d49f9Yigit Boyar            super.runTestOnUiThread(r);
6588ae76f91527ce850f155ce960fb9068bcd5d49f9Yigit Boyar        }
659d7d27e9ebe5c7325e67e1a8af265378bd2056cadChet Haase    }
660c50c4cad31d73e574b27bb3d7581542975e37263Yigit Boyar
661c50c4cad31d73e574b27bb3d7581542975e37263Yigit Boyar    static class TargetTuple {
662c50c4cad31d73e574b27bb3d7581542975e37263Yigit Boyar
663c50c4cad31d73e574b27bb3d7581542975e37263Yigit Boyar        final int mPosition;
664c50c4cad31d73e574b27bb3d7581542975e37263Yigit Boyar
665c50c4cad31d73e574b27bb3d7581542975e37263Yigit Boyar        final int mLayoutDirection;
666c50c4cad31d73e574b27bb3d7581542975e37263Yigit Boyar
667c50c4cad31d73e574b27bb3d7581542975e37263Yigit Boyar        TargetTuple(int position, int layoutDirection) {
668c50c4cad31d73e574b27bb3d7581542975e37263Yigit Boyar            this.mPosition = position;
669c50c4cad31d73e574b27bb3d7581542975e37263Yigit Boyar            this.mLayoutDirection = layoutDirection;
670c50c4cad31d73e574b27bb3d7581542975e37263Yigit Boyar        }
671c50c4cad31d73e574b27bb3d7581542975e37263Yigit Boyar
672c50c4cad31d73e574b27bb3d7581542975e37263Yigit Boyar        @Override
673c50c4cad31d73e574b27bb3d7581542975e37263Yigit Boyar        public String toString() {
674c50c4cad31d73e574b27bb3d7581542975e37263Yigit Boyar            return "TargetTuple{" +
675c50c4cad31d73e574b27bb3d7581542975e37263Yigit Boyar                    "mPosition=" + mPosition +
676c50c4cad31d73e574b27bb3d7581542975e37263Yigit Boyar                    ", mLayoutDirection=" + mLayoutDirection +
677c50c4cad31d73e574b27bb3d7581542975e37263Yigit Boyar                    '}';
678c50c4cad31d73e574b27bb3d7581542975e37263Yigit Boyar        }
679c50c4cad31d73e574b27bb3d7581542975e37263Yigit Boyar    }
68090ca3086dbf66ad6bb8840e46ec8524a705e1c18Yigit Boyar
68190ca3086dbf66ad6bb8840e46ec8524a705e1c18Yigit Boyar    public interface AttachDetachCountingAdapter {
68290ca3086dbf66ad6bb8840e46ec8524a705e1c18Yigit Boyar
68390ca3086dbf66ad6bb8840e46ec8524a705e1c18Yigit Boyar        ViewAttachDetachCounter getCounter();
68490ca3086dbf66ad6bb8840e46ec8524a705e1c18Yigit Boyar    }
68590ca3086dbf66ad6bb8840e46ec8524a705e1c18Yigit Boyar
68690ca3086dbf66ad6bb8840e46ec8524a705e1c18Yigit Boyar    public class ViewAttachDetachCounter {
68790ca3086dbf66ad6bb8840e46ec8524a705e1c18Yigit Boyar
68890ca3086dbf66ad6bb8840e46ec8524a705e1c18Yigit Boyar        Set<RecyclerView.ViewHolder> mAttachedSet = new HashSet<RecyclerView.ViewHolder>();
68990ca3086dbf66ad6bb8840e46ec8524a705e1c18Yigit Boyar
69090ca3086dbf66ad6bb8840e46ec8524a705e1c18Yigit Boyar        public void validateRemaining(RecyclerView recyclerView) {
69190ca3086dbf66ad6bb8840e46ec8524a705e1c18Yigit Boyar            final int childCount = recyclerView.getChildCount();
69290ca3086dbf66ad6bb8840e46ec8524a705e1c18Yigit Boyar            for (int i = 0; i < childCount; i++) {
69390ca3086dbf66ad6bb8840e46ec8524a705e1c18Yigit Boyar                View view = recyclerView.getChildAt(i);
69490ca3086dbf66ad6bb8840e46ec8524a705e1c18Yigit Boyar                RecyclerView.ViewHolder vh = recyclerView.getChildViewHolder(view);
69590ca3086dbf66ad6bb8840e46ec8524a705e1c18Yigit Boyar                assertTrue("remaining view should be in attached set " + vh,
69690ca3086dbf66ad6bb8840e46ec8524a705e1c18Yigit Boyar                        mAttachedSet.contains(vh));
69790ca3086dbf66ad6bb8840e46ec8524a705e1c18Yigit Boyar            }
69890ca3086dbf66ad6bb8840e46ec8524a705e1c18Yigit Boyar            assertEquals("there should not be any views left in attached set",
69990ca3086dbf66ad6bb8840e46ec8524a705e1c18Yigit Boyar                    childCount, mAttachedSet.size());
70090ca3086dbf66ad6bb8840e46ec8524a705e1c18Yigit Boyar        }
70190ca3086dbf66ad6bb8840e46ec8524a705e1c18Yigit Boyar
70290ca3086dbf66ad6bb8840e46ec8524a705e1c18Yigit Boyar        public void onViewDetached(RecyclerView.ViewHolder viewHolder) {
70390ca3086dbf66ad6bb8840e46ec8524a705e1c18Yigit Boyar            try {
70490ca3086dbf66ad6bb8840e46ec8524a705e1c18Yigit Boyar                assertTrue("view holder should be in attached set",
70590ca3086dbf66ad6bb8840e46ec8524a705e1c18Yigit Boyar                        mAttachedSet.remove(viewHolder));
70690ca3086dbf66ad6bb8840e46ec8524a705e1c18Yigit Boyar            } catch (Throwable t) {
70790ca3086dbf66ad6bb8840e46ec8524a705e1c18Yigit Boyar                postExceptionToInstrumentation(t);
70890ca3086dbf66ad6bb8840e46ec8524a705e1c18Yigit Boyar            }
70990ca3086dbf66ad6bb8840e46ec8524a705e1c18Yigit Boyar        }
71090ca3086dbf66ad6bb8840e46ec8524a705e1c18Yigit Boyar
71190ca3086dbf66ad6bb8840e46ec8524a705e1c18Yigit Boyar        public void onViewAttached(RecyclerView.ViewHolder viewHolder) {
71290ca3086dbf66ad6bb8840e46ec8524a705e1c18Yigit Boyar            try {
71390ca3086dbf66ad6bb8840e46ec8524a705e1c18Yigit Boyar                assertTrue("view holder should not be in attached set",
71490ca3086dbf66ad6bb8840e46ec8524a705e1c18Yigit Boyar                        mAttachedSet.add(viewHolder));
71590ca3086dbf66ad6bb8840e46ec8524a705e1c18Yigit Boyar            } catch (Throwable t) {
71690ca3086dbf66ad6bb8840e46ec8524a705e1c18Yigit Boyar                postExceptionToInstrumentation(t);
71790ca3086dbf66ad6bb8840e46ec8524a705e1c18Yigit Boyar            }
71890ca3086dbf66ad6bb8840e46ec8524a705e1c18Yigit Boyar        }
71990ca3086dbf66ad6bb8840e46ec8524a705e1c18Yigit Boyar
72090ca3086dbf66ad6bb8840e46ec8524a705e1c18Yigit Boyar        public void onAttached(RecyclerView recyclerView) {
72190ca3086dbf66ad6bb8840e46ec8524a705e1c18Yigit Boyar            // when a new RV is attached, clear the set and add all view holders
72290ca3086dbf66ad6bb8840e46ec8524a705e1c18Yigit Boyar            mAttachedSet.clear();
72390ca3086dbf66ad6bb8840e46ec8524a705e1c18Yigit Boyar            final int childCount = recyclerView.getChildCount();
72490ca3086dbf66ad6bb8840e46ec8524a705e1c18Yigit Boyar            for (int i = 0; i < childCount; i ++) {
72590ca3086dbf66ad6bb8840e46ec8524a705e1c18Yigit Boyar                View view = recyclerView.getChildAt(i);
72690ca3086dbf66ad6bb8840e46ec8524a705e1c18Yigit Boyar                mAttachedSet.add(recyclerView.getChildViewHolder(view));
72790ca3086dbf66ad6bb8840e46ec8524a705e1c18Yigit Boyar            }
72890ca3086dbf66ad6bb8840e46ec8524a705e1c18Yigit Boyar        }
72990ca3086dbf66ad6bb8840e46ec8524a705e1c18Yigit Boyar
73090ca3086dbf66ad6bb8840e46ec8524a705e1c18Yigit Boyar        public void onDetached(RecyclerView recyclerView) {
73190ca3086dbf66ad6bb8840e46ec8524a705e1c18Yigit Boyar            validateRemaining(recyclerView);
73290ca3086dbf66ad6bb8840e46ec8524a705e1c18Yigit Boyar        }
73390ca3086dbf66ad6bb8840e46ec8524a705e1c18Yigit Boyar    }
7347a59e4b36500bcfddd842376d6c3abe0d9f23d84Yigit Boyar}
735