11ce43e3fefb07662431e9fffe62c40242a52cac6Chris Craik/*
21ce43e3fefb07662431e9fffe62c40242a52cac6Chris Craik * Copyright (C) 2016 The Android Open Source Project
31ce43e3fefb07662431e9fffe62c40242a52cac6Chris Craik *
41ce43e3fefb07662431e9fffe62c40242a52cac6Chris Craik * Licensed under the Apache License, Version 2.0 (the "License");
51ce43e3fefb07662431e9fffe62c40242a52cac6Chris Craik * you may not use this file except in compliance with the License.
61ce43e3fefb07662431e9fffe62c40242a52cac6Chris Craik * You may obtain a copy of the License at
71ce43e3fefb07662431e9fffe62c40242a52cac6Chris Craik *
81ce43e3fefb07662431e9fffe62c40242a52cac6Chris Craik *      http://www.apache.org/licenses/LICENSE-2.0
91ce43e3fefb07662431e9fffe62c40242a52cac6Chris Craik *
101ce43e3fefb07662431e9fffe62c40242a52cac6Chris Craik * Unless required by applicable law or agreed to in writing, software
111ce43e3fefb07662431e9fffe62c40242a52cac6Chris Craik * distributed under the License is distributed on an "AS IS" BASIS,
121ce43e3fefb07662431e9fffe62c40242a52cac6Chris Craik * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
131ce43e3fefb07662431e9fffe62c40242a52cac6Chris Craik * See the License for the specific language governing permissions and
141ce43e3fefb07662431e9fffe62c40242a52cac6Chris Craik * limitations under the License.
151ce43e3fefb07662431e9fffe62c40242a52cac6Chris Craik */
161ce43e3fefb07662431e9fffe62c40242a52cac6Chris Craik
171ce43e3fefb07662431e9fffe62c40242a52cac6Chris Craikpackage android.support.v7.widget;
181ce43e3fefb07662431e9fffe62c40242a52cac6Chris Craik
19e9f9cd8d0e9008340985d17a2541ab24b3adb391Aurimas Liutikasimport static android.support.v7.widget.LinearLayoutManager.HORIZONTAL;
20e9f9cd8d0e9008340985d17a2541ab24b3adb391Aurimas Liutikasimport static android.support.v7.widget.LinearLayoutManager.VERTICAL;
21e9f9cd8d0e9008340985d17a2541ab24b3adb391Aurimas Liutikas
22e9f9cd8d0e9008340985d17a2541ab24b3adb391Aurimas Liutikasimport static junit.framework.Assert.assertEquals;
23e9f9cd8d0e9008340985d17a2541ab24b3adb391Aurimas Liutikasimport static junit.framework.Assert.assertTrue;
24e9f9cd8d0e9008340985d17a2541ab24b3adb391Aurimas Liutikas
25d8b7d71ac23c6bf56d2ae774eccb41731f77205aChris Craikimport android.os.Build;
26754cb29c50f09a83251dd4bb633ba445b2411adbAurimas Liutikasimport android.support.test.filters.MediumTest;
27d8b7d71ac23c6bf56d2ae774eccb41731f77205aChris Craikimport android.support.test.filters.SdkSuppress;
281ce43e3fefb07662431e9fffe62c40242a52cac6Chris Craik
291ce43e3fefb07662431e9fffe62c40242a52cac6Chris Craikimport org.junit.Test;
301ce43e3fefb07662431e9fffe62c40242a52cac6Chris Craikimport org.junit.runner.RunWith;
311ce43e3fefb07662431e9fffe62c40242a52cac6Chris Craikimport org.junit.runners.Parameterized;
321ce43e3fefb07662431e9fffe62c40242a52cac6Chris Craik
331ce43e3fefb07662431e9fffe62c40242a52cac6Chris Craikimport java.util.ArrayList;
341ce43e3fefb07662431e9fffe62c40242a52cac6Chris Craikimport java.util.Arrays;
351ce43e3fefb07662431e9fffe62c40242a52cac6Chris Craikimport java.util.List;
361ce43e3fefb07662431e9fffe62c40242a52cac6Chris Craikimport java.util.concurrent.TimeUnit;
371ce43e3fefb07662431e9fffe62c40242a52cac6Chris Craik
381ce43e3fefb07662431e9fffe62c40242a52cac6Chris Craik@RunWith(Parameterized.class)
39d8b7d71ac23c6bf56d2ae774eccb41731f77205aChris Craik@SdkSuppress(minSdkVersion = Build.VERSION_CODES.LOLLIPOP)
401ce43e3fefb07662431e9fffe62c40242a52cac6Chris Craikpublic class StaggeredGridLayoutManagerCacheTest extends BaseStaggeredGridLayoutManagerTest {
411ce43e3fefb07662431e9fffe62c40242a52cac6Chris Craik
421ce43e3fefb07662431e9fffe62c40242a52cac6Chris Craik    final Config mConfig;
431ce43e3fefb07662431e9fffe62c40242a52cac6Chris Craik    final int mDx;
441ce43e3fefb07662431e9fffe62c40242a52cac6Chris Craik    final int mDy;
451ce43e3fefb07662431e9fffe62c40242a52cac6Chris Craik
461ce43e3fefb07662431e9fffe62c40242a52cac6Chris Craik    public StaggeredGridLayoutManagerCacheTest(Config config, int dx, int dy) {
471ce43e3fefb07662431e9fffe62c40242a52cac6Chris Craik        mConfig = config;
481ce43e3fefb07662431e9fffe62c40242a52cac6Chris Craik        mDx = dx;
491ce43e3fefb07662431e9fffe62c40242a52cac6Chris Craik        mDy = dy;
501ce43e3fefb07662431e9fffe62c40242a52cac6Chris Craik    }
511ce43e3fefb07662431e9fffe62c40242a52cac6Chris Craik
52e9f9cd8d0e9008340985d17a2541ab24b3adb391Aurimas Liutikas    @Parameterized.Parameters(name = "config:{0},dx:{1},dy:{2}")
531ce43e3fefb07662431e9fffe62c40242a52cac6Chris Craik    public static List<Object[]> getParams() {
541ce43e3fefb07662431e9fffe62c40242a52cac6Chris Craik        List<Object[]> result = new ArrayList<>();
551ce43e3fefb07662431e9fffe62c40242a52cac6Chris Craik        List<Config> configs = createBaseVariations();
561ce43e3fefb07662431e9fffe62c40242a52cac6Chris Craik        for (Config config : configs) {
571ce43e3fefb07662431e9fffe62c40242a52cac6Chris Craik            for (int dx : new int[] {-1, 0, 1}) {
581ce43e3fefb07662431e9fffe62c40242a52cac6Chris Craik                for (int dy : new int[] {-1, 0, 1}) {
591ce43e3fefb07662431e9fffe62c40242a52cac6Chris Craik                    result.add(new Object[]{config, dx, dy});
601ce43e3fefb07662431e9fffe62c40242a52cac6Chris Craik                }
611ce43e3fefb07662431e9fffe62c40242a52cac6Chris Craik            }
621ce43e3fefb07662431e9fffe62c40242a52cac6Chris Craik        }
631ce43e3fefb07662431e9fffe62c40242a52cac6Chris Craik        return result;
641ce43e3fefb07662431e9fffe62c40242a52cac6Chris Craik    }
651ce43e3fefb07662431e9fffe62c40242a52cac6Chris Craik
661ce43e3fefb07662431e9fffe62c40242a52cac6Chris Craik    private ArrayList<RecyclerView.ViewHolder> cachedViews() {
671ce43e3fefb07662431e9fffe62c40242a52cac6Chris Craik        return mRecyclerView.mRecycler.mCachedViews;
681ce43e3fefb07662431e9fffe62c40242a52cac6Chris Craik    }
691ce43e3fefb07662431e9fffe62c40242a52cac6Chris Craik
701ce43e3fefb07662431e9fffe62c40242a52cac6Chris Craik    private boolean cachedViewsContains(int position) {
711ce43e3fefb07662431e9fffe62c40242a52cac6Chris Craik        // Note: can't make assumptions about order here, so just check all cached views
721ce43e3fefb07662431e9fffe62c40242a52cac6Chris Craik        for (int i = 0; i < cachedViews().size(); i++) {
731ce43e3fefb07662431e9fffe62c40242a52cac6Chris Craik            if (cachedViews().get(i).getAdapterPosition() == position) return true;
741ce43e3fefb07662431e9fffe62c40242a52cac6Chris Craik        }
751ce43e3fefb07662431e9fffe62c40242a52cac6Chris Craik        return false;
761ce43e3fefb07662431e9fffe62c40242a52cac6Chris Craik    }
771ce43e3fefb07662431e9fffe62c40242a52cac6Chris Craik
781ce43e3fefb07662431e9fffe62c40242a52cac6Chris Craik    public int findFirstVisibleItemPosition() {
791ce43e3fefb07662431e9fffe62c40242a52cac6Chris Craik        int[] positions = new int[mConfig.mSpanCount];
801ce43e3fefb07662431e9fffe62c40242a52cac6Chris Craik        mLayoutManager.findFirstVisibleItemPositions(positions);
811ce43e3fefb07662431e9fffe62c40242a52cac6Chris Craik        Arrays.sort(positions);
821ce43e3fefb07662431e9fffe62c40242a52cac6Chris Craik        return positions[0];
831ce43e3fefb07662431e9fffe62c40242a52cac6Chris Craik    }
841ce43e3fefb07662431e9fffe62c40242a52cac6Chris Craik
851ce43e3fefb07662431e9fffe62c40242a52cac6Chris Craik    public int findLastVisibleItemPosition() {
861ce43e3fefb07662431e9fffe62c40242a52cac6Chris Craik        int[] positions = new int[mConfig.mSpanCount];
871ce43e3fefb07662431e9fffe62c40242a52cac6Chris Craik        mLayoutManager.findLastVisibleItemPositions(positions);
881ce43e3fefb07662431e9fffe62c40242a52cac6Chris Craik        Arrays.sort(positions);
891ce43e3fefb07662431e9fffe62c40242a52cac6Chris Craik        return positions[mConfig.mSpanCount - 1];
901ce43e3fefb07662431e9fffe62c40242a52cac6Chris Craik    }
911ce43e3fefb07662431e9fffe62c40242a52cac6Chris Craik
921ce43e3fefb07662431e9fffe62c40242a52cac6Chris Craik    @MediumTest
931ce43e3fefb07662431e9fffe62c40242a52cac6Chris Craik    @Test
941ce43e3fefb07662431e9fffe62c40242a52cac6Chris Craik    public void cacheAndPrefetch() throws Throwable {
951ce43e3fefb07662431e9fffe62c40242a52cac6Chris Craik        final Config config = (Config) mConfig.clone();
961ce43e3fefb07662431e9fffe62c40242a52cac6Chris Craik        setupByConfig(config);
971ce43e3fefb07662431e9fffe62c40242a52cac6Chris Craik        waitFirstLayout();
981ce43e3fefb07662431e9fffe62c40242a52cac6Chris Craik
9942e7d6fafcde7bfe261dd7d8d75ee53ca0cd6790Aurimas Liutikas        mActivityRule.runOnUiThread(new Runnable() {
1001ce43e3fefb07662431e9fffe62c40242a52cac6Chris Craik            @Override
1011ce43e3fefb07662431e9fffe62c40242a52cac6Chris Craik            public void run() {
1021ce43e3fefb07662431e9fffe62c40242a52cac6Chris Craik                // pretend to have an extra 5s before next frame so prefetch won't abort early
1031ce43e3fefb07662431e9fffe62c40242a52cac6Chris Craik                ((WrappedRecyclerView)mRecyclerView).setDrawingTimeOffset(5000);
1041ce43e3fefb07662431e9fffe62c40242a52cac6Chris Craik
1051ce43e3fefb07662431e9fffe62c40242a52cac6Chris Craik                // scroll to the middle, so we can move in either direction
1061ce43e3fefb07662431e9fffe62c40242a52cac6Chris Craik                mRecyclerView.scrollToPosition(mConfig.mItemCount / 2);
1071ce43e3fefb07662431e9fffe62c40242a52cac6Chris Craik            }
1081ce43e3fefb07662431e9fffe62c40242a52cac6Chris Craik        });
1091ce43e3fefb07662431e9fffe62c40242a52cac6Chris Craik
1101ce43e3fefb07662431e9fffe62c40242a52cac6Chris Craik        mRecyclerView.setItemViewCacheSize(0);
1111ce43e3fefb07662431e9fffe62c40242a52cac6Chris Craik        {
1121ce43e3fefb07662431e9fffe62c40242a52cac6Chris Craik            mLayoutManager.expectPrefetch(1);
11342e7d6fafcde7bfe261dd7d8d75ee53ca0cd6790Aurimas Liutikas            mActivityRule.runOnUiThread(new Runnable() {
1141ce43e3fefb07662431e9fffe62c40242a52cac6Chris Craik                @Override
1151ce43e3fefb07662431e9fffe62c40242a52cac6Chris Craik                public void run() {
1161ce43e3fefb07662431e9fffe62c40242a52cac6Chris Craik                    mRecyclerView.mRecycler.recycleAndClearCachedViews();
11707b2e072ee7e8f424eb95abc77695dc2c5a786bbChris Craik                    mRecyclerView.mGapWorker.postFromTraversal(mRecyclerView, mDx, mDy);
1181ce43e3fefb07662431e9fffe62c40242a52cac6Chris Craik
1191ce43e3fefb07662431e9fffe62c40242a52cac6Chris Craik                    // Lie about post time, so prefetch executes even if it is delayed
12007b2e072ee7e8f424eb95abc77695dc2c5a786bbChris Craik                    mRecyclerView.mGapWorker.mPostTimeNs += TimeUnit.SECONDS.toNanos(5);
1211ce43e3fefb07662431e9fffe62c40242a52cac6Chris Craik                }
1221ce43e3fefb07662431e9fffe62c40242a52cac6Chris Craik            });
1231ce43e3fefb07662431e9fffe62c40242a52cac6Chris Craik            mLayoutManager.waitForPrefetch(1);
1241ce43e3fefb07662431e9fffe62c40242a52cac6Chris Craik        }
1251ce43e3fefb07662431e9fffe62c40242a52cac6Chris Craik
12642e7d6fafcde7bfe261dd7d8d75ee53ca0cd6790Aurimas Liutikas        mActivityRule.runOnUiThread(new Runnable() {
1271ce43e3fefb07662431e9fffe62c40242a52cac6Chris Craik            @Override
1281ce43e3fefb07662431e9fffe62c40242a52cac6Chris Craik            public void run() {
1291ce43e3fefb07662431e9fffe62c40242a52cac6Chris Craik                // validate cache state on UI thread
1301ce43e3fefb07662431e9fffe62c40242a52cac6Chris Craik                if ((config.mOrientation == HORIZONTAL && mDx == 0)
1311ce43e3fefb07662431e9fffe62c40242a52cac6Chris Craik                        || (config.mOrientation == VERTICAL && mDy == 0)) {
1321ce43e3fefb07662431e9fffe62c40242a52cac6Chris Craik                    assertEquals(0, cachedViews().size());
1331ce43e3fefb07662431e9fffe62c40242a52cac6Chris Craik                } else {
1341ce43e3fefb07662431e9fffe62c40242a52cac6Chris Craik                    assertEquals(config.mSpanCount, cachedViews().size());
1351ce43e3fefb07662431e9fffe62c40242a52cac6Chris Craik
1361ce43e3fefb07662431e9fffe62c40242a52cac6Chris Craik                    boolean reverseScroll = config.mOrientation == HORIZONTAL ? mDx < 0 : mDy < 0;
1371ce43e3fefb07662431e9fffe62c40242a52cac6Chris Craik                    int lastVisibleItemPosition = findLastVisibleItemPosition();
1381ce43e3fefb07662431e9fffe62c40242a52cac6Chris Craik                    int firstVisibleItemPosition = findFirstVisibleItemPosition();
1391ce43e3fefb07662431e9fffe62c40242a52cac6Chris Craik
1401ce43e3fefb07662431e9fffe62c40242a52cac6Chris Craik                    for (int i = 0; i < config.mSpanCount; i++) {
1411ce43e3fefb07662431e9fffe62c40242a52cac6Chris Craik                        if (mConfig.mReverseLayout == reverseScroll) {
1421ce43e3fefb07662431e9fffe62c40242a52cac6Chris Craik                            // Pos scroll on pos layout, or reverse scroll on reverse layout
1431ce43e3fefb07662431e9fffe62c40242a52cac6Chris Craik                            // = toward last
1441ce43e3fefb07662431e9fffe62c40242a52cac6Chris Craik                            assertTrue(cachedViewsContains(lastVisibleItemPosition + 1 + i));
1451ce43e3fefb07662431e9fffe62c40242a52cac6Chris Craik                        } else {
1461ce43e3fefb07662431e9fffe62c40242a52cac6Chris Craik                            // Pos scroll on reverse layout, or reverse scroll on pos layout
1471ce43e3fefb07662431e9fffe62c40242a52cac6Chris Craik                            // = toward first
1481ce43e3fefb07662431e9fffe62c40242a52cac6Chris Craik                            assertTrue(cachedViewsContains(firstVisibleItemPosition - 1 - i));
1491ce43e3fefb07662431e9fffe62c40242a52cac6Chris Craik                        }
1501ce43e3fefb07662431e9fffe62c40242a52cac6Chris Craik                    }
1511ce43e3fefb07662431e9fffe62c40242a52cac6Chris Craik                }
1521ce43e3fefb07662431e9fffe62c40242a52cac6Chris Craik            }
1531ce43e3fefb07662431e9fffe62c40242a52cac6Chris Craik        });
1541ce43e3fefb07662431e9fffe62c40242a52cac6Chris Craik    }
1551ce43e3fefb07662431e9fffe62c40242a52cac6Chris Craik}
156