1/*
2 * Copyright 2018 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
17package androidx.recyclerview.widget;
18
19import static org.junit.Assert.assertEquals;
20import static org.junit.Assert.assertSame;
21import static org.junit.Assert.assertTrue;
22
23import android.support.test.filters.MediumTest;
24import android.util.Log;
25import android.view.View;
26
27import org.junit.Test;
28import org.junit.runner.RunWith;
29import org.junit.runners.Parameterized;
30
31import java.util.Arrays;
32import java.util.BitSet;
33import java.util.List;
34
35@MediumTest
36@RunWith(Parameterized.class)
37public class GridLayoutManagerBaseConfigSetTest extends BaseGridLayoutManagerTest {
38    @Parameterized.Parameters(name = "{0}")
39    public static List<Config> params() {
40        return createBaseVariations();
41    }
42
43    private final Config mConfig;
44
45    public GridLayoutManagerBaseConfigSetTest(Config config) {
46        mConfig = config;
47    }
48
49    @Test
50    public void scrollBackAndPreservePositions() throws Throwable {
51        Config config = (Config) mConfig.clone();
52        config.mItemCount = 150;
53        scrollBackAndPreservePositionsTest(config);
54    }
55
56    public void scrollBackAndPreservePositionsTest(final Config config) throws Throwable {
57        final RecyclerView rv = setupBasic(config);
58        for (int i = 1; i < mAdapter.getItemCount(); i += config.mSpanCount + 2) {
59            mAdapter.setFullSpan(i);
60        }
61        waitForFirstLayout(rv);
62        final int[] globalPositions = new int[mAdapter.getItemCount()];
63        Arrays.fill(globalPositions, Integer.MIN_VALUE);
64        final int scrollStep = (mGlm.mOrientationHelper.getTotalSpace() / 20)
65                * (config.mReverseLayout ? -1 : 1);
66        final String logPrefix = config.toString();
67        final int[] globalPos = new int[1];
68        mActivityRule.runOnUiThread(new Runnable() {
69            @Override
70            public void run() {
71                assertSame("test sanity", mRecyclerView, rv);
72                int globalScrollPosition = 0;
73                int visited = 0;
74                while (visited < mAdapter.getItemCount()) {
75                    for (int i = 0; i < mRecyclerView.getChildCount(); i++) {
76                        View child = mRecyclerView.getChildAt(i);
77                        final int pos = mRecyclerView.getChildLayoutPosition(child);
78                        if (globalPositions[pos] != Integer.MIN_VALUE) {
79                            continue;
80                        }
81                        visited++;
82                        GridLayoutManager.LayoutParams lp = (GridLayoutManager.LayoutParams)
83                                child.getLayoutParams();
84                        if (config.mReverseLayout) {
85                            globalPositions[pos] = globalScrollPosition +
86                                    mGlm.mOrientationHelper.getDecoratedEnd(child);
87                        } else {
88                            globalPositions[pos] = globalScrollPosition +
89                                    mGlm.mOrientationHelper.getDecoratedStart(child);
90                        }
91                        assertEquals(logPrefix + " span index should match",
92                                mGlm.getSpanSizeLookup().getSpanIndex(pos, mGlm.getSpanCount()),
93                                lp.getSpanIndex());
94                    }
95                    int scrolled = mGlm.scrollBy(scrollStep,
96                            mRecyclerView.mRecycler, mRecyclerView.mState);
97                    globalScrollPosition += scrolled;
98                    if (scrolled == 0) {
99                        assertEquals(
100                                logPrefix + " If scroll is complete, all views should be visited",
101                                visited, mAdapter.getItemCount());
102                    }
103                }
104                if (DEBUG) {
105                    Log.d(TAG, "done recording positions " + Arrays.toString(globalPositions));
106                }
107                globalPos[0] = globalScrollPosition;
108            }
109        });
110        checkForMainThreadException();
111        // test sanity, ensure scroll happened
112        mActivityRule.runOnUiThread(new Runnable() {
113            @Override
114            public void run() {
115                final int childCount = mGlm.getChildCount();
116                final BitSet expectedPositions = new BitSet();
117                for (int i = 0; i < childCount; i ++) {
118                    expectedPositions.set(mAdapter.getItemCount() - i - 1);
119                }
120                for (int i = 0; i <childCount; i ++) {
121                    final View view = mGlm.getChildAt(i);
122                    int position = mGlm.getPosition(view);
123                    assertTrue("child position should be in last page", expectedPositions.get(position));
124                }
125            }
126        });
127        getInstrumentation().waitForIdleSync();
128        mActivityRule.runOnUiThread(new Runnable() {
129            @Override
130            public void run() {
131                int globalScrollPosition = globalPos[0];
132                // now scroll back and make sure global positions match
133                BitSet shouldTest = new BitSet(mAdapter.getItemCount());
134                shouldTest.set(0, mAdapter.getItemCount() - 1, true);
135                String assertPrefix = config
136                        + " global pos must match when scrolling in reverse for position ";
137                int scrollAmount = Integer.MAX_VALUE;
138                while (!shouldTest.isEmpty() && scrollAmount != 0) {
139                    for (int i = 0; i < mRecyclerView.getChildCount(); i++) {
140                        View child = mRecyclerView.getChildAt(i);
141                        int pos = mRecyclerView.getChildLayoutPosition(child);
142                        if (!shouldTest.get(pos)) {
143                            continue;
144                        }
145                        GridLayoutManager.LayoutParams lp = (GridLayoutManager.LayoutParams)
146                                child.getLayoutParams();
147                        shouldTest.clear(pos);
148                        int globalPos;
149                        if (config.mReverseLayout) {
150                            globalPos = globalScrollPosition +
151                                    mGlm.mOrientationHelper.getDecoratedEnd(child);
152                        } else {
153                            globalPos = globalScrollPosition +
154                                    mGlm.mOrientationHelper.getDecoratedStart(child);
155                        }
156                        assertEquals(assertPrefix + pos,
157                                globalPositions[pos], globalPos);
158                        assertEquals("span index should match",
159                                mGlm.getSpanSizeLookup().getSpanIndex(pos, mGlm.getSpanCount()),
160                                lp.getSpanIndex());
161                    }
162                    scrollAmount = mGlm.scrollBy(-scrollStep,
163                            mRecyclerView.mRecycler, mRecyclerView.mState);
164                    globalScrollPosition += scrollAmount;
165                }
166                assertTrue("all views should be seen", shouldTest.isEmpty());
167            }
168        });
169        checkForMainThreadException();
170    }
171}
172