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