BaseRecyclerViewInstrumentationTest.java revision 7e9da1a2629b657d56e2f54abd18c6249f3be037
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
198ae76f91527ce850f155ce960fb9068bcd5d49f9Yigit Boyarimport android.os.Looper;
20d7d27e9ebe5c7325e67e1a8af265378bd2056cadChet Haaseimport android.test.ActivityInstrumentationTestCase2;
21d7d27e9ebe5c7325e67e1a8af265378bd2056cadChet Haaseimport android.util.Log;
22d7d27e9ebe5c7325e67e1a8af265378bd2056cadChet Haaseimport android.view.View;
23d7d27e9ebe5c7325e67e1a8af265378bd2056cadChet Haaseimport android.view.ViewGroup;
24d7d27e9ebe5c7325e67e1a8af265378bd2056cadChet Haaseimport android.widget.TextView;
25d7d27e9ebe5c7325e67e1a8af265378bd2056cadChet Haase
26d7d27e9ebe5c7325e67e1a8af265378bd2056cadChet Haaseimport java.util.ArrayList;
27d7d27e9ebe5c7325e67e1a8af265378bd2056cadChet Haaseimport java.util.List;
28d7d27e9ebe5c7325e67e1a8af265378bd2056cadChet Haaseimport java.util.concurrent.CountDownLatch;
29d7d27e9ebe5c7325e67e1a8af265378bd2056cadChet Haaseimport java.util.concurrent.TimeUnit;
3011b7cfea15306c0d6e4ed7da09f7698df01695dfYigit Boyarimport java.util.concurrent.atomic.AtomicInteger;
31d7d27e9ebe5c7325e67e1a8af265378bd2056cadChet Haase
32d7d27e9ebe5c7325e67e1a8af265378bd2056cadChet Haaseabstract public class BaseRecyclerViewInstrumentationTest extends
33d7d27e9ebe5c7325e67e1a8af265378bd2056cadChet Haase        ActivityInstrumentationTestCase2<TestActivity> {
34d7d27e9ebe5c7325e67e1a8af265378bd2056cadChet Haase
35d7d27e9ebe5c7325e67e1a8af265378bd2056cadChet Haase    private static final String TAG = "RecyclerViewTest";
36d7d27e9ebe5c7325e67e1a8af265378bd2056cadChet Haase
37b5f8b4fecb531aee6b359b4968409410aba90817Yigit Boyar    private boolean mDebug;
38b5f8b4fecb531aee6b359b4968409410aba90817Yigit Boyar
39b5f8b4fecb531aee6b359b4968409410aba90817Yigit Boyar    protected RecyclerView mRecyclerView;
40d7d27e9ebe5c7325e67e1a8af265378bd2056cadChet Haase
418ae76f91527ce850f155ce960fb9068bcd5d49f9Yigit Boyar    protected AdapterHelper mAdapterHelper;
428ae76f91527ce850f155ce960fb9068bcd5d49f9Yigit Boyar
437e9da1a2629b657d56e2f54abd18c6249f3be037Yigit Boyar    Throwable mainThreadException;
447e9da1a2629b657d56e2f54abd18c6249f3be037Yigit Boyar
45d7d27e9ebe5c7325e67e1a8af265378bd2056cadChet Haase    public BaseRecyclerViewInstrumentationTest() {
46b5f8b4fecb531aee6b359b4968409410aba90817Yigit Boyar        this(false);
47b5f8b4fecb531aee6b359b4968409410aba90817Yigit Boyar    }
48b5f8b4fecb531aee6b359b4968409410aba90817Yigit Boyar
49b5f8b4fecb531aee6b359b4968409410aba90817Yigit Boyar    public BaseRecyclerViewInstrumentationTest(boolean debug) {
508ae76f91527ce850f155ce960fb9068bcd5d49f9Yigit Boyar        super("android.support.v7.recyclerview", TestActivity.class);
51b5f8b4fecb531aee6b359b4968409410aba90817Yigit Boyar        mDebug = debug;
52b5f8b4fecb531aee6b359b4968409410aba90817Yigit Boyar    }
53b5f8b4fecb531aee6b359b4968409410aba90817Yigit Boyar
547e9da1a2629b657d56e2f54abd18c6249f3be037Yigit Boyar    void checkForMainThreadException() throws Throwable {
557e9da1a2629b657d56e2f54abd18c6249f3be037Yigit Boyar        if (mainThreadException != null) {
567e9da1a2629b657d56e2f54abd18c6249f3be037Yigit Boyar            throw mainThreadException;
577e9da1a2629b657d56e2f54abd18c6249f3be037Yigit Boyar        }
587e9da1a2629b657d56e2f54abd18c6249f3be037Yigit Boyar    }
597e9da1a2629b657d56e2f54abd18c6249f3be037Yigit Boyar
607e9da1a2629b657d56e2f54abd18c6249f3be037Yigit Boyar    void postExceptionToInstrumentation(Throwable t) {
617e9da1a2629b657d56e2f54abd18c6249f3be037Yigit Boyar        if (mDebug) {
627e9da1a2629b657d56e2f54abd18c6249f3be037Yigit Boyar            Log.e(TAG, "captured exception on main thread", t);
637e9da1a2629b657d56e2f54abd18c6249f3be037Yigit Boyar        }
647e9da1a2629b657d56e2f54abd18c6249f3be037Yigit Boyar        mainThreadException = t;
657e9da1a2629b657d56e2f54abd18c6249f3be037Yigit Boyar        if (mRecyclerView != null && mRecyclerView
667e9da1a2629b657d56e2f54abd18c6249f3be037Yigit Boyar                .getLayoutManager() instanceof TestLayoutManager) {
677e9da1a2629b657d56e2f54abd18c6249f3be037Yigit Boyar            TestLayoutManager lm = (TestLayoutManager) mRecyclerView.getLayoutManager();
687e9da1a2629b657d56e2f54abd18c6249f3be037Yigit Boyar            // finish all layouts so that we get the correct exception
697e9da1a2629b657d56e2f54abd18c6249f3be037Yigit Boyar            while (lm.layoutLatch.getCount() > 0) {
707e9da1a2629b657d56e2f54abd18c6249f3be037Yigit Boyar                lm.layoutLatch.countDown();
717e9da1a2629b657d56e2f54abd18c6249f3be037Yigit Boyar            }
727e9da1a2629b657d56e2f54abd18c6249f3be037Yigit Boyar        }
737e9da1a2629b657d56e2f54abd18c6249f3be037Yigit Boyar    }
747e9da1a2629b657d56e2f54abd18c6249f3be037Yigit Boyar
758ae76f91527ce850f155ce960fb9068bcd5d49f9Yigit Boyar    @Override
768ae76f91527ce850f155ce960fb9068bcd5d49f9Yigit Boyar    protected void tearDown() throws Exception {
778ae76f91527ce850f155ce960fb9068bcd5d49f9Yigit Boyar        if (mRecyclerView != null) {
788ae76f91527ce850f155ce960fb9068bcd5d49f9Yigit Boyar            try {
798ae76f91527ce850f155ce960fb9068bcd5d49f9Yigit Boyar                removeRecyclerView();
808ae76f91527ce850f155ce960fb9068bcd5d49f9Yigit Boyar            } catch (Throwable throwable) {
818ae76f91527ce850f155ce960fb9068bcd5d49f9Yigit Boyar                throwable.printStackTrace();
828ae76f91527ce850f155ce960fb9068bcd5d49f9Yigit Boyar            }
838ae76f91527ce850f155ce960fb9068bcd5d49f9Yigit Boyar        }
848ae76f91527ce850f155ce960fb9068bcd5d49f9Yigit Boyar        getInstrumentation().waitForIdleSync();
858ae76f91527ce850f155ce960fb9068bcd5d49f9Yigit Boyar        super.tearDown();
868ae76f91527ce850f155ce960fb9068bcd5d49f9Yigit Boyar    }
878ae76f91527ce850f155ce960fb9068bcd5d49f9Yigit Boyar
88b5f8b4fecb531aee6b359b4968409410aba90817Yigit Boyar    public void removeRecyclerView() throws Throwable {
89b5f8b4fecb531aee6b359b4968409410aba90817Yigit Boyar        mRecyclerView = null;
90b5f8b4fecb531aee6b359b4968409410aba90817Yigit Boyar        runTestOnUiThread(new Runnable() {
91b5f8b4fecb531aee6b359b4968409410aba90817Yigit Boyar            @Override
92b5f8b4fecb531aee6b359b4968409410aba90817Yigit Boyar            public void run() {
93b5f8b4fecb531aee6b359b4968409410aba90817Yigit Boyar                getActivity().mContainer.removeAllViews();
94b5f8b4fecb531aee6b359b4968409410aba90817Yigit Boyar            }
95b5f8b4fecb531aee6b359b4968409410aba90817Yigit Boyar        });
96b5f8b4fecb531aee6b359b4968409410aba90817Yigit Boyar    }
97b5f8b4fecb531aee6b359b4968409410aba90817Yigit Boyar
98b5f8b4fecb531aee6b359b4968409410aba90817Yigit Boyar    public void setRecyclerView(final RecyclerView recyclerView) throws Throwable {
99b5f8b4fecb531aee6b359b4968409410aba90817Yigit Boyar        mRecyclerView = recyclerView;
1008ae76f91527ce850f155ce960fb9068bcd5d49f9Yigit Boyar        mAdapterHelper = recyclerView.mAdapterHelper;
101b5f8b4fecb531aee6b359b4968409410aba90817Yigit Boyar        runTestOnUiThread(new Runnable() {
102b5f8b4fecb531aee6b359b4968409410aba90817Yigit Boyar            @Override
103b5f8b4fecb531aee6b359b4968409410aba90817Yigit Boyar            public void run() {
104b5f8b4fecb531aee6b359b4968409410aba90817Yigit Boyar                getActivity().mContainer.addView(recyclerView);
105b5f8b4fecb531aee6b359b4968409410aba90817Yigit Boyar            }
106b5f8b4fecb531aee6b359b4968409410aba90817Yigit Boyar        });
107b5f8b4fecb531aee6b359b4968409410aba90817Yigit Boyar    }
108b5f8b4fecb531aee6b359b4968409410aba90817Yigit Boyar
109b5f8b4fecb531aee6b359b4968409410aba90817Yigit Boyar    public void requestLayoutOnUIThread(final View view) throws Throwable {
110b5f8b4fecb531aee6b359b4968409410aba90817Yigit Boyar        runTestOnUiThread(new Runnable() {
111b5f8b4fecb531aee6b359b4968409410aba90817Yigit Boyar            @Override
112b5f8b4fecb531aee6b359b4968409410aba90817Yigit Boyar            public void run() {
113b5f8b4fecb531aee6b359b4968409410aba90817Yigit Boyar                view.requestLayout();
114b5f8b4fecb531aee6b359b4968409410aba90817Yigit Boyar            }
115b5f8b4fecb531aee6b359b4968409410aba90817Yigit Boyar        });
116b5f8b4fecb531aee6b359b4968409410aba90817Yigit Boyar    }
117b5f8b4fecb531aee6b359b4968409410aba90817Yigit Boyar
118b5f8b4fecb531aee6b359b4968409410aba90817Yigit Boyar    public void scrollBy(final int dt) throws Throwable {
119b5f8b4fecb531aee6b359b4968409410aba90817Yigit Boyar        runTestOnUiThread(new Runnable() {
120b5f8b4fecb531aee6b359b4968409410aba90817Yigit Boyar            @Override
121b5f8b4fecb531aee6b359b4968409410aba90817Yigit Boyar            public void run() {
122b5f8b4fecb531aee6b359b4968409410aba90817Yigit Boyar                if (mRecyclerView.getLayoutManager().canScrollHorizontally()) {
123b5f8b4fecb531aee6b359b4968409410aba90817Yigit Boyar                    mRecyclerView.scrollBy(dt, 0);
124b5f8b4fecb531aee6b359b4968409410aba90817Yigit Boyar                } else {
125b5f8b4fecb531aee6b359b4968409410aba90817Yigit Boyar                    mRecyclerView.scrollBy(0, dt);
126b5f8b4fecb531aee6b359b4968409410aba90817Yigit Boyar                }
127b5f8b4fecb531aee6b359b4968409410aba90817Yigit Boyar
128b5f8b4fecb531aee6b359b4968409410aba90817Yigit Boyar            }
129b5f8b4fecb531aee6b359b4968409410aba90817Yigit Boyar        });
130d7d27e9ebe5c7325e67e1a8af265378bd2056cadChet Haase    }
131d7d27e9ebe5c7325e67e1a8af265378bd2056cadChet Haase
1328edcb0bdeaba6931f9d8154f0c81f57da7ddab2aYigit Boyar    void scrollToPosition(final int position) throws Throwable {
1338edcb0bdeaba6931f9d8154f0c81f57da7ddab2aYigit Boyar        runTestOnUiThread(new Runnable() {
1348edcb0bdeaba6931f9d8154f0c81f57da7ddab2aYigit Boyar            @Override
1358edcb0bdeaba6931f9d8154f0c81f57da7ddab2aYigit Boyar            public void run() {
1368edcb0bdeaba6931f9d8154f0c81f57da7ddab2aYigit Boyar                mRecyclerView.getLayoutManager().scrollToPosition(position);
1378edcb0bdeaba6931f9d8154f0c81f57da7ddab2aYigit Boyar            }
1388edcb0bdeaba6931f9d8154f0c81f57da7ddab2aYigit Boyar        });
1398edcb0bdeaba6931f9d8154f0c81f57da7ddab2aYigit Boyar    }
1408edcb0bdeaba6931f9d8154f0c81f57da7ddab2aYigit Boyar
1418edcb0bdeaba6931f9d8154f0c81f57da7ddab2aYigit Boyar    void smoothScrollToPosition(final int position)
1428edcb0bdeaba6931f9d8154f0c81f57da7ddab2aYigit Boyar            throws Throwable {
1438edcb0bdeaba6931f9d8154f0c81f57da7ddab2aYigit Boyar        Log.d(TAG, "SMOOTH scrolling to " + position);
1448edcb0bdeaba6931f9d8154f0c81f57da7ddab2aYigit Boyar        runTestOnUiThread(new Runnable() {
1458edcb0bdeaba6931f9d8154f0c81f57da7ddab2aYigit Boyar            @Override
1468edcb0bdeaba6931f9d8154f0c81f57da7ddab2aYigit Boyar            public void run() {
1478edcb0bdeaba6931f9d8154f0c81f57da7ddab2aYigit Boyar                mRecyclerView.smoothScrollToPosition(position);
1488edcb0bdeaba6931f9d8154f0c81f57da7ddab2aYigit Boyar            }
1498edcb0bdeaba6931f9d8154f0c81f57da7ddab2aYigit Boyar        });
1508edcb0bdeaba6931f9d8154f0c81f57da7ddab2aYigit Boyar        while (mRecyclerView.getLayoutManager().isSmoothScrolling() ||
1518edcb0bdeaba6931f9d8154f0c81f57da7ddab2aYigit Boyar                mRecyclerView.getScrollState() != RecyclerView.SCROLL_STATE_IDLE) {
1528edcb0bdeaba6931f9d8154f0c81f57da7ddab2aYigit Boyar            if (mDebug) {
1538edcb0bdeaba6931f9d8154f0c81f57da7ddab2aYigit Boyar                Log.d(TAG, "SMOOTH scrolling step");
1548edcb0bdeaba6931f9d8154f0c81f57da7ddab2aYigit Boyar            }
1558edcb0bdeaba6931f9d8154f0c81f57da7ddab2aYigit Boyar            Thread.sleep(200);
1568edcb0bdeaba6931f9d8154f0c81f57da7ddab2aYigit Boyar        }
1578edcb0bdeaba6931f9d8154f0c81f57da7ddab2aYigit Boyar        Log.d(TAG, "SMOOTH scrolling done");
1588edcb0bdeaba6931f9d8154f0c81f57da7ddab2aYigit Boyar    }
1598edcb0bdeaba6931f9d8154f0c81f57da7ddab2aYigit Boyar
160d7d27e9ebe5c7325e67e1a8af265378bd2056cadChet Haase    class TestViewHolder extends RecyclerView.ViewHolder {
161d7d27e9ebe5c7325e67e1a8af265378bd2056cadChet Haase
162d7d27e9ebe5c7325e67e1a8af265378bd2056cadChet Haase        Item mBindedItem;
163d7d27e9ebe5c7325e67e1a8af265378bd2056cadChet Haase
164d7d27e9ebe5c7325e67e1a8af265378bd2056cadChet Haase        public TestViewHolder(View itemView) {
165d7d27e9ebe5c7325e67e1a8af265378bd2056cadChet Haase            super(itemView);
166d7d27e9ebe5c7325e67e1a8af265378bd2056cadChet Haase        }
167d7d27e9ebe5c7325e67e1a8af265378bd2056cadChet Haase    }
168d7d27e9ebe5c7325e67e1a8af265378bd2056cadChet Haase
169d7d27e9ebe5c7325e67e1a8af265378bd2056cadChet Haase    class TestLayoutManager extends RecyclerView.LayoutManager {
170d7d27e9ebe5c7325e67e1a8af265378bd2056cadChet Haase
171d7d27e9ebe5c7325e67e1a8af265378bd2056cadChet Haase        CountDownLatch layoutLatch;
172d7d27e9ebe5c7325e67e1a8af265378bd2056cadChet Haase
173d7d27e9ebe5c7325e67e1a8af265378bd2056cadChet Haase        public void expectLayouts(int count) {
174d7d27e9ebe5c7325e67e1a8af265378bd2056cadChet Haase            layoutLatch = new CountDownLatch(count);
175d7d27e9ebe5c7325e67e1a8af265378bd2056cadChet Haase        }
176d7d27e9ebe5c7325e67e1a8af265378bd2056cadChet Haase
177b5f8b4fecb531aee6b359b4968409410aba90817Yigit Boyar        public void waitForLayout(long timeout, TimeUnit timeUnit) throws Throwable {
178b5f8b4fecb531aee6b359b4968409410aba90817Yigit Boyar            layoutLatch.await(timeout * (mDebug ? 100 : 1), timeUnit);
179d7d27e9ebe5c7325e67e1a8af265378bd2056cadChet Haase            assertEquals("all expected layouts should be executed at the expected time",
180d7d27e9ebe5c7325e67e1a8af265378bd2056cadChet Haase                    0, layoutLatch.getCount());
181d7d27e9ebe5c7325e67e1a8af265378bd2056cadChet Haase        }
182d7d27e9ebe5c7325e67e1a8af265378bd2056cadChet Haase
183b5f8b4fecb531aee6b359b4968409410aba90817Yigit Boyar        public void assertLayoutCount(int count, String msg, long timeout) throws Throwable {
184b5f8b4fecb531aee6b359b4968409410aba90817Yigit Boyar            layoutLatch.await(timeout, TimeUnit.SECONDS);
185b5f8b4fecb531aee6b359b4968409410aba90817Yigit Boyar            assertEquals(msg, count, layoutLatch.getCount());
186b5f8b4fecb531aee6b359b4968409410aba90817Yigit Boyar        }
187b5f8b4fecb531aee6b359b4968409410aba90817Yigit Boyar
188b5f8b4fecb531aee6b359b4968409410aba90817Yigit Boyar        public void assertNoLayout(String msg, long timeout) throws Throwable {
189b5f8b4fecb531aee6b359b4968409410aba90817Yigit Boyar            layoutLatch.await(timeout, TimeUnit.SECONDS);
190b5f8b4fecb531aee6b359b4968409410aba90817Yigit Boyar            assertFalse(msg, layoutLatch.getCount() == 0);
191b5f8b4fecb531aee6b359b4968409410aba90817Yigit Boyar        }
192b5f8b4fecb531aee6b359b4968409410aba90817Yigit Boyar
193b5f8b4fecb531aee6b359b4968409410aba90817Yigit Boyar        public void waitForLayout(long timeout) throws Throwable {
1948ae76f91527ce850f155ce960fb9068bcd5d49f9Yigit Boyar            waitForLayout(timeout * (mDebug ? 10000 : 1), TimeUnit.SECONDS);
195b5f8b4fecb531aee6b359b4968409410aba90817Yigit Boyar        }
196b5f8b4fecb531aee6b359b4968409410aba90817Yigit Boyar
197d7d27e9ebe5c7325e67e1a8af265378bd2056cadChet Haase        @Override
198d7d27e9ebe5c7325e67e1a8af265378bd2056cadChet Haase        public RecyclerView.LayoutParams generateDefaultLayoutParams() {
199d7d27e9ebe5c7325e67e1a8af265378bd2056cadChet Haase            return new RecyclerView.LayoutParams(ViewGroup.LayoutParams.WRAP_CONTENT,
200d7d27e9ebe5c7325e67e1a8af265378bd2056cadChet Haase                    ViewGroup.LayoutParams.WRAP_CONTENT);
201d7d27e9ebe5c7325e67e1a8af265378bd2056cadChet Haase        }
202d7d27e9ebe5c7325e67e1a8af265378bd2056cadChet Haase
203d7d27e9ebe5c7325e67e1a8af265378bd2056cadChet Haase        void assertVisibleItemPositions() {
204d7d27e9ebe5c7325e67e1a8af265378bd2056cadChet Haase            int i = getChildCount();
205d7d27e9ebe5c7325e67e1a8af265378bd2056cadChet Haase            TestAdapter testAdapter = (TestAdapter) mRecyclerView.getAdapter();
206d7d27e9ebe5c7325e67e1a8af265378bd2056cadChet Haase            while (i-- > 0) {
207d7d27e9ebe5c7325e67e1a8af265378bd2056cadChet Haase                View view = getChildAt(i);
208d7d27e9ebe5c7325e67e1a8af265378bd2056cadChet Haase                RecyclerView.LayoutParams lp = (RecyclerView.LayoutParams) view.getLayoutParams();
209d7d27e9ebe5c7325e67e1a8af265378bd2056cadChet Haase                Item item = ((TestViewHolder) lp.mViewHolder).mBindedItem;
210b5f8b4fecb531aee6b359b4968409410aba90817Yigit Boyar                if (mDebug) {
211d7d27e9ebe5c7325e67e1a8af265378bd2056cadChet Haase                    Log.d(TAG, "testing item " + i);
212d7d27e9ebe5c7325e67e1a8af265378bd2056cadChet Haase                }
2138ae76f91527ce850f155ce960fb9068bcd5d49f9Yigit Boyar                if (!lp.isItemRemoved()) {
2148ae76f91527ce850f155ce960fb9068bcd5d49f9Yigit Boyar                    RecyclerView.ViewHolder vh = mRecyclerView.getChildViewHolder(view);
2158ae76f91527ce850f155ce960fb9068bcd5d49f9Yigit Boyar                    assertSame("item position in LP should match adapter value :" + vh,
2168ae76f91527ce850f155ce960fb9068bcd5d49f9Yigit Boyar                            testAdapter.mItems.get(vh.mPosition), item);
2178ae76f91527ce850f155ce960fb9068bcd5d49f9Yigit Boyar                }
218d7d27e9ebe5c7325e67e1a8af265378bd2056cadChet Haase            }
219d7d27e9ebe5c7325e67e1a8af265378bd2056cadChet Haase        }
220d7d27e9ebe5c7325e67e1a8af265378bd2056cadChet Haase
221b5f8b4fecb531aee6b359b4968409410aba90817Yigit Boyar        RecyclerView.LayoutParams getLp(View v) {
222b5f8b4fecb531aee6b359b4968409410aba90817Yigit Boyar            return (RecyclerView.LayoutParams) v.getLayoutParams();
223b5f8b4fecb531aee6b359b4968409410aba90817Yigit Boyar        }
224b5f8b4fecb531aee6b359b4968409410aba90817Yigit Boyar
2258ae76f91527ce850f155ce960fb9068bcd5d49f9Yigit Boyar        /**
2268ae76f91527ce850f155ce960fb9068bcd5d49f9Yigit Boyar         * returns skipped (removed) view count.
2278ae76f91527ce850f155ce960fb9068bcd5d49f9Yigit Boyar         */
2288ae76f91527ce850f155ce960fb9068bcd5d49f9Yigit Boyar        int layoutRange(RecyclerView.Recycler recycler, int start,
229d7d27e9ebe5c7325e67e1a8af265378bd2056cadChet Haase                int end) {
2308ae76f91527ce850f155ce960fb9068bcd5d49f9Yigit Boyar            int skippedAdd = 0;
231b5f8b4fecb531aee6b359b4968409410aba90817Yigit Boyar            if (mDebug) {
232d7d27e9ebe5c7325e67e1a8af265378bd2056cadChet Haase                Log.d(TAG, "will layout items from " + start + " to " + end);
233d7d27e9ebe5c7325e67e1a8af265378bd2056cadChet Haase            }
234d7d27e9ebe5c7325e67e1a8af265378bd2056cadChet Haase            for (int i = start; i < end; i++) {
235b5f8b4fecb531aee6b359b4968409410aba90817Yigit Boyar                if (mDebug) {
236d7d27e9ebe5c7325e67e1a8af265378bd2056cadChet Haase                    Log.d(TAG, "laying out item " + i);
237d7d27e9ebe5c7325e67e1a8af265378bd2056cadChet Haase                }
238d7d27e9ebe5c7325e67e1a8af265378bd2056cadChet Haase                View view = recycler.getViewForPosition(i);
239b5f8b4fecb531aee6b359b4968409410aba90817Yigit Boyar                assertNotNull("view should not be null for valid position. "
240b5f8b4fecb531aee6b359b4968409410aba90817Yigit Boyar                        + "got null view at position " + i, view);
241b5f8b4fecb531aee6b359b4968409410aba90817Yigit Boyar                if (!getLp(view).isItemRemoved()) {
242b5f8b4fecb531aee6b359b4968409410aba90817Yigit Boyar                    addView(view);
2438ae76f91527ce850f155ce960fb9068bcd5d49f9Yigit Boyar                } else {
2448ae76f91527ce850f155ce960fb9068bcd5d49f9Yigit Boyar                    skippedAdd ++;
245b5f8b4fecb531aee6b359b4968409410aba90817Yigit Boyar                }
246b5f8b4fecb531aee6b359b4968409410aba90817Yigit Boyar
247d7d27e9ebe5c7325e67e1a8af265378bd2056cadChet Haase                measureChildWithMargins(view, 0, 0);
248d7d27e9ebe5c7325e67e1a8af265378bd2056cadChet Haase                layoutDecorated(view, 0, (i - start) * 10, getDecoratedMeasuredWidth(view)
249d7d27e9ebe5c7325e67e1a8af265378bd2056cadChet Haase                        , getDecoratedMeasuredHeight(view));
250d7d27e9ebe5c7325e67e1a8af265378bd2056cadChet Haase            }
2518ae76f91527ce850f155ce960fb9068bcd5d49f9Yigit Boyar            return skippedAdd;
252d7d27e9ebe5c7325e67e1a8af265378bd2056cadChet Haase        }
253d7d27e9ebe5c7325e67e1a8af265378bd2056cadChet Haase    }
254d7d27e9ebe5c7325e67e1a8af265378bd2056cadChet Haase
255d7d27e9ebe5c7325e67e1a8af265378bd2056cadChet Haase    static class Item {
25611b7cfea15306c0d6e4ed7da09f7698df01695dfYigit Boyar        final static AtomicInteger idCounter = new AtomicInteger(0);
25711b7cfea15306c0d6e4ed7da09f7698df01695dfYigit Boyar        final public int mId = idCounter.incrementAndGet();
258d7d27e9ebe5c7325e67e1a8af265378bd2056cadChet Haase
259d7d27e9ebe5c7325e67e1a8af265378bd2056cadChet Haase        int originalIndex;
260d7d27e9ebe5c7325e67e1a8af265378bd2056cadChet Haase
261d7d27e9ebe5c7325e67e1a8af265378bd2056cadChet Haase        final String text;
262d7d27e9ebe5c7325e67e1a8af265378bd2056cadChet Haase
263d7d27e9ebe5c7325e67e1a8af265378bd2056cadChet Haase        Item(int originalIndex, String text) {
264d7d27e9ebe5c7325e67e1a8af265378bd2056cadChet Haase            this.originalIndex = originalIndex;
265d7d27e9ebe5c7325e67e1a8af265378bd2056cadChet Haase            this.text = text;
266d7d27e9ebe5c7325e67e1a8af265378bd2056cadChet Haase        }
2678ae76f91527ce850f155ce960fb9068bcd5d49f9Yigit Boyar
2688ae76f91527ce850f155ce960fb9068bcd5d49f9Yigit Boyar        @Override
2698ae76f91527ce850f155ce960fb9068bcd5d49f9Yigit Boyar        public String toString() {
2708ae76f91527ce850f155ce960fb9068bcd5d49f9Yigit Boyar            return "Item{" +
2718ae76f91527ce850f155ce960fb9068bcd5d49f9Yigit Boyar                    "mId=" + mId +
2728ae76f91527ce850f155ce960fb9068bcd5d49f9Yigit Boyar                    ", originalIndex=" + originalIndex +
2738ae76f91527ce850f155ce960fb9068bcd5d49f9Yigit Boyar                    ", text='" + text + '\'' +
2748ae76f91527ce850f155ce960fb9068bcd5d49f9Yigit Boyar                    '}';
2758ae76f91527ce850f155ce960fb9068bcd5d49f9Yigit Boyar        }
276d7d27e9ebe5c7325e67e1a8af265378bd2056cadChet Haase    }
277d7d27e9ebe5c7325e67e1a8af265378bd2056cadChet Haase
278d7d27e9ebe5c7325e67e1a8af265378bd2056cadChet Haase    class TestAdapter extends RecyclerView.Adapter<TestViewHolder> {
279d7d27e9ebe5c7325e67e1a8af265378bd2056cadChet Haase
280d7d27e9ebe5c7325e67e1a8af265378bd2056cadChet Haase        List<Item> mItems;
281d7d27e9ebe5c7325e67e1a8af265378bd2056cadChet Haase
282d7d27e9ebe5c7325e67e1a8af265378bd2056cadChet Haase        TestAdapter(int count) {
283d7d27e9ebe5c7325e67e1a8af265378bd2056cadChet Haase            mItems = new ArrayList<Item>(count);
284d7d27e9ebe5c7325e67e1a8af265378bd2056cadChet Haase            for (int i = 0; i < count; i++) {
285d7d27e9ebe5c7325e67e1a8af265378bd2056cadChet Haase                mItems.add(new Item(i, "Item " + i));
286d7d27e9ebe5c7325e67e1a8af265378bd2056cadChet Haase            }
287d7d27e9ebe5c7325e67e1a8af265378bd2056cadChet Haase        }
288d7d27e9ebe5c7325e67e1a8af265378bd2056cadChet Haase
289d7d27e9ebe5c7325e67e1a8af265378bd2056cadChet Haase        @Override
290d7d27e9ebe5c7325e67e1a8af265378bd2056cadChet Haase        public TestViewHolder onCreateViewHolder(ViewGroup parent,
291d7d27e9ebe5c7325e67e1a8af265378bd2056cadChet Haase                int viewType) {
292d7d27e9ebe5c7325e67e1a8af265378bd2056cadChet Haase            return new TestViewHolder(new TextView(parent.getContext()));
293d7d27e9ebe5c7325e67e1a8af265378bd2056cadChet Haase        }
294d7d27e9ebe5c7325e67e1a8af265378bd2056cadChet Haase
295d7d27e9ebe5c7325e67e1a8af265378bd2056cadChet Haase        @Override
296d7d27e9ebe5c7325e67e1a8af265378bd2056cadChet Haase        public void onBindViewHolder(TestViewHolder holder, int position) {
297d7d27e9ebe5c7325e67e1a8af265378bd2056cadChet Haase            final Item item = mItems.get(position);
298d7d27e9ebe5c7325e67e1a8af265378bd2056cadChet Haase            ((TextView) (holder.itemView)).setText(item.text);
299d7d27e9ebe5c7325e67e1a8af265378bd2056cadChet Haase            holder.mBindedItem = item;
300d7d27e9ebe5c7325e67e1a8af265378bd2056cadChet Haase        }
301d7d27e9ebe5c7325e67e1a8af265378bd2056cadChet Haase
302b5f8b4fecb531aee6b359b4968409410aba90817Yigit Boyar        public void deleteAndNotify(final int start, final int count) throws Throwable {
303b5f8b4fecb531aee6b359b4968409410aba90817Yigit Boyar            deleteAndNotify(new int[]{start, count});
304b5f8b4fecb531aee6b359b4968409410aba90817Yigit Boyar        }
305b5f8b4fecb531aee6b359b4968409410aba90817Yigit Boyar
306b5f8b4fecb531aee6b359b4968409410aba90817Yigit Boyar        /**
307b5f8b4fecb531aee6b359b4968409410aba90817Yigit Boyar         * Deletes items in the given ranges.
308b5f8b4fecb531aee6b359b4968409410aba90817Yigit Boyar         * <p>
309b5f8b4fecb531aee6b359b4968409410aba90817Yigit Boyar         * Note that each operation affects the one after so you should offset them properly.
310b5f8b4fecb531aee6b359b4968409410aba90817Yigit Boyar         * <p>
311b5f8b4fecb531aee6b359b4968409410aba90817Yigit Boyar         * For example, if adapter has 5 items (A,B,C,D,E), and then you call this method with
312b5f8b4fecb531aee6b359b4968409410aba90817Yigit Boyar         * <code>[1, 2],[2, 1]</code>, it will first delete items B,C and the new adapter will be
313b5f8b4fecb531aee6b359b4968409410aba90817Yigit Boyar         * A D E. Then it will delete 2,1 which means it will delete E.
314b5f8b4fecb531aee6b359b4968409410aba90817Yigit Boyar         */
315b5f8b4fecb531aee6b359b4968409410aba90817Yigit Boyar        public void deleteAndNotify(final int[]... startCountTuples) throws Throwable {
3168ae76f91527ce850f155ce960fb9068bcd5d49f9Yigit Boyar            for (int[] tuple : startCountTuples) {
3178ae76f91527ce850f155ce960fb9068bcd5d49f9Yigit Boyar                tuple[1] = -tuple[1];
3188ae76f91527ce850f155ce960fb9068bcd5d49f9Yigit Boyar            }
3198ae76f91527ce850f155ce960fb9068bcd5d49f9Yigit Boyar            new AddRemoveRunnable(startCountTuples).runOnMainThread();
320d7d27e9ebe5c7325e67e1a8af265378bd2056cadChet Haase        }
321d7d27e9ebe5c7325e67e1a8af265378bd2056cadChet Haase
322b5f8b4fecb531aee6b359b4968409410aba90817Yigit Boyar        public void addAndNotify(final int start, final int count) throws Throwable {
323b5f8b4fecb531aee6b359b4968409410aba90817Yigit Boyar            addAndNotify(new int[]{start, count});
324b5f8b4fecb531aee6b359b4968409410aba90817Yigit Boyar        }
325b5f8b4fecb531aee6b359b4968409410aba90817Yigit Boyar
326b5f8b4fecb531aee6b359b4968409410aba90817Yigit Boyar        public void addAndNotify(final int[]... startCountTuples) throws Throwable {
3278ae76f91527ce850f155ce960fb9068bcd5d49f9Yigit Boyar            new AddRemoveRunnable(startCountTuples).runOnMainThread();
3288ae76f91527ce850f155ce960fb9068bcd5d49f9Yigit Boyar        }
329b5f8b4fecb531aee6b359b4968409410aba90817Yigit Boyar
33022b6b8163d2e15eb6d0a89906854dec801945f1dYigit Boyar        public void notifyChange() throws Throwable {
33122b6b8163d2e15eb6d0a89906854dec801945f1dYigit Boyar            runTestOnUiThread(new Runnable() {
33222b6b8163d2e15eb6d0a89906854dec801945f1dYigit Boyar                @Override
33322b6b8163d2e15eb6d0a89906854dec801945f1dYigit Boyar                public void run() {
33422b6b8163d2e15eb6d0a89906854dec801945f1dYigit Boyar                    notifyDataSetChanged();
33522b6b8163d2e15eb6d0a89906854dec801945f1dYigit Boyar                }
33622b6b8163d2e15eb6d0a89906854dec801945f1dYigit Boyar            });
33722b6b8163d2e15eb6d0a89906854dec801945f1dYigit Boyar        }
33822b6b8163d2e15eb6d0a89906854dec801945f1dYigit Boyar
3397e9da1a2629b657d56e2f54abd18c6249f3be037Yigit Boyar        public void notifyItemChange(final int start, final int count) throws Throwable {
3407e9da1a2629b657d56e2f54abd18c6249f3be037Yigit Boyar            runTestOnUiThread(new Runnable() {
3417e9da1a2629b657d56e2f54abd18c6249f3be037Yigit Boyar                @Override
3427e9da1a2629b657d56e2f54abd18c6249f3be037Yigit Boyar                public void run() {
3437e9da1a2629b657d56e2f54abd18c6249f3be037Yigit Boyar                    notifyItemRangeChanged(start, count);
3447e9da1a2629b657d56e2f54abd18c6249f3be037Yigit Boyar                }
3457e9da1a2629b657d56e2f54abd18c6249f3be037Yigit Boyar            });
3467e9da1a2629b657d56e2f54abd18c6249f3be037Yigit Boyar        }
3477e9da1a2629b657d56e2f54abd18c6249f3be037Yigit Boyar
3488ae76f91527ce850f155ce960fb9068bcd5d49f9Yigit Boyar        /**
3498ae76f91527ce850f155ce960fb9068bcd5d49f9Yigit Boyar         * Similar to other methods but negative count means delete and position count means add.
3508ae76f91527ce850f155ce960fb9068bcd5d49f9Yigit Boyar         * <p>
3518ae76f91527ce850f155ce960fb9068bcd5d49f9Yigit Boyar         * For instance, calling this method with <code>[1,1], [2,-1]</code> it will first add an
3528ae76f91527ce850f155ce960fb9068bcd5d49f9Yigit Boyar         * item to index 1, then remove an item from index 2 (updated index 2)
3538ae76f91527ce850f155ce960fb9068bcd5d49f9Yigit Boyar         */
3548ae76f91527ce850f155ce960fb9068bcd5d49f9Yigit Boyar        public void addDeleteAndNotify(final int[]... startCountTuples) throws Throwable {
3558ae76f91527ce850f155ce960fb9068bcd5d49f9Yigit Boyar            new AddRemoveRunnable(startCountTuples).runOnMainThread();
356d7d27e9ebe5c7325e67e1a8af265378bd2056cadChet Haase        }
357d7d27e9ebe5c7325e67e1a8af265378bd2056cadChet Haase
358d7d27e9ebe5c7325e67e1a8af265378bd2056cadChet Haase        @Override
359d7d27e9ebe5c7325e67e1a8af265378bd2056cadChet Haase        public int getItemCount() {
360d7d27e9ebe5c7325e67e1a8af265378bd2056cadChet Haase            return mItems.size();
361d7d27e9ebe5c7325e67e1a8af265378bd2056cadChet Haase        }
3628ae76f91527ce850f155ce960fb9068bcd5d49f9Yigit Boyar
3638ae76f91527ce850f155ce960fb9068bcd5d49f9Yigit Boyar
3648ae76f91527ce850f155ce960fb9068bcd5d49f9Yigit Boyar        private class AddRemoveRunnable implements Runnable {
3658ae76f91527ce850f155ce960fb9068bcd5d49f9Yigit Boyar            final int[][] mStartCountTuples;
3668ae76f91527ce850f155ce960fb9068bcd5d49f9Yigit Boyar
3678ae76f91527ce850f155ce960fb9068bcd5d49f9Yigit Boyar            public AddRemoveRunnable(int[][] startCountTuples) {
3688ae76f91527ce850f155ce960fb9068bcd5d49f9Yigit Boyar                mStartCountTuples = startCountTuples;
3698ae76f91527ce850f155ce960fb9068bcd5d49f9Yigit Boyar            }
3708ae76f91527ce850f155ce960fb9068bcd5d49f9Yigit Boyar
3718ae76f91527ce850f155ce960fb9068bcd5d49f9Yigit Boyar            public void runOnMainThread() throws Throwable {
3728ae76f91527ce850f155ce960fb9068bcd5d49f9Yigit Boyar                if (Looper.myLooper() == Looper.getMainLooper()) {
3738ae76f91527ce850f155ce960fb9068bcd5d49f9Yigit Boyar                    run();
3748ae76f91527ce850f155ce960fb9068bcd5d49f9Yigit Boyar                } else {
3758ae76f91527ce850f155ce960fb9068bcd5d49f9Yigit Boyar                    runTestOnUiThread(this);
3768ae76f91527ce850f155ce960fb9068bcd5d49f9Yigit Boyar                }
3778ae76f91527ce850f155ce960fb9068bcd5d49f9Yigit Boyar            }
3788ae76f91527ce850f155ce960fb9068bcd5d49f9Yigit Boyar
3798ae76f91527ce850f155ce960fb9068bcd5d49f9Yigit Boyar            @Override
3808ae76f91527ce850f155ce960fb9068bcd5d49f9Yigit Boyar            public void run() {
3818ae76f91527ce850f155ce960fb9068bcd5d49f9Yigit Boyar                for (int[] tuple : mStartCountTuples) {
3828ae76f91527ce850f155ce960fb9068bcd5d49f9Yigit Boyar                    if (tuple[1] < 0) {
3838ae76f91527ce850f155ce960fb9068bcd5d49f9Yigit Boyar                        delete(tuple);
3848ae76f91527ce850f155ce960fb9068bcd5d49f9Yigit Boyar                    } else {
3858ae76f91527ce850f155ce960fb9068bcd5d49f9Yigit Boyar                        add(tuple);
3868ae76f91527ce850f155ce960fb9068bcd5d49f9Yigit Boyar                    }
3878ae76f91527ce850f155ce960fb9068bcd5d49f9Yigit Boyar                }
3888ae76f91527ce850f155ce960fb9068bcd5d49f9Yigit Boyar            }
3898ae76f91527ce850f155ce960fb9068bcd5d49f9Yigit Boyar
3908ae76f91527ce850f155ce960fb9068bcd5d49f9Yigit Boyar            private void add(int[] tuple) {
3918ae76f91527ce850f155ce960fb9068bcd5d49f9Yigit Boyar                for (int i = 0; i < tuple[1]; i++) {
3928ae76f91527ce850f155ce960fb9068bcd5d49f9Yigit Boyar                    mItems.add(tuple[0], new Item(i, "new item " + i));
3938ae76f91527ce850f155ce960fb9068bcd5d49f9Yigit Boyar                }
3948ae76f91527ce850f155ce960fb9068bcd5d49f9Yigit Boyar                // offset others
3958ae76f91527ce850f155ce960fb9068bcd5d49f9Yigit Boyar                for (int i = tuple[0] + tuple[1]; i < mItems.size(); i++) {
3968ae76f91527ce850f155ce960fb9068bcd5d49f9Yigit Boyar                    mItems.get(i).originalIndex += tuple[1];
3978ae76f91527ce850f155ce960fb9068bcd5d49f9Yigit Boyar                }
3988ae76f91527ce850f155ce960fb9068bcd5d49f9Yigit Boyar                notifyItemRangeInserted(tuple[0], tuple[1]);
3998ae76f91527ce850f155ce960fb9068bcd5d49f9Yigit Boyar            }
4008ae76f91527ce850f155ce960fb9068bcd5d49f9Yigit Boyar
4018ae76f91527ce850f155ce960fb9068bcd5d49f9Yigit Boyar            private void delete(int[] tuple) {
4028ae76f91527ce850f155ce960fb9068bcd5d49f9Yigit Boyar                for (int i = 0; i < -tuple[1]; i++) {
4038ae76f91527ce850f155ce960fb9068bcd5d49f9Yigit Boyar                    mItems.remove(tuple[0]);
4048ae76f91527ce850f155ce960fb9068bcd5d49f9Yigit Boyar                }
4058ae76f91527ce850f155ce960fb9068bcd5d49f9Yigit Boyar                notifyItemRangeRemoved(tuple[0], -tuple[1]);
4068ae76f91527ce850f155ce960fb9068bcd5d49f9Yigit Boyar            }
4078ae76f91527ce850f155ce960fb9068bcd5d49f9Yigit Boyar        }
4088ae76f91527ce850f155ce960fb9068bcd5d49f9Yigit Boyar    }
4098ae76f91527ce850f155ce960fb9068bcd5d49f9Yigit Boyar
4108ae76f91527ce850f155ce960fb9068bcd5d49f9Yigit Boyar    @Override
4118ae76f91527ce850f155ce960fb9068bcd5d49f9Yigit Boyar    public void runTestOnUiThread(Runnable r) throws Throwable {
4128ae76f91527ce850f155ce960fb9068bcd5d49f9Yigit Boyar        if (Looper.myLooper() == Looper.getMainLooper()) {
4138ae76f91527ce850f155ce960fb9068bcd5d49f9Yigit Boyar            r.run();
4148ae76f91527ce850f155ce960fb9068bcd5d49f9Yigit Boyar        } else {
4158ae76f91527ce850f155ce960fb9068bcd5d49f9Yigit Boyar            super.runTestOnUiThread(r);
4168ae76f91527ce850f155ce960fb9068bcd5d49f9Yigit Boyar        }
417d7d27e9ebe5c7325e67e1a8af265378bd2056cadChet Haase    }
418b5f8b4fecb531aee6b359b4968409410aba90817Yigit Boyar}