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