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
19959f4c0fac89425a8a9842e82fc180ec736fffacYigit Boyarimport static android.support.v7.widget.RecyclerView.SCROLL_STATE_IDLE;
20959f4c0fac89425a8a9842e82fc180ec736fffacYigit Boyar
21959f4c0fac89425a8a9842e82fc180ec736fffacYigit Boyarimport org.hamcrest.CoreMatchers;
220a017072206f93474ccd2706e7983c2ff778b904Yigit Boyarimport org.hamcrest.MatcherAssert;
230a017072206f93474ccd2706e7983c2ff778b904Yigit Boyarimport org.junit.After;
240a017072206f93474ccd2706e7983c2ff778b904Yigit Boyarimport org.junit.Before;
250a017072206f93474ccd2706e7983c2ff778b904Yigit Boyarimport org.junit.Rule;
260a017072206f93474ccd2706e7983c2ff778b904Yigit Boyar
272bf89a9bae2b6110a77cf0b3754b567bb07d12a5Yigit Boyarimport android.app.Instrumentation;
28c50c4cad31d73e574b27bb3d7581542975e37263Yigit Boyarimport android.graphics.Rect;
298ae76f91527ce850f155ce960fb9068bcd5d49f9Yigit Boyarimport android.os.Looper;
300a017072206f93474ccd2706e7983c2ff778b904Yigit Boyarimport android.support.test.InstrumentationRegistry;
310a017072206f93474ccd2706e7983c2ff778b904Yigit Boyarimport android.support.test.rule.ActivityTestRule;
32e2437126f0225135891544315ff72e54c7de0f59Yigit Boyarimport android.support.v4.view.ViewCompat;
33959f4c0fac89425a8a9842e82fc180ec736fffacYigit Boyarimport android.support.v7.recyclerview.test.SameActivityTestRule;
34d7d27e9ebe5c7325e67e1a8af265378bd2056cadChet Haaseimport android.util.Log;
35e71a1df9b3c0e1bd3c21a1b3dd20a41790d4a950Yigit Boyarimport android.view.LayoutInflater;
36d7d27e9ebe5c7325e67e1a8af265378bd2056cadChet Haaseimport android.view.View;
37d7d27e9ebe5c7325e67e1a8af265378bd2056cadChet Haaseimport android.view.ViewGroup;
38310e95e1c6dfe4f26ef594233e65e1ff83e0f1ffYigit Boyarimport android.widget.FrameLayout;
39d7d27e9ebe5c7325e67e1a8af265378bd2056cadChet Haaseimport android.widget.TextView;
40d7d27e9ebe5c7325e67e1a8af265378bd2056cadChet Haase
412bf89a9bae2b6110a77cf0b3754b567bb07d12a5Yigit Boyarimport java.lang.reflect.InvocationTargetException;
422bf89a9bae2b6110a77cf0b3754b567bb07d12a5Yigit Boyarimport java.lang.reflect.Method;
43d7d27e9ebe5c7325e67e1a8af265378bd2056cadChet Haaseimport java.util.ArrayList;
4490ca3086dbf66ad6bb8840e46ec8524a705e1c18Yigit Boyarimport java.util.HashSet;
45d7d27e9ebe5c7325e67e1a8af265378bd2056cadChet Haaseimport java.util.List;
4690ca3086dbf66ad6bb8840e46ec8524a705e1c18Yigit Boyarimport java.util.Set;
47d7d27e9ebe5c7325e67e1a8af265378bd2056cadChet Haaseimport java.util.concurrent.CountDownLatch;
48d7d27e9ebe5c7325e67e1a8af265378bd2056cadChet Haaseimport java.util.concurrent.TimeUnit;
49959f4c0fac89425a8a9842e82fc180ec736fffacYigit Boyarimport java.util.concurrent.atomic.AtomicBoolean;
5011b7cfea15306c0d6e4ed7da09f7698df01695dfYigit Boyarimport java.util.concurrent.atomic.AtomicInteger;
51da50fde358bd3ef2edb273b3bf0265cf5e84ec6bYigit Boyarimport java.util.concurrent.locks.ReentrantLock;
52e71a1df9b3c0e1bd3c21a1b3dd20a41790d4a950Yigit Boyarimport android.support.v7.recyclerview.test.R;
53d7d27e9ebe5c7325e67e1a8af265378bd2056cadChet Haase
540a017072206f93474ccd2706e7983c2ff778b904Yigit Boyarimport static org.junit.Assert.*;
550a017072206f93474ccd2706e7983c2ff778b904Yigit Boyar
5666826566020afc8d11f183cf3fe443ac0a022384Yigit Boyarimport static java.util.concurrent.TimeUnit.SECONDS;
5766826566020afc8d11f183cf3fe443ac0a022384Yigit Boyar
580a017072206f93474ccd2706e7983c2ff778b904Yigit Boyarabstract public class BaseRecyclerViewInstrumentationTest {
59d7d27e9ebe5c7325e67e1a8af265378bd2056cadChet Haase
60d7d27e9ebe5c7325e67e1a8af265378bd2056cadChet Haase    private static final String TAG = "RecyclerViewTest";
61d7d27e9ebe5c7325e67e1a8af265378bd2056cadChet Haase
62b5f8b4fecb531aee6b359b4968409410aba90817Yigit Boyar    private boolean mDebug;
63b5f8b4fecb531aee6b359b4968409410aba90817Yigit Boyar
64b5f8b4fecb531aee6b359b4968409410aba90817Yigit Boyar    protected RecyclerView mRecyclerView;
65d7d27e9ebe5c7325e67e1a8af265378bd2056cadChet Haase
668ae76f91527ce850f155ce960fb9068bcd5d49f9Yigit Boyar    protected AdapterHelper mAdapterHelper;
678ae76f91527ce850f155ce960fb9068bcd5d49f9Yigit Boyar
6866826566020afc8d11f183cf3fe443ac0a022384Yigit Boyar    private Throwable mMainThreadException;
6966826566020afc8d11f183cf3fe443ac0a022384Yigit Boyar
7066826566020afc8d11f183cf3fe443ac0a022384Yigit Boyar    private boolean mIgnoreMainThreadException = false;
717e9da1a2629b657d56e2f54abd18c6249f3be037Yigit Boyar
72e05fbd9cfe05496e82a3abe19e07e8745985e9a5Yigit Boyar    Thread mInstrumentationThread;
73e05fbd9cfe05496e82a3abe19e07e8745985e9a5Yigit Boyar
740a017072206f93474ccd2706e7983c2ff778b904Yigit Boyar    @Rule
75959f4c0fac89425a8a9842e82fc180ec736fffacYigit Boyar    public ActivityTestRule<TestActivity> mActivityRule = new SameActivityTestRule() {
76959f4c0fac89425a8a9842e82fc180ec736fffacYigit Boyar        @Override
77959f4c0fac89425a8a9842e82fc180ec736fffacYigit Boyar        public boolean canReUseActivity() {
78959f4c0fac89425a8a9842e82fc180ec736fffacYigit Boyar            return BaseRecyclerViewInstrumentationTest.this.canReUseActivity();
79959f4c0fac89425a8a9842e82fc180ec736fffacYigit Boyar        }
80959f4c0fac89425a8a9842e82fc180ec736fffacYigit Boyar    };
810a017072206f93474ccd2706e7983c2ff778b904Yigit Boyar
82d7d27e9ebe5c7325e67e1a8af265378bd2056cadChet Haase    public BaseRecyclerViewInstrumentationTest() {
83b5f8b4fecb531aee6b359b4968409410aba90817Yigit Boyar        this(false);
84b5f8b4fecb531aee6b359b4968409410aba90817Yigit Boyar    }
85b5f8b4fecb531aee6b359b4968409410aba90817Yigit Boyar
86b5f8b4fecb531aee6b359b4968409410aba90817Yigit Boyar    public BaseRecyclerViewInstrumentationTest(boolean debug) {
87b5f8b4fecb531aee6b359b4968409410aba90817Yigit Boyar        mDebug = debug;
88b5f8b4fecb531aee6b359b4968409410aba90817Yigit Boyar    }
89b5f8b4fecb531aee6b359b4968409410aba90817Yigit Boyar
907e9da1a2629b657d56e2f54abd18c6249f3be037Yigit Boyar    void checkForMainThreadException() throws Throwable {
9166826566020afc8d11f183cf3fe443ac0a022384Yigit Boyar        if (!mIgnoreMainThreadException && mMainThreadException != null) {
9266826566020afc8d11f183cf3fe443ac0a022384Yigit Boyar            throw mMainThreadException;
937e9da1a2629b657d56e2f54abd18c6249f3be037Yigit Boyar        }
947e9da1a2629b657d56e2f54abd18c6249f3be037Yigit Boyar    }
957e9da1a2629b657d56e2f54abd18c6249f3be037Yigit Boyar
9666826566020afc8d11f183cf3fe443ac0a022384Yigit Boyar    public void setIgnoreMainThreadException(boolean ignoreMainThreadException) {
9766826566020afc8d11f183cf3fe443ac0a022384Yigit Boyar        mIgnoreMainThreadException = ignoreMainThreadException;
9866826566020afc8d11f183cf3fe443ac0a022384Yigit Boyar    }
9966826566020afc8d11f183cf3fe443ac0a022384Yigit Boyar
10066826566020afc8d11f183cf3fe443ac0a022384Yigit Boyar    public Throwable getMainThreadException() {
10166826566020afc8d11f183cf3fe443ac0a022384Yigit Boyar        return mMainThreadException;
10266826566020afc8d11f183cf3fe443ac0a022384Yigit Boyar    }
10366826566020afc8d11f183cf3fe443ac0a022384Yigit Boyar
1040a017072206f93474ccd2706e7983c2ff778b904Yigit Boyar    protected TestActivity getActivity() {
1050a017072206f93474ccd2706e7983c2ff778b904Yigit Boyar        return mActivityRule.getActivity();
1060a017072206f93474ccd2706e7983c2ff778b904Yigit Boyar    }
1070a017072206f93474ccd2706e7983c2ff778b904Yigit Boyar
1080a017072206f93474ccd2706e7983c2ff778b904Yigit Boyar    @Before
1090a017072206f93474ccd2706e7983c2ff778b904Yigit Boyar    public final void setUpInsThread() throws Exception {
110e05fbd9cfe05496e82a3abe19e07e8745985e9a5Yigit Boyar        mInstrumentationThread = Thread.currentThread();
111e05fbd9cfe05496e82a3abe19e07e8745985e9a5Yigit Boyar    }
112e05fbd9cfe05496e82a3abe19e07e8745985e9a5Yigit Boyar
113e2437126f0225135891544315ff72e54c7de0f59Yigit Boyar    void setHasTransientState(final View view, final boolean value) {
114e2437126f0225135891544315ff72e54c7de0f59Yigit Boyar        try {
115e2437126f0225135891544315ff72e54c7de0f59Yigit Boyar            runTestOnUiThread(new Runnable() {
116e2437126f0225135891544315ff72e54c7de0f59Yigit Boyar                @Override
117e2437126f0225135891544315ff72e54c7de0f59Yigit Boyar                public void run() {
118e2437126f0225135891544315ff72e54c7de0f59Yigit Boyar                    ViewCompat.setHasTransientState(view, value);
119e2437126f0225135891544315ff72e54c7de0f59Yigit Boyar                }
120e2437126f0225135891544315ff72e54c7de0f59Yigit Boyar            });
121e2437126f0225135891544315ff72e54c7de0f59Yigit Boyar        } catch (Throwable throwable) {
122e2437126f0225135891544315ff72e54c7de0f59Yigit Boyar            Log.e(TAG, "", throwable);
123e2437126f0225135891544315ff72e54c7de0f59Yigit Boyar        }
124e2437126f0225135891544315ff72e54c7de0f59Yigit Boyar    }
125e2437126f0225135891544315ff72e54c7de0f59Yigit Boyar
126959f4c0fac89425a8a9842e82fc180ec736fffacYigit Boyar    public boolean canReUseActivity() {
127959f4c0fac89425a8a9842e82fc180ec736fffacYigit Boyar        return true;
128959f4c0fac89425a8a9842e82fc180ec736fffacYigit Boyar    }
129959f4c0fac89425a8a9842e82fc180ec736fffacYigit Boyar
1302bf89a9bae2b6110a77cf0b3754b567bb07d12a5Yigit Boyar    protected void enableAccessibility()
1312bf89a9bae2b6110a77cf0b3754b567bb07d12a5Yigit Boyar            throws NoSuchMethodException, InvocationTargetException, IllegalAccessException {
1322bf89a9bae2b6110a77cf0b3754b567bb07d12a5Yigit Boyar        Method getUIAutomation = Instrumentation.class.getMethod("getUiAutomation");
1330a017072206f93474ccd2706e7983c2ff778b904Yigit Boyar        getUIAutomation.invoke(InstrumentationRegistry.getInstrumentation());
1342bf89a9bae2b6110a77cf0b3754b567bb07d12a5Yigit Boyar    }
1352bf89a9bae2b6110a77cf0b3754b567bb07d12a5Yigit Boyar
1360b1059b711009601ff08354e2df6bfc45266e80aYigit Boyar    void setAdapter(final RecyclerView.Adapter adapter) throws Throwable {
1370b1059b711009601ff08354e2df6bfc45266e80aYigit Boyar        runTestOnUiThread(new Runnable() {
1380b1059b711009601ff08354e2df6bfc45266e80aYigit Boyar            @Override
1390b1059b711009601ff08354e2df6bfc45266e80aYigit Boyar            public void run() {
1400b1059b711009601ff08354e2df6bfc45266e80aYigit Boyar                mRecyclerView.setAdapter(adapter);
1410b1059b711009601ff08354e2df6bfc45266e80aYigit Boyar            }
1420b1059b711009601ff08354e2df6bfc45266e80aYigit Boyar        });
1430b1059b711009601ff08354e2df6bfc45266e80aYigit Boyar    }
1440b1059b711009601ff08354e2df6bfc45266e80aYigit Boyar
145d8d42d52f8ae53107ffa849b86496650182e24b8Yigit Boyar    public View focusSearch(final View focused, final int direction)
146f89e1b82c74b7b85df3a349340a643f62fc5bfa1Yigit Boyar            throws Throwable {
147d8d42d52f8ae53107ffa849b86496650182e24b8Yigit Boyar        final View[] result = new View[1];
148f89e1b82c74b7b85df3a349340a643f62fc5bfa1Yigit Boyar        runTestOnUiThread(new Runnable() {
149f89e1b82c74b7b85df3a349340a643f62fc5bfa1Yigit Boyar            @Override
150f89e1b82c74b7b85df3a349340a643f62fc5bfa1Yigit Boyar            public void run() {
151f89e1b82c74b7b85df3a349340a643f62fc5bfa1Yigit Boyar                View view = focused.focusSearch(direction);
152f89e1b82c74b7b85df3a349340a643f62fc5bfa1Yigit Boyar                if (view != null && view != focused) {
153f89e1b82c74b7b85df3a349340a643f62fc5bfa1Yigit Boyar                    view.requestFocus();
154f89e1b82c74b7b85df3a349340a643f62fc5bfa1Yigit Boyar                }
155d8d42d52f8ae53107ffa849b86496650182e24b8Yigit Boyar                result[0] = view;
156f89e1b82c74b7b85df3a349340a643f62fc5bfa1Yigit Boyar            }
157f89e1b82c74b7b85df3a349340a643f62fc5bfa1Yigit Boyar        });
158d8d42d52f8ae53107ffa849b86496650182e24b8Yigit Boyar        return result[0];
159f89e1b82c74b7b85df3a349340a643f62fc5bfa1Yigit Boyar    }
160f89e1b82c74b7b85df3a349340a643f62fc5bfa1Yigit Boyar
161e71a1df9b3c0e1bd3c21a1b3dd20a41790d4a950Yigit Boyar    protected WrappedRecyclerView inflateWrappedRV() {
162e71a1df9b3c0e1bd3c21a1b3dd20a41790d4a950Yigit Boyar        return (WrappedRecyclerView)
163e71a1df9b3c0e1bd3c21a1b3dd20a41790d4a950Yigit Boyar                LayoutInflater.from(getActivity()).inflate(R.layout.wrapped_test_rv,
164e71a1df9b3c0e1bd3c21a1b3dd20a41790d4a950Yigit Boyar                        getRecyclerViewContainer(), false);
165e71a1df9b3c0e1bd3c21a1b3dd20a41790d4a950Yigit Boyar    }
166e71a1df9b3c0e1bd3c21a1b3dd20a41790d4a950Yigit Boyar
1670b1059b711009601ff08354e2df6bfc45266e80aYigit Boyar    void swapAdapter(final RecyclerView.Adapter adapter,
1680b1059b711009601ff08354e2df6bfc45266e80aYigit Boyar            final boolean removeAndRecycleExistingViews) throws Throwable {
1690b1059b711009601ff08354e2df6bfc45266e80aYigit Boyar        runTestOnUiThread(new Runnable() {
1700b1059b711009601ff08354e2df6bfc45266e80aYigit Boyar            @Override
1710b1059b711009601ff08354e2df6bfc45266e80aYigit Boyar            public void run() {
172824a7db96feacf3b68f56993ed72712bc935153eYigit Boyar                try {
173824a7db96feacf3b68f56993ed72712bc935153eYigit Boyar                    mRecyclerView.swapAdapter(adapter, removeAndRecycleExistingViews);
174824a7db96feacf3b68f56993ed72712bc935153eYigit Boyar                } catch (Throwable t) {
175824a7db96feacf3b68f56993ed72712bc935153eYigit Boyar                    postExceptionToInstrumentation(t);
176824a7db96feacf3b68f56993ed72712bc935153eYigit Boyar                }
1770b1059b711009601ff08354e2df6bfc45266e80aYigit Boyar            }
1780b1059b711009601ff08354e2df6bfc45266e80aYigit Boyar        });
179824a7db96feacf3b68f56993ed72712bc935153eYigit Boyar        checkForMainThreadException();
1800b1059b711009601ff08354e2df6bfc45266e80aYigit Boyar    }
1810b1059b711009601ff08354e2df6bfc45266e80aYigit Boyar
1827e9da1a2629b657d56e2f54abd18c6249f3be037Yigit Boyar    void postExceptionToInstrumentation(Throwable t) {
183e05fbd9cfe05496e82a3abe19e07e8745985e9a5Yigit Boyar        if (mInstrumentationThread == Thread.currentThread()) {
184e05fbd9cfe05496e82a3abe19e07e8745985e9a5Yigit Boyar            throw new RuntimeException(t);
185e05fbd9cfe05496e82a3abe19e07e8745985e9a5Yigit Boyar        }
18666826566020afc8d11f183cf3fe443ac0a022384Yigit Boyar        if (mMainThreadException != null) {
1876e83751247c5be0211d7bffaf057431c03dfef38Yigit Boyar            Log.e(TAG, "receiving another main thread exception. dropping.", t);
1886e83751247c5be0211d7bffaf057431c03dfef38Yigit Boyar        } else {
18990ca3086dbf66ad6bb8840e46ec8524a705e1c18Yigit Boyar            Log.e(TAG, "captured exception on main thread", t);
19066826566020afc8d11f183cf3fe443ac0a022384Yigit Boyar            mMainThreadException = t;
1916e83751247c5be0211d7bffaf057431c03dfef38Yigit Boyar        }
1926e83751247c5be0211d7bffaf057431c03dfef38Yigit Boyar
1937e9da1a2629b657d56e2f54abd18c6249f3be037Yigit Boyar        if (mRecyclerView != null && mRecyclerView
1947e9da1a2629b657d56e2f54abd18c6249f3be037Yigit Boyar                .getLayoutManager() instanceof TestLayoutManager) {
1957e9da1a2629b657d56e2f54abd18c6249f3be037Yigit Boyar            TestLayoutManager lm = (TestLayoutManager) mRecyclerView.getLayoutManager();
1967e9da1a2629b657d56e2f54abd18c6249f3be037Yigit Boyar            // finish all layouts so that we get the correct exception
1977f5e94985f795f81884334ca2752a0469a998762Yigit Boyar            if (lm.layoutLatch != null) {
1987f5e94985f795f81884334ca2752a0469a998762Yigit Boyar                while (lm.layoutLatch.getCount() > 0) {
1997f5e94985f795f81884334ca2752a0469a998762Yigit Boyar                    lm.layoutLatch.countDown();
2007f5e94985f795f81884334ca2752a0469a998762Yigit Boyar                }
2017e9da1a2629b657d56e2f54abd18c6249f3be037Yigit Boyar            }
2027e9da1a2629b657d56e2f54abd18c6249f3be037Yigit Boyar        }
2037e9da1a2629b657d56e2f54abd18c6249f3be037Yigit Boyar    }
2047e9da1a2629b657d56e2f54abd18c6249f3be037Yigit Boyar
2050a017072206f93474ccd2706e7983c2ff778b904Yigit Boyar    public Instrumentation getInstrumentation() {
2060a017072206f93474ccd2706e7983c2ff778b904Yigit Boyar        return InstrumentationRegistry.getInstrumentation();
2070a017072206f93474ccd2706e7983c2ff778b904Yigit Boyar    }
2080a017072206f93474ccd2706e7983c2ff778b904Yigit Boyar
2090a017072206f93474ccd2706e7983c2ff778b904Yigit Boyar    @After
2100a017072206f93474ccd2706e7983c2ff778b904Yigit Boyar    public final void tearDown() throws Exception {
2118ae76f91527ce850f155ce960fb9068bcd5d49f9Yigit Boyar        if (mRecyclerView != null) {
2128ae76f91527ce850f155ce960fb9068bcd5d49f9Yigit Boyar            try {
2138ae76f91527ce850f155ce960fb9068bcd5d49f9Yigit Boyar                removeRecyclerView();
2148ae76f91527ce850f155ce960fb9068bcd5d49f9Yigit Boyar            } catch (Throwable throwable) {
2158ae76f91527ce850f155ce960fb9068bcd5d49f9Yigit Boyar                throwable.printStackTrace();
2168ae76f91527ce850f155ce960fb9068bcd5d49f9Yigit Boyar            }
2178ae76f91527ce850f155ce960fb9068bcd5d49f9Yigit Boyar        }
2188ae76f91527ce850f155ce960fb9068bcd5d49f9Yigit Boyar        getInstrumentation().waitForIdleSync();
219ca9ce7ccdccf6b6a2a81da73dd1e6425d90e4b23Yigit Boyar
2206e83751247c5be0211d7bffaf057431c03dfef38Yigit Boyar        try {
2216e83751247c5be0211d7bffaf057431c03dfef38Yigit Boyar            checkForMainThreadException();
2226e83751247c5be0211d7bffaf057431c03dfef38Yigit Boyar        } catch (Exception e) {
2236e83751247c5be0211d7bffaf057431c03dfef38Yigit Boyar            throw e;
2246e83751247c5be0211d7bffaf057431c03dfef38Yigit Boyar        } catch (Throwable throwable) {
225121ba9616e5bed44d2490f1744f7b6a9d3e79866Yigit Boyar            throw new Exception(Log.getStackTraceString(throwable));
2266e83751247c5be0211d7bffaf057431c03dfef38Yigit Boyar        }
2278ae76f91527ce850f155ce960fb9068bcd5d49f9Yigit Boyar    }
2288ae76f91527ce850f155ce960fb9068bcd5d49f9Yigit Boyar
229c50c4cad31d73e574b27bb3d7581542975e37263Yigit Boyar    public Rect getDecoratedRecyclerViewBounds() {
230c50c4cad31d73e574b27bb3d7581542975e37263Yigit Boyar        return new Rect(
231c50c4cad31d73e574b27bb3d7581542975e37263Yigit Boyar                mRecyclerView.getPaddingLeft(),
232c50c4cad31d73e574b27bb3d7581542975e37263Yigit Boyar                mRecyclerView.getPaddingTop(),
23329c349ed8863cc15c2580465227c37771ac49576Yigit Boyar                mRecyclerView.getWidth() - mRecyclerView.getPaddingRight(),
23429c349ed8863cc15c2580465227c37771ac49576Yigit Boyar                mRecyclerView.getHeight() - mRecyclerView.getPaddingBottom()
235c50c4cad31d73e574b27bb3d7581542975e37263Yigit Boyar        );
236c50c4cad31d73e574b27bb3d7581542975e37263Yigit Boyar    }
237c50c4cad31d73e574b27bb3d7581542975e37263Yigit Boyar
238b5f8b4fecb531aee6b359b4968409410aba90817Yigit Boyar    public void removeRecyclerView() throws Throwable {
239d7e2f2ab1d253133fa54f309e74a7ee384c33971Yigit Boyar        if (mRecyclerView == null) {
240d7e2f2ab1d253133fa54f309e74a7ee384c33971Yigit Boyar            return;
241d7e2f2ab1d253133fa54f309e74a7ee384c33971Yigit Boyar        }
24290ca3086dbf66ad6bb8840e46ec8524a705e1c18Yigit Boyar        if (!isMainThread()) {
24390ca3086dbf66ad6bb8840e46ec8524a705e1c18Yigit Boyar            getInstrumentation().waitForIdleSync();
24490ca3086dbf66ad6bb8840e46ec8524a705e1c18Yigit Boyar        }
245b5f8b4fecb531aee6b359b4968409410aba90817Yigit Boyar        runTestOnUiThread(new Runnable() {
246b5f8b4fecb531aee6b359b4968409410aba90817Yigit Boyar            @Override
247b5f8b4fecb531aee6b359b4968409410aba90817Yigit Boyar            public void run() {
248abd0fbfcdd5b438909f126392eca5978b5939b6dYigit Boyar                try {
24966826566020afc8d11f183cf3fe443ac0a022384Yigit Boyar                    // do not run validation if we already have an error
25066826566020afc8d11f183cf3fe443ac0a022384Yigit Boyar                    if (mMainThreadException == null) {
25166826566020afc8d11f183cf3fe443ac0a022384Yigit Boyar                        final RecyclerView.Adapter adapter = mRecyclerView.getAdapter();
25266826566020afc8d11f183cf3fe443ac0a022384Yigit Boyar                        if (adapter instanceof AttachDetachCountingAdapter) {
25366826566020afc8d11f183cf3fe443ac0a022384Yigit Boyar                            ((AttachDetachCountingAdapter) adapter).getCounter()
25466826566020afc8d11f183cf3fe443ac0a022384Yigit Boyar                                    .validateRemaining(mRecyclerView);
25566826566020afc8d11f183cf3fe443ac0a022384Yigit Boyar                        }
256abd0fbfcdd5b438909f126392eca5978b5939b6dYigit Boyar                    }
257a90fb62f06861beb3af9f9b3356ef0bb0685547cYigit Boyar                    getActivity().getContainer().removeAllViews();
258abd0fbfcdd5b438909f126392eca5978b5939b6dYigit Boyar                } catch (Throwable t) {
259abd0fbfcdd5b438909f126392eca5978b5939b6dYigit Boyar                    postExceptionToInstrumentation(t);
26090ca3086dbf66ad6bb8840e46ec8524a705e1c18Yigit Boyar                }
261b5f8b4fecb531aee6b359b4968409410aba90817Yigit Boyar            }
262b5f8b4fecb531aee6b359b4968409410aba90817Yigit Boyar        });
26390ca3086dbf66ad6bb8840e46ec8524a705e1c18Yigit Boyar        mRecyclerView = null;
264b5f8b4fecb531aee6b359b4968409410aba90817Yigit Boyar    }
265b5f8b4fecb531aee6b359b4968409410aba90817Yigit Boyar
2669051a368c00c40f8679ae67147b9f5af6f93638dYigit Boyar    void waitForAnimations(int seconds) throws Throwable {
2679051a368c00c40f8679ae67147b9f5af6f93638dYigit Boyar        final CountDownLatch latch = new CountDownLatch(1);
2689051a368c00c40f8679ae67147b9f5af6f93638dYigit Boyar        runTestOnUiThread(new Runnable() {
2699051a368c00c40f8679ae67147b9f5af6f93638dYigit Boyar            @Override
2709051a368c00c40f8679ae67147b9f5af6f93638dYigit Boyar            public void run() {
2719051a368c00c40f8679ae67147b9f5af6f93638dYigit Boyar                mRecyclerView.mItemAnimator
2729051a368c00c40f8679ae67147b9f5af6f93638dYigit Boyar                        .isRunning(new RecyclerView.ItemAnimator.ItemAnimatorFinishedListener() {
2739051a368c00c40f8679ae67147b9f5af6f93638dYigit Boyar                            @Override
2749051a368c00c40f8679ae67147b9f5af6f93638dYigit Boyar                            public void onAnimationsFinished() {
2759051a368c00c40f8679ae67147b9f5af6f93638dYigit Boyar                                latch.countDown();
2769051a368c00c40f8679ae67147b9f5af6f93638dYigit Boyar                            }
2779051a368c00c40f8679ae67147b9f5af6f93638dYigit Boyar                        });
2789051a368c00c40f8679ae67147b9f5af6f93638dYigit Boyar            }
2799051a368c00c40f8679ae67147b9f5af6f93638dYigit Boyar        });
2809051a368c00c40f8679ae67147b9f5af6f93638dYigit Boyar
2819051a368c00c40f8679ae67147b9f5af6f93638dYigit Boyar        assertTrue("animations didn't finish on expected time of " + seconds + " seconds",
2829051a368c00c40f8679ae67147b9f5af6f93638dYigit Boyar                latch.await(seconds, TimeUnit.SECONDS));
283504c54ea52c1b2aae6f8f4ae128f1dcaac7e3f6aYigit Boyar    }
284504c54ea52c1b2aae6f8f4ae128f1dcaac7e3f6aYigit Boyar
285959f4c0fac89425a8a9842e82fc180ec736fffacYigit Boyar    public void waitForIdleScroll(final RecyclerView recyclerView) throws Throwable {
286959f4c0fac89425a8a9842e82fc180ec736fffacYigit Boyar        final CountDownLatch latch = new CountDownLatch(1);
287959f4c0fac89425a8a9842e82fc180ec736fffacYigit Boyar        runTestOnUiThread(new Runnable() {
288959f4c0fac89425a8a9842e82fc180ec736fffacYigit Boyar            @Override
289959f4c0fac89425a8a9842e82fc180ec736fffacYigit Boyar            public void run() {
290959f4c0fac89425a8a9842e82fc180ec736fffacYigit Boyar                RecyclerView.OnScrollListener listener = new RecyclerView.OnScrollListener() {
291959f4c0fac89425a8a9842e82fc180ec736fffacYigit Boyar                    @Override
292959f4c0fac89425a8a9842e82fc180ec736fffacYigit Boyar                    public void onScrollStateChanged(RecyclerView recyclerView, int newState) {
293959f4c0fac89425a8a9842e82fc180ec736fffacYigit Boyar                        if (newState == SCROLL_STATE_IDLE) {
294959f4c0fac89425a8a9842e82fc180ec736fffacYigit Boyar                            latch.countDown();
295959f4c0fac89425a8a9842e82fc180ec736fffacYigit Boyar                            recyclerView.removeOnScrollListener(this);
296959f4c0fac89425a8a9842e82fc180ec736fffacYigit Boyar                        }
297959f4c0fac89425a8a9842e82fc180ec736fffacYigit Boyar                    }
298959f4c0fac89425a8a9842e82fc180ec736fffacYigit Boyar                };
299959f4c0fac89425a8a9842e82fc180ec736fffacYigit Boyar                if (recyclerView.getScrollState() == SCROLL_STATE_IDLE) {
300959f4c0fac89425a8a9842e82fc180ec736fffacYigit Boyar                    latch.countDown();
301959f4c0fac89425a8a9842e82fc180ec736fffacYigit Boyar                } else {
302959f4c0fac89425a8a9842e82fc180ec736fffacYigit Boyar                    recyclerView.addOnScrollListener(listener);
303959f4c0fac89425a8a9842e82fc180ec736fffacYigit Boyar                }
304959f4c0fac89425a8a9842e82fc180ec736fffacYigit Boyar            }
305959f4c0fac89425a8a9842e82fc180ec736fffacYigit Boyar        });
306959f4c0fac89425a8a9842e82fc180ec736fffacYigit Boyar        assertTrue("should go idle in 10 seconds", latch.await(10, TimeUnit.SECONDS));
307959f4c0fac89425a8a9842e82fc180ec736fffacYigit Boyar    }
308959f4c0fac89425a8a9842e82fc180ec736fffacYigit Boyar
309959f4c0fac89425a8a9842e82fc180ec736fffacYigit Boyar    public boolean requestFocus(final View view, boolean waitForScroll) throws Throwable {
310542f1260934df280985294eaef1ec8469863281fYigit Boyar        final boolean[] result = new boolean[1];
311f89e1b82c74b7b85df3a349340a643f62fc5bfa1Yigit Boyar        try {
312f89e1b82c74b7b85df3a349340a643f62fc5bfa1Yigit Boyar            runTestOnUiThread(new Runnable() {
313f89e1b82c74b7b85df3a349340a643f62fc5bfa1Yigit Boyar                @Override
314f89e1b82c74b7b85df3a349340a643f62fc5bfa1Yigit Boyar                public void run() {
315f89e1b82c74b7b85df3a349340a643f62fc5bfa1Yigit Boyar                    result[0] = view.requestFocus();
316f89e1b82c74b7b85df3a349340a643f62fc5bfa1Yigit Boyar                }
317f89e1b82c74b7b85df3a349340a643f62fc5bfa1Yigit Boyar            });
318f89e1b82c74b7b85df3a349340a643f62fc5bfa1Yigit Boyar        } catch (Throwable throwable) {
319f89e1b82c74b7b85df3a349340a643f62fc5bfa1Yigit Boyar            fail(throwable.getMessage());
320f89e1b82c74b7b85df3a349340a643f62fc5bfa1Yigit Boyar        }
321959f4c0fac89425a8a9842e82fc180ec736fffacYigit Boyar        if (waitForScroll && result[0]) {
322959f4c0fac89425a8a9842e82fc180ec736fffacYigit Boyar            waitForIdleScroll(mRecyclerView);
323959f4c0fac89425a8a9842e82fc180ec736fffacYigit Boyar        }
324542f1260934df280985294eaef1ec8469863281fYigit Boyar        return result[0];
325542f1260934df280985294eaef1ec8469863281fYigit Boyar    }
326542f1260934df280985294eaef1ec8469863281fYigit Boyar
327b5f8b4fecb531aee6b359b4968409410aba90817Yigit Boyar    public void setRecyclerView(final RecyclerView recyclerView) throws Throwable {
3280b1059b711009601ff08354e2df6bfc45266e80aYigit Boyar        setRecyclerView(recyclerView, true);
3290b1059b711009601ff08354e2df6bfc45266e80aYigit Boyar    }
3300b1059b711009601ff08354e2df6bfc45266e80aYigit Boyar    public void setRecyclerView(final RecyclerView recyclerView, boolean assignDummyPool)
3310b1059b711009601ff08354e2df6bfc45266e80aYigit Boyar            throws Throwable {
332f129f1b050d2542a91fe8175eac30183beb07b41Chris Banes        setRecyclerView(recyclerView, assignDummyPool, true);
333e37d2e63f16bc5c2938e372238ba54679a9e578eYigit Boyar    }
334e37d2e63f16bc5c2938e372238ba54679a9e578eYigit Boyar    public void setRecyclerView(final RecyclerView recyclerView, boolean assignDummyPool,
335e37d2e63f16bc5c2938e372238ba54679a9e578eYigit Boyar            boolean addPositionCheckItemAnimator)
336e37d2e63f16bc5c2938e372238ba54679a9e578eYigit Boyar            throws Throwable {
337b5f8b4fecb531aee6b359b4968409410aba90817Yigit Boyar        mRecyclerView = recyclerView;
3380b1059b711009601ff08354e2df6bfc45266e80aYigit Boyar        if (assignDummyPool) {
3390b1059b711009601ff08354e2df6bfc45266e80aYigit Boyar            RecyclerView.RecycledViewPool pool = new RecyclerView.RecycledViewPool() {
3400b1059b711009601ff08354e2df6bfc45266e80aYigit Boyar                @Override
3410b1059b711009601ff08354e2df6bfc45266e80aYigit Boyar                public RecyclerView.ViewHolder getRecycledView(int viewType) {
3420b1059b711009601ff08354e2df6bfc45266e80aYigit Boyar                    RecyclerView.ViewHolder viewHolder = super.getRecycledView(viewType);
3430b1059b711009601ff08354e2df6bfc45266e80aYigit Boyar                    if (viewHolder == null) {
3440b1059b711009601ff08354e2df6bfc45266e80aYigit Boyar                        return null;
3450b1059b711009601ff08354e2df6bfc45266e80aYigit Boyar                    }
3460b1059b711009601ff08354e2df6bfc45266e80aYigit Boyar                    viewHolder.addFlags(RecyclerView.ViewHolder.FLAG_BOUND);
3470b1059b711009601ff08354e2df6bfc45266e80aYigit Boyar                    viewHolder.mPosition = 200;
3480b1059b711009601ff08354e2df6bfc45266e80aYigit Boyar                    viewHolder.mOldPosition = 300;
3490b1059b711009601ff08354e2df6bfc45266e80aYigit Boyar                    viewHolder.mPreLayoutPosition = 500;
3500b1059b711009601ff08354e2df6bfc45266e80aYigit Boyar                    return viewHolder;
3518da5bba529a2fe9bd2aafab28010e11ec4de93c7Yigit Boyar                }
3528da5bba529a2fe9bd2aafab28010e11ec4de93c7Yigit Boyar
3530b1059b711009601ff08354e2df6bfc45266e80aYigit Boyar                @Override
3540b1059b711009601ff08354e2df6bfc45266e80aYigit Boyar                public void putRecycledView(RecyclerView.ViewHolder scrap) {
355619ac016831a5ad75c2e0921c9036b6f030a9e63Yigit Boyar                    assertNull(scrap.mOwnerRecyclerView);
3560b1059b711009601ff08354e2df6bfc45266e80aYigit Boyar                    super.putRecycledView(scrap);
3570b1059b711009601ff08354e2df6bfc45266e80aYigit Boyar                }
3580b1059b711009601ff08354e2df6bfc45266e80aYigit Boyar            };
3590b1059b711009601ff08354e2df6bfc45266e80aYigit Boyar            mRecyclerView.setRecycledViewPool(pool);
3600b1059b711009601ff08354e2df6bfc45266e80aYigit Boyar        }
361e37d2e63f16bc5c2938e372238ba54679a9e578eYigit Boyar        if (addPositionCheckItemAnimator) {
362e37d2e63f16bc5c2938e372238ba54679a9e578eYigit Boyar            mRecyclerView.addItemDecoration(new RecyclerView.ItemDecoration() {
363e37d2e63f16bc5c2938e372238ba54679a9e578eYigit Boyar                @Override
364e37d2e63f16bc5c2938e372238ba54679a9e578eYigit Boyar                public void getItemOffsets(Rect outRect, View view, RecyclerView parent,
365e37d2e63f16bc5c2938e372238ba54679a9e578eYigit Boyar                        RecyclerView.State state) {
366e37d2e63f16bc5c2938e372238ba54679a9e578eYigit Boyar                    RecyclerView.ViewHolder vh = parent.getChildViewHolder(view);
367e37d2e63f16bc5c2938e372238ba54679a9e578eYigit Boyar                    if (!vh.isRemoved()) {
368e37d2e63f16bc5c2938e372238ba54679a9e578eYigit Boyar                        assertNotSame("If getItemOffsets is called, child should have a valid"
3690a017072206f93474ccd2706e7983c2ff778b904Yigit Boyar                                        + " adapter position unless it is removed : " + vh,
3700a017072206f93474ccd2706e7983c2ff778b904Yigit Boyar                                vh.getAdapterPosition(), RecyclerView.NO_POSITION);
371e37d2e63f16bc5c2938e372238ba54679a9e578eYigit Boyar                    }
372e37d2e63f16bc5c2938e372238ba54679a9e578eYigit Boyar                }
373e37d2e63f16bc5c2938e372238ba54679a9e578eYigit Boyar            });
374e37d2e63f16bc5c2938e372238ba54679a9e578eYigit Boyar        }
3758ae76f91527ce850f155ce960fb9068bcd5d49f9Yigit Boyar        mAdapterHelper = recyclerView.mAdapterHelper;
376b5f8b4fecb531aee6b359b4968409410aba90817Yigit Boyar        runTestOnUiThread(new Runnable() {
377b5f8b4fecb531aee6b359b4968409410aba90817Yigit Boyar            @Override
378b5f8b4fecb531aee6b359b4968409410aba90817Yigit Boyar            public void run() {
379a90fb62f06861beb3af9f9b3356ef0bb0685547cYigit Boyar                getActivity().getContainer().addView(recyclerView);
380b5f8b4fecb531aee6b359b4968409410aba90817Yigit Boyar            }
381b5f8b4fecb531aee6b359b4968409410aba90817Yigit Boyar        });
382b5f8b4fecb531aee6b359b4968409410aba90817Yigit Boyar    }
383b5f8b4fecb531aee6b359b4968409410aba90817Yigit Boyar
384310e95e1c6dfe4f26ef594233e65e1ff83e0f1ffYigit Boyar    protected FrameLayout getRecyclerViewContainer() {
385a90fb62f06861beb3af9f9b3356ef0bb0685547cYigit Boyar        return getActivity().getContainer();
386310e95e1c6dfe4f26ef594233e65e1ff83e0f1ffYigit Boyar    }
387310e95e1c6dfe4f26ef594233e65e1ff83e0f1ffYigit Boyar
388e2437126f0225135891544315ff72e54c7de0f59Yigit Boyar    public void requestLayoutOnUIThread(final View view) {
389e2437126f0225135891544315ff72e54c7de0f59Yigit Boyar        try {
390e2437126f0225135891544315ff72e54c7de0f59Yigit Boyar            runTestOnUiThread(new Runnable() {
391e2437126f0225135891544315ff72e54c7de0f59Yigit Boyar                @Override
392e2437126f0225135891544315ff72e54c7de0f59Yigit Boyar                public void run() {
393e2437126f0225135891544315ff72e54c7de0f59Yigit Boyar                    view.requestLayout();
394e2437126f0225135891544315ff72e54c7de0f59Yigit Boyar                }
395e2437126f0225135891544315ff72e54c7de0f59Yigit Boyar            });
396e2437126f0225135891544315ff72e54c7de0f59Yigit Boyar        } catch (Throwable throwable) {
397e2437126f0225135891544315ff72e54c7de0f59Yigit Boyar            Log.e(TAG, "", throwable);
398e2437126f0225135891544315ff72e54c7de0f59Yigit Boyar        }
399b5f8b4fecb531aee6b359b4968409410aba90817Yigit Boyar    }
400b5f8b4fecb531aee6b359b4968409410aba90817Yigit Boyar
401e2437126f0225135891544315ff72e54c7de0f59Yigit Boyar    public void scrollBy(final int dt) {
402e2437126f0225135891544315ff72e54c7de0f59Yigit Boyar        try {
403e2437126f0225135891544315ff72e54c7de0f59Yigit Boyar            runTestOnUiThread(new Runnable() {
404e2437126f0225135891544315ff72e54c7de0f59Yigit Boyar                @Override
405e2437126f0225135891544315ff72e54c7de0f59Yigit Boyar                public void run() {
406e2437126f0225135891544315ff72e54c7de0f59Yigit Boyar                    if (mRecyclerView.getLayoutManager().canScrollHorizontally()) {
407e2437126f0225135891544315ff72e54c7de0f59Yigit Boyar                        mRecyclerView.scrollBy(dt, 0);
408e2437126f0225135891544315ff72e54c7de0f59Yigit Boyar                    } else {
409e2437126f0225135891544315ff72e54c7de0f59Yigit Boyar                        mRecyclerView.scrollBy(0, dt);
410e2437126f0225135891544315ff72e54c7de0f59Yigit Boyar                    }
411b5f8b4fecb531aee6b359b4968409410aba90817Yigit Boyar
412e2437126f0225135891544315ff72e54c7de0f59Yigit Boyar                }
413e2437126f0225135891544315ff72e54c7de0f59Yigit Boyar            });
414e2437126f0225135891544315ff72e54c7de0f59Yigit Boyar        } catch (Throwable throwable) {
415e2437126f0225135891544315ff72e54c7de0f59Yigit Boyar            Log.e(TAG, "", throwable);
416e2437126f0225135891544315ff72e54c7de0f59Yigit Boyar        }
417d7d27e9ebe5c7325e67e1a8af265378bd2056cadChet Haase    }
418d7d27e9ebe5c7325e67e1a8af265378bd2056cadChet Haase
4198edcb0bdeaba6931f9d8154f0c81f57da7ddab2aYigit Boyar    void scrollToPosition(final int position) throws Throwable {
4208edcb0bdeaba6931f9d8154f0c81f57da7ddab2aYigit Boyar        runTestOnUiThread(new Runnable() {
4218edcb0bdeaba6931f9d8154f0c81f57da7ddab2aYigit Boyar            @Override
4228edcb0bdeaba6931f9d8154f0c81f57da7ddab2aYigit Boyar            public void run() {
4238edcb0bdeaba6931f9d8154f0c81f57da7ddab2aYigit Boyar                mRecyclerView.getLayoutManager().scrollToPosition(position);
4248edcb0bdeaba6931f9d8154f0c81f57da7ddab2aYigit Boyar            }
4258edcb0bdeaba6931f9d8154f0c81f57da7ddab2aYigit Boyar        });
4268edcb0bdeaba6931f9d8154f0c81f57da7ddab2aYigit Boyar    }
4278edcb0bdeaba6931f9d8154f0c81f57da7ddab2aYigit Boyar
428959f4c0fac89425a8a9842e82fc180ec736fffacYigit Boyar    void smoothScrollToPosition(final int position) throws Throwable {
429959f4c0fac89425a8a9842e82fc180ec736fffacYigit Boyar        smoothScrollToPosition(position, true);
430959f4c0fac89425a8a9842e82fc180ec736fffacYigit Boyar    }
431959f4c0fac89425a8a9842e82fc180ec736fffacYigit Boyar
432959f4c0fac89425a8a9842e82fc180ec736fffacYigit Boyar    void smoothScrollToPosition(final int position, boolean assertArrival) throws Throwable {
433619ac016831a5ad75c2e0921c9036b6f030a9e63Yigit Boyar        if (mDebug) {
434619ac016831a5ad75c2e0921c9036b6f030a9e63Yigit Boyar            Log.d(TAG, "SMOOTH scrolling to " + position);
435619ac016831a5ad75c2e0921c9036b6f030a9e63Yigit Boyar        }
436959f4c0fac89425a8a9842e82fc180ec736fffacYigit Boyar        final CountDownLatch viewAdded = new CountDownLatch(1);
437959f4c0fac89425a8a9842e82fc180ec736fffacYigit Boyar        final RecyclerView.OnChildAttachStateChangeListener listener =
438959f4c0fac89425a8a9842e82fc180ec736fffacYigit Boyar                new RecyclerView.OnChildAttachStateChangeListener() {
439959f4c0fac89425a8a9842e82fc180ec736fffacYigit Boyar                    @Override
440959f4c0fac89425a8a9842e82fc180ec736fffacYigit Boyar                    public void onChildViewAttachedToWindow(View view) {
441959f4c0fac89425a8a9842e82fc180ec736fffacYigit Boyar                        if (position == mRecyclerView.getChildAdapterPosition(view)) {
442959f4c0fac89425a8a9842e82fc180ec736fffacYigit Boyar                            viewAdded.countDown();
443959f4c0fac89425a8a9842e82fc180ec736fffacYigit Boyar                        }
444959f4c0fac89425a8a9842e82fc180ec736fffacYigit Boyar                    }
445959f4c0fac89425a8a9842e82fc180ec736fffacYigit Boyar                    @Override
446959f4c0fac89425a8a9842e82fc180ec736fffacYigit Boyar                    public void onChildViewDetachedFromWindow(View view) {
447959f4c0fac89425a8a9842e82fc180ec736fffacYigit Boyar                    }
448959f4c0fac89425a8a9842e82fc180ec736fffacYigit Boyar                };
449959f4c0fac89425a8a9842e82fc180ec736fffacYigit Boyar        final AtomicBoolean addedListener = new AtomicBoolean(false);
450959f4c0fac89425a8a9842e82fc180ec736fffacYigit Boyar        runTestOnUiThread(new Runnable() {
451959f4c0fac89425a8a9842e82fc180ec736fffacYigit Boyar            @Override
452959f4c0fac89425a8a9842e82fc180ec736fffacYigit Boyar            public void run() {
453959f4c0fac89425a8a9842e82fc180ec736fffacYigit Boyar                RecyclerView.ViewHolder viewHolderForAdapterPosition =
454959f4c0fac89425a8a9842e82fc180ec736fffacYigit Boyar                        mRecyclerView.findViewHolderForAdapterPosition(position);
455959f4c0fac89425a8a9842e82fc180ec736fffacYigit Boyar                if (viewHolderForAdapterPosition != null) {
456959f4c0fac89425a8a9842e82fc180ec736fffacYigit Boyar                    viewAdded.countDown();
457959f4c0fac89425a8a9842e82fc180ec736fffacYigit Boyar                } else {
458959f4c0fac89425a8a9842e82fc180ec736fffacYigit Boyar                    mRecyclerView.addOnChildAttachStateChangeListener(listener);
459959f4c0fac89425a8a9842e82fc180ec736fffacYigit Boyar                    addedListener.set(true);
460959f4c0fac89425a8a9842e82fc180ec736fffacYigit Boyar                }
461959f4c0fac89425a8a9842e82fc180ec736fffacYigit Boyar
462959f4c0fac89425a8a9842e82fc180ec736fffacYigit Boyar            }
463959f4c0fac89425a8a9842e82fc180ec736fffacYigit Boyar        });
4648edcb0bdeaba6931f9d8154f0c81f57da7ddab2aYigit Boyar        runTestOnUiThread(new Runnable() {
4658edcb0bdeaba6931f9d8154f0c81f57da7ddab2aYigit Boyar            @Override
4668edcb0bdeaba6931f9d8154f0c81f57da7ddab2aYigit Boyar            public void run() {
4678edcb0bdeaba6931f9d8154f0c81f57da7ddab2aYigit Boyar                mRecyclerView.smoothScrollToPosition(position);
4688edcb0bdeaba6931f9d8154f0c81f57da7ddab2aYigit Boyar            }
4698edcb0bdeaba6931f9d8154f0c81f57da7ddab2aYigit Boyar        });
470de5f54783555fa2e778c4ed6760472a002b2589bYigit Boyar        getInstrumentation().waitForIdleSync();
471959f4c0fac89425a8a9842e82fc180ec736fffacYigit Boyar        assertThat("should be able to scroll in 10 seconds", !assertArrival ||
472959f4c0fac89425a8a9842e82fc180ec736fffacYigit Boyar                viewAdded.await(10, TimeUnit.SECONDS),
473959f4c0fac89425a8a9842e82fc180ec736fffacYigit Boyar                CoreMatchers.is(true));
474959f4c0fac89425a8a9842e82fc180ec736fffacYigit Boyar        waitForIdleScroll(mRecyclerView);
475619ac016831a5ad75c2e0921c9036b6f030a9e63Yigit Boyar        if (mDebug) {
476619ac016831a5ad75c2e0921c9036b6f030a9e63Yigit Boyar            Log.d(TAG, "SMOOTH scrolling done");
477619ac016831a5ad75c2e0921c9036b6f030a9e63Yigit Boyar        }
478959f4c0fac89425a8a9842e82fc180ec736fffacYigit Boyar        if (addedListener.get()) {
479959f4c0fac89425a8a9842e82fc180ec736fffacYigit Boyar            runTestOnUiThread(new Runnable() {
480959f4c0fac89425a8a9842e82fc180ec736fffacYigit Boyar                @Override
481959f4c0fac89425a8a9842e82fc180ec736fffacYigit Boyar                public void run() {
482959f4c0fac89425a8a9842e82fc180ec736fffacYigit Boyar                    mRecyclerView.removeOnChildAttachStateChangeListener(listener);
483959f4c0fac89425a8a9842e82fc180ec736fffacYigit Boyar                }
484959f4c0fac89425a8a9842e82fc180ec736fffacYigit Boyar            });
485959f4c0fac89425a8a9842e82fc180ec736fffacYigit Boyar        }
486504c54ea52c1b2aae6f8f4ae128f1dcaac7e3f6aYigit Boyar        getInstrumentation().waitForIdleSync();
4878edcb0bdeaba6931f9d8154f0c81f57da7ddab2aYigit Boyar    }
4888edcb0bdeaba6931f9d8154f0c81f57da7ddab2aYigit Boyar
489ef3e37fe088c7d8e614bbc4ed58d6c0d3fa807a1Dake Gu    void freezeLayout(final boolean freeze) throws Throwable {
490ef3e37fe088c7d8e614bbc4ed58d6c0d3fa807a1Dake Gu        runTestOnUiThread(new Runnable() {
491ef3e37fe088c7d8e614bbc4ed58d6c0d3fa807a1Dake Gu            @Override
492ef3e37fe088c7d8e614bbc4ed58d6c0d3fa807a1Dake Gu            public void run() {
493ef3e37fe088c7d8e614bbc4ed58d6c0d3fa807a1Dake Gu                mRecyclerView.setLayoutFrozen(freeze);
494ef3e37fe088c7d8e614bbc4ed58d6c0d3fa807a1Dake Gu            }
495ef3e37fe088c7d8e614bbc4ed58d6c0d3fa807a1Dake Gu        });
496ef3e37fe088c7d8e614bbc4ed58d6c0d3fa807a1Dake Gu    }
497ef3e37fe088c7d8e614bbc4ed58d6c0d3fa807a1Dake Gu
4984bcf88a56f3048a7ead6f423faa8fa08b95c5daeYigit Boyar    public void setVisibility(final View view, final int visibility) throws Throwable {
4994bcf88a56f3048a7ead6f423faa8fa08b95c5daeYigit Boyar        runTestOnUiThread(new Runnable() {
5004bcf88a56f3048a7ead6f423faa8fa08b95c5daeYigit Boyar            @Override
5014bcf88a56f3048a7ead6f423faa8fa08b95c5daeYigit Boyar            public void run() {
5024bcf88a56f3048a7ead6f423faa8fa08b95c5daeYigit Boyar                view.setVisibility(visibility);
5034bcf88a56f3048a7ead6f423faa8fa08b95c5daeYigit Boyar            }
5044bcf88a56f3048a7ead6f423faa8fa08b95c5daeYigit Boyar        });
5054bcf88a56f3048a7ead6f423faa8fa08b95c5daeYigit Boyar    }
5064bcf88a56f3048a7ead6f423faa8fa08b95c5daeYigit Boyar
5073d37afbbf7bf547c1e1c541df9be9696da668884Yigit Boyar    public class TestViewHolder extends RecyclerView.ViewHolder {
508d7d27e9ebe5c7325e67e1a8af265378bd2056cadChet Haase
509115ba0c7b2a14aa4cd0273952195e1d8f6468f87Yigit Boyar        Item mBoundItem;
510d7d27e9ebe5c7325e67e1a8af265378bd2056cadChet Haase
511d7d27e9ebe5c7325e67e1a8af265378bd2056cadChet Haase        public TestViewHolder(View itemView) {
512d7d27e9ebe5c7325e67e1a8af265378bd2056cadChet Haase            super(itemView);
513310e95e1c6dfe4f26ef594233e65e1ff83e0f1ffYigit Boyar            itemView.setFocusable(true);
514d7d27e9ebe5c7325e67e1a8af265378bd2056cadChet Haase        }
515504c54ea52c1b2aae6f8f4ae128f1dcaac7e3f6aYigit Boyar
516504c54ea52c1b2aae6f8f4ae128f1dcaac7e3f6aYigit Boyar        @Override
517504c54ea52c1b2aae6f8f4ae128f1dcaac7e3f6aYigit Boyar        public String toString() {
518115ba0c7b2a14aa4cd0273952195e1d8f6468f87Yigit Boyar            return super.toString() + " item:" + mBoundItem;
519504c54ea52c1b2aae6f8f4ae128f1dcaac7e3f6aYigit Boyar        }
520d7d27e9ebe5c7325e67e1a8af265378bd2056cadChet Haase    }
521da50fde358bd3ef2edb273b3bf0265cf5e84ec6bYigit Boyar    class DumbLayoutManager extends TestLayoutManager {
522da50fde358bd3ef2edb273b3bf0265cf5e84ec6bYigit Boyar        @Override
523da50fde358bd3ef2edb273b3bf0265cf5e84ec6bYigit Boyar        public void onLayoutChildren(RecyclerView.Recycler recycler, RecyclerView.State state) {
524da50fde358bd3ef2edb273b3bf0265cf5e84ec6bYigit Boyar            detachAndScrapAttachedViews(recycler);
525da50fde358bd3ef2edb273b3bf0265cf5e84ec6bYigit Boyar            layoutRange(recycler, 0, state.getItemCount());
526da50fde358bd3ef2edb273b3bf0265cf5e84ec6bYigit Boyar            if (layoutLatch != null) {
527da50fde358bd3ef2edb273b3bf0265cf5e84ec6bYigit Boyar                layoutLatch.countDown();
528da50fde358bd3ef2edb273b3bf0265cf5e84ec6bYigit Boyar            }
529da50fde358bd3ef2edb273b3bf0265cf5e84ec6bYigit Boyar        }
530da50fde358bd3ef2edb273b3bf0265cf5e84ec6bYigit Boyar    }
531e71a1df9b3c0e1bd3c21a1b3dd20a41790d4a950Yigit Boyar    public class TestLayoutManager extends RecyclerView.LayoutManager {
532f129f1b050d2542a91fe8175eac30183beb07b41Chris Banes        int mScrollVerticallyAmount;
533f129f1b050d2542a91fe8175eac30183beb07b41Chris Banes        int mScrollHorizontallyAmount;
534e71a1df9b3c0e1bd3c21a1b3dd20a41790d4a950Yigit Boyar        protected CountDownLatch layoutLatch;
535e3b89d11cf08a0e9aaa8162757246f286c6df2f8Yigit Boyar        private boolean mSupportsPredictive = false;
536d7d27e9ebe5c7325e67e1a8af265378bd2056cadChet Haase
537d7d27e9ebe5c7325e67e1a8af265378bd2056cadChet Haase        public void expectLayouts(int count) {
538d7d27e9ebe5c7325e67e1a8af265378bd2056cadChet Haase            layoutLatch = new CountDownLatch(count);
539d7d27e9ebe5c7325e67e1a8af265378bd2056cadChet Haase        }
540d7d27e9ebe5c7325e67e1a8af265378bd2056cadChet Haase
54166826566020afc8d11f183cf3fe443ac0a022384Yigit Boyar        public void waitForLayout(int seconds) throws Throwable {
54266826566020afc8d11f183cf3fe443ac0a022384Yigit Boyar            layoutLatch.await(seconds * (mDebug ? 1000 : 1), SECONDS);
54366826566020afc8d11f183cf3fe443ac0a022384Yigit Boyar            checkForMainThreadException();
54466826566020afc8d11f183cf3fe443ac0a022384Yigit Boyar            MatcherAssert.assertThat("all layouts should complete on time",
54566826566020afc8d11f183cf3fe443ac0a022384Yigit Boyar                    layoutLatch.getCount(), CoreMatchers.is(0L));
54666826566020afc8d11f183cf3fe443ac0a022384Yigit Boyar            // use a runnable to ensure RV layout is finished
54766826566020afc8d11f183cf3fe443ac0a022384Yigit Boyar            getInstrumentation().runOnMainSync(new Runnable() {
54866826566020afc8d11f183cf3fe443ac0a022384Yigit Boyar                @Override
54966826566020afc8d11f183cf3fe443ac0a022384Yigit Boyar                public void run() {
55066826566020afc8d11f183cf3fe443ac0a022384Yigit Boyar                }
55166826566020afc8d11f183cf3fe443ac0a022384Yigit Boyar            });
552629687bba8da1a73c48d8fe87393a13581ce46caYigit Boyar        }
553629687bba8da1a73c48d8fe87393a13581ce46caYigit Boyar
554e3b89d11cf08a0e9aaa8162757246f286c6df2f8Yigit Boyar        public boolean isSupportsPredictive() {
555e3b89d11cf08a0e9aaa8162757246f286c6df2f8Yigit Boyar            return mSupportsPredictive;
556e3b89d11cf08a0e9aaa8162757246f286c6df2f8Yigit Boyar        }
557e3b89d11cf08a0e9aaa8162757246f286c6df2f8Yigit Boyar
558e3b89d11cf08a0e9aaa8162757246f286c6df2f8Yigit Boyar        public void setSupportsPredictive(boolean supportsPredictive) {
559e3b89d11cf08a0e9aaa8162757246f286c6df2f8Yigit Boyar            mSupportsPredictive = supportsPredictive;
560e3b89d11cf08a0e9aaa8162757246f286c6df2f8Yigit Boyar        }
561e3b89d11cf08a0e9aaa8162757246f286c6df2f8Yigit Boyar
562e3b89d11cf08a0e9aaa8162757246f286c6df2f8Yigit Boyar        @Override
563e3b89d11cf08a0e9aaa8162757246f286c6df2f8Yigit Boyar        public boolean supportsPredictiveItemAnimations() {
564e3b89d11cf08a0e9aaa8162757246f286c6df2f8Yigit Boyar            return mSupportsPredictive;
565e3b89d11cf08a0e9aaa8162757246f286c6df2f8Yigit Boyar        }
566e3b89d11cf08a0e9aaa8162757246f286c6df2f8Yigit Boyar
567b5f8b4fecb531aee6b359b4968409410aba90817Yigit Boyar        public void assertLayoutCount(int count, String msg, long timeout) throws Throwable {
568b5f8b4fecb531aee6b359b4968409410aba90817Yigit Boyar            layoutLatch.await(timeout, TimeUnit.SECONDS);
569b5f8b4fecb531aee6b359b4968409410aba90817Yigit Boyar            assertEquals(msg, count, layoutLatch.getCount());
570b5f8b4fecb531aee6b359b4968409410aba90817Yigit Boyar        }
571b5f8b4fecb531aee6b359b4968409410aba90817Yigit Boyar
572b5f8b4fecb531aee6b359b4968409410aba90817Yigit Boyar        public void assertNoLayout(String msg, long timeout) throws Throwable {
573b5f8b4fecb531aee6b359b4968409410aba90817Yigit Boyar            layoutLatch.await(timeout, TimeUnit.SECONDS);
574b5f8b4fecb531aee6b359b4968409410aba90817Yigit Boyar            assertFalse(msg, layoutLatch.getCount() == 0);
575b5f8b4fecb531aee6b359b4968409410aba90817Yigit Boyar        }
576b5f8b4fecb531aee6b359b4968409410aba90817Yigit Boyar
577d7d27e9ebe5c7325e67e1a8af265378bd2056cadChet Haase        @Override
578d7d27e9ebe5c7325e67e1a8af265378bd2056cadChet Haase        public RecyclerView.LayoutParams generateDefaultLayoutParams() {
579d7d27e9ebe5c7325e67e1a8af265378bd2056cadChet Haase            return new RecyclerView.LayoutParams(ViewGroup.LayoutParams.WRAP_CONTENT,
580d7d27e9ebe5c7325e67e1a8af265378bd2056cadChet Haase                    ViewGroup.LayoutParams.WRAP_CONTENT);
581d7d27e9ebe5c7325e67e1a8af265378bd2056cadChet Haase        }
582d7d27e9ebe5c7325e67e1a8af265378bd2056cadChet Haase
583d7d27e9ebe5c7325e67e1a8af265378bd2056cadChet Haase        void assertVisibleItemPositions() {
584d7d27e9ebe5c7325e67e1a8af265378bd2056cadChet Haase            int i = getChildCount();
585d7d27e9ebe5c7325e67e1a8af265378bd2056cadChet Haase            TestAdapter testAdapter = (TestAdapter) mRecyclerView.getAdapter();
586d7d27e9ebe5c7325e67e1a8af265378bd2056cadChet Haase            while (i-- > 0) {
587d7d27e9ebe5c7325e67e1a8af265378bd2056cadChet Haase                View view = getChildAt(i);
588d7d27e9ebe5c7325e67e1a8af265378bd2056cadChet Haase                RecyclerView.LayoutParams lp = (RecyclerView.LayoutParams) view.getLayoutParams();
589115ba0c7b2a14aa4cd0273952195e1d8f6468f87Yigit Boyar                Item item = ((TestViewHolder) lp.mViewHolder).mBoundItem;
590b5f8b4fecb531aee6b359b4968409410aba90817Yigit Boyar                if (mDebug) {
591d7d27e9ebe5c7325e67e1a8af265378bd2056cadChet Haase                    Log.d(TAG, "testing item " + i);
592d7d27e9ebe5c7325e67e1a8af265378bd2056cadChet Haase                }
5938ae76f91527ce850f155ce960fb9068bcd5d49f9Yigit Boyar                if (!lp.isItemRemoved()) {
5948ae76f91527ce850f155ce960fb9068bcd5d49f9Yigit Boyar                    RecyclerView.ViewHolder vh = mRecyclerView.getChildViewHolder(view);
5958ae76f91527ce850f155ce960fb9068bcd5d49f9Yigit Boyar                    assertSame("item position in LP should match adapter value :" + vh,
5968ae76f91527ce850f155ce960fb9068bcd5d49f9Yigit Boyar                            testAdapter.mItems.get(vh.mPosition), item);
5978ae76f91527ce850f155ce960fb9068bcd5d49f9Yigit Boyar                }
598d7d27e9ebe5c7325e67e1a8af265378bd2056cadChet Haase            }
599d7d27e9ebe5c7325e67e1a8af265378bd2056cadChet Haase        }
600d7d27e9ebe5c7325e67e1a8af265378bd2056cadChet Haase
601b5f8b4fecb531aee6b359b4968409410aba90817Yigit Boyar        RecyclerView.LayoutParams getLp(View v) {
602b5f8b4fecb531aee6b359b4968409410aba90817Yigit Boyar            return (RecyclerView.LayoutParams) v.getLayoutParams();
603b5f8b4fecb531aee6b359b4968409410aba90817Yigit Boyar        }
604b5f8b4fecb531aee6b359b4968409410aba90817Yigit Boyar
605e71a1df9b3c0e1bd3c21a1b3dd20a41790d4a950Yigit Boyar        protected void layoutRange(RecyclerView.Recycler recycler, int start, int end) {
606fe3996cf5af65fe11cc3ed47b67123ec267bd336Yigit Boyar            assertScrap(recycler);
607b5f8b4fecb531aee6b359b4968409410aba90817Yigit Boyar            if (mDebug) {
608d7d27e9ebe5c7325e67e1a8af265378bd2056cadChet Haase                Log.d(TAG, "will layout items from " + start + " to " + end);
609d7d27e9ebe5c7325e67e1a8af265378bd2056cadChet Haase            }
6105ced882cabdcefbb469e332f6f73983999aad0e5Yigit Boyar            int diff = end > start ? 1 : -1;
611504c54ea52c1b2aae6f8f4ae128f1dcaac7e3f6aYigit Boyar            int top = 0;
6125ced882cabdcefbb469e332f6f73983999aad0e5Yigit Boyar            for (int i = start; i != end; i+=diff) {
613b5f8b4fecb531aee6b359b4968409410aba90817Yigit Boyar                if (mDebug) {
614d7d27e9ebe5c7325e67e1a8af265378bd2056cadChet Haase                    Log.d(TAG, "laying out item " + i);
615d7d27e9ebe5c7325e67e1a8af265378bd2056cadChet Haase                }
616d7d27e9ebe5c7325e67e1a8af265378bd2056cadChet Haase                View view = recycler.getViewForPosition(i);
617b5f8b4fecb531aee6b359b4968409410aba90817Yigit Boyar                assertNotNull("view should not be null for valid position. "
618b5f8b4fecb531aee6b359b4968409410aba90817Yigit Boyar                        + "got null view at position " + i, view);
619c8b0ff5869e17a656a3a1d7fb306efa5b99fa07fYigit Boyar                if (!mRecyclerView.mState.isPreLayout()) {
620c8b0ff5869e17a656a3a1d7fb306efa5b99fa07fYigit Boyar                    RecyclerView.LayoutParams layoutParams = (RecyclerView.LayoutParams) view
621c8b0ff5869e17a656a3a1d7fb306efa5b99fa07fYigit Boyar                            .getLayoutParams();
622c8b0ff5869e17a656a3a1d7fb306efa5b99fa07fYigit Boyar                    assertFalse("In post layout, getViewForPosition should never return a view "
623c8b0ff5869e17a656a3a1d7fb306efa5b99fa07fYigit Boyar                            + "that is removed", layoutParams != null
624c8b0ff5869e17a656a3a1d7fb306efa5b99fa07fYigit Boyar                            && layoutParams.isItemRemoved());
625c8b0ff5869e17a656a3a1d7fb306efa5b99fa07fYigit Boyar
626b5f8b4fecb531aee6b359b4968409410aba90817Yigit Boyar                }
6278da5bba529a2fe9bd2aafab28010e11ec4de93c7Yigit Boyar                assertEquals("getViewForPosition should return correct position",
6288da5bba529a2fe9bd2aafab28010e11ec4de93c7Yigit Boyar                        i, getPosition(view));
629c8b0ff5869e17a656a3a1d7fb306efa5b99fa07fYigit Boyar                addView(view);
630d7d27e9ebe5c7325e67e1a8af265378bd2056cadChet Haase                measureChildWithMargins(view, 0, 0);
631e71a1df9b3c0e1bd3c21a1b3dd20a41790d4a950Yigit Boyar                if (getLayoutDirection() == ViewCompat.LAYOUT_DIRECTION_RTL) {
632e71a1df9b3c0e1bd3c21a1b3dd20a41790d4a950Yigit Boyar                    layoutDecorated(view, getWidth() - getDecoratedMeasuredWidth(view), top,
633e71a1df9b3c0e1bd3c21a1b3dd20a41790d4a950Yigit Boyar                            getWidth(), top + getDecoratedMeasuredHeight(view));
634e71a1df9b3c0e1bd3c21a1b3dd20a41790d4a950Yigit Boyar                } else {
635e71a1df9b3c0e1bd3c21a1b3dd20a41790d4a950Yigit Boyar                    layoutDecorated(view, 0, top, getDecoratedMeasuredWidth(view)
636e71a1df9b3c0e1bd3c21a1b3dd20a41790d4a950Yigit Boyar                            , top + getDecoratedMeasuredHeight(view));
637e71a1df9b3c0e1bd3c21a1b3dd20a41790d4a950Yigit Boyar                }
638e71a1df9b3c0e1bd3c21a1b3dd20a41790d4a950Yigit Boyar
639504c54ea52c1b2aae6f8f4ae128f1dcaac7e3f6aYigit Boyar                top += view.getMeasuredHeight();
640d7d27e9ebe5c7325e67e1a8af265378bd2056cadChet Haase            }
641d7d27e9ebe5c7325e67e1a8af265378bd2056cadChet Haase        }
642fe3996cf5af65fe11cc3ed47b67123ec267bd336Yigit Boyar
643fe3996cf5af65fe11cc3ed47b67123ec267bd336Yigit Boyar        private void assertScrap(RecyclerView.Recycler recycler) {
644e0c347f627f8a78d3e5e3e5eaac9c3ae26208689Yigit Boyar            if (mRecyclerView.getAdapter() != null &&
645e0c347f627f8a78d3e5e3e5eaac9c3ae26208689Yigit Boyar                    !mRecyclerView.getAdapter().hasStableIds()) {
646e0c347f627f8a78d3e5e3e5eaac9c3ae26208689Yigit Boyar                for (RecyclerView.ViewHolder viewHolder : recycler.getScrapList()) {
647e0c347f627f8a78d3e5e3e5eaac9c3ae26208689Yigit Boyar                    assertFalse("Invalid scrap should be no kept", viewHolder.isInvalid());
648e0c347f627f8a78d3e5e3e5eaac9c3ae26208689Yigit Boyar                }
649fe3996cf5af65fe11cc3ed47b67123ec267bd336Yigit Boyar            }
650fe3996cf5af65fe11cc3ed47b67123ec267bd336Yigit Boyar        }
651bb6ebab3b3b85ec20b3bc262289a2460705322f5Yigit Boyar
652bb6ebab3b3b85ec20b3bc262289a2460705322f5Yigit Boyar        @Override
653bb6ebab3b3b85ec20b3bc262289a2460705322f5Yigit Boyar        public boolean canScrollHorizontally() {
654bb6ebab3b3b85ec20b3bc262289a2460705322f5Yigit Boyar            return true;
655bb6ebab3b3b85ec20b3bc262289a2460705322f5Yigit Boyar        }
656bb6ebab3b3b85ec20b3bc262289a2460705322f5Yigit Boyar
657bb6ebab3b3b85ec20b3bc262289a2460705322f5Yigit Boyar        @Override
658bb6ebab3b3b85ec20b3bc262289a2460705322f5Yigit Boyar        public boolean canScrollVertically() {
659bb6ebab3b3b85ec20b3bc262289a2460705322f5Yigit Boyar            return true;
660bb6ebab3b3b85ec20b3bc262289a2460705322f5Yigit Boyar        }
661bb6ebab3b3b85ec20b3bc262289a2460705322f5Yigit Boyar
662bb6ebab3b3b85ec20b3bc262289a2460705322f5Yigit Boyar        @Override
663bb6ebab3b3b85ec20b3bc262289a2460705322f5Yigit Boyar        public int scrollHorizontallyBy(int dx, RecyclerView.Recycler recycler,
664bb6ebab3b3b85ec20b3bc262289a2460705322f5Yigit Boyar                RecyclerView.State state) {
665f129f1b050d2542a91fe8175eac30183beb07b41Chris Banes            mScrollHorizontallyAmount += dx;
666bb6ebab3b3b85ec20b3bc262289a2460705322f5Yigit Boyar            return dx;
667bb6ebab3b3b85ec20b3bc262289a2460705322f5Yigit Boyar        }
668bb6ebab3b3b85ec20b3bc262289a2460705322f5Yigit Boyar
669bb6ebab3b3b85ec20b3bc262289a2460705322f5Yigit Boyar        @Override
670bb6ebab3b3b85ec20b3bc262289a2460705322f5Yigit Boyar        public int scrollVerticallyBy(int dy, RecyclerView.Recycler recycler,
671bb6ebab3b3b85ec20b3bc262289a2460705322f5Yigit Boyar                RecyclerView.State state) {
672f129f1b050d2542a91fe8175eac30183beb07b41Chris Banes            mScrollVerticallyAmount += dy;
673bb6ebab3b3b85ec20b3bc262289a2460705322f5Yigit Boyar            return dy;
674bb6ebab3b3b85ec20b3bc262289a2460705322f5Yigit Boyar        }
675d7d27e9ebe5c7325e67e1a8af265378bd2056cadChet Haase    }
676d7d27e9ebe5c7325e67e1a8af265378bd2056cadChet Haase
677d7d27e9ebe5c7325e67e1a8af265378bd2056cadChet Haase    static class Item {
67811b7cfea15306c0d6e4ed7da09f7698df01695dfYigit Boyar        final static AtomicInteger idCounter = new AtomicInteger(0);
67911b7cfea15306c0d6e4ed7da09f7698df01695dfYigit Boyar        final public int mId = idCounter.incrementAndGet();
680d7d27e9ebe5c7325e67e1a8af265378bd2056cadChet Haase
6812d2e8d88103866b631eb0f3805da137ebcfb0275Yigit Boyar        int mAdapterIndex;
682d7d27e9ebe5c7325e67e1a8af265378bd2056cadChet Haase
6832d2e8d88103866b631eb0f3805da137ebcfb0275Yigit Boyar        final String mText;
6849051a368c00c40f8679ae67147b9f5af6f93638dYigit Boyar        int mType = 0;
685d7d27e9ebe5c7325e67e1a8af265378bd2056cadChet Haase
6862d2e8d88103866b631eb0f3805da137ebcfb0275Yigit Boyar        Item(int adapterIndex, String text) {
6872d2e8d88103866b631eb0f3805da137ebcfb0275Yigit Boyar            mAdapterIndex = adapterIndex;
6882d2e8d88103866b631eb0f3805da137ebcfb0275Yigit Boyar            mText = text;
689d7d27e9ebe5c7325e67e1a8af265378bd2056cadChet Haase        }
6908ae76f91527ce850f155ce960fb9068bcd5d49f9Yigit Boyar
6918ae76f91527ce850f155ce960fb9068bcd5d49f9Yigit Boyar        @Override
6928ae76f91527ce850f155ce960fb9068bcd5d49f9Yigit Boyar        public String toString() {
6938ae76f91527ce850f155ce960fb9068bcd5d49f9Yigit Boyar            return "Item{" +
6948ae76f91527ce850f155ce960fb9068bcd5d49f9Yigit Boyar                    "mId=" + mId +
6952d2e8d88103866b631eb0f3805da137ebcfb0275Yigit Boyar                    ", originalIndex=" + mAdapterIndex +
6962d2e8d88103866b631eb0f3805da137ebcfb0275Yigit Boyar                    ", text='" + mText + '\'' +
6978ae76f91527ce850f155ce960fb9068bcd5d49f9Yigit Boyar                    '}';
6988ae76f91527ce850f155ce960fb9068bcd5d49f9Yigit Boyar        }
699d7d27e9ebe5c7325e67e1a8af265378bd2056cadChet Haase    }
700d7d27e9ebe5c7325e67e1a8af265378bd2056cadChet Haase
701e71a1df9b3c0e1bd3c21a1b3dd20a41790d4a950Yigit Boyar    public class TestAdapter extends RecyclerView.Adapter<TestViewHolder>
70290ca3086dbf66ad6bb8840e46ec8524a705e1c18Yigit Boyar            implements AttachDetachCountingAdapter {
703d7d27e9ebe5c7325e67e1a8af265378bd2056cadChet Haase
7043d8453880afb3e32c4c59c52b8b580f91d78b29fVladislav Kaznacheev        public static final String DEFAULT_ITEM_PREFIX = "Item ";
7053d8453880afb3e32c4c59c52b8b580f91d78b29fVladislav Kaznacheev
70690ca3086dbf66ad6bb8840e46ec8524a705e1c18Yigit Boyar        ViewAttachDetachCounter mAttachmentCounter = new ViewAttachDetachCounter();
707d7d27e9ebe5c7325e67e1a8af265378bd2056cadChet Haase        List<Item> mItems;
708d7d27e9ebe5c7325e67e1a8af265378bd2056cadChet Haase
709e71a1df9b3c0e1bd3c21a1b3dd20a41790d4a950Yigit Boyar        public TestAdapter(int count) {
710d7d27e9ebe5c7325e67e1a8af265378bd2056cadChet Haase            mItems = new ArrayList<Item>(count);
7113d8453880afb3e32c4c59c52b8b580f91d78b29fVladislav Kaznacheev            addItems(0, count, DEFAULT_ITEM_PREFIX);
7123d8453880afb3e32c4c59c52b8b580f91d78b29fVladislav Kaznacheev        }
7133d8453880afb3e32c4c59c52b8b580f91d78b29fVladislav Kaznacheev
7143d8453880afb3e32c4c59c52b8b580f91d78b29fVladislav Kaznacheev        private void addItems(int pos, int count, String prefix) {
7153d8453880afb3e32c4c59c52b8b580f91d78b29fVladislav Kaznacheev            for (int i = 0; i < count; i++, pos++) {
7163d8453880afb3e32c4c59c52b8b580f91d78b29fVladislav Kaznacheev                mItems.add(pos, new Item(pos, prefix));
717d7d27e9ebe5c7325e67e1a8af265378bd2056cadChet Haase            }
718d7d27e9ebe5c7325e67e1a8af265378bd2056cadChet Haase        }
719d7d27e9ebe5c7325e67e1a8af265378bd2056cadChet Haase
720d7d27e9ebe5c7325e67e1a8af265378bd2056cadChet Haase        @Override
7219051a368c00c40f8679ae67147b9f5af6f93638dYigit Boyar        public int getItemViewType(int position) {
7229051a368c00c40f8679ae67147b9f5af6f93638dYigit Boyar            return getItemAt(position).mType;
7239051a368c00c40f8679ae67147b9f5af6f93638dYigit Boyar        }
7249051a368c00c40f8679ae67147b9f5af6f93638dYigit Boyar
7259051a368c00c40f8679ae67147b9f5af6f93638dYigit Boyar        @Override
72690ca3086dbf66ad6bb8840e46ec8524a705e1c18Yigit Boyar        public void onViewAttachedToWindow(TestViewHolder holder) {
72790ca3086dbf66ad6bb8840e46ec8524a705e1c18Yigit Boyar            super.onViewAttachedToWindow(holder);
72890ca3086dbf66ad6bb8840e46ec8524a705e1c18Yigit Boyar            mAttachmentCounter.onViewAttached(holder);
72990ca3086dbf66ad6bb8840e46ec8524a705e1c18Yigit Boyar        }
73090ca3086dbf66ad6bb8840e46ec8524a705e1c18Yigit Boyar
73190ca3086dbf66ad6bb8840e46ec8524a705e1c18Yigit Boyar        @Override
73290ca3086dbf66ad6bb8840e46ec8524a705e1c18Yigit Boyar        public void onViewDetachedFromWindow(TestViewHolder holder) {
73390ca3086dbf66ad6bb8840e46ec8524a705e1c18Yigit Boyar            super.onViewDetachedFromWindow(holder);
73490ca3086dbf66ad6bb8840e46ec8524a705e1c18Yigit Boyar            mAttachmentCounter.onViewDetached(holder);
73590ca3086dbf66ad6bb8840e46ec8524a705e1c18Yigit Boyar        }
73690ca3086dbf66ad6bb8840e46ec8524a705e1c18Yigit Boyar
73790ca3086dbf66ad6bb8840e46ec8524a705e1c18Yigit Boyar        @Override
73890ca3086dbf66ad6bb8840e46ec8524a705e1c18Yigit Boyar        public void onAttachedToRecyclerView(RecyclerView recyclerView) {
73990ca3086dbf66ad6bb8840e46ec8524a705e1c18Yigit Boyar            super.onAttachedToRecyclerView(recyclerView);
74090ca3086dbf66ad6bb8840e46ec8524a705e1c18Yigit Boyar            mAttachmentCounter.onAttached(recyclerView);
74190ca3086dbf66ad6bb8840e46ec8524a705e1c18Yigit Boyar        }
74290ca3086dbf66ad6bb8840e46ec8524a705e1c18Yigit Boyar
74390ca3086dbf66ad6bb8840e46ec8524a705e1c18Yigit Boyar        @Override
74490ca3086dbf66ad6bb8840e46ec8524a705e1c18Yigit Boyar        public void onDetachedFromRecyclerView(RecyclerView recyclerView) {
74590ca3086dbf66ad6bb8840e46ec8524a705e1c18Yigit Boyar            super.onDetachedFromRecyclerView(recyclerView);
74690ca3086dbf66ad6bb8840e46ec8524a705e1c18Yigit Boyar            mAttachmentCounter.onDetached(recyclerView);
74790ca3086dbf66ad6bb8840e46ec8524a705e1c18Yigit Boyar        }
74890ca3086dbf66ad6bb8840e46ec8524a705e1c18Yigit Boyar
74990ca3086dbf66ad6bb8840e46ec8524a705e1c18Yigit Boyar        @Override
750d7d27e9ebe5c7325e67e1a8af265378bd2056cadChet Haase        public TestViewHolder onCreateViewHolder(ViewGroup parent,
751d7d27e9ebe5c7325e67e1a8af265378bd2056cadChet Haase                int viewType) {
752959f4c0fac89425a8a9842e82fc180ec736fffacYigit Boyar            TextView itemView = new TextView(parent.getContext());
753959f4c0fac89425a8a9842e82fc180ec736fffacYigit Boyar            itemView.setFocusableInTouchMode(true);
754959f4c0fac89425a8a9842e82fc180ec736fffacYigit Boyar            itemView.setFocusable(true);
755959f4c0fac89425a8a9842e82fc180ec736fffacYigit Boyar            return new TestViewHolder(itemView);
756d7d27e9ebe5c7325e67e1a8af265378bd2056cadChet Haase        }
757d7d27e9ebe5c7325e67e1a8af265378bd2056cadChet Haase
758d7d27e9ebe5c7325e67e1a8af265378bd2056cadChet Haase        @Override
759d7d27e9ebe5c7325e67e1a8af265378bd2056cadChet Haase        public void onBindViewHolder(TestViewHolder holder, int position) {
760619ac016831a5ad75c2e0921c9036b6f030a9e63Yigit Boyar            assertNotNull(holder.mOwnerRecyclerView);
761619ac016831a5ad75c2e0921c9036b6f030a9e63Yigit Boyar            assertEquals(position, holder.getAdapterPosition());
762d7d27e9ebe5c7325e67e1a8af265378bd2056cadChet Haase            final Item item = mItems.get(position);
7634143554adb9b31b700b6876a251a64419e6111e2Yigit Boyar            ((TextView) (holder.itemView)).setText(item.mText + "(" + item.mId + ")");
764115ba0c7b2a14aa4cd0273952195e1d8f6468f87Yigit Boyar            holder.mBoundItem = item;
765d7d27e9ebe5c7325e67e1a8af265378bd2056cadChet Haase        }
766d7d27e9ebe5c7325e67e1a8af265378bd2056cadChet Haase
767888093b0a071a99d65c01116cf703d46b21f9918Yigit Boyar        public Item getItemAt(int position) {
768888093b0a071a99d65c01116cf703d46b21f9918Yigit Boyar            return mItems.get(position);
769888093b0a071a99d65c01116cf703d46b21f9918Yigit Boyar        }
770888093b0a071a99d65c01116cf703d46b21f9918Yigit Boyar
771619ac016831a5ad75c2e0921c9036b6f030a9e63Yigit Boyar        @Override
772619ac016831a5ad75c2e0921c9036b6f030a9e63Yigit Boyar        public void onViewRecycled(TestViewHolder holder) {
773619ac016831a5ad75c2e0921c9036b6f030a9e63Yigit Boyar            super.onViewRecycled(holder);
774619ac016831a5ad75c2e0921c9036b6f030a9e63Yigit Boyar            final int adapterPosition = holder.getAdapterPosition();
775619ac016831a5ad75c2e0921c9036b6f030a9e63Yigit Boyar            final boolean shouldHavePosition = !holder.isRemoved() && holder.isBound() &&
776619ac016831a5ad75c2e0921c9036b6f030a9e63Yigit Boyar                    !holder.isAdapterPositionUnknown() && !holder.isInvalid();
777619ac016831a5ad75c2e0921c9036b6f030a9e63Yigit Boyar            String log = "Position check for " + holder.toString();
778619ac016831a5ad75c2e0921c9036b6f030a9e63Yigit Boyar            assertEquals(log, shouldHavePosition, adapterPosition != RecyclerView.NO_POSITION);
779619ac016831a5ad75c2e0921c9036b6f030a9e63Yigit Boyar            if (shouldHavePosition) {
780619ac016831a5ad75c2e0921c9036b6f030a9e63Yigit Boyar                assertTrue(log, mItems.size() > adapterPosition);
781619ac016831a5ad75c2e0921c9036b6f030a9e63Yigit Boyar                assertSame(log, holder.mBoundItem, mItems.get(adapterPosition));
782619ac016831a5ad75c2e0921c9036b6f030a9e63Yigit Boyar            }
783619ac016831a5ad75c2e0921c9036b6f030a9e63Yigit Boyar        }
784619ac016831a5ad75c2e0921c9036b6f030a9e63Yigit Boyar
785b5f8b4fecb531aee6b359b4968409410aba90817Yigit Boyar        public void deleteAndNotify(final int start, final int count) throws Throwable {
786b5f8b4fecb531aee6b359b4968409410aba90817Yigit Boyar            deleteAndNotify(new int[]{start, count});
787b5f8b4fecb531aee6b359b4968409410aba90817Yigit Boyar        }
788b5f8b4fecb531aee6b359b4968409410aba90817Yigit Boyar
789b5f8b4fecb531aee6b359b4968409410aba90817Yigit Boyar        /**
790b5f8b4fecb531aee6b359b4968409410aba90817Yigit Boyar         * Deletes items in the given ranges.
791b5f8b4fecb531aee6b359b4968409410aba90817Yigit Boyar         * <p>
792b5f8b4fecb531aee6b359b4968409410aba90817Yigit Boyar         * Note that each operation affects the one after so you should offset them properly.
793b5f8b4fecb531aee6b359b4968409410aba90817Yigit Boyar         * <p>
794b5f8b4fecb531aee6b359b4968409410aba90817Yigit Boyar         * For example, if adapter has 5 items (A,B,C,D,E), and then you call this method with
795b5f8b4fecb531aee6b359b4968409410aba90817Yigit Boyar         * <code>[1, 2],[2, 1]</code>, it will first delete items B,C and the new adapter will be
796b5f8b4fecb531aee6b359b4968409410aba90817Yigit Boyar         * A D E. Then it will delete 2,1 which means it will delete E.
797b5f8b4fecb531aee6b359b4968409410aba90817Yigit Boyar         */
798b5f8b4fecb531aee6b359b4968409410aba90817Yigit Boyar        public void deleteAndNotify(final int[]... startCountTuples) throws Throwable {
7998ae76f91527ce850f155ce960fb9068bcd5d49f9Yigit Boyar            for (int[] tuple : startCountTuples) {
8008ae76f91527ce850f155ce960fb9068bcd5d49f9Yigit Boyar                tuple[1] = -tuple[1];
8018ae76f91527ce850f155ce960fb9068bcd5d49f9Yigit Boyar            }
8028ae76f91527ce850f155ce960fb9068bcd5d49f9Yigit Boyar            new AddRemoveRunnable(startCountTuples).runOnMainThread();
803d7d27e9ebe5c7325e67e1a8af265378bd2056cadChet Haase        }
804d7d27e9ebe5c7325e67e1a8af265378bd2056cadChet Haase
805c8b0ff5869e17a656a3a1d7fb306efa5b99fa07fYigit Boyar        @Override
806c8b0ff5869e17a656a3a1d7fb306efa5b99fa07fYigit Boyar        public long getItemId(int position) {
807c8b0ff5869e17a656a3a1d7fb306efa5b99fa07fYigit Boyar            return hasStableIds() ? mItems.get(position).mId : super.getItemId(position);
808c8b0ff5869e17a656a3a1d7fb306efa5b99fa07fYigit Boyar        }
809c8b0ff5869e17a656a3a1d7fb306efa5b99fa07fYigit Boyar
8102d2e8d88103866b631eb0f3805da137ebcfb0275Yigit Boyar        public void offsetOriginalIndices(int start, int offset) {
8112d2e8d88103866b631eb0f3805da137ebcfb0275Yigit Boyar            for (int i = start; i < mItems.size(); i++) {
8122d2e8d88103866b631eb0f3805da137ebcfb0275Yigit Boyar                mItems.get(i).mAdapterIndex += offset;
8132d2e8d88103866b631eb0f3805da137ebcfb0275Yigit Boyar            }
8142d2e8d88103866b631eb0f3805da137ebcfb0275Yigit Boyar        }
8152d2e8d88103866b631eb0f3805da137ebcfb0275Yigit Boyar
816e0c347f627f8a78d3e5e3e5eaac9c3ae26208689Yigit Boyar        /**
817e0c347f627f8a78d3e5e3e5eaac9c3ae26208689Yigit Boyar         * @param start inclusive
818e0c347f627f8a78d3e5e3e5eaac9c3ae26208689Yigit Boyar         * @param end exclusive
819e0c347f627f8a78d3e5e3e5eaac9c3ae26208689Yigit Boyar         * @param offset
820e0c347f627f8a78d3e5e3e5eaac9c3ae26208689Yigit Boyar         */
821e0c347f627f8a78d3e5e3e5eaac9c3ae26208689Yigit Boyar        public void offsetOriginalIndicesBetween(int start, int end, int offset) {
822e0c347f627f8a78d3e5e3e5eaac9c3ae26208689Yigit Boyar            for (int i = start; i < end && i < mItems.size(); i++) {
823e0c347f627f8a78d3e5e3e5eaac9c3ae26208689Yigit Boyar                mItems.get(i).mAdapterIndex += offset;
824e0c347f627f8a78d3e5e3e5eaac9c3ae26208689Yigit Boyar            }
825e0c347f627f8a78d3e5e3e5eaac9c3ae26208689Yigit Boyar        }
826e0c347f627f8a78d3e5e3e5eaac9c3ae26208689Yigit Boyar
8273d8453880afb3e32c4c59c52b8b580f91d78b29fVladislav Kaznacheev        public void addAndNotify(final int count) throws Throwable {
8283d8453880afb3e32c4c59c52b8b580f91d78b29fVladislav Kaznacheev            assertEquals(0, mItems.size());
8293d8453880afb3e32c4c59c52b8b580f91d78b29fVladislav Kaznacheev            new AddRemoveRunnable(DEFAULT_ITEM_PREFIX, new int[]{0, count}).runOnMainThread();
8303d8453880afb3e32c4c59c52b8b580f91d78b29fVladislav Kaznacheev        }
8313d8453880afb3e32c4c59c52b8b580f91d78b29fVladislav Kaznacheev
832959f4c0fac89425a8a9842e82fc180ec736fffacYigit Boyar        public void resetItemsTo(final List<Item> testItems) throws Throwable {
833959f4c0fac89425a8a9842e82fc180ec736fffacYigit Boyar            if (!mItems.isEmpty()) {
834959f4c0fac89425a8a9842e82fc180ec736fffacYigit Boyar                deleteAndNotify(0, mItems.size());
835959f4c0fac89425a8a9842e82fc180ec736fffacYigit Boyar            }
836959f4c0fac89425a8a9842e82fc180ec736fffacYigit Boyar            mItems = testItems;
837959f4c0fac89425a8a9842e82fc180ec736fffacYigit Boyar            runTestOnUiThread(new Runnable() {
838959f4c0fac89425a8a9842e82fc180ec736fffacYigit Boyar                @Override
839959f4c0fac89425a8a9842e82fc180ec736fffacYigit Boyar                public void run() {
840959f4c0fac89425a8a9842e82fc180ec736fffacYigit Boyar                    notifyItemRangeInserted(0, testItems.size());
841959f4c0fac89425a8a9842e82fc180ec736fffacYigit Boyar                }
842959f4c0fac89425a8a9842e82fc180ec736fffacYigit Boyar            });
843959f4c0fac89425a8a9842e82fc180ec736fffacYigit Boyar        }
844959f4c0fac89425a8a9842e82fc180ec736fffacYigit Boyar
845b5f8b4fecb531aee6b359b4968409410aba90817Yigit Boyar        public void addAndNotify(final int start, final int count) throws Throwable {
846b5f8b4fecb531aee6b359b4968409410aba90817Yigit Boyar            addAndNotify(new int[]{start, count});
847b5f8b4fecb531aee6b359b4968409410aba90817Yigit Boyar        }
848b5f8b4fecb531aee6b359b4968409410aba90817Yigit Boyar
849b5f8b4fecb531aee6b359b4968409410aba90817Yigit Boyar        public void addAndNotify(final int[]... startCountTuples) throws Throwable {
8508ae76f91527ce850f155ce960fb9068bcd5d49f9Yigit Boyar            new AddRemoveRunnable(startCountTuples).runOnMainThread();
8518ae76f91527ce850f155ce960fb9068bcd5d49f9Yigit Boyar        }
852b5f8b4fecb531aee6b359b4968409410aba90817Yigit Boyar
853cadd54707a7aaa4cfa9c8b8051c6185e8717f22eChet Haase        public void dispatchDataSetChanged() throws Throwable {
85422b6b8163d2e15eb6d0a89906854dec801945f1dYigit Boyar            runTestOnUiThread(new Runnable() {
85522b6b8163d2e15eb6d0a89906854dec801945f1dYigit Boyar                @Override
85622b6b8163d2e15eb6d0a89906854dec801945f1dYigit Boyar                public void run() {
85722b6b8163d2e15eb6d0a89906854dec801945f1dYigit Boyar                    notifyDataSetChanged();
85822b6b8163d2e15eb6d0a89906854dec801945f1dYigit Boyar                }
85922b6b8163d2e15eb6d0a89906854dec801945f1dYigit Boyar            });
86022b6b8163d2e15eb6d0a89906854dec801945f1dYigit Boyar        }
86122b6b8163d2e15eb6d0a89906854dec801945f1dYigit Boyar
862cadd54707a7aaa4cfa9c8b8051c6185e8717f22eChet Haase        public void changeAndNotify(final int start, final int count) throws Throwable {
8637e9da1a2629b657d56e2f54abd18c6249f3be037Yigit Boyar            runTestOnUiThread(new Runnable() {
8647e9da1a2629b657d56e2f54abd18c6249f3be037Yigit Boyar                @Override
8657e9da1a2629b657d56e2f54abd18c6249f3be037Yigit Boyar                public void run() {
8667e9da1a2629b657d56e2f54abd18c6249f3be037Yigit Boyar                    notifyItemRangeChanged(start, count);
8677e9da1a2629b657d56e2f54abd18c6249f3be037Yigit Boyar                }
8687e9da1a2629b657d56e2f54abd18c6249f3be037Yigit Boyar            });
8697e9da1a2629b657d56e2f54abd18c6249f3be037Yigit Boyar        }
8707e9da1a2629b657d56e2f54abd18c6249f3be037Yigit Boyar
871121ba9616e5bed44d2490f1744f7b6a9d3e79866Yigit Boyar        public void changeAndNotifyWithPayload(final int start, final int count,
872121ba9616e5bed44d2490f1744f7b6a9d3e79866Yigit Boyar                final Object payload) throws Throwable {
873121ba9616e5bed44d2490f1744f7b6a9d3e79866Yigit Boyar            runTestOnUiThread(new Runnable() {
874121ba9616e5bed44d2490f1744f7b6a9d3e79866Yigit Boyar                @Override
875121ba9616e5bed44d2490f1744f7b6a9d3e79866Yigit Boyar                public void run() {
876121ba9616e5bed44d2490f1744f7b6a9d3e79866Yigit Boyar                    notifyItemRangeChanged(start, count, payload);
877121ba9616e5bed44d2490f1744f7b6a9d3e79866Yigit Boyar                }
878121ba9616e5bed44d2490f1744f7b6a9d3e79866Yigit Boyar            });
879121ba9616e5bed44d2490f1744f7b6a9d3e79866Yigit Boyar        }
880121ba9616e5bed44d2490f1744f7b6a9d3e79866Yigit Boyar
881cadd54707a7aaa4cfa9c8b8051c6185e8717f22eChet Haase        public void changePositionsAndNotify(final int... positions) throws Throwable {
882cadd54707a7aaa4cfa9c8b8051c6185e8717f22eChet Haase            runTestOnUiThread(new Runnable() {
883cadd54707a7aaa4cfa9c8b8051c6185e8717f22eChet Haase                @Override
884cadd54707a7aaa4cfa9c8b8051c6185e8717f22eChet Haase                public void run() {
885cadd54707a7aaa4cfa9c8b8051c6185e8717f22eChet Haase                    for (int i = 0; i < positions.length; i += 1) {
886cadd54707a7aaa4cfa9c8b8051c6185e8717f22eChet Haase                        TestAdapter.super.notifyItemRangeChanged(positions[i], 1);
887cadd54707a7aaa4cfa9c8b8051c6185e8717f22eChet Haase                    }
888cadd54707a7aaa4cfa9c8b8051c6185e8717f22eChet Haase                }
889cadd54707a7aaa4cfa9c8b8051c6185e8717f22eChet Haase            });
890cadd54707a7aaa4cfa9c8b8051c6185e8717f22eChet Haase        }
891cadd54707a7aaa4cfa9c8b8051c6185e8717f22eChet Haase
8928ae76f91527ce850f155ce960fb9068bcd5d49f9Yigit Boyar        /**
8938ae76f91527ce850f155ce960fb9068bcd5d49f9Yigit Boyar         * Similar to other methods but negative count means delete and position count means add.
8948ae76f91527ce850f155ce960fb9068bcd5d49f9Yigit Boyar         * <p>
8958ae76f91527ce850f155ce960fb9068bcd5d49f9Yigit Boyar         * For instance, calling this method with <code>[1,1], [2,-1]</code> it will first add an
8968ae76f91527ce850f155ce960fb9068bcd5d49f9Yigit Boyar         * item to index 1, then remove an item from index 2 (updated index 2)
8978ae76f91527ce850f155ce960fb9068bcd5d49f9Yigit Boyar         */
8988ae76f91527ce850f155ce960fb9068bcd5d49f9Yigit Boyar        public void addDeleteAndNotify(final int[]... startCountTuples) throws Throwable {
8998ae76f91527ce850f155ce960fb9068bcd5d49f9Yigit Boyar            new AddRemoveRunnable(startCountTuples).runOnMainThread();
900d7d27e9ebe5c7325e67e1a8af265378bd2056cadChet Haase        }
901d7d27e9ebe5c7325e67e1a8af265378bd2056cadChet Haase
902d7d27e9ebe5c7325e67e1a8af265378bd2056cadChet Haase        @Override
903d7d27e9ebe5c7325e67e1a8af265378bd2056cadChet Haase        public int getItemCount() {
904d7d27e9ebe5c7325e67e1a8af265378bd2056cadChet Haase            return mItems.size();
905d7d27e9ebe5c7325e67e1a8af265378bd2056cadChet Haase        }
9068ae76f91527ce850f155ce960fb9068bcd5d49f9Yigit Boyar
907115ba0c7b2a14aa4cd0273952195e1d8f6468f87Yigit Boyar        /**
908115ba0c7b2a14aa4cd0273952195e1d8f6468f87Yigit Boyar         * Uses notifyDataSetChanged
909115ba0c7b2a14aa4cd0273952195e1d8f6468f87Yigit Boyar         */
910e0c347f627f8a78d3e5e3e5eaac9c3ae26208689Yigit Boyar        public void moveItems(boolean notifyChange, int[]... fromToTuples) throws Throwable {
911e0c347f627f8a78d3e5e3e5eaac9c3ae26208689Yigit Boyar            for (int i = 0; i < fromToTuples.length; i += 1) {
912e0c347f627f8a78d3e5e3e5eaac9c3ae26208689Yigit Boyar                int[] tuple = fromToTuples[i];
913e0c347f627f8a78d3e5e3e5eaac9c3ae26208689Yigit Boyar                moveItem(tuple[0], tuple[1], false);
914e0c347f627f8a78d3e5e3e5eaac9c3ae26208689Yigit Boyar            }
915e0c347f627f8a78d3e5e3e5eaac9c3ae26208689Yigit Boyar            if (notifyChange) {
916cadd54707a7aaa4cfa9c8b8051c6185e8717f22eChet Haase                dispatchDataSetChanged();
917e0c347f627f8a78d3e5e3e5eaac9c3ae26208689Yigit Boyar            }
918e0c347f627f8a78d3e5e3e5eaac9c3ae26208689Yigit Boyar        }
919e0c347f627f8a78d3e5e3e5eaac9c3ae26208689Yigit Boyar
920115ba0c7b2a14aa4cd0273952195e1d8f6468f87Yigit Boyar        /**
921115ba0c7b2a14aa4cd0273952195e1d8f6468f87Yigit Boyar         * Uses notifyDataSetChanged
922115ba0c7b2a14aa4cd0273952195e1d8f6468f87Yigit Boyar         */
923e0c347f627f8a78d3e5e3e5eaac9c3ae26208689Yigit Boyar        public void moveItem(final int from, final int to, final boolean notifyChange)
924e0c347f627f8a78d3e5e3e5eaac9c3ae26208689Yigit Boyar                throws Throwable {
925e0c347f627f8a78d3e5e3e5eaac9c3ae26208689Yigit Boyar            runTestOnUiThread(new Runnable() {
926e0c347f627f8a78d3e5e3e5eaac9c3ae26208689Yigit Boyar                @Override
927e0c347f627f8a78d3e5e3e5eaac9c3ae26208689Yigit Boyar                public void run() {
928ec60a348ce39889a99f7ca02da94a6867bfdf66fYigit Boyar                    moveInUIThread(from, to);
929e0c347f627f8a78d3e5e3e5eaac9c3ae26208689Yigit Boyar                    if (notifyChange) {
930e0c347f627f8a78d3e5e3e5eaac9c3ae26208689Yigit Boyar                        notifyDataSetChanged();
931e0c347f627f8a78d3e5e3e5eaac9c3ae26208689Yigit Boyar                    }
932e0c347f627f8a78d3e5e3e5eaac9c3ae26208689Yigit Boyar                }
933e0c347f627f8a78d3e5e3e5eaac9c3ae26208689Yigit Boyar            });
934e0c347f627f8a78d3e5e3e5eaac9c3ae26208689Yigit Boyar        }
935e0c347f627f8a78d3e5e3e5eaac9c3ae26208689Yigit Boyar
936115ba0c7b2a14aa4cd0273952195e1d8f6468f87Yigit Boyar        /**
937115ba0c7b2a14aa4cd0273952195e1d8f6468f87Yigit Boyar         * Uses notifyItemMoved
938115ba0c7b2a14aa4cd0273952195e1d8f6468f87Yigit Boyar         */
939115ba0c7b2a14aa4cd0273952195e1d8f6468f87Yigit Boyar        public void moveAndNotify(final int from, final int to) throws Throwable {
940115ba0c7b2a14aa4cd0273952195e1d8f6468f87Yigit Boyar            runTestOnUiThread(new Runnable() {
941115ba0c7b2a14aa4cd0273952195e1d8f6468f87Yigit Boyar                @Override
942115ba0c7b2a14aa4cd0273952195e1d8f6468f87Yigit Boyar                public void run() {
943ec60a348ce39889a99f7ca02da94a6867bfdf66fYigit Boyar                    moveInUIThread(from, to);
944115ba0c7b2a14aa4cd0273952195e1d8f6468f87Yigit Boyar                    notifyItemMoved(from, to);
945115ba0c7b2a14aa4cd0273952195e1d8f6468f87Yigit Boyar                }
946115ba0c7b2a14aa4cd0273952195e1d8f6468f87Yigit Boyar            });
947115ba0c7b2a14aa4cd0273952195e1d8f6468f87Yigit Boyar        }
948115ba0c7b2a14aa4cd0273952195e1d8f6468f87Yigit Boyar
9493d8453880afb3e32c4c59c52b8b580f91d78b29fVladislav Kaznacheev        public void clearOnUIThread() {
9503d8453880afb3e32c4c59c52b8b580f91d78b29fVladislav Kaznacheev            assertEquals("clearOnUIThread called from a wrong thread",
9513d8453880afb3e32c4c59c52b8b580f91d78b29fVladislav Kaznacheev                    Looper.getMainLooper(), Looper.myLooper());
9523d8453880afb3e32c4c59c52b8b580f91d78b29fVladislav Kaznacheev            mItems = new ArrayList<Item>();
9533d8453880afb3e32c4c59c52b8b580f91d78b29fVladislav Kaznacheev            notifyDataSetChanged();
9543d8453880afb3e32c4c59c52b8b580f91d78b29fVladislav Kaznacheev        }
9553d8453880afb3e32c4c59c52b8b580f91d78b29fVladislav Kaznacheev
956ec60a348ce39889a99f7ca02da94a6867bfdf66fYigit Boyar        protected void moveInUIThread(int from, int to) {
957ec60a348ce39889a99f7ca02da94a6867bfdf66fYigit Boyar            Item item = mItems.remove(from);
958ec60a348ce39889a99f7ca02da94a6867bfdf66fYigit Boyar            offsetOriginalIndices(from, -1);
959ec60a348ce39889a99f7ca02da94a6867bfdf66fYigit Boyar            mItems.add(to, item);
960ec60a348ce39889a99f7ca02da94a6867bfdf66fYigit Boyar            offsetOriginalIndices(to + 1, 1);
961ec60a348ce39889a99f7ca02da94a6867bfdf66fYigit Boyar            item.mAdapterIndex = to;
962ec60a348ce39889a99f7ca02da94a6867bfdf66fYigit Boyar        }
963115ba0c7b2a14aa4cd0273952195e1d8f6468f87Yigit Boyar
964115ba0c7b2a14aa4cd0273952195e1d8f6468f87Yigit Boyar
96590ca3086dbf66ad6bb8840e46ec8524a705e1c18Yigit Boyar        @Override
96690ca3086dbf66ad6bb8840e46ec8524a705e1c18Yigit Boyar        public ViewAttachDetachCounter getCounter() {
96790ca3086dbf66ad6bb8840e46ec8524a705e1c18Yigit Boyar            return mAttachmentCounter;
96890ca3086dbf66ad6bb8840e46ec8524a705e1c18Yigit Boyar        }
96990ca3086dbf66ad6bb8840e46ec8524a705e1c18Yigit Boyar
9708ae76f91527ce850f155ce960fb9068bcd5d49f9Yigit Boyar        private class AddRemoveRunnable implements Runnable {
9713d8453880afb3e32c4c59c52b8b580f91d78b29fVladislav Kaznacheev            final String mNewItemPrefix;
9728ae76f91527ce850f155ce960fb9068bcd5d49f9Yigit Boyar            final int[][] mStartCountTuples;
9738ae76f91527ce850f155ce960fb9068bcd5d49f9Yigit Boyar
9743d8453880afb3e32c4c59c52b8b580f91d78b29fVladislav Kaznacheev            public AddRemoveRunnable(String newItemPrefix, int[]... startCountTuples) {
9753d8453880afb3e32c4c59c52b8b580f91d78b29fVladislav Kaznacheev                mNewItemPrefix = newItemPrefix;
9768ae76f91527ce850f155ce960fb9068bcd5d49f9Yigit Boyar                mStartCountTuples = startCountTuples;
9778ae76f91527ce850f155ce960fb9068bcd5d49f9Yigit Boyar            }
9788ae76f91527ce850f155ce960fb9068bcd5d49f9Yigit Boyar
9793d8453880afb3e32c4c59c52b8b580f91d78b29fVladislav Kaznacheev            public AddRemoveRunnable(int[][] startCountTuples) {
9803d8453880afb3e32c4c59c52b8b580f91d78b29fVladislav Kaznacheev                this("new item ", startCountTuples);
9813d8453880afb3e32c4c59c52b8b580f91d78b29fVladislav Kaznacheev            }
9823d8453880afb3e32c4c59c52b8b580f91d78b29fVladislav Kaznacheev
9838ae76f91527ce850f155ce960fb9068bcd5d49f9Yigit Boyar            public void runOnMainThread() throws Throwable {
9848ae76f91527ce850f155ce960fb9068bcd5d49f9Yigit Boyar                if (Looper.myLooper() == Looper.getMainLooper()) {
9858ae76f91527ce850f155ce960fb9068bcd5d49f9Yigit Boyar                    run();
9868ae76f91527ce850f155ce960fb9068bcd5d49f9Yigit Boyar                } else {
9878ae76f91527ce850f155ce960fb9068bcd5d49f9Yigit Boyar                    runTestOnUiThread(this);
9888ae76f91527ce850f155ce960fb9068bcd5d49f9Yigit Boyar                }
9898ae76f91527ce850f155ce960fb9068bcd5d49f9Yigit Boyar            }
9908ae76f91527ce850f155ce960fb9068bcd5d49f9Yigit Boyar
9918ae76f91527ce850f155ce960fb9068bcd5d49f9Yigit Boyar            @Override
9928ae76f91527ce850f155ce960fb9068bcd5d49f9Yigit Boyar            public void run() {
9938ae76f91527ce850f155ce960fb9068bcd5d49f9Yigit Boyar                for (int[] tuple : mStartCountTuples) {
9948ae76f91527ce850f155ce960fb9068bcd5d49f9Yigit Boyar                    if (tuple[1] < 0) {
9958ae76f91527ce850f155ce960fb9068bcd5d49f9Yigit Boyar                        delete(tuple);
9968ae76f91527ce850f155ce960fb9068bcd5d49f9Yigit Boyar                    } else {
9978ae76f91527ce850f155ce960fb9068bcd5d49f9Yigit Boyar                        add(tuple);
9988ae76f91527ce850f155ce960fb9068bcd5d49f9Yigit Boyar                    }
9998ae76f91527ce850f155ce960fb9068bcd5d49f9Yigit Boyar                }
10008ae76f91527ce850f155ce960fb9068bcd5d49f9Yigit Boyar            }
10018ae76f91527ce850f155ce960fb9068bcd5d49f9Yigit Boyar
10028ae76f91527ce850f155ce960fb9068bcd5d49f9Yigit Boyar            private void add(int[] tuple) {
10032d2e8d88103866b631eb0f3805da137ebcfb0275Yigit Boyar                // offset others
10042d2e8d88103866b631eb0f3805da137ebcfb0275Yigit Boyar                offsetOriginalIndices(tuple[0], tuple[1]);
10053d8453880afb3e32c4c59c52b8b580f91d78b29fVladislav Kaznacheev                addItems(tuple[0], tuple[1], mNewItemPrefix);
10068ae76f91527ce850f155ce960fb9068bcd5d49f9Yigit Boyar                notifyItemRangeInserted(tuple[0], tuple[1]);
10078ae76f91527ce850f155ce960fb9068bcd5d49f9Yigit Boyar            }
10088ae76f91527ce850f155ce960fb9068bcd5d49f9Yigit Boyar
10098ae76f91527ce850f155ce960fb9068bcd5d49f9Yigit Boyar            private void delete(int[] tuple) {
10102d2e8d88103866b631eb0f3805da137ebcfb0275Yigit Boyar                final int count = -tuple[1];
10112d2e8d88103866b631eb0f3805da137ebcfb0275Yigit Boyar                offsetOriginalIndices(tuple[0] + count, tuple[1]);
10122d2e8d88103866b631eb0f3805da137ebcfb0275Yigit Boyar                for (int i = 0; i < count; i++) {
10138ae76f91527ce850f155ce960fb9068bcd5d49f9Yigit Boyar                    mItems.remove(tuple[0]);
10148ae76f91527ce850f155ce960fb9068bcd5d49f9Yigit Boyar                }
10152d2e8d88103866b631eb0f3805da137ebcfb0275Yigit Boyar                notifyItemRangeRemoved(tuple[0], count);
10168ae76f91527ce850f155ce960fb9068bcd5d49f9Yigit Boyar            }
10178ae76f91527ce850f155ce960fb9068bcd5d49f9Yigit Boyar        }
10188ae76f91527ce850f155ce960fb9068bcd5d49f9Yigit Boyar    }
10198ae76f91527ce850f155ce960fb9068bcd5d49f9Yigit Boyar
102090ca3086dbf66ad6bb8840e46ec8524a705e1c18Yigit Boyar    public boolean isMainThread() {
102190ca3086dbf66ad6bb8840e46ec8524a705e1c18Yigit Boyar        return Looper.myLooper() == Looper.getMainLooper();
102290ca3086dbf66ad6bb8840e46ec8524a705e1c18Yigit Boyar    }
102390ca3086dbf66ad6bb8840e46ec8524a705e1c18Yigit Boyar
10248ae76f91527ce850f155ce960fb9068bcd5d49f9Yigit Boyar    public void runTestOnUiThread(Runnable r) throws Throwable {
10258ae76f91527ce850f155ce960fb9068bcd5d49f9Yigit Boyar        if (Looper.myLooper() == Looper.getMainLooper()) {
10268ae76f91527ce850f155ce960fb9068bcd5d49f9Yigit Boyar            r.run();
10278ae76f91527ce850f155ce960fb9068bcd5d49f9Yigit Boyar        } else {
10280a017072206f93474ccd2706e7983c2ff778b904Yigit Boyar            InstrumentationRegistry.getInstrumentation().runOnMainSync(r);
10298ae76f91527ce850f155ce960fb9068bcd5d49f9Yigit Boyar        }
1030d7d27e9ebe5c7325e67e1a8af265378bd2056cadChet Haase    }
1031c50c4cad31d73e574b27bb3d7581542975e37263Yigit Boyar
1032c50c4cad31d73e574b27bb3d7581542975e37263Yigit Boyar    static class TargetTuple {
1033c50c4cad31d73e574b27bb3d7581542975e37263Yigit Boyar
1034c50c4cad31d73e574b27bb3d7581542975e37263Yigit Boyar        final int mPosition;
1035c50c4cad31d73e574b27bb3d7581542975e37263Yigit Boyar
1036c50c4cad31d73e574b27bb3d7581542975e37263Yigit Boyar        final int mLayoutDirection;
1037c50c4cad31d73e574b27bb3d7581542975e37263Yigit Boyar
1038c50c4cad31d73e574b27bb3d7581542975e37263Yigit Boyar        TargetTuple(int position, int layoutDirection) {
1039c50c4cad31d73e574b27bb3d7581542975e37263Yigit Boyar            this.mPosition = position;
1040c50c4cad31d73e574b27bb3d7581542975e37263Yigit Boyar            this.mLayoutDirection = layoutDirection;
1041c50c4cad31d73e574b27bb3d7581542975e37263Yigit Boyar        }
1042c50c4cad31d73e574b27bb3d7581542975e37263Yigit Boyar
1043c50c4cad31d73e574b27bb3d7581542975e37263Yigit Boyar        @Override
1044c50c4cad31d73e574b27bb3d7581542975e37263Yigit Boyar        public String toString() {
1045c50c4cad31d73e574b27bb3d7581542975e37263Yigit Boyar            return "TargetTuple{" +
1046c50c4cad31d73e574b27bb3d7581542975e37263Yigit Boyar                    "mPosition=" + mPosition +
1047c50c4cad31d73e574b27bb3d7581542975e37263Yigit Boyar                    ", mLayoutDirection=" + mLayoutDirection +
1048c50c4cad31d73e574b27bb3d7581542975e37263Yigit Boyar                    '}';
1049c50c4cad31d73e574b27bb3d7581542975e37263Yigit Boyar        }
1050c50c4cad31d73e574b27bb3d7581542975e37263Yigit Boyar    }
105190ca3086dbf66ad6bb8840e46ec8524a705e1c18Yigit Boyar
105290ca3086dbf66ad6bb8840e46ec8524a705e1c18Yigit Boyar    public interface AttachDetachCountingAdapter {
105390ca3086dbf66ad6bb8840e46ec8524a705e1c18Yigit Boyar
105490ca3086dbf66ad6bb8840e46ec8524a705e1c18Yigit Boyar        ViewAttachDetachCounter getCounter();
105590ca3086dbf66ad6bb8840e46ec8524a705e1c18Yigit Boyar    }
105690ca3086dbf66ad6bb8840e46ec8524a705e1c18Yigit Boyar
105790ca3086dbf66ad6bb8840e46ec8524a705e1c18Yigit Boyar    public class ViewAttachDetachCounter {
105890ca3086dbf66ad6bb8840e46ec8524a705e1c18Yigit Boyar
105990ca3086dbf66ad6bb8840e46ec8524a705e1c18Yigit Boyar        Set<RecyclerView.ViewHolder> mAttachedSet = new HashSet<RecyclerView.ViewHolder>();
106090ca3086dbf66ad6bb8840e46ec8524a705e1c18Yigit Boyar
106190ca3086dbf66ad6bb8840e46ec8524a705e1c18Yigit Boyar        public void validateRemaining(RecyclerView recyclerView) {
106290ca3086dbf66ad6bb8840e46ec8524a705e1c18Yigit Boyar            final int childCount = recyclerView.getChildCount();
106390ca3086dbf66ad6bb8840e46ec8524a705e1c18Yigit Boyar            for (int i = 0; i < childCount; i++) {
106490ca3086dbf66ad6bb8840e46ec8524a705e1c18Yigit Boyar                View view = recyclerView.getChildAt(i);
106590ca3086dbf66ad6bb8840e46ec8524a705e1c18Yigit Boyar                RecyclerView.ViewHolder vh = recyclerView.getChildViewHolder(view);
106690ca3086dbf66ad6bb8840e46ec8524a705e1c18Yigit Boyar                assertTrue("remaining view should be in attached set " + vh,
106790ca3086dbf66ad6bb8840e46ec8524a705e1c18Yigit Boyar                        mAttachedSet.contains(vh));
106890ca3086dbf66ad6bb8840e46ec8524a705e1c18Yigit Boyar            }
106990ca3086dbf66ad6bb8840e46ec8524a705e1c18Yigit Boyar            assertEquals("there should not be any views left in attached set",
107090ca3086dbf66ad6bb8840e46ec8524a705e1c18Yigit Boyar                    childCount, mAttachedSet.size());
107190ca3086dbf66ad6bb8840e46ec8524a705e1c18Yigit Boyar        }
107290ca3086dbf66ad6bb8840e46ec8524a705e1c18Yigit Boyar
107390ca3086dbf66ad6bb8840e46ec8524a705e1c18Yigit Boyar        public void onViewDetached(RecyclerView.ViewHolder viewHolder) {
107490ca3086dbf66ad6bb8840e46ec8524a705e1c18Yigit Boyar            try {
107590ca3086dbf66ad6bb8840e46ec8524a705e1c18Yigit Boyar                assertTrue("view holder should be in attached set",
107690ca3086dbf66ad6bb8840e46ec8524a705e1c18Yigit Boyar                        mAttachedSet.remove(viewHolder));
107790ca3086dbf66ad6bb8840e46ec8524a705e1c18Yigit Boyar            } catch (Throwable t) {
107890ca3086dbf66ad6bb8840e46ec8524a705e1c18Yigit Boyar                postExceptionToInstrumentation(t);
107990ca3086dbf66ad6bb8840e46ec8524a705e1c18Yigit Boyar            }
108090ca3086dbf66ad6bb8840e46ec8524a705e1c18Yigit Boyar        }
108190ca3086dbf66ad6bb8840e46ec8524a705e1c18Yigit Boyar
108290ca3086dbf66ad6bb8840e46ec8524a705e1c18Yigit Boyar        public void onViewAttached(RecyclerView.ViewHolder viewHolder) {
108390ca3086dbf66ad6bb8840e46ec8524a705e1c18Yigit Boyar            try {
108490ca3086dbf66ad6bb8840e46ec8524a705e1c18Yigit Boyar                assertTrue("view holder should not be in attached set",
108590ca3086dbf66ad6bb8840e46ec8524a705e1c18Yigit Boyar                        mAttachedSet.add(viewHolder));
108690ca3086dbf66ad6bb8840e46ec8524a705e1c18Yigit Boyar            } catch (Throwable t) {
108790ca3086dbf66ad6bb8840e46ec8524a705e1c18Yigit Boyar                postExceptionToInstrumentation(t);
108890ca3086dbf66ad6bb8840e46ec8524a705e1c18Yigit Boyar            }
108990ca3086dbf66ad6bb8840e46ec8524a705e1c18Yigit Boyar        }
109090ca3086dbf66ad6bb8840e46ec8524a705e1c18Yigit Boyar
109190ca3086dbf66ad6bb8840e46ec8524a705e1c18Yigit Boyar        public void onAttached(RecyclerView recyclerView) {
109290ca3086dbf66ad6bb8840e46ec8524a705e1c18Yigit Boyar            // when a new RV is attached, clear the set and add all view holders
109390ca3086dbf66ad6bb8840e46ec8524a705e1c18Yigit Boyar            mAttachedSet.clear();
109490ca3086dbf66ad6bb8840e46ec8524a705e1c18Yigit Boyar            final int childCount = recyclerView.getChildCount();
109590ca3086dbf66ad6bb8840e46ec8524a705e1c18Yigit Boyar            for (int i = 0; i < childCount; i ++) {
109690ca3086dbf66ad6bb8840e46ec8524a705e1c18Yigit Boyar                View view = recyclerView.getChildAt(i);
109790ca3086dbf66ad6bb8840e46ec8524a705e1c18Yigit Boyar                mAttachedSet.add(recyclerView.getChildViewHolder(view));
109890ca3086dbf66ad6bb8840e46ec8524a705e1c18Yigit Boyar            }
109990ca3086dbf66ad6bb8840e46ec8524a705e1c18Yigit Boyar        }
110090ca3086dbf66ad6bb8840e46ec8524a705e1c18Yigit Boyar
110190ca3086dbf66ad6bb8840e46ec8524a705e1c18Yigit Boyar        public void onDetached(RecyclerView recyclerView) {
110290ca3086dbf66ad6bb8840e46ec8524a705e1c18Yigit Boyar            validateRemaining(recyclerView);
110390ca3086dbf66ad6bb8840e46ec8524a705e1c18Yigit Boyar        }
110490ca3086dbf66ad6bb8840e46ec8524a705e1c18Yigit Boyar    }
11057a59e4b36500bcfddd842376d6c3abe0d9f23d84Yigit Boyar}
1106