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
17package android.support.v7.widget;
18
19import static android.support.v7.widget.LinearLayoutManager.HORIZONTAL;
20import static android.support.v7.widget.LinearLayoutManager.VERTICAL;
21
22import static org.junit.Assert.assertEquals;
23import static org.junit.Assert.assertTrue;
24
25import android.os.Build;
26import android.support.test.filters.MediumTest;
27import android.support.test.filters.SdkSuppress;
28
29import org.junit.Test;
30import org.junit.runner.RunWith;
31import org.junit.runners.Parameterized;
32
33import java.util.ArrayList;
34import java.util.List;
35import java.util.concurrent.TimeUnit;
36
37@RunWith(Parameterized.class)
38@SdkSuppress(minSdkVersion = Build.VERSION_CODES.LOLLIPOP)
39public class GridLayoutManagerCacheTest extends BaseGridLayoutManagerTest {
40
41    final Config mConfig;
42    final int mDx;
43    final int mDy;
44
45    public GridLayoutManagerCacheTest(Config config, int dx, int dy) {
46        mConfig = config;
47        mDx = dx;
48        mDy = dy;
49    }
50
51    @Parameterized.Parameters(name = "config:{0},dx:{1},dy:{2}")
52    public static List<Object[]> getParams() {
53        List<Object[]> result = new ArrayList<>();
54        List<Config> configs = createBaseVariations();
55        for (Config config : configs) {
56            for (int dx : new int[] {-1, 0, 1}) {
57                for (int dy : new int[] {-1, 0, 1}) {
58                    result.add(new Object[]{config, dx, dy});
59                }
60            }
61        }
62        return result;
63    }
64
65    private ArrayList<RecyclerView.ViewHolder> cachedViews() {
66        return mRecyclerView.mRecycler.mCachedViews;
67    }
68
69    private boolean cachedViewsContains(int position) {
70        // Note: can't make assumptions about order here, so just check all cached views
71        for (int i = 0; i < cachedViews().size(); i++) {
72            if (cachedViews().get(i).getAdapterPosition() == position) return true;
73        }
74        return false;
75    }
76
77    @MediumTest
78    @Test
79    public void cacheAndPrefetch() throws Throwable {
80        final Config config = (Config) mConfig.clone();
81        RecyclerView recyclerView = setupBasic(config);
82        waitForFirstLayout(recyclerView);
83
84
85        mActivityRule.runOnUiThread(new Runnable() {
86            @Override
87            public void run() {
88                // pretend to have an extra 5s before next frame so prefetch won't abort early
89                ((WrappedRecyclerView)mRecyclerView).setDrawingTimeOffset(5000);
90
91                // scroll to the middle, so we can move in either direction
92                mRecyclerView.scrollToPosition(mConfig.mItemCount / 2);
93            }
94        });
95
96        mRecyclerView.setItemViewCacheSize(0);
97        {
98            mGlm.expectPrefetch(1);
99            mActivityRule.runOnUiThread(new Runnable() {
100                @Override
101                public void run() {
102                    mRecyclerView.mRecycler.recycleAndClearCachedViews();
103                    mRecyclerView.mGapWorker.postFromTraversal(mRecyclerView, mDx, mDy);
104
105                    // Lie about post time, so prefetch executes even if it is delayed
106                    mRecyclerView.mGapWorker.mPostTimeNs += TimeUnit.SECONDS.toNanos(5);
107                }
108            });
109            mGlm.waitForPrefetch(1);
110        }
111
112        mActivityRule.runOnUiThread(new Runnable() {
113            @Override
114            public void run() {
115                // validate cache state on UI thread
116                if ((config.mOrientation == HORIZONTAL && mDx == 0)
117                        || (config.mOrientation == VERTICAL && mDy == 0)) {
118                    assertEquals(0, cachedViews().size());
119                } else {
120                    assertEquals(config.mSpanCount, cachedViews().size());
121
122                    boolean reverseScroll = config.mOrientation == HORIZONTAL ? mDx < 0 : mDy < 0;
123                    int lastVisibleItemPosition = mGlm.findLastVisibleItemPosition();
124                    int firstVisibleItemPosition = mGlm.findFirstVisibleItemPosition();
125
126                    for (int i = 0; i < config.mSpanCount; i++) {
127                        if (mConfig.mReverseLayout == reverseScroll) {
128                            // Pos scroll on pos layout, or reverse scroll on reverse layout
129                            // = toward last
130                            assertTrue(cachedViewsContains(lastVisibleItemPosition + 1 + i));
131                        } else {
132                            // Pos scroll on reverse layout, or reverse scroll on pos layout
133                            // = toward first
134                            assertTrue(cachedViewsContains(firstVisibleItemPosition - 1 - i));
135                        }
136                    }
137                }
138            }
139        });
140    }
141}
142