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
19945dc1e8a9ee0b71e6b2454ccb3e61145d2132f5Chris Craikimport static org.junit.Assert.assertEquals;
200fab45107f043a4856aad2e1fd785b396898616eChris Craikimport static org.junit.Assert.assertFalse;
211e0a453dff8d7bb7a966d006541454c770d1ed05Chris Craikimport static org.junit.Assert.assertNotEquals;
22dbf84daff4f8c5a9e1b8cfa63ab14106dbab9038Chris Craikimport static org.junit.Assert.assertNotNull;
230fd482dee2c8c2aa61e59d6501e46864e16fcb04Dake Guimport static org.junit.Assert.assertSame;
24ee9f1a3ebd8f071cea11fd97f9a3f3d41027189cAurimas Liutikasimport static org.junit.Assert.assertTrue;
25ee9f1a3ebd8f071cea11fd97f9a3f3d41027189cAurimas Liutikasimport static org.mockito.Matchers.any;
26ee9f1a3ebd8f071cea11fd97f9a3f3d41027189cAurimas Liutikasimport static org.mockito.Matchers.anyInt;
271e0a453dff8d7bb7a966d006541454c770d1ed05Chris Craikimport static org.mockito.Matchers.argThat;
28ee9f1a3ebd8f071cea11fd97f9a3f3d41027189cAurimas Liutikasimport static org.mockito.Mockito.mock;
29ee9f1a3ebd8f071cea11fd97f9a3f3d41027189cAurimas Liutikasimport static org.mockito.Mockito.never;
30ee9f1a3ebd8f071cea11fd97f9a3f3d41027189cAurimas Liutikasimport static org.mockito.Mockito.times;
31ee9f1a3ebd8f071cea11fd97f9a3f3d41027189cAurimas Liutikasimport static org.mockito.Mockito.verify;
32ee9f1a3ebd8f071cea11fd97f9a3f3d41027189cAurimas Liutikasimport static org.mockito.Mockito.when;
33ee9f1a3ebd8f071cea11fd97f9a3f3d41027189cAurimas Liutikas
3436c4d66a1bc449309f0a3b97d61dd5bf692d181dChris Craikimport android.content.Context;
35e85a5c8c0bfc0aa1ca0796c7bd1f916049e55d68Aurimas Liutikasimport android.os.Build;
360ceae57ee14da826c31a66c67e70f7f17108d5caChris Craikimport android.os.Parcelable;
370951d1a9c3200368a7b19bd272f7d6c266a11869Chris Craikimport android.os.SystemClock;
3836c4d66a1bc449309f0a3b97d61dd5bf692d181dChris Craikimport android.support.test.InstrumentationRegistry;
39ee9f1a3ebd8f071cea11fd97f9a3f3d41027189cAurimas Liutikasimport android.support.test.filters.SdkSuppress;
40754cb29c50f09a83251dd4bb633ba445b2411adbAurimas Liutikasimport android.support.test.filters.SmallTest;
4136c4d66a1bc449309f0a3b97d61dd5bf692d181dChris Craikimport android.support.test.runner.AndroidJUnit4;
420951d1a9c3200368a7b19bd272f7d6c266a11869Chris Craikimport android.view.MotionEvent;
4336c4d66a1bc449309f0a3b97d61dd5bf692d181dChris Craikimport android.view.View;
4436c4d66a1bc449309f0a3b97d61dd5bf692d181dChris Craikimport android.view.ViewGroup;
450951d1a9c3200368a7b19bd272f7d6c266a11869Chris Craikimport android.widget.FrameLayout;
4636c4d66a1bc449309f0a3b97d61dd5bf692d181dChris Craik
47356880d3de117b067522ad452f4e3eed85ce444cChris Craikimport org.junit.After;
4836c4d66a1bc449309f0a3b97d61dd5bf692d181dChris Craikimport org.junit.Before;
4936c4d66a1bc449309f0a3b97d61dd5bf692d181dChris Craikimport org.junit.Test;
5036c4d66a1bc449309f0a3b97d61dd5bf692d181dChris Craikimport org.junit.runner.RunWith;
51963facb8fce35022f296c38fadafd9a959ab1655Aurimas Liutikasimport org.mockito.ArgumentMatcher;
5236c4d66a1bc449309f0a3b97d61dd5bf692d181dChris Craikimport org.mockito.invocation.InvocationOnMock;
5336c4d66a1bc449309f0a3b97d61dd5bf692d181dChris Craikimport org.mockito.stubbing.Answer;
5436c4d66a1bc449309f0a3b97d61dd5bf692d181dChris Craik
551e0a453dff8d7bb7a966d006541454c770d1ed05Chris Craikimport java.util.ArrayList;
5636c4d66a1bc449309f0a3b97d61dd5bf692d181dChris Craikimport java.util.List;
57a93a0c08b4543401d8e2df9bd4c88f26d3b72c11Chris Craikimport java.util.concurrent.TimeUnit;
5836c4d66a1bc449309f0a3b97d61dd5bf692d181dChris Craik
5936c4d66a1bc449309f0a3b97d61dd5bf692d181dChris Craik@SmallTest
60e85a5c8c0bfc0aa1ca0796c7bd1f916049e55d68Aurimas Liutikas@SdkSuppress(minSdkVersion = Build.VERSION_CODES.LOLLIPOP)
6136c4d66a1bc449309f0a3b97d61dd5bf692d181dChris Craik@RunWith(AndroidJUnit4.class)
6236c4d66a1bc449309f0a3b97d61dd5bf692d181dChris Craikpublic class RecyclerViewCacheTest {
63a93a0c08b4543401d8e2df9bd4c88f26d3b72c11Chris Craik    TimeMockingRecyclerView mRecyclerView;
6436c4d66a1bc449309f0a3b97d61dd5bf692d181dChris Craik    RecyclerView.Recycler mRecycler;
6536c4d66a1bc449309f0a3b97d61dd5bf692d181dChris Craik
66a93a0c08b4543401d8e2df9bd4c88f26d3b72c11Chris Craik    private class TimeMockingRecyclerView extends RecyclerView {
67a93a0c08b4543401d8e2df9bd4c88f26d3b72c11Chris Craik        private long mMockNanoTime = 0;
68a93a0c08b4543401d8e2df9bd4c88f26d3b72c11Chris Craik
69a93a0c08b4543401d8e2df9bd4c88f26d3b72c11Chris Craik        TimeMockingRecyclerView(Context context) {
70a93a0c08b4543401d8e2df9bd4c88f26d3b72c11Chris Craik            super(context);
71a93a0c08b4543401d8e2df9bd4c88f26d3b72c11Chris Craik        }
72a93a0c08b4543401d8e2df9bd4c88f26d3b72c11Chris Craik
73a93a0c08b4543401d8e2df9bd4c88f26d3b72c11Chris Craik        public void registerTimePassingMs(long ms) {
74a93a0c08b4543401d8e2df9bd4c88f26d3b72c11Chris Craik            mMockNanoTime += TimeUnit.MILLISECONDS.toNanos(ms);
75a93a0c08b4543401d8e2df9bd4c88f26d3b72c11Chris Craik        }
76a93a0c08b4543401d8e2df9bd4c88f26d3b72c11Chris Craik
77a93a0c08b4543401d8e2df9bd4c88f26d3b72c11Chris Craik        @Override
78a93a0c08b4543401d8e2df9bd4c88f26d3b72c11Chris Craik        long getNanoTime() {
79a93a0c08b4543401d8e2df9bd4c88f26d3b72c11Chris Craik            return mMockNanoTime;
80a93a0c08b4543401d8e2df9bd4c88f26d3b72c11Chris Craik        }
81bec95cedb8511e1d814181aa518512c7e7fc5cf6Chris Craik
82bec95cedb8511e1d814181aa518512c7e7fc5cf6Chris Craik        @Override
83bec95cedb8511e1d814181aa518512c7e7fc5cf6Chris Craik        public int getWindowVisibility() {
84bec95cedb8511e1d814181aa518512c7e7fc5cf6Chris Craik            // Pretend to be visible to avoid being filtered out
85bec95cedb8511e1d814181aa518512c7e7fc5cf6Chris Craik            return View.VISIBLE;
86bec95cedb8511e1d814181aa518512c7e7fc5cf6Chris Craik        }
87356880d3de117b067522ad452f4e3eed85ce444cChris Craik    }
88a93a0c08b4543401d8e2df9bd4c88f26d3b72c11Chris Craik
8936c4d66a1bc449309f0a3b97d61dd5bf692d181dChris Craik    @Before
90356880d3de117b067522ad452f4e3eed85ce444cChris Craik    public void setup() throws Exception {
91a93a0c08b4543401d8e2df9bd4c88f26d3b72c11Chris Craik        mRecyclerView = new TimeMockingRecyclerView(getContext());
92356880d3de117b067522ad452f4e3eed85ce444cChris Craik        mRecyclerView.onAttachedToWindow();
9336c4d66a1bc449309f0a3b97d61dd5bf692d181dChris Craik        mRecycler = mRecyclerView.mRecycler;
9436c4d66a1bc449309f0a3b97d61dd5bf692d181dChris Craik    }
9536c4d66a1bc449309f0a3b97d61dd5bf692d181dChris Craik
96356880d3de117b067522ad452f4e3eed85ce444cChris Craik    @After
97356880d3de117b067522ad452f4e3eed85ce444cChris Craik    public void teardown() throws Exception {
98356880d3de117b067522ad452f4e3eed85ce444cChris Craik        if (mRecyclerView.isAttachedToWindow()) {
99356880d3de117b067522ad452f4e3eed85ce444cChris Craik            mRecyclerView.onDetachedFromWindow();
100356880d3de117b067522ad452f4e3eed85ce444cChris Craik        }
101356880d3de117b067522ad452f4e3eed85ce444cChris Craik        GapWorker gapWorker = GapWorker.sGapWorker.get();
102356880d3de117b067522ad452f4e3eed85ce444cChris Craik        if (gapWorker != null) {
103356880d3de117b067522ad452f4e3eed85ce444cChris Craik            assertTrue(gapWorker.mRecyclerViews.isEmpty());
104356880d3de117b067522ad452f4e3eed85ce444cChris Craik        }
105356880d3de117b067522ad452f4e3eed85ce444cChris Craik    }
106356880d3de117b067522ad452f4e3eed85ce444cChris Craik
10736c4d66a1bc449309f0a3b97d61dd5bf692d181dChris Craik    private Context getContext() {
10836c4d66a1bc449309f0a3b97d61dd5bf692d181dChris Craik        return InstrumentationRegistry.getContext();
10936c4d66a1bc449309f0a3b97d61dd5bf692d181dChris Craik    }
11036c4d66a1bc449309f0a3b97d61dd5bf692d181dChris Craik
111dbf84daff4f8c5a9e1b8cfa63ab14106dbab9038Chris Craik    private void layout(int width, int height) {
112dbf84daff4f8c5a9e1b8cfa63ab14106dbab9038Chris Craik        mRecyclerView.measure(
113dbf84daff4f8c5a9e1b8cfa63ab14106dbab9038Chris Craik                View.MeasureSpec.makeMeasureSpec(width, View.MeasureSpec.EXACTLY),
114dbf84daff4f8c5a9e1b8cfa63ab14106dbab9038Chris Craik                View.MeasureSpec.makeMeasureSpec(height, View.MeasureSpec.EXACTLY));
115dbf84daff4f8c5a9e1b8cfa63ab14106dbab9038Chris Craik        mRecyclerView.layout(0, 0, width, height);
116dbf84daff4f8c5a9e1b8cfa63ab14106dbab9038Chris Craik    }
117dbf84daff4f8c5a9e1b8cfa63ab14106dbab9038Chris Craik
11836c4d66a1bc449309f0a3b97d61dd5bf692d181dChris Craik    @Test
11936c4d66a1bc449309f0a3b97d61dd5bf692d181dChris Craik    public void prefetchReusesCacheItems() {
12036c4d66a1bc449309f0a3b97d61dd5bf692d181dChris Craik        RecyclerView.LayoutManager prefetchingLayoutManager = new RecyclerView.LayoutManager() {
12136c4d66a1bc449309f0a3b97d61dd5bf692d181dChris Craik            @Override
12236c4d66a1bc449309f0a3b97d61dd5bf692d181dChris Craik            public RecyclerView.LayoutParams generateDefaultLayoutParams() {
12336c4d66a1bc449309f0a3b97d61dd5bf692d181dChris Craik                return new RecyclerView.LayoutParams(ViewGroup.LayoutParams.WRAP_CONTENT,
12436c4d66a1bc449309f0a3b97d61dd5bf692d181dChris Craik                        ViewGroup.LayoutParams.WRAP_CONTENT);
12536c4d66a1bc449309f0a3b97d61dd5bf692d181dChris Craik            }
12636c4d66a1bc449309f0a3b97d61dd5bf692d181dChris Craik
12736c4d66a1bc449309f0a3b97d61dd5bf692d181dChris Craik            @Override
1281e0a453dff8d7bb7a966d006541454c770d1ed05Chris Craik            public void collectAdjacentPrefetchPositions(int dx, int dy, RecyclerView.State state,
1293104d446bcf3da9ffb7a761fd30f0506c30f0cc9Chris Craik                    LayoutPrefetchRegistry prefetchManager) {
130945dc1e8a9ee0b71e6b2454ccb3e61145d2132f5Chris Craik                prefetchManager.addPosition(0, 0);
131945dc1e8a9ee0b71e6b2454ccb3e61145d2132f5Chris Craik                prefetchManager.addPosition(1, 0);
132945dc1e8a9ee0b71e6b2454ccb3e61145d2132f5Chris Craik                prefetchManager.addPosition(2, 0);
13336c4d66a1bc449309f0a3b97d61dd5bf692d181dChris Craik            }
13436c4d66a1bc449309f0a3b97d61dd5bf692d181dChris Craik
13536c4d66a1bc449309f0a3b97d61dd5bf692d181dChris Craik            @Override
13636c4d66a1bc449309f0a3b97d61dd5bf692d181dChris Craik            public void onLayoutChildren(RecyclerView.Recycler recycler, RecyclerView.State state) {
13736c4d66a1bc449309f0a3b97d61dd5bf692d181dChris Craik            }
13836c4d66a1bc449309f0a3b97d61dd5bf692d181dChris Craik        };
13936c4d66a1bc449309f0a3b97d61dd5bf692d181dChris Craik        mRecyclerView.setLayoutManager(prefetchingLayoutManager);
14036c4d66a1bc449309f0a3b97d61dd5bf692d181dChris Craik
14136c4d66a1bc449309f0a3b97d61dd5bf692d181dChris Craik        RecyclerView.Adapter mockAdapter = mock(RecyclerView.Adapter.class);
14236c4d66a1bc449309f0a3b97d61dd5bf692d181dChris Craik        when(mockAdapter.onCreateViewHolder(any(ViewGroup.class), anyInt()))
14336c4d66a1bc449309f0a3b97d61dd5bf692d181dChris Craik                .thenAnswer(new Answer<RecyclerView.ViewHolder>() {
14436c4d66a1bc449309f0a3b97d61dd5bf692d181dChris Craik                    @Override
14536c4d66a1bc449309f0a3b97d61dd5bf692d181dChris Craik                    public RecyclerView.ViewHolder answer(InvocationOnMock invocation)
14636c4d66a1bc449309f0a3b97d61dd5bf692d181dChris Craik                            throws Throwable {
14736c4d66a1bc449309f0a3b97d61dd5bf692d181dChris Craik                        return new RecyclerView.ViewHolder(new View(getContext())) {};
14836c4d66a1bc449309f0a3b97d61dd5bf692d181dChris Craik                    }
14936c4d66a1bc449309f0a3b97d61dd5bf692d181dChris Craik                });
15036c4d66a1bc449309f0a3b97d61dd5bf692d181dChris Craik        when(mockAdapter.getItemCount()).thenReturn(10);
15136c4d66a1bc449309f0a3b97d61dd5bf692d181dChris Craik        mRecyclerView.setAdapter(mockAdapter);
15236c4d66a1bc449309f0a3b97d61dd5bf692d181dChris Craik
153dbf84daff4f8c5a9e1b8cfa63ab14106dbab9038Chris Craik        layout(320, 320);
15436c4d66a1bc449309f0a3b97d61dd5bf692d181dChris Craik
15536c4d66a1bc449309f0a3b97d61dd5bf692d181dChris Craik        verify(mockAdapter, never()).onCreateViewHolder(any(ViewGroup.class), anyInt());
15636c4d66a1bc449309f0a3b97d61dd5bf692d181dChris Craik        verify(mockAdapter, never()).onBindViewHolder(
15736c4d66a1bc449309f0a3b97d61dd5bf692d181dChris Craik                any(RecyclerView.ViewHolder.class), anyInt(), any(List.class));
15836c4d66a1bc449309f0a3b97d61dd5bf692d181dChris Craik        assertTrue(mRecycler.mCachedViews.isEmpty());
15936c4d66a1bc449309f0a3b97d61dd5bf692d181dChris Craik
16036c4d66a1bc449309f0a3b97d61dd5bf692d181dChris Craik        // Prefetch multiple times...
16136c4d66a1bc449309f0a3b97d61dd5bf692d181dChris Craik        for (int i = 0; i < 4; i++) {
16247d17a72daeb141148d13a0b0f2df146f6524a9dChris Craik            mRecyclerView.mGapWorker.prefetch(RecyclerView.FOREVER_NS);
16336c4d66a1bc449309f0a3b97d61dd5bf692d181dChris Craik
16436c4d66a1bc449309f0a3b97d61dd5bf692d181dChris Craik            // ...but should only see the same three items fetched/bound once each
16536c4d66a1bc449309f0a3b97d61dd5bf692d181dChris Craik            verify(mockAdapter, times(3)).onCreateViewHolder(any(ViewGroup.class), anyInt());
16636c4d66a1bc449309f0a3b97d61dd5bf692d181dChris Craik            verify(mockAdapter, times(3)).onBindViewHolder(
16736c4d66a1bc449309f0a3b97d61dd5bf692d181dChris Craik                    any(RecyclerView.ViewHolder.class), anyInt(), any(List.class));
16836c4d66a1bc449309f0a3b97d61dd5bf692d181dChris Craik
16936c4d66a1bc449309f0a3b97d61dd5bf692d181dChris Craik            assertTrue(mRecycler.mCachedViews.size() == 3);
1701e0a453dff8d7bb7a966d006541454c770d1ed05Chris Craik            CacheUtils.verifyCacheContainsPrefetchedPositions(mRecyclerView, 0, 1, 2);
17136c4d66a1bc449309f0a3b97d61dd5bf692d181dChris Craik        }
17236c4d66a1bc449309f0a3b97d61dd5bf692d181dChris Craik    }
17336c4d66a1bc449309f0a3b97d61dd5bf692d181dChris Craik
17436c4d66a1bc449309f0a3b97d61dd5bf692d181dChris Craik    @Test
17536c4d66a1bc449309f0a3b97d61dd5bf692d181dChris Craik    public void prefetchItemsNotEvictedWithInserts() {
176dbf84daff4f8c5a9e1b8cfa63ab14106dbab9038Chris Craik        mRecyclerView.setLayoutManager(new GridLayoutManager(getContext(), 3));
17736c4d66a1bc449309f0a3b97d61dd5bf692d181dChris Craik
17836c4d66a1bc449309f0a3b97d61dd5bf692d181dChris Craik        RecyclerView.Adapter mockAdapter = mock(RecyclerView.Adapter.class);
17936c4d66a1bc449309f0a3b97d61dd5bf692d181dChris Craik        when(mockAdapter.onCreateViewHolder(any(ViewGroup.class), anyInt()))
18036c4d66a1bc449309f0a3b97d61dd5bf692d181dChris Craik                .thenAnswer(new Answer<RecyclerView.ViewHolder>() {
18136c4d66a1bc449309f0a3b97d61dd5bf692d181dChris Craik                    @Override
18236c4d66a1bc449309f0a3b97d61dd5bf692d181dChris Craik                    public RecyclerView.ViewHolder answer(InvocationOnMock invocation)
18336c4d66a1bc449309f0a3b97d61dd5bf692d181dChris Craik                            throws Throwable {
184dbf84daff4f8c5a9e1b8cfa63ab14106dbab9038Chris Craik                        View view = new View(getContext());
185dbf84daff4f8c5a9e1b8cfa63ab14106dbab9038Chris Craik                        view.setMinimumWidth(100);
186dbf84daff4f8c5a9e1b8cfa63ab14106dbab9038Chris Craik                        view.setMinimumHeight(100);
187dbf84daff4f8c5a9e1b8cfa63ab14106dbab9038Chris Craik                        return new RecyclerView.ViewHolder(view) {};
18836c4d66a1bc449309f0a3b97d61dd5bf692d181dChris Craik                    }
18936c4d66a1bc449309f0a3b97d61dd5bf692d181dChris Craik                });
19036c4d66a1bc449309f0a3b97d61dd5bf692d181dChris Craik        when(mockAdapter.getItemCount()).thenReturn(100);
19136c4d66a1bc449309f0a3b97d61dd5bf692d181dChris Craik        mRecyclerView.setAdapter(mockAdapter);
19236c4d66a1bc449309f0a3b97d61dd5bf692d181dChris Craik
193dbf84daff4f8c5a9e1b8cfa63ab14106dbab9038Chris Craik        layout(300, 100);
19436c4d66a1bc449309f0a3b97d61dd5bf692d181dChris Craik
195945dc1e8a9ee0b71e6b2454ccb3e61145d2132f5Chris Craik        assertEquals(2, mRecyclerView.mRecycler.mViewCacheMax);
196dbf84daff4f8c5a9e1b8cfa63ab14106dbab9038Chris Craik        mRecyclerView.mPrefetchRegistry.setPrefetchVector(0, 1);
19747d17a72daeb141148d13a0b0f2df146f6524a9dChris Craik        mRecyclerView.mGapWorker.prefetch(RecyclerView.FOREVER_NS);
198945dc1e8a9ee0b71e6b2454ccb3e61145d2132f5Chris Craik        assertEquals(5, mRecyclerView.mRecycler.mViewCacheMax);
199945dc1e8a9ee0b71e6b2454ccb3e61145d2132f5Chris Craik
2001e0a453dff8d7bb7a966d006541454c770d1ed05Chris Craik        CacheUtils.verifyCacheContainsPrefetchedPositions(mRecyclerView, 3, 4, 5);
20136c4d66a1bc449309f0a3b97d61dd5bf692d181dChris Craik
20236c4d66a1bc449309f0a3b97d61dd5bf692d181dChris Craik        // further views recycled, as though from scrolling, shouldn't evict prefetched views:
20336c4d66a1bc449309f0a3b97d61dd5bf692d181dChris Craik        mRecycler.recycleView(mRecycler.getViewForPosition(10));
204dbf84daff4f8c5a9e1b8cfa63ab14106dbab9038Chris Craik        CacheUtils.verifyCacheContainsPositions(mRecyclerView, 3, 4, 5, 10);
20536c4d66a1bc449309f0a3b97d61dd5bf692d181dChris Craik
20636c4d66a1bc449309f0a3b97d61dd5bf692d181dChris Craik        mRecycler.recycleView(mRecycler.getViewForPosition(20));
207dbf84daff4f8c5a9e1b8cfa63ab14106dbab9038Chris Craik        CacheUtils.verifyCacheContainsPositions(mRecyclerView, 3, 4, 5, 10, 20);
20836c4d66a1bc449309f0a3b97d61dd5bf692d181dChris Craik
20936c4d66a1bc449309f0a3b97d61dd5bf692d181dChris Craik        mRecycler.recycleView(mRecycler.getViewForPosition(30));
210dbf84daff4f8c5a9e1b8cfa63ab14106dbab9038Chris Craik        CacheUtils.verifyCacheContainsPositions(mRecyclerView, 3, 4, 5, 20, 30);
21136c4d66a1bc449309f0a3b97d61dd5bf692d181dChris Craik
21236c4d66a1bc449309f0a3b97d61dd5bf692d181dChris Craik        mRecycler.recycleView(mRecycler.getViewForPosition(40));
213dbf84daff4f8c5a9e1b8cfa63ab14106dbab9038Chris Craik        CacheUtils.verifyCacheContainsPositions(mRecyclerView, 3, 4, 5, 30, 40);
21436c4d66a1bc449309f0a3b97d61dd5bf692d181dChris Craik
21536c4d66a1bc449309f0a3b97d61dd5bf692d181dChris Craik        // After clearing the cache, the prefetch priorities should be cleared as well:
21636c4d66a1bc449309f0a3b97d61dd5bf692d181dChris Craik        mRecyclerView.mRecycler.recycleAndClearCachedViews();
217dbf84daff4f8c5a9e1b8cfa63ab14106dbab9038Chris Craik        for (int i : new int[] {3, 4, 5, 50, 60, 70, 80, 90}) {
21836c4d66a1bc449309f0a3b97d61dd5bf692d181dChris Craik            mRecycler.recycleView(mRecycler.getViewForPosition(i));
21936c4d66a1bc449309f0a3b97d61dd5bf692d181dChris Craik        }
22036c4d66a1bc449309f0a3b97d61dd5bf692d181dChris Craik
22136c4d66a1bc449309f0a3b97d61dd5bf692d181dChris Craik        // cache only contains most recent positions, no priority for previous prefetches:
222356880d3de117b067522ad452f4e3eed85ce444cChris Craik        CacheUtils.verifyCacheContainsPositions(mRecyclerView, 50, 60, 70, 80, 90);
22336c4d66a1bc449309f0a3b97d61dd5bf692d181dChris Craik    }
22436c4d66a1bc449309f0a3b97d61dd5bf692d181dChris Craik
22536c4d66a1bc449309f0a3b97d61dd5bf692d181dChris Craik    @Test
22636c4d66a1bc449309f0a3b97d61dd5bf692d181dChris Craik    public void prefetchItemsNotEvictedOnScroll() {
22736c4d66a1bc449309f0a3b97d61dd5bf692d181dChris Craik        mRecyclerView.setLayoutManager(new GridLayoutManager(getContext(), 3));
22836c4d66a1bc449309f0a3b97d61dd5bf692d181dChris Craik
22936c4d66a1bc449309f0a3b97d61dd5bf692d181dChris Craik        // 100x100 pixel views
23036c4d66a1bc449309f0a3b97d61dd5bf692d181dChris Craik        RecyclerView.Adapter mockAdapter = mock(RecyclerView.Adapter.class);
23136c4d66a1bc449309f0a3b97d61dd5bf692d181dChris Craik        when(mockAdapter.onCreateViewHolder(any(ViewGroup.class), anyInt()))
23236c4d66a1bc449309f0a3b97d61dd5bf692d181dChris Craik                .thenAnswer(new Answer<RecyclerView.ViewHolder>() {
23336c4d66a1bc449309f0a3b97d61dd5bf692d181dChris Craik                    @Override
23436c4d66a1bc449309f0a3b97d61dd5bf692d181dChris Craik                    public RecyclerView.ViewHolder answer(InvocationOnMock invocation)
23536c4d66a1bc449309f0a3b97d61dd5bf692d181dChris Craik                            throws Throwable {
23636c4d66a1bc449309f0a3b97d61dd5bf692d181dChris Craik                        View view = new View(getContext());
23736c4d66a1bc449309f0a3b97d61dd5bf692d181dChris Craik                        view.setMinimumWidth(100);
23836c4d66a1bc449309f0a3b97d61dd5bf692d181dChris Craik                        view.setMinimumHeight(100);
23936c4d66a1bc449309f0a3b97d61dd5bf692d181dChris Craik                        return new RecyclerView.ViewHolder(view) {};
24036c4d66a1bc449309f0a3b97d61dd5bf692d181dChris Craik                    }
24136c4d66a1bc449309f0a3b97d61dd5bf692d181dChris Craik                });
24236c4d66a1bc449309f0a3b97d61dd5bf692d181dChris Craik        when(mockAdapter.getItemCount()).thenReturn(100);
24336c4d66a1bc449309f0a3b97d61dd5bf692d181dChris Craik        mRecyclerView.setAdapter(mockAdapter);
24436c4d66a1bc449309f0a3b97d61dd5bf692d181dChris Craik
24536c4d66a1bc449309f0a3b97d61dd5bf692d181dChris Craik        // NOTE: requested cache size must be smaller than span count so two rows cannot fit
24636c4d66a1bc449309f0a3b97d61dd5bf692d181dChris Craik        mRecyclerView.setItemViewCacheSize(2);
24736c4d66a1bc449309f0a3b97d61dd5bf692d181dChris Craik
248dbf84daff4f8c5a9e1b8cfa63ab14106dbab9038Chris Craik        layout(300, 150);
24936c4d66a1bc449309f0a3b97d61dd5bf692d181dChris Craik        mRecyclerView.scrollBy(0, 75);
25036c4d66a1bc449309f0a3b97d61dd5bf692d181dChris Craik        assertTrue(mRecycler.mCachedViews.isEmpty());
25136c4d66a1bc449309f0a3b97d61dd5bf692d181dChris Craik
25236c4d66a1bc449309f0a3b97d61dd5bf692d181dChris Craik        // rows 0, 1, and 2 are all attached and visible. Prefetch row 3:
253945dc1e8a9ee0b71e6b2454ccb3e61145d2132f5Chris Craik        mRecyclerView.mPrefetchRegistry.setPrefetchVector(0, 1);
25447d17a72daeb141148d13a0b0f2df146f6524a9dChris Craik        mRecyclerView.mGapWorker.prefetch(RecyclerView.FOREVER_NS);
25536c4d66a1bc449309f0a3b97d61dd5bf692d181dChris Craik
25636c4d66a1bc449309f0a3b97d61dd5bf692d181dChris Craik        // row 3 is cached:
257356880d3de117b067522ad452f4e3eed85ce444cChris Craik        CacheUtils.verifyCacheContainsPositions(mRecyclerView, 9, 10, 11);
25836c4d66a1bc449309f0a3b97d61dd5bf692d181dChris Craik        assertTrue(mRecycler.mCachedViews.size() == 3);
25936c4d66a1bc449309f0a3b97d61dd5bf692d181dChris Craik
26036c4d66a1bc449309f0a3b97d61dd5bf692d181dChris Craik        // Scroll so 1 falls off (though 3 is still not on screen)
26136c4d66a1bc449309f0a3b97d61dd5bf692d181dChris Craik        mRecyclerView.scrollBy(0, 50);
26236c4d66a1bc449309f0a3b97d61dd5bf692d181dChris Craik
26336c4d66a1bc449309f0a3b97d61dd5bf692d181dChris Craik        // row 3 is still cached, with a couple other recycled views:
264356880d3de117b067522ad452f4e3eed85ce444cChris Craik        CacheUtils.verifyCacheContainsPositions(mRecyclerView, 9, 10, 11);
26536c4d66a1bc449309f0a3b97d61dd5bf692d181dChris Craik        assertTrue(mRecycler.mCachedViews.size() == 5);
26636c4d66a1bc449309f0a3b97d61dd5bf692d181dChris Craik    }
267a93a0c08b4543401d8e2df9bd4c88f26d3b72c11Chris Craik
268a93a0c08b4543401d8e2df9bd4c88f26d3b72c11Chris Craik    @Test
269b9d3b54b0b5fa223867cf11e47ee3f31ee474031Chris Craik    public void prefetchIsComputingLayout() {
270b9d3b54b0b5fa223867cf11e47ee3f31ee474031Chris Craik        mRecyclerView.setLayoutManager(new LinearLayoutManager(getContext()));
271b9d3b54b0b5fa223867cf11e47ee3f31ee474031Chris Craik
272b9d3b54b0b5fa223867cf11e47ee3f31ee474031Chris Craik        // 100x100 pixel views
273b9d3b54b0b5fa223867cf11e47ee3f31ee474031Chris Craik        RecyclerView.Adapter mockAdapter = mock(RecyclerView.Adapter.class);
274b9d3b54b0b5fa223867cf11e47ee3f31ee474031Chris Craik        when(mockAdapter.onCreateViewHolder(any(ViewGroup.class), anyInt()))
275b9d3b54b0b5fa223867cf11e47ee3f31ee474031Chris Craik                .thenAnswer(new Answer<RecyclerView.ViewHolder>() {
276b9d3b54b0b5fa223867cf11e47ee3f31ee474031Chris Craik                    @Override
277b9d3b54b0b5fa223867cf11e47ee3f31ee474031Chris Craik                    public RecyclerView.ViewHolder answer(InvocationOnMock invocation)
278b9d3b54b0b5fa223867cf11e47ee3f31ee474031Chris Craik                            throws Throwable {
279b9d3b54b0b5fa223867cf11e47ee3f31ee474031Chris Craik                        View view = new View(getContext());
280b9d3b54b0b5fa223867cf11e47ee3f31ee474031Chris Craik                        view.setMinimumWidth(100);
281b9d3b54b0b5fa223867cf11e47ee3f31ee474031Chris Craik                        view.setMinimumHeight(100);
282b9d3b54b0b5fa223867cf11e47ee3f31ee474031Chris Craik                        assertTrue(mRecyclerView.isComputingLayout());
283b9d3b54b0b5fa223867cf11e47ee3f31ee474031Chris Craik                        return new RecyclerView.ViewHolder(view) {};
284b9d3b54b0b5fa223867cf11e47ee3f31ee474031Chris Craik                    }
285b9d3b54b0b5fa223867cf11e47ee3f31ee474031Chris Craik                });
286b9d3b54b0b5fa223867cf11e47ee3f31ee474031Chris Craik        when(mockAdapter.getItemCount()).thenReturn(100);
287b9d3b54b0b5fa223867cf11e47ee3f31ee474031Chris Craik        mRecyclerView.setAdapter(mockAdapter);
288b9d3b54b0b5fa223867cf11e47ee3f31ee474031Chris Craik
289b9d3b54b0b5fa223867cf11e47ee3f31ee474031Chris Craik        layout(100, 100);
290b9d3b54b0b5fa223867cf11e47ee3f31ee474031Chris Craik
291b9d3b54b0b5fa223867cf11e47ee3f31ee474031Chris Craik        verify(mockAdapter, times(1)).onCreateViewHolder(mRecyclerView, 0);
292b9d3b54b0b5fa223867cf11e47ee3f31ee474031Chris Craik
293b9d3b54b0b5fa223867cf11e47ee3f31ee474031Chris Craik        // prefetch an item, should still observe isComputingLayout in that create
294b9d3b54b0b5fa223867cf11e47ee3f31ee474031Chris Craik        mRecyclerView.mPrefetchRegistry.setPrefetchVector(0, 1);
295b9d3b54b0b5fa223867cf11e47ee3f31ee474031Chris Craik        mRecyclerView.mGapWorker.prefetch(RecyclerView.FOREVER_NS);
296b9d3b54b0b5fa223867cf11e47ee3f31ee474031Chris Craik
297b9d3b54b0b5fa223867cf11e47ee3f31ee474031Chris Craik        verify(mockAdapter, times(2)).onCreateViewHolder(mRecyclerView, 0);
298b9d3b54b0b5fa223867cf11e47ee3f31ee474031Chris Craik    }
299b9d3b54b0b5fa223867cf11e47ee3f31ee474031Chris Craik
300b9d3b54b0b5fa223867cf11e47ee3f31ee474031Chris Craik    @Test
301213c0aa34bc54fb1b540a040609f097a4e4f65faChris Craik    public void prefetchAfterOrientationChange() {
302213c0aa34bc54fb1b540a040609f097a4e4f65faChris Craik        LinearLayoutManager layout = new LinearLayoutManager(getContext(),
303213c0aa34bc54fb1b540a040609f097a4e4f65faChris Craik                LinearLayoutManager.VERTICAL, false);
304213c0aa34bc54fb1b540a040609f097a4e4f65faChris Craik        mRecyclerView.setLayoutManager(layout);
305213c0aa34bc54fb1b540a040609f097a4e4f65faChris Craik
306213c0aa34bc54fb1b540a040609f097a4e4f65faChris Craik        // 100x100 pixel views
307213c0aa34bc54fb1b540a040609f097a4e4f65faChris Craik        mRecyclerView.setAdapter(new RecyclerView.Adapter() {
308213c0aa34bc54fb1b540a040609f097a4e4f65faChris Craik            @Override
309213c0aa34bc54fb1b540a040609f097a4e4f65faChris Craik            public RecyclerView.ViewHolder onCreateViewHolder(ViewGroup parent, int viewType) {
310213c0aa34bc54fb1b540a040609f097a4e4f65faChris Craik                View view = new View(getContext());
311213c0aa34bc54fb1b540a040609f097a4e4f65faChris Craik                view.setMinimumWidth(100);
312213c0aa34bc54fb1b540a040609f097a4e4f65faChris Craik                view.setMinimumHeight(100);
313213c0aa34bc54fb1b540a040609f097a4e4f65faChris Craik                assertTrue(mRecyclerView.isComputingLayout());
314213c0aa34bc54fb1b540a040609f097a4e4f65faChris Craik                return new RecyclerView.ViewHolder(view) {};
315213c0aa34bc54fb1b540a040609f097a4e4f65faChris Craik            }
316213c0aa34bc54fb1b540a040609f097a4e4f65faChris Craik
317213c0aa34bc54fb1b540a040609f097a4e4f65faChris Craik            @Override
318213c0aa34bc54fb1b540a040609f097a4e4f65faChris Craik            public void onBindViewHolder(RecyclerView.ViewHolder holder, int position) {}
319213c0aa34bc54fb1b540a040609f097a4e4f65faChris Craik
320213c0aa34bc54fb1b540a040609f097a4e4f65faChris Craik            @Override
321213c0aa34bc54fb1b540a040609f097a4e4f65faChris Craik            public int getItemCount() {
322213c0aa34bc54fb1b540a040609f097a4e4f65faChris Craik                return 100;
323213c0aa34bc54fb1b540a040609f097a4e4f65faChris Craik            }
324213c0aa34bc54fb1b540a040609f097a4e4f65faChris Craik        });
325213c0aa34bc54fb1b540a040609f097a4e4f65faChris Craik
326213c0aa34bc54fb1b540a040609f097a4e4f65faChris Craik        layout(100, 100);
327213c0aa34bc54fb1b540a040609f097a4e4f65faChris Craik
328213c0aa34bc54fb1b540a040609f097a4e4f65faChris Craik        layout.setOrientation(LinearLayoutManager.HORIZONTAL);
329213c0aa34bc54fb1b540a040609f097a4e4f65faChris Craik
330213c0aa34bc54fb1b540a040609f097a4e4f65faChris Craik        // Prefetch an item after changing orientation, before layout - shouldn't crash
331213c0aa34bc54fb1b540a040609f097a4e4f65faChris Craik        mRecyclerView.mPrefetchRegistry.setPrefetchVector(1, 1);
332213c0aa34bc54fb1b540a040609f097a4e4f65faChris Craik        mRecyclerView.mGapWorker.prefetch(RecyclerView.FOREVER_NS);
333213c0aa34bc54fb1b540a040609f097a4e4f65faChris Craik    }
334213c0aa34bc54fb1b540a040609f097a4e4f65faChris Craik
335213c0aa34bc54fb1b540a040609f097a4e4f65faChris Craik    @Test
3360951d1a9c3200368a7b19bd272f7d6c266a11869Chris Craik    public void prefetchDrag() {
3370951d1a9c3200368a7b19bd272f7d6c266a11869Chris Craik        // event dispatch requires a parent
3380951d1a9c3200368a7b19bd272f7d6c266a11869Chris Craik        ViewGroup parent = new FrameLayout(getContext());
3390951d1a9c3200368a7b19bd272f7d6c266a11869Chris Craik        parent.addView(mRecyclerView);
3400951d1a9c3200368a7b19bd272f7d6c266a11869Chris Craik
3410951d1a9c3200368a7b19bd272f7d6c266a11869Chris Craik
3420951d1a9c3200368a7b19bd272f7d6c266a11869Chris Craik        mRecyclerView.setLayoutManager(
3430951d1a9c3200368a7b19bd272f7d6c266a11869Chris Craik                new LinearLayoutManager(getContext(), LinearLayoutManager.VERTICAL, false));
3440951d1a9c3200368a7b19bd272f7d6c266a11869Chris Craik
3450951d1a9c3200368a7b19bd272f7d6c266a11869Chris Craik        // 1000x1000 pixel views
3460951d1a9c3200368a7b19bd272f7d6c266a11869Chris Craik        RecyclerView.Adapter adapter = new RecyclerView.Adapter() {
3470951d1a9c3200368a7b19bd272f7d6c266a11869Chris Craik            @Override
3480951d1a9c3200368a7b19bd272f7d6c266a11869Chris Craik            public RecyclerView.ViewHolder onCreateViewHolder(ViewGroup parent, int viewType) {
3490951d1a9c3200368a7b19bd272f7d6c266a11869Chris Craik                mRecyclerView.registerTimePassingMs(5);
3500951d1a9c3200368a7b19bd272f7d6c266a11869Chris Craik                View view = new View(getContext());
3510951d1a9c3200368a7b19bd272f7d6c266a11869Chris Craik                view.setMinimumWidth(1000);
3520951d1a9c3200368a7b19bd272f7d6c266a11869Chris Craik                view.setMinimumHeight(1000);
3530951d1a9c3200368a7b19bd272f7d6c266a11869Chris Craik                return new RecyclerView.ViewHolder(view) {};
3540951d1a9c3200368a7b19bd272f7d6c266a11869Chris Craik            }
3550951d1a9c3200368a7b19bd272f7d6c266a11869Chris Craik
3560951d1a9c3200368a7b19bd272f7d6c266a11869Chris Craik            @Override
3570951d1a9c3200368a7b19bd272f7d6c266a11869Chris Craik            public void onBindViewHolder(RecyclerView.ViewHolder holder, int position) {
3580951d1a9c3200368a7b19bd272f7d6c266a11869Chris Craik                mRecyclerView.registerTimePassingMs(5);
3590951d1a9c3200368a7b19bd272f7d6c266a11869Chris Craik            }
3600951d1a9c3200368a7b19bd272f7d6c266a11869Chris Craik
3610951d1a9c3200368a7b19bd272f7d6c266a11869Chris Craik            @Override
3620951d1a9c3200368a7b19bd272f7d6c266a11869Chris Craik            public int getItemCount() {
3630951d1a9c3200368a7b19bd272f7d6c266a11869Chris Craik                return 100;
3640951d1a9c3200368a7b19bd272f7d6c266a11869Chris Craik            }
3650951d1a9c3200368a7b19bd272f7d6c266a11869Chris Craik        };
3660951d1a9c3200368a7b19bd272f7d6c266a11869Chris Craik        mRecyclerView.setAdapter(adapter);
3670951d1a9c3200368a7b19bd272f7d6c266a11869Chris Craik
3680951d1a9c3200368a7b19bd272f7d6c266a11869Chris Craik        layout(1000, 1000);
3690951d1a9c3200368a7b19bd272f7d6c266a11869Chris Craik
3700951d1a9c3200368a7b19bd272f7d6c266a11869Chris Craik        long time = SystemClock.uptimeMillis();
3710951d1a9c3200368a7b19bd272f7d6c266a11869Chris Craik        mRecyclerView.onTouchEvent(
3720951d1a9c3200368a7b19bd272f7d6c266a11869Chris Craik                MotionEvent.obtain(time, time, MotionEvent.ACTION_DOWN, 500, 1000, 0));
3730951d1a9c3200368a7b19bd272f7d6c266a11869Chris Craik
3740951d1a9c3200368a7b19bd272f7d6c266a11869Chris Craik        assertEquals(0, mRecyclerView.mPrefetchRegistry.mPrefetchDx);
3750951d1a9c3200368a7b19bd272f7d6c266a11869Chris Craik        assertEquals(0, mRecyclerView.mPrefetchRegistry.mPrefetchDy);
3760951d1a9c3200368a7b19bd272f7d6c266a11869Chris Craik
3770951d1a9c3200368a7b19bd272f7d6c266a11869Chris Craik        // Consume slop
3780951d1a9c3200368a7b19bd272f7d6c266a11869Chris Craik        mRecyclerView.onTouchEvent(
3790951d1a9c3200368a7b19bd272f7d6c266a11869Chris Craik                MotionEvent.obtain(time, time, MotionEvent.ACTION_MOVE, 50, 500, 0));
3800951d1a9c3200368a7b19bd272f7d6c266a11869Chris Craik
3810951d1a9c3200368a7b19bd272f7d6c266a11869Chris Craik        // move by 0,30
3820951d1a9c3200368a7b19bd272f7d6c266a11869Chris Craik        mRecyclerView.onTouchEvent(
3830951d1a9c3200368a7b19bd272f7d6c266a11869Chris Craik                MotionEvent.obtain(time, time, MotionEvent.ACTION_MOVE, 50, 470, 0));
3840951d1a9c3200368a7b19bd272f7d6c266a11869Chris Craik        assertEquals(0, mRecyclerView.mPrefetchRegistry.mPrefetchDx);
3850951d1a9c3200368a7b19bd272f7d6c266a11869Chris Craik        assertEquals(30, mRecyclerView.mPrefetchRegistry.mPrefetchDy);
3860951d1a9c3200368a7b19bd272f7d6c266a11869Chris Craik
3870951d1a9c3200368a7b19bd272f7d6c266a11869Chris Craik        // move by 10,15
3880951d1a9c3200368a7b19bd272f7d6c266a11869Chris Craik        mRecyclerView.onTouchEvent(
3890951d1a9c3200368a7b19bd272f7d6c266a11869Chris Craik                MotionEvent.obtain(time, time, MotionEvent.ACTION_MOVE, 40, 455, 0));
3900951d1a9c3200368a7b19bd272f7d6c266a11869Chris Craik        assertEquals(10, mRecyclerView.mPrefetchRegistry.mPrefetchDx);
3910951d1a9c3200368a7b19bd272f7d6c266a11869Chris Craik        assertEquals(15, mRecyclerView.mPrefetchRegistry.mPrefetchDy);
3920951d1a9c3200368a7b19bd272f7d6c266a11869Chris Craik
3930951d1a9c3200368a7b19bd272f7d6c266a11869Chris Craik        // move by 0,0 - IGNORED
3940951d1a9c3200368a7b19bd272f7d6c266a11869Chris Craik        mRecyclerView.onTouchEvent(
3950951d1a9c3200368a7b19bd272f7d6c266a11869Chris Craik                MotionEvent.obtain(time, time, MotionEvent.ACTION_MOVE, 40, 455, 0));
3960951d1a9c3200368a7b19bd272f7d6c266a11869Chris Craik        assertEquals(10, mRecyclerView.mPrefetchRegistry.mPrefetchDx); // same as prev
3970951d1a9c3200368a7b19bd272f7d6c266a11869Chris Craik        assertEquals(15, mRecyclerView.mPrefetchRegistry.mPrefetchDy); // same as prev
3980951d1a9c3200368a7b19bd272f7d6c266a11869Chris Craik    }
3990951d1a9c3200368a7b19bd272f7d6c266a11869Chris Craik
4000951d1a9c3200368a7b19bd272f7d6c266a11869Chris Craik    @Test
401a93a0c08b4543401d8e2df9bd4c88f26d3b72c11Chris Craik    public void prefetchItemsRespectDeadline() {
402a93a0c08b4543401d8e2df9bd4c88f26d3b72c11Chris Craik        mRecyclerView.setLayoutManager(new GridLayoutManager(getContext(), 3));
403a93a0c08b4543401d8e2df9bd4c88f26d3b72c11Chris Craik
404a93a0c08b4543401d8e2df9bd4c88f26d3b72c11Chris Craik        // 100x100 pixel views
405a93a0c08b4543401d8e2df9bd4c88f26d3b72c11Chris Craik        RecyclerView.Adapter adapter = new RecyclerView.Adapter() {
406a93a0c08b4543401d8e2df9bd4c88f26d3b72c11Chris Craik            @Override
407a93a0c08b4543401d8e2df9bd4c88f26d3b72c11Chris Craik            public RecyclerView.ViewHolder onCreateViewHolder(ViewGroup parent, int viewType) {
408a93a0c08b4543401d8e2df9bd4c88f26d3b72c11Chris Craik                mRecyclerView.registerTimePassingMs(5);
409a93a0c08b4543401d8e2df9bd4c88f26d3b72c11Chris Craik                View view = new View(getContext());
410a93a0c08b4543401d8e2df9bd4c88f26d3b72c11Chris Craik                view.setMinimumWidth(100);
411a93a0c08b4543401d8e2df9bd4c88f26d3b72c11Chris Craik                view.setMinimumHeight(100);
412945dc1e8a9ee0b71e6b2454ccb3e61145d2132f5Chris Craik                return new RecyclerView.ViewHolder(view) {};
413a93a0c08b4543401d8e2df9bd4c88f26d3b72c11Chris Craik            }
414a93a0c08b4543401d8e2df9bd4c88f26d3b72c11Chris Craik
415a93a0c08b4543401d8e2df9bd4c88f26d3b72c11Chris Craik            @Override
416a93a0c08b4543401d8e2df9bd4c88f26d3b72c11Chris Craik            public void onBindViewHolder(RecyclerView.ViewHolder holder, int position) {
417a93a0c08b4543401d8e2df9bd4c88f26d3b72c11Chris Craik                mRecyclerView.registerTimePassingMs(5);
418a93a0c08b4543401d8e2df9bd4c88f26d3b72c11Chris Craik            }
419a93a0c08b4543401d8e2df9bd4c88f26d3b72c11Chris Craik
420a93a0c08b4543401d8e2df9bd4c88f26d3b72c11Chris Craik            @Override
421a93a0c08b4543401d8e2df9bd4c88f26d3b72c11Chris Craik            public int getItemCount() {
422a93a0c08b4543401d8e2df9bd4c88f26d3b72c11Chris Craik                return 100;
423a93a0c08b4543401d8e2df9bd4c88f26d3b72c11Chris Craik            }
424a93a0c08b4543401d8e2df9bd4c88f26d3b72c11Chris Craik        };
425a93a0c08b4543401d8e2df9bd4c88f26d3b72c11Chris Craik        mRecyclerView.setAdapter(adapter);
426a93a0c08b4543401d8e2df9bd4c88f26d3b72c11Chris Craik
427dbf84daff4f8c5a9e1b8cfa63ab14106dbab9038Chris Craik        layout(300, 300);
428a93a0c08b4543401d8e2df9bd4c88f26d3b72c11Chris Craik
429356880d3de117b067522ad452f4e3eed85ce444cChris Craik        // offset scroll so that no prefetch-able views are directly adjacent to viewport
430356880d3de117b067522ad452f4e3eed85ce444cChris Craik        mRecyclerView.scrollBy(0, 50);
431356880d3de117b067522ad452f4e3eed85ce444cChris Craik
432a93a0c08b4543401d8e2df9bd4c88f26d3b72c11Chris Craik        assertTrue(mRecycler.mCachedViews.size() == 0);
433a93a0c08b4543401d8e2df9bd4c88f26d3b72c11Chris Craik        assertTrue(mRecyclerView.getRecycledViewPool().getRecycledViewCount(0) == 0);
434a93a0c08b4543401d8e2df9bd4c88f26d3b72c11Chris Craik
435a93a0c08b4543401d8e2df9bd4c88f26d3b72c11Chris Craik        // Should take 15 ms to inflate, bind, inflate, so give 19 to be safe
436a93a0c08b4543401d8e2df9bd4c88f26d3b72c11Chris Craik        final long deadlineNs = mRecyclerView.getNanoTime() + TimeUnit.MILLISECONDS.toNanos(19);
437a93a0c08b4543401d8e2df9bd4c88f26d3b72c11Chris Craik
438a93a0c08b4543401d8e2df9bd4c88f26d3b72c11Chris Craik        // Timed prefetch
439945dc1e8a9ee0b71e6b2454ccb3e61145d2132f5Chris Craik        mRecyclerView.mPrefetchRegistry.setPrefetchVector(0, 1);
44047d17a72daeb141148d13a0b0f2df146f6524a9dChris Craik        mRecyclerView.mGapWorker.prefetch(deadlineNs);
441a93a0c08b4543401d8e2df9bd4c88f26d3b72c11Chris Craik
442a93a0c08b4543401d8e2df9bd4c88f26d3b72c11Chris Craik        // will have enough time to inflate/bind one view, and inflate another
443a93a0c08b4543401d8e2df9bd4c88f26d3b72c11Chris Craik        assertTrue(mRecycler.mCachedViews.size() == 1);
444a93a0c08b4543401d8e2df9bd4c88f26d3b72c11Chris Craik        assertTrue(mRecyclerView.getRecycledViewPool().getRecycledViewCount(0) == 1);
445356880d3de117b067522ad452f4e3eed85ce444cChris Craik        // Note: order/view below is an implementation detail
446356880d3de117b067522ad452f4e3eed85ce444cChris Craik        CacheUtils.verifyCacheContainsPositions(mRecyclerView, 12);
447a93a0c08b4543401d8e2df9bd4c88f26d3b72c11Chris Craik
448a93a0c08b4543401d8e2df9bd4c88f26d3b72c11Chris Craik
449a93a0c08b4543401d8e2df9bd4c88f26d3b72c11Chris Craik        // Unbounded prefetch this time
45047d17a72daeb141148d13a0b0f2df146f6524a9dChris Craik        mRecyclerView.mGapWorker.prefetch(RecyclerView.FOREVER_NS);
451a93a0c08b4543401d8e2df9bd4c88f26d3b72c11Chris Craik
452a93a0c08b4543401d8e2df9bd4c88f26d3b72c11Chris Craik        // Should finish all work
453a93a0c08b4543401d8e2df9bd4c88f26d3b72c11Chris Craik        assertTrue(mRecycler.mCachedViews.size() == 3);
454a93a0c08b4543401d8e2df9bd4c88f26d3b72c11Chris Craik        assertTrue(mRecyclerView.getRecycledViewPool().getRecycledViewCount(0) == 0);
455356880d3de117b067522ad452f4e3eed85ce444cChris Craik        CacheUtils.verifyCacheContainsPositions(mRecyclerView, 12, 13, 14);
456945dc1e8a9ee0b71e6b2454ccb3e61145d2132f5Chris Craik    }
457945dc1e8a9ee0b71e6b2454ccb3e61145d2132f5Chris Craik
458945dc1e8a9ee0b71e6b2454ccb3e61145d2132f5Chris Craik    @Test
4591e0a453dff8d7bb7a966d006541454c770d1ed05Chris Craik    public void partialPrefetchAvoidsViewRecycledCallback() {
4601e0a453dff8d7bb7a966d006541454c770d1ed05Chris Craik        mRecyclerView.setLayoutManager(new LinearLayoutManager(getContext()));
4611e0a453dff8d7bb7a966d006541454c770d1ed05Chris Craik
4621e0a453dff8d7bb7a966d006541454c770d1ed05Chris Craik        // 100x100 pixel views
4631e0a453dff8d7bb7a966d006541454c770d1ed05Chris Craik        RecyclerView.Adapter adapter = new RecyclerView.Adapter() {
4641e0a453dff8d7bb7a966d006541454c770d1ed05Chris Craik            @Override
4651e0a453dff8d7bb7a966d006541454c770d1ed05Chris Craik            public RecyclerView.ViewHolder onCreateViewHolder(ViewGroup parent, int viewType) {
4661e0a453dff8d7bb7a966d006541454c770d1ed05Chris Craik                mRecyclerView.registerTimePassingMs(5);
4671e0a453dff8d7bb7a966d006541454c770d1ed05Chris Craik                View view = new View(getContext());
4681e0a453dff8d7bb7a966d006541454c770d1ed05Chris Craik                view.setMinimumWidth(100);
4691e0a453dff8d7bb7a966d006541454c770d1ed05Chris Craik                view.setMinimumHeight(100);
4701e0a453dff8d7bb7a966d006541454c770d1ed05Chris Craik                return new RecyclerView.ViewHolder(view) {};
4711e0a453dff8d7bb7a966d006541454c770d1ed05Chris Craik            }
4721e0a453dff8d7bb7a966d006541454c770d1ed05Chris Craik
4731e0a453dff8d7bb7a966d006541454c770d1ed05Chris Craik            @Override
4741e0a453dff8d7bb7a966d006541454c770d1ed05Chris Craik            public void onBindViewHolder(RecyclerView.ViewHolder holder, int position) {
4751e0a453dff8d7bb7a966d006541454c770d1ed05Chris Craik                mRecyclerView.registerTimePassingMs(5);
4761e0a453dff8d7bb7a966d006541454c770d1ed05Chris Craik            }
4771e0a453dff8d7bb7a966d006541454c770d1ed05Chris Craik
4781e0a453dff8d7bb7a966d006541454c770d1ed05Chris Craik            @Override
4791e0a453dff8d7bb7a966d006541454c770d1ed05Chris Craik            public int getItemCount() {
4801e0a453dff8d7bb7a966d006541454c770d1ed05Chris Craik                return 100;
4811e0a453dff8d7bb7a966d006541454c770d1ed05Chris Craik            }
4821e0a453dff8d7bb7a966d006541454c770d1ed05Chris Craik
4831e0a453dff8d7bb7a966d006541454c770d1ed05Chris Craik            @Override
4841e0a453dff8d7bb7a966d006541454c770d1ed05Chris Craik            public void onViewRecycled(RecyclerView.ViewHolder holder) {
4851e0a453dff8d7bb7a966d006541454c770d1ed05Chris Craik                // verify unbound view doesn't get
4861e0a453dff8d7bb7a966d006541454c770d1ed05Chris Craik                assertNotEquals(RecyclerView.NO_POSITION, holder.getAdapterPosition());
4871e0a453dff8d7bb7a966d006541454c770d1ed05Chris Craik            }
4881e0a453dff8d7bb7a966d006541454c770d1ed05Chris Craik        };
4891e0a453dff8d7bb7a966d006541454c770d1ed05Chris Craik        mRecyclerView.setAdapter(adapter);
4901e0a453dff8d7bb7a966d006541454c770d1ed05Chris Craik
4911e0a453dff8d7bb7a966d006541454c770d1ed05Chris Craik        layout(100, 300);
4921e0a453dff8d7bb7a966d006541454c770d1ed05Chris Craik
4931e0a453dff8d7bb7a966d006541454c770d1ed05Chris Craik        // offset scroll so that no prefetch-able views are directly adjacent to viewport
4941e0a453dff8d7bb7a966d006541454c770d1ed05Chris Craik        mRecyclerView.scrollBy(0, 50);
4951e0a453dff8d7bb7a966d006541454c770d1ed05Chris Craik
4961e0a453dff8d7bb7a966d006541454c770d1ed05Chris Craik        assertTrue(mRecycler.mCachedViews.size() == 0);
4971e0a453dff8d7bb7a966d006541454c770d1ed05Chris Craik        assertTrue(mRecyclerView.getRecycledViewPool().getRecycledViewCount(0) == 0);
4981e0a453dff8d7bb7a966d006541454c770d1ed05Chris Craik
4991e0a453dff8d7bb7a966d006541454c770d1ed05Chris Craik        // Should take 10 ms to inflate + bind, so just give it 9 so it doesn't have time to bind
5001e0a453dff8d7bb7a966d006541454c770d1ed05Chris Craik        final long deadlineNs = mRecyclerView.getNanoTime() + TimeUnit.MILLISECONDS.toNanos(9);
5011e0a453dff8d7bb7a966d006541454c770d1ed05Chris Craik
5021e0a453dff8d7bb7a966d006541454c770d1ed05Chris Craik        // Timed prefetch
5031e0a453dff8d7bb7a966d006541454c770d1ed05Chris Craik        mRecyclerView.mPrefetchRegistry.setPrefetchVector(0, 1);
5041e0a453dff8d7bb7a966d006541454c770d1ed05Chris Craik        mRecyclerView.mGapWorker.prefetch(deadlineNs);
5051e0a453dff8d7bb7a966d006541454c770d1ed05Chris Craik
5061e0a453dff8d7bb7a966d006541454c770d1ed05Chris Craik        // will have enough time to inflate but not bind one view
5071e0a453dff8d7bb7a966d006541454c770d1ed05Chris Craik        assertTrue(mRecycler.mCachedViews.size() == 0);
5081e0a453dff8d7bb7a966d006541454c770d1ed05Chris Craik        assertTrue(mRecyclerView.getRecycledViewPool().getRecycledViewCount(0) == 1);
5091e0a453dff8d7bb7a966d006541454c770d1ed05Chris Craik        RecyclerView.ViewHolder pooledHolder = mRecyclerView.getRecycledViewPool()
5101e0a453dff8d7bb7a966d006541454c770d1ed05Chris Craik                .mScrap.get(0).mScrapHeap.get(0);
5111e0a453dff8d7bb7a966d006541454c770d1ed05Chris Craik        assertEquals(RecyclerView.NO_POSITION, pooledHolder.getAdapterPosition());
5121e0a453dff8d7bb7a966d006541454c770d1ed05Chris Craik    }
5131e0a453dff8d7bb7a966d006541454c770d1ed05Chris Craik
5141e0a453dff8d7bb7a966d006541454c770d1ed05Chris Craik    @Test
515945dc1e8a9ee0b71e6b2454ccb3e61145d2132f5Chris Craik    public void prefetchStaggeredItemsPriority() {
516945dc1e8a9ee0b71e6b2454ccb3e61145d2132f5Chris Craik        StaggeredGridLayoutManager sglm =
517945dc1e8a9ee0b71e6b2454ccb3e61145d2132f5Chris Craik                new StaggeredGridLayoutManager(2, StaggeredGridLayoutManager.VERTICAL);
518945dc1e8a9ee0b71e6b2454ccb3e61145d2132f5Chris Craik        mRecyclerView.setLayoutManager(sglm);
519945dc1e8a9ee0b71e6b2454ccb3e61145d2132f5Chris Craik
520945dc1e8a9ee0b71e6b2454ccb3e61145d2132f5Chris Craik        // first view 50x100 pixels, rest are 100x100 so second column is offset
521945dc1e8a9ee0b71e6b2454ccb3e61145d2132f5Chris Craik        mRecyclerView.setAdapter(new RecyclerView.Adapter() {
522945dc1e8a9ee0b71e6b2454ccb3e61145d2132f5Chris Craik            @Override
523945dc1e8a9ee0b71e6b2454ccb3e61145d2132f5Chris Craik            public RecyclerView.ViewHolder onCreateViewHolder(ViewGroup parent, int viewType) {
524945dc1e8a9ee0b71e6b2454ccb3e61145d2132f5Chris Craik                return new RecyclerView.ViewHolder(new View(getContext())) {};
525945dc1e8a9ee0b71e6b2454ccb3e61145d2132f5Chris Craik            }
526945dc1e8a9ee0b71e6b2454ccb3e61145d2132f5Chris Craik
527945dc1e8a9ee0b71e6b2454ccb3e61145d2132f5Chris Craik            @Override
528945dc1e8a9ee0b71e6b2454ccb3e61145d2132f5Chris Craik            public void onBindViewHolder(RecyclerView.ViewHolder holder, int position) {
529945dc1e8a9ee0b71e6b2454ccb3e61145d2132f5Chris Craik                holder.itemView.setMinimumWidth(100);
530945dc1e8a9ee0b71e6b2454ccb3e61145d2132f5Chris Craik                holder.itemView.setMinimumHeight(position == 0 ? 50 : 100);
531945dc1e8a9ee0b71e6b2454ccb3e61145d2132f5Chris Craik            }
532945dc1e8a9ee0b71e6b2454ccb3e61145d2132f5Chris Craik
533945dc1e8a9ee0b71e6b2454ccb3e61145d2132f5Chris Craik            @Override
534945dc1e8a9ee0b71e6b2454ccb3e61145d2132f5Chris Craik            public int getItemCount() {
535945dc1e8a9ee0b71e6b2454ccb3e61145d2132f5Chris Craik                return 100;
536945dc1e8a9ee0b71e6b2454ccb3e61145d2132f5Chris Craik            }
537945dc1e8a9ee0b71e6b2454ccb3e61145d2132f5Chris Craik        });
538945dc1e8a9ee0b71e6b2454ccb3e61145d2132f5Chris Craik
539dbf84daff4f8c5a9e1b8cfa63ab14106dbab9038Chris Craik        layout(200, 200);
540945dc1e8a9ee0b71e6b2454ccb3e61145d2132f5Chris Craik
541945dc1e8a9ee0b71e6b2454ccb3e61145d2132f5Chris Craik        /* Each row is 50 pixels:
542945dc1e8a9ee0b71e6b2454ccb3e61145d2132f5Chris Craik         * ------------- *
543945dc1e8a9ee0b71e6b2454ccb3e61145d2132f5Chris Craik         *   0   |   1   *
544945dc1e8a9ee0b71e6b2454ccb3e61145d2132f5Chris Craik         *   2   |   1   *
545945dc1e8a9ee0b71e6b2454ccb3e61145d2132f5Chris Craik         *   2   |   3   *
546945dc1e8a9ee0b71e6b2454ccb3e61145d2132f5Chris Craik         *___4___|___3___*
547945dc1e8a9ee0b71e6b2454ccb3e61145d2132f5Chris Craik         *   4   |   5   *
548945dc1e8a9ee0b71e6b2454ccb3e61145d2132f5Chris Craik         *   6   |   5   *
549945dc1e8a9ee0b71e6b2454ccb3e61145d2132f5Chris Craik         *      ...      *
550945dc1e8a9ee0b71e6b2454ccb3e61145d2132f5Chris Craik         */
551945dc1e8a9ee0b71e6b2454ccb3e61145d2132f5Chris Craik        assertEquals(5, mRecyclerView.getChildCount());
552945dc1e8a9ee0b71e6b2454ccb3e61145d2132f5Chris Craik        assertEquals(0, sglm.getFirstChildPosition());
553945dc1e8a9ee0b71e6b2454ccb3e61145d2132f5Chris Craik        assertEquals(4, sglm.getLastChildPosition());
554945dc1e8a9ee0b71e6b2454ccb3e61145d2132f5Chris Craik
555945dc1e8a9ee0b71e6b2454ccb3e61145d2132f5Chris Craik        // prefetching down shows 5 at 0 pixels away, 6 at 50 pixels away
556356880d3de117b067522ad452f4e3eed85ce444cChris Craik        CacheUtils.verifyPositionsPrefetched(mRecyclerView, 0, 10,
557945dc1e8a9ee0b71e6b2454ccb3e61145d2132f5Chris Craik                new Integer[] {5, 0}, new Integer[] {6, 50});
558945dc1e8a9ee0b71e6b2454ccb3e61145d2132f5Chris Craik
559945dc1e8a9ee0b71e6b2454ccb3e61145d2132f5Chris Craik        // Prefetch upward shows nothing
560356880d3de117b067522ad452f4e3eed85ce444cChris Craik        CacheUtils.verifyPositionsPrefetched(mRecyclerView, 0, -10);
561945dc1e8a9ee0b71e6b2454ccb3e61145d2132f5Chris Craik
562945dc1e8a9ee0b71e6b2454ccb3e61145d2132f5Chris Craik        mRecyclerView.scrollBy(0, 100);
563945dc1e8a9ee0b71e6b2454ccb3e61145d2132f5Chris Craik
564945dc1e8a9ee0b71e6b2454ccb3e61145d2132f5Chris Craik        /* Each row is 50 pixels:
565945dc1e8a9ee0b71e6b2454ccb3e61145d2132f5Chris Craik         * ------------- *
566945dc1e8a9ee0b71e6b2454ccb3e61145d2132f5Chris Craik         *   0   |   1   *
567945dc1e8a9ee0b71e6b2454ccb3e61145d2132f5Chris Craik         *___2___|___1___*
568945dc1e8a9ee0b71e6b2454ccb3e61145d2132f5Chris Craik         *   2   |   3   *
569945dc1e8a9ee0b71e6b2454ccb3e61145d2132f5Chris Craik         *   4   |   3   *
570945dc1e8a9ee0b71e6b2454ccb3e61145d2132f5Chris Craik         *   4   |   5   *
571945dc1e8a9ee0b71e6b2454ccb3e61145d2132f5Chris Craik         *___6___|___5___*
572945dc1e8a9ee0b71e6b2454ccb3e61145d2132f5Chris Craik         *   6   |   7   *
573945dc1e8a9ee0b71e6b2454ccb3e61145d2132f5Chris Craik         *   8   |   7   *
574945dc1e8a9ee0b71e6b2454ccb3e61145d2132f5Chris Craik         *      ...      *
575945dc1e8a9ee0b71e6b2454ccb3e61145d2132f5Chris Craik         */
576945dc1e8a9ee0b71e6b2454ccb3e61145d2132f5Chris Craik
577945dc1e8a9ee0b71e6b2454ccb3e61145d2132f5Chris Craik        assertEquals(5, mRecyclerView.getChildCount());
578945dc1e8a9ee0b71e6b2454ccb3e61145d2132f5Chris Craik        assertEquals(2, sglm.getFirstChildPosition());
579945dc1e8a9ee0b71e6b2454ccb3e61145d2132f5Chris Craik        assertEquals(6, sglm.getLastChildPosition());
580945dc1e8a9ee0b71e6b2454ccb3e61145d2132f5Chris Craik
581945dc1e8a9ee0b71e6b2454ccb3e61145d2132f5Chris Craik        // prefetching down shows 7 at 0 pixels away, 8 at 50 pixels away
582356880d3de117b067522ad452f4e3eed85ce444cChris Craik        CacheUtils.verifyPositionsPrefetched(mRecyclerView, 0, 10,
583945dc1e8a9ee0b71e6b2454ccb3e61145d2132f5Chris Craik                new Integer[] {7, 0}, new Integer[] {8, 50});
584945dc1e8a9ee0b71e6b2454ccb3e61145d2132f5Chris Craik
585945dc1e8a9ee0b71e6b2454ccb3e61145d2132f5Chris Craik        // prefetching up shows 1 is 0 pixels away, 0 at 50 pixels away
586356880d3de117b067522ad452f4e3eed85ce444cChris Craik        CacheUtils.verifyPositionsPrefetched(mRecyclerView, 0, -10,
587945dc1e8a9ee0b71e6b2454ccb3e61145d2132f5Chris Craik                new Integer[] {1, 0}, new Integer[] {0, 50});
588945dc1e8a9ee0b71e6b2454ccb3e61145d2132f5Chris Craik    }
589dbf84daff4f8c5a9e1b8cfa63ab14106dbab9038Chris Craik
590dbf84daff4f8c5a9e1b8cfa63ab14106dbab9038Chris Craik    @Test
591695d9b8629fa048795298359d4845f0747364fafChris Craik    public void prefetchStaggeredPastBoundary() {
592695d9b8629fa048795298359d4845f0747364fafChris Craik        StaggeredGridLayoutManager sglm =
593695d9b8629fa048795298359d4845f0747364fafChris Craik                new StaggeredGridLayoutManager(2, StaggeredGridLayoutManager.VERTICAL);
594695d9b8629fa048795298359d4845f0747364fafChris Craik        mRecyclerView.setLayoutManager(sglm);
595695d9b8629fa048795298359d4845f0747364fafChris Craik        mRecyclerView.setAdapter(new RecyclerView.Adapter() {
596695d9b8629fa048795298359d4845f0747364fafChris Craik            @Override
597695d9b8629fa048795298359d4845f0747364fafChris Craik            public RecyclerView.ViewHolder onCreateViewHolder(ViewGroup parent, int viewType) {
598695d9b8629fa048795298359d4845f0747364fafChris Craik                return new RecyclerView.ViewHolder(new View(getContext())) {};
599695d9b8629fa048795298359d4845f0747364fafChris Craik            }
600695d9b8629fa048795298359d4845f0747364fafChris Craik
601695d9b8629fa048795298359d4845f0747364fafChris Craik            @Override
602695d9b8629fa048795298359d4845f0747364fafChris Craik            public void onBindViewHolder(RecyclerView.ViewHolder holder, int position) {
603695d9b8629fa048795298359d4845f0747364fafChris Craik                holder.itemView.setMinimumWidth(100);
604695d9b8629fa048795298359d4845f0747364fafChris Craik                holder.itemView.setMinimumHeight(position == 0 ? 100 : 200);
605695d9b8629fa048795298359d4845f0747364fafChris Craik            }
606695d9b8629fa048795298359d4845f0747364fafChris Craik
607695d9b8629fa048795298359d4845f0747364fafChris Craik            @Override
608695d9b8629fa048795298359d4845f0747364fafChris Craik            public int getItemCount() {
609695d9b8629fa048795298359d4845f0747364fafChris Craik                return 2;
610695d9b8629fa048795298359d4845f0747364fafChris Craik            }
611695d9b8629fa048795298359d4845f0747364fafChris Craik        });
612695d9b8629fa048795298359d4845f0747364fafChris Craik
613695d9b8629fa048795298359d4845f0747364fafChris Craik        layout(200, 100);
614695d9b8629fa048795298359d4845f0747364fafChris Craik        mRecyclerView.scrollBy(0, 50);
615695d9b8629fa048795298359d4845f0747364fafChris Craik
616695d9b8629fa048795298359d4845f0747364fafChris Craik        /* Each row is 50 pixels:
617695d9b8629fa048795298359d4845f0747364fafChris Craik         * ------------- *
618695d9b8629fa048795298359d4845f0747364fafChris Craik         *___0___|___1___*
619695d9b8629fa048795298359d4845f0747364fafChris Craik         *   0   |   1   *
620695d9b8629fa048795298359d4845f0747364fafChris Craik         *_______|___1___*
621695d9b8629fa048795298359d4845f0747364fafChris Craik         *       |   1   *
622695d9b8629fa048795298359d4845f0747364fafChris Craik         */
623695d9b8629fa048795298359d4845f0747364fafChris Craik        assertEquals(2, mRecyclerView.getChildCount());
624695d9b8629fa048795298359d4845f0747364fafChris Craik        assertEquals(0, sglm.getFirstChildPosition());
625695d9b8629fa048795298359d4845f0747364fafChris Craik        assertEquals(1, sglm.getLastChildPosition());
626695d9b8629fa048795298359d4845f0747364fafChris Craik
627695d9b8629fa048795298359d4845f0747364fafChris Craik        // prefetch upward gets nothing
628695d9b8629fa048795298359d4845f0747364fafChris Craik        CacheUtils.verifyPositionsPrefetched(mRecyclerView, 0, -10);
629695d9b8629fa048795298359d4845f0747364fafChris Craik
630695d9b8629fa048795298359d4845f0747364fafChris Craik        // prefetch downward gets nothing (and doesn't crash...)
631695d9b8629fa048795298359d4845f0747364fafChris Craik        CacheUtils.verifyPositionsPrefetched(mRecyclerView, 0, 10);
632695d9b8629fa048795298359d4845f0747364fafChris Craik    }
633695d9b8629fa048795298359d4845f0747364fafChris Craik
634695d9b8629fa048795298359d4845f0747364fafChris Craik    @Test
635dbf84daff4f8c5a9e1b8cfa63ab14106dbab9038Chris Craik    public void prefetchItemsSkipAnimations() {
636dbf84daff4f8c5a9e1b8cfa63ab14106dbab9038Chris Craik        LinearLayoutManager llm = new LinearLayoutManager(getContext());
637dbf84daff4f8c5a9e1b8cfa63ab14106dbab9038Chris Craik        mRecyclerView.setLayoutManager(llm);
638dbf84daff4f8c5a9e1b8cfa63ab14106dbab9038Chris Craik        final int[] expandedPosition = new int[] {-1};
639dbf84daff4f8c5a9e1b8cfa63ab14106dbab9038Chris Craik
640dbf84daff4f8c5a9e1b8cfa63ab14106dbab9038Chris Craik        final RecyclerView.Adapter adapter = new RecyclerView.Adapter() {
641dbf84daff4f8c5a9e1b8cfa63ab14106dbab9038Chris Craik            @Override
642dbf84daff4f8c5a9e1b8cfa63ab14106dbab9038Chris Craik            public RecyclerView.ViewHolder onCreateViewHolder(ViewGroup parent, int viewType) {
6431e0a453dff8d7bb7a966d006541454c770d1ed05Chris Craik                return new RecyclerView.ViewHolder(new View(parent.getContext())) {};
644dbf84daff4f8c5a9e1b8cfa63ab14106dbab9038Chris Craik            }
645dbf84daff4f8c5a9e1b8cfa63ab14106dbab9038Chris Craik
646dbf84daff4f8c5a9e1b8cfa63ab14106dbab9038Chris Craik            @Override
647dbf84daff4f8c5a9e1b8cfa63ab14106dbab9038Chris Craik            public void onBindViewHolder(RecyclerView.ViewHolder holder, int position) {
648dbf84daff4f8c5a9e1b8cfa63ab14106dbab9038Chris Craik                int height = expandedPosition[0] == position ? 400 : 100;
649dbf84daff4f8c5a9e1b8cfa63ab14106dbab9038Chris Craik                holder.itemView.setLayoutParams(new RecyclerView.LayoutParams(200, height));
650dbf84daff4f8c5a9e1b8cfa63ab14106dbab9038Chris Craik            }
651dbf84daff4f8c5a9e1b8cfa63ab14106dbab9038Chris Craik
652dbf84daff4f8c5a9e1b8cfa63ab14106dbab9038Chris Craik            @Override
653dbf84daff4f8c5a9e1b8cfa63ab14106dbab9038Chris Craik            public int getItemCount() {
654dbf84daff4f8c5a9e1b8cfa63ab14106dbab9038Chris Craik                return 10;
655dbf84daff4f8c5a9e1b8cfa63ab14106dbab9038Chris Craik            }
656dbf84daff4f8c5a9e1b8cfa63ab14106dbab9038Chris Craik        };
657dbf84daff4f8c5a9e1b8cfa63ab14106dbab9038Chris Craik
658dbf84daff4f8c5a9e1b8cfa63ab14106dbab9038Chris Craik        // make move duration long enough to be able to see the effects
659dbf84daff4f8c5a9e1b8cfa63ab14106dbab9038Chris Craik        RecyclerView.ItemAnimator itemAnimator = mRecyclerView.getItemAnimator();
660dbf84daff4f8c5a9e1b8cfa63ab14106dbab9038Chris Craik        itemAnimator.setMoveDuration(10000);
661dbf84daff4f8c5a9e1b8cfa63ab14106dbab9038Chris Craik        mRecyclerView.setAdapter(adapter);
662dbf84daff4f8c5a9e1b8cfa63ab14106dbab9038Chris Craik
663dbf84daff4f8c5a9e1b8cfa63ab14106dbab9038Chris Craik        layout(200, 400);
664dbf84daff4f8c5a9e1b8cfa63ab14106dbab9038Chris Craik
665dbf84daff4f8c5a9e1b8cfa63ab14106dbab9038Chris Craik        expandedPosition[0] = 1;
666dbf84daff4f8c5a9e1b8cfa63ab14106dbab9038Chris Craik        // insert payload to avoid creating a new view
667dbf84daff4f8c5a9e1b8cfa63ab14106dbab9038Chris Craik        adapter.notifyItemChanged(1, new Object());
668dbf84daff4f8c5a9e1b8cfa63ab14106dbab9038Chris Craik
669dbf84daff4f8c5a9e1b8cfa63ab14106dbab9038Chris Craik        layout(200, 400);
670dbf84daff4f8c5a9e1b8cfa63ab14106dbab9038Chris Craik        layout(200, 400);
671dbf84daff4f8c5a9e1b8cfa63ab14106dbab9038Chris Craik
672dbf84daff4f8c5a9e1b8cfa63ab14106dbab9038Chris Craik        assertTrue(itemAnimator.isRunning());
673dbf84daff4f8c5a9e1b8cfa63ab14106dbab9038Chris Craik        assertEquals(2, llm.getChildCount());
674dbf84daff4f8c5a9e1b8cfa63ab14106dbab9038Chris Craik        assertEquals(4, mRecyclerView.getChildCount());
675dbf84daff4f8c5a9e1b8cfa63ab14106dbab9038Chris Craik
676dbf84daff4f8c5a9e1b8cfa63ab14106dbab9038Chris Craik        // animating view should be observable as hidden, uncached...
677dbf84daff4f8c5a9e1b8cfa63ab14106dbab9038Chris Craik        CacheUtils.verifyCacheDoesNotContainPositions(mRecyclerView, 2);
678dbf84daff4f8c5a9e1b8cfa63ab14106dbab9038Chris Craik        assertNotNull("Animating view should be found, hidden",
679dbf84daff4f8c5a9e1b8cfa63ab14106dbab9038Chris Craik                mRecyclerView.mChildHelper.findHiddenNonRemovedView(2));
680dbf84daff4f8c5a9e1b8cfa63ab14106dbab9038Chris Craik        assertTrue(GapWorker.isPrefetchPositionAttached(mRecyclerView, 2));
681dbf84daff4f8c5a9e1b8cfa63ab14106dbab9038Chris Craik
682dbf84daff4f8c5a9e1b8cfa63ab14106dbab9038Chris Craik        // ...but must not be removed for prefetch
683dbf84daff4f8c5a9e1b8cfa63ab14106dbab9038Chris Craik        mRecyclerView.mPrefetchRegistry.setPrefetchVector(0, 1);
684dbf84daff4f8c5a9e1b8cfa63ab14106dbab9038Chris Craik        mRecyclerView.mGapWorker.prefetch(RecyclerView.FOREVER_NS);
685dbf84daff4f8c5a9e1b8cfa63ab14106dbab9038Chris Craik
686dbf84daff4f8c5a9e1b8cfa63ab14106dbab9038Chris Craik        assertEquals("Prefetch must target one view", 1, mRecyclerView.mPrefetchRegistry.mCount);
687dbf84daff4f8c5a9e1b8cfa63ab14106dbab9038Chris Craik        int prefetchTarget = mRecyclerView.mPrefetchRegistry.mPrefetchArray[0];
688dbf84daff4f8c5a9e1b8cfa63ab14106dbab9038Chris Craik        assertEquals("Prefetch must target view 2", 2, prefetchTarget);
689dbf84daff4f8c5a9e1b8cfa63ab14106dbab9038Chris Craik
690dbf84daff4f8c5a9e1b8cfa63ab14106dbab9038Chris Craik        // animating view still observable as hidden, uncached
691dbf84daff4f8c5a9e1b8cfa63ab14106dbab9038Chris Craik        CacheUtils.verifyCacheDoesNotContainPositions(mRecyclerView, 2);
692dbf84daff4f8c5a9e1b8cfa63ab14106dbab9038Chris Craik        assertNotNull("Animating view should be found, hidden",
693dbf84daff4f8c5a9e1b8cfa63ab14106dbab9038Chris Craik                mRecyclerView.mChildHelper.findHiddenNonRemovedView(2));
694dbf84daff4f8c5a9e1b8cfa63ab14106dbab9038Chris Craik        assertTrue(GapWorker.isPrefetchPositionAttached(mRecyclerView, 2));
695dbf84daff4f8c5a9e1b8cfa63ab14106dbab9038Chris Craik
696dbf84daff4f8c5a9e1b8cfa63ab14106dbab9038Chris Craik        assertTrue(itemAnimator.isRunning());
697dbf84daff4f8c5a9e1b8cfa63ab14106dbab9038Chris Craik        assertEquals(2, llm.getChildCount());
698dbf84daff4f8c5a9e1b8cfa63ab14106dbab9038Chris Craik        assertEquals(4, mRecyclerView.getChildCount());
699dbf84daff4f8c5a9e1b8cfa63ab14106dbab9038Chris Craik    }
7001e0a453dff8d7bb7a966d006541454c770d1ed05Chris Craik
7011e0a453dff8d7bb7a966d006541454c770d1ed05Chris Craik    @Test
7021e0a453dff8d7bb7a966d006541454c770d1ed05Chris Craik    public void viewHolderFindsNestedRecyclerViews() {
7031e0a453dff8d7bb7a966d006541454c770d1ed05Chris Craik        LinearLayoutManager llm = new LinearLayoutManager(getContext());
7041e0a453dff8d7bb7a966d006541454c770d1ed05Chris Craik        mRecyclerView.setLayoutManager(llm);
7051e0a453dff8d7bb7a966d006541454c770d1ed05Chris Craik
7061e0a453dff8d7bb7a966d006541454c770d1ed05Chris Craik        RecyclerView.Adapter mockAdapter = mock(RecyclerView.Adapter.class);
7071e0a453dff8d7bb7a966d006541454c770d1ed05Chris Craik        when(mockAdapter.onCreateViewHolder(any(ViewGroup.class), anyInt()))
7081e0a453dff8d7bb7a966d006541454c770d1ed05Chris Craik                .thenAnswer(new Answer<RecyclerView.ViewHolder>() {
7091e0a453dff8d7bb7a966d006541454c770d1ed05Chris Craik                    @Override
7101e0a453dff8d7bb7a966d006541454c770d1ed05Chris Craik                    public RecyclerView.ViewHolder answer(InvocationOnMock invocation)
7111e0a453dff8d7bb7a966d006541454c770d1ed05Chris Craik                            throws Throwable {
7121e0a453dff8d7bb7a966d006541454c770d1ed05Chris Craik                        View view = new RecyclerView(getContext());
7131e0a453dff8d7bb7a966d006541454c770d1ed05Chris Craik                        view.setLayoutParams(new RecyclerView.LayoutParams(100, 100));
7141e0a453dff8d7bb7a966d006541454c770d1ed05Chris Craik                        return new RecyclerView.ViewHolder(view) {};
7151e0a453dff8d7bb7a966d006541454c770d1ed05Chris Craik                    }
7161e0a453dff8d7bb7a966d006541454c770d1ed05Chris Craik                });
7171e0a453dff8d7bb7a966d006541454c770d1ed05Chris Craik        when(mockAdapter.getItemCount()).thenReturn(100);
7181e0a453dff8d7bb7a966d006541454c770d1ed05Chris Craik        mRecyclerView.setAdapter(mockAdapter);
7191e0a453dff8d7bb7a966d006541454c770d1ed05Chris Craik
7201e0a453dff8d7bb7a966d006541454c770d1ed05Chris Craik        layout(100, 200);
7211e0a453dff8d7bb7a966d006541454c770d1ed05Chris Craik
7221e0a453dff8d7bb7a966d006541454c770d1ed05Chris Craik        verify(mockAdapter, times(2)).onCreateViewHolder(any(ViewGroup.class), anyInt());
7231e0a453dff8d7bb7a966d006541454c770d1ed05Chris Craik        verify(mockAdapter, times(2)).onBindViewHolder(
724963facb8fce35022f296c38fadafd9a959ab1655Aurimas Liutikas                argThat(new ArgumentMatcher<RecyclerView.ViewHolder>() {
7251e0a453dff8d7bb7a966d006541454c770d1ed05Chris Craik                    @Override
726963facb8fce35022f296c38fadafd9a959ab1655Aurimas Liutikas                    public boolean matches(RecyclerView.ViewHolder holder) {
7278123166eaf758eacc6ba5854c9b29d99f21388d6Chris Craik                        return holder.itemView == holder.mNestedRecyclerView.get();
7281e0a453dff8d7bb7a966d006541454c770d1ed05Chris Craik                    }
7291e0a453dff8d7bb7a966d006541454c770d1ed05Chris Craik                }),
7301e0a453dff8d7bb7a966d006541454c770d1ed05Chris Craik                anyInt(),
7311e0a453dff8d7bb7a966d006541454c770d1ed05Chris Craik                any(List.class));
7321e0a453dff8d7bb7a966d006541454c770d1ed05Chris Craik    }
7331e0a453dff8d7bb7a966d006541454c770d1ed05Chris Craik
7340fab45107f043a4856aad2e1fd785b396898616eChris Craik    class InnerAdapter extends RecyclerView.Adapter<InnerAdapter.ViewHolder> {
7350ceae57ee14da826c31a66c67e70f7f17108d5caChris Craik        private static final int INNER_ITEM_COUNT = 20;
736f2f73b7cc9bed0e7204ba43796bb0112cda27cf1Chris Craik        int mItemsBound = 0;
737f2f73b7cc9bed0e7204ba43796bb0112cda27cf1Chris Craik
7380fab45107f043a4856aad2e1fd785b396898616eChris Craik        class ViewHolder extends RecyclerView.ViewHolder {
7391e0a453dff8d7bb7a966d006541454c770d1ed05Chris Craik            ViewHolder(View itemView) {
7401e0a453dff8d7bb7a966d006541454c770d1ed05Chris Craik                super(itemView);
7411e0a453dff8d7bb7a966d006541454c770d1ed05Chris Craik            }
7421e0a453dff8d7bb7a966d006541454c770d1ed05Chris Craik        }
7431e0a453dff8d7bb7a966d006541454c770d1ed05Chris Craik
7441e0a453dff8d7bb7a966d006541454c770d1ed05Chris Craik        InnerAdapter() {}
7451e0a453dff8d7bb7a966d006541454c770d1ed05Chris Craik
7461e0a453dff8d7bb7a966d006541454c770d1ed05Chris Craik        @Override
7471e0a453dff8d7bb7a966d006541454c770d1ed05Chris Craik        public ViewHolder onCreateViewHolder(ViewGroup parent, int viewType) {
7480fab45107f043a4856aad2e1fd785b396898616eChris Craik            mRecyclerView.registerTimePassingMs(5);
7491e0a453dff8d7bb7a966d006541454c770d1ed05Chris Craik            View view = new View(parent.getContext());
7501e0a453dff8d7bb7a966d006541454c770d1ed05Chris Craik            view.setLayoutParams(new RecyclerView.LayoutParams(100, 100));
7511e0a453dff8d7bb7a966d006541454c770d1ed05Chris Craik            return new ViewHolder(view);
7521e0a453dff8d7bb7a966d006541454c770d1ed05Chris Craik        }
7531e0a453dff8d7bb7a966d006541454c770d1ed05Chris Craik
7541e0a453dff8d7bb7a966d006541454c770d1ed05Chris Craik        @Override
755f2f73b7cc9bed0e7204ba43796bb0112cda27cf1Chris Craik        public void onBindViewHolder(ViewHolder holder, int position) {
7560fab45107f043a4856aad2e1fd785b396898616eChris Craik            mRecyclerView.registerTimePassingMs(5);
757f2f73b7cc9bed0e7204ba43796bb0112cda27cf1Chris Craik            mItemsBound++;
758f2f73b7cc9bed0e7204ba43796bb0112cda27cf1Chris Craik        }
7591e0a453dff8d7bb7a966d006541454c770d1ed05Chris Craik
7601e0a453dff8d7bb7a966d006541454c770d1ed05Chris Craik        @Override
7611e0a453dff8d7bb7a966d006541454c770d1ed05Chris Craik        public int getItemCount() {
7621e0a453dff8d7bb7a966d006541454c770d1ed05Chris Craik            return INNER_ITEM_COUNT;
7631e0a453dff8d7bb7a966d006541454c770d1ed05Chris Craik        }
7641e0a453dff8d7bb7a966d006541454c770d1ed05Chris Craik    }
7651e0a453dff8d7bb7a966d006541454c770d1ed05Chris Craik
7660fab45107f043a4856aad2e1fd785b396898616eChris Craik    class OuterAdapter extends RecyclerView.Adapter<OuterAdapter.ViewHolder> {
7671e0a453dff8d7bb7a966d006541454c770d1ed05Chris Craik
7680ceae57ee14da826c31a66c67e70f7f17108d5caChris Craik        private boolean mReverseInner;
7690ceae57ee14da826c31a66c67e70f7f17108d5caChris Craik
7700fab45107f043a4856aad2e1fd785b396898616eChris Craik        class ViewHolder extends RecyclerView.ViewHolder {
7711e0a453dff8d7bb7a966d006541454c770d1ed05Chris Craik            private final RecyclerView mRecyclerView;
7721e0a453dff8d7bb7a966d006541454c770d1ed05Chris Craik            ViewHolder(RecyclerView itemView) {
7731e0a453dff8d7bb7a966d006541454c770d1ed05Chris Craik                super(itemView);
7741e0a453dff8d7bb7a966d006541454c770d1ed05Chris Craik                mRecyclerView = itemView;
7751e0a453dff8d7bb7a966d006541454c770d1ed05Chris Craik            }
7761e0a453dff8d7bb7a966d006541454c770d1ed05Chris Craik        }
7771e0a453dff8d7bb7a966d006541454c770d1ed05Chris Craik
7781e0a453dff8d7bb7a966d006541454c770d1ed05Chris Craik        ArrayList<InnerAdapter> mAdapters = new ArrayList<>();
7790ceae57ee14da826c31a66c67e70f7f17108d5caChris Craik        ArrayList<Parcelable> mSavedStates = new ArrayList<>();
7801e0a453dff8d7bb7a966d006541454c770d1ed05Chris Craik        RecyclerView.RecycledViewPool mSharedPool = new RecyclerView.RecycledViewPool();
7811e0a453dff8d7bb7a966d006541454c770d1ed05Chris Craik
7821e0a453dff8d7bb7a966d006541454c770d1ed05Chris Craik        OuterAdapter() {
7830ceae57ee14da826c31a66c67e70f7f17108d5caChris Craik            this(false);
7840ceae57ee14da826c31a66c67e70f7f17108d5caChris Craik        }
7850ceae57ee14da826c31a66c67e70f7f17108d5caChris Craik
7860ceae57ee14da826c31a66c67e70f7f17108d5caChris Craik        OuterAdapter(boolean reverseInner) {
7878123166eaf758eacc6ba5854c9b29d99f21388d6Chris Craik            this(reverseInner, 10);
7888123166eaf758eacc6ba5854c9b29d99f21388d6Chris Craik        }
7898123166eaf758eacc6ba5854c9b29d99f21388d6Chris Craik
7908123166eaf758eacc6ba5854c9b29d99f21388d6Chris Craik        OuterAdapter(boolean reverseInner, int itemCount) {
7910ceae57ee14da826c31a66c67e70f7f17108d5caChris Craik            mReverseInner = reverseInner;
7928123166eaf758eacc6ba5854c9b29d99f21388d6Chris Craik            for (int i = 0; i < itemCount; i++) {
7931e0a453dff8d7bb7a966d006541454c770d1ed05Chris Craik                mAdapters.add(new InnerAdapter());
7940ceae57ee14da826c31a66c67e70f7f17108d5caChris Craik                mSavedStates.add(null);
7951e0a453dff8d7bb7a966d006541454c770d1ed05Chris Craik            }
7961e0a453dff8d7bb7a966d006541454c770d1ed05Chris Craik        }
7971e0a453dff8d7bb7a966d006541454c770d1ed05Chris Craik
7988123166eaf758eacc6ba5854c9b29d99f21388d6Chris Craik        void addItem() {
7998123166eaf758eacc6ba5854c9b29d99f21388d6Chris Craik            int index = getItemCount();
8008123166eaf758eacc6ba5854c9b29d99f21388d6Chris Craik            mAdapters.add(new InnerAdapter());
8018123166eaf758eacc6ba5854c9b29d99f21388d6Chris Craik            mSavedStates.add(null);
8028123166eaf758eacc6ba5854c9b29d99f21388d6Chris Craik            notifyItemInserted(index);
8038123166eaf758eacc6ba5854c9b29d99f21388d6Chris Craik        }
8048123166eaf758eacc6ba5854c9b29d99f21388d6Chris Craik
8051e0a453dff8d7bb7a966d006541454c770d1ed05Chris Craik        @Override
8061e0a453dff8d7bb7a966d006541454c770d1ed05Chris Craik        public ViewHolder onCreateViewHolder(ViewGroup parent, int viewType) {
8070fab45107f043a4856aad2e1fd785b396898616eChris Craik            mRecyclerView.registerTimePassingMs(5);
8080fab45107f043a4856aad2e1fd785b396898616eChris Craik
809bec95cedb8511e1d814181aa518512c7e7fc5cf6Chris Craik            RecyclerView rv = new RecyclerView(parent.getContext()) {
810bec95cedb8511e1d814181aa518512c7e7fc5cf6Chris Craik                @Override
811bec95cedb8511e1d814181aa518512c7e7fc5cf6Chris Craik                public int getWindowVisibility() {
812bec95cedb8511e1d814181aa518512c7e7fc5cf6Chris Craik                    // Pretend to be visible to avoid being filtered out
813bec95cedb8511e1d814181aa518512c7e7fc5cf6Chris Craik                    return View.VISIBLE;
814bec95cedb8511e1d814181aa518512c7e7fc5cf6Chris Craik                }
815bec95cedb8511e1d814181aa518512c7e7fc5cf6Chris Craik            };
8161e0a453dff8d7bb7a966d006541454c770d1ed05Chris Craik            rv.setLayoutManager(new LinearLayoutManager(parent.getContext(),
8170ceae57ee14da826c31a66c67e70f7f17108d5caChris Craik                    LinearLayoutManager.HORIZONTAL, mReverseInner));
8181e0a453dff8d7bb7a966d006541454c770d1ed05Chris Craik            rv.setRecycledViewPool(mSharedPool);
8191e0a453dff8d7bb7a966d006541454c770d1ed05Chris Craik            rv.setLayoutParams(new RecyclerView.LayoutParams(200, 100));
8201e0a453dff8d7bb7a966d006541454c770d1ed05Chris Craik            return new ViewHolder(rv);
8211e0a453dff8d7bb7a966d006541454c770d1ed05Chris Craik        }
8221e0a453dff8d7bb7a966d006541454c770d1ed05Chris Craik
8231e0a453dff8d7bb7a966d006541454c770d1ed05Chris Craik        @Override
8241e0a453dff8d7bb7a966d006541454c770d1ed05Chris Craik        public void onBindViewHolder(ViewHolder holder, int position) {
8250fab45107f043a4856aad2e1fd785b396898616eChris Craik            mRecyclerView.registerTimePassingMs(5);
8260fab45107f043a4856aad2e1fd785b396898616eChris Craik
8270ceae57ee14da826c31a66c67e70f7f17108d5caChris Craik            // Tests may rely on bound holders not being shared between inner adapters,
8280ceae57ee14da826c31a66c67e70f7f17108d5caChris Craik            // since we force recycle here
8290ceae57ee14da826c31a66c67e70f7f17108d5caChris Craik            holder.mRecyclerView.swapAdapter(mAdapters.get(position), true);
8300ceae57ee14da826c31a66c67e70f7f17108d5caChris Craik
8310ceae57ee14da826c31a66c67e70f7f17108d5caChris Craik            Parcelable savedState = mSavedStates.get(position);
8320ceae57ee14da826c31a66c67e70f7f17108d5caChris Craik            if (savedState != null) {
8330ceae57ee14da826c31a66c67e70f7f17108d5caChris Craik                holder.mRecyclerView.getLayoutManager().onRestoreInstanceState(savedState);
8340ceae57ee14da826c31a66c67e70f7f17108d5caChris Craik                mSavedStates.set(position, null);
8350ceae57ee14da826c31a66c67e70f7f17108d5caChris Craik            }
8360ceae57ee14da826c31a66c67e70f7f17108d5caChris Craik        }
8370ceae57ee14da826c31a66c67e70f7f17108d5caChris Craik
8380ceae57ee14da826c31a66c67e70f7f17108d5caChris Craik        @Override
8390ceae57ee14da826c31a66c67e70f7f17108d5caChris Craik        public void onViewRecycled(ViewHolder holder) {
8400ceae57ee14da826c31a66c67e70f7f17108d5caChris Craik            mSavedStates.set(holder.getAdapterPosition(),
8410ceae57ee14da826c31a66c67e70f7f17108d5caChris Craik                    holder.mRecyclerView.getLayoutManager().onSaveInstanceState());
8421e0a453dff8d7bb7a966d006541454c770d1ed05Chris Craik        }
8431e0a453dff8d7bb7a966d006541454c770d1ed05Chris Craik
8441e0a453dff8d7bb7a966d006541454c770d1ed05Chris Craik        @Override
8451e0a453dff8d7bb7a966d006541454c770d1ed05Chris Craik        public int getItemCount() {
8468123166eaf758eacc6ba5854c9b29d99f21388d6Chris Craik            return mAdapters.size();
8471e0a453dff8d7bb7a966d006541454c770d1ed05Chris Craik        }
8481e0a453dff8d7bb7a966d006541454c770d1ed05Chris Craik    }
8491e0a453dff8d7bb7a966d006541454c770d1ed05Chris Craik
8501e0a453dff8d7bb7a966d006541454c770d1ed05Chris Craik    @Test
8510ceae57ee14da826c31a66c67e70f7f17108d5caChris Craik    public void nestedPrefetchSimple() {
8521e0a453dff8d7bb7a966d006541454c770d1ed05Chris Craik        LinearLayoutManager llm = new LinearLayoutManager(getContext());
853d6696c2abea2771acd000c2269cf9113acc6c0a9Chris Craik        assertEquals(2, llm.getInitialPrefetchItemCount());
8541e0a453dff8d7bb7a966d006541454c770d1ed05Chris Craik
8551e0a453dff8d7bb7a966d006541454c770d1ed05Chris Craik        mRecyclerView.setLayoutManager(llm);
8561e0a453dff8d7bb7a966d006541454c770d1ed05Chris Craik        mRecyclerView.setAdapter(new OuterAdapter());
8571e0a453dff8d7bb7a966d006541454c770d1ed05Chris Craik
8581e0a453dff8d7bb7a966d006541454c770d1ed05Chris Craik        layout(200, 200);
8591e0a453dff8d7bb7a966d006541454c770d1ed05Chris Craik        mRecyclerView.mPrefetchRegistry.setPrefetchVector(0, 1);
8601e0a453dff8d7bb7a966d006541454c770d1ed05Chris Craik
8611e0a453dff8d7bb7a966d006541454c770d1ed05Chris Craik        // prefetch 2 (default)
8621e0a453dff8d7bb7a966d006541454c770d1ed05Chris Craik        mRecyclerView.mGapWorker.prefetch(RecyclerView.FOREVER_NS);
8631e0a453dff8d7bb7a966d006541454c770d1ed05Chris Craik        RecyclerView.ViewHolder holder = CacheUtils.peekAtCachedViewForPosition(mRecyclerView, 2);
8641e0a453dff8d7bb7a966d006541454c770d1ed05Chris Craik        assertNotNull(holder);
8651e0a453dff8d7bb7a966d006541454c770d1ed05Chris Craik        assertNotNull(holder.mNestedRecyclerView);
8668123166eaf758eacc6ba5854c9b29d99f21388d6Chris Craik        RecyclerView innerView = holder.mNestedRecyclerView.get();
8678123166eaf758eacc6ba5854c9b29d99f21388d6Chris Craik        CacheUtils.verifyCacheContainsPrefetchedPositions(innerView, 0, 1);
8681e0a453dff8d7bb7a966d006541454c770d1ed05Chris Craik
8691e0a453dff8d7bb7a966d006541454c770d1ed05Chris Craik        // prefetch 4
8708123166eaf758eacc6ba5854c9b29d99f21388d6Chris Craik        ((LinearLayoutManager) innerView.getLayoutManager())
8711e0a453dff8d7bb7a966d006541454c770d1ed05Chris Craik                .setInitialPrefetchItemCount(4);
8721e0a453dff8d7bb7a966d006541454c770d1ed05Chris Craik        mRecyclerView.mGapWorker.prefetch(RecyclerView.FOREVER_NS);
8738123166eaf758eacc6ba5854c9b29d99f21388d6Chris Craik        CacheUtils.verifyCacheContainsPrefetchedPositions(innerView, 0, 1, 2, 3);
8741e0a453dff8d7bb7a966d006541454c770d1ed05Chris Craik    }
8750ceae57ee14da826c31a66c67e70f7f17108d5caChris Craik
8760ceae57ee14da826c31a66c67e70f7f17108d5caChris Craik    @Test
8770fd482dee2c8c2aa61e59d6501e46864e16fcb04Dake Gu    public void nestedPrefetchNotClearInnerStructureChangeFlag() {
8780fd482dee2c8c2aa61e59d6501e46864e16fcb04Dake Gu        LinearLayoutManager llm = new LinearLayoutManager(getContext());
8790fd482dee2c8c2aa61e59d6501e46864e16fcb04Dake Gu        assertEquals(2, llm.getInitialPrefetchItemCount());
8800fd482dee2c8c2aa61e59d6501e46864e16fcb04Dake Gu
8810fd482dee2c8c2aa61e59d6501e46864e16fcb04Dake Gu        mRecyclerView.setLayoutManager(llm);
8820fd482dee2c8c2aa61e59d6501e46864e16fcb04Dake Gu        mRecyclerView.setAdapter(new OuterAdapter());
8830fd482dee2c8c2aa61e59d6501e46864e16fcb04Dake Gu
8840fd482dee2c8c2aa61e59d6501e46864e16fcb04Dake Gu        layout(200, 200);
8850fd482dee2c8c2aa61e59d6501e46864e16fcb04Dake Gu        mRecyclerView.mPrefetchRegistry.setPrefetchVector(0, 1);
8860fd482dee2c8c2aa61e59d6501e46864e16fcb04Dake Gu
8870fd482dee2c8c2aa61e59d6501e46864e16fcb04Dake Gu        // prefetch 2 (default)
8880fd482dee2c8c2aa61e59d6501e46864e16fcb04Dake Gu        mRecyclerView.mGapWorker.prefetch(RecyclerView.FOREVER_NS);
8890fd482dee2c8c2aa61e59d6501e46864e16fcb04Dake Gu        RecyclerView.ViewHolder holder = CacheUtils.peekAtCachedViewForPosition(mRecyclerView, 2);
8900fd482dee2c8c2aa61e59d6501e46864e16fcb04Dake Gu        assertNotNull(holder);
8910fd482dee2c8c2aa61e59d6501e46864e16fcb04Dake Gu        assertNotNull(holder.mNestedRecyclerView);
8920fd482dee2c8c2aa61e59d6501e46864e16fcb04Dake Gu        RecyclerView innerView = holder.mNestedRecyclerView.get();
8930fd482dee2c8c2aa61e59d6501e46864e16fcb04Dake Gu        RecyclerView.Adapter innerAdapter = innerView.getAdapter();
8940fd482dee2c8c2aa61e59d6501e46864e16fcb04Dake Gu        CacheUtils.verifyCacheContainsPrefetchedPositions(innerView, 0, 1);
8950fd482dee2c8c2aa61e59d6501e46864e16fcb04Dake Gu        // mStructureChanged is initially true before first layout pass.
8960fd482dee2c8c2aa61e59d6501e46864e16fcb04Dake Gu        assertTrue(innerView.mState.mStructureChanged);
8970fd482dee2c8c2aa61e59d6501e46864e16fcb04Dake Gu        assertTrue(innerView.hasPendingAdapterUpdates());
8980fd482dee2c8c2aa61e59d6501e46864e16fcb04Dake Gu
8990fd482dee2c8c2aa61e59d6501e46864e16fcb04Dake Gu        // layout position 2 and clear mStructureChanged
9000fd482dee2c8c2aa61e59d6501e46864e16fcb04Dake Gu        mRecyclerView.scrollToPosition(2);
9010fd482dee2c8c2aa61e59d6501e46864e16fcb04Dake Gu        layout(200, 200);
9020fd482dee2c8c2aa61e59d6501e46864e16fcb04Dake Gu        mRecyclerView.scrollToPosition(0);
9030fd482dee2c8c2aa61e59d6501e46864e16fcb04Dake Gu        layout(200, 200);
9040fd482dee2c8c2aa61e59d6501e46864e16fcb04Dake Gu        assertFalse(innerView.mState.mStructureChanged);
9050fd482dee2c8c2aa61e59d6501e46864e16fcb04Dake Gu        assertFalse(innerView.hasPendingAdapterUpdates());
9060fd482dee2c8c2aa61e59d6501e46864e16fcb04Dake Gu
9070fd482dee2c8c2aa61e59d6501e46864e16fcb04Dake Gu        // notify change on the cached innerView.
9080fd482dee2c8c2aa61e59d6501e46864e16fcb04Dake Gu        innerAdapter.notifyDataSetChanged();
9090fd482dee2c8c2aa61e59d6501e46864e16fcb04Dake Gu        assertTrue(innerView.mState.mStructureChanged);
9100fd482dee2c8c2aa61e59d6501e46864e16fcb04Dake Gu        assertTrue(innerView.hasPendingAdapterUpdates());
9110fd482dee2c8c2aa61e59d6501e46864e16fcb04Dake Gu
9120fd482dee2c8c2aa61e59d6501e46864e16fcb04Dake Gu        // prefetch again
9130fd482dee2c8c2aa61e59d6501e46864e16fcb04Dake Gu        mRecyclerView.mPrefetchRegistry.setPrefetchVector(0, 1);
9140fd482dee2c8c2aa61e59d6501e46864e16fcb04Dake Gu        ((LinearLayoutManager) innerView.getLayoutManager())
9150fd482dee2c8c2aa61e59d6501e46864e16fcb04Dake Gu                .setInitialPrefetchItemCount(2);
9160fd482dee2c8c2aa61e59d6501e46864e16fcb04Dake Gu        mRecyclerView.mGapWorker.prefetch(RecyclerView.FOREVER_NS);
9170fd482dee2c8c2aa61e59d6501e46864e16fcb04Dake Gu        CacheUtils.verifyCacheContainsPrefetchedPositions(innerView, 0, 1);
9180fd482dee2c8c2aa61e59d6501e46864e16fcb04Dake Gu
9190fd482dee2c8c2aa61e59d6501e46864e16fcb04Dake Gu        // The re-prefetch is not necessary get the same inner view but we will get same Adapter
9200fd482dee2c8c2aa61e59d6501e46864e16fcb04Dake Gu        holder = CacheUtils.peekAtCachedViewForPosition(mRecyclerView, 2);
9210fd482dee2c8c2aa61e59d6501e46864e16fcb04Dake Gu        innerView = holder.mNestedRecyclerView.get();
9220fd482dee2c8c2aa61e59d6501e46864e16fcb04Dake Gu        assertSame(innerAdapter, innerView.getAdapter());
9230fd482dee2c8c2aa61e59d6501e46864e16fcb04Dake Gu        // prefetch shouldn't clear the mStructureChanged flag
9240fd482dee2c8c2aa61e59d6501e46864e16fcb04Dake Gu        assertTrue(innerView.mState.mStructureChanged);
9250fd482dee2c8c2aa61e59d6501e46864e16fcb04Dake Gu        assertTrue(innerView.hasPendingAdapterUpdates());
9260fd482dee2c8c2aa61e59d6501e46864e16fcb04Dake Gu    }
9270fd482dee2c8c2aa61e59d6501e46864e16fcb04Dake Gu
9280fd482dee2c8c2aa61e59d6501e46864e16fcb04Dake Gu    @Test
9290ceae57ee14da826c31a66c67e70f7f17108d5caChris Craik    public void nestedPrefetchReverseInner() {
9300ceae57ee14da826c31a66c67e70f7f17108d5caChris Craik        mRecyclerView.setLayoutManager(new LinearLayoutManager(getContext()));
9310ceae57ee14da826c31a66c67e70f7f17108d5caChris Craik        mRecyclerView.setAdapter(new OuterAdapter(/* reverseInner = */ true));
9320ceae57ee14da826c31a66c67e70f7f17108d5caChris Craik
9330ceae57ee14da826c31a66c67e70f7f17108d5caChris Craik        layout(200, 200);
9340ceae57ee14da826c31a66c67e70f7f17108d5caChris Craik        mRecyclerView.mPrefetchRegistry.setPrefetchVector(0, 1);
9350ceae57ee14da826c31a66c67e70f7f17108d5caChris Craik
9360ceae57ee14da826c31a66c67e70f7f17108d5caChris Craik        mRecyclerView.mGapWorker.prefetch(RecyclerView.FOREVER_NS);
9370ceae57ee14da826c31a66c67e70f7f17108d5caChris Craik        RecyclerView.ViewHolder holder = CacheUtils.peekAtCachedViewForPosition(mRecyclerView, 2);
9380ceae57ee14da826c31a66c67e70f7f17108d5caChris Craik
9390ceae57ee14da826c31a66c67e70f7f17108d5caChris Craik        // anchor from right side, should see last two positions
9408123166eaf758eacc6ba5854c9b29d99f21388d6Chris Craik        CacheUtils.verifyCacheContainsPrefetchedPositions(holder.mNestedRecyclerView.get(), 18, 19);
9410ceae57ee14da826c31a66c67e70f7f17108d5caChris Craik    }
9420ceae57ee14da826c31a66c67e70f7f17108d5caChris Craik
9430ceae57ee14da826c31a66c67e70f7f17108d5caChris Craik    @Test
9440ceae57ee14da826c31a66c67e70f7f17108d5caChris Craik    public void nestedPrefetchOffset() {
9450ceae57ee14da826c31a66c67e70f7f17108d5caChris Craik        mRecyclerView.setLayoutManager(new LinearLayoutManager(getContext()));
9460ceae57ee14da826c31a66c67e70f7f17108d5caChris Craik        mRecyclerView.setAdapter(new OuterAdapter());
9470ceae57ee14da826c31a66c67e70f7f17108d5caChris Craik
9480ceae57ee14da826c31a66c67e70f7f17108d5caChris Craik        layout(200, 200);
9490ceae57ee14da826c31a66c67e70f7f17108d5caChris Craik
9500ceae57ee14da826c31a66c67e70f7f17108d5caChris Craik        // Scroll top row by 5.5 items, verify positions 5, 6, 7 showing
9510ceae57ee14da826c31a66c67e70f7f17108d5caChris Craik        RecyclerView inner = (RecyclerView) mRecyclerView.getChildAt(0);
9520ceae57ee14da826c31a66c67e70f7f17108d5caChris Craik        inner.scrollBy(550, 0);
9530ceae57ee14da826c31a66c67e70f7f17108d5caChris Craik        assertEquals(5, RecyclerView.getChildViewHolderInt(inner.getChildAt(0)).mPosition);
9540ceae57ee14da826c31a66c67e70f7f17108d5caChris Craik        assertEquals(6, RecyclerView.getChildViewHolderInt(inner.getChildAt(1)).mPosition);
9550ceae57ee14da826c31a66c67e70f7f17108d5caChris Craik        assertEquals(7, RecyclerView.getChildViewHolderInt(inner.getChildAt(2)).mPosition);
9560ceae57ee14da826c31a66c67e70f7f17108d5caChris Craik
9570ceae57ee14da826c31a66c67e70f7f17108d5caChris Craik        // scroll down 4 rows, up 3 so row 0 is adjacent but uncached
9580ceae57ee14da826c31a66c67e70f7f17108d5caChris Craik        mRecyclerView.scrollBy(0, 400);
9590ceae57ee14da826c31a66c67e70f7f17108d5caChris Craik        mRecyclerView.scrollBy(0, -300);
9600ceae57ee14da826c31a66c67e70f7f17108d5caChris Craik
9610ceae57ee14da826c31a66c67e70f7f17108d5caChris Craik        // top row no longer present
9620ceae57ee14da826c31a66c67e70f7f17108d5caChris Craik        CacheUtils.verifyCacheDoesNotContainPositions(mRecyclerView, 0);
9630ceae57ee14da826c31a66c67e70f7f17108d5caChris Craik
9640ceae57ee14da826c31a66c67e70f7f17108d5caChris Craik        // prefetch upward, and validate that we've gotten the top row with correct offsets
9650ceae57ee14da826c31a66c67e70f7f17108d5caChris Craik        mRecyclerView.mPrefetchRegistry.setPrefetchVector(0, -1);
9660ceae57ee14da826c31a66c67e70f7f17108d5caChris Craik        mRecyclerView.mGapWorker.prefetch(RecyclerView.FOREVER_NS);
9670ceae57ee14da826c31a66c67e70f7f17108d5caChris Craik        inner = (RecyclerView) CacheUtils.peekAtCachedViewForPosition(mRecyclerView, 0).itemView;
9680ceae57ee14da826c31a66c67e70f7f17108d5caChris Craik        CacheUtils.verifyCacheContainsPrefetchedPositions(inner, 5, 6);
9690ceae57ee14da826c31a66c67e70f7f17108d5caChris Craik
9700ceae57ee14da826c31a66c67e70f7f17108d5caChris Craik        // prefetch 4
9710ceae57ee14da826c31a66c67e70f7f17108d5caChris Craik        ((LinearLayoutManager) inner.getLayoutManager()).setInitialPrefetchItemCount(4);
9720ceae57ee14da826c31a66c67e70f7f17108d5caChris Craik        mRecyclerView.mGapWorker.prefetch(RecyclerView.FOREVER_NS);
9730ceae57ee14da826c31a66c67e70f7f17108d5caChris Craik        CacheUtils.verifyCacheContainsPrefetchedPositions(inner, 5, 6, 7, 8);
9740ceae57ee14da826c31a66c67e70f7f17108d5caChris Craik    }
975f2f73b7cc9bed0e7204ba43796bb0112cda27cf1Chris Craik
976f2f73b7cc9bed0e7204ba43796bb0112cda27cf1Chris Craik    @Test
977f2f73b7cc9bed0e7204ba43796bb0112cda27cf1Chris Craik    public void nestedPrefetchNotReset() {
978f2f73b7cc9bed0e7204ba43796bb0112cda27cf1Chris Craik        mRecyclerView.setLayoutManager(new LinearLayoutManager(getContext()));
979f2f73b7cc9bed0e7204ba43796bb0112cda27cf1Chris Craik        OuterAdapter outerAdapter = new OuterAdapter();
980f2f73b7cc9bed0e7204ba43796bb0112cda27cf1Chris Craik        mRecyclerView.setAdapter(outerAdapter);
981f2f73b7cc9bed0e7204ba43796bb0112cda27cf1Chris Craik
982f2f73b7cc9bed0e7204ba43796bb0112cda27cf1Chris Craik        layout(200, 200);
983f2f73b7cc9bed0e7204ba43796bb0112cda27cf1Chris Craik
984f2f73b7cc9bed0e7204ba43796bb0112cda27cf1Chris Craik        mRecyclerView.mPrefetchRegistry.setPrefetchVector(0, 1);
985f2f73b7cc9bed0e7204ba43796bb0112cda27cf1Chris Craik
986f2f73b7cc9bed0e7204ba43796bb0112cda27cf1Chris Craik        // prefetch row 2, items 0 & 1
987f2f73b7cc9bed0e7204ba43796bb0112cda27cf1Chris Craik        assertEquals(0, outerAdapter.mAdapters.get(2).mItemsBound);
988f2f73b7cc9bed0e7204ba43796bb0112cda27cf1Chris Craik        mRecyclerView.mGapWorker.prefetch(RecyclerView.FOREVER_NS);
989f2f73b7cc9bed0e7204ba43796bb0112cda27cf1Chris Craik        RecyclerView.ViewHolder holder = CacheUtils.peekAtCachedViewForPosition(mRecyclerView, 2);
9908123166eaf758eacc6ba5854c9b29d99f21388d6Chris Craik        RecyclerView innerRecyclerView = holder.mNestedRecyclerView.get();
991f2f73b7cc9bed0e7204ba43796bb0112cda27cf1Chris Craik
992f2f73b7cc9bed0e7204ba43796bb0112cda27cf1Chris Craik        assertNotNull(innerRecyclerView);
993f2f73b7cc9bed0e7204ba43796bb0112cda27cf1Chris Craik        CacheUtils.verifyCacheContainsPrefetchedPositions(innerRecyclerView, 0, 1);
994f2f73b7cc9bed0e7204ba43796bb0112cda27cf1Chris Craik        assertEquals(2, outerAdapter.mAdapters.get(2).mItemsBound);
995f2f73b7cc9bed0e7204ba43796bb0112cda27cf1Chris Craik
996f2f73b7cc9bed0e7204ba43796bb0112cda27cf1Chris Craik        // new row comes on, triggers layout...
997f2f73b7cc9bed0e7204ba43796bb0112cda27cf1Chris Craik        mRecyclerView.scrollBy(0, 50);
998f2f73b7cc9bed0e7204ba43796bb0112cda27cf1Chris Craik
999f2f73b7cc9bed0e7204ba43796bb0112cda27cf1Chris Craik        // ... which shouldn't require new items to be bound,
1000f2f73b7cc9bed0e7204ba43796bb0112cda27cf1Chris Craik        // as prefetch has already done that work
1001f2f73b7cc9bed0e7204ba43796bb0112cda27cf1Chris Craik        assertEquals(2, outerAdapter.mAdapters.get(2).mItemsBound);
1002f2f73b7cc9bed0e7204ba43796bb0112cda27cf1Chris Craik    }
10030fab45107f043a4856aad2e1fd785b396898616eChris Craik
10040fab45107f043a4856aad2e1fd785b396898616eChris Craik    static void validateRvChildrenValid(RecyclerView recyclerView, int childCount) {
10050fab45107f043a4856aad2e1fd785b396898616eChris Craik        ChildHelper childHelper = recyclerView.mChildHelper;
10060fab45107f043a4856aad2e1fd785b396898616eChris Craik
10070fab45107f043a4856aad2e1fd785b396898616eChris Craik        assertEquals(childCount, childHelper.getUnfilteredChildCount());
10080fab45107f043a4856aad2e1fd785b396898616eChris Craik        for (int i = 0; i < childHelper.getUnfilteredChildCount(); i++) {
10090fab45107f043a4856aad2e1fd785b396898616eChris Craik            assertFalse(recyclerView.getChildViewHolder(
10100fab45107f043a4856aad2e1fd785b396898616eChris Craik                    childHelper.getUnfilteredChildAt(i)).isInvalid());
10110fab45107f043a4856aad2e1fd785b396898616eChris Craik        }
10120fab45107f043a4856aad2e1fd785b396898616eChris Craik    }
10130fab45107f043a4856aad2e1fd785b396898616eChris Craik
10140fab45107f043a4856aad2e1fd785b396898616eChris Craik    @Test
10150fab45107f043a4856aad2e1fd785b396898616eChris Craik    public void nestedPrefetchCacheNotTouched() {
10160fab45107f043a4856aad2e1fd785b396898616eChris Craik        mRecyclerView.setLayoutManager(new LinearLayoutManager(getContext()));
10170fab45107f043a4856aad2e1fd785b396898616eChris Craik        OuterAdapter outerAdapter = new OuterAdapter();
10180fab45107f043a4856aad2e1fd785b396898616eChris Craik        mRecyclerView.setAdapter(outerAdapter);
10190fab45107f043a4856aad2e1fd785b396898616eChris Craik
10200fab45107f043a4856aad2e1fd785b396898616eChris Craik        layout(200, 200);
10210fab45107f043a4856aad2e1fd785b396898616eChris Craik        mRecyclerView.scrollBy(0, 100);
10220fab45107f043a4856aad2e1fd785b396898616eChris Craik
10230fab45107f043a4856aad2e1fd785b396898616eChris Craik        // item 0 is cached
10240fab45107f043a4856aad2e1fd785b396898616eChris Craik        assertEquals(2, outerAdapter.mAdapters.get(0).mItemsBound);
10250fab45107f043a4856aad2e1fd785b396898616eChris Craik        RecyclerView.ViewHolder holder = CacheUtils.peekAtCachedViewForPosition(mRecyclerView, 0);
10268123166eaf758eacc6ba5854c9b29d99f21388d6Chris Craik        validateRvChildrenValid(holder.mNestedRecyclerView.get(), 2);
10270fab45107f043a4856aad2e1fd785b396898616eChris Craik
10280fab45107f043a4856aad2e1fd785b396898616eChris Craik        // try and prefetch it
10290fab45107f043a4856aad2e1fd785b396898616eChris Craik        mRecyclerView.mPrefetchRegistry.setPrefetchVector(0, -1);
10300fab45107f043a4856aad2e1fd785b396898616eChris Craik        mRecyclerView.mGapWorker.prefetch(RecyclerView.FOREVER_NS);
10310fab45107f043a4856aad2e1fd785b396898616eChris Craik
10320fab45107f043a4856aad2e1fd785b396898616eChris Craik        // make sure cache's inner items aren't rebound unnecessarily
10330fab45107f043a4856aad2e1fd785b396898616eChris Craik        assertEquals(2, outerAdapter.mAdapters.get(0).mItemsBound);
10348123166eaf758eacc6ba5854c9b29d99f21388d6Chris Craik        validateRvChildrenValid(holder.mNestedRecyclerView.get(), 2);
10358123166eaf758eacc6ba5854c9b29d99f21388d6Chris Craik    }
10368123166eaf758eacc6ba5854c9b29d99f21388d6Chris Craik
10378123166eaf758eacc6ba5854c9b29d99f21388d6Chris Craik    @Test
10388123166eaf758eacc6ba5854c9b29d99f21388d6Chris Craik    public void nestedRemoveAnimatingView() {
10398123166eaf758eacc6ba5854c9b29d99f21388d6Chris Craik        mRecyclerView.setLayoutManager(new LinearLayoutManager(getContext()));
10408123166eaf758eacc6ba5854c9b29d99f21388d6Chris Craik        OuterAdapter outerAdapter = new OuterAdapter(false, 1);
10418123166eaf758eacc6ba5854c9b29d99f21388d6Chris Craik        mRecyclerView.setAdapter(outerAdapter);
10428123166eaf758eacc6ba5854c9b29d99f21388d6Chris Craik        mRecyclerView.getItemAnimator().setAddDuration(TimeUnit.MILLISECONDS.toNanos(30));
10438123166eaf758eacc6ba5854c9b29d99f21388d6Chris Craik
10448123166eaf758eacc6ba5854c9b29d99f21388d6Chris Craik        layout(200, 200);
10458123166eaf758eacc6ba5854c9b29d99f21388d6Chris Craik
10468123166eaf758eacc6ba5854c9b29d99f21388d6Chris Craik        // Insert 3 items - only first one in viewport, so only it animates
10478123166eaf758eacc6ba5854c9b29d99f21388d6Chris Craik        for (int i = 0; i < 3; i++) {
10488123166eaf758eacc6ba5854c9b29d99f21388d6Chris Craik            outerAdapter.addItem();
10498123166eaf758eacc6ba5854c9b29d99f21388d6Chris Craik        }
10508123166eaf758eacc6ba5854c9b29d99f21388d6Chris Craik        layout(200, 200); // layout again to kick off animation
10518123166eaf758eacc6ba5854c9b29d99f21388d6Chris Craik
10528123166eaf758eacc6ba5854c9b29d99f21388d6Chris Craik
10538123166eaf758eacc6ba5854c9b29d99f21388d6Chris Craik        // item 1 is animating, so scroll it out of viewport
10548123166eaf758eacc6ba5854c9b29d99f21388d6Chris Craik        mRecyclerView.scrollBy(0, 200);
10558123166eaf758eacc6ba5854c9b29d99f21388d6Chris Craik
10568123166eaf758eacc6ba5854c9b29d99f21388d6Chris Craik        // 2 items attached, 1 cached (pos 0), but item animating pos 1 not accounted for...
10578123166eaf758eacc6ba5854c9b29d99f21388d6Chris Craik        assertEquals(2, mRecyclerView.mChildHelper.getUnfilteredChildCount());
10588123166eaf758eacc6ba5854c9b29d99f21388d6Chris Craik        assertEquals(1, mRecycler.mCachedViews.size());
10598123166eaf758eacc6ba5854c9b29d99f21388d6Chris Craik        CacheUtils.verifyCacheContainsPositions(mRecyclerView, 0);
10608123166eaf758eacc6ba5854c9b29d99f21388d6Chris Craik        assertEquals(0, mRecyclerView.getRecycledViewPool().getRecycledViewCount(0));
10618123166eaf758eacc6ba5854c9b29d99f21388d6Chris Craik
10628123166eaf758eacc6ba5854c9b29d99f21388d6Chris Craik        // until animation ends
10638123166eaf758eacc6ba5854c9b29d99f21388d6Chris Craik        mRecyclerView.getItemAnimator().endAnimations();
10648123166eaf758eacc6ba5854c9b29d99f21388d6Chris Craik        assertEquals(2, mRecyclerView.mChildHelper.getUnfilteredChildCount());
10658123166eaf758eacc6ba5854c9b29d99f21388d6Chris Craik        assertEquals(2, mRecycler.mCachedViews.size());
10668123166eaf758eacc6ba5854c9b29d99f21388d6Chris Craik        CacheUtils.verifyCacheContainsPositions(mRecyclerView, 0, 1);
10678123166eaf758eacc6ba5854c9b29d99f21388d6Chris Craik        assertEquals(0, mRecyclerView.getRecycledViewPool().getRecycledViewCount(0));
10688123166eaf758eacc6ba5854c9b29d99f21388d6Chris Craik
10698123166eaf758eacc6ba5854c9b29d99f21388d6Chris Craik        for (RecyclerView.ViewHolder viewHolder : mRecycler.mCachedViews) {
10708123166eaf758eacc6ba5854c9b29d99f21388d6Chris Craik            assertNotNull(viewHolder.mNestedRecyclerView);
10718123166eaf758eacc6ba5854c9b29d99f21388d6Chris Craik        }
10720fab45107f043a4856aad2e1fd785b396898616eChris Craik    }
10730fab45107f043a4856aad2e1fd785b396898616eChris Craik
10749ad077f4ba751b392e6f4454b09c2305cdc22b0dChris Craik    @Test
10759ad077f4ba751b392e6f4454b09c2305cdc22b0dChris Craik    public void nestedExpandCacheCorrectly() {
10769ad077f4ba751b392e6f4454b09c2305cdc22b0dChris Craik        final int DEFAULT_CACHE_SIZE = RecyclerView.Recycler.DEFAULT_CACHE_SIZE;
10779ad077f4ba751b392e6f4454b09c2305cdc22b0dChris Craik
10789ad077f4ba751b392e6f4454b09c2305cdc22b0dChris Craik        mRecyclerView.setLayoutManager(new LinearLayoutManager(getContext()));
10799ad077f4ba751b392e6f4454b09c2305cdc22b0dChris Craik        OuterAdapter outerAdapter = new OuterAdapter();
10809ad077f4ba751b392e6f4454b09c2305cdc22b0dChris Craik        mRecyclerView.setAdapter(outerAdapter);
10819ad077f4ba751b392e6f4454b09c2305cdc22b0dChris Craik
10829ad077f4ba751b392e6f4454b09c2305cdc22b0dChris Craik        layout(200, 200);
10839ad077f4ba751b392e6f4454b09c2305cdc22b0dChris Craik
10849ad077f4ba751b392e6f4454b09c2305cdc22b0dChris Craik        mRecyclerView.mPrefetchRegistry.setPrefetchVector(0, 1);
10859ad077f4ba751b392e6f4454b09c2305cdc22b0dChris Craik        mRecyclerView.mGapWorker.prefetch(RecyclerView.FOREVER_NS);
10869ad077f4ba751b392e6f4454b09c2305cdc22b0dChris Craik
10879ad077f4ba751b392e6f4454b09c2305cdc22b0dChris Craik        // after initial prefetch, view cache max expanded by number of inner items prefetched (2)
10889ad077f4ba751b392e6f4454b09c2305cdc22b0dChris Craik        RecyclerView.ViewHolder holder = CacheUtils.peekAtCachedViewForPosition(mRecyclerView, 2);
10899ad077f4ba751b392e6f4454b09c2305cdc22b0dChris Craik        RecyclerView innerView = holder.mNestedRecyclerView.get();
10909ad077f4ba751b392e6f4454b09c2305cdc22b0dChris Craik        assertTrue(innerView.getLayoutManager().mPrefetchMaxObservedInInitialPrefetch);
10919ad077f4ba751b392e6f4454b09c2305cdc22b0dChris Craik        assertEquals(2, innerView.getLayoutManager().mPrefetchMaxCountObserved);
10929ad077f4ba751b392e6f4454b09c2305cdc22b0dChris Craik        assertEquals(2 + DEFAULT_CACHE_SIZE, innerView.mRecycler.mViewCacheMax);
10939ad077f4ba751b392e6f4454b09c2305cdc22b0dChris Craik
10949ad077f4ba751b392e6f4454b09c2305cdc22b0dChris Craik        try {
10959ad077f4ba751b392e6f4454b09c2305cdc22b0dChris Craik            // Note: As a hack, we not only must manually dispatch attachToWindow(), but we
10969ad077f4ba751b392e6f4454b09c2305cdc22b0dChris Craik            // also have to be careful to call innerView.mGapWorker below. mRecyclerView.mGapWorker
10979ad077f4ba751b392e6f4454b09c2305cdc22b0dChris Craik            // is registered to the wrong thread, since @setup is called on a different thread
10989ad077f4ba751b392e6f4454b09c2305cdc22b0dChris Craik            // from @Test. Assert this, so this test can be fixed when setup == test thread.
10999ad077f4ba751b392e6f4454b09c2305cdc22b0dChris Craik            assertEquals(1, mRecyclerView.mGapWorker.mRecyclerViews.size());
11009ad077f4ba751b392e6f4454b09c2305cdc22b0dChris Craik            assertFalse(innerView.isAttachedToWindow());
11019ad077f4ba751b392e6f4454b09c2305cdc22b0dChris Craik            innerView.onAttachedToWindow();
11029ad077f4ba751b392e6f4454b09c2305cdc22b0dChris Craik
11039ad077f4ba751b392e6f4454b09c2305cdc22b0dChris Craik            // bring prefetch view into viewport, at which point it shouldn't have cache expanded...
11049ad077f4ba751b392e6f4454b09c2305cdc22b0dChris Craik            mRecyclerView.scrollBy(0, 100);
11059ad077f4ba751b392e6f4454b09c2305cdc22b0dChris Craik            assertFalse(innerView.getLayoutManager().mPrefetchMaxObservedInInitialPrefetch);
11069ad077f4ba751b392e6f4454b09c2305cdc22b0dChris Craik            assertEquals(0, innerView.getLayoutManager().mPrefetchMaxCountObserved);
11079ad077f4ba751b392e6f4454b09c2305cdc22b0dChris Craik            assertEquals(DEFAULT_CACHE_SIZE, innerView.mRecycler.mViewCacheMax);
11089ad077f4ba751b392e6f4454b09c2305cdc22b0dChris Craik
11099ad077f4ba751b392e6f4454b09c2305cdc22b0dChris Craik            // until a valid horizontal prefetch caches an item, and expands view count by one
11109ad077f4ba751b392e6f4454b09c2305cdc22b0dChris Craik            innerView.mPrefetchRegistry.setPrefetchVector(1, 0);
11119ad077f4ba751b392e6f4454b09c2305cdc22b0dChris Craik            innerView.mGapWorker.prefetch(RecyclerView.FOREVER_NS); // NB: must be innerView.mGapWorker
11129ad077f4ba751b392e6f4454b09c2305cdc22b0dChris Craik            assertFalse(innerView.getLayoutManager().mPrefetchMaxObservedInInitialPrefetch);
11139ad077f4ba751b392e6f4454b09c2305cdc22b0dChris Craik            assertEquals(1, innerView.getLayoutManager().mPrefetchMaxCountObserved);
11149ad077f4ba751b392e6f4454b09c2305cdc22b0dChris Craik            assertEquals(1 + DEFAULT_CACHE_SIZE, innerView.mRecycler.mViewCacheMax);
11159ad077f4ba751b392e6f4454b09c2305cdc22b0dChris Craik        } finally {
11169ad077f4ba751b392e6f4454b09c2305cdc22b0dChris Craik            if (innerView.isAttachedToWindow()) {
11179ad077f4ba751b392e6f4454b09c2305cdc22b0dChris Craik                innerView.onDetachedFromWindow();
11189ad077f4ba751b392e6f4454b09c2305cdc22b0dChris Craik            }
11199ad077f4ba751b392e6f4454b09c2305cdc22b0dChris Craik        }
11209ad077f4ba751b392e6f4454b09c2305cdc22b0dChris Craik    }
11219ad077f4ba751b392e6f4454b09c2305cdc22b0dChris Craik
11220fab45107f043a4856aad2e1fd785b396898616eChris Craik    /**
11230fab45107f043a4856aad2e1fd785b396898616eChris Craik     * Similar to OuterAdapter above, but uses notifyDataSetChanged() instead of set/swapAdapter
11240fab45107f043a4856aad2e1fd785b396898616eChris Craik     * to update data for the inner RecyclerViews when containing ViewHolder is bound.
11250fab45107f043a4856aad2e1fd785b396898616eChris Craik     */
11260fab45107f043a4856aad2e1fd785b396898616eChris Craik    class OuterNotifyAdapter extends RecyclerView.Adapter<OuterNotifyAdapter.ViewHolder> {
11270fab45107f043a4856aad2e1fd785b396898616eChris Craik        private static final int OUTER_ITEM_COUNT = 10;
11280fab45107f043a4856aad2e1fd785b396898616eChris Craik
11290fab45107f043a4856aad2e1fd785b396898616eChris Craik        private boolean mReverseInner;
11300fab45107f043a4856aad2e1fd785b396898616eChris Craik
11310fab45107f043a4856aad2e1fd785b396898616eChris Craik        class ViewHolder extends RecyclerView.ViewHolder {
11320fab45107f043a4856aad2e1fd785b396898616eChris Craik            private final RecyclerView mRecyclerView;
11330fab45107f043a4856aad2e1fd785b396898616eChris Craik            private final InnerAdapter mAdapter;
11340fab45107f043a4856aad2e1fd785b396898616eChris Craik            ViewHolder(RecyclerView itemView) {
11350fab45107f043a4856aad2e1fd785b396898616eChris Craik                super(itemView);
11360fab45107f043a4856aad2e1fd785b396898616eChris Craik                mRecyclerView = itemView;
11370fab45107f043a4856aad2e1fd785b396898616eChris Craik                mAdapter = new InnerAdapter();
11380fab45107f043a4856aad2e1fd785b396898616eChris Craik                mRecyclerView.setAdapter(mAdapter);
11390fab45107f043a4856aad2e1fd785b396898616eChris Craik            }
11400fab45107f043a4856aad2e1fd785b396898616eChris Craik        }
11410fab45107f043a4856aad2e1fd785b396898616eChris Craik
11420fab45107f043a4856aad2e1fd785b396898616eChris Craik        ArrayList<Parcelable> mSavedStates = new ArrayList<>();
11430fab45107f043a4856aad2e1fd785b396898616eChris Craik        RecyclerView.RecycledViewPool mSharedPool = new RecyclerView.RecycledViewPool();
11440fab45107f043a4856aad2e1fd785b396898616eChris Craik
11450fab45107f043a4856aad2e1fd785b396898616eChris Craik        OuterNotifyAdapter() {
11460fab45107f043a4856aad2e1fd785b396898616eChris Craik            this(false);
11470fab45107f043a4856aad2e1fd785b396898616eChris Craik        }
11480fab45107f043a4856aad2e1fd785b396898616eChris Craik
11490fab45107f043a4856aad2e1fd785b396898616eChris Craik        OuterNotifyAdapter(boolean reverseInner) {
11500fab45107f043a4856aad2e1fd785b396898616eChris Craik            mReverseInner = reverseInner;
11510fab45107f043a4856aad2e1fd785b396898616eChris Craik            for (int i = 0; i <= OUTER_ITEM_COUNT; i++) {
11520fab45107f043a4856aad2e1fd785b396898616eChris Craik                mSavedStates.add(null);
11530fab45107f043a4856aad2e1fd785b396898616eChris Craik            }
11540fab45107f043a4856aad2e1fd785b396898616eChris Craik        }
11550fab45107f043a4856aad2e1fd785b396898616eChris Craik
11560fab45107f043a4856aad2e1fd785b396898616eChris Craik        @Override
11570fab45107f043a4856aad2e1fd785b396898616eChris Craik        public ViewHolder onCreateViewHolder(ViewGroup parent, int viewType) {
11580fab45107f043a4856aad2e1fd785b396898616eChris Craik            mRecyclerView.registerTimePassingMs(5);
11590fab45107f043a4856aad2e1fd785b396898616eChris Craik            RecyclerView rv = new RecyclerView(parent.getContext());
11600fab45107f043a4856aad2e1fd785b396898616eChris Craik            rv.setLayoutManager(new LinearLayoutManager(parent.getContext(),
11610fab45107f043a4856aad2e1fd785b396898616eChris Craik                    LinearLayoutManager.HORIZONTAL, mReverseInner));
11620fab45107f043a4856aad2e1fd785b396898616eChris Craik            rv.setRecycledViewPool(mSharedPool);
11630fab45107f043a4856aad2e1fd785b396898616eChris Craik            rv.setLayoutParams(new RecyclerView.LayoutParams(200, 100));
11640fab45107f043a4856aad2e1fd785b396898616eChris Craik            return new ViewHolder(rv);
11650fab45107f043a4856aad2e1fd785b396898616eChris Craik        }
11660fab45107f043a4856aad2e1fd785b396898616eChris Craik
11670fab45107f043a4856aad2e1fd785b396898616eChris Craik        @Override
11680fab45107f043a4856aad2e1fd785b396898616eChris Craik        public void onBindViewHolder(ViewHolder holder, int position) {
11690fab45107f043a4856aad2e1fd785b396898616eChris Craik            mRecyclerView.registerTimePassingMs(5);
11700fab45107f043a4856aad2e1fd785b396898616eChris Craik            // if we had actual data to put into our adapter, this is where we'd do it...
11710fab45107f043a4856aad2e1fd785b396898616eChris Craik
11720fab45107f043a4856aad2e1fd785b396898616eChris Craik            // ... then notify the adapter that it has new content:
11730fab45107f043a4856aad2e1fd785b396898616eChris Craik            holder.mAdapter.notifyDataSetChanged();
11740fab45107f043a4856aad2e1fd785b396898616eChris Craik
11750fab45107f043a4856aad2e1fd785b396898616eChris Craik            Parcelable savedState = mSavedStates.get(position);
11760fab45107f043a4856aad2e1fd785b396898616eChris Craik            if (savedState != null) {
11770fab45107f043a4856aad2e1fd785b396898616eChris Craik                holder.mRecyclerView.getLayoutManager().onRestoreInstanceState(savedState);
11780fab45107f043a4856aad2e1fd785b396898616eChris Craik                mSavedStates.set(position, null);
11790fab45107f043a4856aad2e1fd785b396898616eChris Craik            }
11800fab45107f043a4856aad2e1fd785b396898616eChris Craik        }
11810fab45107f043a4856aad2e1fd785b396898616eChris Craik
11820fab45107f043a4856aad2e1fd785b396898616eChris Craik        @Override
11830fab45107f043a4856aad2e1fd785b396898616eChris Craik        public void onViewRecycled(ViewHolder holder) {
1184570ab881350c836743247140a0953f87acc21c8dChris Craik            if (holder.getAdapterPosition() >= 0) {
1185570ab881350c836743247140a0953f87acc21c8dChris Craik                mSavedStates.set(holder.getAdapterPosition(),
1186570ab881350c836743247140a0953f87acc21c8dChris Craik                        holder.mRecyclerView.getLayoutManager().onSaveInstanceState());
1187570ab881350c836743247140a0953f87acc21c8dChris Craik            }
11880fab45107f043a4856aad2e1fd785b396898616eChris Craik        }
11890fab45107f043a4856aad2e1fd785b396898616eChris Craik
11900fab45107f043a4856aad2e1fd785b396898616eChris Craik        @Override
11910fab45107f043a4856aad2e1fd785b396898616eChris Craik        public int getItemCount() {
11920fab45107f043a4856aad2e1fd785b396898616eChris Craik            return OUTER_ITEM_COUNT;
11930fab45107f043a4856aad2e1fd785b396898616eChris Craik        }
11940fab45107f043a4856aad2e1fd785b396898616eChris Craik    }
11950fab45107f043a4856aad2e1fd785b396898616eChris Craik
11960fab45107f043a4856aad2e1fd785b396898616eChris Craik    @Test
11970fab45107f043a4856aad2e1fd785b396898616eChris Craik    public void nestedPrefetchDiscardStaleChildren() {
11980fab45107f043a4856aad2e1fd785b396898616eChris Craik        LinearLayoutManager llm = new LinearLayoutManager(getContext());
1199d6696c2abea2771acd000c2269cf9113acc6c0a9Chris Craik        assertEquals(2, llm.getInitialPrefetchItemCount());
12000fab45107f043a4856aad2e1fd785b396898616eChris Craik
12010fab45107f043a4856aad2e1fd785b396898616eChris Craik        mRecyclerView.setLayoutManager(llm);
12020fab45107f043a4856aad2e1fd785b396898616eChris Craik        OuterNotifyAdapter outerAdapter = new OuterNotifyAdapter();
12030fab45107f043a4856aad2e1fd785b396898616eChris Craik        mRecyclerView.setAdapter(outerAdapter);
12040fab45107f043a4856aad2e1fd785b396898616eChris Craik
12050fab45107f043a4856aad2e1fd785b396898616eChris Craik        // zero cache, so item we prefetch can't already be ready
12060fab45107f043a4856aad2e1fd785b396898616eChris Craik        mRecyclerView.setItemViewCacheSize(0);
12070fab45107f043a4856aad2e1fd785b396898616eChris Craik
12080fab45107f043a4856aad2e1fd785b396898616eChris Craik        // layout 3 items, then resize to 2...
12090fab45107f043a4856aad2e1fd785b396898616eChris Craik        layout(200, 300);
12100fab45107f043a4856aad2e1fd785b396898616eChris Craik        layout(200, 200);
12110fab45107f043a4856aad2e1fd785b396898616eChris Craik
12120fab45107f043a4856aad2e1fd785b396898616eChris Craik        // so 1 item is evicted into the RecycledViewPool (bypassing cache)
12130fab45107f043a4856aad2e1fd785b396898616eChris Craik        assertEquals(1, mRecycler.mRecyclerPool.getRecycledViewCount(0));
12140fab45107f043a4856aad2e1fd785b396898616eChris Craik        assertEquals(0, mRecycler.mCachedViews.size());
12150fab45107f043a4856aad2e1fd785b396898616eChris Craik
12160fab45107f043a4856aad2e1fd785b396898616eChris Craik        // This is a simple imitation of other behavior (namely, varied types in the outer adapter)
12170fab45107f043a4856aad2e1fd785b396898616eChris Craik        // that results in the same initial state to test: items in the pool with attached children
12180fab45107f043a4856aad2e1fd785b396898616eChris Craik        for (RecyclerView.ViewHolder holder : mRecycler.mRecyclerPool.mScrap.get(0).mScrapHeap) {
12190fab45107f043a4856aad2e1fd785b396898616eChris Craik            // verify that children are attached and valid, since the RVs haven't been rebound
12200fab45107f043a4856aad2e1fd785b396898616eChris Craik            assertNotNull(holder.mNestedRecyclerView);
12218123166eaf758eacc6ba5854c9b29d99f21388d6Chris Craik            assertFalse(holder.mNestedRecyclerView.get().mDataSetHasChangedAfterLayout);
12228123166eaf758eacc6ba5854c9b29d99f21388d6Chris Craik            validateRvChildrenValid(holder.mNestedRecyclerView.get(), 2);
12230fab45107f043a4856aad2e1fd785b396898616eChris Craik        }
12240fab45107f043a4856aad2e1fd785b396898616eChris Craik
12250fab45107f043a4856aad2e1fd785b396898616eChris Craik        // prefetch the outer item bind, but without enough time to do any inner binds
12260fab45107f043a4856aad2e1fd785b396898616eChris Craik        final long deadlineNs = mRecyclerView.getNanoTime() + TimeUnit.MILLISECONDS.toNanos(9);
12270fab45107f043a4856aad2e1fd785b396898616eChris Craik        mRecyclerView.mPrefetchRegistry.setPrefetchVector(0, 1);
12280fab45107f043a4856aad2e1fd785b396898616eChris Craik        mRecyclerView.mGapWorker.prefetch(deadlineNs);
12290fab45107f043a4856aad2e1fd785b396898616eChris Craik
12300fab45107f043a4856aad2e1fd785b396898616eChris Craik        // 2 is prefetched without children
12310fab45107f043a4856aad2e1fd785b396898616eChris Craik        CacheUtils.verifyCacheContainsPrefetchedPositions(mRecyclerView, 2);
12320fab45107f043a4856aad2e1fd785b396898616eChris Craik        RecyclerView.ViewHolder holder = CacheUtils.peekAtCachedViewForPosition(mRecyclerView, 2);
12330fab45107f043a4856aad2e1fd785b396898616eChris Craik        assertNotNull(holder);
12340fab45107f043a4856aad2e1fd785b396898616eChris Craik        assertNotNull(holder.mNestedRecyclerView);
12358123166eaf758eacc6ba5854c9b29d99f21388d6Chris Craik        assertEquals(0, holder.mNestedRecyclerView.get().mChildHelper.getUnfilteredChildCount());
12368123166eaf758eacc6ba5854c9b29d99f21388d6Chris Craik        assertEquals(0, holder.mNestedRecyclerView.get().mRecycler.mCachedViews.size());
12370fab45107f043a4856aad2e1fd785b396898616eChris Craik
12380fab45107f043a4856aad2e1fd785b396898616eChris Craik        // but if we give it more time to bind items, it'll now acquire its inner items
12390fab45107f043a4856aad2e1fd785b396898616eChris Craik        mRecyclerView.mGapWorker.prefetch(RecyclerView.FOREVER_NS);
12408123166eaf758eacc6ba5854c9b29d99f21388d6Chris Craik        CacheUtils.verifyCacheContainsPrefetchedPositions(holder.mNestedRecyclerView.get(), 0, 1);
12410fab45107f043a4856aad2e1fd785b396898616eChris Craik    }
1242570ab881350c836743247140a0953f87acc21c8dChris Craik
1243570ab881350c836743247140a0953f87acc21c8dChris Craik
1244570ab881350c836743247140a0953f87acc21c8dChris Craik    @Test
1245570ab881350c836743247140a0953f87acc21c8dChris Craik    public void nestedPrefetchDiscardStalePrefetch() {
1246570ab881350c836743247140a0953f87acc21c8dChris Craik        mRecyclerView.setLayoutManager(new LinearLayoutManager(getContext()));
1247570ab881350c836743247140a0953f87acc21c8dChris Craik        OuterNotifyAdapter outerAdapter = new OuterNotifyAdapter();
1248570ab881350c836743247140a0953f87acc21c8dChris Craik        mRecyclerView.setAdapter(outerAdapter);
1249570ab881350c836743247140a0953f87acc21c8dChris Craik
1250570ab881350c836743247140a0953f87acc21c8dChris Craik        // zero cache, so item we prefetch can't already be ready
1251570ab881350c836743247140a0953f87acc21c8dChris Craik        mRecyclerView.setItemViewCacheSize(0);
1252570ab881350c836743247140a0953f87acc21c8dChris Craik
1253570ab881350c836743247140a0953f87acc21c8dChris Craik        // layout as 2x2, starting on row index 2, with empty cache
1254570ab881350c836743247140a0953f87acc21c8dChris Craik        layout(200, 200);
1255570ab881350c836743247140a0953f87acc21c8dChris Craik        mRecyclerView.scrollBy(0, 200);
1256570ab881350c836743247140a0953f87acc21c8dChris Craik
1257570ab881350c836743247140a0953f87acc21c8dChris Craik        // no views cached, or previously used (so we can trust number in mItemsBound)
1258570ab881350c836743247140a0953f87acc21c8dChris Craik        mRecycler.mRecyclerPool.clear();
1259570ab881350c836743247140a0953f87acc21c8dChris Craik        assertEquals(0, mRecycler.mRecyclerPool.getRecycledViewCount(0));
1260570ab881350c836743247140a0953f87acc21c8dChris Craik        assertEquals(0, mRecycler.mCachedViews.size());
1261570ab881350c836743247140a0953f87acc21c8dChris Craik
1262570ab881350c836743247140a0953f87acc21c8dChris Craik        // prefetch the outer item and its inner children
1263570ab881350c836743247140a0953f87acc21c8dChris Craik        mRecyclerView.mPrefetchRegistry.setPrefetchVector(0, 1);
1264570ab881350c836743247140a0953f87acc21c8dChris Craik        mRecyclerView.mGapWorker.prefetch(RecyclerView.FOREVER_NS);
1265570ab881350c836743247140a0953f87acc21c8dChris Craik
1266570ab881350c836743247140a0953f87acc21c8dChris Craik        // 4 is prefetched with 2 inner children, first two binds
1267570ab881350c836743247140a0953f87acc21c8dChris Craik        CacheUtils.verifyCacheContainsPrefetchedPositions(mRecyclerView, 4);
1268570ab881350c836743247140a0953f87acc21c8dChris Craik        RecyclerView.ViewHolder holder = CacheUtils.peekAtCachedViewForPosition(mRecyclerView, 4);
1269570ab881350c836743247140a0953f87acc21c8dChris Craik        assertNotNull(holder);
1270570ab881350c836743247140a0953f87acc21c8dChris Craik        assertNotNull(holder.mNestedRecyclerView);
1271570ab881350c836743247140a0953f87acc21c8dChris Craik        RecyclerView innerRecyclerView = holder.mNestedRecyclerView.get();
1272570ab881350c836743247140a0953f87acc21c8dChris Craik        assertEquals(0, innerRecyclerView.mChildHelper.getUnfilteredChildCount());
1273570ab881350c836743247140a0953f87acc21c8dChris Craik        assertEquals(2, innerRecyclerView.mRecycler.mCachedViews.size());
1274570ab881350c836743247140a0953f87acc21c8dChris Craik        assertEquals(2, ((InnerAdapter) innerRecyclerView.getAdapter()).mItemsBound);
1275570ab881350c836743247140a0953f87acc21c8dChris Craik
1276570ab881350c836743247140a0953f87acc21c8dChris Craik        // notify data set changed, so any previously prefetched items invalid, and re-prefetch
1277570ab881350c836743247140a0953f87acc21c8dChris Craik        innerRecyclerView.getAdapter().notifyDataSetChanged();
1278570ab881350c836743247140a0953f87acc21c8dChris Craik        mRecyclerView.mPrefetchRegistry.setPrefetchVector(0, 1);
1279570ab881350c836743247140a0953f87acc21c8dChris Craik        mRecyclerView.mGapWorker.prefetch(RecyclerView.FOREVER_NS);
1280570ab881350c836743247140a0953f87acc21c8dChris Craik
1281570ab881350c836743247140a0953f87acc21c8dChris Craik        // 4 is prefetched again...
1282570ab881350c836743247140a0953f87acc21c8dChris Craik        CacheUtils.verifyCacheContainsPrefetchedPositions(mRecyclerView, 4);
1283570ab881350c836743247140a0953f87acc21c8dChris Craik
1284570ab881350c836743247140a0953f87acc21c8dChris Craik        // reusing the same instance with 2 inner children...
1285570ab881350c836743247140a0953f87acc21c8dChris Craik        assertSame(holder, CacheUtils.peekAtCachedViewForPosition(mRecyclerView, 4));
1286570ab881350c836743247140a0953f87acc21c8dChris Craik        assertSame(innerRecyclerView, holder.mNestedRecyclerView.get());
1287570ab881350c836743247140a0953f87acc21c8dChris Craik        assertEquals(0, innerRecyclerView.mChildHelper.getUnfilteredChildCount());
1288570ab881350c836743247140a0953f87acc21c8dChris Craik        assertEquals(2, innerRecyclerView.mRecycler.mCachedViews.size());
1289570ab881350c836743247140a0953f87acc21c8dChris Craik
1290570ab881350c836743247140a0953f87acc21c8dChris Craik        // ... but there should be two new binds
1291570ab881350c836743247140a0953f87acc21c8dChris Craik        assertEquals(4, ((InnerAdapter) innerRecyclerView.getAdapter()).mItemsBound);
1292570ab881350c836743247140a0953f87acc21c8dChris Craik    }
1293945dc1e8a9ee0b71e6b2454ccb3e61145d2132f5Chris Craik}
1294