BaseRecyclerViewInstrumentationTest.java revision 5a9366fba5e26329bc9a988cfd2d43df980f2764
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.ReentrantLock;
42e71a1df9b3c0e1bd3c21a1b3dd20a41790d4a950Yigit Boyarimport android.support.v7.recyclerview.test.R;
43d7d27e9ebe5c7325e67e1a8af265378bd2056cadChet Haase
44d7d27e9ebe5c7325e67e1a8af265378bd2056cadChet Haaseabstract public class BaseRecyclerViewInstrumentationTest extends
45d7d27e9ebe5c7325e67e1a8af265378bd2056cadChet Haase        ActivityInstrumentationTestCase2<TestActivity> {
46d7d27e9ebe5c7325e67e1a8af265378bd2056cadChet Haase
47d7d27e9ebe5c7325e67e1a8af265378bd2056cadChet Haase    private static final String TAG = "RecyclerViewTest";
48d7d27e9ebe5c7325e67e1a8af265378bd2056cadChet Haase
49b5f8b4fecb531aee6b359b4968409410aba90817Yigit Boyar    private boolean mDebug;
50b5f8b4fecb531aee6b359b4968409410aba90817Yigit Boyar
51b5f8b4fecb531aee6b359b4968409410aba90817Yigit Boyar    protected RecyclerView mRecyclerView;
52d7d27e9ebe5c7325e67e1a8af265378bd2056cadChet Haase
538ae76f91527ce850f155ce960fb9068bcd5d49f9Yigit Boyar    protected AdapterHelper mAdapterHelper;
548ae76f91527ce850f155ce960fb9068bcd5d49f9Yigit Boyar
557e9da1a2629b657d56e2f54abd18c6249f3be037Yigit Boyar    Throwable mainThreadException;
567e9da1a2629b657d56e2f54abd18c6249f3be037Yigit Boyar
57e05fbd9cfe05496e82a3abe19e07e8745985e9a5Yigit Boyar    Thread mInstrumentationThread;
58e05fbd9cfe05496e82a3abe19e07e8745985e9a5Yigit Boyar
59d7d27e9ebe5c7325e67e1a8af265378bd2056cadChet Haase    public BaseRecyclerViewInstrumentationTest() {
60b5f8b4fecb531aee6b359b4968409410aba90817Yigit Boyar        this(false);
61b5f8b4fecb531aee6b359b4968409410aba90817Yigit Boyar    }
62b5f8b4fecb531aee6b359b4968409410aba90817Yigit Boyar
63b5f8b4fecb531aee6b359b4968409410aba90817Yigit Boyar    public BaseRecyclerViewInstrumentationTest(boolean debug) {
648ae76f91527ce850f155ce960fb9068bcd5d49f9Yigit Boyar        super("android.support.v7.recyclerview", TestActivity.class);
65b5f8b4fecb531aee6b359b4968409410aba90817Yigit Boyar        mDebug = debug;
66b5f8b4fecb531aee6b359b4968409410aba90817Yigit Boyar    }
67b5f8b4fecb531aee6b359b4968409410aba90817Yigit Boyar
687e9da1a2629b657d56e2f54abd18c6249f3be037Yigit Boyar    void checkForMainThreadException() throws Throwable {
697e9da1a2629b657d56e2f54abd18c6249f3be037Yigit Boyar        if (mainThreadException != null) {
707e9da1a2629b657d56e2f54abd18c6249f3be037Yigit Boyar            throw mainThreadException;
717e9da1a2629b657d56e2f54abd18c6249f3be037Yigit Boyar        }
727e9da1a2629b657d56e2f54abd18c6249f3be037Yigit Boyar    }
737e9da1a2629b657d56e2f54abd18c6249f3be037Yigit Boyar
74e05fbd9cfe05496e82a3abe19e07e8745985e9a5Yigit Boyar    @Override
75e05fbd9cfe05496e82a3abe19e07e8745985e9a5Yigit Boyar    protected void setUp() throws Exception {
76e05fbd9cfe05496e82a3abe19e07e8745985e9a5Yigit Boyar        super.setUp();
77e05fbd9cfe05496e82a3abe19e07e8745985e9a5Yigit Boyar        mInstrumentationThread = Thread.currentThread();
78e05fbd9cfe05496e82a3abe19e07e8745985e9a5Yigit Boyar    }
79e05fbd9cfe05496e82a3abe19e07e8745985e9a5Yigit Boyar
80e2437126f0225135891544315ff72e54c7de0f59Yigit Boyar    void setHasTransientState(final View view, final boolean value) {
81e2437126f0225135891544315ff72e54c7de0f59Yigit Boyar        try {
82e2437126f0225135891544315ff72e54c7de0f59Yigit Boyar            runTestOnUiThread(new Runnable() {
83e2437126f0225135891544315ff72e54c7de0f59Yigit Boyar                @Override
84e2437126f0225135891544315ff72e54c7de0f59Yigit Boyar                public void run() {
85e2437126f0225135891544315ff72e54c7de0f59Yigit Boyar                    ViewCompat.setHasTransientState(view, value);
86e2437126f0225135891544315ff72e54c7de0f59Yigit Boyar                }
87e2437126f0225135891544315ff72e54c7de0f59Yigit Boyar            });
88e2437126f0225135891544315ff72e54c7de0f59Yigit Boyar        } catch (Throwable throwable) {
89e2437126f0225135891544315ff72e54c7de0f59Yigit Boyar            Log.e(TAG, "", throwable);
90e2437126f0225135891544315ff72e54c7de0f59Yigit Boyar        }
91e2437126f0225135891544315ff72e54c7de0f59Yigit Boyar    }
92e2437126f0225135891544315ff72e54c7de0f59Yigit Boyar
932bf89a9bae2b6110a77cf0b3754b567bb07d12a5Yigit Boyar    protected void enableAccessibility()
942bf89a9bae2b6110a77cf0b3754b567bb07d12a5Yigit Boyar            throws NoSuchMethodException, InvocationTargetException, IllegalAccessException {
952bf89a9bae2b6110a77cf0b3754b567bb07d12a5Yigit Boyar        Method getUIAutomation = Instrumentation.class.getMethod("getUiAutomation");
962bf89a9bae2b6110a77cf0b3754b567bb07d12a5Yigit Boyar        getUIAutomation.invoke(getInstrumentation());
972bf89a9bae2b6110a77cf0b3754b567bb07d12a5Yigit Boyar    }
982bf89a9bae2b6110a77cf0b3754b567bb07d12a5Yigit Boyar
990b1059b711009601ff08354e2df6bfc45266e80aYigit Boyar    void setAdapter(final RecyclerView.Adapter adapter) throws Throwable {
1000b1059b711009601ff08354e2df6bfc45266e80aYigit Boyar        runTestOnUiThread(new Runnable() {
1010b1059b711009601ff08354e2df6bfc45266e80aYigit Boyar            @Override
1020b1059b711009601ff08354e2df6bfc45266e80aYigit Boyar            public void run() {
1030b1059b711009601ff08354e2df6bfc45266e80aYigit Boyar                mRecyclerView.setAdapter(adapter);
1040b1059b711009601ff08354e2df6bfc45266e80aYigit Boyar            }
1050b1059b711009601ff08354e2df6bfc45266e80aYigit Boyar        });
1060b1059b711009601ff08354e2df6bfc45266e80aYigit Boyar    }
1070b1059b711009601ff08354e2df6bfc45266e80aYigit Boyar
108e71a1df9b3c0e1bd3c21a1b3dd20a41790d4a950Yigit Boyar    protected WrappedRecyclerView inflateWrappedRV() {
109e71a1df9b3c0e1bd3c21a1b3dd20a41790d4a950Yigit Boyar        return (WrappedRecyclerView)
110e71a1df9b3c0e1bd3c21a1b3dd20a41790d4a950Yigit Boyar                LayoutInflater.from(getActivity()).inflate(R.layout.wrapped_test_rv,
111e71a1df9b3c0e1bd3c21a1b3dd20a41790d4a950Yigit Boyar                        getRecyclerViewContainer(), false);
112e71a1df9b3c0e1bd3c21a1b3dd20a41790d4a950Yigit Boyar    }
113e71a1df9b3c0e1bd3c21a1b3dd20a41790d4a950Yigit Boyar
1140b1059b711009601ff08354e2df6bfc45266e80aYigit Boyar    void swapAdapter(final RecyclerView.Adapter adapter,
1150b1059b711009601ff08354e2df6bfc45266e80aYigit Boyar            final boolean removeAndRecycleExistingViews) throws Throwable {
1160b1059b711009601ff08354e2df6bfc45266e80aYigit Boyar        runTestOnUiThread(new Runnable() {
1170b1059b711009601ff08354e2df6bfc45266e80aYigit Boyar            @Override
1180b1059b711009601ff08354e2df6bfc45266e80aYigit Boyar            public void run() {
119824a7db96feacf3b68f56993ed72712bc935153eYigit Boyar                try {
120824a7db96feacf3b68f56993ed72712bc935153eYigit Boyar                    mRecyclerView.swapAdapter(adapter, removeAndRecycleExistingViews);
121824a7db96feacf3b68f56993ed72712bc935153eYigit Boyar                } catch (Throwable t) {
122824a7db96feacf3b68f56993ed72712bc935153eYigit Boyar                    postExceptionToInstrumentation(t);
123824a7db96feacf3b68f56993ed72712bc935153eYigit Boyar                }
1240b1059b711009601ff08354e2df6bfc45266e80aYigit Boyar            }
1250b1059b711009601ff08354e2df6bfc45266e80aYigit Boyar        });
126824a7db96feacf3b68f56993ed72712bc935153eYigit Boyar        checkForMainThreadException();
1270b1059b711009601ff08354e2df6bfc45266e80aYigit Boyar    }
1280b1059b711009601ff08354e2df6bfc45266e80aYigit Boyar
1297e9da1a2629b657d56e2f54abd18c6249f3be037Yigit Boyar    void postExceptionToInstrumentation(Throwable t) {
130e05fbd9cfe05496e82a3abe19e07e8745985e9a5Yigit Boyar        if (mInstrumentationThread == Thread.currentThread()) {
131e05fbd9cfe05496e82a3abe19e07e8745985e9a5Yigit Boyar            throw new RuntimeException(t);
132e05fbd9cfe05496e82a3abe19e07e8745985e9a5Yigit Boyar        }
1336e83751247c5be0211d7bffaf057431c03dfef38Yigit Boyar        if (mainThreadException != null) {
1346e83751247c5be0211d7bffaf057431c03dfef38Yigit Boyar            Log.e(TAG, "receiving another main thread exception. dropping.", t);
1356e83751247c5be0211d7bffaf057431c03dfef38Yigit Boyar        } else {
13690ca3086dbf66ad6bb8840e46ec8524a705e1c18Yigit Boyar            Log.e(TAG, "captured exception on main thread", t);
1376e83751247c5be0211d7bffaf057431c03dfef38Yigit Boyar            mainThreadException = t;
1386e83751247c5be0211d7bffaf057431c03dfef38Yigit Boyar        }
1396e83751247c5be0211d7bffaf057431c03dfef38Yigit Boyar
1407e9da1a2629b657d56e2f54abd18c6249f3be037Yigit Boyar        if (mRecyclerView != null && mRecyclerView
1417e9da1a2629b657d56e2f54abd18c6249f3be037Yigit Boyar                .getLayoutManager() instanceof TestLayoutManager) {
1427e9da1a2629b657d56e2f54abd18c6249f3be037Yigit Boyar            TestLayoutManager lm = (TestLayoutManager) mRecyclerView.getLayoutManager();
1437e9da1a2629b657d56e2f54abd18c6249f3be037Yigit Boyar            // finish all layouts so that we get the correct exception
1447e9da1a2629b657d56e2f54abd18c6249f3be037Yigit Boyar            while (lm.layoutLatch.getCount() > 0) {
1457e9da1a2629b657d56e2f54abd18c6249f3be037Yigit Boyar                lm.layoutLatch.countDown();
1467e9da1a2629b657d56e2f54abd18c6249f3be037Yigit Boyar            }
1477e9da1a2629b657d56e2f54abd18c6249f3be037Yigit Boyar        }
1487e9da1a2629b657d56e2f54abd18c6249f3be037Yigit Boyar    }
1497e9da1a2629b657d56e2f54abd18c6249f3be037Yigit Boyar
1508ae76f91527ce850f155ce960fb9068bcd5d49f9Yigit Boyar    @Override
1518ae76f91527ce850f155ce960fb9068bcd5d49f9Yigit Boyar    protected void tearDown() throws Exception {
1528ae76f91527ce850f155ce960fb9068bcd5d49f9Yigit Boyar        if (mRecyclerView != null) {
1538ae76f91527ce850f155ce960fb9068bcd5d49f9Yigit Boyar            try {
1548ae76f91527ce850f155ce960fb9068bcd5d49f9Yigit Boyar                removeRecyclerView();
1558ae76f91527ce850f155ce960fb9068bcd5d49f9Yigit Boyar            } catch (Throwable throwable) {
1568ae76f91527ce850f155ce960fb9068bcd5d49f9Yigit Boyar                throwable.printStackTrace();
1578ae76f91527ce850f155ce960fb9068bcd5d49f9Yigit Boyar            }
1588ae76f91527ce850f155ce960fb9068bcd5d49f9Yigit Boyar        }
1598ae76f91527ce850f155ce960fb9068bcd5d49f9Yigit Boyar        getInstrumentation().waitForIdleSync();
160ca9ce7ccdccf6b6a2a81da73dd1e6425d90e4b23Yigit Boyar        super.tearDown();
161ca9ce7ccdccf6b6a2a81da73dd1e6425d90e4b23Yigit Boyar
1626e83751247c5be0211d7bffaf057431c03dfef38Yigit Boyar        try {
1636e83751247c5be0211d7bffaf057431c03dfef38Yigit Boyar            checkForMainThreadException();
1646e83751247c5be0211d7bffaf057431c03dfef38Yigit Boyar        } catch (Exception e) {
1656e83751247c5be0211d7bffaf057431c03dfef38Yigit Boyar            throw e;
1666e83751247c5be0211d7bffaf057431c03dfef38Yigit Boyar        } catch (Throwable throwable) {
167ca9ce7ccdccf6b6a2a81da73dd1e6425d90e4b23Yigit Boyar            throw new Exception(throwable);
1686e83751247c5be0211d7bffaf057431c03dfef38Yigit Boyar        }
1698ae76f91527ce850f155ce960fb9068bcd5d49f9Yigit Boyar    }
1708ae76f91527ce850f155ce960fb9068bcd5d49f9Yigit Boyar
171c50c4cad31d73e574b27bb3d7581542975e37263Yigit Boyar    public Rect getDecoratedRecyclerViewBounds() {
172c50c4cad31d73e574b27bb3d7581542975e37263Yigit Boyar        return new Rect(
173c50c4cad31d73e574b27bb3d7581542975e37263Yigit Boyar                mRecyclerView.getPaddingLeft(),
174c50c4cad31d73e574b27bb3d7581542975e37263Yigit Boyar                mRecyclerView.getPaddingTop(),
175c50c4cad31d73e574b27bb3d7581542975e37263Yigit Boyar                mRecyclerView.getPaddingLeft() + mRecyclerView.getWidth(),
176c50c4cad31d73e574b27bb3d7581542975e37263Yigit Boyar                mRecyclerView.getPaddingTop() + mRecyclerView.getHeight()
177c50c4cad31d73e574b27bb3d7581542975e37263Yigit Boyar        );
178c50c4cad31d73e574b27bb3d7581542975e37263Yigit Boyar    }
179c50c4cad31d73e574b27bb3d7581542975e37263Yigit Boyar
180b5f8b4fecb531aee6b359b4968409410aba90817Yigit Boyar    public void removeRecyclerView() throws Throwable {
181d7e2f2ab1d253133fa54f309e74a7ee384c33971Yigit Boyar        if (mRecyclerView == null) {
182d7e2f2ab1d253133fa54f309e74a7ee384c33971Yigit Boyar            return;
183d7e2f2ab1d253133fa54f309e74a7ee384c33971Yigit Boyar        }
18490ca3086dbf66ad6bb8840e46ec8524a705e1c18Yigit Boyar        if (!isMainThread()) {
18590ca3086dbf66ad6bb8840e46ec8524a705e1c18Yigit Boyar            getInstrumentation().waitForIdleSync();
18690ca3086dbf66ad6bb8840e46ec8524a705e1c18Yigit Boyar        }
187b5f8b4fecb531aee6b359b4968409410aba90817Yigit Boyar        runTestOnUiThread(new Runnable() {
188b5f8b4fecb531aee6b359b4968409410aba90817Yigit Boyar            @Override
189b5f8b4fecb531aee6b359b4968409410aba90817Yigit Boyar            public void run() {
190abd0fbfcdd5b438909f126392eca5978b5939b6dYigit Boyar                try {
191abd0fbfcdd5b438909f126392eca5978b5939b6dYigit Boyar                    final RecyclerView.Adapter adapter = mRecyclerView.getAdapter();
192abd0fbfcdd5b438909f126392eca5978b5939b6dYigit Boyar                    if (adapter instanceof AttachDetachCountingAdapter) {
193abd0fbfcdd5b438909f126392eca5978b5939b6dYigit Boyar                        ((AttachDetachCountingAdapter) adapter).getCounter()
194abd0fbfcdd5b438909f126392eca5978b5939b6dYigit Boyar                                .validateRemaining(mRecyclerView);
195abd0fbfcdd5b438909f126392eca5978b5939b6dYigit Boyar                    }
196abd0fbfcdd5b438909f126392eca5978b5939b6dYigit Boyar                    getActivity().mContainer.removeAllViews();
197abd0fbfcdd5b438909f126392eca5978b5939b6dYigit Boyar                } catch (Throwable t) {
198abd0fbfcdd5b438909f126392eca5978b5939b6dYigit Boyar                    postExceptionToInstrumentation(t);
19990ca3086dbf66ad6bb8840e46ec8524a705e1c18Yigit Boyar                }
200b5f8b4fecb531aee6b359b4968409410aba90817Yigit Boyar            }
201b5f8b4fecb531aee6b359b4968409410aba90817Yigit Boyar        });
20290ca3086dbf66ad6bb8840e46ec8524a705e1c18Yigit Boyar        mRecyclerView = null;
203b5f8b4fecb531aee6b359b4968409410aba90817Yigit Boyar    }
204b5f8b4fecb531aee6b359b4968409410aba90817Yigit Boyar
205504c54ea52c1b2aae6f8f4ae128f1dcaac7e3f6aYigit Boyar    void waitForAnimations(int seconds) throws InterruptedException {
206504c54ea52c1b2aae6f8f4ae128f1dcaac7e3f6aYigit Boyar        final CountDownLatch latch = new CountDownLatch(2);
207504c54ea52c1b2aae6f8f4ae128f1dcaac7e3f6aYigit Boyar        boolean running = mRecyclerView.mItemAnimator
208504c54ea52c1b2aae6f8f4ae128f1dcaac7e3f6aYigit Boyar                .isRunning(new RecyclerView.ItemAnimator.ItemAnimatorFinishedListener() {
209504c54ea52c1b2aae6f8f4ae128f1dcaac7e3f6aYigit Boyar                    @Override
210504c54ea52c1b2aae6f8f4ae128f1dcaac7e3f6aYigit Boyar                    public void onAnimationsFinished() {
211504c54ea52c1b2aae6f8f4ae128f1dcaac7e3f6aYigit Boyar                        latch.countDown();
212504c54ea52c1b2aae6f8f4ae128f1dcaac7e3f6aYigit Boyar                    }
213504c54ea52c1b2aae6f8f4ae128f1dcaac7e3f6aYigit Boyar                });
214504c54ea52c1b2aae6f8f4ae128f1dcaac7e3f6aYigit Boyar        if (running) {
2155a9366fba5e26329bc9a988cfd2d43df980f2764Yigit Boyar            latch.countDown();
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 {
236f129f1b050d2542a91fe8175eac30183beb07b41Chris Banes        setRecyclerView(recyclerView, assignDummyPool, 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 {
393f129f1b050d2542a91fe8175eac30183beb07b41Chris Banes        int mScrollVerticallyAmount;
394f129f1b050d2542a91fe8175eac30183beb07b41Chris Banes        int mScrollHorizontallyAmount;
395e71a1df9b3c0e1bd3c21a1b3dd20a41790d4a950Yigit Boyar        protected CountDownLatch layoutLatch;
396d7d27e9ebe5c7325e67e1a8af265378bd2056cadChet Haase
397d7d27e9ebe5c7325e67e1a8af265378bd2056cadChet Haase        public void expectLayouts(int count) {
398d7d27e9ebe5c7325e67e1a8af265378bd2056cadChet Haase            layoutLatch = new CountDownLatch(count);
399d7d27e9ebe5c7325e67e1a8af265378bd2056cadChet Haase        }
400d7d27e9ebe5c7325e67e1a8af265378bd2056cadChet Haase
401629687bba8da1a73c48d8fe87393a13581ce46caYigit Boyar        public void waitForLayout(long timeout, TimeUnit timeUnit, boolean waitForIdle)
402629687bba8da1a73c48d8fe87393a13581ce46caYigit Boyar                throws Throwable {
403b5f8b4fecb531aee6b359b4968409410aba90817Yigit Boyar            layoutLatch.await(timeout * (mDebug ? 100 : 1), timeUnit);
404d7d27e9ebe5c7325e67e1a8af265378bd2056cadChet Haase            assertEquals("all expected layouts should be executed at the expected time",
405d7d27e9ebe5c7325e67e1a8af265378bd2056cadChet Haase                    0, layoutLatch.getCount());
406629687bba8da1a73c48d8fe87393a13581ce46caYigit Boyar            if (waitForIdle) {
407629687bba8da1a73c48d8fe87393a13581ce46caYigit Boyar                getInstrumentation().waitForIdleSync();
408629687bba8da1a73c48d8fe87393a13581ce46caYigit Boyar            }
409629687bba8da1a73c48d8fe87393a13581ce46caYigit Boyar        }
410629687bba8da1a73c48d8fe87393a13581ce46caYigit Boyar
411629687bba8da1a73c48d8fe87393a13581ce46caYigit Boyar        public void waitForLayout(long timeout, TimeUnit timeUnit)
412629687bba8da1a73c48d8fe87393a13581ce46caYigit Boyar                throws Throwable {
413629687bba8da1a73c48d8fe87393a13581ce46caYigit Boyar            waitForLayout(timeout, timeUnit, true);
414d7d27e9ebe5c7325e67e1a8af265378bd2056cadChet Haase        }
415d7d27e9ebe5c7325e67e1a8af265378bd2056cadChet Haase
416b5f8b4fecb531aee6b359b4968409410aba90817Yigit Boyar        public void assertLayoutCount(int count, String msg, long timeout) throws Throwable {
417b5f8b4fecb531aee6b359b4968409410aba90817Yigit Boyar            layoutLatch.await(timeout, TimeUnit.SECONDS);
418b5f8b4fecb531aee6b359b4968409410aba90817Yigit Boyar            assertEquals(msg, count, layoutLatch.getCount());
419b5f8b4fecb531aee6b359b4968409410aba90817Yigit Boyar        }
420b5f8b4fecb531aee6b359b4968409410aba90817Yigit Boyar
421b5f8b4fecb531aee6b359b4968409410aba90817Yigit Boyar        public void assertNoLayout(String msg, long timeout) throws Throwable {
422b5f8b4fecb531aee6b359b4968409410aba90817Yigit Boyar            layoutLatch.await(timeout, TimeUnit.SECONDS);
423b5f8b4fecb531aee6b359b4968409410aba90817Yigit Boyar            assertFalse(msg, layoutLatch.getCount() == 0);
424b5f8b4fecb531aee6b359b4968409410aba90817Yigit Boyar        }
425b5f8b4fecb531aee6b359b4968409410aba90817Yigit Boyar
426b5f8b4fecb531aee6b359b4968409410aba90817Yigit Boyar        public void waitForLayout(long timeout) throws Throwable {
427629687bba8da1a73c48d8fe87393a13581ce46caYigit Boyar            waitForLayout(timeout * (mDebug ? 10000 : 1), TimeUnit.SECONDS, true);
428629687bba8da1a73c48d8fe87393a13581ce46caYigit Boyar        }
429629687bba8da1a73c48d8fe87393a13581ce46caYigit Boyar
430629687bba8da1a73c48d8fe87393a13581ce46caYigit Boyar        public void waitForLayout(long timeout, boolean waitForIdle) throws Throwable {
431629687bba8da1a73c48d8fe87393a13581ce46caYigit Boyar            waitForLayout(timeout * (mDebug ? 10000 : 1), TimeUnit.SECONDS, waitForIdle);
432b5f8b4fecb531aee6b359b4968409410aba90817Yigit Boyar        }
433b5f8b4fecb531aee6b359b4968409410aba90817Yigit Boyar
434d7d27e9ebe5c7325e67e1a8af265378bd2056cadChet Haase        @Override
435d7d27e9ebe5c7325e67e1a8af265378bd2056cadChet Haase        public RecyclerView.LayoutParams generateDefaultLayoutParams() {
436d7d27e9ebe5c7325e67e1a8af265378bd2056cadChet Haase            return new RecyclerView.LayoutParams(ViewGroup.LayoutParams.WRAP_CONTENT,
437d7d27e9ebe5c7325e67e1a8af265378bd2056cadChet Haase                    ViewGroup.LayoutParams.WRAP_CONTENT);
438d7d27e9ebe5c7325e67e1a8af265378bd2056cadChet Haase        }
439d7d27e9ebe5c7325e67e1a8af265378bd2056cadChet Haase
440d7d27e9ebe5c7325e67e1a8af265378bd2056cadChet Haase        void assertVisibleItemPositions() {
441d7d27e9ebe5c7325e67e1a8af265378bd2056cadChet Haase            int i = getChildCount();
442d7d27e9ebe5c7325e67e1a8af265378bd2056cadChet Haase            TestAdapter testAdapter = (TestAdapter) mRecyclerView.getAdapter();
443d7d27e9ebe5c7325e67e1a8af265378bd2056cadChet Haase            while (i-- > 0) {
444d7d27e9ebe5c7325e67e1a8af265378bd2056cadChet Haase                View view = getChildAt(i);
445d7d27e9ebe5c7325e67e1a8af265378bd2056cadChet Haase                RecyclerView.LayoutParams lp = (RecyclerView.LayoutParams) view.getLayoutParams();
446115ba0c7b2a14aa4cd0273952195e1d8f6468f87Yigit Boyar                Item item = ((TestViewHolder) lp.mViewHolder).mBoundItem;
447b5f8b4fecb531aee6b359b4968409410aba90817Yigit Boyar                if (mDebug) {
448d7d27e9ebe5c7325e67e1a8af265378bd2056cadChet Haase                    Log.d(TAG, "testing item " + i);
449d7d27e9ebe5c7325e67e1a8af265378bd2056cadChet Haase                }
4508ae76f91527ce850f155ce960fb9068bcd5d49f9Yigit Boyar                if (!lp.isItemRemoved()) {
4518ae76f91527ce850f155ce960fb9068bcd5d49f9Yigit Boyar                    RecyclerView.ViewHolder vh = mRecyclerView.getChildViewHolder(view);
4528ae76f91527ce850f155ce960fb9068bcd5d49f9Yigit Boyar                    assertSame("item position in LP should match adapter value :" + vh,
4538ae76f91527ce850f155ce960fb9068bcd5d49f9Yigit Boyar                            testAdapter.mItems.get(vh.mPosition), item);
4548ae76f91527ce850f155ce960fb9068bcd5d49f9Yigit Boyar                }
455d7d27e9ebe5c7325e67e1a8af265378bd2056cadChet Haase            }
456d7d27e9ebe5c7325e67e1a8af265378bd2056cadChet Haase        }
457d7d27e9ebe5c7325e67e1a8af265378bd2056cadChet Haase
458b5f8b4fecb531aee6b359b4968409410aba90817Yigit Boyar        RecyclerView.LayoutParams getLp(View v) {
459b5f8b4fecb531aee6b359b4968409410aba90817Yigit Boyar            return (RecyclerView.LayoutParams) v.getLayoutParams();
460b5f8b4fecb531aee6b359b4968409410aba90817Yigit Boyar        }
461b5f8b4fecb531aee6b359b4968409410aba90817Yigit Boyar
462e71a1df9b3c0e1bd3c21a1b3dd20a41790d4a950Yigit Boyar        protected void layoutRange(RecyclerView.Recycler recycler, int start, int end) {
463fe3996cf5af65fe11cc3ed47b67123ec267bd336Yigit Boyar            assertScrap(recycler);
464b5f8b4fecb531aee6b359b4968409410aba90817Yigit Boyar            if (mDebug) {
465d7d27e9ebe5c7325e67e1a8af265378bd2056cadChet Haase                Log.d(TAG, "will layout items from " + start + " to " + end);
466d7d27e9ebe5c7325e67e1a8af265378bd2056cadChet Haase            }
4675ced882cabdcefbb469e332f6f73983999aad0e5Yigit Boyar            int diff = end > start ? 1 : -1;
468504c54ea52c1b2aae6f8f4ae128f1dcaac7e3f6aYigit Boyar            int top = 0;
4695ced882cabdcefbb469e332f6f73983999aad0e5Yigit Boyar            for (int i = start; i != end; i+=diff) {
470b5f8b4fecb531aee6b359b4968409410aba90817Yigit Boyar                if (mDebug) {
471d7d27e9ebe5c7325e67e1a8af265378bd2056cadChet Haase                    Log.d(TAG, "laying out item " + i);
472d7d27e9ebe5c7325e67e1a8af265378bd2056cadChet Haase                }
473d7d27e9ebe5c7325e67e1a8af265378bd2056cadChet Haase                View view = recycler.getViewForPosition(i);
474b5f8b4fecb531aee6b359b4968409410aba90817Yigit Boyar                assertNotNull("view should not be null for valid position. "
475b5f8b4fecb531aee6b359b4968409410aba90817Yigit Boyar                        + "got null view at position " + i, view);
476c8b0ff5869e17a656a3a1d7fb306efa5b99fa07fYigit Boyar                if (!mRecyclerView.mState.isPreLayout()) {
477c8b0ff5869e17a656a3a1d7fb306efa5b99fa07fYigit Boyar                    RecyclerView.LayoutParams layoutParams = (RecyclerView.LayoutParams) view
478c8b0ff5869e17a656a3a1d7fb306efa5b99fa07fYigit Boyar                            .getLayoutParams();
479c8b0ff5869e17a656a3a1d7fb306efa5b99fa07fYigit Boyar                    assertFalse("In post layout, getViewForPosition should never return a view "
480c8b0ff5869e17a656a3a1d7fb306efa5b99fa07fYigit Boyar                            + "that is removed", layoutParams != null
481c8b0ff5869e17a656a3a1d7fb306efa5b99fa07fYigit Boyar                            && layoutParams.isItemRemoved());
482c8b0ff5869e17a656a3a1d7fb306efa5b99fa07fYigit Boyar
483b5f8b4fecb531aee6b359b4968409410aba90817Yigit Boyar                }
4848da5bba529a2fe9bd2aafab28010e11ec4de93c7Yigit Boyar                assertEquals("getViewForPosition should return correct position",
4858da5bba529a2fe9bd2aafab28010e11ec4de93c7Yigit Boyar                        i, getPosition(view));
486c8b0ff5869e17a656a3a1d7fb306efa5b99fa07fYigit Boyar                addView(view);
487b5f8b4fecb531aee6b359b4968409410aba90817Yigit Boyar
488d7d27e9ebe5c7325e67e1a8af265378bd2056cadChet Haase                measureChildWithMargins(view, 0, 0);
489e71a1df9b3c0e1bd3c21a1b3dd20a41790d4a950Yigit Boyar                if (getLayoutDirection() == ViewCompat.LAYOUT_DIRECTION_RTL) {
490e71a1df9b3c0e1bd3c21a1b3dd20a41790d4a950Yigit Boyar                    layoutDecorated(view, getWidth() - getDecoratedMeasuredWidth(view), top,
491e71a1df9b3c0e1bd3c21a1b3dd20a41790d4a950Yigit Boyar                            getWidth(), top + getDecoratedMeasuredHeight(view));
492e71a1df9b3c0e1bd3c21a1b3dd20a41790d4a950Yigit Boyar                } else {
493e71a1df9b3c0e1bd3c21a1b3dd20a41790d4a950Yigit Boyar                    layoutDecorated(view, 0, top, getDecoratedMeasuredWidth(view)
494e71a1df9b3c0e1bd3c21a1b3dd20a41790d4a950Yigit Boyar                            , top + getDecoratedMeasuredHeight(view));
495e71a1df9b3c0e1bd3c21a1b3dd20a41790d4a950Yigit Boyar                }
496e71a1df9b3c0e1bd3c21a1b3dd20a41790d4a950Yigit Boyar
497504c54ea52c1b2aae6f8f4ae128f1dcaac7e3f6aYigit Boyar                top += view.getMeasuredHeight();
498d7d27e9ebe5c7325e67e1a8af265378bd2056cadChet Haase            }
499d7d27e9ebe5c7325e67e1a8af265378bd2056cadChet Haase        }
500fe3996cf5af65fe11cc3ed47b67123ec267bd336Yigit Boyar
501fe3996cf5af65fe11cc3ed47b67123ec267bd336Yigit Boyar        private void assertScrap(RecyclerView.Recycler recycler) {
502e0c347f627f8a78d3e5e3e5eaac9c3ae26208689Yigit Boyar            if (mRecyclerView.getAdapter() != null &&
503e0c347f627f8a78d3e5e3e5eaac9c3ae26208689Yigit Boyar                    !mRecyclerView.getAdapter().hasStableIds()) {
504e0c347f627f8a78d3e5e3e5eaac9c3ae26208689Yigit Boyar                for (RecyclerView.ViewHolder viewHolder : recycler.getScrapList()) {
505e0c347f627f8a78d3e5e3e5eaac9c3ae26208689Yigit Boyar                    assertFalse("Invalid scrap should be no kept", viewHolder.isInvalid());
506e0c347f627f8a78d3e5e3e5eaac9c3ae26208689Yigit Boyar                }
507fe3996cf5af65fe11cc3ed47b67123ec267bd336Yigit Boyar            }
508fe3996cf5af65fe11cc3ed47b67123ec267bd336Yigit Boyar        }
509bb6ebab3b3b85ec20b3bc262289a2460705322f5Yigit Boyar
510bb6ebab3b3b85ec20b3bc262289a2460705322f5Yigit Boyar        @Override
511bb6ebab3b3b85ec20b3bc262289a2460705322f5Yigit Boyar        public boolean canScrollHorizontally() {
512bb6ebab3b3b85ec20b3bc262289a2460705322f5Yigit Boyar            return true;
513bb6ebab3b3b85ec20b3bc262289a2460705322f5Yigit Boyar        }
514bb6ebab3b3b85ec20b3bc262289a2460705322f5Yigit Boyar
515bb6ebab3b3b85ec20b3bc262289a2460705322f5Yigit Boyar        @Override
516bb6ebab3b3b85ec20b3bc262289a2460705322f5Yigit Boyar        public boolean canScrollVertically() {
517bb6ebab3b3b85ec20b3bc262289a2460705322f5Yigit Boyar            return true;
518bb6ebab3b3b85ec20b3bc262289a2460705322f5Yigit Boyar        }
519bb6ebab3b3b85ec20b3bc262289a2460705322f5Yigit Boyar
520bb6ebab3b3b85ec20b3bc262289a2460705322f5Yigit Boyar        @Override
521bb6ebab3b3b85ec20b3bc262289a2460705322f5Yigit Boyar        public int scrollHorizontallyBy(int dx, RecyclerView.Recycler recycler,
522bb6ebab3b3b85ec20b3bc262289a2460705322f5Yigit Boyar                RecyclerView.State state) {
523f129f1b050d2542a91fe8175eac30183beb07b41Chris Banes            mScrollHorizontallyAmount += dx;
524bb6ebab3b3b85ec20b3bc262289a2460705322f5Yigit Boyar            return dx;
525bb6ebab3b3b85ec20b3bc262289a2460705322f5Yigit Boyar        }
526bb6ebab3b3b85ec20b3bc262289a2460705322f5Yigit Boyar
527bb6ebab3b3b85ec20b3bc262289a2460705322f5Yigit Boyar        @Override
528bb6ebab3b3b85ec20b3bc262289a2460705322f5Yigit Boyar        public int scrollVerticallyBy(int dy, RecyclerView.Recycler recycler,
529bb6ebab3b3b85ec20b3bc262289a2460705322f5Yigit Boyar                RecyclerView.State state) {
530f129f1b050d2542a91fe8175eac30183beb07b41Chris Banes            mScrollVerticallyAmount += dy;
531bb6ebab3b3b85ec20b3bc262289a2460705322f5Yigit Boyar            return dy;
532bb6ebab3b3b85ec20b3bc262289a2460705322f5Yigit Boyar        }
533d7d27e9ebe5c7325e67e1a8af265378bd2056cadChet Haase    }
534d7d27e9ebe5c7325e67e1a8af265378bd2056cadChet Haase
535d7d27e9ebe5c7325e67e1a8af265378bd2056cadChet Haase    static class Item {
53611b7cfea15306c0d6e4ed7da09f7698df01695dfYigit Boyar        final static AtomicInteger idCounter = new AtomicInteger(0);
53711b7cfea15306c0d6e4ed7da09f7698df01695dfYigit Boyar        final public int mId = idCounter.incrementAndGet();
538d7d27e9ebe5c7325e67e1a8af265378bd2056cadChet Haase
5392d2e8d88103866b631eb0f3805da137ebcfb0275Yigit Boyar        int mAdapterIndex;
540d7d27e9ebe5c7325e67e1a8af265378bd2056cadChet Haase
5412d2e8d88103866b631eb0f3805da137ebcfb0275Yigit Boyar        final String mText;
542d7d27e9ebe5c7325e67e1a8af265378bd2056cadChet Haase
5432d2e8d88103866b631eb0f3805da137ebcfb0275Yigit Boyar        Item(int adapterIndex, String text) {
5442d2e8d88103866b631eb0f3805da137ebcfb0275Yigit Boyar            mAdapterIndex = adapterIndex;
5452d2e8d88103866b631eb0f3805da137ebcfb0275Yigit Boyar            mText = text;
546d7d27e9ebe5c7325e67e1a8af265378bd2056cadChet Haase        }
5478ae76f91527ce850f155ce960fb9068bcd5d49f9Yigit Boyar
5488ae76f91527ce850f155ce960fb9068bcd5d49f9Yigit Boyar        @Override
5498ae76f91527ce850f155ce960fb9068bcd5d49f9Yigit Boyar        public String toString() {
5508ae76f91527ce850f155ce960fb9068bcd5d49f9Yigit Boyar            return "Item{" +
5518ae76f91527ce850f155ce960fb9068bcd5d49f9Yigit Boyar                    "mId=" + mId +
5522d2e8d88103866b631eb0f3805da137ebcfb0275Yigit Boyar                    ", originalIndex=" + mAdapterIndex +
5532d2e8d88103866b631eb0f3805da137ebcfb0275Yigit Boyar                    ", text='" + mText + '\'' +
5548ae76f91527ce850f155ce960fb9068bcd5d49f9Yigit Boyar                    '}';
5558ae76f91527ce850f155ce960fb9068bcd5d49f9Yigit Boyar        }
556d7d27e9ebe5c7325e67e1a8af265378bd2056cadChet Haase    }
557d7d27e9ebe5c7325e67e1a8af265378bd2056cadChet Haase
558e71a1df9b3c0e1bd3c21a1b3dd20a41790d4a950Yigit Boyar    public class TestAdapter extends RecyclerView.Adapter<TestViewHolder>
55990ca3086dbf66ad6bb8840e46ec8524a705e1c18Yigit Boyar            implements AttachDetachCountingAdapter {
560d7d27e9ebe5c7325e67e1a8af265378bd2056cadChet Haase
56190ca3086dbf66ad6bb8840e46ec8524a705e1c18Yigit Boyar        ViewAttachDetachCounter mAttachmentCounter = new ViewAttachDetachCounter();
562d7d27e9ebe5c7325e67e1a8af265378bd2056cadChet Haase        List<Item> mItems;
563d7d27e9ebe5c7325e67e1a8af265378bd2056cadChet Haase
564e71a1df9b3c0e1bd3c21a1b3dd20a41790d4a950Yigit Boyar        public TestAdapter(int count) {
565d7d27e9ebe5c7325e67e1a8af265378bd2056cadChet Haase            mItems = new ArrayList<Item>(count);
566d7d27e9ebe5c7325e67e1a8af265378bd2056cadChet Haase            for (int i = 0; i < count; i++) {
567d7d27e9ebe5c7325e67e1a8af265378bd2056cadChet Haase                mItems.add(new Item(i, "Item " + i));
568d7d27e9ebe5c7325e67e1a8af265378bd2056cadChet Haase            }
569d7d27e9ebe5c7325e67e1a8af265378bd2056cadChet Haase        }
570d7d27e9ebe5c7325e67e1a8af265378bd2056cadChet Haase
571d7d27e9ebe5c7325e67e1a8af265378bd2056cadChet Haase        @Override
57290ca3086dbf66ad6bb8840e46ec8524a705e1c18Yigit Boyar        public void onViewAttachedToWindow(TestViewHolder holder) {
57390ca3086dbf66ad6bb8840e46ec8524a705e1c18Yigit Boyar            super.onViewAttachedToWindow(holder);
57490ca3086dbf66ad6bb8840e46ec8524a705e1c18Yigit Boyar            mAttachmentCounter.onViewAttached(holder);
57590ca3086dbf66ad6bb8840e46ec8524a705e1c18Yigit Boyar        }
57690ca3086dbf66ad6bb8840e46ec8524a705e1c18Yigit Boyar
57790ca3086dbf66ad6bb8840e46ec8524a705e1c18Yigit Boyar        @Override
57890ca3086dbf66ad6bb8840e46ec8524a705e1c18Yigit Boyar        public void onViewDetachedFromWindow(TestViewHolder holder) {
57990ca3086dbf66ad6bb8840e46ec8524a705e1c18Yigit Boyar            super.onViewDetachedFromWindow(holder);
58090ca3086dbf66ad6bb8840e46ec8524a705e1c18Yigit Boyar            mAttachmentCounter.onViewDetached(holder);
58190ca3086dbf66ad6bb8840e46ec8524a705e1c18Yigit Boyar        }
58290ca3086dbf66ad6bb8840e46ec8524a705e1c18Yigit Boyar
58390ca3086dbf66ad6bb8840e46ec8524a705e1c18Yigit Boyar        @Override
58490ca3086dbf66ad6bb8840e46ec8524a705e1c18Yigit Boyar        public void onAttachedToRecyclerView(RecyclerView recyclerView) {
58590ca3086dbf66ad6bb8840e46ec8524a705e1c18Yigit Boyar            super.onAttachedToRecyclerView(recyclerView);
58690ca3086dbf66ad6bb8840e46ec8524a705e1c18Yigit Boyar            mAttachmentCounter.onAttached(recyclerView);
58790ca3086dbf66ad6bb8840e46ec8524a705e1c18Yigit Boyar        }
58890ca3086dbf66ad6bb8840e46ec8524a705e1c18Yigit Boyar
58990ca3086dbf66ad6bb8840e46ec8524a705e1c18Yigit Boyar        @Override
59090ca3086dbf66ad6bb8840e46ec8524a705e1c18Yigit Boyar        public void onDetachedFromRecyclerView(RecyclerView recyclerView) {
59190ca3086dbf66ad6bb8840e46ec8524a705e1c18Yigit Boyar            super.onDetachedFromRecyclerView(recyclerView);
59290ca3086dbf66ad6bb8840e46ec8524a705e1c18Yigit Boyar            mAttachmentCounter.onDetached(recyclerView);
59390ca3086dbf66ad6bb8840e46ec8524a705e1c18Yigit Boyar        }
59490ca3086dbf66ad6bb8840e46ec8524a705e1c18Yigit Boyar
59590ca3086dbf66ad6bb8840e46ec8524a705e1c18Yigit Boyar        @Override
596d7d27e9ebe5c7325e67e1a8af265378bd2056cadChet Haase        public TestViewHolder onCreateViewHolder(ViewGroup parent,
597d7d27e9ebe5c7325e67e1a8af265378bd2056cadChet Haase                int viewType) {
598d7d27e9ebe5c7325e67e1a8af265378bd2056cadChet Haase            return new TestViewHolder(new TextView(parent.getContext()));
599d7d27e9ebe5c7325e67e1a8af265378bd2056cadChet Haase        }
600d7d27e9ebe5c7325e67e1a8af265378bd2056cadChet Haase
601d7d27e9ebe5c7325e67e1a8af265378bd2056cadChet Haase        @Override
602d7d27e9ebe5c7325e67e1a8af265378bd2056cadChet Haase        public void onBindViewHolder(TestViewHolder holder, int position) {
603619ac016831a5ad75c2e0921c9036b6f030a9e63Yigit Boyar            assertNotNull(holder.mOwnerRecyclerView);
604619ac016831a5ad75c2e0921c9036b6f030a9e63Yigit Boyar            assertEquals(position, holder.getAdapterPosition());
605d7d27e9ebe5c7325e67e1a8af265378bd2056cadChet Haase            final Item item = mItems.get(position);
6062d2e8d88103866b631eb0f3805da137ebcfb0275Yigit Boyar            ((TextView) (holder.itemView)).setText(item.mText + "(" + item.mAdapterIndex + ")");
607115ba0c7b2a14aa4cd0273952195e1d8f6468f87Yigit Boyar            holder.mBoundItem = item;
608d7d27e9ebe5c7325e67e1a8af265378bd2056cadChet Haase        }
609d7d27e9ebe5c7325e67e1a8af265378bd2056cadChet Haase
610888093b0a071a99d65c01116cf703d46b21f9918Yigit Boyar        public Item getItemAt(int position) {
611888093b0a071a99d65c01116cf703d46b21f9918Yigit Boyar            return mItems.get(position);
612888093b0a071a99d65c01116cf703d46b21f9918Yigit Boyar        }
613888093b0a071a99d65c01116cf703d46b21f9918Yigit Boyar
614619ac016831a5ad75c2e0921c9036b6f030a9e63Yigit Boyar        @Override
615619ac016831a5ad75c2e0921c9036b6f030a9e63Yigit Boyar        public void onViewRecycled(TestViewHolder holder) {
616619ac016831a5ad75c2e0921c9036b6f030a9e63Yigit Boyar            super.onViewRecycled(holder);
617619ac016831a5ad75c2e0921c9036b6f030a9e63Yigit Boyar            final int adapterPosition = holder.getAdapterPosition();
618619ac016831a5ad75c2e0921c9036b6f030a9e63Yigit Boyar            final boolean shouldHavePosition = !holder.isRemoved() && holder.isBound() &&
619619ac016831a5ad75c2e0921c9036b6f030a9e63Yigit Boyar                    !holder.isAdapterPositionUnknown() && !holder.isInvalid();
620619ac016831a5ad75c2e0921c9036b6f030a9e63Yigit Boyar            String log = "Position check for " + holder.toString();
621619ac016831a5ad75c2e0921c9036b6f030a9e63Yigit Boyar            assertEquals(log, shouldHavePosition, adapterPosition != RecyclerView.NO_POSITION);
622619ac016831a5ad75c2e0921c9036b6f030a9e63Yigit Boyar            if (shouldHavePosition) {
623619ac016831a5ad75c2e0921c9036b6f030a9e63Yigit Boyar                assertTrue(log, mItems.size() > adapterPosition);
624619ac016831a5ad75c2e0921c9036b6f030a9e63Yigit Boyar                assertSame(log, holder.mBoundItem, mItems.get(adapterPosition));
625619ac016831a5ad75c2e0921c9036b6f030a9e63Yigit Boyar            }
626619ac016831a5ad75c2e0921c9036b6f030a9e63Yigit Boyar        }
627619ac016831a5ad75c2e0921c9036b6f030a9e63Yigit Boyar
628b5f8b4fecb531aee6b359b4968409410aba90817Yigit Boyar        public void deleteAndNotify(final int start, final int count) throws Throwable {
629b5f8b4fecb531aee6b359b4968409410aba90817Yigit Boyar            deleteAndNotify(new int[]{start, count});
630b5f8b4fecb531aee6b359b4968409410aba90817Yigit Boyar        }
631b5f8b4fecb531aee6b359b4968409410aba90817Yigit Boyar
632b5f8b4fecb531aee6b359b4968409410aba90817Yigit Boyar        /**
633b5f8b4fecb531aee6b359b4968409410aba90817Yigit Boyar         * Deletes items in the given ranges.
634b5f8b4fecb531aee6b359b4968409410aba90817Yigit Boyar         * <p>
635b5f8b4fecb531aee6b359b4968409410aba90817Yigit Boyar         * Note that each operation affects the one after so you should offset them properly.
636b5f8b4fecb531aee6b359b4968409410aba90817Yigit Boyar         * <p>
637b5f8b4fecb531aee6b359b4968409410aba90817Yigit Boyar         * For example, if adapter has 5 items (A,B,C,D,E), and then you call this method with
638b5f8b4fecb531aee6b359b4968409410aba90817Yigit Boyar         * <code>[1, 2],[2, 1]</code>, it will first delete items B,C and the new adapter will be
639b5f8b4fecb531aee6b359b4968409410aba90817Yigit Boyar         * A D E. Then it will delete 2,1 which means it will delete E.
640b5f8b4fecb531aee6b359b4968409410aba90817Yigit Boyar         */
641b5f8b4fecb531aee6b359b4968409410aba90817Yigit Boyar        public void deleteAndNotify(final int[]... startCountTuples) throws Throwable {
6428ae76f91527ce850f155ce960fb9068bcd5d49f9Yigit Boyar            for (int[] tuple : startCountTuples) {
6438ae76f91527ce850f155ce960fb9068bcd5d49f9Yigit Boyar                tuple[1] = -tuple[1];
6448ae76f91527ce850f155ce960fb9068bcd5d49f9Yigit Boyar            }
6458ae76f91527ce850f155ce960fb9068bcd5d49f9Yigit Boyar            new AddRemoveRunnable(startCountTuples).runOnMainThread();
646d7d27e9ebe5c7325e67e1a8af265378bd2056cadChet Haase        }
647d7d27e9ebe5c7325e67e1a8af265378bd2056cadChet Haase
648c8b0ff5869e17a656a3a1d7fb306efa5b99fa07fYigit Boyar        @Override
649c8b0ff5869e17a656a3a1d7fb306efa5b99fa07fYigit Boyar        public long getItemId(int position) {
650c8b0ff5869e17a656a3a1d7fb306efa5b99fa07fYigit Boyar            return hasStableIds() ? mItems.get(position).mId : super.getItemId(position);
651c8b0ff5869e17a656a3a1d7fb306efa5b99fa07fYigit Boyar        }
652c8b0ff5869e17a656a3a1d7fb306efa5b99fa07fYigit Boyar
6532d2e8d88103866b631eb0f3805da137ebcfb0275Yigit Boyar        public void offsetOriginalIndices(int start, int offset) {
6542d2e8d88103866b631eb0f3805da137ebcfb0275Yigit Boyar            for (int i = start; i < mItems.size(); i++) {
6552d2e8d88103866b631eb0f3805da137ebcfb0275Yigit Boyar                mItems.get(i).mAdapterIndex += offset;
6562d2e8d88103866b631eb0f3805da137ebcfb0275Yigit Boyar            }
6572d2e8d88103866b631eb0f3805da137ebcfb0275Yigit Boyar        }
6582d2e8d88103866b631eb0f3805da137ebcfb0275Yigit Boyar
659e0c347f627f8a78d3e5e3e5eaac9c3ae26208689Yigit Boyar        /**
660e0c347f627f8a78d3e5e3e5eaac9c3ae26208689Yigit Boyar         * @param start inclusive
661e0c347f627f8a78d3e5e3e5eaac9c3ae26208689Yigit Boyar         * @param end exclusive
662e0c347f627f8a78d3e5e3e5eaac9c3ae26208689Yigit Boyar         * @param offset
663e0c347f627f8a78d3e5e3e5eaac9c3ae26208689Yigit Boyar         */
664e0c347f627f8a78d3e5e3e5eaac9c3ae26208689Yigit Boyar        public void offsetOriginalIndicesBetween(int start, int end, int offset) {
665e0c347f627f8a78d3e5e3e5eaac9c3ae26208689Yigit Boyar            for (int i = start; i < end && i < mItems.size(); i++) {
666e0c347f627f8a78d3e5e3e5eaac9c3ae26208689Yigit Boyar                mItems.get(i).mAdapterIndex += offset;
667e0c347f627f8a78d3e5e3e5eaac9c3ae26208689Yigit Boyar            }
668e0c347f627f8a78d3e5e3e5eaac9c3ae26208689Yigit Boyar        }
669e0c347f627f8a78d3e5e3e5eaac9c3ae26208689Yigit Boyar
670b5f8b4fecb531aee6b359b4968409410aba90817Yigit Boyar        public void addAndNotify(final int start, final int count) throws Throwable {
671b5f8b4fecb531aee6b359b4968409410aba90817Yigit Boyar            addAndNotify(new int[]{start, count});
672b5f8b4fecb531aee6b359b4968409410aba90817Yigit Boyar        }
673b5f8b4fecb531aee6b359b4968409410aba90817Yigit Boyar
674b5f8b4fecb531aee6b359b4968409410aba90817Yigit Boyar        public void addAndNotify(final int[]... startCountTuples) throws Throwable {
6758ae76f91527ce850f155ce960fb9068bcd5d49f9Yigit Boyar            new AddRemoveRunnable(startCountTuples).runOnMainThread();
6768ae76f91527ce850f155ce960fb9068bcd5d49f9Yigit Boyar        }
677b5f8b4fecb531aee6b359b4968409410aba90817Yigit Boyar
678cadd54707a7aaa4cfa9c8b8051c6185e8717f22eChet Haase        public void dispatchDataSetChanged() throws Throwable {
67922b6b8163d2e15eb6d0a89906854dec801945f1dYigit Boyar            runTestOnUiThread(new Runnable() {
68022b6b8163d2e15eb6d0a89906854dec801945f1dYigit Boyar                @Override
68122b6b8163d2e15eb6d0a89906854dec801945f1dYigit Boyar                public void run() {
68222b6b8163d2e15eb6d0a89906854dec801945f1dYigit Boyar                    notifyDataSetChanged();
68322b6b8163d2e15eb6d0a89906854dec801945f1dYigit Boyar                }
68422b6b8163d2e15eb6d0a89906854dec801945f1dYigit Boyar            });
68522b6b8163d2e15eb6d0a89906854dec801945f1dYigit Boyar        }
68622b6b8163d2e15eb6d0a89906854dec801945f1dYigit Boyar
687cadd54707a7aaa4cfa9c8b8051c6185e8717f22eChet Haase        public void changeAndNotify(final int start, final int count) throws Throwable {
6887e9da1a2629b657d56e2f54abd18c6249f3be037Yigit Boyar            runTestOnUiThread(new Runnable() {
6897e9da1a2629b657d56e2f54abd18c6249f3be037Yigit Boyar                @Override
6907e9da1a2629b657d56e2f54abd18c6249f3be037Yigit Boyar                public void run() {
6917e9da1a2629b657d56e2f54abd18c6249f3be037Yigit Boyar                    notifyItemRangeChanged(start, count);
6927e9da1a2629b657d56e2f54abd18c6249f3be037Yigit Boyar                }
6937e9da1a2629b657d56e2f54abd18c6249f3be037Yigit Boyar            });
6947e9da1a2629b657d56e2f54abd18c6249f3be037Yigit Boyar        }
6957e9da1a2629b657d56e2f54abd18c6249f3be037Yigit Boyar
696cadd54707a7aaa4cfa9c8b8051c6185e8717f22eChet Haase        public void changePositionsAndNotify(final int... positions) throws Throwable {
697cadd54707a7aaa4cfa9c8b8051c6185e8717f22eChet Haase            runTestOnUiThread(new Runnable() {
698cadd54707a7aaa4cfa9c8b8051c6185e8717f22eChet Haase                @Override
699cadd54707a7aaa4cfa9c8b8051c6185e8717f22eChet Haase                public void run() {
700cadd54707a7aaa4cfa9c8b8051c6185e8717f22eChet Haase                    for (int i = 0; i < positions.length; i += 1) {
701cadd54707a7aaa4cfa9c8b8051c6185e8717f22eChet Haase                        TestAdapter.super.notifyItemRangeChanged(positions[i], 1);
702cadd54707a7aaa4cfa9c8b8051c6185e8717f22eChet Haase                    }
703cadd54707a7aaa4cfa9c8b8051c6185e8717f22eChet Haase                }
704cadd54707a7aaa4cfa9c8b8051c6185e8717f22eChet Haase            });
705cadd54707a7aaa4cfa9c8b8051c6185e8717f22eChet Haase        }
706cadd54707a7aaa4cfa9c8b8051c6185e8717f22eChet Haase
7078ae76f91527ce850f155ce960fb9068bcd5d49f9Yigit Boyar        /**
7088ae76f91527ce850f155ce960fb9068bcd5d49f9Yigit Boyar         * Similar to other methods but negative count means delete and position count means add.
7098ae76f91527ce850f155ce960fb9068bcd5d49f9Yigit Boyar         * <p>
7108ae76f91527ce850f155ce960fb9068bcd5d49f9Yigit Boyar         * For instance, calling this method with <code>[1,1], [2,-1]</code> it will first add an
7118ae76f91527ce850f155ce960fb9068bcd5d49f9Yigit Boyar         * item to index 1, then remove an item from index 2 (updated index 2)
7128ae76f91527ce850f155ce960fb9068bcd5d49f9Yigit Boyar         */
7138ae76f91527ce850f155ce960fb9068bcd5d49f9Yigit Boyar        public void addDeleteAndNotify(final int[]... startCountTuples) throws Throwable {
7148ae76f91527ce850f155ce960fb9068bcd5d49f9Yigit Boyar            new AddRemoveRunnable(startCountTuples).runOnMainThread();
715d7d27e9ebe5c7325e67e1a8af265378bd2056cadChet Haase        }
716d7d27e9ebe5c7325e67e1a8af265378bd2056cadChet Haase
717d7d27e9ebe5c7325e67e1a8af265378bd2056cadChet Haase        @Override
718d7d27e9ebe5c7325e67e1a8af265378bd2056cadChet Haase        public int getItemCount() {
719d7d27e9ebe5c7325e67e1a8af265378bd2056cadChet Haase            return mItems.size();
720d7d27e9ebe5c7325e67e1a8af265378bd2056cadChet Haase        }
7218ae76f91527ce850f155ce960fb9068bcd5d49f9Yigit Boyar
722115ba0c7b2a14aa4cd0273952195e1d8f6468f87Yigit Boyar        /**
723115ba0c7b2a14aa4cd0273952195e1d8f6468f87Yigit Boyar         * Uses notifyDataSetChanged
724115ba0c7b2a14aa4cd0273952195e1d8f6468f87Yigit Boyar         */
725e0c347f627f8a78d3e5e3e5eaac9c3ae26208689Yigit Boyar        public void moveItems(boolean notifyChange, int[]... fromToTuples) throws Throwable {
726e0c347f627f8a78d3e5e3e5eaac9c3ae26208689Yigit Boyar            for (int i = 0; i < fromToTuples.length; i += 1) {
727e0c347f627f8a78d3e5e3e5eaac9c3ae26208689Yigit Boyar                int[] tuple = fromToTuples[i];
728e0c347f627f8a78d3e5e3e5eaac9c3ae26208689Yigit Boyar                moveItem(tuple[0], tuple[1], false);
729e0c347f627f8a78d3e5e3e5eaac9c3ae26208689Yigit Boyar            }
730e0c347f627f8a78d3e5e3e5eaac9c3ae26208689Yigit Boyar            if (notifyChange) {
731cadd54707a7aaa4cfa9c8b8051c6185e8717f22eChet Haase                dispatchDataSetChanged();
732e0c347f627f8a78d3e5e3e5eaac9c3ae26208689Yigit Boyar            }
733e0c347f627f8a78d3e5e3e5eaac9c3ae26208689Yigit Boyar        }
734e0c347f627f8a78d3e5e3e5eaac9c3ae26208689Yigit Boyar
735115ba0c7b2a14aa4cd0273952195e1d8f6468f87Yigit Boyar        /**
736115ba0c7b2a14aa4cd0273952195e1d8f6468f87Yigit Boyar         * Uses notifyDataSetChanged
737115ba0c7b2a14aa4cd0273952195e1d8f6468f87Yigit Boyar         */
738e0c347f627f8a78d3e5e3e5eaac9c3ae26208689Yigit Boyar        public void moveItem(final int from, final int to, final boolean notifyChange)
739e0c347f627f8a78d3e5e3e5eaac9c3ae26208689Yigit Boyar                throws Throwable {
740e0c347f627f8a78d3e5e3e5eaac9c3ae26208689Yigit Boyar            runTestOnUiThread(new Runnable() {
741e0c347f627f8a78d3e5e3e5eaac9c3ae26208689Yigit Boyar                @Override
742e0c347f627f8a78d3e5e3e5eaac9c3ae26208689Yigit Boyar                public void run() {
743ec60a348ce39889a99f7ca02da94a6867bfdf66fYigit Boyar                    moveInUIThread(from, to);
744e0c347f627f8a78d3e5e3e5eaac9c3ae26208689Yigit Boyar                    if (notifyChange) {
745e0c347f627f8a78d3e5e3e5eaac9c3ae26208689Yigit Boyar                        notifyDataSetChanged();
746e0c347f627f8a78d3e5e3e5eaac9c3ae26208689Yigit Boyar                    }
747e0c347f627f8a78d3e5e3e5eaac9c3ae26208689Yigit Boyar                }
748e0c347f627f8a78d3e5e3e5eaac9c3ae26208689Yigit Boyar            });
749e0c347f627f8a78d3e5e3e5eaac9c3ae26208689Yigit Boyar        }
750e0c347f627f8a78d3e5e3e5eaac9c3ae26208689Yigit Boyar
751115ba0c7b2a14aa4cd0273952195e1d8f6468f87Yigit Boyar        /**
752115ba0c7b2a14aa4cd0273952195e1d8f6468f87Yigit Boyar         * Uses notifyItemMoved
753115ba0c7b2a14aa4cd0273952195e1d8f6468f87Yigit Boyar         */
754115ba0c7b2a14aa4cd0273952195e1d8f6468f87Yigit Boyar        public void moveAndNotify(final int from, final int to) throws Throwable {
755115ba0c7b2a14aa4cd0273952195e1d8f6468f87Yigit Boyar            runTestOnUiThread(new Runnable() {
756115ba0c7b2a14aa4cd0273952195e1d8f6468f87Yigit Boyar                @Override
757115ba0c7b2a14aa4cd0273952195e1d8f6468f87Yigit Boyar                public void run() {
758ec60a348ce39889a99f7ca02da94a6867bfdf66fYigit Boyar                    moveInUIThread(from, to);
759115ba0c7b2a14aa4cd0273952195e1d8f6468f87Yigit Boyar                    notifyItemMoved(from, to);
760115ba0c7b2a14aa4cd0273952195e1d8f6468f87Yigit Boyar                }
761115ba0c7b2a14aa4cd0273952195e1d8f6468f87Yigit Boyar            });
762115ba0c7b2a14aa4cd0273952195e1d8f6468f87Yigit Boyar        }
763115ba0c7b2a14aa4cd0273952195e1d8f6468f87Yigit Boyar
764ec60a348ce39889a99f7ca02da94a6867bfdf66fYigit Boyar        protected void moveInUIThread(int from, int to) {
765ec60a348ce39889a99f7ca02da94a6867bfdf66fYigit Boyar            Item item = mItems.remove(from);
766ec60a348ce39889a99f7ca02da94a6867bfdf66fYigit Boyar            offsetOriginalIndices(from, -1);
767ec60a348ce39889a99f7ca02da94a6867bfdf66fYigit Boyar            mItems.add(to, item);
768ec60a348ce39889a99f7ca02da94a6867bfdf66fYigit Boyar            offsetOriginalIndices(to + 1, 1);
769ec60a348ce39889a99f7ca02da94a6867bfdf66fYigit Boyar            item.mAdapterIndex = to;
770ec60a348ce39889a99f7ca02da94a6867bfdf66fYigit Boyar        }
771115ba0c7b2a14aa4cd0273952195e1d8f6468f87Yigit Boyar
772115ba0c7b2a14aa4cd0273952195e1d8f6468f87Yigit Boyar
77390ca3086dbf66ad6bb8840e46ec8524a705e1c18Yigit Boyar        @Override
77490ca3086dbf66ad6bb8840e46ec8524a705e1c18Yigit Boyar        public ViewAttachDetachCounter getCounter() {
77590ca3086dbf66ad6bb8840e46ec8524a705e1c18Yigit Boyar            return mAttachmentCounter;
77690ca3086dbf66ad6bb8840e46ec8524a705e1c18Yigit Boyar        }
77790ca3086dbf66ad6bb8840e46ec8524a705e1c18Yigit Boyar
7788ae76f91527ce850f155ce960fb9068bcd5d49f9Yigit Boyar
7798ae76f91527ce850f155ce960fb9068bcd5d49f9Yigit Boyar        private class AddRemoveRunnable implements Runnable {
7808ae76f91527ce850f155ce960fb9068bcd5d49f9Yigit Boyar            final int[][] mStartCountTuples;
7818ae76f91527ce850f155ce960fb9068bcd5d49f9Yigit Boyar
7828ae76f91527ce850f155ce960fb9068bcd5d49f9Yigit Boyar            public AddRemoveRunnable(int[][] startCountTuples) {
7838ae76f91527ce850f155ce960fb9068bcd5d49f9Yigit Boyar                mStartCountTuples = startCountTuples;
7848ae76f91527ce850f155ce960fb9068bcd5d49f9Yigit Boyar            }
7858ae76f91527ce850f155ce960fb9068bcd5d49f9Yigit Boyar
7868ae76f91527ce850f155ce960fb9068bcd5d49f9Yigit Boyar            public void runOnMainThread() throws Throwable {
7878ae76f91527ce850f155ce960fb9068bcd5d49f9Yigit Boyar                if (Looper.myLooper() == Looper.getMainLooper()) {
7888ae76f91527ce850f155ce960fb9068bcd5d49f9Yigit Boyar                    run();
7898ae76f91527ce850f155ce960fb9068bcd5d49f9Yigit Boyar                } else {
7908ae76f91527ce850f155ce960fb9068bcd5d49f9Yigit Boyar                    runTestOnUiThread(this);
7918ae76f91527ce850f155ce960fb9068bcd5d49f9Yigit Boyar                }
7928ae76f91527ce850f155ce960fb9068bcd5d49f9Yigit Boyar            }
7938ae76f91527ce850f155ce960fb9068bcd5d49f9Yigit Boyar
7948ae76f91527ce850f155ce960fb9068bcd5d49f9Yigit Boyar            @Override
7958ae76f91527ce850f155ce960fb9068bcd5d49f9Yigit Boyar            public void run() {
7968ae76f91527ce850f155ce960fb9068bcd5d49f9Yigit Boyar                for (int[] tuple : mStartCountTuples) {
7978ae76f91527ce850f155ce960fb9068bcd5d49f9Yigit Boyar                    if (tuple[1] < 0) {
7988ae76f91527ce850f155ce960fb9068bcd5d49f9Yigit Boyar                        delete(tuple);
7998ae76f91527ce850f155ce960fb9068bcd5d49f9Yigit Boyar                    } else {
8008ae76f91527ce850f155ce960fb9068bcd5d49f9Yigit Boyar                        add(tuple);
8018ae76f91527ce850f155ce960fb9068bcd5d49f9Yigit Boyar                    }
8028ae76f91527ce850f155ce960fb9068bcd5d49f9Yigit Boyar                }
8038ae76f91527ce850f155ce960fb9068bcd5d49f9Yigit Boyar            }
8048ae76f91527ce850f155ce960fb9068bcd5d49f9Yigit Boyar
8058ae76f91527ce850f155ce960fb9068bcd5d49f9Yigit Boyar            private void add(int[] tuple) {
8062d2e8d88103866b631eb0f3805da137ebcfb0275Yigit Boyar                // offset others
8072d2e8d88103866b631eb0f3805da137ebcfb0275Yigit Boyar                offsetOriginalIndices(tuple[0], tuple[1]);
8088ae76f91527ce850f155ce960fb9068bcd5d49f9Yigit Boyar                for (int i = 0; i < tuple[1]; i++) {
8098ae76f91527ce850f155ce960fb9068bcd5d49f9Yigit Boyar                    mItems.add(tuple[0], new Item(i, "new item " + i));
8108ae76f91527ce850f155ce960fb9068bcd5d49f9Yigit Boyar                }
8118ae76f91527ce850f155ce960fb9068bcd5d49f9Yigit Boyar                notifyItemRangeInserted(tuple[0], tuple[1]);
8128ae76f91527ce850f155ce960fb9068bcd5d49f9Yigit Boyar            }
8138ae76f91527ce850f155ce960fb9068bcd5d49f9Yigit Boyar
8148ae76f91527ce850f155ce960fb9068bcd5d49f9Yigit Boyar            private void delete(int[] tuple) {
8152d2e8d88103866b631eb0f3805da137ebcfb0275Yigit Boyar                final int count = -tuple[1];
8162d2e8d88103866b631eb0f3805da137ebcfb0275Yigit Boyar                offsetOriginalIndices(tuple[0] + count, tuple[1]);
8172d2e8d88103866b631eb0f3805da137ebcfb0275Yigit Boyar                for (int i = 0; i < count; i++) {
8188ae76f91527ce850f155ce960fb9068bcd5d49f9Yigit Boyar                    mItems.remove(tuple[0]);
8198ae76f91527ce850f155ce960fb9068bcd5d49f9Yigit Boyar                }
8202d2e8d88103866b631eb0f3805da137ebcfb0275Yigit Boyar                notifyItemRangeRemoved(tuple[0], count);
8218ae76f91527ce850f155ce960fb9068bcd5d49f9Yigit Boyar            }
8228ae76f91527ce850f155ce960fb9068bcd5d49f9Yigit Boyar        }
8238ae76f91527ce850f155ce960fb9068bcd5d49f9Yigit Boyar    }
8248ae76f91527ce850f155ce960fb9068bcd5d49f9Yigit Boyar
82590ca3086dbf66ad6bb8840e46ec8524a705e1c18Yigit Boyar    public boolean isMainThread() {
82690ca3086dbf66ad6bb8840e46ec8524a705e1c18Yigit Boyar        return Looper.myLooper() == Looper.getMainLooper();
82790ca3086dbf66ad6bb8840e46ec8524a705e1c18Yigit Boyar    }
82890ca3086dbf66ad6bb8840e46ec8524a705e1c18Yigit Boyar
8298ae76f91527ce850f155ce960fb9068bcd5d49f9Yigit Boyar    @Override
8308ae76f91527ce850f155ce960fb9068bcd5d49f9Yigit Boyar    public void runTestOnUiThread(Runnable r) throws Throwable {
8318ae76f91527ce850f155ce960fb9068bcd5d49f9Yigit Boyar        if (Looper.myLooper() == Looper.getMainLooper()) {
8328ae76f91527ce850f155ce960fb9068bcd5d49f9Yigit Boyar            r.run();
8338ae76f91527ce850f155ce960fb9068bcd5d49f9Yigit Boyar        } else {
8348ae76f91527ce850f155ce960fb9068bcd5d49f9Yigit Boyar            super.runTestOnUiThread(r);
8358ae76f91527ce850f155ce960fb9068bcd5d49f9Yigit Boyar        }
836d7d27e9ebe5c7325e67e1a8af265378bd2056cadChet Haase    }
837c50c4cad31d73e574b27bb3d7581542975e37263Yigit Boyar
838c50c4cad31d73e574b27bb3d7581542975e37263Yigit Boyar    static class TargetTuple {
839c50c4cad31d73e574b27bb3d7581542975e37263Yigit Boyar
840c50c4cad31d73e574b27bb3d7581542975e37263Yigit Boyar        final int mPosition;
841c50c4cad31d73e574b27bb3d7581542975e37263Yigit Boyar
842c50c4cad31d73e574b27bb3d7581542975e37263Yigit Boyar        final int mLayoutDirection;
843c50c4cad31d73e574b27bb3d7581542975e37263Yigit Boyar
844c50c4cad31d73e574b27bb3d7581542975e37263Yigit Boyar        TargetTuple(int position, int layoutDirection) {
845c50c4cad31d73e574b27bb3d7581542975e37263Yigit Boyar            this.mPosition = position;
846c50c4cad31d73e574b27bb3d7581542975e37263Yigit Boyar            this.mLayoutDirection = layoutDirection;
847c50c4cad31d73e574b27bb3d7581542975e37263Yigit Boyar        }
848c50c4cad31d73e574b27bb3d7581542975e37263Yigit Boyar
849c50c4cad31d73e574b27bb3d7581542975e37263Yigit Boyar        @Override
850c50c4cad31d73e574b27bb3d7581542975e37263Yigit Boyar        public String toString() {
851c50c4cad31d73e574b27bb3d7581542975e37263Yigit Boyar            return "TargetTuple{" +
852c50c4cad31d73e574b27bb3d7581542975e37263Yigit Boyar                    "mPosition=" + mPosition +
853c50c4cad31d73e574b27bb3d7581542975e37263Yigit Boyar                    ", mLayoutDirection=" + mLayoutDirection +
854c50c4cad31d73e574b27bb3d7581542975e37263Yigit Boyar                    '}';
855c50c4cad31d73e574b27bb3d7581542975e37263Yigit Boyar        }
856c50c4cad31d73e574b27bb3d7581542975e37263Yigit Boyar    }
85790ca3086dbf66ad6bb8840e46ec8524a705e1c18Yigit Boyar
85890ca3086dbf66ad6bb8840e46ec8524a705e1c18Yigit Boyar    public interface AttachDetachCountingAdapter {
85990ca3086dbf66ad6bb8840e46ec8524a705e1c18Yigit Boyar
86090ca3086dbf66ad6bb8840e46ec8524a705e1c18Yigit Boyar        ViewAttachDetachCounter getCounter();
86190ca3086dbf66ad6bb8840e46ec8524a705e1c18Yigit Boyar    }
86290ca3086dbf66ad6bb8840e46ec8524a705e1c18Yigit Boyar
86390ca3086dbf66ad6bb8840e46ec8524a705e1c18Yigit Boyar    public class ViewAttachDetachCounter {
86490ca3086dbf66ad6bb8840e46ec8524a705e1c18Yigit Boyar
86590ca3086dbf66ad6bb8840e46ec8524a705e1c18Yigit Boyar        Set<RecyclerView.ViewHolder> mAttachedSet = new HashSet<RecyclerView.ViewHolder>();
86690ca3086dbf66ad6bb8840e46ec8524a705e1c18Yigit Boyar
86790ca3086dbf66ad6bb8840e46ec8524a705e1c18Yigit Boyar        public void validateRemaining(RecyclerView recyclerView) {
86890ca3086dbf66ad6bb8840e46ec8524a705e1c18Yigit Boyar            final int childCount = recyclerView.getChildCount();
86990ca3086dbf66ad6bb8840e46ec8524a705e1c18Yigit Boyar            for (int i = 0; i < childCount; i++) {
87090ca3086dbf66ad6bb8840e46ec8524a705e1c18Yigit Boyar                View view = recyclerView.getChildAt(i);
87190ca3086dbf66ad6bb8840e46ec8524a705e1c18Yigit Boyar                RecyclerView.ViewHolder vh = recyclerView.getChildViewHolder(view);
87290ca3086dbf66ad6bb8840e46ec8524a705e1c18Yigit Boyar                assertTrue("remaining view should be in attached set " + vh,
87390ca3086dbf66ad6bb8840e46ec8524a705e1c18Yigit Boyar                        mAttachedSet.contains(vh));
87490ca3086dbf66ad6bb8840e46ec8524a705e1c18Yigit Boyar            }
87590ca3086dbf66ad6bb8840e46ec8524a705e1c18Yigit Boyar            assertEquals("there should not be any views left in attached set",
87690ca3086dbf66ad6bb8840e46ec8524a705e1c18Yigit Boyar                    childCount, mAttachedSet.size());
87790ca3086dbf66ad6bb8840e46ec8524a705e1c18Yigit Boyar        }
87890ca3086dbf66ad6bb8840e46ec8524a705e1c18Yigit Boyar
87990ca3086dbf66ad6bb8840e46ec8524a705e1c18Yigit Boyar        public void onViewDetached(RecyclerView.ViewHolder viewHolder) {
88090ca3086dbf66ad6bb8840e46ec8524a705e1c18Yigit Boyar            try {
88190ca3086dbf66ad6bb8840e46ec8524a705e1c18Yigit Boyar                assertTrue("view holder should be in attached set",
88290ca3086dbf66ad6bb8840e46ec8524a705e1c18Yigit Boyar                        mAttachedSet.remove(viewHolder));
88390ca3086dbf66ad6bb8840e46ec8524a705e1c18Yigit Boyar            } catch (Throwable t) {
88490ca3086dbf66ad6bb8840e46ec8524a705e1c18Yigit Boyar                postExceptionToInstrumentation(t);
88590ca3086dbf66ad6bb8840e46ec8524a705e1c18Yigit Boyar            }
88690ca3086dbf66ad6bb8840e46ec8524a705e1c18Yigit Boyar        }
88790ca3086dbf66ad6bb8840e46ec8524a705e1c18Yigit Boyar
88890ca3086dbf66ad6bb8840e46ec8524a705e1c18Yigit Boyar        public void onViewAttached(RecyclerView.ViewHolder viewHolder) {
88990ca3086dbf66ad6bb8840e46ec8524a705e1c18Yigit Boyar            try {
89090ca3086dbf66ad6bb8840e46ec8524a705e1c18Yigit Boyar                assertTrue("view holder should not be in attached set",
89190ca3086dbf66ad6bb8840e46ec8524a705e1c18Yigit Boyar                        mAttachedSet.add(viewHolder));
89290ca3086dbf66ad6bb8840e46ec8524a705e1c18Yigit Boyar            } catch (Throwable t) {
89390ca3086dbf66ad6bb8840e46ec8524a705e1c18Yigit Boyar                postExceptionToInstrumentation(t);
89490ca3086dbf66ad6bb8840e46ec8524a705e1c18Yigit Boyar            }
89590ca3086dbf66ad6bb8840e46ec8524a705e1c18Yigit Boyar        }
89690ca3086dbf66ad6bb8840e46ec8524a705e1c18Yigit Boyar
89790ca3086dbf66ad6bb8840e46ec8524a705e1c18Yigit Boyar        public void onAttached(RecyclerView recyclerView) {
89890ca3086dbf66ad6bb8840e46ec8524a705e1c18Yigit Boyar            // when a new RV is attached, clear the set and add all view holders
89990ca3086dbf66ad6bb8840e46ec8524a705e1c18Yigit Boyar            mAttachedSet.clear();
90090ca3086dbf66ad6bb8840e46ec8524a705e1c18Yigit Boyar            final int childCount = recyclerView.getChildCount();
90190ca3086dbf66ad6bb8840e46ec8524a705e1c18Yigit Boyar            for (int i = 0; i < childCount; i ++) {
90290ca3086dbf66ad6bb8840e46ec8524a705e1c18Yigit Boyar                View view = recyclerView.getChildAt(i);
90390ca3086dbf66ad6bb8840e46ec8524a705e1c18Yigit Boyar                mAttachedSet.add(recyclerView.getChildViewHolder(view));
90490ca3086dbf66ad6bb8840e46ec8524a705e1c18Yigit Boyar            }
90590ca3086dbf66ad6bb8840e46ec8524a705e1c18Yigit Boyar        }
90690ca3086dbf66ad6bb8840e46ec8524a705e1c18Yigit Boyar
90790ca3086dbf66ad6bb8840e46ec8524a705e1c18Yigit Boyar        public void onDetached(RecyclerView recyclerView) {
90890ca3086dbf66ad6bb8840e46ec8524a705e1c18Yigit Boyar            validateRemaining(recyclerView);
90990ca3086dbf66ad6bb8840e46ec8524a705e1c18Yigit Boyar        }
91090ca3086dbf66ad6bb8840e46ec8524a705e1c18Yigit Boyar    }
9117a59e4b36500bcfddd842376d6c3abe0d9f23d84Yigit Boyar}
912