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