BaseRecyclerViewInstrumentationTest.java revision e05fbd9cfe05496e82a3abe19e07e8745985e9a5
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
192bf89a9bae2b6110a77cf0b3754b567bb07d12a5Yigit Boyarimport android.app.Instrumentation;
20c50c4cad31d73e574b27bb3d7581542975e37263Yigit Boyarimport android.graphics.Rect;
21e05fbd9cfe05496e82a3abe19e07e8745985e9a5Yigit Boyarimport android.os.Handler;
228ae76f91527ce850f155ce960fb9068bcd5d49f9Yigit Boyarimport android.os.Looper;
23e2437126f0225135891544315ff72e54c7de0f59Yigit Boyarimport android.support.v4.view.ViewCompat;
24d7d27e9ebe5c7325e67e1a8af265378bd2056cadChet Haaseimport android.test.ActivityInstrumentationTestCase2;
25d7d27e9ebe5c7325e67e1a8af265378bd2056cadChet Haaseimport android.util.Log;
26e71a1df9b3c0e1bd3c21a1b3dd20a41790d4a950Yigit Boyarimport android.view.LayoutInflater;
27d7d27e9ebe5c7325e67e1a8af265378bd2056cadChet Haaseimport android.view.View;
28d7d27e9ebe5c7325e67e1a8af265378bd2056cadChet Haaseimport android.view.ViewGroup;
29310e95e1c6dfe4f26ef594233e65e1ff83e0f1ffYigit Boyarimport android.widget.FrameLayout;
30d7d27e9ebe5c7325e67e1a8af265378bd2056cadChet Haaseimport android.widget.TextView;
31d7d27e9ebe5c7325e67e1a8af265378bd2056cadChet Haase
322bf89a9bae2b6110a77cf0b3754b567bb07d12a5Yigit Boyarimport java.lang.reflect.InvocationTargetException;
332bf89a9bae2b6110a77cf0b3754b567bb07d12a5Yigit Boyarimport java.lang.reflect.Method;
34d7d27e9ebe5c7325e67e1a8af265378bd2056cadChet Haaseimport java.util.ArrayList;
3590ca3086dbf66ad6bb8840e46ec8524a705e1c18Yigit Boyarimport java.util.HashSet;
36d7d27e9ebe5c7325e67e1a8af265378bd2056cadChet Haaseimport java.util.List;
3790ca3086dbf66ad6bb8840e46ec8524a705e1c18Yigit Boyarimport java.util.Set;
38d7d27e9ebe5c7325e67e1a8af265378bd2056cadChet Haaseimport java.util.concurrent.CountDownLatch;
39d7d27e9ebe5c7325e67e1a8af265378bd2056cadChet Haaseimport java.util.concurrent.TimeUnit;
4011b7cfea15306c0d6e4ed7da09f7698df01695dfYigit Boyarimport java.util.concurrent.atomic.AtomicInteger;
41da50fde358bd3ef2edb273b3bf0265cf5e84ec6bYigit Boyarimport java.util.concurrent.locks.Lock;
42da50fde358bd3ef2edb273b3bf0265cf5e84ec6bYigit Boyarimport java.util.concurrent.locks.ReentrantLock;
43e71a1df9b3c0e1bd3c21a1b3dd20a41790d4a950Yigit Boyarimport android.support.v7.recyclerview.test.R;
44d7d27e9ebe5c7325e67e1a8af265378bd2056cadChet Haase
45d7d27e9ebe5c7325e67e1a8af265378bd2056cadChet Haaseabstract public class BaseRecyclerViewInstrumentationTest extends
46d7d27e9ebe5c7325e67e1a8af265378bd2056cadChet Haase        ActivityInstrumentationTestCase2<TestActivity> {
47d7d27e9ebe5c7325e67e1a8af265378bd2056cadChet Haase
48d7d27e9ebe5c7325e67e1a8af265378bd2056cadChet Haase    private static final String TAG = "RecyclerViewTest";
49d7d27e9ebe5c7325e67e1a8af265378bd2056cadChet Haase
50b5f8b4fecb531aee6b359b4968409410aba90817Yigit Boyar    private boolean mDebug;
51b5f8b4fecb531aee6b359b4968409410aba90817Yigit Boyar
52b5f8b4fecb531aee6b359b4968409410aba90817Yigit Boyar    protected RecyclerView mRecyclerView;
53d7d27e9ebe5c7325e67e1a8af265378bd2056cadChet Haase
548ae76f91527ce850f155ce960fb9068bcd5d49f9Yigit Boyar    protected AdapterHelper mAdapterHelper;
558ae76f91527ce850f155ce960fb9068bcd5d49f9Yigit Boyar
567e9da1a2629b657d56e2f54abd18c6249f3be037Yigit Boyar    Throwable mainThreadException;
577e9da1a2629b657d56e2f54abd18c6249f3be037Yigit Boyar
58e05fbd9cfe05496e82a3abe19e07e8745985e9a5Yigit Boyar    Thread mInstrumentationThread;
59e05fbd9cfe05496e82a3abe19e07e8745985e9a5Yigit Boyar
60d7d27e9ebe5c7325e67e1a8af265378bd2056cadChet Haase    public BaseRecyclerViewInstrumentationTest() {
61b5f8b4fecb531aee6b359b4968409410aba90817Yigit Boyar        this(false);
62b5f8b4fecb531aee6b359b4968409410aba90817Yigit Boyar    }
63b5f8b4fecb531aee6b359b4968409410aba90817Yigit Boyar
64b5f8b4fecb531aee6b359b4968409410aba90817Yigit Boyar    public BaseRecyclerViewInstrumentationTest(boolean debug) {
658ae76f91527ce850f155ce960fb9068bcd5d49f9Yigit Boyar        super("android.support.v7.recyclerview", TestActivity.class);
66b5f8b4fecb531aee6b359b4968409410aba90817Yigit Boyar        mDebug = debug;
67b5f8b4fecb531aee6b359b4968409410aba90817Yigit Boyar    }
68b5f8b4fecb531aee6b359b4968409410aba90817Yigit Boyar
697e9da1a2629b657d56e2f54abd18c6249f3be037Yigit Boyar    void checkForMainThreadException() throws Throwable {
707e9da1a2629b657d56e2f54abd18c6249f3be037Yigit Boyar        if (mainThreadException != null) {
717e9da1a2629b657d56e2f54abd18c6249f3be037Yigit Boyar            throw mainThreadException;
727e9da1a2629b657d56e2f54abd18c6249f3be037Yigit Boyar        }
737e9da1a2629b657d56e2f54abd18c6249f3be037Yigit Boyar    }
747e9da1a2629b657d56e2f54abd18c6249f3be037Yigit Boyar
75e05fbd9cfe05496e82a3abe19e07e8745985e9a5Yigit Boyar    @Override
76e05fbd9cfe05496e82a3abe19e07e8745985e9a5Yigit Boyar    protected void setUp() throws Exception {
77e05fbd9cfe05496e82a3abe19e07e8745985e9a5Yigit Boyar        super.setUp();
78e05fbd9cfe05496e82a3abe19e07e8745985e9a5Yigit Boyar        mInstrumentationThread = Thread.currentThread();
79e05fbd9cfe05496e82a3abe19e07e8745985e9a5Yigit Boyar    }
80e05fbd9cfe05496e82a3abe19e07e8745985e9a5Yigit Boyar
81e2437126f0225135891544315ff72e54c7de0f59Yigit Boyar    void setHasTransientState(final View view, final boolean value) {
82e2437126f0225135891544315ff72e54c7de0f59Yigit Boyar        try {
83e2437126f0225135891544315ff72e54c7de0f59Yigit Boyar            runTestOnUiThread(new Runnable() {
84e2437126f0225135891544315ff72e54c7de0f59Yigit Boyar                @Override
85e2437126f0225135891544315ff72e54c7de0f59Yigit Boyar                public void run() {
86e2437126f0225135891544315ff72e54c7de0f59Yigit Boyar                    ViewCompat.setHasTransientState(view, value);
87e2437126f0225135891544315ff72e54c7de0f59Yigit Boyar                }
88e2437126f0225135891544315ff72e54c7de0f59Yigit Boyar            });
89e2437126f0225135891544315ff72e54c7de0f59Yigit Boyar        } catch (Throwable throwable) {
90e2437126f0225135891544315ff72e54c7de0f59Yigit Boyar            Log.e(TAG, "", throwable);
91e2437126f0225135891544315ff72e54c7de0f59Yigit Boyar        }
92e2437126f0225135891544315ff72e54c7de0f59Yigit Boyar    }
93e2437126f0225135891544315ff72e54c7de0f59Yigit Boyar
942bf89a9bae2b6110a77cf0b3754b567bb07d12a5Yigit Boyar    protected void enableAccessibility()
952bf89a9bae2b6110a77cf0b3754b567bb07d12a5Yigit Boyar            throws NoSuchMethodException, InvocationTargetException, IllegalAccessException {
962bf89a9bae2b6110a77cf0b3754b567bb07d12a5Yigit Boyar        Method getUIAutomation = Instrumentation.class.getMethod("getUiAutomation");
972bf89a9bae2b6110a77cf0b3754b567bb07d12a5Yigit Boyar        getUIAutomation.invoke(getInstrumentation());
982bf89a9bae2b6110a77cf0b3754b567bb07d12a5Yigit Boyar    }
992bf89a9bae2b6110a77cf0b3754b567bb07d12a5Yigit Boyar
1000b1059b711009601ff08354e2df6bfc45266e80aYigit Boyar    void setAdapter(final RecyclerView.Adapter adapter) throws Throwable {
1010b1059b711009601ff08354e2df6bfc45266e80aYigit Boyar        runTestOnUiThread(new Runnable() {
1020b1059b711009601ff08354e2df6bfc45266e80aYigit Boyar            @Override
1030b1059b711009601ff08354e2df6bfc45266e80aYigit Boyar            public void run() {
1040b1059b711009601ff08354e2df6bfc45266e80aYigit Boyar                mRecyclerView.setAdapter(adapter);
1050b1059b711009601ff08354e2df6bfc45266e80aYigit Boyar            }
1060b1059b711009601ff08354e2df6bfc45266e80aYigit Boyar        });
1070b1059b711009601ff08354e2df6bfc45266e80aYigit Boyar    }
1080b1059b711009601ff08354e2df6bfc45266e80aYigit Boyar
109e71a1df9b3c0e1bd3c21a1b3dd20a41790d4a950Yigit Boyar    protected WrappedRecyclerView inflateWrappedRV() {
110e71a1df9b3c0e1bd3c21a1b3dd20a41790d4a950Yigit Boyar        return (WrappedRecyclerView)
111e71a1df9b3c0e1bd3c21a1b3dd20a41790d4a950Yigit Boyar                LayoutInflater.from(getActivity()).inflate(R.layout.wrapped_test_rv,
112e71a1df9b3c0e1bd3c21a1b3dd20a41790d4a950Yigit Boyar                        getRecyclerViewContainer(), false);
113e71a1df9b3c0e1bd3c21a1b3dd20a41790d4a950Yigit Boyar    }
114e71a1df9b3c0e1bd3c21a1b3dd20a41790d4a950Yigit Boyar
1150b1059b711009601ff08354e2df6bfc45266e80aYigit Boyar    void swapAdapter(final RecyclerView.Adapter adapter,
1160b1059b711009601ff08354e2df6bfc45266e80aYigit Boyar            final boolean removeAndRecycleExistingViews) throws Throwable {
1170b1059b711009601ff08354e2df6bfc45266e80aYigit Boyar        runTestOnUiThread(new Runnable() {
1180b1059b711009601ff08354e2df6bfc45266e80aYigit Boyar            @Override
1190b1059b711009601ff08354e2df6bfc45266e80aYigit Boyar            public void run() {
120824a7db96feacf3b68f56993ed72712bc935153eYigit Boyar                try {
121824a7db96feacf3b68f56993ed72712bc935153eYigit Boyar                    mRecyclerView.swapAdapter(adapter, removeAndRecycleExistingViews);
122824a7db96feacf3b68f56993ed72712bc935153eYigit Boyar                } catch (Throwable t) {
123824a7db96feacf3b68f56993ed72712bc935153eYigit Boyar                    postExceptionToInstrumentation(t);
124824a7db96feacf3b68f56993ed72712bc935153eYigit Boyar                }
1250b1059b711009601ff08354e2df6bfc45266e80aYigit Boyar            }
1260b1059b711009601ff08354e2df6bfc45266e80aYigit Boyar        });
127824a7db96feacf3b68f56993ed72712bc935153eYigit Boyar        checkForMainThreadException();
1280b1059b711009601ff08354e2df6bfc45266e80aYigit Boyar    }
1290b1059b711009601ff08354e2df6bfc45266e80aYigit Boyar
1307e9da1a2629b657d56e2f54abd18c6249f3be037Yigit Boyar    void postExceptionToInstrumentation(Throwable t) {
131e05fbd9cfe05496e82a3abe19e07e8745985e9a5Yigit Boyar        if (mInstrumentationThread == Thread.currentThread()) {
132e05fbd9cfe05496e82a3abe19e07e8745985e9a5Yigit Boyar            throw new RuntimeException(t);
133e05fbd9cfe05496e82a3abe19e07e8745985e9a5Yigit Boyar        }
1346e83751247c5be0211d7bffaf057431c03dfef38Yigit Boyar        if (mainThreadException != null) {
1356e83751247c5be0211d7bffaf057431c03dfef38Yigit Boyar            Log.e(TAG, "receiving another main thread exception. dropping.", t);
1366e83751247c5be0211d7bffaf057431c03dfef38Yigit Boyar        } else {
13790ca3086dbf66ad6bb8840e46ec8524a705e1c18Yigit Boyar            Log.e(TAG, "captured exception on main thread", t);
1386e83751247c5be0211d7bffaf057431c03dfef38Yigit Boyar            mainThreadException = t;
1396e83751247c5be0211d7bffaf057431c03dfef38Yigit Boyar        }
1406e83751247c5be0211d7bffaf057431c03dfef38Yigit Boyar
1417e9da1a2629b657d56e2f54abd18c6249f3be037Yigit Boyar        if (mRecyclerView != null && mRecyclerView
1427e9da1a2629b657d56e2f54abd18c6249f3be037Yigit Boyar                .getLayoutManager() instanceof TestLayoutManager) {
1437e9da1a2629b657d56e2f54abd18c6249f3be037Yigit Boyar            TestLayoutManager lm = (TestLayoutManager) mRecyclerView.getLayoutManager();
1447e9da1a2629b657d56e2f54abd18c6249f3be037Yigit Boyar            // finish all layouts so that we get the correct exception
1457e9da1a2629b657d56e2f54abd18c6249f3be037Yigit Boyar            while (lm.layoutLatch.getCount() > 0) {
1467e9da1a2629b657d56e2f54abd18c6249f3be037Yigit Boyar                lm.layoutLatch.countDown();
1477e9da1a2629b657d56e2f54abd18c6249f3be037Yigit Boyar            }
1487e9da1a2629b657d56e2f54abd18c6249f3be037Yigit Boyar        }
1497e9da1a2629b657d56e2f54abd18c6249f3be037Yigit Boyar    }
1507e9da1a2629b657d56e2f54abd18c6249f3be037Yigit Boyar
1518ae76f91527ce850f155ce960fb9068bcd5d49f9Yigit Boyar    @Override
1528ae76f91527ce850f155ce960fb9068bcd5d49f9Yigit Boyar    protected void tearDown() throws Exception {
1538ae76f91527ce850f155ce960fb9068bcd5d49f9Yigit Boyar        if (mRecyclerView != null) {
1548ae76f91527ce850f155ce960fb9068bcd5d49f9Yigit Boyar            try {
1558ae76f91527ce850f155ce960fb9068bcd5d49f9Yigit Boyar                removeRecyclerView();
1568ae76f91527ce850f155ce960fb9068bcd5d49f9Yigit Boyar            } catch (Throwable throwable) {
1578ae76f91527ce850f155ce960fb9068bcd5d49f9Yigit Boyar                throwable.printStackTrace();
1588ae76f91527ce850f155ce960fb9068bcd5d49f9Yigit Boyar            }
1598ae76f91527ce850f155ce960fb9068bcd5d49f9Yigit Boyar        }
1608ae76f91527ce850f155ce960fb9068bcd5d49f9Yigit Boyar        getInstrumentation().waitForIdleSync();
161ca9ce7ccdccf6b6a2a81da73dd1e6425d90e4b23Yigit Boyar        super.tearDown();
162ca9ce7ccdccf6b6a2a81da73dd1e6425d90e4b23Yigit Boyar
1636e83751247c5be0211d7bffaf057431c03dfef38Yigit Boyar        try {
1646e83751247c5be0211d7bffaf057431c03dfef38Yigit Boyar            checkForMainThreadException();
1656e83751247c5be0211d7bffaf057431c03dfef38Yigit Boyar        } catch (Exception e) {
1666e83751247c5be0211d7bffaf057431c03dfef38Yigit Boyar            throw e;
1676e83751247c5be0211d7bffaf057431c03dfef38Yigit Boyar        } catch (Throwable throwable) {
168ca9ce7ccdccf6b6a2a81da73dd1e6425d90e4b23Yigit Boyar            throw new Exception(throwable);
1696e83751247c5be0211d7bffaf057431c03dfef38Yigit Boyar        }
1708ae76f91527ce850f155ce960fb9068bcd5d49f9Yigit Boyar    }
1718ae76f91527ce850f155ce960fb9068bcd5d49f9Yigit Boyar
172c50c4cad31d73e574b27bb3d7581542975e37263Yigit Boyar    public Rect getDecoratedRecyclerViewBounds() {
173c50c4cad31d73e574b27bb3d7581542975e37263Yigit Boyar        return new Rect(
174c50c4cad31d73e574b27bb3d7581542975e37263Yigit Boyar                mRecyclerView.getPaddingLeft(),
175c50c4cad31d73e574b27bb3d7581542975e37263Yigit Boyar                mRecyclerView.getPaddingTop(),
176c50c4cad31d73e574b27bb3d7581542975e37263Yigit Boyar                mRecyclerView.getPaddingLeft() + mRecyclerView.getWidth(),
177c50c4cad31d73e574b27bb3d7581542975e37263Yigit Boyar                mRecyclerView.getPaddingTop() + mRecyclerView.getHeight()
178c50c4cad31d73e574b27bb3d7581542975e37263Yigit Boyar        );
179c50c4cad31d73e574b27bb3d7581542975e37263Yigit Boyar    }
180c50c4cad31d73e574b27bb3d7581542975e37263Yigit Boyar
181b5f8b4fecb531aee6b359b4968409410aba90817Yigit Boyar    public void removeRecyclerView() throws Throwable {
182d7e2f2ab1d253133fa54f309e74a7ee384c33971Yigit Boyar        if (mRecyclerView == null) {
183d7e2f2ab1d253133fa54f309e74a7ee384c33971Yigit Boyar            return;
184d7e2f2ab1d253133fa54f309e74a7ee384c33971Yigit Boyar        }
18590ca3086dbf66ad6bb8840e46ec8524a705e1c18Yigit Boyar        if (!isMainThread()) {
18690ca3086dbf66ad6bb8840e46ec8524a705e1c18Yigit Boyar            getInstrumentation().waitForIdleSync();
18790ca3086dbf66ad6bb8840e46ec8524a705e1c18Yigit Boyar        }
188b5f8b4fecb531aee6b359b4968409410aba90817Yigit Boyar        runTestOnUiThread(new Runnable() {
189b5f8b4fecb531aee6b359b4968409410aba90817Yigit Boyar            @Override
190b5f8b4fecb531aee6b359b4968409410aba90817Yigit Boyar            public void run() {
191abd0fbfcdd5b438909f126392eca5978b5939b6dYigit Boyar                try {
192abd0fbfcdd5b438909f126392eca5978b5939b6dYigit Boyar                    final RecyclerView.Adapter adapter = mRecyclerView.getAdapter();
193abd0fbfcdd5b438909f126392eca5978b5939b6dYigit Boyar                    if (adapter instanceof AttachDetachCountingAdapter) {
194abd0fbfcdd5b438909f126392eca5978b5939b6dYigit Boyar                        ((AttachDetachCountingAdapter) adapter).getCounter()
195abd0fbfcdd5b438909f126392eca5978b5939b6dYigit Boyar                                .validateRemaining(mRecyclerView);
196abd0fbfcdd5b438909f126392eca5978b5939b6dYigit Boyar                    }
197abd0fbfcdd5b438909f126392eca5978b5939b6dYigit Boyar                    getActivity().mContainer.removeAllViews();
198abd0fbfcdd5b438909f126392eca5978b5939b6dYigit Boyar                } catch (Throwable t) {
199abd0fbfcdd5b438909f126392eca5978b5939b6dYigit Boyar                    postExceptionToInstrumentation(t);
20090ca3086dbf66ad6bb8840e46ec8524a705e1c18Yigit Boyar                }
201b5f8b4fecb531aee6b359b4968409410aba90817Yigit Boyar            }
202b5f8b4fecb531aee6b359b4968409410aba90817Yigit Boyar        });
20390ca3086dbf66ad6bb8840e46ec8524a705e1c18Yigit Boyar        mRecyclerView = null;
204b5f8b4fecb531aee6b359b4968409410aba90817Yigit Boyar    }
205b5f8b4fecb531aee6b359b4968409410aba90817Yigit Boyar
206504c54ea52c1b2aae6f8f4ae128f1dcaac7e3f6aYigit Boyar    void waitForAnimations(int seconds) throws InterruptedException {
207504c54ea52c1b2aae6f8f4ae128f1dcaac7e3f6aYigit Boyar        final CountDownLatch latch = new CountDownLatch(2);
208504c54ea52c1b2aae6f8f4ae128f1dcaac7e3f6aYigit Boyar        boolean running = mRecyclerView.mItemAnimator
209504c54ea52c1b2aae6f8f4ae128f1dcaac7e3f6aYigit Boyar                .isRunning(new RecyclerView.ItemAnimator.ItemAnimatorFinishedListener() {
210504c54ea52c1b2aae6f8f4ae128f1dcaac7e3f6aYigit Boyar                    @Override
211504c54ea52c1b2aae6f8f4ae128f1dcaac7e3f6aYigit Boyar                    public void onAnimationsFinished() {
212504c54ea52c1b2aae6f8f4ae128f1dcaac7e3f6aYigit Boyar                        latch.countDown();
213504c54ea52c1b2aae6f8f4ae128f1dcaac7e3f6aYigit Boyar                    }
214504c54ea52c1b2aae6f8f4ae128f1dcaac7e3f6aYigit Boyar                });
215504c54ea52c1b2aae6f8f4ae128f1dcaac7e3f6aYigit Boyar        if (running) {
216504c54ea52c1b2aae6f8f4ae128f1dcaac7e3f6aYigit Boyar            latch.await(seconds, TimeUnit.SECONDS);
217504c54ea52c1b2aae6f8f4ae128f1dcaac7e3f6aYigit Boyar        }
218504c54ea52c1b2aae6f8f4ae128f1dcaac7e3f6aYigit Boyar    }
219504c54ea52c1b2aae6f8f4ae128f1dcaac7e3f6aYigit Boyar
220542f1260934df280985294eaef1ec8469863281fYigit Boyar    public boolean requestFocus(final View view) {
221542f1260934df280985294eaef1ec8469863281fYigit Boyar        final boolean[] result = new boolean[1];
222542f1260934df280985294eaef1ec8469863281fYigit Boyar        getActivity().runOnUiThread(new Runnable() {
223542f1260934df280985294eaef1ec8469863281fYigit Boyar            @Override
224542f1260934df280985294eaef1ec8469863281fYigit Boyar            public void run() {
225542f1260934df280985294eaef1ec8469863281fYigit Boyar                result[0] = view.requestFocus();
226542f1260934df280985294eaef1ec8469863281fYigit Boyar            }
227542f1260934df280985294eaef1ec8469863281fYigit Boyar        });
228542f1260934df280985294eaef1ec8469863281fYigit Boyar        return result[0];
229542f1260934df280985294eaef1ec8469863281fYigit Boyar    }
230542f1260934df280985294eaef1ec8469863281fYigit Boyar
231b5f8b4fecb531aee6b359b4968409410aba90817Yigit Boyar    public void setRecyclerView(final RecyclerView recyclerView) throws Throwable {
2320b1059b711009601ff08354e2df6bfc45266e80aYigit Boyar        setRecyclerView(recyclerView, true);
2330b1059b711009601ff08354e2df6bfc45266e80aYigit Boyar    }
2340b1059b711009601ff08354e2df6bfc45266e80aYigit Boyar    public void setRecyclerView(final RecyclerView recyclerView, boolean assignDummyPool)
2350b1059b711009601ff08354e2df6bfc45266e80aYigit Boyar            throws Throwable {
236e37d2e63f16bc5c2938e372238ba54679a9e578eYigit Boyar        setRecyclerView(recyclerView, true, true);
237e37d2e63f16bc5c2938e372238ba54679a9e578eYigit Boyar    }
238e37d2e63f16bc5c2938e372238ba54679a9e578eYigit Boyar    public void setRecyclerView(final RecyclerView recyclerView, boolean assignDummyPool,
239e37d2e63f16bc5c2938e372238ba54679a9e578eYigit Boyar            boolean addPositionCheckItemAnimator)
240e37d2e63f16bc5c2938e372238ba54679a9e578eYigit Boyar            throws Throwable {
241b5f8b4fecb531aee6b359b4968409410aba90817Yigit Boyar        mRecyclerView = recyclerView;
2420b1059b711009601ff08354e2df6bfc45266e80aYigit Boyar        if (assignDummyPool) {
2430b1059b711009601ff08354e2df6bfc45266e80aYigit Boyar            RecyclerView.RecycledViewPool pool = new RecyclerView.RecycledViewPool() {
2440b1059b711009601ff08354e2df6bfc45266e80aYigit Boyar                @Override
2450b1059b711009601ff08354e2df6bfc45266e80aYigit Boyar                public RecyclerView.ViewHolder getRecycledView(int viewType) {
2460b1059b711009601ff08354e2df6bfc45266e80aYigit Boyar                    RecyclerView.ViewHolder viewHolder = super.getRecycledView(viewType);
2470b1059b711009601ff08354e2df6bfc45266e80aYigit Boyar                    if (viewHolder == null) {
2480b1059b711009601ff08354e2df6bfc45266e80aYigit Boyar                        return null;
2490b1059b711009601ff08354e2df6bfc45266e80aYigit Boyar                    }
2500b1059b711009601ff08354e2df6bfc45266e80aYigit Boyar                    viewHolder.addFlags(RecyclerView.ViewHolder.FLAG_BOUND);
2510b1059b711009601ff08354e2df6bfc45266e80aYigit Boyar                    viewHolder.mPosition = 200;
2520b1059b711009601ff08354e2df6bfc45266e80aYigit Boyar                    viewHolder.mOldPosition = 300;
2530b1059b711009601ff08354e2df6bfc45266e80aYigit Boyar                    viewHolder.mPreLayoutPosition = 500;
2540b1059b711009601ff08354e2df6bfc45266e80aYigit Boyar                    return viewHolder;
2558da5bba529a2fe9bd2aafab28010e11ec4de93c7Yigit Boyar                }
2568da5bba529a2fe9bd2aafab28010e11ec4de93c7Yigit Boyar
2570b1059b711009601ff08354e2df6bfc45266e80aYigit Boyar                @Override
2580b1059b711009601ff08354e2df6bfc45266e80aYigit Boyar                public void putRecycledView(RecyclerView.ViewHolder scrap) {
259619ac016831a5ad75c2e0921c9036b6f030a9e63Yigit Boyar                    assertNull(scrap.mOwnerRecyclerView);
2600b1059b711009601ff08354e2df6bfc45266e80aYigit Boyar                    super.putRecycledView(scrap);
2610b1059b711009601ff08354e2df6bfc45266e80aYigit Boyar                }
2620b1059b711009601ff08354e2df6bfc45266e80aYigit Boyar            };
2630b1059b711009601ff08354e2df6bfc45266e80aYigit Boyar            mRecyclerView.setRecycledViewPool(pool);
2640b1059b711009601ff08354e2df6bfc45266e80aYigit Boyar        }
265e37d2e63f16bc5c2938e372238ba54679a9e578eYigit Boyar        if (addPositionCheckItemAnimator) {
266e37d2e63f16bc5c2938e372238ba54679a9e578eYigit Boyar            mRecyclerView.addItemDecoration(new RecyclerView.ItemDecoration() {
267e37d2e63f16bc5c2938e372238ba54679a9e578eYigit Boyar                @Override
268e37d2e63f16bc5c2938e372238ba54679a9e578eYigit Boyar                public void getItemOffsets(Rect outRect, View view, RecyclerView parent,
269e37d2e63f16bc5c2938e372238ba54679a9e578eYigit Boyar                        RecyclerView.State state) {
270e37d2e63f16bc5c2938e372238ba54679a9e578eYigit Boyar                    RecyclerView.ViewHolder vh = parent.getChildViewHolder(view);
271e37d2e63f16bc5c2938e372238ba54679a9e578eYigit Boyar                    if (!vh.isRemoved()) {
272e37d2e63f16bc5c2938e372238ba54679a9e578eYigit Boyar                        assertNotSame("If getItemOffsets is called, child should have a valid"
273619ac016831a5ad75c2e0921c9036b6f030a9e63Yigit Boyar                                            + " adapter position unless it is removed : " + vh,
274e37d2e63f16bc5c2938e372238ba54679a9e578eYigit Boyar                                    vh.getAdapterPosition(), RecyclerView.NO_POSITION);
275e37d2e63f16bc5c2938e372238ba54679a9e578eYigit Boyar                    }
276e37d2e63f16bc5c2938e372238ba54679a9e578eYigit Boyar                }
277e37d2e63f16bc5c2938e372238ba54679a9e578eYigit Boyar            });
278e37d2e63f16bc5c2938e372238ba54679a9e578eYigit Boyar        }
2798ae76f91527ce850f155ce960fb9068bcd5d49f9Yigit Boyar        mAdapterHelper = recyclerView.mAdapterHelper;
280b5f8b4fecb531aee6b359b4968409410aba90817Yigit Boyar        runTestOnUiThread(new Runnable() {
281b5f8b4fecb531aee6b359b4968409410aba90817Yigit Boyar            @Override
282b5f8b4fecb531aee6b359b4968409410aba90817Yigit Boyar            public void run() {
283b5f8b4fecb531aee6b359b4968409410aba90817Yigit Boyar                getActivity().mContainer.addView(recyclerView);
284b5f8b4fecb531aee6b359b4968409410aba90817Yigit Boyar            }
285b5f8b4fecb531aee6b359b4968409410aba90817Yigit Boyar        });
286b5f8b4fecb531aee6b359b4968409410aba90817Yigit Boyar    }
287b5f8b4fecb531aee6b359b4968409410aba90817Yigit Boyar
288310e95e1c6dfe4f26ef594233e65e1ff83e0f1ffYigit Boyar    protected FrameLayout getRecyclerViewContainer() {
289310e95e1c6dfe4f26ef594233e65e1ff83e0f1ffYigit Boyar        return getActivity().mContainer;
290310e95e1c6dfe4f26ef594233e65e1ff83e0f1ffYigit Boyar    }
291310e95e1c6dfe4f26ef594233e65e1ff83e0f1ffYigit Boyar
292e2437126f0225135891544315ff72e54c7de0f59Yigit Boyar    public void requestLayoutOnUIThread(final View view) {
293e2437126f0225135891544315ff72e54c7de0f59Yigit Boyar        try {
294e2437126f0225135891544315ff72e54c7de0f59Yigit Boyar            runTestOnUiThread(new Runnable() {
295e2437126f0225135891544315ff72e54c7de0f59Yigit Boyar                @Override
296e2437126f0225135891544315ff72e54c7de0f59Yigit Boyar                public void run() {
297e2437126f0225135891544315ff72e54c7de0f59Yigit Boyar                    view.requestLayout();
298e2437126f0225135891544315ff72e54c7de0f59Yigit Boyar                }
299e2437126f0225135891544315ff72e54c7de0f59Yigit Boyar            });
300e2437126f0225135891544315ff72e54c7de0f59Yigit Boyar        } catch (Throwable throwable) {
301e2437126f0225135891544315ff72e54c7de0f59Yigit Boyar            Log.e(TAG, "", throwable);
302e2437126f0225135891544315ff72e54c7de0f59Yigit Boyar        }
303b5f8b4fecb531aee6b359b4968409410aba90817Yigit Boyar    }
304b5f8b4fecb531aee6b359b4968409410aba90817Yigit Boyar
305e2437126f0225135891544315ff72e54c7de0f59Yigit Boyar    public void scrollBy(final int dt) {
306e2437126f0225135891544315ff72e54c7de0f59Yigit Boyar        try {
307e2437126f0225135891544315ff72e54c7de0f59Yigit Boyar            runTestOnUiThread(new Runnable() {
308e2437126f0225135891544315ff72e54c7de0f59Yigit Boyar                @Override
309e2437126f0225135891544315ff72e54c7de0f59Yigit Boyar                public void run() {
310e2437126f0225135891544315ff72e54c7de0f59Yigit Boyar                    if (mRecyclerView.getLayoutManager().canScrollHorizontally()) {
311e2437126f0225135891544315ff72e54c7de0f59Yigit Boyar                        mRecyclerView.scrollBy(dt, 0);
312e2437126f0225135891544315ff72e54c7de0f59Yigit Boyar                    } else {
313e2437126f0225135891544315ff72e54c7de0f59Yigit Boyar                        mRecyclerView.scrollBy(0, dt);
314e2437126f0225135891544315ff72e54c7de0f59Yigit Boyar                    }
315b5f8b4fecb531aee6b359b4968409410aba90817Yigit Boyar
316e2437126f0225135891544315ff72e54c7de0f59Yigit Boyar                }
317e2437126f0225135891544315ff72e54c7de0f59Yigit Boyar            });
318e2437126f0225135891544315ff72e54c7de0f59Yigit Boyar        } catch (Throwable throwable) {
319e2437126f0225135891544315ff72e54c7de0f59Yigit Boyar            Log.e(TAG, "", throwable);
320e2437126f0225135891544315ff72e54c7de0f59Yigit Boyar        }
321d7d27e9ebe5c7325e67e1a8af265378bd2056cadChet Haase    }
322d7d27e9ebe5c7325e67e1a8af265378bd2056cadChet Haase
3238edcb0bdeaba6931f9d8154f0c81f57da7ddab2aYigit Boyar    void scrollToPosition(final int position) throws Throwable {
3248edcb0bdeaba6931f9d8154f0c81f57da7ddab2aYigit Boyar        runTestOnUiThread(new Runnable() {
3258edcb0bdeaba6931f9d8154f0c81f57da7ddab2aYigit Boyar            @Override
3268edcb0bdeaba6931f9d8154f0c81f57da7ddab2aYigit Boyar            public void run() {
3278edcb0bdeaba6931f9d8154f0c81f57da7ddab2aYigit Boyar                mRecyclerView.getLayoutManager().scrollToPosition(position);
3288edcb0bdeaba6931f9d8154f0c81f57da7ddab2aYigit Boyar            }
3298edcb0bdeaba6931f9d8154f0c81f57da7ddab2aYigit Boyar        });
3308edcb0bdeaba6931f9d8154f0c81f57da7ddab2aYigit Boyar    }
3318edcb0bdeaba6931f9d8154f0c81f57da7ddab2aYigit Boyar
3328edcb0bdeaba6931f9d8154f0c81f57da7ddab2aYigit Boyar    void smoothScrollToPosition(final int position)
3338edcb0bdeaba6931f9d8154f0c81f57da7ddab2aYigit Boyar            throws Throwable {
334619ac016831a5ad75c2e0921c9036b6f030a9e63Yigit Boyar        if (mDebug) {
335619ac016831a5ad75c2e0921c9036b6f030a9e63Yigit Boyar            Log.d(TAG, "SMOOTH scrolling to " + position);
336619ac016831a5ad75c2e0921c9036b6f030a9e63Yigit Boyar        }
3378edcb0bdeaba6931f9d8154f0c81f57da7ddab2aYigit Boyar        runTestOnUiThread(new Runnable() {
3388edcb0bdeaba6931f9d8154f0c81f57da7ddab2aYigit Boyar            @Override
3398edcb0bdeaba6931f9d8154f0c81f57da7ddab2aYigit Boyar            public void run() {
3408edcb0bdeaba6931f9d8154f0c81f57da7ddab2aYigit Boyar                mRecyclerView.smoothScrollToPosition(position);
3418edcb0bdeaba6931f9d8154f0c81f57da7ddab2aYigit Boyar            }
3428edcb0bdeaba6931f9d8154f0c81f57da7ddab2aYigit Boyar        });
343de5f54783555fa2e778c4ed6760472a002b2589bYigit Boyar        getInstrumentation().waitForIdleSync();
344de5f54783555fa2e778c4ed6760472a002b2589bYigit Boyar        Thread.sleep(200); //give scroller some time so start
3458edcb0bdeaba6931f9d8154f0c81f57da7ddab2aYigit Boyar        while (mRecyclerView.getLayoutManager().isSmoothScrolling() ||
3468edcb0bdeaba6931f9d8154f0c81f57da7ddab2aYigit Boyar                mRecyclerView.getScrollState() != RecyclerView.SCROLL_STATE_IDLE) {
3478edcb0bdeaba6931f9d8154f0c81f57da7ddab2aYigit Boyar            if (mDebug) {
3488edcb0bdeaba6931f9d8154f0c81f57da7ddab2aYigit Boyar                Log.d(TAG, "SMOOTH scrolling step");
3498edcb0bdeaba6931f9d8154f0c81f57da7ddab2aYigit Boyar            }
3508edcb0bdeaba6931f9d8154f0c81f57da7ddab2aYigit Boyar            Thread.sleep(200);
3518edcb0bdeaba6931f9d8154f0c81f57da7ddab2aYigit Boyar        }
352619ac016831a5ad75c2e0921c9036b6f030a9e63Yigit Boyar        if (mDebug) {
353619ac016831a5ad75c2e0921c9036b6f030a9e63Yigit Boyar            Log.d(TAG, "SMOOTH scrolling done");
354619ac016831a5ad75c2e0921c9036b6f030a9e63Yigit Boyar        }
355504c54ea52c1b2aae6f8f4ae128f1dcaac7e3f6aYigit Boyar        getInstrumentation().waitForIdleSync();
3568edcb0bdeaba6931f9d8154f0c81f57da7ddab2aYigit Boyar    }
3578edcb0bdeaba6931f9d8154f0c81f57da7ddab2aYigit Boyar
358d7d27e9ebe5c7325e67e1a8af265378bd2056cadChet Haase    class TestViewHolder extends RecyclerView.ViewHolder {
359d7d27e9ebe5c7325e67e1a8af265378bd2056cadChet Haase
360115ba0c7b2a14aa4cd0273952195e1d8f6468f87Yigit Boyar        Item mBoundItem;
361d7d27e9ebe5c7325e67e1a8af265378bd2056cadChet Haase
362d7d27e9ebe5c7325e67e1a8af265378bd2056cadChet Haase        public TestViewHolder(View itemView) {
363d7d27e9ebe5c7325e67e1a8af265378bd2056cadChet Haase            super(itemView);
364310e95e1c6dfe4f26ef594233e65e1ff83e0f1ffYigit Boyar            itemView.setFocusable(true);
365d7d27e9ebe5c7325e67e1a8af265378bd2056cadChet Haase        }
366504c54ea52c1b2aae6f8f4ae128f1dcaac7e3f6aYigit Boyar
367504c54ea52c1b2aae6f8f4ae128f1dcaac7e3f6aYigit Boyar        @Override
368504c54ea52c1b2aae6f8f4ae128f1dcaac7e3f6aYigit Boyar        public String toString() {
369115ba0c7b2a14aa4cd0273952195e1d8f6468f87Yigit Boyar            return super.toString() + " item:" + mBoundItem;
370504c54ea52c1b2aae6f8f4ae128f1dcaac7e3f6aYigit Boyar        }
371d7d27e9ebe5c7325e67e1a8af265378bd2056cadChet Haase    }
372da50fde358bd3ef2edb273b3bf0265cf5e84ec6bYigit Boyar    class DumbLayoutManager extends TestLayoutManager {
373da50fde358bd3ef2edb273b3bf0265cf5e84ec6bYigit Boyar        ReentrantLock mLayoutLock = new ReentrantLock();
374da50fde358bd3ef2edb273b3bf0265cf5e84ec6bYigit Boyar        public void blockLayout() {
375da50fde358bd3ef2edb273b3bf0265cf5e84ec6bYigit Boyar            mLayoutLock.lock();
376da50fde358bd3ef2edb273b3bf0265cf5e84ec6bYigit Boyar        }
377d7d27e9ebe5c7325e67e1a8af265378bd2056cadChet Haase
378da50fde358bd3ef2edb273b3bf0265cf5e84ec6bYigit Boyar        public void unblockLayout() {
379da50fde358bd3ef2edb273b3bf0265cf5e84ec6bYigit Boyar            mLayoutLock.unlock();
380da50fde358bd3ef2edb273b3bf0265cf5e84ec6bYigit Boyar        }
381da50fde358bd3ef2edb273b3bf0265cf5e84ec6bYigit Boyar        @Override
382da50fde358bd3ef2edb273b3bf0265cf5e84ec6bYigit Boyar        public void onLayoutChildren(RecyclerView.Recycler recycler, RecyclerView.State state) {
383da50fde358bd3ef2edb273b3bf0265cf5e84ec6bYigit Boyar            mLayoutLock.lock();
384da50fde358bd3ef2edb273b3bf0265cf5e84ec6bYigit Boyar            detachAndScrapAttachedViews(recycler);
385da50fde358bd3ef2edb273b3bf0265cf5e84ec6bYigit Boyar            layoutRange(recycler, 0, state.getItemCount());
386da50fde358bd3ef2edb273b3bf0265cf5e84ec6bYigit Boyar            if (layoutLatch != null) {
387da50fde358bd3ef2edb273b3bf0265cf5e84ec6bYigit Boyar                layoutLatch.countDown();
388da50fde358bd3ef2edb273b3bf0265cf5e84ec6bYigit Boyar            }
389da50fde358bd3ef2edb273b3bf0265cf5e84ec6bYigit Boyar            mLayoutLock.unlock();
390da50fde358bd3ef2edb273b3bf0265cf5e84ec6bYigit Boyar        }
391da50fde358bd3ef2edb273b3bf0265cf5e84ec6bYigit Boyar    }
392e71a1df9b3c0e1bd3c21a1b3dd20a41790d4a950Yigit Boyar    public class TestLayoutManager extends RecyclerView.LayoutManager {
393e71a1df9b3c0e1bd3c21a1b3dd20a41790d4a950Yigit Boyar        protected CountDownLatch layoutLatch;
394d7d27e9ebe5c7325e67e1a8af265378bd2056cadChet Haase
395d7d27e9ebe5c7325e67e1a8af265378bd2056cadChet Haase        public void expectLayouts(int count) {
396d7d27e9ebe5c7325e67e1a8af265378bd2056cadChet Haase            layoutLatch = new CountDownLatch(count);
397d7d27e9ebe5c7325e67e1a8af265378bd2056cadChet Haase        }
398d7d27e9ebe5c7325e67e1a8af265378bd2056cadChet Haase
399b5f8b4fecb531aee6b359b4968409410aba90817Yigit Boyar        public void waitForLayout(long timeout, TimeUnit timeUnit) throws Throwable {
400b5f8b4fecb531aee6b359b4968409410aba90817Yigit Boyar            layoutLatch.await(timeout * (mDebug ? 100 : 1), timeUnit);
401d7d27e9ebe5c7325e67e1a8af265378bd2056cadChet Haase            assertEquals("all expected layouts should be executed at the expected time",
402d7d27e9ebe5c7325e67e1a8af265378bd2056cadChet Haase                    0, layoutLatch.getCount());
403e0c347f627f8a78d3e5e3e5eaac9c3ae26208689Yigit Boyar            getInstrumentation().waitForIdleSync();
404d7d27e9ebe5c7325e67e1a8af265378bd2056cadChet Haase        }
405d7d27e9ebe5c7325e67e1a8af265378bd2056cadChet Haase
406b5f8b4fecb531aee6b359b4968409410aba90817Yigit Boyar        public void assertLayoutCount(int count, String msg, long timeout) throws Throwable {
407b5f8b4fecb531aee6b359b4968409410aba90817Yigit Boyar            layoutLatch.await(timeout, TimeUnit.SECONDS);
408b5f8b4fecb531aee6b359b4968409410aba90817Yigit Boyar            assertEquals(msg, count, layoutLatch.getCount());
409b5f8b4fecb531aee6b359b4968409410aba90817Yigit Boyar        }
410b5f8b4fecb531aee6b359b4968409410aba90817Yigit Boyar
411b5f8b4fecb531aee6b359b4968409410aba90817Yigit Boyar        public void assertNoLayout(String msg, long timeout) throws Throwable {
412b5f8b4fecb531aee6b359b4968409410aba90817Yigit Boyar            layoutLatch.await(timeout, TimeUnit.SECONDS);
413b5f8b4fecb531aee6b359b4968409410aba90817Yigit Boyar            assertFalse(msg, layoutLatch.getCount() == 0);
414b5f8b4fecb531aee6b359b4968409410aba90817Yigit Boyar        }
415b5f8b4fecb531aee6b359b4968409410aba90817Yigit Boyar
416b5f8b4fecb531aee6b359b4968409410aba90817Yigit Boyar        public void waitForLayout(long timeout) throws Throwable {
4178ae76f91527ce850f155ce960fb9068bcd5d49f9Yigit Boyar            waitForLayout(timeout * (mDebug ? 10000 : 1), TimeUnit.SECONDS);
418b5f8b4fecb531aee6b359b4968409410aba90817Yigit Boyar        }
419b5f8b4fecb531aee6b359b4968409410aba90817Yigit Boyar
420d7d27e9ebe5c7325e67e1a8af265378bd2056cadChet Haase        @Override
421d7d27e9ebe5c7325e67e1a8af265378bd2056cadChet Haase        public RecyclerView.LayoutParams generateDefaultLayoutParams() {
422d7d27e9ebe5c7325e67e1a8af265378bd2056cadChet Haase            return new RecyclerView.LayoutParams(ViewGroup.LayoutParams.WRAP_CONTENT,
423d7d27e9ebe5c7325e67e1a8af265378bd2056cadChet Haase                    ViewGroup.LayoutParams.WRAP_CONTENT);
424d7d27e9ebe5c7325e67e1a8af265378bd2056cadChet Haase        }
425d7d27e9ebe5c7325e67e1a8af265378bd2056cadChet Haase
426d7d27e9ebe5c7325e67e1a8af265378bd2056cadChet Haase        void assertVisibleItemPositions() {
427d7d27e9ebe5c7325e67e1a8af265378bd2056cadChet Haase            int i = getChildCount();
428d7d27e9ebe5c7325e67e1a8af265378bd2056cadChet Haase            TestAdapter testAdapter = (TestAdapter) mRecyclerView.getAdapter();
429d7d27e9ebe5c7325e67e1a8af265378bd2056cadChet Haase            while (i-- > 0) {
430d7d27e9ebe5c7325e67e1a8af265378bd2056cadChet Haase                View view = getChildAt(i);
431d7d27e9ebe5c7325e67e1a8af265378bd2056cadChet Haase                RecyclerView.LayoutParams lp = (RecyclerView.LayoutParams) view.getLayoutParams();
432115ba0c7b2a14aa4cd0273952195e1d8f6468f87Yigit Boyar                Item item = ((TestViewHolder) lp.mViewHolder).mBoundItem;
433b5f8b4fecb531aee6b359b4968409410aba90817Yigit Boyar                if (mDebug) {
434d7d27e9ebe5c7325e67e1a8af265378bd2056cadChet Haase                    Log.d(TAG, "testing item " + i);
435d7d27e9ebe5c7325e67e1a8af265378bd2056cadChet Haase                }
4368ae76f91527ce850f155ce960fb9068bcd5d49f9Yigit Boyar                if (!lp.isItemRemoved()) {
4378ae76f91527ce850f155ce960fb9068bcd5d49f9Yigit Boyar                    RecyclerView.ViewHolder vh = mRecyclerView.getChildViewHolder(view);
4388ae76f91527ce850f155ce960fb9068bcd5d49f9Yigit Boyar                    assertSame("item position in LP should match adapter value :" + vh,
4398ae76f91527ce850f155ce960fb9068bcd5d49f9Yigit Boyar                            testAdapter.mItems.get(vh.mPosition), item);
4408ae76f91527ce850f155ce960fb9068bcd5d49f9Yigit Boyar                }
441d7d27e9ebe5c7325e67e1a8af265378bd2056cadChet Haase            }
442d7d27e9ebe5c7325e67e1a8af265378bd2056cadChet Haase        }
443d7d27e9ebe5c7325e67e1a8af265378bd2056cadChet Haase
444b5f8b4fecb531aee6b359b4968409410aba90817Yigit Boyar        RecyclerView.LayoutParams getLp(View v) {
445b5f8b4fecb531aee6b359b4968409410aba90817Yigit Boyar            return (RecyclerView.LayoutParams) v.getLayoutParams();
446b5f8b4fecb531aee6b359b4968409410aba90817Yigit Boyar        }
447b5f8b4fecb531aee6b359b4968409410aba90817Yigit Boyar
448e71a1df9b3c0e1bd3c21a1b3dd20a41790d4a950Yigit Boyar        protected void layoutRange(RecyclerView.Recycler recycler, int start, int end) {
449fe3996cf5af65fe11cc3ed47b67123ec267bd336Yigit Boyar            assertScrap(recycler);
450b5f8b4fecb531aee6b359b4968409410aba90817Yigit Boyar            if (mDebug) {
451d7d27e9ebe5c7325e67e1a8af265378bd2056cadChet Haase                Log.d(TAG, "will layout items from " + start + " to " + end);
452d7d27e9ebe5c7325e67e1a8af265378bd2056cadChet Haase            }
4535ced882cabdcefbb469e332f6f73983999aad0e5Yigit Boyar            int diff = end > start ? 1 : -1;
454504c54ea52c1b2aae6f8f4ae128f1dcaac7e3f6aYigit Boyar            int top = 0;
4555ced882cabdcefbb469e332f6f73983999aad0e5Yigit Boyar            for (int i = start; i != end; i+=diff) {
456b5f8b4fecb531aee6b359b4968409410aba90817Yigit Boyar                if (mDebug) {
457d7d27e9ebe5c7325e67e1a8af265378bd2056cadChet Haase                    Log.d(TAG, "laying out item " + i);
458d7d27e9ebe5c7325e67e1a8af265378bd2056cadChet Haase                }
459d7d27e9ebe5c7325e67e1a8af265378bd2056cadChet Haase                View view = recycler.getViewForPosition(i);
460b5f8b4fecb531aee6b359b4968409410aba90817Yigit Boyar                assertNotNull("view should not be null for valid position. "
461b5f8b4fecb531aee6b359b4968409410aba90817Yigit Boyar                        + "got null view at position " + i, view);
462c8b0ff5869e17a656a3a1d7fb306efa5b99fa07fYigit Boyar                if (!mRecyclerView.mState.isPreLayout()) {
463c8b0ff5869e17a656a3a1d7fb306efa5b99fa07fYigit Boyar                    RecyclerView.LayoutParams layoutParams = (RecyclerView.LayoutParams) view
464c8b0ff5869e17a656a3a1d7fb306efa5b99fa07fYigit Boyar                            .getLayoutParams();
465c8b0ff5869e17a656a3a1d7fb306efa5b99fa07fYigit Boyar                    assertFalse("In post layout, getViewForPosition should never return a view "
466c8b0ff5869e17a656a3a1d7fb306efa5b99fa07fYigit Boyar                            + "that is removed", layoutParams != null
467c8b0ff5869e17a656a3a1d7fb306efa5b99fa07fYigit Boyar                            && layoutParams.isItemRemoved());
468c8b0ff5869e17a656a3a1d7fb306efa5b99fa07fYigit Boyar
469b5f8b4fecb531aee6b359b4968409410aba90817Yigit Boyar                }
4708da5bba529a2fe9bd2aafab28010e11ec4de93c7Yigit Boyar                assertEquals("getViewForPosition should return correct position",
4718da5bba529a2fe9bd2aafab28010e11ec4de93c7Yigit Boyar                        i, getPosition(view));
472c8b0ff5869e17a656a3a1d7fb306efa5b99fa07fYigit Boyar                addView(view);
473b5f8b4fecb531aee6b359b4968409410aba90817Yigit Boyar
474d7d27e9ebe5c7325e67e1a8af265378bd2056cadChet Haase                measureChildWithMargins(view, 0, 0);
475e71a1df9b3c0e1bd3c21a1b3dd20a41790d4a950Yigit Boyar                if (getLayoutDirection() == ViewCompat.LAYOUT_DIRECTION_RTL) {
476e71a1df9b3c0e1bd3c21a1b3dd20a41790d4a950Yigit Boyar                    layoutDecorated(view, getWidth() - getDecoratedMeasuredWidth(view), top,
477e71a1df9b3c0e1bd3c21a1b3dd20a41790d4a950Yigit Boyar                            getWidth(), top + getDecoratedMeasuredHeight(view));
478e71a1df9b3c0e1bd3c21a1b3dd20a41790d4a950Yigit Boyar                } else {
479e71a1df9b3c0e1bd3c21a1b3dd20a41790d4a950Yigit Boyar                    layoutDecorated(view, 0, top, getDecoratedMeasuredWidth(view)
480e71a1df9b3c0e1bd3c21a1b3dd20a41790d4a950Yigit Boyar                            , top + getDecoratedMeasuredHeight(view));
481e71a1df9b3c0e1bd3c21a1b3dd20a41790d4a950Yigit Boyar                }
482e71a1df9b3c0e1bd3c21a1b3dd20a41790d4a950Yigit Boyar
483504c54ea52c1b2aae6f8f4ae128f1dcaac7e3f6aYigit Boyar                top += view.getMeasuredHeight();
484d7d27e9ebe5c7325e67e1a8af265378bd2056cadChet Haase            }
485d7d27e9ebe5c7325e67e1a8af265378bd2056cadChet Haase        }
486fe3996cf5af65fe11cc3ed47b67123ec267bd336Yigit Boyar
487fe3996cf5af65fe11cc3ed47b67123ec267bd336Yigit Boyar        private void assertScrap(RecyclerView.Recycler recycler) {
488e0c347f627f8a78d3e5e3e5eaac9c3ae26208689Yigit Boyar            if (mRecyclerView.getAdapter() != null &&
489e0c347f627f8a78d3e5e3e5eaac9c3ae26208689Yigit Boyar                    !mRecyclerView.getAdapter().hasStableIds()) {
490e0c347f627f8a78d3e5e3e5eaac9c3ae26208689Yigit Boyar                for (RecyclerView.ViewHolder viewHolder : recycler.getScrapList()) {
491e0c347f627f8a78d3e5e3e5eaac9c3ae26208689Yigit Boyar                    assertFalse("Invalid scrap should be no kept", viewHolder.isInvalid());
492e0c347f627f8a78d3e5e3e5eaac9c3ae26208689Yigit Boyar                }
493fe3996cf5af65fe11cc3ed47b67123ec267bd336Yigit Boyar            }
494fe3996cf5af65fe11cc3ed47b67123ec267bd336Yigit Boyar        }
495bb6ebab3b3b85ec20b3bc262289a2460705322f5Yigit Boyar
496bb6ebab3b3b85ec20b3bc262289a2460705322f5Yigit Boyar        @Override
497bb6ebab3b3b85ec20b3bc262289a2460705322f5Yigit Boyar        public boolean canScrollHorizontally() {
498bb6ebab3b3b85ec20b3bc262289a2460705322f5Yigit Boyar            return true;
499bb6ebab3b3b85ec20b3bc262289a2460705322f5Yigit Boyar        }
500bb6ebab3b3b85ec20b3bc262289a2460705322f5Yigit Boyar
501bb6ebab3b3b85ec20b3bc262289a2460705322f5Yigit Boyar        @Override
502bb6ebab3b3b85ec20b3bc262289a2460705322f5Yigit Boyar        public boolean canScrollVertically() {
503bb6ebab3b3b85ec20b3bc262289a2460705322f5Yigit Boyar            return true;
504bb6ebab3b3b85ec20b3bc262289a2460705322f5Yigit Boyar        }
505bb6ebab3b3b85ec20b3bc262289a2460705322f5Yigit Boyar
506bb6ebab3b3b85ec20b3bc262289a2460705322f5Yigit Boyar        @Override
507bb6ebab3b3b85ec20b3bc262289a2460705322f5Yigit Boyar        public int scrollHorizontallyBy(int dx, RecyclerView.Recycler recycler,
508bb6ebab3b3b85ec20b3bc262289a2460705322f5Yigit Boyar                RecyclerView.State state) {
509bb6ebab3b3b85ec20b3bc262289a2460705322f5Yigit Boyar            return dx;
510bb6ebab3b3b85ec20b3bc262289a2460705322f5Yigit Boyar        }
511bb6ebab3b3b85ec20b3bc262289a2460705322f5Yigit Boyar
512bb6ebab3b3b85ec20b3bc262289a2460705322f5Yigit Boyar        @Override
513bb6ebab3b3b85ec20b3bc262289a2460705322f5Yigit Boyar        public int scrollVerticallyBy(int dy, RecyclerView.Recycler recycler,
514bb6ebab3b3b85ec20b3bc262289a2460705322f5Yigit Boyar                RecyclerView.State state) {
515bb6ebab3b3b85ec20b3bc262289a2460705322f5Yigit Boyar            return dy;
516bb6ebab3b3b85ec20b3bc262289a2460705322f5Yigit Boyar        }
517d7d27e9ebe5c7325e67e1a8af265378bd2056cadChet Haase    }
518d7d27e9ebe5c7325e67e1a8af265378bd2056cadChet Haase
519d7d27e9ebe5c7325e67e1a8af265378bd2056cadChet Haase    static class Item {
52011b7cfea15306c0d6e4ed7da09f7698df01695dfYigit Boyar        final static AtomicInteger idCounter = new AtomicInteger(0);
52111b7cfea15306c0d6e4ed7da09f7698df01695dfYigit Boyar        final public int mId = idCounter.incrementAndGet();
522d7d27e9ebe5c7325e67e1a8af265378bd2056cadChet Haase
5232d2e8d88103866b631eb0f3805da137ebcfb0275Yigit Boyar        int mAdapterIndex;
524d7d27e9ebe5c7325e67e1a8af265378bd2056cadChet Haase
5252d2e8d88103866b631eb0f3805da137ebcfb0275Yigit Boyar        final String mText;
526d7d27e9ebe5c7325e67e1a8af265378bd2056cadChet Haase
5272d2e8d88103866b631eb0f3805da137ebcfb0275Yigit Boyar        Item(int adapterIndex, String text) {
5282d2e8d88103866b631eb0f3805da137ebcfb0275Yigit Boyar            mAdapterIndex = adapterIndex;
5292d2e8d88103866b631eb0f3805da137ebcfb0275Yigit Boyar            mText = text;
530d7d27e9ebe5c7325e67e1a8af265378bd2056cadChet Haase        }
5318ae76f91527ce850f155ce960fb9068bcd5d49f9Yigit Boyar
5328ae76f91527ce850f155ce960fb9068bcd5d49f9Yigit Boyar        @Override
5338ae76f91527ce850f155ce960fb9068bcd5d49f9Yigit Boyar        public String toString() {
5348ae76f91527ce850f155ce960fb9068bcd5d49f9Yigit Boyar            return "Item{" +
5358ae76f91527ce850f155ce960fb9068bcd5d49f9Yigit Boyar                    "mId=" + mId +
5362d2e8d88103866b631eb0f3805da137ebcfb0275Yigit Boyar                    ", originalIndex=" + mAdapterIndex +
5372d2e8d88103866b631eb0f3805da137ebcfb0275Yigit Boyar                    ", text='" + mText + '\'' +
5388ae76f91527ce850f155ce960fb9068bcd5d49f9Yigit Boyar                    '}';
5398ae76f91527ce850f155ce960fb9068bcd5d49f9Yigit Boyar        }
540d7d27e9ebe5c7325e67e1a8af265378bd2056cadChet Haase    }
541d7d27e9ebe5c7325e67e1a8af265378bd2056cadChet Haase
542e71a1df9b3c0e1bd3c21a1b3dd20a41790d4a950Yigit Boyar    public class TestAdapter extends RecyclerView.Adapter<TestViewHolder>
54390ca3086dbf66ad6bb8840e46ec8524a705e1c18Yigit Boyar            implements AttachDetachCountingAdapter {
544d7d27e9ebe5c7325e67e1a8af265378bd2056cadChet Haase
54590ca3086dbf66ad6bb8840e46ec8524a705e1c18Yigit Boyar        ViewAttachDetachCounter mAttachmentCounter = new ViewAttachDetachCounter();
546d7d27e9ebe5c7325e67e1a8af265378bd2056cadChet Haase        List<Item> mItems;
547d7d27e9ebe5c7325e67e1a8af265378bd2056cadChet Haase
548e71a1df9b3c0e1bd3c21a1b3dd20a41790d4a950Yigit Boyar        public TestAdapter(int count) {
549d7d27e9ebe5c7325e67e1a8af265378bd2056cadChet Haase            mItems = new ArrayList<Item>(count);
550d7d27e9ebe5c7325e67e1a8af265378bd2056cadChet Haase            for (int i = 0; i < count; i++) {
551d7d27e9ebe5c7325e67e1a8af265378bd2056cadChet Haase                mItems.add(new Item(i, "Item " + i));
552d7d27e9ebe5c7325e67e1a8af265378bd2056cadChet Haase            }
553d7d27e9ebe5c7325e67e1a8af265378bd2056cadChet Haase        }
554d7d27e9ebe5c7325e67e1a8af265378bd2056cadChet Haase
555d7d27e9ebe5c7325e67e1a8af265378bd2056cadChet Haase        @Override
55690ca3086dbf66ad6bb8840e46ec8524a705e1c18Yigit Boyar        public void onViewAttachedToWindow(TestViewHolder holder) {
55790ca3086dbf66ad6bb8840e46ec8524a705e1c18Yigit Boyar            super.onViewAttachedToWindow(holder);
55890ca3086dbf66ad6bb8840e46ec8524a705e1c18Yigit Boyar            mAttachmentCounter.onViewAttached(holder);
55990ca3086dbf66ad6bb8840e46ec8524a705e1c18Yigit Boyar        }
56090ca3086dbf66ad6bb8840e46ec8524a705e1c18Yigit Boyar
56190ca3086dbf66ad6bb8840e46ec8524a705e1c18Yigit Boyar        @Override
56290ca3086dbf66ad6bb8840e46ec8524a705e1c18Yigit Boyar        public void onViewDetachedFromWindow(TestViewHolder holder) {
56390ca3086dbf66ad6bb8840e46ec8524a705e1c18Yigit Boyar            super.onViewDetachedFromWindow(holder);
56490ca3086dbf66ad6bb8840e46ec8524a705e1c18Yigit Boyar            mAttachmentCounter.onViewDetached(holder);
56590ca3086dbf66ad6bb8840e46ec8524a705e1c18Yigit Boyar        }
56690ca3086dbf66ad6bb8840e46ec8524a705e1c18Yigit Boyar
56790ca3086dbf66ad6bb8840e46ec8524a705e1c18Yigit Boyar        @Override
56890ca3086dbf66ad6bb8840e46ec8524a705e1c18Yigit Boyar        public void onAttachedToRecyclerView(RecyclerView recyclerView) {
56990ca3086dbf66ad6bb8840e46ec8524a705e1c18Yigit Boyar            super.onAttachedToRecyclerView(recyclerView);
57090ca3086dbf66ad6bb8840e46ec8524a705e1c18Yigit Boyar            mAttachmentCounter.onAttached(recyclerView);
57190ca3086dbf66ad6bb8840e46ec8524a705e1c18Yigit Boyar        }
57290ca3086dbf66ad6bb8840e46ec8524a705e1c18Yigit Boyar
57390ca3086dbf66ad6bb8840e46ec8524a705e1c18Yigit Boyar        @Override
57490ca3086dbf66ad6bb8840e46ec8524a705e1c18Yigit Boyar        public void onDetachedFromRecyclerView(RecyclerView recyclerView) {
57590ca3086dbf66ad6bb8840e46ec8524a705e1c18Yigit Boyar            super.onDetachedFromRecyclerView(recyclerView);
57690ca3086dbf66ad6bb8840e46ec8524a705e1c18Yigit Boyar            mAttachmentCounter.onDetached(recyclerView);
57790ca3086dbf66ad6bb8840e46ec8524a705e1c18Yigit Boyar        }
57890ca3086dbf66ad6bb8840e46ec8524a705e1c18Yigit Boyar
57990ca3086dbf66ad6bb8840e46ec8524a705e1c18Yigit Boyar        @Override
580d7d27e9ebe5c7325e67e1a8af265378bd2056cadChet Haase        public TestViewHolder onCreateViewHolder(ViewGroup parent,
581d7d27e9ebe5c7325e67e1a8af265378bd2056cadChet Haase                int viewType) {
582d7d27e9ebe5c7325e67e1a8af265378bd2056cadChet Haase            return new TestViewHolder(new TextView(parent.getContext()));
583d7d27e9ebe5c7325e67e1a8af265378bd2056cadChet Haase        }
584d7d27e9ebe5c7325e67e1a8af265378bd2056cadChet Haase
585d7d27e9ebe5c7325e67e1a8af265378bd2056cadChet Haase        @Override
586d7d27e9ebe5c7325e67e1a8af265378bd2056cadChet Haase        public void onBindViewHolder(TestViewHolder holder, int position) {
587619ac016831a5ad75c2e0921c9036b6f030a9e63Yigit Boyar            assertNotNull(holder.mOwnerRecyclerView);
588619ac016831a5ad75c2e0921c9036b6f030a9e63Yigit Boyar            assertEquals(position, holder.getAdapterPosition());
589d7d27e9ebe5c7325e67e1a8af265378bd2056cadChet Haase            final Item item = mItems.get(position);
5902d2e8d88103866b631eb0f3805da137ebcfb0275Yigit Boyar            ((TextView) (holder.itemView)).setText(item.mText + "(" + item.mAdapterIndex + ")");
591115ba0c7b2a14aa4cd0273952195e1d8f6468f87Yigit Boyar            holder.mBoundItem = item;
592d7d27e9ebe5c7325e67e1a8af265378bd2056cadChet Haase        }
593d7d27e9ebe5c7325e67e1a8af265378bd2056cadChet Haase
594888093b0a071a99d65c01116cf703d46b21f9918Yigit Boyar        public Item getItemAt(int position) {
595888093b0a071a99d65c01116cf703d46b21f9918Yigit Boyar            return mItems.get(position);
596888093b0a071a99d65c01116cf703d46b21f9918Yigit Boyar        }
597888093b0a071a99d65c01116cf703d46b21f9918Yigit Boyar
598619ac016831a5ad75c2e0921c9036b6f030a9e63Yigit Boyar        @Override
599619ac016831a5ad75c2e0921c9036b6f030a9e63Yigit Boyar        public void onViewRecycled(TestViewHolder holder) {
600619ac016831a5ad75c2e0921c9036b6f030a9e63Yigit Boyar            super.onViewRecycled(holder);
601619ac016831a5ad75c2e0921c9036b6f030a9e63Yigit Boyar            final int adapterPosition = holder.getAdapterPosition();
602619ac016831a5ad75c2e0921c9036b6f030a9e63Yigit Boyar            final boolean shouldHavePosition = !holder.isRemoved() && holder.isBound() &&
603619ac016831a5ad75c2e0921c9036b6f030a9e63Yigit Boyar                    !holder.isAdapterPositionUnknown() && !holder.isInvalid();
604619ac016831a5ad75c2e0921c9036b6f030a9e63Yigit Boyar            String log = "Position check for " + holder.toString();
605619ac016831a5ad75c2e0921c9036b6f030a9e63Yigit Boyar            assertEquals(log, shouldHavePosition, adapterPosition != RecyclerView.NO_POSITION);
606619ac016831a5ad75c2e0921c9036b6f030a9e63Yigit Boyar            if (shouldHavePosition) {
607619ac016831a5ad75c2e0921c9036b6f030a9e63Yigit Boyar                assertTrue(log, mItems.size() > adapterPosition);
608619ac016831a5ad75c2e0921c9036b6f030a9e63Yigit Boyar                assertSame(log, holder.mBoundItem, mItems.get(adapterPosition));
609619ac016831a5ad75c2e0921c9036b6f030a9e63Yigit Boyar            }
610619ac016831a5ad75c2e0921c9036b6f030a9e63Yigit Boyar        }
611619ac016831a5ad75c2e0921c9036b6f030a9e63Yigit Boyar
612b5f8b4fecb531aee6b359b4968409410aba90817Yigit Boyar        public void deleteAndNotify(final int start, final int count) throws Throwable {
613b5f8b4fecb531aee6b359b4968409410aba90817Yigit Boyar            deleteAndNotify(new int[]{start, count});
614b5f8b4fecb531aee6b359b4968409410aba90817Yigit Boyar        }
615b5f8b4fecb531aee6b359b4968409410aba90817Yigit Boyar
616b5f8b4fecb531aee6b359b4968409410aba90817Yigit Boyar        /**
617b5f8b4fecb531aee6b359b4968409410aba90817Yigit Boyar         * Deletes items in the given ranges.
618b5f8b4fecb531aee6b359b4968409410aba90817Yigit Boyar         * <p>
619b5f8b4fecb531aee6b359b4968409410aba90817Yigit Boyar         * Note that each operation affects the one after so you should offset them properly.
620b5f8b4fecb531aee6b359b4968409410aba90817Yigit Boyar         * <p>
621b5f8b4fecb531aee6b359b4968409410aba90817Yigit Boyar         * For example, if adapter has 5 items (A,B,C,D,E), and then you call this method with
622b5f8b4fecb531aee6b359b4968409410aba90817Yigit Boyar         * <code>[1, 2],[2, 1]</code>, it will first delete items B,C and the new adapter will be
623b5f8b4fecb531aee6b359b4968409410aba90817Yigit Boyar         * A D E. Then it will delete 2,1 which means it will delete E.
624b5f8b4fecb531aee6b359b4968409410aba90817Yigit Boyar         */
625b5f8b4fecb531aee6b359b4968409410aba90817Yigit Boyar        public void deleteAndNotify(final int[]... startCountTuples) throws Throwable {
6268ae76f91527ce850f155ce960fb9068bcd5d49f9Yigit Boyar            for (int[] tuple : startCountTuples) {
6278ae76f91527ce850f155ce960fb9068bcd5d49f9Yigit Boyar                tuple[1] = -tuple[1];
6288ae76f91527ce850f155ce960fb9068bcd5d49f9Yigit Boyar            }
6298ae76f91527ce850f155ce960fb9068bcd5d49f9Yigit Boyar            new AddRemoveRunnable(startCountTuples).runOnMainThread();
630d7d27e9ebe5c7325e67e1a8af265378bd2056cadChet Haase        }
631d7d27e9ebe5c7325e67e1a8af265378bd2056cadChet Haase
632c8b0ff5869e17a656a3a1d7fb306efa5b99fa07fYigit Boyar        @Override
633c8b0ff5869e17a656a3a1d7fb306efa5b99fa07fYigit Boyar        public long getItemId(int position) {
634c8b0ff5869e17a656a3a1d7fb306efa5b99fa07fYigit Boyar            return hasStableIds() ? mItems.get(position).mId : super.getItemId(position);
635c8b0ff5869e17a656a3a1d7fb306efa5b99fa07fYigit Boyar        }
636c8b0ff5869e17a656a3a1d7fb306efa5b99fa07fYigit Boyar
6372d2e8d88103866b631eb0f3805da137ebcfb0275Yigit Boyar        public void offsetOriginalIndices(int start, int offset) {
6382d2e8d88103866b631eb0f3805da137ebcfb0275Yigit Boyar            for (int i = start; i < mItems.size(); i++) {
6392d2e8d88103866b631eb0f3805da137ebcfb0275Yigit Boyar                mItems.get(i).mAdapterIndex += offset;
6402d2e8d88103866b631eb0f3805da137ebcfb0275Yigit Boyar            }
6412d2e8d88103866b631eb0f3805da137ebcfb0275Yigit Boyar        }
6422d2e8d88103866b631eb0f3805da137ebcfb0275Yigit Boyar
643e0c347f627f8a78d3e5e3e5eaac9c3ae26208689Yigit Boyar        /**
644e0c347f627f8a78d3e5e3e5eaac9c3ae26208689Yigit Boyar         * @param start inclusive
645e0c347f627f8a78d3e5e3e5eaac9c3ae26208689Yigit Boyar         * @param end exclusive
646e0c347f627f8a78d3e5e3e5eaac9c3ae26208689Yigit Boyar         * @param offset
647e0c347f627f8a78d3e5e3e5eaac9c3ae26208689Yigit Boyar         */
648e0c347f627f8a78d3e5e3e5eaac9c3ae26208689Yigit Boyar        public void offsetOriginalIndicesBetween(int start, int end, int offset) {
649e0c347f627f8a78d3e5e3e5eaac9c3ae26208689Yigit Boyar            for (int i = start; i < end && i < mItems.size(); i++) {
650e0c347f627f8a78d3e5e3e5eaac9c3ae26208689Yigit Boyar                mItems.get(i).mAdapterIndex += offset;
651e0c347f627f8a78d3e5e3e5eaac9c3ae26208689Yigit Boyar            }
652e0c347f627f8a78d3e5e3e5eaac9c3ae26208689Yigit Boyar        }
653e0c347f627f8a78d3e5e3e5eaac9c3ae26208689Yigit Boyar
654b5f8b4fecb531aee6b359b4968409410aba90817Yigit Boyar        public void addAndNotify(final int start, final int count) throws Throwable {
655b5f8b4fecb531aee6b359b4968409410aba90817Yigit Boyar            addAndNotify(new int[]{start, count});
656b5f8b4fecb531aee6b359b4968409410aba90817Yigit Boyar        }
657b5f8b4fecb531aee6b359b4968409410aba90817Yigit Boyar
658b5f8b4fecb531aee6b359b4968409410aba90817Yigit Boyar        public void addAndNotify(final int[]... startCountTuples) throws Throwable {
6598ae76f91527ce850f155ce960fb9068bcd5d49f9Yigit Boyar            new AddRemoveRunnable(startCountTuples).runOnMainThread();
6608ae76f91527ce850f155ce960fb9068bcd5d49f9Yigit Boyar        }
661b5f8b4fecb531aee6b359b4968409410aba90817Yigit Boyar
662cadd54707a7aaa4cfa9c8b8051c6185e8717f22eChet Haase        public void dispatchDataSetChanged() throws Throwable {
66322b6b8163d2e15eb6d0a89906854dec801945f1dYigit Boyar            runTestOnUiThread(new Runnable() {
66422b6b8163d2e15eb6d0a89906854dec801945f1dYigit Boyar                @Override
66522b6b8163d2e15eb6d0a89906854dec801945f1dYigit Boyar                public void run() {
66622b6b8163d2e15eb6d0a89906854dec801945f1dYigit Boyar                    notifyDataSetChanged();
66722b6b8163d2e15eb6d0a89906854dec801945f1dYigit Boyar                }
66822b6b8163d2e15eb6d0a89906854dec801945f1dYigit Boyar            });
66922b6b8163d2e15eb6d0a89906854dec801945f1dYigit Boyar        }
67022b6b8163d2e15eb6d0a89906854dec801945f1dYigit Boyar
671cadd54707a7aaa4cfa9c8b8051c6185e8717f22eChet Haase        public void changeAndNotify(final int start, final int count) throws Throwable {
6727e9da1a2629b657d56e2f54abd18c6249f3be037Yigit Boyar            runTestOnUiThread(new Runnable() {
6737e9da1a2629b657d56e2f54abd18c6249f3be037Yigit Boyar                @Override
6747e9da1a2629b657d56e2f54abd18c6249f3be037Yigit Boyar                public void run() {
6757e9da1a2629b657d56e2f54abd18c6249f3be037Yigit Boyar                    notifyItemRangeChanged(start, count);
6767e9da1a2629b657d56e2f54abd18c6249f3be037Yigit Boyar                }
6777e9da1a2629b657d56e2f54abd18c6249f3be037Yigit Boyar            });
6787e9da1a2629b657d56e2f54abd18c6249f3be037Yigit Boyar        }
6797e9da1a2629b657d56e2f54abd18c6249f3be037Yigit Boyar
680cadd54707a7aaa4cfa9c8b8051c6185e8717f22eChet Haase        public void changePositionsAndNotify(final int... positions) throws Throwable {
681cadd54707a7aaa4cfa9c8b8051c6185e8717f22eChet Haase            runTestOnUiThread(new Runnable() {
682cadd54707a7aaa4cfa9c8b8051c6185e8717f22eChet Haase                @Override
683cadd54707a7aaa4cfa9c8b8051c6185e8717f22eChet Haase                public void run() {
684cadd54707a7aaa4cfa9c8b8051c6185e8717f22eChet Haase                    for (int i = 0; i < positions.length; i += 1) {
685cadd54707a7aaa4cfa9c8b8051c6185e8717f22eChet Haase                        TestAdapter.super.notifyItemRangeChanged(positions[i], 1);
686cadd54707a7aaa4cfa9c8b8051c6185e8717f22eChet Haase                    }
687cadd54707a7aaa4cfa9c8b8051c6185e8717f22eChet Haase                }
688cadd54707a7aaa4cfa9c8b8051c6185e8717f22eChet Haase            });
689cadd54707a7aaa4cfa9c8b8051c6185e8717f22eChet Haase        }
690cadd54707a7aaa4cfa9c8b8051c6185e8717f22eChet Haase
6918ae76f91527ce850f155ce960fb9068bcd5d49f9Yigit Boyar        /**
6928ae76f91527ce850f155ce960fb9068bcd5d49f9Yigit Boyar         * Similar to other methods but negative count means delete and position count means add.
6938ae76f91527ce850f155ce960fb9068bcd5d49f9Yigit Boyar         * <p>
6948ae76f91527ce850f155ce960fb9068bcd5d49f9Yigit Boyar         * For instance, calling this method with <code>[1,1], [2,-1]</code> it will first add an
6958ae76f91527ce850f155ce960fb9068bcd5d49f9Yigit Boyar         * item to index 1, then remove an item from index 2 (updated index 2)
6968ae76f91527ce850f155ce960fb9068bcd5d49f9Yigit Boyar         */
6978ae76f91527ce850f155ce960fb9068bcd5d49f9Yigit Boyar        public void addDeleteAndNotify(final int[]... startCountTuples) throws Throwable {
6988ae76f91527ce850f155ce960fb9068bcd5d49f9Yigit Boyar            new AddRemoveRunnable(startCountTuples).runOnMainThread();
699d7d27e9ebe5c7325e67e1a8af265378bd2056cadChet Haase        }
700d7d27e9ebe5c7325e67e1a8af265378bd2056cadChet Haase
701d7d27e9ebe5c7325e67e1a8af265378bd2056cadChet Haase        @Override
702d7d27e9ebe5c7325e67e1a8af265378bd2056cadChet Haase        public int getItemCount() {
703d7d27e9ebe5c7325e67e1a8af265378bd2056cadChet Haase            return mItems.size();
704d7d27e9ebe5c7325e67e1a8af265378bd2056cadChet Haase        }
7058ae76f91527ce850f155ce960fb9068bcd5d49f9Yigit Boyar
706115ba0c7b2a14aa4cd0273952195e1d8f6468f87Yigit Boyar        /**
707115ba0c7b2a14aa4cd0273952195e1d8f6468f87Yigit Boyar         * Uses notifyDataSetChanged
708115ba0c7b2a14aa4cd0273952195e1d8f6468f87Yigit Boyar         */
709e0c347f627f8a78d3e5e3e5eaac9c3ae26208689Yigit Boyar        public void moveItems(boolean notifyChange, int[]... fromToTuples) throws Throwable {
710e0c347f627f8a78d3e5e3e5eaac9c3ae26208689Yigit Boyar            for (int i = 0; i < fromToTuples.length; i += 1) {
711e0c347f627f8a78d3e5e3e5eaac9c3ae26208689Yigit Boyar                int[] tuple = fromToTuples[i];
712e0c347f627f8a78d3e5e3e5eaac9c3ae26208689Yigit Boyar                moveItem(tuple[0], tuple[1], false);
713e0c347f627f8a78d3e5e3e5eaac9c3ae26208689Yigit Boyar            }
714e0c347f627f8a78d3e5e3e5eaac9c3ae26208689Yigit Boyar            if (notifyChange) {
715cadd54707a7aaa4cfa9c8b8051c6185e8717f22eChet Haase                dispatchDataSetChanged();
716e0c347f627f8a78d3e5e3e5eaac9c3ae26208689Yigit Boyar            }
717e0c347f627f8a78d3e5e3e5eaac9c3ae26208689Yigit Boyar        }
718e0c347f627f8a78d3e5e3e5eaac9c3ae26208689Yigit Boyar
719115ba0c7b2a14aa4cd0273952195e1d8f6468f87Yigit Boyar        /**
720115ba0c7b2a14aa4cd0273952195e1d8f6468f87Yigit Boyar         * Uses notifyDataSetChanged
721115ba0c7b2a14aa4cd0273952195e1d8f6468f87Yigit Boyar         */
722e0c347f627f8a78d3e5e3e5eaac9c3ae26208689Yigit Boyar        public void moveItem(final int from, final int to, final boolean notifyChange)
723e0c347f627f8a78d3e5e3e5eaac9c3ae26208689Yigit Boyar                throws Throwable {
724e0c347f627f8a78d3e5e3e5eaac9c3ae26208689Yigit Boyar            runTestOnUiThread(new Runnable() {
725e0c347f627f8a78d3e5e3e5eaac9c3ae26208689Yigit Boyar                @Override
726e0c347f627f8a78d3e5e3e5eaac9c3ae26208689Yigit Boyar                public void run() {
727ec60a348ce39889a99f7ca02da94a6867bfdf66fYigit Boyar                    moveInUIThread(from, to);
728e0c347f627f8a78d3e5e3e5eaac9c3ae26208689Yigit Boyar                    if (notifyChange) {
729e0c347f627f8a78d3e5e3e5eaac9c3ae26208689Yigit Boyar                        notifyDataSetChanged();
730e0c347f627f8a78d3e5e3e5eaac9c3ae26208689Yigit Boyar                    }
731e0c347f627f8a78d3e5e3e5eaac9c3ae26208689Yigit Boyar                }
732e0c347f627f8a78d3e5e3e5eaac9c3ae26208689Yigit Boyar            });
733e0c347f627f8a78d3e5e3e5eaac9c3ae26208689Yigit Boyar        }
734e0c347f627f8a78d3e5e3e5eaac9c3ae26208689Yigit Boyar
735115ba0c7b2a14aa4cd0273952195e1d8f6468f87Yigit Boyar        /**
736115ba0c7b2a14aa4cd0273952195e1d8f6468f87Yigit Boyar         * Uses notifyItemMoved
737115ba0c7b2a14aa4cd0273952195e1d8f6468f87Yigit Boyar         */
738115ba0c7b2a14aa4cd0273952195e1d8f6468f87Yigit Boyar        public void moveAndNotify(final int from, final int to) throws Throwable {
739115ba0c7b2a14aa4cd0273952195e1d8f6468f87Yigit Boyar            runTestOnUiThread(new Runnable() {
740115ba0c7b2a14aa4cd0273952195e1d8f6468f87Yigit Boyar                @Override
741115ba0c7b2a14aa4cd0273952195e1d8f6468f87Yigit Boyar                public void run() {
742ec60a348ce39889a99f7ca02da94a6867bfdf66fYigit Boyar                    moveInUIThread(from, to);
743115ba0c7b2a14aa4cd0273952195e1d8f6468f87Yigit Boyar                    notifyItemMoved(from, to);
744115ba0c7b2a14aa4cd0273952195e1d8f6468f87Yigit Boyar                }
745115ba0c7b2a14aa4cd0273952195e1d8f6468f87Yigit Boyar            });
746115ba0c7b2a14aa4cd0273952195e1d8f6468f87Yigit Boyar        }
747115ba0c7b2a14aa4cd0273952195e1d8f6468f87Yigit Boyar
748ec60a348ce39889a99f7ca02da94a6867bfdf66fYigit Boyar        protected void moveInUIThread(int from, int to) {
749ec60a348ce39889a99f7ca02da94a6867bfdf66fYigit Boyar            Item item = mItems.remove(from);
750ec60a348ce39889a99f7ca02da94a6867bfdf66fYigit Boyar            offsetOriginalIndices(from, -1);
751ec60a348ce39889a99f7ca02da94a6867bfdf66fYigit Boyar            mItems.add(to, item);
752ec60a348ce39889a99f7ca02da94a6867bfdf66fYigit Boyar            offsetOriginalIndices(to + 1, 1);
753ec60a348ce39889a99f7ca02da94a6867bfdf66fYigit Boyar            item.mAdapterIndex = to;
754ec60a348ce39889a99f7ca02da94a6867bfdf66fYigit Boyar        }
755115ba0c7b2a14aa4cd0273952195e1d8f6468f87Yigit Boyar
756115ba0c7b2a14aa4cd0273952195e1d8f6468f87Yigit Boyar
75790ca3086dbf66ad6bb8840e46ec8524a705e1c18Yigit Boyar        @Override
75890ca3086dbf66ad6bb8840e46ec8524a705e1c18Yigit Boyar        public ViewAttachDetachCounter getCounter() {
75990ca3086dbf66ad6bb8840e46ec8524a705e1c18Yigit Boyar            return mAttachmentCounter;
76090ca3086dbf66ad6bb8840e46ec8524a705e1c18Yigit Boyar        }
76190ca3086dbf66ad6bb8840e46ec8524a705e1c18Yigit Boyar
7628ae76f91527ce850f155ce960fb9068bcd5d49f9Yigit Boyar
7638ae76f91527ce850f155ce960fb9068bcd5d49f9Yigit Boyar        private class AddRemoveRunnable implements Runnable {
7648ae76f91527ce850f155ce960fb9068bcd5d49f9Yigit Boyar            final int[][] mStartCountTuples;
7658ae76f91527ce850f155ce960fb9068bcd5d49f9Yigit Boyar
7668ae76f91527ce850f155ce960fb9068bcd5d49f9Yigit Boyar            public AddRemoveRunnable(int[][] startCountTuples) {
7678ae76f91527ce850f155ce960fb9068bcd5d49f9Yigit Boyar                mStartCountTuples = startCountTuples;
7688ae76f91527ce850f155ce960fb9068bcd5d49f9Yigit Boyar            }
7698ae76f91527ce850f155ce960fb9068bcd5d49f9Yigit Boyar
7708ae76f91527ce850f155ce960fb9068bcd5d49f9Yigit Boyar            public void runOnMainThread() throws Throwable {
7718ae76f91527ce850f155ce960fb9068bcd5d49f9Yigit Boyar                if (Looper.myLooper() == Looper.getMainLooper()) {
7728ae76f91527ce850f155ce960fb9068bcd5d49f9Yigit Boyar                    run();
7738ae76f91527ce850f155ce960fb9068bcd5d49f9Yigit Boyar                } else {
7748ae76f91527ce850f155ce960fb9068bcd5d49f9Yigit Boyar                    runTestOnUiThread(this);
7758ae76f91527ce850f155ce960fb9068bcd5d49f9Yigit Boyar                }
7768ae76f91527ce850f155ce960fb9068bcd5d49f9Yigit Boyar            }
7778ae76f91527ce850f155ce960fb9068bcd5d49f9Yigit Boyar
7788ae76f91527ce850f155ce960fb9068bcd5d49f9Yigit Boyar            @Override
7798ae76f91527ce850f155ce960fb9068bcd5d49f9Yigit Boyar            public void run() {
7808ae76f91527ce850f155ce960fb9068bcd5d49f9Yigit Boyar                for (int[] tuple : mStartCountTuples) {
7818ae76f91527ce850f155ce960fb9068bcd5d49f9Yigit Boyar                    if (tuple[1] < 0) {
7828ae76f91527ce850f155ce960fb9068bcd5d49f9Yigit Boyar                        delete(tuple);
7838ae76f91527ce850f155ce960fb9068bcd5d49f9Yigit Boyar                    } else {
7848ae76f91527ce850f155ce960fb9068bcd5d49f9Yigit Boyar                        add(tuple);
7858ae76f91527ce850f155ce960fb9068bcd5d49f9Yigit Boyar                    }
7868ae76f91527ce850f155ce960fb9068bcd5d49f9Yigit Boyar                }
7878ae76f91527ce850f155ce960fb9068bcd5d49f9Yigit Boyar            }
7888ae76f91527ce850f155ce960fb9068bcd5d49f9Yigit Boyar
7898ae76f91527ce850f155ce960fb9068bcd5d49f9Yigit Boyar            private void add(int[] tuple) {
7902d2e8d88103866b631eb0f3805da137ebcfb0275Yigit Boyar                // offset others
7912d2e8d88103866b631eb0f3805da137ebcfb0275Yigit Boyar                offsetOriginalIndices(tuple[0], tuple[1]);
7928ae76f91527ce850f155ce960fb9068bcd5d49f9Yigit Boyar                for (int i = 0; i < tuple[1]; i++) {
7938ae76f91527ce850f155ce960fb9068bcd5d49f9Yigit Boyar                    mItems.add(tuple[0], new Item(i, "new item " + i));
7948ae76f91527ce850f155ce960fb9068bcd5d49f9Yigit Boyar                }
7958ae76f91527ce850f155ce960fb9068bcd5d49f9Yigit Boyar                notifyItemRangeInserted(tuple[0], tuple[1]);
7968ae76f91527ce850f155ce960fb9068bcd5d49f9Yigit Boyar            }
7978ae76f91527ce850f155ce960fb9068bcd5d49f9Yigit Boyar
7988ae76f91527ce850f155ce960fb9068bcd5d49f9Yigit Boyar            private void delete(int[] tuple) {
7992d2e8d88103866b631eb0f3805da137ebcfb0275Yigit Boyar                final int count = -tuple[1];
8002d2e8d88103866b631eb0f3805da137ebcfb0275Yigit Boyar                offsetOriginalIndices(tuple[0] + count, tuple[1]);
8012d2e8d88103866b631eb0f3805da137ebcfb0275Yigit Boyar                for (int i = 0; i < count; i++) {
8028ae76f91527ce850f155ce960fb9068bcd5d49f9Yigit Boyar                    mItems.remove(tuple[0]);
8038ae76f91527ce850f155ce960fb9068bcd5d49f9Yigit Boyar                }
8042d2e8d88103866b631eb0f3805da137ebcfb0275Yigit Boyar                notifyItemRangeRemoved(tuple[0], count);
8058ae76f91527ce850f155ce960fb9068bcd5d49f9Yigit Boyar            }
8068ae76f91527ce850f155ce960fb9068bcd5d49f9Yigit Boyar        }
8078ae76f91527ce850f155ce960fb9068bcd5d49f9Yigit Boyar    }
8088ae76f91527ce850f155ce960fb9068bcd5d49f9Yigit Boyar
80990ca3086dbf66ad6bb8840e46ec8524a705e1c18Yigit Boyar    public boolean isMainThread() {
81090ca3086dbf66ad6bb8840e46ec8524a705e1c18Yigit Boyar        return Looper.myLooper() == Looper.getMainLooper();
81190ca3086dbf66ad6bb8840e46ec8524a705e1c18Yigit Boyar    }
81290ca3086dbf66ad6bb8840e46ec8524a705e1c18Yigit Boyar
8138ae76f91527ce850f155ce960fb9068bcd5d49f9Yigit Boyar    @Override
8148ae76f91527ce850f155ce960fb9068bcd5d49f9Yigit Boyar    public void runTestOnUiThread(Runnable r) throws Throwable {
8158ae76f91527ce850f155ce960fb9068bcd5d49f9Yigit Boyar        if (Looper.myLooper() == Looper.getMainLooper()) {
8168ae76f91527ce850f155ce960fb9068bcd5d49f9Yigit Boyar            r.run();
8178ae76f91527ce850f155ce960fb9068bcd5d49f9Yigit Boyar        } else {
8188ae76f91527ce850f155ce960fb9068bcd5d49f9Yigit Boyar            super.runTestOnUiThread(r);
8198ae76f91527ce850f155ce960fb9068bcd5d49f9Yigit Boyar        }
820d7d27e9ebe5c7325e67e1a8af265378bd2056cadChet Haase    }
821c50c4cad31d73e574b27bb3d7581542975e37263Yigit Boyar
822c50c4cad31d73e574b27bb3d7581542975e37263Yigit Boyar    static class TargetTuple {
823c50c4cad31d73e574b27bb3d7581542975e37263Yigit Boyar
824c50c4cad31d73e574b27bb3d7581542975e37263Yigit Boyar        final int mPosition;
825c50c4cad31d73e574b27bb3d7581542975e37263Yigit Boyar
826c50c4cad31d73e574b27bb3d7581542975e37263Yigit Boyar        final int mLayoutDirection;
827c50c4cad31d73e574b27bb3d7581542975e37263Yigit Boyar
828c50c4cad31d73e574b27bb3d7581542975e37263Yigit Boyar        TargetTuple(int position, int layoutDirection) {
829c50c4cad31d73e574b27bb3d7581542975e37263Yigit Boyar            this.mPosition = position;
830c50c4cad31d73e574b27bb3d7581542975e37263Yigit Boyar            this.mLayoutDirection = layoutDirection;
831c50c4cad31d73e574b27bb3d7581542975e37263Yigit Boyar        }
832c50c4cad31d73e574b27bb3d7581542975e37263Yigit Boyar
833c50c4cad31d73e574b27bb3d7581542975e37263Yigit Boyar        @Override
834c50c4cad31d73e574b27bb3d7581542975e37263Yigit Boyar        public String toString() {
835c50c4cad31d73e574b27bb3d7581542975e37263Yigit Boyar            return "TargetTuple{" +
836c50c4cad31d73e574b27bb3d7581542975e37263Yigit Boyar                    "mPosition=" + mPosition +
837c50c4cad31d73e574b27bb3d7581542975e37263Yigit Boyar                    ", mLayoutDirection=" + mLayoutDirection +
838c50c4cad31d73e574b27bb3d7581542975e37263Yigit Boyar                    '}';
839c50c4cad31d73e574b27bb3d7581542975e37263Yigit Boyar        }
840c50c4cad31d73e574b27bb3d7581542975e37263Yigit Boyar    }
84190ca3086dbf66ad6bb8840e46ec8524a705e1c18Yigit Boyar
84290ca3086dbf66ad6bb8840e46ec8524a705e1c18Yigit Boyar    public interface AttachDetachCountingAdapter {
84390ca3086dbf66ad6bb8840e46ec8524a705e1c18Yigit Boyar
84490ca3086dbf66ad6bb8840e46ec8524a705e1c18Yigit Boyar        ViewAttachDetachCounter getCounter();
84590ca3086dbf66ad6bb8840e46ec8524a705e1c18Yigit Boyar    }
84690ca3086dbf66ad6bb8840e46ec8524a705e1c18Yigit Boyar
84790ca3086dbf66ad6bb8840e46ec8524a705e1c18Yigit Boyar    public class ViewAttachDetachCounter {
84890ca3086dbf66ad6bb8840e46ec8524a705e1c18Yigit Boyar
84990ca3086dbf66ad6bb8840e46ec8524a705e1c18Yigit Boyar        Set<RecyclerView.ViewHolder> mAttachedSet = new HashSet<RecyclerView.ViewHolder>();
85090ca3086dbf66ad6bb8840e46ec8524a705e1c18Yigit Boyar
85190ca3086dbf66ad6bb8840e46ec8524a705e1c18Yigit Boyar        public void validateRemaining(RecyclerView recyclerView) {
85290ca3086dbf66ad6bb8840e46ec8524a705e1c18Yigit Boyar            final int childCount = recyclerView.getChildCount();
85390ca3086dbf66ad6bb8840e46ec8524a705e1c18Yigit Boyar            for (int i = 0; i < childCount; i++) {
85490ca3086dbf66ad6bb8840e46ec8524a705e1c18Yigit Boyar                View view = recyclerView.getChildAt(i);
85590ca3086dbf66ad6bb8840e46ec8524a705e1c18Yigit Boyar                RecyclerView.ViewHolder vh = recyclerView.getChildViewHolder(view);
85690ca3086dbf66ad6bb8840e46ec8524a705e1c18Yigit Boyar                assertTrue("remaining view should be in attached set " + vh,
85790ca3086dbf66ad6bb8840e46ec8524a705e1c18Yigit Boyar                        mAttachedSet.contains(vh));
85890ca3086dbf66ad6bb8840e46ec8524a705e1c18Yigit Boyar            }
85990ca3086dbf66ad6bb8840e46ec8524a705e1c18Yigit Boyar            assertEquals("there should not be any views left in attached set",
86090ca3086dbf66ad6bb8840e46ec8524a705e1c18Yigit Boyar                    childCount, mAttachedSet.size());
86190ca3086dbf66ad6bb8840e46ec8524a705e1c18Yigit Boyar        }
86290ca3086dbf66ad6bb8840e46ec8524a705e1c18Yigit Boyar
86390ca3086dbf66ad6bb8840e46ec8524a705e1c18Yigit Boyar        public void onViewDetached(RecyclerView.ViewHolder viewHolder) {
86490ca3086dbf66ad6bb8840e46ec8524a705e1c18Yigit Boyar            try {
86590ca3086dbf66ad6bb8840e46ec8524a705e1c18Yigit Boyar                assertTrue("view holder should be in attached set",
86690ca3086dbf66ad6bb8840e46ec8524a705e1c18Yigit Boyar                        mAttachedSet.remove(viewHolder));
86790ca3086dbf66ad6bb8840e46ec8524a705e1c18Yigit Boyar            } catch (Throwable t) {
86890ca3086dbf66ad6bb8840e46ec8524a705e1c18Yigit Boyar                postExceptionToInstrumentation(t);
86990ca3086dbf66ad6bb8840e46ec8524a705e1c18Yigit Boyar            }
87090ca3086dbf66ad6bb8840e46ec8524a705e1c18Yigit Boyar        }
87190ca3086dbf66ad6bb8840e46ec8524a705e1c18Yigit Boyar
87290ca3086dbf66ad6bb8840e46ec8524a705e1c18Yigit Boyar        public void onViewAttached(RecyclerView.ViewHolder viewHolder) {
87390ca3086dbf66ad6bb8840e46ec8524a705e1c18Yigit Boyar            try {
87490ca3086dbf66ad6bb8840e46ec8524a705e1c18Yigit Boyar                assertTrue("view holder should not be in attached set",
87590ca3086dbf66ad6bb8840e46ec8524a705e1c18Yigit Boyar                        mAttachedSet.add(viewHolder));
87690ca3086dbf66ad6bb8840e46ec8524a705e1c18Yigit Boyar            } catch (Throwable t) {
87790ca3086dbf66ad6bb8840e46ec8524a705e1c18Yigit Boyar                postExceptionToInstrumentation(t);
87890ca3086dbf66ad6bb8840e46ec8524a705e1c18Yigit Boyar            }
87990ca3086dbf66ad6bb8840e46ec8524a705e1c18Yigit Boyar        }
88090ca3086dbf66ad6bb8840e46ec8524a705e1c18Yigit Boyar
88190ca3086dbf66ad6bb8840e46ec8524a705e1c18Yigit Boyar        public void onAttached(RecyclerView recyclerView) {
88290ca3086dbf66ad6bb8840e46ec8524a705e1c18Yigit Boyar            // when a new RV is attached, clear the set and add all view holders
88390ca3086dbf66ad6bb8840e46ec8524a705e1c18Yigit Boyar            mAttachedSet.clear();
88490ca3086dbf66ad6bb8840e46ec8524a705e1c18Yigit Boyar            final int childCount = recyclerView.getChildCount();
88590ca3086dbf66ad6bb8840e46ec8524a705e1c18Yigit Boyar            for (int i = 0; i < childCount; i ++) {
88690ca3086dbf66ad6bb8840e46ec8524a705e1c18Yigit Boyar                View view = recyclerView.getChildAt(i);
88790ca3086dbf66ad6bb8840e46ec8524a705e1c18Yigit Boyar                mAttachedSet.add(recyclerView.getChildViewHolder(view));
88890ca3086dbf66ad6bb8840e46ec8524a705e1c18Yigit Boyar            }
88990ca3086dbf66ad6bb8840e46ec8524a705e1c18Yigit Boyar        }
89090ca3086dbf66ad6bb8840e46ec8524a705e1c18Yigit Boyar
89190ca3086dbf66ad6bb8840e46ec8524a705e1c18Yigit Boyar        public void onDetached(RecyclerView recyclerView) {
89290ca3086dbf66ad6bb8840e46ec8524a705e1c18Yigit Boyar            validateRemaining(recyclerView);
89390ca3086dbf66ad6bb8840e46ec8524a705e1c18Yigit Boyar        }
89490ca3086dbf66ad6bb8840e46ec8524a705e1c18Yigit Boyar    }
8957a59e4b36500bcfddd842376d6c3abe0d9f23d84Yigit Boyar}
896