• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
1 /*
2  * Copyright (C) 2016 The Android Open Source Project
3  *
4  * Licensed under the Apache License, Version 2.0 (the "License");
5  * you may not use this file except in compliance with the License.
6  * You may obtain a copy of the License at
7  *
8  *      http://www.apache.org/licenses/LICENSE-2.0
9  *
10  * Unless required by applicable law or agreed to in writing, software
11  * distributed under the License is distributed on an "AS IS" BASIS,
12  * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13  * See the License for the specific language governing permissions and
14  * limitations under the License.
15  */
16 package android.support.v7.widget;
17 
18 import android.content.Context;
19 import android.view.View;
20 
21 import java.lang.reflect.Field;
22 import java.util.ArrayList;
23 import java.util.HashSet;
24 import java.util.List;
25 import java.util.Set;
26 import java.util.concurrent.CountDownLatch;
27 
28 import static android.support.v7.widget.LinearLayoutManager.HORIZONTAL;
29 import static android.support.v7.widget.LinearLayoutManager.VERTICAL;
30 import static java.util.concurrent.TimeUnit.SECONDS;
31 import static org.junit.Assert.assertEquals;
32 
33 import org.hamcrest.CoreMatchers;
34 import org.hamcrest.MatcherAssert;
35 
36 public class BaseGridLayoutManagerTest extends BaseRecyclerViewInstrumentationTest {
37 
38     static final String TAG = "GridLayoutManagerTest";
39     static final boolean DEBUG = false;
40 
41     WrappedGridLayoutManager mGlm;
42     GridTestAdapter mAdapter;
43 
setupBasic(Config config)44     public RecyclerView setupBasic(Config config) throws Throwable {
45         return setupBasic(config, new GridTestAdapter(config.mItemCount));
46     }
47 
setupBasic(Config config, GridTestAdapter testAdapter)48     public RecyclerView setupBasic(Config config, GridTestAdapter testAdapter) throws Throwable {
49         RecyclerView recyclerView = new RecyclerView(getActivity());
50         mAdapter = testAdapter;
51         mGlm = new WrappedGridLayoutManager(getActivity(), config.mSpanCount, config.mOrientation,
52                 config.mReverseLayout);
53         mAdapter.assignSpanSizeLookup(mGlm);
54         recyclerView.setAdapter(mAdapter);
55         recyclerView.setLayoutManager(mGlm);
56         return recyclerView;
57     }
58 
createBaseVariations()59     public static List<Config> createBaseVariations() {
60         List<Config> variations = new ArrayList<>();
61         for (int orientation : new int[]{VERTICAL, HORIZONTAL}) {
62             for (boolean reverseLayout : new boolean[]{false, true}) {
63                 for (int spanCount : new int[]{1, 3, 4}) {
64                     variations.add(new Config(spanCount, orientation, reverseLayout));
65                 }
66             }
67         }
68         return variations;
69     }
70 
addConfigVariation(List<Config> base, String fieldName, Object... variations)71     protected static List<Config> addConfigVariation(List<Config> base, String fieldName,
72             Object... variations)
73             throws CloneNotSupportedException, NoSuchFieldException, IllegalAccessException {
74         List<Config> newConfigs = new ArrayList<Config>();
75         Field field = Config.class.getDeclaredField(fieldName);
76         for (Config config : base) {
77             for (Object variation : variations) {
78                 Config newConfig = (Config) config.clone();
79                 field.set(newConfig, variation);
80                 newConfigs.add(newConfig);
81             }
82         }
83         return newConfigs;
84     }
85 
waitForFirstLayout(RecyclerView recyclerView)86     public void waitForFirstLayout(RecyclerView recyclerView) throws Throwable {
87         mGlm.expectLayout(1);
88         setRecyclerView(recyclerView);
89         mGlm.waitForLayout(2);
90     }
91 
getSize(View view)92     protected int getSize(View view) {
93         if (mGlm.getOrientation() == GridLayoutManager.HORIZONTAL) {
94             return view.getWidth();
95         }
96         return view.getHeight();
97     }
98 
getLp(View view)99     GridLayoutManager.LayoutParams getLp(View view) {
100         return (GridLayoutManager.LayoutParams) view.getLayoutParams();
101     }
102 
103     static class Config implements Cloneable {
104 
105         int mSpanCount;
106         int mOrientation = GridLayoutManager.VERTICAL;
107         int mItemCount = 1000;
108         int mSpanPerItem = 1;
109         boolean mReverseLayout = false;
110 
Config(int spanCount, int itemCount)111         Config(int spanCount, int itemCount) {
112             mSpanCount = spanCount;
113             mItemCount = itemCount;
114         }
115 
Config(int spanCount, int orientation, boolean reverseLayout)116         public Config(int spanCount, int orientation, boolean reverseLayout) {
117             mSpanCount = spanCount;
118             mOrientation = orientation;
119             mReverseLayout = reverseLayout;
120         }
121 
orientation(int orientation)122         Config orientation(int orientation) {
123             mOrientation = orientation;
124             return this;
125         }
126 
127         @Override
toString()128         public String toString() {
129             return "Config{" +
130                     "mSpanCount=" + mSpanCount +
131                     ", mOrientation=" + (mOrientation == GridLayoutManager.HORIZONTAL ? "h" : "v") +
132                     ", mItemCount=" + mItemCount +
133                     ", mReverseLayout=" + mReverseLayout +
134                     '}';
135         }
136 
reverseLayout(boolean reverseLayout)137         public Config reverseLayout(boolean reverseLayout) {
138             mReverseLayout = reverseLayout;
139             return this;
140         }
141 
142         @Override
clone()143         protected Object clone() throws CloneNotSupportedException {
144             return super.clone();
145         }
146     }
147 
148     class WrappedGridLayoutManager extends GridLayoutManager {
149 
150         CountDownLatch mLayoutLatch;
151 
152         List<GridLayoutManagerTest.Callback>
153                 mCallbacks = new ArrayList<GridLayoutManagerTest.Callback>();
154 
155         Boolean mFakeRTL;
156         private CountDownLatch snapLatch;
157 
WrappedGridLayoutManager(Context context, int spanCount)158         public WrappedGridLayoutManager(Context context, int spanCount) {
159             super(context, spanCount);
160         }
161 
WrappedGridLayoutManager(Context context, int spanCount, int orientation, boolean reverseLayout)162         public WrappedGridLayoutManager(Context context, int spanCount, int orientation,
163                 boolean reverseLayout) {
164             super(context, spanCount, orientation, reverseLayout);
165         }
166 
167         @Override
isLayoutRTL()168         protected boolean isLayoutRTL() {
169             return mFakeRTL == null ? super.isLayoutRTL() : mFakeRTL;
170         }
171 
setFakeRtl(Boolean fakeRtl)172         public void setFakeRtl(Boolean fakeRtl) {
173             mFakeRTL = fakeRtl;
174             try {
175                 requestLayoutOnUIThread(mRecyclerView);
176             } catch (Throwable throwable) {
177                 postExceptionToInstrumentation(throwable);
178             }
179         }
180 
181         @Override
onLayoutChildren(RecyclerView.Recycler recycler, RecyclerView.State state)182         public void onLayoutChildren(RecyclerView.Recycler recycler, RecyclerView.State state) {
183             try {
184                 for (GridLayoutManagerTest.Callback callback : mCallbacks) {
185                     callback.onBeforeLayout(recycler, state);
186                 }
187                 super.onLayoutChildren(recycler, state);
188                 for (GridLayoutManagerTest.Callback callback : mCallbacks) {
189                     callback.onAfterLayout(recycler, state);
190                 }
191             } catch (Throwable t) {
192                 postExceptionToInstrumentation(t);
193             }
194             mLayoutLatch.countDown();
195         }
196 
197         @Override
createLayoutState()198         LayoutState createLayoutState() {
199             return new LayoutState() {
200                 @Override
201                 View next(RecyclerView.Recycler recycler) {
202                     final boolean hadMore = hasMore(mRecyclerView.mState);
203                     final int position = mCurrentPosition;
204                     View next = super.next(recycler);
205                     assertEquals("if has more, should return a view", hadMore, next != null);
206                     assertEquals("position of the returned view must match current position",
207                             position, RecyclerView.getChildViewHolderInt(next).getLayoutPosition());
208                     return next;
209                 }
210             };
211         }
212 
expectLayout(int layoutCount)213         public void expectLayout(int layoutCount) {
214             mLayoutLatch = new CountDownLatch(layoutCount);
215         }
216 
waitForLayout(int seconds)217         public void waitForLayout(int seconds) throws Throwable {
218             mLayoutLatch.await(seconds * (DEBUG ? 1000 : 1), SECONDS);
219             checkForMainThreadException();
220             MatcherAssert.assertThat("all layouts should complete on time",
221                     mLayoutLatch.getCount(), CoreMatchers.is(0L));
222             // use a runnable to ensure RV layout is finished
223             getInstrumentation().runOnMainSync(new Runnable() {
224                 @Override
225                 public void run() {
226                 }
227             });
228         }
229 
expectIdleState(int count)230         public void expectIdleState(int count) {
231             snapLatch = new CountDownLatch(count);
232             mRecyclerView.addOnScrollListener(new RecyclerView.OnScrollListener() {
233                 @Override
234                 public void onScrollStateChanged(RecyclerView recyclerView, int newState) {
235                     super.onScrollStateChanged(recyclerView, newState);
236                     if (newState == RecyclerView.SCROLL_STATE_IDLE) {
237                         snapLatch.countDown();
238                         if (snapLatch.getCount() == 0L) {
239                             mRecyclerView.removeOnScrollListener(this);
240                         }
241                     }
242                 }
243             });
244         }
245 
waitForSnap(int seconds)246         public void waitForSnap(int seconds) throws Throwable {
247             snapLatch.await(seconds * (DEBUG ? 100 : 1), SECONDS);
248             checkForMainThreadException();
249             MatcherAssert.assertThat("all scrolling should complete on time",
250                     snapLatch.getCount(), CoreMatchers.is(0L));
251             // use a runnable to ensure RV layout is finished
252             getInstrumentation().runOnMainSync(new Runnable() {
253                 @Override
254                 public void run() {
255                 }
256             });
257         }
258     }
259 
260     class GridTestAdapter extends TestAdapter {
261 
262         Set<Integer> mFullSpanItems = new HashSet<Integer>();
263         int mSpanPerItem = 1;
264 
265         GridTestAdapter(int count) {
266             super(count);
267         }
268 
269         GridTestAdapter(int count, int spanPerItem) {
270             super(count);
271             mSpanPerItem = spanPerItem;
272         }
273 
274         void setFullSpan(int... items) {
275             for (int i : items) {
276                 mFullSpanItems.add(i);
277             }
278         }
279 
280         void assignSpanSizeLookup(final GridLayoutManager glm) {
281             glm.setSpanSizeLookup(new GridLayoutManager.SpanSizeLookup() {
282                 @Override
283                 public int getSpanSize(int position) {
284                     return mFullSpanItems.contains(position) ? glm.getSpanCount() : mSpanPerItem;
285                 }
286             });
287         }
288     }
289 
290     class Callback {
291 
292         public void onBeforeLayout(RecyclerView.Recycler recycler, RecyclerView.State state) {
293         }
294 
295         public void onAfterLayout(RecyclerView.Recycler recycler, RecyclerView.State state) {
296         }
297     }
298 }
299