1999c3976674d20b0de5425490bdfe7415b9c6af2Yigit Boyar/* 2999c3976674d20b0de5425490bdfe7415b9c6af2Yigit Boyar * Copyright (C) 2015 The Android Open Source Project 3999c3976674d20b0de5425490bdfe7415b9c6af2Yigit Boyar * 4999c3976674d20b0de5425490bdfe7415b9c6af2Yigit Boyar * Licensed under the Apache License, Version 2.0 (the "License"); 5999c3976674d20b0de5425490bdfe7415b9c6af2Yigit Boyar * you may not use this file except in compliance with the License. 6999c3976674d20b0de5425490bdfe7415b9c6af2Yigit Boyar * You may obtain a copy of the License at 7999c3976674d20b0de5425490bdfe7415b9c6af2Yigit Boyar * 8999c3976674d20b0de5425490bdfe7415b9c6af2Yigit Boyar * http://www.apache.org/licenses/LICENSE-2.0 9999c3976674d20b0de5425490bdfe7415b9c6af2Yigit Boyar * 10999c3976674d20b0de5425490bdfe7415b9c6af2Yigit Boyar * Unless required by applicable law or agreed to in writing, software 11999c3976674d20b0de5425490bdfe7415b9c6af2Yigit Boyar * distributed under the License is distributed on an "AS IS" BASIS, 12999c3976674d20b0de5425490bdfe7415b9c6af2Yigit Boyar * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13999c3976674d20b0de5425490bdfe7415b9c6af2Yigit Boyar * See the License for the specific language governing permissions and 14999c3976674d20b0de5425490bdfe7415b9c6af2Yigit Boyar * limitations under the License. 15999c3976674d20b0de5425490bdfe7415b9c6af2Yigit Boyar */ 16999c3976674d20b0de5425490bdfe7415b9c6af2Yigit Boyar 17999c3976674d20b0de5425490bdfe7415b9c6af2Yigit Boyarpackage android.support.v7.widget; 18999c3976674d20b0de5425490bdfe7415b9c6af2Yigit Boyar 19e9f9cd8d0e9008340985d17a2541ab24b3adb391Aurimas Liutikasimport static org.junit.Assert.assertEquals; 20999c3976674d20b0de5425490bdfe7415b9c6af2Yigit Boyar 21999c3976674d20b0de5425490bdfe7415b9c6af2Yigit Boyarimport android.graphics.Rect; 22999c3976674d20b0de5425490bdfe7415b9c6af2Yigit Boyarimport android.os.Parcel; 23999c3976674d20b0de5425490bdfe7415b9c6af2Yigit Boyarimport android.os.Parcelable; 24999c3976674d20b0de5425490bdfe7415b9c6af2Yigit Boyarimport android.support.test.InstrumentationRegistry; 25754cb29c50f09a83251dd4bb633ba445b2411adbAurimas Liutikasimport android.support.test.filters.LargeTest; 26999c3976674d20b0de5425490bdfe7415b9c6af2Yigit Boyarimport android.util.Log; 27999c3976674d20b0de5425490bdfe7415b9c6af2Yigit Boyar 28e9f9cd8d0e9008340985d17a2541ab24b3adb391Aurimas Liutikasimport org.junit.Test; 29e9f9cd8d0e9008340985d17a2541ab24b3adb391Aurimas Liutikasimport org.junit.runner.RunWith; 30e9f9cd8d0e9008340985d17a2541ab24b3adb391Aurimas Liutikasimport org.junit.runners.Parameterized; 31e9f9cd8d0e9008340985d17a2541ab24b3adb391Aurimas Liutikas 32999c3976674d20b0de5425490bdfe7415b9c6af2Yigit Boyarimport java.util.ArrayList; 33999c3976674d20b0de5425490bdfe7415b9c6af2Yigit Boyarimport java.util.List; 34999c3976674d20b0de5425490bdfe7415b9c6af2Yigit Boyarimport java.util.Map; 35999c3976674d20b0de5425490bdfe7415b9c6af2Yigit Boyarimport java.util.UUID; 36999c3976674d20b0de5425490bdfe7415b9c6af2Yigit Boyar 37999c3976674d20b0de5425490bdfe7415b9c6af2Yigit Boyar@RunWith(Parameterized.class) 38dca8e68e966915b8314095e71538d231a7eee575Yigit Boyar@LargeTest 39999c3976674d20b0de5425490bdfe7415b9c6af2Yigit Boyarpublic class LinearLayoutManagerSavedStateTest extends BaseLinearLayoutManagerTest { 40999c3976674d20b0de5425490bdfe7415b9c6af2Yigit Boyar final Config mConfig; 41999c3976674d20b0de5425490bdfe7415b9c6af2Yigit Boyar final boolean mWaitForLayout; 42999c3976674d20b0de5425490bdfe7415b9c6af2Yigit Boyar final boolean mLoadDataAfterRestore; 43999c3976674d20b0de5425490bdfe7415b9c6af2Yigit Boyar final PostLayoutRunnable mPostLayoutOperation; 44999c3976674d20b0de5425490bdfe7415b9c6af2Yigit Boyar final PostRestoreRunnable mPostRestoreOperation; 45999c3976674d20b0de5425490bdfe7415b9c6af2Yigit Boyar 46999c3976674d20b0de5425490bdfe7415b9c6af2Yigit Boyar public LinearLayoutManagerSavedStateTest(Config config, boolean waitForLayout, 47999c3976674d20b0de5425490bdfe7415b9c6af2Yigit Boyar boolean loadDataAfterRestore, PostLayoutRunnable postLayoutOperation, 48999c3976674d20b0de5425490bdfe7415b9c6af2Yigit Boyar PostRestoreRunnable postRestoreOperation) { 49999c3976674d20b0de5425490bdfe7415b9c6af2Yigit Boyar mConfig = config; 50999c3976674d20b0de5425490bdfe7415b9c6af2Yigit Boyar mWaitForLayout = waitForLayout; 51999c3976674d20b0de5425490bdfe7415b9c6af2Yigit Boyar mLoadDataAfterRestore = loadDataAfterRestore; 52999c3976674d20b0de5425490bdfe7415b9c6af2Yigit Boyar mPostLayoutOperation = postLayoutOperation; 53999c3976674d20b0de5425490bdfe7415b9c6af2Yigit Boyar mPostRestoreOperation = postRestoreOperation; 54999c3976674d20b0de5425490bdfe7415b9c6af2Yigit Boyar mPostLayoutOperation.mLayoutManagerDelegate = new Delegate<WrappedLinearLayoutManager>() { 55999c3976674d20b0de5425490bdfe7415b9c6af2Yigit Boyar @Override 56999c3976674d20b0de5425490bdfe7415b9c6af2Yigit Boyar public WrappedLinearLayoutManager get() { 57999c3976674d20b0de5425490bdfe7415b9c6af2Yigit Boyar return mLayoutManager; 58999c3976674d20b0de5425490bdfe7415b9c6af2Yigit Boyar } 59999c3976674d20b0de5425490bdfe7415b9c6af2Yigit Boyar }; 60999c3976674d20b0de5425490bdfe7415b9c6af2Yigit Boyar mPostLayoutOperation.mTestAdapterDelegate = new Delegate<TestAdapter>() { 61999c3976674d20b0de5425490bdfe7415b9c6af2Yigit Boyar @Override 62999c3976674d20b0de5425490bdfe7415b9c6af2Yigit Boyar public TestAdapter get() { 63999c3976674d20b0de5425490bdfe7415b9c6af2Yigit Boyar return mTestAdapter; 64999c3976674d20b0de5425490bdfe7415b9c6af2Yigit Boyar } 65999c3976674d20b0de5425490bdfe7415b9c6af2Yigit Boyar }; 66999c3976674d20b0de5425490bdfe7415b9c6af2Yigit Boyar mPostRestoreOperation.mLayoutManagerDelegate = new Delegate<WrappedLinearLayoutManager>() { 67999c3976674d20b0de5425490bdfe7415b9c6af2Yigit Boyar @Override 68999c3976674d20b0de5425490bdfe7415b9c6af2Yigit Boyar public WrappedLinearLayoutManager get() { 69999c3976674d20b0de5425490bdfe7415b9c6af2Yigit Boyar return mLayoutManager; 70999c3976674d20b0de5425490bdfe7415b9c6af2Yigit Boyar } 71999c3976674d20b0de5425490bdfe7415b9c6af2Yigit Boyar }; 72999c3976674d20b0de5425490bdfe7415b9c6af2Yigit Boyar mPostRestoreOperation.mTestAdapterDelegate = new Delegate<TestAdapter>() { 73999c3976674d20b0de5425490bdfe7415b9c6af2Yigit Boyar @Override 74999c3976674d20b0de5425490bdfe7415b9c6af2Yigit Boyar public TestAdapter get() { 75999c3976674d20b0de5425490bdfe7415b9c6af2Yigit Boyar return mTestAdapter; 76999c3976674d20b0de5425490bdfe7415b9c6af2Yigit Boyar } 77999c3976674d20b0de5425490bdfe7415b9c6af2Yigit Boyar }; 78999c3976674d20b0de5425490bdfe7415b9c6af2Yigit Boyar } 79999c3976674d20b0de5425490bdfe7415b9c6af2Yigit Boyar 80e9f9cd8d0e9008340985d17a2541ab24b3adb391Aurimas Liutikas @Parameterized.Parameters(name = "{0},waitForLayout:{1},loadDataAfterRestore:{2}" 81e9f9cd8d0e9008340985d17a2541ab24b3adb391Aurimas Liutikas + ",postLayout:{3},postRestore:{4}") 82999c3976674d20b0de5425490bdfe7415b9c6af2Yigit Boyar public static Iterable<Object[]> params() 83999c3976674d20b0de5425490bdfe7415b9c6af2Yigit Boyar throws IllegalAccessException, CloneNotSupportedException, NoSuchFieldException { 84999c3976674d20b0de5425490bdfe7415b9c6af2Yigit Boyar PostLayoutRunnable[] postLayoutOptions = new PostLayoutRunnable[]{ 85999c3976674d20b0de5425490bdfe7415b9c6af2Yigit Boyar new PostLayoutRunnable() { 86999c3976674d20b0de5425490bdfe7415b9c6af2Yigit Boyar @Override 87999c3976674d20b0de5425490bdfe7415b9c6af2Yigit Boyar public void run() throws Throwable { 88999c3976674d20b0de5425490bdfe7415b9c6af2Yigit Boyar // do nothing 89999c3976674d20b0de5425490bdfe7415b9c6af2Yigit Boyar } 90999c3976674d20b0de5425490bdfe7415b9c6af2Yigit Boyar 91999c3976674d20b0de5425490bdfe7415b9c6af2Yigit Boyar @Override 92999c3976674d20b0de5425490bdfe7415b9c6af2Yigit Boyar public String describe() { 93999c3976674d20b0de5425490bdfe7415b9c6af2Yigit Boyar return "doing nothing"; 94999c3976674d20b0de5425490bdfe7415b9c6af2Yigit Boyar } 95999c3976674d20b0de5425490bdfe7415b9c6af2Yigit Boyar }, 96999c3976674d20b0de5425490bdfe7415b9c6af2Yigit Boyar new PostLayoutRunnable() { 97999c3976674d20b0de5425490bdfe7415b9c6af2Yigit Boyar @Override 98999c3976674d20b0de5425490bdfe7415b9c6af2Yigit Boyar public void run() throws Throwable { 99999c3976674d20b0de5425490bdfe7415b9c6af2Yigit Boyar layoutManager().expectLayouts(1); 100999c3976674d20b0de5425490bdfe7415b9c6af2Yigit Boyar scrollToPosition(testAdapter().getItemCount() * 3 / 4); 101999c3976674d20b0de5425490bdfe7415b9c6af2Yigit Boyar layoutManager().waitForLayout(2); 102999c3976674d20b0de5425490bdfe7415b9c6af2Yigit Boyar } 103999c3976674d20b0de5425490bdfe7415b9c6af2Yigit Boyar 104999c3976674d20b0de5425490bdfe7415b9c6af2Yigit Boyar @Override 105999c3976674d20b0de5425490bdfe7415b9c6af2Yigit Boyar public String describe() { 106999c3976674d20b0de5425490bdfe7415b9c6af2Yigit Boyar return "scroll to position"; 107999c3976674d20b0de5425490bdfe7415b9c6af2Yigit Boyar } 108999c3976674d20b0de5425490bdfe7415b9c6af2Yigit Boyar }, 109999c3976674d20b0de5425490bdfe7415b9c6af2Yigit Boyar new PostLayoutRunnable() { 110999c3976674d20b0de5425490bdfe7415b9c6af2Yigit Boyar @Override 111999c3976674d20b0de5425490bdfe7415b9c6af2Yigit Boyar public void run() throws Throwable { 112999c3976674d20b0de5425490bdfe7415b9c6af2Yigit Boyar layoutManager().expectLayouts(1); 113999c3976674d20b0de5425490bdfe7415b9c6af2Yigit Boyar scrollToPositionWithOffset(testAdapter().getItemCount() / 3, 114999c3976674d20b0de5425490bdfe7415b9c6af2Yigit Boyar 50); 115999c3976674d20b0de5425490bdfe7415b9c6af2Yigit Boyar layoutManager().waitForLayout(2); 116999c3976674d20b0de5425490bdfe7415b9c6af2Yigit Boyar } 117999c3976674d20b0de5425490bdfe7415b9c6af2Yigit Boyar 118999c3976674d20b0de5425490bdfe7415b9c6af2Yigit Boyar @Override 119999c3976674d20b0de5425490bdfe7415b9c6af2Yigit Boyar public String describe() { 120999c3976674d20b0de5425490bdfe7415b9c6af2Yigit Boyar return "scroll to position with positive offset"; 121999c3976674d20b0de5425490bdfe7415b9c6af2Yigit Boyar } 122999c3976674d20b0de5425490bdfe7415b9c6af2Yigit Boyar }, 123999c3976674d20b0de5425490bdfe7415b9c6af2Yigit Boyar new PostLayoutRunnable() { 124999c3976674d20b0de5425490bdfe7415b9c6af2Yigit Boyar @Override 125999c3976674d20b0de5425490bdfe7415b9c6af2Yigit Boyar public void run() throws Throwable { 126999c3976674d20b0de5425490bdfe7415b9c6af2Yigit Boyar layoutManager().expectLayouts(1); 127999c3976674d20b0de5425490bdfe7415b9c6af2Yigit Boyar scrollToPositionWithOffset(testAdapter().getItemCount() * 2 / 3, 128999c3976674d20b0de5425490bdfe7415b9c6af2Yigit Boyar -10); // Some tests break if this value is below the item height. 129999c3976674d20b0de5425490bdfe7415b9c6af2Yigit Boyar layoutManager().waitForLayout(2); 130999c3976674d20b0de5425490bdfe7415b9c6af2Yigit Boyar } 131999c3976674d20b0de5425490bdfe7415b9c6af2Yigit Boyar 132999c3976674d20b0de5425490bdfe7415b9c6af2Yigit Boyar @Override 133999c3976674d20b0de5425490bdfe7415b9c6af2Yigit Boyar public String describe() { 134999c3976674d20b0de5425490bdfe7415b9c6af2Yigit Boyar return "scroll to position with negative offset"; 135999c3976674d20b0de5425490bdfe7415b9c6af2Yigit Boyar } 136999c3976674d20b0de5425490bdfe7415b9c6af2Yigit Boyar } 137999c3976674d20b0de5425490bdfe7415b9c6af2Yigit Boyar }; 138999c3976674d20b0de5425490bdfe7415b9c6af2Yigit Boyar 139999c3976674d20b0de5425490bdfe7415b9c6af2Yigit Boyar PostRestoreRunnable[] postRestoreOptions = new PostRestoreRunnable[]{ 140999c3976674d20b0de5425490bdfe7415b9c6af2Yigit Boyar new PostRestoreRunnable() { 141999c3976674d20b0de5425490bdfe7415b9c6af2Yigit Boyar @Override 142999c3976674d20b0de5425490bdfe7415b9c6af2Yigit Boyar public String describe() { 143999c3976674d20b0de5425490bdfe7415b9c6af2Yigit Boyar return "Doing nothing"; 144999c3976674d20b0de5425490bdfe7415b9c6af2Yigit Boyar } 145999c3976674d20b0de5425490bdfe7415b9c6af2Yigit Boyar }, 146999c3976674d20b0de5425490bdfe7415b9c6af2Yigit Boyar new PostRestoreRunnable() { 147999c3976674d20b0de5425490bdfe7415b9c6af2Yigit Boyar @Override 148999c3976674d20b0de5425490bdfe7415b9c6af2Yigit Boyar void onAfterRestore(Config config) throws Throwable { 149999c3976674d20b0de5425490bdfe7415b9c6af2Yigit Boyar // update config as well so that restore assertions will work 150999c3976674d20b0de5425490bdfe7415b9c6af2Yigit Boyar config.mOrientation = 1 - config.mOrientation; 151999c3976674d20b0de5425490bdfe7415b9c6af2Yigit Boyar layoutManager().setOrientation(config.mOrientation); 152999c3976674d20b0de5425490bdfe7415b9c6af2Yigit Boyar } 153999c3976674d20b0de5425490bdfe7415b9c6af2Yigit Boyar 154999c3976674d20b0de5425490bdfe7415b9c6af2Yigit Boyar @Override 155999c3976674d20b0de5425490bdfe7415b9c6af2Yigit Boyar boolean shouldLayoutMatch(Config config) { 156999c3976674d20b0de5425490bdfe7415b9c6af2Yigit Boyar return config.mItemCount == 0; 157999c3976674d20b0de5425490bdfe7415b9c6af2Yigit Boyar } 158999c3976674d20b0de5425490bdfe7415b9c6af2Yigit Boyar 159999c3976674d20b0de5425490bdfe7415b9c6af2Yigit Boyar @Override 160999c3976674d20b0de5425490bdfe7415b9c6af2Yigit Boyar public String describe() { 161999c3976674d20b0de5425490bdfe7415b9c6af2Yigit Boyar return "Changing orientation"; 162999c3976674d20b0de5425490bdfe7415b9c6af2Yigit Boyar } 163999c3976674d20b0de5425490bdfe7415b9c6af2Yigit Boyar }, 164999c3976674d20b0de5425490bdfe7415b9c6af2Yigit Boyar new PostRestoreRunnable() { 165999c3976674d20b0de5425490bdfe7415b9c6af2Yigit Boyar @Override 166999c3976674d20b0de5425490bdfe7415b9c6af2Yigit Boyar void onAfterRestore(Config config) throws Throwable { 167999c3976674d20b0de5425490bdfe7415b9c6af2Yigit Boyar config.mStackFromEnd = !config.mStackFromEnd; 168999c3976674d20b0de5425490bdfe7415b9c6af2Yigit Boyar layoutManager().setStackFromEnd(config.mStackFromEnd); 169999c3976674d20b0de5425490bdfe7415b9c6af2Yigit Boyar } 170999c3976674d20b0de5425490bdfe7415b9c6af2Yigit Boyar 171999c3976674d20b0de5425490bdfe7415b9c6af2Yigit Boyar @Override 172999c3976674d20b0de5425490bdfe7415b9c6af2Yigit Boyar boolean shouldLayoutMatch(Config config) { 173999c3976674d20b0de5425490bdfe7415b9c6af2Yigit Boyar return true; //stack from end should not move items on change 174999c3976674d20b0de5425490bdfe7415b9c6af2Yigit Boyar } 175999c3976674d20b0de5425490bdfe7415b9c6af2Yigit Boyar 176999c3976674d20b0de5425490bdfe7415b9c6af2Yigit Boyar @Override 177999c3976674d20b0de5425490bdfe7415b9c6af2Yigit Boyar public String describe() { 178999c3976674d20b0de5425490bdfe7415b9c6af2Yigit Boyar return "Changing stack from end"; 179999c3976674d20b0de5425490bdfe7415b9c6af2Yigit Boyar } 180999c3976674d20b0de5425490bdfe7415b9c6af2Yigit Boyar }, 181999c3976674d20b0de5425490bdfe7415b9c6af2Yigit Boyar new PostRestoreRunnable() { 182999c3976674d20b0de5425490bdfe7415b9c6af2Yigit Boyar @Override 183999c3976674d20b0de5425490bdfe7415b9c6af2Yigit Boyar void onAfterRestore(Config config) throws Throwable { 184999c3976674d20b0de5425490bdfe7415b9c6af2Yigit Boyar config.mReverseLayout = !config.mReverseLayout; 185999c3976674d20b0de5425490bdfe7415b9c6af2Yigit Boyar layoutManager().setReverseLayout(config.mReverseLayout); 186999c3976674d20b0de5425490bdfe7415b9c6af2Yigit Boyar } 187999c3976674d20b0de5425490bdfe7415b9c6af2Yigit Boyar 188999c3976674d20b0de5425490bdfe7415b9c6af2Yigit Boyar @Override 189999c3976674d20b0de5425490bdfe7415b9c6af2Yigit Boyar boolean shouldLayoutMatch(Config config) { 190999c3976674d20b0de5425490bdfe7415b9c6af2Yigit Boyar return config.mItemCount == 0; 191999c3976674d20b0de5425490bdfe7415b9c6af2Yigit Boyar } 192999c3976674d20b0de5425490bdfe7415b9c6af2Yigit Boyar 193999c3976674d20b0de5425490bdfe7415b9c6af2Yigit Boyar @Override 194999c3976674d20b0de5425490bdfe7415b9c6af2Yigit Boyar public String describe() { 195999c3976674d20b0de5425490bdfe7415b9c6af2Yigit Boyar return "Changing reverse layout"; 196999c3976674d20b0de5425490bdfe7415b9c6af2Yigit Boyar } 197999c3976674d20b0de5425490bdfe7415b9c6af2Yigit Boyar }, 198999c3976674d20b0de5425490bdfe7415b9c6af2Yigit Boyar new PostRestoreRunnable() { 199999c3976674d20b0de5425490bdfe7415b9c6af2Yigit Boyar @Override 200999c3976674d20b0de5425490bdfe7415b9c6af2Yigit Boyar void onAfterRestore(Config config) throws Throwable { 201999c3976674d20b0de5425490bdfe7415b9c6af2Yigit Boyar config.mRecycleChildrenOnDetach = !config.mRecycleChildrenOnDetach; 202999c3976674d20b0de5425490bdfe7415b9c6af2Yigit Boyar layoutManager().setRecycleChildrenOnDetach(config.mRecycleChildrenOnDetach); 203999c3976674d20b0de5425490bdfe7415b9c6af2Yigit Boyar } 204999c3976674d20b0de5425490bdfe7415b9c6af2Yigit Boyar 205999c3976674d20b0de5425490bdfe7415b9c6af2Yigit Boyar @Override 206999c3976674d20b0de5425490bdfe7415b9c6af2Yigit Boyar boolean shouldLayoutMatch(Config config) { 207999c3976674d20b0de5425490bdfe7415b9c6af2Yigit Boyar return true; 208999c3976674d20b0de5425490bdfe7415b9c6af2Yigit Boyar } 209999c3976674d20b0de5425490bdfe7415b9c6af2Yigit Boyar 210999c3976674d20b0de5425490bdfe7415b9c6af2Yigit Boyar @Override 211999c3976674d20b0de5425490bdfe7415b9c6af2Yigit Boyar String describe() { 212999c3976674d20b0de5425490bdfe7415b9c6af2Yigit Boyar return "Change should recycle children"; 213999c3976674d20b0de5425490bdfe7415b9c6af2Yigit Boyar } 214999c3976674d20b0de5425490bdfe7415b9c6af2Yigit Boyar }, 215999c3976674d20b0de5425490bdfe7415b9c6af2Yigit Boyar new PostRestoreRunnable() { 216999c3976674d20b0de5425490bdfe7415b9c6af2Yigit Boyar int position; 217999c3976674d20b0de5425490bdfe7415b9c6af2Yigit Boyar @Override 218999c3976674d20b0de5425490bdfe7415b9c6af2Yigit Boyar void onAfterRestore(Config config) throws Throwable { 219999c3976674d20b0de5425490bdfe7415b9c6af2Yigit Boyar position = testAdapter().getItemCount() / 2; 220999c3976674d20b0de5425490bdfe7415b9c6af2Yigit Boyar layoutManager().scrollToPosition(position); 221999c3976674d20b0de5425490bdfe7415b9c6af2Yigit Boyar } 222999c3976674d20b0de5425490bdfe7415b9c6af2Yigit Boyar 223999c3976674d20b0de5425490bdfe7415b9c6af2Yigit Boyar @Override 224999c3976674d20b0de5425490bdfe7415b9c6af2Yigit Boyar boolean shouldLayoutMatch(Config config) { 225999c3976674d20b0de5425490bdfe7415b9c6af2Yigit Boyar return testAdapter().getItemCount() == 0; 226999c3976674d20b0de5425490bdfe7415b9c6af2Yigit Boyar } 227999c3976674d20b0de5425490bdfe7415b9c6af2Yigit Boyar 228999c3976674d20b0de5425490bdfe7415b9c6af2Yigit Boyar @Override 229999c3976674d20b0de5425490bdfe7415b9c6af2Yigit Boyar String describe() { 230999c3976674d20b0de5425490bdfe7415b9c6af2Yigit Boyar return "Scroll to position " + position ; 231999c3976674d20b0de5425490bdfe7415b9c6af2Yigit Boyar } 232999c3976674d20b0de5425490bdfe7415b9c6af2Yigit Boyar 233999c3976674d20b0de5425490bdfe7415b9c6af2Yigit Boyar @Override 234999c3976674d20b0de5425490bdfe7415b9c6af2Yigit Boyar void onAfterReLayout(Config config) { 235999c3976674d20b0de5425490bdfe7415b9c6af2Yigit Boyar if (testAdapter().getItemCount() > 0) { 236999c3976674d20b0de5425490bdfe7415b9c6af2Yigit Boyar assertEquals(config + ":scrolled view should be last completely visible", 237999c3976674d20b0de5425490bdfe7415b9c6af2Yigit Boyar position, 238999c3976674d20b0de5425490bdfe7415b9c6af2Yigit Boyar config.mStackFromEnd ? 239999c3976674d20b0de5425490bdfe7415b9c6af2Yigit Boyar layoutManager().findLastCompletelyVisibleItemPosition() 240999c3976674d20b0de5425490bdfe7415b9c6af2Yigit Boyar : layoutManager().findFirstCompletelyVisibleItemPosition()); 241999c3976674d20b0de5425490bdfe7415b9c6af2Yigit Boyar } 242999c3976674d20b0de5425490bdfe7415b9c6af2Yigit Boyar } 243999c3976674d20b0de5425490bdfe7415b9c6af2Yigit Boyar } 244999c3976674d20b0de5425490bdfe7415b9c6af2Yigit Boyar }; 245999c3976674d20b0de5425490bdfe7415b9c6af2Yigit Boyar boolean[] waitForLayoutOptions = new boolean[]{true, false}; 246999c3976674d20b0de5425490bdfe7415b9c6af2Yigit Boyar boolean[] loadDataAfterRestoreOptions = new boolean[]{true, false}; 247999c3976674d20b0de5425490bdfe7415b9c6af2Yigit Boyar List<Config> variations = addConfigVariation(createBaseVariations(), "mItemCount", 0, 300); 248999c3976674d20b0de5425490bdfe7415b9c6af2Yigit Boyar variations = addConfigVariation(variations, "mRecycleChildrenOnDetach", true); 249999c3976674d20b0de5425490bdfe7415b9c6af2Yigit Boyar 250999c3976674d20b0de5425490bdfe7415b9c6af2Yigit Boyar List<Object[]> params = new ArrayList<>(); 251999c3976674d20b0de5425490bdfe7415b9c6af2Yigit Boyar for (Config config : variations) { 252999c3976674d20b0de5425490bdfe7415b9c6af2Yigit Boyar for (PostLayoutRunnable postLayoutRunnable : postLayoutOptions) { 253999c3976674d20b0de5425490bdfe7415b9c6af2Yigit Boyar for (boolean waitForLayout : waitForLayoutOptions) { 254999c3976674d20b0de5425490bdfe7415b9c6af2Yigit Boyar for (PostRestoreRunnable postRestoreRunnable : postRestoreOptions) { 255999c3976674d20b0de5425490bdfe7415b9c6af2Yigit Boyar for (boolean loadDataAfterRestore : loadDataAfterRestoreOptions) { 256999c3976674d20b0de5425490bdfe7415b9c6af2Yigit Boyar params.add(new Object[]{ 257999c3976674d20b0de5425490bdfe7415b9c6af2Yigit Boyar config.clone(), waitForLayout, 258999c3976674d20b0de5425490bdfe7415b9c6af2Yigit Boyar loadDataAfterRestore, postLayoutRunnable, postRestoreRunnable 259999c3976674d20b0de5425490bdfe7415b9c6af2Yigit Boyar }); 260999c3976674d20b0de5425490bdfe7415b9c6af2Yigit Boyar } 261999c3976674d20b0de5425490bdfe7415b9c6af2Yigit Boyar } 262999c3976674d20b0de5425490bdfe7415b9c6af2Yigit Boyar 263999c3976674d20b0de5425490bdfe7415b9c6af2Yigit Boyar } 264999c3976674d20b0de5425490bdfe7415b9c6af2Yigit Boyar } 265999c3976674d20b0de5425490bdfe7415b9c6af2Yigit Boyar } 266999c3976674d20b0de5425490bdfe7415b9c6af2Yigit Boyar return params; 267999c3976674d20b0de5425490bdfe7415b9c6af2Yigit Boyar } 268999c3976674d20b0de5425490bdfe7415b9c6af2Yigit Boyar 269999c3976674d20b0de5425490bdfe7415b9c6af2Yigit Boyar @Test 270999c3976674d20b0de5425490bdfe7415b9c6af2Yigit Boyar public void savedStateTest() 271999c3976674d20b0de5425490bdfe7415b9c6af2Yigit Boyar throws Throwable { 272999c3976674d20b0de5425490bdfe7415b9c6af2Yigit Boyar if (DEBUG) { 273999c3976674d20b0de5425490bdfe7415b9c6af2Yigit Boyar Log.d(TAG, "testing saved state with wait for layout = " + mWaitForLayout + " config " + 274999c3976674d20b0de5425490bdfe7415b9c6af2Yigit Boyar mConfig + " post layout action " + mPostLayoutOperation.describe() + 275999c3976674d20b0de5425490bdfe7415b9c6af2Yigit Boyar "post restore action " + mPostRestoreOperation.describe()); 276999c3976674d20b0de5425490bdfe7415b9c6af2Yigit Boyar } 277999c3976674d20b0de5425490bdfe7415b9c6af2Yigit Boyar setupByConfig(mConfig, false); 278999c3976674d20b0de5425490bdfe7415b9c6af2Yigit Boyar 279999c3976674d20b0de5425490bdfe7415b9c6af2Yigit Boyar if (mWaitForLayout) { 280999c3976674d20b0de5425490bdfe7415b9c6af2Yigit Boyar waitForFirstLayout(); 281999c3976674d20b0de5425490bdfe7415b9c6af2Yigit Boyar mPostLayoutOperation.run(); 282999c3976674d20b0de5425490bdfe7415b9c6af2Yigit Boyar } 283999c3976674d20b0de5425490bdfe7415b9c6af2Yigit Boyar Map<Item, Rect> before = mLayoutManager.collectChildCoordinates(); 284999c3976674d20b0de5425490bdfe7415b9c6af2Yigit Boyar Parcelable savedState = mRecyclerView.onSaveInstanceState(); 285999c3976674d20b0de5425490bdfe7415b9c6af2Yigit Boyar // we append a suffix to the parcelable to test out of bounds 286999c3976674d20b0de5425490bdfe7415b9c6af2Yigit Boyar String parcelSuffix = UUID.randomUUID().toString(); 287999c3976674d20b0de5425490bdfe7415b9c6af2Yigit Boyar Parcel parcel = Parcel.obtain(); 288999c3976674d20b0de5425490bdfe7415b9c6af2Yigit Boyar savedState.writeToParcel(parcel, 0); 289999c3976674d20b0de5425490bdfe7415b9c6af2Yigit Boyar parcel.writeString(parcelSuffix); 290999c3976674d20b0de5425490bdfe7415b9c6af2Yigit Boyar removeRecyclerView(); 291999c3976674d20b0de5425490bdfe7415b9c6af2Yigit Boyar // reset for reading 292999c3976674d20b0de5425490bdfe7415b9c6af2Yigit Boyar parcel.setDataPosition(0); 293999c3976674d20b0de5425490bdfe7415b9c6af2Yigit Boyar // re-create 294999c3976674d20b0de5425490bdfe7415b9c6af2Yigit Boyar savedState = RecyclerView.SavedState.CREATOR.createFromParcel(parcel); 295999c3976674d20b0de5425490bdfe7415b9c6af2Yigit Boyar 296999c3976674d20b0de5425490bdfe7415b9c6af2Yigit Boyar final int itemCount = mTestAdapter.getItemCount(); 297959f4c0fac89425a8a9842e82fc180ec736fffacYigit Boyar List<Item> testItems = new ArrayList<>(); 298999c3976674d20b0de5425490bdfe7415b9c6af2Yigit Boyar if (mLoadDataAfterRestore) { 299959f4c0fac89425a8a9842e82fc180ec736fffacYigit Boyar // we cannot delete and re-add since new items may have different sizes. We need the 300959f4c0fac89425a8a9842e82fc180ec736fffacYigit Boyar // exact same adapter. 301959f4c0fac89425a8a9842e82fc180ec736fffacYigit Boyar testItems.addAll(mTestAdapter.mItems); 302999c3976674d20b0de5425490bdfe7415b9c6af2Yigit Boyar mTestAdapter.deleteAndNotify(0, itemCount); 303999c3976674d20b0de5425490bdfe7415b9c6af2Yigit Boyar } 304999c3976674d20b0de5425490bdfe7415b9c6af2Yigit Boyar 305999c3976674d20b0de5425490bdfe7415b9c6af2Yigit Boyar RecyclerView restored = new RecyclerView(getActivity()); 306999c3976674d20b0de5425490bdfe7415b9c6af2Yigit Boyar // this config should be no op. 307999c3976674d20b0de5425490bdfe7415b9c6af2Yigit Boyar mLayoutManager = new WrappedLinearLayoutManager(getActivity(), 308999c3976674d20b0de5425490bdfe7415b9c6af2Yigit Boyar mConfig.mOrientation, mConfig.mReverseLayout); 309999c3976674d20b0de5425490bdfe7415b9c6af2Yigit Boyar mLayoutManager.setStackFromEnd(mConfig.mStackFromEnd); 310999c3976674d20b0de5425490bdfe7415b9c6af2Yigit Boyar restored.setLayoutManager(mLayoutManager); 311999c3976674d20b0de5425490bdfe7415b9c6af2Yigit Boyar // use the same adapter for Rect matching 312999c3976674d20b0de5425490bdfe7415b9c6af2Yigit Boyar restored.setAdapter(mTestAdapter); 313999c3976674d20b0de5425490bdfe7415b9c6af2Yigit Boyar restored.onRestoreInstanceState(savedState); 314999c3976674d20b0de5425490bdfe7415b9c6af2Yigit Boyar 315999c3976674d20b0de5425490bdfe7415b9c6af2Yigit Boyar if (mLoadDataAfterRestore) { 316959f4c0fac89425a8a9842e82fc180ec736fffacYigit Boyar // add the same items back 317959f4c0fac89425a8a9842e82fc180ec736fffacYigit Boyar mTestAdapter.resetItemsTo(testItems); 318999c3976674d20b0de5425490bdfe7415b9c6af2Yigit Boyar } 319999c3976674d20b0de5425490bdfe7415b9c6af2Yigit Boyar 320999c3976674d20b0de5425490bdfe7415b9c6af2Yigit Boyar mPostRestoreOperation.onAfterRestore(mConfig); 321999c3976674d20b0de5425490bdfe7415b9c6af2Yigit Boyar assertEquals("Parcel reading should not go out of bounds", parcelSuffix, 322999c3976674d20b0de5425490bdfe7415b9c6af2Yigit Boyar parcel.readString()); 323999c3976674d20b0de5425490bdfe7415b9c6af2Yigit Boyar mLayoutManager.expectLayouts(1); 324999c3976674d20b0de5425490bdfe7415b9c6af2Yigit Boyar setRecyclerView(restored); 325999c3976674d20b0de5425490bdfe7415b9c6af2Yigit Boyar mLayoutManager.waitForLayout(2); 326999c3976674d20b0de5425490bdfe7415b9c6af2Yigit Boyar // calculate prefix here instead of above to include post restore changes 327999c3976674d20b0de5425490bdfe7415b9c6af2Yigit Boyar final String logPrefix = mConfig + "\npostLayout:" + mPostLayoutOperation.describe() + 328999c3976674d20b0de5425490bdfe7415b9c6af2Yigit Boyar "\npostRestore:" + mPostRestoreOperation.describe() + "\n"; 329999c3976674d20b0de5425490bdfe7415b9c6af2Yigit Boyar assertEquals(logPrefix + " on saved state, reverse layout should be preserved", 330999c3976674d20b0de5425490bdfe7415b9c6af2Yigit Boyar mConfig.mReverseLayout, mLayoutManager.getReverseLayout()); 331999c3976674d20b0de5425490bdfe7415b9c6af2Yigit Boyar assertEquals(logPrefix + " on saved state, orientation should be preserved", 332999c3976674d20b0de5425490bdfe7415b9c6af2Yigit Boyar mConfig.mOrientation, mLayoutManager.getOrientation()); 333999c3976674d20b0de5425490bdfe7415b9c6af2Yigit Boyar assertEquals(logPrefix + " on saved state, stack from end should be preserved", 334999c3976674d20b0de5425490bdfe7415b9c6af2Yigit Boyar mConfig.mStackFromEnd, mLayoutManager.getStackFromEnd()); 335999c3976674d20b0de5425490bdfe7415b9c6af2Yigit Boyar if (mWaitForLayout) { 336999c3976674d20b0de5425490bdfe7415b9c6af2Yigit Boyar final boolean strictItemEquality = !mLoadDataAfterRestore; 337999c3976674d20b0de5425490bdfe7415b9c6af2Yigit Boyar if (mPostRestoreOperation.shouldLayoutMatch(mConfig)) { 338999c3976674d20b0de5425490bdfe7415b9c6af2Yigit Boyar assertRectSetsEqual( 339999c3976674d20b0de5425490bdfe7415b9c6af2Yigit Boyar logPrefix + ": on restore, previous view positions should be preserved", 340999c3976674d20b0de5425490bdfe7415b9c6af2Yigit Boyar before, mLayoutManager.collectChildCoordinates(), strictItemEquality); 341999c3976674d20b0de5425490bdfe7415b9c6af2Yigit Boyar } else { 342999c3976674d20b0de5425490bdfe7415b9c6af2Yigit Boyar assertRectSetsNotEqual( 343999c3976674d20b0de5425490bdfe7415b9c6af2Yigit Boyar logPrefix 344999c3976674d20b0de5425490bdfe7415b9c6af2Yigit Boyar + ": on restore with changes, previous view positions should NOT " 345999c3976674d20b0de5425490bdfe7415b9c6af2Yigit Boyar + "be preserved", 346999c3976674d20b0de5425490bdfe7415b9c6af2Yigit Boyar before, mLayoutManager.collectChildCoordinates(), strictItemEquality); 347999c3976674d20b0de5425490bdfe7415b9c6af2Yigit Boyar } 348999c3976674d20b0de5425490bdfe7415b9c6af2Yigit Boyar mPostRestoreOperation.onAfterReLayout(mConfig); 349999c3976674d20b0de5425490bdfe7415b9c6af2Yigit Boyar } 350999c3976674d20b0de5425490bdfe7415b9c6af2Yigit Boyar } 351999c3976674d20b0de5425490bdfe7415b9c6af2Yigit Boyar 352999c3976674d20b0de5425490bdfe7415b9c6af2Yigit Boyar protected static abstract class PostLayoutRunnable { 353999c3976674d20b0de5425490bdfe7415b9c6af2Yigit Boyar private Delegate<WrappedLinearLayoutManager> mLayoutManagerDelegate; 354999c3976674d20b0de5425490bdfe7415b9c6af2Yigit Boyar private Delegate<TestAdapter> mTestAdapterDelegate; 355999c3976674d20b0de5425490bdfe7415b9c6af2Yigit Boyar protected WrappedLinearLayoutManager layoutManager() { 356999c3976674d20b0de5425490bdfe7415b9c6af2Yigit Boyar return mLayoutManagerDelegate.get(); 357999c3976674d20b0de5425490bdfe7415b9c6af2Yigit Boyar } 358999c3976674d20b0de5425490bdfe7415b9c6af2Yigit Boyar protected TestAdapter testAdapter() { 359999c3976674d20b0de5425490bdfe7415b9c6af2Yigit Boyar return mTestAdapterDelegate.get(); 360999c3976674d20b0de5425490bdfe7415b9c6af2Yigit Boyar } 361999c3976674d20b0de5425490bdfe7415b9c6af2Yigit Boyar 362999c3976674d20b0de5425490bdfe7415b9c6af2Yigit Boyar abstract void run() throws Throwable; 363999c3976674d20b0de5425490bdfe7415b9c6af2Yigit Boyar void scrollToPosition(final int position) { 364999c3976674d20b0de5425490bdfe7415b9c6af2Yigit Boyar InstrumentationRegistry.getInstrumentation().runOnMainSync(new Runnable() { 365999c3976674d20b0de5425490bdfe7415b9c6af2Yigit Boyar @Override 366999c3976674d20b0de5425490bdfe7415b9c6af2Yigit Boyar public void run() { 367999c3976674d20b0de5425490bdfe7415b9c6af2Yigit Boyar layoutManager().scrollToPosition(position); 368999c3976674d20b0de5425490bdfe7415b9c6af2Yigit Boyar } 369999c3976674d20b0de5425490bdfe7415b9c6af2Yigit Boyar }); 370999c3976674d20b0de5425490bdfe7415b9c6af2Yigit Boyar } 371999c3976674d20b0de5425490bdfe7415b9c6af2Yigit Boyar void scrollToPositionWithOffset(final int position, final int offset) { 372999c3976674d20b0de5425490bdfe7415b9c6af2Yigit Boyar InstrumentationRegistry.getInstrumentation().runOnMainSync(new Runnable() { 373999c3976674d20b0de5425490bdfe7415b9c6af2Yigit Boyar @Override 374999c3976674d20b0de5425490bdfe7415b9c6af2Yigit Boyar public void run() { 375999c3976674d20b0de5425490bdfe7415b9c6af2Yigit Boyar layoutManager().scrollToPositionWithOffset(position, offset); 376999c3976674d20b0de5425490bdfe7415b9c6af2Yigit Boyar } 377999c3976674d20b0de5425490bdfe7415b9c6af2Yigit Boyar }); 378999c3976674d20b0de5425490bdfe7415b9c6af2Yigit Boyar } 379999c3976674d20b0de5425490bdfe7415b9c6af2Yigit Boyar abstract String describe(); 380999c3976674d20b0de5425490bdfe7415b9c6af2Yigit Boyar 381999c3976674d20b0de5425490bdfe7415b9c6af2Yigit Boyar @Override 382999c3976674d20b0de5425490bdfe7415b9c6af2Yigit Boyar public String toString() { 383999c3976674d20b0de5425490bdfe7415b9c6af2Yigit Boyar return describe(); 384999c3976674d20b0de5425490bdfe7415b9c6af2Yigit Boyar } 385999c3976674d20b0de5425490bdfe7415b9c6af2Yigit Boyar } 386999c3976674d20b0de5425490bdfe7415b9c6af2Yigit Boyar 387999c3976674d20b0de5425490bdfe7415b9c6af2Yigit Boyar protected static abstract class PostRestoreRunnable { 388999c3976674d20b0de5425490bdfe7415b9c6af2Yigit Boyar private Delegate<WrappedLinearLayoutManager> mLayoutManagerDelegate; 389999c3976674d20b0de5425490bdfe7415b9c6af2Yigit Boyar private Delegate<TestAdapter> mTestAdapterDelegate; 390999c3976674d20b0de5425490bdfe7415b9c6af2Yigit Boyar protected WrappedLinearLayoutManager layoutManager() { 391999c3976674d20b0de5425490bdfe7415b9c6af2Yigit Boyar return mLayoutManagerDelegate.get(); 392999c3976674d20b0de5425490bdfe7415b9c6af2Yigit Boyar } 393999c3976674d20b0de5425490bdfe7415b9c6af2Yigit Boyar protected TestAdapter testAdapter() { 394999c3976674d20b0de5425490bdfe7415b9c6af2Yigit Boyar return mTestAdapterDelegate.get(); 395999c3976674d20b0de5425490bdfe7415b9c6af2Yigit Boyar } 396999c3976674d20b0de5425490bdfe7415b9c6af2Yigit Boyar 397999c3976674d20b0de5425490bdfe7415b9c6af2Yigit Boyar void onAfterRestore(Config config) throws Throwable { 398999c3976674d20b0de5425490bdfe7415b9c6af2Yigit Boyar } 399999c3976674d20b0de5425490bdfe7415b9c6af2Yigit Boyar 400999c3976674d20b0de5425490bdfe7415b9c6af2Yigit Boyar abstract String describe(); 401999c3976674d20b0de5425490bdfe7415b9c6af2Yigit Boyar 402999c3976674d20b0de5425490bdfe7415b9c6af2Yigit Boyar boolean shouldLayoutMatch(Config config) { 403999c3976674d20b0de5425490bdfe7415b9c6af2Yigit Boyar return true; 404999c3976674d20b0de5425490bdfe7415b9c6af2Yigit Boyar } 405999c3976674d20b0de5425490bdfe7415b9c6af2Yigit Boyar 406999c3976674d20b0de5425490bdfe7415b9c6af2Yigit Boyar void onAfterReLayout(Config config) { 407999c3976674d20b0de5425490bdfe7415b9c6af2Yigit Boyar 408999c3976674d20b0de5425490bdfe7415b9c6af2Yigit Boyar }; 409999c3976674d20b0de5425490bdfe7415b9c6af2Yigit Boyar 410999c3976674d20b0de5425490bdfe7415b9c6af2Yigit Boyar @Override 411999c3976674d20b0de5425490bdfe7415b9c6af2Yigit Boyar public String toString() { 412999c3976674d20b0de5425490bdfe7415b9c6af2Yigit Boyar return describe(); 413999c3976674d20b0de5425490bdfe7415b9c6af2Yigit Boyar } 414999c3976674d20b0de5425490bdfe7415b9c6af2Yigit Boyar } 415999c3976674d20b0de5425490bdfe7415b9c6af2Yigit Boyar 416999c3976674d20b0de5425490bdfe7415b9c6af2Yigit Boyar private interface Delegate<T> { 417999c3976674d20b0de5425490bdfe7415b9c6af2Yigit Boyar T get(); 418999c3976674d20b0de5425490bdfe7415b9c6af2Yigit Boyar } 419999c3976674d20b0de5425490bdfe7415b9c6af2Yigit Boyar} 420