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(),
17529c349ed8863cc15c2580465227c37771ac49576Yigit Boyar                mRecyclerView.getWidth() - mRecyclerView.getPaddingRight(),
17629c349ed8863cc15c2580465227c37771ac49576Yigit Boyar                mRecyclerView.getHeight() - mRecyclerView.getPaddingBottom()
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
358ef3e37fe088c7d8e614bbc4ed58d6c0d3fa807a1Dake Gu    void freezeLayout(final boolean freeze) throws Throwable {
359ef3e37fe088c7d8e614bbc4ed58d6c0d3fa807a1Dake Gu        runTestOnUiThread(new Runnable() {
360ef3e37fe088c7d8e614bbc4ed58d6c0d3fa807a1Dake Gu            @Override
361ef3e37fe088c7d8e614bbc4ed58d6c0d3fa807a1Dake Gu            public void run() {
362ef3e37fe088c7d8e614bbc4ed58d6c0d3fa807a1Dake Gu                mRecyclerView.setLayoutFrozen(freeze);
363ef3e37fe088c7d8e614bbc4ed58d6c0d3fa807a1Dake Gu            }
364ef3e37fe088c7d8e614bbc4ed58d6c0d3fa807a1Dake Gu        });
365ef3e37fe088c7d8e614bbc4ed58d6c0d3fa807a1Dake Gu    }
366ef3e37fe088c7d8e614bbc4ed58d6c0d3fa807a1Dake Gu
367d7d27e9ebe5c7325e67e1a8af265378bd2056cadChet Haase    class TestViewHolder extends RecyclerView.ViewHolder {
368d7d27e9ebe5c7325e67e1a8af265378bd2056cadChet Haase
369115ba0c7b2a14aa4cd0273952195e1d8f6468f87Yigit Boyar        Item mBoundItem;
370d7d27e9ebe5c7325e67e1a8af265378bd2056cadChet Haase
371d7d27e9ebe5c7325e67e1a8af265378bd2056cadChet Haase        public TestViewHolder(View itemView) {
372d7d27e9ebe5c7325e67e1a8af265378bd2056cadChet Haase            super(itemView);
373310e95e1c6dfe4f26ef594233e65e1ff83e0f1ffYigit Boyar            itemView.setFocusable(true);
374d7d27e9ebe5c7325e67e1a8af265378bd2056cadChet Haase        }
375504c54ea52c1b2aae6f8f4ae128f1dcaac7e3f6aYigit Boyar
376504c54ea52c1b2aae6f8f4ae128f1dcaac7e3f6aYigit Boyar        @Override
377504c54ea52c1b2aae6f8f4ae128f1dcaac7e3f6aYigit Boyar        public String toString() {
378115ba0c7b2a14aa4cd0273952195e1d8f6468f87Yigit Boyar            return super.toString() + " item:" + mBoundItem;
379504c54ea52c1b2aae6f8f4ae128f1dcaac7e3f6aYigit Boyar        }
380d7d27e9ebe5c7325e67e1a8af265378bd2056cadChet Haase    }
381da50fde358bd3ef2edb273b3bf0265cf5e84ec6bYigit Boyar    class DumbLayoutManager extends TestLayoutManager {
382da50fde358bd3ef2edb273b3bf0265cf5e84ec6bYigit Boyar        ReentrantLock mLayoutLock = new ReentrantLock();
383da50fde358bd3ef2edb273b3bf0265cf5e84ec6bYigit Boyar        public void blockLayout() {
384da50fde358bd3ef2edb273b3bf0265cf5e84ec6bYigit Boyar            mLayoutLock.lock();
385da50fde358bd3ef2edb273b3bf0265cf5e84ec6bYigit Boyar        }
386d7d27e9ebe5c7325e67e1a8af265378bd2056cadChet Haase
387da50fde358bd3ef2edb273b3bf0265cf5e84ec6bYigit Boyar        public void unblockLayout() {
388da50fde358bd3ef2edb273b3bf0265cf5e84ec6bYigit Boyar            mLayoutLock.unlock();
389da50fde358bd3ef2edb273b3bf0265cf5e84ec6bYigit Boyar        }
390da50fde358bd3ef2edb273b3bf0265cf5e84ec6bYigit Boyar        @Override
391da50fde358bd3ef2edb273b3bf0265cf5e84ec6bYigit Boyar        public void onLayoutChildren(RecyclerView.Recycler recycler, RecyclerView.State state) {
392da50fde358bd3ef2edb273b3bf0265cf5e84ec6bYigit Boyar            mLayoutLock.lock();
393da50fde358bd3ef2edb273b3bf0265cf5e84ec6bYigit Boyar            detachAndScrapAttachedViews(recycler);
394da50fde358bd3ef2edb273b3bf0265cf5e84ec6bYigit Boyar            layoutRange(recycler, 0, state.getItemCount());
395da50fde358bd3ef2edb273b3bf0265cf5e84ec6bYigit Boyar            if (layoutLatch != null) {
396da50fde358bd3ef2edb273b3bf0265cf5e84ec6bYigit Boyar                layoutLatch.countDown();
397da50fde358bd3ef2edb273b3bf0265cf5e84ec6bYigit Boyar            }
398da50fde358bd3ef2edb273b3bf0265cf5e84ec6bYigit Boyar            mLayoutLock.unlock();
399da50fde358bd3ef2edb273b3bf0265cf5e84ec6bYigit Boyar        }
400da50fde358bd3ef2edb273b3bf0265cf5e84ec6bYigit Boyar    }
401e71a1df9b3c0e1bd3c21a1b3dd20a41790d4a950Yigit Boyar    public class TestLayoutManager extends RecyclerView.LayoutManager {
402f129f1b050d2542a91fe8175eac30183beb07b41Chris Banes        int mScrollVerticallyAmount;
403f129f1b050d2542a91fe8175eac30183beb07b41Chris Banes        int mScrollHorizontallyAmount;
404e71a1df9b3c0e1bd3c21a1b3dd20a41790d4a950Yigit Boyar        protected CountDownLatch layoutLatch;
405d7d27e9ebe5c7325e67e1a8af265378bd2056cadChet Haase
406d7d27e9ebe5c7325e67e1a8af265378bd2056cadChet Haase        public void expectLayouts(int count) {
407d7d27e9ebe5c7325e67e1a8af265378bd2056cadChet Haase            layoutLatch = new CountDownLatch(count);
408d7d27e9ebe5c7325e67e1a8af265378bd2056cadChet Haase        }
409d7d27e9ebe5c7325e67e1a8af265378bd2056cadChet Haase
410629687bba8da1a73c48d8fe87393a13581ce46caYigit Boyar        public void waitForLayout(long timeout, TimeUnit timeUnit, boolean waitForIdle)
411629687bba8da1a73c48d8fe87393a13581ce46caYigit Boyar                throws Throwable {
412b5f8b4fecb531aee6b359b4968409410aba90817Yigit Boyar            layoutLatch.await(timeout * (mDebug ? 100 : 1), timeUnit);
413d7d27e9ebe5c7325e67e1a8af265378bd2056cadChet Haase            assertEquals("all expected layouts should be executed at the expected time",
414d7d27e9ebe5c7325e67e1a8af265378bd2056cadChet Haase                    0, layoutLatch.getCount());
415629687bba8da1a73c48d8fe87393a13581ce46caYigit Boyar            if (waitForIdle) {
416629687bba8da1a73c48d8fe87393a13581ce46caYigit Boyar                getInstrumentation().waitForIdleSync();
417629687bba8da1a73c48d8fe87393a13581ce46caYigit Boyar            }
418629687bba8da1a73c48d8fe87393a13581ce46caYigit Boyar        }
419629687bba8da1a73c48d8fe87393a13581ce46caYigit Boyar
420629687bba8da1a73c48d8fe87393a13581ce46caYigit Boyar        public void waitForLayout(long timeout, TimeUnit timeUnit)
421629687bba8da1a73c48d8fe87393a13581ce46caYigit Boyar                throws Throwable {
422629687bba8da1a73c48d8fe87393a13581ce46caYigit Boyar            waitForLayout(timeout, timeUnit, true);
423d7d27e9ebe5c7325e67e1a8af265378bd2056cadChet Haase        }
424d7d27e9ebe5c7325e67e1a8af265378bd2056cadChet Haase
425b5f8b4fecb531aee6b359b4968409410aba90817Yigit Boyar        public void assertLayoutCount(int count, String msg, long timeout) throws Throwable {
426b5f8b4fecb531aee6b359b4968409410aba90817Yigit Boyar            layoutLatch.await(timeout, TimeUnit.SECONDS);
427b5f8b4fecb531aee6b359b4968409410aba90817Yigit Boyar            assertEquals(msg, count, layoutLatch.getCount());
428b5f8b4fecb531aee6b359b4968409410aba90817Yigit Boyar        }
429b5f8b4fecb531aee6b359b4968409410aba90817Yigit Boyar
430b5f8b4fecb531aee6b359b4968409410aba90817Yigit Boyar        public void assertNoLayout(String msg, long timeout) throws Throwable {
431b5f8b4fecb531aee6b359b4968409410aba90817Yigit Boyar            layoutLatch.await(timeout, TimeUnit.SECONDS);
432b5f8b4fecb531aee6b359b4968409410aba90817Yigit Boyar            assertFalse(msg, layoutLatch.getCount() == 0);
433b5f8b4fecb531aee6b359b4968409410aba90817Yigit Boyar        }
434b5f8b4fecb531aee6b359b4968409410aba90817Yigit Boyar
435b5f8b4fecb531aee6b359b4968409410aba90817Yigit Boyar        public void waitForLayout(long timeout) throws Throwable {
436629687bba8da1a73c48d8fe87393a13581ce46caYigit Boyar            waitForLayout(timeout * (mDebug ? 10000 : 1), TimeUnit.SECONDS, true);
437629687bba8da1a73c48d8fe87393a13581ce46caYigit Boyar        }
438629687bba8da1a73c48d8fe87393a13581ce46caYigit Boyar
439629687bba8da1a73c48d8fe87393a13581ce46caYigit Boyar        public void waitForLayout(long timeout, boolean waitForIdle) throws Throwable {
440629687bba8da1a73c48d8fe87393a13581ce46caYigit Boyar            waitForLayout(timeout * (mDebug ? 10000 : 1), TimeUnit.SECONDS, waitForIdle);
441b5f8b4fecb531aee6b359b4968409410aba90817Yigit Boyar        }
442b5f8b4fecb531aee6b359b4968409410aba90817Yigit Boyar
443d7d27e9ebe5c7325e67e1a8af265378bd2056cadChet Haase        @Override
444d7d27e9ebe5c7325e67e1a8af265378bd2056cadChet Haase        public RecyclerView.LayoutParams generateDefaultLayoutParams() {
445d7d27e9ebe5c7325e67e1a8af265378bd2056cadChet Haase            return new RecyclerView.LayoutParams(ViewGroup.LayoutParams.WRAP_CONTENT,
446d7d27e9ebe5c7325e67e1a8af265378bd2056cadChet Haase                    ViewGroup.LayoutParams.WRAP_CONTENT);
447d7d27e9ebe5c7325e67e1a8af265378bd2056cadChet Haase        }
448d7d27e9ebe5c7325e67e1a8af265378bd2056cadChet Haase
449d7d27e9ebe5c7325e67e1a8af265378bd2056cadChet Haase        void assertVisibleItemPositions() {
450d7d27e9ebe5c7325e67e1a8af265378bd2056cadChet Haase            int i = getChildCount();
451d7d27e9ebe5c7325e67e1a8af265378bd2056cadChet Haase            TestAdapter testAdapter = (TestAdapter) mRecyclerView.getAdapter();
452d7d27e9ebe5c7325e67e1a8af265378bd2056cadChet Haase            while (i-- > 0) {
453d7d27e9ebe5c7325e67e1a8af265378bd2056cadChet Haase                View view = getChildAt(i);
454d7d27e9ebe5c7325e67e1a8af265378bd2056cadChet Haase                RecyclerView.LayoutParams lp = (RecyclerView.LayoutParams) view.getLayoutParams();
455115ba0c7b2a14aa4cd0273952195e1d8f6468f87Yigit Boyar                Item item = ((TestViewHolder) lp.mViewHolder).mBoundItem;
456b5f8b4fecb531aee6b359b4968409410aba90817Yigit Boyar                if (mDebug) {
457d7d27e9ebe5c7325e67e1a8af265378bd2056cadChet Haase                    Log.d(TAG, "testing item " + i);
458d7d27e9ebe5c7325e67e1a8af265378bd2056cadChet Haase                }
4598ae76f91527ce850f155ce960fb9068bcd5d49f9Yigit Boyar                if (!lp.isItemRemoved()) {
4608ae76f91527ce850f155ce960fb9068bcd5d49f9Yigit Boyar                    RecyclerView.ViewHolder vh = mRecyclerView.getChildViewHolder(view);
4618ae76f91527ce850f155ce960fb9068bcd5d49f9Yigit Boyar                    assertSame("item position in LP should match adapter value :" + vh,
4628ae76f91527ce850f155ce960fb9068bcd5d49f9Yigit Boyar                            testAdapter.mItems.get(vh.mPosition), item);
4638ae76f91527ce850f155ce960fb9068bcd5d49f9Yigit Boyar                }
464d7d27e9ebe5c7325e67e1a8af265378bd2056cadChet Haase            }
465d7d27e9ebe5c7325e67e1a8af265378bd2056cadChet Haase        }
466d7d27e9ebe5c7325e67e1a8af265378bd2056cadChet Haase
467b5f8b4fecb531aee6b359b4968409410aba90817Yigit Boyar        RecyclerView.LayoutParams getLp(View v) {
468b5f8b4fecb531aee6b359b4968409410aba90817Yigit Boyar            return (RecyclerView.LayoutParams) v.getLayoutParams();
469b5f8b4fecb531aee6b359b4968409410aba90817Yigit Boyar        }
470b5f8b4fecb531aee6b359b4968409410aba90817Yigit Boyar
471e71a1df9b3c0e1bd3c21a1b3dd20a41790d4a950Yigit Boyar        protected void layoutRange(RecyclerView.Recycler recycler, int start, int end) {
472fe3996cf5af65fe11cc3ed47b67123ec267bd336Yigit Boyar            assertScrap(recycler);
473b5f8b4fecb531aee6b359b4968409410aba90817Yigit Boyar            if (mDebug) {
474d7d27e9ebe5c7325e67e1a8af265378bd2056cadChet Haase                Log.d(TAG, "will layout items from " + start + " to " + end);
475d7d27e9ebe5c7325e67e1a8af265378bd2056cadChet Haase            }
4765ced882cabdcefbb469e332f6f73983999aad0e5Yigit Boyar            int diff = end > start ? 1 : -1;
477504c54ea52c1b2aae6f8f4ae128f1dcaac7e3f6aYigit Boyar            int top = 0;
4785ced882cabdcefbb469e332f6f73983999aad0e5Yigit Boyar            for (int i = start; i != end; i+=diff) {
479b5f8b4fecb531aee6b359b4968409410aba90817Yigit Boyar                if (mDebug) {
480d7d27e9ebe5c7325e67e1a8af265378bd2056cadChet Haase                    Log.d(TAG, "laying out item " + i);
481d7d27e9ebe5c7325e67e1a8af265378bd2056cadChet Haase                }
482d7d27e9ebe5c7325e67e1a8af265378bd2056cadChet Haase                View view = recycler.getViewForPosition(i);
483b5f8b4fecb531aee6b359b4968409410aba90817Yigit Boyar                assertNotNull("view should not be null for valid position. "
484b5f8b4fecb531aee6b359b4968409410aba90817Yigit Boyar                        + "got null view at position " + i, view);
485c8b0ff5869e17a656a3a1d7fb306efa5b99fa07fYigit Boyar                if (!mRecyclerView.mState.isPreLayout()) {
486c8b0ff5869e17a656a3a1d7fb306efa5b99fa07fYigit Boyar                    RecyclerView.LayoutParams layoutParams = (RecyclerView.LayoutParams) view
487c8b0ff5869e17a656a3a1d7fb306efa5b99fa07fYigit Boyar                            .getLayoutParams();
488c8b0ff5869e17a656a3a1d7fb306efa5b99fa07fYigit Boyar                    assertFalse("In post layout, getViewForPosition should never return a view "
489c8b0ff5869e17a656a3a1d7fb306efa5b99fa07fYigit Boyar                            + "that is removed", layoutParams != null
490c8b0ff5869e17a656a3a1d7fb306efa5b99fa07fYigit Boyar                            && layoutParams.isItemRemoved());
491c8b0ff5869e17a656a3a1d7fb306efa5b99fa07fYigit Boyar
492b5f8b4fecb531aee6b359b4968409410aba90817Yigit Boyar                }
4938da5bba529a2fe9bd2aafab28010e11ec4de93c7Yigit Boyar                assertEquals("getViewForPosition should return correct position",
4948da5bba529a2fe9bd2aafab28010e11ec4de93c7Yigit Boyar                        i, getPosition(view));
495c8b0ff5869e17a656a3a1d7fb306efa5b99fa07fYigit Boyar                addView(view);
496b5f8b4fecb531aee6b359b4968409410aba90817Yigit Boyar
497d7d27e9ebe5c7325e67e1a8af265378bd2056cadChet Haase                measureChildWithMargins(view, 0, 0);
498e71a1df9b3c0e1bd3c21a1b3dd20a41790d4a950Yigit Boyar                if (getLayoutDirection() == ViewCompat.LAYOUT_DIRECTION_RTL) {
499e71a1df9b3c0e1bd3c21a1b3dd20a41790d4a950Yigit Boyar                    layoutDecorated(view, getWidth() - getDecoratedMeasuredWidth(view), top,
500e71a1df9b3c0e1bd3c21a1b3dd20a41790d4a950Yigit Boyar                            getWidth(), top + getDecoratedMeasuredHeight(view));
501e71a1df9b3c0e1bd3c21a1b3dd20a41790d4a950Yigit Boyar                } else {
502e71a1df9b3c0e1bd3c21a1b3dd20a41790d4a950Yigit Boyar                    layoutDecorated(view, 0, top, getDecoratedMeasuredWidth(view)
503e71a1df9b3c0e1bd3c21a1b3dd20a41790d4a950Yigit Boyar                            , top + getDecoratedMeasuredHeight(view));
504e71a1df9b3c0e1bd3c21a1b3dd20a41790d4a950Yigit Boyar                }
505e71a1df9b3c0e1bd3c21a1b3dd20a41790d4a950Yigit Boyar
506504c54ea52c1b2aae6f8f4ae128f1dcaac7e3f6aYigit Boyar                top += view.getMeasuredHeight();
507d7d27e9ebe5c7325e67e1a8af265378bd2056cadChet Haase            }
508d7d27e9ebe5c7325e67e1a8af265378bd2056cadChet Haase        }
509fe3996cf5af65fe11cc3ed47b67123ec267bd336Yigit Boyar
510fe3996cf5af65fe11cc3ed47b67123ec267bd336Yigit Boyar        private void assertScrap(RecyclerView.Recycler recycler) {
511e0c347f627f8a78d3e5e3e5eaac9c3ae26208689Yigit Boyar            if (mRecyclerView.getAdapter() != null &&
512e0c347f627f8a78d3e5e3e5eaac9c3ae26208689Yigit Boyar                    !mRecyclerView.getAdapter().hasStableIds()) {
513e0c347f627f8a78d3e5e3e5eaac9c3ae26208689Yigit Boyar                for (RecyclerView.ViewHolder viewHolder : recycler.getScrapList()) {
514e0c347f627f8a78d3e5e3e5eaac9c3ae26208689Yigit Boyar                    assertFalse("Invalid scrap should be no kept", viewHolder.isInvalid());
515e0c347f627f8a78d3e5e3e5eaac9c3ae26208689Yigit Boyar                }
516fe3996cf5af65fe11cc3ed47b67123ec267bd336Yigit Boyar            }
517fe3996cf5af65fe11cc3ed47b67123ec267bd336Yigit Boyar        }
518bb6ebab3b3b85ec20b3bc262289a2460705322f5Yigit Boyar
519bb6ebab3b3b85ec20b3bc262289a2460705322f5Yigit Boyar        @Override
520bb6ebab3b3b85ec20b3bc262289a2460705322f5Yigit Boyar        public boolean canScrollHorizontally() {
521bb6ebab3b3b85ec20b3bc262289a2460705322f5Yigit Boyar            return true;
522bb6ebab3b3b85ec20b3bc262289a2460705322f5Yigit Boyar        }
523bb6ebab3b3b85ec20b3bc262289a2460705322f5Yigit Boyar
524bb6ebab3b3b85ec20b3bc262289a2460705322f5Yigit Boyar        @Override
525bb6ebab3b3b85ec20b3bc262289a2460705322f5Yigit Boyar        public boolean canScrollVertically() {
526bb6ebab3b3b85ec20b3bc262289a2460705322f5Yigit Boyar            return true;
527bb6ebab3b3b85ec20b3bc262289a2460705322f5Yigit Boyar        }
528bb6ebab3b3b85ec20b3bc262289a2460705322f5Yigit Boyar
529bb6ebab3b3b85ec20b3bc262289a2460705322f5Yigit Boyar        @Override
530bb6ebab3b3b85ec20b3bc262289a2460705322f5Yigit Boyar        public int scrollHorizontallyBy(int dx, RecyclerView.Recycler recycler,
531bb6ebab3b3b85ec20b3bc262289a2460705322f5Yigit Boyar                RecyclerView.State state) {
532f129f1b050d2542a91fe8175eac30183beb07b41Chris Banes            mScrollHorizontallyAmount += dx;
533bb6ebab3b3b85ec20b3bc262289a2460705322f5Yigit Boyar            return dx;
534bb6ebab3b3b85ec20b3bc262289a2460705322f5Yigit Boyar        }
535bb6ebab3b3b85ec20b3bc262289a2460705322f5Yigit Boyar
536bb6ebab3b3b85ec20b3bc262289a2460705322f5Yigit Boyar        @Override
537bb6ebab3b3b85ec20b3bc262289a2460705322f5Yigit Boyar        public int scrollVerticallyBy(int dy, RecyclerView.Recycler recycler,
538bb6ebab3b3b85ec20b3bc262289a2460705322f5Yigit Boyar                RecyclerView.State state) {
539f129f1b050d2542a91fe8175eac30183beb07b41Chris Banes            mScrollVerticallyAmount += dy;
540bb6ebab3b3b85ec20b3bc262289a2460705322f5Yigit Boyar            return dy;
541bb6ebab3b3b85ec20b3bc262289a2460705322f5Yigit Boyar        }
542d7d27e9ebe5c7325e67e1a8af265378bd2056cadChet Haase    }
543d7d27e9ebe5c7325e67e1a8af265378bd2056cadChet Haase
544d7d27e9ebe5c7325e67e1a8af265378bd2056cadChet Haase    static class Item {
54511b7cfea15306c0d6e4ed7da09f7698df01695dfYigit Boyar        final static AtomicInteger idCounter = new AtomicInteger(0);
54611b7cfea15306c0d6e4ed7da09f7698df01695dfYigit Boyar        final public int mId = idCounter.incrementAndGet();
547d7d27e9ebe5c7325e67e1a8af265378bd2056cadChet Haase
5482d2e8d88103866b631eb0f3805da137ebcfb0275Yigit Boyar        int mAdapterIndex;
549d7d27e9ebe5c7325e67e1a8af265378bd2056cadChet Haase
5502d2e8d88103866b631eb0f3805da137ebcfb0275Yigit Boyar        final String mText;
551d7d27e9ebe5c7325e67e1a8af265378bd2056cadChet Haase
5522d2e8d88103866b631eb0f3805da137ebcfb0275Yigit Boyar        Item(int adapterIndex, String text) {
5532d2e8d88103866b631eb0f3805da137ebcfb0275Yigit Boyar            mAdapterIndex = adapterIndex;
5542d2e8d88103866b631eb0f3805da137ebcfb0275Yigit Boyar            mText = text;
555d7d27e9ebe5c7325e67e1a8af265378bd2056cadChet Haase        }
5568ae76f91527ce850f155ce960fb9068bcd5d49f9Yigit Boyar
5578ae76f91527ce850f155ce960fb9068bcd5d49f9Yigit Boyar        @Override
5588ae76f91527ce850f155ce960fb9068bcd5d49f9Yigit Boyar        public String toString() {
5598ae76f91527ce850f155ce960fb9068bcd5d49f9Yigit Boyar            return "Item{" +
5608ae76f91527ce850f155ce960fb9068bcd5d49f9Yigit Boyar                    "mId=" + mId +
5612d2e8d88103866b631eb0f3805da137ebcfb0275Yigit Boyar                    ", originalIndex=" + mAdapterIndex +
5622d2e8d88103866b631eb0f3805da137ebcfb0275Yigit Boyar                    ", text='" + mText + '\'' +
5638ae76f91527ce850f155ce960fb9068bcd5d49f9Yigit Boyar                    '}';
5648ae76f91527ce850f155ce960fb9068bcd5d49f9Yigit Boyar        }
565d7d27e9ebe5c7325e67e1a8af265378bd2056cadChet Haase    }
566d7d27e9ebe5c7325e67e1a8af265378bd2056cadChet Haase
567e71a1df9b3c0e1bd3c21a1b3dd20a41790d4a950Yigit Boyar    public class TestAdapter extends RecyclerView.Adapter<TestViewHolder>
56890ca3086dbf66ad6bb8840e46ec8524a705e1c18Yigit Boyar            implements AttachDetachCountingAdapter {
569d7d27e9ebe5c7325e67e1a8af265378bd2056cadChet Haase
5703d8453880afb3e32c4c59c52b8b580f91d78b29fVladislav Kaznacheev        public static final String DEFAULT_ITEM_PREFIX = "Item ";
5713d8453880afb3e32c4c59c52b8b580f91d78b29fVladislav Kaznacheev
57290ca3086dbf66ad6bb8840e46ec8524a705e1c18Yigit Boyar        ViewAttachDetachCounter mAttachmentCounter = new ViewAttachDetachCounter();
573d7d27e9ebe5c7325e67e1a8af265378bd2056cadChet Haase        List<Item> mItems;
574d7d27e9ebe5c7325e67e1a8af265378bd2056cadChet Haase
575e71a1df9b3c0e1bd3c21a1b3dd20a41790d4a950Yigit Boyar        public TestAdapter(int count) {
576d7d27e9ebe5c7325e67e1a8af265378bd2056cadChet Haase            mItems = new ArrayList<Item>(count);
5773d8453880afb3e32c4c59c52b8b580f91d78b29fVladislav Kaznacheev            addItems(0, count, DEFAULT_ITEM_PREFIX);
5783d8453880afb3e32c4c59c52b8b580f91d78b29fVladislav Kaznacheev        }
5793d8453880afb3e32c4c59c52b8b580f91d78b29fVladislav Kaznacheev
5803d8453880afb3e32c4c59c52b8b580f91d78b29fVladislav Kaznacheev        private void addItems(int pos, int count, String prefix) {
5813d8453880afb3e32c4c59c52b8b580f91d78b29fVladislav Kaznacheev            for (int i = 0; i < count; i++, pos++) {
5823d8453880afb3e32c4c59c52b8b580f91d78b29fVladislav Kaznacheev                mItems.add(pos, new Item(pos, prefix));
583d7d27e9ebe5c7325e67e1a8af265378bd2056cadChet Haase            }
584d7d27e9ebe5c7325e67e1a8af265378bd2056cadChet Haase        }
585d7d27e9ebe5c7325e67e1a8af265378bd2056cadChet Haase
586d7d27e9ebe5c7325e67e1a8af265378bd2056cadChet Haase        @Override
58790ca3086dbf66ad6bb8840e46ec8524a705e1c18Yigit Boyar        public void onViewAttachedToWindow(TestViewHolder holder) {
58890ca3086dbf66ad6bb8840e46ec8524a705e1c18Yigit Boyar            super.onViewAttachedToWindow(holder);
58990ca3086dbf66ad6bb8840e46ec8524a705e1c18Yigit Boyar            mAttachmentCounter.onViewAttached(holder);
59090ca3086dbf66ad6bb8840e46ec8524a705e1c18Yigit Boyar        }
59190ca3086dbf66ad6bb8840e46ec8524a705e1c18Yigit Boyar
59290ca3086dbf66ad6bb8840e46ec8524a705e1c18Yigit Boyar        @Override
59390ca3086dbf66ad6bb8840e46ec8524a705e1c18Yigit Boyar        public void onViewDetachedFromWindow(TestViewHolder holder) {
59490ca3086dbf66ad6bb8840e46ec8524a705e1c18Yigit Boyar            super.onViewDetachedFromWindow(holder);
59590ca3086dbf66ad6bb8840e46ec8524a705e1c18Yigit Boyar            mAttachmentCounter.onViewDetached(holder);
59690ca3086dbf66ad6bb8840e46ec8524a705e1c18Yigit Boyar        }
59790ca3086dbf66ad6bb8840e46ec8524a705e1c18Yigit Boyar
59890ca3086dbf66ad6bb8840e46ec8524a705e1c18Yigit Boyar        @Override
59990ca3086dbf66ad6bb8840e46ec8524a705e1c18Yigit Boyar        public void onAttachedToRecyclerView(RecyclerView recyclerView) {
60090ca3086dbf66ad6bb8840e46ec8524a705e1c18Yigit Boyar            super.onAttachedToRecyclerView(recyclerView);
60190ca3086dbf66ad6bb8840e46ec8524a705e1c18Yigit Boyar            mAttachmentCounter.onAttached(recyclerView);
60290ca3086dbf66ad6bb8840e46ec8524a705e1c18Yigit Boyar        }
60390ca3086dbf66ad6bb8840e46ec8524a705e1c18Yigit Boyar
60490ca3086dbf66ad6bb8840e46ec8524a705e1c18Yigit Boyar        @Override
60590ca3086dbf66ad6bb8840e46ec8524a705e1c18Yigit Boyar        public void onDetachedFromRecyclerView(RecyclerView recyclerView) {
60690ca3086dbf66ad6bb8840e46ec8524a705e1c18Yigit Boyar            super.onDetachedFromRecyclerView(recyclerView);
60790ca3086dbf66ad6bb8840e46ec8524a705e1c18Yigit Boyar            mAttachmentCounter.onDetached(recyclerView);
60890ca3086dbf66ad6bb8840e46ec8524a705e1c18Yigit Boyar        }
60990ca3086dbf66ad6bb8840e46ec8524a705e1c18Yigit Boyar
61090ca3086dbf66ad6bb8840e46ec8524a705e1c18Yigit Boyar        @Override
611d7d27e9ebe5c7325e67e1a8af265378bd2056cadChet Haase        public TestViewHolder onCreateViewHolder(ViewGroup parent,
612d7d27e9ebe5c7325e67e1a8af265378bd2056cadChet Haase                int viewType) {
613d7d27e9ebe5c7325e67e1a8af265378bd2056cadChet Haase            return new TestViewHolder(new TextView(parent.getContext()));
614d7d27e9ebe5c7325e67e1a8af265378bd2056cadChet Haase        }
615d7d27e9ebe5c7325e67e1a8af265378bd2056cadChet Haase
616d7d27e9ebe5c7325e67e1a8af265378bd2056cadChet Haase        @Override
617d7d27e9ebe5c7325e67e1a8af265378bd2056cadChet Haase        public void onBindViewHolder(TestViewHolder holder, int position) {
618619ac016831a5ad75c2e0921c9036b6f030a9e63Yigit Boyar            assertNotNull(holder.mOwnerRecyclerView);
619619ac016831a5ad75c2e0921c9036b6f030a9e63Yigit Boyar            assertEquals(position, holder.getAdapterPosition());
620d7d27e9ebe5c7325e67e1a8af265378bd2056cadChet Haase            final Item item = mItems.get(position);
6212d2e8d88103866b631eb0f3805da137ebcfb0275Yigit Boyar            ((TextView) (holder.itemView)).setText(item.mText + "(" + item.mAdapterIndex + ")");
622115ba0c7b2a14aa4cd0273952195e1d8f6468f87Yigit Boyar            holder.mBoundItem = item;
623d7d27e9ebe5c7325e67e1a8af265378bd2056cadChet Haase        }
624d7d27e9ebe5c7325e67e1a8af265378bd2056cadChet Haase
625888093b0a071a99d65c01116cf703d46b21f9918Yigit Boyar        public Item getItemAt(int position) {
626888093b0a071a99d65c01116cf703d46b21f9918Yigit Boyar            return mItems.get(position);
627888093b0a071a99d65c01116cf703d46b21f9918Yigit Boyar        }
628888093b0a071a99d65c01116cf703d46b21f9918Yigit Boyar
629619ac016831a5ad75c2e0921c9036b6f030a9e63Yigit Boyar        @Override
630619ac016831a5ad75c2e0921c9036b6f030a9e63Yigit Boyar        public void onViewRecycled(TestViewHolder holder) {
631619ac016831a5ad75c2e0921c9036b6f030a9e63Yigit Boyar            super.onViewRecycled(holder);
632619ac016831a5ad75c2e0921c9036b6f030a9e63Yigit Boyar            final int adapterPosition = holder.getAdapterPosition();
633619ac016831a5ad75c2e0921c9036b6f030a9e63Yigit Boyar            final boolean shouldHavePosition = !holder.isRemoved() && holder.isBound() &&
634619ac016831a5ad75c2e0921c9036b6f030a9e63Yigit Boyar                    !holder.isAdapterPositionUnknown() && !holder.isInvalid();
635619ac016831a5ad75c2e0921c9036b6f030a9e63Yigit Boyar            String log = "Position check for " + holder.toString();
636619ac016831a5ad75c2e0921c9036b6f030a9e63Yigit Boyar            assertEquals(log, shouldHavePosition, adapterPosition != RecyclerView.NO_POSITION);
637619ac016831a5ad75c2e0921c9036b6f030a9e63Yigit Boyar            if (shouldHavePosition) {
638619ac016831a5ad75c2e0921c9036b6f030a9e63Yigit Boyar                assertTrue(log, mItems.size() > adapterPosition);
639619ac016831a5ad75c2e0921c9036b6f030a9e63Yigit Boyar                assertSame(log, holder.mBoundItem, mItems.get(adapterPosition));
640619ac016831a5ad75c2e0921c9036b6f030a9e63Yigit Boyar            }
641619ac016831a5ad75c2e0921c9036b6f030a9e63Yigit Boyar        }
642619ac016831a5ad75c2e0921c9036b6f030a9e63Yigit Boyar
643b5f8b4fecb531aee6b359b4968409410aba90817Yigit Boyar        public void deleteAndNotify(final int start, final int count) throws Throwable {
644b5f8b4fecb531aee6b359b4968409410aba90817Yigit Boyar            deleteAndNotify(new int[]{start, count});
645b5f8b4fecb531aee6b359b4968409410aba90817Yigit Boyar        }
646b5f8b4fecb531aee6b359b4968409410aba90817Yigit Boyar
647b5f8b4fecb531aee6b359b4968409410aba90817Yigit Boyar        /**
648b5f8b4fecb531aee6b359b4968409410aba90817Yigit Boyar         * Deletes items in the given ranges.
649b5f8b4fecb531aee6b359b4968409410aba90817Yigit Boyar         * <p>
650b5f8b4fecb531aee6b359b4968409410aba90817Yigit Boyar         * Note that each operation affects the one after so you should offset them properly.
651b5f8b4fecb531aee6b359b4968409410aba90817Yigit Boyar         * <p>
652b5f8b4fecb531aee6b359b4968409410aba90817Yigit Boyar         * For example, if adapter has 5 items (A,B,C,D,E), and then you call this method with
653b5f8b4fecb531aee6b359b4968409410aba90817Yigit Boyar         * <code>[1, 2],[2, 1]</code>, it will first delete items B,C and the new adapter will be
654b5f8b4fecb531aee6b359b4968409410aba90817Yigit Boyar         * A D E. Then it will delete 2,1 which means it will delete E.
655b5f8b4fecb531aee6b359b4968409410aba90817Yigit Boyar         */
656b5f8b4fecb531aee6b359b4968409410aba90817Yigit Boyar        public void deleteAndNotify(final int[]... startCountTuples) throws Throwable {
6578ae76f91527ce850f155ce960fb9068bcd5d49f9Yigit Boyar            for (int[] tuple : startCountTuples) {
6588ae76f91527ce850f155ce960fb9068bcd5d49f9Yigit Boyar                tuple[1] = -tuple[1];
6598ae76f91527ce850f155ce960fb9068bcd5d49f9Yigit Boyar            }
6608ae76f91527ce850f155ce960fb9068bcd5d49f9Yigit Boyar            new AddRemoveRunnable(startCountTuples).runOnMainThread();
661d7d27e9ebe5c7325e67e1a8af265378bd2056cadChet Haase        }
662d7d27e9ebe5c7325e67e1a8af265378bd2056cadChet Haase
663c8b0ff5869e17a656a3a1d7fb306efa5b99fa07fYigit Boyar        @Override
664c8b0ff5869e17a656a3a1d7fb306efa5b99fa07fYigit Boyar        public long getItemId(int position) {
665c8b0ff5869e17a656a3a1d7fb306efa5b99fa07fYigit Boyar            return hasStableIds() ? mItems.get(position).mId : super.getItemId(position);
666c8b0ff5869e17a656a3a1d7fb306efa5b99fa07fYigit Boyar        }
667c8b0ff5869e17a656a3a1d7fb306efa5b99fa07fYigit Boyar
6682d2e8d88103866b631eb0f3805da137ebcfb0275Yigit Boyar        public void offsetOriginalIndices(int start, int offset) {
6692d2e8d88103866b631eb0f3805da137ebcfb0275Yigit Boyar            for (int i = start; i < mItems.size(); i++) {
6702d2e8d88103866b631eb0f3805da137ebcfb0275Yigit Boyar                mItems.get(i).mAdapterIndex += offset;
6712d2e8d88103866b631eb0f3805da137ebcfb0275Yigit Boyar            }
6722d2e8d88103866b631eb0f3805da137ebcfb0275Yigit Boyar        }
6732d2e8d88103866b631eb0f3805da137ebcfb0275Yigit Boyar
674e0c347f627f8a78d3e5e3e5eaac9c3ae26208689Yigit Boyar        /**
675e0c347f627f8a78d3e5e3e5eaac9c3ae26208689Yigit Boyar         * @param start inclusive
676e0c347f627f8a78d3e5e3e5eaac9c3ae26208689Yigit Boyar         * @param end exclusive
677e0c347f627f8a78d3e5e3e5eaac9c3ae26208689Yigit Boyar         * @param offset
678e0c347f627f8a78d3e5e3e5eaac9c3ae26208689Yigit Boyar         */
679e0c347f627f8a78d3e5e3e5eaac9c3ae26208689Yigit Boyar        public void offsetOriginalIndicesBetween(int start, int end, int offset) {
680e0c347f627f8a78d3e5e3e5eaac9c3ae26208689Yigit Boyar            for (int i = start; i < end && i < mItems.size(); i++) {
681e0c347f627f8a78d3e5e3e5eaac9c3ae26208689Yigit Boyar                mItems.get(i).mAdapterIndex += offset;
682e0c347f627f8a78d3e5e3e5eaac9c3ae26208689Yigit Boyar            }
683e0c347f627f8a78d3e5e3e5eaac9c3ae26208689Yigit Boyar        }
684e0c347f627f8a78d3e5e3e5eaac9c3ae26208689Yigit Boyar
6853d8453880afb3e32c4c59c52b8b580f91d78b29fVladislav Kaznacheev        public void addAndNotify(final int count) throws Throwable {
6863d8453880afb3e32c4c59c52b8b580f91d78b29fVladislav Kaznacheev            assertEquals(0, mItems.size());
6873d8453880afb3e32c4c59c52b8b580f91d78b29fVladislav Kaznacheev            new AddRemoveRunnable(DEFAULT_ITEM_PREFIX, new int[]{0, count}).runOnMainThread();
6883d8453880afb3e32c4c59c52b8b580f91d78b29fVladislav Kaznacheev        }
6893d8453880afb3e32c4c59c52b8b580f91d78b29fVladislav Kaznacheev
690b5f8b4fecb531aee6b359b4968409410aba90817Yigit Boyar        public void addAndNotify(final int start, final int count) throws Throwable {
691b5f8b4fecb531aee6b359b4968409410aba90817Yigit Boyar            addAndNotify(new int[]{start, count});
692b5f8b4fecb531aee6b359b4968409410aba90817Yigit Boyar        }
693b5f8b4fecb531aee6b359b4968409410aba90817Yigit Boyar
694b5f8b4fecb531aee6b359b4968409410aba90817Yigit Boyar        public void addAndNotify(final int[]... startCountTuples) throws Throwable {
6958ae76f91527ce850f155ce960fb9068bcd5d49f9Yigit Boyar            new AddRemoveRunnable(startCountTuples).runOnMainThread();
6968ae76f91527ce850f155ce960fb9068bcd5d49f9Yigit Boyar        }
697b5f8b4fecb531aee6b359b4968409410aba90817Yigit Boyar
698cadd54707a7aaa4cfa9c8b8051c6185e8717f22eChet Haase        public void dispatchDataSetChanged() throws Throwable {
69922b6b8163d2e15eb6d0a89906854dec801945f1dYigit Boyar            runTestOnUiThread(new Runnable() {
70022b6b8163d2e15eb6d0a89906854dec801945f1dYigit Boyar                @Override
70122b6b8163d2e15eb6d0a89906854dec801945f1dYigit Boyar                public void run() {
70222b6b8163d2e15eb6d0a89906854dec801945f1dYigit Boyar                    notifyDataSetChanged();
70322b6b8163d2e15eb6d0a89906854dec801945f1dYigit Boyar                }
70422b6b8163d2e15eb6d0a89906854dec801945f1dYigit Boyar            });
70522b6b8163d2e15eb6d0a89906854dec801945f1dYigit Boyar        }
70622b6b8163d2e15eb6d0a89906854dec801945f1dYigit Boyar
707cadd54707a7aaa4cfa9c8b8051c6185e8717f22eChet Haase        public void changeAndNotify(final int start, final int count) throws Throwable {
7087e9da1a2629b657d56e2f54abd18c6249f3be037Yigit Boyar            runTestOnUiThread(new Runnable() {
7097e9da1a2629b657d56e2f54abd18c6249f3be037Yigit Boyar                @Override
7107e9da1a2629b657d56e2f54abd18c6249f3be037Yigit Boyar                public void run() {
7117e9da1a2629b657d56e2f54abd18c6249f3be037Yigit Boyar                    notifyItemRangeChanged(start, count);
7127e9da1a2629b657d56e2f54abd18c6249f3be037Yigit Boyar                }
7137e9da1a2629b657d56e2f54abd18c6249f3be037Yigit Boyar            });
7147e9da1a2629b657d56e2f54abd18c6249f3be037Yigit Boyar        }
7157e9da1a2629b657d56e2f54abd18c6249f3be037Yigit Boyar
716cadd54707a7aaa4cfa9c8b8051c6185e8717f22eChet Haase        public void changePositionsAndNotify(final int... positions) throws Throwable {
717cadd54707a7aaa4cfa9c8b8051c6185e8717f22eChet Haase            runTestOnUiThread(new Runnable() {
718cadd54707a7aaa4cfa9c8b8051c6185e8717f22eChet Haase                @Override
719cadd54707a7aaa4cfa9c8b8051c6185e8717f22eChet Haase                public void run() {
720cadd54707a7aaa4cfa9c8b8051c6185e8717f22eChet Haase                    for (int i = 0; i < positions.length; i += 1) {
721cadd54707a7aaa4cfa9c8b8051c6185e8717f22eChet Haase                        TestAdapter.super.notifyItemRangeChanged(positions[i], 1);
722cadd54707a7aaa4cfa9c8b8051c6185e8717f22eChet Haase                    }
723cadd54707a7aaa4cfa9c8b8051c6185e8717f22eChet Haase                }
724cadd54707a7aaa4cfa9c8b8051c6185e8717f22eChet Haase            });
725cadd54707a7aaa4cfa9c8b8051c6185e8717f22eChet Haase        }
726cadd54707a7aaa4cfa9c8b8051c6185e8717f22eChet Haase
7278ae76f91527ce850f155ce960fb9068bcd5d49f9Yigit Boyar        /**
7288ae76f91527ce850f155ce960fb9068bcd5d49f9Yigit Boyar         * Similar to other methods but negative count means delete and position count means add.
7298ae76f91527ce850f155ce960fb9068bcd5d49f9Yigit Boyar         * <p>
7308ae76f91527ce850f155ce960fb9068bcd5d49f9Yigit Boyar         * For instance, calling this method with <code>[1,1], [2,-1]</code> it will first add an
7318ae76f91527ce850f155ce960fb9068bcd5d49f9Yigit Boyar         * item to index 1, then remove an item from index 2 (updated index 2)
7328ae76f91527ce850f155ce960fb9068bcd5d49f9Yigit Boyar         */
7338ae76f91527ce850f155ce960fb9068bcd5d49f9Yigit Boyar        public void addDeleteAndNotify(final int[]... startCountTuples) throws Throwable {
7348ae76f91527ce850f155ce960fb9068bcd5d49f9Yigit Boyar            new AddRemoveRunnable(startCountTuples).runOnMainThread();
735d7d27e9ebe5c7325e67e1a8af265378bd2056cadChet Haase        }
736d7d27e9ebe5c7325e67e1a8af265378bd2056cadChet Haase
737d7d27e9ebe5c7325e67e1a8af265378bd2056cadChet Haase        @Override
738d7d27e9ebe5c7325e67e1a8af265378bd2056cadChet Haase        public int getItemCount() {
739d7d27e9ebe5c7325e67e1a8af265378bd2056cadChet Haase            return mItems.size();
740d7d27e9ebe5c7325e67e1a8af265378bd2056cadChet Haase        }
7418ae76f91527ce850f155ce960fb9068bcd5d49f9Yigit Boyar
742115ba0c7b2a14aa4cd0273952195e1d8f6468f87Yigit Boyar        /**
743115ba0c7b2a14aa4cd0273952195e1d8f6468f87Yigit Boyar         * Uses notifyDataSetChanged
744115ba0c7b2a14aa4cd0273952195e1d8f6468f87Yigit Boyar         */
745e0c347f627f8a78d3e5e3e5eaac9c3ae26208689Yigit Boyar        public void moveItems(boolean notifyChange, int[]... fromToTuples) throws Throwable {
746e0c347f627f8a78d3e5e3e5eaac9c3ae26208689Yigit Boyar            for (int i = 0; i < fromToTuples.length; i += 1) {
747e0c347f627f8a78d3e5e3e5eaac9c3ae26208689Yigit Boyar                int[] tuple = fromToTuples[i];
748e0c347f627f8a78d3e5e3e5eaac9c3ae26208689Yigit Boyar                moveItem(tuple[0], tuple[1], false);
749e0c347f627f8a78d3e5e3e5eaac9c3ae26208689Yigit Boyar            }
750e0c347f627f8a78d3e5e3e5eaac9c3ae26208689Yigit Boyar            if (notifyChange) {
751cadd54707a7aaa4cfa9c8b8051c6185e8717f22eChet Haase                dispatchDataSetChanged();
752e0c347f627f8a78d3e5e3e5eaac9c3ae26208689Yigit Boyar            }
753e0c347f627f8a78d3e5e3e5eaac9c3ae26208689Yigit Boyar        }
754e0c347f627f8a78d3e5e3e5eaac9c3ae26208689Yigit Boyar
755115ba0c7b2a14aa4cd0273952195e1d8f6468f87Yigit Boyar        /**
756115ba0c7b2a14aa4cd0273952195e1d8f6468f87Yigit Boyar         * Uses notifyDataSetChanged
757115ba0c7b2a14aa4cd0273952195e1d8f6468f87Yigit Boyar         */
758e0c347f627f8a78d3e5e3e5eaac9c3ae26208689Yigit Boyar        public void moveItem(final int from, final int to, final boolean notifyChange)
759e0c347f627f8a78d3e5e3e5eaac9c3ae26208689Yigit Boyar                throws Throwable {
760e0c347f627f8a78d3e5e3e5eaac9c3ae26208689Yigit Boyar            runTestOnUiThread(new Runnable() {
761e0c347f627f8a78d3e5e3e5eaac9c3ae26208689Yigit Boyar                @Override
762e0c347f627f8a78d3e5e3e5eaac9c3ae26208689Yigit Boyar                public void run() {
763ec60a348ce39889a99f7ca02da94a6867bfdf66fYigit Boyar                    moveInUIThread(from, to);
764e0c347f627f8a78d3e5e3e5eaac9c3ae26208689Yigit Boyar                    if (notifyChange) {
765e0c347f627f8a78d3e5e3e5eaac9c3ae26208689Yigit Boyar                        notifyDataSetChanged();
766e0c347f627f8a78d3e5e3e5eaac9c3ae26208689Yigit Boyar                    }
767e0c347f627f8a78d3e5e3e5eaac9c3ae26208689Yigit Boyar                }
768e0c347f627f8a78d3e5e3e5eaac9c3ae26208689Yigit Boyar            });
769e0c347f627f8a78d3e5e3e5eaac9c3ae26208689Yigit Boyar        }
770e0c347f627f8a78d3e5e3e5eaac9c3ae26208689Yigit Boyar
771115ba0c7b2a14aa4cd0273952195e1d8f6468f87Yigit Boyar        /**
772115ba0c7b2a14aa4cd0273952195e1d8f6468f87Yigit Boyar         * Uses notifyItemMoved
773115ba0c7b2a14aa4cd0273952195e1d8f6468f87Yigit Boyar         */
774115ba0c7b2a14aa4cd0273952195e1d8f6468f87Yigit Boyar        public void moveAndNotify(final int from, final int to) throws Throwable {
775115ba0c7b2a14aa4cd0273952195e1d8f6468f87Yigit Boyar            runTestOnUiThread(new Runnable() {
776115ba0c7b2a14aa4cd0273952195e1d8f6468f87Yigit Boyar                @Override
777115ba0c7b2a14aa4cd0273952195e1d8f6468f87Yigit Boyar                public void run() {
778ec60a348ce39889a99f7ca02da94a6867bfdf66fYigit Boyar                    moveInUIThread(from, to);
779115ba0c7b2a14aa4cd0273952195e1d8f6468f87Yigit Boyar                    notifyItemMoved(from, to);
780115ba0c7b2a14aa4cd0273952195e1d8f6468f87Yigit Boyar                }
781115ba0c7b2a14aa4cd0273952195e1d8f6468f87Yigit Boyar            });
782115ba0c7b2a14aa4cd0273952195e1d8f6468f87Yigit Boyar        }
783115ba0c7b2a14aa4cd0273952195e1d8f6468f87Yigit Boyar
7843d8453880afb3e32c4c59c52b8b580f91d78b29fVladislav Kaznacheev        public void clearOnUIThread() {
7853d8453880afb3e32c4c59c52b8b580f91d78b29fVladislav Kaznacheev            assertEquals("clearOnUIThread called from a wrong thread",
7863d8453880afb3e32c4c59c52b8b580f91d78b29fVladislav Kaznacheev                    Looper.getMainLooper(), Looper.myLooper());
7873d8453880afb3e32c4c59c52b8b580f91d78b29fVladislav Kaznacheev            mItems = new ArrayList<Item>();
7883d8453880afb3e32c4c59c52b8b580f91d78b29fVladislav Kaznacheev            notifyDataSetChanged();
7893d8453880afb3e32c4c59c52b8b580f91d78b29fVladislav Kaznacheev        }
7903d8453880afb3e32c4c59c52b8b580f91d78b29fVladislav Kaznacheev
791ec60a348ce39889a99f7ca02da94a6867bfdf66fYigit Boyar        protected void moveInUIThread(int from, int to) {
792ec60a348ce39889a99f7ca02da94a6867bfdf66fYigit Boyar            Item item = mItems.remove(from);
793ec60a348ce39889a99f7ca02da94a6867bfdf66fYigit Boyar            offsetOriginalIndices(from, -1);
794ec60a348ce39889a99f7ca02da94a6867bfdf66fYigit Boyar            mItems.add(to, item);
795ec60a348ce39889a99f7ca02da94a6867bfdf66fYigit Boyar            offsetOriginalIndices(to + 1, 1);
796ec60a348ce39889a99f7ca02da94a6867bfdf66fYigit Boyar            item.mAdapterIndex = to;
797ec60a348ce39889a99f7ca02da94a6867bfdf66fYigit Boyar        }
798115ba0c7b2a14aa4cd0273952195e1d8f6468f87Yigit Boyar
799115ba0c7b2a14aa4cd0273952195e1d8f6468f87Yigit Boyar
80090ca3086dbf66ad6bb8840e46ec8524a705e1c18Yigit Boyar        @Override
80190ca3086dbf66ad6bb8840e46ec8524a705e1c18Yigit Boyar        public ViewAttachDetachCounter getCounter() {
80290ca3086dbf66ad6bb8840e46ec8524a705e1c18Yigit Boyar            return mAttachmentCounter;
80390ca3086dbf66ad6bb8840e46ec8524a705e1c18Yigit Boyar        }
80490ca3086dbf66ad6bb8840e46ec8524a705e1c18Yigit Boyar
8058ae76f91527ce850f155ce960fb9068bcd5d49f9Yigit Boyar
8068ae76f91527ce850f155ce960fb9068bcd5d49f9Yigit Boyar        private class AddRemoveRunnable implements Runnable {
8073d8453880afb3e32c4c59c52b8b580f91d78b29fVladislav Kaznacheev            final String mNewItemPrefix;
8088ae76f91527ce850f155ce960fb9068bcd5d49f9Yigit Boyar            final int[][] mStartCountTuples;
8098ae76f91527ce850f155ce960fb9068bcd5d49f9Yigit Boyar
8103d8453880afb3e32c4c59c52b8b580f91d78b29fVladislav Kaznacheev            public AddRemoveRunnable(String newItemPrefix, int[]... startCountTuples) {
8113d8453880afb3e32c4c59c52b8b580f91d78b29fVladislav Kaznacheev                mNewItemPrefix = newItemPrefix;
8128ae76f91527ce850f155ce960fb9068bcd5d49f9Yigit Boyar                mStartCountTuples = startCountTuples;
8138ae76f91527ce850f155ce960fb9068bcd5d49f9Yigit Boyar            }
8148ae76f91527ce850f155ce960fb9068bcd5d49f9Yigit Boyar
8153d8453880afb3e32c4c59c52b8b580f91d78b29fVladislav Kaznacheev            public AddRemoveRunnable(int[][] startCountTuples) {
8163d8453880afb3e32c4c59c52b8b580f91d78b29fVladislav Kaznacheev                this("new item ", startCountTuples);
8173d8453880afb3e32c4c59c52b8b580f91d78b29fVladislav Kaznacheev            }
8183d8453880afb3e32c4c59c52b8b580f91d78b29fVladislav Kaznacheev
8198ae76f91527ce850f155ce960fb9068bcd5d49f9Yigit Boyar            public void runOnMainThread() throws Throwable {
8208ae76f91527ce850f155ce960fb9068bcd5d49f9Yigit Boyar                if (Looper.myLooper() == Looper.getMainLooper()) {
8218ae76f91527ce850f155ce960fb9068bcd5d49f9Yigit Boyar                    run();
8228ae76f91527ce850f155ce960fb9068bcd5d49f9Yigit Boyar                } else {
8238ae76f91527ce850f155ce960fb9068bcd5d49f9Yigit Boyar                    runTestOnUiThread(this);
8248ae76f91527ce850f155ce960fb9068bcd5d49f9Yigit Boyar                }
8258ae76f91527ce850f155ce960fb9068bcd5d49f9Yigit Boyar            }
8268ae76f91527ce850f155ce960fb9068bcd5d49f9Yigit Boyar
8278ae76f91527ce850f155ce960fb9068bcd5d49f9Yigit Boyar            @Override
8288ae76f91527ce850f155ce960fb9068bcd5d49f9Yigit Boyar            public void run() {
8298ae76f91527ce850f155ce960fb9068bcd5d49f9Yigit Boyar                for (int[] tuple : mStartCountTuples) {
8308ae76f91527ce850f155ce960fb9068bcd5d49f9Yigit Boyar                    if (tuple[1] < 0) {
8318ae76f91527ce850f155ce960fb9068bcd5d49f9Yigit Boyar                        delete(tuple);
8328ae76f91527ce850f155ce960fb9068bcd5d49f9Yigit Boyar                    } else {
8338ae76f91527ce850f155ce960fb9068bcd5d49f9Yigit Boyar                        add(tuple);
8348ae76f91527ce850f155ce960fb9068bcd5d49f9Yigit Boyar                    }
8358ae76f91527ce850f155ce960fb9068bcd5d49f9Yigit Boyar                }
8368ae76f91527ce850f155ce960fb9068bcd5d49f9Yigit Boyar            }
8378ae76f91527ce850f155ce960fb9068bcd5d49f9Yigit Boyar
8388ae76f91527ce850f155ce960fb9068bcd5d49f9Yigit Boyar            private void add(int[] tuple) {
8392d2e8d88103866b631eb0f3805da137ebcfb0275Yigit Boyar                // offset others
8402d2e8d88103866b631eb0f3805da137ebcfb0275Yigit Boyar                offsetOriginalIndices(tuple[0], tuple[1]);
8413d8453880afb3e32c4c59c52b8b580f91d78b29fVladislav Kaznacheev                addItems(tuple[0], tuple[1], mNewItemPrefix);
8428ae76f91527ce850f155ce960fb9068bcd5d49f9Yigit Boyar                notifyItemRangeInserted(tuple[0], tuple[1]);
8438ae76f91527ce850f155ce960fb9068bcd5d49f9Yigit Boyar            }
8448ae76f91527ce850f155ce960fb9068bcd5d49f9Yigit Boyar
8458ae76f91527ce850f155ce960fb9068bcd5d49f9Yigit Boyar            private void delete(int[] tuple) {
8462d2e8d88103866b631eb0f3805da137ebcfb0275Yigit Boyar                final int count = -tuple[1];
8472d2e8d88103866b631eb0f3805da137ebcfb0275Yigit Boyar                offsetOriginalIndices(tuple[0] + count, tuple[1]);
8482d2e8d88103866b631eb0f3805da137ebcfb0275Yigit Boyar                for (int i = 0; i < count; i++) {
8498ae76f91527ce850f155ce960fb9068bcd5d49f9Yigit Boyar                    mItems.remove(tuple[0]);
8508ae76f91527ce850f155ce960fb9068bcd5d49f9Yigit Boyar                }
8512d2e8d88103866b631eb0f3805da137ebcfb0275Yigit Boyar                notifyItemRangeRemoved(tuple[0], count);
8528ae76f91527ce850f155ce960fb9068bcd5d49f9Yigit Boyar            }
8538ae76f91527ce850f155ce960fb9068bcd5d49f9Yigit Boyar        }
8548ae76f91527ce850f155ce960fb9068bcd5d49f9Yigit Boyar    }
8558ae76f91527ce850f155ce960fb9068bcd5d49f9Yigit Boyar
85690ca3086dbf66ad6bb8840e46ec8524a705e1c18Yigit Boyar    public boolean isMainThread() {
85790ca3086dbf66ad6bb8840e46ec8524a705e1c18Yigit Boyar        return Looper.myLooper() == Looper.getMainLooper();
85890ca3086dbf66ad6bb8840e46ec8524a705e1c18Yigit Boyar    }
85990ca3086dbf66ad6bb8840e46ec8524a705e1c18Yigit Boyar
8608ae76f91527ce850f155ce960fb9068bcd5d49f9Yigit Boyar    @Override
8618ae76f91527ce850f155ce960fb9068bcd5d49f9Yigit Boyar    public void runTestOnUiThread(Runnable r) throws Throwable {
8628ae76f91527ce850f155ce960fb9068bcd5d49f9Yigit Boyar        if (Looper.myLooper() == Looper.getMainLooper()) {
8638ae76f91527ce850f155ce960fb9068bcd5d49f9Yigit Boyar            r.run();
8648ae76f91527ce850f155ce960fb9068bcd5d49f9Yigit Boyar        } else {
8658ae76f91527ce850f155ce960fb9068bcd5d49f9Yigit Boyar            super.runTestOnUiThread(r);
8668ae76f91527ce850f155ce960fb9068bcd5d49f9Yigit Boyar        }
867d7d27e9ebe5c7325e67e1a8af265378bd2056cadChet Haase    }
868c50c4cad31d73e574b27bb3d7581542975e37263Yigit Boyar
869c50c4cad31d73e574b27bb3d7581542975e37263Yigit Boyar    static class TargetTuple {
870c50c4cad31d73e574b27bb3d7581542975e37263Yigit Boyar
871c50c4cad31d73e574b27bb3d7581542975e37263Yigit Boyar        final int mPosition;
872c50c4cad31d73e574b27bb3d7581542975e37263Yigit Boyar
873c50c4cad31d73e574b27bb3d7581542975e37263Yigit Boyar        final int mLayoutDirection;
874c50c4cad31d73e574b27bb3d7581542975e37263Yigit Boyar
875c50c4cad31d73e574b27bb3d7581542975e37263Yigit Boyar        TargetTuple(int position, int layoutDirection) {
876c50c4cad31d73e574b27bb3d7581542975e37263Yigit Boyar            this.mPosition = position;
877c50c4cad31d73e574b27bb3d7581542975e37263Yigit Boyar            this.mLayoutDirection = layoutDirection;
878c50c4cad31d73e574b27bb3d7581542975e37263Yigit Boyar        }
879c50c4cad31d73e574b27bb3d7581542975e37263Yigit Boyar
880c50c4cad31d73e574b27bb3d7581542975e37263Yigit Boyar        @Override
881c50c4cad31d73e574b27bb3d7581542975e37263Yigit Boyar        public String toString() {
882c50c4cad31d73e574b27bb3d7581542975e37263Yigit Boyar            return "TargetTuple{" +
883c50c4cad31d73e574b27bb3d7581542975e37263Yigit Boyar                    "mPosition=" + mPosition +
884c50c4cad31d73e574b27bb3d7581542975e37263Yigit Boyar                    ", mLayoutDirection=" + mLayoutDirection +
885c50c4cad31d73e574b27bb3d7581542975e37263Yigit Boyar                    '}';
886c50c4cad31d73e574b27bb3d7581542975e37263Yigit Boyar        }
887c50c4cad31d73e574b27bb3d7581542975e37263Yigit Boyar    }
88890ca3086dbf66ad6bb8840e46ec8524a705e1c18Yigit Boyar
88990ca3086dbf66ad6bb8840e46ec8524a705e1c18Yigit Boyar    public interface AttachDetachCountingAdapter {
89090ca3086dbf66ad6bb8840e46ec8524a705e1c18Yigit Boyar
89190ca3086dbf66ad6bb8840e46ec8524a705e1c18Yigit Boyar        ViewAttachDetachCounter getCounter();
89290ca3086dbf66ad6bb8840e46ec8524a705e1c18Yigit Boyar    }
89390ca3086dbf66ad6bb8840e46ec8524a705e1c18Yigit Boyar
89490ca3086dbf66ad6bb8840e46ec8524a705e1c18Yigit Boyar    public class ViewAttachDetachCounter {
89590ca3086dbf66ad6bb8840e46ec8524a705e1c18Yigit Boyar
89690ca3086dbf66ad6bb8840e46ec8524a705e1c18Yigit Boyar        Set<RecyclerView.ViewHolder> mAttachedSet = new HashSet<RecyclerView.ViewHolder>();
89790ca3086dbf66ad6bb8840e46ec8524a705e1c18Yigit Boyar
89890ca3086dbf66ad6bb8840e46ec8524a705e1c18Yigit Boyar        public void validateRemaining(RecyclerView recyclerView) {
89990ca3086dbf66ad6bb8840e46ec8524a705e1c18Yigit Boyar            final int childCount = recyclerView.getChildCount();
90090ca3086dbf66ad6bb8840e46ec8524a705e1c18Yigit Boyar            for (int i = 0; i < childCount; i++) {
90190ca3086dbf66ad6bb8840e46ec8524a705e1c18Yigit Boyar                View view = recyclerView.getChildAt(i);
90290ca3086dbf66ad6bb8840e46ec8524a705e1c18Yigit Boyar                RecyclerView.ViewHolder vh = recyclerView.getChildViewHolder(view);
90390ca3086dbf66ad6bb8840e46ec8524a705e1c18Yigit Boyar                assertTrue("remaining view should be in attached set " + vh,
90490ca3086dbf66ad6bb8840e46ec8524a705e1c18Yigit Boyar                        mAttachedSet.contains(vh));
90590ca3086dbf66ad6bb8840e46ec8524a705e1c18Yigit Boyar            }
90690ca3086dbf66ad6bb8840e46ec8524a705e1c18Yigit Boyar            assertEquals("there should not be any views left in attached set",
90790ca3086dbf66ad6bb8840e46ec8524a705e1c18Yigit Boyar                    childCount, mAttachedSet.size());
90890ca3086dbf66ad6bb8840e46ec8524a705e1c18Yigit Boyar        }
90990ca3086dbf66ad6bb8840e46ec8524a705e1c18Yigit Boyar
91090ca3086dbf66ad6bb8840e46ec8524a705e1c18Yigit Boyar        public void onViewDetached(RecyclerView.ViewHolder viewHolder) {
91190ca3086dbf66ad6bb8840e46ec8524a705e1c18Yigit Boyar            try {
91290ca3086dbf66ad6bb8840e46ec8524a705e1c18Yigit Boyar                assertTrue("view holder should be in attached set",
91390ca3086dbf66ad6bb8840e46ec8524a705e1c18Yigit Boyar                        mAttachedSet.remove(viewHolder));
91490ca3086dbf66ad6bb8840e46ec8524a705e1c18Yigit Boyar            } catch (Throwable t) {
91590ca3086dbf66ad6bb8840e46ec8524a705e1c18Yigit Boyar                postExceptionToInstrumentation(t);
91690ca3086dbf66ad6bb8840e46ec8524a705e1c18Yigit Boyar            }
91790ca3086dbf66ad6bb8840e46ec8524a705e1c18Yigit Boyar        }
91890ca3086dbf66ad6bb8840e46ec8524a705e1c18Yigit Boyar
91990ca3086dbf66ad6bb8840e46ec8524a705e1c18Yigit Boyar        public void onViewAttached(RecyclerView.ViewHolder viewHolder) {
92090ca3086dbf66ad6bb8840e46ec8524a705e1c18Yigit Boyar            try {
92190ca3086dbf66ad6bb8840e46ec8524a705e1c18Yigit Boyar                assertTrue("view holder should not be in attached set",
92290ca3086dbf66ad6bb8840e46ec8524a705e1c18Yigit Boyar                        mAttachedSet.add(viewHolder));
92390ca3086dbf66ad6bb8840e46ec8524a705e1c18Yigit Boyar            } catch (Throwable t) {
92490ca3086dbf66ad6bb8840e46ec8524a705e1c18Yigit Boyar                postExceptionToInstrumentation(t);
92590ca3086dbf66ad6bb8840e46ec8524a705e1c18Yigit Boyar            }
92690ca3086dbf66ad6bb8840e46ec8524a705e1c18Yigit Boyar        }
92790ca3086dbf66ad6bb8840e46ec8524a705e1c18Yigit Boyar
92890ca3086dbf66ad6bb8840e46ec8524a705e1c18Yigit Boyar        public void onAttached(RecyclerView recyclerView) {
92990ca3086dbf66ad6bb8840e46ec8524a705e1c18Yigit Boyar            // when a new RV is attached, clear the set and add all view holders
93090ca3086dbf66ad6bb8840e46ec8524a705e1c18Yigit Boyar            mAttachedSet.clear();
93190ca3086dbf66ad6bb8840e46ec8524a705e1c18Yigit Boyar            final int childCount = recyclerView.getChildCount();
93290ca3086dbf66ad6bb8840e46ec8524a705e1c18Yigit Boyar            for (int i = 0; i < childCount; i ++) {
93390ca3086dbf66ad6bb8840e46ec8524a705e1c18Yigit Boyar                View view = recyclerView.getChildAt(i);
93490ca3086dbf66ad6bb8840e46ec8524a705e1c18Yigit Boyar                mAttachedSet.add(recyclerView.getChildViewHolder(view));
93590ca3086dbf66ad6bb8840e46ec8524a705e1c18Yigit Boyar            }
93690ca3086dbf66ad6bb8840e46ec8524a705e1c18Yigit Boyar        }
93790ca3086dbf66ad6bb8840e46ec8524a705e1c18Yigit Boyar
93890ca3086dbf66ad6bb8840e46ec8524a705e1c18Yigit Boyar        public void onDetached(RecyclerView recyclerView) {
93990ca3086dbf66ad6bb8840e46ec8524a705e1c18Yigit Boyar            validateRemaining(recyclerView);
94090ca3086dbf66ad6bb8840e46ec8524a705e1c18Yigit Boyar        }
94190ca3086dbf66ad6bb8840e46ec8524a705e1c18Yigit Boyar    }
9427a59e4b36500bcfddd842376d6c3abe0d9f23d84Yigit Boyar}
943