BaseRecyclerViewInstrumentationTest.java revision 5ced882cabdcefbb469e332f6f73983999aad0e5
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
43d7d27e9ebe5c7325e67e1a8af265378bd2056cadChet Haase    public BaseRecyclerViewInstrumentationTest() {
44b5f8b4fecb531aee6b359b4968409410aba90817Yigit Boyar        this(false);
45b5f8b4fecb531aee6b359b4968409410aba90817Yigit Boyar    }
46b5f8b4fecb531aee6b359b4968409410aba90817Yigit Boyar
47b5f8b4fecb531aee6b359b4968409410aba90817Yigit Boyar    public BaseRecyclerViewInstrumentationTest(boolean debug) {
488ae76f91527ce850f155ce960fb9068bcd5d49f9Yigit Boyar        super("android.support.v7.recyclerview", TestActivity.class);
49b5f8b4fecb531aee6b359b4968409410aba90817Yigit Boyar        mDebug = debug;
50b5f8b4fecb531aee6b359b4968409410aba90817Yigit Boyar    }
51b5f8b4fecb531aee6b359b4968409410aba90817Yigit Boyar
528ae76f91527ce850f155ce960fb9068bcd5d49f9Yigit Boyar    @Override
538ae76f91527ce850f155ce960fb9068bcd5d49f9Yigit Boyar    protected void tearDown() throws Exception {
548ae76f91527ce850f155ce960fb9068bcd5d49f9Yigit Boyar        if (mRecyclerView != null) {
558ae76f91527ce850f155ce960fb9068bcd5d49f9Yigit Boyar            try {
568ae76f91527ce850f155ce960fb9068bcd5d49f9Yigit Boyar                removeRecyclerView();
578ae76f91527ce850f155ce960fb9068bcd5d49f9Yigit Boyar            } catch (Throwable throwable) {
588ae76f91527ce850f155ce960fb9068bcd5d49f9Yigit Boyar                throwable.printStackTrace();
598ae76f91527ce850f155ce960fb9068bcd5d49f9Yigit Boyar            }
608ae76f91527ce850f155ce960fb9068bcd5d49f9Yigit Boyar        }
618ae76f91527ce850f155ce960fb9068bcd5d49f9Yigit Boyar        getInstrumentation().waitForIdleSync();
628ae76f91527ce850f155ce960fb9068bcd5d49f9Yigit Boyar        super.tearDown();
638ae76f91527ce850f155ce960fb9068bcd5d49f9Yigit Boyar    }
648ae76f91527ce850f155ce960fb9068bcd5d49f9Yigit Boyar
65b5f8b4fecb531aee6b359b4968409410aba90817Yigit Boyar    public void removeRecyclerView() throws Throwable {
66b5f8b4fecb531aee6b359b4968409410aba90817Yigit Boyar        mRecyclerView = null;
67b5f8b4fecb531aee6b359b4968409410aba90817Yigit Boyar        runTestOnUiThread(new Runnable() {
68b5f8b4fecb531aee6b359b4968409410aba90817Yigit Boyar            @Override
69b5f8b4fecb531aee6b359b4968409410aba90817Yigit Boyar            public void run() {
70b5f8b4fecb531aee6b359b4968409410aba90817Yigit Boyar                getActivity().mContainer.removeAllViews();
71b5f8b4fecb531aee6b359b4968409410aba90817Yigit Boyar            }
72b5f8b4fecb531aee6b359b4968409410aba90817Yigit Boyar        });
73b5f8b4fecb531aee6b359b4968409410aba90817Yigit Boyar    }
74b5f8b4fecb531aee6b359b4968409410aba90817Yigit Boyar
75b5f8b4fecb531aee6b359b4968409410aba90817Yigit Boyar    public void setRecyclerView(final RecyclerView recyclerView) throws Throwable {
76b5f8b4fecb531aee6b359b4968409410aba90817Yigit Boyar        mRecyclerView = recyclerView;
778ae76f91527ce850f155ce960fb9068bcd5d49f9Yigit Boyar        mAdapterHelper = recyclerView.mAdapterHelper;
78b5f8b4fecb531aee6b359b4968409410aba90817Yigit Boyar        runTestOnUiThread(new Runnable() {
79b5f8b4fecb531aee6b359b4968409410aba90817Yigit Boyar            @Override
80b5f8b4fecb531aee6b359b4968409410aba90817Yigit Boyar            public void run() {
81b5f8b4fecb531aee6b359b4968409410aba90817Yigit Boyar                getActivity().mContainer.addView(recyclerView);
82b5f8b4fecb531aee6b359b4968409410aba90817Yigit Boyar            }
83b5f8b4fecb531aee6b359b4968409410aba90817Yigit Boyar        });
84b5f8b4fecb531aee6b359b4968409410aba90817Yigit Boyar    }
85b5f8b4fecb531aee6b359b4968409410aba90817Yigit Boyar
86b5f8b4fecb531aee6b359b4968409410aba90817Yigit Boyar    public void requestLayoutOnUIThread(final View view) throws Throwable {
87b5f8b4fecb531aee6b359b4968409410aba90817Yigit Boyar        runTestOnUiThread(new Runnable() {
88b5f8b4fecb531aee6b359b4968409410aba90817Yigit Boyar            @Override
89b5f8b4fecb531aee6b359b4968409410aba90817Yigit Boyar            public void run() {
90b5f8b4fecb531aee6b359b4968409410aba90817Yigit Boyar                view.requestLayout();
91b5f8b4fecb531aee6b359b4968409410aba90817Yigit Boyar            }
92b5f8b4fecb531aee6b359b4968409410aba90817Yigit Boyar        });
93b5f8b4fecb531aee6b359b4968409410aba90817Yigit Boyar    }
94b5f8b4fecb531aee6b359b4968409410aba90817Yigit Boyar
95b5f8b4fecb531aee6b359b4968409410aba90817Yigit Boyar    public void scrollBy(final int dt) throws Throwable {
96b5f8b4fecb531aee6b359b4968409410aba90817Yigit Boyar        runTestOnUiThread(new Runnable() {
97b5f8b4fecb531aee6b359b4968409410aba90817Yigit Boyar            @Override
98b5f8b4fecb531aee6b359b4968409410aba90817Yigit Boyar            public void run() {
99b5f8b4fecb531aee6b359b4968409410aba90817Yigit Boyar                if (mRecyclerView.getLayoutManager().canScrollHorizontally()) {
100b5f8b4fecb531aee6b359b4968409410aba90817Yigit Boyar                    mRecyclerView.scrollBy(dt, 0);
101b5f8b4fecb531aee6b359b4968409410aba90817Yigit Boyar                } else {
102b5f8b4fecb531aee6b359b4968409410aba90817Yigit Boyar                    mRecyclerView.scrollBy(0, dt);
103b5f8b4fecb531aee6b359b4968409410aba90817Yigit Boyar                }
104b5f8b4fecb531aee6b359b4968409410aba90817Yigit Boyar
105b5f8b4fecb531aee6b359b4968409410aba90817Yigit Boyar            }
106b5f8b4fecb531aee6b359b4968409410aba90817Yigit Boyar        });
107d7d27e9ebe5c7325e67e1a8af265378bd2056cadChet Haase    }
108d7d27e9ebe5c7325e67e1a8af265378bd2056cadChet Haase
1098edcb0bdeaba6931f9d8154f0c81f57da7ddab2aYigit Boyar    void scrollToPosition(final int position) throws Throwable {
1108edcb0bdeaba6931f9d8154f0c81f57da7ddab2aYigit Boyar        runTestOnUiThread(new Runnable() {
1118edcb0bdeaba6931f9d8154f0c81f57da7ddab2aYigit Boyar            @Override
1128edcb0bdeaba6931f9d8154f0c81f57da7ddab2aYigit Boyar            public void run() {
1138edcb0bdeaba6931f9d8154f0c81f57da7ddab2aYigit Boyar                mRecyclerView.getLayoutManager().scrollToPosition(position);
1148edcb0bdeaba6931f9d8154f0c81f57da7ddab2aYigit Boyar            }
1158edcb0bdeaba6931f9d8154f0c81f57da7ddab2aYigit Boyar        });
1168edcb0bdeaba6931f9d8154f0c81f57da7ddab2aYigit Boyar    }
1178edcb0bdeaba6931f9d8154f0c81f57da7ddab2aYigit Boyar
1188edcb0bdeaba6931f9d8154f0c81f57da7ddab2aYigit Boyar    void smoothScrollToPosition(final int position)
1198edcb0bdeaba6931f9d8154f0c81f57da7ddab2aYigit Boyar            throws Throwable {
1208edcb0bdeaba6931f9d8154f0c81f57da7ddab2aYigit Boyar        Log.d(TAG, "SMOOTH scrolling to " + position);
1218edcb0bdeaba6931f9d8154f0c81f57da7ddab2aYigit Boyar        runTestOnUiThread(new Runnable() {
1228edcb0bdeaba6931f9d8154f0c81f57da7ddab2aYigit Boyar            @Override
1238edcb0bdeaba6931f9d8154f0c81f57da7ddab2aYigit Boyar            public void run() {
1248edcb0bdeaba6931f9d8154f0c81f57da7ddab2aYigit Boyar                mRecyclerView.smoothScrollToPosition(position);
1258edcb0bdeaba6931f9d8154f0c81f57da7ddab2aYigit Boyar            }
1268edcb0bdeaba6931f9d8154f0c81f57da7ddab2aYigit Boyar        });
1278edcb0bdeaba6931f9d8154f0c81f57da7ddab2aYigit Boyar        while (mRecyclerView.getLayoutManager().isSmoothScrolling() ||
1288edcb0bdeaba6931f9d8154f0c81f57da7ddab2aYigit Boyar                mRecyclerView.getScrollState() != RecyclerView.SCROLL_STATE_IDLE) {
1298edcb0bdeaba6931f9d8154f0c81f57da7ddab2aYigit Boyar            if (mDebug) {
1308edcb0bdeaba6931f9d8154f0c81f57da7ddab2aYigit Boyar                Log.d(TAG, "SMOOTH scrolling step");
1318edcb0bdeaba6931f9d8154f0c81f57da7ddab2aYigit Boyar            }
1328edcb0bdeaba6931f9d8154f0c81f57da7ddab2aYigit Boyar            Thread.sleep(200);
1338edcb0bdeaba6931f9d8154f0c81f57da7ddab2aYigit Boyar        }
1348edcb0bdeaba6931f9d8154f0c81f57da7ddab2aYigit Boyar        Log.d(TAG, "SMOOTH scrolling done");
1358edcb0bdeaba6931f9d8154f0c81f57da7ddab2aYigit Boyar    }
1368edcb0bdeaba6931f9d8154f0c81f57da7ddab2aYigit Boyar
137d7d27e9ebe5c7325e67e1a8af265378bd2056cadChet Haase    class TestViewHolder extends RecyclerView.ViewHolder {
138d7d27e9ebe5c7325e67e1a8af265378bd2056cadChet Haase
139d7d27e9ebe5c7325e67e1a8af265378bd2056cadChet Haase        Item mBindedItem;
140d7d27e9ebe5c7325e67e1a8af265378bd2056cadChet Haase
141d7d27e9ebe5c7325e67e1a8af265378bd2056cadChet Haase        public TestViewHolder(View itemView) {
142d7d27e9ebe5c7325e67e1a8af265378bd2056cadChet Haase            super(itemView);
143d7d27e9ebe5c7325e67e1a8af265378bd2056cadChet Haase        }
144d7d27e9ebe5c7325e67e1a8af265378bd2056cadChet Haase    }
145d7d27e9ebe5c7325e67e1a8af265378bd2056cadChet Haase
146d7d27e9ebe5c7325e67e1a8af265378bd2056cadChet Haase    class TestLayoutManager extends RecyclerView.LayoutManager {
147d7d27e9ebe5c7325e67e1a8af265378bd2056cadChet Haase
148d7d27e9ebe5c7325e67e1a8af265378bd2056cadChet Haase        CountDownLatch layoutLatch;
149d7d27e9ebe5c7325e67e1a8af265378bd2056cadChet Haase
150d7d27e9ebe5c7325e67e1a8af265378bd2056cadChet Haase        public void expectLayouts(int count) {
151d7d27e9ebe5c7325e67e1a8af265378bd2056cadChet Haase            layoutLatch = new CountDownLatch(count);
152d7d27e9ebe5c7325e67e1a8af265378bd2056cadChet Haase        }
153d7d27e9ebe5c7325e67e1a8af265378bd2056cadChet Haase
154b5f8b4fecb531aee6b359b4968409410aba90817Yigit Boyar        public void waitForLayout(long timeout, TimeUnit timeUnit) throws Throwable {
155b5f8b4fecb531aee6b359b4968409410aba90817Yigit Boyar            layoutLatch.await(timeout * (mDebug ? 100 : 1), timeUnit);
156d7d27e9ebe5c7325e67e1a8af265378bd2056cadChet Haase            assertEquals("all expected layouts should be executed at the expected time",
157d7d27e9ebe5c7325e67e1a8af265378bd2056cadChet Haase                    0, layoutLatch.getCount());
158d7d27e9ebe5c7325e67e1a8af265378bd2056cadChet Haase        }
159d7d27e9ebe5c7325e67e1a8af265378bd2056cadChet Haase
160b5f8b4fecb531aee6b359b4968409410aba90817Yigit Boyar        public void assertLayoutCount(int count, String msg, long timeout) throws Throwable {
161b5f8b4fecb531aee6b359b4968409410aba90817Yigit Boyar            layoutLatch.await(timeout, TimeUnit.SECONDS);
162b5f8b4fecb531aee6b359b4968409410aba90817Yigit Boyar            assertEquals(msg, count, layoutLatch.getCount());
163b5f8b4fecb531aee6b359b4968409410aba90817Yigit Boyar        }
164b5f8b4fecb531aee6b359b4968409410aba90817Yigit Boyar
165b5f8b4fecb531aee6b359b4968409410aba90817Yigit Boyar        public void assertNoLayout(String msg, long timeout) throws Throwable {
166b5f8b4fecb531aee6b359b4968409410aba90817Yigit Boyar            layoutLatch.await(timeout, TimeUnit.SECONDS);
167b5f8b4fecb531aee6b359b4968409410aba90817Yigit Boyar            assertFalse(msg, layoutLatch.getCount() == 0);
168b5f8b4fecb531aee6b359b4968409410aba90817Yigit Boyar        }
169b5f8b4fecb531aee6b359b4968409410aba90817Yigit Boyar
170b5f8b4fecb531aee6b359b4968409410aba90817Yigit Boyar        public void waitForLayout(long timeout) throws Throwable {
1718ae76f91527ce850f155ce960fb9068bcd5d49f9Yigit Boyar            waitForLayout(timeout * (mDebug ? 10000 : 1), TimeUnit.SECONDS);
172b5f8b4fecb531aee6b359b4968409410aba90817Yigit Boyar        }
173b5f8b4fecb531aee6b359b4968409410aba90817Yigit Boyar
174d7d27e9ebe5c7325e67e1a8af265378bd2056cadChet Haase        @Override
175d7d27e9ebe5c7325e67e1a8af265378bd2056cadChet Haase        public RecyclerView.LayoutParams generateDefaultLayoutParams() {
176d7d27e9ebe5c7325e67e1a8af265378bd2056cadChet Haase            return new RecyclerView.LayoutParams(ViewGroup.LayoutParams.WRAP_CONTENT,
177d7d27e9ebe5c7325e67e1a8af265378bd2056cadChet Haase                    ViewGroup.LayoutParams.WRAP_CONTENT);
178d7d27e9ebe5c7325e67e1a8af265378bd2056cadChet Haase        }
179d7d27e9ebe5c7325e67e1a8af265378bd2056cadChet Haase
180d7d27e9ebe5c7325e67e1a8af265378bd2056cadChet Haase        void assertVisibleItemPositions() {
181d7d27e9ebe5c7325e67e1a8af265378bd2056cadChet Haase            int i = getChildCount();
182d7d27e9ebe5c7325e67e1a8af265378bd2056cadChet Haase            TestAdapter testAdapter = (TestAdapter) mRecyclerView.getAdapter();
183d7d27e9ebe5c7325e67e1a8af265378bd2056cadChet Haase            while (i-- > 0) {
184d7d27e9ebe5c7325e67e1a8af265378bd2056cadChet Haase                View view = getChildAt(i);
185d7d27e9ebe5c7325e67e1a8af265378bd2056cadChet Haase                RecyclerView.LayoutParams lp = (RecyclerView.LayoutParams) view.getLayoutParams();
186d7d27e9ebe5c7325e67e1a8af265378bd2056cadChet Haase                Item item = ((TestViewHolder) lp.mViewHolder).mBindedItem;
187b5f8b4fecb531aee6b359b4968409410aba90817Yigit Boyar                if (mDebug) {
188d7d27e9ebe5c7325e67e1a8af265378bd2056cadChet Haase                    Log.d(TAG, "testing item " + i);
189d7d27e9ebe5c7325e67e1a8af265378bd2056cadChet Haase                }
1908ae76f91527ce850f155ce960fb9068bcd5d49f9Yigit Boyar                if (!lp.isItemRemoved()) {
1918ae76f91527ce850f155ce960fb9068bcd5d49f9Yigit Boyar                    RecyclerView.ViewHolder vh = mRecyclerView.getChildViewHolder(view);
1928ae76f91527ce850f155ce960fb9068bcd5d49f9Yigit Boyar                    assertSame("item position in LP should match adapter value :" + vh,
1938ae76f91527ce850f155ce960fb9068bcd5d49f9Yigit Boyar                            testAdapter.mItems.get(vh.mPosition), item);
1948ae76f91527ce850f155ce960fb9068bcd5d49f9Yigit Boyar                }
195d7d27e9ebe5c7325e67e1a8af265378bd2056cadChet Haase            }
196d7d27e9ebe5c7325e67e1a8af265378bd2056cadChet Haase        }
197d7d27e9ebe5c7325e67e1a8af265378bd2056cadChet Haase
198b5f8b4fecb531aee6b359b4968409410aba90817Yigit Boyar        RecyclerView.LayoutParams getLp(View v) {
199b5f8b4fecb531aee6b359b4968409410aba90817Yigit Boyar            return (RecyclerView.LayoutParams) v.getLayoutParams();
200b5f8b4fecb531aee6b359b4968409410aba90817Yigit Boyar        }
201b5f8b4fecb531aee6b359b4968409410aba90817Yigit Boyar
2028ae76f91527ce850f155ce960fb9068bcd5d49f9Yigit Boyar        /**
2038ae76f91527ce850f155ce960fb9068bcd5d49f9Yigit Boyar         * returns skipped (removed) view count.
2048ae76f91527ce850f155ce960fb9068bcd5d49f9Yigit Boyar         */
2058ae76f91527ce850f155ce960fb9068bcd5d49f9Yigit Boyar        int layoutRange(RecyclerView.Recycler recycler, int start,
206d7d27e9ebe5c7325e67e1a8af265378bd2056cadChet Haase                int end) {
2078ae76f91527ce850f155ce960fb9068bcd5d49f9Yigit Boyar            int skippedAdd = 0;
208b5f8b4fecb531aee6b359b4968409410aba90817Yigit Boyar            if (mDebug) {
209d7d27e9ebe5c7325e67e1a8af265378bd2056cadChet Haase                Log.d(TAG, "will layout items from " + start + " to " + end);
210d7d27e9ebe5c7325e67e1a8af265378bd2056cadChet Haase            }
2115ced882cabdcefbb469e332f6f73983999aad0e5Yigit Boyar            int diff = end > start ? 1 : -1;
2125ced882cabdcefbb469e332f6f73983999aad0e5Yigit Boyar            for (int i = start; i != end; i+=diff) {
213b5f8b4fecb531aee6b359b4968409410aba90817Yigit Boyar                if (mDebug) {
214d7d27e9ebe5c7325e67e1a8af265378bd2056cadChet Haase                    Log.d(TAG, "laying out item " + i);
215d7d27e9ebe5c7325e67e1a8af265378bd2056cadChet Haase                }
216d7d27e9ebe5c7325e67e1a8af265378bd2056cadChet Haase                View view = recycler.getViewForPosition(i);
217b5f8b4fecb531aee6b359b4968409410aba90817Yigit Boyar                assertNotNull("view should not be null for valid position. "
218b5f8b4fecb531aee6b359b4968409410aba90817Yigit Boyar                        + "got null view at position " + i, view);
219b5f8b4fecb531aee6b359b4968409410aba90817Yigit Boyar                if (!getLp(view).isItemRemoved()) {
220b5f8b4fecb531aee6b359b4968409410aba90817Yigit Boyar                    addView(view);
2218ae76f91527ce850f155ce960fb9068bcd5d49f9Yigit Boyar                } else {
2228ae76f91527ce850f155ce960fb9068bcd5d49f9Yigit Boyar                    skippedAdd ++;
223b5f8b4fecb531aee6b359b4968409410aba90817Yigit Boyar                }
224b5f8b4fecb531aee6b359b4968409410aba90817Yigit Boyar
225d7d27e9ebe5c7325e67e1a8af265378bd2056cadChet Haase                measureChildWithMargins(view, 0, 0);
2265ced882cabdcefbb469e332f6f73983999aad0e5Yigit Boyar                layoutDecorated(view, 0, Math.abs(i - start) * 10, getDecoratedMeasuredWidth(view)
227d7d27e9ebe5c7325e67e1a8af265378bd2056cadChet Haase                        , getDecoratedMeasuredHeight(view));
228d7d27e9ebe5c7325e67e1a8af265378bd2056cadChet Haase            }
2298ae76f91527ce850f155ce960fb9068bcd5d49f9Yigit Boyar            return skippedAdd;
230d7d27e9ebe5c7325e67e1a8af265378bd2056cadChet Haase        }
231d7d27e9ebe5c7325e67e1a8af265378bd2056cadChet Haase    }
232d7d27e9ebe5c7325e67e1a8af265378bd2056cadChet Haase
233d7d27e9ebe5c7325e67e1a8af265378bd2056cadChet Haase    static class Item {
23411b7cfea15306c0d6e4ed7da09f7698df01695dfYigit Boyar        final static AtomicInteger idCounter = new AtomicInteger(0);
23511b7cfea15306c0d6e4ed7da09f7698df01695dfYigit Boyar        final public int mId = idCounter.incrementAndGet();
236d7d27e9ebe5c7325e67e1a8af265378bd2056cadChet Haase
237d7d27e9ebe5c7325e67e1a8af265378bd2056cadChet Haase        int originalIndex;
238d7d27e9ebe5c7325e67e1a8af265378bd2056cadChet Haase
239d7d27e9ebe5c7325e67e1a8af265378bd2056cadChet Haase        final String text;
240d7d27e9ebe5c7325e67e1a8af265378bd2056cadChet Haase
241d7d27e9ebe5c7325e67e1a8af265378bd2056cadChet Haase        Item(int originalIndex, String text) {
242d7d27e9ebe5c7325e67e1a8af265378bd2056cadChet Haase            this.originalIndex = originalIndex;
243d7d27e9ebe5c7325e67e1a8af265378bd2056cadChet Haase            this.text = text;
244d7d27e9ebe5c7325e67e1a8af265378bd2056cadChet Haase        }
2458ae76f91527ce850f155ce960fb9068bcd5d49f9Yigit Boyar
2468ae76f91527ce850f155ce960fb9068bcd5d49f9Yigit Boyar        @Override
2478ae76f91527ce850f155ce960fb9068bcd5d49f9Yigit Boyar        public String toString() {
2488ae76f91527ce850f155ce960fb9068bcd5d49f9Yigit Boyar            return "Item{" +
2498ae76f91527ce850f155ce960fb9068bcd5d49f9Yigit Boyar                    "mId=" + mId +
2508ae76f91527ce850f155ce960fb9068bcd5d49f9Yigit Boyar                    ", originalIndex=" + originalIndex +
2518ae76f91527ce850f155ce960fb9068bcd5d49f9Yigit Boyar                    ", text='" + text + '\'' +
2528ae76f91527ce850f155ce960fb9068bcd5d49f9Yigit Boyar                    '}';
2538ae76f91527ce850f155ce960fb9068bcd5d49f9Yigit Boyar        }
254d7d27e9ebe5c7325e67e1a8af265378bd2056cadChet Haase    }
255d7d27e9ebe5c7325e67e1a8af265378bd2056cadChet Haase
256d7d27e9ebe5c7325e67e1a8af265378bd2056cadChet Haase    class TestAdapter extends RecyclerView.Adapter<TestViewHolder> {
257d7d27e9ebe5c7325e67e1a8af265378bd2056cadChet Haase
258d7d27e9ebe5c7325e67e1a8af265378bd2056cadChet Haase        List<Item> mItems;
259d7d27e9ebe5c7325e67e1a8af265378bd2056cadChet Haase
260d7d27e9ebe5c7325e67e1a8af265378bd2056cadChet Haase        TestAdapter(int count) {
261d7d27e9ebe5c7325e67e1a8af265378bd2056cadChet Haase            mItems = new ArrayList<Item>(count);
262d7d27e9ebe5c7325e67e1a8af265378bd2056cadChet Haase            for (int i = 0; i < count; i++) {
263d7d27e9ebe5c7325e67e1a8af265378bd2056cadChet Haase                mItems.add(new Item(i, "Item " + i));
264d7d27e9ebe5c7325e67e1a8af265378bd2056cadChet Haase            }
265d7d27e9ebe5c7325e67e1a8af265378bd2056cadChet Haase        }
266d7d27e9ebe5c7325e67e1a8af265378bd2056cadChet Haase
267d7d27e9ebe5c7325e67e1a8af265378bd2056cadChet Haase        @Override
268d7d27e9ebe5c7325e67e1a8af265378bd2056cadChet Haase        public TestViewHolder onCreateViewHolder(ViewGroup parent,
269d7d27e9ebe5c7325e67e1a8af265378bd2056cadChet Haase                int viewType) {
270d7d27e9ebe5c7325e67e1a8af265378bd2056cadChet Haase            return new TestViewHolder(new TextView(parent.getContext()));
271d7d27e9ebe5c7325e67e1a8af265378bd2056cadChet Haase        }
272d7d27e9ebe5c7325e67e1a8af265378bd2056cadChet Haase
273d7d27e9ebe5c7325e67e1a8af265378bd2056cadChet Haase        @Override
274d7d27e9ebe5c7325e67e1a8af265378bd2056cadChet Haase        public void onBindViewHolder(TestViewHolder holder, int position) {
275d7d27e9ebe5c7325e67e1a8af265378bd2056cadChet Haase            final Item item = mItems.get(position);
276d7d27e9ebe5c7325e67e1a8af265378bd2056cadChet Haase            ((TextView) (holder.itemView)).setText(item.text);
277d7d27e9ebe5c7325e67e1a8af265378bd2056cadChet Haase            holder.mBindedItem = item;
278d7d27e9ebe5c7325e67e1a8af265378bd2056cadChet Haase        }
279d7d27e9ebe5c7325e67e1a8af265378bd2056cadChet Haase
280b5f8b4fecb531aee6b359b4968409410aba90817Yigit Boyar        public void deleteAndNotify(final int start, final int count) throws Throwable {
281b5f8b4fecb531aee6b359b4968409410aba90817Yigit Boyar            deleteAndNotify(new int[]{start, count});
282b5f8b4fecb531aee6b359b4968409410aba90817Yigit Boyar        }
283b5f8b4fecb531aee6b359b4968409410aba90817Yigit Boyar
284b5f8b4fecb531aee6b359b4968409410aba90817Yigit Boyar        /**
285b5f8b4fecb531aee6b359b4968409410aba90817Yigit Boyar         * Deletes items in the given ranges.
286b5f8b4fecb531aee6b359b4968409410aba90817Yigit Boyar         * <p>
287b5f8b4fecb531aee6b359b4968409410aba90817Yigit Boyar         * Note that each operation affects the one after so you should offset them properly.
288b5f8b4fecb531aee6b359b4968409410aba90817Yigit Boyar         * <p>
289b5f8b4fecb531aee6b359b4968409410aba90817Yigit Boyar         * For example, if adapter has 5 items (A,B,C,D,E), and then you call this method with
290b5f8b4fecb531aee6b359b4968409410aba90817Yigit Boyar         * <code>[1, 2],[2, 1]</code>, it will first delete items B,C and the new adapter will be
291b5f8b4fecb531aee6b359b4968409410aba90817Yigit Boyar         * A D E. Then it will delete 2,1 which means it will delete E.
292b5f8b4fecb531aee6b359b4968409410aba90817Yigit Boyar         */
293b5f8b4fecb531aee6b359b4968409410aba90817Yigit Boyar        public void deleteAndNotify(final int[]... startCountTuples) throws Throwable {
2948ae76f91527ce850f155ce960fb9068bcd5d49f9Yigit Boyar            for (int[] tuple : startCountTuples) {
2958ae76f91527ce850f155ce960fb9068bcd5d49f9Yigit Boyar                tuple[1] = -tuple[1];
2968ae76f91527ce850f155ce960fb9068bcd5d49f9Yigit Boyar            }
2978ae76f91527ce850f155ce960fb9068bcd5d49f9Yigit Boyar            new AddRemoveRunnable(startCountTuples).runOnMainThread();
298d7d27e9ebe5c7325e67e1a8af265378bd2056cadChet Haase        }
299d7d27e9ebe5c7325e67e1a8af265378bd2056cadChet Haase
300b5f8b4fecb531aee6b359b4968409410aba90817Yigit Boyar        public void addAndNotify(final int start, final int count) throws Throwable {
301b5f8b4fecb531aee6b359b4968409410aba90817Yigit Boyar            addAndNotify(new int[]{start, count});
302b5f8b4fecb531aee6b359b4968409410aba90817Yigit Boyar        }
303b5f8b4fecb531aee6b359b4968409410aba90817Yigit Boyar
304b5f8b4fecb531aee6b359b4968409410aba90817Yigit Boyar        public void addAndNotify(final int[]... startCountTuples) throws Throwable {
3058ae76f91527ce850f155ce960fb9068bcd5d49f9Yigit Boyar            new AddRemoveRunnable(startCountTuples).runOnMainThread();
3068ae76f91527ce850f155ce960fb9068bcd5d49f9Yigit Boyar        }
307b5f8b4fecb531aee6b359b4968409410aba90817Yigit Boyar
30822b6b8163d2e15eb6d0a89906854dec801945f1dYigit Boyar        public void notifyChange() throws Throwable {
30922b6b8163d2e15eb6d0a89906854dec801945f1dYigit Boyar            runTestOnUiThread(new Runnable() {
31022b6b8163d2e15eb6d0a89906854dec801945f1dYigit Boyar                @Override
31122b6b8163d2e15eb6d0a89906854dec801945f1dYigit Boyar                public void run() {
31222b6b8163d2e15eb6d0a89906854dec801945f1dYigit Boyar                    notifyDataSetChanged();
31322b6b8163d2e15eb6d0a89906854dec801945f1dYigit Boyar                }
31422b6b8163d2e15eb6d0a89906854dec801945f1dYigit Boyar            });
31522b6b8163d2e15eb6d0a89906854dec801945f1dYigit Boyar        }
31622b6b8163d2e15eb6d0a89906854dec801945f1dYigit Boyar
3178ae76f91527ce850f155ce960fb9068bcd5d49f9Yigit Boyar        /**
3188ae76f91527ce850f155ce960fb9068bcd5d49f9Yigit Boyar         * Similar to other methods but negative count means delete and position count means add.
3198ae76f91527ce850f155ce960fb9068bcd5d49f9Yigit Boyar         * <p>
3208ae76f91527ce850f155ce960fb9068bcd5d49f9Yigit Boyar         * For instance, calling this method with <code>[1,1], [2,-1]</code> it will first add an
3218ae76f91527ce850f155ce960fb9068bcd5d49f9Yigit Boyar         * item to index 1, then remove an item from index 2 (updated index 2)
3228ae76f91527ce850f155ce960fb9068bcd5d49f9Yigit Boyar         */
3238ae76f91527ce850f155ce960fb9068bcd5d49f9Yigit Boyar        public void addDeleteAndNotify(final int[]... startCountTuples) throws Throwable {
3248ae76f91527ce850f155ce960fb9068bcd5d49f9Yigit Boyar            new AddRemoveRunnable(startCountTuples).runOnMainThread();
325d7d27e9ebe5c7325e67e1a8af265378bd2056cadChet Haase        }
326d7d27e9ebe5c7325e67e1a8af265378bd2056cadChet Haase
327d7d27e9ebe5c7325e67e1a8af265378bd2056cadChet Haase        @Override
328d7d27e9ebe5c7325e67e1a8af265378bd2056cadChet Haase        public int getItemCount() {
329d7d27e9ebe5c7325e67e1a8af265378bd2056cadChet Haase            return mItems.size();
330d7d27e9ebe5c7325e67e1a8af265378bd2056cadChet Haase        }
3318ae76f91527ce850f155ce960fb9068bcd5d49f9Yigit Boyar
3328ae76f91527ce850f155ce960fb9068bcd5d49f9Yigit Boyar
3338ae76f91527ce850f155ce960fb9068bcd5d49f9Yigit Boyar        private class AddRemoveRunnable implements Runnable {
3348ae76f91527ce850f155ce960fb9068bcd5d49f9Yigit Boyar            final int[][] mStartCountTuples;
3358ae76f91527ce850f155ce960fb9068bcd5d49f9Yigit Boyar
3368ae76f91527ce850f155ce960fb9068bcd5d49f9Yigit Boyar            public AddRemoveRunnable(int[][] startCountTuples) {
3378ae76f91527ce850f155ce960fb9068bcd5d49f9Yigit Boyar                mStartCountTuples = startCountTuples;
3388ae76f91527ce850f155ce960fb9068bcd5d49f9Yigit Boyar            }
3398ae76f91527ce850f155ce960fb9068bcd5d49f9Yigit Boyar
3408ae76f91527ce850f155ce960fb9068bcd5d49f9Yigit Boyar            public void runOnMainThread() throws Throwable {
3418ae76f91527ce850f155ce960fb9068bcd5d49f9Yigit Boyar                if (Looper.myLooper() == Looper.getMainLooper()) {
3428ae76f91527ce850f155ce960fb9068bcd5d49f9Yigit Boyar                    run();
3438ae76f91527ce850f155ce960fb9068bcd5d49f9Yigit Boyar                } else {
3448ae76f91527ce850f155ce960fb9068bcd5d49f9Yigit Boyar                    runTestOnUiThread(this);
3458ae76f91527ce850f155ce960fb9068bcd5d49f9Yigit Boyar                }
3468ae76f91527ce850f155ce960fb9068bcd5d49f9Yigit Boyar            }
3478ae76f91527ce850f155ce960fb9068bcd5d49f9Yigit Boyar
3488ae76f91527ce850f155ce960fb9068bcd5d49f9Yigit Boyar            @Override
3498ae76f91527ce850f155ce960fb9068bcd5d49f9Yigit Boyar            public void run() {
3508ae76f91527ce850f155ce960fb9068bcd5d49f9Yigit Boyar                for (int[] tuple : mStartCountTuples) {
3518ae76f91527ce850f155ce960fb9068bcd5d49f9Yigit Boyar                    if (tuple[1] < 0) {
3528ae76f91527ce850f155ce960fb9068bcd5d49f9Yigit Boyar                        delete(tuple);
3538ae76f91527ce850f155ce960fb9068bcd5d49f9Yigit Boyar                    } else {
3548ae76f91527ce850f155ce960fb9068bcd5d49f9Yigit Boyar                        add(tuple);
3558ae76f91527ce850f155ce960fb9068bcd5d49f9Yigit Boyar                    }
3568ae76f91527ce850f155ce960fb9068bcd5d49f9Yigit Boyar                }
3578ae76f91527ce850f155ce960fb9068bcd5d49f9Yigit Boyar            }
3588ae76f91527ce850f155ce960fb9068bcd5d49f9Yigit Boyar
3598ae76f91527ce850f155ce960fb9068bcd5d49f9Yigit Boyar            private void add(int[] tuple) {
3608ae76f91527ce850f155ce960fb9068bcd5d49f9Yigit Boyar                for (int i = 0; i < tuple[1]; i++) {
3618ae76f91527ce850f155ce960fb9068bcd5d49f9Yigit Boyar                    mItems.add(tuple[0], new Item(i, "new item " + i));
3628ae76f91527ce850f155ce960fb9068bcd5d49f9Yigit Boyar                }
3638ae76f91527ce850f155ce960fb9068bcd5d49f9Yigit Boyar                // offset others
3648ae76f91527ce850f155ce960fb9068bcd5d49f9Yigit Boyar                for (int i = tuple[0] + tuple[1]; i < mItems.size(); i++) {
3658ae76f91527ce850f155ce960fb9068bcd5d49f9Yigit Boyar                    mItems.get(i).originalIndex += tuple[1];
3668ae76f91527ce850f155ce960fb9068bcd5d49f9Yigit Boyar                }
3678ae76f91527ce850f155ce960fb9068bcd5d49f9Yigit Boyar                notifyItemRangeInserted(tuple[0], tuple[1]);
3688ae76f91527ce850f155ce960fb9068bcd5d49f9Yigit Boyar            }
3698ae76f91527ce850f155ce960fb9068bcd5d49f9Yigit Boyar
3708ae76f91527ce850f155ce960fb9068bcd5d49f9Yigit Boyar            private void delete(int[] tuple) {
3718ae76f91527ce850f155ce960fb9068bcd5d49f9Yigit Boyar                for (int i = 0; i < -tuple[1]; i++) {
3728ae76f91527ce850f155ce960fb9068bcd5d49f9Yigit Boyar                    mItems.remove(tuple[0]);
3738ae76f91527ce850f155ce960fb9068bcd5d49f9Yigit Boyar                }
3748ae76f91527ce850f155ce960fb9068bcd5d49f9Yigit Boyar                notifyItemRangeRemoved(tuple[0], -tuple[1]);
3758ae76f91527ce850f155ce960fb9068bcd5d49f9Yigit Boyar            }
3768ae76f91527ce850f155ce960fb9068bcd5d49f9Yigit Boyar        }
3778ae76f91527ce850f155ce960fb9068bcd5d49f9Yigit Boyar    }
3788ae76f91527ce850f155ce960fb9068bcd5d49f9Yigit Boyar
3798ae76f91527ce850f155ce960fb9068bcd5d49f9Yigit Boyar    @Override
3808ae76f91527ce850f155ce960fb9068bcd5d49f9Yigit Boyar    public void runTestOnUiThread(Runnable r) throws Throwable {
3818ae76f91527ce850f155ce960fb9068bcd5d49f9Yigit Boyar        if (Looper.myLooper() == Looper.getMainLooper()) {
3828ae76f91527ce850f155ce960fb9068bcd5d49f9Yigit Boyar            r.run();
3838ae76f91527ce850f155ce960fb9068bcd5d49f9Yigit Boyar        } else {
3848ae76f91527ce850f155ce960fb9068bcd5d49f9Yigit Boyar            super.runTestOnUiThread(r);
3858ae76f91527ce850f155ce960fb9068bcd5d49f9Yigit Boyar        }
386d7d27e9ebe5c7325e67e1a8af265378bd2056cadChet Haase    }
387b5f8b4fecb531aee6b359b4968409410aba90817Yigit Boyar}