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 androidx.recyclerview.widget.LinearLayoutManager.HORIZONTAL;
20import static androidx.recyclerview.widget.LinearLayoutManager.VERTICAL;
21
22import static org.junit.Assert.assertEquals;
23
24import android.os.Build;
25import android.support.test.filters.MediumTest;
26import android.support.test.filters.SdkSuppress;
27
28import org.junit.Test;
29import org.junit.runner.RunWith;
30import org.junit.runners.Parameterized;
31
32import java.util.ArrayList;
33import java.util.List;
34import java.util.concurrent.TimeUnit;
35
36@RunWith(Parameterized.class)
37@SdkSuppress(minSdkVersion = Build.VERSION_CODES.LOLLIPOP)
38public class LinearLayoutManagerCacheTest extends BaseLinearLayoutManagerTest {
39
40    final Config mConfig;
41    final int mDx;
42    final int mDy;
43
44    public LinearLayoutManagerCacheTest(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    @MediumTest
69    @Test
70    public void cacheAndPrefetch() throws Throwable {
71        final Config config = (Config) mConfig.clone();
72
73        setupByConfig(config, true);
74
75        mActivityRule.runOnUiThread(new Runnable() {
76            @Override
77            public void run() {
78                // pretend to have an extra 5s before next frame so prefetch won't abort early
79                ((WrappedRecyclerView)mRecyclerView).setDrawingTimeOffset(5000);
80
81                mRecyclerView.scrollToPosition(100);
82            }
83        });
84
85        mRecyclerView.setItemViewCacheSize(0);
86        {
87            mLayoutManager.expectPrefetch(1);
88            mActivityRule.runOnUiThread(new Runnable() {
89                @Override
90                public void run() {
91                    mRecyclerView.mRecycler.recycleAndClearCachedViews();
92                    mRecyclerView.mGapWorker.postFromTraversal(mRecyclerView, mDx, mDy);
93
94                    // Lie about post time, so prefetch executes even if it is delayed
95                    mRecyclerView.mGapWorker.mPostTimeNs += TimeUnit.SECONDS.toNanos(5);
96                }
97            });
98            mLayoutManager.waitForPrefetch(1);
99        }
100
101
102        mActivityRule.runOnUiThread(new Runnable() {
103            @Override
104            public void run() {
105                // validate cache state on UI thread
106                if ((config.mOrientation == HORIZONTAL && mDx == 0)
107                        || (config.mOrientation == VERTICAL && mDy == 0)) {
108                    assertEquals(0, cachedViews().size());
109                } else {
110                    boolean reverseScroll = config.mOrientation == HORIZONTAL ? mDx < 0 : mDy < 0;
111                    int lastVisibleItemPosition = mLayoutManager.findLastVisibleItemPosition();
112                    int firstVisibleItemPosition = mLayoutManager.findFirstVisibleItemPosition();
113                    assertEquals(1, cachedViews().size());
114                    int prefetchedPosition = cachedViews().get(0).getAdapterPosition();
115                    if (mConfig.mReverseLayout == reverseScroll) {
116                        // Pos scroll on pos layout, or reverse scroll on reverse layout = toward last
117                        assertEquals(lastVisibleItemPosition + 1, prefetchedPosition);
118                    } else {
119                        // Pos scroll on reverse layout, or reverse scroll on pos layout = toward first
120                        assertEquals(firstVisibleItemPosition - 1, prefetchedPosition);
121                    }
122                }
123            }
124        });
125    }
126}
127