RecyclerViewCacheTest.java revision a93a0c08b4543401d8e2df9bd4c88f26d3b72c11
136c4d66a1bc449309f0a3b97d61dd5bf692d181dChris Craik/*
236c4d66a1bc449309f0a3b97d61dd5bf692d181dChris Craik * Copyright (C) 2016 The Android Open Source Project
336c4d66a1bc449309f0a3b97d61dd5bf692d181dChris Craik *
436c4d66a1bc449309f0a3b97d61dd5bf692d181dChris Craik * Licensed under the Apache License, Version 2.0 (the "License");
536c4d66a1bc449309f0a3b97d61dd5bf692d181dChris Craik * you may not use this file except in compliance with the License.
636c4d66a1bc449309f0a3b97d61dd5bf692d181dChris Craik * You may obtain a copy of the License at
736c4d66a1bc449309f0a3b97d61dd5bf692d181dChris Craik *
836c4d66a1bc449309f0a3b97d61dd5bf692d181dChris Craik *      http://www.apache.org/licenses/LICENSE-2.0
936c4d66a1bc449309f0a3b97d61dd5bf692d181dChris Craik *
1036c4d66a1bc449309f0a3b97d61dd5bf692d181dChris Craik * Unless required by applicable law or agreed to in writing, software
1136c4d66a1bc449309f0a3b97d61dd5bf692d181dChris Craik * distributed under the License is distributed on an "AS IS" BASIS,
1236c4d66a1bc449309f0a3b97d61dd5bf692d181dChris Craik * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
1336c4d66a1bc449309f0a3b97d61dd5bf692d181dChris Craik * See the License for the specific language governing permissions and
1436c4d66a1bc449309f0a3b97d61dd5bf692d181dChris Craik * limitations under the License.
1536c4d66a1bc449309f0a3b97d61dd5bf692d181dChris Craik */
1636c4d66a1bc449309f0a3b97d61dd5bf692d181dChris Craik
1736c4d66a1bc449309f0a3b97d61dd5bf692d181dChris Craikpackage android.support.v7.widget;
1836c4d66a1bc449309f0a3b97d61dd5bf692d181dChris Craik
19ee9f1a3ebd8f071cea11fd97f9a3f3d41027189cAurimas Liutikasimport static org.junit.Assert.assertTrue;
20ee9f1a3ebd8f071cea11fd97f9a3f3d41027189cAurimas Liutikasimport static org.junit.Assert.fail;
21ee9f1a3ebd8f071cea11fd97f9a3f3d41027189cAurimas Liutikasimport static org.mockito.Matchers.any;
22ee9f1a3ebd8f071cea11fd97f9a3f3d41027189cAurimas Liutikasimport static org.mockito.Matchers.anyInt;
23ee9f1a3ebd8f071cea11fd97f9a3f3d41027189cAurimas Liutikasimport static org.mockito.Mockito.mock;
24ee9f1a3ebd8f071cea11fd97f9a3f3d41027189cAurimas Liutikasimport static org.mockito.Mockito.never;
25ee9f1a3ebd8f071cea11fd97f9a3f3d41027189cAurimas Liutikasimport static org.mockito.Mockito.times;
26ee9f1a3ebd8f071cea11fd97f9a3f3d41027189cAurimas Liutikasimport static org.mockito.Mockito.verify;
27ee9f1a3ebd8f071cea11fd97f9a3f3d41027189cAurimas Liutikasimport static org.mockito.Mockito.when;
28ee9f1a3ebd8f071cea11fd97f9a3f3d41027189cAurimas Liutikas
2936c4d66a1bc449309f0a3b97d61dd5bf692d181dChris Craikimport android.content.Context;
30e85a5c8c0bfc0aa1ca0796c7bd1f916049e55d68Aurimas Liutikasimport android.os.Build;
3136c4d66a1bc449309f0a3b97d61dd5bf692d181dChris Craikimport android.support.test.InstrumentationRegistry;
32ee9f1a3ebd8f071cea11fd97f9a3f3d41027189cAurimas Liutikasimport android.support.test.filters.SdkSuppress;
3336c4d66a1bc449309f0a3b97d61dd5bf692d181dChris Craikimport android.support.test.runner.AndroidJUnit4;
3436c4d66a1bc449309f0a3b97d61dd5bf692d181dChris Craikimport android.test.suitebuilder.annotation.SmallTest;
3536c4d66a1bc449309f0a3b97d61dd5bf692d181dChris Craikimport android.view.View;
3636c4d66a1bc449309f0a3b97d61dd5bf692d181dChris Craikimport android.view.ViewGroup;
3736c4d66a1bc449309f0a3b97d61dd5bf692d181dChris Craik
3836c4d66a1bc449309f0a3b97d61dd5bf692d181dChris Craikimport org.junit.Before;
3936c4d66a1bc449309f0a3b97d61dd5bf692d181dChris Craikimport org.junit.Test;
4036c4d66a1bc449309f0a3b97d61dd5bf692d181dChris Craikimport org.junit.runner.RunWith;
4136c4d66a1bc449309f0a3b97d61dd5bf692d181dChris Craikimport org.mockito.invocation.InvocationOnMock;
4236c4d66a1bc449309f0a3b97d61dd5bf692d181dChris Craikimport org.mockito.stubbing.Answer;
4336c4d66a1bc449309f0a3b97d61dd5bf692d181dChris Craik
4436c4d66a1bc449309f0a3b97d61dd5bf692d181dChris Craikimport java.util.List;
45a93a0c08b4543401d8e2df9bd4c88f26d3b72c11Chris Craikimport java.util.concurrent.TimeUnit;
4636c4d66a1bc449309f0a3b97d61dd5bf692d181dChris Craik
4736c4d66a1bc449309f0a3b97d61dd5bf692d181dChris Craik@SmallTest
48e85a5c8c0bfc0aa1ca0796c7bd1f916049e55d68Aurimas Liutikas@SdkSuppress(minSdkVersion = Build.VERSION_CODES.LOLLIPOP)
4936c4d66a1bc449309f0a3b97d61dd5bf692d181dChris Craik@RunWith(AndroidJUnit4.class)
5036c4d66a1bc449309f0a3b97d61dd5bf692d181dChris Craikpublic class RecyclerViewCacheTest {
51a93a0c08b4543401d8e2df9bd4c88f26d3b72c11Chris Craik    TimeMockingRecyclerView mRecyclerView;
5236c4d66a1bc449309f0a3b97d61dd5bf692d181dChris Craik    RecyclerView.Recycler mRecycler;
5336c4d66a1bc449309f0a3b97d61dd5bf692d181dChris Craik
54a93a0c08b4543401d8e2df9bd4c88f26d3b72c11Chris Craik    private class TimeMockingRecyclerView extends RecyclerView {
55a93a0c08b4543401d8e2df9bd4c88f26d3b72c11Chris Craik        private long mMockNanoTime = 0;
56a93a0c08b4543401d8e2df9bd4c88f26d3b72c11Chris Craik
57a93a0c08b4543401d8e2df9bd4c88f26d3b72c11Chris Craik        TimeMockingRecyclerView(Context context) {
58a93a0c08b4543401d8e2df9bd4c88f26d3b72c11Chris Craik            super(context);
59a93a0c08b4543401d8e2df9bd4c88f26d3b72c11Chris Craik        }
60a93a0c08b4543401d8e2df9bd4c88f26d3b72c11Chris Craik
61a93a0c08b4543401d8e2df9bd4c88f26d3b72c11Chris Craik        public void registerTimePassingMs(long ms) {
62a93a0c08b4543401d8e2df9bd4c88f26d3b72c11Chris Craik            mMockNanoTime += TimeUnit.MILLISECONDS.toNanos(ms);
63a93a0c08b4543401d8e2df9bd4c88f26d3b72c11Chris Craik        }
64a93a0c08b4543401d8e2df9bd4c88f26d3b72c11Chris Craik
65a93a0c08b4543401d8e2df9bd4c88f26d3b72c11Chris Craik        @Override
66a93a0c08b4543401d8e2df9bd4c88f26d3b72c11Chris Craik        long getNanoTime() {
67a93a0c08b4543401d8e2df9bd4c88f26d3b72c11Chris Craik            return mMockNanoTime;
68a93a0c08b4543401d8e2df9bd4c88f26d3b72c11Chris Craik        }
69a93a0c08b4543401d8e2df9bd4c88f26d3b72c11Chris Craik    };
70a93a0c08b4543401d8e2df9bd4c88f26d3b72c11Chris Craik
7136c4d66a1bc449309f0a3b97d61dd5bf692d181dChris Craik    @Before
7236c4d66a1bc449309f0a3b97d61dd5bf692d181dChris Craik    public void setUp() throws Exception {
73a93a0c08b4543401d8e2df9bd4c88f26d3b72c11Chris Craik        mRecyclerView = new TimeMockingRecyclerView(getContext());
7436c4d66a1bc449309f0a3b97d61dd5bf692d181dChris Craik        mRecycler = mRecyclerView.mRecycler;
7536c4d66a1bc449309f0a3b97d61dd5bf692d181dChris Craik    }
7636c4d66a1bc449309f0a3b97d61dd5bf692d181dChris Craik
7736c4d66a1bc449309f0a3b97d61dd5bf692d181dChris Craik    private Context getContext() {
7836c4d66a1bc449309f0a3b97d61dd5bf692d181dChris Craik        return InstrumentationRegistry.getContext();
7936c4d66a1bc449309f0a3b97d61dd5bf692d181dChris Craik    }
8036c4d66a1bc449309f0a3b97d61dd5bf692d181dChris Craik
8136c4d66a1bc449309f0a3b97d61dd5bf692d181dChris Craik    @Test
8236c4d66a1bc449309f0a3b97d61dd5bf692d181dChris Craik    public void prefetchReusesCacheItems() {
8336c4d66a1bc449309f0a3b97d61dd5bf692d181dChris Craik        RecyclerView.LayoutManager prefetchingLayoutManager = new RecyclerView.LayoutManager() {
8436c4d66a1bc449309f0a3b97d61dd5bf692d181dChris Craik            @Override
8536c4d66a1bc449309f0a3b97d61dd5bf692d181dChris Craik            public RecyclerView.LayoutParams generateDefaultLayoutParams() {
8636c4d66a1bc449309f0a3b97d61dd5bf692d181dChris Craik                return new RecyclerView.LayoutParams(ViewGroup.LayoutParams.WRAP_CONTENT,
8736c4d66a1bc449309f0a3b97d61dd5bf692d181dChris Craik                        ViewGroup.LayoutParams.WRAP_CONTENT);
8836c4d66a1bc449309f0a3b97d61dd5bf692d181dChris Craik            }
8936c4d66a1bc449309f0a3b97d61dd5bf692d181dChris Craik
9036c4d66a1bc449309f0a3b97d61dd5bf692d181dChris Craik            @Override
9136c4d66a1bc449309f0a3b97d61dd5bf692d181dChris Craik            int getItemPrefetchCount() {
9236c4d66a1bc449309f0a3b97d61dd5bf692d181dChris Craik                return 3;
9336c4d66a1bc449309f0a3b97d61dd5bf692d181dChris Craik            }
9436c4d66a1bc449309f0a3b97d61dd5bf692d181dChris Craik
9536c4d66a1bc449309f0a3b97d61dd5bf692d181dChris Craik            @Override
9636c4d66a1bc449309f0a3b97d61dd5bf692d181dChris Craik            int gatherPrefetchIndices(int dx, int dy, RecyclerView.State state, int[] outIndices) {
9736c4d66a1bc449309f0a3b97d61dd5bf692d181dChris Craik                outIndices[0] = 0;
9836c4d66a1bc449309f0a3b97d61dd5bf692d181dChris Craik                outIndices[1] = 1;
9936c4d66a1bc449309f0a3b97d61dd5bf692d181dChris Craik                outIndices[2] = 2;
10036c4d66a1bc449309f0a3b97d61dd5bf692d181dChris Craik                return 3;
10136c4d66a1bc449309f0a3b97d61dd5bf692d181dChris Craik            }
10236c4d66a1bc449309f0a3b97d61dd5bf692d181dChris Craik
10336c4d66a1bc449309f0a3b97d61dd5bf692d181dChris Craik            @Override
10436c4d66a1bc449309f0a3b97d61dd5bf692d181dChris Craik            public void onLayoutChildren(RecyclerView.Recycler recycler, RecyclerView.State state) {
10536c4d66a1bc449309f0a3b97d61dd5bf692d181dChris Craik            }
10636c4d66a1bc449309f0a3b97d61dd5bf692d181dChris Craik        };
10736c4d66a1bc449309f0a3b97d61dd5bf692d181dChris Craik        mRecyclerView.setLayoutManager(prefetchingLayoutManager);
10836c4d66a1bc449309f0a3b97d61dd5bf692d181dChris Craik
10936c4d66a1bc449309f0a3b97d61dd5bf692d181dChris Craik        RecyclerView.Adapter mockAdapter = mock(RecyclerView.Adapter.class);
11036c4d66a1bc449309f0a3b97d61dd5bf692d181dChris Craik        when(mockAdapter.onCreateViewHolder(any(ViewGroup.class), anyInt()))
11136c4d66a1bc449309f0a3b97d61dd5bf692d181dChris Craik                .thenAnswer(new Answer<RecyclerView.ViewHolder>() {
11236c4d66a1bc449309f0a3b97d61dd5bf692d181dChris Craik                    @Override
11336c4d66a1bc449309f0a3b97d61dd5bf692d181dChris Craik                    public RecyclerView.ViewHolder answer(InvocationOnMock invocation)
11436c4d66a1bc449309f0a3b97d61dd5bf692d181dChris Craik                            throws Throwable {
11536c4d66a1bc449309f0a3b97d61dd5bf692d181dChris Craik                        return new RecyclerView.ViewHolder(new View(getContext())) {};
11636c4d66a1bc449309f0a3b97d61dd5bf692d181dChris Craik                    }
11736c4d66a1bc449309f0a3b97d61dd5bf692d181dChris Craik                });
11836c4d66a1bc449309f0a3b97d61dd5bf692d181dChris Craik        when(mockAdapter.getItemCount()).thenReturn(10);
11936c4d66a1bc449309f0a3b97d61dd5bf692d181dChris Craik        mRecyclerView.setAdapter(mockAdapter);
12036c4d66a1bc449309f0a3b97d61dd5bf692d181dChris Craik
12136c4d66a1bc449309f0a3b97d61dd5bf692d181dChris Craik        mRecyclerView.measure(View.MeasureSpec.AT_MOST | 320, View.MeasureSpec.AT_MOST | 240);
12236c4d66a1bc449309f0a3b97d61dd5bf692d181dChris Craik        mRecyclerView.layout(0, 0, 320, 320);
12336c4d66a1bc449309f0a3b97d61dd5bf692d181dChris Craik
12436c4d66a1bc449309f0a3b97d61dd5bf692d181dChris Craik        verify(mockAdapter, never()).onCreateViewHolder(any(ViewGroup.class), anyInt());
12536c4d66a1bc449309f0a3b97d61dd5bf692d181dChris Craik        verify(mockAdapter, never()).onBindViewHolder(
12636c4d66a1bc449309f0a3b97d61dd5bf692d181dChris Craik                any(RecyclerView.ViewHolder.class), anyInt(), any(List.class));
12736c4d66a1bc449309f0a3b97d61dd5bf692d181dChris Craik        assertTrue(mRecycler.mCachedViews.isEmpty());
12836c4d66a1bc449309f0a3b97d61dd5bf692d181dChris Craik
12936c4d66a1bc449309f0a3b97d61dd5bf692d181dChris Craik        // Prefetch multiple times...
13036c4d66a1bc449309f0a3b97d61dd5bf692d181dChris Craik        for (int i = 0; i < 4; i++) {
131a93a0c08b4543401d8e2df9bd4c88f26d3b72c11Chris Craik            GapWorker.layoutPrefetch(RecyclerView.FOREVER_NS, mRecyclerView);
13236c4d66a1bc449309f0a3b97d61dd5bf692d181dChris Craik
13336c4d66a1bc449309f0a3b97d61dd5bf692d181dChris Craik            // ...but should only see the same three items fetched/bound once each
13436c4d66a1bc449309f0a3b97d61dd5bf692d181dChris Craik            verify(mockAdapter, times(3)).onCreateViewHolder(any(ViewGroup.class), anyInt());
13536c4d66a1bc449309f0a3b97d61dd5bf692d181dChris Craik            verify(mockAdapter, times(3)).onBindViewHolder(
13636c4d66a1bc449309f0a3b97d61dd5bf692d181dChris Craik                    any(RecyclerView.ViewHolder.class), anyInt(), any(List.class));
13736c4d66a1bc449309f0a3b97d61dd5bf692d181dChris Craik
13836c4d66a1bc449309f0a3b97d61dd5bf692d181dChris Craik            assertTrue(mRecycler.mCachedViews.size() == 3);
13936c4d66a1bc449309f0a3b97d61dd5bf692d181dChris Craik            verifyCacheContainsPositions(0, 1, 2);
14036c4d66a1bc449309f0a3b97d61dd5bf692d181dChris Craik        }
14136c4d66a1bc449309f0a3b97d61dd5bf692d181dChris Craik    }
14236c4d66a1bc449309f0a3b97d61dd5bf692d181dChris Craik
14336c4d66a1bc449309f0a3b97d61dd5bf692d181dChris Craik    private void verifyCacheContainsPosition(int position) {
14436c4d66a1bc449309f0a3b97d61dd5bf692d181dChris Craik        for (int i = 0; i < mRecycler.mCachedViews.size(); i++) {
14536c4d66a1bc449309f0a3b97d61dd5bf692d181dChris Craik            if (mRecycler.mCachedViews.get(i).mPosition == position) return;
14636c4d66a1bc449309f0a3b97d61dd5bf692d181dChris Craik        }
14736c4d66a1bc449309f0a3b97d61dd5bf692d181dChris Craik        fail("Cache does not contain position " + position);
14836c4d66a1bc449309f0a3b97d61dd5bf692d181dChris Craik    }
14936c4d66a1bc449309f0a3b97d61dd5bf692d181dChris Craik
15036c4d66a1bc449309f0a3b97d61dd5bf692d181dChris Craik    private void verifyCacheContainsPositions(Integer... positions) {
15136c4d66a1bc449309f0a3b97d61dd5bf692d181dChris Craik        for (int i = 0; i < positions.length; i++) {
15236c4d66a1bc449309f0a3b97d61dd5bf692d181dChris Craik            verifyCacheContainsPosition(positions[i]);
15336c4d66a1bc449309f0a3b97d61dd5bf692d181dChris Craik        }
15436c4d66a1bc449309f0a3b97d61dd5bf692d181dChris Craik    }
15536c4d66a1bc449309f0a3b97d61dd5bf692d181dChris Craik
15636c4d66a1bc449309f0a3b97d61dd5bf692d181dChris Craik    @Test
15736c4d66a1bc449309f0a3b97d61dd5bf692d181dChris Craik    public void prefetchItemsNotEvictedWithInserts() {
15836c4d66a1bc449309f0a3b97d61dd5bf692d181dChris Craik        mRecyclerView.setLayoutManager(new GridLayoutManager(getContext(), 3));
15936c4d66a1bc449309f0a3b97d61dd5bf692d181dChris Craik
16036c4d66a1bc449309f0a3b97d61dd5bf692d181dChris Craik        RecyclerView.Adapter mockAdapter = mock(RecyclerView.Adapter.class);
16136c4d66a1bc449309f0a3b97d61dd5bf692d181dChris Craik        when(mockAdapter.onCreateViewHolder(any(ViewGroup.class), anyInt()))
16236c4d66a1bc449309f0a3b97d61dd5bf692d181dChris Craik                .thenAnswer(new Answer<RecyclerView.ViewHolder>() {
16336c4d66a1bc449309f0a3b97d61dd5bf692d181dChris Craik                    @Override
16436c4d66a1bc449309f0a3b97d61dd5bf692d181dChris Craik                    public RecyclerView.ViewHolder answer(InvocationOnMock invocation)
16536c4d66a1bc449309f0a3b97d61dd5bf692d181dChris Craik                            throws Throwable {
16636c4d66a1bc449309f0a3b97d61dd5bf692d181dChris Craik                        return new RecyclerView.ViewHolder(new View(getContext())) {};
16736c4d66a1bc449309f0a3b97d61dd5bf692d181dChris Craik                    }
16836c4d66a1bc449309f0a3b97d61dd5bf692d181dChris Craik                });
16936c4d66a1bc449309f0a3b97d61dd5bf692d181dChris Craik        when(mockAdapter.getItemCount()).thenReturn(100);
17036c4d66a1bc449309f0a3b97d61dd5bf692d181dChris Craik        mRecyclerView.setAdapter(mockAdapter);
17136c4d66a1bc449309f0a3b97d61dd5bf692d181dChris Craik
17236c4d66a1bc449309f0a3b97d61dd5bf692d181dChris Craik
17336c4d66a1bc449309f0a3b97d61dd5bf692d181dChris Craik        mRecyclerView.measure(View.MeasureSpec.AT_MOST | 320, View.MeasureSpec.AT_MOST | 320);
17436c4d66a1bc449309f0a3b97d61dd5bf692d181dChris Craik        mRecyclerView.layout(0, 0, 320, 320);
17536c4d66a1bc449309f0a3b97d61dd5bf692d181dChris Craik
17607b2e072ee7e8f424eb95abc77695dc2c5a786bbChris Craik        mRecyclerView.mPrefetchArray = new int[] { 0, 1, 2 };
177a93a0c08b4543401d8e2df9bd4c88f26d3b72c11Chris Craik        GapWorker.layoutPrefetchImpl(Long.MAX_VALUE, mRecycler, mRecyclerView.mPrefetchArray, 3);
17836c4d66a1bc449309f0a3b97d61dd5bf692d181dChris Craik        verifyCacheContainsPositions(0, 1, 2);
17936c4d66a1bc449309f0a3b97d61dd5bf692d181dChris Craik
18036c4d66a1bc449309f0a3b97d61dd5bf692d181dChris Craik        // further views recycled, as though from scrolling, shouldn't evict prefetched views:
18136c4d66a1bc449309f0a3b97d61dd5bf692d181dChris Craik        mRecycler.recycleView(mRecycler.getViewForPosition(10));
18236c4d66a1bc449309f0a3b97d61dd5bf692d181dChris Craik        verifyCacheContainsPositions(0, 1, 2, 10);
18336c4d66a1bc449309f0a3b97d61dd5bf692d181dChris Craik
18436c4d66a1bc449309f0a3b97d61dd5bf692d181dChris Craik        mRecycler.recycleView(mRecycler.getViewForPosition(20));
18536c4d66a1bc449309f0a3b97d61dd5bf692d181dChris Craik        verifyCacheContainsPositions(0, 1, 2, 10, 20);
18636c4d66a1bc449309f0a3b97d61dd5bf692d181dChris Craik
18736c4d66a1bc449309f0a3b97d61dd5bf692d181dChris Craik        mRecycler.recycleView(mRecycler.getViewForPosition(30));
18836c4d66a1bc449309f0a3b97d61dd5bf692d181dChris Craik        verifyCacheContainsPositions(0, 1, 2, 20, 30);
18936c4d66a1bc449309f0a3b97d61dd5bf692d181dChris Craik
19036c4d66a1bc449309f0a3b97d61dd5bf692d181dChris Craik        mRecycler.recycleView(mRecycler.getViewForPosition(40));
19136c4d66a1bc449309f0a3b97d61dd5bf692d181dChris Craik        verifyCacheContainsPositions(0, 1, 2, 30, 40);
19236c4d66a1bc449309f0a3b97d61dd5bf692d181dChris Craik
19336c4d66a1bc449309f0a3b97d61dd5bf692d181dChris Craik
19436c4d66a1bc449309f0a3b97d61dd5bf692d181dChris Craik        // After clearing the cache, the prefetch priorities should be cleared as well:
19536c4d66a1bc449309f0a3b97d61dd5bf692d181dChris Craik        mRecyclerView.mRecycler.recycleAndClearCachedViews();
19636c4d66a1bc449309f0a3b97d61dd5bf692d181dChris Craik        for (int i : new int[] {0, 1, 2, 50, 60, 70, 80, 90}) {
19736c4d66a1bc449309f0a3b97d61dd5bf692d181dChris Craik            mRecycler.recycleView(mRecycler.getViewForPosition(i));
19836c4d66a1bc449309f0a3b97d61dd5bf692d181dChris Craik        }
19936c4d66a1bc449309f0a3b97d61dd5bf692d181dChris Craik
20036c4d66a1bc449309f0a3b97d61dd5bf692d181dChris Craik        // cache only contains most recent positions, no priority for previous prefetches:
20136c4d66a1bc449309f0a3b97d61dd5bf692d181dChris Craik        verifyCacheContainsPositions(50, 60, 70, 80, 90);
20236c4d66a1bc449309f0a3b97d61dd5bf692d181dChris Craik
20336c4d66a1bc449309f0a3b97d61dd5bf692d181dChris Craik    }
20436c4d66a1bc449309f0a3b97d61dd5bf692d181dChris Craik
20536c4d66a1bc449309f0a3b97d61dd5bf692d181dChris Craik    @Test
20636c4d66a1bc449309f0a3b97d61dd5bf692d181dChris Craik    public void prefetchItemsNotEvictedOnScroll() {
20736c4d66a1bc449309f0a3b97d61dd5bf692d181dChris Craik        mRecyclerView.setLayoutManager(new GridLayoutManager(getContext(), 3));
20836c4d66a1bc449309f0a3b97d61dd5bf692d181dChris Craik
20936c4d66a1bc449309f0a3b97d61dd5bf692d181dChris Craik        // 100x100 pixel views
21036c4d66a1bc449309f0a3b97d61dd5bf692d181dChris Craik        RecyclerView.Adapter mockAdapter = mock(RecyclerView.Adapter.class);
21136c4d66a1bc449309f0a3b97d61dd5bf692d181dChris Craik        when(mockAdapter.onCreateViewHolder(any(ViewGroup.class), anyInt()))
21236c4d66a1bc449309f0a3b97d61dd5bf692d181dChris Craik                .thenAnswer(new Answer<RecyclerView.ViewHolder>() {
21336c4d66a1bc449309f0a3b97d61dd5bf692d181dChris Craik                    @Override
21436c4d66a1bc449309f0a3b97d61dd5bf692d181dChris Craik                    public RecyclerView.ViewHolder answer(InvocationOnMock invocation)
21536c4d66a1bc449309f0a3b97d61dd5bf692d181dChris Craik                            throws Throwable {
21636c4d66a1bc449309f0a3b97d61dd5bf692d181dChris Craik                        View view = new View(getContext());
21736c4d66a1bc449309f0a3b97d61dd5bf692d181dChris Craik                        view.setMinimumWidth(100);
21836c4d66a1bc449309f0a3b97d61dd5bf692d181dChris Craik                        view.setMinimumHeight(100);
21936c4d66a1bc449309f0a3b97d61dd5bf692d181dChris Craik                        return new RecyclerView.ViewHolder(view) {};
22036c4d66a1bc449309f0a3b97d61dd5bf692d181dChris Craik                    }
22136c4d66a1bc449309f0a3b97d61dd5bf692d181dChris Craik                });
22236c4d66a1bc449309f0a3b97d61dd5bf692d181dChris Craik        when(mockAdapter.getItemCount()).thenReturn(100);
22336c4d66a1bc449309f0a3b97d61dd5bf692d181dChris Craik        mRecyclerView.setAdapter(mockAdapter);
22436c4d66a1bc449309f0a3b97d61dd5bf692d181dChris Craik
22536c4d66a1bc449309f0a3b97d61dd5bf692d181dChris Craik        // NOTE: requested cache size must be smaller than span count so two rows cannot fit
22636c4d66a1bc449309f0a3b97d61dd5bf692d181dChris Craik        mRecyclerView.setItemViewCacheSize(2);
22736c4d66a1bc449309f0a3b97d61dd5bf692d181dChris Craik
22836c4d66a1bc449309f0a3b97d61dd5bf692d181dChris Craik        mRecyclerView.measure(View.MeasureSpec.AT_MOST | 300, View.MeasureSpec.AT_MOST | 200);
22936c4d66a1bc449309f0a3b97d61dd5bf692d181dChris Craik        mRecyclerView.layout(0, 0, 300, 150);
23036c4d66a1bc449309f0a3b97d61dd5bf692d181dChris Craik        mRecyclerView.scrollBy(0, 75);
23136c4d66a1bc449309f0a3b97d61dd5bf692d181dChris Craik        assertTrue(mRecycler.mCachedViews.isEmpty());
23236c4d66a1bc449309f0a3b97d61dd5bf692d181dChris Craik
23336c4d66a1bc449309f0a3b97d61dd5bf692d181dChris Craik        // rows 0, 1, and 2 are all attached and visible. Prefetch row 3:
234a93a0c08b4543401d8e2df9bd4c88f26d3b72c11Chris Craik        mRecyclerView.mPrefetchDx = 0;
235a93a0c08b4543401d8e2df9bd4c88f26d3b72c11Chris Craik        mRecyclerView.mPrefetchDy = 1;
236a93a0c08b4543401d8e2df9bd4c88f26d3b72c11Chris Craik        GapWorker.layoutPrefetch(RecyclerView.FOREVER_NS, mRecyclerView);
23736c4d66a1bc449309f0a3b97d61dd5bf692d181dChris Craik
23836c4d66a1bc449309f0a3b97d61dd5bf692d181dChris Craik        // row 3 is cached:
23936c4d66a1bc449309f0a3b97d61dd5bf692d181dChris Craik        verifyCacheContainsPositions(9, 10, 11);
24036c4d66a1bc449309f0a3b97d61dd5bf692d181dChris Craik        assertTrue(mRecycler.mCachedViews.size() == 3);
24136c4d66a1bc449309f0a3b97d61dd5bf692d181dChris Craik
24236c4d66a1bc449309f0a3b97d61dd5bf692d181dChris Craik        // Scroll so 1 falls off (though 3 is still not on screen)
24336c4d66a1bc449309f0a3b97d61dd5bf692d181dChris Craik        mRecyclerView.scrollBy(0, 50);
24436c4d66a1bc449309f0a3b97d61dd5bf692d181dChris Craik
24536c4d66a1bc449309f0a3b97d61dd5bf692d181dChris Craik        // row 3 is still cached, with a couple other recycled views:
24636c4d66a1bc449309f0a3b97d61dd5bf692d181dChris Craik        verifyCacheContainsPositions(9, 10, 11);
24736c4d66a1bc449309f0a3b97d61dd5bf692d181dChris Craik        assertTrue(mRecycler.mCachedViews.size() == 5);
24836c4d66a1bc449309f0a3b97d61dd5bf692d181dChris Craik    }
249a93a0c08b4543401d8e2df9bd4c88f26d3b72c11Chris Craik
250a93a0c08b4543401d8e2df9bd4c88f26d3b72c11Chris Craik    @Test
251a93a0c08b4543401d8e2df9bd4c88f26d3b72c11Chris Craik    public void prefetchItemsRespectDeadline() {
252a93a0c08b4543401d8e2df9bd4c88f26d3b72c11Chris Craik        mRecyclerView.setLayoutManager(new GridLayoutManager(getContext(), 3));
253a93a0c08b4543401d8e2df9bd4c88f26d3b72c11Chris Craik
254a93a0c08b4543401d8e2df9bd4c88f26d3b72c11Chris Craik        // 100x100 pixel views
255a93a0c08b4543401d8e2df9bd4c88f26d3b72c11Chris Craik        RecyclerView.Adapter adapter = new RecyclerView.Adapter() {
256a93a0c08b4543401d8e2df9bd4c88f26d3b72c11Chris Craik            @Override
257a93a0c08b4543401d8e2df9bd4c88f26d3b72c11Chris Craik            public RecyclerView.ViewHolder onCreateViewHolder(ViewGroup parent, int viewType) {
258a93a0c08b4543401d8e2df9bd4c88f26d3b72c11Chris Craik                mRecyclerView.registerTimePassingMs(5);
259a93a0c08b4543401d8e2df9bd4c88f26d3b72c11Chris Craik                View view = new View(getContext());
260a93a0c08b4543401d8e2df9bd4c88f26d3b72c11Chris Craik                view.setMinimumWidth(100);
261a93a0c08b4543401d8e2df9bd4c88f26d3b72c11Chris Craik                view.setMinimumHeight(100);
262a93a0c08b4543401d8e2df9bd4c88f26d3b72c11Chris Craik                RecyclerView.ViewHolder holder = new RecyclerView.ViewHolder(view) {};
263a93a0c08b4543401d8e2df9bd4c88f26d3b72c11Chris Craik                return holder;
264a93a0c08b4543401d8e2df9bd4c88f26d3b72c11Chris Craik            }
265a93a0c08b4543401d8e2df9bd4c88f26d3b72c11Chris Craik
266a93a0c08b4543401d8e2df9bd4c88f26d3b72c11Chris Craik            @Override
267a93a0c08b4543401d8e2df9bd4c88f26d3b72c11Chris Craik            public void onBindViewHolder(RecyclerView.ViewHolder holder, int position) {
268a93a0c08b4543401d8e2df9bd4c88f26d3b72c11Chris Craik                mRecyclerView.registerTimePassingMs(5);
269a93a0c08b4543401d8e2df9bd4c88f26d3b72c11Chris Craik            }
270a93a0c08b4543401d8e2df9bd4c88f26d3b72c11Chris Craik
271a93a0c08b4543401d8e2df9bd4c88f26d3b72c11Chris Craik            @Override
272a93a0c08b4543401d8e2df9bd4c88f26d3b72c11Chris Craik            public int getItemCount() {
273a93a0c08b4543401d8e2df9bd4c88f26d3b72c11Chris Craik                return 100;
274a93a0c08b4543401d8e2df9bd4c88f26d3b72c11Chris Craik            }
275a93a0c08b4543401d8e2df9bd4c88f26d3b72c11Chris Craik        };
276a93a0c08b4543401d8e2df9bd4c88f26d3b72c11Chris Craik        mRecyclerView.setAdapter(adapter);
277a93a0c08b4543401d8e2df9bd4c88f26d3b72c11Chris Craik
278a93a0c08b4543401d8e2df9bd4c88f26d3b72c11Chris Craik        mRecyclerView.measure(View.MeasureSpec.AT_MOST | 300, View.MeasureSpec.AT_MOST | 300);
279a93a0c08b4543401d8e2df9bd4c88f26d3b72c11Chris Craik        mRecyclerView.layout(0, 0, 300, 300);
280a93a0c08b4543401d8e2df9bd4c88f26d3b72c11Chris Craik
281a93a0c08b4543401d8e2df9bd4c88f26d3b72c11Chris Craik        assertTrue(mRecycler.mCachedViews.size() == 0);
282a93a0c08b4543401d8e2df9bd4c88f26d3b72c11Chris Craik        assertTrue(mRecyclerView.getRecycledViewPool().getRecycledViewCount(0) == 0);
283a93a0c08b4543401d8e2df9bd4c88f26d3b72c11Chris Craik
284a93a0c08b4543401d8e2df9bd4c88f26d3b72c11Chris Craik        // Should take 15 ms to inflate, bind, inflate, so give 19 to be safe
285a93a0c08b4543401d8e2df9bd4c88f26d3b72c11Chris Craik        final long deadlineNs = mRecyclerView.getNanoTime() + TimeUnit.MILLISECONDS.toNanos(19);
286a93a0c08b4543401d8e2df9bd4c88f26d3b72c11Chris Craik
287a93a0c08b4543401d8e2df9bd4c88f26d3b72c11Chris Craik        // Timed prefetch
288a93a0c08b4543401d8e2df9bd4c88f26d3b72c11Chris Craik        mRecyclerView.mPrefetchDx = 0;
289a93a0c08b4543401d8e2df9bd4c88f26d3b72c11Chris Craik        mRecyclerView.mPrefetchDy = 1;
290a93a0c08b4543401d8e2df9bd4c88f26d3b72c11Chris Craik        GapWorker.layoutPrefetch(deadlineNs, mRecyclerView);
291a93a0c08b4543401d8e2df9bd4c88f26d3b72c11Chris Craik
292a93a0c08b4543401d8e2df9bd4c88f26d3b72c11Chris Craik        // will have enough time to inflate/bind one view, and inflate another
293a93a0c08b4543401d8e2df9bd4c88f26d3b72c11Chris Craik        assertTrue(mRecycler.mCachedViews.size() == 1);
294a93a0c08b4543401d8e2df9bd4c88f26d3b72c11Chris Craik        assertTrue(mRecyclerView.getRecycledViewPool().getRecycledViewCount(0) == 1);
295a93a0c08b4543401d8e2df9bd4c88f26d3b72c11Chris Craik        verifyCacheContainsPositions(11); // Note: order/view here is an implementation detail
296a93a0c08b4543401d8e2df9bd4c88f26d3b72c11Chris Craik
297a93a0c08b4543401d8e2df9bd4c88f26d3b72c11Chris Craik
298a93a0c08b4543401d8e2df9bd4c88f26d3b72c11Chris Craik        // Unbounded prefetch this time
299a93a0c08b4543401d8e2df9bd4c88f26d3b72c11Chris Craik        GapWorker.layoutPrefetch(RecyclerView.FOREVER_NS, mRecyclerView);
300a93a0c08b4543401d8e2df9bd4c88f26d3b72c11Chris Craik
301a93a0c08b4543401d8e2df9bd4c88f26d3b72c11Chris Craik        // Should finish all work
302a93a0c08b4543401d8e2df9bd4c88f26d3b72c11Chris Craik        assertTrue(mRecycler.mCachedViews.size() == 3);
303a93a0c08b4543401d8e2df9bd4c88f26d3b72c11Chris Craik        assertTrue(mRecyclerView.getRecycledViewPool().getRecycledViewCount(0) == 0);
304a93a0c08b4543401d8e2df9bd4c88f26d3b72c11Chris Craik        verifyCacheContainsPositions(9, 10, 11);
305a93a0c08b4543401d8e2df9bd4c88f26d3b72c11Chris Craik    }
30636c4d66a1bc449309f0a3b97d61dd5bf692d181dChris Craik}